More git goodies

Posted by craig

If you are in a subdirectory of your project and want to know where the .git directory lives above you:

git rev-parse --git-dir

My coworker Guillermo Castro (aka the JavaGeek) found this one by pure necessity. He was told recently that a bunch of related changes which had been checked in over a series of separate commits needed to be merged into an emergency patch branch for production. No problem he thought, git cherry-pick to the rescue! Not so fast. For 90% of the cases, yes he could just cherry-pick an entire commit into the patch branch. But there was that 10% of the cases where few files that were supposed to be part of the patch which were checked in to commits that contained changes to files which were not to be included in the branch. Here is how he solved this problem:

* he knew that the files he was interested resided under two directories * he knew the sha1 commit hash for the commit that contained changes to the files he was interested in (but also had commits to other files he wasn’t) * he wanted only the changes to files under the two directories to be part of his commit.
git log -1 -p <commit hash> <paths or file names>

The “-1” tells git to only prepare a patch file for the files under the paths specified based on the first commit (which is the one you specified in the param.

Along with the above, he also found a way to cherry-pick commits without actually committing them locally (i.e. just keep the changes staged)
git cherry-pick --no-commit <commits>

Also, if you are on a Mac, there is a nice version of gitk called “gitx” I highly recommend you grab. It’s “pretty” and has some neat features which you can find in the screencasts section of the site.


git-svn gotcha

Posted by craig

I was having problems with the following setup the other day:

[remote deveoper] <===> [shared git repo] <===> [me] <===> [client's svn repo]

So my remote developer and I push and pull to/from the shared git repo, and then I sync changes to and from the client’s svn repo using git-svn.
h1. Problem My problem is, when I am ready to merge changes from my local master branch to trunk-local, if I do a “git merge master” and then try to issue any git-svn commands I get the following errors:
======================
$ git merge master
Updating d88106e..77b86ae
Fast forward
 community/pom.xml |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

$ git svn dcommit
Can't call method "full_url" on an undefined value at
/usr/local/git/libexec/git-core/git-svn line 425.

$ git svn rebase
Unable to determine upstream SVN information from working tree history
======================

The only way I’ve seem to be able to remedy this is if I add the “subtree” merge strategy to the merge command:

git merge -s subtree master

Then git-svn doesn’t get confused about it’s repo, but when you look at the repo using gitk, you see something like:

[trunk-local]--[remotes/trunk]  Merge branch 'master' into trunk-local
| \
|  \
|    [master]--[remotes/origin/master]  "last master commit msg" 
|    |
|    |
|  /
/

When I use the normal merge strategy then gitk shows all branches at the same level, but git-svn is of course b0rked.

Solution

I tried asking a few people on #git (freenode irc channel for git) for help, but the problem was a bit too involved to explain on irc and have someon follow along, so instead what I did was I asked on the official git mailing list (git@vger.kernel.org) and I got an answer almost immediately from Björn Steinbrink with a solution that seems to work just great! His input was to use --no-ff when merging my changes into the svn tracking branch (trunk-local). Here is his explanation:

The original merge you did ended up as a fast-forward, ie. no merge commit was created. I guess that your history is so, that somehow the remotes/trunk stuff is reachable through the second parent of some merge commit that exists in your history. But git-svn uses—first-parent to find its upstream, so it cannot find that in your scenario. I guess it’s best if you use “git merge—no-ff master” to force the creation of a merge commit. Subtree happens to work because it implies—no-ff, but I’m not sure whether there might be downsides to using the subtree strategy, so I’d rather go with the explicit—no-ff and the normal merge strategies.

Hopes this helps other’s in the same situation as I was in. Thanks Björn Steinbrink


Tip: mimic user file permissions to group

Posted by craig

I ran into a situation the other day where I had setup a git repository for myself and wanted to grant read/write access to another fellow who is helping out on the project. He could pull from the repository just fine, but couldn’t push to it because the directory permissions on the repo were only setup for me to write to.

After creating a new group for us, adding our user ids to it and then doing a recursive chgrp on the repo I realized that by default git repositories are not setup for group writes unless you explicitly specify that at the time of creations (i.e. git init --shared=group. So I really needed a way to recursively mirror the user’s file permissions on all files and directories in the repo to that of the group.

Searching long and hard I could not find a canned solution to this, perhaps one exists out there but because “mimic user file permissions to group” and “mirror user file permissions to group” don’t yield good results in google, I was forced to write my own Perl script:

#!/usr/bin/perl
use File::stat;
use Fcntl ':mode';

foreach (@ARGV) {
    next unless -e $_;
    $stats = stat($_);
    $mode = substr(sprintf("%04o", $stats->mode), -3);
    $out = "$_ from $mode to ";
    $mode =~ s/(\d)(\d)(\d)/$1$1$3/;
    $out = "$out $mode";
    printf($out . "\n");
    chmod oct($mode), $_;
}

The Usage on this script is:

mimicuserperms.pl <name of file or directory>

To use it recursively:

find . -exec mimicuserperms.pl {} \;

DISCLAIMER: Not sure if this will work on every single system out there, I mean it literally just grabs the last three file perms found by stat and takes the first character and duplicates it over the second. So use at your own risk! Perhaps, backup whatever you are going to use this on first before trying.


git gotcha

Posted by craig

Just thought I’d post about a problem I was having with git concerning the setup and cloning of a remote repository. Basically, if you’ve setup a repository on a remote machine, and want to clone from that machine onto a local one using ssh, you may run into problems like I did.

The error I received was:

ThaDonMBP:workspace craiger$ git clone craiger@192.168.2.10:/git/myproj
Initialized empty Git repository in /private/workspace/myproj/.git
craiger@192.168.2.10's password: 
bash: git-upload-pack: command not found
fatal: The remote end hung up unexpectedly

The problem is that git is expecting git-upload-pack in a specific directory on the remote machine, and that is /usr/bin or /usr/local/bin. That’s not where I had git installed, I had it installed in /opt/git/bin. So all you need to do is symlink your git executables to /usr/bin like so:

craiger@192.168.2.10:~$ cd /usr/bin
craiger@192.168.2.10:/usr/bin$ sudo ln -s /opt/git/bin/* .

After that, things should work!


A few git-svn Tips

Posted by craig

  1. clone a remote svn repo and it’s branches and tags
    git-svn clone -T trunk -b branches -t tags -r 1124:HEAD https://mysvnserver/myproj
    this will bring down all history from revision 1124 onward intoa local git repository and will create remote branches that track the projects inside your myproj/branches and myproj/tags folders

  2. find out what remote svn branch your local git branch is tracking:
    git log --no-color --first-parent | grep git-svn-id | head -n1

  3. given you did the clone from step 1, you could create a local branch which tracks a remote branch (so that your git-svn rebase/decommit commands execute against the right remote branch)
    git checkout -b trunk-local trunk
    this will create a new local branch called “trunk-local” that tracks the remote “trunk” branch in svn. Any git-svn rebase or dcommit commands issued while trunk-local is your current branch will be executed against the remote trunk branch.

  4. after executing the code from step 2, you find that your local master branch is setup against a remote branch other than “trunk” (I like having my local master branch tracking trunk) you can reset it to track trunk by issuing this command:
    git reset --hard trunk

  5. want to merge changes from one local branch to the current branch, but don’t want the history of commits? That is, if after the merge you do a git-svn dcommit you don’t want a separate commit for each “git commit” that took place in the “branch to merge to current”, you just want one uber commit.
    git merge --squash <branch to merge to current>

  6. refer back to the last local commit message (useful if you forgot to include a file in the last commit and would like to re-use the message)
    git log HEAD^..HEAD --pretty=format:%s


Yeah, so git is good.

Posted by craig

Late Adopter

I’m a late adopter of almost everything. Being a long time “windows guy” I typically wait at least 2 years after a new version of windows is released before upgrading. I am on Vista currently only because it came with my new PC, if not for that fact I’d be still slinging XP. The only reason I’m on Windows at all is because of gaming and my inherent laziness to create a dual boot system. Things have changed recently though, when I got the new PC it did not come with video card, it was onboard, and not anything I could play a modern game with. So this means I haven’t played a PC game since approx. Dec 27th (not that I’m keeping track or anything :P).

Because of my previous contracts, I’ve been developing on Windows exclusively. My choices were usually limited by corporate standards, lack of Linux vpn client, etc… But with my current contract I have a choice, so what I’ve done is I’ve been running Ubuntu within a VMWare session on Vista. I really like this option for two reasons:
  1. I can backup my entire development environment, OS and all, by simply copying the VMWare image file
  2. If I need to work from my laptop, I copy the image over and I am up in running in a matter of minutes in the exact same environment (no saying “oh crap, tool XYZ isn’t installed”)

git, a different animal

So…. getting back to the subject, I don’t typically change unless I need to. I’ve been coding Java for roughly 8 years, prior to that it was VB. I tried RoR, but never really got farther than admireing Ruby’s closures. That’s the trouble, when you are developing professionally in a language and wish to “switch” it’s not like you can just go out onto the market and say “I’m a Java developer looking for a contract that teaches me RoR”. It just doesn’t work that way, people want you to come in on a contract hitting the ground running, not crawling. Same goes for version control systems, you use what the corporation paying your salary uses. Sure you might oggle the features of some other VCS, but unless you have a set of personal projects you can use it on, you aren’t going to get to know that other system.

Sure you can learn things “on your own”, in your “spare time” (does such a thing exist for some people?) but unless you do something like contribute to an OSS project, or create a publically facing app, you won’t have the experience to go along with your self teaching. I have found experience is what sells you out in the market.

git is different though, git allows you to use it without anyone else knowing you are using it. That’s a great feature, it’s almost like if you were able to write your code in Ruby/Rails and just prior to commiting you run the magic program which converts it to Java. You get to learn the ins and outs of a new framework and someone else pays you to do it. What I’ve been able to do with git is clone my client’s svn repository into a local git repository using git-svn and commit/branch against the local version. This is great because I can work on several features at the same time, and when one is ready to be promoted up to the svn repo, I can commit only those changes I made to my local “feature branch”! When I’m done committing the feature I was working, I switch out of it’s branch, execute a an git-svn rebase in my other feature’s branch, and continue my work.

The above reasons contribute to why I’m a relatively early adopter of git (at the insistence of Marc and Alex mind you), for the simple reason that it’s unobtrusive.

The Changeset Shuffle

> svn stat
? com/mycompany/feature/new/SomeNewFeature.java
M com/mycompany/feature/old/SomeOldFeatureThatNeededFixing.java
M com/mycompany/common/SomeCommonClassModifiedByBothFeatures.java   <--- Ah crap!
> cp com/mycompany/common/SomeCommonClassModifiedByBothFeatures.java SomeCommonClassModifiedByBothFeatures.java.new
> vim com/mycompany/common/SomeCommonClassModifiedByBothFeatures.java <--- remove the new features
> svn ci -m "qa fix 10032" 
> mv SomeCommonClassModifiedByBothFeatures.java.new com/mycompany/common/SomeCommonClassModifiedByBothFeatures.java

git really helps you in those times where you are working on some new/experimental feature and then are hit with a critical bug fix found during a QA cycle. Instead of doing the “change-set shuffle” ; you know, when you are frantically trying to figure out which files should be commited that were part of the fix and which were part of the experimental stuff ; you instead just “stash” away the experimental stuff, or branch your master and fix from there. I know you SVN’ites are going to tell me “but I can branch just as well as you can!”. This is true to some extent, however with git I can do it without cluttering up the remote svn repo with a separate branch for the change. Using git I can easily and safely from the comfort of my own machine, branch, fix, commit a change and reconcile it to the remote svn repo. All of my git commits, all of the comments I made for those commits (you do comment your commits right!) all get pushed up to the svn repo. Beautiful.

How to learn

Marc put me on to PeepCode specifically their excellent git intro screen cast. I’m very much a visual learner, so the screen cast format was an excellent choice for me. You can keep track of some of the other resources I find helpful with git, by taking a look at my del.icio.us links.

In conclusion…

I find git really rocks because:
  • I can use it in a corporate environment that doesn’t support it
    • I can’t do that easily with other VCS
  • It promotes “playing”
    • I can branch, be merry, experiment without the changeset-shuffle
  • git promotes branching!
    • yeah, people are shit scared of branching in svn, mostly from a lack of understanding, but a fair bit of it stems from the fact you are branching in the remote repository, you just don’t want to mess things up!
  • It’s fast, like really fast.
  • It’s distributed, so you can have people clone your repo to their local systems and then they can push their changes to you or visa versa

I guess the one gripe I might have is that instead of revision numbers, git uses “hashes” to mark an atomic checkin. I don’t know about you, but I am constantly asked at work “What revision was that changed checked into?” It’s a lot easier to answer “rev.381” than to say “rev.9a02133e6f288864bc6bc9b18ec01732649892h3”

I think Marc said it best, you have to try it in order to have that “a hah” moment, so go out and try it! Need help? Let us know on freenode in #basementcoders, or leave a comment right here.