Git

From HerzbubeWiki

Jump to: navigation, search

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:


Contents

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


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 .git containing the version control information
  • A bare repository just contains the version control information and no working tree. All the contents of the .git directory 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 am automatically 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

http://gitx.frim.nl/


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.
Personal tools
francesca