wiki:GitWorkflow

Note: If you just want to check out the Kea source, then use the instructions on the GitGuidelines page.

Git workflow

This is a tutorialish explanation how git can be used with regard to Kea (there are many guides to git over the internet, for example this one). The commands are explained what they do, so it should be easier to grasp than a dull list of commands.

Theory

Before we start, we need little bit of theory.

While subversion doesn't really have any branches and it uses subdirectories, git heavilly uses branches. You can switch between them, create new ones, merge as you like, delete them, etc. Many of such operations happen automatically under the hood as well. But branch itself isn't some kind of tracked object. It is just a name for a commit (state of the files and directories) and if new commits appeare at the branch, the name is just updated to point to the new commit.

Another concept with git are repositories. With subversion, there's only one repository and everybody talks directly to it. With git, you have your own full-featured repository, with history, etc. Most of the time, you work with it (imagine you have the subversion server on your localhost). And there are commands/routines to synchronize repositories. We have a dedicated „central“ repository, and everyone synces with it.

For that reason, there are two kinds of branches. One is branches in your local repository, called local branches. You can directly modify them (commit, or do other nasty things with them we will not go into here). And there are remote branches. The name is little bit misleading. When you talk with a remote repository (fetch), all branches local in that repository are copyed into your local repository as remote branches. These are snapshot of the remote repository at the time of fetch. These branches can not be modified directly, but you can read from them, create local branches from them, update from them, etc.

Getting the repository

You can create your repository by this command (you can ommit the username if it is the same as your local username):

git clone ssh://username@git.kea.isc.org/git/kea

This command does many things (git has some basic commands and many compound ones, you can run the low-level ones manually if you have a need for that). First, it creates a new, empty repository. Then it configures it to „know“ a remote repository, whose URL you just provided and calls that one „origin“. Then it fetches it (reads its content and copyes all its local branches and whole history). If it was everything, we could examine the repository, but couldn't do anything. So there are more steps it does.

It creates a new local branch called „master“ (it is equivalent of trunk in subversion) based on remote branch „origin/master“ and configures it to „track it“ (notes down into the configuration that „origin/master“ is its friendly remote branch). Then it checks out that branch (switches to it).

So, now you have a configured repository that knows the „central“ one, with single branch you can modify, compile, examine, etc.

You may tweak the configuration more (you should set at last your name and email address, see GitGuidelines), from general behaviour (what merging strategy should be used), trough internal tuning (how often a housekeeping should be done) to colored output. The manual pages contain a lot of info about that.

Receiving changes

If you did the above in the evening and you are curious whan happened to the master branch over the night, you want to call:

git pull

Again, this is compound command. It does two things. First, it does a fetch ‒ reads the remote repository and updates the local snapshot of it. Then it merges the remote branch the current branch tracks into it (in the case of master, it is origin/master). If you did no changes to your local master, then it will be a smooth update without a merge commit and there's no chance of conflicts.

Doing a review

Let's say you want to review the branch for ticket #123. You need to get the newest changes and switch to the branch. So, first off, you would do

git fetch

(you could do pull as well, as it does fetch by itself, or you could skip this one if you knew there were no changes on the branch since you did fetch or pull the last time).

We could directly switch to the remote branch:

git checkout origin/trac123

This changes the files in the repository to the version on the head of that branch. You can read the code, examine the history with things like git log, gitview, qgit or other tools. Note that all these tools show „revision numbers“ in the form of 40 hexa characters (these are sha1 hashes of the commits). You can put this id in any place where a branch is expected, so you can switch to it or you can call:

git show [the-id]

to show the changes done in that exact commit, or

git diff [the-id]

to show the differences between the commit and current state. You can specify only few first characters of the id, if they are unique, or learn some other ways to specify commits (like HEAD for grandparent commit of the current one).

After you are done with reading, you could just switch by checkout somewhere else.

However, there's a cleaner way. As said above, the remote branches are read only and git even produces a warning in this case about a detached HEAD state (it means there's no local branch now and the commits you would do could get lost easilly). The correct way is to create a local branch that tracks the remote branch first ‒ something like clone did for us with master. We can do it for example by:

git checkout origin/trac123 -b my_branch_name

It will create a branch called my_branch_name based on origin/trac123 and set it to track it. Then it switched to the my_branch_name branch. You can make changes to it, call pull on it to get additional changes from upstream, etc.

When you're done with the branch (because it got merged by the author to master), just switch to master, update it and delete your local branch:

git checkout master
git pull
git branch -d my_branch_name

The reason here is that we can't delete a branch we are currently on and -d checks the branch is already merged into current branch. If we wanted to skip the check, we could use -D, but the check is nice as it ensures we never lose any commits.

Doing development

Now when you want to write some code, there are several things you need to do. We will go trough them one by one.

Creating the branch

You probably want to branch from master and start working on the code. So, we switch to master, update it (if needed) and create a new local branch. The name is yours, so it doesn't matter, this does not go to the repository yet.

git checkout master
git pull
git branch new_branch_name
git checkout new_branch_name

We have a nice branch to work with, but it is only local and has no connection with any remote branch. So we need to transfer the new branch to the remote repository and tell our local branch that it is its friendly branch (eg. that it should track it). This doesn't work by creating branch named „origin/something“ ‒ git will not stop you from that, but next fetch would remove it, origin is brought to snapshot the repository, not the other way around.

git push origin new_branch_name:refs/heads/remote_branch_name
git branch new_branch_name --set-upstream origin/remote_branch_name

The push command is for transfering changes to remote repository (something like inverse of fetch, but not really). In this complicated call it tells the remote repository to update the remote_branch_name branch (which does not exist yet) to point to the same commit as our local branch new_branch_name. Don't worry, you need this complicated call only at creation and deletion of remote branch. The second one tells our branch what is it's friendly remote one. After that, git pull and git push will know where to get or put changes (so from now on, it is ok to call pull and push without additional parameters).

The above method is the canonical way to push new branches. You can, however, use a bit of smartness in git to type a little less (which also saves you from misspelling things). If the remote branch name is supposed to be the same as the local one, and if it is an unambiguous name (which with our branch name conventions, they are), you can simply use

git push origin new_branch_name

You can also add -u (or --upstream) to automatically start tracking the remote branch you just pushed. Example:

~/repos/bind10 (trac1017)
> git push -u origin trac1017
Counting objects: 17, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 821 bytes, done.
Total 9 (delta 7), reused 0 (delta 0)
To ssh://git.bind10.isc.org/var/bind10/git/bind10
 * [new branch]      trac1017 -> trac1017
Branch trac1017 set up to track remote branch trac1017 from origin.
~/repos/bind10 (trac1017)
>

Creating commits

So, we have a branch to put our commits into. Now let's write some code. When you are satisfied, just call this:

git commit -a

It will ask for a commit message/description. The first line is short log message (like subject of email), the rest is the longer continuation. Start your first line with the branch name in square brackets. If you tend to forget this, we have some scripts you can use to automate this on GitTips.

The commit will be created in your local branch.

The -a parameter means „put all changes into the commit“. Git allows to put only parts of what you did into the commit, either by listing the names of files as parameters of commit, or marking them beforehead by add.

If you add or remove files, you can use:

git add
git rm
git mv

The third one is just the combination of two above. Git does not store any explicit history for a file, it tracks the changes by looking for similar parts of code, so if you remove and add a file, you lose nothing compared to mv. If you split file into two, the parts still have memory of who wrote each line.

Sharing the commits

So, you have been coding for a while and created a bunch of commits. Now you need to share them with the rest:

git pull
git push

The pull is there for the unprobable situation that someone added some commits to the remote branch while you've been working on it as well. It will merge them into your local one. The push will send the commits into the remote repository (and it would reject to do so if there were changes you didn't merge into your version, so don't worry if you forget to call pull first, it will remind you).

While fetch receives all changes, git pull receives everything, but merges only the current branch. Similarly, git push pushes only the current branch (at last with the newer versions and with default configuration).

Merging

Now the time has come that you have finished a review or you want to bring in changes from other branch (with git, it can be any branch, not only master). So, bring all branches you want to work with up to date first. Then switch to one of the merged branches (the one you want to merge into) and then call

git merge the_other_branch

If there were some changes, but the merge worked authomatically, you are just prompted for a commit message. Remember that this commit, like any other, is local one. If there are conflicts, git says so and you need to resolve them. Edit the files it talks about and call git add on them (like you were adding them again). It will not let you commit until you resolve all the conflicts. Then, when you are done, just commit what you have done.

Then you probably should push the changes onto the server.

There's no real need for doing the „sync with trunk“ as with SVN before merge there. But if you do, nothing extremely bad happens. It's just unnatural and the branches will have unexpected order in history.

So, if we had a branch called „cool_feature“ and wanted to merge into trunk, we would do:

# Bring everything up-to-date
> git checkout cool_feature
> git pull
> git checkout master
> git pull
# Do the actual merge
> git merge cool_feature --no-ff
Automerging some_file
Automerging other_file
(Conflict) Automerging of other_file failed.
> vim other_file
> git add other_file
> git commit
# It asks for commit message now

# Push it to server
> git push

If the merge goes wrong and you want to start over, you can always do a git reset --hard origin/master, which sets your local state to the master on server.

Notice the --no-ff option. It disallows fast-forwarding and creates a commit every time. This is to mark the end of branch even in case nothing changed. Also, if you insist on merging master into the topic branch first, please include this parameter on the merge back to master, so the natural order of history is preserved.

In case the master changed in between you pulled and tried to push, the push will get rejected. Simply pull again, it'll merge the local and remote changes and allow you to push.

Deleting our branch

Previously work branches were removed after they were merged into the master. This is no longer necessary but here are the instructions in case it is useful to remove a branch.

git checkout master # Probably not needed, we are still on that one, right?
git branch -d cool_feature

But we need to remove the branch on the remote repository as well. We do it by pushing „empty branch“ into it:

git push origin :refs/heads/remote_branch_name

This deletes it on the server. Like with push, if it is an unambigous name, you can simply use the branch name

git push origin :remote_branch_name

But if someone else deletes it and you fetch, it is not deleted in your snapshot (it stays on the last known commit forever). If you want to get rid of such stray branches, you can call:

git remote prune origin

It will delete all branches (in the remote origin, your local branches are untouched by it) that are not present on the server.

When you make a mistake

There are few things you can do when something goes wrong:

To remove everything that is not tracked by git, you can call:

git clean -fxd

If you did changes to a file you didn't want to, you can get the original (from last commit) by:

git checkout file_name

Or, if you really made a mess, you can call:

git reset --hard

Which will remove all uncommited changes. Really all of them, without asking, so be careful with it.

Fancy tools

It is just list of few handy commands for more advanced use, but without explanation:

  • git commit --amend
  • git add --patch
  • git bisect
  • git stash
  • git rebase --interactive
  • git cherry-pick

They are nice to know, but (probably with the exception of stash) you should get familiar with git first.

Things to be aware of

It is problematic to reset or rebase branches that are already pushed (eg. anything that asks for -f when pushing). If someone has a copy of the branch and does git pull, it will do a merge of his and the new version, which can merge two incompatible lines of history or something. The more „public“ branch, the more problematic (a lot of people might be unhappy about such things on master, so take this one as last resort, prefer revert if possible). It is probably OK on a development version before review, when nobody has seen it yet or if it's really soon after the push (therefore the chance someone got the version is really low).

Getting help

Git tries to be helpful while not bothering too much, so it prints tips from time to time. Also, you can get a short help (if you replace the „something“ by a command):

git something -h

or a longer explanation by either of:

man git-something
git help something

And, of course, the Internet is full of manuals, tips and guides.

Last modified 2 years ago Last modified on Nov 12, 2014, 3:27:38 AM