forked from kollerma/git-submodule-tools
-
Notifications
You must be signed in to change notification settings - Fork 0
/
git-converge-submodules
executable file
·213 lines (203 loc) · 7.2 KB
/
git-converge-submodules
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
#!/bin/bash -e
## If local and remote have diverged, but actually
## only the submodule pointers are different
## force a merge to syncronise them again.
## -q for quiet operation
## -f for forcing trying to converge
## how does it work:
## 1. prefer local commit pointer
## if only submodules and .gitmodules has changed:
## if submodule was removed in remote since merge-base: remove it
## if submodule was removed locally: keep it removed
## if submodule urls are different locally and remotely
## if submodule did not change in remote since merge-base: keep local
## else use remote
## else: give up
## read input, display help if necessary
if [[ "$@" == *--help* ]]; then
cat<<EOF
Resolve merge conflicts
This command tries to automatically resolve the most common conflicts
that arise when working with git submodules. It takes care only of
conflicts involving submodules and the file ".gitmodules".
Usage:
git converge-submodules [-q] [-f]
-q: act quietly
-f: disable some checks to force execution
EOF
exit 0;
fi
while getopts ":fq" opt; do
case $opt in
q)
q=1
#echo "-q was triggered!" >&2
;;
f)
force=1
#echo "-f was triggered!" >&2
;;
\?)
# ignore this
#echo "Invalid option: -$OPTARG" >&2
;;
esac
done
## from the git mailinglist:
function git
{
LC_MESSAGES=C command git "$@"
}
export git
## ensure we are in the toplevel directory
cdup=$(git rev-parse --show-toplevel) &&
cd "$cdup" || {
echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree"
exit 1
}
if [ -f .gitmodules ]; then
## Check if local and remote have diverged
## in the submodules and merge if necessary
## check if its a remote tracking branch and get it
tmp=`git branch --no-color -vv 2> /dev/null`
while read line; do
if [[ "${line:0:1}" != "*" ]]; then
continue
fi
##echo "$line"
branch=`expr "$line" : '\** *\([^ ]*\)'`
remote=`expr "$line" : '.*\[\(.*\)\]'` || nontracking=0
if [[ `expr "$remote" : '.*:.*\(behind\)'` == 0 ]]; then
## if we're not behind, there's no need to converge...
exit 0
fi
remote=`expr "$remote" : '\([^:]*\)'`
done <<< "$tmp"
if [[ ! "$nontracking" ]]; then
## get submodules
sub=`git ls-files --error-unmatch --stage | grep -E '^160000' | sed -e 's/^.* //' | tr '\n' ' '`
if [[ $force -eq 1 ]] || ! git diff "$branch"..."$remote" --no-ext-diff --quiet --exit-code -- $sub; then
## there are differences, do a merge
## check for uncommmited changes first
if ! git check-clean --uncommitted --unmerged --exit-code --ignore-submodules; then
cat >&2 <<EOF
Error in $PWD:
Uncommitted changes, cannot converge submodules.
EOF
exit 1
fi
echo "Converging local and remote..."
## check for differences other than in submodule pointers
output=`git diff $branch...$remote --no-ext-diff --stat --ignore-submodules`
if [[ "$output" != "" ]]; then
## ok, there may be just differences in .gitmodules
if [[ $(echo "$output" | sed -e '$ d' -e '/ .gitmodules/ d') == "" ]]; then
## so probably a submodule was removed in the remote
## and we made some changes to it
## create a backup of the current .gitmodules
cp .gitmodules .gitmodules-old
## checkout the version from merge base
mergebase=`git merge-base HEAD origin/master`
git checkout "$mergebase" -- .gitmodules || { echo "Mergebase: $mergebase at $PWD"; exit 1 ;}
cp .gitmodules .gitmodules-mb
## checkout remote .gitmodules
git checkout "$remote" -- .gitmodules
## now walk submodules in .gitmodules~ and check their status
tmp=`git config -f .gitmodules-old --get-regexp "submodule\..*\.path"`
while read line; do
##echo "$line"
lsub=`expr "$line" : 'submodule\.\(.*\)\.path '`
lpath=${line#submodule.*.path }
lurl=`git config -f .gitmodules-old --get submodule."$lsub".url`
## check if this submodule exists in remote
## if the url is different, favor theirs if changed since $mergehead
rurl=`git config -f .gitmodules --get submodule."$lsub".url || echo ""`
##echo "rurl: $rurl"
if [[ "$rurl" ]]; then
## if the urls haven't changed: continue
if [[ "$rurl" != "$lurl" ]]; then
mburl=`git config -f .gitmodules-mb --get submodules."$lsub".url || echo ""`
##echo "mburl: $mburl"
if [ "$rurl" == "$mburl" ]; then
## ok, url has changed in local but not remote
## use local version
git config -f .gitmodules submodule."$lsub".url "$lurl"
else
## use remote url (already in .gitmodules)
## but we have to update the commit pointer
git checkout "$remote" -- "$lsub"
fi
fi
else
## seems the submodule does not exist in remote
## check if it was present in merge-base
if [ "$(git config -f .gitmodules-mb --get submodule."$lsub".url)" != "" ]; then
## yes, it was there
## so remove it aswell
cat<<EOF
Removing submodule "$lsub" dropped in remote.
Issue "git submodule add $lurl $lsub" to add it again.
EOF
git rm-submodule --no-commit "$lsub" ||
echo "Submodule has already been removed."
continue
fi
## ok, it was added later: keep it
## add it to .gitmodules
##echo "adding $lsub at $lurl"
git config -f .gitmodules submodule."$lsub".path "$lpath"
git config -f .gitmodules submodule."$lsub".url "$lurl"
fi
done <<< "$tmp"
rm .gitmodules-old .gitmodules-mb
git add .gitmodules
## sync new configs
git submodule --quiet sync
## update
git submodule --quiet update --init --recursive
## attach heads
## git submodule --quiet foreach --recursive git attach-head
git submodule --quiet foreach --recursive 'echo "$toplevel/$path"' |
xargs -r -n1 -P5 bash -c 'cd "$1"; git attach-head' xargs
## commit
git rcommit -qam "Converged submodules" --non-recursive
## now we're ready to merge
git merge "$remote" -s recursive -X ours -q
else
cat >&2 <<EOF
Error in $PWD: (got cold feet)
Differences between local and remote other than just submodules,
cannot converge branches. Merge by hand.
EOF
exit 1
fi
else
## there are just differences in the submodule pointers
## TODO: help to merge this aswell
git merge $remote -q || {
## there was a merge conflict, i.e. both pointers different from mergebase
## get conflicting pointers
conflicts=`git status --porcelain | grep -E '^UU' | sed -e 's/UU //' | tr '\n' ' '`
## prefer our version
git checkout HEAD -- $conflicts
## commit
git commit -qm "Converged submodules, used local submodule version for conflicts: $conflicts."
}
## update submodules
git submodule --quiet update --init --recursive
## attach heads
#git submodule --quiet foreach --recursive git attach-head
git submodule --quiet foreach --recursive 'echo "$toplevel/$path"' |
xargs -r -n1 -P5 bash -c 'cd "$1"; git attach-head' xargs
fi
echo "done."
else
if [[ $q -ne 1 ]] && ! git diff "$branch".."$remote" --no-ext-diff --quiet --exit-code -- $sub; then
cat<<EOF
Notice in $PWD:
Not converging, use "git converge-submodules -f" if necessary
EOF
fi
fi
fi
fi