Working with git

From Einstein Toolkit Documentation
Jump to: navigation, search

Contents

Interacting with a remote repository

This page describes solution to typical needs with git. If you are new to git, please read any of the fine tutorials to learn about the concepts that git offers, and how one would typically work with git.

Later, if you get stuck, or if you maneuvered yourself into a corner, you can come back here to find solutions to particular problems. That is, this page is not organised by "how do I do thing that are natural for git", but rather "how do I map things I need to do onto actions that are natural for git".

This page is split into three parts:

  • Interacting with a remote repository (pull, push, merge)
  • Local commits
  • Working (adding/removing files, undoing changes)


Check out a repository:

 git clone <remote-repo-url> <local-repo-name>

For example

 git clone git://carpetcode.org/McLachlan McLachlan

Find out what changes were made to a remote repository since I last pulled:

 git fetch
 git log HEAD..origin

Update from a repository, if there are no local uncommitted changes:

 git pull --rebase

Hint: If you find you always want to rebase when you pull, then make it the default behavior by setting the autosetuprebase option

 git config --global branch.autosetuprebase always

Now whenever you run

 git pull

it will do a rebase too.

Update from a repository, if there are local changes:

Save all local changes:

 git stash

Then update as described above:

 git pull --rebase

Re-apply all local changes:

 git stash pop

If there are no conflicts, you are done. If there are conflicts, handle them, then:

 git stash drop

since "stash pop" doesn't pop the stash if there are conflicts. "git stash list" shows all stashes. View the content of a stash with "git stash show <stash>".

Push to a repository:

Ensure all remote changes have been pulled (see above), then:

 git push

Create a branch containing the current snapshop of the local repository, including all uncommitted changes, and switch to this branch

Create a new branch pointing to the current commit:

 git branch <newbranchname> 

Check out the new branch:

 git checkout <newbranchname>

These can be combined into a single command:

 git checkout -b <newbranchname>

List all branches

 git branch -a

If you also want to see the relationship to remote branches, use very verbose mode

 git branch -vva

Find out which branch I am on:

 git branch

The branch marked with a star "*" is the current branch.

Switch to a branch, if there are no uncommitted changes

 git checkout <branch-name>

If the branch name happens to be the same as a file name, you can explicitly specify that you're referring to a branch:

 git checkout <branch-name> --

Committing

Commit a change:

Commit certain files:

 git commit <list-of-files-to-commit>

Commit all modified files which are already under version control:

 git commit -a

Modify a commit that hasn't propagated yet, keeping the working tree the same:

 git commit --amend <files>

This is the same as "git commit", except it rewrites the last commit instead of creating a new one. <files> lists any files with new changes that you want to include. You can also say "-a" to mean all registered files.

Undo a commit that hasn't propagated yet, keeping the working tree the same:

 git reset HEAD~1

This deletes the last commit but leaves the changes from it in the working tree. HEAD~1 means one commit before the latest.

Revert a commit (i.e. create another commit), dropping all changes that it made:

 git revert <commit>

If your working tree is not clean, do a "git stash" before and a "git stash pop" after.

Revert a commit (i.e. create another commit) that may have propagated already, keeping all the changes as local modifications:

 git show <commit> | git apply -R

Making local changes

Look at local modifications:

List modified files:

 git status

Get a diff between the local repo and the working tree ("which changes did I make since the last commit?"):

 git diff HEAD [<filename>]

Get a diff between the local repo and the staging area ("which changes did I mark for committing?"):

 git diff --staged [<filename>]

Get a diff between the staging area and the working tree ("which of my changes would not be committed?"):

 git diff [<filename>]

Get a diff between the remote repository and working tree ("what would a 'git pull' do?"):

 git diff origin [<filename>]

Revert an uncommitted change to a file:

 git checkout <file>

If there is also a branch with the same name as the file, then we can specify explicitly that we mean the file.

 git checkout -- <file>

Add a file to the repository:

First create file locally, then:

 git add <list-of-files-to-add>
 git commit

Remove a file from the repository:

Remove the files from the working tree and mark them as removed in the staging area, then commit the staging area:

 git rm <files>
 git commit

Undo adding a file to the repository:

 git reset HEAD <file>

This leaves the file in place, but removes it from version control.

Undo removing a file from the repository, recreating the file manually:

Recreate file locally, then:

 git reset HEAD <file>

Undo removing a file from the repository, resurrecting the file in the repository:

 git reset HEAD <file>
 git checkout <file>

Useful hints

Aliases

Aliases are a handy way to simplify the commands needed for your own common tasks. For example, you could add a "what's upstream" command to easily find out which commits will be pulled

 git config --global alias.wu '!sh -c "git fetch && git log origin..HEAD"'

From now on, just run

 git wu

and it will list the commits which would be merged in by a pull. Similarly, it can be useful to have a "what's downstream" command to know which commits would be pushed:

 git config --global alias.wd "log origin..HEAD"

Another useful example is if you often find yourself wanting to do the stash-pull-pop sequence, you could create an alias for that:

 git config --global alias.sp '!sh -c "git stash && git pull --rebase && git stash pop"'

Easy rebasing with mergetool

Rebasing is mostly painless with git. The only problem arises when there is a conflicting commit and you have to manually intervene. In this case, the mergetool command can be invaluable:

 git mergetool

This will present a diff utility (opendiff on Mac OSX) which allows you to easily select which changes you want. Once you have finished, save and quit, then run

 git rebase --continue

If you want more manual control, edit the conflicting files manually and get them how you want them. Make sure to add them:

 git add <conflicting-files>

and then continue the rebase:

 git rebase --continue

Remove files unknown to git

Sometimes you just want to get rid of all files which aren't under version control. For this use the clean command. First, run

 git clean -n

to do a dry-run and see what will be deleted. If you're happy with this change, run

 git clean -f

to actually get rid of the files.

Oops, I didn't mean to do that

Don't worry if you accidentally do an operation which you didn't intend to do. It can usually be recovered. For this, the reflog command can be very useful. It essentially lists all recent operations. For example, say you accidentally deleted the last commit using reset and now you realise that you actually do want it after all. Simply run

 git reflog

and find the SHA1 of the commit you want in the list, then cherry-pick that commit

 git cherry pick <commit-sha1>

Aargh, I made a total mess

It can happen that you get your repository into a hopeless mess and just want to get it back to its original state, disregarding all local changes. This can be achieved using the -f option to checkout

 git checkout -f HEAD

This will throw away all local changes and will bring your repository back to the state it would be in if you freshly checked it out (with the exception of files which aren't under version control).

Saving credentials in your keyring

Git will ask for credentials when it needs to authenticate itself against a server to push/pull changes. By default it will ask for credentials each time it needs access which can be cumbersome. The usual solution is to use ssh public key based access and add the key to the keyring managed by ssh-agent (or equivalent). For https based access this does not work and one can instead use git's credendtial helpers introudced in version 1.7.9 (man page or bitbucket tutorial). A simple approach that mimics what svn does is to cache the credentials in memory for a while:

 git config --global credential.helper cache
 git config --global credential.helper 'cache --timeout=3600'

A more comprehensive approach would use gnome-keyring (source code only, included in git 1.8.0) or osxkeychain or an encrypted .netrc file.

Workflows

These workflows are designed to be self-contained. git is a complicated program, and the documentation can be confusing. The aim of these sections is that if you stick to the commands and techniques used in each workflow, you should not get into a mess or need to consult any other git documentation.

SVN style

Checking out

Clone the repository (like "svn checkout")

 git clone <url>

Work on your new feature or bug fix.

Updating

Every so often, you will want to make sure you are working against the latest version of the code. This is accomplished by

 git pull

If you get an error about a file not being "up to date", this means that you have made changes to a file which has also been changed in the remote repository. You need to "stash" your changes away in a safe place, then do the update, then apply your changes to the new version:

 git stash
 git pull
 git stash pop

This is equivalent to "svn update". If all is well, your changes will be applied to the working copy and dropped from the stash. If there are conflicts, these will be marked with conflict markers in the source files just like with SVN. Your changes will also be kept in the stash for safe keeping, rather than being dropped. You will need to fix these conflicts. Once you have fixed all the conflicts, use "git reset" to tell git that you have fixed the conflicts. To be tidy, you can now drop your changes from the stash with "git stash drop".

What have I done?

To see what changes you have made in your local copy, use

 git diff HEAD

Reverting local changes

If you want to revert one or more files to its clean state before you started editing it, use

 git checkout <file1> <file2> ...

Status

If you want to see what files have been modified, added or removed, use

 git status

Adding and removing files

If you need to add a new file to the repository, use

 git add -N <filename>

(like svn add) for the file you have created. (The -N stops the content of the file being added to the "staging area", which would make "git diff" give confusing results. Don't worry about this.) If you need to remove a file, use

 git rm <filename>

(like svn rm).

Committing

Eventually you will have your working directory in the state that you would like to commit. Ensure that you are up-to-date as described above. The next stage depends on whether you want to push your commit directly to the repository (if you have commit rights) or if you want to send your work as a patch.

Direct commit

Make a local commit:

 git commit -a

This will prompt you for a commit message. The "-a" means "all registered files". This will make a local commit in your repository. Push this commit to the central repository:

 git push

These two commands together are equivalent to "svn commit".

Your work is now in the central repository, and you are done.

Submitting a patch file

Sometimes you don't have commit access to the repository, or you want to have your work reviewed by someone else before you commit it. You can package up your changes into a "patch file" with

 git diff > ~/Uploads/MyWork.patch

This file can be emailed or added to an issue-tracker.

Suppose you need to modify your work. Edit the working-copy files to incorporate the modifications, and then generate a new patch file as above. Repeat until the patch is final.

If you are responsible for committing the accepted patch, you can use the procedure in "Direct commit" above. If someone else has committed it, you don't need your local changes any more, and you can erase them using

 git checkout

You can then update from the remote repository with

 git pull

and you are done.

Git style

This style is useful if you want to keep multiple changes organised locally.

Clone the repository:

 git clone <url>

Work on your new feature or bug fix. If you need to add a new file to the repository, use

 git add -N <filename>

for the file you have created. (The -N tells git just to register an empty file as added to the repository. If you omit it, the entire file will be put in the staging area, which might not be what you want if you only want to commit part of the file).

If you need to remove a file, use

 git rm <filename>

Suppose you now want to bundle up some of your work into a single unit. This is achieved by making a "local commit". If there are no other changes to the files that contain the work you want to commit, you can make a commit in one step using

 git commit <file1><file2> ...

You will be prompted to enter a commit message to describe this change. You can type "git log" to see all the commits, and your new commit will be at the top of the list. If your work is in files which have other changes which you don't want to commit just yet, then you can interactively select which parts of the files to commit using a two-stage process. First you will "add" parts of the files to the "staging area", then you will commit the staging area:

For files which need to be added in their entirety:

 git add <file1> <file2> ...

For files which need certain parts only:

 git add -p <file1><file2> ...

This will ask you, for each individual change in each file, whether you want to stage that "hunk" or not. You can answer "yes", "no", "all" or "done" by entering the first letter of each. Type "?" for help on the other options. Quitting the interactive "git add" will leave all the hunks you have staged staged. If you want to unstage a hunk, you can use

 git reset <file1> <file2> ...

to unstage the content of entire files, or

 git reset -p <file1> <file2> ...

to select hunks to unstage. This gives you the same interface as "git add -p", but lets you unstage rather than stage each hunk.

You can see all the hunks which are currently staged using

 git diff --staged

Once you are happy with what is staged, you can commit it using

 git commit

You will be prompted for a commit message. Once your changes are committed, you can see them using "git log".

You can commit as many changes locally as you like. Nothing has yet been sent to a remote repository.

Every so often, you will want to make sure you are working against the latest version of the code. This is accomplished by

 git stash
 git pull --rebase
 git stash pop

The "git pull --rebase" will unapply all your local commits, update your local repository with commits from the remote repository, and then attempt to re-apply your commits. This process might generate conflicts. These will be indicated in the output of "git pull --rebase". You can edit the files to fix the conflicts. When you have finished fixing the conflict, you need to mark the files as fixed using

 git add <file1> <file2> ...

and then

 git rebase --continue

to move on to the next commit. If at some point you decide to abandon the rebase, you can use

 git rebase --abort

This will take you back to your last commit, i.e. to before the "git pull --rebase" command.

Once the "git pull --rebase" command has finished, and you have fixed any conflicts, you will use the "git stash pop" command as above. This will restore any local uncommitted changes. Again you might have conflicts which you need to fix in your working copy.


Eventually you will have a set of commits at the end of your history (at the beginning of the output of "git log") that you would like to push. Ensure that you are up-to-date as described above. The next stage depends on whether you want to push your commits directly to the repository (if you have commit rights) or if you want to send your work as a series of patches.

Direct commit

If you want to push the patches directly to the repository, you can use

 git push

This will push all your local unpushed commits to the repository that you cloned from. If you need to push to a different repository (for example if you cloned from a read-only git:// repository and you need to push to an ssh git@server:/repo style repository), you can add the URL of the repository:

 git push <url>

If you get an error about a "non-fastforward" push, this means that you were not up-to-date when you tried to push. Use the "git pull --rebase" command as described above to make yourself up-to-date and try again.

Your work is now in the central repository, and you are done.

Submitting a series of patches

Sometimes you don't have commit access to the repository, or you want to have your work reviewed by someone else before you commit it. You can package up all your local commits which don't exist in the remote repository (refered to as "origin") with

 git format-patch -o ~/patches origin

This will make a series of numbered patch files named after the subject of each commit (the first line in the commit message) in the ~/patches directory. These files can be emailed or added to an issue-tracker.

Editing your commits

Suppose you have several local commits, but you decide you need to make changes before pushing or making patches. One option is to make the changes you need and then make new commits with these changes. You can then either push the entire set of commits or make new patch files with "git format-patch" as above.

Git allows you to modify your existing local commits. If you only need to modify the last commit, you can just make your changes to your files and use

 git commit --amend <file1> <file2> ...

This will modify the last commit to incorporate your new changes.

If you have to edit a commit which is not the last one, you can use

 git rebase -i <commit>

WORK IN PROGRESS HERE

In the editor window which appears, change "pick" to "edit", save the file and exit the editor. Make your changes to the working tree, then do

 git rebase --continue