##// END OF EJS Templates
checkunfinished: accommodate histedit quirk...
Matt Mackall -
r19496:607191a4 stable
parent child Browse files
Show More
@@ -1,878 +1,878 b''
1 1 # histedit.py - interactive history editing for mercurial
2 2 #
3 3 # Copyright 2009 Augie Fackler <raf@durin42.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 """interactive history editing
8 8
9 9 With this extension installed, Mercurial gains one new command: histedit. Usage
10 10 is as follows, assuming the following history::
11 11
12 12 @ 3[tip] 7c2fd3b9020c 2009-04-27 18:04 -0500 durin42
13 13 | Add delta
14 14 |
15 15 o 2 030b686bedc4 2009-04-27 18:04 -0500 durin42
16 16 | Add gamma
17 17 |
18 18 o 1 c561b4e977df 2009-04-27 18:04 -0500 durin42
19 19 | Add beta
20 20 |
21 21 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
22 22 Add alpha
23 23
24 24 If you were to run ``hg histedit c561b4e977df``, you would see the following
25 25 file open in your editor::
26 26
27 27 pick c561b4e977df Add beta
28 28 pick 030b686bedc4 Add gamma
29 29 pick 7c2fd3b9020c Add delta
30 30
31 31 # Edit history between c561b4e977df and 7c2fd3b9020c
32 32 #
33 33 # Commands:
34 34 # p, pick = use commit
35 35 # e, edit = use commit, but stop for amending
36 36 # f, fold = use commit, but fold into previous commit (combines N and N-1)
37 37 # d, drop = remove commit from history
38 38 # m, mess = edit message without changing commit content
39 39 #
40 40
41 41 In this file, lines beginning with ``#`` are ignored. You must specify a rule
42 42 for each revision in your history. For example, if you had meant to add gamma
43 43 before beta, and then wanted to add delta in the same revision as beta, you
44 44 would reorganize the file to look like this::
45 45
46 46 pick 030b686bedc4 Add gamma
47 47 pick c561b4e977df Add beta
48 48 fold 7c2fd3b9020c Add delta
49 49
50 50 # Edit history between c561b4e977df and 7c2fd3b9020c
51 51 #
52 52 # Commands:
53 53 # p, pick = use commit
54 54 # e, edit = use commit, but stop for amending
55 55 # f, fold = use commit, but fold into previous commit (combines N and N-1)
56 56 # d, drop = remove commit from history
57 57 # m, mess = edit message without changing commit content
58 58 #
59 59
60 60 At which point you close the editor and ``histedit`` starts working. When you
61 61 specify a ``fold`` operation, ``histedit`` will open an editor when it folds
62 62 those revisions together, offering you a chance to clean up the commit message::
63 63
64 64 Add beta
65 65 ***
66 66 Add delta
67 67
68 68 Edit the commit message to your liking, then close the editor. For
69 69 this example, let's assume that the commit message was changed to
70 70 ``Add beta and delta.`` After histedit has run and had a chance to
71 71 remove any old or temporary revisions it needed, the history looks
72 72 like this::
73 73
74 74 @ 2[tip] 989b4d060121 2009-04-27 18:04 -0500 durin42
75 75 | Add beta and delta.
76 76 |
77 77 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
78 78 | Add gamma
79 79 |
80 80 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
81 81 Add alpha
82 82
83 83 Note that ``histedit`` does *not* remove any revisions (even its own temporary
84 84 ones) until after it has completed all the editing operations, so it will
85 85 probably perform several strip operations when it's done. For the above example,
86 86 it had to run strip twice. Strip can be slow depending on a variety of factors,
87 87 so you might need to be a little patient. You can choose to keep the original
88 88 revisions by passing the ``--keep`` flag.
89 89
90 90 The ``edit`` operation will drop you back to a command prompt,
91 91 allowing you to edit files freely, or even use ``hg record`` to commit
92 92 some changes as a separate commit. When you're done, any remaining
93 93 uncommitted changes will be committed as well. When done, run ``hg
94 94 histedit --continue`` to finish this step. You'll be prompted for a
95 95 new commit message, but the default commit message will be the
96 96 original message for the ``edit`` ed revision.
97 97
98 98 The ``message`` operation will give you a chance to revise a commit
99 99 message without changing the contents. It's a shortcut for doing
100 100 ``edit`` immediately followed by `hg histedit --continue``.
101 101
102 102 If ``histedit`` encounters a conflict when moving a revision (while
103 103 handling ``pick`` or ``fold``), it'll stop in a similar manner to
104 104 ``edit`` with the difference that it won't prompt you for a commit
105 105 message when done. If you decide at this point that you don't like how
106 106 much work it will be to rearrange history, or that you made a mistake,
107 107 you can use ``hg histedit --abort`` to abandon the new changes you
108 108 have made and return to the state before you attempted to edit your
109 109 history.
110 110
111 111 If we clone the histedit-ed example repository above and add four more
112 112 changes, such that we have the following history::
113 113
114 114 @ 6[tip] 038383181893 2009-04-27 18:04 -0500 stefan
115 115 | Add theta
116 116 |
117 117 o 5 140988835471 2009-04-27 18:04 -0500 stefan
118 118 | Add eta
119 119 |
120 120 o 4 122930637314 2009-04-27 18:04 -0500 stefan
121 121 | Add zeta
122 122 |
123 123 o 3 836302820282 2009-04-27 18:04 -0500 stefan
124 124 | Add epsilon
125 125 |
126 126 o 2 989b4d060121 2009-04-27 18:04 -0500 durin42
127 127 | Add beta and delta.
128 128 |
129 129 o 1 081603921c3f 2009-04-27 18:04 -0500 durin42
130 130 | Add gamma
131 131 |
132 132 o 0 d8d2fcd0e319 2009-04-27 18:04 -0500 durin42
133 133 Add alpha
134 134
135 135 If you run ``hg histedit --outgoing`` on the clone then it is the same
136 136 as running ``hg histedit 836302820282``. If you need plan to push to a
137 137 repository that Mercurial does not detect to be related to the source
138 138 repo, you can add a ``--force`` option.
139 139 """
140 140
141 141 try:
142 142 import cPickle as pickle
143 143 pickle.dump # import now
144 144 except ImportError:
145 145 import pickle
146 146 import os
147 147 import sys
148 148
149 149 from mercurial import cmdutil
150 150 from mercurial import discovery
151 151 from mercurial import error
152 152 from mercurial import copies
153 153 from mercurial import context
154 154 from mercurial import hg
155 155 from mercurial import lock as lockmod
156 156 from mercurial import node
157 157 from mercurial import repair
158 158 from mercurial import scmutil
159 159 from mercurial import util
160 160 from mercurial import obsolete
161 161 from mercurial import merge as mergemod
162 162 from mercurial.i18n import _
163 163
164 164 cmdtable = {}
165 165 command = cmdutil.command(cmdtable)
166 166
167 167 testedwith = 'internal'
168 168
169 169 # i18n: command names and abbreviations must remain untranslated
170 170 editcomment = _("""# Edit history between %s and %s
171 171 #
172 172 # Commands:
173 173 # p, pick = use commit
174 174 # e, edit = use commit, but stop for amending
175 175 # f, fold = use commit, but fold into previous commit (combines N and N-1)
176 176 # d, drop = remove commit from history
177 177 # m, mess = edit message without changing commit content
178 178 #
179 179 """)
180 180
181 181 def commitfuncfor(repo, src):
182 182 """Build a commit function for the replacement of <src>
183 183
184 184 This function ensure we apply the same treatment to all changesets.
185 185
186 186 - Add a 'histedit_source' entry in extra.
187 187
188 188 Note that fold have its own separated logic because its handling is a bit
189 189 different and not easily factored out of the fold method.
190 190 """
191 191 phasemin = src.phase()
192 192 def commitfunc(**kwargs):
193 193 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
194 194 try:
195 195 repo.ui.setconfig('phases', 'new-commit', phasemin)
196 196 extra = kwargs.get('extra', {}).copy()
197 197 extra['histedit_source'] = src.hex()
198 198 kwargs['extra'] = extra
199 199 return repo.commit(**kwargs)
200 200 finally:
201 201 repo.ui.restoreconfig(phasebackup)
202 202 return commitfunc
203 203
204 204
205 205
206 206 def applychanges(ui, repo, ctx, opts):
207 207 """Merge changeset from ctx (only) in the current working directory"""
208 208 wcpar = repo.dirstate.parents()[0]
209 209 if ctx.p1().node() == wcpar:
210 210 # edition ar "in place" we do not need to make any merge,
211 211 # just applies changes on parent for edition
212 212 cmdutil.revert(ui, repo, ctx, (wcpar, node.nullid), all=True)
213 213 stats = None
214 214 else:
215 215 try:
216 216 # ui.forcemerge is an internal variable, do not document
217 217 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
218 218 stats = mergemod.update(repo, ctx.node(), True, True, False,
219 219 ctx.p1().node())
220 220 finally:
221 221 repo.ui.setconfig('ui', 'forcemerge', '')
222 222 repo.setparents(wcpar, node.nullid)
223 223 repo.dirstate.write()
224 224 # fix up dirstate for copies and renames
225 225 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
226 226 return stats
227 227
228 228 def collapse(repo, first, last, commitopts):
229 229 """collapse the set of revisions from first to last as new one.
230 230
231 231 Expected commit options are:
232 232 - message
233 233 - date
234 234 - username
235 235 Commit message is edited in all cases.
236 236
237 237 This function works in memory."""
238 238 ctxs = list(repo.set('%d::%d', first, last))
239 239 if not ctxs:
240 240 return None
241 241 base = first.parents()[0]
242 242
243 243 # commit a new version of the old changeset, including the update
244 244 # collect all files which might be affected
245 245 files = set()
246 246 for ctx in ctxs:
247 247 files.update(ctx.files())
248 248
249 249 # Recompute copies (avoid recording a -> b -> a)
250 250 copied = copies.pathcopies(base, last)
251 251
252 252 # prune files which were reverted by the updates
253 253 def samefile(f):
254 254 if f in last.manifest():
255 255 a = last.filectx(f)
256 256 if f in base.manifest():
257 257 b = base.filectx(f)
258 258 return (a.data() == b.data()
259 259 and a.flags() == b.flags())
260 260 else:
261 261 return False
262 262 else:
263 263 return f not in base.manifest()
264 264 files = [f for f in files if not samefile(f)]
265 265 # commit version of these files as defined by head
266 266 headmf = last.manifest()
267 267 def filectxfn(repo, ctx, path):
268 268 if path in headmf:
269 269 fctx = last[path]
270 270 flags = fctx.flags()
271 271 mctx = context.memfilectx(fctx.path(), fctx.data(),
272 272 islink='l' in flags,
273 273 isexec='x' in flags,
274 274 copied=copied.get(path))
275 275 return mctx
276 276 raise IOError()
277 277
278 278 if commitopts.get('message'):
279 279 message = commitopts['message']
280 280 else:
281 281 message = first.description()
282 282 user = commitopts.get('user')
283 283 date = commitopts.get('date')
284 284 extra = commitopts.get('extra')
285 285
286 286 parents = (first.p1().node(), first.p2().node())
287 287 new = context.memctx(repo,
288 288 parents=parents,
289 289 text=message,
290 290 files=files,
291 291 filectxfn=filectxfn,
292 292 user=user,
293 293 date=date,
294 294 extra=extra)
295 295 new._text = cmdutil.commitforceeditor(repo, new, [])
296 296 return repo.commitctx(new)
297 297
298 298 def pick(ui, repo, ctx, ha, opts):
299 299 oldctx = repo[ha]
300 300 if oldctx.parents()[0] == ctx:
301 301 ui.debug('node %s unchanged\n' % ha)
302 302 return oldctx, []
303 303 hg.update(repo, ctx.node())
304 304 stats = applychanges(ui, repo, oldctx, opts)
305 305 if stats and stats[3] > 0:
306 306 raise error.InterventionRequired(_('Fix up the change and run '
307 307 'hg histedit --continue'))
308 308 # drop the second merge parent
309 309 commit = commitfuncfor(repo, oldctx)
310 310 n = commit(text=oldctx.description(), user=oldctx.user(),
311 311 date=oldctx.date(), extra=oldctx.extra())
312 312 if n is None:
313 313 ui.warn(_('%s: empty changeset\n')
314 314 % node.hex(ha))
315 315 return ctx, []
316 316 new = repo[n]
317 317 return new, [(oldctx.node(), (n,))]
318 318
319 319
320 320 def edit(ui, repo, ctx, ha, opts):
321 321 oldctx = repo[ha]
322 322 hg.update(repo, ctx.node())
323 323 applychanges(ui, repo, oldctx, opts)
324 324 raise error.InterventionRequired(
325 325 _('Make changes as needed, you may commit or record as needed now.\n'
326 326 'When you are finished, run hg histedit --continue to resume.'))
327 327
328 328 def fold(ui, repo, ctx, ha, opts):
329 329 oldctx = repo[ha]
330 330 hg.update(repo, ctx.node())
331 331 stats = applychanges(ui, repo, oldctx, opts)
332 332 if stats and stats[3] > 0:
333 333 raise error.InterventionRequired(
334 334 _('Fix up the change and run hg histedit --continue'))
335 335 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
336 336 date=oldctx.date(), extra=oldctx.extra())
337 337 if n is None:
338 338 ui.warn(_('%s: empty changeset')
339 339 % node.hex(ha))
340 340 return ctx, []
341 341 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
342 342
343 343 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
344 344 parent = ctx.parents()[0].node()
345 345 hg.update(repo, parent)
346 346 ### prepare new commit data
347 347 commitopts = opts.copy()
348 348 # username
349 349 if ctx.user() == oldctx.user():
350 350 username = ctx.user()
351 351 else:
352 352 username = ui.username()
353 353 commitopts['user'] = username
354 354 # commit message
355 355 newmessage = '\n***\n'.join(
356 356 [ctx.description()] +
357 357 [repo[r].description() for r in internalchanges] +
358 358 [oldctx.description()]) + '\n'
359 359 commitopts['message'] = newmessage
360 360 # date
361 361 commitopts['date'] = max(ctx.date(), oldctx.date())
362 362 extra = ctx.extra().copy()
363 363 # histedit_source
364 364 # note: ctx is likely a temporary commit but that the best we can do here
365 365 # This is sufficient to solve issue3681 anyway
366 366 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex())
367 367 commitopts['extra'] = extra
368 368 phasebackup = repo.ui.backupconfig('phases', 'new-commit')
369 369 try:
370 370 phasemin = max(ctx.phase(), oldctx.phase())
371 371 repo.ui.setconfig('phases', 'new-commit', phasemin)
372 372 n = collapse(repo, ctx, repo[newnode], commitopts)
373 373 finally:
374 374 repo.ui.restoreconfig(phasebackup)
375 375 if n is None:
376 376 return ctx, []
377 377 hg.update(repo, n)
378 378 replacements = [(oldctx.node(), (newnode,)),
379 379 (ctx.node(), (n,)),
380 380 (newnode, (n,)),
381 381 ]
382 382 for ich in internalchanges:
383 383 replacements.append((ich, (n,)))
384 384 return repo[n], replacements
385 385
386 386 def drop(ui, repo, ctx, ha, opts):
387 387 return ctx, [(repo[ha].node(), ())]
388 388
389 389
390 390 def message(ui, repo, ctx, ha, opts):
391 391 oldctx = repo[ha]
392 392 hg.update(repo, ctx.node())
393 393 stats = applychanges(ui, repo, oldctx, opts)
394 394 if stats and stats[3] > 0:
395 395 raise error.InterventionRequired(
396 396 _('Fix up the change and run hg histedit --continue'))
397 397 message = oldctx.description() + '\n'
398 398 message = ui.edit(message, ui.username())
399 399 commit = commitfuncfor(repo, oldctx)
400 400 new = commit(text=message, user=oldctx.user(), date=oldctx.date(),
401 401 extra=oldctx.extra())
402 402 newctx = repo[new]
403 403 if oldctx.node() != newctx.node():
404 404 return newctx, [(oldctx.node(), (new,))]
405 405 # We didn't make an edit, so just indicate no replaced nodes
406 406 return newctx, []
407 407
408 408 def findoutgoing(ui, repo, remote=None, force=False, opts={}):
409 409 """utility function to find the first outgoing changeset
410 410
411 411 Used by initialisation code"""
412 412 dest = ui.expandpath(remote or 'default-push', remote or 'default')
413 413 dest, revs = hg.parseurl(dest, None)[:2]
414 414 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
415 415
416 416 revs, checkout = hg.addbranchrevs(repo, repo, revs, None)
417 417 other = hg.peer(repo, opts, dest)
418 418
419 419 if revs:
420 420 revs = [repo.lookup(rev) for rev in revs]
421 421
422 422 # hexlify nodes from outgoing, because we're going to parse
423 423 # parent[0] using revsingle below, and if the binary hash
424 424 # contains special revset characters like ":" the revset
425 425 # parser can choke.
426 426 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force)
427 427 if not outgoing.missing:
428 428 raise util.Abort(_('no outgoing ancestors'))
429 429 return outgoing.missing[0]
430 430
431 431 actiontable = {'p': pick,
432 432 'pick': pick,
433 433 'e': edit,
434 434 'edit': edit,
435 435 'f': fold,
436 436 'fold': fold,
437 437 'd': drop,
438 438 'drop': drop,
439 439 'm': message,
440 440 'mess': message,
441 441 }
442 442
443 443 @command('histedit',
444 444 [('', 'commands', '',
445 445 _('Read history edits from the specified file.')),
446 446 ('c', 'continue', False, _('continue an edit already in progress')),
447 447 ('k', 'keep', False,
448 448 _("don't strip old nodes after edit is complete")),
449 449 ('', 'abort', False, _('abort an edit in progress')),
450 450 ('o', 'outgoing', False, _('changesets not found in destination')),
451 451 ('f', 'force', False,
452 452 _('force outgoing even for unrelated repositories')),
453 453 ('r', 'rev', [], _('first revision to be edited'))],
454 454 _("[PARENT]"))
455 455 def histedit(ui, repo, *freeargs, **opts):
456 456 """interactively edit changeset history
457 457 """
458 458 # TODO only abort if we try and histedit mq patches, not just
459 459 # blanket if mq patches are applied somewhere
460 460 mq = getattr(repo, 'mq', None)
461 461 if mq and mq.applied:
462 462 raise util.Abort(_('source has mq patches applied'))
463 463
464 464 # basic argument incompatibility processing
465 465 outg = opts.get('outgoing')
466 466 cont = opts.get('continue')
467 467 abort = opts.get('abort')
468 468 force = opts.get('force')
469 469 rules = opts.get('commands', '')
470 470 revs = opts.get('rev', [])
471 471 goal = 'new' # This invocation goal, in new, continue, abort
472 472 if force and not outg:
473 473 raise util.Abort(_('--force only allowed with --outgoing'))
474 474 if cont:
475 475 if util.any((outg, abort, revs, freeargs, rules)):
476 476 raise util.Abort(_('no arguments allowed with --continue'))
477 477 goal = 'continue'
478 478 elif abort:
479 479 if util.any((outg, revs, freeargs, rules)):
480 480 raise util.Abort(_('no arguments allowed with --abort'))
481 481 goal = 'abort'
482 482 else:
483 483 if os.path.exists(os.path.join(repo.path, 'histedit-state')):
484 484 raise util.Abort(_('history edit already in progress, try '
485 485 '--continue or --abort'))
486 486 if outg:
487 487 if revs:
488 488 raise util.Abort(_('no revisions allowed with --outgoing'))
489 489 if len(freeargs) > 1:
490 490 raise util.Abort(
491 491 _('only one repo argument allowed with --outgoing'))
492 492 else:
493 493 revs.extend(freeargs)
494 494 if len(revs) != 1:
495 495 raise util.Abort(
496 496 _('histedit requires exactly one parent revision'))
497 497
498 498
499 499 if goal == 'continue':
500 500 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
501 501 currentparent, wantnull = repo.dirstate.parents()
502 502 parentctx = repo[parentctxnode]
503 503 parentctx, repl = bootstrapcontinue(ui, repo, parentctx, rules, opts)
504 504 replacements.extend(repl)
505 505 elif goal == 'abort':
506 506 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
507 507 mapping, tmpnodes, leafs, _ntm = processreplacement(repo, replacements)
508 508 ui.debug('restore wc to old parent %s\n' % node.short(topmost))
509 509 hg.clean(repo, topmost)
510 510 cleanupnode(ui, repo, 'created', tmpnodes)
511 511 cleanupnode(ui, repo, 'temp', leafs)
512 512 os.unlink(os.path.join(repo.path, 'histedit-state'))
513 513 return
514 514 else:
515 515 cmdutil.checkunfinished(repo)
516 516 cmdutil.bailifchanged(repo)
517 517
518 518 topmost, empty = repo.dirstate.parents()
519 519 if outg:
520 520 if freeargs:
521 521 remote = freeargs[0]
522 522 else:
523 523 remote = None
524 524 root = findoutgoing(ui, repo, remote, force, opts)
525 525 else:
526 526 root = revs[0]
527 527 root = scmutil.revsingle(repo, root).node()
528 528
529 529 keep = opts.get('keep', False)
530 530 revs = between(repo, root, topmost, keep)
531 531 if not revs:
532 532 raise util.Abort(_('%s is not an ancestor of working directory') %
533 533 node.short(root))
534 534
535 535 ctxs = [repo[r] for r in revs]
536 536 if not rules:
537 537 rules = '\n'.join([makedesc(c) for c in ctxs])
538 538 rules += '\n\n'
539 539 rules += editcomment % (node.short(root), node.short(topmost))
540 540 rules = ui.edit(rules, ui.username())
541 541 # Save edit rules in .hg/histedit-last-edit.txt in case
542 542 # the user needs to ask for help after something
543 543 # surprising happens.
544 544 f = open(repo.join('histedit-last-edit.txt'), 'w')
545 545 f.write(rules)
546 546 f.close()
547 547 else:
548 548 if rules == '-':
549 549 f = sys.stdin
550 550 else:
551 551 f = open(rules)
552 552 rules = f.read()
553 553 f.close()
554 554 rules = [l for l in (r.strip() for r in rules.splitlines())
555 555 if l and not l[0] == '#']
556 556 rules = verifyrules(rules, repo, ctxs)
557 557
558 558 parentctx = repo[root].parents()[0]
559 559 keep = opts.get('keep', False)
560 560 replacements = []
561 561
562 562
563 563 while rules:
564 564 writestate(repo, parentctx.node(), rules, keep, topmost, replacements)
565 565 action, ha = rules.pop(0)
566 566 ui.debug('histedit: processing %s %s\n' % (action, ha))
567 567 actfunc = actiontable[action]
568 568 parentctx, replacement_ = actfunc(ui, repo, parentctx, ha, opts)
569 569 replacements.extend(replacement_)
570 570
571 571 hg.update(repo, parentctx.node())
572 572
573 573 mapping, tmpnodes, created, ntm = processreplacement(repo, replacements)
574 574 if mapping:
575 575 for prec, succs in mapping.iteritems():
576 576 if not succs:
577 577 ui.debug('histedit: %s is dropped\n' % node.short(prec))
578 578 else:
579 579 ui.debug('histedit: %s is replaced by %s\n' % (
580 580 node.short(prec), node.short(succs[0])))
581 581 if len(succs) > 1:
582 582 m = 'histedit: %s'
583 583 for n in succs[1:]:
584 584 ui.debug(m % node.short(n))
585 585
586 586 if not keep:
587 587 if mapping:
588 588 movebookmarks(ui, repo, mapping, topmost, ntm)
589 589 # TODO update mq state
590 590 if obsolete._enabled:
591 591 markers = []
592 592 # sort by revision number because it sound "right"
593 593 for prec in sorted(mapping, key=repo.changelog.rev):
594 594 succs = mapping[prec]
595 595 markers.append((repo[prec],
596 596 tuple(repo[s] for s in succs)))
597 597 if markers:
598 598 obsolete.createmarkers(repo, markers)
599 599 else:
600 600 cleanupnode(ui, repo, 'replaced', mapping)
601 601
602 602 cleanupnode(ui, repo, 'temp', tmpnodes)
603 603 os.unlink(os.path.join(repo.path, 'histedit-state'))
604 604 if os.path.exists(repo.sjoin('undo')):
605 605 os.unlink(repo.sjoin('undo'))
606 606
607 607
608 608 def bootstrapcontinue(ui, repo, parentctx, rules, opts):
609 609 action, currentnode = rules.pop(0)
610 610 ctx = repo[currentnode]
611 611 # is there any new commit between the expected parent and "."
612 612 #
613 613 # note: does not take non linear new change in account (but previous
614 614 # implementation didn't used them anyway (issue3655)
615 615 newchildren = [c.node() for c in repo.set('(%d::.)', parentctx)]
616 616 if parentctx.node() != node.nullid:
617 617 if not newchildren:
618 618 # `parentctxnode` should match but no result. This means that
619 619 # currentnode is not a descendant from parentctxnode.
620 620 msg = _('%s is not an ancestor of working directory')
621 621 hint = _('update to %s or descendant and run "hg histedit '
622 622 '--continue" again') % parentctx
623 623 raise util.Abort(msg % parentctx, hint=hint)
624 624 newchildren.pop(0) # remove parentctxnode
625 625 # Commit dirty working directory if necessary
626 626 new = None
627 627 m, a, r, d = repo.status()[:4]
628 628 if m or a or r or d:
629 629 # prepare the message for the commit to comes
630 630 if action in ('f', 'fold'):
631 631 message = 'fold-temp-revision %s' % currentnode
632 632 else:
633 633 message = ctx.description() + '\n'
634 634 if action in ('e', 'edit', 'm', 'mess'):
635 635 editor = cmdutil.commitforceeditor
636 636 else:
637 637 editor = False
638 638 commit = commitfuncfor(repo, ctx)
639 639 new = commit(text=message, user=ctx.user(),
640 640 date=ctx.date(), extra=ctx.extra(),
641 641 editor=editor)
642 642 if new is not None:
643 643 newchildren.append(new)
644 644
645 645 replacements = []
646 646 # track replacements
647 647 if ctx.node() not in newchildren:
648 648 # note: new children may be empty when the changeset is dropped.
649 649 # this happen e.g during conflicting pick where we revert content
650 650 # to parent.
651 651 replacements.append((ctx.node(), tuple(newchildren)))
652 652
653 653 if action in ('f', 'fold'):
654 654 if newchildren:
655 655 # finalize fold operation if applicable
656 656 if new is None:
657 657 new = newchildren[-1]
658 658 else:
659 659 newchildren.pop() # remove new from internal changes
660 660 parentctx, repl = finishfold(ui, repo, parentctx, ctx, new, opts,
661 661 newchildren)
662 662 replacements.extend(repl)
663 663 else:
664 664 # newchildren is empty if the fold did not result in any commit
665 665 # this happen when all folded change are discarded during the
666 666 # merge.
667 667 replacements.append((ctx.node(), (parentctx.node(),)))
668 668 elif newchildren:
669 669 # otherwise update "parentctx" before proceeding to further operation
670 670 parentctx = repo[newchildren[-1]]
671 671 return parentctx, replacements
672 672
673 673
674 674 def between(repo, old, new, keep):
675 675 """select and validate the set of revision to edit
676 676
677 677 When keep is false, the specified set can't have children."""
678 678 ctxs = list(repo.set('%n::%n', old, new))
679 679 if ctxs and not keep:
680 680 if (not obsolete._enabled and
681 681 repo.revs('(%ld::) - (%ld)', ctxs, ctxs)):
682 682 raise util.Abort(_('cannot edit history that would orphan nodes'))
683 683 if repo.revs('(%ld) and merge()', ctxs):
684 684 raise util.Abort(_('cannot edit history that contains merges'))
685 685 root = ctxs[0] # list is already sorted by repo.set
686 686 if not root.phase():
687 687 raise util.Abort(_('cannot edit immutable changeset: %s') % root)
688 688 return [c.node() for c in ctxs]
689 689
690 690
691 691 def writestate(repo, parentnode, rules, keep, topmost, replacements):
692 692 fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
693 693 pickle.dump((parentnode, rules, keep, topmost, replacements), fp)
694 694 fp.close()
695 695
696 696 def readstate(repo):
697 697 """Returns a tuple of (parentnode, rules, keep, topmost, replacements).
698 698 """
699 699 fp = open(os.path.join(repo.path, 'histedit-state'))
700 700 return pickle.load(fp)
701 701
702 702
703 703 def makedesc(c):
704 704 """build a initial action line for a ctx `c`
705 705
706 706 line are in the form:
707 707
708 708 pick <hash> <rev> <summary>
709 709 """
710 710 summary = ''
711 711 if c.description():
712 712 summary = c.description().splitlines()[0]
713 713 line = 'pick %s %d %s' % (c, c.rev(), summary)
714 714 return line[:80] # trim to 80 chars so it's not stupidly wide in my editor
715 715
716 716 def verifyrules(rules, repo, ctxs):
717 717 """Verify that there exists exactly one edit rule per given changeset.
718 718
719 719 Will abort if there are to many or too few rules, a malformed rule,
720 720 or a rule on a changeset outside of the user-given range.
721 721 """
722 722 parsed = []
723 723 expected = set(str(c) for c in ctxs)
724 724 seen = set()
725 725 for r in rules:
726 726 if ' ' not in r:
727 727 raise util.Abort(_('malformed line "%s"') % r)
728 728 action, rest = r.split(' ', 1)
729 729 ha = rest.strip().split(' ', 1)[0]
730 730 try:
731 731 ha = str(repo[ha]) # ensure its a short hash
732 732 except error.RepoError:
733 733 raise util.Abort(_('unknown changeset %s listed') % ha)
734 734 if ha not in expected:
735 735 raise util.Abort(
736 736 _('may not use changesets other than the ones listed'))
737 737 if ha in seen:
738 738 raise util.Abort(_('duplicated command for changeset %s') % ha)
739 739 seen.add(ha)
740 740 if action not in actiontable:
741 741 raise util.Abort(_('unknown action "%s"') % action)
742 742 parsed.append([action, ha])
743 743 missing = sorted(expected - seen) # sort to stabilize output
744 744 if missing:
745 745 raise util.Abort(_('missing rules for changeset %s') % missing[0],
746 746 hint=_('do you want to use the drop action?'))
747 747 return parsed
748 748
749 749 def processreplacement(repo, replacements):
750 750 """process the list of replacements to return
751 751
752 752 1) the final mapping between original and created nodes
753 753 2) the list of temporary node created by histedit
754 754 3) the list of new commit created by histedit"""
755 755 allsuccs = set()
756 756 replaced = set()
757 757 fullmapping = {}
758 758 # initialise basic set
759 759 # fullmapping record all operation recorded in replacement
760 760 for rep in replacements:
761 761 allsuccs.update(rep[1])
762 762 replaced.add(rep[0])
763 763 fullmapping.setdefault(rep[0], set()).update(rep[1])
764 764 new = allsuccs - replaced
765 765 tmpnodes = allsuccs & replaced
766 766 # Reduce content fullmapping into direct relation between original nodes
767 767 # and final node created during history edition
768 768 # Dropped changeset are replaced by an empty list
769 769 toproceed = set(fullmapping)
770 770 final = {}
771 771 while toproceed:
772 772 for x in list(toproceed):
773 773 succs = fullmapping[x]
774 774 for s in list(succs):
775 775 if s in toproceed:
776 776 # non final node with unknown closure
777 777 # We can't process this now
778 778 break
779 779 elif s in final:
780 780 # non final node, replace with closure
781 781 succs.remove(s)
782 782 succs.update(final[s])
783 783 else:
784 784 final[x] = succs
785 785 toproceed.remove(x)
786 786 # remove tmpnodes from final mapping
787 787 for n in tmpnodes:
788 788 del final[n]
789 789 # we expect all changes involved in final to exist in the repo
790 790 # turn `final` into list (topologically sorted)
791 791 nm = repo.changelog.nodemap
792 792 for prec, succs in final.items():
793 793 final[prec] = sorted(succs, key=nm.get)
794 794
795 795 # computed topmost element (necessary for bookmark)
796 796 if new:
797 797 newtopmost = sorted(new, key=repo.changelog.rev)[-1]
798 798 elif not final:
799 799 # Nothing rewritten at all. we won't need `newtopmost`
800 800 # It is the same as `oldtopmost` and `processreplacement` know it
801 801 newtopmost = None
802 802 else:
803 803 # every body died. The newtopmost is the parent of the root.
804 804 newtopmost = repo[sorted(final, key=repo.changelog.rev)[0]].p1().node()
805 805
806 806 return final, tmpnodes, new, newtopmost
807 807
808 808 def movebookmarks(ui, repo, mapping, oldtopmost, newtopmost):
809 809 """Move bookmark from old to newly created node"""
810 810 if not mapping:
811 811 # if nothing got rewritten there is not purpose for this function
812 812 return
813 813 moves = []
814 814 for bk, old in sorted(repo._bookmarks.iteritems()):
815 815 if old == oldtopmost:
816 816 # special case ensure bookmark stay on tip.
817 817 #
818 818 # This is arguably a feature and we may only want that for the
819 819 # active bookmark. But the behavior is kept compatible with the old
820 820 # version for now.
821 821 moves.append((bk, newtopmost))
822 822 continue
823 823 base = old
824 824 new = mapping.get(base, None)
825 825 if new is None:
826 826 continue
827 827 while not new:
828 828 # base is killed, trying with parent
829 829 base = repo[base].p1().node()
830 830 new = mapping.get(base, (base,))
831 831 # nothing to move
832 832 moves.append((bk, new[-1]))
833 833 if moves:
834 834 marks = repo._bookmarks
835 835 for mark, new in moves:
836 836 old = marks[mark]
837 837 ui.note(_('histedit: moving bookmarks %s from %s to %s\n')
838 838 % (mark, node.short(old), node.short(new)))
839 839 marks[mark] = new
840 840 marks.write()
841 841
842 842 def cleanupnode(ui, repo, name, nodes):
843 843 """strip a group of nodes from the repository
844 844
845 845 The set of node to strip may contains unknown nodes."""
846 846 ui.debug('should strip %s nodes %s\n' %
847 847 (name, ', '.join([node.short(n) for n in nodes])))
848 848 lock = None
849 849 try:
850 850 lock = repo.lock()
851 851 # Find all node that need to be stripped
852 852 # (we hg %lr instead of %ln to silently ignore unknown item
853 853 nm = repo.changelog.nodemap
854 854 nodes = [n for n in nodes if n in nm]
855 855 roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
856 856 for c in roots:
857 857 # We should process node in reverse order to strip tip most first.
858 858 # but this trigger a bug in changegroup hook.
859 859 # This would reduce bundle overhead
860 860 repair.strip(ui, repo, c)
861 861 finally:
862 862 lockmod.release(lock)
863 863
864 864 def summaryhook(ui, repo):
865 865 if not os.path.exists(repo.join('histedit-state')):
866 866 return
867 867 (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
868 868 if rules:
869 869 # i18n: column positioning for "hg summary"
870 870 ui.write(_('hist: %s (histedit --continue)\n') %
871 871 (ui.label(_('%d remaining'), 'histedit.remaining') %
872 872 len(rules)))
873 873
874 874 def extsetup(ui):
875 875 cmdutil.summaryhooks.add('histedit', summaryhook)
876 876 cmdutil.unfinishedstates.append(
877 ['histedit-state', False, _('histedit in progress'),
877 ['histedit-state', False, True, _('histedit in progress'),
878 878 _("use 'hg histedit --continue' or 'hg histedit --abort'")])
@@ -1,804 +1,804 b''
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 of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''command to move sets of revisions to a different ancestor
9 9
10 10 This extension lets you rebase changesets in an existing Mercurial
11 11 repository.
12 12
13 13 For more information:
14 14 http://mercurial.selenic.com/wiki/RebaseExtension
15 15 '''
16 16
17 17 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
18 18 from mercurial import extensions, patch, scmutil, phases, obsolete, error
19 19 from mercurial.commands import templateopts
20 20 from mercurial.node import nullrev
21 21 from mercurial.lock import release
22 22 from mercurial.i18n import _
23 23 import os, errno
24 24
25 25 nullmerge = -2
26 26 revignored = -3
27 27
28 28 cmdtable = {}
29 29 command = cmdutil.command(cmdtable)
30 30 testedwith = 'internal'
31 31
32 32 @command('rebase',
33 33 [('s', 'source', '',
34 34 _('rebase from the specified changeset'), _('REV')),
35 35 ('b', 'base', '',
36 36 _('rebase from the base of the specified changeset '
37 37 '(up to greatest common ancestor of base and dest)'),
38 38 _('REV')),
39 39 ('r', 'rev', [],
40 40 _('rebase these revisions'),
41 41 _('REV')),
42 42 ('d', 'dest', '',
43 43 _('rebase onto the specified changeset'), _('REV')),
44 44 ('', 'collapse', False, _('collapse the rebased changesets')),
45 45 ('m', 'message', '',
46 46 _('use text as collapse commit message'), _('TEXT')),
47 47 ('e', 'edit', False, _('invoke editor on commit messages')),
48 48 ('l', 'logfile', '',
49 49 _('read collapse commit message from file'), _('FILE')),
50 50 ('', 'keep', False, _('keep original changesets')),
51 51 ('', 'keepbranches', False, _('keep original branch names')),
52 52 ('D', 'detach', False, _('(DEPRECATED)')),
53 53 ('t', 'tool', '', _('specify merge tool')),
54 54 ('c', 'continue', False, _('continue an interrupted rebase')),
55 55 ('a', 'abort', False, _('abort an interrupted rebase'))] +
56 56 templateopts,
57 57 _('[-s REV | -b REV] [-d REV] [OPTION]'))
58 58 def rebase(ui, repo, **opts):
59 59 """move changeset (and descendants) to a different branch
60 60
61 61 Rebase uses repeated merging to graft changesets from one part of
62 62 history (the source) onto another (the destination). This can be
63 63 useful for linearizing *local* changes relative to a master
64 64 development tree.
65 65
66 66 You should not rebase changesets that have already been shared
67 67 with others. Doing so will force everybody else to perform the
68 68 same rebase or they will end up with duplicated changesets after
69 69 pulling in your rebased changesets.
70 70
71 71 In its default configuration, Mercurial will prevent you from
72 72 rebasing published changes. See :hg:`help phases` for details.
73 73
74 74 If you don't specify a destination changeset (``-d/--dest``),
75 75 rebase uses the current branch tip as the destination. (The
76 76 destination changeset is not modified by rebasing, but new
77 77 changesets are added as its descendants.)
78 78
79 79 You can specify which changesets to rebase in two ways: as a
80 80 "source" changeset or as a "base" changeset. Both are shorthand
81 81 for a topologically related set of changesets (the "source
82 82 branch"). If you specify source (``-s/--source``), rebase will
83 83 rebase that changeset and all of its descendants onto dest. If you
84 84 specify base (``-b/--base``), rebase will select ancestors of base
85 85 back to but not including the common ancestor with dest. Thus,
86 86 ``-b`` is less precise but more convenient than ``-s``: you can
87 87 specify any changeset in the source branch, and rebase will select
88 88 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
89 89 uses the parent of the working directory as the base.
90 90
91 91 For advanced usage, a third way is available through the ``--rev``
92 92 option. It allows you to specify an arbitrary set of changesets to
93 93 rebase. Descendants of revs you specify with this option are not
94 94 automatically included in the rebase.
95 95
96 96 By default, rebase recreates the changesets in the source branch
97 97 as descendants of dest and then destroys the originals. Use
98 98 ``--keep`` to preserve the original source changesets. Some
99 99 changesets in the source branch (e.g. merges from the destination
100 100 branch) may be dropped if they no longer contribute any change.
101 101
102 102 One result of the rules for selecting the destination changeset
103 103 and source branch is that, unlike ``merge``, rebase will do
104 104 nothing if you are at the branch tip of a named branch
105 105 with two heads. You need to explicitly specify source and/or
106 106 destination (or ``update`` to the other head, if it's the head of
107 107 the intended source branch).
108 108
109 109 If a rebase is interrupted to manually resolve a merge, it can be
110 110 continued with --continue/-c or aborted with --abort/-a.
111 111
112 112 Returns 0 on success, 1 if nothing to rebase.
113 113 """
114 114 originalwd = target = None
115 115 activebookmark = None
116 116 external = nullrev
117 117 state = {}
118 118 skipped = set()
119 119 targetancestors = set()
120 120
121 121 editor = None
122 122 if opts.get('edit'):
123 123 editor = cmdutil.commitforceeditor
124 124
125 125 lock = wlock = None
126 126 try:
127 127 wlock = repo.wlock()
128 128 lock = repo.lock()
129 129
130 130 # Validate input and define rebasing points
131 131 destf = opts.get('dest', None)
132 132 srcf = opts.get('source', None)
133 133 basef = opts.get('base', None)
134 134 revf = opts.get('rev', [])
135 135 contf = opts.get('continue')
136 136 abortf = opts.get('abort')
137 137 collapsef = opts.get('collapse', False)
138 138 collapsemsg = cmdutil.logmessage(ui, opts)
139 139 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
140 140 keepf = opts.get('keep', False)
141 141 keepbranchesf = opts.get('keepbranches', False)
142 142 # keepopen is not meant for use on the command line, but by
143 143 # other extensions
144 144 keepopen = opts.get('keepopen', False)
145 145
146 146 if collapsemsg and not collapsef:
147 147 raise util.Abort(
148 148 _('message can only be specified with collapse'))
149 149
150 150 if contf or abortf:
151 151 if contf and abortf:
152 152 raise util.Abort(_('cannot use both abort and continue'))
153 153 if collapsef:
154 154 raise util.Abort(
155 155 _('cannot use collapse with continue or abort'))
156 156 if srcf or basef or destf:
157 157 raise util.Abort(
158 158 _('abort and continue do not allow specifying revisions'))
159 159 if opts.get('tool', False):
160 160 ui.warn(_('tool option will be ignored\n'))
161 161
162 162 (originalwd, target, state, skipped, collapsef, keepf,
163 163 keepbranchesf, external, activebookmark) = restorestatus(repo)
164 164 if abortf:
165 165 return abort(repo, originalwd, target, state)
166 166 else:
167 167 if srcf and basef:
168 168 raise util.Abort(_('cannot specify both a '
169 169 'source and a base'))
170 170 if revf and basef:
171 171 raise util.Abort(_('cannot specify both a '
172 172 'revision and a base'))
173 173 if revf and srcf:
174 174 raise util.Abort(_('cannot specify both a '
175 175 'revision and a source'))
176 176
177 177 cmdutil.checkunfinished(repo)
178 178 cmdutil.bailifchanged(repo)
179 179
180 180 if not destf:
181 181 # Destination defaults to the latest revision in the
182 182 # current branch
183 183 branch = repo[None].branch()
184 184 dest = repo[branch]
185 185 else:
186 186 dest = scmutil.revsingle(repo, destf)
187 187
188 188 if revf:
189 189 rebaseset = repo.revs('%lr', revf)
190 190 elif srcf:
191 191 src = scmutil.revrange(repo, [srcf])
192 192 rebaseset = repo.revs('(%ld)::', src)
193 193 else:
194 194 base = scmutil.revrange(repo, [basef or '.'])
195 195 rebaseset = repo.revs(
196 196 '(children(ancestor(%ld, %d)) and ::(%ld))::',
197 197 base, dest, base)
198 198 if rebaseset:
199 199 root = min(rebaseset)
200 200 else:
201 201 root = None
202 202
203 203 if not rebaseset:
204 204 repo.ui.debug('base is ancestor of destination\n')
205 205 result = None
206 206 elif (not (keepf or obsolete._enabled)
207 207 and repo.revs('first(children(%ld) - %ld)',
208 208 rebaseset, rebaseset)):
209 209 raise util.Abort(
210 210 _("can't remove original changesets with"
211 211 " unrebased descendants"),
212 212 hint=_('use --keep to keep original changesets'))
213 213 else:
214 214 result = buildstate(repo, dest, rebaseset, collapsef)
215 215
216 216 if not result:
217 217 # Empty state built, nothing to rebase
218 218 ui.status(_('nothing to rebase\n'))
219 219 return 1
220 220 elif not keepf and not repo[root].mutable():
221 221 raise util.Abort(_("can't rebase immutable changeset %s")
222 222 % repo[root],
223 223 hint=_('see hg help phases for details'))
224 224 else:
225 225 originalwd, target, state = result
226 226 if collapsef:
227 227 targetancestors = repo.changelog.ancestors([target],
228 228 inclusive=True)
229 229 external = checkexternal(repo, state, targetancestors)
230 230
231 231 if keepbranchesf:
232 232 assert not extrafn, 'cannot use both keepbranches and extrafn'
233 233 def extrafn(ctx, extra):
234 234 extra['branch'] = ctx.branch()
235 235 if collapsef:
236 236 branches = set()
237 237 for rev in state:
238 238 branches.add(repo[rev].branch())
239 239 if len(branches) > 1:
240 240 raise util.Abort(_('cannot collapse multiple named '
241 241 'branches'))
242 242
243 243
244 244 # Rebase
245 245 if not targetancestors:
246 246 targetancestors = repo.changelog.ancestors([target], inclusive=True)
247 247
248 248 # Keep track of the current bookmarks in order to reset them later
249 249 currentbookmarks = repo._bookmarks.copy()
250 250 activebookmark = activebookmark or repo._bookmarkcurrent
251 251 if activebookmark:
252 252 bookmarks.unsetcurrent(repo)
253 253
254 254 sortedstate = sorted(state)
255 255 total = len(sortedstate)
256 256 pos = 0
257 257 for rev in sortedstate:
258 258 pos += 1
259 259 if state[rev] == -1:
260 260 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
261 261 _('changesets'), total)
262 262 p1, p2 = defineparents(repo, rev, target, state,
263 263 targetancestors)
264 264 storestatus(repo, originalwd, target, state, collapsef, keepf,
265 265 keepbranchesf, external, activebookmark)
266 266 if len(repo.parents()) == 2:
267 267 repo.ui.debug('resuming interrupted rebase\n')
268 268 else:
269 269 try:
270 270 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
271 271 stats = rebasenode(repo, rev, p1, state, collapsef)
272 272 if stats and stats[3] > 0:
273 273 raise error.InterventionRequired(
274 274 _('unresolved conflicts (see hg '
275 275 'resolve, then hg rebase --continue)'))
276 276 finally:
277 277 ui.setconfig('ui', 'forcemerge', '')
278 278 cmdutil.duplicatecopies(repo, rev, target)
279 279 if not collapsef:
280 280 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
281 281 editor=editor)
282 282 else:
283 283 # Skip commit if we are collapsing
284 284 repo.setparents(repo[p1].node())
285 285 newrev = None
286 286 # Update the state
287 287 if newrev is not None:
288 288 state[rev] = repo[newrev].rev()
289 289 else:
290 290 if not collapsef:
291 291 ui.note(_('no changes, revision %d skipped\n') % rev)
292 292 ui.debug('next revision set to %s\n' % p1)
293 293 skipped.add(rev)
294 294 state[rev] = p1
295 295
296 296 ui.progress(_('rebasing'), None)
297 297 ui.note(_('rebase merging completed\n'))
298 298
299 299 if collapsef and not keepopen:
300 300 p1, p2 = defineparents(repo, min(state), target,
301 301 state, targetancestors)
302 302 if collapsemsg:
303 303 commitmsg = collapsemsg
304 304 else:
305 305 commitmsg = 'Collapsed revision'
306 306 for rebased in state:
307 307 if rebased not in skipped and state[rebased] > nullmerge:
308 308 commitmsg += '\n* %s' % repo[rebased].description()
309 309 commitmsg = ui.edit(commitmsg, repo.ui.username())
310 310 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
311 311 extrafn=extrafn, editor=editor)
312 312
313 313 if 'qtip' in repo.tags():
314 314 updatemq(repo, state, skipped, **opts)
315 315
316 316 if currentbookmarks:
317 317 # Nodeids are needed to reset bookmarks
318 318 nstate = {}
319 319 for k, v in state.iteritems():
320 320 if v > nullmerge:
321 321 nstate[repo[k].node()] = repo[v].node()
322 322 # XXX this is the same as dest.node() for the non-continue path --
323 323 # this should probably be cleaned up
324 324 targetnode = repo[target].node()
325 325
326 326 if not keepf:
327 327 collapsedas = None
328 328 if collapsef:
329 329 collapsedas = newrev
330 330 clearrebased(ui, repo, state, skipped, collapsedas)
331 331
332 332 if currentbookmarks:
333 333 updatebookmarks(repo, targetnode, nstate, currentbookmarks)
334 334
335 335 clearstatus(repo)
336 336 ui.note(_("rebase completed\n"))
337 337 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
338 338 if skipped:
339 339 ui.note(_("%d revisions have been skipped\n") % len(skipped))
340 340
341 341 if (activebookmark and
342 342 repo['tip'].node() == repo._bookmarks[activebookmark]):
343 343 bookmarks.setcurrent(repo, activebookmark)
344 344
345 345 finally:
346 346 release(lock, wlock)
347 347
348 348 def checkexternal(repo, state, targetancestors):
349 349 """Check whether one or more external revisions need to be taken in
350 350 consideration. In the latter case, abort.
351 351 """
352 352 external = nullrev
353 353 source = min(state)
354 354 for rev in state:
355 355 if rev == source:
356 356 continue
357 357 # Check externals and fail if there are more than one
358 358 for p in repo[rev].parents():
359 359 if (p.rev() not in state
360 360 and p.rev() not in targetancestors):
361 361 if external != nullrev:
362 362 raise util.Abort(_('unable to collapse, there is more '
363 363 'than one external parent'))
364 364 external = p.rev()
365 365 return external
366 366
367 367 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
368 368 'Commit the changes and store useful information in extra'
369 369 try:
370 370 repo.setparents(repo[p1].node(), repo[p2].node())
371 371 ctx = repo[rev]
372 372 if commitmsg is None:
373 373 commitmsg = ctx.description()
374 374 extra = {'rebase_source': ctx.hex()}
375 375 if extrafn:
376 376 extrafn(ctx, extra)
377 377 # Commit might fail if unresolved files exist
378 378 newrev = repo.commit(text=commitmsg, user=ctx.user(),
379 379 date=ctx.date(), extra=extra, editor=editor)
380 380 repo.dirstate.setbranch(repo[newrev].branch())
381 381 targetphase = max(ctx.phase(), phases.draft)
382 382 # retractboundary doesn't overwrite upper phase inherited from parent
383 383 newnode = repo[newrev].node()
384 384 if newnode:
385 385 phases.retractboundary(repo, targetphase, [newnode])
386 386 return newrev
387 387 except util.Abort:
388 388 # Invalidate the previous setparents
389 389 repo.dirstate.invalidate()
390 390 raise
391 391
392 392 def rebasenode(repo, rev, p1, state, collapse):
393 393 'Rebase a single revision'
394 394 # Merge phase
395 395 # Update to target and merge it with local
396 396 if repo['.'].rev() != repo[p1].rev():
397 397 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
398 398 merge.update(repo, p1, False, True, False)
399 399 else:
400 400 repo.ui.debug(" already in target\n")
401 401 repo.dirstate.write()
402 402 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
403 403 base = None
404 404 if repo[rev].rev() != repo[min(state)].rev():
405 405 base = repo[rev].p1().node()
406 406 # When collapsing in-place, the parent is the common ancestor, we
407 407 # have to allow merging with it.
408 408 return merge.update(repo, rev, True, True, False, base, collapse)
409 409
410 410 def nearestrebased(repo, rev, state):
411 411 """return the nearest ancestors of rev in the rebase result"""
412 412 rebased = [r for r in state if state[r] > nullmerge]
413 413 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
414 414 if candidates:
415 415 return state[candidates[0]]
416 416 else:
417 417 return None
418 418
419 419 def defineparents(repo, rev, target, state, targetancestors):
420 420 'Return the new parent relationship of the revision that will be rebased'
421 421 parents = repo[rev].parents()
422 422 p1 = p2 = nullrev
423 423
424 424 P1n = parents[0].rev()
425 425 if P1n in targetancestors:
426 426 p1 = target
427 427 elif P1n in state:
428 428 if state[P1n] == nullmerge:
429 429 p1 = target
430 430 elif state[P1n] == revignored:
431 431 p1 = nearestrebased(repo, P1n, state)
432 432 if p1 is None:
433 433 p1 = target
434 434 else:
435 435 p1 = state[P1n]
436 436 else: # P1n external
437 437 p1 = target
438 438 p2 = P1n
439 439
440 440 if len(parents) == 2 and parents[1].rev() not in targetancestors:
441 441 P2n = parents[1].rev()
442 442 # interesting second parent
443 443 if P2n in state:
444 444 if p1 == target: # P1n in targetancestors or external
445 445 p1 = state[P2n]
446 446 elif state[P2n] == revignored:
447 447 p2 = nearestrebased(repo, P2n, state)
448 448 if p2 is None:
449 449 # no ancestors rebased yet, detach
450 450 p2 = target
451 451 else:
452 452 p2 = state[P2n]
453 453 else: # P2n external
454 454 if p2 != nullrev: # P1n external too => rev is a merged revision
455 455 raise util.Abort(_('cannot use revision %d as base, result '
456 456 'would have 3 parents') % rev)
457 457 p2 = P2n
458 458 repo.ui.debug(" future parents are %d and %d\n" %
459 459 (repo[p1].rev(), repo[p2].rev()))
460 460 return p1, p2
461 461
462 462 def isagitpatch(repo, patchname):
463 463 'Return true if the given patch is in git format'
464 464 mqpatch = os.path.join(repo.mq.path, patchname)
465 465 for line in patch.linereader(file(mqpatch, 'rb')):
466 466 if line.startswith('diff --git'):
467 467 return True
468 468 return False
469 469
470 470 def updatemq(repo, state, skipped, **opts):
471 471 'Update rebased mq patches - finalize and then import them'
472 472 mqrebase = {}
473 473 mq = repo.mq
474 474 original_series = mq.fullseries[:]
475 475 skippedpatches = set()
476 476
477 477 for p in mq.applied:
478 478 rev = repo[p.node].rev()
479 479 if rev in state:
480 480 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
481 481 (rev, p.name))
482 482 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
483 483 else:
484 484 # Applied but not rebased, not sure this should happen
485 485 skippedpatches.add(p.name)
486 486
487 487 if mqrebase:
488 488 mq.finish(repo, mqrebase.keys())
489 489
490 490 # We must start import from the newest revision
491 491 for rev in sorted(mqrebase, reverse=True):
492 492 if rev not in skipped:
493 493 name, isgit = mqrebase[rev]
494 494 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
495 495 mq.qimport(repo, (), patchname=name, git=isgit,
496 496 rev=[str(state[rev])])
497 497 else:
498 498 # Rebased and skipped
499 499 skippedpatches.add(mqrebase[rev][0])
500 500
501 501 # Patches were either applied and rebased and imported in
502 502 # order, applied and removed or unapplied. Discard the removed
503 503 # ones while preserving the original series order and guards.
504 504 newseries = [s for s in original_series
505 505 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
506 506 mq.fullseries[:] = newseries
507 507 mq.seriesdirty = True
508 508 mq.savedirty()
509 509
510 510 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
511 511 'Move bookmarks to their correct changesets, and delete divergent ones'
512 512 marks = repo._bookmarks
513 513 for k, v in originalbookmarks.iteritems():
514 514 if v in nstate:
515 515 # update the bookmarks for revs that have moved
516 516 marks[k] = nstate[v]
517 517 bookmarks.deletedivergent(repo, [targetnode], k)
518 518
519 519 marks.write()
520 520
521 521 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
522 522 external, activebookmark):
523 523 'Store the current status to allow recovery'
524 524 f = repo.opener("rebasestate", "w")
525 525 f.write(repo[originalwd].hex() + '\n')
526 526 f.write(repo[target].hex() + '\n')
527 527 f.write(repo[external].hex() + '\n')
528 528 f.write('%d\n' % int(collapse))
529 529 f.write('%d\n' % int(keep))
530 530 f.write('%d\n' % int(keepbranches))
531 531 f.write('%s\n' % (activebookmark or ''))
532 532 for d, v in state.iteritems():
533 533 oldrev = repo[d].hex()
534 534 if v > nullmerge:
535 535 newrev = repo[v].hex()
536 536 else:
537 537 newrev = v
538 538 f.write("%s:%s\n" % (oldrev, newrev))
539 539 f.close()
540 540 repo.ui.debug('rebase status stored\n')
541 541
542 542 def clearstatus(repo):
543 543 'Remove the status files'
544 544 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
545 545
546 546 def restorestatus(repo):
547 547 'Restore a previously stored status'
548 548 try:
549 549 target = None
550 550 collapse = False
551 551 external = nullrev
552 552 activebookmark = None
553 553 state = {}
554 554 f = repo.opener("rebasestate")
555 555 for i, l in enumerate(f.read().splitlines()):
556 556 if i == 0:
557 557 originalwd = repo[l].rev()
558 558 elif i == 1:
559 559 target = repo[l].rev()
560 560 elif i == 2:
561 561 external = repo[l].rev()
562 562 elif i == 3:
563 563 collapse = bool(int(l))
564 564 elif i == 4:
565 565 keep = bool(int(l))
566 566 elif i == 5:
567 567 keepbranches = bool(int(l))
568 568 elif i == 6 and not (len(l) == 81 and ':' in l):
569 569 # line 6 is a recent addition, so for backwards compatibility
570 570 # check that the line doesn't look like the oldrev:newrev lines
571 571 activebookmark = l
572 572 else:
573 573 oldrev, newrev = l.split(':')
574 574 if newrev in (str(nullmerge), str(revignored)):
575 575 state[repo[oldrev].rev()] = int(newrev)
576 576 else:
577 577 state[repo[oldrev].rev()] = repo[newrev].rev()
578 578 skipped = set()
579 579 # recompute the set of skipped revs
580 580 if not collapse:
581 581 seen = set([target])
582 582 for old, new in sorted(state.items()):
583 583 if new != nullrev and new in seen:
584 584 skipped.add(old)
585 585 seen.add(new)
586 586 repo.ui.debug('computed skipped revs: %s\n' % skipped)
587 587 repo.ui.debug('rebase status resumed\n')
588 588 return (originalwd, target, state, skipped,
589 589 collapse, keep, keepbranches, external, activebookmark)
590 590 except IOError, err:
591 591 if err.errno != errno.ENOENT:
592 592 raise
593 593 raise util.Abort(_('no rebase in progress'))
594 594
595 595 def abort(repo, originalwd, target, state):
596 596 'Restore the repository to its original state'
597 597 dstates = [s for s in state.values() if s != nullrev]
598 598 immutable = [d for d in dstates if not repo[d].mutable()]
599 599 if immutable:
600 600 raise util.Abort(_("can't abort rebase due to immutable changesets %s")
601 601 % ', '.join(str(repo[r]) for r in immutable),
602 602 hint=_('see hg help phases for details'))
603 603
604 604 descendants = set()
605 605 if dstates:
606 606 descendants = set(repo.changelog.descendants(dstates))
607 607 if descendants - set(dstates):
608 608 repo.ui.warn(_("warning: new changesets detected on target branch, "
609 609 "can't abort\n"))
610 610 return -1
611 611 else:
612 612 # Strip from the first rebased revision
613 613 merge.update(repo, repo[originalwd].rev(), False, True, False)
614 614 rebased = filter(lambda x: x > -1 and x != target, state.values())
615 615 if rebased:
616 616 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
617 617 # no backup of rebased cset versions needed
618 618 repair.strip(repo.ui, repo, strippoints)
619 619 clearstatus(repo)
620 620 repo.ui.warn(_('rebase aborted\n'))
621 621 return 0
622 622
623 623 def buildstate(repo, dest, rebaseset, collapse):
624 624 '''Define which revisions are going to be rebased and where
625 625
626 626 repo: repo
627 627 dest: context
628 628 rebaseset: set of rev
629 629 '''
630 630
631 631 # This check isn't strictly necessary, since mq detects commits over an
632 632 # applied patch. But it prevents messing up the working directory when
633 633 # a partially completed rebase is blocked by mq.
634 634 if 'qtip' in repo.tags() and (dest.node() in
635 635 [s.node for s in repo.mq.applied]):
636 636 raise util.Abort(_('cannot rebase onto an applied mq patch'))
637 637
638 638 roots = list(repo.set('roots(%ld)', rebaseset))
639 639 if not roots:
640 640 raise util.Abort(_('no matching revisions'))
641 641 roots.sort()
642 642 state = {}
643 643 detachset = set()
644 644 for root in roots:
645 645 commonbase = root.ancestor(dest)
646 646 if commonbase == root:
647 647 raise util.Abort(_('source is ancestor of destination'))
648 648 if commonbase == dest:
649 649 samebranch = root.branch() == dest.branch()
650 650 if not collapse and samebranch and root in dest.children():
651 651 repo.ui.debug('source is a child of destination\n')
652 652 return None
653 653
654 654 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, roots))
655 655 state.update(dict.fromkeys(rebaseset, nullrev))
656 656 # Rebase tries to turn <dest> into a parent of <root> while
657 657 # preserving the number of parents of rebased changesets:
658 658 #
659 659 # - A changeset with a single parent will always be rebased as a
660 660 # changeset with a single parent.
661 661 #
662 662 # - A merge will be rebased as merge unless its parents are both
663 663 # ancestors of <dest> or are themselves in the rebased set and
664 664 # pruned while rebased.
665 665 #
666 666 # If one parent of <root> is an ancestor of <dest>, the rebased
667 667 # version of this parent will be <dest>. This is always true with
668 668 # --base option.
669 669 #
670 670 # Otherwise, we need to *replace* the original parents with
671 671 # <dest>. This "detaches" the rebased set from its former location
672 672 # and rebases it onto <dest>. Changes introduced by ancestors of
673 673 # <root> not common with <dest> (the detachset, marked as
674 674 # nullmerge) are "removed" from the rebased changesets.
675 675 #
676 676 # - If <root> has a single parent, set it to <dest>.
677 677 #
678 678 # - If <root> is a merge, we cannot decide which parent to
679 679 # replace, the rebase operation is not clearly defined.
680 680 #
681 681 # The table below sums up this behavior:
682 682 #
683 683 # +------------------+----------------------+-------------------------+
684 684 # | | one parent | merge |
685 685 # +------------------+----------------------+-------------------------+
686 686 # | parent in | new parent is <dest> | parents in ::<dest> are |
687 687 # | ::<dest> | | remapped to <dest> |
688 688 # +------------------+----------------------+-------------------------+
689 689 # | unrelated source | new parent is <dest> | ambiguous, abort |
690 690 # +------------------+----------------------+-------------------------+
691 691 #
692 692 # The actual abort is handled by `defineparents`
693 693 if len(root.parents()) <= 1:
694 694 # ancestors of <root> not ancestors of <dest>
695 695 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
696 696 [root.rev()]))
697 697 for r in detachset:
698 698 if r not in state:
699 699 state[r] = nullmerge
700 700 if len(roots) > 1:
701 701 # If we have multiple roots, we may have "hole" in the rebase set.
702 702 # Rebase roots that descend from those "hole" should not be detached as
703 703 # other root are. We use the special `revignored` to inform rebase that
704 704 # the revision should be ignored but that `defineparents` should search
705 705 # a rebase destination that make sense regarding rebased topology.
706 706 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
707 707 for ignored in set(rebasedomain) - set(rebaseset):
708 708 state[ignored] = revignored
709 709 return repo['.'].rev(), dest.rev(), state
710 710
711 711 def clearrebased(ui, repo, state, skipped, collapsedas=None):
712 712 """dispose of rebased revision at the end of the rebase
713 713
714 714 If `collapsedas` is not None, the rebase was a collapse whose result if the
715 715 `collapsedas` node."""
716 716 if obsolete._enabled:
717 717 markers = []
718 718 for rev, newrev in sorted(state.items()):
719 719 if newrev >= 0:
720 720 if rev in skipped:
721 721 succs = ()
722 722 elif collapsedas is not None:
723 723 succs = (repo[collapsedas],)
724 724 else:
725 725 succs = (repo[newrev],)
726 726 markers.append((repo[rev], succs))
727 727 if markers:
728 728 obsolete.createmarkers(repo, markers)
729 729 else:
730 730 rebased = [rev for rev in state if state[rev] > nullmerge]
731 731 if rebased:
732 732 stripped = []
733 733 for root in repo.set('roots(%ld)', rebased):
734 734 if set(repo.changelog.descendants([root.rev()])) - set(state):
735 735 ui.warn(_("warning: new changesets detected "
736 736 "on source branch, not stripping\n"))
737 737 else:
738 738 stripped.append(root.node())
739 739 if stripped:
740 740 # backup the old csets by default
741 741 repair.strip(ui, repo, stripped, "all")
742 742
743 743
744 744 def pullrebase(orig, ui, repo, *args, **opts):
745 745 'Call rebase after pull if the latter has been invoked with --rebase'
746 746 if opts.get('rebase'):
747 747 if opts.get('update'):
748 748 del opts['update']
749 749 ui.debug('--update and --rebase are not compatible, ignoring '
750 750 'the update flag\n')
751 751
752 752 movemarkfrom = repo['.'].node()
753 753 cmdutil.bailifchanged(repo)
754 754 revsprepull = len(repo)
755 755 origpostincoming = commands.postincoming
756 756 def _dummy(*args, **kwargs):
757 757 pass
758 758 commands.postincoming = _dummy
759 759 try:
760 760 orig(ui, repo, *args, **opts)
761 761 finally:
762 762 commands.postincoming = origpostincoming
763 763 revspostpull = len(repo)
764 764 if revspostpull > revsprepull:
765 765 # --rev option from pull conflict with rebase own --rev
766 766 # dropping it
767 767 if 'rev' in opts:
768 768 del opts['rev']
769 769 rebase(ui, repo, **opts)
770 770 branch = repo[None].branch()
771 771 dest = repo[branch].rev()
772 772 if dest != repo['.'].rev():
773 773 # there was nothing to rebase we force an update
774 774 hg.update(repo, dest)
775 775 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
776 776 ui.status(_("updating bookmark %s\n")
777 777 % repo._bookmarkcurrent)
778 778 else:
779 779 if opts.get('tool'):
780 780 raise util.Abort(_('--tool can only be used with --rebase'))
781 781 orig(ui, repo, *args, **opts)
782 782
783 783 def summaryhook(ui, repo):
784 784 if not os.path.exists(repo.join('rebasestate')):
785 785 return
786 786 state = restorestatus(repo)[2]
787 787 numrebased = len([i for i in state.itervalues() if i != -1])
788 788 # i18n: column positioning for "hg summary"
789 789 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
790 790 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
791 791 ui.label(_('%d remaining'), 'rebase.remaining') %
792 792 (len(state) - numrebased)))
793 793
794 794 def uisetup(ui):
795 795 'Replace pull with a decorator to provide --rebase option'
796 796 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
797 797 entry[1].append(('', 'rebase', None,
798 798 _("rebase working directory to branch head")))
799 799 entry[1].append(('t', 'tool', '',
800 800 _("specify merge tool for rebase")))
801 801 cmdutil.summaryhooks.add('rebase', summaryhook)
802 802 cmdutil.unfinishedstates.append(
803 ['rebasestate', False, _('rebase in progress'),
803 ['rebasestate', False, False, _('rebase in progress'),
804 804 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
@@ -1,692 +1,692 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 changes to another parent revision,
11 11 possibly in another repository. The transplant is done using 'diff' patches.
12 12
13 13 Transplanted patches are recorded in .hg/transplant/transplants, as a
14 14 map from a changeset hash to its hash in the source repository.
15 15 '''
16 16
17 17 from mercurial.i18n import _
18 18 import os, tempfile
19 19 from mercurial.node import short
20 20 from mercurial import bundlerepo, hg, merge, match
21 21 from mercurial import patch, revlog, scmutil, util, error, cmdutil
22 22 from mercurial import revset, templatekw
23 23
24 24 class TransplantError(error.Abort):
25 25 pass
26 26
27 27 cmdtable = {}
28 28 command = cmdutil.command(cmdtable)
29 29 testedwith = 'internal'
30 30
31 31 class transplantentry(object):
32 32 def __init__(self, lnode, rnode):
33 33 self.lnode = lnode
34 34 self.rnode = rnode
35 35
36 36 class transplants(object):
37 37 def __init__(self, path=None, transplantfile=None, opener=None):
38 38 self.path = path
39 39 self.transplantfile = transplantfile
40 40 self.opener = opener
41 41
42 42 if not opener:
43 43 self.opener = scmutil.opener(self.path)
44 44 self.transplants = {}
45 45 self.dirty = False
46 46 self.read()
47 47
48 48 def read(self):
49 49 abspath = os.path.join(self.path, self.transplantfile)
50 50 if self.transplantfile and os.path.exists(abspath):
51 51 for line in self.opener.read(self.transplantfile).splitlines():
52 52 lnode, rnode = map(revlog.bin, line.split(':'))
53 53 list = self.transplants.setdefault(rnode, [])
54 54 list.append(transplantentry(lnode, rnode))
55 55
56 56 def write(self):
57 57 if self.dirty and self.transplantfile:
58 58 if not os.path.isdir(self.path):
59 59 os.mkdir(self.path)
60 60 fp = self.opener(self.transplantfile, 'w')
61 61 for list in self.transplants.itervalues():
62 62 for t in list:
63 63 l, r = map(revlog.hex, (t.lnode, t.rnode))
64 64 fp.write(l + ':' + r + '\n')
65 65 fp.close()
66 66 self.dirty = False
67 67
68 68 def get(self, rnode):
69 69 return self.transplants.get(rnode) or []
70 70
71 71 def set(self, lnode, rnode):
72 72 list = self.transplants.setdefault(rnode, [])
73 73 list.append(transplantentry(lnode, rnode))
74 74 self.dirty = True
75 75
76 76 def remove(self, transplant):
77 77 list = self.transplants.get(transplant.rnode)
78 78 if list:
79 79 del list[list.index(transplant)]
80 80 self.dirty = True
81 81
82 82 class transplanter(object):
83 83 def __init__(self, ui, repo):
84 84 self.ui = ui
85 85 self.path = repo.join('transplant')
86 86 self.opener = scmutil.opener(self.path)
87 87 self.transplants = transplants(self.path, 'transplants',
88 88 opener=self.opener)
89 89 self.editor = None
90 90
91 91 def applied(self, repo, node, parent):
92 92 '''returns True if a node is already an ancestor of parent
93 93 or is parent or has already been transplanted'''
94 94 if hasnode(repo, parent):
95 95 parentrev = repo.changelog.rev(parent)
96 96 if hasnode(repo, node):
97 97 rev = repo.changelog.rev(node)
98 98 reachable = repo.changelog.ancestors([parentrev], rev,
99 99 inclusive=True)
100 100 if rev in reachable:
101 101 return True
102 102 for t in self.transplants.get(node):
103 103 # it might have been stripped
104 104 if not hasnode(repo, t.lnode):
105 105 self.transplants.remove(t)
106 106 return False
107 107 lnoderev = repo.changelog.rev(t.lnode)
108 108 if lnoderev in repo.changelog.ancestors([parentrev], lnoderev,
109 109 inclusive=True):
110 110 return True
111 111 return False
112 112
113 113 def apply(self, repo, source, revmap, merges, opts={}):
114 114 '''apply the revisions in revmap one by one in revision order'''
115 115 revs = sorted(revmap)
116 116 p1, p2 = repo.dirstate.parents()
117 117 pulls = []
118 118 diffopts = patch.diffopts(self.ui, opts)
119 119 diffopts.git = True
120 120
121 121 lock = wlock = tr = None
122 122 try:
123 123 wlock = repo.wlock()
124 124 lock = repo.lock()
125 125 tr = repo.transaction('transplant')
126 126 for rev in revs:
127 127 node = revmap[rev]
128 128 revstr = '%s:%s' % (rev, short(node))
129 129
130 130 if self.applied(repo, node, p1):
131 131 self.ui.warn(_('skipping already applied revision %s\n') %
132 132 revstr)
133 133 continue
134 134
135 135 parents = source.changelog.parents(node)
136 136 if not (opts.get('filter') or opts.get('log')):
137 137 # If the changeset parent is the same as the
138 138 # wdir's parent, just pull it.
139 139 if parents[0] == p1:
140 140 pulls.append(node)
141 141 p1 = node
142 142 continue
143 143 if pulls:
144 144 if source != repo:
145 145 repo.pull(source.peer(), heads=pulls)
146 146 merge.update(repo, pulls[-1], False, False, None)
147 147 p1, p2 = repo.dirstate.parents()
148 148 pulls = []
149 149
150 150 domerge = False
151 151 if node in merges:
152 152 # pulling all the merge revs at once would mean we
153 153 # couldn't transplant after the latest even if
154 154 # transplants before them fail.
155 155 domerge = True
156 156 if not hasnode(repo, node):
157 157 repo.pull(source, heads=[node])
158 158
159 159 skipmerge = False
160 160 if parents[1] != revlog.nullid:
161 161 if not opts.get('parent'):
162 162 self.ui.note(_('skipping merge changeset %s:%s\n')
163 163 % (rev, short(node)))
164 164 skipmerge = True
165 165 else:
166 166 parent = source.lookup(opts['parent'])
167 167 if parent not in parents:
168 168 raise util.Abort(_('%s is not a parent of %s') %
169 169 (short(parent), short(node)))
170 170 else:
171 171 parent = parents[0]
172 172
173 173 if skipmerge:
174 174 patchfile = None
175 175 else:
176 176 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
177 177 fp = os.fdopen(fd, 'w')
178 178 gen = patch.diff(source, parent, node, opts=diffopts)
179 179 for chunk in gen:
180 180 fp.write(chunk)
181 181 fp.close()
182 182
183 183 del revmap[rev]
184 184 if patchfile or domerge:
185 185 try:
186 186 try:
187 187 n = self.applyone(repo, node,
188 188 source.changelog.read(node),
189 189 patchfile, merge=domerge,
190 190 log=opts.get('log'),
191 191 filter=opts.get('filter'))
192 192 except TransplantError:
193 193 # Do not rollback, it is up to the user to
194 194 # fix the merge or cancel everything
195 195 tr.close()
196 196 raise
197 197 if n and domerge:
198 198 self.ui.status(_('%s merged at %s\n') % (revstr,
199 199 short(n)))
200 200 elif n:
201 201 self.ui.status(_('%s transplanted to %s\n')
202 202 % (short(node),
203 203 short(n)))
204 204 finally:
205 205 if patchfile:
206 206 os.unlink(patchfile)
207 207 tr.close()
208 208 if pulls:
209 209 repo.pull(source.peer(), heads=pulls)
210 210 merge.update(repo, pulls[-1], False, False, None)
211 211 finally:
212 212 self.saveseries(revmap, merges)
213 213 self.transplants.write()
214 214 if tr:
215 215 tr.release()
216 216 lock.release()
217 217 wlock.release()
218 218
219 219 def filter(self, filter, node, changelog, patchfile):
220 220 '''arbitrarily rewrite changeset before applying it'''
221 221
222 222 self.ui.status(_('filtering %s\n') % patchfile)
223 223 user, date, msg = (changelog[1], changelog[2], changelog[4])
224 224 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
225 225 fp = os.fdopen(fd, 'w')
226 226 fp.write("# HG changeset patch\n")
227 227 fp.write("# User %s\n" % user)
228 228 fp.write("# Date %d %d\n" % date)
229 229 fp.write(msg + '\n')
230 230 fp.close()
231 231
232 232 try:
233 233 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
234 234 util.shellquote(patchfile)),
235 235 environ={'HGUSER': changelog[1],
236 236 'HGREVISION': revlog.hex(node),
237 237 },
238 238 onerr=util.Abort, errprefix=_('filter failed'),
239 239 out=self.ui.fout)
240 240 user, date, msg = self.parselog(file(headerfile))[1:4]
241 241 finally:
242 242 os.unlink(headerfile)
243 243
244 244 return (user, date, msg)
245 245
246 246 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
247 247 filter=None):
248 248 '''apply the patch in patchfile to the repository as a transplant'''
249 249 (manifest, user, (time, timezone), files, message) = cl[:5]
250 250 date = "%d %d" % (time, timezone)
251 251 extra = {'transplant_source': node}
252 252 if filter:
253 253 (user, date, message) = self.filter(filter, node, cl, patchfile)
254 254
255 255 if log:
256 256 # we don't translate messages inserted into commits
257 257 message += '\n(transplanted from %s)' % revlog.hex(node)
258 258
259 259 self.ui.status(_('applying %s\n') % short(node))
260 260 self.ui.note('%s %s\n%s\n' % (user, date, message))
261 261
262 262 if not patchfile and not merge:
263 263 raise util.Abort(_('can only omit patchfile if merging'))
264 264 if patchfile:
265 265 try:
266 266 files = set()
267 267 patch.patch(self.ui, repo, patchfile, files=files, eolmode=None)
268 268 files = list(files)
269 269 except Exception, inst:
270 270 seriespath = os.path.join(self.path, 'series')
271 271 if os.path.exists(seriespath):
272 272 os.unlink(seriespath)
273 273 p1 = repo.dirstate.p1()
274 274 p2 = node
275 275 self.log(user, date, message, p1, p2, merge=merge)
276 276 self.ui.write(str(inst) + '\n')
277 277 raise TransplantError(_('fix up the merge and run '
278 278 'hg transplant --continue'))
279 279 else:
280 280 files = None
281 281 if merge:
282 282 p1, p2 = repo.dirstate.parents()
283 283 repo.setparents(p1, node)
284 284 m = match.always(repo.root, '')
285 285 else:
286 286 m = match.exact(repo.root, '', files)
287 287
288 288 n = repo.commit(message, user, date, extra=extra, match=m,
289 289 editor=self.editor)
290 290 if not n:
291 291 self.ui.warn(_('skipping emptied changeset %s\n') % short(node))
292 292 return None
293 293 if not merge:
294 294 self.transplants.set(n, node)
295 295
296 296 return n
297 297
298 298 def resume(self, repo, source, opts):
299 299 '''recover last transaction and apply remaining changesets'''
300 300 if os.path.exists(os.path.join(self.path, 'journal')):
301 301 n, node = self.recover(repo, source, opts)
302 302 self.ui.status(_('%s transplanted as %s\n') % (short(node),
303 303 short(n)))
304 304 seriespath = os.path.join(self.path, 'series')
305 305 if not os.path.exists(seriespath):
306 306 self.transplants.write()
307 307 return
308 308 nodes, merges = self.readseries()
309 309 revmap = {}
310 310 for n in nodes:
311 311 revmap[source.changelog.rev(n)] = n
312 312 os.unlink(seriespath)
313 313
314 314 self.apply(repo, source, revmap, merges, opts)
315 315
316 316 def recover(self, repo, source, opts):
317 317 '''commit working directory using journal metadata'''
318 318 node, user, date, message, parents = self.readlog()
319 319 merge = False
320 320
321 321 if not user or not date or not message or not parents[0]:
322 322 raise util.Abort(_('transplant log file is corrupt'))
323 323
324 324 parent = parents[0]
325 325 if len(parents) > 1:
326 326 if opts.get('parent'):
327 327 parent = source.lookup(opts['parent'])
328 328 if parent not in parents:
329 329 raise util.Abort(_('%s is not a parent of %s') %
330 330 (short(parent), short(node)))
331 331 else:
332 332 merge = True
333 333
334 334 extra = {'transplant_source': node}
335 335 wlock = repo.wlock()
336 336 try:
337 337 p1, p2 = repo.dirstate.parents()
338 338 if p1 != parent:
339 339 raise util.Abort(
340 340 _('working dir not at transplant parent %s') %
341 341 revlog.hex(parent))
342 342 if merge:
343 343 repo.setparents(p1, parents[1])
344 344 n = repo.commit(message, user, date, extra=extra,
345 345 editor=self.editor)
346 346 if not n:
347 347 raise util.Abort(_('commit failed'))
348 348 if not merge:
349 349 self.transplants.set(n, node)
350 350 self.unlog()
351 351
352 352 return n, node
353 353 finally:
354 354 wlock.release()
355 355
356 356 def readseries(self):
357 357 nodes = []
358 358 merges = []
359 359 cur = nodes
360 360 for line in self.opener.read('series').splitlines():
361 361 if line.startswith('# Merges'):
362 362 cur = merges
363 363 continue
364 364 cur.append(revlog.bin(line))
365 365
366 366 return (nodes, merges)
367 367
368 368 def saveseries(self, revmap, merges):
369 369 if not revmap:
370 370 return
371 371
372 372 if not os.path.isdir(self.path):
373 373 os.mkdir(self.path)
374 374 series = self.opener('series', 'w')
375 375 for rev in sorted(revmap):
376 376 series.write(revlog.hex(revmap[rev]) + '\n')
377 377 if merges:
378 378 series.write('# Merges\n')
379 379 for m in merges:
380 380 series.write(revlog.hex(m) + '\n')
381 381 series.close()
382 382
383 383 def parselog(self, fp):
384 384 parents = []
385 385 message = []
386 386 node = revlog.nullid
387 387 inmsg = False
388 388 user = None
389 389 date = None
390 390 for line in fp.read().splitlines():
391 391 if inmsg:
392 392 message.append(line)
393 393 elif line.startswith('# User '):
394 394 user = line[7:]
395 395 elif line.startswith('# Date '):
396 396 date = line[7:]
397 397 elif line.startswith('# Node ID '):
398 398 node = revlog.bin(line[10:])
399 399 elif line.startswith('# Parent '):
400 400 parents.append(revlog.bin(line[9:]))
401 401 elif not line.startswith('# '):
402 402 inmsg = True
403 403 message.append(line)
404 404 if None in (user, date):
405 405 raise util.Abort(_("filter corrupted changeset (no user or date)"))
406 406 return (node, user, date, '\n'.join(message), parents)
407 407
408 408 def log(self, user, date, message, p1, p2, merge=False):
409 409 '''journal changelog metadata for later recover'''
410 410
411 411 if not os.path.isdir(self.path):
412 412 os.mkdir(self.path)
413 413 fp = self.opener('journal', 'w')
414 414 fp.write('# User %s\n' % user)
415 415 fp.write('# Date %s\n' % date)
416 416 fp.write('# Node ID %s\n' % revlog.hex(p2))
417 417 fp.write('# Parent ' + revlog.hex(p1) + '\n')
418 418 if merge:
419 419 fp.write('# Parent ' + revlog.hex(p2) + '\n')
420 420 fp.write(message.rstrip() + '\n')
421 421 fp.close()
422 422
423 423 def readlog(self):
424 424 return self.parselog(self.opener('journal'))
425 425
426 426 def unlog(self):
427 427 '''remove changelog journal'''
428 428 absdst = os.path.join(self.path, 'journal')
429 429 if os.path.exists(absdst):
430 430 os.unlink(absdst)
431 431
432 432 def transplantfilter(self, repo, source, root):
433 433 def matchfn(node):
434 434 if self.applied(repo, node, root):
435 435 return False
436 436 if source.changelog.parents(node)[1] != revlog.nullid:
437 437 return False
438 438 extra = source.changelog.read(node)[5]
439 439 cnode = extra.get('transplant_source')
440 440 if cnode and self.applied(repo, cnode, root):
441 441 return False
442 442 return True
443 443
444 444 return matchfn
445 445
446 446 def hasnode(repo, node):
447 447 try:
448 448 return repo.changelog.rev(node) is not None
449 449 except error.RevlogError:
450 450 return False
451 451
452 452 def browserevs(ui, repo, nodes, opts):
453 453 '''interactively transplant changesets'''
454 454 def browsehelp(ui):
455 455 ui.write(_('y: transplant this changeset\n'
456 456 'n: skip this changeset\n'
457 457 'm: merge at this changeset\n'
458 458 'p: show patch\n'
459 459 'c: commit selected changesets\n'
460 460 'q: cancel transplant\n'
461 461 '?: show this help\n'))
462 462
463 463 displayer = cmdutil.show_changeset(ui, repo, opts)
464 464 transplants = []
465 465 merges = []
466 466 for node in nodes:
467 467 displayer.show(repo[node])
468 468 action = None
469 469 while not action:
470 470 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
471 471 if action == '?':
472 472 browsehelp(ui)
473 473 action = None
474 474 elif action == 'p':
475 475 parent = repo.changelog.parents(node)[0]
476 476 for chunk in patch.diff(repo, parent, node):
477 477 ui.write(chunk)
478 478 action = None
479 479 elif action not in ('y', 'n', 'm', 'c', 'q'):
480 480 ui.write(_('no such option\n'))
481 481 action = None
482 482 if action == 'y':
483 483 transplants.append(node)
484 484 elif action == 'm':
485 485 merges.append(node)
486 486 elif action == 'c':
487 487 break
488 488 elif action == 'q':
489 489 transplants = ()
490 490 merges = ()
491 491 break
492 492 displayer.close()
493 493 return (transplants, merges)
494 494
495 495 @command('transplant',
496 496 [('s', 'source', '', _('transplant changesets from REPO'), _('REPO')),
497 497 ('b', 'branch', [], _('use this source changeset as head'), _('REV')),
498 498 ('a', 'all', None, _('pull all changesets up to the --branch revisions')),
499 499 ('p', 'prune', [], _('skip over REV'), _('REV')),
500 500 ('m', 'merge', [], _('merge at REV'), _('REV')),
501 501 ('', 'parent', '',
502 502 _('parent to choose when transplanting merge'), _('REV')),
503 503 ('e', 'edit', False, _('invoke editor on commit messages')),
504 504 ('', 'log', None, _('append transplant info to log message')),
505 505 ('c', 'continue', None, _('continue last transplant session '
506 506 'after fixing conflicts')),
507 507 ('', 'filter', '',
508 508 _('filter changesets through command'), _('CMD'))],
509 509 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
510 510 '[-m REV] [REV]...'))
511 511 def transplant(ui, repo, *revs, **opts):
512 512 '''transplant changesets from another branch
513 513
514 514 Selected changesets will be applied on top of the current working
515 515 directory with the log of the original changeset. The changesets
516 516 are copied and will thus appear twice in the history with different
517 517 identities.
518 518
519 519 Consider using the graft command if everything is inside the same
520 520 repository - it will use merges and will usually give a better result.
521 521 Use the rebase extension if the changesets are unpublished and you want
522 522 to move them instead of copying them.
523 523
524 524 If --log is specified, log messages will have a comment appended
525 525 of the form::
526 526
527 527 (transplanted from CHANGESETHASH)
528 528
529 529 You can rewrite the changelog message with the --filter option.
530 530 Its argument will be invoked with the current changelog message as
531 531 $1 and the patch as $2.
532 532
533 533 --source/-s specifies another repository to use for selecting changesets,
534 534 just as if it temporarily had been pulled.
535 535 If --branch/-b is specified, these revisions will be used as
536 536 heads when deciding which changsets to transplant, just as if only
537 537 these revisions had been pulled.
538 538 If --all/-a is specified, all the revisions up to the heads specified
539 539 with --branch will be transplanted.
540 540
541 541 Example:
542 542
543 543 - transplant all changes up to REV on top of your current revision::
544 544
545 545 hg transplant --branch REV --all
546 546
547 547 You can optionally mark selected transplanted changesets as merge
548 548 changesets. You will not be prompted to transplant any ancestors
549 549 of a merged transplant, and you can merge descendants of them
550 550 normally instead of transplanting them.
551 551
552 552 Merge changesets may be transplanted directly by specifying the
553 553 proper parent changeset by calling :hg:`transplant --parent`.
554 554
555 555 If no merges or revisions are provided, :hg:`transplant` will
556 556 start an interactive changeset browser.
557 557
558 558 If a changeset application fails, you can fix the merge by hand
559 559 and then resume where you left off by calling :hg:`transplant
560 560 --continue/-c`.
561 561 '''
562 562 def incwalk(repo, csets, match=util.always):
563 563 for node in csets:
564 564 if match(node):
565 565 yield node
566 566
567 567 def transplantwalk(repo, dest, heads, match=util.always):
568 568 '''Yield all nodes that are ancestors of a head but not ancestors
569 569 of dest.
570 570 If no heads are specified, the heads of repo will be used.'''
571 571 if not heads:
572 572 heads = repo.heads()
573 573 ancestors = []
574 574 for head in heads:
575 575 ancestors.append(repo.changelog.ancestor(dest, head))
576 576 for node in repo.changelog.nodesbetween(ancestors, heads)[0]:
577 577 if match(node):
578 578 yield node
579 579
580 580 def checkopts(opts, revs):
581 581 if opts.get('continue'):
582 582 if opts.get('branch') or opts.get('all') or opts.get('merge'):
583 583 raise util.Abort(_('--continue is incompatible with '
584 584 '--branch, --all and --merge'))
585 585 return
586 586 if not (opts.get('source') or revs or
587 587 opts.get('merge') or opts.get('branch')):
588 588 raise util.Abort(_('no source URL, branch revision or revision '
589 589 'list provided'))
590 590 if opts.get('all'):
591 591 if not opts.get('branch'):
592 592 raise util.Abort(_('--all requires a branch revision'))
593 593 if revs:
594 594 raise util.Abort(_('--all is incompatible with a '
595 595 'revision list'))
596 596
597 597 checkopts(opts, revs)
598 598
599 599 if not opts.get('log'):
600 600 opts['log'] = ui.config('transplant', 'log')
601 601 if not opts.get('filter'):
602 602 opts['filter'] = ui.config('transplant', 'filter')
603 603
604 604 tp = transplanter(ui, repo)
605 605 if opts.get('edit'):
606 606 tp.editor = cmdutil.commitforceeditor
607 607
608 608 cmdutil.checkunfinished(repo)
609 609 p1, p2 = repo.dirstate.parents()
610 610 if len(repo) > 0 and p1 == revlog.nullid:
611 611 raise util.Abort(_('no revision checked out'))
612 612 if not opts.get('continue'):
613 613 if p2 != revlog.nullid:
614 614 raise util.Abort(_('outstanding uncommitted merges'))
615 615 m, a, r, d = repo.status()[:4]
616 616 if m or a or r or d:
617 617 raise util.Abort(_('outstanding local changes'))
618 618
619 619 sourcerepo = opts.get('source')
620 620 if sourcerepo:
621 621 peer = hg.peer(repo, opts, ui.expandpath(sourcerepo))
622 622 heads = map(peer.lookup, opts.get('branch', ()))
623 623 source, csets, cleanupfn = bundlerepo.getremotechanges(ui, repo, peer,
624 624 onlyheads=heads, force=True)
625 625 else:
626 626 source = repo
627 627 heads = map(source.lookup, opts.get('branch', ()))
628 628 cleanupfn = None
629 629
630 630 try:
631 631 if opts.get('continue'):
632 632 tp.resume(repo, source, opts)
633 633 return
634 634
635 635 tf = tp.transplantfilter(repo, source, p1)
636 636 if opts.get('prune'):
637 637 prune = set(source.lookup(r)
638 638 for r in scmutil.revrange(source, opts.get('prune')))
639 639 matchfn = lambda x: tf(x) and x not in prune
640 640 else:
641 641 matchfn = tf
642 642 merges = map(source.lookup, opts.get('merge', ()))
643 643 revmap = {}
644 644 if revs:
645 645 for r in scmutil.revrange(source, revs):
646 646 revmap[int(r)] = source.lookup(r)
647 647 elif opts.get('all') or not merges:
648 648 if source != repo:
649 649 alltransplants = incwalk(source, csets, match=matchfn)
650 650 else:
651 651 alltransplants = transplantwalk(source, p1, heads,
652 652 match=matchfn)
653 653 if opts.get('all'):
654 654 revs = alltransplants
655 655 else:
656 656 revs, newmerges = browserevs(ui, source, alltransplants, opts)
657 657 merges.extend(newmerges)
658 658 for r in revs:
659 659 revmap[source.changelog.rev(r)] = r
660 660 for r in merges:
661 661 revmap[source.changelog.rev(r)] = r
662 662
663 663 tp.apply(repo, source, revmap, merges, opts)
664 664 finally:
665 665 if cleanupfn:
666 666 cleanupfn()
667 667
668 668 def revsettransplanted(repo, subset, x):
669 669 """``transplanted([set])``
670 670 Transplanted changesets in set, or all transplanted changesets.
671 671 """
672 672 if x:
673 673 s = revset.getset(repo, subset, x)
674 674 else:
675 675 s = subset
676 676 return [r for r in s if repo[r].extra().get('transplant_source')]
677 677
678 678 def kwtransplanted(repo, ctx, **args):
679 679 """:transplanted: String. The node identifier of the transplanted
680 680 changeset if any."""
681 681 n = ctx.extra().get('transplant_source')
682 682 return n and revlog.hex(n) or ''
683 683
684 684 def extsetup(ui):
685 685 revset.symbols['transplanted'] = revsettransplanted
686 686 templatekw.keywords['transplanted'] = kwtransplanted
687 687 cmdutil.unfinishedstates.append(
688 ['series', True, _('transplant in progress'),
688 ['series', True, False, _('transplant in progress'),
689 689 _("use 'hg transplant --continue' or 'hg update' to abort")])
690 690
691 691 # tell hggettext to extract docstrings from these functions:
692 692 i18nfunctions = [revsettransplanted, kwtransplanted]
@@ -1,2136 +1,2138 b''
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.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 from node import hex, nullid, nullrev, short
9 9 from i18n import _
10 10 import os, sys, errno, re, tempfile
11 11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 12 import match as matchmod
13 13 import subrepo, context, repair, graphmod, revset, phases, obsolete
14 14 import changelog
15 15 import bookmarks
16 16 import lock as lockmod
17 17
18 18 def parsealiases(cmd):
19 19 return cmd.lstrip("^").split("|")
20 20
21 21 def findpossible(cmd, table, strict=False):
22 22 """
23 23 Return cmd -> (aliases, command table entry)
24 24 for each matching command.
25 25 Return debug commands (or their aliases) only if no normal command matches.
26 26 """
27 27 choice = {}
28 28 debugchoice = {}
29 29
30 30 if cmd in table:
31 31 # short-circuit exact matches, "log" alias beats "^log|history"
32 32 keys = [cmd]
33 33 else:
34 34 keys = table.keys()
35 35
36 36 for e in keys:
37 37 aliases = parsealiases(e)
38 38 found = None
39 39 if cmd in aliases:
40 40 found = cmd
41 41 elif not strict:
42 42 for a in aliases:
43 43 if a.startswith(cmd):
44 44 found = a
45 45 break
46 46 if found is not None:
47 47 if aliases[0].startswith("debug") or found.startswith("debug"):
48 48 debugchoice[found] = (aliases, table[e])
49 49 else:
50 50 choice[found] = (aliases, table[e])
51 51
52 52 if not choice and debugchoice:
53 53 choice = debugchoice
54 54
55 55 return choice
56 56
57 57 def findcmd(cmd, table, strict=True):
58 58 """Return (aliases, command table entry) for command string."""
59 59 choice = findpossible(cmd, table, strict)
60 60
61 61 if cmd in choice:
62 62 return choice[cmd]
63 63
64 64 if len(choice) > 1:
65 65 clist = choice.keys()
66 66 clist.sort()
67 67 raise error.AmbiguousCommand(cmd, clist)
68 68
69 69 if choice:
70 70 return choice.values()[0]
71 71
72 72 raise error.UnknownCommand(cmd)
73 73
74 74 def findrepo(p):
75 75 while not os.path.isdir(os.path.join(p, ".hg")):
76 76 oldp, p = p, os.path.dirname(p)
77 77 if p == oldp:
78 78 return None
79 79
80 80 return p
81 81
82 82 def bailifchanged(repo):
83 83 if repo.dirstate.p2() != nullid:
84 84 raise util.Abort(_('outstanding uncommitted merge'))
85 85 modified, added, removed, deleted = repo.status()[:4]
86 86 if modified or added or removed or deleted:
87 87 raise util.Abort(_("outstanding uncommitted changes"))
88 88 ctx = repo[None]
89 89 for s in sorted(ctx.substate):
90 90 if ctx.sub(s).dirty():
91 91 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
92 92
93 93 def logmessage(ui, opts):
94 94 """ get the log message according to -m and -l option """
95 95 message = opts.get('message')
96 96 logfile = opts.get('logfile')
97 97
98 98 if message and logfile:
99 99 raise util.Abort(_('options --message and --logfile are mutually '
100 100 'exclusive'))
101 101 if not message and logfile:
102 102 try:
103 103 if logfile == '-':
104 104 message = ui.fin.read()
105 105 else:
106 106 message = '\n'.join(util.readfile(logfile).splitlines())
107 107 except IOError, inst:
108 108 raise util.Abort(_("can't read commit message '%s': %s") %
109 109 (logfile, inst.strerror))
110 110 return message
111 111
112 112 def loglimit(opts):
113 113 """get the log limit according to option -l/--limit"""
114 114 limit = opts.get('limit')
115 115 if limit:
116 116 try:
117 117 limit = int(limit)
118 118 except ValueError:
119 119 raise util.Abort(_('limit must be a positive integer'))
120 120 if limit <= 0:
121 121 raise util.Abort(_('limit must be positive'))
122 122 else:
123 123 limit = None
124 124 return limit
125 125
126 126 def makefilename(repo, pat, node, desc=None,
127 127 total=None, seqno=None, revwidth=None, pathname=None):
128 128 node_expander = {
129 129 'H': lambda: hex(node),
130 130 'R': lambda: str(repo.changelog.rev(node)),
131 131 'h': lambda: short(node),
132 132 'm': lambda: re.sub('[^\w]', '_', str(desc))
133 133 }
134 134 expander = {
135 135 '%': lambda: '%',
136 136 'b': lambda: os.path.basename(repo.root),
137 137 }
138 138
139 139 try:
140 140 if node:
141 141 expander.update(node_expander)
142 142 if node:
143 143 expander['r'] = (lambda:
144 144 str(repo.changelog.rev(node)).zfill(revwidth or 0))
145 145 if total is not None:
146 146 expander['N'] = lambda: str(total)
147 147 if seqno is not None:
148 148 expander['n'] = lambda: str(seqno)
149 149 if total is not None and seqno is not None:
150 150 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
151 151 if pathname is not None:
152 152 expander['s'] = lambda: os.path.basename(pathname)
153 153 expander['d'] = lambda: os.path.dirname(pathname) or '.'
154 154 expander['p'] = lambda: pathname
155 155
156 156 newname = []
157 157 patlen = len(pat)
158 158 i = 0
159 159 while i < patlen:
160 160 c = pat[i]
161 161 if c == '%':
162 162 i += 1
163 163 c = pat[i]
164 164 c = expander[c]()
165 165 newname.append(c)
166 166 i += 1
167 167 return ''.join(newname)
168 168 except KeyError, inst:
169 169 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
170 170 inst.args[0])
171 171
172 172 def makefileobj(repo, pat, node=None, desc=None, total=None,
173 173 seqno=None, revwidth=None, mode='wb', modemap={},
174 174 pathname=None):
175 175
176 176 writable = mode not in ('r', 'rb')
177 177
178 178 if not pat or pat == '-':
179 179 fp = writable and repo.ui.fout or repo.ui.fin
180 180 if util.safehasattr(fp, 'fileno'):
181 181 return os.fdopen(os.dup(fp.fileno()), mode)
182 182 else:
183 183 # if this fp can't be duped properly, return
184 184 # a dummy object that can be closed
185 185 class wrappedfileobj(object):
186 186 noop = lambda x: None
187 187 def __init__(self, f):
188 188 self.f = f
189 189 def __getattr__(self, attr):
190 190 if attr == 'close':
191 191 return self.noop
192 192 else:
193 193 return getattr(self.f, attr)
194 194
195 195 return wrappedfileobj(fp)
196 196 if util.safehasattr(pat, 'write') and writable:
197 197 return pat
198 198 if util.safehasattr(pat, 'read') and 'r' in mode:
199 199 return pat
200 200 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
201 201 mode = modemap.get(fn, mode)
202 202 if mode == 'wb':
203 203 modemap[fn] = 'ab'
204 204 return open(fn, mode)
205 205
206 206 def openrevlog(repo, cmd, file_, opts):
207 207 """opens the changelog, manifest, a filelog or a given revlog"""
208 208 cl = opts['changelog']
209 209 mf = opts['manifest']
210 210 msg = None
211 211 if cl and mf:
212 212 msg = _('cannot specify --changelog and --manifest at the same time')
213 213 elif cl or mf:
214 214 if file_:
215 215 msg = _('cannot specify filename with --changelog or --manifest')
216 216 elif not repo:
217 217 msg = _('cannot specify --changelog or --manifest '
218 218 'without a repository')
219 219 if msg:
220 220 raise util.Abort(msg)
221 221
222 222 r = None
223 223 if repo:
224 224 if cl:
225 225 r = repo.changelog
226 226 elif mf:
227 227 r = repo.manifest
228 228 elif file_:
229 229 filelog = repo.file(file_)
230 230 if len(filelog):
231 231 r = filelog
232 232 if not r:
233 233 if not file_:
234 234 raise error.CommandError(cmd, _('invalid arguments'))
235 235 if not os.path.isfile(file_):
236 236 raise util.Abort(_("revlog '%s' not found") % file_)
237 237 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
238 238 file_[:-2] + ".i")
239 239 return r
240 240
241 241 def copy(ui, repo, pats, opts, rename=False):
242 242 # called with the repo lock held
243 243 #
244 244 # hgsep => pathname that uses "/" to separate directories
245 245 # ossep => pathname that uses os.sep to separate directories
246 246 cwd = repo.getcwd()
247 247 targets = {}
248 248 after = opts.get("after")
249 249 dryrun = opts.get("dry_run")
250 250 wctx = repo[None]
251 251
252 252 def walkpat(pat):
253 253 srcs = []
254 254 badstates = after and '?' or '?r'
255 255 m = scmutil.match(repo[None], [pat], opts, globbed=True)
256 256 for abs in repo.walk(m):
257 257 state = repo.dirstate[abs]
258 258 rel = m.rel(abs)
259 259 exact = m.exact(abs)
260 260 if state in badstates:
261 261 if exact and state == '?':
262 262 ui.warn(_('%s: not copying - file is not managed\n') % rel)
263 263 if exact and state == 'r':
264 264 ui.warn(_('%s: not copying - file has been marked for'
265 265 ' remove\n') % rel)
266 266 continue
267 267 # abs: hgsep
268 268 # rel: ossep
269 269 srcs.append((abs, rel, exact))
270 270 return srcs
271 271
272 272 # abssrc: hgsep
273 273 # relsrc: ossep
274 274 # otarget: ossep
275 275 def copyfile(abssrc, relsrc, otarget, exact):
276 276 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
277 277 if '/' in abstarget:
278 278 # We cannot normalize abstarget itself, this would prevent
279 279 # case only renames, like a => A.
280 280 abspath, absname = abstarget.rsplit('/', 1)
281 281 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
282 282 reltarget = repo.pathto(abstarget, cwd)
283 283 target = repo.wjoin(abstarget)
284 284 src = repo.wjoin(abssrc)
285 285 state = repo.dirstate[abstarget]
286 286
287 287 scmutil.checkportable(ui, abstarget)
288 288
289 289 # check for collisions
290 290 prevsrc = targets.get(abstarget)
291 291 if prevsrc is not None:
292 292 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
293 293 (reltarget, repo.pathto(abssrc, cwd),
294 294 repo.pathto(prevsrc, cwd)))
295 295 return
296 296
297 297 # check for overwrites
298 298 exists = os.path.lexists(target)
299 299 samefile = False
300 300 if exists and abssrc != abstarget:
301 301 if (repo.dirstate.normalize(abssrc) ==
302 302 repo.dirstate.normalize(abstarget)):
303 303 if not rename:
304 304 ui.warn(_("%s: can't copy - same file\n") % reltarget)
305 305 return
306 306 exists = False
307 307 samefile = True
308 308
309 309 if not after and exists or after and state in 'mn':
310 310 if not opts['force']:
311 311 ui.warn(_('%s: not overwriting - file exists\n') %
312 312 reltarget)
313 313 return
314 314
315 315 if after:
316 316 if not exists:
317 317 if rename:
318 318 ui.warn(_('%s: not recording move - %s does not exist\n') %
319 319 (relsrc, reltarget))
320 320 else:
321 321 ui.warn(_('%s: not recording copy - %s does not exist\n') %
322 322 (relsrc, reltarget))
323 323 return
324 324 elif not dryrun:
325 325 try:
326 326 if exists:
327 327 os.unlink(target)
328 328 targetdir = os.path.dirname(target) or '.'
329 329 if not os.path.isdir(targetdir):
330 330 os.makedirs(targetdir)
331 331 if samefile:
332 332 tmp = target + "~hgrename"
333 333 os.rename(src, tmp)
334 334 os.rename(tmp, target)
335 335 else:
336 336 util.copyfile(src, target)
337 337 srcexists = True
338 338 except IOError, inst:
339 339 if inst.errno == errno.ENOENT:
340 340 ui.warn(_('%s: deleted in working copy\n') % relsrc)
341 341 srcexists = False
342 342 else:
343 343 ui.warn(_('%s: cannot copy - %s\n') %
344 344 (relsrc, inst.strerror))
345 345 return True # report a failure
346 346
347 347 if ui.verbose or not exact:
348 348 if rename:
349 349 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
350 350 else:
351 351 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
352 352
353 353 targets[abstarget] = abssrc
354 354
355 355 # fix up dirstate
356 356 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
357 357 dryrun=dryrun, cwd=cwd)
358 358 if rename and not dryrun:
359 359 if not after and srcexists and not samefile:
360 360 util.unlinkpath(repo.wjoin(abssrc))
361 361 wctx.forget([abssrc])
362 362
363 363 # pat: ossep
364 364 # dest ossep
365 365 # srcs: list of (hgsep, hgsep, ossep, bool)
366 366 # return: function that takes hgsep and returns ossep
367 367 def targetpathfn(pat, dest, srcs):
368 368 if os.path.isdir(pat):
369 369 abspfx = scmutil.canonpath(repo.root, cwd, pat)
370 370 abspfx = util.localpath(abspfx)
371 371 if destdirexists:
372 372 striplen = len(os.path.split(abspfx)[0])
373 373 else:
374 374 striplen = len(abspfx)
375 375 if striplen:
376 376 striplen += len(os.sep)
377 377 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
378 378 elif destdirexists:
379 379 res = lambda p: os.path.join(dest,
380 380 os.path.basename(util.localpath(p)))
381 381 else:
382 382 res = lambda p: dest
383 383 return res
384 384
385 385 # pat: ossep
386 386 # dest ossep
387 387 # srcs: list of (hgsep, hgsep, ossep, bool)
388 388 # return: function that takes hgsep and returns ossep
389 389 def targetpathafterfn(pat, dest, srcs):
390 390 if matchmod.patkind(pat):
391 391 # a mercurial pattern
392 392 res = lambda p: os.path.join(dest,
393 393 os.path.basename(util.localpath(p)))
394 394 else:
395 395 abspfx = scmutil.canonpath(repo.root, cwd, pat)
396 396 if len(abspfx) < len(srcs[0][0]):
397 397 # A directory. Either the target path contains the last
398 398 # component of the source path or it does not.
399 399 def evalpath(striplen):
400 400 score = 0
401 401 for s in srcs:
402 402 t = os.path.join(dest, util.localpath(s[0])[striplen:])
403 403 if os.path.lexists(t):
404 404 score += 1
405 405 return score
406 406
407 407 abspfx = util.localpath(abspfx)
408 408 striplen = len(abspfx)
409 409 if striplen:
410 410 striplen += len(os.sep)
411 411 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
412 412 score = evalpath(striplen)
413 413 striplen1 = len(os.path.split(abspfx)[0])
414 414 if striplen1:
415 415 striplen1 += len(os.sep)
416 416 if evalpath(striplen1) > score:
417 417 striplen = striplen1
418 418 res = lambda p: os.path.join(dest,
419 419 util.localpath(p)[striplen:])
420 420 else:
421 421 # a file
422 422 if destdirexists:
423 423 res = lambda p: os.path.join(dest,
424 424 os.path.basename(util.localpath(p)))
425 425 else:
426 426 res = lambda p: dest
427 427 return res
428 428
429 429
430 430 pats = scmutil.expandpats(pats)
431 431 if not pats:
432 432 raise util.Abort(_('no source or destination specified'))
433 433 if len(pats) == 1:
434 434 raise util.Abort(_('no destination specified'))
435 435 dest = pats.pop()
436 436 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
437 437 if not destdirexists:
438 438 if len(pats) > 1 or matchmod.patkind(pats[0]):
439 439 raise util.Abort(_('with multiple sources, destination must be an '
440 440 'existing directory'))
441 441 if util.endswithsep(dest):
442 442 raise util.Abort(_('destination %s is not a directory') % dest)
443 443
444 444 tfn = targetpathfn
445 445 if after:
446 446 tfn = targetpathafterfn
447 447 copylist = []
448 448 for pat in pats:
449 449 srcs = walkpat(pat)
450 450 if not srcs:
451 451 continue
452 452 copylist.append((tfn(pat, dest, srcs), srcs))
453 453 if not copylist:
454 454 raise util.Abort(_('no files to copy'))
455 455
456 456 errors = 0
457 457 for targetpath, srcs in copylist:
458 458 for abssrc, relsrc, exact in srcs:
459 459 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
460 460 errors += 1
461 461
462 462 if errors:
463 463 ui.warn(_('(consider using --after)\n'))
464 464
465 465 return errors != 0
466 466
467 467 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
468 468 runargs=None, appendpid=False):
469 469 '''Run a command as a service.'''
470 470
471 471 if opts['daemon'] and not opts['daemon_pipefds']:
472 472 # Signal child process startup with file removal
473 473 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
474 474 os.close(lockfd)
475 475 try:
476 476 if not runargs:
477 477 runargs = util.hgcmd() + sys.argv[1:]
478 478 runargs.append('--daemon-pipefds=%s' % lockpath)
479 479 # Don't pass --cwd to the child process, because we've already
480 480 # changed directory.
481 481 for i in xrange(1, len(runargs)):
482 482 if runargs[i].startswith('--cwd='):
483 483 del runargs[i]
484 484 break
485 485 elif runargs[i].startswith('--cwd'):
486 486 del runargs[i:i + 2]
487 487 break
488 488 def condfn():
489 489 return not os.path.exists(lockpath)
490 490 pid = util.rundetached(runargs, condfn)
491 491 if pid < 0:
492 492 raise util.Abort(_('child process failed to start'))
493 493 finally:
494 494 try:
495 495 os.unlink(lockpath)
496 496 except OSError, e:
497 497 if e.errno != errno.ENOENT:
498 498 raise
499 499 if parentfn:
500 500 return parentfn(pid)
501 501 else:
502 502 return
503 503
504 504 if initfn:
505 505 initfn()
506 506
507 507 if opts['pid_file']:
508 508 mode = appendpid and 'a' or 'w'
509 509 fp = open(opts['pid_file'], mode)
510 510 fp.write(str(os.getpid()) + '\n')
511 511 fp.close()
512 512
513 513 if opts['daemon_pipefds']:
514 514 lockpath = opts['daemon_pipefds']
515 515 try:
516 516 os.setsid()
517 517 except AttributeError:
518 518 pass
519 519 os.unlink(lockpath)
520 520 util.hidewindow()
521 521 sys.stdout.flush()
522 522 sys.stderr.flush()
523 523
524 524 nullfd = os.open(os.devnull, os.O_RDWR)
525 525 logfilefd = nullfd
526 526 if logfile:
527 527 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
528 528 os.dup2(nullfd, 0)
529 529 os.dup2(logfilefd, 1)
530 530 os.dup2(logfilefd, 2)
531 531 if nullfd not in (0, 1, 2):
532 532 os.close(nullfd)
533 533 if logfile and logfilefd not in (0, 1, 2):
534 534 os.close(logfilefd)
535 535
536 536 if runfn:
537 537 return runfn()
538 538
539 539 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
540 540 opts=None):
541 541 '''export changesets as hg patches.'''
542 542
543 543 total = len(revs)
544 544 revwidth = max([len(str(rev)) for rev in revs])
545 545 filemode = {}
546 546
547 547 def single(rev, seqno, fp):
548 548 ctx = repo[rev]
549 549 node = ctx.node()
550 550 parents = [p.node() for p in ctx.parents() if p]
551 551 branch = ctx.branch()
552 552 if switch_parent:
553 553 parents.reverse()
554 554 prev = (parents and parents[0]) or nullid
555 555
556 556 shouldclose = False
557 557 if not fp and len(template) > 0:
558 558 desc_lines = ctx.description().rstrip().split('\n')
559 559 desc = desc_lines[0] #Commit always has a first line.
560 560 fp = makefileobj(repo, template, node, desc=desc, total=total,
561 561 seqno=seqno, revwidth=revwidth, mode='wb',
562 562 modemap=filemode)
563 563 if fp != template:
564 564 shouldclose = True
565 565 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
566 566 repo.ui.note("%s\n" % fp.name)
567 567
568 568 if not fp:
569 569 write = repo.ui.write
570 570 else:
571 571 def write(s, **kw):
572 572 fp.write(s)
573 573
574 574
575 575 write("# HG changeset patch\n")
576 576 write("# User %s\n" % ctx.user())
577 577 write("# Date %d %d\n" % ctx.date())
578 578 write("# %s\n" % util.datestr(ctx.date()))
579 579 if branch and branch != 'default':
580 580 write("# Branch %s\n" % branch)
581 581 write("# Node ID %s\n" % hex(node))
582 582 write("# Parent %s\n" % hex(prev))
583 583 if len(parents) > 1:
584 584 write("# Parent %s\n" % hex(parents[1]))
585 585 write(ctx.description().rstrip())
586 586 write("\n\n")
587 587
588 588 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
589 589 write(chunk, label=label)
590 590
591 591 if shouldclose:
592 592 fp.close()
593 593
594 594 for seqno, rev in enumerate(revs):
595 595 single(rev, seqno + 1, fp)
596 596
597 597 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
598 598 changes=None, stat=False, fp=None, prefix='',
599 599 listsubrepos=False):
600 600 '''show diff or diffstat.'''
601 601 if fp is None:
602 602 write = ui.write
603 603 else:
604 604 def write(s, **kw):
605 605 fp.write(s)
606 606
607 607 if stat:
608 608 diffopts = diffopts.copy(context=0)
609 609 width = 80
610 610 if not ui.plain():
611 611 width = ui.termwidth()
612 612 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
613 613 prefix=prefix)
614 614 for chunk, label in patch.diffstatui(util.iterlines(chunks),
615 615 width=width,
616 616 git=diffopts.git):
617 617 write(chunk, label=label)
618 618 else:
619 619 for chunk, label in patch.diffui(repo, node1, node2, match,
620 620 changes, diffopts, prefix=prefix):
621 621 write(chunk, label=label)
622 622
623 623 if listsubrepos:
624 624 ctx1 = repo[node1]
625 625 ctx2 = repo[node2]
626 626 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
627 627 tempnode2 = node2
628 628 try:
629 629 if node2 is not None:
630 630 tempnode2 = ctx2.substate[subpath][1]
631 631 except KeyError:
632 632 # A subrepo that existed in node1 was deleted between node1 and
633 633 # node2 (inclusive). Thus, ctx2's substate won't contain that
634 634 # subpath. The best we can do is to ignore it.
635 635 tempnode2 = None
636 636 submatch = matchmod.narrowmatcher(subpath, match)
637 637 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
638 638 stat=stat, fp=fp, prefix=prefix)
639 639
640 640 class changeset_printer(object):
641 641 '''show changeset information when templating not requested.'''
642 642
643 643 def __init__(self, ui, repo, patch, diffopts, buffered):
644 644 self.ui = ui
645 645 self.repo = repo
646 646 self.buffered = buffered
647 647 self.patch = patch
648 648 self.diffopts = diffopts
649 649 self.header = {}
650 650 self.hunk = {}
651 651 self.lastheader = None
652 652 self.footer = None
653 653
654 654 def flush(self, rev):
655 655 if rev in self.header:
656 656 h = self.header[rev]
657 657 if h != self.lastheader:
658 658 self.lastheader = h
659 659 self.ui.write(h)
660 660 del self.header[rev]
661 661 if rev in self.hunk:
662 662 self.ui.write(self.hunk[rev])
663 663 del self.hunk[rev]
664 664 return 1
665 665 return 0
666 666
667 667 def close(self):
668 668 if self.footer:
669 669 self.ui.write(self.footer)
670 670
671 671 def show(self, ctx, copies=None, matchfn=None, **props):
672 672 if self.buffered:
673 673 self.ui.pushbuffer()
674 674 self._show(ctx, copies, matchfn, props)
675 675 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
676 676 else:
677 677 self._show(ctx, copies, matchfn, props)
678 678
679 679 def _show(self, ctx, copies, matchfn, props):
680 680 '''show a single changeset or file revision'''
681 681 changenode = ctx.node()
682 682 rev = ctx.rev()
683 683
684 684 if self.ui.quiet:
685 685 self.ui.write("%d:%s\n" % (rev, short(changenode)),
686 686 label='log.node')
687 687 return
688 688
689 689 log = self.repo.changelog
690 690 date = util.datestr(ctx.date())
691 691
692 692 hexfunc = self.ui.debugflag and hex or short
693 693
694 694 parents = [(p, hexfunc(log.node(p)))
695 695 for p in self._meaningful_parentrevs(log, rev)]
696 696
697 697 # i18n: column positioning for "hg log"
698 698 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
699 699 label='log.changeset changeset.%s' % ctx.phasestr())
700 700
701 701 branch = ctx.branch()
702 702 # don't show the default branch name
703 703 if branch != 'default':
704 704 # i18n: column positioning for "hg log"
705 705 self.ui.write(_("branch: %s\n") % branch,
706 706 label='log.branch')
707 707 for bookmark in self.repo.nodebookmarks(changenode):
708 708 # i18n: column positioning for "hg log"
709 709 self.ui.write(_("bookmark: %s\n") % bookmark,
710 710 label='log.bookmark')
711 711 for tag in self.repo.nodetags(changenode):
712 712 # i18n: column positioning for "hg log"
713 713 self.ui.write(_("tag: %s\n") % tag,
714 714 label='log.tag')
715 715 if self.ui.debugflag and ctx.phase():
716 716 # i18n: column positioning for "hg log"
717 717 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
718 718 label='log.phase')
719 719 for parent in parents:
720 720 # i18n: column positioning for "hg log"
721 721 self.ui.write(_("parent: %d:%s\n") % parent,
722 722 label='log.parent changeset.%s' % ctx.phasestr())
723 723
724 724 if self.ui.debugflag:
725 725 mnode = ctx.manifestnode()
726 726 # i18n: column positioning for "hg log"
727 727 self.ui.write(_("manifest: %d:%s\n") %
728 728 (self.repo.manifest.rev(mnode), hex(mnode)),
729 729 label='ui.debug log.manifest')
730 730 # i18n: column positioning for "hg log"
731 731 self.ui.write(_("user: %s\n") % ctx.user(),
732 732 label='log.user')
733 733 # i18n: column positioning for "hg log"
734 734 self.ui.write(_("date: %s\n") % date,
735 735 label='log.date')
736 736
737 737 if self.ui.debugflag:
738 738 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
739 739 for key, value in zip([# i18n: column positioning for "hg log"
740 740 _("files:"),
741 741 # i18n: column positioning for "hg log"
742 742 _("files+:"),
743 743 # i18n: column positioning for "hg log"
744 744 _("files-:")], files):
745 745 if value:
746 746 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
747 747 label='ui.debug log.files')
748 748 elif ctx.files() and self.ui.verbose:
749 749 # i18n: column positioning for "hg log"
750 750 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
751 751 label='ui.note log.files')
752 752 if copies and self.ui.verbose:
753 753 copies = ['%s (%s)' % c for c in copies]
754 754 # i18n: column positioning for "hg log"
755 755 self.ui.write(_("copies: %s\n") % ' '.join(copies),
756 756 label='ui.note log.copies')
757 757
758 758 extra = ctx.extra()
759 759 if extra and self.ui.debugflag:
760 760 for key, value in sorted(extra.items()):
761 761 # i18n: column positioning for "hg log"
762 762 self.ui.write(_("extra: %s=%s\n")
763 763 % (key, value.encode('string_escape')),
764 764 label='ui.debug log.extra')
765 765
766 766 description = ctx.description().strip()
767 767 if description:
768 768 if self.ui.verbose:
769 769 self.ui.write(_("description:\n"),
770 770 label='ui.note log.description')
771 771 self.ui.write(description,
772 772 label='ui.note log.description')
773 773 self.ui.write("\n\n")
774 774 else:
775 775 # i18n: column positioning for "hg log"
776 776 self.ui.write(_("summary: %s\n") %
777 777 description.splitlines()[0],
778 778 label='log.summary')
779 779 self.ui.write("\n")
780 780
781 781 self.showpatch(changenode, matchfn)
782 782
783 783 def showpatch(self, node, matchfn):
784 784 if not matchfn:
785 785 matchfn = self.patch
786 786 if matchfn:
787 787 stat = self.diffopts.get('stat')
788 788 diff = self.diffopts.get('patch')
789 789 diffopts = patch.diffopts(self.ui, self.diffopts)
790 790 prev = self.repo.changelog.parents(node)[0]
791 791 if stat:
792 792 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
793 793 match=matchfn, stat=True)
794 794 if diff:
795 795 if stat:
796 796 self.ui.write("\n")
797 797 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
798 798 match=matchfn, stat=False)
799 799 self.ui.write("\n")
800 800
801 801 def _meaningful_parentrevs(self, log, rev):
802 802 """Return list of meaningful (or all if debug) parentrevs for rev.
803 803
804 804 For merges (two non-nullrev revisions) both parents are meaningful.
805 805 Otherwise the first parent revision is considered meaningful if it
806 806 is not the preceding revision.
807 807 """
808 808 parents = log.parentrevs(rev)
809 809 if not self.ui.debugflag and parents[1] == nullrev:
810 810 if parents[0] >= rev - 1:
811 811 parents = []
812 812 else:
813 813 parents = [parents[0]]
814 814 return parents
815 815
816 816
817 817 class changeset_templater(changeset_printer):
818 818 '''format changeset information.'''
819 819
820 820 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
821 821 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
822 822 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
823 823 defaulttempl = {
824 824 'parent': '{rev}:{node|formatnode} ',
825 825 'manifest': '{rev}:{node|formatnode}',
826 826 'file_copy': '{name} ({source})',
827 827 'extra': '{key}={value|stringescape}'
828 828 }
829 829 # filecopy is preserved for compatibility reasons
830 830 defaulttempl['filecopy'] = defaulttempl['file_copy']
831 831 self.t = templater.templater(mapfile, {'formatnode': formatnode},
832 832 cache=defaulttempl)
833 833 self.cache = {}
834 834
835 835 def use_template(self, t):
836 836 '''set template string to use'''
837 837 self.t.cache['changeset'] = t
838 838
839 839 def _meaningful_parentrevs(self, ctx):
840 840 """Return list of meaningful (or all if debug) parentrevs for rev.
841 841 """
842 842 parents = ctx.parents()
843 843 if len(parents) > 1:
844 844 return parents
845 845 if self.ui.debugflag:
846 846 return [parents[0], self.repo['null']]
847 847 if parents[0].rev() >= ctx.rev() - 1:
848 848 return []
849 849 return parents
850 850
851 851 def _show(self, ctx, copies, matchfn, props):
852 852 '''show a single changeset or file revision'''
853 853
854 854 showlist = templatekw.showlist
855 855
856 856 # showparents() behaviour depends on ui trace level which
857 857 # causes unexpected behaviours at templating level and makes
858 858 # it harder to extract it in a standalone function. Its
859 859 # behaviour cannot be changed so leave it here for now.
860 860 def showparents(**args):
861 861 ctx = args['ctx']
862 862 parents = [[('rev', p.rev()), ('node', p.hex())]
863 863 for p in self._meaningful_parentrevs(ctx)]
864 864 return showlist('parent', parents, **args)
865 865
866 866 props = props.copy()
867 867 props.update(templatekw.keywords)
868 868 props['parents'] = showparents
869 869 props['templ'] = self.t
870 870 props['ctx'] = ctx
871 871 props['repo'] = self.repo
872 872 props['revcache'] = {'copies': copies}
873 873 props['cache'] = self.cache
874 874
875 875 # find correct templates for current mode
876 876
877 877 tmplmodes = [
878 878 (True, None),
879 879 (self.ui.verbose, 'verbose'),
880 880 (self.ui.quiet, 'quiet'),
881 881 (self.ui.debugflag, 'debug'),
882 882 ]
883 883
884 884 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
885 885 for mode, postfix in tmplmodes:
886 886 for type in types:
887 887 cur = postfix and ('%s_%s' % (type, postfix)) or type
888 888 if mode and cur in self.t:
889 889 types[type] = cur
890 890
891 891 try:
892 892
893 893 # write header
894 894 if types['header']:
895 895 h = templater.stringify(self.t(types['header'], **props))
896 896 if self.buffered:
897 897 self.header[ctx.rev()] = h
898 898 else:
899 899 if self.lastheader != h:
900 900 self.lastheader = h
901 901 self.ui.write(h)
902 902
903 903 # write changeset metadata, then patch if requested
904 904 key = types['changeset']
905 905 self.ui.write(templater.stringify(self.t(key, **props)))
906 906 self.showpatch(ctx.node(), matchfn)
907 907
908 908 if types['footer']:
909 909 if not self.footer:
910 910 self.footer = templater.stringify(self.t(types['footer'],
911 911 **props))
912 912
913 913 except KeyError, inst:
914 914 msg = _("%s: no key named '%s'")
915 915 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
916 916 except SyntaxError, inst:
917 917 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
918 918
919 919 def show_changeset(ui, repo, opts, buffered=False):
920 920 """show one changeset using template or regular display.
921 921
922 922 Display format will be the first non-empty hit of:
923 923 1. option 'template'
924 924 2. option 'style'
925 925 3. [ui] setting 'logtemplate'
926 926 4. [ui] setting 'style'
927 927 If all of these values are either the unset or the empty string,
928 928 regular display via changeset_printer() is done.
929 929 """
930 930 # options
931 931 patch = False
932 932 if opts.get('patch') or opts.get('stat'):
933 933 patch = scmutil.matchall(repo)
934 934
935 935 tmpl = opts.get('template')
936 936 style = None
937 937 if tmpl:
938 938 tmpl = templater.parsestring(tmpl, quoted=False)
939 939 else:
940 940 style = opts.get('style')
941 941
942 942 # ui settings
943 943 if not (tmpl or style):
944 944 tmpl = ui.config('ui', 'logtemplate')
945 945 if tmpl:
946 946 try:
947 947 tmpl = templater.parsestring(tmpl)
948 948 except SyntaxError:
949 949 tmpl = templater.parsestring(tmpl, quoted=False)
950 950 else:
951 951 style = util.expandpath(ui.config('ui', 'style', ''))
952 952
953 953 if not (tmpl or style):
954 954 return changeset_printer(ui, repo, patch, opts, buffered)
955 955
956 956 mapfile = None
957 957 if style and not tmpl:
958 958 mapfile = style
959 959 if not os.path.split(mapfile)[0]:
960 960 mapname = (templater.templatepath('map-cmdline.' + mapfile)
961 961 or templater.templatepath(mapfile))
962 962 if mapname:
963 963 mapfile = mapname
964 964
965 965 try:
966 966 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
967 967 except SyntaxError, inst:
968 968 raise util.Abort(inst.args[0])
969 969 if tmpl:
970 970 t.use_template(tmpl)
971 971 return t
972 972
973 973 def finddate(ui, repo, date):
974 974 """Find the tipmost changeset that matches the given date spec"""
975 975
976 976 df = util.matchdate(date)
977 977 m = scmutil.matchall(repo)
978 978 results = {}
979 979
980 980 def prep(ctx, fns):
981 981 d = ctx.date()
982 982 if df(d[0]):
983 983 results[ctx.rev()] = d
984 984
985 985 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
986 986 rev = ctx.rev()
987 987 if rev in results:
988 988 ui.status(_("found revision %s from %s\n") %
989 989 (rev, util.datestr(results[rev])))
990 990 return str(rev)
991 991
992 992 raise util.Abort(_("revision matching date not found"))
993 993
994 994 def increasingwindows(start, end, windowsize=8, sizelimit=512):
995 995 if start < end:
996 996 while start < end:
997 997 yield start, min(windowsize, end - start)
998 998 start += windowsize
999 999 if windowsize < sizelimit:
1000 1000 windowsize *= 2
1001 1001 else:
1002 1002 while start > end:
1003 1003 yield start, min(windowsize, start - end - 1)
1004 1004 start -= windowsize
1005 1005 if windowsize < sizelimit:
1006 1006 windowsize *= 2
1007 1007
1008 1008 class FileWalkError(Exception):
1009 1009 pass
1010 1010
1011 1011 def walkfilerevs(repo, match, follow, revs, fncache):
1012 1012 '''Walks the file history for the matched files.
1013 1013
1014 1014 Returns the changeset revs that are involved in the file history.
1015 1015
1016 1016 Throws FileWalkError if the file history can't be walked using
1017 1017 filelogs alone.
1018 1018 '''
1019 1019 wanted = set()
1020 1020 copies = []
1021 1021 minrev, maxrev = min(revs), max(revs)
1022 1022 def filerevgen(filelog, last):
1023 1023 """
1024 1024 Only files, no patterns. Check the history of each file.
1025 1025
1026 1026 Examines filelog entries within minrev, maxrev linkrev range
1027 1027 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1028 1028 tuples in backwards order
1029 1029 """
1030 1030 cl_count = len(repo)
1031 1031 revs = []
1032 1032 for j in xrange(0, last + 1):
1033 1033 linkrev = filelog.linkrev(j)
1034 1034 if linkrev < minrev:
1035 1035 continue
1036 1036 # only yield rev for which we have the changelog, it can
1037 1037 # happen while doing "hg log" during a pull or commit
1038 1038 if linkrev >= cl_count:
1039 1039 break
1040 1040
1041 1041 parentlinkrevs = []
1042 1042 for p in filelog.parentrevs(j):
1043 1043 if p != nullrev:
1044 1044 parentlinkrevs.append(filelog.linkrev(p))
1045 1045 n = filelog.node(j)
1046 1046 revs.append((linkrev, parentlinkrevs,
1047 1047 follow and filelog.renamed(n)))
1048 1048
1049 1049 return reversed(revs)
1050 1050 def iterfiles():
1051 1051 pctx = repo['.']
1052 1052 for filename in match.files():
1053 1053 if follow:
1054 1054 if filename not in pctx:
1055 1055 raise util.Abort(_('cannot follow file not in parent '
1056 1056 'revision: "%s"') % filename)
1057 1057 yield filename, pctx[filename].filenode()
1058 1058 else:
1059 1059 yield filename, None
1060 1060 for filename_node in copies:
1061 1061 yield filename_node
1062 1062
1063 1063 for file_, node in iterfiles():
1064 1064 filelog = repo.file(file_)
1065 1065 if not len(filelog):
1066 1066 if node is None:
1067 1067 # A zero count may be a directory or deleted file, so
1068 1068 # try to find matching entries on the slow path.
1069 1069 if follow:
1070 1070 raise util.Abort(
1071 1071 _('cannot follow nonexistent file: "%s"') % file_)
1072 1072 raise FileWalkError("Cannot walk via filelog")
1073 1073 else:
1074 1074 continue
1075 1075
1076 1076 if node is None:
1077 1077 last = len(filelog) - 1
1078 1078 else:
1079 1079 last = filelog.rev(node)
1080 1080
1081 1081
1082 1082 # keep track of all ancestors of the file
1083 1083 ancestors = set([filelog.linkrev(last)])
1084 1084
1085 1085 # iterate from latest to oldest revision
1086 1086 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1087 1087 if not follow:
1088 1088 if rev > maxrev:
1089 1089 continue
1090 1090 else:
1091 1091 # Note that last might not be the first interesting
1092 1092 # rev to us:
1093 1093 # if the file has been changed after maxrev, we'll
1094 1094 # have linkrev(last) > maxrev, and we still need
1095 1095 # to explore the file graph
1096 1096 if rev not in ancestors:
1097 1097 continue
1098 1098 # XXX insert 1327 fix here
1099 1099 if flparentlinkrevs:
1100 1100 ancestors.update(flparentlinkrevs)
1101 1101
1102 1102 fncache.setdefault(rev, []).append(file_)
1103 1103 wanted.add(rev)
1104 1104 if copied:
1105 1105 copies.append(copied)
1106 1106
1107 1107 return wanted
1108 1108
1109 1109 def walkchangerevs(repo, match, opts, prepare):
1110 1110 '''Iterate over files and the revs in which they changed.
1111 1111
1112 1112 Callers most commonly need to iterate backwards over the history
1113 1113 in which they are interested. Doing so has awful (quadratic-looking)
1114 1114 performance, so we use iterators in a "windowed" way.
1115 1115
1116 1116 We walk a window of revisions in the desired order. Within the
1117 1117 window, we first walk forwards to gather data, then in the desired
1118 1118 order (usually backwards) to display it.
1119 1119
1120 1120 This function returns an iterator yielding contexts. Before
1121 1121 yielding each context, the iterator will first call the prepare
1122 1122 function on each context in the window in forward order.'''
1123 1123
1124 1124 follow = opts.get('follow') or opts.get('follow_first')
1125 1125
1126 1126 if opts.get('rev'):
1127 1127 revs = scmutil.revrange(repo, opts.get('rev'))
1128 1128 elif follow:
1129 1129 revs = repo.revs('reverse(:.)')
1130 1130 else:
1131 1131 revs = list(repo)
1132 1132 revs.reverse()
1133 1133 if not revs:
1134 1134 return []
1135 1135 wanted = set()
1136 1136 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1137 1137 fncache = {}
1138 1138 change = repo.changectx
1139 1139
1140 1140 # First step is to fill wanted, the set of revisions that we want to yield.
1141 1141 # When it does not induce extra cost, we also fill fncache for revisions in
1142 1142 # wanted: a cache of filenames that were changed (ctx.files()) and that
1143 1143 # match the file filtering conditions.
1144 1144
1145 1145 if not slowpath and not match.files():
1146 1146 # No files, no patterns. Display all revs.
1147 1147 wanted = set(revs)
1148 1148
1149 1149 if not slowpath and match.files():
1150 1150 # We only have to read through the filelog to find wanted revisions
1151 1151
1152 1152 try:
1153 1153 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1154 1154 except FileWalkError:
1155 1155 slowpath = True
1156 1156
1157 1157 # We decided to fall back to the slowpath because at least one
1158 1158 # of the paths was not a file. Check to see if at least one of them
1159 1159 # existed in history, otherwise simply return
1160 1160 for path in match.files():
1161 1161 if path == '.' or path in repo.store:
1162 1162 break
1163 1163 else:
1164 1164 return []
1165 1165
1166 1166 if slowpath:
1167 1167 # We have to read the changelog to match filenames against
1168 1168 # changed files
1169 1169
1170 1170 if follow:
1171 1171 raise util.Abort(_('can only follow copies/renames for explicit '
1172 1172 'filenames'))
1173 1173
1174 1174 # The slow path checks files modified in every changeset.
1175 1175 for i in sorted(revs):
1176 1176 ctx = change(i)
1177 1177 matches = filter(match, ctx.files())
1178 1178 if matches:
1179 1179 fncache[i] = matches
1180 1180 wanted.add(i)
1181 1181
1182 1182 class followfilter(object):
1183 1183 def __init__(self, onlyfirst=False):
1184 1184 self.startrev = nullrev
1185 1185 self.roots = set()
1186 1186 self.onlyfirst = onlyfirst
1187 1187
1188 1188 def match(self, rev):
1189 1189 def realparents(rev):
1190 1190 if self.onlyfirst:
1191 1191 return repo.changelog.parentrevs(rev)[0:1]
1192 1192 else:
1193 1193 return filter(lambda x: x != nullrev,
1194 1194 repo.changelog.parentrevs(rev))
1195 1195
1196 1196 if self.startrev == nullrev:
1197 1197 self.startrev = rev
1198 1198 return True
1199 1199
1200 1200 if rev > self.startrev:
1201 1201 # forward: all descendants
1202 1202 if not self.roots:
1203 1203 self.roots.add(self.startrev)
1204 1204 for parent in realparents(rev):
1205 1205 if parent in self.roots:
1206 1206 self.roots.add(rev)
1207 1207 return True
1208 1208 else:
1209 1209 # backwards: all parents
1210 1210 if not self.roots:
1211 1211 self.roots.update(realparents(self.startrev))
1212 1212 if rev in self.roots:
1213 1213 self.roots.remove(rev)
1214 1214 self.roots.update(realparents(rev))
1215 1215 return True
1216 1216
1217 1217 return False
1218 1218
1219 1219 # it might be worthwhile to do this in the iterator if the rev range
1220 1220 # is descending and the prune args are all within that range
1221 1221 for rev in opts.get('prune', ()):
1222 1222 rev = repo[rev].rev()
1223 1223 ff = followfilter()
1224 1224 stop = min(revs[0], revs[-1])
1225 1225 for x in xrange(rev, stop - 1, -1):
1226 1226 if ff.match(x):
1227 1227 wanted.discard(x)
1228 1228
1229 1229 # Choose a small initial window if we will probably only visit a
1230 1230 # few commits.
1231 1231 limit = loglimit(opts)
1232 1232 windowsize = 8
1233 1233 if limit:
1234 1234 windowsize = min(limit, windowsize)
1235 1235
1236 1236 # Now that wanted is correctly initialized, we can iterate over the
1237 1237 # revision range, yielding only revisions in wanted.
1238 1238 def iterate():
1239 1239 if follow and not match.files():
1240 1240 ff = followfilter(onlyfirst=opts.get('follow_first'))
1241 1241 def want(rev):
1242 1242 return ff.match(rev) and rev in wanted
1243 1243 else:
1244 1244 def want(rev):
1245 1245 return rev in wanted
1246 1246
1247 1247 for i, window in increasingwindows(0, len(revs), windowsize):
1248 1248 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1249 1249 for rev in sorted(nrevs):
1250 1250 fns = fncache.get(rev)
1251 1251 ctx = change(rev)
1252 1252 if not fns:
1253 1253 def fns_generator():
1254 1254 for f in ctx.files():
1255 1255 if match(f):
1256 1256 yield f
1257 1257 fns = fns_generator()
1258 1258 prepare(ctx, fns)
1259 1259 for rev in nrevs:
1260 1260 yield change(rev)
1261 1261 return iterate()
1262 1262
1263 1263 def _makegraphfilematcher(repo, pats, followfirst):
1264 1264 # When displaying a revision with --patch --follow FILE, we have
1265 1265 # to know which file of the revision must be diffed. With
1266 1266 # --follow, we want the names of the ancestors of FILE in the
1267 1267 # revision, stored in "fcache". "fcache" is populated by
1268 1268 # reproducing the graph traversal already done by --follow revset
1269 1269 # and relating linkrevs to file names (which is not "correct" but
1270 1270 # good enough).
1271 1271 fcache = {}
1272 1272 fcacheready = [False]
1273 1273 pctx = repo['.']
1274 1274 wctx = repo[None]
1275 1275
1276 1276 def populate():
1277 1277 for fn in pats:
1278 1278 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1279 1279 for c in i:
1280 1280 fcache.setdefault(c.linkrev(), set()).add(c.path())
1281 1281
1282 1282 def filematcher(rev):
1283 1283 if not fcacheready[0]:
1284 1284 # Lazy initialization
1285 1285 fcacheready[0] = True
1286 1286 populate()
1287 1287 return scmutil.match(wctx, fcache.get(rev, []), default='path')
1288 1288
1289 1289 return filematcher
1290 1290
1291 1291 def _makegraphlogrevset(repo, pats, opts, revs):
1292 1292 """Return (expr, filematcher) where expr is a revset string built
1293 1293 from log options and file patterns or None. If --stat or --patch
1294 1294 are not passed filematcher is None. Otherwise it is a callable
1295 1295 taking a revision number and returning a match objects filtering
1296 1296 the files to be detailed when displaying the revision.
1297 1297 """
1298 1298 opt2revset = {
1299 1299 'no_merges': ('not merge()', None),
1300 1300 'only_merges': ('merge()', None),
1301 1301 '_ancestors': ('ancestors(%(val)s)', None),
1302 1302 '_fancestors': ('_firstancestors(%(val)s)', None),
1303 1303 '_descendants': ('descendants(%(val)s)', None),
1304 1304 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1305 1305 '_matchfiles': ('_matchfiles(%(val)s)', None),
1306 1306 'date': ('date(%(val)r)', None),
1307 1307 'branch': ('branch(%(val)r)', ' or '),
1308 1308 '_patslog': ('filelog(%(val)r)', ' or '),
1309 1309 '_patsfollow': ('follow(%(val)r)', ' or '),
1310 1310 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1311 1311 'keyword': ('keyword(%(val)r)', ' or '),
1312 1312 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1313 1313 'user': ('user(%(val)r)', ' or '),
1314 1314 }
1315 1315
1316 1316 opts = dict(opts)
1317 1317 # follow or not follow?
1318 1318 follow = opts.get('follow') or opts.get('follow_first')
1319 1319 followfirst = opts.get('follow_first') and 1 or 0
1320 1320 # --follow with FILE behaviour depends on revs...
1321 1321 startrev = revs[0]
1322 1322 followdescendants = (len(revs) > 1 and revs[0] < revs[1]) and 1 or 0
1323 1323
1324 1324 # branch and only_branch are really aliases and must be handled at
1325 1325 # the same time
1326 1326 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1327 1327 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1328 1328 # pats/include/exclude are passed to match.match() directly in
1329 1329 # _matchfiles() revset but walkchangerevs() builds its matcher with
1330 1330 # scmutil.match(). The difference is input pats are globbed on
1331 1331 # platforms without shell expansion (windows).
1332 1332 pctx = repo[None]
1333 1333 match, pats = scmutil.matchandpats(pctx, pats, opts)
1334 1334 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1335 1335 if not slowpath:
1336 1336 for f in match.files():
1337 1337 if follow and f not in pctx:
1338 1338 raise util.Abort(_('cannot follow file not in parent '
1339 1339 'revision: "%s"') % f)
1340 1340 filelog = repo.file(f)
1341 1341 if not filelog:
1342 1342 # A zero count may be a directory or deleted file, so
1343 1343 # try to find matching entries on the slow path.
1344 1344 if follow:
1345 1345 raise util.Abort(
1346 1346 _('cannot follow nonexistent file: "%s"') % f)
1347 1347 slowpath = True
1348 1348
1349 1349 # We decided to fall back to the slowpath because at least one
1350 1350 # of the paths was not a file. Check to see if at least one of them
1351 1351 # existed in history - in that case, we'll continue down the
1352 1352 # slowpath; otherwise, we can turn off the slowpath
1353 1353 if slowpath:
1354 1354 for path in match.files():
1355 1355 if path == '.' or path in repo.store:
1356 1356 break
1357 1357 else:
1358 1358 slowpath = False
1359 1359
1360 1360 if slowpath:
1361 1361 # See walkchangerevs() slow path.
1362 1362 #
1363 1363 if follow:
1364 1364 raise util.Abort(_('can only follow copies/renames for explicit '
1365 1365 'filenames'))
1366 1366 # pats/include/exclude cannot be represented as separate
1367 1367 # revset expressions as their filtering logic applies at file
1368 1368 # level. For instance "-I a -X a" matches a revision touching
1369 1369 # "a" and "b" while "file(a) and not file(b)" does
1370 1370 # not. Besides, filesets are evaluated against the working
1371 1371 # directory.
1372 1372 matchargs = ['r:', 'd:relpath']
1373 1373 for p in pats:
1374 1374 matchargs.append('p:' + p)
1375 1375 for p in opts.get('include', []):
1376 1376 matchargs.append('i:' + p)
1377 1377 for p in opts.get('exclude', []):
1378 1378 matchargs.append('x:' + p)
1379 1379 matchargs = ','.join(('%r' % p) for p in matchargs)
1380 1380 opts['_matchfiles'] = matchargs
1381 1381 else:
1382 1382 if follow:
1383 1383 fpats = ('_patsfollow', '_patsfollowfirst')
1384 1384 fnopats = (('_ancestors', '_fancestors'),
1385 1385 ('_descendants', '_fdescendants'))
1386 1386 if pats:
1387 1387 # follow() revset interprets its file argument as a
1388 1388 # manifest entry, so use match.files(), not pats.
1389 1389 opts[fpats[followfirst]] = list(match.files())
1390 1390 else:
1391 1391 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1392 1392 else:
1393 1393 opts['_patslog'] = list(pats)
1394 1394
1395 1395 filematcher = None
1396 1396 if opts.get('patch') or opts.get('stat'):
1397 1397 if follow:
1398 1398 filematcher = _makegraphfilematcher(repo, pats, followfirst)
1399 1399 else:
1400 1400 filematcher = lambda rev: match
1401 1401
1402 1402 expr = []
1403 1403 for op, val in opts.iteritems():
1404 1404 if not val:
1405 1405 continue
1406 1406 if op not in opt2revset:
1407 1407 continue
1408 1408 revop, andor = opt2revset[op]
1409 1409 if '%(val)' not in revop:
1410 1410 expr.append(revop)
1411 1411 else:
1412 1412 if not isinstance(val, list):
1413 1413 e = revop % {'val': val}
1414 1414 else:
1415 1415 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1416 1416 expr.append(e)
1417 1417
1418 1418 if expr:
1419 1419 expr = '(' + ' and '.join(expr) + ')'
1420 1420 else:
1421 1421 expr = None
1422 1422 return expr, filematcher
1423 1423
1424 1424 def getgraphlogrevs(repo, pats, opts):
1425 1425 """Return (revs, expr, filematcher) where revs is an iterable of
1426 1426 revision numbers, expr is a revset string built from log options
1427 1427 and file patterns or None, and used to filter 'revs'. If --stat or
1428 1428 --patch are not passed filematcher is None. Otherwise it is a
1429 1429 callable taking a revision number and returning a match objects
1430 1430 filtering the files to be detailed when displaying the revision.
1431 1431 """
1432 1432 if not len(repo):
1433 1433 return [], None, None
1434 1434 limit = loglimit(opts)
1435 1435 # Default --rev value depends on --follow but --follow behaviour
1436 1436 # depends on revisions resolved from --rev...
1437 1437 follow = opts.get('follow') or opts.get('follow_first')
1438 1438 possiblyunsorted = False # whether revs might need sorting
1439 1439 if opts.get('rev'):
1440 1440 revs = scmutil.revrange(repo, opts['rev'])
1441 1441 # Don't sort here because _makegraphlogrevset might depend on the
1442 1442 # order of revs
1443 1443 possiblyunsorted = True
1444 1444 else:
1445 1445 if follow and len(repo) > 0:
1446 1446 revs = repo.revs('reverse(:.)')
1447 1447 else:
1448 1448 revs = list(repo.changelog)
1449 1449 revs.reverse()
1450 1450 if not revs:
1451 1451 return [], None, None
1452 1452 expr, filematcher = _makegraphlogrevset(repo, pats, opts, revs)
1453 1453 if possiblyunsorted:
1454 1454 revs.sort(reverse=True)
1455 1455 if expr:
1456 1456 # Revset matchers often operate faster on revisions in changelog
1457 1457 # order, because most filters deal with the changelog.
1458 1458 revs.reverse()
1459 1459 matcher = revset.match(repo.ui, expr)
1460 1460 # Revset matches can reorder revisions. "A or B" typically returns
1461 1461 # returns the revision matching A then the revision matching B. Sort
1462 1462 # again to fix that.
1463 1463 revs = matcher(repo, revs)
1464 1464 revs.sort(reverse=True)
1465 1465 if limit is not None:
1466 1466 revs = revs[:limit]
1467 1467
1468 1468 return revs, expr, filematcher
1469 1469
1470 1470 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1471 1471 filematcher=None):
1472 1472 seen, state = [], graphmod.asciistate()
1473 1473 for rev, type, ctx, parents in dag:
1474 1474 char = 'o'
1475 1475 if ctx.node() in showparents:
1476 1476 char = '@'
1477 1477 elif ctx.obsolete():
1478 1478 char = 'x'
1479 1479 copies = None
1480 1480 if getrenamed and ctx.rev():
1481 1481 copies = []
1482 1482 for fn in ctx.files():
1483 1483 rename = getrenamed(fn, ctx.rev())
1484 1484 if rename:
1485 1485 copies.append((fn, rename[0]))
1486 1486 revmatchfn = None
1487 1487 if filematcher is not None:
1488 1488 revmatchfn = filematcher(ctx.rev())
1489 1489 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1490 1490 lines = displayer.hunk.pop(rev).split('\n')
1491 1491 if not lines[-1]:
1492 1492 del lines[-1]
1493 1493 displayer.flush(rev)
1494 1494 edges = edgefn(type, char, lines, seen, rev, parents)
1495 1495 for type, char, lines, coldata in edges:
1496 1496 graphmod.ascii(ui, state, type, char, lines, coldata)
1497 1497 displayer.close()
1498 1498
1499 1499 def graphlog(ui, repo, *pats, **opts):
1500 1500 # Parameters are identical to log command ones
1501 1501 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1502 1502 revdag = graphmod.dagwalker(repo, revs)
1503 1503
1504 1504 getrenamed = None
1505 1505 if opts.get('copies'):
1506 1506 endrev = None
1507 1507 if opts.get('rev'):
1508 1508 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
1509 1509 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1510 1510 displayer = show_changeset(ui, repo, opts, buffered=True)
1511 1511 showparents = [ctx.node() for ctx in repo[None].parents()]
1512 1512 displaygraph(ui, revdag, displayer, showparents,
1513 1513 graphmod.asciiedges, getrenamed, filematcher)
1514 1514
1515 1515 def checkunsupportedgraphflags(pats, opts):
1516 1516 for op in ["newest_first"]:
1517 1517 if op in opts and opts[op]:
1518 1518 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1519 1519 % op.replace("_", "-"))
1520 1520
1521 1521 def graphrevs(repo, nodes, opts):
1522 1522 limit = loglimit(opts)
1523 1523 nodes.reverse()
1524 1524 if limit is not None:
1525 1525 nodes = nodes[:limit]
1526 1526 return graphmod.nodes(repo, nodes)
1527 1527
1528 1528 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1529 1529 join = lambda f: os.path.join(prefix, f)
1530 1530 bad = []
1531 1531 oldbad = match.bad
1532 1532 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1533 1533 names = []
1534 1534 wctx = repo[None]
1535 1535 cca = None
1536 1536 abort, warn = scmutil.checkportabilityalert(ui)
1537 1537 if abort or warn:
1538 1538 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1539 1539 for f in repo.walk(match):
1540 1540 exact = match.exact(f)
1541 1541 if exact or not explicitonly and f not in repo.dirstate:
1542 1542 if cca:
1543 1543 cca(f)
1544 1544 names.append(f)
1545 1545 if ui.verbose or not exact:
1546 1546 ui.status(_('adding %s\n') % match.rel(join(f)))
1547 1547
1548 1548 for subpath in sorted(wctx.substate):
1549 1549 sub = wctx.sub(subpath)
1550 1550 try:
1551 1551 submatch = matchmod.narrowmatcher(subpath, match)
1552 1552 if listsubrepos:
1553 1553 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1554 1554 False))
1555 1555 else:
1556 1556 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1557 1557 True))
1558 1558 except error.LookupError:
1559 1559 ui.status(_("skipping missing subrepository: %s\n")
1560 1560 % join(subpath))
1561 1561
1562 1562 if not dryrun:
1563 1563 rejected = wctx.add(names, prefix)
1564 1564 bad.extend(f for f in rejected if f in match.files())
1565 1565 return bad
1566 1566
1567 1567 def forget(ui, repo, match, prefix, explicitonly):
1568 1568 join = lambda f: os.path.join(prefix, f)
1569 1569 bad = []
1570 1570 oldbad = match.bad
1571 1571 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1572 1572 wctx = repo[None]
1573 1573 forgot = []
1574 1574 s = repo.status(match=match, clean=True)
1575 1575 forget = sorted(s[0] + s[1] + s[3] + s[6])
1576 1576 if explicitonly:
1577 1577 forget = [f for f in forget if match.exact(f)]
1578 1578
1579 1579 for subpath in sorted(wctx.substate):
1580 1580 sub = wctx.sub(subpath)
1581 1581 try:
1582 1582 submatch = matchmod.narrowmatcher(subpath, match)
1583 1583 subbad, subforgot = sub.forget(ui, submatch, prefix)
1584 1584 bad.extend([subpath + '/' + f for f in subbad])
1585 1585 forgot.extend([subpath + '/' + f for f in subforgot])
1586 1586 except error.LookupError:
1587 1587 ui.status(_("skipping missing subrepository: %s\n")
1588 1588 % join(subpath))
1589 1589
1590 1590 if not explicitonly:
1591 1591 for f in match.files():
1592 1592 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1593 1593 if f not in forgot:
1594 1594 if os.path.exists(match.rel(join(f))):
1595 1595 ui.warn(_('not removing %s: '
1596 1596 'file is already untracked\n')
1597 1597 % match.rel(join(f)))
1598 1598 bad.append(f)
1599 1599
1600 1600 for f in forget:
1601 1601 if ui.verbose or not match.exact(f):
1602 1602 ui.status(_('removing %s\n') % match.rel(join(f)))
1603 1603
1604 1604 rejected = wctx.forget(forget, prefix)
1605 1605 bad.extend(f for f in rejected if f in match.files())
1606 1606 forgot.extend(forget)
1607 1607 return bad, forgot
1608 1608
1609 1609 def duplicatecopies(repo, rev, fromrev):
1610 1610 '''reproduce copies from fromrev to rev in the dirstate'''
1611 1611 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
1612 1612 # copies.pathcopies returns backward renames, so dst might not
1613 1613 # actually be in the dirstate
1614 1614 if repo.dirstate[dst] in "nma":
1615 1615 repo.dirstate.copy(src, dst)
1616 1616
1617 1617 def commit(ui, repo, commitfunc, pats, opts):
1618 1618 '''commit the specified files or all outstanding changes'''
1619 1619 date = opts.get('date')
1620 1620 if date:
1621 1621 opts['date'] = util.parsedate(date)
1622 1622 message = logmessage(ui, opts)
1623 1623
1624 1624 # extract addremove carefully -- this function can be called from a command
1625 1625 # that doesn't support addremove
1626 1626 if opts.get('addremove'):
1627 1627 scmutil.addremove(repo, pats, opts)
1628 1628
1629 1629 return commitfunc(ui, repo, message,
1630 1630 scmutil.match(repo[None], pats, opts), opts)
1631 1631
1632 1632 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1633 1633 ui.note(_('amending changeset %s\n') % old)
1634 1634 base = old.p1()
1635 1635
1636 1636 wlock = lock = newid = None
1637 1637 try:
1638 1638 wlock = repo.wlock()
1639 1639 lock = repo.lock()
1640 1640 tr = repo.transaction('amend')
1641 1641 try:
1642 1642 # See if we got a message from -m or -l, if not, open the editor
1643 1643 # with the message of the changeset to amend
1644 1644 message = logmessage(ui, opts)
1645 1645 # ensure logfile does not conflict with later enforcement of the
1646 1646 # message. potential logfile content has been processed by
1647 1647 # `logmessage` anyway.
1648 1648 opts.pop('logfile')
1649 1649 # First, do a regular commit to record all changes in the working
1650 1650 # directory (if there are any)
1651 1651 ui.callhooks = False
1652 1652 currentbookmark = repo._bookmarkcurrent
1653 1653 try:
1654 1654 repo._bookmarkcurrent = None
1655 1655 opts['message'] = 'temporary amend commit for %s' % old
1656 1656 node = commit(ui, repo, commitfunc, pats, opts)
1657 1657 finally:
1658 1658 repo._bookmarkcurrent = currentbookmark
1659 1659 ui.callhooks = True
1660 1660 ctx = repo[node]
1661 1661
1662 1662 # Participating changesets:
1663 1663 #
1664 1664 # node/ctx o - new (intermediate) commit that contains changes
1665 1665 # | from working dir to go into amending commit
1666 1666 # | (or a workingctx if there were no changes)
1667 1667 # |
1668 1668 # old o - changeset to amend
1669 1669 # |
1670 1670 # base o - parent of amending changeset
1671 1671
1672 1672 # Update extra dict from amended commit (e.g. to preserve graft
1673 1673 # source)
1674 1674 extra.update(old.extra())
1675 1675
1676 1676 # Also update it from the intermediate commit or from the wctx
1677 1677 extra.update(ctx.extra())
1678 1678
1679 1679 if len(old.parents()) > 1:
1680 1680 # ctx.files() isn't reliable for merges, so fall back to the
1681 1681 # slower repo.status() method
1682 1682 files = set([fn for st in repo.status(base, old)[:3]
1683 1683 for fn in st])
1684 1684 else:
1685 1685 files = set(old.files())
1686 1686
1687 1687 # Second, we use either the commit we just did, or if there were no
1688 1688 # changes the parent of the working directory as the version of the
1689 1689 # files in the final amend commit
1690 1690 if node:
1691 1691 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
1692 1692
1693 1693 user = ctx.user()
1694 1694 date = ctx.date()
1695 1695 # Recompute copies (avoid recording a -> b -> a)
1696 1696 copied = copies.pathcopies(base, ctx)
1697 1697
1698 1698 # Prune files which were reverted by the updates: if old
1699 1699 # introduced file X and our intermediate commit, node,
1700 1700 # renamed that file, then those two files are the same and
1701 1701 # we can discard X from our list of files. Likewise if X
1702 1702 # was deleted, it's no longer relevant
1703 1703 files.update(ctx.files())
1704 1704
1705 1705 def samefile(f):
1706 1706 if f in ctx.manifest():
1707 1707 a = ctx.filectx(f)
1708 1708 if f in base.manifest():
1709 1709 b = base.filectx(f)
1710 1710 return (not a.cmp(b)
1711 1711 and a.flags() == b.flags())
1712 1712 else:
1713 1713 return False
1714 1714 else:
1715 1715 return f not in base.manifest()
1716 1716 files = [f for f in files if not samefile(f)]
1717 1717
1718 1718 def filectxfn(repo, ctx_, path):
1719 1719 try:
1720 1720 fctx = ctx[path]
1721 1721 flags = fctx.flags()
1722 1722 mctx = context.memfilectx(fctx.path(), fctx.data(),
1723 1723 islink='l' in flags,
1724 1724 isexec='x' in flags,
1725 1725 copied=copied.get(path))
1726 1726 return mctx
1727 1727 except KeyError:
1728 1728 raise IOError
1729 1729 else:
1730 1730 ui.note(_('copying changeset %s to %s\n') % (old, base))
1731 1731
1732 1732 # Use version of files as in the old cset
1733 1733 def filectxfn(repo, ctx_, path):
1734 1734 try:
1735 1735 return old.filectx(path)
1736 1736 except KeyError:
1737 1737 raise IOError
1738 1738
1739 1739 user = opts.get('user') or old.user()
1740 1740 date = opts.get('date') or old.date()
1741 1741 editmsg = False
1742 1742 if not message:
1743 1743 editmsg = True
1744 1744 message = old.description()
1745 1745
1746 1746 pureextra = extra.copy()
1747 1747 extra['amend_source'] = old.hex()
1748 1748
1749 1749 new = context.memctx(repo,
1750 1750 parents=[base.node(), old.p2().node()],
1751 1751 text=message,
1752 1752 files=files,
1753 1753 filectxfn=filectxfn,
1754 1754 user=user,
1755 1755 date=date,
1756 1756 extra=extra)
1757 1757 if editmsg:
1758 1758 new._text = commitforceeditor(repo, new, [])
1759 1759
1760 1760 newdesc = changelog.stripdesc(new.description())
1761 1761 if ((not node)
1762 1762 and newdesc == old.description()
1763 1763 and user == old.user()
1764 1764 and date == old.date()
1765 1765 and pureextra == old.extra()):
1766 1766 # nothing changed. continuing here would create a new node
1767 1767 # anyway because of the amend_source noise.
1768 1768 #
1769 1769 # This not what we expect from amend.
1770 1770 return old.node()
1771 1771
1772 1772 ph = repo.ui.config('phases', 'new-commit', phases.draft)
1773 1773 try:
1774 1774 repo.ui.setconfig('phases', 'new-commit', old.phase())
1775 1775 newid = repo.commitctx(new)
1776 1776 finally:
1777 1777 repo.ui.setconfig('phases', 'new-commit', ph)
1778 1778 if newid != old.node():
1779 1779 # Reroute the working copy parent to the new changeset
1780 1780 repo.setparents(newid, nullid)
1781 1781
1782 1782 # Move bookmarks from old parent to amend commit
1783 1783 bms = repo.nodebookmarks(old.node())
1784 1784 if bms:
1785 1785 marks = repo._bookmarks
1786 1786 for bm in bms:
1787 1787 marks[bm] = newid
1788 1788 marks.write()
1789 1789 #commit the whole amend process
1790 1790 if obsolete._enabled and newid != old.node():
1791 1791 # mark the new changeset as successor of the rewritten one
1792 1792 new = repo[newid]
1793 1793 obs = [(old, (new,))]
1794 1794 if node:
1795 1795 obs.append((ctx, ()))
1796 1796
1797 1797 obsolete.createmarkers(repo, obs)
1798 1798 tr.close()
1799 1799 finally:
1800 1800 tr.release()
1801 1801 if (not obsolete._enabled) and newid != old.node():
1802 1802 # Strip the intermediate commit (if there was one) and the amended
1803 1803 # commit
1804 1804 if node:
1805 1805 ui.note(_('stripping intermediate changeset %s\n') % ctx)
1806 1806 ui.note(_('stripping amended changeset %s\n') % old)
1807 1807 repair.strip(ui, repo, old.node(), topic='amend-backup')
1808 1808 finally:
1809 1809 if newid is None:
1810 1810 repo.dirstate.invalidate()
1811 1811 lockmod.release(lock, wlock)
1812 1812 return newid
1813 1813
1814 1814 def commiteditor(repo, ctx, subs):
1815 1815 if ctx.description():
1816 1816 return ctx.description()
1817 1817 return commitforceeditor(repo, ctx, subs)
1818 1818
1819 1819 def commitforceeditor(repo, ctx, subs):
1820 1820 edittext = []
1821 1821 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1822 1822 if ctx.description():
1823 1823 edittext.append(ctx.description())
1824 1824 edittext.append("")
1825 1825 edittext.append("") # Empty line between message and comments.
1826 1826 edittext.append(_("HG: Enter commit message."
1827 1827 " Lines beginning with 'HG:' are removed."))
1828 1828 edittext.append(_("HG: Leave message empty to abort commit."))
1829 1829 edittext.append("HG: --")
1830 1830 edittext.append(_("HG: user: %s") % ctx.user())
1831 1831 if ctx.p2():
1832 1832 edittext.append(_("HG: branch merge"))
1833 1833 if ctx.branch():
1834 1834 edittext.append(_("HG: branch '%s'") % ctx.branch())
1835 1835 if bookmarks.iscurrent(repo):
1836 1836 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
1837 1837 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1838 1838 edittext.extend([_("HG: added %s") % f for f in added])
1839 1839 edittext.extend([_("HG: changed %s") % f for f in modified])
1840 1840 edittext.extend([_("HG: removed %s") % f for f in removed])
1841 1841 if not added and not modified and not removed:
1842 1842 edittext.append(_("HG: no files changed"))
1843 1843 edittext.append("")
1844 1844 # run editor in the repository root
1845 1845 olddir = os.getcwd()
1846 1846 os.chdir(repo.root)
1847 1847 text = repo.ui.edit("\n".join(edittext), ctx.user())
1848 1848 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1849 1849 os.chdir(olddir)
1850 1850
1851 1851 if not text.strip():
1852 1852 raise util.Abort(_("empty commit message"))
1853 1853
1854 1854 return text
1855 1855
1856 1856 def commitstatus(repo, node, branch, bheads=None, opts={}):
1857 1857 ctx = repo[node]
1858 1858 parents = ctx.parents()
1859 1859
1860 1860 if (not opts.get('amend') and bheads and node not in bheads and not
1861 1861 [x for x in parents if x.node() in bheads and x.branch() == branch]):
1862 1862 repo.ui.status(_('created new head\n'))
1863 1863 # The message is not printed for initial roots. For the other
1864 1864 # changesets, it is printed in the following situations:
1865 1865 #
1866 1866 # Par column: for the 2 parents with ...
1867 1867 # N: null or no parent
1868 1868 # B: parent is on another named branch
1869 1869 # C: parent is a regular non head changeset
1870 1870 # H: parent was a branch head of the current branch
1871 1871 # Msg column: whether we print "created new head" message
1872 1872 # In the following, it is assumed that there already exists some
1873 1873 # initial branch heads of the current branch, otherwise nothing is
1874 1874 # printed anyway.
1875 1875 #
1876 1876 # Par Msg Comment
1877 1877 # N N y additional topo root
1878 1878 #
1879 1879 # B N y additional branch root
1880 1880 # C N y additional topo head
1881 1881 # H N n usual case
1882 1882 #
1883 1883 # B B y weird additional branch root
1884 1884 # C B y branch merge
1885 1885 # H B n merge with named branch
1886 1886 #
1887 1887 # C C y additional head from merge
1888 1888 # C H n merge with a head
1889 1889 #
1890 1890 # H H n head merge: head count decreases
1891 1891
1892 1892 if not opts.get('close_branch'):
1893 1893 for r in parents:
1894 1894 if r.closesbranch() and r.branch() == branch:
1895 1895 repo.ui.status(_('reopening closed branch head %d\n') % r)
1896 1896
1897 1897 if repo.ui.debugflag:
1898 1898 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1899 1899 elif repo.ui.verbose:
1900 1900 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1901 1901
1902 1902 def revert(ui, repo, ctx, parents, *pats, **opts):
1903 1903 parent, p2 = parents
1904 1904 node = ctx.node()
1905 1905
1906 1906 mf = ctx.manifest()
1907 1907 if node == parent:
1908 1908 pmf = mf
1909 1909 else:
1910 1910 pmf = None
1911 1911
1912 1912 # need all matching names in dirstate and manifest of target rev,
1913 1913 # so have to walk both. do not print errors if files exist in one
1914 1914 # but not other.
1915 1915
1916 1916 names = {}
1917 1917
1918 1918 wlock = repo.wlock()
1919 1919 try:
1920 1920 # walk dirstate.
1921 1921
1922 1922 m = scmutil.match(repo[None], pats, opts)
1923 1923 m.bad = lambda x, y: False
1924 1924 for abs in repo.walk(m):
1925 1925 names[abs] = m.rel(abs), m.exact(abs)
1926 1926
1927 1927 # walk target manifest.
1928 1928
1929 1929 def badfn(path, msg):
1930 1930 if path in names:
1931 1931 return
1932 1932 if path in ctx.substate:
1933 1933 return
1934 1934 path_ = path + '/'
1935 1935 for f in names:
1936 1936 if f.startswith(path_):
1937 1937 return
1938 1938 ui.warn("%s: %s\n" % (m.rel(path), msg))
1939 1939
1940 1940 m = scmutil.match(ctx, pats, opts)
1941 1941 m.bad = badfn
1942 1942 for abs in ctx.walk(m):
1943 1943 if abs not in names:
1944 1944 names[abs] = m.rel(abs), m.exact(abs)
1945 1945
1946 1946 # get the list of subrepos that must be reverted
1947 1947 targetsubs = sorted(s for s in ctx.substate if m(s))
1948 1948 m = scmutil.matchfiles(repo, names)
1949 1949 changes = repo.status(match=m)[:4]
1950 1950 modified, added, removed, deleted = map(set, changes)
1951 1951
1952 1952 # if f is a rename, also revert the source
1953 1953 cwd = repo.getcwd()
1954 1954 for f in added:
1955 1955 src = repo.dirstate.copied(f)
1956 1956 if src and src not in names and repo.dirstate[src] == 'r':
1957 1957 removed.add(src)
1958 1958 names[src] = (repo.pathto(src, cwd), True)
1959 1959
1960 1960 def removeforget(abs):
1961 1961 if repo.dirstate[abs] == 'a':
1962 1962 return _('forgetting %s\n')
1963 1963 return _('removing %s\n')
1964 1964
1965 1965 revert = ([], _('reverting %s\n'))
1966 1966 add = ([], _('adding %s\n'))
1967 1967 remove = ([], removeforget)
1968 1968 undelete = ([], _('undeleting %s\n'))
1969 1969
1970 1970 disptable = (
1971 1971 # dispatch table:
1972 1972 # file state
1973 1973 # action if in target manifest
1974 1974 # action if not in target manifest
1975 1975 # make backup if in target manifest
1976 1976 # make backup if not in target manifest
1977 1977 (modified, revert, remove, True, True),
1978 1978 (added, revert, remove, True, False),
1979 1979 (removed, undelete, None, False, False),
1980 1980 (deleted, revert, remove, False, False),
1981 1981 )
1982 1982
1983 1983 for abs, (rel, exact) in sorted(names.items()):
1984 1984 mfentry = mf.get(abs)
1985 1985 target = repo.wjoin(abs)
1986 1986 def handle(xlist, dobackup):
1987 1987 xlist[0].append(abs)
1988 1988 if (dobackup and not opts.get('no_backup') and
1989 1989 os.path.lexists(target)):
1990 1990 bakname = "%s.orig" % rel
1991 1991 ui.note(_('saving current version of %s as %s\n') %
1992 1992 (rel, bakname))
1993 1993 if not opts.get('dry_run'):
1994 1994 util.rename(target, bakname)
1995 1995 if ui.verbose or not exact:
1996 1996 msg = xlist[1]
1997 1997 if not isinstance(msg, basestring):
1998 1998 msg = msg(abs)
1999 1999 ui.status(msg % rel)
2000 2000 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2001 2001 if abs not in table:
2002 2002 continue
2003 2003 # file has changed in dirstate
2004 2004 if mfentry:
2005 2005 handle(hitlist, backuphit)
2006 2006 elif misslist is not None:
2007 2007 handle(misslist, backupmiss)
2008 2008 break
2009 2009 else:
2010 2010 if abs not in repo.dirstate:
2011 2011 if mfentry:
2012 2012 handle(add, True)
2013 2013 elif exact:
2014 2014 ui.warn(_('file not managed: %s\n') % rel)
2015 2015 continue
2016 2016 # file has not changed in dirstate
2017 2017 if node == parent:
2018 2018 if exact:
2019 2019 ui.warn(_('no changes needed to %s\n') % rel)
2020 2020 continue
2021 2021 if pmf is None:
2022 2022 # only need parent manifest in this unlikely case,
2023 2023 # so do not read by default
2024 2024 pmf = repo[parent].manifest()
2025 2025 if abs in pmf and mfentry:
2026 2026 # if version of file is same in parent and target
2027 2027 # manifests, do nothing
2028 2028 if (pmf[abs] != mfentry or
2029 2029 pmf.flags(abs) != mf.flags(abs)):
2030 2030 handle(revert, False)
2031 2031 else:
2032 2032 handle(remove, False)
2033 2033
2034 2034 if not opts.get('dry_run'):
2035 2035 def checkout(f):
2036 2036 fc = ctx[f]
2037 2037 repo.wwrite(f, fc.data(), fc.flags())
2038 2038
2039 2039 audit_path = scmutil.pathauditor(repo.root)
2040 2040 for f in remove[0]:
2041 2041 if repo.dirstate[f] == 'a':
2042 2042 repo.dirstate.drop(f)
2043 2043 continue
2044 2044 audit_path(f)
2045 2045 try:
2046 2046 util.unlinkpath(repo.wjoin(f))
2047 2047 except OSError:
2048 2048 pass
2049 2049 repo.dirstate.remove(f)
2050 2050
2051 2051 normal = None
2052 2052 if node == parent:
2053 2053 # We're reverting to our parent. If possible, we'd like status
2054 2054 # to report the file as clean. We have to use normallookup for
2055 2055 # merges to avoid losing information about merged/dirty files.
2056 2056 if p2 != nullid:
2057 2057 normal = repo.dirstate.normallookup
2058 2058 else:
2059 2059 normal = repo.dirstate.normal
2060 2060 for f in revert[0]:
2061 2061 checkout(f)
2062 2062 if normal:
2063 2063 normal(f)
2064 2064
2065 2065 for f in add[0]:
2066 2066 checkout(f)
2067 2067 repo.dirstate.add(f)
2068 2068
2069 2069 normal = repo.dirstate.normallookup
2070 2070 if node == parent and p2 == nullid:
2071 2071 normal = repo.dirstate.normal
2072 2072 for f in undelete[0]:
2073 2073 checkout(f)
2074 2074 normal(f)
2075 2075
2076 2076 copied = copies.pathcopies(repo[parent], ctx)
2077 2077
2078 2078 for f in add[0] + undelete[0] + revert[0]:
2079 2079 if f in copied:
2080 2080 repo.dirstate.copy(copied[f], f)
2081 2081
2082 2082 if targetsubs:
2083 2083 # Revert the subrepos on the revert list
2084 2084 for sub in targetsubs:
2085 2085 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2086 2086 finally:
2087 2087 wlock.release()
2088 2088
2089 2089 def command(table):
2090 2090 '''returns a function object bound to table which can be used as
2091 2091 a decorator for populating table as a command table'''
2092 2092
2093 2093 def cmd(name, options=(), synopsis=None):
2094 2094 def decorator(func):
2095 2095 if synopsis:
2096 2096 table[name] = func, list(options), synopsis
2097 2097 else:
2098 2098 table[name] = func, list(options)
2099 2099 return func
2100 2100 return decorator
2101 2101
2102 2102 return cmd
2103 2103
2104 2104 # a list of (ui, repo) functions called by commands.summary
2105 2105 summaryhooks = util.hooks()
2106 2106
2107 2107 # A list of state files kept by multistep operations like graft.
2108 2108 # Since graft cannot be aborted, it is considered 'clearable' by update.
2109 2109 # note: bisect is intentionally excluded
2110 # (state file, clearable, error, hint)
2110 # (state file, clearable, allowcommit, error, hint)
2111 2111 unfinishedstates = [
2112 ('graftstate', True, _('graft in progress'),
2112 ('graftstate', True, False, _('graft in progress'),
2113 2113 _("use 'hg graft --continue' or 'hg update' to abort")),
2114 ('updatestate', True, _('last update was interrupted'),
2114 ('updatestate', True, False, _('last update was interrupted'),
2115 2115 _("use 'hg update' to get a consistent checkout"))
2116 2116 ]
2117 2117
2118 def checkunfinished(repo):
2118 def checkunfinished(repo, commit=False):
2119 2119 '''Look for an unfinished multistep operation, like graft, and abort
2120 2120 if found. It's probably good to check this right before
2121 2121 bailifchanged().
2122 2122 '''
2123 for f, clearable, msg, hint in unfinishedstates:
2123 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2124 if commit and allowcommit:
2125 continue
2124 2126 if repo.vfs.exists(f):
2125 2127 raise util.Abort(msg, hint=hint)
2126 2128
2127 2129 def clearunfinished(repo):
2128 2130 '''Check for unfinished operations (as above), and clear the ones
2129 2131 that are clearable.
2130 2132 '''
2131 for f, clearable, msg, hint in unfinishedstates:
2133 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2132 2134 if not clearable and repo.vfs.exists(f):
2133 2135 raise util.Abort(msg, hint=hint)
2134 for f, clearable, msg, hint in unfinishedstates:
2136 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2135 2137 if clearable and repo.vfs.exists(f):
2136 2138 util.unlink(repo.join(f))
@@ -1,5914 +1,5914 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.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 from node import hex, bin, nullid, nullrev, short
9 9 from lock import release
10 10 from i18n import _
11 11 import os, re, difflib, time, tempfile, errno
12 12 import hg, scmutil, util, revlog, copies, error, bookmarks
13 13 import patch, help, encoding, templatekw, discovery
14 14 import archival, changegroup, cmdutil, hbisect
15 15 import sshserver, hgweb, hgweb.server, commandserver
16 16 import merge as mergemod
17 17 import minirst, revset, fileset
18 18 import dagparser, context, simplemerge, graphmod
19 19 import random, setdiscovery, treediscovery, dagutil, pvec, localrepo
20 20 import phases, obsolete
21 21
22 22 table = {}
23 23
24 24 command = cmdutil.command(table)
25 25
26 26 # common command options
27 27
28 28 globalopts = [
29 29 ('R', 'repository', '',
30 30 _('repository root directory or name of overlay bundle file'),
31 31 _('REPO')),
32 32 ('', 'cwd', '',
33 33 _('change working directory'), _('DIR')),
34 34 ('y', 'noninteractive', None,
35 35 _('do not prompt, automatically pick the first choice for all prompts')),
36 36 ('q', 'quiet', None, _('suppress output')),
37 37 ('v', 'verbose', None, _('enable additional output')),
38 38 ('', 'config', [],
39 39 _('set/override config option (use \'section.name=value\')'),
40 40 _('CONFIG')),
41 41 ('', 'debug', None, _('enable debugging output')),
42 42 ('', 'debugger', None, _('start debugger')),
43 43 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
44 44 _('ENCODE')),
45 45 ('', 'encodingmode', encoding.encodingmode,
46 46 _('set the charset encoding mode'), _('MODE')),
47 47 ('', 'traceback', None, _('always print a traceback on exception')),
48 48 ('', 'time', None, _('time how long the command takes')),
49 49 ('', 'profile', None, _('print command execution profile')),
50 50 ('', 'version', None, _('output version information and exit')),
51 51 ('h', 'help', None, _('display help and exit')),
52 52 ('', 'hidden', False, _('consider hidden changesets')),
53 53 ]
54 54
55 55 dryrunopts = [('n', 'dry-run', None,
56 56 _('do not perform actions, just print output'))]
57 57
58 58 remoteopts = [
59 59 ('e', 'ssh', '',
60 60 _('specify ssh command to use'), _('CMD')),
61 61 ('', 'remotecmd', '',
62 62 _('specify hg command to run on the remote side'), _('CMD')),
63 63 ('', 'insecure', None,
64 64 _('do not verify server certificate (ignoring web.cacerts config)')),
65 65 ]
66 66
67 67 walkopts = [
68 68 ('I', 'include', [],
69 69 _('include names matching the given patterns'), _('PATTERN')),
70 70 ('X', 'exclude', [],
71 71 _('exclude names matching the given patterns'), _('PATTERN')),
72 72 ]
73 73
74 74 commitopts = [
75 75 ('m', 'message', '',
76 76 _('use text as commit message'), _('TEXT')),
77 77 ('l', 'logfile', '',
78 78 _('read commit message from file'), _('FILE')),
79 79 ]
80 80
81 81 commitopts2 = [
82 82 ('d', 'date', '',
83 83 _('record the specified date as commit date'), _('DATE')),
84 84 ('u', 'user', '',
85 85 _('record the specified user as committer'), _('USER')),
86 86 ]
87 87
88 88 templateopts = [
89 89 ('', 'style', '',
90 90 _('display using template map file'), _('STYLE')),
91 91 ('', 'template', '',
92 92 _('display with template'), _('TEMPLATE')),
93 93 ]
94 94
95 95 logopts = [
96 96 ('p', 'patch', None, _('show patch')),
97 97 ('g', 'git', None, _('use git extended diff format')),
98 98 ('l', 'limit', '',
99 99 _('limit number of changes displayed'), _('NUM')),
100 100 ('M', 'no-merges', None, _('do not show merges')),
101 101 ('', 'stat', None, _('output diffstat-style summary of changes')),
102 102 ('G', 'graph', None, _("show the revision DAG")),
103 103 ] + templateopts
104 104
105 105 diffopts = [
106 106 ('a', 'text', None, _('treat all files as text')),
107 107 ('g', 'git', None, _('use git extended diff format')),
108 108 ('', 'nodates', None, _('omit dates from diff headers'))
109 109 ]
110 110
111 111 diffwsopts = [
112 112 ('w', 'ignore-all-space', None,
113 113 _('ignore white space when comparing lines')),
114 114 ('b', 'ignore-space-change', None,
115 115 _('ignore changes in the amount of white space')),
116 116 ('B', 'ignore-blank-lines', None,
117 117 _('ignore changes whose lines are all blank')),
118 118 ]
119 119
120 120 diffopts2 = [
121 121 ('p', 'show-function', None, _('show which function each change is in')),
122 122 ('', 'reverse', None, _('produce a diff that undoes the changes')),
123 123 ] + diffwsopts + [
124 124 ('U', 'unified', '',
125 125 _('number of lines of context to show'), _('NUM')),
126 126 ('', 'stat', None, _('output diffstat-style summary of changes')),
127 127 ]
128 128
129 129 mergetoolopts = [
130 130 ('t', 'tool', '', _('specify merge tool')),
131 131 ]
132 132
133 133 similarityopts = [
134 134 ('s', 'similarity', '',
135 135 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
136 136 ]
137 137
138 138 subrepoopts = [
139 139 ('S', 'subrepos', None,
140 140 _('recurse into subrepositories'))
141 141 ]
142 142
143 143 # Commands start here, listed alphabetically
144 144
145 145 @command('^add',
146 146 walkopts + subrepoopts + dryrunopts,
147 147 _('[OPTION]... [FILE]...'))
148 148 def add(ui, repo, *pats, **opts):
149 149 """add the specified files on the next commit
150 150
151 151 Schedule files to be version controlled and added to the
152 152 repository.
153 153
154 154 The files will be added to the repository at the next commit. To
155 155 undo an add before that, see :hg:`forget`.
156 156
157 157 If no names are given, add all files to the repository.
158 158
159 159 .. container:: verbose
160 160
161 161 An example showing how new (unknown) files are added
162 162 automatically by :hg:`add`::
163 163
164 164 $ ls
165 165 foo.c
166 166 $ hg status
167 167 ? foo.c
168 168 $ hg add
169 169 adding foo.c
170 170 $ hg status
171 171 A foo.c
172 172
173 173 Returns 0 if all files are successfully added.
174 174 """
175 175
176 176 m = scmutil.match(repo[None], pats, opts)
177 177 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
178 178 opts.get('subrepos'), prefix="", explicitonly=False)
179 179 return rejected and 1 or 0
180 180
181 181 @command('addremove',
182 182 similarityopts + walkopts + dryrunopts,
183 183 _('[OPTION]... [FILE]...'))
184 184 def addremove(ui, repo, *pats, **opts):
185 185 """add all new files, delete all missing files
186 186
187 187 Add all new files and remove all missing files from the
188 188 repository.
189 189
190 190 New files are ignored if they match any of the patterns in
191 191 ``.hgignore``. As with add, these changes take effect at the next
192 192 commit.
193 193
194 194 Use the -s/--similarity option to detect renamed files. This
195 195 option takes a percentage between 0 (disabled) and 100 (files must
196 196 be identical) as its parameter. With a parameter greater than 0,
197 197 this compares every removed file with every added file and records
198 198 those similar enough as renames. Detecting renamed files this way
199 199 can be expensive. After using this option, :hg:`status -C` can be
200 200 used to check which files were identified as moved or renamed. If
201 201 not specified, -s/--similarity defaults to 100 and only renames of
202 202 identical files are detected.
203 203
204 204 Returns 0 if all files are successfully added.
205 205 """
206 206 try:
207 207 sim = float(opts.get('similarity') or 100)
208 208 except ValueError:
209 209 raise util.Abort(_('similarity must be a number'))
210 210 if sim < 0 or sim > 100:
211 211 raise util.Abort(_('similarity must be between 0 and 100'))
212 212 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
213 213
214 214 @command('^annotate|blame',
215 215 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
216 216 ('', 'follow', None,
217 217 _('follow copies/renames and list the filename (DEPRECATED)')),
218 218 ('', 'no-follow', None, _("don't follow copies and renames")),
219 219 ('a', 'text', None, _('treat all files as text')),
220 220 ('u', 'user', None, _('list the author (long with -v)')),
221 221 ('f', 'file', None, _('list the filename')),
222 222 ('d', 'date', None, _('list the date (short with -q)')),
223 223 ('n', 'number', None, _('list the revision number (default)')),
224 224 ('c', 'changeset', None, _('list the changeset')),
225 225 ('l', 'line-number', None, _('show line number at the first appearance'))
226 226 ] + diffwsopts + walkopts,
227 227 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
228 228 def annotate(ui, repo, *pats, **opts):
229 229 """show changeset information by line for each file
230 230
231 231 List changes in files, showing the revision id responsible for
232 232 each line
233 233
234 234 This command is useful for discovering when a change was made and
235 235 by whom.
236 236
237 237 Without the -a/--text option, annotate will avoid processing files
238 238 it detects as binary. With -a, annotate will annotate the file
239 239 anyway, although the results will probably be neither useful
240 240 nor desirable.
241 241
242 242 Returns 0 on success.
243 243 """
244 244 if opts.get('follow'):
245 245 # --follow is deprecated and now just an alias for -f/--file
246 246 # to mimic the behavior of Mercurial before version 1.5
247 247 opts['file'] = True
248 248
249 249 datefunc = ui.quiet and util.shortdate or util.datestr
250 250 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
251 251
252 252 if not pats:
253 253 raise util.Abort(_('at least one filename or pattern is required'))
254 254
255 255 hexfn = ui.debugflag and hex or short
256 256
257 257 opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
258 258 ('number', ' ', lambda x: str(x[0].rev())),
259 259 ('changeset', ' ', lambda x: hexfn(x[0].node())),
260 260 ('date', ' ', getdate),
261 261 ('file', ' ', lambda x: x[0].path()),
262 262 ('line_number', ':', lambda x: str(x[1])),
263 263 ]
264 264
265 265 if (not opts.get('user') and not opts.get('changeset')
266 266 and not opts.get('date') and not opts.get('file')):
267 267 opts['number'] = True
268 268
269 269 linenumber = opts.get('line_number') is not None
270 270 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
271 271 raise util.Abort(_('at least one of -n/-c is required for -l'))
272 272
273 273 funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
274 274 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
275 275
276 276 def bad(x, y):
277 277 raise util.Abort("%s: %s" % (x, y))
278 278
279 279 ctx = scmutil.revsingle(repo, opts.get('rev'))
280 280 m = scmutil.match(ctx, pats, opts)
281 281 m.bad = bad
282 282 follow = not opts.get('no_follow')
283 283 diffopts = patch.diffopts(ui, opts, section='annotate')
284 284 for abs in ctx.walk(m):
285 285 fctx = ctx[abs]
286 286 if not opts.get('text') and util.binary(fctx.data()):
287 287 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
288 288 continue
289 289
290 290 lines = fctx.annotate(follow=follow, linenumber=linenumber,
291 291 diffopts=diffopts)
292 292 pieces = []
293 293
294 294 for f, sep in funcmap:
295 295 l = [f(n) for n, dummy in lines]
296 296 if l:
297 297 sized = [(x, encoding.colwidth(x)) for x in l]
298 298 ml = max([w for x, w in sized])
299 299 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
300 300 for x, w in sized])
301 301
302 302 if pieces:
303 303 for p, l in zip(zip(*pieces), lines):
304 304 ui.write("%s: %s" % ("".join(p), l[1]))
305 305
306 306 if lines and not lines[-1][1].endswith('\n'):
307 307 ui.write('\n')
308 308
309 309 @command('archive',
310 310 [('', 'no-decode', None, _('do not pass files through decoders')),
311 311 ('p', 'prefix', '', _('directory prefix for files in archive'),
312 312 _('PREFIX')),
313 313 ('r', 'rev', '', _('revision to distribute'), _('REV')),
314 314 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
315 315 ] + subrepoopts + walkopts,
316 316 _('[OPTION]... DEST'))
317 317 def archive(ui, repo, dest, **opts):
318 318 '''create an unversioned archive of a repository revision
319 319
320 320 By default, the revision used is the parent of the working
321 321 directory; use -r/--rev to specify a different revision.
322 322
323 323 The archive type is automatically detected based on file
324 324 extension (or override using -t/--type).
325 325
326 326 .. container:: verbose
327 327
328 328 Examples:
329 329
330 330 - create a zip file containing the 1.0 release::
331 331
332 332 hg archive -r 1.0 project-1.0.zip
333 333
334 334 - create a tarball excluding .hg files::
335 335
336 336 hg archive project.tar.gz -X ".hg*"
337 337
338 338 Valid types are:
339 339
340 340 :``files``: a directory full of files (default)
341 341 :``tar``: tar archive, uncompressed
342 342 :``tbz2``: tar archive, compressed using bzip2
343 343 :``tgz``: tar archive, compressed using gzip
344 344 :``uzip``: zip archive, uncompressed
345 345 :``zip``: zip archive, compressed using deflate
346 346
347 347 The exact name of the destination archive or directory is given
348 348 using a format string; see :hg:`help export` for details.
349 349
350 350 Each member added to an archive file has a directory prefix
351 351 prepended. Use -p/--prefix to specify a format string for the
352 352 prefix. The default is the basename of the archive, with suffixes
353 353 removed.
354 354
355 355 Returns 0 on success.
356 356 '''
357 357
358 358 ctx = scmutil.revsingle(repo, opts.get('rev'))
359 359 if not ctx:
360 360 raise util.Abort(_('no working directory: please specify a revision'))
361 361 node = ctx.node()
362 362 dest = cmdutil.makefilename(repo, dest, node)
363 363 if os.path.realpath(dest) == repo.root:
364 364 raise util.Abort(_('repository root cannot be destination'))
365 365
366 366 kind = opts.get('type') or archival.guesskind(dest) or 'files'
367 367 prefix = opts.get('prefix')
368 368
369 369 if dest == '-':
370 370 if kind == 'files':
371 371 raise util.Abort(_('cannot archive plain files to stdout'))
372 372 dest = cmdutil.makefileobj(repo, dest)
373 373 if not prefix:
374 374 prefix = os.path.basename(repo.root) + '-%h'
375 375
376 376 prefix = cmdutil.makefilename(repo, prefix, node)
377 377 matchfn = scmutil.match(ctx, [], opts)
378 378 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
379 379 matchfn, prefix, subrepos=opts.get('subrepos'))
380 380
381 381 @command('backout',
382 382 [('', 'merge', None, _('merge with old dirstate parent after backout')),
383 383 ('', 'parent', '',
384 384 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
385 385 ('r', 'rev', '', _('revision to backout'), _('REV')),
386 386 ] + mergetoolopts + walkopts + commitopts + commitopts2,
387 387 _('[OPTION]... [-r] REV'))
388 388 def backout(ui, repo, node=None, rev=None, **opts):
389 389 '''reverse effect of earlier changeset
390 390
391 391 Prepare a new changeset with the effect of REV undone in the
392 392 current working directory.
393 393
394 394 If REV is the parent of the working directory, then this new changeset
395 395 is committed automatically. Otherwise, hg needs to merge the
396 396 changes and the merged result is left uncommitted.
397 397
398 398 .. note::
399 399 backout cannot be used to fix either an unwanted or
400 400 incorrect merge.
401 401
402 402 .. container:: verbose
403 403
404 404 By default, the pending changeset will have one parent,
405 405 maintaining a linear history. With --merge, the pending
406 406 changeset will instead have two parents: the old parent of the
407 407 working directory and a new child of REV that simply undoes REV.
408 408
409 409 Before version 1.7, the behavior without --merge was equivalent
410 410 to specifying --merge followed by :hg:`update --clean .` to
411 411 cancel the merge and leave the child of REV as a head to be
412 412 merged separately.
413 413
414 414 See :hg:`help dates` for a list of formats valid for -d/--date.
415 415
416 416 Returns 0 on success.
417 417 '''
418 418 if rev and node:
419 419 raise util.Abort(_("please specify just one revision"))
420 420
421 421 if not rev:
422 422 rev = node
423 423
424 424 if not rev:
425 425 raise util.Abort(_("please specify a revision to backout"))
426 426
427 427 date = opts.get('date')
428 428 if date:
429 429 opts['date'] = util.parsedate(date)
430 430
431 431 cmdutil.checkunfinished(repo)
432 432 cmdutil.bailifchanged(repo)
433 433 node = scmutil.revsingle(repo, rev).node()
434 434
435 435 op1, op2 = repo.dirstate.parents()
436 436 a = repo.changelog.ancestor(op1, node)
437 437 if a != node:
438 438 raise util.Abort(_('cannot backout change on a different branch'))
439 439
440 440 p1, p2 = repo.changelog.parents(node)
441 441 if p1 == nullid:
442 442 raise util.Abort(_('cannot backout a change with no parents'))
443 443 if p2 != nullid:
444 444 if not opts.get('parent'):
445 445 raise util.Abort(_('cannot backout a merge changeset'))
446 446 p = repo.lookup(opts['parent'])
447 447 if p not in (p1, p2):
448 448 raise util.Abort(_('%s is not a parent of %s') %
449 449 (short(p), short(node)))
450 450 parent = p
451 451 else:
452 452 if opts.get('parent'):
453 453 raise util.Abort(_('cannot use --parent on non-merge changeset'))
454 454 parent = p1
455 455
456 456 # the backout should appear on the same branch
457 457 wlock = repo.wlock()
458 458 try:
459 459 branch = repo.dirstate.branch()
460 460 bheads = repo.branchheads(branch)
461 461 hg.clean(repo, node, show_stats=False)
462 462 repo.dirstate.setbranch(branch)
463 463 rctx = scmutil.revsingle(repo, hex(parent))
464 464 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
465 465 if not opts.get('merge') and op1 != node:
466 466 try:
467 467 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
468 468 return hg.update(repo, op1)
469 469 finally:
470 470 ui.setconfig('ui', 'forcemerge', '')
471 471
472 472 e = cmdutil.commiteditor
473 473 if not opts['message'] and not opts['logfile']:
474 474 # we don't translate commit messages
475 475 opts['message'] = "Backed out changeset %s" % short(node)
476 476 e = cmdutil.commitforceeditor
477 477
478 478 def commitfunc(ui, repo, message, match, opts):
479 479 return repo.commit(message, opts.get('user'), opts.get('date'),
480 480 match, editor=e)
481 481 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
482 482 cmdutil.commitstatus(repo, newnode, branch, bheads)
483 483
484 484 def nice(node):
485 485 return '%d:%s' % (repo.changelog.rev(node), short(node))
486 486 ui.status(_('changeset %s backs out changeset %s\n') %
487 487 (nice(repo.changelog.tip()), nice(node)))
488 488 if opts.get('merge') and op1 != node:
489 489 hg.clean(repo, op1, show_stats=False)
490 490 ui.status(_('merging with changeset %s\n')
491 491 % nice(repo.changelog.tip()))
492 492 try:
493 493 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
494 494 return hg.merge(repo, hex(repo.changelog.tip()))
495 495 finally:
496 496 ui.setconfig('ui', 'forcemerge', '')
497 497 finally:
498 498 wlock.release()
499 499 return 0
500 500
501 501 @command('bisect',
502 502 [('r', 'reset', False, _('reset bisect state')),
503 503 ('g', 'good', False, _('mark changeset good')),
504 504 ('b', 'bad', False, _('mark changeset bad')),
505 505 ('s', 'skip', False, _('skip testing changeset')),
506 506 ('e', 'extend', False, _('extend the bisect range')),
507 507 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
508 508 ('U', 'noupdate', False, _('do not update to target'))],
509 509 _("[-gbsr] [-U] [-c CMD] [REV]"))
510 510 def bisect(ui, repo, rev=None, extra=None, command=None,
511 511 reset=None, good=None, bad=None, skip=None, extend=None,
512 512 noupdate=None):
513 513 """subdivision search of changesets
514 514
515 515 This command helps to find changesets which introduce problems. To
516 516 use, mark the earliest changeset you know exhibits the problem as
517 517 bad, then mark the latest changeset which is free from the problem
518 518 as good. Bisect will update your working directory to a revision
519 519 for testing (unless the -U/--noupdate option is specified). Once
520 520 you have performed tests, mark the working directory as good or
521 521 bad, and bisect will either update to another candidate changeset
522 522 or announce that it has found the bad revision.
523 523
524 524 As a shortcut, you can also use the revision argument to mark a
525 525 revision as good or bad without checking it out first.
526 526
527 527 If you supply a command, it will be used for automatic bisection.
528 528 The environment variable HG_NODE will contain the ID of the
529 529 changeset being tested. The exit status of the command will be
530 530 used to mark revisions as good or bad: status 0 means good, 125
531 531 means to skip the revision, 127 (command not found) will abort the
532 532 bisection, and any other non-zero exit status means the revision
533 533 is bad.
534 534
535 535 .. container:: verbose
536 536
537 537 Some examples:
538 538
539 539 - start a bisection with known bad revision 12, and good revision 34::
540 540
541 541 hg bisect --bad 34
542 542 hg bisect --good 12
543 543
544 544 - advance the current bisection by marking current revision as good or
545 545 bad::
546 546
547 547 hg bisect --good
548 548 hg bisect --bad
549 549
550 550 - mark the current revision, or a known revision, to be skipped (e.g. if
551 551 that revision is not usable because of another issue)::
552 552
553 553 hg bisect --skip
554 554 hg bisect --skip 23
555 555
556 556 - skip all revisions that do not touch directories ``foo`` or ``bar``
557 557
558 558 hg bisect --skip '!( file("path:foo") & file("path:bar") )'
559 559
560 560 - forget the current bisection::
561 561
562 562 hg bisect --reset
563 563
564 564 - use 'make && make tests' to automatically find the first broken
565 565 revision::
566 566
567 567 hg bisect --reset
568 568 hg bisect --bad 34
569 569 hg bisect --good 12
570 570 hg bisect --command 'make && make tests'
571 571
572 572 - see all changesets whose states are already known in the current
573 573 bisection::
574 574
575 575 hg log -r "bisect(pruned)"
576 576
577 577 - see the changeset currently being bisected (especially useful
578 578 if running with -U/--noupdate)::
579 579
580 580 hg log -r "bisect(current)"
581 581
582 582 - see all changesets that took part in the current bisection::
583 583
584 584 hg log -r "bisect(range)"
585 585
586 586 - with the graphlog extension, you can even get a nice graph::
587 587
588 588 hg log --graph -r "bisect(range)"
589 589
590 590 See :hg:`help revsets` for more about the `bisect()` keyword.
591 591
592 592 Returns 0 on success.
593 593 """
594 594 def extendbisectrange(nodes, good):
595 595 # bisect is incomplete when it ends on a merge node and
596 596 # one of the parent was not checked.
597 597 parents = repo[nodes[0]].parents()
598 598 if len(parents) > 1:
599 599 side = good and state['bad'] or state['good']
600 600 num = len(set(i.node() for i in parents) & set(side))
601 601 if num == 1:
602 602 return parents[0].ancestor(parents[1])
603 603 return None
604 604
605 605 def print_result(nodes, good):
606 606 displayer = cmdutil.show_changeset(ui, repo, {})
607 607 if len(nodes) == 1:
608 608 # narrowed it down to a single revision
609 609 if good:
610 610 ui.write(_("The first good revision is:\n"))
611 611 else:
612 612 ui.write(_("The first bad revision is:\n"))
613 613 displayer.show(repo[nodes[0]])
614 614 extendnode = extendbisectrange(nodes, good)
615 615 if extendnode is not None:
616 616 ui.write(_('Not all ancestors of this changeset have been'
617 617 ' checked.\nUse bisect --extend to continue the '
618 618 'bisection from\nthe common ancestor, %s.\n')
619 619 % extendnode)
620 620 else:
621 621 # multiple possible revisions
622 622 if good:
623 623 ui.write(_("Due to skipped revisions, the first "
624 624 "good revision could be any of:\n"))
625 625 else:
626 626 ui.write(_("Due to skipped revisions, the first "
627 627 "bad revision could be any of:\n"))
628 628 for n in nodes:
629 629 displayer.show(repo[n])
630 630 displayer.close()
631 631
632 632 def check_state(state, interactive=True):
633 633 if not state['good'] or not state['bad']:
634 634 if (good or bad or skip or reset) and interactive:
635 635 return
636 636 if not state['good']:
637 637 raise util.Abort(_('cannot bisect (no known good revisions)'))
638 638 else:
639 639 raise util.Abort(_('cannot bisect (no known bad revisions)'))
640 640 return True
641 641
642 642 # backward compatibility
643 643 if rev in "good bad reset init".split():
644 644 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
645 645 cmd, rev, extra = rev, extra, None
646 646 if cmd == "good":
647 647 good = True
648 648 elif cmd == "bad":
649 649 bad = True
650 650 else:
651 651 reset = True
652 652 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
653 653 raise util.Abort(_('incompatible arguments'))
654 654
655 655 cmdutil.checkunfinished(repo)
656 656
657 657 if reset:
658 658 p = repo.join("bisect.state")
659 659 if os.path.exists(p):
660 660 os.unlink(p)
661 661 return
662 662
663 663 state = hbisect.load_state(repo)
664 664
665 665 if command:
666 666 changesets = 1
667 667 try:
668 668 node = state['current'][0]
669 669 except LookupError:
670 670 if noupdate:
671 671 raise util.Abort(_('current bisect revision is unknown - '
672 672 'start a new bisect to fix'))
673 673 node, p2 = repo.dirstate.parents()
674 674 if p2 != nullid:
675 675 raise util.Abort(_('current bisect revision is a merge'))
676 676 try:
677 677 while changesets:
678 678 # update state
679 679 state['current'] = [node]
680 680 hbisect.save_state(repo, state)
681 681 status = util.system(command,
682 682 environ={'HG_NODE': hex(node)},
683 683 out=ui.fout)
684 684 if status == 125:
685 685 transition = "skip"
686 686 elif status == 0:
687 687 transition = "good"
688 688 # status < 0 means process was killed
689 689 elif status == 127:
690 690 raise util.Abort(_("failed to execute %s") % command)
691 691 elif status < 0:
692 692 raise util.Abort(_("%s killed") % command)
693 693 else:
694 694 transition = "bad"
695 695 ctx = scmutil.revsingle(repo, rev, node)
696 696 rev = None # clear for future iterations
697 697 state[transition].append(ctx.node())
698 698 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
699 699 check_state(state, interactive=False)
700 700 # bisect
701 701 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
702 702 # update to next check
703 703 node = nodes[0]
704 704 if not noupdate:
705 705 cmdutil.bailifchanged(repo)
706 706 hg.clean(repo, node, show_stats=False)
707 707 finally:
708 708 state['current'] = [node]
709 709 hbisect.save_state(repo, state)
710 710 print_result(nodes, good)
711 711 return
712 712
713 713 # update state
714 714
715 715 if rev:
716 716 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
717 717 else:
718 718 nodes = [repo.lookup('.')]
719 719
720 720 if good or bad or skip:
721 721 if good:
722 722 state['good'] += nodes
723 723 elif bad:
724 724 state['bad'] += nodes
725 725 elif skip:
726 726 state['skip'] += nodes
727 727 hbisect.save_state(repo, state)
728 728
729 729 if not check_state(state):
730 730 return
731 731
732 732 # actually bisect
733 733 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
734 734 if extend:
735 735 if not changesets:
736 736 extendnode = extendbisectrange(nodes, good)
737 737 if extendnode is not None:
738 738 ui.write(_("Extending search to changeset %d:%s\n"
739 739 % (extendnode.rev(), extendnode)))
740 740 state['current'] = [extendnode.node()]
741 741 hbisect.save_state(repo, state)
742 742 if noupdate:
743 743 return
744 744 cmdutil.bailifchanged(repo)
745 745 return hg.clean(repo, extendnode.node())
746 746 raise util.Abort(_("nothing to extend"))
747 747
748 748 if changesets == 0:
749 749 print_result(nodes, good)
750 750 else:
751 751 assert len(nodes) == 1 # only a single node can be tested next
752 752 node = nodes[0]
753 753 # compute the approximate number of remaining tests
754 754 tests, size = 0, 2
755 755 while size <= changesets:
756 756 tests, size = tests + 1, size * 2
757 757 rev = repo.changelog.rev(node)
758 758 ui.write(_("Testing changeset %d:%s "
759 759 "(%d changesets remaining, ~%d tests)\n")
760 760 % (rev, short(node), changesets, tests))
761 761 state['current'] = [node]
762 762 hbisect.save_state(repo, state)
763 763 if not noupdate:
764 764 cmdutil.bailifchanged(repo)
765 765 return hg.clean(repo, node)
766 766
767 767 @command('bookmarks|bookmark',
768 768 [('f', 'force', False, _('force')),
769 769 ('r', 'rev', '', _('revision'), _('REV')),
770 770 ('d', 'delete', False, _('delete a given bookmark')),
771 771 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
772 772 ('i', 'inactive', False, _('mark a bookmark inactive'))],
773 773 _('hg bookmarks [OPTIONS]... [NAME]...'))
774 774 def bookmark(ui, repo, *names, **opts):
775 775 '''track a line of development with movable markers
776 776
777 777 Bookmarks are pointers to certain commits that move when committing.
778 778 Bookmarks are local. They can be renamed, copied and deleted. It is
779 779 possible to use :hg:`merge NAME` to merge from a given bookmark, and
780 780 :hg:`update NAME` to update to a given bookmark.
781 781
782 782 You can use :hg:`bookmark NAME` to set a bookmark on the working
783 783 directory's parent revision with the given name. If you specify
784 784 a revision using -r REV (where REV may be an existing bookmark),
785 785 the bookmark is assigned to that revision.
786 786
787 787 Bookmarks can be pushed and pulled between repositories (see :hg:`help
788 788 push` and :hg:`help pull`). This requires both the local and remote
789 789 repositories to support bookmarks. For versions prior to 1.8, this means
790 790 the bookmarks extension must be enabled.
791 791
792 792 If you set a bookmark called '@', new clones of the repository will
793 793 have that revision checked out (and the bookmark made active) by
794 794 default.
795 795
796 796 With -i/--inactive, the new bookmark will not be made the active
797 797 bookmark. If -r/--rev is given, the new bookmark will not be made
798 798 active even if -i/--inactive is not given. If no NAME is given, the
799 799 current active bookmark will be marked inactive.
800 800 '''
801 801 force = opts.get('force')
802 802 rev = opts.get('rev')
803 803 delete = opts.get('delete')
804 804 rename = opts.get('rename')
805 805 inactive = opts.get('inactive')
806 806
807 807 hexfn = ui.debugflag and hex or short
808 808 marks = repo._bookmarks
809 809 cur = repo.changectx('.').node()
810 810
811 811 def checkformat(mark):
812 812 mark = mark.strip()
813 813 if not mark:
814 814 raise util.Abort(_("bookmark names cannot consist entirely of "
815 815 "whitespace"))
816 816 scmutil.checknewlabel(repo, mark, 'bookmark')
817 817 return mark
818 818
819 819 def checkconflict(repo, mark, force=False, target=None):
820 820 if mark in marks and not force:
821 821 if target:
822 822 if marks[mark] == target and target == cur:
823 823 # re-activating a bookmark
824 824 return
825 825 anc = repo.changelog.ancestors([repo[target].rev()])
826 826 bmctx = repo[marks[mark]]
827 827 divs = [repo[b].node() for b in marks
828 828 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
829 829
830 830 # allow resolving a single divergent bookmark even if moving
831 831 # the bookmark across branches when a revision is specified
832 832 # that contains a divergent bookmark
833 833 if bmctx.rev() not in anc and target in divs:
834 834 bookmarks.deletedivergent(repo, [target], mark)
835 835 return
836 836
837 837 deletefrom = [b for b in divs
838 838 if repo[b].rev() in anc or b == target]
839 839 bookmarks.deletedivergent(repo, deletefrom, mark)
840 840 if bmctx.rev() in anc:
841 841 ui.status(_("moving bookmark '%s' forward from %s\n") %
842 842 (mark, short(bmctx.node())))
843 843 return
844 844 raise util.Abort(_("bookmark '%s' already exists "
845 845 "(use -f to force)") % mark)
846 846 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
847 847 and not force):
848 848 raise util.Abort(
849 849 _("a bookmark cannot have the name of an existing branch"))
850 850
851 851 if delete and rename:
852 852 raise util.Abort(_("--delete and --rename are incompatible"))
853 853 if delete and rev:
854 854 raise util.Abort(_("--rev is incompatible with --delete"))
855 855 if rename and rev:
856 856 raise util.Abort(_("--rev is incompatible with --rename"))
857 857 if not names and (delete or rev):
858 858 raise util.Abort(_("bookmark name required"))
859 859
860 860 if delete:
861 861 for mark in names:
862 862 if mark not in marks:
863 863 raise util.Abort(_("bookmark '%s' does not exist") % mark)
864 864 if mark == repo._bookmarkcurrent:
865 865 bookmarks.setcurrent(repo, None)
866 866 del marks[mark]
867 867 marks.write()
868 868
869 869 elif rename:
870 870 if not names:
871 871 raise util.Abort(_("new bookmark name required"))
872 872 elif len(names) > 1:
873 873 raise util.Abort(_("only one new bookmark name allowed"))
874 874 mark = checkformat(names[0])
875 875 if rename not in marks:
876 876 raise util.Abort(_("bookmark '%s' does not exist") % rename)
877 877 checkconflict(repo, mark, force)
878 878 marks[mark] = marks[rename]
879 879 if repo._bookmarkcurrent == rename and not inactive:
880 880 bookmarks.setcurrent(repo, mark)
881 881 del marks[rename]
882 882 marks.write()
883 883
884 884 elif names:
885 885 newact = None
886 886 for mark in names:
887 887 mark = checkformat(mark)
888 888 if newact is None:
889 889 newact = mark
890 890 if inactive and mark == repo._bookmarkcurrent:
891 891 bookmarks.setcurrent(repo, None)
892 892 return
893 893 tgt = cur
894 894 if rev:
895 895 tgt = scmutil.revsingle(repo, rev).node()
896 896 checkconflict(repo, mark, force, tgt)
897 897 marks[mark] = tgt
898 898 if not inactive and cur == marks[newact] and not rev:
899 899 bookmarks.setcurrent(repo, newact)
900 900 elif cur != tgt and newact == repo._bookmarkcurrent:
901 901 bookmarks.setcurrent(repo, None)
902 902 marks.write()
903 903
904 904 # Same message whether trying to deactivate the current bookmark (-i
905 905 # with no NAME) or listing bookmarks
906 906 elif len(marks) == 0:
907 907 ui.status(_("no bookmarks set\n"))
908 908
909 909 elif inactive:
910 910 if not repo._bookmarkcurrent:
911 911 ui.status(_("no active bookmark\n"))
912 912 else:
913 913 bookmarks.setcurrent(repo, None)
914 914
915 915 else: # show bookmarks
916 916 for bmark, n in sorted(marks.iteritems()):
917 917 current = repo._bookmarkcurrent
918 918 if bmark == current:
919 919 prefix, label = '*', 'bookmarks.current'
920 920 else:
921 921 prefix, label = ' ', ''
922 922
923 923 if ui.quiet:
924 924 ui.write("%s\n" % bmark, label=label)
925 925 else:
926 926 ui.write(" %s %-25s %d:%s\n" % (
927 927 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
928 928 label=label)
929 929
930 930 @command('branch',
931 931 [('f', 'force', None,
932 932 _('set branch name even if it shadows an existing branch')),
933 933 ('C', 'clean', None, _('reset branch name to parent branch name'))],
934 934 _('[-fC] [NAME]'))
935 935 def branch(ui, repo, label=None, **opts):
936 936 """set or show the current branch name
937 937
938 938 .. note::
939 939 Branch names are permanent and global. Use :hg:`bookmark` to create a
940 940 light-weight bookmark instead. See :hg:`help glossary` for more
941 941 information about named branches and bookmarks.
942 942
943 943 With no argument, show the current branch name. With one argument,
944 944 set the working directory branch name (the branch will not exist
945 945 in the repository until the next commit). Standard practice
946 946 recommends that primary development take place on the 'default'
947 947 branch.
948 948
949 949 Unless -f/--force is specified, branch will not let you set a
950 950 branch name that already exists, even if it's inactive.
951 951
952 952 Use -C/--clean to reset the working directory branch to that of
953 953 the parent of the working directory, negating a previous branch
954 954 change.
955 955
956 956 Use the command :hg:`update` to switch to an existing branch. Use
957 957 :hg:`commit --close-branch` to mark this branch as closed.
958 958
959 959 Returns 0 on success.
960 960 """
961 961 if label:
962 962 label = label.strip()
963 963
964 964 if not opts.get('clean') and not label:
965 965 ui.write("%s\n" % repo.dirstate.branch())
966 966 return
967 967
968 968 wlock = repo.wlock()
969 969 try:
970 970 if opts.get('clean'):
971 971 label = repo[None].p1().branch()
972 972 repo.dirstate.setbranch(label)
973 973 ui.status(_('reset working directory to branch %s\n') % label)
974 974 elif label:
975 975 if not opts.get('force') and label in repo.branchmap():
976 976 if label not in [p.branch() for p in repo.parents()]:
977 977 raise util.Abort(_('a branch of the same name already'
978 978 ' exists'),
979 979 # i18n: "it" refers to an existing branch
980 980 hint=_("use 'hg update' to switch to it"))
981 981 scmutil.checknewlabel(repo, label, 'branch')
982 982 repo.dirstate.setbranch(label)
983 983 ui.status(_('marked working directory as branch %s\n') % label)
984 984 ui.status(_('(branches are permanent and global, '
985 985 'did you want a bookmark?)\n'))
986 986 finally:
987 987 wlock.release()
988 988
989 989 @command('branches',
990 990 [('a', 'active', False, _('show only branches that have unmerged heads')),
991 991 ('c', 'closed', False, _('show normal and closed branches'))],
992 992 _('[-ac]'))
993 993 def branches(ui, repo, active=False, closed=False):
994 994 """list repository named branches
995 995
996 996 List the repository's named branches, indicating which ones are
997 997 inactive. If -c/--closed is specified, also list branches which have
998 998 been marked closed (see :hg:`commit --close-branch`).
999 999
1000 1000 If -a/--active is specified, only show active branches. A branch
1001 1001 is considered active if it contains repository heads.
1002 1002
1003 1003 Use the command :hg:`update` to switch to an existing branch.
1004 1004
1005 1005 Returns 0.
1006 1006 """
1007 1007
1008 1008 hexfunc = ui.debugflag and hex or short
1009 1009
1010 1010 activebranches = set([repo[n].branch() for n in repo.heads()])
1011 1011 branches = []
1012 1012 for tag, heads in repo.branchmap().iteritems():
1013 1013 for h in reversed(heads):
1014 1014 ctx = repo[h]
1015 1015 isopen = not ctx.closesbranch()
1016 1016 if isopen:
1017 1017 tip = ctx
1018 1018 break
1019 1019 else:
1020 1020 tip = repo[heads[-1]]
1021 1021 isactive = tag in activebranches and isopen
1022 1022 branches.append((tip, isactive, isopen))
1023 1023 branches.sort(key=lambda i: (i[1], i[0].rev(), i[0].branch(), i[2]),
1024 1024 reverse=True)
1025 1025
1026 1026 for ctx, isactive, isopen in branches:
1027 1027 if (not active) or isactive:
1028 1028 if isactive:
1029 1029 label = 'branches.active'
1030 1030 notice = ''
1031 1031 elif not isopen:
1032 1032 if not closed:
1033 1033 continue
1034 1034 label = 'branches.closed'
1035 1035 notice = _(' (closed)')
1036 1036 else:
1037 1037 label = 'branches.inactive'
1038 1038 notice = _(' (inactive)')
1039 1039 if ctx.branch() == repo.dirstate.branch():
1040 1040 label = 'branches.current'
1041 1041 rev = str(ctx.rev()).rjust(31 - encoding.colwidth(ctx.branch()))
1042 1042 rev = ui.label('%s:%s' % (rev, hexfunc(ctx.node())),
1043 1043 'log.changeset changeset.%s' % ctx.phasestr())
1044 1044 tag = ui.label(ctx.branch(), label)
1045 1045 if ui.quiet:
1046 1046 ui.write("%s\n" % tag)
1047 1047 else:
1048 1048 ui.write("%s %s%s\n" % (tag, rev, notice))
1049 1049
1050 1050 @command('bundle',
1051 1051 [('f', 'force', None, _('run even when the destination is unrelated')),
1052 1052 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1053 1053 _('REV')),
1054 1054 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1055 1055 _('BRANCH')),
1056 1056 ('', 'base', [],
1057 1057 _('a base changeset assumed to be available at the destination'),
1058 1058 _('REV')),
1059 1059 ('a', 'all', None, _('bundle all changesets in the repository')),
1060 1060 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1061 1061 ] + remoteopts,
1062 1062 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1063 1063 def bundle(ui, repo, fname, dest=None, **opts):
1064 1064 """create a changegroup file
1065 1065
1066 1066 Generate a compressed changegroup file collecting changesets not
1067 1067 known to be in another repository.
1068 1068
1069 1069 If you omit the destination repository, then hg assumes the
1070 1070 destination will have all the nodes you specify with --base
1071 1071 parameters. To create a bundle containing all changesets, use
1072 1072 -a/--all (or --base null).
1073 1073
1074 1074 You can change compression method with the -t/--type option.
1075 1075 The available compression methods are: none, bzip2, and
1076 1076 gzip (by default, bundles are compressed using bzip2).
1077 1077
1078 1078 The bundle file can then be transferred using conventional means
1079 1079 and applied to another repository with the unbundle or pull
1080 1080 command. This is useful when direct push and pull are not
1081 1081 available or when exporting an entire repository is undesirable.
1082 1082
1083 1083 Applying bundles preserves all changeset contents including
1084 1084 permissions, copy/rename information, and revision history.
1085 1085
1086 1086 Returns 0 on success, 1 if no changes found.
1087 1087 """
1088 1088 revs = None
1089 1089 if 'rev' in opts:
1090 1090 revs = scmutil.revrange(repo, opts['rev'])
1091 1091
1092 1092 bundletype = opts.get('type', 'bzip2').lower()
1093 1093 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1094 1094 bundletype = btypes.get(bundletype)
1095 1095 if bundletype not in changegroup.bundletypes:
1096 1096 raise util.Abort(_('unknown bundle type specified with --type'))
1097 1097
1098 1098 if opts.get('all'):
1099 1099 base = ['null']
1100 1100 else:
1101 1101 base = scmutil.revrange(repo, opts.get('base'))
1102 1102 # TODO: get desired bundlecaps from command line.
1103 1103 bundlecaps = None
1104 1104 if base:
1105 1105 if dest:
1106 1106 raise util.Abort(_("--base is incompatible with specifying "
1107 1107 "a destination"))
1108 1108 common = [repo.lookup(rev) for rev in base]
1109 1109 heads = revs and map(repo.lookup, revs) or revs
1110 1110 cg = repo.getbundle('bundle', heads=heads, common=common,
1111 1111 bundlecaps=bundlecaps)
1112 1112 outgoing = None
1113 1113 else:
1114 1114 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1115 1115 dest, branches = hg.parseurl(dest, opts.get('branch'))
1116 1116 other = hg.peer(repo, opts, dest)
1117 1117 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1118 1118 heads = revs and map(repo.lookup, revs) or revs
1119 1119 outgoing = discovery.findcommonoutgoing(repo, other,
1120 1120 onlyheads=heads,
1121 1121 force=opts.get('force'),
1122 1122 portable=True)
1123 1123 cg = repo.getlocalbundle('bundle', outgoing, bundlecaps)
1124 1124 if not cg:
1125 1125 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1126 1126 return 1
1127 1127
1128 1128 changegroup.writebundle(cg, fname, bundletype)
1129 1129
1130 1130 @command('cat',
1131 1131 [('o', 'output', '',
1132 1132 _('print output to file with formatted name'), _('FORMAT')),
1133 1133 ('r', 'rev', '', _('print the given revision'), _('REV')),
1134 1134 ('', 'decode', None, _('apply any matching decode filter')),
1135 1135 ] + walkopts,
1136 1136 _('[OPTION]... FILE...'))
1137 1137 def cat(ui, repo, file1, *pats, **opts):
1138 1138 """output the current or given revision of files
1139 1139
1140 1140 Print the specified files as they were at the given revision. If
1141 1141 no revision is given, the parent of the working directory is used.
1142 1142
1143 1143 Output may be to a file, in which case the name of the file is
1144 1144 given using a format string. The formatting rules are the same as
1145 1145 for the export command, with the following additions:
1146 1146
1147 1147 :``%s``: basename of file being printed
1148 1148 :``%d``: dirname of file being printed, or '.' if in repository root
1149 1149 :``%p``: root-relative path name of file being printed
1150 1150
1151 1151 Returns 0 on success.
1152 1152 """
1153 1153 ctx = scmutil.revsingle(repo, opts.get('rev'))
1154 1154 err = 1
1155 1155 m = scmutil.match(ctx, (file1,) + pats, opts)
1156 1156 for abs in ctx.walk(m):
1157 1157 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1158 1158 pathname=abs)
1159 1159 data = ctx[abs].data()
1160 1160 if opts.get('decode'):
1161 1161 data = repo.wwritedata(abs, data)
1162 1162 fp.write(data)
1163 1163 fp.close()
1164 1164 err = 0
1165 1165 return err
1166 1166
1167 1167 @command('^clone',
1168 1168 [('U', 'noupdate', None,
1169 1169 _('the clone will include an empty working copy (only a repository)')),
1170 1170 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1171 1171 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1172 1172 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1173 1173 ('', 'pull', None, _('use pull protocol to copy metadata')),
1174 1174 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1175 1175 ] + remoteopts,
1176 1176 _('[OPTION]... SOURCE [DEST]'))
1177 1177 def clone(ui, source, dest=None, **opts):
1178 1178 """make a copy of an existing repository
1179 1179
1180 1180 Create a copy of an existing repository in a new directory.
1181 1181
1182 1182 If no destination directory name is specified, it defaults to the
1183 1183 basename of the source.
1184 1184
1185 1185 The location of the source is added to the new repository's
1186 1186 ``.hg/hgrc`` file, as the default to be used for future pulls.
1187 1187
1188 1188 Only local paths and ``ssh://`` URLs are supported as
1189 1189 destinations. For ``ssh://`` destinations, no working directory or
1190 1190 ``.hg/hgrc`` will be created on the remote side.
1191 1191
1192 1192 To pull only a subset of changesets, specify one or more revisions
1193 1193 identifiers with -r/--rev or branches with -b/--branch. The
1194 1194 resulting clone will contain only the specified changesets and
1195 1195 their ancestors. These options (or 'clone src#rev dest') imply
1196 1196 --pull, even for local source repositories. Note that specifying a
1197 1197 tag will include the tagged changeset but not the changeset
1198 1198 containing the tag.
1199 1199
1200 1200 If the source repository has a bookmark called '@' set, that
1201 1201 revision will be checked out in the new repository by default.
1202 1202
1203 1203 To check out a particular version, use -u/--update, or
1204 1204 -U/--noupdate to create a clone with no working directory.
1205 1205
1206 1206 .. container:: verbose
1207 1207
1208 1208 For efficiency, hardlinks are used for cloning whenever the
1209 1209 source and destination are on the same filesystem (note this
1210 1210 applies only to the repository data, not to the working
1211 1211 directory). Some filesystems, such as AFS, implement hardlinking
1212 1212 incorrectly, but do not report errors. In these cases, use the
1213 1213 --pull option to avoid hardlinking.
1214 1214
1215 1215 In some cases, you can clone repositories and the working
1216 1216 directory using full hardlinks with ::
1217 1217
1218 1218 $ cp -al REPO REPOCLONE
1219 1219
1220 1220 This is the fastest way to clone, but it is not always safe. The
1221 1221 operation is not atomic (making sure REPO is not modified during
1222 1222 the operation is up to you) and you have to make sure your
1223 1223 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1224 1224 so). Also, this is not compatible with certain extensions that
1225 1225 place their metadata under the .hg directory, such as mq.
1226 1226
1227 1227 Mercurial will update the working directory to the first applicable
1228 1228 revision from this list:
1229 1229
1230 1230 a) null if -U or the source repository has no changesets
1231 1231 b) if -u . and the source repository is local, the first parent of
1232 1232 the source repository's working directory
1233 1233 c) the changeset specified with -u (if a branch name, this means the
1234 1234 latest head of that branch)
1235 1235 d) the changeset specified with -r
1236 1236 e) the tipmost head specified with -b
1237 1237 f) the tipmost head specified with the url#branch source syntax
1238 1238 g) the revision marked with the '@' bookmark, if present
1239 1239 h) the tipmost head of the default branch
1240 1240 i) tip
1241 1241
1242 1242 Examples:
1243 1243
1244 1244 - clone a remote repository to a new directory named hg/::
1245 1245
1246 1246 hg clone http://selenic.com/hg
1247 1247
1248 1248 - create a lightweight local clone::
1249 1249
1250 1250 hg clone project/ project-feature/
1251 1251
1252 1252 - clone from an absolute path on an ssh server (note double-slash)::
1253 1253
1254 1254 hg clone ssh://user@server//home/projects/alpha/
1255 1255
1256 1256 - do a high-speed clone over a LAN while checking out a
1257 1257 specified version::
1258 1258
1259 1259 hg clone --uncompressed http://server/repo -u 1.5
1260 1260
1261 1261 - create a repository without changesets after a particular revision::
1262 1262
1263 1263 hg clone -r 04e544 experimental/ good/
1264 1264
1265 1265 - clone (and track) a particular named branch::
1266 1266
1267 1267 hg clone http://selenic.com/hg#stable
1268 1268
1269 1269 See :hg:`help urls` for details on specifying URLs.
1270 1270
1271 1271 Returns 0 on success.
1272 1272 """
1273 1273 if opts.get('noupdate') and opts.get('updaterev'):
1274 1274 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1275 1275
1276 1276 r = hg.clone(ui, opts, source, dest,
1277 1277 pull=opts.get('pull'),
1278 1278 stream=opts.get('uncompressed'),
1279 1279 rev=opts.get('rev'),
1280 1280 update=opts.get('updaterev') or not opts.get('noupdate'),
1281 1281 branch=opts.get('branch'))
1282 1282
1283 1283 return r is None
1284 1284
1285 1285 @command('^commit|ci',
1286 1286 [('A', 'addremove', None,
1287 1287 _('mark new/missing files as added/removed before committing')),
1288 1288 ('', 'close-branch', None,
1289 1289 _('mark a branch as closed, hiding it from the branch list')),
1290 1290 ('', 'amend', None, _('amend the parent of the working dir')),
1291 1291 ('s', 'secret', None, _('use the secret phase for committing')),
1292 1292 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1293 1293 _('[OPTION]... [FILE]...'))
1294 1294 def commit(ui, repo, *pats, **opts):
1295 1295 """commit the specified files or all outstanding changes
1296 1296
1297 1297 Commit changes to the given files into the repository. Unlike a
1298 1298 centralized SCM, this operation is a local operation. See
1299 1299 :hg:`push` for a way to actively distribute your changes.
1300 1300
1301 1301 If a list of files is omitted, all changes reported by :hg:`status`
1302 1302 will be committed.
1303 1303
1304 1304 If you are committing the result of a merge, do not provide any
1305 1305 filenames or -I/-X filters.
1306 1306
1307 1307 If no commit message is specified, Mercurial starts your
1308 1308 configured editor where you can enter a message. In case your
1309 1309 commit fails, you will find a backup of your message in
1310 1310 ``.hg/last-message.txt``.
1311 1311
1312 1312 The --amend flag can be used to amend the parent of the
1313 1313 working directory with a new commit that contains the changes
1314 1314 in the parent in addition to those currently reported by :hg:`status`,
1315 1315 if there are any. The old commit is stored in a backup bundle in
1316 1316 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1317 1317 on how to restore it).
1318 1318
1319 1319 Message, user and date are taken from the amended commit unless
1320 1320 specified. When a message isn't specified on the command line,
1321 1321 the editor will open with the message of the amended commit.
1322 1322
1323 1323 It is not possible to amend public changesets (see :hg:`help phases`)
1324 1324 or changesets that have children.
1325 1325
1326 1326 See :hg:`help dates` for a list of formats valid for -d/--date.
1327 1327
1328 1328 Returns 0 on success, 1 if nothing changed.
1329 1329 """
1330 1330 if opts.get('subrepos'):
1331 1331 if opts.get('amend'):
1332 1332 raise util.Abort(_('cannot amend with --subrepos'))
1333 1333 # Let --subrepos on the command line override config setting.
1334 1334 ui.setconfig('ui', 'commitsubrepos', True)
1335 1335
1336 1336 # Save this for restoring it later
1337 1337 oldcommitphase = ui.config('phases', 'new-commit')
1338 1338
1339 cmdutil.checkunfinished(repo)
1339 cmdutil.checkunfinished(repo, commit=True)
1340 1340
1341 1341 branch = repo[None].branch()
1342 1342 bheads = repo.branchheads(branch)
1343 1343
1344 1344 extra = {}
1345 1345 if opts.get('close_branch'):
1346 1346 extra['close'] = 1
1347 1347
1348 1348 if not bheads:
1349 1349 raise util.Abort(_('can only close branch heads'))
1350 1350 elif opts.get('amend'):
1351 1351 if repo.parents()[0].p1().branch() != branch and \
1352 1352 repo.parents()[0].p2().branch() != branch:
1353 1353 raise util.Abort(_('can only close branch heads'))
1354 1354
1355 1355 if opts.get('amend'):
1356 1356 if ui.configbool('ui', 'commitsubrepos'):
1357 1357 raise util.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1358 1358
1359 1359 old = repo['.']
1360 1360 if old.phase() == phases.public:
1361 1361 raise util.Abort(_('cannot amend public changesets'))
1362 1362 if len(repo[None].parents()) > 1:
1363 1363 raise util.Abort(_('cannot amend while merging'))
1364 1364 if (not obsolete._enabled) and old.children():
1365 1365 raise util.Abort(_('cannot amend changeset with children'))
1366 1366
1367 1367 e = cmdutil.commiteditor
1368 1368 if opts.get('force_editor'):
1369 1369 e = cmdutil.commitforceeditor
1370 1370
1371 1371 def commitfunc(ui, repo, message, match, opts):
1372 1372 editor = e
1373 1373 # message contains text from -m or -l, if it's empty,
1374 1374 # open the editor with the old message
1375 1375 if not message:
1376 1376 message = old.description()
1377 1377 editor = cmdutil.commitforceeditor
1378 1378 try:
1379 1379 if opts.get('secret'):
1380 1380 ui.setconfig('phases', 'new-commit', 'secret')
1381 1381
1382 1382 return repo.commit(message,
1383 1383 opts.get('user') or old.user(),
1384 1384 opts.get('date') or old.date(),
1385 1385 match,
1386 1386 editor=editor,
1387 1387 extra=extra)
1388 1388 finally:
1389 1389 ui.setconfig('phases', 'new-commit', oldcommitphase)
1390 1390
1391 1391 current = repo._bookmarkcurrent
1392 1392 marks = old.bookmarks()
1393 1393 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1394 1394 if node == old.node():
1395 1395 ui.status(_("nothing changed\n"))
1396 1396 return 1
1397 1397 elif marks:
1398 1398 ui.debug('moving bookmarks %r from %s to %s\n' %
1399 1399 (marks, old.hex(), hex(node)))
1400 1400 newmarks = repo._bookmarks
1401 1401 for bm in marks:
1402 1402 newmarks[bm] = node
1403 1403 if bm == current:
1404 1404 bookmarks.setcurrent(repo, bm)
1405 1405 newmarks.write()
1406 1406 else:
1407 1407 e = cmdutil.commiteditor
1408 1408 if opts.get('force_editor'):
1409 1409 e = cmdutil.commitforceeditor
1410 1410
1411 1411 def commitfunc(ui, repo, message, match, opts):
1412 1412 try:
1413 1413 if opts.get('secret'):
1414 1414 ui.setconfig('phases', 'new-commit', 'secret')
1415 1415
1416 1416 return repo.commit(message, opts.get('user'), opts.get('date'),
1417 1417 match, editor=e, extra=extra)
1418 1418 finally:
1419 1419 ui.setconfig('phases', 'new-commit', oldcommitphase)
1420 1420
1421 1421
1422 1422 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1423 1423
1424 1424 if not node:
1425 1425 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1426 1426 if stat[3]:
1427 1427 ui.status(_("nothing changed (%d missing files, see "
1428 1428 "'hg status')\n") % len(stat[3]))
1429 1429 else:
1430 1430 ui.status(_("nothing changed\n"))
1431 1431 return 1
1432 1432
1433 1433 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1434 1434
1435 1435 @command('copy|cp',
1436 1436 [('A', 'after', None, _('record a copy that has already occurred')),
1437 1437 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1438 1438 ] + walkopts + dryrunopts,
1439 1439 _('[OPTION]... [SOURCE]... DEST'))
1440 1440 def copy(ui, repo, *pats, **opts):
1441 1441 """mark files as copied for the next commit
1442 1442
1443 1443 Mark dest as having copies of source files. If dest is a
1444 1444 directory, copies are put in that directory. If dest is a file,
1445 1445 the source must be a single file.
1446 1446
1447 1447 By default, this command copies the contents of files as they
1448 1448 exist in the working directory. If invoked with -A/--after, the
1449 1449 operation is recorded, but no copying is performed.
1450 1450
1451 1451 This command takes effect with the next commit. To undo a copy
1452 1452 before that, see :hg:`revert`.
1453 1453
1454 1454 Returns 0 on success, 1 if errors are encountered.
1455 1455 """
1456 1456 wlock = repo.wlock(False)
1457 1457 try:
1458 1458 return cmdutil.copy(ui, repo, pats, opts)
1459 1459 finally:
1460 1460 wlock.release()
1461 1461
1462 1462 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1463 1463 def debugancestor(ui, repo, *args):
1464 1464 """find the ancestor revision of two revisions in a given index"""
1465 1465 if len(args) == 3:
1466 1466 index, rev1, rev2 = args
1467 1467 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1468 1468 lookup = r.lookup
1469 1469 elif len(args) == 2:
1470 1470 if not repo:
1471 1471 raise util.Abort(_("there is no Mercurial repository here "
1472 1472 "(.hg not found)"))
1473 1473 rev1, rev2 = args
1474 1474 r = repo.changelog
1475 1475 lookup = repo.lookup
1476 1476 else:
1477 1477 raise util.Abort(_('either two or three arguments required'))
1478 1478 a = r.ancestor(lookup(rev1), lookup(rev2))
1479 1479 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1480 1480
1481 1481 @command('debugbuilddag',
1482 1482 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1483 1483 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1484 1484 ('n', 'new-file', None, _('add new file at each rev'))],
1485 1485 _('[OPTION]... [TEXT]'))
1486 1486 def debugbuilddag(ui, repo, text=None,
1487 1487 mergeable_file=False,
1488 1488 overwritten_file=False,
1489 1489 new_file=False):
1490 1490 """builds a repo with a given DAG from scratch in the current empty repo
1491 1491
1492 1492 The description of the DAG is read from stdin if not given on the
1493 1493 command line.
1494 1494
1495 1495 Elements:
1496 1496
1497 1497 - "+n" is a linear run of n nodes based on the current default parent
1498 1498 - "." is a single node based on the current default parent
1499 1499 - "$" resets the default parent to null (implied at the start);
1500 1500 otherwise the default parent is always the last node created
1501 1501 - "<p" sets the default parent to the backref p
1502 1502 - "*p" is a fork at parent p, which is a backref
1503 1503 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1504 1504 - "/p2" is a merge of the preceding node and p2
1505 1505 - ":tag" defines a local tag for the preceding node
1506 1506 - "@branch" sets the named branch for subsequent nodes
1507 1507 - "#...\\n" is a comment up to the end of the line
1508 1508
1509 1509 Whitespace between the above elements is ignored.
1510 1510
1511 1511 A backref is either
1512 1512
1513 1513 - a number n, which references the node curr-n, where curr is the current
1514 1514 node, or
1515 1515 - the name of a local tag you placed earlier using ":tag", or
1516 1516 - empty to denote the default parent.
1517 1517
1518 1518 All string valued-elements are either strictly alphanumeric, or must
1519 1519 be enclosed in double quotes ("..."), with "\\" as escape character.
1520 1520 """
1521 1521
1522 1522 if text is None:
1523 1523 ui.status(_("reading DAG from stdin\n"))
1524 1524 text = ui.fin.read()
1525 1525
1526 1526 cl = repo.changelog
1527 1527 if len(cl) > 0:
1528 1528 raise util.Abort(_('repository is not empty'))
1529 1529
1530 1530 # determine number of revs in DAG
1531 1531 total = 0
1532 1532 for type, data in dagparser.parsedag(text):
1533 1533 if type == 'n':
1534 1534 total += 1
1535 1535
1536 1536 if mergeable_file:
1537 1537 linesperrev = 2
1538 1538 # make a file with k lines per rev
1539 1539 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1540 1540 initialmergedlines.append("")
1541 1541
1542 1542 tags = []
1543 1543
1544 1544 lock = tr = None
1545 1545 try:
1546 1546 lock = repo.lock()
1547 1547 tr = repo.transaction("builddag")
1548 1548
1549 1549 at = -1
1550 1550 atbranch = 'default'
1551 1551 nodeids = []
1552 1552 id = 0
1553 1553 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1554 1554 for type, data in dagparser.parsedag(text):
1555 1555 if type == 'n':
1556 1556 ui.note(('node %s\n' % str(data)))
1557 1557 id, ps = data
1558 1558
1559 1559 files = []
1560 1560 fctxs = {}
1561 1561
1562 1562 p2 = None
1563 1563 if mergeable_file:
1564 1564 fn = "mf"
1565 1565 p1 = repo[ps[0]]
1566 1566 if len(ps) > 1:
1567 1567 p2 = repo[ps[1]]
1568 1568 pa = p1.ancestor(p2)
1569 1569 base, local, other = [x[fn].data() for x in (pa, p1,
1570 1570 p2)]
1571 1571 m3 = simplemerge.Merge3Text(base, local, other)
1572 1572 ml = [l.strip() for l in m3.merge_lines()]
1573 1573 ml.append("")
1574 1574 elif at > 0:
1575 1575 ml = p1[fn].data().split("\n")
1576 1576 else:
1577 1577 ml = initialmergedlines
1578 1578 ml[id * linesperrev] += " r%i" % id
1579 1579 mergedtext = "\n".join(ml)
1580 1580 files.append(fn)
1581 1581 fctxs[fn] = context.memfilectx(fn, mergedtext)
1582 1582
1583 1583 if overwritten_file:
1584 1584 fn = "of"
1585 1585 files.append(fn)
1586 1586 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1587 1587
1588 1588 if new_file:
1589 1589 fn = "nf%i" % id
1590 1590 files.append(fn)
1591 1591 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1592 1592 if len(ps) > 1:
1593 1593 if not p2:
1594 1594 p2 = repo[ps[1]]
1595 1595 for fn in p2:
1596 1596 if fn.startswith("nf"):
1597 1597 files.append(fn)
1598 1598 fctxs[fn] = p2[fn]
1599 1599
1600 1600 def fctxfn(repo, cx, path):
1601 1601 return fctxs.get(path)
1602 1602
1603 1603 if len(ps) == 0 or ps[0] < 0:
1604 1604 pars = [None, None]
1605 1605 elif len(ps) == 1:
1606 1606 pars = [nodeids[ps[0]], None]
1607 1607 else:
1608 1608 pars = [nodeids[p] for p in ps]
1609 1609 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1610 1610 date=(id, 0),
1611 1611 user="debugbuilddag",
1612 1612 extra={'branch': atbranch})
1613 1613 nodeid = repo.commitctx(cx)
1614 1614 nodeids.append(nodeid)
1615 1615 at = id
1616 1616 elif type == 'l':
1617 1617 id, name = data
1618 1618 ui.note(('tag %s\n' % name))
1619 1619 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1620 1620 elif type == 'a':
1621 1621 ui.note(('branch %s\n' % data))
1622 1622 atbranch = data
1623 1623 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1624 1624 tr.close()
1625 1625
1626 1626 if tags:
1627 1627 repo.opener.write("localtags", "".join(tags))
1628 1628 finally:
1629 1629 ui.progress(_('building'), None)
1630 1630 release(tr, lock)
1631 1631
1632 1632 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1633 1633 def debugbundle(ui, bundlepath, all=None, **opts):
1634 1634 """lists the contents of a bundle"""
1635 1635 f = hg.openpath(ui, bundlepath)
1636 1636 try:
1637 1637 gen = changegroup.readbundle(f, bundlepath)
1638 1638 if all:
1639 1639 ui.write(("format: id, p1, p2, cset, delta base, len(delta)\n"))
1640 1640
1641 1641 def showchunks(named):
1642 1642 ui.write("\n%s\n" % named)
1643 1643 chain = None
1644 1644 while True:
1645 1645 chunkdata = gen.deltachunk(chain)
1646 1646 if not chunkdata:
1647 1647 break
1648 1648 node = chunkdata['node']
1649 1649 p1 = chunkdata['p1']
1650 1650 p2 = chunkdata['p2']
1651 1651 cs = chunkdata['cs']
1652 1652 deltabase = chunkdata['deltabase']
1653 1653 delta = chunkdata['delta']
1654 1654 ui.write("%s %s %s %s %s %s\n" %
1655 1655 (hex(node), hex(p1), hex(p2),
1656 1656 hex(cs), hex(deltabase), len(delta)))
1657 1657 chain = node
1658 1658
1659 1659 chunkdata = gen.changelogheader()
1660 1660 showchunks("changelog")
1661 1661 chunkdata = gen.manifestheader()
1662 1662 showchunks("manifest")
1663 1663 while True:
1664 1664 chunkdata = gen.filelogheader()
1665 1665 if not chunkdata:
1666 1666 break
1667 1667 fname = chunkdata['filename']
1668 1668 showchunks(fname)
1669 1669 else:
1670 1670 chunkdata = gen.changelogheader()
1671 1671 chain = None
1672 1672 while True:
1673 1673 chunkdata = gen.deltachunk(chain)
1674 1674 if not chunkdata:
1675 1675 break
1676 1676 node = chunkdata['node']
1677 1677 ui.write("%s\n" % hex(node))
1678 1678 chain = node
1679 1679 finally:
1680 1680 f.close()
1681 1681
1682 1682 @command('debugcheckstate', [], '')
1683 1683 def debugcheckstate(ui, repo):
1684 1684 """validate the correctness of the current dirstate"""
1685 1685 parent1, parent2 = repo.dirstate.parents()
1686 1686 m1 = repo[parent1].manifest()
1687 1687 m2 = repo[parent2].manifest()
1688 1688 errors = 0
1689 1689 for f in repo.dirstate:
1690 1690 state = repo.dirstate[f]
1691 1691 if state in "nr" and f not in m1:
1692 1692 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1693 1693 errors += 1
1694 1694 if state in "a" and f in m1:
1695 1695 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1696 1696 errors += 1
1697 1697 if state in "m" and f not in m1 and f not in m2:
1698 1698 ui.warn(_("%s in state %s, but not in either manifest\n") %
1699 1699 (f, state))
1700 1700 errors += 1
1701 1701 for f in m1:
1702 1702 state = repo.dirstate[f]
1703 1703 if state not in "nrm":
1704 1704 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1705 1705 errors += 1
1706 1706 if errors:
1707 1707 error = _(".hg/dirstate inconsistent with current parent's manifest")
1708 1708 raise util.Abort(error)
1709 1709
1710 1710 @command('debugcommands', [], _('[COMMAND]'))
1711 1711 def debugcommands(ui, cmd='', *args):
1712 1712 """list all available commands and options"""
1713 1713 for cmd, vals in sorted(table.iteritems()):
1714 1714 cmd = cmd.split('|')[0].strip('^')
1715 1715 opts = ', '.join([i[1] for i in vals[1]])
1716 1716 ui.write('%s: %s\n' % (cmd, opts))
1717 1717
1718 1718 @command('debugcomplete',
1719 1719 [('o', 'options', None, _('show the command options'))],
1720 1720 _('[-o] CMD'))
1721 1721 def debugcomplete(ui, cmd='', **opts):
1722 1722 """returns the completion list associated with the given command"""
1723 1723
1724 1724 if opts.get('options'):
1725 1725 options = []
1726 1726 otables = [globalopts]
1727 1727 if cmd:
1728 1728 aliases, entry = cmdutil.findcmd(cmd, table, False)
1729 1729 otables.append(entry[1])
1730 1730 for t in otables:
1731 1731 for o in t:
1732 1732 if "(DEPRECATED)" in o[3]:
1733 1733 continue
1734 1734 if o[0]:
1735 1735 options.append('-%s' % o[0])
1736 1736 options.append('--%s' % o[1])
1737 1737 ui.write("%s\n" % "\n".join(options))
1738 1738 return
1739 1739
1740 1740 cmdlist = cmdutil.findpossible(cmd, table)
1741 1741 if ui.verbose:
1742 1742 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1743 1743 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1744 1744
1745 1745 @command('debugdag',
1746 1746 [('t', 'tags', None, _('use tags as labels')),
1747 1747 ('b', 'branches', None, _('annotate with branch names')),
1748 1748 ('', 'dots', None, _('use dots for runs')),
1749 1749 ('s', 'spaces', None, _('separate elements by spaces'))],
1750 1750 _('[OPTION]... [FILE [REV]...]'))
1751 1751 def debugdag(ui, repo, file_=None, *revs, **opts):
1752 1752 """format the changelog or an index DAG as a concise textual description
1753 1753
1754 1754 If you pass a revlog index, the revlog's DAG is emitted. If you list
1755 1755 revision numbers, they get labeled in the output as rN.
1756 1756
1757 1757 Otherwise, the changelog DAG of the current repo is emitted.
1758 1758 """
1759 1759 spaces = opts.get('spaces')
1760 1760 dots = opts.get('dots')
1761 1761 if file_:
1762 1762 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1763 1763 revs = set((int(r) for r in revs))
1764 1764 def events():
1765 1765 for r in rlog:
1766 1766 yield 'n', (r, list(set(p for p in rlog.parentrevs(r)
1767 1767 if p != -1)))
1768 1768 if r in revs:
1769 1769 yield 'l', (r, "r%i" % r)
1770 1770 elif repo:
1771 1771 cl = repo.changelog
1772 1772 tags = opts.get('tags')
1773 1773 branches = opts.get('branches')
1774 1774 if tags:
1775 1775 labels = {}
1776 1776 for l, n in repo.tags().items():
1777 1777 labels.setdefault(cl.rev(n), []).append(l)
1778 1778 def events():
1779 1779 b = "default"
1780 1780 for r in cl:
1781 1781 if branches:
1782 1782 newb = cl.read(cl.node(r))[5]['branch']
1783 1783 if newb != b:
1784 1784 yield 'a', newb
1785 1785 b = newb
1786 1786 yield 'n', (r, list(set(p for p in cl.parentrevs(r)
1787 1787 if p != -1)))
1788 1788 if tags:
1789 1789 ls = labels.get(r)
1790 1790 if ls:
1791 1791 for l in ls:
1792 1792 yield 'l', (r, l)
1793 1793 else:
1794 1794 raise util.Abort(_('need repo for changelog dag'))
1795 1795
1796 1796 for line in dagparser.dagtextlines(events(),
1797 1797 addspaces=spaces,
1798 1798 wraplabels=True,
1799 1799 wrapannotations=True,
1800 1800 wrapnonlinear=dots,
1801 1801 usedots=dots,
1802 1802 maxlinewidth=70):
1803 1803 ui.write(line)
1804 1804 ui.write("\n")
1805 1805
1806 1806 @command('debugdata',
1807 1807 [('c', 'changelog', False, _('open changelog')),
1808 1808 ('m', 'manifest', False, _('open manifest'))],
1809 1809 _('-c|-m|FILE REV'))
1810 1810 def debugdata(ui, repo, file_, rev = None, **opts):
1811 1811 """dump the contents of a data file revision"""
1812 1812 if opts.get('changelog') or opts.get('manifest'):
1813 1813 file_, rev = None, file_
1814 1814 elif rev is None:
1815 1815 raise error.CommandError('debugdata', _('invalid arguments'))
1816 1816 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1817 1817 try:
1818 1818 ui.write(r.revision(r.lookup(rev)))
1819 1819 except KeyError:
1820 1820 raise util.Abort(_('invalid revision identifier %s') % rev)
1821 1821
1822 1822 @command('debugdate',
1823 1823 [('e', 'extended', None, _('try extended date formats'))],
1824 1824 _('[-e] DATE [RANGE]'))
1825 1825 def debugdate(ui, date, range=None, **opts):
1826 1826 """parse and display a date"""
1827 1827 if opts["extended"]:
1828 1828 d = util.parsedate(date, util.extendeddateformats)
1829 1829 else:
1830 1830 d = util.parsedate(date)
1831 1831 ui.write(("internal: %s %s\n") % d)
1832 1832 ui.write(("standard: %s\n") % util.datestr(d))
1833 1833 if range:
1834 1834 m = util.matchdate(range)
1835 1835 ui.write(("match: %s\n") % m(d[0]))
1836 1836
1837 1837 @command('debugdiscovery',
1838 1838 [('', 'old', None, _('use old-style discovery')),
1839 1839 ('', 'nonheads', None,
1840 1840 _('use old-style discovery with non-heads included')),
1841 1841 ] + remoteopts,
1842 1842 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1843 1843 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1844 1844 """runs the changeset discovery protocol in isolation"""
1845 1845 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
1846 1846 opts.get('branch'))
1847 1847 remote = hg.peer(repo, opts, remoteurl)
1848 1848 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1849 1849
1850 1850 # make sure tests are repeatable
1851 1851 random.seed(12323)
1852 1852
1853 1853 def doit(localheads, remoteheads, remote=remote):
1854 1854 if opts.get('old'):
1855 1855 if localheads:
1856 1856 raise util.Abort('cannot use localheads with old style '
1857 1857 'discovery')
1858 1858 if not util.safehasattr(remote, 'branches'):
1859 1859 # enable in-client legacy support
1860 1860 remote = localrepo.locallegacypeer(remote.local())
1861 1861 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1862 1862 force=True)
1863 1863 common = set(common)
1864 1864 if not opts.get('nonheads'):
1865 1865 ui.write(("unpruned common: %s\n") %
1866 1866 " ".join(sorted(short(n) for n in common)))
1867 1867 dag = dagutil.revlogdag(repo.changelog)
1868 1868 all = dag.ancestorset(dag.internalizeall(common))
1869 1869 common = dag.externalizeall(dag.headsetofconnecteds(all))
1870 1870 else:
1871 1871 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1872 1872 common = set(common)
1873 1873 rheads = set(hds)
1874 1874 lheads = set(repo.heads())
1875 1875 ui.write(("common heads: %s\n") %
1876 1876 " ".join(sorted(short(n) for n in common)))
1877 1877 if lheads <= common:
1878 1878 ui.write(("local is subset\n"))
1879 1879 elif rheads <= common:
1880 1880 ui.write(("remote is subset\n"))
1881 1881
1882 1882 serverlogs = opts.get('serverlog')
1883 1883 if serverlogs:
1884 1884 for filename in serverlogs:
1885 1885 logfile = open(filename, 'r')
1886 1886 try:
1887 1887 line = logfile.readline()
1888 1888 while line:
1889 1889 parts = line.strip().split(';')
1890 1890 op = parts[1]
1891 1891 if op == 'cg':
1892 1892 pass
1893 1893 elif op == 'cgss':
1894 1894 doit(parts[2].split(' '), parts[3].split(' '))
1895 1895 elif op == 'unb':
1896 1896 doit(parts[3].split(' '), parts[2].split(' '))
1897 1897 line = logfile.readline()
1898 1898 finally:
1899 1899 logfile.close()
1900 1900
1901 1901 else:
1902 1902 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1903 1903 opts.get('remote_head'))
1904 1904 localrevs = opts.get('local_head')
1905 1905 doit(localrevs, remoterevs)
1906 1906
1907 1907 @command('debugfileset',
1908 1908 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
1909 1909 _('[-r REV] FILESPEC'))
1910 1910 def debugfileset(ui, repo, expr, **opts):
1911 1911 '''parse and apply a fileset specification'''
1912 1912 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
1913 1913 if ui.verbose:
1914 1914 tree = fileset.parse(expr)[0]
1915 1915 ui.note(tree, "\n")
1916 1916
1917 1917 for f in fileset.getfileset(ctx, expr):
1918 1918 ui.write("%s\n" % f)
1919 1919
1920 1920 @command('debugfsinfo', [], _('[PATH]'))
1921 1921 def debugfsinfo(ui, path = "."):
1922 1922 """show information detected about current filesystem"""
1923 1923 util.writefile('.debugfsinfo', '')
1924 1924 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
1925 1925 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
1926 1926 ui.write(('case-sensitive: %s\n') % (util.checkcase('.debugfsinfo')
1927 1927 and 'yes' or 'no'))
1928 1928 os.unlink('.debugfsinfo')
1929 1929
1930 1930 @command('debuggetbundle',
1931 1931 [('H', 'head', [], _('id of head node'), _('ID')),
1932 1932 ('C', 'common', [], _('id of common node'), _('ID')),
1933 1933 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1934 1934 _('REPO FILE [-H|-C ID]...'))
1935 1935 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1936 1936 """retrieves a bundle from a repo
1937 1937
1938 1938 Every ID must be a full-length hex node id string. Saves the bundle to the
1939 1939 given file.
1940 1940 """
1941 1941 repo = hg.peer(ui, opts, repopath)
1942 1942 if not repo.capable('getbundle'):
1943 1943 raise util.Abort("getbundle() not supported by target repository")
1944 1944 args = {}
1945 1945 if common:
1946 1946 args['common'] = [bin(s) for s in common]
1947 1947 if head:
1948 1948 args['heads'] = [bin(s) for s in head]
1949 1949 # TODO: get desired bundlecaps from command line.
1950 1950 args['bundlecaps'] = None
1951 1951 bundle = repo.getbundle('debug', **args)
1952 1952
1953 1953 bundletype = opts.get('type', 'bzip2').lower()
1954 1954 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1955 1955 bundletype = btypes.get(bundletype)
1956 1956 if bundletype not in changegroup.bundletypes:
1957 1957 raise util.Abort(_('unknown bundle type specified with --type'))
1958 1958 changegroup.writebundle(bundle, bundlepath, bundletype)
1959 1959
1960 1960 @command('debugignore', [], '')
1961 1961 def debugignore(ui, repo, *values, **opts):
1962 1962 """display the combined ignore pattern"""
1963 1963 ignore = repo.dirstate._ignore
1964 1964 includepat = getattr(ignore, 'includepat', None)
1965 1965 if includepat is not None:
1966 1966 ui.write("%s\n" % includepat)
1967 1967 else:
1968 1968 raise util.Abort(_("no ignore patterns found"))
1969 1969
1970 1970 @command('debugindex',
1971 1971 [('c', 'changelog', False, _('open changelog')),
1972 1972 ('m', 'manifest', False, _('open manifest')),
1973 1973 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1974 1974 _('[-f FORMAT] -c|-m|FILE'))
1975 1975 def debugindex(ui, repo, file_ = None, **opts):
1976 1976 """dump the contents of an index file"""
1977 1977 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1978 1978 format = opts.get('format', 0)
1979 1979 if format not in (0, 1):
1980 1980 raise util.Abort(_("unknown format %d") % format)
1981 1981
1982 1982 generaldelta = r.version & revlog.REVLOGGENERALDELTA
1983 1983 if generaldelta:
1984 1984 basehdr = ' delta'
1985 1985 else:
1986 1986 basehdr = ' base'
1987 1987
1988 1988 if format == 0:
1989 1989 ui.write(" rev offset length " + basehdr + " linkrev"
1990 1990 " nodeid p1 p2\n")
1991 1991 elif format == 1:
1992 1992 ui.write(" rev flag offset length"
1993 1993 " size " + basehdr + " link p1 p2"
1994 1994 " nodeid\n")
1995 1995
1996 1996 for i in r:
1997 1997 node = r.node(i)
1998 1998 if generaldelta:
1999 1999 base = r.deltaparent(i)
2000 2000 else:
2001 2001 base = r.chainbase(i)
2002 2002 if format == 0:
2003 2003 try:
2004 2004 pp = r.parents(node)
2005 2005 except Exception:
2006 2006 pp = [nullid, nullid]
2007 2007 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
2008 2008 i, r.start(i), r.length(i), base, r.linkrev(i),
2009 2009 short(node), short(pp[0]), short(pp[1])))
2010 2010 elif format == 1:
2011 2011 pr = r.parentrevs(i)
2012 2012 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
2013 2013 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
2014 2014 base, r.linkrev(i), pr[0], pr[1], short(node)))
2015 2015
2016 2016 @command('debugindexdot', [], _('FILE'))
2017 2017 def debugindexdot(ui, repo, file_):
2018 2018 """dump an index DAG as a graphviz dot file"""
2019 2019 r = None
2020 2020 if repo:
2021 2021 filelog = repo.file(file_)
2022 2022 if len(filelog):
2023 2023 r = filelog
2024 2024 if not r:
2025 2025 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
2026 2026 ui.write(("digraph G {\n"))
2027 2027 for i in r:
2028 2028 node = r.node(i)
2029 2029 pp = r.parents(node)
2030 2030 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
2031 2031 if pp[1] != nullid:
2032 2032 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
2033 2033 ui.write("}\n")
2034 2034
2035 2035 @command('debuginstall', [], '')
2036 2036 def debuginstall(ui):
2037 2037 '''test Mercurial installation
2038 2038
2039 2039 Returns 0 on success.
2040 2040 '''
2041 2041
2042 2042 def writetemp(contents):
2043 2043 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
2044 2044 f = os.fdopen(fd, "wb")
2045 2045 f.write(contents)
2046 2046 f.close()
2047 2047 return name
2048 2048
2049 2049 problems = 0
2050 2050
2051 2051 # encoding
2052 2052 ui.status(_("checking encoding (%s)...\n") % encoding.encoding)
2053 2053 try:
2054 2054 encoding.fromlocal("test")
2055 2055 except util.Abort, inst:
2056 2056 ui.write(" %s\n" % inst)
2057 2057 ui.write(_(" (check that your locale is properly set)\n"))
2058 2058 problems += 1
2059 2059
2060 2060 # Python lib
2061 2061 ui.status(_("checking Python lib (%s)...\n")
2062 2062 % os.path.dirname(os.__file__))
2063 2063
2064 2064 # compiled modules
2065 2065 ui.status(_("checking installed modules (%s)...\n")
2066 2066 % os.path.dirname(__file__))
2067 2067 try:
2068 2068 import bdiff, mpatch, base85, osutil
2069 2069 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
2070 2070 except Exception, inst:
2071 2071 ui.write(" %s\n" % inst)
2072 2072 ui.write(_(" One or more extensions could not be found"))
2073 2073 ui.write(_(" (check that you compiled the extensions)\n"))
2074 2074 problems += 1
2075 2075
2076 2076 # templates
2077 2077 import templater
2078 2078 p = templater.templatepath()
2079 2079 ui.status(_("checking templates (%s)...\n") % ' '.join(p))
2080 2080 try:
2081 2081 templater.templater(templater.templatepath("map-cmdline.default"))
2082 2082 except Exception, inst:
2083 2083 ui.write(" %s\n" % inst)
2084 2084 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
2085 2085 problems += 1
2086 2086
2087 2087 # editor
2088 2088 ui.status(_("checking commit editor...\n"))
2089 2089 editor = ui.geteditor()
2090 2090 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
2091 2091 if not cmdpath:
2092 2092 if editor == 'vi':
2093 2093 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
2094 2094 ui.write(_(" (specify a commit editor in your configuration"
2095 2095 " file)\n"))
2096 2096 else:
2097 2097 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
2098 2098 ui.write(_(" (specify a commit editor in your configuration"
2099 2099 " file)\n"))
2100 2100 problems += 1
2101 2101
2102 2102 # check username
2103 2103 ui.status(_("checking username...\n"))
2104 2104 try:
2105 2105 ui.username()
2106 2106 except util.Abort, e:
2107 2107 ui.write(" %s\n" % e)
2108 2108 ui.write(_(" (specify a username in your configuration file)\n"))
2109 2109 problems += 1
2110 2110
2111 2111 if not problems:
2112 2112 ui.status(_("no problems detected\n"))
2113 2113 else:
2114 2114 ui.write(_("%s problems detected,"
2115 2115 " please check your install!\n") % problems)
2116 2116
2117 2117 return problems
2118 2118
2119 2119 @command('debugknown', [], _('REPO ID...'))
2120 2120 def debugknown(ui, repopath, *ids, **opts):
2121 2121 """test whether node ids are known to a repo
2122 2122
2123 2123 Every ID must be a full-length hex node id string. Returns a list of 0s
2124 2124 and 1s indicating unknown/known.
2125 2125 """
2126 2126 repo = hg.peer(ui, opts, repopath)
2127 2127 if not repo.capable('known'):
2128 2128 raise util.Abort("known() not supported by target repository")
2129 2129 flags = repo.known([bin(s) for s in ids])
2130 2130 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2131 2131
2132 2132 @command('debuglabelcomplete', [], _('LABEL...'))
2133 2133 def debuglabelcomplete(ui, repo, *args):
2134 2134 '''complete "labels" - tags, open branch names, bookmark names'''
2135 2135
2136 2136 labels = set()
2137 2137 labels.update(t[0] for t in repo.tagslist())
2138 2138 labels.update(repo._bookmarks.keys())
2139 2139 for heads in repo.branchmap().itervalues():
2140 2140 for h in heads:
2141 2141 ctx = repo[h]
2142 2142 if not ctx.closesbranch():
2143 2143 labels.add(ctx.branch())
2144 2144 completions = set()
2145 2145 if not args:
2146 2146 args = ['']
2147 2147 for a in args:
2148 2148 completions.update(l for l in labels if l.startswith(a))
2149 2149 ui.write('\n'.join(sorted(completions)))
2150 2150 ui.write('\n')
2151 2151
2152 2152 @command('debugobsolete',
2153 2153 [('', 'flags', 0, _('markers flag')),
2154 2154 ] + commitopts2,
2155 2155 _('[OBSOLETED [REPLACEMENT] [REPL... ]'))
2156 2156 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2157 2157 """create arbitrary obsolete marker
2158 2158
2159 2159 With no arguments, displays the list of obsolescence markers."""
2160 2160 def parsenodeid(s):
2161 2161 try:
2162 2162 # We do not use revsingle/revrange functions here to accept
2163 2163 # arbitrary node identifiers, possibly not present in the
2164 2164 # local repository.
2165 2165 n = bin(s)
2166 2166 if len(n) != len(nullid):
2167 2167 raise TypeError()
2168 2168 return n
2169 2169 except TypeError:
2170 2170 raise util.Abort('changeset references must be full hexadecimal '
2171 2171 'node identifiers')
2172 2172
2173 2173 if precursor is not None:
2174 2174 metadata = {}
2175 2175 if 'date' in opts:
2176 2176 metadata['date'] = opts['date']
2177 2177 metadata['user'] = opts['user'] or ui.username()
2178 2178 succs = tuple(parsenodeid(succ) for succ in successors)
2179 2179 l = repo.lock()
2180 2180 try:
2181 2181 tr = repo.transaction('debugobsolete')
2182 2182 try:
2183 2183 repo.obsstore.create(tr, parsenodeid(precursor), succs,
2184 2184 opts['flags'], metadata)
2185 2185 tr.close()
2186 2186 finally:
2187 2187 tr.release()
2188 2188 finally:
2189 2189 l.release()
2190 2190 else:
2191 2191 for m in obsolete.allmarkers(repo):
2192 2192 ui.write(hex(m.precnode()))
2193 2193 for repl in m.succnodes():
2194 2194 ui.write(' ')
2195 2195 ui.write(hex(repl))
2196 2196 ui.write(' %X ' % m._data[2])
2197 2197 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
2198 2198 sorted(m.metadata().items()))))
2199 2199 ui.write('\n')
2200 2200
2201 2201 @command('debugpathcomplete',
2202 2202 [('f', 'full', None, _('complete an entire path')),
2203 2203 ('n', 'normal', None, _('show only normal files')),
2204 2204 ('a', 'added', None, _('show only added files')),
2205 2205 ('r', 'removed', None, _('show only removed files'))],
2206 2206 _('FILESPEC...'))
2207 2207 def debugpathcomplete(ui, repo, *specs, **opts):
2208 2208 '''complete part or all of a tracked path
2209 2209
2210 2210 This command supports shells that offer path name completion. It
2211 2211 currently completes only files already known to the dirstate.
2212 2212
2213 2213 Completion extends only to the next path segment unless
2214 2214 --full is specified, in which case entire paths are used.'''
2215 2215
2216 2216 def complete(path, acceptable):
2217 2217 dirstate = repo.dirstate
2218 2218 spec = os.path.normpath(os.path.join(os.getcwd(), path))
2219 2219 rootdir = repo.root + os.sep
2220 2220 if spec != repo.root and not spec.startswith(rootdir):
2221 2221 return [], []
2222 2222 if os.path.isdir(spec):
2223 2223 spec += '/'
2224 2224 spec = spec[len(rootdir):]
2225 2225 fixpaths = os.sep != '/'
2226 2226 if fixpaths:
2227 2227 spec = spec.replace(os.sep, '/')
2228 2228 speclen = len(spec)
2229 2229 fullpaths = opts['full']
2230 2230 files, dirs = set(), set()
2231 2231 adddir, addfile = dirs.add, files.add
2232 2232 for f, st in dirstate.iteritems():
2233 2233 if f.startswith(spec) and st[0] in acceptable:
2234 2234 if fixpaths:
2235 2235 f = f.replace('/', os.sep)
2236 2236 if fullpaths:
2237 2237 addfile(f)
2238 2238 continue
2239 2239 s = f.find(os.sep, speclen)
2240 2240 if s >= 0:
2241 2241 adddir(f[:s + 1])
2242 2242 else:
2243 2243 addfile(f)
2244 2244 return files, dirs
2245 2245
2246 2246 acceptable = ''
2247 2247 if opts['normal']:
2248 2248 acceptable += 'nm'
2249 2249 if opts['added']:
2250 2250 acceptable += 'a'
2251 2251 if opts['removed']:
2252 2252 acceptable += 'r'
2253 2253 cwd = repo.getcwd()
2254 2254 if not specs:
2255 2255 specs = ['.']
2256 2256
2257 2257 files, dirs = set(), set()
2258 2258 for spec in specs:
2259 2259 f, d = complete(spec, acceptable or 'nmar')
2260 2260 files.update(f)
2261 2261 dirs.update(d)
2262 2262 if not files and len(dirs) == 1:
2263 2263 # force the shell to consider a completion that matches one
2264 2264 # directory and zero files to be ambiguous
2265 2265 dirs.add(iter(dirs).next() + '.')
2266 2266 files.update(dirs)
2267 2267 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
2268 2268 ui.write('\n')
2269 2269
2270 2270 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
2271 2271 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2272 2272 '''access the pushkey key/value protocol
2273 2273
2274 2274 With two args, list the keys in the given namespace.
2275 2275
2276 2276 With five args, set a key to new if it currently is set to old.
2277 2277 Reports success or failure.
2278 2278 '''
2279 2279
2280 2280 target = hg.peer(ui, {}, repopath)
2281 2281 if keyinfo:
2282 2282 key, old, new = keyinfo
2283 2283 r = target.pushkey(namespace, key, old, new)
2284 2284 ui.status(str(r) + '\n')
2285 2285 return not r
2286 2286 else:
2287 2287 for k, v in sorted(target.listkeys(namespace).iteritems()):
2288 2288 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2289 2289 v.encode('string-escape')))
2290 2290
2291 2291 @command('debugpvec', [], _('A B'))
2292 2292 def debugpvec(ui, repo, a, b=None):
2293 2293 ca = scmutil.revsingle(repo, a)
2294 2294 cb = scmutil.revsingle(repo, b)
2295 2295 pa = pvec.ctxpvec(ca)
2296 2296 pb = pvec.ctxpvec(cb)
2297 2297 if pa == pb:
2298 2298 rel = "="
2299 2299 elif pa > pb:
2300 2300 rel = ">"
2301 2301 elif pa < pb:
2302 2302 rel = "<"
2303 2303 elif pa | pb:
2304 2304 rel = "|"
2305 2305 ui.write(_("a: %s\n") % pa)
2306 2306 ui.write(_("b: %s\n") % pb)
2307 2307 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2308 2308 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2309 2309 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2310 2310 pa.distance(pb), rel))
2311 2311
2312 2312 @command('debugrebuilddirstate|debugrebuildstate',
2313 2313 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
2314 2314 _('[-r REV]'))
2315 2315 def debugrebuilddirstate(ui, repo, rev):
2316 2316 """rebuild the dirstate as it would look like for the given revision
2317 2317
2318 2318 If no revision is specified the first current parent will be used.
2319 2319
2320 2320 The dirstate will be set to the files of the given revision.
2321 2321 The actual working directory content or existing dirstate
2322 2322 information such as adds or removes is not considered.
2323 2323
2324 2324 One use of this command is to make the next :hg:`status` invocation
2325 2325 check the actual file content.
2326 2326 """
2327 2327 ctx = scmutil.revsingle(repo, rev)
2328 2328 wlock = repo.wlock()
2329 2329 try:
2330 2330 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2331 2331 finally:
2332 2332 wlock.release()
2333 2333
2334 2334 @command('debugrename',
2335 2335 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2336 2336 _('[-r REV] FILE'))
2337 2337 def debugrename(ui, repo, file1, *pats, **opts):
2338 2338 """dump rename information"""
2339 2339
2340 2340 ctx = scmutil.revsingle(repo, opts.get('rev'))
2341 2341 m = scmutil.match(ctx, (file1,) + pats, opts)
2342 2342 for abs in ctx.walk(m):
2343 2343 fctx = ctx[abs]
2344 2344 o = fctx.filelog().renamed(fctx.filenode())
2345 2345 rel = m.rel(abs)
2346 2346 if o:
2347 2347 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2348 2348 else:
2349 2349 ui.write(_("%s not renamed\n") % rel)
2350 2350
2351 2351 @command('debugrevlog',
2352 2352 [('c', 'changelog', False, _('open changelog')),
2353 2353 ('m', 'manifest', False, _('open manifest')),
2354 2354 ('d', 'dump', False, _('dump index data'))],
2355 2355 _('-c|-m|FILE'))
2356 2356 def debugrevlog(ui, repo, file_ = None, **opts):
2357 2357 """show data and statistics about a revlog"""
2358 2358 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2359 2359
2360 2360 if opts.get("dump"):
2361 2361 numrevs = len(r)
2362 2362 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
2363 2363 " rawsize totalsize compression heads\n")
2364 2364 ts = 0
2365 2365 heads = set()
2366 2366 for rev in xrange(numrevs):
2367 2367 dbase = r.deltaparent(rev)
2368 2368 if dbase == -1:
2369 2369 dbase = rev
2370 2370 cbase = r.chainbase(rev)
2371 2371 p1, p2 = r.parentrevs(rev)
2372 2372 rs = r.rawsize(rev)
2373 2373 ts = ts + rs
2374 2374 heads -= set(r.parentrevs(rev))
2375 2375 heads.add(rev)
2376 2376 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
2377 2377 (rev, p1, p2, r.start(rev), r.end(rev),
2378 2378 r.start(dbase), r.start(cbase),
2379 2379 r.start(p1), r.start(p2),
2380 2380 rs, ts, ts / r.end(rev), len(heads)))
2381 2381 return 0
2382 2382
2383 2383 v = r.version
2384 2384 format = v & 0xFFFF
2385 2385 flags = []
2386 2386 gdelta = False
2387 2387 if v & revlog.REVLOGNGINLINEDATA:
2388 2388 flags.append('inline')
2389 2389 if v & revlog.REVLOGGENERALDELTA:
2390 2390 gdelta = True
2391 2391 flags.append('generaldelta')
2392 2392 if not flags:
2393 2393 flags = ['(none)']
2394 2394
2395 2395 nummerges = 0
2396 2396 numfull = 0
2397 2397 numprev = 0
2398 2398 nump1 = 0
2399 2399 nump2 = 0
2400 2400 numother = 0
2401 2401 nump1prev = 0
2402 2402 nump2prev = 0
2403 2403 chainlengths = []
2404 2404
2405 2405 datasize = [None, 0, 0L]
2406 2406 fullsize = [None, 0, 0L]
2407 2407 deltasize = [None, 0, 0L]
2408 2408
2409 2409 def addsize(size, l):
2410 2410 if l[0] is None or size < l[0]:
2411 2411 l[0] = size
2412 2412 if size > l[1]:
2413 2413 l[1] = size
2414 2414 l[2] += size
2415 2415
2416 2416 numrevs = len(r)
2417 2417 for rev in xrange(numrevs):
2418 2418 p1, p2 = r.parentrevs(rev)
2419 2419 delta = r.deltaparent(rev)
2420 2420 if format > 0:
2421 2421 addsize(r.rawsize(rev), datasize)
2422 2422 if p2 != nullrev:
2423 2423 nummerges += 1
2424 2424 size = r.length(rev)
2425 2425 if delta == nullrev:
2426 2426 chainlengths.append(0)
2427 2427 numfull += 1
2428 2428 addsize(size, fullsize)
2429 2429 else:
2430 2430 chainlengths.append(chainlengths[delta] + 1)
2431 2431 addsize(size, deltasize)
2432 2432 if delta == rev - 1:
2433 2433 numprev += 1
2434 2434 if delta == p1:
2435 2435 nump1prev += 1
2436 2436 elif delta == p2:
2437 2437 nump2prev += 1
2438 2438 elif delta == p1:
2439 2439 nump1 += 1
2440 2440 elif delta == p2:
2441 2441 nump2 += 1
2442 2442 elif delta != nullrev:
2443 2443 numother += 1
2444 2444
2445 2445 # Adjust size min value for empty cases
2446 2446 for size in (datasize, fullsize, deltasize):
2447 2447 if size[0] is None:
2448 2448 size[0] = 0
2449 2449
2450 2450 numdeltas = numrevs - numfull
2451 2451 numoprev = numprev - nump1prev - nump2prev
2452 2452 totalrawsize = datasize[2]
2453 2453 datasize[2] /= numrevs
2454 2454 fulltotal = fullsize[2]
2455 2455 fullsize[2] /= numfull
2456 2456 deltatotal = deltasize[2]
2457 2457 if numrevs - numfull > 0:
2458 2458 deltasize[2] /= numrevs - numfull
2459 2459 totalsize = fulltotal + deltatotal
2460 2460 avgchainlen = sum(chainlengths) / numrevs
2461 2461 compratio = totalrawsize / totalsize
2462 2462
2463 2463 basedfmtstr = '%%%dd\n'
2464 2464 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2465 2465
2466 2466 def dfmtstr(max):
2467 2467 return basedfmtstr % len(str(max))
2468 2468 def pcfmtstr(max, padding=0):
2469 2469 return basepcfmtstr % (len(str(max)), ' ' * padding)
2470 2470
2471 2471 def pcfmt(value, total):
2472 2472 return (value, 100 * float(value) / total)
2473 2473
2474 2474 ui.write(('format : %d\n') % format)
2475 2475 ui.write(('flags : %s\n') % ', '.join(flags))
2476 2476
2477 2477 ui.write('\n')
2478 2478 fmt = pcfmtstr(totalsize)
2479 2479 fmt2 = dfmtstr(totalsize)
2480 2480 ui.write(('revisions : ') + fmt2 % numrevs)
2481 2481 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2482 2482 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2483 2483 ui.write(('revisions : ') + fmt2 % numrevs)
2484 2484 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
2485 2485 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2486 2486 ui.write(('revision size : ') + fmt2 % totalsize)
2487 2487 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
2488 2488 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2489 2489
2490 2490 ui.write('\n')
2491 2491 fmt = dfmtstr(max(avgchainlen, compratio))
2492 2492 ui.write(('avg chain length : ') + fmt % avgchainlen)
2493 2493 ui.write(('compression ratio : ') + fmt % compratio)
2494 2494
2495 2495 if format > 0:
2496 2496 ui.write('\n')
2497 2497 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2498 2498 % tuple(datasize))
2499 2499 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2500 2500 % tuple(fullsize))
2501 2501 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2502 2502 % tuple(deltasize))
2503 2503
2504 2504 if numdeltas > 0:
2505 2505 ui.write('\n')
2506 2506 fmt = pcfmtstr(numdeltas)
2507 2507 fmt2 = pcfmtstr(numdeltas, 4)
2508 2508 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2509 2509 if numprev > 0:
2510 2510 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2511 2511 numprev))
2512 2512 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2513 2513 numprev))
2514 2514 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2515 2515 numprev))
2516 2516 if gdelta:
2517 2517 ui.write(('deltas against p1 : ')
2518 2518 + fmt % pcfmt(nump1, numdeltas))
2519 2519 ui.write(('deltas against p2 : ')
2520 2520 + fmt % pcfmt(nump2, numdeltas))
2521 2521 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2522 2522 numdeltas))
2523 2523
2524 2524 @command('debugrevspec', [], ('REVSPEC'))
2525 2525 def debugrevspec(ui, repo, expr):
2526 2526 """parse and apply a revision specification
2527 2527
2528 2528 Use --verbose to print the parsed tree before and after aliases
2529 2529 expansion.
2530 2530 """
2531 2531 if ui.verbose:
2532 2532 tree = revset.parse(expr)[0]
2533 2533 ui.note(revset.prettyformat(tree), "\n")
2534 2534 newtree = revset.findaliases(ui, tree)
2535 2535 if newtree != tree:
2536 2536 ui.note(revset.prettyformat(newtree), "\n")
2537 2537 func = revset.match(ui, expr)
2538 2538 for c in func(repo, range(len(repo))):
2539 2539 ui.write("%s\n" % c)
2540 2540
2541 2541 @command('debugsetparents', [], _('REV1 [REV2]'))
2542 2542 def debugsetparents(ui, repo, rev1, rev2=None):
2543 2543 """manually set the parents of the current working directory
2544 2544
2545 2545 This is useful for writing repository conversion tools, but should
2546 2546 be used with care.
2547 2547
2548 2548 Returns 0 on success.
2549 2549 """
2550 2550
2551 2551 r1 = scmutil.revsingle(repo, rev1).node()
2552 2552 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2553 2553
2554 2554 wlock = repo.wlock()
2555 2555 try:
2556 2556 repo.setparents(r1, r2)
2557 2557 finally:
2558 2558 wlock.release()
2559 2559
2560 2560 @command('debugdirstate|debugstate',
2561 2561 [('', 'nodates', None, _('do not display the saved mtime')),
2562 2562 ('', 'datesort', None, _('sort by saved mtime'))],
2563 2563 _('[OPTION]...'))
2564 2564 def debugstate(ui, repo, nodates=None, datesort=None):
2565 2565 """show the contents of the current dirstate"""
2566 2566 timestr = ""
2567 2567 showdate = not nodates
2568 2568 if datesort:
2569 2569 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2570 2570 else:
2571 2571 keyfunc = None # sort by filename
2572 2572 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2573 2573 if showdate:
2574 2574 if ent[3] == -1:
2575 2575 # Pad or slice to locale representation
2576 2576 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2577 2577 time.localtime(0)))
2578 2578 timestr = 'unset'
2579 2579 timestr = (timestr[:locale_len] +
2580 2580 ' ' * (locale_len - len(timestr)))
2581 2581 else:
2582 2582 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2583 2583 time.localtime(ent[3]))
2584 2584 if ent[1] & 020000:
2585 2585 mode = 'lnk'
2586 2586 else:
2587 2587 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2588 2588 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2589 2589 for f in repo.dirstate.copies():
2590 2590 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2591 2591
2592 2592 @command('debugsub',
2593 2593 [('r', 'rev', '',
2594 2594 _('revision to check'), _('REV'))],
2595 2595 _('[-r REV] [REV]'))
2596 2596 def debugsub(ui, repo, rev=None):
2597 2597 ctx = scmutil.revsingle(repo, rev, None)
2598 2598 for k, v in sorted(ctx.substate.items()):
2599 2599 ui.write(('path %s\n') % k)
2600 2600 ui.write((' source %s\n') % v[0])
2601 2601 ui.write((' revision %s\n') % v[1])
2602 2602
2603 2603 @command('debugsuccessorssets',
2604 2604 [],
2605 2605 _('[REV]'))
2606 2606 def debugsuccessorssets(ui, repo, *revs):
2607 2607 """show set of successors for revision
2608 2608
2609 2609 A successors set of changeset A is a consistent group of revisions that
2610 2610 succeed A. It contains non-obsolete changesets only.
2611 2611
2612 2612 In most cases a changeset A has a single successors set containing a single
2613 2613 successor (changeset A replaced by A').
2614 2614
2615 2615 A changeset that is made obsolete with no successors are called "pruned".
2616 2616 Such changesets have no successors sets at all.
2617 2617
2618 2618 A changeset that has been "split" will have a successors set containing
2619 2619 more than one successor.
2620 2620
2621 2621 A changeset that has been rewritten in multiple different ways is called
2622 2622 "divergent". Such changesets have multiple successor sets (each of which
2623 2623 may also be split, i.e. have multiple successors).
2624 2624
2625 2625 Results are displayed as follows::
2626 2626
2627 2627 <rev1>
2628 2628 <successors-1A>
2629 2629 <rev2>
2630 2630 <successors-2A>
2631 2631 <successors-2B1> <successors-2B2> <successors-2B3>
2632 2632
2633 2633 Here rev2 has two possible (i.e. divergent) successors sets. The first
2634 2634 holds one element, whereas the second holds three (i.e. the changeset has
2635 2635 been split).
2636 2636 """
2637 2637 # passed to successorssets caching computation from one call to another
2638 2638 cache = {}
2639 2639 ctx2str = str
2640 2640 node2str = short
2641 2641 if ui.debug():
2642 2642 def ctx2str(ctx):
2643 2643 return ctx.hex()
2644 2644 node2str = hex
2645 2645 for rev in scmutil.revrange(repo, revs):
2646 2646 ctx = repo[rev]
2647 2647 ui.write('%s\n'% ctx2str(ctx))
2648 2648 for succsset in obsolete.successorssets(repo, ctx.node(), cache):
2649 2649 if succsset:
2650 2650 ui.write(' ')
2651 2651 ui.write(node2str(succsset[0]))
2652 2652 for node in succsset[1:]:
2653 2653 ui.write(' ')
2654 2654 ui.write(node2str(node))
2655 2655 ui.write('\n')
2656 2656
2657 2657 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2658 2658 def debugwalk(ui, repo, *pats, **opts):
2659 2659 """show how files match on given patterns"""
2660 2660 m = scmutil.match(repo[None], pats, opts)
2661 2661 items = list(repo.walk(m))
2662 2662 if not items:
2663 2663 return
2664 2664 f = lambda fn: fn
2665 2665 if ui.configbool('ui', 'slash') and os.sep != '/':
2666 2666 f = lambda fn: util.normpath(fn)
2667 2667 fmt = 'f %%-%ds %%-%ds %%s' % (
2668 2668 max([len(abs) for abs in items]),
2669 2669 max([len(m.rel(abs)) for abs in items]))
2670 2670 for abs in items:
2671 2671 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2672 2672 ui.write("%s\n" % line.rstrip())
2673 2673
2674 2674 @command('debugwireargs',
2675 2675 [('', 'three', '', 'three'),
2676 2676 ('', 'four', '', 'four'),
2677 2677 ('', 'five', '', 'five'),
2678 2678 ] + remoteopts,
2679 2679 _('REPO [OPTIONS]... [ONE [TWO]]'))
2680 2680 def debugwireargs(ui, repopath, *vals, **opts):
2681 2681 repo = hg.peer(ui, opts, repopath)
2682 2682 for opt in remoteopts:
2683 2683 del opts[opt[1]]
2684 2684 args = {}
2685 2685 for k, v in opts.iteritems():
2686 2686 if v:
2687 2687 args[k] = v
2688 2688 # run twice to check that we don't mess up the stream for the next command
2689 2689 res1 = repo.debugwireargs(*vals, **args)
2690 2690 res2 = repo.debugwireargs(*vals, **args)
2691 2691 ui.write("%s\n" % res1)
2692 2692 if res1 != res2:
2693 2693 ui.warn("%s\n" % res2)
2694 2694
2695 2695 @command('^diff',
2696 2696 [('r', 'rev', [], _('revision'), _('REV')),
2697 2697 ('c', 'change', '', _('change made by revision'), _('REV'))
2698 2698 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2699 2699 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2700 2700 def diff(ui, repo, *pats, **opts):
2701 2701 """diff repository (or selected files)
2702 2702
2703 2703 Show differences between revisions for the specified files.
2704 2704
2705 2705 Differences between files are shown using the unified diff format.
2706 2706
2707 2707 .. note::
2708 2708 diff may generate unexpected results for merges, as it will
2709 2709 default to comparing against the working directory's first
2710 2710 parent changeset if no revisions are specified.
2711 2711
2712 2712 When two revision arguments are given, then changes are shown
2713 2713 between those revisions. If only one revision is specified then
2714 2714 that revision is compared to the working directory, and, when no
2715 2715 revisions are specified, the working directory files are compared
2716 2716 to its parent.
2717 2717
2718 2718 Alternatively you can specify -c/--change with a revision to see
2719 2719 the changes in that changeset relative to its first parent.
2720 2720
2721 2721 Without the -a/--text option, diff will avoid generating diffs of
2722 2722 files it detects as binary. With -a, diff will generate a diff
2723 2723 anyway, probably with undesirable results.
2724 2724
2725 2725 Use the -g/--git option to generate diffs in the git extended diff
2726 2726 format. For more information, read :hg:`help diffs`.
2727 2727
2728 2728 .. container:: verbose
2729 2729
2730 2730 Examples:
2731 2731
2732 2732 - compare a file in the current working directory to its parent::
2733 2733
2734 2734 hg diff foo.c
2735 2735
2736 2736 - compare two historical versions of a directory, with rename info::
2737 2737
2738 2738 hg diff --git -r 1.0:1.2 lib/
2739 2739
2740 2740 - get change stats relative to the last change on some date::
2741 2741
2742 2742 hg diff --stat -r "date('may 2')"
2743 2743
2744 2744 - diff all newly-added files that contain a keyword::
2745 2745
2746 2746 hg diff "set:added() and grep(GNU)"
2747 2747
2748 2748 - compare a revision and its parents::
2749 2749
2750 2750 hg diff -c 9353 # compare against first parent
2751 2751 hg diff -r 9353^:9353 # same using revset syntax
2752 2752 hg diff -r 9353^2:9353 # compare against the second parent
2753 2753
2754 2754 Returns 0 on success.
2755 2755 """
2756 2756
2757 2757 revs = opts.get('rev')
2758 2758 change = opts.get('change')
2759 2759 stat = opts.get('stat')
2760 2760 reverse = opts.get('reverse')
2761 2761
2762 2762 if revs and change:
2763 2763 msg = _('cannot specify --rev and --change at the same time')
2764 2764 raise util.Abort(msg)
2765 2765 elif change:
2766 2766 node2 = scmutil.revsingle(repo, change, None).node()
2767 2767 node1 = repo[node2].p1().node()
2768 2768 else:
2769 2769 node1, node2 = scmutil.revpair(repo, revs)
2770 2770
2771 2771 if reverse:
2772 2772 node1, node2 = node2, node1
2773 2773
2774 2774 diffopts = patch.diffopts(ui, opts)
2775 2775 m = scmutil.match(repo[node2], pats, opts)
2776 2776 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2777 2777 listsubrepos=opts.get('subrepos'))
2778 2778
2779 2779 @command('^export',
2780 2780 [('o', 'output', '',
2781 2781 _('print output to file with formatted name'), _('FORMAT')),
2782 2782 ('', 'switch-parent', None, _('diff against the second parent')),
2783 2783 ('r', 'rev', [], _('revisions to export'), _('REV')),
2784 2784 ] + diffopts,
2785 2785 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
2786 2786 def export(ui, repo, *changesets, **opts):
2787 2787 """dump the header and diffs for one or more changesets
2788 2788
2789 2789 Print the changeset header and diffs for one or more revisions.
2790 2790 If no revision is given, the parent of the working directory is used.
2791 2791
2792 2792 The information shown in the changeset header is: author, date,
2793 2793 branch name (if non-default), changeset hash, parent(s) and commit
2794 2794 comment.
2795 2795
2796 2796 .. note::
2797 2797 export may generate unexpected diff output for merge
2798 2798 changesets, as it will compare the merge changeset against its
2799 2799 first parent only.
2800 2800
2801 2801 Output may be to a file, in which case the name of the file is
2802 2802 given using a format string. The formatting rules are as follows:
2803 2803
2804 2804 :``%%``: literal "%" character
2805 2805 :``%H``: changeset hash (40 hexadecimal digits)
2806 2806 :``%N``: number of patches being generated
2807 2807 :``%R``: changeset revision number
2808 2808 :``%b``: basename of the exporting repository
2809 2809 :``%h``: short-form changeset hash (12 hexadecimal digits)
2810 2810 :``%m``: first line of the commit message (only alphanumeric characters)
2811 2811 :``%n``: zero-padded sequence number, starting at 1
2812 2812 :``%r``: zero-padded changeset revision number
2813 2813
2814 2814 Without the -a/--text option, export will avoid generating diffs
2815 2815 of files it detects as binary. With -a, export will generate a
2816 2816 diff anyway, probably with undesirable results.
2817 2817
2818 2818 Use the -g/--git option to generate diffs in the git extended diff
2819 2819 format. See :hg:`help diffs` for more information.
2820 2820
2821 2821 With the --switch-parent option, the diff will be against the
2822 2822 second parent. It can be useful to review a merge.
2823 2823
2824 2824 .. container:: verbose
2825 2825
2826 2826 Examples:
2827 2827
2828 2828 - use export and import to transplant a bugfix to the current
2829 2829 branch::
2830 2830
2831 2831 hg export -r 9353 | hg import -
2832 2832
2833 2833 - export all the changesets between two revisions to a file with
2834 2834 rename information::
2835 2835
2836 2836 hg export --git -r 123:150 > changes.txt
2837 2837
2838 2838 - split outgoing changes into a series of patches with
2839 2839 descriptive names::
2840 2840
2841 2841 hg export -r "outgoing()" -o "%n-%m.patch"
2842 2842
2843 2843 Returns 0 on success.
2844 2844 """
2845 2845 changesets += tuple(opts.get('rev', []))
2846 2846 if not changesets:
2847 2847 changesets = ['.']
2848 2848 revs = scmutil.revrange(repo, changesets)
2849 2849 if not revs:
2850 2850 raise util.Abort(_("export requires at least one changeset"))
2851 2851 if len(revs) > 1:
2852 2852 ui.note(_('exporting patches:\n'))
2853 2853 else:
2854 2854 ui.note(_('exporting patch:\n'))
2855 2855 cmdutil.export(repo, revs, template=opts.get('output'),
2856 2856 switch_parent=opts.get('switch_parent'),
2857 2857 opts=patch.diffopts(ui, opts))
2858 2858
2859 2859 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2860 2860 def forget(ui, repo, *pats, **opts):
2861 2861 """forget the specified files on the next commit
2862 2862
2863 2863 Mark the specified files so they will no longer be tracked
2864 2864 after the next commit.
2865 2865
2866 2866 This only removes files from the current branch, not from the
2867 2867 entire project history, and it does not delete them from the
2868 2868 working directory.
2869 2869
2870 2870 To undo a forget before the next commit, see :hg:`add`.
2871 2871
2872 2872 .. container:: verbose
2873 2873
2874 2874 Examples:
2875 2875
2876 2876 - forget newly-added binary files::
2877 2877
2878 2878 hg forget "set:added() and binary()"
2879 2879
2880 2880 - forget files that would be excluded by .hgignore::
2881 2881
2882 2882 hg forget "set:hgignore()"
2883 2883
2884 2884 Returns 0 on success.
2885 2885 """
2886 2886
2887 2887 if not pats:
2888 2888 raise util.Abort(_('no files specified'))
2889 2889
2890 2890 m = scmutil.match(repo[None], pats, opts)
2891 2891 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2892 2892 return rejected and 1 or 0
2893 2893
2894 2894 @command(
2895 2895 'graft',
2896 2896 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2897 2897 ('c', 'continue', False, _('resume interrupted graft')),
2898 2898 ('e', 'edit', False, _('invoke editor on commit messages')),
2899 2899 ('', 'log', None, _('append graft info to log message')),
2900 2900 ('D', 'currentdate', False,
2901 2901 _('record the current date as commit date')),
2902 2902 ('U', 'currentuser', False,
2903 2903 _('record the current user as committer'), _('DATE'))]
2904 2904 + commitopts2 + mergetoolopts + dryrunopts,
2905 2905 _('[OPTION]... [-r] REV...'))
2906 2906 def graft(ui, repo, *revs, **opts):
2907 2907 '''copy changes from other branches onto the current branch
2908 2908
2909 2909 This command uses Mercurial's merge logic to copy individual
2910 2910 changes from other branches without merging branches in the
2911 2911 history graph. This is sometimes known as 'backporting' or
2912 2912 'cherry-picking'. By default, graft will copy user, date, and
2913 2913 description from the source changesets.
2914 2914
2915 2915 Changesets that are ancestors of the current revision, that have
2916 2916 already been grafted, or that are merges will be skipped.
2917 2917
2918 2918 If --log is specified, log messages will have a comment appended
2919 2919 of the form::
2920 2920
2921 2921 (grafted from CHANGESETHASH)
2922 2922
2923 2923 If a graft merge results in conflicts, the graft process is
2924 2924 interrupted so that the current merge can be manually resolved.
2925 2925 Once all conflicts are addressed, the graft process can be
2926 2926 continued with the -c/--continue option.
2927 2927
2928 2928 .. note::
2929 2929 The -c/--continue option does not reapply earlier options.
2930 2930
2931 2931 .. container:: verbose
2932 2932
2933 2933 Examples:
2934 2934
2935 2935 - copy a single change to the stable branch and edit its description::
2936 2936
2937 2937 hg update stable
2938 2938 hg graft --edit 9393
2939 2939
2940 2940 - graft a range of changesets with one exception, updating dates::
2941 2941
2942 2942 hg graft -D "2085::2093 and not 2091"
2943 2943
2944 2944 - continue a graft after resolving conflicts::
2945 2945
2946 2946 hg graft -c
2947 2947
2948 2948 - show the source of a grafted changeset::
2949 2949
2950 2950 hg log --debug -r .
2951 2951
2952 2952 Returns 0 on successful completion.
2953 2953 '''
2954 2954
2955 2955 revs = list(revs)
2956 2956 revs.extend(opts['rev'])
2957 2957
2958 2958 if not opts.get('user') and opts.get('currentuser'):
2959 2959 opts['user'] = ui.username()
2960 2960 if not opts.get('date') and opts.get('currentdate'):
2961 2961 opts['date'] = "%d %d" % util.makedate()
2962 2962
2963 2963 editor = None
2964 2964 if opts.get('edit'):
2965 2965 editor = cmdutil.commitforceeditor
2966 2966
2967 2967 cont = False
2968 2968 if opts['continue']:
2969 2969 cont = True
2970 2970 if revs:
2971 2971 raise util.Abort(_("can't specify --continue and revisions"))
2972 2972 # read in unfinished revisions
2973 2973 try:
2974 2974 nodes = repo.opener.read('graftstate').splitlines()
2975 2975 revs = [repo[node].rev() for node in nodes]
2976 2976 except IOError, inst:
2977 2977 if inst.errno != errno.ENOENT:
2978 2978 raise
2979 2979 raise util.Abort(_("no graft state found, can't continue"))
2980 2980 else:
2981 2981 cmdutil.checkunfinished(repo)
2982 2982 cmdutil.bailifchanged(repo)
2983 2983 if not revs:
2984 2984 raise util.Abort(_('no revisions specified'))
2985 2985 revs = scmutil.revrange(repo, revs)
2986 2986
2987 2987 # check for merges
2988 2988 for rev in repo.revs('%ld and merge()', revs):
2989 2989 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2990 2990 revs.remove(rev)
2991 2991 if not revs:
2992 2992 return -1
2993 2993
2994 2994 # check for ancestors of dest branch
2995 2995 crev = repo['.'].rev()
2996 2996 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2997 2997 # don't mutate while iterating, create a copy
2998 2998 for rev in list(revs):
2999 2999 if rev in ancestors:
3000 3000 ui.warn(_('skipping ancestor revision %s\n') % rev)
3001 3001 revs.remove(rev)
3002 3002 if not revs:
3003 3003 return -1
3004 3004
3005 3005 # analyze revs for earlier grafts
3006 3006 ids = {}
3007 3007 for ctx in repo.set("%ld", revs):
3008 3008 ids[ctx.hex()] = ctx.rev()
3009 3009 n = ctx.extra().get('source')
3010 3010 if n:
3011 3011 ids[n] = ctx.rev()
3012 3012
3013 3013 # check ancestors for earlier grafts
3014 3014 ui.debug('scanning for duplicate grafts\n')
3015 3015
3016 3016 for rev in repo.changelog.findmissingrevs(revs, [crev]):
3017 3017 ctx = repo[rev]
3018 3018 n = ctx.extra().get('source')
3019 3019 if n in ids:
3020 3020 r = repo[n].rev()
3021 3021 if r in revs:
3022 3022 ui.warn(_('skipping already grafted revision %s\n') % r)
3023 3023 revs.remove(r)
3024 3024 elif ids[n] in revs:
3025 3025 ui.warn(_('skipping already grafted revision %s '
3026 3026 '(same origin %d)\n') % (ids[n], r))
3027 3027 revs.remove(ids[n])
3028 3028 elif ctx.hex() in ids:
3029 3029 r = ids[ctx.hex()]
3030 3030 ui.warn(_('skipping already grafted revision %s '
3031 3031 '(was grafted from %d)\n') % (r, rev))
3032 3032 revs.remove(r)
3033 3033 if not revs:
3034 3034 return -1
3035 3035
3036 3036 wlock = repo.wlock()
3037 3037 try:
3038 3038 current = repo['.']
3039 3039 for pos, ctx in enumerate(repo.set("%ld", revs)):
3040 3040
3041 3041 ui.status(_('grafting revision %s\n') % ctx.rev())
3042 3042 if opts.get('dry_run'):
3043 3043 continue
3044 3044
3045 3045 source = ctx.extra().get('source')
3046 3046 if not source:
3047 3047 source = ctx.hex()
3048 3048 extra = {'source': source}
3049 3049 user = ctx.user()
3050 3050 if opts.get('user'):
3051 3051 user = opts['user']
3052 3052 date = ctx.date()
3053 3053 if opts.get('date'):
3054 3054 date = opts['date']
3055 3055 message = ctx.description()
3056 3056 if opts.get('log'):
3057 3057 message += '\n(grafted from %s)' % ctx.hex()
3058 3058
3059 3059 # we don't merge the first commit when continuing
3060 3060 if not cont:
3061 3061 # perform the graft merge with p1(rev) as 'ancestor'
3062 3062 try:
3063 3063 # ui.forcemerge is an internal variable, do not document
3064 3064 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
3065 3065 stats = mergemod.update(repo, ctx.node(), True, True, False,
3066 3066 ctx.p1().node())
3067 3067 finally:
3068 3068 repo.ui.setconfig('ui', 'forcemerge', '')
3069 3069 # report any conflicts
3070 3070 if stats and stats[3] > 0:
3071 3071 # write out state for --continue
3072 3072 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
3073 3073 repo.opener.write('graftstate', ''.join(nodelines))
3074 3074 raise util.Abort(
3075 3075 _("unresolved conflicts, can't continue"),
3076 3076 hint=_('use hg resolve and hg graft --continue'))
3077 3077 else:
3078 3078 cont = False
3079 3079
3080 3080 # drop the second merge parent
3081 3081 repo.setparents(current.node(), nullid)
3082 3082 repo.dirstate.write()
3083 3083 # fix up dirstate for copies and renames
3084 3084 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
3085 3085
3086 3086 # commit
3087 3087 node = repo.commit(text=message, user=user,
3088 3088 date=date, extra=extra, editor=editor)
3089 3089 if node is None:
3090 3090 ui.status(_('graft for revision %s is empty\n') % ctx.rev())
3091 3091 else:
3092 3092 current = repo[node]
3093 3093 finally:
3094 3094 wlock.release()
3095 3095
3096 3096 # remove state when we complete successfully
3097 3097 if not opts.get('dry_run'):
3098 3098 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
3099 3099
3100 3100 return 0
3101 3101
3102 3102 @command('grep',
3103 3103 [('0', 'print0', None, _('end fields with NUL')),
3104 3104 ('', 'all', None, _('print all revisions that match')),
3105 3105 ('a', 'text', None, _('treat all files as text')),
3106 3106 ('f', 'follow', None,
3107 3107 _('follow changeset history,'
3108 3108 ' or file history across copies and renames')),
3109 3109 ('i', 'ignore-case', None, _('ignore case when matching')),
3110 3110 ('l', 'files-with-matches', None,
3111 3111 _('print only filenames and revisions that match')),
3112 3112 ('n', 'line-number', None, _('print matching line numbers')),
3113 3113 ('r', 'rev', [],
3114 3114 _('only search files changed within revision range'), _('REV')),
3115 3115 ('u', 'user', None, _('list the author (long with -v)')),
3116 3116 ('d', 'date', None, _('list the date (short with -q)')),
3117 3117 ] + walkopts,
3118 3118 _('[OPTION]... PATTERN [FILE]...'))
3119 3119 def grep(ui, repo, pattern, *pats, **opts):
3120 3120 """search for a pattern in specified files and revisions
3121 3121
3122 3122 Search revisions of files for a regular expression.
3123 3123
3124 3124 This command behaves differently than Unix grep. It only accepts
3125 3125 Python/Perl regexps. It searches repository history, not the
3126 3126 working directory. It always prints the revision number in which a
3127 3127 match appears.
3128 3128
3129 3129 By default, grep only prints output for the first revision of a
3130 3130 file in which it finds a match. To get it to print every revision
3131 3131 that contains a change in match status ("-" for a match that
3132 3132 becomes a non-match, or "+" for a non-match that becomes a match),
3133 3133 use the --all flag.
3134 3134
3135 3135 Returns 0 if a match is found, 1 otherwise.
3136 3136 """
3137 3137 reflags = re.M
3138 3138 if opts.get('ignore_case'):
3139 3139 reflags |= re.I
3140 3140 try:
3141 3141 regexp = util.compilere(pattern, reflags)
3142 3142 except re.error, inst:
3143 3143 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
3144 3144 return 1
3145 3145 sep, eol = ':', '\n'
3146 3146 if opts.get('print0'):
3147 3147 sep = eol = '\0'
3148 3148
3149 3149 getfile = util.lrucachefunc(repo.file)
3150 3150
3151 3151 def matchlines(body):
3152 3152 begin = 0
3153 3153 linenum = 0
3154 3154 while begin < len(body):
3155 3155 match = regexp.search(body, begin)
3156 3156 if not match:
3157 3157 break
3158 3158 mstart, mend = match.span()
3159 3159 linenum += body.count('\n', begin, mstart) + 1
3160 3160 lstart = body.rfind('\n', begin, mstart) + 1 or begin
3161 3161 begin = body.find('\n', mend) + 1 or len(body) + 1
3162 3162 lend = begin - 1
3163 3163 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3164 3164
3165 3165 class linestate(object):
3166 3166 def __init__(self, line, linenum, colstart, colend):
3167 3167 self.line = line
3168 3168 self.linenum = linenum
3169 3169 self.colstart = colstart
3170 3170 self.colend = colend
3171 3171
3172 3172 def __hash__(self):
3173 3173 return hash((self.linenum, self.line))
3174 3174
3175 3175 def __eq__(self, other):
3176 3176 return self.line == other.line
3177 3177
3178 3178 matches = {}
3179 3179 copies = {}
3180 3180 def grepbody(fn, rev, body):
3181 3181 matches[rev].setdefault(fn, [])
3182 3182 m = matches[rev][fn]
3183 3183 for lnum, cstart, cend, line in matchlines(body):
3184 3184 s = linestate(line, lnum, cstart, cend)
3185 3185 m.append(s)
3186 3186
3187 3187 def difflinestates(a, b):
3188 3188 sm = difflib.SequenceMatcher(None, a, b)
3189 3189 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3190 3190 if tag == 'insert':
3191 3191 for i in xrange(blo, bhi):
3192 3192 yield ('+', b[i])
3193 3193 elif tag == 'delete':
3194 3194 for i in xrange(alo, ahi):
3195 3195 yield ('-', a[i])
3196 3196 elif tag == 'replace':
3197 3197 for i in xrange(alo, ahi):
3198 3198 yield ('-', a[i])
3199 3199 for i in xrange(blo, bhi):
3200 3200 yield ('+', b[i])
3201 3201
3202 3202 def display(fn, ctx, pstates, states):
3203 3203 rev = ctx.rev()
3204 3204 datefunc = ui.quiet and util.shortdate or util.datestr
3205 3205 found = False
3206 3206 filerevmatches = {}
3207 3207 def binary():
3208 3208 flog = getfile(fn)
3209 3209 return util.binary(flog.read(ctx.filenode(fn)))
3210 3210
3211 3211 if opts.get('all'):
3212 3212 iter = difflinestates(pstates, states)
3213 3213 else:
3214 3214 iter = [('', l) for l in states]
3215 3215 for change, l in iter:
3216 3216 cols = [(fn, 'grep.filename'), (str(rev), 'grep.rev')]
3217 3217 before, match, after = None, None, None
3218 3218
3219 3219 if opts.get('line_number'):
3220 3220 cols.append((str(l.linenum), 'grep.linenumber'))
3221 3221 if opts.get('all'):
3222 3222 cols.append((change, 'grep.change'))
3223 3223 if opts.get('user'):
3224 3224 cols.append((ui.shortuser(ctx.user()), 'grep.user'))
3225 3225 if opts.get('date'):
3226 3226 cols.append((datefunc(ctx.date()), 'grep.date'))
3227 3227 if opts.get('files_with_matches'):
3228 3228 c = (fn, rev)
3229 3229 if c in filerevmatches:
3230 3230 continue
3231 3231 filerevmatches[c] = 1
3232 3232 else:
3233 3233 before = l.line[:l.colstart]
3234 3234 match = l.line[l.colstart:l.colend]
3235 3235 after = l.line[l.colend:]
3236 3236 for col, label in cols[:-1]:
3237 3237 ui.write(col, label=label)
3238 3238 ui.write(sep, label='grep.sep')
3239 3239 ui.write(cols[-1][0], label=cols[-1][1])
3240 3240 if before is not None:
3241 3241 ui.write(sep, label='grep.sep')
3242 3242 if not opts.get('text') and binary():
3243 3243 ui.write(" Binary file matches")
3244 3244 else:
3245 3245 ui.write(before)
3246 3246 ui.write(match, label='grep.match')
3247 3247 ui.write(after)
3248 3248 ui.write(eol)
3249 3249 found = True
3250 3250 return found
3251 3251
3252 3252 skip = {}
3253 3253 revfiles = {}
3254 3254 matchfn = scmutil.match(repo[None], pats, opts)
3255 3255 found = False
3256 3256 follow = opts.get('follow')
3257 3257
3258 3258 def prep(ctx, fns):
3259 3259 rev = ctx.rev()
3260 3260 pctx = ctx.p1()
3261 3261 parent = pctx.rev()
3262 3262 matches.setdefault(rev, {})
3263 3263 matches.setdefault(parent, {})
3264 3264 files = revfiles.setdefault(rev, [])
3265 3265 for fn in fns:
3266 3266 flog = getfile(fn)
3267 3267 try:
3268 3268 fnode = ctx.filenode(fn)
3269 3269 except error.LookupError:
3270 3270 continue
3271 3271
3272 3272 copied = flog.renamed(fnode)
3273 3273 copy = follow and copied and copied[0]
3274 3274 if copy:
3275 3275 copies.setdefault(rev, {})[fn] = copy
3276 3276 if fn in skip:
3277 3277 if copy:
3278 3278 skip[copy] = True
3279 3279 continue
3280 3280 files.append(fn)
3281 3281
3282 3282 if fn not in matches[rev]:
3283 3283 grepbody(fn, rev, flog.read(fnode))
3284 3284
3285 3285 pfn = copy or fn
3286 3286 if pfn not in matches[parent]:
3287 3287 try:
3288 3288 fnode = pctx.filenode(pfn)
3289 3289 grepbody(pfn, parent, flog.read(fnode))
3290 3290 except error.LookupError:
3291 3291 pass
3292 3292
3293 3293 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3294 3294 rev = ctx.rev()
3295 3295 parent = ctx.p1().rev()
3296 3296 for fn in sorted(revfiles.get(rev, [])):
3297 3297 states = matches[rev][fn]
3298 3298 copy = copies.get(rev, {}).get(fn)
3299 3299 if fn in skip:
3300 3300 if copy:
3301 3301 skip[copy] = True
3302 3302 continue
3303 3303 pstates = matches.get(parent, {}).get(copy or fn, [])
3304 3304 if pstates or states:
3305 3305 r = display(fn, ctx, pstates, states)
3306 3306 found = found or r
3307 3307 if r and not opts.get('all'):
3308 3308 skip[fn] = True
3309 3309 if copy:
3310 3310 skip[copy] = True
3311 3311 del matches[rev]
3312 3312 del revfiles[rev]
3313 3313
3314 3314 return not found
3315 3315
3316 3316 @command('heads',
3317 3317 [('r', 'rev', '',
3318 3318 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3319 3319 ('t', 'topo', False, _('show topological heads only')),
3320 3320 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3321 3321 ('c', 'closed', False, _('show normal and closed branch heads')),
3322 3322 ] + templateopts,
3323 3323 _('[-ct] [-r STARTREV] [REV]...'))
3324 3324 def heads(ui, repo, *branchrevs, **opts):
3325 3325 """show branch heads
3326 3326
3327 3327 With no arguments, show all open branch heads in the repository.
3328 3328 Branch heads are changesets that have no descendants on the
3329 3329 same branch. They are where development generally takes place and
3330 3330 are the usual targets for update and merge operations.
3331 3331
3332 3332 If one or more REVs are given, only open branch heads on the
3333 3333 branches associated with the specified changesets are shown. This
3334 3334 means that you can use :hg:`heads .` to see the heads on the
3335 3335 currently checked-out branch.
3336 3336
3337 3337 If -c/--closed is specified, also show branch heads marked closed
3338 3338 (see :hg:`commit --close-branch`).
3339 3339
3340 3340 If STARTREV is specified, only those heads that are descendants of
3341 3341 STARTREV will be displayed.
3342 3342
3343 3343 If -t/--topo is specified, named branch mechanics will be ignored and only
3344 3344 topological heads (changesets with no children) will be shown.
3345 3345
3346 3346 Returns 0 if matching heads are found, 1 if not.
3347 3347 """
3348 3348
3349 3349 start = None
3350 3350 if 'rev' in opts:
3351 3351 start = scmutil.revsingle(repo, opts['rev'], None).node()
3352 3352
3353 3353 if opts.get('topo'):
3354 3354 heads = [repo[h] for h in repo.heads(start)]
3355 3355 else:
3356 3356 heads = []
3357 3357 for branch in repo.branchmap():
3358 3358 heads += repo.branchheads(branch, start, opts.get('closed'))
3359 3359 heads = [repo[h] for h in heads]
3360 3360
3361 3361 if branchrevs:
3362 3362 branches = set(repo[br].branch() for br in branchrevs)
3363 3363 heads = [h for h in heads if h.branch() in branches]
3364 3364
3365 3365 if opts.get('active') and branchrevs:
3366 3366 dagheads = repo.heads(start)
3367 3367 heads = [h for h in heads if h.node() in dagheads]
3368 3368
3369 3369 if branchrevs:
3370 3370 haveheads = set(h.branch() for h in heads)
3371 3371 if branches - haveheads:
3372 3372 headless = ', '.join(b for b in branches - haveheads)
3373 3373 msg = _('no open branch heads found on branches %s')
3374 3374 if opts.get('rev'):
3375 3375 msg += _(' (started at %s)') % opts['rev']
3376 3376 ui.warn((msg + '\n') % headless)
3377 3377
3378 3378 if not heads:
3379 3379 return 1
3380 3380
3381 3381 heads = sorted(heads, key=lambda x: -x.rev())
3382 3382 displayer = cmdutil.show_changeset(ui, repo, opts)
3383 3383 for ctx in heads:
3384 3384 displayer.show(ctx)
3385 3385 displayer.close()
3386 3386
3387 3387 @command('help',
3388 3388 [('e', 'extension', None, _('show only help for extensions')),
3389 3389 ('c', 'command', None, _('show only help for commands')),
3390 3390 ('k', 'keyword', '', _('show topics matching keyword')),
3391 3391 ],
3392 3392 _('[-ec] [TOPIC]'))
3393 3393 def help_(ui, name=None, **opts):
3394 3394 """show help for a given topic or a help overview
3395 3395
3396 3396 With no arguments, print a list of commands with short help messages.
3397 3397
3398 3398 Given a topic, extension, or command name, print help for that
3399 3399 topic.
3400 3400
3401 3401 Returns 0 if successful.
3402 3402 """
3403 3403
3404 3404 textwidth = min(ui.termwidth(), 80) - 2
3405 3405
3406 3406 keep = ui.verbose and ['verbose'] or []
3407 3407 text = help.help_(ui, name, **opts)
3408 3408
3409 3409 formatted, pruned = minirst.format(text, textwidth, keep=keep)
3410 3410 if 'verbose' in pruned:
3411 3411 keep.append('omitted')
3412 3412 else:
3413 3413 keep.append('notomitted')
3414 3414 formatted, pruned = minirst.format(text, textwidth, keep=keep)
3415 3415 ui.write(formatted)
3416 3416
3417 3417
3418 3418 @command('identify|id',
3419 3419 [('r', 'rev', '',
3420 3420 _('identify the specified revision'), _('REV')),
3421 3421 ('n', 'num', None, _('show local revision number')),
3422 3422 ('i', 'id', None, _('show global revision id')),
3423 3423 ('b', 'branch', None, _('show branch')),
3424 3424 ('t', 'tags', None, _('show tags')),
3425 3425 ('B', 'bookmarks', None, _('show bookmarks')),
3426 3426 ] + remoteopts,
3427 3427 _('[-nibtB] [-r REV] [SOURCE]'))
3428 3428 def identify(ui, repo, source=None, rev=None,
3429 3429 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3430 3430 """identify the working copy or specified revision
3431 3431
3432 3432 Print a summary identifying the repository state at REV using one or
3433 3433 two parent hash identifiers, followed by a "+" if the working
3434 3434 directory has uncommitted changes, the branch name (if not default),
3435 3435 a list of tags, and a list of bookmarks.
3436 3436
3437 3437 When REV is not given, print a summary of the current state of the
3438 3438 repository.
3439 3439
3440 3440 Specifying a path to a repository root or Mercurial bundle will
3441 3441 cause lookup to operate on that repository/bundle.
3442 3442
3443 3443 .. container:: verbose
3444 3444
3445 3445 Examples:
3446 3446
3447 3447 - generate a build identifier for the working directory::
3448 3448
3449 3449 hg id --id > build-id.dat
3450 3450
3451 3451 - find the revision corresponding to a tag::
3452 3452
3453 3453 hg id -n -r 1.3
3454 3454
3455 3455 - check the most recent revision of a remote repository::
3456 3456
3457 3457 hg id -r tip http://selenic.com/hg/
3458 3458
3459 3459 Returns 0 if successful.
3460 3460 """
3461 3461
3462 3462 if not repo and not source:
3463 3463 raise util.Abort(_("there is no Mercurial repository here "
3464 3464 "(.hg not found)"))
3465 3465
3466 3466 hexfunc = ui.debugflag and hex or short
3467 3467 default = not (num or id or branch or tags or bookmarks)
3468 3468 output = []
3469 3469 revs = []
3470 3470
3471 3471 if source:
3472 3472 source, branches = hg.parseurl(ui.expandpath(source))
3473 3473 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3474 3474 repo = peer.local()
3475 3475 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3476 3476
3477 3477 if not repo:
3478 3478 if num or branch or tags:
3479 3479 raise util.Abort(
3480 3480 _("can't query remote revision number, branch, or tags"))
3481 3481 if not rev and revs:
3482 3482 rev = revs[0]
3483 3483 if not rev:
3484 3484 rev = "tip"
3485 3485
3486 3486 remoterev = peer.lookup(rev)
3487 3487 if default or id:
3488 3488 output = [hexfunc(remoterev)]
3489 3489
3490 3490 def getbms():
3491 3491 bms = []
3492 3492
3493 3493 if 'bookmarks' in peer.listkeys('namespaces'):
3494 3494 hexremoterev = hex(remoterev)
3495 3495 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3496 3496 if bmr == hexremoterev]
3497 3497
3498 3498 return sorted(bms)
3499 3499
3500 3500 if bookmarks:
3501 3501 output.extend(getbms())
3502 3502 elif default and not ui.quiet:
3503 3503 # multiple bookmarks for a single parent separated by '/'
3504 3504 bm = '/'.join(getbms())
3505 3505 if bm:
3506 3506 output.append(bm)
3507 3507 else:
3508 3508 if not rev:
3509 3509 ctx = repo[None]
3510 3510 parents = ctx.parents()
3511 3511 changed = ""
3512 3512 if default or id or num:
3513 3513 if (util.any(repo.status())
3514 3514 or util.any(ctx.sub(s).dirty() for s in ctx.substate)):
3515 3515 changed = '+'
3516 3516 if default or id:
3517 3517 output = ["%s%s" %
3518 3518 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3519 3519 if num:
3520 3520 output.append("%s%s" %
3521 3521 ('+'.join([str(p.rev()) for p in parents]), changed))
3522 3522 else:
3523 3523 ctx = scmutil.revsingle(repo, rev)
3524 3524 if default or id:
3525 3525 output = [hexfunc(ctx.node())]
3526 3526 if num:
3527 3527 output.append(str(ctx.rev()))
3528 3528
3529 3529 if default and not ui.quiet:
3530 3530 b = ctx.branch()
3531 3531 if b != 'default':
3532 3532 output.append("(%s)" % b)
3533 3533
3534 3534 # multiple tags for a single parent separated by '/'
3535 3535 t = '/'.join(ctx.tags())
3536 3536 if t:
3537 3537 output.append(t)
3538 3538
3539 3539 # multiple bookmarks for a single parent separated by '/'
3540 3540 bm = '/'.join(ctx.bookmarks())
3541 3541 if bm:
3542 3542 output.append(bm)
3543 3543 else:
3544 3544 if branch:
3545 3545 output.append(ctx.branch())
3546 3546
3547 3547 if tags:
3548 3548 output.extend(ctx.tags())
3549 3549
3550 3550 if bookmarks:
3551 3551 output.extend(ctx.bookmarks())
3552 3552
3553 3553 ui.write("%s\n" % ' '.join(output))
3554 3554
3555 3555 @command('import|patch',
3556 3556 [('p', 'strip', 1,
3557 3557 _('directory strip option for patch. This has the same '
3558 3558 'meaning as the corresponding patch option'), _('NUM')),
3559 3559 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3560 3560 ('e', 'edit', False, _('invoke editor on commit messages')),
3561 3561 ('f', 'force', None,
3562 3562 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3563 3563 ('', 'no-commit', None,
3564 3564 _("don't commit, just update the working directory")),
3565 3565 ('', 'bypass', None,
3566 3566 _("apply patch without touching the working directory")),
3567 3567 ('', 'exact', None,
3568 3568 _('apply patch to the nodes from which it was generated')),
3569 3569 ('', 'import-branch', None,
3570 3570 _('use any branch information in patch (implied by --exact)'))] +
3571 3571 commitopts + commitopts2 + similarityopts,
3572 3572 _('[OPTION]... PATCH...'))
3573 3573 def import_(ui, repo, patch1=None, *patches, **opts):
3574 3574 """import an ordered set of patches
3575 3575
3576 3576 Import a list of patches and commit them individually (unless
3577 3577 --no-commit is specified).
3578 3578
3579 3579 Because import first applies changes to the working directory,
3580 3580 import will abort if there are outstanding changes.
3581 3581
3582 3582 You can import a patch straight from a mail message. Even patches
3583 3583 as attachments work (to use the body part, it must have type
3584 3584 text/plain or text/x-patch). From and Subject headers of email
3585 3585 message are used as default committer and commit message. All
3586 3586 text/plain body parts before first diff are added to commit
3587 3587 message.
3588 3588
3589 3589 If the imported patch was generated by :hg:`export`, user and
3590 3590 description from patch override values from message headers and
3591 3591 body. Values given on command line with -m/--message and -u/--user
3592 3592 override these.
3593 3593
3594 3594 If --exact is specified, import will set the working directory to
3595 3595 the parent of each patch before applying it, and will abort if the
3596 3596 resulting changeset has a different ID than the one recorded in
3597 3597 the patch. This may happen due to character set problems or other
3598 3598 deficiencies in the text patch format.
3599 3599
3600 3600 Use --bypass to apply and commit patches directly to the
3601 3601 repository, not touching the working directory. Without --exact,
3602 3602 patches will be applied on top of the working directory parent
3603 3603 revision.
3604 3604
3605 3605 With -s/--similarity, hg will attempt to discover renames and
3606 3606 copies in the patch in the same way as :hg:`addremove`.
3607 3607
3608 3608 To read a patch from standard input, use "-" as the patch name. If
3609 3609 a URL is specified, the patch will be downloaded from it.
3610 3610 See :hg:`help dates` for a list of formats valid for -d/--date.
3611 3611
3612 3612 .. container:: verbose
3613 3613
3614 3614 Examples:
3615 3615
3616 3616 - import a traditional patch from a website and detect renames::
3617 3617
3618 3618 hg import -s 80 http://example.com/bugfix.patch
3619 3619
3620 3620 - import a changeset from an hgweb server::
3621 3621
3622 3622 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
3623 3623
3624 3624 - import all the patches in an Unix-style mbox::
3625 3625
3626 3626 hg import incoming-patches.mbox
3627 3627
3628 3628 - attempt to exactly restore an exported changeset (not always
3629 3629 possible)::
3630 3630
3631 3631 hg import --exact proposed-fix.patch
3632 3632
3633 3633 Returns 0 on success.
3634 3634 """
3635 3635
3636 3636 if not patch1:
3637 3637 raise util.Abort(_('need at least one patch to import'))
3638 3638
3639 3639 patches = (patch1,) + patches
3640 3640
3641 3641 date = opts.get('date')
3642 3642 if date:
3643 3643 opts['date'] = util.parsedate(date)
3644 3644
3645 3645 editor = cmdutil.commiteditor
3646 3646 if opts.get('edit'):
3647 3647 editor = cmdutil.commitforceeditor
3648 3648
3649 3649 update = not opts.get('bypass')
3650 3650 if not update and opts.get('no_commit'):
3651 3651 raise util.Abort(_('cannot use --no-commit with --bypass'))
3652 3652 try:
3653 3653 sim = float(opts.get('similarity') or 0)
3654 3654 except ValueError:
3655 3655 raise util.Abort(_('similarity must be a number'))
3656 3656 if sim < 0 or sim > 100:
3657 3657 raise util.Abort(_('similarity must be between 0 and 100'))
3658 3658 if sim and not update:
3659 3659 raise util.Abort(_('cannot use --similarity with --bypass'))
3660 3660
3661 3661 if update:
3662 3662 cmdutil.checkunfinished(repo)
3663 3663 if (opts.get('exact') or not opts.get('force')) and update:
3664 3664 cmdutil.bailifchanged(repo)
3665 3665
3666 3666 base = opts["base"]
3667 3667 strip = opts["strip"]
3668 3668 wlock = lock = tr = None
3669 3669 msgs = []
3670 3670
3671 3671 def tryone(ui, hunk, parents):
3672 3672 tmpname, message, user, date, branch, nodeid, p1, p2 = \
3673 3673 patch.extract(ui, hunk)
3674 3674
3675 3675 if not tmpname:
3676 3676 return (None, None)
3677 3677 msg = _('applied to working directory')
3678 3678
3679 3679 try:
3680 3680 cmdline_message = cmdutil.logmessage(ui, opts)
3681 3681 if cmdline_message:
3682 3682 # pickup the cmdline msg
3683 3683 message = cmdline_message
3684 3684 elif message:
3685 3685 # pickup the patch msg
3686 3686 message = message.strip()
3687 3687 else:
3688 3688 # launch the editor
3689 3689 message = None
3690 3690 ui.debug('message:\n%s\n' % message)
3691 3691
3692 3692 if len(parents) == 1:
3693 3693 parents.append(repo[nullid])
3694 3694 if opts.get('exact'):
3695 3695 if not nodeid or not p1:
3696 3696 raise util.Abort(_('not a Mercurial patch'))
3697 3697 p1 = repo[p1]
3698 3698 p2 = repo[p2 or nullid]
3699 3699 elif p2:
3700 3700 try:
3701 3701 p1 = repo[p1]
3702 3702 p2 = repo[p2]
3703 3703 # Without any options, consider p2 only if the
3704 3704 # patch is being applied on top of the recorded
3705 3705 # first parent.
3706 3706 if p1 != parents[0]:
3707 3707 p1 = parents[0]
3708 3708 p2 = repo[nullid]
3709 3709 except error.RepoError:
3710 3710 p1, p2 = parents
3711 3711 else:
3712 3712 p1, p2 = parents
3713 3713
3714 3714 n = None
3715 3715 if update:
3716 3716 if p1 != parents[0]:
3717 3717 hg.clean(repo, p1.node())
3718 3718 if p2 != parents[1]:
3719 3719 repo.setparents(p1.node(), p2.node())
3720 3720
3721 3721 if opts.get('exact') or opts.get('import_branch'):
3722 3722 repo.dirstate.setbranch(branch or 'default')
3723 3723
3724 3724 files = set()
3725 3725 patch.patch(ui, repo, tmpname, strip=strip, files=files,
3726 3726 eolmode=None, similarity=sim / 100.0)
3727 3727 files = list(files)
3728 3728 if opts.get('no_commit'):
3729 3729 if message:
3730 3730 msgs.append(message)
3731 3731 else:
3732 3732 if opts.get('exact') or p2:
3733 3733 # If you got here, you either use --force and know what
3734 3734 # you are doing or used --exact or a merge patch while
3735 3735 # being updated to its first parent.
3736 3736 m = None
3737 3737 else:
3738 3738 m = scmutil.matchfiles(repo, files or [])
3739 3739 n = repo.commit(message, opts.get('user') or user,
3740 3740 opts.get('date') or date, match=m,
3741 3741 editor=editor)
3742 3742 else:
3743 3743 if opts.get('exact') or opts.get('import_branch'):
3744 3744 branch = branch or 'default'
3745 3745 else:
3746 3746 branch = p1.branch()
3747 3747 store = patch.filestore()
3748 3748 try:
3749 3749 files = set()
3750 3750 try:
3751 3751 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
3752 3752 files, eolmode=None)
3753 3753 except patch.PatchError, e:
3754 3754 raise util.Abort(str(e))
3755 3755 memctx = patch.makememctx(repo, (p1.node(), p2.node()),
3756 3756 message,
3757 3757 opts.get('user') or user,
3758 3758 opts.get('date') or date,
3759 3759 branch, files, store,
3760 3760 editor=cmdutil.commiteditor)
3761 3761 repo.savecommitmessage(memctx.description())
3762 3762 n = memctx.commit()
3763 3763 finally:
3764 3764 store.close()
3765 3765 if opts.get('exact') and hex(n) != nodeid:
3766 3766 raise util.Abort(_('patch is damaged or loses information'))
3767 3767 if n:
3768 3768 # i18n: refers to a short changeset id
3769 3769 msg = _('created %s') % short(n)
3770 3770 return (msg, n)
3771 3771 finally:
3772 3772 os.unlink(tmpname)
3773 3773
3774 3774 try:
3775 3775 try:
3776 3776 wlock = repo.wlock()
3777 3777 if not opts.get('no_commit'):
3778 3778 lock = repo.lock()
3779 3779 tr = repo.transaction('import')
3780 3780 parents = repo.parents()
3781 3781 for patchurl in patches:
3782 3782 if patchurl == '-':
3783 3783 ui.status(_('applying patch from stdin\n'))
3784 3784 patchfile = ui.fin
3785 3785 patchurl = 'stdin' # for error message
3786 3786 else:
3787 3787 patchurl = os.path.join(base, patchurl)
3788 3788 ui.status(_('applying %s\n') % patchurl)
3789 3789 patchfile = hg.openpath(ui, patchurl)
3790 3790
3791 3791 haspatch = False
3792 3792 for hunk in patch.split(patchfile):
3793 3793 (msg, node) = tryone(ui, hunk, parents)
3794 3794 if msg:
3795 3795 haspatch = True
3796 3796 ui.note(msg + '\n')
3797 3797 if update or opts.get('exact'):
3798 3798 parents = repo.parents()
3799 3799 else:
3800 3800 parents = [repo[node]]
3801 3801
3802 3802 if not haspatch:
3803 3803 raise util.Abort(_('%s: no diffs found') % patchurl)
3804 3804
3805 3805 if tr:
3806 3806 tr.close()
3807 3807 if msgs:
3808 3808 repo.savecommitmessage('\n* * *\n'.join(msgs))
3809 3809 except: # re-raises
3810 3810 # wlock.release() indirectly calls dirstate.write(): since
3811 3811 # we're crashing, we do not want to change the working dir
3812 3812 # parent after all, so make sure it writes nothing
3813 3813 repo.dirstate.invalidate()
3814 3814 raise
3815 3815 finally:
3816 3816 if tr:
3817 3817 tr.release()
3818 3818 release(lock, wlock)
3819 3819
3820 3820 @command('incoming|in',
3821 3821 [('f', 'force', None,
3822 3822 _('run even if remote repository is unrelated')),
3823 3823 ('n', 'newest-first', None, _('show newest record first')),
3824 3824 ('', 'bundle', '',
3825 3825 _('file to store the bundles into'), _('FILE')),
3826 3826 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3827 3827 ('B', 'bookmarks', False, _("compare bookmarks")),
3828 3828 ('b', 'branch', [],
3829 3829 _('a specific branch you would like to pull'), _('BRANCH')),
3830 3830 ] + logopts + remoteopts + subrepoopts,
3831 3831 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3832 3832 def incoming(ui, repo, source="default", **opts):
3833 3833 """show new changesets found in source
3834 3834
3835 3835 Show new changesets found in the specified path/URL or the default
3836 3836 pull location. These are the changesets that would have been pulled
3837 3837 if a pull at the time you issued this command.
3838 3838
3839 3839 For remote repository, using --bundle avoids downloading the
3840 3840 changesets twice if the incoming is followed by a pull.
3841 3841
3842 3842 See pull for valid source format details.
3843 3843
3844 3844 Returns 0 if there are incoming changes, 1 otherwise.
3845 3845 """
3846 3846 if opts.get('graph'):
3847 3847 cmdutil.checkunsupportedgraphflags([], opts)
3848 3848 def display(other, chlist, displayer):
3849 3849 revdag = cmdutil.graphrevs(other, chlist, opts)
3850 3850 showparents = [ctx.node() for ctx in repo[None].parents()]
3851 3851 cmdutil.displaygraph(ui, revdag, displayer, showparents,
3852 3852 graphmod.asciiedges)
3853 3853
3854 3854 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3855 3855 return 0
3856 3856
3857 3857 if opts.get('bundle') and opts.get('subrepos'):
3858 3858 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3859 3859
3860 3860 if opts.get('bookmarks'):
3861 3861 source, branches = hg.parseurl(ui.expandpath(source),
3862 3862 opts.get('branch'))
3863 3863 other = hg.peer(repo, opts, source)
3864 3864 if 'bookmarks' not in other.listkeys('namespaces'):
3865 3865 ui.warn(_("remote doesn't support bookmarks\n"))
3866 3866 return 0
3867 3867 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3868 3868 return bookmarks.diff(ui, repo, other)
3869 3869
3870 3870 repo._subtoppath = ui.expandpath(source)
3871 3871 try:
3872 3872 return hg.incoming(ui, repo, source, opts)
3873 3873 finally:
3874 3874 del repo._subtoppath
3875 3875
3876 3876
3877 3877 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
3878 3878 def init(ui, dest=".", **opts):
3879 3879 """create a new repository in the given directory
3880 3880
3881 3881 Initialize a new repository in the given directory. If the given
3882 3882 directory does not exist, it will be created.
3883 3883
3884 3884 If no directory is given, the current directory is used.
3885 3885
3886 3886 It is possible to specify an ``ssh://`` URL as the destination.
3887 3887 See :hg:`help urls` for more information.
3888 3888
3889 3889 Returns 0 on success.
3890 3890 """
3891 3891 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3892 3892
3893 3893 @command('locate',
3894 3894 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3895 3895 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3896 3896 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3897 3897 ] + walkopts,
3898 3898 _('[OPTION]... [PATTERN]...'))
3899 3899 def locate(ui, repo, *pats, **opts):
3900 3900 """locate files matching specific patterns
3901 3901
3902 3902 Print files under Mercurial control in the working directory whose
3903 3903 names match the given patterns.
3904 3904
3905 3905 By default, this command searches all directories in the working
3906 3906 directory. To search just the current directory and its
3907 3907 subdirectories, use "--include .".
3908 3908
3909 3909 If no patterns are given to match, this command prints the names
3910 3910 of all files under Mercurial control in the working directory.
3911 3911
3912 3912 If you want to feed the output of this command into the "xargs"
3913 3913 command, use the -0 option to both this command and "xargs". This
3914 3914 will avoid the problem of "xargs" treating single filenames that
3915 3915 contain whitespace as multiple filenames.
3916 3916
3917 3917 Returns 0 if a match is found, 1 otherwise.
3918 3918 """
3919 3919 end = opts.get('print0') and '\0' or '\n'
3920 3920 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3921 3921
3922 3922 ret = 1
3923 3923 m = scmutil.match(repo[rev], pats, opts, default='relglob')
3924 3924 m.bad = lambda x, y: False
3925 3925 for abs in repo[rev].walk(m):
3926 3926 if not rev and abs not in repo.dirstate:
3927 3927 continue
3928 3928 if opts.get('fullpath'):
3929 3929 ui.write(repo.wjoin(abs), end)
3930 3930 else:
3931 3931 ui.write(((pats and m.rel(abs)) or abs), end)
3932 3932 ret = 0
3933 3933
3934 3934 return ret
3935 3935
3936 3936 @command('^log|history',
3937 3937 [('f', 'follow', None,
3938 3938 _('follow changeset history, or file history across copies and renames')),
3939 3939 ('', 'follow-first', None,
3940 3940 _('only follow the first parent of merge changesets (DEPRECATED)')),
3941 3941 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3942 3942 ('C', 'copies', None, _('show copied files')),
3943 3943 ('k', 'keyword', [],
3944 3944 _('do case-insensitive search for a given text'), _('TEXT')),
3945 3945 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
3946 3946 ('', 'removed', None, _('include revisions where files were removed')),
3947 3947 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3948 3948 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3949 3949 ('', 'only-branch', [],
3950 3950 _('show only changesets within the given named branch (DEPRECATED)'),
3951 3951 _('BRANCH')),
3952 3952 ('b', 'branch', [],
3953 3953 _('show changesets within the given named branch'), _('BRANCH')),
3954 3954 ('P', 'prune', [],
3955 3955 _('do not display revision or any of its ancestors'), _('REV')),
3956 3956 ] + logopts + walkopts,
3957 3957 _('[OPTION]... [FILE]'))
3958 3958 def log(ui, repo, *pats, **opts):
3959 3959 """show revision history of entire repository or files
3960 3960
3961 3961 Print the revision history of the specified files or the entire
3962 3962 project.
3963 3963
3964 3964 If no revision range is specified, the default is ``tip:0`` unless
3965 3965 --follow is set, in which case the working directory parent is
3966 3966 used as the starting revision.
3967 3967
3968 3968 File history is shown without following rename or copy history of
3969 3969 files. Use -f/--follow with a filename to follow history across
3970 3970 renames and copies. --follow without a filename will only show
3971 3971 ancestors or descendants of the starting revision.
3972 3972
3973 3973 By default this command prints revision number and changeset id,
3974 3974 tags, non-trivial parents, user, date and time, and a summary for
3975 3975 each commit. When the -v/--verbose switch is used, the list of
3976 3976 changed files and full commit message are shown.
3977 3977
3978 3978 .. note::
3979 3979 log -p/--patch may generate unexpected diff output for merge
3980 3980 changesets, as it will only compare the merge changeset against
3981 3981 its first parent. Also, only files different from BOTH parents
3982 3982 will appear in files:.
3983 3983
3984 3984 .. note::
3985 3985 for performance reasons, log FILE may omit duplicate changes
3986 3986 made on branches and will not show deletions. To see all
3987 3987 changes including duplicates and deletions, use the --removed
3988 3988 switch.
3989 3989
3990 3990 .. container:: verbose
3991 3991
3992 3992 Some examples:
3993 3993
3994 3994 - changesets with full descriptions and file lists::
3995 3995
3996 3996 hg log -v
3997 3997
3998 3998 - changesets ancestral to the working directory::
3999 3999
4000 4000 hg log -f
4001 4001
4002 4002 - last 10 commits on the current branch::
4003 4003
4004 4004 hg log -l 10 -b .
4005 4005
4006 4006 - changesets showing all modifications of a file, including removals::
4007 4007
4008 4008 hg log --removed file.c
4009 4009
4010 4010 - all changesets that touch a directory, with diffs, excluding merges::
4011 4011
4012 4012 hg log -Mp lib/
4013 4013
4014 4014 - all revision numbers that match a keyword::
4015 4015
4016 4016 hg log -k bug --template "{rev}\\n"
4017 4017
4018 4018 - check if a given changeset is included is a tagged release::
4019 4019
4020 4020 hg log -r "a21ccf and ancestor(1.9)"
4021 4021
4022 4022 - find all changesets by some user in a date range::
4023 4023
4024 4024 hg log -k alice -d "may 2008 to jul 2008"
4025 4025
4026 4026 - summary of all changesets after the last tag::
4027 4027
4028 4028 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4029 4029
4030 4030 See :hg:`help dates` for a list of formats valid for -d/--date.
4031 4031
4032 4032 See :hg:`help revisions` and :hg:`help revsets` for more about
4033 4033 specifying revisions.
4034 4034
4035 4035 See :hg:`help templates` for more about pre-packaged styles and
4036 4036 specifying custom templates.
4037 4037
4038 4038 Returns 0 on success.
4039 4039 """
4040 4040 if opts.get('graph'):
4041 4041 return cmdutil.graphlog(ui, repo, *pats, **opts)
4042 4042
4043 4043 matchfn = scmutil.match(repo[None], pats, opts)
4044 4044 limit = cmdutil.loglimit(opts)
4045 4045 count = 0
4046 4046
4047 4047 getrenamed, endrev = None, None
4048 4048 if opts.get('copies'):
4049 4049 if opts.get('rev'):
4050 4050 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
4051 4051 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
4052 4052
4053 4053 df = False
4054 4054 if opts.get("date"):
4055 4055 df = util.matchdate(opts["date"])
4056 4056
4057 4057 branches = opts.get('branch', []) + opts.get('only_branch', [])
4058 4058 opts['branch'] = [repo.lookupbranch(b) for b in branches]
4059 4059
4060 4060 displayer = cmdutil.show_changeset(ui, repo, opts, True)
4061 4061 def prep(ctx, fns):
4062 4062 rev = ctx.rev()
4063 4063 parents = [p for p in repo.changelog.parentrevs(rev)
4064 4064 if p != nullrev]
4065 4065 if opts.get('no_merges') and len(parents) == 2:
4066 4066 return
4067 4067 if opts.get('only_merges') and len(parents) != 2:
4068 4068 return
4069 4069 if opts.get('branch') and ctx.branch() not in opts['branch']:
4070 4070 return
4071 4071 if df and not df(ctx.date()[0]):
4072 4072 return
4073 4073
4074 4074 lower = encoding.lower
4075 4075 if opts.get('user'):
4076 4076 luser = lower(ctx.user())
4077 4077 for k in [lower(x) for x in opts['user']]:
4078 4078 if (k in luser):
4079 4079 break
4080 4080 else:
4081 4081 return
4082 4082 if opts.get('keyword'):
4083 4083 luser = lower(ctx.user())
4084 4084 ldesc = lower(ctx.description())
4085 4085 lfiles = lower(" ".join(ctx.files()))
4086 4086 for k in [lower(x) for x in opts['keyword']]:
4087 4087 if (k in luser or k in ldesc or k in lfiles):
4088 4088 break
4089 4089 else:
4090 4090 return
4091 4091
4092 4092 copies = None
4093 4093 if getrenamed is not None and rev:
4094 4094 copies = []
4095 4095 for fn in ctx.files():
4096 4096 rename = getrenamed(fn, rev)
4097 4097 if rename:
4098 4098 copies.append((fn, rename[0]))
4099 4099
4100 4100 revmatchfn = None
4101 4101 if opts.get('patch') or opts.get('stat'):
4102 4102 if opts.get('follow') or opts.get('follow_first'):
4103 4103 # note: this might be wrong when following through merges
4104 4104 revmatchfn = scmutil.match(repo[None], fns, default='path')
4105 4105 else:
4106 4106 revmatchfn = matchfn
4107 4107
4108 4108 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
4109 4109
4110 4110 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
4111 4111 if displayer.flush(ctx.rev()):
4112 4112 count += 1
4113 4113 if count == limit:
4114 4114 break
4115 4115 displayer.close()
4116 4116
4117 4117 @command('manifest',
4118 4118 [('r', 'rev', '', _('revision to display'), _('REV')),
4119 4119 ('', 'all', False, _("list files from all revisions"))],
4120 4120 _('[-r REV]'))
4121 4121 def manifest(ui, repo, node=None, rev=None, **opts):
4122 4122 """output the current or given revision of the project manifest
4123 4123
4124 4124 Print a list of version controlled files for the given revision.
4125 4125 If no revision is given, the first parent of the working directory
4126 4126 is used, or the null revision if no revision is checked out.
4127 4127
4128 4128 With -v, print file permissions, symlink and executable bits.
4129 4129 With --debug, print file revision hashes.
4130 4130
4131 4131 If option --all is specified, the list of all files from all revisions
4132 4132 is printed. This includes deleted and renamed files.
4133 4133
4134 4134 Returns 0 on success.
4135 4135 """
4136 4136
4137 4137 fm = ui.formatter('manifest', opts)
4138 4138
4139 4139 if opts.get('all'):
4140 4140 if rev or node:
4141 4141 raise util.Abort(_("can't specify a revision with --all"))
4142 4142
4143 4143 res = []
4144 4144 prefix = "data/"
4145 4145 suffix = ".i"
4146 4146 plen = len(prefix)
4147 4147 slen = len(suffix)
4148 4148 lock = repo.lock()
4149 4149 try:
4150 4150 for fn, b, size in repo.store.datafiles():
4151 4151 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
4152 4152 res.append(fn[plen:-slen])
4153 4153 finally:
4154 4154 lock.release()
4155 4155 for f in res:
4156 4156 fm.startitem()
4157 4157 fm.write("path", '%s\n', f)
4158 4158 fm.end()
4159 4159 return
4160 4160
4161 4161 if rev and node:
4162 4162 raise util.Abort(_("please specify just one revision"))
4163 4163
4164 4164 if not node:
4165 4165 node = rev
4166 4166
4167 4167 char = {'l': '@', 'x': '*', '': ''}
4168 4168 mode = {'l': '644', 'x': '755', '': '644'}
4169 4169 ctx = scmutil.revsingle(repo, node)
4170 4170 mf = ctx.manifest()
4171 4171 for f in ctx:
4172 4172 fm.startitem()
4173 4173 fl = ctx[f].flags()
4174 4174 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
4175 4175 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
4176 4176 fm.write('path', '%s\n', f)
4177 4177 fm.end()
4178 4178
4179 4179 @command('^merge',
4180 4180 [('f', 'force', None,
4181 4181 _('force a merge including outstanding changes (DEPRECATED)')),
4182 4182 ('r', 'rev', '', _('revision to merge'), _('REV')),
4183 4183 ('P', 'preview', None,
4184 4184 _('review revisions to merge (no merge is performed)'))
4185 4185 ] + mergetoolopts,
4186 4186 _('[-P] [-f] [[-r] REV]'))
4187 4187 def merge(ui, repo, node=None, **opts):
4188 4188 """merge working directory with another revision
4189 4189
4190 4190 The current working directory is updated with all changes made in
4191 4191 the requested revision since the last common predecessor revision.
4192 4192
4193 4193 Files that changed between either parent are marked as changed for
4194 4194 the next commit and a commit must be performed before any further
4195 4195 updates to the repository are allowed. The next commit will have
4196 4196 two parents.
4197 4197
4198 4198 ``--tool`` can be used to specify the merge tool used for file
4199 4199 merges. It overrides the HGMERGE environment variable and your
4200 4200 configuration files. See :hg:`help merge-tools` for options.
4201 4201
4202 4202 If no revision is specified, the working directory's parent is a
4203 4203 head revision, and the current branch contains exactly one other
4204 4204 head, the other head is merged with by default. Otherwise, an
4205 4205 explicit revision with which to merge with must be provided.
4206 4206
4207 4207 :hg:`resolve` must be used to resolve unresolved files.
4208 4208
4209 4209 To undo an uncommitted merge, use :hg:`update --clean .` which
4210 4210 will check out a clean copy of the original merge parent, losing
4211 4211 all changes.
4212 4212
4213 4213 Returns 0 on success, 1 if there are unresolved files.
4214 4214 """
4215 4215
4216 4216 if opts.get('rev') and node:
4217 4217 raise util.Abort(_("please specify just one revision"))
4218 4218 if not node:
4219 4219 node = opts.get('rev')
4220 4220
4221 4221 if node:
4222 4222 node = scmutil.revsingle(repo, node).node()
4223 4223
4224 4224 if not node and repo._bookmarkcurrent:
4225 4225 bmheads = repo.bookmarkheads(repo._bookmarkcurrent)
4226 4226 curhead = repo[repo._bookmarkcurrent].node()
4227 4227 if len(bmheads) == 2:
4228 4228 if curhead == bmheads[0]:
4229 4229 node = bmheads[1]
4230 4230 else:
4231 4231 node = bmheads[0]
4232 4232 elif len(bmheads) > 2:
4233 4233 raise util.Abort(_("multiple matching bookmarks to merge - "
4234 4234 "please merge with an explicit rev or bookmark"),
4235 4235 hint=_("run 'hg heads' to see all heads"))
4236 4236 elif len(bmheads) <= 1:
4237 4237 raise util.Abort(_("no matching bookmark to merge - "
4238 4238 "please merge with an explicit rev or bookmark"),
4239 4239 hint=_("run 'hg heads' to see all heads"))
4240 4240
4241 4241 if not node and not repo._bookmarkcurrent:
4242 4242 branch = repo[None].branch()
4243 4243 bheads = repo.branchheads(branch)
4244 4244 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
4245 4245
4246 4246 if len(nbhs) > 2:
4247 4247 raise util.Abort(_("branch '%s' has %d heads - "
4248 4248 "please merge with an explicit rev")
4249 4249 % (branch, len(bheads)),
4250 4250 hint=_("run 'hg heads .' to see heads"))
4251 4251
4252 4252 parent = repo.dirstate.p1()
4253 4253 if len(nbhs) <= 1:
4254 4254 if len(bheads) > 1:
4255 4255 raise util.Abort(_("heads are bookmarked - "
4256 4256 "please merge with an explicit rev"),
4257 4257 hint=_("run 'hg heads' to see all heads"))
4258 4258 if len(repo.heads()) > 1:
4259 4259 raise util.Abort(_("branch '%s' has one head - "
4260 4260 "please merge with an explicit rev")
4261 4261 % branch,
4262 4262 hint=_("run 'hg heads' to see all heads"))
4263 4263 msg, hint = _('nothing to merge'), None
4264 4264 if parent != repo.lookup(branch):
4265 4265 hint = _("use 'hg update' instead")
4266 4266 raise util.Abort(msg, hint=hint)
4267 4267
4268 4268 if parent not in bheads:
4269 4269 raise util.Abort(_('working directory not at a head revision'),
4270 4270 hint=_("use 'hg update' or merge with an "
4271 4271 "explicit revision"))
4272 4272 if parent == nbhs[0]:
4273 4273 node = nbhs[-1]
4274 4274 else:
4275 4275 node = nbhs[0]
4276 4276
4277 4277 if opts.get('preview'):
4278 4278 # find nodes that are ancestors of p2 but not of p1
4279 4279 p1 = repo.lookup('.')
4280 4280 p2 = repo.lookup(node)
4281 4281 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4282 4282
4283 4283 displayer = cmdutil.show_changeset(ui, repo, opts)
4284 4284 for node in nodes:
4285 4285 displayer.show(repo[node])
4286 4286 displayer.close()
4287 4287 return 0
4288 4288
4289 4289 try:
4290 4290 # ui.forcemerge is an internal variable, do not document
4291 4291 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4292 4292 return hg.merge(repo, node, force=opts.get('force'))
4293 4293 finally:
4294 4294 ui.setconfig('ui', 'forcemerge', '')
4295 4295
4296 4296 @command('outgoing|out',
4297 4297 [('f', 'force', None, _('run even when the destination is unrelated')),
4298 4298 ('r', 'rev', [],
4299 4299 _('a changeset intended to be included in the destination'), _('REV')),
4300 4300 ('n', 'newest-first', None, _('show newest record first')),
4301 4301 ('B', 'bookmarks', False, _('compare bookmarks')),
4302 4302 ('b', 'branch', [], _('a specific branch you would like to push'),
4303 4303 _('BRANCH')),
4304 4304 ] + logopts + remoteopts + subrepoopts,
4305 4305 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4306 4306 def outgoing(ui, repo, dest=None, **opts):
4307 4307 """show changesets not found in the destination
4308 4308
4309 4309 Show changesets not found in the specified destination repository
4310 4310 or the default push location. These are the changesets that would
4311 4311 be pushed if a push was requested.
4312 4312
4313 4313 See pull for details of valid destination formats.
4314 4314
4315 4315 Returns 0 if there are outgoing changes, 1 otherwise.
4316 4316 """
4317 4317 if opts.get('graph'):
4318 4318 cmdutil.checkunsupportedgraphflags([], opts)
4319 4319 o = hg._outgoing(ui, repo, dest, opts)
4320 4320 if o is None:
4321 4321 return
4322 4322
4323 4323 revdag = cmdutil.graphrevs(repo, o, opts)
4324 4324 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
4325 4325 showparents = [ctx.node() for ctx in repo[None].parents()]
4326 4326 cmdutil.displaygraph(ui, revdag, displayer, showparents,
4327 4327 graphmod.asciiedges)
4328 4328 return 0
4329 4329
4330 4330 if opts.get('bookmarks'):
4331 4331 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4332 4332 dest, branches = hg.parseurl(dest, opts.get('branch'))
4333 4333 other = hg.peer(repo, opts, dest)
4334 4334 if 'bookmarks' not in other.listkeys('namespaces'):
4335 4335 ui.warn(_("remote doesn't support bookmarks\n"))
4336 4336 return 0
4337 4337 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4338 4338 return bookmarks.diff(ui, other, repo)
4339 4339
4340 4340 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4341 4341 try:
4342 4342 return hg.outgoing(ui, repo, dest, opts)
4343 4343 finally:
4344 4344 del repo._subtoppath
4345 4345
4346 4346 @command('parents',
4347 4347 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4348 4348 ] + templateopts,
4349 4349 _('[-r REV] [FILE]'))
4350 4350 def parents(ui, repo, file_=None, **opts):
4351 4351 """show the parents of the working directory or revision
4352 4352
4353 4353 Print the working directory's parent revisions. If a revision is
4354 4354 given via -r/--rev, the parent of that revision will be printed.
4355 4355 If a file argument is given, the revision in which the file was
4356 4356 last changed (before the working directory revision or the
4357 4357 argument to --rev if given) is printed.
4358 4358
4359 4359 Returns 0 on success.
4360 4360 """
4361 4361
4362 4362 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4363 4363
4364 4364 if file_:
4365 4365 m = scmutil.match(ctx, (file_,), opts)
4366 4366 if m.anypats() or len(m.files()) != 1:
4367 4367 raise util.Abort(_('can only specify an explicit filename'))
4368 4368 file_ = m.files()[0]
4369 4369 filenodes = []
4370 4370 for cp in ctx.parents():
4371 4371 if not cp:
4372 4372 continue
4373 4373 try:
4374 4374 filenodes.append(cp.filenode(file_))
4375 4375 except error.LookupError:
4376 4376 pass
4377 4377 if not filenodes:
4378 4378 raise util.Abort(_("'%s' not found in manifest!") % file_)
4379 4379 p = []
4380 4380 for fn in filenodes:
4381 4381 fctx = repo.filectx(file_, fileid=fn)
4382 4382 p.append(fctx.node())
4383 4383 else:
4384 4384 p = [cp.node() for cp in ctx.parents()]
4385 4385
4386 4386 displayer = cmdutil.show_changeset(ui, repo, opts)
4387 4387 for n in p:
4388 4388 if n != nullid:
4389 4389 displayer.show(repo[n])
4390 4390 displayer.close()
4391 4391
4392 4392 @command('paths', [], _('[NAME]'))
4393 4393 def paths(ui, repo, search=None):
4394 4394 """show aliases for remote repositories
4395 4395
4396 4396 Show definition of symbolic path name NAME. If no name is given,
4397 4397 show definition of all available names.
4398 4398
4399 4399 Option -q/--quiet suppresses all output when searching for NAME
4400 4400 and shows only the path names when listing all definitions.
4401 4401
4402 4402 Path names are defined in the [paths] section of your
4403 4403 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4404 4404 repository, ``.hg/hgrc`` is used, too.
4405 4405
4406 4406 The path names ``default`` and ``default-push`` have a special
4407 4407 meaning. When performing a push or pull operation, they are used
4408 4408 as fallbacks if no location is specified on the command-line.
4409 4409 When ``default-push`` is set, it will be used for push and
4410 4410 ``default`` will be used for pull; otherwise ``default`` is used
4411 4411 as the fallback for both. When cloning a repository, the clone
4412 4412 source is written as ``default`` in ``.hg/hgrc``. Note that
4413 4413 ``default`` and ``default-push`` apply to all inbound (e.g.
4414 4414 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4415 4415 :hg:`bundle`) operations.
4416 4416
4417 4417 See :hg:`help urls` for more information.
4418 4418
4419 4419 Returns 0 on success.
4420 4420 """
4421 4421 if search:
4422 4422 for name, path in ui.configitems("paths"):
4423 4423 if name == search:
4424 4424 ui.status("%s\n" % util.hidepassword(path))
4425 4425 return
4426 4426 if not ui.quiet:
4427 4427 ui.warn(_("not found!\n"))
4428 4428 return 1
4429 4429 else:
4430 4430 for name, path in ui.configitems("paths"):
4431 4431 if ui.quiet:
4432 4432 ui.write("%s\n" % name)
4433 4433 else:
4434 4434 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4435 4435
4436 4436 @command('phase',
4437 4437 [('p', 'public', False, _('set changeset phase to public')),
4438 4438 ('d', 'draft', False, _('set changeset phase to draft')),
4439 4439 ('s', 'secret', False, _('set changeset phase to secret')),
4440 4440 ('f', 'force', False, _('allow to move boundary backward')),
4441 4441 ('r', 'rev', [], _('target revision'), _('REV')),
4442 4442 ],
4443 4443 _('[-p|-d|-s] [-f] [-r] REV...'))
4444 4444 def phase(ui, repo, *revs, **opts):
4445 4445 """set or show the current phase name
4446 4446
4447 4447 With no argument, show the phase name of specified revisions.
4448 4448
4449 4449 With one of -p/--public, -d/--draft or -s/--secret, change the
4450 4450 phase value of the specified revisions.
4451 4451
4452 4452 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
4453 4453 lower phase to an higher phase. Phases are ordered as follows::
4454 4454
4455 4455 public < draft < secret
4456 4456
4457 4457 Return 0 on success, 1 if no phases were changed or some could not
4458 4458 be changed.
4459 4459 """
4460 4460 # search for a unique phase argument
4461 4461 targetphase = None
4462 4462 for idx, name in enumerate(phases.phasenames):
4463 4463 if opts[name]:
4464 4464 if targetphase is not None:
4465 4465 raise util.Abort(_('only one phase can be specified'))
4466 4466 targetphase = idx
4467 4467
4468 4468 # look for specified revision
4469 4469 revs = list(revs)
4470 4470 revs.extend(opts['rev'])
4471 4471 if not revs:
4472 4472 raise util.Abort(_('no revisions specified'))
4473 4473
4474 4474 revs = scmutil.revrange(repo, revs)
4475 4475
4476 4476 lock = None
4477 4477 ret = 0
4478 4478 if targetphase is None:
4479 4479 # display
4480 4480 for r in revs:
4481 4481 ctx = repo[r]
4482 4482 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4483 4483 else:
4484 4484 lock = repo.lock()
4485 4485 try:
4486 4486 # set phase
4487 4487 if not revs:
4488 4488 raise util.Abort(_('empty revision set'))
4489 4489 nodes = [repo[r].node() for r in revs]
4490 4490 olddata = repo._phasecache.getphaserevs(repo)[:]
4491 4491 phases.advanceboundary(repo, targetphase, nodes)
4492 4492 if opts['force']:
4493 4493 phases.retractboundary(repo, targetphase, nodes)
4494 4494 finally:
4495 4495 lock.release()
4496 4496 # moving revision from public to draft may hide them
4497 4497 # We have to check result on an unfiltered repository
4498 4498 unfi = repo.unfiltered()
4499 4499 newdata = repo._phasecache.getphaserevs(unfi)
4500 4500 changes = sum(o != newdata[i] for i, o in enumerate(olddata))
4501 4501 cl = unfi.changelog
4502 4502 rejected = [n for n in nodes
4503 4503 if newdata[cl.rev(n)] < targetphase]
4504 4504 if rejected:
4505 4505 ui.warn(_('cannot move %i changesets to a more permissive '
4506 4506 'phase, use --force\n') % len(rejected))
4507 4507 ret = 1
4508 4508 if changes:
4509 4509 msg = _('phase changed for %i changesets\n') % changes
4510 4510 if ret:
4511 4511 ui.status(msg)
4512 4512 else:
4513 4513 ui.note(msg)
4514 4514 else:
4515 4515 ui.warn(_('no phases changed\n'))
4516 4516 ret = 1
4517 4517 return ret
4518 4518
4519 4519 def postincoming(ui, repo, modheads, optupdate, checkout):
4520 4520 if modheads == 0:
4521 4521 return
4522 4522 if optupdate:
4523 4523 movemarkfrom = repo['.'].node()
4524 4524 try:
4525 4525 ret = hg.update(repo, checkout)
4526 4526 except util.Abort, inst:
4527 4527 ui.warn(_("not updating: %s\n") % str(inst))
4528 4528 return 0
4529 4529 if not ret and not checkout:
4530 4530 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
4531 4531 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
4532 4532 return ret
4533 4533 if modheads > 1:
4534 4534 currentbranchheads = len(repo.branchheads())
4535 4535 if currentbranchheads == modheads:
4536 4536 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4537 4537 elif currentbranchheads > 1:
4538 4538 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4539 4539 "merge)\n"))
4540 4540 else:
4541 4541 ui.status(_("(run 'hg heads' to see heads)\n"))
4542 4542 else:
4543 4543 ui.status(_("(run 'hg update' to get a working copy)\n"))
4544 4544
4545 4545 @command('^pull',
4546 4546 [('u', 'update', None,
4547 4547 _('update to new branch head if changesets were pulled')),
4548 4548 ('f', 'force', None, _('run even when remote repository is unrelated')),
4549 4549 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4550 4550 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4551 4551 ('b', 'branch', [], _('a specific branch you would like to pull'),
4552 4552 _('BRANCH')),
4553 4553 ] + remoteopts,
4554 4554 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4555 4555 def pull(ui, repo, source="default", **opts):
4556 4556 """pull changes from the specified source
4557 4557
4558 4558 Pull changes from a remote repository to a local one.
4559 4559
4560 4560 This finds all changes from the repository at the specified path
4561 4561 or URL and adds them to a local repository (the current one unless
4562 4562 -R is specified). By default, this does not update the copy of the
4563 4563 project in the working directory.
4564 4564
4565 4565 Use :hg:`incoming` if you want to see what would have been added
4566 4566 by a pull at the time you issued this command. If you then decide
4567 4567 to add those changes to the repository, you should use :hg:`pull
4568 4568 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4569 4569
4570 4570 If SOURCE is omitted, the 'default' path will be used.
4571 4571 See :hg:`help urls` for more information.
4572 4572
4573 4573 Returns 0 on success, 1 if an update had unresolved files.
4574 4574 """
4575 4575 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4576 4576 other = hg.peer(repo, opts, source)
4577 4577 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4578 4578 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4579 4579
4580 4580 remotebookmarks = other.listkeys('bookmarks')
4581 4581
4582 4582 if opts.get('bookmark'):
4583 4583 if not revs:
4584 4584 revs = []
4585 4585 for b in opts['bookmark']:
4586 4586 if b not in remotebookmarks:
4587 4587 raise util.Abort(_('remote bookmark %s not found!') % b)
4588 4588 revs.append(remotebookmarks[b])
4589 4589
4590 4590 if revs:
4591 4591 try:
4592 4592 revs = [other.lookup(rev) for rev in revs]
4593 4593 except error.CapabilityError:
4594 4594 err = _("other repository doesn't support revision lookup, "
4595 4595 "so a rev cannot be specified.")
4596 4596 raise util.Abort(err)
4597 4597
4598 4598 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
4599 4599 bookmarks.updatefromremote(ui, repo, remotebookmarks, source)
4600 4600 if checkout:
4601 4601 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4602 4602 repo._subtoppath = source
4603 4603 try:
4604 4604 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4605 4605
4606 4606 finally:
4607 4607 del repo._subtoppath
4608 4608
4609 4609 # update specified bookmarks
4610 4610 if opts.get('bookmark'):
4611 4611 marks = repo._bookmarks
4612 4612 for b in opts['bookmark']:
4613 4613 # explicit pull overrides local bookmark if any
4614 4614 ui.status(_("importing bookmark %s\n") % b)
4615 4615 marks[b] = repo[remotebookmarks[b]].node()
4616 4616 marks.write()
4617 4617
4618 4618 return ret
4619 4619
4620 4620 @command('^push',
4621 4621 [('f', 'force', None, _('force push')),
4622 4622 ('r', 'rev', [],
4623 4623 _('a changeset intended to be included in the destination'),
4624 4624 _('REV')),
4625 4625 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4626 4626 ('b', 'branch', [],
4627 4627 _('a specific branch you would like to push'), _('BRANCH')),
4628 4628 ('', 'new-branch', False, _('allow pushing a new branch')),
4629 4629 ] + remoteopts,
4630 4630 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4631 4631 def push(ui, repo, dest=None, **opts):
4632 4632 """push changes to the specified destination
4633 4633
4634 4634 Push changesets from the local repository to the specified
4635 4635 destination.
4636 4636
4637 4637 This operation is symmetrical to pull: it is identical to a pull
4638 4638 in the destination repository from the current one.
4639 4639
4640 4640 By default, push will not allow creation of new heads at the
4641 4641 destination, since multiple heads would make it unclear which head
4642 4642 to use. In this situation, it is recommended to pull and merge
4643 4643 before pushing.
4644 4644
4645 4645 Use --new-branch if you want to allow push to create a new named
4646 4646 branch that is not present at the destination. This allows you to
4647 4647 only create a new branch without forcing other changes.
4648 4648
4649 4649 Use -f/--force to override the default behavior and push all
4650 4650 changesets on all branches.
4651 4651
4652 4652 If -r/--rev is used, the specified revision and all its ancestors
4653 4653 will be pushed to the remote repository.
4654 4654
4655 4655 If -B/--bookmark is used, the specified bookmarked revision, its
4656 4656 ancestors, and the bookmark will be pushed to the remote
4657 4657 repository.
4658 4658
4659 4659 Please see :hg:`help urls` for important details about ``ssh://``
4660 4660 URLs. If DESTINATION is omitted, a default path will be used.
4661 4661
4662 4662 Returns 0 if push was successful, 1 if nothing to push.
4663 4663 """
4664 4664
4665 4665 if opts.get('bookmark'):
4666 4666 for b in opts['bookmark']:
4667 4667 # translate -B options to -r so changesets get pushed
4668 4668 if b in repo._bookmarks:
4669 4669 opts.setdefault('rev', []).append(b)
4670 4670 else:
4671 4671 # if we try to push a deleted bookmark, translate it to null
4672 4672 # this lets simultaneous -r, -b options continue working
4673 4673 opts.setdefault('rev', []).append("null")
4674 4674
4675 4675 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4676 4676 dest, branches = hg.parseurl(dest, opts.get('branch'))
4677 4677 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4678 4678 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4679 4679 other = hg.peer(repo, opts, dest)
4680 4680 if revs:
4681 4681 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4682 4682
4683 4683 repo._subtoppath = dest
4684 4684 try:
4685 4685 # push subrepos depth-first for coherent ordering
4686 4686 c = repo['']
4687 4687 subs = c.substate # only repos that are committed
4688 4688 for s in sorted(subs):
4689 4689 if c.sub(s).push(opts) == 0:
4690 4690 return False
4691 4691 finally:
4692 4692 del repo._subtoppath
4693 4693 result = repo.push(other, opts.get('force'), revs=revs,
4694 4694 newbranch=opts.get('new_branch'))
4695 4695
4696 4696 result = not result
4697 4697
4698 4698 if opts.get('bookmark'):
4699 4699 rb = other.listkeys('bookmarks')
4700 4700 for b in opts['bookmark']:
4701 4701 # explicit push overrides remote bookmark if any
4702 4702 if b in repo._bookmarks:
4703 4703 ui.status(_("exporting bookmark %s\n") % b)
4704 4704 new = repo[b].hex()
4705 4705 elif b in rb:
4706 4706 ui.status(_("deleting remote bookmark %s\n") % b)
4707 4707 new = '' # delete
4708 4708 else:
4709 4709 ui.warn(_('bookmark %s does not exist on the local '
4710 4710 'or remote repository!\n') % b)
4711 4711 return 2
4712 4712 old = rb.get(b, '')
4713 4713 r = other.pushkey('bookmarks', b, old, new)
4714 4714 if not r:
4715 4715 ui.warn(_('updating bookmark %s failed!\n') % b)
4716 4716 if not result:
4717 4717 result = 2
4718 4718
4719 4719 return result
4720 4720
4721 4721 @command('recover', [])
4722 4722 def recover(ui, repo):
4723 4723 """roll back an interrupted transaction
4724 4724
4725 4725 Recover from an interrupted commit or pull.
4726 4726
4727 4727 This command tries to fix the repository status after an
4728 4728 interrupted operation. It should only be necessary when Mercurial
4729 4729 suggests it.
4730 4730
4731 4731 Returns 0 if successful, 1 if nothing to recover or verify fails.
4732 4732 """
4733 4733 if repo.recover():
4734 4734 return hg.verify(repo)
4735 4735 return 1
4736 4736
4737 4737 @command('^remove|rm',
4738 4738 [('A', 'after', None, _('record delete for missing files')),
4739 4739 ('f', 'force', None,
4740 4740 _('remove (and delete) file even if added or modified')),
4741 4741 ] + walkopts,
4742 4742 _('[OPTION]... FILE...'))
4743 4743 def remove(ui, repo, *pats, **opts):
4744 4744 """remove the specified files on the next commit
4745 4745
4746 4746 Schedule the indicated files for removal from the current branch.
4747 4747
4748 4748 This command schedules the files to be removed at the next commit.
4749 4749 To undo a remove before that, see :hg:`revert`. To undo added
4750 4750 files, see :hg:`forget`.
4751 4751
4752 4752 .. container:: verbose
4753 4753
4754 4754 -A/--after can be used to remove only files that have already
4755 4755 been deleted, -f/--force can be used to force deletion, and -Af
4756 4756 can be used to remove files from the next revision without
4757 4757 deleting them from the working directory.
4758 4758
4759 4759 The following table details the behavior of remove for different
4760 4760 file states (columns) and option combinations (rows). The file
4761 4761 states are Added [A], Clean [C], Modified [M] and Missing [!]
4762 4762 (as reported by :hg:`status`). The actions are Warn, Remove
4763 4763 (from branch) and Delete (from disk):
4764 4764
4765 4765 ======= == == == ==
4766 4766 A C M !
4767 4767 ======= == == == ==
4768 4768 none W RD W R
4769 4769 -f R RD RD R
4770 4770 -A W W W R
4771 4771 -Af R R R R
4772 4772 ======= == == == ==
4773 4773
4774 4774 Note that remove never deletes files in Added [A] state from the
4775 4775 working directory, not even if option --force is specified.
4776 4776
4777 4777 Returns 0 on success, 1 if any warnings encountered.
4778 4778 """
4779 4779
4780 4780 ret = 0
4781 4781 after, force = opts.get('after'), opts.get('force')
4782 4782 if not pats and not after:
4783 4783 raise util.Abort(_('no files specified'))
4784 4784
4785 4785 m = scmutil.match(repo[None], pats, opts)
4786 4786 s = repo.status(match=m, clean=True)
4787 4787 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
4788 4788
4789 4789 # warn about failure to delete explicit files/dirs
4790 4790 wctx = repo[None]
4791 4791 for f in m.files():
4792 4792 if f in repo.dirstate or f in wctx.dirs():
4793 4793 continue
4794 4794 if os.path.exists(m.rel(f)):
4795 4795 if os.path.isdir(m.rel(f)):
4796 4796 ui.warn(_('not removing %s: no tracked files\n') % m.rel(f))
4797 4797 else:
4798 4798 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
4799 4799 # missing files will generate a warning elsewhere
4800 4800 ret = 1
4801 4801
4802 4802 if force:
4803 4803 list = modified + deleted + clean + added
4804 4804 elif after:
4805 4805 list = deleted
4806 4806 for f in modified + added + clean:
4807 4807 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
4808 4808 ret = 1
4809 4809 else:
4810 4810 list = deleted + clean
4811 4811 for f in modified:
4812 4812 ui.warn(_('not removing %s: file is modified (use -f'
4813 4813 ' to force removal)\n') % m.rel(f))
4814 4814 ret = 1
4815 4815 for f in added:
4816 4816 ui.warn(_('not removing %s: file has been marked for add'
4817 4817 ' (use forget to undo)\n') % m.rel(f))
4818 4818 ret = 1
4819 4819
4820 4820 for f in sorted(list):
4821 4821 if ui.verbose or not m.exact(f):
4822 4822 ui.status(_('removing %s\n') % m.rel(f))
4823 4823
4824 4824 wlock = repo.wlock()
4825 4825 try:
4826 4826 if not after:
4827 4827 for f in list:
4828 4828 if f in added:
4829 4829 continue # we never unlink added files on remove
4830 4830 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
4831 4831 repo[None].forget(list)
4832 4832 finally:
4833 4833 wlock.release()
4834 4834
4835 4835 return ret
4836 4836
4837 4837 @command('rename|move|mv',
4838 4838 [('A', 'after', None, _('record a rename that has already occurred')),
4839 4839 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4840 4840 ] + walkopts + dryrunopts,
4841 4841 _('[OPTION]... SOURCE... DEST'))
4842 4842 def rename(ui, repo, *pats, **opts):
4843 4843 """rename files; equivalent of copy + remove
4844 4844
4845 4845 Mark dest as copies of sources; mark sources for deletion. If dest
4846 4846 is a directory, copies are put in that directory. If dest is a
4847 4847 file, there can only be one source.
4848 4848
4849 4849 By default, this command copies the contents of files as they
4850 4850 exist in the working directory. If invoked with -A/--after, the
4851 4851 operation is recorded, but no copying is performed.
4852 4852
4853 4853 This command takes effect at the next commit. To undo a rename
4854 4854 before that, see :hg:`revert`.
4855 4855
4856 4856 Returns 0 on success, 1 if errors are encountered.
4857 4857 """
4858 4858 wlock = repo.wlock(False)
4859 4859 try:
4860 4860 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4861 4861 finally:
4862 4862 wlock.release()
4863 4863
4864 4864 @command('resolve',
4865 4865 [('a', 'all', None, _('select all unresolved files')),
4866 4866 ('l', 'list', None, _('list state of files needing merge')),
4867 4867 ('m', 'mark', None, _('mark files as resolved')),
4868 4868 ('u', 'unmark', None, _('mark files as unresolved')),
4869 4869 ('n', 'no-status', None, _('hide status prefix'))]
4870 4870 + mergetoolopts + walkopts,
4871 4871 _('[OPTION]... [FILE]...'))
4872 4872 def resolve(ui, repo, *pats, **opts):
4873 4873 """redo merges or set/view the merge status of files
4874 4874
4875 4875 Merges with unresolved conflicts are often the result of
4876 4876 non-interactive merging using the ``internal:merge`` configuration
4877 4877 setting, or a command-line merge tool like ``diff3``. The resolve
4878 4878 command is used to manage the files involved in a merge, after
4879 4879 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4880 4880 working directory must have two parents). See :hg:`help
4881 4881 merge-tools` for information on configuring merge tools.
4882 4882
4883 4883 The resolve command can be used in the following ways:
4884 4884
4885 4885 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4886 4886 files, discarding any previous merge attempts. Re-merging is not
4887 4887 performed for files already marked as resolved. Use ``--all/-a``
4888 4888 to select all unresolved files. ``--tool`` can be used to specify
4889 4889 the merge tool used for the given files. It overrides the HGMERGE
4890 4890 environment variable and your configuration files. Previous file
4891 4891 contents are saved with a ``.orig`` suffix.
4892 4892
4893 4893 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4894 4894 (e.g. after having manually fixed-up the files). The default is
4895 4895 to mark all unresolved files.
4896 4896
4897 4897 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4898 4898 default is to mark all resolved files.
4899 4899
4900 4900 - :hg:`resolve -l`: list files which had or still have conflicts.
4901 4901 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4902 4902
4903 4903 Note that Mercurial will not let you commit files with unresolved
4904 4904 merge conflicts. You must use :hg:`resolve -m ...` before you can
4905 4905 commit after a conflicting merge.
4906 4906
4907 4907 Returns 0 on success, 1 if any files fail a resolve attempt.
4908 4908 """
4909 4909
4910 4910 all, mark, unmark, show, nostatus = \
4911 4911 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
4912 4912
4913 4913 if (show and (mark or unmark)) or (mark and unmark):
4914 4914 raise util.Abort(_("too many options specified"))
4915 4915 if pats and all:
4916 4916 raise util.Abort(_("can't specify --all and patterns"))
4917 4917 if not (all or pats or show or mark or unmark):
4918 4918 raise util.Abort(_('no files or directories specified; '
4919 4919 'use --all to remerge all files'))
4920 4920
4921 4921 ms = mergemod.mergestate(repo)
4922 4922 m = scmutil.match(repo[None], pats, opts)
4923 4923 ret = 0
4924 4924
4925 4925 for f in ms:
4926 4926 if m(f):
4927 4927 if show:
4928 4928 if nostatus:
4929 4929 ui.write("%s\n" % f)
4930 4930 else:
4931 4931 ui.write("%s %s\n" % (ms[f].upper(), f),
4932 4932 label='resolve.' +
4933 4933 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
4934 4934 elif mark:
4935 4935 ms.mark(f, "r")
4936 4936 elif unmark:
4937 4937 ms.mark(f, "u")
4938 4938 else:
4939 4939 wctx = repo[None]
4940 4940 mctx = wctx.parents()[-1]
4941 4941
4942 4942 # backup pre-resolve (merge uses .orig for its own purposes)
4943 4943 a = repo.wjoin(f)
4944 4944 util.copyfile(a, a + ".resolve")
4945 4945
4946 4946 try:
4947 4947 # resolve file
4948 4948 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4949 4949 if ms.resolve(f, wctx, mctx):
4950 4950 ret = 1
4951 4951 finally:
4952 4952 ui.setconfig('ui', 'forcemerge', '')
4953 4953 ms.commit()
4954 4954
4955 4955 # replace filemerge's .orig file with our resolve file
4956 4956 util.rename(a + ".resolve", a + ".orig")
4957 4957
4958 4958 ms.commit()
4959 4959 return ret
4960 4960
4961 4961 @command('revert',
4962 4962 [('a', 'all', None, _('revert all changes when no arguments given')),
4963 4963 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4964 4964 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4965 4965 ('C', 'no-backup', None, _('do not save backup copies of files')),
4966 4966 ] + walkopts + dryrunopts,
4967 4967 _('[OPTION]... [-r REV] [NAME]...'))
4968 4968 def revert(ui, repo, *pats, **opts):
4969 4969 """restore files to their checkout state
4970 4970
4971 4971 .. note::
4972 4972 To check out earlier revisions, you should use :hg:`update REV`.
4973 4973 To cancel an uncommitted merge (and lose your changes),
4974 4974 use :hg:`update --clean .`.
4975 4975
4976 4976 With no revision specified, revert the specified files or directories
4977 4977 to the contents they had in the parent of the working directory.
4978 4978 This restores the contents of files to an unmodified
4979 4979 state and unschedules adds, removes, copies, and renames. If the
4980 4980 working directory has two parents, you must explicitly specify a
4981 4981 revision.
4982 4982
4983 4983 Using the -r/--rev or -d/--date options, revert the given files or
4984 4984 directories to their states as of a specific revision. Because
4985 4985 revert does not change the working directory parents, this will
4986 4986 cause these files to appear modified. This can be helpful to "back
4987 4987 out" some or all of an earlier change. See :hg:`backout` for a
4988 4988 related method.
4989 4989
4990 4990 Modified files are saved with a .orig suffix before reverting.
4991 4991 To disable these backups, use --no-backup.
4992 4992
4993 4993 See :hg:`help dates` for a list of formats valid for -d/--date.
4994 4994
4995 4995 Returns 0 on success.
4996 4996 """
4997 4997
4998 4998 if opts.get("date"):
4999 4999 if opts.get("rev"):
5000 5000 raise util.Abort(_("you can't specify a revision and a date"))
5001 5001 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5002 5002
5003 5003 parent, p2 = repo.dirstate.parents()
5004 5004 if not opts.get('rev') and p2 != nullid:
5005 5005 # revert after merge is a trap for new users (issue2915)
5006 5006 raise util.Abort(_('uncommitted merge with no revision specified'),
5007 5007 hint=_('use "hg update" or see "hg help revert"'))
5008 5008
5009 5009 ctx = scmutil.revsingle(repo, opts.get('rev'))
5010 5010
5011 5011 if not pats and not opts.get('all'):
5012 5012 msg = _("no files or directories specified")
5013 5013 if p2 != nullid:
5014 5014 hint = _("uncommitted merge, use --all to discard all changes,"
5015 5015 " or 'hg update -C .' to abort the merge")
5016 5016 raise util.Abort(msg, hint=hint)
5017 5017 dirty = util.any(repo.status())
5018 5018 node = ctx.node()
5019 5019 if node != parent:
5020 5020 if dirty:
5021 5021 hint = _("uncommitted changes, use --all to discard all"
5022 5022 " changes, or 'hg update %s' to update") % ctx.rev()
5023 5023 else:
5024 5024 hint = _("use --all to revert all files,"
5025 5025 " or 'hg update %s' to update") % ctx.rev()
5026 5026 elif dirty:
5027 5027 hint = _("uncommitted changes, use --all to discard all changes")
5028 5028 else:
5029 5029 hint = _("use --all to revert all files")
5030 5030 raise util.Abort(msg, hint=hint)
5031 5031
5032 5032 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
5033 5033
5034 5034 @command('rollback', dryrunopts +
5035 5035 [('f', 'force', False, _('ignore safety measures'))])
5036 5036 def rollback(ui, repo, **opts):
5037 5037 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5038 5038
5039 5039 Please use :hg:`commit --amend` instead of rollback to correct
5040 5040 mistakes in the last commit.
5041 5041
5042 5042 This command should be used with care. There is only one level of
5043 5043 rollback, and there is no way to undo a rollback. It will also
5044 5044 restore the dirstate at the time of the last transaction, losing
5045 5045 any dirstate changes since that time. This command does not alter
5046 5046 the working directory.
5047 5047
5048 5048 Transactions are used to encapsulate the effects of all commands
5049 5049 that create new changesets or propagate existing changesets into a
5050 5050 repository.
5051 5051
5052 5052 .. container:: verbose
5053 5053
5054 5054 For example, the following commands are transactional, and their
5055 5055 effects can be rolled back:
5056 5056
5057 5057 - commit
5058 5058 - import
5059 5059 - pull
5060 5060 - push (with this repository as the destination)
5061 5061 - unbundle
5062 5062
5063 5063 To avoid permanent data loss, rollback will refuse to rollback a
5064 5064 commit transaction if it isn't checked out. Use --force to
5065 5065 override this protection.
5066 5066
5067 5067 This command is not intended for use on public repositories. Once
5068 5068 changes are visible for pull by other users, rolling a transaction
5069 5069 back locally is ineffective (someone else may already have pulled
5070 5070 the changes). Furthermore, a race is possible with readers of the
5071 5071 repository; for example an in-progress pull from the repository
5072 5072 may fail if a rollback is performed.
5073 5073
5074 5074 Returns 0 on success, 1 if no rollback data is available.
5075 5075 """
5076 5076 return repo.rollback(dryrun=opts.get('dry_run'),
5077 5077 force=opts.get('force'))
5078 5078
5079 5079 @command('root', [])
5080 5080 def root(ui, repo):
5081 5081 """print the root (top) of the current working directory
5082 5082
5083 5083 Print the root directory of the current repository.
5084 5084
5085 5085 Returns 0 on success.
5086 5086 """
5087 5087 ui.write(repo.root + "\n")
5088 5088
5089 5089 @command('^serve',
5090 5090 [('A', 'accesslog', '', _('name of access log file to write to'),
5091 5091 _('FILE')),
5092 5092 ('d', 'daemon', None, _('run server in background')),
5093 5093 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
5094 5094 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5095 5095 # use string type, then we can check if something was passed
5096 5096 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5097 5097 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5098 5098 _('ADDR')),
5099 5099 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5100 5100 _('PREFIX')),
5101 5101 ('n', 'name', '',
5102 5102 _('name to show in web pages (default: working directory)'), _('NAME')),
5103 5103 ('', 'web-conf', '',
5104 5104 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
5105 5105 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5106 5106 _('FILE')),
5107 5107 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5108 5108 ('', 'stdio', None, _('for remote clients')),
5109 5109 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
5110 5110 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5111 5111 ('', 'style', '', _('template style to use'), _('STYLE')),
5112 5112 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5113 5113 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
5114 5114 _('[OPTION]...'))
5115 5115 def serve(ui, repo, **opts):
5116 5116 """start stand-alone webserver
5117 5117
5118 5118 Start a local HTTP repository browser and pull server. You can use
5119 5119 this for ad-hoc sharing and browsing of repositories. It is
5120 5120 recommended to use a real web server to serve a repository for
5121 5121 longer periods of time.
5122 5122
5123 5123 Please note that the server does not implement access control.
5124 5124 This means that, by default, anybody can read from the server and
5125 5125 nobody can write to it by default. Set the ``web.allow_push``
5126 5126 option to ``*`` to allow everybody to push to the server. You
5127 5127 should use a real web server if you need to authenticate users.
5128 5128
5129 5129 By default, the server logs accesses to stdout and errors to
5130 5130 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5131 5131 files.
5132 5132
5133 5133 To have the server choose a free port number to listen on, specify
5134 5134 a port number of 0; in this case, the server will print the port
5135 5135 number it uses.
5136 5136
5137 5137 Returns 0 on success.
5138 5138 """
5139 5139
5140 5140 if opts["stdio"] and opts["cmdserver"]:
5141 5141 raise util.Abort(_("cannot use --stdio with --cmdserver"))
5142 5142
5143 5143 def checkrepo():
5144 5144 if repo is None:
5145 5145 raise error.RepoError(_("there is no Mercurial repository here"
5146 5146 " (.hg not found)"))
5147 5147
5148 5148 if opts["stdio"]:
5149 5149 checkrepo()
5150 5150 s = sshserver.sshserver(ui, repo)
5151 5151 s.serve_forever()
5152 5152
5153 5153 if opts["cmdserver"]:
5154 5154 checkrepo()
5155 5155 s = commandserver.server(ui, repo, opts["cmdserver"])
5156 5156 return s.serve()
5157 5157
5158 5158 # this way we can check if something was given in the command-line
5159 5159 if opts.get('port'):
5160 5160 opts['port'] = util.getport(opts.get('port'))
5161 5161
5162 5162 baseui = repo and repo.baseui or ui
5163 5163 optlist = ("name templates style address port prefix ipv6"
5164 5164 " accesslog errorlog certificate encoding")
5165 5165 for o in optlist.split():
5166 5166 val = opts.get(o, '')
5167 5167 if val in (None, ''): # should check against default options instead
5168 5168 continue
5169 5169 baseui.setconfig("web", o, val)
5170 5170 if repo and repo.ui != baseui:
5171 5171 repo.ui.setconfig("web", o, val)
5172 5172
5173 5173 o = opts.get('web_conf') or opts.get('webdir_conf')
5174 5174 if not o:
5175 5175 if not repo:
5176 5176 raise error.RepoError(_("there is no Mercurial repository"
5177 5177 " here (.hg not found)"))
5178 5178 o = repo
5179 5179
5180 5180 app = hgweb.hgweb(o, baseui=baseui)
5181 5181
5182 5182 class service(object):
5183 5183 def init(self):
5184 5184 util.setsignalhandler()
5185 5185 self.httpd = hgweb.server.create_server(ui, app)
5186 5186
5187 5187 if opts['port'] and not ui.verbose:
5188 5188 return
5189 5189
5190 5190 if self.httpd.prefix:
5191 5191 prefix = self.httpd.prefix.strip('/') + '/'
5192 5192 else:
5193 5193 prefix = ''
5194 5194
5195 5195 port = ':%d' % self.httpd.port
5196 5196 if port == ':80':
5197 5197 port = ''
5198 5198
5199 5199 bindaddr = self.httpd.addr
5200 5200 if bindaddr == '0.0.0.0':
5201 5201 bindaddr = '*'
5202 5202 elif ':' in bindaddr: # IPv6
5203 5203 bindaddr = '[%s]' % bindaddr
5204 5204
5205 5205 fqaddr = self.httpd.fqaddr
5206 5206 if ':' in fqaddr:
5207 5207 fqaddr = '[%s]' % fqaddr
5208 5208 if opts['port']:
5209 5209 write = ui.status
5210 5210 else:
5211 5211 write = ui.write
5212 5212 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5213 5213 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5214 5214
5215 5215 def run(self):
5216 5216 self.httpd.serve_forever()
5217 5217
5218 5218 service = service()
5219 5219
5220 5220 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5221 5221
5222 5222 @command('showconfig|debugconfig',
5223 5223 [('u', 'untrusted', None, _('show untrusted configuration options'))],
5224 5224 _('[-u] [NAME]...'))
5225 5225 def showconfig(ui, repo, *values, **opts):
5226 5226 """show combined config settings from all hgrc files
5227 5227
5228 5228 With no arguments, print names and values of all config items.
5229 5229
5230 5230 With one argument of the form section.name, print just the value
5231 5231 of that config item.
5232 5232
5233 5233 With multiple arguments, print names and values of all config
5234 5234 items with matching section names.
5235 5235
5236 5236 With --debug, the source (filename and line number) is printed
5237 5237 for each config item.
5238 5238
5239 5239 Returns 0 on success.
5240 5240 """
5241 5241
5242 5242 for f in scmutil.rcpath():
5243 5243 ui.debug('read config from: %s\n' % f)
5244 5244 untrusted = bool(opts.get('untrusted'))
5245 5245 if values:
5246 5246 sections = [v for v in values if '.' not in v]
5247 5247 items = [v for v in values if '.' in v]
5248 5248 if len(items) > 1 or items and sections:
5249 5249 raise util.Abort(_('only one config item permitted'))
5250 5250 for section, name, value in ui.walkconfig(untrusted=untrusted):
5251 5251 value = str(value).replace('\n', '\\n')
5252 5252 sectname = section + '.' + name
5253 5253 if values:
5254 5254 for v in values:
5255 5255 if v == section:
5256 5256 ui.debug('%s: ' %
5257 5257 ui.configsource(section, name, untrusted))
5258 5258 ui.write('%s=%s\n' % (sectname, value))
5259 5259 elif v == sectname:
5260 5260 ui.debug('%s: ' %
5261 5261 ui.configsource(section, name, untrusted))
5262 5262 ui.write(value, '\n')
5263 5263 else:
5264 5264 ui.debug('%s: ' %
5265 5265 ui.configsource(section, name, untrusted))
5266 5266 ui.write('%s=%s\n' % (sectname, value))
5267 5267
5268 5268 @command('^status|st',
5269 5269 [('A', 'all', None, _('show status of all files')),
5270 5270 ('m', 'modified', None, _('show only modified files')),
5271 5271 ('a', 'added', None, _('show only added files')),
5272 5272 ('r', 'removed', None, _('show only removed files')),
5273 5273 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5274 5274 ('c', 'clean', None, _('show only files without changes')),
5275 5275 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5276 5276 ('i', 'ignored', None, _('show only ignored files')),
5277 5277 ('n', 'no-status', None, _('hide status prefix')),
5278 5278 ('C', 'copies', None, _('show source of copied files')),
5279 5279 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5280 5280 ('', 'rev', [], _('show difference from revision'), _('REV')),
5281 5281 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5282 5282 ] + walkopts + subrepoopts,
5283 5283 _('[OPTION]... [FILE]...'))
5284 5284 def status(ui, repo, *pats, **opts):
5285 5285 """show changed files in the working directory
5286 5286
5287 5287 Show status of files in the repository. If names are given, only
5288 5288 files that match are shown. Files that are clean or ignored or
5289 5289 the source of a copy/move operation, are not listed unless
5290 5290 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5291 5291 Unless options described with "show only ..." are given, the
5292 5292 options -mardu are used.
5293 5293
5294 5294 Option -q/--quiet hides untracked (unknown and ignored) files
5295 5295 unless explicitly requested with -u/--unknown or -i/--ignored.
5296 5296
5297 5297 .. note::
5298 5298 status may appear to disagree with diff if permissions have
5299 5299 changed or a merge has occurred. The standard diff format does
5300 5300 not report permission changes and diff only reports changes
5301 5301 relative to one merge parent.
5302 5302
5303 5303 If one revision is given, it is used as the base revision.
5304 5304 If two revisions are given, the differences between them are
5305 5305 shown. The --change option can also be used as a shortcut to list
5306 5306 the changed files of a revision from its first parent.
5307 5307
5308 5308 The codes used to show the status of files are::
5309 5309
5310 5310 M = modified
5311 5311 A = added
5312 5312 R = removed
5313 5313 C = clean
5314 5314 ! = missing (deleted by non-hg command, but still tracked)
5315 5315 ? = not tracked
5316 5316 I = ignored
5317 5317 = origin of the previous file listed as A (added)
5318 5318
5319 5319 .. container:: verbose
5320 5320
5321 5321 Examples:
5322 5322
5323 5323 - show changes in the working directory relative to a
5324 5324 changeset::
5325 5325
5326 5326 hg status --rev 9353
5327 5327
5328 5328 - show all changes including copies in an existing changeset::
5329 5329
5330 5330 hg status --copies --change 9353
5331 5331
5332 5332 - get a NUL separated list of added files, suitable for xargs::
5333 5333
5334 5334 hg status -an0
5335 5335
5336 5336 Returns 0 on success.
5337 5337 """
5338 5338
5339 5339 revs = opts.get('rev')
5340 5340 change = opts.get('change')
5341 5341
5342 5342 if revs and change:
5343 5343 msg = _('cannot specify --rev and --change at the same time')
5344 5344 raise util.Abort(msg)
5345 5345 elif change:
5346 5346 node2 = scmutil.revsingle(repo, change, None).node()
5347 5347 node1 = repo[node2].p1().node()
5348 5348 else:
5349 5349 node1, node2 = scmutil.revpair(repo, revs)
5350 5350
5351 5351 cwd = (pats and repo.getcwd()) or ''
5352 5352 end = opts.get('print0') and '\0' or '\n'
5353 5353 copy = {}
5354 5354 states = 'modified added removed deleted unknown ignored clean'.split()
5355 5355 show = [k for k in states if opts.get(k)]
5356 5356 if opts.get('all'):
5357 5357 show += ui.quiet and (states[:4] + ['clean']) or states
5358 5358 if not show:
5359 5359 show = ui.quiet and states[:4] or states[:5]
5360 5360
5361 5361 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5362 5362 'ignored' in show, 'clean' in show, 'unknown' in show,
5363 5363 opts.get('subrepos'))
5364 5364 changestates = zip(states, 'MAR!?IC', stat)
5365 5365
5366 5366 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5367 5367 copy = copies.pathcopies(repo[node1], repo[node2])
5368 5368
5369 5369 fm = ui.formatter('status', opts)
5370 5370 fmt = '%s' + end
5371 5371 showchar = not opts.get('no_status')
5372 5372
5373 5373 for state, char, files in changestates:
5374 5374 if state in show:
5375 5375 label = 'status.' + state
5376 5376 for f in files:
5377 5377 fm.startitem()
5378 5378 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5379 5379 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
5380 5380 if f in copy:
5381 5381 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5382 5382 label='status.copied')
5383 5383 fm.end()
5384 5384
5385 5385 @command('^summary|sum',
5386 5386 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5387 5387 def summary(ui, repo, **opts):
5388 5388 """summarize working directory state
5389 5389
5390 5390 This generates a brief summary of the working directory state,
5391 5391 including parents, branch, commit status, and available updates.
5392 5392
5393 5393 With the --remote option, this will check the default paths for
5394 5394 incoming and outgoing changes. This can be time-consuming.
5395 5395
5396 5396 Returns 0 on success.
5397 5397 """
5398 5398
5399 5399 ctx = repo[None]
5400 5400 parents = ctx.parents()
5401 5401 pnode = parents[0].node()
5402 5402 marks = []
5403 5403
5404 5404 for p in parents:
5405 5405 # label with log.changeset (instead of log.parent) since this
5406 5406 # shows a working directory parent *changeset*:
5407 5407 # i18n: column positioning for "hg summary"
5408 5408 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5409 5409 label='log.changeset changeset.%s' % p.phasestr())
5410 5410 ui.write(' '.join(p.tags()), label='log.tag')
5411 5411 if p.bookmarks():
5412 5412 marks.extend(p.bookmarks())
5413 5413 if p.rev() == -1:
5414 5414 if not len(repo):
5415 5415 ui.write(_(' (empty repository)'))
5416 5416 else:
5417 5417 ui.write(_(' (no revision checked out)'))
5418 5418 ui.write('\n')
5419 5419 if p.description():
5420 5420 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5421 5421 label='log.summary')
5422 5422
5423 5423 branch = ctx.branch()
5424 5424 bheads = repo.branchheads(branch)
5425 5425 # i18n: column positioning for "hg summary"
5426 5426 m = _('branch: %s\n') % branch
5427 5427 if branch != 'default':
5428 5428 ui.write(m, label='log.branch')
5429 5429 else:
5430 5430 ui.status(m, label='log.branch')
5431 5431
5432 5432 if marks:
5433 5433 current = repo._bookmarkcurrent
5434 5434 # i18n: column positioning for "hg summary"
5435 5435 ui.write(_('bookmarks:'), label='log.bookmark')
5436 5436 if current is not None:
5437 5437 if current in marks:
5438 5438 ui.write(' *' + current, label='bookmarks.current')
5439 5439 marks.remove(current)
5440 5440 else:
5441 5441 ui.write(' [%s]' % current, label='bookmarks.current')
5442 5442 for m in marks:
5443 5443 ui.write(' ' + m, label='log.bookmark')
5444 5444 ui.write('\n', label='log.bookmark')
5445 5445
5446 5446 st = list(repo.status(unknown=True))[:6]
5447 5447
5448 5448 c = repo.dirstate.copies()
5449 5449 copied, renamed = [], []
5450 5450 for d, s in c.iteritems():
5451 5451 if s in st[2]:
5452 5452 st[2].remove(s)
5453 5453 renamed.append(d)
5454 5454 else:
5455 5455 copied.append(d)
5456 5456 if d in st[1]:
5457 5457 st[1].remove(d)
5458 5458 st.insert(3, renamed)
5459 5459 st.insert(4, copied)
5460 5460
5461 5461 ms = mergemod.mergestate(repo)
5462 5462 st.append([f for f in ms if ms[f] == 'u'])
5463 5463
5464 5464 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5465 5465 st.append(subs)
5466 5466
5467 5467 labels = [ui.label(_('%d modified'), 'status.modified'),
5468 5468 ui.label(_('%d added'), 'status.added'),
5469 5469 ui.label(_('%d removed'), 'status.removed'),
5470 5470 ui.label(_('%d renamed'), 'status.copied'),
5471 5471 ui.label(_('%d copied'), 'status.copied'),
5472 5472 ui.label(_('%d deleted'), 'status.deleted'),
5473 5473 ui.label(_('%d unknown'), 'status.unknown'),
5474 5474 ui.label(_('%d ignored'), 'status.ignored'),
5475 5475 ui.label(_('%d unresolved'), 'resolve.unresolved'),
5476 5476 ui.label(_('%d subrepos'), 'status.modified')]
5477 5477 t = []
5478 5478 for s, l in zip(st, labels):
5479 5479 if s:
5480 5480 t.append(l % len(s))
5481 5481
5482 5482 t = ', '.join(t)
5483 5483 cleanworkdir = False
5484 5484
5485 5485 if repo.vfs.exists('updatestate'):
5486 5486 t += _(' (interrupted update)')
5487 5487 elif len(parents) > 1:
5488 5488 t += _(' (merge)')
5489 5489 elif branch != parents[0].branch():
5490 5490 t += _(' (new branch)')
5491 5491 elif (parents[0].closesbranch() and
5492 5492 pnode in repo.branchheads(branch, closed=True)):
5493 5493 t += _(' (head closed)')
5494 5494 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
5495 5495 t += _(' (clean)')
5496 5496 cleanworkdir = True
5497 5497 elif pnode not in bheads:
5498 5498 t += _(' (new branch head)')
5499 5499
5500 5500 if cleanworkdir:
5501 5501 # i18n: column positioning for "hg summary"
5502 5502 ui.status(_('commit: %s\n') % t.strip())
5503 5503 else:
5504 5504 # i18n: column positioning for "hg summary"
5505 5505 ui.write(_('commit: %s\n') % t.strip())
5506 5506
5507 5507 # all ancestors of branch heads - all ancestors of parent = new csets
5508 5508 new = len(repo.changelog.findmissing([ctx.node() for ctx in parents],
5509 5509 bheads))
5510 5510
5511 5511 if new == 0:
5512 5512 # i18n: column positioning for "hg summary"
5513 5513 ui.status(_('update: (current)\n'))
5514 5514 elif pnode not in bheads:
5515 5515 # i18n: column positioning for "hg summary"
5516 5516 ui.write(_('update: %d new changesets (update)\n') % new)
5517 5517 else:
5518 5518 # i18n: column positioning for "hg summary"
5519 5519 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5520 5520 (new, len(bheads)))
5521 5521
5522 5522 cmdutil.summaryhooks(ui, repo)
5523 5523
5524 5524 if opts.get('remote'):
5525 5525 t = []
5526 5526 source, branches = hg.parseurl(ui.expandpath('default'))
5527 5527 sbranch = branches[0]
5528 5528 other = hg.peer(repo, {}, source)
5529 5529 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5530 5530 if revs:
5531 5531 revs = [other.lookup(rev) for rev in revs]
5532 5532 ui.debug('comparing with %s\n' % util.hidepassword(source))
5533 5533 repo.ui.pushbuffer()
5534 5534 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5535 5535 _common, incoming, _rheads = commoninc
5536 5536 repo.ui.popbuffer()
5537 5537 if incoming:
5538 5538 t.append(_('1 or more incoming'))
5539 5539
5540 5540 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5541 5541 dbranch = branches[0]
5542 5542 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5543 5543 if source != dest:
5544 5544 other = hg.peer(repo, {}, dest)
5545 5545 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5546 5546 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5547 5547 commoninc = None
5548 5548 if revs:
5549 5549 revs = [repo.lookup(rev) for rev in revs]
5550 5550 repo.ui.pushbuffer()
5551 5551 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs,
5552 5552 commoninc=commoninc)
5553 5553 repo.ui.popbuffer()
5554 5554 o = outgoing.missing
5555 5555 if o:
5556 5556 t.append(_('%d outgoing') % len(o))
5557 5557 if 'bookmarks' in other.listkeys('namespaces'):
5558 5558 lmarks = repo.listkeys('bookmarks')
5559 5559 rmarks = other.listkeys('bookmarks')
5560 5560 diff = set(rmarks) - set(lmarks)
5561 5561 if len(diff) > 0:
5562 5562 t.append(_('%d incoming bookmarks') % len(diff))
5563 5563 diff = set(lmarks) - set(rmarks)
5564 5564 if len(diff) > 0:
5565 5565 t.append(_('%d outgoing bookmarks') % len(diff))
5566 5566
5567 5567 if t:
5568 5568 # i18n: column positioning for "hg summary"
5569 5569 ui.write(_('remote: %s\n') % (', '.join(t)))
5570 5570 else:
5571 5571 # i18n: column positioning for "hg summary"
5572 5572 ui.status(_('remote: (synced)\n'))
5573 5573
5574 5574 @command('tag',
5575 5575 [('f', 'force', None, _('force tag')),
5576 5576 ('l', 'local', None, _('make the tag local')),
5577 5577 ('r', 'rev', '', _('revision to tag'), _('REV')),
5578 5578 ('', 'remove', None, _('remove a tag')),
5579 5579 # -l/--local is already there, commitopts cannot be used
5580 5580 ('e', 'edit', None, _('edit commit message')),
5581 5581 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
5582 5582 ] + commitopts2,
5583 5583 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5584 5584 def tag(ui, repo, name1, *names, **opts):
5585 5585 """add one or more tags for the current or given revision
5586 5586
5587 5587 Name a particular revision using <name>.
5588 5588
5589 5589 Tags are used to name particular revisions of the repository and are
5590 5590 very useful to compare different revisions, to go back to significant
5591 5591 earlier versions or to mark branch points as releases, etc. Changing
5592 5592 an existing tag is normally disallowed; use -f/--force to override.
5593 5593
5594 5594 If no revision is given, the parent of the working directory is
5595 5595 used.
5596 5596
5597 5597 To facilitate version control, distribution, and merging of tags,
5598 5598 they are stored as a file named ".hgtags" which is managed similarly
5599 5599 to other project files and can be hand-edited if necessary. This
5600 5600 also means that tagging creates a new commit. The file
5601 5601 ".hg/localtags" is used for local tags (not shared among
5602 5602 repositories).
5603 5603
5604 5604 Tag commits are usually made at the head of a branch. If the parent
5605 5605 of the working directory is not a branch head, :hg:`tag` aborts; use
5606 5606 -f/--force to force the tag commit to be based on a non-head
5607 5607 changeset.
5608 5608
5609 5609 See :hg:`help dates` for a list of formats valid for -d/--date.
5610 5610
5611 5611 Since tag names have priority over branch names during revision
5612 5612 lookup, using an existing branch name as a tag name is discouraged.
5613 5613
5614 5614 Returns 0 on success.
5615 5615 """
5616 5616 wlock = lock = None
5617 5617 try:
5618 5618 wlock = repo.wlock()
5619 5619 lock = repo.lock()
5620 5620 rev_ = "."
5621 5621 names = [t.strip() for t in (name1,) + names]
5622 5622 if len(names) != len(set(names)):
5623 5623 raise util.Abort(_('tag names must be unique'))
5624 5624 for n in names:
5625 5625 scmutil.checknewlabel(repo, n, 'tag')
5626 5626 if not n:
5627 5627 raise util.Abort(_('tag names cannot consist entirely of '
5628 5628 'whitespace'))
5629 5629 if opts.get('rev') and opts.get('remove'):
5630 5630 raise util.Abort(_("--rev and --remove are incompatible"))
5631 5631 if opts.get('rev'):
5632 5632 rev_ = opts['rev']
5633 5633 message = opts.get('message')
5634 5634 if opts.get('remove'):
5635 5635 expectedtype = opts.get('local') and 'local' or 'global'
5636 5636 for n in names:
5637 5637 if not repo.tagtype(n):
5638 5638 raise util.Abort(_("tag '%s' does not exist") % n)
5639 5639 if repo.tagtype(n) != expectedtype:
5640 5640 if expectedtype == 'global':
5641 5641 raise util.Abort(_("tag '%s' is not a global tag") % n)
5642 5642 else:
5643 5643 raise util.Abort(_("tag '%s' is not a local tag") % n)
5644 5644 rev_ = nullid
5645 5645 if not message:
5646 5646 # we don't translate commit messages
5647 5647 message = 'Removed tag %s' % ', '.join(names)
5648 5648 elif not opts.get('force'):
5649 5649 for n in names:
5650 5650 if n in repo.tags():
5651 5651 raise util.Abort(_("tag '%s' already exists "
5652 5652 "(use -f to force)") % n)
5653 5653 if not opts.get('local'):
5654 5654 p1, p2 = repo.dirstate.parents()
5655 5655 if p2 != nullid:
5656 5656 raise util.Abort(_('uncommitted merge'))
5657 5657 bheads = repo.branchheads()
5658 5658 if not opts.get('force') and bheads and p1 not in bheads:
5659 5659 raise util.Abort(_('not at a branch head (use -f to force)'))
5660 5660 r = scmutil.revsingle(repo, rev_).node()
5661 5661
5662 5662 if not message:
5663 5663 # we don't translate commit messages
5664 5664 message = ('Added tag %s for changeset %s' %
5665 5665 (', '.join(names), short(r)))
5666 5666
5667 5667 date = opts.get('date')
5668 5668 if date:
5669 5669 date = util.parsedate(date)
5670 5670
5671 5671 if opts.get('edit'):
5672 5672 message = ui.edit(message, ui.username())
5673 5673
5674 5674 # don't allow tagging the null rev
5675 5675 if (not opts.get('remove') and
5676 5676 scmutil.revsingle(repo, rev_).rev() == nullrev):
5677 5677 raise util.Abort(_("cannot tag null revision"))
5678 5678
5679 5679 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
5680 5680 finally:
5681 5681 release(lock, wlock)
5682 5682
5683 5683 @command('tags', [], '')
5684 5684 def tags(ui, repo, **opts):
5685 5685 """list repository tags
5686 5686
5687 5687 This lists both regular and local tags. When the -v/--verbose
5688 5688 switch is used, a third column "local" is printed for local tags.
5689 5689
5690 5690 Returns 0 on success.
5691 5691 """
5692 5692
5693 5693 fm = ui.formatter('tags', opts)
5694 5694 hexfunc = ui.debugflag and hex or short
5695 5695 tagtype = ""
5696 5696
5697 5697 for t, n in reversed(repo.tagslist()):
5698 5698 hn = hexfunc(n)
5699 5699 label = 'tags.normal'
5700 5700 tagtype = ''
5701 5701 if repo.tagtype(t) == 'local':
5702 5702 label = 'tags.local'
5703 5703 tagtype = 'local'
5704 5704
5705 5705 fm.startitem()
5706 5706 fm.write('tag', '%s', t, label=label)
5707 5707 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5708 5708 fm.condwrite(not ui.quiet, 'rev id', fmt,
5709 5709 repo.changelog.rev(n), hn, label=label)
5710 5710 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5711 5711 tagtype, label=label)
5712 5712 fm.plain('\n')
5713 5713 fm.end()
5714 5714
5715 5715 @command('tip',
5716 5716 [('p', 'patch', None, _('show patch')),
5717 5717 ('g', 'git', None, _('use git extended diff format')),
5718 5718 ] + templateopts,
5719 5719 _('[-p] [-g]'))
5720 5720 def tip(ui, repo, **opts):
5721 5721 """show the tip revision (DEPRECATED)
5722 5722
5723 5723 The tip revision (usually just called the tip) is the changeset
5724 5724 most recently added to the repository (and therefore the most
5725 5725 recently changed head).
5726 5726
5727 5727 If you have just made a commit, that commit will be the tip. If
5728 5728 you have just pulled changes from another repository, the tip of
5729 5729 that repository becomes the current tip. The "tip" tag is special
5730 5730 and cannot be renamed or assigned to a different changeset.
5731 5731
5732 5732 This command is deprecated, please use :hg:`heads` instead.
5733 5733
5734 5734 Returns 0 on success.
5735 5735 """
5736 5736 displayer = cmdutil.show_changeset(ui, repo, opts)
5737 5737 displayer.show(repo['tip'])
5738 5738 displayer.close()
5739 5739
5740 5740 @command('unbundle',
5741 5741 [('u', 'update', None,
5742 5742 _('update to new branch head if changesets were unbundled'))],
5743 5743 _('[-u] FILE...'))
5744 5744 def unbundle(ui, repo, fname1, *fnames, **opts):
5745 5745 """apply one or more changegroup files
5746 5746
5747 5747 Apply one or more compressed changegroup files generated by the
5748 5748 bundle command.
5749 5749
5750 5750 Returns 0 on success, 1 if an update has unresolved files.
5751 5751 """
5752 5752 fnames = (fname1,) + fnames
5753 5753
5754 5754 lock = repo.lock()
5755 5755 wc = repo['.']
5756 5756 try:
5757 5757 for fname in fnames:
5758 5758 f = hg.openpath(ui, fname)
5759 5759 gen = changegroup.readbundle(f, fname)
5760 5760 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
5761 5761 finally:
5762 5762 lock.release()
5763 5763 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
5764 5764 return postincoming(ui, repo, modheads, opts.get('update'), None)
5765 5765
5766 5766 @command('^update|up|checkout|co',
5767 5767 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5768 5768 ('c', 'check', None,
5769 5769 _('update across branches if no uncommitted changes')),
5770 5770 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5771 5771 ('r', 'rev', '', _('revision'), _('REV'))],
5772 5772 _('[-c] [-C] [-d DATE] [[-r] REV]'))
5773 5773 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
5774 5774 """update working directory (or switch revisions)
5775 5775
5776 5776 Update the repository's working directory to the specified
5777 5777 changeset. If no changeset is specified, update to the tip of the
5778 5778 current named branch and move the current bookmark (see :hg:`help
5779 5779 bookmarks`).
5780 5780
5781 5781 Update sets the working directory's parent revision to the specified
5782 5782 changeset (see :hg:`help parents`).
5783 5783
5784 5784 If the changeset is not a descendant or ancestor of the working
5785 5785 directory's parent, the update is aborted. With the -c/--check
5786 5786 option, the working directory is checked for uncommitted changes; if
5787 5787 none are found, the working directory is updated to the specified
5788 5788 changeset.
5789 5789
5790 5790 .. container:: verbose
5791 5791
5792 5792 The following rules apply when the working directory contains
5793 5793 uncommitted changes:
5794 5794
5795 5795 1. If neither -c/--check nor -C/--clean is specified, and if
5796 5796 the requested changeset is an ancestor or descendant of
5797 5797 the working directory's parent, the uncommitted changes
5798 5798 are merged into the requested changeset and the merged
5799 5799 result is left uncommitted. If the requested changeset is
5800 5800 not an ancestor or descendant (that is, it is on another
5801 5801 branch), the update is aborted and the uncommitted changes
5802 5802 are preserved.
5803 5803
5804 5804 2. With the -c/--check option, the update is aborted and the
5805 5805 uncommitted changes are preserved.
5806 5806
5807 5807 3. With the -C/--clean option, uncommitted changes are discarded and
5808 5808 the working directory is updated to the requested changeset.
5809 5809
5810 5810 To cancel an uncommitted merge (and lose your changes), use
5811 5811 :hg:`update --clean .`.
5812 5812
5813 5813 Use null as the changeset to remove the working directory (like
5814 5814 :hg:`clone -U`).
5815 5815
5816 5816 If you want to revert just one file to an older revision, use
5817 5817 :hg:`revert [-r REV] NAME`.
5818 5818
5819 5819 See :hg:`help dates` for a list of formats valid for -d/--date.
5820 5820
5821 5821 Returns 0 on success, 1 if there are unresolved files.
5822 5822 """
5823 5823 if rev and node:
5824 5824 raise util.Abort(_("please specify just one revision"))
5825 5825
5826 5826 if rev is None or rev == '':
5827 5827 rev = node
5828 5828
5829 5829 cmdutil.clearunfinished(repo)
5830 5830
5831 5831 # with no argument, we also move the current bookmark, if any
5832 5832 movemarkfrom = None
5833 5833 if rev is None:
5834 5834 curmark = repo._bookmarkcurrent
5835 5835 if bookmarks.iscurrent(repo):
5836 5836 movemarkfrom = repo['.'].node()
5837 5837 elif curmark:
5838 5838 ui.status(_("updating to active bookmark %s\n") % curmark)
5839 5839 rev = curmark
5840 5840
5841 5841 # if we defined a bookmark, we have to remember the original bookmark name
5842 5842 brev = rev
5843 5843 rev = scmutil.revsingle(repo, rev, rev).rev()
5844 5844
5845 5845 if check and clean:
5846 5846 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
5847 5847
5848 5848 if date:
5849 5849 if rev is not None:
5850 5850 raise util.Abort(_("you can't specify a revision and a date"))
5851 5851 rev = cmdutil.finddate(ui, repo, date)
5852 5852
5853 5853 if check:
5854 5854 c = repo[None]
5855 5855 if c.dirty(merge=False, branch=False, missing=True):
5856 5856 raise util.Abort(_("uncommitted local changes"))
5857 5857 if rev is None:
5858 5858 rev = repo[repo[None].branch()].rev()
5859 5859 mergemod._checkunknown(repo, repo[None], repo[rev])
5860 5860
5861 5861 if clean:
5862 5862 ret = hg.clean(repo, rev)
5863 5863 else:
5864 5864 ret = hg.update(repo, rev)
5865 5865
5866 5866 if not ret and movemarkfrom:
5867 5867 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
5868 5868 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
5869 5869 elif brev in repo._bookmarks:
5870 5870 bookmarks.setcurrent(repo, brev)
5871 5871 elif brev:
5872 5872 bookmarks.unsetcurrent(repo)
5873 5873
5874 5874 return ret
5875 5875
5876 5876 @command('verify', [])
5877 5877 def verify(ui, repo):
5878 5878 """verify the integrity of the repository
5879 5879
5880 5880 Verify the integrity of the current repository.
5881 5881
5882 5882 This will perform an extensive check of the repository's
5883 5883 integrity, validating the hashes and checksums of each entry in
5884 5884 the changelog, manifest, and tracked files, as well as the
5885 5885 integrity of their crosslinks and indices.
5886 5886
5887 5887 Please see http://mercurial.selenic.com/wiki/RepositoryCorruption
5888 5888 for more information about recovery from corruption of the
5889 5889 repository.
5890 5890
5891 5891 Returns 0 on success, 1 if errors are encountered.
5892 5892 """
5893 5893 return hg.verify(repo)
5894 5894
5895 5895 @command('version', [])
5896 5896 def version_(ui):
5897 5897 """output version and copyright information"""
5898 5898 ui.write(_("Mercurial Distributed SCM (version %s)\n")
5899 5899 % util.version())
5900 5900 ui.status(_(
5901 5901 "(see http://mercurial.selenic.com for more information)\n"
5902 5902 "\nCopyright (C) 2005-2013 Matt Mackall and others\n"
5903 5903 "This is free software; see the source for copying conditions. "
5904 5904 "There is NO\nwarranty; "
5905 5905 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5906 5906 ))
5907 5907
5908 5908 norepo = ("clone init version help debugcommands debugcomplete"
5909 5909 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
5910 5910 " debugknown debuggetbundle debugbundle")
5911 5911 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
5912 5912 " debugdata debugindex debugindexdot debugrevlog")
5913 5913 inferrepo = ("add addremove annotate cat commit diff grep forget log parents"
5914 5914 " remove resolve status debugwalk")
@@ -1,226 +1,226 b''
1 1 $ . "$TESTDIR/histedit-helpers.sh"
2 2
3 3 $ cat >> $HGRCPATH <<EOF
4 4 > [extensions]
5 5 > graphlog=
6 6 > histedit=
7 7 > EOF
8 8
9 9 $ initrepo ()
10 10 > {
11 11 > hg init r
12 12 > cd r
13 13 > for x in a b c d e f ; do
14 14 > echo $x > $x
15 15 > hg add $x
16 16 > hg ci -m $x
17 17 > done
18 18 > }
19 19
20 20 $ initrepo
21 21
22 22 log before edit
23 23 $ hg log --graph
24 24 @ changeset: 5:652413bf663e
25 25 | tag: tip
26 26 | user: test
27 27 | date: Thu Jan 01 00:00:00 1970 +0000
28 28 | summary: f
29 29 |
30 30 o changeset: 4:e860deea161a
31 31 | user: test
32 32 | date: Thu Jan 01 00:00:00 1970 +0000
33 33 | summary: e
34 34 |
35 35 o changeset: 3:055a42cdd887
36 36 | user: test
37 37 | date: Thu Jan 01 00:00:00 1970 +0000
38 38 | summary: d
39 39 |
40 40 o changeset: 2:177f92b77385
41 41 | user: test
42 42 | date: Thu Jan 01 00:00:00 1970 +0000
43 43 | summary: c
44 44 |
45 45 o changeset: 1:d2ae7f538514
46 46 | user: test
47 47 | date: Thu Jan 01 00:00:00 1970 +0000
48 48 | summary: b
49 49 |
50 50 o changeset: 0:cb9a9f314b8b
51 51 user: test
52 52 date: Thu Jan 01 00:00:00 1970 +0000
53 53 summary: a
54 54
55 55
56 56 edit the history
57 57 $ hg histedit 177f92b77385 --commands - 2>&1 << EOF| fixbundle
58 58 > pick 177f92b77385 c
59 59 > pick 055a42cdd887 d
60 60 > edit e860deea161a e
61 61 > pick 652413bf663e f
62 62 > EOF
63 63 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
64 64 Make changes as needed, you may commit or record as needed now.
65 65 When you are finished, run hg histedit --continue to resume.
66 66
67 67 Go at a random point and try to continue
68 68
69 69 $ hg id -n
70 70 3+
71 71 $ hg up 0
72 72 abort: histedit in progress
73 73 (use 'hg histedit --continue' or 'hg histedit --abort')
74 74 [255]
75 75
76 commit, then edit the revision (temporarily disable histedit to allow commit)
77 $ hg ci -m 'wat' --config 'extensions.histedit=!'
76 commit, then edit the revision
77 $ hg ci -m 'wat'
78 78 created new head
79 79 $ echo a > e
80 80 $ HGEDITOR='echo foobaz > ' hg histedit --continue 2>&1 | fixbundle
81 81 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
82 82 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
83 83
84 84 $ hg log --graph
85 85 @ changeset: 6:b5f70786f9b0
86 86 | tag: tip
87 87 | user: test
88 88 | date: Thu Jan 01 00:00:00 1970 +0000
89 89 | summary: f
90 90 |
91 91 o changeset: 5:a5e1ba2f7afb
92 92 | user: test
93 93 | date: Thu Jan 01 00:00:00 1970 +0000
94 94 | summary: foobaz
95 95 |
96 96 o changeset: 4:1a60820cd1f6
97 97 | user: test
98 98 | date: Thu Jan 01 00:00:00 1970 +0000
99 99 | summary: wat
100 100 |
101 101 o changeset: 3:055a42cdd887
102 102 | user: test
103 103 | date: Thu Jan 01 00:00:00 1970 +0000
104 104 | summary: d
105 105 |
106 106 o changeset: 2:177f92b77385
107 107 | user: test
108 108 | date: Thu Jan 01 00:00:00 1970 +0000
109 109 | summary: c
110 110 |
111 111 o changeset: 1:d2ae7f538514
112 112 | user: test
113 113 | date: Thu Jan 01 00:00:00 1970 +0000
114 114 | summary: b
115 115 |
116 116 o changeset: 0:cb9a9f314b8b
117 117 user: test
118 118 date: Thu Jan 01 00:00:00 1970 +0000
119 119 summary: a
120 120
121 121
122 122 $ hg cat e
123 123 a
124 124
125 125 check histedit_source
126 126
127 127 $ hg log --debug --rev 5
128 128 changeset: 5:a5e1ba2f7afb899ef1581cea528fd885d2fca70d
129 129 phase: draft
130 130 parent: 4:1a60820cd1f6004a362aa622ebc47d59bc48eb34
131 131 parent: -1:0000000000000000000000000000000000000000
132 132 manifest: 5:5ad3be8791f39117565557781f5464363b918a45
133 133 user: test
134 134 date: Thu Jan 01 00:00:00 1970 +0000
135 135 files: e
136 136 extra: branch=default
137 137 extra: histedit_source=e860deea161a2f77de56603b340ebbb4536308ae
138 138 description:
139 139 foobaz
140 140
141 141
142 142
143 143 $ hg histedit tip --commands - 2>&1 <<EOF| fixbundle
144 144 > edit b5f70786f9b0 f
145 145 > EOF
146 146 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
147 147 Make changes as needed, you may commit or record as needed now.
148 148 When you are finished, run hg histedit --continue to resume.
149 149 $ hg status
150 150 A f
151 151
152 152 $ hg summary
153 153 parent: 5:a5e1ba2f7afb
154 154 foobaz
155 155 branch: default
156 156 commit: 1 added (new branch head)
157 157 update: 1 new changesets (update)
158 158 hist: 1 remaining (histedit --continue)
159 159
160 160 $ HGEDITOR='true' hg histedit --continue
161 161 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
162 162 saved backup bundle to $TESTTMP/r/.hg/strip-backup/b5f70786f9b0-backup.hg (glob)
163 163
164 164 $ hg status
165 165
166 166 log after edit
167 167 $ hg log --limit 1
168 168 changeset: 6:a107ee126658
169 169 tag: tip
170 170 user: test
171 171 date: Thu Jan 01 00:00:00 1970 +0000
172 172 summary: f
173 173
174 174
175 175 say we'll change the message, but don't.
176 176 $ cat > ../edit.sh <<EOF
177 177 > cat "\$1" | sed s/pick/mess/ > tmp
178 178 > mv tmp "\$1"
179 179 > EOF
180 180 $ HGEDITOR="sh ../edit.sh" hg histedit tip 2>&1 | fixbundle
181 181 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
182 182 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
183 183 $ hg status
184 184 $ hg log --limit 1
185 185 changeset: 6:1fd3b2fe7754
186 186 tag: tip
187 187 user: test
188 188 date: Thu Jan 01 00:00:00 1970 +0000
189 189 summary: f
190 190
191 191
192 192 modify the message
193 193 $ hg histedit tip --commands - 2>&1 << EOF | fixbundle
194 194 > mess 1fd3b2fe7754 f
195 195 > EOF
196 196 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
197 197 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
198 198 $ hg status
199 199 $ hg log --limit 1
200 200 changeset: 6:62feedb1200e
201 201 tag: tip
202 202 user: test
203 203 date: Thu Jan 01 00:00:00 1970 +0000
204 204 summary: f
205 205
206 206
207 207 rollback should not work after a histedit
208 208 $ hg rollback
209 209 no rollback information available
210 210 [1]
211 211
212 212 $ cd ..
213 213 $ hg clone -qr0 r r0
214 214 $ cd r0
215 215 $ hg phase -fdr0
216 216 $ hg histedit --commands - 0 2>&1 << EOF
217 217 > edit cb9a9f314b8b a > $EDITED
218 218 > EOF
219 219 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
220 220 adding a
221 221 Make changes as needed, you may commit or record as needed now.
222 222 When you are finished, run hg histedit --continue to resume.
223 223 [1]
224 224 $ HGEDITOR=true hg histedit --continue
225 225 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
226 226 saved backup bundle to $TESTTMP/r0/.hg/strip-backup/cb9a9f314b8b-backup.hg (glob)
@@ -1,302 +1,302 b''
1 1 Test histedit extention: Fold commands
2 2 ======================================
3 3
4 4 This test file is dedicated to testing the fold command in non conflicting
5 5 case.
6 6
7 7 Initialization
8 8 ---------------
9 9
10 10
11 11 $ . "$TESTDIR/histedit-helpers.sh"
12 12
13 13 $ cat >> $HGRCPATH <<EOF
14 14 > [alias]
15 15 > logt = log --template '{rev}:{node|short} {desc|firstline}\n'
16 16 > [extensions]
17 17 > graphlog=
18 18 > histedit=
19 19 > EOF
20 20
21 21
22 22 Simple folding
23 23 --------------------
24 24 $ initrepo ()
25 25 > {
26 26 > hg init r
27 27 > cd r
28 28 > for x in a b c d e f ; do
29 29 > echo $x > $x
30 30 > hg add $x
31 31 > hg ci -m $x
32 32 > done
33 33 > }
34 34
35 35 $ initrepo
36 36
37 37 log before edit
38 38 $ hg logt --graph
39 39 @ 5:652413bf663e f
40 40 |
41 41 o 4:e860deea161a e
42 42 |
43 43 o 3:055a42cdd887 d
44 44 |
45 45 o 2:177f92b77385 c
46 46 |
47 47 o 1:d2ae7f538514 b
48 48 |
49 49 o 0:cb9a9f314b8b a
50 50
51 51
52 52 $ hg histedit 177f92b77385 --commands - 2>&1 <<EOF | fixbundle
53 53 > pick e860deea161a e
54 54 > pick 652413bf663e f
55 55 > fold 177f92b77385 c
56 56 > pick 055a42cdd887 d
57 57 > EOF
58 58 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
59 59 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
60 60 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
61 61 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
62 62 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
63 63 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
64 64 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
65 65
66 66 log after edit
67 67 $ hg logt --graph
68 68 @ 4:9c277da72c9b d
69 69 |
70 70 o 3:6de59d13424a f
71 71 |
72 72 o 2:ee283cb5f2d5 e
73 73 |
74 74 o 1:d2ae7f538514 b
75 75 |
76 76 o 0:cb9a9f314b8b a
77 77
78 78
79 79 post-fold manifest
80 80 $ hg manifest
81 81 a
82 82 b
83 83 c
84 84 d
85 85 e
86 86 f
87 87
88 88
89 89 check histedit_source
90 90
91 91 $ hg log --debug --rev 3
92 92 changeset: 3:6de59d13424a8a13acd3e975514aed29dd0d9b2d
93 93 phase: draft
94 94 parent: 2:ee283cb5f2d5955443f23a27b697a04339e9a39a
95 95 parent: -1:0000000000000000000000000000000000000000
96 96 manifest: 3:81eede616954057198ead0b2c73b41d1f392829a
97 97 user: test
98 98 date: Thu Jan 01 00:00:00 1970 +0000
99 99 files+: c f
100 100 extra: branch=default
101 101 extra: histedit_source=a4f7421b80f79fcc59fff01bcbf4a53d127dd6d3,177f92b773850b59254aa5e923436f921b55483b
102 102 description:
103 103 f
104 104 ***
105 105 c
106 106
107 107
108 108
109 109 $ cd ..
110 110
111 111 folding and creating no new change doesn't break:
112 112 -------------------------------------------------
113 113
114 114 folded content is dropped during a merge. The folded commit should properly disapear.
115 115
116 116 $ mkdir fold-to-empty-test
117 117 $ cd fold-to-empty-test
118 118 $ hg init
119 119 $ printf "1\n2\n3\n" > file
120 120 $ hg add file
121 121 $ hg commit -m '1+2+3'
122 122 $ echo 4 >> file
123 123 $ hg commit -m '+4'
124 124 $ echo 5 >> file
125 125 $ hg commit -m '+5'
126 126 $ echo 6 >> file
127 127 $ hg commit -m '+6'
128 128 $ hg logt --graph
129 129 @ 3:251d831eeec5 +6
130 130 |
131 131 o 2:888f9082bf99 +5
132 132 |
133 133 o 1:617f94f13c0f +4
134 134 |
135 135 o 0:0189ba417d34 1+2+3
136 136
137 137
138 138 $ hg histedit 1 --commands - << EOF
139 139 > pick 617f94f13c0f 1 +4
140 140 > drop 888f9082bf99 2 +5
141 141 > fold 251d831eeec5 3 +6
142 142 > EOF
143 143 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
144 144 merging file
145 145 warning: conflicts during merge.
146 146 merging file incomplete! (edit conflicts, then use 'hg resolve --mark')
147 147 Fix up the change and run hg histedit --continue
148 148 [1]
149 149 There were conflicts, we keep P1 content. This
150 150 should effectively drop the changes from +6.
151 151 $ hg status
152 152 M file
153 153 ? file.orig
154 154 $ hg resolve -l
155 155 U file
156 156 $ hg revert -r 'p1()' file
157 157 $ hg resolve --mark file
158 158 $ hg histedit --continue
159 159 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
160 160 saved backup bundle to $TESTTMP/*-backup.hg (glob)
161 161 $ hg logt --graph
162 162 @ 1:617f94f13c0f +4
163 163 |
164 164 o 0:0189ba417d34 1+2+3
165 165
166 166
167 167 $ cd ..
168 168
169 169
170 170 Test fold through dropped
171 171 -------------------------
172 172
173 173
174 174 Test corner case where folded revision is separated from its parent by a
175 175 dropped revision.
176 176
177 177
178 178 $ hg init fold-with-dropped
179 179 $ cd fold-with-dropped
180 180 $ printf "1\n2\n3\n" > file
181 181 $ hg commit -Am '1+2+3'
182 182 adding file
183 183 $ echo 4 >> file
184 184 $ hg commit -m '+4'
185 185 $ echo 5 >> file
186 186 $ hg commit -m '+5'
187 187 $ echo 6 >> file
188 188 $ hg commit -m '+6'
189 189 $ hg logt -G
190 190 @ 3:251d831eeec5 +6
191 191 |
192 192 o 2:888f9082bf99 +5
193 193 |
194 194 o 1:617f94f13c0f +4
195 195 |
196 196 o 0:0189ba417d34 1+2+3
197 197
198 198 $ hg histedit 1 --commands - << EOF
199 199 > pick 617f94f13c0f 1 +4
200 200 > drop 888f9082bf99 2 +5
201 201 > fold 251d831eeec5 3 +6
202 202 > EOF
203 203 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
204 204 merging file
205 205 warning: conflicts during merge.
206 206 merging file incomplete! (edit conflicts, then use 'hg resolve --mark')
207 207 Fix up the change and run hg histedit --continue
208 208 [1]
209 209 $ cat > file << EOF
210 210 > 1
211 211 > 2
212 212 > 3
213 213 > 4
214 214 > 5
215 215 > EOF
216 216 $ hg resolve --mark file
217 $ hg commit -m '+5.2' --config 'extensions.histedit=!'
217 $ hg commit -m '+5.2'
218 218 created new head
219 219 $ echo 6 >> file
220 220 $ HGEDITOR=cat hg histedit --continue
221 221 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
222 222 +4
223 223 ***
224 224 +5.2
225 225 ***
226 226 +6
227 227
228 228
229 229
230 230 HG: Enter commit message. Lines beginning with 'HG:' are removed.
231 231 HG: Leave message empty to abort commit.
232 232 HG: --
233 233 HG: user: test
234 234 HG: branch 'default'
235 235 HG: changed file
236 236 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
237 237 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
238 238 saved backup bundle to $TESTTMP/fold-with-dropped/.hg/strip-backup/617f94f13c0f-backup.hg (glob)
239 239 $ hg logt -G
240 240 @ 1:10c647b2cdd5 +4
241 241 |
242 242 o 0:0189ba417d34 1+2+3
243 243
244 244 $ hg export tip
245 245 # HG changeset patch
246 246 # User test
247 247 # Date 0 0
248 248 # Thu Jan 01 00:00:00 1970 +0000
249 249 # Node ID 10c647b2cdd54db0603ecb99b2ff5ce66d5a5323
250 250 # Parent 0189ba417d34df9dda55f88b637dcae9917b5964
251 251 +4
252 252 ***
253 253 +5.2
254 254 ***
255 255 +6
256 256
257 257 diff -r 0189ba417d34 -r 10c647b2cdd5 file
258 258 --- a/file Thu Jan 01 00:00:00 1970 +0000
259 259 +++ b/file Thu Jan 01 00:00:00 1970 +0000
260 260 @@ -1,3 +1,6 @@
261 261 1
262 262 2
263 263 3
264 264 +4
265 265 +5
266 266 +6
267 267 $ cd ..
268 268
269 269
270 270 Folding with initial rename (issue3729)
271 271 ---------------------------------------
272 272
273 273 $ hg init fold-rename
274 274 $ cd fold-rename
275 275 $ echo a > a.txt
276 276 $ hg add a.txt
277 277 $ hg commit -m a
278 278 $ hg rename a.txt b.txt
279 279 $ hg commit -m rename
280 280 $ echo b >> b.txt
281 281 $ hg commit -m b
282 282
283 283 $ hg logt --follow b.txt
284 284 2:e0371e0426bc b
285 285 1:1c4f440a8085 rename
286 286 0:6c795aa153cb a
287 287
288 288 $ hg histedit 1c4f440a8085 --commands - 2>&1 << EOF | fixbundle
289 289 > pick 1c4f440a8085 rename
290 290 > fold e0371e0426bc b
291 291 > EOF
292 292 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
293 293 reverting b.txt
294 294 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
295 295 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
296 296 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
297 297
298 298 $ hg logt --follow b.txt
299 299 1:cf858d235c76 rename
300 300 0:6c795aa153cb a
301 301
302 302 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now