##// END OF EJS Templates
patch: remove patch.patch() cwd argument
Patrick Mezard -
r14382:2d16f15d default
parent child Browse files
Show More
@@ -1,3307 +1,3307 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, scmutil, util, revset
49 49 from mercurial import repair, extensions, url, error
50 50 from mercurial import patch as patchmod
51 51 import os, sys, re, errno, shutil
52 52
53 53 commands.norepo += " qclone"
54 54
55 55 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
56 56
57 57 cmdtable = {}
58 58 command = cmdutil.command(cmdtable)
59 59
60 60 # Patch names looks like unix-file names.
61 61 # They must be joinable with queue directory and result in the patch path.
62 62 normname = util.normpath
63 63
64 64 class statusentry(object):
65 65 def __init__(self, node, name):
66 66 self.node, self.name = node, name
67 67 def __repr__(self):
68 68 return hex(self.node) + ':' + self.name
69 69
70 70 class patchheader(object):
71 71 def __init__(self, pf, plainmode=False):
72 72 def eatdiff(lines):
73 73 while lines:
74 74 l = lines[-1]
75 75 if (l.startswith("diff -") or
76 76 l.startswith("Index:") or
77 77 l.startswith("===========")):
78 78 del lines[-1]
79 79 else:
80 80 break
81 81 def eatempty(lines):
82 82 while lines:
83 83 if not lines[-1].strip():
84 84 del lines[-1]
85 85 else:
86 86 break
87 87
88 88 message = []
89 89 comments = []
90 90 user = None
91 91 date = None
92 92 parent = None
93 93 format = None
94 94 subject = None
95 95 branch = None
96 96 nodeid = None
97 97 diffstart = 0
98 98
99 99 for line in file(pf):
100 100 line = line.rstrip()
101 101 if (line.startswith('diff --git')
102 102 or (diffstart and line.startswith('+++ '))):
103 103 diffstart = 2
104 104 break
105 105 diffstart = 0 # reset
106 106 if line.startswith("--- "):
107 107 diffstart = 1
108 108 continue
109 109 elif format == "hgpatch":
110 110 # parse values when importing the result of an hg export
111 111 if line.startswith("# User "):
112 112 user = line[7:]
113 113 elif line.startswith("# Date "):
114 114 date = line[7:]
115 115 elif line.startswith("# Parent "):
116 116 parent = line[9:].lstrip()
117 117 elif line.startswith("# Branch "):
118 118 branch = line[9:]
119 119 elif line.startswith("# Node ID "):
120 120 nodeid = line[10:]
121 121 elif not line.startswith("# ") and line:
122 122 message.append(line)
123 123 format = None
124 124 elif line == '# HG changeset patch':
125 125 message = []
126 126 format = "hgpatch"
127 127 elif (format != "tagdone" and (line.startswith("Subject: ") or
128 128 line.startswith("subject: "))):
129 129 subject = line[9:]
130 130 format = "tag"
131 131 elif (format != "tagdone" and (line.startswith("From: ") or
132 132 line.startswith("from: "))):
133 133 user = line[6:]
134 134 format = "tag"
135 135 elif (format != "tagdone" and (line.startswith("Date: ") or
136 136 line.startswith("date: "))):
137 137 date = line[6:]
138 138 format = "tag"
139 139 elif format == "tag" and line == "":
140 140 # when looking for tags (subject: from: etc) they
141 141 # end once you find a blank line in the source
142 142 format = "tagdone"
143 143 elif message or line:
144 144 message.append(line)
145 145 comments.append(line)
146 146
147 147 eatdiff(message)
148 148 eatdiff(comments)
149 149 # Remember the exact starting line of the patch diffs before consuming
150 150 # empty lines, for external use by TortoiseHg and others
151 151 self.diffstartline = len(comments)
152 152 eatempty(message)
153 153 eatempty(comments)
154 154
155 155 # make sure message isn't empty
156 156 if format and format.startswith("tag") and subject:
157 157 message.insert(0, "")
158 158 message.insert(0, subject)
159 159
160 160 self.message = message
161 161 self.comments = comments
162 162 self.user = user
163 163 self.date = date
164 164 self.parent = parent
165 165 # nodeid and branch are for external use by TortoiseHg and others
166 166 self.nodeid = nodeid
167 167 self.branch = branch
168 168 self.haspatch = diffstart > 1
169 169 self.plainmode = plainmode
170 170
171 171 def setuser(self, user):
172 172 if not self.updateheader(['From: ', '# User '], user):
173 173 try:
174 174 patchheaderat = self.comments.index('# HG changeset patch')
175 175 self.comments.insert(patchheaderat + 1, '# User ' + user)
176 176 except ValueError:
177 177 if self.plainmode or self._hasheader(['Date: ']):
178 178 self.comments = ['From: ' + user] + self.comments
179 179 else:
180 180 tmp = ['# HG changeset patch', '# User ' + user, '']
181 181 self.comments = tmp + self.comments
182 182 self.user = user
183 183
184 184 def setdate(self, date):
185 185 if not self.updateheader(['Date: ', '# Date '], date):
186 186 try:
187 187 patchheaderat = self.comments.index('# HG changeset patch')
188 188 self.comments.insert(patchheaderat + 1, '# Date ' + date)
189 189 except ValueError:
190 190 if self.plainmode or self._hasheader(['From: ']):
191 191 self.comments = ['Date: ' + date] + self.comments
192 192 else:
193 193 tmp = ['# HG changeset patch', '# Date ' + date, '']
194 194 self.comments = tmp + self.comments
195 195 self.date = date
196 196
197 197 def setparent(self, parent):
198 198 if not self.updateheader(['# Parent '], parent):
199 199 try:
200 200 patchheaderat = self.comments.index('# HG changeset patch')
201 201 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
202 202 except ValueError:
203 203 pass
204 204 self.parent = parent
205 205
206 206 def setmessage(self, message):
207 207 if self.comments:
208 208 self._delmsg()
209 209 self.message = [message]
210 210 self.comments += self.message
211 211
212 212 def updateheader(self, prefixes, new):
213 213 '''Update all references to a field in the patch header.
214 214 Return whether the field is present.'''
215 215 res = False
216 216 for prefix in prefixes:
217 217 for i in xrange(len(self.comments)):
218 218 if self.comments[i].startswith(prefix):
219 219 self.comments[i] = prefix + new
220 220 res = True
221 221 break
222 222 return res
223 223
224 224 def _hasheader(self, prefixes):
225 225 '''Check if a header starts with any of the given prefixes.'''
226 226 for prefix in prefixes:
227 227 for comment in self.comments:
228 228 if comment.startswith(prefix):
229 229 return True
230 230 return False
231 231
232 232 def __str__(self):
233 233 if not self.comments:
234 234 return ''
235 235 return '\n'.join(self.comments) + '\n\n'
236 236
237 237 def _delmsg(self):
238 238 '''Remove existing message, keeping the rest of the comments fields.
239 239 If comments contains 'subject: ', message will prepend
240 240 the field and a blank line.'''
241 241 if self.message:
242 242 subj = 'subject: ' + self.message[0].lower()
243 243 for i in xrange(len(self.comments)):
244 244 if subj == self.comments[i].lower():
245 245 del self.comments[i]
246 246 self.message = self.message[2:]
247 247 break
248 248 ci = 0
249 249 for mi in self.message:
250 250 while mi != self.comments[ci]:
251 251 ci += 1
252 252 del self.comments[ci]
253 253
254 254 class queue(object):
255 255 def __init__(self, ui, path, patchdir=None):
256 256 self.basepath = path
257 257 try:
258 258 fh = open(os.path.join(path, 'patches.queue'))
259 259 cur = fh.read().rstrip()
260 260 fh.close()
261 261 if not cur:
262 262 curpath = os.path.join(path, 'patches')
263 263 else:
264 264 curpath = os.path.join(path, 'patches-' + cur)
265 265 except IOError:
266 266 curpath = os.path.join(path, 'patches')
267 267 self.path = patchdir or curpath
268 268 self.opener = scmutil.opener(self.path)
269 269 self.ui = ui
270 270 self.applied_dirty = 0
271 271 self.series_dirty = 0
272 272 self.added = []
273 273 self.series_path = "series"
274 274 self.status_path = "status"
275 275 self.guards_path = "guards"
276 276 self.active_guards = None
277 277 self.guards_dirty = False
278 278 # Handle mq.git as a bool with extended values
279 279 try:
280 280 gitmode = ui.configbool('mq', 'git', None)
281 281 if gitmode is None:
282 282 raise error.ConfigError()
283 283 self.gitmode = gitmode and 'yes' or 'no'
284 284 except error.ConfigError:
285 285 self.gitmode = ui.config('mq', 'git', 'auto').lower()
286 286 self.plainmode = ui.configbool('mq', 'plain', False)
287 287
288 288 @util.propertycache
289 289 def applied(self):
290 290 if os.path.exists(self.join(self.status_path)):
291 291 def parselines(lines):
292 292 for l in lines:
293 293 entry = l.split(':', 1)
294 294 if len(entry) > 1:
295 295 n, name = entry
296 296 yield statusentry(bin(n), name)
297 297 elif l.strip():
298 298 self.ui.warn(_('malformated mq status line: %s\n') % entry)
299 299 # else we ignore empty lines
300 300 lines = self.opener.read(self.status_path).splitlines()
301 301 return list(parselines(lines))
302 302 return []
303 303
304 304 @util.propertycache
305 305 def full_series(self):
306 306 if os.path.exists(self.join(self.series_path)):
307 307 return self.opener.read(self.series_path).splitlines()
308 308 return []
309 309
310 310 @util.propertycache
311 311 def series(self):
312 312 self.parse_series()
313 313 return self.series
314 314
315 315 @util.propertycache
316 316 def series_guards(self):
317 317 self.parse_series()
318 318 return self.series_guards
319 319
320 320 def invalidate(self):
321 321 for a in 'applied full_series series series_guards'.split():
322 322 if a in self.__dict__:
323 323 delattr(self, a)
324 324 self.applied_dirty = 0
325 325 self.series_dirty = 0
326 326 self.guards_dirty = False
327 327 self.active_guards = None
328 328
329 329 def diffopts(self, opts={}, patchfn=None):
330 330 diffopts = patchmod.diffopts(self.ui, opts)
331 331 if self.gitmode == 'auto':
332 332 diffopts.upgrade = True
333 333 elif self.gitmode == 'keep':
334 334 pass
335 335 elif self.gitmode in ('yes', 'no'):
336 336 diffopts.git = self.gitmode == 'yes'
337 337 else:
338 338 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
339 339 ' got %s') % self.gitmode)
340 340 if patchfn:
341 341 diffopts = self.patchopts(diffopts, patchfn)
342 342 return diffopts
343 343
344 344 def patchopts(self, diffopts, *patches):
345 345 """Return a copy of input diff options with git set to true if
346 346 referenced patch is a git patch and should be preserved as such.
347 347 """
348 348 diffopts = diffopts.copy()
349 349 if not diffopts.git and self.gitmode == 'keep':
350 350 for patchfn in patches:
351 351 patchf = self.opener(patchfn, 'r')
352 352 # if the patch was a git patch, refresh it as a git patch
353 353 for line in patchf:
354 354 if line.startswith('diff --git'):
355 355 diffopts.git = True
356 356 break
357 357 patchf.close()
358 358 return diffopts
359 359
360 360 def join(self, *p):
361 361 return os.path.join(self.path, *p)
362 362
363 363 def find_series(self, patch):
364 364 def matchpatch(l):
365 365 l = l.split('#', 1)[0]
366 366 return l.strip() == patch
367 367 for index, l in enumerate(self.full_series):
368 368 if matchpatch(l):
369 369 return index
370 370 return None
371 371
372 372 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
373 373
374 374 def parse_series(self):
375 375 self.series = []
376 376 self.series_guards = []
377 377 for l in self.full_series:
378 378 h = l.find('#')
379 379 if h == -1:
380 380 patch = l
381 381 comment = ''
382 382 elif h == 0:
383 383 continue
384 384 else:
385 385 patch = l[:h]
386 386 comment = l[h:]
387 387 patch = patch.strip()
388 388 if patch:
389 389 if patch in self.series:
390 390 raise util.Abort(_('%s appears more than once in %s') %
391 391 (patch, self.join(self.series_path)))
392 392 self.series.append(patch)
393 393 self.series_guards.append(self.guard_re.findall(comment))
394 394
395 395 def check_guard(self, guard):
396 396 if not guard:
397 397 return _('guard cannot be an empty string')
398 398 bad_chars = '# \t\r\n\f'
399 399 first = guard[0]
400 400 if first in '-+':
401 401 return (_('guard %r starts with invalid character: %r') %
402 402 (guard, first))
403 403 for c in bad_chars:
404 404 if c in guard:
405 405 return _('invalid character in guard %r: %r') % (guard, c)
406 406
407 407 def set_active(self, guards):
408 408 for guard in guards:
409 409 bad = self.check_guard(guard)
410 410 if bad:
411 411 raise util.Abort(bad)
412 412 guards = sorted(set(guards))
413 413 self.ui.debug('active guards: %s\n' % ' '.join(guards))
414 414 self.active_guards = guards
415 415 self.guards_dirty = True
416 416
417 417 def active(self):
418 418 if self.active_guards is None:
419 419 self.active_guards = []
420 420 try:
421 421 guards = self.opener.read(self.guards_path).split()
422 422 except IOError, err:
423 423 if err.errno != errno.ENOENT:
424 424 raise
425 425 guards = []
426 426 for i, guard in enumerate(guards):
427 427 bad = self.check_guard(guard)
428 428 if bad:
429 429 self.ui.warn('%s:%d: %s\n' %
430 430 (self.join(self.guards_path), i + 1, bad))
431 431 else:
432 432 self.active_guards.append(guard)
433 433 return self.active_guards
434 434
435 435 def set_guards(self, idx, guards):
436 436 for g in guards:
437 437 if len(g) < 2:
438 438 raise util.Abort(_('guard %r too short') % g)
439 439 if g[0] not in '-+':
440 440 raise util.Abort(_('guard %r starts with invalid char') % g)
441 441 bad = self.check_guard(g[1:])
442 442 if bad:
443 443 raise util.Abort(bad)
444 444 drop = self.guard_re.sub('', self.full_series[idx])
445 445 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
446 446 self.parse_series()
447 447 self.series_dirty = True
448 448
449 449 def pushable(self, idx):
450 450 if isinstance(idx, str):
451 451 idx = self.series.index(idx)
452 452 patchguards = self.series_guards[idx]
453 453 if not patchguards:
454 454 return True, None
455 455 guards = self.active()
456 456 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
457 457 if exactneg:
458 458 return False, exactneg[0]
459 459 pos = [g for g in patchguards if g[0] == '+']
460 460 exactpos = [g for g in pos if g[1:] in guards]
461 461 if pos:
462 462 if exactpos:
463 463 return True, exactpos[0]
464 464 return False, pos
465 465 return True, ''
466 466
467 467 def explain_pushable(self, idx, all_patches=False):
468 468 write = all_patches and self.ui.write or self.ui.warn
469 469 if all_patches or self.ui.verbose:
470 470 if isinstance(idx, str):
471 471 idx = self.series.index(idx)
472 472 pushable, why = self.pushable(idx)
473 473 if all_patches and pushable:
474 474 if why is None:
475 475 write(_('allowing %s - no guards in effect\n') %
476 476 self.series[idx])
477 477 else:
478 478 if not why:
479 479 write(_('allowing %s - no matching negative guards\n') %
480 480 self.series[idx])
481 481 else:
482 482 write(_('allowing %s - guarded by %r\n') %
483 483 (self.series[idx], why))
484 484 if not pushable:
485 485 if why:
486 486 write(_('skipping %s - guarded by %r\n') %
487 487 (self.series[idx], why))
488 488 else:
489 489 write(_('skipping %s - no matching guards\n') %
490 490 self.series[idx])
491 491
492 492 def save_dirty(self):
493 493 def write_list(items, path):
494 494 fp = self.opener(path, 'w')
495 495 for i in items:
496 496 fp.write("%s\n" % i)
497 497 fp.close()
498 498 if self.applied_dirty:
499 499 write_list(map(str, self.applied), self.status_path)
500 500 if self.series_dirty:
501 501 write_list(self.full_series, self.series_path)
502 502 if self.guards_dirty:
503 503 write_list(self.active_guards, self.guards_path)
504 504 if self.added:
505 505 qrepo = self.qrepo()
506 506 if qrepo:
507 507 qrepo[None].add(f for f in self.added if f not in qrepo[None])
508 508 self.added = []
509 509
510 510 def removeundo(self, repo):
511 511 undo = repo.sjoin('undo')
512 512 if not os.path.exists(undo):
513 513 return
514 514 try:
515 515 os.unlink(undo)
516 516 except OSError, inst:
517 517 self.ui.warn(_('error removing undo: %s\n') % str(inst))
518 518
519 519 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
520 520 fp=None, changes=None, opts={}):
521 521 stat = opts.get('stat')
522 522 m = scmutil.match(repo, files, opts)
523 523 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
524 524 changes, stat, fp)
525 525
526 526 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
527 527 # first try just applying the patch
528 528 (err, n) = self.apply(repo, [patch], update_status=False,
529 529 strict=True, merge=rev)
530 530
531 531 if err == 0:
532 532 return (err, n)
533 533
534 534 if n is None:
535 535 raise util.Abort(_("apply failed for patch %s") % patch)
536 536
537 537 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
538 538
539 539 # apply failed, strip away that rev and merge.
540 540 hg.clean(repo, head)
541 541 self.strip(repo, [n], update=False, backup='strip')
542 542
543 543 ctx = repo[rev]
544 544 ret = hg.merge(repo, rev)
545 545 if ret:
546 546 raise util.Abort(_("update returned %d") % ret)
547 547 n = repo.commit(ctx.description(), ctx.user(), force=True)
548 548 if n is None:
549 549 raise util.Abort(_("repo commit failed"))
550 550 try:
551 551 ph = patchheader(mergeq.join(patch), self.plainmode)
552 552 except:
553 553 raise util.Abort(_("unable to read %s") % patch)
554 554
555 555 diffopts = self.patchopts(diffopts, patch)
556 556 patchf = self.opener(patch, "w")
557 557 comments = str(ph)
558 558 if comments:
559 559 patchf.write(comments)
560 560 self.printdiff(repo, diffopts, head, n, fp=patchf)
561 561 patchf.close()
562 562 self.removeundo(repo)
563 563 return (0, n)
564 564
565 565 def qparents(self, repo, rev=None):
566 566 if rev is None:
567 567 (p1, p2) = repo.dirstate.parents()
568 568 if p2 == nullid:
569 569 return p1
570 570 if not self.applied:
571 571 return None
572 572 return self.applied[-1].node
573 573 p1, p2 = repo.changelog.parents(rev)
574 574 if p2 != nullid and p2 in [x.node for x in self.applied]:
575 575 return p2
576 576 return p1
577 577
578 578 def mergepatch(self, repo, mergeq, series, diffopts):
579 579 if not self.applied:
580 580 # each of the patches merged in will have two parents. This
581 581 # can confuse the qrefresh, qdiff, and strip code because it
582 582 # needs to know which parent is actually in the patch queue.
583 583 # so, we insert a merge marker with only one parent. This way
584 584 # the first patch in the queue is never a merge patch
585 585 #
586 586 pname = ".hg.patches.merge.marker"
587 587 n = repo.commit('[mq]: merge marker', force=True)
588 588 self.removeundo(repo)
589 589 self.applied.append(statusentry(n, pname))
590 590 self.applied_dirty = 1
591 591
592 592 head = self.qparents(repo)
593 593
594 594 for patch in series:
595 595 patch = mergeq.lookup(patch, strict=True)
596 596 if not patch:
597 597 self.ui.warn(_("patch %s does not exist\n") % patch)
598 598 return (1, None)
599 599 pushable, reason = self.pushable(patch)
600 600 if not pushable:
601 601 self.explain_pushable(patch, all_patches=True)
602 602 continue
603 603 info = mergeq.isapplied(patch)
604 604 if not info:
605 605 self.ui.warn(_("patch %s is not applied\n") % patch)
606 606 return (1, None)
607 607 rev = info[1]
608 608 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
609 609 if head:
610 610 self.applied.append(statusentry(head, patch))
611 611 self.applied_dirty = 1
612 612 if err:
613 613 return (err, head)
614 614 self.save_dirty()
615 615 return (0, head)
616 616
617 617 def patch(self, repo, patchfile):
618 618 '''Apply patchfile to the working directory.
619 619 patchfile: name of patch file'''
620 620 files = {}
621 621 try:
622 622 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
623 cwd=repo.root, files=files, eolmode=None)
623 files=files, eolmode=None)
624 624 return (True, list(files), fuzz)
625 625 except Exception, inst:
626 626 self.ui.note(str(inst) + '\n')
627 627 if not self.ui.verbose:
628 628 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
629 629 return (False, list(files), False)
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 wlock = lock = tr = None
634 634 try:
635 635 wlock = repo.wlock()
636 636 lock = repo.lock()
637 637 tr = repo.transaction("qpush")
638 638 try:
639 639 ret = self._apply(repo, series, list, update_status,
640 640 strict, patchdir, merge, all_files=all_files)
641 641 tr.close()
642 642 self.save_dirty()
643 643 return ret
644 644 except:
645 645 try:
646 646 tr.abort()
647 647 finally:
648 648 repo.invalidate()
649 649 repo.dirstate.invalidate()
650 650 raise
651 651 finally:
652 652 release(tr, lock, wlock)
653 653 self.removeundo(repo)
654 654
655 655 def _apply(self, repo, series, list=False, update_status=True,
656 656 strict=False, patchdir=None, merge=None, all_files=None):
657 657 '''returns (error, hash)
658 658 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
659 659 # TODO unify with commands.py
660 660 if not patchdir:
661 661 patchdir = self.path
662 662 err = 0
663 663 n = None
664 664 for patchname in series:
665 665 pushable, reason = self.pushable(patchname)
666 666 if not pushable:
667 667 self.explain_pushable(patchname, all_patches=True)
668 668 continue
669 669 self.ui.status(_("applying %s\n") % patchname)
670 670 pf = os.path.join(patchdir, patchname)
671 671
672 672 try:
673 673 ph = patchheader(self.join(patchname), self.plainmode)
674 674 except IOError:
675 675 self.ui.warn(_("unable to read %s\n") % patchname)
676 676 err = 1
677 677 break
678 678
679 679 message = ph.message
680 680 if not message:
681 681 # The commit message should not be translated
682 682 message = "imported patch %s\n" % patchname
683 683 else:
684 684 if list:
685 685 # The commit message should not be translated
686 686 message.append("\nimported patch %s" % patchname)
687 687 message = '\n'.join(message)
688 688
689 689 if ph.haspatch:
690 690 (patcherr, files, fuzz) = self.patch(repo, pf)
691 691 if all_files is not None:
692 692 all_files.update(files)
693 693 patcherr = not patcherr
694 694 else:
695 695 self.ui.warn(_("patch %s is empty\n") % patchname)
696 696 patcherr, files, fuzz = 0, [], 0
697 697
698 698 if merge and files:
699 699 # Mark as removed/merged and update dirstate parent info
700 700 removed = []
701 701 merged = []
702 702 for f in files:
703 703 if os.path.lexists(repo.wjoin(f)):
704 704 merged.append(f)
705 705 else:
706 706 removed.append(f)
707 707 for f in removed:
708 708 repo.dirstate.remove(f)
709 709 for f in merged:
710 710 repo.dirstate.merge(f)
711 711 p1, p2 = repo.dirstate.parents()
712 712 repo.dirstate.setparents(p1, merge)
713 713
714 714 match = scmutil.matchfiles(repo, files or [])
715 715 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
716 716
717 717 if n is None:
718 718 raise util.Abort(_("repository commit failed"))
719 719
720 720 if update_status:
721 721 self.applied.append(statusentry(n, patchname))
722 722
723 723 if patcherr:
724 724 self.ui.warn(_("patch failed, rejects left in working dir\n"))
725 725 err = 2
726 726 break
727 727
728 728 if fuzz and strict:
729 729 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
730 730 err = 3
731 731 break
732 732 return (err, n)
733 733
734 734 def _cleanup(self, patches, numrevs, keep=False):
735 735 if not keep:
736 736 r = self.qrepo()
737 737 if r:
738 738 r[None].remove(patches, True)
739 739 else:
740 740 for p in patches:
741 741 os.unlink(self.join(p))
742 742
743 743 if numrevs:
744 744 qfinished = self.applied[:numrevs]
745 745 del self.applied[:numrevs]
746 746 self.applied_dirty = 1
747 747
748 748 unknown = []
749 749
750 750 for (i, p) in sorted([(self.find_series(p), p) for p in patches],
751 751 reverse=True):
752 752 if i is not None:
753 753 del self.full_series[i]
754 754 else:
755 755 unknown.append(p)
756 756
757 757 if unknown:
758 758 if numrevs:
759 759 rev = dict((entry.name, entry.node) for entry in qfinished)
760 760 for p in unknown:
761 761 msg = _('revision %s refers to unknown patches: %s\n')
762 762 self.ui.warn(msg % (short(rev[p]), p))
763 763 else:
764 764 msg = _('unknown patches: %s\n')
765 765 raise util.Abort(''.join(msg % p for p in unknown))
766 766
767 767 self.parse_series()
768 768 self.series_dirty = 1
769 769
770 770 def _revpatches(self, repo, revs):
771 771 firstrev = repo[self.applied[0].node].rev()
772 772 patches = []
773 773 for i, rev in enumerate(revs):
774 774
775 775 if rev < firstrev:
776 776 raise util.Abort(_('revision %d is not managed') % rev)
777 777
778 778 ctx = repo[rev]
779 779 base = self.applied[i].node
780 780 if ctx.node() != base:
781 781 msg = _('cannot delete revision %d above applied patches')
782 782 raise util.Abort(msg % rev)
783 783
784 784 patch = self.applied[i].name
785 785 for fmt in ('[mq]: %s', 'imported patch %s'):
786 786 if ctx.description() == fmt % patch:
787 787 msg = _('patch %s finalized without changeset message\n')
788 788 repo.ui.status(msg % patch)
789 789 break
790 790
791 791 patches.append(patch)
792 792 return patches
793 793
794 794 def finish(self, repo, revs):
795 795 patches = self._revpatches(repo, sorted(revs))
796 796 self._cleanup(patches, len(patches))
797 797
798 798 def delete(self, repo, patches, opts):
799 799 if not patches and not opts.get('rev'):
800 800 raise util.Abort(_('qdelete requires at least one revision or '
801 801 'patch name'))
802 802
803 803 realpatches = []
804 804 for patch in patches:
805 805 patch = self.lookup(patch, strict=True)
806 806 info = self.isapplied(patch)
807 807 if info:
808 808 raise util.Abort(_("cannot delete applied patch %s") % patch)
809 809 if patch not in self.series:
810 810 raise util.Abort(_("patch %s not in series file") % patch)
811 811 if patch not in realpatches:
812 812 realpatches.append(patch)
813 813
814 814 numrevs = 0
815 815 if opts.get('rev'):
816 816 if not self.applied:
817 817 raise util.Abort(_('no patches applied'))
818 818 revs = scmutil.revrange(repo, opts.get('rev'))
819 819 if len(revs) > 1 and revs[0] > revs[1]:
820 820 revs.reverse()
821 821 revpatches = self._revpatches(repo, revs)
822 822 realpatches += revpatches
823 823 numrevs = len(revpatches)
824 824
825 825 self._cleanup(realpatches, numrevs, opts.get('keep'))
826 826
827 827 def check_toppatch(self, repo):
828 828 if self.applied:
829 829 top = self.applied[-1].node
830 830 patch = self.applied[-1].name
831 831 pp = repo.dirstate.parents()
832 832 if top not in pp:
833 833 raise util.Abort(_("working directory revision is not qtip"))
834 834 return top, patch
835 835 return None, None
836 836
837 837 def check_substate(self, repo):
838 838 '''return list of subrepos at a different revision than substate.
839 839 Abort if any subrepos have uncommitted changes.'''
840 840 inclsubs = []
841 841 wctx = repo[None]
842 842 for s in wctx.substate:
843 843 if wctx.sub(s).dirty(True):
844 844 raise util.Abort(
845 845 _("uncommitted changes in subrepository %s") % s)
846 846 elif wctx.sub(s).dirty():
847 847 inclsubs.append(s)
848 848 return inclsubs
849 849
850 850 def localchangesfound(self, refresh=True):
851 851 if refresh:
852 852 raise util.Abort(_("local changes found, refresh first"))
853 853 else:
854 854 raise util.Abort(_("local changes found"))
855 855
856 856 def check_localchanges(self, repo, force=False, refresh=True):
857 857 m, a, r, d = repo.status()[:4]
858 858 if (m or a or r or d) and not force:
859 859 self.localchangesfound(refresh)
860 860 return m, a, r, d
861 861
862 862 _reserved = ('series', 'status', 'guards', '.', '..')
863 863 def check_reserved_name(self, name):
864 864 if name in self._reserved:
865 865 raise util.Abort(_('"%s" cannot be used as the name of a patch')
866 866 % name)
867 867 for prefix in ('.hg', '.mq'):
868 868 if name.startswith(prefix):
869 869 raise util.Abort(_('patch name cannot begin with "%s"')
870 870 % prefix)
871 871 for c in ('#', ':'):
872 872 if c in name:
873 873 raise util.Abort(_('"%s" cannot be used in the name of a patch')
874 874 % c)
875 875
876 876
877 877 def new(self, repo, patchfn, *pats, **opts):
878 878 """options:
879 879 msg: a string or a no-argument function returning a string
880 880 """
881 881 msg = opts.get('msg')
882 882 user = opts.get('user')
883 883 date = opts.get('date')
884 884 if date:
885 885 date = util.parsedate(date)
886 886 diffopts = self.diffopts({'git': opts.get('git')})
887 887 self.check_reserved_name(patchfn)
888 888 if os.path.exists(self.join(patchfn)):
889 889 if os.path.isdir(self.join(patchfn)):
890 890 raise util.Abort(_('"%s" already exists as a directory')
891 891 % patchfn)
892 892 else:
893 893 raise util.Abort(_('patch "%s" already exists') % patchfn)
894 894
895 895 inclsubs = self.check_substate(repo)
896 896 if inclsubs:
897 897 inclsubs.append('.hgsubstate')
898 898 if opts.get('include') or opts.get('exclude') or pats:
899 899 if inclsubs:
900 900 pats = list(pats or []) + inclsubs
901 901 match = scmutil.match(repo, pats, opts)
902 902 # detect missing files in pats
903 903 def badfn(f, msg):
904 904 if f != '.hgsubstate': # .hgsubstate is auto-created
905 905 raise util.Abort('%s: %s' % (f, msg))
906 906 match.bad = badfn
907 907 m, a, r, d = repo.status(match=match)[:4]
908 908 else:
909 909 m, a, r, d = self.check_localchanges(repo, force=True)
910 910 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
911 911 if len(repo[None].parents()) > 1:
912 912 raise util.Abort(_('cannot manage merge changesets'))
913 913 commitfiles = m + a + r
914 914 self.check_toppatch(repo)
915 915 insert = self.full_series_end()
916 916 wlock = repo.wlock()
917 917 try:
918 918 try:
919 919 # if patch file write fails, abort early
920 920 p = self.opener(patchfn, "w")
921 921 except IOError, e:
922 922 raise util.Abort(_('cannot write patch "%s": %s')
923 923 % (patchfn, e.strerror))
924 924 try:
925 925 if self.plainmode:
926 926 if user:
927 927 p.write("From: " + user + "\n")
928 928 if not date:
929 929 p.write("\n")
930 930 if date:
931 931 p.write("Date: %d %d\n\n" % date)
932 932 else:
933 933 p.write("# HG changeset patch\n")
934 934 p.write("# Parent "
935 935 + hex(repo[None].p1().node()) + "\n")
936 936 if user:
937 937 p.write("# User " + user + "\n")
938 938 if date:
939 939 p.write("# Date %s %s\n\n" % date)
940 940 if hasattr(msg, '__call__'):
941 941 msg = msg()
942 942 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
943 943 n = repo.commit(commitmsg, user, date, match=match, force=True)
944 944 if n is None:
945 945 raise util.Abort(_("repo commit failed"))
946 946 try:
947 947 self.full_series[insert:insert] = [patchfn]
948 948 self.applied.append(statusentry(n, patchfn))
949 949 self.parse_series()
950 950 self.series_dirty = 1
951 951 self.applied_dirty = 1
952 952 if msg:
953 953 msg = msg + "\n\n"
954 954 p.write(msg)
955 955 if commitfiles:
956 956 parent = self.qparents(repo, n)
957 957 chunks = patchmod.diff(repo, node1=parent, node2=n,
958 958 match=match, opts=diffopts)
959 959 for chunk in chunks:
960 960 p.write(chunk)
961 961 p.close()
962 962 wlock.release()
963 963 wlock = None
964 964 r = self.qrepo()
965 965 if r:
966 966 r[None].add([patchfn])
967 967 except:
968 968 repo.rollback()
969 969 raise
970 970 except Exception:
971 971 patchpath = self.join(patchfn)
972 972 try:
973 973 os.unlink(patchpath)
974 974 except:
975 975 self.ui.warn(_('error unlinking %s\n') % patchpath)
976 976 raise
977 977 self.removeundo(repo)
978 978 finally:
979 979 release(wlock)
980 980
981 981 def strip(self, repo, revs, update=True, backup="all", force=None):
982 982 wlock = lock = None
983 983 try:
984 984 wlock = repo.wlock()
985 985 lock = repo.lock()
986 986
987 987 if update:
988 988 self.check_localchanges(repo, force=force, refresh=False)
989 989 urev = self.qparents(repo, revs[0])
990 990 hg.clean(repo, urev)
991 991 repo.dirstate.write()
992 992
993 993 self.removeundo(repo)
994 994 for rev in revs:
995 995 repair.strip(self.ui, repo, rev, backup)
996 996 # strip may have unbundled a set of backed up revisions after
997 997 # the actual strip
998 998 self.removeundo(repo)
999 999 finally:
1000 1000 release(lock, wlock)
1001 1001
1002 1002 def isapplied(self, patch):
1003 1003 """returns (index, rev, patch)"""
1004 1004 for i, a in enumerate(self.applied):
1005 1005 if a.name == patch:
1006 1006 return (i, a.node, a.name)
1007 1007 return None
1008 1008
1009 1009 # if the exact patch name does not exist, we try a few
1010 1010 # variations. If strict is passed, we try only #1
1011 1011 #
1012 1012 # 1) a number to indicate an offset in the series file
1013 1013 # 2) a unique substring of the patch name was given
1014 1014 # 3) patchname[-+]num to indicate an offset in the series file
1015 1015 def lookup(self, patch, strict=False):
1016 1016 patch = patch and str(patch)
1017 1017
1018 1018 def partial_name(s):
1019 1019 if s in self.series:
1020 1020 return s
1021 1021 matches = [x for x in self.series if s in x]
1022 1022 if len(matches) > 1:
1023 1023 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1024 1024 for m in matches:
1025 1025 self.ui.warn(' %s\n' % m)
1026 1026 return None
1027 1027 if matches:
1028 1028 return matches[0]
1029 1029 if self.series and self.applied:
1030 1030 if s == 'qtip':
1031 1031 return self.series[self.series_end(True)-1]
1032 1032 if s == 'qbase':
1033 1033 return self.series[0]
1034 1034 return None
1035 1035
1036 1036 if patch is None:
1037 1037 return None
1038 1038 if patch in self.series:
1039 1039 return patch
1040 1040
1041 1041 if not os.path.isfile(self.join(patch)):
1042 1042 try:
1043 1043 sno = int(patch)
1044 1044 except (ValueError, OverflowError):
1045 1045 pass
1046 1046 else:
1047 1047 if -len(self.series) <= sno < len(self.series):
1048 1048 return self.series[sno]
1049 1049
1050 1050 if not strict:
1051 1051 res = partial_name(patch)
1052 1052 if res:
1053 1053 return res
1054 1054 minus = patch.rfind('-')
1055 1055 if minus >= 0:
1056 1056 res = partial_name(patch[:minus])
1057 1057 if res:
1058 1058 i = self.series.index(res)
1059 1059 try:
1060 1060 off = int(patch[minus + 1:] or 1)
1061 1061 except (ValueError, OverflowError):
1062 1062 pass
1063 1063 else:
1064 1064 if i - off >= 0:
1065 1065 return self.series[i - off]
1066 1066 plus = patch.rfind('+')
1067 1067 if plus >= 0:
1068 1068 res = partial_name(patch[:plus])
1069 1069 if res:
1070 1070 i = self.series.index(res)
1071 1071 try:
1072 1072 off = int(patch[plus + 1:] or 1)
1073 1073 except (ValueError, OverflowError):
1074 1074 pass
1075 1075 else:
1076 1076 if i + off < len(self.series):
1077 1077 return self.series[i + off]
1078 1078 raise util.Abort(_("patch %s not in series") % patch)
1079 1079
1080 1080 def push(self, repo, patch=None, force=False, list=False,
1081 1081 mergeq=None, all=False, move=False, exact=False):
1082 1082 diffopts = self.diffopts()
1083 1083 wlock = repo.wlock()
1084 1084 try:
1085 1085 heads = []
1086 1086 for b, ls in repo.branchmap().iteritems():
1087 1087 heads += ls
1088 1088 if not heads:
1089 1089 heads = [nullid]
1090 1090 if repo.dirstate.p1() not in heads and not exact:
1091 1091 self.ui.status(_("(working directory not at a head)\n"))
1092 1092
1093 1093 if not self.series:
1094 1094 self.ui.warn(_('no patches in series\n'))
1095 1095 return 0
1096 1096
1097 1097 patch = self.lookup(patch)
1098 1098 # Suppose our series file is: A B C and the current 'top'
1099 1099 # patch is B. qpush C should be performed (moving forward)
1100 1100 # qpush B is a NOP (no change) qpush A is an error (can't
1101 1101 # go backwards with qpush)
1102 1102 if patch:
1103 1103 info = self.isapplied(patch)
1104 1104 if info and info[0] >= len(self.applied) - 1:
1105 1105 self.ui.warn(
1106 1106 _('qpush: %s is already at the top\n') % patch)
1107 1107 return 0
1108 1108
1109 1109 pushable, reason = self.pushable(patch)
1110 1110 if pushable:
1111 1111 if self.series.index(patch) < self.series_end():
1112 1112 raise util.Abort(
1113 1113 _("cannot push to a previous patch: %s") % patch)
1114 1114 else:
1115 1115 if reason:
1116 1116 reason = _('guarded by %r') % reason
1117 1117 else:
1118 1118 reason = _('no matching guards')
1119 1119 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1120 1120 return 1
1121 1121 elif all:
1122 1122 patch = self.series[-1]
1123 1123 if self.isapplied(patch):
1124 1124 self.ui.warn(_('all patches are currently applied\n'))
1125 1125 return 0
1126 1126
1127 1127 # Following the above example, starting at 'top' of B:
1128 1128 # qpush should be performed (pushes C), but a subsequent
1129 1129 # qpush without an argument is an error (nothing to
1130 1130 # apply). This allows a loop of "...while hg qpush..." to
1131 1131 # work as it detects an error when done
1132 1132 start = self.series_end()
1133 1133 if start == len(self.series):
1134 1134 self.ui.warn(_('patch series already fully applied\n'))
1135 1135 return 1
1136 1136
1137 1137 if exact:
1138 1138 if move:
1139 1139 raise util.Abort(_("cannot use --exact and --move together"))
1140 1140 if self.applied:
1141 1141 raise util.Abort(_("cannot push --exact with applied patches"))
1142 1142 root = self.series[start]
1143 1143 target = patchheader(self.join(root), self.plainmode).parent
1144 1144 if not target:
1145 1145 raise util.Abort(_("%s does not have a parent recorded" % root))
1146 1146 if not repo[target] == repo['.']:
1147 1147 hg.update(repo, target)
1148 1148
1149 1149 if move:
1150 1150 if not patch:
1151 1151 raise util.Abort(_("please specify the patch to move"))
1152 1152 for i, rpn in enumerate(self.full_series[start:]):
1153 1153 # strip markers for patch guards
1154 1154 if self.guard_re.split(rpn, 1)[0] == patch:
1155 1155 break
1156 1156 index = start + i
1157 1157 assert index < len(self.full_series)
1158 1158 fullpatch = self.full_series[index]
1159 1159 del self.full_series[index]
1160 1160 self.full_series.insert(start, fullpatch)
1161 1161 self.parse_series()
1162 1162 self.series_dirty = 1
1163 1163
1164 1164 self.applied_dirty = 1
1165 1165 if start > 0:
1166 1166 self.check_toppatch(repo)
1167 1167 if not patch:
1168 1168 patch = self.series[start]
1169 1169 end = start + 1
1170 1170 else:
1171 1171 end = self.series.index(patch, start) + 1
1172 1172
1173 1173 s = self.series[start:end]
1174 1174
1175 1175 if not force:
1176 1176 mm, aa, rr, dd = repo.status()[:4]
1177 1177 wcfiles = set(mm + aa + rr + dd)
1178 1178 if wcfiles:
1179 1179 for patchname in s:
1180 1180 pf = os.path.join(self.path, patchname)
1181 1181 patchfiles = patchmod.changedfiles(self.ui, repo, pf)
1182 1182 if wcfiles.intersection(patchfiles):
1183 1183 self.localchangesfound(self.applied)
1184 1184 elif mergeq:
1185 1185 self.check_localchanges(refresh=self.applied)
1186 1186
1187 1187 all_files = set()
1188 1188 try:
1189 1189 if mergeq:
1190 1190 ret = self.mergepatch(repo, mergeq, s, diffopts)
1191 1191 else:
1192 1192 ret = self.apply(repo, s, list, all_files=all_files)
1193 1193 except:
1194 1194 self.ui.warn(_('cleaning up working directory...'))
1195 1195 node = repo.dirstate.p1()
1196 1196 hg.revert(repo, node, None)
1197 1197 # only remove unknown files that we know we touched or
1198 1198 # created while patching
1199 1199 for f in all_files:
1200 1200 if f not in repo.dirstate:
1201 1201 try:
1202 1202 util.unlinkpath(repo.wjoin(f))
1203 1203 except OSError, inst:
1204 1204 if inst.errno != errno.ENOENT:
1205 1205 raise
1206 1206 self.ui.warn(_('done\n'))
1207 1207 raise
1208 1208
1209 1209 if not self.applied:
1210 1210 return ret[0]
1211 1211 top = self.applied[-1].name
1212 1212 if ret[0] and ret[0] > 1:
1213 1213 msg = _("errors during apply, please fix and refresh %s\n")
1214 1214 self.ui.write(msg % top)
1215 1215 else:
1216 1216 self.ui.write(_("now at: %s\n") % top)
1217 1217 return ret[0]
1218 1218
1219 1219 finally:
1220 1220 wlock.release()
1221 1221
1222 1222 def pop(self, repo, patch=None, force=False, update=True, all=False):
1223 1223 wlock = repo.wlock()
1224 1224 try:
1225 1225 if patch:
1226 1226 # index, rev, patch
1227 1227 info = self.isapplied(patch)
1228 1228 if not info:
1229 1229 patch = self.lookup(patch)
1230 1230 info = self.isapplied(patch)
1231 1231 if not info:
1232 1232 raise util.Abort(_("patch %s is not applied") % patch)
1233 1233
1234 1234 if not self.applied:
1235 1235 # Allow qpop -a to work repeatedly,
1236 1236 # but not qpop without an argument
1237 1237 self.ui.warn(_("no patches applied\n"))
1238 1238 return not all
1239 1239
1240 1240 if all:
1241 1241 start = 0
1242 1242 elif patch:
1243 1243 start = info[0] + 1
1244 1244 else:
1245 1245 start = len(self.applied) - 1
1246 1246
1247 1247 if start >= len(self.applied):
1248 1248 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1249 1249 return
1250 1250
1251 1251 if not update:
1252 1252 parents = repo.dirstate.parents()
1253 1253 rr = [x.node for x in self.applied]
1254 1254 for p in parents:
1255 1255 if p in rr:
1256 1256 self.ui.warn(_("qpop: forcing dirstate update\n"))
1257 1257 update = True
1258 1258 else:
1259 1259 parents = [p.node() for p in repo[None].parents()]
1260 1260 needupdate = False
1261 1261 for entry in self.applied[start:]:
1262 1262 if entry.node in parents:
1263 1263 needupdate = True
1264 1264 break
1265 1265 update = needupdate
1266 1266
1267 1267 self.applied_dirty = 1
1268 1268 end = len(self.applied)
1269 1269 rev = self.applied[start].node
1270 1270 if update:
1271 1271 top = self.check_toppatch(repo)[0]
1272 1272
1273 1273 try:
1274 1274 heads = repo.changelog.heads(rev)
1275 1275 except error.LookupError:
1276 1276 node = short(rev)
1277 1277 raise util.Abort(_('trying to pop unknown node %s') % node)
1278 1278
1279 1279 if heads != [self.applied[-1].node]:
1280 1280 raise util.Abort(_("popping would remove a revision not "
1281 1281 "managed by this patch queue"))
1282 1282
1283 1283 # we know there are no local changes, so we can make a simplified
1284 1284 # form of hg.update.
1285 1285 if update:
1286 1286 qp = self.qparents(repo, rev)
1287 1287 ctx = repo[qp]
1288 1288 m, a, r, d = repo.status(qp, top)[:4]
1289 1289 parentfiles = set(m + a + r + d)
1290 1290 if not force and parentfiles:
1291 1291 mm, aa, rr, dd = repo.status()[:4]
1292 1292 wcfiles = set(mm + aa + rr + dd)
1293 1293 if wcfiles.intersection(parentfiles):
1294 1294 self.localchangesfound()
1295 1295 if d:
1296 1296 raise util.Abort(_("deletions found between repo revs"))
1297 1297 for f in a:
1298 1298 try:
1299 1299 util.unlinkpath(repo.wjoin(f))
1300 1300 except OSError, e:
1301 1301 if e.errno != errno.ENOENT:
1302 1302 raise
1303 1303 repo.dirstate.forget(f)
1304 1304 for f in m + r:
1305 1305 fctx = ctx[f]
1306 1306 repo.wwrite(f, fctx.data(), fctx.flags())
1307 1307 repo.dirstate.normal(f)
1308 1308 repo.dirstate.setparents(qp, nullid)
1309 1309 for patch in reversed(self.applied[start:end]):
1310 1310 self.ui.status(_("popping %s\n") % patch.name)
1311 1311 del self.applied[start:end]
1312 1312 self.strip(repo, [rev], update=False, backup='strip')
1313 1313 if self.applied:
1314 1314 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1315 1315 else:
1316 1316 self.ui.write(_("patch queue now empty\n"))
1317 1317 finally:
1318 1318 wlock.release()
1319 1319
1320 1320 def diff(self, repo, pats, opts):
1321 1321 top, patch = self.check_toppatch(repo)
1322 1322 if not top:
1323 1323 self.ui.write(_("no patches applied\n"))
1324 1324 return
1325 1325 qp = self.qparents(repo, top)
1326 1326 if opts.get('reverse'):
1327 1327 node1, node2 = None, qp
1328 1328 else:
1329 1329 node1, node2 = qp, None
1330 1330 diffopts = self.diffopts(opts, patch)
1331 1331 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1332 1332
1333 1333 def refresh(self, repo, pats=None, **opts):
1334 1334 if not self.applied:
1335 1335 self.ui.write(_("no patches applied\n"))
1336 1336 return 1
1337 1337 msg = opts.get('msg', '').rstrip()
1338 1338 newuser = opts.get('user')
1339 1339 newdate = opts.get('date')
1340 1340 if newdate:
1341 1341 newdate = '%d %d' % util.parsedate(newdate)
1342 1342 wlock = repo.wlock()
1343 1343
1344 1344 try:
1345 1345 self.check_toppatch(repo)
1346 1346 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1347 1347 if repo.changelog.heads(top) != [top]:
1348 1348 raise util.Abort(_("cannot refresh a revision with children"))
1349 1349
1350 1350 inclsubs = self.check_substate(repo)
1351 1351
1352 1352 cparents = repo.changelog.parents(top)
1353 1353 patchparent = self.qparents(repo, top)
1354 1354 ph = patchheader(self.join(patchfn), self.plainmode)
1355 1355 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1356 1356 if msg:
1357 1357 ph.setmessage(msg)
1358 1358 if newuser:
1359 1359 ph.setuser(newuser)
1360 1360 if newdate:
1361 1361 ph.setdate(newdate)
1362 1362 ph.setparent(hex(patchparent))
1363 1363
1364 1364 # only commit new patch when write is complete
1365 1365 patchf = self.opener(patchfn, 'w', atomictemp=True)
1366 1366
1367 1367 comments = str(ph)
1368 1368 if comments:
1369 1369 patchf.write(comments)
1370 1370
1371 1371 # update the dirstate in place, strip off the qtip commit
1372 1372 # and then commit.
1373 1373 #
1374 1374 # this should really read:
1375 1375 # mm, dd, aa = repo.status(top, patchparent)[:3]
1376 1376 # but we do it backwards to take advantage of manifest/chlog
1377 1377 # caching against the next repo.status call
1378 1378 mm, aa, dd = repo.status(patchparent, top)[:3]
1379 1379 changes = repo.changelog.read(top)
1380 1380 man = repo.manifest.read(changes[0])
1381 1381 aaa = aa[:]
1382 1382 matchfn = scmutil.match(repo, pats, opts)
1383 1383 # in short mode, we only diff the files included in the
1384 1384 # patch already plus specified files
1385 1385 if opts.get('short'):
1386 1386 # if amending a patch, we start with existing
1387 1387 # files plus specified files - unfiltered
1388 1388 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1389 1389 # filter with inc/exl options
1390 1390 matchfn = scmutil.match(repo, opts=opts)
1391 1391 else:
1392 1392 match = scmutil.matchall(repo)
1393 1393 m, a, r, d = repo.status(match=match)[:4]
1394 1394 mm = set(mm)
1395 1395 aa = set(aa)
1396 1396 dd = set(dd)
1397 1397
1398 1398 # we might end up with files that were added between
1399 1399 # qtip and the dirstate parent, but then changed in the
1400 1400 # local dirstate. in this case, we want them to only
1401 1401 # show up in the added section
1402 1402 for x in m:
1403 1403 if x not in aa:
1404 1404 mm.add(x)
1405 1405 # we might end up with files added by the local dirstate that
1406 1406 # were deleted by the patch. In this case, they should only
1407 1407 # show up in the changed section.
1408 1408 for x in a:
1409 1409 if x in dd:
1410 1410 dd.remove(x)
1411 1411 mm.add(x)
1412 1412 else:
1413 1413 aa.add(x)
1414 1414 # make sure any files deleted in the local dirstate
1415 1415 # are not in the add or change column of the patch
1416 1416 forget = []
1417 1417 for x in d + r:
1418 1418 if x in aa:
1419 1419 aa.remove(x)
1420 1420 forget.append(x)
1421 1421 continue
1422 1422 else:
1423 1423 mm.discard(x)
1424 1424 dd.add(x)
1425 1425
1426 1426 m = list(mm)
1427 1427 r = list(dd)
1428 1428 a = list(aa)
1429 1429 c = [filter(matchfn, l) for l in (m, a, r)]
1430 1430 match = scmutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1431 1431 chunks = patchmod.diff(repo, patchparent, match=match,
1432 1432 changes=c, opts=diffopts)
1433 1433 for chunk in chunks:
1434 1434 patchf.write(chunk)
1435 1435
1436 1436 try:
1437 1437 if diffopts.git or diffopts.upgrade:
1438 1438 copies = {}
1439 1439 for dst in a:
1440 1440 src = repo.dirstate.copied(dst)
1441 1441 # during qfold, the source file for copies may
1442 1442 # be removed. Treat this as a simple add.
1443 1443 if src is not None and src in repo.dirstate:
1444 1444 copies.setdefault(src, []).append(dst)
1445 1445 repo.dirstate.add(dst)
1446 1446 # remember the copies between patchparent and qtip
1447 1447 for dst in aaa:
1448 1448 f = repo.file(dst)
1449 1449 src = f.renamed(man[dst])
1450 1450 if src:
1451 1451 copies.setdefault(src[0], []).extend(
1452 1452 copies.get(dst, []))
1453 1453 if dst in a:
1454 1454 copies[src[0]].append(dst)
1455 1455 # we can't copy a file created by the patch itself
1456 1456 if dst in copies:
1457 1457 del copies[dst]
1458 1458 for src, dsts in copies.iteritems():
1459 1459 for dst in dsts:
1460 1460 repo.dirstate.copy(src, dst)
1461 1461 else:
1462 1462 for dst in a:
1463 1463 repo.dirstate.add(dst)
1464 1464 # Drop useless copy information
1465 1465 for f in list(repo.dirstate.copies()):
1466 1466 repo.dirstate.copy(None, f)
1467 1467 for f in r:
1468 1468 repo.dirstate.remove(f)
1469 1469 # if the patch excludes a modified file, mark that
1470 1470 # file with mtime=0 so status can see it.
1471 1471 mm = []
1472 1472 for i in xrange(len(m)-1, -1, -1):
1473 1473 if not matchfn(m[i]):
1474 1474 mm.append(m[i])
1475 1475 del m[i]
1476 1476 for f in m:
1477 1477 repo.dirstate.normal(f)
1478 1478 for f in mm:
1479 1479 repo.dirstate.normallookup(f)
1480 1480 for f in forget:
1481 1481 repo.dirstate.forget(f)
1482 1482
1483 1483 if not msg:
1484 1484 if not ph.message:
1485 1485 message = "[mq]: %s\n" % patchfn
1486 1486 else:
1487 1487 message = "\n".join(ph.message)
1488 1488 else:
1489 1489 message = msg
1490 1490
1491 1491 user = ph.user or changes[1]
1492 1492
1493 1493 # assumes strip can roll itself back if interrupted
1494 1494 repo.dirstate.setparents(*cparents)
1495 1495 self.applied.pop()
1496 1496 self.applied_dirty = 1
1497 1497 self.strip(repo, [top], update=False,
1498 1498 backup='strip')
1499 1499 except:
1500 1500 repo.dirstate.invalidate()
1501 1501 raise
1502 1502
1503 1503 try:
1504 1504 # might be nice to attempt to roll back strip after this
1505 1505 n = repo.commit(message, user, ph.date, match=match,
1506 1506 force=True)
1507 1507 # only write patch after a successful commit
1508 1508 patchf.rename()
1509 1509 self.applied.append(statusentry(n, patchfn))
1510 1510 except:
1511 1511 ctx = repo[cparents[0]]
1512 1512 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1513 1513 self.save_dirty()
1514 1514 self.ui.warn(_('refresh interrupted while patch was popped! '
1515 1515 '(revert --all, qpush to recover)\n'))
1516 1516 raise
1517 1517 finally:
1518 1518 wlock.release()
1519 1519 self.removeundo(repo)
1520 1520
1521 1521 def init(self, repo, create=False):
1522 1522 if not create and os.path.isdir(self.path):
1523 1523 raise util.Abort(_("patch queue directory already exists"))
1524 1524 try:
1525 1525 os.mkdir(self.path)
1526 1526 except OSError, inst:
1527 1527 if inst.errno != errno.EEXIST or not create:
1528 1528 raise
1529 1529 if create:
1530 1530 return self.qrepo(create=True)
1531 1531
1532 1532 def unapplied(self, repo, patch=None):
1533 1533 if patch and patch not in self.series:
1534 1534 raise util.Abort(_("patch %s is not in series file") % patch)
1535 1535 if not patch:
1536 1536 start = self.series_end()
1537 1537 else:
1538 1538 start = self.series.index(patch) + 1
1539 1539 unapplied = []
1540 1540 for i in xrange(start, len(self.series)):
1541 1541 pushable, reason = self.pushable(i)
1542 1542 if pushable:
1543 1543 unapplied.append((i, self.series[i]))
1544 1544 self.explain_pushable(i)
1545 1545 return unapplied
1546 1546
1547 1547 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1548 1548 summary=False):
1549 1549 def displayname(pfx, patchname, state):
1550 1550 if pfx:
1551 1551 self.ui.write(pfx)
1552 1552 if summary:
1553 1553 ph = patchheader(self.join(patchname), self.plainmode)
1554 1554 msg = ph.message and ph.message[0] or ''
1555 1555 if self.ui.formatted():
1556 1556 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1557 1557 if width > 0:
1558 1558 msg = util.ellipsis(msg, width)
1559 1559 else:
1560 1560 msg = ''
1561 1561 self.ui.write(patchname, label='qseries.' + state)
1562 1562 self.ui.write(': ')
1563 1563 self.ui.write(msg, label='qseries.message.' + state)
1564 1564 else:
1565 1565 self.ui.write(patchname, label='qseries.' + state)
1566 1566 self.ui.write('\n')
1567 1567
1568 1568 applied = set([p.name for p in self.applied])
1569 1569 if length is None:
1570 1570 length = len(self.series) - start
1571 1571 if not missing:
1572 1572 if self.ui.verbose:
1573 1573 idxwidth = len(str(start + length - 1))
1574 1574 for i in xrange(start, start + length):
1575 1575 patch = self.series[i]
1576 1576 if patch in applied:
1577 1577 char, state = 'A', 'applied'
1578 1578 elif self.pushable(i)[0]:
1579 1579 char, state = 'U', 'unapplied'
1580 1580 else:
1581 1581 char, state = 'G', 'guarded'
1582 1582 pfx = ''
1583 1583 if self.ui.verbose:
1584 1584 pfx = '%*d %s ' % (idxwidth, i, char)
1585 1585 elif status and status != char:
1586 1586 continue
1587 1587 displayname(pfx, patch, state)
1588 1588 else:
1589 1589 msng_list = []
1590 1590 for root, dirs, files in os.walk(self.path):
1591 1591 d = root[len(self.path) + 1:]
1592 1592 for f in files:
1593 1593 fl = os.path.join(d, f)
1594 1594 if (fl not in self.series and
1595 1595 fl not in (self.status_path, self.series_path,
1596 1596 self.guards_path)
1597 1597 and not fl.startswith('.')):
1598 1598 msng_list.append(fl)
1599 1599 for x in sorted(msng_list):
1600 1600 pfx = self.ui.verbose and ('D ') or ''
1601 1601 displayname(pfx, x, 'missing')
1602 1602
1603 1603 def issaveline(self, l):
1604 1604 if l.name == '.hg.patches.save.line':
1605 1605 return True
1606 1606
1607 1607 def qrepo(self, create=False):
1608 1608 ui = self.ui.copy()
1609 1609 ui.setconfig('paths', 'default', '', overlay=False)
1610 1610 ui.setconfig('paths', 'default-push', '', overlay=False)
1611 1611 if create or os.path.isdir(self.join(".hg")):
1612 1612 return hg.repository(ui, path=self.path, create=create)
1613 1613
1614 1614 def restore(self, repo, rev, delete=None, qupdate=None):
1615 1615 desc = repo[rev].description().strip()
1616 1616 lines = desc.splitlines()
1617 1617 i = 0
1618 1618 datastart = None
1619 1619 series = []
1620 1620 applied = []
1621 1621 qpp = None
1622 1622 for i, line in enumerate(lines):
1623 1623 if line == 'Patch Data:':
1624 1624 datastart = i + 1
1625 1625 elif line.startswith('Dirstate:'):
1626 1626 l = line.rstrip()
1627 1627 l = l[10:].split(' ')
1628 1628 qpp = [bin(x) for x in l]
1629 1629 elif datastart is not None:
1630 1630 l = line.rstrip()
1631 1631 n, name = l.split(':', 1)
1632 1632 if n:
1633 1633 applied.append(statusentry(bin(n), name))
1634 1634 else:
1635 1635 series.append(l)
1636 1636 if datastart is None:
1637 1637 self.ui.warn(_("No saved patch data found\n"))
1638 1638 return 1
1639 1639 self.ui.warn(_("restoring status: %s\n") % lines[0])
1640 1640 self.full_series = series
1641 1641 self.applied = applied
1642 1642 self.parse_series()
1643 1643 self.series_dirty = 1
1644 1644 self.applied_dirty = 1
1645 1645 heads = repo.changelog.heads()
1646 1646 if delete:
1647 1647 if rev not in heads:
1648 1648 self.ui.warn(_("save entry has children, leaving it alone\n"))
1649 1649 else:
1650 1650 self.ui.warn(_("removing save entry %s\n") % short(rev))
1651 1651 pp = repo.dirstate.parents()
1652 1652 if rev in pp:
1653 1653 update = True
1654 1654 else:
1655 1655 update = False
1656 1656 self.strip(repo, [rev], update=update, backup='strip')
1657 1657 if qpp:
1658 1658 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1659 1659 (short(qpp[0]), short(qpp[1])))
1660 1660 if qupdate:
1661 1661 self.ui.status(_("updating queue directory\n"))
1662 1662 r = self.qrepo()
1663 1663 if not r:
1664 1664 self.ui.warn(_("Unable to load queue repository\n"))
1665 1665 return 1
1666 1666 hg.clean(r, qpp[0])
1667 1667
1668 1668 def save(self, repo, msg=None):
1669 1669 if not self.applied:
1670 1670 self.ui.warn(_("save: no patches applied, exiting\n"))
1671 1671 return 1
1672 1672 if self.issaveline(self.applied[-1]):
1673 1673 self.ui.warn(_("status is already saved\n"))
1674 1674 return 1
1675 1675
1676 1676 if not msg:
1677 1677 msg = _("hg patches saved state")
1678 1678 else:
1679 1679 msg = "hg patches: " + msg.rstrip('\r\n')
1680 1680 r = self.qrepo()
1681 1681 if r:
1682 1682 pp = r.dirstate.parents()
1683 1683 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1684 1684 msg += "\n\nPatch Data:\n"
1685 1685 msg += ''.join('%s\n' % x for x in self.applied)
1686 1686 msg += ''.join(':%s\n' % x for x in self.full_series)
1687 1687 n = repo.commit(msg, force=True)
1688 1688 if not n:
1689 1689 self.ui.warn(_("repo commit failed\n"))
1690 1690 return 1
1691 1691 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1692 1692 self.applied_dirty = 1
1693 1693 self.removeundo(repo)
1694 1694
1695 1695 def full_series_end(self):
1696 1696 if self.applied:
1697 1697 p = self.applied[-1].name
1698 1698 end = self.find_series(p)
1699 1699 if end is None:
1700 1700 return len(self.full_series)
1701 1701 return end + 1
1702 1702 return 0
1703 1703
1704 1704 def series_end(self, all_patches=False):
1705 1705 """If all_patches is False, return the index of the next pushable patch
1706 1706 in the series, or the series length. If all_patches is True, return the
1707 1707 index of the first patch past the last applied one.
1708 1708 """
1709 1709 end = 0
1710 1710 def next(start):
1711 1711 if all_patches or start >= len(self.series):
1712 1712 return start
1713 1713 for i in xrange(start, len(self.series)):
1714 1714 p, reason = self.pushable(i)
1715 1715 if p:
1716 1716 break
1717 1717 self.explain_pushable(i)
1718 1718 return i
1719 1719 if self.applied:
1720 1720 p = self.applied[-1].name
1721 1721 try:
1722 1722 end = self.series.index(p)
1723 1723 except ValueError:
1724 1724 return 0
1725 1725 return next(end + 1)
1726 1726 return next(end)
1727 1727
1728 1728 def appliedname(self, index):
1729 1729 pname = self.applied[index].name
1730 1730 if not self.ui.verbose:
1731 1731 p = pname
1732 1732 else:
1733 1733 p = str(self.series.index(pname)) + " " + pname
1734 1734 return p
1735 1735
1736 1736 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1737 1737 force=None, git=False):
1738 1738 def checkseries(patchname):
1739 1739 if patchname in self.series:
1740 1740 raise util.Abort(_('patch %s is already in the series file')
1741 1741 % patchname)
1742 1742 def checkfile(patchname):
1743 1743 if not force and os.path.exists(self.join(patchname)):
1744 1744 raise util.Abort(_('patch "%s" already exists')
1745 1745 % patchname)
1746 1746
1747 1747 if rev:
1748 1748 if files:
1749 1749 raise util.Abort(_('option "-r" not valid when importing '
1750 1750 'files'))
1751 1751 rev = scmutil.revrange(repo, rev)
1752 1752 rev.sort(reverse=True)
1753 1753 if (len(files) > 1 or len(rev) > 1) and patchname:
1754 1754 raise util.Abort(_('option "-n" not valid when importing multiple '
1755 1755 'patches'))
1756 1756 if rev:
1757 1757 # If mq patches are applied, we can only import revisions
1758 1758 # that form a linear path to qbase.
1759 1759 # Otherwise, they should form a linear path to a head.
1760 1760 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1761 1761 if len(heads) > 1:
1762 1762 raise util.Abort(_('revision %d is the root of more than one '
1763 1763 'branch') % rev[-1])
1764 1764 if self.applied:
1765 1765 base = repo.changelog.node(rev[0])
1766 1766 if base in [n.node for n in self.applied]:
1767 1767 raise util.Abort(_('revision %d is already managed')
1768 1768 % rev[0])
1769 1769 if heads != [self.applied[-1].node]:
1770 1770 raise util.Abort(_('revision %d is not the parent of '
1771 1771 'the queue') % rev[0])
1772 1772 base = repo.changelog.rev(self.applied[0].node)
1773 1773 lastparent = repo.changelog.parentrevs(base)[0]
1774 1774 else:
1775 1775 if heads != [repo.changelog.node(rev[0])]:
1776 1776 raise util.Abort(_('revision %d has unmanaged children')
1777 1777 % rev[0])
1778 1778 lastparent = None
1779 1779
1780 1780 diffopts = self.diffopts({'git': git})
1781 1781 for r in rev:
1782 1782 p1, p2 = repo.changelog.parentrevs(r)
1783 1783 n = repo.changelog.node(r)
1784 1784 if p2 != nullrev:
1785 1785 raise util.Abort(_('cannot import merge revision %d') % r)
1786 1786 if lastparent and lastparent != r:
1787 1787 raise util.Abort(_('revision %d is not the parent of %d')
1788 1788 % (r, lastparent))
1789 1789 lastparent = p1
1790 1790
1791 1791 if not patchname:
1792 1792 patchname = normname('%d.diff' % r)
1793 1793 self.check_reserved_name(patchname)
1794 1794 checkseries(patchname)
1795 1795 checkfile(patchname)
1796 1796 self.full_series.insert(0, patchname)
1797 1797
1798 1798 patchf = self.opener(patchname, "w")
1799 1799 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1800 1800 patchf.close()
1801 1801
1802 1802 se = statusentry(n, patchname)
1803 1803 self.applied.insert(0, se)
1804 1804
1805 1805 self.added.append(patchname)
1806 1806 patchname = None
1807 1807 self.parse_series()
1808 1808 self.applied_dirty = 1
1809 1809 self.series_dirty = True
1810 1810
1811 1811 for i, filename in enumerate(files):
1812 1812 if existing:
1813 1813 if filename == '-':
1814 1814 raise util.Abort(_('-e is incompatible with import from -'))
1815 1815 filename = normname(filename)
1816 1816 self.check_reserved_name(filename)
1817 1817 originpath = self.join(filename)
1818 1818 if not os.path.isfile(originpath):
1819 1819 raise util.Abort(_("patch %s does not exist") % filename)
1820 1820
1821 1821 if patchname:
1822 1822 self.check_reserved_name(patchname)
1823 1823 checkfile(patchname)
1824 1824
1825 1825 self.ui.write(_('renaming %s to %s\n')
1826 1826 % (filename, patchname))
1827 1827 util.rename(originpath, self.join(patchname))
1828 1828 else:
1829 1829 patchname = filename
1830 1830
1831 1831 else:
1832 1832 try:
1833 1833 if filename == '-':
1834 1834 if not patchname:
1835 1835 raise util.Abort(
1836 1836 _('need --name to import a patch from -'))
1837 1837 text = sys.stdin.read()
1838 1838 else:
1839 1839 fp = url.open(self.ui, filename)
1840 1840 text = fp.read()
1841 1841 fp.close()
1842 1842 except (OSError, IOError):
1843 1843 raise util.Abort(_("unable to read file %s") % filename)
1844 1844 if not patchname:
1845 1845 patchname = normname(os.path.basename(filename))
1846 1846 self.check_reserved_name(patchname)
1847 1847 checkfile(patchname)
1848 1848 patchf = self.opener(patchname, "w")
1849 1849 patchf.write(text)
1850 1850 patchf.close()
1851 1851 if not force:
1852 1852 checkseries(patchname)
1853 1853 if patchname not in self.series:
1854 1854 index = self.full_series_end() + i
1855 1855 self.full_series[index:index] = [patchname]
1856 1856 self.parse_series()
1857 1857 self.series_dirty = True
1858 1858 self.ui.warn(_("adding %s to series file\n") % patchname)
1859 1859 self.added.append(patchname)
1860 1860 patchname = None
1861 1861
1862 1862 self.removeundo(repo)
1863 1863
1864 1864 @command("qdelete|qremove|qrm",
1865 1865 [('k', 'keep', None, _('keep patch file')),
1866 1866 ('r', 'rev', [],
1867 1867 _('stop managing a revision (DEPRECATED)'), _('REV'))],
1868 1868 _('hg qdelete [-k] [PATCH]...'))
1869 1869 def delete(ui, repo, *patches, **opts):
1870 1870 """remove patches from queue
1871 1871
1872 1872 The patches must not be applied, and at least one patch is required. With
1873 1873 -k/--keep, the patch files are preserved in the patch directory.
1874 1874
1875 1875 To stop managing a patch and move it into permanent history,
1876 1876 use the :hg:`qfinish` command."""
1877 1877 q = repo.mq
1878 1878 q.delete(repo, patches, opts)
1879 1879 q.save_dirty()
1880 1880 return 0
1881 1881
1882 1882 @command("qapplied",
1883 1883 [('1', 'last', None, _('show only the last patch'))
1884 1884 ] + seriesopts,
1885 1885 _('hg qapplied [-1] [-s] [PATCH]'))
1886 1886 def applied(ui, repo, patch=None, **opts):
1887 1887 """print the patches already applied
1888 1888
1889 1889 Returns 0 on success."""
1890 1890
1891 1891 q = repo.mq
1892 1892
1893 1893 if patch:
1894 1894 if patch not in q.series:
1895 1895 raise util.Abort(_("patch %s is not in series file") % patch)
1896 1896 end = q.series.index(patch) + 1
1897 1897 else:
1898 1898 end = q.series_end(True)
1899 1899
1900 1900 if opts.get('last') and not end:
1901 1901 ui.write(_("no patches applied\n"))
1902 1902 return 1
1903 1903 elif opts.get('last') and end == 1:
1904 1904 ui.write(_("only one patch applied\n"))
1905 1905 return 1
1906 1906 elif opts.get('last'):
1907 1907 start = end - 2
1908 1908 end = 1
1909 1909 else:
1910 1910 start = 0
1911 1911
1912 1912 q.qseries(repo, length=end, start=start, status='A',
1913 1913 summary=opts.get('summary'))
1914 1914
1915 1915
1916 1916 @command("qunapplied",
1917 1917 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
1918 1918 _('hg qunapplied [-1] [-s] [PATCH]'))
1919 1919 def unapplied(ui, repo, patch=None, **opts):
1920 1920 """print the patches not yet applied
1921 1921
1922 1922 Returns 0 on success."""
1923 1923
1924 1924 q = repo.mq
1925 1925 if patch:
1926 1926 if patch not in q.series:
1927 1927 raise util.Abort(_("patch %s is not in series file") % patch)
1928 1928 start = q.series.index(patch) + 1
1929 1929 else:
1930 1930 start = q.series_end(True)
1931 1931
1932 1932 if start == len(q.series) and opts.get('first'):
1933 1933 ui.write(_("all patches applied\n"))
1934 1934 return 1
1935 1935
1936 1936 length = opts.get('first') and 1 or None
1937 1937 q.qseries(repo, start=start, length=length, status='U',
1938 1938 summary=opts.get('summary'))
1939 1939
1940 1940 @command("qimport",
1941 1941 [('e', 'existing', None, _('import file in patch directory')),
1942 1942 ('n', 'name', '',
1943 1943 _('name of patch file'), _('NAME')),
1944 1944 ('f', 'force', None, _('overwrite existing files')),
1945 1945 ('r', 'rev', [],
1946 1946 _('place existing revisions under mq control'), _('REV')),
1947 1947 ('g', 'git', None, _('use git extended diff format')),
1948 1948 ('P', 'push', None, _('qpush after importing'))],
1949 1949 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...'))
1950 1950 def qimport(ui, repo, *filename, **opts):
1951 1951 """import a patch
1952 1952
1953 1953 The patch is inserted into the series after the last applied
1954 1954 patch. If no patches have been applied, qimport prepends the patch
1955 1955 to the series.
1956 1956
1957 1957 The patch will have the same name as its source file unless you
1958 1958 give it a new one with -n/--name.
1959 1959
1960 1960 You can register an existing patch inside the patch directory with
1961 1961 the -e/--existing flag.
1962 1962
1963 1963 With -f/--force, an existing patch of the same name will be
1964 1964 overwritten.
1965 1965
1966 1966 An existing changeset may be placed under mq control with -r/--rev
1967 1967 (e.g. qimport --rev tip -n patch will place tip under mq control).
1968 1968 With -g/--git, patches imported with --rev will use the git diff
1969 1969 format. See the diffs help topic for information on why this is
1970 1970 important for preserving rename/copy information and permission
1971 1971 changes. Use :hg:`qfinish` to remove changesets from mq control.
1972 1972
1973 1973 To import a patch from standard input, pass - as the patch file.
1974 1974 When importing from standard input, a patch name must be specified
1975 1975 using the --name flag.
1976 1976
1977 1977 To import an existing patch while renaming it::
1978 1978
1979 1979 hg qimport -e existing-patch -n new-name
1980 1980
1981 1981 Returns 0 if import succeeded.
1982 1982 """
1983 1983 q = repo.mq
1984 1984 try:
1985 1985 q.qimport(repo, filename, patchname=opts.get('name'),
1986 1986 existing=opts.get('existing'), force=opts.get('force'),
1987 1987 rev=opts.get('rev'), git=opts.get('git'))
1988 1988 finally:
1989 1989 q.save_dirty()
1990 1990
1991 1991 if opts.get('push') and not opts.get('rev'):
1992 1992 return q.push(repo, None)
1993 1993 return 0
1994 1994
1995 1995 def qinit(ui, repo, create):
1996 1996 """initialize a new queue repository
1997 1997
1998 1998 This command also creates a series file for ordering patches, and
1999 1999 an mq-specific .hgignore file in the queue repository, to exclude
2000 2000 the status and guards files (these contain mostly transient state).
2001 2001
2002 2002 Returns 0 if initialization succeeded."""
2003 2003 q = repo.mq
2004 2004 r = q.init(repo, create)
2005 2005 q.save_dirty()
2006 2006 if r:
2007 2007 if not os.path.exists(r.wjoin('.hgignore')):
2008 2008 fp = r.wopener('.hgignore', 'w')
2009 2009 fp.write('^\\.hg\n')
2010 2010 fp.write('^\\.mq\n')
2011 2011 fp.write('syntax: glob\n')
2012 2012 fp.write('status\n')
2013 2013 fp.write('guards\n')
2014 2014 fp.close()
2015 2015 if not os.path.exists(r.wjoin('series')):
2016 2016 r.wopener('series', 'w').close()
2017 2017 r[None].add(['.hgignore', 'series'])
2018 2018 commands.add(ui, r)
2019 2019 return 0
2020 2020
2021 2021 @command("^qinit",
2022 2022 [('c', 'create-repo', None, _('create queue repository'))],
2023 2023 _('hg qinit [-c]'))
2024 2024 def init(ui, repo, **opts):
2025 2025 """init a new queue repository (DEPRECATED)
2026 2026
2027 2027 The queue repository is unversioned by default. If
2028 2028 -c/--create-repo is specified, qinit will create a separate nested
2029 2029 repository for patches (qinit -c may also be run later to convert
2030 2030 an unversioned patch repository into a versioned one). You can use
2031 2031 qcommit to commit changes to this queue repository.
2032 2032
2033 2033 This command is deprecated. Without -c, it's implied by other relevant
2034 2034 commands. With -c, use :hg:`init --mq` instead."""
2035 2035 return qinit(ui, repo, create=opts.get('create_repo'))
2036 2036
2037 2037 @command("qclone",
2038 2038 [('', 'pull', None, _('use pull protocol to copy metadata')),
2039 2039 ('U', 'noupdate', None, _('do not update the new working directories')),
2040 2040 ('', 'uncompressed', None,
2041 2041 _('use uncompressed transfer (fast over LAN)')),
2042 2042 ('p', 'patches', '',
2043 2043 _('location of source patch repository'), _('REPO')),
2044 2044 ] + commands.remoteopts,
2045 2045 _('hg qclone [OPTION]... SOURCE [DEST]'))
2046 2046 def clone(ui, source, dest=None, **opts):
2047 2047 '''clone main and patch repository at same time
2048 2048
2049 2049 If source is local, destination will have no patches applied. If
2050 2050 source is remote, this command can not check if patches are
2051 2051 applied in source, so cannot guarantee that patches are not
2052 2052 applied in destination. If you clone remote repository, be sure
2053 2053 before that it has no patches applied.
2054 2054
2055 2055 Source patch repository is looked for in <src>/.hg/patches by
2056 2056 default. Use -p <url> to change.
2057 2057
2058 2058 The patch directory must be a nested Mercurial repository, as
2059 2059 would be created by :hg:`init --mq`.
2060 2060
2061 2061 Return 0 on success.
2062 2062 '''
2063 2063 def patchdir(repo):
2064 2064 url = repo.url()
2065 2065 if url.endswith('/'):
2066 2066 url = url[:-1]
2067 2067 return url + '/.hg/patches'
2068 2068 if dest is None:
2069 2069 dest = hg.defaultdest(source)
2070 2070 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
2071 2071 if opts.get('patches'):
2072 2072 patchespath = ui.expandpath(opts.get('patches'))
2073 2073 else:
2074 2074 patchespath = patchdir(sr)
2075 2075 try:
2076 2076 hg.repository(ui, patchespath)
2077 2077 except error.RepoError:
2078 2078 raise util.Abort(_('versioned patch repository not found'
2079 2079 ' (see init --mq)'))
2080 2080 qbase, destrev = None, None
2081 2081 if sr.local():
2082 2082 if sr.mq.applied:
2083 2083 qbase = sr.mq.applied[0].node
2084 2084 if not hg.islocal(dest):
2085 2085 heads = set(sr.heads())
2086 2086 destrev = list(heads.difference(sr.heads(qbase)))
2087 2087 destrev.append(sr.changelog.parents(qbase)[0])
2088 2088 elif sr.capable('lookup'):
2089 2089 try:
2090 2090 qbase = sr.lookup('qbase')
2091 2091 except error.RepoError:
2092 2092 pass
2093 2093 ui.note(_('cloning main repository\n'))
2094 2094 sr, dr = hg.clone(ui, sr.url(), dest,
2095 2095 pull=opts.get('pull'),
2096 2096 rev=destrev,
2097 2097 update=False,
2098 2098 stream=opts.get('uncompressed'))
2099 2099 ui.note(_('cloning patch repository\n'))
2100 2100 hg.clone(ui, opts.get('patches') or patchdir(sr), patchdir(dr),
2101 2101 pull=opts.get('pull'), update=not opts.get('noupdate'),
2102 2102 stream=opts.get('uncompressed'))
2103 2103 if dr.local():
2104 2104 if qbase:
2105 2105 ui.note(_('stripping applied patches from destination '
2106 2106 'repository\n'))
2107 2107 dr.mq.strip(dr, [qbase], update=False, backup=None)
2108 2108 if not opts.get('noupdate'):
2109 2109 ui.note(_('updating destination repository\n'))
2110 2110 hg.update(dr, dr.changelog.tip())
2111 2111
2112 2112 @command("qcommit|qci",
2113 2113 commands.table["^commit|ci"][1],
2114 2114 _('hg qcommit [OPTION]... [FILE]...'))
2115 2115 def commit(ui, repo, *pats, **opts):
2116 2116 """commit changes in the queue repository (DEPRECATED)
2117 2117
2118 2118 This command is deprecated; use :hg:`commit --mq` instead."""
2119 2119 q = repo.mq
2120 2120 r = q.qrepo()
2121 2121 if not r:
2122 2122 raise util.Abort('no queue repository')
2123 2123 commands.commit(r.ui, r, *pats, **opts)
2124 2124
2125 2125 @command("qseries",
2126 2126 [('m', 'missing', None, _('print patches not in series')),
2127 2127 ] + seriesopts,
2128 2128 _('hg qseries [-ms]'))
2129 2129 def series(ui, repo, **opts):
2130 2130 """print the entire series file
2131 2131
2132 2132 Returns 0 on success."""
2133 2133 repo.mq.qseries(repo, missing=opts.get('missing'), summary=opts.get('summary'))
2134 2134 return 0
2135 2135
2136 2136 @command("qtop", [] + seriesopts, _('hg qtop [-s]'))
2137 2137 def top(ui, repo, **opts):
2138 2138 """print the name of the current patch
2139 2139
2140 2140 Returns 0 on success."""
2141 2141 q = repo.mq
2142 2142 t = q.applied and q.series_end(True) or 0
2143 2143 if t:
2144 2144 q.qseries(repo, start=t - 1, length=1, status='A',
2145 2145 summary=opts.get('summary'))
2146 2146 else:
2147 2147 ui.write(_("no patches applied\n"))
2148 2148 return 1
2149 2149
2150 2150 @command("qnext", [] + seriesopts, _('hg qnext [-s]'))
2151 2151 def next(ui, repo, **opts):
2152 2152 """print the name of the next patch
2153 2153
2154 2154 Returns 0 on success."""
2155 2155 q = repo.mq
2156 2156 end = q.series_end()
2157 2157 if end == len(q.series):
2158 2158 ui.write(_("all patches applied\n"))
2159 2159 return 1
2160 2160 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2161 2161
2162 2162 @command("qprev", [] + seriesopts, _('hg qprev [-s]'))
2163 2163 def prev(ui, repo, **opts):
2164 2164 """print the name of the previous patch
2165 2165
2166 2166 Returns 0 on success."""
2167 2167 q = repo.mq
2168 2168 l = len(q.applied)
2169 2169 if l == 1:
2170 2170 ui.write(_("only one patch applied\n"))
2171 2171 return 1
2172 2172 if not l:
2173 2173 ui.write(_("no patches applied\n"))
2174 2174 return 1
2175 2175 q.qseries(repo, start=l - 2, length=1, status='A',
2176 2176 summary=opts.get('summary'))
2177 2177
2178 2178 def setupheaderopts(ui, opts):
2179 2179 if not opts.get('user') and opts.get('currentuser'):
2180 2180 opts['user'] = ui.username()
2181 2181 if not opts.get('date') and opts.get('currentdate'):
2182 2182 opts['date'] = "%d %d" % util.makedate()
2183 2183
2184 2184 @command("^qnew",
2185 2185 [('e', 'edit', None, _('edit commit message')),
2186 2186 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2187 2187 ('g', 'git', None, _('use git extended diff format')),
2188 2188 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2189 2189 ('u', 'user', '',
2190 2190 _('add "From: <USER>" to patch'), _('USER')),
2191 2191 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2192 2192 ('d', 'date', '',
2193 2193 _('add "Date: <DATE>" to patch'), _('DATE'))
2194 2194 ] + commands.walkopts + commands.commitopts,
2195 2195 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2196 2196 def new(ui, repo, patch, *args, **opts):
2197 2197 """create a new patch
2198 2198
2199 2199 qnew creates a new patch on top of the currently-applied patch (if
2200 2200 any). The patch will be initialized with any outstanding changes
2201 2201 in the working directory. You may also use -I/--include,
2202 2202 -X/--exclude, and/or a list of files after the patch name to add
2203 2203 only changes to matching files to the new patch, leaving the rest
2204 2204 as uncommitted modifications.
2205 2205
2206 2206 -u/--user and -d/--date can be used to set the (given) user and
2207 2207 date, respectively. -U/--currentuser and -D/--currentdate set user
2208 2208 to current user and date to current date.
2209 2209
2210 2210 -e/--edit, -m/--message or -l/--logfile set the patch header as
2211 2211 well as the commit message. If none is specified, the header is
2212 2212 empty and the commit message is '[mq]: PATCH'.
2213 2213
2214 2214 Use the -g/--git option to keep the patch in the git extended diff
2215 2215 format. Read the diffs help topic for more information on why this
2216 2216 is important for preserving permission changes and copy/rename
2217 2217 information.
2218 2218
2219 2219 Returns 0 on successful creation of a new patch.
2220 2220 """
2221 2221 msg = cmdutil.logmessage(opts)
2222 2222 def getmsg():
2223 2223 return ui.edit(msg, opts.get('user') or ui.username())
2224 2224 q = repo.mq
2225 2225 opts['msg'] = msg
2226 2226 if opts.get('edit'):
2227 2227 opts['msg'] = getmsg
2228 2228 else:
2229 2229 opts['msg'] = msg
2230 2230 setupheaderopts(ui, opts)
2231 2231 q.new(repo, patch, *args, **opts)
2232 2232 q.save_dirty()
2233 2233 return 0
2234 2234
2235 2235 @command("^qrefresh",
2236 2236 [('e', 'edit', None, _('edit commit message')),
2237 2237 ('g', 'git', None, _('use git extended diff format')),
2238 2238 ('s', 'short', None,
2239 2239 _('refresh only files already in the patch and specified files')),
2240 2240 ('U', 'currentuser', None,
2241 2241 _('add/update author field in patch with current user')),
2242 2242 ('u', 'user', '',
2243 2243 _('add/update author field in patch with given user'), _('USER')),
2244 2244 ('D', 'currentdate', None,
2245 2245 _('add/update date field in patch with current date')),
2246 2246 ('d', 'date', '',
2247 2247 _('add/update date field in patch with given date'), _('DATE'))
2248 2248 ] + commands.walkopts + commands.commitopts,
2249 2249 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2250 2250 def refresh(ui, repo, *pats, **opts):
2251 2251 """update the current patch
2252 2252
2253 2253 If any file patterns are provided, the refreshed patch will
2254 2254 contain only the modifications that match those patterns; the
2255 2255 remaining modifications will remain in the working directory.
2256 2256
2257 2257 If -s/--short is specified, files currently included in the patch
2258 2258 will be refreshed just like matched files and remain in the patch.
2259 2259
2260 2260 If -e/--edit is specified, Mercurial will start your configured editor for
2261 2261 you to enter a message. In case qrefresh fails, you will find a backup of
2262 2262 your message in ``.hg/last-message.txt``.
2263 2263
2264 2264 hg add/remove/copy/rename work as usual, though you might want to
2265 2265 use git-style patches (-g/--git or [diff] git=1) to track copies
2266 2266 and renames. See the diffs help topic for more information on the
2267 2267 git diff format.
2268 2268
2269 2269 Returns 0 on success.
2270 2270 """
2271 2271 q = repo.mq
2272 2272 message = cmdutil.logmessage(opts)
2273 2273 if opts.get('edit'):
2274 2274 if not q.applied:
2275 2275 ui.write(_("no patches applied\n"))
2276 2276 return 1
2277 2277 if message:
2278 2278 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2279 2279 patch = q.applied[-1].name
2280 2280 ph = patchheader(q.join(patch), q.plainmode)
2281 2281 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2282 2282 # We don't want to lose the patch message if qrefresh fails (issue2062)
2283 2283 msgfile = repo.opener('last-message.txt', 'wb')
2284 2284 msgfile.write(message)
2285 2285 msgfile.close()
2286 2286 setupheaderopts(ui, opts)
2287 2287 ret = q.refresh(repo, pats, msg=message, **opts)
2288 2288 q.save_dirty()
2289 2289 return ret
2290 2290
2291 2291 @command("^qdiff",
2292 2292 commands.diffopts + commands.diffopts2 + commands.walkopts,
2293 2293 _('hg qdiff [OPTION]... [FILE]...'))
2294 2294 def diff(ui, repo, *pats, **opts):
2295 2295 """diff of the current patch and subsequent modifications
2296 2296
2297 2297 Shows a diff which includes the current patch as well as any
2298 2298 changes which have been made in the working directory since the
2299 2299 last refresh (thus showing what the current patch would become
2300 2300 after a qrefresh).
2301 2301
2302 2302 Use :hg:`diff` if you only want to see the changes made since the
2303 2303 last qrefresh, or :hg:`export qtip` if you want to see changes
2304 2304 made by the current patch without including changes made since the
2305 2305 qrefresh.
2306 2306
2307 2307 Returns 0 on success.
2308 2308 """
2309 2309 repo.mq.diff(repo, pats, opts)
2310 2310 return 0
2311 2311
2312 2312 @command('qfold',
2313 2313 [('e', 'edit', None, _('edit patch header')),
2314 2314 ('k', 'keep', None, _('keep folded patch files')),
2315 2315 ] + commands.commitopts,
2316 2316 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2317 2317 def fold(ui, repo, *files, **opts):
2318 2318 """fold the named patches into the current patch
2319 2319
2320 2320 Patches must not yet be applied. Each patch will be successively
2321 2321 applied to the current patch in the order given. If all the
2322 2322 patches apply successfully, the current patch will be refreshed
2323 2323 with the new cumulative patch, and the folded patches will be
2324 2324 deleted. With -k/--keep, the folded patch files will not be
2325 2325 removed afterwards.
2326 2326
2327 2327 The header for each folded patch will be concatenated with the
2328 2328 current patch header, separated by a line of ``* * *``.
2329 2329
2330 2330 Returns 0 on success."""
2331 2331
2332 2332 q = repo.mq
2333 2333
2334 2334 if not files:
2335 2335 raise util.Abort(_('qfold requires at least one patch name'))
2336 2336 if not q.check_toppatch(repo)[0]:
2337 2337 raise util.Abort(_('no patches applied'))
2338 2338 q.check_localchanges(repo)
2339 2339
2340 2340 message = cmdutil.logmessage(opts)
2341 2341 if opts.get('edit'):
2342 2342 if message:
2343 2343 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2344 2344
2345 2345 parent = q.lookup('qtip')
2346 2346 patches = []
2347 2347 messages = []
2348 2348 for f in files:
2349 2349 p = q.lookup(f)
2350 2350 if p in patches or p == parent:
2351 2351 ui.warn(_('Skipping already folded patch %s\n') % p)
2352 2352 if q.isapplied(p):
2353 2353 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2354 2354 patches.append(p)
2355 2355
2356 2356 for p in patches:
2357 2357 if not message:
2358 2358 ph = patchheader(q.join(p), q.plainmode)
2359 2359 if ph.message:
2360 2360 messages.append(ph.message)
2361 2361 pf = q.join(p)
2362 2362 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2363 2363 if not patchsuccess:
2364 2364 raise util.Abort(_('error folding patch %s') % p)
2365 2365
2366 2366 if not message:
2367 2367 ph = patchheader(q.join(parent), q.plainmode)
2368 2368 message, user = ph.message, ph.user
2369 2369 for msg in messages:
2370 2370 message.append('* * *')
2371 2371 message.extend(msg)
2372 2372 message = '\n'.join(message)
2373 2373
2374 2374 if opts.get('edit'):
2375 2375 message = ui.edit(message, user or ui.username())
2376 2376
2377 2377 diffopts = q.patchopts(q.diffopts(), *patches)
2378 2378 q.refresh(repo, msg=message, git=diffopts.git)
2379 2379 q.delete(repo, patches, opts)
2380 2380 q.save_dirty()
2381 2381
2382 2382 @command("qgoto",
2383 2383 [('f', 'force', None, _('overwrite any local changes'))],
2384 2384 _('hg qgoto [OPTION]... PATCH'))
2385 2385 def goto(ui, repo, patch, **opts):
2386 2386 '''push or pop patches until named patch is at top of stack
2387 2387
2388 2388 Returns 0 on success.'''
2389 2389 q = repo.mq
2390 2390 patch = q.lookup(patch)
2391 2391 if q.isapplied(patch):
2392 2392 ret = q.pop(repo, patch, force=opts.get('force'))
2393 2393 else:
2394 2394 ret = q.push(repo, patch, force=opts.get('force'))
2395 2395 q.save_dirty()
2396 2396 return ret
2397 2397
2398 2398 @command("qguard",
2399 2399 [('l', 'list', None, _('list all patches and guards')),
2400 2400 ('n', 'none', None, _('drop all guards'))],
2401 2401 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2402 2402 def guard(ui, repo, *args, **opts):
2403 2403 '''set or print guards for a patch
2404 2404
2405 2405 Guards control whether a patch can be pushed. A patch with no
2406 2406 guards is always pushed. A patch with a positive guard ("+foo") is
2407 2407 pushed only if the :hg:`qselect` command has activated it. A patch with
2408 2408 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2409 2409 has activated it.
2410 2410
2411 2411 With no arguments, print the currently active guards.
2412 2412 With arguments, set guards for the named patch.
2413 2413
2414 2414 .. note::
2415 2415 Specifying negative guards now requires '--'.
2416 2416
2417 2417 To set guards on another patch::
2418 2418
2419 2419 hg qguard other.patch -- +2.6.17 -stable
2420 2420
2421 2421 Returns 0 on success.
2422 2422 '''
2423 2423 def status(idx):
2424 2424 guards = q.series_guards[idx] or ['unguarded']
2425 2425 if q.series[idx] in applied:
2426 2426 state = 'applied'
2427 2427 elif q.pushable(idx)[0]:
2428 2428 state = 'unapplied'
2429 2429 else:
2430 2430 state = 'guarded'
2431 2431 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2432 2432 ui.write('%s: ' % ui.label(q.series[idx], label))
2433 2433
2434 2434 for i, guard in enumerate(guards):
2435 2435 if guard.startswith('+'):
2436 2436 ui.write(guard, label='qguard.positive')
2437 2437 elif guard.startswith('-'):
2438 2438 ui.write(guard, label='qguard.negative')
2439 2439 else:
2440 2440 ui.write(guard, label='qguard.unguarded')
2441 2441 if i != len(guards) - 1:
2442 2442 ui.write(' ')
2443 2443 ui.write('\n')
2444 2444 q = repo.mq
2445 2445 applied = set(p.name for p in q.applied)
2446 2446 patch = None
2447 2447 args = list(args)
2448 2448 if opts.get('list'):
2449 2449 if args or opts.get('none'):
2450 2450 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2451 2451 for i in xrange(len(q.series)):
2452 2452 status(i)
2453 2453 return
2454 2454 if not args or args[0][0:1] in '-+':
2455 2455 if not q.applied:
2456 2456 raise util.Abort(_('no patches applied'))
2457 2457 patch = q.applied[-1].name
2458 2458 if patch is None and args[0][0:1] not in '-+':
2459 2459 patch = args.pop(0)
2460 2460 if patch is None:
2461 2461 raise util.Abort(_('no patch to work with'))
2462 2462 if args or opts.get('none'):
2463 2463 idx = q.find_series(patch)
2464 2464 if idx is None:
2465 2465 raise util.Abort(_('no patch named %s') % patch)
2466 2466 q.set_guards(idx, args)
2467 2467 q.save_dirty()
2468 2468 else:
2469 2469 status(q.series.index(q.lookup(patch)))
2470 2470
2471 2471 @command("qheader", [], _('hg qheader [PATCH]'))
2472 2472 def header(ui, repo, patch=None):
2473 2473 """print the header of the topmost or specified patch
2474 2474
2475 2475 Returns 0 on success."""
2476 2476 q = repo.mq
2477 2477
2478 2478 if patch:
2479 2479 patch = q.lookup(patch)
2480 2480 else:
2481 2481 if not q.applied:
2482 2482 ui.write(_('no patches applied\n'))
2483 2483 return 1
2484 2484 patch = q.lookup('qtip')
2485 2485 ph = patchheader(q.join(patch), q.plainmode)
2486 2486
2487 2487 ui.write('\n'.join(ph.message) + '\n')
2488 2488
2489 2489 def lastsavename(path):
2490 2490 (directory, base) = os.path.split(path)
2491 2491 names = os.listdir(directory)
2492 2492 namere = re.compile("%s.([0-9]+)" % base)
2493 2493 maxindex = None
2494 2494 maxname = None
2495 2495 for f in names:
2496 2496 m = namere.match(f)
2497 2497 if m:
2498 2498 index = int(m.group(1))
2499 2499 if maxindex is None or index > maxindex:
2500 2500 maxindex = index
2501 2501 maxname = f
2502 2502 if maxname:
2503 2503 return (os.path.join(directory, maxname), maxindex)
2504 2504 return (None, None)
2505 2505
2506 2506 def savename(path):
2507 2507 (last, index) = lastsavename(path)
2508 2508 if last is None:
2509 2509 index = 0
2510 2510 newpath = path + ".%d" % (index + 1)
2511 2511 return newpath
2512 2512
2513 2513 @command("^qpush",
2514 2514 [('f', 'force', None, _('apply on top of local changes')),
2515 2515 ('e', 'exact', None, _('apply the target patch to its recorded parent')),
2516 2516 ('l', 'list', None, _('list patch name in commit text')),
2517 2517 ('a', 'all', None, _('apply all patches')),
2518 2518 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2519 2519 ('n', 'name', '',
2520 2520 _('merge queue name (DEPRECATED)'), _('NAME')),
2521 2521 ('', 'move', None, _('reorder patch series and apply only the patch'))],
2522 2522 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2523 2523 def push(ui, repo, patch=None, **opts):
2524 2524 """push the next patch onto the stack
2525 2525
2526 2526 When -f/--force is applied, all local changes in patched files
2527 2527 will be lost.
2528 2528
2529 2529 Return 0 on success.
2530 2530 """
2531 2531 q = repo.mq
2532 2532 mergeq = None
2533 2533
2534 2534 if opts.get('merge'):
2535 2535 if opts.get('name'):
2536 2536 newpath = repo.join(opts.get('name'))
2537 2537 else:
2538 2538 newpath, i = lastsavename(q.path)
2539 2539 if not newpath:
2540 2540 ui.warn(_("no saved queues found, please use -n\n"))
2541 2541 return 1
2542 2542 mergeq = queue(ui, repo.join(""), newpath)
2543 2543 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2544 2544 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2545 2545 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2546 2546 exact=opts.get('exact'))
2547 2547 return ret
2548 2548
2549 2549 @command("^qpop",
2550 2550 [('a', 'all', None, _('pop all patches')),
2551 2551 ('n', 'name', '',
2552 2552 _('queue name to pop (DEPRECATED)'), _('NAME')),
2553 2553 ('f', 'force', None, _('forget any local changes to patched files'))],
2554 2554 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2555 2555 def pop(ui, repo, patch=None, **opts):
2556 2556 """pop the current patch off the stack
2557 2557
2558 2558 By default, pops off the top of the patch stack. If given a patch
2559 2559 name, keeps popping off patches until the named patch is at the
2560 2560 top of the stack.
2561 2561
2562 2562 Return 0 on success.
2563 2563 """
2564 2564 localupdate = True
2565 2565 if opts.get('name'):
2566 2566 q = queue(ui, repo.join(""), repo.join(opts.get('name')))
2567 2567 ui.warn(_('using patch queue: %s\n') % q.path)
2568 2568 localupdate = False
2569 2569 else:
2570 2570 q = repo.mq
2571 2571 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2572 2572 all=opts.get('all'))
2573 2573 q.save_dirty()
2574 2574 return ret
2575 2575
2576 2576 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2577 2577 def rename(ui, repo, patch, name=None, **opts):
2578 2578 """rename a patch
2579 2579
2580 2580 With one argument, renames the current patch to PATCH1.
2581 2581 With two arguments, renames PATCH1 to PATCH2.
2582 2582
2583 2583 Returns 0 on success."""
2584 2584
2585 2585 q = repo.mq
2586 2586
2587 2587 if not name:
2588 2588 name = patch
2589 2589 patch = None
2590 2590
2591 2591 if patch:
2592 2592 patch = q.lookup(patch)
2593 2593 else:
2594 2594 if not q.applied:
2595 2595 ui.write(_('no patches applied\n'))
2596 2596 return
2597 2597 patch = q.lookup('qtip')
2598 2598 absdest = q.join(name)
2599 2599 if os.path.isdir(absdest):
2600 2600 name = normname(os.path.join(name, os.path.basename(patch)))
2601 2601 absdest = q.join(name)
2602 2602 if os.path.exists(absdest):
2603 2603 raise util.Abort(_('%s already exists') % absdest)
2604 2604
2605 2605 if name in q.series:
2606 2606 raise util.Abort(
2607 2607 _('A patch named %s already exists in the series file') % name)
2608 2608
2609 2609 ui.note(_('renaming %s to %s\n') % (patch, name))
2610 2610 i = q.find_series(patch)
2611 2611 guards = q.guard_re.findall(q.full_series[i])
2612 2612 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2613 2613 q.parse_series()
2614 2614 q.series_dirty = 1
2615 2615
2616 2616 info = q.isapplied(patch)
2617 2617 if info:
2618 2618 q.applied[info[0]] = statusentry(info[1], name)
2619 2619 q.applied_dirty = 1
2620 2620
2621 2621 destdir = os.path.dirname(absdest)
2622 2622 if not os.path.isdir(destdir):
2623 2623 os.makedirs(destdir)
2624 2624 util.rename(q.join(patch), absdest)
2625 2625 r = q.qrepo()
2626 2626 if r and patch in r.dirstate:
2627 2627 wctx = r[None]
2628 2628 wlock = r.wlock()
2629 2629 try:
2630 2630 if r.dirstate[patch] == 'a':
2631 2631 r.dirstate.forget(patch)
2632 2632 r.dirstate.add(name)
2633 2633 else:
2634 2634 if r.dirstate[name] == 'r':
2635 2635 wctx.undelete([name])
2636 2636 wctx.copy(patch, name)
2637 2637 wctx.remove([patch], False)
2638 2638 finally:
2639 2639 wlock.release()
2640 2640
2641 2641 q.save_dirty()
2642 2642
2643 2643 @command("qrestore",
2644 2644 [('d', 'delete', None, _('delete save entry')),
2645 2645 ('u', 'update', None, _('update queue working directory'))],
2646 2646 _('hg qrestore [-d] [-u] REV'))
2647 2647 def restore(ui, repo, rev, **opts):
2648 2648 """restore the queue state saved by a revision (DEPRECATED)
2649 2649
2650 2650 This command is deprecated, use :hg:`rebase` instead."""
2651 2651 rev = repo.lookup(rev)
2652 2652 q = repo.mq
2653 2653 q.restore(repo, rev, delete=opts.get('delete'),
2654 2654 qupdate=opts.get('update'))
2655 2655 q.save_dirty()
2656 2656 return 0
2657 2657
2658 2658 @command("qsave",
2659 2659 [('c', 'copy', None, _('copy patch directory')),
2660 2660 ('n', 'name', '',
2661 2661 _('copy directory name'), _('NAME')),
2662 2662 ('e', 'empty', None, _('clear queue status file')),
2663 2663 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2664 2664 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2665 2665 def save(ui, repo, **opts):
2666 2666 """save current queue state (DEPRECATED)
2667 2667
2668 2668 This command is deprecated, use :hg:`rebase` instead."""
2669 2669 q = repo.mq
2670 2670 message = cmdutil.logmessage(opts)
2671 2671 ret = q.save(repo, msg=message)
2672 2672 if ret:
2673 2673 return ret
2674 2674 q.save_dirty()
2675 2675 if opts.get('copy'):
2676 2676 path = q.path
2677 2677 if opts.get('name'):
2678 2678 newpath = os.path.join(q.basepath, opts.get('name'))
2679 2679 if os.path.exists(newpath):
2680 2680 if not os.path.isdir(newpath):
2681 2681 raise util.Abort(_('destination %s exists and is not '
2682 2682 'a directory') % newpath)
2683 2683 if not opts.get('force'):
2684 2684 raise util.Abort(_('destination %s exists, '
2685 2685 'use -f to force') % newpath)
2686 2686 else:
2687 2687 newpath = savename(path)
2688 2688 ui.warn(_("copy %s to %s\n") % (path, newpath))
2689 2689 util.copyfiles(path, newpath)
2690 2690 if opts.get('empty'):
2691 2691 try:
2692 2692 os.unlink(q.join(q.status_path))
2693 2693 except:
2694 2694 pass
2695 2695 return 0
2696 2696
2697 2697 @command("strip",
2698 2698 [('f', 'force', None, _('force removal of changesets, discard '
2699 2699 'uncommitted changes (no backup)')),
2700 2700 ('b', 'backup', None, _('bundle only changesets with local revision'
2701 2701 ' number greater than REV which are not'
2702 2702 ' descendants of REV (DEPRECATED)')),
2703 2703 ('n', 'no-backup', None, _('no backups')),
2704 2704 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
2705 2705 ('k', 'keep', None, _("do not modify working copy during strip"))],
2706 2706 _('hg strip [-k] [-f] [-n] REV...'))
2707 2707 def strip(ui, repo, *revs, **opts):
2708 2708 """strip changesets and all their descendants from the repository
2709 2709
2710 2710 The strip command removes the specified changesets and all their
2711 2711 descendants. If the working directory has uncommitted changes, the
2712 2712 operation is aborted unless the --force flag is supplied, in which
2713 2713 case changes will be discarded.
2714 2714
2715 2715 If a parent of the working directory is stripped, then the working
2716 2716 directory will automatically be updated to the most recent
2717 2717 available ancestor of the stripped parent after the operation
2718 2718 completes.
2719 2719
2720 2720 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2721 2721 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2722 2722 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2723 2723 where BUNDLE is the bundle file created by the strip. Note that
2724 2724 the local revision numbers will in general be different after the
2725 2725 restore.
2726 2726
2727 2727 Use the --no-backup option to discard the backup bundle once the
2728 2728 operation completes.
2729 2729
2730 2730 Return 0 on success.
2731 2731 """
2732 2732 backup = 'all'
2733 2733 if opts.get('backup'):
2734 2734 backup = 'strip'
2735 2735 elif opts.get('no_backup') or opts.get('nobackup'):
2736 2736 backup = 'none'
2737 2737
2738 2738 cl = repo.changelog
2739 2739 revs = set(scmutil.revrange(repo, revs))
2740 2740 if not revs:
2741 2741 raise util.Abort(_('empty revision set'))
2742 2742
2743 2743 descendants = set(cl.descendants(*revs))
2744 2744 strippedrevs = revs.union(descendants)
2745 2745 roots = revs.difference(descendants)
2746 2746
2747 2747 update = False
2748 2748 # if one of the wdir parent is stripped we'll need
2749 2749 # to update away to an earlier revision
2750 2750 for p in repo.dirstate.parents():
2751 2751 if p != nullid and cl.rev(p) in strippedrevs:
2752 2752 update = True
2753 2753 break
2754 2754
2755 2755 rootnodes = set(cl.node(r) for r in roots)
2756 2756
2757 2757 q = repo.mq
2758 2758 if q.applied:
2759 2759 # refresh queue state if we're about to strip
2760 2760 # applied patches
2761 2761 if cl.rev(repo.lookup('qtip')) in strippedrevs:
2762 2762 q.applied_dirty = True
2763 2763 start = 0
2764 2764 end = len(q.applied)
2765 2765 for i, statusentry in enumerate(q.applied):
2766 2766 if statusentry.node in rootnodes:
2767 2767 # if one of the stripped roots is an applied
2768 2768 # patch, only part of the queue is stripped
2769 2769 start = i
2770 2770 break
2771 2771 del q.applied[start:end]
2772 2772 q.save_dirty()
2773 2773
2774 2774 revs = list(rootnodes)
2775 2775 if update and opts.get('keep'):
2776 2776 wlock = repo.wlock()
2777 2777 try:
2778 2778 urev = repo.mq.qparents(repo, revs[0])
2779 2779 repo.dirstate.rebuild(urev, repo[urev].manifest())
2780 2780 repo.dirstate.write()
2781 2781 update = False
2782 2782 finally:
2783 2783 wlock.release()
2784 2784
2785 2785 repo.mq.strip(repo, revs, backup=backup, update=update,
2786 2786 force=opts.get('force'))
2787 2787 return 0
2788 2788
2789 2789 @command("qselect",
2790 2790 [('n', 'none', None, _('disable all guards')),
2791 2791 ('s', 'series', None, _('list all guards in series file')),
2792 2792 ('', 'pop', None, _('pop to before first guarded applied patch')),
2793 2793 ('', 'reapply', None, _('pop, then reapply patches'))],
2794 2794 _('hg qselect [OPTION]... [GUARD]...'))
2795 2795 def select(ui, repo, *args, **opts):
2796 2796 '''set or print guarded patches to push
2797 2797
2798 2798 Use the :hg:`qguard` command to set or print guards on patch, then use
2799 2799 qselect to tell mq which guards to use. A patch will be pushed if
2800 2800 it has no guards or any positive guards match the currently
2801 2801 selected guard, but will not be pushed if any negative guards
2802 2802 match the current guard. For example::
2803 2803
2804 2804 qguard foo.patch -- -stable (negative guard)
2805 2805 qguard bar.patch +stable (positive guard)
2806 2806 qselect stable
2807 2807
2808 2808 This activates the "stable" guard. mq will skip foo.patch (because
2809 2809 it has a negative match) but push bar.patch (because it has a
2810 2810 positive match).
2811 2811
2812 2812 With no arguments, prints the currently active guards.
2813 2813 With one argument, sets the active guard.
2814 2814
2815 2815 Use -n/--none to deactivate guards (no other arguments needed).
2816 2816 When no guards are active, patches with positive guards are
2817 2817 skipped and patches with negative guards are pushed.
2818 2818
2819 2819 qselect can change the guards on applied patches. It does not pop
2820 2820 guarded patches by default. Use --pop to pop back to the last
2821 2821 applied patch that is not guarded. Use --reapply (which implies
2822 2822 --pop) to push back to the current patch afterwards, but skip
2823 2823 guarded patches.
2824 2824
2825 2825 Use -s/--series to print a list of all guards in the series file
2826 2826 (no other arguments needed). Use -v for more information.
2827 2827
2828 2828 Returns 0 on success.'''
2829 2829
2830 2830 q = repo.mq
2831 2831 guards = q.active()
2832 2832 if args or opts.get('none'):
2833 2833 old_unapplied = q.unapplied(repo)
2834 2834 old_guarded = [i for i in xrange(len(q.applied)) if
2835 2835 not q.pushable(i)[0]]
2836 2836 q.set_active(args)
2837 2837 q.save_dirty()
2838 2838 if not args:
2839 2839 ui.status(_('guards deactivated\n'))
2840 2840 if not opts.get('pop') and not opts.get('reapply'):
2841 2841 unapplied = q.unapplied(repo)
2842 2842 guarded = [i for i in xrange(len(q.applied))
2843 2843 if not q.pushable(i)[0]]
2844 2844 if len(unapplied) != len(old_unapplied):
2845 2845 ui.status(_('number of unguarded, unapplied patches has '
2846 2846 'changed from %d to %d\n') %
2847 2847 (len(old_unapplied), len(unapplied)))
2848 2848 if len(guarded) != len(old_guarded):
2849 2849 ui.status(_('number of guarded, applied patches has changed '
2850 2850 'from %d to %d\n') %
2851 2851 (len(old_guarded), len(guarded)))
2852 2852 elif opts.get('series'):
2853 2853 guards = {}
2854 2854 noguards = 0
2855 2855 for gs in q.series_guards:
2856 2856 if not gs:
2857 2857 noguards += 1
2858 2858 for g in gs:
2859 2859 guards.setdefault(g, 0)
2860 2860 guards[g] += 1
2861 2861 if ui.verbose:
2862 2862 guards['NONE'] = noguards
2863 2863 guards = guards.items()
2864 2864 guards.sort(key=lambda x: x[0][1:])
2865 2865 if guards:
2866 2866 ui.note(_('guards in series file:\n'))
2867 2867 for guard, count in guards:
2868 2868 ui.note('%2d ' % count)
2869 2869 ui.write(guard, '\n')
2870 2870 else:
2871 2871 ui.note(_('no guards in series file\n'))
2872 2872 else:
2873 2873 if guards:
2874 2874 ui.note(_('active guards:\n'))
2875 2875 for g in guards:
2876 2876 ui.write(g, '\n')
2877 2877 else:
2878 2878 ui.write(_('no active guards\n'))
2879 2879 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
2880 2880 popped = False
2881 2881 if opts.get('pop') or opts.get('reapply'):
2882 2882 for i in xrange(len(q.applied)):
2883 2883 pushable, reason = q.pushable(i)
2884 2884 if not pushable:
2885 2885 ui.status(_('popping guarded patches\n'))
2886 2886 popped = True
2887 2887 if i == 0:
2888 2888 q.pop(repo, all=True)
2889 2889 else:
2890 2890 q.pop(repo, i - 1)
2891 2891 break
2892 2892 if popped:
2893 2893 try:
2894 2894 if reapply:
2895 2895 ui.status(_('reapplying unguarded patches\n'))
2896 2896 q.push(repo, reapply)
2897 2897 finally:
2898 2898 q.save_dirty()
2899 2899
2900 2900 @command("qfinish",
2901 2901 [('a', 'applied', None, _('finish all applied changesets'))],
2902 2902 _('hg qfinish [-a] [REV]...'))
2903 2903 def finish(ui, repo, *revrange, **opts):
2904 2904 """move applied patches into repository history
2905 2905
2906 2906 Finishes the specified revisions (corresponding to applied
2907 2907 patches) by moving them out of mq control into regular repository
2908 2908 history.
2909 2909
2910 2910 Accepts a revision range or the -a/--applied option. If --applied
2911 2911 is specified, all applied mq revisions are removed from mq
2912 2912 control. Otherwise, the given revisions must be at the base of the
2913 2913 stack of applied patches.
2914 2914
2915 2915 This can be especially useful if your changes have been applied to
2916 2916 an upstream repository, or if you are about to push your changes
2917 2917 to upstream.
2918 2918
2919 2919 Returns 0 on success.
2920 2920 """
2921 2921 if not opts.get('applied') and not revrange:
2922 2922 raise util.Abort(_('no revisions specified'))
2923 2923 elif opts.get('applied'):
2924 2924 revrange = ('qbase::qtip',) + revrange
2925 2925
2926 2926 q = repo.mq
2927 2927 if not q.applied:
2928 2928 ui.status(_('no patches applied\n'))
2929 2929 return 0
2930 2930
2931 2931 revs = scmutil.revrange(repo, revrange)
2932 2932 q.finish(repo, revs)
2933 2933 q.save_dirty()
2934 2934 return 0
2935 2935
2936 2936 @command("qqueue",
2937 2937 [('l', 'list', False, _('list all available queues')),
2938 2938 ('c', 'create', False, _('create new queue')),
2939 2939 ('', 'rename', False, _('rename active queue')),
2940 2940 ('', 'delete', False, _('delete reference to queue')),
2941 2941 ('', 'purge', False, _('delete queue, and remove patch dir')),
2942 2942 ],
2943 2943 _('[OPTION] [QUEUE]'))
2944 2944 def qqueue(ui, repo, name=None, **opts):
2945 2945 '''manage multiple patch queues
2946 2946
2947 2947 Supports switching between different patch queues, as well as creating
2948 2948 new patch queues and deleting existing ones.
2949 2949
2950 2950 Omitting a queue name or specifying -l/--list will show you the registered
2951 2951 queues - by default the "normal" patches queue is registered. The currently
2952 2952 active queue will be marked with "(active)".
2953 2953
2954 2954 To create a new queue, use -c/--create. The queue is automatically made
2955 2955 active, except in the case where there are applied patches from the
2956 2956 currently active queue in the repository. Then the queue will only be
2957 2957 created and switching will fail.
2958 2958
2959 2959 To delete an existing queue, use --delete. You cannot delete the currently
2960 2960 active queue.
2961 2961
2962 2962 Returns 0 on success.
2963 2963 '''
2964 2964
2965 2965 q = repo.mq
2966 2966
2967 2967 _defaultqueue = 'patches'
2968 2968 _allqueues = 'patches.queues'
2969 2969 _activequeue = 'patches.queue'
2970 2970
2971 2971 def _getcurrent():
2972 2972 cur = os.path.basename(q.path)
2973 2973 if cur.startswith('patches-'):
2974 2974 cur = cur[8:]
2975 2975 return cur
2976 2976
2977 2977 def _noqueues():
2978 2978 try:
2979 2979 fh = repo.opener(_allqueues, 'r')
2980 2980 fh.close()
2981 2981 except IOError:
2982 2982 return True
2983 2983
2984 2984 return False
2985 2985
2986 2986 def _getqueues():
2987 2987 current = _getcurrent()
2988 2988
2989 2989 try:
2990 2990 fh = repo.opener(_allqueues, 'r')
2991 2991 queues = [queue.strip() for queue in fh if queue.strip()]
2992 2992 fh.close()
2993 2993 if current not in queues:
2994 2994 queues.append(current)
2995 2995 except IOError:
2996 2996 queues = [_defaultqueue]
2997 2997
2998 2998 return sorted(queues)
2999 2999
3000 3000 def _setactive(name):
3001 3001 if q.applied:
3002 3002 raise util.Abort(_('patches applied - cannot set new queue active'))
3003 3003 _setactivenocheck(name)
3004 3004
3005 3005 def _setactivenocheck(name):
3006 3006 fh = repo.opener(_activequeue, 'w')
3007 3007 if name != 'patches':
3008 3008 fh.write(name)
3009 3009 fh.close()
3010 3010
3011 3011 def _addqueue(name):
3012 3012 fh = repo.opener(_allqueues, 'a')
3013 3013 fh.write('%s\n' % (name,))
3014 3014 fh.close()
3015 3015
3016 3016 def _queuedir(name):
3017 3017 if name == 'patches':
3018 3018 return repo.join('patches')
3019 3019 else:
3020 3020 return repo.join('patches-' + name)
3021 3021
3022 3022 def _validname(name):
3023 3023 for n in name:
3024 3024 if n in ':\\/.':
3025 3025 return False
3026 3026 return True
3027 3027
3028 3028 def _delete(name):
3029 3029 if name not in existing:
3030 3030 raise util.Abort(_('cannot delete queue that does not exist'))
3031 3031
3032 3032 current = _getcurrent()
3033 3033
3034 3034 if name == current:
3035 3035 raise util.Abort(_('cannot delete currently active queue'))
3036 3036
3037 3037 fh = repo.opener('patches.queues.new', 'w')
3038 3038 for queue in existing:
3039 3039 if queue == name:
3040 3040 continue
3041 3041 fh.write('%s\n' % (queue,))
3042 3042 fh.close()
3043 3043 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3044 3044
3045 3045 if not name or opts.get('list'):
3046 3046 current = _getcurrent()
3047 3047 for queue in _getqueues():
3048 3048 ui.write('%s' % (queue,))
3049 3049 if queue == current and not ui.quiet:
3050 3050 ui.write(_(' (active)\n'))
3051 3051 else:
3052 3052 ui.write('\n')
3053 3053 return
3054 3054
3055 3055 if not _validname(name):
3056 3056 raise util.Abort(
3057 3057 _('invalid queue name, may not contain the characters ":\\/."'))
3058 3058
3059 3059 existing = _getqueues()
3060 3060
3061 3061 if opts.get('create'):
3062 3062 if name in existing:
3063 3063 raise util.Abort(_('queue "%s" already exists') % name)
3064 3064 if _noqueues():
3065 3065 _addqueue(_defaultqueue)
3066 3066 _addqueue(name)
3067 3067 _setactive(name)
3068 3068 elif opts.get('rename'):
3069 3069 current = _getcurrent()
3070 3070 if name == current:
3071 3071 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3072 3072 if name in existing:
3073 3073 raise util.Abort(_('queue "%s" already exists') % name)
3074 3074
3075 3075 olddir = _queuedir(current)
3076 3076 newdir = _queuedir(name)
3077 3077
3078 3078 if os.path.exists(newdir):
3079 3079 raise util.Abort(_('non-queue directory "%s" already exists') %
3080 3080 newdir)
3081 3081
3082 3082 fh = repo.opener('patches.queues.new', 'w')
3083 3083 for queue in existing:
3084 3084 if queue == current:
3085 3085 fh.write('%s\n' % (name,))
3086 3086 if os.path.exists(olddir):
3087 3087 util.rename(olddir, newdir)
3088 3088 else:
3089 3089 fh.write('%s\n' % (queue,))
3090 3090 fh.close()
3091 3091 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3092 3092 _setactivenocheck(name)
3093 3093 elif opts.get('delete'):
3094 3094 _delete(name)
3095 3095 elif opts.get('purge'):
3096 3096 if name in existing:
3097 3097 _delete(name)
3098 3098 qdir = _queuedir(name)
3099 3099 if os.path.exists(qdir):
3100 3100 shutil.rmtree(qdir)
3101 3101 else:
3102 3102 if name not in existing:
3103 3103 raise util.Abort(_('use --create to create a new queue'))
3104 3104 _setactive(name)
3105 3105
3106 3106 def reposetup(ui, repo):
3107 3107 class mqrepo(repo.__class__):
3108 3108 @util.propertycache
3109 3109 def mq(self):
3110 3110 return queue(self.ui, self.join(""))
3111 3111
3112 3112 def abort_if_wdir_patched(self, errmsg, force=False):
3113 3113 if self.mq.applied and not force:
3114 3114 parents = self.dirstate.parents()
3115 3115 patches = [s.node for s in self.mq.applied]
3116 3116 if parents[0] in patches or parents[1] in patches:
3117 3117 raise util.Abort(errmsg)
3118 3118
3119 3119 def commit(self, text="", user=None, date=None, match=None,
3120 3120 force=False, editor=False, extra={}):
3121 3121 self.abort_if_wdir_patched(
3122 3122 _('cannot commit over an applied mq patch'),
3123 3123 force)
3124 3124
3125 3125 return super(mqrepo, self).commit(text, user, date, match, force,
3126 3126 editor, extra)
3127 3127
3128 3128 def checkpush(self, force, revs):
3129 3129 if self.mq.applied and not force:
3130 3130 haspatches = True
3131 3131 if revs:
3132 3132 # Assume applied patches have no non-patch descendants
3133 3133 # and are not on remote already. If they appear in the
3134 3134 # set of resolved 'revs', bail out.
3135 3135 applied = set(e.node for e in self.mq.applied)
3136 3136 haspatches = bool([n for n in revs if n in applied])
3137 3137 if haspatches:
3138 3138 raise util.Abort(_('source has mq patches applied'))
3139 3139 super(mqrepo, self).checkpush(force, revs)
3140 3140
3141 3141 def _findtags(self):
3142 3142 '''augment tags from base class with patch tags'''
3143 3143 result = super(mqrepo, self)._findtags()
3144 3144
3145 3145 q = self.mq
3146 3146 if not q.applied:
3147 3147 return result
3148 3148
3149 3149 mqtags = [(patch.node, patch.name) for patch in q.applied]
3150 3150
3151 3151 try:
3152 3152 self.changelog.rev(mqtags[-1][0])
3153 3153 except error.RepoLookupError:
3154 3154 self.ui.warn(_('mq status file refers to unknown node %s\n')
3155 3155 % short(mqtags[-1][0]))
3156 3156 return result
3157 3157
3158 3158 mqtags.append((mqtags[-1][0], 'qtip'))
3159 3159 mqtags.append((mqtags[0][0], 'qbase'))
3160 3160 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3161 3161 tags = result[0]
3162 3162 for patch in mqtags:
3163 3163 if patch[1] in tags:
3164 3164 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
3165 3165 % patch[1])
3166 3166 else:
3167 3167 tags[patch[1]] = patch[0]
3168 3168
3169 3169 return result
3170 3170
3171 3171 def _branchtags(self, partial, lrev):
3172 3172 q = self.mq
3173 3173 if not q.applied:
3174 3174 return super(mqrepo, self)._branchtags(partial, lrev)
3175 3175
3176 3176 cl = self.changelog
3177 3177 qbasenode = q.applied[0].node
3178 3178 try:
3179 3179 qbase = cl.rev(qbasenode)
3180 3180 except error.LookupError:
3181 3181 self.ui.warn(_('mq status file refers to unknown node %s\n')
3182 3182 % short(qbasenode))
3183 3183 return super(mqrepo, self)._branchtags(partial, lrev)
3184 3184
3185 3185 start = lrev + 1
3186 3186 if start < qbase:
3187 3187 # update the cache (excluding the patches) and save it
3188 3188 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
3189 3189 self._updatebranchcache(partial, ctxgen)
3190 3190 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
3191 3191 start = qbase
3192 3192 # if start = qbase, the cache is as updated as it should be.
3193 3193 # if start > qbase, the cache includes (part of) the patches.
3194 3194 # we might as well use it, but we won't save it.
3195 3195
3196 3196 # update the cache up to the tip
3197 3197 ctxgen = (self[r] for r in xrange(start, len(cl)))
3198 3198 self._updatebranchcache(partial, ctxgen)
3199 3199
3200 3200 return partial
3201 3201
3202 3202 if repo.local():
3203 3203 repo.__class__ = mqrepo
3204 3204
3205 3205 def mqimport(orig, ui, repo, *args, **kwargs):
3206 3206 if (hasattr(repo, 'abort_if_wdir_patched')
3207 3207 and not kwargs.get('no_commit', False)):
3208 3208 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
3209 3209 kwargs.get('force'))
3210 3210 return orig(ui, repo, *args, **kwargs)
3211 3211
3212 3212 def mqinit(orig, ui, *args, **kwargs):
3213 3213 mq = kwargs.pop('mq', None)
3214 3214
3215 3215 if not mq:
3216 3216 return orig(ui, *args, **kwargs)
3217 3217
3218 3218 if args:
3219 3219 repopath = args[0]
3220 3220 if not hg.islocal(repopath):
3221 3221 raise util.Abort(_('only a local queue repository '
3222 3222 'may be initialized'))
3223 3223 else:
3224 3224 repopath = cmdutil.findrepo(os.getcwd())
3225 3225 if not repopath:
3226 3226 raise util.Abort(_('there is no Mercurial repository here '
3227 3227 '(.hg not found)'))
3228 3228 repo = hg.repository(ui, repopath)
3229 3229 return qinit(ui, repo, True)
3230 3230
3231 3231 def mqcommand(orig, ui, repo, *args, **kwargs):
3232 3232 """Add --mq option to operate on patch repository instead of main"""
3233 3233
3234 3234 # some commands do not like getting unknown options
3235 3235 mq = kwargs.pop('mq', None)
3236 3236
3237 3237 if not mq:
3238 3238 return orig(ui, repo, *args, **kwargs)
3239 3239
3240 3240 q = repo.mq
3241 3241 r = q.qrepo()
3242 3242 if not r:
3243 3243 raise util.Abort(_('no queue repository'))
3244 3244 return orig(r.ui, r, *args, **kwargs)
3245 3245
3246 3246 def summary(orig, ui, repo, *args, **kwargs):
3247 3247 r = orig(ui, repo, *args, **kwargs)
3248 3248 q = repo.mq
3249 3249 m = []
3250 3250 a, u = len(q.applied), len(q.unapplied(repo))
3251 3251 if a:
3252 3252 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3253 3253 if u:
3254 3254 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3255 3255 if m:
3256 3256 ui.write("mq: %s\n" % ', '.join(m))
3257 3257 else:
3258 3258 ui.note(_("mq: (empty queue)\n"))
3259 3259 return r
3260 3260
3261 3261 def revsetmq(repo, subset, x):
3262 3262 """``mq()``
3263 3263 Changesets managed by MQ.
3264 3264 """
3265 3265 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3266 3266 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3267 3267 return [r for r in subset if r in applied]
3268 3268
3269 3269 def extsetup(ui):
3270 3270 revset.symbols['mq'] = revsetmq
3271 3271
3272 3272 # tell hggettext to extract docstrings from these functions:
3273 3273 i18nfunctions = [revsetmq]
3274 3274
3275 3275 def uisetup(ui):
3276 3276 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3277 3277
3278 3278 extensions.wrapcommand(commands.table, 'import', mqimport)
3279 3279 extensions.wrapcommand(commands.table, 'summary', summary)
3280 3280
3281 3281 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3282 3282 entry[1].extend(mqopt)
3283 3283
3284 3284 nowrap = set(commands.norepo.split(" ") + ['qrecord'])
3285 3285
3286 3286 def dotable(cmdtable):
3287 3287 for cmd in cmdtable.keys():
3288 3288 cmd = cmdutil.parsealiases(cmd)[0]
3289 3289 if cmd in nowrap:
3290 3290 continue
3291 3291 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3292 3292 entry[1].extend(mqopt)
3293 3293
3294 3294 dotable(commands.table)
3295 3295
3296 3296 for extname, extmodule in extensions.extensions():
3297 3297 if extmodule.__file__ != __file__:
3298 3298 dotable(getattr(extmodule, 'cmdtable', {}))
3299 3299
3300 3300
3301 3301 colortable = {'qguard.negative': 'red',
3302 3302 'qguard.positive': 'yellow',
3303 3303 'qguard.unguarded': 'green',
3304 3304 'qseries.applied': 'blue bold underline',
3305 3305 'qseries.guarded': 'black bold',
3306 3306 'qseries.missing': 'red bold',
3307 3307 'qseries.unapplied': 'black bold'}
@@ -1,632 +1,631 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, hg, merge, match
19 19 from mercurial import patch, revlog, scmutil, util, error, cmdutil
20 20 from mercurial import revset, templatekw
21 21
22 22 cmdtable = {}
23 23 command = cmdutil.command(cmdtable)
24 24
25 25 class transplantentry(object):
26 26 def __init__(self, lnode, rnode):
27 27 self.lnode = lnode
28 28 self.rnode = rnode
29 29
30 30 class transplants(object):
31 31 def __init__(self, path=None, transplantfile=None, opener=None):
32 32 self.path = path
33 33 self.transplantfile = transplantfile
34 34 self.opener = opener
35 35
36 36 if not opener:
37 37 self.opener = scmutil.opener(self.path)
38 38 self.transplants = {}
39 39 self.dirty = False
40 40 self.read()
41 41
42 42 def read(self):
43 43 abspath = os.path.join(self.path, self.transplantfile)
44 44 if self.transplantfile and os.path.exists(abspath):
45 45 for line in self.opener.read(self.transplantfile).splitlines():
46 46 lnode, rnode = map(revlog.bin, line.split(':'))
47 47 list = self.transplants.setdefault(rnode, [])
48 48 list.append(transplantentry(lnode, rnode))
49 49
50 50 def write(self):
51 51 if self.dirty and self.transplantfile:
52 52 if not os.path.isdir(self.path):
53 53 os.mkdir(self.path)
54 54 fp = self.opener(self.transplantfile, 'w')
55 55 for list in self.transplants.itervalues():
56 56 for t in list:
57 57 l, r = map(revlog.hex, (t.lnode, t.rnode))
58 58 fp.write(l + ':' + r + '\n')
59 59 fp.close()
60 60 self.dirty = False
61 61
62 62 def get(self, rnode):
63 63 return self.transplants.get(rnode) or []
64 64
65 65 def set(self, lnode, rnode):
66 66 list = self.transplants.setdefault(rnode, [])
67 67 list.append(transplantentry(lnode, rnode))
68 68 self.dirty = True
69 69
70 70 def remove(self, transplant):
71 71 list = self.transplants.get(transplant.rnode)
72 72 if list:
73 73 del list[list.index(transplant)]
74 74 self.dirty = True
75 75
76 76 class transplanter(object):
77 77 def __init__(self, ui, repo):
78 78 self.ui = ui
79 79 self.path = repo.join('transplant')
80 80 self.opener = scmutil.opener(self.path)
81 81 self.transplants = transplants(self.path, 'transplants',
82 82 opener=self.opener)
83 83
84 84 def applied(self, repo, node, parent):
85 85 '''returns True if a node is already an ancestor of parent
86 86 or has already been transplanted'''
87 87 if hasnode(repo, node):
88 88 if node in repo.changelog.reachable(parent, stop=node):
89 89 return True
90 90 for t in self.transplants.get(node):
91 91 # it might have been stripped
92 92 if not hasnode(repo, t.lnode):
93 93 self.transplants.remove(t)
94 94 return False
95 95 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
96 96 return True
97 97 return False
98 98
99 99 def apply(self, repo, source, revmap, merges, opts={}):
100 100 '''apply the revisions in revmap one by one in revision order'''
101 101 revs = sorted(revmap)
102 102 p1, p2 = repo.dirstate.parents()
103 103 pulls = []
104 104 diffopts = patch.diffopts(self.ui, opts)
105 105 diffopts.git = True
106 106
107 107 lock = wlock = None
108 108 try:
109 109 wlock = repo.wlock()
110 110 lock = repo.lock()
111 111 for rev in revs:
112 112 node = revmap[rev]
113 113 revstr = '%s:%s' % (rev, revlog.short(node))
114 114
115 115 if self.applied(repo, node, p1):
116 116 self.ui.warn(_('skipping already applied revision %s\n') %
117 117 revstr)
118 118 continue
119 119
120 120 parents = source.changelog.parents(node)
121 121 if not opts.get('filter'):
122 122 # If the changeset parent is the same as the
123 123 # wdir's parent, just pull it.
124 124 if parents[0] == p1:
125 125 pulls.append(node)
126 126 p1 = node
127 127 continue
128 128 if pulls:
129 129 if source != repo:
130 130 repo.pull(source, heads=pulls)
131 131 merge.update(repo, pulls[-1], False, False, None)
132 132 p1, p2 = repo.dirstate.parents()
133 133 pulls = []
134 134
135 135 domerge = False
136 136 if node in merges:
137 137 # pulling all the merge revs at once would mean we
138 138 # couldn't transplant after the latest even if
139 139 # transplants before them fail.
140 140 domerge = True
141 141 if not hasnode(repo, node):
142 142 repo.pull(source, heads=[node])
143 143
144 144 if parents[1] != revlog.nullid:
145 145 self.ui.note(_('skipping merge changeset %s:%s\n')
146 146 % (rev, revlog.short(node)))
147 147 patchfile = None
148 148 else:
149 149 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
150 150 fp = os.fdopen(fd, 'w')
151 151 gen = patch.diff(source, parents[0], node, opts=diffopts)
152 152 for chunk in gen:
153 153 fp.write(chunk)
154 154 fp.close()
155 155
156 156 del revmap[rev]
157 157 if patchfile or domerge:
158 158 try:
159 159 n = self.applyone(repo, node,
160 160 source.changelog.read(node),
161 161 patchfile, merge=domerge,
162 162 log=opts.get('log'),
163 163 filter=opts.get('filter'))
164 164 if n and domerge:
165 165 self.ui.status(_('%s merged at %s\n') % (revstr,
166 166 revlog.short(n)))
167 167 elif n:
168 168 self.ui.status(_('%s transplanted to %s\n')
169 169 % (revlog.short(node),
170 170 revlog.short(n)))
171 171 finally:
172 172 if patchfile:
173 173 os.unlink(patchfile)
174 174 if pulls:
175 175 repo.pull(source, heads=pulls)
176 176 merge.update(repo, pulls[-1], False, False, None)
177 177 finally:
178 178 self.saveseries(revmap, merges)
179 179 self.transplants.write()
180 180 lock.release()
181 181 wlock.release()
182 182
183 183 def filter(self, filter, node, changelog, patchfile):
184 184 '''arbitrarily rewrite changeset before applying it'''
185 185
186 186 self.ui.status(_('filtering %s\n') % patchfile)
187 187 user, date, msg = (changelog[1], changelog[2], changelog[4])
188 188 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
189 189 fp = os.fdopen(fd, 'w')
190 190 fp.write("# HG changeset patch\n")
191 191 fp.write("# User %s\n" % user)
192 192 fp.write("# Date %d %d\n" % date)
193 193 fp.write(msg + '\n')
194 194 fp.close()
195 195
196 196 try:
197 197 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
198 198 util.shellquote(patchfile)),
199 199 environ={'HGUSER': changelog[1],
200 200 'HGREVISION': revlog.hex(node),
201 201 },
202 202 onerr=util.Abort, errprefix=_('filter failed'))
203 203 user, date, msg = self.parselog(file(headerfile))[1:4]
204 204 finally:
205 205 os.unlink(headerfile)
206 206
207 207 return (user, date, msg)
208 208
209 209 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
210 210 filter=None):
211 211 '''apply the patch in patchfile to the repository as a transplant'''
212 212 (manifest, user, (time, timezone), files, message) = cl[:5]
213 213 date = "%d %d" % (time, timezone)
214 214 extra = {'transplant_source': node}
215 215 if filter:
216 216 (user, date, message) = self.filter(filter, node, cl, patchfile)
217 217
218 218 if log:
219 219 # we don't translate messages inserted into commits
220 220 message += '\n(transplanted from %s)' % revlog.hex(node)
221 221
222 222 self.ui.status(_('applying %s\n') % revlog.short(node))
223 223 self.ui.note('%s %s\n%s\n' % (user, date, message))
224 224
225 225 if not patchfile and not merge:
226 226 raise util.Abort(_('can only omit patchfile if merging'))
227 227 if patchfile:
228 228 try:
229 229 files = {}
230 patch.patch(self.ui, repo, patchfile, cwd=repo.root,
231 files=files, eolmode=None)
230 patch.patch(self.ui, repo, patchfile, files=files, eolmode=None)
232 231 files = list(files)
233 232 if not files:
234 233 self.ui.warn(_('%s: empty changeset') % revlog.hex(node))
235 234 return None
236 235 except Exception, inst:
237 236 seriespath = os.path.join(self.path, 'series')
238 237 if os.path.exists(seriespath):
239 238 os.unlink(seriespath)
240 239 p1 = repo.dirstate.p1()
241 240 p2 = node
242 241 self.log(user, date, message, p1, p2, merge=merge)
243 242 self.ui.write(str(inst) + '\n')
244 243 raise util.Abort(_('fix up the merge and run '
245 244 'hg transplant --continue'))
246 245 else:
247 246 files = None
248 247 if merge:
249 248 p1, p2 = repo.dirstate.parents()
250 249 repo.dirstate.setparents(p1, node)
251 250 m = match.always(repo.root, '')
252 251 else:
253 252 m = match.exact(repo.root, '', files)
254 253
255 254 n = repo.commit(message, user, date, extra=extra, match=m)
256 255 if not n:
257 256 # Crash here to prevent an unclear crash later, in
258 257 # transplants.write(). This can happen if patch.patch()
259 258 # does nothing but claims success or if repo.status() fails
260 259 # to report changes done by patch.patch(). These both
261 260 # appear to be bugs in other parts of Mercurial, but dying
262 261 # here, as soon as we can detect the problem, is preferable
263 262 # to silently dropping changesets on the floor.
264 263 raise RuntimeError('nothing committed after transplant')
265 264 if not merge:
266 265 self.transplants.set(n, node)
267 266
268 267 return n
269 268
270 269 def resume(self, repo, source, opts=None):
271 270 '''recover last transaction and apply remaining changesets'''
272 271 if os.path.exists(os.path.join(self.path, 'journal')):
273 272 n, node = self.recover(repo)
274 273 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
275 274 revlog.short(n)))
276 275 seriespath = os.path.join(self.path, 'series')
277 276 if not os.path.exists(seriespath):
278 277 self.transplants.write()
279 278 return
280 279 nodes, merges = self.readseries()
281 280 revmap = {}
282 281 for n in nodes:
283 282 revmap[source.changelog.rev(n)] = n
284 283 os.unlink(seriespath)
285 284
286 285 self.apply(repo, source, revmap, merges, opts)
287 286
288 287 def recover(self, repo):
289 288 '''commit working directory using journal metadata'''
290 289 node, user, date, message, parents = self.readlog()
291 290 merge = len(parents) == 2
292 291
293 292 if not user or not date or not message or not parents[0]:
294 293 raise util.Abort(_('transplant log file is corrupt'))
295 294
296 295 extra = {'transplant_source': node}
297 296 wlock = repo.wlock()
298 297 try:
299 298 p1, p2 = repo.dirstate.parents()
300 299 if p1 != parents[0]:
301 300 raise util.Abort(
302 301 _('working dir not at transplant parent %s') %
303 302 revlog.hex(parents[0]))
304 303 if merge:
305 304 repo.dirstate.setparents(p1, parents[1])
306 305 n = repo.commit(message, user, date, extra=extra)
307 306 if not n:
308 307 raise util.Abort(_('commit failed'))
309 308 if not merge:
310 309 self.transplants.set(n, node)
311 310 self.unlog()
312 311
313 312 return n, node
314 313 finally:
315 314 wlock.release()
316 315
317 316 def readseries(self):
318 317 nodes = []
319 318 merges = []
320 319 cur = nodes
321 320 for line in self.opener.read('series').splitlines():
322 321 if line.startswith('# Merges'):
323 322 cur = merges
324 323 continue
325 324 cur.append(revlog.bin(line))
326 325
327 326 return (nodes, merges)
328 327
329 328 def saveseries(self, revmap, merges):
330 329 if not revmap:
331 330 return
332 331
333 332 if not os.path.isdir(self.path):
334 333 os.mkdir(self.path)
335 334 series = self.opener('series', 'w')
336 335 for rev in sorted(revmap):
337 336 series.write(revlog.hex(revmap[rev]) + '\n')
338 337 if merges:
339 338 series.write('# Merges\n')
340 339 for m in merges:
341 340 series.write(revlog.hex(m) + '\n')
342 341 series.close()
343 342
344 343 def parselog(self, fp):
345 344 parents = []
346 345 message = []
347 346 node = revlog.nullid
348 347 inmsg = False
349 348 user = None
350 349 date = None
351 350 for line in fp.read().splitlines():
352 351 if inmsg:
353 352 message.append(line)
354 353 elif line.startswith('# User '):
355 354 user = line[7:]
356 355 elif line.startswith('# Date '):
357 356 date = line[7:]
358 357 elif line.startswith('# Node ID '):
359 358 node = revlog.bin(line[10:])
360 359 elif line.startswith('# Parent '):
361 360 parents.append(revlog.bin(line[9:]))
362 361 elif not line.startswith('# '):
363 362 inmsg = True
364 363 message.append(line)
365 364 if None in (user, date):
366 365 raise util.Abort(_("filter corrupted changeset (no user or date)"))
367 366 return (node, user, date, '\n'.join(message), parents)
368 367
369 368 def log(self, user, date, message, p1, p2, merge=False):
370 369 '''journal changelog metadata for later recover'''
371 370
372 371 if not os.path.isdir(self.path):
373 372 os.mkdir(self.path)
374 373 fp = self.opener('journal', 'w')
375 374 fp.write('# User %s\n' % user)
376 375 fp.write('# Date %s\n' % date)
377 376 fp.write('# Node ID %s\n' % revlog.hex(p2))
378 377 fp.write('# Parent ' + revlog.hex(p1) + '\n')
379 378 if merge:
380 379 fp.write('# Parent ' + revlog.hex(p2) + '\n')
381 380 fp.write(message.rstrip() + '\n')
382 381 fp.close()
383 382
384 383 def readlog(self):
385 384 return self.parselog(self.opener('journal'))
386 385
387 386 def unlog(self):
388 387 '''remove changelog journal'''
389 388 absdst = os.path.join(self.path, 'journal')
390 389 if os.path.exists(absdst):
391 390 os.unlink(absdst)
392 391
393 392 def transplantfilter(self, repo, source, root):
394 393 def matchfn(node):
395 394 if self.applied(repo, node, root):
396 395 return False
397 396 if source.changelog.parents(node)[1] != revlog.nullid:
398 397 return False
399 398 extra = source.changelog.read(node)[5]
400 399 cnode = extra.get('transplant_source')
401 400 if cnode and self.applied(repo, cnode, root):
402 401 return False
403 402 return True
404 403
405 404 return matchfn
406 405
407 406 def hasnode(repo, node):
408 407 try:
409 408 return repo.changelog.rev(node) is not None
410 409 except error.RevlogError:
411 410 return False
412 411
413 412 def browserevs(ui, repo, nodes, opts):
414 413 '''interactively transplant changesets'''
415 414 def browsehelp(ui):
416 415 ui.write(_('y: transplant this changeset\n'
417 416 'n: skip this changeset\n'
418 417 'm: merge at this changeset\n'
419 418 'p: show patch\n'
420 419 'c: commit selected changesets\n'
421 420 'q: cancel transplant\n'
422 421 '?: show this help\n'))
423 422
424 423 displayer = cmdutil.show_changeset(ui, repo, opts)
425 424 transplants = []
426 425 merges = []
427 426 for node in nodes:
428 427 displayer.show(repo[node])
429 428 action = None
430 429 while not action:
431 430 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
432 431 if action == '?':
433 432 browsehelp(ui)
434 433 action = None
435 434 elif action == 'p':
436 435 parent = repo.changelog.parents(node)[0]
437 436 for chunk in patch.diff(repo, parent, node):
438 437 ui.write(chunk)
439 438 action = None
440 439 elif action not in ('y', 'n', 'm', 'c', 'q'):
441 440 ui.write(_('no such option\n'))
442 441 action = None
443 442 if action == 'y':
444 443 transplants.append(node)
445 444 elif action == 'm':
446 445 merges.append(node)
447 446 elif action == 'c':
448 447 break
449 448 elif action == 'q':
450 449 transplants = ()
451 450 merges = ()
452 451 break
453 452 displayer.close()
454 453 return (transplants, merges)
455 454
456 455 @command('transplant',
457 456 [('s', 'source', '', _('pull patches from REPO'), _('REPO')),
458 457 ('b', 'branch', [],
459 458 _('pull patches from branch BRANCH'), _('BRANCH')),
460 459 ('a', 'all', None, _('pull all changesets up to BRANCH')),
461 460 ('p', 'prune', [], _('skip over REV'), _('REV')),
462 461 ('m', 'merge', [], _('merge at REV'), _('REV')),
463 462 ('', 'log', None, _('append transplant info to log message')),
464 463 ('c', 'continue', None, _('continue last transplant session '
465 464 'after repair')),
466 465 ('', 'filter', '',
467 466 _('filter changesets through command'), _('CMD'))],
468 467 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
469 468 '[-m REV] [REV]...'))
470 469 def transplant(ui, repo, *revs, **opts):
471 470 '''transplant changesets from another branch
472 471
473 472 Selected changesets will be applied on top of the current working
474 473 directory with the log of the original changeset. The changesets
475 474 are copied and will thus appear twice in the history. Use the
476 475 rebase extension instead if you want to move a whole branch of
477 476 unpublished changesets.
478 477
479 478 If --log is specified, log messages will have a comment appended
480 479 of the form::
481 480
482 481 (transplanted from CHANGESETHASH)
483 482
484 483 You can rewrite the changelog message with the --filter option.
485 484 Its argument will be invoked with the current changelog message as
486 485 $1 and the patch as $2.
487 486
488 487 If --source/-s is specified, selects changesets from the named
489 488 repository. If --branch/-b is specified, selects changesets from
490 489 the branch holding the named revision, up to that revision. If
491 490 --all/-a is specified, all changesets on the branch will be
492 491 transplanted, otherwise you will be prompted to select the
493 492 changesets you want.
494 493
495 494 :hg:`transplant --branch REVISION --all` will transplant the
496 495 selected branch (up to the named revision) onto your current
497 496 working directory.
498 497
499 498 You can optionally mark selected transplanted changesets as merge
500 499 changesets. You will not be prompted to transplant any ancestors
501 500 of a merged transplant, and you can merge descendants of them
502 501 normally instead of transplanting them.
503 502
504 503 If no merges or revisions are provided, :hg:`transplant` will
505 504 start an interactive changeset browser.
506 505
507 506 If a changeset application fails, you can fix the merge by hand
508 507 and then resume where you left off by calling :hg:`transplant
509 508 --continue/-c`.
510 509 '''
511 510 def incwalk(repo, csets, match=util.always):
512 511 for node in csets:
513 512 if match(node):
514 513 yield node
515 514
516 515 def transplantwalk(repo, root, branches, match=util.always):
517 516 if not branches:
518 517 branches = repo.heads()
519 518 ancestors = []
520 519 for branch in branches:
521 520 ancestors.append(repo.changelog.ancestor(root, branch))
522 521 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
523 522 if match(node):
524 523 yield node
525 524
526 525 def checkopts(opts, revs):
527 526 if opts.get('continue'):
528 527 if opts.get('branch') or opts.get('all') or opts.get('merge'):
529 528 raise util.Abort(_('--continue is incompatible with '
530 529 'branch, all or merge'))
531 530 return
532 531 if not (opts.get('source') or revs or
533 532 opts.get('merge') or opts.get('branch')):
534 533 raise util.Abort(_('no source URL, branch tag or revision '
535 534 'list provided'))
536 535 if opts.get('all'):
537 536 if not opts.get('branch'):
538 537 raise util.Abort(_('--all requires a branch revision'))
539 538 if revs:
540 539 raise util.Abort(_('--all is incompatible with a '
541 540 'revision list'))
542 541
543 542 checkopts(opts, revs)
544 543
545 544 if not opts.get('log'):
546 545 opts['log'] = ui.config('transplant', 'log')
547 546 if not opts.get('filter'):
548 547 opts['filter'] = ui.config('transplant', 'filter')
549 548
550 549 tp = transplanter(ui, repo)
551 550
552 551 p1, p2 = repo.dirstate.parents()
553 552 if len(repo) > 0 and p1 == revlog.nullid:
554 553 raise util.Abort(_('no revision checked out'))
555 554 if not opts.get('continue'):
556 555 if p2 != revlog.nullid:
557 556 raise util.Abort(_('outstanding uncommitted merges'))
558 557 m, a, r, d = repo.status()[:4]
559 558 if m or a or r or d:
560 559 raise util.Abort(_('outstanding local changes'))
561 560
562 561 sourcerepo = opts.get('source')
563 562 if sourcerepo:
564 563 source = hg.repository(ui, ui.expandpath(sourcerepo))
565 564 branches = map(source.lookup, opts.get('branch', ()))
566 565 source, csets, cleanupfn = bundlerepo.getremotechanges(ui, repo, source,
567 566 onlyheads=branches, force=True)
568 567 else:
569 568 source = repo
570 569 branches = map(source.lookup, opts.get('branch', ()))
571 570 cleanupfn = None
572 571
573 572 try:
574 573 if opts.get('continue'):
575 574 tp.resume(repo, source, opts)
576 575 return
577 576
578 577 tf = tp.transplantfilter(repo, source, p1)
579 578 if opts.get('prune'):
580 579 prune = [source.lookup(r)
581 580 for r in scmutil.revrange(source, opts.get('prune'))]
582 581 matchfn = lambda x: tf(x) and x not in prune
583 582 else:
584 583 matchfn = tf
585 584 merges = map(source.lookup, opts.get('merge', ()))
586 585 revmap = {}
587 586 if revs:
588 587 for r in scmutil.revrange(source, revs):
589 588 revmap[int(r)] = source.lookup(r)
590 589 elif opts.get('all') or not merges:
591 590 if source != repo:
592 591 alltransplants = incwalk(source, csets, match=matchfn)
593 592 else:
594 593 alltransplants = transplantwalk(source, p1, branches,
595 594 match=matchfn)
596 595 if opts.get('all'):
597 596 revs = alltransplants
598 597 else:
599 598 revs, newmerges = browserevs(ui, source, alltransplants, opts)
600 599 merges.extend(newmerges)
601 600 for r in revs:
602 601 revmap[source.changelog.rev(r)] = r
603 602 for r in merges:
604 603 revmap[source.changelog.rev(r)] = r
605 604
606 605 tp.apply(repo, source, revmap, merges, opts)
607 606 finally:
608 607 if cleanupfn:
609 608 cleanupfn()
610 609
611 610 def revsettransplanted(repo, subset, x):
612 611 """``transplanted([set])``
613 612 Transplanted changesets in set, or all transplanted changesets.
614 613 """
615 614 if x:
616 615 s = revset.getset(repo, subset, x)
617 616 else:
618 617 s = subset
619 618 return [r for r in s if repo[r].extra().get('transplant_source')]
620 619
621 620 def kwtransplanted(repo, ctx, **args):
622 621 """:transplanted: String. The node identifier of the transplanted
623 622 changeset if any."""
624 623 n = ctx.extra().get('transplant_source')
625 624 return n and revlog.hex(n) or ''
626 625
627 626 def extsetup(ui):
628 627 revset.symbols['transplanted'] = revsettransplanted
629 628 templatekw.keywords['transplanted'] = kwtransplanted
630 629
631 630 # tell hggettext to extract docstrings from these functions:
632 631 i18nfunctions = [revsettransplanted, kwtransplanted]
@@ -1,5051 +1,5051 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import hex, bin, nullid, nullrev, short
9 9 from lock import release
10 10 from i18n import _, gettext
11 11 import os, re, sys, difflib, time, tempfile
12 12 import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
13 13 import patch, help, url, encoding, templatekw, discovery
14 14 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
15 15 import merge as mergemod
16 16 import minirst, revset
17 17 import dagparser, context, simplemerge
18 18 import random, setdiscovery, treediscovery, dagutil
19 19
20 20 table = {}
21 21
22 22 command = cmdutil.command(table)
23 23
24 24 # common command options
25 25
26 26 globalopts = [
27 27 ('R', 'repository', '',
28 28 _('repository root directory or name of overlay bundle file'),
29 29 _('REPO')),
30 30 ('', 'cwd', '',
31 31 _('change working directory'), _('DIR')),
32 32 ('y', 'noninteractive', None,
33 33 _('do not prompt, assume \'yes\' for any required answers')),
34 34 ('q', 'quiet', None, _('suppress output')),
35 35 ('v', 'verbose', None, _('enable additional output')),
36 36 ('', 'config', [],
37 37 _('set/override config option (use \'section.name=value\')'),
38 38 _('CONFIG')),
39 39 ('', 'debug', None, _('enable debugging output')),
40 40 ('', 'debugger', None, _('start debugger')),
41 41 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
42 42 _('ENCODE')),
43 43 ('', 'encodingmode', encoding.encodingmode,
44 44 _('set the charset encoding mode'), _('MODE')),
45 45 ('', 'traceback', None, _('always print a traceback on exception')),
46 46 ('', 'time', None, _('time how long the command takes')),
47 47 ('', 'profile', None, _('print command execution profile')),
48 48 ('', 'version', None, _('output version information and exit')),
49 49 ('h', 'help', None, _('display help and exit')),
50 50 ]
51 51
52 52 dryrunopts = [('n', 'dry-run', None,
53 53 _('do not perform actions, just print output'))]
54 54
55 55 remoteopts = [
56 56 ('e', 'ssh', '',
57 57 _('specify ssh command to use'), _('CMD')),
58 58 ('', 'remotecmd', '',
59 59 _('specify hg command to run on the remote side'), _('CMD')),
60 60 ('', 'insecure', None,
61 61 _('do not verify server certificate (ignoring web.cacerts config)')),
62 62 ]
63 63
64 64 walkopts = [
65 65 ('I', 'include', [],
66 66 _('include names matching the given patterns'), _('PATTERN')),
67 67 ('X', 'exclude', [],
68 68 _('exclude names matching the given patterns'), _('PATTERN')),
69 69 ]
70 70
71 71 commitopts = [
72 72 ('m', 'message', '',
73 73 _('use text as commit message'), _('TEXT')),
74 74 ('l', 'logfile', '',
75 75 _('read commit message from file'), _('FILE')),
76 76 ]
77 77
78 78 commitopts2 = [
79 79 ('d', 'date', '',
80 80 _('record the specified date as commit date'), _('DATE')),
81 81 ('u', 'user', '',
82 82 _('record the specified user as committer'), _('USER')),
83 83 ]
84 84
85 85 templateopts = [
86 86 ('', 'style', '',
87 87 _('display using template map file'), _('STYLE')),
88 88 ('', 'template', '',
89 89 _('display with template'), _('TEMPLATE')),
90 90 ]
91 91
92 92 logopts = [
93 93 ('p', 'patch', None, _('show patch')),
94 94 ('g', 'git', None, _('use git extended diff format')),
95 95 ('l', 'limit', '',
96 96 _('limit number of changes displayed'), _('NUM')),
97 97 ('M', 'no-merges', None, _('do not show merges')),
98 98 ('', 'stat', None, _('output diffstat-style summary of changes')),
99 99 ] + templateopts
100 100
101 101 diffopts = [
102 102 ('a', 'text', None, _('treat all files as text')),
103 103 ('g', 'git', None, _('use git extended diff format')),
104 104 ('', 'nodates', None, _('omit dates from diff headers'))
105 105 ]
106 106
107 107 diffopts2 = [
108 108 ('p', 'show-function', None, _('show which function each change is in')),
109 109 ('', 'reverse', None, _('produce a diff that undoes the changes')),
110 110 ('w', 'ignore-all-space', None,
111 111 _('ignore white space when comparing lines')),
112 112 ('b', 'ignore-space-change', None,
113 113 _('ignore changes in the amount of white space')),
114 114 ('B', 'ignore-blank-lines', None,
115 115 _('ignore changes whose lines are all blank')),
116 116 ('U', 'unified', '',
117 117 _('number of lines of context to show'), _('NUM')),
118 118 ('', 'stat', None, _('output diffstat-style summary of changes')),
119 119 ]
120 120
121 121 similarityopts = [
122 122 ('s', 'similarity', '',
123 123 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
124 124 ]
125 125
126 126 subrepoopts = [
127 127 ('S', 'subrepos', None,
128 128 _('recurse into subrepositories'))
129 129 ]
130 130
131 131 # Commands start here, listed alphabetically
132 132
133 133 @command('^add',
134 134 walkopts + subrepoopts + dryrunopts,
135 135 _('[OPTION]... [FILE]...'))
136 136 def add(ui, repo, *pats, **opts):
137 137 """add the specified files on the next commit
138 138
139 139 Schedule files to be version controlled and added to the
140 140 repository.
141 141
142 142 The files will be added to the repository at the next commit. To
143 143 undo an add before that, see :hg:`forget`.
144 144
145 145 If no names are given, add all files to the repository.
146 146
147 147 .. container:: verbose
148 148
149 149 An example showing how new (unknown) files are added
150 150 automatically by :hg:`add`::
151 151
152 152 $ ls
153 153 foo.c
154 154 $ hg status
155 155 ? foo.c
156 156 $ hg add
157 157 adding foo.c
158 158 $ hg status
159 159 A foo.c
160 160
161 161 Returns 0 if all files are successfully added.
162 162 """
163 163
164 164 m = scmutil.match(repo, pats, opts)
165 165 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
166 166 opts.get('subrepos'), prefix="")
167 167 return rejected and 1 or 0
168 168
169 169 @command('addremove',
170 170 similarityopts + walkopts + dryrunopts,
171 171 _('[OPTION]... [FILE]...'))
172 172 def addremove(ui, repo, *pats, **opts):
173 173 """add all new files, delete all missing files
174 174
175 175 Add all new files and remove all missing files from the
176 176 repository.
177 177
178 178 New files are ignored if they match any of the patterns in
179 179 ``.hgignore``. As with add, these changes take effect at the next
180 180 commit.
181 181
182 182 Use the -s/--similarity option to detect renamed files. With a
183 183 parameter greater than 0, this compares every removed file with
184 184 every added file and records those similar enough as renames. This
185 185 option takes a percentage between 0 (disabled) and 100 (files must
186 186 be identical) as its parameter. Detecting renamed files this way
187 187 can be expensive. After using this option, :hg:`status -C` can be
188 188 used to check which files were identified as moved or renamed.
189 189
190 190 Returns 0 if all files are successfully added.
191 191 """
192 192 try:
193 193 sim = float(opts.get('similarity') or 100)
194 194 except ValueError:
195 195 raise util.Abort(_('similarity must be a number'))
196 196 if sim < 0 or sim > 100:
197 197 raise util.Abort(_('similarity must be between 0 and 100'))
198 198 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
199 199
200 200 @command('^annotate|blame',
201 201 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
202 202 ('', 'follow', None,
203 203 _('follow copies/renames and list the filename (DEPRECATED)')),
204 204 ('', 'no-follow', None, _("don't follow copies and renames")),
205 205 ('a', 'text', None, _('treat all files as text')),
206 206 ('u', 'user', None, _('list the author (long with -v)')),
207 207 ('f', 'file', None, _('list the filename')),
208 208 ('d', 'date', None, _('list the date (short with -q)')),
209 209 ('n', 'number', None, _('list the revision number (default)')),
210 210 ('c', 'changeset', None, _('list the changeset')),
211 211 ('l', 'line-number', None, _('show line number at the first appearance'))
212 212 ] + walkopts,
213 213 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
214 214 def annotate(ui, repo, *pats, **opts):
215 215 """show changeset information by line for each file
216 216
217 217 List changes in files, showing the revision id responsible for
218 218 each line
219 219
220 220 This command is useful for discovering when a change was made and
221 221 by whom.
222 222
223 223 Without the -a/--text option, annotate will avoid processing files
224 224 it detects as binary. With -a, annotate will annotate the file
225 225 anyway, although the results will probably be neither useful
226 226 nor desirable.
227 227
228 228 Returns 0 on success.
229 229 """
230 230 if opts.get('follow'):
231 231 # --follow is deprecated and now just an alias for -f/--file
232 232 # to mimic the behavior of Mercurial before version 1.5
233 233 opts['file'] = True
234 234
235 235 datefunc = ui.quiet and util.shortdate or util.datestr
236 236 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
237 237
238 238 if not pats:
239 239 raise util.Abort(_('at least one filename or pattern is required'))
240 240
241 241 opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
242 242 ('number', ' ', lambda x: str(x[0].rev())),
243 243 ('changeset', ' ', lambda x: short(x[0].node())),
244 244 ('date', ' ', getdate),
245 245 ('file', ' ', lambda x: x[0].path()),
246 246 ('line_number', ':', lambda x: str(x[1])),
247 247 ]
248 248
249 249 if (not opts.get('user') and not opts.get('changeset')
250 250 and not opts.get('date') and not opts.get('file')):
251 251 opts['number'] = True
252 252
253 253 linenumber = opts.get('line_number') is not None
254 254 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
255 255 raise util.Abort(_('at least one of -n/-c is required for -l'))
256 256
257 257 funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
258 258 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
259 259
260 260 def bad(x, y):
261 261 raise util.Abort("%s: %s" % (x, y))
262 262
263 263 ctx = scmutil.revsingle(repo, opts.get('rev'))
264 264 m = scmutil.match(repo, pats, opts)
265 265 m.bad = bad
266 266 follow = not opts.get('no_follow')
267 267 for abs in ctx.walk(m):
268 268 fctx = ctx[abs]
269 269 if not opts.get('text') and util.binary(fctx.data()):
270 270 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
271 271 continue
272 272
273 273 lines = fctx.annotate(follow=follow, linenumber=linenumber)
274 274 pieces = []
275 275
276 276 for f, sep in funcmap:
277 277 l = [f(n) for n, dummy in lines]
278 278 if l:
279 279 sized = [(x, encoding.colwidth(x)) for x in l]
280 280 ml = max([w for x, w in sized])
281 281 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
282 282 for x, w in sized])
283 283
284 284 if pieces:
285 285 for p, l in zip(zip(*pieces), lines):
286 286 ui.write("%s: %s" % ("".join(p), l[1]))
287 287
288 288 @command('archive',
289 289 [('', 'no-decode', None, _('do not pass files through decoders')),
290 290 ('p', 'prefix', '', _('directory prefix for files in archive'),
291 291 _('PREFIX')),
292 292 ('r', 'rev', '', _('revision to distribute'), _('REV')),
293 293 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
294 294 ] + subrepoopts + walkopts,
295 295 _('[OPTION]... DEST'))
296 296 def archive(ui, repo, dest, **opts):
297 297 '''create an unversioned archive of a repository revision
298 298
299 299 By default, the revision used is the parent of the working
300 300 directory; use -r/--rev to specify a different revision.
301 301
302 302 The archive type is automatically detected based on file
303 303 extension (or override using -t/--type).
304 304
305 305 Valid types are:
306 306
307 307 :``files``: a directory full of files (default)
308 308 :``tar``: tar archive, uncompressed
309 309 :``tbz2``: tar archive, compressed using bzip2
310 310 :``tgz``: tar archive, compressed using gzip
311 311 :``uzip``: zip archive, uncompressed
312 312 :``zip``: zip archive, compressed using deflate
313 313
314 314 The exact name of the destination archive or directory is given
315 315 using a format string; see :hg:`help export` for details.
316 316
317 317 Each member added to an archive file has a directory prefix
318 318 prepended. Use -p/--prefix to specify a format string for the
319 319 prefix. The default is the basename of the archive, with suffixes
320 320 removed.
321 321
322 322 Returns 0 on success.
323 323 '''
324 324
325 325 ctx = scmutil.revsingle(repo, opts.get('rev'))
326 326 if not ctx:
327 327 raise util.Abort(_('no working directory: please specify a revision'))
328 328 node = ctx.node()
329 329 dest = cmdutil.makefilename(repo, dest, node)
330 330 if os.path.realpath(dest) == repo.root:
331 331 raise util.Abort(_('repository root cannot be destination'))
332 332
333 333 kind = opts.get('type') or archival.guesskind(dest) or 'files'
334 334 prefix = opts.get('prefix')
335 335
336 336 if dest == '-':
337 337 if kind == 'files':
338 338 raise util.Abort(_('cannot archive plain files to stdout'))
339 339 dest = sys.stdout
340 340 if not prefix:
341 341 prefix = os.path.basename(repo.root) + '-%h'
342 342
343 343 prefix = cmdutil.makefilename(repo, prefix, node)
344 344 matchfn = scmutil.match(repo, [], opts)
345 345 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
346 346 matchfn, prefix, subrepos=opts.get('subrepos'))
347 347
348 348 @command('backout',
349 349 [('', 'merge', None, _('merge with old dirstate parent after backout')),
350 350 ('', 'parent', '', _('parent to choose when backing out merge'), _('REV')),
351 351 ('t', 'tool', '', _('specify merge tool')),
352 352 ('r', 'rev', '', _('revision to backout'), _('REV')),
353 353 ] + walkopts + commitopts + commitopts2,
354 354 _('[OPTION]... [-r] REV'))
355 355 def backout(ui, repo, node=None, rev=None, **opts):
356 356 '''reverse effect of earlier changeset
357 357
358 358 Prepare a new changeset with the effect of REV undone in the
359 359 current working directory.
360 360
361 361 If REV is the parent of the working directory, then this new changeset
362 362 is committed automatically. Otherwise, hg needs to merge the
363 363 changes and the merged result is left uncommitted.
364 364
365 365 By default, the pending changeset will have one parent,
366 366 maintaining a linear history. With --merge, the pending changeset
367 367 will instead have two parents: the old parent of the working
368 368 directory and a new child of REV that simply undoes REV.
369 369
370 370 Before version 1.7, the behavior without --merge was equivalent to
371 371 specifying --merge followed by :hg:`update --clean .` to cancel
372 372 the merge and leave the child of REV as a head to be merged
373 373 separately.
374 374
375 375 See :hg:`help dates` for a list of formats valid for -d/--date.
376 376
377 377 Returns 0 on success.
378 378 '''
379 379 if rev and node:
380 380 raise util.Abort(_("please specify just one revision"))
381 381
382 382 if not rev:
383 383 rev = node
384 384
385 385 if not rev:
386 386 raise util.Abort(_("please specify a revision to backout"))
387 387
388 388 date = opts.get('date')
389 389 if date:
390 390 opts['date'] = util.parsedate(date)
391 391
392 392 cmdutil.bailifchanged(repo)
393 393 node = scmutil.revsingle(repo, rev).node()
394 394
395 395 op1, op2 = repo.dirstate.parents()
396 396 a = repo.changelog.ancestor(op1, node)
397 397 if a != node:
398 398 raise util.Abort(_('cannot backout change on a different branch'))
399 399
400 400 p1, p2 = repo.changelog.parents(node)
401 401 if p1 == nullid:
402 402 raise util.Abort(_('cannot backout a change with no parents'))
403 403 if p2 != nullid:
404 404 if not opts.get('parent'):
405 405 raise util.Abort(_('cannot backout a merge changeset without '
406 406 '--parent'))
407 407 p = repo.lookup(opts['parent'])
408 408 if p not in (p1, p2):
409 409 raise util.Abort(_('%s is not a parent of %s') %
410 410 (short(p), short(node)))
411 411 parent = p
412 412 else:
413 413 if opts.get('parent'):
414 414 raise util.Abort(_('cannot use --parent on non-merge changeset'))
415 415 parent = p1
416 416
417 417 # the backout should appear on the same branch
418 418 branch = repo.dirstate.branch()
419 419 hg.clean(repo, node, show_stats=False)
420 420 repo.dirstate.setbranch(branch)
421 421 revert_opts = opts.copy()
422 422 revert_opts['date'] = None
423 423 revert_opts['all'] = True
424 424 revert_opts['rev'] = hex(parent)
425 425 revert_opts['no_backup'] = None
426 426 revert(ui, repo, **revert_opts)
427 427 if not opts.get('merge') and op1 != node:
428 428 try:
429 429 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
430 430 return hg.update(repo, op1)
431 431 finally:
432 432 ui.setconfig('ui', 'forcemerge', '')
433 433
434 434 commit_opts = opts.copy()
435 435 commit_opts['addremove'] = False
436 436 if not commit_opts['message'] and not commit_opts['logfile']:
437 437 # we don't translate commit messages
438 438 commit_opts['message'] = "Backed out changeset %s" % short(node)
439 439 commit_opts['force_editor'] = True
440 440 commit(ui, repo, **commit_opts)
441 441 def nice(node):
442 442 return '%d:%s' % (repo.changelog.rev(node), short(node))
443 443 ui.status(_('changeset %s backs out changeset %s\n') %
444 444 (nice(repo.changelog.tip()), nice(node)))
445 445 if opts.get('merge') and op1 != node:
446 446 hg.clean(repo, op1, show_stats=False)
447 447 ui.status(_('merging with changeset %s\n')
448 448 % nice(repo.changelog.tip()))
449 449 try:
450 450 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
451 451 return hg.merge(repo, hex(repo.changelog.tip()))
452 452 finally:
453 453 ui.setconfig('ui', 'forcemerge', '')
454 454 return 0
455 455
456 456 @command('bisect',
457 457 [('r', 'reset', False, _('reset bisect state')),
458 458 ('g', 'good', False, _('mark changeset good')),
459 459 ('b', 'bad', False, _('mark changeset bad')),
460 460 ('s', 'skip', False, _('skip testing changeset')),
461 461 ('e', 'extend', False, _('extend the bisect range')),
462 462 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
463 463 ('U', 'noupdate', False, _('do not update to target'))],
464 464 _("[-gbsr] [-U] [-c CMD] [REV]"))
465 465 def bisect(ui, repo, rev=None, extra=None, command=None,
466 466 reset=None, good=None, bad=None, skip=None, extend=None,
467 467 noupdate=None):
468 468 """subdivision search of changesets
469 469
470 470 This command helps to find changesets which introduce problems. To
471 471 use, mark the earliest changeset you know exhibits the problem as
472 472 bad, then mark the latest changeset which is free from the problem
473 473 as good. Bisect will update your working directory to a revision
474 474 for testing (unless the -U/--noupdate option is specified). Once
475 475 you have performed tests, mark the working directory as good or
476 476 bad, and bisect will either update to another candidate changeset
477 477 or announce that it has found the bad revision.
478 478
479 479 As a shortcut, you can also use the revision argument to mark a
480 480 revision as good or bad without checking it out first.
481 481
482 482 If you supply a command, it will be used for automatic bisection.
483 483 Its exit status will be used to mark revisions as good or bad:
484 484 status 0 means good, 125 means to skip the revision, 127
485 485 (command not found) will abort the bisection, and any other
486 486 non-zero exit status means the revision is bad.
487 487
488 488 Returns 0 on success.
489 489 """
490 490 def extendbisectrange(nodes, good):
491 491 # bisect is incomplete when it ends on a merge node and
492 492 # one of the parent was not checked.
493 493 parents = repo[nodes[0]].parents()
494 494 if len(parents) > 1:
495 495 side = good and state['bad'] or state['good']
496 496 num = len(set(i.node() for i in parents) & set(side))
497 497 if num == 1:
498 498 return parents[0].ancestor(parents[1])
499 499 return None
500 500
501 501 def print_result(nodes, good):
502 502 displayer = cmdutil.show_changeset(ui, repo, {})
503 503 if len(nodes) == 1:
504 504 # narrowed it down to a single revision
505 505 if good:
506 506 ui.write(_("The first good revision is:\n"))
507 507 else:
508 508 ui.write(_("The first bad revision is:\n"))
509 509 displayer.show(repo[nodes[0]])
510 510 extendnode = extendbisectrange(nodes, good)
511 511 if extendnode is not None:
512 512 ui.write(_('Not all ancestors of this changeset have been'
513 513 ' checked.\nUse bisect --extend to continue the '
514 514 'bisection from\nthe common ancestor, %s.\n')
515 515 % extendnode)
516 516 else:
517 517 # multiple possible revisions
518 518 if good:
519 519 ui.write(_("Due to skipped revisions, the first "
520 520 "good revision could be any of:\n"))
521 521 else:
522 522 ui.write(_("Due to skipped revisions, the first "
523 523 "bad revision could be any of:\n"))
524 524 for n in nodes:
525 525 displayer.show(repo[n])
526 526 displayer.close()
527 527
528 528 def check_state(state, interactive=True):
529 529 if not state['good'] or not state['bad']:
530 530 if (good or bad or skip or reset) and interactive:
531 531 return
532 532 if not state['good']:
533 533 raise util.Abort(_('cannot bisect (no known good revisions)'))
534 534 else:
535 535 raise util.Abort(_('cannot bisect (no known bad revisions)'))
536 536 return True
537 537
538 538 # backward compatibility
539 539 if rev in "good bad reset init".split():
540 540 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
541 541 cmd, rev, extra = rev, extra, None
542 542 if cmd == "good":
543 543 good = True
544 544 elif cmd == "bad":
545 545 bad = True
546 546 else:
547 547 reset = True
548 548 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
549 549 raise util.Abort(_('incompatible arguments'))
550 550
551 551 if reset:
552 552 p = repo.join("bisect.state")
553 553 if os.path.exists(p):
554 554 os.unlink(p)
555 555 return
556 556
557 557 state = hbisect.load_state(repo)
558 558
559 559 if command:
560 560 changesets = 1
561 561 try:
562 562 while changesets:
563 563 # update state
564 564 status = util.system(command)
565 565 if status == 125:
566 566 transition = "skip"
567 567 elif status == 0:
568 568 transition = "good"
569 569 # status < 0 means process was killed
570 570 elif status == 127:
571 571 raise util.Abort(_("failed to execute %s") % command)
572 572 elif status < 0:
573 573 raise util.Abort(_("%s killed") % command)
574 574 else:
575 575 transition = "bad"
576 576 ctx = scmutil.revsingle(repo, rev)
577 577 rev = None # clear for future iterations
578 578 state[transition].append(ctx.node())
579 579 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
580 580 check_state(state, interactive=False)
581 581 # bisect
582 582 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
583 583 # update to next check
584 584 cmdutil.bailifchanged(repo)
585 585 hg.clean(repo, nodes[0], show_stats=False)
586 586 finally:
587 587 hbisect.save_state(repo, state)
588 588 print_result(nodes, good)
589 589 return
590 590
591 591 # update state
592 592
593 593 if rev:
594 594 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
595 595 else:
596 596 nodes = [repo.lookup('.')]
597 597
598 598 if good or bad or skip:
599 599 if good:
600 600 state['good'] += nodes
601 601 elif bad:
602 602 state['bad'] += nodes
603 603 elif skip:
604 604 state['skip'] += nodes
605 605 hbisect.save_state(repo, state)
606 606
607 607 if not check_state(state):
608 608 return
609 609
610 610 # actually bisect
611 611 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
612 612 if extend:
613 613 if not changesets:
614 614 extendnode = extendbisectrange(nodes, good)
615 615 if extendnode is not None:
616 616 ui.write(_("Extending search to changeset %d:%s\n"
617 617 % (extendnode.rev(), extendnode)))
618 618 if noupdate:
619 619 return
620 620 cmdutil.bailifchanged(repo)
621 621 return hg.clean(repo, extendnode.node())
622 622 raise util.Abort(_("nothing to extend"))
623 623
624 624 if changesets == 0:
625 625 print_result(nodes, good)
626 626 else:
627 627 assert len(nodes) == 1 # only a single node can be tested next
628 628 node = nodes[0]
629 629 # compute the approximate number of remaining tests
630 630 tests, size = 0, 2
631 631 while size <= changesets:
632 632 tests, size = tests + 1, size * 2
633 633 rev = repo.changelog.rev(node)
634 634 ui.write(_("Testing changeset %d:%s "
635 635 "(%d changesets remaining, ~%d tests)\n")
636 636 % (rev, short(node), changesets, tests))
637 637 if not noupdate:
638 638 cmdutil.bailifchanged(repo)
639 639 return hg.clean(repo, node)
640 640
641 641 @command('bookmarks',
642 642 [('f', 'force', False, _('force')),
643 643 ('r', 'rev', '', _('revision'), _('REV')),
644 644 ('d', 'delete', False, _('delete a given bookmark')),
645 645 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
646 646 ('i', 'inactive', False, _('do not mark a new bookmark active'))],
647 647 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
648 648 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
649 649 rename=None, inactive=False):
650 650 '''track a line of development with movable markers
651 651
652 652 Bookmarks are pointers to certain commits that move when
653 653 committing. Bookmarks are local. They can be renamed, copied and
654 654 deleted. It is possible to use bookmark names in :hg:`merge` and
655 655 :hg:`update` to merge and update respectively to a given bookmark.
656 656
657 657 You can use :hg:`bookmark NAME` to set a bookmark on the working
658 658 directory's parent revision with the given name. If you specify
659 659 a revision using -r REV (where REV may be an existing bookmark),
660 660 the bookmark is assigned to that revision.
661 661
662 662 Bookmarks can be pushed and pulled between repositories (see :hg:`help
663 663 push` and :hg:`help pull`). This requires both the local and remote
664 664 repositories to support bookmarks. For versions prior to 1.8, this means
665 665 the bookmarks extension must be enabled.
666 666 '''
667 667 hexfn = ui.debugflag and hex or short
668 668 marks = repo._bookmarks
669 669 cur = repo.changectx('.').node()
670 670
671 671 if rename:
672 672 if rename not in marks:
673 673 raise util.Abort(_("bookmark '%s' does not exist") % rename)
674 674 if mark in marks and not force:
675 675 raise util.Abort(_("bookmark '%s' already exists "
676 676 "(use -f to force)") % mark)
677 677 if mark is None:
678 678 raise util.Abort(_("new bookmark name required"))
679 679 marks[mark] = marks[rename]
680 680 if repo._bookmarkcurrent == rename and not inactive:
681 681 bookmarks.setcurrent(repo, mark)
682 682 del marks[rename]
683 683 bookmarks.write(repo)
684 684 return
685 685
686 686 if delete:
687 687 if mark is None:
688 688 raise util.Abort(_("bookmark name required"))
689 689 if mark not in marks:
690 690 raise util.Abort(_("bookmark '%s' does not exist") % mark)
691 691 if mark == repo._bookmarkcurrent:
692 692 bookmarks.setcurrent(repo, None)
693 693 del marks[mark]
694 694 bookmarks.write(repo)
695 695 return
696 696
697 697 if mark is not None:
698 698 if "\n" in mark:
699 699 raise util.Abort(_("bookmark name cannot contain newlines"))
700 700 mark = mark.strip()
701 701 if not mark:
702 702 raise util.Abort(_("bookmark names cannot consist entirely of "
703 703 "whitespace"))
704 704 if inactive and mark == repo._bookmarkcurrent:
705 705 bookmarks.setcurrent(repo, None)
706 706 return
707 707 if mark in marks and not force:
708 708 raise util.Abort(_("bookmark '%s' already exists "
709 709 "(use -f to force)") % mark)
710 710 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
711 711 and not force):
712 712 raise util.Abort(
713 713 _("a bookmark cannot have the name of an existing branch"))
714 714 if rev:
715 715 marks[mark] = repo.lookup(rev)
716 716 else:
717 717 marks[mark] = repo.changectx('.').node()
718 718 if not inactive and repo.changectx('.').node() == marks[mark]:
719 719 bookmarks.setcurrent(repo, mark)
720 720 bookmarks.write(repo)
721 721 return
722 722
723 723 if mark is None:
724 724 if rev:
725 725 raise util.Abort(_("bookmark name required"))
726 726 if len(marks) == 0:
727 727 ui.status(_("no bookmarks set\n"))
728 728 else:
729 729 for bmark, n in sorted(marks.iteritems()):
730 730 current = repo._bookmarkcurrent
731 731 if bmark == current and n == cur:
732 732 prefix, label = '*', 'bookmarks.current'
733 733 else:
734 734 prefix, label = ' ', ''
735 735
736 736 if ui.quiet:
737 737 ui.write("%s\n" % bmark, label=label)
738 738 else:
739 739 ui.write(" %s %-25s %d:%s\n" % (
740 740 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
741 741 label=label)
742 742 return
743 743
744 744 @command('branch',
745 745 [('f', 'force', None,
746 746 _('set branch name even if it shadows an existing branch')),
747 747 ('C', 'clean', None, _('reset branch name to parent branch name'))],
748 748 _('[-fC] [NAME]'))
749 749 def branch(ui, repo, label=None, **opts):
750 750 """set or show the current branch name
751 751
752 752 With no argument, show the current branch name. With one argument,
753 753 set the working directory branch name (the branch will not exist
754 754 in the repository until the next commit). Standard practice
755 755 recommends that primary development take place on the 'default'
756 756 branch.
757 757
758 758 Unless -f/--force is specified, branch will not let you set a
759 759 branch name that already exists, even if it's inactive.
760 760
761 761 Use -C/--clean to reset the working directory branch to that of
762 762 the parent of the working directory, negating a previous branch
763 763 change.
764 764
765 765 Use the command :hg:`update` to switch to an existing branch. Use
766 766 :hg:`commit --close-branch` to mark this branch as closed.
767 767
768 768 Returns 0 on success.
769 769 """
770 770
771 771 if opts.get('clean'):
772 772 label = repo[None].p1().branch()
773 773 repo.dirstate.setbranch(label)
774 774 ui.status(_('reset working directory to branch %s\n') % label)
775 775 elif label:
776 776 if not opts.get('force') and label in repo.branchtags():
777 777 if label not in [p.branch() for p in repo.parents()]:
778 778 raise util.Abort(_('a branch of the same name already exists'),
779 779 # i18n: "it" refers to an existing branch
780 780 hint=_("use 'hg update' to switch to it"))
781 781 repo.dirstate.setbranch(label)
782 782 ui.status(_('marked working directory as branch %s\n') % label)
783 783 else:
784 784 ui.write("%s\n" % repo.dirstate.branch())
785 785
786 786 @command('branches',
787 787 [('a', 'active', False, _('show only branches that have unmerged heads')),
788 788 ('c', 'closed', False, _('show normal and closed branches'))],
789 789 _('[-ac]'))
790 790 def branches(ui, repo, active=False, closed=False):
791 791 """list repository named branches
792 792
793 793 List the repository's named branches, indicating which ones are
794 794 inactive. If -c/--closed is specified, also list branches which have
795 795 been marked closed (see :hg:`commit --close-branch`).
796 796
797 797 If -a/--active is specified, only show active branches. A branch
798 798 is considered active if it contains repository heads.
799 799
800 800 Use the command :hg:`update` to switch to an existing branch.
801 801
802 802 Returns 0.
803 803 """
804 804
805 805 hexfunc = ui.debugflag and hex or short
806 806 activebranches = [repo[n].branch() for n in repo.heads()]
807 807 def testactive(tag, node):
808 808 realhead = tag in activebranches
809 809 open = node in repo.branchheads(tag, closed=False)
810 810 return realhead and open
811 811 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
812 812 for tag, node in repo.branchtags().items()],
813 813 reverse=True)
814 814
815 815 for isactive, node, tag in branches:
816 816 if (not active) or isactive:
817 817 if ui.quiet:
818 818 ui.write("%s\n" % tag)
819 819 else:
820 820 hn = repo.lookup(node)
821 821 if isactive:
822 822 label = 'branches.active'
823 823 notice = ''
824 824 elif hn not in repo.branchheads(tag, closed=False):
825 825 if not closed:
826 826 continue
827 827 label = 'branches.closed'
828 828 notice = _(' (closed)')
829 829 else:
830 830 label = 'branches.inactive'
831 831 notice = _(' (inactive)')
832 832 if tag == repo.dirstate.branch():
833 833 label = 'branches.current'
834 834 rev = str(node).rjust(31 - encoding.colwidth(tag))
835 835 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
836 836 tag = ui.label(tag, label)
837 837 ui.write("%s %s%s\n" % (tag, rev, notice))
838 838
839 839 @command('bundle',
840 840 [('f', 'force', None, _('run even when the destination is unrelated')),
841 841 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
842 842 _('REV')),
843 843 ('b', 'branch', [], _('a specific branch you would like to bundle'),
844 844 _('BRANCH')),
845 845 ('', 'base', [],
846 846 _('a base changeset assumed to be available at the destination'),
847 847 _('REV')),
848 848 ('a', 'all', None, _('bundle all changesets in the repository')),
849 849 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
850 850 ] + remoteopts,
851 851 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
852 852 def bundle(ui, repo, fname, dest=None, **opts):
853 853 """create a changegroup file
854 854
855 855 Generate a compressed changegroup file collecting changesets not
856 856 known to be in another repository.
857 857
858 858 If you omit the destination repository, then hg assumes the
859 859 destination will have all the nodes you specify with --base
860 860 parameters. To create a bundle containing all changesets, use
861 861 -a/--all (or --base null).
862 862
863 863 You can change compression method with the -t/--type option.
864 864 The available compression methods are: none, bzip2, and
865 865 gzip (by default, bundles are compressed using bzip2).
866 866
867 867 The bundle file can then be transferred using conventional means
868 868 and applied to another repository with the unbundle or pull
869 869 command. This is useful when direct push and pull are not
870 870 available or when exporting an entire repository is undesirable.
871 871
872 872 Applying bundles preserves all changeset contents including
873 873 permissions, copy/rename information, and revision history.
874 874
875 875 Returns 0 on success, 1 if no changes found.
876 876 """
877 877 revs = None
878 878 if 'rev' in opts:
879 879 revs = scmutil.revrange(repo, opts['rev'])
880 880
881 881 if opts.get('all'):
882 882 base = ['null']
883 883 else:
884 884 base = scmutil.revrange(repo, opts.get('base'))
885 885 if base:
886 886 if dest:
887 887 raise util.Abort(_("--base is incompatible with specifying "
888 888 "a destination"))
889 889 common = [repo.lookup(rev) for rev in base]
890 890 heads = revs and map(repo.lookup, revs) or revs
891 891 else:
892 892 dest = ui.expandpath(dest or 'default-push', dest or 'default')
893 893 dest, branches = hg.parseurl(dest, opts.get('branch'))
894 894 other = hg.repository(hg.remoteui(repo, opts), dest)
895 895 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
896 896 heads = revs and map(repo.lookup, revs) or revs
897 897 common, outheads = discovery.findcommonoutgoing(repo, other,
898 898 onlyheads=heads,
899 899 force=opts.get('force'))
900 900
901 901 cg = repo.getbundle('bundle', common=common, heads=heads)
902 902 if not cg:
903 903 ui.status(_("no changes found\n"))
904 904 return 1
905 905
906 906 bundletype = opts.get('type', 'bzip2').lower()
907 907 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
908 908 bundletype = btypes.get(bundletype)
909 909 if bundletype not in changegroup.bundletypes:
910 910 raise util.Abort(_('unknown bundle type specified with --type'))
911 911
912 912 changegroup.writebundle(cg, fname, bundletype)
913 913
914 914 @command('cat',
915 915 [('o', 'output', '',
916 916 _('print output to file with formatted name'), _('FORMAT')),
917 917 ('r', 'rev', '', _('print the given revision'), _('REV')),
918 918 ('', 'decode', None, _('apply any matching decode filter')),
919 919 ] + walkopts,
920 920 _('[OPTION]... FILE...'))
921 921 def cat(ui, repo, file1, *pats, **opts):
922 922 """output the current or given revision of files
923 923
924 924 Print the specified files as they were at the given revision. If
925 925 no revision is given, the parent of the working directory is used,
926 926 or tip if no revision is checked out.
927 927
928 928 Output may be to a file, in which case the name of the file is
929 929 given using a format string. The formatting rules are the same as
930 930 for the export command, with the following additions:
931 931
932 932 :``%s``: basename of file being printed
933 933 :``%d``: dirname of file being printed, or '.' if in repository root
934 934 :``%p``: root-relative path name of file being printed
935 935
936 936 Returns 0 on success.
937 937 """
938 938 ctx = scmutil.revsingle(repo, opts.get('rev'))
939 939 err = 1
940 940 m = scmutil.match(repo, (file1,) + pats, opts)
941 941 for abs in ctx.walk(m):
942 942 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
943 943 pathname=abs)
944 944 data = ctx[abs].data()
945 945 if opts.get('decode'):
946 946 data = repo.wwritedata(abs, data)
947 947 fp.write(data)
948 948 fp.close()
949 949 err = 0
950 950 return err
951 951
952 952 @command('^clone',
953 953 [('U', 'noupdate', None,
954 954 _('the clone will include an empty working copy (only a repository)')),
955 955 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
956 956 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
957 957 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
958 958 ('', 'pull', None, _('use pull protocol to copy metadata')),
959 959 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
960 960 ] + remoteopts,
961 961 _('[OPTION]... SOURCE [DEST]'))
962 962 def clone(ui, source, dest=None, **opts):
963 963 """make a copy of an existing repository
964 964
965 965 Create a copy of an existing repository in a new directory.
966 966
967 967 If no destination directory name is specified, it defaults to the
968 968 basename of the source.
969 969
970 970 The location of the source is added to the new repository's
971 971 ``.hg/hgrc`` file, as the default to be used for future pulls.
972 972
973 973 See :hg:`help urls` for valid source format details.
974 974
975 975 It is possible to specify an ``ssh://`` URL as the destination, but no
976 976 ``.hg/hgrc`` and working directory will be created on the remote side.
977 977 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
978 978
979 979 A set of changesets (tags, or branch names) to pull may be specified
980 980 by listing each changeset (tag, or branch name) with -r/--rev.
981 981 If -r/--rev is used, the cloned repository will contain only a subset
982 982 of the changesets of the source repository. Only the set of changesets
983 983 defined by all -r/--rev options (including all their ancestors)
984 984 will be pulled into the destination repository.
985 985 No subsequent changesets (including subsequent tags) will be present
986 986 in the destination.
987 987
988 988 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
989 989 local source repositories.
990 990
991 991 For efficiency, hardlinks are used for cloning whenever the source
992 992 and destination are on the same filesystem (note this applies only
993 993 to the repository data, not to the working directory). Some
994 994 filesystems, such as AFS, implement hardlinking incorrectly, but
995 995 do not report errors. In these cases, use the --pull option to
996 996 avoid hardlinking.
997 997
998 998 In some cases, you can clone repositories and the working directory
999 999 using full hardlinks with ::
1000 1000
1001 1001 $ cp -al REPO REPOCLONE
1002 1002
1003 1003 This is the fastest way to clone, but it is not always safe. The
1004 1004 operation is not atomic (making sure REPO is not modified during
1005 1005 the operation is up to you) and you have to make sure your editor
1006 1006 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
1007 1007 this is not compatible with certain extensions that place their
1008 1008 metadata under the .hg directory, such as mq.
1009 1009
1010 1010 Mercurial will update the working directory to the first applicable
1011 1011 revision from this list:
1012 1012
1013 1013 a) null if -U or the source repository has no changesets
1014 1014 b) if -u . and the source repository is local, the first parent of
1015 1015 the source repository's working directory
1016 1016 c) the changeset specified with -u (if a branch name, this means the
1017 1017 latest head of that branch)
1018 1018 d) the changeset specified with -r
1019 1019 e) the tipmost head specified with -b
1020 1020 f) the tipmost head specified with the url#branch source syntax
1021 1021 g) the tipmost head of the default branch
1022 1022 h) tip
1023 1023
1024 1024 Returns 0 on success.
1025 1025 """
1026 1026 if opts.get('noupdate') and opts.get('updaterev'):
1027 1027 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1028 1028
1029 1029 r = hg.clone(hg.remoteui(ui, opts), source, dest,
1030 1030 pull=opts.get('pull'),
1031 1031 stream=opts.get('uncompressed'),
1032 1032 rev=opts.get('rev'),
1033 1033 update=opts.get('updaterev') or not opts.get('noupdate'),
1034 1034 branch=opts.get('branch'))
1035 1035
1036 1036 return r is None
1037 1037
1038 1038 @command('^commit|ci',
1039 1039 [('A', 'addremove', None,
1040 1040 _('mark new/missing files as added/removed before committing')),
1041 1041 ('', 'close-branch', None,
1042 1042 _('mark a branch as closed, hiding it from the branch list')),
1043 1043 ] + walkopts + commitopts + commitopts2,
1044 1044 _('[OPTION]... [FILE]...'))
1045 1045 def commit(ui, repo, *pats, **opts):
1046 1046 """commit the specified files or all outstanding changes
1047 1047
1048 1048 Commit changes to the given files into the repository. Unlike a
1049 1049 centralized SCM, this operation is a local operation. See
1050 1050 :hg:`push` for a way to actively distribute your changes.
1051 1051
1052 1052 If a list of files is omitted, all changes reported by :hg:`status`
1053 1053 will be committed.
1054 1054
1055 1055 If you are committing the result of a merge, do not provide any
1056 1056 filenames or -I/-X filters.
1057 1057
1058 1058 If no commit message is specified, Mercurial starts your
1059 1059 configured editor where you can enter a message. In case your
1060 1060 commit fails, you will find a backup of your message in
1061 1061 ``.hg/last-message.txt``.
1062 1062
1063 1063 See :hg:`help dates` for a list of formats valid for -d/--date.
1064 1064
1065 1065 Returns 0 on success, 1 if nothing changed.
1066 1066 """
1067 1067 extra = {}
1068 1068 if opts.get('close_branch'):
1069 1069 if repo['.'].node() not in repo.branchheads():
1070 1070 # The topo heads set is included in the branch heads set of the
1071 1071 # current branch, so it's sufficient to test branchheads
1072 1072 raise util.Abort(_('can only close branch heads'))
1073 1073 extra['close'] = 1
1074 1074 e = cmdutil.commiteditor
1075 1075 if opts.get('force_editor'):
1076 1076 e = cmdutil.commitforceeditor
1077 1077
1078 1078 def commitfunc(ui, repo, message, match, opts):
1079 1079 return repo.commit(message, opts.get('user'), opts.get('date'), match,
1080 1080 editor=e, extra=extra)
1081 1081
1082 1082 branch = repo[None].branch()
1083 1083 bheads = repo.branchheads(branch)
1084 1084
1085 1085 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1086 1086 if not node:
1087 1087 stat = repo.status(match=scmutil.match(repo, pats, opts))
1088 1088 if stat[3]:
1089 1089 ui.status(_("nothing changed (%d missing files, see 'hg status')\n")
1090 1090 % len(stat[3]))
1091 1091 else:
1092 1092 ui.status(_("nothing changed\n"))
1093 1093 return 1
1094 1094
1095 1095 ctx = repo[node]
1096 1096 parents = ctx.parents()
1097 1097
1098 1098 if bheads and not [x for x in parents
1099 1099 if x.node() in bheads and x.branch() == branch]:
1100 1100 ui.status(_('created new head\n'))
1101 1101 # The message is not printed for initial roots. For the other
1102 1102 # changesets, it is printed in the following situations:
1103 1103 #
1104 1104 # Par column: for the 2 parents with ...
1105 1105 # N: null or no parent
1106 1106 # B: parent is on another named branch
1107 1107 # C: parent is a regular non head changeset
1108 1108 # H: parent was a branch head of the current branch
1109 1109 # Msg column: whether we print "created new head" message
1110 1110 # In the following, it is assumed that there already exists some
1111 1111 # initial branch heads of the current branch, otherwise nothing is
1112 1112 # printed anyway.
1113 1113 #
1114 1114 # Par Msg Comment
1115 1115 # NN y additional topo root
1116 1116 #
1117 1117 # BN y additional branch root
1118 1118 # CN y additional topo head
1119 1119 # HN n usual case
1120 1120 #
1121 1121 # BB y weird additional branch root
1122 1122 # CB y branch merge
1123 1123 # HB n merge with named branch
1124 1124 #
1125 1125 # CC y additional head from merge
1126 1126 # CH n merge with a head
1127 1127 #
1128 1128 # HH n head merge: head count decreases
1129 1129
1130 1130 if not opts.get('close_branch'):
1131 1131 for r in parents:
1132 1132 if r.extra().get('close') and r.branch() == branch:
1133 1133 ui.status(_('reopening closed branch head %d\n') % r)
1134 1134
1135 1135 if ui.debugflag:
1136 1136 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1137 1137 elif ui.verbose:
1138 1138 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1139 1139
1140 1140 @command('copy|cp',
1141 1141 [('A', 'after', None, _('record a copy that has already occurred')),
1142 1142 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1143 1143 ] + walkopts + dryrunopts,
1144 1144 _('[OPTION]... [SOURCE]... DEST'))
1145 1145 def copy(ui, repo, *pats, **opts):
1146 1146 """mark files as copied for the next commit
1147 1147
1148 1148 Mark dest as having copies of source files. If dest is a
1149 1149 directory, copies are put in that directory. If dest is a file,
1150 1150 the source must be a single file.
1151 1151
1152 1152 By default, this command copies the contents of files as they
1153 1153 exist in the working directory. If invoked with -A/--after, the
1154 1154 operation is recorded, but no copying is performed.
1155 1155
1156 1156 This command takes effect with the next commit. To undo a copy
1157 1157 before that, see :hg:`revert`.
1158 1158
1159 1159 Returns 0 on success, 1 if errors are encountered.
1160 1160 """
1161 1161 wlock = repo.wlock(False)
1162 1162 try:
1163 1163 return cmdutil.copy(ui, repo, pats, opts)
1164 1164 finally:
1165 1165 wlock.release()
1166 1166
1167 1167 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1168 1168 def debugancestor(ui, repo, *args):
1169 1169 """find the ancestor revision of two revisions in a given index"""
1170 1170 if len(args) == 3:
1171 1171 index, rev1, rev2 = args
1172 1172 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1173 1173 lookup = r.lookup
1174 1174 elif len(args) == 2:
1175 1175 if not repo:
1176 1176 raise util.Abort(_("there is no Mercurial repository here "
1177 1177 "(.hg not found)"))
1178 1178 rev1, rev2 = args
1179 1179 r = repo.changelog
1180 1180 lookup = repo.lookup
1181 1181 else:
1182 1182 raise util.Abort(_('either two or three arguments required'))
1183 1183 a = r.ancestor(lookup(rev1), lookup(rev2))
1184 1184 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1185 1185
1186 1186 @command('debugbuilddag',
1187 1187 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1188 1188 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1189 1189 ('n', 'new-file', None, _('add new file at each rev'))],
1190 1190 _('[OPTION]... [TEXT]'))
1191 1191 def debugbuilddag(ui, repo, text=None,
1192 1192 mergeable_file=False,
1193 1193 overwritten_file=False,
1194 1194 new_file=False):
1195 1195 """builds a repo with a given DAG from scratch in the current empty repo
1196 1196
1197 1197 The description of the DAG is read from stdin if not given on the
1198 1198 command line.
1199 1199
1200 1200 Elements:
1201 1201
1202 1202 - "+n" is a linear run of n nodes based on the current default parent
1203 1203 - "." is a single node based on the current default parent
1204 1204 - "$" resets the default parent to null (implied at the start);
1205 1205 otherwise the default parent is always the last node created
1206 1206 - "<p" sets the default parent to the backref p
1207 1207 - "*p" is a fork at parent p, which is a backref
1208 1208 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1209 1209 - "/p2" is a merge of the preceding node and p2
1210 1210 - ":tag" defines a local tag for the preceding node
1211 1211 - "@branch" sets the named branch for subsequent nodes
1212 1212 - "#...\\n" is a comment up to the end of the line
1213 1213
1214 1214 Whitespace between the above elements is ignored.
1215 1215
1216 1216 A backref is either
1217 1217
1218 1218 - a number n, which references the node curr-n, where curr is the current
1219 1219 node, or
1220 1220 - the name of a local tag you placed earlier using ":tag", or
1221 1221 - empty to denote the default parent.
1222 1222
1223 1223 All string valued-elements are either strictly alphanumeric, or must
1224 1224 be enclosed in double quotes ("..."), with "\\" as escape character.
1225 1225 """
1226 1226
1227 1227 if text is None:
1228 1228 ui.status(_("reading DAG from stdin\n"))
1229 1229 text = sys.stdin.read()
1230 1230
1231 1231 cl = repo.changelog
1232 1232 if len(cl) > 0:
1233 1233 raise util.Abort(_('repository is not empty'))
1234 1234
1235 1235 # determine number of revs in DAG
1236 1236 total = 0
1237 1237 for type, data in dagparser.parsedag(text):
1238 1238 if type == 'n':
1239 1239 total += 1
1240 1240
1241 1241 if mergeable_file:
1242 1242 linesperrev = 2
1243 1243 # make a file with k lines per rev
1244 1244 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1245 1245 initialmergedlines.append("")
1246 1246
1247 1247 tags = []
1248 1248
1249 1249 tr = repo.transaction("builddag")
1250 1250 try:
1251 1251
1252 1252 at = -1
1253 1253 atbranch = 'default'
1254 1254 nodeids = []
1255 1255 ui.progress(_('building'), 0, unit=_('revisions'), total=total)
1256 1256 for type, data in dagparser.parsedag(text):
1257 1257 if type == 'n':
1258 1258 ui.note('node %s\n' % str(data))
1259 1259 id, ps = data
1260 1260
1261 1261 files = []
1262 1262 fctxs = {}
1263 1263
1264 1264 p2 = None
1265 1265 if mergeable_file:
1266 1266 fn = "mf"
1267 1267 p1 = repo[ps[0]]
1268 1268 if len(ps) > 1:
1269 1269 p2 = repo[ps[1]]
1270 1270 pa = p1.ancestor(p2)
1271 1271 base, local, other = [x[fn].data() for x in pa, p1, p2]
1272 1272 m3 = simplemerge.Merge3Text(base, local, other)
1273 1273 ml = [l.strip() for l in m3.merge_lines()]
1274 1274 ml.append("")
1275 1275 elif at > 0:
1276 1276 ml = p1[fn].data().split("\n")
1277 1277 else:
1278 1278 ml = initialmergedlines
1279 1279 ml[id * linesperrev] += " r%i" % id
1280 1280 mergedtext = "\n".join(ml)
1281 1281 files.append(fn)
1282 1282 fctxs[fn] = context.memfilectx(fn, mergedtext)
1283 1283
1284 1284 if overwritten_file:
1285 1285 fn = "of"
1286 1286 files.append(fn)
1287 1287 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1288 1288
1289 1289 if new_file:
1290 1290 fn = "nf%i" % id
1291 1291 files.append(fn)
1292 1292 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1293 1293 if len(ps) > 1:
1294 1294 if not p2:
1295 1295 p2 = repo[ps[1]]
1296 1296 for fn in p2:
1297 1297 if fn.startswith("nf"):
1298 1298 files.append(fn)
1299 1299 fctxs[fn] = p2[fn]
1300 1300
1301 1301 def fctxfn(repo, cx, path):
1302 1302 return fctxs.get(path)
1303 1303
1304 1304 if len(ps) == 0 or ps[0] < 0:
1305 1305 pars = [None, None]
1306 1306 elif len(ps) == 1:
1307 1307 pars = [nodeids[ps[0]], None]
1308 1308 else:
1309 1309 pars = [nodeids[p] for p in ps]
1310 1310 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1311 1311 date=(id, 0),
1312 1312 user="debugbuilddag",
1313 1313 extra={'branch': atbranch})
1314 1314 nodeid = repo.commitctx(cx)
1315 1315 nodeids.append(nodeid)
1316 1316 at = id
1317 1317 elif type == 'l':
1318 1318 id, name = data
1319 1319 ui.note('tag %s\n' % name)
1320 1320 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1321 1321 elif type == 'a':
1322 1322 ui.note('branch %s\n' % data)
1323 1323 atbranch = data
1324 1324 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1325 1325 tr.close()
1326 1326 finally:
1327 1327 ui.progress(_('building'), None)
1328 1328 tr.release()
1329 1329
1330 1330 if tags:
1331 1331 repo.opener.write("localtags", "".join(tags))
1332 1332
1333 1333 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1334 1334 def debugbundle(ui, bundlepath, all=None, **opts):
1335 1335 """lists the contents of a bundle"""
1336 1336 f = url.open(ui, bundlepath)
1337 1337 try:
1338 1338 gen = changegroup.readbundle(f, bundlepath)
1339 1339 if all:
1340 1340 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1341 1341
1342 1342 def showchunks(named):
1343 1343 ui.write("\n%s\n" % named)
1344 1344 chain = None
1345 1345 while 1:
1346 1346 chunkdata = gen.deltachunk(chain)
1347 1347 if not chunkdata:
1348 1348 break
1349 1349 node = chunkdata['node']
1350 1350 p1 = chunkdata['p1']
1351 1351 p2 = chunkdata['p2']
1352 1352 cs = chunkdata['cs']
1353 1353 deltabase = chunkdata['deltabase']
1354 1354 delta = chunkdata['delta']
1355 1355 ui.write("%s %s %s %s %s %s\n" %
1356 1356 (hex(node), hex(p1), hex(p2),
1357 1357 hex(cs), hex(deltabase), len(delta)))
1358 1358 chain = node
1359 1359
1360 1360 chunkdata = gen.changelogheader()
1361 1361 showchunks("changelog")
1362 1362 chunkdata = gen.manifestheader()
1363 1363 showchunks("manifest")
1364 1364 while 1:
1365 1365 chunkdata = gen.filelogheader()
1366 1366 if not chunkdata:
1367 1367 break
1368 1368 fname = chunkdata['filename']
1369 1369 showchunks(fname)
1370 1370 else:
1371 1371 chunkdata = gen.changelogheader()
1372 1372 chain = None
1373 1373 while 1:
1374 1374 chunkdata = gen.deltachunk(chain)
1375 1375 if not chunkdata:
1376 1376 break
1377 1377 node = chunkdata['node']
1378 1378 ui.write("%s\n" % hex(node))
1379 1379 chain = node
1380 1380 finally:
1381 1381 f.close()
1382 1382
1383 1383 @command('debugcheckstate', [], '')
1384 1384 def debugcheckstate(ui, repo):
1385 1385 """validate the correctness of the current dirstate"""
1386 1386 parent1, parent2 = repo.dirstate.parents()
1387 1387 m1 = repo[parent1].manifest()
1388 1388 m2 = repo[parent2].manifest()
1389 1389 errors = 0
1390 1390 for f in repo.dirstate:
1391 1391 state = repo.dirstate[f]
1392 1392 if state in "nr" and f not in m1:
1393 1393 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1394 1394 errors += 1
1395 1395 if state in "a" and f in m1:
1396 1396 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1397 1397 errors += 1
1398 1398 if state in "m" and f not in m1 and f not in m2:
1399 1399 ui.warn(_("%s in state %s, but not in either manifest\n") %
1400 1400 (f, state))
1401 1401 errors += 1
1402 1402 for f in m1:
1403 1403 state = repo.dirstate[f]
1404 1404 if state not in "nrm":
1405 1405 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1406 1406 errors += 1
1407 1407 if errors:
1408 1408 error = _(".hg/dirstate inconsistent with current parent's manifest")
1409 1409 raise util.Abort(error)
1410 1410
1411 1411 @command('debugcommands', [], _('[COMMAND]'))
1412 1412 def debugcommands(ui, cmd='', *args):
1413 1413 """list all available commands and options"""
1414 1414 for cmd, vals in sorted(table.iteritems()):
1415 1415 cmd = cmd.split('|')[0].strip('^')
1416 1416 opts = ', '.join([i[1] for i in vals[1]])
1417 1417 ui.write('%s: %s\n' % (cmd, opts))
1418 1418
1419 1419 @command('debugcomplete',
1420 1420 [('o', 'options', None, _('show the command options'))],
1421 1421 _('[-o] CMD'))
1422 1422 def debugcomplete(ui, cmd='', **opts):
1423 1423 """returns the completion list associated with the given command"""
1424 1424
1425 1425 if opts.get('options'):
1426 1426 options = []
1427 1427 otables = [globalopts]
1428 1428 if cmd:
1429 1429 aliases, entry = cmdutil.findcmd(cmd, table, False)
1430 1430 otables.append(entry[1])
1431 1431 for t in otables:
1432 1432 for o in t:
1433 1433 if "(DEPRECATED)" in o[3]:
1434 1434 continue
1435 1435 if o[0]:
1436 1436 options.append('-%s' % o[0])
1437 1437 options.append('--%s' % o[1])
1438 1438 ui.write("%s\n" % "\n".join(options))
1439 1439 return
1440 1440
1441 1441 cmdlist = cmdutil.findpossible(cmd, table)
1442 1442 if ui.verbose:
1443 1443 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1444 1444 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1445 1445
1446 1446 @command('debugdag',
1447 1447 [('t', 'tags', None, _('use tags as labels')),
1448 1448 ('b', 'branches', None, _('annotate with branch names')),
1449 1449 ('', 'dots', None, _('use dots for runs')),
1450 1450 ('s', 'spaces', None, _('separate elements by spaces'))],
1451 1451 _('[OPTION]... [FILE [REV]...]'))
1452 1452 def debugdag(ui, repo, file_=None, *revs, **opts):
1453 1453 """format the changelog or an index DAG as a concise textual description
1454 1454
1455 1455 If you pass a revlog index, the revlog's DAG is emitted. If you list
1456 1456 revision numbers, they get labelled in the output as rN.
1457 1457
1458 1458 Otherwise, the changelog DAG of the current repo is emitted.
1459 1459 """
1460 1460 spaces = opts.get('spaces')
1461 1461 dots = opts.get('dots')
1462 1462 if file_:
1463 1463 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1464 1464 revs = set((int(r) for r in revs))
1465 1465 def events():
1466 1466 for r in rlog:
1467 1467 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1468 1468 if r in revs:
1469 1469 yield 'l', (r, "r%i" % r)
1470 1470 elif repo:
1471 1471 cl = repo.changelog
1472 1472 tags = opts.get('tags')
1473 1473 branches = opts.get('branches')
1474 1474 if tags:
1475 1475 labels = {}
1476 1476 for l, n in repo.tags().items():
1477 1477 labels.setdefault(cl.rev(n), []).append(l)
1478 1478 def events():
1479 1479 b = "default"
1480 1480 for r in cl:
1481 1481 if branches:
1482 1482 newb = cl.read(cl.node(r))[5]['branch']
1483 1483 if newb != b:
1484 1484 yield 'a', newb
1485 1485 b = newb
1486 1486 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1487 1487 if tags:
1488 1488 ls = labels.get(r)
1489 1489 if ls:
1490 1490 for l in ls:
1491 1491 yield 'l', (r, l)
1492 1492 else:
1493 1493 raise util.Abort(_('need repo for changelog dag'))
1494 1494
1495 1495 for line in dagparser.dagtextlines(events(),
1496 1496 addspaces=spaces,
1497 1497 wraplabels=True,
1498 1498 wrapannotations=True,
1499 1499 wrapnonlinear=dots,
1500 1500 usedots=dots,
1501 1501 maxlinewidth=70):
1502 1502 ui.write(line)
1503 1503 ui.write("\n")
1504 1504
1505 1505 @command('debugdata',
1506 1506 [('c', 'changelog', False, _('open changelog')),
1507 1507 ('m', 'manifest', False, _('open manifest'))],
1508 1508 _('-c|-m|FILE REV'))
1509 1509 def debugdata(ui, repo, file_, rev = None, **opts):
1510 1510 """dump the contents of a data file revision"""
1511 1511 if opts.get('changelog') or opts.get('manifest'):
1512 1512 file_, rev = None, file_
1513 1513 elif rev is None:
1514 1514 raise error.CommandError('debugdata', _('invalid arguments'))
1515 1515 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1516 1516 try:
1517 1517 ui.write(r.revision(r.lookup(rev)))
1518 1518 except KeyError:
1519 1519 raise util.Abort(_('invalid revision identifier %s') % rev)
1520 1520
1521 1521 @command('debugdate',
1522 1522 [('e', 'extended', None, _('try extended date formats'))],
1523 1523 _('[-e] DATE [RANGE]'))
1524 1524 def debugdate(ui, date, range=None, **opts):
1525 1525 """parse and display a date"""
1526 1526 if opts["extended"]:
1527 1527 d = util.parsedate(date, util.extendeddateformats)
1528 1528 else:
1529 1529 d = util.parsedate(date)
1530 1530 ui.write("internal: %s %s\n" % d)
1531 1531 ui.write("standard: %s\n" % util.datestr(d))
1532 1532 if range:
1533 1533 m = util.matchdate(range)
1534 1534 ui.write("match: %s\n" % m(d[0]))
1535 1535
1536 1536 @command('debugdiscovery',
1537 1537 [('', 'old', None, _('use old-style discovery')),
1538 1538 ('', 'nonheads', None,
1539 1539 _('use old-style discovery with non-heads included')),
1540 1540 ] + remoteopts,
1541 1541 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1542 1542 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1543 1543 """runs the changeset discovery protocol in isolation"""
1544 1544 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl), opts.get('branch'))
1545 1545 remote = hg.repository(hg.remoteui(repo, opts), remoteurl)
1546 1546 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1547 1547
1548 1548 # make sure tests are repeatable
1549 1549 random.seed(12323)
1550 1550
1551 1551 def doit(localheads, remoteheads):
1552 1552 if opts.get('old'):
1553 1553 if localheads:
1554 1554 raise util.Abort('cannot use localheads with old style discovery')
1555 1555 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1556 1556 force=True)
1557 1557 common = set(common)
1558 1558 if not opts.get('nonheads'):
1559 1559 ui.write("unpruned common: %s\n" % " ".join([short(n)
1560 1560 for n in common]))
1561 1561 dag = dagutil.revlogdag(repo.changelog)
1562 1562 all = dag.ancestorset(dag.internalizeall(common))
1563 1563 common = dag.externalizeall(dag.headsetofconnecteds(all))
1564 1564 else:
1565 1565 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1566 1566 common = set(common)
1567 1567 rheads = set(hds)
1568 1568 lheads = set(repo.heads())
1569 1569 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1570 1570 if lheads <= common:
1571 1571 ui.write("local is subset\n")
1572 1572 elif rheads <= common:
1573 1573 ui.write("remote is subset\n")
1574 1574
1575 1575 serverlogs = opts.get('serverlog')
1576 1576 if serverlogs:
1577 1577 for filename in serverlogs:
1578 1578 logfile = open(filename, 'r')
1579 1579 try:
1580 1580 line = logfile.readline()
1581 1581 while line:
1582 1582 parts = line.strip().split(';')
1583 1583 op = parts[1]
1584 1584 if op == 'cg':
1585 1585 pass
1586 1586 elif op == 'cgss':
1587 1587 doit(parts[2].split(' '), parts[3].split(' '))
1588 1588 elif op == 'unb':
1589 1589 doit(parts[3].split(' '), parts[2].split(' '))
1590 1590 line = logfile.readline()
1591 1591 finally:
1592 1592 logfile.close()
1593 1593
1594 1594 else:
1595 1595 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1596 1596 opts.get('remote_head'))
1597 1597 localrevs = opts.get('local_head')
1598 1598 doit(localrevs, remoterevs)
1599 1599
1600 1600 @command('debugfsinfo', [], _('[PATH]'))
1601 1601 def debugfsinfo(ui, path = "."):
1602 1602 """show information detected about current filesystem"""
1603 1603 util.writefile('.debugfsinfo', '')
1604 1604 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1605 1605 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1606 1606 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1607 1607 and 'yes' or 'no'))
1608 1608 os.unlink('.debugfsinfo')
1609 1609
1610 1610 @command('debuggetbundle',
1611 1611 [('H', 'head', [], _('id of head node'), _('ID')),
1612 1612 ('C', 'common', [], _('id of common node'), _('ID')),
1613 1613 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1614 1614 _('REPO FILE [-H|-C ID]...'))
1615 1615 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1616 1616 """retrieves a bundle from a repo
1617 1617
1618 1618 Every ID must be a full-length hex node id string. Saves the bundle to the
1619 1619 given file.
1620 1620 """
1621 1621 repo = hg.repository(ui, repopath)
1622 1622 if not repo.capable('getbundle'):
1623 1623 raise util.Abort("getbundle() not supported by target repository")
1624 1624 args = {}
1625 1625 if common:
1626 1626 args['common'] = [bin(s) for s in common]
1627 1627 if head:
1628 1628 args['heads'] = [bin(s) for s in head]
1629 1629 bundle = repo.getbundle('debug', **args)
1630 1630
1631 1631 bundletype = opts.get('type', 'bzip2').lower()
1632 1632 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1633 1633 bundletype = btypes.get(bundletype)
1634 1634 if bundletype not in changegroup.bundletypes:
1635 1635 raise util.Abort(_('unknown bundle type specified with --type'))
1636 1636 changegroup.writebundle(bundle, bundlepath, bundletype)
1637 1637
1638 1638 @command('debugignore', [], '')
1639 1639 def debugignore(ui, repo, *values, **opts):
1640 1640 """display the combined ignore pattern"""
1641 1641 ignore = repo.dirstate._ignore
1642 1642 if hasattr(ignore, 'includepat'):
1643 1643 ui.write("%s\n" % ignore.includepat)
1644 1644 else:
1645 1645 raise util.Abort(_("no ignore patterns found"))
1646 1646
1647 1647 @command('debugindex',
1648 1648 [('c', 'changelog', False, _('open changelog')),
1649 1649 ('m', 'manifest', False, _('open manifest')),
1650 1650 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1651 1651 _('[-f FORMAT] -c|-m|FILE'))
1652 1652 def debugindex(ui, repo, file_ = None, **opts):
1653 1653 """dump the contents of an index file"""
1654 1654 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1655 1655 format = opts.get('format', 0)
1656 1656 if format not in (0, 1):
1657 1657 raise util.Abort(_("unknown format %d") % format)
1658 1658
1659 1659 generaldelta = r.version & revlog.REVLOGGENERALDELTA
1660 1660 if generaldelta:
1661 1661 basehdr = ' delta'
1662 1662 else:
1663 1663 basehdr = ' base'
1664 1664
1665 1665 if format == 0:
1666 1666 ui.write(" rev offset length " + basehdr + " linkrev"
1667 1667 " nodeid p1 p2\n")
1668 1668 elif format == 1:
1669 1669 ui.write(" rev flag offset length"
1670 1670 " size " + basehdr + " link p1 p2 nodeid\n")
1671 1671
1672 1672 for i in r:
1673 1673 node = r.node(i)
1674 1674 if generaldelta:
1675 1675 base = r.deltaparent(i)
1676 1676 else:
1677 1677 base = r.chainbase(i)
1678 1678 if format == 0:
1679 1679 try:
1680 1680 pp = r.parents(node)
1681 1681 except:
1682 1682 pp = [nullid, nullid]
1683 1683 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1684 1684 i, r.start(i), r.length(i), base, r.linkrev(i),
1685 1685 short(node), short(pp[0]), short(pp[1])))
1686 1686 elif format == 1:
1687 1687 pr = r.parentrevs(i)
1688 1688 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1689 1689 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1690 1690 base, r.linkrev(i), pr[0], pr[1], short(node)))
1691 1691
1692 1692 @command('debugindexdot', [], _('FILE'))
1693 1693 def debugindexdot(ui, repo, file_):
1694 1694 """dump an index DAG as a graphviz dot file"""
1695 1695 r = None
1696 1696 if repo:
1697 1697 filelog = repo.file(file_)
1698 1698 if len(filelog):
1699 1699 r = filelog
1700 1700 if not r:
1701 1701 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1702 1702 ui.write("digraph G {\n")
1703 1703 for i in r:
1704 1704 node = r.node(i)
1705 1705 pp = r.parents(node)
1706 1706 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1707 1707 if pp[1] != nullid:
1708 1708 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1709 1709 ui.write("}\n")
1710 1710
1711 1711 @command('debuginstall', [], '')
1712 1712 def debuginstall(ui):
1713 1713 '''test Mercurial installation
1714 1714
1715 1715 Returns 0 on success.
1716 1716 '''
1717 1717
1718 1718 def writetemp(contents):
1719 1719 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1720 1720 f = os.fdopen(fd, "wb")
1721 1721 f.write(contents)
1722 1722 f.close()
1723 1723 return name
1724 1724
1725 1725 problems = 0
1726 1726
1727 1727 # encoding
1728 1728 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1729 1729 try:
1730 1730 encoding.fromlocal("test")
1731 1731 except util.Abort, inst:
1732 1732 ui.write(" %s\n" % inst)
1733 1733 ui.write(_(" (check that your locale is properly set)\n"))
1734 1734 problems += 1
1735 1735
1736 1736 # compiled modules
1737 1737 ui.status(_("Checking installed modules (%s)...\n")
1738 1738 % os.path.dirname(__file__))
1739 1739 try:
1740 1740 import bdiff, mpatch, base85, osutil
1741 1741 except Exception, inst:
1742 1742 ui.write(" %s\n" % inst)
1743 1743 ui.write(_(" One or more extensions could not be found"))
1744 1744 ui.write(_(" (check that you compiled the extensions)\n"))
1745 1745 problems += 1
1746 1746
1747 1747 # templates
1748 1748 ui.status(_("Checking templates...\n"))
1749 1749 try:
1750 1750 import templater
1751 1751 templater.templater(templater.templatepath("map-cmdline.default"))
1752 1752 except Exception, inst:
1753 1753 ui.write(" %s\n" % inst)
1754 1754 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1755 1755 problems += 1
1756 1756
1757 1757 # editor
1758 1758 ui.status(_("Checking commit editor...\n"))
1759 1759 editor = ui.geteditor()
1760 1760 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
1761 1761 if not cmdpath:
1762 1762 if editor == 'vi':
1763 1763 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1764 1764 ui.write(_(" (specify a commit editor in your configuration"
1765 1765 " file)\n"))
1766 1766 else:
1767 1767 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1768 1768 ui.write(_(" (specify a commit editor in your configuration"
1769 1769 " file)\n"))
1770 1770 problems += 1
1771 1771
1772 1772 # check username
1773 1773 ui.status(_("Checking username...\n"))
1774 1774 try:
1775 1775 ui.username()
1776 1776 except util.Abort, e:
1777 1777 ui.write(" %s\n" % e)
1778 1778 ui.write(_(" (specify a username in your configuration file)\n"))
1779 1779 problems += 1
1780 1780
1781 1781 if not problems:
1782 1782 ui.status(_("No problems detected\n"))
1783 1783 else:
1784 1784 ui.write(_("%s problems detected,"
1785 1785 " please check your install!\n") % problems)
1786 1786
1787 1787 return problems
1788 1788
1789 1789 @command('debugknown', [], _('REPO ID...'))
1790 1790 def debugknown(ui, repopath, *ids, **opts):
1791 1791 """test whether node ids are known to a repo
1792 1792
1793 1793 Every ID must be a full-length hex node id string. Returns a list of 0s and 1s
1794 1794 indicating unknown/known.
1795 1795 """
1796 1796 repo = hg.repository(ui, repopath)
1797 1797 if not repo.capable('known'):
1798 1798 raise util.Abort("known() not supported by target repository")
1799 1799 flags = repo.known([bin(s) for s in ids])
1800 1800 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1801 1801
1802 1802 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
1803 1803 def debugpushkey(ui, repopath, namespace, *keyinfo):
1804 1804 '''access the pushkey key/value protocol
1805 1805
1806 1806 With two args, list the keys in the given namespace.
1807 1807
1808 1808 With five args, set a key to new if it currently is set to old.
1809 1809 Reports success or failure.
1810 1810 '''
1811 1811
1812 1812 target = hg.repository(ui, repopath)
1813 1813 if keyinfo:
1814 1814 key, old, new = keyinfo
1815 1815 r = target.pushkey(namespace, key, old, new)
1816 1816 ui.status(str(r) + '\n')
1817 1817 return not r
1818 1818 else:
1819 1819 for k, v in target.listkeys(namespace).iteritems():
1820 1820 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1821 1821 v.encode('string-escape')))
1822 1822
1823 1823 @command('debugrebuildstate',
1824 1824 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
1825 1825 _('[-r REV] [REV]'))
1826 1826 def debugrebuildstate(ui, repo, rev="tip"):
1827 1827 """rebuild the dirstate as it would look like for the given revision"""
1828 1828 ctx = scmutil.revsingle(repo, rev)
1829 1829 wlock = repo.wlock()
1830 1830 try:
1831 1831 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1832 1832 finally:
1833 1833 wlock.release()
1834 1834
1835 1835 @command('debugrename',
1836 1836 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1837 1837 _('[-r REV] FILE'))
1838 1838 def debugrename(ui, repo, file1, *pats, **opts):
1839 1839 """dump rename information"""
1840 1840
1841 1841 ctx = scmutil.revsingle(repo, opts.get('rev'))
1842 1842 m = scmutil.match(repo, (file1,) + pats, opts)
1843 1843 for abs in ctx.walk(m):
1844 1844 fctx = ctx[abs]
1845 1845 o = fctx.filelog().renamed(fctx.filenode())
1846 1846 rel = m.rel(abs)
1847 1847 if o:
1848 1848 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1849 1849 else:
1850 1850 ui.write(_("%s not renamed\n") % rel)
1851 1851
1852 1852 @command('debugrevlog',
1853 1853 [('c', 'changelog', False, _('open changelog')),
1854 1854 ('m', 'manifest', False, _('open manifest')),
1855 1855 ('d', 'dump', False, _('dump index data'))],
1856 1856 _('-c|-m|FILE'))
1857 1857 def debugrevlog(ui, repo, file_ = None, **opts):
1858 1858 """show data and statistics about a revlog"""
1859 1859 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
1860 1860
1861 1861 if opts.get("dump"):
1862 1862 numrevs = len(r)
1863 1863 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
1864 1864 " rawsize totalsize compression heads\n")
1865 1865 ts = 0
1866 1866 heads = set()
1867 1867 for rev in xrange(numrevs):
1868 1868 dbase = r.deltaparent(rev)
1869 1869 if dbase == -1:
1870 1870 dbase = rev
1871 1871 cbase = r.chainbase(rev)
1872 1872 p1, p2 = r.parentrevs(rev)
1873 1873 rs = r.rawsize(rev)
1874 1874 ts = ts + rs
1875 1875 heads -= set(r.parentrevs(rev))
1876 1876 heads.add(rev)
1877 1877 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
1878 1878 (rev, p1, p2, r.start(rev), r.end(rev),
1879 1879 r.start(dbase), r.start(cbase),
1880 1880 r.start(p1), r.start(p2),
1881 1881 rs, ts, ts / r.end(rev), len(heads)))
1882 1882 return 0
1883 1883
1884 1884 v = r.version
1885 1885 format = v & 0xFFFF
1886 1886 flags = []
1887 1887 gdelta = False
1888 1888 if v & revlog.REVLOGNGINLINEDATA:
1889 1889 flags.append('inline')
1890 1890 if v & revlog.REVLOGGENERALDELTA:
1891 1891 gdelta = True
1892 1892 flags.append('generaldelta')
1893 1893 if not flags:
1894 1894 flags = ['(none)']
1895 1895
1896 1896 nummerges = 0
1897 1897 numfull = 0
1898 1898 numprev = 0
1899 1899 nump1 = 0
1900 1900 nump2 = 0
1901 1901 numother = 0
1902 1902 nump1prev = 0
1903 1903 nump2prev = 0
1904 1904 chainlengths = []
1905 1905
1906 1906 datasize = [None, 0, 0L]
1907 1907 fullsize = [None, 0, 0L]
1908 1908 deltasize = [None, 0, 0L]
1909 1909
1910 1910 def addsize(size, l):
1911 1911 if l[0] is None or size < l[0]:
1912 1912 l[0] = size
1913 1913 if size > l[1]:
1914 1914 l[1] = size
1915 1915 l[2] += size
1916 1916
1917 1917 numrevs = len(r)
1918 1918 for rev in xrange(numrevs):
1919 1919 p1, p2 = r.parentrevs(rev)
1920 1920 delta = r.deltaparent(rev)
1921 1921 if format > 0:
1922 1922 addsize(r.rawsize(rev), datasize)
1923 1923 if p2 != nullrev:
1924 1924 nummerges += 1
1925 1925 size = r.length(rev)
1926 1926 if delta == nullrev:
1927 1927 chainlengths.append(0)
1928 1928 numfull += 1
1929 1929 addsize(size, fullsize)
1930 1930 else:
1931 1931 chainlengths.append(chainlengths[delta] + 1)
1932 1932 addsize(size, deltasize)
1933 1933 if delta == rev - 1:
1934 1934 numprev += 1
1935 1935 if delta == p1:
1936 1936 nump1prev += 1
1937 1937 elif delta == p2:
1938 1938 nump2prev += 1
1939 1939 elif delta == p1:
1940 1940 nump1 += 1
1941 1941 elif delta == p2:
1942 1942 nump2 += 1
1943 1943 elif delta != nullrev:
1944 1944 numother += 1
1945 1945
1946 1946 numdeltas = numrevs - numfull
1947 1947 numoprev = numprev - nump1prev - nump2prev
1948 1948 totalrawsize = datasize[2]
1949 1949 datasize[2] /= numrevs
1950 1950 fulltotal = fullsize[2]
1951 1951 fullsize[2] /= numfull
1952 1952 deltatotal = deltasize[2]
1953 1953 deltasize[2] /= numrevs - numfull
1954 1954 totalsize = fulltotal + deltatotal
1955 1955 avgchainlen = sum(chainlengths) / numrevs
1956 1956 compratio = totalrawsize / totalsize
1957 1957
1958 1958 basedfmtstr = '%%%dd\n'
1959 1959 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
1960 1960
1961 1961 def dfmtstr(max):
1962 1962 return basedfmtstr % len(str(max))
1963 1963 def pcfmtstr(max, padding=0):
1964 1964 return basepcfmtstr % (len(str(max)), ' ' * padding)
1965 1965
1966 1966 def pcfmt(value, total):
1967 1967 return (value, 100 * float(value) / total)
1968 1968
1969 1969 ui.write('format : %d\n' % format)
1970 1970 ui.write('flags : %s\n' % ', '.join(flags))
1971 1971
1972 1972 ui.write('\n')
1973 1973 fmt = pcfmtstr(totalsize)
1974 1974 fmt2 = dfmtstr(totalsize)
1975 1975 ui.write('revisions : ' + fmt2 % numrevs)
1976 1976 ui.write(' merges : ' + fmt % pcfmt(nummerges, numrevs))
1977 1977 ui.write(' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs))
1978 1978 ui.write('revisions : ' + fmt2 % numrevs)
1979 1979 ui.write(' full : ' + fmt % pcfmt(numfull, numrevs))
1980 1980 ui.write(' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
1981 1981 ui.write('revision size : ' + fmt2 % totalsize)
1982 1982 ui.write(' full : ' + fmt % pcfmt(fulltotal, totalsize))
1983 1983 ui.write(' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
1984 1984
1985 1985 ui.write('\n')
1986 1986 fmt = dfmtstr(max(avgchainlen, compratio))
1987 1987 ui.write('avg chain length : ' + fmt % avgchainlen)
1988 1988 ui.write('compression ratio : ' + fmt % compratio)
1989 1989
1990 1990 if format > 0:
1991 1991 ui.write('\n')
1992 1992 ui.write('uncompressed data size (min/max/avg) : %d / %d / %d\n'
1993 1993 % tuple(datasize))
1994 1994 ui.write('full revision size (min/max/avg) : %d / %d / %d\n'
1995 1995 % tuple(fullsize))
1996 1996 ui.write('delta size (min/max/avg) : %d / %d / %d\n'
1997 1997 % tuple(deltasize))
1998 1998
1999 1999 if numdeltas > 0:
2000 2000 ui.write('\n')
2001 2001 fmt = pcfmtstr(numdeltas)
2002 2002 fmt2 = pcfmtstr(numdeltas, 4)
2003 2003 ui.write('deltas against prev : ' + fmt % pcfmt(numprev, numdeltas))
2004 2004 if numprev > 0:
2005 2005 ui.write(' where prev = p1 : ' + fmt2 % pcfmt(nump1prev, numprev))
2006 2006 ui.write(' where prev = p2 : ' + fmt2 % pcfmt(nump2prev, numprev))
2007 2007 ui.write(' other : ' + fmt2 % pcfmt(numoprev, numprev))
2008 2008 if gdelta:
2009 2009 ui.write('deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas))
2010 2010 ui.write('deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas))
2011 2011 ui.write('deltas against other : ' + fmt % pcfmt(numother, numdeltas))
2012 2012
2013 2013 @command('debugrevspec', [], ('REVSPEC'))
2014 2014 def debugrevspec(ui, repo, expr):
2015 2015 '''parse and apply a revision specification'''
2016 2016 if ui.verbose:
2017 2017 tree = revset.parse(expr)[0]
2018 2018 ui.note(tree, "\n")
2019 2019 newtree = revset.findaliases(ui, tree)
2020 2020 if newtree != tree:
2021 2021 ui.note(newtree, "\n")
2022 2022 func = revset.match(ui, expr)
2023 2023 for c in func(repo, range(len(repo))):
2024 2024 ui.write("%s\n" % c)
2025 2025
2026 2026 @command('debugsetparents', [], _('REV1 [REV2]'))
2027 2027 def debugsetparents(ui, repo, rev1, rev2=None):
2028 2028 """manually set the parents of the current working directory
2029 2029
2030 2030 This is useful for writing repository conversion tools, but should
2031 2031 be used with care.
2032 2032
2033 2033 Returns 0 on success.
2034 2034 """
2035 2035
2036 2036 r1 = scmutil.revsingle(repo, rev1).node()
2037 2037 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2038 2038
2039 2039 wlock = repo.wlock()
2040 2040 try:
2041 2041 repo.dirstate.setparents(r1, r2)
2042 2042 finally:
2043 2043 wlock.release()
2044 2044
2045 2045 @command('debugstate',
2046 2046 [('', 'nodates', None, _('do not display the saved mtime')),
2047 2047 ('', 'datesort', None, _('sort by saved mtime'))],
2048 2048 _('[OPTION]...'))
2049 2049 def debugstate(ui, repo, nodates=None, datesort=None):
2050 2050 """show the contents of the current dirstate"""
2051 2051 timestr = ""
2052 2052 showdate = not nodates
2053 2053 if datesort:
2054 2054 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2055 2055 else:
2056 2056 keyfunc = None # sort by filename
2057 2057 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2058 2058 if showdate:
2059 2059 if ent[3] == -1:
2060 2060 # Pad or slice to locale representation
2061 2061 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2062 2062 time.localtime(0)))
2063 2063 timestr = 'unset'
2064 2064 timestr = (timestr[:locale_len] +
2065 2065 ' ' * (locale_len - len(timestr)))
2066 2066 else:
2067 2067 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2068 2068 time.localtime(ent[3]))
2069 2069 if ent[1] & 020000:
2070 2070 mode = 'lnk'
2071 2071 else:
2072 2072 mode = '%3o' % (ent[1] & 0777)
2073 2073 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2074 2074 for f in repo.dirstate.copies():
2075 2075 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2076 2076
2077 2077 @command('debugsub',
2078 2078 [('r', 'rev', '',
2079 2079 _('revision to check'), _('REV'))],
2080 2080 _('[-r REV] [REV]'))
2081 2081 def debugsub(ui, repo, rev=None):
2082 2082 ctx = scmutil.revsingle(repo, rev, None)
2083 2083 for k, v in sorted(ctx.substate.items()):
2084 2084 ui.write('path %s\n' % k)
2085 2085 ui.write(' source %s\n' % v[0])
2086 2086 ui.write(' revision %s\n' % v[1])
2087 2087
2088 2088 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2089 2089 def debugwalk(ui, repo, *pats, **opts):
2090 2090 """show how files match on given patterns"""
2091 2091 m = scmutil.match(repo, pats, opts)
2092 2092 items = list(repo.walk(m))
2093 2093 if not items:
2094 2094 return
2095 2095 fmt = 'f %%-%ds %%-%ds %%s' % (
2096 2096 max([len(abs) for abs in items]),
2097 2097 max([len(m.rel(abs)) for abs in items]))
2098 2098 for abs in items:
2099 2099 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
2100 2100 ui.write("%s\n" % line.rstrip())
2101 2101
2102 2102 @command('debugwireargs',
2103 2103 [('', 'three', '', 'three'),
2104 2104 ('', 'four', '', 'four'),
2105 2105 ('', 'five', '', 'five'),
2106 2106 ] + remoteopts,
2107 2107 _('REPO [OPTIONS]... [ONE [TWO]]'))
2108 2108 def debugwireargs(ui, repopath, *vals, **opts):
2109 2109 repo = hg.repository(hg.remoteui(ui, opts), repopath)
2110 2110 for opt in remoteopts:
2111 2111 del opts[opt[1]]
2112 2112 args = {}
2113 2113 for k, v in opts.iteritems():
2114 2114 if v:
2115 2115 args[k] = v
2116 2116 # run twice to check that we don't mess up the stream for the next command
2117 2117 res1 = repo.debugwireargs(*vals, **args)
2118 2118 res2 = repo.debugwireargs(*vals, **args)
2119 2119 ui.write("%s\n" % res1)
2120 2120 if res1 != res2:
2121 2121 ui.warn("%s\n" % res2)
2122 2122
2123 2123 @command('^diff',
2124 2124 [('r', 'rev', [], _('revision'), _('REV')),
2125 2125 ('c', 'change', '', _('change made by revision'), _('REV'))
2126 2126 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2127 2127 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2128 2128 def diff(ui, repo, *pats, **opts):
2129 2129 """diff repository (or selected files)
2130 2130
2131 2131 Show differences between revisions for the specified files.
2132 2132
2133 2133 Differences between files are shown using the unified diff format.
2134 2134
2135 2135 .. note::
2136 2136 diff may generate unexpected results for merges, as it will
2137 2137 default to comparing against the working directory's first
2138 2138 parent changeset if no revisions are specified.
2139 2139
2140 2140 When two revision arguments are given, then changes are shown
2141 2141 between those revisions. If only one revision is specified then
2142 2142 that revision is compared to the working directory, and, when no
2143 2143 revisions are specified, the working directory files are compared
2144 2144 to its parent.
2145 2145
2146 2146 Alternatively you can specify -c/--change with a revision to see
2147 2147 the changes in that changeset relative to its first parent.
2148 2148
2149 2149 Without the -a/--text option, diff will avoid generating diffs of
2150 2150 files it detects as binary. With -a, diff will generate a diff
2151 2151 anyway, probably with undesirable results.
2152 2152
2153 2153 Use the -g/--git option to generate diffs in the git extended diff
2154 2154 format. For more information, read :hg:`help diffs`.
2155 2155
2156 2156 Returns 0 on success.
2157 2157 """
2158 2158
2159 2159 revs = opts.get('rev')
2160 2160 change = opts.get('change')
2161 2161 stat = opts.get('stat')
2162 2162 reverse = opts.get('reverse')
2163 2163
2164 2164 if revs and change:
2165 2165 msg = _('cannot specify --rev and --change at the same time')
2166 2166 raise util.Abort(msg)
2167 2167 elif change:
2168 2168 node2 = scmutil.revsingle(repo, change, None).node()
2169 2169 node1 = repo[node2].p1().node()
2170 2170 else:
2171 2171 node1, node2 = scmutil.revpair(repo, revs)
2172 2172
2173 2173 if reverse:
2174 2174 node1, node2 = node2, node1
2175 2175
2176 2176 diffopts = patch.diffopts(ui, opts)
2177 2177 m = scmutil.match(repo, pats, opts)
2178 2178 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2179 2179 listsubrepos=opts.get('subrepos'))
2180 2180
2181 2181 @command('^export',
2182 2182 [('o', 'output', '',
2183 2183 _('print output to file with formatted name'), _('FORMAT')),
2184 2184 ('', 'switch-parent', None, _('diff against the second parent')),
2185 2185 ('r', 'rev', [], _('revisions to export'), _('REV')),
2186 2186 ] + diffopts,
2187 2187 _('[OPTION]... [-o OUTFILESPEC] REV...'))
2188 2188 def export(ui, repo, *changesets, **opts):
2189 2189 """dump the header and diffs for one or more changesets
2190 2190
2191 2191 Print the changeset header and diffs for one or more revisions.
2192 2192
2193 2193 The information shown in the changeset header is: author, date,
2194 2194 branch name (if non-default), changeset hash, parent(s) and commit
2195 2195 comment.
2196 2196
2197 2197 .. note::
2198 2198 export may generate unexpected diff output for merge
2199 2199 changesets, as it will compare the merge changeset against its
2200 2200 first parent only.
2201 2201
2202 2202 Output may be to a file, in which case the name of the file is
2203 2203 given using a format string. The formatting rules are as follows:
2204 2204
2205 2205 :``%%``: literal "%" character
2206 2206 :``%H``: changeset hash (40 hexadecimal digits)
2207 2207 :``%N``: number of patches being generated
2208 2208 :``%R``: changeset revision number
2209 2209 :``%b``: basename of the exporting repository
2210 2210 :``%h``: short-form changeset hash (12 hexadecimal digits)
2211 2211 :``%n``: zero-padded sequence number, starting at 1
2212 2212 :``%r``: zero-padded changeset revision number
2213 2213
2214 2214 Without the -a/--text option, export will avoid generating diffs
2215 2215 of files it detects as binary. With -a, export will generate a
2216 2216 diff anyway, probably with undesirable results.
2217 2217
2218 2218 Use the -g/--git option to generate diffs in the git extended diff
2219 2219 format. See :hg:`help diffs` for more information.
2220 2220
2221 2221 With the --switch-parent option, the diff will be against the
2222 2222 second parent. It can be useful to review a merge.
2223 2223
2224 2224 Returns 0 on success.
2225 2225 """
2226 2226 changesets += tuple(opts.get('rev', []))
2227 2227 if not changesets:
2228 2228 raise util.Abort(_("export requires at least one changeset"))
2229 2229 revs = scmutil.revrange(repo, changesets)
2230 2230 if len(revs) > 1:
2231 2231 ui.note(_('exporting patches:\n'))
2232 2232 else:
2233 2233 ui.note(_('exporting patch:\n'))
2234 2234 cmdutil.export(repo, revs, template=opts.get('output'),
2235 2235 switch_parent=opts.get('switch_parent'),
2236 2236 opts=patch.diffopts(ui, opts))
2237 2237
2238 2238 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2239 2239 def forget(ui, repo, *pats, **opts):
2240 2240 """forget the specified files on the next commit
2241 2241
2242 2242 Mark the specified files so they will no longer be tracked
2243 2243 after the next commit.
2244 2244
2245 2245 This only removes files from the current branch, not from the
2246 2246 entire project history, and it does not delete them from the
2247 2247 working directory.
2248 2248
2249 2249 To undo a forget before the next commit, see :hg:`add`.
2250 2250
2251 2251 Returns 0 on success.
2252 2252 """
2253 2253
2254 2254 if not pats:
2255 2255 raise util.Abort(_('no files specified'))
2256 2256
2257 2257 m = scmutil.match(repo, pats, opts)
2258 2258 s = repo.status(match=m, clean=True)
2259 2259 forget = sorted(s[0] + s[1] + s[3] + s[6])
2260 2260 errs = 0
2261 2261
2262 2262 for f in m.files():
2263 2263 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2264 2264 ui.warn(_('not removing %s: file is already untracked\n')
2265 2265 % m.rel(f))
2266 2266 errs = 1
2267 2267
2268 2268 for f in forget:
2269 2269 if ui.verbose or not m.exact(f):
2270 2270 ui.status(_('removing %s\n') % m.rel(f))
2271 2271
2272 2272 repo[None].remove(forget, unlink=False)
2273 2273 return errs
2274 2274
2275 2275 @command('grep',
2276 2276 [('0', 'print0', None, _('end fields with NUL')),
2277 2277 ('', 'all', None, _('print all revisions that match')),
2278 2278 ('a', 'text', None, _('treat all files as text')),
2279 2279 ('f', 'follow', None,
2280 2280 _('follow changeset history,'
2281 2281 ' or file history across copies and renames')),
2282 2282 ('i', 'ignore-case', None, _('ignore case when matching')),
2283 2283 ('l', 'files-with-matches', None,
2284 2284 _('print only filenames and revisions that match')),
2285 2285 ('n', 'line-number', None, _('print matching line numbers')),
2286 2286 ('r', 'rev', [],
2287 2287 _('only search files changed within revision range'), _('REV')),
2288 2288 ('u', 'user', None, _('list the author (long with -v)')),
2289 2289 ('d', 'date', None, _('list the date (short with -q)')),
2290 2290 ] + walkopts,
2291 2291 _('[OPTION]... PATTERN [FILE]...'))
2292 2292 def grep(ui, repo, pattern, *pats, **opts):
2293 2293 """search for a pattern in specified files and revisions
2294 2294
2295 2295 Search revisions of files for a regular expression.
2296 2296
2297 2297 This command behaves differently than Unix grep. It only accepts
2298 2298 Python/Perl regexps. It searches repository history, not the
2299 2299 working directory. It always prints the revision number in which a
2300 2300 match appears.
2301 2301
2302 2302 By default, grep only prints output for the first revision of a
2303 2303 file in which it finds a match. To get it to print every revision
2304 2304 that contains a change in match status ("-" for a match that
2305 2305 becomes a non-match, or "+" for a non-match that becomes a match),
2306 2306 use the --all flag.
2307 2307
2308 2308 Returns 0 if a match is found, 1 otherwise.
2309 2309 """
2310 2310 reflags = 0
2311 2311 if opts.get('ignore_case'):
2312 2312 reflags |= re.I
2313 2313 try:
2314 2314 regexp = re.compile(pattern, reflags)
2315 2315 except re.error, inst:
2316 2316 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2317 2317 return 1
2318 2318 sep, eol = ':', '\n'
2319 2319 if opts.get('print0'):
2320 2320 sep = eol = '\0'
2321 2321
2322 2322 getfile = util.lrucachefunc(repo.file)
2323 2323
2324 2324 def matchlines(body):
2325 2325 begin = 0
2326 2326 linenum = 0
2327 2327 while True:
2328 2328 match = regexp.search(body, begin)
2329 2329 if not match:
2330 2330 break
2331 2331 mstart, mend = match.span()
2332 2332 linenum += body.count('\n', begin, mstart) + 1
2333 2333 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2334 2334 begin = body.find('\n', mend) + 1 or len(body)
2335 2335 lend = begin - 1
2336 2336 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2337 2337
2338 2338 class linestate(object):
2339 2339 def __init__(self, line, linenum, colstart, colend):
2340 2340 self.line = line
2341 2341 self.linenum = linenum
2342 2342 self.colstart = colstart
2343 2343 self.colend = colend
2344 2344
2345 2345 def __hash__(self):
2346 2346 return hash((self.linenum, self.line))
2347 2347
2348 2348 def __eq__(self, other):
2349 2349 return self.line == other.line
2350 2350
2351 2351 matches = {}
2352 2352 copies = {}
2353 2353 def grepbody(fn, rev, body):
2354 2354 matches[rev].setdefault(fn, [])
2355 2355 m = matches[rev][fn]
2356 2356 for lnum, cstart, cend, line in matchlines(body):
2357 2357 s = linestate(line, lnum, cstart, cend)
2358 2358 m.append(s)
2359 2359
2360 2360 def difflinestates(a, b):
2361 2361 sm = difflib.SequenceMatcher(None, a, b)
2362 2362 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2363 2363 if tag == 'insert':
2364 2364 for i in xrange(blo, bhi):
2365 2365 yield ('+', b[i])
2366 2366 elif tag == 'delete':
2367 2367 for i in xrange(alo, ahi):
2368 2368 yield ('-', a[i])
2369 2369 elif tag == 'replace':
2370 2370 for i in xrange(alo, ahi):
2371 2371 yield ('-', a[i])
2372 2372 for i in xrange(blo, bhi):
2373 2373 yield ('+', b[i])
2374 2374
2375 2375 def display(fn, ctx, pstates, states):
2376 2376 rev = ctx.rev()
2377 2377 datefunc = ui.quiet and util.shortdate or util.datestr
2378 2378 found = False
2379 2379 filerevmatches = {}
2380 2380 def binary():
2381 2381 flog = getfile(fn)
2382 2382 return util.binary(flog.read(ctx.filenode(fn)))
2383 2383
2384 2384 if opts.get('all'):
2385 2385 iter = difflinestates(pstates, states)
2386 2386 else:
2387 2387 iter = [('', l) for l in states]
2388 2388 for change, l in iter:
2389 2389 cols = [fn, str(rev)]
2390 2390 before, match, after = None, None, None
2391 2391 if opts.get('line_number'):
2392 2392 cols.append(str(l.linenum))
2393 2393 if opts.get('all'):
2394 2394 cols.append(change)
2395 2395 if opts.get('user'):
2396 2396 cols.append(ui.shortuser(ctx.user()))
2397 2397 if opts.get('date'):
2398 2398 cols.append(datefunc(ctx.date()))
2399 2399 if opts.get('files_with_matches'):
2400 2400 c = (fn, rev)
2401 2401 if c in filerevmatches:
2402 2402 continue
2403 2403 filerevmatches[c] = 1
2404 2404 else:
2405 2405 before = l.line[:l.colstart]
2406 2406 match = l.line[l.colstart:l.colend]
2407 2407 after = l.line[l.colend:]
2408 2408 ui.write(sep.join(cols))
2409 2409 if before is not None:
2410 2410 if not opts.get('text') and binary():
2411 2411 ui.write(sep + " Binary file matches")
2412 2412 else:
2413 2413 ui.write(sep + before)
2414 2414 ui.write(match, label='grep.match')
2415 2415 ui.write(after)
2416 2416 ui.write(eol)
2417 2417 found = True
2418 2418 return found
2419 2419
2420 2420 skip = {}
2421 2421 revfiles = {}
2422 2422 matchfn = scmutil.match(repo, pats, opts)
2423 2423 found = False
2424 2424 follow = opts.get('follow')
2425 2425
2426 2426 def prep(ctx, fns):
2427 2427 rev = ctx.rev()
2428 2428 pctx = ctx.p1()
2429 2429 parent = pctx.rev()
2430 2430 matches.setdefault(rev, {})
2431 2431 matches.setdefault(parent, {})
2432 2432 files = revfiles.setdefault(rev, [])
2433 2433 for fn in fns:
2434 2434 flog = getfile(fn)
2435 2435 try:
2436 2436 fnode = ctx.filenode(fn)
2437 2437 except error.LookupError:
2438 2438 continue
2439 2439
2440 2440 copied = flog.renamed(fnode)
2441 2441 copy = follow and copied and copied[0]
2442 2442 if copy:
2443 2443 copies.setdefault(rev, {})[fn] = copy
2444 2444 if fn in skip:
2445 2445 if copy:
2446 2446 skip[copy] = True
2447 2447 continue
2448 2448 files.append(fn)
2449 2449
2450 2450 if fn not in matches[rev]:
2451 2451 grepbody(fn, rev, flog.read(fnode))
2452 2452
2453 2453 pfn = copy or fn
2454 2454 if pfn not in matches[parent]:
2455 2455 try:
2456 2456 fnode = pctx.filenode(pfn)
2457 2457 grepbody(pfn, parent, flog.read(fnode))
2458 2458 except error.LookupError:
2459 2459 pass
2460 2460
2461 2461 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2462 2462 rev = ctx.rev()
2463 2463 parent = ctx.p1().rev()
2464 2464 for fn in sorted(revfiles.get(rev, [])):
2465 2465 states = matches[rev][fn]
2466 2466 copy = copies.get(rev, {}).get(fn)
2467 2467 if fn in skip:
2468 2468 if copy:
2469 2469 skip[copy] = True
2470 2470 continue
2471 2471 pstates = matches.get(parent, {}).get(copy or fn, [])
2472 2472 if pstates or states:
2473 2473 r = display(fn, ctx, pstates, states)
2474 2474 found = found or r
2475 2475 if r and not opts.get('all'):
2476 2476 skip[fn] = True
2477 2477 if copy:
2478 2478 skip[copy] = True
2479 2479 del matches[rev]
2480 2480 del revfiles[rev]
2481 2481
2482 2482 return not found
2483 2483
2484 2484 @command('heads',
2485 2485 [('r', 'rev', '',
2486 2486 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2487 2487 ('t', 'topo', False, _('show topological heads only')),
2488 2488 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2489 2489 ('c', 'closed', False, _('show normal and closed branch heads')),
2490 2490 ] + templateopts,
2491 2491 _('[-ac] [-r STARTREV] [REV]...'))
2492 2492 def heads(ui, repo, *branchrevs, **opts):
2493 2493 """show current repository heads or show branch heads
2494 2494
2495 2495 With no arguments, show all repository branch heads.
2496 2496
2497 2497 Repository "heads" are changesets with no child changesets. They are
2498 2498 where development generally takes place and are the usual targets
2499 2499 for update and merge operations. Branch heads are changesets that have
2500 2500 no child changeset on the same branch.
2501 2501
2502 2502 If one or more REVs are given, only branch heads on the branches
2503 2503 associated with the specified changesets are shown.
2504 2504
2505 2505 If -c/--closed is specified, also show branch heads marked closed
2506 2506 (see :hg:`commit --close-branch`).
2507 2507
2508 2508 If STARTREV is specified, only those heads that are descendants of
2509 2509 STARTREV will be displayed.
2510 2510
2511 2511 If -t/--topo is specified, named branch mechanics will be ignored and only
2512 2512 changesets without children will be shown.
2513 2513
2514 2514 Returns 0 if matching heads are found, 1 if not.
2515 2515 """
2516 2516
2517 2517 start = None
2518 2518 if 'rev' in opts:
2519 2519 start = scmutil.revsingle(repo, opts['rev'], None).node()
2520 2520
2521 2521 if opts.get('topo'):
2522 2522 heads = [repo[h] for h in repo.heads(start)]
2523 2523 else:
2524 2524 heads = []
2525 2525 for b, ls in repo.branchmap().iteritems():
2526 2526 if start is None:
2527 2527 heads += [repo[h] for h in ls]
2528 2528 continue
2529 2529 startrev = repo.changelog.rev(start)
2530 2530 descendants = set(repo.changelog.descendants(startrev))
2531 2531 descendants.add(startrev)
2532 2532 rev = repo.changelog.rev
2533 2533 heads += [repo[h] for h in ls if rev(h) in descendants]
2534 2534
2535 2535 if branchrevs:
2536 2536 branches = set(repo[br].branch() for br in branchrevs)
2537 2537 heads = [h for h in heads if h.branch() in branches]
2538 2538
2539 2539 if not opts.get('closed'):
2540 2540 heads = [h for h in heads if not h.extra().get('close')]
2541 2541
2542 2542 if opts.get('active') and branchrevs:
2543 2543 dagheads = repo.heads(start)
2544 2544 heads = [h for h in heads if h.node() in dagheads]
2545 2545
2546 2546 if branchrevs:
2547 2547 haveheads = set(h.branch() for h in heads)
2548 2548 if branches - haveheads:
2549 2549 headless = ', '.join(b for b in branches - haveheads)
2550 2550 msg = _('no open branch heads found on branches %s')
2551 2551 if opts.get('rev'):
2552 2552 msg += _(' (started at %s)' % opts['rev'])
2553 2553 ui.warn((msg + '\n') % headless)
2554 2554
2555 2555 if not heads:
2556 2556 return 1
2557 2557
2558 2558 heads = sorted(heads, key=lambda x: -x.rev())
2559 2559 displayer = cmdutil.show_changeset(ui, repo, opts)
2560 2560 for ctx in heads:
2561 2561 displayer.show(ctx)
2562 2562 displayer.close()
2563 2563
2564 2564 @command('help',
2565 2565 [('e', 'extension', None, _('show only help for extensions')),
2566 2566 ('c', 'command', None, _('show only help for commands'))],
2567 2567 _('[-ec] [TOPIC]'))
2568 2568 def help_(ui, name=None, with_version=False, unknowncmd=False, full=True, **opts):
2569 2569 """show help for a given topic or a help overview
2570 2570
2571 2571 With no arguments, print a list of commands with short help messages.
2572 2572
2573 2573 Given a topic, extension, or command name, print help for that
2574 2574 topic.
2575 2575
2576 2576 Returns 0 if successful.
2577 2577 """
2578 2578 option_lists = []
2579 2579 textwidth = min(ui.termwidth(), 80) - 2
2580 2580
2581 2581 def addglobalopts(aliases):
2582 2582 if ui.verbose:
2583 2583 option_lists.append((_("global options:"), globalopts))
2584 2584 if name == 'shortlist':
2585 2585 option_lists.append((_('use "hg help" for the full list '
2586 2586 'of commands'), ()))
2587 2587 else:
2588 2588 if name == 'shortlist':
2589 2589 msg = _('use "hg help" for the full list of commands '
2590 2590 'or "hg -v" for details')
2591 2591 elif name and not full:
2592 2592 msg = _('use "hg help %s" to show the full help text' % name)
2593 2593 elif aliases:
2594 2594 msg = _('use "hg -v help%s" to show builtin aliases and '
2595 2595 'global options') % (name and " " + name or "")
2596 2596 else:
2597 2597 msg = _('use "hg -v help %s" to show global options') % name
2598 2598 option_lists.append((msg, ()))
2599 2599
2600 2600 def helpcmd(name):
2601 2601 if with_version:
2602 2602 version_(ui)
2603 2603 ui.write('\n')
2604 2604
2605 2605 try:
2606 2606 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
2607 2607 except error.AmbiguousCommand, inst:
2608 2608 # py3k fix: except vars can't be used outside the scope of the
2609 2609 # except block, nor can be used inside a lambda. python issue4617
2610 2610 prefix = inst.args[0]
2611 2611 select = lambda c: c.lstrip('^').startswith(prefix)
2612 2612 helplist(_('list of commands:\n\n'), select)
2613 2613 return
2614 2614
2615 2615 # check if it's an invalid alias and display its error if it is
2616 2616 if getattr(entry[0], 'badalias', False):
2617 2617 if not unknowncmd:
2618 2618 entry[0](ui)
2619 2619 return
2620 2620
2621 2621 # synopsis
2622 2622 if len(entry) > 2:
2623 2623 if entry[2].startswith('hg'):
2624 2624 ui.write("%s\n" % entry[2])
2625 2625 else:
2626 2626 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
2627 2627 else:
2628 2628 ui.write('hg %s\n' % aliases[0])
2629 2629
2630 2630 # aliases
2631 2631 if full and not ui.quiet and len(aliases) > 1:
2632 2632 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
2633 2633
2634 2634 # description
2635 2635 doc = gettext(entry[0].__doc__)
2636 2636 if not doc:
2637 2637 doc = _("(no help text available)")
2638 2638 if hasattr(entry[0], 'definition'): # aliased command
2639 2639 if entry[0].definition.startswith('!'): # shell alias
2640 2640 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
2641 2641 else:
2642 2642 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
2643 2643 if ui.quiet or not full:
2644 2644 doc = doc.splitlines()[0]
2645 2645 keep = ui.verbose and ['verbose'] or []
2646 2646 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
2647 2647 ui.write("\n%s\n" % formatted)
2648 2648 if pruned:
2649 2649 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
2650 2650
2651 2651 if not ui.quiet:
2652 2652 # options
2653 2653 if entry[1]:
2654 2654 option_lists.append((_("options:\n"), entry[1]))
2655 2655
2656 2656 addglobalopts(False)
2657 2657
2658 2658 # check if this command shadows a non-trivial (multi-line)
2659 2659 # extension help text
2660 2660 try:
2661 2661 mod = extensions.find(name)
2662 2662 doc = gettext(mod.__doc__) or ''
2663 2663 if '\n' in doc.strip():
2664 2664 msg = _('use "hg help -e %s" to show help for '
2665 2665 'the %s extension') % (name, name)
2666 2666 ui.write('\n%s\n' % msg)
2667 2667 except KeyError:
2668 2668 pass
2669 2669
2670 2670 def helplist(header, select=None):
2671 2671 h = {}
2672 2672 cmds = {}
2673 2673 for c, e in table.iteritems():
2674 2674 f = c.split("|", 1)[0]
2675 2675 if select and not select(f):
2676 2676 continue
2677 2677 if (not select and name != 'shortlist' and
2678 2678 e[0].__module__ != __name__):
2679 2679 continue
2680 2680 if name == "shortlist" and not f.startswith("^"):
2681 2681 continue
2682 2682 f = f.lstrip("^")
2683 2683 if not ui.debugflag and f.startswith("debug"):
2684 2684 continue
2685 2685 doc = e[0].__doc__
2686 2686 if doc and 'DEPRECATED' in doc and not ui.verbose:
2687 2687 continue
2688 2688 doc = gettext(doc)
2689 2689 if not doc:
2690 2690 doc = _("(no help text available)")
2691 2691 h[f] = doc.splitlines()[0].rstrip()
2692 2692 cmds[f] = c.lstrip("^")
2693 2693
2694 2694 if not h:
2695 2695 ui.status(_('no commands defined\n'))
2696 2696 return
2697 2697
2698 2698 ui.status(header)
2699 2699 fns = sorted(h)
2700 2700 m = max(map(len, fns))
2701 2701 for f in fns:
2702 2702 if ui.verbose:
2703 2703 commands = cmds[f].replace("|",", ")
2704 2704 ui.write(" %s:\n %s\n"%(commands, h[f]))
2705 2705 else:
2706 2706 ui.write('%s\n' % (util.wrap(h[f], textwidth,
2707 2707 initindent=' %-*s ' % (m, f),
2708 2708 hangindent=' ' * (m + 4))))
2709 2709
2710 2710 if not ui.quiet:
2711 2711 addglobalopts(True)
2712 2712
2713 2713 def helptopic(name):
2714 2714 for names, header, doc in help.helptable:
2715 2715 if name in names:
2716 2716 break
2717 2717 else:
2718 2718 raise error.UnknownCommand(name)
2719 2719
2720 2720 # description
2721 2721 if not doc:
2722 2722 doc = _("(no help text available)")
2723 2723 if hasattr(doc, '__call__'):
2724 2724 doc = doc()
2725 2725
2726 2726 ui.write("%s\n\n" % header)
2727 2727 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
2728 2728 try:
2729 2729 cmdutil.findcmd(name, table)
2730 2730 ui.write(_('\nuse "hg help -c %s" to see help for '
2731 2731 'the %s command\n') % (name, name))
2732 2732 except error.UnknownCommand:
2733 2733 pass
2734 2734
2735 2735 def helpext(name):
2736 2736 try:
2737 2737 mod = extensions.find(name)
2738 2738 doc = gettext(mod.__doc__) or _('no help text available')
2739 2739 except KeyError:
2740 2740 mod = None
2741 2741 doc = extensions.disabledext(name)
2742 2742 if not doc:
2743 2743 raise error.UnknownCommand(name)
2744 2744
2745 2745 if '\n' not in doc:
2746 2746 head, tail = doc, ""
2747 2747 else:
2748 2748 head, tail = doc.split('\n', 1)
2749 2749 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
2750 2750 if tail:
2751 2751 ui.write(minirst.format(tail, textwidth))
2752 2752 ui.status('\n\n')
2753 2753
2754 2754 if mod:
2755 2755 try:
2756 2756 ct = mod.cmdtable
2757 2757 except AttributeError:
2758 2758 ct = {}
2759 2759 modcmds = set([c.split('|', 1)[0] for c in ct])
2760 2760 helplist(_('list of commands:\n\n'), modcmds.__contains__)
2761 2761 else:
2762 2762 ui.write(_('use "hg help extensions" for information on enabling '
2763 2763 'extensions\n'))
2764 2764
2765 2765 def helpextcmd(name):
2766 2766 cmd, ext, mod = extensions.disabledcmd(ui, name, ui.config('ui', 'strict'))
2767 2767 doc = gettext(mod.__doc__).splitlines()[0]
2768 2768
2769 2769 msg = help.listexts(_("'%s' is provided by the following "
2770 2770 "extension:") % cmd, {ext: doc}, indent=4)
2771 2771 ui.write(minirst.format(msg, textwidth))
2772 2772 ui.write('\n\n')
2773 2773 ui.write(_('use "hg help extensions" for information on enabling '
2774 2774 'extensions\n'))
2775 2775
2776 2776 if name and name != 'shortlist':
2777 2777 i = None
2778 2778 if unknowncmd:
2779 2779 queries = (helpextcmd,)
2780 2780 elif opts.get('extension'):
2781 2781 queries = (helpext,)
2782 2782 elif opts.get('command'):
2783 2783 queries = (helpcmd,)
2784 2784 else:
2785 2785 queries = (helptopic, helpcmd, helpext, helpextcmd)
2786 2786 for f in queries:
2787 2787 try:
2788 2788 f(name)
2789 2789 i = None
2790 2790 break
2791 2791 except error.UnknownCommand, inst:
2792 2792 i = inst
2793 2793 if i:
2794 2794 raise i
2795 2795
2796 2796 else:
2797 2797 # program name
2798 2798 if ui.verbose or with_version:
2799 2799 version_(ui)
2800 2800 else:
2801 2801 ui.status(_("Mercurial Distributed SCM\n"))
2802 2802 ui.status('\n')
2803 2803
2804 2804 # list of commands
2805 2805 if name == "shortlist":
2806 2806 header = _('basic commands:\n\n')
2807 2807 else:
2808 2808 header = _('list of commands:\n\n')
2809 2809
2810 2810 helplist(header)
2811 2811 if name != 'shortlist':
2812 2812 text = help.listexts(_('enabled extensions:'), extensions.enabled())
2813 2813 if text:
2814 2814 ui.write("\n%s\n" % minirst.format(text, textwidth))
2815 2815
2816 2816 # list all option lists
2817 2817 opt_output = []
2818 2818 multioccur = False
2819 2819 for title, options in option_lists:
2820 2820 opt_output.append(("\n%s" % title, None))
2821 2821 for option in options:
2822 2822 if len(option) == 5:
2823 2823 shortopt, longopt, default, desc, optlabel = option
2824 2824 else:
2825 2825 shortopt, longopt, default, desc = option
2826 2826 optlabel = _("VALUE") # default label
2827 2827
2828 2828 if _("DEPRECATED") in desc and not ui.verbose:
2829 2829 continue
2830 2830 if isinstance(default, list):
2831 2831 numqualifier = " %s [+]" % optlabel
2832 2832 multioccur = True
2833 2833 elif (default is not None) and not isinstance(default, bool):
2834 2834 numqualifier = " %s" % optlabel
2835 2835 else:
2836 2836 numqualifier = ""
2837 2837 opt_output.append(("%2s%s" %
2838 2838 (shortopt and "-%s" % shortopt,
2839 2839 longopt and " --%s%s" %
2840 2840 (longopt, numqualifier)),
2841 2841 "%s%s" % (desc,
2842 2842 default
2843 2843 and _(" (default: %s)") % default
2844 2844 or "")))
2845 2845 if multioccur:
2846 2846 msg = _("\n[+] marked option can be specified multiple times")
2847 2847 if ui.verbose and name != 'shortlist':
2848 2848 opt_output.append((msg, None))
2849 2849 else:
2850 2850 opt_output.insert(-1, (msg, None))
2851 2851
2852 2852 if not name:
2853 2853 ui.write(_("\nadditional help topics:\n\n"))
2854 2854 topics = []
2855 2855 for names, header, doc in help.helptable:
2856 2856 topics.append((sorted(names, key=len, reverse=True)[0], header))
2857 2857 topics_len = max([len(s[0]) for s in topics])
2858 2858 for t, desc in topics:
2859 2859 ui.write(" %-*s %s\n" % (topics_len, t, desc))
2860 2860
2861 2861 if opt_output:
2862 2862 colwidth = encoding.colwidth
2863 2863 # normalize: (opt or message, desc or None, width of opt)
2864 2864 entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0)
2865 2865 for opt, desc in opt_output]
2866 2866 hanging = max([e[2] for e in entries])
2867 2867 for opt, desc, width in entries:
2868 2868 if desc:
2869 2869 initindent = ' %s%s ' % (opt, ' ' * (hanging - width))
2870 2870 hangindent = ' ' * (hanging + 3)
2871 2871 ui.write('%s\n' % (util.wrap(desc, textwidth,
2872 2872 initindent=initindent,
2873 2873 hangindent=hangindent)))
2874 2874 else:
2875 2875 ui.write("%s\n" % opt)
2876 2876
2877 2877 @command('identify|id',
2878 2878 [('r', 'rev', '',
2879 2879 _('identify the specified revision'), _('REV')),
2880 2880 ('n', 'num', None, _('show local revision number')),
2881 2881 ('i', 'id', None, _('show global revision id')),
2882 2882 ('b', 'branch', None, _('show branch')),
2883 2883 ('t', 'tags', None, _('show tags')),
2884 2884 ('B', 'bookmarks', None, _('show bookmarks'))],
2885 2885 _('[-nibtB] [-r REV] [SOURCE]'))
2886 2886 def identify(ui, repo, source=None, rev=None,
2887 2887 num=None, id=None, branch=None, tags=None, bookmarks=None):
2888 2888 """identify the working copy or specified revision
2889 2889
2890 2890 Print a summary identifying the repository state at REV using one or
2891 2891 two parent hash identifiers, followed by a "+" if the working
2892 2892 directory has uncommitted changes, the branch name (if not default),
2893 2893 a list of tags, and a list of bookmarks.
2894 2894
2895 2895 When REV is not given, print a summary of the current state of the
2896 2896 repository.
2897 2897
2898 2898 Specifying a path to a repository root or Mercurial bundle will
2899 2899 cause lookup to operate on that repository/bundle.
2900 2900
2901 2901 Returns 0 if successful.
2902 2902 """
2903 2903
2904 2904 if not repo and not source:
2905 2905 raise util.Abort(_("there is no Mercurial repository here "
2906 2906 "(.hg not found)"))
2907 2907
2908 2908 hexfunc = ui.debugflag and hex or short
2909 2909 default = not (num or id or branch or tags or bookmarks)
2910 2910 output = []
2911 2911 revs = []
2912 2912
2913 2913 if source:
2914 2914 source, branches = hg.parseurl(ui.expandpath(source))
2915 2915 repo = hg.repository(ui, source)
2916 2916 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
2917 2917
2918 2918 if not repo.local():
2919 2919 if num or branch or tags:
2920 2920 raise util.Abort(
2921 2921 _("can't query remote revision number, branch, or tags"))
2922 2922 if not rev and revs:
2923 2923 rev = revs[0]
2924 2924 if not rev:
2925 2925 rev = "tip"
2926 2926
2927 2927 remoterev = repo.lookup(rev)
2928 2928 if default or id:
2929 2929 output = [hexfunc(remoterev)]
2930 2930
2931 2931 def getbms():
2932 2932 bms = []
2933 2933
2934 2934 if 'bookmarks' in repo.listkeys('namespaces'):
2935 2935 hexremoterev = hex(remoterev)
2936 2936 bms = [bm for bm, bmr in repo.listkeys('bookmarks').iteritems()
2937 2937 if bmr == hexremoterev]
2938 2938
2939 2939 return bms
2940 2940
2941 2941 if bookmarks:
2942 2942 output.extend(getbms())
2943 2943 elif default and not ui.quiet:
2944 2944 # multiple bookmarks for a single parent separated by '/'
2945 2945 bm = '/'.join(getbms())
2946 2946 if bm:
2947 2947 output.append(bm)
2948 2948 else:
2949 2949 if not rev:
2950 2950 ctx = repo[None]
2951 2951 parents = ctx.parents()
2952 2952 changed = ""
2953 2953 if default or id or num:
2954 2954 changed = util.any(repo.status()) and "+" or ""
2955 2955 if default or id:
2956 2956 output = ["%s%s" %
2957 2957 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
2958 2958 if num:
2959 2959 output.append("%s%s" %
2960 2960 ('+'.join([str(p.rev()) for p in parents]), changed))
2961 2961 else:
2962 2962 ctx = scmutil.revsingle(repo, rev)
2963 2963 if default or id:
2964 2964 output = [hexfunc(ctx.node())]
2965 2965 if num:
2966 2966 output.append(str(ctx.rev()))
2967 2967
2968 2968 if default and not ui.quiet:
2969 2969 b = ctx.branch()
2970 2970 if b != 'default':
2971 2971 output.append("(%s)" % b)
2972 2972
2973 2973 # multiple tags for a single parent separated by '/'
2974 2974 t = '/'.join(ctx.tags())
2975 2975 if t:
2976 2976 output.append(t)
2977 2977
2978 2978 # multiple bookmarks for a single parent separated by '/'
2979 2979 bm = '/'.join(ctx.bookmarks())
2980 2980 if bm:
2981 2981 output.append(bm)
2982 2982 else:
2983 2983 if branch:
2984 2984 output.append(ctx.branch())
2985 2985
2986 2986 if tags:
2987 2987 output.extend(ctx.tags())
2988 2988
2989 2989 if bookmarks:
2990 2990 output.extend(ctx.bookmarks())
2991 2991
2992 2992 ui.write("%s\n" % ' '.join(output))
2993 2993
2994 2994 @command('import|patch',
2995 2995 [('p', 'strip', 1,
2996 2996 _('directory strip option for patch. This has the same '
2997 2997 'meaning as the corresponding patch option'), _('NUM')),
2998 2998 ('b', 'base', '', _('base path'), _('PATH')),
2999 2999 ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
3000 3000 ('', 'no-commit', None,
3001 3001 _("don't commit, just update the working directory")),
3002 3002 ('', 'exact', None,
3003 3003 _('apply patch to the nodes from which it was generated')),
3004 3004 ('', 'import-branch', None,
3005 3005 _('use any branch information in patch (implied by --exact)'))] +
3006 3006 commitopts + commitopts2 + similarityopts,
3007 3007 _('[OPTION]... PATCH...'))
3008 3008 def import_(ui, repo, patch1, *patches, **opts):
3009 3009 """import an ordered set of patches
3010 3010
3011 3011 Import a list of patches and commit them individually (unless
3012 3012 --no-commit is specified).
3013 3013
3014 3014 If there are outstanding changes in the working directory, import
3015 3015 will abort unless given the -f/--force flag.
3016 3016
3017 3017 You can import a patch straight from a mail message. Even patches
3018 3018 as attachments work (to use the body part, it must have type
3019 3019 text/plain or text/x-patch). From and Subject headers of email
3020 3020 message are used as default committer and commit message. All
3021 3021 text/plain body parts before first diff are added to commit
3022 3022 message.
3023 3023
3024 3024 If the imported patch was generated by :hg:`export`, user and
3025 3025 description from patch override values from message headers and
3026 3026 body. Values given on command line with -m/--message and -u/--user
3027 3027 override these.
3028 3028
3029 3029 If --exact is specified, import will set the working directory to
3030 3030 the parent of each patch before applying it, and will abort if the
3031 3031 resulting changeset has a different ID than the one recorded in
3032 3032 the patch. This may happen due to character set problems or other
3033 3033 deficiencies in the text patch format.
3034 3034
3035 3035 With -s/--similarity, hg will attempt to discover renames and
3036 3036 copies in the patch in the same way as 'addremove'.
3037 3037
3038 3038 To read a patch from standard input, use "-" as the patch name. If
3039 3039 a URL is specified, the patch will be downloaded from it.
3040 3040 See :hg:`help dates` for a list of formats valid for -d/--date.
3041 3041
3042 3042 Returns 0 on success.
3043 3043 """
3044 3044 patches = (patch1,) + patches
3045 3045
3046 3046 date = opts.get('date')
3047 3047 if date:
3048 3048 opts['date'] = util.parsedate(date)
3049 3049
3050 3050 try:
3051 3051 sim = float(opts.get('similarity') or 0)
3052 3052 except ValueError:
3053 3053 raise util.Abort(_('similarity must be a number'))
3054 3054 if sim < 0 or sim > 100:
3055 3055 raise util.Abort(_('similarity must be between 0 and 100'))
3056 3056
3057 3057 if opts.get('exact') or not opts.get('force'):
3058 3058 cmdutil.bailifchanged(repo)
3059 3059
3060 3060 d = opts["base"]
3061 3061 strip = opts["strip"]
3062 3062 wlock = lock = None
3063 3063 msgs = []
3064 3064
3065 3065 def tryone(ui, hunk):
3066 3066 tmpname, message, user, date, branch, nodeid, p1, p2 = \
3067 3067 patch.extract(ui, hunk)
3068 3068
3069 3069 if not tmpname:
3070 3070 return None
3071 3071 commitid = _('to working directory')
3072 3072
3073 3073 try:
3074 3074 cmdline_message = cmdutil.logmessage(opts)
3075 3075 if cmdline_message:
3076 3076 # pickup the cmdline msg
3077 3077 message = cmdline_message
3078 3078 elif message:
3079 3079 # pickup the patch msg
3080 3080 message = message.strip()
3081 3081 else:
3082 3082 # launch the editor
3083 3083 message = None
3084 3084 ui.debug('message:\n%s\n' % message)
3085 3085
3086 3086 wp = repo.parents()
3087 3087 if opts.get('exact'):
3088 3088 if not nodeid or not p1:
3089 3089 raise util.Abort(_('not a Mercurial patch'))
3090 3090 p1 = repo.lookup(p1)
3091 3091 p2 = repo.lookup(p2 or hex(nullid))
3092 3092
3093 3093 if p1 != wp[0].node():
3094 3094 hg.clean(repo, p1)
3095 3095 repo.dirstate.setparents(p1, p2)
3096 3096 elif p2:
3097 3097 try:
3098 3098 p1 = repo.lookup(p1)
3099 3099 p2 = repo.lookup(p2)
3100 3100 if p1 == wp[0].node():
3101 3101 repo.dirstate.setparents(p1, p2)
3102 3102 except error.RepoError:
3103 3103 pass
3104 3104 if opts.get('exact') or opts.get('import_branch'):
3105 3105 repo.dirstate.setbranch(branch or 'default')
3106 3106
3107 3107 files = {}
3108 patch.patch(ui, repo, tmpname, strip=strip, cwd=repo.root,
3109 files=files, eolmode=None, similarity=sim / 100.0)
3108 patch.patch(ui, repo, tmpname, strip=strip, files=files,
3109 eolmode=None, similarity=sim / 100.0)
3110 3110 files = list(files)
3111 3111 if opts.get('no_commit'):
3112 3112 if message:
3113 3113 msgs.append(message)
3114 3114 else:
3115 3115 if opts.get('exact'):
3116 3116 m = None
3117 3117 else:
3118 3118 m = scmutil.matchfiles(repo, files or [])
3119 3119 n = repo.commit(message, opts.get('user') or user,
3120 3120 opts.get('date') or date, match=m,
3121 3121 editor=cmdutil.commiteditor)
3122 3122 if opts.get('exact'):
3123 3123 if hex(n) != nodeid:
3124 3124 repo.rollback()
3125 3125 raise util.Abort(_('patch is damaged'
3126 3126 ' or loses information'))
3127 3127 # Force a dirstate write so that the next transaction
3128 3128 # backups an up-do-date file.
3129 3129 repo.dirstate.write()
3130 3130 if n:
3131 3131 commitid = short(n)
3132 3132
3133 3133 return commitid
3134 3134 finally:
3135 3135 os.unlink(tmpname)
3136 3136
3137 3137 try:
3138 3138 wlock = repo.wlock()
3139 3139 lock = repo.lock()
3140 3140 lastcommit = None
3141 3141 for p in patches:
3142 3142 pf = os.path.join(d, p)
3143 3143
3144 3144 if pf == '-':
3145 3145 ui.status(_("applying patch from stdin\n"))
3146 3146 pf = sys.stdin
3147 3147 else:
3148 3148 ui.status(_("applying %s\n") % p)
3149 3149 pf = url.open(ui, pf)
3150 3150
3151 3151 haspatch = False
3152 3152 for hunk in patch.split(pf):
3153 3153 commitid = tryone(ui, hunk)
3154 3154 if commitid:
3155 3155 haspatch = True
3156 3156 if lastcommit:
3157 3157 ui.status(_('applied %s\n') % lastcommit)
3158 3158 lastcommit = commitid
3159 3159
3160 3160 if not haspatch:
3161 3161 raise util.Abort(_('no diffs found'))
3162 3162
3163 3163 if msgs:
3164 3164 repo.opener.write('last-message.txt', '\n* * *\n'.join(msgs))
3165 3165 finally:
3166 3166 release(lock, wlock)
3167 3167
3168 3168 @command('incoming|in',
3169 3169 [('f', 'force', None,
3170 3170 _('run even if remote repository is unrelated')),
3171 3171 ('n', 'newest-first', None, _('show newest record first')),
3172 3172 ('', 'bundle', '',
3173 3173 _('file to store the bundles into'), _('FILE')),
3174 3174 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3175 3175 ('B', 'bookmarks', False, _("compare bookmarks")),
3176 3176 ('b', 'branch', [],
3177 3177 _('a specific branch you would like to pull'), _('BRANCH')),
3178 3178 ] + logopts + remoteopts + subrepoopts,
3179 3179 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3180 3180 def incoming(ui, repo, source="default", **opts):
3181 3181 """show new changesets found in source
3182 3182
3183 3183 Show new changesets found in the specified path/URL or the default
3184 3184 pull location. These are the changesets that would have been pulled
3185 3185 if a pull at the time you issued this command.
3186 3186
3187 3187 For remote repository, using --bundle avoids downloading the
3188 3188 changesets twice if the incoming is followed by a pull.
3189 3189
3190 3190 See pull for valid source format details.
3191 3191
3192 3192 Returns 0 if there are incoming changes, 1 otherwise.
3193 3193 """
3194 3194 if opts.get('bundle') and opts.get('subrepos'):
3195 3195 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3196 3196
3197 3197 if opts.get('bookmarks'):
3198 3198 source, branches = hg.parseurl(ui.expandpath(source),
3199 3199 opts.get('branch'))
3200 3200 other = hg.repository(hg.remoteui(repo, opts), source)
3201 3201 if 'bookmarks' not in other.listkeys('namespaces'):
3202 3202 ui.warn(_("remote doesn't support bookmarks\n"))
3203 3203 return 0
3204 3204 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3205 3205 return bookmarks.diff(ui, repo, other)
3206 3206
3207 3207 repo._subtoppath = ui.expandpath(source)
3208 3208 try:
3209 3209 return hg.incoming(ui, repo, source, opts)
3210 3210 finally:
3211 3211 del repo._subtoppath
3212 3212
3213 3213
3214 3214 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
3215 3215 def init(ui, dest=".", **opts):
3216 3216 """create a new repository in the given directory
3217 3217
3218 3218 Initialize a new repository in the given directory. If the given
3219 3219 directory does not exist, it will be created.
3220 3220
3221 3221 If no directory is given, the current directory is used.
3222 3222
3223 3223 It is possible to specify an ``ssh://`` URL as the destination.
3224 3224 See :hg:`help urls` for more information.
3225 3225
3226 3226 Returns 0 on success.
3227 3227 """
3228 3228 hg.repository(hg.remoteui(ui, opts), ui.expandpath(dest), create=True)
3229 3229
3230 3230 @command('locate',
3231 3231 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3232 3232 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3233 3233 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3234 3234 ] + walkopts,
3235 3235 _('[OPTION]... [PATTERN]...'))
3236 3236 def locate(ui, repo, *pats, **opts):
3237 3237 """locate files matching specific patterns
3238 3238
3239 3239 Print files under Mercurial control in the working directory whose
3240 3240 names match the given patterns.
3241 3241
3242 3242 By default, this command searches all directories in the working
3243 3243 directory. To search just the current directory and its
3244 3244 subdirectories, use "--include .".
3245 3245
3246 3246 If no patterns are given to match, this command prints the names
3247 3247 of all files under Mercurial control in the working directory.
3248 3248
3249 3249 If you want to feed the output of this command into the "xargs"
3250 3250 command, use the -0 option to both this command and "xargs". This
3251 3251 will avoid the problem of "xargs" treating single filenames that
3252 3252 contain whitespace as multiple filenames.
3253 3253
3254 3254 Returns 0 if a match is found, 1 otherwise.
3255 3255 """
3256 3256 end = opts.get('print0') and '\0' or '\n'
3257 3257 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3258 3258
3259 3259 ret = 1
3260 3260 m = scmutil.match(repo, pats, opts, default='relglob')
3261 3261 m.bad = lambda x, y: False
3262 3262 for abs in repo[rev].walk(m):
3263 3263 if not rev and abs not in repo.dirstate:
3264 3264 continue
3265 3265 if opts.get('fullpath'):
3266 3266 ui.write(repo.wjoin(abs), end)
3267 3267 else:
3268 3268 ui.write(((pats and m.rel(abs)) or abs), end)
3269 3269 ret = 0
3270 3270
3271 3271 return ret
3272 3272
3273 3273 @command('^log|history',
3274 3274 [('f', 'follow', None,
3275 3275 _('follow changeset history, or file history across copies and renames')),
3276 3276 ('', 'follow-first', None,
3277 3277 _('only follow the first parent of merge changesets')),
3278 3278 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3279 3279 ('C', 'copies', None, _('show copied files')),
3280 3280 ('k', 'keyword', [],
3281 3281 _('do case-insensitive search for a given text'), _('TEXT')),
3282 3282 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
3283 3283 ('', 'removed', None, _('include revisions where files were removed')),
3284 3284 ('m', 'only-merges', None, _('show only merges')),
3285 3285 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3286 3286 ('', 'only-branch', [],
3287 3287 _('show only changesets within the given named branch (DEPRECATED)'),
3288 3288 _('BRANCH')),
3289 3289 ('b', 'branch', [],
3290 3290 _('show changesets within the given named branch'), _('BRANCH')),
3291 3291 ('P', 'prune', [],
3292 3292 _('do not display revision or any of its ancestors'), _('REV')),
3293 3293 ] + logopts + walkopts,
3294 3294 _('[OPTION]... [FILE]'))
3295 3295 def log(ui, repo, *pats, **opts):
3296 3296 """show revision history of entire repository or files
3297 3297
3298 3298 Print the revision history of the specified files or the entire
3299 3299 project.
3300 3300
3301 3301 File history is shown without following rename or copy history of
3302 3302 files. Use -f/--follow with a filename to follow history across
3303 3303 renames and copies. --follow without a filename will only show
3304 3304 ancestors or descendants of the starting revision. --follow-first
3305 3305 only follows the first parent of merge revisions.
3306 3306
3307 3307 If no revision range is specified, the default is ``tip:0`` unless
3308 3308 --follow is set, in which case the working directory parent is
3309 3309 used as the starting revision. You can specify a revision set for
3310 3310 log, see :hg:`help revsets` for more information.
3311 3311
3312 3312 See :hg:`help dates` for a list of formats valid for -d/--date.
3313 3313
3314 3314 By default this command prints revision number and changeset id,
3315 3315 tags, non-trivial parents, user, date and time, and a summary for
3316 3316 each commit. When the -v/--verbose switch is used, the list of
3317 3317 changed files and full commit message are shown.
3318 3318
3319 3319 .. note::
3320 3320 log -p/--patch may generate unexpected diff output for merge
3321 3321 changesets, as it will only compare the merge changeset against
3322 3322 its first parent. Also, only files different from BOTH parents
3323 3323 will appear in files:.
3324 3324
3325 3325 Returns 0 on success.
3326 3326 """
3327 3327
3328 3328 matchfn = scmutil.match(repo, pats, opts)
3329 3329 limit = cmdutil.loglimit(opts)
3330 3330 count = 0
3331 3331
3332 3332 endrev = None
3333 3333 if opts.get('copies') and opts.get('rev'):
3334 3334 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
3335 3335
3336 3336 df = False
3337 3337 if opts["date"]:
3338 3338 df = util.matchdate(opts["date"])
3339 3339
3340 3340 branches = opts.get('branch', []) + opts.get('only_branch', [])
3341 3341 opts['branch'] = [repo.lookupbranch(b) for b in branches]
3342 3342
3343 3343 displayer = cmdutil.show_changeset(ui, repo, opts, True)
3344 3344 def prep(ctx, fns):
3345 3345 rev = ctx.rev()
3346 3346 parents = [p for p in repo.changelog.parentrevs(rev)
3347 3347 if p != nullrev]
3348 3348 if opts.get('no_merges') and len(parents) == 2:
3349 3349 return
3350 3350 if opts.get('only_merges') and len(parents) != 2:
3351 3351 return
3352 3352 if opts.get('branch') and ctx.branch() not in opts['branch']:
3353 3353 return
3354 3354 if df and not df(ctx.date()[0]):
3355 3355 return
3356 3356 if opts['user'] and not [k for k in opts['user']
3357 3357 if k.lower() in ctx.user().lower()]:
3358 3358 return
3359 3359 if opts.get('keyword'):
3360 3360 for k in [kw.lower() for kw in opts['keyword']]:
3361 3361 if (k in ctx.user().lower() or
3362 3362 k in ctx.description().lower() or
3363 3363 k in " ".join(ctx.files()).lower()):
3364 3364 break
3365 3365 else:
3366 3366 return
3367 3367
3368 3368 copies = None
3369 3369 if opts.get('copies') and rev:
3370 3370 copies = []
3371 3371 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3372 3372 for fn in ctx.files():
3373 3373 rename = getrenamed(fn, rev)
3374 3374 if rename:
3375 3375 copies.append((fn, rename[0]))
3376 3376
3377 3377 revmatchfn = None
3378 3378 if opts.get('patch') or opts.get('stat'):
3379 3379 if opts.get('follow') or opts.get('follow_first'):
3380 3380 # note: this might be wrong when following through merges
3381 3381 revmatchfn = scmutil.match(repo, fns, default='path')
3382 3382 else:
3383 3383 revmatchfn = matchfn
3384 3384
3385 3385 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
3386 3386
3387 3387 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3388 3388 if count == limit:
3389 3389 break
3390 3390 if displayer.flush(ctx.rev()):
3391 3391 count += 1
3392 3392 displayer.close()
3393 3393
3394 3394 @command('manifest',
3395 3395 [('r', 'rev', '', _('revision to display'), _('REV'))],
3396 3396 _('[-r REV]'))
3397 3397 def manifest(ui, repo, node=None, rev=None):
3398 3398 """output the current or given revision of the project manifest
3399 3399
3400 3400 Print a list of version controlled files for the given revision.
3401 3401 If no revision is given, the first parent of the working directory
3402 3402 is used, or the null revision if no revision is checked out.
3403 3403
3404 3404 With -v, print file permissions, symlink and executable bits.
3405 3405 With --debug, print file revision hashes.
3406 3406
3407 3407 Returns 0 on success.
3408 3408 """
3409 3409
3410 3410 if rev and node:
3411 3411 raise util.Abort(_("please specify just one revision"))
3412 3412
3413 3413 if not node:
3414 3414 node = rev
3415 3415
3416 3416 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
3417 3417 ctx = scmutil.revsingle(repo, node)
3418 3418 for f in ctx:
3419 3419 if ui.debugflag:
3420 3420 ui.write("%40s " % hex(ctx.manifest()[f]))
3421 3421 if ui.verbose:
3422 3422 ui.write(decor[ctx.flags(f)])
3423 3423 ui.write("%s\n" % f)
3424 3424
3425 3425 @command('^merge',
3426 3426 [('f', 'force', None, _('force a merge with outstanding changes')),
3427 3427 ('t', 'tool', '', _('specify merge tool')),
3428 3428 ('r', 'rev', '', _('revision to merge'), _('REV')),
3429 3429 ('P', 'preview', None,
3430 3430 _('review revisions to merge (no merge is performed)'))],
3431 3431 _('[-P] [-f] [[-r] REV]'))
3432 3432 def merge(ui, repo, node=None, **opts):
3433 3433 """merge working directory with another revision
3434 3434
3435 3435 The current working directory is updated with all changes made in
3436 3436 the requested revision since the last common predecessor revision.
3437 3437
3438 3438 Files that changed between either parent are marked as changed for
3439 3439 the next commit and a commit must be performed before any further
3440 3440 updates to the repository are allowed. The next commit will have
3441 3441 two parents.
3442 3442
3443 3443 ``--tool`` can be used to specify the merge tool used for file
3444 3444 merges. It overrides the HGMERGE environment variable and your
3445 3445 configuration files. See :hg:`help merge-tools` for options.
3446 3446
3447 3447 If no revision is specified, the working directory's parent is a
3448 3448 head revision, and the current branch contains exactly one other
3449 3449 head, the other head is merged with by default. Otherwise, an
3450 3450 explicit revision with which to merge with must be provided.
3451 3451
3452 3452 :hg:`resolve` must be used to resolve unresolved files.
3453 3453
3454 3454 To undo an uncommitted merge, use :hg:`update --clean .` which
3455 3455 will check out a clean copy of the original merge parent, losing
3456 3456 all changes.
3457 3457
3458 3458 Returns 0 on success, 1 if there are unresolved files.
3459 3459 """
3460 3460
3461 3461 if opts.get('rev') and node:
3462 3462 raise util.Abort(_("please specify just one revision"))
3463 3463 if not node:
3464 3464 node = opts.get('rev')
3465 3465
3466 3466 if not node:
3467 3467 branch = repo[None].branch()
3468 3468 bheads = repo.branchheads(branch)
3469 3469 if len(bheads) > 2:
3470 3470 raise util.Abort(_("branch '%s' has %d heads - "
3471 3471 "please merge with an explicit rev")
3472 3472 % (branch, len(bheads)),
3473 3473 hint=_("run 'hg heads .' to see heads"))
3474 3474
3475 3475 parent = repo.dirstate.p1()
3476 3476 if len(bheads) == 1:
3477 3477 if len(repo.heads()) > 1:
3478 3478 raise util.Abort(_("branch '%s' has one head - "
3479 3479 "please merge with an explicit rev")
3480 3480 % branch,
3481 3481 hint=_("run 'hg heads' to see all heads"))
3482 3482 msg = _('there is nothing to merge')
3483 3483 if parent != repo.lookup(repo[None].branch()):
3484 3484 msg = _('%s - use "hg update" instead') % msg
3485 3485 raise util.Abort(msg)
3486 3486
3487 3487 if parent not in bheads:
3488 3488 raise util.Abort(_('working directory not at a head revision'),
3489 3489 hint=_("use 'hg update' or merge with an "
3490 3490 "explicit revision"))
3491 3491 node = parent == bheads[0] and bheads[-1] or bheads[0]
3492 3492 else:
3493 3493 node = scmutil.revsingle(repo, node).node()
3494 3494
3495 3495 if opts.get('preview'):
3496 3496 # find nodes that are ancestors of p2 but not of p1
3497 3497 p1 = repo.lookup('.')
3498 3498 p2 = repo.lookup(node)
3499 3499 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3500 3500
3501 3501 displayer = cmdutil.show_changeset(ui, repo, opts)
3502 3502 for node in nodes:
3503 3503 displayer.show(repo[node])
3504 3504 displayer.close()
3505 3505 return 0
3506 3506
3507 3507 try:
3508 3508 # ui.forcemerge is an internal variable, do not document
3509 3509 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
3510 3510 return hg.merge(repo, node, force=opts.get('force'))
3511 3511 finally:
3512 3512 ui.setconfig('ui', 'forcemerge', '')
3513 3513
3514 3514 @command('outgoing|out',
3515 3515 [('f', 'force', None, _('run even when the destination is unrelated')),
3516 3516 ('r', 'rev', [],
3517 3517 _('a changeset intended to be included in the destination'), _('REV')),
3518 3518 ('n', 'newest-first', None, _('show newest record first')),
3519 3519 ('B', 'bookmarks', False, _('compare bookmarks')),
3520 3520 ('b', 'branch', [], _('a specific branch you would like to push'),
3521 3521 _('BRANCH')),
3522 3522 ] + logopts + remoteopts + subrepoopts,
3523 3523 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3524 3524 def outgoing(ui, repo, dest=None, **opts):
3525 3525 """show changesets not found in the destination
3526 3526
3527 3527 Show changesets not found in the specified destination repository
3528 3528 or the default push location. These are the changesets that would
3529 3529 be pushed if a push was requested.
3530 3530
3531 3531 See pull for details of valid destination formats.
3532 3532
3533 3533 Returns 0 if there are outgoing changes, 1 otherwise.
3534 3534 """
3535 3535
3536 3536 if opts.get('bookmarks'):
3537 3537 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3538 3538 dest, branches = hg.parseurl(dest, opts.get('branch'))
3539 3539 other = hg.repository(hg.remoteui(repo, opts), dest)
3540 3540 if 'bookmarks' not in other.listkeys('namespaces'):
3541 3541 ui.warn(_("remote doesn't support bookmarks\n"))
3542 3542 return 0
3543 3543 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3544 3544 return bookmarks.diff(ui, other, repo)
3545 3545
3546 3546 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3547 3547 try:
3548 3548 return hg.outgoing(ui, repo, dest, opts)
3549 3549 finally:
3550 3550 del repo._subtoppath
3551 3551
3552 3552 @command('parents',
3553 3553 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3554 3554 ] + templateopts,
3555 3555 _('[-r REV] [FILE]'))
3556 3556 def parents(ui, repo, file_=None, **opts):
3557 3557 """show the parents of the working directory or revision
3558 3558
3559 3559 Print the working directory's parent revisions. If a revision is
3560 3560 given via -r/--rev, the parent of that revision will be printed.
3561 3561 If a file argument is given, the revision in which the file was
3562 3562 last changed (before the working directory revision or the
3563 3563 argument to --rev if given) is printed.
3564 3564
3565 3565 Returns 0 on success.
3566 3566 """
3567 3567
3568 3568 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3569 3569
3570 3570 if file_:
3571 3571 m = scmutil.match(repo, (file_,), opts)
3572 3572 if m.anypats() or len(m.files()) != 1:
3573 3573 raise util.Abort(_('can only specify an explicit filename'))
3574 3574 file_ = m.files()[0]
3575 3575 filenodes = []
3576 3576 for cp in ctx.parents():
3577 3577 if not cp:
3578 3578 continue
3579 3579 try:
3580 3580 filenodes.append(cp.filenode(file_))
3581 3581 except error.LookupError:
3582 3582 pass
3583 3583 if not filenodes:
3584 3584 raise util.Abort(_("'%s' not found in manifest!") % file_)
3585 3585 fl = repo.file(file_)
3586 3586 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
3587 3587 else:
3588 3588 p = [cp.node() for cp in ctx.parents()]
3589 3589
3590 3590 displayer = cmdutil.show_changeset(ui, repo, opts)
3591 3591 for n in p:
3592 3592 if n != nullid:
3593 3593 displayer.show(repo[n])
3594 3594 displayer.close()
3595 3595
3596 3596 @command('paths', [], _('[NAME]'))
3597 3597 def paths(ui, repo, search=None):
3598 3598 """show aliases for remote repositories
3599 3599
3600 3600 Show definition of symbolic path name NAME. If no name is given,
3601 3601 show definition of all available names.
3602 3602
3603 3603 Option -q/--quiet suppresses all output when searching for NAME
3604 3604 and shows only the path names when listing all definitions.
3605 3605
3606 3606 Path names are defined in the [paths] section of your
3607 3607 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3608 3608 repository, ``.hg/hgrc`` is used, too.
3609 3609
3610 3610 The path names ``default`` and ``default-push`` have a special
3611 3611 meaning. When performing a push or pull operation, they are used
3612 3612 as fallbacks if no location is specified on the command-line.
3613 3613 When ``default-push`` is set, it will be used for push and
3614 3614 ``default`` will be used for pull; otherwise ``default`` is used
3615 3615 as the fallback for both. When cloning a repository, the clone
3616 3616 source is written as ``default`` in ``.hg/hgrc``. Note that
3617 3617 ``default`` and ``default-push`` apply to all inbound (e.g.
3618 3618 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
3619 3619 :hg:`bundle`) operations.
3620 3620
3621 3621 See :hg:`help urls` for more information.
3622 3622
3623 3623 Returns 0 on success.
3624 3624 """
3625 3625 if search:
3626 3626 for name, path in ui.configitems("paths"):
3627 3627 if name == search:
3628 3628 ui.status("%s\n" % util.hidepassword(path))
3629 3629 return
3630 3630 if not ui.quiet:
3631 3631 ui.warn(_("not found!\n"))
3632 3632 return 1
3633 3633 else:
3634 3634 for name, path in ui.configitems("paths"):
3635 3635 if ui.quiet:
3636 3636 ui.write("%s\n" % name)
3637 3637 else:
3638 3638 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
3639 3639
3640 3640 def postincoming(ui, repo, modheads, optupdate, checkout):
3641 3641 if modheads == 0:
3642 3642 return
3643 3643 if optupdate:
3644 3644 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
3645 3645 return hg.update(repo, checkout)
3646 3646 else:
3647 3647 ui.status(_("not updating, since new heads added\n"))
3648 3648 if modheads > 1:
3649 3649 currentbranchheads = len(repo.branchheads())
3650 3650 if currentbranchheads == modheads:
3651 3651 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3652 3652 elif currentbranchheads > 1:
3653 3653 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to merge)\n"))
3654 3654 else:
3655 3655 ui.status(_("(run 'hg heads' to see heads)\n"))
3656 3656 else:
3657 3657 ui.status(_("(run 'hg update' to get a working copy)\n"))
3658 3658
3659 3659 @command('^pull',
3660 3660 [('u', 'update', None,
3661 3661 _('update to new branch head if changesets were pulled')),
3662 3662 ('f', 'force', None, _('run even when remote repository is unrelated')),
3663 3663 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3664 3664 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3665 3665 ('b', 'branch', [], _('a specific branch you would like to pull'),
3666 3666 _('BRANCH')),
3667 3667 ] + remoteopts,
3668 3668 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3669 3669 def pull(ui, repo, source="default", **opts):
3670 3670 """pull changes from the specified source
3671 3671
3672 3672 Pull changes from a remote repository to a local one.
3673 3673
3674 3674 This finds all changes from the repository at the specified path
3675 3675 or URL and adds them to a local repository (the current one unless
3676 3676 -R is specified). By default, this does not update the copy of the
3677 3677 project in the working directory.
3678 3678
3679 3679 Use :hg:`incoming` if you want to see what would have been added
3680 3680 by a pull at the time you issued this command. If you then decide
3681 3681 to add those changes to the repository, you should use :hg:`pull
3682 3682 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3683 3683
3684 3684 If SOURCE is omitted, the 'default' path will be used.
3685 3685 See :hg:`help urls` for more information.
3686 3686
3687 3687 Returns 0 on success, 1 if an update had unresolved files.
3688 3688 """
3689 3689 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3690 3690 other = hg.repository(hg.remoteui(repo, opts), source)
3691 3691 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3692 3692 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3693 3693
3694 3694 if opts.get('bookmark'):
3695 3695 if not revs:
3696 3696 revs = []
3697 3697 rb = other.listkeys('bookmarks')
3698 3698 for b in opts['bookmark']:
3699 3699 if b not in rb:
3700 3700 raise util.Abort(_('remote bookmark %s not found!') % b)
3701 3701 revs.append(rb[b])
3702 3702
3703 3703 if revs:
3704 3704 try:
3705 3705 revs = [other.lookup(rev) for rev in revs]
3706 3706 except error.CapabilityError:
3707 3707 err = _("other repository doesn't support revision lookup, "
3708 3708 "so a rev cannot be specified.")
3709 3709 raise util.Abort(err)
3710 3710
3711 3711 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
3712 3712 bookmarks.updatefromremote(ui, repo, other)
3713 3713 if checkout:
3714 3714 checkout = str(repo.changelog.rev(other.lookup(checkout)))
3715 3715 repo._subtoppath = source
3716 3716 try:
3717 3717 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
3718 3718
3719 3719 finally:
3720 3720 del repo._subtoppath
3721 3721
3722 3722 # update specified bookmarks
3723 3723 if opts.get('bookmark'):
3724 3724 for b in opts['bookmark']:
3725 3725 # explicit pull overrides local bookmark if any
3726 3726 ui.status(_("importing bookmark %s\n") % b)
3727 3727 repo._bookmarks[b] = repo[rb[b]].node()
3728 3728 bookmarks.write(repo)
3729 3729
3730 3730 return ret
3731 3731
3732 3732 @command('^push',
3733 3733 [('f', 'force', None, _('force push')),
3734 3734 ('r', 'rev', [],
3735 3735 _('a changeset intended to be included in the destination'),
3736 3736 _('REV')),
3737 3737 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
3738 3738 ('b', 'branch', [],
3739 3739 _('a specific branch you would like to push'), _('BRANCH')),
3740 3740 ('', 'new-branch', False, _('allow pushing a new branch')),
3741 3741 ] + remoteopts,
3742 3742 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
3743 3743 def push(ui, repo, dest=None, **opts):
3744 3744 """push changes to the specified destination
3745 3745
3746 3746 Push changesets from the local repository to the specified
3747 3747 destination.
3748 3748
3749 3749 This operation is symmetrical to pull: it is identical to a pull
3750 3750 in the destination repository from the current one.
3751 3751
3752 3752 By default, push will not allow creation of new heads at the
3753 3753 destination, since multiple heads would make it unclear which head
3754 3754 to use. In this situation, it is recommended to pull and merge
3755 3755 before pushing.
3756 3756
3757 3757 Use --new-branch if you want to allow push to create a new named
3758 3758 branch that is not present at the destination. This allows you to
3759 3759 only create a new branch without forcing other changes.
3760 3760
3761 3761 Use -f/--force to override the default behavior and push all
3762 3762 changesets on all branches.
3763 3763
3764 3764 If -r/--rev is used, the specified revision and all its ancestors
3765 3765 will be pushed to the remote repository.
3766 3766
3767 3767 Please see :hg:`help urls` for important details about ``ssh://``
3768 3768 URLs. If DESTINATION is omitted, a default path will be used.
3769 3769
3770 3770 Returns 0 if push was successful, 1 if nothing to push.
3771 3771 """
3772 3772
3773 3773 if opts.get('bookmark'):
3774 3774 for b in opts['bookmark']:
3775 3775 # translate -B options to -r so changesets get pushed
3776 3776 if b in repo._bookmarks:
3777 3777 opts.setdefault('rev', []).append(b)
3778 3778 else:
3779 3779 # if we try to push a deleted bookmark, translate it to null
3780 3780 # this lets simultaneous -r, -b options continue working
3781 3781 opts.setdefault('rev', []).append("null")
3782 3782
3783 3783 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3784 3784 dest, branches = hg.parseurl(dest, opts.get('branch'))
3785 3785 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
3786 3786 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
3787 3787 other = hg.repository(hg.remoteui(repo, opts), dest)
3788 3788 if revs:
3789 3789 revs = [repo.lookup(rev) for rev in revs]
3790 3790
3791 3791 repo._subtoppath = dest
3792 3792 try:
3793 3793 # push subrepos depth-first for coherent ordering
3794 3794 c = repo['']
3795 3795 subs = c.substate # only repos that are committed
3796 3796 for s in sorted(subs):
3797 3797 if not c.sub(s).push(opts.get('force')):
3798 3798 return False
3799 3799 finally:
3800 3800 del repo._subtoppath
3801 3801 result = repo.push(other, opts.get('force'), revs=revs,
3802 3802 newbranch=opts.get('new_branch'))
3803 3803
3804 3804 result = (result == 0)
3805 3805
3806 3806 if opts.get('bookmark'):
3807 3807 rb = other.listkeys('bookmarks')
3808 3808 for b in opts['bookmark']:
3809 3809 # explicit push overrides remote bookmark if any
3810 3810 if b in repo._bookmarks:
3811 3811 ui.status(_("exporting bookmark %s\n") % b)
3812 3812 new = repo[b].hex()
3813 3813 elif b in rb:
3814 3814 ui.status(_("deleting remote bookmark %s\n") % b)
3815 3815 new = '' # delete
3816 3816 else:
3817 3817 ui.warn(_('bookmark %s does not exist on the local '
3818 3818 'or remote repository!\n') % b)
3819 3819 return 2
3820 3820 old = rb.get(b, '')
3821 3821 r = other.pushkey('bookmarks', b, old, new)
3822 3822 if not r:
3823 3823 ui.warn(_('updating bookmark %s failed!\n') % b)
3824 3824 if not result:
3825 3825 result = 2
3826 3826
3827 3827 return result
3828 3828
3829 3829 @command('recover', [])
3830 3830 def recover(ui, repo):
3831 3831 """roll back an interrupted transaction
3832 3832
3833 3833 Recover from an interrupted commit or pull.
3834 3834
3835 3835 This command tries to fix the repository status after an
3836 3836 interrupted operation. It should only be necessary when Mercurial
3837 3837 suggests it.
3838 3838
3839 3839 Returns 0 if successful, 1 if nothing to recover or verify fails.
3840 3840 """
3841 3841 if repo.recover():
3842 3842 return hg.verify(repo)
3843 3843 return 1
3844 3844
3845 3845 @command('^remove|rm',
3846 3846 [('A', 'after', None, _('record delete for missing files')),
3847 3847 ('f', 'force', None,
3848 3848 _('remove (and delete) file even if added or modified')),
3849 3849 ] + walkopts,
3850 3850 _('[OPTION]... FILE...'))
3851 3851 def remove(ui, repo, *pats, **opts):
3852 3852 """remove the specified files on the next commit
3853 3853
3854 3854 Schedule the indicated files for removal from the repository.
3855 3855
3856 3856 This only removes files from the current branch, not from the
3857 3857 entire project history. -A/--after can be used to remove only
3858 3858 files that have already been deleted, -f/--force can be used to
3859 3859 force deletion, and -Af can be used to remove files from the next
3860 3860 revision without deleting them from the working directory.
3861 3861
3862 3862 The following table details the behavior of remove for different
3863 3863 file states (columns) and option combinations (rows). The file
3864 3864 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
3865 3865 reported by :hg:`status`). The actions are Warn, Remove (from
3866 3866 branch) and Delete (from disk)::
3867 3867
3868 3868 A C M !
3869 3869 none W RD W R
3870 3870 -f R RD RD R
3871 3871 -A W W W R
3872 3872 -Af R R R R
3873 3873
3874 3874 This command schedules the files to be removed at the next commit.
3875 3875 To undo a remove before that, see :hg:`revert`.
3876 3876
3877 3877 Returns 0 on success, 1 if any warnings encountered.
3878 3878 """
3879 3879
3880 3880 ret = 0
3881 3881 after, force = opts.get('after'), opts.get('force')
3882 3882 if not pats and not after:
3883 3883 raise util.Abort(_('no files specified'))
3884 3884
3885 3885 m = scmutil.match(repo, pats, opts)
3886 3886 s = repo.status(match=m, clean=True)
3887 3887 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
3888 3888
3889 3889 for f in m.files():
3890 3890 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
3891 3891 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
3892 3892 ret = 1
3893 3893
3894 3894 if force:
3895 3895 remove, forget = modified + deleted + clean, added
3896 3896 elif after:
3897 3897 remove, forget = deleted, []
3898 3898 for f in modified + added + clean:
3899 3899 ui.warn(_('not removing %s: file still exists (use -f'
3900 3900 ' to force removal)\n') % m.rel(f))
3901 3901 ret = 1
3902 3902 else:
3903 3903 remove, forget = deleted + clean, []
3904 3904 for f in modified:
3905 3905 ui.warn(_('not removing %s: file is modified (use -f'
3906 3906 ' to force removal)\n') % m.rel(f))
3907 3907 ret = 1
3908 3908 for f in added:
3909 3909 ui.warn(_('not removing %s: file has been marked for add (use -f'
3910 3910 ' to force removal)\n') % m.rel(f))
3911 3911 ret = 1
3912 3912
3913 3913 for f in sorted(remove + forget):
3914 3914 if ui.verbose or not m.exact(f):
3915 3915 ui.status(_('removing %s\n') % m.rel(f))
3916 3916
3917 3917 repo[None].forget(forget)
3918 3918 repo[None].remove(remove, unlink=not after)
3919 3919 return ret
3920 3920
3921 3921 @command('rename|move|mv',
3922 3922 [('A', 'after', None, _('record a rename that has already occurred')),
3923 3923 ('f', 'force', None, _('forcibly copy over an existing managed file')),
3924 3924 ] + walkopts + dryrunopts,
3925 3925 _('[OPTION]... SOURCE... DEST'))
3926 3926 def rename(ui, repo, *pats, **opts):
3927 3927 """rename files; equivalent of copy + remove
3928 3928
3929 3929 Mark dest as copies of sources; mark sources for deletion. If dest
3930 3930 is a directory, copies are put in that directory. If dest is a
3931 3931 file, there can only be one source.
3932 3932
3933 3933 By default, this command copies the contents of files as they
3934 3934 exist in the working directory. If invoked with -A/--after, the
3935 3935 operation is recorded, but no copying is performed.
3936 3936
3937 3937 This command takes effect at the next commit. To undo a rename
3938 3938 before that, see :hg:`revert`.
3939 3939
3940 3940 Returns 0 on success, 1 if errors are encountered.
3941 3941 """
3942 3942 wlock = repo.wlock(False)
3943 3943 try:
3944 3944 return cmdutil.copy(ui, repo, pats, opts, rename=True)
3945 3945 finally:
3946 3946 wlock.release()
3947 3947
3948 3948 @command('resolve',
3949 3949 [('a', 'all', None, _('select all unresolved files')),
3950 3950 ('l', 'list', None, _('list state of files needing merge')),
3951 3951 ('m', 'mark', None, _('mark files as resolved')),
3952 3952 ('u', 'unmark', None, _('mark files as unresolved')),
3953 3953 ('t', 'tool', '', _('specify merge tool')),
3954 3954 ('n', 'no-status', None, _('hide status prefix'))]
3955 3955 + walkopts,
3956 3956 _('[OPTION]... [FILE]...'))
3957 3957 def resolve(ui, repo, *pats, **opts):
3958 3958 """redo merges or set/view the merge status of files
3959 3959
3960 3960 Merges with unresolved conflicts are often the result of
3961 3961 non-interactive merging using the ``internal:merge`` configuration
3962 3962 setting, or a command-line merge tool like ``diff3``. The resolve
3963 3963 command is used to manage the files involved in a merge, after
3964 3964 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
3965 3965 working directory must have two parents).
3966 3966
3967 3967 The resolve command can be used in the following ways:
3968 3968
3969 3969 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
3970 3970 files, discarding any previous merge attempts. Re-merging is not
3971 3971 performed for files already marked as resolved. Use ``--all/-a``
3972 3972 to selects all unresolved files. ``--tool`` can be used to specify
3973 3973 the merge tool used for the given files. It overrides the HGMERGE
3974 3974 environment variable and your configuration files.
3975 3975
3976 3976 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
3977 3977 (e.g. after having manually fixed-up the files). The default is
3978 3978 to mark all unresolved files.
3979 3979
3980 3980 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
3981 3981 default is to mark all resolved files.
3982 3982
3983 3983 - :hg:`resolve -l`: list files which had or still have conflicts.
3984 3984 In the printed list, ``U`` = unresolved and ``R`` = resolved.
3985 3985
3986 3986 Note that Mercurial will not let you commit files with unresolved
3987 3987 merge conflicts. You must use :hg:`resolve -m ...` before you can
3988 3988 commit after a conflicting merge.
3989 3989
3990 3990 Returns 0 on success, 1 if any files fail a resolve attempt.
3991 3991 """
3992 3992
3993 3993 all, mark, unmark, show, nostatus = \
3994 3994 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
3995 3995
3996 3996 if (show and (mark or unmark)) or (mark and unmark):
3997 3997 raise util.Abort(_("too many options specified"))
3998 3998 if pats and all:
3999 3999 raise util.Abort(_("can't specify --all and patterns"))
4000 4000 if not (all or pats or show or mark or unmark):
4001 4001 raise util.Abort(_('no files or directories specified; '
4002 4002 'use --all to remerge all files'))
4003 4003
4004 4004 ms = mergemod.mergestate(repo)
4005 4005 m = scmutil.match(repo, pats, opts)
4006 4006 ret = 0
4007 4007
4008 4008 for f in ms:
4009 4009 if m(f):
4010 4010 if show:
4011 4011 if nostatus:
4012 4012 ui.write("%s\n" % f)
4013 4013 else:
4014 4014 ui.write("%s %s\n" % (ms[f].upper(), f),
4015 4015 label='resolve.' +
4016 4016 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
4017 4017 elif mark:
4018 4018 ms.mark(f, "r")
4019 4019 elif unmark:
4020 4020 ms.mark(f, "u")
4021 4021 else:
4022 4022 wctx = repo[None]
4023 4023 mctx = wctx.parents()[-1]
4024 4024
4025 4025 # backup pre-resolve (merge uses .orig for its own purposes)
4026 4026 a = repo.wjoin(f)
4027 4027 util.copyfile(a, a + ".resolve")
4028 4028
4029 4029 try:
4030 4030 # resolve file
4031 4031 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4032 4032 if ms.resolve(f, wctx, mctx):
4033 4033 ret = 1
4034 4034 finally:
4035 4035 ui.setconfig('ui', 'forcemerge', '')
4036 4036
4037 4037 # replace filemerge's .orig file with our resolve file
4038 4038 util.rename(a + ".resolve", a + ".orig")
4039 4039
4040 4040 ms.commit()
4041 4041 return ret
4042 4042
4043 4043 @command('revert',
4044 4044 [('a', 'all', None, _('revert all changes when no arguments given')),
4045 4045 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4046 4046 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4047 4047 ('', 'no-backup', None, _('do not save backup copies of files')),
4048 4048 ] + walkopts + dryrunopts,
4049 4049 _('[OPTION]... [-r REV] [NAME]...'))
4050 4050 def revert(ui, repo, *pats, **opts):
4051 4051 """restore individual files or directories to an earlier state
4052 4052
4053 4053 .. note::
4054 4054 This command is most likely not what you are looking for.
4055 4055 Revert will partially overwrite content in the working
4056 4056 directory without changing the working directory parents. Use
4057 4057 :hg:`update -r rev` to check out earlier revisions, or
4058 4058 :hg:`update --clean .` to undo a merge which has added another
4059 4059 parent.
4060 4060
4061 4061 With no revision specified, revert the named files or directories
4062 4062 to the contents they had in the parent of the working directory.
4063 4063 This restores the contents of the affected files to an unmodified
4064 4064 state and unschedules adds, removes, copies, and renames. If the
4065 4065 working directory has two parents, you must explicitly specify a
4066 4066 revision.
4067 4067
4068 4068 Using the -r/--rev option, revert the given files or directories
4069 4069 to their contents as of a specific revision. This can be helpful
4070 4070 to "roll back" some or all of an earlier change. See :hg:`help
4071 4071 dates` for a list of formats valid for -d/--date.
4072 4072
4073 4073 Revert modifies the working directory. It does not commit any
4074 4074 changes, or change the parent of the working directory. If you
4075 4075 revert to a revision other than the parent of the working
4076 4076 directory, the reverted files will thus appear modified
4077 4077 afterwards.
4078 4078
4079 4079 If a file has been deleted, it is restored. Files scheduled for
4080 4080 addition are just unscheduled and left as they are. If the
4081 4081 executable mode of a file was changed, it is reset.
4082 4082
4083 4083 If names are given, all files matching the names are reverted.
4084 4084 If no arguments are given, no files are reverted.
4085 4085
4086 4086 Modified files are saved with a .orig suffix before reverting.
4087 4087 To disable these backups, use --no-backup.
4088 4088
4089 4089 Returns 0 on success.
4090 4090 """
4091 4091
4092 4092 if opts.get("date"):
4093 4093 if opts.get("rev"):
4094 4094 raise util.Abort(_("you can't specify a revision and a date"))
4095 4095 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4096 4096
4097 4097 parent, p2 = repo.dirstate.parents()
4098 4098 if not opts.get('rev') and p2 != nullid:
4099 4099 raise util.Abort(_('uncommitted merge - '
4100 4100 'use "hg update", see "hg help revert"'))
4101 4101
4102 4102 if not pats and not opts.get('all'):
4103 4103 raise util.Abort(_('no files or directories specified; '
4104 4104 'use --all to revert the whole repo'))
4105 4105
4106 4106 ctx = scmutil.revsingle(repo, opts.get('rev'))
4107 4107 node = ctx.node()
4108 4108 mf = ctx.manifest()
4109 4109 if node == parent:
4110 4110 pmf = mf
4111 4111 else:
4112 4112 pmf = None
4113 4113
4114 4114 # need all matching names in dirstate and manifest of target rev,
4115 4115 # so have to walk both. do not print errors if files exist in one
4116 4116 # but not other.
4117 4117
4118 4118 names = {}
4119 4119
4120 4120 wlock = repo.wlock()
4121 4121 try:
4122 4122 # walk dirstate.
4123 4123
4124 4124 m = scmutil.match(repo, pats, opts)
4125 4125 m.bad = lambda x, y: False
4126 4126 for abs in repo.walk(m):
4127 4127 names[abs] = m.rel(abs), m.exact(abs)
4128 4128
4129 4129 # walk target manifest.
4130 4130
4131 4131 def badfn(path, msg):
4132 4132 if path in names:
4133 4133 return
4134 4134 path_ = path + '/'
4135 4135 for f in names:
4136 4136 if f.startswith(path_):
4137 4137 return
4138 4138 ui.warn("%s: %s\n" % (m.rel(path), msg))
4139 4139
4140 4140 m = scmutil.match(repo, pats, opts)
4141 4141 m.bad = badfn
4142 4142 for abs in repo[node].walk(m):
4143 4143 if abs not in names:
4144 4144 names[abs] = m.rel(abs), m.exact(abs)
4145 4145
4146 4146 m = scmutil.matchfiles(repo, names)
4147 4147 changes = repo.status(match=m)[:4]
4148 4148 modified, added, removed, deleted = map(set, changes)
4149 4149
4150 4150 # if f is a rename, also revert the source
4151 4151 cwd = repo.getcwd()
4152 4152 for f in added:
4153 4153 src = repo.dirstate.copied(f)
4154 4154 if src and src not in names and repo.dirstate[src] == 'r':
4155 4155 removed.add(src)
4156 4156 names[src] = (repo.pathto(src, cwd), True)
4157 4157
4158 4158 def removeforget(abs):
4159 4159 if repo.dirstate[abs] == 'a':
4160 4160 return _('forgetting %s\n')
4161 4161 return _('removing %s\n')
4162 4162
4163 4163 revert = ([], _('reverting %s\n'))
4164 4164 add = ([], _('adding %s\n'))
4165 4165 remove = ([], removeforget)
4166 4166 undelete = ([], _('undeleting %s\n'))
4167 4167
4168 4168 disptable = (
4169 4169 # dispatch table:
4170 4170 # file state
4171 4171 # action if in target manifest
4172 4172 # action if not in target manifest
4173 4173 # make backup if in target manifest
4174 4174 # make backup if not in target manifest
4175 4175 (modified, revert, remove, True, True),
4176 4176 (added, revert, remove, True, False),
4177 4177 (removed, undelete, None, False, False),
4178 4178 (deleted, revert, remove, False, False),
4179 4179 )
4180 4180
4181 4181 for abs, (rel, exact) in sorted(names.items()):
4182 4182 mfentry = mf.get(abs)
4183 4183 target = repo.wjoin(abs)
4184 4184 def handle(xlist, dobackup):
4185 4185 xlist[0].append(abs)
4186 4186 if (dobackup and not opts.get('no_backup') and
4187 4187 os.path.lexists(target)):
4188 4188 bakname = "%s.orig" % rel
4189 4189 ui.note(_('saving current version of %s as %s\n') %
4190 4190 (rel, bakname))
4191 4191 if not opts.get('dry_run'):
4192 4192 util.rename(target, bakname)
4193 4193 if ui.verbose or not exact:
4194 4194 msg = xlist[1]
4195 4195 if not isinstance(msg, basestring):
4196 4196 msg = msg(abs)
4197 4197 ui.status(msg % rel)
4198 4198 for table, hitlist, misslist, backuphit, backupmiss in disptable:
4199 4199 if abs not in table:
4200 4200 continue
4201 4201 # file has changed in dirstate
4202 4202 if mfentry:
4203 4203 handle(hitlist, backuphit)
4204 4204 elif misslist is not None:
4205 4205 handle(misslist, backupmiss)
4206 4206 break
4207 4207 else:
4208 4208 if abs not in repo.dirstate:
4209 4209 if mfentry:
4210 4210 handle(add, True)
4211 4211 elif exact:
4212 4212 ui.warn(_('file not managed: %s\n') % rel)
4213 4213 continue
4214 4214 # file has not changed in dirstate
4215 4215 if node == parent:
4216 4216 if exact:
4217 4217 ui.warn(_('no changes needed to %s\n') % rel)
4218 4218 continue
4219 4219 if pmf is None:
4220 4220 # only need parent manifest in this unlikely case,
4221 4221 # so do not read by default
4222 4222 pmf = repo[parent].manifest()
4223 4223 if abs in pmf:
4224 4224 if mfentry:
4225 4225 # if version of file is same in parent and target
4226 4226 # manifests, do nothing
4227 4227 if (pmf[abs] != mfentry or
4228 4228 pmf.flags(abs) != mf.flags(abs)):
4229 4229 handle(revert, False)
4230 4230 else:
4231 4231 handle(remove, False)
4232 4232
4233 4233 if not opts.get('dry_run'):
4234 4234 def checkout(f):
4235 4235 fc = ctx[f]
4236 4236 repo.wwrite(f, fc.data(), fc.flags())
4237 4237
4238 4238 audit_path = scmutil.pathauditor(repo.root)
4239 4239 for f in remove[0]:
4240 4240 if repo.dirstate[f] == 'a':
4241 4241 repo.dirstate.forget(f)
4242 4242 continue
4243 4243 audit_path(f)
4244 4244 try:
4245 4245 util.unlinkpath(repo.wjoin(f))
4246 4246 except OSError:
4247 4247 pass
4248 4248 repo.dirstate.remove(f)
4249 4249
4250 4250 normal = None
4251 4251 if node == parent:
4252 4252 # We're reverting to our parent. If possible, we'd like status
4253 4253 # to report the file as clean. We have to use normallookup for
4254 4254 # merges to avoid losing information about merged/dirty files.
4255 4255 if p2 != nullid:
4256 4256 normal = repo.dirstate.normallookup
4257 4257 else:
4258 4258 normal = repo.dirstate.normal
4259 4259 for f in revert[0]:
4260 4260 checkout(f)
4261 4261 if normal:
4262 4262 normal(f)
4263 4263
4264 4264 for f in add[0]:
4265 4265 checkout(f)
4266 4266 repo.dirstate.add(f)
4267 4267
4268 4268 normal = repo.dirstate.normallookup
4269 4269 if node == parent and p2 == nullid:
4270 4270 normal = repo.dirstate.normal
4271 4271 for f in undelete[0]:
4272 4272 checkout(f)
4273 4273 normal(f)
4274 4274
4275 4275 finally:
4276 4276 wlock.release()
4277 4277
4278 4278 @command('rollback', dryrunopts)
4279 4279 def rollback(ui, repo, **opts):
4280 4280 """roll back the last transaction (dangerous)
4281 4281
4282 4282 This command should be used with care. There is only one level of
4283 4283 rollback, and there is no way to undo a rollback. It will also
4284 4284 restore the dirstate at the time of the last transaction, losing
4285 4285 any dirstate changes since that time. This command does not alter
4286 4286 the working directory.
4287 4287
4288 4288 Transactions are used to encapsulate the effects of all commands
4289 4289 that create new changesets or propagate existing changesets into a
4290 4290 repository. For example, the following commands are transactional,
4291 4291 and their effects can be rolled back:
4292 4292
4293 4293 - commit
4294 4294 - import
4295 4295 - pull
4296 4296 - push (with this repository as the destination)
4297 4297 - unbundle
4298 4298
4299 4299 This command is not intended for use on public repositories. Once
4300 4300 changes are visible for pull by other users, rolling a transaction
4301 4301 back locally is ineffective (someone else may already have pulled
4302 4302 the changes). Furthermore, a race is possible with readers of the
4303 4303 repository; for example an in-progress pull from the repository
4304 4304 may fail if a rollback is performed.
4305 4305
4306 4306 Returns 0 on success, 1 if no rollback data is available.
4307 4307 """
4308 4308 return repo.rollback(opts.get('dry_run'))
4309 4309
4310 4310 @command('root', [])
4311 4311 def root(ui, repo):
4312 4312 """print the root (top) of the current working directory
4313 4313
4314 4314 Print the root directory of the current repository.
4315 4315
4316 4316 Returns 0 on success.
4317 4317 """
4318 4318 ui.write(repo.root + "\n")
4319 4319
4320 4320 @command('^serve',
4321 4321 [('A', 'accesslog', '', _('name of access log file to write to'),
4322 4322 _('FILE')),
4323 4323 ('d', 'daemon', None, _('run server in background')),
4324 4324 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
4325 4325 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4326 4326 # use string type, then we can check if something was passed
4327 4327 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4328 4328 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4329 4329 _('ADDR')),
4330 4330 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4331 4331 _('PREFIX')),
4332 4332 ('n', 'name', '',
4333 4333 _('name to show in web pages (default: working directory)'), _('NAME')),
4334 4334 ('', 'web-conf', '',
4335 4335 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
4336 4336 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4337 4337 _('FILE')),
4338 4338 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4339 4339 ('', 'stdio', None, _('for remote clients')),
4340 4340 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4341 4341 ('', 'style', '', _('template style to use'), _('STYLE')),
4342 4342 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4343 4343 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
4344 4344 _('[OPTION]...'))
4345 4345 def serve(ui, repo, **opts):
4346 4346 """start stand-alone webserver
4347 4347
4348 4348 Start a local HTTP repository browser and pull server. You can use
4349 4349 this for ad-hoc sharing and browsing of repositories. It is
4350 4350 recommended to use a real web server to serve a repository for
4351 4351 longer periods of time.
4352 4352
4353 4353 Please note that the server does not implement access control.
4354 4354 This means that, by default, anybody can read from the server and
4355 4355 nobody can write to it by default. Set the ``web.allow_push``
4356 4356 option to ``*`` to allow everybody to push to the server. You
4357 4357 should use a real web server if you need to authenticate users.
4358 4358
4359 4359 By default, the server logs accesses to stdout and errors to
4360 4360 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4361 4361 files.
4362 4362
4363 4363 To have the server choose a free port number to listen on, specify
4364 4364 a port number of 0; in this case, the server will print the port
4365 4365 number it uses.
4366 4366
4367 4367 Returns 0 on success.
4368 4368 """
4369 4369
4370 4370 if opts["stdio"]:
4371 4371 if repo is None:
4372 4372 raise error.RepoError(_("There is no Mercurial repository here"
4373 4373 " (.hg not found)"))
4374 4374 s = sshserver.sshserver(ui, repo)
4375 4375 s.serve_forever()
4376 4376
4377 4377 # this way we can check if something was given in the command-line
4378 4378 if opts.get('port'):
4379 4379 opts['port'] = util.getport(opts.get('port'))
4380 4380
4381 4381 baseui = repo and repo.baseui or ui
4382 4382 optlist = ("name templates style address port prefix ipv6"
4383 4383 " accesslog errorlog certificate encoding")
4384 4384 for o in optlist.split():
4385 4385 val = opts.get(o, '')
4386 4386 if val in (None, ''): # should check against default options instead
4387 4387 continue
4388 4388 baseui.setconfig("web", o, val)
4389 4389 if repo and repo.ui != baseui:
4390 4390 repo.ui.setconfig("web", o, val)
4391 4391
4392 4392 o = opts.get('web_conf') or opts.get('webdir_conf')
4393 4393 if not o:
4394 4394 if not repo:
4395 4395 raise error.RepoError(_("There is no Mercurial repository"
4396 4396 " here (.hg not found)"))
4397 4397 o = repo.root
4398 4398
4399 4399 app = hgweb.hgweb(o, baseui=ui)
4400 4400
4401 4401 class service(object):
4402 4402 def init(self):
4403 4403 util.setsignalhandler()
4404 4404 self.httpd = hgweb.server.create_server(ui, app)
4405 4405
4406 4406 if opts['port'] and not ui.verbose:
4407 4407 return
4408 4408
4409 4409 if self.httpd.prefix:
4410 4410 prefix = self.httpd.prefix.strip('/') + '/'
4411 4411 else:
4412 4412 prefix = ''
4413 4413
4414 4414 port = ':%d' % self.httpd.port
4415 4415 if port == ':80':
4416 4416 port = ''
4417 4417
4418 4418 bindaddr = self.httpd.addr
4419 4419 if bindaddr == '0.0.0.0':
4420 4420 bindaddr = '*'
4421 4421 elif ':' in bindaddr: # IPv6
4422 4422 bindaddr = '[%s]' % bindaddr
4423 4423
4424 4424 fqaddr = self.httpd.fqaddr
4425 4425 if ':' in fqaddr:
4426 4426 fqaddr = '[%s]' % fqaddr
4427 4427 if opts['port']:
4428 4428 write = ui.status
4429 4429 else:
4430 4430 write = ui.write
4431 4431 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
4432 4432 (fqaddr, port, prefix, bindaddr, self.httpd.port))
4433 4433
4434 4434 def run(self):
4435 4435 self.httpd.serve_forever()
4436 4436
4437 4437 service = service()
4438 4438
4439 4439 cmdutil.service(opts, initfn=service.init, runfn=service.run)
4440 4440
4441 4441 @command('showconfig|debugconfig',
4442 4442 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4443 4443 _('[-u] [NAME]...'))
4444 4444 def showconfig(ui, repo, *values, **opts):
4445 4445 """show combined config settings from all hgrc files
4446 4446
4447 4447 With no arguments, print names and values of all config items.
4448 4448
4449 4449 With one argument of the form section.name, print just the value
4450 4450 of that config item.
4451 4451
4452 4452 With multiple arguments, print names and values of all config
4453 4453 items with matching section names.
4454 4454
4455 4455 With --debug, the source (filename and line number) is printed
4456 4456 for each config item.
4457 4457
4458 4458 Returns 0 on success.
4459 4459 """
4460 4460
4461 4461 for f in scmutil.rcpath():
4462 4462 ui.debug(_('read config from: %s\n') % f)
4463 4463 untrusted = bool(opts.get('untrusted'))
4464 4464 if values:
4465 4465 sections = [v for v in values if '.' not in v]
4466 4466 items = [v for v in values if '.' in v]
4467 4467 if len(items) > 1 or items and sections:
4468 4468 raise util.Abort(_('only one config item permitted'))
4469 4469 for section, name, value in ui.walkconfig(untrusted=untrusted):
4470 4470 value = str(value).replace('\n', '\\n')
4471 4471 sectname = section + '.' + name
4472 4472 if values:
4473 4473 for v in values:
4474 4474 if v == section:
4475 4475 ui.debug('%s: ' %
4476 4476 ui.configsource(section, name, untrusted))
4477 4477 ui.write('%s=%s\n' % (sectname, value))
4478 4478 elif v == sectname:
4479 4479 ui.debug('%s: ' %
4480 4480 ui.configsource(section, name, untrusted))
4481 4481 ui.write(value, '\n')
4482 4482 else:
4483 4483 ui.debug('%s: ' %
4484 4484 ui.configsource(section, name, untrusted))
4485 4485 ui.write('%s=%s\n' % (sectname, value))
4486 4486
4487 4487 @command('^status|st',
4488 4488 [('A', 'all', None, _('show status of all files')),
4489 4489 ('m', 'modified', None, _('show only modified files')),
4490 4490 ('a', 'added', None, _('show only added files')),
4491 4491 ('r', 'removed', None, _('show only removed files')),
4492 4492 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4493 4493 ('c', 'clean', None, _('show only files without changes')),
4494 4494 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4495 4495 ('i', 'ignored', None, _('show only ignored files')),
4496 4496 ('n', 'no-status', None, _('hide status prefix')),
4497 4497 ('C', 'copies', None, _('show source of copied files')),
4498 4498 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4499 4499 ('', 'rev', [], _('show difference from revision'), _('REV')),
4500 4500 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4501 4501 ] + walkopts + subrepoopts,
4502 4502 _('[OPTION]... [FILE]...'))
4503 4503 def status(ui, repo, *pats, **opts):
4504 4504 """show changed files in the working directory
4505 4505
4506 4506 Show status of files in the repository. If names are given, only
4507 4507 files that match are shown. Files that are clean or ignored or
4508 4508 the source of a copy/move operation, are not listed unless
4509 4509 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4510 4510 Unless options described with "show only ..." are given, the
4511 4511 options -mardu are used.
4512 4512
4513 4513 Option -q/--quiet hides untracked (unknown and ignored) files
4514 4514 unless explicitly requested with -u/--unknown or -i/--ignored.
4515 4515
4516 4516 .. note::
4517 4517 status may appear to disagree with diff if permissions have
4518 4518 changed or a merge has occurred. The standard diff format does
4519 4519 not report permission changes and diff only reports changes
4520 4520 relative to one merge parent.
4521 4521
4522 4522 If one revision is given, it is used as the base revision.
4523 4523 If two revisions are given, the differences between them are
4524 4524 shown. The --change option can also be used as a shortcut to list
4525 4525 the changed files of a revision from its first parent.
4526 4526
4527 4527 The codes used to show the status of files are::
4528 4528
4529 4529 M = modified
4530 4530 A = added
4531 4531 R = removed
4532 4532 C = clean
4533 4533 ! = missing (deleted by non-hg command, but still tracked)
4534 4534 ? = not tracked
4535 4535 I = ignored
4536 4536 = origin of the previous file listed as A (added)
4537 4537
4538 4538 Returns 0 on success.
4539 4539 """
4540 4540
4541 4541 revs = opts.get('rev')
4542 4542 change = opts.get('change')
4543 4543
4544 4544 if revs and change:
4545 4545 msg = _('cannot specify --rev and --change at the same time')
4546 4546 raise util.Abort(msg)
4547 4547 elif change:
4548 4548 node2 = repo.lookup(change)
4549 4549 node1 = repo[node2].p1().node()
4550 4550 else:
4551 4551 node1, node2 = scmutil.revpair(repo, revs)
4552 4552
4553 4553 cwd = (pats and repo.getcwd()) or ''
4554 4554 end = opts.get('print0') and '\0' or '\n'
4555 4555 copy = {}
4556 4556 states = 'modified added removed deleted unknown ignored clean'.split()
4557 4557 show = [k for k in states if opts.get(k)]
4558 4558 if opts.get('all'):
4559 4559 show += ui.quiet and (states[:4] + ['clean']) or states
4560 4560 if not show:
4561 4561 show = ui.quiet and states[:4] or states[:5]
4562 4562
4563 4563 stat = repo.status(node1, node2, scmutil.match(repo, pats, opts),
4564 4564 'ignored' in show, 'clean' in show, 'unknown' in show,
4565 4565 opts.get('subrepos'))
4566 4566 changestates = zip(states, 'MAR!?IC', stat)
4567 4567
4568 4568 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
4569 4569 ctxn = repo[nullid]
4570 4570 ctx1 = repo[node1]
4571 4571 ctx2 = repo[node2]
4572 4572 added = stat[1]
4573 4573 if node2 is None:
4574 4574 added = stat[0] + stat[1] # merged?
4575 4575
4576 4576 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
4577 4577 if k in added:
4578 4578 copy[k] = v
4579 4579 elif v in added:
4580 4580 copy[v] = k
4581 4581
4582 4582 for state, char, files in changestates:
4583 4583 if state in show:
4584 4584 format = "%s %%s%s" % (char, end)
4585 4585 if opts.get('no_status'):
4586 4586 format = "%%s%s" % end
4587 4587
4588 4588 for f in files:
4589 4589 ui.write(format % repo.pathto(f, cwd),
4590 4590 label='status.' + state)
4591 4591 if f in copy:
4592 4592 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
4593 4593 label='status.copied')
4594 4594
4595 4595 @command('^summary|sum',
4596 4596 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
4597 4597 def summary(ui, repo, **opts):
4598 4598 """summarize working directory state
4599 4599
4600 4600 This generates a brief summary of the working directory state,
4601 4601 including parents, branch, commit status, and available updates.
4602 4602
4603 4603 With the --remote option, this will check the default paths for
4604 4604 incoming and outgoing changes. This can be time-consuming.
4605 4605
4606 4606 Returns 0 on success.
4607 4607 """
4608 4608
4609 4609 ctx = repo[None]
4610 4610 parents = ctx.parents()
4611 4611 pnode = parents[0].node()
4612 4612
4613 4613 for p in parents:
4614 4614 # label with log.changeset (instead of log.parent) since this
4615 4615 # shows a working directory parent *changeset*:
4616 4616 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
4617 4617 label='log.changeset')
4618 4618 ui.write(' '.join(p.tags()), label='log.tag')
4619 4619 if p.bookmarks():
4620 4620 ui.write(' ' + ' '.join(p.bookmarks()), label='log.bookmark')
4621 4621 if p.rev() == -1:
4622 4622 if not len(repo):
4623 4623 ui.write(_(' (empty repository)'))
4624 4624 else:
4625 4625 ui.write(_(' (no revision checked out)'))
4626 4626 ui.write('\n')
4627 4627 if p.description():
4628 4628 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
4629 4629 label='log.summary')
4630 4630
4631 4631 branch = ctx.branch()
4632 4632 bheads = repo.branchheads(branch)
4633 4633 m = _('branch: %s\n') % branch
4634 4634 if branch != 'default':
4635 4635 ui.write(m, label='log.branch')
4636 4636 else:
4637 4637 ui.status(m, label='log.branch')
4638 4638
4639 4639 st = list(repo.status(unknown=True))[:6]
4640 4640
4641 4641 c = repo.dirstate.copies()
4642 4642 copied, renamed = [], []
4643 4643 for d, s in c.iteritems():
4644 4644 if s in st[2]:
4645 4645 st[2].remove(s)
4646 4646 renamed.append(d)
4647 4647 else:
4648 4648 copied.append(d)
4649 4649 if d in st[1]:
4650 4650 st[1].remove(d)
4651 4651 st.insert(3, renamed)
4652 4652 st.insert(4, copied)
4653 4653
4654 4654 ms = mergemod.mergestate(repo)
4655 4655 st.append([f for f in ms if ms[f] == 'u'])
4656 4656
4657 4657 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
4658 4658 st.append(subs)
4659 4659
4660 4660 labels = [ui.label(_('%d modified'), 'status.modified'),
4661 4661 ui.label(_('%d added'), 'status.added'),
4662 4662 ui.label(_('%d removed'), 'status.removed'),
4663 4663 ui.label(_('%d renamed'), 'status.copied'),
4664 4664 ui.label(_('%d copied'), 'status.copied'),
4665 4665 ui.label(_('%d deleted'), 'status.deleted'),
4666 4666 ui.label(_('%d unknown'), 'status.unknown'),
4667 4667 ui.label(_('%d ignored'), 'status.ignored'),
4668 4668 ui.label(_('%d unresolved'), 'resolve.unresolved'),
4669 4669 ui.label(_('%d subrepos'), 'status.modified')]
4670 4670 t = []
4671 4671 for s, l in zip(st, labels):
4672 4672 if s:
4673 4673 t.append(l % len(s))
4674 4674
4675 4675 t = ', '.join(t)
4676 4676 cleanworkdir = False
4677 4677
4678 4678 if len(parents) > 1:
4679 4679 t += _(' (merge)')
4680 4680 elif branch != parents[0].branch():
4681 4681 t += _(' (new branch)')
4682 4682 elif (parents[0].extra().get('close') and
4683 4683 pnode in repo.branchheads(branch, closed=True)):
4684 4684 t += _(' (head closed)')
4685 4685 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
4686 4686 t += _(' (clean)')
4687 4687 cleanworkdir = True
4688 4688 elif pnode not in bheads:
4689 4689 t += _(' (new branch head)')
4690 4690
4691 4691 if cleanworkdir:
4692 4692 ui.status(_('commit: %s\n') % t.strip())
4693 4693 else:
4694 4694 ui.write(_('commit: %s\n') % t.strip())
4695 4695
4696 4696 # all ancestors of branch heads - all ancestors of parent = new csets
4697 4697 new = [0] * len(repo)
4698 4698 cl = repo.changelog
4699 4699 for a in [cl.rev(n) for n in bheads]:
4700 4700 new[a] = 1
4701 4701 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
4702 4702 new[a] = 1
4703 4703 for a in [p.rev() for p in parents]:
4704 4704 if a >= 0:
4705 4705 new[a] = 0
4706 4706 for a in cl.ancestors(*[p.rev() for p in parents]):
4707 4707 new[a] = 0
4708 4708 new = sum(new)
4709 4709
4710 4710 if new == 0:
4711 4711 ui.status(_('update: (current)\n'))
4712 4712 elif pnode not in bheads:
4713 4713 ui.write(_('update: %d new changesets (update)\n') % new)
4714 4714 else:
4715 4715 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
4716 4716 (new, len(bheads)))
4717 4717
4718 4718 if opts.get('remote'):
4719 4719 t = []
4720 4720 source, branches = hg.parseurl(ui.expandpath('default'))
4721 4721 other = hg.repository(hg.remoteui(repo, {}), source)
4722 4722 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4723 4723 ui.debug('comparing with %s\n' % util.hidepassword(source))
4724 4724 repo.ui.pushbuffer()
4725 4725 commoninc = discovery.findcommonincoming(repo, other)
4726 4726 _common, incoming, _rheads = commoninc
4727 4727 repo.ui.popbuffer()
4728 4728 if incoming:
4729 4729 t.append(_('1 or more incoming'))
4730 4730
4731 4731 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
4732 4732 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
4733 4733 if source != dest:
4734 4734 other = hg.repository(hg.remoteui(repo, {}), dest)
4735 4735 commoninc = None
4736 4736 ui.debug('comparing with %s\n' % util.hidepassword(dest))
4737 4737 repo.ui.pushbuffer()
4738 4738 common, outheads = discovery.findcommonoutgoing(repo, other,
4739 4739 commoninc=commoninc)
4740 4740 repo.ui.popbuffer()
4741 4741 o = repo.changelog.findmissing(common=common, heads=outheads)
4742 4742 if o:
4743 4743 t.append(_('%d outgoing') % len(o))
4744 4744 if 'bookmarks' in other.listkeys('namespaces'):
4745 4745 lmarks = repo.listkeys('bookmarks')
4746 4746 rmarks = other.listkeys('bookmarks')
4747 4747 diff = set(rmarks) - set(lmarks)
4748 4748 if len(diff) > 0:
4749 4749 t.append(_('%d incoming bookmarks') % len(diff))
4750 4750 diff = set(lmarks) - set(rmarks)
4751 4751 if len(diff) > 0:
4752 4752 t.append(_('%d outgoing bookmarks') % len(diff))
4753 4753
4754 4754 if t:
4755 4755 ui.write(_('remote: %s\n') % (', '.join(t)))
4756 4756 else:
4757 4757 ui.status(_('remote: (synced)\n'))
4758 4758
4759 4759 @command('tag',
4760 4760 [('f', 'force', None, _('force tag')),
4761 4761 ('l', 'local', None, _('make the tag local')),
4762 4762 ('r', 'rev', '', _('revision to tag'), _('REV')),
4763 4763 ('', 'remove', None, _('remove a tag')),
4764 4764 # -l/--local is already there, commitopts cannot be used
4765 4765 ('e', 'edit', None, _('edit commit message')),
4766 4766 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
4767 4767 ] + commitopts2,
4768 4768 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
4769 4769 def tag(ui, repo, name1, *names, **opts):
4770 4770 """add one or more tags for the current or given revision
4771 4771
4772 4772 Name a particular revision using <name>.
4773 4773
4774 4774 Tags are used to name particular revisions of the repository and are
4775 4775 very useful to compare different revisions, to go back to significant
4776 4776 earlier versions or to mark branch points as releases, etc. Changing
4777 4777 an existing tag is normally disallowed; use -f/--force to override.
4778 4778
4779 4779 If no revision is given, the parent of the working directory is
4780 4780 used, or tip if no revision is checked out.
4781 4781
4782 4782 To facilitate version control, distribution, and merging of tags,
4783 4783 they are stored as a file named ".hgtags" which is managed similarly
4784 4784 to other project files and can be hand-edited if necessary. This
4785 4785 also means that tagging creates a new commit. The file
4786 4786 ".hg/localtags" is used for local tags (not shared among
4787 4787 repositories).
4788 4788
4789 4789 Tag commits are usually made at the head of a branch. If the parent
4790 4790 of the working directory is not a branch head, :hg:`tag` aborts; use
4791 4791 -f/--force to force the tag commit to be based on a non-head
4792 4792 changeset.
4793 4793
4794 4794 See :hg:`help dates` for a list of formats valid for -d/--date.
4795 4795
4796 4796 Since tag names have priority over branch names during revision
4797 4797 lookup, using an existing branch name as a tag name is discouraged.
4798 4798
4799 4799 Returns 0 on success.
4800 4800 """
4801 4801
4802 4802 rev_ = "."
4803 4803 names = [t.strip() for t in (name1,) + names]
4804 4804 if len(names) != len(set(names)):
4805 4805 raise util.Abort(_('tag names must be unique'))
4806 4806 for n in names:
4807 4807 if n in ['tip', '.', 'null']:
4808 4808 raise util.Abort(_("the name '%s' is reserved") % n)
4809 4809 if not n:
4810 4810 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
4811 4811 if opts.get('rev') and opts.get('remove'):
4812 4812 raise util.Abort(_("--rev and --remove are incompatible"))
4813 4813 if opts.get('rev'):
4814 4814 rev_ = opts['rev']
4815 4815 message = opts.get('message')
4816 4816 if opts.get('remove'):
4817 4817 expectedtype = opts.get('local') and 'local' or 'global'
4818 4818 for n in names:
4819 4819 if not repo.tagtype(n):
4820 4820 raise util.Abort(_("tag '%s' does not exist") % n)
4821 4821 if repo.tagtype(n) != expectedtype:
4822 4822 if expectedtype == 'global':
4823 4823 raise util.Abort(_("tag '%s' is not a global tag") % n)
4824 4824 else:
4825 4825 raise util.Abort(_("tag '%s' is not a local tag") % n)
4826 4826 rev_ = nullid
4827 4827 if not message:
4828 4828 # we don't translate commit messages
4829 4829 message = 'Removed tag %s' % ', '.join(names)
4830 4830 elif not opts.get('force'):
4831 4831 for n in names:
4832 4832 if n in repo.tags():
4833 4833 raise util.Abort(_("tag '%s' already exists "
4834 4834 "(use -f to force)") % n)
4835 4835 if not opts.get('local'):
4836 4836 p1, p2 = repo.dirstate.parents()
4837 4837 if p2 != nullid:
4838 4838 raise util.Abort(_('uncommitted merge'))
4839 4839 bheads = repo.branchheads()
4840 4840 if not opts.get('force') and bheads and p1 not in bheads:
4841 4841 raise util.Abort(_('not at a branch head (use -f to force)'))
4842 4842 r = scmutil.revsingle(repo, rev_).node()
4843 4843
4844 4844 if not message:
4845 4845 # we don't translate commit messages
4846 4846 message = ('Added tag %s for changeset %s' %
4847 4847 (', '.join(names), short(r)))
4848 4848
4849 4849 date = opts.get('date')
4850 4850 if date:
4851 4851 date = util.parsedate(date)
4852 4852
4853 4853 if opts.get('edit'):
4854 4854 message = ui.edit(message, ui.username())
4855 4855
4856 4856 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
4857 4857
4858 4858 @command('tags', [], '')
4859 4859 def tags(ui, repo):
4860 4860 """list repository tags
4861 4861
4862 4862 This lists both regular and local tags. When the -v/--verbose
4863 4863 switch is used, a third column "local" is printed for local tags.
4864 4864
4865 4865 Returns 0 on success.
4866 4866 """
4867 4867
4868 4868 hexfunc = ui.debugflag and hex or short
4869 4869 tagtype = ""
4870 4870
4871 4871 for t, n in reversed(repo.tagslist()):
4872 4872 if ui.quiet:
4873 4873 ui.write("%s\n" % t)
4874 4874 continue
4875 4875
4876 4876 hn = hexfunc(n)
4877 4877 r = "%5d:%s" % (repo.changelog.rev(n), hn)
4878 4878 spaces = " " * (30 - encoding.colwidth(t))
4879 4879
4880 4880 if ui.verbose:
4881 4881 if repo.tagtype(t) == 'local':
4882 4882 tagtype = " local"
4883 4883 else:
4884 4884 tagtype = ""
4885 4885 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
4886 4886
4887 4887 @command('tip',
4888 4888 [('p', 'patch', None, _('show patch')),
4889 4889 ('g', 'git', None, _('use git extended diff format')),
4890 4890 ] + templateopts,
4891 4891 _('[-p] [-g]'))
4892 4892 def tip(ui, repo, **opts):
4893 4893 """show the tip revision
4894 4894
4895 4895 The tip revision (usually just called the tip) is the changeset
4896 4896 most recently added to the repository (and therefore the most
4897 4897 recently changed head).
4898 4898
4899 4899 If you have just made a commit, that commit will be the tip. If
4900 4900 you have just pulled changes from another repository, the tip of
4901 4901 that repository becomes the current tip. The "tip" tag is special
4902 4902 and cannot be renamed or assigned to a different changeset.
4903 4903
4904 4904 Returns 0 on success.
4905 4905 """
4906 4906 displayer = cmdutil.show_changeset(ui, repo, opts)
4907 4907 displayer.show(repo[len(repo) - 1])
4908 4908 displayer.close()
4909 4909
4910 4910 @command('unbundle',
4911 4911 [('u', 'update', None,
4912 4912 _('update to new branch head if changesets were unbundled'))],
4913 4913 _('[-u] FILE...'))
4914 4914 def unbundle(ui, repo, fname1, *fnames, **opts):
4915 4915 """apply one or more changegroup files
4916 4916
4917 4917 Apply one or more compressed changegroup files generated by the
4918 4918 bundle command.
4919 4919
4920 4920 Returns 0 on success, 1 if an update has unresolved files.
4921 4921 """
4922 4922 fnames = (fname1,) + fnames
4923 4923
4924 4924 lock = repo.lock()
4925 4925 wc = repo['.']
4926 4926 try:
4927 4927 for fname in fnames:
4928 4928 f = url.open(ui, fname)
4929 4929 gen = changegroup.readbundle(f, fname)
4930 4930 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
4931 4931 lock=lock)
4932 4932 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
4933 4933 finally:
4934 4934 lock.release()
4935 4935 return postincoming(ui, repo, modheads, opts.get('update'), None)
4936 4936
4937 4937 @command('^update|up|checkout|co',
4938 4938 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
4939 4939 ('c', 'check', None,
4940 4940 _('update across branches if no uncommitted changes')),
4941 4941 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4942 4942 ('r', 'rev', '', _('revision'), _('REV'))],
4943 4943 _('[-c] [-C] [-d DATE] [[-r] REV]'))
4944 4944 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
4945 4945 """update working directory (or switch revisions)
4946 4946
4947 4947 Update the repository's working directory to the specified
4948 4948 changeset. If no changeset is specified, update to the tip of the
4949 4949 current named branch.
4950 4950
4951 4951 If the changeset is not a descendant of the working directory's
4952 4952 parent, the update is aborted. With the -c/--check option, the
4953 4953 working directory is checked for uncommitted changes; if none are
4954 4954 found, the working directory is updated to the specified
4955 4955 changeset.
4956 4956
4957 4957 The following rules apply when the working directory contains
4958 4958 uncommitted changes:
4959 4959
4960 4960 1. If neither -c/--check nor -C/--clean is specified, and if
4961 4961 the requested changeset is an ancestor or descendant of
4962 4962 the working directory's parent, the uncommitted changes
4963 4963 are merged into the requested changeset and the merged
4964 4964 result is left uncommitted. If the requested changeset is
4965 4965 not an ancestor or descendant (that is, it is on another
4966 4966 branch), the update is aborted and the uncommitted changes
4967 4967 are preserved.
4968 4968
4969 4969 2. With the -c/--check option, the update is aborted and the
4970 4970 uncommitted changes are preserved.
4971 4971
4972 4972 3. With the -C/--clean option, uncommitted changes are discarded and
4973 4973 the working directory is updated to the requested changeset.
4974 4974
4975 4975 Use null as the changeset to remove the working directory (like
4976 4976 :hg:`clone -U`).
4977 4977
4978 4978 If you want to update just one file to an older changeset, use
4979 4979 :hg:`revert`.
4980 4980
4981 4981 See :hg:`help dates` for a list of formats valid for -d/--date.
4982 4982
4983 4983 Returns 0 on success, 1 if there are unresolved files.
4984 4984 """
4985 4985 if rev and node:
4986 4986 raise util.Abort(_("please specify just one revision"))
4987 4987
4988 4988 if rev is None or rev == '':
4989 4989 rev = node
4990 4990
4991 4991 # if we defined a bookmark, we have to remember the original bookmark name
4992 4992 brev = rev
4993 4993 rev = scmutil.revsingle(repo, rev, rev).rev()
4994 4994
4995 4995 if check and clean:
4996 4996 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
4997 4997
4998 4998 if check:
4999 4999 # we could use dirty() but we can ignore merge and branch trivia
5000 5000 c = repo[None]
5001 5001 if c.modified() or c.added() or c.removed():
5002 5002 raise util.Abort(_("uncommitted local changes"))
5003 5003
5004 5004 if date:
5005 5005 if rev is not None:
5006 5006 raise util.Abort(_("you can't specify a revision and a date"))
5007 5007 rev = cmdutil.finddate(ui, repo, date)
5008 5008
5009 5009 if clean or check:
5010 5010 ret = hg.clean(repo, rev)
5011 5011 else:
5012 5012 ret = hg.update(repo, rev)
5013 5013
5014 5014 if brev in repo._bookmarks:
5015 5015 bookmarks.setcurrent(repo, brev)
5016 5016
5017 5017 return ret
5018 5018
5019 5019 @command('verify', [])
5020 5020 def verify(ui, repo):
5021 5021 """verify the integrity of the repository
5022 5022
5023 5023 Verify the integrity of the current repository.
5024 5024
5025 5025 This will perform an extensive check of the repository's
5026 5026 integrity, validating the hashes and checksums of each entry in
5027 5027 the changelog, manifest, and tracked files, as well as the
5028 5028 integrity of their crosslinks and indices.
5029 5029
5030 5030 Returns 0 on success, 1 if errors are encountered.
5031 5031 """
5032 5032 return hg.verify(repo)
5033 5033
5034 5034 @command('version', [])
5035 5035 def version_(ui):
5036 5036 """output version and copyright information"""
5037 5037 ui.write(_("Mercurial Distributed SCM (version %s)\n")
5038 5038 % util.version())
5039 5039 ui.status(_(
5040 5040 "(see http://mercurial.selenic.com for more information)\n"
5041 5041 "\nCopyright (C) 2005-2011 Matt Mackall and others\n"
5042 5042 "This is free software; see the source for copying conditions. "
5043 5043 "There is NO\nwarranty; "
5044 5044 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5045 5045 ))
5046 5046
5047 5047 norepo = ("clone init version help debugcommands debugcomplete"
5048 5048 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
5049 5049 " debugknown debuggetbundle debugbundle")
5050 5050 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
5051 5051 " debugdata debugindex debugindexdot debugrevlog")
@@ -1,1779 +1,1780 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, errno, re
10 10 import tempfile, zlib
11 11
12 12 from i18n import _
13 13 from node import hex, nullid, short
14 14 import base85, mdiff, scmutil, 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
22 22 # public functions
23 23
24 24 def split(stream):
25 25 '''return an iterator of individual patches from a stream'''
26 26 def isheader(line, inheader):
27 27 if inheader and line[0] in (' ', '\t'):
28 28 # continuation
29 29 return True
30 30 if line[0] in (' ', '-', '+'):
31 31 # diff line - don't check for header pattern in there
32 32 return False
33 33 l = line.split(': ', 1)
34 34 return len(l) == 2 and ' ' not in l[0]
35 35
36 36 def chunk(lines):
37 37 return cStringIO.StringIO(''.join(lines))
38 38
39 39 def hgsplit(stream, cur):
40 40 inheader = True
41 41
42 42 for line in stream:
43 43 if not line.strip():
44 44 inheader = False
45 45 if not inheader and line.startswith('# HG changeset patch'):
46 46 yield chunk(cur)
47 47 cur = []
48 48 inheader = True
49 49
50 50 cur.append(line)
51 51
52 52 if cur:
53 53 yield chunk(cur)
54 54
55 55 def mboxsplit(stream, cur):
56 56 for line in stream:
57 57 if line.startswith('From '):
58 58 for c in split(chunk(cur[1:])):
59 59 yield c
60 60 cur = []
61 61
62 62 cur.append(line)
63 63
64 64 if cur:
65 65 for c in split(chunk(cur[1:])):
66 66 yield c
67 67
68 68 def mimesplit(stream, cur):
69 69 def msgfp(m):
70 70 fp = cStringIO.StringIO()
71 71 g = email.Generator.Generator(fp, mangle_from_=False)
72 72 g.flatten(m)
73 73 fp.seek(0)
74 74 return fp
75 75
76 76 for line in stream:
77 77 cur.append(line)
78 78 c = chunk(cur)
79 79
80 80 m = email.Parser.Parser().parse(c)
81 81 if not m.is_multipart():
82 82 yield msgfp(m)
83 83 else:
84 84 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
85 85 for part in m.walk():
86 86 ct = part.get_content_type()
87 87 if ct not in ok_types:
88 88 continue
89 89 yield msgfp(part)
90 90
91 91 def headersplit(stream, cur):
92 92 inheader = False
93 93
94 94 for line in stream:
95 95 if not inheader and isheader(line, inheader):
96 96 yield chunk(cur)
97 97 cur = []
98 98 inheader = True
99 99 if inheader and not isheader(line, inheader):
100 100 inheader = False
101 101
102 102 cur.append(line)
103 103
104 104 if cur:
105 105 yield chunk(cur)
106 106
107 107 def remainder(cur):
108 108 yield chunk(cur)
109 109
110 110 class fiter(object):
111 111 def __init__(self, fp):
112 112 self.fp = fp
113 113
114 114 def __iter__(self):
115 115 return self
116 116
117 117 def next(self):
118 118 l = self.fp.readline()
119 119 if not l:
120 120 raise StopIteration
121 121 return l
122 122
123 123 inheader = False
124 124 cur = []
125 125
126 126 mimeheaders = ['content-type']
127 127
128 128 if not hasattr(stream, 'next'):
129 129 # http responses, for example, have readline but not next
130 130 stream = fiter(stream)
131 131
132 132 for line in stream:
133 133 cur.append(line)
134 134 if line.startswith('# HG changeset patch'):
135 135 return hgsplit(stream, cur)
136 136 elif line.startswith('From '):
137 137 return mboxsplit(stream, cur)
138 138 elif isheader(line, inheader):
139 139 inheader = True
140 140 if line.split(':', 1)[0].lower() in mimeheaders:
141 141 # let email parser handle this
142 142 return mimesplit(stream, cur)
143 143 elif line.startswith('--- ') and inheader:
144 144 # No evil headers seen by diff start, split by hand
145 145 return headersplit(stream, cur)
146 146 # Not enough info, keep reading
147 147
148 148 # if we are here, we have a very plain patch
149 149 return remainder(cur)
150 150
151 151 def extract(ui, fileobj):
152 152 '''extract patch from data read from fileobj.
153 153
154 154 patch can be a normal patch or contained in an email message.
155 155
156 156 return tuple (filename, message, user, date, branch, node, p1, p2).
157 157 Any item in the returned tuple can be None. If filename is None,
158 158 fileobj did not contain a patch. Caller must unlink filename when done.'''
159 159
160 160 # attempt to detect the start of a patch
161 161 # (this heuristic is borrowed from quilt)
162 162 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
163 163 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
164 164 r'---[ \t].*?^\+\+\+[ \t]|'
165 165 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
166 166
167 167 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
168 168 tmpfp = os.fdopen(fd, 'w')
169 169 try:
170 170 msg = email.Parser.Parser().parse(fileobj)
171 171
172 172 subject = msg['Subject']
173 173 user = msg['From']
174 174 if not subject and not user:
175 175 # Not an email, restore parsed headers if any
176 176 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
177 177
178 178 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
179 179 # should try to parse msg['Date']
180 180 date = None
181 181 nodeid = None
182 182 branch = None
183 183 parents = []
184 184
185 185 if subject:
186 186 if subject.startswith('[PATCH'):
187 187 pend = subject.find(']')
188 188 if pend >= 0:
189 189 subject = subject[pend + 1:].lstrip()
190 190 subject = subject.replace('\n\t', ' ')
191 191 ui.debug('Subject: %s\n' % subject)
192 192 if user:
193 193 ui.debug('From: %s\n' % user)
194 194 diffs_seen = 0
195 195 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
196 196 message = ''
197 197 for part in msg.walk():
198 198 content_type = part.get_content_type()
199 199 ui.debug('Content-Type: %s\n' % content_type)
200 200 if content_type not in ok_types:
201 201 continue
202 202 payload = part.get_payload(decode=True)
203 203 m = diffre.search(payload)
204 204 if m:
205 205 hgpatch = False
206 206 hgpatchheader = False
207 207 ignoretext = False
208 208
209 209 ui.debug('found patch at byte %d\n' % m.start(0))
210 210 diffs_seen += 1
211 211 cfp = cStringIO.StringIO()
212 212 for line in payload[:m.start(0)].splitlines():
213 213 if line.startswith('# HG changeset patch') and not hgpatch:
214 214 ui.debug('patch generated by hg export\n')
215 215 hgpatch = True
216 216 hgpatchheader = True
217 217 # drop earlier commit message content
218 218 cfp.seek(0)
219 219 cfp.truncate()
220 220 subject = None
221 221 elif hgpatchheader:
222 222 if line.startswith('# User '):
223 223 user = line[7:]
224 224 ui.debug('From: %s\n' % user)
225 225 elif line.startswith("# Date "):
226 226 date = line[7:]
227 227 elif line.startswith("# Branch "):
228 228 branch = line[9:]
229 229 elif line.startswith("# Node ID "):
230 230 nodeid = line[10:]
231 231 elif line.startswith("# Parent "):
232 232 parents.append(line[10:])
233 233 elif not line.startswith("# "):
234 234 hgpatchheader = False
235 235 elif line == '---' and gitsendmail:
236 236 ignoretext = True
237 237 if not hgpatchheader and not ignoretext:
238 238 cfp.write(line)
239 239 cfp.write('\n')
240 240 message = cfp.getvalue()
241 241 if tmpfp:
242 242 tmpfp.write(payload)
243 243 if not payload.endswith('\n'):
244 244 tmpfp.write('\n')
245 245 elif not diffs_seen and message and content_type == 'text/plain':
246 246 message += '\n' + payload
247 247 except:
248 248 tmpfp.close()
249 249 os.unlink(tmpname)
250 250 raise
251 251
252 252 if subject and not message.startswith(subject):
253 253 message = '%s\n%s' % (subject, message)
254 254 tmpfp.close()
255 255 if not diffs_seen:
256 256 os.unlink(tmpname)
257 257 return None, message, user, date, branch, None, None, None
258 258 p1 = parents and parents.pop(0) or None
259 259 p2 = parents and parents.pop(0) or None
260 260 return tmpname, message, user, date, branch, nodeid, p1, p2
261 261
262 262 class patchmeta(object):
263 263 """Patched file metadata
264 264
265 265 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
266 266 or COPY. 'path' is patched file path. 'oldpath' is set to the
267 267 origin file when 'op' is either COPY or RENAME, None otherwise. If
268 268 file mode is changed, 'mode' is a tuple (islink, isexec) where
269 269 'islink' is True if the file is a symlink and 'isexec' is True if
270 270 the file is executable. Otherwise, 'mode' is None.
271 271 """
272 272 def __init__(self, path):
273 273 self.path = path
274 274 self.oldpath = None
275 275 self.mode = None
276 276 self.op = 'MODIFY'
277 277 self.binary = False
278 278
279 279 def setmode(self, mode):
280 280 islink = mode & 020000
281 281 isexec = mode & 0100
282 282 self.mode = (islink, isexec)
283 283
284 284 def __repr__(self):
285 285 return "<patchmeta %s %r>" % (self.op, self.path)
286 286
287 287 def readgitpatch(lr):
288 288 """extract git-style metadata about patches from <patchname>"""
289 289
290 290 # Filter patch for git information
291 291 gp = None
292 292 gitpatches = []
293 293 for line in lr:
294 294 line = line.rstrip(' \r\n')
295 295 if line.startswith('diff --git'):
296 296 m = gitre.match(line)
297 297 if m:
298 298 if gp:
299 299 gitpatches.append(gp)
300 300 dst = m.group(2)
301 301 gp = patchmeta(dst)
302 302 elif gp:
303 303 if line.startswith('--- '):
304 304 gitpatches.append(gp)
305 305 gp = None
306 306 continue
307 307 if line.startswith('rename from '):
308 308 gp.op = 'RENAME'
309 309 gp.oldpath = line[12:]
310 310 elif line.startswith('rename to '):
311 311 gp.path = line[10:]
312 312 elif line.startswith('copy from '):
313 313 gp.op = 'COPY'
314 314 gp.oldpath = line[10:]
315 315 elif line.startswith('copy to '):
316 316 gp.path = line[8:]
317 317 elif line.startswith('deleted file'):
318 318 gp.op = 'DELETE'
319 319 elif line.startswith('new file mode '):
320 320 gp.op = 'ADD'
321 321 gp.setmode(int(line[-6:], 8))
322 322 elif line.startswith('new mode '):
323 323 gp.setmode(int(line[-6:], 8))
324 324 elif line.startswith('GIT binary patch'):
325 325 gp.binary = True
326 326 if gp:
327 327 gitpatches.append(gp)
328 328
329 329 return gitpatches
330 330
331 331 class linereader(object):
332 332 # simple class to allow pushing lines back into the input stream
333 333 def __init__(self, fp, textmode=False):
334 334 self.fp = fp
335 335 self.buf = []
336 336 self.textmode = textmode
337 337 self.eol = None
338 338
339 339 def push(self, line):
340 340 if line is not None:
341 341 self.buf.append(line)
342 342
343 343 def readline(self):
344 344 if self.buf:
345 345 l = self.buf[0]
346 346 del self.buf[0]
347 347 return l
348 348 l = self.fp.readline()
349 349 if not self.eol:
350 350 if l.endswith('\r\n'):
351 351 self.eol = '\r\n'
352 352 elif l.endswith('\n'):
353 353 self.eol = '\n'
354 354 if self.textmode and l.endswith('\r\n'):
355 355 l = l[:-2] + '\n'
356 356 return l
357 357
358 358 def __iter__(self):
359 359 while 1:
360 360 l = self.readline()
361 361 if not l:
362 362 break
363 363 yield l
364 364
365 365 class abstractbackend(object):
366 366 def __init__(self, ui):
367 367 self.ui = ui
368 368
369 369 def readlines(self, fname):
370 370 """Return target file lines, or its content as a single line
371 371 for symlinks.
372 372 """
373 373 raise NotImplementedError
374 374
375 375 def writelines(self, fname, lines, mode):
376 376 """Write lines to target file. mode is a (islink, isexec)
377 377 tuple, or None if there is no mode information.
378 378 """
379 379 raise NotImplementedError
380 380
381 381 def unlink(self, fname):
382 382 """Unlink target file."""
383 383 raise NotImplementedError
384 384
385 385 def writerej(self, fname, failed, total, lines):
386 386 """Write rejected lines for fname. total is the number of hunks
387 387 which failed to apply and total the total number of hunks for this
388 388 files.
389 389 """
390 390 pass
391 391
392 392 def copy(self, src, dst):
393 393 """Copy src file into dst file. Create intermediate directories if
394 394 necessary. Files are specified relatively to the patching base
395 395 directory.
396 396 """
397 397 raise NotImplementedError
398 398
399 399 def exists(self, fname):
400 400 raise NotImplementedError
401 401
402 402 def setmode(self, fname, islink, isexec):
403 403 """Change target file mode."""
404 404 raise NotImplementedError
405 405
406 406 class fsbackend(abstractbackend):
407 407 def __init__(self, ui, basedir):
408 408 super(fsbackend, self).__init__(ui)
409 409 self.opener = scmutil.opener(basedir)
410 410
411 411 def _join(self, f):
412 412 return os.path.join(self.opener.base, f)
413 413
414 414 def readlines(self, fname):
415 415 if os.path.islink(self._join(fname)):
416 416 return [os.readlink(self._join(fname))]
417 417 fp = self.opener(fname, 'r')
418 418 try:
419 419 return list(fp)
420 420 finally:
421 421 fp.close()
422 422
423 423 def writelines(self, fname, lines, mode):
424 424 if not mode:
425 425 # Preserve mode information
426 426 isexec, islink = False, False
427 427 try:
428 428 isexec = os.lstat(self._join(fname)).st_mode & 0100 != 0
429 429 islink = os.path.islink(self._join(fname))
430 430 except OSError, e:
431 431 if e.errno != errno.ENOENT:
432 432 raise
433 433 else:
434 434 islink, isexec = mode
435 435 if islink:
436 436 self.opener.symlink(''.join(lines), fname)
437 437 else:
438 438 self.opener(fname, 'w').writelines(lines)
439 439 if isexec:
440 440 util.setflags(self._join(fname), False, True)
441 441
442 442 def unlink(self, fname):
443 443 try:
444 444 util.unlinkpath(self._join(fname))
445 445 except OSError, inst:
446 446 if inst.errno != errno.ENOENT:
447 447 raise
448 448
449 449 def writerej(self, fname, failed, total, lines):
450 450 fname = fname + ".rej"
451 451 self.ui.warn(
452 452 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
453 453 (failed, total, fname))
454 454 fp = self.opener(fname, 'w')
455 455 fp.writelines(lines)
456 456 fp.close()
457 457
458 458 def copy(self, src, dst):
459 459 basedir = self.opener.base
460 460 abssrc, absdst = [scmutil.canonpath(basedir, basedir, x)
461 461 for x in [src, dst]]
462 462 if os.path.lexists(absdst):
463 463 raise util.Abort(_("cannot create %s: destination already exists")
464 464 % dst)
465 465 dstdir = os.path.dirname(absdst)
466 466 if dstdir and not os.path.isdir(dstdir):
467 467 try:
468 468 os.makedirs(dstdir)
469 469 except IOError:
470 470 raise util.Abort(
471 471 _("cannot create %s: unable to create destination directory")
472 472 % dst)
473 473 util.copyfile(abssrc, absdst)
474 474
475 475 def exists(self, fname):
476 476 return os.path.lexists(self._join(fname))
477 477
478 478 def setmode(self, fname, islink, isexec):
479 479 util.setflags(self._join(fname), islink, isexec)
480 480
481 481 class workingbackend(fsbackend):
482 482 def __init__(self, ui, repo, similarity):
483 483 super(workingbackend, self).__init__(ui, repo.root)
484 484 self.repo = repo
485 485 self.similarity = similarity
486 486 self.removed = set()
487 487 self.changed = set()
488 488 self.copied = []
489 489
490 490 def writelines(self, fname, lines, mode):
491 491 super(workingbackend, self).writelines(fname, lines, mode)
492 492 self.changed.add(fname)
493 493
494 494 def unlink(self, fname):
495 495 super(workingbackend, self).unlink(fname)
496 496 self.removed.add(fname)
497 497 self.changed.add(fname)
498 498
499 499 def copy(self, src, dst):
500 500 super(workingbackend, self).copy(src, dst)
501 501 self.copied.append((src, dst))
502 502 self.changed.add(dst)
503 503
504 504 def setmode(self, fname, islink, isexec):
505 505 super(workingbackend, self).setmode(fname, islink, isexec)
506 506 self.changed.add(fname)
507 507
508 508 def close(self):
509 509 wctx = self.repo[None]
510 510 addremoved = set(self.changed)
511 511 for src, dst in self.copied:
512 512 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
513 513 addremoved.discard(src)
514 514 if (not self.similarity) and self.removed:
515 515 wctx.remove(sorted(self.removed))
516 516 if addremoved:
517 517 cwd = self.repo.getcwd()
518 518 if cwd:
519 519 addremoved = [util.pathto(self.repo.root, cwd, f)
520 520 for f in addremoved]
521 521 scmutil.addremove(self.repo, addremoved, similarity=self.similarity)
522 522 return sorted(self.changed)
523 523
524 524 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
525 525 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
526 526 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
527 527 eolmodes = ['strict', 'crlf', 'lf', 'auto']
528 528
529 529 class patchfile(object):
530 530 def __init__(self, ui, fname, backend, mode, missing=False,
531 531 eolmode='strict'):
532 532 self.fname = fname
533 533 self.eolmode = eolmode
534 534 self.eol = None
535 535 self.backend = backend
536 536 self.ui = ui
537 537 self.lines = []
538 538 self.exists = False
539 539 self.missing = missing
540 540 self.mode = mode
541 541 if not missing:
542 542 try:
543 543 self.lines = self.backend.readlines(fname)
544 544 if self.lines:
545 545 # Normalize line endings
546 546 if self.lines[0].endswith('\r\n'):
547 547 self.eol = '\r\n'
548 548 elif self.lines[0].endswith('\n'):
549 549 self.eol = '\n'
550 550 if eolmode != 'strict':
551 551 nlines = []
552 552 for l in self.lines:
553 553 if l.endswith('\r\n'):
554 554 l = l[:-2] + '\n'
555 555 nlines.append(l)
556 556 self.lines = nlines
557 557 self.exists = True
558 558 except IOError:
559 559 pass
560 560 else:
561 561 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
562 562
563 563 self.hash = {}
564 564 self.dirty = 0
565 565 self.offset = 0
566 566 self.skew = 0
567 567 self.rej = []
568 568 self.fileprinted = False
569 569 self.printfile(False)
570 570 self.hunks = 0
571 571
572 572 def writelines(self, fname, lines, mode):
573 573 if self.eolmode == 'auto':
574 574 eol = self.eol
575 575 elif self.eolmode == 'crlf':
576 576 eol = '\r\n'
577 577 else:
578 578 eol = '\n'
579 579
580 580 if self.eolmode != 'strict' and eol and eol != '\n':
581 581 rawlines = []
582 582 for l in lines:
583 583 if l and l[-1] == '\n':
584 584 l = l[:-1] + eol
585 585 rawlines.append(l)
586 586 lines = rawlines
587 587
588 588 self.backend.writelines(fname, lines, mode)
589 589
590 590 def printfile(self, warn):
591 591 if self.fileprinted:
592 592 return
593 593 if warn or self.ui.verbose:
594 594 self.fileprinted = True
595 595 s = _("patching file %s\n") % self.fname
596 596 if warn:
597 597 self.ui.warn(s)
598 598 else:
599 599 self.ui.note(s)
600 600
601 601
602 602 def findlines(self, l, linenum):
603 603 # looks through the hash and finds candidate lines. The
604 604 # result is a list of line numbers sorted based on distance
605 605 # from linenum
606 606
607 607 cand = self.hash.get(l, [])
608 608 if len(cand) > 1:
609 609 # resort our list of potentials forward then back.
610 610 cand.sort(key=lambda x: abs(x - linenum))
611 611 return cand
612 612
613 613 def write_rej(self):
614 614 # our rejects are a little different from patch(1). This always
615 615 # creates rejects in the same form as the original patch. A file
616 616 # header is inserted so that you can run the reject through patch again
617 617 # without having to type the filename.
618 618 if not self.rej:
619 619 return
620 620 base = os.path.basename(self.fname)
621 621 lines = ["--- %s\n+++ %s\n" % (base, base)]
622 622 for x in self.rej:
623 623 for l in x.hunk:
624 624 lines.append(l)
625 625 if l[-1] != '\n':
626 626 lines.append("\n\ No newline at end of file\n")
627 627 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
628 628
629 629 def apply(self, h):
630 630 if not h.complete():
631 631 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
632 632 (h.number, h.desc, len(h.a), h.lena, len(h.b),
633 633 h.lenb))
634 634
635 635 self.hunks += 1
636 636
637 637 if self.missing:
638 638 self.rej.append(h)
639 639 return -1
640 640
641 641 if self.exists and h.createfile():
642 642 self.ui.warn(_("file %s already exists\n") % self.fname)
643 643 self.rej.append(h)
644 644 return -1
645 645
646 646 if isinstance(h, binhunk):
647 647 if h.rmfile():
648 648 self.backend.unlink(self.fname)
649 649 else:
650 650 self.lines[:] = h.new()
651 651 self.offset += len(h.new())
652 652 self.dirty = True
653 653 return 0
654 654
655 655 horig = h
656 656 if (self.eolmode in ('crlf', 'lf')
657 657 or self.eolmode == 'auto' and self.eol):
658 658 # If new eols are going to be normalized, then normalize
659 659 # hunk data before patching. Otherwise, preserve input
660 660 # line-endings.
661 661 h = h.getnormalized()
662 662
663 663 # fast case first, no offsets, no fuzz
664 664 old = h.old()
665 665 # patch starts counting at 1 unless we are adding the file
666 666 if h.starta == 0:
667 667 start = 0
668 668 else:
669 669 start = h.starta + self.offset - 1
670 670 orig_start = start
671 671 # if there's skew we want to emit the "(offset %d lines)" even
672 672 # when the hunk cleanly applies at start + skew, so skip the
673 673 # fast case code
674 674 if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0:
675 675 if h.rmfile():
676 676 self.backend.unlink(self.fname)
677 677 else:
678 678 self.lines[start : start + h.lena] = h.new()
679 679 self.offset += h.lenb - h.lena
680 680 self.dirty = True
681 681 return 0
682 682
683 683 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
684 684 self.hash = {}
685 685 for x, s in enumerate(self.lines):
686 686 self.hash.setdefault(s, []).append(x)
687 687 if h.hunk[-1][0] != ' ':
688 688 # if the hunk tried to put something at the bottom of the file
689 689 # override the start line and use eof here
690 690 search_start = len(self.lines)
691 691 else:
692 692 search_start = orig_start + self.skew
693 693
694 694 for fuzzlen in xrange(3):
695 695 for toponly in [True, False]:
696 696 old = h.old(fuzzlen, toponly)
697 697
698 698 cand = self.findlines(old[0][1:], search_start)
699 699 for l in cand:
700 700 if diffhelpers.testhunk(old, self.lines, l) == 0:
701 701 newlines = h.new(fuzzlen, toponly)
702 702 self.lines[l : l + len(old)] = newlines
703 703 self.offset += len(newlines) - len(old)
704 704 self.skew = l - orig_start
705 705 self.dirty = True
706 706 offset = l - orig_start - fuzzlen
707 707 if fuzzlen:
708 708 msg = _("Hunk #%d succeeded at %d "
709 709 "with fuzz %d "
710 710 "(offset %d lines).\n")
711 711 self.printfile(True)
712 712 self.ui.warn(msg %
713 713 (h.number, l + 1, fuzzlen, offset))
714 714 else:
715 715 msg = _("Hunk #%d succeeded at %d "
716 716 "(offset %d lines).\n")
717 717 self.ui.note(msg % (h.number, l + 1, offset))
718 718 return fuzzlen
719 719 self.printfile(True)
720 720 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
721 721 self.rej.append(horig)
722 722 return -1
723 723
724 724 def close(self):
725 725 if self.dirty:
726 726 self.writelines(self.fname, self.lines, self.mode)
727 727 self.write_rej()
728 728 return len(self.rej)
729 729
730 730 class hunk(object):
731 731 def __init__(self, desc, num, lr, context, create=False, remove=False):
732 732 self.number = num
733 733 self.desc = desc
734 734 self.hunk = [desc]
735 735 self.a = []
736 736 self.b = []
737 737 self.starta = self.lena = None
738 738 self.startb = self.lenb = None
739 739 if lr is not None:
740 740 if context:
741 741 self.read_context_hunk(lr)
742 742 else:
743 743 self.read_unified_hunk(lr)
744 744 self.create = create
745 745 self.remove = remove and not create
746 746
747 747 def getnormalized(self):
748 748 """Return a copy with line endings normalized to LF."""
749 749
750 750 def normalize(lines):
751 751 nlines = []
752 752 for line in lines:
753 753 if line.endswith('\r\n'):
754 754 line = line[:-2] + '\n'
755 755 nlines.append(line)
756 756 return nlines
757 757
758 758 # Dummy object, it is rebuilt manually
759 759 nh = hunk(self.desc, self.number, None, None, False, False)
760 760 nh.number = self.number
761 761 nh.desc = self.desc
762 762 nh.hunk = self.hunk
763 763 nh.a = normalize(self.a)
764 764 nh.b = normalize(self.b)
765 765 nh.starta = self.starta
766 766 nh.startb = self.startb
767 767 nh.lena = self.lena
768 768 nh.lenb = self.lenb
769 769 nh.create = self.create
770 770 nh.remove = self.remove
771 771 return nh
772 772
773 773 def read_unified_hunk(self, lr):
774 774 m = unidesc.match(self.desc)
775 775 if not m:
776 776 raise PatchError(_("bad hunk #%d") % self.number)
777 777 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
778 778 if self.lena is None:
779 779 self.lena = 1
780 780 else:
781 781 self.lena = int(self.lena)
782 782 if self.lenb is None:
783 783 self.lenb = 1
784 784 else:
785 785 self.lenb = int(self.lenb)
786 786 self.starta = int(self.starta)
787 787 self.startb = int(self.startb)
788 788 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
789 789 # if we hit eof before finishing out the hunk, the last line will
790 790 # be zero length. Lets try to fix it up.
791 791 while len(self.hunk[-1]) == 0:
792 792 del self.hunk[-1]
793 793 del self.a[-1]
794 794 del self.b[-1]
795 795 self.lena -= 1
796 796 self.lenb -= 1
797 797 self._fixnewline(lr)
798 798
799 799 def read_context_hunk(self, lr):
800 800 self.desc = lr.readline()
801 801 m = contextdesc.match(self.desc)
802 802 if not m:
803 803 raise PatchError(_("bad hunk #%d") % self.number)
804 804 foo, self.starta, foo2, aend, foo3 = m.groups()
805 805 self.starta = int(self.starta)
806 806 if aend is None:
807 807 aend = self.starta
808 808 self.lena = int(aend) - self.starta
809 809 if self.starta:
810 810 self.lena += 1
811 811 for x in xrange(self.lena):
812 812 l = lr.readline()
813 813 if l.startswith('---'):
814 814 # lines addition, old block is empty
815 815 lr.push(l)
816 816 break
817 817 s = l[2:]
818 818 if l.startswith('- ') or l.startswith('! '):
819 819 u = '-' + s
820 820 elif l.startswith(' '):
821 821 u = ' ' + s
822 822 else:
823 823 raise PatchError(_("bad hunk #%d old text line %d") %
824 824 (self.number, x))
825 825 self.a.append(u)
826 826 self.hunk.append(u)
827 827
828 828 l = lr.readline()
829 829 if l.startswith('\ '):
830 830 s = self.a[-1][:-1]
831 831 self.a[-1] = s
832 832 self.hunk[-1] = s
833 833 l = lr.readline()
834 834 m = contextdesc.match(l)
835 835 if not m:
836 836 raise PatchError(_("bad hunk #%d") % self.number)
837 837 foo, self.startb, foo2, bend, foo3 = m.groups()
838 838 self.startb = int(self.startb)
839 839 if bend is None:
840 840 bend = self.startb
841 841 self.lenb = int(bend) - self.startb
842 842 if self.startb:
843 843 self.lenb += 1
844 844 hunki = 1
845 845 for x in xrange(self.lenb):
846 846 l = lr.readline()
847 847 if l.startswith('\ '):
848 848 # XXX: the only way to hit this is with an invalid line range.
849 849 # The no-eol marker is not counted in the line range, but I
850 850 # guess there are diff(1) out there which behave differently.
851 851 s = self.b[-1][:-1]
852 852 self.b[-1] = s
853 853 self.hunk[hunki - 1] = s
854 854 continue
855 855 if not l:
856 856 # line deletions, new block is empty and we hit EOF
857 857 lr.push(l)
858 858 break
859 859 s = l[2:]
860 860 if l.startswith('+ ') or l.startswith('! '):
861 861 u = '+' + s
862 862 elif l.startswith(' '):
863 863 u = ' ' + s
864 864 elif len(self.b) == 0:
865 865 # line deletions, new block is empty
866 866 lr.push(l)
867 867 break
868 868 else:
869 869 raise PatchError(_("bad hunk #%d old text line %d") %
870 870 (self.number, x))
871 871 self.b.append(s)
872 872 while True:
873 873 if hunki >= len(self.hunk):
874 874 h = ""
875 875 else:
876 876 h = self.hunk[hunki]
877 877 hunki += 1
878 878 if h == u:
879 879 break
880 880 elif h.startswith('-'):
881 881 continue
882 882 else:
883 883 self.hunk.insert(hunki - 1, u)
884 884 break
885 885
886 886 if not self.a:
887 887 # this happens when lines were only added to the hunk
888 888 for x in self.hunk:
889 889 if x.startswith('-') or x.startswith(' '):
890 890 self.a.append(x)
891 891 if not self.b:
892 892 # this happens when lines were only deleted from the hunk
893 893 for x in self.hunk:
894 894 if x.startswith('+') or x.startswith(' '):
895 895 self.b.append(x[1:])
896 896 # @@ -start,len +start,len @@
897 897 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
898 898 self.startb, self.lenb)
899 899 self.hunk[0] = self.desc
900 900 self._fixnewline(lr)
901 901
902 902 def _fixnewline(self, lr):
903 903 l = lr.readline()
904 904 if l.startswith('\ '):
905 905 diffhelpers.fix_newline(self.hunk, self.a, self.b)
906 906 else:
907 907 lr.push(l)
908 908
909 909 def complete(self):
910 910 return len(self.a) == self.lena and len(self.b) == self.lenb
911 911
912 912 def createfile(self):
913 913 return self.starta == 0 and self.lena == 0 and self.create
914 914
915 915 def rmfile(self):
916 916 return self.startb == 0 and self.lenb == 0 and self.remove
917 917
918 918 def fuzzit(self, l, fuzz, toponly):
919 919 # this removes context lines from the top and bottom of list 'l'. It
920 920 # checks the hunk to make sure only context lines are removed, and then
921 921 # returns a new shortened list of lines.
922 922 fuzz = min(fuzz, len(l)-1)
923 923 if fuzz:
924 924 top = 0
925 925 bot = 0
926 926 hlen = len(self.hunk)
927 927 for x in xrange(hlen - 1):
928 928 # the hunk starts with the @@ line, so use x+1
929 929 if self.hunk[x + 1][0] == ' ':
930 930 top += 1
931 931 else:
932 932 break
933 933 if not toponly:
934 934 for x in xrange(hlen - 1):
935 935 if self.hunk[hlen - bot - 1][0] == ' ':
936 936 bot += 1
937 937 else:
938 938 break
939 939
940 940 # top and bot now count context in the hunk
941 941 # adjust them if either one is short
942 942 context = max(top, bot, 3)
943 943 if bot < context:
944 944 bot = max(0, fuzz - (context - bot))
945 945 else:
946 946 bot = min(fuzz, bot)
947 947 if top < context:
948 948 top = max(0, fuzz - (context - top))
949 949 else:
950 950 top = min(fuzz, top)
951 951
952 952 return l[top:len(l)-bot]
953 953 return l
954 954
955 955 def old(self, fuzz=0, toponly=False):
956 956 return self.fuzzit(self.a, fuzz, toponly)
957 957
958 958 def new(self, fuzz=0, toponly=False):
959 959 return self.fuzzit(self.b, fuzz, toponly)
960 960
961 961 class binhunk:
962 962 'A binary patch file. Only understands literals so far.'
963 963 def __init__(self, gitpatch):
964 964 self.gitpatch = gitpatch
965 965 self.text = None
966 966 self.hunk = ['GIT binary patch\n']
967 967
968 968 def createfile(self):
969 969 return self.gitpatch.op == 'ADD'
970 970
971 971 def rmfile(self):
972 972 return self.gitpatch.op == 'DELETE'
973 973
974 974 def complete(self):
975 975 return self.text is not None
976 976
977 977 def new(self):
978 978 return [self.text]
979 979
980 980 def extract(self, lr):
981 981 line = lr.readline()
982 982 self.hunk.append(line)
983 983 while line and not line.startswith('literal '):
984 984 line = lr.readline()
985 985 self.hunk.append(line)
986 986 if not line:
987 987 raise PatchError(_('could not extract binary patch'))
988 988 size = int(line[8:].rstrip())
989 989 dec = []
990 990 line = lr.readline()
991 991 self.hunk.append(line)
992 992 while len(line) > 1:
993 993 l = line[0]
994 994 if l <= 'Z' and l >= 'A':
995 995 l = ord(l) - ord('A') + 1
996 996 else:
997 997 l = ord(l) - ord('a') + 27
998 998 dec.append(base85.b85decode(line[1:-1])[:l])
999 999 line = lr.readline()
1000 1000 self.hunk.append(line)
1001 1001 text = zlib.decompress(''.join(dec))
1002 1002 if len(text) != size:
1003 1003 raise PatchError(_('binary patch is %d bytes, not %d') %
1004 1004 len(text), size)
1005 1005 self.text = text
1006 1006
1007 1007 def parsefilename(str):
1008 1008 # --- filename \t|space stuff
1009 1009 s = str[4:].rstrip('\r\n')
1010 1010 i = s.find('\t')
1011 1011 if i < 0:
1012 1012 i = s.find(' ')
1013 1013 if i < 0:
1014 1014 return s
1015 1015 return s[:i]
1016 1016
1017 1017 def pathstrip(path, strip):
1018 1018 pathlen = len(path)
1019 1019 i = 0
1020 1020 if strip == 0:
1021 1021 return '', path.rstrip()
1022 1022 count = strip
1023 1023 while count > 0:
1024 1024 i = path.find('/', i)
1025 1025 if i == -1:
1026 1026 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1027 1027 (count, strip, path))
1028 1028 i += 1
1029 1029 # consume '//' in the path
1030 1030 while i < pathlen - 1 and path[i] == '/':
1031 1031 i += 1
1032 1032 count -= 1
1033 1033 return path[:i].lstrip(), path[i:].rstrip()
1034 1034
1035 1035 def selectfile(backend, afile_orig, bfile_orig, hunk, strip):
1036 1036 nulla = afile_orig == "/dev/null"
1037 1037 nullb = bfile_orig == "/dev/null"
1038 1038 abase, afile = pathstrip(afile_orig, strip)
1039 1039 gooda = not nulla and backend.exists(afile)
1040 1040 bbase, bfile = pathstrip(bfile_orig, strip)
1041 1041 if afile == bfile:
1042 1042 goodb = gooda
1043 1043 else:
1044 1044 goodb = not nullb and backend.exists(bfile)
1045 1045 createfunc = hunk.createfile
1046 1046 missing = not goodb and not gooda and not createfunc()
1047 1047
1048 1048 # some diff programs apparently produce patches where the afile is
1049 1049 # not /dev/null, but afile starts with bfile
1050 1050 abasedir = afile[:afile.rfind('/') + 1]
1051 1051 bbasedir = bfile[:bfile.rfind('/') + 1]
1052 1052 if missing and abasedir == bbasedir and afile.startswith(bfile):
1053 1053 # this isn't very pretty
1054 1054 hunk.create = True
1055 1055 if createfunc():
1056 1056 missing = False
1057 1057 else:
1058 1058 hunk.create = False
1059 1059
1060 1060 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1061 1061 # diff is between a file and its backup. In this case, the original
1062 1062 # file should be patched (see original mpatch code).
1063 1063 isbackup = (abase == bbase and bfile.startswith(afile))
1064 1064 fname = None
1065 1065 if not missing:
1066 1066 if gooda and goodb:
1067 1067 fname = isbackup and afile or bfile
1068 1068 elif gooda:
1069 1069 fname = afile
1070 1070
1071 1071 if not fname:
1072 1072 if not nullb:
1073 1073 fname = isbackup and afile or bfile
1074 1074 elif not nulla:
1075 1075 fname = afile
1076 1076 else:
1077 1077 raise PatchError(_("undefined source and destination files"))
1078 1078
1079 1079 return fname, missing
1080 1080
1081 1081 def scangitpatch(lr, firstline):
1082 1082 """
1083 1083 Git patches can emit:
1084 1084 - rename a to b
1085 1085 - change b
1086 1086 - copy a to c
1087 1087 - change c
1088 1088
1089 1089 We cannot apply this sequence as-is, the renamed 'a' could not be
1090 1090 found for it would have been renamed already. And we cannot copy
1091 1091 from 'b' instead because 'b' would have been changed already. So
1092 1092 we scan the git patch for copy and rename commands so we can
1093 1093 perform the copies ahead of time.
1094 1094 """
1095 1095 pos = 0
1096 1096 try:
1097 1097 pos = lr.fp.tell()
1098 1098 fp = lr.fp
1099 1099 except IOError:
1100 1100 fp = cStringIO.StringIO(lr.fp.read())
1101 1101 gitlr = linereader(fp, lr.textmode)
1102 1102 gitlr.push(firstline)
1103 1103 gitpatches = readgitpatch(gitlr)
1104 1104 fp.seek(pos)
1105 1105 return gitpatches
1106 1106
1107 1107 def iterhunks(fp):
1108 1108 """Read a patch and yield the following events:
1109 1109 - ("file", afile, bfile, firsthunk): select a new target file.
1110 1110 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1111 1111 "file" event.
1112 1112 - ("git", gitchanges): current diff is in git format, gitchanges
1113 1113 maps filenames to gitpatch records. Unique event.
1114 1114 """
1115 1115 changed = {}
1116 1116 afile = ""
1117 1117 bfile = ""
1118 1118 state = None
1119 1119 hunknum = 0
1120 1120 emitfile = newfile = False
1121 1121 git = False
1122 1122
1123 1123 # our states
1124 1124 BFILE = 1
1125 1125 context = None
1126 1126 lr = linereader(fp)
1127 1127
1128 1128 while True:
1129 1129 x = lr.readline()
1130 1130 if not x:
1131 1131 break
1132 1132 if (state == BFILE and ((not context and x[0] == '@') or
1133 1133 ((context is not False) and x.startswith('***************')))):
1134 1134 if context is None and x.startswith('***************'):
1135 1135 context = True
1136 1136 gpatch = changed.get(bfile)
1137 1137 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
1138 1138 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
1139 1139 h = hunk(x, hunknum + 1, lr, context, create, remove)
1140 1140 hunknum += 1
1141 1141 if emitfile:
1142 1142 emitfile = False
1143 1143 yield 'file', (afile, bfile, h, gpatch and gpatch.mode or None)
1144 1144 yield 'hunk', h
1145 1145 elif state == BFILE and x.startswith('GIT binary patch'):
1146 1146 gpatch = changed[bfile]
1147 1147 h = binhunk(gpatch)
1148 1148 hunknum += 1
1149 1149 if emitfile:
1150 1150 emitfile = False
1151 1151 yield 'file', ('a/' + afile, 'b/' + bfile, h,
1152 1152 gpatch and gpatch.mode or None)
1153 1153 h.extract(lr)
1154 1154 yield 'hunk', h
1155 1155 elif x.startswith('diff --git'):
1156 1156 # check for git diff, scanning the whole patch file if needed
1157 1157 m = gitre.match(x)
1158 1158 if m:
1159 1159 afile, bfile = m.group(1, 2)
1160 1160 if not git:
1161 1161 git = True
1162 1162 gitpatches = scangitpatch(lr, x)
1163 1163 yield 'git', gitpatches
1164 1164 for gp in gitpatches:
1165 1165 changed[gp.path] = gp
1166 1166 # else error?
1167 1167 # copy/rename + modify should modify target, not source
1168 1168 gp = changed.get(bfile)
1169 1169 if gp and (gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD')
1170 1170 or gp.mode):
1171 1171 afile = bfile
1172 1172 newfile = True
1173 1173 elif x.startswith('---'):
1174 1174 # check for a unified diff
1175 1175 l2 = lr.readline()
1176 1176 if not l2.startswith('+++'):
1177 1177 lr.push(l2)
1178 1178 continue
1179 1179 newfile = True
1180 1180 context = False
1181 1181 afile = parsefilename(x)
1182 1182 bfile = parsefilename(l2)
1183 1183 elif x.startswith('***'):
1184 1184 # check for a context diff
1185 1185 l2 = lr.readline()
1186 1186 if not l2.startswith('---'):
1187 1187 lr.push(l2)
1188 1188 continue
1189 1189 l3 = lr.readline()
1190 1190 lr.push(l3)
1191 1191 if not l3.startswith("***************"):
1192 1192 lr.push(l2)
1193 1193 continue
1194 1194 newfile = True
1195 1195 context = True
1196 1196 afile = parsefilename(x)
1197 1197 bfile = parsefilename(l2)
1198 1198
1199 1199 if newfile:
1200 1200 newfile = False
1201 1201 emitfile = True
1202 1202 state = BFILE
1203 1203 hunknum = 0
1204 1204
1205 1205 def applydiff(ui, fp, changed, backend, strip=1, eolmode='strict'):
1206 1206 """Reads a patch from fp and tries to apply it.
1207 1207
1208 1208 The dict 'changed' is filled in with all of the filenames changed
1209 1209 by the patch. Returns 0 for a clean patch, -1 if any rejects were
1210 1210 found and 1 if there was any fuzz.
1211 1211
1212 1212 If 'eolmode' is 'strict', the patch content and patched file are
1213 1213 read in binary mode. Otherwise, line endings are ignored when
1214 1214 patching then normalized according to 'eolmode'.
1215 1215 """
1216 1216 return _applydiff(ui, fp, patchfile, backend, changed, strip=strip,
1217 1217 eolmode=eolmode)
1218 1218
1219 1219 def _applydiff(ui, fp, patcher, backend, changed, strip=1, eolmode='strict'):
1220 1220 rejects = 0
1221 1221 err = 0
1222 1222 current_file = None
1223 1223
1224 1224 for state, values in iterhunks(fp):
1225 1225 if state == 'hunk':
1226 1226 if not current_file:
1227 1227 continue
1228 1228 ret = current_file.apply(values)
1229 1229 if ret >= 0:
1230 1230 changed.setdefault(current_file.fname, None)
1231 1231 if ret > 0:
1232 1232 err = 1
1233 1233 elif state == 'file':
1234 1234 if current_file:
1235 1235 rejects += current_file.close()
1236 1236 afile, bfile, first_hunk, mode = values
1237 1237 try:
1238 1238 current_file, missing = selectfile(backend, afile, bfile,
1239 1239 first_hunk, strip)
1240 1240 current_file = patcher(ui, current_file, backend, mode,
1241 1241 missing=missing, eolmode=eolmode)
1242 1242 except PatchError, inst:
1243 1243 ui.warn(str(inst) + '\n')
1244 1244 current_file = None
1245 1245 rejects += 1
1246 1246 continue
1247 1247 elif state == 'git':
1248 1248 for gp in values:
1249 1249 gp.path = pathstrip(gp.path, strip - 1)[1]
1250 1250 if gp.oldpath:
1251 1251 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1252 1252 if gp.op in ('COPY', 'RENAME'):
1253 1253 backend.copy(gp.oldpath, gp.path)
1254 1254 changed[gp.path] = gp
1255 1255 else:
1256 1256 raise util.Abort(_('unsupported parser state: %s') % state)
1257 1257
1258 1258 if current_file:
1259 1259 rejects += current_file.close()
1260 1260
1261 1261 # Handle mode changes without hunk
1262 1262 removed = set()
1263 1263 for gp in changed.itervalues():
1264 1264 if not gp:
1265 1265 continue
1266 1266 if gp.op == 'DELETE':
1267 1267 removed.add(gp.path)
1268 1268 continue
1269 1269 if gp.op == 'RENAME':
1270 1270 removed.add(gp.oldpath)
1271 1271 if gp.mode:
1272 1272 if gp.op == 'ADD' and not backend.exists(gp.path):
1273 1273 # Added files without content have no hunk and must be created
1274 1274 backend.writelines(gp.path, [], gp.mode)
1275 1275 else:
1276 1276 backend.setmode(gp.path, gp.mode[0], gp.mode[1])
1277 1277 for path in sorted(removed):
1278 1278 backend.unlink(path)
1279 1279
1280 1280 if rejects:
1281 1281 return -1
1282 1282 return err
1283 1283
1284 def _externalpatch(ui, repo, patcher, patchname, strip, cwd, files,
1284 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1285 1285 similarity):
1286 1286 """use <patcher> to apply <patchname> to the working directory.
1287 1287 returns whether patch was applied with fuzz factor."""
1288 1288
1289 1289 fuzz = False
1290 1290 args = []
1291 cwd = repo.root
1291 1292 if cwd:
1292 1293 args.append('-d %s' % util.shellquote(cwd))
1293 1294 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1294 1295 util.shellquote(patchname)))
1295 1296 try:
1296 1297 for line in fp:
1297 1298 line = line.rstrip()
1298 1299 ui.note(line + '\n')
1299 1300 if line.startswith('patching file '):
1300 1301 pf = util.parsepatchoutput(line)
1301 1302 printed_file = False
1302 1303 files.setdefault(pf, None)
1303 1304 elif line.find('with fuzz') >= 0:
1304 1305 fuzz = True
1305 1306 if not printed_file:
1306 1307 ui.warn(pf + '\n')
1307 1308 printed_file = True
1308 1309 ui.warn(line + '\n')
1309 1310 elif line.find('saving rejects to file') >= 0:
1310 1311 ui.warn(line + '\n')
1311 1312 elif line.find('FAILED') >= 0:
1312 1313 if not printed_file:
1313 1314 ui.warn(pf + '\n')
1314 1315 printed_file = True
1315 1316 ui.warn(line + '\n')
1316 1317 finally:
1317 1318 if files:
1318 1319 cfiles = list(files)
1319 1320 cwd = repo.getcwd()
1320 1321 if cwd:
1321 1322 cfiles = [util.pathto(repo.root, cwd, f)
1322 1323 for f in cfile]
1323 1324 scmutil.addremove(repo, cfiles, similarity=similarity)
1324 1325 code = fp.close()
1325 1326 if code:
1326 1327 raise PatchError(_("patch command failed: %s") %
1327 1328 util.explainexit(code)[0])
1328 1329 return fuzz
1329 1330
1330 1331 def internalpatch(ui, repo, patchobj, strip, files=None, eolmode='strict',
1331 1332 similarity=0):
1332 1333 """use builtin patch to apply <patchobj> to the working directory.
1333 1334 returns whether patch was applied with fuzz factor."""
1334 1335
1335 1336 if files is None:
1336 1337 files = {}
1337 1338 if eolmode is None:
1338 1339 eolmode = ui.config('patch', 'eol', 'strict')
1339 1340 if eolmode.lower() not in eolmodes:
1340 1341 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1341 1342 eolmode = eolmode.lower()
1342 1343
1343 1344 backend = workingbackend(ui, repo, similarity)
1344 1345 try:
1345 1346 fp = open(patchobj, 'rb')
1346 1347 except TypeError:
1347 1348 fp = patchobj
1348 1349 try:
1349 1350 ret = applydiff(ui, fp, files, backend, strip=strip, eolmode=eolmode)
1350 1351 finally:
1351 1352 if fp != patchobj:
1352 1353 fp.close()
1353 1354 files.update(dict.fromkeys(backend.close()))
1354 1355 if ret < 0:
1355 1356 raise PatchError(_('patch failed to apply'))
1356 1357 return ret > 0
1357 1358
1358 def patch(ui, repo, patchname, strip=1, cwd=None, files=None, eolmode='strict',
1359 def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
1359 1360 similarity=0):
1360 1361 """Apply <patchname> to the working directory.
1361 1362
1362 1363 'eolmode' specifies how end of lines should be handled. It can be:
1363 1364 - 'strict': inputs are read in binary mode, EOLs are preserved
1364 1365 - 'crlf': EOLs are ignored when patching and reset to CRLF
1365 1366 - 'lf': EOLs are ignored when patching and reset to LF
1366 1367 - None: get it from user settings, default to 'strict'
1367 1368 'eolmode' is ignored when using an external patcher program.
1368 1369
1369 1370 Returns whether patch was applied with fuzz factor.
1370 1371 """
1371 1372 patcher = ui.config('ui', 'patch')
1372 1373 if files is None:
1373 1374 files = {}
1374 1375 try:
1375 1376 if patcher:
1376 1377 return _externalpatch(ui, repo, patcher, patchname, strip,
1377 cwd, files, similarity)
1378 files, similarity)
1378 1379 return internalpatch(ui, repo, patchname, strip, files, eolmode,
1379 1380 similarity)
1380 1381 except PatchError, err:
1381 1382 raise util.Abort(str(err))
1382 1383
1383 1384 def changedfiles(ui, repo, patchpath, strip=1):
1384 1385 backend = fsbackend(ui, repo.root)
1385 1386 fp = open(patchpath, 'rb')
1386 1387 try:
1387 1388 changed = set()
1388 1389 for state, values in iterhunks(fp):
1389 1390 if state == 'hunk':
1390 1391 continue
1391 1392 elif state == 'file':
1392 1393 afile, bfile, first_hunk, mode = values
1393 1394 current_file, missing = selectfile(backend, afile, bfile,
1394 1395 first_hunk, strip)
1395 1396 changed.add(current_file)
1396 1397 elif state == 'git':
1397 1398 for gp in values:
1398 1399 gp.path = pathstrip(gp.path, strip - 1)[1]
1399 1400 changed.add(gp.path)
1400 1401 if gp.oldpath:
1401 1402 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1402 1403 if gp.op == 'RENAME':
1403 1404 changed.add(gp.oldpath)
1404 1405 else:
1405 1406 raise util.Abort(_('unsupported parser state: %s') % state)
1406 1407 return changed
1407 1408 finally:
1408 1409 fp.close()
1409 1410
1410 1411 def b85diff(to, tn):
1411 1412 '''print base85-encoded binary diff'''
1412 1413 def gitindex(text):
1413 1414 if not text:
1414 1415 return hex(nullid)
1415 1416 l = len(text)
1416 1417 s = util.sha1('blob %d\0' % l)
1417 1418 s.update(text)
1418 1419 return s.hexdigest()
1419 1420
1420 1421 def fmtline(line):
1421 1422 l = len(line)
1422 1423 if l <= 26:
1423 1424 l = chr(ord('A') + l - 1)
1424 1425 else:
1425 1426 l = chr(l - 26 + ord('a') - 1)
1426 1427 return '%c%s\n' % (l, base85.b85encode(line, True))
1427 1428
1428 1429 def chunk(text, csize=52):
1429 1430 l = len(text)
1430 1431 i = 0
1431 1432 while i < l:
1432 1433 yield text[i:i + csize]
1433 1434 i += csize
1434 1435
1435 1436 tohash = gitindex(to)
1436 1437 tnhash = gitindex(tn)
1437 1438 if tohash == tnhash:
1438 1439 return ""
1439 1440
1440 1441 # TODO: deltas
1441 1442 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1442 1443 (tohash, tnhash, len(tn))]
1443 1444 for l in chunk(zlib.compress(tn)):
1444 1445 ret.append(fmtline(l))
1445 1446 ret.append('\n')
1446 1447 return ''.join(ret)
1447 1448
1448 1449 class GitDiffRequired(Exception):
1449 1450 pass
1450 1451
1451 1452 def diffopts(ui, opts=None, untrusted=False):
1452 1453 def get(key, name=None, getter=ui.configbool):
1453 1454 return ((opts and opts.get(key)) or
1454 1455 getter('diff', name or key, None, untrusted=untrusted))
1455 1456 return mdiff.diffopts(
1456 1457 text=opts and opts.get('text'),
1457 1458 git=get('git'),
1458 1459 nodates=get('nodates'),
1459 1460 showfunc=get('show_function', 'showfunc'),
1460 1461 ignorews=get('ignore_all_space', 'ignorews'),
1461 1462 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1462 1463 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1463 1464 context=get('unified', getter=ui.config))
1464 1465
1465 1466 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1466 1467 losedatafn=None, prefix=''):
1467 1468 '''yields diff of changes to files between two nodes, or node and
1468 1469 working directory.
1469 1470
1470 1471 if node1 is None, use first dirstate parent instead.
1471 1472 if node2 is None, compare node1 with working directory.
1472 1473
1473 1474 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1474 1475 every time some change cannot be represented with the current
1475 1476 patch format. Return False to upgrade to git patch format, True to
1476 1477 accept the loss or raise an exception to abort the diff. It is
1477 1478 called with the name of current file being diffed as 'fn'. If set
1478 1479 to None, patches will always be upgraded to git format when
1479 1480 necessary.
1480 1481
1481 1482 prefix is a filename prefix that is prepended to all filenames on
1482 1483 display (used for subrepos).
1483 1484 '''
1484 1485
1485 1486 if opts is None:
1486 1487 opts = mdiff.defaultopts
1487 1488
1488 1489 if not node1 and not node2:
1489 1490 node1 = repo.dirstate.p1()
1490 1491
1491 1492 def lrugetfilectx():
1492 1493 cache = {}
1493 1494 order = []
1494 1495 def getfilectx(f, ctx):
1495 1496 fctx = ctx.filectx(f, filelog=cache.get(f))
1496 1497 if f not in cache:
1497 1498 if len(cache) > 20:
1498 1499 del cache[order.pop(0)]
1499 1500 cache[f] = fctx.filelog()
1500 1501 else:
1501 1502 order.remove(f)
1502 1503 order.append(f)
1503 1504 return fctx
1504 1505 return getfilectx
1505 1506 getfilectx = lrugetfilectx()
1506 1507
1507 1508 ctx1 = repo[node1]
1508 1509 ctx2 = repo[node2]
1509 1510
1510 1511 if not changes:
1511 1512 changes = repo.status(ctx1, ctx2, match=match)
1512 1513 modified, added, removed = changes[:3]
1513 1514
1514 1515 if not modified and not added and not removed:
1515 1516 return []
1516 1517
1517 1518 revs = None
1518 1519 if not repo.ui.quiet:
1519 1520 hexfunc = repo.ui.debugflag and hex or short
1520 1521 revs = [hexfunc(node) for node in [node1, node2] if node]
1521 1522
1522 1523 copy = {}
1523 1524 if opts.git or opts.upgrade:
1524 1525 copy = copies.copies(repo, ctx1, ctx2, repo[nullid])[0]
1525 1526
1526 1527 difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2,
1527 1528 modified, added, removed, copy, getfilectx, opts, losedata, prefix)
1528 1529 if opts.upgrade and not opts.git:
1529 1530 try:
1530 1531 def losedata(fn):
1531 1532 if not losedatafn or not losedatafn(fn=fn):
1532 1533 raise GitDiffRequired()
1533 1534 # Buffer the whole output until we are sure it can be generated
1534 1535 return list(difffn(opts.copy(git=False), losedata))
1535 1536 except GitDiffRequired:
1536 1537 return difffn(opts.copy(git=True), None)
1537 1538 else:
1538 1539 return difffn(opts, None)
1539 1540
1540 1541 def difflabel(func, *args, **kw):
1541 1542 '''yields 2-tuples of (output, label) based on the output of func()'''
1542 1543 prefixes = [('diff', 'diff.diffline'),
1543 1544 ('copy', 'diff.extended'),
1544 1545 ('rename', 'diff.extended'),
1545 1546 ('old', 'diff.extended'),
1546 1547 ('new', 'diff.extended'),
1547 1548 ('deleted', 'diff.extended'),
1548 1549 ('---', 'diff.file_a'),
1549 1550 ('+++', 'diff.file_b'),
1550 1551 ('@@', 'diff.hunk'),
1551 1552 ('-', 'diff.deleted'),
1552 1553 ('+', 'diff.inserted')]
1553 1554
1554 1555 for chunk in func(*args, **kw):
1555 1556 lines = chunk.split('\n')
1556 1557 for i, line in enumerate(lines):
1557 1558 if i != 0:
1558 1559 yield ('\n', '')
1559 1560 stripline = line
1560 1561 if line and line[0] in '+-':
1561 1562 # highlight trailing whitespace, but only in changed lines
1562 1563 stripline = line.rstrip()
1563 1564 for prefix, label in prefixes:
1564 1565 if stripline.startswith(prefix):
1565 1566 yield (stripline, label)
1566 1567 break
1567 1568 else:
1568 1569 yield (line, '')
1569 1570 if line != stripline:
1570 1571 yield (line[len(stripline):], 'diff.trailingwhitespace')
1571 1572
1572 1573 def diffui(*args, **kw):
1573 1574 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1574 1575 return difflabel(diff, *args, **kw)
1575 1576
1576 1577
1577 1578 def _addmodehdr(header, omode, nmode):
1578 1579 if omode != nmode:
1579 1580 header.append('old mode %s\n' % omode)
1580 1581 header.append('new mode %s\n' % nmode)
1581 1582
1582 1583 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1583 1584 copy, getfilectx, opts, losedatafn, prefix):
1584 1585
1585 1586 def join(f):
1586 1587 return os.path.join(prefix, f)
1587 1588
1588 1589 date1 = util.datestr(ctx1.date())
1589 1590 man1 = ctx1.manifest()
1590 1591
1591 1592 gone = set()
1592 1593 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1593 1594
1594 1595 copyto = dict([(v, k) for k, v in copy.items()])
1595 1596
1596 1597 if opts.git:
1597 1598 revs = None
1598 1599
1599 1600 for f in sorted(modified + added + removed):
1600 1601 to = None
1601 1602 tn = None
1602 1603 dodiff = True
1603 1604 header = []
1604 1605 if f in man1:
1605 1606 to = getfilectx(f, ctx1).data()
1606 1607 if f not in removed:
1607 1608 tn = getfilectx(f, ctx2).data()
1608 1609 a, b = f, f
1609 1610 if opts.git or losedatafn:
1610 1611 if f in added:
1611 1612 mode = gitmode[ctx2.flags(f)]
1612 1613 if f in copy or f in copyto:
1613 1614 if opts.git:
1614 1615 if f in copy:
1615 1616 a = copy[f]
1616 1617 else:
1617 1618 a = copyto[f]
1618 1619 omode = gitmode[man1.flags(a)]
1619 1620 _addmodehdr(header, omode, mode)
1620 1621 if a in removed and a not in gone:
1621 1622 op = 'rename'
1622 1623 gone.add(a)
1623 1624 else:
1624 1625 op = 'copy'
1625 1626 header.append('%s from %s\n' % (op, join(a)))
1626 1627 header.append('%s to %s\n' % (op, join(f)))
1627 1628 to = getfilectx(a, ctx1).data()
1628 1629 else:
1629 1630 losedatafn(f)
1630 1631 else:
1631 1632 if opts.git:
1632 1633 header.append('new file mode %s\n' % mode)
1633 1634 elif ctx2.flags(f):
1634 1635 losedatafn(f)
1635 1636 # In theory, if tn was copied or renamed we should check
1636 1637 # if the source is binary too but the copy record already
1637 1638 # forces git mode.
1638 1639 if util.binary(tn):
1639 1640 if opts.git:
1640 1641 dodiff = 'binary'
1641 1642 else:
1642 1643 losedatafn(f)
1643 1644 if not opts.git and not tn:
1644 1645 # regular diffs cannot represent new empty file
1645 1646 losedatafn(f)
1646 1647 elif f in removed:
1647 1648 if opts.git:
1648 1649 # have we already reported a copy above?
1649 1650 if ((f in copy and copy[f] in added
1650 1651 and copyto[copy[f]] == f) or
1651 1652 (f in copyto and copyto[f] in added
1652 1653 and copy[copyto[f]] == f)):
1653 1654 dodiff = False
1654 1655 else:
1655 1656 header.append('deleted file mode %s\n' %
1656 1657 gitmode[man1.flags(f)])
1657 1658 elif not to or util.binary(to):
1658 1659 # regular diffs cannot represent empty file deletion
1659 1660 losedatafn(f)
1660 1661 else:
1661 1662 oflag = man1.flags(f)
1662 1663 nflag = ctx2.flags(f)
1663 1664 binary = util.binary(to) or util.binary(tn)
1664 1665 if opts.git:
1665 1666 _addmodehdr(header, gitmode[oflag], gitmode[nflag])
1666 1667 if binary:
1667 1668 dodiff = 'binary'
1668 1669 elif binary or nflag != oflag:
1669 1670 losedatafn(f)
1670 1671 if opts.git:
1671 1672 header.insert(0, mdiff.diffline(revs, join(a), join(b), opts))
1672 1673
1673 1674 if dodiff:
1674 1675 if dodiff == 'binary':
1675 1676 text = b85diff(to, tn)
1676 1677 else:
1677 1678 text = mdiff.unidiff(to, date1,
1678 1679 # ctx2 date may be dynamic
1679 1680 tn, util.datestr(ctx2.date()),
1680 1681 join(a), join(b), revs, opts=opts)
1681 1682 if header and (text or len(header) > 1):
1682 1683 yield ''.join(header)
1683 1684 if text:
1684 1685 yield text
1685 1686
1686 1687 def diffstatdata(lines):
1687 1688 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1688 1689
1689 1690 filename, adds, removes = None, 0, 0
1690 1691 for line in lines:
1691 1692 if line.startswith('diff'):
1692 1693 if filename:
1693 1694 isbinary = adds == 0 and removes == 0
1694 1695 yield (filename, adds, removes, isbinary)
1695 1696 # set numbers to 0 anyway when starting new file
1696 1697 adds, removes = 0, 0
1697 1698 if line.startswith('diff --git'):
1698 1699 filename = gitre.search(line).group(1)
1699 1700 elif line.startswith('diff -r'):
1700 1701 # format: "diff -r ... -r ... filename"
1701 1702 filename = diffre.search(line).group(1)
1702 1703 elif line.startswith('+') and not line.startswith('+++'):
1703 1704 adds += 1
1704 1705 elif line.startswith('-') and not line.startswith('---'):
1705 1706 removes += 1
1706 1707 if filename:
1707 1708 isbinary = adds == 0 and removes == 0
1708 1709 yield (filename, adds, removes, isbinary)
1709 1710
1710 1711 def diffstat(lines, width=80, git=False):
1711 1712 output = []
1712 1713 stats = list(diffstatdata(lines))
1713 1714
1714 1715 maxtotal, maxname = 0, 0
1715 1716 totaladds, totalremoves = 0, 0
1716 1717 hasbinary = False
1717 1718
1718 1719 sized = [(filename, adds, removes, isbinary, encoding.colwidth(filename))
1719 1720 for filename, adds, removes, isbinary in stats]
1720 1721
1721 1722 for filename, adds, removes, isbinary, namewidth in sized:
1722 1723 totaladds += adds
1723 1724 totalremoves += removes
1724 1725 maxname = max(maxname, namewidth)
1725 1726 maxtotal = max(maxtotal, adds + removes)
1726 1727 if isbinary:
1727 1728 hasbinary = True
1728 1729
1729 1730 countwidth = len(str(maxtotal))
1730 1731 if hasbinary and countwidth < 3:
1731 1732 countwidth = 3
1732 1733 graphwidth = width - countwidth - maxname - 6
1733 1734 if graphwidth < 10:
1734 1735 graphwidth = 10
1735 1736
1736 1737 def scale(i):
1737 1738 if maxtotal <= graphwidth:
1738 1739 return i
1739 1740 # If diffstat runs out of room it doesn't print anything,
1740 1741 # which isn't very useful, so always print at least one + or -
1741 1742 # if there were at least some changes.
1742 1743 return max(i * graphwidth // maxtotal, int(bool(i)))
1743 1744
1744 1745 for filename, adds, removes, isbinary, namewidth in sized:
1745 1746 if git and isbinary:
1746 1747 count = 'Bin'
1747 1748 else:
1748 1749 count = adds + removes
1749 1750 pluses = '+' * scale(adds)
1750 1751 minuses = '-' * scale(removes)
1751 1752 output.append(' %s%s | %*s %s%s\n' %
1752 1753 (filename, ' ' * (maxname - namewidth),
1753 1754 countwidth, count,
1754 1755 pluses, minuses))
1755 1756
1756 1757 if stats:
1757 1758 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1758 1759 % (len(stats), totaladds, totalremoves))
1759 1760
1760 1761 return ''.join(output)
1761 1762
1762 1763 def diffstatui(*args, **kw):
1763 1764 '''like diffstat(), but yields 2-tuples of (output, label) for
1764 1765 ui.write()
1765 1766 '''
1766 1767
1767 1768 for line in diffstat(*args, **kw).splitlines():
1768 1769 if line and line[-1] in '+-':
1769 1770 name, graph = line.rsplit(' ', 1)
1770 1771 yield (name + ' ', '')
1771 1772 m = re.search(r'\++', graph)
1772 1773 if m:
1773 1774 yield (m.group(0), 'diffstat.inserted')
1774 1775 m = re.search(r'-+', graph)
1775 1776 if m:
1776 1777 yield (m.group(0), 'diffstat.deleted')
1777 1778 else:
1778 1779 yield (line, '')
1779 1780 yield ('\n', '')
General Comments 0
You need to be logged in to leave comments. Login now