##// END OF EJS Templates
transplant: fix crash if filter script munges log file...
Luke Plant -
r13789:7e503118 default
parent child Browse files
Show More
@@ -1,643 +1,647 b''
1 1 # Patch transplanting extension for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''command to transplant changesets from another branch
9 9
10 10 This extension allows you to transplant patches from another branch.
11 11
12 12 Transplanted patches are recorded in .hg/transplant/transplants, as a
13 13 map from a changeset hash to its hash in the source repository.
14 14 '''
15 15
16 16 from mercurial.i18n import _
17 17 import os, tempfile
18 18 from mercurial import bundlerepo, cmdutil, hg, merge, match
19 19 from mercurial import patch, revlog, util, error
20 20 from mercurial import revset, templatekw
21 21
22 22 class transplantentry(object):
23 23 def __init__(self, lnode, rnode):
24 24 self.lnode = lnode
25 25 self.rnode = rnode
26 26
27 27 class transplants(object):
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 list = self.transplants.setdefault(rnode, [])
45 45 list.append(transplantentry(lnode, rnode))
46 46
47 47 def write(self):
48 48 if self.dirty and self.transplantfile:
49 49 if not os.path.isdir(self.path):
50 50 os.mkdir(self.path)
51 51 fp = self.opener(self.transplantfile, 'w')
52 52 for list in self.transplants.itervalues():
53 53 for t in list:
54 54 l, r = map(revlog.hex, (t.lnode, t.rnode))
55 55 fp.write(l + ':' + r + '\n')
56 56 fp.close()
57 57 self.dirty = False
58 58
59 59 def get(self, rnode):
60 60 return self.transplants.get(rnode) or []
61 61
62 62 def set(self, lnode, rnode):
63 63 list = self.transplants.setdefault(rnode, [])
64 64 list.append(transplantentry(lnode, rnode))
65 65 self.dirty = True
66 66
67 67 def remove(self, transplant):
68 68 list = self.transplants.get(transplant.rnode)
69 69 if list:
70 70 del list[list.index(transplant)]
71 71 self.dirty = True
72 72
73 73 class transplanter(object):
74 74 def __init__(self, ui, repo):
75 75 self.ui = ui
76 76 self.path = repo.join('transplant')
77 77 self.opener = util.opener(self.path)
78 78 self.transplants = transplants(self.path, 'transplants',
79 79 opener=self.opener)
80 80
81 81 def applied(self, repo, node, parent):
82 82 '''returns True if a node is already an ancestor of parent
83 83 or has already been transplanted'''
84 84 if hasnode(repo, node):
85 85 if node in repo.changelog.reachable(parent, stop=node):
86 86 return True
87 87 for t in self.transplants.get(node):
88 88 # it might have been stripped
89 89 if not hasnode(repo, t.lnode):
90 90 self.transplants.remove(t)
91 91 return False
92 92 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
93 93 return True
94 94 return False
95 95
96 96 def apply(self, repo, source, revmap, merges, opts={}):
97 97 '''apply the revisions in revmap one by one in revision order'''
98 98 revs = sorted(revmap)
99 99 p1, p2 = repo.dirstate.parents()
100 100 pulls = []
101 101 diffopts = patch.diffopts(self.ui, opts)
102 102 diffopts.git = True
103 103
104 104 lock = wlock = None
105 105 try:
106 106 wlock = repo.wlock()
107 107 lock = repo.lock()
108 108 for rev in revs:
109 109 node = revmap[rev]
110 110 revstr = '%s:%s' % (rev, revlog.short(node))
111 111
112 112 if self.applied(repo, node, p1):
113 113 self.ui.warn(_('skipping already applied revision %s\n') %
114 114 revstr)
115 115 continue
116 116
117 117 parents = source.changelog.parents(node)
118 118 if not opts.get('filter'):
119 119 # If the changeset parent is the same as the
120 120 # wdir's parent, just pull it.
121 121 if parents[0] == p1:
122 122 pulls.append(node)
123 123 p1 = node
124 124 continue
125 125 if pulls:
126 126 if source != repo:
127 127 repo.pull(source, heads=pulls)
128 128 merge.update(repo, pulls[-1], False, False, None)
129 129 p1, p2 = repo.dirstate.parents()
130 130 pulls = []
131 131
132 132 domerge = False
133 133 if node in merges:
134 134 # pulling all the merge revs at once would mean we
135 135 # couldn't transplant after the latest even if
136 136 # transplants before them fail.
137 137 domerge = True
138 138 if not hasnode(repo, node):
139 139 repo.pull(source, heads=[node])
140 140
141 141 if parents[1] != revlog.nullid:
142 142 self.ui.note(_('skipping merge changeset %s:%s\n')
143 143 % (rev, revlog.short(node)))
144 144 patchfile = None
145 145 else:
146 146 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
147 147 fp = os.fdopen(fd, 'w')
148 148 gen = patch.diff(source, parents[0], node, opts=diffopts)
149 149 for chunk in gen:
150 150 fp.write(chunk)
151 151 fp.close()
152 152
153 153 del revmap[rev]
154 154 if patchfile or domerge:
155 155 try:
156 156 n = self.applyone(repo, node,
157 157 source.changelog.read(node),
158 158 patchfile, merge=domerge,
159 159 log=opts.get('log'),
160 160 filter=opts.get('filter'))
161 161 if n and domerge:
162 162 self.ui.status(_('%s merged at %s\n') % (revstr,
163 163 revlog.short(n)))
164 164 elif n:
165 165 self.ui.status(_('%s transplanted to %s\n')
166 166 % (revlog.short(node),
167 167 revlog.short(n)))
168 168 finally:
169 169 if patchfile:
170 170 os.unlink(patchfile)
171 171 if pulls:
172 172 repo.pull(source, heads=pulls)
173 173 merge.update(repo, pulls[-1], False, False, None)
174 174 finally:
175 175 self.saveseries(revmap, merges)
176 176 self.transplants.write()
177 177 lock.release()
178 178 wlock.release()
179 179
180 180 def filter(self, filter, node, changelog, patchfile):
181 181 '''arbitrarily rewrite changeset before applying it'''
182 182
183 183 self.ui.status(_('filtering %s\n') % patchfile)
184 184 user, date, msg = (changelog[1], changelog[2], changelog[4])
185 185 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
186 186 fp = os.fdopen(fd, 'w')
187 187 fp.write("# HG changeset patch\n")
188 188 fp.write("# User %s\n" % user)
189 189 fp.write("# Date %d %d\n" % date)
190 190 fp.write(msg + '\n')
191 191 fp.close()
192 192
193 193 try:
194 194 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
195 195 util.shellquote(patchfile)),
196 196 environ={'HGUSER': changelog[1],
197 197 'HGREVISION': revlog.hex(node),
198 198 },
199 199 onerr=util.Abort, errprefix=_('filter failed'))
200 200 user, date, msg = self.parselog(file(headerfile))[1:4]
201 201 finally:
202 202 os.unlink(headerfile)
203 203
204 204 return (user, date, msg)
205 205
206 206 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
207 207 filter=None):
208 208 '''apply the patch in patchfile to the repository as a transplant'''
209 209 (manifest, user, (time, timezone), files, message) = cl[:5]
210 210 date = "%d %d" % (time, timezone)
211 211 extra = {'transplant_source': node}
212 212 if filter:
213 213 (user, date, message) = self.filter(filter, node, cl, patchfile)
214 214
215 215 if log:
216 216 # we don't translate messages inserted into commits
217 217 message += '\n(transplanted from %s)' % revlog.hex(node)
218 218
219 219 self.ui.status(_('applying %s\n') % revlog.short(node))
220 220 self.ui.note('%s %s\n%s\n' % (user, date, message))
221 221
222 222 if not patchfile and not merge:
223 223 raise util.Abort(_('can only omit patchfile if merging'))
224 224 if patchfile:
225 225 try:
226 226 files = {}
227 227 try:
228 228 patch.patch(patchfile, self.ui, cwd=repo.root,
229 229 files=files, eolmode=None)
230 230 if not files:
231 231 self.ui.warn(_('%s: empty changeset')
232 232 % revlog.hex(node))
233 233 return None
234 234 finally:
235 235 files = cmdutil.updatedir(self.ui, repo, files)
236 236 except Exception, inst:
237 237 seriespath = os.path.join(self.path, 'series')
238 238 if os.path.exists(seriespath):
239 239 os.unlink(seriespath)
240 240 p1 = repo.dirstate.parents()[0]
241 241 p2 = node
242 242 self.log(user, date, message, p1, p2, merge=merge)
243 243 self.ui.write(str(inst) + '\n')
244 244 raise util.Abort(_('fix up the merge and run '
245 245 'hg transplant --continue'))
246 246 else:
247 247 files = None
248 248 if merge:
249 249 p1, p2 = repo.dirstate.parents()
250 250 repo.dirstate.setparents(p1, node)
251 251 m = match.always(repo.root, '')
252 252 else:
253 253 m = match.exact(repo.root, '', files)
254 254
255 255 n = repo.commit(message, user, date, extra=extra, match=m)
256 256 if not n:
257 257 # Crash here to prevent an unclear crash later, in
258 258 # transplants.write(). This can happen if patch.patch()
259 259 # does nothing but claims success or if repo.status() fails
260 260 # to report changes done by patch.patch(). These both
261 261 # appear to be bugs in other parts of Mercurial, but dying
262 262 # here, as soon as we can detect the problem, is preferable
263 263 # to silently dropping changesets on the floor.
264 264 raise RuntimeError('nothing committed after transplant')
265 265 if not merge:
266 266 self.transplants.set(n, node)
267 267
268 268 return n
269 269
270 270 def resume(self, repo, source, opts=None):
271 271 '''recover last transaction and apply remaining changesets'''
272 272 if os.path.exists(os.path.join(self.path, 'journal')):
273 273 n, node = self.recover(repo)
274 274 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
275 275 revlog.short(n)))
276 276 seriespath = os.path.join(self.path, 'series')
277 277 if not os.path.exists(seriespath):
278 278 self.transplants.write()
279 279 return
280 280 nodes, merges = self.readseries()
281 281 revmap = {}
282 282 for n in nodes:
283 283 revmap[source.changelog.rev(n)] = n
284 284 os.unlink(seriespath)
285 285
286 286 self.apply(repo, source, revmap, merges, opts)
287 287
288 288 def recover(self, repo):
289 289 '''commit working directory using journal metadata'''
290 290 node, user, date, message, parents = self.readlog()
291 291 merge = len(parents) == 2
292 292
293 293 if not user or not date or not message or not parents[0]:
294 294 raise util.Abort(_('transplant log file is corrupt'))
295 295
296 296 extra = {'transplant_source': node}
297 297 wlock = repo.wlock()
298 298 try:
299 299 p1, p2 = repo.dirstate.parents()
300 300 if p1 != parents[0]:
301 301 raise util.Abort(
302 302 _('working dir not at transplant parent %s') %
303 303 revlog.hex(parents[0]))
304 304 if merge:
305 305 repo.dirstate.setparents(p1, parents[1])
306 306 n = repo.commit(message, user, date, extra=extra)
307 307 if not n:
308 308 raise util.Abort(_('commit failed'))
309 309 if not merge:
310 310 self.transplants.set(n, node)
311 311 self.unlog()
312 312
313 313 return n, node
314 314 finally:
315 315 wlock.release()
316 316
317 317 def readseries(self):
318 318 nodes = []
319 319 merges = []
320 320 cur = nodes
321 321 for line in self.opener('series').read().splitlines():
322 322 if line.startswith('# Merges'):
323 323 cur = merges
324 324 continue
325 325 cur.append(revlog.bin(line))
326 326
327 327 return (nodes, merges)
328 328
329 329 def saveseries(self, revmap, merges):
330 330 if not revmap:
331 331 return
332 332
333 333 if not os.path.isdir(self.path):
334 334 os.mkdir(self.path)
335 335 series = self.opener('series', 'w')
336 336 for rev in sorted(revmap):
337 337 series.write(revlog.hex(revmap[rev]) + '\n')
338 338 if merges:
339 339 series.write('# Merges\n')
340 340 for m in merges:
341 341 series.write(revlog.hex(m) + '\n')
342 342 series.close()
343 343
344 344 def parselog(self, fp):
345 345 parents = []
346 346 message = []
347 347 node = revlog.nullid
348 348 inmsg = False
349 user = None
350 date = None
349 351 for line in fp.read().splitlines():
350 352 if inmsg:
351 353 message.append(line)
352 354 elif line.startswith('# User '):
353 355 user = line[7:]
354 356 elif line.startswith('# Date '):
355 357 date = line[7:]
356 358 elif line.startswith('# Node ID '):
357 359 node = revlog.bin(line[10:])
358 360 elif line.startswith('# Parent '):
359 361 parents.append(revlog.bin(line[9:]))
360 362 elif not line.startswith('# '):
361 363 inmsg = True
362 364 message.append(line)
365 if None in (user, date):
366 raise util.Abort(_("filter produced garbled log file"))
363 367 return (node, user, date, '\n'.join(message), parents)
364 368
365 369 def log(self, user, date, message, p1, p2, merge=False):
366 370 '''journal changelog metadata for later recover'''
367 371
368 372 if not os.path.isdir(self.path):
369 373 os.mkdir(self.path)
370 374 fp = self.opener('journal', 'w')
371 375 fp.write('# User %s\n' % user)
372 376 fp.write('# Date %s\n' % date)
373 377 fp.write('# Node ID %s\n' % revlog.hex(p2))
374 378 fp.write('# Parent ' + revlog.hex(p1) + '\n')
375 379 if merge:
376 380 fp.write('# Parent ' + revlog.hex(p2) + '\n')
377 381 fp.write(message.rstrip() + '\n')
378 382 fp.close()
379 383
380 384 def readlog(self):
381 385 return self.parselog(self.opener('journal'))
382 386
383 387 def unlog(self):
384 388 '''remove changelog journal'''
385 389 absdst = os.path.join(self.path, 'journal')
386 390 if os.path.exists(absdst):
387 391 os.unlink(absdst)
388 392
389 393 def transplantfilter(self, repo, source, root):
390 394 def matchfn(node):
391 395 if self.applied(repo, node, root):
392 396 return False
393 397 if source.changelog.parents(node)[1] != revlog.nullid:
394 398 return False
395 399 extra = source.changelog.read(node)[5]
396 400 cnode = extra.get('transplant_source')
397 401 if cnode and self.applied(repo, cnode, root):
398 402 return False
399 403 return True
400 404
401 405 return matchfn
402 406
403 407 def hasnode(repo, node):
404 408 try:
405 409 return repo.changelog.rev(node) is not None
406 410 except error.RevlogError:
407 411 return False
408 412
409 413 def browserevs(ui, repo, nodes, opts):
410 414 '''interactively transplant changesets'''
411 415 def browsehelp(ui):
412 416 ui.write(_('y: transplant this changeset\n'
413 417 'n: skip this changeset\n'
414 418 'm: merge at this changeset\n'
415 419 'p: show patch\n'
416 420 'c: commit selected changesets\n'
417 421 'q: cancel transplant\n'
418 422 '?: show this help\n'))
419 423
420 424 displayer = cmdutil.show_changeset(ui, repo, opts)
421 425 transplants = []
422 426 merges = []
423 427 for node in nodes:
424 428 displayer.show(repo[node])
425 429 action = None
426 430 while not action:
427 431 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
428 432 if action == '?':
429 433 browsehelp(ui)
430 434 action = None
431 435 elif action == 'p':
432 436 parent = repo.changelog.parents(node)[0]
433 437 for chunk in patch.diff(repo, parent, node):
434 438 ui.write(chunk)
435 439 action = None
436 440 elif action not in ('y', 'n', 'm', 'c', 'q'):
437 441 ui.write(_('no such option\n'))
438 442 action = None
439 443 if action == 'y':
440 444 transplants.append(node)
441 445 elif action == 'm':
442 446 merges.append(node)
443 447 elif action == 'c':
444 448 break
445 449 elif action == 'q':
446 450 transplants = ()
447 451 merges = ()
448 452 break
449 453 displayer.close()
450 454 return (transplants, merges)
451 455
452 456 def transplant(ui, repo, *revs, **opts):
453 457 '''transplant changesets from another branch
454 458
455 459 Selected changesets will be applied on top of the current working
456 460 directory with the log of the original changeset. The changesets
457 461 are copied and will thus appear twice in the history. Use the
458 462 rebase extension instead if you want to move a whole branch of
459 463 unpublished changesets.
460 464
461 465 If --log is specified, log messages will have a comment appended
462 466 of the form::
463 467
464 468 (transplanted from CHANGESETHASH)
465 469
466 470 You can rewrite the changelog message with the --filter option.
467 471 Its argument will be invoked with the current changelog message as
468 472 $1 and the patch as $2.
469 473
470 474 If --source/-s is specified, selects changesets from the named
471 475 repository. If --branch/-b is specified, selects changesets from
472 476 the branch holding the named revision, up to that revision. If
473 477 --all/-a is specified, all changesets on the branch will be
474 478 transplanted, otherwise you will be prompted to select the
475 479 changesets you want.
476 480
477 481 :hg:`transplant --branch REVISION --all` will transplant the
478 482 selected branch (up to the named revision) onto your current
479 483 working directory.
480 484
481 485 You can optionally mark selected transplanted changesets as merge
482 486 changesets. You will not be prompted to transplant any ancestors
483 487 of a merged transplant, and you can merge descendants of them
484 488 normally instead of transplanting them.
485 489
486 490 If no merges or revisions are provided, :hg:`transplant` will
487 491 start an interactive changeset browser.
488 492
489 493 If a changeset application fails, you can fix the merge by hand
490 494 and then resume where you left off by calling :hg:`transplant
491 495 --continue/-c`.
492 496 '''
493 497 def incwalk(repo, incoming, branches, match=util.always):
494 498 if not branches:
495 499 branches = None
496 500 for node in repo.changelog.nodesbetween(incoming, branches)[0]:
497 501 if match(node):
498 502 yield node
499 503
500 504 def transplantwalk(repo, root, branches, match=util.always):
501 505 if not branches:
502 506 branches = repo.heads()
503 507 ancestors = []
504 508 for branch in branches:
505 509 ancestors.append(repo.changelog.ancestor(root, branch))
506 510 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
507 511 if match(node):
508 512 yield node
509 513
510 514 def checkopts(opts, revs):
511 515 if opts.get('continue'):
512 516 if opts.get('branch') or opts.get('all') or opts.get('merge'):
513 517 raise util.Abort(_('--continue is incompatible with '
514 518 'branch, all or merge'))
515 519 return
516 520 if not (opts.get('source') or revs or
517 521 opts.get('merge') or opts.get('branch')):
518 522 raise util.Abort(_('no source URL, branch tag or revision '
519 523 'list provided'))
520 524 if opts.get('all'):
521 525 if not opts.get('branch'):
522 526 raise util.Abort(_('--all requires a branch revision'))
523 527 if revs:
524 528 raise util.Abort(_('--all is incompatible with a '
525 529 'revision list'))
526 530
527 531 checkopts(opts, revs)
528 532
529 533 if not opts.get('log'):
530 534 opts['log'] = ui.config('transplant', 'log')
531 535 if not opts.get('filter'):
532 536 opts['filter'] = ui.config('transplant', 'filter')
533 537
534 538 tp = transplanter(ui, repo)
535 539
536 540 p1, p2 = repo.dirstate.parents()
537 541 if len(repo) > 0 and p1 == revlog.nullid:
538 542 raise util.Abort(_('no revision checked out'))
539 543 if not opts.get('continue'):
540 544 if p2 != revlog.nullid:
541 545 raise util.Abort(_('outstanding uncommitted merges'))
542 546 m, a, r, d = repo.status()[:4]
543 547 if m or a or r or d:
544 548 raise util.Abort(_('outstanding local changes'))
545 549
546 550 bundle = None
547 551 source = opts.get('source')
548 552 if source:
549 553 sourcerepo = ui.expandpath(source)
550 554 source = hg.repository(ui, sourcerepo)
551 555 source, common, incoming, bundle = bundlerepo.getremotechanges(ui, repo,
552 556 source, force=True)
553 557 else:
554 558 source = repo
555 559
556 560 try:
557 561 if opts.get('continue'):
558 562 tp.resume(repo, source, opts)
559 563 return
560 564
561 565 tf = tp.transplantfilter(repo, source, p1)
562 566 if opts.get('prune'):
563 567 prune = [source.lookup(r)
564 568 for r in cmdutil.revrange(source, opts.get('prune'))]
565 569 matchfn = lambda x: tf(x) and x not in prune
566 570 else:
567 571 matchfn = tf
568 572 branches = map(source.lookup, opts.get('branch', ()))
569 573 merges = map(source.lookup, opts.get('merge', ()))
570 574 revmap = {}
571 575 if revs:
572 576 for r in cmdutil.revrange(source, revs):
573 577 revmap[int(r)] = source.lookup(r)
574 578 elif opts.get('all') or not merges:
575 579 if source != repo:
576 580 alltransplants = incwalk(source, incoming, branches,
577 581 match=matchfn)
578 582 else:
579 583 alltransplants = transplantwalk(source, p1, branches,
580 584 match=matchfn)
581 585 if opts.get('all'):
582 586 revs = alltransplants
583 587 else:
584 588 revs, newmerges = browserevs(ui, source, alltransplants, opts)
585 589 merges.extend(newmerges)
586 590 for r in revs:
587 591 revmap[source.changelog.rev(r)] = r
588 592 for r in merges:
589 593 revmap[source.changelog.rev(r)] = r
590 594
591 595 tp.apply(repo, source, revmap, merges, opts)
592 596 finally:
593 597 if bundle:
594 598 source.close()
595 599 os.unlink(bundle)
596 600
597 601 def revsettransplanted(repo, subset, x):
598 602 """``transplanted(set)``
599 603 Transplanted changesets in set.
600 604 """
601 605 if x:
602 606 s = revset.getset(repo, subset, x)
603 607 else:
604 608 s = subset
605 609 cs = set()
606 610 for r in xrange(0, len(repo)):
607 611 if repo[r].extra().get('transplant_source'):
608 612 cs.add(r)
609 613 return [r for r in s if r in cs]
610 614
611 615 def kwtransplanted(repo, ctx, **args):
612 616 """:transplanted: String. The node identifier of the transplanted
613 617 changeset if any."""
614 618 n = ctx.extra().get('transplant_source')
615 619 return n and revlog.hex(n) or ''
616 620
617 621 def extsetup(ui):
618 622 revset.symbols['transplanted'] = revsettransplanted
619 623 templatekw.keywords['transplanted'] = kwtransplanted
620 624
621 625 cmdtable = {
622 626 "transplant":
623 627 (transplant,
624 628 [('s', 'source', '',
625 629 _('pull patches from REPO'), _('REPO')),
626 630 ('b', 'branch', [],
627 631 _('pull patches from branch BRANCH'), _('BRANCH')),
628 632 ('a', 'all', None, _('pull all changesets up to BRANCH')),
629 633 ('p', 'prune', [],
630 634 _('skip over REV'), _('REV')),
631 635 ('m', 'merge', [],
632 636 _('merge at REV'), _('REV')),
633 637 ('', 'log', None, _('append transplant info to log message')),
634 638 ('c', 'continue', None, _('continue last transplant session '
635 639 'after repair')),
636 640 ('', 'filter', '',
637 641 _('filter changesets through command'), _('CMD'))],
638 642 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
639 643 '[-m REV] [REV]...'))
640 644 }
641 645
642 646 # tell hggettext to extract docstrings from these functions:
643 647 i18nfunctions = [revsettransplanted, kwtransplanted]
@@ -1,396 +1,409 b''
1 1 $ cat <<EOF >> $HGRCPATH
2 2 > [extensions]
3 3 > transplant=
4 4 > EOF
5 5
6 6 $ hg init t
7 7 $ cd t
8 8 $ echo r1 > r1
9 9 $ hg ci -Amr1 -d'0 0'
10 10 adding r1
11 11 $ echo r2 > r2
12 12 $ hg ci -Amr2 -d'1 0'
13 13 adding r2
14 14 $ hg up 0
15 15 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
16 16
17 17 $ echo b1 > b1
18 18 $ hg ci -Amb1 -d '0 0'
19 19 adding b1
20 20 created new head
21 21 $ echo b2 > b2
22 22 $ hg ci -Amb2 -d '1 0'
23 23 adding b2
24 24 $ echo b3 > b3
25 25 $ hg ci -Amb3 -d '2 0'
26 26 adding b3
27 27
28 28 $ hg log --template '{rev} {parents} {desc}\n'
29 29 4 b3
30 30 3 b2
31 31 2 0:17ab29e464c6 b1
32 32 1 r2
33 33 0 r1
34 34
35 35 $ hg clone . ../rebase
36 36 updating to branch default
37 37 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
38 38 $ cd ../rebase
39 39
40 40 $ hg up -C 1
41 41 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
42 42
43 43 rebase b onto r1
44 44
45 45 $ hg transplant -a -b tip
46 46 applying 37a1297eb21b
47 47 37a1297eb21b transplanted to e234d668f844
48 48 applying 722f4667af76
49 49 722f4667af76 transplanted to 539f377d78df
50 50 applying a53251cdf717
51 51 a53251cdf717 transplanted to ffd6818a3975
52 52 $ hg log --template '{rev} {parents} {desc}\n'
53 53 7 b3
54 54 6 b2
55 55 5 1:d11e3596cc1a b1
56 56 4 b3
57 57 3 b2
58 58 2 0:17ab29e464c6 b1
59 59 1 r2
60 60 0 r1
61 61
62 62 test transplanted revset
63 63
64 64 $ hg log -r 'transplanted()' --template '{rev} {parents} {desc}\n'
65 65 5 1:d11e3596cc1a b1
66 66 6 b2
67 67 7 b3
68 68 $ hg help revsets | grep transplanted
69 69 "transplanted(set)"
70 70
71 71 test tranplanted keyword
72 72
73 73 $ hg log --template '{rev} {transplanted}\n'
74 74 7 a53251cdf717679d1907b289f991534be05c997a
75 75 6 722f4667af767100cb15b6a79324bf8abbfe1ef4
76 76 5 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21
77 77 4
78 78 3
79 79 2
80 80 1
81 81 0
82 82
83 83 $ hg clone ../t ../prune
84 84 updating to branch default
85 85 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
86 86 $ cd ../prune
87 87
88 88 $ hg up -C 1
89 89 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
90 90
91 91 rebase b onto r1, skipping b2
92 92
93 93 $ hg transplant -a -b tip -p 3
94 94 applying 37a1297eb21b
95 95 37a1297eb21b transplanted to e234d668f844
96 96 applying a53251cdf717
97 97 a53251cdf717 transplanted to 7275fda4d04f
98 98 $ hg log --template '{rev} {parents} {desc}\n'
99 99 6 b3
100 100 5 1:d11e3596cc1a b1
101 101 4 b3
102 102 3 b2
103 103 2 0:17ab29e464c6 b1
104 104 1 r2
105 105 0 r1
106 106
107 107
108 108 remote transplant
109 109
110 110 $ hg clone -r 1 ../t ../remote
111 111 adding changesets
112 112 adding manifests
113 113 adding file changes
114 114 added 2 changesets with 2 changes to 2 files
115 115 updating to branch default
116 116 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 117 $ cd ../remote
118 118 $ hg transplant --log -s ../t 2 4
119 119 searching for changes
120 120 applying 37a1297eb21b
121 121 37a1297eb21b transplanted to c19cf0ccb069
122 122 applying a53251cdf717
123 123 a53251cdf717 transplanted to f7fe5bf98525
124 124 $ hg log --template '{rev} {parents} {desc}\n'
125 125 3 b3
126 126 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
127 127 2 b1
128 128 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
129 129 1 r2
130 130 0 r1
131 131
132 132 skip previous transplants
133 133
134 134 $ hg transplant -s ../t -a -b 4
135 135 searching for changes
136 136 applying 722f4667af76
137 137 722f4667af76 transplanted to 47156cd86c0b
138 138 $ hg log --template '{rev} {parents} {desc}\n'
139 139 4 b2
140 140 3 b3
141 141 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
142 142 2 b1
143 143 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
144 144 1 r2
145 145 0 r1
146 146
147 147 skip local changes transplanted to the source
148 148
149 149 $ echo b4 > b4
150 150 $ hg ci -Amb4 -d '3 0'
151 151 adding b4
152 152 $ hg clone ../t ../pullback
153 153 updating to branch default
154 154 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
155 155 $ cd ../pullback
156 156 $ hg transplant -s ../remote -a -b tip
157 157 searching for changes
158 158 applying 4333daefcb15
159 159 4333daefcb15 transplanted to 5f42c04e07cc
160 160
161 161
162 162 remote transplant with pull
163 163
164 164 $ hg -R ../t serve -p $HGPORT -d --pid-file=../t.pid
165 165 $ cat ../t.pid >> $DAEMON_PIDS
166 166
167 167 $ hg clone -r 0 ../t ../rp
168 168 adding changesets
169 169 adding manifests
170 170 adding file changes
171 171 added 1 changesets with 1 changes to 1 files
172 172 updating to branch default
173 173 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
174 174 $ cd ../rp
175 175 $ hg transplant -s http://localhost:$HGPORT/ 2 4
176 176 searching for changes
177 177 searching for changes
178 178 adding changesets
179 179 adding manifests
180 180 adding file changes
181 181 added 1 changesets with 1 changes to 1 files
182 182 applying a53251cdf717
183 183 a53251cdf717 transplanted to 8d9279348abb
184 184 $ hg log --template '{rev} {parents} {desc}\n'
185 185 2 b3
186 186 1 b1
187 187 0 r1
188 188
189 189 transplant --continue
190 190
191 191 $ hg init ../tc
192 192 $ cd ../tc
193 193 $ cat <<EOF > foo
194 194 > foo
195 195 > bar
196 196 > baz
197 197 > EOF
198 198 $ echo toremove > toremove
199 199 $ hg ci -Amfoo
200 200 adding foo
201 201 adding toremove
202 202 $ cat <<EOF > foo
203 203 > foo2
204 204 > bar2
205 205 > baz2
206 206 > EOF
207 207 $ rm toremove
208 208 $ echo added > added
209 209 $ hg ci -Amfoo2
210 210 adding added
211 211 removing toremove
212 212 $ echo bar > bar
213 213 $ hg ci -Ambar
214 214 adding bar
215 215 $ echo bar2 >> bar
216 216 $ hg ci -mbar2
217 217 $ hg up 0
218 218 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
219 219 $ echo foobar > foo
220 220 $ hg ci -mfoobar
221 221 created new head
222 222 $ hg transplant 1:3
223 223 applying a1e30dd1b8e7
224 224 patching file foo
225 225 Hunk #1 FAILED at 0
226 226 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
227 227 patch failed to apply
228 228 abort: fix up the merge and run hg transplant --continue
229 229 [255]
230 230
231 231 transplant -c shouldn't use an old changeset
232 232
233 233 $ hg up -C
234 234 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
235 235 $ rm added
236 236 $ hg transplant 1
237 237 applying a1e30dd1b8e7
238 238 patching file foo
239 239 Hunk #1 FAILED at 0
240 240 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
241 241 patch failed to apply
242 242 abort: fix up the merge and run hg transplant --continue
243 243 [255]
244 244 $ hg transplant --continue
245 245 a1e30dd1b8e7 transplanted as f1563cf27039
246 246 $ hg transplant 1:3
247 247 skipping already applied revision 1:a1e30dd1b8e7
248 248 applying 1739ac5f6139
249 249 1739ac5f6139 transplanted to d649c221319f
250 250 applying 0282d5fbbe02
251 251 0282d5fbbe02 transplanted to 77418277ccb3
252 252 $ hg locate
253 253 added
254 254 bar
255 255 foo
256 256 $ cd ..
257 257
258 258 Issue1111: Test transplant --merge
259 259
260 260 $ hg init t1111
261 261 $ cd t1111
262 262 $ echo a > a
263 263 $ hg ci -Am adda
264 264 adding a
265 265 $ echo b >> a
266 266 $ hg ci -m appendb
267 267 $ echo c >> a
268 268 $ hg ci -m appendc
269 269 $ hg up -C 0
270 270 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
271 271 $ echo d >> a
272 272 $ hg ci -m appendd
273 273 created new head
274 274
275 275 tranplant
276 276
277 277 $ hg transplant -m 1
278 278 applying 42dc4432fd35
279 279 1:42dc4432fd35 merged at a9f4acbac129
280 280 $ cd ..
281 281
282 282 test transplant into empty repository
283 283
284 284 $ hg init empty
285 285 $ cd empty
286 286 $ hg transplant -s ../t -b tip -a
287 287 adding changesets
288 288 adding manifests
289 289 adding file changes
290 290 added 4 changesets with 4 changes to 4 files
291 291 $ cd ..
292 292
293 293
294 294 test filter
295 295
296 296 $ hg init filter
297 297 $ cd filter
298 298 $ cat <<'EOF' >test-filter
299 299 > #!/bin/sh
300 300 > sed 's/r1/r2/' $1 > $1.new
301 301 > mv $1.new $1
302 302 > EOF
303 303 $ chmod +x test-filter
304 304 $ hg transplant -s ../t -b tip -a --filter ./test-filter
305 305 filtering * (glob)
306 306 applying 17ab29e464c6
307 307 17ab29e464c6 transplanted to e9ffc54ea104
308 308 filtering * (glob)
309 309 applying 37a1297eb21b
310 310 37a1297eb21b transplanted to 348b36d0b6a5
311 311 filtering * (glob)
312 312 applying 722f4667af76
313 313 722f4667af76 transplanted to 0aa6979afb95
314 314 filtering * (glob)
315 315 applying a53251cdf717
316 316 a53251cdf717 transplanted to 14f8512272b5
317 317 $ hg log --template '{rev} {parents} {desc}\n'
318 318 3 b3
319 319 2 b2
320 320 1 b1
321 321 0 r2
322 322 $ cd ..
323 323
324 324
325 325 test filter with failed patch
326 326
327 327 $ cd filter
328 328 $ hg up 0
329 329 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
330 330 $ echo foo > b1
331 331 $ hg ci -Am foo
332 332 adding b1
333 333 adding test-filter
334 334 created new head
335 335 $ hg transplant 1 --filter ./test-filter
336 336 filtering * (glob)
337 337 applying 348b36d0b6a5
338 338 file b1 already exists
339 339 1 out of 1 hunks FAILED -- saving rejects to file b1.rej
340 340 patch failed to apply
341 341 abort: fix up the merge and run hg transplant --continue
342 342 [255]
343 343 $ cd ..
344 344
345 345 test environment passed to filter
346 346
347 347 $ hg init filter-environment
348 348 $ cd filter-environment
349 349 $ cat <<'EOF' >test-filter-environment
350 350 > #!/bin/sh
351 351 > echo "Transplant by $HGUSER" >> $1
352 352 > echo "Transplant from rev $HGREVISION" >> $1
353 353 > EOF
354 354 $ chmod +x test-filter-environment
355 355 $ hg transplant -s ../t --filter ./test-filter-environment 0
356 356 filtering * (glob)
357 357 applying 17ab29e464c6
358 358 17ab29e464c6 transplanted to 5190e68026a0
359 359
360 360 $ hg log --template '{rev} {parents} {desc}\n'
361 361 0 r1
362 362 Transplant by test
363 363 Transplant from rev 17ab29e464c6ca53e329470efe2a9918ac617a6f
364 364 $ cd ..
365 365
366 test transplant with filter handles invalid changelog
367
368 $ hg init filter-invalid-log
369 $ cd filter-invalid-log
370 $ cat <<'EOF' >test-filter-invalid-log
371 > #!/bin/sh
372 > echo "" > $1
373 > EOF
374 $ chmod +x test-filter-invalid-log
375 $ hg transplant -s ../t --filter ./test-filter-invalid-log 0
376 filtering * (glob)
377 abort: filter failed
378 [255]
366 379
367 380 test with a win32ext like setup (differing EOLs)
368 381
369 382 $ hg init twin1
370 383 $ cd twin1
371 384 $ echo a > a
372 385 $ echo b > b
373 386 $ echo b >> b
374 387 $ hg ci -Am t
375 388 adding a
376 389 adding b
377 390 $ echo a > b
378 391 $ echo b >> b
379 392 $ hg ci -m changeb
380 393 $ cd ..
381 394
382 395 $ hg init twin2
383 396 $ cd twin2
384 397 $ echo '[patch]' >> .hg/hgrc
385 398 $ echo 'eol = crlf' >> .hg/hgrc
386 399 $ python -c "file('b', 'wb').write('b\r\nb\r\n')"
387 400 $ hg ci -m addb
388 401 nothing changed
389 402 [1]
390 403 $ hg transplant -s ../twin1 tip
391 404 applying 2e849d776c17
392 405 2e849d776c17 transplanted to 589cea8ba85b
393 406 $ cat b
394 407 a\r (esc)
395 408 b\r (esc)
396 409 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now