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!


Oracle Sproc Tip

Posted by craig

A colleague of mine, Guillermo Castro, showed me how to print the results of a stored procedure call that returns a ref cursor in Oracle.

In SqlDeveloper you would make a script like so:

var results ref cursor;
var o_status number;
var o_err_msg varchar2;
exec :results := myschema.MY_SPROC(773490, 'TEST', :o_status, :o_err_msg);
print results;
print o_status;
print o_err_msg;

This will output the values of the refcursor (a result set generated by MY_SPROC) as well as two “normal” out parameters :o_status and :o_err_msg.


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