Last active
February 19, 2020 13:34
-
-
Save njpearman/8890fb2d4fc0767712449e61b6bfb399 to your computer and use it in GitHub Desktop.
Reattaching tags to a rebased branch in a git repo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# This script applies tags that have become detached from a rebased branch, assuming | |
# that the original message for each tag has been unchanged in the rebased branch. | |
# This is necessary because tags get bound to a commit hash and a rebase will alter | |
# the commit hashes on a branch, "detaching" any tags that have been attached. It is | |
# of course debatable whether tags _should_ be moved around at all, but I needed to | |
# do this for a particular repo I have been working on. It has also been a good | |
# opportunity for me to explore git and some command line tools. It is not meant to | |
# be a reference script and there will almost certainly be much more effective ways | |
# of achieving what this script achieves. However, I hope that it is an interesting | |
# read. | |
# | |
# The script will most likely break if the original message no longer exists in the | |
# rebased branch. Things will also go wrong if a more recent commit has a matching | |
# commit message. | |
# | |
# The script makes assumptions, such as that the message to attach to the tag is | |
# 'End of unit'. | |
# | |
# I had particular difficulty with piping the `git log ..` commands generated by awk. | |
# I don't understand the process handling for the `git log` command but the output | |
# from those awk'd commands are put onto one line rather than continuing with the pipe. | |
# Other `git` commands behaved in the expected way, being piped into the next command | |
# but not `git log`. This is why I needed to use `sed` to insert linefeeds, `\n`, to | |
# then move to the next step. | |
# | |
# The unparameterised `xargs` is just to remove the whitespace at the start of | |
# the line created by the formatting of `awk`. This is in fact a lucky hack as | |
# the `awk` call prepends whitespace as a delimiter; the output from the `git log ...` | |
# is not "joined" and therefore there is either a leading or trailing empty item | |
# depending on how `awk` formats the command. I luckily did this with leading | |
# whitespace first time, which is easy to remove. | |
# | |
# It is highly likely that this script can be compacted as I suspect that formatting | |
# arguments could be used, removing the need for some of the filtering and formatting | |
# done with `grep` and `sed`. | |
# | |
# Stepping through each piped command, this script specifically: | |
# * gets a sorted list of git commits filtered to be only those that are tagged, and formats | |
# the log to only output the associated tag, ordered oldest first. | |
# * removes everything from these lines except the tag name | |
# * diffs this list with a sorted list of all tags in the repo | |
# * filters the diff for those lines that are tags that are in the repo but not | |
# on the current branch | |
# * removes the diff indicator at the start of each filtered line. | |
# * uses `git show` to get the commit message associated with each missing tag. | |
# * filters only the lines that include the summary of 'tag: <tag>:<summary>' | |
# * formats and executes a `git log ...` command to find commits on the current branch | |
# that match the original commit message associated with the tag. | |
# * removes the leading whitespace using `xargs` | |
# * replaces the whitespace with a linefeed. | |
# * uses `awk` to delete the unattached tag and add it again to the commit hash with | |
# a message that matches the originally associated commit, and a date matching the | |
# commit date. In order to get the correct date, a checkout is done. | |
git log --pretty=format:'%D' | sort \ | |
| sed -e 's/.*: //' \ | |
| diff - <(git for-each-ref --format '%(tag)' | sort) \ | |
| grep '^>' \ | |
| sed 's/> //' \ | |
| xargs git show --pretty=format:%D:%s \ | |
| grep ^tag: \ | |
| awk -F ':' ' { system("git log -1 --pretty=format:\"" $2 ":%h\" --grep=\"" $3 "\"") } ' \ | |
| xargs \ | |
| sed 's/ /\n/g' \ | |
| awk -F ':' '{system("git checkout " $1); system("git tag -d " $1); system("GIT_COMMITTER_DATE=\"$(git show --format=%aD | head -1)\" git tag -m \"End of unit\" " $1 " " $2)}' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment