forked from mvidner/recover-broken-git
-
Notifications
You must be signed in to change notification settings - Fork 0
/
recover-repo
executable file
·178 lines (150 loc) · 4.68 KB
/
recover-repo
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
#!/bin/sh -x
set -o errexit
set -o nounset
LIB=$(cd $(dirname $0); pwd)
rm -rf /tmp/tmp.* # cleanup broken checkout
PRODUCTION_REPO=$1
FIXED_REPO=$2
SVN_ROOT=$3
MODULE=$4
BRANCH_MAP=$5
WORK_DIR=/var/tmp/recover-broken-git
mkdir -p $WORK_DIR
cd $WORK_DIR
MODULE_DIR=${PRODUCTION_REPO##*/}
if [ ! -d $MODULE_DIR ]; then
git clone --bare $PRODUCTION_REPO $MODULE_DIR
else
echo "already done '$MODULE' - skipping"
exit 0
fi
cd $MODULE_DIR
git fetch origin
git remote | grep fixed || git remote add fixed $FIXED_REPO
git fetch fixed
# (cut: cut the columns with the * marker of the current branch)
BRANCHES=`git branch | cut -c3- | grep -v broken/`
#BRANCHES="master Code-11-SP2"
# $1 SVN URL (ROOT/BRANCH/MODULE)
# $2 git repo as a local directory
# $3 git branch
# $4 name; optional, basename of svn url is used
# result: diff sent to stdout, working dirs are removed
svn_git_compare() {
local SVNREPO="$1"
local GITREPO="$2"
local GITBRANCH="$3"
local NAME="${4-${SVNREPO##*/}}"
DIR=`mktemp -d`
pushd $DIR >/dev/null
svn export --quiet --ignore-keywords $SVNREPO $NAME-svn
(cd $GITREPO ; git archive --format=tar --prefix $NAME-git/ \
$GITBRANCH ) \
| tar xf -
SAME=true
# git-diff better than plain diff
git --no-pager diff $NAME-svn $NAME-git | tee $DIR/repo.diff
# non-empty diff => fail
test -s $DIR/repo.diff && SAME=false
popd >/dev/null
rm -rf $DIR
$SAME # can stop everything
}
svn_branch_from_git_branch() {
local BRANCH=$1
local SVN_BRANCH
SVN_BRANCH=`sed -n "s/^$BRANCH[[:space:]]\+\([^[:space:]]*\).*/\1/;T;p;q" $BRANCH_MAP`
if [ -n "$SVN_BRANCH" ]; then
SVN_BRANCH=branches/$SVN_BRANCH
elif [ $BRANCH = master ]; then
SVN_BRANCH=trunk
else
SVN_BRANCH=branches/$BRANCH
fi
echo $SVN_BRANCH
}
# $1 git branch
last_commit_in_svn() {
git rev-list -n1 --grep="svn path=/.*; revision=" $1
}
RECOVERY_NEEDED=false
for BRANCH in $BRANCHES; do
SVN_BRANCH=`svn_branch_from_git_branch $BRANCH`
SVN_TREE=$SVN_ROOT/$SVN_BRANCH/$MODULE
# ensure that we have a tag pointing to the last commit in SVN
LAST_COMMIT_IN_SVN=$(git rev-list -n1 svn/$BRANCH -- || true)
if [ -z "$LAST_COMMIT_IN_SVN" ]; then
LAST_COMMIT_IN_SVN=$(last_commit_in_svn $BRANCH)
git tag svn/$BRANCH $LAST_COMMIT_IN_SVN
fi
# compare the production repo. if there is no difference we can continue
# with the next branch
currentrev=`git rev-list -n1 $BRANCH`
fixedrev=`git rev-list -n1 fixed/$BRANCH || true`
if test $currentrev != $fixedrev ; then
echo "SHA1 sums differ"
RECOVERY_NEEDED=true
fi
if svn_git_compare "$SVN_TREE" `pwd` svn/$BRANCH; then
echo $MODULE/$BRANCH is OK already
continue
fi
echo $MODULE/$BRANCH needs recovery
# but even if only one tree is different,
# the history of all child trees changes
RECOVERY_NEEDED=true
done
if ! $RECOVERY_NEEDED; then
echo $MODULE is ALL OK already
exit 0
fi
echo "GONNA FIX, HOLD ON TO YOUR HAT"
for BRANCH in $BRANCHES; do
SVN_BRANCH=`svn_branch_from_git_branch $BRANCH`
SVN_TREE=$SVN_ROOT/$SVN_BRANCH/$MODULE
# there is a difference.
# compare that the fixed repo has no difference
HAVEBRANCH=true
git rev-list -n1 fixed/$BRANCH || HAVEBRANCH=false
if $HAVEBRANCH ; then
svn_git_compare "$SVN_TREE" `pwd` `last_commit_in_svn fixed/$BRANCH`
# compare and skip if equal
currentrev=`git rev-list -n1 $BRANCH`
fixedrev=`git rev-list -n1 fixed/$BRANCH || true`
if test $currentrev = $fixedrev ; then
echo "skipping branch '$BRANCH'"
continue
fi
fi
# next: rename branch refs
git branch broken/$BRANCH $BRANCH || true # rename to broken, but do not overwrite later
git tag broken/svn/$BRANCH svn/$BRANCH || true
# assume last commit on remotes/fixed/$BRANCH is from SVN
if $HAVEBRANCH ; then
git tag -f svn/$BRANCH remotes/fixed/$BRANCH
# rebase
D=`pwd`
pushd /tmp
git clone $D tmp.$BRANCH
cd tmp.$BRANCH
[ "$BRANCH" = master ] || git branch $BRANCH origin/$BRANCH
if ! git rebase --preserve-merges --onto svn/$BRANCH broken/svn/$BRANCH $BRANCH ; then
# if someone re-added the executable bits the above breaks because of an empty commit
# so we retry it without --preserve-merges to make it work
git rebase --abort
git rebase --onto svn/$BRANCH broken/svn/$BRANCH $BRANCH
fi
git push -f origin $BRANCH
cd ..
rm -rf tmp.$BRANCH
popd
else
echo "removing old broken branch '$BRANCH' (dropped module)"
# because we can not delete the branch we are on
echo "ref: refs/heads/broken/$BRANCH" > HEAD
git branch -d $BRANCH
fi
# push. failsafe? iterate?
done
# update tag refs to fixed ones
git fetch fixed --tags