1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
|
#!/bin/bash -eE
#
# Copyright (c) 2012-2013 Luke Shumaker <lukeshu@sbcglobal.net>
#
USAGE='[-h|-v|--svn] <in-branch> <out-branch> <filters>...'
LONG_USAGE='Like filter-branch, but can be used to update branches.
This creates or updates <out-branch> from <in-branch>. If <in-branch> already
exists, it only runs new commits through the filter. This way, this can be
used to pull from a remote with a different layout than yours.
This will append "git-rewrite-id" to all commit messages, similar to
"git-svn-id" with `git-svn`.
-h Show this text
-v Be more verbose
--svn Use an existing "git-svn-id" instead of "git-rewrite-id"
(in-branch was generated by git-svn)
<filters> is passed directly to `git filter-branch`, and therefore has the same
format.
'
SUBDIRECTORY_OP=Yes
OPTIONS_SPEC=
. git-sh-setup
. git-sh-i18n
require_work_tree_exists
# when $gitmode is true, $ibranch's commits are used as IDs
gitmode=true
tag='git-rewrite-id'
ibranch=''
obranch=''
wbranch=''
panic() {
echo 'panic: malformed call to internal function' >&2
exit 1
}
# Usage: verbose arg1 arg2...
# Print the arguments, but only if in verbose mode.
# Verbose mode works by redefining this function during option parsing if -v is
# encountered.
verbose() {
:
}
################################################################################
# Usage: id2commit $branch $id
# Returns the commit on $branch with $id.
id2commit() {
[[ $# == 2 ]] || panic
local branch=$1
local id=$2
if [[ $branch == $ibranch ]] && $gitmode; then
printf '%s\n' "$id"
else
git log "$branch" --pretty=format:'%H' --grep "${tag}: ${id}"
fi
}
# Usage: commit2id $branch $commit
# Returns the id of $commit, which is on $branch.
commit2id() {
[[ $# == 2 ]] || panic
local branch=$1
local commit=$2
if [[ $branch == $ibranch ]] && $gitmode; then
printf '%s\n' "$commit"
else
git log "$branch" -n1 --pretty=formtat:'%B' "$commit" | sed -n "s|^\s*${tag}: ||p"
fi
}
# commit2commit
# Usage: c2c $from $to $commit
# Returns the commit on branch $to that has the same id as $commit, which is
# on branch $from.
c2c() {
[[ $# == 3 ]] || panic
local from=$1
local to=$2
local commit=$3
if [[ "$from" == "$to" ]]; then
# optimization
# also, properly normalizes $ibranch when $gitmode=true
git log "$commit" -n1 --pretty=format:'%H'
else
id2commit "$to" "$(commit2id "$from" "$commit")"
fi
}
################################################################################
main() {
# Parse command line arguments #########################################
while true; do
case "${1:-}" in
--svn)
gitmode=false
tag='git-svn-id'
shift
;;
-h)
usage
return 0
;;
-v)
verbose() { echo "$*"; }
shift
;;
*)
break
;;
esac
done
if [[ $# < 3 ]]; then
usage
return 1
fi
ibranch=$1
obranch=$2
shift 2
local filters=();
if $gitmode; then
# Add a filter to append the id to the commit message.
# This is a little confusing to read because of double quoting.
filters=(--msg-filter "sed '\$a'\"${tag}: \${GIT_COMMIT}\"")
# Here it is annotated; "@" indicates characters to be taken
# literally, and "^" indicates string expansion.
#
# Argument to git-filter-branch:
# sed '$a'"${tag}: ${GIT_COMMIT}"
# @@@@@@@@@^^^^^^@@@@@@@@@@@@@@@@
# Argument to sed:
# $a${tag}: ${GIT_COMMIT}
# @@^^^^^^@@^^^^^^^^^^^^^
fi
filters+=("$@")
# Main #################################################################
wbranch="$obranch.tmp"
local revlist
local rebase
if git checkout "$obranch" -- 2>/dev/null; then
# obranch exists, update it
echo 'Updating existing rewritten branch...'
local icommit="$(c2c "$ibranch" "$ibranch" "$ibranch")"
local ocommit="$(c2c "$obranch" "$ibranch" "$obranch")"
if [[ "$icommit" == "$ocommit" ]]; then
echo "Nothing to do"
return 0
fi
local common="$(c2c "$obranch" "$ibranch" "$obranch")"
if c2c "$ibranch" "$ibranch" "${common}^" &>/dev/null; then
revlist="$(c2c "$obranch" "$ibranch" "$obranch")^..${wbranch}"
else
# There is only one commit from $ibranch in $obranch
revlist="$wbranch"
fi
rebase=true
else
# obranch does not exist, create it
echo 'Creating new rewritten branch...'
revlist=$wbranch
rebase=false
fi
git checkout "$ibranch"
git branch -D "$wbranch" 2>/dev/null || true
git checkout -b "$wbranch"
git filter-branch -f "${filters[@]}" "$revlist"
if $rebase; then
# rebase the changes in wbranch onto obranch
echo 'Rebasing rewrites onto existing branch...'
local wcommit="$(c2c "$wbranch" "$ibranch" "$wbranch")"
if [[ "$wcommit" == "$ocommit" ]]; then
echo "Nothing to do"
return 0
fi
local commonish="$(c2c "$obranch" "$wbranch" "$obranch")"
cmd=(git rebase --onto "$obranch" "$commonish" "$wbranch")
verbose
verbose " o---o---o $obranch"
verbose ' :'
verbose " o---o---o---o---o $ibranch"
verbose ' \ :'
verbose ' `-C---o---o '"$wbranch"
verbose
verbose " C = $commonish"
verbose
verbose " ${cmd[*]}"
verbose
"${cmd[@]}"
git checkout "$obranch"
git branch -d "$wbranch"
else
git branch -m "$wbranch" "$obranch"
fi
}
main "$@"
|