##// END OF EJS Templates
merge with stable
Matt Mackall -
r17059:fba17a64 merge default
parent child Browse files
Show More
@@ -1,3574 +1,3578 b''
1 1 # mq.py - patch queues for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Chris Mason <mason@suse.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 '''manage a stack of patches
9 9
10 10 This extension lets you work with a stack of patches in a Mercurial
11 11 repository. It manages two stacks of patches - all known patches, and
12 12 applied patches (subset of known patches).
13 13
14 14 Known patches are represented as patch files in the .hg/patches
15 15 directory. Applied patches are both patch files and changesets.
16 16
17 17 Common tasks (use :hg:`help command` for more details)::
18 18
19 19 create new patch qnew
20 20 import existing patch qimport
21 21
22 22 print patch series qseries
23 23 print applied patches qapplied
24 24
25 25 add known patch to applied stack qpush
26 26 remove patch from applied stack qpop
27 27 refresh contents of top applied patch qrefresh
28 28
29 29 By default, mq will automatically use git patches when required to
30 30 avoid losing file mode changes, copy records, binary files or empty
31 31 files creations or deletions. This behaviour can be configured with::
32 32
33 33 [mq]
34 34 git = auto/keep/yes/no
35 35
36 36 If set to 'keep', mq will obey the [diff] section configuration while
37 37 preserving existing git patches upon qrefresh. If set to 'yes' or
38 38 'no', mq will override the [diff] section and always generate git or
39 39 regular patches, possibly losing data in the second case.
40 40
41 41 It may be desirable for mq changesets to be kept in the secret phase (see
42 42 :hg:`help phases`), which can be enabled with the following setting::
43 43
44 44 [mq]
45 45 secret = True
46 46
47 47 You will by default be managing a patch queue named "patches". You can
48 48 create other, independent patch queues with the :hg:`qqueue` command.
49 49
50 50 If the working directory contains uncommitted files, qpush, qpop and
51 51 qgoto abort immediately. If -f/--force is used, the changes are
52 52 discarded. Setting::
53 53
54 54 [mq]
55 55 keepchanges = True
56 56
57 57 make them behave as if --keep-changes were passed, and non-conflicting
58 58 local changes will be tolerated and preserved. If incompatible options
59 59 such as -f/--force or --exact are passed, this setting is ignored.
60 60 '''
61 61
62 62 from mercurial.i18n import _
63 63 from mercurial.node import bin, hex, short, nullid, nullrev
64 64 from mercurial.lock import release
65 65 from mercurial import commands, cmdutil, hg, scmutil, util, revset
66 66 from mercurial import repair, extensions, url, error, phases
67 67 from mercurial import patch as patchmod
68 68 import os, re, errno, shutil
69 69
70 70 commands.norepo += " qclone"
71 71
72 72 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
73 73
74 74 cmdtable = {}
75 75 command = cmdutil.command(cmdtable)
76 76 testedwith = 'internal'
77 77
78 78 # Patch names looks like unix-file names.
79 79 # They must be joinable with queue directory and result in the patch path.
80 80 normname = util.normpath
81 81
82 82 class statusentry(object):
83 83 def __init__(self, node, name):
84 84 self.node, self.name = node, name
85 85 def __repr__(self):
86 86 return hex(self.node) + ':' + self.name
87 87
88 88 class patchheader(object):
89 89 def __init__(self, pf, plainmode=False):
90 90 def eatdiff(lines):
91 91 while lines:
92 92 l = lines[-1]
93 93 if (l.startswith("diff -") or
94 94 l.startswith("Index:") or
95 95 l.startswith("===========")):
96 96 del lines[-1]
97 97 else:
98 98 break
99 99 def eatempty(lines):
100 100 while lines:
101 101 if not lines[-1].strip():
102 102 del lines[-1]
103 103 else:
104 104 break
105 105
106 106 message = []
107 107 comments = []
108 108 user = None
109 109 date = None
110 110 parent = None
111 111 format = None
112 112 subject = None
113 113 branch = None
114 114 nodeid = None
115 115 diffstart = 0
116 116
117 117 for line in file(pf):
118 118 line = line.rstrip()
119 119 if (line.startswith('diff --git')
120 120 or (diffstart and line.startswith('+++ '))):
121 121 diffstart = 2
122 122 break
123 123 diffstart = 0 # reset
124 124 if line.startswith("--- "):
125 125 diffstart = 1
126 126 continue
127 127 elif format == "hgpatch":
128 128 # parse values when importing the result of an hg export
129 129 if line.startswith("# User "):
130 130 user = line[7:]
131 131 elif line.startswith("# Date "):
132 132 date = line[7:]
133 133 elif line.startswith("# Parent "):
134 134 parent = line[9:].lstrip()
135 135 elif line.startswith("# Branch "):
136 136 branch = line[9:]
137 137 elif line.startswith("# Node ID "):
138 138 nodeid = line[10:]
139 139 elif not line.startswith("# ") and line:
140 140 message.append(line)
141 141 format = None
142 142 elif line == '# HG changeset patch':
143 143 message = []
144 144 format = "hgpatch"
145 145 elif (format != "tagdone" and (line.startswith("Subject: ") or
146 146 line.startswith("subject: "))):
147 147 subject = line[9:]
148 148 format = "tag"
149 149 elif (format != "tagdone" and (line.startswith("From: ") or
150 150 line.startswith("from: "))):
151 151 user = line[6:]
152 152 format = "tag"
153 153 elif (format != "tagdone" and (line.startswith("Date: ") or
154 154 line.startswith("date: "))):
155 155 date = line[6:]
156 156 format = "tag"
157 157 elif format == "tag" and line == "":
158 158 # when looking for tags (subject: from: etc) they
159 159 # end once you find a blank line in the source
160 160 format = "tagdone"
161 161 elif message or line:
162 162 message.append(line)
163 163 comments.append(line)
164 164
165 165 eatdiff(message)
166 166 eatdiff(comments)
167 167 # Remember the exact starting line of the patch diffs before consuming
168 168 # empty lines, for external use by TortoiseHg and others
169 169 self.diffstartline = len(comments)
170 170 eatempty(message)
171 171 eatempty(comments)
172 172
173 173 # make sure message isn't empty
174 174 if format and format.startswith("tag") and subject:
175 175 message.insert(0, "")
176 176 message.insert(0, subject)
177 177
178 178 self.message = message
179 179 self.comments = comments
180 180 self.user = user
181 181 self.date = date
182 182 self.parent = parent
183 183 # nodeid and branch are for external use by TortoiseHg and others
184 184 self.nodeid = nodeid
185 185 self.branch = branch
186 186 self.haspatch = diffstart > 1
187 187 self.plainmode = plainmode
188 188
189 189 def setuser(self, user):
190 190 if not self.updateheader(['From: ', '# User '], user):
191 191 try:
192 192 patchheaderat = self.comments.index('# HG changeset patch')
193 193 self.comments.insert(patchheaderat + 1, '# User ' + user)
194 194 except ValueError:
195 195 if self.plainmode or self._hasheader(['Date: ']):
196 196 self.comments = ['From: ' + user] + self.comments
197 197 else:
198 198 tmp = ['# HG changeset patch', '# User ' + user, '']
199 199 self.comments = tmp + self.comments
200 200 self.user = user
201 201
202 202 def setdate(self, date):
203 203 if not self.updateheader(['Date: ', '# Date '], date):
204 204 try:
205 205 patchheaderat = self.comments.index('# HG changeset patch')
206 206 self.comments.insert(patchheaderat + 1, '# Date ' + date)
207 207 except ValueError:
208 208 if self.plainmode or self._hasheader(['From: ']):
209 209 self.comments = ['Date: ' + date] + self.comments
210 210 else:
211 211 tmp = ['# HG changeset patch', '# Date ' + date, '']
212 212 self.comments = tmp + self.comments
213 213 self.date = date
214 214
215 215 def setparent(self, parent):
216 216 if not self.updateheader(['# Parent '], parent):
217 217 try:
218 218 patchheaderat = self.comments.index('# HG changeset patch')
219 219 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
220 220 except ValueError:
221 221 pass
222 222 self.parent = parent
223 223
224 224 def setmessage(self, message):
225 225 if self.comments:
226 226 self._delmsg()
227 227 self.message = [message]
228 228 self.comments += self.message
229 229
230 230 def updateheader(self, prefixes, new):
231 231 '''Update all references to a field in the patch header.
232 232 Return whether the field is present.'''
233 233 res = False
234 234 for prefix in prefixes:
235 235 for i in xrange(len(self.comments)):
236 236 if self.comments[i].startswith(prefix):
237 237 self.comments[i] = prefix + new
238 238 res = True
239 239 break
240 240 return res
241 241
242 242 def _hasheader(self, prefixes):
243 243 '''Check if a header starts with any of the given prefixes.'''
244 244 for prefix in prefixes:
245 245 for comment in self.comments:
246 246 if comment.startswith(prefix):
247 247 return True
248 248 return False
249 249
250 250 def __str__(self):
251 251 if not self.comments:
252 252 return ''
253 253 return '\n'.join(self.comments) + '\n\n'
254 254
255 255 def _delmsg(self):
256 256 '''Remove existing message, keeping the rest of the comments fields.
257 257 If comments contains 'subject: ', message will prepend
258 258 the field and a blank line.'''
259 259 if self.message:
260 260 subj = 'subject: ' + self.message[0].lower()
261 261 for i in xrange(len(self.comments)):
262 262 if subj == self.comments[i].lower():
263 263 del self.comments[i]
264 264 self.message = self.message[2:]
265 265 break
266 266 ci = 0
267 267 for mi in self.message:
268 268 while mi != self.comments[ci]:
269 269 ci += 1
270 270 del self.comments[ci]
271 271
272 272 def newcommit(repo, phase, *args, **kwargs):
273 273 """helper dedicated to ensure a commit respect mq.secret setting
274 274
275 275 It should be used instead of repo.commit inside the mq source for operation
276 276 creating new changeset.
277 277 """
278 278 if phase is None:
279 279 if repo.ui.configbool('mq', 'secret', False):
280 280 phase = phases.secret
281 281 if phase is not None:
282 282 backup = repo.ui.backupconfig('phases', 'new-commit')
283 283 # Marking the repository as committing an mq patch can be used
284 284 # to optimize operations like _branchtags().
285 285 repo._committingpatch = True
286 286 try:
287 287 if phase is not None:
288 288 repo.ui.setconfig('phases', 'new-commit', phase)
289 289 return repo.commit(*args, **kwargs)
290 290 finally:
291 291 repo._committingpatch = False
292 292 if phase is not None:
293 293 repo.ui.restoreconfig(backup)
294 294
295 295 class AbortNoCleanup(error.Abort):
296 296 pass
297 297
298 298 class queue(object):
299 299 def __init__(self, ui, path, patchdir=None):
300 300 self.basepath = path
301 301 try:
302 302 fh = open(os.path.join(path, 'patches.queue'))
303 303 cur = fh.read().rstrip()
304 304 fh.close()
305 305 if not cur:
306 306 curpath = os.path.join(path, 'patches')
307 307 else:
308 308 curpath = os.path.join(path, 'patches-' + cur)
309 309 except IOError:
310 310 curpath = os.path.join(path, 'patches')
311 311 self.path = patchdir or curpath
312 312 self.opener = scmutil.opener(self.path)
313 313 self.ui = ui
314 314 self.applieddirty = False
315 315 self.seriesdirty = False
316 316 self.added = []
317 317 self.seriespath = "series"
318 318 self.statuspath = "status"
319 319 self.guardspath = "guards"
320 320 self.activeguards = None
321 321 self.guardsdirty = False
322 322 # Handle mq.git as a bool with extended values
323 323 try:
324 324 gitmode = ui.configbool('mq', 'git', None)
325 325 if gitmode is None:
326 326 raise error.ConfigError
327 327 self.gitmode = gitmode and 'yes' or 'no'
328 328 except error.ConfigError:
329 329 self.gitmode = ui.config('mq', 'git', 'auto').lower()
330 330 self.plainmode = ui.configbool('mq', 'plain', False)
331 331
332 332 @util.propertycache
333 333 def applied(self):
334 334 def parselines(lines):
335 335 for l in lines:
336 336 entry = l.split(':', 1)
337 337 if len(entry) > 1:
338 338 n, name = entry
339 339 yield statusentry(bin(n), name)
340 340 elif l.strip():
341 341 self.ui.warn(_('malformated mq status line: %s\n') % entry)
342 342 # else we ignore empty lines
343 343 try:
344 344 lines = self.opener.read(self.statuspath).splitlines()
345 345 return list(parselines(lines))
346 346 except IOError, e:
347 347 if e.errno == errno.ENOENT:
348 348 return []
349 349 raise
350 350
351 351 @util.propertycache
352 352 def fullseries(self):
353 353 try:
354 354 return self.opener.read(self.seriespath).splitlines()
355 355 except IOError, e:
356 356 if e.errno == errno.ENOENT:
357 357 return []
358 358 raise
359 359
360 360 @util.propertycache
361 361 def series(self):
362 362 self.parseseries()
363 363 return self.series
364 364
365 365 @util.propertycache
366 366 def seriesguards(self):
367 367 self.parseseries()
368 368 return self.seriesguards
369 369
370 370 def invalidate(self):
371 371 for a in 'applied fullseries series seriesguards'.split():
372 372 if a in self.__dict__:
373 373 delattr(self, a)
374 374 self.applieddirty = False
375 375 self.seriesdirty = False
376 376 self.guardsdirty = False
377 377 self.activeguards = None
378 378
379 379 def diffopts(self, opts={}, patchfn=None):
380 380 diffopts = patchmod.diffopts(self.ui, opts)
381 381 if self.gitmode == 'auto':
382 382 diffopts.upgrade = True
383 383 elif self.gitmode == 'keep':
384 384 pass
385 385 elif self.gitmode in ('yes', 'no'):
386 386 diffopts.git = self.gitmode == 'yes'
387 387 else:
388 388 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
389 389 ' got %s') % self.gitmode)
390 390 if patchfn:
391 391 diffopts = self.patchopts(diffopts, patchfn)
392 392 return diffopts
393 393
394 394 def patchopts(self, diffopts, *patches):
395 395 """Return a copy of input diff options with git set to true if
396 396 referenced patch is a git patch and should be preserved as such.
397 397 """
398 398 diffopts = diffopts.copy()
399 399 if not diffopts.git and self.gitmode == 'keep':
400 400 for patchfn in patches:
401 401 patchf = self.opener(patchfn, 'r')
402 402 # if the patch was a git patch, refresh it as a git patch
403 403 for line in patchf:
404 404 if line.startswith('diff --git'):
405 405 diffopts.git = True
406 406 break
407 407 patchf.close()
408 408 return diffopts
409 409
410 410 def join(self, *p):
411 411 return os.path.join(self.path, *p)
412 412
413 413 def findseries(self, patch):
414 414 def matchpatch(l):
415 415 l = l.split('#', 1)[0]
416 416 return l.strip() == patch
417 417 for index, l in enumerate(self.fullseries):
418 418 if matchpatch(l):
419 419 return index
420 420 return None
421 421
422 422 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
423 423
424 424 def parseseries(self):
425 425 self.series = []
426 426 self.seriesguards = []
427 427 for l in self.fullseries:
428 428 h = l.find('#')
429 429 if h == -1:
430 430 patch = l
431 431 comment = ''
432 432 elif h == 0:
433 433 continue
434 434 else:
435 435 patch = l[:h]
436 436 comment = l[h:]
437 437 patch = patch.strip()
438 438 if patch:
439 439 if patch in self.series:
440 440 raise util.Abort(_('%s appears more than once in %s') %
441 441 (patch, self.join(self.seriespath)))
442 442 self.series.append(patch)
443 443 self.seriesguards.append(self.guard_re.findall(comment))
444 444
445 445 def checkguard(self, guard):
446 446 if not guard:
447 447 return _('guard cannot be an empty string')
448 448 bad_chars = '# \t\r\n\f'
449 449 first = guard[0]
450 450 if first in '-+':
451 451 return (_('guard %r starts with invalid character: %r') %
452 452 (guard, first))
453 453 for c in bad_chars:
454 454 if c in guard:
455 455 return _('invalid character in guard %r: %r') % (guard, c)
456 456
457 457 def setactive(self, guards):
458 458 for guard in guards:
459 459 bad = self.checkguard(guard)
460 460 if bad:
461 461 raise util.Abort(bad)
462 462 guards = sorted(set(guards))
463 463 self.ui.debug('active guards: %s\n' % ' '.join(guards))
464 464 self.activeguards = guards
465 465 self.guardsdirty = True
466 466
467 467 def active(self):
468 468 if self.activeguards is None:
469 469 self.activeguards = []
470 470 try:
471 471 guards = self.opener.read(self.guardspath).split()
472 472 except IOError, err:
473 473 if err.errno != errno.ENOENT:
474 474 raise
475 475 guards = []
476 476 for i, guard in enumerate(guards):
477 477 bad = self.checkguard(guard)
478 478 if bad:
479 479 self.ui.warn('%s:%d: %s\n' %
480 480 (self.join(self.guardspath), i + 1, bad))
481 481 else:
482 482 self.activeguards.append(guard)
483 483 return self.activeguards
484 484
485 485 def setguards(self, idx, guards):
486 486 for g in guards:
487 487 if len(g) < 2:
488 488 raise util.Abort(_('guard %r too short') % g)
489 489 if g[0] not in '-+':
490 490 raise util.Abort(_('guard %r starts with invalid char') % g)
491 491 bad = self.checkguard(g[1:])
492 492 if bad:
493 493 raise util.Abort(bad)
494 494 drop = self.guard_re.sub('', self.fullseries[idx])
495 495 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
496 496 self.parseseries()
497 497 self.seriesdirty = True
498 498
499 499 def pushable(self, idx):
500 500 if isinstance(idx, str):
501 501 idx = self.series.index(idx)
502 502 patchguards = self.seriesguards[idx]
503 503 if not patchguards:
504 504 return True, None
505 505 guards = self.active()
506 506 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
507 507 if exactneg:
508 508 return False, repr(exactneg[0])
509 509 pos = [g for g in patchguards if g[0] == '+']
510 510 exactpos = [g for g in pos if g[1:] in guards]
511 511 if pos:
512 512 if exactpos:
513 513 return True, repr(exactpos[0])
514 514 return False, ' '.join(map(repr, pos))
515 515 return True, ''
516 516
517 517 def explainpushable(self, idx, all_patches=False):
518 518 write = all_patches and self.ui.write or self.ui.warn
519 519 if all_patches or self.ui.verbose:
520 520 if isinstance(idx, str):
521 521 idx = self.series.index(idx)
522 522 pushable, why = self.pushable(idx)
523 523 if all_patches and pushable:
524 524 if why is None:
525 525 write(_('allowing %s - no guards in effect\n') %
526 526 self.series[idx])
527 527 else:
528 528 if not why:
529 529 write(_('allowing %s - no matching negative guards\n') %
530 530 self.series[idx])
531 531 else:
532 532 write(_('allowing %s - guarded by %s\n') %
533 533 (self.series[idx], why))
534 534 if not pushable:
535 535 if why:
536 536 write(_('skipping %s - guarded by %s\n') %
537 537 (self.series[idx], why))
538 538 else:
539 539 write(_('skipping %s - no matching guards\n') %
540 540 self.series[idx])
541 541
542 542 def savedirty(self):
543 543 def writelist(items, path):
544 544 fp = self.opener(path, 'w')
545 545 for i in items:
546 546 fp.write("%s\n" % i)
547 547 fp.close()
548 548 if self.applieddirty:
549 549 writelist(map(str, self.applied), self.statuspath)
550 550 self.applieddirty = False
551 551 if self.seriesdirty:
552 552 writelist(self.fullseries, self.seriespath)
553 553 self.seriesdirty = False
554 554 if self.guardsdirty:
555 555 writelist(self.activeguards, self.guardspath)
556 556 self.guardsdirty = False
557 557 if self.added:
558 558 qrepo = self.qrepo()
559 559 if qrepo:
560 560 qrepo[None].add(f for f in self.added if f not in qrepo[None])
561 561 self.added = []
562 562
563 563 def removeundo(self, repo):
564 564 undo = repo.sjoin('undo')
565 565 if not os.path.exists(undo):
566 566 return
567 567 try:
568 568 os.unlink(undo)
569 569 except OSError, inst:
570 570 self.ui.warn(_('error removing undo: %s\n') % str(inst))
571 571
572 572 def backup(self, repo, files, copy=False):
573 573 # backup local changes in --force case
574 574 for f in sorted(files):
575 575 absf = repo.wjoin(f)
576 576 if os.path.lexists(absf):
577 577 self.ui.note(_('saving current version of %s as %s\n') %
578 578 (f, f + '.orig'))
579 579 if copy:
580 580 util.copyfile(absf, absf + '.orig')
581 581 else:
582 582 util.rename(absf, absf + '.orig')
583 583
584 584 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
585 585 fp=None, changes=None, opts={}):
586 586 stat = opts.get('stat')
587 587 m = scmutil.match(repo[node1], files, opts)
588 588 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
589 589 changes, stat, fp)
590 590
591 591 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
592 592 # first try just applying the patch
593 593 (err, n) = self.apply(repo, [patch], update_status=False,
594 594 strict=True, merge=rev)
595 595
596 596 if err == 0:
597 597 return (err, n)
598 598
599 599 if n is None:
600 600 raise util.Abort(_("apply failed for patch %s") % patch)
601 601
602 602 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
603 603
604 604 # apply failed, strip away that rev and merge.
605 605 hg.clean(repo, head)
606 606 self.strip(repo, [n], update=False, backup='strip')
607 607
608 608 ctx = repo[rev]
609 609 ret = hg.merge(repo, rev)
610 610 if ret:
611 611 raise util.Abort(_("update returned %d") % ret)
612 612 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
613 613 if n is None:
614 614 raise util.Abort(_("repo commit failed"))
615 615 try:
616 616 ph = patchheader(mergeq.join(patch), self.plainmode)
617 617 except Exception:
618 618 raise util.Abort(_("unable to read %s") % patch)
619 619
620 620 diffopts = self.patchopts(diffopts, patch)
621 621 patchf = self.opener(patch, "w")
622 622 comments = str(ph)
623 623 if comments:
624 624 patchf.write(comments)
625 625 self.printdiff(repo, diffopts, head, n, fp=patchf)
626 626 patchf.close()
627 627 self.removeundo(repo)
628 628 return (0, n)
629 629
630 630 def qparents(self, repo, rev=None):
631 631 if rev is None:
632 632 (p1, p2) = repo.dirstate.parents()
633 633 if p2 == nullid:
634 634 return p1
635 635 if not self.applied:
636 636 return None
637 637 return self.applied[-1].node
638 638 p1, p2 = repo.changelog.parents(rev)
639 639 if p2 != nullid and p2 in [x.node for x in self.applied]:
640 640 return p2
641 641 return p1
642 642
643 643 def mergepatch(self, repo, mergeq, series, diffopts):
644 644 if not self.applied:
645 645 # each of the patches merged in will have two parents. This
646 646 # can confuse the qrefresh, qdiff, and strip code because it
647 647 # needs to know which parent is actually in the patch queue.
648 648 # so, we insert a merge marker with only one parent. This way
649 649 # the first patch in the queue is never a merge patch
650 650 #
651 651 pname = ".hg.patches.merge.marker"
652 652 n = newcommit(repo, None, '[mq]: merge marker', force=True)
653 653 self.removeundo(repo)
654 654 self.applied.append(statusentry(n, pname))
655 655 self.applieddirty = True
656 656
657 657 head = self.qparents(repo)
658 658
659 659 for patch in series:
660 660 patch = mergeq.lookup(patch, strict=True)
661 661 if not patch:
662 662 self.ui.warn(_("patch %s does not exist\n") % patch)
663 663 return (1, None)
664 664 pushable, reason = self.pushable(patch)
665 665 if not pushable:
666 666 self.explainpushable(patch, all_patches=True)
667 667 continue
668 668 info = mergeq.isapplied(patch)
669 669 if not info:
670 670 self.ui.warn(_("patch %s is not applied\n") % patch)
671 671 return (1, None)
672 672 rev = info[1]
673 673 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
674 674 if head:
675 675 self.applied.append(statusentry(head, patch))
676 676 self.applieddirty = True
677 677 if err:
678 678 return (err, head)
679 679 self.savedirty()
680 680 return (0, head)
681 681
682 682 def patch(self, repo, patchfile):
683 683 '''Apply patchfile to the working directory.
684 684 patchfile: name of patch file'''
685 685 files = set()
686 686 try:
687 687 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
688 688 files=files, eolmode=None)
689 689 return (True, list(files), fuzz)
690 690 except Exception, inst:
691 691 self.ui.note(str(inst) + '\n')
692 692 if not self.ui.verbose:
693 693 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
694 694 self.ui.traceback()
695 695 return (False, list(files), False)
696 696
697 697 def apply(self, repo, series, list=False, update_status=True,
698 698 strict=False, patchdir=None, merge=None, all_files=None,
699 699 tobackup=None, keepchanges=False):
700 700 wlock = lock = tr = None
701 701 try:
702 702 wlock = repo.wlock()
703 703 lock = repo.lock()
704 704 tr = repo.transaction("qpush")
705 705 try:
706 706 ret = self._apply(repo, series, list, update_status,
707 707 strict, patchdir, merge, all_files=all_files,
708 708 tobackup=tobackup, keepchanges=keepchanges)
709 709 tr.close()
710 710 self.savedirty()
711 711 return ret
712 712 except AbortNoCleanup:
713 713 tr.close()
714 714 self.savedirty()
715 715 return 2, repo.dirstate.p1()
716 716 except: # re-raises
717 717 try:
718 718 tr.abort()
719 719 finally:
720 720 repo.invalidate()
721 721 repo.dirstate.invalidate()
722 722 self.invalidate()
723 723 raise
724 724 finally:
725 725 release(tr, lock, wlock)
726 726 self.removeundo(repo)
727 727
728 728 def _apply(self, repo, series, list=False, update_status=True,
729 729 strict=False, patchdir=None, merge=None, all_files=None,
730 730 tobackup=None, keepchanges=False):
731 731 """returns (error, hash)
732 732
733 733 error = 1 for unable to read, 2 for patch failed, 3 for patch
734 734 fuzz. tobackup is None or a set of files to backup before they
735 735 are modified by a patch.
736 736 """
737 737 # TODO unify with commands.py
738 738 if not patchdir:
739 739 patchdir = self.path
740 740 err = 0
741 741 n = None
742 742 for patchname in series:
743 743 pushable, reason = self.pushable(patchname)
744 744 if not pushable:
745 745 self.explainpushable(patchname, all_patches=True)
746 746 continue
747 747 self.ui.status(_("applying %s\n") % patchname)
748 748 pf = os.path.join(patchdir, patchname)
749 749
750 750 try:
751 751 ph = patchheader(self.join(patchname), self.plainmode)
752 752 except IOError:
753 753 self.ui.warn(_("unable to read %s\n") % patchname)
754 754 err = 1
755 755 break
756 756
757 757 message = ph.message
758 758 if not message:
759 759 # The commit message should not be translated
760 760 message = "imported patch %s\n" % patchname
761 761 else:
762 762 if list:
763 763 # The commit message should not be translated
764 764 message.append("\nimported patch %s" % patchname)
765 765 message = '\n'.join(message)
766 766
767 767 if ph.haspatch:
768 768 if tobackup:
769 769 touched = patchmod.changedfiles(self.ui, repo, pf)
770 770 touched = set(touched) & tobackup
771 771 if touched and keepchanges:
772 772 raise AbortNoCleanup(
773 773 _("local changes found, refresh first"))
774 774 self.backup(repo, touched, copy=True)
775 775 tobackup = tobackup - touched
776 776 (patcherr, files, fuzz) = self.patch(repo, pf)
777 777 if all_files is not None:
778 778 all_files.update(files)
779 779 patcherr = not patcherr
780 780 else:
781 781 self.ui.warn(_("patch %s is empty\n") % patchname)
782 782 patcherr, files, fuzz = 0, [], 0
783 783
784 784 if merge and files:
785 785 # Mark as removed/merged and update dirstate parent info
786 786 removed = []
787 787 merged = []
788 788 for f in files:
789 789 if os.path.lexists(repo.wjoin(f)):
790 790 merged.append(f)
791 791 else:
792 792 removed.append(f)
793 793 for f in removed:
794 794 repo.dirstate.remove(f)
795 795 for f in merged:
796 796 repo.dirstate.merge(f)
797 797 p1, p2 = repo.dirstate.parents()
798 798 repo.setparents(p1, merge)
799 799
800 800 match = scmutil.matchfiles(repo, files or [])
801 801 oldtip = repo['tip']
802 802 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
803 803 force=True)
804 804 if repo['tip'] == oldtip:
805 805 raise util.Abort(_("qpush exactly duplicates child changeset"))
806 806 if n is None:
807 807 raise util.Abort(_("repository commit failed"))
808 808
809 809 if update_status:
810 810 self.applied.append(statusentry(n, patchname))
811 811
812 812 if patcherr:
813 813 self.ui.warn(_("patch failed, rejects left in working dir\n"))
814 814 err = 2
815 815 break
816 816
817 817 if fuzz and strict:
818 818 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
819 819 err = 3
820 820 break
821 821 return (err, n)
822 822
823 823 def _cleanup(self, patches, numrevs, keep=False):
824 824 if not keep:
825 825 r = self.qrepo()
826 826 if r:
827 827 r[None].forget(patches)
828 828 for p in patches:
829 829 os.unlink(self.join(p))
830 830
831 831 qfinished = []
832 832 if numrevs:
833 833 qfinished = self.applied[:numrevs]
834 834 del self.applied[:numrevs]
835 835 self.applieddirty = True
836 836
837 837 unknown = []
838 838
839 839 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
840 840 reverse=True):
841 841 if i is not None:
842 842 del self.fullseries[i]
843 843 else:
844 844 unknown.append(p)
845 845
846 846 if unknown:
847 847 if numrevs:
848 848 rev = dict((entry.name, entry.node) for entry in qfinished)
849 849 for p in unknown:
850 850 msg = _('revision %s refers to unknown patches: %s\n')
851 851 self.ui.warn(msg % (short(rev[p]), p))
852 852 else:
853 853 msg = _('unknown patches: %s\n')
854 854 raise util.Abort(''.join(msg % p for p in unknown))
855 855
856 856 self.parseseries()
857 857 self.seriesdirty = True
858 858 return [entry.node for entry in qfinished]
859 859
860 860 def _revpatches(self, repo, revs):
861 861 firstrev = repo[self.applied[0].node].rev()
862 862 patches = []
863 863 for i, rev in enumerate(revs):
864 864
865 865 if rev < firstrev:
866 866 raise util.Abort(_('revision %d is not managed') % rev)
867 867
868 868 ctx = repo[rev]
869 869 base = self.applied[i].node
870 870 if ctx.node() != base:
871 871 msg = _('cannot delete revision %d above applied patches')
872 872 raise util.Abort(msg % rev)
873 873
874 874 patch = self.applied[i].name
875 875 for fmt in ('[mq]: %s', 'imported patch %s'):
876 876 if ctx.description() == fmt % patch:
877 877 msg = _('patch %s finalized without changeset message\n')
878 878 repo.ui.status(msg % patch)
879 879 break
880 880
881 881 patches.append(patch)
882 882 return patches
883 883
884 884 def finish(self, repo, revs):
885 885 # Manually trigger phase computation to ensure phasedefaults is
886 886 # executed before we remove the patches.
887 887 repo._phasecache
888 888 patches = self._revpatches(repo, sorted(revs))
889 889 qfinished = self._cleanup(patches, len(patches))
890 890 if qfinished and repo.ui.configbool('mq', 'secret', False):
891 891 # only use this logic when the secret option is added
892 892 oldqbase = repo[qfinished[0]]
893 893 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
894 894 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
895 895 phases.advanceboundary(repo, tphase, qfinished)
896 896
897 897 def delete(self, repo, patches, opts):
898 898 if not patches and not opts.get('rev'):
899 899 raise util.Abort(_('qdelete requires at least one revision or '
900 900 'patch name'))
901 901
902 902 realpatches = []
903 903 for patch in patches:
904 904 patch = self.lookup(patch, strict=True)
905 905 info = self.isapplied(patch)
906 906 if info:
907 907 raise util.Abort(_("cannot delete applied patch %s") % patch)
908 908 if patch not in self.series:
909 909 raise util.Abort(_("patch %s not in series file") % patch)
910 910 if patch not in realpatches:
911 911 realpatches.append(patch)
912 912
913 913 numrevs = 0
914 914 if opts.get('rev'):
915 915 if not self.applied:
916 916 raise util.Abort(_('no patches applied'))
917 917 revs = scmutil.revrange(repo, opts.get('rev'))
918 918 if len(revs) > 1 and revs[0] > revs[1]:
919 919 revs.reverse()
920 920 revpatches = self._revpatches(repo, revs)
921 921 realpatches += revpatches
922 922 numrevs = len(revpatches)
923 923
924 924 self._cleanup(realpatches, numrevs, opts.get('keep'))
925 925
926 926 def checktoppatch(self, repo):
927 927 if self.applied:
928 928 top = self.applied[-1].node
929 929 patch = self.applied[-1].name
930 930 pp = repo.dirstate.parents()
931 931 if top not in pp:
932 932 raise util.Abort(_("working directory revision is not qtip"))
933 933 return top, patch
934 934 return None, None
935 935
936 936 def checksubstate(self, repo):
937 937 '''return list of subrepos at a different revision than substate.
938 938 Abort if any subrepos have uncommitted changes.'''
939 939 inclsubs = []
940 940 wctx = repo[None]
941 941 for s in wctx.substate:
942 942 if wctx.sub(s).dirty(True):
943 943 raise util.Abort(
944 944 _("uncommitted changes in subrepository %s") % s)
945 945 elif wctx.sub(s).dirty():
946 946 inclsubs.append(s)
947 947 return inclsubs
948 948
949 949 def localchangesfound(self, refresh=True):
950 950 if refresh:
951 951 raise util.Abort(_("local changes found, refresh first"))
952 952 else:
953 953 raise util.Abort(_("local changes found"))
954 954
955 955 def checklocalchanges(self, repo, force=False, refresh=True):
956 956 m, a, r, d = repo.status()[:4]
957 957 if (m or a or r or d) and not force:
958 958 self.localchangesfound(refresh)
959 959 return m, a, r, d
960 960
961 961 _reserved = ('series', 'status', 'guards', '.', '..')
962 962 def checkreservedname(self, name):
963 963 if name in self._reserved:
964 964 raise util.Abort(_('"%s" cannot be used as the name of a patch')
965 965 % name)
966 966 for prefix in ('.hg', '.mq'):
967 967 if name.startswith(prefix):
968 968 raise util.Abort(_('patch name cannot begin with "%s"')
969 969 % prefix)
970 970 for c in ('#', ':'):
971 971 if c in name:
972 972 raise util.Abort(_('"%s" cannot be used in the name of a patch')
973 973 % c)
974 974
975 975 def checkpatchname(self, name, force=False):
976 976 self.checkreservedname(name)
977 977 if not force and os.path.exists(self.join(name)):
978 978 if os.path.isdir(self.join(name)):
979 979 raise util.Abort(_('"%s" already exists as a directory')
980 980 % name)
981 981 else:
982 982 raise util.Abort(_('patch "%s" already exists') % name)
983 983
984 984 def checkkeepchanges(self, keepchanges, force):
985 985 if force and keepchanges:
986 986 raise util.Abort(_('cannot use both --force and --keep-changes'))
987 987
988 988 def new(self, repo, patchfn, *pats, **opts):
989 989 """options:
990 990 msg: a string or a no-argument function returning a string
991 991 """
992 992 msg = opts.get('msg')
993 993 user = opts.get('user')
994 994 date = opts.get('date')
995 995 if date:
996 996 date = util.parsedate(date)
997 997 diffopts = self.diffopts({'git': opts.get('git')})
998 998 if opts.get('checkname', True):
999 999 self.checkpatchname(patchfn)
1000 1000 inclsubs = self.checksubstate(repo)
1001 1001 if inclsubs:
1002 1002 inclsubs.append('.hgsubstate')
1003 1003 substatestate = repo.dirstate['.hgsubstate']
1004 1004 if opts.get('include') or opts.get('exclude') or pats:
1005 1005 if inclsubs:
1006 1006 pats = list(pats or []) + inclsubs
1007 1007 match = scmutil.match(repo[None], pats, opts)
1008 1008 # detect missing files in pats
1009 1009 def badfn(f, msg):
1010 1010 if f != '.hgsubstate': # .hgsubstate is auto-created
1011 1011 raise util.Abort('%s: %s' % (f, msg))
1012 1012 match.bad = badfn
1013 1013 changes = repo.status(match=match)
1014 1014 m, a, r, d = changes[:4]
1015 1015 else:
1016 1016 changes = self.checklocalchanges(repo, force=True)
1017 1017 m, a, r, d = changes
1018 1018 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
1019 1019 if len(repo[None].parents()) > 1:
1020 1020 raise util.Abort(_('cannot manage merge changesets'))
1021 1021 commitfiles = m + a + r
1022 1022 self.checktoppatch(repo)
1023 1023 insert = self.fullseriesend()
1024 1024 wlock = repo.wlock()
1025 1025 try:
1026 1026 try:
1027 1027 # if patch file write fails, abort early
1028 1028 p = self.opener(patchfn, "w")
1029 1029 except IOError, e:
1030 1030 raise util.Abort(_('cannot write patch "%s": %s')
1031 1031 % (patchfn, e.strerror))
1032 1032 try:
1033 1033 if self.plainmode:
1034 1034 if user:
1035 1035 p.write("From: " + user + "\n")
1036 1036 if not date:
1037 1037 p.write("\n")
1038 1038 if date:
1039 1039 p.write("Date: %d %d\n\n" % date)
1040 1040 else:
1041 1041 p.write("# HG changeset patch\n")
1042 1042 p.write("# Parent "
1043 1043 + hex(repo[None].p1().node()) + "\n")
1044 1044 if user:
1045 1045 p.write("# User " + user + "\n")
1046 1046 if date:
1047 1047 p.write("# Date %s %s\n\n" % date)
1048 1048 if util.safehasattr(msg, '__call__'):
1049 1049 msg = msg()
1050 1050 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
1051 1051 n = newcommit(repo, None, commitmsg, user, date, match=match,
1052 1052 force=True)
1053 1053 if n is None:
1054 1054 raise util.Abort(_("repo commit failed"))
1055 1055 try:
1056 1056 self.fullseries[insert:insert] = [patchfn]
1057 1057 self.applied.append(statusentry(n, patchfn))
1058 1058 self.parseseries()
1059 1059 self.seriesdirty = True
1060 1060 self.applieddirty = True
1061 1061 if msg:
1062 1062 msg = msg + "\n\n"
1063 1063 p.write(msg)
1064 1064 if commitfiles:
1065 1065 parent = self.qparents(repo, n)
1066 1066 if inclsubs:
1067 1067 if substatestate in 'a?':
1068 1068 changes[1].append('.hgsubstate')
1069 1069 elif substatestate in 'r':
1070 1070 changes[2].append('.hgsubstate')
1071 1071 else: # modified
1072 1072 changes[0].append('.hgsubstate')
1073 1073 chunks = patchmod.diff(repo, node1=parent, node2=n,
1074 1074 changes=changes, opts=diffopts)
1075 1075 for chunk in chunks:
1076 1076 p.write(chunk)
1077 1077 p.close()
1078 1078 r = self.qrepo()
1079 1079 if r:
1080 1080 r[None].add([patchfn])
1081 1081 except: # re-raises
1082 1082 repo.rollback()
1083 1083 raise
1084 1084 except Exception:
1085 1085 patchpath = self.join(patchfn)
1086 1086 try:
1087 1087 os.unlink(patchpath)
1088 1088 except OSError:
1089 1089 self.ui.warn(_('error unlinking %s\n') % patchpath)
1090 1090 raise
1091 1091 self.removeundo(repo)
1092 1092 finally:
1093 1093 release(wlock)
1094 1094
1095 1095 def strip(self, repo, revs, update=True, backup="all", force=None):
1096 1096 wlock = lock = None
1097 1097 try:
1098 1098 wlock = repo.wlock()
1099 1099 lock = repo.lock()
1100 1100
1101 1101 if update:
1102 1102 self.checklocalchanges(repo, force=force, refresh=False)
1103 1103 urev = self.qparents(repo, revs[0])
1104 1104 hg.clean(repo, urev)
1105 1105 repo.dirstate.write()
1106 1106
1107 1107 repair.strip(self.ui, repo, revs, backup)
1108 1108 finally:
1109 1109 release(lock, wlock)
1110 1110
1111 1111 def isapplied(self, patch):
1112 1112 """returns (index, rev, patch)"""
1113 1113 for i, a in enumerate(self.applied):
1114 1114 if a.name == patch:
1115 1115 return (i, a.node, a.name)
1116 1116 return None
1117 1117
1118 1118 # if the exact patch name does not exist, we try a few
1119 1119 # variations. If strict is passed, we try only #1
1120 1120 #
1121 1121 # 1) a number (as string) to indicate an offset in the series file
1122 1122 # 2) a unique substring of the patch name was given
1123 1123 # 3) patchname[-+]num to indicate an offset in the series file
1124 1124 def lookup(self, patch, strict=False):
1125 1125 def partialname(s):
1126 1126 if s in self.series:
1127 1127 return s
1128 1128 matches = [x for x in self.series if s in x]
1129 1129 if len(matches) > 1:
1130 1130 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1131 1131 for m in matches:
1132 1132 self.ui.warn(' %s\n' % m)
1133 1133 return None
1134 1134 if matches:
1135 1135 return matches[0]
1136 1136 if self.series and self.applied:
1137 1137 if s == 'qtip':
1138 1138 return self.series[self.seriesend(True)-1]
1139 1139 if s == 'qbase':
1140 1140 return self.series[0]
1141 1141 return None
1142 1142
1143 1143 if patch in self.series:
1144 1144 return patch
1145 1145
1146 1146 if not os.path.isfile(self.join(patch)):
1147 1147 try:
1148 1148 sno = int(patch)
1149 1149 except (ValueError, OverflowError):
1150 1150 pass
1151 1151 else:
1152 1152 if -len(self.series) <= sno < len(self.series):
1153 1153 return self.series[sno]
1154 1154
1155 1155 if not strict:
1156 1156 res = partialname(patch)
1157 1157 if res:
1158 1158 return res
1159 1159 minus = patch.rfind('-')
1160 1160 if minus >= 0:
1161 1161 res = partialname(patch[:minus])
1162 1162 if res:
1163 1163 i = self.series.index(res)
1164 1164 try:
1165 1165 off = int(patch[minus + 1:] or 1)
1166 1166 except (ValueError, OverflowError):
1167 1167 pass
1168 1168 else:
1169 1169 if i - off >= 0:
1170 1170 return self.series[i - off]
1171 1171 plus = patch.rfind('+')
1172 1172 if plus >= 0:
1173 1173 res = partialname(patch[:plus])
1174 1174 if res:
1175 1175 i = self.series.index(res)
1176 1176 try:
1177 1177 off = int(patch[plus + 1:] or 1)
1178 1178 except (ValueError, OverflowError):
1179 1179 pass
1180 1180 else:
1181 1181 if i + off < len(self.series):
1182 1182 return self.series[i + off]
1183 1183 raise util.Abort(_("patch %s not in series") % patch)
1184 1184
1185 1185 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1186 1186 all=False, move=False, exact=False, nobackup=False,
1187 1187 keepchanges=False):
1188 1188 self.checkkeepchanges(keepchanges, force)
1189 1189 diffopts = self.diffopts()
1190 1190 wlock = repo.wlock()
1191 1191 try:
1192 1192 heads = []
1193 1193 for b, ls in repo.branchmap().iteritems():
1194 1194 heads += ls
1195 1195 if not heads:
1196 1196 heads = [nullid]
1197 1197 if repo.dirstate.p1() not in heads and not exact:
1198 1198 self.ui.status(_("(working directory not at a head)\n"))
1199 1199
1200 1200 if not self.series:
1201 1201 self.ui.warn(_('no patches in series\n'))
1202 1202 return 0
1203 1203
1204 1204 # Suppose our series file is: A B C and the current 'top'
1205 1205 # patch is B. qpush C should be performed (moving forward)
1206 1206 # qpush B is a NOP (no change) qpush A is an error (can't
1207 1207 # go backwards with qpush)
1208 1208 if patch:
1209 1209 patch = self.lookup(patch)
1210 1210 info = self.isapplied(patch)
1211 1211 if info and info[0] >= len(self.applied) - 1:
1212 1212 self.ui.warn(
1213 1213 _('qpush: %s is already at the top\n') % patch)
1214 1214 return 0
1215 1215
1216 1216 pushable, reason = self.pushable(patch)
1217 1217 if pushable:
1218 1218 if self.series.index(patch) < self.seriesend():
1219 1219 raise util.Abort(
1220 1220 _("cannot push to a previous patch: %s") % patch)
1221 1221 else:
1222 1222 if reason:
1223 1223 reason = _('guarded by %s') % reason
1224 1224 else:
1225 1225 reason = _('no matching guards')
1226 1226 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1227 1227 return 1
1228 1228 elif all:
1229 1229 patch = self.series[-1]
1230 1230 if self.isapplied(patch):
1231 1231 self.ui.warn(_('all patches are currently applied\n'))
1232 1232 return 0
1233 1233
1234 1234 # Following the above example, starting at 'top' of B:
1235 1235 # qpush should be performed (pushes C), but a subsequent
1236 1236 # qpush without an argument is an error (nothing to
1237 1237 # apply). This allows a loop of "...while hg qpush..." to
1238 1238 # work as it detects an error when done
1239 1239 start = self.seriesend()
1240 1240 if start == len(self.series):
1241 1241 self.ui.warn(_('patch series already fully applied\n'))
1242 1242 return 1
1243 1243 if not force and not keepchanges:
1244 1244 self.checklocalchanges(repo, refresh=self.applied)
1245 1245
1246 1246 if exact:
1247 1247 if keepchanges:
1248 1248 raise util.Abort(
1249 1249 _("cannot use --exact and --keep-changes together"))
1250 1250 if move:
1251 1251 raise util.Abort(_('cannot use --exact and --move '
1252 1252 'together'))
1253 1253 if self.applied:
1254 1254 raise util.Abort(_('cannot push --exact with applied '
1255 1255 'patches'))
1256 1256 root = self.series[start]
1257 1257 target = patchheader(self.join(root), self.plainmode).parent
1258 1258 if not target:
1259 1259 raise util.Abort(
1260 1260 _("%s does not have a parent recorded") % root)
1261 1261 if not repo[target] == repo['.']:
1262 1262 hg.update(repo, target)
1263 1263
1264 1264 if move:
1265 1265 if not patch:
1266 1266 raise util.Abort(_("please specify the patch to move"))
1267 1267 for fullstart, rpn in enumerate(self.fullseries):
1268 1268 # strip markers for patch guards
1269 1269 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1270 1270 break
1271 1271 for i, rpn in enumerate(self.fullseries[fullstart:]):
1272 1272 # strip markers for patch guards
1273 1273 if self.guard_re.split(rpn, 1)[0] == patch:
1274 1274 break
1275 1275 index = fullstart + i
1276 1276 assert index < len(self.fullseries)
1277 1277 fullpatch = self.fullseries[index]
1278 1278 del self.fullseries[index]
1279 1279 self.fullseries.insert(fullstart, fullpatch)
1280 1280 self.parseseries()
1281 1281 self.seriesdirty = True
1282 1282
1283 1283 self.applieddirty = True
1284 1284 if start > 0:
1285 1285 self.checktoppatch(repo)
1286 1286 if not patch:
1287 1287 patch = self.series[start]
1288 1288 end = start + 1
1289 1289 else:
1290 1290 end = self.series.index(patch, start) + 1
1291 1291
1292 1292 tobackup = set()
1293 1293 if (not nobackup and force) or keepchanges:
1294 1294 m, a, r, d = self.checklocalchanges(repo, force=True)
1295 1295 if keepchanges:
1296 1296 tobackup.update(m + a + r + d)
1297 1297 else:
1298 1298 tobackup.update(m + a)
1299 1299
1300 1300 s = self.series[start:end]
1301 1301 all_files = set()
1302 1302 try:
1303 1303 if mergeq:
1304 1304 ret = self.mergepatch(repo, mergeq, s, diffopts)
1305 1305 else:
1306 1306 ret = self.apply(repo, s, list, all_files=all_files,
1307 1307 tobackup=tobackup, keepchanges=keepchanges)
1308 1308 except: # re-raises
1309 1309 self.ui.warn(_('cleaning up working directory...'))
1310 1310 node = repo.dirstate.p1()
1311 1311 hg.revert(repo, node, None)
1312 1312 # only remove unknown files that we know we touched or
1313 1313 # created while patching
1314 1314 for f in all_files:
1315 1315 if f not in repo.dirstate:
1316 1316 try:
1317 1317 util.unlinkpath(repo.wjoin(f))
1318 1318 except OSError, inst:
1319 1319 if inst.errno != errno.ENOENT:
1320 1320 raise
1321 1321 self.ui.warn(_('done\n'))
1322 1322 raise
1323 1323
1324 1324 if not self.applied:
1325 1325 return ret[0]
1326 1326 top = self.applied[-1].name
1327 1327 if ret[0] and ret[0] > 1:
1328 1328 msg = _("errors during apply, please fix and refresh %s\n")
1329 1329 self.ui.write(msg % top)
1330 1330 else:
1331 1331 self.ui.write(_("now at: %s\n") % top)
1332 1332 return ret[0]
1333 1333
1334 1334 finally:
1335 1335 wlock.release()
1336 1336
1337 1337 def pop(self, repo, patch=None, force=False, update=True, all=False,
1338 1338 nobackup=False, keepchanges=False):
1339 1339 self.checkkeepchanges(keepchanges, force)
1340 1340 wlock = repo.wlock()
1341 1341 try:
1342 1342 if patch:
1343 1343 # index, rev, patch
1344 1344 info = self.isapplied(patch)
1345 1345 if not info:
1346 1346 patch = self.lookup(patch)
1347 1347 info = self.isapplied(patch)
1348 1348 if not info:
1349 1349 raise util.Abort(_("patch %s is not applied") % patch)
1350 1350
1351 1351 if not self.applied:
1352 1352 # Allow qpop -a to work repeatedly,
1353 1353 # but not qpop without an argument
1354 1354 self.ui.warn(_("no patches applied\n"))
1355 1355 return not all
1356 1356
1357 1357 if all:
1358 1358 start = 0
1359 1359 elif patch:
1360 1360 start = info[0] + 1
1361 1361 else:
1362 1362 start = len(self.applied) - 1
1363 1363
1364 1364 if start >= len(self.applied):
1365 1365 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1366 1366 return
1367 1367
1368 1368 if not update:
1369 1369 parents = repo.dirstate.parents()
1370 1370 rr = [x.node for x in self.applied]
1371 1371 for p in parents:
1372 1372 if p in rr:
1373 1373 self.ui.warn(_("qpop: forcing dirstate update\n"))
1374 1374 update = True
1375 1375 else:
1376 1376 parents = [p.node() for p in repo[None].parents()]
1377 1377 needupdate = False
1378 1378 for entry in self.applied[start:]:
1379 1379 if entry.node in parents:
1380 1380 needupdate = True
1381 1381 break
1382 1382 update = needupdate
1383 1383
1384 1384 tobackup = set()
1385 1385 if update:
1386 1386 m, a, r, d = self.checklocalchanges(
1387 1387 repo, force=force or keepchanges)
1388 1388 if force:
1389 1389 if not nobackup:
1390 1390 tobackup.update(m + a)
1391 1391 elif keepchanges:
1392 1392 tobackup.update(m + a + r + d)
1393 1393
1394 1394 self.applieddirty = True
1395 1395 end = len(self.applied)
1396 1396 rev = self.applied[start].node
1397 1397 if update:
1398 1398 top = self.checktoppatch(repo)[0]
1399 1399
1400 1400 try:
1401 1401 heads = repo.changelog.heads(rev)
1402 1402 except error.LookupError:
1403 1403 node = short(rev)
1404 1404 raise util.Abort(_('trying to pop unknown node %s') % node)
1405 1405
1406 1406 if heads != [self.applied[-1].node]:
1407 1407 raise util.Abort(_("popping would remove a revision not "
1408 1408 "managed by this patch queue"))
1409 1409 if not repo[self.applied[-1].node].mutable():
1410 1410 raise util.Abort(
1411 1411 _("popping would remove an immutable revision"),
1412 1412 hint=_('see "hg help phases" for details'))
1413 1413
1414 1414 # we know there are no local changes, so we can make a simplified
1415 1415 # form of hg.update.
1416 1416 if update:
1417 1417 qp = self.qparents(repo, rev)
1418 1418 ctx = repo[qp]
1419 1419 m, a, r, d = repo.status(qp, top)[:4]
1420 1420 if d:
1421 1421 raise util.Abort(_("deletions found between repo revs"))
1422 1422
1423 1423 tobackup = set(a + m + r) & tobackup
1424 1424 if keepchanges and tobackup:
1425 1425 self.localchangesfound()
1426 1426 self.backup(repo, tobackup)
1427 1427
1428 1428 for f in a:
1429 1429 try:
1430 1430 util.unlinkpath(repo.wjoin(f))
1431 1431 except OSError, e:
1432 1432 if e.errno != errno.ENOENT:
1433 1433 raise
1434 1434 repo.dirstate.drop(f)
1435 1435 for f in m + r:
1436 1436 fctx = ctx[f]
1437 1437 repo.wwrite(f, fctx.data(), fctx.flags())
1438 1438 repo.dirstate.normal(f)
1439 1439 repo.setparents(qp, nullid)
1440 1440 for patch in reversed(self.applied[start:end]):
1441 1441 self.ui.status(_("popping %s\n") % patch.name)
1442 1442 del self.applied[start:end]
1443 1443 self.strip(repo, [rev], update=False, backup='strip')
1444 1444 if self.applied:
1445 1445 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1446 1446 else:
1447 1447 self.ui.write(_("patch queue now empty\n"))
1448 1448 finally:
1449 1449 wlock.release()
1450 1450
1451 1451 def diff(self, repo, pats, opts):
1452 1452 top, patch = self.checktoppatch(repo)
1453 1453 if not top:
1454 1454 self.ui.write(_("no patches applied\n"))
1455 1455 return
1456 1456 qp = self.qparents(repo, top)
1457 1457 if opts.get('reverse'):
1458 1458 node1, node2 = None, qp
1459 1459 else:
1460 1460 node1, node2 = qp, None
1461 1461 diffopts = self.diffopts(opts, patch)
1462 1462 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1463 1463
1464 1464 def refresh(self, repo, pats=None, **opts):
1465 1465 if not self.applied:
1466 1466 self.ui.write(_("no patches applied\n"))
1467 1467 return 1
1468 1468 msg = opts.get('msg', '').rstrip()
1469 1469 newuser = opts.get('user')
1470 1470 newdate = opts.get('date')
1471 1471 if newdate:
1472 1472 newdate = '%d %d' % util.parsedate(newdate)
1473 1473 wlock = repo.wlock()
1474 1474
1475 1475 try:
1476 1476 self.checktoppatch(repo)
1477 1477 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1478 1478 if repo.changelog.heads(top) != [top]:
1479 1479 raise util.Abort(_("cannot refresh a revision with children"))
1480 1480 if not repo[top].mutable():
1481 1481 raise util.Abort(_("cannot refresh immutable revision"),
1482 1482 hint=_('see "hg help phases" for details'))
1483 1483
1484 1484 inclsubs = self.checksubstate(repo)
1485 1485
1486 1486 cparents = repo.changelog.parents(top)
1487 1487 patchparent = self.qparents(repo, top)
1488 1488 ph = patchheader(self.join(patchfn), self.plainmode)
1489 1489 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1490 1490 if msg:
1491 1491 ph.setmessage(msg)
1492 1492 if newuser:
1493 1493 ph.setuser(newuser)
1494 1494 if newdate:
1495 1495 ph.setdate(newdate)
1496 1496 ph.setparent(hex(patchparent))
1497 1497
1498 1498 # only commit new patch when write is complete
1499 1499 patchf = self.opener(patchfn, 'w', atomictemp=True)
1500 1500
1501 1501 comments = str(ph)
1502 1502 if comments:
1503 1503 patchf.write(comments)
1504 1504
1505 1505 # update the dirstate in place, strip off the qtip commit
1506 1506 # and then commit.
1507 1507 #
1508 1508 # this should really read:
1509 1509 # mm, dd, aa = repo.status(top, patchparent)[:3]
1510 1510 # but we do it backwards to take advantage of manifest/chlog
1511 1511 # caching against the next repo.status call
1512 1512 mm, aa, dd = repo.status(patchparent, top)[:3]
1513 1513 changes = repo.changelog.read(top)
1514 1514 man = repo.manifest.read(changes[0])
1515 1515 aaa = aa[:]
1516 1516 matchfn = scmutil.match(repo[None], pats, opts)
1517 1517 # in short mode, we only diff the files included in the
1518 1518 # patch already plus specified files
1519 1519 if opts.get('short'):
1520 1520 # if amending a patch, we start with existing
1521 1521 # files plus specified files - unfiltered
1522 1522 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1523 1523 # filter with inc/exl options
1524 1524 matchfn = scmutil.match(repo[None], opts=opts)
1525 1525 else:
1526 1526 match = scmutil.matchall(repo)
1527 1527 m, a, r, d = repo.status(match=match)[:4]
1528 1528 mm = set(mm)
1529 1529 aa = set(aa)
1530 1530 dd = set(dd)
1531 1531
1532 1532 # we might end up with files that were added between
1533 1533 # qtip and the dirstate parent, but then changed in the
1534 1534 # local dirstate. in this case, we want them to only
1535 1535 # show up in the added section
1536 1536 for x in m:
1537 1537 if x not in aa:
1538 1538 mm.add(x)
1539 1539 # we might end up with files added by the local dirstate that
1540 1540 # were deleted by the patch. In this case, they should only
1541 1541 # show up in the changed section.
1542 1542 for x in a:
1543 1543 if x in dd:
1544 1544 dd.remove(x)
1545 1545 mm.add(x)
1546 1546 else:
1547 1547 aa.add(x)
1548 1548 # make sure any files deleted in the local dirstate
1549 1549 # are not in the add or change column of the patch
1550 1550 forget = []
1551 1551 for x in d + r:
1552 1552 if x in aa:
1553 1553 aa.remove(x)
1554 1554 forget.append(x)
1555 1555 continue
1556 1556 else:
1557 1557 mm.discard(x)
1558 1558 dd.add(x)
1559 1559
1560 1560 m = list(mm)
1561 1561 r = list(dd)
1562 1562 a = list(aa)
1563 1563 c = [filter(matchfn, l) for l in (m, a, r)]
1564 1564 match = scmutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1565 1565 chunks = patchmod.diff(repo, patchparent, match=match,
1566 1566 changes=c, opts=diffopts)
1567 1567 for chunk in chunks:
1568 1568 patchf.write(chunk)
1569 1569
1570 1570 try:
1571 1571 if diffopts.git or diffopts.upgrade:
1572 1572 copies = {}
1573 1573 for dst in a:
1574 1574 src = repo.dirstate.copied(dst)
1575 1575 # during qfold, the source file for copies may
1576 1576 # be removed. Treat this as a simple add.
1577 1577 if src is not None and src in repo.dirstate:
1578 1578 copies.setdefault(src, []).append(dst)
1579 1579 repo.dirstate.add(dst)
1580 1580 # remember the copies between patchparent and qtip
1581 1581 for dst in aaa:
1582 1582 f = repo.file(dst)
1583 1583 src = f.renamed(man[dst])
1584 1584 if src:
1585 1585 copies.setdefault(src[0], []).extend(
1586 1586 copies.get(dst, []))
1587 1587 if dst in a:
1588 1588 copies[src[0]].append(dst)
1589 1589 # we can't copy a file created by the patch itself
1590 1590 if dst in copies:
1591 1591 del copies[dst]
1592 1592 for src, dsts in copies.iteritems():
1593 1593 for dst in dsts:
1594 1594 repo.dirstate.copy(src, dst)
1595 1595 else:
1596 1596 for dst in a:
1597 1597 repo.dirstate.add(dst)
1598 1598 # Drop useless copy information
1599 1599 for f in list(repo.dirstate.copies()):
1600 1600 repo.dirstate.copy(None, f)
1601 1601 for f in r:
1602 1602 repo.dirstate.remove(f)
1603 1603 # if the patch excludes a modified file, mark that
1604 1604 # file with mtime=0 so status can see it.
1605 1605 mm = []
1606 1606 for i in xrange(len(m)-1, -1, -1):
1607 1607 if not matchfn(m[i]):
1608 1608 mm.append(m[i])
1609 1609 del m[i]
1610 1610 for f in m:
1611 1611 repo.dirstate.normal(f)
1612 1612 for f in mm:
1613 1613 repo.dirstate.normallookup(f)
1614 1614 for f in forget:
1615 1615 repo.dirstate.drop(f)
1616 1616
1617 1617 if not msg:
1618 1618 if not ph.message:
1619 1619 message = "[mq]: %s\n" % patchfn
1620 1620 else:
1621 1621 message = "\n".join(ph.message)
1622 1622 else:
1623 1623 message = msg
1624 1624
1625 1625 user = ph.user or changes[1]
1626 1626
1627 1627 oldphase = repo[top].phase()
1628 1628
1629 1629 # assumes strip can roll itself back if interrupted
1630 1630 repo.setparents(*cparents)
1631 1631 self.applied.pop()
1632 1632 self.applieddirty = True
1633 1633 self.strip(repo, [top], update=False,
1634 1634 backup='strip')
1635 1635 except: # re-raises
1636 1636 repo.dirstate.invalidate()
1637 1637 raise
1638 1638
1639 1639 try:
1640 1640 # might be nice to attempt to roll back strip after this
1641 1641
1642 1642 # Ensure we create a new changeset in the same phase than
1643 1643 # the old one.
1644 1644 n = newcommit(repo, oldphase, message, user, ph.date,
1645 1645 match=match, force=True)
1646 1646 # only write patch after a successful commit
1647 1647 patchf.close()
1648 1648 self.applied.append(statusentry(n, patchfn))
1649 1649 except: # re-raises
1650 1650 ctx = repo[cparents[0]]
1651 1651 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1652 1652 self.savedirty()
1653 1653 self.ui.warn(_('refresh interrupted while patch was popped! '
1654 1654 '(revert --all, qpush to recover)\n'))
1655 1655 raise
1656 1656 finally:
1657 1657 wlock.release()
1658 1658 self.removeundo(repo)
1659 1659
1660 1660 def init(self, repo, create=False):
1661 1661 if not create and os.path.isdir(self.path):
1662 1662 raise util.Abort(_("patch queue directory already exists"))
1663 1663 try:
1664 1664 os.mkdir(self.path)
1665 1665 except OSError, inst:
1666 1666 if inst.errno != errno.EEXIST or not create:
1667 1667 raise
1668 1668 if create:
1669 1669 return self.qrepo(create=True)
1670 1670
1671 1671 def unapplied(self, repo, patch=None):
1672 1672 if patch and patch not in self.series:
1673 1673 raise util.Abort(_("patch %s is not in series file") % patch)
1674 1674 if not patch:
1675 1675 start = self.seriesend()
1676 1676 else:
1677 1677 start = self.series.index(patch) + 1
1678 1678 unapplied = []
1679 1679 for i in xrange(start, len(self.series)):
1680 1680 pushable, reason = self.pushable(i)
1681 1681 if pushable:
1682 1682 unapplied.append((i, self.series[i]))
1683 1683 self.explainpushable(i)
1684 1684 return unapplied
1685 1685
1686 1686 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1687 1687 summary=False):
1688 1688 def displayname(pfx, patchname, state):
1689 1689 if pfx:
1690 1690 self.ui.write(pfx)
1691 1691 if summary:
1692 1692 ph = patchheader(self.join(patchname), self.plainmode)
1693 1693 msg = ph.message and ph.message[0] or ''
1694 1694 if self.ui.formatted():
1695 1695 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1696 1696 if width > 0:
1697 1697 msg = util.ellipsis(msg, width)
1698 1698 else:
1699 1699 msg = ''
1700 1700 self.ui.write(patchname, label='qseries.' + state)
1701 1701 self.ui.write(': ')
1702 1702 self.ui.write(msg, label='qseries.message.' + state)
1703 1703 else:
1704 1704 self.ui.write(patchname, label='qseries.' + state)
1705 1705 self.ui.write('\n')
1706 1706
1707 1707 applied = set([p.name for p in self.applied])
1708 1708 if length is None:
1709 1709 length = len(self.series) - start
1710 1710 if not missing:
1711 1711 if self.ui.verbose:
1712 1712 idxwidth = len(str(start + length - 1))
1713 1713 for i in xrange(start, start + length):
1714 1714 patch = self.series[i]
1715 1715 if patch in applied:
1716 1716 char, state = 'A', 'applied'
1717 1717 elif self.pushable(i)[0]:
1718 1718 char, state = 'U', 'unapplied'
1719 1719 else:
1720 1720 char, state = 'G', 'guarded'
1721 1721 pfx = ''
1722 1722 if self.ui.verbose:
1723 1723 pfx = '%*d %s ' % (idxwidth, i, char)
1724 1724 elif status and status != char:
1725 1725 continue
1726 1726 displayname(pfx, patch, state)
1727 1727 else:
1728 1728 msng_list = []
1729 1729 for root, dirs, files in os.walk(self.path):
1730 1730 d = root[len(self.path) + 1:]
1731 1731 for f in files:
1732 1732 fl = os.path.join(d, f)
1733 1733 if (fl not in self.series and
1734 1734 fl not in (self.statuspath, self.seriespath,
1735 1735 self.guardspath)
1736 1736 and not fl.startswith('.')):
1737 1737 msng_list.append(fl)
1738 1738 for x in sorted(msng_list):
1739 1739 pfx = self.ui.verbose and ('D ') or ''
1740 1740 displayname(pfx, x, 'missing')
1741 1741
1742 1742 def issaveline(self, l):
1743 1743 if l.name == '.hg.patches.save.line':
1744 1744 return True
1745 1745
1746 1746 def qrepo(self, create=False):
1747 1747 ui = self.ui.copy()
1748 1748 ui.setconfig('paths', 'default', '', overlay=False)
1749 1749 ui.setconfig('paths', 'default-push', '', overlay=False)
1750 1750 if create or os.path.isdir(self.join(".hg")):
1751 1751 return hg.repository(ui, path=self.path, create=create)
1752 1752
1753 1753 def restore(self, repo, rev, delete=None, qupdate=None):
1754 1754 desc = repo[rev].description().strip()
1755 1755 lines = desc.splitlines()
1756 1756 i = 0
1757 1757 datastart = None
1758 1758 series = []
1759 1759 applied = []
1760 1760 qpp = None
1761 1761 for i, line in enumerate(lines):
1762 1762 if line == 'Patch Data:':
1763 1763 datastart = i + 1
1764 1764 elif line.startswith('Dirstate:'):
1765 1765 l = line.rstrip()
1766 1766 l = l[10:].split(' ')
1767 1767 qpp = [bin(x) for x in l]
1768 1768 elif datastart is not None:
1769 1769 l = line.rstrip()
1770 1770 n, name = l.split(':', 1)
1771 1771 if n:
1772 1772 applied.append(statusentry(bin(n), name))
1773 1773 else:
1774 1774 series.append(l)
1775 1775 if datastart is None:
1776 1776 self.ui.warn(_("no saved patch data found\n"))
1777 1777 return 1
1778 1778 self.ui.warn(_("restoring status: %s\n") % lines[0])
1779 1779 self.fullseries = series
1780 1780 self.applied = applied
1781 1781 self.parseseries()
1782 1782 self.seriesdirty = True
1783 1783 self.applieddirty = True
1784 1784 heads = repo.changelog.heads()
1785 1785 if delete:
1786 1786 if rev not in heads:
1787 1787 self.ui.warn(_("save entry has children, leaving it alone\n"))
1788 1788 else:
1789 1789 self.ui.warn(_("removing save entry %s\n") % short(rev))
1790 1790 pp = repo.dirstate.parents()
1791 1791 if rev in pp:
1792 1792 update = True
1793 1793 else:
1794 1794 update = False
1795 1795 self.strip(repo, [rev], update=update, backup='strip')
1796 1796 if qpp:
1797 1797 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1798 1798 (short(qpp[0]), short(qpp[1])))
1799 1799 if qupdate:
1800 1800 self.ui.status(_("updating queue directory\n"))
1801 1801 r = self.qrepo()
1802 1802 if not r:
1803 1803 self.ui.warn(_("unable to load queue repository\n"))
1804 1804 return 1
1805 1805 hg.clean(r, qpp[0])
1806 1806
1807 1807 def save(self, repo, msg=None):
1808 1808 if not self.applied:
1809 1809 self.ui.warn(_("save: no patches applied, exiting\n"))
1810 1810 return 1
1811 1811 if self.issaveline(self.applied[-1]):
1812 1812 self.ui.warn(_("status is already saved\n"))
1813 1813 return 1
1814 1814
1815 1815 if not msg:
1816 1816 msg = _("hg patches saved state")
1817 1817 else:
1818 1818 msg = "hg patches: " + msg.rstrip('\r\n')
1819 1819 r = self.qrepo()
1820 1820 if r:
1821 1821 pp = r.dirstate.parents()
1822 1822 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1823 1823 msg += "\n\nPatch Data:\n"
1824 1824 msg += ''.join('%s\n' % x for x in self.applied)
1825 1825 msg += ''.join(':%s\n' % x for x in self.fullseries)
1826 1826 n = repo.commit(msg, force=True)
1827 1827 if not n:
1828 1828 self.ui.warn(_("repo commit failed\n"))
1829 1829 return 1
1830 1830 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1831 1831 self.applieddirty = True
1832 1832 self.removeundo(repo)
1833 1833
1834 1834 def fullseriesend(self):
1835 1835 if self.applied:
1836 1836 p = self.applied[-1].name
1837 1837 end = self.findseries(p)
1838 1838 if end is None:
1839 1839 return len(self.fullseries)
1840 1840 return end + 1
1841 1841 return 0
1842 1842
1843 1843 def seriesend(self, all_patches=False):
1844 1844 """If all_patches is False, return the index of the next pushable patch
1845 1845 in the series, or the series length. If all_patches is True, return the
1846 1846 index of the first patch past the last applied one.
1847 1847 """
1848 1848 end = 0
1849 1849 def next(start):
1850 1850 if all_patches or start >= len(self.series):
1851 1851 return start
1852 1852 for i in xrange(start, len(self.series)):
1853 1853 p, reason = self.pushable(i)
1854 1854 if p:
1855 1855 return i
1856 1856 self.explainpushable(i)
1857 1857 return len(self.series)
1858 1858 if self.applied:
1859 1859 p = self.applied[-1].name
1860 1860 try:
1861 1861 end = self.series.index(p)
1862 1862 except ValueError:
1863 1863 return 0
1864 1864 return next(end + 1)
1865 1865 return next(end)
1866 1866
1867 1867 def appliedname(self, index):
1868 1868 pname = self.applied[index].name
1869 1869 if not self.ui.verbose:
1870 1870 p = pname
1871 1871 else:
1872 1872 p = str(self.series.index(pname)) + " " + pname
1873 1873 return p
1874 1874
1875 1875 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1876 1876 force=None, git=False):
1877 1877 def checkseries(patchname):
1878 1878 if patchname in self.series:
1879 1879 raise util.Abort(_('patch %s is already in the series file')
1880 1880 % patchname)
1881 1881
1882 1882 if rev:
1883 1883 if files:
1884 1884 raise util.Abort(_('option "-r" not valid when importing '
1885 1885 'files'))
1886 1886 rev = scmutil.revrange(repo, rev)
1887 1887 rev.sort(reverse=True)
1888 1888 elif not files:
1889 1889 raise util.Abort(_('no files or revisions specified'))
1890 1890 if (len(files) > 1 or len(rev) > 1) and patchname:
1891 1891 raise util.Abort(_('option "-n" not valid when importing multiple '
1892 1892 'patches'))
1893 1893 imported = []
1894 1894 if rev:
1895 1895 # If mq patches are applied, we can only import revisions
1896 1896 # that form a linear path to qbase.
1897 1897 # Otherwise, they should form a linear path to a head.
1898 1898 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1899 1899 if len(heads) > 1:
1900 1900 raise util.Abort(_('revision %d is the root of more than one '
1901 1901 'branch') % rev[-1])
1902 1902 if self.applied:
1903 1903 base = repo.changelog.node(rev[0])
1904 1904 if base in [n.node for n in self.applied]:
1905 1905 raise util.Abort(_('revision %d is already managed')
1906 1906 % rev[0])
1907 1907 if heads != [self.applied[-1].node]:
1908 1908 raise util.Abort(_('revision %d is not the parent of '
1909 1909 'the queue') % rev[0])
1910 1910 base = repo.changelog.rev(self.applied[0].node)
1911 1911 lastparent = repo.changelog.parentrevs(base)[0]
1912 1912 else:
1913 1913 if heads != [repo.changelog.node(rev[0])]:
1914 1914 raise util.Abort(_('revision %d has unmanaged children')
1915 1915 % rev[0])
1916 1916 lastparent = None
1917 1917
1918 1918 diffopts = self.diffopts({'git': git})
1919 1919 for r in rev:
1920 1920 if not repo[r].mutable():
1921 1921 raise util.Abort(_('revision %d is not mutable') % r,
1922 1922 hint=_('see "hg help phases" for details'))
1923 1923 p1, p2 = repo.changelog.parentrevs(r)
1924 1924 n = repo.changelog.node(r)
1925 1925 if p2 != nullrev:
1926 1926 raise util.Abort(_('cannot import merge revision %d') % r)
1927 1927 if lastparent and lastparent != r:
1928 1928 raise util.Abort(_('revision %d is not the parent of %d')
1929 1929 % (r, lastparent))
1930 1930 lastparent = p1
1931 1931
1932 1932 if not patchname:
1933 1933 patchname = normname('%d.diff' % r)
1934 1934 checkseries(patchname)
1935 1935 self.checkpatchname(patchname, force)
1936 1936 self.fullseries.insert(0, patchname)
1937 1937
1938 1938 patchf = self.opener(patchname, "w")
1939 1939 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1940 1940 patchf.close()
1941 1941
1942 1942 se = statusentry(n, patchname)
1943 1943 self.applied.insert(0, se)
1944 1944
1945 1945 self.added.append(patchname)
1946 1946 imported.append(patchname)
1947 1947 patchname = None
1948 1948 if rev and repo.ui.configbool('mq', 'secret', False):
1949 1949 # if we added anything with --rev, we must move the secret root
1950 1950 phases.retractboundary(repo, phases.secret, [n])
1951 1951 self.parseseries()
1952 1952 self.applieddirty = True
1953 1953 self.seriesdirty = True
1954 1954
1955 1955 for i, filename in enumerate(files):
1956 1956 if existing:
1957 1957 if filename == '-':
1958 1958 raise util.Abort(_('-e is incompatible with import from -'))
1959 1959 filename = normname(filename)
1960 1960 self.checkreservedname(filename)
1961 1961 originpath = self.join(filename)
1962 1962 if not os.path.isfile(originpath):
1963 1963 raise util.Abort(_("patch %s does not exist") % filename)
1964 1964
1965 1965 if patchname:
1966 1966 self.checkpatchname(patchname, force)
1967 1967
1968 1968 self.ui.write(_('renaming %s to %s\n')
1969 1969 % (filename, patchname))
1970 1970 util.rename(originpath, self.join(patchname))
1971 1971 else:
1972 1972 patchname = filename
1973 1973
1974 1974 else:
1975 1975 if filename == '-' and not patchname:
1976 1976 raise util.Abort(_('need --name to import a patch from -'))
1977 1977 elif not patchname:
1978 1978 patchname = normname(os.path.basename(filename.rstrip('/')))
1979 1979 self.checkpatchname(patchname, force)
1980 1980 try:
1981 1981 if filename == '-':
1982 1982 text = self.ui.fin.read()
1983 1983 else:
1984 1984 fp = url.open(self.ui, filename)
1985 1985 text = fp.read()
1986 1986 fp.close()
1987 1987 except (OSError, IOError):
1988 1988 raise util.Abort(_("unable to read file %s") % filename)
1989 1989 patchf = self.opener(patchname, "w")
1990 1990 patchf.write(text)
1991 1991 patchf.close()
1992 1992 if not force:
1993 1993 checkseries(patchname)
1994 1994 if patchname not in self.series:
1995 1995 index = self.fullseriesend() + i
1996 1996 self.fullseries[index:index] = [patchname]
1997 1997 self.parseseries()
1998 1998 self.seriesdirty = True
1999 1999 self.ui.warn(_("adding %s to series file\n") % patchname)
2000 2000 self.added.append(patchname)
2001 2001 imported.append(patchname)
2002 2002 patchname = None
2003 2003
2004 2004 self.removeundo(repo)
2005 2005 return imported
2006 2006
2007 2007 def fixkeepchangesopts(ui, opts):
2008 2008 if (not ui.configbool('mq', 'keepchanges') or opts.get('force')
2009 2009 or opts.get('exact')):
2010 2010 return opts
2011 2011 opts = dict(opts)
2012 2012 opts['keep_changes'] = True
2013 2013 return opts
2014 2014
2015 2015 @command("qdelete|qremove|qrm",
2016 2016 [('k', 'keep', None, _('keep patch file')),
2017 2017 ('r', 'rev', [],
2018 2018 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2019 2019 _('hg qdelete [-k] [PATCH]...'))
2020 2020 def delete(ui, repo, *patches, **opts):
2021 2021 """remove patches from queue
2022 2022
2023 2023 The patches must not be applied, and at least one patch is required. Exact
2024 2024 patch identifiers must be given. With -k/--keep, the patch files are
2025 2025 preserved in the patch directory.
2026 2026
2027 2027 To stop managing a patch and move it into permanent history,
2028 2028 use the :hg:`qfinish` command."""
2029 2029 q = repo.mq
2030 2030 q.delete(repo, patches, opts)
2031 2031 q.savedirty()
2032 2032 return 0
2033 2033
2034 2034 @command("qapplied",
2035 2035 [('1', 'last', None, _('show only the preceding applied patch'))
2036 2036 ] + seriesopts,
2037 2037 _('hg qapplied [-1] [-s] [PATCH]'))
2038 2038 def applied(ui, repo, patch=None, **opts):
2039 2039 """print the patches already applied
2040 2040
2041 2041 Returns 0 on success."""
2042 2042
2043 2043 q = repo.mq
2044 2044
2045 2045 if patch:
2046 2046 if patch not in q.series:
2047 2047 raise util.Abort(_("patch %s is not in series file") % patch)
2048 2048 end = q.series.index(patch) + 1
2049 2049 else:
2050 2050 end = q.seriesend(True)
2051 2051
2052 2052 if opts.get('last') and not end:
2053 2053 ui.write(_("no patches applied\n"))
2054 2054 return 1
2055 2055 elif opts.get('last') and end == 1:
2056 2056 ui.write(_("only one patch applied\n"))
2057 2057 return 1
2058 2058 elif opts.get('last'):
2059 2059 start = end - 2
2060 2060 end = 1
2061 2061 else:
2062 2062 start = 0
2063 2063
2064 2064 q.qseries(repo, length=end, start=start, status='A',
2065 2065 summary=opts.get('summary'))
2066 2066
2067 2067
2068 2068 @command("qunapplied",
2069 2069 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2070 2070 _('hg qunapplied [-1] [-s] [PATCH]'))
2071 2071 def unapplied(ui, repo, patch=None, **opts):
2072 2072 """print the patches not yet applied
2073 2073
2074 2074 Returns 0 on success."""
2075 2075
2076 2076 q = repo.mq
2077 2077 if patch:
2078 2078 if patch not in q.series:
2079 2079 raise util.Abort(_("patch %s is not in series file") % patch)
2080 2080 start = q.series.index(patch) + 1
2081 2081 else:
2082 2082 start = q.seriesend(True)
2083 2083
2084 2084 if start == len(q.series) and opts.get('first'):
2085 2085 ui.write(_("all patches applied\n"))
2086 2086 return 1
2087 2087
2088 2088 length = opts.get('first') and 1 or None
2089 2089 q.qseries(repo, start=start, length=length, status='U',
2090 2090 summary=opts.get('summary'))
2091 2091
2092 2092 @command("qimport",
2093 2093 [('e', 'existing', None, _('import file in patch directory')),
2094 2094 ('n', 'name', '',
2095 2095 _('name of patch file'), _('NAME')),
2096 2096 ('f', 'force', None, _('overwrite existing files')),
2097 2097 ('r', 'rev', [],
2098 2098 _('place existing revisions under mq control'), _('REV')),
2099 2099 ('g', 'git', None, _('use git extended diff format')),
2100 2100 ('P', 'push', None, _('qpush after importing'))],
2101 2101 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'))
2102 2102 def qimport(ui, repo, *filename, **opts):
2103 2103 """import a patch or existing changeset
2104 2104
2105 2105 The patch is inserted into the series after the last applied
2106 2106 patch. If no patches have been applied, qimport prepends the patch
2107 2107 to the series.
2108 2108
2109 2109 The patch will have the same name as its source file unless you
2110 2110 give it a new one with -n/--name.
2111 2111
2112 2112 You can register an existing patch inside the patch directory with
2113 2113 the -e/--existing flag.
2114 2114
2115 2115 With -f/--force, an existing patch of the same name will be
2116 2116 overwritten.
2117 2117
2118 2118 An existing changeset may be placed under mq control with -r/--rev
2119 2119 (e.g. qimport --rev tip -n patch will place tip under mq control).
2120 2120 With -g/--git, patches imported with --rev will use the git diff
2121 2121 format. See the diffs help topic for information on why this is
2122 2122 important for preserving rename/copy information and permission
2123 2123 changes. Use :hg:`qfinish` to remove changesets from mq control.
2124 2124
2125 2125 To import a patch from standard input, pass - as the patch file.
2126 2126 When importing from standard input, a patch name must be specified
2127 2127 using the --name flag.
2128 2128
2129 2129 To import an existing patch while renaming it::
2130 2130
2131 2131 hg qimport -e existing-patch -n new-name
2132 2132
2133 2133 Returns 0 if import succeeded.
2134 2134 """
2135 2135 lock = repo.lock() # cause this may move phase
2136 2136 try:
2137 2137 q = repo.mq
2138 2138 try:
2139 2139 imported = q.qimport(
2140 2140 repo, filename, patchname=opts.get('name'),
2141 2141 existing=opts.get('existing'), force=opts.get('force'),
2142 2142 rev=opts.get('rev'), git=opts.get('git'))
2143 2143 finally:
2144 2144 q.savedirty()
2145 2145 finally:
2146 2146 lock.release()
2147 2147
2148 2148 if imported and opts.get('push') and not opts.get('rev'):
2149 2149 return q.push(repo, imported[-1])
2150 2150 return 0
2151 2151
2152 2152 def qinit(ui, repo, create):
2153 2153 """initialize a new queue repository
2154 2154
2155 2155 This command also creates a series file for ordering patches, and
2156 2156 an mq-specific .hgignore file in the queue repository, to exclude
2157 2157 the status and guards files (these contain mostly transient state).
2158 2158
2159 2159 Returns 0 if initialization succeeded."""
2160 2160 q = repo.mq
2161 2161 r = q.init(repo, create)
2162 2162 q.savedirty()
2163 2163 if r:
2164 2164 if not os.path.exists(r.wjoin('.hgignore')):
2165 2165 fp = r.wopener('.hgignore', 'w')
2166 2166 fp.write('^\\.hg\n')
2167 2167 fp.write('^\\.mq\n')
2168 2168 fp.write('syntax: glob\n')
2169 2169 fp.write('status\n')
2170 2170 fp.write('guards\n')
2171 2171 fp.close()
2172 2172 if not os.path.exists(r.wjoin('series')):
2173 2173 r.wopener('series', 'w').close()
2174 2174 r[None].add(['.hgignore', 'series'])
2175 2175 commands.add(ui, r)
2176 2176 return 0
2177 2177
2178 2178 @command("^qinit",
2179 2179 [('c', 'create-repo', None, _('create queue repository'))],
2180 2180 _('hg qinit [-c]'))
2181 2181 def init(ui, repo, **opts):
2182 2182 """init a new queue repository (DEPRECATED)
2183 2183
2184 2184 The queue repository is unversioned by default. If
2185 2185 -c/--create-repo is specified, qinit will create a separate nested
2186 2186 repository for patches (qinit -c may also be run later to convert
2187 2187 an unversioned patch repository into a versioned one). You can use
2188 2188 qcommit to commit changes to this queue repository.
2189 2189
2190 2190 This command is deprecated. Without -c, it's implied by other relevant
2191 2191 commands. With -c, use :hg:`init --mq` instead."""
2192 2192 return qinit(ui, repo, create=opts.get('create_repo'))
2193 2193
2194 2194 @command("qclone",
2195 2195 [('', 'pull', None, _('use pull protocol to copy metadata')),
2196 2196 ('U', 'noupdate', None,
2197 2197 _('do not update the new working directories')),
2198 2198 ('', 'uncompressed', None,
2199 2199 _('use uncompressed transfer (fast over LAN)')),
2200 2200 ('p', 'patches', '',
2201 2201 _('location of source patch repository'), _('REPO')),
2202 2202 ] + commands.remoteopts,
2203 2203 _('hg qclone [OPTION]... SOURCE [DEST]'))
2204 2204 def clone(ui, source, dest=None, **opts):
2205 2205 '''clone main and patch repository at same time
2206 2206
2207 2207 If source is local, destination will have no patches applied. If
2208 2208 source is remote, this command can not check if patches are
2209 2209 applied in source, so cannot guarantee that patches are not
2210 2210 applied in destination. If you clone remote repository, be sure
2211 2211 before that it has no patches applied.
2212 2212
2213 2213 Source patch repository is looked for in <src>/.hg/patches by
2214 2214 default. Use -p <url> to change.
2215 2215
2216 2216 The patch directory must be a nested Mercurial repository, as
2217 2217 would be created by :hg:`init --mq`.
2218 2218
2219 2219 Return 0 on success.
2220 2220 '''
2221 2221 def patchdir(repo):
2222 2222 """compute a patch repo url from a repo object"""
2223 2223 url = repo.url()
2224 2224 if url.endswith('/'):
2225 2225 url = url[:-1]
2226 2226 return url + '/.hg/patches'
2227 2227
2228 2228 # main repo (destination and sources)
2229 2229 if dest is None:
2230 2230 dest = hg.defaultdest(source)
2231 2231 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
2232 2232
2233 2233 # patches repo (source only)
2234 2234 if opts.get('patches'):
2235 2235 patchespath = ui.expandpath(opts.get('patches'))
2236 2236 else:
2237 2237 patchespath = patchdir(sr)
2238 2238 try:
2239 2239 hg.repository(ui, patchespath)
2240 2240 except error.RepoError:
2241 2241 raise util.Abort(_('versioned patch repository not found'
2242 2242 ' (see init --mq)'))
2243 2243 qbase, destrev = None, None
2244 2244 if sr.local():
2245 2245 if sr.mq.applied and sr[qbase].phase() != phases.secret:
2246 2246 qbase = sr.mq.applied[0].node
2247 2247 if not hg.islocal(dest):
2248 2248 heads = set(sr.heads())
2249 2249 destrev = list(heads.difference(sr.heads(qbase)))
2250 2250 destrev.append(sr.changelog.parents(qbase)[0])
2251 2251 elif sr.capable('lookup'):
2252 2252 try:
2253 2253 qbase = sr.lookup('qbase')
2254 2254 except error.RepoError:
2255 2255 pass
2256 2256
2257 2257 ui.note(_('cloning main repository\n'))
2258 2258 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2259 2259 pull=opts.get('pull'),
2260 2260 rev=destrev,
2261 2261 update=False,
2262 2262 stream=opts.get('uncompressed'))
2263 2263
2264 2264 ui.note(_('cloning patch repository\n'))
2265 2265 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2266 2266 pull=opts.get('pull'), update=not opts.get('noupdate'),
2267 2267 stream=opts.get('uncompressed'))
2268 2268
2269 2269 if dr.local():
2270 2270 if qbase:
2271 2271 ui.note(_('stripping applied patches from destination '
2272 2272 'repository\n'))
2273 2273 dr.mq.strip(dr, [qbase], update=False, backup=None)
2274 2274 if not opts.get('noupdate'):
2275 2275 ui.note(_('updating destination repository\n'))
2276 2276 hg.update(dr, dr.changelog.tip())
2277 2277
2278 2278 @command("qcommit|qci",
2279 2279 commands.table["^commit|ci"][1],
2280 2280 _('hg qcommit [OPTION]... [FILE]...'))
2281 2281 def commit(ui, repo, *pats, **opts):
2282 2282 """commit changes in the queue repository (DEPRECATED)
2283 2283
2284 2284 This command is deprecated; use :hg:`commit --mq` instead."""
2285 2285 q = repo.mq
2286 2286 r = q.qrepo()
2287 2287 if not r:
2288 2288 raise util.Abort('no queue repository')
2289 2289 commands.commit(r.ui, r, *pats, **opts)
2290 2290
2291 2291 @command("qseries",
2292 2292 [('m', 'missing', None, _('print patches not in series')),
2293 2293 ] + seriesopts,
2294 2294 _('hg qseries [-ms]'))
2295 2295 def series(ui, repo, **opts):
2296 2296 """print the entire series file
2297 2297
2298 2298 Returns 0 on success."""
2299 2299 repo.mq.qseries(repo, missing=opts.get('missing'),
2300 2300 summary=opts.get('summary'))
2301 2301 return 0
2302 2302
2303 2303 @command("qtop", seriesopts, _('hg qtop [-s]'))
2304 2304 def top(ui, repo, **opts):
2305 2305 """print the name of the current patch
2306 2306
2307 2307 Returns 0 on success."""
2308 2308 q = repo.mq
2309 2309 t = q.applied and q.seriesend(True) or 0
2310 2310 if t:
2311 2311 q.qseries(repo, start=t - 1, length=1, status='A',
2312 2312 summary=opts.get('summary'))
2313 2313 else:
2314 2314 ui.write(_("no patches applied\n"))
2315 2315 return 1
2316 2316
2317 2317 @command("qnext", seriesopts, _('hg qnext [-s]'))
2318 2318 def next(ui, repo, **opts):
2319 2319 """print the name of the next pushable patch
2320 2320
2321 2321 Returns 0 on success."""
2322 2322 q = repo.mq
2323 2323 end = q.seriesend()
2324 2324 if end == len(q.series):
2325 2325 ui.write(_("all patches applied\n"))
2326 2326 return 1
2327 2327 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2328 2328
2329 2329 @command("qprev", seriesopts, _('hg qprev [-s]'))
2330 2330 def prev(ui, repo, **opts):
2331 2331 """print the name of the preceding applied patch
2332 2332
2333 2333 Returns 0 on success."""
2334 2334 q = repo.mq
2335 2335 l = len(q.applied)
2336 2336 if l == 1:
2337 2337 ui.write(_("only one patch applied\n"))
2338 2338 return 1
2339 2339 if not l:
2340 2340 ui.write(_("no patches applied\n"))
2341 2341 return 1
2342 2342 idx = q.series.index(q.applied[-2].name)
2343 2343 q.qseries(repo, start=idx, length=1, status='A',
2344 2344 summary=opts.get('summary'))
2345 2345
2346 2346 def setupheaderopts(ui, opts):
2347 2347 if not opts.get('user') and opts.get('currentuser'):
2348 2348 opts['user'] = ui.username()
2349 2349 if not opts.get('date') and opts.get('currentdate'):
2350 2350 opts['date'] = "%d %d" % util.makedate()
2351 2351
2352 2352 @command("^qnew",
2353 2353 [('e', 'edit', None, _('edit commit message')),
2354 2354 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2355 2355 ('g', 'git', None, _('use git extended diff format')),
2356 2356 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2357 2357 ('u', 'user', '',
2358 2358 _('add "From: <USER>" to patch'), _('USER')),
2359 2359 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2360 2360 ('d', 'date', '',
2361 2361 _('add "Date: <DATE>" to patch'), _('DATE'))
2362 2362 ] + commands.walkopts + commands.commitopts,
2363 2363 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2364 2364 def new(ui, repo, patch, *args, **opts):
2365 2365 """create a new patch
2366 2366
2367 2367 qnew creates a new patch on top of the currently-applied patch (if
2368 2368 any). The patch will be initialized with any outstanding changes
2369 2369 in the working directory. You may also use -I/--include,
2370 2370 -X/--exclude, and/or a list of files after the patch name to add
2371 2371 only changes to matching files to the new patch, leaving the rest
2372 2372 as uncommitted modifications.
2373 2373
2374 2374 -u/--user and -d/--date can be used to set the (given) user and
2375 2375 date, respectively. -U/--currentuser and -D/--currentdate set user
2376 2376 to current user and date to current date.
2377 2377
2378 2378 -e/--edit, -m/--message or -l/--logfile set the patch header as
2379 2379 well as the commit message. If none is specified, the header is
2380 2380 empty and the commit message is '[mq]: PATCH'.
2381 2381
2382 2382 Use the -g/--git option to keep the patch in the git extended diff
2383 2383 format. Read the diffs help topic for more information on why this
2384 2384 is important for preserving permission changes and copy/rename
2385 2385 information.
2386 2386
2387 2387 Returns 0 on successful creation of a new patch.
2388 2388 """
2389 2389 msg = cmdutil.logmessage(ui, opts)
2390 2390 def getmsg():
2391 2391 return ui.edit(msg, opts.get('user') or ui.username())
2392 2392 q = repo.mq
2393 2393 opts['msg'] = msg
2394 2394 if opts.get('edit'):
2395 2395 opts['msg'] = getmsg
2396 2396 else:
2397 2397 opts['msg'] = msg
2398 2398 setupheaderopts(ui, opts)
2399 2399 q.new(repo, patch, *args, **opts)
2400 2400 q.savedirty()
2401 2401 return 0
2402 2402
2403 2403 @command("^qrefresh",
2404 2404 [('e', 'edit', None, _('edit commit message')),
2405 2405 ('g', 'git', None, _('use git extended diff format')),
2406 2406 ('s', 'short', None,
2407 2407 _('refresh only files already in the patch and specified files')),
2408 2408 ('U', 'currentuser', None,
2409 2409 _('add/update author field in patch with current user')),
2410 2410 ('u', 'user', '',
2411 2411 _('add/update author field in patch with given user'), _('USER')),
2412 2412 ('D', 'currentdate', None,
2413 2413 _('add/update date field in patch with current date')),
2414 2414 ('d', 'date', '',
2415 2415 _('add/update date field in patch with given date'), _('DATE'))
2416 2416 ] + commands.walkopts + commands.commitopts,
2417 2417 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2418 2418 def refresh(ui, repo, *pats, **opts):
2419 2419 """update the current patch
2420 2420
2421 2421 If any file patterns are provided, the refreshed patch will
2422 2422 contain only the modifications that match those patterns; the
2423 2423 remaining modifications will remain in the working directory.
2424 2424
2425 2425 If -s/--short is specified, files currently included in the patch
2426 2426 will be refreshed just like matched files and remain in the patch.
2427 2427
2428 2428 If -e/--edit is specified, Mercurial will start your configured editor for
2429 2429 you to enter a message. In case qrefresh fails, you will find a backup of
2430 2430 your message in ``.hg/last-message.txt``.
2431 2431
2432 2432 hg add/remove/copy/rename work as usual, though you might want to
2433 2433 use git-style patches (-g/--git or [diff] git=1) to track copies
2434 2434 and renames. See the diffs help topic for more information on the
2435 2435 git diff format.
2436 2436
2437 2437 Returns 0 on success.
2438 2438 """
2439 2439 q = repo.mq
2440 2440 message = cmdutil.logmessage(ui, opts)
2441 2441 if opts.get('edit'):
2442 2442 if not q.applied:
2443 2443 ui.write(_("no patches applied\n"))
2444 2444 return 1
2445 2445 if message:
2446 2446 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2447 2447 patch = q.applied[-1].name
2448 2448 ph = patchheader(q.join(patch), q.plainmode)
2449 2449 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2450 2450 # We don't want to lose the patch message if qrefresh fails (issue2062)
2451 2451 repo.savecommitmessage(message)
2452 2452 setupheaderopts(ui, opts)
2453 2453 wlock = repo.wlock()
2454 2454 try:
2455 2455 ret = q.refresh(repo, pats, msg=message, **opts)
2456 2456 q.savedirty()
2457 2457 return ret
2458 2458 finally:
2459 2459 wlock.release()
2460 2460
2461 2461 @command("^qdiff",
2462 2462 commands.diffopts + commands.diffopts2 + commands.walkopts,
2463 2463 _('hg qdiff [OPTION]... [FILE]...'))
2464 2464 def diff(ui, repo, *pats, **opts):
2465 2465 """diff of the current patch and subsequent modifications
2466 2466
2467 2467 Shows a diff which includes the current patch as well as any
2468 2468 changes which have been made in the working directory since the
2469 2469 last refresh (thus showing what the current patch would become
2470 2470 after a qrefresh).
2471 2471
2472 2472 Use :hg:`diff` if you only want to see the changes made since the
2473 2473 last qrefresh, or :hg:`export qtip` if you want to see changes
2474 2474 made by the current patch without including changes made since the
2475 2475 qrefresh.
2476 2476
2477 2477 Returns 0 on success.
2478 2478 """
2479 2479 repo.mq.diff(repo, pats, opts)
2480 2480 return 0
2481 2481
2482 2482 @command('qfold',
2483 2483 [('e', 'edit', None, _('edit patch header')),
2484 2484 ('k', 'keep', None, _('keep folded patch files')),
2485 2485 ] + commands.commitopts,
2486 2486 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2487 2487 def fold(ui, repo, *files, **opts):
2488 2488 """fold the named patches into the current patch
2489 2489
2490 2490 Patches must not yet be applied. Each patch will be successively
2491 2491 applied to the current patch in the order given. If all the
2492 2492 patches apply successfully, the current patch will be refreshed
2493 2493 with the new cumulative patch, and the folded patches will be
2494 2494 deleted. With -k/--keep, the folded patch files will not be
2495 2495 removed afterwards.
2496 2496
2497 2497 The header for each folded patch will be concatenated with the
2498 2498 current patch header, separated by a line of ``* * *``.
2499 2499
2500 2500 Returns 0 on success."""
2501 2501 q = repo.mq
2502 2502 if not files:
2503 2503 raise util.Abort(_('qfold requires at least one patch name'))
2504 2504 if not q.checktoppatch(repo)[0]:
2505 2505 raise util.Abort(_('no patches applied'))
2506 2506 q.checklocalchanges(repo)
2507 2507
2508 2508 message = cmdutil.logmessage(ui, opts)
2509 2509 if opts.get('edit'):
2510 2510 if message:
2511 2511 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2512 2512
2513 2513 parent = q.lookup('qtip')
2514 2514 patches = []
2515 2515 messages = []
2516 2516 for f in files:
2517 2517 p = q.lookup(f)
2518 2518 if p in patches or p == parent:
2519 2519 ui.warn(_('skipping already folded patch %s\n') % p)
2520 2520 if q.isapplied(p):
2521 2521 raise util.Abort(_('qfold cannot fold already applied patch %s')
2522 2522 % p)
2523 2523 patches.append(p)
2524 2524
2525 2525 for p in patches:
2526 2526 if not message:
2527 2527 ph = patchheader(q.join(p), q.plainmode)
2528 2528 if ph.message:
2529 2529 messages.append(ph.message)
2530 2530 pf = q.join(p)
2531 2531 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2532 2532 if not patchsuccess:
2533 2533 raise util.Abort(_('error folding patch %s') % p)
2534 2534
2535 2535 if not message:
2536 2536 ph = patchheader(q.join(parent), q.plainmode)
2537 2537 message, user = ph.message, ph.user
2538 2538 for msg in messages:
2539 2539 message.append('* * *')
2540 2540 message.extend(msg)
2541 2541 message = '\n'.join(message)
2542 2542
2543 2543 if opts.get('edit'):
2544 2544 message = ui.edit(message, user or ui.username())
2545 2545
2546 2546 diffopts = q.patchopts(q.diffopts(), *patches)
2547 2547 wlock = repo.wlock()
2548 2548 try:
2549 2549 q.refresh(repo, msg=message, git=diffopts.git)
2550 2550 q.delete(repo, patches, opts)
2551 2551 q.savedirty()
2552 2552 finally:
2553 2553 wlock.release()
2554 2554
2555 2555 @command("qgoto",
2556 2556 [('', 'keep-changes', None,
2557 2557 _('tolerate non-conflicting local changes')),
2558 2558 ('f', 'force', None, _('overwrite any local changes')),
2559 2559 ('', 'no-backup', None, _('do not save backup copies of files'))],
2560 2560 _('hg qgoto [OPTION]... PATCH'))
2561 2561 def goto(ui, repo, patch, **opts):
2562 2562 '''push or pop patches until named patch is at top of stack
2563 2563
2564 2564 Returns 0 on success.'''
2565 2565 opts = fixkeepchangesopts(ui, opts)
2566 2566 q = repo.mq
2567 2567 patch = q.lookup(patch)
2568 2568 nobackup = opts.get('no_backup')
2569 2569 keepchanges = opts.get('keep_changes')
2570 2570 if q.isapplied(patch):
2571 2571 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2572 2572 keepchanges=keepchanges)
2573 2573 else:
2574 2574 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2575 2575 keepchanges=keepchanges)
2576 2576 q.savedirty()
2577 2577 return ret
2578 2578
2579 2579 @command("qguard",
2580 2580 [('l', 'list', None, _('list all patches and guards')),
2581 2581 ('n', 'none', None, _('drop all guards'))],
2582 2582 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2583 2583 def guard(ui, repo, *args, **opts):
2584 2584 '''set or print guards for a patch
2585 2585
2586 2586 Guards control whether a patch can be pushed. A patch with no
2587 2587 guards is always pushed. A patch with a positive guard ("+foo") is
2588 2588 pushed only if the :hg:`qselect` command has activated it. A patch with
2589 2589 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2590 2590 has activated it.
2591 2591
2592 2592 With no arguments, print the currently active guards.
2593 2593 With arguments, set guards for the named patch.
2594 2594
2595 2595 .. note::
2596 2596 Specifying negative guards now requires '--'.
2597 2597
2598 2598 To set guards on another patch::
2599 2599
2600 2600 hg qguard other.patch -- +2.6.17 -stable
2601 2601
2602 2602 Returns 0 on success.
2603 2603 '''
2604 2604 def status(idx):
2605 2605 guards = q.seriesguards[idx] or ['unguarded']
2606 2606 if q.series[idx] in applied:
2607 2607 state = 'applied'
2608 2608 elif q.pushable(idx)[0]:
2609 2609 state = 'unapplied'
2610 2610 else:
2611 2611 state = 'guarded'
2612 2612 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2613 2613 ui.write('%s: ' % ui.label(q.series[idx], label))
2614 2614
2615 2615 for i, guard in enumerate(guards):
2616 2616 if guard.startswith('+'):
2617 2617 ui.write(guard, label='qguard.positive')
2618 2618 elif guard.startswith('-'):
2619 2619 ui.write(guard, label='qguard.negative')
2620 2620 else:
2621 2621 ui.write(guard, label='qguard.unguarded')
2622 2622 if i != len(guards) - 1:
2623 2623 ui.write(' ')
2624 2624 ui.write('\n')
2625 2625 q = repo.mq
2626 2626 applied = set(p.name for p in q.applied)
2627 2627 patch = None
2628 2628 args = list(args)
2629 2629 if opts.get('list'):
2630 2630 if args or opts.get('none'):
2631 2631 raise util.Abort(_('cannot mix -l/--list with options or '
2632 2632 'arguments'))
2633 2633 for i in xrange(len(q.series)):
2634 2634 status(i)
2635 2635 return
2636 2636 if not args or args[0][0:1] in '-+':
2637 2637 if not q.applied:
2638 2638 raise util.Abort(_('no patches applied'))
2639 2639 patch = q.applied[-1].name
2640 2640 if patch is None and args[0][0:1] not in '-+':
2641 2641 patch = args.pop(0)
2642 2642 if patch is None:
2643 2643 raise util.Abort(_('no patch to work with'))
2644 2644 if args or opts.get('none'):
2645 2645 idx = q.findseries(patch)
2646 2646 if idx is None:
2647 2647 raise util.Abort(_('no patch named %s') % patch)
2648 2648 q.setguards(idx, args)
2649 2649 q.savedirty()
2650 2650 else:
2651 2651 status(q.series.index(q.lookup(patch)))
2652 2652
2653 2653 @command("qheader", [], _('hg qheader [PATCH]'))
2654 2654 def header(ui, repo, patch=None):
2655 2655 """print the header of the topmost or specified patch
2656 2656
2657 2657 Returns 0 on success."""
2658 2658 q = repo.mq
2659 2659
2660 2660 if patch:
2661 2661 patch = q.lookup(patch)
2662 2662 else:
2663 2663 if not q.applied:
2664 2664 ui.write(_('no patches applied\n'))
2665 2665 return 1
2666 2666 patch = q.lookup('qtip')
2667 2667 ph = patchheader(q.join(patch), q.plainmode)
2668 2668
2669 2669 ui.write('\n'.join(ph.message) + '\n')
2670 2670
2671 2671 def lastsavename(path):
2672 2672 (directory, base) = os.path.split(path)
2673 2673 names = os.listdir(directory)
2674 2674 namere = re.compile("%s.([0-9]+)" % base)
2675 2675 maxindex = None
2676 2676 maxname = None
2677 2677 for f in names:
2678 2678 m = namere.match(f)
2679 2679 if m:
2680 2680 index = int(m.group(1))
2681 2681 if maxindex is None or index > maxindex:
2682 2682 maxindex = index
2683 2683 maxname = f
2684 2684 if maxname:
2685 2685 return (os.path.join(directory, maxname), maxindex)
2686 2686 return (None, None)
2687 2687
2688 2688 def savename(path):
2689 2689 (last, index) = lastsavename(path)
2690 2690 if last is None:
2691 2691 index = 0
2692 2692 newpath = path + ".%d" % (index + 1)
2693 2693 return newpath
2694 2694
2695 2695 @command("^qpush",
2696 2696 [('', 'keep-changes', None,
2697 2697 _('tolerate non-conflicting local changes')),
2698 2698 ('f', 'force', None, _('apply on top of local changes')),
2699 2699 ('e', 'exact', None,
2700 2700 _('apply the target patch to its recorded parent')),
2701 2701 ('l', 'list', None, _('list patch name in commit text')),
2702 2702 ('a', 'all', None, _('apply all patches')),
2703 2703 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2704 2704 ('n', 'name', '',
2705 2705 _('merge queue name (DEPRECATED)'), _('NAME')),
2706 2706 ('', 'move', None,
2707 2707 _('reorder patch series and apply only the patch')),
2708 2708 ('', 'no-backup', None, _('do not save backup copies of files'))],
2709 2709 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2710 2710 def push(ui, repo, patch=None, **opts):
2711 2711 """push the next patch onto the stack
2712 2712
2713 2713 By default, abort if the working directory contains uncommitted
2714 2714 changes. With --keep-changes, abort only if the uncommitted files
2715 2715 overlap with patched files. With -f/--force, backup and patch over
2716 2716 uncommitted changes.
2717 2717
2718 2718 Return 0 on success.
2719 2719 """
2720 2720 q = repo.mq
2721 2721 mergeq = None
2722 2722
2723 2723 opts = fixkeepchangesopts(ui, opts)
2724 2724 if opts.get('merge'):
2725 2725 if opts.get('name'):
2726 2726 newpath = repo.join(opts.get('name'))
2727 2727 else:
2728 2728 newpath, i = lastsavename(q.path)
2729 2729 if not newpath:
2730 2730 ui.warn(_("no saved queues found, please use -n\n"))
2731 2731 return 1
2732 2732 mergeq = queue(ui, repo.path, newpath)
2733 2733 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2734 2734 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2735 2735 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2736 2736 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2737 2737 keepchanges=opts.get('keep_changes'))
2738 2738 return ret
2739 2739
2740 2740 @command("^qpop",
2741 2741 [('a', 'all', None, _('pop all patches')),
2742 2742 ('n', 'name', '',
2743 2743 _('queue name to pop (DEPRECATED)'), _('NAME')),
2744 2744 ('', 'keep-changes', None,
2745 2745 _('tolerate non-conflicting local changes')),
2746 2746 ('f', 'force', None, _('forget any local changes to patched files')),
2747 2747 ('', 'no-backup', None, _('do not save backup copies of files'))],
2748 2748 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2749 2749 def pop(ui, repo, patch=None, **opts):
2750 2750 """pop the current patch off the stack
2751 2751
2752 2752 Without argument, pops off the top of the patch stack. If given a
2753 2753 patch name, keeps popping off patches until the named patch is at
2754 2754 the top of the stack.
2755 2755
2756 2756 By default, abort if the working directory contains uncommitted
2757 2757 changes. With --keep-changes, abort only if the uncommitted files
2758 2758 overlap with patched files. With -f/--force, backup and discard
2759 2759 changes made to such files.
2760 2760
2761 2761 Return 0 on success.
2762 2762 """
2763 2763 opts = fixkeepchangesopts(ui, opts)
2764 2764 localupdate = True
2765 2765 if opts.get('name'):
2766 2766 q = queue(ui, repo.path, repo.join(opts.get('name')))
2767 2767 ui.warn(_('using patch queue: %s\n') % q.path)
2768 2768 localupdate = False
2769 2769 else:
2770 2770 q = repo.mq
2771 2771 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2772 2772 all=opts.get('all'), nobackup=opts.get('no_backup'),
2773 2773 keepchanges=opts.get('keep_changes'))
2774 2774 q.savedirty()
2775 2775 return ret
2776 2776
2777 2777 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2778 2778 def rename(ui, repo, patch, name=None, **opts):
2779 2779 """rename a patch
2780 2780
2781 2781 With one argument, renames the current patch to PATCH1.
2782 2782 With two arguments, renames PATCH1 to PATCH2.
2783 2783
2784 2784 Returns 0 on success."""
2785 2785 q = repo.mq
2786 2786 if not name:
2787 2787 name = patch
2788 2788 patch = None
2789 2789
2790 2790 if patch:
2791 2791 patch = q.lookup(patch)
2792 2792 else:
2793 2793 if not q.applied:
2794 2794 ui.write(_('no patches applied\n'))
2795 2795 return
2796 2796 patch = q.lookup('qtip')
2797 2797 absdest = q.join(name)
2798 2798 if os.path.isdir(absdest):
2799 2799 name = normname(os.path.join(name, os.path.basename(patch)))
2800 2800 absdest = q.join(name)
2801 2801 q.checkpatchname(name)
2802 2802
2803 2803 ui.note(_('renaming %s to %s\n') % (patch, name))
2804 2804 i = q.findseries(patch)
2805 2805 guards = q.guard_re.findall(q.fullseries[i])
2806 2806 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2807 2807 q.parseseries()
2808 2808 q.seriesdirty = True
2809 2809
2810 2810 info = q.isapplied(patch)
2811 2811 if info:
2812 2812 q.applied[info[0]] = statusentry(info[1], name)
2813 2813 q.applieddirty = True
2814 2814
2815 2815 destdir = os.path.dirname(absdest)
2816 2816 if not os.path.isdir(destdir):
2817 2817 os.makedirs(destdir)
2818 2818 util.rename(q.join(patch), absdest)
2819 2819 r = q.qrepo()
2820 2820 if r and patch in r.dirstate:
2821 2821 wctx = r[None]
2822 2822 wlock = r.wlock()
2823 2823 try:
2824 2824 if r.dirstate[patch] == 'a':
2825 2825 r.dirstate.drop(patch)
2826 2826 r.dirstate.add(name)
2827 2827 else:
2828 2828 wctx.copy(patch, name)
2829 2829 wctx.forget([patch])
2830 2830 finally:
2831 2831 wlock.release()
2832 2832
2833 2833 q.savedirty()
2834 2834
2835 2835 @command("qrestore",
2836 2836 [('d', 'delete', None, _('delete save entry')),
2837 2837 ('u', 'update', None, _('update queue working directory'))],
2838 2838 _('hg qrestore [-d] [-u] REV'))
2839 2839 def restore(ui, repo, rev, **opts):
2840 2840 """restore the queue state saved by a revision (DEPRECATED)
2841 2841
2842 2842 This command is deprecated, use :hg:`rebase` instead."""
2843 2843 rev = repo.lookup(rev)
2844 2844 q = repo.mq
2845 2845 q.restore(repo, rev, delete=opts.get('delete'),
2846 2846 qupdate=opts.get('update'))
2847 2847 q.savedirty()
2848 2848 return 0
2849 2849
2850 2850 @command("qsave",
2851 2851 [('c', 'copy', None, _('copy patch directory')),
2852 2852 ('n', 'name', '',
2853 2853 _('copy directory name'), _('NAME')),
2854 2854 ('e', 'empty', None, _('clear queue status file')),
2855 2855 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2856 2856 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2857 2857 def save(ui, repo, **opts):
2858 2858 """save current queue state (DEPRECATED)
2859 2859
2860 2860 This command is deprecated, use :hg:`rebase` instead."""
2861 2861 q = repo.mq
2862 2862 message = cmdutil.logmessage(ui, opts)
2863 2863 ret = q.save(repo, msg=message)
2864 2864 if ret:
2865 2865 return ret
2866 2866 q.savedirty() # save to .hg/patches before copying
2867 2867 if opts.get('copy'):
2868 2868 path = q.path
2869 2869 if opts.get('name'):
2870 2870 newpath = os.path.join(q.basepath, opts.get('name'))
2871 2871 if os.path.exists(newpath):
2872 2872 if not os.path.isdir(newpath):
2873 2873 raise util.Abort(_('destination %s exists and is not '
2874 2874 'a directory') % newpath)
2875 2875 if not opts.get('force'):
2876 2876 raise util.Abort(_('destination %s exists, '
2877 2877 'use -f to force') % newpath)
2878 2878 else:
2879 2879 newpath = savename(path)
2880 2880 ui.warn(_("copy %s to %s\n") % (path, newpath))
2881 2881 util.copyfiles(path, newpath)
2882 2882 if opts.get('empty'):
2883 2883 del q.applied[:]
2884 2884 q.applieddirty = True
2885 2885 q.savedirty()
2886 2886 return 0
2887 2887
2888 2888 @command("strip",
2889 2889 [
2890 2890 ('r', 'rev', [], _('strip specified revision (optional, '
2891 2891 'can specify revisions without this '
2892 2892 'option)'), _('REV')),
2893 2893 ('f', 'force', None, _('force removal of changesets, discard '
2894 2894 'uncommitted changes (no backup)')),
2895 2895 ('b', 'backup', None, _('bundle only changesets with local revision'
2896 2896 ' number greater than REV which are not'
2897 2897 ' descendants of REV (DEPRECATED)')),
2898 2898 ('', 'no-backup', None, _('no backups')),
2899 2899 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
2900 2900 ('n', '', None, _('ignored (DEPRECATED)')),
2901 2901 ('k', 'keep', None, _("do not modify working copy during strip")),
2902 2902 ('B', 'bookmark', '', _("remove revs only reachable from given"
2903 2903 " bookmark"))],
2904 2904 _('hg strip [-k] [-f] [-n] [-B bookmark] [-r] REV...'))
2905 2905 def strip(ui, repo, *revs, **opts):
2906 2906 """strip changesets and all their descendants from the repository
2907 2907
2908 2908 The strip command removes the specified changesets and all their
2909 2909 descendants. If the working directory has uncommitted changes, the
2910 2910 operation is aborted unless the --force flag is supplied, in which
2911 2911 case changes will be discarded.
2912 2912
2913 2913 If a parent of the working directory is stripped, then the working
2914 2914 directory will automatically be updated to the most recent
2915 2915 available ancestor of the stripped parent after the operation
2916 2916 completes.
2917 2917
2918 2918 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2919 2919 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2920 2920 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2921 2921 where BUNDLE is the bundle file created by the strip. Note that
2922 2922 the local revision numbers will in general be different after the
2923 2923 restore.
2924 2924
2925 2925 Use the --no-backup option to discard the backup bundle once the
2926 2926 operation completes.
2927 2927
2928 Strip is not a history-rewriting operation and can be used on
2929 changesets in the public phase. But if the stripped changesets have
2930 been pushed to a remote repository you will likely pull them again.
2931
2928 2932 Return 0 on success.
2929 2933 """
2930 2934 backup = 'all'
2931 2935 if opts.get('backup'):
2932 2936 backup = 'strip'
2933 2937 elif opts.get('no_backup') or opts.get('nobackup'):
2934 2938 backup = 'none'
2935 2939
2936 2940 cl = repo.changelog
2937 2941 revs = list(revs) + opts.get('rev')
2938 2942 revs = set(scmutil.revrange(repo, revs))
2939 2943
2940 2944 if opts.get('bookmark'):
2941 2945 mark = opts.get('bookmark')
2942 2946 marks = repo._bookmarks
2943 2947 if mark not in marks:
2944 2948 raise util.Abort(_("bookmark '%s' not found") % mark)
2945 2949
2946 2950 # If the requested bookmark is not the only one pointing to a
2947 2951 # a revision we have to only delete the bookmark and not strip
2948 2952 # anything. revsets cannot detect that case.
2949 2953 uniquebm = True
2950 2954 for m, n in marks.iteritems():
2951 2955 if m != mark and n == repo[mark].node():
2952 2956 uniquebm = False
2953 2957 break
2954 2958 if uniquebm:
2955 2959 rsrevs = repo.revs("ancestors(bookmark(%s)) - "
2956 2960 "ancestors(head() and not bookmark(%s)) - "
2957 2961 "ancestors(bookmark() and not bookmark(%s))",
2958 2962 mark, mark, mark)
2959 2963 revs.update(set(rsrevs))
2960 2964 if not revs:
2961 2965 del marks[mark]
2962 2966 repo._writebookmarks(mark)
2963 2967 ui.write(_("bookmark '%s' deleted\n") % mark)
2964 2968
2965 2969 if not revs:
2966 2970 raise util.Abort(_('empty revision set'))
2967 2971
2968 2972 descendants = set(cl.descendants(revs))
2969 2973 strippedrevs = revs.union(descendants)
2970 2974 roots = revs.difference(descendants)
2971 2975
2972 2976 update = False
2973 2977 # if one of the wdir parent is stripped we'll need
2974 2978 # to update away to an earlier revision
2975 2979 for p in repo.dirstate.parents():
2976 2980 if p != nullid and cl.rev(p) in strippedrevs:
2977 2981 update = True
2978 2982 break
2979 2983
2980 2984 rootnodes = set(cl.node(r) for r in roots)
2981 2985
2982 2986 q = repo.mq
2983 2987 if q.applied:
2984 2988 # refresh queue state if we're about to strip
2985 2989 # applied patches
2986 2990 if cl.rev(repo.lookup('qtip')) in strippedrevs:
2987 2991 q.applieddirty = True
2988 2992 start = 0
2989 2993 end = len(q.applied)
2990 2994 for i, statusentry in enumerate(q.applied):
2991 2995 if statusentry.node in rootnodes:
2992 2996 # if one of the stripped roots is an applied
2993 2997 # patch, only part of the queue is stripped
2994 2998 start = i
2995 2999 break
2996 3000 del q.applied[start:end]
2997 3001 q.savedirty()
2998 3002
2999 3003 revs = list(rootnodes)
3000 3004 if update and opts.get('keep'):
3001 3005 wlock = repo.wlock()
3002 3006 try:
3003 3007 urev = repo.mq.qparents(repo, revs[0])
3004 3008 repo.dirstate.rebuild(urev, repo[urev].manifest())
3005 3009 repo.dirstate.write()
3006 3010 update = False
3007 3011 finally:
3008 3012 wlock.release()
3009 3013
3010 3014 if opts.get('bookmark'):
3011 3015 del marks[mark]
3012 3016 repo._writebookmarks(marks)
3013 3017 ui.write(_("bookmark '%s' deleted\n") % mark)
3014 3018
3015 3019 repo.mq.strip(repo, revs, backup=backup, update=update,
3016 3020 force=opts.get('force'))
3017 3021
3018 3022 return 0
3019 3023
3020 3024 @command("qselect",
3021 3025 [('n', 'none', None, _('disable all guards')),
3022 3026 ('s', 'series', None, _('list all guards in series file')),
3023 3027 ('', 'pop', None, _('pop to before first guarded applied patch')),
3024 3028 ('', 'reapply', None, _('pop, then reapply patches'))],
3025 3029 _('hg qselect [OPTION]... [GUARD]...'))
3026 3030 def select(ui, repo, *args, **opts):
3027 3031 '''set or print guarded patches to push
3028 3032
3029 3033 Use the :hg:`qguard` command to set or print guards on patch, then use
3030 3034 qselect to tell mq which guards to use. A patch will be pushed if
3031 3035 it has no guards or any positive guards match the currently
3032 3036 selected guard, but will not be pushed if any negative guards
3033 3037 match the current guard. For example::
3034 3038
3035 3039 qguard foo.patch -- -stable (negative guard)
3036 3040 qguard bar.patch +stable (positive guard)
3037 3041 qselect stable
3038 3042
3039 3043 This activates the "stable" guard. mq will skip foo.patch (because
3040 3044 it has a negative match) but push bar.patch (because it has a
3041 3045 positive match).
3042 3046
3043 3047 With no arguments, prints the currently active guards.
3044 3048 With one argument, sets the active guard.
3045 3049
3046 3050 Use -n/--none to deactivate guards (no other arguments needed).
3047 3051 When no guards are active, patches with positive guards are
3048 3052 skipped and patches with negative guards are pushed.
3049 3053
3050 3054 qselect can change the guards on applied patches. It does not pop
3051 3055 guarded patches by default. Use --pop to pop back to the last
3052 3056 applied patch that is not guarded. Use --reapply (which implies
3053 3057 --pop) to push back to the current patch afterwards, but skip
3054 3058 guarded patches.
3055 3059
3056 3060 Use -s/--series to print a list of all guards in the series file
3057 3061 (no other arguments needed). Use -v for more information.
3058 3062
3059 3063 Returns 0 on success.'''
3060 3064
3061 3065 q = repo.mq
3062 3066 guards = q.active()
3063 3067 if args or opts.get('none'):
3064 3068 old_unapplied = q.unapplied(repo)
3065 3069 old_guarded = [i for i in xrange(len(q.applied)) if
3066 3070 not q.pushable(i)[0]]
3067 3071 q.setactive(args)
3068 3072 q.savedirty()
3069 3073 if not args:
3070 3074 ui.status(_('guards deactivated\n'))
3071 3075 if not opts.get('pop') and not opts.get('reapply'):
3072 3076 unapplied = q.unapplied(repo)
3073 3077 guarded = [i for i in xrange(len(q.applied))
3074 3078 if not q.pushable(i)[0]]
3075 3079 if len(unapplied) != len(old_unapplied):
3076 3080 ui.status(_('number of unguarded, unapplied patches has '
3077 3081 'changed from %d to %d\n') %
3078 3082 (len(old_unapplied), len(unapplied)))
3079 3083 if len(guarded) != len(old_guarded):
3080 3084 ui.status(_('number of guarded, applied patches has changed '
3081 3085 'from %d to %d\n') %
3082 3086 (len(old_guarded), len(guarded)))
3083 3087 elif opts.get('series'):
3084 3088 guards = {}
3085 3089 noguards = 0
3086 3090 for gs in q.seriesguards:
3087 3091 if not gs:
3088 3092 noguards += 1
3089 3093 for g in gs:
3090 3094 guards.setdefault(g, 0)
3091 3095 guards[g] += 1
3092 3096 if ui.verbose:
3093 3097 guards['NONE'] = noguards
3094 3098 guards = guards.items()
3095 3099 guards.sort(key=lambda x: x[0][1:])
3096 3100 if guards:
3097 3101 ui.note(_('guards in series file:\n'))
3098 3102 for guard, count in guards:
3099 3103 ui.note('%2d ' % count)
3100 3104 ui.write(guard, '\n')
3101 3105 else:
3102 3106 ui.note(_('no guards in series file\n'))
3103 3107 else:
3104 3108 if guards:
3105 3109 ui.note(_('active guards:\n'))
3106 3110 for g in guards:
3107 3111 ui.write(g, '\n')
3108 3112 else:
3109 3113 ui.write(_('no active guards\n'))
3110 3114 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
3111 3115 popped = False
3112 3116 if opts.get('pop') or opts.get('reapply'):
3113 3117 for i in xrange(len(q.applied)):
3114 3118 pushable, reason = q.pushable(i)
3115 3119 if not pushable:
3116 3120 ui.status(_('popping guarded patches\n'))
3117 3121 popped = True
3118 3122 if i == 0:
3119 3123 q.pop(repo, all=True)
3120 3124 else:
3121 3125 q.pop(repo, str(i - 1))
3122 3126 break
3123 3127 if popped:
3124 3128 try:
3125 3129 if reapply:
3126 3130 ui.status(_('reapplying unguarded patches\n'))
3127 3131 q.push(repo, reapply)
3128 3132 finally:
3129 3133 q.savedirty()
3130 3134
3131 3135 @command("qfinish",
3132 3136 [('a', 'applied', None, _('finish all applied changesets'))],
3133 3137 _('hg qfinish [-a] [REV]...'))
3134 3138 def finish(ui, repo, *revrange, **opts):
3135 3139 """move applied patches into repository history
3136 3140
3137 3141 Finishes the specified revisions (corresponding to applied
3138 3142 patches) by moving them out of mq control into regular repository
3139 3143 history.
3140 3144
3141 3145 Accepts a revision range or the -a/--applied option. If --applied
3142 3146 is specified, all applied mq revisions are removed from mq
3143 3147 control. Otherwise, the given revisions must be at the base of the
3144 3148 stack of applied patches.
3145 3149
3146 3150 This can be especially useful if your changes have been applied to
3147 3151 an upstream repository, or if you are about to push your changes
3148 3152 to upstream.
3149 3153
3150 3154 Returns 0 on success.
3151 3155 """
3152 3156 if not opts.get('applied') and not revrange:
3153 3157 raise util.Abort(_('no revisions specified'))
3154 3158 elif opts.get('applied'):
3155 3159 revrange = ('qbase::qtip',) + revrange
3156 3160
3157 3161 q = repo.mq
3158 3162 if not q.applied:
3159 3163 ui.status(_('no patches applied\n'))
3160 3164 return 0
3161 3165
3162 3166 revs = scmutil.revrange(repo, revrange)
3163 3167 if repo['.'].rev() in revs and repo[None].files():
3164 3168 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3165 3169 # queue.finish may changes phases but leave the responsability to lock the
3166 3170 # repo to the caller to avoid deadlock with wlock. This command code is
3167 3171 # responsability for this locking.
3168 3172 lock = repo.lock()
3169 3173 try:
3170 3174 q.finish(repo, revs)
3171 3175 q.savedirty()
3172 3176 finally:
3173 3177 lock.release()
3174 3178 return 0
3175 3179
3176 3180 @command("qqueue",
3177 3181 [('l', 'list', False, _('list all available queues')),
3178 3182 ('', 'active', False, _('print name of active queue')),
3179 3183 ('c', 'create', False, _('create new queue')),
3180 3184 ('', 'rename', False, _('rename active queue')),
3181 3185 ('', 'delete', False, _('delete reference to queue')),
3182 3186 ('', 'purge', False, _('delete queue, and remove patch dir')),
3183 3187 ],
3184 3188 _('[OPTION] [QUEUE]'))
3185 3189 def qqueue(ui, repo, name=None, **opts):
3186 3190 '''manage multiple patch queues
3187 3191
3188 3192 Supports switching between different patch queues, as well as creating
3189 3193 new patch queues and deleting existing ones.
3190 3194
3191 3195 Omitting a queue name or specifying -l/--list will show you the registered
3192 3196 queues - by default the "normal" patches queue is registered. The currently
3193 3197 active queue will be marked with "(active)". Specifying --active will print
3194 3198 only the name of the active queue.
3195 3199
3196 3200 To create a new queue, use -c/--create. The queue is automatically made
3197 3201 active, except in the case where there are applied patches from the
3198 3202 currently active queue in the repository. Then the queue will only be
3199 3203 created and switching will fail.
3200 3204
3201 3205 To delete an existing queue, use --delete. You cannot delete the currently
3202 3206 active queue.
3203 3207
3204 3208 Returns 0 on success.
3205 3209 '''
3206 3210 q = repo.mq
3207 3211 _defaultqueue = 'patches'
3208 3212 _allqueues = 'patches.queues'
3209 3213 _activequeue = 'patches.queue'
3210 3214
3211 3215 def _getcurrent():
3212 3216 cur = os.path.basename(q.path)
3213 3217 if cur.startswith('patches-'):
3214 3218 cur = cur[8:]
3215 3219 return cur
3216 3220
3217 3221 def _noqueues():
3218 3222 try:
3219 3223 fh = repo.opener(_allqueues, 'r')
3220 3224 fh.close()
3221 3225 except IOError:
3222 3226 return True
3223 3227
3224 3228 return False
3225 3229
3226 3230 def _getqueues():
3227 3231 current = _getcurrent()
3228 3232
3229 3233 try:
3230 3234 fh = repo.opener(_allqueues, 'r')
3231 3235 queues = [queue.strip() for queue in fh if queue.strip()]
3232 3236 fh.close()
3233 3237 if current not in queues:
3234 3238 queues.append(current)
3235 3239 except IOError:
3236 3240 queues = [_defaultqueue]
3237 3241
3238 3242 return sorted(queues)
3239 3243
3240 3244 def _setactive(name):
3241 3245 if q.applied:
3242 3246 raise util.Abort(_('patches applied - cannot set new queue active'))
3243 3247 _setactivenocheck(name)
3244 3248
3245 3249 def _setactivenocheck(name):
3246 3250 fh = repo.opener(_activequeue, 'w')
3247 3251 if name != 'patches':
3248 3252 fh.write(name)
3249 3253 fh.close()
3250 3254
3251 3255 def _addqueue(name):
3252 3256 fh = repo.opener(_allqueues, 'a')
3253 3257 fh.write('%s\n' % (name,))
3254 3258 fh.close()
3255 3259
3256 3260 def _queuedir(name):
3257 3261 if name == 'patches':
3258 3262 return repo.join('patches')
3259 3263 else:
3260 3264 return repo.join('patches-' + name)
3261 3265
3262 3266 def _validname(name):
3263 3267 for n in name:
3264 3268 if n in ':\\/.':
3265 3269 return False
3266 3270 return True
3267 3271
3268 3272 def _delete(name):
3269 3273 if name not in existing:
3270 3274 raise util.Abort(_('cannot delete queue that does not exist'))
3271 3275
3272 3276 current = _getcurrent()
3273 3277
3274 3278 if name == current:
3275 3279 raise util.Abort(_('cannot delete currently active queue'))
3276 3280
3277 3281 fh = repo.opener('patches.queues.new', 'w')
3278 3282 for queue in existing:
3279 3283 if queue == name:
3280 3284 continue
3281 3285 fh.write('%s\n' % (queue,))
3282 3286 fh.close()
3283 3287 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3284 3288
3285 3289 if not name or opts.get('list') or opts.get('active'):
3286 3290 current = _getcurrent()
3287 3291 if opts.get('active'):
3288 3292 ui.write('%s\n' % (current,))
3289 3293 return
3290 3294 for queue in _getqueues():
3291 3295 ui.write('%s' % (queue,))
3292 3296 if queue == current and not ui.quiet:
3293 3297 ui.write(_(' (active)\n'))
3294 3298 else:
3295 3299 ui.write('\n')
3296 3300 return
3297 3301
3298 3302 if not _validname(name):
3299 3303 raise util.Abort(
3300 3304 _('invalid queue name, may not contain the characters ":\\/."'))
3301 3305
3302 3306 existing = _getqueues()
3303 3307
3304 3308 if opts.get('create'):
3305 3309 if name in existing:
3306 3310 raise util.Abort(_('queue "%s" already exists') % name)
3307 3311 if _noqueues():
3308 3312 _addqueue(_defaultqueue)
3309 3313 _addqueue(name)
3310 3314 _setactive(name)
3311 3315 elif opts.get('rename'):
3312 3316 current = _getcurrent()
3313 3317 if name == current:
3314 3318 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3315 3319 if name in existing:
3316 3320 raise util.Abort(_('queue "%s" already exists') % name)
3317 3321
3318 3322 olddir = _queuedir(current)
3319 3323 newdir = _queuedir(name)
3320 3324
3321 3325 if os.path.exists(newdir):
3322 3326 raise util.Abort(_('non-queue directory "%s" already exists') %
3323 3327 newdir)
3324 3328
3325 3329 fh = repo.opener('patches.queues.new', 'w')
3326 3330 for queue in existing:
3327 3331 if queue == current:
3328 3332 fh.write('%s\n' % (name,))
3329 3333 if os.path.exists(olddir):
3330 3334 util.rename(olddir, newdir)
3331 3335 else:
3332 3336 fh.write('%s\n' % (queue,))
3333 3337 fh.close()
3334 3338 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3335 3339 _setactivenocheck(name)
3336 3340 elif opts.get('delete'):
3337 3341 _delete(name)
3338 3342 elif opts.get('purge'):
3339 3343 if name in existing:
3340 3344 _delete(name)
3341 3345 qdir = _queuedir(name)
3342 3346 if os.path.exists(qdir):
3343 3347 shutil.rmtree(qdir)
3344 3348 else:
3345 3349 if name not in existing:
3346 3350 raise util.Abort(_('use --create to create a new queue'))
3347 3351 _setactive(name)
3348 3352
3349 3353 def mqphasedefaults(repo, roots):
3350 3354 """callback used to set mq changeset as secret when no phase data exists"""
3351 3355 if repo.mq.applied:
3352 3356 if repo.ui.configbool('mq', 'secret', False):
3353 3357 mqphase = phases.secret
3354 3358 else:
3355 3359 mqphase = phases.draft
3356 3360 qbase = repo[repo.mq.applied[0].node]
3357 3361 roots[mqphase].add(qbase.node())
3358 3362 return roots
3359 3363
3360 3364 def reposetup(ui, repo):
3361 3365 class mqrepo(repo.__class__):
3362 3366 @util.propertycache
3363 3367 def mq(self):
3364 3368 return queue(self.ui, self.path)
3365 3369
3366 3370 def abortifwdirpatched(self, errmsg, force=False):
3367 3371 if self.mq.applied and not force:
3368 3372 parents = self.dirstate.parents()
3369 3373 patches = [s.node for s in self.mq.applied]
3370 3374 if parents[0] in patches or parents[1] in patches:
3371 3375 raise util.Abort(errmsg)
3372 3376
3373 3377 def commit(self, text="", user=None, date=None, match=None,
3374 3378 force=False, editor=False, extra={}):
3375 3379 self.abortifwdirpatched(
3376 3380 _('cannot commit over an applied mq patch'),
3377 3381 force)
3378 3382
3379 3383 return super(mqrepo, self).commit(text, user, date, match, force,
3380 3384 editor, extra)
3381 3385
3382 3386 def checkpush(self, force, revs):
3383 3387 if self.mq.applied and not force:
3384 3388 outapplied = [e.node for e in self.mq.applied]
3385 3389 if revs:
3386 3390 # Assume applied patches have no non-patch descendants and
3387 3391 # are not on remote already. Filtering any changeset not
3388 3392 # pushed.
3389 3393 heads = set(revs)
3390 3394 for node in reversed(outapplied):
3391 3395 if node in heads:
3392 3396 break
3393 3397 else:
3394 3398 outapplied.pop()
3395 3399 # looking for pushed and shared changeset
3396 3400 for node in outapplied:
3397 3401 if repo[node].phase() < phases.secret:
3398 3402 raise util.Abort(_('source has mq patches applied'))
3399 3403 # no non-secret patches pushed
3400 3404 super(mqrepo, self).checkpush(force, revs)
3401 3405
3402 3406 def _findtags(self):
3403 3407 '''augment tags from base class with patch tags'''
3404 3408 result = super(mqrepo, self)._findtags()
3405 3409
3406 3410 q = self.mq
3407 3411 if not q.applied:
3408 3412 return result
3409 3413
3410 3414 mqtags = [(patch.node, patch.name) for patch in q.applied]
3411 3415
3412 3416 try:
3413 3417 self.changelog.rev(mqtags[-1][0])
3414 3418 except error.LookupError:
3415 3419 self.ui.warn(_('mq status file refers to unknown node %s\n')
3416 3420 % short(mqtags[-1][0]))
3417 3421 return result
3418 3422
3419 3423 mqtags.append((mqtags[-1][0], 'qtip'))
3420 3424 mqtags.append((mqtags[0][0], 'qbase'))
3421 3425 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3422 3426 tags = result[0]
3423 3427 for patch in mqtags:
3424 3428 if patch[1] in tags:
3425 3429 self.ui.warn(_('tag %s overrides mq patch of the same '
3426 3430 'name\n') % patch[1])
3427 3431 else:
3428 3432 tags[patch[1]] = patch[0]
3429 3433
3430 3434 return result
3431 3435
3432 3436 def _branchtags(self, partial, lrev):
3433 3437 q = self.mq
3434 3438 cl = self.changelog
3435 3439 qbase = None
3436 3440 if not q.applied:
3437 3441 if getattr(self, '_committingpatch', False):
3438 3442 # Committing a new patch, must be tip
3439 3443 qbase = len(cl) - 1
3440 3444 else:
3441 3445 qbasenode = q.applied[0].node
3442 3446 try:
3443 3447 qbase = cl.rev(qbasenode)
3444 3448 except error.LookupError:
3445 3449 self.ui.warn(_('mq status file refers to unknown node %s\n')
3446 3450 % short(qbasenode))
3447 3451 if qbase is None:
3448 3452 return super(mqrepo, self)._branchtags(partial, lrev)
3449 3453
3450 3454 start = lrev + 1
3451 3455 if start < qbase:
3452 3456 # update the cache (excluding the patches) and save it
3453 3457 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
3454 3458 self._updatebranchcache(partial, ctxgen)
3455 3459 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
3456 3460 start = qbase
3457 3461 # if start = qbase, the cache is as updated as it should be.
3458 3462 # if start > qbase, the cache includes (part of) the patches.
3459 3463 # we might as well use it, but we won't save it.
3460 3464
3461 3465 # update the cache up to the tip
3462 3466 ctxgen = (self[r] for r in xrange(start, len(cl)))
3463 3467 self._updatebranchcache(partial, ctxgen)
3464 3468
3465 3469 return partial
3466 3470
3467 3471 if repo.local():
3468 3472 repo.__class__ = mqrepo
3469 3473
3470 3474 repo._phasedefaults.append(mqphasedefaults)
3471 3475
3472 3476 def mqimport(orig, ui, repo, *args, **kwargs):
3473 3477 if (util.safehasattr(repo, 'abortifwdirpatched')
3474 3478 and not kwargs.get('no_commit', False)):
3475 3479 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3476 3480 kwargs.get('force'))
3477 3481 return orig(ui, repo, *args, **kwargs)
3478 3482
3479 3483 def mqinit(orig, ui, *args, **kwargs):
3480 3484 mq = kwargs.pop('mq', None)
3481 3485
3482 3486 if not mq:
3483 3487 return orig(ui, *args, **kwargs)
3484 3488
3485 3489 if args:
3486 3490 repopath = args[0]
3487 3491 if not hg.islocal(repopath):
3488 3492 raise util.Abort(_('only a local queue repository '
3489 3493 'may be initialized'))
3490 3494 else:
3491 3495 repopath = cmdutil.findrepo(os.getcwd())
3492 3496 if not repopath:
3493 3497 raise util.Abort(_('there is no Mercurial repository here '
3494 3498 '(.hg not found)'))
3495 3499 repo = hg.repository(ui, repopath)
3496 3500 return qinit(ui, repo, True)
3497 3501
3498 3502 def mqcommand(orig, ui, repo, *args, **kwargs):
3499 3503 """Add --mq option to operate on patch repository instead of main"""
3500 3504
3501 3505 # some commands do not like getting unknown options
3502 3506 mq = kwargs.pop('mq', None)
3503 3507
3504 3508 if not mq:
3505 3509 return orig(ui, repo, *args, **kwargs)
3506 3510
3507 3511 q = repo.mq
3508 3512 r = q.qrepo()
3509 3513 if not r:
3510 3514 raise util.Abort(_('no queue repository'))
3511 3515 return orig(r.ui, r, *args, **kwargs)
3512 3516
3513 3517 def summary(orig, ui, repo, *args, **kwargs):
3514 3518 r = orig(ui, repo, *args, **kwargs)
3515 3519 q = repo.mq
3516 3520 m = []
3517 3521 a, u = len(q.applied), len(q.unapplied(repo))
3518 3522 if a:
3519 3523 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3520 3524 if u:
3521 3525 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3522 3526 if m:
3523 3527 ui.write("mq: %s\n" % ', '.join(m))
3524 3528 else:
3525 3529 ui.note(_("mq: (empty queue)\n"))
3526 3530 return r
3527 3531
3528 3532 def revsetmq(repo, subset, x):
3529 3533 """``mq()``
3530 3534 Changesets managed by MQ.
3531 3535 """
3532 3536 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3533 3537 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3534 3538 return [r for r in subset if r in applied]
3535 3539
3536 3540 def extsetup(ui):
3537 3541 revset.symbols['mq'] = revsetmq
3538 3542
3539 3543 # tell hggettext to extract docstrings from these functions:
3540 3544 i18nfunctions = [revsetmq]
3541 3545
3542 3546 def uisetup(ui):
3543 3547 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3544 3548
3545 3549 extensions.wrapcommand(commands.table, 'import', mqimport)
3546 3550 extensions.wrapcommand(commands.table, 'summary', summary)
3547 3551
3548 3552 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3549 3553 entry[1].extend(mqopt)
3550 3554
3551 3555 nowrap = set(commands.norepo.split(" "))
3552 3556
3553 3557 def dotable(cmdtable):
3554 3558 for cmd in cmdtable.keys():
3555 3559 cmd = cmdutil.parsealiases(cmd)[0]
3556 3560 if cmd in nowrap:
3557 3561 continue
3558 3562 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3559 3563 entry[1].extend(mqopt)
3560 3564
3561 3565 dotable(commands.table)
3562 3566
3563 3567 for extname, extmodule in extensions.extensions():
3564 3568 if extmodule.__file__ != __file__:
3565 3569 dotable(getattr(extmodule, 'cmdtable', {}))
3566 3570
3567 3571
3568 3572 colortable = {'qguard.negative': 'red',
3569 3573 'qguard.positive': 'yellow',
3570 3574 'qguard.unguarded': 'green',
3571 3575 'qseries.applied': 'blue bold underline',
3572 3576 'qseries.guarded': 'black bold',
3573 3577 'qseries.missing': 'red bold',
3574 3578 'qseries.unapplied': 'black bold'}
@@ -1,703 +1,709 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
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
27 27 cmdtable = {}
28 28 command = cmdutil.command(cmdtable)
29 29 testedwith = 'internal'
30 30
31 31 @command('rebase',
32 32 [('s', 'source', '',
33 33 _('rebase from the specified changeset'), _('REV')),
34 34 ('b', 'base', '',
35 35 _('rebase from the base of the specified changeset '
36 36 '(up to greatest common ancestor of base and dest)'),
37 37 _('REV')),
38 38 ('r', 'rev', [],
39 39 _('rebase these revisions'),
40 40 _('REV')),
41 41 ('d', 'dest', '',
42 42 _('rebase onto the specified changeset'), _('REV')),
43 43 ('', 'collapse', False, _('collapse the rebased changesets')),
44 44 ('m', 'message', '',
45 45 _('use text as collapse commit message'), _('TEXT')),
46 46 ('e', 'edit', False, _('invoke editor on commit messages')),
47 47 ('l', 'logfile', '',
48 48 _('read collapse commit message from file'), _('FILE')),
49 49 ('', 'keep', False, _('keep original changesets')),
50 50 ('', 'keepbranches', False, _('keep original branch names')),
51 51 ('D', 'detach', False, _('(DEPRECATED)')),
52 52 ('t', 'tool', '', _('specify merge tool')),
53 53 ('c', 'continue', False, _('continue an interrupted rebase')),
54 54 ('a', 'abort', False, _('abort an interrupted rebase'))] +
55 55 templateopts,
56 56 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
57 57 'hg rebase {-a|-c}'))
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 If you don't specify a destination changeset (``-d/--dest``),
72 72 rebase uses the tipmost head of the current named branch as the
73 73 destination. (The destination changeset is not modified by
74 74 rebasing, but new changesets are added as its descendants.)
75 75
76 76 You can specify which changesets to rebase in two ways: as a
77 77 "source" changeset or as a "base" changeset. Both are shorthand
78 78 for a topologically related set of changesets (the "source
79 79 branch"). If you specify source (``-s/--source``), rebase will
80 80 rebase that changeset and all of its descendants onto dest. If you
81 81 specify base (``-b/--base``), rebase will select ancestors of base
82 82 back to but not including the common ancestor with dest. Thus,
83 83 ``-b`` is less precise but more convenient than ``-s``: you can
84 84 specify any changeset in the source branch, and rebase will select
85 85 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
86 86 uses the parent of the working directory as the base.
87 87
88 88 By default, rebase recreates the changesets in the source branch
89 89 as descendants of dest and then destroys the originals. Use
90 90 ``--keep`` to preserve the original source changesets. Some
91 91 changesets in the source branch (e.g. merges from the destination
92 92 branch) may be dropped if they no longer contribute any change.
93 93
94 94 One result of the rules for selecting the destination changeset
95 95 and source branch is that, unlike ``merge``, rebase will do
96 96 nothing if you are at the latest (tipmost) head of a named branch
97 97 with two heads. You need to explicitly specify source and/or
98 98 destination (or ``update`` to the other head, if it's the head of
99 99 the intended source branch).
100 100
101 101 If a rebase is interrupted to manually resolve a merge, it can be
102 102 continued with --continue/-c or aborted with --abort/-a.
103 103
104 104 Returns 0 on success, 1 if nothing to rebase.
105 105 """
106 106 originalwd = target = None
107 107 external = nullrev
108 108 state = {}
109 109 skipped = set()
110 110 targetancestors = set()
111 111
112 112 editor = None
113 113 if opts.get('edit'):
114 114 editor = cmdutil.commitforceeditor
115 115
116 116 lock = wlock = None
117 117 try:
118 118 wlock = repo.wlock()
119 119 lock = repo.lock()
120 120
121 121 # Validate input and define rebasing points
122 122 destf = opts.get('dest', None)
123 123 srcf = opts.get('source', None)
124 124 basef = opts.get('base', None)
125 125 revf = opts.get('rev', [])
126 126 contf = opts.get('continue')
127 127 abortf = opts.get('abort')
128 128 collapsef = opts.get('collapse', False)
129 129 collapsemsg = cmdutil.logmessage(ui, opts)
130 130 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
131 131 keepf = opts.get('keep', False)
132 132 keepbranchesf = opts.get('keepbranches', False)
133 133 # keepopen is not meant for use on the command line, but by
134 134 # other extensions
135 135 keepopen = opts.get('keepopen', False)
136 136
137 137 if collapsemsg and not collapsef:
138 138 raise util.Abort(
139 139 _('message can only be specified with collapse'))
140 140
141 141 if contf or abortf:
142 142 if contf and abortf:
143 143 raise util.Abort(_('cannot use both abort and continue'))
144 144 if collapsef:
145 145 raise util.Abort(
146 146 _('cannot use collapse with continue or abort'))
147 147 if srcf or basef or destf:
148 148 raise util.Abort(
149 149 _('abort and continue do not allow specifying revisions'))
150 150 if opts.get('tool', False):
151 151 ui.warn(_('tool option will be ignored\n'))
152 152
153 153 (originalwd, target, state, skipped, collapsef, keepf,
154 154 keepbranchesf, external) = restorestatus(repo)
155 155 if abortf:
156 156 return abort(repo, originalwd, target, state)
157 157 else:
158 158 if srcf and basef:
159 159 raise util.Abort(_('cannot specify both a '
160 160 'source and a base'))
161 161 if revf and basef:
162 162 raise util.Abort(_('cannot specify both a '
163 163 'revision and a base'))
164 164 if revf and srcf:
165 165 raise util.Abort(_('cannot specify both a '
166 166 'revision and a source'))
167 167
168 168 cmdutil.bailifchanged(repo)
169 169
170 170 if not destf:
171 171 # Destination defaults to the latest revision in the
172 172 # current branch
173 173 branch = repo[None].branch()
174 174 dest = repo[branch]
175 175 else:
176 176 dest = scmutil.revsingle(repo, destf)
177 177
178 178 if revf:
179 179 rebaseset = repo.revs('%lr', revf)
180 180 elif srcf:
181 181 src = scmutil.revrange(repo, [srcf])
182 182 rebaseset = repo.revs('(%ld)::', src)
183 183 else:
184 184 base = scmutil.revrange(repo, [basef or '.'])
185 185 rebaseset = repo.revs(
186 186 '(children(ancestor(%ld, %d)) and ::(%ld))::',
187 187 base, dest, base)
188 188
189 189 if rebaseset:
190 190 root = min(rebaseset)
191 191 else:
192 192 root = None
193 193
194 194 if not rebaseset:
195 195 repo.ui.debug('base is ancestor of destination\n')
196 196 result = None
197 197 elif not keepf and list(repo.revs('first(children(%ld) - %ld)',
198 198 rebaseset, rebaseset)):
199 199 raise util.Abort(
200 200 _("can't remove original changesets with"
201 201 " unrebased descendants"),
202 202 hint=_('use --keep to keep original changesets'))
203 203 elif not keepf and not repo[root].mutable():
204 204 raise util.Abort(_("can't rebase immutable changeset %s")
205 205 % repo[root],
206 206 hint=_('see hg help phases for details'))
207 207 else:
208 208 result = buildstate(repo, dest, rebaseset, collapsef)
209 209
210 210 if not result:
211 211 # Empty state built, nothing to rebase
212 212 ui.status(_('nothing to rebase\n'))
213 213 return 1
214 214 else:
215 215 originalwd, target, state = result
216 216 if collapsef:
217 217 targetancestors = set(repo.changelog.ancestors([target]))
218 218 targetancestors.add(target)
219 219 external = checkexternal(repo, state, targetancestors)
220 220
221 221 if keepbranchesf:
222 222 assert not extrafn, 'cannot use both keepbranches and extrafn'
223 223 def extrafn(ctx, extra):
224 224 extra['branch'] = ctx.branch()
225 225 if collapsef:
226 226 branches = set()
227 227 for rev in state:
228 228 branches.add(repo[rev].branch())
229 229 if len(branches) > 1:
230 230 raise util.Abort(_('cannot collapse multiple named '
231 231 'branches'))
232 232
233 233
234 234 # Rebase
235 235 if not targetancestors:
236 236 targetancestors = set(repo.changelog.ancestors([target]))
237 237 targetancestors.add(target)
238 238
239 239 # Keep track of the current bookmarks in order to reset them later
240 240 currentbookmarks = repo._bookmarks.copy()
241 activebookmark = repo._bookmarkcurrent
242 if activebookmark:
243 bookmarks.unsetcurrent(repo)
241 244
242 245 sortedstate = sorted(state)
243 246 total = len(sortedstate)
244 247 pos = 0
245 248 for rev in sortedstate:
246 249 pos += 1
247 250 if state[rev] == -1:
248 251 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
249 252 _('changesets'), total)
250 253 storestatus(repo, originalwd, target, state, collapsef, keepf,
251 254 keepbranchesf, external)
252 255 p1, p2 = defineparents(repo, rev, target, state,
253 256 targetancestors)
254 257 if len(repo.parents()) == 2:
255 258 repo.ui.debug('resuming interrupted rebase\n')
256 259 else:
257 260 try:
258 261 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
259 262 stats = rebasenode(repo, rev, p1, state, collapsef)
260 263 if stats and stats[3] > 0:
261 264 raise util.Abort(_('unresolved conflicts (see hg '
262 265 'resolve, then hg rebase --continue)'))
263 266 finally:
264 267 ui.setconfig('ui', 'forcemerge', '')
265 268 cmdutil.duplicatecopies(repo, rev, target)
266 269 if not collapsef:
267 270 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
268 271 editor=editor)
269 272 else:
270 273 # Skip commit if we are collapsing
271 274 repo.setparents(repo[p1].node())
272 275 newrev = None
273 276 # Update the state
274 277 if newrev is not None:
275 278 state[rev] = repo[newrev].rev()
276 279 else:
277 280 if not collapsef:
278 281 ui.note(_('no changes, revision %d skipped\n') % rev)
279 282 ui.debug('next revision set to %s\n' % p1)
280 283 skipped.add(rev)
281 284 state[rev] = p1
282 285
283 286 ui.progress(_('rebasing'), None)
284 287 ui.note(_('rebase merging completed\n'))
285 288
286 289 if collapsef and not keepopen:
287 290 p1, p2 = defineparents(repo, min(state), target,
288 291 state, targetancestors)
289 292 if collapsemsg:
290 293 commitmsg = collapsemsg
291 294 else:
292 295 commitmsg = 'Collapsed revision'
293 296 for rebased in state:
294 297 if rebased not in skipped and state[rebased] != nullmerge:
295 298 commitmsg += '\n* %s' % repo[rebased].description()
296 299 commitmsg = ui.edit(commitmsg, repo.ui.username())
297 300 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
298 301 extrafn=extrafn, editor=editor)
299 302
300 303 if 'qtip' in repo.tags():
301 304 updatemq(repo, state, skipped, **opts)
302 305
303 306 if currentbookmarks:
304 307 # Nodeids are needed to reset bookmarks
305 308 nstate = {}
306 309 for k, v in state.iteritems():
307 310 if v != nullmerge:
308 311 nstate[repo[k].node()] = repo[v].node()
309 312
310 313 if not keepf:
311 314 # Remove no more useful revisions
312 315 rebased = [rev for rev in state if state[rev] != nullmerge]
313 316 if rebased:
314 317 if set(repo.changelog.descendants([min(rebased)])) - set(state):
315 318 ui.warn(_("warning: new changesets detected "
316 319 "on source branch, not stripping\n"))
317 320 else:
318 321 # backup the old csets by default
319 322 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
320 323
321 324 if currentbookmarks:
322 325 updatebookmarks(repo, nstate, currentbookmarks, **opts)
323 326
324 327 clearstatus(repo)
325 328 ui.note(_("rebase completed\n"))
326 329 if os.path.exists(repo.sjoin('undo')):
327 330 util.unlinkpath(repo.sjoin('undo'))
328 331 if skipped:
329 332 ui.note(_("%d revisions have been skipped\n") % len(skipped))
333
334 if (activebookmark and
335 repo['tip'].node() == repo._bookmarks[activebookmark]):
336 bookmarks.setcurrent(repo, activebookmark)
337
330 338 finally:
331 339 release(lock, wlock)
332 340
333 341 def checkexternal(repo, state, targetancestors):
334 342 """Check whether one or more external revisions need to be taken in
335 343 consideration. In the latter case, abort.
336 344 """
337 345 external = nullrev
338 346 source = min(state)
339 347 for rev in state:
340 348 if rev == source:
341 349 continue
342 350 # Check externals and fail if there are more than one
343 351 for p in repo[rev].parents():
344 352 if (p.rev() not in state
345 353 and p.rev() not in targetancestors):
346 354 if external != nullrev:
347 355 raise util.Abort(_('unable to collapse, there is more '
348 356 'than one external parent'))
349 357 external = p.rev()
350 358 return external
351 359
352 360 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
353 361 'Commit the changes and store useful information in extra'
354 362 try:
355 363 repo.setparents(repo[p1].node(), repo[p2].node())
356 364 ctx = repo[rev]
357 365 if commitmsg is None:
358 366 commitmsg = ctx.description()
359 367 extra = {'rebase_source': ctx.hex()}
360 368 if extrafn:
361 369 extrafn(ctx, extra)
362 370 # Commit might fail if unresolved files exist
363 371 newrev = repo.commit(text=commitmsg, user=ctx.user(),
364 372 date=ctx.date(), extra=extra, editor=editor)
365 373 repo.dirstate.setbranch(repo[newrev].branch())
366 374 targetphase = max(ctx.phase(), phases.draft)
367 375 # retractboundary doesn't overwrite upper phase inherited from parent
368 376 newnode = repo[newrev].node()
369 377 if newnode:
370 378 phases.retractboundary(repo, targetphase, [newnode])
371 379 return newrev
372 380 except util.Abort:
373 381 # Invalidate the previous setparents
374 382 repo.dirstate.invalidate()
375 383 raise
376 384
377 385 def rebasenode(repo, rev, p1, state, collapse):
378 386 'Rebase a single revision'
379 387 # Merge phase
380 388 # Update to target and merge it with local
381 389 if repo['.'].rev() != repo[p1].rev():
382 390 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
383 391 merge.update(repo, p1, False, True, False)
384 392 else:
385 393 repo.ui.debug(" already in target\n")
386 394 repo.dirstate.write()
387 395 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
388 396 base = None
389 397 if repo[rev].rev() != repo[min(state)].rev():
390 398 base = repo[rev].p1().node()
391 399 # When collapsing in-place, the parent is the common ancestor, we
392 400 # have to allow merging with it.
393 401 return merge.update(repo, rev, True, True, False, base, collapse)
394 402
395 403 def defineparents(repo, rev, target, state, targetancestors):
396 404 'Return the new parent relationship of the revision that will be rebased'
397 405 parents = repo[rev].parents()
398 406 p1 = p2 = nullrev
399 407
400 408 P1n = parents[0].rev()
401 409 if P1n in targetancestors:
402 410 p1 = target
403 411 elif P1n in state:
404 412 if state[P1n] == nullmerge:
405 413 p1 = target
406 414 else:
407 415 p1 = state[P1n]
408 416 else: # P1n external
409 417 p1 = target
410 418 p2 = P1n
411 419
412 420 if len(parents) == 2 and parents[1].rev() not in targetancestors:
413 421 P2n = parents[1].rev()
414 422 # interesting second parent
415 423 if P2n in state:
416 424 if p1 == target: # P1n in targetancestors or external
417 425 p1 = state[P2n]
418 426 else:
419 427 p2 = state[P2n]
420 428 else: # P2n external
421 429 if p2 != nullrev: # P1n external too => rev is a merged revision
422 430 raise util.Abort(_('cannot use revision %d as base, result '
423 431 'would have 3 parents') % rev)
424 432 p2 = P2n
425 433 repo.ui.debug(" future parents are %d and %d\n" %
426 434 (repo[p1].rev(), repo[p2].rev()))
427 435 return p1, p2
428 436
429 437 def isagitpatch(repo, patchname):
430 438 'Return true if the given patch is in git format'
431 439 mqpatch = os.path.join(repo.mq.path, patchname)
432 440 for line in patch.linereader(file(mqpatch, 'rb')):
433 441 if line.startswith('diff --git'):
434 442 return True
435 443 return False
436 444
437 445 def updatemq(repo, state, skipped, **opts):
438 446 'Update rebased mq patches - finalize and then import them'
439 447 mqrebase = {}
440 448 mq = repo.mq
441 449 original_series = mq.fullseries[:]
442 450 skippedpatches = set()
443 451
444 452 for p in mq.applied:
445 453 rev = repo[p.node].rev()
446 454 if rev in state:
447 455 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
448 456 (rev, p.name))
449 457 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
450 458 else:
451 459 # Applied but not rebased, not sure this should happen
452 460 skippedpatches.add(p.name)
453 461
454 462 if mqrebase:
455 463 mq.finish(repo, mqrebase.keys())
456 464
457 465 # We must start import from the newest revision
458 466 for rev in sorted(mqrebase, reverse=True):
459 467 if rev not in skipped:
460 468 name, isgit = mqrebase[rev]
461 469 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
462 470 mq.qimport(repo, (), patchname=name, git=isgit,
463 471 rev=[str(state[rev])])
464 472 else:
465 473 # Rebased and skipped
466 474 skippedpatches.add(mqrebase[rev][0])
467 475
468 476 # Patches were either applied and rebased and imported in
469 477 # order, applied and removed or unapplied. Discard the removed
470 478 # ones while preserving the original series order and guards.
471 479 newseries = [s for s in original_series
472 480 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
473 481 mq.fullseries[:] = newseries
474 482 mq.seriesdirty = True
475 483 mq.savedirty()
476 484
477 485 def updatebookmarks(repo, nstate, originalbookmarks, **opts):
478 486 'Move bookmarks to their correct changesets'
479 current = repo._bookmarkcurrent
480 487 for k, v in originalbookmarks.iteritems():
481 488 if v in nstate:
482 489 if nstate[v] != nullmerge:
483 # reset the pointer if the bookmark was moved incorrectly
484 if k != current:
490 # update the bookmarks for revs that have moved
485 491 repo._bookmarks[k] = nstate[v]
486 492
487 493 bookmarks.write(repo)
488 494
489 495 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
490 496 external):
491 497 'Store the current status to allow recovery'
492 498 f = repo.opener("rebasestate", "w")
493 499 f.write(repo[originalwd].hex() + '\n')
494 500 f.write(repo[target].hex() + '\n')
495 501 f.write(repo[external].hex() + '\n')
496 502 f.write('%d\n' % int(collapse))
497 503 f.write('%d\n' % int(keep))
498 504 f.write('%d\n' % int(keepbranches))
499 505 for d, v in state.iteritems():
500 506 oldrev = repo[d].hex()
501 507 if v != nullmerge:
502 508 newrev = repo[v].hex()
503 509 else:
504 510 newrev = v
505 511 f.write("%s:%s\n" % (oldrev, newrev))
506 512 f.close()
507 513 repo.ui.debug('rebase status stored\n')
508 514
509 515 def clearstatus(repo):
510 516 'Remove the status files'
511 517 if os.path.exists(repo.join("rebasestate")):
512 518 util.unlinkpath(repo.join("rebasestate"))
513 519
514 520 def restorestatus(repo):
515 521 'Restore a previously stored status'
516 522 try:
517 523 target = None
518 524 collapse = False
519 525 external = nullrev
520 526 state = {}
521 527 f = repo.opener("rebasestate")
522 528 for i, l in enumerate(f.read().splitlines()):
523 529 if i == 0:
524 530 originalwd = repo[l].rev()
525 531 elif i == 1:
526 532 target = repo[l].rev()
527 533 elif i == 2:
528 534 external = repo[l].rev()
529 535 elif i == 3:
530 536 collapse = bool(int(l))
531 537 elif i == 4:
532 538 keep = bool(int(l))
533 539 elif i == 5:
534 540 keepbranches = bool(int(l))
535 541 else:
536 542 oldrev, newrev = l.split(':')
537 543 if newrev != str(nullmerge):
538 544 state[repo[oldrev].rev()] = repo[newrev].rev()
539 545 else:
540 546 state[repo[oldrev].rev()] = int(newrev)
541 547 skipped = set()
542 548 # recompute the set of skipped revs
543 549 if not collapse:
544 550 seen = set([target])
545 551 for old, new in sorted(state.items()):
546 552 if new != nullrev and new in seen:
547 553 skipped.add(old)
548 554 seen.add(new)
549 555 repo.ui.debug('computed skipped revs: %s\n' % skipped)
550 556 repo.ui.debug('rebase status resumed\n')
551 557 return (originalwd, target, state, skipped,
552 558 collapse, keep, keepbranches, external)
553 559 except IOError, err:
554 560 if err.errno != errno.ENOENT:
555 561 raise
556 562 raise util.Abort(_('no rebase in progress'))
557 563
558 564 def abort(repo, originalwd, target, state):
559 565 'Restore the repository to its original state'
560 566 dstates = [s for s in state.values() if s != nullrev]
561 567 immutable = [d for d in dstates if not repo[d].mutable()]
562 568 if immutable:
563 569 raise util.Abort(_("can't abort rebase due to immutable changesets %s")
564 570 % ', '.join(str(repo[r]) for r in immutable),
565 571 hint=_('see hg help phases for details'))
566 572
567 573 descendants = set()
568 574 if dstates:
569 575 descendants = set(repo.changelog.descendants(dstates))
570 576 if descendants - set(dstates):
571 577 repo.ui.warn(_("warning: new changesets detected on target branch, "
572 578 "can't abort\n"))
573 579 return -1
574 580 else:
575 581 # Strip from the first rebased revision
576 582 merge.update(repo, repo[originalwd].rev(), False, True, False)
577 583 rebased = filter(lambda x: x > -1 and x != target, state.values())
578 584 if rebased:
579 585 strippoint = min(rebased)
580 586 # no backup of rebased cset versions needed
581 587 repair.strip(repo.ui, repo, repo[strippoint].node())
582 588 clearstatus(repo)
583 589 repo.ui.warn(_('rebase aborted\n'))
584 590 return 0
585 591
586 592 def buildstate(repo, dest, rebaseset, collapse):
587 593 '''Define which revisions are going to be rebased and where
588 594
589 595 repo: repo
590 596 dest: context
591 597 rebaseset: set of rev
592 598 '''
593 599
594 600 # This check isn't strictly necessary, since mq detects commits over an
595 601 # applied patch. But it prevents messing up the working directory when
596 602 # a partially completed rebase is blocked by mq.
597 603 if 'qtip' in repo.tags() and (dest.node() in
598 604 [s.node for s in repo.mq.applied]):
599 605 raise util.Abort(_('cannot rebase onto an applied mq patch'))
600 606
601 607 roots = list(repo.set('roots(%ld)', rebaseset))
602 608 if not roots:
603 609 raise util.Abort(_('no matching revisions'))
604 610 if len(roots) > 1:
605 611 raise util.Abort(_("can't rebase multiple roots"))
606 612 root = roots[0]
607 613
608 614 commonbase = root.ancestor(dest)
609 615 if commonbase == root:
610 616 raise util.Abort(_('source is ancestor of destination'))
611 617 if commonbase == dest:
612 618 samebranch = root.branch() == dest.branch()
613 619 if not collapse and samebranch and root in dest.children():
614 620 repo.ui.debug('source is a child of destination\n')
615 621 return None
616 622
617 623 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, root))
618 624 state = dict.fromkeys(rebaseset, nullrev)
619 625 # Rebase tries to turn <dest> into a parent of <root> while
620 626 # preserving the number of parents of rebased changesets:
621 627 #
622 628 # - A changeset with a single parent will always be rebased as a
623 629 # changeset with a single parent.
624 630 #
625 631 # - A merge will be rebased as merge unless its parents are both
626 632 # ancestors of <dest> or are themselves in the rebased set and
627 633 # pruned while rebased.
628 634 #
629 635 # If one parent of <root> is an ancestor of <dest>, the rebased
630 636 # version of this parent will be <dest>. This is always true with
631 637 # --base option.
632 638 #
633 639 # Otherwise, we need to *replace* the original parents with
634 640 # <dest>. This "detaches" the rebased set from its former location
635 641 # and rebases it onto <dest>. Changes introduced by ancestors of
636 642 # <root> not common with <dest> (the detachset, marked as
637 643 # nullmerge) are "removed" from the rebased changesets.
638 644 #
639 645 # - If <root> has a single parent, set it to <dest>.
640 646 #
641 647 # - If <root> is a merge, we cannot decide which parent to
642 648 # replace, the rebase operation is not clearly defined.
643 649 #
644 650 # The table below sums up this behavior:
645 651 #
646 652 # +--------------------+----------------------+-------------------------+
647 653 # | | one parent | merge |
648 654 # +--------------------+----------------------+-------------------------+
649 655 # | parent in ::<dest> | new parent is <dest> | parents in ::<dest> are |
650 656 # | | | remapped to <dest> |
651 657 # +--------------------+----------------------+-------------------------+
652 658 # | unrelated source | new parent is <dest> | ambiguous, abort |
653 659 # +--------------------+----------------------+-------------------------+
654 660 #
655 661 # The actual abort is handled by `defineparents`
656 662 if len(root.parents()) <= 1:
657 663 # (strict) ancestors of <root> not ancestors of <dest>
658 664 detachset = repo.revs('::%d - ::%d - %d', root, commonbase, root)
659 665 state.update(dict.fromkeys(detachset, nullmerge))
660 666 return repo['.'].rev(), dest.rev(), state
661 667
662 668 def pullrebase(orig, ui, repo, *args, **opts):
663 669 'Call rebase after pull if the latter has been invoked with --rebase'
664 670 if opts.get('rebase'):
665 671 if opts.get('update'):
666 672 del opts['update']
667 673 ui.debug('--update and --rebase are not compatible, ignoring '
668 674 'the update flag\n')
669 675
670 676 movemarkfrom = repo['.'].node()
671 677 cmdutil.bailifchanged(repo)
672 678 revsprepull = len(repo)
673 679 origpostincoming = commands.postincoming
674 680 def _dummy(*args, **kwargs):
675 681 pass
676 682 commands.postincoming = _dummy
677 683 try:
678 684 orig(ui, repo, *args, **opts)
679 685 finally:
680 686 commands.postincoming = origpostincoming
681 687 revspostpull = len(repo)
682 688 if revspostpull > revsprepull:
683 689 rebase(ui, repo, **opts)
684 690 branch = repo[None].branch()
685 691 dest = repo[branch].rev()
686 692 if dest != repo['.'].rev():
687 693 # there was nothing to rebase we force an update
688 694 hg.update(repo, dest)
689 695 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
690 696 ui.status(_("updating bookmark %s\n")
691 697 % repo._bookmarkcurrent)
692 698 else:
693 699 if opts.get('tool'):
694 700 raise util.Abort(_('--tool can only be used with --rebase'))
695 701 orig(ui, repo, *args, **opts)
696 702
697 703 def uisetup(ui):
698 704 'Replace pull with a decorator to provide --rebase option'
699 705 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
700 706 entry[1].append(('', 'rebase', None,
701 707 _("rebase working directory to branch head")))
702 708 entry[1].append(('t', 'tool', '',
703 709 _("specify merge tool for rebase")))
@@ -1,1656 +1,1660 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, bookmarks
14 14
15 15 def parsealiases(cmd):
16 16 return cmd.lstrip("^").split("|")
17 17
18 18 def findpossible(cmd, table, strict=False):
19 19 """
20 20 Return cmd -> (aliases, command table entry)
21 21 for each matching command.
22 22 Return debug commands (or their aliases) only if no normal command matches.
23 23 """
24 24 choice = {}
25 25 debugchoice = {}
26 26
27 27 if cmd in table:
28 28 # short-circuit exact matches, "log" alias beats "^log|history"
29 29 keys = [cmd]
30 30 else:
31 31 keys = table.keys()
32 32
33 33 for e in keys:
34 34 aliases = parsealiases(e)
35 35 found = None
36 36 if cmd in aliases:
37 37 found = cmd
38 38 elif not strict:
39 39 for a in aliases:
40 40 if a.startswith(cmd):
41 41 found = a
42 42 break
43 43 if found is not None:
44 44 if aliases[0].startswith("debug") or found.startswith("debug"):
45 45 debugchoice[found] = (aliases, table[e])
46 46 else:
47 47 choice[found] = (aliases, table[e])
48 48
49 49 if not choice and debugchoice:
50 50 choice = debugchoice
51 51
52 52 return choice
53 53
54 54 def findcmd(cmd, table, strict=True):
55 55 """Return (aliases, command table entry) for command string."""
56 56 choice = findpossible(cmd, table, strict)
57 57
58 58 if cmd in choice:
59 59 return choice[cmd]
60 60
61 61 if len(choice) > 1:
62 62 clist = choice.keys()
63 63 clist.sort()
64 64 raise error.AmbiguousCommand(cmd, clist)
65 65
66 66 if choice:
67 67 return choice.values()[0]
68 68
69 69 raise error.UnknownCommand(cmd)
70 70
71 71 def findrepo(p):
72 72 while not os.path.isdir(os.path.join(p, ".hg")):
73 73 oldp, p = p, os.path.dirname(p)
74 74 if p == oldp:
75 75 return None
76 76
77 77 return p
78 78
79 79 def bailifchanged(repo):
80 80 if repo.dirstate.p2() != nullid:
81 81 raise util.Abort(_('outstanding uncommitted merge'))
82 82 modified, added, removed, deleted = repo.status()[:4]
83 83 if modified or added or removed or deleted:
84 84 raise util.Abort(_("outstanding uncommitted changes"))
85 85 ctx = repo[None]
86 86 for s in ctx.substate:
87 87 if ctx.sub(s).dirty():
88 88 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
89 89
90 90 def logmessage(ui, opts):
91 91 """ get the log message according to -m and -l option """
92 92 message = opts.get('message')
93 93 logfile = opts.get('logfile')
94 94
95 95 if message and logfile:
96 96 raise util.Abort(_('options --message and --logfile are mutually '
97 97 'exclusive'))
98 98 if not message and logfile:
99 99 try:
100 100 if logfile == '-':
101 101 message = ui.fin.read()
102 102 else:
103 103 message = '\n'.join(util.readfile(logfile).splitlines())
104 104 except IOError, inst:
105 105 raise util.Abort(_("can't read commit message '%s': %s") %
106 106 (logfile, inst.strerror))
107 107 return message
108 108
109 109 def loglimit(opts):
110 110 """get the log limit according to option -l/--limit"""
111 111 limit = opts.get('limit')
112 112 if limit:
113 113 try:
114 114 limit = int(limit)
115 115 except ValueError:
116 116 raise util.Abort(_('limit must be a positive integer'))
117 117 if limit <= 0:
118 118 raise util.Abort(_('limit must be positive'))
119 119 else:
120 120 limit = None
121 121 return limit
122 122
123 123 def makefilename(repo, pat, node, desc=None,
124 124 total=None, seqno=None, revwidth=None, pathname=None):
125 125 node_expander = {
126 126 'H': lambda: hex(node),
127 127 'R': lambda: str(repo.changelog.rev(node)),
128 128 'h': lambda: short(node),
129 129 'm': lambda: re.sub('[^\w]', '_', str(desc))
130 130 }
131 131 expander = {
132 132 '%': lambda: '%',
133 133 'b': lambda: os.path.basename(repo.root),
134 134 }
135 135
136 136 try:
137 137 if node:
138 138 expander.update(node_expander)
139 139 if node:
140 140 expander['r'] = (lambda:
141 141 str(repo.changelog.rev(node)).zfill(revwidth or 0))
142 142 if total is not None:
143 143 expander['N'] = lambda: str(total)
144 144 if seqno is not None:
145 145 expander['n'] = lambda: str(seqno)
146 146 if total is not None and seqno is not None:
147 147 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
148 148 if pathname is not None:
149 149 expander['s'] = lambda: os.path.basename(pathname)
150 150 expander['d'] = lambda: os.path.dirname(pathname) or '.'
151 151 expander['p'] = lambda: pathname
152 152
153 153 newname = []
154 154 patlen = len(pat)
155 155 i = 0
156 156 while i < patlen:
157 157 c = pat[i]
158 158 if c == '%':
159 159 i += 1
160 160 c = pat[i]
161 161 c = expander[c]()
162 162 newname.append(c)
163 163 i += 1
164 164 return ''.join(newname)
165 165 except KeyError, inst:
166 166 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
167 167 inst.args[0])
168 168
169 169 def makefileobj(repo, pat, node=None, desc=None, total=None,
170 170 seqno=None, revwidth=None, mode='wb', pathname=None):
171 171
172 172 writable = mode not in ('r', 'rb')
173 173
174 174 if not pat or pat == '-':
175 175 fp = writable and repo.ui.fout or repo.ui.fin
176 176 if util.safehasattr(fp, 'fileno'):
177 177 return os.fdopen(os.dup(fp.fileno()), mode)
178 178 else:
179 179 # if this fp can't be duped properly, return
180 180 # a dummy object that can be closed
181 181 class wrappedfileobj(object):
182 182 noop = lambda x: None
183 183 def __init__(self, f):
184 184 self.f = f
185 185 def __getattr__(self, attr):
186 186 if attr == 'close':
187 187 return self.noop
188 188 else:
189 189 return getattr(self.f, attr)
190 190
191 191 return wrappedfileobj(fp)
192 192 if util.safehasattr(pat, 'write') and writable:
193 193 return pat
194 194 if util.safehasattr(pat, 'read') and 'r' in mode:
195 195 return pat
196 196 return open(makefilename(repo, pat, node, desc, total, seqno, revwidth,
197 197 pathname),
198 198 mode)
199 199
200 200 def openrevlog(repo, cmd, file_, opts):
201 201 """opens the changelog, manifest, a filelog or a given revlog"""
202 202 cl = opts['changelog']
203 203 mf = opts['manifest']
204 204 msg = None
205 205 if cl and mf:
206 206 msg = _('cannot specify --changelog and --manifest at the same time')
207 207 elif cl or mf:
208 208 if file_:
209 209 msg = _('cannot specify filename with --changelog or --manifest')
210 210 elif not repo:
211 211 msg = _('cannot specify --changelog or --manifest '
212 212 'without a repository')
213 213 if msg:
214 214 raise util.Abort(msg)
215 215
216 216 r = None
217 217 if repo:
218 218 if cl:
219 219 r = repo.changelog
220 220 elif mf:
221 221 r = repo.manifest
222 222 elif file_:
223 223 filelog = repo.file(file_)
224 224 if len(filelog):
225 225 r = filelog
226 226 if not r:
227 227 if not file_:
228 228 raise error.CommandError(cmd, _('invalid arguments'))
229 229 if not os.path.isfile(file_):
230 230 raise util.Abort(_("revlog '%s' not found") % file_)
231 231 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
232 232 file_[:-2] + ".i")
233 233 return r
234 234
235 235 def copy(ui, repo, pats, opts, rename=False):
236 236 # called with the repo lock held
237 237 #
238 238 # hgsep => pathname that uses "/" to separate directories
239 239 # ossep => pathname that uses os.sep to separate directories
240 240 cwd = repo.getcwd()
241 241 targets = {}
242 242 after = opts.get("after")
243 243 dryrun = opts.get("dry_run")
244 244 wctx = repo[None]
245 245
246 246 def walkpat(pat):
247 247 srcs = []
248 248 badstates = after and '?' or '?r'
249 249 m = scmutil.match(repo[None], [pat], opts, globbed=True)
250 250 for abs in repo.walk(m):
251 251 state = repo.dirstate[abs]
252 252 rel = m.rel(abs)
253 253 exact = m.exact(abs)
254 254 if state in badstates:
255 255 if exact and state == '?':
256 256 ui.warn(_('%s: not copying - file is not managed\n') % rel)
257 257 if exact and state == 'r':
258 258 ui.warn(_('%s: not copying - file has been marked for'
259 259 ' remove\n') % rel)
260 260 continue
261 261 # abs: hgsep
262 262 # rel: ossep
263 263 srcs.append((abs, rel, exact))
264 264 return srcs
265 265
266 266 # abssrc: hgsep
267 267 # relsrc: ossep
268 268 # otarget: ossep
269 269 def copyfile(abssrc, relsrc, otarget, exact):
270 270 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
271 271 if '/' in abstarget:
272 272 # We cannot normalize abstarget itself, this would prevent
273 273 # case only renames, like a => A.
274 274 abspath, absname = abstarget.rsplit('/', 1)
275 275 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
276 276 reltarget = repo.pathto(abstarget, cwd)
277 277 target = repo.wjoin(abstarget)
278 278 src = repo.wjoin(abssrc)
279 279 state = repo.dirstate[abstarget]
280 280
281 281 scmutil.checkportable(ui, abstarget)
282 282
283 283 # check for collisions
284 284 prevsrc = targets.get(abstarget)
285 285 if prevsrc is not None:
286 286 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
287 287 (reltarget, repo.pathto(abssrc, cwd),
288 288 repo.pathto(prevsrc, cwd)))
289 289 return
290 290
291 291 # check for overwrites
292 292 exists = os.path.lexists(target)
293 293 samefile = False
294 294 if exists and abssrc != abstarget:
295 295 if (repo.dirstate.normalize(abssrc) ==
296 296 repo.dirstate.normalize(abstarget)):
297 297 if not rename:
298 298 ui.warn(_("%s: can't copy - same file\n") % reltarget)
299 299 return
300 300 exists = False
301 301 samefile = True
302 302
303 303 if not after and exists or after and state in 'mn':
304 304 if not opts['force']:
305 305 ui.warn(_('%s: not overwriting - file exists\n') %
306 306 reltarget)
307 307 return
308 308
309 309 if after:
310 310 if not exists:
311 311 if rename:
312 312 ui.warn(_('%s: not recording move - %s does not exist\n') %
313 313 (relsrc, reltarget))
314 314 else:
315 315 ui.warn(_('%s: not recording copy - %s does not exist\n') %
316 316 (relsrc, reltarget))
317 317 return
318 318 elif not dryrun:
319 319 try:
320 320 if exists:
321 321 os.unlink(target)
322 322 targetdir = os.path.dirname(target) or '.'
323 323 if not os.path.isdir(targetdir):
324 324 os.makedirs(targetdir)
325 325 if samefile:
326 326 tmp = target + "~hgrename"
327 327 os.rename(src, tmp)
328 328 os.rename(tmp, target)
329 329 else:
330 330 util.copyfile(src, target)
331 331 srcexists = True
332 332 except IOError, inst:
333 333 if inst.errno == errno.ENOENT:
334 334 ui.warn(_('%s: deleted in working copy\n') % relsrc)
335 335 srcexists = False
336 336 else:
337 337 ui.warn(_('%s: cannot copy - %s\n') %
338 338 (relsrc, inst.strerror))
339 339 return True # report a failure
340 340
341 341 if ui.verbose or not exact:
342 342 if rename:
343 343 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
344 344 else:
345 345 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
346 346
347 347 targets[abstarget] = abssrc
348 348
349 349 # fix up dirstate
350 350 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
351 351 dryrun=dryrun, cwd=cwd)
352 352 if rename and not dryrun:
353 353 if not after and srcexists and not samefile:
354 354 util.unlinkpath(repo.wjoin(abssrc))
355 355 wctx.forget([abssrc])
356 356
357 357 # pat: ossep
358 358 # dest ossep
359 359 # srcs: list of (hgsep, hgsep, ossep, bool)
360 360 # return: function that takes hgsep and returns ossep
361 361 def targetpathfn(pat, dest, srcs):
362 362 if os.path.isdir(pat):
363 363 abspfx = scmutil.canonpath(repo.root, cwd, pat)
364 364 abspfx = util.localpath(abspfx)
365 365 if destdirexists:
366 366 striplen = len(os.path.split(abspfx)[0])
367 367 else:
368 368 striplen = len(abspfx)
369 369 if striplen:
370 370 striplen += len(os.sep)
371 371 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
372 372 elif destdirexists:
373 373 res = lambda p: os.path.join(dest,
374 374 os.path.basename(util.localpath(p)))
375 375 else:
376 376 res = lambda p: dest
377 377 return res
378 378
379 379 # pat: ossep
380 380 # dest ossep
381 381 # srcs: list of (hgsep, hgsep, ossep, bool)
382 382 # return: function that takes hgsep and returns ossep
383 383 def targetpathafterfn(pat, dest, srcs):
384 384 if matchmod.patkind(pat):
385 385 # a mercurial pattern
386 386 res = lambda p: os.path.join(dest,
387 387 os.path.basename(util.localpath(p)))
388 388 else:
389 389 abspfx = scmutil.canonpath(repo.root, cwd, pat)
390 390 if len(abspfx) < len(srcs[0][0]):
391 391 # A directory. Either the target path contains the last
392 392 # component of the source path or it does not.
393 393 def evalpath(striplen):
394 394 score = 0
395 395 for s in srcs:
396 396 t = os.path.join(dest, util.localpath(s[0])[striplen:])
397 397 if os.path.lexists(t):
398 398 score += 1
399 399 return score
400 400
401 401 abspfx = util.localpath(abspfx)
402 402 striplen = len(abspfx)
403 403 if striplen:
404 404 striplen += len(os.sep)
405 405 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
406 406 score = evalpath(striplen)
407 407 striplen1 = len(os.path.split(abspfx)[0])
408 408 if striplen1:
409 409 striplen1 += len(os.sep)
410 410 if evalpath(striplen1) > score:
411 411 striplen = striplen1
412 412 res = lambda p: os.path.join(dest,
413 413 util.localpath(p)[striplen:])
414 414 else:
415 415 # a file
416 416 if destdirexists:
417 417 res = lambda p: os.path.join(dest,
418 418 os.path.basename(util.localpath(p)))
419 419 else:
420 420 res = lambda p: dest
421 421 return res
422 422
423 423
424 424 pats = scmutil.expandpats(pats)
425 425 if not pats:
426 426 raise util.Abort(_('no source or destination specified'))
427 427 if len(pats) == 1:
428 428 raise util.Abort(_('no destination specified'))
429 429 dest = pats.pop()
430 430 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
431 431 if not destdirexists:
432 432 if len(pats) > 1 or matchmod.patkind(pats[0]):
433 433 raise util.Abort(_('with multiple sources, destination must be an '
434 434 'existing directory'))
435 435 if util.endswithsep(dest):
436 436 raise util.Abort(_('destination %s is not a directory') % dest)
437 437
438 438 tfn = targetpathfn
439 439 if after:
440 440 tfn = targetpathafterfn
441 441 copylist = []
442 442 for pat in pats:
443 443 srcs = walkpat(pat)
444 444 if not srcs:
445 445 continue
446 446 copylist.append((tfn(pat, dest, srcs), srcs))
447 447 if not copylist:
448 448 raise util.Abort(_('no files to copy'))
449 449
450 450 errors = 0
451 451 for targetpath, srcs in copylist:
452 452 for abssrc, relsrc, exact in srcs:
453 453 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
454 454 errors += 1
455 455
456 456 if errors:
457 457 ui.warn(_('(consider using --after)\n'))
458 458
459 459 return errors != 0
460 460
461 461 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
462 462 runargs=None, appendpid=False):
463 463 '''Run a command as a service.'''
464 464
465 465 if opts['daemon'] and not opts['daemon_pipefds']:
466 466 # Signal child process startup with file removal
467 467 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
468 468 os.close(lockfd)
469 469 try:
470 470 if not runargs:
471 471 runargs = util.hgcmd() + sys.argv[1:]
472 472 runargs.append('--daemon-pipefds=%s' % lockpath)
473 473 # Don't pass --cwd to the child process, because we've already
474 474 # changed directory.
475 475 for i in xrange(1, len(runargs)):
476 476 if runargs[i].startswith('--cwd='):
477 477 del runargs[i]
478 478 break
479 479 elif runargs[i].startswith('--cwd'):
480 480 del runargs[i:i + 2]
481 481 break
482 482 def condfn():
483 483 return not os.path.exists(lockpath)
484 484 pid = util.rundetached(runargs, condfn)
485 485 if pid < 0:
486 486 raise util.Abort(_('child process failed to start'))
487 487 finally:
488 488 try:
489 489 os.unlink(lockpath)
490 490 except OSError, e:
491 491 if e.errno != errno.ENOENT:
492 492 raise
493 493 if parentfn:
494 494 return parentfn(pid)
495 495 else:
496 496 return
497 497
498 498 if initfn:
499 499 initfn()
500 500
501 501 if opts['pid_file']:
502 502 mode = appendpid and 'a' or 'w'
503 503 fp = open(opts['pid_file'], mode)
504 504 fp.write(str(os.getpid()) + '\n')
505 505 fp.close()
506 506
507 507 if opts['daemon_pipefds']:
508 508 lockpath = opts['daemon_pipefds']
509 509 try:
510 510 os.setsid()
511 511 except AttributeError:
512 512 pass
513 513 os.unlink(lockpath)
514 514 util.hidewindow()
515 515 sys.stdout.flush()
516 516 sys.stderr.flush()
517 517
518 518 nullfd = os.open(util.nulldev, os.O_RDWR)
519 519 logfilefd = nullfd
520 520 if logfile:
521 521 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
522 522 os.dup2(nullfd, 0)
523 523 os.dup2(logfilefd, 1)
524 524 os.dup2(logfilefd, 2)
525 525 if nullfd not in (0, 1, 2):
526 526 os.close(nullfd)
527 527 if logfile and logfilefd not in (0, 1, 2):
528 528 os.close(logfilefd)
529 529
530 530 if runfn:
531 531 return runfn()
532 532
533 533 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
534 534 opts=None):
535 535 '''export changesets as hg patches.'''
536 536
537 537 total = len(revs)
538 538 revwidth = max([len(str(rev)) for rev in revs])
539 539
540 540 def single(rev, seqno, fp):
541 541 ctx = repo[rev]
542 542 node = ctx.node()
543 543 parents = [p.node() for p in ctx.parents() if p]
544 544 branch = ctx.branch()
545 545 if switch_parent:
546 546 parents.reverse()
547 547 prev = (parents and parents[0]) or nullid
548 548
549 549 shouldclose = False
550 550 if not fp:
551 551 desc_lines = ctx.description().rstrip().split('\n')
552 552 desc = desc_lines[0] #Commit always has a first line.
553 553 fp = makefileobj(repo, template, node, desc=desc, total=total,
554 554 seqno=seqno, revwidth=revwidth, mode='ab')
555 555 if fp != template:
556 556 shouldclose = True
557 557 if fp != sys.stdout and util.safehasattr(fp, 'name'):
558 558 repo.ui.note("%s\n" % fp.name)
559 559
560 560 fp.write("# HG changeset patch\n")
561 561 fp.write("# User %s\n" % ctx.user())
562 562 fp.write("# Date %d %d\n" % ctx.date())
563 563 if branch and branch != 'default':
564 564 fp.write("# Branch %s\n" % branch)
565 565 fp.write("# Node ID %s\n" % hex(node))
566 566 fp.write("# Parent %s\n" % hex(prev))
567 567 if len(parents) > 1:
568 568 fp.write("# Parent %s\n" % hex(parents[1]))
569 569 fp.write(ctx.description().rstrip())
570 570 fp.write("\n\n")
571 571
572 572 for chunk in patch.diff(repo, prev, node, opts=opts):
573 573 fp.write(chunk)
574 574
575 575 if shouldclose:
576 576 fp.close()
577 577
578 578 for seqno, rev in enumerate(revs):
579 579 single(rev, seqno + 1, fp)
580 580
581 581 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
582 582 changes=None, stat=False, fp=None, prefix='',
583 583 listsubrepos=False):
584 584 '''show diff or diffstat.'''
585 585 if fp is None:
586 586 write = ui.write
587 587 else:
588 588 def write(s, **kw):
589 589 fp.write(s)
590 590
591 591 if stat:
592 592 diffopts = diffopts.copy(context=0)
593 593 width = 80
594 594 if not ui.plain():
595 595 width = ui.termwidth()
596 596 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
597 597 prefix=prefix)
598 598 for chunk, label in patch.diffstatui(util.iterlines(chunks),
599 599 width=width,
600 600 git=diffopts.git):
601 601 write(chunk, label=label)
602 602 else:
603 603 for chunk, label in patch.diffui(repo, node1, node2, match,
604 604 changes, diffopts, prefix=prefix):
605 605 write(chunk, label=label)
606 606
607 607 if listsubrepos:
608 608 ctx1 = repo[node1]
609 609 ctx2 = repo[node2]
610 610 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
611 611 tempnode2 = node2
612 612 try:
613 613 if node2 is not None:
614 614 tempnode2 = ctx2.substate[subpath][1]
615 615 except KeyError:
616 616 # A subrepo that existed in node1 was deleted between node1 and
617 617 # node2 (inclusive). Thus, ctx2's substate won't contain that
618 618 # subpath. The best we can do is to ignore it.
619 619 tempnode2 = None
620 620 submatch = matchmod.narrowmatcher(subpath, match)
621 621 sub.diff(diffopts, tempnode2, submatch, changes=changes,
622 622 stat=stat, fp=fp, prefix=prefix)
623 623
624 624 class changeset_printer(object):
625 625 '''show changeset information when templating not requested.'''
626 626
627 627 def __init__(self, ui, repo, patch, diffopts, buffered):
628 628 self.ui = ui
629 629 self.repo = repo
630 630 self.buffered = buffered
631 631 self.patch = patch
632 632 self.diffopts = diffopts
633 633 self.header = {}
634 634 self.hunk = {}
635 635 self.lastheader = None
636 636 self.footer = None
637 637
638 638 def flush(self, rev):
639 639 if rev in self.header:
640 640 h = self.header[rev]
641 641 if h != self.lastheader:
642 642 self.lastheader = h
643 643 self.ui.write(h)
644 644 del self.header[rev]
645 645 if rev in self.hunk:
646 646 self.ui.write(self.hunk[rev])
647 647 del self.hunk[rev]
648 648 return 1
649 649 return 0
650 650
651 651 def close(self):
652 652 if self.footer:
653 653 self.ui.write(self.footer)
654 654
655 655 def show(self, ctx, copies=None, matchfn=None, **props):
656 656 if self.buffered:
657 657 self.ui.pushbuffer()
658 658 self._show(ctx, copies, matchfn, props)
659 659 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
660 660 else:
661 661 self._show(ctx, copies, matchfn, props)
662 662
663 663 def _show(self, ctx, copies, matchfn, props):
664 664 '''show a single changeset or file revision'''
665 665 changenode = ctx.node()
666 666 rev = ctx.rev()
667 667
668 668 if self.ui.quiet:
669 669 self.ui.write("%d:%s\n" % (rev, short(changenode)),
670 670 label='log.node')
671 671 return
672 672
673 673 log = self.repo.changelog
674 674 date = util.datestr(ctx.date())
675 675
676 676 hexfunc = self.ui.debugflag and hex or short
677 677
678 678 parents = [(p, hexfunc(log.node(p)))
679 679 for p in self._meaningful_parentrevs(log, rev)]
680 680
681 681 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
682 682 label='log.changeset')
683 683
684 684 branch = ctx.branch()
685 685 # don't show the default branch name
686 686 if branch != 'default':
687 687 self.ui.write(_("branch: %s\n") % branch,
688 688 label='log.branch')
689 689 for bookmark in self.repo.nodebookmarks(changenode):
690 690 self.ui.write(_("bookmark: %s\n") % bookmark,
691 691 label='log.bookmark')
692 692 for tag in self.repo.nodetags(changenode):
693 693 self.ui.write(_("tag: %s\n") % tag,
694 694 label='log.tag')
695 695 if self.ui.debugflag and ctx.phase():
696 696 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
697 697 label='log.phase')
698 698 for parent in parents:
699 699 self.ui.write(_("parent: %d:%s\n") % parent,
700 700 label='log.parent')
701 701
702 702 if self.ui.debugflag:
703 703 mnode = ctx.manifestnode()
704 704 self.ui.write(_("manifest: %d:%s\n") %
705 705 (self.repo.manifest.rev(mnode), hex(mnode)),
706 706 label='ui.debug log.manifest')
707 707 self.ui.write(_("user: %s\n") % ctx.user(),
708 708 label='log.user')
709 709 self.ui.write(_("date: %s\n") % date,
710 710 label='log.date')
711 711
712 712 if self.ui.debugflag:
713 713 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
714 714 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
715 715 files):
716 716 if value:
717 717 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
718 718 label='ui.debug log.files')
719 719 elif ctx.files() and self.ui.verbose:
720 720 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
721 721 label='ui.note log.files')
722 722 if copies and self.ui.verbose:
723 723 copies = ['%s (%s)' % c for c in copies]
724 724 self.ui.write(_("copies: %s\n") % ' '.join(copies),
725 725 label='ui.note log.copies')
726 726
727 727 extra = ctx.extra()
728 728 if extra and self.ui.debugflag:
729 729 for key, value in sorted(extra.items()):
730 730 self.ui.write(_("extra: %s=%s\n")
731 731 % (key, value.encode('string_escape')),
732 732 label='ui.debug log.extra')
733 733
734 734 description = ctx.description().strip()
735 735 if description:
736 736 if self.ui.verbose:
737 737 self.ui.write(_("description:\n"),
738 738 label='ui.note log.description')
739 739 self.ui.write(description,
740 740 label='ui.note log.description')
741 741 self.ui.write("\n\n")
742 742 else:
743 743 self.ui.write(_("summary: %s\n") %
744 744 description.splitlines()[0],
745 745 label='log.summary')
746 746 self.ui.write("\n")
747 747
748 748 self.showpatch(changenode, matchfn)
749 749
750 750 def showpatch(self, node, matchfn):
751 751 if not matchfn:
752 752 matchfn = self.patch
753 753 if matchfn:
754 754 stat = self.diffopts.get('stat')
755 755 diff = self.diffopts.get('patch')
756 756 diffopts = patch.diffopts(self.ui, self.diffopts)
757 757 prev = self.repo.changelog.parents(node)[0]
758 758 if stat:
759 759 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
760 760 match=matchfn, stat=True)
761 761 if diff:
762 762 if stat:
763 763 self.ui.write("\n")
764 764 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
765 765 match=matchfn, stat=False)
766 766 self.ui.write("\n")
767 767
768 768 def _meaningful_parentrevs(self, log, rev):
769 769 """Return list of meaningful (or all if debug) parentrevs for rev.
770 770
771 771 For merges (two non-nullrev revisions) both parents are meaningful.
772 772 Otherwise the first parent revision is considered meaningful if it
773 773 is not the preceding revision.
774 774 """
775 775 parents = log.parentrevs(rev)
776 776 if not self.ui.debugflag and parents[1] == nullrev:
777 777 if parents[0] >= rev - 1:
778 778 parents = []
779 779 else:
780 780 parents = [parents[0]]
781 781 return parents
782 782
783 783
784 784 class changeset_templater(changeset_printer):
785 785 '''format changeset information.'''
786 786
787 787 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
788 788 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
789 789 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
790 790 defaulttempl = {
791 791 'parent': '{rev}:{node|formatnode} ',
792 792 'manifest': '{rev}:{node|formatnode}',
793 793 'file_copy': '{name} ({source})',
794 794 'extra': '{key}={value|stringescape}'
795 795 }
796 796 # filecopy is preserved for compatibility reasons
797 797 defaulttempl['filecopy'] = defaulttempl['file_copy']
798 798 self.t = templater.templater(mapfile, {'formatnode': formatnode},
799 799 cache=defaulttempl)
800 800 self.cache = {}
801 801
802 802 def use_template(self, t):
803 803 '''set template string to use'''
804 804 self.t.cache['changeset'] = t
805 805
806 806 def _meaningful_parentrevs(self, ctx):
807 807 """Return list of meaningful (or all if debug) parentrevs for rev.
808 808 """
809 809 parents = ctx.parents()
810 810 if len(parents) > 1:
811 811 return parents
812 812 if self.ui.debugflag:
813 813 return [parents[0], self.repo['null']]
814 814 if parents[0].rev() >= ctx.rev() - 1:
815 815 return []
816 816 return parents
817 817
818 818 def _show(self, ctx, copies, matchfn, props):
819 819 '''show a single changeset or file revision'''
820 820
821 821 showlist = templatekw.showlist
822 822
823 823 # showparents() behaviour depends on ui trace level which
824 824 # causes unexpected behaviours at templating level and makes
825 825 # it harder to extract it in a standalone function. Its
826 826 # behaviour cannot be changed so leave it here for now.
827 827 def showparents(**args):
828 828 ctx = args['ctx']
829 829 parents = [[('rev', p.rev()), ('node', p.hex())]
830 830 for p in self._meaningful_parentrevs(ctx)]
831 831 return showlist('parent', parents, **args)
832 832
833 833 props = props.copy()
834 834 props.update(templatekw.keywords)
835 835 props['parents'] = showparents
836 836 props['templ'] = self.t
837 837 props['ctx'] = ctx
838 838 props['repo'] = self.repo
839 839 props['revcache'] = {'copies': copies}
840 840 props['cache'] = self.cache
841 841
842 842 # find correct templates for current mode
843 843
844 844 tmplmodes = [
845 845 (True, None),
846 846 (self.ui.verbose, 'verbose'),
847 847 (self.ui.quiet, 'quiet'),
848 848 (self.ui.debugflag, 'debug'),
849 849 ]
850 850
851 851 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
852 852 for mode, postfix in tmplmodes:
853 853 for type in types:
854 854 cur = postfix and ('%s_%s' % (type, postfix)) or type
855 855 if mode and cur in self.t:
856 856 types[type] = cur
857 857
858 858 try:
859 859
860 860 # write header
861 861 if types['header']:
862 862 h = templater.stringify(self.t(types['header'], **props))
863 863 if self.buffered:
864 864 self.header[ctx.rev()] = h
865 865 else:
866 866 if self.lastheader != h:
867 867 self.lastheader = h
868 868 self.ui.write(h)
869 869
870 870 # write changeset metadata, then patch if requested
871 871 key = types['changeset']
872 872 self.ui.write(templater.stringify(self.t(key, **props)))
873 873 self.showpatch(ctx.node(), matchfn)
874 874
875 875 if types['footer']:
876 876 if not self.footer:
877 877 self.footer = templater.stringify(self.t(types['footer'],
878 878 **props))
879 879
880 880 except KeyError, inst:
881 881 msg = _("%s: no key named '%s'")
882 882 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
883 883 except SyntaxError, inst:
884 884 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
885 885
886 886 def show_changeset(ui, repo, opts, buffered=False):
887 887 """show one changeset using template or regular display.
888 888
889 889 Display format will be the first non-empty hit of:
890 890 1. option 'template'
891 891 2. option 'style'
892 892 3. [ui] setting 'logtemplate'
893 893 4. [ui] setting 'style'
894 894 If all of these values are either the unset or the empty string,
895 895 regular display via changeset_printer() is done.
896 896 """
897 897 # options
898 898 patch = False
899 899 if opts.get('patch') or opts.get('stat'):
900 900 patch = scmutil.matchall(repo)
901 901
902 902 tmpl = opts.get('template')
903 903 style = None
904 904 if tmpl:
905 905 tmpl = templater.parsestring(tmpl, quoted=False)
906 906 else:
907 907 style = opts.get('style')
908 908
909 909 # ui settings
910 910 if not (tmpl or style):
911 911 tmpl = ui.config('ui', 'logtemplate')
912 912 if tmpl:
913 913 try:
914 914 tmpl = templater.parsestring(tmpl)
915 915 except SyntaxError:
916 916 tmpl = templater.parsestring(tmpl, quoted=False)
917 917 else:
918 918 style = util.expandpath(ui.config('ui', 'style', ''))
919 919
920 920 if not (tmpl or style):
921 921 return changeset_printer(ui, repo, patch, opts, buffered)
922 922
923 923 mapfile = None
924 924 if style and not tmpl:
925 925 mapfile = style
926 926 if not os.path.split(mapfile)[0]:
927 927 mapname = (templater.templatepath('map-cmdline.' + mapfile)
928 928 or templater.templatepath(mapfile))
929 929 if mapname:
930 930 mapfile = mapname
931 931
932 932 try:
933 933 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
934 934 except SyntaxError, inst:
935 935 raise util.Abort(inst.args[0])
936 936 if tmpl:
937 937 t.use_template(tmpl)
938 938 return t
939 939
940 940 def finddate(ui, repo, date):
941 941 """Find the tipmost changeset that matches the given date spec"""
942 942
943 943 df = util.matchdate(date)
944 944 m = scmutil.matchall(repo)
945 945 results = {}
946 946
947 947 def prep(ctx, fns):
948 948 d = ctx.date()
949 949 if df(d[0]):
950 950 results[ctx.rev()] = d
951 951
952 952 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
953 953 rev = ctx.rev()
954 954 if rev in results:
955 955 ui.status(_("found revision %s from %s\n") %
956 956 (rev, util.datestr(results[rev])))
957 957 return str(rev)
958 958
959 959 raise util.Abort(_("revision matching date not found"))
960 960
961 961 def increasingwindows(start, end, windowsize=8, sizelimit=512):
962 962 if start < end:
963 963 while start < end:
964 964 yield start, min(windowsize, end - start)
965 965 start += windowsize
966 966 if windowsize < sizelimit:
967 967 windowsize *= 2
968 968 else:
969 969 while start > end:
970 970 yield start, min(windowsize, start - end - 1)
971 971 start -= windowsize
972 972 if windowsize < sizelimit:
973 973 windowsize *= 2
974 974
975 975 def walkchangerevs(repo, match, opts, prepare):
976 976 '''Iterate over files and the revs in which they changed.
977 977
978 978 Callers most commonly need to iterate backwards over the history
979 979 in which they are interested. Doing so has awful (quadratic-looking)
980 980 performance, so we use iterators in a "windowed" way.
981 981
982 982 We walk a window of revisions in the desired order. Within the
983 983 window, we first walk forwards to gather data, then in the desired
984 984 order (usually backwards) to display it.
985 985
986 986 This function returns an iterator yielding contexts. Before
987 987 yielding each context, the iterator will first call the prepare
988 988 function on each context in the window in forward order.'''
989 989
990 990 follow = opts.get('follow') or opts.get('follow_first')
991 991
992 992 if not len(repo):
993 993 return []
994 994
995 995 if follow:
996 996 defrange = '%s:0' % repo['.'].rev()
997 997 else:
998 998 defrange = '-1:0'
999 999 revs = scmutil.revrange(repo, opts['rev'] or [defrange])
1000 1000 if not revs:
1001 1001 return []
1002 1002 wanted = set()
1003 1003 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1004 1004 fncache = {}
1005 1005 change = repo.changectx
1006 1006
1007 1007 # First step is to fill wanted, the set of revisions that we want to yield.
1008 1008 # When it does not induce extra cost, we also fill fncache for revisions in
1009 1009 # wanted: a cache of filenames that were changed (ctx.files()) and that
1010 1010 # match the file filtering conditions.
1011 1011
1012 1012 if not slowpath and not match.files():
1013 1013 # No files, no patterns. Display all revs.
1014 1014 wanted = set(revs)
1015 1015 copies = []
1016 1016
1017 1017 if not slowpath and match.files():
1018 1018 # We only have to read through the filelog to find wanted revisions
1019 1019
1020 1020 minrev, maxrev = min(revs), max(revs)
1021 1021 def filerevgen(filelog, last):
1022 1022 """
1023 1023 Only files, no patterns. Check the history of each file.
1024 1024
1025 1025 Examines filelog entries within minrev, maxrev linkrev range
1026 1026 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1027 1027 tuples in backwards order
1028 1028 """
1029 1029 cl_count = len(repo)
1030 1030 revs = []
1031 1031 for j in xrange(0, last + 1):
1032 1032 linkrev = filelog.linkrev(j)
1033 1033 if linkrev < minrev:
1034 1034 continue
1035 1035 # only yield rev for which we have the changelog, it can
1036 1036 # happen while doing "hg log" during a pull or commit
1037 1037 if linkrev >= cl_count:
1038 1038 break
1039 1039
1040 1040 parentlinkrevs = []
1041 1041 for p in filelog.parentrevs(j):
1042 1042 if p != nullrev:
1043 1043 parentlinkrevs.append(filelog.linkrev(p))
1044 1044 n = filelog.node(j)
1045 1045 revs.append((linkrev, parentlinkrevs,
1046 1046 follow and filelog.renamed(n)))
1047 1047
1048 1048 return reversed(revs)
1049 1049 def iterfiles():
1050 1050 pctx = repo['.']
1051 1051 for filename in match.files():
1052 1052 if follow:
1053 1053 if filename not in pctx:
1054 1054 raise util.Abort(_('cannot follow file not in parent '
1055 1055 'revision: "%s"') % filename)
1056 1056 yield filename, pctx[filename].filenode()
1057 1057 else:
1058 1058 yield filename, None
1059 1059 for filename_node in copies:
1060 1060 yield filename_node
1061 1061 for file_, node in iterfiles():
1062 1062 filelog = repo.file(file_)
1063 1063 if not len(filelog):
1064 1064 if node is None:
1065 1065 # A zero count may be a directory or deleted file, so
1066 1066 # try to find matching entries on the slow path.
1067 1067 if follow:
1068 1068 raise util.Abort(
1069 1069 _('cannot follow nonexistent file: "%s"') % file_)
1070 1070 slowpath = True
1071 1071 break
1072 1072 else:
1073 1073 continue
1074 1074
1075 1075 if node is None:
1076 1076 last = len(filelog) - 1
1077 1077 else:
1078 1078 last = filelog.rev(node)
1079 1079
1080 1080
1081 1081 # keep track of all ancestors of the file
1082 1082 ancestors = set([filelog.linkrev(last)])
1083 1083
1084 1084 # iterate from latest to oldest revision
1085 1085 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1086 1086 if not follow:
1087 1087 if rev > maxrev:
1088 1088 continue
1089 1089 else:
1090 1090 # Note that last might not be the first interesting
1091 1091 # rev to us:
1092 1092 # if the file has been changed after maxrev, we'll
1093 1093 # have linkrev(last) > maxrev, and we still need
1094 1094 # to explore the file graph
1095 1095 if rev not in ancestors:
1096 1096 continue
1097 1097 # XXX insert 1327 fix here
1098 1098 if flparentlinkrevs:
1099 1099 ancestors.update(flparentlinkrevs)
1100 1100
1101 1101 fncache.setdefault(rev, []).append(file_)
1102 1102 wanted.add(rev)
1103 1103 if copied:
1104 1104 copies.append(copied)
1105 1105 if slowpath:
1106 1106 # We have to read the changelog to match filenames against
1107 1107 # changed files
1108 1108
1109 1109 if follow:
1110 1110 raise util.Abort(_('can only follow copies/renames for explicit '
1111 1111 'filenames'))
1112 1112
1113 1113 # The slow path checks files modified in every changeset.
1114 1114 for i in sorted(revs):
1115 1115 ctx = change(i)
1116 1116 matches = filter(match, ctx.files())
1117 1117 if matches:
1118 1118 fncache[i] = matches
1119 1119 wanted.add(i)
1120 1120
1121 1121 class followfilter(object):
1122 1122 def __init__(self, onlyfirst=False):
1123 1123 self.startrev = nullrev
1124 1124 self.roots = set()
1125 1125 self.onlyfirst = onlyfirst
1126 1126
1127 1127 def match(self, rev):
1128 1128 def realparents(rev):
1129 1129 if self.onlyfirst:
1130 1130 return repo.changelog.parentrevs(rev)[0:1]
1131 1131 else:
1132 1132 return filter(lambda x: x != nullrev,
1133 1133 repo.changelog.parentrevs(rev))
1134 1134
1135 1135 if self.startrev == nullrev:
1136 1136 self.startrev = rev
1137 1137 return True
1138 1138
1139 1139 if rev > self.startrev:
1140 1140 # forward: all descendants
1141 1141 if not self.roots:
1142 1142 self.roots.add(self.startrev)
1143 1143 for parent in realparents(rev):
1144 1144 if parent in self.roots:
1145 1145 self.roots.add(rev)
1146 1146 return True
1147 1147 else:
1148 1148 # backwards: all parents
1149 1149 if not self.roots:
1150 1150 self.roots.update(realparents(self.startrev))
1151 1151 if rev in self.roots:
1152 1152 self.roots.remove(rev)
1153 1153 self.roots.update(realparents(rev))
1154 1154 return True
1155 1155
1156 1156 return False
1157 1157
1158 1158 # it might be worthwhile to do this in the iterator if the rev range
1159 1159 # is descending and the prune args are all within that range
1160 1160 for rev in opts.get('prune', ()):
1161 1161 rev = repo[rev].rev()
1162 1162 ff = followfilter()
1163 1163 stop = min(revs[0], revs[-1])
1164 1164 for x in xrange(rev, stop - 1, -1):
1165 1165 if ff.match(x):
1166 1166 wanted.discard(x)
1167 1167
1168 1168 # Now that wanted is correctly initialized, we can iterate over the
1169 1169 # revision range, yielding only revisions in wanted.
1170 1170 def iterate():
1171 1171 if follow and not match.files():
1172 1172 ff = followfilter(onlyfirst=opts.get('follow_first'))
1173 1173 def want(rev):
1174 1174 return ff.match(rev) and rev in wanted
1175 1175 else:
1176 1176 def want(rev):
1177 1177 return rev in wanted
1178 1178
1179 1179 for i, window in increasingwindows(0, len(revs)):
1180 1180 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1181 1181 for rev in sorted(nrevs):
1182 1182 fns = fncache.get(rev)
1183 1183 ctx = change(rev)
1184 1184 if not fns:
1185 1185 def fns_generator():
1186 1186 for f in ctx.files():
1187 1187 if match(f):
1188 1188 yield f
1189 1189 fns = fns_generator()
1190 1190 prepare(ctx, fns)
1191 1191 for rev in nrevs:
1192 1192 yield change(rev)
1193 1193 return iterate()
1194 1194
1195 1195 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1196 1196 join = lambda f: os.path.join(prefix, f)
1197 1197 bad = []
1198 1198 oldbad = match.bad
1199 1199 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1200 1200 names = []
1201 1201 wctx = repo[None]
1202 1202 cca = None
1203 1203 abort, warn = scmutil.checkportabilityalert(ui)
1204 1204 if abort or warn:
1205 1205 cca = scmutil.casecollisionauditor(ui, abort, wctx)
1206 1206 for f in repo.walk(match):
1207 1207 exact = match.exact(f)
1208 1208 if exact or not explicitonly and f not in repo.dirstate:
1209 1209 if cca:
1210 1210 cca(f)
1211 1211 names.append(f)
1212 1212 if ui.verbose or not exact:
1213 1213 ui.status(_('adding %s\n') % match.rel(join(f)))
1214 1214
1215 1215 for subpath in wctx.substate:
1216 1216 sub = wctx.sub(subpath)
1217 1217 try:
1218 1218 submatch = matchmod.narrowmatcher(subpath, match)
1219 1219 if listsubrepos:
1220 1220 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1221 1221 False))
1222 1222 else:
1223 1223 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1224 1224 True))
1225 1225 except error.LookupError:
1226 1226 ui.status(_("skipping missing subrepository: %s\n")
1227 1227 % join(subpath))
1228 1228
1229 1229 if not dryrun:
1230 1230 rejected = wctx.add(names, prefix)
1231 1231 bad.extend(f for f in rejected if f in match.files())
1232 1232 return bad
1233 1233
1234 1234 def forget(ui, repo, match, prefix, explicitonly):
1235 1235 join = lambda f: os.path.join(prefix, f)
1236 1236 bad = []
1237 1237 oldbad = match.bad
1238 1238 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1239 1239 wctx = repo[None]
1240 1240 forgot = []
1241 1241 s = repo.status(match=match, clean=True)
1242 1242 forget = sorted(s[0] + s[1] + s[3] + s[6])
1243 1243 if explicitonly:
1244 1244 forget = [f for f in forget if match.exact(f)]
1245 1245
1246 1246 for subpath in wctx.substate:
1247 1247 sub = wctx.sub(subpath)
1248 1248 try:
1249 1249 submatch = matchmod.narrowmatcher(subpath, match)
1250 1250 subbad, subforgot = sub.forget(ui, submatch, prefix)
1251 1251 bad.extend([subpath + '/' + f for f in subbad])
1252 1252 forgot.extend([subpath + '/' + f for f in subforgot])
1253 1253 except error.LookupError:
1254 1254 ui.status(_("skipping missing subrepository: %s\n")
1255 1255 % join(subpath))
1256 1256
1257 1257 if not explicitonly:
1258 1258 for f in match.files():
1259 1259 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1260 1260 if f not in forgot:
1261 1261 if os.path.exists(match.rel(join(f))):
1262 1262 ui.warn(_('not removing %s: '
1263 1263 'file is already untracked\n')
1264 1264 % match.rel(join(f)))
1265 1265 bad.append(f)
1266 1266
1267 1267 for f in forget:
1268 1268 if ui.verbose or not match.exact(f):
1269 1269 ui.status(_('removing %s\n') % match.rel(join(f)))
1270 1270
1271 1271 rejected = wctx.forget(forget, prefix)
1272 1272 bad.extend(f for f in rejected if f in match.files())
1273 1273 forgot.extend(forget)
1274 1274 return bad, forgot
1275 1275
1276 1276 def duplicatecopies(repo, rev, p1):
1277 1277 "Reproduce copies found in the source revision in the dirstate for grafts"
1278 1278 for dst, src in copies.pathcopies(repo[p1], repo[rev]).iteritems():
1279 1279 repo.dirstate.copy(src, dst)
1280 1280
1281 1281 def commit(ui, repo, commitfunc, pats, opts):
1282 1282 '''commit the specified files or all outstanding changes'''
1283 1283 date = opts.get('date')
1284 1284 if date:
1285 1285 opts['date'] = util.parsedate(date)
1286 1286 message = logmessage(ui, opts)
1287 1287
1288 1288 # extract addremove carefully -- this function can be called from a command
1289 1289 # that doesn't support addremove
1290 1290 if opts.get('addremove'):
1291 1291 scmutil.addremove(repo, pats, opts)
1292 1292
1293 1293 return commitfunc(ui, repo, message,
1294 1294 scmutil.match(repo[None], pats, opts), opts)
1295 1295
1296 1296 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1297 1297 ui.note(_('amending changeset %s\n') % old)
1298 1298 base = old.p1()
1299 1299
1300 1300 wlock = repo.wlock()
1301 1301 try:
1302 1302 # First, do a regular commit to record all changes in the working
1303 1303 # directory (if there are any)
1304 ui.callhooks = False
1305 try:
1304 1306 node = commit(ui, repo, commitfunc, pats, opts)
1307 finally:
1308 ui.callhooks = True
1305 1309 ctx = repo[node]
1306 1310
1307 1311 # Participating changesets:
1308 1312 #
1309 1313 # node/ctx o - new (intermediate) commit that contains changes from
1310 1314 # | working dir to go into amending commit (or a workingctx
1311 1315 # | if there were no changes)
1312 1316 # |
1313 1317 # old o - changeset to amend
1314 1318 # |
1315 1319 # base o - parent of amending changeset
1316 1320
1317 1321 # Update extra dict from amended commit (e.g. to preserve graft source)
1318 1322 extra.update(old.extra())
1319 1323
1320 1324 # Also update it from the intermediate commit or from the wctx
1321 1325 extra.update(ctx.extra())
1322 1326
1323 1327 files = set(old.files())
1324 1328
1325 1329 # Second, we use either the commit we just did, or if there were no
1326 1330 # changes the parent of the working directory as the version of the
1327 1331 # files in the final amend commit
1328 1332 if node:
1329 1333 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
1330 1334
1331 1335 user = ctx.user()
1332 1336 date = ctx.date()
1333 1337 message = ctx.description()
1334 1338 # Recompute copies (avoid recording a -> b -> a)
1335 1339 copied = copies.pathcopies(base, ctx)
1336 1340
1337 1341 # Prune files which were reverted by the updates: if old introduced
1338 1342 # file X and our intermediate commit, node, renamed that file, then
1339 1343 # those two files are the same and we can discard X from our list
1340 1344 # of files. Likewise if X was deleted, it's no longer relevant
1341 1345 files.update(ctx.files())
1342 1346
1343 1347 def samefile(f):
1344 1348 if f in ctx.manifest():
1345 1349 a = ctx.filectx(f)
1346 1350 if f in base.manifest():
1347 1351 b = base.filectx(f)
1348 1352 return (not a.cmp(b)
1349 1353 and a.flags() == b.flags())
1350 1354 else:
1351 1355 return False
1352 1356 else:
1353 1357 return f not in base.manifest()
1354 1358 files = [f for f in files if not samefile(f)]
1355 1359
1356 1360 def filectxfn(repo, ctx_, path):
1357 1361 try:
1358 1362 fctx = ctx[path]
1359 1363 flags = fctx.flags()
1360 1364 mctx = context.memfilectx(fctx.path(), fctx.data(),
1361 1365 islink='l' in flags,
1362 1366 isexec='x' in flags,
1363 1367 copied=copied.get(path))
1364 1368 return mctx
1365 1369 except KeyError:
1366 1370 raise IOError
1367 1371 else:
1368 1372 ui.note(_('copying changeset %s to %s\n') % (old, base))
1369 1373
1370 1374 # Use version of files as in the old cset
1371 1375 def filectxfn(repo, ctx_, path):
1372 1376 try:
1373 1377 return old.filectx(path)
1374 1378 except KeyError:
1375 1379 raise IOError
1376 1380
1377 1381 # See if we got a message from -m or -l, if not, open the editor
1378 1382 # with the message of the changeset to amend
1379 1383 user = opts.get('user') or old.user()
1380 1384 date = opts.get('date') or old.date()
1381 1385 message = logmessage(ui, opts)
1382 1386 if not message:
1383 1387 cctx = context.workingctx(repo, old.description(), user, date,
1384 1388 extra,
1385 1389 repo.status(base.node(), old.node()))
1386 1390 message = commitforceeditor(repo, cctx, [])
1387 1391
1388 1392 new = context.memctx(repo,
1389 1393 parents=[base.node(), nullid],
1390 1394 text=message,
1391 1395 files=files,
1392 1396 filectxfn=filectxfn,
1393 1397 user=user,
1394 1398 date=date,
1395 1399 extra=extra)
1396 1400 newid = repo.commitctx(new)
1397 1401 if newid != old.node():
1398 1402 # Reroute the working copy parent to the new changeset
1399 1403 repo.setparents(newid, nullid)
1400 1404
1401 1405 # Move bookmarks from old parent to amend commit
1402 1406 bms = repo.nodebookmarks(old.node())
1403 1407 if bms:
1404 1408 for bm in bms:
1405 1409 repo._bookmarks[bm] = newid
1406 1410 bookmarks.write(repo)
1407 1411
1408 1412 # Strip the intermediate commit (if there was one) and the amended
1409 1413 # commit
1410 1414 lock = repo.lock()
1411 1415 try:
1412 1416 if node:
1413 1417 ui.note(_('stripping intermediate changeset %s\n') % ctx)
1414 1418 ui.note(_('stripping amended changeset %s\n') % old)
1415 1419 repair.strip(ui, repo, old.node(), topic='amend-backup')
1416 1420 finally:
1417 1421 lock.release()
1418 1422 finally:
1419 1423 wlock.release()
1420 1424 return newid
1421 1425
1422 1426 def commiteditor(repo, ctx, subs):
1423 1427 if ctx.description():
1424 1428 return ctx.description()
1425 1429 return commitforceeditor(repo, ctx, subs)
1426 1430
1427 1431 def commitforceeditor(repo, ctx, subs):
1428 1432 edittext = []
1429 1433 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1430 1434 if ctx.description():
1431 1435 edittext.append(ctx.description())
1432 1436 edittext.append("")
1433 1437 edittext.append("") # Empty line between message and comments.
1434 1438 edittext.append(_("HG: Enter commit message."
1435 1439 " Lines beginning with 'HG:' are removed."))
1436 1440 edittext.append(_("HG: Leave message empty to abort commit."))
1437 1441 edittext.append("HG: --")
1438 1442 edittext.append(_("HG: user: %s") % ctx.user())
1439 1443 if ctx.p2():
1440 1444 edittext.append(_("HG: branch merge"))
1441 1445 if ctx.branch():
1442 1446 edittext.append(_("HG: branch '%s'") % ctx.branch())
1443 1447 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1444 1448 edittext.extend([_("HG: added %s") % f for f in added])
1445 1449 edittext.extend([_("HG: changed %s") % f for f in modified])
1446 1450 edittext.extend([_("HG: removed %s") % f for f in removed])
1447 1451 if not added and not modified and not removed:
1448 1452 edittext.append(_("HG: no files changed"))
1449 1453 edittext.append("")
1450 1454 # run editor in the repository root
1451 1455 olddir = os.getcwd()
1452 1456 os.chdir(repo.root)
1453 1457 text = repo.ui.edit("\n".join(edittext), ctx.user())
1454 1458 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1455 1459 os.chdir(olddir)
1456 1460
1457 1461 if not text.strip():
1458 1462 raise util.Abort(_("empty commit message"))
1459 1463
1460 1464 return text
1461 1465
1462 1466 def revert(ui, repo, ctx, parents, *pats, **opts):
1463 1467 parent, p2 = parents
1464 1468 node = ctx.node()
1465 1469
1466 1470 mf = ctx.manifest()
1467 1471 if node == parent:
1468 1472 pmf = mf
1469 1473 else:
1470 1474 pmf = None
1471 1475
1472 1476 # need all matching names in dirstate and manifest of target rev,
1473 1477 # so have to walk both. do not print errors if files exist in one
1474 1478 # but not other.
1475 1479
1476 1480 names = {}
1477 1481
1478 1482 wlock = repo.wlock()
1479 1483 try:
1480 1484 # walk dirstate.
1481 1485
1482 1486 m = scmutil.match(repo[None], pats, opts)
1483 1487 m.bad = lambda x, y: False
1484 1488 for abs in repo.walk(m):
1485 1489 names[abs] = m.rel(abs), m.exact(abs)
1486 1490
1487 1491 # walk target manifest.
1488 1492
1489 1493 def badfn(path, msg):
1490 1494 if path in names:
1491 1495 return
1492 1496 if path in ctx.substate:
1493 1497 return
1494 1498 path_ = path + '/'
1495 1499 for f in names:
1496 1500 if f.startswith(path_):
1497 1501 return
1498 1502 ui.warn("%s: %s\n" % (m.rel(path), msg))
1499 1503
1500 1504 m = scmutil.match(ctx, pats, opts)
1501 1505 m.bad = badfn
1502 1506 for abs in ctx.walk(m):
1503 1507 if abs not in names:
1504 1508 names[abs] = m.rel(abs), m.exact(abs)
1505 1509
1506 1510 # get the list of subrepos that must be reverted
1507 1511 targetsubs = [s for s in ctx.substate if m(s)]
1508 1512 m = scmutil.matchfiles(repo, names)
1509 1513 changes = repo.status(match=m)[:4]
1510 1514 modified, added, removed, deleted = map(set, changes)
1511 1515
1512 1516 # if f is a rename, also revert the source
1513 1517 cwd = repo.getcwd()
1514 1518 for f in added:
1515 1519 src = repo.dirstate.copied(f)
1516 1520 if src and src not in names and repo.dirstate[src] == 'r':
1517 1521 removed.add(src)
1518 1522 names[src] = (repo.pathto(src, cwd), True)
1519 1523
1520 1524 def removeforget(abs):
1521 1525 if repo.dirstate[abs] == 'a':
1522 1526 return _('forgetting %s\n')
1523 1527 return _('removing %s\n')
1524 1528
1525 1529 revert = ([], _('reverting %s\n'))
1526 1530 add = ([], _('adding %s\n'))
1527 1531 remove = ([], removeforget)
1528 1532 undelete = ([], _('undeleting %s\n'))
1529 1533
1530 1534 disptable = (
1531 1535 # dispatch table:
1532 1536 # file state
1533 1537 # action if in target manifest
1534 1538 # action if not in target manifest
1535 1539 # make backup if in target manifest
1536 1540 # make backup if not in target manifest
1537 1541 (modified, revert, remove, True, True),
1538 1542 (added, revert, remove, True, False),
1539 1543 (removed, undelete, None, False, False),
1540 1544 (deleted, revert, remove, False, False),
1541 1545 )
1542 1546
1543 1547 for abs, (rel, exact) in sorted(names.items()):
1544 1548 mfentry = mf.get(abs)
1545 1549 target = repo.wjoin(abs)
1546 1550 def handle(xlist, dobackup):
1547 1551 xlist[0].append(abs)
1548 1552 if (dobackup and not opts.get('no_backup') and
1549 1553 os.path.lexists(target)):
1550 1554 bakname = "%s.orig" % rel
1551 1555 ui.note(_('saving current version of %s as %s\n') %
1552 1556 (rel, bakname))
1553 1557 if not opts.get('dry_run'):
1554 1558 util.rename(target, bakname)
1555 1559 if ui.verbose or not exact:
1556 1560 msg = xlist[1]
1557 1561 if not isinstance(msg, basestring):
1558 1562 msg = msg(abs)
1559 1563 ui.status(msg % rel)
1560 1564 for table, hitlist, misslist, backuphit, backupmiss in disptable:
1561 1565 if abs not in table:
1562 1566 continue
1563 1567 # file has changed in dirstate
1564 1568 if mfentry:
1565 1569 handle(hitlist, backuphit)
1566 1570 elif misslist is not None:
1567 1571 handle(misslist, backupmiss)
1568 1572 break
1569 1573 else:
1570 1574 if abs not in repo.dirstate:
1571 1575 if mfentry:
1572 1576 handle(add, True)
1573 1577 elif exact:
1574 1578 ui.warn(_('file not managed: %s\n') % rel)
1575 1579 continue
1576 1580 # file has not changed in dirstate
1577 1581 if node == parent:
1578 1582 if exact:
1579 1583 ui.warn(_('no changes needed to %s\n') % rel)
1580 1584 continue
1581 1585 if pmf is None:
1582 1586 # only need parent manifest in this unlikely case,
1583 1587 # so do not read by default
1584 1588 pmf = repo[parent].manifest()
1585 1589 if abs in pmf and mfentry:
1586 1590 # if version of file is same in parent and target
1587 1591 # manifests, do nothing
1588 1592 if (pmf[abs] != mfentry or
1589 1593 pmf.flags(abs) != mf.flags(abs)):
1590 1594 handle(revert, False)
1591 1595 else:
1592 1596 handle(remove, False)
1593 1597
1594 1598 if not opts.get('dry_run'):
1595 1599 def checkout(f):
1596 1600 fc = ctx[f]
1597 1601 repo.wwrite(f, fc.data(), fc.flags())
1598 1602
1599 1603 audit_path = scmutil.pathauditor(repo.root)
1600 1604 for f in remove[0]:
1601 1605 if repo.dirstate[f] == 'a':
1602 1606 repo.dirstate.drop(f)
1603 1607 continue
1604 1608 audit_path(f)
1605 1609 try:
1606 1610 util.unlinkpath(repo.wjoin(f))
1607 1611 except OSError:
1608 1612 pass
1609 1613 repo.dirstate.remove(f)
1610 1614
1611 1615 normal = None
1612 1616 if node == parent:
1613 1617 # We're reverting to our parent. If possible, we'd like status
1614 1618 # to report the file as clean. We have to use normallookup for
1615 1619 # merges to avoid losing information about merged/dirty files.
1616 1620 if p2 != nullid:
1617 1621 normal = repo.dirstate.normallookup
1618 1622 else:
1619 1623 normal = repo.dirstate.normal
1620 1624 for f in revert[0]:
1621 1625 checkout(f)
1622 1626 if normal:
1623 1627 normal(f)
1624 1628
1625 1629 for f in add[0]:
1626 1630 checkout(f)
1627 1631 repo.dirstate.add(f)
1628 1632
1629 1633 normal = repo.dirstate.normallookup
1630 1634 if node == parent and p2 == nullid:
1631 1635 normal = repo.dirstate.normal
1632 1636 for f in undelete[0]:
1633 1637 checkout(f)
1634 1638 normal(f)
1635 1639
1636 1640 if targetsubs:
1637 1641 # Revert the subrepos on the revert list
1638 1642 for sub in targetsubs:
1639 1643 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
1640 1644 finally:
1641 1645 wlock.release()
1642 1646
1643 1647 def command(table):
1644 1648 '''returns a function object bound to table which can be used as
1645 1649 a decorator for populating table as a command table'''
1646 1650
1647 1651 def cmd(name, options, synopsis=None):
1648 1652 def decorator(func):
1649 1653 if synopsis:
1650 1654 table[name] = func, options[:], synopsis
1651 1655 else:
1652 1656 table[name] = func, options[:]
1653 1657 return func
1654 1658 return decorator
1655 1659
1656 1660 return cmd
@@ -1,5782 +1,5783 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 _, gettext
11 11 import os, re, difflib, time, tempfile, errno
12 12 import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
13 13 import patch, help, url, 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
19 19 import random, setdiscovery, treediscovery, dagutil, pvec
20 20 import phases
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 ]
53 53
54 54 dryrunopts = [('n', 'dry-run', None,
55 55 _('do not perform actions, just print output'))]
56 56
57 57 remoteopts = [
58 58 ('e', 'ssh', '',
59 59 _('specify ssh command to use'), _('CMD')),
60 60 ('', 'remotecmd', '',
61 61 _('specify hg command to run on the remote side'), _('CMD')),
62 62 ('', 'insecure', None,
63 63 _('do not verify server certificate (ignoring web.cacerts config)')),
64 64 ]
65 65
66 66 walkopts = [
67 67 ('I', 'include', [],
68 68 _('include names matching the given patterns'), _('PATTERN')),
69 69 ('X', 'exclude', [],
70 70 _('exclude names matching the given patterns'), _('PATTERN')),
71 71 ]
72 72
73 73 commitopts = [
74 74 ('m', 'message', '',
75 75 _('use text as commit message'), _('TEXT')),
76 76 ('l', 'logfile', '',
77 77 _('read commit message from file'), _('FILE')),
78 78 ]
79 79
80 80 commitopts2 = [
81 81 ('d', 'date', '',
82 82 _('record the specified date as commit date'), _('DATE')),
83 83 ('u', 'user', '',
84 84 _('record the specified user as committer'), _('USER')),
85 85 ]
86 86
87 87 templateopts = [
88 88 ('', 'style', '',
89 89 _('display using template map file'), _('STYLE')),
90 90 ('', 'template', '',
91 91 _('display with template'), _('TEMPLATE')),
92 92 ]
93 93
94 94 logopts = [
95 95 ('p', 'patch', None, _('show patch')),
96 96 ('g', 'git', None, _('use git extended diff format')),
97 97 ('l', 'limit', '',
98 98 _('limit number of changes displayed'), _('NUM')),
99 99 ('M', 'no-merges', None, _('do not show merges')),
100 100 ('', 'stat', None, _('output diffstat-style summary of changes')),
101 101 ] + templateopts
102 102
103 103 diffopts = [
104 104 ('a', 'text', None, _('treat all files as text')),
105 105 ('g', 'git', None, _('use git extended diff format')),
106 106 ('', 'nodates', None, _('omit dates from diff headers'))
107 107 ]
108 108
109 109 diffwsopts = [
110 110 ('w', 'ignore-all-space', None,
111 111 _('ignore white space when comparing lines')),
112 112 ('b', 'ignore-space-change', None,
113 113 _('ignore changes in the amount of white space')),
114 114 ('B', 'ignore-blank-lines', None,
115 115 _('ignore changes whose lines are all blank')),
116 116 ]
117 117
118 118 diffopts2 = [
119 119 ('p', 'show-function', None, _('show which function each change is in')),
120 120 ('', 'reverse', None, _('produce a diff that undoes the changes')),
121 121 ] + diffwsopts + [
122 122 ('U', 'unified', '',
123 123 _('number of lines of context to show'), _('NUM')),
124 124 ('', 'stat', None, _('output diffstat-style summary of changes')),
125 125 ]
126 126
127 127 mergetoolopts = [
128 128 ('t', 'tool', '', _('specify merge tool')),
129 129 ]
130 130
131 131 similarityopts = [
132 132 ('s', 'similarity', '',
133 133 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
134 134 ]
135 135
136 136 subrepoopts = [
137 137 ('S', 'subrepos', None,
138 138 _('recurse into subrepositories'))
139 139 ]
140 140
141 141 # Commands start here, listed alphabetically
142 142
143 143 @command('^add',
144 144 walkopts + subrepoopts + dryrunopts,
145 145 _('[OPTION]... [FILE]...'))
146 146 def add(ui, repo, *pats, **opts):
147 147 """add the specified files on the next commit
148 148
149 149 Schedule files to be version controlled and added to the
150 150 repository.
151 151
152 152 The files will be added to the repository at the next commit. To
153 153 undo an add before that, see :hg:`forget`.
154 154
155 155 If no names are given, add all files to the repository.
156 156
157 157 .. container:: verbose
158 158
159 159 An example showing how new (unknown) files are added
160 160 automatically by :hg:`add`::
161 161
162 162 $ ls
163 163 foo.c
164 164 $ hg status
165 165 ? foo.c
166 166 $ hg add
167 167 adding foo.c
168 168 $ hg status
169 169 A foo.c
170 170
171 171 Returns 0 if all files are successfully added.
172 172 """
173 173
174 174 m = scmutil.match(repo[None], pats, opts)
175 175 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
176 176 opts.get('subrepos'), prefix="", explicitonly=False)
177 177 return rejected and 1 or 0
178 178
179 179 @command('addremove',
180 180 similarityopts + walkopts + dryrunopts,
181 181 _('[OPTION]... [FILE]...'))
182 182 def addremove(ui, repo, *pats, **opts):
183 183 """add all new files, delete all missing files
184 184
185 185 Add all new files and remove all missing files from the
186 186 repository.
187 187
188 188 New files are ignored if they match any of the patterns in
189 189 ``.hgignore``. As with add, these changes take effect at the next
190 190 commit.
191 191
192 192 Use the -s/--similarity option to detect renamed files. With a
193 193 parameter greater than 0, this compares every removed file with
194 194 every added file and records those similar enough as renames. This
195 195 option takes a percentage between 0 (disabled) and 100 (files must
196 196 be identical) as its parameter. Detecting renamed files this way
197 197 can be expensive. After using this option, :hg:`status -C` can be
198 198 used to check which files were identified as moved or renamed.
199 199 If this option is not specified, only renames of identical files
200 200 are detected.
201 201
202 202 Returns 0 if all files are successfully added.
203 203 """
204 204 try:
205 205 sim = float(opts.get('similarity') or 100)
206 206 except ValueError:
207 207 raise util.Abort(_('similarity must be a number'))
208 208 if sim < 0 or sim > 100:
209 209 raise util.Abort(_('similarity must be between 0 and 100'))
210 210 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
211 211
212 212 @command('^annotate|blame',
213 213 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
214 214 ('', 'follow', None,
215 215 _('follow copies/renames and list the filename (DEPRECATED)')),
216 216 ('', 'no-follow', None, _("don't follow copies and renames")),
217 217 ('a', 'text', None, _('treat all files as text')),
218 218 ('u', 'user', None, _('list the author (long with -v)')),
219 219 ('f', 'file', None, _('list the filename')),
220 220 ('d', 'date', None, _('list the date (short with -q)')),
221 221 ('n', 'number', None, _('list the revision number (default)')),
222 222 ('c', 'changeset', None, _('list the changeset')),
223 223 ('l', 'line-number', None, _('show line number at the first appearance'))
224 224 ] + diffwsopts + walkopts,
225 225 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
226 226 def annotate(ui, repo, *pats, **opts):
227 227 """show changeset information by line for each file
228 228
229 229 List changes in files, showing the revision id responsible for
230 230 each line
231 231
232 232 This command is useful for discovering when a change was made and
233 233 by whom.
234 234
235 235 Without the -a/--text option, annotate will avoid processing files
236 236 it detects as binary. With -a, annotate will annotate the file
237 237 anyway, although the results will probably be neither useful
238 238 nor desirable.
239 239
240 240 Returns 0 on success.
241 241 """
242 242 if opts.get('follow'):
243 243 # --follow is deprecated and now just an alias for -f/--file
244 244 # to mimic the behavior of Mercurial before version 1.5
245 245 opts['file'] = True
246 246
247 247 datefunc = ui.quiet and util.shortdate or util.datestr
248 248 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
249 249
250 250 if not pats:
251 251 raise util.Abort(_('at least one filename or pattern is required'))
252 252
253 253 hexfn = ui.debugflag and hex or short
254 254
255 255 opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
256 256 ('number', ' ', lambda x: str(x[0].rev())),
257 257 ('changeset', ' ', lambda x: hexfn(x[0].node())),
258 258 ('date', ' ', getdate),
259 259 ('file', ' ', lambda x: x[0].path()),
260 260 ('line_number', ':', lambda x: str(x[1])),
261 261 ]
262 262
263 263 if (not opts.get('user') and not opts.get('changeset')
264 264 and not opts.get('date') and not opts.get('file')):
265 265 opts['number'] = True
266 266
267 267 linenumber = opts.get('line_number') is not None
268 268 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
269 269 raise util.Abort(_('at least one of -n/-c is required for -l'))
270 270
271 271 funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
272 272 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
273 273
274 274 def bad(x, y):
275 275 raise util.Abort("%s: %s" % (x, y))
276 276
277 277 ctx = scmutil.revsingle(repo, opts.get('rev'))
278 278 m = scmutil.match(ctx, pats, opts)
279 279 m.bad = bad
280 280 follow = not opts.get('no_follow')
281 281 diffopts = patch.diffopts(ui, opts, section='annotate')
282 282 for abs in ctx.walk(m):
283 283 fctx = ctx[abs]
284 284 if not opts.get('text') and util.binary(fctx.data()):
285 285 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
286 286 continue
287 287
288 288 lines = fctx.annotate(follow=follow, linenumber=linenumber,
289 289 diffopts=diffopts)
290 290 pieces = []
291 291
292 292 for f, sep in funcmap:
293 293 l = [f(n) for n, dummy in lines]
294 294 if l:
295 295 sized = [(x, encoding.colwidth(x)) for x in l]
296 296 ml = max([w for x, w in sized])
297 297 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
298 298 for x, w in sized])
299 299
300 300 if pieces:
301 301 for p, l in zip(zip(*pieces), lines):
302 302 ui.write("%s: %s" % ("".join(p), l[1]))
303 303
304 304 if lines and not lines[-1][1].endswith('\n'):
305 305 ui.write('\n')
306 306
307 307 @command('archive',
308 308 [('', 'no-decode', None, _('do not pass files through decoders')),
309 309 ('p', 'prefix', '', _('directory prefix for files in archive'),
310 310 _('PREFIX')),
311 311 ('r', 'rev', '', _('revision to distribute'), _('REV')),
312 312 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
313 313 ] + subrepoopts + walkopts,
314 314 _('[OPTION]... DEST'))
315 315 def archive(ui, repo, dest, **opts):
316 316 '''create an unversioned archive of a repository revision
317 317
318 318 By default, the revision used is the parent of the working
319 319 directory; use -r/--rev to specify a different revision.
320 320
321 321 The archive type is automatically detected based on file
322 322 extension (or override using -t/--type).
323 323
324 324 .. container:: verbose
325 325
326 326 Examples:
327 327
328 328 - create a zip file containing the 1.0 release::
329 329
330 330 hg archive -r 1.0 project-1.0.zip
331 331
332 332 - create a tarball excluding .hg files::
333 333
334 334 hg archive project.tar.gz -X ".hg*"
335 335
336 336 Valid types are:
337 337
338 338 :``files``: a directory full of files (default)
339 339 :``tar``: tar archive, uncompressed
340 340 :``tbz2``: tar archive, compressed using bzip2
341 341 :``tgz``: tar archive, compressed using gzip
342 342 :``uzip``: zip archive, uncompressed
343 343 :``zip``: zip archive, compressed using deflate
344 344
345 345 The exact name of the destination archive or directory is given
346 346 using a format string; see :hg:`help export` for details.
347 347
348 348 Each member added to an archive file has a directory prefix
349 349 prepended. Use -p/--prefix to specify a format string for the
350 350 prefix. The default is the basename of the archive, with suffixes
351 351 removed.
352 352
353 353 Returns 0 on success.
354 354 '''
355 355
356 356 ctx = scmutil.revsingle(repo, opts.get('rev'))
357 357 if not ctx:
358 358 raise util.Abort(_('no working directory: please specify a revision'))
359 359 node = ctx.node()
360 360 dest = cmdutil.makefilename(repo, dest, node)
361 361 if os.path.realpath(dest) == repo.root:
362 362 raise util.Abort(_('repository root cannot be destination'))
363 363
364 364 kind = opts.get('type') or archival.guesskind(dest) or 'files'
365 365 prefix = opts.get('prefix')
366 366
367 367 if dest == '-':
368 368 if kind == 'files':
369 369 raise util.Abort(_('cannot archive plain files to stdout'))
370 370 dest = cmdutil.makefileobj(repo, dest)
371 371 if not prefix:
372 372 prefix = os.path.basename(repo.root) + '-%h'
373 373
374 374 prefix = cmdutil.makefilename(repo, prefix, node)
375 375 matchfn = scmutil.match(ctx, [], opts)
376 376 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
377 377 matchfn, prefix, subrepos=opts.get('subrepos'))
378 378
379 379 @command('backout',
380 380 [('', 'merge', None, _('merge with old dirstate parent after backout')),
381 381 ('', 'parent', '',
382 382 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
383 383 ('r', 'rev', '', _('revision to backout'), _('REV')),
384 384 ] + mergetoolopts + walkopts + commitopts + commitopts2,
385 385 _('[OPTION]... [-r] REV'))
386 386 def backout(ui, repo, node=None, rev=None, **opts):
387 387 '''reverse effect of earlier changeset
388 388
389 389 Prepare a new changeset with the effect of REV undone in the
390 390 current working directory.
391 391
392 392 If REV is the parent of the working directory, then this new changeset
393 393 is committed automatically. Otherwise, hg needs to merge the
394 394 changes and the merged result is left uncommitted.
395 395
396 396 .. note::
397 397 backout cannot be used to fix either an unwanted or
398 398 incorrect merge.
399 399
400 400 .. container:: verbose
401 401
402 402 By default, the pending changeset will have one parent,
403 403 maintaining a linear history. With --merge, the pending
404 404 changeset will instead have two parents: the old parent of the
405 405 working directory and a new child of REV that simply undoes REV.
406 406
407 407 Before version 1.7, the behavior without --merge was equivalent
408 408 to specifying --merge followed by :hg:`update --clean .` to
409 409 cancel the merge and leave the child of REV as a head to be
410 410 merged separately.
411 411
412 412 See :hg:`help dates` for a list of formats valid for -d/--date.
413 413
414 414 Returns 0 on success.
415 415 '''
416 416 if rev and node:
417 417 raise util.Abort(_("please specify just one revision"))
418 418
419 419 if not rev:
420 420 rev = node
421 421
422 422 if not rev:
423 423 raise util.Abort(_("please specify a revision to backout"))
424 424
425 425 date = opts.get('date')
426 426 if date:
427 427 opts['date'] = util.parsedate(date)
428 428
429 429 cmdutil.bailifchanged(repo)
430 430 node = scmutil.revsingle(repo, rev).node()
431 431
432 432 op1, op2 = repo.dirstate.parents()
433 433 a = repo.changelog.ancestor(op1, node)
434 434 if a != node:
435 435 raise util.Abort(_('cannot backout change on a different branch'))
436 436
437 437 p1, p2 = repo.changelog.parents(node)
438 438 if p1 == nullid:
439 439 raise util.Abort(_('cannot backout a change with no parents'))
440 440 if p2 != nullid:
441 441 if not opts.get('parent'):
442 442 raise util.Abort(_('cannot backout a merge changeset'))
443 443 p = repo.lookup(opts['parent'])
444 444 if p not in (p1, p2):
445 445 raise util.Abort(_('%s is not a parent of %s') %
446 446 (short(p), short(node)))
447 447 parent = p
448 448 else:
449 449 if opts.get('parent'):
450 450 raise util.Abort(_('cannot use --parent on non-merge changeset'))
451 451 parent = p1
452 452
453 453 # the backout should appear on the same branch
454 454 wlock = repo.wlock()
455 455 try:
456 456 branch = repo.dirstate.branch()
457 457 hg.clean(repo, node, show_stats=False)
458 458 repo.dirstate.setbranch(branch)
459 459 revert_opts = opts.copy()
460 460 revert_opts['date'] = None
461 461 revert_opts['all'] = True
462 462 revert_opts['rev'] = hex(parent)
463 463 revert_opts['no_backup'] = None
464 464 revert(ui, repo, **revert_opts)
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 commit_opts = opts.copy()
473 473 commit_opts['addremove'] = False
474 474 if not commit_opts['message'] and not commit_opts['logfile']:
475 475 # we don't translate commit messages
476 476 commit_opts['message'] = "Backed out changeset %s" % short(node)
477 477 commit_opts['force_editor'] = True
478 478 commit(ui, repo, **commit_opts)
479 479 def nice(node):
480 480 return '%d:%s' % (repo.changelog.rev(node), short(node))
481 481 ui.status(_('changeset %s backs out changeset %s\n') %
482 482 (nice(repo.changelog.tip()), nice(node)))
483 483 if opts.get('merge') and op1 != node:
484 484 hg.clean(repo, op1, show_stats=False)
485 485 ui.status(_('merging with changeset %s\n')
486 486 % nice(repo.changelog.tip()))
487 487 try:
488 488 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
489 489 return hg.merge(repo, hex(repo.changelog.tip()))
490 490 finally:
491 491 ui.setconfig('ui', 'forcemerge', '')
492 492 finally:
493 493 wlock.release()
494 494 return 0
495 495
496 496 @command('bisect',
497 497 [('r', 'reset', False, _('reset bisect state')),
498 498 ('g', 'good', False, _('mark changeset good')),
499 499 ('b', 'bad', False, _('mark changeset bad')),
500 500 ('s', 'skip', False, _('skip testing changeset')),
501 501 ('e', 'extend', False, _('extend the bisect range')),
502 502 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
503 503 ('U', 'noupdate', False, _('do not update to target'))],
504 504 _("[-gbsr] [-U] [-c CMD] [REV]"))
505 505 def bisect(ui, repo, rev=None, extra=None, command=None,
506 506 reset=None, good=None, bad=None, skip=None, extend=None,
507 507 noupdate=None):
508 508 """subdivision search of changesets
509 509
510 510 This command helps to find changesets which introduce problems. To
511 511 use, mark the earliest changeset you know exhibits the problem as
512 512 bad, then mark the latest changeset which is free from the problem
513 513 as good. Bisect will update your working directory to a revision
514 514 for testing (unless the -U/--noupdate option is specified). Once
515 515 you have performed tests, mark the working directory as good or
516 516 bad, and bisect will either update to another candidate changeset
517 517 or announce that it has found the bad revision.
518 518
519 519 As a shortcut, you can also use the revision argument to mark a
520 520 revision as good or bad without checking it out first.
521 521
522 522 If you supply a command, it will be used for automatic bisection.
523 523 The environment variable HG_NODE will contain the ID of the
524 524 changeset being tested. The exit status of the command will be
525 525 used to mark revisions as good or bad: status 0 means good, 125
526 526 means to skip the revision, 127 (command not found) will abort the
527 527 bisection, and any other non-zero exit status means the revision
528 528 is bad.
529 529
530 530 .. container:: verbose
531 531
532 532 Some examples:
533 533
534 534 - start a bisection with known bad revision 12, and good revision 34::
535 535
536 536 hg bisect --bad 34
537 537 hg bisect --good 12
538 538
539 539 - advance the current bisection by marking current revision as good or
540 540 bad::
541 541
542 542 hg bisect --good
543 543 hg bisect --bad
544 544
545 545 - mark the current revision, or a known revision, to be skipped (eg. if
546 546 that revision is not usable because of another issue)::
547 547
548 548 hg bisect --skip
549 549 hg bisect --skip 23
550 550
551 551 - forget the current bisection::
552 552
553 553 hg bisect --reset
554 554
555 555 - use 'make && make tests' to automatically find the first broken
556 556 revision::
557 557
558 558 hg bisect --reset
559 559 hg bisect --bad 34
560 560 hg bisect --good 12
561 561 hg bisect --command 'make && make tests'
562 562
563 563 - see all changesets whose states are already known in the current
564 564 bisection::
565 565
566 566 hg log -r "bisect(pruned)"
567 567
568 568 - see the changeset currently being bisected (especially useful
569 569 if running with -U/--noupdate)::
570 570
571 571 hg log -r "bisect(current)"
572 572
573 573 - see all changesets that took part in the current bisection::
574 574
575 575 hg log -r "bisect(range)"
576 576
577 577 - with the graphlog extension, you can even get a nice graph::
578 578
579 579 hg log --graph -r "bisect(range)"
580 580
581 581 See :hg:`help revsets` for more about the `bisect()` keyword.
582 582
583 583 Returns 0 on success.
584 584 """
585 585 def extendbisectrange(nodes, good):
586 586 # bisect is incomplete when it ends on a merge node and
587 587 # one of the parent was not checked.
588 588 parents = repo[nodes[0]].parents()
589 589 if len(parents) > 1:
590 590 side = good and state['bad'] or state['good']
591 591 num = len(set(i.node() for i in parents) & set(side))
592 592 if num == 1:
593 593 return parents[0].ancestor(parents[1])
594 594 return None
595 595
596 596 def print_result(nodes, good):
597 597 displayer = cmdutil.show_changeset(ui, repo, {})
598 598 if len(nodes) == 1:
599 599 # narrowed it down to a single revision
600 600 if good:
601 601 ui.write(_("The first good revision is:\n"))
602 602 else:
603 603 ui.write(_("The first bad revision is:\n"))
604 604 displayer.show(repo[nodes[0]])
605 605 extendnode = extendbisectrange(nodes, good)
606 606 if extendnode is not None:
607 607 ui.write(_('Not all ancestors of this changeset have been'
608 608 ' checked.\nUse bisect --extend to continue the '
609 609 'bisection from\nthe common ancestor, %s.\n')
610 610 % extendnode)
611 611 else:
612 612 # multiple possible revisions
613 613 if good:
614 614 ui.write(_("Due to skipped revisions, the first "
615 615 "good revision could be any of:\n"))
616 616 else:
617 617 ui.write(_("Due to skipped revisions, the first "
618 618 "bad revision could be any of:\n"))
619 619 for n in nodes:
620 620 displayer.show(repo[n])
621 621 displayer.close()
622 622
623 623 def check_state(state, interactive=True):
624 624 if not state['good'] or not state['bad']:
625 625 if (good or bad or skip or reset) and interactive:
626 626 return
627 627 if not state['good']:
628 628 raise util.Abort(_('cannot bisect (no known good revisions)'))
629 629 else:
630 630 raise util.Abort(_('cannot bisect (no known bad revisions)'))
631 631 return True
632 632
633 633 # backward compatibility
634 634 if rev in "good bad reset init".split():
635 635 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
636 636 cmd, rev, extra = rev, extra, None
637 637 if cmd == "good":
638 638 good = True
639 639 elif cmd == "bad":
640 640 bad = True
641 641 else:
642 642 reset = True
643 643 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
644 644 raise util.Abort(_('incompatible arguments'))
645 645
646 646 if reset:
647 647 p = repo.join("bisect.state")
648 648 if os.path.exists(p):
649 649 os.unlink(p)
650 650 return
651 651
652 652 state = hbisect.load_state(repo)
653 653
654 654 if command:
655 655 changesets = 1
656 656 try:
657 657 node = state['current'][0]
658 658 except LookupError:
659 659 if noupdate:
660 660 raise util.Abort(_('current bisect revision is unknown - '
661 661 'start a new bisect to fix'))
662 662 node, p2 = repo.dirstate.parents()
663 663 if p2 != nullid:
664 664 raise util.Abort(_('current bisect revision is a merge'))
665 665 try:
666 666 while changesets:
667 667 # update state
668 668 state['current'] = [node]
669 669 hbisect.save_state(repo, state)
670 670 status = util.system(command,
671 671 environ={'HG_NODE': hex(node)},
672 672 out=ui.fout)
673 673 if status == 125:
674 674 transition = "skip"
675 675 elif status == 0:
676 676 transition = "good"
677 677 # status < 0 means process was killed
678 678 elif status == 127:
679 679 raise util.Abort(_("failed to execute %s") % command)
680 680 elif status < 0:
681 681 raise util.Abort(_("%s killed") % command)
682 682 else:
683 683 transition = "bad"
684 684 ctx = scmutil.revsingle(repo, rev, node)
685 685 rev = None # clear for future iterations
686 686 state[transition].append(ctx.node())
687 687 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
688 688 check_state(state, interactive=False)
689 689 # bisect
690 690 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
691 691 # update to next check
692 692 node = nodes[0]
693 693 if not noupdate:
694 694 cmdutil.bailifchanged(repo)
695 695 hg.clean(repo, node, show_stats=False)
696 696 finally:
697 697 state['current'] = [node]
698 698 hbisect.save_state(repo, state)
699 699 print_result(nodes, good)
700 700 return
701 701
702 702 # update state
703 703
704 704 if rev:
705 705 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
706 706 else:
707 707 nodes = [repo.lookup('.')]
708 708
709 709 if good or bad or skip:
710 710 if good:
711 711 state['good'] += nodes
712 712 elif bad:
713 713 state['bad'] += nodes
714 714 elif skip:
715 715 state['skip'] += nodes
716 716 hbisect.save_state(repo, state)
717 717
718 718 if not check_state(state):
719 719 return
720 720
721 721 # actually bisect
722 722 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
723 723 if extend:
724 724 if not changesets:
725 725 extendnode = extendbisectrange(nodes, good)
726 726 if extendnode is not None:
727 727 ui.write(_("Extending search to changeset %d:%s\n"
728 728 % (extendnode.rev(), extendnode)))
729 729 state['current'] = [extendnode.node()]
730 730 hbisect.save_state(repo, state)
731 731 if noupdate:
732 732 return
733 733 cmdutil.bailifchanged(repo)
734 734 return hg.clean(repo, extendnode.node())
735 735 raise util.Abort(_("nothing to extend"))
736 736
737 737 if changesets == 0:
738 738 print_result(nodes, good)
739 739 else:
740 740 assert len(nodes) == 1 # only a single node can be tested next
741 741 node = nodes[0]
742 742 # compute the approximate number of remaining tests
743 743 tests, size = 0, 2
744 744 while size <= changesets:
745 745 tests, size = tests + 1, size * 2
746 746 rev = repo.changelog.rev(node)
747 747 ui.write(_("Testing changeset %d:%s "
748 748 "(%d changesets remaining, ~%d tests)\n")
749 749 % (rev, short(node), changesets, tests))
750 750 state['current'] = [node]
751 751 hbisect.save_state(repo, state)
752 752 if not noupdate:
753 753 cmdutil.bailifchanged(repo)
754 754 return hg.clean(repo, node)
755 755
756 756 @command('bookmarks',
757 757 [('f', 'force', False, _('force')),
758 758 ('r', 'rev', '', _('revision'), _('REV')),
759 759 ('d', 'delete', False, _('delete a given bookmark')),
760 760 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
761 761 ('i', 'inactive', False, _('mark a bookmark inactive'))],
762 762 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
763 763 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
764 764 rename=None, inactive=False):
765 765 '''track a line of development with movable markers
766 766
767 767 Bookmarks are pointers to certain commits that move when committing.
768 768 Bookmarks are local. They can be renamed, copied and deleted. It is
769 769 possible to use :hg:`merge NAME` to merge from a given bookmark, and
770 770 :hg:`update NAME` to update to a given bookmark.
771 771
772 772 You can use :hg:`bookmark NAME` to set a bookmark on the working
773 773 directory's parent revision with the given name. If you specify
774 774 a revision using -r REV (where REV may be an existing bookmark),
775 775 the bookmark is assigned to that revision.
776 776
777 777 Bookmarks can be pushed and pulled between repositories (see :hg:`help
778 778 push` and :hg:`help pull`). This requires both the local and remote
779 779 repositories to support bookmarks. For versions prior to 1.8, this means
780 780 the bookmarks extension must be enabled.
781 781
782 782 With -i/--inactive, the new bookmark will not be made the active
783 783 bookmark. If -r/--rev is given, the new bookmark will not be made
784 784 active even if -i/--inactive is not given. If no NAME is given, the
785 785 current active bookmark will be marked inactive.
786 786 '''
787 787 hexfn = ui.debugflag and hex or short
788 788 marks = repo._bookmarks
789 789 cur = repo.changectx('.').node()
790 790
791 791 if delete:
792 792 if mark is None:
793 793 raise util.Abort(_("bookmark name required"))
794 794 if mark not in marks:
795 795 raise util.Abort(_("bookmark '%s' does not exist") % mark)
796 796 if mark == repo._bookmarkcurrent:
797 797 bookmarks.setcurrent(repo, None)
798 798 del marks[mark]
799 799 bookmarks.write(repo)
800 800 return
801 801
802 802 if rename:
803 803 if rename not in marks:
804 804 raise util.Abort(_("bookmark '%s' does not exist") % rename)
805 805 if mark in marks and not force:
806 806 raise util.Abort(_("bookmark '%s' already exists "
807 807 "(use -f to force)") % mark)
808 808 if mark is None:
809 809 raise util.Abort(_("new bookmark name required"))
810 810 marks[mark] = marks[rename]
811 811 if repo._bookmarkcurrent == rename and not inactive:
812 812 bookmarks.setcurrent(repo, mark)
813 813 del marks[rename]
814 814 bookmarks.write(repo)
815 815 return
816 816
817 817 if mark is not None:
818 818 if "\n" in mark:
819 819 raise util.Abort(_("bookmark name cannot contain newlines"))
820 820 mark = mark.strip()
821 821 if not mark:
822 822 raise util.Abort(_("bookmark names cannot consist entirely of "
823 823 "whitespace"))
824 824 if inactive and mark == repo._bookmarkcurrent:
825 825 bookmarks.setcurrent(repo, None)
826 826 return
827 827 if mark in marks and not force:
828 828 raise util.Abort(_("bookmark '%s' already exists "
829 829 "(use -f to force)") % mark)
830 830 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
831 831 and not force):
832 832 raise util.Abort(
833 833 _("a bookmark cannot have the name of an existing branch"))
834 834 if rev:
835 835 marks[mark] = repo.lookup(rev)
836 836 else:
837 837 marks[mark] = cur
838 838 if not inactive and cur == marks[mark]:
839 839 bookmarks.setcurrent(repo, mark)
840 840 bookmarks.write(repo)
841 841 return
842 842
843 843 if mark is None:
844 844 if rev:
845 845 raise util.Abort(_("bookmark name required"))
846 846 if len(marks) == 0:
847 847 ui.status(_("no bookmarks set\n"))
848 848 else:
849 849 for bmark, n in sorted(marks.iteritems()):
850 850 current = repo._bookmarkcurrent
851 851 if bmark == current and n == cur:
852 852 prefix, label = '*', 'bookmarks.current'
853 853 else:
854 854 prefix, label = ' ', ''
855 855
856 856 if ui.quiet:
857 857 ui.write("%s\n" % bmark, label=label)
858 858 else:
859 859 ui.write(" %s %-25s %d:%s\n" % (
860 860 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
861 861 label=label)
862 862 return
863 863
864 864 @command('branch',
865 865 [('f', 'force', None,
866 866 _('set branch name even if it shadows an existing branch')),
867 867 ('C', 'clean', None, _('reset branch name to parent branch name'))],
868 868 _('[-fC] [NAME]'))
869 869 def branch(ui, repo, label=None, **opts):
870 870 """set or show the current branch name
871 871
872 872 .. note::
873 873 Branch names are permanent and global. Use :hg:`bookmark` to create a
874 874 light-weight bookmark instead. See :hg:`help glossary` for more
875 875 information about named branches and bookmarks.
876 876
877 877 With no argument, show the current branch name. With one argument,
878 878 set the working directory branch name (the branch will not exist
879 879 in the repository until the next commit). Standard practice
880 880 recommends that primary development take place on the 'default'
881 881 branch.
882 882
883 883 Unless -f/--force is specified, branch will not let you set a
884 884 branch name that already exists, even if it's inactive.
885 885
886 886 Use -C/--clean to reset the working directory branch to that of
887 887 the parent of the working directory, negating a previous branch
888 888 change.
889 889
890 890 Use the command :hg:`update` to switch to an existing branch. Use
891 891 :hg:`commit --close-branch` to mark this branch as closed.
892 892
893 893 Returns 0 on success.
894 894 """
895 895 if not opts.get('clean') and not label:
896 896 ui.write("%s\n" % repo.dirstate.branch())
897 897 return
898 898
899 899 wlock = repo.wlock()
900 900 try:
901 901 if opts.get('clean'):
902 902 label = repo[None].p1().branch()
903 903 repo.dirstate.setbranch(label)
904 904 ui.status(_('reset working directory to branch %s\n') % label)
905 905 elif label:
906 906 if not opts.get('force') and label in repo.branchmap():
907 907 if label not in [p.branch() for p in repo.parents()]:
908 908 raise util.Abort(_('a branch of the same name already'
909 909 ' exists'),
910 910 # i18n: "it" refers to an existing branch
911 911 hint=_("use 'hg update' to switch to it"))
912 912 repo.dirstate.setbranch(label)
913 913 ui.status(_('marked working directory as branch %s\n') % label)
914 914 ui.status(_('(branches are permanent and global, '
915 915 'did you want a bookmark?)\n'))
916 916 finally:
917 917 wlock.release()
918 918
919 919 @command('branches',
920 920 [('a', 'active', False, _('show only branches that have unmerged heads')),
921 921 ('c', 'closed', False, _('show normal and closed branches'))],
922 922 _('[-ac]'))
923 923 def branches(ui, repo, active=False, closed=False):
924 924 """list repository named branches
925 925
926 926 List the repository's named branches, indicating which ones are
927 927 inactive. If -c/--closed is specified, also list branches which have
928 928 been marked closed (see :hg:`commit --close-branch`).
929 929
930 930 If -a/--active is specified, only show active branches. A branch
931 931 is considered active if it contains repository heads.
932 932
933 933 Use the command :hg:`update` to switch to an existing branch.
934 934
935 935 Returns 0.
936 936 """
937 937
938 938 hexfunc = ui.debugflag and hex or short
939 939
940 940 activebranches = set([repo[n].branch() for n in repo.heads()])
941 941 branches = []
942 942 for tag, heads in repo.branchmap().iteritems():
943 943 for h in reversed(heads):
944 944 ctx = repo[h]
945 945 isopen = not ctx.closesbranch()
946 946 if isopen:
947 947 tip = ctx
948 948 break
949 949 else:
950 950 tip = repo[heads[-1]]
951 951 isactive = tag in activebranches and isopen
952 952 branches.append((tip, isactive, isopen))
953 953 branches.sort(key=lambda i: (i[1], i[0].rev(), i[0].branch(), i[2]),
954 954 reverse=True)
955 955
956 956 for ctx, isactive, isopen in branches:
957 957 if (not active) or isactive:
958 958 if isactive:
959 959 label = 'branches.active'
960 960 notice = ''
961 961 elif not isopen:
962 962 if not closed:
963 963 continue
964 964 label = 'branches.closed'
965 965 notice = _(' (closed)')
966 966 else:
967 967 label = 'branches.inactive'
968 968 notice = _(' (inactive)')
969 969 if ctx.branch() == repo.dirstate.branch():
970 970 label = 'branches.current'
971 971 rev = str(ctx.rev()).rjust(31 - encoding.colwidth(ctx.branch()))
972 972 rev = ui.label('%s:%s' % (rev, hexfunc(ctx.node())),
973 973 'log.changeset')
974 974 tag = ui.label(ctx.branch(), label)
975 975 if ui.quiet:
976 976 ui.write("%s\n" % tag)
977 977 else:
978 978 ui.write("%s %s%s\n" % (tag, rev, notice))
979 979
980 980 @command('bundle',
981 981 [('f', 'force', None, _('run even when the destination is unrelated')),
982 982 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
983 983 _('REV')),
984 984 ('b', 'branch', [], _('a specific branch you would like to bundle'),
985 985 _('BRANCH')),
986 986 ('', 'base', [],
987 987 _('a base changeset assumed to be available at the destination'),
988 988 _('REV')),
989 989 ('a', 'all', None, _('bundle all changesets in the repository')),
990 990 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
991 991 ] + remoteopts,
992 992 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
993 993 def bundle(ui, repo, fname, dest=None, **opts):
994 994 """create a changegroup file
995 995
996 996 Generate a compressed changegroup file collecting changesets not
997 997 known to be in another repository.
998 998
999 999 If you omit the destination repository, then hg assumes the
1000 1000 destination will have all the nodes you specify with --base
1001 1001 parameters. To create a bundle containing all changesets, use
1002 1002 -a/--all (or --base null).
1003 1003
1004 1004 You can change compression method with the -t/--type option.
1005 1005 The available compression methods are: none, bzip2, and
1006 1006 gzip (by default, bundles are compressed using bzip2).
1007 1007
1008 1008 The bundle file can then be transferred using conventional means
1009 1009 and applied to another repository with the unbundle or pull
1010 1010 command. This is useful when direct push and pull are not
1011 1011 available or when exporting an entire repository is undesirable.
1012 1012
1013 1013 Applying bundles preserves all changeset contents including
1014 1014 permissions, copy/rename information, and revision history.
1015 1015
1016 1016 Returns 0 on success, 1 if no changes found.
1017 1017 """
1018 1018 revs = None
1019 1019 if 'rev' in opts:
1020 1020 revs = scmutil.revrange(repo, opts['rev'])
1021 1021
1022 1022 bundletype = opts.get('type', 'bzip2').lower()
1023 1023 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1024 1024 bundletype = btypes.get(bundletype)
1025 1025 if bundletype not in changegroup.bundletypes:
1026 1026 raise util.Abort(_('unknown bundle type specified with --type'))
1027 1027
1028 1028 if opts.get('all'):
1029 1029 base = ['null']
1030 1030 else:
1031 1031 base = scmutil.revrange(repo, opts.get('base'))
1032 1032 if base:
1033 1033 if dest:
1034 1034 raise util.Abort(_("--base is incompatible with specifying "
1035 1035 "a destination"))
1036 1036 common = [repo.lookup(rev) for rev in base]
1037 1037 heads = revs and map(repo.lookup, revs) or revs
1038 1038 cg = repo.getbundle('bundle', heads=heads, common=common)
1039 1039 outgoing = None
1040 1040 else:
1041 1041 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1042 1042 dest, branches = hg.parseurl(dest, opts.get('branch'))
1043 1043 other = hg.peer(repo, opts, dest)
1044 1044 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
1045 1045 heads = revs and map(repo.lookup, revs) or revs
1046 1046 outgoing = discovery.findcommonoutgoing(repo, other,
1047 1047 onlyheads=heads,
1048 1048 force=opts.get('force'),
1049 1049 portable=True)
1050 1050 cg = repo.getlocalbundle('bundle', outgoing)
1051 1051 if not cg:
1052 1052 scmutil.nochangesfound(ui, outgoing and outgoing.excluded)
1053 1053 return 1
1054 1054
1055 1055 changegroup.writebundle(cg, fname, bundletype)
1056 1056
1057 1057 @command('cat',
1058 1058 [('o', 'output', '',
1059 1059 _('print output to file with formatted name'), _('FORMAT')),
1060 1060 ('r', 'rev', '', _('print the given revision'), _('REV')),
1061 1061 ('', 'decode', None, _('apply any matching decode filter')),
1062 1062 ] + walkopts,
1063 1063 _('[OPTION]... FILE...'))
1064 1064 def cat(ui, repo, file1, *pats, **opts):
1065 1065 """output the current or given revision of files
1066 1066
1067 1067 Print the specified files as they were at the given revision. If
1068 1068 no revision is given, the parent of the working directory is used,
1069 1069 or tip if no revision is checked out.
1070 1070
1071 1071 Output may be to a file, in which case the name of the file is
1072 1072 given using a format string. The formatting rules are the same as
1073 1073 for the export command, with the following additions:
1074 1074
1075 1075 :``%s``: basename of file being printed
1076 1076 :``%d``: dirname of file being printed, or '.' if in repository root
1077 1077 :``%p``: root-relative path name of file being printed
1078 1078
1079 1079 Returns 0 on success.
1080 1080 """
1081 1081 ctx = scmutil.revsingle(repo, opts.get('rev'))
1082 1082 err = 1
1083 1083 m = scmutil.match(ctx, (file1,) + pats, opts)
1084 1084 for abs in ctx.walk(m):
1085 1085 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1086 1086 pathname=abs)
1087 1087 data = ctx[abs].data()
1088 1088 if opts.get('decode'):
1089 1089 data = repo.wwritedata(abs, data)
1090 1090 fp.write(data)
1091 1091 fp.close()
1092 1092 err = 0
1093 1093 return err
1094 1094
1095 1095 @command('^clone',
1096 1096 [('U', 'noupdate', None,
1097 1097 _('the clone will include an empty working copy (only a repository)')),
1098 1098 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
1099 1099 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1100 1100 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1101 1101 ('', 'pull', None, _('use pull protocol to copy metadata')),
1102 1102 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1103 1103 ] + remoteopts,
1104 1104 _('[OPTION]... SOURCE [DEST]'))
1105 1105 def clone(ui, source, dest=None, **opts):
1106 1106 """make a copy of an existing repository
1107 1107
1108 1108 Create a copy of an existing repository in a new directory.
1109 1109
1110 1110 If no destination directory name is specified, it defaults to the
1111 1111 basename of the source.
1112 1112
1113 1113 The location of the source is added to the new repository's
1114 1114 ``.hg/hgrc`` file, as the default to be used for future pulls.
1115 1115
1116 1116 Only local paths and ``ssh://`` URLs are supported as
1117 1117 destinations. For ``ssh://`` destinations, no working directory or
1118 1118 ``.hg/hgrc`` will be created on the remote side.
1119 1119
1120 1120 To pull only a subset of changesets, specify one or more revisions
1121 1121 identifiers with -r/--rev or branches with -b/--branch. The
1122 1122 resulting clone will contain only the specified changesets and
1123 1123 their ancestors. These options (or 'clone src#rev dest') imply
1124 1124 --pull, even for local source repositories. Note that specifying a
1125 1125 tag will include the tagged changeset but not the changeset
1126 1126 containing the tag.
1127 1127
1128 1128 To check out a particular version, use -u/--update, or
1129 1129 -U/--noupdate to create a clone with no working directory.
1130 1130
1131 1131 .. container:: verbose
1132 1132
1133 1133 For efficiency, hardlinks are used for cloning whenever the
1134 1134 source and destination are on the same filesystem (note this
1135 1135 applies only to the repository data, not to the working
1136 1136 directory). Some filesystems, such as AFS, implement hardlinking
1137 1137 incorrectly, but do not report errors. In these cases, use the
1138 1138 --pull option to avoid hardlinking.
1139 1139
1140 1140 In some cases, you can clone repositories and the working
1141 1141 directory using full hardlinks with ::
1142 1142
1143 1143 $ cp -al REPO REPOCLONE
1144 1144
1145 1145 This is the fastest way to clone, but it is not always safe. The
1146 1146 operation is not atomic (making sure REPO is not modified during
1147 1147 the operation is up to you) and you have to make sure your
1148 1148 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1149 1149 so). Also, this is not compatible with certain extensions that
1150 1150 place their metadata under the .hg directory, such as mq.
1151 1151
1152 1152 Mercurial will update the working directory to the first applicable
1153 1153 revision from this list:
1154 1154
1155 1155 a) null if -U or the source repository has no changesets
1156 1156 b) if -u . and the source repository is local, the first parent of
1157 1157 the source repository's working directory
1158 1158 c) the changeset specified with -u (if a branch name, this means the
1159 1159 latest head of that branch)
1160 1160 d) the changeset specified with -r
1161 1161 e) the tipmost head specified with -b
1162 1162 f) the tipmost head specified with the url#branch source syntax
1163 1163 g) the tipmost head of the default branch
1164 1164 h) tip
1165 1165
1166 1166 Examples:
1167 1167
1168 1168 - clone a remote repository to a new directory named hg/::
1169 1169
1170 1170 hg clone http://selenic.com/hg
1171 1171
1172 1172 - create a lightweight local clone::
1173 1173
1174 1174 hg clone project/ project-feature/
1175 1175
1176 1176 - clone from an absolute path on an ssh server (note double-slash)::
1177 1177
1178 1178 hg clone ssh://user@server//home/projects/alpha/
1179 1179
1180 1180 - do a high-speed clone over a LAN while checking out a
1181 1181 specified version::
1182 1182
1183 1183 hg clone --uncompressed http://server/repo -u 1.5
1184 1184
1185 1185 - create a repository without changesets after a particular revision::
1186 1186
1187 1187 hg clone -r 04e544 experimental/ good/
1188 1188
1189 1189 - clone (and track) a particular named branch::
1190 1190
1191 1191 hg clone http://selenic.com/hg#stable
1192 1192
1193 1193 See :hg:`help urls` for details on specifying URLs.
1194 1194
1195 1195 Returns 0 on success.
1196 1196 """
1197 1197 if opts.get('noupdate') and opts.get('updaterev'):
1198 1198 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1199 1199
1200 1200 r = hg.clone(ui, opts, source, dest,
1201 1201 pull=opts.get('pull'),
1202 1202 stream=opts.get('uncompressed'),
1203 1203 rev=opts.get('rev'),
1204 1204 update=opts.get('updaterev') or not opts.get('noupdate'),
1205 1205 branch=opts.get('branch'))
1206 1206
1207 1207 return r is None
1208 1208
1209 1209 @command('^commit|ci',
1210 1210 [('A', 'addremove', None,
1211 1211 _('mark new/missing files as added/removed before committing')),
1212 1212 ('', 'close-branch', None,
1213 1213 _('mark a branch as closed, hiding it from the branch list')),
1214 1214 ('', 'amend', None, _('amend the parent of the working dir')),
1215 1215 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1216 1216 _('[OPTION]... [FILE]...'))
1217 1217 def commit(ui, repo, *pats, **opts):
1218 1218 """commit the specified files or all outstanding changes
1219 1219
1220 1220 Commit changes to the given files into the repository. Unlike a
1221 1221 centralized SCM, this operation is a local operation. See
1222 1222 :hg:`push` for a way to actively distribute your changes.
1223 1223
1224 1224 If a list of files is omitted, all changes reported by :hg:`status`
1225 1225 will be committed.
1226 1226
1227 1227 If you are committing the result of a merge, do not provide any
1228 1228 filenames or -I/-X filters.
1229 1229
1230 1230 If no commit message is specified, Mercurial starts your
1231 1231 configured editor where you can enter a message. In case your
1232 1232 commit fails, you will find a backup of your message in
1233 1233 ``.hg/last-message.txt``.
1234 1234
1235 1235 The --amend flag can be used to amend the parent of the
1236 1236 working directory with a new commit that contains the changes
1237 1237 in the parent in addition to those currently reported by :hg:`status`,
1238 1238 if there are any. The old commit is stored in a backup bundle in
1239 1239 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1240 1240 on how to restore it).
1241 1241
1242 1242 Message, user and date are taken from the amended commit unless
1243 1243 specified. When a message isn't specified on the command line,
1244 1244 the editor will open with the message of the amended commit.
1245 1245
1246 1246 It is not possible to amend public changesets (see :hg:`help phases`)
1247 1247 or changesets that have children.
1248 1248
1249 1249 See :hg:`help dates` for a list of formats valid for -d/--date.
1250 1250
1251 1251 Returns 0 on success, 1 if nothing changed.
1252 1252 """
1253 1253 if opts.get('subrepos'):
1254 1254 # Let --subrepos on the command line overide config setting.
1255 1255 ui.setconfig('ui', 'commitsubrepos', True)
1256 1256
1257 1257 extra = {}
1258 1258 if opts.get('close_branch'):
1259 1259 if repo['.'].node() not in repo.branchheads():
1260 1260 # The topo heads set is included in the branch heads set of the
1261 1261 # current branch, so it's sufficient to test branchheads
1262 1262 raise util.Abort(_('can only close branch heads'))
1263 1263 extra['close'] = 1
1264 1264
1265 1265 branch = repo[None].branch()
1266 1266 bheads = repo.branchheads(branch)
1267 1267
1268 1268 if opts.get('amend'):
1269 1269 if ui.configbool('ui', 'commitsubrepos'):
1270 1270 raise util.Abort(_('cannot amend recursively'))
1271 1271
1272 1272 old = repo['.']
1273 1273 if old.phase() == phases.public:
1274 1274 raise util.Abort(_('cannot amend public changesets'))
1275 1275 if len(old.parents()) > 1:
1276 1276 raise util.Abort(_('cannot amend merge changesets'))
1277 1277 if len(repo[None].parents()) > 1:
1278 1278 raise util.Abort(_('cannot amend while merging'))
1279 1279 if old.children():
1280 1280 raise util.Abort(_('cannot amend changeset with children'))
1281 1281
1282 1282 e = cmdutil.commiteditor
1283 1283 if opts.get('force_editor'):
1284 1284 e = cmdutil.commitforceeditor
1285 1285
1286 1286 def commitfunc(ui, repo, message, match, opts):
1287 1287 editor = e
1288 1288 # message contains text from -m or -l, if it's empty,
1289 1289 # open the editor with the old message
1290 1290 if not message:
1291 1291 message = old.description()
1292 1292 editor = cmdutil.commitforceeditor
1293 1293 return repo.commit(message,
1294 1294 opts.get('user') or old.user(),
1295 1295 opts.get('date') or old.date(),
1296 1296 match,
1297 1297 editor=editor,
1298 1298 extra=extra)
1299 1299
1300 1300 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1301 1301 if node == old.node():
1302 1302 ui.status(_("nothing changed\n"))
1303 1303 return 1
1304 1304 else:
1305 1305 e = cmdutil.commiteditor
1306 1306 if opts.get('force_editor'):
1307 1307 e = cmdutil.commitforceeditor
1308 1308
1309 1309 def commitfunc(ui, repo, message, match, opts):
1310 1310 return repo.commit(message, opts.get('user'), opts.get('date'),
1311 1311 match, editor=e, extra=extra)
1312 1312
1313 1313 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1314 1314
1315 1315 if not node:
1316 1316 stat = repo.status(match=scmutil.match(repo[None], pats, opts))
1317 1317 if stat[3]:
1318 1318 ui.status(_("nothing changed (%d missing files, see "
1319 1319 "'hg status')\n") % len(stat[3]))
1320 1320 else:
1321 1321 ui.status(_("nothing changed\n"))
1322 1322 return 1
1323 1323
1324 1324 ctx = repo[node]
1325 1325 parents = ctx.parents()
1326 1326
1327 1327 if (not opts.get('amend') and bheads and node not in bheads and not
1328 1328 [x for x in parents if x.node() in bheads and x.branch() == branch]):
1329 1329 ui.status(_('created new head\n'))
1330 1330 # The message is not printed for initial roots. For the other
1331 1331 # changesets, it is printed in the following situations:
1332 1332 #
1333 1333 # Par column: for the 2 parents with ...
1334 1334 # N: null or no parent
1335 1335 # B: parent is on another named branch
1336 1336 # C: parent is a regular non head changeset
1337 1337 # H: parent was a branch head of the current branch
1338 1338 # Msg column: whether we print "created new head" message
1339 1339 # In the following, it is assumed that there already exists some
1340 1340 # initial branch heads of the current branch, otherwise nothing is
1341 1341 # printed anyway.
1342 1342 #
1343 1343 # Par Msg Comment
1344 1344 # NN y additional topo root
1345 1345 #
1346 1346 # BN y additional branch root
1347 1347 # CN y additional topo head
1348 1348 # HN n usual case
1349 1349 #
1350 1350 # BB y weird additional branch root
1351 1351 # CB y branch merge
1352 1352 # HB n merge with named branch
1353 1353 #
1354 1354 # CC y additional head from merge
1355 1355 # CH n merge with a head
1356 1356 #
1357 1357 # HH n head merge: head count decreases
1358 1358
1359 1359 if not opts.get('close_branch'):
1360 1360 for r in parents:
1361 1361 if r.closesbranch() and r.branch() == branch:
1362 1362 ui.status(_('reopening closed branch head %d\n') % r)
1363 1363
1364 1364 if ui.debugflag:
1365 1365 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1366 1366 elif ui.verbose:
1367 1367 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1368 1368
1369 1369 @command('copy|cp',
1370 1370 [('A', 'after', None, _('record a copy that has already occurred')),
1371 1371 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1372 1372 ] + walkopts + dryrunopts,
1373 1373 _('[OPTION]... [SOURCE]... DEST'))
1374 1374 def copy(ui, repo, *pats, **opts):
1375 1375 """mark files as copied for the next commit
1376 1376
1377 1377 Mark dest as having copies of source files. If dest is a
1378 1378 directory, copies are put in that directory. If dest is a file,
1379 1379 the source must be a single file.
1380 1380
1381 1381 By default, this command copies the contents of files as they
1382 1382 exist in the working directory. If invoked with -A/--after, the
1383 1383 operation is recorded, but no copying is performed.
1384 1384
1385 1385 This command takes effect with the next commit. To undo a copy
1386 1386 before that, see :hg:`revert`.
1387 1387
1388 1388 Returns 0 on success, 1 if errors are encountered.
1389 1389 """
1390 1390 wlock = repo.wlock(False)
1391 1391 try:
1392 1392 return cmdutil.copy(ui, repo, pats, opts)
1393 1393 finally:
1394 1394 wlock.release()
1395 1395
1396 1396 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1397 1397 def debugancestor(ui, repo, *args):
1398 1398 """find the ancestor revision of two revisions in a given index"""
1399 1399 if len(args) == 3:
1400 1400 index, rev1, rev2 = args
1401 1401 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1402 1402 lookup = r.lookup
1403 1403 elif len(args) == 2:
1404 1404 if not repo:
1405 1405 raise util.Abort(_("there is no Mercurial repository here "
1406 1406 "(.hg not found)"))
1407 1407 rev1, rev2 = args
1408 1408 r = repo.changelog
1409 1409 lookup = repo.lookup
1410 1410 else:
1411 1411 raise util.Abort(_('either two or three arguments required'))
1412 1412 a = r.ancestor(lookup(rev1), lookup(rev2))
1413 1413 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1414 1414
1415 1415 @command('debugbuilddag',
1416 1416 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1417 1417 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1418 1418 ('n', 'new-file', None, _('add new file at each rev'))],
1419 1419 _('[OPTION]... [TEXT]'))
1420 1420 def debugbuilddag(ui, repo, text=None,
1421 1421 mergeable_file=False,
1422 1422 overwritten_file=False,
1423 1423 new_file=False):
1424 1424 """builds a repo with a given DAG from scratch in the current empty repo
1425 1425
1426 1426 The description of the DAG is read from stdin if not given on the
1427 1427 command line.
1428 1428
1429 1429 Elements:
1430 1430
1431 1431 - "+n" is a linear run of n nodes based on the current default parent
1432 1432 - "." is a single node based on the current default parent
1433 1433 - "$" resets the default parent to null (implied at the start);
1434 1434 otherwise the default parent is always the last node created
1435 1435 - "<p" sets the default parent to the backref p
1436 1436 - "*p" is a fork at parent p, which is a backref
1437 1437 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1438 1438 - "/p2" is a merge of the preceding node and p2
1439 1439 - ":tag" defines a local tag for the preceding node
1440 1440 - "@branch" sets the named branch for subsequent nodes
1441 1441 - "#...\\n" is a comment up to the end of the line
1442 1442
1443 1443 Whitespace between the above elements is ignored.
1444 1444
1445 1445 A backref is either
1446 1446
1447 1447 - a number n, which references the node curr-n, where curr is the current
1448 1448 node, or
1449 1449 - the name of a local tag you placed earlier using ":tag", or
1450 1450 - empty to denote the default parent.
1451 1451
1452 1452 All string valued-elements are either strictly alphanumeric, or must
1453 1453 be enclosed in double quotes ("..."), with "\\" as escape character.
1454 1454 """
1455 1455
1456 1456 if text is None:
1457 1457 ui.status(_("reading DAG from stdin\n"))
1458 1458 text = ui.fin.read()
1459 1459
1460 1460 cl = repo.changelog
1461 1461 if len(cl) > 0:
1462 1462 raise util.Abort(_('repository is not empty'))
1463 1463
1464 1464 # determine number of revs in DAG
1465 1465 total = 0
1466 1466 for type, data in dagparser.parsedag(text):
1467 1467 if type == 'n':
1468 1468 total += 1
1469 1469
1470 1470 if mergeable_file:
1471 1471 linesperrev = 2
1472 1472 # make a file with k lines per rev
1473 1473 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1474 1474 initialmergedlines.append("")
1475 1475
1476 1476 tags = []
1477 1477
1478 1478 lock = tr = None
1479 1479 try:
1480 1480 lock = repo.lock()
1481 1481 tr = repo.transaction("builddag")
1482 1482
1483 1483 at = -1
1484 1484 atbranch = 'default'
1485 1485 nodeids = []
1486 1486 id = 0
1487 1487 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1488 1488 for type, data in dagparser.parsedag(text):
1489 1489 if type == 'n':
1490 1490 ui.note('node %s\n' % str(data))
1491 1491 id, ps = data
1492 1492
1493 1493 files = []
1494 1494 fctxs = {}
1495 1495
1496 1496 p2 = None
1497 1497 if mergeable_file:
1498 1498 fn = "mf"
1499 1499 p1 = repo[ps[0]]
1500 1500 if len(ps) > 1:
1501 1501 p2 = repo[ps[1]]
1502 1502 pa = p1.ancestor(p2)
1503 1503 base, local, other = [x[fn].data() for x in pa, p1, p2]
1504 1504 m3 = simplemerge.Merge3Text(base, local, other)
1505 1505 ml = [l.strip() for l in m3.merge_lines()]
1506 1506 ml.append("")
1507 1507 elif at > 0:
1508 1508 ml = p1[fn].data().split("\n")
1509 1509 else:
1510 1510 ml = initialmergedlines
1511 1511 ml[id * linesperrev] += " r%i" % id
1512 1512 mergedtext = "\n".join(ml)
1513 1513 files.append(fn)
1514 1514 fctxs[fn] = context.memfilectx(fn, mergedtext)
1515 1515
1516 1516 if overwritten_file:
1517 1517 fn = "of"
1518 1518 files.append(fn)
1519 1519 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1520 1520
1521 1521 if new_file:
1522 1522 fn = "nf%i" % id
1523 1523 files.append(fn)
1524 1524 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1525 1525 if len(ps) > 1:
1526 1526 if not p2:
1527 1527 p2 = repo[ps[1]]
1528 1528 for fn in p2:
1529 1529 if fn.startswith("nf"):
1530 1530 files.append(fn)
1531 1531 fctxs[fn] = p2[fn]
1532 1532
1533 1533 def fctxfn(repo, cx, path):
1534 1534 return fctxs.get(path)
1535 1535
1536 1536 if len(ps) == 0 or ps[0] < 0:
1537 1537 pars = [None, None]
1538 1538 elif len(ps) == 1:
1539 1539 pars = [nodeids[ps[0]], None]
1540 1540 else:
1541 1541 pars = [nodeids[p] for p in ps]
1542 1542 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1543 1543 date=(id, 0),
1544 1544 user="debugbuilddag",
1545 1545 extra={'branch': atbranch})
1546 1546 nodeid = repo.commitctx(cx)
1547 1547 nodeids.append(nodeid)
1548 1548 at = id
1549 1549 elif type == 'l':
1550 1550 id, name = data
1551 1551 ui.note('tag %s\n' % name)
1552 1552 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1553 1553 elif type == 'a':
1554 1554 ui.note('branch %s\n' % data)
1555 1555 atbranch = data
1556 1556 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1557 1557 tr.close()
1558 1558
1559 1559 if tags:
1560 1560 repo.opener.write("localtags", "".join(tags))
1561 1561 finally:
1562 1562 ui.progress(_('building'), None)
1563 1563 release(tr, lock)
1564 1564
1565 1565 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1566 1566 def debugbundle(ui, bundlepath, all=None, **opts):
1567 1567 """lists the contents of a bundle"""
1568 1568 f = url.open(ui, bundlepath)
1569 1569 try:
1570 1570 gen = changegroup.readbundle(f, bundlepath)
1571 1571 if all:
1572 1572 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1573 1573
1574 1574 def showchunks(named):
1575 1575 ui.write("\n%s\n" % named)
1576 1576 chain = None
1577 1577 while True:
1578 1578 chunkdata = gen.deltachunk(chain)
1579 1579 if not chunkdata:
1580 1580 break
1581 1581 node = chunkdata['node']
1582 1582 p1 = chunkdata['p1']
1583 1583 p2 = chunkdata['p2']
1584 1584 cs = chunkdata['cs']
1585 1585 deltabase = chunkdata['deltabase']
1586 1586 delta = chunkdata['delta']
1587 1587 ui.write("%s %s %s %s %s %s\n" %
1588 1588 (hex(node), hex(p1), hex(p2),
1589 1589 hex(cs), hex(deltabase), len(delta)))
1590 1590 chain = node
1591 1591
1592 1592 chunkdata = gen.changelogheader()
1593 1593 showchunks("changelog")
1594 1594 chunkdata = gen.manifestheader()
1595 1595 showchunks("manifest")
1596 1596 while True:
1597 1597 chunkdata = gen.filelogheader()
1598 1598 if not chunkdata:
1599 1599 break
1600 1600 fname = chunkdata['filename']
1601 1601 showchunks(fname)
1602 1602 else:
1603 1603 chunkdata = gen.changelogheader()
1604 1604 chain = None
1605 1605 while True:
1606 1606 chunkdata = gen.deltachunk(chain)
1607 1607 if not chunkdata:
1608 1608 break
1609 1609 node = chunkdata['node']
1610 1610 ui.write("%s\n" % hex(node))
1611 1611 chain = node
1612 1612 finally:
1613 1613 f.close()
1614 1614
1615 1615 @command('debugcheckstate', [], '')
1616 1616 def debugcheckstate(ui, repo):
1617 1617 """validate the correctness of the current dirstate"""
1618 1618 parent1, parent2 = repo.dirstate.parents()
1619 1619 m1 = repo[parent1].manifest()
1620 1620 m2 = repo[parent2].manifest()
1621 1621 errors = 0
1622 1622 for f in repo.dirstate:
1623 1623 state = repo.dirstate[f]
1624 1624 if state in "nr" and f not in m1:
1625 1625 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1626 1626 errors += 1
1627 1627 if state in "a" and f in m1:
1628 1628 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1629 1629 errors += 1
1630 1630 if state in "m" and f not in m1 and f not in m2:
1631 1631 ui.warn(_("%s in state %s, but not in either manifest\n") %
1632 1632 (f, state))
1633 1633 errors += 1
1634 1634 for f in m1:
1635 1635 state = repo.dirstate[f]
1636 1636 if state not in "nrm":
1637 1637 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1638 1638 errors += 1
1639 1639 if errors:
1640 1640 error = _(".hg/dirstate inconsistent with current parent's manifest")
1641 1641 raise util.Abort(error)
1642 1642
1643 1643 @command('debugcommands', [], _('[COMMAND]'))
1644 1644 def debugcommands(ui, cmd='', *args):
1645 1645 """list all available commands and options"""
1646 1646 for cmd, vals in sorted(table.iteritems()):
1647 1647 cmd = cmd.split('|')[0].strip('^')
1648 1648 opts = ', '.join([i[1] for i in vals[1]])
1649 1649 ui.write('%s: %s\n' % (cmd, opts))
1650 1650
1651 1651 @command('debugcomplete',
1652 1652 [('o', 'options', None, _('show the command options'))],
1653 1653 _('[-o] CMD'))
1654 1654 def debugcomplete(ui, cmd='', **opts):
1655 1655 """returns the completion list associated with the given command"""
1656 1656
1657 1657 if opts.get('options'):
1658 1658 options = []
1659 1659 otables = [globalopts]
1660 1660 if cmd:
1661 1661 aliases, entry = cmdutil.findcmd(cmd, table, False)
1662 1662 otables.append(entry[1])
1663 1663 for t in otables:
1664 1664 for o in t:
1665 1665 if "(DEPRECATED)" in o[3]:
1666 1666 continue
1667 1667 if o[0]:
1668 1668 options.append('-%s' % o[0])
1669 1669 options.append('--%s' % o[1])
1670 1670 ui.write("%s\n" % "\n".join(options))
1671 1671 return
1672 1672
1673 1673 cmdlist = cmdutil.findpossible(cmd, table)
1674 1674 if ui.verbose:
1675 1675 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1676 1676 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1677 1677
1678 1678 @command('debugdag',
1679 1679 [('t', 'tags', None, _('use tags as labels')),
1680 1680 ('b', 'branches', None, _('annotate with branch names')),
1681 1681 ('', 'dots', None, _('use dots for runs')),
1682 1682 ('s', 'spaces', None, _('separate elements by spaces'))],
1683 1683 _('[OPTION]... [FILE [REV]...]'))
1684 1684 def debugdag(ui, repo, file_=None, *revs, **opts):
1685 1685 """format the changelog or an index DAG as a concise textual description
1686 1686
1687 1687 If you pass a revlog index, the revlog's DAG is emitted. If you list
1688 1688 revision numbers, they get labelled in the output as rN.
1689 1689
1690 1690 Otherwise, the changelog DAG of the current repo is emitted.
1691 1691 """
1692 1692 spaces = opts.get('spaces')
1693 1693 dots = opts.get('dots')
1694 1694 if file_:
1695 1695 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1696 1696 revs = set((int(r) for r in revs))
1697 1697 def events():
1698 1698 for r in rlog:
1699 1699 yield 'n', (r, list(set(p for p in rlog.parentrevs(r)
1700 1700 if p != -1)))
1701 1701 if r in revs:
1702 1702 yield 'l', (r, "r%i" % r)
1703 1703 elif repo:
1704 1704 cl = repo.changelog
1705 1705 tags = opts.get('tags')
1706 1706 branches = opts.get('branches')
1707 1707 if tags:
1708 1708 labels = {}
1709 1709 for l, n in repo.tags().items():
1710 1710 labels.setdefault(cl.rev(n), []).append(l)
1711 1711 def events():
1712 1712 b = "default"
1713 1713 for r in cl:
1714 1714 if branches:
1715 1715 newb = cl.read(cl.node(r))[5]['branch']
1716 1716 if newb != b:
1717 1717 yield 'a', newb
1718 1718 b = newb
1719 1719 yield 'n', (r, list(set(p for p in cl.parentrevs(r)
1720 1720 if p != -1)))
1721 1721 if tags:
1722 1722 ls = labels.get(r)
1723 1723 if ls:
1724 1724 for l in ls:
1725 1725 yield 'l', (r, l)
1726 1726 else:
1727 1727 raise util.Abort(_('need repo for changelog dag'))
1728 1728
1729 1729 for line in dagparser.dagtextlines(events(),
1730 1730 addspaces=spaces,
1731 1731 wraplabels=True,
1732 1732 wrapannotations=True,
1733 1733 wrapnonlinear=dots,
1734 1734 usedots=dots,
1735 1735 maxlinewidth=70):
1736 1736 ui.write(line)
1737 1737 ui.write("\n")
1738 1738
1739 1739 @command('debugdata',
1740 1740 [('c', 'changelog', False, _('open changelog')),
1741 1741 ('m', 'manifest', False, _('open manifest'))],
1742 1742 _('-c|-m|FILE REV'))
1743 1743 def debugdata(ui, repo, file_, rev = None, **opts):
1744 1744 """dump the contents of a data file revision"""
1745 1745 if opts.get('changelog') or opts.get('manifest'):
1746 1746 file_, rev = None, file_
1747 1747 elif rev is None:
1748 1748 raise error.CommandError('debugdata', _('invalid arguments'))
1749 1749 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1750 1750 try:
1751 1751 ui.write(r.revision(r.lookup(rev)))
1752 1752 except KeyError:
1753 1753 raise util.Abort(_('invalid revision identifier %s') % rev)
1754 1754
1755 1755 @command('debugdate',
1756 1756 [('e', 'extended', None, _('try extended date formats'))],
1757 1757 _('[-e] DATE [RANGE]'))
1758 1758 def debugdate(ui, date, range=None, **opts):
1759 1759 """parse and display a date"""
1760 1760 if opts["extended"]:
1761 1761 d = util.parsedate(date, util.extendeddateformats)
1762 1762 else:
1763 1763 d = util.parsedate(date)
1764 1764 ui.write("internal: %s %s\n" % d)
1765 1765 ui.write("standard: %s\n" % util.datestr(d))
1766 1766 if range:
1767 1767 m = util.matchdate(range)
1768 1768 ui.write("match: %s\n" % m(d[0]))
1769 1769
1770 1770 @command('debugdiscovery',
1771 1771 [('', 'old', None, _('use old-style discovery')),
1772 1772 ('', 'nonheads', None,
1773 1773 _('use old-style discovery with non-heads included')),
1774 1774 ] + remoteopts,
1775 1775 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1776 1776 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1777 1777 """runs the changeset discovery protocol in isolation"""
1778 1778 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
1779 1779 opts.get('branch'))
1780 1780 remote = hg.peer(repo, opts, remoteurl)
1781 1781 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1782 1782
1783 1783 # make sure tests are repeatable
1784 1784 random.seed(12323)
1785 1785
1786 1786 def doit(localheads, remoteheads):
1787 1787 if opts.get('old'):
1788 1788 if localheads:
1789 1789 raise util.Abort('cannot use localheads with old style '
1790 1790 'discovery')
1791 1791 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1792 1792 force=True)
1793 1793 common = set(common)
1794 1794 if not opts.get('nonheads'):
1795 1795 ui.write("unpruned common: %s\n" % " ".join([short(n)
1796 1796 for n in common]))
1797 1797 dag = dagutil.revlogdag(repo.changelog)
1798 1798 all = dag.ancestorset(dag.internalizeall(common))
1799 1799 common = dag.externalizeall(dag.headsetofconnecteds(all))
1800 1800 else:
1801 1801 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1802 1802 common = set(common)
1803 1803 rheads = set(hds)
1804 1804 lheads = set(repo.heads())
1805 1805 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1806 1806 if lheads <= common:
1807 1807 ui.write("local is subset\n")
1808 1808 elif rheads <= common:
1809 1809 ui.write("remote is subset\n")
1810 1810
1811 1811 serverlogs = opts.get('serverlog')
1812 1812 if serverlogs:
1813 1813 for filename in serverlogs:
1814 1814 logfile = open(filename, 'r')
1815 1815 try:
1816 1816 line = logfile.readline()
1817 1817 while line:
1818 1818 parts = line.strip().split(';')
1819 1819 op = parts[1]
1820 1820 if op == 'cg':
1821 1821 pass
1822 1822 elif op == 'cgss':
1823 1823 doit(parts[2].split(' '), parts[3].split(' '))
1824 1824 elif op == 'unb':
1825 1825 doit(parts[3].split(' '), parts[2].split(' '))
1826 1826 line = logfile.readline()
1827 1827 finally:
1828 1828 logfile.close()
1829 1829
1830 1830 else:
1831 1831 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1832 1832 opts.get('remote_head'))
1833 1833 localrevs = opts.get('local_head')
1834 1834 doit(localrevs, remoterevs)
1835 1835
1836 1836 @command('debugfileset', [], ('REVSPEC'))
1837 1837 def debugfileset(ui, repo, expr):
1838 1838 '''parse and apply a fileset specification'''
1839 1839 if ui.verbose:
1840 1840 tree = fileset.parse(expr)[0]
1841 1841 ui.note(tree, "\n")
1842 1842
1843 1843 for f in fileset.getfileset(repo[None], expr):
1844 1844 ui.write("%s\n" % f)
1845 1845
1846 1846 @command('debugfsinfo', [], _('[PATH]'))
1847 1847 def debugfsinfo(ui, path = "."):
1848 1848 """show information detected about current filesystem"""
1849 1849 util.writefile('.debugfsinfo', '')
1850 1850 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1851 1851 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1852 1852 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1853 1853 and 'yes' or 'no'))
1854 1854 os.unlink('.debugfsinfo')
1855 1855
1856 1856 @command('debuggetbundle',
1857 1857 [('H', 'head', [], _('id of head node'), _('ID')),
1858 1858 ('C', 'common', [], _('id of common node'), _('ID')),
1859 1859 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1860 1860 _('REPO FILE [-H|-C ID]...'))
1861 1861 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1862 1862 """retrieves a bundle from a repo
1863 1863
1864 1864 Every ID must be a full-length hex node id string. Saves the bundle to the
1865 1865 given file.
1866 1866 """
1867 1867 repo = hg.peer(ui, opts, repopath)
1868 1868 if not repo.capable('getbundle'):
1869 1869 raise util.Abort("getbundle() not supported by target repository")
1870 1870 args = {}
1871 1871 if common:
1872 1872 args['common'] = [bin(s) for s in common]
1873 1873 if head:
1874 1874 args['heads'] = [bin(s) for s in head]
1875 1875 bundle = repo.getbundle('debug', **args)
1876 1876
1877 1877 bundletype = opts.get('type', 'bzip2').lower()
1878 1878 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1879 1879 bundletype = btypes.get(bundletype)
1880 1880 if bundletype not in changegroup.bundletypes:
1881 1881 raise util.Abort(_('unknown bundle type specified with --type'))
1882 1882 changegroup.writebundle(bundle, bundlepath, bundletype)
1883 1883
1884 1884 @command('debugignore', [], '')
1885 1885 def debugignore(ui, repo, *values, **opts):
1886 1886 """display the combined ignore pattern"""
1887 1887 ignore = repo.dirstate._ignore
1888 1888 includepat = getattr(ignore, 'includepat', None)
1889 1889 if includepat is not None:
1890 1890 ui.write("%s\n" % includepat)
1891 1891 else:
1892 1892 raise util.Abort(_("no ignore patterns found"))
1893 1893
1894 1894 @command('debugindex',
1895 1895 [('c', 'changelog', False, _('open changelog')),
1896 1896 ('m', 'manifest', False, _('open manifest')),
1897 1897 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1898 1898 _('[-f FORMAT] -c|-m|FILE'))
1899 1899 def debugindex(ui, repo, file_ = None, **opts):
1900 1900 """dump the contents of an index file"""
1901 1901 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1902 1902 format = opts.get('format', 0)
1903 1903 if format not in (0, 1):
1904 1904 raise util.Abort(_("unknown format %d") % format)
1905 1905
1906 1906 generaldelta = r.version & revlog.REVLOGGENERALDELTA
1907 1907 if generaldelta:
1908 1908 basehdr = ' delta'
1909 1909 else:
1910 1910 basehdr = ' base'
1911 1911
1912 1912 if format == 0:
1913 1913 ui.write(" rev offset length " + basehdr + " linkrev"
1914 1914 " nodeid p1 p2\n")
1915 1915 elif format == 1:
1916 1916 ui.write(" rev flag offset length"
1917 1917 " size " + basehdr + " link p1 p2"
1918 1918 " nodeid\n")
1919 1919
1920 1920 for i in r:
1921 1921 node = r.node(i)
1922 1922 if generaldelta:
1923 1923 base = r.deltaparent(i)
1924 1924 else:
1925 1925 base = r.chainbase(i)
1926 1926 if format == 0:
1927 1927 try:
1928 1928 pp = r.parents(node)
1929 1929 except Exception:
1930 1930 pp = [nullid, nullid]
1931 1931 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1932 1932 i, r.start(i), r.length(i), base, r.linkrev(i),
1933 1933 short(node), short(pp[0]), short(pp[1])))
1934 1934 elif format == 1:
1935 1935 pr = r.parentrevs(i)
1936 1936 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1937 1937 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1938 1938 base, r.linkrev(i), pr[0], pr[1], short(node)))
1939 1939
1940 1940 @command('debugindexdot', [], _('FILE'))
1941 1941 def debugindexdot(ui, repo, file_):
1942 1942 """dump an index DAG as a graphviz dot file"""
1943 1943 r = None
1944 1944 if repo:
1945 1945 filelog = repo.file(file_)
1946 1946 if len(filelog):
1947 1947 r = filelog
1948 1948 if not r:
1949 1949 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1950 1950 ui.write("digraph G {\n")
1951 1951 for i in r:
1952 1952 node = r.node(i)
1953 1953 pp = r.parents(node)
1954 1954 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1955 1955 if pp[1] != nullid:
1956 1956 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1957 1957 ui.write("}\n")
1958 1958
1959 1959 @command('debuginstall', [], '')
1960 1960 def debuginstall(ui):
1961 1961 '''test Mercurial installation
1962 1962
1963 1963 Returns 0 on success.
1964 1964 '''
1965 1965
1966 1966 def writetemp(contents):
1967 1967 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1968 1968 f = os.fdopen(fd, "wb")
1969 1969 f.write(contents)
1970 1970 f.close()
1971 1971 return name
1972 1972
1973 1973 problems = 0
1974 1974
1975 1975 # encoding
1976 1976 ui.status(_("checking encoding (%s)...\n") % encoding.encoding)
1977 1977 try:
1978 1978 encoding.fromlocal("test")
1979 1979 except util.Abort, inst:
1980 1980 ui.write(" %s\n" % inst)
1981 1981 ui.write(_(" (check that your locale is properly set)\n"))
1982 1982 problems += 1
1983 1983
1984 1984 # compiled modules
1985 1985 ui.status(_("checking installed modules (%s)...\n")
1986 1986 % os.path.dirname(__file__))
1987 1987 try:
1988 1988 import bdiff, mpatch, base85, osutil
1989 1989 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1990 1990 except Exception, inst:
1991 1991 ui.write(" %s\n" % inst)
1992 1992 ui.write(_(" One or more extensions could not be found"))
1993 1993 ui.write(_(" (check that you compiled the extensions)\n"))
1994 1994 problems += 1
1995 1995
1996 1996 # templates
1997 1997 import templater
1998 1998 p = templater.templatepath()
1999 1999 ui.status(_("checking templates (%s)...\n") % ' '.join(p))
2000 2000 try:
2001 2001 templater.templater(templater.templatepath("map-cmdline.default"))
2002 2002 except Exception, inst:
2003 2003 ui.write(" %s\n" % inst)
2004 2004 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
2005 2005 problems += 1
2006 2006
2007 2007 # editor
2008 2008 ui.status(_("checking commit editor...\n"))
2009 2009 editor = ui.geteditor()
2010 2010 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
2011 2011 if not cmdpath:
2012 2012 if editor == 'vi':
2013 2013 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
2014 2014 ui.write(_(" (specify a commit editor in your configuration"
2015 2015 " file)\n"))
2016 2016 else:
2017 2017 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
2018 2018 ui.write(_(" (specify a commit editor in your configuration"
2019 2019 " file)\n"))
2020 2020 problems += 1
2021 2021
2022 2022 # check username
2023 2023 ui.status(_("checking username...\n"))
2024 2024 try:
2025 2025 ui.username()
2026 2026 except util.Abort, e:
2027 2027 ui.write(" %s\n" % e)
2028 2028 ui.write(_(" (specify a username in your configuration file)\n"))
2029 2029 problems += 1
2030 2030
2031 2031 if not problems:
2032 2032 ui.status(_("no problems detected\n"))
2033 2033 else:
2034 2034 ui.write(_("%s problems detected,"
2035 2035 " please check your install!\n") % problems)
2036 2036
2037 2037 return problems
2038 2038
2039 2039 @command('debugknown', [], _('REPO ID...'))
2040 2040 def debugknown(ui, repopath, *ids, **opts):
2041 2041 """test whether node ids are known to a repo
2042 2042
2043 2043 Every ID must be a full-length hex node id string. Returns a list of 0s
2044 2044 and 1s indicating unknown/known.
2045 2045 """
2046 2046 repo = hg.peer(ui, opts, repopath)
2047 2047 if not repo.capable('known'):
2048 2048 raise util.Abort("known() not supported by target repository")
2049 2049 flags = repo.known([bin(s) for s in ids])
2050 2050 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
2051 2051
2052 2052 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
2053 2053 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2054 2054 '''access the pushkey key/value protocol
2055 2055
2056 2056 With two args, list the keys in the given namespace.
2057 2057
2058 2058 With five args, set a key to new if it currently is set to old.
2059 2059 Reports success or failure.
2060 2060 '''
2061 2061
2062 2062 target = hg.peer(ui, {}, repopath)
2063 2063 if keyinfo:
2064 2064 key, old, new = keyinfo
2065 2065 r = target.pushkey(namespace, key, old, new)
2066 2066 ui.status(str(r) + '\n')
2067 2067 return not r
2068 2068 else:
2069 2069 for k, v in target.listkeys(namespace).iteritems():
2070 2070 ui.write("%s\t%s\n" % (k.encode('string-escape'),
2071 2071 v.encode('string-escape')))
2072 2072
2073 2073 @command('debugpvec', [], _('A B'))
2074 2074 def debugpvec(ui, repo, a, b=None):
2075 2075 ca = scmutil.revsingle(repo, a)
2076 2076 cb = scmutil.revsingle(repo, b)
2077 2077 pa = pvec.ctxpvec(ca)
2078 2078 pb = pvec.ctxpvec(cb)
2079 2079 if pa == pb:
2080 2080 rel = "="
2081 2081 elif pa > pb:
2082 2082 rel = ">"
2083 2083 elif pa < pb:
2084 2084 rel = "<"
2085 2085 elif pa | pb:
2086 2086 rel = "|"
2087 2087 ui.write(_("a: %s\n") % pa)
2088 2088 ui.write(_("b: %s\n") % pb)
2089 2089 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2090 2090 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2091 2091 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2092 2092 pa.distance(pb), rel))
2093 2093
2094 2094 @command('debugrebuildstate',
2095 2095 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
2096 2096 _('[-r REV] [REV]'))
2097 2097 def debugrebuildstate(ui, repo, rev="tip"):
2098 2098 """rebuild the dirstate as it would look like for the given revision"""
2099 2099 ctx = scmutil.revsingle(repo, rev)
2100 2100 wlock = repo.wlock()
2101 2101 try:
2102 2102 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2103 2103 finally:
2104 2104 wlock.release()
2105 2105
2106 2106 @command('debugrename',
2107 2107 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2108 2108 _('[-r REV] FILE'))
2109 2109 def debugrename(ui, repo, file1, *pats, **opts):
2110 2110 """dump rename information"""
2111 2111
2112 2112 ctx = scmutil.revsingle(repo, opts.get('rev'))
2113 2113 m = scmutil.match(ctx, (file1,) + pats, opts)
2114 2114 for abs in ctx.walk(m):
2115 2115 fctx = ctx[abs]
2116 2116 o = fctx.filelog().renamed(fctx.filenode())
2117 2117 rel = m.rel(abs)
2118 2118 if o:
2119 2119 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2120 2120 else:
2121 2121 ui.write(_("%s not renamed\n") % rel)
2122 2122
2123 2123 @command('debugrevlog',
2124 2124 [('c', 'changelog', False, _('open changelog')),
2125 2125 ('m', 'manifest', False, _('open manifest')),
2126 2126 ('d', 'dump', False, _('dump index data'))],
2127 2127 _('-c|-m|FILE'))
2128 2128 def debugrevlog(ui, repo, file_ = None, **opts):
2129 2129 """show data and statistics about a revlog"""
2130 2130 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2131 2131
2132 2132 if opts.get("dump"):
2133 2133 numrevs = len(r)
2134 2134 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
2135 2135 " rawsize totalsize compression heads\n")
2136 2136 ts = 0
2137 2137 heads = set()
2138 2138 for rev in xrange(numrevs):
2139 2139 dbase = r.deltaparent(rev)
2140 2140 if dbase == -1:
2141 2141 dbase = rev
2142 2142 cbase = r.chainbase(rev)
2143 2143 p1, p2 = r.parentrevs(rev)
2144 2144 rs = r.rawsize(rev)
2145 2145 ts = ts + rs
2146 2146 heads -= set(r.parentrevs(rev))
2147 2147 heads.add(rev)
2148 2148 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
2149 2149 (rev, p1, p2, r.start(rev), r.end(rev),
2150 2150 r.start(dbase), r.start(cbase),
2151 2151 r.start(p1), r.start(p2),
2152 2152 rs, ts, ts / r.end(rev), len(heads)))
2153 2153 return 0
2154 2154
2155 2155 v = r.version
2156 2156 format = v & 0xFFFF
2157 2157 flags = []
2158 2158 gdelta = False
2159 2159 if v & revlog.REVLOGNGINLINEDATA:
2160 2160 flags.append('inline')
2161 2161 if v & revlog.REVLOGGENERALDELTA:
2162 2162 gdelta = True
2163 2163 flags.append('generaldelta')
2164 2164 if not flags:
2165 2165 flags = ['(none)']
2166 2166
2167 2167 nummerges = 0
2168 2168 numfull = 0
2169 2169 numprev = 0
2170 2170 nump1 = 0
2171 2171 nump2 = 0
2172 2172 numother = 0
2173 2173 nump1prev = 0
2174 2174 nump2prev = 0
2175 2175 chainlengths = []
2176 2176
2177 2177 datasize = [None, 0, 0L]
2178 2178 fullsize = [None, 0, 0L]
2179 2179 deltasize = [None, 0, 0L]
2180 2180
2181 2181 def addsize(size, l):
2182 2182 if l[0] is None or size < l[0]:
2183 2183 l[0] = size
2184 2184 if size > l[1]:
2185 2185 l[1] = size
2186 2186 l[2] += size
2187 2187
2188 2188 numrevs = len(r)
2189 2189 for rev in xrange(numrevs):
2190 2190 p1, p2 = r.parentrevs(rev)
2191 2191 delta = r.deltaparent(rev)
2192 2192 if format > 0:
2193 2193 addsize(r.rawsize(rev), datasize)
2194 2194 if p2 != nullrev:
2195 2195 nummerges += 1
2196 2196 size = r.length(rev)
2197 2197 if delta == nullrev:
2198 2198 chainlengths.append(0)
2199 2199 numfull += 1
2200 2200 addsize(size, fullsize)
2201 2201 else:
2202 2202 chainlengths.append(chainlengths[delta] + 1)
2203 2203 addsize(size, deltasize)
2204 2204 if delta == rev - 1:
2205 2205 numprev += 1
2206 2206 if delta == p1:
2207 2207 nump1prev += 1
2208 2208 elif delta == p2:
2209 2209 nump2prev += 1
2210 2210 elif delta == p1:
2211 2211 nump1 += 1
2212 2212 elif delta == p2:
2213 2213 nump2 += 1
2214 2214 elif delta != nullrev:
2215 2215 numother += 1
2216 2216
2217 2217 numdeltas = numrevs - numfull
2218 2218 numoprev = numprev - nump1prev - nump2prev
2219 2219 totalrawsize = datasize[2]
2220 2220 datasize[2] /= numrevs
2221 2221 fulltotal = fullsize[2]
2222 2222 fullsize[2] /= numfull
2223 2223 deltatotal = deltasize[2]
2224 2224 deltasize[2] /= numrevs - numfull
2225 2225 totalsize = fulltotal + deltatotal
2226 2226 avgchainlen = sum(chainlengths) / numrevs
2227 2227 compratio = totalrawsize / totalsize
2228 2228
2229 2229 basedfmtstr = '%%%dd\n'
2230 2230 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2231 2231
2232 2232 def dfmtstr(max):
2233 2233 return basedfmtstr % len(str(max))
2234 2234 def pcfmtstr(max, padding=0):
2235 2235 return basepcfmtstr % (len(str(max)), ' ' * padding)
2236 2236
2237 2237 def pcfmt(value, total):
2238 2238 return (value, 100 * float(value) / total)
2239 2239
2240 2240 ui.write('format : %d\n' % format)
2241 2241 ui.write('flags : %s\n' % ', '.join(flags))
2242 2242
2243 2243 ui.write('\n')
2244 2244 fmt = pcfmtstr(totalsize)
2245 2245 fmt2 = dfmtstr(totalsize)
2246 2246 ui.write('revisions : ' + fmt2 % numrevs)
2247 2247 ui.write(' merges : ' + fmt % pcfmt(nummerges, numrevs))
2248 2248 ui.write(' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs))
2249 2249 ui.write('revisions : ' + fmt2 % numrevs)
2250 2250 ui.write(' full : ' + fmt % pcfmt(numfull, numrevs))
2251 2251 ui.write(' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
2252 2252 ui.write('revision size : ' + fmt2 % totalsize)
2253 2253 ui.write(' full : ' + fmt % pcfmt(fulltotal, totalsize))
2254 2254 ui.write(' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
2255 2255
2256 2256 ui.write('\n')
2257 2257 fmt = dfmtstr(max(avgchainlen, compratio))
2258 2258 ui.write('avg chain length : ' + fmt % avgchainlen)
2259 2259 ui.write('compression ratio : ' + fmt % compratio)
2260 2260
2261 2261 if format > 0:
2262 2262 ui.write('\n')
2263 2263 ui.write('uncompressed data size (min/max/avg) : %d / %d / %d\n'
2264 2264 % tuple(datasize))
2265 2265 ui.write('full revision size (min/max/avg) : %d / %d / %d\n'
2266 2266 % tuple(fullsize))
2267 2267 ui.write('delta size (min/max/avg) : %d / %d / %d\n'
2268 2268 % tuple(deltasize))
2269 2269
2270 2270 if numdeltas > 0:
2271 2271 ui.write('\n')
2272 2272 fmt = pcfmtstr(numdeltas)
2273 2273 fmt2 = pcfmtstr(numdeltas, 4)
2274 2274 ui.write('deltas against prev : ' + fmt % pcfmt(numprev, numdeltas))
2275 2275 if numprev > 0:
2276 2276 ui.write(' where prev = p1 : ' + fmt2 % pcfmt(nump1prev,
2277 2277 numprev))
2278 2278 ui.write(' where prev = p2 : ' + fmt2 % pcfmt(nump2prev,
2279 2279 numprev))
2280 2280 ui.write(' other : ' + fmt2 % pcfmt(numoprev,
2281 2281 numprev))
2282 2282 if gdelta:
2283 2283 ui.write('deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas))
2284 2284 ui.write('deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas))
2285 2285 ui.write('deltas against other : ' + fmt % pcfmt(numother,
2286 2286 numdeltas))
2287 2287
2288 2288 @command('debugrevspec', [], ('REVSPEC'))
2289 2289 def debugrevspec(ui, repo, expr):
2290 2290 """parse and apply a revision specification
2291 2291
2292 2292 Use --verbose to print the parsed tree before and after aliases
2293 2293 expansion.
2294 2294 """
2295 2295 if ui.verbose:
2296 2296 tree = revset.parse(expr)[0]
2297 2297 ui.note(revset.prettyformat(tree), "\n")
2298 2298 newtree = revset.findaliases(ui, tree)
2299 2299 if newtree != tree:
2300 2300 ui.note(revset.prettyformat(newtree), "\n")
2301 2301 func = revset.match(ui, expr)
2302 2302 for c in func(repo, range(len(repo))):
2303 2303 ui.write("%s\n" % c)
2304 2304
2305 2305 @command('debugsetparents', [], _('REV1 [REV2]'))
2306 2306 def debugsetparents(ui, repo, rev1, rev2=None):
2307 2307 """manually set the parents of the current working directory
2308 2308
2309 2309 This is useful for writing repository conversion tools, but should
2310 2310 be used with care.
2311 2311
2312 2312 Returns 0 on success.
2313 2313 """
2314 2314
2315 2315 r1 = scmutil.revsingle(repo, rev1).node()
2316 2316 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2317 2317
2318 2318 wlock = repo.wlock()
2319 2319 try:
2320 2320 repo.setparents(r1, r2)
2321 2321 finally:
2322 2322 wlock.release()
2323 2323
2324 2324 @command('debugstate',
2325 2325 [('', 'nodates', None, _('do not display the saved mtime')),
2326 2326 ('', 'datesort', None, _('sort by saved mtime'))],
2327 2327 _('[OPTION]...'))
2328 2328 def debugstate(ui, repo, nodates=None, datesort=None):
2329 2329 """show the contents of the current dirstate"""
2330 2330 timestr = ""
2331 2331 showdate = not nodates
2332 2332 if datesort:
2333 2333 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2334 2334 else:
2335 2335 keyfunc = None # sort by filename
2336 2336 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2337 2337 if showdate:
2338 2338 if ent[3] == -1:
2339 2339 # Pad or slice to locale representation
2340 2340 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2341 2341 time.localtime(0)))
2342 2342 timestr = 'unset'
2343 2343 timestr = (timestr[:locale_len] +
2344 2344 ' ' * (locale_len - len(timestr)))
2345 2345 else:
2346 2346 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2347 2347 time.localtime(ent[3]))
2348 2348 if ent[1] & 020000:
2349 2349 mode = 'lnk'
2350 2350 else:
2351 2351 mode = '%3o' % (ent[1] & 0777 & ~util.umask)
2352 2352 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2353 2353 for f in repo.dirstate.copies():
2354 2354 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2355 2355
2356 2356 @command('debugsub',
2357 2357 [('r', 'rev', '',
2358 2358 _('revision to check'), _('REV'))],
2359 2359 _('[-r REV] [REV]'))
2360 2360 def debugsub(ui, repo, rev=None):
2361 2361 ctx = scmutil.revsingle(repo, rev, None)
2362 2362 for k, v in sorted(ctx.substate.items()):
2363 2363 ui.write('path %s\n' % k)
2364 2364 ui.write(' source %s\n' % v[0])
2365 2365 ui.write(' revision %s\n' % v[1])
2366 2366
2367 2367 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2368 2368 def debugwalk(ui, repo, *pats, **opts):
2369 2369 """show how files match on given patterns"""
2370 2370 m = scmutil.match(repo[None], pats, opts)
2371 2371 items = list(repo.walk(m))
2372 2372 if not items:
2373 2373 return
2374 2374 f = lambda fn: fn
2375 2375 if ui.configbool('ui', 'slash') and os.sep != '/':
2376 2376 f = lambda fn: util.normpath(fn)
2377 2377 fmt = 'f %%-%ds %%-%ds %%s' % (
2378 2378 max([len(abs) for abs in items]),
2379 2379 max([len(m.rel(abs)) for abs in items]))
2380 2380 for abs in items:
2381 2381 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2382 2382 ui.write("%s\n" % line.rstrip())
2383 2383
2384 2384 @command('debugwireargs',
2385 2385 [('', 'three', '', 'three'),
2386 2386 ('', 'four', '', 'four'),
2387 2387 ('', 'five', '', 'five'),
2388 2388 ] + remoteopts,
2389 2389 _('REPO [OPTIONS]... [ONE [TWO]]'))
2390 2390 def debugwireargs(ui, repopath, *vals, **opts):
2391 2391 repo = hg.peer(ui, opts, repopath)
2392 2392 for opt in remoteopts:
2393 2393 del opts[opt[1]]
2394 2394 args = {}
2395 2395 for k, v in opts.iteritems():
2396 2396 if v:
2397 2397 args[k] = v
2398 2398 # run twice to check that we don't mess up the stream for the next command
2399 2399 res1 = repo.debugwireargs(*vals, **args)
2400 2400 res2 = repo.debugwireargs(*vals, **args)
2401 2401 ui.write("%s\n" % res1)
2402 2402 if res1 != res2:
2403 2403 ui.warn("%s\n" % res2)
2404 2404
2405 2405 @command('^diff',
2406 2406 [('r', 'rev', [], _('revision'), _('REV')),
2407 2407 ('c', 'change', '', _('change made by revision'), _('REV'))
2408 2408 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2409 2409 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2410 2410 def diff(ui, repo, *pats, **opts):
2411 2411 """diff repository (or selected files)
2412 2412
2413 2413 Show differences between revisions for the specified files.
2414 2414
2415 2415 Differences between files are shown using the unified diff format.
2416 2416
2417 2417 .. note::
2418 2418 diff may generate unexpected results for merges, as it will
2419 2419 default to comparing against the working directory's first
2420 2420 parent changeset if no revisions are specified.
2421 2421
2422 2422 When two revision arguments are given, then changes are shown
2423 2423 between those revisions. If only one revision is specified then
2424 2424 that revision is compared to the working directory, and, when no
2425 2425 revisions are specified, the working directory files are compared
2426 2426 to its parent.
2427 2427
2428 2428 Alternatively you can specify -c/--change with a revision to see
2429 2429 the changes in that changeset relative to its first parent.
2430 2430
2431 2431 Without the -a/--text option, diff will avoid generating diffs of
2432 2432 files it detects as binary. With -a, diff will generate a diff
2433 2433 anyway, probably with undesirable results.
2434 2434
2435 2435 Use the -g/--git option to generate diffs in the git extended diff
2436 2436 format. For more information, read :hg:`help diffs`.
2437 2437
2438 2438 .. container:: verbose
2439 2439
2440 2440 Examples:
2441 2441
2442 2442 - compare a file in the current working directory to its parent::
2443 2443
2444 2444 hg diff foo.c
2445 2445
2446 2446 - compare two historical versions of a directory, with rename info::
2447 2447
2448 2448 hg diff --git -r 1.0:1.2 lib/
2449 2449
2450 2450 - get change stats relative to the last change on some date::
2451 2451
2452 2452 hg diff --stat -r "date('may 2')"
2453 2453
2454 2454 - diff all newly-added files that contain a keyword::
2455 2455
2456 2456 hg diff "set:added() and grep(GNU)"
2457 2457
2458 2458 - compare a revision and its parents::
2459 2459
2460 2460 hg diff -c 9353 # compare against first parent
2461 2461 hg diff -r 9353^:9353 # same using revset syntax
2462 2462 hg diff -r 9353^2:9353 # compare against the second parent
2463 2463
2464 2464 Returns 0 on success.
2465 2465 """
2466 2466
2467 2467 revs = opts.get('rev')
2468 2468 change = opts.get('change')
2469 2469 stat = opts.get('stat')
2470 2470 reverse = opts.get('reverse')
2471 2471
2472 2472 if revs and change:
2473 2473 msg = _('cannot specify --rev and --change at the same time')
2474 2474 raise util.Abort(msg)
2475 2475 elif change:
2476 2476 node2 = scmutil.revsingle(repo, change, None).node()
2477 2477 node1 = repo[node2].p1().node()
2478 2478 else:
2479 2479 node1, node2 = scmutil.revpair(repo, revs)
2480 2480
2481 2481 if reverse:
2482 2482 node1, node2 = node2, node1
2483 2483
2484 2484 diffopts = patch.diffopts(ui, opts)
2485 2485 m = scmutil.match(repo[node2], pats, opts)
2486 2486 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2487 2487 listsubrepos=opts.get('subrepos'))
2488 2488
2489 2489 @command('^export',
2490 2490 [('o', 'output', '',
2491 2491 _('print output to file with formatted name'), _('FORMAT')),
2492 2492 ('', 'switch-parent', None, _('diff against the second parent')),
2493 2493 ('r', 'rev', [], _('revisions to export'), _('REV')),
2494 2494 ] + diffopts,
2495 2495 _('[OPTION]... [-o OUTFILESPEC] [-r] REV...'))
2496 2496 def export(ui, repo, *changesets, **opts):
2497 2497 """dump the header and diffs for one or more changesets
2498 2498
2499 2499 Print the changeset header and diffs for one or more revisions.
2500 2500
2501 2501 The information shown in the changeset header is: author, date,
2502 2502 branch name (if non-default), changeset hash, parent(s) and commit
2503 2503 comment.
2504 2504
2505 2505 .. note::
2506 2506 export may generate unexpected diff output for merge
2507 2507 changesets, as it will compare the merge changeset against its
2508 2508 first parent only.
2509 2509
2510 2510 Output may be to a file, in which case the name of the file is
2511 2511 given using a format string. The formatting rules are as follows:
2512 2512
2513 2513 :``%%``: literal "%" character
2514 2514 :``%H``: changeset hash (40 hexadecimal digits)
2515 2515 :``%N``: number of patches being generated
2516 2516 :``%R``: changeset revision number
2517 2517 :``%b``: basename of the exporting repository
2518 2518 :``%h``: short-form changeset hash (12 hexadecimal digits)
2519 2519 :``%m``: first line of the commit message (only alphanumeric characters)
2520 2520 :``%n``: zero-padded sequence number, starting at 1
2521 2521 :``%r``: zero-padded changeset revision number
2522 2522
2523 2523 Without the -a/--text option, export will avoid generating diffs
2524 2524 of files it detects as binary. With -a, export will generate a
2525 2525 diff anyway, probably with undesirable results.
2526 2526
2527 2527 Use the -g/--git option to generate diffs in the git extended diff
2528 2528 format. See :hg:`help diffs` for more information.
2529 2529
2530 2530 With the --switch-parent option, the diff will be against the
2531 2531 second parent. It can be useful to review a merge.
2532 2532
2533 2533 .. container:: verbose
2534 2534
2535 2535 Examples:
2536 2536
2537 2537 - use export and import to transplant a bugfix to the current
2538 2538 branch::
2539 2539
2540 2540 hg export -r 9353 | hg import -
2541 2541
2542 2542 - export all the changesets between two revisions to a file with
2543 2543 rename information::
2544 2544
2545 2545 hg export --git -r 123:150 > changes.txt
2546 2546
2547 2547 - split outgoing changes into a series of patches with
2548 2548 descriptive names::
2549 2549
2550 2550 hg export -r "outgoing()" -o "%n-%m.patch"
2551 2551
2552 2552 Returns 0 on success.
2553 2553 """
2554 2554 changesets += tuple(opts.get('rev', []))
2555 2555 revs = scmutil.revrange(repo, changesets)
2556 2556 if not revs:
2557 2557 raise util.Abort(_("export requires at least one changeset"))
2558 2558 if len(revs) > 1:
2559 2559 ui.note(_('exporting patches:\n'))
2560 2560 else:
2561 2561 ui.note(_('exporting patch:\n'))
2562 2562 cmdutil.export(repo, revs, template=opts.get('output'),
2563 2563 switch_parent=opts.get('switch_parent'),
2564 2564 opts=patch.diffopts(ui, opts))
2565 2565
2566 2566 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2567 2567 def forget(ui, repo, *pats, **opts):
2568 2568 """forget the specified files on the next commit
2569 2569
2570 2570 Mark the specified files so they will no longer be tracked
2571 2571 after the next commit.
2572 2572
2573 2573 This only removes files from the current branch, not from the
2574 2574 entire project history, and it does not delete them from the
2575 2575 working directory.
2576 2576
2577 2577 To undo a forget before the next commit, see :hg:`add`.
2578 2578
2579 2579 .. container:: verbose
2580 2580
2581 2581 Examples:
2582 2582
2583 2583 - forget newly-added binary files::
2584 2584
2585 2585 hg forget "set:added() and binary()"
2586 2586
2587 2587 - forget files that would be excluded by .hgignore::
2588 2588
2589 2589 hg forget "set:hgignore()"
2590 2590
2591 2591 Returns 0 on success.
2592 2592 """
2593 2593
2594 2594 if not pats:
2595 2595 raise util.Abort(_('no files specified'))
2596 2596
2597 2597 m = scmutil.match(repo[None], pats, opts)
2598 2598 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2599 2599 return rejected and 1 or 0
2600 2600
2601 2601 @command(
2602 2602 'graft',
2603 2603 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2604 2604 ('c', 'continue', False, _('resume interrupted graft')),
2605 2605 ('e', 'edit', False, _('invoke editor on commit messages')),
2606 2606 ('', 'log', None, _('append graft info to log message')),
2607 2607 ('D', 'currentdate', False,
2608 2608 _('record the current date as commit date')),
2609 2609 ('U', 'currentuser', False,
2610 2610 _('record the current user as committer'), _('DATE'))]
2611 2611 + commitopts2 + mergetoolopts + dryrunopts,
2612 2612 _('[OPTION]... [-r] REV...'))
2613 2613 def graft(ui, repo, *revs, **opts):
2614 2614 '''copy changes from other branches onto the current branch
2615 2615
2616 2616 This command uses Mercurial's merge logic to copy individual
2617 2617 changes from other branches without merging branches in the
2618 2618 history graph. This is sometimes known as 'backporting' or
2619 2619 'cherry-picking'. By default, graft will copy user, date, and
2620 2620 description from the source changesets.
2621 2621
2622 2622 Changesets that are ancestors of the current revision, that have
2623 2623 already been grafted, or that are merges will be skipped.
2624 2624
2625 2625 If --log is specified, log messages will have a comment appended
2626 2626 of the form::
2627 2627
2628 2628 (grafted from CHANGESETHASH)
2629 2629
2630 2630 If a graft merge results in conflicts, the graft process is
2631 2631 interrupted so that the current merge can be manually resolved.
2632 2632 Once all conflicts are addressed, the graft process can be
2633 2633 continued with the -c/--continue option.
2634 2634
2635 2635 .. note::
2636 2636 The -c/--continue option does not reapply earlier options.
2637 2637
2638 2638 .. container:: verbose
2639 2639
2640 2640 Examples:
2641 2641
2642 2642 - copy a single change to the stable branch and edit its description::
2643 2643
2644 2644 hg update stable
2645 2645 hg graft --edit 9393
2646 2646
2647 2647 - graft a range of changesets with one exception, updating dates::
2648 2648
2649 2649 hg graft -D "2085::2093 and not 2091"
2650 2650
2651 2651 - continue a graft after resolving conflicts::
2652 2652
2653 2653 hg graft -c
2654 2654
2655 2655 - show the source of a grafted changeset::
2656 2656
2657 2657 hg log --debug -r tip
2658 2658
2659 2659 Returns 0 on successful completion.
2660 2660 '''
2661 2661
2662 2662 revs = list(revs)
2663 2663 revs.extend(opts['rev'])
2664 2664
2665 2665 if not opts.get('user') and opts.get('currentuser'):
2666 2666 opts['user'] = ui.username()
2667 2667 if not opts.get('date') and opts.get('currentdate'):
2668 2668 opts['date'] = "%d %d" % util.makedate()
2669 2669
2670 2670 editor = None
2671 2671 if opts.get('edit'):
2672 2672 editor = cmdutil.commitforceeditor
2673 2673
2674 2674 cont = False
2675 2675 if opts['continue']:
2676 2676 cont = True
2677 2677 if revs:
2678 2678 raise util.Abort(_("can't specify --continue and revisions"))
2679 2679 # read in unfinished revisions
2680 2680 try:
2681 2681 nodes = repo.opener.read('graftstate').splitlines()
2682 2682 revs = [repo[node].rev() for node in nodes]
2683 2683 except IOError, inst:
2684 2684 if inst.errno != errno.ENOENT:
2685 2685 raise
2686 2686 raise util.Abort(_("no graft state found, can't continue"))
2687 2687 else:
2688 2688 cmdutil.bailifchanged(repo)
2689 2689 if not revs:
2690 2690 raise util.Abort(_('no revisions specified'))
2691 2691 revs = scmutil.revrange(repo, revs)
2692 2692
2693 2693 # check for merges
2694 2694 for rev in repo.revs('%ld and merge()', revs):
2695 2695 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2696 2696 revs.remove(rev)
2697 2697 if not revs:
2698 2698 return -1
2699 2699
2700 2700 # check for ancestors of dest branch
2701 2701 for rev in repo.revs('::. and %ld', revs):
2702 2702 ui.warn(_('skipping ancestor revision %s\n') % rev)
2703 2703 revs.remove(rev)
2704 2704 if not revs:
2705 2705 return -1
2706 2706
2707 2707 # analyze revs for earlier grafts
2708 2708 ids = {}
2709 2709 for ctx in repo.set("%ld", revs):
2710 2710 ids[ctx.hex()] = ctx.rev()
2711 2711 n = ctx.extra().get('source')
2712 2712 if n:
2713 2713 ids[n] = ctx.rev()
2714 2714
2715 2715 # check ancestors for earlier grafts
2716 2716 ui.debug('scanning for duplicate grafts\n')
2717 2717 for ctx in repo.set("::. - ::%ld", revs):
2718 2718 n = ctx.extra().get('source')
2719 2719 if n in ids:
2720 2720 r = repo[n].rev()
2721 2721 if r in revs:
2722 2722 ui.warn(_('skipping already grafted revision %s\n') % r)
2723 2723 revs.remove(r)
2724 2724 elif ids[n] in revs:
2725 2725 ui.warn(_('skipping already grafted revision %s '
2726 2726 '(same origin %d)\n') % (ids[n], r))
2727 2727 revs.remove(ids[n])
2728 2728 elif ctx.hex() in ids:
2729 2729 r = ids[ctx.hex()]
2730 2730 ui.warn(_('skipping already grafted revision %s '
2731 2731 '(was grafted from %d)\n') % (r, ctx.rev()))
2732 2732 revs.remove(r)
2733 2733 if not revs:
2734 2734 return -1
2735 2735
2736 2736 wlock = repo.wlock()
2737 2737 try:
2738 2738 for pos, ctx in enumerate(repo.set("%ld", revs)):
2739 2739 current = repo['.']
2740 2740
2741 2741 ui.status(_('grafting revision %s\n') % ctx.rev())
2742 2742 if opts.get('dry_run'):
2743 2743 continue
2744 2744
2745 2745 # we don't merge the first commit when continuing
2746 2746 if not cont:
2747 2747 # perform the graft merge with p1(rev) as 'ancestor'
2748 2748 try:
2749 2749 # ui.forcemerge is an internal variable, do not document
2750 2750 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
2751 2751 stats = mergemod.update(repo, ctx.node(), True, True, False,
2752 2752 ctx.p1().node())
2753 2753 finally:
2754 2754 repo.ui.setconfig('ui', 'forcemerge', '')
2755 # drop the second merge parent
2756 repo.setparents(current.node(), nullid)
2757 repo.dirstate.write()
2758 # fix up dirstate for copies and renames
2759 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
2760 2755 # report any conflicts
2761 2756 if stats and stats[3] > 0:
2762 2757 # write out state for --continue
2763 2758 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2764 2759 repo.opener.write('graftstate', ''.join(nodelines))
2765 2760 raise util.Abort(
2766 2761 _("unresolved conflicts, can't continue"),
2767 2762 hint=_('use hg resolve and hg graft --continue'))
2768 2763 else:
2769 2764 cont = False
2770 2765
2766 # drop the second merge parent
2767 repo.setparents(current.node(), nullid)
2768 repo.dirstate.write()
2769 # fix up dirstate for copies and renames
2770 cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
2771
2771 2772 # commit
2772 2773 source = ctx.extra().get('source')
2773 2774 if not source:
2774 2775 source = ctx.hex()
2775 2776 extra = {'source': source}
2776 2777 user = ctx.user()
2777 2778 if opts.get('user'):
2778 2779 user = opts['user']
2779 2780 date = ctx.date()
2780 2781 if opts.get('date'):
2781 2782 date = opts['date']
2782 2783 message = ctx.description()
2783 2784 if opts.get('log'):
2784 2785 message += '\n(grafted from %s)' % ctx.hex()
2785 2786 node = repo.commit(text=message, user=user,
2786 2787 date=date, extra=extra, editor=editor)
2787 2788 if node is None:
2788 2789 ui.status(_('graft for revision %s is empty\n') % ctx.rev())
2789 2790 finally:
2790 2791 wlock.release()
2791 2792
2792 2793 # remove state when we complete successfully
2793 2794 if not opts.get('dry_run') and os.path.exists(repo.join('graftstate')):
2794 2795 util.unlinkpath(repo.join('graftstate'))
2795 2796
2796 2797 return 0
2797 2798
2798 2799 @command('grep',
2799 2800 [('0', 'print0', None, _('end fields with NUL')),
2800 2801 ('', 'all', None, _('print all revisions that match')),
2801 2802 ('a', 'text', None, _('treat all files as text')),
2802 2803 ('f', 'follow', None,
2803 2804 _('follow changeset history,'
2804 2805 ' or file history across copies and renames')),
2805 2806 ('i', 'ignore-case', None, _('ignore case when matching')),
2806 2807 ('l', 'files-with-matches', None,
2807 2808 _('print only filenames and revisions that match')),
2808 2809 ('n', 'line-number', None, _('print matching line numbers')),
2809 2810 ('r', 'rev', [],
2810 2811 _('only search files changed within revision range'), _('REV')),
2811 2812 ('u', 'user', None, _('list the author (long with -v)')),
2812 2813 ('d', 'date', None, _('list the date (short with -q)')),
2813 2814 ] + walkopts,
2814 2815 _('[OPTION]... PATTERN [FILE]...'))
2815 2816 def grep(ui, repo, pattern, *pats, **opts):
2816 2817 """search for a pattern in specified files and revisions
2817 2818
2818 2819 Search revisions of files for a regular expression.
2819 2820
2820 2821 This command behaves differently than Unix grep. It only accepts
2821 2822 Python/Perl regexps. It searches repository history, not the
2822 2823 working directory. It always prints the revision number in which a
2823 2824 match appears.
2824 2825
2825 2826 By default, grep only prints output for the first revision of a
2826 2827 file in which it finds a match. To get it to print every revision
2827 2828 that contains a change in match status ("-" for a match that
2828 2829 becomes a non-match, or "+" for a non-match that becomes a match),
2829 2830 use the --all flag.
2830 2831
2831 2832 Returns 0 if a match is found, 1 otherwise.
2832 2833 """
2833 2834 reflags = re.M
2834 2835 if opts.get('ignore_case'):
2835 2836 reflags |= re.I
2836 2837 try:
2837 2838 regexp = re.compile(pattern, reflags)
2838 2839 except re.error, inst:
2839 2840 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2840 2841 return 1
2841 2842 sep, eol = ':', '\n'
2842 2843 if opts.get('print0'):
2843 2844 sep = eol = '\0'
2844 2845
2845 2846 getfile = util.lrucachefunc(repo.file)
2846 2847
2847 2848 def matchlines(body):
2848 2849 begin = 0
2849 2850 linenum = 0
2850 2851 while True:
2851 2852 match = regexp.search(body, begin)
2852 2853 if not match:
2853 2854 break
2854 2855 mstart, mend = match.span()
2855 2856 linenum += body.count('\n', begin, mstart) + 1
2856 2857 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2857 2858 begin = body.find('\n', mend) + 1 or len(body) + 1
2858 2859 lend = begin - 1
2859 2860 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2860 2861
2861 2862 class linestate(object):
2862 2863 def __init__(self, line, linenum, colstart, colend):
2863 2864 self.line = line
2864 2865 self.linenum = linenum
2865 2866 self.colstart = colstart
2866 2867 self.colend = colend
2867 2868
2868 2869 def __hash__(self):
2869 2870 return hash((self.linenum, self.line))
2870 2871
2871 2872 def __eq__(self, other):
2872 2873 return self.line == other.line
2873 2874
2874 2875 matches = {}
2875 2876 copies = {}
2876 2877 def grepbody(fn, rev, body):
2877 2878 matches[rev].setdefault(fn, [])
2878 2879 m = matches[rev][fn]
2879 2880 for lnum, cstart, cend, line in matchlines(body):
2880 2881 s = linestate(line, lnum, cstart, cend)
2881 2882 m.append(s)
2882 2883
2883 2884 def difflinestates(a, b):
2884 2885 sm = difflib.SequenceMatcher(None, a, b)
2885 2886 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2886 2887 if tag == 'insert':
2887 2888 for i in xrange(blo, bhi):
2888 2889 yield ('+', b[i])
2889 2890 elif tag == 'delete':
2890 2891 for i in xrange(alo, ahi):
2891 2892 yield ('-', a[i])
2892 2893 elif tag == 'replace':
2893 2894 for i in xrange(alo, ahi):
2894 2895 yield ('-', a[i])
2895 2896 for i in xrange(blo, bhi):
2896 2897 yield ('+', b[i])
2897 2898
2898 2899 def display(fn, ctx, pstates, states):
2899 2900 rev = ctx.rev()
2900 2901 datefunc = ui.quiet and util.shortdate or util.datestr
2901 2902 found = False
2902 2903 filerevmatches = {}
2903 2904 def binary():
2904 2905 flog = getfile(fn)
2905 2906 return util.binary(flog.read(ctx.filenode(fn)))
2906 2907
2907 2908 if opts.get('all'):
2908 2909 iter = difflinestates(pstates, states)
2909 2910 else:
2910 2911 iter = [('', l) for l in states]
2911 2912 for change, l in iter:
2912 2913 cols = [fn, str(rev)]
2913 2914 before, match, after = None, None, None
2914 2915 if opts.get('line_number'):
2915 2916 cols.append(str(l.linenum))
2916 2917 if opts.get('all'):
2917 2918 cols.append(change)
2918 2919 if opts.get('user'):
2919 2920 cols.append(ui.shortuser(ctx.user()))
2920 2921 if opts.get('date'):
2921 2922 cols.append(datefunc(ctx.date()))
2922 2923 if opts.get('files_with_matches'):
2923 2924 c = (fn, rev)
2924 2925 if c in filerevmatches:
2925 2926 continue
2926 2927 filerevmatches[c] = 1
2927 2928 else:
2928 2929 before = l.line[:l.colstart]
2929 2930 match = l.line[l.colstart:l.colend]
2930 2931 after = l.line[l.colend:]
2931 2932 ui.write(sep.join(cols))
2932 2933 if before is not None:
2933 2934 if not opts.get('text') and binary():
2934 2935 ui.write(sep + " Binary file matches")
2935 2936 else:
2936 2937 ui.write(sep + before)
2937 2938 ui.write(match, label='grep.match')
2938 2939 ui.write(after)
2939 2940 ui.write(eol)
2940 2941 found = True
2941 2942 return found
2942 2943
2943 2944 skip = {}
2944 2945 revfiles = {}
2945 2946 matchfn = scmutil.match(repo[None], pats, opts)
2946 2947 found = False
2947 2948 follow = opts.get('follow')
2948 2949
2949 2950 def prep(ctx, fns):
2950 2951 rev = ctx.rev()
2951 2952 pctx = ctx.p1()
2952 2953 parent = pctx.rev()
2953 2954 matches.setdefault(rev, {})
2954 2955 matches.setdefault(parent, {})
2955 2956 files = revfiles.setdefault(rev, [])
2956 2957 for fn in fns:
2957 2958 flog = getfile(fn)
2958 2959 try:
2959 2960 fnode = ctx.filenode(fn)
2960 2961 except error.LookupError:
2961 2962 continue
2962 2963
2963 2964 copied = flog.renamed(fnode)
2964 2965 copy = follow and copied and copied[0]
2965 2966 if copy:
2966 2967 copies.setdefault(rev, {})[fn] = copy
2967 2968 if fn in skip:
2968 2969 if copy:
2969 2970 skip[copy] = True
2970 2971 continue
2971 2972 files.append(fn)
2972 2973
2973 2974 if fn not in matches[rev]:
2974 2975 grepbody(fn, rev, flog.read(fnode))
2975 2976
2976 2977 pfn = copy or fn
2977 2978 if pfn not in matches[parent]:
2978 2979 try:
2979 2980 fnode = pctx.filenode(pfn)
2980 2981 grepbody(pfn, parent, flog.read(fnode))
2981 2982 except error.LookupError:
2982 2983 pass
2983 2984
2984 2985 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2985 2986 rev = ctx.rev()
2986 2987 parent = ctx.p1().rev()
2987 2988 for fn in sorted(revfiles.get(rev, [])):
2988 2989 states = matches[rev][fn]
2989 2990 copy = copies.get(rev, {}).get(fn)
2990 2991 if fn in skip:
2991 2992 if copy:
2992 2993 skip[copy] = True
2993 2994 continue
2994 2995 pstates = matches.get(parent, {}).get(copy or fn, [])
2995 2996 if pstates or states:
2996 2997 r = display(fn, ctx, pstates, states)
2997 2998 found = found or r
2998 2999 if r and not opts.get('all'):
2999 3000 skip[fn] = True
3000 3001 if copy:
3001 3002 skip[copy] = True
3002 3003 del matches[rev]
3003 3004 del revfiles[rev]
3004 3005
3005 3006 return not found
3006 3007
3007 3008 @command('heads',
3008 3009 [('r', 'rev', '',
3009 3010 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3010 3011 ('t', 'topo', False, _('show topological heads only')),
3011 3012 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3012 3013 ('c', 'closed', False, _('show normal and closed branch heads')),
3013 3014 ] + templateopts,
3014 3015 _('[-ct] [-r STARTREV] [REV]...'))
3015 3016 def heads(ui, repo, *branchrevs, **opts):
3016 3017 """show current repository heads or show branch heads
3017 3018
3018 3019 With no arguments, show all repository branch heads.
3019 3020
3020 3021 Repository "heads" are changesets with no child changesets. They are
3021 3022 where development generally takes place and are the usual targets
3022 3023 for update and merge operations. Branch heads are changesets that have
3023 3024 no child changeset on the same branch.
3024 3025
3025 3026 If one or more REVs are given, only branch heads on the branches
3026 3027 associated with the specified changesets are shown. This means
3027 3028 that you can use :hg:`heads foo` to see the heads on a branch
3028 3029 named ``foo``.
3029 3030
3030 3031 If -c/--closed is specified, also show branch heads marked closed
3031 3032 (see :hg:`commit --close-branch`).
3032 3033
3033 3034 If STARTREV is specified, only those heads that are descendants of
3034 3035 STARTREV will be displayed.
3035 3036
3036 3037 If -t/--topo is specified, named branch mechanics will be ignored and only
3037 3038 changesets without children will be shown.
3038 3039
3039 3040 Returns 0 if matching heads are found, 1 if not.
3040 3041 """
3041 3042
3042 3043 start = None
3043 3044 if 'rev' in opts:
3044 3045 start = scmutil.revsingle(repo, opts['rev'], None).node()
3045 3046
3046 3047 if opts.get('topo'):
3047 3048 heads = [repo[h] for h in repo.heads(start)]
3048 3049 else:
3049 3050 heads = []
3050 3051 for branch in repo.branchmap():
3051 3052 heads += repo.branchheads(branch, start, opts.get('closed'))
3052 3053 heads = [repo[h] for h in heads]
3053 3054
3054 3055 if branchrevs:
3055 3056 branches = set(repo[br].branch() for br in branchrevs)
3056 3057 heads = [h for h in heads if h.branch() in branches]
3057 3058
3058 3059 if opts.get('active') and branchrevs:
3059 3060 dagheads = repo.heads(start)
3060 3061 heads = [h for h in heads if h.node() in dagheads]
3061 3062
3062 3063 if branchrevs:
3063 3064 haveheads = set(h.branch() for h in heads)
3064 3065 if branches - haveheads:
3065 3066 headless = ', '.join(b for b in branches - haveheads)
3066 3067 msg = _('no open branch heads found on branches %s')
3067 3068 if opts.get('rev'):
3068 3069 msg += _(' (started at %s)') % opts['rev']
3069 3070 ui.warn((msg + '\n') % headless)
3070 3071
3071 3072 if not heads:
3072 3073 return 1
3073 3074
3074 3075 heads = sorted(heads, key=lambda x: -x.rev())
3075 3076 displayer = cmdutil.show_changeset(ui, repo, opts)
3076 3077 for ctx in heads:
3077 3078 displayer.show(ctx)
3078 3079 displayer.close()
3079 3080
3080 3081 @command('help',
3081 3082 [('e', 'extension', None, _('show only help for extensions')),
3082 3083 ('c', 'command', None, _('show only help for commands')),
3083 3084 ('k', 'keyword', '', _('show topics matching keyword')),
3084 3085 ],
3085 3086 _('[-ec] [TOPIC]'))
3086 3087 def help_(ui, name=None, unknowncmd=False, full=True, **opts):
3087 3088 """show help for a given topic or a help overview
3088 3089
3089 3090 With no arguments, print a list of commands with short help messages.
3090 3091
3091 3092 Given a topic, extension, or command name, print help for that
3092 3093 topic.
3093 3094
3094 3095 Returns 0 if successful.
3095 3096 """
3096 3097
3097 3098 textwidth = min(ui.termwidth(), 80) - 2
3098 3099
3099 3100 def helpcmd(name):
3100 3101 try:
3101 3102 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
3102 3103 except error.AmbiguousCommand, inst:
3103 3104 # py3k fix: except vars can't be used outside the scope of the
3104 3105 # except block, nor can be used inside a lambda. python issue4617
3105 3106 prefix = inst.args[0]
3106 3107 select = lambda c: c.lstrip('^').startswith(prefix)
3107 3108 rst = helplist(select)
3108 3109 return rst
3109 3110
3110 3111 rst = []
3111 3112
3112 3113 # check if it's an invalid alias and display its error if it is
3113 3114 if getattr(entry[0], 'badalias', False):
3114 3115 if not unknowncmd:
3115 3116 ui.pushbuffer()
3116 3117 entry[0](ui)
3117 3118 rst.append(ui.popbuffer())
3118 3119 return rst
3119 3120
3120 3121 # synopsis
3121 3122 if len(entry) > 2:
3122 3123 if entry[2].startswith('hg'):
3123 3124 rst.append("%s\n" % entry[2])
3124 3125 else:
3125 3126 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
3126 3127 else:
3127 3128 rst.append('hg %s\n' % aliases[0])
3128 3129 # aliases
3129 3130 if full and not ui.quiet and len(aliases) > 1:
3130 3131 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
3131 3132 rst.append('\n')
3132 3133
3133 3134 # description
3134 3135 doc = gettext(entry[0].__doc__)
3135 3136 if not doc:
3136 3137 doc = _("(no help text available)")
3137 3138 if util.safehasattr(entry[0], 'definition'): # aliased command
3138 3139 if entry[0].definition.startswith('!'): # shell alias
3139 3140 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
3140 3141 else:
3141 3142 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
3142 3143 doc = doc.splitlines(True)
3143 3144 if ui.quiet or not full:
3144 3145 rst.append(doc[0])
3145 3146 else:
3146 3147 rst.extend(doc)
3147 3148 rst.append('\n')
3148 3149
3149 3150 # check if this command shadows a non-trivial (multi-line)
3150 3151 # extension help text
3151 3152 try:
3152 3153 mod = extensions.find(name)
3153 3154 doc = gettext(mod.__doc__) or ''
3154 3155 if '\n' in doc.strip():
3155 3156 msg = _('use "hg help -e %s" to show help for '
3156 3157 'the %s extension') % (name, name)
3157 3158 rst.append('\n%s\n' % msg)
3158 3159 except KeyError:
3159 3160 pass
3160 3161
3161 3162 # options
3162 3163 if not ui.quiet and entry[1]:
3163 3164 rst.append('\n%s\n\n' % _("options:"))
3164 3165 rst.append(help.optrst(entry[1], ui.verbose))
3165 3166
3166 3167 if ui.verbose:
3167 3168 rst.append('\n%s\n\n' % _("global options:"))
3168 3169 rst.append(help.optrst(globalopts, ui.verbose))
3169 3170
3170 3171 if not ui.verbose:
3171 3172 if not full:
3172 3173 rst.append(_('\nuse "hg help %s" to show the full help text\n')
3173 3174 % name)
3174 3175 elif not ui.quiet:
3175 3176 rst.append(_('\nuse "hg -v help %s" to show more info\n')
3176 3177 % name)
3177 3178 return rst
3178 3179
3179 3180
3180 3181 def helplist(select=None):
3181 3182 # list of commands
3182 3183 if name == "shortlist":
3183 3184 header = _('basic commands:\n\n')
3184 3185 else:
3185 3186 header = _('list of commands:\n\n')
3186 3187
3187 3188 h = {}
3188 3189 cmds = {}
3189 3190 for c, e in table.iteritems():
3190 3191 f = c.split("|", 1)[0]
3191 3192 if select and not select(f):
3192 3193 continue
3193 3194 if (not select and name != 'shortlist' and
3194 3195 e[0].__module__ != __name__):
3195 3196 continue
3196 3197 if name == "shortlist" and not f.startswith("^"):
3197 3198 continue
3198 3199 f = f.lstrip("^")
3199 3200 if not ui.debugflag and f.startswith("debug"):
3200 3201 continue
3201 3202 doc = e[0].__doc__
3202 3203 if doc and 'DEPRECATED' in doc and not ui.verbose:
3203 3204 continue
3204 3205 doc = gettext(doc)
3205 3206 if not doc:
3206 3207 doc = _("(no help text available)")
3207 3208 h[f] = doc.splitlines()[0].rstrip()
3208 3209 cmds[f] = c.lstrip("^")
3209 3210
3210 3211 rst = []
3211 3212 if not h:
3212 3213 if not ui.quiet:
3213 3214 rst.append(_('no commands defined\n'))
3214 3215 return rst
3215 3216
3216 3217 if not ui.quiet:
3217 3218 rst.append(header)
3218 3219 fns = sorted(h)
3219 3220 for f in fns:
3220 3221 if ui.verbose:
3221 3222 commands = cmds[f].replace("|",", ")
3222 3223 rst.append(" :%s: %s\n" % (commands, h[f]))
3223 3224 else:
3224 3225 rst.append(' :%s: %s\n' % (f, h[f]))
3225 3226
3226 3227 if not name:
3227 3228 exts = help.listexts(_('enabled extensions:'), extensions.enabled())
3228 3229 if exts:
3229 3230 rst.append('\n')
3230 3231 rst.extend(exts)
3231 3232
3232 3233 rst.append(_("\nadditional help topics:\n\n"))
3233 3234 topics = []
3234 3235 for names, header, doc in help.helptable:
3235 3236 topics.append((sorted(names, key=len, reverse=True)[0], header))
3236 3237 for t, desc in topics:
3237 3238 rst.append(" :%s: %s\n" % (t, desc))
3238 3239
3239 3240 optlist = []
3240 3241 if not ui.quiet:
3241 3242 if ui.verbose:
3242 3243 optlist.append((_("global options:"), globalopts))
3243 3244 if name == 'shortlist':
3244 3245 optlist.append((_('use "hg help" for the full list '
3245 3246 'of commands'), ()))
3246 3247 else:
3247 3248 if name == 'shortlist':
3248 3249 msg = _('use "hg help" for the full list of commands '
3249 3250 'or "hg -v" for details')
3250 3251 elif name and not full:
3251 3252 msg = _('use "hg help %s" to show the full help '
3252 3253 'text') % name
3253 3254 else:
3254 3255 msg = _('use "hg -v help%s" to show builtin aliases and '
3255 3256 'global options') % (name and " " + name or "")
3256 3257 optlist.append((msg, ()))
3257 3258
3258 3259 if optlist:
3259 3260 for title, options in optlist:
3260 3261 rst.append('\n%s\n' % title)
3261 3262 if options:
3262 3263 rst.append('\n%s\n' % help.optrst(options, ui.verbose))
3263 3264 return rst
3264 3265
3265 3266 def helptopic(name):
3266 3267 for names, header, doc in help.helptable:
3267 3268 if name in names:
3268 3269 break
3269 3270 else:
3270 3271 raise error.UnknownCommand(name)
3271 3272
3272 3273 rst = ["%s\n\n" % header]
3273 3274 # description
3274 3275 if not doc:
3275 3276 rst.append(" %s\n" % _("(no help text available)"))
3276 3277 if util.safehasattr(doc, '__call__'):
3277 3278 rst += [" %s\n" % l for l in doc().splitlines()]
3278 3279
3279 3280 try:
3280 3281 cmdutil.findcmd(name, table)
3281 3282 rst.append(_('\nuse "hg help -c %s" to see help for '
3282 3283 'the %s command\n') % (name, name))
3283 3284 except error.UnknownCommand:
3284 3285 pass
3285 3286 return rst
3286 3287
3287 3288 def helpext(name):
3288 3289 try:
3289 3290 mod = extensions.find(name)
3290 3291 doc = gettext(mod.__doc__) or _('no help text available')
3291 3292 except KeyError:
3292 3293 mod = None
3293 3294 doc = extensions.disabledext(name)
3294 3295 if not doc:
3295 3296 raise error.UnknownCommand(name)
3296 3297
3297 3298 if '\n' not in doc:
3298 3299 head, tail = doc, ""
3299 3300 else:
3300 3301 head, tail = doc.split('\n', 1)
3301 3302 rst = [_('%s extension - %s\n\n') % (name.split('.')[-1], head)]
3302 3303 if tail:
3303 3304 rst.extend(tail.splitlines(True))
3304 3305 rst.append('\n')
3305 3306
3306 3307 if mod:
3307 3308 try:
3308 3309 ct = mod.cmdtable
3309 3310 except AttributeError:
3310 3311 ct = {}
3311 3312 modcmds = set([c.split('|', 1)[0] for c in ct])
3312 3313 rst.extend(helplist(modcmds.__contains__))
3313 3314 else:
3314 3315 rst.append(_('use "hg help extensions" for information on enabling '
3315 3316 'extensions\n'))
3316 3317 return rst
3317 3318
3318 3319 def helpextcmd(name):
3319 3320 cmd, ext, mod = extensions.disabledcmd(ui, name,
3320 3321 ui.configbool('ui', 'strict'))
3321 3322 doc = gettext(mod.__doc__).splitlines()[0]
3322 3323
3323 3324 rst = help.listexts(_("'%s' is provided by the following "
3324 3325 "extension:") % cmd, {ext: doc}, indent=4)
3325 3326 rst.append('\n')
3326 3327 rst.append(_('use "hg help extensions" for information on enabling '
3327 3328 'extensions\n'))
3328 3329 return rst
3329 3330
3330 3331
3331 3332 rst = []
3332 3333 kw = opts.get('keyword')
3333 3334 if kw:
3334 3335 matches = help.topicmatch(kw)
3335 3336 for t, title in (('topics', _('Topics')),
3336 3337 ('commands', _('Commands')),
3337 3338 ('extensions', _('Extensions')),
3338 3339 ('extensioncommands', _('Extension Commands'))):
3339 3340 if matches[t]:
3340 3341 rst.append('%s:\n\n' % title)
3341 3342 rst.extend(minirst.maketable(sorted(matches[t]), 1))
3342 3343 rst.append('\n')
3343 3344 elif name and name != 'shortlist':
3344 3345 i = None
3345 3346 if unknowncmd:
3346 3347 queries = (helpextcmd,)
3347 3348 elif opts.get('extension'):
3348 3349 queries = (helpext,)
3349 3350 elif opts.get('command'):
3350 3351 queries = (helpcmd,)
3351 3352 else:
3352 3353 queries = (helptopic, helpcmd, helpext, helpextcmd)
3353 3354 for f in queries:
3354 3355 try:
3355 3356 rst = f(name)
3356 3357 i = None
3357 3358 break
3358 3359 except error.UnknownCommand, inst:
3359 3360 i = inst
3360 3361 if i:
3361 3362 raise i
3362 3363 else:
3363 3364 # program name
3364 3365 if not ui.quiet:
3365 3366 rst = [_("Mercurial Distributed SCM\n"), '\n']
3366 3367 rst.extend(helplist())
3367 3368
3368 3369 keep = ui.verbose and ['verbose'] or []
3369 3370 formatted, pruned = minirst.format(''.join(rst), textwidth, keep=keep)
3370 3371 ui.write(formatted)
3371 3372
3372 3373
3373 3374 @command('identify|id',
3374 3375 [('r', 'rev', '',
3375 3376 _('identify the specified revision'), _('REV')),
3376 3377 ('n', 'num', None, _('show local revision number')),
3377 3378 ('i', 'id', None, _('show global revision id')),
3378 3379 ('b', 'branch', None, _('show branch')),
3379 3380 ('t', 'tags', None, _('show tags')),
3380 3381 ('B', 'bookmarks', None, _('show bookmarks')),
3381 3382 ] + remoteopts,
3382 3383 _('[-nibtB] [-r REV] [SOURCE]'))
3383 3384 def identify(ui, repo, source=None, rev=None,
3384 3385 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3385 3386 """identify the working copy or specified revision
3386 3387
3387 3388 Print a summary identifying the repository state at REV using one or
3388 3389 two parent hash identifiers, followed by a "+" if the working
3389 3390 directory has uncommitted changes, the branch name (if not default),
3390 3391 a list of tags, and a list of bookmarks.
3391 3392
3392 3393 When REV is not given, print a summary of the current state of the
3393 3394 repository.
3394 3395
3395 3396 Specifying a path to a repository root or Mercurial bundle will
3396 3397 cause lookup to operate on that repository/bundle.
3397 3398
3398 3399 .. container:: verbose
3399 3400
3400 3401 Examples:
3401 3402
3402 3403 - generate a build identifier for the working directory::
3403 3404
3404 3405 hg id --id > build-id.dat
3405 3406
3406 3407 - find the revision corresponding to a tag::
3407 3408
3408 3409 hg id -n -r 1.3
3409 3410
3410 3411 - check the most recent revision of a remote repository::
3411 3412
3412 3413 hg id -r tip http://selenic.com/hg/
3413 3414
3414 3415 Returns 0 if successful.
3415 3416 """
3416 3417
3417 3418 if not repo and not source:
3418 3419 raise util.Abort(_("there is no Mercurial repository here "
3419 3420 "(.hg not found)"))
3420 3421
3421 3422 hexfunc = ui.debugflag and hex or short
3422 3423 default = not (num or id or branch or tags or bookmarks)
3423 3424 output = []
3424 3425 revs = []
3425 3426
3426 3427 if source:
3427 3428 source, branches = hg.parseurl(ui.expandpath(source))
3428 3429 repo = hg.peer(ui, opts, source)
3429 3430 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3430 3431
3431 3432 if not repo.local():
3432 3433 if num or branch or tags:
3433 3434 raise util.Abort(
3434 3435 _("can't query remote revision number, branch, or tags"))
3435 3436 if not rev and revs:
3436 3437 rev = revs[0]
3437 3438 if not rev:
3438 3439 rev = "tip"
3439 3440
3440 3441 remoterev = repo.lookup(rev)
3441 3442 if default or id:
3442 3443 output = [hexfunc(remoterev)]
3443 3444
3444 3445 def getbms():
3445 3446 bms = []
3446 3447
3447 3448 if 'bookmarks' in repo.listkeys('namespaces'):
3448 3449 hexremoterev = hex(remoterev)
3449 3450 bms = [bm for bm, bmr in repo.listkeys('bookmarks').iteritems()
3450 3451 if bmr == hexremoterev]
3451 3452
3452 3453 return bms
3453 3454
3454 3455 if bookmarks:
3455 3456 output.extend(getbms())
3456 3457 elif default and not ui.quiet:
3457 3458 # multiple bookmarks for a single parent separated by '/'
3458 3459 bm = '/'.join(getbms())
3459 3460 if bm:
3460 3461 output.append(bm)
3461 3462 else:
3462 3463 if not rev:
3463 3464 ctx = repo[None]
3464 3465 parents = ctx.parents()
3465 3466 changed = ""
3466 3467 if default or id or num:
3467 3468 changed = util.any(repo.status()) and "+" or ""
3468 3469 if default or id:
3469 3470 output = ["%s%s" %
3470 3471 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
3471 3472 if num:
3472 3473 output.append("%s%s" %
3473 3474 ('+'.join([str(p.rev()) for p in parents]), changed))
3474 3475 else:
3475 3476 ctx = scmutil.revsingle(repo, rev)
3476 3477 if default or id:
3477 3478 output = [hexfunc(ctx.node())]
3478 3479 if num:
3479 3480 output.append(str(ctx.rev()))
3480 3481
3481 3482 if default and not ui.quiet:
3482 3483 b = ctx.branch()
3483 3484 if b != 'default':
3484 3485 output.append("(%s)" % b)
3485 3486
3486 3487 # multiple tags for a single parent separated by '/'
3487 3488 t = '/'.join(ctx.tags())
3488 3489 if t:
3489 3490 output.append(t)
3490 3491
3491 3492 # multiple bookmarks for a single parent separated by '/'
3492 3493 bm = '/'.join(ctx.bookmarks())
3493 3494 if bm:
3494 3495 output.append(bm)
3495 3496 else:
3496 3497 if branch:
3497 3498 output.append(ctx.branch())
3498 3499
3499 3500 if tags:
3500 3501 output.extend(ctx.tags())
3501 3502
3502 3503 if bookmarks:
3503 3504 output.extend(ctx.bookmarks())
3504 3505
3505 3506 ui.write("%s\n" % ' '.join(output))
3506 3507
3507 3508 @command('import|patch',
3508 3509 [('p', 'strip', 1,
3509 3510 _('directory strip option for patch. This has the same '
3510 3511 'meaning as the corresponding patch option'), _('NUM')),
3511 3512 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3512 3513 ('e', 'edit', False, _('invoke editor on commit messages')),
3513 3514 ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
3514 3515 ('', 'no-commit', None,
3515 3516 _("don't commit, just update the working directory")),
3516 3517 ('', 'bypass', None,
3517 3518 _("apply patch without touching the working directory")),
3518 3519 ('', 'exact', None,
3519 3520 _('apply patch to the nodes from which it was generated')),
3520 3521 ('', 'import-branch', None,
3521 3522 _('use any branch information in patch (implied by --exact)'))] +
3522 3523 commitopts + commitopts2 + similarityopts,
3523 3524 _('[OPTION]... PATCH...'))
3524 3525 def import_(ui, repo, patch1=None, *patches, **opts):
3525 3526 """import an ordered set of patches
3526 3527
3527 3528 Import a list of patches and commit them individually (unless
3528 3529 --no-commit is specified).
3529 3530
3530 3531 If there are outstanding changes in the working directory, import
3531 3532 will abort unless given the -f/--force flag.
3532 3533
3533 3534 You can import a patch straight from a mail message. Even patches
3534 3535 as attachments work (to use the body part, it must have type
3535 3536 text/plain or text/x-patch). From and Subject headers of email
3536 3537 message are used as default committer and commit message. All
3537 3538 text/plain body parts before first diff are added to commit
3538 3539 message.
3539 3540
3540 3541 If the imported patch was generated by :hg:`export`, user and
3541 3542 description from patch override values from message headers and
3542 3543 body. Values given on command line with -m/--message and -u/--user
3543 3544 override these.
3544 3545
3545 3546 If --exact is specified, import will set the working directory to
3546 3547 the parent of each patch before applying it, and will abort if the
3547 3548 resulting changeset has a different ID than the one recorded in
3548 3549 the patch. This may happen due to character set problems or other
3549 3550 deficiencies in the text patch format.
3550 3551
3551 3552 Use --bypass to apply and commit patches directly to the
3552 3553 repository, not touching the working directory. Without --exact,
3553 3554 patches will be applied on top of the working directory parent
3554 3555 revision.
3555 3556
3556 3557 With -s/--similarity, hg will attempt to discover renames and
3557 3558 copies in the patch in the same way as :hg:`addremove`.
3558 3559
3559 3560 To read a patch from standard input, use "-" as the patch name. If
3560 3561 a URL is specified, the patch will be downloaded from it.
3561 3562 See :hg:`help dates` for a list of formats valid for -d/--date.
3562 3563
3563 3564 .. container:: verbose
3564 3565
3565 3566 Examples:
3566 3567
3567 3568 - import a traditional patch from a website and detect renames::
3568 3569
3569 3570 hg import -s 80 http://example.com/bugfix.patch
3570 3571
3571 3572 - import a changeset from an hgweb server::
3572 3573
3573 3574 hg import http://www.selenic.com/hg/rev/5ca8c111e9aa
3574 3575
3575 3576 - import all the patches in an Unix-style mbox::
3576 3577
3577 3578 hg import incoming-patches.mbox
3578 3579
3579 3580 - attempt to exactly restore an exported changeset (not always
3580 3581 possible)::
3581 3582
3582 3583 hg import --exact proposed-fix.patch
3583 3584
3584 3585 Returns 0 on success.
3585 3586 """
3586 3587
3587 3588 if not patch1:
3588 3589 raise util.Abort(_('need at least one patch to import'))
3589 3590
3590 3591 patches = (patch1,) + patches
3591 3592
3592 3593 date = opts.get('date')
3593 3594 if date:
3594 3595 opts['date'] = util.parsedate(date)
3595 3596
3596 3597 editor = cmdutil.commiteditor
3597 3598 if opts.get('edit'):
3598 3599 editor = cmdutil.commitforceeditor
3599 3600
3600 3601 update = not opts.get('bypass')
3601 3602 if not update and opts.get('no_commit'):
3602 3603 raise util.Abort(_('cannot use --no-commit with --bypass'))
3603 3604 try:
3604 3605 sim = float(opts.get('similarity') or 0)
3605 3606 except ValueError:
3606 3607 raise util.Abort(_('similarity must be a number'))
3607 3608 if sim < 0 or sim > 100:
3608 3609 raise util.Abort(_('similarity must be between 0 and 100'))
3609 3610 if sim and not update:
3610 3611 raise util.Abort(_('cannot use --similarity with --bypass'))
3611 3612
3612 3613 if (opts.get('exact') or not opts.get('force')) and update:
3613 3614 cmdutil.bailifchanged(repo)
3614 3615
3615 3616 base = opts["base"]
3616 3617 strip = opts["strip"]
3617 3618 wlock = lock = tr = None
3618 3619 msgs = []
3619 3620
3620 3621 def checkexact(repo, n, nodeid):
3621 3622 if opts.get('exact') and hex(n) != nodeid:
3622 3623 repo.rollback()
3623 3624 raise util.Abort(_('patch is damaged or loses information'))
3624 3625
3625 3626 def tryone(ui, hunk, parents):
3626 3627 tmpname, message, user, date, branch, nodeid, p1, p2 = \
3627 3628 patch.extract(ui, hunk)
3628 3629
3629 3630 if not tmpname:
3630 3631 return (None, None)
3631 3632 msg = _('applied to working directory')
3632 3633
3633 3634 try:
3634 3635 cmdline_message = cmdutil.logmessage(ui, opts)
3635 3636 if cmdline_message:
3636 3637 # pickup the cmdline msg
3637 3638 message = cmdline_message
3638 3639 elif message:
3639 3640 # pickup the patch msg
3640 3641 message = message.strip()
3641 3642 else:
3642 3643 # launch the editor
3643 3644 message = None
3644 3645 ui.debug('message:\n%s\n' % message)
3645 3646
3646 3647 if len(parents) == 1:
3647 3648 parents.append(repo[nullid])
3648 3649 if opts.get('exact'):
3649 3650 if not nodeid or not p1:
3650 3651 raise util.Abort(_('not a Mercurial patch'))
3651 3652 p1 = repo[p1]
3652 3653 p2 = repo[p2 or nullid]
3653 3654 elif p2:
3654 3655 try:
3655 3656 p1 = repo[p1]
3656 3657 p2 = repo[p2]
3657 3658 # Without any options, consider p2 only if the
3658 3659 # patch is being applied on top of the recorded
3659 3660 # first parent.
3660 3661 if p1 != parents[0]:
3661 3662 p1 = parents[0]
3662 3663 p2 = repo[nullid]
3663 3664 except error.RepoError:
3664 3665 p1, p2 = parents
3665 3666 else:
3666 3667 p1, p2 = parents
3667 3668
3668 3669 n = None
3669 3670 if update:
3670 3671 if p1 != parents[0]:
3671 3672 hg.clean(repo, p1.node())
3672 3673 if p2 != parents[1]:
3673 3674 repo.setparents(p1.node(), p2.node())
3674 3675
3675 3676 if opts.get('exact') or opts.get('import_branch'):
3676 3677 repo.dirstate.setbranch(branch or 'default')
3677 3678
3678 3679 files = set()
3679 3680 patch.patch(ui, repo, tmpname, strip=strip, files=files,
3680 3681 eolmode=None, similarity=sim / 100.0)
3681 3682 files = list(files)
3682 3683 if opts.get('no_commit'):
3683 3684 if message:
3684 3685 msgs.append(message)
3685 3686 else:
3686 3687 if opts.get('exact') or p2:
3687 3688 # If you got here, you either use --force and know what
3688 3689 # you are doing or used --exact or a merge patch while
3689 3690 # being updated to its first parent.
3690 3691 m = None
3691 3692 else:
3692 3693 m = scmutil.matchfiles(repo, files or [])
3693 3694 n = repo.commit(message, opts.get('user') or user,
3694 3695 opts.get('date') or date, match=m,
3695 3696 editor=editor)
3696 3697 checkexact(repo, n, nodeid)
3697 3698 else:
3698 3699 if opts.get('exact') or opts.get('import_branch'):
3699 3700 branch = branch or 'default'
3700 3701 else:
3701 3702 branch = p1.branch()
3702 3703 store = patch.filestore()
3703 3704 try:
3704 3705 files = set()
3705 3706 try:
3706 3707 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
3707 3708 files, eolmode=None)
3708 3709 except patch.PatchError, e:
3709 3710 raise util.Abort(str(e))
3710 3711 memctx = patch.makememctx(repo, (p1.node(), p2.node()),
3711 3712 message,
3712 3713 opts.get('user') or user,
3713 3714 opts.get('date') or date,
3714 3715 branch, files, store,
3715 3716 editor=cmdutil.commiteditor)
3716 3717 repo.savecommitmessage(memctx.description())
3717 3718 n = memctx.commit()
3718 3719 checkexact(repo, n, nodeid)
3719 3720 finally:
3720 3721 store.close()
3721 3722 if n:
3722 3723 # i18n: refers to a short changeset id
3723 3724 msg = _('created %s') % short(n)
3724 3725 return (msg, n)
3725 3726 finally:
3726 3727 os.unlink(tmpname)
3727 3728
3728 3729 try:
3729 3730 try:
3730 3731 wlock = repo.wlock()
3731 3732 if not opts.get('no_commit'):
3732 3733 lock = repo.lock()
3733 3734 tr = repo.transaction('import')
3734 3735 parents = repo.parents()
3735 3736 for patchurl in patches:
3736 3737 if patchurl == '-':
3737 3738 ui.status(_('applying patch from stdin\n'))
3738 3739 patchfile = ui.fin
3739 3740 patchurl = 'stdin' # for error message
3740 3741 else:
3741 3742 patchurl = os.path.join(base, patchurl)
3742 3743 ui.status(_('applying %s\n') % patchurl)
3743 3744 patchfile = url.open(ui, patchurl)
3744 3745
3745 3746 haspatch = False
3746 3747 for hunk in patch.split(patchfile):
3747 3748 (msg, node) = tryone(ui, hunk, parents)
3748 3749 if msg:
3749 3750 haspatch = True
3750 3751 ui.note(msg + '\n')
3751 3752 if update or opts.get('exact'):
3752 3753 parents = repo.parents()
3753 3754 else:
3754 3755 parents = [repo[node]]
3755 3756
3756 3757 if not haspatch:
3757 3758 raise util.Abort(_('%s: no diffs found') % patchurl)
3758 3759
3759 3760 if tr:
3760 3761 tr.close()
3761 3762 if msgs:
3762 3763 repo.savecommitmessage('\n* * *\n'.join(msgs))
3763 3764 except: # re-raises
3764 3765 # wlock.release() indirectly calls dirstate.write(): since
3765 3766 # we're crashing, we do not want to change the working dir
3766 3767 # parent after all, so make sure it writes nothing
3767 3768 repo.dirstate.invalidate()
3768 3769 raise
3769 3770 finally:
3770 3771 if tr:
3771 3772 tr.release()
3772 3773 release(lock, wlock)
3773 3774
3774 3775 @command('incoming|in',
3775 3776 [('f', 'force', None,
3776 3777 _('run even if remote repository is unrelated')),
3777 3778 ('n', 'newest-first', None, _('show newest record first')),
3778 3779 ('', 'bundle', '',
3779 3780 _('file to store the bundles into'), _('FILE')),
3780 3781 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3781 3782 ('B', 'bookmarks', False, _("compare bookmarks")),
3782 3783 ('b', 'branch', [],
3783 3784 _('a specific branch you would like to pull'), _('BRANCH')),
3784 3785 ] + logopts + remoteopts + subrepoopts,
3785 3786 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3786 3787 def incoming(ui, repo, source="default", **opts):
3787 3788 """show new changesets found in source
3788 3789
3789 3790 Show new changesets found in the specified path/URL or the default
3790 3791 pull location. These are the changesets that would have been pulled
3791 3792 if a pull at the time you issued this command.
3792 3793
3793 3794 For remote repository, using --bundle avoids downloading the
3794 3795 changesets twice if the incoming is followed by a pull.
3795 3796
3796 3797 See pull for valid source format details.
3797 3798
3798 3799 Returns 0 if there are incoming changes, 1 otherwise.
3799 3800 """
3800 3801 if opts.get('bundle') and opts.get('subrepos'):
3801 3802 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3802 3803
3803 3804 if opts.get('bookmarks'):
3804 3805 source, branches = hg.parseurl(ui.expandpath(source),
3805 3806 opts.get('branch'))
3806 3807 other = hg.peer(repo, opts, source)
3807 3808 if 'bookmarks' not in other.listkeys('namespaces'):
3808 3809 ui.warn(_("remote doesn't support bookmarks\n"))
3809 3810 return 0
3810 3811 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3811 3812 return bookmarks.diff(ui, repo, other)
3812 3813
3813 3814 repo._subtoppath = ui.expandpath(source)
3814 3815 try:
3815 3816 return hg.incoming(ui, repo, source, opts)
3816 3817 finally:
3817 3818 del repo._subtoppath
3818 3819
3819 3820
3820 3821 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
3821 3822 def init(ui, dest=".", **opts):
3822 3823 """create a new repository in the given directory
3823 3824
3824 3825 Initialize a new repository in the given directory. If the given
3825 3826 directory does not exist, it will be created.
3826 3827
3827 3828 If no directory is given, the current directory is used.
3828 3829
3829 3830 It is possible to specify an ``ssh://`` URL as the destination.
3830 3831 See :hg:`help urls` for more information.
3831 3832
3832 3833 Returns 0 on success.
3833 3834 """
3834 3835 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3835 3836
3836 3837 @command('locate',
3837 3838 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3838 3839 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3839 3840 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3840 3841 ] + walkopts,
3841 3842 _('[OPTION]... [PATTERN]...'))
3842 3843 def locate(ui, repo, *pats, **opts):
3843 3844 """locate files matching specific patterns
3844 3845
3845 3846 Print files under Mercurial control in the working directory whose
3846 3847 names match the given patterns.
3847 3848
3848 3849 By default, this command searches all directories in the working
3849 3850 directory. To search just the current directory and its
3850 3851 subdirectories, use "--include .".
3851 3852
3852 3853 If no patterns are given to match, this command prints the names
3853 3854 of all files under Mercurial control in the working directory.
3854 3855
3855 3856 If you want to feed the output of this command into the "xargs"
3856 3857 command, use the -0 option to both this command and "xargs". This
3857 3858 will avoid the problem of "xargs" treating single filenames that
3858 3859 contain whitespace as multiple filenames.
3859 3860
3860 3861 Returns 0 if a match is found, 1 otherwise.
3861 3862 """
3862 3863 end = opts.get('print0') and '\0' or '\n'
3863 3864 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3864 3865
3865 3866 ret = 1
3866 3867 m = scmutil.match(repo[rev], pats, opts, default='relglob')
3867 3868 m.bad = lambda x, y: False
3868 3869 for abs in repo[rev].walk(m):
3869 3870 if not rev and abs not in repo.dirstate:
3870 3871 continue
3871 3872 if opts.get('fullpath'):
3872 3873 ui.write(repo.wjoin(abs), end)
3873 3874 else:
3874 3875 ui.write(((pats and m.rel(abs)) or abs), end)
3875 3876 ret = 0
3876 3877
3877 3878 return ret
3878 3879
3879 3880 @command('^log|history',
3880 3881 [('f', 'follow', None,
3881 3882 _('follow changeset history, or file history across copies and renames')),
3882 3883 ('', 'follow-first', None,
3883 3884 _('only follow the first parent of merge changesets (DEPRECATED)')),
3884 3885 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3885 3886 ('C', 'copies', None, _('show copied files')),
3886 3887 ('k', 'keyword', [],
3887 3888 _('do case-insensitive search for a given text'), _('TEXT')),
3888 3889 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
3889 3890 ('', 'removed', None, _('include revisions where files were removed')),
3890 3891 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3891 3892 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3892 3893 ('', 'only-branch', [],
3893 3894 _('show only changesets within the given named branch (DEPRECATED)'),
3894 3895 _('BRANCH')),
3895 3896 ('b', 'branch', [],
3896 3897 _('show changesets within the given named branch'), _('BRANCH')),
3897 3898 ('P', 'prune', [],
3898 3899 _('do not display revision or any of its ancestors'), _('REV')),
3899 3900 ('', 'hidden', False, _('show hidden changesets (DEPRECATED)')),
3900 3901 ] + logopts + walkopts,
3901 3902 _('[OPTION]... [FILE]'))
3902 3903 def log(ui, repo, *pats, **opts):
3903 3904 """show revision history of entire repository or files
3904 3905
3905 3906 Print the revision history of the specified files or the entire
3906 3907 project.
3907 3908
3908 3909 If no revision range is specified, the default is ``tip:0`` unless
3909 3910 --follow is set, in which case the working directory parent is
3910 3911 used as the starting revision.
3911 3912
3912 3913 File history is shown without following rename or copy history of
3913 3914 files. Use -f/--follow with a filename to follow history across
3914 3915 renames and copies. --follow without a filename will only show
3915 3916 ancestors or descendants of the starting revision.
3916 3917
3917 3918 By default this command prints revision number and changeset id,
3918 3919 tags, non-trivial parents, user, date and time, and a summary for
3919 3920 each commit. When the -v/--verbose switch is used, the list of
3920 3921 changed files and full commit message are shown.
3921 3922
3922 3923 .. note::
3923 3924 log -p/--patch may generate unexpected diff output for merge
3924 3925 changesets, as it will only compare the merge changeset against
3925 3926 its first parent. Also, only files different from BOTH parents
3926 3927 will appear in files:.
3927 3928
3928 3929 .. note::
3929 3930 for performance reasons, log FILE may omit duplicate changes
3930 3931 made on branches and will not show deletions. To see all
3931 3932 changes including duplicates and deletions, use the --removed
3932 3933 switch.
3933 3934
3934 3935 .. container:: verbose
3935 3936
3936 3937 Some examples:
3937 3938
3938 3939 - changesets with full descriptions and file lists::
3939 3940
3940 3941 hg log -v
3941 3942
3942 3943 - changesets ancestral to the working directory::
3943 3944
3944 3945 hg log -f
3945 3946
3946 3947 - last 10 commits on the current branch::
3947 3948
3948 3949 hg log -l 10 -b .
3949 3950
3950 3951 - changesets showing all modifications of a file, including removals::
3951 3952
3952 3953 hg log --removed file.c
3953 3954
3954 3955 - all changesets that touch a directory, with diffs, excluding merges::
3955 3956
3956 3957 hg log -Mp lib/
3957 3958
3958 3959 - all revision numbers that match a keyword::
3959 3960
3960 3961 hg log -k bug --template "{rev}\\n"
3961 3962
3962 3963 - check if a given changeset is included is a tagged release::
3963 3964
3964 3965 hg log -r "a21ccf and ancestor(1.9)"
3965 3966
3966 3967 - find all changesets by some user in a date range::
3967 3968
3968 3969 hg log -k alice -d "may 2008 to jul 2008"
3969 3970
3970 3971 - summary of all changesets after the last tag::
3971 3972
3972 3973 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3973 3974
3974 3975 See :hg:`help dates` for a list of formats valid for -d/--date.
3975 3976
3976 3977 See :hg:`help revisions` and :hg:`help revsets` for more about
3977 3978 specifying revisions.
3978 3979
3979 3980 See :hg:`help templates` for more about pre-packaged styles and
3980 3981 specifying custom templates.
3981 3982
3982 3983 Returns 0 on success.
3983 3984 """
3984 3985
3985 3986 matchfn = scmutil.match(repo[None], pats, opts)
3986 3987 limit = cmdutil.loglimit(opts)
3987 3988 count = 0
3988 3989
3989 3990 getrenamed, endrev = None, None
3990 3991 if opts.get('copies'):
3991 3992 if opts.get('rev'):
3992 3993 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
3993 3994 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3994 3995
3995 3996 df = False
3996 3997 if opts["date"]:
3997 3998 df = util.matchdate(opts["date"])
3998 3999
3999 4000 branches = opts.get('branch', []) + opts.get('only_branch', [])
4000 4001 opts['branch'] = [repo.lookupbranch(b) for b in branches]
4001 4002
4002 4003 displayer = cmdutil.show_changeset(ui, repo, opts, True)
4003 4004 def prep(ctx, fns):
4004 4005 rev = ctx.rev()
4005 4006 parents = [p for p in repo.changelog.parentrevs(rev)
4006 4007 if p != nullrev]
4007 4008 if opts.get('no_merges') and len(parents) == 2:
4008 4009 return
4009 4010 if opts.get('only_merges') and len(parents) != 2:
4010 4011 return
4011 4012 if opts.get('branch') and ctx.branch() not in opts['branch']:
4012 4013 return
4013 4014 if not opts.get('hidden') and ctx.hidden():
4014 4015 return
4015 4016 if df and not df(ctx.date()[0]):
4016 4017 return
4017 4018
4018 4019 lower = encoding.lower
4019 4020 if opts.get('user'):
4020 4021 luser = lower(ctx.user())
4021 4022 for k in [lower(x) for x in opts['user']]:
4022 4023 if (k in luser):
4023 4024 break
4024 4025 else:
4025 4026 return
4026 4027 if opts.get('keyword'):
4027 4028 luser = lower(ctx.user())
4028 4029 ldesc = lower(ctx.description())
4029 4030 lfiles = lower(" ".join(ctx.files()))
4030 4031 for k in [lower(x) for x in opts['keyword']]:
4031 4032 if (k in luser or k in ldesc or k in lfiles):
4032 4033 break
4033 4034 else:
4034 4035 return
4035 4036
4036 4037 copies = None
4037 4038 if getrenamed is not None and rev:
4038 4039 copies = []
4039 4040 for fn in ctx.files():
4040 4041 rename = getrenamed(fn, rev)
4041 4042 if rename:
4042 4043 copies.append((fn, rename[0]))
4043 4044
4044 4045 revmatchfn = None
4045 4046 if opts.get('patch') or opts.get('stat'):
4046 4047 if opts.get('follow') or opts.get('follow_first'):
4047 4048 # note: this might be wrong when following through merges
4048 4049 revmatchfn = scmutil.match(repo[None], fns, default='path')
4049 4050 else:
4050 4051 revmatchfn = matchfn
4051 4052
4052 4053 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
4053 4054
4054 4055 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
4055 4056 if count == limit:
4056 4057 break
4057 4058 if displayer.flush(ctx.rev()):
4058 4059 count += 1
4059 4060 displayer.close()
4060 4061
4061 4062 @command('manifest',
4062 4063 [('r', 'rev', '', _('revision to display'), _('REV')),
4063 4064 ('', 'all', False, _("list files from all revisions"))],
4064 4065 _('[-r REV]'))
4065 4066 def manifest(ui, repo, node=None, rev=None, **opts):
4066 4067 """output the current or given revision of the project manifest
4067 4068
4068 4069 Print a list of version controlled files for the given revision.
4069 4070 If no revision is given, the first parent of the working directory
4070 4071 is used, or the null revision if no revision is checked out.
4071 4072
4072 4073 With -v, print file permissions, symlink and executable bits.
4073 4074 With --debug, print file revision hashes.
4074 4075
4075 4076 If option --all is specified, the list of all files from all revisions
4076 4077 is printed. This includes deleted and renamed files.
4077 4078
4078 4079 Returns 0 on success.
4079 4080 """
4080 4081 if opts.get('all'):
4081 4082 if rev or node:
4082 4083 raise util.Abort(_("can't specify a revision with --all"))
4083 4084
4084 4085 res = []
4085 4086 prefix = "data/"
4086 4087 suffix = ".i"
4087 4088 plen = len(prefix)
4088 4089 slen = len(suffix)
4089 4090 lock = repo.lock()
4090 4091 try:
4091 4092 for fn, b, size in repo.store.datafiles():
4092 4093 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
4093 4094 res.append(fn[plen:-slen])
4094 4095 finally:
4095 4096 lock.release()
4096 4097 for f in sorted(res):
4097 4098 ui.write("%s\n" % f)
4098 4099 return
4099 4100
4100 4101 if rev and node:
4101 4102 raise util.Abort(_("please specify just one revision"))
4102 4103
4103 4104 if not node:
4104 4105 node = rev
4105 4106
4106 4107 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
4107 4108 ctx = scmutil.revsingle(repo, node)
4108 4109 for f in ctx:
4109 4110 if ui.debugflag:
4110 4111 ui.write("%40s " % hex(ctx.manifest()[f]))
4111 4112 if ui.verbose:
4112 4113 ui.write(decor[ctx.flags(f)])
4113 4114 ui.write("%s\n" % f)
4114 4115
4115 4116 @command('^merge',
4116 4117 [('f', 'force', None, _('force a merge with outstanding changes')),
4117 4118 ('r', 'rev', '', _('revision to merge'), _('REV')),
4118 4119 ('P', 'preview', None,
4119 4120 _('review revisions to merge (no merge is performed)'))
4120 4121 ] + mergetoolopts,
4121 4122 _('[-P] [-f] [[-r] REV]'))
4122 4123 def merge(ui, repo, node=None, **opts):
4123 4124 """merge working directory with another revision
4124 4125
4125 4126 The current working directory is updated with all changes made in
4126 4127 the requested revision since the last common predecessor revision.
4127 4128
4128 4129 Files that changed between either parent are marked as changed for
4129 4130 the next commit and a commit must be performed before any further
4130 4131 updates to the repository are allowed. The next commit will have
4131 4132 two parents.
4132 4133
4133 4134 ``--tool`` can be used to specify the merge tool used for file
4134 4135 merges. It overrides the HGMERGE environment variable and your
4135 4136 configuration files. See :hg:`help merge-tools` for options.
4136 4137
4137 4138 If no revision is specified, the working directory's parent is a
4138 4139 head revision, and the current branch contains exactly one other
4139 4140 head, the other head is merged with by default. Otherwise, an
4140 4141 explicit revision with which to merge with must be provided.
4141 4142
4142 4143 :hg:`resolve` must be used to resolve unresolved files.
4143 4144
4144 4145 To undo an uncommitted merge, use :hg:`update --clean .` which
4145 4146 will check out a clean copy of the original merge parent, losing
4146 4147 all changes.
4147 4148
4148 4149 Returns 0 on success, 1 if there are unresolved files.
4149 4150 """
4150 4151
4151 4152 if opts.get('rev') and node:
4152 4153 raise util.Abort(_("please specify just one revision"))
4153 4154 if not node:
4154 4155 node = opts.get('rev')
4155 4156
4156 4157 if node:
4157 4158 node = scmutil.revsingle(repo, node).node()
4158 4159
4159 4160 if not node and repo._bookmarkcurrent:
4160 4161 bmheads = repo.bookmarkheads(repo._bookmarkcurrent)
4161 4162 curhead = repo[repo._bookmarkcurrent]
4162 4163 if len(bmheads) == 2:
4163 4164 if curhead == bmheads[0]:
4164 4165 node = bmheads[1]
4165 4166 else:
4166 4167 node = bmheads[0]
4167 4168 elif len(bmheads) > 2:
4168 4169 raise util.Abort(_("multiple matching bookmarks to merge - "
4169 4170 "please merge with an explicit rev or bookmark"),
4170 4171 hint=_("run 'hg heads' to see all heads"))
4171 4172 elif len(bmheads) <= 1:
4172 4173 raise util.Abort(_("no matching bookmark to merge - "
4173 4174 "please merge with an explicit rev or bookmark"),
4174 4175 hint=_("run 'hg heads' to see all heads"))
4175 4176
4176 4177 if not node and not repo._bookmarkcurrent:
4177 4178 branch = repo[None].branch()
4178 4179 bheads = repo.branchheads(branch)
4179 4180 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
4180 4181
4181 4182 if len(nbhs) > 2:
4182 4183 raise util.Abort(_("branch '%s' has %d heads - "
4183 4184 "please merge with an explicit rev")
4184 4185 % (branch, len(bheads)),
4185 4186 hint=_("run 'hg heads .' to see heads"))
4186 4187
4187 4188 parent = repo.dirstate.p1()
4188 4189 if len(nbhs) == 1:
4189 4190 if len(bheads) > 1:
4190 4191 raise util.Abort(_("heads are bookmarked - "
4191 4192 "please merge with an explicit rev"),
4192 4193 hint=_("run 'hg heads' to see all heads"))
4193 4194 if len(repo.heads()) > 1:
4194 4195 raise util.Abort(_("branch '%s' has one head - "
4195 4196 "please merge with an explicit rev")
4196 4197 % branch,
4197 4198 hint=_("run 'hg heads' to see all heads"))
4198 4199 msg, hint = _('nothing to merge'), None
4199 4200 if parent != repo.lookup(branch):
4200 4201 hint = _("use 'hg update' instead")
4201 4202 raise util.Abort(msg, hint=hint)
4202 4203
4203 4204 if parent not in bheads:
4204 4205 raise util.Abort(_('working directory not at a head revision'),
4205 4206 hint=_("use 'hg update' or merge with an "
4206 4207 "explicit revision"))
4207 4208 if parent == nbhs[0]:
4208 4209 node = nbhs[-1]
4209 4210 else:
4210 4211 node = nbhs[0]
4211 4212
4212 4213 if opts.get('preview'):
4213 4214 # find nodes that are ancestors of p2 but not of p1
4214 4215 p1 = repo.lookup('.')
4215 4216 p2 = repo.lookup(node)
4216 4217 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4217 4218
4218 4219 displayer = cmdutil.show_changeset(ui, repo, opts)
4219 4220 for node in nodes:
4220 4221 displayer.show(repo[node])
4221 4222 displayer.close()
4222 4223 return 0
4223 4224
4224 4225 try:
4225 4226 # ui.forcemerge is an internal variable, do not document
4226 4227 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4227 4228 return hg.merge(repo, node, force=opts.get('force'))
4228 4229 finally:
4229 4230 ui.setconfig('ui', 'forcemerge', '')
4230 4231
4231 4232 @command('outgoing|out',
4232 4233 [('f', 'force', None, _('run even when the destination is unrelated')),
4233 4234 ('r', 'rev', [],
4234 4235 _('a changeset intended to be included in the destination'), _('REV')),
4235 4236 ('n', 'newest-first', None, _('show newest record first')),
4236 4237 ('B', 'bookmarks', False, _('compare bookmarks')),
4237 4238 ('b', 'branch', [], _('a specific branch you would like to push'),
4238 4239 _('BRANCH')),
4239 4240 ] + logopts + remoteopts + subrepoopts,
4240 4241 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
4241 4242 def outgoing(ui, repo, dest=None, **opts):
4242 4243 """show changesets not found in the destination
4243 4244
4244 4245 Show changesets not found in the specified destination repository
4245 4246 or the default push location. These are the changesets that would
4246 4247 be pushed if a push was requested.
4247 4248
4248 4249 See pull for details of valid destination formats.
4249 4250
4250 4251 Returns 0 if there are outgoing changes, 1 otherwise.
4251 4252 """
4252 4253
4253 4254 if opts.get('bookmarks'):
4254 4255 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4255 4256 dest, branches = hg.parseurl(dest, opts.get('branch'))
4256 4257 other = hg.peer(repo, opts, dest)
4257 4258 if 'bookmarks' not in other.listkeys('namespaces'):
4258 4259 ui.warn(_("remote doesn't support bookmarks\n"))
4259 4260 return 0
4260 4261 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4261 4262 return bookmarks.diff(ui, other, repo)
4262 4263
4263 4264 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
4264 4265 try:
4265 4266 return hg.outgoing(ui, repo, dest, opts)
4266 4267 finally:
4267 4268 del repo._subtoppath
4268 4269
4269 4270 @command('parents',
4270 4271 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4271 4272 ] + templateopts,
4272 4273 _('[-r REV] [FILE]'))
4273 4274 def parents(ui, repo, file_=None, **opts):
4274 4275 """show the parents of the working directory or revision
4275 4276
4276 4277 Print the working directory's parent revisions. If a revision is
4277 4278 given via -r/--rev, the parent of that revision will be printed.
4278 4279 If a file argument is given, the revision in which the file was
4279 4280 last changed (before the working directory revision or the
4280 4281 argument to --rev if given) is printed.
4281 4282
4282 4283 Returns 0 on success.
4283 4284 """
4284 4285
4285 4286 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
4286 4287
4287 4288 if file_:
4288 4289 m = scmutil.match(ctx, (file_,), opts)
4289 4290 if m.anypats() or len(m.files()) != 1:
4290 4291 raise util.Abort(_('can only specify an explicit filename'))
4291 4292 file_ = m.files()[0]
4292 4293 filenodes = []
4293 4294 for cp in ctx.parents():
4294 4295 if not cp:
4295 4296 continue
4296 4297 try:
4297 4298 filenodes.append(cp.filenode(file_))
4298 4299 except error.LookupError:
4299 4300 pass
4300 4301 if not filenodes:
4301 4302 raise util.Abort(_("'%s' not found in manifest!") % file_)
4302 4303 fl = repo.file(file_)
4303 4304 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
4304 4305 else:
4305 4306 p = [cp.node() for cp in ctx.parents()]
4306 4307
4307 4308 displayer = cmdutil.show_changeset(ui, repo, opts)
4308 4309 for n in p:
4309 4310 if n != nullid:
4310 4311 displayer.show(repo[n])
4311 4312 displayer.close()
4312 4313
4313 4314 @command('paths', [], _('[NAME]'))
4314 4315 def paths(ui, repo, search=None):
4315 4316 """show aliases for remote repositories
4316 4317
4317 4318 Show definition of symbolic path name NAME. If no name is given,
4318 4319 show definition of all available names.
4319 4320
4320 4321 Option -q/--quiet suppresses all output when searching for NAME
4321 4322 and shows only the path names when listing all definitions.
4322 4323
4323 4324 Path names are defined in the [paths] section of your
4324 4325 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4325 4326 repository, ``.hg/hgrc`` is used, too.
4326 4327
4327 4328 The path names ``default`` and ``default-push`` have a special
4328 4329 meaning. When performing a push or pull operation, they are used
4329 4330 as fallbacks if no location is specified on the command-line.
4330 4331 When ``default-push`` is set, it will be used for push and
4331 4332 ``default`` will be used for pull; otherwise ``default`` is used
4332 4333 as the fallback for both. When cloning a repository, the clone
4333 4334 source is written as ``default`` in ``.hg/hgrc``. Note that
4334 4335 ``default`` and ``default-push`` apply to all inbound (e.g.
4335 4336 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
4336 4337 :hg:`bundle`) operations.
4337 4338
4338 4339 See :hg:`help urls` for more information.
4339 4340
4340 4341 Returns 0 on success.
4341 4342 """
4342 4343 if search:
4343 4344 for name, path in ui.configitems("paths"):
4344 4345 if name == search:
4345 4346 ui.status("%s\n" % util.hidepassword(path))
4346 4347 return
4347 4348 if not ui.quiet:
4348 4349 ui.warn(_("not found!\n"))
4349 4350 return 1
4350 4351 else:
4351 4352 for name, path in ui.configitems("paths"):
4352 4353 if ui.quiet:
4353 4354 ui.write("%s\n" % name)
4354 4355 else:
4355 4356 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
4356 4357
4357 4358 @command('^phase',
4358 4359 [('p', 'public', False, _('set changeset phase to public')),
4359 4360 ('d', 'draft', False, _('set changeset phase to draft')),
4360 4361 ('s', 'secret', False, _('set changeset phase to secret')),
4361 4362 ('f', 'force', False, _('allow to move boundary backward')),
4362 4363 ('r', 'rev', [], _('target revision'), _('REV')),
4363 4364 ],
4364 4365 _('[-p|-d|-s] [-f] [-r] REV...'))
4365 4366 def phase(ui, repo, *revs, **opts):
4366 4367 """set or show the current phase name
4367 4368
4368 4369 With no argument, show the phase name of specified revisions.
4369 4370
4370 4371 With one of -p/--public, -d/--draft or -s/--secret, change the
4371 4372 phase value of the specified revisions.
4372 4373
4373 4374 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
4374 4375 lower phase to an higher phase. Phases are ordered as follows::
4375 4376
4376 4377 public < draft < secret
4377 4378
4378 4379 Return 0 on success, 1 if no phases were changed or some could not
4379 4380 be changed.
4380 4381 """
4381 4382 # search for a unique phase argument
4382 4383 targetphase = None
4383 4384 for idx, name in enumerate(phases.phasenames):
4384 4385 if opts[name]:
4385 4386 if targetphase is not None:
4386 4387 raise util.Abort(_('only one phase can be specified'))
4387 4388 targetphase = idx
4388 4389
4389 4390 # look for specified revision
4390 4391 revs = list(revs)
4391 4392 revs.extend(opts['rev'])
4392 4393 if not revs:
4393 4394 raise util.Abort(_('no revisions specified'))
4394 4395
4395 4396 revs = scmutil.revrange(repo, revs)
4396 4397
4397 4398 lock = None
4398 4399 ret = 0
4399 4400 if targetphase is None:
4400 4401 # display
4401 4402 for r in revs:
4402 4403 ctx = repo[r]
4403 4404 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4404 4405 else:
4405 4406 lock = repo.lock()
4406 4407 try:
4407 4408 # set phase
4408 4409 if not revs:
4409 4410 raise util.Abort(_('empty revision set'))
4410 4411 nodes = [repo[r].node() for r in revs]
4411 4412 olddata = repo._phasecache.getphaserevs(repo)[:]
4412 4413 phases.advanceboundary(repo, targetphase, nodes)
4413 4414 if opts['force']:
4414 4415 phases.retractboundary(repo, targetphase, nodes)
4415 4416 finally:
4416 4417 lock.release()
4417 4418 newdata = repo._phasecache.getphaserevs(repo)
4418 4419 changes = sum(o != newdata[i] for i, o in enumerate(olddata))
4419 4420 rejected = [n for n in nodes
4420 4421 if newdata[repo[n].rev()] < targetphase]
4421 4422 if rejected:
4422 4423 ui.warn(_('cannot move %i changesets to a more permissive '
4423 4424 'phase, use --force\n') % len(rejected))
4424 4425 ret = 1
4425 4426 if changes:
4426 4427 msg = _('phase changed for %i changesets\n') % changes
4427 4428 if ret:
4428 4429 ui.status(msg)
4429 4430 else:
4430 4431 ui.note(msg)
4431 4432 else:
4432 4433 ui.warn(_('no phases changed\n'))
4433 4434 ret = 1
4434 4435 return ret
4435 4436
4436 4437 def postincoming(ui, repo, modheads, optupdate, checkout):
4437 4438 if modheads == 0:
4438 4439 return
4439 4440 if optupdate:
4440 4441 movemarkfrom = repo['.'].node()
4441 4442 try:
4442 4443 ret = hg.update(repo, checkout)
4443 4444 except util.Abort, inst:
4444 4445 ui.warn(_("not updating: %s\n") % str(inst))
4445 4446 return 0
4446 4447 if not ret and not checkout:
4447 4448 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
4448 4449 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
4449 4450 return ret
4450 4451 if modheads > 1:
4451 4452 currentbranchheads = len(repo.branchheads())
4452 4453 if currentbranchheads == modheads:
4453 4454 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4454 4455 elif currentbranchheads > 1:
4455 4456 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4456 4457 "merge)\n"))
4457 4458 else:
4458 4459 ui.status(_("(run 'hg heads' to see heads)\n"))
4459 4460 else:
4460 4461 ui.status(_("(run 'hg update' to get a working copy)\n"))
4461 4462
4462 4463 @command('^pull',
4463 4464 [('u', 'update', None,
4464 4465 _('update to new branch head if changesets were pulled')),
4465 4466 ('f', 'force', None, _('run even when remote repository is unrelated')),
4466 4467 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4467 4468 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4468 4469 ('b', 'branch', [], _('a specific branch you would like to pull'),
4469 4470 _('BRANCH')),
4470 4471 ] + remoteopts,
4471 4472 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4472 4473 def pull(ui, repo, source="default", **opts):
4473 4474 """pull changes from the specified source
4474 4475
4475 4476 Pull changes from a remote repository to a local one.
4476 4477
4477 4478 This finds all changes from the repository at the specified path
4478 4479 or URL and adds them to a local repository (the current one unless
4479 4480 -R is specified). By default, this does not update the copy of the
4480 4481 project in the working directory.
4481 4482
4482 4483 Use :hg:`incoming` if you want to see what would have been added
4483 4484 by a pull at the time you issued this command. If you then decide
4484 4485 to add those changes to the repository, you should use :hg:`pull
4485 4486 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4486 4487
4487 4488 If SOURCE is omitted, the 'default' path will be used.
4488 4489 See :hg:`help urls` for more information.
4489 4490
4490 4491 Returns 0 on success, 1 if an update had unresolved files.
4491 4492 """
4492 4493 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4493 4494 other = hg.peer(repo, opts, source)
4494 4495 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4495 4496 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4496 4497
4497 4498 if opts.get('bookmark'):
4498 4499 if not revs:
4499 4500 revs = []
4500 4501 rb = other.listkeys('bookmarks')
4501 4502 for b in opts['bookmark']:
4502 4503 if b not in rb:
4503 4504 raise util.Abort(_('remote bookmark %s not found!') % b)
4504 4505 revs.append(rb[b])
4505 4506
4506 4507 if revs:
4507 4508 try:
4508 4509 revs = [other.lookup(rev) for rev in revs]
4509 4510 except error.CapabilityError:
4510 4511 err = _("other repository doesn't support revision lookup, "
4511 4512 "so a rev cannot be specified.")
4512 4513 raise util.Abort(err)
4513 4514
4514 4515 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
4515 4516 bookmarks.updatefromremote(ui, repo, other, source)
4516 4517 if checkout:
4517 4518 checkout = str(repo.changelog.rev(other.lookup(checkout)))
4518 4519 repo._subtoppath = source
4519 4520 try:
4520 4521 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
4521 4522
4522 4523 finally:
4523 4524 del repo._subtoppath
4524 4525
4525 4526 # update specified bookmarks
4526 4527 if opts.get('bookmark'):
4527 4528 for b in opts['bookmark']:
4528 4529 # explicit pull overrides local bookmark if any
4529 4530 ui.status(_("importing bookmark %s\n") % b)
4530 4531 repo._bookmarks[b] = repo[rb[b]].node()
4531 4532 bookmarks.write(repo)
4532 4533
4533 4534 return ret
4534 4535
4535 4536 @command('^push',
4536 4537 [('f', 'force', None, _('force push')),
4537 4538 ('r', 'rev', [],
4538 4539 _('a changeset intended to be included in the destination'),
4539 4540 _('REV')),
4540 4541 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4541 4542 ('b', 'branch', [],
4542 4543 _('a specific branch you would like to push'), _('BRANCH')),
4543 4544 ('', 'new-branch', False, _('allow pushing a new branch')),
4544 4545 ] + remoteopts,
4545 4546 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4546 4547 def push(ui, repo, dest=None, **opts):
4547 4548 """push changes to the specified destination
4548 4549
4549 4550 Push changesets from the local repository to the specified
4550 4551 destination.
4551 4552
4552 4553 This operation is symmetrical to pull: it is identical to a pull
4553 4554 in the destination repository from the current one.
4554 4555
4555 4556 By default, push will not allow creation of new heads at the
4556 4557 destination, since multiple heads would make it unclear which head
4557 4558 to use. In this situation, it is recommended to pull and merge
4558 4559 before pushing.
4559 4560
4560 4561 Use --new-branch if you want to allow push to create a new named
4561 4562 branch that is not present at the destination. This allows you to
4562 4563 only create a new branch without forcing other changes.
4563 4564
4564 4565 Use -f/--force to override the default behavior and push all
4565 4566 changesets on all branches.
4566 4567
4567 4568 If -r/--rev is used, the specified revision and all its ancestors
4568 4569 will be pushed to the remote repository.
4569 4570
4570 4571 Please see :hg:`help urls` for important details about ``ssh://``
4571 4572 URLs. If DESTINATION is omitted, a default path will be used.
4572 4573
4573 4574 Returns 0 if push was successful, 1 if nothing to push.
4574 4575 """
4575 4576
4576 4577 if opts.get('bookmark'):
4577 4578 for b in opts['bookmark']:
4578 4579 # translate -B options to -r so changesets get pushed
4579 4580 if b in repo._bookmarks:
4580 4581 opts.setdefault('rev', []).append(b)
4581 4582 else:
4582 4583 # if we try to push a deleted bookmark, translate it to null
4583 4584 # this lets simultaneous -r, -b options continue working
4584 4585 opts.setdefault('rev', []).append("null")
4585 4586
4586 4587 dest = ui.expandpath(dest or 'default-push', dest or 'default')
4587 4588 dest, branches = hg.parseurl(dest, opts.get('branch'))
4588 4589 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4589 4590 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4590 4591 other = hg.peer(repo, opts, dest)
4591 4592 if revs:
4592 4593 revs = [repo.lookup(rev) for rev in revs]
4593 4594
4594 4595 repo._subtoppath = dest
4595 4596 try:
4596 4597 # push subrepos depth-first for coherent ordering
4597 4598 c = repo['']
4598 4599 subs = c.substate # only repos that are committed
4599 4600 for s in sorted(subs):
4600 4601 if c.sub(s).push(opts) == 0:
4601 4602 return False
4602 4603 finally:
4603 4604 del repo._subtoppath
4604 4605 result = repo.push(other, opts.get('force'), revs=revs,
4605 4606 newbranch=opts.get('new_branch'))
4606 4607
4607 4608 result = not result
4608 4609
4609 4610 if opts.get('bookmark'):
4610 4611 rb = other.listkeys('bookmarks')
4611 4612 for b in opts['bookmark']:
4612 4613 # explicit push overrides remote bookmark if any
4613 4614 if b in repo._bookmarks:
4614 4615 ui.status(_("exporting bookmark %s\n") % b)
4615 4616 new = repo[b].hex()
4616 4617 elif b in rb:
4617 4618 ui.status(_("deleting remote bookmark %s\n") % b)
4618 4619 new = '' # delete
4619 4620 else:
4620 4621 ui.warn(_('bookmark %s does not exist on the local '
4621 4622 'or remote repository!\n') % b)
4622 4623 return 2
4623 4624 old = rb.get(b, '')
4624 4625 r = other.pushkey('bookmarks', b, old, new)
4625 4626 if not r:
4626 4627 ui.warn(_('updating bookmark %s failed!\n') % b)
4627 4628 if not result:
4628 4629 result = 2
4629 4630
4630 4631 return result
4631 4632
4632 4633 @command('recover', [])
4633 4634 def recover(ui, repo):
4634 4635 """roll back an interrupted transaction
4635 4636
4636 4637 Recover from an interrupted commit or pull.
4637 4638
4638 4639 This command tries to fix the repository status after an
4639 4640 interrupted operation. It should only be necessary when Mercurial
4640 4641 suggests it.
4641 4642
4642 4643 Returns 0 if successful, 1 if nothing to recover or verify fails.
4643 4644 """
4644 4645 if repo.recover():
4645 4646 return hg.verify(repo)
4646 4647 return 1
4647 4648
4648 4649 @command('^remove|rm',
4649 4650 [('A', 'after', None, _('record delete for missing files')),
4650 4651 ('f', 'force', None,
4651 4652 _('remove (and delete) file even if added or modified')),
4652 4653 ] + walkopts,
4653 4654 _('[OPTION]... FILE...'))
4654 4655 def remove(ui, repo, *pats, **opts):
4655 4656 """remove the specified files on the next commit
4656 4657
4657 4658 Schedule the indicated files for removal from the current branch.
4658 4659
4659 4660 This command schedules the files to be removed at the next commit.
4660 4661 To undo a remove before that, see :hg:`revert`. To undo added
4661 4662 files, see :hg:`forget`.
4662 4663
4663 4664 .. container:: verbose
4664 4665
4665 4666 -A/--after can be used to remove only files that have already
4666 4667 been deleted, -f/--force can be used to force deletion, and -Af
4667 4668 can be used to remove files from the next revision without
4668 4669 deleting them from the working directory.
4669 4670
4670 4671 The following table details the behavior of remove for different
4671 4672 file states (columns) and option combinations (rows). The file
4672 4673 states are Added [A], Clean [C], Modified [M] and Missing [!]
4673 4674 (as reported by :hg:`status`). The actions are Warn, Remove
4674 4675 (from branch) and Delete (from disk):
4675 4676
4676 4677 ======= == == == ==
4677 4678 A C M !
4678 4679 ======= == == == ==
4679 4680 none W RD W R
4680 4681 -f R RD RD R
4681 4682 -A W W W R
4682 4683 -Af R R R R
4683 4684 ======= == == == ==
4684 4685
4685 4686 Note that remove never deletes files in Added [A] state from the
4686 4687 working directory, not even if option --force is specified.
4687 4688
4688 4689 Returns 0 on success, 1 if any warnings encountered.
4689 4690 """
4690 4691
4691 4692 ret = 0
4692 4693 after, force = opts.get('after'), opts.get('force')
4693 4694 if not pats and not after:
4694 4695 raise util.Abort(_('no files specified'))
4695 4696
4696 4697 m = scmutil.match(repo[None], pats, opts)
4697 4698 s = repo.status(match=m, clean=True)
4698 4699 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
4699 4700
4700 4701 for f in m.files():
4701 4702 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
4702 4703 if os.path.exists(m.rel(f)):
4703 4704 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
4704 4705 ret = 1
4705 4706
4706 4707 if force:
4707 4708 list = modified + deleted + clean + added
4708 4709 elif after:
4709 4710 list = deleted
4710 4711 for f in modified + added + clean:
4711 4712 ui.warn(_('not removing %s: file still exists (use -f'
4712 4713 ' to force removal)\n') % m.rel(f))
4713 4714 ret = 1
4714 4715 else:
4715 4716 list = deleted + clean
4716 4717 for f in modified:
4717 4718 ui.warn(_('not removing %s: file is modified (use -f'
4718 4719 ' to force removal)\n') % m.rel(f))
4719 4720 ret = 1
4720 4721 for f in added:
4721 4722 ui.warn(_('not removing %s: file has been marked for add'
4722 4723 ' (use forget to undo)\n') % m.rel(f))
4723 4724 ret = 1
4724 4725
4725 4726 for f in sorted(list):
4726 4727 if ui.verbose or not m.exact(f):
4727 4728 ui.status(_('removing %s\n') % m.rel(f))
4728 4729
4729 4730 wlock = repo.wlock()
4730 4731 try:
4731 4732 if not after:
4732 4733 for f in list:
4733 4734 if f in added:
4734 4735 continue # we never unlink added files on remove
4735 4736 try:
4736 4737 util.unlinkpath(repo.wjoin(f))
4737 4738 except OSError, inst:
4738 4739 if inst.errno != errno.ENOENT:
4739 4740 raise
4740 4741 repo[None].forget(list)
4741 4742 finally:
4742 4743 wlock.release()
4743 4744
4744 4745 return ret
4745 4746
4746 4747 @command('rename|move|mv',
4747 4748 [('A', 'after', None, _('record a rename that has already occurred')),
4748 4749 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4749 4750 ] + walkopts + dryrunopts,
4750 4751 _('[OPTION]... SOURCE... DEST'))
4751 4752 def rename(ui, repo, *pats, **opts):
4752 4753 """rename files; equivalent of copy + remove
4753 4754
4754 4755 Mark dest as copies of sources; mark sources for deletion. If dest
4755 4756 is a directory, copies are put in that directory. If dest is a
4756 4757 file, there can only be one source.
4757 4758
4758 4759 By default, this command copies the contents of files as they
4759 4760 exist in the working directory. If invoked with -A/--after, the
4760 4761 operation is recorded, but no copying is performed.
4761 4762
4762 4763 This command takes effect at the next commit. To undo a rename
4763 4764 before that, see :hg:`revert`.
4764 4765
4765 4766 Returns 0 on success, 1 if errors are encountered.
4766 4767 """
4767 4768 wlock = repo.wlock(False)
4768 4769 try:
4769 4770 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4770 4771 finally:
4771 4772 wlock.release()
4772 4773
4773 4774 @command('resolve',
4774 4775 [('a', 'all', None, _('select all unresolved files')),
4775 4776 ('l', 'list', None, _('list state of files needing merge')),
4776 4777 ('m', 'mark', None, _('mark files as resolved')),
4777 4778 ('u', 'unmark', None, _('mark files as unresolved')),
4778 4779 ('n', 'no-status', None, _('hide status prefix'))]
4779 4780 + mergetoolopts + walkopts,
4780 4781 _('[OPTION]... [FILE]...'))
4781 4782 def resolve(ui, repo, *pats, **opts):
4782 4783 """redo merges or set/view the merge status of files
4783 4784
4784 4785 Merges with unresolved conflicts are often the result of
4785 4786 non-interactive merging using the ``internal:merge`` configuration
4786 4787 setting, or a command-line merge tool like ``diff3``. The resolve
4787 4788 command is used to manage the files involved in a merge, after
4788 4789 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4789 4790 working directory must have two parents). See :hg:`help
4790 4791 merge-tools` for information on configuring merge tools.
4791 4792
4792 4793 The resolve command can be used in the following ways:
4793 4794
4794 4795 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4795 4796 files, discarding any previous merge attempts. Re-merging is not
4796 4797 performed for files already marked as resolved. Use ``--all/-a``
4797 4798 to select all unresolved files. ``--tool`` can be used to specify
4798 4799 the merge tool used for the given files. It overrides the HGMERGE
4799 4800 environment variable and your configuration files. Previous file
4800 4801 contents are saved with a ``.orig`` suffix.
4801 4802
4802 4803 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4803 4804 (e.g. after having manually fixed-up the files). The default is
4804 4805 to mark all unresolved files.
4805 4806
4806 4807 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4807 4808 default is to mark all resolved files.
4808 4809
4809 4810 - :hg:`resolve -l`: list files which had or still have conflicts.
4810 4811 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4811 4812
4812 4813 Note that Mercurial will not let you commit files with unresolved
4813 4814 merge conflicts. You must use :hg:`resolve -m ...` before you can
4814 4815 commit after a conflicting merge.
4815 4816
4816 4817 Returns 0 on success, 1 if any files fail a resolve attempt.
4817 4818 """
4818 4819
4819 4820 all, mark, unmark, show, nostatus = \
4820 4821 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
4821 4822
4822 4823 if (show and (mark or unmark)) or (mark and unmark):
4823 4824 raise util.Abort(_("too many options specified"))
4824 4825 if pats and all:
4825 4826 raise util.Abort(_("can't specify --all and patterns"))
4826 4827 if not (all or pats or show or mark or unmark):
4827 4828 raise util.Abort(_('no files or directories specified; '
4828 4829 'use --all to remerge all files'))
4829 4830
4830 4831 ms = mergemod.mergestate(repo)
4831 4832 m = scmutil.match(repo[None], pats, opts)
4832 4833 ret = 0
4833 4834
4834 4835 for f in ms:
4835 4836 if m(f):
4836 4837 if show:
4837 4838 if nostatus:
4838 4839 ui.write("%s\n" % f)
4839 4840 else:
4840 4841 ui.write("%s %s\n" % (ms[f].upper(), f),
4841 4842 label='resolve.' +
4842 4843 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
4843 4844 elif mark:
4844 4845 ms.mark(f, "r")
4845 4846 elif unmark:
4846 4847 ms.mark(f, "u")
4847 4848 else:
4848 4849 wctx = repo[None]
4849 4850 mctx = wctx.parents()[-1]
4850 4851
4851 4852 # backup pre-resolve (merge uses .orig for its own purposes)
4852 4853 a = repo.wjoin(f)
4853 4854 util.copyfile(a, a + ".resolve")
4854 4855
4855 4856 try:
4856 4857 # resolve file
4857 4858 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4858 4859 if ms.resolve(f, wctx, mctx):
4859 4860 ret = 1
4860 4861 finally:
4861 4862 ui.setconfig('ui', 'forcemerge', '')
4862 4863
4863 4864 # replace filemerge's .orig file with our resolve file
4864 4865 util.rename(a + ".resolve", a + ".orig")
4865 4866
4866 4867 ms.commit()
4867 4868 return ret
4868 4869
4869 4870 @command('revert',
4870 4871 [('a', 'all', None, _('revert all changes when no arguments given')),
4871 4872 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4872 4873 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4873 4874 ('C', 'no-backup', None, _('do not save backup copies of files')),
4874 4875 ] + walkopts + dryrunopts,
4875 4876 _('[OPTION]... [-r REV] [NAME]...'))
4876 4877 def revert(ui, repo, *pats, **opts):
4877 4878 """restore files to their checkout state
4878 4879
4879 4880 .. note::
4880 4881 To check out earlier revisions, you should use :hg:`update REV`.
4881 4882 To cancel a merge (and lose your changes), use :hg:`update --clean .`.
4882 4883
4883 4884 With no revision specified, revert the specified files or directories
4884 4885 to the contents they had in the parent of the working directory.
4885 4886 This restores the contents of files to an unmodified
4886 4887 state and unschedules adds, removes, copies, and renames. If the
4887 4888 working directory has two parents, you must explicitly specify a
4888 4889 revision.
4889 4890
4890 4891 Using the -r/--rev or -d/--date options, revert the given files or
4891 4892 directories to their states as of a specific revision. Because
4892 4893 revert does not change the working directory parents, this will
4893 4894 cause these files to appear modified. This can be helpful to "back
4894 4895 out" some or all of an earlier change. See :hg:`backout` for a
4895 4896 related method.
4896 4897
4897 4898 Modified files are saved with a .orig suffix before reverting.
4898 4899 To disable these backups, use --no-backup.
4899 4900
4900 4901 See :hg:`help dates` for a list of formats valid for -d/--date.
4901 4902
4902 4903 Returns 0 on success.
4903 4904 """
4904 4905
4905 4906 if opts.get("date"):
4906 4907 if opts.get("rev"):
4907 4908 raise util.Abort(_("you can't specify a revision and a date"))
4908 4909 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4909 4910
4910 4911 parent, p2 = repo.dirstate.parents()
4911 4912 if not opts.get('rev') and p2 != nullid:
4912 4913 # revert after merge is a trap for new users (issue2915)
4913 4914 raise util.Abort(_('uncommitted merge with no revision specified'),
4914 4915 hint=_('use "hg update" or see "hg help revert"'))
4915 4916
4916 4917 ctx = scmutil.revsingle(repo, opts.get('rev'))
4917 4918
4918 4919 if not pats and not opts.get('all'):
4919 4920 msg = _("no files or directories specified")
4920 4921 if p2 != nullid:
4921 4922 hint = _("uncommitted merge, use --all to discard all changes,"
4922 4923 " or 'hg update -C .' to abort the merge")
4923 4924 raise util.Abort(msg, hint=hint)
4924 4925 dirty = util.any(repo.status())
4925 4926 node = ctx.node()
4926 4927 if node != parent:
4927 4928 if dirty:
4928 4929 hint = _("uncommitted changes, use --all to discard all"
4929 4930 " changes, or 'hg update %s' to update") % ctx.rev()
4930 4931 else:
4931 4932 hint = _("use --all to revert all files,"
4932 4933 " or 'hg update %s' to update") % ctx.rev()
4933 4934 elif dirty:
4934 4935 hint = _("uncommitted changes, use --all to discard all changes")
4935 4936 else:
4936 4937 hint = _("use --all to revert all files")
4937 4938 raise util.Abort(msg, hint=hint)
4938 4939
4939 4940 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
4940 4941
4941 4942 @command('rollback', dryrunopts +
4942 4943 [('f', 'force', False, _('ignore safety measures'))])
4943 4944 def rollback(ui, repo, **opts):
4944 4945 """roll back the last transaction (dangerous)
4945 4946
4946 4947 This command should be used with care. There is only one level of
4947 4948 rollback, and there is no way to undo a rollback. It will also
4948 4949 restore the dirstate at the time of the last transaction, losing
4949 4950 any dirstate changes since that time. This command does not alter
4950 4951 the working directory.
4951 4952
4952 4953 Transactions are used to encapsulate the effects of all commands
4953 4954 that create new changesets or propagate existing changesets into a
4954 4955 repository. For example, the following commands are transactional,
4955 4956 and their effects can be rolled back:
4956 4957
4957 4958 - commit
4958 4959 - import
4959 4960 - pull
4960 4961 - push (with this repository as the destination)
4961 4962 - unbundle
4962 4963
4963 4964 To avoid permanent data loss, rollback will refuse to rollback a
4964 4965 commit transaction if it isn't checked out. Use --force to
4965 4966 override this protection.
4966 4967
4967 4968 This command is not intended for use on public repositories. Once
4968 4969 changes are visible for pull by other users, rolling a transaction
4969 4970 back locally is ineffective (someone else may already have pulled
4970 4971 the changes). Furthermore, a race is possible with readers of the
4971 4972 repository; for example an in-progress pull from the repository
4972 4973 may fail if a rollback is performed.
4973 4974
4974 4975 Returns 0 on success, 1 if no rollback data is available.
4975 4976 """
4976 4977 return repo.rollback(dryrun=opts.get('dry_run'),
4977 4978 force=opts.get('force'))
4978 4979
4979 4980 @command('root', [])
4980 4981 def root(ui, repo):
4981 4982 """print the root (top) of the current working directory
4982 4983
4983 4984 Print the root directory of the current repository.
4984 4985
4985 4986 Returns 0 on success.
4986 4987 """
4987 4988 ui.write(repo.root + "\n")
4988 4989
4989 4990 @command('^serve',
4990 4991 [('A', 'accesslog', '', _('name of access log file to write to'),
4991 4992 _('FILE')),
4992 4993 ('d', 'daemon', None, _('run server in background')),
4993 4994 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
4994 4995 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4995 4996 # use string type, then we can check if something was passed
4996 4997 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4997 4998 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4998 4999 _('ADDR')),
4999 5000 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5000 5001 _('PREFIX')),
5001 5002 ('n', 'name', '',
5002 5003 _('name to show in web pages (default: working directory)'), _('NAME')),
5003 5004 ('', 'web-conf', '',
5004 5005 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
5005 5006 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5006 5007 _('FILE')),
5007 5008 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5008 5009 ('', 'stdio', None, _('for remote clients')),
5009 5010 ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
5010 5011 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5011 5012 ('', 'style', '', _('template style to use'), _('STYLE')),
5012 5013 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5013 5014 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
5014 5015 _('[OPTION]...'))
5015 5016 def serve(ui, repo, **opts):
5016 5017 """start stand-alone webserver
5017 5018
5018 5019 Start a local HTTP repository browser and pull server. You can use
5019 5020 this for ad-hoc sharing and browsing of repositories. It is
5020 5021 recommended to use a real web server to serve a repository for
5021 5022 longer periods of time.
5022 5023
5023 5024 Please note that the server does not implement access control.
5024 5025 This means that, by default, anybody can read from the server and
5025 5026 nobody can write to it by default. Set the ``web.allow_push``
5026 5027 option to ``*`` to allow everybody to push to the server. You
5027 5028 should use a real web server if you need to authenticate users.
5028 5029
5029 5030 By default, the server logs accesses to stdout and errors to
5030 5031 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5031 5032 files.
5032 5033
5033 5034 To have the server choose a free port number to listen on, specify
5034 5035 a port number of 0; in this case, the server will print the port
5035 5036 number it uses.
5036 5037
5037 5038 Returns 0 on success.
5038 5039 """
5039 5040
5040 5041 if opts["stdio"] and opts["cmdserver"]:
5041 5042 raise util.Abort(_("cannot use --stdio with --cmdserver"))
5042 5043
5043 5044 def checkrepo():
5044 5045 if repo is None:
5045 5046 raise error.RepoError(_("there is no Mercurial repository here"
5046 5047 " (.hg not found)"))
5047 5048
5048 5049 if opts["stdio"]:
5049 5050 checkrepo()
5050 5051 s = sshserver.sshserver(ui, repo)
5051 5052 s.serve_forever()
5052 5053
5053 5054 if opts["cmdserver"]:
5054 5055 checkrepo()
5055 5056 s = commandserver.server(ui, repo, opts["cmdserver"])
5056 5057 return s.serve()
5057 5058
5058 5059 # this way we can check if something was given in the command-line
5059 5060 if opts.get('port'):
5060 5061 opts['port'] = util.getport(opts.get('port'))
5061 5062
5062 5063 baseui = repo and repo.baseui or ui
5063 5064 optlist = ("name templates style address port prefix ipv6"
5064 5065 " accesslog errorlog certificate encoding")
5065 5066 for o in optlist.split():
5066 5067 val = opts.get(o, '')
5067 5068 if val in (None, ''): # should check against default options instead
5068 5069 continue
5069 5070 baseui.setconfig("web", o, val)
5070 5071 if repo and repo.ui != baseui:
5071 5072 repo.ui.setconfig("web", o, val)
5072 5073
5073 5074 o = opts.get('web_conf') or opts.get('webdir_conf')
5074 5075 if not o:
5075 5076 if not repo:
5076 5077 raise error.RepoError(_("there is no Mercurial repository"
5077 5078 " here (.hg not found)"))
5078 5079 o = repo.root
5079 5080
5080 5081 app = hgweb.hgweb(o, baseui=ui)
5081 5082
5082 5083 class service(object):
5083 5084 def init(self):
5084 5085 util.setsignalhandler()
5085 5086 self.httpd = hgweb.server.create_server(ui, app)
5086 5087
5087 5088 if opts['port'] and not ui.verbose:
5088 5089 return
5089 5090
5090 5091 if self.httpd.prefix:
5091 5092 prefix = self.httpd.prefix.strip('/') + '/'
5092 5093 else:
5093 5094 prefix = ''
5094 5095
5095 5096 port = ':%d' % self.httpd.port
5096 5097 if port == ':80':
5097 5098 port = ''
5098 5099
5099 5100 bindaddr = self.httpd.addr
5100 5101 if bindaddr == '0.0.0.0':
5101 5102 bindaddr = '*'
5102 5103 elif ':' in bindaddr: # IPv6
5103 5104 bindaddr = '[%s]' % bindaddr
5104 5105
5105 5106 fqaddr = self.httpd.fqaddr
5106 5107 if ':' in fqaddr:
5107 5108 fqaddr = '[%s]' % fqaddr
5108 5109 if opts['port']:
5109 5110 write = ui.status
5110 5111 else:
5111 5112 write = ui.write
5112 5113 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
5113 5114 (fqaddr, port, prefix, bindaddr, self.httpd.port))
5114 5115
5115 5116 def run(self):
5116 5117 self.httpd.serve_forever()
5117 5118
5118 5119 service = service()
5119 5120
5120 5121 cmdutil.service(opts, initfn=service.init, runfn=service.run)
5121 5122
5122 5123 @command('showconfig|debugconfig',
5123 5124 [('u', 'untrusted', None, _('show untrusted configuration options'))],
5124 5125 _('[-u] [NAME]...'))
5125 5126 def showconfig(ui, repo, *values, **opts):
5126 5127 """show combined config settings from all hgrc files
5127 5128
5128 5129 With no arguments, print names and values of all config items.
5129 5130
5130 5131 With one argument of the form section.name, print just the value
5131 5132 of that config item.
5132 5133
5133 5134 With multiple arguments, print names and values of all config
5134 5135 items with matching section names.
5135 5136
5136 5137 With --debug, the source (filename and line number) is printed
5137 5138 for each config item.
5138 5139
5139 5140 Returns 0 on success.
5140 5141 """
5141 5142
5142 5143 for f in scmutil.rcpath():
5143 5144 ui.debug('read config from: %s\n' % f)
5144 5145 untrusted = bool(opts.get('untrusted'))
5145 5146 if values:
5146 5147 sections = [v for v in values if '.' not in v]
5147 5148 items = [v for v in values if '.' in v]
5148 5149 if len(items) > 1 or items and sections:
5149 5150 raise util.Abort(_('only one config item permitted'))
5150 5151 for section, name, value in ui.walkconfig(untrusted=untrusted):
5151 5152 value = str(value).replace('\n', '\\n')
5152 5153 sectname = section + '.' + name
5153 5154 if values:
5154 5155 for v in values:
5155 5156 if v == section:
5156 5157 ui.debug('%s: ' %
5157 5158 ui.configsource(section, name, untrusted))
5158 5159 ui.write('%s=%s\n' % (sectname, value))
5159 5160 elif v == sectname:
5160 5161 ui.debug('%s: ' %
5161 5162 ui.configsource(section, name, untrusted))
5162 5163 ui.write(value, '\n')
5163 5164 else:
5164 5165 ui.debug('%s: ' %
5165 5166 ui.configsource(section, name, untrusted))
5166 5167 ui.write('%s=%s\n' % (sectname, value))
5167 5168
5168 5169 @command('^status|st',
5169 5170 [('A', 'all', None, _('show status of all files')),
5170 5171 ('m', 'modified', None, _('show only modified files')),
5171 5172 ('a', 'added', None, _('show only added files')),
5172 5173 ('r', 'removed', None, _('show only removed files')),
5173 5174 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5174 5175 ('c', 'clean', None, _('show only files without changes')),
5175 5176 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5176 5177 ('i', 'ignored', None, _('show only ignored files')),
5177 5178 ('n', 'no-status', None, _('hide status prefix')),
5178 5179 ('C', 'copies', None, _('show source of copied files')),
5179 5180 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5180 5181 ('', 'rev', [], _('show difference from revision'), _('REV')),
5181 5182 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5182 5183 ] + walkopts + subrepoopts,
5183 5184 _('[OPTION]... [FILE]...'))
5184 5185 def status(ui, repo, *pats, **opts):
5185 5186 """show changed files in the working directory
5186 5187
5187 5188 Show status of files in the repository. If names are given, only
5188 5189 files that match are shown. Files that are clean or ignored or
5189 5190 the source of a copy/move operation, are not listed unless
5190 5191 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5191 5192 Unless options described with "show only ..." are given, the
5192 5193 options -mardu are used.
5193 5194
5194 5195 Option -q/--quiet hides untracked (unknown and ignored) files
5195 5196 unless explicitly requested with -u/--unknown or -i/--ignored.
5196 5197
5197 5198 .. note::
5198 5199 status may appear to disagree with diff if permissions have
5199 5200 changed or a merge has occurred. The standard diff format does
5200 5201 not report permission changes and diff only reports changes
5201 5202 relative to one merge parent.
5202 5203
5203 5204 If one revision is given, it is used as the base revision.
5204 5205 If two revisions are given, the differences between them are
5205 5206 shown. The --change option can also be used as a shortcut to list
5206 5207 the changed files of a revision from its first parent.
5207 5208
5208 5209 The codes used to show the status of files are::
5209 5210
5210 5211 M = modified
5211 5212 A = added
5212 5213 R = removed
5213 5214 C = clean
5214 5215 ! = missing (deleted by non-hg command, but still tracked)
5215 5216 ? = not tracked
5216 5217 I = ignored
5217 5218 = origin of the previous file listed as A (added)
5218 5219
5219 5220 .. container:: verbose
5220 5221
5221 5222 Examples:
5222 5223
5223 5224 - show changes in the working directory relative to a
5224 5225 changeset::
5225 5226
5226 5227 hg status --rev 9353
5227 5228
5228 5229 - show all changes including copies in an existing changeset::
5229 5230
5230 5231 hg status --copies --change 9353
5231 5232
5232 5233 - get a NUL separated list of added files, suitable for xargs::
5233 5234
5234 5235 hg status -an0
5235 5236
5236 5237 Returns 0 on success.
5237 5238 """
5238 5239
5239 5240 revs = opts.get('rev')
5240 5241 change = opts.get('change')
5241 5242
5242 5243 if revs and change:
5243 5244 msg = _('cannot specify --rev and --change at the same time')
5244 5245 raise util.Abort(msg)
5245 5246 elif change:
5246 5247 node2 = scmutil.revsingle(repo, change, None).node()
5247 5248 node1 = repo[node2].p1().node()
5248 5249 else:
5249 5250 node1, node2 = scmutil.revpair(repo, revs)
5250 5251
5251 5252 cwd = (pats and repo.getcwd()) or ''
5252 5253 end = opts.get('print0') and '\0' or '\n'
5253 5254 copy = {}
5254 5255 states = 'modified added removed deleted unknown ignored clean'.split()
5255 5256 show = [k for k in states if opts.get(k)]
5256 5257 if opts.get('all'):
5257 5258 show += ui.quiet and (states[:4] + ['clean']) or states
5258 5259 if not show:
5259 5260 show = ui.quiet and states[:4] or states[:5]
5260 5261
5261 5262 stat = repo.status(node1, node2, scmutil.match(repo[node2], pats, opts),
5262 5263 'ignored' in show, 'clean' in show, 'unknown' in show,
5263 5264 opts.get('subrepos'))
5264 5265 changestates = zip(states, 'MAR!?IC', stat)
5265 5266
5266 5267 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
5267 5268 copy = copies.pathcopies(repo[node1], repo[node2])
5268 5269
5269 5270 fm = ui.formatter('status', opts)
5270 5271 format = '%s %s' + end
5271 5272 if opts.get('no_status'):
5272 5273 format = '%.0s%s' + end
5273 5274
5274 5275 for state, char, files in changestates:
5275 5276 if state in show:
5276 5277 label = 'status.' + state
5277 5278 for f in files:
5278 5279 fm.startitem()
5279 5280 fm.write("status path", format, char,
5280 5281 repo.pathto(f, cwd), label=label)
5281 5282 if f in copy:
5282 5283 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
5283 5284 label='status.copied')
5284 5285 fm.end()
5285 5286
5286 5287 @command('^summary|sum',
5287 5288 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
5288 5289 def summary(ui, repo, **opts):
5289 5290 """summarize working directory state
5290 5291
5291 5292 This generates a brief summary of the working directory state,
5292 5293 including parents, branch, commit status, and available updates.
5293 5294
5294 5295 With the --remote option, this will check the default paths for
5295 5296 incoming and outgoing changes. This can be time-consuming.
5296 5297
5297 5298 Returns 0 on success.
5298 5299 """
5299 5300
5300 5301 ctx = repo[None]
5301 5302 parents = ctx.parents()
5302 5303 pnode = parents[0].node()
5303 5304 marks = []
5304 5305
5305 5306 for p in parents:
5306 5307 # label with log.changeset (instead of log.parent) since this
5307 5308 # shows a working directory parent *changeset*:
5308 5309 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
5309 5310 label='log.changeset')
5310 5311 ui.write(' '.join(p.tags()), label='log.tag')
5311 5312 if p.bookmarks():
5312 5313 marks.extend(p.bookmarks())
5313 5314 if p.rev() == -1:
5314 5315 if not len(repo):
5315 5316 ui.write(_(' (empty repository)'))
5316 5317 else:
5317 5318 ui.write(_(' (no revision checked out)'))
5318 5319 ui.write('\n')
5319 5320 if p.description():
5320 5321 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5321 5322 label='log.summary')
5322 5323
5323 5324 branch = ctx.branch()
5324 5325 bheads = repo.branchheads(branch)
5325 5326 m = _('branch: %s\n') % branch
5326 5327 if branch != 'default':
5327 5328 ui.write(m, label='log.branch')
5328 5329 else:
5329 5330 ui.status(m, label='log.branch')
5330 5331
5331 5332 if marks:
5332 5333 current = repo._bookmarkcurrent
5333 5334 ui.write(_('bookmarks:'), label='log.bookmark')
5334 5335 if current is not None:
5335 5336 try:
5336 5337 marks.remove(current)
5337 5338 ui.write(' *' + current, label='bookmarks.current')
5338 5339 except ValueError:
5339 5340 # current bookmark not in parent ctx marks
5340 5341 pass
5341 5342 for m in marks:
5342 5343 ui.write(' ' + m, label='log.bookmark')
5343 5344 ui.write('\n', label='log.bookmark')
5344 5345
5345 5346 st = list(repo.status(unknown=True))[:6]
5346 5347
5347 5348 c = repo.dirstate.copies()
5348 5349 copied, renamed = [], []
5349 5350 for d, s in c.iteritems():
5350 5351 if s in st[2]:
5351 5352 st[2].remove(s)
5352 5353 renamed.append(d)
5353 5354 else:
5354 5355 copied.append(d)
5355 5356 if d in st[1]:
5356 5357 st[1].remove(d)
5357 5358 st.insert(3, renamed)
5358 5359 st.insert(4, copied)
5359 5360
5360 5361 ms = mergemod.mergestate(repo)
5361 5362 st.append([f for f in ms if ms[f] == 'u'])
5362 5363
5363 5364 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5364 5365 st.append(subs)
5365 5366
5366 5367 labels = [ui.label(_('%d modified'), 'status.modified'),
5367 5368 ui.label(_('%d added'), 'status.added'),
5368 5369 ui.label(_('%d removed'), 'status.removed'),
5369 5370 ui.label(_('%d renamed'), 'status.copied'),
5370 5371 ui.label(_('%d copied'), 'status.copied'),
5371 5372 ui.label(_('%d deleted'), 'status.deleted'),
5372 5373 ui.label(_('%d unknown'), 'status.unknown'),
5373 5374 ui.label(_('%d ignored'), 'status.ignored'),
5374 5375 ui.label(_('%d unresolved'), 'resolve.unresolved'),
5375 5376 ui.label(_('%d subrepos'), 'status.modified')]
5376 5377 t = []
5377 5378 for s, l in zip(st, labels):
5378 5379 if s:
5379 5380 t.append(l % len(s))
5380 5381
5381 5382 t = ', '.join(t)
5382 5383 cleanworkdir = False
5383 5384
5384 5385 if len(parents) > 1:
5385 5386 t += _(' (merge)')
5386 5387 elif branch != parents[0].branch():
5387 5388 t += _(' (new branch)')
5388 5389 elif (parents[0].closesbranch() and
5389 5390 pnode in repo.branchheads(branch, closed=True)):
5390 5391 t += _(' (head closed)')
5391 5392 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
5392 5393 t += _(' (clean)')
5393 5394 cleanworkdir = True
5394 5395 elif pnode not in bheads:
5395 5396 t += _(' (new branch head)')
5396 5397
5397 5398 if cleanworkdir:
5398 5399 ui.status(_('commit: %s\n') % t.strip())
5399 5400 else:
5400 5401 ui.write(_('commit: %s\n') % t.strip())
5401 5402
5402 5403 # all ancestors of branch heads - all ancestors of parent = new csets
5403 5404 new = [0] * len(repo)
5404 5405 cl = repo.changelog
5405 5406 for a in [cl.rev(n) for n in bheads]:
5406 5407 new[a] = 1
5407 5408 for a in cl.ancestors([cl.rev(n) for n in bheads]):
5408 5409 new[a] = 1
5409 5410 for a in [p.rev() for p in parents]:
5410 5411 if a >= 0:
5411 5412 new[a] = 0
5412 5413 for a in cl.ancestors([p.rev() for p in parents]):
5413 5414 new[a] = 0
5414 5415 new = sum(new)
5415 5416
5416 5417 if new == 0:
5417 5418 ui.status(_('update: (current)\n'))
5418 5419 elif pnode not in bheads:
5419 5420 ui.write(_('update: %d new changesets (update)\n') % new)
5420 5421 else:
5421 5422 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5422 5423 (new, len(bheads)))
5423 5424
5424 5425 if opts.get('remote'):
5425 5426 t = []
5426 5427 source, branches = hg.parseurl(ui.expandpath('default'))
5427 5428 other = hg.peer(repo, {}, source)
5428 5429 revs, checkout = hg.addbranchrevs(repo, other, branches,
5429 5430 opts.get('rev'))
5430 5431 ui.debug('comparing with %s\n' % util.hidepassword(source))
5431 5432 repo.ui.pushbuffer()
5432 5433 commoninc = discovery.findcommonincoming(repo, other)
5433 5434 _common, incoming, _rheads = commoninc
5434 5435 repo.ui.popbuffer()
5435 5436 if incoming:
5436 5437 t.append(_('1 or more incoming'))
5437 5438
5438 5439 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5439 5440 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5440 5441 if source != dest:
5441 5442 other = hg.peer(repo, {}, dest)
5442 5443 commoninc = None
5443 5444 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5444 5445 repo.ui.pushbuffer()
5445 5446 outgoing = discovery.findcommonoutgoing(repo, other,
5446 5447 commoninc=commoninc)
5447 5448 repo.ui.popbuffer()
5448 5449 o = outgoing.missing
5449 5450 if o:
5450 5451 t.append(_('%d outgoing') % len(o))
5451 5452 if 'bookmarks' in other.listkeys('namespaces'):
5452 5453 lmarks = repo.listkeys('bookmarks')
5453 5454 rmarks = other.listkeys('bookmarks')
5454 5455 diff = set(rmarks) - set(lmarks)
5455 5456 if len(diff) > 0:
5456 5457 t.append(_('%d incoming bookmarks') % len(diff))
5457 5458 diff = set(lmarks) - set(rmarks)
5458 5459 if len(diff) > 0:
5459 5460 t.append(_('%d outgoing bookmarks') % len(diff))
5460 5461
5461 5462 if t:
5462 5463 ui.write(_('remote: %s\n') % (', '.join(t)))
5463 5464 else:
5464 5465 ui.status(_('remote: (synced)\n'))
5465 5466
5466 5467 @command('tag',
5467 5468 [('f', 'force', None, _('force tag')),
5468 5469 ('l', 'local', None, _('make the tag local')),
5469 5470 ('r', 'rev', '', _('revision to tag'), _('REV')),
5470 5471 ('', 'remove', None, _('remove a tag')),
5471 5472 # -l/--local is already there, commitopts cannot be used
5472 5473 ('e', 'edit', None, _('edit commit message')),
5473 5474 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
5474 5475 ] + commitopts2,
5475 5476 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5476 5477 def tag(ui, repo, name1, *names, **opts):
5477 5478 """add one or more tags for the current or given revision
5478 5479
5479 5480 Name a particular revision using <name>.
5480 5481
5481 5482 Tags are used to name particular revisions of the repository and are
5482 5483 very useful to compare different revisions, to go back to significant
5483 5484 earlier versions or to mark branch points as releases, etc. Changing
5484 5485 an existing tag is normally disallowed; use -f/--force to override.
5485 5486
5486 5487 If no revision is given, the parent of the working directory is
5487 5488 used, or tip if no revision is checked out.
5488 5489
5489 5490 To facilitate version control, distribution, and merging of tags,
5490 5491 they are stored as a file named ".hgtags" which is managed similarly
5491 5492 to other project files and can be hand-edited if necessary. This
5492 5493 also means that tagging creates a new commit. The file
5493 5494 ".hg/localtags" is used for local tags (not shared among
5494 5495 repositories).
5495 5496
5496 5497 Tag commits are usually made at the head of a branch. If the parent
5497 5498 of the working directory is not a branch head, :hg:`tag` aborts; use
5498 5499 -f/--force to force the tag commit to be based on a non-head
5499 5500 changeset.
5500 5501
5501 5502 See :hg:`help dates` for a list of formats valid for -d/--date.
5502 5503
5503 5504 Since tag names have priority over branch names during revision
5504 5505 lookup, using an existing branch name as a tag name is discouraged.
5505 5506
5506 5507 Returns 0 on success.
5507 5508 """
5508 5509 wlock = lock = None
5509 5510 try:
5510 5511 wlock = repo.wlock()
5511 5512 lock = repo.lock()
5512 5513 rev_ = "."
5513 5514 names = [t.strip() for t in (name1,) + names]
5514 5515 if len(names) != len(set(names)):
5515 5516 raise util.Abort(_('tag names must be unique'))
5516 5517 for n in names:
5517 5518 if n in ['tip', '.', 'null']:
5518 5519 raise util.Abort(_("the name '%s' is reserved") % n)
5519 5520 if not n:
5520 5521 raise util.Abort(_('tag names cannot consist entirely of '
5521 5522 'whitespace'))
5522 5523 if opts.get('rev') and opts.get('remove'):
5523 5524 raise util.Abort(_("--rev and --remove are incompatible"))
5524 5525 if opts.get('rev'):
5525 5526 rev_ = opts['rev']
5526 5527 message = opts.get('message')
5527 5528 if opts.get('remove'):
5528 5529 expectedtype = opts.get('local') and 'local' or 'global'
5529 5530 for n in names:
5530 5531 if not repo.tagtype(n):
5531 5532 raise util.Abort(_("tag '%s' does not exist") % n)
5532 5533 if repo.tagtype(n) != expectedtype:
5533 5534 if expectedtype == 'global':
5534 5535 raise util.Abort(_("tag '%s' is not a global tag") % n)
5535 5536 else:
5536 5537 raise util.Abort(_("tag '%s' is not a local tag") % n)
5537 5538 rev_ = nullid
5538 5539 if not message:
5539 5540 # we don't translate commit messages
5540 5541 message = 'Removed tag %s' % ', '.join(names)
5541 5542 elif not opts.get('force'):
5542 5543 for n in names:
5543 5544 if n in repo.tags():
5544 5545 raise util.Abort(_("tag '%s' already exists "
5545 5546 "(use -f to force)") % n)
5546 5547 if not opts.get('local'):
5547 5548 p1, p2 = repo.dirstate.parents()
5548 5549 if p2 != nullid:
5549 5550 raise util.Abort(_('uncommitted merge'))
5550 5551 bheads = repo.branchheads()
5551 5552 if not opts.get('force') and bheads and p1 not in bheads:
5552 5553 raise util.Abort(_('not at a branch head (use -f to force)'))
5553 5554 r = scmutil.revsingle(repo, rev_).node()
5554 5555
5555 5556 if not message:
5556 5557 # we don't translate commit messages
5557 5558 message = ('Added tag %s for changeset %s' %
5558 5559 (', '.join(names), short(r)))
5559 5560
5560 5561 date = opts.get('date')
5561 5562 if date:
5562 5563 date = util.parsedate(date)
5563 5564
5564 5565 if opts.get('edit'):
5565 5566 message = ui.edit(message, ui.username())
5566 5567
5567 5568 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
5568 5569 finally:
5569 5570 release(lock, wlock)
5570 5571
5571 5572 @command('tags', [], '')
5572 5573 def tags(ui, repo):
5573 5574 """list repository tags
5574 5575
5575 5576 This lists both regular and local tags. When the -v/--verbose
5576 5577 switch is used, a third column "local" is printed for local tags.
5577 5578
5578 5579 Returns 0 on success.
5579 5580 """
5580 5581
5581 5582 hexfunc = ui.debugflag and hex or short
5582 5583 tagtype = ""
5583 5584
5584 5585 for t, n in reversed(repo.tagslist()):
5585 5586 if ui.quiet:
5586 5587 ui.write("%s\n" % t, label='tags.normal')
5587 5588 continue
5588 5589
5589 5590 hn = hexfunc(n)
5590 5591 r = "%5d:%s" % (repo.changelog.rev(n), hn)
5591 5592 rev = ui.label(r, 'log.changeset')
5592 5593 spaces = " " * (30 - encoding.colwidth(t))
5593 5594
5594 5595 tag = ui.label(t, 'tags.normal')
5595 5596 if ui.verbose:
5596 5597 if repo.tagtype(t) == 'local':
5597 5598 tagtype = " local"
5598 5599 tag = ui.label(t, 'tags.local')
5599 5600 else:
5600 5601 tagtype = ""
5601 5602 ui.write("%s%s %s%s\n" % (tag, spaces, rev, tagtype))
5602 5603
5603 5604 @command('tip',
5604 5605 [('p', 'patch', None, _('show patch')),
5605 5606 ('g', 'git', None, _('use git extended diff format')),
5606 5607 ] + templateopts,
5607 5608 _('[-p] [-g]'))
5608 5609 def tip(ui, repo, **opts):
5609 5610 """show the tip revision
5610 5611
5611 5612 The tip revision (usually just called the tip) is the changeset
5612 5613 most recently added to the repository (and therefore the most
5613 5614 recently changed head).
5614 5615
5615 5616 If you have just made a commit, that commit will be the tip. If
5616 5617 you have just pulled changes from another repository, the tip of
5617 5618 that repository becomes the current tip. The "tip" tag is special
5618 5619 and cannot be renamed or assigned to a different changeset.
5619 5620
5620 5621 Returns 0 on success.
5621 5622 """
5622 5623 displayer = cmdutil.show_changeset(ui, repo, opts)
5623 5624 displayer.show(repo[len(repo) - 1])
5624 5625 displayer.close()
5625 5626
5626 5627 @command('unbundle',
5627 5628 [('u', 'update', None,
5628 5629 _('update to new branch head if changesets were unbundled'))],
5629 5630 _('[-u] FILE...'))
5630 5631 def unbundle(ui, repo, fname1, *fnames, **opts):
5631 5632 """apply one or more changegroup files
5632 5633
5633 5634 Apply one or more compressed changegroup files generated by the
5634 5635 bundle command.
5635 5636
5636 5637 Returns 0 on success, 1 if an update has unresolved files.
5637 5638 """
5638 5639 fnames = (fname1,) + fnames
5639 5640
5640 5641 lock = repo.lock()
5641 5642 wc = repo['.']
5642 5643 try:
5643 5644 for fname in fnames:
5644 5645 f = url.open(ui, fname)
5645 5646 gen = changegroup.readbundle(f, fname)
5646 5647 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
5647 5648 finally:
5648 5649 lock.release()
5649 5650 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
5650 5651 return postincoming(ui, repo, modheads, opts.get('update'), None)
5651 5652
5652 5653 @command('^update|up|checkout|co',
5653 5654 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5654 5655 ('c', 'check', None,
5655 5656 _('update across branches if no uncommitted changes')),
5656 5657 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5657 5658 ('r', 'rev', '', _('revision'), _('REV'))],
5658 5659 _('[-c] [-C] [-d DATE] [[-r] REV]'))
5659 5660 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
5660 5661 """update working directory (or switch revisions)
5661 5662
5662 5663 Update the repository's working directory to the specified
5663 5664 changeset. If no changeset is specified, update to the tip of the
5664 5665 current named branch and move the current bookmark (see :hg:`help
5665 5666 bookmarks`).
5666 5667
5667 5668 If the changeset is not a descendant or ancestor of the working
5668 5669 directory's parent, the update is aborted. With the -c/--check
5669 5670 option, the working directory is checked for uncommitted changes; if
5670 5671 none are found, the working directory is updated to the specified
5671 5672 changeset.
5672 5673
5673 5674 Update sets the working directory's parent revison to the specified
5674 5675 changeset (see :hg:`help parents`).
5675 5676
5676 5677 The following rules apply when the working directory contains
5677 5678 uncommitted changes:
5678 5679
5679 5680 1. If neither -c/--check nor -C/--clean is specified, and if
5680 5681 the requested changeset is an ancestor or descendant of
5681 5682 the working directory's parent, the uncommitted changes
5682 5683 are merged into the requested changeset and the merged
5683 5684 result is left uncommitted. If the requested changeset is
5684 5685 not an ancestor or descendant (that is, it is on another
5685 5686 branch), the update is aborted and the uncommitted changes
5686 5687 are preserved.
5687 5688
5688 5689 2. With the -c/--check option, the update is aborted and the
5689 5690 uncommitted changes are preserved.
5690 5691
5691 5692 3. With the -C/--clean option, uncommitted changes are discarded and
5692 5693 the working directory is updated to the requested changeset.
5693 5694
5694 5695 Use null as the changeset to remove the working directory (like
5695 5696 :hg:`clone -U`).
5696 5697
5697 5698 If you want to revert just one file to an older revision, use
5698 5699 :hg:`revert [-r REV] NAME`.
5699 5700
5700 5701 See :hg:`help dates` for a list of formats valid for -d/--date.
5701 5702
5702 5703 Returns 0 on success, 1 if there are unresolved files.
5703 5704 """
5704 5705 if rev and node:
5705 5706 raise util.Abort(_("please specify just one revision"))
5706 5707
5707 5708 if rev is None or rev == '':
5708 5709 rev = node
5709 5710
5710 5711 # with no argument, we also move the current bookmark, if any
5711 5712 movemarkfrom = None
5712 5713 if rev is None or node == '':
5713 5714 movemarkfrom = repo['.'].node()
5714 5715
5715 5716 # if we defined a bookmark, we have to remember the original bookmark name
5716 5717 brev = rev
5717 5718 rev = scmutil.revsingle(repo, rev, rev).rev()
5718 5719
5719 5720 if check and clean:
5720 5721 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
5721 5722
5722 5723 if date:
5723 5724 if rev is not None:
5724 5725 raise util.Abort(_("you can't specify a revision and a date"))
5725 5726 rev = cmdutil.finddate(ui, repo, date)
5726 5727
5727 5728 if check:
5728 5729 c = repo[None]
5729 5730 if c.dirty(merge=False, branch=False):
5730 5731 raise util.Abort(_("uncommitted local changes"))
5731 5732 if rev is None:
5732 5733 rev = repo[repo[None].branch()].rev()
5733 5734 mergemod._checkunknown(repo, repo[None], repo[rev])
5734 5735
5735 5736 if clean:
5736 5737 ret = hg.clean(repo, rev)
5737 5738 else:
5738 5739 ret = hg.update(repo, rev)
5739 5740
5740 5741 if not ret and movemarkfrom:
5741 5742 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
5742 5743 ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
5743 5744 elif brev in repo._bookmarks:
5744 5745 bookmarks.setcurrent(repo, brev)
5745 5746 elif brev:
5746 5747 bookmarks.unsetcurrent(repo)
5747 5748
5748 5749 return ret
5749 5750
5750 5751 @command('verify', [])
5751 5752 def verify(ui, repo):
5752 5753 """verify the integrity of the repository
5753 5754
5754 5755 Verify the integrity of the current repository.
5755 5756
5756 5757 This will perform an extensive check of the repository's
5757 5758 integrity, validating the hashes and checksums of each entry in
5758 5759 the changelog, manifest, and tracked files, as well as the
5759 5760 integrity of their crosslinks and indices.
5760 5761
5761 5762 Returns 0 on success, 1 if errors are encountered.
5762 5763 """
5763 5764 return hg.verify(repo)
5764 5765
5765 5766 @command('version', [])
5766 5767 def version_(ui):
5767 5768 """output version and copyright information"""
5768 5769 ui.write(_("Mercurial Distributed SCM (version %s)\n")
5769 5770 % util.version())
5770 5771 ui.status(_(
5771 5772 "(see http://mercurial.selenic.com for more information)\n"
5772 5773 "\nCopyright (C) 2005-2012 Matt Mackall and others\n"
5773 5774 "This is free software; see the source for copying conditions. "
5774 5775 "There is NO\nwarranty; "
5775 5776 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5776 5777 ))
5777 5778
5778 5779 norepo = ("clone init version help debugcommands debugcomplete"
5779 5780 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
5780 5781 " debugknown debuggetbundle debugbundle")
5781 5782 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
5782 5783 " debugdata debugindex debugindexdot debugrevlog")
@@ -1,181 +1,184 b''
1 1 # hook.py - hook support for mercurial
2 2 #
3 3 # Copyright 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 i18n import _
9 9 import os, sys
10 10 import extensions, util
11 11
12 12 def _pythonhook(ui, repo, name, hname, funcname, args, throw):
13 13 '''call python hook. hook is callable object, looked up as
14 14 name in python module. if callable returns "true", hook
15 15 fails, else passes. if hook raises exception, treated as
16 16 hook failure. exception propagates if throw is "true".
17 17
18 18 reason for "true" meaning "hook failed" is so that
19 19 unmodified commands (e.g. mercurial.commands.update) can
20 20 be run as hooks without wrappers to convert return values.'''
21 21
22 22 ui.note(_("calling hook %s: %s\n") % (hname, funcname))
23 23 obj = funcname
24 24 if not util.safehasattr(obj, '__call__'):
25 25 d = funcname.rfind('.')
26 26 if d == -1:
27 27 raise util.Abort(_('%s hook is invalid ("%s" not in '
28 28 'a module)') % (hname, funcname))
29 29 modname = funcname[:d]
30 30 oldpaths = sys.path
31 31 if util.mainfrozen():
32 32 # binary installs require sys.path manipulation
33 33 modpath, modfile = os.path.split(modname)
34 34 if modpath and modfile:
35 35 sys.path = sys.path[:] + [modpath]
36 36 modname = modfile
37 37 try:
38 38 obj = __import__(modname)
39 39 except ImportError:
40 40 e1 = sys.exc_type, sys.exc_value, sys.exc_traceback
41 41 try:
42 42 # extensions are loaded with hgext_ prefix
43 43 obj = __import__("hgext_%s" % modname)
44 44 except ImportError:
45 45 e2 = sys.exc_type, sys.exc_value, sys.exc_traceback
46 46 if ui.tracebackflag:
47 47 ui.warn(_('exception from first failed import attempt:\n'))
48 48 ui.traceback(e1)
49 49 if ui.tracebackflag:
50 50 ui.warn(_('exception from second failed import attempt:\n'))
51 51 ui.traceback(e2)
52 52 raise util.Abort(_('%s hook is invalid '
53 53 '(import of "%s" failed)') %
54 54 (hname, modname))
55 55 sys.path = oldpaths
56 56 try:
57 57 for p in funcname.split('.')[1:]:
58 58 obj = getattr(obj, p)
59 59 except AttributeError:
60 60 raise util.Abort(_('%s hook is invalid '
61 61 '("%s" is not defined)') %
62 62 (hname, funcname))
63 63 if not util.safehasattr(obj, '__call__'):
64 64 raise util.Abort(_('%s hook is invalid '
65 65 '("%s" is not callable)') %
66 66 (hname, funcname))
67 67 try:
68 68 try:
69 69 # redirect IO descriptors the the ui descriptors so hooks
70 70 # that write directly to these don't mess up the command
71 71 # protocol when running through the command server
72 72 old = sys.stdout, sys.stderr, sys.stdin
73 73 sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin
74 74
75 75 r = obj(ui=ui, repo=repo, hooktype=name, **args)
76 76 except KeyboardInterrupt:
77 77 raise
78 78 except Exception, exc:
79 79 if isinstance(exc, util.Abort):
80 80 ui.warn(_('error: %s hook failed: %s\n') %
81 81 (hname, exc.args[0]))
82 82 else:
83 83 ui.warn(_('error: %s hook raised an exception: '
84 84 '%s\n') % (hname, exc))
85 85 if throw:
86 86 raise
87 87 ui.traceback()
88 88 return True
89 89 finally:
90 90 sys.stdout, sys.stderr, sys.stdin = old
91 91 if r:
92 92 if throw:
93 93 raise util.Abort(_('%s hook failed') % hname)
94 94 ui.warn(_('warning: %s hook failed\n') % hname)
95 95 return r
96 96
97 97 def _exthook(ui, repo, name, cmd, args, throw):
98 98 ui.note(_("running hook %s: %s\n") % (name, cmd))
99 99
100 100 env = {}
101 101 for k, v in args.iteritems():
102 102 if util.safehasattr(v, '__call__'):
103 103 v = v()
104 104 if isinstance(v, dict):
105 105 # make the dictionary element order stable across Python
106 106 # implementations
107 107 v = ('{' +
108 108 ', '.join('%r: %r' % i for i in sorted(v.iteritems())) +
109 109 '}')
110 110 env['HG_' + k.upper()] = v
111 111
112 112 if repo:
113 113 cwd = repo.root
114 114 else:
115 115 cwd = os.getcwd()
116 116 if 'HG_URL' in env and env['HG_URL'].startswith('remote:http'):
117 117 r = util.system(cmd, environ=env, cwd=cwd, out=ui)
118 118 else:
119 119 r = util.system(cmd, environ=env, cwd=cwd, out=ui.fout)
120 120 if r:
121 121 desc, r = util.explainexit(r)
122 122 if throw:
123 123 raise util.Abort(_('%s hook %s') % (name, desc))
124 124 ui.warn(_('warning: %s hook %s\n') % (name, desc))
125 125 return r
126 126
127 127 def _allhooks(ui):
128 128 hooks = []
129 129 for name, cmd in ui.configitems('hooks'):
130 130 if not name.startswith('priority'):
131 131 priority = ui.configint('hooks', 'priority.%s' % name, 0)
132 132 hooks.append((-priority, len(hooks), name, cmd))
133 133 return [(k, v) for p, o, k, v in sorted(hooks)]
134 134
135 135 _redirect = False
136 136 def redirect(state):
137 137 global _redirect
138 138 _redirect = state
139 139
140 140 def hook(ui, repo, name, throw=False, **args):
141 if not ui.callhooks:
142 return False
143
141 144 r = False
142 145
143 146 oldstdout = -1
144 147 if _redirect:
145 148 try:
146 149 stdoutno = sys.__stdout__.fileno()
147 150 stderrno = sys.__stderr__.fileno()
148 151 # temporarily redirect stdout to stderr, if possible
149 152 if stdoutno >= 0 and stderrno >= 0:
150 153 sys.__stdout__.flush()
151 154 oldstdout = os.dup(stdoutno)
152 155 os.dup2(stderrno, stdoutno)
153 156 except AttributeError:
154 157 # __stdout/err__ doesn't have fileno(), it's not a real file
155 158 pass
156 159
157 160 try:
158 161 for hname, cmd in _allhooks(ui):
159 162 if hname.split('.')[0] != name or not cmd:
160 163 continue
161 164 if util.safehasattr(cmd, '__call__'):
162 165 r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r
163 166 elif cmd.startswith('python:'):
164 167 if cmd.count(':') >= 2:
165 168 path, cmd = cmd[7:].rsplit(':', 1)
166 169 path = util.expandpath(path)
167 170 if repo:
168 171 path = os.path.join(repo.root, path)
169 172 mod = extensions.loadpath(path, 'hghook.%s' % hname)
170 173 hookfn = getattr(mod, cmd)
171 174 else:
172 175 hookfn = cmd[7:].strip()
173 176 r = _pythonhook(ui, repo, name, hname, hookfn, args, throw) or r
174 177 else:
175 178 r = _exthook(ui, repo, hname, cmd, args, throw) or r
176 179 finally:
177 180 if _redirect and oldstdout >= 0:
178 181 os.dup2(oldstdout, stdoutno)
179 182 os.close(oldstdout)
180 183
181 184 return r
@@ -1,759 +1,761 b''
1 1 # ui.py - user interface bits 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 i18n import _
9 9 import errno, getpass, os, socket, sys, tempfile, traceback
10 10 import config, scmutil, util, error, formatter
11 11
12 12 class ui(object):
13 13 def __init__(self, src=None):
14 14 self._buffers = []
15 15 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
16 16 self._reportuntrusted = True
17 17 self._ocfg = config.config() # overlay
18 18 self._tcfg = config.config() # trusted
19 19 self._ucfg = config.config() # untrusted
20 20 self._trustusers = set()
21 21 self._trustgroups = set()
22 self.callhooks = True
22 23
23 24 if src:
24 25 self.fout = src.fout
25 26 self.ferr = src.ferr
26 27 self.fin = src.fin
27 28
28 29 self._tcfg = src._tcfg.copy()
29 30 self._ucfg = src._ucfg.copy()
30 31 self._ocfg = src._ocfg.copy()
31 32 self._trustusers = src._trustusers.copy()
32 33 self._trustgroups = src._trustgroups.copy()
33 34 self.environ = src.environ
35 self.callhooks = src.callhooks
34 36 self.fixconfig()
35 37 else:
36 38 self.fout = sys.stdout
37 39 self.ferr = sys.stderr
38 40 self.fin = sys.stdin
39 41
40 42 # shared read-only environment
41 43 self.environ = os.environ
42 44 # we always trust global config files
43 45 for f in scmutil.rcpath():
44 46 self.readconfig(f, trust=True)
45 47
46 48 def copy(self):
47 49 return self.__class__(self)
48 50
49 51 def formatter(self, topic, opts):
50 52 return formatter.formatter(self, topic, opts)
51 53
52 54 def _trusted(self, fp, f):
53 55 st = util.fstat(fp)
54 56 if util.isowner(st):
55 57 return True
56 58
57 59 tusers, tgroups = self._trustusers, self._trustgroups
58 60 if '*' in tusers or '*' in tgroups:
59 61 return True
60 62
61 63 user = util.username(st.st_uid)
62 64 group = util.groupname(st.st_gid)
63 65 if user in tusers or group in tgroups or user == util.username():
64 66 return True
65 67
66 68 if self._reportuntrusted:
67 69 self.warn(_('not trusting file %s from untrusted '
68 70 'user %s, group %s\n') % (f, user, group))
69 71 return False
70 72
71 73 def readconfig(self, filename, root=None, trust=False,
72 74 sections=None, remap=None):
73 75 try:
74 76 fp = open(filename)
75 77 except IOError:
76 78 if not sections: # ignore unless we were looking for something
77 79 return
78 80 raise
79 81
80 82 cfg = config.config()
81 83 trusted = sections or trust or self._trusted(fp, filename)
82 84
83 85 try:
84 86 cfg.read(filename, fp, sections=sections, remap=remap)
85 87 fp.close()
86 88 except error.ConfigError, inst:
87 89 if trusted:
88 90 raise
89 91 self.warn(_("ignored: %s\n") % str(inst))
90 92
91 93 if self.plain():
92 94 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
93 95 'logtemplate', 'style',
94 96 'traceback', 'verbose'):
95 97 if k in cfg['ui']:
96 98 del cfg['ui'][k]
97 99 for k, v in cfg.items('defaults'):
98 100 del cfg['defaults'][k]
99 101 # Don't remove aliases from the configuration if in the exceptionlist
100 102 if self.plain('alias'):
101 103 for k, v in cfg.items('alias'):
102 104 del cfg['alias'][k]
103 105
104 106 if trusted:
105 107 self._tcfg.update(cfg)
106 108 self._tcfg.update(self._ocfg)
107 109 self._ucfg.update(cfg)
108 110 self._ucfg.update(self._ocfg)
109 111
110 112 if root is None:
111 113 root = os.path.expanduser('~')
112 114 self.fixconfig(root=root)
113 115
114 116 def fixconfig(self, root=None, section=None):
115 117 if section in (None, 'paths'):
116 118 # expand vars and ~
117 119 # translate paths relative to root (or home) into absolute paths
118 120 root = root or os.getcwd()
119 121 for c in self._tcfg, self._ucfg, self._ocfg:
120 122 for n, p in c.items('paths'):
121 123 if not p:
122 124 continue
123 125 if '%%' in p:
124 126 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
125 127 % (n, p, self.configsource('paths', n)))
126 128 p = p.replace('%%', '%')
127 129 p = util.expandpath(p)
128 130 if not util.hasscheme(p) and not os.path.isabs(p):
129 131 p = os.path.normpath(os.path.join(root, p))
130 132 c.set("paths", n, p)
131 133
132 134 if section in (None, 'ui'):
133 135 # update ui options
134 136 self.debugflag = self.configbool('ui', 'debug')
135 137 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
136 138 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
137 139 if self.verbose and self.quiet:
138 140 self.quiet = self.verbose = False
139 141 self._reportuntrusted = self.debugflag or self.configbool("ui",
140 142 "report_untrusted", True)
141 143 self.tracebackflag = self.configbool('ui', 'traceback', False)
142 144
143 145 if section in (None, 'trusted'):
144 146 # update trust information
145 147 self._trustusers.update(self.configlist('trusted', 'users'))
146 148 self._trustgroups.update(self.configlist('trusted', 'groups'))
147 149
148 150 def backupconfig(self, section, item):
149 151 return (self._ocfg.backup(section, item),
150 152 self._tcfg.backup(section, item),
151 153 self._ucfg.backup(section, item),)
152 154 def restoreconfig(self, data):
153 155 self._ocfg.restore(data[0])
154 156 self._tcfg.restore(data[1])
155 157 self._ucfg.restore(data[2])
156 158
157 159 def setconfig(self, section, name, value, overlay=True):
158 160 if overlay:
159 161 self._ocfg.set(section, name, value)
160 162 self._tcfg.set(section, name, value)
161 163 self._ucfg.set(section, name, value)
162 164 self.fixconfig(section=section)
163 165
164 166 def _data(self, untrusted):
165 167 return untrusted and self._ucfg or self._tcfg
166 168
167 169 def configsource(self, section, name, untrusted=False):
168 170 return self._data(untrusted).source(section, name) or 'none'
169 171
170 172 def config(self, section, name, default=None, untrusted=False):
171 173 if isinstance(name, list):
172 174 alternates = name
173 175 else:
174 176 alternates = [name]
175 177
176 178 for n in alternates:
177 179 value = self._data(untrusted).get(section, name, None)
178 180 if value is not None:
179 181 name = n
180 182 break
181 183 else:
182 184 value = default
183 185
184 186 if self.debugflag and not untrusted and self._reportuntrusted:
185 187 uvalue = self._ucfg.get(section, name)
186 188 if uvalue is not None and uvalue != value:
187 189 self.debug("ignoring untrusted configuration option "
188 190 "%s.%s = %s\n" % (section, name, uvalue))
189 191 return value
190 192
191 193 def configpath(self, section, name, default=None, untrusted=False):
192 194 'get a path config item, expanded relative to repo root or config file'
193 195 v = self.config(section, name, default, untrusted)
194 196 if v is None:
195 197 return None
196 198 if not os.path.isabs(v) or "://" not in v:
197 199 src = self.configsource(section, name, untrusted)
198 200 if ':' in src:
199 201 base = os.path.dirname(src.rsplit(':')[0])
200 202 v = os.path.join(base, os.path.expanduser(v))
201 203 return v
202 204
203 205 def configbool(self, section, name, default=False, untrusted=False):
204 206 """parse a configuration element as a boolean
205 207
206 208 >>> u = ui(); s = 'foo'
207 209 >>> u.setconfig(s, 'true', 'yes')
208 210 >>> u.configbool(s, 'true')
209 211 True
210 212 >>> u.setconfig(s, 'false', 'no')
211 213 >>> u.configbool(s, 'false')
212 214 False
213 215 >>> u.configbool(s, 'unknown')
214 216 False
215 217 >>> u.configbool(s, 'unknown', True)
216 218 True
217 219 >>> u.setconfig(s, 'invalid', 'somevalue')
218 220 >>> u.configbool(s, 'invalid')
219 221 Traceback (most recent call last):
220 222 ...
221 223 ConfigError: foo.invalid is not a boolean ('somevalue')
222 224 """
223 225
224 226 v = self.config(section, name, None, untrusted)
225 227 if v is None:
226 228 return default
227 229 if isinstance(v, bool):
228 230 return v
229 231 b = util.parsebool(v)
230 232 if b is None:
231 233 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
232 234 % (section, name, v))
233 235 return b
234 236
235 237 def configint(self, section, name, default=None, untrusted=False):
236 238 """parse a configuration element as an integer
237 239
238 240 >>> u = ui(); s = 'foo'
239 241 >>> u.setconfig(s, 'int1', '42')
240 242 >>> u.configint(s, 'int1')
241 243 42
242 244 >>> u.setconfig(s, 'int2', '-42')
243 245 >>> u.configint(s, 'int2')
244 246 -42
245 247 >>> u.configint(s, 'unknown', 7)
246 248 7
247 249 >>> u.setconfig(s, 'invalid', 'somevalue')
248 250 >>> u.configint(s, 'invalid')
249 251 Traceback (most recent call last):
250 252 ...
251 253 ConfigError: foo.invalid is not an integer ('somevalue')
252 254 """
253 255
254 256 v = self.config(section, name, None, untrusted)
255 257 if v is None:
256 258 return default
257 259 try:
258 260 return int(v)
259 261 except ValueError:
260 262 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
261 263 % (section, name, v))
262 264
263 265 def configlist(self, section, name, default=None, untrusted=False):
264 266 """parse a configuration element as a list of comma/space separated
265 267 strings
266 268
267 269 >>> u = ui(); s = 'foo'
268 270 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
269 271 >>> u.configlist(s, 'list1')
270 272 ['this', 'is', 'a small', 'test']
271 273 """
272 274
273 275 def _parse_plain(parts, s, offset):
274 276 whitespace = False
275 277 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
276 278 whitespace = True
277 279 offset += 1
278 280 if offset >= len(s):
279 281 return None, parts, offset
280 282 if whitespace:
281 283 parts.append('')
282 284 if s[offset] == '"' and not parts[-1]:
283 285 return _parse_quote, parts, offset + 1
284 286 elif s[offset] == '"' and parts[-1][-1] == '\\':
285 287 parts[-1] = parts[-1][:-1] + s[offset]
286 288 return _parse_plain, parts, offset + 1
287 289 parts[-1] += s[offset]
288 290 return _parse_plain, parts, offset + 1
289 291
290 292 def _parse_quote(parts, s, offset):
291 293 if offset < len(s) and s[offset] == '"': # ""
292 294 parts.append('')
293 295 offset += 1
294 296 while offset < len(s) and (s[offset].isspace() or
295 297 s[offset] == ','):
296 298 offset += 1
297 299 return _parse_plain, parts, offset
298 300
299 301 while offset < len(s) and s[offset] != '"':
300 302 if (s[offset] == '\\' and offset + 1 < len(s)
301 303 and s[offset + 1] == '"'):
302 304 offset += 1
303 305 parts[-1] += '"'
304 306 else:
305 307 parts[-1] += s[offset]
306 308 offset += 1
307 309
308 310 if offset >= len(s):
309 311 real_parts = _configlist(parts[-1])
310 312 if not real_parts:
311 313 parts[-1] = '"'
312 314 else:
313 315 real_parts[0] = '"' + real_parts[0]
314 316 parts = parts[:-1]
315 317 parts.extend(real_parts)
316 318 return None, parts, offset
317 319
318 320 offset += 1
319 321 while offset < len(s) and s[offset] in [' ', ',']:
320 322 offset += 1
321 323
322 324 if offset < len(s):
323 325 if offset + 1 == len(s) and s[offset] == '"':
324 326 parts[-1] += '"'
325 327 offset += 1
326 328 else:
327 329 parts.append('')
328 330 else:
329 331 return None, parts, offset
330 332
331 333 return _parse_plain, parts, offset
332 334
333 335 def _configlist(s):
334 336 s = s.rstrip(' ,')
335 337 if not s:
336 338 return []
337 339 parser, parts, offset = _parse_plain, [''], 0
338 340 while parser:
339 341 parser, parts, offset = parser(parts, s, offset)
340 342 return parts
341 343
342 344 result = self.config(section, name, untrusted=untrusted)
343 345 if result is None:
344 346 result = default or []
345 347 if isinstance(result, basestring):
346 348 result = _configlist(result.lstrip(' ,\n'))
347 349 if result is None:
348 350 result = default or []
349 351 return result
350 352
351 353 def has_section(self, section, untrusted=False):
352 354 '''tell whether section exists in config.'''
353 355 return section in self._data(untrusted)
354 356
355 357 def configitems(self, section, untrusted=False):
356 358 items = self._data(untrusted).items(section)
357 359 if self.debugflag and not untrusted and self._reportuntrusted:
358 360 for k, v in self._ucfg.items(section):
359 361 if self._tcfg.get(section, k) != v:
360 362 self.debug("ignoring untrusted configuration option "
361 363 "%s.%s = %s\n" % (section, k, v))
362 364 return items
363 365
364 366 def walkconfig(self, untrusted=False):
365 367 cfg = self._data(untrusted)
366 368 for section in cfg.sections():
367 369 for name, value in self.configitems(section, untrusted):
368 370 yield section, name, value
369 371
370 372 def plain(self, feature=None):
371 373 '''is plain mode active?
372 374
373 375 Plain mode means that all configuration variables which affect
374 376 the behavior and output of Mercurial should be
375 377 ignored. Additionally, the output should be stable,
376 378 reproducible and suitable for use in scripts or applications.
377 379
378 380 The only way to trigger plain mode is by setting either the
379 381 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
380 382
381 383 The return value can either be
382 384 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
383 385 - True otherwise
384 386 '''
385 387 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
386 388 return False
387 389 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
388 390 if feature and exceptions:
389 391 return feature not in exceptions
390 392 return True
391 393
392 394 def username(self):
393 395 """Return default username to be used in commits.
394 396
395 397 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
396 398 and stop searching if one of these is set.
397 399 If not found and ui.askusername is True, ask the user, else use
398 400 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
399 401 """
400 402 user = os.environ.get("HGUSER")
401 403 if user is None:
402 404 user = self.config("ui", "username")
403 405 if user is not None:
404 406 user = os.path.expandvars(user)
405 407 if user is None:
406 408 user = os.environ.get("EMAIL")
407 409 if user is None and self.configbool("ui", "askusername"):
408 410 user = self.prompt(_("enter a commit username:"), default=None)
409 411 if user is None and not self.interactive():
410 412 try:
411 413 user = '%s@%s' % (util.getuser(), socket.getfqdn())
412 414 self.warn(_("no username found, using '%s' instead\n") % user)
413 415 except KeyError:
414 416 pass
415 417 if not user:
416 418 raise util.Abort(_('no username supplied (see "hg help config")'))
417 419 if "\n" in user:
418 420 raise util.Abort(_("username %s contains a newline\n") % repr(user))
419 421 return user
420 422
421 423 def shortuser(self, user):
422 424 """Return a short representation of a user name or email address."""
423 425 if not self.verbose:
424 426 user = util.shortuser(user)
425 427 return user
426 428
427 429 def expandpath(self, loc, default=None):
428 430 """Return repository location relative to cwd or from [paths]"""
429 431 if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
430 432 return loc
431 433
432 434 path = self.config('paths', loc)
433 435 if not path and default is not None:
434 436 path = self.config('paths', default)
435 437 return path or loc
436 438
437 439 def pushbuffer(self):
438 440 self._buffers.append([])
439 441
440 442 def popbuffer(self, labeled=False):
441 443 '''pop the last buffer and return the buffered output
442 444
443 445 If labeled is True, any labels associated with buffered
444 446 output will be handled. By default, this has no effect
445 447 on the output returned, but extensions and GUI tools may
446 448 handle this argument and returned styled output. If output
447 449 is being buffered so it can be captured and parsed or
448 450 processed, labeled should not be set to True.
449 451 '''
450 452 return "".join(self._buffers.pop())
451 453
452 454 def write(self, *args, **opts):
453 455 '''write args to output
454 456
455 457 By default, this method simply writes to the buffer or stdout,
456 458 but extensions or GUI tools may override this method,
457 459 write_err(), popbuffer(), and label() to style output from
458 460 various parts of hg.
459 461
460 462 An optional keyword argument, "label", can be passed in.
461 463 This should be a string containing label names separated by
462 464 space. Label names take the form of "topic.type". For example,
463 465 ui.debug() issues a label of "ui.debug".
464 466
465 467 When labeling output for a specific command, a label of
466 468 "cmdname.type" is recommended. For example, status issues
467 469 a label of "status.modified" for modified files.
468 470 '''
469 471 if self._buffers:
470 472 self._buffers[-1].extend([str(a) for a in args])
471 473 else:
472 474 for a in args:
473 475 self.fout.write(str(a))
474 476
475 477 def write_err(self, *args, **opts):
476 478 try:
477 479 if not getattr(self.fout, 'closed', False):
478 480 self.fout.flush()
479 481 for a in args:
480 482 self.ferr.write(str(a))
481 483 # stderr may be buffered under win32 when redirected to files,
482 484 # including stdout.
483 485 if not getattr(self.ferr, 'closed', False):
484 486 self.ferr.flush()
485 487 except IOError, inst:
486 488 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
487 489 raise
488 490
489 491 def flush(self):
490 492 try: self.fout.flush()
491 493 except (IOError, ValueError): pass
492 494 try: self.ferr.flush()
493 495 except (IOError, ValueError): pass
494 496
495 497 def _isatty(self, fh):
496 498 if self.configbool('ui', 'nontty', False):
497 499 return False
498 500 return util.isatty(fh)
499 501
500 502 def interactive(self):
501 503 '''is interactive input allowed?
502 504
503 505 An interactive session is a session where input can be reasonably read
504 506 from `sys.stdin'. If this function returns false, any attempt to read
505 507 from stdin should fail with an error, unless a sensible default has been
506 508 specified.
507 509
508 510 Interactiveness is triggered by the value of the `ui.interactive'
509 511 configuration variable or - if it is unset - when `sys.stdin' points
510 512 to a terminal device.
511 513
512 514 This function refers to input only; for output, see `ui.formatted()'.
513 515 '''
514 516 i = self.configbool("ui", "interactive", None)
515 517 if i is None:
516 518 # some environments replace stdin without implementing isatty
517 519 # usually those are non-interactive
518 520 return self._isatty(self.fin)
519 521
520 522 return i
521 523
522 524 def termwidth(self):
523 525 '''how wide is the terminal in columns?
524 526 '''
525 527 if 'COLUMNS' in os.environ:
526 528 try:
527 529 return int(os.environ['COLUMNS'])
528 530 except ValueError:
529 531 pass
530 532 return util.termwidth()
531 533
532 534 def formatted(self):
533 535 '''should formatted output be used?
534 536
535 537 It is often desirable to format the output to suite the output medium.
536 538 Examples of this are truncating long lines or colorizing messages.
537 539 However, this is not often not desirable when piping output into other
538 540 utilities, e.g. `grep'.
539 541
540 542 Formatted output is triggered by the value of the `ui.formatted'
541 543 configuration variable or - if it is unset - when `sys.stdout' points
542 544 to a terminal device. Please note that `ui.formatted' should be
543 545 considered an implementation detail; it is not intended for use outside
544 546 Mercurial or its extensions.
545 547
546 548 This function refers to output only; for input, see `ui.interactive()'.
547 549 This function always returns false when in plain mode, see `ui.plain()'.
548 550 '''
549 551 if self.plain():
550 552 return False
551 553
552 554 i = self.configbool("ui", "formatted", None)
553 555 if i is None:
554 556 # some environments replace stdout without implementing isatty
555 557 # usually those are non-interactive
556 558 return self._isatty(self.fout)
557 559
558 560 return i
559 561
560 562 def _readline(self, prompt=''):
561 563 if self._isatty(self.fin):
562 564 try:
563 565 # magically add command line editing support, where
564 566 # available
565 567 import readline
566 568 # force demandimport to really load the module
567 569 readline.read_history_file
568 570 # windows sometimes raises something other than ImportError
569 571 except Exception:
570 572 pass
571 573
572 574 # call write() so output goes through subclassed implementation
573 575 # e.g. color extension on Windows
574 576 self.write(prompt)
575 577
576 578 # instead of trying to emulate raw_input, swap (self.fin,
577 579 # self.fout) with (sys.stdin, sys.stdout)
578 580 oldin = sys.stdin
579 581 oldout = sys.stdout
580 582 sys.stdin = self.fin
581 583 sys.stdout = self.fout
582 584 line = raw_input(' ')
583 585 sys.stdin = oldin
584 586 sys.stdout = oldout
585 587
586 588 # When stdin is in binary mode on Windows, it can cause
587 589 # raw_input() to emit an extra trailing carriage return
588 590 if os.linesep == '\r\n' and line and line[-1] == '\r':
589 591 line = line[:-1]
590 592 return line
591 593
592 594 def prompt(self, msg, default="y"):
593 595 """Prompt user with msg, read response.
594 596 If ui is not interactive, the default is returned.
595 597 """
596 598 if not self.interactive():
597 599 self.write(msg, ' ', default, "\n")
598 600 return default
599 601 try:
600 602 r = self._readline(self.label(msg, 'ui.prompt'))
601 603 if not r:
602 604 return default
603 605 return r
604 606 except EOFError:
605 607 raise util.Abort(_('response expected'))
606 608
607 609 def promptchoice(self, msg, choices, default=0):
608 610 """Prompt user with msg, read response, and ensure it matches
609 611 one of the provided choices. The index of the choice is returned.
610 612 choices is a sequence of acceptable responses with the format:
611 613 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
612 614 If ui is not interactive, the default is returned.
613 615 """
614 616 resps = [s[s.index('&')+1].lower() for s in choices]
615 617 while True:
616 618 r = self.prompt(msg, resps[default])
617 619 if r.lower() in resps:
618 620 return resps.index(r.lower())
619 621 self.write(_("unrecognized response\n"))
620 622
621 623 def getpass(self, prompt=None, default=None):
622 624 if not self.interactive():
623 625 return default
624 626 try:
625 627 return getpass.getpass(prompt or _('password: '))
626 628 except EOFError:
627 629 raise util.Abort(_('response expected'))
628 630 def status(self, *msg, **opts):
629 631 '''write status message to output (if ui.quiet is False)
630 632
631 633 This adds an output label of "ui.status".
632 634 '''
633 635 if not self.quiet:
634 636 opts['label'] = opts.get('label', '') + ' ui.status'
635 637 self.write(*msg, **opts)
636 638 def warn(self, *msg, **opts):
637 639 '''write warning message to output (stderr)
638 640
639 641 This adds an output label of "ui.warning".
640 642 '''
641 643 opts['label'] = opts.get('label', '') + ' ui.warning'
642 644 self.write_err(*msg, **opts)
643 645 def note(self, *msg, **opts):
644 646 '''write note to output (if ui.verbose is True)
645 647
646 648 This adds an output label of "ui.note".
647 649 '''
648 650 if self.verbose:
649 651 opts['label'] = opts.get('label', '') + ' ui.note'
650 652 self.write(*msg, **opts)
651 653 def debug(self, *msg, **opts):
652 654 '''write debug message to output (if ui.debugflag is True)
653 655
654 656 This adds an output label of "ui.debug".
655 657 '''
656 658 if self.debugflag:
657 659 opts['label'] = opts.get('label', '') + ' ui.debug'
658 660 self.write(*msg, **opts)
659 661 def edit(self, text, user):
660 662 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
661 663 text=True)
662 664 try:
663 665 f = os.fdopen(fd, "w")
664 666 f.write(text)
665 667 f.close()
666 668
667 669 editor = self.geteditor()
668 670
669 671 util.system("%s \"%s\"" % (editor, name),
670 672 environ={'HGUSER': user},
671 673 onerr=util.Abort, errprefix=_("edit failed"),
672 674 out=self.fout)
673 675
674 676 f = open(name)
675 677 t = f.read()
676 678 f.close()
677 679 finally:
678 680 os.unlink(name)
679 681
680 682 return t
681 683
682 684 def traceback(self, exc=None):
683 685 '''print exception traceback if traceback printing enabled.
684 686 only to call in exception handler. returns true if traceback
685 687 printed.'''
686 688 if self.tracebackflag:
687 689 if exc:
688 690 traceback.print_exception(exc[0], exc[1], exc[2],
689 691 file=self.ferr)
690 692 else:
691 693 traceback.print_exc(file=self.ferr)
692 694 return self.tracebackflag
693 695
694 696 def geteditor(self):
695 697 '''return editor to use'''
696 698 if sys.platform == 'plan9':
697 699 # vi is the MIPS instruction simulator on Plan 9. We
698 700 # instead default to E to plumb commit messages to
699 701 # avoid confusion.
700 702 editor = 'E'
701 703 else:
702 704 editor = 'vi'
703 705 return (os.environ.get("HGEDITOR") or
704 706 self.config("ui", "editor") or
705 707 os.environ.get("VISUAL") or
706 708 os.environ.get("EDITOR", editor))
707 709
708 710 def progress(self, topic, pos, item="", unit="", total=None):
709 711 '''show a progress message
710 712
711 713 With stock hg, this is simply a debug message that is hidden
712 714 by default, but with extensions or GUI tools it may be
713 715 visible. 'topic' is the current operation, 'item' is a
714 716 non-numeric marker of the current position (ie the currently
715 717 in-process file), 'pos' is the current numeric position (ie
716 718 revision, bytes, etc.), unit is a corresponding unit label,
717 719 and total is the highest expected pos.
718 720
719 721 Multiple nested topics may be active at a time.
720 722
721 723 All topics should be marked closed by setting pos to None at
722 724 termination.
723 725 '''
724 726
725 727 if pos is None or not self.debugflag:
726 728 return
727 729
728 730 if unit:
729 731 unit = ' ' + unit
730 732 if item:
731 733 item = ' ' + item
732 734
733 735 if total:
734 736 pct = 100.0 * pos / total
735 737 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
736 738 % (topic, item, pos, total, unit, pct))
737 739 else:
738 740 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
739 741
740 742 def log(self, service, message):
741 743 '''hook for logging facility extensions
742 744
743 745 service should be a readily-identifiable subsystem, which will
744 746 allow filtering.
745 747 message should be a newline-terminated string to log.
746 748 '''
747 749 pass
748 750
749 751 def label(self, msg, label):
750 752 '''style msg based on supplied label
751 753
752 754 Like ui.write(), this just returns msg unchanged, but extensions
753 755 and GUI tools can override it to allow styling output without
754 756 writing it.
755 757
756 758 ui.write(s, 'label') is equivalent to
757 759 ui.write(ui.label(s, 'label')).
758 760 '''
759 761 return msg
@@ -1,349 +1,355 b''
1 1 $ hg init
2 2
3 3 Setup:
4 4
5 5 $ echo a >> a
6 6 $ hg ci -Am 'base'
7 7 adding a
8 8
9 9 Refuse to amend public csets:
10 10
11 11 $ hg phase -r . -p
12 12 $ hg ci --amend
13 13 abort: cannot amend public changesets
14 14 [255]
15 15 $ hg phase -r . -f -d
16 16
17 17 $ echo a >> a
18 18 $ hg ci -Am 'base1'
19 19
20 20 Nothing to amend:
21 21
22 22 $ hg ci --amend
23 23 nothing changed
24 24 [1]
25 25
26 $ echo '[hooks]' >> $HGRCPATH
27 $ echo 'pretxncommit.foo = echo "pretxncommit $HG_NODE"; hg id -r $HG_NODE' >> $HGRCPATH
28
26 29 Amending changeset with changes in working dir:
27 30
28 31 $ echo a >> a
29 32 $ hg ci --amend -m 'amend base1'
30 saved backup bundle to $TESTTMP/.hg/strip-backup/489edb5b847d-amend-backup.hg (glob)
33 pretxncommit 9cd25b479c51be2f4ed2c38e7abdf7ce67d8e0dc
34 9cd25b479c51 tip
35 saved backup bundle to $TESTTMP/.hg/strip-backup/489edb5b847d-amend-backup.hg
36 $ echo 'pretxncommit.foo = ' >> $HGRCPATH
31 37 $ hg diff -c .
32 38 diff -r ad120869acf0 -r 9cd25b479c51 a
33 39 --- a/a Thu Jan 01 00:00:00 1970 +0000
34 40 +++ b/a Thu Jan 01 00:00:00 1970 +0000
35 41 @@ -1,1 +1,3 @@
36 42 a
37 43 +a
38 44 +a
39 45 $ hg log
40 46 changeset: 1:9cd25b479c51
41 47 tag: tip
42 48 user: test
43 49 date: Thu Jan 01 00:00:00 1970 +0000
44 50 summary: amend base1
45 51
46 52 changeset: 0:ad120869acf0
47 53 user: test
48 54 date: Thu Jan 01 00:00:00 1970 +0000
49 55 summary: base
50 56
51 57
52 58 Add new file:
53 59
54 60 $ echo b > b
55 61 $ hg ci --amend -Am 'amend base1 new file'
56 62 adding b
57 63 saved backup bundle to $TESTTMP/.hg/strip-backup/9cd25b479c51-amend-backup.hg (glob)
58 64
59 65 Remove file that was added in amended commit:
60 66
61 67 $ hg rm b
62 68 $ hg ci --amend -m 'amend base1 remove new file'
63 69 saved backup bundle to $TESTTMP/.hg/strip-backup/e2bb3ecffd2f-amend-backup.hg (glob)
64 70
65 71 $ hg cat b
66 72 b: no such file in rev 664a9b2d60cd
67 73 [1]
68 74
69 75 No changes, just a different message:
70 76
71 77 $ hg ci -v --amend -m 'no changes, new message'
72 78 amending changeset 664a9b2d60cd
73 79 copying changeset 664a9b2d60cd to ad120869acf0
74 80 a
75 81 stripping amended changeset 664a9b2d60cd
76 82 1 changesets found
77 83 saved backup bundle to $TESTTMP/.hg/strip-backup/664a9b2d60cd-amend-backup.hg (glob)
78 84 1 changesets found
79 85 adding branch
80 86 adding changesets
81 87 adding manifests
82 88 adding file changes
83 89 added 1 changesets with 1 changes to 1 files
84 90 committed changeset 1:ea6e356ff2ad
85 91 $ hg diff -c .
86 92 diff -r ad120869acf0 -r ea6e356ff2ad a
87 93 --- a/a Thu Jan 01 00:00:00 1970 +0000
88 94 +++ b/a Thu Jan 01 00:00:00 1970 +0000
89 95 @@ -1,1 +1,3 @@
90 96 a
91 97 +a
92 98 +a
93 99 $ hg log
94 100 changeset: 1:ea6e356ff2ad
95 101 tag: tip
96 102 user: test
97 103 date: Thu Jan 01 00:00:00 1970 +0000
98 104 summary: no changes, new message
99 105
100 106 changeset: 0:ad120869acf0
101 107 user: test
102 108 date: Thu Jan 01 00:00:00 1970 +0000
103 109 summary: base
104 110
105 111
106 112 Disable default date on commit so when -d isn't given, the old date is preserved:
107 113
108 114 $ echo '[defaults]' >> $HGRCPATH
109 115 $ echo 'commit=' >> $HGRCPATH
110 116
111 117 Test -u/-d:
112 118
113 119 $ hg ci --amend -u foo -d '1 0'
114 120 saved backup bundle to $TESTTMP/.hg/strip-backup/ea6e356ff2ad-amend-backup.hg (glob)
115 121 $ echo a >> a
116 122 $ hg ci --amend -u foo -d '1 0'
117 123 saved backup bundle to $TESTTMP/.hg/strip-backup/377b91ce8b56-amend-backup.hg (glob)
118 124 $ hg log -r .
119 125 changeset: 1:2c94e4a5756f
120 126 tag: tip
121 127 user: foo
122 128 date: Thu Jan 01 00:00:01 1970 +0000
123 129 summary: no changes, new message
124 130
125 131
126 132 Open editor with old commit message if a message isn't given otherwise:
127 133
128 134 $ cat > editor.sh << '__EOF__'
129 135 > #!/bin/sh
130 136 > cat $1
131 137 > echo "another precious commit message" > "$1"
132 138 > __EOF__
133 139 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
134 140 amending changeset 2c94e4a5756f
135 141 copying changeset 2c94e4a5756f to ad120869acf0
136 142 no changes, new message
137 143
138 144
139 145 HG: Enter commit message. Lines beginning with 'HG:' are removed.
140 146 HG: Leave message empty to abort commit.
141 147 HG: --
142 148 HG: user: foo
143 149 HG: branch 'default'
144 150 HG: changed a
145 151 a
146 152 stripping amended changeset 2c94e4a5756f
147 153 1 changesets found
148 154 saved backup bundle to $TESTTMP/.hg/strip-backup/2c94e4a5756f-amend-backup.hg (glob)
149 155 1 changesets found
150 156 adding branch
151 157 adding changesets
152 158 adding manifests
153 159 adding file changes
154 160 added 1 changesets with 1 changes to 1 files
155 161 committed changeset 1:ffb49186f961
156 162
157 163 Same, but with changes in working dir (different code path):
158 164
159 165 $ echo a >> a
160 166 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
161 167 amending changeset ffb49186f961
162 168 another precious commit message
163 169
164 170
165 171 HG: Enter commit message. Lines beginning with 'HG:' are removed.
166 172 HG: Leave message empty to abort commit.
167 173 HG: --
168 174 HG: user: foo
169 175 HG: branch 'default'
170 176 HG: changed a
171 177 a
172 178 copying changeset 27f3aacd3011 to ad120869acf0
173 179 a
174 180 stripping intermediate changeset 27f3aacd3011
175 181 stripping amended changeset ffb49186f961
176 182 2 changesets found
177 183 saved backup bundle to $TESTTMP/.hg/strip-backup/ffb49186f961-amend-backup.hg (glob)
178 184 1 changesets found
179 185 adding branch
180 186 adding changesets
181 187 adding manifests
182 188 adding file changes
183 189 added 1 changesets with 1 changes to 1 files
184 190 committed changeset 1:fb6cca43446f
185 191
186 192 $ rm editor.sh
187 193 $ hg log -r .
188 194 changeset: 1:fb6cca43446f
189 195 tag: tip
190 196 user: foo
191 197 date: Thu Jan 01 00:00:01 1970 +0000
192 198 summary: another precious commit message
193 199
194 200
195 201 Moving bookmarks, preserve active bookmark:
196 202
197 203 $ hg book book1
198 204 $ hg book book2
199 205 $ hg ci --amend -m 'move bookmarks'
200 206 saved backup bundle to $TESTTMP/.hg/strip-backup/fb6cca43446f-amend-backup.hg (glob)
201 207 $ hg book
202 208 book1 1:0cf1c7a51bcf
203 209 * book2 1:0cf1c7a51bcf
204 210 $ echo a >> a
205 211 $ hg ci --amend -m 'move bookmarks'
206 212 saved backup bundle to $TESTTMP/.hg/strip-backup/0cf1c7a51bcf-amend-backup.hg (glob)
207 213 $ hg book
208 214 book1 1:7344472bd951
209 215 * book2 1:7344472bd951
210 216
211 217 $ echo '[defaults]' >> $HGRCPATH
212 218 $ echo "commit=-d '0 0'" >> $HGRCPATH
213 219
214 220 Moving branches:
215 221
216 222 $ hg branch foo
217 223 marked working directory as branch foo
218 224 (branches are permanent and global, did you want a bookmark?)
219 225 $ echo a >> a
220 226 $ hg ci -m 'branch foo'
221 227 $ hg branch default -f
222 228 marked working directory as branch default
223 229 (branches are permanent and global, did you want a bookmark?)
224 230 $ hg ci --amend -m 'back to default'
225 231 saved backup bundle to $TESTTMP/.hg/strip-backup/1661ca36a2db-amend-backup.hg (glob)
226 232 $ hg branches
227 233 default 2:f24ee5961967
228 234
229 235 Close branch:
230 236
231 237 $ hg up -q 0
232 238 $ echo b >> b
233 239 $ hg branch foo
234 240 marked working directory as branch foo
235 241 (branches are permanent and global, did you want a bookmark?)
236 242 $ hg ci -Am 'fork'
237 243 adding b
238 244 $ echo b >> b
239 245 $ hg ci -mb
240 246 $ hg ci --amend --close-branch -m 'closing branch foo'
241 247 saved backup bundle to $TESTTMP/.hg/strip-backup/c962248fa264-amend-backup.hg (glob)
242 248
243 249 Same thing, different code path:
244 250
245 251 $ echo b >> b
246 252 $ hg ci -m 'reopen branch'
247 253 reopening closed branch head 4
248 254 $ echo b >> b
249 255 $ hg ci --amend --close-branch
250 256 saved backup bundle to $TESTTMP/.hg/strip-backup/5e302dcc12b8-amend-backup.hg (glob)
251 257 $ hg branches
252 258 default 2:f24ee5961967
253 259
254 260 Refuse to amend merges:
255 261
256 262 $ hg up -q default
257 263 $ hg merge foo
258 264 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
259 265 (branch merge, don't forget to commit)
260 266 $ hg ci --amend
261 267 abort: cannot amend while merging
262 268 [255]
263 269 $ hg ci -m 'merge'
264 270 $ hg ci --amend
265 271 abort: cannot amend merge changesets
266 272 [255]
267 273
268 274 Follow copies/renames:
269 275
270 276 $ hg mv b c
271 277 $ hg ci -m 'b -> c'
272 278 $ hg mv c d
273 279 $ hg ci --amend -m 'b -> d'
274 280 saved backup bundle to $TESTTMP/.hg/strip-backup/9c207120aa98-amend-backup.hg (glob)
275 281 $ hg st --rev '.^' --copies d
276 282 A d
277 283 b
278 284 $ hg cp d e
279 285 $ hg ci -m 'e = d'
280 286 $ hg cp e f
281 287 $ hg ci --amend -m 'f = d'
282 288 saved backup bundle to $TESTTMP/.hg/strip-backup/fda2b3b27b22-amend-backup.hg (glob)
283 289 $ hg st --rev '.^' --copies f
284 290 A f
285 291 d
286 292
287 293 $ mv f f.orig
288 294 $ hg rm -A f
289 295 $ hg ci -m removef
290 296 $ hg cp a f
291 297 $ mv f.orig f
292 298 $ hg ci --amend -m replacef
293 299 saved backup bundle to $TESTTMP/.hg/strip-backup/20a7413547f9-amend-backup.hg (glob)
294 300 $ hg st --change . --copies
295 301 $ hg log -r . --template "{file_copies}\n"
296 302
297 303
298 304 Move added file (issue3410):
299 305
300 306 $ echo g >> g
301 307 $ hg ci -Am g
302 308 adding g
303 309 $ hg mv g h
304 310 $ hg ci --amend
305 311 saved backup bundle to $TESTTMP/.hg/strip-backup/5daa77a5d616-amend-backup.hg (glob)
306 312 $ hg st --change . --copies h
307 313 A h
308 314 $ hg log -r . --template "{file_copies}\n"
309 315
310 316
311 317 Can't rollback an amend:
312 318
313 319 $ hg rollback
314 320 no rollback information available
315 321 [1]
316 322
317 323 Preserve extra dict (issue3430):
318 324
319 325 $ hg branch a
320 326 marked working directory as branch a
321 327 (branches are permanent and global, did you want a bookmark?)
322 328 $ echo a >> a
323 329 $ hg ci -ma
324 330 $ hg ci --amend -m "a'"
325 331 saved backup bundle to $TESTTMP/.hg/strip-backup/167f8e3031df-amend-backup.hg (glob)
326 332 $ hg log -r . --template "{branch}\n"
327 333 a
328 334 $ hg ci --amend -m "a''"
329 335 saved backup bundle to $TESTTMP/.hg/strip-backup/ceac1a44c806-amend-backup.hg (glob)
330 336 $ hg log -r . --template "{branch}\n"
331 337 a
332 338
333 339 Also preserve other entries in the dict that are in the old commit,
334 340 first graft something so there's an additional entry:
335 341
336 342 $ hg up 0 -q
337 343 $ echo z > z
338 344 $ hg ci -Am 'fork'
339 345 adding z
340 346 created new head
341 347 $ hg up 11
342 348 5 files updated, 0 files merged, 1 files removed, 0 files unresolved
343 349 $ hg graft 12
344 350 grafting revision 12
345 351 $ hg ci --amend -m 'graft amend'
346 352 saved backup bundle to $TESTTMP/.hg/strip-backup/18a5124daf7a-amend-backup.hg (glob)
347 353 $ hg log -r . --debug | grep extra
348 354 extra: branch=a
349 355 extra: source=2647734878ef0236dda712fae9c1651cf694ea8a
@@ -1,307 +1,360 b''
1 1 Create a repo with some stuff in it:
2 2
3 3 $ hg init a
4 4 $ cd a
5 5 $ echo a > a
6 6 $ echo a > d
7 7 $ echo a > e
8 8 $ hg ci -qAm0
9 9 $ echo b > a
10 10 $ hg ci -m1 -u bar
11 11 $ hg mv a b
12 12 $ hg ci -m2
13 13 $ hg cp b c
14 14 $ hg ci -m3 -u baz
15 15 $ echo b > d
16 16 $ echo f > e
17 17 $ hg ci -m4
18 18 $ hg up -q 3
19 19 $ echo b > e
20 20 $ hg branch -q stable
21 21 $ hg ci -m5
22 22 $ hg merge -q default --tool internal:local
23 23 $ hg branch -q default
24 24 $ hg ci -m6
25 25 $ hg phase --public 3
26 26 $ hg phase --force --secret 6
27 27
28 28 $ hg --config extensions.graphlog= log -G --template '{author}@{rev}.{phase}: {desc}\n'
29 29 @ test@6.secret: 6
30 30 |\
31 31 | o test@5.draft: 5
32 32 | |
33 33 o | test@4.draft: 4
34 34 |/
35 35 o baz@3.public: 3
36 36 |
37 37 o test@2.public: 2
38 38 |
39 39 o bar@1.public: 1
40 40 |
41 41 o test@0.public: 0
42 42
43 43
44 44 Need to specify a rev:
45 45
46 46 $ hg graft
47 47 abort: no revisions specified
48 48 [255]
49 49
50 50 Can't graft ancestor:
51 51
52 52 $ hg graft 1 2
53 53 skipping ancestor revision 1
54 54 skipping ancestor revision 2
55 55 [255]
56 56
57 57 Specify revisions with -r:
58 58
59 59 $ hg graft -r 1 -r 2
60 60 skipping ancestor revision 1
61 61 skipping ancestor revision 2
62 62 [255]
63 63
64 64 $ hg graft -r 1 2
65 65 skipping ancestor revision 2
66 66 skipping ancestor revision 1
67 67 [255]
68 68
69 69 Can't graft with dirty wd:
70 70
71 71 $ hg up -q 0
72 72 $ echo foo > a
73 73 $ hg graft 1
74 74 abort: outstanding uncommitted changes
75 75 [255]
76 76 $ hg revert a
77 77
78 78 Graft a rename:
79 79
80 80 $ hg graft 2 -u foo
81 81 grafting revision 2
82 82 merging a and b to b
83 83 $ hg export tip --git
84 84 # HG changeset patch
85 85 # User foo
86 86 # Date 0 0
87 87 # Node ID ef0ef43d49e79e81ddafdc7997401ba0041efc82
88 88 # Parent 68795b066622ca79a25816a662041d8f78f3cd9e
89 89 2
90 90
91 91 diff --git a/a b/b
92 92 rename from a
93 93 rename to b
94 94
95 95 Look for extra:source
96 96
97 97 $ hg log --debug -r tip
98 98 changeset: 7:ef0ef43d49e79e81ddafdc7997401ba0041efc82
99 99 tag: tip
100 100 phase: draft
101 101 parent: 0:68795b066622ca79a25816a662041d8f78f3cd9e
102 102 parent: -1:0000000000000000000000000000000000000000
103 103 manifest: 7:e59b6b228f9cbf9903d5e9abf996e083a1f533eb
104 104 user: foo
105 105 date: Thu Jan 01 00:00:00 1970 +0000
106 106 files+: b
107 107 files-: a
108 108 extra: branch=default
109 109 extra: source=5c095ad7e90f871700f02dd1fa5012cb4498a2d4
110 110 description:
111 111 2
112 112
113 113
114 114
115 115 Graft out of order, skipping a merge and a duplicate
116 116
117 117 $ hg graft 1 5 4 3 'merge()' 2 -n
118 118 skipping ungraftable merge revision 6
119 119 skipping already grafted revision 2
120 120 grafting revision 1
121 121 grafting revision 5
122 122 grafting revision 4
123 123 grafting revision 3
124 124
125 125 $ hg graft 1 5 4 3 'merge()' 2 --debug
126 126 skipping ungraftable merge revision 6
127 127 scanning for duplicate grafts
128 128 skipping already grafted revision 2
129 129 grafting revision 1
130 130 searching for copies back to rev 1
131 131 unmatched files in local:
132 132 b
133 133 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
134 134 b -> a *
135 135 checking for directory renames
136 136 resolving manifests
137 137 overwrite: False, partial: False
138 138 ancestor: 68795b066622, local: ef0ef43d49e7+, remote: 5d205f8b35b6
139 139 b: local copied/moved to a -> m
140 140 preserving b for resolve of b
141 141 updating: b 1/1 files (100.00%)
142 142 picked tool 'internal:merge' for b (binary False symlink False)
143 143 merging b and a to b
144 144 my b@ef0ef43d49e7+ other a@5d205f8b35b6 ancestor a@68795b066622
145 145 premerge successful
146 146 b
147 147 grafting revision 5
148 148 searching for copies back to rev 1
149 149 resolving manifests
150 150 overwrite: False, partial: False
151 151 ancestor: 4c60f11aa304, local: 6b9e5368ca4e+, remote: 97f8bfe72746
152 152 e: remote is newer -> g
153 153 updating: e 1/1 files (100.00%)
154 154 getting e
155 155 e
156 156 grafting revision 4
157 157 searching for copies back to rev 1
158 158 resolving manifests
159 159 overwrite: False, partial: False
160 160 ancestor: 4c60f11aa304, local: 1905859650ec+, remote: 9c233e8e184d
161 161 e: versions differ -> m
162 162 d: remote is newer -> g
163 163 preserving e for resolve of e
164 164 updating: d 1/2 files (50.00%)
165 165 getting d
166 166 updating: e 2/2 files (100.00%)
167 167 picked tool 'internal:merge' for e (binary False symlink False)
168 168 merging e
169 169 my e@1905859650ec+ other e@9c233e8e184d ancestor e@68795b066622
170 170 warning: conflicts during merge.
171 171 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
172 172 abort: unresolved conflicts, can't continue
173 173 (use hg resolve and hg graft --continue)
174 174 [255]
175 175
176 176 Continue without resolve should fail:
177 177
178 178 $ hg graft -c
179 179 grafting revision 4
180 180 abort: unresolved merge conflicts (see hg help resolve)
181 181 [255]
182 182
183 183 Fix up:
184 184
185 185 $ echo b > e
186 186 $ hg resolve -m e
187 187
188 188 Continue with a revision should fail:
189 189
190 190 $ hg graft -c 6
191 191 abort: can't specify --continue and revisions
192 192 [255]
193 193
194 194 $ hg graft -c -r 6
195 195 abort: can't specify --continue and revisions
196 196 [255]
197 197
198 198 Continue for real, clobber usernames
199 199
200 200 $ hg graft -c -U
201 201 grafting revision 4
202 202 grafting revision 3
203 203
204 204 Compare with original:
205 205
206 206 $ hg diff -r 6
207 207 $ hg status --rev 0:. -C
208 208 M d
209 209 M e
210 210 A b
211 211 a
212 212 A c
213 213 a
214 214 R a
215 215
216 216 View graph:
217 217
218 218 $ hg --config extensions.graphlog= log -G --template '{author}@{rev}.{phase}: {desc}\n'
219 219 @ test@11.draft: 3
220 220 |
221 221 o test@10.draft: 4
222 222 |
223 223 o test@9.draft: 5
224 224 |
225 225 o bar@8.draft: 1
226 226 |
227 227 o foo@7.draft: 2
228 228 |
229 229 | o test@6.secret: 6
230 230 | |\
231 231 | | o test@5.draft: 5
232 232 | | |
233 233 | o | test@4.draft: 4
234 234 | |/
235 235 | o baz@3.public: 3
236 236 | |
237 237 | o test@2.public: 2
238 238 | |
239 239 | o bar@1.public: 1
240 240 |/
241 241 o test@0.public: 0
242 242
243 243 Graft again onto another branch should preserve the original source
244 244 $ hg up -q 0
245 245 $ echo 'g'>g
246 246 $ hg add g
247 247 $ hg ci -m 7
248 248 created new head
249 249 $ hg graft 7
250 250 grafting revision 7
251 251
252 252 $ hg log -r 7 --template '{rev}:{node}\n'
253 253 7:ef0ef43d49e79e81ddafdc7997401ba0041efc82
254 254 $ hg log -r 2 --template '{rev}:{node}\n'
255 255 2:5c095ad7e90f871700f02dd1fa5012cb4498a2d4
256 256
257 257 $ hg log --debug -r tip
258 258 changeset: 13:9db0f28fd3747e92c57d015f53b5593aeec53c2d
259 259 tag: tip
260 260 phase: draft
261 261 parent: 12:b592ea63bb0c19a6c5c44685ee29a2284f9f1b8f
262 262 parent: -1:0000000000000000000000000000000000000000
263 263 manifest: 13:dc313617b8c32457c0d589e0dbbedfe71f3cd637
264 264 user: foo
265 265 date: Thu Jan 01 00:00:00 1970 +0000
266 266 files+: b
267 267 files-: a
268 268 extra: branch=default
269 269 extra: source=5c095ad7e90f871700f02dd1fa5012cb4498a2d4
270 270 description:
271 271 2
272 272
273 273
274 274 Disallow grafting an already grafted cset onto its original branch
275 275 $ hg up -q 6
276 276 $ hg graft 7
277 277 skipping already grafted revision 7 (was grafted from 2)
278 278 [255]
279 279
280 280 Disallow grafting already grafted csets with the same origin onto each other
281 281 $ hg up -q 13
282 282 $ hg graft 2
283 283 skipping already grafted revision 2
284 284 [255]
285 285 $ hg graft 7
286 286 skipping already grafted revision 7 (same origin 2)
287 287 [255]
288 288
289 289 $ hg up -q 7
290 290 $ hg graft 2
291 291 skipping already grafted revision 2
292 292 [255]
293 293 $ hg graft tip
294 294 skipping already grafted revision 13 (same origin 2)
295 295 [255]
296 296
297 297 Graft with --log
298 298
299 299 $ hg up -Cq 1
300 300 $ hg graft 3 --log -u foo
301 301 grafting revision 3
302 302 warning: can't find ancestor for 'c' copied from 'b'!
303 303 $ hg log --template '{rev} {parents} {desc}\n' -r tip
304 304 14 1:5d205f8b35b6 3
305 305 (grafted from 4c60f11aa304a54ae1c199feb94e7fc771e51ed8)
306 306
307 $ cd ..
307 Resolve conflicted graft
308 $ hg up -q 0
309 $ echo b > a
310 $ hg ci -m 8
311 created new head
312 $ echo a > a
313 $ hg ci -m 9
314 $ hg graft 1 --tool internal:fail
315 grafting revision 1
316 abort: unresolved conflicts, can't continue
317 (use hg resolve and hg graft --continue)
318 [255]
319 $ hg resolve --all
320 merging a
321 $ hg graft -c
322 grafting revision 1
323 $ hg export tip --git
324 # HG changeset patch
325 # User bar
326 # Date 0 0
327 # Node ID 64ecd9071ce83c6e62f538d8ce7709d53f32ebf7
328 # Parent 4bdb9a9d0b84ffee1d30f0dfc7744cade17aa19c
329 1
330
331 diff --git a/a b/a
332 --- a/a
333 +++ b/a
334 @@ -1,1 +1,1 @@
335 -a
336 +b
337
338 Resolve conflicted graft with rename
339 $ echo c > a
340 $ hg ci -m 10
341 $ hg graft 2 --tool internal:fail
342 grafting revision 2
343 abort: unresolved conflicts, can't continue
344 (use hg resolve and hg graft --continue)
345 [255]
346 $ hg resolve --all
347 merging a and b to b
348 $ hg graft -c
349 grafting revision 2
350 $ hg export tip --git
351 # HG changeset patch
352 # User test
353 # Date 0 0
354 # Node ID 2e80e1351d6ed50302fe1e05f8bd1d4d412b6e11
355 # Parent e5a51ae854a8bbaaf25cc5c6a57ff46042dadbb4
356 2
357
358 diff --git a/a b/b
359 rename from a
360 rename to b
@@ -1,90 +1,113 b''
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > graphlog=
4 4 > rebase=
5 5 >
6 6 > [phases]
7 7 > publish=False
8 8 >
9 9 > [alias]
10 10 > tglog = log -G --template "{rev}: '{desc}' bookmarks: {bookmarks}\n"
11 11 > EOF
12 12
13 13 Create a repo with several bookmarks
14 14 $ hg init a
15 15 $ cd a
16 16
17 17 $ echo a > a
18 18 $ hg ci -Am A
19 19 adding a
20 20
21 21 $ echo b > b
22 22 $ hg ci -Am B
23 23 adding b
24 24 $ hg book 'X'
25 25 $ hg book 'Y'
26 26
27 27 $ echo c > c
28 28 $ hg ci -Am C
29 29 adding c
30 30 $ hg book 'Z'
31 31
32 32 $ hg up -q 0
33 33
34 34 $ echo d > d
35 35 $ hg ci -Am D
36 36 adding d
37 37 created new head
38 38
39 $ hg book W
40
39 41 $ hg tglog
40 @ 3: 'D' bookmarks:
42 @ 3: 'D' bookmarks: W
41 43 |
42 44 | o 2: 'C' bookmarks: Y Z
43 45 | |
44 46 | o 1: 'B' bookmarks: X
45 47 |/
46 48 o 0: 'A' bookmarks:
47 49
48 50
49 51 Move only rebased bookmarks
50 52
51 53 $ cd ..
52 54 $ hg clone -q a a1
53 55
54 56 $ cd a1
55 57 $ hg up -q Z
56 58
57 59 $ hg rebase -s Y -d 3
58 60 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
59 61
60 62 $ hg tglog
61 63 @ 3: 'C' bookmarks: Y Z
62 64 |
63 o 2: 'D' bookmarks:
65 o 2: 'D' bookmarks: W
64 66 |
65 67 | o 1: 'B' bookmarks: X
66 68 |/
67 69 o 0: 'A' bookmarks:
68 70
69 71 Keep bookmarks to the correct rebased changeset
70 72
71 73 $ cd ..
72 74 $ hg clone -q a a2
73 75
74 76 $ cd a2
75 77 $ hg up -q Z
76 78
77 79 $ hg rebase -s 1 -d 3
78 80 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
79 81
80 82 $ hg tglog
81 83 @ 3: 'C' bookmarks: Y Z
82 84 |
83 85 o 2: 'B' bookmarks: X
84 86 |
85 o 1: 'D' bookmarks:
87 o 1: 'D' bookmarks: W
88 |
89 o 0: 'A' bookmarks:
90
91
92 Keep active bookmark on the correct changeset
93
94 $ cd ..
95 $ hg clone -q a a3
96
97 $ cd a3
98 $ hg up -q X
99
100 $ hg rebase -d W
101 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
102
103 $ hg tglog
104 @ 3: 'C' bookmarks: Y Z
105 |
106 o 2: 'B' bookmarks: X
107 |
108 o 1: 'D' bookmarks: W
86 109 |
87 110 o 0: 'A' bookmarks:
88 111
89 112
90 113 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now