Note: The below is similar to this question (i.e. same motivation & general idea), but tries to be more careful: My version should never touch the repository state (to preserve staged files etc), and should properly handle submodules.
The goal of the below script is to create a tag for the current state of the git repository. If the repository/any submodules are dirty, it should create a "detached commit" without modifying the current state of the index etc. to capture the file content. The motivation for this is to be able to have a tag describing the source code that went into a build. So if I later want to figure out what code was used for a build, I have something to git checkout, even if the repository was dirty at the time of the commit (as happens often while implementing new stuff).
The code is split into two files:
get_git_tag.sh
This is the main script to call - it generates the tag name, and calls the inner script to do the work
#!/bin/bash
set -euo pipefail
tag=$(date +"dev-%Y%m%d-%H%M%S")
cd "$(dirname "$0")/../../"
bash "$(dirname "$0")/iget_git_tag.sh" "$tag" "main"
iget_git_tag.sh
The script that handles the actual tag creation & recursively calls itself
#!/bin/bash
set -euo pipefail
if [ -z "$(git status --porcelain)" ]; then
# if not dirty, use any vXXX type tag if available, otherwise the commit SHA
git describe --tags --exact-match --match "v[0-9]*" 2>/dev/null || git rev-parse --verify --short HEAD
else
# if dirty, create a "detached commit"
tmp_git_index=$(mktemp)
cp $(git rev-parse --git-path index) "$tmp_git_index"
export GIT_INDEX_FILE="$tmp_git_index"
# stage all changes to the temporary index file
git add --all
# recursively call the script on submodules to produce tags, and add the relevant commit SHAs to the index
git submodule foreach --quiet "GIT_INDEX_FILE=$GIT_INDEX_FILE git -C \$toplevel update-index --cacheinfo 160000,\$(git rev-parse \$(bash \"$(realpath "$0")\" \"$1\" sub)),\$sm_path"
build_sha=$(GIT_AUTHOR_DATE="1.1.1970 0:0:0" GIT_COMMITTER_DATE="1.1.1970 0:0:0" git commit-tree $(git write-tree) -p HEAD -m "Build commit")
# if the build was already tagged, use that tag, otherwise make a new one
git describe --tags --exact-match --match "dev-*" "$build_sha" 2>/dev/null || (git tag "$1" "$build_sha"; echo "$1")
fi
Questions
What I am mainly interested in:
- Any fundamental issues with this approach?
- I did quite a few tests of different scenarios, but I probably missed even more: Are there scenarios/repository states that are likely to break this script? If a few tags are invalid, that's not so great, but even worse would be if the script actually modifies the repository state itself (e.g. staged files, file contents) rather than only creating the detached commits
- Does the code properly handle nested submodules? I don't plan on having them in my repository any time soon, but ideally it would be future-proof in that regard.