Oninaig
Oninaig

Reputation: 91

Using regex in git shell when checking out multiple branches

If I have the following git shell command:

for branch in `git branch -a | grep remotes | grep -v master | sed 's/^.*old\/\(.*\)/\1/g'`; do git branch --track $branch remotes/old/$branch; done

This checks out every single remote branch that exists on the old remote and tracks them using the same name that they have on that remote. However, what if I wanted to slightly change the name that the local branches are checked out has?

What if I have the following remote branches:

release/1.2.1.0
release/1.2.1.1

And I want to check them out under the same parent folder release but I only want the last 3 digits in the version number. So I want my local branches to be:

release/2.1.0
release/2.1.1

I have a simple javascript regex that matches the last 3 digits of the version string: (?:\d\.)(\d.*)

This uses a non-matching group to toss out the first digit followed by the period. The question is, how do I apply that regex to the $branch variable in the git shell bash script above?

Upvotes: 0

Views: 895

Answers (1)

torek
torek

Reputation: 488183

First, avoid git branch for loops like this. The correct tool here is git for-each-ref, which is designed to work with scripting languages (git branch is aimed at users and the output format may change in the future, for instance).

To loop over all remote-tracking branches, simply tell for-each-ref to scan the remote-tracking branch namespace. Since you want, more specifically, the remote named old, you can do that very easily by adding /old as well:

git for-each-ref refs/remotes/old

The output here defaults to a triple of objectname objecttype refname. We only care about the refname part (and we can use the :short modifier to drop refs/remotes/ as well, if we like, although we still need to drop the old/ too so we could get away without the modifier). Thus we want to include --format=%(refname:short).

Moving on to bash, bash has built-in regular expression support. Its RE syntax is not quite the same, though, so your existing RE must change. Here is one that probably works for your needs:

bash$ x=1.2.3.4
bash$ [[ $x =~ ([0-9]\.)([0-9.]+) ]] && echo ${BASH_REMATCH[2]}
2.3.4

(There is a bit of subtlety here: using $x changes the way the =~ match applies, which in our case is probably good. As an old school Unix person I generally prefer using expr myself, but in this case I might resort to doing this in Python, which has Perl-style REs, and Javascript/ECMAscript REs are modeled on Perl's. But all that is more or less irrelevant. The most important is that this RE is slightly sub-par as a version number matcher. For instance, it matches strings like "1.3..6". We're safe in that these are invalid branch names—double dots are verboten since they would conflict with the set subtraction syntax in gitrevisions—but it's generally a bit sloppy; with some work we could come up with a tighter expression. It also fails to match revisions starting with two or more digits, but your original RE did as well, so I left that in on purpose.)

Reading in a loop in shell, using -r is generally wise (see Etan Reisner's comment), although in this case we could omit it safely since git controls branch names. I will use it in the example just for form's sake.

Putting these all together:

warn() {
    echo "warning: $@" 1>&2
}

# Given an input name release/\d\.(\d|\.)+, make
# a local branch named release/\2 (more or less).
make_local_release_branch() {
    local relnum newname

    relnum=${1#old/release/}
    [[ $relnum =~ ([0-9]\.)([0-9.]+) ]] || {
        warn "remote-tracking branch $1 does not conform to name style, ignored"
        return
    }
    newname=release/${BASH_REMATCH[2]}
    git rev-parse -q --verify refs/heads/$newname >/dev/null && {
        warn "branch $newname already exists, remote-tracking branch $1 ignored"
        return
    }
    git branch --track $branch $1
}

git for-each-ref --format='%(refname:short)' refs/remotes/old |
while read -r rmtbranch; do
    case $rmtbranch in
    old/release/[0-9]*) make_local_release_branch "$rmtbranch";;
    *) warn "skipping remote branch $rmtbranch -- not old/release/[digit]";;
    esac
done

(this whole thing is entirely untested).

Upvotes: 2

Related Questions