Wednesday, September 30, 2009

Effectively publishing branches with Git / Deleting remote branches

We recently ran into some problems when trying to push branches to a central remote to share changes with each other. We were following this workflow:

1. Create a local branch: git checkout -b feature
2. Make some changes.
3. Decide that a coworker should see this feature branch: git push origin feature
4. Make more changes.
5. Push those changes: git push

At this point git push may return an error or try to do something very wrong. Those of you more experienced with git have already spotted the problem. At no point did we tell git that our local branch is now tracking the new remote branch we published! This prevents us from running git push/pull without arguments.

Some users use the following workflow to get around this problem:
1. Create a remote branch: git push origin master:refs/heads/feature
2. Checkout remote branch: git checkout -b feature origin/feature (--track is default when the start point is a remote branch)

I don't like this workflow though. It assumes that I'm connected to the internet where git is trying to leverage disconnected workflows. It also assumes that I know when I create the branch that I'm going to want/need to share it. This isn't always true.

I feel that the right solution to this problem is to update the git config with the information it needs to start tracking the remote branch. Doing this by hand (either editing .git/config or using the git config command) is error prone at best. I was going to write a script until I realized that I already had the tool installed that I needed.

Let me introduce you to William's miscellaneous git tools.

Once I've installed git-publish-branch, my workflow looks like this:
1. Create a local branch: git checkout -b feature
2. Make some changes.
3. Decide that a coworker should see this feature branch: git publish-branch
(Without arguments publish-branch pushes the current branch to origin.)
4. Make more changes.
5. Push those changes: git push

This works correctly. You can also check to see if you're tracking the right branch and if you're in sync with the humorously named git-wtf from the same website.

Bonus points: Run "git config --global color.ui auto" for fun colors from these commands.



The other thing we discovered is that when we delete remote branches (with "git push origin :branch-to-delete"), the person who did the delete has their remote tracking branch (not the same as the remote branch itself) deleted but others who have run git fetch at some point still have that stale remote tracking branch. This can be cleaned up by running "git remote prune origin" periodically. My desire would be to get a notification of a deleted branch when I run git fetch but that's not how it works.

Monday, September 28, 2009

Git Archive

I've been working with some code on rightscale. A common workflow for me is:

1. Hack, hack, hack.
2. Commit.
3. Export to a compressed tarball.
4. Upload to rightscale as a script attachment.

Today I figured out a better way to do #3. Here is a simple script I wrote to automate the export (names changed).

#!/bin/bash

echo "Reminder: This exports from master."

git archive --prefix=client1/ master | gzip > client1-tools.tar.gz

This saves me a step over copying my working copy and removing .git.

Tuesday, September 15, 2009

Everyday Git Workflow

I use git each day both as an svn client and in git-only environments. Here's my standard workflow for each environment.

Init
If this is a new project, merely create a directory to work in and run git init.

Clone
git clone user@host:repo.git
or
git svn clone -s https://host/path/to/svn/repo (the -s says this uses trunk, branches, and tags directories)

This leaves you with a git repository that is in sync with your upstream.

If you've gone the svn route and need git to understand your svn:ignore props, run this:
git-svn show-ignore > .gitignore (Beware, it takes a while.)

Feature Branches
I generally create a new "feature branch" for each feature I want to work on. This allows me to switch cleanly between features as I see fit.
git checkout -b feature

When moving from branch to branch after I've made changes, I use git stash to save those changes. Git will move your pending changes in your working copy and index with you when you checkout a different branch (handy when you start editing in the wrong branch).
git stash (by itself will create a new stash of your working copy and index)
git stash list (shows all stashes)
git stash pop [] (Removes the named, or first, stash and applies it to your working copy/index)

Beginning of Day
I generally start by checking out the master branch (git checkout master) and updating from upstream.
git pull
or
git svn rebase

If there were changes in there that I need in one of my feature branches, I'll check it out and merge.
git checkout feature
git merge master
If there were any conflicts, fix them as part of the merge.

Working
Once I'm in a feature branch, I typically code there unless an interruption causes me to switch to another. That simply looks like this:
*Code, code, etc*
git add
git commit -m "Here's a short explanation of what I changed. Don't skimp on this part."

git diff can also be useful during the add/commit process to see exactly what you changed. git add --patch can also be used to "stage" (the process of adding changes to the index) individual hunks from each file instead of the entire file.

Sharing Branches
If your sysadmin has given you access to a remote git repository (I recommend using gitosis), you can share your feature branches with other devs without committing to your master/svn repo.

Note that in the following instructions I'm using "origin" as my remote. If you're working with a remote git repo already instead of svn you'll need to use a different name here.

git remote add origin user@host:repo.git
git checkout feature
git push origin feature

Now your coworker can run:
git remote add origin user@host:repo.git
git fetch
git branch -a (lists all branches including remote branches)
git checkout -b feature origin/feature (where "feature" is the local branch name and "origin/feature" is the remote branch name)

After you've done your initial push or checkout, you can just run git push to update the remote branch or git pull to fetch and merge from that remote branch (with the above caveat about already using origin). In this way, you can continue to distribute changes to one another.

Committing Upstream
Once you're done with your feature, it's time to commit to master/svn. Do one last pull or svn rebase on master (as described above). Then it's time to run the process in reverse with one important change.
Assuming your working copy is already on your feature branch.
git merge master (to sync with your upstream in your feature branch)
Fix any conflicts and commit the merge.
git checkout master
git merge --squash feature (The --squash flag will combine all your changes into one complete changeset. This will also leave everything uncommitted in the index so you can have a look.)
git commit -m "Here's a great summary of everything I did in my feature branch that will be visible upstream,"

git push
or
git svn dcommit

Props
I got my start with git-svn after reading a post by the viget guys.

Wednesday, September 02, 2009

git-svn and --squash

When you're using git-svn, it's important to remember to use git merge --squash when you're merging a feature branch back to master in preparation for a git svn dcommit. In case you forget (like I just did) here are some simple steps to repair what you've done without dealing with interactive rebase.

The setup (what you should avoid):
git checkout -b feature
hack, hack, hack, merge with changes on master, etc
git checkout master
git merge feature
git svn dcommit

My experience at this point was that the first changeset from the merge was successfully committed to svn followed by the rebase failing. Dcommit actually will commit each revision independently and rebase between them. I was left in interactive rebase. Instead of trying to clean this up iteratively by running through all the changesets I did the following:

(this is still on the master branch)
git rebase --abort
git reset --merge (the merge option sticks everything in your working copy into the index)
git svn rebase (to get the commit that made it to svn)
git merge --squash feature
git commit -m "Here's my feature for all you svn users!"
git svn dcommit

This will successfully push ONE commit with all your remaining changes up to svn. Hope this helps someone who finds themselves in this situation. I also expect to have to read this again next time I forget --squash. Happy coding!