##// END OF EJS Templates
rebase: avoid redundant merges (issue1301)
Stefano Tortarolo -
r7278:45495d78 default
parent child Browse files
Show More
@@ -1,391 +1,421
1 1 # rebase.py - rebasing feature for mercurial
2 2 #
3 3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 '''move sets of revisions to a different ancestor
9 9
10 10 This extension lets you rebase changesets in an existing Mercurial repository.
11 11
12 12 For more information:
13 13 http://www.selenic.com/mercurial/wiki/index.cgi/RebaseProject
14 14 '''
15 15
16 from mercurial import util, repair, merge, cmdutil, dispatch, commands, extensions
16 from mercurial import util, repair, merge, cmdutil, dispatch, commands
17 from mercurial import extensions, ancestor
17 18 from mercurial.commands import templateopts
18 19 from mercurial.node import nullrev
19 20 from mercurial.i18n import _
20 21 import os, errno
21 22
23 def rebasemerge(repo, rev, first=False):
24 'return the correct ancestor'
25 oldancestor = ancestor.ancestor
26
27 def newancestor(a, b, pfunc):
28 ancestor.ancestor = oldancestor
29 anc = ancestor.ancestor(a, b, pfunc)
30 if b == rev:
31 return repo[rev].parents()[0].rev()
32 return ancestor.ancestor(a, b, pfunc)
33
34 if not first:
35 ancestor.ancestor = newancestor
36 else:
37 repo.ui.debug(_("First revision, do not change ancestor\n"))
38 stats = merge.update(repo, rev, True, True, False)
39 return stats
40
22 41 def rebase(ui, repo, **opts):
23 42 """move changeset (and descendants) to a different branch
24 43
25 44 Rebase uses repeated merging to graft changesets from one part of history
26 45 onto another. This can be useful for linearizing local changes relative to
27 46 a master development tree.
28 47
29 48 If a rebase is interrupted to manually resolve a merge, it can be continued
30 49 with --continue or aborted with --abort.
31 50 """
32 51 originalwd = target = source = None
33 52 external = nullrev
34 53 state = skipped = {}
35 54
36 55 lock = wlock = None
37 56 try:
38 57 lock = repo.lock()
39 58 wlock = repo.wlock()
40 59
41 60 # Validate input and define rebasing points
42 61 destf = opts.get('dest', None)
43 62 srcf = opts.get('source', None)
44 63 basef = opts.get('base', None)
45 64 contf = opts.get('continue')
46 65 abortf = opts.get('abort')
47 66 collapsef = opts.get('collapse', False)
48 67 if contf or abortf:
49 68 if contf and abortf:
50 69 raise dispatch.ParseError('rebase',
51 70 _('cannot use both abort and continue'))
52 71 if collapsef:
53 72 raise dispatch.ParseError('rebase',
54 73 _('cannot use collapse with continue or abort'))
55 74
56 75 if (srcf or basef or destf):
57 76 raise dispatch.ParseError('rebase',
58 77 _('abort and continue do not allow specifying revisions'))
59 78
60 79 originalwd, target, state, collapsef, external = restorestatus(repo)
61 80 if abortf:
62 81 abort(repo, originalwd, target, state)
63 82 return
64 83 else:
65 84 if srcf and basef:
66 85 raise dispatch.ParseError('rebase', _('cannot specify both a '
67 86 'revision and a base'))
68 87 cmdutil.bail_if_changed(repo)
69 88 result = buildstate(repo, destf, srcf, basef, collapsef)
70 89 if result:
71 90 originalwd, target, state, external = result
72 91 else: # Empty state built, nothing to rebase
73 92 repo.ui.status(_('nothing to rebase\n'))
74 93 return
75 94
76 95 # Rebase
77 96 targetancestors = list(repo.changelog.ancestors(target))
78 97 targetancestors.append(target)
79 98
80 99 for rev in util.sort(state):
81 100 if state[rev] == -1:
82 101 storestatus(repo, originalwd, target, state, collapsef,
83 102 external)
84 103 rebasenode(repo, rev, target, state, skipped, targetancestors,
85 104 collapsef)
86 105 ui.note(_('rebase merging completed\n'))
87 106
88 107 if collapsef:
89 108 p1, p2 = defineparents(repo, min(state), target,
90 109 state, targetancestors)
91 110 concludenode(repo, rev, p1, external, state, collapsef,
92 111 last=True, skipped=skipped)
93 112
94 113 if 'qtip' in repo.tags():
95 114 updatemq(repo, state, skipped, **opts)
96 115
97 116 if not opts.get('keep'):
98 117 # Remove no more useful revisions
99 118 if (util.set(repo.changelog.descendants(min(state)))
100 119 - util.set(state.keys())):
101 120 ui.warn(_("warning: new changesets detected on source branch, "
102 121 "not stripping\n"))
103 122 else:
104 123 repair.strip(repo.ui, repo, repo[min(state)].node(), "strip")
105 124
106 125 clearstatus(repo)
107 126 ui.status(_("rebase completed\n"))
108 127 if os.path.exists(repo.sjoin('undo')):
109 128 util.unlink(repo.sjoin('undo'))
110 129 if skipped:
111 130 ui.note(_("%d revisions have been skipped\n") % len(skipped))
112 131 finally:
113 132 del lock, wlock
114 133
115 134 def concludenode(repo, rev, p1, p2, state, collapse, last=False, skipped={}):
116 135 """Skip commit if collapsing has been required and rev is not the last
117 136 revision, commit otherwise
118 137 """
119 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
138 repo.ui.debug(_(" set parents\n"))
139 if collapse and not last:
140 repo.dirstate.setparents(repo[p1].node())
141 return None
120 142
121 if collapse and not last:
122 return None
143 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
123 144
124 145 # Commit, record the old nodeid
125 146 m, a, r = repo.status()[:3]
126 147 newrev = nullrev
127 148 try:
128 149 if last:
129 150 commitmsg = 'Collapsed revision'
130 151 for rebased in state:
131 152 if rebased not in skipped:
132 153 commitmsg += '\n* %s' % repo[rebased].description()
133 154 commitmsg = repo.ui.edit(commitmsg, repo.ui.username())
134 155 else:
135 156 commitmsg = repo[rev].description()
136 157 # Commit might fail if unresolved files exist
137 158 newrev = repo.commit(m+a+r,
138 159 text=commitmsg,
139 160 user=repo[rev].user(),
140 161 date=repo[rev].date(),
141 162 extra={'rebase_source': repo[rev].hex()})
142 163 return newrev
143 164 except util.Abort:
144 165 # Invalidate the previous setparents
145 166 repo.dirstate.invalidate()
146 167 raise
147 168
148 169 def rebasenode(repo, rev, target, state, skipped, targetancestors, collapse):
149 170 'Rebase a single revision'
150 repo.ui.debug(_("rebasing %d:%s\n") % (rev, repo[rev].node()))
171 repo.ui.debug(_("rebasing %d:%s\n") % (rev, repo[rev]))
151 172
152 173 p1, p2 = defineparents(repo, rev, target, state, targetancestors)
153 174
175 repo.ui.debug(_(" future parents are %d and %d\n") % (repo[p1].rev(),
176 repo[p2].rev()))
177
154 178 # Merge phase
155 179 if len(repo.parents()) != 2:
156 180 # Update to target and merge it with local
157 merge.update(repo, p1, False, True, False)
181 if repo['.'].rev() != repo[p1].rev():
182 repo.ui.debug(_(" update to %d:%s\n") % (repo[p1].rev(), repo[p1]))
183 merge.update(repo, p1, False, True, False)
184 else:
185 repo.ui.debug(_(" already in target\n"))
158 186 repo.dirstate.write()
159 stats = merge.update(repo, rev, True, False, False)
187 repo.ui.debug(_(" merge against %d:%s\n") % (repo[rev].rev(), repo[rev]))
188 first = repo[rev].rev() == repo[min(state)].rev()
189 stats = rebasemerge(repo, rev, first)
160 190
161 191 if stats[3] > 0:
162 192 raise util.Abort(_('fix unresolved conflicts with hg resolve then '
163 193 'run hg rebase --continue'))
164 194 else: # we have an interrupted rebase
165 195 repo.ui.debug(_('resuming interrupted rebase\n'))
166 196
167 197
168 198 newrev = concludenode(repo, rev, p1, p2, state, collapse)
169 199
170 200 # Update the state
171 201 if newrev is not None:
172 202 state[rev] = repo[newrev].rev()
173 203 else:
174 204 if not collapse:
175 205 repo.ui.note(_('no changes, revision %d skipped\n') % rev)
176 206 repo.ui.debug(_('next revision set to %s\n') % p1)
177 207 skipped[rev] = True
178 208 state[rev] = p1
179 209
180 210 def defineparents(repo, rev, target, state, targetancestors):
181 211 'Return the new parent relationship of the revision that will be rebased'
182 212 parents = repo[rev].parents()
183 213 p1 = p2 = nullrev
184 214
185 215 P1n = parents[0].rev()
186 216 if P1n in targetancestors:
187 217 p1 = target
188 218 elif P1n in state:
189 219 p1 = state[P1n]
190 220 else: # P1n external
191 221 p1 = target
192 222 p2 = P1n
193 223
194 224 if len(parents) == 2 and parents[1].rev() not in targetancestors:
195 225 P2n = parents[1].rev()
196 226 # interesting second parent
197 227 if P2n in state:
198 228 if p1 == target: # P1n in targetancestors or external
199 229 p1 = state[P2n]
200 230 else:
201 231 p2 = state[P2n]
202 232 else: # P2n external
203 233 if p2 != nullrev: # P1n external too => rev is a merged revision
204 234 raise util.Abort(_('cannot use revision %d as base, result '
205 235 'would have 3 parents') % rev)
206 236 p2 = P2n
207 237 return p1, p2
208 238
209 239 def updatemq(repo, state, skipped, **opts):
210 240 'Update rebased mq patches - finalize and then import them'
211 241 mqrebase = {}
212 242 for p in repo.mq.applied:
213 243 if repo[p.rev].rev() in state:
214 244 repo.ui.debug(_('revision %d is an mq patch (%s), finalize it.\n') %
215 245 (repo[p.rev].rev(), p.name))
216 246 mqrebase[repo[p.rev].rev()] = p.name
217 247
218 248 if mqrebase:
219 249 repo.mq.finish(repo, mqrebase.keys())
220 250
221 251 # We must start import from the newest revision
222 252 mq = mqrebase.keys()
223 253 mq.sort()
224 254 mq.reverse()
225 255 for rev in mq:
226 256 if rev not in skipped:
227 257 repo.ui.debug(_('import mq patch %d (%s)\n')
228 258 % (state[rev], mqrebase[rev]))
229 259 repo.mq.qimport(repo, (), patchname=mqrebase[rev],
230 260 git=opts.get('git', False),rev=[str(state[rev])])
231 261 repo.mq.save_dirty()
232 262
233 263 def storestatus(repo, originalwd, target, state, collapse, external):
234 264 'Store the current status to allow recovery'
235 265 f = repo.opener("rebasestate", "w")
236 266 f.write(repo[originalwd].hex() + '\n')
237 267 f.write(repo[target].hex() + '\n')
238 268 f.write(repo[external].hex() + '\n')
239 269 f.write('%d\n' % int(collapse))
240 270 for d, v in state.items():
241 271 oldrev = repo[d].hex()
242 272 newrev = repo[v].hex()
243 273 f.write("%s:%s\n" % (oldrev, newrev))
244 274 f.close()
245 275 repo.ui.debug(_('rebase status stored\n'))
246 276
247 277 def clearstatus(repo):
248 278 'Remove the status files'
249 279 if os.path.exists(repo.join("rebasestate")):
250 280 util.unlink(repo.join("rebasestate"))
251 281
252 282 def restorestatus(repo):
253 283 'Restore a previously stored status'
254 284 try:
255 285 target = None
256 286 collapse = False
257 287 external = nullrev
258 288 state = {}
259 289 f = repo.opener("rebasestate")
260 290 for i, l in enumerate(f.read().splitlines()):
261 291 if i == 0:
262 292 originalwd = repo[l].rev()
263 293 elif i == 1:
264 294 target = repo[l].rev()
265 295 elif i == 2:
266 296 external = repo[l].rev()
267 297 elif i == 3:
268 298 collapse = bool(int(l))
269 299 else:
270 300 oldrev, newrev = l.split(':')
271 301 state[repo[oldrev].rev()] = repo[newrev].rev()
272 302 repo.ui.debug(_('rebase status resumed\n'))
273 303 return originalwd, target, state, collapse, external
274 304 except IOError, err:
275 305 if err.errno != errno.ENOENT:
276 306 raise
277 307 raise util.Abort(_('no rebase in progress'))
278 308
279 309 def abort(repo, originalwd, target, state):
280 310 'Restore the repository to its original state'
281 311 if util.set(repo.changelog.descendants(target)) - util.set(state.values()):
282 312 repo.ui.warn(_("warning: new changesets detected on target branch, "
283 313 "not stripping\n"))
284 314 else:
285 315 # Strip from the first rebased revision
286 316 merge.update(repo, repo[originalwd].rev(), False, True, False)
287 317 rebased = filter(lambda x: x > -1, state.values())
288 318 if rebased:
289 319 strippoint = min(rebased)
290 320 repair.strip(repo.ui, repo, repo[strippoint].node(), "strip")
291 321 clearstatus(repo)
292 322 repo.ui.status(_('rebase aborted\n'))
293 323
294 324 def buildstate(repo, dest, src, base, collapse):
295 325 'Define which revisions are going to be rebased and where'
296 326 state = {}
297 327 targetancestors = util.set()
298 328
299 329 if not dest:
300 330 # Destination defaults to the latest revision in the current branch
301 331 branch = repo[None].branch()
302 332 dest = repo[branch].rev()
303 333 else:
304 334 if 'qtip' in repo.tags() and (repo[dest].hex() in
305 335 [s.rev for s in repo.mq.applied]):
306 336 raise util.Abort(_('cannot rebase onto an applied mq patch'))
307 337 dest = repo[dest].rev()
308 338
309 339 if src:
310 340 commonbase = repo[src].ancestor(repo[dest])
311 341 if commonbase == repo[src]:
312 342 raise util.Abort(_('cannot rebase an ancestor'))
313 343 if commonbase == repo[dest]:
314 344 raise util.Abort(_('cannot rebase a descendant'))
315 345 source = repo[src].rev()
316 346 else:
317 347 if base:
318 348 cwd = repo[base].rev()
319 349 else:
320 350 cwd = repo['.'].rev()
321 351
322 352 if cwd == dest:
323 353 repo.ui.debug(_('already working on current\n'))
324 354 return None
325 355
326 356 targetancestors = util.set(repo.changelog.ancestors(dest))
327 357 if cwd in targetancestors:
328 358 repo.ui.debug(_('already working on the current branch\n'))
329 359 return None
330 360
331 361 cwdancestors = util.set(repo.changelog.ancestors(cwd))
332 362 cwdancestors.add(cwd)
333 363 rebasingbranch = cwdancestors - targetancestors
334 364 source = min(rebasingbranch)
335 365
336 366 repo.ui.debug(_('rebase onto %d starting from %d\n') % (dest, source))
337 367 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
338 368 external = nullrev
339 369 if collapse:
340 370 if not targetancestors:
341 371 targetancestors = util.set(repo.changelog.ancestors(dest))
342 372 for rev in state:
343 373 # Check externals and fail if there are more than one
344 374 for p in repo[rev].parents():
345 375 if (p.rev() not in state and p.rev() != source
346 376 and p.rev() not in targetancestors):
347 377 if external != nullrev:
348 378 raise util.Abort(_('unable to collapse, there is more '
349 379 'than one external parent'))
350 380 external = p.rev()
351 381
352 382 state[source] = nullrev
353 383 return repo['.'].rev(), repo[dest].rev(), state, external
354 384
355 385 def pullrebase(orig, ui, repo, *args, **opts):
356 386 'Call rebase after pull if the latter has been invoked with --rebase'
357 387 if opts.get('rebase'):
358 388 if opts.get('update'):
359 389 raise util.Abort(_('--update and --rebase are not compatible'))
360 390
361 391 cmdutil.bail_if_changed(repo)
362 392 revsprepull = len(repo)
363 393 orig(ui, repo, *args, **opts)
364 394 revspostpull = len(repo)
365 395 if revspostpull > revsprepull:
366 396 rebase(ui, repo, **opts)
367 397 else:
368 398 orig(ui, repo, *args, **opts)
369 399
370 400 def uisetup(ui):
371 401 'Replace pull with a decorator to provide --rebase option'
372 402 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
373 403 entry[1].append(('', 'rebase', None,
374 404 _("rebase working directory to branch head"))
375 405 )
376 406
377 407 cmdtable = {
378 408 "rebase":
379 409 (rebase,
380 410 [
381 411 ('', 'keep', False, _('keep original revisions')),
382 412 ('s', 'source', '', _('rebase from a given revision')),
383 413 ('b', 'base', '', _('rebase from the base of a given revision')),
384 414 ('d', 'dest', '', _('rebase onto a given revision')),
385 415 ('', 'collapse', False, _('collapse the rebased revisions')),
386 416 ('c', 'continue', False, _('continue an interrupted rebase')),
387 417 ('a', 'abort', False, _('abort an interrupted rebase')),] +
388 418 templateopts,
389 419 _('hg rebase [-s rev | -b rev] [-d rev] [--collapse] | [-c] | [-a] | '
390 420 '[--keep]')),
391 421 }
@@ -1,91 +1,180
1 1 #!/bin/sh
2 2
3 3 echo "[extensions]" >> $HGRCPATH
4 4 echo "graphlog=" >> $HGRCPATH
5 5 echo "rebase=" >> $HGRCPATH
6 6
7 7 BASE=`pwd`
8 8
9 9 addcommit () {
10 10 echo $1 > $1
11 11 hg add $1
12 12 hg commit -d "${2} 0" -u test -m $1
13 13 }
14 14
15 15 commit () {
16 16 hg commit -d "${2} 0" -u test -m $1
17 17 }
18 18
19 19 createrepo () {
20 20 cd $BASE
21 21 rm -rf a
22 22 hg init a
23 23 cd a
24 24 addcommit "A" 0
25 25 addcommit "B" 1
26 26 addcommit "C" 2
27 27 addcommit "D" 3
28 28
29 29 hg update -C 0
30 30 addcommit "E" 4
31 31
32 32 hg update -C 0
33 33 addcommit "F" 5
34 34
35 35 hg merge -r 4
36 36 commit "G" 6
37 37
38 38 hg update -C 5
39 39 addcommit "H" 7
40 40 }
41 41
42 42 createrepo > /dev/null 2>&1
43 43 hg glog --template '{rev}: {desc}\n'
44 echo '% Rebasing'
44 echo '% Rebasing B onto H'
45 45 hg up -C 3
46 46 hg rebase --collapse 2>&1 | sed 's/\(saving bundle to \).*/\1/'
47 47 hg glog --template '{rev}: {desc}\n'
48 echo "Expected A, B, C, D, F, H"
49 hg manifest
48 50
49 51 createrepo > /dev/null 2>&1
50 echo '% Rebasing'
52 echo
53 echo '% Rebasing G onto H'
51 54 hg rebase --base 6 --collapse 2>&1 | sed 's/\(saving bundle to \).*/\1/'
52 55 hg glog --template '{rev}: {desc}\n'
56 echo "Expected A, E, F, H"
57 hg manifest
53 58
54 59 createrepocomplex () {
55 60 cd $BASE
56 61 rm -rf a
57 62 hg init a
58 63 cd a
59 64 addcommit "A" 0
60 65 addcommit "B" 1
61 66
62 67 hg up 0
63 68 addcommit "C" 2
64 69 hg merge
65 70 commit "D" 3
66 71
67 72 hg up 1
68 73 addcommit "E" 4
69 74
70 75 addcommit "F" 5
71 76
72 77 hg merge
73 78 commit "G" 6
74 79
75 80 hg up 0
76 81 addcommit "H" 7
77 82 }
78 83
84 echo
79 85 createrepocomplex > /dev/null 2>&1
80 86 hg glog --template '{rev}: {desc}\n'
81 87
82 88 echo
83 89 echo '% Rebase and collapse - more than one external (fail)'
84 90 hg rebase -s 2 --collapse
85 91
86 92 echo
87 echo '% Rebase and collapse'
93 echo '% Rebase and collapse - E onto H'
88 94 hg rebase -s 4 --collapse 2>&1 | sed 's/\(saving bundle to \).*/\1/'
89 95 hg glog --template '{rev}: {desc}\n'
96 echo "Expected A, B, C, E, F, H"
97 hg manifest
90 98
99 createrepocomplex () {
100 cd $BASE
101 rm -rf a
102 hg init a
103 cd a
104 addcommit "A" 0
105 addcommit "B" 1
106
107 hg up 0
108 addcommit "C" 2
109 hg merge
110 commit "D" 3
111
112 hg up 1
113 addcommit "E" 4
114
115 echo "F" > E
116 commit "F" 5
117
118 addcommit "G" 6
119
120 hg merge
121 commit "H" 7
122
123 hg up 0
124 addcommit "I" 8
125 }
126
127 echo
128 createrepocomplex > /dev/null 2>&1
129 hg glog --template '{rev}: {desc}\n'
130
131 echo
132 echo '% Rebase and collapse - E onto I'
133 hg rebase -s 4 --collapse
134
135 echo '% Fix conflict and continue'
136 echo 'Resolved merge' > E
137 hg resolve -m E
138 hg rebase -c 2>&1 | sed 's/\(saving bundle to \).*/\1/'
139
140 hg glog --template '{rev}: {desc}\n'
141
142 echo "Expected A, B, C, E, G, I"
143 hg manifest
144
145 echo 'Cat E:'
146 cat E
147
148 createrepocomplex () {
149 cd $BASE
150 rm -rf a
151 hg init a
152 cd a
153 addcommit "A" 0
154 addcommit "B" 1
155
156 addcommit "C" 2
157 hg up 1
158
159 addcommit "D" 3
160
161 hg merge
162 commit "E" 4
163
164 hg up 0
165 addcommit "F" 5
166 }
167
168 echo
169 createrepocomplex > /dev/null 2>&1
170 hg glog --template '{rev}: {desc}\n'
171
172 echo
173 echo '% Rebase and collapse - B onto F'
174 hg rebase -s 1 --collapse 2>&1 | sed 's/\(saving bundle to \).*/\1/'
175
176 hg glog --template '{rev}: {desc}\n'
177
178 echo "Expected A, B, C, D, F"
179 hg manifest
91 180 exit 0
@@ -1,104 +1,219
1 1 @ 7: H
2 2 |
3 3 | o 6: G
4 4 |/|
5 5 o | 5: F
6 6 | |
7 7 | o 4: E
8 8 |/
9 9 | o 3: D
10 10 | |
11 11 | o 2: C
12 12 | |
13 13 | o 1: B
14 14 |/
15 15 o 0: A
16 16
17 % Rebasing
17 % Rebasing B onto H
18 18 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
19 19 saving bundle to
20 20 adding branch
21 21 adding changesets
22 22 adding manifests
23 23 adding file changes
24 24 added 5 changesets with 6 changes to 6 files (+1 heads)
25 25 rebase completed
26 26 @ 5: Collapsed revision
27 27 | * B
28 28 | * C
29 29 | * D
30 30 o 4: H
31 31 |
32 32 | o 3: G
33 33 |/|
34 34 o | 2: F
35 35 | |
36 36 | o 1: E
37 37 |/
38 38 o 0: A
39 39
40 % Rebasing
40 Expected A, B, C, D, F, H
41 A
42 B
43 C
44 D
45 F
46 H
47
48 % Rebasing G onto H
41 49 saving bundle to
42 50 adding branch
43 51 adding changesets
44 52 adding manifests
45 53 adding file changes
46 54 added 3 changesets with 3 changes to 3 files (+1 heads)
47 55 rebase completed
48 56 @ 6: Collapsed revision
49 57 | * E
50 58 | * G
51 59 o 5: H
52 60 |
53 61 o 4: F
54 62 |
55 63 | o 3: D
56 64 | |
57 65 | o 2: C
58 66 | |
59 67 | o 1: B
60 68 |/
61 69 o 0: A
62 70
71 Expected A, E, F, H
72 A
73 E
74 F
75 H
76
63 77 @ 7: H
64 78 |
65 79 | o 6: G
66 80 | |\
67 81 | | o 5: F
68 82 | | |
69 83 | | o 4: E
70 84 | | |
71 85 | o | 3: D
72 86 | |\|
73 87 | o | 2: C
74 88 |/ /
75 89 | o 1: B
76 90 |/
77 91 o 0: A
78 92
79 93
80 94 % Rebase and collapse - more than one external (fail)
81 95 abort: unable to collapse, there is more than one external parent
82 96
83 % Rebase and collapse
97 % Rebase and collapse - E onto H
84 98 saving bundle to
85 99 adding branch
86 100 adding changesets
87 101 adding manifests
88 102 adding file changes
89 103 added 2 changesets with 3 changes to 3 files
90 104 rebase completed
91 105 @ 5: Collapsed revision
92 106 |\ * E
93 107 | | * F
94 108 | | * G
95 109 | o 4: H
96 110 | |
97 111 o | 3: D
98 112 |\ \
99 113 | o | 2: C
100 114 | |/
101 115 o / 1: B
102 116 |/
103 117 o 0: A
104 118
119 Expected A, B, C, E, F, H
120 A
121 B
122 C
123 E
124 F
125 H
126
127 @ 8: I
128 |
129 | o 7: H
130 | |\
131 | | o 6: G
132 | | |
133 | | o 5: F
134 | | |
135 | | o 4: E
136 | | |
137 | o | 3: D
138 | |\|
139 | o | 2: C
140 |/ /
141 | o 1: B
142 |/
143 o 0: A
144
145
146 % Rebase and collapse - E onto I
147 merging E
148 warning: conflicts during merge.
149 merging E failed!
150 abort: fix unresolved conflicts with hg resolve then run hg rebase --continue
151 % Fix conflict and continue
152 saving bundle to
153 adding branch
154 adding changesets
155 adding manifests
156 adding file changes
157 added 2 changesets with 3 changes to 3 files
158 rebase completed
159 @ 5: Collapsed revision
160 |\ * E
161 | | * F
162 | | * G
163 | | * H
164 | o 4: I
165 | |
166 o | 3: D
167 |\ \
168 | o | 2: C
169 | |/
170 o / 1: B
171 |/
172 o 0: A
173
174 Expected A, B, C, E, G, I
175 A
176 B
177 C
178 E
179 G
180 I
181 Cat E:
182 Resolved merge
183
184 @ 5: F
185 |
186 | o 4: E
187 | |\
188 | | o 3: D
189 | | |
190 | o | 2: C
191 | |/
192 | o 1: B
193 |/
194 o 0: A
195
196
197 % Rebase and collapse - B onto F
198 saving bundle to
199 adding branch
200 adding changesets
201 adding manifests
202 adding file changes
203 added 2 changesets with 4 changes to 4 files
204 rebase completed
205 @ 2: Collapsed revision
206 | * B
207 | * C
208 | * D
209 | * E
210 o 1: F
211 |
212 o 0: A
213
214 Expected A, B, C, D, F
215 A
216 B
217 C
218 D
219 F
@@ -1,55 +1,78
1 1 #!/bin/sh
2 2
3 3 echo "[extensions]" >> $HGRCPATH
4 4 echo "graphlog=" >> $HGRCPATH
5 5 echo "rebase=" >> $HGRCPATH
6 6
7 7 cleanoutput () {
8 8 sed -e 's/\(Rebase status stored to\).*/\1/' \
9 9 -e 's/\(Rebase status restored from\).*/\1/' \
10 10 -e 's/\(saving bundle to \).*/\1/'
11 11 }
12 12
13 13 hg init a
14 14 cd a
15 15 echo 'c1' >common
16 16 hg add common
17 17 hg commit -d '0 0' -u test -m "C1"
18 18
19 19 echo 'c2' >>common
20 20 hg commit -d '1 0' -u test -m "C2"
21 21
22 22 echo 'c3' >>common
23 23 hg commit -d '2 0' -u test -m "C3"
24 24
25 25 hg update -C 1
26 26 echo 'l1' >>extra
27 27 hg add extra
28 28 hg commit -d '3 0' -u test -m "L1"
29 29
30 30 sed -e 's/c2/l2/' common > common.new
31 31 mv common.new common
32 32 hg commit -d '4 0' -u test -m "L2"
33 33
34 echo 'l3' >> extra2
35 hg add extra2
36 hg commit -d '5 0' -u test -m "L3"
37
34 38 hg glog --template '{rev}: {desc}\n'
35 39
36 40 echo
37 41 echo '% Try to call --continue'
38 42 hg rebase --continue
39 43
40 44 echo
41 45 echo '% Conflicting rebase'
42 46 hg rebase -s 3 -d 2
43 47
44 48 echo
45 49 echo '% Try to continue without solving the conflict'
46 50 hg rebase --continue
47 51
48 52 echo
49 53 echo '% Conclude rebase'
50 echo 'solved merge' >common
54 echo 'resolved merge' >common
51 55 hg resolve -m common
52 56 hg rebase --continue 2>&1 | cleanoutput
53 57
58 hg glog --template '{rev}: {desc}\n'
54 59
55 hg glog --template '{rev}: {desc}\n'
60 echo
61 echo '% Check correctness'
62 echo ' - Rev. 0'
63 hg cat -r 0 common
64
65 echo ' - Rev. 1'
66 hg cat -r 1 common
67
68 echo ' - Rev. 2'
69 hg cat -r 2 common
70
71 echo ' - Rev. 3'
72 hg cat -r 3 common
73
74 echo ' - Rev. 4'
75 hg cat -r 4 common
76
77 echo ' - Rev. 5'
78 hg cat -r 5 common
@@ -1,43 +1,66
1 1 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
2 2 created new head
3 @ 4: L2
3 @ 5: L3
4 |
5 o 4: L2
4 6 |
5 7 o 3: L1
6 8 |
7 9 | o 2: C3
8 10 |/
9 11 o 1: C2
10 12 |
11 13 o 0: C1
12 14
13 15
14 16 % Try to call --continue
15 17 abort: no rebase in progress
16 18
17 19 % Conflicting rebase
18 20 merging common
19 21 warning: conflicts during merge.
20 22 merging common failed!
21 23 abort: fix unresolved conflicts with hg resolve then run hg rebase --continue
22 24
23 25 % Try to continue without solving the conflict
24 26 abort: unresolved merge conflicts (see hg resolve)
25 27
26 28 % Conclude rebase
27 29 saving bundle to
28 30 adding branch
29 31 adding changesets
30 32 adding manifests
31 33 adding file changes
32 added 2 changesets with 2 changes to 2 files
34 added 3 changesets with 3 changes to 3 files
33 35 rebase completed
34 @ 4: L2
36 @ 5: L3
37 |
38 o 4: L2
35 39 |
36 40 o 3: L1
37 41 |
38 42 o 2: C3
39 43 |
40 44 o 1: C2
41 45 |
42 46 o 0: C1
43 47
48
49 % Check correctness
50 - Rev. 0
51 c1
52 - Rev. 1
53 c1
54 c2
55 - Rev. 2
56 c1
57 c2
58 c3
59 - Rev. 3
60 c1
61 c2
62 c3
63 - Rev. 4
64 resolved merge
65 - Rev. 5
66 resolved merge
General Comments 0
You need to be logged in to leave comments. Login now