Oct 05, 2013

Example git forkflow: branch hopping

I wanted to show my mercurian friends around git, and what better way to do that than by real examples.

This morning I wanted to persist the changes I've been making to pelican-mockingbird theme for use in my own blog. Increased verbosity for illustration purposes.

$ git status
# Not currently on any branch.
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   static/css/pygments.css
#   modified:   static/css/screen.css
#   modified:   templates/article_stub.html
#   modified:   templates/base.html
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   templates/disqus.html
no changes added to commit (use "git add" and/or "git commit -a")

You may notice I'm "not on any branch" (also called detached head state). This is because I am working inside a submodule of pelican-themes and the submodule is pinned to a particular commit.

First, I fork pelican-mockingbird on Github and add my new forked repo as a remote.

$ git remote add mine git@github.com:unthingable/pelican-mockingbird.git

Since I'm not on a branch I need to get on one. I want it to be the master from my new fork.

First, fetch the state of the new origin:

$ git fetch mine
From github.com:unthingable/pelican-mockingbird
 * [new branch]      master     -> mine/master

It found one remote branch master and fetched it under a local alias mine/master. Now I attempt to checkout the remote branch. This is not the branch I'll be working in, but it is a quick way to bring the working dir into the most current state.

$ git checkout mine/master
error: Your local changes to the following files would be overwritten by checkout:
    static/css/pygments.css
    static/css/screen.css
    templates/article_stub.html
    templates/base.html
Please, commit your changes or stash them before you can switch branches.
Aborting

OK, apparently I was missing some recent commits and now the incoming changes are conflict with my local changes. Before I risk losing my work, it is time for salvation:

$ git stash
Saved working directory and index state WIP on (no branch): 2f4c1b6 update screenshot
HEAD is now at 2f4c1b6 update screenshot

Now I can get current:

$ git checkout mine/master
Previous HEAD position was 2f4c1b6... update screenshot
HEAD is now at fa46a80... Merge pull request #5 from kevmo314/master

So far so good. Let's bring my changes back in:

$ git stash pop
Auto-merging templates/base.html
Auto-merging templates/article_stub.html
CONFLICT (content): Merge conflict in templates/article_stub.html
Auto-merging static/css/screen.css
Auto-merging static/css/pygments.css
CONFLICT (content): Merge conflict in static/css/pygments.css

Even though I popped the stash, the stashed changes is still there. At this point (or any point in the future) I can decide to scrap what I'm doing, go back to the original commit I started working from (2f4c1b6) and reapply my stashed changes conflict-free. This would fully restore the state I had in the beginning of the post.

I decide to keep the new changes and fix the conflicts. Now my changes are in a happy state and I am ready to make a new commit atop fa46a80 (the last commit on remote master).

$ git status
# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   static/css/screen.css
#   modified:   templates/base.html
#
# Unmerged paths:
#   (use "git reset HEAD <file>..." to unstage)
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
#   both modified:      static/css/pygments.css
#   both modified:      templates/article_stub.html
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   templates/disqus.html

Let's tell git my conflicts are resolved, and also add the new template:

$ git add templates/ static/

(channeling Gallagher) Can it be that easy? Yes, it's that easy!

$ git status
# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   static/css/pygments.css
#   modified:   static/css/screen.css
#   modified:   templates/article_stub.html
#   modified:   templates/base.html
#   new file:   templates/disqus.html

Now we're ready to commit and push. But wait, we're still not on any branch. Let's take our current state and make a new branch out of it. I don't want to touch my master just yet, just in case I bork something — after all, I have been doing mercurial for a few months now...

$ git branch
* (no branch)
  master
$ git checkout -b new-master
M   static/css/pygments.css
M   static/css/screen.css
M   templates/article_stub.html
M   templates/base.html
A   templates/disqus.html
Switched to a new branch 'new-master'
$ git commit -am 'fix.'
[new-master e542d31] fix.
 5 files changed, 125 insertions(+), 82 deletions(-)
 rewrite static/css/pygments.css (98%)
 create mode 100644 templates/disqus.html
$ git log
commit e542d311cf1a6ed318c1807a118a7f4a5ce93f80
Author: unthingable <me@example.com>
Date:   Tue Nov 5 10:08:43 2013 -0800

    fix.

commit fa46a8024ceb821a8717ed36f7b9d2a6a06d2060
Merge: decd2d4 7ea3637
Author: william light <wrl@illest.net>
Date:   Sun Oct 13 06:02:45 2013 -0700

    Merge pull request #5 from kevmo314/master

    use more standardized strftime formats

Looking good! Now I want to take new-master and push to my github master without renaming. Can it be that easy?

$ git push mine new-master master
Counting objects: 18, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (10/10), 2.28 KiB, done.
Total 10 (delta 5), reused 0 (delta 0)
To git@github.com:unthingable/pelican-mockingbird.git
 * [new branch]      new-master -> new-master

Oops, it's been a while and I forgot the proper syntax. Now there is a branch called new-master on github. Let's fix and clean up:

$ git push mine new-master:master
Total 0 (delta 0), reused 0 (delta 0)
To git@github.com:unthingable/pelican-mockingbird.git
   fa46a80..e542d31  new-master -> master
$ git push mine :new-master
To git@github.com:unthingable/pelican-mockingbird.git
 - [deleted]         new-master

All done. I still have a local master that I don't need anymore, so one more thing to tidy up:

$ git branch -av
  master              fa46a80 Merge pull request #5 from kevmo314/master
* new-master          51e3737 readme
  remotes/mine/master 51e3737 readme
$ git branch -d master
Deleted branch master (was fa46a80).
$ git branch --move master
$ git branch -v
* master 51e3737 readme