##// END OF EJS Templates
transplant: log source node when recovering too.
Brendan Cully -
r3758:889f7e74 default
parent child Browse files
Show More
@@ -1,568 +1,570
1 1 # Patch transplanting extension for Mercurial
2 2 #
3 3 # Copyright 2006 Brendan Cully <brendan@kublai.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 from mercurial.demandload import *
9 9 from mercurial.i18n import gettext as _
10 10 demandload(globals(), 'os tempfile')
11 11 demandload(globals(), 'mercurial:bundlerepo,cmdutil,commands,hg,merge,patch')
12 12 demandload(globals(), 'mercurial:revlog,util')
13 13
14 14 '''patch transplanting tool
15 15
16 16 This extension allows you to transplant patches from another branch.
17 17
18 18 Transplanted patches are recorded in .hg/transplant/transplants, as a map
19 19 from a changeset hash to its hash in the source repository.
20 20 '''
21 21
22 22 class transplantentry:
23 23 def __init__(self, lnode, rnode):
24 24 self.lnode = lnode
25 25 self.rnode = rnode
26 26
27 27 class transplants:
28 28 def __init__(self, path=None, transplantfile=None, opener=None):
29 29 self.path = path
30 30 self.transplantfile = transplantfile
31 31 self.opener = opener
32 32
33 33 if not opener:
34 34 self.opener = util.opener(self.path)
35 35 self.transplants = []
36 36 self.dirty = False
37 37 self.read()
38 38
39 39 def read(self):
40 40 abspath = os.path.join(self.path, self.transplantfile)
41 41 if self.transplantfile and os.path.exists(abspath):
42 42 for line in self.opener(self.transplantfile).read().splitlines():
43 43 lnode, rnode = map(revlog.bin, line.split(':'))
44 44 self.transplants.append(transplantentry(lnode, rnode))
45 45
46 46 def write(self):
47 47 if self.dirty and self.transplantfile:
48 48 if not os.path.isdir(self.path):
49 49 os.mkdir(self.path)
50 50 fp = self.opener(self.transplantfile, 'w')
51 51 for c in self.transplants:
52 52 l, r = map(revlog.hex, (c.lnode, c.rnode))
53 53 fp.write(l + ':' + r + '\n')
54 54 fp.close()
55 55 self.dirty = False
56 56
57 57 def get(self, rnode):
58 58 return [t for t in self.transplants if t.rnode == rnode]
59 59
60 60 def set(self, lnode, rnode):
61 61 self.transplants.append(transplantentry(lnode, rnode))
62 62 self.dirty = True
63 63
64 64 def remove(self, transplant):
65 65 del self.transplants[self.transplants.index(transplant)]
66 66 self.dirty = True
67 67
68 68 class transplanter:
69 69 def __init__(self, ui, repo):
70 70 self.ui = ui
71 71 self.path = repo.join('transplant')
72 72 self.opener = util.opener(self.path)
73 73 self.transplants = transplants(self.path, 'transplants', opener=self.opener)
74 74
75 75 def applied(self, repo, node, parent):
76 76 '''returns True if a node is already an ancestor of parent
77 77 or has already been transplanted'''
78 78 if hasnode(repo, node):
79 79 if node in repo.changelog.reachable(parent, stop=node):
80 80 return True
81 81 for t in self.transplants.get(node):
82 82 # it might have been stripped
83 83 if not hasnode(repo, t.lnode):
84 84 self.transplants.remove(t)
85 85 return False
86 86 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
87 87 return True
88 88 return False
89 89
90 90 def apply(self, repo, source, revmap, merges, opts={}):
91 91 '''apply the revisions in revmap one by one in revision order'''
92 92 revs = revmap.keys()
93 93 revs.sort()
94 94
95 95 p1, p2 = repo.dirstate.parents()
96 96 pulls = []
97 97 diffopts = patch.diffopts(self.ui, opts)
98 98 diffopts.git = True
99 99
100 100 lock = repo.lock()
101 101 wlock = repo.wlock()
102 102 try:
103 103 for rev in revs:
104 104 node = revmap[rev]
105 105 revstr = '%s:%s' % (rev, revlog.short(node))
106 106
107 107 if self.applied(repo, node, p1):
108 108 self.ui.warn(_('skipping already applied revision %s\n') %
109 109 revstr)
110 110 continue
111 111
112 112 parents = source.changelog.parents(node)
113 113 if not opts.get('filter'):
114 114 # If the changeset parent is the same as the wdir's parent,
115 115 # just pull it.
116 116 if parents[0] == p1:
117 117 pulls.append(node)
118 118 p1 = node
119 119 continue
120 120 if pulls:
121 121 if source != repo:
122 122 repo.pull(source, heads=pulls, lock=lock)
123 123 merge.update(repo, pulls[-1], wlock=wlock)
124 124 p1, p2 = repo.dirstate.parents()
125 125 pulls = []
126 126
127 127 domerge = False
128 128 if node in merges:
129 129 # pulling all the merge revs at once would mean we couldn't
130 130 # transplant after the latest even if transplants before them
131 131 # fail.
132 132 domerge = True
133 133 if not hasnode(repo, node):
134 134 repo.pull(source, heads=[node], lock=lock)
135 135
136 136 if parents[1] != revlog.nullid:
137 137 self.ui.note(_('skipping merge changeset %s:%s\n')
138 138 % (rev, revlog.short(node)))
139 139 patchfile = None
140 140 else:
141 141 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
142 142 fp = os.fdopen(fd, 'w')
143 143 patch.export(source, [node], fp=fp, opts=diffopts)
144 144 fp.close()
145 145
146 146 del revmap[rev]
147 147 if patchfile or domerge:
148 148 try:
149 149 n = self.applyone(repo, node, source.changelog.read(node),
150 150 patchfile, merge=domerge,
151 151 log=opts.get('log'),
152 152 filter=opts.get('filter'),
153 153 lock=lock, wlock=wlock)
154 154 if domerge:
155 155 self.ui.status(_('%s merged at %s\n') % (revstr,
156 156 revlog.short(n)))
157 157 else:
158 158 self.ui.status(_('%s transplanted to %s\n') % (revlog.short(node),
159 159 revlog.short(n)))
160 160 finally:
161 161 if patchfile:
162 162 os.unlink(patchfile)
163 163 if pulls:
164 164 repo.pull(source, heads=pulls, lock=lock)
165 165 merge.update(repo, pulls[-1], wlock=wlock)
166 166 finally:
167 167 self.saveseries(revmap, merges)
168 168 self.transplants.write()
169 169
170 170 def filter(self, filter, changelog, patchfile):
171 171 '''arbitrarily rewrite changeset before applying it'''
172 172
173 173 self.ui.status('filtering %s\n' % patchfile)
174 174 util.system('%s %s' % (filter, util.shellquote(patchfile)),
175 175 environ={'HGUSER': changelog[1]},
176 176 onerr=util.Abort, errprefix=_('filter failed'))
177 177
178 178 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
179 179 filter=None, lock=None, wlock=None):
180 180 '''apply the patch in patchfile to the repository as a transplant'''
181 181 (manifest, user, (time, timezone), files, message) = cl[:5]
182 182 date = "%d %d" % (time, timezone)
183 183 extra = {'transplant_source': node}
184 184 if filter:
185 185 self.filter(filter, cl, patchfile)
186 186 patchfile, message, user, date = patch.extract(self.ui, file(patchfile))
187 187
188 188 if log:
189 189 message += '\n(transplanted from %s)' % revlog.hex(node)
190 190
191 191 self.ui.status(_('applying %s\n') % revlog.short(node))
192 192 self.ui.note('%s %s\n%s\n' % (user, date, message))
193 193
194 194 if not patchfile and not merge:
195 195 raise util.Abort(_('can only omit patchfile if merging'))
196 196 if patchfile:
197 197 try:
198 198 files = {}
199 199 try:
200 200 fuzz = patch.patch(patchfile, self.ui, cwd=repo.root,
201 201 files=files)
202 202 if not files:
203 203 self.ui.warn(_('%s: empty changeset') % revlog.hex(node))
204 204 return
205 205 finally:
206 206 files = patch.updatedir(self.ui, repo, files, wlock=wlock)
207 207 if filter:
208 208 os.unlink(patchfile)
209 209 except Exception, inst:
210 210 if filter:
211 211 os.unlink(patchfile)
212 212 seriespath = os.path.join(self.path, 'series')
213 213 if os.path.exists(seriespath):
214 214 os.unlink(seriespath)
215 215 p1 = repo.dirstate.parents()[0]
216 216 p2 = node
217 217 self.log(user, date, message, p1, p2, merge=merge)
218 218 self.ui.write(str(inst) + '\n')
219 219 raise util.Abort(_('Fix up the merge and run hg transplant --continue'))
220 220 else:
221 221 files = None
222 222 if merge:
223 223 p1, p2 = repo.dirstate.parents()
224 224 repo.dirstate.setparents(p1, node)
225 225
226 226 n = repo.commit(files, message, user, date, lock=lock, wlock=wlock,
227 227 extra=extra)
228 228 if not merge:
229 229 self.transplants.set(n, node)
230 230
231 231 return n
232 232
233 233 def resume(self, repo, source, opts=None):
234 234 '''recover last transaction and apply remaining changesets'''
235 235 if os.path.exists(os.path.join(self.path, 'journal')):
236 236 n, node = self.recover(repo)
237 237 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
238 238 revlog.short(n)))
239 239 seriespath = os.path.join(self.path, 'series')
240 240 if not os.path.exists(seriespath):
241 self.transplants.write()
241 242 return
242 243 nodes, merges = self.readseries()
243 244 revmap = {}
244 245 for n in nodes:
245 246 revmap[source.changelog.rev(n)] = n
246 247 os.unlink(seriespath)
247 248
248 249 self.apply(repo, source, revmap, merges, opts)
249 250
250 251 def recover(self, repo):
251 252 '''commit working directory using journal metadata'''
252 253 node, user, date, message, parents = self.readlog()
253 254 merge = len(parents) == 2
254 255
255 256 if not user or not date or not message or not parents[0]:
256 257 raise util.Abort(_('transplant log file is corrupt'))
257 258
259 extra = {'transplant_source': node}
258 260 wlock = repo.wlock()
259 261 p1, p2 = repo.dirstate.parents()
260 262 if p1 != parents[0]:
261 263 raise util.Abort(_('working dir not at transplant parent %s') %
262 264 revlog.hex(parents[0]))
263 265 if merge:
264 266 repo.dirstate.setparents(p1, parents[1])
265 n = repo.commit(None, message, user, date, wlock=wlock)
267 n = repo.commit(None, message, user, date, wlock=wlock, extra=extra)
266 268 if not n:
267 269 raise util.Abort(_('commit failed'))
268 270 if not merge:
269 271 self.transplants.set(n, node)
270 272 self.unlog()
271 273
272 274 return n, node
273 275
274 276 def readseries(self):
275 277 nodes = []
276 278 merges = []
277 279 cur = nodes
278 280 for line in self.opener('series').read().splitlines():
279 281 if line.startswith('# Merges'):
280 282 cur = merges
281 283 continue
282 284 cur.append(revlog.bin(line))
283 285
284 286 return (nodes, merges)
285 287
286 288 def saveseries(self, revmap, merges):
287 289 if not revmap:
288 290 return
289 291
290 292 if not os.path.isdir(self.path):
291 293 os.mkdir(self.path)
292 294 series = self.opener('series', 'w')
293 295 revs = revmap.keys()
294 296 revs.sort()
295 297 for rev in revs:
296 298 series.write(revlog.hex(revmap[rev]) + '\n')
297 299 if merges:
298 300 series.write('# Merges\n')
299 301 for m in merges:
300 302 series.write(revlog.hex(m) + '\n')
301 303 series.close()
302 304
303 305 def log(self, user, date, message, p1, p2, merge=False):
304 306 '''journal changelog metadata for later recover'''
305 307
306 308 if not os.path.isdir(self.path):
307 309 os.mkdir(self.path)
308 310 fp = self.opener('journal', 'w')
309 311 fp.write('# User %s\n' % user)
310 312 fp.write('# Date %s\n' % date)
311 313 fp.write('# Node ID %s\n' % revlog.hex(p2))
312 314 fp.write('# Parent ' + revlog.hex(p1) + '\n')
313 315 if merge:
314 316 fp.write('# Parent ' + revlog.hex(p2) + '\n')
315 317 fp.write(message.rstrip() + '\n')
316 318 fp.close()
317 319
318 320 def readlog(self):
319 321 parents = []
320 322 message = []
321 323 for line in self.opener('journal').read().splitlines():
322 324 if line.startswith('# User '):
323 325 user = line[7:]
324 326 elif line.startswith('# Date '):
325 327 date = line[7:]
326 328 elif line.startswith('# Node ID '):
327 329 node = revlog.bin(line[10:])
328 330 elif line.startswith('# Parent '):
329 331 parents.append(revlog.bin(line[9:]))
330 332 else:
331 333 message.append(line)
332 334 return (node, user, date, '\n'.join(message), parents)
333 335
334 336 def unlog(self):
335 337 '''remove changelog journal'''
336 338 absdst = os.path.join(self.path, 'journal')
337 339 if os.path.exists(absdst):
338 340 os.unlink(absdst)
339 341
340 342 def transplantfilter(self, repo, source, root):
341 343 def matchfn(node):
342 344 if self.applied(repo, node, root):
343 345 return False
344 346 if source.changelog.parents(node)[1] != revlog.nullid:
345 347 return False
346 348 extra = source.changelog.read(node)[5]
347 349 cnode = extra.get('transplant_source')
348 350 if cnode and self.applied(repo, cnode, root):
349 351 return False
350 352 return True
351 353
352 354 return matchfn
353 355
354 356 def hasnode(repo, node):
355 357 try:
356 358 return repo.changelog.rev(node) != None
357 359 except revlog.RevlogError:
358 360 return False
359 361
360 362 def browserevs(ui, repo, nodes, opts):
361 363 '''interactively transplant changesets'''
362 364 def browsehelp(ui):
363 365 ui.write('y: transplant this changeset\n'
364 366 'n: skip this changeset\n'
365 367 'm: merge at this changeset\n'
366 368 'p: show patch\n'
367 369 'c: commit selected changesets\n'
368 370 'q: cancel transplant\n'
369 371 '?: show this help\n')
370 372
371 373 displayer = cmdutil.show_changeset(ui, repo, opts)
372 374 transplants = []
373 375 merges = []
374 376 for node in nodes:
375 377 displayer.show(changenode=node)
376 378 action = None
377 379 while not action:
378 380 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
379 381 if action == '?':
380 382 browsehelp(ui)
381 383 action = None
382 384 elif action == 'p':
383 385 parent = repo.changelog.parents(node)[0]
384 386 patch.diff(repo, parent, node)
385 387 action = None
386 388 elif action not in ('y', 'n', 'm', 'c', 'q'):
387 389 ui.write('no such option\n')
388 390 action = None
389 391 if action == 'y':
390 392 transplants.append(node)
391 393 elif action == 'm':
392 394 merges.append(node)
393 395 elif action == 'c':
394 396 break
395 397 elif action == 'q':
396 398 transplants = ()
397 399 merges = ()
398 400 break
399 401 return (transplants, merges)
400 402
401 403 def transplant(ui, repo, *revs, **opts):
402 404 '''transplant changesets from another branch
403 405
404 406 Selected changesets will be applied on top of the current working
405 407 directory with the log of the original changeset. If --log is
406 408 specified, log messages will have a comment appended of the form:
407 409
408 410 (transplanted from CHANGESETHASH)
409 411
410 412 You can rewrite the changelog message with the --filter option.
411 413 Its argument will be invoked with the current changelog message
412 414 as $1 and the patch as $2.
413 415
414 416 If --source is specified, selects changesets from the named
415 417 repository. If --branch is specified, selects changesets from the
416 418 branch holding the named revision, up to that revision. If --all
417 419 is specified, all changesets on the branch will be transplanted,
418 420 otherwise you will be prompted to select the changesets you want.
419 421
420 422 hg transplant --branch REVISION --all will rebase the selected branch
421 423 (up to the named revision) onto your current working directory.
422 424
423 425 You can optionally mark selected transplanted changesets as
424 426 merge changesets. You will not be prompted to transplant any
425 427 ancestors of a merged transplant, and you can merge descendants
426 428 of them normally instead of transplanting them.
427 429
428 430 If no merges or revisions are provided, hg transplant will start
429 431 an interactive changeset browser.
430 432
431 433 If a changeset application fails, you can fix the merge by hand and
432 434 then resume where you left off by calling hg transplant --continue.
433 435 '''
434 436 def getoneitem(opts, item, errmsg):
435 437 val = opts.get(item)
436 438 if val:
437 439 if len(val) > 1:
438 440 raise util.Abort(errmsg)
439 441 else:
440 442 return val[0]
441 443
442 444 def getremotechanges(repo, url):
443 445 sourcerepo = ui.expandpath(url)
444 446 source = hg.repository(ui, sourcerepo)
445 447 incoming = repo.findincoming(source, force=True)
446 448 if not incoming:
447 449 return (source, None, None)
448 450
449 451 bundle = None
450 452 if not source.local():
451 453 cg = source.changegroup(incoming, 'incoming')
452 454 bundle = commands.write_bundle(cg, compress=False)
453 455 source = bundlerepo.bundlerepository(ui, repo.root, bundle)
454 456
455 457 return (source, incoming, bundle)
456 458
457 459 def incwalk(repo, incoming, branches, match=util.always):
458 460 if not branches:
459 461 branches=None
460 462 for node in repo.changelog.nodesbetween(incoming, branches)[0]:
461 463 if match(node):
462 464 yield node
463 465
464 466 def transplantwalk(repo, root, branches, match=util.always):
465 467 if not branches:
466 468 branches = repo.heads()
467 469 ancestors = []
468 470 for branch in branches:
469 471 ancestors.append(repo.changelog.ancestor(root, branch))
470 472 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
471 473 if match(node):
472 474 yield node
473 475
474 476 def checkopts(opts, revs):
475 477 if opts.get('continue'):
476 478 if filter(lambda opt: opts.get(opt), ('branch', 'all', 'merge')):
477 479 raise util.Abort(_('--continue is incompatible with branch, all or merge'))
478 480 return
479 481 if not (opts.get('source') or revs or
480 482 opts.get('merge') or opts.get('branch')):
481 483 raise util.Abort(_('no source URL, branch tag or revision list provided'))
482 484 if opts.get('all'):
483 485 if not opts.get('branch'):
484 486 raise util.Abort(_('--all requires a branch revision'))
485 487 if revs:
486 488 raise util.Abort(_('--all is incompatible with a revision list'))
487 489
488 490 checkopts(opts, revs)
489 491
490 492 if not opts.get('log'):
491 493 opts['log'] = ui.config('transplant', 'log')
492 494 if not opts.get('filter'):
493 495 opts['filter'] = ui.config('transplant', 'filter')
494 496
495 497 tp = transplanter(ui, repo)
496 498
497 499 p1, p2 = repo.dirstate.parents()
498 500 if p1 == revlog.nullid:
499 501 raise util.Abort(_('no revision checked out'))
500 502 if not opts.get('continue'):
501 503 if p2 != revlog.nullid:
502 504 raise util.Abort(_('outstanding uncommitted merges'))
503 505 m, a, r, d = repo.status()[:4]
504 506 if m or a or r or d:
505 507 raise util.Abort(_('outstanding local changes'))
506 508
507 509 bundle = None
508 510 source = opts.get('source')
509 511 if source:
510 512 (source, incoming, bundle) = getremotechanges(repo, source)
511 513 else:
512 514 source = repo
513 515
514 516 try:
515 517 if opts.get('continue'):
516 518 tp.resume(repo, source, opts)
517 519 return
518 520
519 521 tf=tp.transplantfilter(repo, source, p1)
520 522 if opts.get('prune'):
521 523 prune = [source.lookup(r)
522 524 for r in cmdutil.revrange(source, opts.get('prune'))]
523 525 matchfn = lambda x: tf(x) and x not in prune
524 526 else:
525 527 matchfn = tf
526 528 branches = map(source.lookup, opts.get('branch', ()))
527 529 merges = map(source.lookup, opts.get('merge', ()))
528 530 revmap = {}
529 531 if revs:
530 532 for r in cmdutil.revrange(source, revs):
531 533 revmap[int(r)] = source.lookup(r)
532 534 elif opts.get('all') or not merges:
533 535 if source != repo:
534 536 alltransplants = incwalk(source, incoming, branches, match=matchfn)
535 537 else:
536 538 alltransplants = transplantwalk(source, p1, branches, match=matchfn)
537 539 if opts.get('all'):
538 540 revs = alltransplants
539 541 else:
540 542 revs, newmerges = browserevs(ui, source, alltransplants, opts)
541 543 merges.extend(newmerges)
542 544 for r in revs:
543 545 revmap[source.changelog.rev(r)] = r
544 546 for r in merges:
545 547 revmap[source.changelog.rev(r)] = r
546 548
547 549 revs = revmap.keys()
548 550 revs.sort()
549 551 pulls = []
550 552
551 553 tp.apply(repo, source, revmap, merges, opts)
552 554 finally:
553 555 if bundle:
554 556 os.unlink(bundle)
555 557
556 558 cmdtable = {
557 559 "transplant":
558 560 (transplant,
559 561 [('s', 'source', '', _('pull patches from REPOSITORY')),
560 562 ('b', 'branch', [], _('pull patches from branch BRANCH')),
561 563 ('a', 'all', None, _('pull all changesets up to BRANCH')),
562 564 ('p', 'prune', [], _('skip over REV')),
563 565 ('m', 'merge', [], _('merge at REV')),
564 566 ('', 'log', None, _('append transplant info to log message')),
565 567 ('c', 'continue', None, _('continue last transplant session after repair')),
566 568 ('', 'filter', '', _('filter changesets through FILTER'))],
567 569 _('hg transplant [-s REPOSITORY] [-b BRANCH] [-p REV] [-m REV] [-n] REV...'))
568 570 }
@@ -1,90 +1,93
1 1 #!/bin/sh
2 2
3 3 cat <<EOF >> $HGRCPATH
4 4 [extensions]
5 5 transplant=
6 6 EOF
7 7
8 8 hg init t
9 9 cd t
10 10 echo r1 > r1
11 11 hg ci -Amr1 -d'0 0'
12 12 echo r2 > r2
13 13 hg ci -Amr2 -d'1 0'
14 14 hg up 0
15 15
16 16 echo b1 > b1
17 17 hg ci -Amb1 -d '0 0'
18 18 echo b2 > b2
19 19 hg ci -Amb2 -d '1 0'
20 20 echo b3 > b3
21 21 hg ci -Amb3 -d '2 0'
22 22
23 23 hg log --template '{rev} {parents} {desc}\n'
24 24
25 25 cd ..
26 26 hg clone t rebase
27 27 cd rebase
28 28
29 29 hg up -C 1
30 30 echo '% rebase b onto r1'
31 31 hg transplant -a -b tip
32 32 hg log --template '{rev} {parents} {desc}\n'
33 33
34 34 cd ..
35 35 hg clone t prune
36 36 cd prune
37 37
38 38 hg up -C 1
39 39 echo '% rebase b onto r1, skipping b2'
40 40 hg transplant -a -b tip -p 3
41 41 hg log --template '{rev} {parents} {desc}\n'
42 42
43 43 cd ..
44 44 echo '% remote transplant'
45 45 hg clone -r 1 t remote
46 46 cd remote
47 47 hg transplant --log -s ../t 2 4
48 48 hg log --template '{rev} {parents} {desc}\n'
49 49
50 50 echo '% skip previous transplants'
51 51 hg transplant -s ../t -a -b 4
52 52 hg log --template '{rev} {parents} {desc}\n'
53 53
54 54 echo '% skip local changes transplanted to the source'
55 55 echo b4 > b4
56 56 hg ci -Amb4 -d '3 0'
57 57 cd ..
58 58 hg clone t pullback
59 59 cd pullback
60 60 hg transplant -s ../remote -a -b tip
61 61
62 62 echo '% transplant --continue'
63 63 hg init ../tc
64 64 cd ../tc
65 65 cat <<EOF > foo
66 66 foo
67 67 bar
68 68 baz
69 69 EOF
70 70 echo toremove > toremove
71 71 hg ci -Amfoo -d '0 0'
72 72 cat <<EOF > foo
73 73 foo2
74 74 bar2
75 75 baz2
76 76 EOF
77 77 rm toremove
78 78 echo added > added
79 79 hg ci -Amfoo2 -d '0 0'
80 80 echo bar > bar
81 81 hg ci -Ambar -d '0 0'
82 82 echo bar2 >> bar
83 83 hg ci -mbar2 -d '0 0'
84 84 hg up 0
85 85 echo foobar > foo
86 86 hg ci -mfoobar -d '0 0'
87 87 hg transplant 1:3
88 echo merge > foo
88 # transplant -c shouldn't use an old changeset
89 hg up -C
90 hg transplant 1
89 91 hg transplant --continue
92 hg transplant 1:3
90 93 hg locate
@@ -1,98 +1,106
1 1 adding r1
2 2 adding r2
3 3 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
4 4 adding b1
5 5 adding b2
6 6 adding b3
7 7 4 b3
8 8 3 b2
9 9 2 0:17ab29e464c6 b1
10 10 1 r2
11 11 0 r1
12 12 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
13 13 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
14 14 % rebase b onto r1
15 15 applying 37a1297eb21b
16 16 37a1297eb21b transplanted to e234d668f844
17 17 applying 722f4667af76
18 18 722f4667af76 transplanted to 539f377d78df
19 19 applying a53251cdf717
20 20 a53251cdf717 transplanted to ffd6818a3975
21 21 7 b3
22 22 6 b2
23 23 5 1:d11e3596cc1a b1
24 24 4 b3
25 25 3 b2
26 26 2 0:17ab29e464c6 b1
27 27 1 r2
28 28 0 r1
29 29 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
30 30 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
31 31 % rebase b onto r1, skipping b2
32 32 applying 37a1297eb21b
33 33 37a1297eb21b transplanted to e234d668f844
34 34 applying a53251cdf717
35 35 a53251cdf717 transplanted to 7275fda4d04f
36 36 6 b3
37 37 5 1:d11e3596cc1a b1
38 38 4 b3
39 39 3 b2
40 40 2 0:17ab29e464c6 b1
41 41 1 r2
42 42 0 r1
43 43 % remote transplant
44 44 requesting all changes
45 45 adding changesets
46 46 adding manifests
47 47 adding file changes
48 48 added 2 changesets with 2 changes to 2 files
49 49 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
50 50 searching for changes
51 51 applying 37a1297eb21b
52 52 37a1297eb21b transplanted to c19cf0ccb069
53 53 applying a53251cdf717
54 54 a53251cdf717 transplanted to f7fe5bf98525
55 55 3 b3
56 56 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
57 57 2 b1
58 58 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
59 59 1 r2
60 60 0 r1
61 61 % skip previous transplants
62 62 searching for changes
63 63 applying 722f4667af76
64 64 722f4667af76 transplanted to 47156cd86c0b
65 65 4 b2
66 66 3 b3
67 67 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
68 68 2 b1
69 69 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
70 70 1 r2
71 71 0 r1
72 72 % skip local changes transplanted to the source
73 73 adding b4
74 74 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
75 75 searching for changes
76 76 applying 4333daefcb15
77 77 4333daefcb15 transplanted to 5f42c04e07cc
78 78 % transplant --continue
79 79 adding foo
80 80 adding toremove
81 81 adding added
82 82 removing toremove
83 83 adding bar
84 84 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
85 85 applying a1e30dd1b8e7
86 86 foo
87 87 Hunk #1 FAILED at 1.
88 88 1 out of 1 hunk FAILED -- saving rejects to file foo.rej
89 89 patch command failed: exited with status 1
90 90 abort: Fix up the merge and run hg transplant --continue
91 a1e30dd1b8e7 transplanted as e6d0b5145568
91 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
92 applying a1e30dd1b8e7
93 foo
94 Hunk #1 FAILED at 1.
95 1 out of 1 hunk FAILED -- saving rejects to file foo.rej
96 patch command failed: exited with status 1
97 abort: Fix up the merge and run hg transplant --continue
98 a1e30dd1b8e7 transplanted as f1563cf27039
99 skipping already applied revision 1:a1e30dd1b8e7
92 100 applying 1739ac5f6139
93 1739ac5f6139 transplanted to 48f780141a79
101 1739ac5f6139 transplanted to d649c221319f
94 102 applying 0282d5fbbe02
95 0282d5fbbe02 transplanted to 821d17b1a3ed
103 0282d5fbbe02 transplanted to 77418277ccb3
96 104 added
97 105 bar
98 106 foo
General Comments 0
You need to be logged in to leave comments. Login now