Branches mapping

To define user’s repository layout SubGit uses branches mapping. Each directory (branch or tag) is mapped to a Git reference in the Git repository. The mapping can be one-to-one some certain branches/tags/trunk like

branches = path/to/certain/branch:refs/heads/certain/branch

or wildcard mappings can be used.

branches = path/to/*/wildcard/branches/*:refs/heads/*/*

SVN paths and Git references, that are not matched by these rules, are not translated by SubGit.

There’s no much difference between trunk, branches, and tags from both SubGit and SVN perspectives. Shelves are special kind of branches used by SubGit to represent Git anonymous branches in Subversion repository (as SVN doesn’t support branches without names).

The following repository is the most common:

trunk = trunk:refs/heads/master
branches = branches/*:refs/heads/*
shelves = shelves/*:refs/shelves/*
tags = tags/*:refs/tags/*

It follows SVN best practices.

The rules have the form

SVN_PATTERN:GIT_PATTERN

where SVN_PATH (left part of mapping equation) is a path relative to the project ‘svn.url’ location, and GIT_REFERENCE (right part) is a Git reference e.g. refs/heads/master. Mapping could be either one-to-one or many-to-many, so those paths may be partially replaced with *’s. Keep in mind that the number of aterisks should be equal at right and left side of the mapping. Example as follows:

branches = branches/*:refs/heads/*
branches = branches/*/*:refs/heads/*/*
branches = branches/*/project:refs/heads/*
branches = branches/feature_*_2015:refs/heads/features/*

If the SVN repository doesn’t have trunk/branches/tags tags at all and keeps all data in the project root the following rule should be used (branches/tags/shelves options should be omitted).

trunk = :refs/heads/master

This layout could be generated automatically by running the following command:

$ subgit configure --layout directory <directory name>

Moreover, there is a feature of autedetection the whole layout by running this:

$ subgit configure --layout auto --trunk path/to/trunk <directory>

Where path/to/trunk is a relative path (relative to project ‘svn.url’ location) of an SVN directory which plays a role of trunk (usually this option looks like --trunk trunk). The command will scan SVN history of trunk directory and all directories where it was copied from and to. It doesn’t work for branches in SVN which were created by file system copy + svn add (instead of svn copy command, which is recommended by SVN).

As a side effect this command creates ‘authors.txt’ as well.

Always check your automatically generated layout! It’s usually reasonable in the most of the cases, but it’s always better to be sure.

Branches mapping limitations

There are some configurations that are not supported by SubGit. For example, if the layout looks like this:

trunk = trunk:refs/heads/master
branches = branches/*:refs/heads/*
branches = branches/releases/*:refs/heads/*
shelves = shelves/*:refs/shelves/*
tags = tags/*:refs/tags/*

SubGit will render an error, because in this example branches paths have an overlap. Pay attention to

branches = branches/*:refs/heads/*

and

branches = branches/releases/*:refs/heads/*

Two different SVN branches (branches/* and branches/releases/*) point to the same Git reference rule (refs/heads/*). This would create an ambiguity when someone pushes Git branch named refs/heads/branch (should SubGit translate it to branches/branch or branches/releases/branch?) To avoid ambiguity SubGit forbids such a configuration.

The layout in this case can be corrected as shown below:

trunk = trunk:refs/heads/master
branches = branches/*:refs/heads/*
branches = branches/releases/*:refs/heads/releases/*
shelves = shelves/*:refs/shelves/*
tags = tags/*:refs/tags/*

Now the branches/releases/* is going to be directly translated to refs/heads/releases/* and branches/* to refs/heads/* and vice versa, and everything is going to be alright.

With something like this in the configuration file, SubGit will return an error of a missing the ‘trunk=’ part.

branches = branches/*:refs/heads/*
shelves = shelves/*:refs/shelves/*
tags = tags/*:refs/tags/*

More avdanced case. For example, what will happen with the layout like this?

trunk = trunk:refs/heads/master
branches = branches/*:refs/heads/*
branches = branches/*/*:refs/heads/*/*
shelves = shelves/*:refs/shelves/*
tags = tags/*:refs/tags/*

In this case

branches = branches/*:refs/heads/*

only is going to be ignored. There’s a reason. Suppose that for the translation purposes of branch that is called ’x’, i.e. ‘branch/x’, refs/heads/x link will be created, physically it would be stored as GIT_REPO/refs/heads/x file, which leaves out the opportunity of creating a directory that has the same ‘x’ name. And the other way around, refs/heads/x/y path excludes the opportunity of creating refs/heads/x file. SubGit always tends to choose patterns that translate as much references as possible, and in that case it is the first one, and thus branches = branches/*:refs/heads/* rule will be ignored.

One should keep in mind that more that one asterisk is not allowed in one part of mapping equation, because it surely creates ambiguity during the translation, allowing at least two different interpretations. For example, if we had a pattern like ‘x*x*x’, and path like ‘xyxyxyx’ (note, that in pattern there are three x’s and in the string there are four), we could interpret it two different ways: ‘xyxyxyx’ or ‘xyxyxyx’. This would create an ambiguity. That’s why this is not allowed in SubGit configuration.

Ambiguous pattern

To exclude patterns from translation the following option could be useful:

excludeBranches = branches/month-_*_year-2001*

It shouldn’t contain leading or trailing slashes but may contain several asterisks (as there’s no ambiguity anymore).

Finally, it should be noted that nested branches are not supported. This means that branches/branch/dir and branches/branch can’t be considered as branches as the same time, either branches/branch is a branch and ‘dir’ is a subdirectory in it; or branches/branch/dir is a branch, then branches/branch content is ignored (except ‘dir’ itself).

Written on January 13, 2017