##// END OF EJS Templates
patch: break import cycle with cmdutil...
Martin Geisler -
r12266:00658492 default
parent child Browse files
Show More
@@ -1,3119 +1,3119 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 You will by default be managing a patch queue named "patches". You can
42 42 create other, independent patch queues with the :hg:`qqueue` command.
43 43 '''
44 44
45 45 from mercurial.i18n import _
46 46 from mercurial.node import bin, hex, short, nullid, nullrev
47 47 from mercurial.lock import release
48 48 from mercurial import commands, cmdutil, hg, patch, util
49 49 from mercurial import repair, extensions, url, error
50 50 import os, sys, re, errno, shutil
51 51
52 52 commands.norepo += " qclone"
53 53
54 54 # Patch names looks like unix-file names.
55 55 # They must be joinable with queue directory and result in the patch path.
56 56 normname = util.normpath
57 57
58 58 class statusentry(object):
59 59 def __init__(self, node, name):
60 60 self.node, self.name = node, name
61 61 def __repr__(self):
62 62 return hex(self.node) + ':' + self.name
63 63
64 64 class patchheader(object):
65 65 def __init__(self, pf, plainmode=False):
66 66 def eatdiff(lines):
67 67 while lines:
68 68 l = lines[-1]
69 69 if (l.startswith("diff -") or
70 70 l.startswith("Index:") or
71 71 l.startswith("===========")):
72 72 del lines[-1]
73 73 else:
74 74 break
75 75 def eatempty(lines):
76 76 while lines:
77 77 if not lines[-1].strip():
78 78 del lines[-1]
79 79 else:
80 80 break
81 81
82 82 message = []
83 83 comments = []
84 84 user = None
85 85 date = None
86 86 parent = None
87 87 format = None
88 88 subject = None
89 89 diffstart = 0
90 90
91 91 for line in file(pf):
92 92 line = line.rstrip()
93 93 if (line.startswith('diff --git')
94 94 or (diffstart and line.startswith('+++ '))):
95 95 diffstart = 2
96 96 break
97 97 diffstart = 0 # reset
98 98 if line.startswith("--- "):
99 99 diffstart = 1
100 100 continue
101 101 elif format == "hgpatch":
102 102 # parse values when importing the result of an hg export
103 103 if line.startswith("# User "):
104 104 user = line[7:]
105 105 elif line.startswith("# Date "):
106 106 date = line[7:]
107 107 elif line.startswith("# Parent "):
108 108 parent = line[9:]
109 109 elif not line.startswith("# ") and line:
110 110 message.append(line)
111 111 format = None
112 112 elif line == '# HG changeset patch':
113 113 message = []
114 114 format = "hgpatch"
115 115 elif (format != "tagdone" and (line.startswith("Subject: ") or
116 116 line.startswith("subject: "))):
117 117 subject = line[9:]
118 118 format = "tag"
119 119 elif (format != "tagdone" and (line.startswith("From: ") or
120 120 line.startswith("from: "))):
121 121 user = line[6:]
122 122 format = "tag"
123 123 elif (format != "tagdone" and (line.startswith("Date: ") or
124 124 line.startswith("date: "))):
125 125 date = line[6:]
126 126 format = "tag"
127 127 elif format == "tag" and line == "":
128 128 # when looking for tags (subject: from: etc) they
129 129 # end once you find a blank line in the source
130 130 format = "tagdone"
131 131 elif message or line:
132 132 message.append(line)
133 133 comments.append(line)
134 134
135 135 eatdiff(message)
136 136 eatdiff(comments)
137 137 eatempty(message)
138 138 eatempty(comments)
139 139
140 140 # make sure message isn't empty
141 141 if format and format.startswith("tag") and subject:
142 142 message.insert(0, "")
143 143 message.insert(0, subject)
144 144
145 145 self.message = message
146 146 self.comments = comments
147 147 self.user = user
148 148 self.date = date
149 149 self.parent = parent
150 150 self.haspatch = diffstart > 1
151 151 self.plainmode = plainmode
152 152
153 153 def setuser(self, user):
154 154 if not self.updateheader(['From: ', '# User '], user):
155 155 try:
156 156 patchheaderat = self.comments.index('# HG changeset patch')
157 157 self.comments.insert(patchheaderat + 1, '# User ' + user)
158 158 except ValueError:
159 159 if self.plainmode or self._hasheader(['Date: ']):
160 160 self.comments = ['From: ' + user] + self.comments
161 161 else:
162 162 tmp = ['# HG changeset patch', '# User ' + user, '']
163 163 self.comments = tmp + self.comments
164 164 self.user = user
165 165
166 166 def setdate(self, date):
167 167 if not self.updateheader(['Date: ', '# Date '], date):
168 168 try:
169 169 patchheaderat = self.comments.index('# HG changeset patch')
170 170 self.comments.insert(patchheaderat + 1, '# Date ' + date)
171 171 except ValueError:
172 172 if self.plainmode or self._hasheader(['From: ']):
173 173 self.comments = ['Date: ' + date] + self.comments
174 174 else:
175 175 tmp = ['# HG changeset patch', '# Date ' + date, '']
176 176 self.comments = tmp + self.comments
177 177 self.date = date
178 178
179 179 def setparent(self, parent):
180 180 if not self.updateheader(['# Parent '], parent):
181 181 try:
182 182 patchheaderat = self.comments.index('# HG changeset patch')
183 183 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
184 184 except ValueError:
185 185 pass
186 186 self.parent = parent
187 187
188 188 def setmessage(self, message):
189 189 if self.comments:
190 190 self._delmsg()
191 191 self.message = [message]
192 192 self.comments += self.message
193 193
194 194 def updateheader(self, prefixes, new):
195 195 '''Update all references to a field in the patch header.
196 196 Return whether the field is present.'''
197 197 res = False
198 198 for prefix in prefixes:
199 199 for i in xrange(len(self.comments)):
200 200 if self.comments[i].startswith(prefix):
201 201 self.comments[i] = prefix + new
202 202 res = True
203 203 break
204 204 return res
205 205
206 206 def _hasheader(self, prefixes):
207 207 '''Check if a header starts with any of the given prefixes.'''
208 208 for prefix in prefixes:
209 209 for comment in self.comments:
210 210 if comment.startswith(prefix):
211 211 return True
212 212 return False
213 213
214 214 def __str__(self):
215 215 if not self.comments:
216 216 return ''
217 217 return '\n'.join(self.comments) + '\n\n'
218 218
219 219 def _delmsg(self):
220 220 '''Remove existing message, keeping the rest of the comments fields.
221 221 If comments contains 'subject: ', message will prepend
222 222 the field and a blank line.'''
223 223 if self.message:
224 224 subj = 'subject: ' + self.message[0].lower()
225 225 for i in xrange(len(self.comments)):
226 226 if subj == self.comments[i].lower():
227 227 del self.comments[i]
228 228 self.message = self.message[2:]
229 229 break
230 230 ci = 0
231 231 for mi in self.message:
232 232 while mi != self.comments[ci]:
233 233 ci += 1
234 234 del self.comments[ci]
235 235
236 236 class queue(object):
237 237 def __init__(self, ui, path, patchdir=None):
238 238 self.basepath = path
239 239 try:
240 240 fh = open(os.path.join(path, 'patches.queue'))
241 241 cur = fh.read().rstrip()
242 242 if not cur:
243 243 curpath = os.path.join(path, 'patches')
244 244 else:
245 245 curpath = os.path.join(path, 'patches-' + cur)
246 246 except IOError:
247 247 curpath = os.path.join(path, 'patches')
248 248 self.path = patchdir or curpath
249 249 self.opener = util.opener(self.path)
250 250 self.ui = ui
251 251 self.applied_dirty = 0
252 252 self.series_dirty = 0
253 253 self.added = []
254 254 self.series_path = "series"
255 255 self.status_path = "status"
256 256 self.guards_path = "guards"
257 257 self.active_guards = None
258 258 self.guards_dirty = False
259 259 # Handle mq.git as a bool with extended values
260 260 try:
261 261 gitmode = ui.configbool('mq', 'git', None)
262 262 if gitmode is None:
263 263 raise error.ConfigError()
264 264 self.gitmode = gitmode and 'yes' or 'no'
265 265 except error.ConfigError:
266 266 self.gitmode = ui.config('mq', 'git', 'auto').lower()
267 267 self.plainmode = ui.configbool('mq', 'plain', False)
268 268
269 269 @util.propertycache
270 270 def applied(self):
271 271 if os.path.exists(self.join(self.status_path)):
272 272 def parse(l):
273 273 n, name = l.split(':', 1)
274 274 return statusentry(bin(n), name)
275 275 lines = self.opener(self.status_path).read().splitlines()
276 276 return [parse(l) for l in lines]
277 277 return []
278 278
279 279 @util.propertycache
280 280 def full_series(self):
281 281 if os.path.exists(self.join(self.series_path)):
282 282 return self.opener(self.series_path).read().splitlines()
283 283 return []
284 284
285 285 @util.propertycache
286 286 def series(self):
287 287 self.parse_series()
288 288 return self.series
289 289
290 290 @util.propertycache
291 291 def series_guards(self):
292 292 self.parse_series()
293 293 return self.series_guards
294 294
295 295 def invalidate(self):
296 296 for a in 'applied full_series series series_guards'.split():
297 297 if a in self.__dict__:
298 298 delattr(self, a)
299 299 self.applied_dirty = 0
300 300 self.series_dirty = 0
301 301 self.guards_dirty = False
302 302 self.active_guards = None
303 303
304 304 def diffopts(self, opts={}, patchfn=None):
305 305 diffopts = patch.diffopts(self.ui, opts)
306 306 if self.gitmode == 'auto':
307 307 diffopts.upgrade = True
308 308 elif self.gitmode == 'keep':
309 309 pass
310 310 elif self.gitmode in ('yes', 'no'):
311 311 diffopts.git = self.gitmode == 'yes'
312 312 else:
313 313 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
314 314 ' got %s') % self.gitmode)
315 315 if patchfn:
316 316 diffopts = self.patchopts(diffopts, patchfn)
317 317 return diffopts
318 318
319 319 def patchopts(self, diffopts, *patches):
320 320 """Return a copy of input diff options with git set to true if
321 321 referenced patch is a git patch and should be preserved as such.
322 322 """
323 323 diffopts = diffopts.copy()
324 324 if not diffopts.git and self.gitmode == 'keep':
325 325 for patchfn in patches:
326 326 patchf = self.opener(patchfn, 'r')
327 327 # if the patch was a git patch, refresh it as a git patch
328 328 for line in patchf:
329 329 if line.startswith('diff --git'):
330 330 diffopts.git = True
331 331 break
332 332 patchf.close()
333 333 return diffopts
334 334
335 335 def join(self, *p):
336 336 return os.path.join(self.path, *p)
337 337
338 338 def find_series(self, patch):
339 339 def matchpatch(l):
340 340 l = l.split('#', 1)[0]
341 341 return l.strip() == patch
342 342 for index, l in enumerate(self.full_series):
343 343 if matchpatch(l):
344 344 return index
345 345 return None
346 346
347 347 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
348 348
349 349 def parse_series(self):
350 350 self.series = []
351 351 self.series_guards = []
352 352 for l in self.full_series:
353 353 h = l.find('#')
354 354 if h == -1:
355 355 patch = l
356 356 comment = ''
357 357 elif h == 0:
358 358 continue
359 359 else:
360 360 patch = l[:h]
361 361 comment = l[h:]
362 362 patch = patch.strip()
363 363 if patch:
364 364 if patch in self.series:
365 365 raise util.Abort(_('%s appears more than once in %s') %
366 366 (patch, self.join(self.series_path)))
367 367 self.series.append(patch)
368 368 self.series_guards.append(self.guard_re.findall(comment))
369 369
370 370 def check_guard(self, guard):
371 371 if not guard:
372 372 return _('guard cannot be an empty string')
373 373 bad_chars = '# \t\r\n\f'
374 374 first = guard[0]
375 375 if first in '-+':
376 376 return (_('guard %r starts with invalid character: %r') %
377 377 (guard, first))
378 378 for c in bad_chars:
379 379 if c in guard:
380 380 return _('invalid character in guard %r: %r') % (guard, c)
381 381
382 382 def set_active(self, guards):
383 383 for guard in guards:
384 384 bad = self.check_guard(guard)
385 385 if bad:
386 386 raise util.Abort(bad)
387 387 guards = sorted(set(guards))
388 388 self.ui.debug('active guards: %s\n' % ' '.join(guards))
389 389 self.active_guards = guards
390 390 self.guards_dirty = True
391 391
392 392 def active(self):
393 393 if self.active_guards is None:
394 394 self.active_guards = []
395 395 try:
396 396 guards = self.opener(self.guards_path).read().split()
397 397 except IOError, err:
398 398 if err.errno != errno.ENOENT:
399 399 raise
400 400 guards = []
401 401 for i, guard in enumerate(guards):
402 402 bad = self.check_guard(guard)
403 403 if bad:
404 404 self.ui.warn('%s:%d: %s\n' %
405 405 (self.join(self.guards_path), i + 1, bad))
406 406 else:
407 407 self.active_guards.append(guard)
408 408 return self.active_guards
409 409
410 410 def set_guards(self, idx, guards):
411 411 for g in guards:
412 412 if len(g) < 2:
413 413 raise util.Abort(_('guard %r too short') % g)
414 414 if g[0] not in '-+':
415 415 raise util.Abort(_('guard %r starts with invalid char') % g)
416 416 bad = self.check_guard(g[1:])
417 417 if bad:
418 418 raise util.Abort(bad)
419 419 drop = self.guard_re.sub('', self.full_series[idx])
420 420 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
421 421 self.parse_series()
422 422 self.series_dirty = True
423 423
424 424 def pushable(self, idx):
425 425 if isinstance(idx, str):
426 426 idx = self.series.index(idx)
427 427 patchguards = self.series_guards[idx]
428 428 if not patchguards:
429 429 return True, None
430 430 guards = self.active()
431 431 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
432 432 if exactneg:
433 433 return False, exactneg[0]
434 434 pos = [g for g in patchguards if g[0] == '+']
435 435 exactpos = [g for g in pos if g[1:] in guards]
436 436 if pos:
437 437 if exactpos:
438 438 return True, exactpos[0]
439 439 return False, pos
440 440 return True, ''
441 441
442 442 def explain_pushable(self, idx, all_patches=False):
443 443 write = all_patches and self.ui.write or self.ui.warn
444 444 if all_patches or self.ui.verbose:
445 445 if isinstance(idx, str):
446 446 idx = self.series.index(idx)
447 447 pushable, why = self.pushable(idx)
448 448 if all_patches and pushable:
449 449 if why is None:
450 450 write(_('allowing %s - no guards in effect\n') %
451 451 self.series[idx])
452 452 else:
453 453 if not why:
454 454 write(_('allowing %s - no matching negative guards\n') %
455 455 self.series[idx])
456 456 else:
457 457 write(_('allowing %s - guarded by %r\n') %
458 458 (self.series[idx], why))
459 459 if not pushable:
460 460 if why:
461 461 write(_('skipping %s - guarded by %r\n') %
462 462 (self.series[idx], why))
463 463 else:
464 464 write(_('skipping %s - no matching guards\n') %
465 465 self.series[idx])
466 466
467 467 def save_dirty(self):
468 468 def write_list(items, path):
469 469 fp = self.opener(path, 'w')
470 470 for i in items:
471 471 fp.write("%s\n" % i)
472 472 fp.close()
473 473 if self.applied_dirty:
474 474 write_list(map(str, self.applied), self.status_path)
475 475 if self.series_dirty:
476 476 write_list(self.full_series, self.series_path)
477 477 if self.guards_dirty:
478 478 write_list(self.active_guards, self.guards_path)
479 479 if self.added:
480 480 qrepo = self.qrepo()
481 481 if qrepo:
482 482 qrepo[None].add(self.added)
483 483 self.added = []
484 484
485 485 def removeundo(self, repo):
486 486 undo = repo.sjoin('undo')
487 487 if not os.path.exists(undo):
488 488 return
489 489 try:
490 490 os.unlink(undo)
491 491 except OSError, inst:
492 492 self.ui.warn(_('error removing undo: %s\n') % str(inst))
493 493
494 494 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
495 495 fp=None, changes=None, opts={}):
496 496 stat = opts.get('stat')
497 497 m = cmdutil.match(repo, files, opts)
498 498 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
499 499 changes, stat, fp)
500 500
501 501 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
502 502 # first try just applying the patch
503 503 (err, n) = self.apply(repo, [patch], update_status=False,
504 504 strict=True, merge=rev)
505 505
506 506 if err == 0:
507 507 return (err, n)
508 508
509 509 if n is None:
510 510 raise util.Abort(_("apply failed for patch %s") % patch)
511 511
512 512 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
513 513
514 514 # apply failed, strip away that rev and merge.
515 515 hg.clean(repo, head)
516 516 self.strip(repo, [n], update=False, backup='strip')
517 517
518 518 ctx = repo[rev]
519 519 ret = hg.merge(repo, rev)
520 520 if ret:
521 521 raise util.Abort(_("update returned %d") % ret)
522 522 n = repo.commit(ctx.description(), ctx.user(), force=True)
523 523 if n is None:
524 524 raise util.Abort(_("repo commit failed"))
525 525 try:
526 526 ph = patchheader(mergeq.join(patch), self.plainmode)
527 527 except:
528 528 raise util.Abort(_("unable to read %s") % patch)
529 529
530 530 diffopts = self.patchopts(diffopts, patch)
531 531 patchf = self.opener(patch, "w")
532 532 comments = str(ph)
533 533 if comments:
534 534 patchf.write(comments)
535 535 self.printdiff(repo, diffopts, head, n, fp=patchf)
536 536 patchf.close()
537 537 self.removeundo(repo)
538 538 return (0, n)
539 539
540 540 def qparents(self, repo, rev=None):
541 541 if rev is None:
542 542 (p1, p2) = repo.dirstate.parents()
543 543 if p2 == nullid:
544 544 return p1
545 545 if not self.applied:
546 546 return None
547 547 return self.applied[-1].node
548 548 p1, p2 = repo.changelog.parents(rev)
549 549 if p2 != nullid and p2 in [x.node for x in self.applied]:
550 550 return p2
551 551 return p1
552 552
553 553 def mergepatch(self, repo, mergeq, series, diffopts):
554 554 if not self.applied:
555 555 # each of the patches merged in will have two parents. This
556 556 # can confuse the qrefresh, qdiff, and strip code because it
557 557 # needs to know which parent is actually in the patch queue.
558 558 # so, we insert a merge marker with only one parent. This way
559 559 # the first patch in the queue is never a merge patch
560 560 #
561 561 pname = ".hg.patches.merge.marker"
562 562 n = repo.commit('[mq]: merge marker', force=True)
563 563 self.removeundo(repo)
564 564 self.applied.append(statusentry(n, pname))
565 565 self.applied_dirty = 1
566 566
567 567 head = self.qparents(repo)
568 568
569 569 for patch in series:
570 570 patch = mergeq.lookup(patch, strict=True)
571 571 if not patch:
572 572 self.ui.warn(_("patch %s does not exist\n") % patch)
573 573 return (1, None)
574 574 pushable, reason = self.pushable(patch)
575 575 if not pushable:
576 576 self.explain_pushable(patch, all_patches=True)
577 577 continue
578 578 info = mergeq.isapplied(patch)
579 579 if not info:
580 580 self.ui.warn(_("patch %s is not applied\n") % patch)
581 581 return (1, None)
582 582 rev = info[1]
583 583 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
584 584 if head:
585 585 self.applied.append(statusentry(head, patch))
586 586 self.applied_dirty = 1
587 587 if err:
588 588 return (err, head)
589 589 self.save_dirty()
590 590 return (0, head)
591 591
592 592 def patch(self, repo, patchfile):
593 593 '''Apply patchfile to the working directory.
594 594 patchfile: name of patch file'''
595 595 files = {}
596 596 try:
597 597 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
598 598 files=files, eolmode=None)
599 599 except Exception, inst:
600 600 self.ui.note(str(inst) + '\n')
601 601 if not self.ui.verbose:
602 602 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
603 603 return (False, files, False)
604 604
605 605 return (True, files, fuzz)
606 606
607 607 def apply(self, repo, series, list=False, update_status=True,
608 608 strict=False, patchdir=None, merge=None, all_files=None):
609 609 wlock = lock = tr = None
610 610 try:
611 611 wlock = repo.wlock()
612 612 lock = repo.lock()
613 613 tr = repo.transaction("qpush")
614 614 try:
615 615 ret = self._apply(repo, series, list, update_status,
616 616 strict, patchdir, merge, all_files=all_files)
617 617 tr.close()
618 618 self.save_dirty()
619 619 return ret
620 620 except:
621 621 try:
622 622 tr.abort()
623 623 finally:
624 624 repo.invalidate()
625 625 repo.dirstate.invalidate()
626 626 raise
627 627 finally:
628 628 release(tr, lock, wlock)
629 629 self.removeundo(repo)
630 630
631 631 def _apply(self, repo, series, list=False, update_status=True,
632 632 strict=False, patchdir=None, merge=None, all_files=None):
633 633 '''returns (error, hash)
634 634 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
635 635 # TODO unify with commands.py
636 636 if not patchdir:
637 637 patchdir = self.path
638 638 err = 0
639 639 n = None
640 640 for patchname in series:
641 641 pushable, reason = self.pushable(patchname)
642 642 if not pushable:
643 643 self.explain_pushable(patchname, all_patches=True)
644 644 continue
645 645 self.ui.status(_("applying %s\n") % patchname)
646 646 pf = os.path.join(patchdir, patchname)
647 647
648 648 try:
649 649 ph = patchheader(self.join(patchname), self.plainmode)
650 650 except:
651 651 self.ui.warn(_("unable to read %s\n") % patchname)
652 652 err = 1
653 653 break
654 654
655 655 message = ph.message
656 656 if not message:
657 657 message = "imported patch %s\n" % patchname
658 658 else:
659 659 if list:
660 660 message.append("\nimported patch %s" % patchname)
661 661 message = '\n'.join(message)
662 662
663 663 if ph.haspatch:
664 664 (patcherr, files, fuzz) = self.patch(repo, pf)
665 665 if all_files is not None:
666 666 all_files.update(files)
667 667 patcherr = not patcherr
668 668 else:
669 669 self.ui.warn(_("patch %s is empty\n") % patchname)
670 670 patcherr, files, fuzz = 0, [], 0
671 671
672 672 if merge and files:
673 673 # Mark as removed/merged and update dirstate parent info
674 674 removed = []
675 675 merged = []
676 676 for f in files:
677 677 if os.path.exists(repo.wjoin(f)):
678 678 merged.append(f)
679 679 else:
680 680 removed.append(f)
681 681 for f in removed:
682 682 repo.dirstate.remove(f)
683 683 for f in merged:
684 684 repo.dirstate.merge(f)
685 685 p1, p2 = repo.dirstate.parents()
686 686 repo.dirstate.setparents(p1, merge)
687 687
688 files = patch.updatedir(self.ui, repo, files)
688 files = cmdutil.updatedir(self.ui, repo, files)
689 689 match = cmdutil.matchfiles(repo, files or [])
690 690 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
691 691
692 692 if n is None:
693 693 raise util.Abort(_("repo commit failed"))
694 694
695 695 if update_status:
696 696 self.applied.append(statusentry(n, patchname))
697 697
698 698 if patcherr:
699 699 self.ui.warn(_("patch failed, rejects left in working dir\n"))
700 700 err = 2
701 701 break
702 702
703 703 if fuzz and strict:
704 704 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
705 705 err = 3
706 706 break
707 707 return (err, n)
708 708
709 709 def _cleanup(self, patches, numrevs, keep=False):
710 710 if not keep:
711 711 r = self.qrepo()
712 712 if r:
713 713 r[None].remove(patches, True)
714 714 else:
715 715 for p in patches:
716 716 os.unlink(self.join(p))
717 717
718 718 if numrevs:
719 719 del self.applied[:numrevs]
720 720 self.applied_dirty = 1
721 721
722 722 for i in sorted([self.find_series(p) for p in patches], reverse=True):
723 723 del self.full_series[i]
724 724 self.parse_series()
725 725 self.series_dirty = 1
726 726
727 727 def _revpatches(self, repo, revs):
728 728 firstrev = repo[self.applied[0].node].rev()
729 729 patches = []
730 730 for i, rev in enumerate(revs):
731 731
732 732 if rev < firstrev:
733 733 raise util.Abort(_('revision %d is not managed') % rev)
734 734
735 735 ctx = repo[rev]
736 736 base = self.applied[i].node
737 737 if ctx.node() != base:
738 738 msg = _('cannot delete revision %d above applied patches')
739 739 raise util.Abort(msg % rev)
740 740
741 741 patch = self.applied[i].name
742 742 for fmt in ('[mq]: %s', 'imported patch %s'):
743 743 if ctx.description() == fmt % patch:
744 744 msg = _('patch %s finalized without changeset message\n')
745 745 repo.ui.status(msg % patch)
746 746 break
747 747
748 748 patches.append(patch)
749 749 return patches
750 750
751 751 def finish(self, repo, revs):
752 752 patches = self._revpatches(repo, sorted(revs))
753 753 self._cleanup(patches, len(patches))
754 754
755 755 def delete(self, repo, patches, opts):
756 756 if not patches and not opts.get('rev'):
757 757 raise util.Abort(_('qdelete requires at least one revision or '
758 758 'patch name'))
759 759
760 760 realpatches = []
761 761 for patch in patches:
762 762 patch = self.lookup(patch, strict=True)
763 763 info = self.isapplied(patch)
764 764 if info:
765 765 raise util.Abort(_("cannot delete applied patch %s") % patch)
766 766 if patch not in self.series:
767 767 raise util.Abort(_("patch %s not in series file") % patch)
768 768 realpatches.append(patch)
769 769
770 770 numrevs = 0
771 771 if opts.get('rev'):
772 772 if not self.applied:
773 773 raise util.Abort(_('no patches applied'))
774 774 revs = cmdutil.revrange(repo, opts['rev'])
775 775 if len(revs) > 1 and revs[0] > revs[1]:
776 776 revs.reverse()
777 777 revpatches = self._revpatches(repo, revs)
778 778 realpatches += revpatches
779 779 numrevs = len(revpatches)
780 780
781 781 self._cleanup(realpatches, numrevs, opts.get('keep'))
782 782
783 783 def check_toppatch(self, repo):
784 784 if self.applied:
785 785 top = self.applied[-1].node
786 786 patch = self.applied[-1].name
787 787 pp = repo.dirstate.parents()
788 788 if top not in pp:
789 789 raise util.Abort(_("working directory revision is not qtip"))
790 790 return top, patch
791 791 return None, None
792 792
793 793 def check_localchanges(self, repo, force=False, refresh=True):
794 794 m, a, r, d = repo.status()[:4]
795 795 if (m or a or r or d) and not force:
796 796 if refresh:
797 797 raise util.Abort(_("local changes found, refresh first"))
798 798 else:
799 799 raise util.Abort(_("local changes found"))
800 800 return m, a, r, d
801 801
802 802 _reserved = ('series', 'status', 'guards')
803 803 def check_reserved_name(self, name):
804 804 if (name in self._reserved or name.startswith('.hg')
805 805 or name.startswith('.mq') or '#' in name or ':' in name):
806 806 raise util.Abort(_('"%s" cannot be used as the name of a patch')
807 807 % name)
808 808
809 809 def new(self, repo, patchfn, *pats, **opts):
810 810 """options:
811 811 msg: a string or a no-argument function returning a string
812 812 """
813 813 msg = opts.get('msg')
814 814 user = opts.get('user')
815 815 date = opts.get('date')
816 816 if date:
817 817 date = util.parsedate(date)
818 818 diffopts = self.diffopts({'git': opts.get('git')})
819 819 self.check_reserved_name(patchfn)
820 820 if os.path.exists(self.join(patchfn)):
821 821 raise util.Abort(_('patch "%s" already exists') % patchfn)
822 822 if opts.get('include') or opts.get('exclude') or pats:
823 823 match = cmdutil.match(repo, pats, opts)
824 824 # detect missing files in pats
825 825 def badfn(f, msg):
826 826 raise util.Abort('%s: %s' % (f, msg))
827 827 match.bad = badfn
828 828 m, a, r, d = repo.status(match=match)[:4]
829 829 else:
830 830 m, a, r, d = self.check_localchanges(repo, force=True)
831 831 match = cmdutil.matchfiles(repo, m + a + r)
832 832 if len(repo[None].parents()) > 1:
833 833 raise util.Abort(_('cannot manage merge changesets'))
834 834 commitfiles = m + a + r
835 835 self.check_toppatch(repo)
836 836 insert = self.full_series_end()
837 837 wlock = repo.wlock()
838 838 try:
839 839 # if patch file write fails, abort early
840 840 p = self.opener(patchfn, "w")
841 841 try:
842 842 if self.plainmode:
843 843 if user:
844 844 p.write("From: " + user + "\n")
845 845 if not date:
846 846 p.write("\n")
847 847 if date:
848 848 p.write("Date: %d %d\n\n" % date)
849 849 else:
850 850 p.write("# HG changeset patch\n")
851 851 p.write("# Parent "
852 852 + hex(repo[None].parents()[0].node()) + "\n")
853 853 if user:
854 854 p.write("# User " + user + "\n")
855 855 if date:
856 856 p.write("# Date %s %s\n\n" % date)
857 857 if hasattr(msg, '__call__'):
858 858 msg = msg()
859 859 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
860 860 n = repo.commit(commitmsg, user, date, match=match, force=True)
861 861 if n is None:
862 862 raise util.Abort(_("repo commit failed"))
863 863 try:
864 864 self.full_series[insert:insert] = [patchfn]
865 865 self.applied.append(statusentry(n, patchfn))
866 866 self.parse_series()
867 867 self.series_dirty = 1
868 868 self.applied_dirty = 1
869 869 if msg:
870 870 msg = msg + "\n\n"
871 871 p.write(msg)
872 872 if commitfiles:
873 873 parent = self.qparents(repo, n)
874 874 chunks = patch.diff(repo, node1=parent, node2=n,
875 875 match=match, opts=diffopts)
876 876 for chunk in chunks:
877 877 p.write(chunk)
878 878 p.close()
879 879 wlock.release()
880 880 wlock = None
881 881 r = self.qrepo()
882 882 if r:
883 883 r[None].add([patchfn])
884 884 except:
885 885 repo.rollback()
886 886 raise
887 887 except Exception:
888 888 patchpath = self.join(patchfn)
889 889 try:
890 890 os.unlink(patchpath)
891 891 except:
892 892 self.ui.warn(_('error unlinking %s\n') % patchpath)
893 893 raise
894 894 self.removeundo(repo)
895 895 finally:
896 896 release(wlock)
897 897
898 898 def strip(self, repo, revs, update=True, backup="all", force=None):
899 899 wlock = lock = None
900 900 try:
901 901 wlock = repo.wlock()
902 902 lock = repo.lock()
903 903
904 904 if update:
905 905 self.check_localchanges(repo, force=force, refresh=False)
906 906 urev = self.qparents(repo, revs[0])
907 907 hg.clean(repo, urev)
908 908 repo.dirstate.write()
909 909
910 910 self.removeundo(repo)
911 911 for rev in revs:
912 912 repair.strip(self.ui, repo, rev, backup)
913 913 # strip may have unbundled a set of backed up revisions after
914 914 # the actual strip
915 915 self.removeundo(repo)
916 916 finally:
917 917 release(lock, wlock)
918 918
919 919 def isapplied(self, patch):
920 920 """returns (index, rev, patch)"""
921 921 for i, a in enumerate(self.applied):
922 922 if a.name == patch:
923 923 return (i, a.node, a.name)
924 924 return None
925 925
926 926 # if the exact patch name does not exist, we try a few
927 927 # variations. If strict is passed, we try only #1
928 928 #
929 929 # 1) a number to indicate an offset in the series file
930 930 # 2) a unique substring of the patch name was given
931 931 # 3) patchname[-+]num to indicate an offset in the series file
932 932 def lookup(self, patch, strict=False):
933 933 patch = patch and str(patch)
934 934
935 935 def partial_name(s):
936 936 if s in self.series:
937 937 return s
938 938 matches = [x for x in self.series if s in x]
939 939 if len(matches) > 1:
940 940 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
941 941 for m in matches:
942 942 self.ui.warn(' %s\n' % m)
943 943 return None
944 944 if matches:
945 945 return matches[0]
946 946 if self.series and self.applied:
947 947 if s == 'qtip':
948 948 return self.series[self.series_end(True)-1]
949 949 if s == 'qbase':
950 950 return self.series[0]
951 951 return None
952 952
953 953 if patch is None:
954 954 return None
955 955 if patch in self.series:
956 956 return patch
957 957
958 958 if not os.path.isfile(self.join(patch)):
959 959 try:
960 960 sno = int(patch)
961 961 except (ValueError, OverflowError):
962 962 pass
963 963 else:
964 964 if -len(self.series) <= sno < len(self.series):
965 965 return self.series[sno]
966 966
967 967 if not strict:
968 968 res = partial_name(patch)
969 969 if res:
970 970 return res
971 971 minus = patch.rfind('-')
972 972 if minus >= 0:
973 973 res = partial_name(patch[:minus])
974 974 if res:
975 975 i = self.series.index(res)
976 976 try:
977 977 off = int(patch[minus + 1:] or 1)
978 978 except (ValueError, OverflowError):
979 979 pass
980 980 else:
981 981 if i - off >= 0:
982 982 return self.series[i - off]
983 983 plus = patch.rfind('+')
984 984 if plus >= 0:
985 985 res = partial_name(patch[:plus])
986 986 if res:
987 987 i = self.series.index(res)
988 988 try:
989 989 off = int(patch[plus + 1:] or 1)
990 990 except (ValueError, OverflowError):
991 991 pass
992 992 else:
993 993 if i + off < len(self.series):
994 994 return self.series[i + off]
995 995 raise util.Abort(_("patch %s not in series") % patch)
996 996
997 997 def push(self, repo, patch=None, force=False, list=False,
998 998 mergeq=None, all=False, move=False):
999 999 diffopts = self.diffopts()
1000 1000 wlock = repo.wlock()
1001 1001 try:
1002 1002 heads = []
1003 1003 for b, ls in repo.branchmap().iteritems():
1004 1004 heads += ls
1005 1005 if not heads:
1006 1006 heads = [nullid]
1007 1007 if repo.dirstate.parents()[0] not in heads:
1008 1008 self.ui.status(_("(working directory not at a head)\n"))
1009 1009
1010 1010 if not self.series:
1011 1011 self.ui.warn(_('no patches in series\n'))
1012 1012 return 0
1013 1013
1014 1014 patch = self.lookup(patch)
1015 1015 # Suppose our series file is: A B C and the current 'top'
1016 1016 # patch is B. qpush C should be performed (moving forward)
1017 1017 # qpush B is a NOP (no change) qpush A is an error (can't
1018 1018 # go backwards with qpush)
1019 1019 if patch:
1020 1020 info = self.isapplied(patch)
1021 1021 if info:
1022 1022 if info[0] < len(self.applied) - 1:
1023 1023 raise util.Abort(
1024 1024 _("cannot push to a previous patch: %s") % patch)
1025 1025 self.ui.warn(
1026 1026 _('qpush: %s is already at the top\n') % patch)
1027 1027 return 0
1028 1028 pushable, reason = self.pushable(patch)
1029 1029 if not pushable:
1030 1030 if reason:
1031 1031 reason = _('guarded by %r') % reason
1032 1032 else:
1033 1033 reason = _('no matching guards')
1034 1034 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1035 1035 return 1
1036 1036 elif all:
1037 1037 patch = self.series[-1]
1038 1038 if self.isapplied(patch):
1039 1039 self.ui.warn(_('all patches are currently applied\n'))
1040 1040 return 0
1041 1041
1042 1042 # Following the above example, starting at 'top' of B:
1043 1043 # qpush should be performed (pushes C), but a subsequent
1044 1044 # qpush without an argument is an error (nothing to
1045 1045 # apply). This allows a loop of "...while hg qpush..." to
1046 1046 # work as it detects an error when done
1047 1047 start = self.series_end()
1048 1048 if start == len(self.series):
1049 1049 self.ui.warn(_('patch series already fully applied\n'))
1050 1050 return 1
1051 1051 if not force:
1052 1052 self.check_localchanges(repo)
1053 1053
1054 1054 if move:
1055 1055 if not patch:
1056 1056 raise util.Abort(_("please specify the patch to move"))
1057 1057 for i, rpn in enumerate(self.full_series[start:]):
1058 1058 # strip markers for patch guards
1059 1059 if self.guard_re.split(rpn, 1)[0] == patch:
1060 1060 break
1061 1061 index = start + i
1062 1062 assert index < len(self.full_series)
1063 1063 fullpatch = self.full_series[index]
1064 1064 del self.full_series[index]
1065 1065 self.full_series.insert(start, fullpatch)
1066 1066 self.parse_series()
1067 1067 self.series_dirty = 1
1068 1068
1069 1069 self.applied_dirty = 1
1070 1070 if start > 0:
1071 1071 self.check_toppatch(repo)
1072 1072 if not patch:
1073 1073 patch = self.series[start]
1074 1074 end = start + 1
1075 1075 else:
1076 1076 end = self.series.index(patch, start) + 1
1077 1077
1078 1078 s = self.series[start:end]
1079 1079 all_files = set()
1080 1080 try:
1081 1081 if mergeq:
1082 1082 ret = self.mergepatch(repo, mergeq, s, diffopts)
1083 1083 else:
1084 1084 ret = self.apply(repo, s, list, all_files=all_files)
1085 1085 except:
1086 1086 self.ui.warn(_('cleaning up working directory...'))
1087 1087 node = repo.dirstate.parents()[0]
1088 1088 hg.revert(repo, node, None)
1089 1089 # only remove unknown files that we know we touched or
1090 1090 # created while patching
1091 1091 for f in all_files:
1092 1092 if f not in repo.dirstate:
1093 1093 try:
1094 1094 util.unlink(repo.wjoin(f))
1095 1095 except OSError, inst:
1096 1096 if inst.errno != errno.ENOENT:
1097 1097 raise
1098 1098 self.ui.warn(_('done\n'))
1099 1099 raise
1100 1100
1101 1101 if not self.applied:
1102 1102 return ret[0]
1103 1103 top = self.applied[-1].name
1104 1104 if ret[0] and ret[0] > 1:
1105 1105 msg = _("errors during apply, please fix and refresh %s\n")
1106 1106 self.ui.write(msg % top)
1107 1107 else:
1108 1108 self.ui.write(_("now at: %s\n") % top)
1109 1109 return ret[0]
1110 1110
1111 1111 finally:
1112 1112 wlock.release()
1113 1113
1114 1114 def pop(self, repo, patch=None, force=False, update=True, all=False):
1115 1115 wlock = repo.wlock()
1116 1116 try:
1117 1117 if patch:
1118 1118 # index, rev, patch
1119 1119 info = self.isapplied(patch)
1120 1120 if not info:
1121 1121 patch = self.lookup(patch)
1122 1122 info = self.isapplied(patch)
1123 1123 if not info:
1124 1124 raise util.Abort(_("patch %s is not applied") % patch)
1125 1125
1126 1126 if not self.applied:
1127 1127 # Allow qpop -a to work repeatedly,
1128 1128 # but not qpop without an argument
1129 1129 self.ui.warn(_("no patches applied\n"))
1130 1130 return not all
1131 1131
1132 1132 if all:
1133 1133 start = 0
1134 1134 elif patch:
1135 1135 start = info[0] + 1
1136 1136 else:
1137 1137 start = len(self.applied) - 1
1138 1138
1139 1139 if start >= len(self.applied):
1140 1140 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1141 1141 return
1142 1142
1143 1143 if not update:
1144 1144 parents = repo.dirstate.parents()
1145 1145 rr = [x.node for x in self.applied]
1146 1146 for p in parents:
1147 1147 if p in rr:
1148 1148 self.ui.warn(_("qpop: forcing dirstate update\n"))
1149 1149 update = True
1150 1150 else:
1151 1151 parents = [p.node() for p in repo[None].parents()]
1152 1152 needupdate = False
1153 1153 for entry in self.applied[start:]:
1154 1154 if entry.node in parents:
1155 1155 needupdate = True
1156 1156 break
1157 1157 update = needupdate
1158 1158
1159 1159 if not force and update:
1160 1160 self.check_localchanges(repo)
1161 1161
1162 1162 self.applied_dirty = 1
1163 1163 end = len(self.applied)
1164 1164 rev = self.applied[start].node
1165 1165 if update:
1166 1166 top = self.check_toppatch(repo)[0]
1167 1167
1168 1168 try:
1169 1169 heads = repo.changelog.heads(rev)
1170 1170 except error.LookupError:
1171 1171 node = short(rev)
1172 1172 raise util.Abort(_('trying to pop unknown node %s') % node)
1173 1173
1174 1174 if heads != [self.applied[-1].node]:
1175 1175 raise util.Abort(_("popping would remove a revision not "
1176 1176 "managed by this patch queue"))
1177 1177
1178 1178 # we know there are no local changes, so we can make a simplified
1179 1179 # form of hg.update.
1180 1180 if update:
1181 1181 qp = self.qparents(repo, rev)
1182 1182 ctx = repo[qp]
1183 1183 m, a, r, d = repo.status(qp, top)[:4]
1184 1184 if d:
1185 1185 raise util.Abort(_("deletions found between repo revs"))
1186 1186 for f in a:
1187 1187 try:
1188 1188 util.unlink(repo.wjoin(f))
1189 1189 except OSError, e:
1190 1190 if e.errno != errno.ENOENT:
1191 1191 raise
1192 1192 repo.dirstate.forget(f)
1193 1193 for f in m + r:
1194 1194 fctx = ctx[f]
1195 1195 repo.wwrite(f, fctx.data(), fctx.flags())
1196 1196 repo.dirstate.normal(f)
1197 1197 repo.dirstate.setparents(qp, nullid)
1198 1198 for patch in reversed(self.applied[start:end]):
1199 1199 self.ui.status(_("popping %s\n") % patch.name)
1200 1200 del self.applied[start:end]
1201 1201 self.strip(repo, [rev], update=False, backup='strip')
1202 1202 if self.applied:
1203 1203 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1204 1204 else:
1205 1205 self.ui.write(_("patch queue now empty\n"))
1206 1206 finally:
1207 1207 wlock.release()
1208 1208
1209 1209 def diff(self, repo, pats, opts):
1210 1210 top, patch = self.check_toppatch(repo)
1211 1211 if not top:
1212 1212 self.ui.write(_("no patches applied\n"))
1213 1213 return
1214 1214 qp = self.qparents(repo, top)
1215 1215 if opts.get('reverse'):
1216 1216 node1, node2 = None, qp
1217 1217 else:
1218 1218 node1, node2 = qp, None
1219 1219 diffopts = self.diffopts(opts, patch)
1220 1220 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1221 1221
1222 1222 def refresh(self, repo, pats=None, **opts):
1223 1223 if not self.applied:
1224 1224 self.ui.write(_("no patches applied\n"))
1225 1225 return 1
1226 1226 msg = opts.get('msg', '').rstrip()
1227 1227 newuser = opts.get('user')
1228 1228 newdate = opts.get('date')
1229 1229 if newdate:
1230 1230 newdate = '%d %d' % util.parsedate(newdate)
1231 1231 wlock = repo.wlock()
1232 1232
1233 1233 try:
1234 1234 self.check_toppatch(repo)
1235 1235 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1236 1236 if repo.changelog.heads(top) != [top]:
1237 1237 raise util.Abort(_("cannot refresh a revision with children"))
1238 1238
1239 1239 cparents = repo.changelog.parents(top)
1240 1240 patchparent = self.qparents(repo, top)
1241 1241 ph = patchheader(self.join(patchfn), self.plainmode)
1242 1242 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1243 1243 if msg:
1244 1244 ph.setmessage(msg)
1245 1245 if newuser:
1246 1246 ph.setuser(newuser)
1247 1247 if newdate:
1248 1248 ph.setdate(newdate)
1249 1249 ph.setparent(hex(patchparent))
1250 1250
1251 1251 # only commit new patch when write is complete
1252 1252 patchf = self.opener(patchfn, 'w', atomictemp=True)
1253 1253
1254 1254 comments = str(ph)
1255 1255 if comments:
1256 1256 patchf.write(comments)
1257 1257
1258 1258 # update the dirstate in place, strip off the qtip commit
1259 1259 # and then commit.
1260 1260 #
1261 1261 # this should really read:
1262 1262 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1263 1263 # but we do it backwards to take advantage of manifest/chlog
1264 1264 # caching against the next repo.status call
1265 1265 mm, aa, dd, aa2 = repo.status(patchparent, top)[:4]
1266 1266 changes = repo.changelog.read(top)
1267 1267 man = repo.manifest.read(changes[0])
1268 1268 aaa = aa[:]
1269 1269 matchfn = cmdutil.match(repo, pats, opts)
1270 1270 # in short mode, we only diff the files included in the
1271 1271 # patch already plus specified files
1272 1272 if opts.get('short'):
1273 1273 # if amending a patch, we start with existing
1274 1274 # files plus specified files - unfiltered
1275 1275 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1276 1276 # filter with inc/exl options
1277 1277 matchfn = cmdutil.match(repo, opts=opts)
1278 1278 else:
1279 1279 match = cmdutil.matchall(repo)
1280 1280 m, a, r, d = repo.status(match=match)[:4]
1281 1281
1282 1282 # we might end up with files that were added between
1283 1283 # qtip and the dirstate parent, but then changed in the
1284 1284 # local dirstate. in this case, we want them to only
1285 1285 # show up in the added section
1286 1286 for x in m:
1287 1287 if x not in aa:
1288 1288 mm.append(x)
1289 1289 # we might end up with files added by the local dirstate that
1290 1290 # were deleted by the patch. In this case, they should only
1291 1291 # show up in the changed section.
1292 1292 for x in a:
1293 1293 if x in dd:
1294 1294 del dd[dd.index(x)]
1295 1295 mm.append(x)
1296 1296 else:
1297 1297 aa.append(x)
1298 1298 # make sure any files deleted in the local dirstate
1299 1299 # are not in the add or change column of the patch
1300 1300 forget = []
1301 1301 for x in d + r:
1302 1302 if x in aa:
1303 1303 del aa[aa.index(x)]
1304 1304 forget.append(x)
1305 1305 continue
1306 1306 elif x in mm:
1307 1307 del mm[mm.index(x)]
1308 1308 dd.append(x)
1309 1309
1310 1310 m = list(set(mm))
1311 1311 r = list(set(dd))
1312 1312 a = list(set(aa))
1313 1313 c = [filter(matchfn, l) for l in (m, a, r)]
1314 1314 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1315 1315 chunks = patch.diff(repo, patchparent, match=match,
1316 1316 changes=c, opts=diffopts)
1317 1317 for chunk in chunks:
1318 1318 patchf.write(chunk)
1319 1319
1320 1320 try:
1321 1321 if diffopts.git or diffopts.upgrade:
1322 1322 copies = {}
1323 1323 for dst in a:
1324 1324 src = repo.dirstate.copied(dst)
1325 1325 # during qfold, the source file for copies may
1326 1326 # be removed. Treat this as a simple add.
1327 1327 if src is not None and src in repo.dirstate:
1328 1328 copies.setdefault(src, []).append(dst)
1329 1329 repo.dirstate.add(dst)
1330 1330 # remember the copies between patchparent and qtip
1331 1331 for dst in aaa:
1332 1332 f = repo.file(dst)
1333 1333 src = f.renamed(man[dst])
1334 1334 if src:
1335 1335 copies.setdefault(src[0], []).extend(
1336 1336 copies.get(dst, []))
1337 1337 if dst in a:
1338 1338 copies[src[0]].append(dst)
1339 1339 # we can't copy a file created by the patch itself
1340 1340 if dst in copies:
1341 1341 del copies[dst]
1342 1342 for src, dsts in copies.iteritems():
1343 1343 for dst in dsts:
1344 1344 repo.dirstate.copy(src, dst)
1345 1345 else:
1346 1346 for dst in a:
1347 1347 repo.dirstate.add(dst)
1348 1348 # Drop useless copy information
1349 1349 for f in list(repo.dirstate.copies()):
1350 1350 repo.dirstate.copy(None, f)
1351 1351 for f in r:
1352 1352 repo.dirstate.remove(f)
1353 1353 # if the patch excludes a modified file, mark that
1354 1354 # file with mtime=0 so status can see it.
1355 1355 mm = []
1356 1356 for i in xrange(len(m)-1, -1, -1):
1357 1357 if not matchfn(m[i]):
1358 1358 mm.append(m[i])
1359 1359 del m[i]
1360 1360 for f in m:
1361 1361 repo.dirstate.normal(f)
1362 1362 for f in mm:
1363 1363 repo.dirstate.normallookup(f)
1364 1364 for f in forget:
1365 1365 repo.dirstate.forget(f)
1366 1366
1367 1367 if not msg:
1368 1368 if not ph.message:
1369 1369 message = "[mq]: %s\n" % patchfn
1370 1370 else:
1371 1371 message = "\n".join(ph.message)
1372 1372 else:
1373 1373 message = msg
1374 1374
1375 1375 user = ph.user or changes[1]
1376 1376
1377 1377 # assumes strip can roll itself back if interrupted
1378 1378 repo.dirstate.setparents(*cparents)
1379 1379 self.applied.pop()
1380 1380 self.applied_dirty = 1
1381 1381 self.strip(repo, [top], update=False,
1382 1382 backup='strip')
1383 1383 except:
1384 1384 repo.dirstate.invalidate()
1385 1385 raise
1386 1386
1387 1387 try:
1388 1388 # might be nice to attempt to roll back strip after this
1389 1389 patchf.rename()
1390 1390 n = repo.commit(message, user, ph.date, match=match,
1391 1391 force=True)
1392 1392 self.applied.append(statusentry(n, patchfn))
1393 1393 except:
1394 1394 ctx = repo[cparents[0]]
1395 1395 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1396 1396 self.save_dirty()
1397 1397 self.ui.warn(_('refresh interrupted while patch was popped! '
1398 1398 '(revert --all, qpush to recover)\n'))
1399 1399 raise
1400 1400 finally:
1401 1401 wlock.release()
1402 1402 self.removeundo(repo)
1403 1403
1404 1404 def init(self, repo, create=False):
1405 1405 if not create and os.path.isdir(self.path):
1406 1406 raise util.Abort(_("patch queue directory already exists"))
1407 1407 try:
1408 1408 os.mkdir(self.path)
1409 1409 except OSError, inst:
1410 1410 if inst.errno != errno.EEXIST or not create:
1411 1411 raise
1412 1412 if create:
1413 1413 return self.qrepo(create=True)
1414 1414
1415 1415 def unapplied(self, repo, patch=None):
1416 1416 if patch and patch not in self.series:
1417 1417 raise util.Abort(_("patch %s is not in series file") % patch)
1418 1418 if not patch:
1419 1419 start = self.series_end()
1420 1420 else:
1421 1421 start = self.series.index(patch) + 1
1422 1422 unapplied = []
1423 1423 for i in xrange(start, len(self.series)):
1424 1424 pushable, reason = self.pushable(i)
1425 1425 if pushable:
1426 1426 unapplied.append((i, self.series[i]))
1427 1427 self.explain_pushable(i)
1428 1428 return unapplied
1429 1429
1430 1430 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1431 1431 summary=False):
1432 1432 def displayname(pfx, patchname, state):
1433 1433 if pfx:
1434 1434 self.ui.write(pfx)
1435 1435 if summary:
1436 1436 ph = patchheader(self.join(patchname), self.plainmode)
1437 1437 msg = ph.message and ph.message[0] or ''
1438 1438 if self.ui.formatted():
1439 1439 width = util.termwidth() - len(pfx) - len(patchname) - 2
1440 1440 if width > 0:
1441 1441 msg = util.ellipsis(msg, width)
1442 1442 else:
1443 1443 msg = ''
1444 1444 self.ui.write(patchname, label='qseries.' + state)
1445 1445 self.ui.write(': ')
1446 1446 self.ui.write(msg, label='qseries.message.' + state)
1447 1447 else:
1448 1448 self.ui.write(patchname, label='qseries.' + state)
1449 1449 self.ui.write('\n')
1450 1450
1451 1451 applied = set([p.name for p in self.applied])
1452 1452 if length is None:
1453 1453 length = len(self.series) - start
1454 1454 if not missing:
1455 1455 if self.ui.verbose:
1456 1456 idxwidth = len(str(start + length - 1))
1457 1457 for i in xrange(start, start + length):
1458 1458 patch = self.series[i]
1459 1459 if patch in applied:
1460 1460 char, state = 'A', 'applied'
1461 1461 elif self.pushable(i)[0]:
1462 1462 char, state = 'U', 'unapplied'
1463 1463 else:
1464 1464 char, state = 'G', 'guarded'
1465 1465 pfx = ''
1466 1466 if self.ui.verbose:
1467 1467 pfx = '%*d %s ' % (idxwidth, i, char)
1468 1468 elif status and status != char:
1469 1469 continue
1470 1470 displayname(pfx, patch, state)
1471 1471 else:
1472 1472 msng_list = []
1473 1473 for root, dirs, files in os.walk(self.path):
1474 1474 d = root[len(self.path) + 1:]
1475 1475 for f in files:
1476 1476 fl = os.path.join(d, f)
1477 1477 if (fl not in self.series and
1478 1478 fl not in (self.status_path, self.series_path,
1479 1479 self.guards_path)
1480 1480 and not fl.startswith('.')):
1481 1481 msng_list.append(fl)
1482 1482 for x in sorted(msng_list):
1483 1483 pfx = self.ui.verbose and ('D ') or ''
1484 1484 displayname(pfx, x, 'missing')
1485 1485
1486 1486 def issaveline(self, l):
1487 1487 if l.name == '.hg.patches.save.line':
1488 1488 return True
1489 1489
1490 1490 def qrepo(self, create=False):
1491 1491 ui = self.ui.copy()
1492 1492 ui.setconfig('paths', 'default', '', overlay=False)
1493 1493 ui.setconfig('paths', 'default-push', '', overlay=False)
1494 1494 if create or os.path.isdir(self.join(".hg")):
1495 1495 return hg.repository(ui, path=self.path, create=create)
1496 1496
1497 1497 def restore(self, repo, rev, delete=None, qupdate=None):
1498 1498 desc = repo[rev].description().strip()
1499 1499 lines = desc.splitlines()
1500 1500 i = 0
1501 1501 datastart = None
1502 1502 series = []
1503 1503 applied = []
1504 1504 qpp = None
1505 1505 for i, line in enumerate(lines):
1506 1506 if line == 'Patch Data:':
1507 1507 datastart = i + 1
1508 1508 elif line.startswith('Dirstate:'):
1509 1509 l = line.rstrip()
1510 1510 l = l[10:].split(' ')
1511 1511 qpp = [bin(x) for x in l]
1512 1512 elif datastart != None:
1513 1513 l = line.rstrip()
1514 1514 n, name = l.split(':', 1)
1515 1515 if n:
1516 1516 applied.append(statusentry(bin(n), name))
1517 1517 else:
1518 1518 series.append(l)
1519 1519 if datastart is None:
1520 1520 self.ui.warn(_("No saved patch data found\n"))
1521 1521 return 1
1522 1522 self.ui.warn(_("restoring status: %s\n") % lines[0])
1523 1523 self.full_series = series
1524 1524 self.applied = applied
1525 1525 self.parse_series()
1526 1526 self.series_dirty = 1
1527 1527 self.applied_dirty = 1
1528 1528 heads = repo.changelog.heads()
1529 1529 if delete:
1530 1530 if rev not in heads:
1531 1531 self.ui.warn(_("save entry has children, leaving it alone\n"))
1532 1532 else:
1533 1533 self.ui.warn(_("removing save entry %s\n") % short(rev))
1534 1534 pp = repo.dirstate.parents()
1535 1535 if rev in pp:
1536 1536 update = True
1537 1537 else:
1538 1538 update = False
1539 1539 self.strip(repo, [rev], update=update, backup='strip')
1540 1540 if qpp:
1541 1541 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1542 1542 (short(qpp[0]), short(qpp[1])))
1543 1543 if qupdate:
1544 1544 self.ui.status(_("queue directory updating\n"))
1545 1545 r = self.qrepo()
1546 1546 if not r:
1547 1547 self.ui.warn(_("Unable to load queue repository\n"))
1548 1548 return 1
1549 1549 hg.clean(r, qpp[0])
1550 1550
1551 1551 def save(self, repo, msg=None):
1552 1552 if not self.applied:
1553 1553 self.ui.warn(_("save: no patches applied, exiting\n"))
1554 1554 return 1
1555 1555 if self.issaveline(self.applied[-1]):
1556 1556 self.ui.warn(_("status is already saved\n"))
1557 1557 return 1
1558 1558
1559 1559 if not msg:
1560 1560 msg = _("hg patches saved state")
1561 1561 else:
1562 1562 msg = "hg patches: " + msg.rstrip('\r\n')
1563 1563 r = self.qrepo()
1564 1564 if r:
1565 1565 pp = r.dirstate.parents()
1566 1566 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1567 1567 msg += "\n\nPatch Data:\n"
1568 1568 msg += ''.join('%s\n' % x for x in self.applied)
1569 1569 msg += ''.join(':%s\n' % x for x in self.full_series)
1570 1570 n = repo.commit(msg, force=True)
1571 1571 if not n:
1572 1572 self.ui.warn(_("repo commit failed\n"))
1573 1573 return 1
1574 1574 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1575 1575 self.applied_dirty = 1
1576 1576 self.removeundo(repo)
1577 1577
1578 1578 def full_series_end(self):
1579 1579 if self.applied:
1580 1580 p = self.applied[-1].name
1581 1581 end = self.find_series(p)
1582 1582 if end is None:
1583 1583 return len(self.full_series)
1584 1584 return end + 1
1585 1585 return 0
1586 1586
1587 1587 def series_end(self, all_patches=False):
1588 1588 """If all_patches is False, return the index of the next pushable patch
1589 1589 in the series, or the series length. If all_patches is True, return the
1590 1590 index of the first patch past the last applied one.
1591 1591 """
1592 1592 end = 0
1593 1593 def next(start):
1594 1594 if all_patches or start >= len(self.series):
1595 1595 return start
1596 1596 for i in xrange(start, len(self.series)):
1597 1597 p, reason = self.pushable(i)
1598 1598 if p:
1599 1599 break
1600 1600 self.explain_pushable(i)
1601 1601 return i
1602 1602 if self.applied:
1603 1603 p = self.applied[-1].name
1604 1604 try:
1605 1605 end = self.series.index(p)
1606 1606 except ValueError:
1607 1607 return 0
1608 1608 return next(end + 1)
1609 1609 return next(end)
1610 1610
1611 1611 def appliedname(self, index):
1612 1612 pname = self.applied[index].name
1613 1613 if not self.ui.verbose:
1614 1614 p = pname
1615 1615 else:
1616 1616 p = str(self.series.index(pname)) + " " + pname
1617 1617 return p
1618 1618
1619 1619 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1620 1620 force=None, git=False):
1621 1621 def checkseries(patchname):
1622 1622 if patchname in self.series:
1623 1623 raise util.Abort(_('patch %s is already in the series file')
1624 1624 % patchname)
1625 1625 def checkfile(patchname):
1626 1626 if not force and os.path.exists(self.join(patchname)):
1627 1627 raise util.Abort(_('patch "%s" already exists')
1628 1628 % patchname)
1629 1629
1630 1630 if rev:
1631 1631 if files:
1632 1632 raise util.Abort(_('option "-r" not valid when importing '
1633 1633 'files'))
1634 1634 rev = cmdutil.revrange(repo, rev)
1635 1635 rev.sort(reverse=True)
1636 1636 if (len(files) > 1 or len(rev) > 1) and patchname:
1637 1637 raise util.Abort(_('option "-n" not valid when importing multiple '
1638 1638 'patches'))
1639 1639 if rev:
1640 1640 # If mq patches are applied, we can only import revisions
1641 1641 # that form a linear path to qbase.
1642 1642 # Otherwise, they should form a linear path to a head.
1643 1643 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1644 1644 if len(heads) > 1:
1645 1645 raise util.Abort(_('revision %d is the root of more than one '
1646 1646 'branch') % rev[-1])
1647 1647 if self.applied:
1648 1648 base = repo.changelog.node(rev[0])
1649 1649 if base in [n.node for n in self.applied]:
1650 1650 raise util.Abort(_('revision %d is already managed')
1651 1651 % rev[0])
1652 1652 if heads != [self.applied[-1].node]:
1653 1653 raise util.Abort(_('revision %d is not the parent of '
1654 1654 'the queue') % rev[0])
1655 1655 base = repo.changelog.rev(self.applied[0].node)
1656 1656 lastparent = repo.changelog.parentrevs(base)[0]
1657 1657 else:
1658 1658 if heads != [repo.changelog.node(rev[0])]:
1659 1659 raise util.Abort(_('revision %d has unmanaged children')
1660 1660 % rev[0])
1661 1661 lastparent = None
1662 1662
1663 1663 diffopts = self.diffopts({'git': git})
1664 1664 for r in rev:
1665 1665 p1, p2 = repo.changelog.parentrevs(r)
1666 1666 n = repo.changelog.node(r)
1667 1667 if p2 != nullrev:
1668 1668 raise util.Abort(_('cannot import merge revision %d') % r)
1669 1669 if lastparent and lastparent != r:
1670 1670 raise util.Abort(_('revision %d is not the parent of %d')
1671 1671 % (r, lastparent))
1672 1672 lastparent = p1
1673 1673
1674 1674 if not patchname:
1675 1675 patchname = normname('%d.diff' % r)
1676 1676 self.check_reserved_name(patchname)
1677 1677 checkseries(patchname)
1678 1678 checkfile(patchname)
1679 1679 self.full_series.insert(0, patchname)
1680 1680
1681 1681 patchf = self.opener(patchname, "w")
1682 1682 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1683 1683 patchf.close()
1684 1684
1685 1685 se = statusentry(n, patchname)
1686 1686 self.applied.insert(0, se)
1687 1687
1688 1688 self.added.append(patchname)
1689 1689 patchname = None
1690 1690 self.parse_series()
1691 1691 self.applied_dirty = 1
1692 1692 self.series_dirty = True
1693 1693
1694 1694 for i, filename in enumerate(files):
1695 1695 if existing:
1696 1696 if filename == '-':
1697 1697 raise util.Abort(_('-e is incompatible with import from -'))
1698 1698 filename = normname(filename)
1699 1699 self.check_reserved_name(filename)
1700 1700 originpath = self.join(filename)
1701 1701 if not os.path.isfile(originpath):
1702 1702 raise util.Abort(_("patch %s does not exist") % filename)
1703 1703
1704 1704 if patchname:
1705 1705 self.check_reserved_name(patchname)
1706 1706 checkfile(patchname)
1707 1707
1708 1708 self.ui.write(_('renaming %s to %s\n')
1709 1709 % (filename, patchname))
1710 1710 util.rename(originpath, self.join(patchname))
1711 1711 else:
1712 1712 patchname = filename
1713 1713
1714 1714 else:
1715 1715 try:
1716 1716 if filename == '-':
1717 1717 if not patchname:
1718 1718 raise util.Abort(
1719 1719 _('need --name to import a patch from -'))
1720 1720 text = sys.stdin.read()
1721 1721 else:
1722 1722 text = url.open(self.ui, filename).read()
1723 1723 except (OSError, IOError):
1724 1724 raise util.Abort(_("unable to read file %s") % filename)
1725 1725 if not patchname:
1726 1726 patchname = normname(os.path.basename(filename))
1727 1727 self.check_reserved_name(patchname)
1728 1728 checkfile(patchname)
1729 1729 patchf = self.opener(patchname, "w")
1730 1730 patchf.write(text)
1731 1731 if not force:
1732 1732 checkseries(patchname)
1733 1733 if patchname not in self.series:
1734 1734 index = self.full_series_end() + i
1735 1735 self.full_series[index:index] = [patchname]
1736 1736 self.parse_series()
1737 1737 self.series_dirty = True
1738 1738 self.ui.warn(_("adding %s to series file\n") % patchname)
1739 1739 self.added.append(patchname)
1740 1740 patchname = None
1741 1741
1742 1742 def delete(ui, repo, *patches, **opts):
1743 1743 """remove patches from queue
1744 1744
1745 1745 The patches must not be applied, and at least one patch is required. With
1746 1746 -k/--keep, the patch files are preserved in the patch directory.
1747 1747
1748 1748 To stop managing a patch and move it into permanent history,
1749 1749 use the :hg:`qfinish` command."""
1750 1750 q = repo.mq
1751 1751 q.delete(repo, patches, opts)
1752 1752 q.save_dirty()
1753 1753 return 0
1754 1754
1755 1755 def applied(ui, repo, patch=None, **opts):
1756 1756 """print the patches already applied"""
1757 1757
1758 1758 q = repo.mq
1759 1759
1760 1760 if patch:
1761 1761 if patch not in q.series:
1762 1762 raise util.Abort(_("patch %s is not in series file") % patch)
1763 1763 end = q.series.index(patch) + 1
1764 1764 else:
1765 1765 end = q.series_end(True)
1766 1766
1767 1767 if opts.get('last') and not end:
1768 1768 ui.write(_("no patches applied\n"))
1769 1769 return 1
1770 1770 elif opts.get('last') and end == 1:
1771 1771 ui.write(_("only one patch applied\n"))
1772 1772 return 1
1773 1773 elif opts.get('last'):
1774 1774 start = end - 2
1775 1775 end = 1
1776 1776 else:
1777 1777 start = 0
1778 1778
1779 1779 return q.qseries(repo, length=end, start=start, status='A',
1780 1780 summary=opts.get('summary'))
1781 1781
1782 1782 def unapplied(ui, repo, patch=None, **opts):
1783 1783 """print the patches not yet applied"""
1784 1784
1785 1785 q = repo.mq
1786 1786 if patch:
1787 1787 if patch not in q.series:
1788 1788 raise util.Abort(_("patch %s is not in series file") % patch)
1789 1789 start = q.series.index(patch) + 1
1790 1790 else:
1791 1791 start = q.series_end(True)
1792 1792
1793 1793 if start == len(q.series) and opts.get('first'):
1794 1794 ui.write(_("all patches applied\n"))
1795 1795 return 1
1796 1796
1797 1797 length = opts.get('first') and 1 or None
1798 1798 return q.qseries(repo, start=start, length=length, status='U',
1799 1799 summary=opts.get('summary'))
1800 1800
1801 1801 def qimport(ui, repo, *filename, **opts):
1802 1802 """import a patch
1803 1803
1804 1804 The patch is inserted into the series after the last applied
1805 1805 patch. If no patches have been applied, qimport prepends the patch
1806 1806 to the series.
1807 1807
1808 1808 The patch will have the same name as its source file unless you
1809 1809 give it a new one with -n/--name.
1810 1810
1811 1811 You can register an existing patch inside the patch directory with
1812 1812 the -e/--existing flag.
1813 1813
1814 1814 With -f/--force, an existing patch of the same name will be
1815 1815 overwritten.
1816 1816
1817 1817 An existing changeset may be placed under mq control with -r/--rev
1818 1818 (e.g. qimport --rev tip -n patch will place tip under mq control).
1819 1819 With -g/--git, patches imported with --rev will use the git diff
1820 1820 format. See the diffs help topic for information on why this is
1821 1821 important for preserving rename/copy information and permission
1822 1822 changes.
1823 1823
1824 1824 To import a patch from standard input, pass - as the patch file.
1825 1825 When importing from standard input, a patch name must be specified
1826 1826 using the --name flag.
1827 1827
1828 1828 To import an existing patch while renaming it::
1829 1829
1830 1830 hg qimport -e existing-patch -n new-name
1831 1831 """
1832 1832 q = repo.mq
1833 1833 try:
1834 1834 q.qimport(repo, filename, patchname=opts['name'],
1835 1835 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1836 1836 git=opts['git'])
1837 1837 finally:
1838 1838 q.save_dirty()
1839 1839
1840 1840 if opts.get('push') and not opts.get('rev'):
1841 1841 return q.push(repo, None)
1842 1842 return 0
1843 1843
1844 1844 def qinit(ui, repo, create):
1845 1845 """initialize a new queue repository
1846 1846
1847 1847 This command also creates a series file for ordering patches, and
1848 1848 an mq-specific .hgignore file in the queue repository, to exclude
1849 1849 the status and guards files (these contain mostly transient state)."""
1850 1850 q = repo.mq
1851 1851 r = q.init(repo, create)
1852 1852 q.save_dirty()
1853 1853 if r:
1854 1854 if not os.path.exists(r.wjoin('.hgignore')):
1855 1855 fp = r.wopener('.hgignore', 'w')
1856 1856 fp.write('^\\.hg\n')
1857 1857 fp.write('^\\.mq\n')
1858 1858 fp.write('syntax: glob\n')
1859 1859 fp.write('status\n')
1860 1860 fp.write('guards\n')
1861 1861 fp.close()
1862 1862 if not os.path.exists(r.wjoin('series')):
1863 1863 r.wopener('series', 'w').close()
1864 1864 r[None].add(['.hgignore', 'series'])
1865 1865 commands.add(ui, r)
1866 1866 return 0
1867 1867
1868 1868 def init(ui, repo, **opts):
1869 1869 """init a new queue repository (DEPRECATED)
1870 1870
1871 1871 The queue repository is unversioned by default. If
1872 1872 -c/--create-repo is specified, qinit will create a separate nested
1873 1873 repository for patches (qinit -c may also be run later to convert
1874 1874 an unversioned patch repository into a versioned one). You can use
1875 1875 qcommit to commit changes to this queue repository.
1876 1876
1877 1877 This command is deprecated. Without -c, it's implied by other relevant
1878 1878 commands. With -c, use :hg:`init --mq` instead."""
1879 1879 return qinit(ui, repo, create=opts['create_repo'])
1880 1880
1881 1881 def clone(ui, source, dest=None, **opts):
1882 1882 '''clone main and patch repository at same time
1883 1883
1884 1884 If source is local, destination will have no patches applied. If
1885 1885 source is remote, this command can not check if patches are
1886 1886 applied in source, so cannot guarantee that patches are not
1887 1887 applied in destination. If you clone remote repository, be sure
1888 1888 before that it has no patches applied.
1889 1889
1890 1890 Source patch repository is looked for in <src>/.hg/patches by
1891 1891 default. Use -p <url> to change.
1892 1892
1893 1893 The patch directory must be a nested Mercurial repository, as
1894 1894 would be created by :hg:`init --mq`.
1895 1895 '''
1896 1896 def patchdir(repo):
1897 1897 url = repo.url()
1898 1898 if url.endswith('/'):
1899 1899 url = url[:-1]
1900 1900 return url + '/.hg/patches'
1901 1901 if dest is None:
1902 1902 dest = hg.defaultdest(source)
1903 1903 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
1904 1904 if opts['patches']:
1905 1905 patchespath = ui.expandpath(opts['patches'])
1906 1906 else:
1907 1907 patchespath = patchdir(sr)
1908 1908 try:
1909 1909 hg.repository(ui, patchespath)
1910 1910 except error.RepoError:
1911 1911 raise util.Abort(_('versioned patch repository not found'
1912 1912 ' (see init --mq)'))
1913 1913 qbase, destrev = None, None
1914 1914 if sr.local():
1915 1915 if sr.mq.applied:
1916 1916 qbase = sr.mq.applied[0].node
1917 1917 if not hg.islocal(dest):
1918 1918 heads = set(sr.heads())
1919 1919 destrev = list(heads.difference(sr.heads(qbase)))
1920 1920 destrev.append(sr.changelog.parents(qbase)[0])
1921 1921 elif sr.capable('lookup'):
1922 1922 try:
1923 1923 qbase = sr.lookup('qbase')
1924 1924 except error.RepoError:
1925 1925 pass
1926 1926 ui.note(_('cloning main repository\n'))
1927 1927 sr, dr = hg.clone(ui, sr.url(), dest,
1928 1928 pull=opts['pull'],
1929 1929 rev=destrev,
1930 1930 update=False,
1931 1931 stream=opts['uncompressed'])
1932 1932 ui.note(_('cloning patch repository\n'))
1933 1933 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1934 1934 pull=opts['pull'], update=not opts['noupdate'],
1935 1935 stream=opts['uncompressed'])
1936 1936 if dr.local():
1937 1937 if qbase:
1938 1938 ui.note(_('stripping applied patches from destination '
1939 1939 'repository\n'))
1940 1940 dr.mq.strip(dr, [qbase], update=False, backup=None)
1941 1941 if not opts['noupdate']:
1942 1942 ui.note(_('updating destination repository\n'))
1943 1943 hg.update(dr, dr.changelog.tip())
1944 1944
1945 1945 def commit(ui, repo, *pats, **opts):
1946 1946 """commit changes in the queue repository (DEPRECATED)
1947 1947
1948 1948 This command is deprecated; use :hg:`commit --mq` instead."""
1949 1949 q = repo.mq
1950 1950 r = q.qrepo()
1951 1951 if not r:
1952 1952 raise util.Abort('no queue repository')
1953 1953 commands.commit(r.ui, r, *pats, **opts)
1954 1954
1955 1955 def series(ui, repo, **opts):
1956 1956 """print the entire series file"""
1957 1957 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1958 1958 return 0
1959 1959
1960 1960 def top(ui, repo, **opts):
1961 1961 """print the name of the current patch"""
1962 1962 q = repo.mq
1963 1963 t = q.applied and q.series_end(True) or 0
1964 1964 if t:
1965 1965 return q.qseries(repo, start=t - 1, length=1, status='A',
1966 1966 summary=opts.get('summary'))
1967 1967 else:
1968 1968 ui.write(_("no patches applied\n"))
1969 1969 return 1
1970 1970
1971 1971 def next(ui, repo, **opts):
1972 1972 """print the name of the next patch"""
1973 1973 q = repo.mq
1974 1974 end = q.series_end()
1975 1975 if end == len(q.series):
1976 1976 ui.write(_("all patches applied\n"))
1977 1977 return 1
1978 1978 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1979 1979
1980 1980 def prev(ui, repo, **opts):
1981 1981 """print the name of the previous patch"""
1982 1982 q = repo.mq
1983 1983 l = len(q.applied)
1984 1984 if l == 1:
1985 1985 ui.write(_("only one patch applied\n"))
1986 1986 return 1
1987 1987 if not l:
1988 1988 ui.write(_("no patches applied\n"))
1989 1989 return 1
1990 1990 return q.qseries(repo, start=l - 2, length=1, status='A',
1991 1991 summary=opts.get('summary'))
1992 1992
1993 1993 def setupheaderopts(ui, opts):
1994 1994 if not opts.get('user') and opts.get('currentuser'):
1995 1995 opts['user'] = ui.username()
1996 1996 if not opts.get('date') and opts.get('currentdate'):
1997 1997 opts['date'] = "%d %d" % util.makedate()
1998 1998
1999 1999 def new(ui, repo, patch, *args, **opts):
2000 2000 """create a new patch
2001 2001
2002 2002 qnew creates a new patch on top of the currently-applied patch (if
2003 2003 any). The patch will be initialized with any outstanding changes
2004 2004 in the working directory. You may also use -I/--include,
2005 2005 -X/--exclude, and/or a list of files after the patch name to add
2006 2006 only changes to matching files to the new patch, leaving the rest
2007 2007 as uncommitted modifications.
2008 2008
2009 2009 -u/--user and -d/--date can be used to set the (given) user and
2010 2010 date, respectively. -U/--currentuser and -D/--currentdate set user
2011 2011 to current user and date to current date.
2012 2012
2013 2013 -e/--edit, -m/--message or -l/--logfile set the patch header as
2014 2014 well as the commit message. If none is specified, the header is
2015 2015 empty and the commit message is '[mq]: PATCH'.
2016 2016
2017 2017 Use the -g/--git option to keep the patch in the git extended diff
2018 2018 format. Read the diffs help topic for more information on why this
2019 2019 is important for preserving permission changes and copy/rename
2020 2020 information.
2021 2021 """
2022 2022 msg = cmdutil.logmessage(opts)
2023 2023 def getmsg():
2024 2024 return ui.edit(msg, opts['user'] or ui.username())
2025 2025 q = repo.mq
2026 2026 opts['msg'] = msg
2027 2027 if opts.get('edit'):
2028 2028 opts['msg'] = getmsg
2029 2029 else:
2030 2030 opts['msg'] = msg
2031 2031 setupheaderopts(ui, opts)
2032 2032 q.new(repo, patch, *args, **opts)
2033 2033 q.save_dirty()
2034 2034 return 0
2035 2035
2036 2036 def refresh(ui, repo, *pats, **opts):
2037 2037 """update the current patch
2038 2038
2039 2039 If any file patterns are provided, the refreshed patch will
2040 2040 contain only the modifications that match those patterns; the
2041 2041 remaining modifications will remain in the working directory.
2042 2042
2043 2043 If -s/--short is specified, files currently included in the patch
2044 2044 will be refreshed just like matched files and remain in the patch.
2045 2045
2046 2046 If -e/--edit is specified, Mercurial will start your configured editor for
2047 2047 you to enter a message. In case qrefresh fails, you will find a backup of
2048 2048 your message in ``.hg/last-message.txt``.
2049 2049
2050 2050 hg add/remove/copy/rename work as usual, though you might want to
2051 2051 use git-style patches (-g/--git or [diff] git=1) to track copies
2052 2052 and renames. See the diffs help topic for more information on the
2053 2053 git diff format.
2054 2054 """
2055 2055 q = repo.mq
2056 2056 message = cmdutil.logmessage(opts)
2057 2057 if opts['edit']:
2058 2058 if not q.applied:
2059 2059 ui.write(_("no patches applied\n"))
2060 2060 return 1
2061 2061 if message:
2062 2062 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2063 2063 patch = q.applied[-1].name
2064 2064 ph = patchheader(q.join(patch), q.plainmode)
2065 2065 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2066 2066 # We don't want to lose the patch message if qrefresh fails (issue2062)
2067 2067 msgfile = repo.opener('last-message.txt', 'wb')
2068 2068 msgfile.write(message)
2069 2069 msgfile.close()
2070 2070 setupheaderopts(ui, opts)
2071 2071 ret = q.refresh(repo, pats, msg=message, **opts)
2072 2072 q.save_dirty()
2073 2073 return ret
2074 2074
2075 2075 def diff(ui, repo, *pats, **opts):
2076 2076 """diff of the current patch and subsequent modifications
2077 2077
2078 2078 Shows a diff which includes the current patch as well as any
2079 2079 changes which have been made in the working directory since the
2080 2080 last refresh (thus showing what the current patch would become
2081 2081 after a qrefresh).
2082 2082
2083 2083 Use :hg:`diff` if you only want to see the changes made since the
2084 2084 last qrefresh, or :hg:`export qtip` if you want to see changes
2085 2085 made by the current patch without including changes made since the
2086 2086 qrefresh.
2087 2087 """
2088 2088 repo.mq.diff(repo, pats, opts)
2089 2089 return 0
2090 2090
2091 2091 def fold(ui, repo, *files, **opts):
2092 2092 """fold the named patches into the current patch
2093 2093
2094 2094 Patches must not yet be applied. Each patch will be successively
2095 2095 applied to the current patch in the order given. If all the
2096 2096 patches apply successfully, the current patch will be refreshed
2097 2097 with the new cumulative patch, and the folded patches will be
2098 2098 deleted. With -k/--keep, the folded patch files will not be
2099 2099 removed afterwards.
2100 2100
2101 2101 The header for each folded patch will be concatenated with the
2102 2102 current patch header, separated by a line of '* * *'."""
2103 2103
2104 2104 q = repo.mq
2105 2105
2106 2106 if not files:
2107 2107 raise util.Abort(_('qfold requires at least one patch name'))
2108 2108 if not q.check_toppatch(repo)[0]:
2109 2109 raise util.Abort(_('no patches applied'))
2110 2110 q.check_localchanges(repo)
2111 2111
2112 2112 message = cmdutil.logmessage(opts)
2113 2113 if opts['edit']:
2114 2114 if message:
2115 2115 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2116 2116
2117 2117 parent = q.lookup('qtip')
2118 2118 patches = []
2119 2119 messages = []
2120 2120 for f in files:
2121 2121 p = q.lookup(f)
2122 2122 if p in patches or p == parent:
2123 2123 ui.warn(_('Skipping already folded patch %s\n') % p)
2124 2124 if q.isapplied(p):
2125 2125 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2126 2126 patches.append(p)
2127 2127
2128 2128 for p in patches:
2129 2129 if not message:
2130 2130 ph = patchheader(q.join(p), q.plainmode)
2131 2131 if ph.message:
2132 2132 messages.append(ph.message)
2133 2133 pf = q.join(p)
2134 2134 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2135 2135 if not patchsuccess:
2136 2136 raise util.Abort(_('error folding patch %s') % p)
2137 patch.updatedir(ui, repo, files)
2137 cmdutil.updatedir(ui, repo, files)
2138 2138
2139 2139 if not message:
2140 2140 ph = patchheader(q.join(parent), q.plainmode)
2141 2141 message, user = ph.message, ph.user
2142 2142 for msg in messages:
2143 2143 message.append('* * *')
2144 2144 message.extend(msg)
2145 2145 message = '\n'.join(message)
2146 2146
2147 2147 if opts['edit']:
2148 2148 message = ui.edit(message, user or ui.username())
2149 2149
2150 2150 diffopts = q.patchopts(q.diffopts(), *patches)
2151 2151 q.refresh(repo, msg=message, git=diffopts.git)
2152 2152 q.delete(repo, patches, opts)
2153 2153 q.save_dirty()
2154 2154
2155 2155 def goto(ui, repo, patch, **opts):
2156 2156 '''push or pop patches until named patch is at top of stack'''
2157 2157 q = repo.mq
2158 2158 patch = q.lookup(patch)
2159 2159 if q.isapplied(patch):
2160 2160 ret = q.pop(repo, patch, force=opts['force'])
2161 2161 else:
2162 2162 ret = q.push(repo, patch, force=opts['force'])
2163 2163 q.save_dirty()
2164 2164 return ret
2165 2165
2166 2166 def guard(ui, repo, *args, **opts):
2167 2167 '''set or print guards for a patch
2168 2168
2169 2169 Guards control whether a patch can be pushed. A patch with no
2170 2170 guards is always pushed. A patch with a positive guard ("+foo") is
2171 2171 pushed only if the :hg:`qselect` command has activated it. A patch with
2172 2172 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2173 2173 has activated it.
2174 2174
2175 2175 With no arguments, print the currently active guards.
2176 2176 With arguments, set guards for the named patch.
2177 2177 NOTE: Specifying negative guards now requires '--'.
2178 2178
2179 2179 To set guards on another patch::
2180 2180
2181 2181 hg qguard other.patch -- +2.6.17 -stable
2182 2182 '''
2183 2183 def status(idx):
2184 2184 guards = q.series_guards[idx] or ['unguarded']
2185 2185 if q.series[idx] in applied:
2186 2186 state = 'applied'
2187 2187 elif q.pushable(idx)[0]:
2188 2188 state = 'unapplied'
2189 2189 else:
2190 2190 state = 'guarded'
2191 2191 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2192 2192 ui.write('%s: ' % ui.label(q.series[idx], label))
2193 2193
2194 2194 for i, guard in enumerate(guards):
2195 2195 if guard.startswith('+'):
2196 2196 ui.write(guard, label='qguard.positive')
2197 2197 elif guard.startswith('-'):
2198 2198 ui.write(guard, label='qguard.negative')
2199 2199 else:
2200 2200 ui.write(guard, label='qguard.unguarded')
2201 2201 if i != len(guards) - 1:
2202 2202 ui.write(' ')
2203 2203 ui.write('\n')
2204 2204 q = repo.mq
2205 2205 applied = set(p.name for p in q.applied)
2206 2206 patch = None
2207 2207 args = list(args)
2208 2208 if opts['list']:
2209 2209 if args or opts['none']:
2210 2210 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2211 2211 for i in xrange(len(q.series)):
2212 2212 status(i)
2213 2213 return
2214 2214 if not args or args[0][0:1] in '-+':
2215 2215 if not q.applied:
2216 2216 raise util.Abort(_('no patches applied'))
2217 2217 patch = q.applied[-1].name
2218 2218 if patch is None and args[0][0:1] not in '-+':
2219 2219 patch = args.pop(0)
2220 2220 if patch is None:
2221 2221 raise util.Abort(_('no patch to work with'))
2222 2222 if args or opts['none']:
2223 2223 idx = q.find_series(patch)
2224 2224 if idx is None:
2225 2225 raise util.Abort(_('no patch named %s') % patch)
2226 2226 q.set_guards(idx, args)
2227 2227 q.save_dirty()
2228 2228 else:
2229 2229 status(q.series.index(q.lookup(patch)))
2230 2230
2231 2231 def header(ui, repo, patch=None):
2232 2232 """print the header of the topmost or specified patch"""
2233 2233 q = repo.mq
2234 2234
2235 2235 if patch:
2236 2236 patch = q.lookup(patch)
2237 2237 else:
2238 2238 if not q.applied:
2239 2239 ui.write(_('no patches applied\n'))
2240 2240 return 1
2241 2241 patch = q.lookup('qtip')
2242 2242 ph = patchheader(q.join(patch), q.plainmode)
2243 2243
2244 2244 ui.write('\n'.join(ph.message) + '\n')
2245 2245
2246 2246 def lastsavename(path):
2247 2247 (directory, base) = os.path.split(path)
2248 2248 names = os.listdir(directory)
2249 2249 namere = re.compile("%s.([0-9]+)" % base)
2250 2250 maxindex = None
2251 2251 maxname = None
2252 2252 for f in names:
2253 2253 m = namere.match(f)
2254 2254 if m:
2255 2255 index = int(m.group(1))
2256 2256 if maxindex is None or index > maxindex:
2257 2257 maxindex = index
2258 2258 maxname = f
2259 2259 if maxname:
2260 2260 return (os.path.join(directory, maxname), maxindex)
2261 2261 return (None, None)
2262 2262
2263 2263 def savename(path):
2264 2264 (last, index) = lastsavename(path)
2265 2265 if last is None:
2266 2266 index = 0
2267 2267 newpath = path + ".%d" % (index + 1)
2268 2268 return newpath
2269 2269
2270 2270 def push(ui, repo, patch=None, **opts):
2271 2271 """push the next patch onto the stack
2272 2272
2273 2273 When -f/--force is applied, all local changes in patched files
2274 2274 will be lost.
2275 2275 """
2276 2276 q = repo.mq
2277 2277 mergeq = None
2278 2278
2279 2279 if opts['merge']:
2280 2280 if opts['name']:
2281 2281 newpath = repo.join(opts['name'])
2282 2282 else:
2283 2283 newpath, i = lastsavename(q.path)
2284 2284 if not newpath:
2285 2285 ui.warn(_("no saved queues found, please use -n\n"))
2286 2286 return 1
2287 2287 mergeq = queue(ui, repo.join(""), newpath)
2288 2288 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2289 2289 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2290 2290 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'))
2291 2291 return ret
2292 2292
2293 2293 def pop(ui, repo, patch=None, **opts):
2294 2294 """pop the current patch off the stack
2295 2295
2296 2296 By default, pops off the top of the patch stack. If given a patch
2297 2297 name, keeps popping off patches until the named patch is at the
2298 2298 top of the stack.
2299 2299 """
2300 2300 localupdate = True
2301 2301 if opts['name']:
2302 2302 q = queue(ui, repo.join(""), repo.join(opts['name']))
2303 2303 ui.warn(_('using patch queue: %s\n') % q.path)
2304 2304 localupdate = False
2305 2305 else:
2306 2306 q = repo.mq
2307 2307 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2308 2308 all=opts['all'])
2309 2309 q.save_dirty()
2310 2310 return ret
2311 2311
2312 2312 def rename(ui, repo, patch, name=None, **opts):
2313 2313 """rename a patch
2314 2314
2315 2315 With one argument, renames the current patch to PATCH1.
2316 2316 With two arguments, renames PATCH1 to PATCH2."""
2317 2317
2318 2318 q = repo.mq
2319 2319
2320 2320 if not name:
2321 2321 name = patch
2322 2322 patch = None
2323 2323
2324 2324 if patch:
2325 2325 patch = q.lookup(patch)
2326 2326 else:
2327 2327 if not q.applied:
2328 2328 ui.write(_('no patches applied\n'))
2329 2329 return
2330 2330 patch = q.lookup('qtip')
2331 2331 absdest = q.join(name)
2332 2332 if os.path.isdir(absdest):
2333 2333 name = normname(os.path.join(name, os.path.basename(patch)))
2334 2334 absdest = q.join(name)
2335 2335 if os.path.exists(absdest):
2336 2336 raise util.Abort(_('%s already exists') % absdest)
2337 2337
2338 2338 if name in q.series:
2339 2339 raise util.Abort(
2340 2340 _('A patch named %s already exists in the series file') % name)
2341 2341
2342 2342 ui.note(_('renaming %s to %s\n') % (patch, name))
2343 2343 i = q.find_series(patch)
2344 2344 guards = q.guard_re.findall(q.full_series[i])
2345 2345 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2346 2346 q.parse_series()
2347 2347 q.series_dirty = 1
2348 2348
2349 2349 info = q.isapplied(patch)
2350 2350 if info:
2351 2351 q.applied[info[0]] = statusentry(info[1], name)
2352 2352 q.applied_dirty = 1
2353 2353
2354 2354 destdir = os.path.dirname(absdest)
2355 2355 if not os.path.isdir(destdir):
2356 2356 os.makedirs(destdir)
2357 2357 util.rename(q.join(patch), absdest)
2358 2358 r = q.qrepo()
2359 2359 if r:
2360 2360 wctx = r[None]
2361 2361 wlock = r.wlock()
2362 2362 try:
2363 2363 if r.dirstate[patch] == 'a':
2364 2364 r.dirstate.forget(patch)
2365 2365 r.dirstate.add(name)
2366 2366 else:
2367 2367 if r.dirstate[name] == 'r':
2368 2368 wctx.undelete([name])
2369 2369 wctx.copy(patch, name)
2370 2370 wctx.remove([patch], False)
2371 2371 finally:
2372 2372 wlock.release()
2373 2373
2374 2374 q.save_dirty()
2375 2375
2376 2376 def restore(ui, repo, rev, **opts):
2377 2377 """restore the queue state saved by a revision (DEPRECATED)
2378 2378
2379 2379 This command is deprecated, use rebase --mq instead."""
2380 2380 rev = repo.lookup(rev)
2381 2381 q = repo.mq
2382 2382 q.restore(repo, rev, delete=opts['delete'],
2383 2383 qupdate=opts['update'])
2384 2384 q.save_dirty()
2385 2385 return 0
2386 2386
2387 2387 def save(ui, repo, **opts):
2388 2388 """save current queue state (DEPRECATED)
2389 2389
2390 2390 This command is deprecated, use rebase --mq instead."""
2391 2391 q = repo.mq
2392 2392 message = cmdutil.logmessage(opts)
2393 2393 ret = q.save(repo, msg=message)
2394 2394 if ret:
2395 2395 return ret
2396 2396 q.save_dirty()
2397 2397 if opts['copy']:
2398 2398 path = q.path
2399 2399 if opts['name']:
2400 2400 newpath = os.path.join(q.basepath, opts['name'])
2401 2401 if os.path.exists(newpath):
2402 2402 if not os.path.isdir(newpath):
2403 2403 raise util.Abort(_('destination %s exists and is not '
2404 2404 'a directory') % newpath)
2405 2405 if not opts['force']:
2406 2406 raise util.Abort(_('destination %s exists, '
2407 2407 'use -f to force') % newpath)
2408 2408 else:
2409 2409 newpath = savename(path)
2410 2410 ui.warn(_("copy %s to %s\n") % (path, newpath))
2411 2411 util.copyfiles(path, newpath)
2412 2412 if opts['empty']:
2413 2413 try:
2414 2414 os.unlink(q.join(q.status_path))
2415 2415 except:
2416 2416 pass
2417 2417 return 0
2418 2418
2419 2419 def strip(ui, repo, *revs, **opts):
2420 2420 """strip changesets and all their descendants from the repository
2421 2421
2422 2422 The strip command removes the specified changesets and all their
2423 2423 descendants. If the working directory has uncommitted changes,
2424 2424 the operation is aborted unless the --force flag is supplied.
2425 2425
2426 2426 If a parent of the working directory is stripped, then the working
2427 2427 directory will automatically be updated to the most recent
2428 2428 available ancestor of the stripped parent after the operation
2429 2429 completes.
2430 2430
2431 2431 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2432 2432 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2433 2433 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2434 2434 where BUNDLE is the bundle file created by the strip. Note that
2435 2435 the local revision numbers will in general be different after the
2436 2436 restore.
2437 2437
2438 2438 Use the --nobackup option to discard the backup bundle once the
2439 2439 operation completes.
2440 2440 """
2441 2441 backup = 'all'
2442 2442 if opts['backup']:
2443 2443 backup = 'strip'
2444 2444 elif opts['nobackup']:
2445 2445 backup = 'none'
2446 2446
2447 2447 cl = repo.changelog
2448 2448 revs = set(cl.rev(repo.lookup(r)) for r in revs)
2449 2449
2450 2450 descendants = set(cl.descendants(*revs))
2451 2451 strippedrevs = revs.union(descendants)
2452 2452 roots = revs.difference(descendants)
2453 2453
2454 2454 update = False
2455 2455 # if one of the wdir parent is stripped we'll need
2456 2456 # to update away to an earlier revision
2457 2457 for p in repo.dirstate.parents():
2458 2458 if p != nullid and cl.rev(p) in strippedrevs:
2459 2459 update = True
2460 2460 break
2461 2461
2462 2462 rootnodes = set(cl.node(r) for r in roots)
2463 2463
2464 2464 q = repo.mq
2465 2465 if q.applied:
2466 2466 # refresh queue state if we're about to strip
2467 2467 # applied patches
2468 2468 if cl.rev(repo.lookup('qtip')) in strippedrevs:
2469 2469 q.applied_dirty = True
2470 2470 start = 0
2471 2471 end = len(q.applied)
2472 2472 for i, statusentry in enumerate(q.applied):
2473 2473 if statusentry.node in rootnodes:
2474 2474 # if one of the stripped roots is an applied
2475 2475 # patch, only part of the queue is stripped
2476 2476 start = i
2477 2477 break
2478 2478 del q.applied[start:end]
2479 2479 q.save_dirty()
2480 2480
2481 2481 repo.mq.strip(repo, list(rootnodes), backup=backup, update=update,
2482 2482 force=opts['force'])
2483 2483 return 0
2484 2484
2485 2485 def select(ui, repo, *args, **opts):
2486 2486 '''set or print guarded patches to push
2487 2487
2488 2488 Use the :hg:`qguard` command to set or print guards on patch, then use
2489 2489 qselect to tell mq which guards to use. A patch will be pushed if
2490 2490 it has no guards or any positive guards match the currently
2491 2491 selected guard, but will not be pushed if any negative guards
2492 2492 match the current guard. For example::
2493 2493
2494 2494 qguard foo.patch -stable (negative guard)
2495 2495 qguard bar.patch +stable (positive guard)
2496 2496 qselect stable
2497 2497
2498 2498 This activates the "stable" guard. mq will skip foo.patch (because
2499 2499 it has a negative match) but push bar.patch (because it has a
2500 2500 positive match).
2501 2501
2502 2502 With no arguments, prints the currently active guards.
2503 2503 With one argument, sets the active guard.
2504 2504
2505 2505 Use -n/--none to deactivate guards (no other arguments needed).
2506 2506 When no guards are active, patches with positive guards are
2507 2507 skipped and patches with negative guards are pushed.
2508 2508
2509 2509 qselect can change the guards on applied patches. It does not pop
2510 2510 guarded patches by default. Use --pop to pop back to the last
2511 2511 applied patch that is not guarded. Use --reapply (which implies
2512 2512 --pop) to push back to the current patch afterwards, but skip
2513 2513 guarded patches.
2514 2514
2515 2515 Use -s/--series to print a list of all guards in the series file
2516 2516 (no other arguments needed). Use -v for more information.'''
2517 2517
2518 2518 q = repo.mq
2519 2519 guards = q.active()
2520 2520 if args or opts['none']:
2521 2521 old_unapplied = q.unapplied(repo)
2522 2522 old_guarded = [i for i in xrange(len(q.applied)) if
2523 2523 not q.pushable(i)[0]]
2524 2524 q.set_active(args)
2525 2525 q.save_dirty()
2526 2526 if not args:
2527 2527 ui.status(_('guards deactivated\n'))
2528 2528 if not opts['pop'] and not opts['reapply']:
2529 2529 unapplied = q.unapplied(repo)
2530 2530 guarded = [i for i in xrange(len(q.applied))
2531 2531 if not q.pushable(i)[0]]
2532 2532 if len(unapplied) != len(old_unapplied):
2533 2533 ui.status(_('number of unguarded, unapplied patches has '
2534 2534 'changed from %d to %d\n') %
2535 2535 (len(old_unapplied), len(unapplied)))
2536 2536 if len(guarded) != len(old_guarded):
2537 2537 ui.status(_('number of guarded, applied patches has changed '
2538 2538 'from %d to %d\n') %
2539 2539 (len(old_guarded), len(guarded)))
2540 2540 elif opts['series']:
2541 2541 guards = {}
2542 2542 noguards = 0
2543 2543 for gs in q.series_guards:
2544 2544 if not gs:
2545 2545 noguards += 1
2546 2546 for g in gs:
2547 2547 guards.setdefault(g, 0)
2548 2548 guards[g] += 1
2549 2549 if ui.verbose:
2550 2550 guards['NONE'] = noguards
2551 2551 guards = guards.items()
2552 2552 guards.sort(key=lambda x: x[0][1:])
2553 2553 if guards:
2554 2554 ui.note(_('guards in series file:\n'))
2555 2555 for guard, count in guards:
2556 2556 ui.note('%2d ' % count)
2557 2557 ui.write(guard, '\n')
2558 2558 else:
2559 2559 ui.note(_('no guards in series file\n'))
2560 2560 else:
2561 2561 if guards:
2562 2562 ui.note(_('active guards:\n'))
2563 2563 for g in guards:
2564 2564 ui.write(g, '\n')
2565 2565 else:
2566 2566 ui.write(_('no active guards\n'))
2567 2567 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2568 2568 popped = False
2569 2569 if opts['pop'] or opts['reapply']:
2570 2570 for i in xrange(len(q.applied)):
2571 2571 pushable, reason = q.pushable(i)
2572 2572 if not pushable:
2573 2573 ui.status(_('popping guarded patches\n'))
2574 2574 popped = True
2575 2575 if i == 0:
2576 2576 q.pop(repo, all=True)
2577 2577 else:
2578 2578 q.pop(repo, i - 1)
2579 2579 break
2580 2580 if popped:
2581 2581 try:
2582 2582 if reapply:
2583 2583 ui.status(_('reapplying unguarded patches\n'))
2584 2584 q.push(repo, reapply)
2585 2585 finally:
2586 2586 q.save_dirty()
2587 2587
2588 2588 def finish(ui, repo, *revrange, **opts):
2589 2589 """move applied patches into repository history
2590 2590
2591 2591 Finishes the specified revisions (corresponding to applied
2592 2592 patches) by moving them out of mq control into regular repository
2593 2593 history.
2594 2594
2595 2595 Accepts a revision range or the -a/--applied option. If --applied
2596 2596 is specified, all applied mq revisions are removed from mq
2597 2597 control. Otherwise, the given revisions must be at the base of the
2598 2598 stack of applied patches.
2599 2599
2600 2600 This can be especially useful if your changes have been applied to
2601 2601 an upstream repository, or if you are about to push your changes
2602 2602 to upstream.
2603 2603 """
2604 2604 if not opts['applied'] and not revrange:
2605 2605 raise util.Abort(_('no revisions specified'))
2606 2606 elif opts['applied']:
2607 2607 revrange = ('qbase::qtip',) + revrange
2608 2608
2609 2609 q = repo.mq
2610 2610 if not q.applied:
2611 2611 ui.status(_('no patches applied\n'))
2612 2612 return 0
2613 2613
2614 2614 revs = cmdutil.revrange(repo, revrange)
2615 2615 q.finish(repo, revs)
2616 2616 q.save_dirty()
2617 2617 return 0
2618 2618
2619 2619 def qqueue(ui, repo, name=None, **opts):
2620 2620 '''manage multiple patch queues
2621 2621
2622 2622 Supports switching between different patch queues, as well as creating
2623 2623 new patch queues and deleting existing ones.
2624 2624
2625 2625 Omitting a queue name or specifying -l/--list will show you the registered
2626 2626 queues - by default the "normal" patches queue is registered. The currently
2627 2627 active queue will be marked with "(active)".
2628 2628
2629 2629 To create a new queue, use -c/--create. The queue is automatically made
2630 2630 active, except in the case where there are applied patches from the
2631 2631 currently active queue in the repository. Then the queue will only be
2632 2632 created and switching will fail.
2633 2633
2634 2634 To delete an existing queue, use --delete. You cannot delete the currently
2635 2635 active queue.
2636 2636 '''
2637 2637
2638 2638 q = repo.mq
2639 2639
2640 2640 _defaultqueue = 'patches'
2641 2641 _allqueues = 'patches.queues'
2642 2642 _activequeue = 'patches.queue'
2643 2643
2644 2644 def _getcurrent():
2645 2645 cur = os.path.basename(q.path)
2646 2646 if cur.startswith('patches-'):
2647 2647 cur = cur[8:]
2648 2648 return cur
2649 2649
2650 2650 def _noqueues():
2651 2651 try:
2652 2652 fh = repo.opener(_allqueues, 'r')
2653 2653 fh.close()
2654 2654 except IOError:
2655 2655 return True
2656 2656
2657 2657 return False
2658 2658
2659 2659 def _getqueues():
2660 2660 current = _getcurrent()
2661 2661
2662 2662 try:
2663 2663 fh = repo.opener(_allqueues, 'r')
2664 2664 queues = [queue.strip() for queue in fh if queue.strip()]
2665 2665 if current not in queues:
2666 2666 queues.append(current)
2667 2667 except IOError:
2668 2668 queues = [_defaultqueue]
2669 2669
2670 2670 return sorted(queues)
2671 2671
2672 2672 def _setactive(name):
2673 2673 if q.applied:
2674 2674 raise util.Abort(_('patches applied - cannot set new queue active'))
2675 2675 _setactivenocheck(name)
2676 2676
2677 2677 def _setactivenocheck(name):
2678 2678 fh = repo.opener(_activequeue, 'w')
2679 2679 if name != 'patches':
2680 2680 fh.write(name)
2681 2681 fh.close()
2682 2682
2683 2683 def _addqueue(name):
2684 2684 fh = repo.opener(_allqueues, 'a')
2685 2685 fh.write('%s\n' % (name,))
2686 2686 fh.close()
2687 2687
2688 2688 def _queuedir(name):
2689 2689 if name == 'patches':
2690 2690 return repo.join('patches')
2691 2691 else:
2692 2692 return repo.join('patches-' + name)
2693 2693
2694 2694 def _validname(name):
2695 2695 for n in name:
2696 2696 if n in ':\\/.':
2697 2697 return False
2698 2698 return True
2699 2699
2700 2700 def _delete(name):
2701 2701 if name not in existing:
2702 2702 raise util.Abort(_('cannot delete queue that does not exist'))
2703 2703
2704 2704 current = _getcurrent()
2705 2705
2706 2706 if name == current:
2707 2707 raise util.Abort(_('cannot delete currently active queue'))
2708 2708
2709 2709 fh = repo.opener('patches.queues.new', 'w')
2710 2710 for queue in existing:
2711 2711 if queue == name:
2712 2712 continue
2713 2713 fh.write('%s\n' % (queue,))
2714 2714 fh.close()
2715 2715 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2716 2716
2717 2717 if not name or opts.get('list'):
2718 2718 current = _getcurrent()
2719 2719 for queue in _getqueues():
2720 2720 ui.write('%s' % (queue,))
2721 2721 if queue == current and not ui.quiet:
2722 2722 ui.write(_(' (active)\n'))
2723 2723 else:
2724 2724 ui.write('\n')
2725 2725 return
2726 2726
2727 2727 if not _validname(name):
2728 2728 raise util.Abort(
2729 2729 _('invalid queue name, may not contain the characters ":\\/."'))
2730 2730
2731 2731 existing = _getqueues()
2732 2732
2733 2733 if opts.get('create'):
2734 2734 if name in existing:
2735 2735 raise util.Abort(_('queue "%s" already exists') % name)
2736 2736 if _noqueues():
2737 2737 _addqueue(_defaultqueue)
2738 2738 _addqueue(name)
2739 2739 _setactive(name)
2740 2740 elif opts.get('rename'):
2741 2741 current = _getcurrent()
2742 2742 if name == current:
2743 2743 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
2744 2744 if name in existing:
2745 2745 raise util.Abort(_('queue "%s" already exists') % name)
2746 2746
2747 2747 olddir = _queuedir(current)
2748 2748 newdir = _queuedir(name)
2749 2749
2750 2750 if os.path.exists(newdir):
2751 2751 raise util.Abort(_('non-queue directory "%s" already exists') %
2752 2752 newdir)
2753 2753
2754 2754 fh = repo.opener('patches.queues.new', 'w')
2755 2755 for queue in existing:
2756 2756 if queue == current:
2757 2757 fh.write('%s\n' % (name,))
2758 2758 if os.path.exists(olddir):
2759 2759 util.rename(olddir, newdir)
2760 2760 else:
2761 2761 fh.write('%s\n' % (queue,))
2762 2762 fh.close()
2763 2763 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2764 2764 _setactivenocheck(name)
2765 2765 elif opts.get('delete'):
2766 2766 _delete(name)
2767 2767 elif opts.get('purge'):
2768 2768 if name in existing:
2769 2769 _delete(name)
2770 2770 qdir = _queuedir(name)
2771 2771 if os.path.exists(qdir):
2772 2772 shutil.rmtree(qdir)
2773 2773 else:
2774 2774 if name not in existing:
2775 2775 raise util.Abort(_('use --create to create a new queue'))
2776 2776 _setactive(name)
2777 2777
2778 2778 def reposetup(ui, repo):
2779 2779 class mqrepo(repo.__class__):
2780 2780 @util.propertycache
2781 2781 def mq(self):
2782 2782 return queue(self.ui, self.join(""))
2783 2783
2784 2784 def abort_if_wdir_patched(self, errmsg, force=False):
2785 2785 if self.mq.applied and not force:
2786 2786 parent = self.dirstate.parents()[0]
2787 2787 if parent in [s.node for s in self.mq.applied]:
2788 2788 raise util.Abort(errmsg)
2789 2789
2790 2790 def commit(self, text="", user=None, date=None, match=None,
2791 2791 force=False, editor=False, extra={}):
2792 2792 self.abort_if_wdir_patched(
2793 2793 _('cannot commit over an applied mq patch'),
2794 2794 force)
2795 2795
2796 2796 return super(mqrepo, self).commit(text, user, date, match, force,
2797 2797 editor, extra)
2798 2798
2799 2799 def push(self, remote, force=False, revs=None, newbranch=False):
2800 2800 if self.mq.applied and not force and not revs:
2801 2801 raise util.Abort(_('source has mq patches applied'))
2802 2802 return super(mqrepo, self).push(remote, force, revs, newbranch)
2803 2803
2804 2804 def _findtags(self):
2805 2805 '''augment tags from base class with patch tags'''
2806 2806 result = super(mqrepo, self)._findtags()
2807 2807
2808 2808 q = self.mq
2809 2809 if not q.applied:
2810 2810 return result
2811 2811
2812 2812 mqtags = [(patch.node, patch.name) for patch in q.applied]
2813 2813
2814 2814 if mqtags[-1][0] not in self.changelog.nodemap:
2815 2815 self.ui.warn(_('mq status file refers to unknown node %s\n')
2816 2816 % short(mqtags[-1][0]))
2817 2817 return result
2818 2818
2819 2819 mqtags.append((mqtags[-1][0], 'qtip'))
2820 2820 mqtags.append((mqtags[0][0], 'qbase'))
2821 2821 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2822 2822 tags = result[0]
2823 2823 for patch in mqtags:
2824 2824 if patch[1] in tags:
2825 2825 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2826 2826 % patch[1])
2827 2827 else:
2828 2828 tags[patch[1]] = patch[0]
2829 2829
2830 2830 return result
2831 2831
2832 2832 def _branchtags(self, partial, lrev):
2833 2833 q = self.mq
2834 2834 if not q.applied:
2835 2835 return super(mqrepo, self)._branchtags(partial, lrev)
2836 2836
2837 2837 cl = self.changelog
2838 2838 qbasenode = q.applied[0].node
2839 2839 if qbasenode not in cl.nodemap:
2840 2840 self.ui.warn(_('mq status file refers to unknown node %s\n')
2841 2841 % short(qbasenode))
2842 2842 return super(mqrepo, self)._branchtags(partial, lrev)
2843 2843
2844 2844 qbase = cl.rev(qbasenode)
2845 2845 start = lrev + 1
2846 2846 if start < qbase:
2847 2847 # update the cache (excluding the patches) and save it
2848 2848 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
2849 2849 self._updatebranchcache(partial, ctxgen)
2850 2850 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
2851 2851 start = qbase
2852 2852 # if start = qbase, the cache is as updated as it should be.
2853 2853 # if start > qbase, the cache includes (part of) the patches.
2854 2854 # we might as well use it, but we won't save it.
2855 2855
2856 2856 # update the cache up to the tip
2857 2857 ctxgen = (self[r] for r in xrange(start, len(cl)))
2858 2858 self._updatebranchcache(partial, ctxgen)
2859 2859
2860 2860 return partial
2861 2861
2862 2862 if repo.local():
2863 2863 repo.__class__ = mqrepo
2864 2864
2865 2865 def mqimport(orig, ui, repo, *args, **kwargs):
2866 2866 if (hasattr(repo, 'abort_if_wdir_patched')
2867 2867 and not kwargs.get('no_commit', False)):
2868 2868 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2869 2869 kwargs.get('force'))
2870 2870 return orig(ui, repo, *args, **kwargs)
2871 2871
2872 2872 def mqinit(orig, ui, *args, **kwargs):
2873 2873 mq = kwargs.pop('mq', None)
2874 2874
2875 2875 if not mq:
2876 2876 return orig(ui, *args, **kwargs)
2877 2877
2878 2878 if args:
2879 2879 repopath = args[0]
2880 2880 if not hg.islocal(repopath):
2881 2881 raise util.Abort(_('only a local queue repository '
2882 2882 'may be initialized'))
2883 2883 else:
2884 2884 repopath = cmdutil.findrepo(os.getcwd())
2885 2885 if not repopath:
2886 2886 raise util.Abort(_('there is no Mercurial repository here '
2887 2887 '(.hg not found)'))
2888 2888 repo = hg.repository(ui, repopath)
2889 2889 return qinit(ui, repo, True)
2890 2890
2891 2891 def mqcommand(orig, ui, repo, *args, **kwargs):
2892 2892 """Add --mq option to operate on patch repository instead of main"""
2893 2893
2894 2894 # some commands do not like getting unknown options
2895 2895 mq = kwargs.pop('mq', None)
2896 2896
2897 2897 if not mq:
2898 2898 return orig(ui, repo, *args, **kwargs)
2899 2899
2900 2900 q = repo.mq
2901 2901 r = q.qrepo()
2902 2902 if not r:
2903 2903 raise util.Abort(_('no queue repository'))
2904 2904 return orig(r.ui, r, *args, **kwargs)
2905 2905
2906 2906 def summary(orig, ui, repo, *args, **kwargs):
2907 2907 r = orig(ui, repo, *args, **kwargs)
2908 2908 q = repo.mq
2909 2909 m = []
2910 2910 a, u = len(q.applied), len(q.unapplied(repo))
2911 2911 if a:
2912 2912 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
2913 2913 if u:
2914 2914 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
2915 2915 if m:
2916 2916 ui.write("mq: %s\n" % ', '.join(m))
2917 2917 else:
2918 2918 ui.note(_("mq: (empty queue)\n"))
2919 2919 return r
2920 2920
2921 2921 def uisetup(ui):
2922 2922 mqopt = [('', 'mq', None, _("operate on patch repository"))]
2923 2923
2924 2924 extensions.wrapcommand(commands.table, 'import', mqimport)
2925 2925 extensions.wrapcommand(commands.table, 'summary', summary)
2926 2926
2927 2927 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
2928 2928 entry[1].extend(mqopt)
2929 2929
2930 2930 nowrap = set(commands.norepo.split(" ") + ['qrecord'])
2931 2931
2932 2932 def dotable(cmdtable):
2933 2933 for cmd in cmdtable.keys():
2934 2934 cmd = cmdutil.parsealiases(cmd)[0]
2935 2935 if cmd in nowrap:
2936 2936 continue
2937 2937 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
2938 2938 entry[1].extend(mqopt)
2939 2939
2940 2940 dotable(commands.table)
2941 2941
2942 2942 for extname, extmodule in extensions.extensions():
2943 2943 if extmodule.__file__ != __file__:
2944 2944 dotable(getattr(extmodule, 'cmdtable', {}))
2945 2945
2946 2946 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2947 2947
2948 2948 cmdtable = {
2949 2949 "qapplied":
2950 2950 (applied,
2951 2951 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
2952 2952 _('hg qapplied [-1] [-s] [PATCH]')),
2953 2953 "qclone":
2954 2954 (clone,
2955 2955 [('', 'pull', None, _('use pull protocol to copy metadata')),
2956 2956 ('U', 'noupdate', None, _('do not update the new working directories')),
2957 2957 ('', 'uncompressed', None,
2958 2958 _('use uncompressed transfer (fast over LAN)')),
2959 2959 ('p', 'patches', '',
2960 2960 _('location of source patch repository'), _('REPO')),
2961 2961 ] + commands.remoteopts,
2962 2962 _('hg qclone [OPTION]... SOURCE [DEST]')),
2963 2963 "qcommit|qci":
2964 2964 (commit,
2965 2965 commands.table["^commit|ci"][1],
2966 2966 _('hg qcommit [OPTION]... [FILE]...')),
2967 2967 "^qdiff":
2968 2968 (diff,
2969 2969 commands.diffopts + commands.diffopts2 + commands.walkopts,
2970 2970 _('hg qdiff [OPTION]... [FILE]...')),
2971 2971 "qdelete|qremove|qrm":
2972 2972 (delete,
2973 2973 [('k', 'keep', None, _('keep patch file')),
2974 2974 ('r', 'rev', [],
2975 2975 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2976 2976 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2977 2977 'qfold':
2978 2978 (fold,
2979 2979 [('e', 'edit', None, _('edit patch header')),
2980 2980 ('k', 'keep', None, _('keep folded patch files')),
2981 2981 ] + commands.commitopts,
2982 2982 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2983 2983 'qgoto':
2984 2984 (goto,
2985 2985 [('f', 'force', None, _('overwrite any local changes'))],
2986 2986 _('hg qgoto [OPTION]... PATCH')),
2987 2987 'qguard':
2988 2988 (guard,
2989 2989 [('l', 'list', None, _('list all patches and guards')),
2990 2990 ('n', 'none', None, _('drop all guards'))],
2991 2991 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')),
2992 2992 'qheader': (header, [], _('hg qheader [PATCH]')),
2993 2993 "qimport":
2994 2994 (qimport,
2995 2995 [('e', 'existing', None, _('import file in patch directory')),
2996 2996 ('n', 'name', '',
2997 2997 _('name of patch file'), _('NAME')),
2998 2998 ('f', 'force', None, _('overwrite existing files')),
2999 2999 ('r', 'rev', [],
3000 3000 _('place existing revisions under mq control'), _('REV')),
3001 3001 ('g', 'git', None, _('use git extended diff format')),
3002 3002 ('P', 'push', None, _('qpush after importing'))],
3003 3003 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
3004 3004 "^qinit":
3005 3005 (init,
3006 3006 [('c', 'create-repo', None, _('create queue repository'))],
3007 3007 _('hg qinit [-c]')),
3008 3008 "^qnew":
3009 3009 (new,
3010 3010 [('e', 'edit', None, _('edit commit message')),
3011 3011 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
3012 3012 ('g', 'git', None, _('use git extended diff format')),
3013 3013 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
3014 3014 ('u', 'user', '',
3015 3015 _('add "From: <USER>" to patch'), _('USER')),
3016 3016 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
3017 3017 ('d', 'date', '',
3018 3018 _('add "Date: <DATE>" to patch'), _('DATE'))
3019 3019 ] + commands.walkopts + commands.commitopts,
3020 3020 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...')),
3021 3021 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
3022 3022 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
3023 3023 "^qpop":
3024 3024 (pop,
3025 3025 [('a', 'all', None, _('pop all patches')),
3026 3026 ('n', 'name', '',
3027 3027 _('queue name to pop (DEPRECATED)'), _('NAME')),
3028 3028 ('f', 'force', None, _('forget any local changes to patched files'))],
3029 3029 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
3030 3030 "^qpush":
3031 3031 (push,
3032 3032 [('f', 'force', None, _('apply if the patch has rejects')),
3033 3033 ('l', 'list', None, _('list patch name in commit text')),
3034 3034 ('a', 'all', None, _('apply all patches')),
3035 3035 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
3036 3036 ('n', 'name', '',
3037 3037 _('merge queue name (DEPRECATED)'), _('NAME')),
3038 3038 ('', 'move', None, _('reorder patch series and apply only the patch'))],
3039 3039 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [--move] [PATCH | INDEX]')),
3040 3040 "^qrefresh":
3041 3041 (refresh,
3042 3042 [('e', 'edit', None, _('edit commit message')),
3043 3043 ('g', 'git', None, _('use git extended diff format')),
3044 3044 ('s', 'short', None,
3045 3045 _('refresh only files already in the patch and specified files')),
3046 3046 ('U', 'currentuser', None,
3047 3047 _('add/update author field in patch with current user')),
3048 3048 ('u', 'user', '',
3049 3049 _('add/update author field in patch with given user'), _('USER')),
3050 3050 ('D', 'currentdate', None,
3051 3051 _('add/update date field in patch with current date')),
3052 3052 ('d', 'date', '',
3053 3053 _('add/update date field in patch with given date'), _('DATE'))
3054 3054 ] + commands.walkopts + commands.commitopts,
3055 3055 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
3056 3056 'qrename|qmv':
3057 3057 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
3058 3058 "qrestore":
3059 3059 (restore,
3060 3060 [('d', 'delete', None, _('delete save entry')),
3061 3061 ('u', 'update', None, _('update queue working directory'))],
3062 3062 _('hg qrestore [-d] [-u] REV')),
3063 3063 "qsave":
3064 3064 (save,
3065 3065 [('c', 'copy', None, _('copy patch directory')),
3066 3066 ('n', 'name', '',
3067 3067 _('copy directory name'), _('NAME')),
3068 3068 ('e', 'empty', None, _('clear queue status file')),
3069 3069 ('f', 'force', None, _('force copy'))] + commands.commitopts,
3070 3070 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
3071 3071 "qselect":
3072 3072 (select,
3073 3073 [('n', 'none', None, _('disable all guards')),
3074 3074 ('s', 'series', None, _('list all guards in series file')),
3075 3075 ('', 'pop', None, _('pop to before first guarded applied patch')),
3076 3076 ('', 'reapply', None, _('pop, then reapply patches'))],
3077 3077 _('hg qselect [OPTION]... [GUARD]...')),
3078 3078 "qseries":
3079 3079 (series,
3080 3080 [('m', 'missing', None, _('print patches not in series')),
3081 3081 ] + seriesopts,
3082 3082 _('hg qseries [-ms]')),
3083 3083 "strip":
3084 3084 (strip,
3085 3085 [('f', 'force', None, _('force removal of changesets even if the '
3086 3086 'working directory has uncommitted changes')),
3087 3087 ('b', 'backup', None, _('bundle only changesets with local revision'
3088 3088 ' number greater than REV which are not'
3089 3089 ' descendants of REV (DEPRECATED)')),
3090 3090 ('n', 'nobackup', None, _('no backups'))],
3091 3091 _('hg strip [-f] [-n] REV...')),
3092 3092 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
3093 3093 "qunapplied":
3094 3094 (unapplied,
3095 3095 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
3096 3096 _('hg qunapplied [-1] [-s] [PATCH]')),
3097 3097 "qfinish":
3098 3098 (finish,
3099 3099 [('a', 'applied', None, _('finish all applied changesets'))],
3100 3100 _('hg qfinish [-a] [REV]...')),
3101 3101 'qqueue':
3102 3102 (qqueue,
3103 3103 [
3104 3104 ('l', 'list', False, _('list all available queues')),
3105 3105 ('c', 'create', False, _('create new queue')),
3106 3106 ('', 'rename', False, _('rename active queue')),
3107 3107 ('', 'delete', False, _('delete reference to queue')),
3108 3108 ('', 'purge', False, _('delete queue, and remove patch dir')),
3109 3109 ],
3110 3110 _('[OPTION] [QUEUE]')),
3111 3111 }
3112 3112
3113 3113 colortable = {'qguard.negative': 'red',
3114 3114 'qguard.positive': 'yellow',
3115 3115 'qguard.unguarded': 'green',
3116 3116 'qseries.applied': 'blue bold underline',
3117 3117 'qseries.guarded': 'black bold',
3118 3118 'qseries.missing': 'red bold',
3119 3119 'qseries.unapplied': 'black bold'}
@@ -1,573 +1,573 b''
1 1 # record.py
2 2 #
3 3 # Copyright 2007 Bryan O'Sullivan <bos@serpentine.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 '''commands to interactively select changes for commit/qrefresh'''
9 9
10 10 from mercurial.i18n import gettext, _
11 11 from mercurial import cmdutil, commands, extensions, hg, mdiff, patch
12 12 from mercurial import util
13 13 import copy, cStringIO, errno, os, re, tempfile
14 14
15 15 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
16 16
17 17 def scanpatch(fp):
18 18 """like patch.iterhunks, but yield different events
19 19
20 20 - ('file', [header_lines + fromfile + tofile])
21 21 - ('context', [context_lines])
22 22 - ('hunk', [hunk_lines])
23 23 - ('range', (-start,len, +start,len, diffp))
24 24 """
25 25 lr = patch.linereader(fp)
26 26
27 27 def scanwhile(first, p):
28 28 """scan lr while predicate holds"""
29 29 lines = [first]
30 30 while True:
31 31 line = lr.readline()
32 32 if not line:
33 33 break
34 34 if p(line):
35 35 lines.append(line)
36 36 else:
37 37 lr.push(line)
38 38 break
39 39 return lines
40 40
41 41 while True:
42 42 line = lr.readline()
43 43 if not line:
44 44 break
45 45 if line.startswith('diff --git a/'):
46 46 def notheader(line):
47 47 s = line.split(None, 1)
48 48 return not s or s[0] not in ('---', 'diff')
49 49 header = scanwhile(line, notheader)
50 50 fromfile = lr.readline()
51 51 if fromfile.startswith('---'):
52 52 tofile = lr.readline()
53 53 header += [fromfile, tofile]
54 54 else:
55 55 lr.push(fromfile)
56 56 yield 'file', header
57 57 elif line[0] == ' ':
58 58 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
59 59 elif line[0] in '-+':
60 60 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
61 61 else:
62 62 m = lines_re.match(line)
63 63 if m:
64 64 yield 'range', m.groups()
65 65 else:
66 66 raise patch.PatchError('unknown patch content: %r' % line)
67 67
68 68 class header(object):
69 69 """patch header
70 70
71 71 XXX shoudn't we move this to mercurial/patch.py ?
72 72 """
73 73 diff_re = re.compile('diff --git a/(.*) b/(.*)$')
74 74 allhunks_re = re.compile('(?:index|new file|deleted file) ')
75 75 pretty_re = re.compile('(?:new file|deleted file) ')
76 76 special_re = re.compile('(?:index|new|deleted|copy|rename) ')
77 77
78 78 def __init__(self, header):
79 79 self.header = header
80 80 self.hunks = []
81 81
82 82 def binary(self):
83 83 for h in self.header:
84 84 if h.startswith('index '):
85 85 return True
86 86
87 87 def pretty(self, fp):
88 88 for h in self.header:
89 89 if h.startswith('index '):
90 90 fp.write(_('this modifies a binary file (all or nothing)\n'))
91 91 break
92 92 if self.pretty_re.match(h):
93 93 fp.write(h)
94 94 if self.binary():
95 95 fp.write(_('this is a binary file\n'))
96 96 break
97 97 if h.startswith('---'):
98 98 fp.write(_('%d hunks, %d lines changed\n') %
99 99 (len(self.hunks),
100 100 sum([max(h.added, h.removed) for h in self.hunks])))
101 101 break
102 102 fp.write(h)
103 103
104 104 def write(self, fp):
105 105 fp.write(''.join(self.header))
106 106
107 107 def allhunks(self):
108 108 for h in self.header:
109 109 if self.allhunks_re.match(h):
110 110 return True
111 111
112 112 def files(self):
113 113 fromfile, tofile = self.diff_re.match(self.header[0]).groups()
114 114 if fromfile == tofile:
115 115 return [fromfile]
116 116 return [fromfile, tofile]
117 117
118 118 def filename(self):
119 119 return self.files()[-1]
120 120
121 121 def __repr__(self):
122 122 return '<header %s>' % (' '.join(map(repr, self.files())))
123 123
124 124 def special(self):
125 125 for h in self.header:
126 126 if self.special_re.match(h):
127 127 return True
128 128
129 129 def countchanges(hunk):
130 130 """hunk -> (n+,n-)"""
131 131 add = len([h for h in hunk if h[0] == '+'])
132 132 rem = len([h for h in hunk if h[0] == '-'])
133 133 return add, rem
134 134
135 135 class hunk(object):
136 136 """patch hunk
137 137
138 138 XXX shouldn't we merge this with patch.hunk ?
139 139 """
140 140 maxcontext = 3
141 141
142 142 def __init__(self, header, fromline, toline, proc, before, hunk, after):
143 143 def trimcontext(number, lines):
144 144 delta = len(lines) - self.maxcontext
145 145 if False and delta > 0:
146 146 return number + delta, lines[:self.maxcontext]
147 147 return number, lines
148 148
149 149 self.header = header
150 150 self.fromline, self.before = trimcontext(fromline, before)
151 151 self.toline, self.after = trimcontext(toline, after)
152 152 self.proc = proc
153 153 self.hunk = hunk
154 154 self.added, self.removed = countchanges(self.hunk)
155 155
156 156 def write(self, fp):
157 157 delta = len(self.before) + len(self.after)
158 158 if self.after and self.after[-1] == '\\ No newline at end of file\n':
159 159 delta -= 1
160 160 fromlen = delta + self.removed
161 161 tolen = delta + self.added
162 162 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
163 163 (self.fromline, fromlen, self.toline, tolen,
164 164 self.proc and (' ' + self.proc)))
165 165 fp.write(''.join(self.before + self.hunk + self.after))
166 166
167 167 pretty = write
168 168
169 169 def filename(self):
170 170 return self.header.filename()
171 171
172 172 def __repr__(self):
173 173 return '<hunk %r@%d>' % (self.filename(), self.fromline)
174 174
175 175 def parsepatch(fp):
176 176 """patch -> [] of hunks """
177 177 class parser(object):
178 178 """patch parsing state machine"""
179 179 def __init__(self):
180 180 self.fromline = 0
181 181 self.toline = 0
182 182 self.proc = ''
183 183 self.header = None
184 184 self.context = []
185 185 self.before = []
186 186 self.hunk = []
187 187 self.stream = []
188 188
189 189 def addrange(self, limits):
190 190 fromstart, fromend, tostart, toend, proc = limits
191 191 self.fromline = int(fromstart)
192 192 self.toline = int(tostart)
193 193 self.proc = proc
194 194
195 195 def addcontext(self, context):
196 196 if self.hunk:
197 197 h = hunk(self.header, self.fromline, self.toline, self.proc,
198 198 self.before, self.hunk, context)
199 199 self.header.hunks.append(h)
200 200 self.stream.append(h)
201 201 self.fromline += len(self.before) + h.removed
202 202 self.toline += len(self.before) + h.added
203 203 self.before = []
204 204 self.hunk = []
205 205 self.proc = ''
206 206 self.context = context
207 207
208 208 def addhunk(self, hunk):
209 209 if self.context:
210 210 self.before = self.context
211 211 self.context = []
212 212 self.hunk = hunk
213 213
214 214 def newfile(self, hdr):
215 215 self.addcontext([])
216 216 h = header(hdr)
217 217 self.stream.append(h)
218 218 self.header = h
219 219
220 220 def finished(self):
221 221 self.addcontext([])
222 222 return self.stream
223 223
224 224 transitions = {
225 225 'file': {'context': addcontext,
226 226 'file': newfile,
227 227 'hunk': addhunk,
228 228 'range': addrange},
229 229 'context': {'file': newfile,
230 230 'hunk': addhunk,
231 231 'range': addrange},
232 232 'hunk': {'context': addcontext,
233 233 'file': newfile,
234 234 'range': addrange},
235 235 'range': {'context': addcontext,
236 236 'hunk': addhunk},
237 237 }
238 238
239 239 p = parser()
240 240
241 241 state = 'context'
242 242 for newstate, data in scanpatch(fp):
243 243 try:
244 244 p.transitions[state][newstate](p, data)
245 245 except KeyError:
246 246 raise patch.PatchError('unhandled transition: %s -> %s' %
247 247 (state, newstate))
248 248 state = newstate
249 249 return p.finished()
250 250
251 251 def filterpatch(ui, chunks):
252 252 """Interactively filter patch chunks into applied-only chunks"""
253 253 chunks = list(chunks)
254 254 chunks.reverse()
255 255 seen = set()
256 256 def consumefile():
257 257 """fetch next portion from chunks until a 'header' is seen
258 258 NB: header == new-file mark
259 259 """
260 260 consumed = []
261 261 while chunks:
262 262 if isinstance(chunks[-1], header):
263 263 break
264 264 else:
265 265 consumed.append(chunks.pop())
266 266 return consumed
267 267
268 268 resp_all = [None] # this two are changed from inside prompt,
269 269 resp_file = [None] # so can't be usual variables
270 270 applied = {} # 'filename' -> [] of chunks
271 271 def prompt(query):
272 272 """prompt query, and process base inputs
273 273
274 274 - y/n for the rest of file
275 275 - y/n for the rest
276 276 - ? (help)
277 277 - q (quit)
278 278
279 279 Returns True/False and sets reps_all and resp_file as
280 280 appropriate.
281 281 """
282 282 if resp_all[0] is not None:
283 283 return resp_all[0]
284 284 if resp_file[0] is not None:
285 285 return resp_file[0]
286 286 while True:
287 287 resps = _('[Ynsfdaq?]')
288 288 choices = (_('&Yes, record this change'),
289 289 _('&No, skip this change'),
290 290 _('&Skip remaining changes to this file'),
291 291 _('Record remaining changes to this &file'),
292 292 _('&Done, skip remaining changes and files'),
293 293 _('Record &all changes to all remaining files'),
294 294 _('&Quit, recording no changes'),
295 295 _('&?'))
296 296 r = ui.promptchoice("%s %s" % (query, resps), choices)
297 297 ui.write("\n")
298 298 if r == 7: # ?
299 299 doc = gettext(record.__doc__)
300 300 c = doc.find('::') + 2
301 301 for l in doc[c:].splitlines():
302 302 if l.startswith(' '):
303 303 ui.write(l.strip(), '\n')
304 304 continue
305 305 elif r == 0: # yes
306 306 ret = True
307 307 elif r == 1: # no
308 308 ret = False
309 309 elif r == 2: # Skip
310 310 ret = resp_file[0] = False
311 311 elif r == 3: # file (Record remaining)
312 312 ret = resp_file[0] = True
313 313 elif r == 4: # done, skip remaining
314 314 ret = resp_all[0] = False
315 315 elif r == 5: # all
316 316 ret = resp_all[0] = True
317 317 elif r == 6: # quit
318 318 raise util.Abort(_('user quit'))
319 319 return ret
320 320 pos, total = 0, len(chunks) - 1
321 321 while chunks:
322 322 pos = total - len(chunks) + 1
323 323 chunk = chunks.pop()
324 324 if isinstance(chunk, header):
325 325 # new-file mark
326 326 resp_file = [None]
327 327 fixoffset = 0
328 328 hdr = ''.join(chunk.header)
329 329 if hdr in seen:
330 330 consumefile()
331 331 continue
332 332 seen.add(hdr)
333 333 if resp_all[0] is None:
334 334 chunk.pretty(ui)
335 335 r = prompt(_('examine changes to %s?') %
336 336 _(' and ').join(map(repr, chunk.files())))
337 337 if r:
338 338 applied[chunk.filename()] = [chunk]
339 339 if chunk.allhunks():
340 340 applied[chunk.filename()] += consumefile()
341 341 else:
342 342 consumefile()
343 343 else:
344 344 # new hunk
345 345 if resp_file[0] is None and resp_all[0] is None:
346 346 chunk.pretty(ui)
347 347 r = total == 1 and prompt(_('record this change to %r?') %
348 348 chunk.filename()) \
349 349 or prompt(_('record change %d/%d to %r?') %
350 350 (pos, total, chunk.filename()))
351 351 if r:
352 352 if fixoffset:
353 353 chunk = copy.copy(chunk)
354 354 chunk.toline += fixoffset
355 355 applied[chunk.filename()].append(chunk)
356 356 else:
357 357 fixoffset += chunk.removed - chunk.added
358 358 return sum([h for h in applied.itervalues()
359 359 if h[0].special() or len(h) > 1], [])
360 360
361 361 def record(ui, repo, *pats, **opts):
362 362 '''interactively select changes to commit
363 363
364 364 If a list of files is omitted, all changes reported by :hg:`status`
365 365 will be candidates for recording.
366 366
367 367 See :hg:`help dates` for a list of formats valid for -d/--date.
368 368
369 369 You will be prompted for whether to record changes to each
370 370 modified file, and for files with multiple changes, for each
371 371 change to use. For each query, the following responses are
372 372 possible::
373 373
374 374 y - record this change
375 375 n - skip this change
376 376
377 377 s - skip remaining changes to this file
378 378 f - record remaining changes to this file
379 379
380 380 d - done, skip remaining changes and files
381 381 a - record all changes to all remaining files
382 382 q - quit, recording no changes
383 383
384 384 ? - display help
385 385
386 386 This command is not available when committing a merge.'''
387 387
388 388 dorecord(ui, repo, commands.commit, *pats, **opts)
389 389
390 390
391 391 def qrecord(ui, repo, patch, *pats, **opts):
392 392 '''interactively record a new patch
393 393
394 394 See :hg:`help qnew` & :hg:`help record` for more information and
395 395 usage.
396 396 '''
397 397
398 398 try:
399 399 mq = extensions.find('mq')
400 400 except KeyError:
401 401 raise util.Abort(_("'mq' extension not loaded"))
402 402
403 403 def committomq(ui, repo, *pats, **opts):
404 404 mq.new(ui, repo, patch, *pats, **opts)
405 405
406 406 opts = opts.copy()
407 407 opts['force'] = True # always 'qnew -f'
408 408 dorecord(ui, repo, committomq, *pats, **opts)
409 409
410 410
411 411 def dorecord(ui, repo, commitfunc, *pats, **opts):
412 412 if not ui.interactive():
413 413 raise util.Abort(_('running non-interactively, use commit instead'))
414 414
415 415 def recordfunc(ui, repo, message, match, opts):
416 416 """This is generic record driver.
417 417
418 418 Its job is to interactively filter local changes, and accordingly
419 419 prepare working dir into a state, where the job can be delegated to
420 420 non-interactive commit command such as 'commit' or 'qrefresh'.
421 421
422 422 After the actual job is done by non-interactive command, working dir
423 423 state is restored to original.
424 424
425 425 In the end we'll record interesting changes, and everything else will be
426 426 left in place, so the user can continue his work.
427 427 """
428 428
429 429 merge = len(repo[None].parents()) > 1
430 430 if merge:
431 431 raise util.Abort(_('cannot partially commit a merge '
432 432 '(use hg commit instead)'))
433 433
434 434 changes = repo.status(match=match)[:3]
435 435 diffopts = mdiff.diffopts(git=True, nodates=True)
436 436 chunks = patch.diff(repo, changes=changes, opts=diffopts)
437 437 fp = cStringIO.StringIO()
438 438 fp.write(''.join(chunks))
439 439 fp.seek(0)
440 440
441 441 # 1. filter patch, so we have intending-to apply subset of it
442 442 chunks = filterpatch(ui, parsepatch(fp))
443 443 del fp
444 444
445 445 contenders = set()
446 446 for h in chunks:
447 447 try:
448 448 contenders.update(set(h.files()))
449 449 except AttributeError:
450 450 pass
451 451
452 452 changed = changes[0] + changes[1] + changes[2]
453 453 newfiles = [f for f in changed if f in contenders]
454 454 if not newfiles:
455 455 ui.status(_('no changes to record\n'))
456 456 return 0
457 457
458 458 modified = set(changes[0])
459 459
460 460 # 2. backup changed files, so we can restore them in the end
461 461 backups = {}
462 462 backupdir = repo.join('record-backups')
463 463 try:
464 464 os.mkdir(backupdir)
465 465 except OSError, err:
466 466 if err.errno != errno.EEXIST:
467 467 raise
468 468 try:
469 469 # backup continues
470 470 for f in newfiles:
471 471 if f not in modified:
472 472 continue
473 473 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
474 474 dir=backupdir)
475 475 os.close(fd)
476 476 ui.debug('backup %r as %r\n' % (f, tmpname))
477 477 util.copyfile(repo.wjoin(f), tmpname)
478 478 backups[f] = tmpname
479 479
480 480 fp = cStringIO.StringIO()
481 481 for c in chunks:
482 482 if c.filename() in backups:
483 483 c.write(fp)
484 484 dopatch = fp.tell()
485 485 fp.seek(0)
486 486
487 487 # 3a. apply filtered patch to clean repo (clean)
488 488 if backups:
489 489 hg.revert(repo, repo.dirstate.parents()[0],
490 490 lambda key: key in backups)
491 491
492 492 # 3b. (apply)
493 493 if dopatch:
494 494 try:
495 495 ui.debug('applying patch\n')
496 496 ui.debug(fp.getvalue())
497 497 pfiles = {}
498 498 patch.internalpatch(fp, ui, 1, repo.root, files=pfiles,
499 499 eolmode=None)
500 patch.updatedir(ui, repo, pfiles)
500 cmdutil.updatedir(ui, repo, pfiles)
501 501 except patch.PatchError, err:
502 502 s = str(err)
503 503 if s:
504 504 raise util.Abort(s)
505 505 else:
506 506 raise util.Abort(_('patch failed to apply'))
507 507 del fp
508 508
509 509 # 4. We prepared working directory according to filtered patch.
510 510 # Now is the time to delegate the job to commit/qrefresh or the like!
511 511
512 512 # it is important to first chdir to repo root -- we'll call a
513 513 # highlevel command with list of pathnames relative to repo root
514 514 cwd = os.getcwd()
515 515 os.chdir(repo.root)
516 516 try:
517 517 commitfunc(ui, repo, *newfiles, **opts)
518 518 finally:
519 519 os.chdir(cwd)
520 520
521 521 return 0
522 522 finally:
523 523 # 5. finally restore backed-up files
524 524 try:
525 525 for realname, tmpname in backups.iteritems():
526 526 ui.debug('restoring %r to %r\n' % (tmpname, realname))
527 527 util.copyfile(tmpname, repo.wjoin(realname))
528 528 os.unlink(tmpname)
529 529 os.rmdir(backupdir)
530 530 except OSError:
531 531 pass
532 532
533 533 # wrap ui.write so diff output can be labeled/colorized
534 534 def wrapwrite(orig, *args, **kw):
535 535 label = kw.pop('label', '')
536 536 for chunk, l in patch.difflabel(lambda: args):
537 537 orig(chunk, label=label + l)
538 538 oldwrite = ui.write
539 539 extensions.wrapfunction(ui, 'write', wrapwrite)
540 540 try:
541 541 return cmdutil.commit(ui, repo, recordfunc, pats, opts)
542 542 finally:
543 543 ui.write = oldwrite
544 544
545 545 cmdtable = {
546 546 "record":
547 547 (record,
548 548
549 549 # add commit options
550 550 commands.table['^commit|ci'][1],
551 551
552 552 _('hg record [OPTION]... [FILE]...')),
553 553 }
554 554
555 555
556 556 def uisetup(ui):
557 557 try:
558 558 mq = extensions.find('mq')
559 559 except KeyError:
560 560 return
561 561
562 562 qcmdtable = {
563 563 "qrecord":
564 564 (qrecord,
565 565
566 566 # add qnew options, except '--force'
567 567 [opt for opt in mq.cmdtable['^qnew'][1] if opt[1] != 'force'],
568 568
569 569 _('hg qrecord [OPTION]... PATCH [FILE]...')),
570 570 }
571 571
572 572 cmdtable.update(qcmdtable)
573 573
@@ -1,620 +1,620 b''
1 1 # Patch transplanting extension for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''command to transplant changesets from another branch
9 9
10 10 This extension allows you to transplant patches from another branch.
11 11
12 12 Transplanted patches are recorded in .hg/transplant/transplants, as a
13 13 map from a changeset hash to its hash in the source repository.
14 14 '''
15 15
16 16 from mercurial.i18n import _
17 17 import os, tempfile
18 18 from mercurial import bundlerepo, changegroup, cmdutil, hg, merge, match
19 19 from mercurial import patch, revlog, util, error, discovery
20 20
21 21 class transplantentry(object):
22 22 def __init__(self, lnode, rnode):
23 23 self.lnode = lnode
24 24 self.rnode = rnode
25 25
26 26 class transplants(object):
27 27 def __init__(self, path=None, transplantfile=None, opener=None):
28 28 self.path = path
29 29 self.transplantfile = transplantfile
30 30 self.opener = opener
31 31
32 32 if not opener:
33 33 self.opener = util.opener(self.path)
34 34 self.transplants = []
35 35 self.dirty = False
36 36 self.read()
37 37
38 38 def read(self):
39 39 abspath = os.path.join(self.path, self.transplantfile)
40 40 if self.transplantfile and os.path.exists(abspath):
41 41 for line in self.opener(self.transplantfile).read().splitlines():
42 42 lnode, rnode = map(revlog.bin, line.split(':'))
43 43 self.transplants.append(transplantentry(lnode, rnode))
44 44
45 45 def write(self):
46 46 if self.dirty and self.transplantfile:
47 47 if not os.path.isdir(self.path):
48 48 os.mkdir(self.path)
49 49 fp = self.opener(self.transplantfile, 'w')
50 50 for c in self.transplants:
51 51 l, r = map(revlog.hex, (c.lnode, c.rnode))
52 52 fp.write(l + ':' + r + '\n')
53 53 fp.close()
54 54 self.dirty = False
55 55
56 56 def get(self, rnode):
57 57 return [t for t in self.transplants if t.rnode == rnode]
58 58
59 59 def set(self, lnode, rnode):
60 60 self.transplants.append(transplantentry(lnode, rnode))
61 61 self.dirty = True
62 62
63 63 def remove(self, transplant):
64 64 del self.transplants[self.transplants.index(transplant)]
65 65 self.dirty = True
66 66
67 67 class transplanter(object):
68 68 def __init__(self, ui, repo):
69 69 self.ui = ui
70 70 self.path = repo.join('transplant')
71 71 self.opener = util.opener(self.path)
72 72 self.transplants = transplants(self.path, 'transplants',
73 73 opener=self.opener)
74 74
75 75 def applied(self, repo, node, parent):
76 76 '''returns True if a node is already an ancestor of parent
77 77 or has already been transplanted'''
78 78 if hasnode(repo, node):
79 79 if node in repo.changelog.reachable(parent, stop=node):
80 80 return True
81 81 for t in self.transplants.get(node):
82 82 # it might have been stripped
83 83 if not hasnode(repo, t.lnode):
84 84 self.transplants.remove(t)
85 85 return False
86 86 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
87 87 return True
88 88 return False
89 89
90 90 def apply(self, repo, source, revmap, merges, opts={}):
91 91 '''apply the revisions in revmap one by one in revision order'''
92 92 revs = sorted(revmap)
93 93 p1, p2 = repo.dirstate.parents()
94 94 pulls = []
95 95 diffopts = patch.diffopts(self.ui, opts)
96 96 diffopts.git = True
97 97
98 98 lock = wlock = None
99 99 try:
100 100 wlock = repo.wlock()
101 101 lock = repo.lock()
102 102 for rev in revs:
103 103 node = revmap[rev]
104 104 revstr = '%s:%s' % (rev, revlog.short(node))
105 105
106 106 if self.applied(repo, node, p1):
107 107 self.ui.warn(_('skipping already applied revision %s\n') %
108 108 revstr)
109 109 continue
110 110
111 111 parents = source.changelog.parents(node)
112 112 if not opts.get('filter'):
113 113 # If the changeset parent is the same as the
114 114 # wdir's parent, just pull it.
115 115 if parents[0] == p1:
116 116 pulls.append(node)
117 117 p1 = node
118 118 continue
119 119 if pulls:
120 120 if source != repo:
121 121 repo.pull(source, heads=pulls)
122 122 merge.update(repo, pulls[-1], False, False, None)
123 123 p1, p2 = repo.dirstate.parents()
124 124 pulls = []
125 125
126 126 domerge = False
127 127 if node in merges:
128 128 # pulling all the merge revs at once would mean we
129 129 # couldn't transplant after the latest even if
130 130 # transplants before them fail.
131 131 domerge = True
132 132 if not hasnode(repo, node):
133 133 repo.pull(source, heads=[node])
134 134
135 135 if parents[1] != revlog.nullid:
136 136 self.ui.note(_('skipping merge changeset %s:%s\n')
137 137 % (rev, revlog.short(node)))
138 138 patchfile = None
139 139 else:
140 140 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
141 141 fp = os.fdopen(fd, 'w')
142 142 gen = patch.diff(source, parents[0], node, opts=diffopts)
143 143 for chunk in gen:
144 144 fp.write(chunk)
145 145 fp.close()
146 146
147 147 del revmap[rev]
148 148 if patchfile or domerge:
149 149 try:
150 150 n = self.applyone(repo, node,
151 151 source.changelog.read(node),
152 152 patchfile, merge=domerge,
153 153 log=opts.get('log'),
154 154 filter=opts.get('filter'))
155 155 if n and domerge:
156 156 self.ui.status(_('%s merged at %s\n') % (revstr,
157 157 revlog.short(n)))
158 158 elif n:
159 159 self.ui.status(_('%s transplanted to %s\n')
160 160 % (revlog.short(node),
161 161 revlog.short(n)))
162 162 finally:
163 163 if patchfile:
164 164 os.unlink(patchfile)
165 165 if pulls:
166 166 repo.pull(source, heads=pulls)
167 167 merge.update(repo, pulls[-1], False, False, None)
168 168 finally:
169 169 self.saveseries(revmap, merges)
170 170 self.transplants.write()
171 171 lock.release()
172 172 wlock.release()
173 173
174 174 def filter(self, filter, changelog, patchfile):
175 175 '''arbitrarily rewrite changeset before applying it'''
176 176
177 177 self.ui.status(_('filtering %s\n') % patchfile)
178 178 user, date, msg = (changelog[1], changelog[2], changelog[4])
179 179
180 180 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
181 181 fp = os.fdopen(fd, 'w')
182 182 fp.write("# HG changeset patch\n")
183 183 fp.write("# User %s\n" % user)
184 184 fp.write("# Date %d %d\n" % date)
185 185 fp.write(msg + '\n')
186 186 fp.close()
187 187
188 188 try:
189 189 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
190 190 util.shellquote(patchfile)),
191 191 environ={'HGUSER': changelog[1]},
192 192 onerr=util.Abort, errprefix=_('filter failed'))
193 193 user, date, msg = self.parselog(file(headerfile))[1:4]
194 194 finally:
195 195 os.unlink(headerfile)
196 196
197 197 return (user, date, msg)
198 198
199 199 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
200 200 filter=None):
201 201 '''apply the patch in patchfile to the repository as a transplant'''
202 202 (manifest, user, (time, timezone), files, message) = cl[:5]
203 203 date = "%d %d" % (time, timezone)
204 204 extra = {'transplant_source': node}
205 205 if filter:
206 206 (user, date, message) = self.filter(filter, cl, patchfile)
207 207
208 208 if log:
209 209 # we don't translate messages inserted into commits
210 210 message += '\n(transplanted from %s)' % revlog.hex(node)
211 211
212 212 self.ui.status(_('applying %s\n') % revlog.short(node))
213 213 self.ui.note('%s %s\n%s\n' % (user, date, message))
214 214
215 215 if not patchfile and not merge:
216 216 raise util.Abort(_('can only omit patchfile if merging'))
217 217 if patchfile:
218 218 try:
219 219 files = {}
220 220 try:
221 221 patch.patch(patchfile, self.ui, cwd=repo.root,
222 222 files=files, eolmode=None)
223 223 if not files:
224 224 self.ui.warn(_('%s: empty changeset')
225 225 % revlog.hex(node))
226 226 return None
227 227 finally:
228 files = patch.updatedir(self.ui, repo, files)
228 files = cmdutil.updatedir(self.ui, repo, files)
229 229 except Exception, inst:
230 230 seriespath = os.path.join(self.path, 'series')
231 231 if os.path.exists(seriespath):
232 232 os.unlink(seriespath)
233 233 p1 = repo.dirstate.parents()[0]
234 234 p2 = node
235 235 self.log(user, date, message, p1, p2, merge=merge)
236 236 self.ui.write(str(inst) + '\n')
237 237 raise util.Abort(_('fix up the merge and run '
238 238 'hg transplant --continue'))
239 239 else:
240 240 files = None
241 241 if merge:
242 242 p1, p2 = repo.dirstate.parents()
243 243 repo.dirstate.setparents(p1, node)
244 244 m = match.always(repo.root, '')
245 245 else:
246 246 m = match.exact(repo.root, '', files)
247 247
248 248 n = repo.commit(message, user, date, extra=extra, match=m)
249 249 if not n:
250 250 # Crash here to prevent an unclear crash later, in
251 251 # transplants.write(). This can happen if patch.patch()
252 252 # does nothing but claims success or if repo.status() fails
253 253 # to report changes done by patch.patch(). These both
254 254 # appear to be bugs in other parts of Mercurial, but dying
255 255 # here, as soon as we can detect the problem, is preferable
256 256 # to silently dropping changesets on the floor.
257 257 raise RuntimeError('nothing committed after transplant')
258 258 if not merge:
259 259 self.transplants.set(n, node)
260 260
261 261 return n
262 262
263 263 def resume(self, repo, source, opts=None):
264 264 '''recover last transaction and apply remaining changesets'''
265 265 if os.path.exists(os.path.join(self.path, 'journal')):
266 266 n, node = self.recover(repo)
267 267 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
268 268 revlog.short(n)))
269 269 seriespath = os.path.join(self.path, 'series')
270 270 if not os.path.exists(seriespath):
271 271 self.transplants.write()
272 272 return
273 273 nodes, merges = self.readseries()
274 274 revmap = {}
275 275 for n in nodes:
276 276 revmap[source.changelog.rev(n)] = n
277 277 os.unlink(seriespath)
278 278
279 279 self.apply(repo, source, revmap, merges, opts)
280 280
281 281 def recover(self, repo):
282 282 '''commit working directory using journal metadata'''
283 283 node, user, date, message, parents = self.readlog()
284 284 merge = len(parents) == 2
285 285
286 286 if not user or not date or not message or not parents[0]:
287 287 raise util.Abort(_('transplant log file is corrupt'))
288 288
289 289 extra = {'transplant_source': node}
290 290 wlock = repo.wlock()
291 291 try:
292 292 p1, p2 = repo.dirstate.parents()
293 293 if p1 != parents[0]:
294 294 raise util.Abort(
295 295 _('working dir not at transplant parent %s') %
296 296 revlog.hex(parents[0]))
297 297 if merge:
298 298 repo.dirstate.setparents(p1, parents[1])
299 299 n = repo.commit(message, user, date, extra=extra)
300 300 if not n:
301 301 raise util.Abort(_('commit failed'))
302 302 if not merge:
303 303 self.transplants.set(n, node)
304 304 self.unlog()
305 305
306 306 return n, node
307 307 finally:
308 308 wlock.release()
309 309
310 310 def readseries(self):
311 311 nodes = []
312 312 merges = []
313 313 cur = nodes
314 314 for line in self.opener('series').read().splitlines():
315 315 if line.startswith('# Merges'):
316 316 cur = merges
317 317 continue
318 318 cur.append(revlog.bin(line))
319 319
320 320 return (nodes, merges)
321 321
322 322 def saveseries(self, revmap, merges):
323 323 if not revmap:
324 324 return
325 325
326 326 if not os.path.isdir(self.path):
327 327 os.mkdir(self.path)
328 328 series = self.opener('series', 'w')
329 329 for rev in sorted(revmap):
330 330 series.write(revlog.hex(revmap[rev]) + '\n')
331 331 if merges:
332 332 series.write('# Merges\n')
333 333 for m in merges:
334 334 series.write(revlog.hex(m) + '\n')
335 335 series.close()
336 336
337 337 def parselog(self, fp):
338 338 parents = []
339 339 message = []
340 340 node = revlog.nullid
341 341 inmsg = False
342 342 for line in fp.read().splitlines():
343 343 if inmsg:
344 344 message.append(line)
345 345 elif line.startswith('# User '):
346 346 user = line[7:]
347 347 elif line.startswith('# Date '):
348 348 date = line[7:]
349 349 elif line.startswith('# Node ID '):
350 350 node = revlog.bin(line[10:])
351 351 elif line.startswith('# Parent '):
352 352 parents.append(revlog.bin(line[9:]))
353 353 elif not line.startswith('# '):
354 354 inmsg = True
355 355 message.append(line)
356 356 return (node, user, date, '\n'.join(message), parents)
357 357
358 358 def log(self, user, date, message, p1, p2, merge=False):
359 359 '''journal changelog metadata for later recover'''
360 360
361 361 if not os.path.isdir(self.path):
362 362 os.mkdir(self.path)
363 363 fp = self.opener('journal', 'w')
364 364 fp.write('# User %s\n' % user)
365 365 fp.write('# Date %s\n' % date)
366 366 fp.write('# Node ID %s\n' % revlog.hex(p2))
367 367 fp.write('# Parent ' + revlog.hex(p1) + '\n')
368 368 if merge:
369 369 fp.write('# Parent ' + revlog.hex(p2) + '\n')
370 370 fp.write(message.rstrip() + '\n')
371 371 fp.close()
372 372
373 373 def readlog(self):
374 374 return self.parselog(self.opener('journal'))
375 375
376 376 def unlog(self):
377 377 '''remove changelog journal'''
378 378 absdst = os.path.join(self.path, 'journal')
379 379 if os.path.exists(absdst):
380 380 os.unlink(absdst)
381 381
382 382 def transplantfilter(self, repo, source, root):
383 383 def matchfn(node):
384 384 if self.applied(repo, node, root):
385 385 return False
386 386 if source.changelog.parents(node)[1] != revlog.nullid:
387 387 return False
388 388 extra = source.changelog.read(node)[5]
389 389 cnode = extra.get('transplant_source')
390 390 if cnode and self.applied(repo, cnode, root):
391 391 return False
392 392 return True
393 393
394 394 return matchfn
395 395
396 396 def hasnode(repo, node):
397 397 try:
398 398 return repo.changelog.rev(node) != None
399 399 except error.RevlogError:
400 400 return False
401 401
402 402 def browserevs(ui, repo, nodes, opts):
403 403 '''interactively transplant changesets'''
404 404 def browsehelp(ui):
405 405 ui.write(_('y: transplant this changeset\n'
406 406 'n: skip this changeset\n'
407 407 'm: merge at this changeset\n'
408 408 'p: show patch\n'
409 409 'c: commit selected changesets\n'
410 410 'q: cancel transplant\n'
411 411 '?: show this help\n'))
412 412
413 413 displayer = cmdutil.show_changeset(ui, repo, opts)
414 414 transplants = []
415 415 merges = []
416 416 for node in nodes:
417 417 displayer.show(repo[node])
418 418 action = None
419 419 while not action:
420 420 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
421 421 if action == '?':
422 422 browsehelp(ui)
423 423 action = None
424 424 elif action == 'p':
425 425 parent = repo.changelog.parents(node)[0]
426 426 for chunk in patch.diff(repo, parent, node):
427 427 ui.write(chunk)
428 428 action = None
429 429 elif action not in ('y', 'n', 'm', 'c', 'q'):
430 430 ui.write(_('no such option\n'))
431 431 action = None
432 432 if action == 'y':
433 433 transplants.append(node)
434 434 elif action == 'm':
435 435 merges.append(node)
436 436 elif action == 'c':
437 437 break
438 438 elif action == 'q':
439 439 transplants = ()
440 440 merges = ()
441 441 break
442 442 displayer.close()
443 443 return (transplants, merges)
444 444
445 445 def transplant(ui, repo, *revs, **opts):
446 446 '''transplant changesets from another branch
447 447
448 448 Selected changesets will be applied on top of the current working
449 449 directory with the log of the original changeset. If --log is
450 450 specified, log messages will have a comment appended of the form::
451 451
452 452 (transplanted from CHANGESETHASH)
453 453
454 454 You can rewrite the changelog message with the --filter option.
455 455 Its argument will be invoked with the current changelog message as
456 456 $1 and the patch as $2.
457 457
458 458 If --source/-s is specified, selects changesets from the named
459 459 repository. If --branch/-b is specified, selects changesets from
460 460 the branch holding the named revision, up to that revision. If
461 461 --all/-a is specified, all changesets on the branch will be
462 462 transplanted, otherwise you will be prompted to select the
463 463 changesets you want.
464 464
465 465 :hg:`transplant --branch REVISION --all` will rebase the selected
466 466 branch (up to the named revision) onto your current working
467 467 directory.
468 468
469 469 You can optionally mark selected transplanted changesets as merge
470 470 changesets. You will not be prompted to transplant any ancestors
471 471 of a merged transplant, and you can merge descendants of them
472 472 normally instead of transplanting them.
473 473
474 474 If no merges or revisions are provided, :hg:`transplant` will
475 475 start an interactive changeset browser.
476 476
477 477 If a changeset application fails, you can fix the merge by hand
478 478 and then resume where you left off by calling :hg:`transplant
479 479 --continue/-c`.
480 480 '''
481 481 def getremotechanges(repo, url):
482 482 sourcerepo = ui.expandpath(url)
483 483 source = hg.repository(ui, sourcerepo)
484 484 tmp = discovery.findcommonincoming(repo, source, force=True)
485 485 common, incoming, rheads = tmp
486 486 if not incoming:
487 487 return (source, None, None)
488 488
489 489 bundle = None
490 490 if not source.local():
491 491 if source.capable('changegroupsubset'):
492 492 cg = source.changegroupsubset(incoming, rheads, 'incoming')
493 493 else:
494 494 cg = source.changegroup(incoming, 'incoming')
495 495 bundle = changegroup.writebundle(cg, None, 'HG10UN')
496 496 source = bundlerepo.bundlerepository(ui, repo.root, bundle)
497 497
498 498 return (source, incoming, bundle)
499 499
500 500 def incwalk(repo, incoming, branches, match=util.always):
501 501 if not branches:
502 502 branches = None
503 503 for node in repo.changelog.nodesbetween(incoming, branches)[0]:
504 504 if match(node):
505 505 yield node
506 506
507 507 def transplantwalk(repo, root, branches, match=util.always):
508 508 if not branches:
509 509 branches = repo.heads()
510 510 ancestors = []
511 511 for branch in branches:
512 512 ancestors.append(repo.changelog.ancestor(root, branch))
513 513 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
514 514 if match(node):
515 515 yield node
516 516
517 517 def checkopts(opts, revs):
518 518 if opts.get('continue'):
519 519 if opts.get('branch') or opts.get('all') or opts.get('merge'):
520 520 raise util.Abort(_('--continue is incompatible with '
521 521 'branch, all or merge'))
522 522 return
523 523 if not (opts.get('source') or revs or
524 524 opts.get('merge') or opts.get('branch')):
525 525 raise util.Abort(_('no source URL, branch tag or revision '
526 526 'list provided'))
527 527 if opts.get('all'):
528 528 if not opts.get('branch'):
529 529 raise util.Abort(_('--all requires a branch revision'))
530 530 if revs:
531 531 raise util.Abort(_('--all is incompatible with a '
532 532 'revision list'))
533 533
534 534 checkopts(opts, revs)
535 535
536 536 if not opts.get('log'):
537 537 opts['log'] = ui.config('transplant', 'log')
538 538 if not opts.get('filter'):
539 539 opts['filter'] = ui.config('transplant', 'filter')
540 540
541 541 tp = transplanter(ui, repo)
542 542
543 543 p1, p2 = repo.dirstate.parents()
544 544 if len(repo) > 0 and p1 == revlog.nullid:
545 545 raise util.Abort(_('no revision checked out'))
546 546 if not opts.get('continue'):
547 547 if p2 != revlog.nullid:
548 548 raise util.Abort(_('outstanding uncommitted merges'))
549 549 m, a, r, d = repo.status()[:4]
550 550 if m or a or r or d:
551 551 raise util.Abort(_('outstanding local changes'))
552 552
553 553 bundle = None
554 554 source = opts.get('source')
555 555 if source:
556 556 (source, incoming, bundle) = getremotechanges(repo, source)
557 557 else:
558 558 source = repo
559 559
560 560 try:
561 561 if opts.get('continue'):
562 562 tp.resume(repo, source, opts)
563 563 return
564 564
565 565 tf = tp.transplantfilter(repo, source, p1)
566 566 if opts.get('prune'):
567 567 prune = [source.lookup(r)
568 568 for r in cmdutil.revrange(source, opts.get('prune'))]
569 569 matchfn = lambda x: tf(x) and x not in prune
570 570 else:
571 571 matchfn = tf
572 572 branches = map(source.lookup, opts.get('branch', ()))
573 573 merges = map(source.lookup, opts.get('merge', ()))
574 574 revmap = {}
575 575 if revs:
576 576 for r in cmdutil.revrange(source, revs):
577 577 revmap[int(r)] = source.lookup(r)
578 578 elif opts.get('all') or not merges:
579 579 if source != repo:
580 580 alltransplants = incwalk(source, incoming, branches,
581 581 match=matchfn)
582 582 else:
583 583 alltransplants = transplantwalk(source, p1, branches,
584 584 match=matchfn)
585 585 if opts.get('all'):
586 586 revs = alltransplants
587 587 else:
588 588 revs, newmerges = browserevs(ui, source, alltransplants, opts)
589 589 merges.extend(newmerges)
590 590 for r in revs:
591 591 revmap[source.changelog.rev(r)] = r
592 592 for r in merges:
593 593 revmap[source.changelog.rev(r)] = r
594 594
595 595 tp.apply(repo, source, revmap, merges, opts)
596 596 finally:
597 597 if bundle:
598 598 source.close()
599 599 os.unlink(bundle)
600 600
601 601 cmdtable = {
602 602 "transplant":
603 603 (transplant,
604 604 [('s', 'source', '',
605 605 _('pull patches from REPO'), _('REPO')),
606 606 ('b', 'branch', [],
607 607 _('pull patches from branch BRANCH'), _('BRANCH')),
608 608 ('a', 'all', None, _('pull all changesets up to BRANCH')),
609 609 ('p', 'prune', [],
610 610 _('skip over REV'), _('REV')),
611 611 ('m', 'merge', [],
612 612 _('merge at REV'), _('REV')),
613 613 ('', 'log', None, _('append transplant info to log message')),
614 614 ('c', 'continue', None, _('continue last transplant session '
615 615 'after repair')),
616 616 ('', 'filter', '',
617 617 _('filter changesets through command'), _('CMD'))],
618 618 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
619 619 '[-m REV] [REV]...'))
620 620 }
@@ -1,1293 +1,1336 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, glob, tempfile
11 11 import util, templater, patch, error, encoding, templatekw
12 12 import match as matchmod
13 13 import similar, revset, subrepo
14 14
15 15 revrangesep = ':'
16 16
17 17 def parsealiases(cmd):
18 18 return cmd.lstrip("^").split("|")
19 19
20 20 def findpossible(cmd, table, strict=False):
21 21 """
22 22 Return cmd -> (aliases, command table entry)
23 23 for each matching command.
24 24 Return debug commands (or their aliases) only if no normal command matches.
25 25 """
26 26 choice = {}
27 27 debugchoice = {}
28 28 for e in table.keys():
29 29 aliases = parsealiases(e)
30 30 found = None
31 31 if cmd in aliases:
32 32 found = cmd
33 33 elif not strict:
34 34 for a in aliases:
35 35 if a.startswith(cmd):
36 36 found = a
37 37 break
38 38 if found is not None:
39 39 if aliases[0].startswith("debug") or found.startswith("debug"):
40 40 debugchoice[found] = (aliases, table[e])
41 41 else:
42 42 choice[found] = (aliases, table[e])
43 43
44 44 if not choice and debugchoice:
45 45 choice = debugchoice
46 46
47 47 return choice
48 48
49 49 def findcmd(cmd, table, strict=True):
50 50 """Return (aliases, command table entry) for command string."""
51 51 choice = findpossible(cmd, table, strict)
52 52
53 53 if cmd in choice:
54 54 return choice[cmd]
55 55
56 56 if len(choice) > 1:
57 57 clist = choice.keys()
58 58 clist.sort()
59 59 raise error.AmbiguousCommand(cmd, clist)
60 60
61 61 if choice:
62 62 return choice.values()[0]
63 63
64 64 raise error.UnknownCommand(cmd)
65 65
66 66 def findrepo(p):
67 67 while not os.path.isdir(os.path.join(p, ".hg")):
68 68 oldp, p = p, os.path.dirname(p)
69 69 if p == oldp:
70 70 return None
71 71
72 72 return p
73 73
74 74 def bail_if_changed(repo):
75 75 if repo.dirstate.parents()[1] != nullid:
76 76 raise util.Abort(_('outstanding uncommitted merge'))
77 77 modified, added, removed, deleted = repo.status()[:4]
78 78 if modified or added or removed or deleted:
79 79 raise util.Abort(_("outstanding uncommitted changes"))
80 80
81 81 def logmessage(opts):
82 82 """ get the log message according to -m and -l option """
83 83 message = opts.get('message')
84 84 logfile = opts.get('logfile')
85 85
86 86 if message and logfile:
87 87 raise util.Abort(_('options --message and --logfile are mutually '
88 88 'exclusive'))
89 89 if not message and logfile:
90 90 try:
91 91 if logfile == '-':
92 92 message = sys.stdin.read()
93 93 else:
94 94 message = open(logfile).read()
95 95 except IOError, inst:
96 96 raise util.Abort(_("can't read commit message '%s': %s") %
97 97 (logfile, inst.strerror))
98 98 return message
99 99
100 100 def loglimit(opts):
101 101 """get the log limit according to option -l/--limit"""
102 102 limit = opts.get('limit')
103 103 if limit:
104 104 try:
105 105 limit = int(limit)
106 106 except ValueError:
107 107 raise util.Abort(_('limit must be a positive integer'))
108 108 if limit <= 0:
109 109 raise util.Abort(_('limit must be positive'))
110 110 else:
111 111 limit = None
112 112 return limit
113 113
114 114 def revpair(repo, revs):
115 115 '''return pair of nodes, given list of revisions. second item can
116 116 be None, meaning use working dir.'''
117 117
118 118 def revfix(repo, val, defval):
119 119 if not val and val != 0 and defval is not None:
120 120 val = defval
121 121 return repo.lookup(val)
122 122
123 123 if not revs:
124 124 return repo.dirstate.parents()[0], None
125 125 end = None
126 126 if len(revs) == 1:
127 127 if revrangesep in revs[0]:
128 128 start, end = revs[0].split(revrangesep, 1)
129 129 start = revfix(repo, start, 0)
130 130 end = revfix(repo, end, len(repo) - 1)
131 131 else:
132 132 start = revfix(repo, revs[0], None)
133 133 elif len(revs) == 2:
134 134 if revrangesep in revs[0] or revrangesep in revs[1]:
135 135 raise util.Abort(_('too many revisions specified'))
136 136 start = revfix(repo, revs[0], None)
137 137 end = revfix(repo, revs[1], None)
138 138 else:
139 139 raise util.Abort(_('too many revisions specified'))
140 140 return start, end
141 141
142 142 def revrange(repo, revs):
143 143 """Yield revision as strings from a list of revision specifications."""
144 144
145 145 def revfix(repo, val, defval):
146 146 if not val and val != 0 and defval is not None:
147 147 return defval
148 148 return repo.changelog.rev(repo.lookup(val))
149 149
150 150 seen, l = set(), []
151 151 for spec in revs:
152 152 # attempt to parse old-style ranges first to deal with
153 153 # things like old-tag which contain query metacharacters
154 154 try:
155 155 if revrangesep in spec:
156 156 start, end = spec.split(revrangesep, 1)
157 157 start = revfix(repo, start, 0)
158 158 end = revfix(repo, end, len(repo) - 1)
159 159 step = start > end and -1 or 1
160 160 for rev in xrange(start, end + step, step):
161 161 if rev in seen:
162 162 continue
163 163 seen.add(rev)
164 164 l.append(rev)
165 165 continue
166 166 elif spec and spec in repo: # single unquoted rev
167 167 rev = revfix(repo, spec, None)
168 168 if rev in seen:
169 169 continue
170 170 seen.add(rev)
171 171 l.append(rev)
172 172 continue
173 173 except error.RepoLookupError:
174 174 pass
175 175
176 176 # fall through to new-style queries if old-style fails
177 177 m = revset.match(spec)
178 178 for r in m(repo, range(len(repo))):
179 179 if r not in seen:
180 180 l.append(r)
181 181 seen.update(l)
182 182
183 183 return l
184 184
185 185 def make_filename(repo, pat, node,
186 186 total=None, seqno=None, revwidth=None, pathname=None):
187 187 node_expander = {
188 188 'H': lambda: hex(node),
189 189 'R': lambda: str(repo.changelog.rev(node)),
190 190 'h': lambda: short(node),
191 191 }
192 192 expander = {
193 193 '%': lambda: '%',
194 194 'b': lambda: os.path.basename(repo.root),
195 195 }
196 196
197 197 try:
198 198 if node:
199 199 expander.update(node_expander)
200 200 if node:
201 201 expander['r'] = (lambda:
202 202 str(repo.changelog.rev(node)).zfill(revwidth or 0))
203 203 if total is not None:
204 204 expander['N'] = lambda: str(total)
205 205 if seqno is not None:
206 206 expander['n'] = lambda: str(seqno)
207 207 if total is not None and seqno is not None:
208 208 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
209 209 if pathname is not None:
210 210 expander['s'] = lambda: os.path.basename(pathname)
211 211 expander['d'] = lambda: os.path.dirname(pathname) or '.'
212 212 expander['p'] = lambda: pathname
213 213
214 214 newname = []
215 215 patlen = len(pat)
216 216 i = 0
217 217 while i < patlen:
218 218 c = pat[i]
219 219 if c == '%':
220 220 i += 1
221 221 c = pat[i]
222 222 c = expander[c]()
223 223 newname.append(c)
224 224 i += 1
225 225 return ''.join(newname)
226 226 except KeyError, inst:
227 227 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
228 228 inst.args[0])
229 229
230 230 def make_file(repo, pat, node=None,
231 231 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
232 232
233 233 writable = 'w' in mode or 'a' in mode
234 234
235 235 if not pat or pat == '-':
236 236 return writable and sys.stdout or sys.stdin
237 237 if hasattr(pat, 'write') and writable:
238 238 return pat
239 239 if hasattr(pat, 'read') and 'r' in mode:
240 240 return pat
241 241 return open(make_filename(repo, pat, node, total, seqno, revwidth,
242 242 pathname),
243 243 mode)
244 244
245 245 def expandpats(pats):
246 246 if not util.expandglobs:
247 247 return list(pats)
248 248 ret = []
249 249 for p in pats:
250 250 kind, name = matchmod._patsplit(p, None)
251 251 if kind is None:
252 252 try:
253 253 globbed = glob.glob(name)
254 254 except re.error:
255 255 globbed = [name]
256 256 if globbed:
257 257 ret.extend(globbed)
258 258 continue
259 259 ret.append(p)
260 260 return ret
261 261
262 262 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
263 263 if not globbed and default == 'relpath':
264 264 pats = expandpats(pats or [])
265 265 m = matchmod.match(repo.root, repo.getcwd(), pats,
266 266 opts.get('include'), opts.get('exclude'), default,
267 267 auditor=repo.auditor)
268 268 def badfn(f, msg):
269 269 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
270 270 m.bad = badfn
271 271 return m
272 272
273 273 def matchall(repo):
274 274 return matchmod.always(repo.root, repo.getcwd())
275 275
276 276 def matchfiles(repo, files):
277 277 return matchmod.exact(repo.root, repo.getcwd(), files)
278 278
279 279 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
280 280 if dry_run is None:
281 281 dry_run = opts.get('dry_run')
282 282 if similarity is None:
283 283 similarity = float(opts.get('similarity') or 0)
284 284 # we'd use status here, except handling of symlinks and ignore is tricky
285 285 added, unknown, deleted, removed = [], [], [], []
286 286 audit_path = util.path_auditor(repo.root)
287 287 m = match(repo, pats, opts)
288 288 for abs in repo.walk(m):
289 289 target = repo.wjoin(abs)
290 290 good = True
291 291 try:
292 292 audit_path(abs)
293 293 except:
294 294 good = False
295 295 rel = m.rel(abs)
296 296 exact = m.exact(abs)
297 297 if good and abs not in repo.dirstate:
298 298 unknown.append(abs)
299 299 if repo.ui.verbose or not exact:
300 300 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
301 301 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
302 302 or (os.path.isdir(target) and not os.path.islink(target))):
303 303 deleted.append(abs)
304 304 if repo.ui.verbose or not exact:
305 305 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
306 306 # for finding renames
307 307 elif repo.dirstate[abs] == 'r':
308 308 removed.append(abs)
309 309 elif repo.dirstate[abs] == 'a':
310 310 added.append(abs)
311 311 copies = {}
312 312 if similarity > 0:
313 313 for old, new, score in similar.findrenames(repo,
314 314 added + unknown, removed + deleted, similarity):
315 315 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
316 316 repo.ui.status(_('recording removal of %s as rename to %s '
317 317 '(%d%% similar)\n') %
318 318 (m.rel(old), m.rel(new), score * 100))
319 319 copies[new] = old
320 320
321 321 if not dry_run:
322 322 wctx = repo[None]
323 323 wlock = repo.wlock()
324 324 try:
325 325 wctx.remove(deleted)
326 326 wctx.add(unknown)
327 327 for new, old in copies.iteritems():
328 328 wctx.copy(old, new)
329 329 finally:
330 330 wlock.release()
331 331
332 def updatedir(ui, repo, patches, similarity=0):
333 '''Update dirstate after patch application according to metadata'''
334 if not patches:
335 return
336 copies = []
337 removes = set()
338 cfiles = patches.keys()
339 cwd = repo.getcwd()
340 if cwd:
341 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
342 for f in patches:
343 gp = patches[f]
344 if not gp:
345 continue
346 if gp.op == 'RENAME':
347 copies.append((gp.oldpath, gp.path))
348 removes.add(gp.oldpath)
349 elif gp.op == 'COPY':
350 copies.append((gp.oldpath, gp.path))
351 elif gp.op == 'DELETE':
352 removes.add(gp.path)
353
354 wctx = repo[None]
355 for src, dst in copies:
356 wctx.copy(src, dst)
357 if (not similarity) and removes:
358 wctx.remove(sorted(removes), True)
359
360 for f in patches:
361 gp = patches[f]
362 if gp and gp.mode:
363 islink, isexec = gp.mode
364 dst = repo.wjoin(gp.path)
365 # patch won't create empty files
366 if gp.op == 'ADD' and not os.path.exists(dst):
367 flags = (isexec and 'x' or '') + (islink and 'l' or '')
368 repo.wwrite(gp.path, '', flags)
369 util.set_flags(dst, islink, isexec)
370 addremove(repo, cfiles, similarity=similarity)
371 files = patches.keys()
372 files.extend([r for r in removes if r not in files])
373 return sorted(files)
374
332 375 def copy(ui, repo, pats, opts, rename=False):
333 376 # called with the repo lock held
334 377 #
335 378 # hgsep => pathname that uses "/" to separate directories
336 379 # ossep => pathname that uses os.sep to separate directories
337 380 cwd = repo.getcwd()
338 381 targets = {}
339 382 after = opts.get("after")
340 383 dryrun = opts.get("dry_run")
341 384 wctx = repo[None]
342 385
343 386 def walkpat(pat):
344 387 srcs = []
345 388 badstates = after and '?' or '?r'
346 389 m = match(repo, [pat], opts, globbed=True)
347 390 for abs in repo.walk(m):
348 391 state = repo.dirstate[abs]
349 392 rel = m.rel(abs)
350 393 exact = m.exact(abs)
351 394 if state in badstates:
352 395 if exact and state == '?':
353 396 ui.warn(_('%s: not copying - file is not managed\n') % rel)
354 397 if exact and state == 'r':
355 398 ui.warn(_('%s: not copying - file has been marked for'
356 399 ' remove\n') % rel)
357 400 continue
358 401 # abs: hgsep
359 402 # rel: ossep
360 403 srcs.append((abs, rel, exact))
361 404 return srcs
362 405
363 406 # abssrc: hgsep
364 407 # relsrc: ossep
365 408 # otarget: ossep
366 409 def copyfile(abssrc, relsrc, otarget, exact):
367 410 abstarget = util.canonpath(repo.root, cwd, otarget)
368 411 reltarget = repo.pathto(abstarget, cwd)
369 412 target = repo.wjoin(abstarget)
370 413 src = repo.wjoin(abssrc)
371 414 state = repo.dirstate[abstarget]
372 415
373 416 # check for collisions
374 417 prevsrc = targets.get(abstarget)
375 418 if prevsrc is not None:
376 419 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
377 420 (reltarget, repo.pathto(abssrc, cwd),
378 421 repo.pathto(prevsrc, cwd)))
379 422 return
380 423
381 424 # check for overwrites
382 425 exists = os.path.exists(target)
383 426 if not after and exists or after and state in 'mn':
384 427 if not opts['force']:
385 428 ui.warn(_('%s: not overwriting - file exists\n') %
386 429 reltarget)
387 430 return
388 431
389 432 if after:
390 433 if not exists:
391 434 if rename:
392 435 ui.warn(_('%s: not recording move - %s does not exist\n') %
393 436 (relsrc, reltarget))
394 437 else:
395 438 ui.warn(_('%s: not recording copy - %s does not exist\n') %
396 439 (relsrc, reltarget))
397 440 return
398 441 elif not dryrun:
399 442 try:
400 443 if exists:
401 444 os.unlink(target)
402 445 targetdir = os.path.dirname(target) or '.'
403 446 if not os.path.isdir(targetdir):
404 447 os.makedirs(targetdir)
405 448 util.copyfile(src, target)
406 449 except IOError, inst:
407 450 if inst.errno == errno.ENOENT:
408 451 ui.warn(_('%s: deleted in working copy\n') % relsrc)
409 452 else:
410 453 ui.warn(_('%s: cannot copy - %s\n') %
411 454 (relsrc, inst.strerror))
412 455 return True # report a failure
413 456
414 457 if ui.verbose or not exact:
415 458 if rename:
416 459 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
417 460 else:
418 461 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
419 462
420 463 targets[abstarget] = abssrc
421 464
422 465 # fix up dirstate
423 466 origsrc = repo.dirstate.copied(abssrc) or abssrc
424 467 if abstarget == origsrc: # copying back a copy?
425 468 if state not in 'mn' and not dryrun:
426 469 repo.dirstate.normallookup(abstarget)
427 470 else:
428 471 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
429 472 if not ui.quiet:
430 473 ui.warn(_("%s has not been committed yet, so no copy "
431 474 "data will be stored for %s.\n")
432 475 % (repo.pathto(origsrc, cwd), reltarget))
433 476 if repo.dirstate[abstarget] in '?r' and not dryrun:
434 477 wctx.add([abstarget])
435 478 elif not dryrun:
436 479 wctx.copy(origsrc, abstarget)
437 480
438 481 if rename and not dryrun:
439 482 wctx.remove([abssrc], not after)
440 483
441 484 # pat: ossep
442 485 # dest ossep
443 486 # srcs: list of (hgsep, hgsep, ossep, bool)
444 487 # return: function that takes hgsep and returns ossep
445 488 def targetpathfn(pat, dest, srcs):
446 489 if os.path.isdir(pat):
447 490 abspfx = util.canonpath(repo.root, cwd, pat)
448 491 abspfx = util.localpath(abspfx)
449 492 if destdirexists:
450 493 striplen = len(os.path.split(abspfx)[0])
451 494 else:
452 495 striplen = len(abspfx)
453 496 if striplen:
454 497 striplen += len(os.sep)
455 498 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
456 499 elif destdirexists:
457 500 res = lambda p: os.path.join(dest,
458 501 os.path.basename(util.localpath(p)))
459 502 else:
460 503 res = lambda p: dest
461 504 return res
462 505
463 506 # pat: ossep
464 507 # dest ossep
465 508 # srcs: list of (hgsep, hgsep, ossep, bool)
466 509 # return: function that takes hgsep and returns ossep
467 510 def targetpathafterfn(pat, dest, srcs):
468 511 if matchmod.patkind(pat):
469 512 # a mercurial pattern
470 513 res = lambda p: os.path.join(dest,
471 514 os.path.basename(util.localpath(p)))
472 515 else:
473 516 abspfx = util.canonpath(repo.root, cwd, pat)
474 517 if len(abspfx) < len(srcs[0][0]):
475 518 # A directory. Either the target path contains the last
476 519 # component of the source path or it does not.
477 520 def evalpath(striplen):
478 521 score = 0
479 522 for s in srcs:
480 523 t = os.path.join(dest, util.localpath(s[0])[striplen:])
481 524 if os.path.exists(t):
482 525 score += 1
483 526 return score
484 527
485 528 abspfx = util.localpath(abspfx)
486 529 striplen = len(abspfx)
487 530 if striplen:
488 531 striplen += len(os.sep)
489 532 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
490 533 score = evalpath(striplen)
491 534 striplen1 = len(os.path.split(abspfx)[0])
492 535 if striplen1:
493 536 striplen1 += len(os.sep)
494 537 if evalpath(striplen1) > score:
495 538 striplen = striplen1
496 539 res = lambda p: os.path.join(dest,
497 540 util.localpath(p)[striplen:])
498 541 else:
499 542 # a file
500 543 if destdirexists:
501 544 res = lambda p: os.path.join(dest,
502 545 os.path.basename(util.localpath(p)))
503 546 else:
504 547 res = lambda p: dest
505 548 return res
506 549
507 550
508 551 pats = expandpats(pats)
509 552 if not pats:
510 553 raise util.Abort(_('no source or destination specified'))
511 554 if len(pats) == 1:
512 555 raise util.Abort(_('no destination specified'))
513 556 dest = pats.pop()
514 557 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
515 558 if not destdirexists:
516 559 if len(pats) > 1 or matchmod.patkind(pats[0]):
517 560 raise util.Abort(_('with multiple sources, destination must be an '
518 561 'existing directory'))
519 562 if util.endswithsep(dest):
520 563 raise util.Abort(_('destination %s is not a directory') % dest)
521 564
522 565 tfn = targetpathfn
523 566 if after:
524 567 tfn = targetpathafterfn
525 568 copylist = []
526 569 for pat in pats:
527 570 srcs = walkpat(pat)
528 571 if not srcs:
529 572 continue
530 573 copylist.append((tfn(pat, dest, srcs), srcs))
531 574 if not copylist:
532 575 raise util.Abort(_('no files to copy'))
533 576
534 577 errors = 0
535 578 for targetpath, srcs in copylist:
536 579 for abssrc, relsrc, exact in srcs:
537 580 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
538 581 errors += 1
539 582
540 583 if errors:
541 584 ui.warn(_('(consider using --after)\n'))
542 585
543 586 return errors != 0
544 587
545 588 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
546 589 runargs=None, appendpid=False):
547 590 '''Run a command as a service.'''
548 591
549 592 if opts['daemon'] and not opts['daemon_pipefds']:
550 593 # Signal child process startup with file removal
551 594 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
552 595 os.close(lockfd)
553 596 try:
554 597 if not runargs:
555 598 runargs = util.hgcmd() + sys.argv[1:]
556 599 runargs.append('--daemon-pipefds=%s' % lockpath)
557 600 # Don't pass --cwd to the child process, because we've already
558 601 # changed directory.
559 602 for i in xrange(1, len(runargs)):
560 603 if runargs[i].startswith('--cwd='):
561 604 del runargs[i]
562 605 break
563 606 elif runargs[i].startswith('--cwd'):
564 607 del runargs[i:i + 2]
565 608 break
566 609 def condfn():
567 610 return not os.path.exists(lockpath)
568 611 pid = util.rundetached(runargs, condfn)
569 612 if pid < 0:
570 613 raise util.Abort(_('child process failed to start'))
571 614 finally:
572 615 try:
573 616 os.unlink(lockpath)
574 617 except OSError, e:
575 618 if e.errno != errno.ENOENT:
576 619 raise
577 620 if parentfn:
578 621 return parentfn(pid)
579 622 else:
580 623 return
581 624
582 625 if initfn:
583 626 initfn()
584 627
585 628 if opts['pid_file']:
586 629 mode = appendpid and 'a' or 'w'
587 630 fp = open(opts['pid_file'], mode)
588 631 fp.write(str(os.getpid()) + '\n')
589 632 fp.close()
590 633
591 634 if opts['daemon_pipefds']:
592 635 lockpath = opts['daemon_pipefds']
593 636 try:
594 637 os.setsid()
595 638 except AttributeError:
596 639 pass
597 640 os.unlink(lockpath)
598 641 util.hidewindow()
599 642 sys.stdout.flush()
600 643 sys.stderr.flush()
601 644
602 645 nullfd = os.open(util.nulldev, os.O_RDWR)
603 646 logfilefd = nullfd
604 647 if logfile:
605 648 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
606 649 os.dup2(nullfd, 0)
607 650 os.dup2(logfilefd, 1)
608 651 os.dup2(logfilefd, 2)
609 652 if nullfd not in (0, 1, 2):
610 653 os.close(nullfd)
611 654 if logfile and logfilefd not in (0, 1, 2):
612 655 os.close(logfilefd)
613 656
614 657 if runfn:
615 658 return runfn()
616 659
617 660 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
618 661 opts=None):
619 662 '''export changesets as hg patches.'''
620 663
621 664 total = len(revs)
622 665 revwidth = max([len(str(rev)) for rev in revs])
623 666
624 667 def single(rev, seqno, fp):
625 668 ctx = repo[rev]
626 669 node = ctx.node()
627 670 parents = [p.node() for p in ctx.parents() if p]
628 671 branch = ctx.branch()
629 672 if switch_parent:
630 673 parents.reverse()
631 674 prev = (parents and parents[0]) or nullid
632 675
633 676 if not fp:
634 677 fp = make_file(repo, template, node, total=total, seqno=seqno,
635 678 revwidth=revwidth, mode='ab')
636 679 if fp != sys.stdout and hasattr(fp, 'name'):
637 680 repo.ui.note("%s\n" % fp.name)
638 681
639 682 fp.write("# HG changeset patch\n")
640 683 fp.write("# User %s\n" % ctx.user())
641 684 fp.write("# Date %d %d\n" % ctx.date())
642 685 if branch and branch != 'default':
643 686 fp.write("# Branch %s\n" % branch)
644 687 fp.write("# Node ID %s\n" % hex(node))
645 688 fp.write("# Parent %s\n" % hex(prev))
646 689 if len(parents) > 1:
647 690 fp.write("# Parent %s\n" % hex(parents[1]))
648 691 fp.write(ctx.description().rstrip())
649 692 fp.write("\n\n")
650 693
651 694 for chunk in patch.diff(repo, prev, node, opts=opts):
652 695 fp.write(chunk)
653 696
654 697 for seqno, rev in enumerate(revs):
655 698 single(rev, seqno + 1, fp)
656 699
657 700 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
658 701 changes=None, stat=False, fp=None, prefix='',
659 702 listsubrepos=False):
660 703 '''show diff or diffstat.'''
661 704 if fp is None:
662 705 write = ui.write
663 706 else:
664 707 def write(s, **kw):
665 708 fp.write(s)
666 709
667 710 if stat:
668 711 diffopts = diffopts.copy(context=0)
669 712 width = 80
670 713 if not ui.plain():
671 714 width = util.termwidth()
672 715 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
673 716 prefix=prefix)
674 717 for chunk, label in patch.diffstatui(util.iterlines(chunks),
675 718 width=width,
676 719 git=diffopts.git):
677 720 write(chunk, label=label)
678 721 else:
679 722 for chunk, label in patch.diffui(repo, node1, node2, match,
680 723 changes, diffopts, prefix=prefix):
681 724 write(chunk, label=label)
682 725
683 726 if listsubrepos:
684 727 ctx1 = repo[node1]
685 728 ctx2 = repo[node2]
686 729 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
687 730 if node2 is not None:
688 731 node2 = ctx2.substate[subpath][1]
689 732 submatch = matchmod.narrowmatcher(subpath, match)
690 733 sub.diff(diffopts, node2, submatch, changes=changes,
691 734 stat=stat, fp=fp, prefix=prefix)
692 735
693 736 class changeset_printer(object):
694 737 '''show changeset information when templating not requested.'''
695 738
696 739 def __init__(self, ui, repo, patch, diffopts, buffered):
697 740 self.ui = ui
698 741 self.repo = repo
699 742 self.buffered = buffered
700 743 self.patch = patch
701 744 self.diffopts = diffopts
702 745 self.header = {}
703 746 self.hunk = {}
704 747 self.lastheader = None
705 748 self.footer = None
706 749
707 750 def flush(self, rev):
708 751 if rev in self.header:
709 752 h = self.header[rev]
710 753 if h != self.lastheader:
711 754 self.lastheader = h
712 755 self.ui.write(h)
713 756 del self.header[rev]
714 757 if rev in self.hunk:
715 758 self.ui.write(self.hunk[rev])
716 759 del self.hunk[rev]
717 760 return 1
718 761 return 0
719 762
720 763 def close(self):
721 764 if self.footer:
722 765 self.ui.write(self.footer)
723 766
724 767 def show(self, ctx, copies=None, matchfn=None, **props):
725 768 if self.buffered:
726 769 self.ui.pushbuffer()
727 770 self._show(ctx, copies, matchfn, props)
728 771 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
729 772 else:
730 773 self._show(ctx, copies, matchfn, props)
731 774
732 775 def _show(self, ctx, copies, matchfn, props):
733 776 '''show a single changeset or file revision'''
734 777 changenode = ctx.node()
735 778 rev = ctx.rev()
736 779
737 780 if self.ui.quiet:
738 781 self.ui.write("%d:%s\n" % (rev, short(changenode)),
739 782 label='log.node')
740 783 return
741 784
742 785 log = self.repo.changelog
743 786 date = util.datestr(ctx.date())
744 787
745 788 hexfunc = self.ui.debugflag and hex or short
746 789
747 790 parents = [(p, hexfunc(log.node(p)))
748 791 for p in self._meaningful_parentrevs(log, rev)]
749 792
750 793 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
751 794 label='log.changeset')
752 795
753 796 branch = ctx.branch()
754 797 # don't show the default branch name
755 798 if branch != 'default':
756 799 branch = encoding.tolocal(branch)
757 800 self.ui.write(_("branch: %s\n") % branch,
758 801 label='log.branch')
759 802 for tag in self.repo.nodetags(changenode):
760 803 self.ui.write(_("tag: %s\n") % tag,
761 804 label='log.tag')
762 805 for parent in parents:
763 806 self.ui.write(_("parent: %d:%s\n") % parent,
764 807 label='log.parent')
765 808
766 809 if self.ui.debugflag:
767 810 mnode = ctx.manifestnode()
768 811 self.ui.write(_("manifest: %d:%s\n") %
769 812 (self.repo.manifest.rev(mnode), hex(mnode)),
770 813 label='ui.debug log.manifest')
771 814 self.ui.write(_("user: %s\n") % ctx.user(),
772 815 label='log.user')
773 816 self.ui.write(_("date: %s\n") % date,
774 817 label='log.date')
775 818
776 819 if self.ui.debugflag:
777 820 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
778 821 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
779 822 files):
780 823 if value:
781 824 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
782 825 label='ui.debug log.files')
783 826 elif ctx.files() and self.ui.verbose:
784 827 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
785 828 label='ui.note log.files')
786 829 if copies and self.ui.verbose:
787 830 copies = ['%s (%s)' % c for c in copies]
788 831 self.ui.write(_("copies: %s\n") % ' '.join(copies),
789 832 label='ui.note log.copies')
790 833
791 834 extra = ctx.extra()
792 835 if extra and self.ui.debugflag:
793 836 for key, value in sorted(extra.items()):
794 837 self.ui.write(_("extra: %s=%s\n")
795 838 % (key, value.encode('string_escape')),
796 839 label='ui.debug log.extra')
797 840
798 841 description = ctx.description().strip()
799 842 if description:
800 843 if self.ui.verbose:
801 844 self.ui.write(_("description:\n"),
802 845 label='ui.note log.description')
803 846 self.ui.write(description,
804 847 label='ui.note log.description')
805 848 self.ui.write("\n\n")
806 849 else:
807 850 self.ui.write(_("summary: %s\n") %
808 851 description.splitlines()[0],
809 852 label='log.summary')
810 853 self.ui.write("\n")
811 854
812 855 self.showpatch(changenode, matchfn)
813 856
814 857 def showpatch(self, node, matchfn):
815 858 if not matchfn:
816 859 matchfn = self.patch
817 860 if matchfn:
818 861 stat = self.diffopts.get('stat')
819 862 diff = self.diffopts.get('patch')
820 863 diffopts = patch.diffopts(self.ui, self.diffopts)
821 864 prev = self.repo.changelog.parents(node)[0]
822 865 if stat:
823 866 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
824 867 match=matchfn, stat=True)
825 868 if diff:
826 869 if stat:
827 870 self.ui.write("\n")
828 871 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
829 872 match=matchfn, stat=False)
830 873 self.ui.write("\n")
831 874
832 875 def _meaningful_parentrevs(self, log, rev):
833 876 """Return list of meaningful (or all if debug) parentrevs for rev.
834 877
835 878 For merges (two non-nullrev revisions) both parents are meaningful.
836 879 Otherwise the first parent revision is considered meaningful if it
837 880 is not the preceding revision.
838 881 """
839 882 parents = log.parentrevs(rev)
840 883 if not self.ui.debugflag and parents[1] == nullrev:
841 884 if parents[0] >= rev - 1:
842 885 parents = []
843 886 else:
844 887 parents = [parents[0]]
845 888 return parents
846 889
847 890
848 891 class changeset_templater(changeset_printer):
849 892 '''format changeset information.'''
850 893
851 894 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
852 895 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
853 896 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
854 897 defaulttempl = {
855 898 'parent': '{rev}:{node|formatnode} ',
856 899 'manifest': '{rev}:{node|formatnode}',
857 900 'file_copy': '{name} ({source})',
858 901 'extra': '{key}={value|stringescape}'
859 902 }
860 903 # filecopy is preserved for compatibility reasons
861 904 defaulttempl['filecopy'] = defaulttempl['file_copy']
862 905 self.t = templater.templater(mapfile, {'formatnode': formatnode},
863 906 cache=defaulttempl)
864 907 self.cache = {}
865 908
866 909 def use_template(self, t):
867 910 '''set template string to use'''
868 911 self.t.cache['changeset'] = t
869 912
870 913 def _meaningful_parentrevs(self, ctx):
871 914 """Return list of meaningful (or all if debug) parentrevs for rev.
872 915 """
873 916 parents = ctx.parents()
874 917 if len(parents) > 1:
875 918 return parents
876 919 if self.ui.debugflag:
877 920 return [parents[0], self.repo['null']]
878 921 if parents[0].rev() >= ctx.rev() - 1:
879 922 return []
880 923 return parents
881 924
882 925 def _show(self, ctx, copies, matchfn, props):
883 926 '''show a single changeset or file revision'''
884 927
885 928 showlist = templatekw.showlist
886 929
887 930 # showparents() behaviour depends on ui trace level which
888 931 # causes unexpected behaviours at templating level and makes
889 932 # it harder to extract it in a standalone function. Its
890 933 # behaviour cannot be changed so leave it here for now.
891 934 def showparents(**args):
892 935 ctx = args['ctx']
893 936 parents = [[('rev', p.rev()), ('node', p.hex())]
894 937 for p in self._meaningful_parentrevs(ctx)]
895 938 return showlist('parent', parents, **args)
896 939
897 940 props = props.copy()
898 941 props.update(templatekw.keywords)
899 942 props['parents'] = showparents
900 943 props['templ'] = self.t
901 944 props['ctx'] = ctx
902 945 props['repo'] = self.repo
903 946 props['revcache'] = {'copies': copies}
904 947 props['cache'] = self.cache
905 948
906 949 # find correct templates for current mode
907 950
908 951 tmplmodes = [
909 952 (True, None),
910 953 (self.ui.verbose, 'verbose'),
911 954 (self.ui.quiet, 'quiet'),
912 955 (self.ui.debugflag, 'debug'),
913 956 ]
914 957
915 958 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
916 959 for mode, postfix in tmplmodes:
917 960 for type in types:
918 961 cur = postfix and ('%s_%s' % (type, postfix)) or type
919 962 if mode and cur in self.t:
920 963 types[type] = cur
921 964
922 965 try:
923 966
924 967 # write header
925 968 if types['header']:
926 969 h = templater.stringify(self.t(types['header'], **props))
927 970 if self.buffered:
928 971 self.header[ctx.rev()] = h
929 972 else:
930 973 if self.lastheader != h:
931 974 self.lastheader = h
932 975 self.ui.write(h)
933 976
934 977 # write changeset metadata, then patch if requested
935 978 key = types['changeset']
936 979 self.ui.write(templater.stringify(self.t(key, **props)))
937 980 self.showpatch(ctx.node(), matchfn)
938 981
939 982 if types['footer']:
940 983 if not self.footer:
941 984 self.footer = templater.stringify(self.t(types['footer'],
942 985 **props))
943 986
944 987 except KeyError, inst:
945 988 msg = _("%s: no key named '%s'")
946 989 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
947 990 except SyntaxError, inst:
948 991 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
949 992
950 993 def show_changeset(ui, repo, opts, buffered=False):
951 994 """show one changeset using template or regular display.
952 995
953 996 Display format will be the first non-empty hit of:
954 997 1. option 'template'
955 998 2. option 'style'
956 999 3. [ui] setting 'logtemplate'
957 1000 4. [ui] setting 'style'
958 1001 If all of these values are either the unset or the empty string,
959 1002 regular display via changeset_printer() is done.
960 1003 """
961 1004 # options
962 1005 patch = False
963 1006 if opts.get('patch') or opts.get('stat'):
964 1007 patch = matchall(repo)
965 1008
966 1009 tmpl = opts.get('template')
967 1010 style = None
968 1011 if tmpl:
969 1012 tmpl = templater.parsestring(tmpl, quoted=False)
970 1013 else:
971 1014 style = opts.get('style')
972 1015
973 1016 # ui settings
974 1017 if not (tmpl or style):
975 1018 tmpl = ui.config('ui', 'logtemplate')
976 1019 if tmpl:
977 1020 tmpl = templater.parsestring(tmpl)
978 1021 else:
979 1022 style = util.expandpath(ui.config('ui', 'style', ''))
980 1023
981 1024 if not (tmpl or style):
982 1025 return changeset_printer(ui, repo, patch, opts, buffered)
983 1026
984 1027 mapfile = None
985 1028 if style and not tmpl:
986 1029 mapfile = style
987 1030 if not os.path.split(mapfile)[0]:
988 1031 mapname = (templater.templatepath('map-cmdline.' + mapfile)
989 1032 or templater.templatepath(mapfile))
990 1033 if mapname:
991 1034 mapfile = mapname
992 1035
993 1036 try:
994 1037 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
995 1038 except SyntaxError, inst:
996 1039 raise util.Abort(inst.args[0])
997 1040 if tmpl:
998 1041 t.use_template(tmpl)
999 1042 return t
1000 1043
1001 1044 def finddate(ui, repo, date):
1002 1045 """Find the tipmost changeset that matches the given date spec"""
1003 1046
1004 1047 df = util.matchdate(date)
1005 1048 m = matchall(repo)
1006 1049 results = {}
1007 1050
1008 1051 def prep(ctx, fns):
1009 1052 d = ctx.date()
1010 1053 if df(d[0]):
1011 1054 results[ctx.rev()] = d
1012 1055
1013 1056 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1014 1057 rev = ctx.rev()
1015 1058 if rev in results:
1016 1059 ui.status(_("Found revision %s from %s\n") %
1017 1060 (rev, util.datestr(results[rev])))
1018 1061 return str(rev)
1019 1062
1020 1063 raise util.Abort(_("revision matching date not found"))
1021 1064
1022 1065 def walkchangerevs(repo, match, opts, prepare):
1023 1066 '''Iterate over files and the revs in which they changed.
1024 1067
1025 1068 Callers most commonly need to iterate backwards over the history
1026 1069 in which they are interested. Doing so has awful (quadratic-looking)
1027 1070 performance, so we use iterators in a "windowed" way.
1028 1071
1029 1072 We walk a window of revisions in the desired order. Within the
1030 1073 window, we first walk forwards to gather data, then in the desired
1031 1074 order (usually backwards) to display it.
1032 1075
1033 1076 This function returns an iterator yielding contexts. Before
1034 1077 yielding each context, the iterator will first call the prepare
1035 1078 function on each context in the window in forward order.'''
1036 1079
1037 1080 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1038 1081 if start < end:
1039 1082 while start < end:
1040 1083 yield start, min(windowsize, end - start)
1041 1084 start += windowsize
1042 1085 if windowsize < sizelimit:
1043 1086 windowsize *= 2
1044 1087 else:
1045 1088 while start > end:
1046 1089 yield start, min(windowsize, start - end - 1)
1047 1090 start -= windowsize
1048 1091 if windowsize < sizelimit:
1049 1092 windowsize *= 2
1050 1093
1051 1094 follow = opts.get('follow') or opts.get('follow_first')
1052 1095
1053 1096 if not len(repo):
1054 1097 return []
1055 1098
1056 1099 if follow:
1057 1100 defrange = '%s:0' % repo['.'].rev()
1058 1101 else:
1059 1102 defrange = '-1:0'
1060 1103 revs = revrange(repo, opts['rev'] or [defrange])
1061 1104 if not revs:
1062 1105 return []
1063 1106 wanted = set()
1064 1107 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1065 1108 fncache = {}
1066 1109 change = util.cachefunc(repo.changectx)
1067 1110
1068 1111 # First step is to fill wanted, the set of revisions that we want to yield.
1069 1112 # When it does not induce extra cost, we also fill fncache for revisions in
1070 1113 # wanted: a cache of filenames that were changed (ctx.files()) and that
1071 1114 # match the file filtering conditions.
1072 1115
1073 1116 if not slowpath and not match.files():
1074 1117 # No files, no patterns. Display all revs.
1075 1118 wanted = set(revs)
1076 1119 copies = []
1077 1120
1078 1121 if not slowpath:
1079 1122 # We only have to read through the filelog to find wanted revisions
1080 1123
1081 1124 minrev, maxrev = min(revs), max(revs)
1082 1125 def filerevgen(filelog, last):
1083 1126 """
1084 1127 Only files, no patterns. Check the history of each file.
1085 1128
1086 1129 Examines filelog entries within minrev, maxrev linkrev range
1087 1130 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1088 1131 tuples in backwards order
1089 1132 """
1090 1133 cl_count = len(repo)
1091 1134 revs = []
1092 1135 for j in xrange(0, last + 1):
1093 1136 linkrev = filelog.linkrev(j)
1094 1137 if linkrev < minrev:
1095 1138 continue
1096 1139 # only yield rev for which we have the changelog, it can
1097 1140 # happen while doing "hg log" during a pull or commit
1098 1141 if linkrev > maxrev or linkrev >= cl_count:
1099 1142 break
1100 1143
1101 1144 parentlinkrevs = []
1102 1145 for p in filelog.parentrevs(j):
1103 1146 if p != nullrev:
1104 1147 parentlinkrevs.append(filelog.linkrev(p))
1105 1148 n = filelog.node(j)
1106 1149 revs.append((linkrev, parentlinkrevs,
1107 1150 follow and filelog.renamed(n)))
1108 1151
1109 1152 return reversed(revs)
1110 1153 def iterfiles():
1111 1154 for filename in match.files():
1112 1155 yield filename, None
1113 1156 for filename_node in copies:
1114 1157 yield filename_node
1115 1158 for file_, node in iterfiles():
1116 1159 filelog = repo.file(file_)
1117 1160 if not len(filelog):
1118 1161 if node is None:
1119 1162 # A zero count may be a directory or deleted file, so
1120 1163 # try to find matching entries on the slow path.
1121 1164 if follow:
1122 1165 raise util.Abort(
1123 1166 _('cannot follow nonexistent file: "%s"') % file_)
1124 1167 slowpath = True
1125 1168 break
1126 1169 else:
1127 1170 continue
1128 1171
1129 1172 if node is None:
1130 1173 last = len(filelog) - 1
1131 1174 else:
1132 1175 last = filelog.rev(node)
1133 1176
1134 1177
1135 1178 # keep track of all ancestors of the file
1136 1179 ancestors = set([filelog.linkrev(last)])
1137 1180
1138 1181 # iterate from latest to oldest revision
1139 1182 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1140 1183 if rev not in ancestors:
1141 1184 continue
1142 1185 # XXX insert 1327 fix here
1143 1186 if flparentlinkrevs:
1144 1187 ancestors.update(flparentlinkrevs)
1145 1188
1146 1189 fncache.setdefault(rev, []).append(file_)
1147 1190 wanted.add(rev)
1148 1191 if copied:
1149 1192 copies.append(copied)
1150 1193 if slowpath:
1151 1194 # We have to read the changelog to match filenames against
1152 1195 # changed files
1153 1196
1154 1197 if follow:
1155 1198 raise util.Abort(_('can only follow copies/renames for explicit '
1156 1199 'filenames'))
1157 1200
1158 1201 # The slow path checks files modified in every changeset.
1159 1202 for i in sorted(revs):
1160 1203 ctx = change(i)
1161 1204 matches = filter(match, ctx.files())
1162 1205 if matches:
1163 1206 fncache[i] = matches
1164 1207 wanted.add(i)
1165 1208
1166 1209 class followfilter(object):
1167 1210 def __init__(self, onlyfirst=False):
1168 1211 self.startrev = nullrev
1169 1212 self.roots = set()
1170 1213 self.onlyfirst = onlyfirst
1171 1214
1172 1215 def match(self, rev):
1173 1216 def realparents(rev):
1174 1217 if self.onlyfirst:
1175 1218 return repo.changelog.parentrevs(rev)[0:1]
1176 1219 else:
1177 1220 return filter(lambda x: x != nullrev,
1178 1221 repo.changelog.parentrevs(rev))
1179 1222
1180 1223 if self.startrev == nullrev:
1181 1224 self.startrev = rev
1182 1225 return True
1183 1226
1184 1227 if rev > self.startrev:
1185 1228 # forward: all descendants
1186 1229 if not self.roots:
1187 1230 self.roots.add(self.startrev)
1188 1231 for parent in realparents(rev):
1189 1232 if parent in self.roots:
1190 1233 self.roots.add(rev)
1191 1234 return True
1192 1235 else:
1193 1236 # backwards: all parents
1194 1237 if not self.roots:
1195 1238 self.roots.update(realparents(self.startrev))
1196 1239 if rev in self.roots:
1197 1240 self.roots.remove(rev)
1198 1241 self.roots.update(realparents(rev))
1199 1242 return True
1200 1243
1201 1244 return False
1202 1245
1203 1246 # it might be worthwhile to do this in the iterator if the rev range
1204 1247 # is descending and the prune args are all within that range
1205 1248 for rev in opts.get('prune', ()):
1206 1249 rev = repo.changelog.rev(repo.lookup(rev))
1207 1250 ff = followfilter()
1208 1251 stop = min(revs[0], revs[-1])
1209 1252 for x in xrange(rev, stop - 1, -1):
1210 1253 if ff.match(x):
1211 1254 wanted.discard(x)
1212 1255
1213 1256 # Now that wanted is correctly initialized, we can iterate over the
1214 1257 # revision range, yielding only revisions in wanted.
1215 1258 def iterate():
1216 1259 if follow and not match.files():
1217 1260 ff = followfilter(onlyfirst=opts.get('follow_first'))
1218 1261 def want(rev):
1219 1262 return ff.match(rev) and rev in wanted
1220 1263 else:
1221 1264 def want(rev):
1222 1265 return rev in wanted
1223 1266
1224 1267 for i, window in increasing_windows(0, len(revs)):
1225 1268 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1226 1269 for rev in sorted(nrevs):
1227 1270 fns = fncache.get(rev)
1228 1271 ctx = change(rev)
1229 1272 if not fns:
1230 1273 def fns_generator():
1231 1274 for f in ctx.files():
1232 1275 if match(f):
1233 1276 yield f
1234 1277 fns = fns_generator()
1235 1278 prepare(ctx, fns)
1236 1279 for rev in nrevs:
1237 1280 yield change(rev)
1238 1281 return iterate()
1239 1282
1240 1283 def commit(ui, repo, commitfunc, pats, opts):
1241 1284 '''commit the specified files or all outstanding changes'''
1242 1285 date = opts.get('date')
1243 1286 if date:
1244 1287 opts['date'] = util.parsedate(date)
1245 1288 message = logmessage(opts)
1246 1289
1247 1290 # extract addremove carefully -- this function can be called from a command
1248 1291 # that doesn't support addremove
1249 1292 if opts.get('addremove'):
1250 1293 addremove(repo, pats, opts)
1251 1294
1252 1295 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1253 1296
1254 1297 def commiteditor(repo, ctx, subs):
1255 1298 if ctx.description():
1256 1299 return ctx.description()
1257 1300 return commitforceeditor(repo, ctx, subs)
1258 1301
1259 1302 def commitforceeditor(repo, ctx, subs):
1260 1303 edittext = []
1261 1304 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1262 1305 if ctx.description():
1263 1306 edittext.append(ctx.description())
1264 1307 edittext.append("")
1265 1308 edittext.append("") # Empty line between message and comments.
1266 1309 edittext.append(_("HG: Enter commit message."
1267 1310 " Lines beginning with 'HG:' are removed."))
1268 1311 edittext.append(_("HG: Leave message empty to abort commit."))
1269 1312 edittext.append("HG: --")
1270 1313 edittext.append(_("HG: user: %s") % ctx.user())
1271 1314 if ctx.p2():
1272 1315 edittext.append(_("HG: branch merge"))
1273 1316 if ctx.branch():
1274 1317 edittext.append(_("HG: branch '%s'")
1275 1318 % encoding.tolocal(ctx.branch()))
1276 1319 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1277 1320 edittext.extend([_("HG: added %s") % f for f in added])
1278 1321 edittext.extend([_("HG: changed %s") % f for f in modified])
1279 1322 edittext.extend([_("HG: removed %s") % f for f in removed])
1280 1323 if not added and not modified and not removed:
1281 1324 edittext.append(_("HG: no files changed"))
1282 1325 edittext.append("")
1283 1326 # run editor in the repository root
1284 1327 olddir = os.getcwd()
1285 1328 os.chdir(repo.root)
1286 1329 text = repo.ui.edit("\n".join(edittext), ctx.user())
1287 1330 text = re.sub("(?m)^HG:.*\n", "", text)
1288 1331 os.chdir(olddir)
1289 1332
1290 1333 if not text.strip():
1291 1334 raise util.Abort(_("empty commit message"))
1292 1335
1293 1336 return text
@@ -1,4522 +1,4522 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, nullid, nullrev, short
9 9 from lock import release
10 10 from i18n import _, gettext
11 11 import os, re, sys, difflib, time, tempfile
12 12 import hg, util, revlog, bundlerepo, extensions, copies, error
13 13 import patch, help, mdiff, url, encoding, templatekw, discovery
14 14 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
15 15 import merge as mergemod
16 16 import minirst, revset
17 17 import dagparser
18 18
19 19 # Commands start here, listed alphabetically
20 20
21 21 def add(ui, repo, *pats, **opts):
22 22 """add the specified files on the next commit
23 23
24 24 Schedule files to be version controlled and added to the
25 25 repository.
26 26
27 27 The files will be added to the repository at the next commit. To
28 28 undo an add before that, see :hg:`forget`.
29 29
30 30 If no names are given, add all files to the repository.
31 31
32 32 .. container:: verbose
33 33
34 34 An example showing how new (unknown) files are added
35 35 automatically by :hg:`add`::
36 36
37 37 $ ls
38 38 foo.c
39 39 $ hg status
40 40 ? foo.c
41 41 $ hg add
42 42 adding foo.c
43 43 $ hg status
44 44 A foo.c
45 45
46 46 Returns 0 if all files are successfully added.
47 47 """
48 48
49 49 bad = []
50 50 names = []
51 51 m = cmdutil.match(repo, pats, opts)
52 52 oldbad = m.bad
53 53 m.bad = lambda x, y: bad.append(x) or oldbad(x, y)
54 54
55 55 for f in repo.walk(m):
56 56 exact = m.exact(f)
57 57 if exact or f not in repo.dirstate:
58 58 names.append(f)
59 59 if ui.verbose or not exact:
60 60 ui.status(_('adding %s\n') % m.rel(f))
61 61 if not opts.get('dry_run'):
62 62 rejected = repo[None].add(names)
63 63 bad += [f for f in rejected if f in m.files()]
64 64 return bad and 1 or 0
65 65
66 66 def addremove(ui, repo, *pats, **opts):
67 67 """add all new files, delete all missing files
68 68
69 69 Add all new files and remove all missing files from the
70 70 repository.
71 71
72 72 New files are ignored if they match any of the patterns in
73 73 .hgignore. As with add, these changes take effect at the next
74 74 commit.
75 75
76 76 Use the -s/--similarity option to detect renamed files. With a
77 77 parameter greater than 0, this compares every removed file with
78 78 every added file and records those similar enough as renames. This
79 79 option takes a percentage between 0 (disabled) and 100 (files must
80 80 be identical) as its parameter. Detecting renamed files this way
81 81 can be expensive. After using this option, :hg:`status -C` can be
82 82 used to check which files were identified as moved or renamed.
83 83
84 84 Returns 0 if all files are successfully added.
85 85 """
86 86 try:
87 87 sim = float(opts.get('similarity') or 100)
88 88 except ValueError:
89 89 raise util.Abort(_('similarity must be a number'))
90 90 if sim < 0 or sim > 100:
91 91 raise util.Abort(_('similarity must be between 0 and 100'))
92 92 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
93 93
94 94 def annotate(ui, repo, *pats, **opts):
95 95 """show changeset information by line for each file
96 96
97 97 List changes in files, showing the revision id responsible for
98 98 each line
99 99
100 100 This command is useful for discovering when a change was made and
101 101 by whom.
102 102
103 103 Without the -a/--text option, annotate will avoid processing files
104 104 it detects as binary. With -a, annotate will annotate the file
105 105 anyway, although the results will probably be neither useful
106 106 nor desirable.
107 107
108 108 Returns 0 on success.
109 109 """
110 110 if opts.get('follow'):
111 111 # --follow is deprecated and now just an alias for -f/--file
112 112 # to mimic the behavior of Mercurial before version 1.5
113 113 opts['file'] = 1
114 114
115 115 datefunc = ui.quiet and util.shortdate or util.datestr
116 116 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
117 117
118 118 if not pats:
119 119 raise util.Abort(_('at least one filename or pattern is required'))
120 120
121 121 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
122 122 ('number', lambda x: str(x[0].rev())),
123 123 ('changeset', lambda x: short(x[0].node())),
124 124 ('date', getdate),
125 125 ('file', lambda x: x[0].path()),
126 126 ]
127 127
128 128 if (not opts.get('user') and not opts.get('changeset')
129 129 and not opts.get('date') and not opts.get('file')):
130 130 opts['number'] = 1
131 131
132 132 linenumber = opts.get('line_number') is not None
133 133 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
134 134 raise util.Abort(_('at least one of -n/-c is required for -l'))
135 135
136 136 funcmap = [func for op, func in opmap if opts.get(op)]
137 137 if linenumber:
138 138 lastfunc = funcmap[-1]
139 139 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
140 140
141 141 ctx = repo[opts.get('rev')]
142 142 m = cmdutil.match(repo, pats, opts)
143 143 follow = not opts.get('no_follow')
144 144 for abs in ctx.walk(m):
145 145 fctx = ctx[abs]
146 146 if not opts.get('text') and util.binary(fctx.data()):
147 147 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
148 148 continue
149 149
150 150 lines = fctx.annotate(follow=follow, linenumber=linenumber)
151 151 pieces = []
152 152
153 153 for f in funcmap:
154 154 l = [f(n) for n, dummy in lines]
155 155 if l:
156 156 sized = [(x, encoding.colwidth(x)) for x in l]
157 157 ml = max([w for x, w in sized])
158 158 pieces.append(["%s%s" % (' ' * (ml - w), x) for x, w in sized])
159 159
160 160 if pieces:
161 161 for p, l in zip(zip(*pieces), lines):
162 162 ui.write("%s: %s" % (" ".join(p), l[1]))
163 163
164 164 def archive(ui, repo, dest, **opts):
165 165 '''create an unversioned archive of a repository revision
166 166
167 167 By default, the revision used is the parent of the working
168 168 directory; use -r/--rev to specify a different revision.
169 169
170 170 The archive type is automatically detected based on file
171 171 extension (or override using -t/--type).
172 172
173 173 Valid types are:
174 174
175 175 :``files``: a directory full of files (default)
176 176 :``tar``: tar archive, uncompressed
177 177 :``tbz2``: tar archive, compressed using bzip2
178 178 :``tgz``: tar archive, compressed using gzip
179 179 :``uzip``: zip archive, uncompressed
180 180 :``zip``: zip archive, compressed using deflate
181 181
182 182 The exact name of the destination archive or directory is given
183 183 using a format string; see :hg:`help export` for details.
184 184
185 185 Each member added to an archive file has a directory prefix
186 186 prepended. Use -p/--prefix to specify a format string for the
187 187 prefix. The default is the basename of the archive, with suffixes
188 188 removed.
189 189
190 190 Returns 0 on success.
191 191 '''
192 192
193 193 ctx = repo[opts.get('rev')]
194 194 if not ctx:
195 195 raise util.Abort(_('no working directory: please specify a revision'))
196 196 node = ctx.node()
197 197 dest = cmdutil.make_filename(repo, dest, node)
198 198 if os.path.realpath(dest) == repo.root:
199 199 raise util.Abort(_('repository root cannot be destination'))
200 200
201 201 kind = opts.get('type') or archival.guesskind(dest) or 'files'
202 202 prefix = opts.get('prefix')
203 203
204 204 if dest == '-':
205 205 if kind == 'files':
206 206 raise util.Abort(_('cannot archive plain files to stdout'))
207 207 dest = sys.stdout
208 208 if not prefix:
209 209 prefix = os.path.basename(repo.root) + '-%h'
210 210
211 211 prefix = cmdutil.make_filename(repo, prefix, node)
212 212 matchfn = cmdutil.match(repo, [], opts)
213 213 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
214 214 matchfn, prefix)
215 215
216 216 def backout(ui, repo, node=None, rev=None, **opts):
217 217 '''reverse effect of earlier changeset
218 218
219 219 Commit the backed out changes as a new changeset. The new
220 220 changeset is a child of the backed out changeset.
221 221
222 222 If you backout a changeset other than the tip, a new head is
223 223 created. This head will be the new tip and you should merge this
224 224 backout changeset with another head.
225 225
226 226 The --merge option remembers the parent of the working directory
227 227 before starting the backout, then merges the new head with that
228 228 changeset afterwards. This saves you from doing the merge by hand.
229 229 The result of this merge is not committed, as with a normal merge.
230 230
231 231 See :hg:`help dates` for a list of formats valid for -d/--date.
232 232
233 233 Returns 0 on success.
234 234 '''
235 235 if rev and node:
236 236 raise util.Abort(_("please specify just one revision"))
237 237
238 238 if not rev:
239 239 rev = node
240 240
241 241 if not rev:
242 242 raise util.Abort(_("please specify a revision to backout"))
243 243
244 244 date = opts.get('date')
245 245 if date:
246 246 opts['date'] = util.parsedate(date)
247 247
248 248 cmdutil.bail_if_changed(repo)
249 249 node = repo.lookup(rev)
250 250
251 251 op1, op2 = repo.dirstate.parents()
252 252 a = repo.changelog.ancestor(op1, node)
253 253 if a != node:
254 254 raise util.Abort(_('cannot backout change on a different branch'))
255 255
256 256 p1, p2 = repo.changelog.parents(node)
257 257 if p1 == nullid:
258 258 raise util.Abort(_('cannot backout a change with no parents'))
259 259 if p2 != nullid:
260 260 if not opts.get('parent'):
261 261 raise util.Abort(_('cannot backout a merge changeset without '
262 262 '--parent'))
263 263 p = repo.lookup(opts['parent'])
264 264 if p not in (p1, p2):
265 265 raise util.Abort(_('%s is not a parent of %s') %
266 266 (short(p), short(node)))
267 267 parent = p
268 268 else:
269 269 if opts.get('parent'):
270 270 raise util.Abort(_('cannot use --parent on non-merge changeset'))
271 271 parent = p1
272 272
273 273 # the backout should appear on the same branch
274 274 branch = repo.dirstate.branch()
275 275 hg.clean(repo, node, show_stats=False)
276 276 repo.dirstate.setbranch(branch)
277 277 revert_opts = opts.copy()
278 278 revert_opts['date'] = None
279 279 revert_opts['all'] = True
280 280 revert_opts['rev'] = hex(parent)
281 281 revert_opts['no_backup'] = None
282 282 revert(ui, repo, **revert_opts)
283 283 commit_opts = opts.copy()
284 284 commit_opts['addremove'] = False
285 285 if not commit_opts['message'] and not commit_opts['logfile']:
286 286 # we don't translate commit messages
287 287 commit_opts['message'] = "Backed out changeset %s" % short(node)
288 288 commit_opts['force_editor'] = True
289 289 commit(ui, repo, **commit_opts)
290 290 def nice(node):
291 291 return '%d:%s' % (repo.changelog.rev(node), short(node))
292 292 ui.status(_('changeset %s backs out changeset %s\n') %
293 293 (nice(repo.changelog.tip()), nice(node)))
294 294 if op1 != node:
295 295 hg.clean(repo, op1, show_stats=False)
296 296 if opts.get('merge'):
297 297 ui.status(_('merging with changeset %s\n')
298 298 % nice(repo.changelog.tip()))
299 299 hg.merge(repo, hex(repo.changelog.tip()))
300 300 else:
301 301 ui.status(_('the backout changeset is a new head - '
302 302 'do not forget to merge\n'))
303 303 ui.status(_('(use "backout --merge" '
304 304 'if you want to auto-merge)\n'))
305 305
306 306 def bisect(ui, repo, rev=None, extra=None, command=None,
307 307 reset=None, good=None, bad=None, skip=None, noupdate=None):
308 308 """subdivision search of changesets
309 309
310 310 This command helps to find changesets which introduce problems. To
311 311 use, mark the earliest changeset you know exhibits the problem as
312 312 bad, then mark the latest changeset which is free from the problem
313 313 as good. Bisect will update your working directory to a revision
314 314 for testing (unless the -U/--noupdate option is specified). Once
315 315 you have performed tests, mark the working directory as good or
316 316 bad, and bisect will either update to another candidate changeset
317 317 or announce that it has found the bad revision.
318 318
319 319 As a shortcut, you can also use the revision argument to mark a
320 320 revision as good or bad without checking it out first.
321 321
322 322 If you supply a command, it will be used for automatic bisection.
323 323 Its exit status will be used to mark revisions as good or bad:
324 324 status 0 means good, 125 means to skip the revision, 127
325 325 (command not found) will abort the bisection, and any other
326 326 non-zero exit status means the revision is bad.
327 327
328 328 Returns 0 on success.
329 329 """
330 330 def print_result(nodes, good):
331 331 displayer = cmdutil.show_changeset(ui, repo, {})
332 332 if len(nodes) == 1:
333 333 # narrowed it down to a single revision
334 334 if good:
335 335 ui.write(_("The first good revision is:\n"))
336 336 else:
337 337 ui.write(_("The first bad revision is:\n"))
338 338 displayer.show(repo[nodes[0]])
339 339 parents = repo[nodes[0]].parents()
340 340 if len(parents) > 1:
341 341 side = good and state['bad'] or state['good']
342 342 num = len(set(i.node() for i in parents) & set(side))
343 343 if num == 1:
344 344 common = parents[0].ancestor(parents[1])
345 345 ui.write(_('Not all ancestors of this changeset have been'
346 346 ' checked.\nTo check the other ancestors, start'
347 347 ' from the common ancestor, %s.\n' % common))
348 348 else:
349 349 # multiple possible revisions
350 350 if good:
351 351 ui.write(_("Due to skipped revisions, the first "
352 352 "good revision could be any of:\n"))
353 353 else:
354 354 ui.write(_("Due to skipped revisions, the first "
355 355 "bad revision could be any of:\n"))
356 356 for n in nodes:
357 357 displayer.show(repo[n])
358 358 displayer.close()
359 359
360 360 def check_state(state, interactive=True):
361 361 if not state['good'] or not state['bad']:
362 362 if (good or bad or skip or reset) and interactive:
363 363 return
364 364 if not state['good']:
365 365 raise util.Abort(_('cannot bisect (no known good revisions)'))
366 366 else:
367 367 raise util.Abort(_('cannot bisect (no known bad revisions)'))
368 368 return True
369 369
370 370 # backward compatibility
371 371 if rev in "good bad reset init".split():
372 372 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
373 373 cmd, rev, extra = rev, extra, None
374 374 if cmd == "good":
375 375 good = True
376 376 elif cmd == "bad":
377 377 bad = True
378 378 else:
379 379 reset = True
380 380 elif extra or good + bad + skip + reset + bool(command) > 1:
381 381 raise util.Abort(_('incompatible arguments'))
382 382
383 383 if reset:
384 384 p = repo.join("bisect.state")
385 385 if os.path.exists(p):
386 386 os.unlink(p)
387 387 return
388 388
389 389 state = hbisect.load_state(repo)
390 390
391 391 if command:
392 392 changesets = 1
393 393 try:
394 394 while changesets:
395 395 # update state
396 396 status = util.system(command)
397 397 if status == 125:
398 398 transition = "skip"
399 399 elif status == 0:
400 400 transition = "good"
401 401 # status < 0 means process was killed
402 402 elif status == 127:
403 403 raise util.Abort(_("failed to execute %s") % command)
404 404 elif status < 0:
405 405 raise util.Abort(_("%s killed") % command)
406 406 else:
407 407 transition = "bad"
408 408 ctx = repo[rev or '.']
409 409 state[transition].append(ctx.node())
410 410 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
411 411 check_state(state, interactive=False)
412 412 # bisect
413 413 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
414 414 # update to next check
415 415 cmdutil.bail_if_changed(repo)
416 416 hg.clean(repo, nodes[0], show_stats=False)
417 417 finally:
418 418 hbisect.save_state(repo, state)
419 419 print_result(nodes, good)
420 420 return
421 421
422 422 # update state
423 423
424 424 if rev:
425 425 nodes = [repo.lookup(i) for i in cmdutil.revrange(repo, [rev])]
426 426 else:
427 427 nodes = [repo.lookup('.')]
428 428
429 429 if good or bad or skip:
430 430 if good:
431 431 state['good'] += nodes
432 432 elif bad:
433 433 state['bad'] += nodes
434 434 elif skip:
435 435 state['skip'] += nodes
436 436 hbisect.save_state(repo, state)
437 437
438 438 if not check_state(state):
439 439 return
440 440
441 441 # actually bisect
442 442 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
443 443 if changesets == 0:
444 444 print_result(nodes, good)
445 445 else:
446 446 assert len(nodes) == 1 # only a single node can be tested next
447 447 node = nodes[0]
448 448 # compute the approximate number of remaining tests
449 449 tests, size = 0, 2
450 450 while size <= changesets:
451 451 tests, size = tests + 1, size * 2
452 452 rev = repo.changelog.rev(node)
453 453 ui.write(_("Testing changeset %d:%s "
454 454 "(%d changesets remaining, ~%d tests)\n")
455 455 % (rev, short(node), changesets, tests))
456 456 if not noupdate:
457 457 cmdutil.bail_if_changed(repo)
458 458 return hg.clean(repo, node)
459 459
460 460 def branch(ui, repo, label=None, **opts):
461 461 """set or show the current branch name
462 462
463 463 With no argument, show the current branch name. With one argument,
464 464 set the working directory branch name (the branch will not exist
465 465 in the repository until the next commit). Standard practice
466 466 recommends that primary development take place on the 'default'
467 467 branch.
468 468
469 469 Unless -f/--force is specified, branch will not let you set a
470 470 branch name that already exists, even if it's inactive.
471 471
472 472 Use -C/--clean to reset the working directory branch to that of
473 473 the parent of the working directory, negating a previous branch
474 474 change.
475 475
476 476 Use the command :hg:`update` to switch to an existing branch. Use
477 477 :hg:`commit --close-branch` to mark this branch as closed.
478 478
479 479 Returns 0 on success.
480 480 """
481 481
482 482 if opts.get('clean'):
483 483 label = repo[None].parents()[0].branch()
484 484 repo.dirstate.setbranch(label)
485 485 ui.status(_('reset working directory to branch %s\n') % label)
486 486 elif label:
487 487 utflabel = encoding.fromlocal(label)
488 488 if not opts.get('force') and utflabel in repo.branchtags():
489 489 if label not in [p.branch() for p in repo.parents()]:
490 490 raise util.Abort(_('a branch of the same name already exists'
491 491 " (use 'hg update' to switch to it)"))
492 492 repo.dirstate.setbranch(utflabel)
493 493 ui.status(_('marked working directory as branch %s\n') % label)
494 494 else:
495 495 ui.write("%s\n" % encoding.tolocal(repo.dirstate.branch()))
496 496
497 497 def branches(ui, repo, active=False, closed=False):
498 498 """list repository named branches
499 499
500 500 List the repository's named branches, indicating which ones are
501 501 inactive. If -c/--closed is specified, also list branches which have
502 502 been marked closed (see :hg:`commit --close-branch`).
503 503
504 504 If -a/--active is specified, only show active branches. A branch
505 505 is considered active if it contains repository heads.
506 506
507 507 Use the command :hg:`update` to switch to an existing branch.
508 508
509 509 Returns 0.
510 510 """
511 511
512 512 hexfunc = ui.debugflag and hex or short
513 513 activebranches = [repo[n].branch() for n in repo.heads()]
514 514 def testactive(tag, node):
515 515 realhead = tag in activebranches
516 516 open = node in repo.branchheads(tag, closed=False)
517 517 return realhead and open
518 518 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
519 519 for tag, node in repo.branchtags().items()],
520 520 reverse=True)
521 521
522 522 for isactive, node, tag in branches:
523 523 if (not active) or isactive:
524 524 encodedtag = encoding.tolocal(tag)
525 525 if ui.quiet:
526 526 ui.write("%s\n" % encodedtag)
527 527 else:
528 528 hn = repo.lookup(node)
529 529 if isactive:
530 530 label = 'branches.active'
531 531 notice = ''
532 532 elif hn not in repo.branchheads(tag, closed=False):
533 533 if not closed:
534 534 continue
535 535 label = 'branches.closed'
536 536 notice = _(' (closed)')
537 537 else:
538 538 label = 'branches.inactive'
539 539 notice = _(' (inactive)')
540 540 if tag == repo.dirstate.branch():
541 541 label = 'branches.current'
542 542 rev = str(node).rjust(31 - encoding.colwidth(encodedtag))
543 543 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
544 544 encodedtag = ui.label(encodedtag, label)
545 545 ui.write("%s %s%s\n" % (encodedtag, rev, notice))
546 546
547 547 def bundle(ui, repo, fname, dest=None, **opts):
548 548 """create a changegroup file
549 549
550 550 Generate a compressed changegroup file collecting changesets not
551 551 known to be in another repository.
552 552
553 553 If you omit the destination repository, then hg assumes the
554 554 destination will have all the nodes you specify with --base
555 555 parameters. To create a bundle containing all changesets, use
556 556 -a/--all (or --base null).
557 557
558 558 You can change compression method with the -t/--type option.
559 559 The available compression methods are: none, bzip2, and
560 560 gzip (by default, bundles are compressed using bzip2).
561 561
562 562 The bundle file can then be transferred using conventional means
563 563 and applied to another repository with the unbundle or pull
564 564 command. This is useful when direct push and pull are not
565 565 available or when exporting an entire repository is undesirable.
566 566
567 567 Applying bundles preserves all changeset contents including
568 568 permissions, copy/rename information, and revision history.
569 569
570 570 Returns 0 on success, 1 if no changes found.
571 571 """
572 572 revs = opts.get('rev') or None
573 573 if opts.get('all'):
574 574 base = ['null']
575 575 else:
576 576 base = opts.get('base')
577 577 if base:
578 578 if dest:
579 579 raise util.Abort(_("--base is incompatible with specifying "
580 580 "a destination"))
581 581 base = [repo.lookup(rev) for rev in base]
582 582 # create the right base
583 583 # XXX: nodesbetween / changegroup* should be "fixed" instead
584 584 o = []
585 585 has = set((nullid,))
586 586 for n in base:
587 587 has.update(repo.changelog.reachable(n))
588 588 if revs:
589 589 revs = [repo.lookup(rev) for rev in revs]
590 590 visit = revs[:]
591 591 has.difference_update(visit)
592 592 else:
593 593 visit = repo.changelog.heads()
594 594 seen = {}
595 595 while visit:
596 596 n = visit.pop(0)
597 597 parents = [p for p in repo.changelog.parents(n) if p not in has]
598 598 if len(parents) == 0:
599 599 if n not in has:
600 600 o.append(n)
601 601 else:
602 602 for p in parents:
603 603 if p not in seen:
604 604 seen[p] = 1
605 605 visit.append(p)
606 606 else:
607 607 dest = ui.expandpath(dest or 'default-push', dest or 'default')
608 608 dest, branches = hg.parseurl(dest, opts.get('branch'))
609 609 other = hg.repository(hg.remoteui(repo, opts), dest)
610 610 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
611 611 if revs:
612 612 revs = [repo.lookup(rev) for rev in revs]
613 613 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
614 614
615 615 if not o:
616 616 ui.status(_("no changes found\n"))
617 617 return 1
618 618
619 619 if revs:
620 620 cg = repo.changegroupsubset(o, revs, 'bundle')
621 621 else:
622 622 cg = repo.changegroup(o, 'bundle')
623 623
624 624 bundletype = opts.get('type', 'bzip2').lower()
625 625 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
626 626 bundletype = btypes.get(bundletype)
627 627 if bundletype not in changegroup.bundletypes:
628 628 raise util.Abort(_('unknown bundle type specified with --type'))
629 629
630 630 changegroup.writebundle(cg, fname, bundletype)
631 631
632 632 def cat(ui, repo, file1, *pats, **opts):
633 633 """output the current or given revision of files
634 634
635 635 Print the specified files as they were at the given revision. If
636 636 no revision is given, the parent of the working directory is used,
637 637 or tip if no revision is checked out.
638 638
639 639 Output may be to a file, in which case the name of the file is
640 640 given using a format string. The formatting rules are the same as
641 641 for the export command, with the following additions:
642 642
643 643 :``%s``: basename of file being printed
644 644 :``%d``: dirname of file being printed, or '.' if in repository root
645 645 :``%p``: root-relative path name of file being printed
646 646
647 647 Returns 0 on success.
648 648 """
649 649 ctx = repo[opts.get('rev')]
650 650 err = 1
651 651 m = cmdutil.match(repo, (file1,) + pats, opts)
652 652 for abs in ctx.walk(m):
653 653 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
654 654 data = ctx[abs].data()
655 655 if opts.get('decode'):
656 656 data = repo.wwritedata(abs, data)
657 657 fp.write(data)
658 658 err = 0
659 659 return err
660 660
661 661 def clone(ui, source, dest=None, **opts):
662 662 """make a copy of an existing repository
663 663
664 664 Create a copy of an existing repository in a new directory.
665 665
666 666 If no destination directory name is specified, it defaults to the
667 667 basename of the source.
668 668
669 669 The location of the source is added to the new repository's
670 670 .hg/hgrc file, as the default to be used for future pulls.
671 671
672 672 See :hg:`help urls` for valid source format details.
673 673
674 674 It is possible to specify an ``ssh://`` URL as the destination, but no
675 675 .hg/hgrc and working directory will be created on the remote side.
676 676 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
677 677
678 678 A set of changesets (tags, or branch names) to pull may be specified
679 679 by listing each changeset (tag, or branch name) with -r/--rev.
680 680 If -r/--rev is used, the cloned repository will contain only a subset
681 681 of the changesets of the source repository. Only the set of changesets
682 682 defined by all -r/--rev options (including all their ancestors)
683 683 will be pulled into the destination repository.
684 684 No subsequent changesets (including subsequent tags) will be present
685 685 in the destination.
686 686
687 687 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
688 688 local source repositories.
689 689
690 690 For efficiency, hardlinks are used for cloning whenever the source
691 691 and destination are on the same filesystem (note this applies only
692 692 to the repository data, not to the working directory). Some
693 693 filesystems, such as AFS, implement hardlinking incorrectly, but
694 694 do not report errors. In these cases, use the --pull option to
695 695 avoid hardlinking.
696 696
697 697 In some cases, you can clone repositories and the working directory
698 698 using full hardlinks with ::
699 699
700 700 $ cp -al REPO REPOCLONE
701 701
702 702 This is the fastest way to clone, but it is not always safe. The
703 703 operation is not atomic (making sure REPO is not modified during
704 704 the operation is up to you) and you have to make sure your editor
705 705 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
706 706 this is not compatible with certain extensions that place their
707 707 metadata under the .hg directory, such as mq.
708 708
709 709 Mercurial will update the working directory to the first applicable
710 710 revision from this list:
711 711
712 712 a) null if -U or the source repository has no changesets
713 713 b) if -u . and the source repository is local, the first parent of
714 714 the source repository's working directory
715 715 c) the changeset specified with -u (if a branch name, this means the
716 716 latest head of that branch)
717 717 d) the changeset specified with -r
718 718 e) the tipmost head specified with -b
719 719 f) the tipmost head specified with the url#branch source syntax
720 720 g) the tipmost head of the default branch
721 721 h) tip
722 722
723 723 Returns 0 on success.
724 724 """
725 725 if opts.get('noupdate') and opts.get('updaterev'):
726 726 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
727 727
728 728 r = hg.clone(hg.remoteui(ui, opts), source, dest,
729 729 pull=opts.get('pull'),
730 730 stream=opts.get('uncompressed'),
731 731 rev=opts.get('rev'),
732 732 update=opts.get('updaterev') or not opts.get('noupdate'),
733 733 branch=opts.get('branch'))
734 734
735 735 return r is None
736 736
737 737 def commit(ui, repo, *pats, **opts):
738 738 """commit the specified files or all outstanding changes
739 739
740 740 Commit changes to the given files into the repository. Unlike a
741 741 centralized RCS, this operation is a local operation. See
742 742 :hg:`push` for a way to actively distribute your changes.
743 743
744 744 If a list of files is omitted, all changes reported by :hg:`status`
745 745 will be committed.
746 746
747 747 If you are committing the result of a merge, do not provide any
748 748 filenames or -I/-X filters.
749 749
750 750 If no commit message is specified, Mercurial starts your
751 751 configured editor where you can enter a message. In case your
752 752 commit fails, you will find a backup of your message in
753 753 ``.hg/last-message.txt``.
754 754
755 755 See :hg:`help dates` for a list of formats valid for -d/--date.
756 756
757 757 Returns 0 on success, 1 if nothing changed.
758 758 """
759 759 extra = {}
760 760 if opts.get('close_branch'):
761 761 if repo['.'].node() not in repo.branchheads():
762 762 # The topo heads set is included in the branch heads set of the
763 763 # current branch, so it's sufficient to test branchheads
764 764 raise util.Abort(_('can only close branch heads'))
765 765 extra['close'] = 1
766 766 e = cmdutil.commiteditor
767 767 if opts.get('force_editor'):
768 768 e = cmdutil.commitforceeditor
769 769
770 770 def commitfunc(ui, repo, message, match, opts):
771 771 return repo.commit(message, opts.get('user'), opts.get('date'), match,
772 772 editor=e, extra=extra)
773 773
774 774 branch = repo[None].branch()
775 775 bheads = repo.branchheads(branch)
776 776
777 777 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
778 778 if not node:
779 779 ui.status(_("nothing changed\n"))
780 780 return 1
781 781
782 782 ctx = repo[node]
783 783 parents = ctx.parents()
784 784
785 785 if bheads and not [x for x in parents
786 786 if x.node() in bheads and x.branch() == branch]:
787 787 ui.status(_('created new head\n'))
788 788 # The message is not printed for initial roots. For the other
789 789 # changesets, it is printed in the following situations:
790 790 #
791 791 # Par column: for the 2 parents with ...
792 792 # N: null or no parent
793 793 # B: parent is on another named branch
794 794 # C: parent is a regular non head changeset
795 795 # H: parent was a branch head of the current branch
796 796 # Msg column: whether we print "created new head" message
797 797 # In the following, it is assumed that there already exists some
798 798 # initial branch heads of the current branch, otherwise nothing is
799 799 # printed anyway.
800 800 #
801 801 # Par Msg Comment
802 802 # NN y additional topo root
803 803 #
804 804 # BN y additional branch root
805 805 # CN y additional topo head
806 806 # HN n usual case
807 807 #
808 808 # BB y weird additional branch root
809 809 # CB y branch merge
810 810 # HB n merge with named branch
811 811 #
812 812 # CC y additional head from merge
813 813 # CH n merge with a head
814 814 #
815 815 # HH n head merge: head count decreases
816 816
817 817 if not opts.get('close_branch'):
818 818 for r in parents:
819 819 if r.extra().get('close') and r.branch() == branch:
820 820 ui.status(_('reopening closed branch head %d\n') % r)
821 821
822 822 if ui.debugflag:
823 823 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
824 824 elif ui.verbose:
825 825 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
826 826
827 827 def copy(ui, repo, *pats, **opts):
828 828 """mark files as copied for the next commit
829 829
830 830 Mark dest as having copies of source files. If dest is a
831 831 directory, copies are put in that directory. If dest is a file,
832 832 the source must be a single file.
833 833
834 834 By default, this command copies the contents of files as they
835 835 exist in the working directory. If invoked with -A/--after, the
836 836 operation is recorded, but no copying is performed.
837 837
838 838 This command takes effect with the next commit. To undo a copy
839 839 before that, see :hg:`revert`.
840 840
841 841 Returns 0 on success, 1 if errors are encountered.
842 842 """
843 843 wlock = repo.wlock(False)
844 844 try:
845 845 return cmdutil.copy(ui, repo, pats, opts)
846 846 finally:
847 847 wlock.release()
848 848
849 849 def debugancestor(ui, repo, *args):
850 850 """find the ancestor revision of two revisions in a given index"""
851 851 if len(args) == 3:
852 852 index, rev1, rev2 = args
853 853 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
854 854 lookup = r.lookup
855 855 elif len(args) == 2:
856 856 if not repo:
857 857 raise util.Abort(_("there is no Mercurial repository here "
858 858 "(.hg not found)"))
859 859 rev1, rev2 = args
860 860 r = repo.changelog
861 861 lookup = repo.lookup
862 862 else:
863 863 raise util.Abort(_('either two or three arguments required'))
864 864 a = r.ancestor(lookup(rev1), lookup(rev2))
865 865 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
866 866
867 867 def debugbuilddag(ui, repo, text,
868 868 mergeable_file=False,
869 869 appended_file=False,
870 870 overwritten_file=False,
871 871 new_file=False):
872 872 """builds a repo with a given dag from scratch in the current empty repo
873 873
874 874 Elements:
875 875
876 876 - "+n" is a linear run of n nodes based on the current default parent
877 877 - "." is a single node based on the current default parent
878 878 - "$" resets the default parent to null (implied at the start);
879 879 otherwise the default parent is always the last node created
880 880 - "<p" sets the default parent to the backref p
881 881 - "*p" is a fork at parent p, which is a backref
882 882 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
883 883 - "/p2" is a merge of the preceding node and p2
884 884 - ":tag" defines a local tag for the preceding node
885 885 - "@branch" sets the named branch for subsequent nodes
886 886 - "!command" runs the command using your shell
887 887 - "!!my command\\n" is like "!", but to the end of the line
888 888 - "#...\\n" is a comment up to the end of the line
889 889
890 890 Whitespace between the above elements is ignored.
891 891
892 892 A backref is either
893 893
894 894 - a number n, which references the node curr-n, where curr is the current
895 895 node, or
896 896 - the name of a local tag you placed earlier using ":tag", or
897 897 - empty to denote the default parent.
898 898
899 899 All string valued-elements are either strictly alphanumeric, or must
900 900 be enclosed in double quotes ("..."), with "\\" as escape character.
901 901
902 902 Note that the --overwritten-file and --appended-file options imply the
903 903 use of "HGMERGE=internal:local" during DAG buildup.
904 904 """
905 905
906 906 if not (mergeable_file or appended_file or overwritten_file or new_file):
907 907 raise util.Abort(_('need at least one of -m, -a, -o, -n'))
908 908
909 909 if len(repo.changelog) > 0:
910 910 raise util.Abort(_('repository is not empty'))
911 911
912 912 if overwritten_file or appended_file:
913 913 # we don't want to fail in merges during buildup
914 914 os.environ['HGMERGE'] = 'internal:local'
915 915
916 916 def writefile(fname, text, fmode="wb"):
917 917 f = open(fname, fmode)
918 918 try:
919 919 f.write(text)
920 920 finally:
921 921 f.close()
922 922
923 923 if mergeable_file:
924 924 linesperrev = 2
925 925 # determine number of revs in DAG
926 926 n = 0
927 927 for type, data in dagparser.parsedag(text):
928 928 if type == 'n':
929 929 n += 1
930 930 # make a file with k lines per rev
931 931 writefile("mf", "\n".join(str(i) for i in xrange(0, n * linesperrev))
932 932 + "\n")
933 933
934 934 at = -1
935 935 atbranch = 'default'
936 936 for type, data in dagparser.parsedag(text):
937 937 if type == 'n':
938 938 ui.status('node %s\n' % str(data))
939 939 id, ps = data
940 940 p1 = ps[0]
941 941 if p1 != at:
942 942 update(ui, repo, node=p1, clean=True)
943 943 at = p1
944 944 if repo.dirstate.branch() != atbranch:
945 945 branch(ui, repo, atbranch, force=True)
946 946 if len(ps) > 1:
947 947 p2 = ps[1]
948 948 merge(ui, repo, node=p2)
949 949
950 950 if mergeable_file:
951 951 f = open("mf", "rb+")
952 952 try:
953 953 lines = f.read().split("\n")
954 954 lines[id * linesperrev] += " r%i" % id
955 955 f.seek(0)
956 956 f.write("\n".join(lines))
957 957 finally:
958 958 f.close()
959 959
960 960 if appended_file:
961 961 writefile("af", "r%i\n" % id, "ab")
962 962
963 963 if overwritten_file:
964 964 writefile("of", "r%i\n" % id)
965 965
966 966 if new_file:
967 967 writefile("nf%i" % id, "r%i\n" % id)
968 968
969 969 commit(ui, repo, addremove=True, message="r%i" % id, date=(id, 0))
970 970 at = id
971 971 elif type == 'l':
972 972 id, name = data
973 973 ui.status('tag %s\n' % name)
974 974 tag(ui, repo, name, local=True)
975 975 elif type == 'a':
976 976 ui.status('branch %s\n' % data)
977 977 atbranch = data
978 978 elif type in 'cC':
979 979 r = util.system(data, cwd=repo.root)
980 980 if r:
981 981 desc, r = util.explain_exit(r)
982 982 raise util.Abort(_('%s command %s') % (data, desc))
983 983
984 984 def debugcommands(ui, cmd='', *args):
985 985 """list all available commands and options"""
986 986 for cmd, vals in sorted(table.iteritems()):
987 987 cmd = cmd.split('|')[0].strip('^')
988 988 opts = ', '.join([i[1] for i in vals[1]])
989 989 ui.write('%s: %s\n' % (cmd, opts))
990 990
991 991 def debugcomplete(ui, cmd='', **opts):
992 992 """returns the completion list associated with the given command"""
993 993
994 994 if opts.get('options'):
995 995 options = []
996 996 otables = [globalopts]
997 997 if cmd:
998 998 aliases, entry = cmdutil.findcmd(cmd, table, False)
999 999 otables.append(entry[1])
1000 1000 for t in otables:
1001 1001 for o in t:
1002 1002 if "(DEPRECATED)" in o[3]:
1003 1003 continue
1004 1004 if o[0]:
1005 1005 options.append('-%s' % o[0])
1006 1006 options.append('--%s' % o[1])
1007 1007 ui.write("%s\n" % "\n".join(options))
1008 1008 return
1009 1009
1010 1010 cmdlist = cmdutil.findpossible(cmd, table)
1011 1011 if ui.verbose:
1012 1012 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1013 1013 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1014 1014
1015 1015 def debugfsinfo(ui, path = "."):
1016 1016 """show information detected about current filesystem"""
1017 1017 open('.debugfsinfo', 'w').write('')
1018 1018 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1019 1019 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1020 1020 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1021 1021 and 'yes' or 'no'))
1022 1022 os.unlink('.debugfsinfo')
1023 1023
1024 1024 def debugrebuildstate(ui, repo, rev="tip"):
1025 1025 """rebuild the dirstate as it would look like for the given revision"""
1026 1026 ctx = repo[rev]
1027 1027 wlock = repo.wlock()
1028 1028 try:
1029 1029 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1030 1030 finally:
1031 1031 wlock.release()
1032 1032
1033 1033 def debugcheckstate(ui, repo):
1034 1034 """validate the correctness of the current dirstate"""
1035 1035 parent1, parent2 = repo.dirstate.parents()
1036 1036 m1 = repo[parent1].manifest()
1037 1037 m2 = repo[parent2].manifest()
1038 1038 errors = 0
1039 1039 for f in repo.dirstate:
1040 1040 state = repo.dirstate[f]
1041 1041 if state in "nr" and f not in m1:
1042 1042 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1043 1043 errors += 1
1044 1044 if state in "a" and f in m1:
1045 1045 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1046 1046 errors += 1
1047 1047 if state in "m" and f not in m1 and f not in m2:
1048 1048 ui.warn(_("%s in state %s, but not in either manifest\n") %
1049 1049 (f, state))
1050 1050 errors += 1
1051 1051 for f in m1:
1052 1052 state = repo.dirstate[f]
1053 1053 if state not in "nrm":
1054 1054 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1055 1055 errors += 1
1056 1056 if errors:
1057 1057 error = _(".hg/dirstate inconsistent with current parent's manifest")
1058 1058 raise util.Abort(error)
1059 1059
1060 1060 def showconfig(ui, repo, *values, **opts):
1061 1061 """show combined config settings from all hgrc files
1062 1062
1063 1063 With no arguments, print names and values of all config items.
1064 1064
1065 1065 With one argument of the form section.name, print just the value
1066 1066 of that config item.
1067 1067
1068 1068 With multiple arguments, print names and values of all config
1069 1069 items with matching section names.
1070 1070
1071 1071 With --debug, the source (filename and line number) is printed
1072 1072 for each config item.
1073 1073
1074 1074 Returns 0 on success.
1075 1075 """
1076 1076
1077 1077 for f in util.rcpath():
1078 1078 ui.debug(_('read config from: %s\n') % f)
1079 1079 untrusted = bool(opts.get('untrusted'))
1080 1080 if values:
1081 1081 if len([v for v in values if '.' in v]) > 1:
1082 1082 raise util.Abort(_('only one config item permitted'))
1083 1083 for section, name, value in ui.walkconfig(untrusted=untrusted):
1084 1084 sectname = section + '.' + name
1085 1085 if values:
1086 1086 for v in values:
1087 1087 if v == section:
1088 1088 ui.debug('%s: ' %
1089 1089 ui.configsource(section, name, untrusted))
1090 1090 ui.write('%s=%s\n' % (sectname, value))
1091 1091 elif v == sectname:
1092 1092 ui.debug('%s: ' %
1093 1093 ui.configsource(section, name, untrusted))
1094 1094 ui.write(value, '\n')
1095 1095 else:
1096 1096 ui.debug('%s: ' %
1097 1097 ui.configsource(section, name, untrusted))
1098 1098 ui.write('%s=%s\n' % (sectname, value))
1099 1099
1100 1100 def debugpushkey(ui, repopath, namespace, *keyinfo):
1101 1101 '''access the pushkey key/value protocol
1102 1102
1103 1103 With two args, list the keys in the given namespace.
1104 1104
1105 1105 With five args, set a key to new if it currently is set to old.
1106 1106 Reports success or failure.
1107 1107 '''
1108 1108
1109 1109 target = hg.repository(ui, repopath)
1110 1110 if keyinfo:
1111 1111 key, old, new = keyinfo
1112 1112 r = target.pushkey(namespace, key, old, new)
1113 1113 ui.status(str(r) + '\n')
1114 1114 return not(r)
1115 1115 else:
1116 1116 for k, v in target.listkeys(namespace).iteritems():
1117 1117 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1118 1118 v.encode('string-escape')))
1119 1119
1120 1120 def debugrevspec(ui, repo, expr):
1121 1121 '''parse and apply a revision specification'''
1122 1122 if ui.verbose:
1123 1123 tree = revset.parse(expr)
1124 1124 ui.note(tree, "\n")
1125 1125 func = revset.match(expr)
1126 1126 for c in func(repo, range(len(repo))):
1127 1127 ui.write("%s\n" % c)
1128 1128
1129 1129 def debugsetparents(ui, repo, rev1, rev2=None):
1130 1130 """manually set the parents of the current working directory
1131 1131
1132 1132 This is useful for writing repository conversion tools, but should
1133 1133 be used with care.
1134 1134
1135 1135 Returns 0 on success.
1136 1136 """
1137 1137
1138 1138 if not rev2:
1139 1139 rev2 = hex(nullid)
1140 1140
1141 1141 wlock = repo.wlock()
1142 1142 try:
1143 1143 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1144 1144 finally:
1145 1145 wlock.release()
1146 1146
1147 1147 def debugstate(ui, repo, nodates=None):
1148 1148 """show the contents of the current dirstate"""
1149 1149 timestr = ""
1150 1150 showdate = not nodates
1151 1151 for file_, ent in sorted(repo.dirstate._map.iteritems()):
1152 1152 if showdate:
1153 1153 if ent[3] == -1:
1154 1154 # Pad or slice to locale representation
1155 1155 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
1156 1156 time.localtime(0)))
1157 1157 timestr = 'unset'
1158 1158 timestr = (timestr[:locale_len] +
1159 1159 ' ' * (locale_len - len(timestr)))
1160 1160 else:
1161 1161 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
1162 1162 time.localtime(ent[3]))
1163 1163 if ent[1] & 020000:
1164 1164 mode = 'lnk'
1165 1165 else:
1166 1166 mode = '%3o' % (ent[1] & 0777)
1167 1167 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
1168 1168 for f in repo.dirstate.copies():
1169 1169 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1170 1170
1171 1171 def debugsub(ui, repo, rev=None):
1172 1172 if rev == '':
1173 1173 rev = None
1174 1174 for k, v in sorted(repo[rev].substate.items()):
1175 1175 ui.write('path %s\n' % k)
1176 1176 ui.write(' source %s\n' % v[0])
1177 1177 ui.write(' revision %s\n' % v[1])
1178 1178
1179 1179 def debugdag(ui, repo, file_=None, *revs, **opts):
1180 1180 """format the changelog or an index DAG as a concise textual description
1181 1181
1182 1182 If you pass a revlog index, the revlog's DAG is emitted. If you list
1183 1183 revision numbers, they get labelled in the output as rN.
1184 1184
1185 1185 Otherwise, the changelog DAG of the current repo is emitted.
1186 1186 """
1187 1187 spaces = opts.get('spaces')
1188 1188 dots = opts.get('dots')
1189 1189 if file_:
1190 1190 rlog = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1191 1191 revs = set((int(r) for r in revs))
1192 1192 def events():
1193 1193 for r in rlog:
1194 1194 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1195 1195 if r in revs:
1196 1196 yield 'l', (r, "r%i" % r)
1197 1197 elif repo:
1198 1198 cl = repo.changelog
1199 1199 tags = opts.get('tags')
1200 1200 branches = opts.get('branches')
1201 1201 if tags:
1202 1202 labels = {}
1203 1203 for l, n in repo.tags().items():
1204 1204 labels.setdefault(cl.rev(n), []).append(l)
1205 1205 def events():
1206 1206 b = "default"
1207 1207 for r in cl:
1208 1208 if branches:
1209 1209 newb = cl.read(cl.node(r))[5]['branch']
1210 1210 if newb != b:
1211 1211 yield 'a', newb
1212 1212 b = newb
1213 1213 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1214 1214 if tags:
1215 1215 ls = labels.get(r)
1216 1216 if ls:
1217 1217 for l in ls:
1218 1218 yield 'l', (r, l)
1219 1219 else:
1220 1220 raise util.Abort(_('need repo for changelog dag'))
1221 1221
1222 1222 for line in dagparser.dagtextlines(events(),
1223 1223 addspaces=spaces,
1224 1224 wraplabels=True,
1225 1225 wrapannotations=True,
1226 1226 wrapnonlinear=dots,
1227 1227 usedots=dots,
1228 1228 maxlinewidth=70):
1229 1229 ui.write(line)
1230 1230 ui.write("\n")
1231 1231
1232 1232 def debugdata(ui, repo, file_, rev):
1233 1233 """dump the contents of a data file revision"""
1234 1234 r = None
1235 1235 if repo:
1236 1236 filelog = repo.file(file_)
1237 1237 if len(filelog):
1238 1238 r = filelog
1239 1239 if not r:
1240 1240 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
1241 1241 try:
1242 1242 ui.write(r.revision(r.lookup(rev)))
1243 1243 except KeyError:
1244 1244 raise util.Abort(_('invalid revision identifier %s') % rev)
1245 1245
1246 1246 def debugdate(ui, date, range=None, **opts):
1247 1247 """parse and display a date"""
1248 1248 if opts["extended"]:
1249 1249 d = util.parsedate(date, util.extendeddateformats)
1250 1250 else:
1251 1251 d = util.parsedate(date)
1252 1252 ui.write("internal: %s %s\n" % d)
1253 1253 ui.write("standard: %s\n" % util.datestr(d))
1254 1254 if range:
1255 1255 m = util.matchdate(range)
1256 1256 ui.write("match: %s\n" % m(d[0]))
1257 1257
1258 1258 def debugindex(ui, repo, file_):
1259 1259 """dump the contents of an index file"""
1260 1260 r = None
1261 1261 if repo:
1262 1262 filelog = repo.file(file_)
1263 1263 if len(filelog):
1264 1264 r = filelog
1265 1265 if not r:
1266 1266 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1267 1267 ui.write(" rev offset length base linkrev"
1268 1268 " nodeid p1 p2\n")
1269 1269 for i in r:
1270 1270 node = r.node(i)
1271 1271 try:
1272 1272 pp = r.parents(node)
1273 1273 except:
1274 1274 pp = [nullid, nullid]
1275 1275 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1276 1276 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
1277 1277 short(node), short(pp[0]), short(pp[1])))
1278 1278
1279 1279 def debugindexdot(ui, repo, file_):
1280 1280 """dump an index DAG as a graphviz dot file"""
1281 1281 r = None
1282 1282 if repo:
1283 1283 filelog = repo.file(file_)
1284 1284 if len(filelog):
1285 1285 r = filelog
1286 1286 if not r:
1287 1287 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1288 1288 ui.write("digraph G {\n")
1289 1289 for i in r:
1290 1290 node = r.node(i)
1291 1291 pp = r.parents(node)
1292 1292 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1293 1293 if pp[1] != nullid:
1294 1294 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1295 1295 ui.write("}\n")
1296 1296
1297 1297 def debuginstall(ui):
1298 1298 '''test Mercurial installation
1299 1299
1300 1300 Returns 0 on success.
1301 1301 '''
1302 1302
1303 1303 def writetemp(contents):
1304 1304 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1305 1305 f = os.fdopen(fd, "wb")
1306 1306 f.write(contents)
1307 1307 f.close()
1308 1308 return name
1309 1309
1310 1310 problems = 0
1311 1311
1312 1312 # encoding
1313 1313 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1314 1314 try:
1315 1315 encoding.fromlocal("test")
1316 1316 except util.Abort, inst:
1317 1317 ui.write(" %s\n" % inst)
1318 1318 ui.write(_(" (check that your locale is properly set)\n"))
1319 1319 problems += 1
1320 1320
1321 1321 # compiled modules
1322 1322 ui.status(_("Checking installed modules (%s)...\n")
1323 1323 % os.path.dirname(__file__))
1324 1324 try:
1325 1325 import bdiff, mpatch, base85, osutil
1326 1326 except Exception, inst:
1327 1327 ui.write(" %s\n" % inst)
1328 1328 ui.write(_(" One or more extensions could not be found"))
1329 1329 ui.write(_(" (check that you compiled the extensions)\n"))
1330 1330 problems += 1
1331 1331
1332 1332 # templates
1333 1333 ui.status(_("Checking templates...\n"))
1334 1334 try:
1335 1335 import templater
1336 1336 templater.templater(templater.templatepath("map-cmdline.default"))
1337 1337 except Exception, inst:
1338 1338 ui.write(" %s\n" % inst)
1339 1339 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1340 1340 problems += 1
1341 1341
1342 1342 # patch
1343 1343 ui.status(_("Checking patch...\n"))
1344 1344 patchproblems = 0
1345 1345 a = "1\n2\n3\n4\n"
1346 1346 b = "1\n2\n3\ninsert\n4\n"
1347 1347 fa = writetemp(a)
1348 1348 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
1349 1349 os.path.basename(fa))
1350 1350 fd = writetemp(d)
1351 1351
1352 1352 files = {}
1353 1353 try:
1354 1354 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
1355 1355 except util.Abort, e:
1356 1356 ui.write(_(" patch call failed:\n"))
1357 1357 ui.write(" " + str(e) + "\n")
1358 1358 patchproblems += 1
1359 1359 else:
1360 1360 if list(files) != [os.path.basename(fa)]:
1361 1361 ui.write(_(" unexpected patch output!\n"))
1362 1362 patchproblems += 1
1363 1363 a = open(fa).read()
1364 1364 if a != b:
1365 1365 ui.write(_(" patch test failed!\n"))
1366 1366 patchproblems += 1
1367 1367
1368 1368 if patchproblems:
1369 1369 if ui.config('ui', 'patch'):
1370 1370 ui.write(_(" (Current patch tool may be incompatible with patch,"
1371 1371 " or misconfigured. Please check your configuration"
1372 1372 " file)\n"))
1373 1373 else:
1374 1374 ui.write(_(" Internal patcher failure, please report this error"
1375 1375 " to http://mercurial.selenic.com/bts/\n"))
1376 1376 problems += patchproblems
1377 1377
1378 1378 os.unlink(fa)
1379 1379 os.unlink(fd)
1380 1380
1381 1381 # editor
1382 1382 ui.status(_("Checking commit editor...\n"))
1383 1383 editor = ui.geteditor()
1384 1384 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1385 1385 if not cmdpath:
1386 1386 if editor == 'vi':
1387 1387 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1388 1388 ui.write(_(" (specify a commit editor in your configuration"
1389 1389 " file)\n"))
1390 1390 else:
1391 1391 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1392 1392 ui.write(_(" (specify a commit editor in your configuration"
1393 1393 " file)\n"))
1394 1394 problems += 1
1395 1395
1396 1396 # check username
1397 1397 ui.status(_("Checking username...\n"))
1398 1398 try:
1399 1399 ui.username()
1400 1400 except util.Abort, e:
1401 1401 ui.write(" %s\n" % e)
1402 1402 ui.write(_(" (specify a username in your configuration file)\n"))
1403 1403 problems += 1
1404 1404
1405 1405 if not problems:
1406 1406 ui.status(_("No problems detected\n"))
1407 1407 else:
1408 1408 ui.write(_("%s problems detected,"
1409 1409 " please check your install!\n") % problems)
1410 1410
1411 1411 return problems
1412 1412
1413 1413 def debugrename(ui, repo, file1, *pats, **opts):
1414 1414 """dump rename information"""
1415 1415
1416 1416 ctx = repo[opts.get('rev')]
1417 1417 m = cmdutil.match(repo, (file1,) + pats, opts)
1418 1418 for abs in ctx.walk(m):
1419 1419 fctx = ctx[abs]
1420 1420 o = fctx.filelog().renamed(fctx.filenode())
1421 1421 rel = m.rel(abs)
1422 1422 if o:
1423 1423 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1424 1424 else:
1425 1425 ui.write(_("%s not renamed\n") % rel)
1426 1426
1427 1427 def debugwalk(ui, repo, *pats, **opts):
1428 1428 """show how files match on given patterns"""
1429 1429 m = cmdutil.match(repo, pats, opts)
1430 1430 items = list(repo.walk(m))
1431 1431 if not items:
1432 1432 return
1433 1433 fmt = 'f %%-%ds %%-%ds %%s' % (
1434 1434 max([len(abs) for abs in items]),
1435 1435 max([len(m.rel(abs)) for abs in items]))
1436 1436 for abs in items:
1437 1437 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1438 1438 ui.write("%s\n" % line.rstrip())
1439 1439
1440 1440 def diff(ui, repo, *pats, **opts):
1441 1441 """diff repository (or selected files)
1442 1442
1443 1443 Show differences between revisions for the specified files.
1444 1444
1445 1445 Differences between files are shown using the unified diff format.
1446 1446
1447 1447 NOTE: diff may generate unexpected results for merges, as it will
1448 1448 default to comparing against the working directory's first parent
1449 1449 changeset if no revisions are specified.
1450 1450
1451 1451 When two revision arguments are given, then changes are shown
1452 1452 between those revisions. If only one revision is specified then
1453 1453 that revision is compared to the working directory, and, when no
1454 1454 revisions are specified, the working directory files are compared
1455 1455 to its parent.
1456 1456
1457 1457 Alternatively you can specify -c/--change with a revision to see
1458 1458 the changes in that changeset relative to its first parent.
1459 1459
1460 1460 Without the -a/--text option, diff will avoid generating diffs of
1461 1461 files it detects as binary. With -a, diff will generate a diff
1462 1462 anyway, probably with undesirable results.
1463 1463
1464 1464 Use the -g/--git option to generate diffs in the git extended diff
1465 1465 format. For more information, read :hg:`help diffs`.
1466 1466
1467 1467 Returns 0 on success.
1468 1468 """
1469 1469
1470 1470 revs = opts.get('rev')
1471 1471 change = opts.get('change')
1472 1472 stat = opts.get('stat')
1473 1473 reverse = opts.get('reverse')
1474 1474
1475 1475 if revs and change:
1476 1476 msg = _('cannot specify --rev and --change at the same time')
1477 1477 raise util.Abort(msg)
1478 1478 elif change:
1479 1479 node2 = repo.lookup(change)
1480 1480 node1 = repo[node2].parents()[0].node()
1481 1481 else:
1482 1482 node1, node2 = cmdutil.revpair(repo, revs)
1483 1483
1484 1484 if reverse:
1485 1485 node1, node2 = node2, node1
1486 1486
1487 1487 diffopts = patch.diffopts(ui, opts)
1488 1488 m = cmdutil.match(repo, pats, opts)
1489 1489 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1490 1490 listsubrepos=opts.get('subrepos'))
1491 1491
1492 1492 def export(ui, repo, *changesets, **opts):
1493 1493 """dump the header and diffs for one or more changesets
1494 1494
1495 1495 Print the changeset header and diffs for one or more revisions.
1496 1496
1497 1497 The information shown in the changeset header is: author, date,
1498 1498 branch name (if non-default), changeset hash, parent(s) and commit
1499 1499 comment.
1500 1500
1501 1501 NOTE: export may generate unexpected diff output for merge
1502 1502 changesets, as it will compare the merge changeset against its
1503 1503 first parent only.
1504 1504
1505 1505 Output may be to a file, in which case the name of the file is
1506 1506 given using a format string. The formatting rules are as follows:
1507 1507
1508 1508 :``%%``: literal "%" character
1509 1509 :``%H``: changeset hash (40 hexadecimal digits)
1510 1510 :``%N``: number of patches being generated
1511 1511 :``%R``: changeset revision number
1512 1512 :``%b``: basename of the exporting repository
1513 1513 :``%h``: short-form changeset hash (12 hexadecimal digits)
1514 1514 :``%n``: zero-padded sequence number, starting at 1
1515 1515 :``%r``: zero-padded changeset revision number
1516 1516
1517 1517 Without the -a/--text option, export will avoid generating diffs
1518 1518 of files it detects as binary. With -a, export will generate a
1519 1519 diff anyway, probably with undesirable results.
1520 1520
1521 1521 Use the -g/--git option to generate diffs in the git extended diff
1522 1522 format. See :hg:`help diffs` for more information.
1523 1523
1524 1524 With the --switch-parent option, the diff will be against the
1525 1525 second parent. It can be useful to review a merge.
1526 1526
1527 1527 Returns 0 on success.
1528 1528 """
1529 1529 changesets += tuple(opts.get('rev', []))
1530 1530 if not changesets:
1531 1531 raise util.Abort(_("export requires at least one changeset"))
1532 1532 revs = cmdutil.revrange(repo, changesets)
1533 1533 if len(revs) > 1:
1534 1534 ui.note(_('exporting patches:\n'))
1535 1535 else:
1536 1536 ui.note(_('exporting patch:\n'))
1537 1537 cmdutil.export(repo, revs, template=opts.get('output'),
1538 1538 switch_parent=opts.get('switch_parent'),
1539 1539 opts=patch.diffopts(ui, opts))
1540 1540
1541 1541 def forget(ui, repo, *pats, **opts):
1542 1542 """forget the specified files on the next commit
1543 1543
1544 1544 Mark the specified files so they will no longer be tracked
1545 1545 after the next commit.
1546 1546
1547 1547 This only removes files from the current branch, not from the
1548 1548 entire project history, and it does not delete them from the
1549 1549 working directory.
1550 1550
1551 1551 To undo a forget before the next commit, see :hg:`add`.
1552 1552
1553 1553 Returns 0 on success.
1554 1554 """
1555 1555
1556 1556 if not pats:
1557 1557 raise util.Abort(_('no files specified'))
1558 1558
1559 1559 m = cmdutil.match(repo, pats, opts)
1560 1560 s = repo.status(match=m, clean=True)
1561 1561 forget = sorted(s[0] + s[1] + s[3] + s[6])
1562 1562 errs = 0
1563 1563
1564 1564 for f in m.files():
1565 1565 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1566 1566 ui.warn(_('not removing %s: file is already untracked\n')
1567 1567 % m.rel(f))
1568 1568 errs = 1
1569 1569
1570 1570 for f in forget:
1571 1571 if ui.verbose or not m.exact(f):
1572 1572 ui.status(_('removing %s\n') % m.rel(f))
1573 1573
1574 1574 repo[None].remove(forget, unlink=False)
1575 1575 return errs
1576 1576
1577 1577 def grep(ui, repo, pattern, *pats, **opts):
1578 1578 """search for a pattern in specified files and revisions
1579 1579
1580 1580 Search revisions of files for a regular expression.
1581 1581
1582 1582 This command behaves differently than Unix grep. It only accepts
1583 1583 Python/Perl regexps. It searches repository history, not the
1584 1584 working directory. It always prints the revision number in which a
1585 1585 match appears.
1586 1586
1587 1587 By default, grep only prints output for the first revision of a
1588 1588 file in which it finds a match. To get it to print every revision
1589 1589 that contains a change in match status ("-" for a match that
1590 1590 becomes a non-match, or "+" for a non-match that becomes a match),
1591 1591 use the --all flag.
1592 1592
1593 1593 Returns 0 if a match is found, 1 otherwise.
1594 1594 """
1595 1595 reflags = 0
1596 1596 if opts.get('ignore_case'):
1597 1597 reflags |= re.I
1598 1598 try:
1599 1599 regexp = re.compile(pattern, reflags)
1600 1600 except Exception, inst:
1601 1601 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1602 1602 return 1
1603 1603 sep, eol = ':', '\n'
1604 1604 if opts.get('print0'):
1605 1605 sep = eol = '\0'
1606 1606
1607 1607 getfile = util.lrucachefunc(repo.file)
1608 1608
1609 1609 def matchlines(body):
1610 1610 begin = 0
1611 1611 linenum = 0
1612 1612 while True:
1613 1613 match = regexp.search(body, begin)
1614 1614 if not match:
1615 1615 break
1616 1616 mstart, mend = match.span()
1617 1617 linenum += body.count('\n', begin, mstart) + 1
1618 1618 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1619 1619 begin = body.find('\n', mend) + 1 or len(body)
1620 1620 lend = begin - 1
1621 1621 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1622 1622
1623 1623 class linestate(object):
1624 1624 def __init__(self, line, linenum, colstart, colend):
1625 1625 self.line = line
1626 1626 self.linenum = linenum
1627 1627 self.colstart = colstart
1628 1628 self.colend = colend
1629 1629
1630 1630 def __hash__(self):
1631 1631 return hash((self.linenum, self.line))
1632 1632
1633 1633 def __eq__(self, other):
1634 1634 return self.line == other.line
1635 1635
1636 1636 matches = {}
1637 1637 copies = {}
1638 1638 def grepbody(fn, rev, body):
1639 1639 matches[rev].setdefault(fn, [])
1640 1640 m = matches[rev][fn]
1641 1641 for lnum, cstart, cend, line in matchlines(body):
1642 1642 s = linestate(line, lnum, cstart, cend)
1643 1643 m.append(s)
1644 1644
1645 1645 def difflinestates(a, b):
1646 1646 sm = difflib.SequenceMatcher(None, a, b)
1647 1647 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1648 1648 if tag == 'insert':
1649 1649 for i in xrange(blo, bhi):
1650 1650 yield ('+', b[i])
1651 1651 elif tag == 'delete':
1652 1652 for i in xrange(alo, ahi):
1653 1653 yield ('-', a[i])
1654 1654 elif tag == 'replace':
1655 1655 for i in xrange(alo, ahi):
1656 1656 yield ('-', a[i])
1657 1657 for i in xrange(blo, bhi):
1658 1658 yield ('+', b[i])
1659 1659
1660 1660 def display(fn, ctx, pstates, states):
1661 1661 rev = ctx.rev()
1662 1662 datefunc = ui.quiet and util.shortdate or util.datestr
1663 1663 found = False
1664 1664 filerevmatches = {}
1665 1665 if opts.get('all'):
1666 1666 iter = difflinestates(pstates, states)
1667 1667 else:
1668 1668 iter = [('', l) for l in states]
1669 1669 for change, l in iter:
1670 1670 cols = [fn, str(rev)]
1671 1671 before, match, after = None, None, None
1672 1672 if opts.get('line_number'):
1673 1673 cols.append(str(l.linenum))
1674 1674 if opts.get('all'):
1675 1675 cols.append(change)
1676 1676 if opts.get('user'):
1677 1677 cols.append(ui.shortuser(ctx.user()))
1678 1678 if opts.get('date'):
1679 1679 cols.append(datefunc(ctx.date()))
1680 1680 if opts.get('files_with_matches'):
1681 1681 c = (fn, rev)
1682 1682 if c in filerevmatches:
1683 1683 continue
1684 1684 filerevmatches[c] = 1
1685 1685 else:
1686 1686 before = l.line[:l.colstart]
1687 1687 match = l.line[l.colstart:l.colend]
1688 1688 after = l.line[l.colend:]
1689 1689 ui.write(sep.join(cols))
1690 1690 if before is not None:
1691 1691 ui.write(sep + before)
1692 1692 ui.write(match, label='grep.match')
1693 1693 ui.write(after)
1694 1694 ui.write(eol)
1695 1695 found = True
1696 1696 return found
1697 1697
1698 1698 skip = {}
1699 1699 revfiles = {}
1700 1700 matchfn = cmdutil.match(repo, pats, opts)
1701 1701 found = False
1702 1702 follow = opts.get('follow')
1703 1703
1704 1704 def prep(ctx, fns):
1705 1705 rev = ctx.rev()
1706 1706 pctx = ctx.parents()[0]
1707 1707 parent = pctx.rev()
1708 1708 matches.setdefault(rev, {})
1709 1709 matches.setdefault(parent, {})
1710 1710 files = revfiles.setdefault(rev, [])
1711 1711 for fn in fns:
1712 1712 flog = getfile(fn)
1713 1713 try:
1714 1714 fnode = ctx.filenode(fn)
1715 1715 except error.LookupError:
1716 1716 continue
1717 1717
1718 1718 copied = flog.renamed(fnode)
1719 1719 copy = follow and copied and copied[0]
1720 1720 if copy:
1721 1721 copies.setdefault(rev, {})[fn] = copy
1722 1722 if fn in skip:
1723 1723 if copy:
1724 1724 skip[copy] = True
1725 1725 continue
1726 1726 files.append(fn)
1727 1727
1728 1728 if fn not in matches[rev]:
1729 1729 grepbody(fn, rev, flog.read(fnode))
1730 1730
1731 1731 pfn = copy or fn
1732 1732 if pfn not in matches[parent]:
1733 1733 try:
1734 1734 fnode = pctx.filenode(pfn)
1735 1735 grepbody(pfn, parent, flog.read(fnode))
1736 1736 except error.LookupError:
1737 1737 pass
1738 1738
1739 1739 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
1740 1740 rev = ctx.rev()
1741 1741 parent = ctx.parents()[0].rev()
1742 1742 for fn in sorted(revfiles.get(rev, [])):
1743 1743 states = matches[rev][fn]
1744 1744 copy = copies.get(rev, {}).get(fn)
1745 1745 if fn in skip:
1746 1746 if copy:
1747 1747 skip[copy] = True
1748 1748 continue
1749 1749 pstates = matches.get(parent, {}).get(copy or fn, [])
1750 1750 if pstates or states:
1751 1751 r = display(fn, ctx, pstates, states)
1752 1752 found = found or r
1753 1753 if r and not opts.get('all'):
1754 1754 skip[fn] = True
1755 1755 if copy:
1756 1756 skip[copy] = True
1757 1757 del matches[rev]
1758 1758 del revfiles[rev]
1759 1759
1760 1760 return not found
1761 1761
1762 1762 def heads(ui, repo, *branchrevs, **opts):
1763 1763 """show current repository heads or show branch heads
1764 1764
1765 1765 With no arguments, show all repository branch heads.
1766 1766
1767 1767 Repository "heads" are changesets with no child changesets. They are
1768 1768 where development generally takes place and are the usual targets
1769 1769 for update and merge operations. Branch heads are changesets that have
1770 1770 no child changeset on the same branch.
1771 1771
1772 1772 If one or more REVs are given, only branch heads on the branches
1773 1773 associated with the specified changesets are shown.
1774 1774
1775 1775 If -c/--closed is specified, also show branch heads marked closed
1776 1776 (see :hg:`commit --close-branch`).
1777 1777
1778 1778 If STARTREV is specified, only those heads that are descendants of
1779 1779 STARTREV will be displayed.
1780 1780
1781 1781 If -t/--topo is specified, named branch mechanics will be ignored and only
1782 1782 changesets without children will be shown.
1783 1783
1784 1784 Returns 0 if matching heads are found, 1 if not.
1785 1785 """
1786 1786
1787 1787 if opts.get('rev'):
1788 1788 start = repo.lookup(opts['rev'])
1789 1789 else:
1790 1790 start = None
1791 1791
1792 1792 if opts.get('topo'):
1793 1793 heads = [repo[h] for h in repo.heads(start)]
1794 1794 else:
1795 1795 heads = []
1796 1796 for b, ls in repo.branchmap().iteritems():
1797 1797 if start is None:
1798 1798 heads += [repo[h] for h in ls]
1799 1799 continue
1800 1800 startrev = repo.changelog.rev(start)
1801 1801 descendants = set(repo.changelog.descendants(startrev))
1802 1802 descendants.add(startrev)
1803 1803 rev = repo.changelog.rev
1804 1804 heads += [repo[h] for h in ls if rev(h) in descendants]
1805 1805
1806 1806 if branchrevs:
1807 1807 decode, encode = encoding.fromlocal, encoding.tolocal
1808 1808 branches = set(repo[decode(br)].branch() for br in branchrevs)
1809 1809 heads = [h for h in heads if h.branch() in branches]
1810 1810
1811 1811 if not opts.get('closed'):
1812 1812 heads = [h for h in heads if not h.extra().get('close')]
1813 1813
1814 1814 if opts.get('active') and branchrevs:
1815 1815 dagheads = repo.heads(start)
1816 1816 heads = [h for h in heads if h.node() in dagheads]
1817 1817
1818 1818 if branchrevs:
1819 1819 haveheads = set(h.branch() for h in heads)
1820 1820 if branches - haveheads:
1821 1821 headless = ', '.join(encode(b) for b in branches - haveheads)
1822 1822 msg = _('no open branch heads found on branches %s')
1823 1823 if opts.get('rev'):
1824 1824 msg += _(' (started at %s)' % opts['rev'])
1825 1825 ui.warn((msg + '\n') % headless)
1826 1826
1827 1827 if not heads:
1828 1828 return 1
1829 1829
1830 1830 heads = sorted(heads, key=lambda x: -x.rev())
1831 1831 displayer = cmdutil.show_changeset(ui, repo, opts)
1832 1832 for ctx in heads:
1833 1833 displayer.show(ctx)
1834 1834 displayer.close()
1835 1835
1836 1836 def help_(ui, name=None, with_version=False, unknowncmd=False):
1837 1837 """show help for a given topic or a help overview
1838 1838
1839 1839 With no arguments, print a list of commands with short help messages.
1840 1840
1841 1841 Given a topic, extension, or command name, print help for that
1842 1842 topic.
1843 1843
1844 1844 Returns 0 if successful.
1845 1845 """
1846 1846 option_lists = []
1847 1847 textwidth = util.termwidth() - 2
1848 1848
1849 1849 def addglobalopts(aliases):
1850 1850 if ui.verbose:
1851 1851 option_lists.append((_("global options:"), globalopts))
1852 1852 if name == 'shortlist':
1853 1853 option_lists.append((_('use "hg help" for the full list '
1854 1854 'of commands'), ()))
1855 1855 else:
1856 1856 if name == 'shortlist':
1857 1857 msg = _('use "hg help" for the full list of commands '
1858 1858 'or "hg -v" for details')
1859 1859 elif aliases:
1860 1860 msg = _('use "hg -v help%s" to show aliases and '
1861 1861 'global options') % (name and " " + name or "")
1862 1862 else:
1863 1863 msg = _('use "hg -v help %s" to show global options') % name
1864 1864 option_lists.append((msg, ()))
1865 1865
1866 1866 def helpcmd(name):
1867 1867 if with_version:
1868 1868 version_(ui)
1869 1869 ui.write('\n')
1870 1870
1871 1871 try:
1872 1872 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
1873 1873 except error.AmbiguousCommand, inst:
1874 1874 # py3k fix: except vars can't be used outside the scope of the
1875 1875 # except block, nor can be used inside a lambda. python issue4617
1876 1876 prefix = inst.args[0]
1877 1877 select = lambda c: c.lstrip('^').startswith(prefix)
1878 1878 helplist(_('list of commands:\n\n'), select)
1879 1879 return
1880 1880
1881 1881 # check if it's an invalid alias and display its error if it is
1882 1882 if getattr(entry[0], 'badalias', False):
1883 1883 if not unknowncmd:
1884 1884 entry[0](ui)
1885 1885 return
1886 1886
1887 1887 # synopsis
1888 1888 if len(entry) > 2:
1889 1889 if entry[2].startswith('hg'):
1890 1890 ui.write("%s\n" % entry[2])
1891 1891 else:
1892 1892 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
1893 1893 else:
1894 1894 ui.write('hg %s\n' % aliases[0])
1895 1895
1896 1896 # aliases
1897 1897 if not ui.quiet and len(aliases) > 1:
1898 1898 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1899 1899
1900 1900 # description
1901 1901 doc = gettext(entry[0].__doc__)
1902 1902 if not doc:
1903 1903 doc = _("(no help text available)")
1904 1904 if hasattr(entry[0], 'definition'): # aliased command
1905 1905 if entry[0].definition.startswith('!'): # shell alias
1906 1906 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
1907 1907 else:
1908 1908 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
1909 1909 if ui.quiet:
1910 1910 doc = doc.splitlines()[0]
1911 1911 keep = ui.verbose and ['verbose'] or []
1912 1912 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
1913 1913 ui.write("\n%s\n" % formatted)
1914 1914 if pruned:
1915 1915 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
1916 1916
1917 1917 if not ui.quiet:
1918 1918 # options
1919 1919 if entry[1]:
1920 1920 option_lists.append((_("options:\n"), entry[1]))
1921 1921
1922 1922 addglobalopts(False)
1923 1923
1924 1924 def helplist(header, select=None):
1925 1925 h = {}
1926 1926 cmds = {}
1927 1927 for c, e in table.iteritems():
1928 1928 f = c.split("|", 1)[0]
1929 1929 if select and not select(f):
1930 1930 continue
1931 1931 if (not select and name != 'shortlist' and
1932 1932 e[0].__module__ != __name__):
1933 1933 continue
1934 1934 if name == "shortlist" and not f.startswith("^"):
1935 1935 continue
1936 1936 f = f.lstrip("^")
1937 1937 if not ui.debugflag and f.startswith("debug"):
1938 1938 continue
1939 1939 doc = e[0].__doc__
1940 1940 if doc and 'DEPRECATED' in doc and not ui.verbose:
1941 1941 continue
1942 1942 doc = gettext(doc)
1943 1943 if not doc:
1944 1944 doc = _("(no help text available)")
1945 1945 h[f] = doc.splitlines()[0].rstrip()
1946 1946 cmds[f] = c.lstrip("^")
1947 1947
1948 1948 if not h:
1949 1949 ui.status(_('no commands defined\n'))
1950 1950 return
1951 1951
1952 1952 ui.status(header)
1953 1953 fns = sorted(h)
1954 1954 m = max(map(len, fns))
1955 1955 for f in fns:
1956 1956 if ui.verbose:
1957 1957 commands = cmds[f].replace("|",", ")
1958 1958 ui.write(" %s:\n %s\n"%(commands, h[f]))
1959 1959 else:
1960 1960 ui.write('%s\n' % (util.wrap(h[f],
1961 1961 initindent=' %-*s ' % (m, f),
1962 1962 hangindent=' ' * (m + 4))))
1963 1963
1964 1964 if not ui.quiet:
1965 1965 addglobalopts(True)
1966 1966
1967 1967 def helptopic(name):
1968 1968 for names, header, doc in help.helptable:
1969 1969 if name in names:
1970 1970 break
1971 1971 else:
1972 1972 raise error.UnknownCommand(name)
1973 1973
1974 1974 # description
1975 1975 if not doc:
1976 1976 doc = _("(no help text available)")
1977 1977 if hasattr(doc, '__call__'):
1978 1978 doc = doc()
1979 1979
1980 1980 ui.write("%s\n\n" % header)
1981 1981 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
1982 1982
1983 1983 def helpext(name):
1984 1984 try:
1985 1985 mod = extensions.find(name)
1986 1986 doc = gettext(mod.__doc__) or _('no help text available')
1987 1987 except KeyError:
1988 1988 mod = None
1989 1989 doc = extensions.disabledext(name)
1990 1990 if not doc:
1991 1991 raise error.UnknownCommand(name)
1992 1992
1993 1993 if '\n' not in doc:
1994 1994 head, tail = doc, ""
1995 1995 else:
1996 1996 head, tail = doc.split('\n', 1)
1997 1997 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
1998 1998 if tail:
1999 1999 ui.write(minirst.format(tail, textwidth))
2000 2000 ui.status('\n\n')
2001 2001
2002 2002 if mod:
2003 2003 try:
2004 2004 ct = mod.cmdtable
2005 2005 except AttributeError:
2006 2006 ct = {}
2007 2007 modcmds = set([c.split('|', 1)[0] for c in ct])
2008 2008 helplist(_('list of commands:\n\n'), modcmds.__contains__)
2009 2009 else:
2010 2010 ui.write(_('use "hg help extensions" for information on enabling '
2011 2011 'extensions\n'))
2012 2012
2013 2013 def helpextcmd(name):
2014 2014 cmd, ext, mod = extensions.disabledcmd(name, ui.config('ui', 'strict'))
2015 2015 doc = gettext(mod.__doc__).splitlines()[0]
2016 2016
2017 2017 msg = help.listexts(_("'%s' is provided by the following "
2018 2018 "extension:") % cmd, {ext: doc}, len(ext),
2019 2019 indent=4)
2020 2020 ui.write(minirst.format(msg, textwidth))
2021 2021 ui.write('\n\n')
2022 2022 ui.write(_('use "hg help extensions" for information on enabling '
2023 2023 'extensions\n'))
2024 2024
2025 2025 if name and name != 'shortlist':
2026 2026 i = None
2027 2027 if unknowncmd:
2028 2028 queries = (helpextcmd,)
2029 2029 else:
2030 2030 queries = (helptopic, helpcmd, helpext, helpextcmd)
2031 2031 for f in queries:
2032 2032 try:
2033 2033 f(name)
2034 2034 i = None
2035 2035 break
2036 2036 except error.UnknownCommand, inst:
2037 2037 i = inst
2038 2038 if i:
2039 2039 raise i
2040 2040
2041 2041 else:
2042 2042 # program name
2043 2043 if ui.verbose or with_version:
2044 2044 version_(ui)
2045 2045 else:
2046 2046 ui.status(_("Mercurial Distributed SCM\n"))
2047 2047 ui.status('\n')
2048 2048
2049 2049 # list of commands
2050 2050 if name == "shortlist":
2051 2051 header = _('basic commands:\n\n')
2052 2052 else:
2053 2053 header = _('list of commands:\n\n')
2054 2054
2055 2055 helplist(header)
2056 2056 if name != 'shortlist':
2057 2057 exts, maxlength = extensions.enabled()
2058 2058 text = help.listexts(_('enabled extensions:'), exts, maxlength)
2059 2059 if text:
2060 2060 ui.write("\n%s\n" % minirst.format(text, textwidth))
2061 2061
2062 2062 # list all option lists
2063 2063 opt_output = []
2064 2064 multioccur = False
2065 2065 for title, options in option_lists:
2066 2066 opt_output.append(("\n%s" % title, None))
2067 2067 for option in options:
2068 2068 if len(option) == 5:
2069 2069 shortopt, longopt, default, desc, optlabel = option
2070 2070 else:
2071 2071 shortopt, longopt, default, desc = option
2072 2072 optlabel = _("VALUE") # default label
2073 2073
2074 2074 if _("DEPRECATED") in desc and not ui.verbose:
2075 2075 continue
2076 2076 if isinstance(default, list):
2077 2077 numqualifier = " %s [+]" % optlabel
2078 2078 multioccur = True
2079 2079 elif (default is not None) and not isinstance(default, bool):
2080 2080 numqualifier = " %s" % optlabel
2081 2081 else:
2082 2082 numqualifier = ""
2083 2083 opt_output.append(("%2s%s" %
2084 2084 (shortopt and "-%s" % shortopt,
2085 2085 longopt and " --%s%s" %
2086 2086 (longopt, numqualifier)),
2087 2087 "%s%s" % (desc,
2088 2088 default
2089 2089 and _(" (default: %s)") % default
2090 2090 or "")))
2091 2091 if multioccur:
2092 2092 msg = _("\n[+] marked option can be specified multiple times")
2093 2093 if ui.verbose and name != 'shortlist':
2094 2094 opt_output.append((msg, None))
2095 2095 else:
2096 2096 opt_output.insert(-1, (msg, None))
2097 2097
2098 2098 if not name:
2099 2099 ui.write(_("\nadditional help topics:\n\n"))
2100 2100 topics = []
2101 2101 for names, header, doc in help.helptable:
2102 2102 topics.append((sorted(names, key=len, reverse=True)[0], header))
2103 2103 topics_len = max([len(s[0]) for s in topics])
2104 2104 for t, desc in topics:
2105 2105 ui.write(" %-*s %s\n" % (topics_len, t, desc))
2106 2106
2107 2107 if opt_output:
2108 2108 colwidth = encoding.colwidth
2109 2109 # normalize: (opt or message, desc or None, width of opt)
2110 2110 entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0)
2111 2111 for opt, desc in opt_output]
2112 2112 hanging = max([e[2] for e in entries])
2113 2113 for opt, desc, width in entries:
2114 2114 if desc:
2115 2115 initindent = ' %s%s ' % (opt, ' ' * (hanging - width))
2116 2116 hangindent = ' ' * (hanging + 3)
2117 2117 ui.write('%s\n' % (util.wrap(desc,
2118 2118 initindent=initindent,
2119 2119 hangindent=hangindent)))
2120 2120 else:
2121 2121 ui.write("%s\n" % opt)
2122 2122
2123 2123 def identify(ui, repo, source=None,
2124 2124 rev=None, num=None, id=None, branch=None, tags=None):
2125 2125 """identify the working copy or specified revision
2126 2126
2127 2127 With no revision, print a summary of the current state of the
2128 2128 repository.
2129 2129
2130 2130 Specifying a path to a repository root or Mercurial bundle will
2131 2131 cause lookup to operate on that repository/bundle.
2132 2132
2133 2133 This summary identifies the repository state using one or two
2134 2134 parent hash identifiers, followed by a "+" if there are
2135 2135 uncommitted changes in the working directory, a list of tags for
2136 2136 this revision and a branch name for non-default branches.
2137 2137
2138 2138 Returns 0 if successful.
2139 2139 """
2140 2140
2141 2141 if not repo and not source:
2142 2142 raise util.Abort(_("there is no Mercurial repository here "
2143 2143 "(.hg not found)"))
2144 2144
2145 2145 hexfunc = ui.debugflag and hex or short
2146 2146 default = not (num or id or branch or tags)
2147 2147 output = []
2148 2148
2149 2149 revs = []
2150 2150 if source:
2151 2151 source, branches = hg.parseurl(ui.expandpath(source))
2152 2152 repo = hg.repository(ui, source)
2153 2153 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
2154 2154
2155 2155 if not repo.local():
2156 2156 if not rev and revs:
2157 2157 rev = revs[0]
2158 2158 if not rev:
2159 2159 rev = "tip"
2160 2160 if num or branch or tags:
2161 2161 raise util.Abort(
2162 2162 "can't query remote revision number, branch, or tags")
2163 2163 output = [hexfunc(repo.lookup(rev))]
2164 2164 elif not rev:
2165 2165 ctx = repo[None]
2166 2166 parents = ctx.parents()
2167 2167 changed = False
2168 2168 if default or id or num:
2169 2169 changed = util.any(repo.status())
2170 2170 if default or id:
2171 2171 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
2172 2172 (changed) and "+" or "")]
2173 2173 if num:
2174 2174 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
2175 2175 (changed) and "+" or ""))
2176 2176 else:
2177 2177 ctx = repo[rev]
2178 2178 if default or id:
2179 2179 output = [hexfunc(ctx.node())]
2180 2180 if num:
2181 2181 output.append(str(ctx.rev()))
2182 2182
2183 2183 if repo.local() and default and not ui.quiet:
2184 2184 b = encoding.tolocal(ctx.branch())
2185 2185 if b != 'default':
2186 2186 output.append("(%s)" % b)
2187 2187
2188 2188 # multiple tags for a single parent separated by '/'
2189 2189 t = "/".join(ctx.tags())
2190 2190 if t:
2191 2191 output.append(t)
2192 2192
2193 2193 if branch:
2194 2194 output.append(encoding.tolocal(ctx.branch()))
2195 2195
2196 2196 if tags:
2197 2197 output.extend(ctx.tags())
2198 2198
2199 2199 ui.write("%s\n" % ' '.join(output))
2200 2200
2201 2201 def import_(ui, repo, patch1, *patches, **opts):
2202 2202 """import an ordered set of patches
2203 2203
2204 2204 Import a list of patches and commit them individually (unless
2205 2205 --no-commit is specified).
2206 2206
2207 2207 If there are outstanding changes in the working directory, import
2208 2208 will abort unless given the -f/--force flag.
2209 2209
2210 2210 You can import a patch straight from a mail message. Even patches
2211 2211 as attachments work (to use the body part, it must have type
2212 2212 text/plain or text/x-patch). From and Subject headers of email
2213 2213 message are used as default committer and commit message. All
2214 2214 text/plain body parts before first diff are added to commit
2215 2215 message.
2216 2216
2217 2217 If the imported patch was generated by :hg:`export`, user and
2218 2218 description from patch override values from message headers and
2219 2219 body. Values given on command line with -m/--message and -u/--user
2220 2220 override these.
2221 2221
2222 2222 If --exact is specified, import will set the working directory to
2223 2223 the parent of each patch before applying it, and will abort if the
2224 2224 resulting changeset has a different ID than the one recorded in
2225 2225 the patch. This may happen due to character set problems or other
2226 2226 deficiencies in the text patch format.
2227 2227
2228 2228 With -s/--similarity, hg will attempt to discover renames and
2229 2229 copies in the patch in the same way as 'addremove'.
2230 2230
2231 2231 To read a patch from standard input, use "-" as the patch name. If
2232 2232 a URL is specified, the patch will be downloaded from it.
2233 2233 See :hg:`help dates` for a list of formats valid for -d/--date.
2234 2234
2235 2235 Returns 0 on success.
2236 2236 """
2237 2237 patches = (patch1,) + patches
2238 2238
2239 2239 date = opts.get('date')
2240 2240 if date:
2241 2241 opts['date'] = util.parsedate(date)
2242 2242
2243 2243 try:
2244 2244 sim = float(opts.get('similarity') or 0)
2245 2245 except ValueError:
2246 2246 raise util.Abort(_('similarity must be a number'))
2247 2247 if sim < 0 or sim > 100:
2248 2248 raise util.Abort(_('similarity must be between 0 and 100'))
2249 2249
2250 2250 if opts.get('exact') or not opts.get('force'):
2251 2251 cmdutil.bail_if_changed(repo)
2252 2252
2253 2253 d = opts["base"]
2254 2254 strip = opts["strip"]
2255 2255 wlock = lock = None
2256 2256
2257 2257 def tryone(ui, hunk):
2258 2258 tmpname, message, user, date, branch, nodeid, p1, p2 = \
2259 2259 patch.extract(ui, hunk)
2260 2260
2261 2261 if not tmpname:
2262 2262 return None
2263 2263 commitid = _('to working directory')
2264 2264
2265 2265 try:
2266 2266 cmdline_message = cmdutil.logmessage(opts)
2267 2267 if cmdline_message:
2268 2268 # pickup the cmdline msg
2269 2269 message = cmdline_message
2270 2270 elif message:
2271 2271 # pickup the patch msg
2272 2272 message = message.strip()
2273 2273 else:
2274 2274 # launch the editor
2275 2275 message = None
2276 2276 ui.debug('message:\n%s\n' % message)
2277 2277
2278 2278 wp = repo.parents()
2279 2279 if opts.get('exact'):
2280 2280 if not nodeid or not p1:
2281 2281 raise util.Abort(_('not a Mercurial patch'))
2282 2282 p1 = repo.lookup(p1)
2283 2283 p2 = repo.lookup(p2 or hex(nullid))
2284 2284
2285 2285 if p1 != wp[0].node():
2286 2286 hg.clean(repo, p1)
2287 2287 repo.dirstate.setparents(p1, p2)
2288 2288 elif p2:
2289 2289 try:
2290 2290 p1 = repo.lookup(p1)
2291 2291 p2 = repo.lookup(p2)
2292 2292 if p1 == wp[0].node():
2293 2293 repo.dirstate.setparents(p1, p2)
2294 2294 except error.RepoError:
2295 2295 pass
2296 2296 if opts.get('exact') or opts.get('import_branch'):
2297 2297 repo.dirstate.setbranch(branch or 'default')
2298 2298
2299 2299 files = {}
2300 2300 try:
2301 2301 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
2302 2302 files=files, eolmode=None)
2303 2303 finally:
2304 files = patch.updatedir(ui, repo, files,
2305 similarity=sim / 100.0)
2304 files = cmdutil.updatedir(ui, repo, files,
2305 similarity=sim / 100.0)
2306 2306 if not opts.get('no_commit'):
2307 2307 if opts.get('exact'):
2308 2308 m = None
2309 2309 else:
2310 2310 m = cmdutil.matchfiles(repo, files or [])
2311 2311 n = repo.commit(message, opts.get('user') or user,
2312 2312 opts.get('date') or date, match=m,
2313 2313 editor=cmdutil.commiteditor)
2314 2314 if opts.get('exact'):
2315 2315 if hex(n) != nodeid:
2316 2316 repo.rollback()
2317 2317 raise util.Abort(_('patch is damaged'
2318 2318 ' or loses information'))
2319 2319 # Force a dirstate write so that the next transaction
2320 2320 # backups an up-do-date file.
2321 2321 repo.dirstate.write()
2322 2322 if n:
2323 2323 commitid = short(n)
2324 2324
2325 2325 return commitid
2326 2326 finally:
2327 2327 os.unlink(tmpname)
2328 2328
2329 2329 try:
2330 2330 wlock = repo.wlock()
2331 2331 lock = repo.lock()
2332 2332 lastcommit = None
2333 2333 for p in patches:
2334 2334 pf = os.path.join(d, p)
2335 2335
2336 2336 if pf == '-':
2337 2337 ui.status(_("applying patch from stdin\n"))
2338 2338 pf = sys.stdin
2339 2339 else:
2340 2340 ui.status(_("applying %s\n") % p)
2341 2341 pf = url.open(ui, pf)
2342 2342
2343 2343 haspatch = False
2344 2344 for hunk in patch.split(pf):
2345 2345 commitid = tryone(ui, hunk)
2346 2346 if commitid:
2347 2347 haspatch = True
2348 2348 if lastcommit:
2349 2349 ui.status(_('applied %s\n') % lastcommit)
2350 2350 lastcommit = commitid
2351 2351
2352 2352 if not haspatch:
2353 2353 raise util.Abort(_('no diffs found'))
2354 2354
2355 2355 finally:
2356 2356 release(lock, wlock)
2357 2357
2358 2358 def incoming(ui, repo, source="default", **opts):
2359 2359 """show new changesets found in source
2360 2360
2361 2361 Show new changesets found in the specified path/URL or the default
2362 2362 pull location. These are the changesets that would have been pulled
2363 2363 if a pull at the time you issued this command.
2364 2364
2365 2365 For remote repository, using --bundle avoids downloading the
2366 2366 changesets twice if the incoming is followed by a pull.
2367 2367
2368 2368 See pull for valid source format details.
2369 2369
2370 2370 Returns 0 if there are incoming changes, 1 otherwise.
2371 2371 """
2372 2372 limit = cmdutil.loglimit(opts)
2373 2373 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2374 2374 other = hg.repository(hg.remoteui(repo, opts), source)
2375 2375 ui.status(_('comparing with %s\n') % url.hidepassword(source))
2376 2376 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2377 2377 if revs:
2378 2378 revs = [other.lookup(rev) for rev in revs]
2379 2379
2380 2380 tmp = discovery.findcommonincoming(repo, other, heads=revs,
2381 2381 force=opts.get('force'))
2382 2382 common, incoming, rheads = tmp
2383 2383 if not incoming:
2384 2384 try:
2385 2385 os.unlink(opts["bundle"])
2386 2386 except:
2387 2387 pass
2388 2388 ui.status(_("no changes found\n"))
2389 2389 return 1
2390 2390
2391 2391 cleanup = None
2392 2392 try:
2393 2393 fname = opts["bundle"]
2394 2394 if fname or not other.local():
2395 2395 # create a bundle (uncompressed if other repo is not local)
2396 2396
2397 2397 if revs is None and other.capable('changegroupsubset'):
2398 2398 revs = rheads
2399 2399
2400 2400 if revs is None:
2401 2401 cg = other.changegroup(incoming, "incoming")
2402 2402 else:
2403 2403 cg = other.changegroupsubset(incoming, revs, 'incoming')
2404 2404 bundletype = other.local() and "HG10BZ" or "HG10UN"
2405 2405 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
2406 2406 # keep written bundle?
2407 2407 if opts["bundle"]:
2408 2408 cleanup = None
2409 2409 if not other.local():
2410 2410 # use the created uncompressed bundlerepo
2411 2411 other = bundlerepo.bundlerepository(ui, repo.root, fname)
2412 2412
2413 2413 o = other.changelog.nodesbetween(incoming, revs)[0]
2414 2414 if opts.get('newest_first'):
2415 2415 o.reverse()
2416 2416 displayer = cmdutil.show_changeset(ui, other, opts)
2417 2417 count = 0
2418 2418 for n in o:
2419 2419 if limit is not None and count >= limit:
2420 2420 break
2421 2421 parents = [p for p in other.changelog.parents(n) if p != nullid]
2422 2422 if opts.get('no_merges') and len(parents) == 2:
2423 2423 continue
2424 2424 count += 1
2425 2425 displayer.show(other[n])
2426 2426 displayer.close()
2427 2427 finally:
2428 2428 if hasattr(other, 'close'):
2429 2429 other.close()
2430 2430 if cleanup:
2431 2431 os.unlink(cleanup)
2432 2432
2433 2433 def init(ui, dest=".", **opts):
2434 2434 """create a new repository in the given directory
2435 2435
2436 2436 Initialize a new repository in the given directory. If the given
2437 2437 directory does not exist, it will be created.
2438 2438
2439 2439 If no directory is given, the current directory is used.
2440 2440
2441 2441 It is possible to specify an ``ssh://`` URL as the destination.
2442 2442 See :hg:`help urls` for more information.
2443 2443
2444 2444 Returns 0 on success.
2445 2445 """
2446 2446 hg.repository(hg.remoteui(ui, opts), dest, create=1)
2447 2447
2448 2448 def locate(ui, repo, *pats, **opts):
2449 2449 """locate files matching specific patterns
2450 2450
2451 2451 Print files under Mercurial control in the working directory whose
2452 2452 names match the given patterns.
2453 2453
2454 2454 By default, this command searches all directories in the working
2455 2455 directory. To search just the current directory and its
2456 2456 subdirectories, use "--include .".
2457 2457
2458 2458 If no patterns are given to match, this command prints the names
2459 2459 of all files under Mercurial control in the working directory.
2460 2460
2461 2461 If you want to feed the output of this command into the "xargs"
2462 2462 command, use the -0 option to both this command and "xargs". This
2463 2463 will avoid the problem of "xargs" treating single filenames that
2464 2464 contain whitespace as multiple filenames.
2465 2465
2466 2466 Returns 0 if a match is found, 1 otherwise.
2467 2467 """
2468 2468 end = opts.get('print0') and '\0' or '\n'
2469 2469 rev = opts.get('rev') or None
2470 2470
2471 2471 ret = 1
2472 2472 m = cmdutil.match(repo, pats, opts, default='relglob')
2473 2473 m.bad = lambda x, y: False
2474 2474 for abs in repo[rev].walk(m):
2475 2475 if not rev and abs not in repo.dirstate:
2476 2476 continue
2477 2477 if opts.get('fullpath'):
2478 2478 ui.write(repo.wjoin(abs), end)
2479 2479 else:
2480 2480 ui.write(((pats and m.rel(abs)) or abs), end)
2481 2481 ret = 0
2482 2482
2483 2483 return ret
2484 2484
2485 2485 def log(ui, repo, *pats, **opts):
2486 2486 """show revision history of entire repository or files
2487 2487
2488 2488 Print the revision history of the specified files or the entire
2489 2489 project.
2490 2490
2491 2491 File history is shown without following rename or copy history of
2492 2492 files. Use -f/--follow with a filename to follow history across
2493 2493 renames and copies. --follow without a filename will only show
2494 2494 ancestors or descendants of the starting revision. --follow-first
2495 2495 only follows the first parent of merge revisions.
2496 2496
2497 2497 If no revision range is specified, the default is tip:0 unless
2498 2498 --follow is set, in which case the working directory parent is
2499 2499 used as the starting revision. You can specify a revision set for
2500 2500 log, see :hg:`help revsets` for more information.
2501 2501
2502 2502 See :hg:`help dates` for a list of formats valid for -d/--date.
2503 2503
2504 2504 By default this command prints revision number and changeset id,
2505 2505 tags, non-trivial parents, user, date and time, and a summary for
2506 2506 each commit. When the -v/--verbose switch is used, the list of
2507 2507 changed files and full commit message are shown.
2508 2508
2509 2509 NOTE: log -p/--patch may generate unexpected diff output for merge
2510 2510 changesets, as it will only compare the merge changeset against
2511 2511 its first parent. Also, only files different from BOTH parents
2512 2512 will appear in files:.
2513 2513
2514 2514 Returns 0 on success.
2515 2515 """
2516 2516
2517 2517 matchfn = cmdutil.match(repo, pats, opts)
2518 2518 limit = cmdutil.loglimit(opts)
2519 2519 count = 0
2520 2520
2521 2521 endrev = None
2522 2522 if opts.get('copies') and opts.get('rev'):
2523 2523 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2524 2524
2525 2525 df = False
2526 2526 if opts["date"]:
2527 2527 df = util.matchdate(opts["date"])
2528 2528
2529 2529 branches = opts.get('branch', []) + opts.get('only_branch', [])
2530 2530 opts['branch'] = [repo.lookupbranch(b) for b in branches]
2531 2531
2532 2532 displayer = cmdutil.show_changeset(ui, repo, opts, True)
2533 2533 def prep(ctx, fns):
2534 2534 rev = ctx.rev()
2535 2535 parents = [p for p in repo.changelog.parentrevs(rev)
2536 2536 if p != nullrev]
2537 2537 if opts.get('no_merges') and len(parents) == 2:
2538 2538 return
2539 2539 if opts.get('only_merges') and len(parents) != 2:
2540 2540 return
2541 2541 if opts.get('branch') and ctx.branch() not in opts['branch']:
2542 2542 return
2543 2543 if df and not df(ctx.date()[0]):
2544 2544 return
2545 2545 if opts['user'] and not [k for k in opts['user'] if k in ctx.user()]:
2546 2546 return
2547 2547 if opts.get('keyword'):
2548 2548 for k in [kw.lower() for kw in opts['keyword']]:
2549 2549 if (k in ctx.user().lower() or
2550 2550 k in ctx.description().lower() or
2551 2551 k in " ".join(ctx.files()).lower()):
2552 2552 break
2553 2553 else:
2554 2554 return
2555 2555
2556 2556 copies = None
2557 2557 if opts.get('copies') and rev:
2558 2558 copies = []
2559 2559 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2560 2560 for fn in ctx.files():
2561 2561 rename = getrenamed(fn, rev)
2562 2562 if rename:
2563 2563 copies.append((fn, rename[0]))
2564 2564
2565 2565 revmatchfn = None
2566 2566 if opts.get('patch') or opts.get('stat'):
2567 2567 revmatchfn = cmdutil.match(repo, fns, default='path')
2568 2568
2569 2569 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2570 2570
2571 2571 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2572 2572 if count == limit:
2573 2573 break
2574 2574 if displayer.flush(ctx.rev()):
2575 2575 count += 1
2576 2576 displayer.close()
2577 2577
2578 2578 def manifest(ui, repo, node=None, rev=None):
2579 2579 """output the current or given revision of the project manifest
2580 2580
2581 2581 Print a list of version controlled files for the given revision.
2582 2582 If no revision is given, the first parent of the working directory
2583 2583 is used, or the null revision if no revision is checked out.
2584 2584
2585 2585 With -v, print file permissions, symlink and executable bits.
2586 2586 With --debug, print file revision hashes.
2587 2587
2588 2588 Returns 0 on success.
2589 2589 """
2590 2590
2591 2591 if rev and node:
2592 2592 raise util.Abort(_("please specify just one revision"))
2593 2593
2594 2594 if not node:
2595 2595 node = rev
2596 2596
2597 2597 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2598 2598 ctx = repo[node]
2599 2599 for f in ctx:
2600 2600 if ui.debugflag:
2601 2601 ui.write("%40s " % hex(ctx.manifest()[f]))
2602 2602 if ui.verbose:
2603 2603 ui.write(decor[ctx.flags(f)])
2604 2604 ui.write("%s\n" % f)
2605 2605
2606 2606 def merge(ui, repo, node=None, **opts):
2607 2607 """merge working directory with another revision
2608 2608
2609 2609 The current working directory is updated with all changes made in
2610 2610 the requested revision since the last common predecessor revision.
2611 2611
2612 2612 Files that changed between either parent are marked as changed for
2613 2613 the next commit and a commit must be performed before any further
2614 2614 updates to the repository are allowed. The next commit will have
2615 2615 two parents.
2616 2616
2617 2617 If no revision is specified, the working directory's parent is a
2618 2618 head revision, and the current branch contains exactly one other
2619 2619 head, the other head is merged with by default. Otherwise, an
2620 2620 explicit revision with which to merge with must be provided.
2621 2621
2622 2622 To undo an uncommitted merge, use :hg:`update --clean .` which
2623 2623 will check out a clean copy of the original merge parent, losing
2624 2624 all changes.
2625 2625
2626 2626 Returns 0 on success, 1 if there are unresolved files.
2627 2627 """
2628 2628
2629 2629 if opts.get('rev') and node:
2630 2630 raise util.Abort(_("please specify just one revision"))
2631 2631 if not node:
2632 2632 node = opts.get('rev')
2633 2633
2634 2634 if not node:
2635 2635 branch = repo.changectx(None).branch()
2636 2636 bheads = repo.branchheads(branch)
2637 2637 if len(bheads) > 2:
2638 2638 raise util.Abort(_(
2639 2639 'branch \'%s\' has %d heads - '
2640 2640 'please merge with an explicit rev\n'
2641 2641 '(run \'hg heads .\' to see heads)')
2642 2642 % (branch, len(bheads)))
2643 2643
2644 2644 parent = repo.dirstate.parents()[0]
2645 2645 if len(bheads) == 1:
2646 2646 if len(repo.heads()) > 1:
2647 2647 raise util.Abort(_(
2648 2648 'branch \'%s\' has one head - '
2649 2649 'please merge with an explicit rev\n'
2650 2650 '(run \'hg heads\' to see all heads)')
2651 2651 % branch)
2652 2652 msg = _('there is nothing to merge')
2653 2653 if parent != repo.lookup(repo[None].branch()):
2654 2654 msg = _('%s - use "hg update" instead') % msg
2655 2655 raise util.Abort(msg)
2656 2656
2657 2657 if parent not in bheads:
2658 2658 raise util.Abort(_('working dir not at a head rev - '
2659 2659 'use "hg update" or merge with an explicit rev'))
2660 2660 node = parent == bheads[0] and bheads[-1] or bheads[0]
2661 2661
2662 2662 if opts.get('preview'):
2663 2663 # find nodes that are ancestors of p2 but not of p1
2664 2664 p1 = repo.lookup('.')
2665 2665 p2 = repo.lookup(node)
2666 2666 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
2667 2667
2668 2668 displayer = cmdutil.show_changeset(ui, repo, opts)
2669 2669 for node in nodes:
2670 2670 displayer.show(repo[node])
2671 2671 displayer.close()
2672 2672 return 0
2673 2673
2674 2674 return hg.merge(repo, node, force=opts.get('force'))
2675 2675
2676 2676 def outgoing(ui, repo, dest=None, **opts):
2677 2677 """show changesets not found in the destination
2678 2678
2679 2679 Show changesets not found in the specified destination repository
2680 2680 or the default push location. These are the changesets that would
2681 2681 be pushed if a push was requested.
2682 2682
2683 2683 See pull for details of valid destination formats.
2684 2684
2685 2685 Returns 0 if there are outgoing changes, 1 otherwise.
2686 2686 """
2687 2687 limit = cmdutil.loglimit(opts)
2688 2688 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2689 2689 dest, branches = hg.parseurl(dest, opts.get('branch'))
2690 2690 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2691 2691 if revs:
2692 2692 revs = [repo.lookup(rev) for rev in revs]
2693 2693
2694 2694 other = hg.repository(hg.remoteui(repo, opts), dest)
2695 2695 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2696 2696 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
2697 2697 if not o:
2698 2698 ui.status(_("no changes found\n"))
2699 2699 return 1
2700 2700 o = repo.changelog.nodesbetween(o, revs)[0]
2701 2701 if opts.get('newest_first'):
2702 2702 o.reverse()
2703 2703 displayer = cmdutil.show_changeset(ui, repo, opts)
2704 2704 count = 0
2705 2705 for n in o:
2706 2706 if limit is not None and count >= limit:
2707 2707 break
2708 2708 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2709 2709 if opts.get('no_merges') and len(parents) == 2:
2710 2710 continue
2711 2711 count += 1
2712 2712 displayer.show(repo[n])
2713 2713 displayer.close()
2714 2714
2715 2715 def parents(ui, repo, file_=None, **opts):
2716 2716 """show the parents of the working directory or revision
2717 2717
2718 2718 Print the working directory's parent revisions. If a revision is
2719 2719 given via -r/--rev, the parent of that revision will be printed.
2720 2720 If a file argument is given, the revision in which the file was
2721 2721 last changed (before the working directory revision or the
2722 2722 argument to --rev if given) is printed.
2723 2723
2724 2724 Returns 0 on success.
2725 2725 """
2726 2726 rev = opts.get('rev')
2727 2727 if rev:
2728 2728 ctx = repo[rev]
2729 2729 else:
2730 2730 ctx = repo[None]
2731 2731
2732 2732 if file_:
2733 2733 m = cmdutil.match(repo, (file_,), opts)
2734 2734 if m.anypats() or len(m.files()) != 1:
2735 2735 raise util.Abort(_('can only specify an explicit filename'))
2736 2736 file_ = m.files()[0]
2737 2737 filenodes = []
2738 2738 for cp in ctx.parents():
2739 2739 if not cp:
2740 2740 continue
2741 2741 try:
2742 2742 filenodes.append(cp.filenode(file_))
2743 2743 except error.LookupError:
2744 2744 pass
2745 2745 if not filenodes:
2746 2746 raise util.Abort(_("'%s' not found in manifest!") % file_)
2747 2747 fl = repo.file(file_)
2748 2748 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2749 2749 else:
2750 2750 p = [cp.node() for cp in ctx.parents()]
2751 2751
2752 2752 displayer = cmdutil.show_changeset(ui, repo, opts)
2753 2753 for n in p:
2754 2754 if n != nullid:
2755 2755 displayer.show(repo[n])
2756 2756 displayer.close()
2757 2757
2758 2758 def paths(ui, repo, search=None):
2759 2759 """show aliases for remote repositories
2760 2760
2761 2761 Show definition of symbolic path name NAME. If no name is given,
2762 2762 show definition of all available names.
2763 2763
2764 2764 Path names are defined in the [paths] section of your
2765 2765 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
2766 2766 repository, ``.hg/hgrc`` is used, too.
2767 2767
2768 2768 The path names ``default`` and ``default-push`` have a special
2769 2769 meaning. When performing a push or pull operation, they are used
2770 2770 as fallbacks if no location is specified on the command-line.
2771 2771 When ``default-push`` is set, it will be used for push and
2772 2772 ``default`` will be used for pull; otherwise ``default`` is used
2773 2773 as the fallback for both. When cloning a repository, the clone
2774 2774 source is written as ``default`` in ``.hg/hgrc``. Note that
2775 2775 ``default`` and ``default-push`` apply to all inbound (e.g.
2776 2776 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
2777 2777 :hg:`bundle`) operations.
2778 2778
2779 2779 See :hg:`help urls` for more information.
2780 2780
2781 2781 Returns 0 on success.
2782 2782 """
2783 2783 if search:
2784 2784 for name, path in ui.configitems("paths"):
2785 2785 if name == search:
2786 2786 ui.write("%s\n" % url.hidepassword(path))
2787 2787 return
2788 2788 ui.warn(_("not found!\n"))
2789 2789 return 1
2790 2790 else:
2791 2791 for name, path in ui.configitems("paths"):
2792 2792 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2793 2793
2794 2794 def postincoming(ui, repo, modheads, optupdate, checkout):
2795 2795 if modheads == 0:
2796 2796 return
2797 2797 if optupdate:
2798 2798 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2799 2799 return hg.update(repo, checkout)
2800 2800 else:
2801 2801 ui.status(_("not updating, since new heads added\n"))
2802 2802 if modheads > 1:
2803 2803 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2804 2804 else:
2805 2805 ui.status(_("(run 'hg update' to get a working copy)\n"))
2806 2806
2807 2807 def pull(ui, repo, source="default", **opts):
2808 2808 """pull changes from the specified source
2809 2809
2810 2810 Pull changes from a remote repository to a local one.
2811 2811
2812 2812 This finds all changes from the repository at the specified path
2813 2813 or URL and adds them to a local repository (the current one unless
2814 2814 -R is specified). By default, this does not update the copy of the
2815 2815 project in the working directory.
2816 2816
2817 2817 Use :hg:`incoming` if you want to see what would have been added
2818 2818 by a pull at the time you issued this command. If you then decide
2819 2819 to add those changes to the repository, you should use :hg:`pull
2820 2820 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
2821 2821
2822 2822 If SOURCE is omitted, the 'default' path will be used.
2823 2823 See :hg:`help urls` for more information.
2824 2824
2825 2825 Returns 0 on success, 1 if an update had unresolved files.
2826 2826 """
2827 2827 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2828 2828 other = hg.repository(hg.remoteui(repo, opts), source)
2829 2829 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2830 2830 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2831 2831 if revs:
2832 2832 try:
2833 2833 revs = [other.lookup(rev) for rev in revs]
2834 2834 except error.CapabilityError:
2835 2835 err = _("other repository doesn't support revision lookup, "
2836 2836 "so a rev cannot be specified.")
2837 2837 raise util.Abort(err)
2838 2838
2839 2839 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2840 2840 if checkout:
2841 2841 checkout = str(repo.changelog.rev(other.lookup(checkout)))
2842 2842 return postincoming(ui, repo, modheads, opts.get('update'), checkout)
2843 2843
2844 2844 def push(ui, repo, dest=None, **opts):
2845 2845 """push changes to the specified destination
2846 2846
2847 2847 Push changesets from the local repository to the specified
2848 2848 destination.
2849 2849
2850 2850 This operation is symmetrical to pull: it is identical to a pull
2851 2851 in the destination repository from the current one.
2852 2852
2853 2853 By default, push will not allow creation of new heads at the
2854 2854 destination, since multiple heads would make it unclear which head
2855 2855 to use. In this situation, it is recommended to pull and merge
2856 2856 before pushing.
2857 2857
2858 2858 Use --new-branch if you want to allow push to create a new named
2859 2859 branch that is not present at the destination. This allows you to
2860 2860 only create a new branch without forcing other changes.
2861 2861
2862 2862 Use -f/--force to override the default behavior and push all
2863 2863 changesets on all branches.
2864 2864
2865 2865 If -r/--rev is used, the specified revision and all its ancestors
2866 2866 will be pushed to the remote repository.
2867 2867
2868 2868 Please see :hg:`help urls` for important details about ``ssh://``
2869 2869 URLs. If DESTINATION is omitted, a default path will be used.
2870 2870
2871 2871 Returns 0 if push was successful, 1 if nothing to push.
2872 2872 """
2873 2873 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2874 2874 dest, branches = hg.parseurl(dest, opts.get('branch'))
2875 2875 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2876 2876 other = hg.repository(hg.remoteui(repo, opts), dest)
2877 2877 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
2878 2878 if revs:
2879 2879 revs = [repo.lookup(rev) for rev in revs]
2880 2880
2881 2881 # push subrepos depth-first for coherent ordering
2882 2882 c = repo['']
2883 2883 subs = c.substate # only repos that are committed
2884 2884 for s in sorted(subs):
2885 2885 if not c.sub(s).push(opts.get('force')):
2886 2886 return False
2887 2887
2888 2888 r = repo.push(other, opts.get('force'), revs=revs,
2889 2889 newbranch=opts.get('new_branch'))
2890 2890 return r == 0
2891 2891
2892 2892 def recover(ui, repo):
2893 2893 """roll back an interrupted transaction
2894 2894
2895 2895 Recover from an interrupted commit or pull.
2896 2896
2897 2897 This command tries to fix the repository status after an
2898 2898 interrupted operation. It should only be necessary when Mercurial
2899 2899 suggests it.
2900 2900
2901 2901 Returns 0 if successful, 1 if nothing to recover or verify fails.
2902 2902 """
2903 2903 if repo.recover():
2904 2904 return hg.verify(repo)
2905 2905 return 1
2906 2906
2907 2907 def remove(ui, repo, *pats, **opts):
2908 2908 """remove the specified files on the next commit
2909 2909
2910 2910 Schedule the indicated files for removal from the repository.
2911 2911
2912 2912 This only removes files from the current branch, not from the
2913 2913 entire project history. -A/--after can be used to remove only
2914 2914 files that have already been deleted, -f/--force can be used to
2915 2915 force deletion, and -Af can be used to remove files from the next
2916 2916 revision without deleting them from the working directory.
2917 2917
2918 2918 The following table details the behavior of remove for different
2919 2919 file states (columns) and option combinations (rows). The file
2920 2920 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
2921 2921 reported by :hg:`status`). The actions are Warn, Remove (from
2922 2922 branch) and Delete (from disk)::
2923 2923
2924 2924 A C M !
2925 2925 none W RD W R
2926 2926 -f R RD RD R
2927 2927 -A W W W R
2928 2928 -Af R R R R
2929 2929
2930 2930 This command schedules the files to be removed at the next commit.
2931 2931 To undo a remove before that, see :hg:`revert`.
2932 2932
2933 2933 Returns 0 on success, 1 if any warnings encountered.
2934 2934 """
2935 2935
2936 2936 ret = 0
2937 2937 after, force = opts.get('after'), opts.get('force')
2938 2938 if not pats and not after:
2939 2939 raise util.Abort(_('no files specified'))
2940 2940
2941 2941 m = cmdutil.match(repo, pats, opts)
2942 2942 s = repo.status(match=m, clean=True)
2943 2943 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2944 2944
2945 2945 for f in m.files():
2946 2946 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2947 2947 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
2948 2948 ret = 1
2949 2949
2950 2950 if force:
2951 2951 remove, forget = modified + deleted + clean, added
2952 2952 elif after:
2953 2953 remove, forget = deleted, []
2954 2954 for f in modified + added + clean:
2955 2955 ui.warn(_('not removing %s: file still exists (use -f'
2956 2956 ' to force removal)\n') % m.rel(f))
2957 2957 ret = 1
2958 2958 else:
2959 2959 remove, forget = deleted + clean, []
2960 2960 for f in modified:
2961 2961 ui.warn(_('not removing %s: file is modified (use -f'
2962 2962 ' to force removal)\n') % m.rel(f))
2963 2963 ret = 1
2964 2964 for f in added:
2965 2965 ui.warn(_('not removing %s: file has been marked for add (use -f'
2966 2966 ' to force removal)\n') % m.rel(f))
2967 2967 ret = 1
2968 2968
2969 2969 for f in sorted(remove + forget):
2970 2970 if ui.verbose or not m.exact(f):
2971 2971 ui.status(_('removing %s\n') % m.rel(f))
2972 2972
2973 2973 repo[None].forget(forget)
2974 2974 repo[None].remove(remove, unlink=not after)
2975 2975 return ret
2976 2976
2977 2977 def rename(ui, repo, *pats, **opts):
2978 2978 """rename files; equivalent of copy + remove
2979 2979
2980 2980 Mark dest as copies of sources; mark sources for deletion. If dest
2981 2981 is a directory, copies are put in that directory. If dest is a
2982 2982 file, there can only be one source.
2983 2983
2984 2984 By default, this command copies the contents of files as they
2985 2985 exist in the working directory. If invoked with -A/--after, the
2986 2986 operation is recorded, but no copying is performed.
2987 2987
2988 2988 This command takes effect at the next commit. To undo a rename
2989 2989 before that, see :hg:`revert`.
2990 2990
2991 2991 Returns 0 on success, 1 if errors are encountered.
2992 2992 """
2993 2993 wlock = repo.wlock(False)
2994 2994 try:
2995 2995 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2996 2996 finally:
2997 2997 wlock.release()
2998 2998
2999 2999 def resolve(ui, repo, *pats, **opts):
3000 3000 """redo merges or set/view the merge status of files
3001 3001
3002 3002 Merges with unresolved conflicts are often the result of
3003 3003 non-interactive merging using the ``internal:merge`` configuration
3004 3004 setting, or a command-line merge tool like ``diff3``. The resolve
3005 3005 command is used to manage the files involved in a merge, after
3006 3006 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
3007 3007 working directory must have two parents).
3008 3008
3009 3009 The resolve command can be used in the following ways:
3010 3010
3011 3011 - :hg:`resolve FILE...`: attempt to re-merge the specified files,
3012 3012 discarding any previous merge attempts. Re-merging is not
3013 3013 performed for files already marked as resolved. Use ``--all/-a``
3014 3014 to selects all unresolved files.
3015 3015
3016 3016 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
3017 3017 (e.g. after having manually fixed-up the files). The default is
3018 3018 to mark all unresolved files.
3019 3019
3020 3020 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
3021 3021 default is to mark all resolved files.
3022 3022
3023 3023 - :hg:`resolve -l`: list files which had or still have conflicts.
3024 3024 In the printed list, ``U`` = unresolved and ``R`` = resolved.
3025 3025
3026 3026 Note that Mercurial will not let you commit files with unresolved
3027 3027 merge conflicts. You must use :hg:`resolve -m ...` before you can
3028 3028 commit after a conflicting merge.
3029 3029
3030 3030 Returns 0 on success, 1 if any files fail a resolve attempt.
3031 3031 """
3032 3032
3033 3033 all, mark, unmark, show, nostatus = \
3034 3034 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
3035 3035
3036 3036 if (show and (mark or unmark)) or (mark and unmark):
3037 3037 raise util.Abort(_("too many options specified"))
3038 3038 if pats and all:
3039 3039 raise util.Abort(_("can't specify --all and patterns"))
3040 3040 if not (all or pats or show or mark or unmark):
3041 3041 raise util.Abort(_('no files or directories specified; '
3042 3042 'use --all to remerge all files'))
3043 3043
3044 3044 ms = mergemod.mergestate(repo)
3045 3045 m = cmdutil.match(repo, pats, opts)
3046 3046 ret = 0
3047 3047
3048 3048 for f in ms:
3049 3049 if m(f):
3050 3050 if show:
3051 3051 if nostatus:
3052 3052 ui.write("%s\n" % f)
3053 3053 else:
3054 3054 ui.write("%s %s\n" % (ms[f].upper(), f),
3055 3055 label='resolve.' +
3056 3056 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
3057 3057 elif mark:
3058 3058 ms.mark(f, "r")
3059 3059 elif unmark:
3060 3060 ms.mark(f, "u")
3061 3061 else:
3062 3062 wctx = repo[None]
3063 3063 mctx = wctx.parents()[-1]
3064 3064
3065 3065 # backup pre-resolve (merge uses .orig for its own purposes)
3066 3066 a = repo.wjoin(f)
3067 3067 util.copyfile(a, a + ".resolve")
3068 3068
3069 3069 # resolve file
3070 3070 if ms.resolve(f, wctx, mctx):
3071 3071 ret = 1
3072 3072
3073 3073 # replace filemerge's .orig file with our resolve file
3074 3074 util.rename(a + ".resolve", a + ".orig")
3075 3075 return ret
3076 3076
3077 3077 def revert(ui, repo, *pats, **opts):
3078 3078 """restore individual files or directories to an earlier state
3079 3079
3080 3080 NOTE: This command is most likely not what you are looking for. revert
3081 3081 will partially overwrite content in the working directory without changing
3082 3082 the working directory parents. Use :hg:`update -r rev` to check out earlier
3083 3083 revisions, or :hg:`update --clean .` to undo a merge which has added
3084 3084 another parent.
3085 3085
3086 3086 With no revision specified, revert the named files or directories
3087 3087 to the contents they had in the parent of the working directory.
3088 3088 This restores the contents of the affected files to an unmodified
3089 3089 state and unschedules adds, removes, copies, and renames. If the
3090 3090 working directory has two parents, you must explicitly specify a
3091 3091 revision.
3092 3092
3093 3093 Using the -r/--rev option, revert the given files or directories
3094 3094 to their contents as of a specific revision. This can be helpful
3095 3095 to "roll back" some or all of an earlier change. See :hg:`help
3096 3096 dates` for a list of formats valid for -d/--date.
3097 3097
3098 3098 Revert modifies the working directory. It does not commit any
3099 3099 changes, or change the parent of the working directory. If you
3100 3100 revert to a revision other than the parent of the working
3101 3101 directory, the reverted files will thus appear modified
3102 3102 afterwards.
3103 3103
3104 3104 If a file has been deleted, it is restored. If the executable mode
3105 3105 of a file was changed, it is reset.
3106 3106
3107 3107 If names are given, all files matching the names are reverted.
3108 3108 If no arguments are given, no files are reverted.
3109 3109
3110 3110 Modified files are saved with a .orig suffix before reverting.
3111 3111 To disable these backups, use --no-backup.
3112 3112
3113 3113 Returns 0 on success.
3114 3114 """
3115 3115
3116 3116 if opts.get("date"):
3117 3117 if opts.get("rev"):
3118 3118 raise util.Abort(_("you can't specify a revision and a date"))
3119 3119 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
3120 3120
3121 3121 if not pats and not opts.get('all'):
3122 3122 raise util.Abort(_('no files or directories specified; '
3123 3123 'use --all to revert the whole repo'))
3124 3124
3125 3125 parent, p2 = repo.dirstate.parents()
3126 3126 if not opts.get('rev') and p2 != nullid:
3127 3127 raise util.Abort(_('uncommitted merge - please provide a '
3128 3128 'specific revision'))
3129 3129 ctx = repo[opts.get('rev')]
3130 3130 node = ctx.node()
3131 3131 mf = ctx.manifest()
3132 3132 if node == parent:
3133 3133 pmf = mf
3134 3134 else:
3135 3135 pmf = None
3136 3136
3137 3137 # need all matching names in dirstate and manifest of target rev,
3138 3138 # so have to walk both. do not print errors if files exist in one
3139 3139 # but not other.
3140 3140
3141 3141 names = {}
3142 3142
3143 3143 wlock = repo.wlock()
3144 3144 try:
3145 3145 # walk dirstate.
3146 3146
3147 3147 m = cmdutil.match(repo, pats, opts)
3148 3148 m.bad = lambda x, y: False
3149 3149 for abs in repo.walk(m):
3150 3150 names[abs] = m.rel(abs), m.exact(abs)
3151 3151
3152 3152 # walk target manifest.
3153 3153
3154 3154 def badfn(path, msg):
3155 3155 if path in names:
3156 3156 return
3157 3157 path_ = path + '/'
3158 3158 for f in names:
3159 3159 if f.startswith(path_):
3160 3160 return
3161 3161 ui.warn("%s: %s\n" % (m.rel(path), msg))
3162 3162
3163 3163 m = cmdutil.match(repo, pats, opts)
3164 3164 m.bad = badfn
3165 3165 for abs in repo[node].walk(m):
3166 3166 if abs not in names:
3167 3167 names[abs] = m.rel(abs), m.exact(abs)
3168 3168
3169 3169 m = cmdutil.matchfiles(repo, names)
3170 3170 changes = repo.status(match=m)[:4]
3171 3171 modified, added, removed, deleted = map(set, changes)
3172 3172
3173 3173 # if f is a rename, also revert the source
3174 3174 cwd = repo.getcwd()
3175 3175 for f in added:
3176 3176 src = repo.dirstate.copied(f)
3177 3177 if src and src not in names and repo.dirstate[src] == 'r':
3178 3178 removed.add(src)
3179 3179 names[src] = (repo.pathto(src, cwd), True)
3180 3180
3181 3181 def removeforget(abs):
3182 3182 if repo.dirstate[abs] == 'a':
3183 3183 return _('forgetting %s\n')
3184 3184 return _('removing %s\n')
3185 3185
3186 3186 revert = ([], _('reverting %s\n'))
3187 3187 add = ([], _('adding %s\n'))
3188 3188 remove = ([], removeforget)
3189 3189 undelete = ([], _('undeleting %s\n'))
3190 3190
3191 3191 disptable = (
3192 3192 # dispatch table:
3193 3193 # file state
3194 3194 # action if in target manifest
3195 3195 # action if not in target manifest
3196 3196 # make backup if in target manifest
3197 3197 # make backup if not in target manifest
3198 3198 (modified, revert, remove, True, True),
3199 3199 (added, revert, remove, True, False),
3200 3200 (removed, undelete, None, False, False),
3201 3201 (deleted, revert, remove, False, False),
3202 3202 )
3203 3203
3204 3204 for abs, (rel, exact) in sorted(names.items()):
3205 3205 mfentry = mf.get(abs)
3206 3206 target = repo.wjoin(abs)
3207 3207 def handle(xlist, dobackup):
3208 3208 xlist[0].append(abs)
3209 3209 if (dobackup and not opts.get('no_backup') and
3210 3210 os.path.lexists(target)):
3211 3211 bakname = "%s.orig" % rel
3212 3212 ui.note(_('saving current version of %s as %s\n') %
3213 3213 (rel, bakname))
3214 3214 if not opts.get('dry_run'):
3215 3215 util.rename(target, bakname)
3216 3216 if ui.verbose or not exact:
3217 3217 msg = xlist[1]
3218 3218 if not isinstance(msg, basestring):
3219 3219 msg = msg(abs)
3220 3220 ui.status(msg % rel)
3221 3221 for table, hitlist, misslist, backuphit, backupmiss in disptable:
3222 3222 if abs not in table:
3223 3223 continue
3224 3224 # file has changed in dirstate
3225 3225 if mfentry:
3226 3226 handle(hitlist, backuphit)
3227 3227 elif misslist is not None:
3228 3228 handle(misslist, backupmiss)
3229 3229 break
3230 3230 else:
3231 3231 if abs not in repo.dirstate:
3232 3232 if mfentry:
3233 3233 handle(add, True)
3234 3234 elif exact:
3235 3235 ui.warn(_('file not managed: %s\n') % rel)
3236 3236 continue
3237 3237 # file has not changed in dirstate
3238 3238 if node == parent:
3239 3239 if exact:
3240 3240 ui.warn(_('no changes needed to %s\n') % rel)
3241 3241 continue
3242 3242 if pmf is None:
3243 3243 # only need parent manifest in this unlikely case,
3244 3244 # so do not read by default
3245 3245 pmf = repo[parent].manifest()
3246 3246 if abs in pmf:
3247 3247 if mfentry:
3248 3248 # if version of file is same in parent and target
3249 3249 # manifests, do nothing
3250 3250 if (pmf[abs] != mfentry or
3251 3251 pmf.flags(abs) != mf.flags(abs)):
3252 3252 handle(revert, False)
3253 3253 else:
3254 3254 handle(remove, False)
3255 3255
3256 3256 if not opts.get('dry_run'):
3257 3257 def checkout(f):
3258 3258 fc = ctx[f]
3259 3259 repo.wwrite(f, fc.data(), fc.flags())
3260 3260
3261 3261 audit_path = util.path_auditor(repo.root)
3262 3262 for f in remove[0]:
3263 3263 if repo.dirstate[f] == 'a':
3264 3264 repo.dirstate.forget(f)
3265 3265 continue
3266 3266 audit_path(f)
3267 3267 try:
3268 3268 util.unlink(repo.wjoin(f))
3269 3269 except OSError:
3270 3270 pass
3271 3271 repo.dirstate.remove(f)
3272 3272
3273 3273 normal = None
3274 3274 if node == parent:
3275 3275 # We're reverting to our parent. If possible, we'd like status
3276 3276 # to report the file as clean. We have to use normallookup for
3277 3277 # merges to avoid losing information about merged/dirty files.
3278 3278 if p2 != nullid:
3279 3279 normal = repo.dirstate.normallookup
3280 3280 else:
3281 3281 normal = repo.dirstate.normal
3282 3282 for f in revert[0]:
3283 3283 checkout(f)
3284 3284 if normal:
3285 3285 normal(f)
3286 3286
3287 3287 for f in add[0]:
3288 3288 checkout(f)
3289 3289 repo.dirstate.add(f)
3290 3290
3291 3291 normal = repo.dirstate.normallookup
3292 3292 if node == parent and p2 == nullid:
3293 3293 normal = repo.dirstate.normal
3294 3294 for f in undelete[0]:
3295 3295 checkout(f)
3296 3296 normal(f)
3297 3297
3298 3298 finally:
3299 3299 wlock.release()
3300 3300
3301 3301 def rollback(ui, repo, **opts):
3302 3302 """roll back the last transaction (dangerous)
3303 3303
3304 3304 This command should be used with care. There is only one level of
3305 3305 rollback, and there is no way to undo a rollback. It will also
3306 3306 restore the dirstate at the time of the last transaction, losing
3307 3307 any dirstate changes since that time. This command does not alter
3308 3308 the working directory.
3309 3309
3310 3310 Transactions are used to encapsulate the effects of all commands
3311 3311 that create new changesets or propagate existing changesets into a
3312 3312 repository. For example, the following commands are transactional,
3313 3313 and their effects can be rolled back:
3314 3314
3315 3315 - commit
3316 3316 - import
3317 3317 - pull
3318 3318 - push (with this repository as the destination)
3319 3319 - unbundle
3320 3320
3321 3321 This command is not intended for use on public repositories. Once
3322 3322 changes are visible for pull by other users, rolling a transaction
3323 3323 back locally is ineffective (someone else may already have pulled
3324 3324 the changes). Furthermore, a race is possible with readers of the
3325 3325 repository; for example an in-progress pull from the repository
3326 3326 may fail if a rollback is performed.
3327 3327
3328 3328 Returns 0 on success, 1 if no rollback data is available.
3329 3329 """
3330 3330 return repo.rollback(opts.get('dry_run'))
3331 3331
3332 3332 def root(ui, repo):
3333 3333 """print the root (top) of the current working directory
3334 3334
3335 3335 Print the root directory of the current repository.
3336 3336
3337 3337 Returns 0 on success.
3338 3338 """
3339 3339 ui.write(repo.root + "\n")
3340 3340
3341 3341 def serve(ui, repo, **opts):
3342 3342 """start stand-alone webserver
3343 3343
3344 3344 Start a local HTTP repository browser and pull server. You can use
3345 3345 this for ad-hoc sharing and browing of repositories. It is
3346 3346 recommended to use a real web server to serve a repository for
3347 3347 longer periods of time.
3348 3348
3349 3349 Please note that the server does not implement access control.
3350 3350 This means that, by default, anybody can read from the server and
3351 3351 nobody can write to it by default. Set the ``web.allow_push``
3352 3352 option to ``*`` to allow everybody to push to the server. You
3353 3353 should use a real web server if you need to authenticate users.
3354 3354
3355 3355 By default, the server logs accesses to stdout and errors to
3356 3356 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
3357 3357 files.
3358 3358
3359 3359 To have the server choose a free port number to listen on, specify
3360 3360 a port number of 0; in this case, the server will print the port
3361 3361 number it uses.
3362 3362
3363 3363 Returns 0 on success.
3364 3364 """
3365 3365
3366 3366 if opts["stdio"]:
3367 3367 if repo is None:
3368 3368 raise error.RepoError(_("There is no Mercurial repository here"
3369 3369 " (.hg not found)"))
3370 3370 s = sshserver.sshserver(ui, repo)
3371 3371 s.serve_forever()
3372 3372
3373 3373 # this way we can check if something was given in the command-line
3374 3374 if opts.get('port'):
3375 3375 opts['port'] = util.getport(opts.get('port'))
3376 3376
3377 3377 baseui = repo and repo.baseui or ui
3378 3378 optlist = ("name templates style address port prefix ipv6"
3379 3379 " accesslog errorlog certificate encoding")
3380 3380 for o in optlist.split():
3381 3381 val = opts.get(o, '')
3382 3382 if val in (None, ''): # should check against default options instead
3383 3383 continue
3384 3384 baseui.setconfig("web", o, val)
3385 3385 if repo and repo.ui != baseui:
3386 3386 repo.ui.setconfig("web", o, val)
3387 3387
3388 3388 o = opts.get('web_conf') or opts.get('webdir_conf')
3389 3389 if not o:
3390 3390 if not repo:
3391 3391 raise error.RepoError(_("There is no Mercurial repository"
3392 3392 " here (.hg not found)"))
3393 3393 o = repo.root
3394 3394
3395 3395 app = hgweb.hgweb(o, baseui=ui)
3396 3396
3397 3397 class service(object):
3398 3398 def init(self):
3399 3399 util.set_signal_handler()
3400 3400 self.httpd = hgweb.server.create_server(ui, app)
3401 3401
3402 3402 if opts['port'] and not ui.verbose:
3403 3403 return
3404 3404
3405 3405 if self.httpd.prefix:
3406 3406 prefix = self.httpd.prefix.strip('/') + '/'
3407 3407 else:
3408 3408 prefix = ''
3409 3409
3410 3410 port = ':%d' % self.httpd.port
3411 3411 if port == ':80':
3412 3412 port = ''
3413 3413
3414 3414 bindaddr = self.httpd.addr
3415 3415 if bindaddr == '0.0.0.0':
3416 3416 bindaddr = '*'
3417 3417 elif ':' in bindaddr: # IPv6
3418 3418 bindaddr = '[%s]' % bindaddr
3419 3419
3420 3420 fqaddr = self.httpd.fqaddr
3421 3421 if ':' in fqaddr:
3422 3422 fqaddr = '[%s]' % fqaddr
3423 3423 if opts['port']:
3424 3424 write = ui.status
3425 3425 else:
3426 3426 write = ui.write
3427 3427 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
3428 3428 (fqaddr, port, prefix, bindaddr, self.httpd.port))
3429 3429
3430 3430 def run(self):
3431 3431 self.httpd.serve_forever()
3432 3432
3433 3433 service = service()
3434 3434
3435 3435 cmdutil.service(opts, initfn=service.init, runfn=service.run)
3436 3436
3437 3437 def status(ui, repo, *pats, **opts):
3438 3438 """show changed files in the working directory
3439 3439
3440 3440 Show status of files in the repository. If names are given, only
3441 3441 files that match are shown. Files that are clean or ignored or
3442 3442 the source of a copy/move operation, are not listed unless
3443 3443 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
3444 3444 Unless options described with "show only ..." are given, the
3445 3445 options -mardu are used.
3446 3446
3447 3447 Option -q/--quiet hides untracked (unknown and ignored) files
3448 3448 unless explicitly requested with -u/--unknown or -i/--ignored.
3449 3449
3450 3450 NOTE: status may appear to disagree with diff if permissions have
3451 3451 changed or a merge has occurred. The standard diff format does not
3452 3452 report permission changes and diff only reports changes relative
3453 3453 to one merge parent.
3454 3454
3455 3455 If one revision is given, it is used as the base revision.
3456 3456 If two revisions are given, the differences between them are
3457 3457 shown. The --change option can also be used as a shortcut to list
3458 3458 the changed files of a revision from its first parent.
3459 3459
3460 3460 The codes used to show the status of files are::
3461 3461
3462 3462 M = modified
3463 3463 A = added
3464 3464 R = removed
3465 3465 C = clean
3466 3466 ! = missing (deleted by non-hg command, but still tracked)
3467 3467 ? = not tracked
3468 3468 I = ignored
3469 3469 = origin of the previous file listed as A (added)
3470 3470
3471 3471 Returns 0 on success.
3472 3472 """
3473 3473
3474 3474 revs = opts.get('rev')
3475 3475 change = opts.get('change')
3476 3476
3477 3477 if revs and change:
3478 3478 msg = _('cannot specify --rev and --change at the same time')
3479 3479 raise util.Abort(msg)
3480 3480 elif change:
3481 3481 node2 = repo.lookup(change)
3482 3482 node1 = repo[node2].parents()[0].node()
3483 3483 else:
3484 3484 node1, node2 = cmdutil.revpair(repo, revs)
3485 3485
3486 3486 cwd = (pats and repo.getcwd()) or ''
3487 3487 end = opts.get('print0') and '\0' or '\n'
3488 3488 copy = {}
3489 3489 states = 'modified added removed deleted unknown ignored clean'.split()
3490 3490 show = [k for k in states if opts.get(k)]
3491 3491 if opts.get('all'):
3492 3492 show += ui.quiet and (states[:4] + ['clean']) or states
3493 3493 if not show:
3494 3494 show = ui.quiet and states[:4] or states[:5]
3495 3495
3496 3496 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
3497 3497 'ignored' in show, 'clean' in show, 'unknown' in show,
3498 3498 opts.get('subrepos'))
3499 3499 changestates = zip(states, 'MAR!?IC', stat)
3500 3500
3501 3501 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
3502 3502 ctxn = repo[nullid]
3503 3503 ctx1 = repo[node1]
3504 3504 ctx2 = repo[node2]
3505 3505 added = stat[1]
3506 3506 if node2 is None:
3507 3507 added = stat[0] + stat[1] # merged?
3508 3508
3509 3509 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
3510 3510 if k in added:
3511 3511 copy[k] = v
3512 3512 elif v in added:
3513 3513 copy[v] = k
3514 3514
3515 3515 for state, char, files in changestates:
3516 3516 if state in show:
3517 3517 format = "%s %%s%s" % (char, end)
3518 3518 if opts.get('no_status'):
3519 3519 format = "%%s%s" % end
3520 3520
3521 3521 for f in files:
3522 3522 ui.write(format % repo.pathto(f, cwd),
3523 3523 label='status.' + state)
3524 3524 if f in copy:
3525 3525 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
3526 3526 label='status.copied')
3527 3527
3528 3528 def summary(ui, repo, **opts):
3529 3529 """summarize working directory state
3530 3530
3531 3531 This generates a brief summary of the working directory state,
3532 3532 including parents, branch, commit status, and available updates.
3533 3533
3534 3534 With the --remote option, this will check the default paths for
3535 3535 incoming and outgoing changes. This can be time-consuming.
3536 3536
3537 3537 Returns 0 on success.
3538 3538 """
3539 3539
3540 3540 ctx = repo[None]
3541 3541 parents = ctx.parents()
3542 3542 pnode = parents[0].node()
3543 3543
3544 3544 for p in parents:
3545 3545 # label with log.changeset (instead of log.parent) since this
3546 3546 # shows a working directory parent *changeset*:
3547 3547 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
3548 3548 label='log.changeset')
3549 3549 ui.write(' '.join(p.tags()), label='log.tag')
3550 3550 if p.rev() == -1:
3551 3551 if not len(repo):
3552 3552 ui.write(_(' (empty repository)'))
3553 3553 else:
3554 3554 ui.write(_(' (no revision checked out)'))
3555 3555 ui.write('\n')
3556 3556 if p.description():
3557 3557 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
3558 3558 label='log.summary')
3559 3559
3560 3560 branch = ctx.branch()
3561 3561 bheads = repo.branchheads(branch)
3562 3562 m = _('branch: %s\n') % branch
3563 3563 if branch != 'default':
3564 3564 ui.write(m, label='log.branch')
3565 3565 else:
3566 3566 ui.status(m, label='log.branch')
3567 3567
3568 3568 st = list(repo.status(unknown=True))[:6]
3569 3569
3570 3570 c = repo.dirstate.copies()
3571 3571 copied, renamed = [], []
3572 3572 for d, s in c.iteritems():
3573 3573 if s in st[2]:
3574 3574 st[2].remove(s)
3575 3575 renamed.append(d)
3576 3576 else:
3577 3577 copied.append(d)
3578 3578 if d in st[1]:
3579 3579 st[1].remove(d)
3580 3580 st.insert(3, renamed)
3581 3581 st.insert(4, copied)
3582 3582
3583 3583 ms = mergemod.mergestate(repo)
3584 3584 st.append([f for f in ms if ms[f] == 'u'])
3585 3585
3586 3586 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
3587 3587 st.append(subs)
3588 3588
3589 3589 labels = [ui.label(_('%d modified'), 'status.modified'),
3590 3590 ui.label(_('%d added'), 'status.added'),
3591 3591 ui.label(_('%d removed'), 'status.removed'),
3592 3592 ui.label(_('%d renamed'), 'status.copied'),
3593 3593 ui.label(_('%d copied'), 'status.copied'),
3594 3594 ui.label(_('%d deleted'), 'status.deleted'),
3595 3595 ui.label(_('%d unknown'), 'status.unknown'),
3596 3596 ui.label(_('%d ignored'), 'status.ignored'),
3597 3597 ui.label(_('%d unresolved'), 'resolve.unresolved'),
3598 3598 ui.label(_('%d subrepos'), 'status.modified')]
3599 3599 t = []
3600 3600 for s, l in zip(st, labels):
3601 3601 if s:
3602 3602 t.append(l % len(s))
3603 3603
3604 3604 t = ', '.join(t)
3605 3605 cleanworkdir = False
3606 3606
3607 3607 if len(parents) > 1:
3608 3608 t += _(' (merge)')
3609 3609 elif branch != parents[0].branch():
3610 3610 t += _(' (new branch)')
3611 3611 elif (parents[0].extra().get('close') and
3612 3612 pnode in repo.branchheads(branch, closed=True)):
3613 3613 t += _(' (head closed)')
3614 3614 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
3615 3615 t += _(' (clean)')
3616 3616 cleanworkdir = True
3617 3617 elif pnode not in bheads:
3618 3618 t += _(' (new branch head)')
3619 3619
3620 3620 if cleanworkdir:
3621 3621 ui.status(_('commit: %s\n') % t.strip())
3622 3622 else:
3623 3623 ui.write(_('commit: %s\n') % t.strip())
3624 3624
3625 3625 # all ancestors of branch heads - all ancestors of parent = new csets
3626 3626 new = [0] * len(repo)
3627 3627 cl = repo.changelog
3628 3628 for a in [cl.rev(n) for n in bheads]:
3629 3629 new[a] = 1
3630 3630 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3631 3631 new[a] = 1
3632 3632 for a in [p.rev() for p in parents]:
3633 3633 if a >= 0:
3634 3634 new[a] = 0
3635 3635 for a in cl.ancestors(*[p.rev() for p in parents]):
3636 3636 new[a] = 0
3637 3637 new = sum(new)
3638 3638
3639 3639 if new == 0:
3640 3640 ui.status(_('update: (current)\n'))
3641 3641 elif pnode not in bheads:
3642 3642 ui.write(_('update: %d new changesets (update)\n') % new)
3643 3643 else:
3644 3644 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
3645 3645 (new, len(bheads)))
3646 3646
3647 3647 if opts.get('remote'):
3648 3648 t = []
3649 3649 source, branches = hg.parseurl(ui.expandpath('default'))
3650 3650 other = hg.repository(hg.remoteui(repo, {}), source)
3651 3651 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3652 3652 ui.debug('comparing with %s\n' % url.hidepassword(source))
3653 3653 repo.ui.pushbuffer()
3654 3654 common, incoming, rheads = discovery.findcommonincoming(repo, other)
3655 3655 repo.ui.popbuffer()
3656 3656 if incoming:
3657 3657 t.append(_('1 or more incoming'))
3658 3658
3659 3659 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
3660 3660 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3661 3661 other = hg.repository(hg.remoteui(repo, {}), dest)
3662 3662 ui.debug('comparing with %s\n' % url.hidepassword(dest))
3663 3663 repo.ui.pushbuffer()
3664 3664 o = discovery.findoutgoing(repo, other)
3665 3665 repo.ui.popbuffer()
3666 3666 o = repo.changelog.nodesbetween(o, None)[0]
3667 3667 if o:
3668 3668 t.append(_('%d outgoing') % len(o))
3669 3669
3670 3670 if t:
3671 3671 ui.write(_('remote: %s\n') % (', '.join(t)))
3672 3672 else:
3673 3673 ui.status(_('remote: (synced)\n'))
3674 3674
3675 3675 def tag(ui, repo, name1, *names, **opts):
3676 3676 """add one or more tags for the current or given revision
3677 3677
3678 3678 Name a particular revision using <name>.
3679 3679
3680 3680 Tags are used to name particular revisions of the repository and are
3681 3681 very useful to compare different revisions, to go back to significant
3682 3682 earlier versions or to mark branch points as releases, etc.
3683 3683
3684 3684 If no revision is given, the parent of the working directory is
3685 3685 used, or tip if no revision is checked out.
3686 3686
3687 3687 To facilitate version control, distribution, and merging of tags,
3688 3688 they are stored as a file named ".hgtags" which is managed
3689 3689 similarly to other project files and can be hand-edited if
3690 3690 necessary. The file '.hg/localtags' is used for local tags (not
3691 3691 shared among repositories).
3692 3692
3693 3693 See :hg:`help dates` for a list of formats valid for -d/--date.
3694 3694
3695 3695 Since tag names have priority over branch names during revision
3696 3696 lookup, using an existing branch name as a tag name is discouraged.
3697 3697
3698 3698 Returns 0 on success.
3699 3699 """
3700 3700
3701 3701 rev_ = "."
3702 3702 names = [t.strip() for t in (name1,) + names]
3703 3703 if len(names) != len(set(names)):
3704 3704 raise util.Abort(_('tag names must be unique'))
3705 3705 for n in names:
3706 3706 if n in ['tip', '.', 'null']:
3707 3707 raise util.Abort(_('the name \'%s\' is reserved') % n)
3708 3708 if not n:
3709 3709 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
3710 3710 if opts.get('rev') and opts.get('remove'):
3711 3711 raise util.Abort(_("--rev and --remove are incompatible"))
3712 3712 if opts.get('rev'):
3713 3713 rev_ = opts['rev']
3714 3714 message = opts.get('message')
3715 3715 if opts.get('remove'):
3716 3716 expectedtype = opts.get('local') and 'local' or 'global'
3717 3717 for n in names:
3718 3718 if not repo.tagtype(n):
3719 3719 raise util.Abort(_('tag \'%s\' does not exist') % n)
3720 3720 if repo.tagtype(n) != expectedtype:
3721 3721 if expectedtype == 'global':
3722 3722 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
3723 3723 else:
3724 3724 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
3725 3725 rev_ = nullid
3726 3726 if not message:
3727 3727 # we don't translate commit messages
3728 3728 message = 'Removed tag %s' % ', '.join(names)
3729 3729 elif not opts.get('force'):
3730 3730 for n in names:
3731 3731 if n in repo.tags():
3732 3732 raise util.Abort(_('tag \'%s\' already exists '
3733 3733 '(use -f to force)') % n)
3734 3734 if not rev_ and repo.dirstate.parents()[1] != nullid:
3735 3735 raise util.Abort(_('uncommitted merge - please provide a '
3736 3736 'specific revision'))
3737 3737 r = repo[rev_].node()
3738 3738
3739 3739 if not message:
3740 3740 # we don't translate commit messages
3741 3741 message = ('Added tag %s for changeset %s' %
3742 3742 (', '.join(names), short(r)))
3743 3743
3744 3744 date = opts.get('date')
3745 3745 if date:
3746 3746 date = util.parsedate(date)
3747 3747
3748 3748 if opts.get('edit'):
3749 3749 message = ui.edit(message, ui.username())
3750 3750
3751 3751 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
3752 3752
3753 3753 def tags(ui, repo):
3754 3754 """list repository tags
3755 3755
3756 3756 This lists both regular and local tags. When the -v/--verbose
3757 3757 switch is used, a third column "local" is printed for local tags.
3758 3758
3759 3759 Returns 0 on success.
3760 3760 """
3761 3761
3762 3762 hexfunc = ui.debugflag and hex or short
3763 3763 tagtype = ""
3764 3764
3765 3765 for t, n in reversed(repo.tagslist()):
3766 3766 if ui.quiet:
3767 3767 ui.write("%s\n" % t)
3768 3768 continue
3769 3769
3770 3770 try:
3771 3771 hn = hexfunc(n)
3772 3772 r = "%5d:%s" % (repo.changelog.rev(n), hn)
3773 3773 except error.LookupError:
3774 3774 r = " ?:%s" % hn
3775 3775 else:
3776 3776 spaces = " " * (30 - encoding.colwidth(t))
3777 3777 if ui.verbose:
3778 3778 if repo.tagtype(t) == 'local':
3779 3779 tagtype = " local"
3780 3780 else:
3781 3781 tagtype = ""
3782 3782 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
3783 3783
3784 3784 def tip(ui, repo, **opts):
3785 3785 """show the tip revision
3786 3786
3787 3787 The tip revision (usually just called the tip) is the changeset
3788 3788 most recently added to the repository (and therefore the most
3789 3789 recently changed head).
3790 3790
3791 3791 If you have just made a commit, that commit will be the tip. If
3792 3792 you have just pulled changes from another repository, the tip of
3793 3793 that repository becomes the current tip. The "tip" tag is special
3794 3794 and cannot be renamed or assigned to a different changeset.
3795 3795
3796 3796 Returns 0 on success.
3797 3797 """
3798 3798 displayer = cmdutil.show_changeset(ui, repo, opts)
3799 3799 displayer.show(repo[len(repo) - 1])
3800 3800 displayer.close()
3801 3801
3802 3802 def unbundle(ui, repo, fname1, *fnames, **opts):
3803 3803 """apply one or more changegroup files
3804 3804
3805 3805 Apply one or more compressed changegroup files generated by the
3806 3806 bundle command.
3807 3807
3808 3808 Returns 0 on success, 1 if an update has unresolved files.
3809 3809 """
3810 3810 fnames = (fname1,) + fnames
3811 3811
3812 3812 lock = repo.lock()
3813 3813 try:
3814 3814 for fname in fnames:
3815 3815 f = url.open(ui, fname)
3816 3816 gen = changegroup.readbundle(f, fname)
3817 3817 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
3818 3818 lock=lock)
3819 3819 finally:
3820 3820 lock.release()
3821 3821
3822 3822 return postincoming(ui, repo, modheads, opts.get('update'), None)
3823 3823
3824 3824 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
3825 3825 """update working directory (or switch revisions)
3826 3826
3827 3827 Update the repository's working directory to the specified
3828 3828 changeset.
3829 3829
3830 3830 If no changeset is specified, attempt to update to the tip of the
3831 3831 current branch. If this changeset is a descendant of the working
3832 3832 directory's parent, update to it, otherwise abort.
3833 3833
3834 3834 The following rules apply when the working directory contains
3835 3835 uncommitted changes:
3836 3836
3837 3837 1. If neither -c/--check nor -C/--clean is specified, and if
3838 3838 the requested changeset is an ancestor or descendant of
3839 3839 the working directory's parent, the uncommitted changes
3840 3840 are merged into the requested changeset and the merged
3841 3841 result is left uncommitted. If the requested changeset is
3842 3842 not an ancestor or descendant (that is, it is on another
3843 3843 branch), the update is aborted and the uncommitted changes
3844 3844 are preserved.
3845 3845
3846 3846 2. With the -c/--check option, the update is aborted and the
3847 3847 uncommitted changes are preserved.
3848 3848
3849 3849 3. With the -C/--clean option, uncommitted changes are discarded and
3850 3850 the working directory is updated to the requested changeset.
3851 3851
3852 3852 Use null as the changeset to remove the working directory (like
3853 3853 :hg:`clone -U`).
3854 3854
3855 3855 If you want to update just one file to an older changeset, use :hg:`revert`.
3856 3856
3857 3857 See :hg:`help dates` for a list of formats valid for -d/--date.
3858 3858
3859 3859 Returns 0 on success, 1 if there are unresolved files.
3860 3860 """
3861 3861 if rev and node:
3862 3862 raise util.Abort(_("please specify just one revision"))
3863 3863
3864 3864 if not rev:
3865 3865 rev = node
3866 3866
3867 3867 if check and clean:
3868 3868 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
3869 3869
3870 3870 if check:
3871 3871 # we could use dirty() but we can ignore merge and branch trivia
3872 3872 c = repo[None]
3873 3873 if c.modified() or c.added() or c.removed():
3874 3874 raise util.Abort(_("uncommitted local changes"))
3875 3875
3876 3876 if date:
3877 3877 if rev:
3878 3878 raise util.Abort(_("you can't specify a revision and a date"))
3879 3879 rev = cmdutil.finddate(ui, repo, date)
3880 3880
3881 3881 if clean or check:
3882 3882 return hg.clean(repo, rev)
3883 3883 else:
3884 3884 return hg.update(repo, rev)
3885 3885
3886 3886 def verify(ui, repo):
3887 3887 """verify the integrity of the repository
3888 3888
3889 3889 Verify the integrity of the current repository.
3890 3890
3891 3891 This will perform an extensive check of the repository's
3892 3892 integrity, validating the hashes and checksums of each entry in
3893 3893 the changelog, manifest, and tracked files, as well as the
3894 3894 integrity of their crosslinks and indices.
3895 3895
3896 3896 Returns 0 on success, 1 if errors are encountered.
3897 3897 """
3898 3898 return hg.verify(repo)
3899 3899
3900 3900 def version_(ui):
3901 3901 """output version and copyright information"""
3902 3902 ui.write(_("Mercurial Distributed SCM (version %s)\n")
3903 3903 % util.version())
3904 3904 ui.status(_(
3905 3905 "\nCopyright (C) 2005-2010 Matt Mackall <mpm@selenic.com> and others\n"
3906 3906 "This is free software; see the source for copying conditions. "
3907 3907 "There is NO\nwarranty; "
3908 3908 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
3909 3909 ))
3910 3910
3911 3911 # Command options and aliases are listed here, alphabetically
3912 3912
3913 3913 globalopts = [
3914 3914 ('R', 'repository', '',
3915 3915 _('repository root directory or name of overlay bundle file'),
3916 3916 _('REPO')),
3917 3917 ('', 'cwd', '',
3918 3918 _('change working directory'), _('DIR')),
3919 3919 ('y', 'noninteractive', None,
3920 3920 _('do not prompt, assume \'yes\' for any required answers')),
3921 3921 ('q', 'quiet', None, _('suppress output')),
3922 3922 ('v', 'verbose', None, _('enable additional output')),
3923 3923 ('', 'config', [],
3924 3924 _('set/override config option (use \'section.name=value\')'),
3925 3925 _('CONFIG')),
3926 3926 ('', 'debug', None, _('enable debugging output')),
3927 3927 ('', 'debugger', None, _('start debugger')),
3928 3928 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
3929 3929 _('ENCODE')),
3930 3930 ('', 'encodingmode', encoding.encodingmode,
3931 3931 _('set the charset encoding mode'), _('MODE')),
3932 3932 ('', 'traceback', None, _('always print a traceback on exception')),
3933 3933 ('', 'time', None, _('time how long the command takes')),
3934 3934 ('', 'profile', None, _('print command execution profile')),
3935 3935 ('', 'version', None, _('output version information and exit')),
3936 3936 ('h', 'help', None, _('display help and exit')),
3937 3937 ]
3938 3938
3939 3939 dryrunopts = [('n', 'dry-run', None,
3940 3940 _('do not perform actions, just print output'))]
3941 3941
3942 3942 remoteopts = [
3943 3943 ('e', 'ssh', '',
3944 3944 _('specify ssh command to use'), _('CMD')),
3945 3945 ('', 'remotecmd', '',
3946 3946 _('specify hg command to run on the remote side'), _('CMD')),
3947 3947 ]
3948 3948
3949 3949 walkopts = [
3950 3950 ('I', 'include', [],
3951 3951 _('include names matching the given patterns'), _('PATTERN')),
3952 3952 ('X', 'exclude', [],
3953 3953 _('exclude names matching the given patterns'), _('PATTERN')),
3954 3954 ]
3955 3955
3956 3956 commitopts = [
3957 3957 ('m', 'message', '',
3958 3958 _('use text as commit message'), _('TEXT')),
3959 3959 ('l', 'logfile', '',
3960 3960 _('read commit message from file'), _('FILE')),
3961 3961 ]
3962 3962
3963 3963 commitopts2 = [
3964 3964 ('d', 'date', '',
3965 3965 _('record datecode as commit date'), _('DATE')),
3966 3966 ('u', 'user', '',
3967 3967 _('record the specified user as committer'), _('USER')),
3968 3968 ]
3969 3969
3970 3970 templateopts = [
3971 3971 ('', 'style', '',
3972 3972 _('display using template map file'), _('STYLE')),
3973 3973 ('', 'template', '',
3974 3974 _('display with template'), _('TEMPLATE')),
3975 3975 ]
3976 3976
3977 3977 logopts = [
3978 3978 ('p', 'patch', None, _('show patch')),
3979 3979 ('g', 'git', None, _('use git extended diff format')),
3980 3980 ('l', 'limit', '',
3981 3981 _('limit number of changes displayed'), _('NUM')),
3982 3982 ('M', 'no-merges', None, _('do not show merges')),
3983 3983 ('', 'stat', None, _('output diffstat-style summary of changes')),
3984 3984 ] + templateopts
3985 3985
3986 3986 diffopts = [
3987 3987 ('a', 'text', None, _('treat all files as text')),
3988 3988 ('g', 'git', None, _('use git extended diff format')),
3989 3989 ('', 'nodates', None, _('omit dates from diff headers'))
3990 3990 ]
3991 3991
3992 3992 diffopts2 = [
3993 3993 ('p', 'show-function', None, _('show which function each change is in')),
3994 3994 ('', 'reverse', None, _('produce a diff that undoes the changes')),
3995 3995 ('w', 'ignore-all-space', None,
3996 3996 _('ignore white space when comparing lines')),
3997 3997 ('b', 'ignore-space-change', None,
3998 3998 _('ignore changes in the amount of white space')),
3999 3999 ('B', 'ignore-blank-lines', None,
4000 4000 _('ignore changes whose lines are all blank')),
4001 4001 ('U', 'unified', '',
4002 4002 _('number of lines of context to show'), _('NUM')),
4003 4003 ('', 'stat', None, _('output diffstat-style summary of changes')),
4004 4004 ]
4005 4005
4006 4006 similarityopts = [
4007 4007 ('s', 'similarity', '',
4008 4008 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
4009 4009 ]
4010 4010
4011 4011 subrepoopts = [
4012 4012 ('S', 'subrepos', None,
4013 4013 _('recurse into subrepositories'))
4014 4014 ]
4015 4015
4016 4016 table = {
4017 4017 "^add": (add, walkopts + dryrunopts, _('[OPTION]... [FILE]...')),
4018 4018 "addremove":
4019 4019 (addremove, similarityopts + walkopts + dryrunopts,
4020 4020 _('[OPTION]... [FILE]...')),
4021 4021 "^annotate|blame":
4022 4022 (annotate,
4023 4023 [('r', 'rev', '',
4024 4024 _('annotate the specified revision'), _('REV')),
4025 4025 ('', 'follow', None,
4026 4026 _('follow copies/renames and list the filename (DEPRECATED)')),
4027 4027 ('', 'no-follow', None, _("don't follow copies and renames")),
4028 4028 ('a', 'text', None, _('treat all files as text')),
4029 4029 ('u', 'user', None, _('list the author (long with -v)')),
4030 4030 ('f', 'file', None, _('list the filename')),
4031 4031 ('d', 'date', None, _('list the date (short with -q)')),
4032 4032 ('n', 'number', None, _('list the revision number (default)')),
4033 4033 ('c', 'changeset', None, _('list the changeset')),
4034 4034 ('l', 'line-number', None,
4035 4035 _('show line number at the first appearance'))
4036 4036 ] + walkopts,
4037 4037 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
4038 4038 "archive":
4039 4039 (archive,
4040 4040 [('', 'no-decode', None, _('do not pass files through decoders')),
4041 4041 ('p', 'prefix', '',
4042 4042 _('directory prefix for files in archive'), _('PREFIX')),
4043 4043 ('r', 'rev', '',
4044 4044 _('revision to distribute'), _('REV')),
4045 4045 ('t', 'type', '',
4046 4046 _('type of distribution to create'), _('TYPE')),
4047 4047 ] + walkopts,
4048 4048 _('[OPTION]... DEST')),
4049 4049 "backout":
4050 4050 (backout,
4051 4051 [('', 'merge', None,
4052 4052 _('merge with old dirstate parent after backout')),
4053 4053 ('', 'parent', '',
4054 4054 _('parent to choose when backing out merge'), _('REV')),
4055 4055 ('r', 'rev', '',
4056 4056 _('revision to backout'), _('REV')),
4057 4057 ] + walkopts + commitopts + commitopts2,
4058 4058 _('[OPTION]... [-r] REV')),
4059 4059 "bisect":
4060 4060 (bisect,
4061 4061 [('r', 'reset', False, _('reset bisect state')),
4062 4062 ('g', 'good', False, _('mark changeset good')),
4063 4063 ('b', 'bad', False, _('mark changeset bad')),
4064 4064 ('s', 'skip', False, _('skip testing changeset')),
4065 4065 ('c', 'command', '',
4066 4066 _('use command to check changeset state'), _('CMD')),
4067 4067 ('U', 'noupdate', False, _('do not update to target'))],
4068 4068 _("[-gbsr] [-U] [-c CMD] [REV]")),
4069 4069 "branch":
4070 4070 (branch,
4071 4071 [('f', 'force', None,
4072 4072 _('set branch name even if it shadows an existing branch')),
4073 4073 ('C', 'clean', None, _('reset branch name to parent branch name'))],
4074 4074 _('[-fC] [NAME]')),
4075 4075 "branches":
4076 4076 (branches,
4077 4077 [('a', 'active', False,
4078 4078 _('show only branches that have unmerged heads')),
4079 4079 ('c', 'closed', False,
4080 4080 _('show normal and closed branches'))],
4081 4081 _('[-ac]')),
4082 4082 "bundle":
4083 4083 (bundle,
4084 4084 [('f', 'force', None,
4085 4085 _('run even when the destination is unrelated')),
4086 4086 ('r', 'rev', [],
4087 4087 _('a changeset intended to be added to the destination'),
4088 4088 _('REV')),
4089 4089 ('b', 'branch', [],
4090 4090 _('a specific branch you would like to bundle'),
4091 4091 _('BRANCH')),
4092 4092 ('', 'base', [],
4093 4093 _('a base changeset assumed to be available at the destination'),
4094 4094 _('REV')),
4095 4095 ('a', 'all', None, _('bundle all changesets in the repository')),
4096 4096 ('t', 'type', 'bzip2',
4097 4097 _('bundle compression type to use'), _('TYPE')),
4098 4098 ] + remoteopts,
4099 4099 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
4100 4100 "cat":
4101 4101 (cat,
4102 4102 [('o', 'output', '',
4103 4103 _('print output to file with formatted name'), _('FORMAT')),
4104 4104 ('r', 'rev', '',
4105 4105 _('print the given revision'), _('REV')),
4106 4106 ('', 'decode', None, _('apply any matching decode filter')),
4107 4107 ] + walkopts,
4108 4108 _('[OPTION]... FILE...')),
4109 4109 "^clone":
4110 4110 (clone,
4111 4111 [('U', 'noupdate', None,
4112 4112 _('the clone will include an empty working copy (only a repository)')),
4113 4113 ('u', 'updaterev', '',
4114 4114 _('revision, tag or branch to check out'), _('REV')),
4115 4115 ('r', 'rev', [],
4116 4116 _('include the specified changeset'), _('REV')),
4117 4117 ('b', 'branch', [],
4118 4118 _('clone only the specified branch'), _('BRANCH')),
4119 4119 ('', 'pull', None, _('use pull protocol to copy metadata')),
4120 4120 ('', 'uncompressed', None,
4121 4121 _('use uncompressed transfer (fast over LAN)')),
4122 4122 ] + remoteopts,
4123 4123 _('[OPTION]... SOURCE [DEST]')),
4124 4124 "^commit|ci":
4125 4125 (commit,
4126 4126 [('A', 'addremove', None,
4127 4127 _('mark new/missing files as added/removed before committing')),
4128 4128 ('', 'close-branch', None,
4129 4129 _('mark a branch as closed, hiding it from the branch list')),
4130 4130 ] + walkopts + commitopts + commitopts2,
4131 4131 _('[OPTION]... [FILE]...')),
4132 4132 "copy|cp":
4133 4133 (copy,
4134 4134 [('A', 'after', None, _('record a copy that has already occurred')),
4135 4135 ('f', 'force', None,
4136 4136 _('forcibly copy over an existing managed file')),
4137 4137 ] + walkopts + dryrunopts,
4138 4138 _('[OPTION]... [SOURCE]... DEST')),
4139 4139 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
4140 4140 "debugbuilddag":
4141 4141 (debugbuilddag,
4142 4142 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
4143 4143 ('a', 'appended-file', None, _('add single file all revs append to')),
4144 4144 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
4145 4145 ('n', 'new-file', None, _('add new file at each rev')),
4146 4146 ],
4147 4147 _('[OPTION]... TEXT')),
4148 4148 "debugcheckstate": (debugcheckstate, [], ''),
4149 4149 "debugcommands": (debugcommands, [], _('[COMMAND]')),
4150 4150 "debugcomplete":
4151 4151 (debugcomplete,
4152 4152 [('o', 'options', None, _('show the command options'))],
4153 4153 _('[-o] CMD')),
4154 4154 "debugdag":
4155 4155 (debugdag,
4156 4156 [('t', 'tags', None, _('use tags as labels')),
4157 4157 ('b', 'branches', None, _('annotate with branch names')),
4158 4158 ('', 'dots', None, _('use dots for runs')),
4159 4159 ('s', 'spaces', None, _('separate elements by spaces')),
4160 4160 ],
4161 4161 _('[OPTION]... [FILE [REV]...]')),
4162 4162 "debugdate":
4163 4163 (debugdate,
4164 4164 [('e', 'extended', None, _('try extended date formats'))],
4165 4165 _('[-e] DATE [RANGE]')),
4166 4166 "debugdata": (debugdata, [], _('FILE REV')),
4167 4167 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
4168 4168 "debugindex": (debugindex, [], _('FILE')),
4169 4169 "debugindexdot": (debugindexdot, [], _('FILE')),
4170 4170 "debuginstall": (debuginstall, [], ''),
4171 4171 "debugpushkey": (debugpushkey, [], _('REPO NAMESPACE [KEY OLD NEW]')),
4172 4172 "debugrebuildstate":
4173 4173 (debugrebuildstate,
4174 4174 [('r', 'rev', '',
4175 4175 _('revision to rebuild to'), _('REV'))],
4176 4176 _('[-r REV] [REV]')),
4177 4177 "debugrename":
4178 4178 (debugrename,
4179 4179 [('r', 'rev', '',
4180 4180 _('revision to debug'), _('REV'))],
4181 4181 _('[-r REV] FILE')),
4182 4182 "debugrevspec":
4183 4183 (debugrevspec, [], ('REVSPEC')),
4184 4184 "debugsetparents":
4185 4185 (debugsetparents, [], _('REV1 [REV2]')),
4186 4186 "debugstate":
4187 4187 (debugstate,
4188 4188 [('', 'nodates', None, _('do not display the saved mtime'))],
4189 4189 _('[OPTION]...')),
4190 4190 "debugsub":
4191 4191 (debugsub,
4192 4192 [('r', 'rev', '',
4193 4193 _('revision to check'), _('REV'))],
4194 4194 _('[-r REV] [REV]')),
4195 4195 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
4196 4196 "^diff":
4197 4197 (diff,
4198 4198 [('r', 'rev', [],
4199 4199 _('revision'), _('REV')),
4200 4200 ('c', 'change', '',
4201 4201 _('change made by revision'), _('REV'))
4202 4202 ] + diffopts + diffopts2 + walkopts + subrepoopts,
4203 4203 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')),
4204 4204 "^export":
4205 4205 (export,
4206 4206 [('o', 'output', '',
4207 4207 _('print output to file with formatted name'), _('FORMAT')),
4208 4208 ('', 'switch-parent', None, _('diff against the second parent')),
4209 4209 ('r', 'rev', [],
4210 4210 _('revisions to export'), _('REV')),
4211 4211 ] + diffopts,
4212 4212 _('[OPTION]... [-o OUTFILESPEC] REV...')),
4213 4213 "^forget":
4214 4214 (forget,
4215 4215 [] + walkopts,
4216 4216 _('[OPTION]... FILE...')),
4217 4217 "grep":
4218 4218 (grep,
4219 4219 [('0', 'print0', None, _('end fields with NUL')),
4220 4220 ('', 'all', None, _('print all revisions that match')),
4221 4221 ('f', 'follow', None,
4222 4222 _('follow changeset history,'
4223 4223 ' or file history across copies and renames')),
4224 4224 ('i', 'ignore-case', None, _('ignore case when matching')),
4225 4225 ('l', 'files-with-matches', None,
4226 4226 _('print only filenames and revisions that match')),
4227 4227 ('n', 'line-number', None, _('print matching line numbers')),
4228 4228 ('r', 'rev', [],
4229 4229 _('only search files changed within revision range'), _('REV')),
4230 4230 ('u', 'user', None, _('list the author (long with -v)')),
4231 4231 ('d', 'date', None, _('list the date (short with -q)')),
4232 4232 ] + walkopts,
4233 4233 _('[OPTION]... PATTERN [FILE]...')),
4234 4234 "heads":
4235 4235 (heads,
4236 4236 [('r', 'rev', '',
4237 4237 _('show only heads which are descendants of REV'), _('REV')),
4238 4238 ('t', 'topo', False, _('show topological heads only')),
4239 4239 ('a', 'active', False,
4240 4240 _('show active branchheads only (DEPRECATED)')),
4241 4241 ('c', 'closed', False,
4242 4242 _('show normal and closed branch heads')),
4243 4243 ] + templateopts,
4244 4244 _('[-ac] [-r REV] [REV]...')),
4245 4245 "help": (help_, [], _('[TOPIC]')),
4246 4246 "identify|id":
4247 4247 (identify,
4248 4248 [('r', 'rev', '',
4249 4249 _('identify the specified revision'), _('REV')),
4250 4250 ('n', 'num', None, _('show local revision number')),
4251 4251 ('i', 'id', None, _('show global revision id')),
4252 4252 ('b', 'branch', None, _('show branch')),
4253 4253 ('t', 'tags', None, _('show tags'))],
4254 4254 _('[-nibt] [-r REV] [SOURCE]')),
4255 4255 "import|patch":
4256 4256 (import_,
4257 4257 [('p', 'strip', 1,
4258 4258 _('directory strip option for patch. This has the same '
4259 4259 'meaning as the corresponding patch option'),
4260 4260 _('NUM')),
4261 4261 ('b', 'base', '',
4262 4262 _('base path'), _('PATH')),
4263 4263 ('f', 'force', None,
4264 4264 _('skip check for outstanding uncommitted changes')),
4265 4265 ('', 'no-commit', None,
4266 4266 _("don't commit, just update the working directory")),
4267 4267 ('', 'exact', None,
4268 4268 _('apply patch to the nodes from which it was generated')),
4269 4269 ('', 'import-branch', None,
4270 4270 _('use any branch information in patch (implied by --exact)'))] +
4271 4271 commitopts + commitopts2 + similarityopts,
4272 4272 _('[OPTION]... PATCH...')),
4273 4273 "incoming|in":
4274 4274 (incoming,
4275 4275 [('f', 'force', None,
4276 4276 _('run even if remote repository is unrelated')),
4277 4277 ('n', 'newest-first', None, _('show newest record first')),
4278 4278 ('', 'bundle', '',
4279 4279 _('file to store the bundles into'), _('FILE')),
4280 4280 ('r', 'rev', [],
4281 4281 _('a remote changeset intended to be added'), _('REV')),
4282 4282 ('b', 'branch', [],
4283 4283 _('a specific branch you would like to pull'), _('BRANCH')),
4284 4284 ] + logopts + remoteopts,
4285 4285 _('[-p] [-n] [-M] [-f] [-r REV]...'
4286 4286 ' [--bundle FILENAME] [SOURCE]')),
4287 4287 "^init":
4288 4288 (init,
4289 4289 remoteopts,
4290 4290 _('[-e CMD] [--remotecmd CMD] [DEST]')),
4291 4291 "locate":
4292 4292 (locate,
4293 4293 [('r', 'rev', '',
4294 4294 _('search the repository as it is in REV'), _('REV')),
4295 4295 ('0', 'print0', None,
4296 4296 _('end filenames with NUL, for use with xargs')),
4297 4297 ('f', 'fullpath', None,
4298 4298 _('print complete paths from the filesystem root')),
4299 4299 ] + walkopts,
4300 4300 _('[OPTION]... [PATTERN]...')),
4301 4301 "^log|history":
4302 4302 (log,
4303 4303 [('f', 'follow', None,
4304 4304 _('follow changeset history,'
4305 4305 ' or file history across copies and renames')),
4306 4306 ('', 'follow-first', None,
4307 4307 _('only follow the first parent of merge changesets')),
4308 4308 ('d', 'date', '',
4309 4309 _('show revisions matching date spec'), _('DATE')),
4310 4310 ('C', 'copies', None, _('show copied files')),
4311 4311 ('k', 'keyword', [],
4312 4312 _('do case-insensitive search for a given text'), _('TEXT')),
4313 4313 ('r', 'rev', [],
4314 4314 _('show the specified revision or range'), _('REV')),
4315 4315 ('', 'removed', None, _('include revisions where files were removed')),
4316 4316 ('m', 'only-merges', None, _('show only merges')),
4317 4317 ('u', 'user', [],
4318 4318 _('revisions committed by user'), _('USER')),
4319 4319 ('', 'only-branch', [],
4320 4320 _('show only changesets within the given named branch (DEPRECATED)'),
4321 4321 _('BRANCH')),
4322 4322 ('b', 'branch', [],
4323 4323 _('show changesets within the given named branch'), _('BRANCH')),
4324 4324 ('P', 'prune', [],
4325 4325 _('do not display revision or any of its ancestors'), _('REV')),
4326 4326 ] + logopts + walkopts,
4327 4327 _('[OPTION]... [FILE]')),
4328 4328 "manifest":
4329 4329 (manifest,
4330 4330 [('r', 'rev', '',
4331 4331 _('revision to display'), _('REV'))],
4332 4332 _('[-r REV]')),
4333 4333 "^merge":
4334 4334 (merge,
4335 4335 [('f', 'force', None, _('force a merge with outstanding changes')),
4336 4336 ('r', 'rev', '',
4337 4337 _('revision to merge'), _('REV')),
4338 4338 ('P', 'preview', None,
4339 4339 _('review revisions to merge (no merge is performed)'))],
4340 4340 _('[-P] [-f] [[-r] REV]')),
4341 4341 "outgoing|out":
4342 4342 (outgoing,
4343 4343 [('f', 'force', None,
4344 4344 _('run even when the destination is unrelated')),
4345 4345 ('r', 'rev', [],
4346 4346 _('a changeset intended to be included in the destination'),
4347 4347 _('REV')),
4348 4348 ('n', 'newest-first', None, _('show newest record first')),
4349 4349 ('b', 'branch', [],
4350 4350 _('a specific branch you would like to push'), _('BRANCH')),
4351 4351 ] + logopts + remoteopts,
4352 4352 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
4353 4353 "parents":
4354 4354 (parents,
4355 4355 [('r', 'rev', '',
4356 4356 _('show parents of the specified revision'), _('REV')),
4357 4357 ] + templateopts,
4358 4358 _('[-r REV] [FILE]')),
4359 4359 "paths": (paths, [], _('[NAME]')),
4360 4360 "^pull":
4361 4361 (pull,
4362 4362 [('u', 'update', None,
4363 4363 _('update to new branch head if changesets were pulled')),
4364 4364 ('f', 'force', None,
4365 4365 _('run even when remote repository is unrelated')),
4366 4366 ('r', 'rev', [],
4367 4367 _('a remote changeset intended to be added'), _('REV')),
4368 4368 ('b', 'branch', [],
4369 4369 _('a specific branch you would like to pull'), _('BRANCH')),
4370 4370 ] + remoteopts,
4371 4371 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
4372 4372 "^push":
4373 4373 (push,
4374 4374 [('f', 'force', None, _('force push')),
4375 4375 ('r', 'rev', [],
4376 4376 _('a changeset intended to be included in the destination'),
4377 4377 _('REV')),
4378 4378 ('b', 'branch', [],
4379 4379 _('a specific branch you would like to push'), _('BRANCH')),
4380 4380 ('', 'new-branch', False, _('allow pushing a new branch')),
4381 4381 ] + remoteopts,
4382 4382 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
4383 4383 "recover": (recover, []),
4384 4384 "^remove|rm":
4385 4385 (remove,
4386 4386 [('A', 'after', None, _('record delete for missing files')),
4387 4387 ('f', 'force', None,
4388 4388 _('remove (and delete) file even if added or modified')),
4389 4389 ] + walkopts,
4390 4390 _('[OPTION]... FILE...')),
4391 4391 "rename|mv":
4392 4392 (rename,
4393 4393 [('A', 'after', None, _('record a rename that has already occurred')),
4394 4394 ('f', 'force', None,
4395 4395 _('forcibly copy over an existing managed file')),
4396 4396 ] + walkopts + dryrunopts,
4397 4397 _('[OPTION]... SOURCE... DEST')),
4398 4398 "resolve":
4399 4399 (resolve,
4400 4400 [('a', 'all', None, _('select all unresolved files')),
4401 4401 ('l', 'list', None, _('list state of files needing merge')),
4402 4402 ('m', 'mark', None, _('mark files as resolved')),
4403 4403 ('u', 'unmark', None, _('mark files as unresolved')),
4404 4404 ('n', 'no-status', None, _('hide status prefix'))]
4405 4405 + walkopts,
4406 4406 _('[OPTION]... [FILE]...')),
4407 4407 "revert":
4408 4408 (revert,
4409 4409 [('a', 'all', None, _('revert all changes when no arguments given')),
4410 4410 ('d', 'date', '',
4411 4411 _('tipmost revision matching date'), _('DATE')),
4412 4412 ('r', 'rev', '',
4413 4413 _('revert to the specified revision'), _('REV')),
4414 4414 ('', 'no-backup', None, _('do not save backup copies of files')),
4415 4415 ] + walkopts + dryrunopts,
4416 4416 _('[OPTION]... [-r REV] [NAME]...')),
4417 4417 "rollback": (rollback, dryrunopts),
4418 4418 "root": (root, []),
4419 4419 "^serve":
4420 4420 (serve,
4421 4421 [('A', 'accesslog', '',
4422 4422 _('name of access log file to write to'), _('FILE')),
4423 4423 ('d', 'daemon', None, _('run server in background')),
4424 4424 ('', 'daemon-pipefds', '',
4425 4425 _('used internally by daemon mode'), _('NUM')),
4426 4426 ('E', 'errorlog', '',
4427 4427 _('name of error log file to write to'), _('FILE')),
4428 4428 # use string type, then we can check if something was passed
4429 4429 ('p', 'port', '',
4430 4430 _('port to listen on (default: 8000)'), _('PORT')),
4431 4431 ('a', 'address', '',
4432 4432 _('address to listen on (default: all interfaces)'), _('ADDR')),
4433 4433 ('', 'prefix', '',
4434 4434 _('prefix path to serve from (default: server root)'), _('PREFIX')),
4435 4435 ('n', 'name', '',
4436 4436 _('name to show in web pages (default: working directory)'),
4437 4437 _('NAME')),
4438 4438 ('', 'web-conf', '',
4439 4439 _('name of the hgweb config file (serve more than one repository)'),
4440 4440 _('FILE')),
4441 4441 ('', 'webdir-conf', '',
4442 4442 _('name of the hgweb config file (DEPRECATED)'), _('FILE')),
4443 4443 ('', 'pid-file', '',
4444 4444 _('name of file to write process ID to'), _('FILE')),
4445 4445 ('', 'stdio', None, _('for remote clients')),
4446 4446 ('t', 'templates', '',
4447 4447 _('web templates to use'), _('TEMPLATE')),
4448 4448 ('', 'style', '',
4449 4449 _('template style to use'), _('STYLE')),
4450 4450 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4451 4451 ('', 'certificate', '',
4452 4452 _('SSL certificate file'), _('FILE'))],
4453 4453 _('[OPTION]...')),
4454 4454 "showconfig|debugconfig":
4455 4455 (showconfig,
4456 4456 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4457 4457 _('[-u] [NAME]...')),
4458 4458 "^summary|sum":
4459 4459 (summary,
4460 4460 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
4461 4461 "^status|st":
4462 4462 (status,
4463 4463 [('A', 'all', None, _('show status of all files')),
4464 4464 ('m', 'modified', None, _('show only modified files')),
4465 4465 ('a', 'added', None, _('show only added files')),
4466 4466 ('r', 'removed', None, _('show only removed files')),
4467 4467 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4468 4468 ('c', 'clean', None, _('show only files without changes')),
4469 4469 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4470 4470 ('i', 'ignored', None, _('show only ignored files')),
4471 4471 ('n', 'no-status', None, _('hide status prefix')),
4472 4472 ('C', 'copies', None, _('show source of copied files')),
4473 4473 ('0', 'print0', None,
4474 4474 _('end filenames with NUL, for use with xargs')),
4475 4475 ('', 'rev', [],
4476 4476 _('show difference from revision'), _('REV')),
4477 4477 ('', 'change', '',
4478 4478 _('list the changed files of a revision'), _('REV')),
4479 4479 ] + walkopts + subrepoopts,
4480 4480 _('[OPTION]... [FILE]...')),
4481 4481 "tag":
4482 4482 (tag,
4483 4483 [('f', 'force', None, _('replace existing tag')),
4484 4484 ('l', 'local', None, _('make the tag local')),
4485 4485 ('r', 'rev', '',
4486 4486 _('revision to tag'), _('REV')),
4487 4487 ('', 'remove', None, _('remove a tag')),
4488 4488 # -l/--local is already there, commitopts cannot be used
4489 4489 ('e', 'edit', None, _('edit commit message')),
4490 4490 ('m', 'message', '',
4491 4491 _('use <text> as commit message'), _('TEXT')),
4492 4492 ] + commitopts2,
4493 4493 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
4494 4494 "tags": (tags, [], ''),
4495 4495 "tip":
4496 4496 (tip,
4497 4497 [('p', 'patch', None, _('show patch')),
4498 4498 ('g', 'git', None, _('use git extended diff format')),
4499 4499 ] + templateopts,
4500 4500 _('[-p] [-g]')),
4501 4501 "unbundle":
4502 4502 (unbundle,
4503 4503 [('u', 'update', None,
4504 4504 _('update to new branch head if changesets were unbundled'))],
4505 4505 _('[-u] FILE...')),
4506 4506 "^update|up|checkout|co":
4507 4507 (update,
4508 4508 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
4509 4509 ('c', 'check', None, _('check for uncommitted changes')),
4510 4510 ('d', 'date', '',
4511 4511 _('tipmost revision matching date'), _('DATE')),
4512 4512 ('r', 'rev', '',
4513 4513 _('revision'), _('REV'))],
4514 4514 _('[-c] [-C] [-d DATE] [[-r] REV]')),
4515 4515 "verify": (verify, []),
4516 4516 "version": (version_, []),
4517 4517 }
4518 4518
4519 4519 norepo = ("clone init version help debugcommands debugcomplete"
4520 4520 " debugdate debuginstall debugfsinfo debugpushkey")
4521 4521 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
4522 4522 " debugdata debugindex debugindexdot")
@@ -1,1715 +1,1672 b''
1 1 # patch.py - patch file parsing routines
2 2 #
3 3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 import cStringIO, email.Parser, os, re
10 10 import tempfile, zlib
11 11
12 12 from i18n import _
13 13 from node import hex, nullid, short
14 import base85, cmdutil, mdiff, util, diffhelpers, copies, encoding
14 import base85, mdiff, util, diffhelpers, copies, encoding
15 15
16 16 gitre = re.compile('diff --git a/(.*) b/(.*)')
17 17
18 18 class PatchError(Exception):
19 19 pass
20 20
21 21 class NoHunks(PatchError):
22 22 pass
23 23
24 24 # helper functions
25 25
26 26 def copyfile(src, dst, basedir):
27 27 abssrc, absdst = [util.canonpath(basedir, basedir, x) for x in [src, dst]]
28 28 if os.path.exists(absdst):
29 29 raise util.Abort(_("cannot create %s: destination already exists") %
30 30 dst)
31 31
32 32 dstdir = os.path.dirname(absdst)
33 33 if dstdir and not os.path.isdir(dstdir):
34 34 try:
35 35 os.makedirs(dstdir)
36 36 except IOError:
37 37 raise util.Abort(
38 38 _("cannot create %s: unable to create destination directory")
39 39 % dst)
40 40
41 41 util.copyfile(abssrc, absdst)
42 42
43 43 # public functions
44 44
45 45 def split(stream):
46 46 '''return an iterator of individual patches from a stream'''
47 47 def isheader(line, inheader):
48 48 if inheader and line[0] in (' ', '\t'):
49 49 # continuation
50 50 return True
51 51 if line[0] in (' ', '-', '+'):
52 52 # diff line - don't check for header pattern in there
53 53 return False
54 54 l = line.split(': ', 1)
55 55 return len(l) == 2 and ' ' not in l[0]
56 56
57 57 def chunk(lines):
58 58 return cStringIO.StringIO(''.join(lines))
59 59
60 60 def hgsplit(stream, cur):
61 61 inheader = True
62 62
63 63 for line in stream:
64 64 if not line.strip():
65 65 inheader = False
66 66 if not inheader and line.startswith('# HG changeset patch'):
67 67 yield chunk(cur)
68 68 cur = []
69 69 inheader = True
70 70
71 71 cur.append(line)
72 72
73 73 if cur:
74 74 yield chunk(cur)
75 75
76 76 def mboxsplit(stream, cur):
77 77 for line in stream:
78 78 if line.startswith('From '):
79 79 for c in split(chunk(cur[1:])):
80 80 yield c
81 81 cur = []
82 82
83 83 cur.append(line)
84 84
85 85 if cur:
86 86 for c in split(chunk(cur[1:])):
87 87 yield c
88 88
89 89 def mimesplit(stream, cur):
90 90 def msgfp(m):
91 91 fp = cStringIO.StringIO()
92 92 g = email.Generator.Generator(fp, mangle_from_=False)
93 93 g.flatten(m)
94 94 fp.seek(0)
95 95 return fp
96 96
97 97 for line in stream:
98 98 cur.append(line)
99 99 c = chunk(cur)
100 100
101 101 m = email.Parser.Parser().parse(c)
102 102 if not m.is_multipart():
103 103 yield msgfp(m)
104 104 else:
105 105 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
106 106 for part in m.walk():
107 107 ct = part.get_content_type()
108 108 if ct not in ok_types:
109 109 continue
110 110 yield msgfp(part)
111 111
112 112 def headersplit(stream, cur):
113 113 inheader = False
114 114
115 115 for line in stream:
116 116 if not inheader and isheader(line, inheader):
117 117 yield chunk(cur)
118 118 cur = []
119 119 inheader = True
120 120 if inheader and not isheader(line, inheader):
121 121 inheader = False
122 122
123 123 cur.append(line)
124 124
125 125 if cur:
126 126 yield chunk(cur)
127 127
128 128 def remainder(cur):
129 129 yield chunk(cur)
130 130
131 131 class fiter(object):
132 132 def __init__(self, fp):
133 133 self.fp = fp
134 134
135 135 def __iter__(self):
136 136 return self
137 137
138 138 def next(self):
139 139 l = self.fp.readline()
140 140 if not l:
141 141 raise StopIteration
142 142 return l
143 143
144 144 inheader = False
145 145 cur = []
146 146
147 147 mimeheaders = ['content-type']
148 148
149 149 if not hasattr(stream, 'next'):
150 150 # http responses, for example, have readline but not next
151 151 stream = fiter(stream)
152 152
153 153 for line in stream:
154 154 cur.append(line)
155 155 if line.startswith('# HG changeset patch'):
156 156 return hgsplit(stream, cur)
157 157 elif line.startswith('From '):
158 158 return mboxsplit(stream, cur)
159 159 elif isheader(line, inheader):
160 160 inheader = True
161 161 if line.split(':', 1)[0].lower() in mimeheaders:
162 162 # let email parser handle this
163 163 return mimesplit(stream, cur)
164 164 elif line.startswith('--- ') and inheader:
165 165 # No evil headers seen by diff start, split by hand
166 166 return headersplit(stream, cur)
167 167 # Not enough info, keep reading
168 168
169 169 # if we are here, we have a very plain patch
170 170 return remainder(cur)
171 171
172 172 def extract(ui, fileobj):
173 173 '''extract patch from data read from fileobj.
174 174
175 175 patch can be a normal patch or contained in an email message.
176 176
177 177 return tuple (filename, message, user, date, branch, node, p1, p2).
178 178 Any item in the returned tuple can be None. If filename is None,
179 179 fileobj did not contain a patch. Caller must unlink filename when done.'''
180 180
181 181 # attempt to detect the start of a patch
182 182 # (this heuristic is borrowed from quilt)
183 183 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
184 184 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
185 185 r'---[ \t].*?^\+\+\+[ \t]|'
186 186 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
187 187
188 188 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
189 189 tmpfp = os.fdopen(fd, 'w')
190 190 try:
191 191 msg = email.Parser.Parser().parse(fileobj)
192 192
193 193 subject = msg['Subject']
194 194 user = msg['From']
195 195 if not subject and not user:
196 196 # Not an email, restore parsed headers if any
197 197 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
198 198
199 199 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
200 200 # should try to parse msg['Date']
201 201 date = None
202 202 nodeid = None
203 203 branch = None
204 204 parents = []
205 205
206 206 if subject:
207 207 if subject.startswith('[PATCH'):
208 208 pend = subject.find(']')
209 209 if pend >= 0:
210 210 subject = subject[pend + 1:].lstrip()
211 211 subject = subject.replace('\n\t', ' ')
212 212 ui.debug('Subject: %s\n' % subject)
213 213 if user:
214 214 ui.debug('From: %s\n' % user)
215 215 diffs_seen = 0
216 216 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
217 217 message = ''
218 218 for part in msg.walk():
219 219 content_type = part.get_content_type()
220 220 ui.debug('Content-Type: %s\n' % content_type)
221 221 if content_type not in ok_types:
222 222 continue
223 223 payload = part.get_payload(decode=True)
224 224 m = diffre.search(payload)
225 225 if m:
226 226 hgpatch = False
227 227 ignoretext = False
228 228
229 229 ui.debug('found patch at byte %d\n' % m.start(0))
230 230 diffs_seen += 1
231 231 cfp = cStringIO.StringIO()
232 232 for line in payload[:m.start(0)].splitlines():
233 233 if line.startswith('# HG changeset patch'):
234 234 ui.debug('patch generated by hg export\n')
235 235 hgpatch = True
236 236 # drop earlier commit message content
237 237 cfp.seek(0)
238 238 cfp.truncate()
239 239 subject = None
240 240 elif hgpatch:
241 241 if line.startswith('# User '):
242 242 user = line[7:]
243 243 ui.debug('From: %s\n' % user)
244 244 elif line.startswith("# Date "):
245 245 date = line[7:]
246 246 elif line.startswith("# Branch "):
247 247 branch = line[9:]
248 248 elif line.startswith("# Node ID "):
249 249 nodeid = line[10:]
250 250 elif line.startswith("# Parent "):
251 251 parents.append(line[10:])
252 252 elif line == '---' and gitsendmail:
253 253 ignoretext = True
254 254 if not line.startswith('# ') and not ignoretext:
255 255 cfp.write(line)
256 256 cfp.write('\n')
257 257 message = cfp.getvalue()
258 258 if tmpfp:
259 259 tmpfp.write(payload)
260 260 if not payload.endswith('\n'):
261 261 tmpfp.write('\n')
262 262 elif not diffs_seen and message and content_type == 'text/plain':
263 263 message += '\n' + payload
264 264 except:
265 265 tmpfp.close()
266 266 os.unlink(tmpname)
267 267 raise
268 268
269 269 if subject and not message.startswith(subject):
270 270 message = '%s\n%s' % (subject, message)
271 271 tmpfp.close()
272 272 if not diffs_seen:
273 273 os.unlink(tmpname)
274 274 return None, message, user, date, branch, None, None, None
275 275 p1 = parents and parents.pop(0) or None
276 276 p2 = parents and parents.pop(0) or None
277 277 return tmpname, message, user, date, branch, nodeid, p1, p2
278 278
279 279 GP_PATCH = 1 << 0 # we have to run patch
280 280 GP_FILTER = 1 << 1 # there's some copy/rename operation
281 281 GP_BINARY = 1 << 2 # there's a binary patch
282 282
283 283 class patchmeta(object):
284 284 """Patched file metadata
285 285
286 286 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
287 287 or COPY. 'path' is patched file path. 'oldpath' is set to the
288 288 origin file when 'op' is either COPY or RENAME, None otherwise. If
289 289 file mode is changed, 'mode' is a tuple (islink, isexec) where
290 290 'islink' is True if the file is a symlink and 'isexec' is True if
291 291 the file is executable. Otherwise, 'mode' is None.
292 292 """
293 293 def __init__(self, path):
294 294 self.path = path
295 295 self.oldpath = None
296 296 self.mode = None
297 297 self.op = 'MODIFY'
298 298 self.lineno = 0
299 299 self.binary = False
300 300
301 301 def setmode(self, mode):
302 302 islink = mode & 020000
303 303 isexec = mode & 0100
304 304 self.mode = (islink, isexec)
305 305
306 306 def __repr__(self):
307 307 return "<patchmeta %s %r>" % (self.op, self.path)
308 308
309 309 def readgitpatch(lr):
310 310 """extract git-style metadata about patches from <patchname>"""
311 311
312 312 # Filter patch for git information
313 313 gp = None
314 314 gitpatches = []
315 315 # Can have a git patch with only metadata, causing patch to complain
316 316 dopatch = 0
317 317
318 318 lineno = 0
319 319 for line in lr:
320 320 lineno += 1
321 321 line = line.rstrip(' \r\n')
322 322 if line.startswith('diff --git'):
323 323 m = gitre.match(line)
324 324 if m:
325 325 if gp:
326 326 gitpatches.append(gp)
327 327 dst = m.group(2)
328 328 gp = patchmeta(dst)
329 329 gp.lineno = lineno
330 330 elif gp:
331 331 if line.startswith('--- '):
332 332 if gp.op in ('COPY', 'RENAME'):
333 333 dopatch |= GP_FILTER
334 334 gitpatches.append(gp)
335 335 gp = None
336 336 dopatch |= GP_PATCH
337 337 continue
338 338 if line.startswith('rename from '):
339 339 gp.op = 'RENAME'
340 340 gp.oldpath = line[12:]
341 341 elif line.startswith('rename to '):
342 342 gp.path = line[10:]
343 343 elif line.startswith('copy from '):
344 344 gp.op = 'COPY'
345 345 gp.oldpath = line[10:]
346 346 elif line.startswith('copy to '):
347 347 gp.path = line[8:]
348 348 elif line.startswith('deleted file'):
349 349 gp.op = 'DELETE'
350 350 elif line.startswith('new file mode '):
351 351 gp.op = 'ADD'
352 352 gp.setmode(int(line[-6:], 8))
353 353 elif line.startswith('new mode '):
354 354 gp.setmode(int(line[-6:], 8))
355 355 elif line.startswith('GIT binary patch'):
356 356 dopatch |= GP_BINARY
357 357 gp.binary = True
358 358 if gp:
359 359 gitpatches.append(gp)
360 360
361 361 if not gitpatches:
362 362 dopatch = GP_PATCH
363 363
364 364 return (dopatch, gitpatches)
365 365
366 366 class linereader(object):
367 367 # simple class to allow pushing lines back into the input stream
368 368 def __init__(self, fp, textmode=False):
369 369 self.fp = fp
370 370 self.buf = []
371 371 self.textmode = textmode
372 372 self.eol = None
373 373
374 374 def push(self, line):
375 375 if line is not None:
376 376 self.buf.append(line)
377 377
378 378 def readline(self):
379 379 if self.buf:
380 380 l = self.buf[0]
381 381 del self.buf[0]
382 382 return l
383 383 l = self.fp.readline()
384 384 if not self.eol:
385 385 if l.endswith('\r\n'):
386 386 self.eol = '\r\n'
387 387 elif l.endswith('\n'):
388 388 self.eol = '\n'
389 389 if self.textmode and l.endswith('\r\n'):
390 390 l = l[:-2] + '\n'
391 391 return l
392 392
393 393 def __iter__(self):
394 394 while 1:
395 395 l = self.readline()
396 396 if not l:
397 397 break
398 398 yield l
399 399
400 400 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
401 401 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
402 402 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
403 403 eolmodes = ['strict', 'crlf', 'lf', 'auto']
404 404
405 405 class patchfile(object):
406 406 def __init__(self, ui, fname, opener, missing=False, eolmode='strict'):
407 407 self.fname = fname
408 408 self.eolmode = eolmode
409 409 self.eol = None
410 410 self.opener = opener
411 411 self.ui = ui
412 412 self.lines = []
413 413 self.exists = False
414 414 self.missing = missing
415 415 if not missing:
416 416 try:
417 417 self.lines = self.readlines(fname)
418 418 self.exists = True
419 419 except IOError:
420 420 pass
421 421 else:
422 422 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
423 423
424 424 self.hash = {}
425 425 self.dirty = 0
426 426 self.offset = 0
427 427 self.skew = 0
428 428 self.rej = []
429 429 self.fileprinted = False
430 430 self.printfile(False)
431 431 self.hunks = 0
432 432
433 433 def readlines(self, fname):
434 434 if os.path.islink(fname):
435 435 return [os.readlink(fname)]
436 436 fp = self.opener(fname, 'r')
437 437 try:
438 438 lr = linereader(fp, self.eolmode != 'strict')
439 439 lines = list(lr)
440 440 self.eol = lr.eol
441 441 return lines
442 442 finally:
443 443 fp.close()
444 444
445 445 def writelines(self, fname, lines):
446 446 # Ensure supplied data ends in fname, being a regular file or
447 # a symlink. updatedir() will -too magically- take care of
448 # setting it to the proper type afterwards.
447 # a symlink. cmdutil.updatedir will -too magically- take care
448 # of setting it to the proper type afterwards.
449 449 islink = os.path.islink(fname)
450 450 if islink:
451 451 fp = cStringIO.StringIO()
452 452 else:
453 453 fp = self.opener(fname, 'w')
454 454 try:
455 455 if self.eolmode == 'auto':
456 456 eol = self.eol
457 457 elif self.eolmode == 'crlf':
458 458 eol = '\r\n'
459 459 else:
460 460 eol = '\n'
461 461
462 462 if self.eolmode != 'strict' and eol and eol != '\n':
463 463 for l in lines:
464 464 if l and l[-1] == '\n':
465 465 l = l[:-1] + eol
466 466 fp.write(l)
467 467 else:
468 468 fp.writelines(lines)
469 469 if islink:
470 470 self.opener.symlink(fp.getvalue(), fname)
471 471 finally:
472 472 fp.close()
473 473
474 474 def unlink(self, fname):
475 475 os.unlink(fname)
476 476
477 477 def printfile(self, warn):
478 478 if self.fileprinted:
479 479 return
480 480 if warn or self.ui.verbose:
481 481 self.fileprinted = True
482 482 s = _("patching file %s\n") % self.fname
483 483 if warn:
484 484 self.ui.warn(s)
485 485 else:
486 486 self.ui.note(s)
487 487
488 488
489 489 def findlines(self, l, linenum):
490 490 # looks through the hash and finds candidate lines. The
491 491 # result is a list of line numbers sorted based on distance
492 492 # from linenum
493 493
494 494 cand = self.hash.get(l, [])
495 495 if len(cand) > 1:
496 496 # resort our list of potentials forward then back.
497 497 cand.sort(key=lambda x: abs(x - linenum))
498 498 return cand
499 499
500 500 def hashlines(self):
501 501 self.hash = {}
502 502 for x, s in enumerate(self.lines):
503 503 self.hash.setdefault(s, []).append(x)
504 504
505 505 def write_rej(self):
506 506 # our rejects are a little different from patch(1). This always
507 507 # creates rejects in the same form as the original patch. A file
508 508 # header is inserted so that you can run the reject through patch again
509 509 # without having to type the filename.
510 510
511 511 if not self.rej:
512 512 return
513 513
514 514 fname = self.fname + ".rej"
515 515 self.ui.warn(
516 516 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
517 517 (len(self.rej), self.hunks, fname))
518 518
519 519 def rejlines():
520 520 base = os.path.basename(self.fname)
521 521 yield "--- %s\n+++ %s\n" % (base, base)
522 522 for x in self.rej:
523 523 for l in x.hunk:
524 524 yield l
525 525 if l[-1] != '\n':
526 526 yield "\n\ No newline at end of file\n"
527 527
528 528 self.writelines(fname, rejlines())
529 529
530 530 def apply(self, h):
531 531 if not h.complete():
532 532 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
533 533 (h.number, h.desc, len(h.a), h.lena, len(h.b),
534 534 h.lenb))
535 535
536 536 self.hunks += 1
537 537
538 538 if self.missing:
539 539 self.rej.append(h)
540 540 return -1
541 541
542 542 if self.exists and h.createfile():
543 543 self.ui.warn(_("file %s already exists\n") % self.fname)
544 544 self.rej.append(h)
545 545 return -1
546 546
547 547 if isinstance(h, binhunk):
548 548 if h.rmfile():
549 549 self.unlink(self.fname)
550 550 else:
551 551 self.lines[:] = h.new()
552 552 self.offset += len(h.new())
553 553 self.dirty = 1
554 554 return 0
555 555
556 556 horig = h
557 557 if (self.eolmode in ('crlf', 'lf')
558 558 or self.eolmode == 'auto' and self.eol):
559 559 # If new eols are going to be normalized, then normalize
560 560 # hunk data before patching. Otherwise, preserve input
561 561 # line-endings.
562 562 h = h.getnormalized()
563 563
564 564 # fast case first, no offsets, no fuzz
565 565 old = h.old()
566 566 # patch starts counting at 1 unless we are adding the file
567 567 if h.starta == 0:
568 568 start = 0
569 569 else:
570 570 start = h.starta + self.offset - 1
571 571 orig_start = start
572 572 # if there's skew we want to emit the "(offset %d lines)" even
573 573 # when the hunk cleanly applies at start + skew, so skip the
574 574 # fast case code
575 575 if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0:
576 576 if h.rmfile():
577 577 self.unlink(self.fname)
578 578 else:
579 579 self.lines[start : start + h.lena] = h.new()
580 580 self.offset += h.lenb - h.lena
581 581 self.dirty = 1
582 582 return 0
583 583
584 584 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
585 585 self.hashlines()
586 586 if h.hunk[-1][0] != ' ':
587 587 # if the hunk tried to put something at the bottom of the file
588 588 # override the start line and use eof here
589 589 search_start = len(self.lines)
590 590 else:
591 591 search_start = orig_start + self.skew
592 592
593 593 for fuzzlen in xrange(3):
594 594 for toponly in [True, False]:
595 595 old = h.old(fuzzlen, toponly)
596 596
597 597 cand = self.findlines(old[0][1:], search_start)
598 598 for l in cand:
599 599 if diffhelpers.testhunk(old, self.lines, l) == 0:
600 600 newlines = h.new(fuzzlen, toponly)
601 601 self.lines[l : l + len(old)] = newlines
602 602 self.offset += len(newlines) - len(old)
603 603 self.skew = l - orig_start
604 604 self.dirty = 1
605 605 offset = l - orig_start - fuzzlen
606 606 if fuzzlen:
607 607 msg = _("Hunk #%d succeeded at %d "
608 608 "with fuzz %d "
609 609 "(offset %d lines).\n")
610 610 self.printfile(True)
611 611 self.ui.warn(msg %
612 612 (h.number, l + 1, fuzzlen, offset))
613 613 else:
614 614 msg = _("Hunk #%d succeeded at %d "
615 615 "(offset %d lines).\n")
616 616 self.ui.note(msg % (h.number, l + 1, offset))
617 617 return fuzzlen
618 618 self.printfile(True)
619 619 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
620 620 self.rej.append(horig)
621 621 return -1
622 622
623 623 class hunk(object):
624 624 def __init__(self, desc, num, lr, context, create=False, remove=False):
625 625 self.number = num
626 626 self.desc = desc
627 627 self.hunk = [desc]
628 628 self.a = []
629 629 self.b = []
630 630 self.starta = self.lena = None
631 631 self.startb = self.lenb = None
632 632 if lr is not None:
633 633 if context:
634 634 self.read_context_hunk(lr)
635 635 else:
636 636 self.read_unified_hunk(lr)
637 637 self.create = create
638 638 self.remove = remove and not create
639 639
640 640 def getnormalized(self):
641 641 """Return a copy with line endings normalized to LF."""
642 642
643 643 def normalize(lines):
644 644 nlines = []
645 645 for line in lines:
646 646 if line.endswith('\r\n'):
647 647 line = line[:-2] + '\n'
648 648 nlines.append(line)
649 649 return nlines
650 650
651 651 # Dummy object, it is rebuilt manually
652 652 nh = hunk(self.desc, self.number, None, None, False, False)
653 653 nh.number = self.number
654 654 nh.desc = self.desc
655 655 nh.hunk = self.hunk
656 656 nh.a = normalize(self.a)
657 657 nh.b = normalize(self.b)
658 658 nh.starta = self.starta
659 659 nh.startb = self.startb
660 660 nh.lena = self.lena
661 661 nh.lenb = self.lenb
662 662 nh.create = self.create
663 663 nh.remove = self.remove
664 664 return nh
665 665
666 666 def read_unified_hunk(self, lr):
667 667 m = unidesc.match(self.desc)
668 668 if not m:
669 669 raise PatchError(_("bad hunk #%d") % self.number)
670 670 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
671 671 if self.lena is None:
672 672 self.lena = 1
673 673 else:
674 674 self.lena = int(self.lena)
675 675 if self.lenb is None:
676 676 self.lenb = 1
677 677 else:
678 678 self.lenb = int(self.lenb)
679 679 self.starta = int(self.starta)
680 680 self.startb = int(self.startb)
681 681 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
682 682 # if we hit eof before finishing out the hunk, the last line will
683 683 # be zero length. Lets try to fix it up.
684 684 while len(self.hunk[-1]) == 0:
685 685 del self.hunk[-1]
686 686 del self.a[-1]
687 687 del self.b[-1]
688 688 self.lena -= 1
689 689 self.lenb -= 1
690 690
691 691 def read_context_hunk(self, lr):
692 692 self.desc = lr.readline()
693 693 m = contextdesc.match(self.desc)
694 694 if not m:
695 695 raise PatchError(_("bad hunk #%d") % self.number)
696 696 foo, self.starta, foo2, aend, foo3 = m.groups()
697 697 self.starta = int(self.starta)
698 698 if aend is None:
699 699 aend = self.starta
700 700 self.lena = int(aend) - self.starta
701 701 if self.starta:
702 702 self.lena += 1
703 703 for x in xrange(self.lena):
704 704 l = lr.readline()
705 705 if l.startswith('---'):
706 706 lr.push(l)
707 707 break
708 708 s = l[2:]
709 709 if l.startswith('- ') or l.startswith('! '):
710 710 u = '-' + s
711 711 elif l.startswith(' '):
712 712 u = ' ' + s
713 713 else:
714 714 raise PatchError(_("bad hunk #%d old text line %d") %
715 715 (self.number, x))
716 716 self.a.append(u)
717 717 self.hunk.append(u)
718 718
719 719 l = lr.readline()
720 720 if l.startswith('\ '):
721 721 s = self.a[-1][:-1]
722 722 self.a[-1] = s
723 723 self.hunk[-1] = s
724 724 l = lr.readline()
725 725 m = contextdesc.match(l)
726 726 if not m:
727 727 raise PatchError(_("bad hunk #%d") % self.number)
728 728 foo, self.startb, foo2, bend, foo3 = m.groups()
729 729 self.startb = int(self.startb)
730 730 if bend is None:
731 731 bend = self.startb
732 732 self.lenb = int(bend) - self.startb
733 733 if self.startb:
734 734 self.lenb += 1
735 735 hunki = 1
736 736 for x in xrange(self.lenb):
737 737 l = lr.readline()
738 738 if l.startswith('\ '):
739 739 s = self.b[-1][:-1]
740 740 self.b[-1] = s
741 741 self.hunk[hunki - 1] = s
742 742 continue
743 743 if not l:
744 744 lr.push(l)
745 745 break
746 746 s = l[2:]
747 747 if l.startswith('+ ') or l.startswith('! '):
748 748 u = '+' + s
749 749 elif l.startswith(' '):
750 750 u = ' ' + s
751 751 elif len(self.b) == 0:
752 752 # this can happen when the hunk does not add any lines
753 753 lr.push(l)
754 754 break
755 755 else:
756 756 raise PatchError(_("bad hunk #%d old text line %d") %
757 757 (self.number, x))
758 758 self.b.append(s)
759 759 while True:
760 760 if hunki >= len(self.hunk):
761 761 h = ""
762 762 else:
763 763 h = self.hunk[hunki]
764 764 hunki += 1
765 765 if h == u:
766 766 break
767 767 elif h.startswith('-'):
768 768 continue
769 769 else:
770 770 self.hunk.insert(hunki - 1, u)
771 771 break
772 772
773 773 if not self.a:
774 774 # this happens when lines were only added to the hunk
775 775 for x in self.hunk:
776 776 if x.startswith('-') or x.startswith(' '):
777 777 self.a.append(x)
778 778 if not self.b:
779 779 # this happens when lines were only deleted from the hunk
780 780 for x in self.hunk:
781 781 if x.startswith('+') or x.startswith(' '):
782 782 self.b.append(x[1:])
783 783 # @@ -start,len +start,len @@
784 784 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
785 785 self.startb, self.lenb)
786 786 self.hunk[0] = self.desc
787 787
788 788 def fix_newline(self):
789 789 diffhelpers.fix_newline(self.hunk, self.a, self.b)
790 790
791 791 def complete(self):
792 792 return len(self.a) == self.lena and len(self.b) == self.lenb
793 793
794 794 def createfile(self):
795 795 return self.starta == 0 and self.lena == 0 and self.create
796 796
797 797 def rmfile(self):
798 798 return self.startb == 0 and self.lenb == 0 and self.remove
799 799
800 800 def fuzzit(self, l, fuzz, toponly):
801 801 # this removes context lines from the top and bottom of list 'l'. It
802 802 # checks the hunk to make sure only context lines are removed, and then
803 803 # returns a new shortened list of lines.
804 804 fuzz = min(fuzz, len(l)-1)
805 805 if fuzz:
806 806 top = 0
807 807 bot = 0
808 808 hlen = len(self.hunk)
809 809 for x in xrange(hlen - 1):
810 810 # the hunk starts with the @@ line, so use x+1
811 811 if self.hunk[x + 1][0] == ' ':
812 812 top += 1
813 813 else:
814 814 break
815 815 if not toponly:
816 816 for x in xrange(hlen - 1):
817 817 if self.hunk[hlen - bot - 1][0] == ' ':
818 818 bot += 1
819 819 else:
820 820 break
821 821
822 822 # top and bot now count context in the hunk
823 823 # adjust them if either one is short
824 824 context = max(top, bot, 3)
825 825 if bot < context:
826 826 bot = max(0, fuzz - (context - bot))
827 827 else:
828 828 bot = min(fuzz, bot)
829 829 if top < context:
830 830 top = max(0, fuzz - (context - top))
831 831 else:
832 832 top = min(fuzz, top)
833 833
834 834 return l[top:len(l)-bot]
835 835 return l
836 836
837 837 def old(self, fuzz=0, toponly=False):
838 838 return self.fuzzit(self.a, fuzz, toponly)
839 839
840 840 def new(self, fuzz=0, toponly=False):
841 841 return self.fuzzit(self.b, fuzz, toponly)
842 842
843 843 class binhunk:
844 844 'A binary patch file. Only understands literals so far.'
845 845 def __init__(self, gitpatch):
846 846 self.gitpatch = gitpatch
847 847 self.text = None
848 848 self.hunk = ['GIT binary patch\n']
849 849
850 850 def createfile(self):
851 851 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
852 852
853 853 def rmfile(self):
854 854 return self.gitpatch.op == 'DELETE'
855 855
856 856 def complete(self):
857 857 return self.text is not None
858 858
859 859 def new(self):
860 860 return [self.text]
861 861
862 862 def extract(self, lr):
863 863 line = lr.readline()
864 864 self.hunk.append(line)
865 865 while line and not line.startswith('literal '):
866 866 line = lr.readline()
867 867 self.hunk.append(line)
868 868 if not line:
869 869 raise PatchError(_('could not extract binary patch'))
870 870 size = int(line[8:].rstrip())
871 871 dec = []
872 872 line = lr.readline()
873 873 self.hunk.append(line)
874 874 while len(line) > 1:
875 875 l = line[0]
876 876 if l <= 'Z' and l >= 'A':
877 877 l = ord(l) - ord('A') + 1
878 878 else:
879 879 l = ord(l) - ord('a') + 27
880 880 dec.append(base85.b85decode(line[1:-1])[:l])
881 881 line = lr.readline()
882 882 self.hunk.append(line)
883 883 text = zlib.decompress(''.join(dec))
884 884 if len(text) != size:
885 885 raise PatchError(_('binary patch is %d bytes, not %d') %
886 886 len(text), size)
887 887 self.text = text
888 888
889 889 def parsefilename(str):
890 890 # --- filename \t|space stuff
891 891 s = str[4:].rstrip('\r\n')
892 892 i = s.find('\t')
893 893 if i < 0:
894 894 i = s.find(' ')
895 895 if i < 0:
896 896 return s
897 897 return s[:i]
898 898
899 899 def pathstrip(path, strip):
900 900 pathlen = len(path)
901 901 i = 0
902 902 if strip == 0:
903 903 return '', path.rstrip()
904 904 count = strip
905 905 while count > 0:
906 906 i = path.find('/', i)
907 907 if i == -1:
908 908 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
909 909 (count, strip, path))
910 910 i += 1
911 911 # consume '//' in the path
912 912 while i < pathlen - 1 and path[i] == '/':
913 913 i += 1
914 914 count -= 1
915 915 return path[:i].lstrip(), path[i:].rstrip()
916 916
917 917 def selectfile(afile_orig, bfile_orig, hunk, strip):
918 918 nulla = afile_orig == "/dev/null"
919 919 nullb = bfile_orig == "/dev/null"
920 920 abase, afile = pathstrip(afile_orig, strip)
921 921 gooda = not nulla and os.path.lexists(afile)
922 922 bbase, bfile = pathstrip(bfile_orig, strip)
923 923 if afile == bfile:
924 924 goodb = gooda
925 925 else:
926 926 goodb = not nullb and os.path.exists(bfile)
927 927 createfunc = hunk.createfile
928 928 missing = not goodb and not gooda and not createfunc()
929 929
930 930 # some diff programs apparently produce patches where the afile is
931 931 # not /dev/null, but afile starts with bfile
932 932 abasedir = afile[:afile.rfind('/') + 1]
933 933 bbasedir = bfile[:bfile.rfind('/') + 1]
934 934 if missing and abasedir == bbasedir and afile.startswith(bfile):
935 935 # this isn't very pretty
936 936 hunk.create = True
937 937 if createfunc():
938 938 missing = False
939 939 else:
940 940 hunk.create = False
941 941
942 942 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
943 943 # diff is between a file and its backup. In this case, the original
944 944 # file should be patched (see original mpatch code).
945 945 isbackup = (abase == bbase and bfile.startswith(afile))
946 946 fname = None
947 947 if not missing:
948 948 if gooda and goodb:
949 949 fname = isbackup and afile or bfile
950 950 elif gooda:
951 951 fname = afile
952 952
953 953 if not fname:
954 954 if not nullb:
955 955 fname = isbackup and afile or bfile
956 956 elif not nulla:
957 957 fname = afile
958 958 else:
959 959 raise PatchError(_("undefined source and destination files"))
960 960
961 961 return fname, missing
962 962
963 963 def scangitpatch(lr, firstline):
964 964 """
965 965 Git patches can emit:
966 966 - rename a to b
967 967 - change b
968 968 - copy a to c
969 969 - change c
970 970
971 971 We cannot apply this sequence as-is, the renamed 'a' could not be
972 972 found for it would have been renamed already. And we cannot copy
973 973 from 'b' instead because 'b' would have been changed already. So
974 974 we scan the git patch for copy and rename commands so we can
975 975 perform the copies ahead of time.
976 976 """
977 977 pos = 0
978 978 try:
979 979 pos = lr.fp.tell()
980 980 fp = lr.fp
981 981 except IOError:
982 982 fp = cStringIO.StringIO(lr.fp.read())
983 983 gitlr = linereader(fp, lr.textmode)
984 984 gitlr.push(firstline)
985 985 (dopatch, gitpatches) = readgitpatch(gitlr)
986 986 fp.seek(pos)
987 987 return dopatch, gitpatches
988 988
989 989 def iterhunks(ui, fp, sourcefile=None):
990 990 """Read a patch and yield the following events:
991 991 - ("file", afile, bfile, firsthunk): select a new target file.
992 992 - ("hunk", hunk): a new hunk is ready to be applied, follows a
993 993 "file" event.
994 994 - ("git", gitchanges): current diff is in git format, gitchanges
995 995 maps filenames to gitpatch records. Unique event.
996 996 """
997 997 changed = {}
998 998 current_hunk = None
999 999 afile = ""
1000 1000 bfile = ""
1001 1001 state = None
1002 1002 hunknum = 0
1003 1003 emitfile = False
1004 1004 git = False
1005 1005
1006 1006 # our states
1007 1007 BFILE = 1
1008 1008 context = None
1009 1009 lr = linereader(fp)
1010 1010 # gitworkdone is True if a git operation (copy, rename, ...) was
1011 1011 # performed already for the current file. Useful when the file
1012 1012 # section may have no hunk.
1013 1013 gitworkdone = False
1014 1014 empty = None
1015 1015
1016 1016 while True:
1017 1017 newfile = newgitfile = False
1018 1018 x = lr.readline()
1019 1019 if not x:
1020 1020 break
1021 1021 if current_hunk:
1022 1022 if x.startswith('\ '):
1023 1023 current_hunk.fix_newline()
1024 1024 yield 'hunk', current_hunk
1025 1025 current_hunk = None
1026 1026 empty = False
1027 1027 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
1028 1028 ((context is not False) and x.startswith('***************')))):
1029 1029 try:
1030 1030 if context is None and x.startswith('***************'):
1031 1031 context = True
1032 1032 gpatch = changed.get(bfile)
1033 1033 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
1034 1034 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
1035 1035 current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
1036 1036 except PatchError, err:
1037 1037 ui.debug(err)
1038 1038 current_hunk = None
1039 1039 continue
1040 1040 hunknum += 1
1041 1041 if emitfile:
1042 1042 emitfile = False
1043 1043 yield 'file', (afile, bfile, current_hunk)
1044 1044 empty = False
1045 1045 elif state == BFILE and x.startswith('GIT binary patch'):
1046 1046 current_hunk = binhunk(changed[bfile])
1047 1047 hunknum += 1
1048 1048 if emitfile:
1049 1049 emitfile = False
1050 1050 yield 'file', ('a/' + afile, 'b/' + bfile, current_hunk)
1051 1051 empty = False
1052 1052 current_hunk.extract(lr)
1053 1053 elif x.startswith('diff --git'):
1054 1054 # check for git diff, scanning the whole patch file if needed
1055 1055 m = gitre.match(x)
1056 1056 gitworkdone = False
1057 1057 if m:
1058 1058 afile, bfile = m.group(1, 2)
1059 1059 if not git:
1060 1060 git = True
1061 1061 gitpatches = scangitpatch(lr, x)[1]
1062 1062 yield 'git', gitpatches
1063 1063 for gp in gitpatches:
1064 1064 changed[gp.path] = gp
1065 1065 # else error?
1066 1066 # copy/rename + modify should modify target, not source
1067 1067 gp = changed.get(bfile)
1068 1068 if gp and (gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD')
1069 1069 or gp.mode):
1070 1070 afile = bfile
1071 1071 gitworkdone = True
1072 1072 newgitfile = True
1073 1073 elif x.startswith('---'):
1074 1074 # check for a unified diff
1075 1075 l2 = lr.readline()
1076 1076 if not l2.startswith('+++'):
1077 1077 lr.push(l2)
1078 1078 continue
1079 1079 newfile = True
1080 1080 context = False
1081 1081 afile = parsefilename(x)
1082 1082 bfile = parsefilename(l2)
1083 1083 elif x.startswith('***'):
1084 1084 # check for a context diff
1085 1085 l2 = lr.readline()
1086 1086 if not l2.startswith('---'):
1087 1087 lr.push(l2)
1088 1088 continue
1089 1089 l3 = lr.readline()
1090 1090 lr.push(l3)
1091 1091 if not l3.startswith("***************"):
1092 1092 lr.push(l2)
1093 1093 continue
1094 1094 newfile = True
1095 1095 context = True
1096 1096 afile = parsefilename(x)
1097 1097 bfile = parsefilename(l2)
1098 1098
1099 1099 if newfile:
1100 1100 if empty:
1101 1101 raise NoHunks
1102 1102 empty = not gitworkdone
1103 1103 gitworkdone = False
1104 1104
1105 1105 if newgitfile or newfile:
1106 1106 emitfile = True
1107 1107 state = BFILE
1108 1108 hunknum = 0
1109 1109 if current_hunk:
1110 1110 if current_hunk.complete():
1111 1111 yield 'hunk', current_hunk
1112 1112 empty = False
1113 1113 else:
1114 1114 raise PatchError(_("malformed patch %s %s") % (afile,
1115 1115 current_hunk.desc))
1116 1116
1117 1117 if (empty is None and not gitworkdone) or empty:
1118 1118 raise NoHunks
1119 1119
1120 1120
1121 1121 def applydiff(ui, fp, changed, strip=1, sourcefile=None, eolmode='strict'):
1122 1122 """Reads a patch from fp and tries to apply it.
1123 1123
1124 1124 The dict 'changed' is filled in with all of the filenames changed
1125 1125 by the patch. Returns 0 for a clean patch, -1 if any rejects were
1126 1126 found and 1 if there was any fuzz.
1127 1127
1128 1128 If 'eolmode' is 'strict', the patch content and patched file are
1129 1129 read in binary mode. Otherwise, line endings are ignored when
1130 1130 patching then normalized according to 'eolmode'.
1131 1131
1132 Callers probably want to call 'updatedir' after this to apply
1133 certain categories of changes not done by this function.
1132 Callers probably want to call 'cmdutil.updatedir' after this to
1133 apply certain categories of changes not done by this function.
1134 1134 """
1135 1135 return _applydiff(
1136 1136 ui, fp, patchfile, copyfile,
1137 1137 changed, strip=strip, sourcefile=sourcefile, eolmode=eolmode)
1138 1138
1139 1139
1140 1140 def _applydiff(ui, fp, patcher, copyfn, changed, strip=1,
1141 1141 sourcefile=None, eolmode='strict'):
1142 1142 rejects = 0
1143 1143 err = 0
1144 1144 current_file = None
1145 1145 cwd = os.getcwd()
1146 1146 opener = util.opener(cwd)
1147 1147
1148 1148 def closefile():
1149 1149 if not current_file:
1150 1150 return 0
1151 1151 if current_file.dirty:
1152 1152 current_file.writelines(current_file.fname, current_file.lines)
1153 1153 current_file.write_rej()
1154 1154 return len(current_file.rej)
1155 1155
1156 1156 for state, values in iterhunks(ui, fp, sourcefile):
1157 1157 if state == 'hunk':
1158 1158 if not current_file:
1159 1159 continue
1160 1160 ret = current_file.apply(values)
1161 1161 if ret >= 0:
1162 1162 changed.setdefault(current_file.fname, None)
1163 1163 if ret > 0:
1164 1164 err = 1
1165 1165 elif state == 'file':
1166 1166 rejects += closefile()
1167 1167 afile, bfile, first_hunk = values
1168 1168 try:
1169 1169 if sourcefile:
1170 1170 current_file = patcher(ui, sourcefile, opener,
1171 1171 eolmode=eolmode)
1172 1172 else:
1173 1173 current_file, missing = selectfile(afile, bfile,
1174 1174 first_hunk, strip)
1175 1175 current_file = patcher(ui, current_file, opener,
1176 1176 missing=missing, eolmode=eolmode)
1177 1177 except PatchError, err:
1178 1178 ui.warn(str(err) + '\n')
1179 1179 current_file = None
1180 1180 rejects += 1
1181 1181 continue
1182 1182 elif state == 'git':
1183 1183 for gp in values:
1184 1184 gp.path = pathstrip(gp.path, strip - 1)[1]
1185 1185 if gp.oldpath:
1186 1186 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1187 1187 if gp.op in ('COPY', 'RENAME'):
1188 1188 copyfn(gp.oldpath, gp.path, cwd)
1189 1189 changed[gp.path] = gp
1190 1190 else:
1191 1191 raise util.Abort(_('unsupported parser state: %s') % state)
1192 1192
1193 1193 rejects += closefile()
1194 1194
1195 1195 if rejects:
1196 1196 return -1
1197 1197 return err
1198 1198
1199 def updatedir(ui, repo, patches, similarity=0):
1200 '''Update dirstate after patch application according to metadata'''
1201 if not patches:
1202 return
1203 copies = []
1204 removes = set()
1205 cfiles = patches.keys()
1206 cwd = repo.getcwd()
1207 if cwd:
1208 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1209 for f in patches:
1210 gp = patches[f]
1211 if not gp:
1212 continue
1213 if gp.op == 'RENAME':
1214 copies.append((gp.oldpath, gp.path))
1215 removes.add(gp.oldpath)
1216 elif gp.op == 'COPY':
1217 copies.append((gp.oldpath, gp.path))
1218 elif gp.op == 'DELETE':
1219 removes.add(gp.path)
1220
1221 wctx = repo[None]
1222 for src, dst in copies:
1223 wctx.copy(src, dst)
1224 if (not similarity) and removes:
1225 wctx.remove(sorted(removes), True)
1226
1227 for f in patches:
1228 gp = patches[f]
1229 if gp and gp.mode:
1230 islink, isexec = gp.mode
1231 dst = repo.wjoin(gp.path)
1232 # patch won't create empty files
1233 if gp.op == 'ADD' and not os.path.exists(dst):
1234 flags = (isexec and 'x' or '') + (islink and 'l' or '')
1235 repo.wwrite(gp.path, '', flags)
1236 util.set_flags(dst, islink, isexec)
1237 cmdutil.addremove(repo, cfiles, similarity=similarity)
1238 files = patches.keys()
1239 files.extend([r for r in removes if r not in files])
1240 return sorted(files)
1241
1242 1199 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
1243 1200 """use <patcher> to apply <patchname> to the working directory.
1244 1201 returns whether patch was applied with fuzz factor."""
1245 1202
1246 1203 fuzz = False
1247 1204 if cwd:
1248 1205 args.append('-d %s' % util.shellquote(cwd))
1249 1206 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1250 1207 util.shellquote(patchname)))
1251 1208
1252 1209 for line in fp:
1253 1210 line = line.rstrip()
1254 1211 ui.note(line + '\n')
1255 1212 if line.startswith('patching file '):
1256 1213 pf = util.parse_patch_output(line)
1257 1214 printed_file = False
1258 1215 files.setdefault(pf, None)
1259 1216 elif line.find('with fuzz') >= 0:
1260 1217 fuzz = True
1261 1218 if not printed_file:
1262 1219 ui.warn(pf + '\n')
1263 1220 printed_file = True
1264 1221 ui.warn(line + '\n')
1265 1222 elif line.find('saving rejects to file') >= 0:
1266 1223 ui.warn(line + '\n')
1267 1224 elif line.find('FAILED') >= 0:
1268 1225 if not printed_file:
1269 1226 ui.warn(pf + '\n')
1270 1227 printed_file = True
1271 1228 ui.warn(line + '\n')
1272 1229 code = fp.close()
1273 1230 if code:
1274 1231 raise PatchError(_("patch command failed: %s") %
1275 1232 util.explain_exit(code)[0])
1276 1233 return fuzz
1277 1234
1278 1235 def internalpatch(patchobj, ui, strip, cwd, files=None, eolmode='strict'):
1279 1236 """use builtin patch to apply <patchobj> to the working directory.
1280 1237 returns whether patch was applied with fuzz factor."""
1281 1238
1282 1239 if files is None:
1283 1240 files = {}
1284 1241 if eolmode is None:
1285 1242 eolmode = ui.config('patch', 'eol', 'strict')
1286 1243 if eolmode.lower() not in eolmodes:
1287 1244 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1288 1245 eolmode = eolmode.lower()
1289 1246
1290 1247 try:
1291 1248 fp = open(patchobj, 'rb')
1292 1249 except TypeError:
1293 1250 fp = patchobj
1294 1251 if cwd:
1295 1252 curdir = os.getcwd()
1296 1253 os.chdir(cwd)
1297 1254 try:
1298 1255 ret = applydiff(ui, fp, files, strip=strip, eolmode=eolmode)
1299 1256 finally:
1300 1257 if cwd:
1301 1258 os.chdir(curdir)
1302 1259 if fp != patchobj:
1303 1260 fp.close()
1304 1261 if ret < 0:
1305 1262 raise PatchError
1306 1263 return ret > 0
1307 1264
1308 1265 def patch(patchname, ui, strip=1, cwd=None, files=None, eolmode='strict'):
1309 1266 """Apply <patchname> to the working directory.
1310 1267
1311 1268 'eolmode' specifies how end of lines should be handled. It can be:
1312 1269 - 'strict': inputs are read in binary mode, EOLs are preserved
1313 1270 - 'crlf': EOLs are ignored when patching and reset to CRLF
1314 1271 - 'lf': EOLs are ignored when patching and reset to LF
1315 1272 - None: get it from user settings, default to 'strict'
1316 1273 'eolmode' is ignored when using an external patcher program.
1317 1274
1318 1275 Returns whether patch was applied with fuzz factor.
1319 1276 """
1320 1277 patcher = ui.config('ui', 'patch')
1321 1278 args = []
1322 1279 if files is None:
1323 1280 files = {}
1324 1281 try:
1325 1282 if patcher:
1326 1283 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1327 1284 files)
1328 1285 else:
1329 1286 try:
1330 1287 return internalpatch(patchname, ui, strip, cwd, files, eolmode)
1331 1288 except NoHunks:
1332 1289 ui.warn(_('internal patcher failed\n'
1333 1290 'please report details to '
1334 1291 'http://mercurial.selenic.com/bts/\n'
1335 1292 'or mercurial@selenic.com\n'))
1336 1293 patcher = (util.find_exe('gpatch') or util.find_exe('patch')
1337 1294 or 'patch')
1338 1295 ui.debug('no valid hunks found; trying with %r instead\n' %
1339 1296 patcher)
1340 1297 if util.needbinarypatch():
1341 1298 args.append('--binary')
1342 1299 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1343 1300 files)
1344 1301 except PatchError, err:
1345 1302 s = str(err)
1346 1303 if s:
1347 1304 raise util.Abort(s)
1348 1305 else:
1349 1306 raise util.Abort(_('patch failed to apply'))
1350 1307
1351 1308 def b85diff(to, tn):
1352 1309 '''print base85-encoded binary diff'''
1353 1310 def gitindex(text):
1354 1311 if not text:
1355 1312 return hex(nullid)
1356 1313 l = len(text)
1357 1314 s = util.sha1('blob %d\0' % l)
1358 1315 s.update(text)
1359 1316 return s.hexdigest()
1360 1317
1361 1318 def fmtline(line):
1362 1319 l = len(line)
1363 1320 if l <= 26:
1364 1321 l = chr(ord('A') + l - 1)
1365 1322 else:
1366 1323 l = chr(l - 26 + ord('a') - 1)
1367 1324 return '%c%s\n' % (l, base85.b85encode(line, True))
1368 1325
1369 1326 def chunk(text, csize=52):
1370 1327 l = len(text)
1371 1328 i = 0
1372 1329 while i < l:
1373 1330 yield text[i:i + csize]
1374 1331 i += csize
1375 1332
1376 1333 tohash = gitindex(to)
1377 1334 tnhash = gitindex(tn)
1378 1335 if tohash == tnhash:
1379 1336 return ""
1380 1337
1381 1338 # TODO: deltas
1382 1339 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1383 1340 (tohash, tnhash, len(tn))]
1384 1341 for l in chunk(zlib.compress(tn)):
1385 1342 ret.append(fmtline(l))
1386 1343 ret.append('\n')
1387 1344 return ''.join(ret)
1388 1345
1389 1346 class GitDiffRequired(Exception):
1390 1347 pass
1391 1348
1392 1349 def diffopts(ui, opts=None, untrusted=False):
1393 1350 def get(key, name=None, getter=ui.configbool):
1394 1351 return ((opts and opts.get(key)) or
1395 1352 getter('diff', name or key, None, untrusted=untrusted))
1396 1353 return mdiff.diffopts(
1397 1354 text=opts and opts.get('text'),
1398 1355 git=get('git'),
1399 1356 nodates=get('nodates'),
1400 1357 showfunc=get('show_function', 'showfunc'),
1401 1358 ignorews=get('ignore_all_space', 'ignorews'),
1402 1359 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1403 1360 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1404 1361 context=get('unified', getter=ui.config))
1405 1362
1406 1363 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1407 1364 losedatafn=None, prefix=''):
1408 1365 '''yields diff of changes to files between two nodes, or node and
1409 1366 working directory.
1410 1367
1411 1368 if node1 is None, use first dirstate parent instead.
1412 1369 if node2 is None, compare node1 with working directory.
1413 1370
1414 1371 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1415 1372 every time some change cannot be represented with the current
1416 1373 patch format. Return False to upgrade to git patch format, True to
1417 1374 accept the loss or raise an exception to abort the diff. It is
1418 1375 called with the name of current file being diffed as 'fn'. If set
1419 1376 to None, patches will always be upgraded to git format when
1420 1377 necessary.
1421 1378
1422 1379 prefix is a filename prefix that is prepended to all filenames on
1423 1380 display (used for subrepos).
1424 1381 '''
1425 1382
1426 1383 if opts is None:
1427 1384 opts = mdiff.defaultopts
1428 1385
1429 1386 if not node1 and not node2:
1430 1387 node1 = repo.dirstate.parents()[0]
1431 1388
1432 1389 def lrugetfilectx():
1433 1390 cache = {}
1434 1391 order = []
1435 1392 def getfilectx(f, ctx):
1436 1393 fctx = ctx.filectx(f, filelog=cache.get(f))
1437 1394 if f not in cache:
1438 1395 if len(cache) > 20:
1439 1396 del cache[order.pop(0)]
1440 1397 cache[f] = fctx.filelog()
1441 1398 else:
1442 1399 order.remove(f)
1443 1400 order.append(f)
1444 1401 return fctx
1445 1402 return getfilectx
1446 1403 getfilectx = lrugetfilectx()
1447 1404
1448 1405 ctx1 = repo[node1]
1449 1406 ctx2 = repo[node2]
1450 1407
1451 1408 if not changes:
1452 1409 changes = repo.status(ctx1, ctx2, match=match)
1453 1410 modified, added, removed = changes[:3]
1454 1411
1455 1412 if not modified and not added and not removed:
1456 1413 return []
1457 1414
1458 1415 revs = None
1459 1416 if not repo.ui.quiet:
1460 1417 hexfunc = repo.ui.debugflag and hex or short
1461 1418 revs = [hexfunc(node) for node in [node1, node2] if node]
1462 1419
1463 1420 copy = {}
1464 1421 if opts.git or opts.upgrade:
1465 1422 copy = copies.copies(repo, ctx1, ctx2, repo[nullid])[0]
1466 1423
1467 1424 difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2,
1468 1425 modified, added, removed, copy, getfilectx, opts, losedata, prefix)
1469 1426 if opts.upgrade and not opts.git:
1470 1427 try:
1471 1428 def losedata(fn):
1472 1429 if not losedatafn or not losedatafn(fn=fn):
1473 1430 raise GitDiffRequired()
1474 1431 # Buffer the whole output until we are sure it can be generated
1475 1432 return list(difffn(opts.copy(git=False), losedata))
1476 1433 except GitDiffRequired:
1477 1434 return difffn(opts.copy(git=True), None)
1478 1435 else:
1479 1436 return difffn(opts, None)
1480 1437
1481 1438 def difflabel(func, *args, **kw):
1482 1439 '''yields 2-tuples of (output, label) based on the output of func()'''
1483 1440 prefixes = [('diff', 'diff.diffline'),
1484 1441 ('copy', 'diff.extended'),
1485 1442 ('rename', 'diff.extended'),
1486 1443 ('old', 'diff.extended'),
1487 1444 ('new', 'diff.extended'),
1488 1445 ('deleted', 'diff.extended'),
1489 1446 ('---', 'diff.file_a'),
1490 1447 ('+++', 'diff.file_b'),
1491 1448 ('@@', 'diff.hunk'),
1492 1449 ('-', 'diff.deleted'),
1493 1450 ('+', 'diff.inserted')]
1494 1451
1495 1452 for chunk in func(*args, **kw):
1496 1453 lines = chunk.split('\n')
1497 1454 for i, line in enumerate(lines):
1498 1455 if i != 0:
1499 1456 yield ('\n', '')
1500 1457 stripline = line
1501 1458 if line and line[0] in '+-':
1502 1459 # highlight trailing whitespace, but only in changed lines
1503 1460 stripline = line.rstrip()
1504 1461 for prefix, label in prefixes:
1505 1462 if stripline.startswith(prefix):
1506 1463 yield (stripline, label)
1507 1464 break
1508 1465 else:
1509 1466 yield (line, '')
1510 1467 if line != stripline:
1511 1468 yield (line[len(stripline):], 'diff.trailingwhitespace')
1512 1469
1513 1470 def diffui(*args, **kw):
1514 1471 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1515 1472 return difflabel(diff, *args, **kw)
1516 1473
1517 1474
1518 1475 def _addmodehdr(header, omode, nmode):
1519 1476 if omode != nmode:
1520 1477 header.append('old mode %s\n' % omode)
1521 1478 header.append('new mode %s\n' % nmode)
1522 1479
1523 1480 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1524 1481 copy, getfilectx, opts, losedatafn, prefix):
1525 1482
1526 1483 def join(f):
1527 1484 return os.path.join(prefix, f)
1528 1485
1529 1486 date1 = util.datestr(ctx1.date())
1530 1487 man1 = ctx1.manifest()
1531 1488
1532 1489 gone = set()
1533 1490 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1534 1491
1535 1492 copyto = dict([(v, k) for k, v in copy.items()])
1536 1493
1537 1494 if opts.git:
1538 1495 revs = None
1539 1496
1540 1497 for f in sorted(modified + added + removed):
1541 1498 to = None
1542 1499 tn = None
1543 1500 dodiff = True
1544 1501 header = []
1545 1502 if f in man1:
1546 1503 to = getfilectx(f, ctx1).data()
1547 1504 if f not in removed:
1548 1505 tn = getfilectx(f, ctx2).data()
1549 1506 a, b = f, f
1550 1507 if opts.git or losedatafn:
1551 1508 if f in added:
1552 1509 mode = gitmode[ctx2.flags(f)]
1553 1510 if f in copy or f in copyto:
1554 1511 if opts.git:
1555 1512 if f in copy:
1556 1513 a = copy[f]
1557 1514 else:
1558 1515 a = copyto[f]
1559 1516 omode = gitmode[man1.flags(a)]
1560 1517 _addmodehdr(header, omode, mode)
1561 1518 if a in removed and a not in gone:
1562 1519 op = 'rename'
1563 1520 gone.add(a)
1564 1521 else:
1565 1522 op = 'copy'
1566 1523 header.append('%s from %s\n' % (op, join(a)))
1567 1524 header.append('%s to %s\n' % (op, join(f)))
1568 1525 to = getfilectx(a, ctx1).data()
1569 1526 else:
1570 1527 losedatafn(f)
1571 1528 else:
1572 1529 if opts.git:
1573 1530 header.append('new file mode %s\n' % mode)
1574 1531 elif ctx2.flags(f):
1575 1532 losedatafn(f)
1576 1533 if util.binary(tn):
1577 1534 if opts.git:
1578 1535 dodiff = 'binary'
1579 1536 else:
1580 1537 losedatafn(f)
1581 1538 if not opts.git and not tn:
1582 1539 # regular diffs cannot represent new empty file
1583 1540 losedatafn(f)
1584 1541 elif f in removed:
1585 1542 if opts.git:
1586 1543 # have we already reported a copy above?
1587 1544 if ((f in copy and copy[f] in added
1588 1545 and copyto[copy[f]] == f) or
1589 1546 (f in copyto and copyto[f] in added
1590 1547 and copy[copyto[f]] == f)):
1591 1548 dodiff = False
1592 1549 else:
1593 1550 header.append('deleted file mode %s\n' %
1594 1551 gitmode[man1.flags(f)])
1595 1552 elif not to:
1596 1553 # regular diffs cannot represent empty file deletion
1597 1554 losedatafn(f)
1598 1555 else:
1599 1556 oflag = man1.flags(f)
1600 1557 nflag = ctx2.flags(f)
1601 1558 binary = util.binary(to) or util.binary(tn)
1602 1559 if opts.git:
1603 1560 _addmodehdr(header, gitmode[oflag], gitmode[nflag])
1604 1561 if binary:
1605 1562 dodiff = 'binary'
1606 1563 elif binary or nflag != oflag:
1607 1564 losedatafn(f)
1608 1565 if opts.git:
1609 1566 header.insert(0, mdiff.diffline(revs, join(a), join(b), opts))
1610 1567
1611 1568 if dodiff:
1612 1569 if dodiff == 'binary':
1613 1570 text = b85diff(to, tn)
1614 1571 else:
1615 1572 text = mdiff.unidiff(to, date1,
1616 1573 # ctx2 date may be dynamic
1617 1574 tn, util.datestr(ctx2.date()),
1618 1575 join(a), join(b), revs, opts=opts)
1619 1576 if header and (text or len(header) > 1):
1620 1577 yield ''.join(header)
1621 1578 if text:
1622 1579 yield text
1623 1580
1624 1581 def diffstatdata(lines):
1625 1582 filename, adds, removes = None, 0, 0
1626 1583 for line in lines:
1627 1584 if line.startswith('diff'):
1628 1585 if filename:
1629 1586 isbinary = adds == 0 and removes == 0
1630 1587 yield (filename, adds, removes, isbinary)
1631 1588 # set numbers to 0 anyway when starting new file
1632 1589 adds, removes = 0, 0
1633 1590 if line.startswith('diff --git'):
1634 1591 filename = gitre.search(line).group(1)
1635 1592 else:
1636 1593 # format: "diff -r ... -r ... filename"
1637 1594 filename = line.split(None, 5)[-1]
1638 1595 elif line.startswith('+') and not line.startswith('+++'):
1639 1596 adds += 1
1640 1597 elif line.startswith('-') and not line.startswith('---'):
1641 1598 removes += 1
1642 1599 if filename:
1643 1600 isbinary = adds == 0 and removes == 0
1644 1601 yield (filename, adds, removes, isbinary)
1645 1602
1646 1603 def diffstat(lines, width=80, git=False):
1647 1604 output = []
1648 1605 stats = list(diffstatdata(lines))
1649 1606
1650 1607 maxtotal, maxname = 0, 0
1651 1608 totaladds, totalremoves = 0, 0
1652 1609 hasbinary = False
1653 1610
1654 1611 sized = [(filename, adds, removes, isbinary, encoding.colwidth(filename))
1655 1612 for filename, adds, removes, isbinary in stats]
1656 1613
1657 1614 for filename, adds, removes, isbinary, namewidth in sized:
1658 1615 totaladds += adds
1659 1616 totalremoves += removes
1660 1617 maxname = max(maxname, namewidth)
1661 1618 maxtotal = max(maxtotal, adds + removes)
1662 1619 if isbinary:
1663 1620 hasbinary = True
1664 1621
1665 1622 countwidth = len(str(maxtotal))
1666 1623 if hasbinary and countwidth < 3:
1667 1624 countwidth = 3
1668 1625 graphwidth = width - countwidth - maxname - 6
1669 1626 if graphwidth < 10:
1670 1627 graphwidth = 10
1671 1628
1672 1629 def scale(i):
1673 1630 if maxtotal <= graphwidth:
1674 1631 return i
1675 1632 # If diffstat runs out of room it doesn't print anything,
1676 1633 # which isn't very useful, so always print at least one + or -
1677 1634 # if there were at least some changes.
1678 1635 return max(i * graphwidth // maxtotal, int(bool(i)))
1679 1636
1680 1637 for filename, adds, removes, isbinary, namewidth in sized:
1681 1638 if git and isbinary:
1682 1639 count = 'Bin'
1683 1640 else:
1684 1641 count = adds + removes
1685 1642 pluses = '+' * scale(adds)
1686 1643 minuses = '-' * scale(removes)
1687 1644 output.append(' %s%s | %*s %s%s\n' %
1688 1645 (filename, ' ' * (maxname - namewidth),
1689 1646 countwidth, count,
1690 1647 pluses, minuses))
1691 1648
1692 1649 if stats:
1693 1650 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1694 1651 % (len(stats), totaladds, totalremoves))
1695 1652
1696 1653 return ''.join(output)
1697 1654
1698 1655 def diffstatui(*args, **kw):
1699 1656 '''like diffstat(), but yields 2-tuples of (output, label) for
1700 1657 ui.write()
1701 1658 '''
1702 1659
1703 1660 for line in diffstat(*args, **kw).splitlines():
1704 1661 if line and line[-1] in '+-':
1705 1662 name, graph = line.rsplit(' ', 1)
1706 1663 yield (name + ' ', '')
1707 1664 m = re.search(r'\++', graph)
1708 1665 if m:
1709 1666 yield (m.group(0), 'diffstat.inserted')
1710 1667 m = re.search(r'-+', graph)
1711 1668 if m:
1712 1669 yield (m.group(0), 'diffstat.deleted')
1713 1670 else:
1714 1671 yield (line, '')
1715 1672 yield ('\n', '')
General Comments 0
You need to be logged in to leave comments. Login now