In this project I am working, we do deployments based on tags. Whilst it's obligatory that the tags are done against the master branch (after you merge the release there), sometimes by mistake someone can tag against a dev or release branch, which is incorrect. That causes several problems.
In our deployment script, there is a step in which we do clone a specific tag from git, using a process like the one described in this question: Download a specific tag with Git
$ git clone
$ git checkout tags/<tag_name>
How can I amend this script to check if this tag was actually done against the master branch? I would like to then stop the deployment and throw an error if the branch is not the master.
Thanks.
As several people noted, your question cannot really be answered until it is reformulated. This is because a Git tag or branch name simply identifies one specific commit. The desired effect of a branch name is to identify the tip commit of a branch, which changes over time, so that the specific commit it identifies also changes over time. The desired effect of a tag name is to identify one specific commit forever, without changing. So if someone tags master
, there will be some moments in time during which parsing the name master
produces commit hash H, and parsing the tag name also produces commit hash H:
if test $(git rev-parse master) = $(git rev-parse $tag^{commit}); then
echo "master and $tag both identify the same commit"
else
echo "master and $tag identify two different commits"
fi
This particular test is valid until someone makes the branch name master
advance, after which it is no longer useful. If we draw this the way I typically like to draw Git commit graphs on StackOverflow, we can see this as:
tag
|
v
...--o--o--H <-- master
/
...--o--o <-- develop
Currently the names tag
and master
both identify commit H, which is a merge commit. As soon as someone creates a new commit on master
, though, the graph becomes:
tag
|
v
...--o--o--H--I <-- master
/
...--o--o <-- develop
Now master
identifies new commit I
, so doing the rev-parse tag^{commit}
will find H
while doing the rev-parse master
will find I
and they won't be equal and the test will fail.
(I drew commit I
as an ordinary commit here, but it could be a merge commit with a second parent. If so, imagine a second backwards-pointing line / arrow emerging from I
, pointing to some other earlier commit.)
Philippe's answer comes in two forms and answers a slightly different question. Since branch names do move over time, we can use git branch --contains
to find all branch names that make the tagged commit reachable, and see if one of these is master
. This will give a true / yes answer for the case above. Unfortunately, it will also tell you that the tag error
is contained within master
—which is true!—for this graph:
tag
|
v
...--o--o--H <-- master
/
...--o--G <-- develop
^
|
error
This is because the tag error
identifies commit G
, and commit G
is reachable from commit H
(by following the second parent of H
). In fact any tag along the bottom row, pointing to any commit contained within the develop
branch, identifies a commit contained within the master
branch, since every commit currently on develop
is also on master
.
(Incidentally, the difference between using git rev-parse your-tag
and using git rev-list -n 1 your-annotated-tag
is covered by using git rev-parse $tag^{commit}
. The issue here is that an annotated tag has an actual repository object, of type "annotated tag", to which the name points; using git rev-parse your-annotated-tag
alone finds the tag object rather than its commit. The ^{commit}
suffix syntax is described in the gitrevisions documentation.)
There is a way to tell whether the commit to which any given tag points is in the history of master
that occurs only on the first-parent chain. It is not the prettiest: git branch --contains
and git merge-base --is-ancestor
are the usual building blocks for finding reachability but both follow all parents. To follow only first parents, we need to use git rev-list --first-parent
to enumerate all the commits reachable from the name master
when following only first parents. Then we simply check whether the tagged revision is in that list. Here's a bad way to do it that makes clear what we are doing:
tagged_commit=$(git rev-parse $tag^{commit}) ||
fatal "tag $tag does not exist or does not point to a commit object"
found=false
for hash in $(git rev-list --first-parent master); do
test $hash == $tagged_commit && found=true
done
To make this much faster, it would be better to pipe the git rev-list
output through a grep
that searches for $tagged_commit
(with grep's output discarded since we only care about the status):
if git rev-list --first-parent master | grep $tagged_commit >/dev/null; then
echo "$tag points to a commit reachable via first-parent from master"
else
echo "$tag does not point to a commit reachable via first-parent from master"
fi
for instance. (One flaw here is that git rev-list
will run all the way through every reachable commit; in a large repository, this can take seconds.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With