Git Tips and Tricks

Last update: 17 Dec 2018 [History] [Edit]

Cheat Sheet

We have an ATLAS cheat sheet that gives the most important git commands and terms that we use in ATLAS code development. Keep this by your side!

Migrating from the old repository

On the 17th December 2018, ATLAS updated the Athena repository to make it public and open-source. If you are an ATLAS member and have forks or clones from before that date, please look at the instructions on the twiki

Useful aliases

A few useful aliases that you can add to your ~/.gitconfig:

[alias]
# Nicely formatted history
hist = log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
lg =  log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(bold yellow)%d%C(reset)' --all
# Find/show merge request for commit on current branch (http://stackoverflow.com/q/8475448/)
find-merge = "!sh -c 'commit=$0 && branch=${1:-HEAD} && (git rev-list $commit..$branch --ancestry-path | cat -n; git rev-list $commit..$branch --first-parent | cat -n) | sort -k2 | uniq -f1 -d | sort -n | tail -1 | cut -f2'"
show-merge = "!sh -c 'merge=$(git find-merge $0 $1) && [ -n \"$merge\" ] && git show $merge'"

Useful environment variables

If you want to avoid switching between your source and build directories, you can tell git where your working directory and repository resides by setting:

export GIT_WORK_TREE=/path/to/my/git/repo
export GIT_DIR=${GIT_WORK_TREE}/.git

All git commands (e.g. git diff) will then operate as if you had executed them in your working directory even if your current directory is different. Note the above is a special case of using multiple work directories with the same git repository. For further details see Git Loves the Environment and git-worktree.

Diffing

A plain git diff -- PATH shows the difference of the currently checked out files against the copy that is staged (or last commit) for PATH. It is very common to diff against a commit ID or a tag and as ATLAS makes tags for each release it is easy to diff changes between the current version and a release tag:

git diff release/21.0.8 -- Tools/PyJobTransforms

To show the difference between two releases use:

git diff release/21.0.1..release/21.0.8 -- Tools/PyJobTransforms
git diff nightly/21.0/2017-05-05T2130..nightly/21.0/2017-05-06T2130 -- MagneticField

Use git diff --name-only to only show the file names that changed. GitLab also has web-based diff feature that can be useful for look at a limited number of changes, e.g. between two nightlies (it does not allow to filter on a PATH though).

git revisions --help gives more information on how to specify revisions:

  • HEAD has the obvious meaning
  • adding ^ means parent of, so HEAD^ means the commit before current HEAD
  • adding ~N means the Nth ancestor, so HEAD~10 means the 10th commit before HEAD
  • using the range specifier A..B is equivalent to A B in the above examples

Commit Logs

If you replace diff with log in the above commands, you will see the commits and their log messages.

git log   # from HEAD backwards
git log release/21.0.1..release/21.0.8 -- Tools/PyJobTransforms

There are many options controlling how much to show, e.g, --oneline or --pretty=format:... (see the examples above).

git show [commit id] shows both the commit log and the diff with respect to the previous version.

Rebase

What is a rebase?

git rebase is a way to rewrite a repository’s history, but preserving the substance of code changes. It can be very useful in a certain situations:

  • A commit was made that had a small mistake, and a subsequent tiny fix was added; however, the mistake is irrelevant to the code history and should be squashed, merging the two commits together into a single correct commit.
  • There is a mistake or omission in a commit message and it needs to be be rewritten without changing the code itself.
  • There is a commit that was made in error and needs to be removed, but this commit is no longer the head of the branch (otherwise git reset would work).
  • A long running development needs to be resynced with its parent branch where rebase is a cleaner alternative to merging.

Note that for the first two cases, if the commit in question is the HEAD, then one can use git commit --amend instead.

Warnings

  • As rebase rewrites a branch’s history it creates a new version of the branch that is now inconsistent with the previous version. This then requires a special force option to update any remotes. This is fine for “private” developments, e.g., in your own fork, but if anyone else is working from the same feature branch they will have to carefully incorporate your rebased update, so really make sure that everyone knows how to do this before you start, especially if other developers have also made changes to the same branch.
  • Git, as ever, tries to make sure that you don’t lose your work and will allow you to roll back from a troublesome rebase. However, if you proceed carelessly you may be destructive enough that you make it difficult or impossible to recover work you accidentally trampled on. If you really get into trouble then stop and ask for help, don’t push on blindly and never delete the clone that got into trouble unless you are 100% sure that all the substantive code changes you have are safely stored in a remote branch (see the advice below about using git reflog in this situation).

Some specifically good documentation is on the Atlassian pages.

Interactive Rebasing

One of the most useful forms of rebasing, used for squashing commits and rewriting commit messages, is the interactive rebase. To do this run

git rebase -i HEAD~N

Where git will keep commit HEAD~N commit the same, but then rebase everything else from here up to HEAD; HEAD~N is the Nth ancestor of HEAD (be aware that if there are merge commits in the range you specify you might well get more commits included that you expect, git log --oneline HEAD~N..HEAD will help, but it is strongly discouraged to rebase across merge commits).

Now in your editor a page will open, showing every commit that will be part of the rebase along with what will happen to it (the default is pick meaning keep this commit as is) and the one line commit message.

Alert Do make sure that your EDITOR environment variable is set to an editor that you know how to use.

To merge a commit with its parent, change pick to squash (or just s); if you choose fixup instead then the commit message for the fixed-up commit will be discarded. In both cases you will have the chance to rewrite the commit message for the merged commit. To just rewrite a commit message change the command to reword. The descriptions of the other command options are pretty clear.

When you save and exit your editor git will execute each of the rebase commands in turn, producing a new commit history from the starting point.

A much more extensive discussion of these options is in the git tools documentation.

Rebase Instead of Merge

Rebasing against another source branch will rewind your branch to the last common commit, then it will fast forward, applying the commits from the updated source branch, finally it will then replay your changes on top of this updated state. It’s a way of making it look like your changes were developed directly on top of the commit that the your copy of the source branch was fast forwarded to. So it avoids the extra commit needed for a merge and produces a cleaner history.

To do this, fetch changes and run git rebase SOURCE_BRANCH instead of git merge. Or you can run git pull --rebase to combine the fetch and the rebase in one operation.

Merge conflicts are resolved in the same way for rebase as for merge.

If something goes wrong…

If some merge conflict resolution is going badly wrong then you can abort a rebase in progress with

git rebase --abort

This would also allow you to restart with a different conflict resolution strategy.

If you really made a mess, then use git reflog to find the commit ID before you started the rebase and use git reset --hard COMMIT to move the head of your branch back to before the rebase was started (this is generally an excellent fall back strategy if something goes awry in your development).

Force Push

If you fix a branch using rebase that already exists in a remote you will need the -f option when you push to force the revised history to be accepted.

If another developer is using a branch that was rebased they should fetch the rebased branch, but with the -f option, then rebase their new features into the updated branch using the --onto option of rebase as discussed here. The fact that this is very tricky is one of the reasons that rebasing branches seen and used by others is really not recommended.

CMake Errors

If you get strange cmake errors e.g.

undefined reference to `pthread_create'

then it’s likely that you have:

testarea = <pwd>

in an .asetup file, and you also ran asetup inside the athena/ directory. You could remove testarea, delete the extraneous files (use git status to find what was added) and re-run. However we instead recommend running asetup in the top-level directory (i.e. the directory which contains athena/), as this means you don’t need to modify your configuration but also avoids asetup creating unnecessary local .asetup files.