Tag specific Git commits based on commit message contents

Posted on

Problem

I am using GNU Bash 4.3, and Git 2.1.4, to tag certain git commits based on their commit messages. Specifically, I want to tag:

  • as v0.0.1 the commit with message “Commit 5”,
  • as v0.0.2 the commit with message “Commit 10”,

given the following commit log:

$ git log --format=oneline --decorate
924d9120dec9f61d0f99a5155c51bad63d24b37f (HEAD, master) Commit 10
c62b2f68ea6b8ff047dd5f1e99010ebf434709ef Commit 9
feec2d76977521018ff33fd6e109530fdb69291d Commit 8
9a25b2ea11d0aa19af33761c7c81bd10d0f17949 Commit 7
c7b02e556738e7cd2a2459e07af731e5677ab090 Commit 6
1bf5b98cf90340e714bd0bd13c7721f94749fa5f Commit 5
8a9c25eda46b3424a870584e4a8a4f90f6e5aa2c Commit 4
0ecf2464ec82a3aa74abb6f6c2127315ef4c2495 Commit 3
e18bdb2ae52f2149421965ff69d96dba46cd7c0a Commit 2
cf10d47067bef60ae787e5421e32b4541bdf52be Commit 1

Here is my initial solution:

$ v=(1 2); c=(5 10); for i in 0 1; do git tag -a v0.0.${v[$i]} -m "Tag v0.0.${v[$i]}" $(git rev-parse :/'Commit '${c[$i]}); done

This clearly works as desired:

$ git log --format=oneline --decorate
924d9120dec9f61d0f99a5155c51bad63d24b37f (HEAD, tag: v0.0.2, master) Commit 10
c62b2f68ea6b8ff047dd5f1e99010ebf434709ef Commit 9
feec2d76977521018ff33fd6e109530fdb69291d Commit 8
9a25b2ea11d0aa19af33761c7c81bd10d0f17949 Commit 7
c7b02e556738e7cd2a2459e07af731e5677ab090 Commit 6
1bf5b98cf90340e714bd0bd13c7721f94749fa5f (tag: v0.0.1) Commit 5
8a9c25eda46b3424a870584e4a8a4f90f6e5aa2c Commit 4
0ecf2464ec82a3aa74abb6f6c2127315ef4c2495 Commit 3
e18bdb2ae52f2149421965ff69d96dba46cd7c0a Commit 2
cf10d47067bef60ae787e5421e32b4541bdf52be Commit 1

But it seems excessively verbose and fragile.

Questions:

  • Instead of separately defining three arrays (v=(1 2); c=(5 10); for i in 0 1), can I create a single array for the two independent variables (e.g. in pseudocode: vc=((1 5) (2 10))) and then determine the incrementor’s value automatically upon each iteration?

  • Instead of using a for loop, can I use brace expansion or some other more concise and elegant approach?

I would prefer a one-liner, but I do not require a one-liner.

Solution

You could extract the main operation to a function:

tag() { local v=$1 c=$2; git tag -a v0.0.$v -m "Tag v0.0.$v" $(git rev-parse :/"Commit $c"); }

And then call this function with the desired values:

tag 1 5
tag 2 10

This is very easy to read and understand.

As per using nested arrays, you cannot do that in Bash, but you can always emulate a 2D-array with appropriate indexing, for example:

vc=(1 5 2 10); for ((i = 0; i < ${#vc[@]}; i+=2)); do v=${vc[i]}; c=${vc[i+1]}; git tag -a v0.0.$v -m "Tag v0.0.$v" $(git rev-parse :/"Commit $c"); done

If I had to write a “single-use” quickie command, I would write it like this:

while read version commit; do
    git tag -a v0.0.$version -m "Tag v0.0.$version" $(git rev-parse :/'Commit '$commit)
done

Then, as the script awaits input, type:

1 5
2 10
^D

In contrast to your parallel arrays, the main advantage of this approach is that each line constitutes a commit-version pair.

Leave a Reply

Your email address will not be published.