loading...

Git – Using Git with Subversion Repositories

How to install web server (IIS) on Windows Server 2019

As you become more and more comfortable with Git, you’ll
likely find it harder and harder to work without such a capable tool. But
sometimes you’ll have to do without Git—say, if you work with a team whose
source code is managed by some other VCS. (SVN, for example, is popular
among open source projects.) Luckily, the Git developers have created
numerous plug-ins to import and synchronize source code revisions with other
systems.

This chapter demonstrates how to use Git when the rest of your team
employs SVN. This chapter also provides guidance if more of your teammates
want to make the switch to Git, and it explains what to do if your team
wants to drop SVN entirely.

Example: A Shallow Clone of a Single Branch

To begin, let’s make a shallow clone of a single SVN
branch. Specifically, let’s work with the source code of SVN itself (which
is guaranteed to be managed with SVN for as long as this book is in print)
and a particular set of revisions, 33005 through 33142, from the 1.5.x
branch of SVN.

The first step is to clone the SVN repository:

    $ git svn clone -r33005:33142 \
        http://svn.collab.net/repos/
      svn/branches/1.5.x/ svn.git

Tip

In some Git packages, such as those provided by the Debian and
Ubuntu Linux distributions, the git
svn
command is an optional part of Git.
If you type git svn and are warned
that svn is not a
git-command,
try to install the git-svn
package. (See Chapter 2 for details about
installing Git packages.)

The git svn clone command is more
verbose than the typical git clone and
is usually slower than running either Git or SVN separately.[36] In this example, however, the initial clone won’t be
too slow, because the working set is but a small
portion of the history of a single branch.

Once git svn clone finishes,
glance at your new Git repository:

    $ cd svn.git

    $ ls
    ./           build/        contrib/      HACKING      README       win-tests.py
    ../          build.conf    COPYING       INSTALL      STATUS       www/
    aclocal.m4   CHANGES       doc/          Makefile.in  subversion/
    autogen.sh*  COMMITTERS    gen-make.py*  notes/       tools/
    BUGS         configure.ac  .git/         packages/    TRANSLATING

    $ git branch -a
    * master
      git-svn

    $ git log -1
    commit 05026566123844aa2d65a6896bf7c6e65fc53f7c
    Author: hwright <hwright@612f8ebc-c883-4be0-9ee0-a4e9ef946e3a>
    Date:   Wed Sep 17 17:45:15 2008 +0000

        Merge r32790, r32796, r32798 from trunk:

         * r32790, r32796, r32798
           Fix issue #2505: make switch continue after deleting locally modified
           directories, as it update and merge do.
           Notes:
             r32796 updates the docstring.
             r32798 is an obvious fix.
           Justification:
             Small fix (with test).  User requests.
           Votes:
             +1: danielsh, zhakov, cmpilato


        git-svn-id: http://svn.collab.net/repos/svn/branches/
			  1.5.x@33142 612f8ebc-c883-4be0-9ee0-a4e9ef946e3a

    $ git log --pretty=oneline --abbrev-commit
    0502656... Merge r32790, r32796, r32798 from trunk:
    77a44ab... Cast some votes, approving changes.
    de50536... Add r33136 to the r33137 group.
    96d6de4... Recommend r33137 for backport to 1.5.x.
    e2d810c... * STATUS: Nominate r32771 and vote for r32968, r32975.
    23e5373...  * subversion/po/ko.po: Korean translation updated (no 
							fuzzy left; applied from trunk of r33034)
    92902fa...  * subversion/po/ko.po: Merged translation from trunk r32990
    4e7f79a... Per the proposal in
                    http://svn.haxx.se/dev/archive-2008-08/0148.shtml, 
					Add release stream openness indications to the
                    STATUS files on our various release branches.
    f9eae83... Merge r31546 from trunk:

There are a few things to observe:

  • You can now manipulate all the imported commits directly
    with Git, ignoring the SVN server. Only git
    svn
    commands talk to the server; other Git commands such as
    git blame, git log, and git
    diff
    are as fast as always and function even when you’re not
    online. This offline feature is a major reason developers prefer to
    use git svn instead of SVN.

  • The working directory lacks .svn directories, but it does have the
    familiar .git directory. Prior to SVN version 1.7,
    when you check out an SVN project, each subdirectory contains a
    .svn directory for bookkeeping.
    However, git svn does its
    bookkeeping in the .git
    directory, as Git always does. The git
    svn
    command does use an extra directory called .git/svn, which is described
    momentarily.

  • Even though you checked out a branch named 1.5.x, the local branch has the standard Git
    name master. Nonetheless, it still
    corresponds to the 1.5.x branch,
    revision 33142. The local repository also has a remote ref called
    git-svn, which is the parent of the
    local master branch.

  • The author’s name and email address in git log is atypical for Git. For example,
    the author is listed as hwright
    instead of the author’s real name, Hyrum Wright. Moreover, his email
    address a string of hex digits. Unfortunately, SVN doesn’t store an
    author’s full name or email address. Instead, it stores only the
    author’s login, which in this case is hwright. However, because Git wants the
    extra information, git svn
    fabricates it. The string of hex digits is the unique ID of the SVN
    repository. With it, Git can uniquely identify
    this particular hwright user on this particular server by
    using his generated email address.

Tip

If you know the proper name and email address of every
developer in your SVN project, you can specify the
--authors-file option to use a list of known identities
instead of a set of manufactured ones. However, this is optional and
matters only if you care about the aesthetics of your logs. Most
developers don’t. Run the command git help
svn
for more information.

Note

User identification differs between SVN and Git. Every SVN
user must have a login on the central repository server to make a
commit. Login names must be unique and thus are suitable for
identification in SVN.

Git, on the other hand, does not require a server. In Git’s case,
the user’s email address is the only reliable, easily understood, and
globally unique string.

  • SVN users don’t typically write one-line summaries in commit
    messages, as Git users do, so the one line format from git log produces rather ugly results.
    There’s not much you can do about this, but you might ask or encourage
    your SVN colleagues to adopt the one-line summary voluntarily. After
    all, a one-line summary is helpful in any VCS.

  • There’s an extra line in each commit message, prefixed
    with git-svn-id. This line is used
    by git svn to keep track of where
    the commit came from. In this case, the commit came from http://svn.collab.net/repos/svn/branches/1.5.x, as of
    revision 33142, and the server unique ID is the same one used to
    generate Hyrum’s fake email address.

  • git svn created a new
    commit ID number (0502656…) for each commit. If you used exactly the
    same Git software and command-line options as those shown here, then
    the commit numbers you see on your local system should likewise be
    identical. That’s appropriate, because your local commits are the same
    commits from the same remote repository. This detail is critical in
    certain git svn work flows, as
    you’ll see shortly.

    It’s also fragile. If you use different git svn clone options, even just cloning a
    different revision sequence, then all your commit
    IDs will change.

Making Your Changes in Git

Now that you have a Git repository of SVN source code, the
next thing to do is make a change:

    $ echo 'I am now a subversion developer!' >hello.txt
    $ git add hello.txt
    $ git commit -m 'My first subversion commit'

Congratulations, you’ve contributed your first change to the SVN
source code!

Well, not really. You’ve committed your first
change to the SVN source code. In plain SVN, where every commit is
stored in the central repository, committing a change and sharing it
with everyone is the same thing. In Git, however, a commit is just an
object in your local repository until you push the change to someone
else. And git svn doesn’t change
that.

Alas, if you want to contribute your changes back, the usual Git
operation doesn’t work:

    $ git push origin master
    fatal: 'origin': unable to chdir or not a git archive
    fatal: The remote end hung up unexpectedly

In other words: you didn’t create a Git remote called origin, so the command doesn’t make any sense.
(For more about defining remotes, see Chapter 12.) In fact, a Git remote won’t
solve this problem. If you want to commit back to SVN, you must use
git svn dcommit.[37]

    $ git svn dcommit
    Committing to http://svn.collab.net/repos/svn/branches/1.5.x ...
    Authentication realm: <http://svn.collab.net:80> Subversion Committers
    Password for 'bob':

If you actually had commit access to the central SVN source code
repository (only a few people in the world have this privilege), you
would enter your password at the prompt and git
svn
would do its magic. But then things would become even more
confusing, because you’re trying to commit to a revision that isn’t the
latest one.

Let’s examine what to do about this next.

Fetching Before Committing

Recall that SVN keeps a linear, sequential view of
history. If your local copy has an older version from the SVN repository
(it does) and you’ve made a commit to that old version (you did), then
there’s no way to send it back to the server. SVN simply has no way of
creating a new branch at an earlier point in the history of a
project.

However, you did create a fork in the history, as a Git commit
always does. That leaves two possibilities:

  1. The history was intentionally forked. You want to keep both
    parts of the history, merge them together, and commit the merge to
    SVN.

  2. The fork wasn’t intentional and it would be better to
    linearize it and then commit.

Does this sound familiar? It’s similar to the choice between
merging and rebasing discussed in rebase Versus merge of Chapter 10. The former option corresponds to
git merge, and the latter is akin to
git rebase.

The good news here is that, once again, Git offers both options.
The bad news is that SVN is going lose some part of your history no
matter which option is chosen.

To continue, fetch the latest revisions from SVN:[38]

    $ git svn fetch
        M       STATUS
        M       build.conf
        M       COMMITTERS
    r33143 = 152840fb7ec59d642362b2de5d8f98ba87d58a87 (git-svn)
        M       STATUS
    r33193 = 13fc53806d777e3035f26ff5d1eedd5d1b157317 (git-svn)
        M       STATUS
    r33194 = d70041fd576337b1d0e605d7f4eb2feb8ce08f86 (git-svn)

You can interpret the previous log messages as follows:

  • The M means a file was
    modified.

  • r33143 is the SVN revision
    number of a change.

  • 152840f... is the
    corresponding Git commit ID generated by git svn.

  • git-svn is the name of the
    remote ref that’s been updated with the new commit.

Let’s look at what’s going on:

    $ git log --pretty=oneline --abbrev-commit --left-right master...git-svn
    <2e5f71c... My first subversion commit
    >d70041f... * STATUS: Added note to r33173.
    >13fc538... * STATUS: Nominate r33173 for backport.
    >152840f... Merge r31203 from trunk:

In plain English, the left branch ( master) has one new commit and the right
branch ( git-svn) has three new
commits. (You’ll likely see different output when you run the command
because this output was captured during production of the book.) The
--left-right
option and the symmetric difference operator ( ...) are discussed in git log with conflicts of Chapter 9 and in Commit Ranges
of Chapter 6, respectively.

Before you can commit back to SVN, you need one branch with all
the commits in one place. Additionally, any new commits must be relative
to the current state of the git-svn
branch because that’s all SVN knows how to do.

Committing Through git svn rebase

The most obvious way to add your changes is to rebase them
on top of the git-svn branch:

    $ git checkout master

    # Rebase current master branch on the upstream git-svn branch
    $ git rebase git-svn
    First, rewinding head to replay your work on top of it...
    Applying: My first subversion commit

    $ git log --pretty=oneline --abbrev-commit --left-right master...git-svn
     <0c4c620... My first subversion commit

A shortcut for git svn fetch
followed by git rebase git-svn is
simply git svn rebase. The latter
command automatically deduces that your branch is based on the one
called git-svn, fetches that from
SVN, and rebases your branch onto it. Furthermore, when git svn dcommit notices that your SVN branch
is out of date, it doesn’t just give up; it automatically calls git svn rebase first.

Warning

If you always want to rebase instead of merging, git svn rebase is a great time saver. But if
you don’t like rewriting history by default, you must be
very careful not to dcommit until
you’ve done git svn fetch and
git merge
manually
.

If you’re just using Git as a convenient way to access your SVN
history, then rebasing is fine—just as git
rebase
is a perfectly fine way to rearrange a set of patches
you’re working on—as long as you’ve never pushed those patches to anyone
else. But rebasing with git svn faces
all the same drawbacks as rebasing in general.

If you rebase your patches before committing them to SVN, make
sure you understand the following:

  • Don’t create local branches and git
    merge
    them. As mentioned in rebase Versus merge of Chapter 10, rebasing confuses git merge. With plain Git, you can choose
    not to rebase any branch that another branch is based on, but with
    git svn you don’t have that
    option. All your branches are based on the
    git-svn branch, and that’s the
    one that all other branches need to be based on.

  • Don’t let anyone pull from or clone your repository; let them
    use git svn to create their own
    Git repository instead. Because pulling one repository into another
    always causes a merge, it
    won’t work, and for the same reason that git merge won’t work when you’ve rebased
    your repository.

  • Rebase and dcommit frequently. Remember, an SVN user does the
    equivalent of a git push every
    time she makes a commit, and that’s still the best way to keep
    things under control when your history has to stay linear.

  • Don’t forget that, when you rebase a series of patches onto
    another branch, the intermediate versions created by the patches
    never really existed and were never really tested. You are
    essentially rewriting history and, indeed, that’s what it is. If
    later you try to use git bisect
    or git blame (or svn blame in SVN) to determine when a
    problem was introduced, you won’t have a true view of what
    happened.

Do these warnings make git svn
rebase
sound dangerous? Good. Every variation of git rebase is treacherous. However, if you
follow the rules and don’t try anything fancy, you’ll be OK.

Now let’s try something fancy.

Pushing, Pulling, Branching, and Merging with git svn

Rebasing all the time is fine if you simply want to use Git
as a glorified SVN repository mirror. Even that by itself is a great step
forward: you get to work offline; you get faster log, blame,
and diff operations; and you don’t
annoy your coworkers who are perfectly happy using SVN. Nobody even has to
know you’re using Git.

But what if you want to do a little more than that? Maybe one of
your coworkers wants to collaborate with you on a new feature using Git.
Or perhaps you want to work on a few topic branches at a time and wait on
committing them back to SVN until you’re sure they’re ready. Most of all,
maybe you find SVN’s merging features tedious and you want to use Git’s
much more advanced capabilities.

If you use git svn
rebase
, you can’t really do any of those things. The good news
is that if you avoid using rebase, git
svn
will let you do it all.

There’s only one catch: your fancy, nonlinear history won’t ever be
in SVN. Your SVN-using coworkers will see the results of your hard work in
the form of an occasional squashed merge commit (see Squash Merges in Chapter 9),
but they won’t be able to see exactly how you got there.

If that’s going to be a problem, you should probably skip the rest
of this chapter. But if your coworkers don’t care—most developers don’t
look at others’ histories, anyway—or if you want to use it to prod your
coworkers to try out Git, then what’s described next is a much more
powerful way to use git svn.

Keeping Your Commit IDs Straight

Recall from Chapter 10 that
a rebase is disruptive because it generates entirely new commits that
represent the same changes. The new commits have new commit IDs, and
when you merge one branch with one of the new commits into another
branch that had one of the old commits, Git has no way of knowing you’re
applying the same change twice. The result is duplicate entries in
git log and sometimes a merge
conflict.

With plain Git, preventing such situations is easy: avoid
git cherry-pick and git rebase and the problems won’t occur at
all. Or use the commands carefully, and issues will occur only in
controlled situations.

With git svn, however,
there’s one more potential source of problems, and it’s not as easy to
avoid. The problem is that the Git commit objects created by your
git svn are not always the same as
the ones produced by other people’s git
svn
, and you can’t do anything about it. For example:

  • If you have a different version of Git than someone else, your
    git svn might generate different
    commits than your coworker. (The Git developers try very hard to
    avoid this, but it can happen.)

  • If you use the --authors-file option to remap
    author names or apply various other git
    svn
    options that change its behavior, all the commit IDs
    will be different.

  • If you use a SVN URI that’s different from someone else
    working in the SVN repository (e.g., if you access an anonymous SVN
    repository but someone else uses an authenticated method to access
    the same repository), then your git-svn-id lines will be different; this
    changes the commit message, which changes the SHA1 of the commit,
    which changes the commit ID.

  • If you fetch only a subset of SVN revisions by using the
    -r option to git svn
    clone
    (as in the first example in this chapter) and
    someone else fetches a different subset, then the history will be
    different and so the commit IDs will be different.

  • If you use git
    merge
    and then git svn
    dcommit
    the results, the new commit will look different to
    you from the same commit that other people retrieve through git svn fetch because only your copy of
    git svn knows the true history of
    that commit. (Remember that, on its way into SVN, the history
    information is lost, so even Git users retrieving from SVN can’t get
    that history back again.)

With all those caveats, it might sound like trying to coordinate
between git svn users is almost
impossible. But there’s one simple trick you can use to avoid all these
problems: make sure there’s only one Git
repository, the gatekeeper, which uses git svn fetch or git
svn dcommit
.

Using this trick has several advantages:

  • Because only one repository ever interfaces with SVN, there
    will never be a problem with incompatible commit IDs, because every
    commit is created only once.

  • Your Git-using coworkers will never have to learn how to use
    git svn.

  • Because all Git users are just using plain Git, they can
    collaborate with each other, using any Git workflow, without
    worrying about SVN.

  • It’s faster to convert a new user from SVN to Git because a
    git clone operation is much
    faster than downloading every single revision from SVN, one at a
    time.

  • If your entire team eventually converts to Git, then you can
    simply unplug the SVN server one day and nobody will know the
    difference.

But there’s one main disadvantage: you end up with a bottleneck
between the Git world and the SVN world. Everything must go through a
single Git repository, which is probably administered by a small number
of people.

At first, compared to a completely distributed Git setup,
requiring a centrally managed git svn
repository may seem like a step backward. But you already have a central
SVN repository, so this doesn’t make matters any worse.

Let’s look at setting up that central gatekeeper
repository.

Cloning All the Branches

Earlier, when you set up a personal git svn repository, the procedure cloned just
a few revisions of a single branch. That’s good enough for one person
who wants to do some work offline, but if an entire team is to share the
same repository then you can’t make assumptions about what parts are
needed and what parts are not. You want all the branches, all the tags,
and all the revisions of each branch.

Because this is such a common requirement, Git has an option to
perform a complete clone. Let’s clone the SVN source code again, but
this time doing all the branches:

    # All on one line
    $ git svn clone --stdlayout --prefix=svn/
        -r33005:33142 http://svn.collab.net/repos/svn svn-all.git

Warning

The best way to produce a gatekeeper repository is to leave out
the -r option entirely. But if you did that here, it
would take hours or even days to complete. As of this writing, the SVN
source code contains tens of thousands of revisions, and git svn would have to download each one
individually over the Internet. If you’re following along with this
example, keep the -r option. But if you’re setting up
a Git repository for your own SVN project, leave it out.

Notice the new options:

  • --stdlayout tells git svn that the repository branches are
    set up in the standard SVN way, with the /trunk, /branches, and /tags subdirectories corresponding
    (respectively) to mainline
    development, branches, and tags. If your repository is laid out
    differently then you can try the --trunk,
    --branches, and --tags options
    instead, or edit .git/config to set the
    refspec option by hand. Type git help svn for more information.

  • --prefix=svn/ creates all the remote
    refs with the prefix svn/,
    allowing you to refer to individual branches as svn/trunk and svn/1.5.x. Without this option, your SVN
    remote refs wouldn’t have any prefix at all, making it easy to
    confuse them with local branches.

git svn should churn for a
while. When it’s all over, the results look like this:

    $ cd svn-all.git
    $ git branch -a -v | cut -c1-60
    * master               0502656 Merge r32790, r32796, r32798
      svn/1.0.x            19e69aa Merge the 1.0.x-issue-2751 br
      svn/1.1.x            e20a6ce Per the proposal in http://sv
      svn/1.2.x            70a5c8a Per the proposal in http://sv
      svn/1.3.x            32f8c36 * STATUS: Leave a breadcrumb
      svn/1.4.x            23ecb32 Per the proposal in http://sv
      svn/1.5.x            0502656 Merge r32790, r32796, r32798
      svn/1.5.x-issue2489  2bbe257 On the 1.5.x-issue2489 branch
      svn/explore-wc       798f467 On the explore-wg branch:
      svn/file-externals   4c6e642 On the file externals branch.
      svn/ignore-mergeinfo e3d51f1 On the ignore-mergeinfo branc
      svn/ignore-prop-mods 7790729 On the ignore-prop-mods branc
      svn/svnpatch-diff    918b5ba On the 'svnpatch-diff' branch
      svn/tree-conflicts   79f44eb On the tree-conflicts branch,
      svn/trunk            ae47f26 Remove YADFC (yet another dep

The local master branch has
automatically been created, but it isn’t what you might expect. It’s
pointing at the same commit as the svn/1.5.x branch, not the svn/trunk branch. Why? The most recent commit
in the range specified with -r belonged to the svn/1.5.x branch. (But don’t count on this
behavior; it’s likely to change in a future version of git svn.) Instead, let’s fix it up to point at
the trunk:

    $ git reset --hard svn/trunk
    HEAD is now at ae47f26 Remove YADFC (yet another deprecated function call).

    $ git branch -a -v | cut -c1-60
    * master               ae47f26 Remove YADFC (yet another dep
      svn/1.0.x            19e69aa Merge the 1.0.x-issue-2751 br
      svn/1.1.x            e20a6ce Per the proposal in http://sv
      svn/1.2.x            70a5c8a Per the proposal in http://sv
      svn/1.3.x            32f8c36 * STATUS: Leave a breadcrumb
      svn/1.4.x            23ecb32 Per the proposal in http://sv
      svn/1.5.x            0502656 Merge r32790, r32796, r32798
      svn/1.5.x-issue2489  2bbe257 On the 1.5.x-issue2489 branch
      svn/explore-wc       798f467 On the explore-wg branch:
      svn/file-externals   4c6e642 On the file externals branch.
      svn/ignore-mergeinfo e3d51f1 On the ignore-mergeinfo branc
      svn/ignore-prop-mods 7790729 On the ignore-prop-mods branc
      svn/svnpatch-diff    918b5ba On the 'svnpatch-diff' branch
      svn/tree-conflicts   79f44eb On the tree-conflicts branch,
      svn/trunk            ae47f26 Remove YADFC (yet another dep

Sharing Your Repository

After importing your complete git
svn
gatekeeper repository from SVN, you need to publish it.
You do that in the same way you would set up any bare repository (see
Chapter 12), but with one trick: the
SVN branches that git svn creates are
actually remote refs, not branches. The usual technique doesn’t quite
work:

    $ cd ..

    $ mkdir svn-bare.git

    $ cd svn-bare.git

    $ git init --bare
    Initialized empty Git repository in /tmp/svn-bare/

    $ cd ..

    $ cd svn-all.git

    $ git push --all ../svn-bare.git
    Counting objects: 2331, done.
    Compressing objects: 100% (1684/1684), done.
    Writing objects: 100% (2331/2331), 7.05 MiB | 7536 KiB/s, done.
    Total 2331 (delta 827), reused 1656 (delta 616)
    To ../svn-bare
     * [new branch]      master -> master

You’re almost there. With git
push
you copied the master
branch but none of the svn/ branches.
To make things work properly, modify the git
push
command by telling it explicitly to copy those
branches:

    $ git push ../svn-bare.git 'refs/remotes/svn/*:refs/heads/svn/*'
    Counting objects: 6423, done.
    Compressing objects: 100% (1559/1559), done.
    Writing objects: 100% (5377/5377), 8.01 MiB, done.
    Total 5377 (delta 3856), reused 5167 (delta 3697)
    To ../svn-bare
     * [new branch]      svn/1.0.x -> svn/1.0.x
     * [new branch]      svn/1.1.x -> svn/1.1.x
     * [new branch]      svn/1.2.x -> svn/1.2.x
     * [new branch]      svn/1.3.x -> svn/1.3.x
     * [new branch]      svn/1.4.x -> svn/1.4.x
     * [new branch]      svn/1.5.x -> svn/1.5.x
     * [new branch]      svn/1.5.x-issue2489 ->  svn/1.5.x-issue2489
     * [new branch]      svn/explore-wc -> svn/explore-wc
     * [new branch]      svn/file-externals -> svn/file-externals
     * [new branch]      svn/ignore-mergeinfo -> svn/ignore-mergeinfo
     * [new branch]      svn/ignore-prop-mods -> svn/ignore-prop-mods
     * [new branch]      svn/svnpatch-diff -> svn/svnpatch-diff
     * [new branch]      svn/tree-conflicts -> svn/tree-conflicts
     * [new branch]      svn/trunk -> svn/trunk

This takes the svn/ refs, which
are considered remote refs, from the local repository and copies them to
the remote repository, where they are considered heads (i.e., local
branches).[39]

Once the enhanced git push is
done, your repository is ready. Tell your coworkers to go ahead and
clone your svn-bare.git repository.
They can then push, pull, branch, and merge among themselves without a
problem.

Merging Back into Subversion

Eventually, you and your team will want to push changes
from Git back into SVN. As before, you’ll do this using git svn dcommit, but you need not rebase
first. Instead, you can first git
merge
or git pull the
changes into a branch in the svn/
hierarchy and then dcommit only the single new merged commit.

For instance, suppose that your changes are in a branch called
new-feature and that you want to
dcommit it into svn/trunk. Here’s
what to do:

    $ git checkout svn/trunk
    Note: moving to "svn/trunk" which isn't a local branch
    If you want to create a new branch from this checkout, you may do so
    (now or later) by using -b with the checkout command again. Example:
      git checkout -b <new_branch_name>
    HEAD is now at ae47f26... Remove YADFC (yet another deprecated function call).

    $ git merge --no-ff new-feature
    Merge made by recursive.
     hello.txt |    1 +
     1 files changed, 1 insertions(+), 0 deletions(-)
     create mode 100644 hello.txt

    $ git svn dcommit

There are three surprising things here:

  • Rather than checking out your local branch, new-feature, and merging in svn/trunk, you must
    do it the other way around. Normally, merging works fine in either
    direction, but git svn won’t work
    if you do it the other way.

  • You merge using the --no-ff option,
    which ensures there will always be a merge commit (even though
    sometimes a merge commit might seem unnecessary).

  • You do the whole operation on a disconnected HEAD, which sounds dangerous.

You absolutely must do all three surprising things, or the
operation won’t work reliably.

How dcommit handles merges

To understand why it’s necessary to dcommit in such a
strange way, consider carefully how dcommit works.

First, dcommit figures out the SVN branch to commit to
by looking at the git-svn-id of
commits in the history.

Tip

If you’re nervous about which branch dcommit will pick, you
can use git svn dcommit -n to try
a harmless dry run.

If your team has been doing fancy things—which is, after all,
the point of this section—then there might be merges and cherry-picked
patches on your new-feature branch,
and some of those merges might have git-svn-id lines from branches other than
the one to which you want to commit.

To resolve the ambiguity, git
svn
looks at only the left side of every merge, in the same
way that git log –first-parent
does. That’s why merging from svn/trunk into new-feature doesn’t work: svn/trunk would end up on the right, not the
left, and git svn wouldn’t see it.
Worse, it would think your branch was based on an older version of the
SVN branch and so would try to automatically git svn rebase it for you, making a terrible
mess.

The same reasoning explains why --no-ff is
necessary. If you check out the new- feature branch and git merge svn/trunk, then check out the
svn/trunk branch and git merge new-feature without the
--no-ff option, Git will do a fast-forward rather
than a merge. This is efficient, but results in svn/trunk being on the right side, with the
same problem as before.

Finally, after it figures all this out, git svn dcommit needs to create one new
commit in SVN corresponding to your merge commit. When that’s done, it
must add a git-svn-id line to the
commit message, which means the commit ID
changes
, so it’s not the same commit anymore.

The new merge commit ends up in the real svn/trunk branch, and the merge commit you
created earlier (on the detached HEAD) is now redundant. In fact, it’s worse
than redundant. Using it for anything else eventually results in
conflicts. So, just forget about that commit. If you haven’t put it on
a branch in the first place, it’s that much easier to
forget.

Miscellaneous Notes on Working with Subversion

There are a few more things that you might want to know when you’re
using git svn.

svn:ignore Versus .gitignore

In any VCS, you need to be able to specify files that you
want the system to ignore, such as backup files, compiled executables,
and so on.

In SVN, this is done by setting the svn:ignore property on a directory. In Git,
you create a file called .gitignore, as explained in The .gitignore File of Chapter 5.

Conveniently, git svn provides
an easy way to map from svn:ignore to
.gitignore. There are two
approaches to consider:

  • git svn
    create-ignore
    automatically creates .gitignore files to match the svn:ignore properties. You can then commit
    them, if you’d like.

  • git svn show-ignore finds
    all the svn:ignore properties in
    your whole project and prints the entire list. You can capture the
    command’s output and put it in your .git/info/exclude file.

Which technique you choose depends on how covert your git svn usage is. If you don’t want to commit
the .gitignore files into your
repository, thus making them show up for your SVN-using coworkers, then
use the exclude file. Otherwise, .gitignore is usually the way to go, because
it’s automatically shared by everyone else using Git on that
project.

Reconstructing the git-svn Cache

The git svn command
stores extra housekeeping information in the .git/svn directory. This information is used,
for example, to quickly detect whether a particular SVN revision has already been downloaded and
so doesn’t need to be downloaded again. It also contains all the same
git-svn-id information that shows up
in imported commit messages.

If that’s the case, then why do the git-svn-id lines exist at all? The reason is
that, because the lines are added to the commit object and the content
of a commit object determines its ID, it follows that the commit ID
changes after sending it through git svn
dcommit
, and changing the commit IDs can make future Git
merging painful unless you follow the careful steps listed earlier. But
if Git just omitted the git-svn-id
lines, then the commit IDs wouldn’t have to change and git svn would still work fine. Right?

Yes, except for one important detail. The .git/svn directory isn’t cloned with your Git
repository. An important part of Git’s security design is that only
blob, tree, and commit objects are ever shared. Hence, the git-svn-id lines need to be part of a commit
object, and anyone with a clone of your repository will get all the
information they need to reconstruct the .git/svn directory. This has two
advantages:

  1. If you accidentally lose your gatekeeper repository or break
    something, or if you disappear and there’s nobody to maintain your
    repository, then anyone with a clone of your repository can set up a
    new one.

  2. If git-svn has a bug and
    corrupts its .git/svn
    directory, you can regenerate it whenever you want.

You can try out regenerating the cache information whenever you
want by moving the .git/svn
directory out of the way. Try this:

    $ cd svn-all.git
    $ mv .git/svn /tmp/git-svn-backup
    $ git svn fetch -r33005:33142

Here, git svn regenerates its
cache and fetches the requested objects. (As before, you would normally
leave off the -r option to avoid downloading thousands
of commits, but this is just an example.)


[36] The git svn command is
sluggish because it isn’t highly optimized. SVN support in Git has
fewer users and developers than plain Git or plain SVN. Additionally,
git svn simply has more work to do.
Git downloads the repository’s history, not just the most recent
version, whereas the SVN protocol is optimized for downloading just
one version at a time.

[37] Why dcommit instead of commit?
The original git svn commit
command was destructive and poorly designed, and it should be
eschewed. However, rather than break backward compatibility, the
git svn developers decided to add
a new command, dcommit. The old
commit command is now better
known as set-tree, but don’t use
that command, either.

[38] Your local repository will definitely be missing revisions,
because only a subset of all revisions was cloned at the start.
You’ll probably see more new revisions than those shown here,
because SVN developers are still working on the 1.5.x branch.

[39] If you think this sounds convoluted, you’re right. Eventually,
git svn may offer a way to simply
create local branches instead of remote refs, so that git push –all will work as
expected.

Comments are closed.

loading...