-
Notifications
You must be signed in to change notification settings - Fork 2
/
4-way-diff
executable file
·127 lines (110 loc) · 3.25 KB
/
4-way-diff
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
#!/bin/bash
#
# Perform a 4-way diff between:
# - local feature/topic branch + whether it is clean (fully committed)
# - remote feature/topic branch
# - remote main branch
# - local main branch
#
# Give the user a good message about which is not in sync
# and what needs to be done to make it so.
#
# pragma to protect from some of my githooks trying to execute this:
# pre-commit-exec-exempt
#
# Author: Ariel Faigon, 2021
#
# Default name to use for 'origin'
# Some call it 'remote' or 'upstream'- adapt to taste
UPSTREAM=${UPSTREAM:-origin}
# Default names to try (guess) 'main/master' branch name - adapt to taste
MAIN_BRANCH_NAMES=( 'dev' 'main' 'master' )
#
# $TB branch is throwaway, temporary to do the sync/merge work
# in a way that is totally non-destructive to any other branches
# We remove it after the merge with $MB is successful.
# We can call it whatever we want. It should never exist.
#
TB='temp-branch'
function out() {
echo "$0: $*"
}
function err() {
out "$@" >&2
}
function die() {
err "$@"
exit 1
}
function current-branch() {
git rev-parse --abbrev-ref HEAD
}
function reflog-last-checkout() {
local CB
CB="$(current-branch)"
git reflog |
grep -Eo ": moving from .* to $CB\$" |
perl -ne '
if (/^: moving from (.*) to (.*)$/) {
my ($from, $to) = ($1, $2);
# print STDERR "perl: matched from=$from to=$to\n";
if ($from ne "'"$TB"'") {
print $from, "\n";
exit 0;
}
}
'
}
function local-branch-exists() {
local branchname="$1"
git show-ref --verify --quiet "refs/heads/$branchname"
# $? == 0 means local branch <branchname> exists.
}
function get-main-branch-name() {
local arg="$1" # preferred branch (optional)
local lc
lc="$(reflog-last-checkout)"
# $arg is explicitly passed by user, others are guesses
for branch in "$arg" "${MAIN_BRANCH_NAMES[@]}" "$lc"; do
if [[ -z "$branch" ]]; then
continue
fi
if local-branch-exists "$branch"; then
echo "$branch"
return
fi
done
}
function remote-branch-exists() {
git ls-remote --heads "$UPSTREAM" | grep -q "refs/heads/$1\$"
}
#
# -- main
#
CB="$(current-branch)"
MB="$(get-main-branch-name "$1")"
if git diff --exit-code; then
echo "(1) $CB branch is clean, cool"
else
die "(1) $CB branch not clean. Need to commit (or stash) locally"
fi
if git diff --exit-code "$UPSTREAM/$CB" "$CB"; then
echo "(2) $CB branch: local == remote, no need to push, cool"
else
die "(2) $CB branch: local != remote. Need to push"
fi
if remote-branch-exists "$CB"; then
if git diff --exit-code "$UPSTREAM/$MB" "$CB"; then
echo "(3) local $CB == remote $MB, no need to remote-merge"
else
die "(3) local $CB != remote $MB. Need to remote-merge"
fi
else
echo "(3) remote $CB doesn't exist (already merged?)"
fi
if git diff --exit-code "$MB"; then
echo "(4) local $CB == local $MB, no need to pull in $MB, cool"
else
err "(4) local $CB != local $MB. Need to pull in local $MB"
printf "To fix:\n\tgit checkout %s && git pull && git checkout %s\n" "$MB" "$CB"
fi