Skip to content

Using Git with this repository

endrelaszlo edited this page Oct 27, 2012 · 6 revisions

Using Git with this repository

This is intended as a minimal "cheat-sheet" explaining how to perform each of the steps in the workflow for working with the repository. In order to learn about git in general, see the following resources:

Terminology

  • local repository is your local copy (also referred to as clone) of the code base, containing the entire history of the project
  • upstream repository is the "master" copy of the code base on GitHub (note that this is the "master" copy only by convention - any clone is a fully functional repository)
  • branch is a branch in the history DAG (really only a reference to a particular revision)

Getting the code

git clone [email protected]:OP2/OP2-Common.git
cd OP2-Common

Configuring Git (mandatory)

Checking your commits conforms to coding guidelines

Install a Git pre-commit hook to automatically check for tab and whitespace errors before committing. In the root directory of your local Git repository do

git config --local core.whitespace "space-before-tab, tab-in-indent, trailing-space, tabwidth=2"
mv .git/hooks/pre-commit.sample .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit

Avoid merge commits in the same branch

Configure Git to use a rebase instead of a merge strategy when pulling a branch that has diverged:

git config --global branch.autosetuprebase always
git config --global pull.rebase true

Note: This will cause a pull to fail if you have uncommitted local changes (since Git prevents you from rebasing in that case). If that happens, stash your changes with git stash and bring them back after the rebase with git stash pop.

Make Git remember merge conflict resolutions

git config --global rerere.enabled true

Show a history graph

Set the aliases lg to show you a graph representation of the history of the current branch and lga for all branches:

git config --global alias.lg "log --graph --decorate --pretty=oneline --abbrev-commit"
git config --global alias.lga "log --graph --decorate --pretty=oneline --abbrev-commit"

Workflow

  • There is one master branch. Nobody commits changes directly to the master branch.
  • When you want to make changes, create a new branch and make your changes in that branch.
  • When you have made the changes that you want to, and are happy with them, create a pull request on Github.
  • Other developers will review the changes in your pull request.
  • After reviewing your changes, others may suggest changes that should be made before you merge the pull request. In that case, push the changes to your branch and the pull request is automatically updated.
  • Once everybody is happy with your changes, the pull request can be merged.
  • In order to avoid merges being made prematurely due to misunderstandings, you should not merge your own pull requests.

Branches

To list the branches in your local copy of the repository:

git branch -v

To list all the branches, including those in your local repository as well as all the remotes:

git branch -av

To check out (switch your local working tree to) branch <branch>, use:

git checkout <branch>

To check out a remote tracking branch <branch> from remote (i.e. create a local branch that mirrors the corresponding upstream branch), use:

git checkout -t origin/<branch>

To delete branch <branch> in your local repository, run:

git branch -D <branch>

To delete a branch on Github, you must push "nothing" to that branch. To do that, use:

git push origin :<branchname>

Reviewing and committing your changes

To review which files you have changed, use:

git status

When you have made some changes and would like to commit them, you need to tell git which of the files that you have worked on that you would like to commit. To do this, you "add" them to the staging area. For example, if you have changed file1.c and file2.c you would run:

git add file1.c file2.c

You can repeatedly use git add to add as many files as you like to the index. You can run git status at any time to see which files you have staged so far. When you have staged all the files that you want to commit, run

git commit

You will be presented with a text editor in which you can edit this "commit message", which describes your changes. Once you have entered the commit message, save it and exit you editor. This will complete the commit.

Commit messages

Commit messages should be formatted according to the commonly agree rules from Tim Pope. In brief:

  • lines of a commit message must not exceed 72 characters
  • the 1st line of a commit message should not exceed 50 characters
  • if the commit message consists of more than 1 line, the first line must be followed by a blank line

Getting changes

When you would like to fetch the changes that other people have pushed to Github, you run

git fetch

This doesn't automatically make any changes to your local copies of the repository - it just copies the changes into your copy of the Github repository.

If you want to bring in changes from another (diverged) branch into your current branch, you merge the two branch. To merge the changes from branch into your current local branch, run

git merge <branch>

If you want to update your local branch with changes from the corresponding upstream branch, you pull the upstream branch, which effectively combines a fetch and a merge. To pull changes from branch <branch> on GitHub, run:

git pull origin <branch>

Belying its name, git pull will actually create a merge commit (which is most likely not what you want), unless your branch can be fast-forwarded. This is the case if the branches have not diverged, i.e. the local branch is a direct ancestor of the upstream branch. This is only the case if Git tells you:

Your branch is behind 'origin/x' by 1 commit, and can be fast-forwarded.

This is normally the only circumstance under which you want to pull.

Warning: git pull will go horribly wrong (and certainly not do what you intend), if the upstream branch has been rebased! See below for how to deal with that situation.

Golden rule: Never pull before having explicitly fetched all the changes. Do not pull unless you're absolutely sure what is going to happen and it is what you intend. Better: forget about pull, always git fetch, inspect the changes and decide what to do. You can even alias pull to train yourself not to use it:

git config --global alias.pull "!echo 'Thou shalt not pull!'"

Publishing changes

To push your local branch <branch> up to Github so that others can see it, use

git push origin <branch>

If you have rebased your branch (see below) and you want to push it, the push will be rejected by default because your branch won't be based on the same ancestor as the branch on Github. To successfully push in this case, you will need to force the push:

git push origin <branchname> -f

Warning: Do not use -f if you haven't rebased! Doing so will silently overwrite any upstream changes by others and can lead to data loss!

Rebasing

When you want to make a pull request, your branch will need to be based on the /master/ branch. To rebase the current branch on /master/, run:

git rebase -i master

This will present a list of commits that will be rebased. Make sure this is the range of commits you expect. If that is the case, quit the editor to start the rebase process. If that is not the case, clear the editor (i.e. completely delete the list of commits), save and quit to abort the rebase.

This will apply each of the commits in the current branch on top of the master branch, and then move the reference for your branch to the last commit. The result is that your branch is now based on the master branch.

You may find that after the application of a commit there are conflicts that need to be resolved. To see which files are conflicted, use the git status command. To resolve the conflicts, edit each of the conflicted files and then add them with the git add command. Once you have resolved all the conflicts, you can continue with:

git rebase --continue

If things go horribly wrong, you can abort the rebase at any time and return to your branch as it was with:

git rebase --abort

Note: rebasing is an operation that rewrites (i.e. changes) history and alters ancestry relationships of commits. Be sure you have thoroughly understood the concepts (and to back to the Git visual reference if you feel you haven't) to avoid mistakes or data loss.

Dealing with upstream rebase

When fetching changes, git will indicate if an upstream branch has been rebased:

 + 9fecf86...c6218cd reduction-test-case -> origin/reduction-test-case  (forced update)

Warning: If that is the case you must not (under no circumstances) pull from that upstream branch into your local branch. If you did, git would try to merge your non-rebased local branch with the rebased upstream branch, which would lead to all sorts of obscure merge conflicts and you would end up with 2 parallel sidelines of the same history from the point where the rebased and non-rebased branches diverged. But since you never pull from an upstream branch that has been rebased there is no problem.

In the following we will assume you're on local branch x and when fetching you note that upstream branch origin/x has been rebased. What to do depends on the state of your local branch:

You don't have any local changes that haven't been rebased

Your local branch is on the same "commit" as the rebased branch. Note that you cannot compare the SHA1 hashes - these will always differ! You will have to use some "fuzzy logic" to compare, e.g. commit message and patch. If this is the case you can simply hard-reset your local branch:

git reset --hard origin/x

Warning: This will lose all local changes, committed and uncommitted. You must be absolutely sure that you're on local branch x before hard resetting.

You have local changes that haven't been rebased yet

A "previous revision" of your local branch has been rebased, so you need to transfer those changes that are missing upstream (and only those - make sure you don't replicate commits!) by rebasing them on the upstream branch. Assuming you're on branch x which has N commits that are "missing" upstream, you run:

git rebase -i HEAD~N --onto origin/x

The rebase works the same way as explained above.

Changes to generated files

After running the MATLAB generator, generated files will have the current time stamp set in the header. If that's the only change to a generated file, we don't want to commit it because we only want to have "real" changes in the revision history. Save the following script as git-reset-generated-timestamp:

#! /bin/bash

# Reset changes that change a single line (as they occur when re-generating
# OP2 kernels)

echo "Changes to be reset:"
git diff $@ -- `git diff --stat=150 $@ | fgrep "2 +-" | cut -d' ' -f 2`
read -p "Are you sure you want to reset those changes? y/[n] " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]
then
  git checkout $@ -- `git diff --stat=150 $@ | fgrep "2 +-" | cut -d' ' -f 2`
  echo "Changes have been reset"
else
  echo "Aborted, nothing has been changed"
fi

Call it as git-reset-generated-timestamp HEAD if you haven't committed yet or git-reset-generated-timestamp HEAD^ if you've just committed and run git commit -a --amend -C HEAD to amend the commit.

Warning: make sure the diff shown really only contains the changes you want to reset!