-
-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathgit-linearize
executable file
·324 lines (285 loc) · 10.7 KB
/
git-linearize
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
#!/bin/bash
#
# This script makes sure that all commit hashes have nice and incremental prefixes.
#
# Move this script to somewhere on your path, and run it as "git linearize".
#
# Built by Gustav Westling (@zegl) and friends.
# https://github.com/zegl/extremely-linear
#
# Built with https://github.com/not-an-aardvark/lucky-commit
#
# Remember, this is a script that you've downloaded from the internet.
# Don't run it on anything important unless you know what you're doing.
# (Which I'm sure that you do, only epic rockstar ninja developers would use a program like this one).
#
#
set -euo pipefail
# Argument parsing :-)
EL_FORMAT="%07d0"
VERBOSE_LOG=0
EL_CMD="linearize"
EL_IF_BRANCH=""
while test $# -gt 0; do
case "$1" in
-h | --help)
LIGHT_GREEN='\033[1;32m'
NC='\033[0m' # No Color
echo -e "${LIGHT_GREEN}git linearize - Create an extremely linear git history - github.com/zegl/extremely-linear${NC}"
echo ""
echo "git linearize [command] [options]"
echo ""
echo "command: (default command is to run the linearization process)"
echo " -h, --help show brief help"
echo " --install-hook installs git-linearize as a post-commit hook (current repo only)"
echo " --make-epoch makes the current commit the linearized epoch (00000000), use to adopt git-linearize in"
echo " existing repositories without having to rewrite the full history"
echo ""
echo "general options (for all commands):"
echo " -v, --verbose more verbose logging"
echo " --short use shorter 6 digit prefix (quick mode)"
echo " --format [format] specify your own prefix format (pritnf style)"
echo " --if-branch [name] only run if the current branch is [name]"
echo ""
echo " All command generally support all general options. For example, specifying --format to --install-hook means"
echo " that git-linearize will be called with the same format in the future when triggered by the hook."
exit 0
;;
--short)
EL_FORMAT="%06d"
shift
;;
--format)
shift
if test $# -gt 0; then
export EL_FORMAT=$1
else
echo "no format specified"
exit 1
fi
shift
;;
--install-hook)
shift
EL_CMD="install_hook"
;;
--make-epoch)
shift
EL_CMD="make_epoch"
;;
--if-branch)
shift
EL_IF_BRANCH=$1
shift
;;
-v | --verbose)
shift
VERBOSE_LOG=1
;;
*)
break
;;
esac
done
########################################################################################################################
# Helper functions #
########################################################################################################################
function echoinfo() {
LIGHT_GREEN='\033[1;32m'
NC='\033[0m' # No Color
printf "${LIGHT_GREEN}%s${NC}\n" "$1"
}
function echoerr() {
RED='\033[0;31m'
NC='\033[0m' # No Color
printf "${RED}%s${NC}\n" "$1" >&2
}
function echodebug() {
if ((VERBOSE_LOG)); then
echoinfo "$1"
fi
}
function debug() {
if ((VERBOSE_LOG)); then
cat >&2
fi
}
function git_root() {
git rev-parse --show-toplevel
}
function git_current_branch() {
git branch --show-current
}
function linearize_root_commit() {
# shellcheck disable=SC2059 # Disabled because EL_FORMAT contains the format
printf "$EL_FORMAT" 0
}
function find_root_on_current_branch() {
# Find the most recent epoch commit (if any) on the current branch
# Returns the full commit hash of the root commit
git log --oneline --no-abbrev-commit |
grep "^$(linearize_root_commit)" |
head -n1 |
awk '{print $1}'
}
function git_has_linearize_root() {
# Use custom root if there the repositoru has a 00000000-commit that has a parent (is not the repo root)
if git show "$(find_root_on_current_branch)^" >/dev/null 2>&1; then
return 0
else
return 1
fi
}
function git_is_ready() {
# current directory has changes to tracked files
if git status --porcelain | grep -v -q "??"; then
return 1
fi
# no changes, or only changes to untracked files
return 0
}
########################################################################################################################
# Dependencies check #
########################################################################################################################
if ! command -v lucky_commit &>/dev/null; then
echoerr "[!] Dependency lucky_commit was not found on your PATH"
exit 1
fi
########################################################################################################################
# cmd_install_hook installs git-linearize as a post-commit hook in the current repository #
# #
# Trigger with "--install-hook" #
# #
# If the arguments "--if-branch [name]" or "--format [format]" or "--short" are passed to "--install-hook" they will #
# be forwarded to the execution of git-linearize when triggered by the hook. #
########################################################################################################################
function cmd_install_hook() {
FILE="$(git_root)/.git/hooks/post-commit"
if [ -f "$FILE" ]; then
echoerr "post-commit hook already exists at $FILE. Aborting!"
exit 1
fi
FORWARD_IF_BRANCH=""
if [[ -n $EL_IF_BRANCH ]]; then
FORWARD_IF_BRANCH="--if-branch ${EL_IF_BRANCH}"
fi
FORWARD_FORMAT=""
if [[ -n $EL_FORMAT ]]; then
FORWARD_IF_BRANCH="--format ${EL_FORMAT}"
fi
cat >"$FILE" <<-EOM
#!/bin/bash
git linearize ${FORWARD_IF_BRANCH} ${FORWARD_FORMAT}
EOM
chmod +x "$FILE"
echoinfo "Installed hook to .git/hooks/post-commit!"
}
# Check that we're running inside a git directory
if ! git_root &>/dev/null; then
echoerr "[!] fatal: not a git repository (or any of the parent directories)"
exit 1
fi
########################################################################################################################
# cmd_linearize is the default command of git-linearize, it rebases the current branch and gives all commits #
# incremental commit sha1 prefixes. #
# #
# Use "--if-branch [name]" to only run if the currently checked out branch matches the specified name. #
########################################################################################################################
function cmd_linearize() {
# Check branch
if [[ -n $EL_IF_BRANCH ]] && [[ ${EL_IF_BRANCH} != "$(git_current_branch)" ]]; then
echodebug "[x] Current branch is $(git_current_branch), expected ${EL_IF_BRANCH}. Skipping. :-)"
exit 0
fi
if ! git_is_ready; then
echoerr "[x] The current git directory is not clean. Skipping. :-)"
echoerr " Don't worry, git linearize will clean up all commits all at once later."
exit 0
fi
if git_has_linearize_root; then
found_root=$(find_root_on_current_branch)
echodebug "[x] Repository has a custom root commit (${found_root}), using it as the root"
commits=$(git log --format=format:%H --reverse "${found_root}^...HEAD")
else
echodebug "[x] Repository has a no custom root commit, using the repository root"
commits=$(git log --format=format:%H --reverse)
fi
# Remember which branch we are on
pre_branch=$(git branch --show-current)
# Find start commit
did_reset=0
i=0
stashed_commit=""
for sha1 in $commits; do
# Desired prefix of commit
# shellcheck disable=SC2059 # Disabled because EL_FORMAT contains the format
prefix=$(printf "$EL_FORMAT" $i)
((i = i + 1))
# Looping through the full history since the root commit
# Making sure that each commit has the expected prefix
if [[ $sha1 == $prefix* ]] && ((!did_reset)); then
echodebug "[x] $sha1 starts with $prefix, doing nothing"
continue
else
# Found the first commit that does not have the correct prefix
# Reset to this commits parent (the last commit with a good prefix)
if ((!did_reset)); then
echodebug "[x] Found first misaligned commit=$sha1"
# Create a new temporary branch to use while crunching the numbers
git branch -D extremely-linear | debug || true
git checkout -b extremely-linear | debug
# Create a stash of the local unasved state
# Will be restored at the end if we had any unsaved changes
stashed_commit="$(git stash create)"
git reset --hard "$sha1"
did_reset=1
else
# Cherry pick the next commit
git cherry-pick --keep-redundant-commits --allow-empty-message "$sha1"
fi
echodebug "[x] Fixing $sha1 (looking for prefix=$prefix)"
# Run lucky_commit
lucky_commit "$prefix"
new_sha=$(git rev-parse HEAD)
echoinfo "[x] $sha1 is now $new_sha"
fi
done
if ((did_reset)); then
# Move the branch that we used to be on to our new and __improved__ branch!
git branch -f "$pre_branch" | debug
git checkout "$pre_branch" | debug
# Pop stash if we stashed something
# If there were local changes, then restore them
if [ -n "${stashed_commit}" ]; then
git stash apply "${stashed_commit}"
fi
echoinfo "[x] All done, have a good day"
else
echoinfo "[x] All done, have a good day (nothing to do)"
fi
}
########################################################################################################################
# cmd_make_epoch can be run with the --make-epoch flag #
# #
# Marks the current commit as the epoch (00000000), instead of using the repository root commit as the epoch. #
# Respects the --format/--short flags #
########################################################################################################################
function cmd_make_epoch() {
prefix=$(linearize_root_commit)
echodebug "[x] Fixing $(git rev-parse HEAD) (looking for prefix=$prefix)"
lucky_commit "$prefix"
new_sha=$(git rev-parse HEAD)
echoinfo "[x] All done, ${new_sha} is the new git-linearize epoch :-)"
}
# Time to run something!
case "$EL_CMD" in
linearize)
cmd_linearize
;;
install_hook)
cmd_install_hook
;;
make_epoch)
cmd_make_epoch
;;
esac