|
| 1 | +#!/bin/bash |
| 2 | + |
| 3 | +all_branches=0 |
| 4 | + |
| 5 | +DIE() { |
| 6 | + echo "ERROR: $*" >&2 |
| 7 | + exit 1 |
| 8 | +} |
| 9 | + |
| 10 | +LOG() { |
| 11 | + local level=1ドル |
| 12 | + shift |
| 13 | + |
| 14 | + (( verbose >= level )) && echo "${0##*/}: $*" >&2 |
| 15 | +} |
| 16 | + |
| 17 | +OPTS_SPEC="\ |
| 18 | +${0##*/} [<options>] <ref> |
| 19 | + |
| 20 | +Replace a commit message with the content of the specified |
| 21 | +file (or stdin if unspecified). |
| 22 | +-- |
| 23 | +h,help Show the help |
| 24 | + |
| 25 | +a,all Update all branches from which the commit is reachable |
| 26 | +v,verbose Increase logging verbosity |
| 27 | +F,message-file= get message from file instead of stdin |
| 28 | +" |
| 29 | + |
| 30 | +eval "$(git rev-parse --parseopt -- "$@" <<<$OPTS_SPEC || echo exit $?)" |
| 31 | + |
| 32 | +while (( $# > 0 )); do |
| 33 | + case 1ドル in |
| 34 | + (-a) all_branches=1 |
| 35 | + shift |
| 36 | + ;; |
| 37 | + |
| 38 | + (-v) let verbose++ |
| 39 | + shift |
| 40 | + ;; |
| 41 | + |
| 42 | + (-F) message_file=2ドル |
| 43 | + shift 2 |
| 44 | + ;; |
| 45 | + |
| 46 | + (--) shift |
| 47 | + break |
| 48 | + ;; |
| 49 | + esac |
| 50 | +done |
| 51 | + |
| 52 | +(( $# == 1 )) || DIE "missing refspec" |
| 53 | +target=1ドル |
| 54 | + |
| 55 | +# get the current branch name |
| 56 | +branch=$(git rev-parse --symbolic-full-name HEAD) |
| 57 | +[[ $branch = HEAD ]] && DIE "unable to determine branch name" |
| 58 | + |
| 59 | +# git the full commit id of our target commit (this allows us to |
| 60 | +# specify the target as a short commit id, or as something like |
| 61 | +# `HEAD~3` or `:/interesting`. |
| 62 | +oldref=$(git rev-parse --verify "$target") || DIE "invalid reference: $target" |
| 63 | + |
| 64 | +if (( all_branches )); then |
| 65 | + branch_list=($(git branch --contains $oldref --format='%(refname)')) |
| 66 | +else |
| 67 | + branch_list=($branch) |
| 68 | +fi |
| 69 | + |
| 70 | +for branch in "${branch_list[@]}"; do |
| 71 | + LOG 1 "checking branch $branch" |
| 72 | + |
| 73 | + # verify that target is an ancestor of current HEAD |
| 74 | + git merge-base --is-ancestor $oldref $branch || |
| 75 | + DIE "$target ($oldref) is reachable from $branch but is not an ancestor of $branch" |
| 76 | + |
| 77 | + # check for merge commits |
| 78 | + git log --format='%p' $oldref..$branch | awk 'NF>1 {exit 1}' || |
| 79 | + DIE "history contains one or more merge commits" |
| 80 | +done |
| 81 | + |
| 82 | +# generate a replacement commit object, reading the new commit message |
| 83 | +# from stdin. |
| 84 | +newref=$( |
| 85 | +(git cat-file -p $oldref | sed '/^$/q'; cat $message_file) | |
| 86 | + git hash-object -t commit --stdin -w |
| 87 | +) |
| 88 | +echo $newref |
| 89 | + |
| 90 | +# iterate over commits between our target commit and HEAD in |
| 91 | +# reverse order, replacing parent points with updated commit objects |
| 92 | +for branch in "${branch_list[@]}"; do |
| 93 | + LOG 0 "updating branch $branch" |
| 94 | + for rev in $(git rev-list --reverse ${oldref}..$branch); do |
| 95 | + LOG 2 "updating rev $rev" |
| 96 | + newref=$(git cat-file -p $rev | |
| 97 | + sed "1,/^$/ {/^parent/ s/$oldref/$newref/}" | |
| 98 | + git hash-object -t commit --stdin -w) |
| 99 | + oldref=$rev |
| 100 | + done |
| 101 | + |
| 102 | + # update the branch pointer to the head of the modified tree |
| 103 | + git update-ref $branch $newref |
| 104 | +done |
0 commit comments