Differences between revisions 52 and 53
Revision 52 as of 2009-11-29 10:38:37
Size: 13284
Comment: Fix stuff I broke accidently
Revision 53 as of 2009-11-29 13:32:56
Size: 12915
Comment: Link to warning page
Deletions are marked like this. Additions are marked like this.
Line 13: Line 13:
'''WARNING:''' With git 1.6.4 and below, git clone pulls down the whole repository, not just the objects referenced from the repository heads, which means an extra 150MB or so of data that isn't really usable. See [[http://thread.gmane.org/gmane.comp.version-control.git/124805|this thread]] for more details. It's probably best for even read-only users to use the "Advanced" procedure below; although the setup is a bit complex, usage is not.

If you don't have commit access to the GCC SVN repository, and you are happy with getting the default set of trunk and 4.x branches, you can just do

If you don't have commit access to the GCC SVN repository, and you are happy with getting the default set of trunk and 4.x branches, you can just do (git <= 1.6.4 [[http://gcc.gnu.org/wiki/GitMirror/WARNING_1_6_4| Warning]])

GCC GIT mirror

There is a read-only git mirror of the gcc svn repository available at:

http://gcc.gnu.org/git/gcc.git
git://gcc.gnu.org/git/gcc.git
git+ssh://gcc.gnu.org/git/gcc.git

It can be browsed at the repo.or.cz web viewer.

Simple (Read-Only) Usage

If you don't have commit access to the GCC SVN repository, and you are happy with getting the default set of trunk and 4.x branches, you can just do (git <= 1.6.4 Warning)

git clone git://gcc.gnu.org/git/gcc.git

and then

git pull

any time you want to update your tree. You will probably also want to set up git-merge-changelog, as described later. To extract patches for submission, use

git format-patch origin/trunk

Advanced Usage

The above works fine for browsing the repository and simple hacking, but has various issues. Here's what seems to me to be the optimal way of using git-svn with the GCC repository. Please email me (jason) if you have any questions/issues with these instructions.

Setup

mkdir gcc-git
cd gcc-git
git init
# Need to use this exact SVN url for gcc.git and git-svn to work together.  Adding an SSH username to the url is ok.
git svn init -s svn+ssh://gcc.gnu.org/svn/gcc
git remote add origin git://gcc.gnu.org/git/gcc.git
# Get the trunk.
git config remote.origin.fetch refs/remotes/trunk:refs/remotes/trunk
# Also get the various release branches so we can refer to them as needed.
for f in 2_95 3_0 3_1 3_2 3_3 3_4 4_0 4_1 4_2 4_3 4_4; do git config --add remote.origin.fetch refs/remotes/gcc-$f-branch:refs/remotes/gcc-$f-branch; done
for f in 1_00 1_1; do git config --add remote.origin.fetch refs/remotes/egcs_${f}_branch:refs/remotes/egcs_${f}_branch; done
# Add more branches if you want, e.g.
# git config --add remote.origin.fetch refs/remotes/lto:refs/remotes/lto
# If you want all branches, do
# git config remote.origin.fetch refs/remotes/*:refs/remotes/*
# Actually pull down the stuff.
git fetch
# Create master branch tracking SVN trunk
git checkout -b master trunk
# Ignore the same files that SVN does.
git svn show-ignore >> .git/info/exclude

and you're all set to start hacking. Note that the first remote.origin.fetch line doesn't use --add because we aren't interested in origin's heads, just its remotes that track SVN. The uncommented lines pull down the SVN trunk and release branches; look at the commented lines if you want other branches. Pulling all branches increases the size of the .git directory by about 30% over just trunk and release branches.

If you want a branch that lives in a subdirectory of branches, such as redhat/gcc-4_4-branch, you can't get it from the gcc.git mirror for some reason, so leave out the remote.origin.fetch line for that branch (and don't use the wildcard fetch commented out above), and use git svn fetch to fetch the branch from SVN:

git config --unset svn-remote.svn.branches
git config --unset svn-remote.svn.tags
git config --add svn-remote.svn.fetch branches/redhat/gcc-4_4-branch:refs/remotes/redhat/gcc-4_4-branch
git svn fetch
git svn init -s svn+ssh://gcc.gnu.org/svn/gcc

clearing branches and tags across the fetch so it doesn't pull the whole world down from SVN (and take forever).

git-merge-changelog

Before long you'll get frustrated with git's handling of ChangeLog merges, which is just as bad as SVN's. But there's a fix for that!

git clone git://git.savannah.gnu.org/gnulib.git
cd gnulib
./gnulib-tool --create-testdir --dir=/tmp/testdir123 git-merge-changelog
cd /tmp/testdir123
./configure
make
make install
git config --global merge.merge-changelog.name "GNU-style ChangeLog merge driver"
git config --global merge.merge-changelog.driver "/usr/local/bin/git-merge-changelog %O %A %B"
echo "ChangeLog   merge=merge-changelog" >> ~/gcc-git/.git/info/attributes

NOTE: git-merge-changelog used to be extremely slow for cherry-picking changes from trunk to release branches, but this was fixed with a change on 2009-07-02.

Usage

Pull changes from the git mirror as desired using

git pull

or from the SVN repository (e.g. if you need a change that isn't in the git mirror yet, or are working on a branch that isn't in the git mirror) using

git svn rebase

I routinely do both of these to update my tree, depending on the circumstance: * git pull without --rebase gives a nice summary of what files have been updated, and will pull into a dirty working tree, and preserves the timestamps on files that aren't affected by the pull, but does a merge if I have any local commits; * git svn rebase grabs any very recent changes and rebases my local commits on top of the current SVN head in preparation for a checkin.

You probably don't want to use git svn fetch other than for the initial import of a branch, as shown above.

To look at your local changes, use

git log -p trunk..

To push all of them to the SVN server, use

git svn dcommit

Update bash function

Here's my generic "update this source tree" bash function. When I want to rebase, I do that manually either with 'git svn rebase' or 'git rebase -i trunk'

up() { if [ -d CVS ]; then cvs -q update "$@";
       elif [ -d .svn ]; then svn up "$@";
       elif gitd=`git rev-parse --git-dir 2>/dev/null`; then
           if [ -f $gitd/objects/info/alternates ]; then
               for d in `cat $gitd/objects/info/alternates`; do
                   (cd $d; git fetch)
               done
           fi
           if [ -d $gitd/patches ] && `stg top 2>/dev/null`; then
               stg pull
           else
               git pull
           fi
       fi; }

Working with other branches

To switch your current working directory to a different branch, first set up a local tracking branch:

git checkout -b gcc44 gcc-4_4-branch

Then you can switch between branches with

git checkout master
git checkout gcc44

To make a separate working directory for hacking on another branch at the same time, you can do

BRANCH=mybranch
mkdir $BRANCH
cd $BRANCH
git init
# Tell git not to re-fetch shared objects in your main repository
echo ~/gcc-git/.git/objects >> .git/objects/info/alternates
cp ~/gcc-git/.git/info/{attributes,exclude} .git/info/
git svn init -s svn+ssh://gcc.gnu.org/svn/gcc
git remote add origin git://gcc.gnu.org/git/gcc.git
git config remote.origin.fetch refs/remotes/trunk:refs/remotes/trunk
git config --add remote.origin.fetch refs/remotes/$BRANCH:refs/remotes/$BRANCH
# Make sure the main repository is up to date; see below
(cd ~/gcc-git; git fetch)
git fetch
git checkout -b master $BRANCH

And then do pull/rebase/dcommit as with your main repository. This repository should be significantly smaller than the main one as it only contains the changes on the branch itself; it uses your main repository for anything on the trunk. In fact, if you do this for a branch that's already in your main repository, the .git directory in the new repository will be less than 10MB. If you pull in this repository sooner than in the main one, you'll get a few duplicate objects here, but you can clean them up with 'git gc'. You can use this repository without doing anything in your trunk repository as long as you don't delete the trunk repository or remove branches that this one is using.

The double fetch is to work around an apparent shortcoming of the "alternates" functionality (at least with 1.6.4): if the alternate repository doesn't have the objects at the heads of *all* the branches you are fetching with the initial fetch, git's quickfetch fails and it will download everything. So if your main repository is even one commit behind the head of any branch, git will re-download the whole shebang all over again. So we need to make sure that the main repository is fully up to date before we do the initial fetch.

Note that using git cherry-pick to copy changes between SVN branches brings along svn metadata which could confuse git-svn into thinking you're on a different SVN branch, so be sure to do a git commit --amend to edit the commit message before dcommitting to the new branch.

rebase -i

git rebase -i is a very useful tool for organizing local changes for svn dcommit.

If I want to update a patch other than the current one, I just check in my current work and use git rebase -i to move it up the list and squash it into my earlier patch. Another approach would be to stash my current changes, tell rebase -i to edit the patch I want to change, pop the stash and then commit --amend.

If I want to apply some but not all of my current commits to SVN, I use git rebase -i to move the ones I want to apply to the top of the list, select "edit" for the last one I want to apply, and do git svn dcommit before git rebase --continue.

Useful aliases

Here are some aliases I'm finding useful (to add to your ~/.gitconfig). Note that these only work with the advanced setup due to differences in the branch.$BRANCH.merge config.

[alias]
        st = status
        ci = commit
        br = branch
        co = checkout
        sr = svn rebase
        sci = svn dcommit
        # The current branch.
        cbr = "!expr `git symbolic-ref HEAD` : 'refs/heads/\\(.*\\)'"
        # The branch being tracked by the current branch.
        track = "!git config --get branch.`git cbr`.merge"
        # Show all the local commits on this branch.
        lg = "!git log -p `git track`.."
        # Write all the local commits to ~/patch, filtering out modifications to ChangeLog files
        lgp = "!git log -p `git track`.. | filterdiff -x '*/ChangeLog' | sed -e '/^diff.*ChangeLog/{N;d}' > ~/patch"
        # Show all the local changes on this branch as one big diff.
        df = "!git diff `git track`"
        dfc = diff --cached
        # Reorganize the local commits on this branch.
        rb = "!git rebase -i `git track`"
        rc = rebase --continue
        # 'git rmerge mybranch' to reintegrate a temporary branch onto the top of the current branch
        rmerge = "!f(){ cur=`git cbr`; git rebase $cur $1; git rebase $1 $cur; }; f"

Moving changes between machines

I tend to do development on my laptop, and then send patches off to a hefty compile server to do full regression testing. Before git, I did this by rsyncing diffs, but git simplifies the process. First, add to your gcc-git/.git/config:

[remote "testbox"]
        url = testbox.foo.bar:gcc-git
        push = +master:master

Then to push your current changes to the testbox, just "git push testbox". Note that you'll then need to "git co -f" or "git reset --hard" on the testbox to update the working copy from the git repository. This could be another alias:

        tup = "!git push testbox && ssh testbox.foo.bar 'cd gcc-git; git co -f'"

Old Information

Below here is information that no longer describes my use of git, but that other people might find interesting.

Stacked Git

I used to find Stacked Git useful for hacking on GCC, since it makes it easy to pop off patches that I'm still working on so I can check the one I've just finished testing into SVN. But lately I've stopped using it and just use git branches directly.

With stg, if I want to remove a commit from my tree temporarily and come back to it later, I do

stg uncommit mypatchname [myotherpatchname...]
stg pop -a

and then when I want to work on it again,

stg push mypatchname [myotherpatchname...]
stg commit

This way stg only knows about unapplied patches, so it won't get confused when I do a 'git pull'.

And I periodically look at the list of suspended patches with

stg series

Making Patches

I currently make patches for gcc-patches using my 'git lgp' alias, below.

However, git always produces unified diffs, while the GCC project policy is to prefer context diffs. I don't bother with this, but if you really want to make a context diff, you can create a wrapper script somewhere:

cat > ~/bin/git_diff_wrapper <<EOF
#! /bin/sh
exec diff -p -L "$1" "$2" "$5" | cat
EOF
chmod +x ~/bin/git_diff_wrapper

and when you want to make a context diff for submitting a patch, do

GIT_EXTERNAL_DIFF=git_diff_wrapper git diff trunk

Further Reading

An introduction to git-svn for Subversion/SVK users and deserters

git-svn workflow

None: GitMirror (last edited 2017-09-15 17:54:29 by JasonMerrill)