wiki:GitCrashCourse

Cloning a git repository

What this means is that you actually download the complete code repository to your own hard drive. This is different from centralised VCS/SCMs, which only have a copy of the files you checked out. With git you get a complete copy of the complete history, locally. This is one of the reasons why git can be very quick.

git clone {repository} [{local name}]

The {repository} string can point at a git://, ssh://, http[s]: URL or a file path to a local directory. When the cloning is completed, you will automatically have checked out the 'master' branch.

Introducing yourself

Git should know who you are, as that will be important for the git commit logs. So please enter a directory created by git clone and do:

git config user.name 'Your name'
git config user.email 'my@email.com'

This will only set these variables locally in this repository. I would probably recommend you to make these changes globally as well, as this can be easy to forget if your do another git clone.

git config --global user.name 'Your name'
git config --global user.email 'my@email.com'

Some might only need to do the latter one, but some might want to use different e-mail addresses for their commits for different projects.

Other neat features to enable

Making git colourful

This is highly recommended, as it is easier to read commit logs and patches via the git commands this way.

git config --global color.branch auto
git config --global color.diff auto
git config --global color.log auto
git config --global color.interactive auto
git config --global color.status auto
git config --global color.pager true

Notice that we here force colours when using the pager. This might mean you need to add one more change as well:

git config --global core.pager 'less -X -R -F'

Preparing for sending patches via mail

If you choose to install the git-send-email package, you can very easily submit patches via e-mail. We will go into these details later on, but some configuration should be done.

git config --global sendemail.chainreplyto false
git config --global sendemail.smtpserver {your SMTP server}

If you're using SSL or TLS against your SMTP server, add this:

git config --global sendemail.smtpencryption {tls|ssl} (choose tls or ssl)

If you're authenticating against the SMTP server, you must add:

git config --global sendemail.smtpuser {your-username}

You can also set your password in the config, by setting sendemail.smtppass - but the password will be saved in clear text'''

Configuration files

Now do this:

cat ~/.gitconfig
cat .git/config

As you can see, the configuration files are not that hard to read. These files may be edited manually if you want to as well.

Concepts

Tags

If you wonder where 'v2.1_rc15' came from in the example above, that's a tag name. And tags can be found by doing:

git tag -l

A tag is just a name of a particular commit ID. Commit ID's can be tricky to remember, and then you can insert tag names which are a bit more descriptive.

Branches

Branches are almost the same as tags, but with one difference - you can commit to them. You can also quickly create your own branch to test out things, without being worried about your former commits.

First, let's look at some branches:

git branch

This will list all your local branches. But we also have some remote branches.

git branch -r

Notice the difference between them, where remote branches are prefixed with origin/. This is because git tracks remote branches separately from your local branches. So you can have a master branch which is different from origin/master. Another thing, to be able to do commits on remote branches, you must check them out as a local branch before you can do commit.

You can also add more remote repositories, and will then have different remote names. origin is just a remote name, which is the default name for the remote where you did your initial clone. We will not dig into this here.

Common tasks

Checking the status of the project

git status

This command gives you an overview over all files which have been modified and all new and unregistered files. The output of git status should be pretty obvious.

Checking your changes

git diff [{filename}]

This will list all local changes which has not been committed.

Saving changes

All files new files will need to be added to an index to be prepared for a commit. Consider this as a "pre stage" which needs to be done. This is also different from other VCS/SCMs. But it has it's advantages as well. So if you add or modifies files, you need to do this to add files to the index when you want to commit your changes:

git add {filename/directory}

If you have a long list of files under the git status section called Changed but not updated:, you can do:

git add -u

This adds all these files into the index. If you now try to do a git diff', you'll see that nothing turns up. And if you modifies one of the files and do git diff' again, you'll find that only your last changes is shown. This is normal behaviour. git compares to what's in saved the index and what the local file looks like.

If you want to see the changes from what's in your index and the previously committed version, you'll need to do:

git diff --cached

Please also try:

git status

Notice the change of the message for the files which you have added. Now, let's commit!

git commit -s

Now an editor should turn up, you can enter your commit statement and exit. Notice the -s argument. That means sign-off'. That adds an 'Signed-off-by: ' signature at the end of the commit log. Please do it a habit to always sign-off your commits. That simply means you approve the commit. And this is especially important if you commit work for others.

To change the author of some code in the commit log, you can do that via the commit as well:

git commit -s --author "Authors name <authors@email.com>"

This is important to remember if you apply patches from others. This way it is possible to track from whom the different code parts came from.

Look at the commit log

git log

This is the most basic usage. Other nice usages is to follow a files commit log, and not the complete project's commit history:

git log --follow {filename}

If you also add -p, you can also see the diff.

There's also a feature called git show. This shows the contents of a commit or other git objects. If you just do

git show

it will show the last git commit. But you can give it a commit ID which you can find in the git log. If you give it a branch name or a tag name, you will see the git commit of the branch point or at the commit where the tag points at.

git show 684790552dde0475745015a76762ecc5a11eedab     (commit ID)
git show 684790552dd                                  (partial commit ID)
git show origin/allmerged                             (remote branch)
git show v2.1.1                                       (tag name)

Creating patch files

When you have done some commits, you probably want to share them. Say you want to pick out the last commit.

git format-patch HEAD~1

This notation might look odd. HEAD is a keyword, which means head of the commit log. It's where the last commit happened. It's because git format-patch takes ranges. This notation means, from HEAD - 1 commit and to the last commit. If you do just HEAD, the range "distance" will be 0, and no patch file will be created. It's like saying from A to A. But you really want to say, from A to B. To explain that, you can do:

git format-patch HEAD~1..HEAD

So, what happens if you change HEAD~1 to HEAD~3 ... well, you normally* get the 3 top-most commits as patch files. git format-patch will report the file names it generates on-the-fly.

  • I say normally, because if there are merges in the scope you give, you will get all patches which was merged in.

git format-patch can also take commit ID's which you find in the commit log, in addition to branch names and tag names. So if you want to have all commits from OpenVPN 2.1_rc15 to OpenVPN 2.1_rc22 ... you can do:

git format-patch v2.1_rc15..v2.1_rc22

You can also do this with git log to see which patche files git format-patch will generate.

Sending patches to the openvpn-devel mailing list

If you have installed the git-send-email package and done the configuration steps mentioned above, sending patches to the mailing list is just as easy as creating the patch files. Please note that the git send-email command expects to get patch file(s). Just do:

git send-email --to {email address}  {patch files}

and follow the instructions you're given.

As a simpler alternative, you can also combine the git format-patch and git send-email commands by doing this:

git send-email --to {email address} HEAD~1

This will send the last commit to the given mail address. If you change HEAD~1 to HEAD~4, the 4 last commits will be sent.

Checkout a branch

In OpenVPN, there's a branch called allmerged. So to checkout that branch locally, we do:

git checkout -b allmerged origin/allmerged

That's it :) Now you can do commits to your own copy of the allmerged branch.

If you find out that you want to do some experimental stuff, to see how it works out, just do:

git checkout -b my_test_branch

And it will create a new branch, enter it, from the latest commit where you where. You can now even commit into this branch.

Getting up-to-speed with the official repository

If you have done local changes and added your own commits on top of a git branch, you might at some point want to submit these patches to the mailing list. Or you might just want to test these patches on the latest stuff from the official upstream git repository. The best way to do this is to rebase your branch against the latest changes in the official repositories. This operation is often referred to as "rebasing against upstream".

First we want to get a fresh update from the official upstream repository. If you used git clone against one of the official repositories, downloading the latest stuff is done like this:

git fetch origin

The name origin is a name of the default repository used when you did the git clone operation. To see which remote repositories you have configured, you can use this command:

git remote -v

This reports the alias and the URL for the repository. If the upstream tree is not named origin, you need to replace that with the appropriate git remote alias.

When you have done the git fetch, you have only downloaded the latest stuff into your copy of the remote repository. No files in your checked-out git branch has been modified yet. So to really apply those changes to your own branch, make sure that you have a clean git tree with no modified files (use git status to see the status of your currently checked out branch). Then run this command:

git rebase origin

This will first rewind the git branch you are working on, temporarily taking out all your local changes. Then it will add all the modifications done in the remote tree. When that is done, it will apply all your local changes on top of the latest remote branch. If you want to rebase against a particular upstream branch, you do this by also mentioning the branch name:

git rebase origin/master

It might be that there are some conflicts when doing such rebases. Sometimes it is not so easily for git to understand which version you want to use, your local changes or the upstream version. In these cases, git rebase will stop and ask for your help. You need to use git status or git diff to see which files have issues, and edit each of them manually - removing the code which is not supposed to be there. Then you use git add <file(s)> to indicate you are done with the files. If git status now does not list any modified files, then you need to use git rebase --skip - as the patch is not relevant any more (the upstream tree have already applied these changes) - otherwise you must run git rebase --continue.

Sometimes there are a lot of conflicts, and this job takes a little while. Other times, there are no conflicts and this git rebase operation is done in just a few minutes. For massive conflicts, it might be worth checking out git mergetool. This also needs a separate tool, and meld is one such tool which can be used.

If you are worried about messing up such rebases, just checkout a test branch and try it there first. Then you'll see how it goes without messing up anything else.

When git status shows a clean status ("working directory clean") or just lists files not committed to your branch, then you have successfully rebased your tree to the latest upstream branch.

Merge branches

If you find out that one branch has some stuff you want to test out with your own stuff in your own branch, it's quite easy.

git merge {branch to merge into your branch}

If there are some conflicts, just do a git diff to see what they are. You need to solve these issues before you then can continue. The conflict blocks are pretty visible and you will hopefully understand what to leave in the file and what to remove. When that's done, it's the same procedure as before - git add and git commit.

If there were no conflicts, a merge commit is done automatically for you.

When to use git merge and when to use git rebase

This can be a bit difficult to understand at first. But it is important that you learn to separate between what is your upstream source and what is your downstream source.

A very rouge description can be that upstream source is where you will send your changes to in the end. Downstream are those git repositories which uses 'your' git repository as their upstream source.

When sending patches to the openvpn-devel mailing list, it is important that your changes are rebased against the latest upstream branch you want to provide your patches against. So to use git format-patch and/or git send-email - to send patches to your upstream source, you need to first 'rebase' against the latest git tree you want your changes to be applied to. When this is done, you can use git format-patch and/or git send-email.

If you are pulling in changes from other branches who uses 'your branch(es)' as an upstream source, then you need to 'merge' those changes into your own tree. This is because these git repositories are your downstream. This way you can easily further submit these changes to your upstream tree.

A little ASCII art again:

                      [official openvpn.git]           (Downstream: Bobs repository)
                                 |
                                 |
                  [Bob Boss's openvpn-bob.git repo]    (Upstream: official openvpn.git)
                              /    \                   (Downstream: Johns and Janes repos)
                             /      \ 
                            /        \
                           /          \
                    [John Doe]      [Jane Doe]         (Upstream: Bob's repo)

In this scenario, Johns and Janes upstream tree is Bob's openvpn-bob.git repository. This means that Bob has two downstream trees, Johns and Janes repositories. Bobs upstream tree is the official openvpn.git repository. So John and Jane should rebase their trees against Bobs repository. When Bob pulls in Johns or Janes changes, he needs to merge them into his tree. Then Bob can rebase his tree against the official upstream openvpn.git repository. When Bob has done that successfully, he can easily submit those changes to the openvpn-devel mailing list.

Last modified 11 years ago Last modified on 11/05/12 17:18:26