Git tutorial (EPFL version)

Introduction

What is Git?

Git is a source control system: a system that can be used to manage a directory tree storing source code, documentation or other files (this is called a repository), offering the following main features:

The basic 3-operation workflow

Git uses three important operations, which every user should understand:

The branching workflow

Advanced users should become familiar with a git feature called branching.

Branches are multiple versions of the file tree that exist in parallel. Their main use is to keep the clean version in the master branch (known as 'trunk' in SVN) and the work-in-progress version in a separate branch. Once you are happy with the changes, you merge them into the stable branch. This is very useful especially when there are at least two people working on the same repository: each one maintains his own development branch, and they all eventually merge into the stable branch.

A nice feature of branching in git is that when switching to another branch, git automagically makes all the files and changes appear and disappear, depending on whether they belong to the current branch or not. So there is no need to make duplicate directories etc.

A nasty problem is that git does not know what to do with uncommited changes when you switch the branch (what it does is to just carry the changes over, which is most likely not what you intend). You should always commit everything before you switch branches, or stash the changes (more about that below).

The rule of thumb is that you should always have a clean tree (no uncommited changes) whenever you move code around (downloading from remote repository, switching branches, merging changes between branches).

Setup git

Use the following gitconfig, which has some sane settings. (save it as ~/.gitconfig or /etc/gitconfig). Fill in your name and e-mail.

[user]
  name = John Doe
  email = john.doe@epfl.ch
[color]
  ui = auto
[color "branch"]
  current = yellow
  local = yellow
  remote = green
[color "diff"]
  meta = yellow bold
  frag = magenta bold
  old = red bold
  new = green bold
[color "status"]
  added = green
  changed = yellow bold
  untracked = cyan
  deleted = red
[format]
  pretty = %C(yellow)%h%Creset %C(bold blue)%an <%ae>%Creset %s %C(green bold)(%cr)%Creset %C(green)(%ci)%Creset%C(yellow bold)%d%Creset
[merge]
  conflictstyle = diff3
[alias]
  delta = "!f() { git diff $1^ $1; }; f"
  deltaf = "!f() { git diff --name-status $1^ $1; }; f"

You may also find it useful to customize your shell's prompt when working in a git directory. There are many options available, search for something like custom shell prompt git.

Basic operations

Create a new repository

Create a local copy of a remote repository

Run the command as indicated by https://git.epfl.ch, for example:
git clone https://mygaspar@git.epfl.ch/repo/smalltest.git

Display the current state of your copy

git status

If you use the gitconfig file given above, you should see in yellow files tracked by git which have changes not added to commit, in green files tracked by git which have changes added to commit but not commited yet (i.e. the commit has not been finalized) and in blue the files which are not tracked by git.

$warn If you see a message such as "Your branch is ahead of 'origin/master' by 1 commit", it means that you commited locally but did not push (i.e. upload) the commit to the remote repository. Keep in mind that in git commits are always local, and the push needs to be done manually. $warn

Display a diff with the changes not yet added to commit

git diff

Add a new file to commit

git add path/to/file

Add a modified file to commit

git add path/to/file

Add parts of a new/modified file to commit

git add -p path/to/file

Commit

First, check that you added all the necessary files to the commit, then commit:

git status
git commit -m 'The commit message'

Push the commited changes from the local copy into the remote copy

In git, the remote repository is called origin, and the name of the main branch is master (like trunk in SVN). Therefore the following command means "push the commited changes into the repository origin on branch master":

git push origin master

Pull changes from the remote copy into the local copy

$warn Warning: you should always commit before pulling. $warn

If you have rebase conflicts, see instructions for solving them in the next sections.

git pull --rebase origin master

Display the commit log

git log

Display the commit log with diff

git log -p

Display the commit log with per-file change statistics

git log --stat

Display the commit log with commit graph

git log --graph

Display the changes (diff) from a specific commit

Note: this is not an actual git command; it is an alias defined in the gitconfig given above.

git delta thecommithash

Display the list of files changed in a specific commit

Note: this is not an actual git command; it is an alias defined in the gitconfig given above.

git deltaf thecommithash

Fixing mistakes

$warn When in doubt, make a manual backup! $warn

Change the last commit message

Run this command and then commit again:

git commit --amend

Undo the last commit (keep the changed files)

I strongly suggest you make a manual backup of your files first.

git reset --soft HEAD~1

Undo a git add before git commit (on commit no changes will be committed to the file; does not modify the file)

git reset HEAD path/to/file

Undo all git adds before git commit (i.e. empty the commit; does not modify the files)

git reset

Throw away local changes to a file (resets the file to the repo version)

git checkout -- path/to/file

Remove a file from the repo (keep the file on disk)

git rm --cached path/to/file

Remove a file from the repo (delete the file from disk)

git rm path/to/file

Store away all uncommited changes (push to a stack structure)

git stash

Restore all uncommited changes previously stashed (pop from the stack structure)

git stash pop

You modified the wrong branch (but did not commit)

git checkout the_right_branch

You accidentaly destroyed commits

You ran some command found on the Internet, which was supposed to help you modify your commit history, but instead you lost your last commit(s). They do not even show up in the log anymore, and you start to panic.

First, make a backup of your repository (copy the entire directory somewhere else).

Then inspect the output of:

git reflog

If you see your commit there (let's say its hash is acc3551b13), you can still recover it. Run:

git merge acc3551b13

Git branch basics

There is always a branch called master, which is similar to trunk in SVN.

To find out how using more than one branch can be useful, see the 'Git branch workflow' below.

Display existing local branches

These are branches that have been created or downloaded locally. Normally git will not download all existing branches from the remote unless instructed specifically.

git branch

Display all existing branches (local and remote)

Remote branches are prefixed with remotes/.

git branch -a

Create a new branch and switch to it

git checkout -b the_branch_name:

Switch to another existing branch

git checkout the_branch_name

Switch to another branch that does not exist locally but exists in remote

git checkout -b the_branch_name origin/the_branch_name

Delete a local branch

git branch -d the_branch_name

Merge changes from branch_x into the current branch

$warn WARNING: NEVER start a merge when you have uncommited changes in any branch. $warn

Note: you might have to solve merge conflicts.

git merge branch_x

Rebase changes from branch_x into the current branch

$warn WARNING: NEVER start a merge when you have uncommited changes in any branch. $warn

Note: you might have to solve rebase conflicts.

git rebase branch_x

Merge vs. rebase

Merge takes all the changes in one branch and merges them into another branch in one commit, logging that two parallel timelines have joined. Use it ONLY for intentional changes (new features, modified behavior etc.)

Rebase moves the point at which you branched in the past to a new starting point at the current time, putting all the individual commits that happened in the meantime in a linear timeline. The previous parallel timelines disappear from history. Use it for unrelated changes (e.g. synchronizing changes to module A with changes to module B).

The 'Git branch workflow' below shows when to use merge and when rebase.

$warn WARNING Commit your changes whenever you switch branches, otherwise git checkout will carry the uncommited changes to the other branch. Alternatively you can do a git stash to store away your work and finally git stash pop when you want to restore it. Make sure you are in the right branch when you commit/stash/unstash. $warn

Git branch workflow

You should always keep the clean, stable version of your work in the master branch, especially if you share your repository with someone else. Do not work in master, instead use a different development branch called yourname-devel (e.g. john-devel).

More advanced users also create a separate branch for any well-defined task which requires several commits with changes that are independent of the work currently hapenning in the master branch; these development branches are usually called feature branches.

There are a few important actions you need to know how to perform:

1. Work in the development branch and commit there

Create the branch john-devel and switch to it:

git checkout -b john-devel
git push -u origin john-devel

Make some changes. Commit as usual.

git add blah blah blah
git commit -m 'Blah'

2. Sync changes from the master branch

You notice master has been updated remotely (e.g. by a coworker), and you decide to incorporate the changes into your branch. First, commit your changes in the development branch.

git add blah blah blah
git commit -m 'Blah'

Switch to master and pull the changes from remote:

git checkout master
git pull --rebase origin master

Bring those changes back into the development branch (rebase will redo your commits on top of them, unless there are conflicts):

git checkout john-devel
git rebase master

Now you can continue your work in the development branch.

3. Save the changes in the development branch remotely

All the commits you make (in any branch) are local to your machine. It is good practice to make several small commits during the day, and upload all your changes to the remote repo at the end of every day--or whenever you switch from one machine to another (e.g. desktop to laptop).

Push commited changes from local into the remote repository:

git push origin john-devel

Pull changes from remote into local (you should always commit first):

git pull --rebase origin john-devel

Note that the last two commands are similar to the way of pulling/pushing changes in the master branch.

4. Merge all the changes from the development branch into master

This should be done when the task implemented in the development branch is completed.

First, make sure you have committed all the changes in the development branch. Then switch to master and merge:

git checkout master
git pull --rebase origin master
git merge john-devel

Solving merge conflicts

If you use the gitconfig recommended above, text files with conflicts will contain something like this:

<<<<<<<
Changes from the current branch.
|||||||
The common ancestor version.
=======
Changes from branch_x.
>>>>>>>

You need to edit the file so that in the end it is clean, without any markers (<<<<, ||||, ====, >>>>).

It might be useful to inspect the commit history starting from the common ancestor. To do this, run:

git log --merge -p path/to/file

Once you have resolved the conflicts in a file, run:

git add path/to/file

Once you have resolved all the conflicts, you must do a commit to complete the merge:

git commit -m 'Merged branch_x, resolved conflicts'

If you want to abort the merge and reset your working copy to the state it was before the merge:

git merge --abort

Versions of git older than 1.7.4 need:

git reset --merge

Versions of git older than 1.6.2 need:

git reset --hard

Solving rebase conflicts

This is similar to solving merge conflicts. You clean up files with conflicts, and then run:

git rebase --continue

Repeat until git is happy.

It might happen that you want to drop your local changes from a commit completely in a way that looks like an empty commit, in which case git will suspect that you are making a mistake. In this case you need to run:

git rebase --skip

If you want to abort the rebase and reset your working copy to the state it was before the rebase:

git rebase --abort

Tip: make your life easy and rebase often (daily is usually fine), otherwise you will have tons of conflicts which might be difficult to merge. Rebasing weeks of work is bad practice.

Git as SVN client

You need to install git-svn. Afterwards, use git operations as usual, except for the following:

Create a local copy of the SVN repository

git svn clone --stdlayout https://the.svn.repo

Pull commits from the remote repository

git svn rebase

Push commits to the remote repository

git svn dcommit

Show the SVN log (with SVN revision numbers)

git svn log

Advanced operations

Ignore changes to a file

git update-index --assume-unchanged /path/to/file

Stop ignoring changes to a file

git update-index --no-assume-unchanged /path/to/file

Change your author name/email in all commits

$warn WARNING: using git push --mirror will first erase the content of the remote repo, then upload your version. If anything breaks during this process, you might have a bad time!!! Backup first using git clone --mirror. $warn

Step 1: mirror the remote into a new directory

mkdir tmp
cd tmp
git clone --mirror https://the.remote.repo

Now backup:

cd ..
cp -r tmp tmp-backup
cd tmp

Step 2: change your name in all commits

git filter-branch --commit-filter 'if [ "$GIT_AUTHOR_NAME" = "Bob" ]; then export GIT_AUTHOR_NAME="Robert"; export GIT_AUTHOR_EMAIL=robert@example.com; fi; git commit-tree "$@"'

Step 3: force the remote repository to become a perfect copy of this repository

git push --mirror

Remove a file from all the commit history:

git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch the/path/to/file' --prune-empty --tag-name-filter cat -- --all
git show-ref --head
git update-ref -d refs/original/refs/heads/master
git update-ref -d refs/original/refs/remotes/origin/master
git update-ref -d refs/original/refs/stash
git push origin master --force