A Git cheatsheet

    This is a cheat sheet for using git as your personal repository, while still maintaining the master code in svn. Some of these recommendations will change if we adopt git as the team wide tool.

    Getting Git

    Debian/Ubuntu

    apt-get install git git-core
    

    Redhat/Centos

    yum install git

    (must have epel yum repo set up)

    Windows

    1. Get MSYSGIT: http://code.google.com/p/msysgit
    2. Install
      1. If presented with the option to use TortoisePlink instead of openssh for ssh, choose the option (the enables pageant use)
      2. For line ending selection, choose the last option "commit line endings as they are."
    3. Get Bash as a bonus!

    Best windows gui option is http://code.google.com/p/gitextensions/, but i'd still recommend getting familiar with doing as much as possible from the command line. You'll have a better understanding of how things work and most of the time it will be faster.

    OS X

    1. Get git-osx-installer: http://code.google.com/p/git-osx-installer/
    2. Install
    3. Get GitX as graphical UI: http://gitx.frim.nl/

    Global config

    Windows

    Files you need to create or edit in your user directory.

    .gitconfig

    This configuration assumes that you installed the perforce client to get their excellent 3-way merge client, p4merge.exe

    [core]
            excludesfile = <absolute path to user dir>/.gitignore
    [diff]
            external = <absolute path to user dir>/winmerge
    [merge]
            keepBackup = false;
            tool = p4merge
    
    [mergetool "p4merge"]
            keepTemporaries = false
            trustExitCode = false
            keepBackup = false
            cmd = p4merge.exe $BASE $LOCAL $REMOTE $MERGED
            path = C:/Program Files/Perforce/p4merge.exe
    
    [user]
            name = <full name>
            email = <email address>
    

    .gitignore

    .svn/
    [Dd]ebug/
    [Rr]elease/
    Signed Debug/
    Signed Release/
    obj/
    *.ncb
    *.suo
    _ReSharper.*/
    *.pdb
    *.snk
    *.user
    

    winmerge

    This is a shell script to invoke WinMerge from the command line with git diff

    #!/bin/sh
    
    # diff is called by git with 7 parameters:
    # path old-file old-hex old-mode new-file new-hex new-mode
    
    "C:/Program Files/SourceGear/DiffMerge/DiffMerge.exe" "$2" "$5" | cat
    

    Setting up a local repo

    To turn an existing directory tree into a git repository:

    git init
    

    Now the current directory is an empty git repository. If you are using MSYSGIT, the bash status information should be have (master) appended, indicating that you are in a git repository on the master branch.

    However, there is nothing in the repository, since you haven't told git to add any files. Before you add files to the repository, you should make sure that you have configured your global ignore files. In addition, if you are using this purely as a personal repository to keep your WIP safe, you can probably more project specific files to be ignored. With Dream or DekiWiki, it's generally safe to also ignore bin/ and dist/ directories, since they are local build artifacts, although you may want to keep redist/ in case you plan to sync this repository on another machine and don't want to have to do a full rebuild to keep your redist dependencies up to date with your WIP code changes.

    To locally ignore additional files, simply create a file called .gitignore which will affect the current directory tree recursively. The simplest way is with cat:

    $ cat >.gitignore
    dist/
    bin/
    [Press Ctrl-D to save file]
     
    

    Where the $ is command line prompt, not part of the command.

    Assuming that you have set up your various ignore options to skip all the files you don't want to persist, you can add the files to the repository like this:

    git add .
    

    This will recursively add all files starting at the current directory, i.e. at the dot.

    Adding files, merely stages them for a commit, so next commit the files like this:

    git commit -m'import'
    

    Where the -m is the command line shortcut for the commit comment.

    A workflow for keeping in sync with SVN trunk

    Since git is meant to facilitate easy branching for parallel development of multiple features, it is best to always keep your master clean of local changes and synced with SVN, and sync your WIP branches periodically and only merge your code back to master after you've committed it to SVN.

    Create your short-term WIP branch, dev

    Assuming you just created a new repository and have imported everything into master, you should create your development branch dev:

    git checkout -b dev
    

    Checkout is used to switch branches (all switching is done in place, i.e. you don't need directories per branch). The -b option creates the branch being switched to.

    This operation will complete immediately and you are ready to start making your short-term modifications to trunk.

    Reverting local modifications

    While git does have a revert command it does not refer to local changes, as does the svn version. To revert changes to the last committed change you use checkout again:

    git checkout .
    

    Will remove all local changes. You can also do checkout of individual files.

    Staging files for commit

    In git, files are not committed, unless they are staged first. This means that even if git know about a file you have modified, a commit will not affect that file until it has been explicitly added to commit stage. Adding files to the stage can be done in a number of ways, as will be shown below, but generally the mechanism for an existing file is the same as a new file, i.e. git add.

    At any time you can see what git knows about the current state of your repo with git status. Let's assume that you have three files in your repo: new, a file that git has not seen before, unstaged, a file that git is tracking and has changes, but is not staged, and staged, a file that git is tracking, has changes and has already been added to the stage. The output of git status would look like this:

    # On branch dev
    # Changes to be committed:
    #   (use "git reset HEAD <file>..." to unstage)
    #
    #       modified:   staged
    #
    # Changed but not updated:
    #   (use "git add <file>..." to update what will be committed)
    #   (use "git checkout -- <file>..." to discard changes in working directory)
    #
    #       modified:   unstaged
    #
    # Untracked files:
    #   (use "git add <file>..." to include in what will be committed)
    #
    #       new
    

    A git commit now would only commit staged. The easiest way to stage all files and commit them, including all untracked files is to use the same add as used for the import:

    git add .
    git commit -m'something has changed' 
    

    If you do not have untracked files, there is a shorter way to stage all modified files and commit them:

    git commit -a -m'comitting all tracked, modified files'

    Commit often

    One of the motivations behind keeping your own git repository in parallel with SVN is that you can keep a revision history of your work in progress, whether that code works or not, since it's just your own personal undo stack. So, get in the habit of running git commit on dev any time you feel like you've made a non-trivial amount of changes.

    Syncing master to SVN

    Periodically, preferably daily, you should sync master to SVN so that you pick up breaking changes others are committing sooner rather than later. To switch back to master for the sync, you cannot have any local changes pending for dev (i.e. commit your changes on dev or stash them -- stash will be covered below).

    git checkout master

    Now that you are on the master, do an svn update and then commit that changes:

    git add .
    git commit -m'syncing to r12345'
    

    Syncing dev to master

    Now that the master is in sync with SVN, pick up the changes into your own dev branch. Since git tracks merge history, taking care of this while you are developing, avoids a larger and likely more complex merge when you are ready to commit it to the master.

    git checkout dev
    git merge master
    

    No merge conflicts

    If no merge conflicts were encountered, you are done. The applicable changes were simply applied to your branch including full revision history (take a look at git log to see the details) and no commit is required.

    Dealing with merge conflicts - mergetool

    If a merge conflict did occur, you must clear the errors manually to proceed. The simplest way to handle this scenario is to have a 3-way merge tool configured (p4merge configuration was shown above) and to run the mergetool:

    git mergetool
    

    This will interactively walk you through the conflicts and lets you resolve each in turn. Once you accept the merge it adds the merged file to the staging area for commit.

    Dealing with merge conflicts - ours and theirs

    Sometimes mergetool is not appropriate. Maybe the files are binary and/or you already know you want to accept your or their version. When go git status you will see the unmerged paths liked under this header:

    # Unmerged paths:
    #   (use "git add/rm <file>..." as appropriate to mark resolution)
    #
    #       both modified:      foo

    This means the file is not considered merged, however git does not detect when you have resolved the conflict. I.e. if you were to manually edit the file to remove the merge markers, the git status would not have changed. After merging, each file needs to be be added via git add. Of course if you resolve all files, you can just use git commit -a -m'...' to automatically add them. The only drawback is that if you have a lot files to merge you will want to add them to staging as you go, since you won't have any visual indication which files are already merged. In addition, if you want to run mergetool on the remaining files, you will want to git add the manually merged files, otherwise mergetool will ignore your merges.

    To take your or their changes to a conflicted files you can simply use git checkout --ours <list of files> or git checkout --theirs <list of files> respectively. Same as with manual merging, you still have to call git add on those files to mark them as resolved.

    Stashing your local changes

    As noted above, local changes to tracked files prevent you from switching branches. While commits are cheap and add to your revision history, maybe you need to switch to another branch for a hotfix, or want to sync the master to svn, but you don't know if there are even changes. In both those cases, you may not deem your local changes worthy of their own commit.

    For these types of scenarios, git allows you to squirrel away your local changes (note: it does not affecte untracked files) so that you can checkout another branch:

    git stash
    

    Your branch will not be clean of any modifications and you could make other local changes and commit them, or switch to another branch and work there. When you are done and want to resume that work in progress, simply unstash them with:

    git stash apply
    

    and resume working on those changes.

    Some caveats about git stash:

    • The stash is global stack
      • If you do another stash it will be stored on top of the first one
      • Applies will unstash in reverse order of stash
      • You can manually specify which stack index to apply to change apply order
    • You can stash on one branch and apply on another, so you need to keep track of where you stashed things
    • You cannot apply a stash when there are local changes present
    • An apply is subject to a merge, so stashing and applying after commit of new changes may require merge conflict resolution
    • The stash is local only, so if you push changes for remote backup, your stash will not be backed up
      • This also means that unlike commit history, you cannot pick up your stash on another machine

    Stashing is a powerful concept, but the nature of the stack means that trying to do anything complicated can easily lead to confusion about where what changes are. In most cases, actually committing the changes is the better way to handle things.

    Putting your work onto SVN trunk

    When you are ready to commit your work to trunk, make sure you've first synced master to svn and merged those changes into your work branch. It is generally safe to skip getting master synced and merged to your branch at this stage unless:

    • Your branch is behind the master (i.e. has not merged the most recent master changes
    • You are only committing some of the local changes
    • You are not reasonably certain that you won't get sidetracked from this review & commit and have to commit the work of another branch to trunk first,

    Note: If you are not checking in all local changes, it's usually a sign that you were developing two unrelated features on a single branch. Since you have cheap branching and merging at your disposal, you should really consider doing these on separate branches.

    Once everything on your work branch is ready, perform your code review and svn commit your changes to trunk as usual. After the changes are committed, you must merge the changes back to master:

    git checkout master
    git merge dev
    

    This should happen without any merge conflicts, since master shouldn't have any changes that haven't previously been merged.

    Note: The reason you svn commit from your dev branch, rather than merging to the master and then committing is that your review will likely introduce some changes, which should be part of the dev branch's commit history.

    Continuing to work on your dev branch

    Since merge history is tracted, merging back and forth between branches doesn't create ugly merge conflict messes like svn does, so once you have merged your changes to master, it's perfectly safe to continue new work on your dev branch and continue the cycle of syncing master and merging onto your dev branch.

    Remote back-up of your repo

    Up until now, all work was completely local. This meant that everything was nice and fast, but it also means that should something happen to your machine, all that revision history and work in progress could be lost.

    Unlike svn, git doesn't have a central server. Any repo can push into or pull from another repo. This means that we can simply set up a repo on another machine and push our changes there periodically for safe keeping.

    Setup msysgit with pageant (Windows only)

    Unlike linux, windows ssh-agent support for the command line isn't so simple. The common agent setup uses Pageant.exe which works in conjunction with Putty. You can set up Msysgit to plug into this as well, so if you already have pageant working for your svn setup, this is probably the simplest path. The rest of this section assumes that you already have a key setup in pageant for svn.

    1. Make sure you have plink.exe in your Putty directory (same site as Putty)
    2. If you don't have your public counterpart for your private key, use putty to ssh into the machine you already have authorized and copy the contents of ~/.ssh/authorized_keys into the clipboard
    3. Use putty to ssh into the machine you will be using as your git backup machine
    4. Create ~/.ssh/authorized_keys with the contents of your public key and chmod 640 the file
    5. Setup the environment variable GIT_SSH to point to plink.exe (c:\program files\putty\plink.exe)
    6. Done!

    Note, that pageant still won't help your for using ssh from from the command line, so if you need to ssh into the machine and don't want to provide the password each time, use putty.

    Create a bare remote repository

    You will need ssh account to another machine that has git installed. To create our backup target, ssh to backup machine, create a new directory for the repository, cd to the directory and initialize it:

    git --bare init
    

    The difference between this command and the one we used to create our local repository is that this creates a repository without any branches or local state. This repo is not for local use on the target machine, but just a place to push our repo to and pull again, if you need to get the files after a crash or on another machine.

    Tell your repository about the remote

    Usually when you clone a repository from somewhere else, such as github, your local copy refers to the place it cloned the repo from as 'origin'. Since we're starting from a new repo, but might use that remote as our central storage accessed from multiple machines, we should also call that remote 'origin'

    git remote add origin ssh://remoteserver/absolute/path/to/remote/repo
    

    Since you are using ssh, ssh is responsible for authenticating your requests. This means it will prompt you for a password for any pull or push. You can avoid this by using ssh-agent or creating a trusted key without a password (assuming your local machine is secure enough for you to feel comfortable with having a password-less key on it).

    Associate your branches to the remote

    This part is both tedious and rather like black magic, but must complete the following steps for every one of your branches. If you add a new branch in the future and want it backed up, you must complete theses steps again (replace <branchname> with the name of your branch):

    git checkout <branchname>
    git push origin <branchname>
    git branch --set-upstream <branchname> origin/<branchname>
    
    

    Once you have completed the setup, you can push changes from all branches at once with just:

    git push
    

    Checking out your repo on another machine

    Now that the repo is safely on a network accessible machine, it's trivial to clone the repo to another machine and easily take all your work in progress with you from machine to machine.

    Setup the new machine with the same configuration as above, open a git shell in the directory that will be the parent of your repo and clone your repo:

    git clone ssh://remoteserver/absolute/path/to/remote/repo <reponame>

    If the repository is already named what you want it to be called on this machine, you don't have to specify <reponame>. However chances are that for shadowing svn, you named the repo Dream, but it  will be checked out into trunk.

    This check out will have pulled the master from the remote and set up the tracking configuration, so you can just use git pull and git push for future sync operations.

    Checking out additional branches

    At any time you can use git branch -a to show all local and remote branches. To check out another branch use:

    git checkout -t origin/<branchname>

    This will check out <branchname> and set up tracking to sync operations.

    Note: If you create a new branch on this machine, you will have to go through association steps shown above to push the branch to the remote and continue tracking in from here on.

    Tag page
    You must login to post a comment.

    Copyright © 2011 MindTouch, Inc. Powered by