Yehuda Katz is a member of the Ember.js, Ruby on Rails and jQuery Core Teams; he spends his daytime hours at the startup he founded, Tilde Inc.. Yehuda is co-author of best-selling jQuery in Action and Rails 3 in Action. He spends most of his time hacking on open source—his main projects, like Thor, Handlebars and Janus—or traveling the world doing evangelism work. He can be found on Twitter as @wycats and on Github.

My Common Git Workflow

A recent post that was highly ranked on Hacker News complained about common git workflows causing him serious pain. While I won’t get into the merit of his user experience complaints, I do want to talk about his specific use-case and how I personally work with it in git.

Best I can tell, Mike Taylor (the guy in the post) either tried to figure out a standard git workflow on his own, or he followed poor instructions that tried to bootstrap someone from an svn background while intentionally leaving out important information. In any event, I’ll step through my personal workflow for his scenario, contrasting with subversion as I go.

Cloning the Repository

The very first step when working with a repository is to clone it. In subversion, this is accomplished via svn checkout svn://url/to/repo/trunk. This retrieves the most recent revision of the trunk branch of the repository.

In git, this is accomplished via git clone git://url/to/repo (the http protocol is also possible). This retrieves the entire repository, including other branches and tags.

Making the Change

In both git and subversion, you make the change using a normal text editor.

After Making the Change

In git, you make a local commit, marking the difference between the most recent pulled version (master) and the changes you made. In subversion, the normal workflow does not involve making a change, but apparently some people make manual diffs in order to have a local copy of the changes before updating from the remote. Here’s an example comment from the Hacker News post:

I’ll tell you what happens when I use svn and there’s been an upstream change: I never update my local tree with local modifications. Instead, I extract all my local changes into a diff, then I update my local tree, and then I merge my diff back into the updated tree and commit.

When I need three-way merging, which isn’t often – usually patch can resync simple things like line offsets – it’s handled by a file comparison tool. I have a simple script which handles this

My personal process for making the commit in git almost always involves the gitx GUI, which lets me see the changes for each individual file, select the files (or chunks in the files) to commit, and then commit the whole thing. I sometimes break up the changes into several granular commits, if appropriate.

Updating from the remote

Now that we have our local changes, the next step is to update from the remote. In subversion, you would run svn up. Here, subversion will apply a merge strategy to attempt to merge the remote changes with the local ones that you made. If a merge was unsuccessful, subversion will tell you that a conflict has occurred. If you did not manually save off a diff file, there is no way to get back to the status from before you made the change.

In git, you would run git pull. By default, git applies the “recursive” strategy, which tries to merge your current files with the remote files at the most recent revision. As with subversion, this can result in a conflict. You can also pass the --rebase flag to pull, which is how I usually work. This tells git to stash away your commits, pull the remote changes, and then reapply your changes on top one at a time.

If you use --rebase, you may get a conflict for each of your local commits, which is usually easier to handle than a bunch of conflicts all at once.

I definitely recommend using --rebase which also provides instructions for dealing with conflicts as they arise.

In either case, in my experience, git’s merging capabilities are more advanced than subversion’s. This will result in many fewer cases where conflicts occur.

Resolving Conflicts

From here on, I am assuming you followed my advice and used git pull --rebase.

If a conflict has occurred, you will find that if you run git status, all of the non-conflicting files are already listed as “staged”, while the conflicting files are listed outside the staging area. This means that the non-conflicting files are already considered “added” to the current commit.

To resolve the conflicts, fix up the files listed outside the staging area and git add them. Again, I normally use gitx to move the resolved files into the staging area.

Once you have resolved the conflict, run git rebase --continue. This tells git to use the fixed up changes you just made instead of the original commit it was trying to put on top of the changes you got from the remote.

In subversion, if you got a conflict, subversion will create three files for you: file.mine, file.rOLD, and file.rNEW. You are responsible for fixing up the conflicts and getting back a working file. Once you are done, you run svn resolved.

NOTE: If you had not used git pull --rebase, but instead did raw git pull, you would fix up the files, add the files using git add or gitx, and the run git commit to seal the deal

Yikes! Something went wrong!

In git, if something goes wrong, you just run git reset --hard, which will bring you back to your last local commit.

In subversion, it’s not always possible unless you manually stored off a diff before you started.

Pushing

Now that you’re in sync with the remote server, you push your changes. In git, you run git push. In subversion, you run svn commit.

One Glossed-Over Difference

Subversion allows you to commit changes even if you haven’t svn uped and there have been changes to the remote, as long as there are no conflicts between your local files and the remote files.

Git never allows you to push changes to the remote if there have been remote changes. I personally prefer the git behavior, but I could see why someone might prefer the subversion behavior. However, I glossed over this difference because every subversion reference I’ve found advises running svn up before a commit, and I personally always did that in my years using subversion.

Comparison

Here’s a workflow comparison between git and subversion:

Operation git svn
Clone a repository git clone git://github.com/rails/rails.git svn checkout http://dev.rubyonrails.org/svn/rails/trunk
Preparing changes git commit (using gitx) nothing or create a manual diff
Update from the remote git pull --rebase svn up
Resolving conflicts git add then git rebase --continue svn resolve
Resolving conflicts without –rebase git add then git commit N/A
Yikes! Rolling back git reset –hard svn up -rOLD then apply diff (only if you manually made a diff first)
Pushing git push svn commit

Note that I am not attempting to provide an exhaustive guide to git here; there are many more git features that are quite useful. Additionally, I personally do a lot of local branching, and prefer to be able to think about git in terms of cheap branches, but the original poster explicitly said that he’d rather not. As a result, I didn’t address that here.

I also don’t believe that thinking of git in terms of subversion is a good idea. That said, the point of this post (and the point of the original poster) is that there are a set of high-level version control operations that you’d expect git to be able to handle in simple cases without a lot of fuss.

35 Responses to “My Common Git Workflow”

Minor correction: there doesn’t necessarily need to be a conflict (at least in the since of what happens when a merge goes wrong) for svn to refuse a commit. There just needs to be a change to a file you’re committing since the last svn up.

Thanks, Yehuda, that’s helpful.

Don’t bother. I think the very basic issue Mike has with Git is that some people try to sell it as a drop-in replacement for CVS/SVN. Git will just never endear to devs that have been working with traditional VCSs for ages and were perfectly happy with them (can you blame them?). You need to have an open mindset when switching to Git and actually put some effort to understand the three levels your source goes through (local, staging, remote).

I use Git in my daily work (I work together with Mike) and I’m perfectly happy with it, but then, I have only used CVS/SVN for about three years before switching to Git. So I didn’t mind changing my habits a bit.

Thanks for the ‘git pull –rebase’ suggestion. The explanation of how to deal with the conflicts was also helpful.

I didn’t know about “git reset –hard”. Thanks for sharing!

yeah! thanks so much for the “git pull –rebase”. I never knew there was something like that in git. really should read more about git.

Thanks for posting this. I’ve been putting off learning git, but after reading this I think I’m going to have to finally check it out.

If you reset –hard in the middle of a pull -rebase, you will still be in the middle of a rebase, right? I think you need to do a rebase –abort to really get back to where you started.

In svn when i need to rollback i use `svn revert`. I try to don’t forget to do an update (usually i use an sh alias to do it like `cd /path/to/project; svn update;`) before any change so i got less conflicts and it works fine for me.

The mistake I made repeatedly while learning this workflow was in using git commit to ‘resolve’ a merge conflict after first making changes and then git add. I did not realize git add followed by git rebase –continue would suffice.

/me light bulb ON

I can’t agree more with the sentiment that you shouldn’t think of git in svn terms (and I tell students we get as interns as much). It’s an entirely different beast. This write up is a great break down of how some basic things map back and forth but in my experience going much further past these, like mapping your exact workflow to git, is pain. My first day on git had me tossing entire change sets out because I was trying to treat git like svn (still not sure how i managed that, but it drove the point home).

Once I got past that I was running full steam. While I definitely understand the initial struggles of learning git, I am boggled by the extended ones (ok, besides the syntax for deleting a remote branch…wtf). I had it forced on me one day. I came in that morning and the principle of the co said “We moved to git”. It was a rough day or three but after that I was golden. Add github in there and watching Linus’s talk on the why of git and I don’t think I could go back to SVN.

That said I do wonder if git will have its svn much like cvs did. Certain things about git feel so weird at first, can’t help but wonder if those things could be improved and I don’t really see git doing it.

I understand it’s not a good idea to rebase a branch which has already been pushed to a remote repository. It can cause problems for others who have pulled that branch.

So I wonder if git pull –rebase avoids this problem, the man page simply says that it causes git pull to do a fetch/rebase rather than a fetch/merge

This problem was solved a while ago with a great post from my friend @reinh.

http://reinh.com/blog/2009/03/02/a-git-workflow-for-agile-teams.html

This is a great post both for its content and its tact. Nicely done and very helpful. Thank you!

@Rick DeNatale: Rebase + push is only an issue if you modify history that has already been pushed. Git pull –rebase only rewrites history that the remote hasn’t seen yet, so it’s not an issue.

> If you did not manually save off a diff file, there is no way to get back to the status from before you made the change.

This statement is incorrect. It’s not _easy_, but it definitely is possible. When you “svn up” and there is conflicts, subversion puts multiple files in your working copy: the base revision (as of your last checkout or update), the new revision on the server (both named file.r### where ### is the revision number), and your locally modified changes (named file.mine). You can resolve the normally-marked commits, or pick any of those three files to switch back to.

That assumes a conflict has occurred. Sometimes, svn can successfully merge the files, resulting in irreversible changes.

This is the most succinct piece I’ve seen for getting SVN die hards up to speed on Git. Thanks for the article!

Hey Yehuda,

Nice article! Note however that subversion (new versions at least) have a sort-of staging area called changelists

http://svnbook.red-bean.com/en/1.5/svn.advanced.changelists.html

Regards!

`git checkout -f` is a shorter way to roll back to HEAD instead of `git reset –hard`

I also like “git pull –rebase” very much. Using “branch..rebase = true” and “branch.autosetuprebase = always” to make “git pull” use rebase by default is something I like even more.

I also find the following alias very helpful:

pp = !git pull && git push

It allows me to use “git pp” to pull and push immediately if there are no conflicts.

(the “!” just tells git that what follows is a full command, and not just the name of git command)

Nice article.

Also, for those folks who get into git for multiple-contributor projects, a next step is to think about team-level workflows. For that, I recommend a look into gerrit — http://code.google.com/p/gerrit

We use it at work as it adds a beneficial layer of code review workflow, with a web app that provides nice pending-change visibility. For example: http://source.android.com/submit-patches/workflow

I think it should be noted that ‘git reset –hard’ is one of the few things you can do in git that can actually cause your work to be lost. That command overwrites the unstaged changes in the working directory. If this is what you want, ‘git reset –hard’ is what you need. But I always take a little pause before pressing Enter on that command, just as I do with ‘rm -rf’.

If you want to reset to the last commit without losing any changes, check out ‘git stash’.

With Git, nothing committed is ever lost (until the next garbage collect with git-gc).
You can find ‘lost’ commits using
git log -g
These can then be cherry-picked.
So, even after a git reset –hard, you should still be able to git back your old commits.

That said, I agree with Mark Wilden about the little pause!

if you find git more complex and time consuming than expected, consider going back to svn. not that many people/projects really need all the fancy features of a distributed vcs. but if you really do need a dvcs, most developers find mercurial easier to use.

mercurial has the added advantage of being windows friendly, whereas git only runs on windows using a *nix compatibility layer, such as cygwin.

Are you aware of msysgit. I have never had any problems running git natively on Windows…

I really enjoy Aanand Prasad’s git-up for pulling changes from a remote origin. It even automatically stashes for you. http://github.com/aanand/git-up

This is exactly what Joel Spolsky begged not to do when explaining a DVCS. A chart of how to use the DVCS in term of SVN equivalent commands. This is just wrong.

In GIT you move away from SVN because it works in terms of changesets, not versions, because branching and merging works, and because there is no master pserver to rely on. Thus, your changesets can travel from one branch to the other, local or remote, back and forth. You can just forget about the commands and rely on a GUI tool of have the docs handy, but remember why GIT, HG and BZR are not Subversion.

When in a conflict in a rebase, git mergetool rocks :)

I’m wrestling with moving from SVN to Git, but problem with Git has nothing to do with SVN.

I’m working on a collaborative project stored at GitHub. We’re told to fork that project and clone our forks. So far so good. The problem is keeping the local clone up to date.

Say I make a change, upload it to my fork at GitHub and issue a pull request. Until my pull request is honored (and it may never be), my working copy is out of synch with the original repository (the one I forked from). The only way I know of to keep my fork up to date with Git is to pull from the original repository and push to the fork. But if I pull from the original repository, my changes will be undone and if I push to my fork, the changes will be undone there.

This probably shows a deep misunderstanding of Git and there’s probably a simple solution, but I sure haven’t found it. I can’t deduce a workable workflow for this situation. The workflow suggested here is great, but not if you don’t have commit rights to the repository.

Eduardo: Spolsky’s “learn DVCS from scratch” concept works well with people who are able to let go of their SVN mental model and approach the idea fresh. But when people are so entrenched in their old model that they can’t break free from it, a different tact is required.

I can’t agree with the statement that svn does not allow you to get back to the state before a conflict.
The three files you mentioned, .mine .rOLD, and .rNEW will help with that.
If I’m not mistaking, if you rename all the .mine files to their previous ‘versions’ (foo.c.mine to foo.c) and run svn up -rOLD you should be back in the state before the conflict, not diffs required

I love this article; thanks! I just got started with git and I use SVN so it’s nice to see this!

The most important thing that i am taking away from this post is: git pull –rebase !
thanks

git is a place where younger less experienced coders can mess around and learn with their local copy of the repository, without messing up the real repository.

Thanks for the –rebase idea.

I personally don’t like the way git seems to demand that every file I touch gets merged if there is a conflict. Some files have conflicts and I don’t want them merged, and I don’t want to edit them because I know the files are wrong.

Leave a Reply

Archives

Categories

Meta