Git
From HerzbubeWiki
This page is about Git and how to use it, either locally or within its greater ecosystem (e.g. GitHub).
Cross-links on this wiki:
References
- Homepage
- http://git-scm.com/
- Git Community Book
- http://book.git-scm.com/
- Git User Manual
- http://www.kernel.org/pub/software/scm/git/docs/user-manual.html
- Git - SVN Crash Course
- http://git-scm.com/course/svn.html
- Git Magic
- http://www-cs-students.stanford.edu/~blynn/gitmagic/index.html
- Link collection
- http://git-scm.com/documentation
- man pages
- git, gittutorial, gittutorial-2, gitcore-tutorial, gitglossary
The Server Side
Overview
To operate on Git repositories, the following Debian package needs to be installed:
git-core
Git is a distributed version control system, so there is per se no central server that stores "the" repository. However, it is of course possible to define a workflow where there is a "blessed repository" on a dedicated server machine - this site nicely sums this up. The "The Server Side" chapter tries to get a bearing on how such a blessed repository (or in fact several of them) can be set up.
This page explains various options that can be used to setup a git server:
- git-daemon (provided by the git-daemon-run Debian package) provides read-only access over the dedicated TCP port 9418
- WebDAV provides read+write access over HTTP/HTTPS
- SSH also provides read+write access
- last - and least! - read+write access is possible over regular file system operations
The following is a short discussion of each option:
- File system access: This is from the SourceSafe stone age - I am not even considering this option.
- git-daemon: I don't favour the git-daemon solution because using a specific TCP port has two drawbacks: 1) Another open port makes my server more vulnerable, and 2) it may be impossible to access the repository from a machine behind a proxy.
- WebDAV is attractive because it works over the well-known and widely available HTTP port, and this article provides an overview how to setup WebDAV access. However, the Git community book suggests that performance and reliability of WebDAV are not too good, which is why I am not looking further into this solution for the moment.
- SSH therefore seems to be the only remaining viable option for providing read AND write access. Note, however, that this solution does not provide anonymous read-only access (e.g. for public projects).
Documentation
- gitweb + gitosis
- http://hokietux.net/blog/?p=58 (/dev/null, "A Guide to setting up git, gitosis and gitweb")
- http://vafer.org/blog/20080115011407 and http://vafer.org/blog/20080115011413 (Torsten Curdt, part 2+3 of "From subversion to git")
- gitosis
- http://scie.nti.st/2007/11/14/hosting-git-repositories-the-easy-and-secure-way
- /usr/share/doc/gitosis/README.rst.gz
- /usr/share/doc/gitosis/README.Debian
git-over-SSH (read+write)
Overview
The following Debian package promises to simplify the task of setting up Git-over-SSH:
gitosis
To quote from gitosis' README file:
gitosis aims to make hosting git repos easier and safer. It manages multiple repositories under one user account, using SSH keys to identify users. End users do not need shell accounts on the server, they will talk to one shared account that will not let them run arbitrary commands.
Client side: Create the "admin" identity
Before we start actually doing anything with gitosis, we need to create an identity that we can use in the future to perform administrative work such as adding and removing repositories and users.
Because gitosis uses SSH for user identification, we have to do the following to create the "admin" identy:
- Login to your local machine (NOT the git server!!!) with the user that is going to administrate git repositories and users in the future; you will do these admin tasks locally, NOT on the git server
- Generate a 2048 bit RSA key for use with SSH (unless you already have such a key that you can use for SSH authentication); obviously you should protect the key by entering a nice passphrase.
ssh-keygen -t rsa -f ~/.ssh/admin.id_rsa
As a result, you now have two files: one file that contains the private, passphrase protected key; another file that contains the corresponding public key:
~/.ssh/admin.id_rsa ~/.ssh/admin.id_rsa.pub
Finally, transfer the public key file to the Git server, e.g. to /tmp/admin.id_rsa.pub.
Client side: Working with roles
I prefer to separate my work as an administrator (see above) from the work I do as a normal user. For this reason I create a second RSA key to distinguish the two roles
ssh-keygen -t rsa -f ~/.ssh/patrick.id_rsa
Again, transfer the public key file to the Git server, e.g. to /tmp/patrick.id_rsa.pub
Now your ~/.ssh directory should look like something like this:
-rw------- 1 patrick staff 3311 21 Sep 16:55 admin.id_rsa -rw-r--r-- 1 patrick staff 740 21 Sep 16:55 admin.id_rsa.pub -rw------- 1 patrick staff 3311 21 Sep 16:55 patrick.id_rsa -rw-r--r-- 1 patrick staff 740 21 Sep 16:55 patrick.id_rsa.pub
Finally, do a bit of SSH configuration by adding the following stuff to ~/.ssh/config. The discussion of what these configuration options mean can be found on this wiki on the OpenSSH page.
Host gitosis-admin HostName git.herzbube.ch User gitosis IdentityFile ~/.ssh/admin.id_rsa IdentitiesOnly yes Host gitosis-user HostName git.herzbube.ch User gitosis IdentityFile ~/.ssh/patrick.id_rsa IdentitiesOnly yes
That's it for the moment for the client machine.
Server side: Installation + configuration
gitosis places its stuff in /srv/gitosis. This is undesirable in my case because I do not have a separate filesystem for /srv, instead I have provisioned for applications and services to place their stuff in /var. To fix this, I create /srv as a symlink which "redirects" into /var:
ln -s /var/srv /srv
Now we can install the package gitosis. As a side-effect, the package adds a new system user gitosis and tries to create that user's home directory
/srv/gitosis
The home directory is properly created even though there are two misleading warning messages during the installation process that seem to indicate that directory creation failed. These warnings can be ignored. In newer versions of the package, these error messages should be gone.
Now perform the remaining configuration, which is the initial setup for the gitosis user:
sudo -H -u gitosis gitosis-init </tmp/admin.id_rsa.pub
Among other things, this will do the following
- Add the public key of the "admin" identity to ~/.ssh/authorized_keys, which is a list of trusted keys that can be used to perform an SSH login as the gitosis user (the user will be restricted to certain commands, though)
- Create the directory ~/repositories - this will hold all the git repositories managed through the gitosis user
- Create the administrative git repository ~/repositories/gitosis-admin.git
Client side: Final administrative work
To perform the final administrative work steps, switch back to your local machine and issue the following command:
git clone gitosis-admin:gitosis-admin.git
Due to our SSH setup (see further up), the host alias "gitosis-admin" now causes SSH to use the proper "admin" identity to perform the clone. Afterwards the Git clone is connected to the remote origin "gitosis-admin", which means that when we perform the next git-push or git-pull operation SSH will again use the configuration for the "gitosis-admin" alias, i.e. the "admin" identity.
Note: On my Mac, a GUI dialog pops up where you can enter the passphrase you used to protect the private RSA key; even nicer is that the dialog offers to store the passphrase in your Mac OS X login keychain so that in the future you don't have to enter the passphrase at all. If this is too much convenience, you can always decline.
You now have a clone of the administration repository gitosis-admin.git, in which you can change things to setup the actual production repositories and those users that will have write access. I suggest adding the following things as default to gitosis.conf:
[gitosis] # Disable public access by default; enable access later on explicitly for # each repository that should be visible daemon = no gitweb = no [group gitosis-admin] members = admin writable = gitosis-admin
Add a user
Edit gitosis-admin/gitosis.conf; add the user to an existing group, or make a new group; for instance
[group gods] # On my server I use this group because I always have write access to my own repos # (and I don't host foreign repos) members = patrick # Later when we add repos, we are also adding them to the "writable" option # in this group. # writable = repo1 repo2 repo3 [...]
Goto gitosis-admin/keydir and add the user's public key to the file that is named "<username>.pub"; for instance
cp ~/.ssh/patrick.id_rsa.pub patrick.pub
Commit changes locally
git add . git commit -m "added user patrick & granted access to all repos"
Push changes to the server; the user will now be added on the remote side (e.g. the public key will be added to ~/.ssh/authorized_keys)
git push
Remove a user
TODO
Rename a user
WARNING: It is unclear what the following actions do if a user already has committed changes! I used this only once at the very beginning, to rename the initial administrative user that was created by gitosis-init with the stupid name "root@git.herzbube.ch"
- Change the user's name where it occurs in gitosis-admin/gitosis.conf
- Rename the user's public key (inside gitosis-admin/keydir) with git mv
- Commit & push changes
Change a user's public key
WARNING: It is unclear whether or not this has to be done as admin, or basically as anyone else than the user whose key changes. I tried this once at the beginning, but then I had also tampered with authorized_keys at the same time :-(
Just change the public key file gitosis-admin/keydir/foo.pub, then commit & push the change.
Add a repository
Edit gitosis-admin/gitosis.conf; add the repository to any group that should have any access; also add a section for repo-specific options; for instance
[group foo-developers] members = john mary writable = foo [repo foo] daemon = yes gitweb = yes owner = yourname description = bla bla
Notes:
- "daemon = yes" creates the file git-daemon-export-ok in the repository directory on the server side
- "gitweb = yes" adds the repository to the project list located in the file /srv/gitosis/gitosis/projects.list on the server side
- "owner = yourname" adds the owner's name to /srv/gitosis/gitosis/projects.list; gitweb will always display this name, even if the repository's config file has an "owner = " entry in its "[gitweb]" section; if the owner is not set in gitosis.conf, gitweb will fall back on the repository's config file; if this also does not contain an owner, gitweb will use the GECOS field of the owner of the repository directory on the server side
- "description = bla bla" creates the file description in the repository directory on the server side; this will be picked up by gitweb
Now commit changes locally, then push the changes to the server.
Next, create a new repository locally, and then push it to the server:
mkdir foo cd foo git init git remote add origin gitosis-user:foo.git # do some work, git add and commit files git push --all git push --tags
Alternatively, to "import" an existing repository (even a bare one) into gitosis, just execute the last two commands:
cd foo git remote add origin gitosis-user:foo.git git push --all git push --tags
Note: We used the "gitosis-user" host alias that causes SSH to use the options it has stored for that alias in its config file ~/.ssh/config. See further up for details on how to work with different roles (i.e. admin and user role).
To prepare the repository for visibility in gitweb, and for read-only access with git-over-HTTP, the repository now needs to be prepared on the server side as follows:
- Change permissions of the repository base directory (without this, the repository will not be visible in gitweb, and it will not be clone'able with git-over-HTTP)
chmod 755 /srv/gitosis/repositories/foo
- Enable the post-update hook (this hook runs git-update-server-info which is required to manage some information required by dumb transports such as git-over-HTTP)
cd /srv/gitosis/repositories/foo/hooks mv post-update.sample post-update chmod +x post-update
- Manually run git-update-server-info once
cd /srv/gitosis/repositories/foo/ sudo -H -u gitosis git update-server-info
The repository is now ready to be cloned on the local side:
git clone gitosis-user:foo.git
The above only pulled the "master" branch. To receive additional branches:
git fetch origin BRANCHNAME
git-over-HTTP (read-only)
I make my Git repositories available using the so-called "dumb transport" git-over-HTTP. I have assigned an Apache vhost to this transport that is accessible under http://git.herzbube.ch/. The configuration details are further down in the section "Apache vhost configuration".
A repository for the project "foo" is now cloneable with the following command:
git clone http://git.herzbube.ch/foo.git
Note: The post-update hook must be enabled for each repository that should be cloneable via git-over-HTTP. Specifically, this hook must execute git-update-server-info. It is also necessary to run git-update-server-info manually once after the repository has been created via gitosis. See above in the gitosis section for more information.
Web access
Debian package to install:
gitweb
gitweb is a CGI script that is located in
/usr/lib/cgi-bin/gitweb.cgi
gitweb configuration (esp. /etc/gitweb.conf, but also per-repository configuration) is documented in
/usr/share/doc/gitweb/README
The following things have to be changed in the configuration file /etc/gitweb.conf (note that this file is a fragment of Perl code, so you can put some fancy stuff in there):
$projectroot = "/srv/gitosis/repositories";
$projects_list = "/srv/gitosis/gitosis/projects.list";
# html text to include at home page
$home_text = "/srv/gitosis/gitweb/indextext.html";
# Only export repos that we want to be publicly visible.
# gitosis is creating the "git-daemon-export-ok" file if gitosis.conf
# has the option "daemon = yes" for a repository.
$export_ok = "git-daemon-export-ok";
# Make wider for long project descriptions
$projects_list_description_width = 70;
# Site name
$site_name = "Git trees on git.herzbube.ch";
# Allow for pretty URLs, e.g. http://git.herzbube.ch/gitweb.cgi/acexpander.git
# instead of http://git.herzbube.ch/gitweb.cgi?p=acexpander.git;a=summary
$feature{'pathinfo'}{'default'} = [1];
# List of URLs that can be used to clone a repository. Will be displayed in the
# project summary. Repositories can override this if they have a "cloneurl" file
# in their top-level directory (that file's content is displayed), or if their
# config file contains the gitweb.url setting.
@git_base_url_list = ("http://git.herzbube.ch");
# Place the following items in the /gitweb location so that we can make them
# available in the Apache configuration by publishing the /usr/share/gitweb directory
# (otherwise we would have to create a separate alias for each of these files)
$stylesheet = "/gitweb/gitweb.css";
$logo = "/gitweb/git-logo.png";
$favicon = "/gitweb/git-favicon.png";
Gitweb is made available under the Apache vhost http://git.herzbube.ch/gitweb.cgi. The configuration details are further down in the section "Apache vhost configuration".
Write the header file /srv/gitosis/gitweb/indextext.html (the following text is based on what can be found at git.kernel.org):
<p> To clone one of these trees, install <a href="http://git-scm.com/">git</a>, and run: <blockquote> <code>git clone http://git.herzbube.ch/</code> + project path. </blockquote> The clone URL for a specific project is available on the summary page of that project. </p> <p> For more information about <a href="http://git-scm.com/">git</a>, see an <a href="http://git-scm.com/documentation">overview</a> of available documentation, the <a href="http://www.kernel.org/pub/software/scm/git/docs/gittutorial.html">tutorial</a> or the <a href="http://www.kernel.org/pub/software/scm/git/docs">man pages</a>. </p>
Apache vhost configuration
# --------------------------------------------------------------------------------
# git.herzbube.ch
# --------------------------------------------------------------------------------
<VirtualHost *:80>
ServerName git.herzbube.ch
ServerAdmin webmaster@herzbube.ch
ErrorLog /var/log/apache2/herzbube.ch/error.log
CustomLog /var/log/apache2/herzbube.ch/access.log combined
DocumentRoot /srv/gitosis/repositories
Alias /robots.txt /var/www/herzbube.ch/git.herzbube.ch/robots.txt
# Make the gitweb CGI script available directly under the vhost root
Alias /gitweb.cgi /usr/lib/cgi-bin/gitweb.cgi
# Make the auxiliary gitweb files (stylesheet, etc.) available to the CGI script.
# The file locations are configured in /etc/gitweb.conf.
Alias /gitweb/ /usr/share/gitweb/
<Directory /srv/gitosis/repositories/>
Allow from all
</Directory>
<Directory /usr/share/gitweb/>
Allow from all
</Directory>
</VirtualHost>
# --------------------------------------------------------------------------------
# SSL Host
# --------------------------------------------------------------------------------
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName git.herzbube.ch
ServerAdmin webmaster@herzbube.ch
ErrorLog /var/log/apache2/herzbube.ch/error.log
CustomLog /var/log/apache2/herzbube.ch/access.log combined
DocumentRoot /srv/gitosis/repositories
Alias /robots.txt /var/www/herzbube.ch/git.herzbube.ch/robots.txt
Alias /gitweb.cgi /usr/lib/cgi-bin/gitweb.cgi
Alias /gitweb/ /usr/share/gitweb/
<Directory /srv/gitosis/repositories/>
Allow from all
</Directory>
<Directory /usr/share/gitweb/>
Allow from all
</Directory>
SSLEngine on
SSLCertificateFile /etc/ssl/certs/herzbube.ch.crt
SSLCertificateKeyFile /etc/ssl/private/herzbube.ch.key.unsecure
SSLCertificateChainFile /etc/ssl/certs/cacert.org.certchain
SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown
</VirtualHost>
</IfModule>
The Local Side
Configuration file
The git configuration file contains a number of variables that affect the behaviour of git commands. A non-comprehensive list is available on the man page of git config. Configuration files exist on three levels:
- .git/config file for each repository is used to store the information for that repository
- $HOME/.gitconfig is used to store per user information
- The file /etc/gitconfig can be used to store system-wide defaults
To write to a configuration file, use the command
git config <section.variable> <value>
Using the --global argument writes to the user specific configuration file, --system writes to the system-wide defaults. With no argument, the repository specific configuration file will be written to.
Important settings, or settings that I like to have are:
- User name and email address in ~/.gitconfig. These are used for things like git-commit
git config --global user.name "Patrick Näf" git config --global user.email herzbube@herzbube.ch git config --global user.signingkey 3FF38573
- User specific gitignore patterns:
git config --global core.excludesfile "$HOME/.gitignore"
- Use colors for git-status and git-diff
git config --global color.status true git config --global color.diff true
Working Tree vs. Working Copy
In Subversion, users check out one revision from the central, shared repository into a directory that is then called the "working copy". The working copy therefore contains one, and only one, revision (this is simplified, but I need it to be so that I can make a useful comparison to git).
In git, the "working copy" is called "working tree". However, the directory space that contains the working tree at the same time also stores the git repository. As opposed to Subversion where each directory has its own .svn directory, the git working tree has exactly one .git directory in its root folder: That folder contains the entire git repository with all the branches.
A single git repository can track an arbitrary number of branches, but your working tree is associated with just one of them (the "current" or "checked out" branch), and HEAD points to that branch (= the tip, or head, of that branch).
git init: Administration of repositories
Create a new non-bare repository in .git in the current directory:
git init
(set the GIT_DIR variable to create the repository in a directory named differently, or in a different location; by default, GIT_DIR points to .git)
Create a new bare repository:
mkdir repodir cd repodir git init --bare
Difference between bare and non-bare repositories:
- A non-bare repository has a working tree and a hidden directory
.gitcontaining the version control information - A bare repository just contains the version control information and no working tree. All the contents of the
.gitdirectory are placed in the main directory itself - Only bare repositories can be the target of a push
- The purpose of bare repositories is for having a central (usually remote) repository that a number of people can push to
- To convert a bare into a non-bare repository: Clone the bare repo, then delete the original
- To convert a non-bare repo into a bare one:
git clone --bare -l /path/to/non/bare/repo /path/to/new/bare/repo
Ignoring files
Files generated by a build process (e.g. object files), or by the operating system (.DS_Store), or whatever, should not be versioned. git ignores those files if you tell it their names. You do so by specifying so-called gitignore patterns, either on the command line of certain git commands, or in so-called gitignore files.
Patterns are read from various sources in the following order (this list is taken almost verbatim from the man page of gitignore(5):
- Patterns read from the command line for those commands that support them.
- Patterns read from a .gitignore file in the same directory as the path, or in any parent directory, with patterns in the higher level files (up to the root) being overridden by those in lower level files down to the directory containing the file. These patterns match relative to the location of the .gitignore file. A project normally includes such .gitignore files in its repository, containing patterns for files generated as part of the project build.
- Patterns read from $GIT_DIR/info/exclude.
- Patterns read from the file specified by the configuration variable core.excludesfile; you would set that variable by saying, for instance:
git config --global core.excludesfile "$HOME/.gitignore"
Example for $HOME/.gitignore:
.DS_Store .svn/
Complaining about whitespace
Almost all editors I have encountered add unnecessary whitespace at the end of a line in certain situations, usually when they try to help with line indentation. Some editors have an option that removes trailing whitespace when a file is saved, but most do not. Fortunately, git has support for checking for common whitespace problems.
The option core.whitespace in ~/.gitconfig allows to define which whitespace problems should be noticed whenver a whitespace check is run. The default already enables checking for the most important problem, blank-at-eol, so usually you will not have to modify your .gitconfig file.
To enable whitespace problem checks in a repository (local or remote), you can enable the default pre-commit hook:
cd /path/to/repo cd .git/hooks mv pre-commit.sample pre-commit
git add: Adding files/directories or making changes to existing files/directories
Files:
- a new file or directory needs to be added using git add
- a file whose content has changed needs to be added using git add
- when git add is run it looks at the file's current content and determines what needs to be added; the content is said to be staged for inclusion in the next commit
- when a file's content changes after git add has been run, git add needs to be run AGAIN because the new content is NOT automatically staged for inclusion
Directories:
- it seems that a new empty directory can NOT be added using git add; I was unable to do this, and so far I did not find information about this special (mis)behaviour of git
- if a directory contains files, it is sufficient to git add the directory; the operation will then recursively iterate over the files; if another file is later added to the directory, the new file is NOT automatically staged for inclusion - git add needs to be run AGAIN
git mv: Renaming or moving files/directories
Existing files and directories can be renamed or moved to a new location using git mv. The result must still be committed.
Note: If a directory becomes empty due to a move operation, the next commit will remove it from source control. If people pull the change, the directory will disappear on their side, too. If a puller has a local change in the directory, the directory will not be deleted, though.
git rm: Removing files/directories
Existing files can be removed using git rm. The result must still be committed.
Note 1: Directories are not normally removed, unless the -r option is specified.
Note 2: If a directory becomes empty due to a remove operation, the same rules apply as with git mv.
git status/diff: See local changes
git status prints out
- which changes will be committed next time git commit is run
- which changes have not been staged for committing yet; Note: Empty directories do not appear here; directories only appear if they have at least one file inside
git diff prints out
- the changes that have not been staged yet
- in other words: the difference between the working tree and the index
git diff --cached prints out
- the changes that have been staged and will be included in the next commit
- in other words: the difference between the index and the HEAD of the current branch (usually "master")
git reset: Undo changes
git reset can be used to undo all sorts of changes, including destroying commits already made. The command is rather dangerous and you must know what you are doing or you may damage your repository...
Unstage all files that have been staged with git add, keeping all local changes:
git reset
Unstage a single file:
git reset foo.c
Throw away all local changes that have not been committed yet (this is useful after a merge, e.g. to throw away the merge results because of too many conflicts):
git reset --hard
Undo the last commit (because it was incomplete, or just not quite right), make additional changes, then redo the commit:
git reset --soft HEAD^ # leaves working tree; the old head is stored in .git/ORIG_HEAD <do some changes> git add . git commit -c ORIG_HEAD # redo commit, re-using the previous commit message (can still be edited)
git stash: Temporarily stash all local changes
Sometimes one needs to interrupt the current work and do something else. A useful workflow is this:
- Temporarily stash all local changes and revert to a clean working tree
- Do something else, probably commit
- Get back the changes that have been stashed away and resume the original work
The commands for this are:
git stash # do some work git commit git stash apply # the stash is kept git stash pop # the stash is applied and then thrown away
It is possible to have multiple stashes. Useful commands:
git stash blablabla # create new stash with a message
git stash list # list all stashes
git stash show -p stash@{1} # display diff (-p = patch format) between named stash and its original parent
git stash pop stash@{1} # apply the named stash
git stash drop stash@{1} # throw away the named stash
git clear # throw away all stashes
git commit: Make changes to the repository
Commit staged changes:
git commit -m "bla bla bla"
Notes:
- Author name and email address are taken from ~/.gitconfig.
- As a convenience, the -a option can be used to automatically stage files that have been modified and deleted. New files are not staged, though.
To fix the commit message of the last commit:
git commit --amend
To add another file to the last commit, or make additional changes to a file already in the commit:
git add <file> git commit --amend
git show: Display information about commits and other stuff
Note: The man page for git-show is totally incomplete, for instance it does not show the --name-only option :-(
Display the files that changed in a commit:
git show --name-only 356da73
Also display diffs:
git show 356da73
git tag: Working with tags
git tag -s -m "tagging release 0.8.5" 0.8.5 356da73
- Creates a tag named "0.8.5"
- The tag refers to commit object 356da73
- The message specified by -m is associated with the tag
- Using GnuPG, the tag is PGP-signed, using the PGP key that matches the committer's email address; although I have not formally researched this, I presume that the committer's email address would be the one that has been defined in ~/.gitconfig under the option "user.email".
To use a specific PGP key, i.e. not the default one that matches the committer's email address, one has to set the "user.signingkey" option, either in the repository's configuration file, or the global configuration file. For instance:
git config --global user.signingkey 3FF38573 # use the key ID
Set the GIT_COMMITTER_DATE environment variable to create a tag with a given date instead of the current date (useful to backdate date, e.g. after populating a Git repository with content from another SCM). For instance:
GIT_COMMITTER_DATE="2009-06-14 12:58:50" git tag -s -m "tagging release 0.3" 0.3 ed598d1f3d6fac50b67daac2c191798c451cc962
Delete an existing tag:
git tag -d 0.1
Note: If the tag has already been pushed to the server, this must be done both on the client and on the server (a tag-delete cannot be pushed). THIS IS NOT RECOMMENDED!!! See the man page for git-tag for details.
List all tags that exist in the repository:
git tag
Verify the signature of a tag:
git tag -v 0.1
Find tags that contain the given commit:
git tag --contains 356da73
git log: Information on the history
This somewhat looks like what I am used from svn log:
git log --name-status
Another abbreviated version of the history:
git log --stat --summary
Commits since v2.5 which modify Makefile:
git log v2.5.. Makefile
Commits between v2.5 and v2.6:
git log v2.5..v2.6
Commits made on the current branch (which is not master), all the way back since it was branched
git log ^master HEAD git log master..HEAD # equivalent, but apparently the more common shorthand git log HEAD --not master # equivalent, but here it's important to place --not at the end because --not affects all of the subsequent arguments (not just 1)
git clone
Get a copy of a remote/upstream repository:
git clone /path/to/repo
Notes:
- The copy is created in the current directory in a folder named "repo"
- The same branch is checked out that is currently active in the remote/upstream repository
- The origin is set to the remote/upstream repository; it is said that we are tracking that remote/upstream repo; the origin is later going to be used by pull and fetch commands
git pull
Pull all changes in all branches from the remote repository that is our origin, into the local repository:
git pull master
Notes:
- The changes are not only pulled, but the changes in the remote current branch are also merged immediately into the working tree
- It is therefore a good idea to commit local changes before pulling
- In addition, the local current branch should somehow match the remote current branch
- Instead of pulling, which means an immediate merge, one could first do a "fetch" and then inspect the remote changes
git fetch /path/to/repo master git log -p HEAD..FETCH_HEAD # shows remote changes since histories forked git log -p HEAD...FETCH_HEAD # shows remote AND local changes since histories forked
git fetch
To fetch a remote branch:
git fetch origin work-for-0.4:work-for-0.4
Notes:
- "origin" is an alias for a repository URL that has previously been set with "git remote"
- on the left-hand side of the ":" is the name of the remote branch
- on the right-hand side of the ":" is the name under which the branch should be stored locally; I have not found out how abbreviate this so that git automatically uses the remote name locally (another one of git's many mysteries)
git push
Push local changes in the currently checked out branch into the remote repository that is the origin:
git push
Push all tags:
git push --tags
Notes:
- This is useful for a workflow that uses a shared repository
- This can also be used to update a blessed repository
- For a complete sync, both push commands must be run - the normal push does not sync tags, and vice versa
git branch: List/create/delete branches
List all branches that exist:
git branch
Create a new branch (but don't check it out). The first example splits off at the head of the currently checked out branch, the second splits off at the named commit.
git branch work-for-0.5 git branch work-for-0.5 7a8c9912
Rename a branch:
git branch -m old new
Delete a branch:
git branch -d mybranch
git checkout: Switch working tree
Switch to another branch
Change the working tree to point to a different branch:
git checkout newbranch
Create a new branch and check it out immediately. The first example splits off at the head of the currently checked out branch, the second splits off at the named branch.
git checkout -b newbranch git checkout -b newbranch oldbranch
If you have local changes, the checkout command will fail unless you specify one of the following:
git checkout --merge newbranch # merge changes git checkout -f newbranch # discards changes
Notes:
- The merge works regardless of whether the changes have been added to the index or not
- Conflicts are not reported in any way, though, you have to detect these by yourself :-(((( A conflicted file will contain markes such as this one: "<<<<<<< master:doc/ChangeLog"
- Files that are present but matched by .gitignore are retained - it has not been verified yet if this applies to any file that is not version-controlled
Switch to an earlier commit
To go back in history and get the repository's state as it was in a specific commit:
git checkout 7a8c9912
After this you are no longer on a branch, this can be verified as follows: nargothrond:~/Documents/dev/littlego --> git branch
* (no branch) master
To return to HEAD of the master branch:
git checkout master
git merge: Merge changes from another branch
Important: In other version control systems (Subversion is a notable example) a merge between branches generates a diff between the two branches, the diff is then applied to the target branch, and you commit the merge as one big, gigantic commit. A merge in Git works differently! When a merge is started, the commits from the source branch are automatically "replayed" into the target branch, until either a conflict occurs or there are no more commits to merge. This behaviour is very startling if you encounter it for the first time, but it is also incredibly powerful: No more fiddling around with individual commits, just one command and you are done with the merge and ready to move on to the next task.
Merge the current HEAD and working tree with another branch:
git merge otherbranch
Note: Recovery from a merge with too many conflicts is possible with git reset.
git remote: Manage remote ("tracked") repositories
Show a list of existing "remotes", i.e. remote repositories whose branche are tracked in the local repo (the "-v" option tells git to be verbose and also list the remote URL):
git remote -v
Add a new remote named "foo" that points to the repository at the given URL
git remote add foo git://linux-nfs.org/pub/linux/nfs-2.6.git git remote add foo gitosis-user:scjd.git # the server gitosis-user is defined in .ssh/config
Once a remote repository has been set, its content can be fetched: git fetch foo # fetch all branches
Rename a remote
git remote rename old new
Remove a remote
git remote rm foo
Conflict handling
Note: Stuff in this chapter has been extracted from the man page of git merge.
Throw away all local changes (e.g. too many conflicts):
git reset --hard
Show different versions of files that are in conflict (usually 3 versions: 1 = common ancestor, 2 = HEAD version, 3 = remote version):
git ls-files -u
Show each one of these three versions of a conflicted file:
git show :1:filename # common ancestor git show :2:filename # HEAD version git show :3:filename # remote version
Run graphical merge tool (on Mac OS X usually launches FileMerge via the opendiff cmdline utility):
git mergetool
Generating and applying patches
A good overview is this: http://ariejan.net/2009/10/26/how-to-create-and-apply-a-patch-with-git/
Generating patches
Generate a patch that contains one commit A only:
git format-patch -1 A
Note: The resulting file is placed in the current working directory and named after the first line of the commit message. For instance:
0001-final-changes-for-release-0.1.patch
Write the above patch to a different output directory:
git format-patch -1 A -o /tmp/patchdir
Generate a series of patches from commit A+1 up to HEAD:
git format-patch A -o /tmp/patchdir
Generate a series of patches from commit A up to HEAD:
git format-patch A^ -o /tmp/patchdir
Generate a series of patches from the beginning of history up to commit A:
git format-patch A -o /tmp/patchdir --root
Generate a series of patches from commit A to commit B:
git format-patch A^..B -o /tmp/patchdir
Applying patches
Get an overview of what is in the patch:
git apply --stat /path/to/patch
Test whether the patch applies cleanly. If no errors are printed, the patch applies cleanly.
git apply --check /path/to/patch
Apply the patch (without committing):
git apply /path/to/patch
Apply the patch and generate a "Signed-off-by" tag in the commit message. This tag is read by Github and others to provide useful info about how the commit ended up in the code.
git am --signoff /path/to/patch
Notes:
- Working in a single-committer environment, I find the generated tag not so useful
- Very useful, however, is that
git amautomatically uses the comment that is part of the patch file to generate a commit message AND even performs the commit for you. This allows to apply patches very fast - if they apply cleanly - To recover from a patch that did not apply, use this command
git am --abort
Convert sub-directory into repository of its own
The following command "rewrites" a repository to look as if sub-directory foo has been its project root, and discards all other history. This effectively turns the sub-directory into a repository of its own. I don't pretend to understand in the least what this command does, but I got the magic from this stackoverflow.com question.
git filter-branch --subdirectory-filter foo -- --all
Important note: This action drastically modifies the repository!!! Perform this only on a clone, or push all changes first, or make a backup first.
Depending on how long the old repository has been in use before it was rewritten, the newly rewritten repository still contains quite a bit of overhead and hidden cruft from the old repository. Although there probably are other and better ways to do this, my way of cleaning up is to clone the newly rewritten repository.
The following is a full transcript of how I extracted my HTB repository from the Tools repository:
cd /tmp git clone gitosis-user:tools.git # get a fresh copy of the repository to convert cd tools git filter-branch --subdirectory-filter htb -- --all cd .. git clone file:///tmp/tools htb du -sh tools htb 880K tools 524K htb
Further cleanup steps:
- Get another fresh clone of the original repository and remove the sub-directory that has been extracted. I do this with a simple
git rm -r foo. This leaves the sub-directory's history intact, but I am sure there is a way to destroy the history as well. - Add the newly rewritten repository to gitosis. The only problem here is that the rewritten repository already has a remote that is still connected to the original repository - this can be easily resolved by removing the remote first:
cd /tmp/htb git remote rm origin git remote add origin gitosis-user:htb.git
Diagnostics & error recovery
Check repository integrity:
git fsck --full
If a packed archive exists (pack files are normally located in GITDIR/objects/pack), extract the single objects within the pack and write them to the current repository (note: a pack file always has an accompanying .idx file whch probably must be present as well):
git unpack-objects </tmp/foo.pack
To see the type of an object (the example object would be located in GITDIR/objects/6c/8cae4994b5ec7891ccb1527d30634997a978ee):
git cat-file -t 6c8cae4994b5ec7891ccb1527d30634997a978ee
To see the content (pretty-printed) of an object with ID ID:
git cat-file -p ID
To see the content of a tree object with object ID T (is equivalent to the "cat-file" command if the object is a tree):
git ls-tree T
To see the content of a tree object that belongs to commit with object ID C:
git ls-tree C
To recursively list the content of a tree object (note: it is important to specify the -r option in front of the tree object ID, otherwise git will interpret the option as a pattern to match):
git ls-tree -r 6c8cae4994b5ec7891ccb1527d30634997a978ee
Recreate a tree object from ls-tree formatted text:
cd ~/git/backups/foo.git git ls-tree 6c8cae4994b5ec7891ccb1527d30634997a978ee >/tmp/lstree.txt cd ~/git/recovery/foo.git git mktree </tmp/lstree.txt
Show information about a commit with object ID C:
git show C
Recreate a commit object from tree with object ID T, linking it to the parent commit object with object ID C (note that author name, email and date are taken from environment variables, or from configuration file items):
git commit-tree T -p C </tmp/changelog
Print out the object ID that a file would get if it were made into a blob:
git hash-object <doc/README
Recreate a blob object (and print its ID):
git hash-object -w <doc/README
Migrating from Subversion
- git svn is definitely not usable for migrating a Subversion repository
- git svnimport was removed from git-core in 1.5.4 in favor of git svn (the perl script still exists in the source tree, but is unsupported)
- since I have no other tool, at the moment I use a combination of manual commands to hopefully achieve the proper result:
- start with checking out revision 1 of the entire Subversion repository; this might give you the bare bones "trunk", "branches" and "tags" directory structure
- update the Subversion working copy to revision 2, etc., until you get to the first change that you want to record as a commit in the git repository
- now change directory to "trunk" and issue git init, followed by git add . and git commit -m "<message>"
- go on in in this way (svn update, followed by git add/commit) until your svn updates fetch either a branch or a tag
- if the svn update fetches a branch: TODO
- if the svn update fetches a tag
- if the tag is made from the trunk, go to the trunk and say git tag -s <tag> <SHA-160>; the SHA hash is the one of the most recent commit in the trunk
- if the tag is made from a branch: TODO
Information on specific clients
TODO
GitHub
Overview
Signing up with GitHub provides a free (for open source projects) public place to host Git repositories. A few general notes:
- To be allowed to commit, an account needs to be associated with one or more public SSH keys
- An account also has a secret API token. When the account password is changed, the API token also changes. The API token is used by "some tools" that connect to GitHub without SSH. Which tools use this is currently unknown.
Local Git configuration
The following commands will add some entries to your ~/.gitconfig. Note that the API token must be kept secret!
git config --global github.user herzbube git config --global github.token 123abc456def
Cloning a GitHub repository
git clone git@github.com:herzbube/reponame.git
Add a patch to a project where you don't have write access
- Fork the project
- Clone the project locally
- Make changes, commit & push back to GitHub
- On GitHub navigate to the forked project, then at the top of the screen click the button "Pull Request" (not the link "Pull Requests" which will display requests for your forked repository)
- GitHub help on pull requests has all the details
- Once the request has been sent, an issue will be created for the target (original) repository. The commits that were included in the pull requests are attached to the issue.
