##// END OF EJS Templates
diffstat: with --git, mark binary files with Bin...
Brodie Rao -
r9642:7d17794f default
parent child Browse files
Show More
@@ -1,2693 +1,2694 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, incorporated herein by reference.
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 prepare repository to work with patches qinit
20 20 create new patch qnew
21 21 import existing patch qimport
22 22
23 23 print patch series qseries
24 24 print applied patches qapplied
25 25
26 26 add known patch to applied stack qpush
27 27 remove patch from applied stack qpop
28 28 refresh contents of top applied patch qrefresh
29 29 '''
30 30
31 31 from mercurial.i18n import _
32 32 from mercurial.node import bin, hex, short, nullid, nullrev
33 33 from mercurial.lock import release
34 34 from mercurial import commands, cmdutil, hg, patch, util
35 35 from mercurial import repair, extensions, url, error
36 36 import os, sys, re, errno
37 37
38 38 commands.norepo += " qclone"
39 39
40 40 # Patch names looks like unix-file names.
41 41 # They must be joinable with queue directory and result in the patch path.
42 42 normname = util.normpath
43 43
44 44 class statusentry(object):
45 45 def __init__(self, rev, name=None):
46 46 if not name:
47 47 fields = rev.split(':', 1)
48 48 if len(fields) == 2:
49 49 self.rev, self.name = fields
50 50 else:
51 51 self.rev, self.name = None, None
52 52 else:
53 53 self.rev, self.name = rev, name
54 54
55 55 def __str__(self):
56 56 return self.rev + ':' + self.name
57 57
58 58 class patchheader(object):
59 59 def __init__(self, pf):
60 60 def eatdiff(lines):
61 61 while lines:
62 62 l = lines[-1]
63 63 if (l.startswith("diff -") or
64 64 l.startswith("Index:") or
65 65 l.startswith("===========")):
66 66 del lines[-1]
67 67 else:
68 68 break
69 69 def eatempty(lines):
70 70 while lines:
71 71 l = lines[-1]
72 72 if re.match('\s*$', l):
73 73 del lines[-1]
74 74 else:
75 75 break
76 76
77 77 message = []
78 78 comments = []
79 79 user = None
80 80 date = None
81 81 format = None
82 82 subject = None
83 83 diffstart = 0
84 84
85 85 for line in file(pf):
86 86 line = line.rstrip()
87 87 if line.startswith('diff --git'):
88 88 diffstart = 2
89 89 break
90 90 if diffstart:
91 91 if line.startswith('+++ '):
92 92 diffstart = 2
93 93 break
94 94 if line.startswith("--- "):
95 95 diffstart = 1
96 96 continue
97 97 elif format == "hgpatch":
98 98 # parse values when importing the result of an hg export
99 99 if line.startswith("# User "):
100 100 user = line[7:]
101 101 elif line.startswith("# Date "):
102 102 date = line[7:]
103 103 elif not line.startswith("# ") and line:
104 104 message.append(line)
105 105 format = None
106 106 elif line == '# HG changeset patch':
107 107 message = []
108 108 format = "hgpatch"
109 109 elif (format != "tagdone" and (line.startswith("Subject: ") or
110 110 line.startswith("subject: "))):
111 111 subject = line[9:]
112 112 format = "tag"
113 113 elif (format != "tagdone" and (line.startswith("From: ") or
114 114 line.startswith("from: "))):
115 115 user = line[6:]
116 116 format = "tag"
117 117 elif format == "tag" and line == "":
118 118 # when looking for tags (subject: from: etc) they
119 119 # end once you find a blank line in the source
120 120 format = "tagdone"
121 121 elif message or line:
122 122 message.append(line)
123 123 comments.append(line)
124 124
125 125 eatdiff(message)
126 126 eatdiff(comments)
127 127 eatempty(message)
128 128 eatempty(comments)
129 129
130 130 # make sure message isn't empty
131 131 if format and format.startswith("tag") and subject:
132 132 message.insert(0, "")
133 133 message.insert(0, subject)
134 134
135 135 self.message = message
136 136 self.comments = comments
137 137 self.user = user
138 138 self.date = date
139 139 self.haspatch = diffstart > 1
140 140
141 141 def setuser(self, user):
142 142 if not self.updateheader(['From: ', '# User '], user):
143 143 try:
144 144 patchheaderat = self.comments.index('# HG changeset patch')
145 145 self.comments.insert(patchheaderat + 1, '# User ' + user)
146 146 except ValueError:
147 147 if self._hasheader(['Date: ']):
148 148 self.comments = ['From: ' + user] + self.comments
149 149 else:
150 150 tmp = ['# HG changeset patch', '# User ' + user, '']
151 151 self.comments = tmp + self.comments
152 152 self.user = user
153 153
154 154 def setdate(self, date):
155 155 if not self.updateheader(['Date: ', '# Date '], date):
156 156 try:
157 157 patchheaderat = self.comments.index('# HG changeset patch')
158 158 self.comments.insert(patchheaderat + 1, '# Date ' + date)
159 159 except ValueError:
160 160 if self._hasheader(['From: ']):
161 161 self.comments = ['Date: ' + date] + self.comments
162 162 else:
163 163 tmp = ['# HG changeset patch', '# Date ' + date, '']
164 164 self.comments = tmp + self.comments
165 165 self.date = date
166 166
167 167 def setmessage(self, message):
168 168 if self.comments:
169 169 self._delmsg()
170 170 self.message = [message]
171 171 self.comments += self.message
172 172
173 173 def updateheader(self, prefixes, new):
174 174 '''Update all references to a field in the patch header.
175 175 Return whether the field is present.'''
176 176 res = False
177 177 for prefix in prefixes:
178 178 for i in xrange(len(self.comments)):
179 179 if self.comments[i].startswith(prefix):
180 180 self.comments[i] = prefix + new
181 181 res = True
182 182 break
183 183 return res
184 184
185 185 def _hasheader(self, prefixes):
186 186 '''Check if a header starts with any of the given prefixes.'''
187 187 for prefix in prefixes:
188 188 for comment in self.comments:
189 189 if comment.startswith(prefix):
190 190 return True
191 191 return False
192 192
193 193 def __str__(self):
194 194 if not self.comments:
195 195 return ''
196 196 return '\n'.join(self.comments) + '\n\n'
197 197
198 198 def _delmsg(self):
199 199 '''Remove existing message, keeping the rest of the comments fields.
200 200 If comments contains 'subject: ', message will prepend
201 201 the field and a blank line.'''
202 202 if self.message:
203 203 subj = 'subject: ' + self.message[0].lower()
204 204 for i in xrange(len(self.comments)):
205 205 if subj == self.comments[i].lower():
206 206 del self.comments[i]
207 207 self.message = self.message[2:]
208 208 break
209 209 ci = 0
210 210 for mi in self.message:
211 211 while mi != self.comments[ci]:
212 212 ci += 1
213 213 del self.comments[ci]
214 214
215 215 class queue(object):
216 216 def __init__(self, ui, path, patchdir=None):
217 217 self.basepath = path
218 218 self.path = patchdir or os.path.join(path, "patches")
219 219 self.opener = util.opener(self.path)
220 220 self.ui = ui
221 221 self.applied_dirty = 0
222 222 self.series_dirty = 0
223 223 self.series_path = "series"
224 224 self.status_path = "status"
225 225 self.guards_path = "guards"
226 226 self.active_guards = None
227 227 self.guards_dirty = False
228 228 self._diffopts = None
229 229
230 230 @util.propertycache
231 231 def applied(self):
232 232 if os.path.exists(self.join(self.status_path)):
233 233 lines = self.opener(self.status_path).read().splitlines()
234 234 return [statusentry(l) for l in lines]
235 235 return []
236 236
237 237 @util.propertycache
238 238 def full_series(self):
239 239 if os.path.exists(self.join(self.series_path)):
240 240 return self.opener(self.series_path).read().splitlines()
241 241 return []
242 242
243 243 @util.propertycache
244 244 def series(self):
245 245 self.parse_series()
246 246 return self.series
247 247
248 248 @util.propertycache
249 249 def series_guards(self):
250 250 self.parse_series()
251 251 return self.series_guards
252 252
253 253 def invalidate(self):
254 254 for a in 'applied full_series series series_guards'.split():
255 255 if a in self.__dict__:
256 256 delattr(self, a)
257 257 self.applied_dirty = 0
258 258 self.series_dirty = 0
259 259 self.guards_dirty = False
260 260 self.active_guards = None
261 261
262 262 def diffopts(self):
263 263 if self._diffopts is None:
264 264 self._diffopts = patch.diffopts(self.ui)
265 265 return self._diffopts
266 266
267 267 def join(self, *p):
268 268 return os.path.join(self.path, *p)
269 269
270 270 def find_series(self, patch):
271 271 pre = re.compile("(\s*)([^#]+)")
272 272 index = 0
273 273 for l in self.full_series:
274 274 m = pre.match(l)
275 275 if m:
276 276 s = m.group(2)
277 277 s = s.rstrip()
278 278 if s == patch:
279 279 return index
280 280 index += 1
281 281 return None
282 282
283 283 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
284 284
285 285 def parse_series(self):
286 286 self.series = []
287 287 self.series_guards = []
288 288 for l in self.full_series:
289 289 h = l.find('#')
290 290 if h == -1:
291 291 patch = l
292 292 comment = ''
293 293 elif h == 0:
294 294 continue
295 295 else:
296 296 patch = l[:h]
297 297 comment = l[h:]
298 298 patch = patch.strip()
299 299 if patch:
300 300 if patch in self.series:
301 301 raise util.Abort(_('%s appears more than once in %s') %
302 302 (patch, self.join(self.series_path)))
303 303 self.series.append(patch)
304 304 self.series_guards.append(self.guard_re.findall(comment))
305 305
306 306 def check_guard(self, guard):
307 307 if not guard:
308 308 return _('guard cannot be an empty string')
309 309 bad_chars = '# \t\r\n\f'
310 310 first = guard[0]
311 311 if first in '-+':
312 312 return (_('guard %r starts with invalid character: %r') %
313 313 (guard, first))
314 314 for c in bad_chars:
315 315 if c in guard:
316 316 return _('invalid character in guard %r: %r') % (guard, c)
317 317
318 318 def set_active(self, guards):
319 319 for guard in guards:
320 320 bad = self.check_guard(guard)
321 321 if bad:
322 322 raise util.Abort(bad)
323 323 guards = sorted(set(guards))
324 324 self.ui.debug('active guards: %s\n' % ' '.join(guards))
325 325 self.active_guards = guards
326 326 self.guards_dirty = True
327 327
328 328 def active(self):
329 329 if self.active_guards is None:
330 330 self.active_guards = []
331 331 try:
332 332 guards = self.opener(self.guards_path).read().split()
333 333 except IOError, err:
334 334 if err.errno != errno.ENOENT: raise
335 335 guards = []
336 336 for i, guard in enumerate(guards):
337 337 bad = self.check_guard(guard)
338 338 if bad:
339 339 self.ui.warn('%s:%d: %s\n' %
340 340 (self.join(self.guards_path), i + 1, bad))
341 341 else:
342 342 self.active_guards.append(guard)
343 343 return self.active_guards
344 344
345 345 def set_guards(self, idx, guards):
346 346 for g in guards:
347 347 if len(g) < 2:
348 348 raise util.Abort(_('guard %r too short') % g)
349 349 if g[0] not in '-+':
350 350 raise util.Abort(_('guard %r starts with invalid char') % g)
351 351 bad = self.check_guard(g[1:])
352 352 if bad:
353 353 raise util.Abort(bad)
354 354 drop = self.guard_re.sub('', self.full_series[idx])
355 355 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
356 356 self.parse_series()
357 357 self.series_dirty = True
358 358
359 359 def pushable(self, idx):
360 360 if isinstance(idx, str):
361 361 idx = self.series.index(idx)
362 362 patchguards = self.series_guards[idx]
363 363 if not patchguards:
364 364 return True, None
365 365 guards = self.active()
366 366 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
367 367 if exactneg:
368 368 return False, exactneg[0]
369 369 pos = [g for g in patchguards if g[0] == '+']
370 370 exactpos = [g for g in pos if g[1:] in guards]
371 371 if pos:
372 372 if exactpos:
373 373 return True, exactpos[0]
374 374 return False, pos
375 375 return True, ''
376 376
377 377 def explain_pushable(self, idx, all_patches=False):
378 378 write = all_patches and self.ui.write or self.ui.warn
379 379 if all_patches or self.ui.verbose:
380 380 if isinstance(idx, str):
381 381 idx = self.series.index(idx)
382 382 pushable, why = self.pushable(idx)
383 383 if all_patches and pushable:
384 384 if why is None:
385 385 write(_('allowing %s - no guards in effect\n') %
386 386 self.series[idx])
387 387 else:
388 388 if not why:
389 389 write(_('allowing %s - no matching negative guards\n') %
390 390 self.series[idx])
391 391 else:
392 392 write(_('allowing %s - guarded by %r\n') %
393 393 (self.series[idx], why))
394 394 if not pushable:
395 395 if why:
396 396 write(_('skipping %s - guarded by %r\n') %
397 397 (self.series[idx], why))
398 398 else:
399 399 write(_('skipping %s - no matching guards\n') %
400 400 self.series[idx])
401 401
402 402 def save_dirty(self):
403 403 def write_list(items, path):
404 404 fp = self.opener(path, 'w')
405 405 for i in items:
406 406 fp.write("%s\n" % i)
407 407 fp.close()
408 408 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
409 409 if self.series_dirty: write_list(self.full_series, self.series_path)
410 410 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
411 411
412 412 def removeundo(self, repo):
413 413 undo = repo.sjoin('undo')
414 414 if not os.path.exists(undo):
415 415 return
416 416 try:
417 417 os.unlink(undo)
418 418 except OSError, inst:
419 419 self.ui.warn(_('error removing undo: %s\n') % str(inst))
420 420
421 421 def printdiff(self, repo, node1, node2=None, files=None,
422 422 fp=None, changes=None, opts={}):
423 423 stat = opts.get('stat')
424 424 if stat:
425 425 opts['unified'] = '0'
426 426
427 427 m = cmdutil.match(repo, files, opts)
428 428 chunks = patch.diff(repo, node1, node2, m, changes, self.diffopts())
429 429 write = fp is None and repo.ui.write or fp.write
430 430 if stat:
431 431 width = self.ui.interactive() and util.termwidth() or 80
432 write(patch.diffstat(util.iterlines(chunks), width=width))
432 write(patch.diffstat(util.iterlines(chunks), width=width,
433 git=self.diffopts().git))
433 434 else:
434 435 for chunk in chunks:
435 436 write(chunk)
436 437
437 438 def mergeone(self, repo, mergeq, head, patch, rev):
438 439 # first try just applying the patch
439 440 (err, n) = self.apply(repo, [ patch ], update_status=False,
440 441 strict=True, merge=rev)
441 442
442 443 if err == 0:
443 444 return (err, n)
444 445
445 446 if n is None:
446 447 raise util.Abort(_("apply failed for patch %s") % patch)
447 448
448 449 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
449 450
450 451 # apply failed, strip away that rev and merge.
451 452 hg.clean(repo, head)
452 453 self.strip(repo, n, update=False, backup='strip')
453 454
454 455 ctx = repo[rev]
455 456 ret = hg.merge(repo, rev)
456 457 if ret:
457 458 raise util.Abort(_("update returned %d") % ret)
458 459 n = repo.commit(ctx.description(), ctx.user(), force=True)
459 460 if n is None:
460 461 raise util.Abort(_("repo commit failed"))
461 462 try:
462 463 ph = patchheader(mergeq.join(patch))
463 464 except:
464 465 raise util.Abort(_("unable to read %s") % patch)
465 466
466 467 patchf = self.opener(patch, "w")
467 468 comments = str(ph)
468 469 if comments:
469 470 patchf.write(comments)
470 471 self.printdiff(repo, head, n, fp=patchf)
471 472 patchf.close()
472 473 self.removeundo(repo)
473 474 return (0, n)
474 475
475 476 def qparents(self, repo, rev=None):
476 477 if rev is None:
477 478 (p1, p2) = repo.dirstate.parents()
478 479 if p2 == nullid:
479 480 return p1
480 481 if len(self.applied) == 0:
481 482 return None
482 483 return bin(self.applied[-1].rev)
483 484 pp = repo.changelog.parents(rev)
484 485 if pp[1] != nullid:
485 486 arevs = [ x.rev for x in self.applied ]
486 487 p0 = hex(pp[0])
487 488 p1 = hex(pp[1])
488 489 if p0 in arevs:
489 490 return pp[0]
490 491 if p1 in arevs:
491 492 return pp[1]
492 493 return pp[0]
493 494
494 495 def mergepatch(self, repo, mergeq, series):
495 496 if len(self.applied) == 0:
496 497 # each of the patches merged in will have two parents. This
497 498 # can confuse the qrefresh, qdiff, and strip code because it
498 499 # needs to know which parent is actually in the patch queue.
499 500 # so, we insert a merge marker with only one parent. This way
500 501 # the first patch in the queue is never a merge patch
501 502 #
502 503 pname = ".hg.patches.merge.marker"
503 504 n = repo.commit('[mq]: merge marker', force=True)
504 505 self.removeundo(repo)
505 506 self.applied.append(statusentry(hex(n), pname))
506 507 self.applied_dirty = 1
507 508
508 509 head = self.qparents(repo)
509 510
510 511 for patch in series:
511 512 patch = mergeq.lookup(patch, strict=True)
512 513 if not patch:
513 514 self.ui.warn(_("patch %s does not exist\n") % patch)
514 515 return (1, None)
515 516 pushable, reason = self.pushable(patch)
516 517 if not pushable:
517 518 self.explain_pushable(patch, all_patches=True)
518 519 continue
519 520 info = mergeq.isapplied(patch)
520 521 if not info:
521 522 self.ui.warn(_("patch %s is not applied\n") % patch)
522 523 return (1, None)
523 524 rev = bin(info[1])
524 525 (err, head) = self.mergeone(repo, mergeq, head, patch, rev)
525 526 if head:
526 527 self.applied.append(statusentry(hex(head), patch))
527 528 self.applied_dirty = 1
528 529 if err:
529 530 return (err, head)
530 531 self.save_dirty()
531 532 return (0, head)
532 533
533 534 def patch(self, repo, patchfile):
534 535 '''Apply patchfile to the working directory.
535 536 patchfile: name of patch file'''
536 537 files = {}
537 538 try:
538 539 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
539 540 files=files, eolmode=None)
540 541 except Exception, inst:
541 542 self.ui.note(str(inst) + '\n')
542 543 if not self.ui.verbose:
543 544 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
544 545 return (False, files, False)
545 546
546 547 return (True, files, fuzz)
547 548
548 549 def apply(self, repo, series, list=False, update_status=True,
549 550 strict=False, patchdir=None, merge=None, all_files={}):
550 551 wlock = lock = tr = None
551 552 try:
552 553 wlock = repo.wlock()
553 554 lock = repo.lock()
554 555 tr = repo.transaction()
555 556 try:
556 557 ret = self._apply(repo, series, list, update_status,
557 558 strict, patchdir, merge, all_files=all_files)
558 559 tr.close()
559 560 self.save_dirty()
560 561 return ret
561 562 except:
562 563 try:
563 564 tr.abort()
564 565 finally:
565 566 repo.invalidate()
566 567 repo.dirstate.invalidate()
567 568 raise
568 569 finally:
569 570 del tr
570 571 release(lock, wlock)
571 572 self.removeundo(repo)
572 573
573 574 def _apply(self, repo, series, list=False, update_status=True,
574 575 strict=False, patchdir=None, merge=None, all_files={}):
575 576 '''returns (error, hash)
576 577 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
577 578 # TODO unify with commands.py
578 579 if not patchdir:
579 580 patchdir = self.path
580 581 err = 0
581 582 n = None
582 583 for patchname in series:
583 584 pushable, reason = self.pushable(patchname)
584 585 if not pushable:
585 586 self.explain_pushable(patchname, all_patches=True)
586 587 continue
587 588 self.ui.status(_("applying %s\n") % patchname)
588 589 pf = os.path.join(patchdir, patchname)
589 590
590 591 try:
591 592 ph = patchheader(self.join(patchname))
592 593 except:
593 594 self.ui.warn(_("unable to read %s\n") % patchname)
594 595 err = 1
595 596 break
596 597
597 598 message = ph.message
598 599 if not message:
599 600 message = _("imported patch %s\n") % patchname
600 601 else:
601 602 if list:
602 603 message.append(_("\nimported patch %s") % patchname)
603 604 message = '\n'.join(message)
604 605
605 606 if ph.haspatch:
606 607 (patcherr, files, fuzz) = self.patch(repo, pf)
607 608 all_files.update(files)
608 609 patcherr = not patcherr
609 610 else:
610 611 self.ui.warn(_("patch %s is empty\n") % patchname)
611 612 patcherr, files, fuzz = 0, [], 0
612 613
613 614 if merge and files:
614 615 # Mark as removed/merged and update dirstate parent info
615 616 removed = []
616 617 merged = []
617 618 for f in files:
618 619 if os.path.exists(repo.wjoin(f)):
619 620 merged.append(f)
620 621 else:
621 622 removed.append(f)
622 623 for f in removed:
623 624 repo.dirstate.remove(f)
624 625 for f in merged:
625 626 repo.dirstate.merge(f)
626 627 p1, p2 = repo.dirstate.parents()
627 628 repo.dirstate.setparents(p1, merge)
628 629
629 630 files = patch.updatedir(self.ui, repo, files)
630 631 match = cmdutil.matchfiles(repo, files or [])
631 632 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
632 633
633 634 if n is None:
634 635 raise util.Abort(_("repo commit failed"))
635 636
636 637 if update_status:
637 638 self.applied.append(statusentry(hex(n), patchname))
638 639
639 640 if patcherr:
640 641 self.ui.warn(_("patch failed, rejects left in working dir\n"))
641 642 err = 2
642 643 break
643 644
644 645 if fuzz and strict:
645 646 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
646 647 err = 3
647 648 break
648 649 return (err, n)
649 650
650 651 def _cleanup(self, patches, numrevs, keep=False):
651 652 if not keep:
652 653 r = self.qrepo()
653 654 if r:
654 655 r.remove(patches, True)
655 656 else:
656 657 for p in patches:
657 658 os.unlink(self.join(p))
658 659
659 660 if numrevs:
660 661 del self.applied[:numrevs]
661 662 self.applied_dirty = 1
662 663
663 664 for i in sorted([self.find_series(p) for p in patches], reverse=True):
664 665 del self.full_series[i]
665 666 self.parse_series()
666 667 self.series_dirty = 1
667 668
668 669 def _revpatches(self, repo, revs):
669 670 firstrev = repo[self.applied[0].rev].rev()
670 671 patches = []
671 672 for i, rev in enumerate(revs):
672 673
673 674 if rev < firstrev:
674 675 raise util.Abort(_('revision %d is not managed') % rev)
675 676
676 677 ctx = repo[rev]
677 678 base = bin(self.applied[i].rev)
678 679 if ctx.node() != base:
679 680 msg = _('cannot delete revision %d above applied patches')
680 681 raise util.Abort(msg % rev)
681 682
682 683 patch = self.applied[i].name
683 684 for fmt in ('[mq]: %s', 'imported patch %s'):
684 685 if ctx.description() == fmt % patch:
685 686 msg = _('patch %s finalized without changeset message\n')
686 687 repo.ui.status(msg % patch)
687 688 break
688 689
689 690 patches.append(patch)
690 691 return patches
691 692
692 693 def finish(self, repo, revs):
693 694 patches = self._revpatches(repo, sorted(revs))
694 695 self._cleanup(patches, len(patches))
695 696
696 697 def delete(self, repo, patches, opts):
697 698 if not patches and not opts.get('rev'):
698 699 raise util.Abort(_('qdelete requires at least one revision or '
699 700 'patch name'))
700 701
701 702 realpatches = []
702 703 for patch in patches:
703 704 patch = self.lookup(patch, strict=True)
704 705 info = self.isapplied(patch)
705 706 if info:
706 707 raise util.Abort(_("cannot delete applied patch %s") % patch)
707 708 if patch not in self.series:
708 709 raise util.Abort(_("patch %s not in series file") % patch)
709 710 realpatches.append(patch)
710 711
711 712 numrevs = 0
712 713 if opts.get('rev'):
713 714 if not self.applied:
714 715 raise util.Abort(_('no patches applied'))
715 716 revs = cmdutil.revrange(repo, opts['rev'])
716 717 if len(revs) > 1 and revs[0] > revs[1]:
717 718 revs.reverse()
718 719 revpatches = self._revpatches(repo, revs)
719 720 realpatches += revpatches
720 721 numrevs = len(revpatches)
721 722
722 723 self._cleanup(realpatches, numrevs, opts.get('keep'))
723 724
724 725 def check_toppatch(self, repo):
725 726 if len(self.applied) > 0:
726 727 top = bin(self.applied[-1].rev)
727 728 pp = repo.dirstate.parents()
728 729 if top not in pp:
729 730 raise util.Abort(_("working directory revision is not qtip"))
730 731 return top
731 732 return None
732 733 def check_localchanges(self, repo, force=False, refresh=True):
733 734 m, a, r, d = repo.status()[:4]
734 735 if m or a or r or d:
735 736 if not force:
736 737 if refresh:
737 738 raise util.Abort(_("local changes found, refresh first"))
738 739 else:
739 740 raise util.Abort(_("local changes found"))
740 741 return m, a, r, d
741 742
742 743 _reserved = ('series', 'status', 'guards')
743 744 def check_reserved_name(self, name):
744 745 if (name in self._reserved or name.startswith('.hg')
745 746 or name.startswith('.mq')):
746 747 raise util.Abort(_('"%s" cannot be used as the name of a patch')
747 748 % name)
748 749
749 750 def new(self, repo, patchfn, *pats, **opts):
750 751 """options:
751 752 msg: a string or a no-argument function returning a string
752 753 """
753 754 msg = opts.get('msg')
754 755 force = opts.get('force')
755 756 user = opts.get('user')
756 757 date = opts.get('date')
757 758 if date:
758 759 date = util.parsedate(date)
759 760 self.check_reserved_name(patchfn)
760 761 if os.path.exists(self.join(patchfn)):
761 762 raise util.Abort(_('patch "%s" already exists') % patchfn)
762 763 if opts.get('include') or opts.get('exclude') or pats:
763 764 match = cmdutil.match(repo, pats, opts)
764 765 # detect missing files in pats
765 766 def badfn(f, msg):
766 767 raise util.Abort('%s: %s' % (f, msg))
767 768 match.bad = badfn
768 769 m, a, r, d = repo.status(match=match)[:4]
769 770 else:
770 771 m, a, r, d = self.check_localchanges(repo, force)
771 772 match = cmdutil.matchfiles(repo, m + a + r)
772 773 commitfiles = m + a + r
773 774 self.check_toppatch(repo)
774 775 insert = self.full_series_end()
775 776 wlock = repo.wlock()
776 777 try:
777 778 # if patch file write fails, abort early
778 779 p = self.opener(patchfn, "w")
779 780 try:
780 781 if date:
781 782 p.write("# HG changeset patch\n")
782 783 if user:
783 784 p.write("# User " + user + "\n")
784 785 p.write("# Date %d %d\n\n" % date)
785 786 elif user:
786 787 p.write("From: " + user + "\n\n")
787 788
788 789 if hasattr(msg, '__call__'):
789 790 msg = msg()
790 791 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
791 792 n = repo.commit(commitmsg, user, date, match=match, force=True)
792 793 if n is None:
793 794 raise util.Abort(_("repo commit failed"))
794 795 try:
795 796 self.full_series[insert:insert] = [patchfn]
796 797 self.applied.append(statusentry(hex(n), patchfn))
797 798 self.parse_series()
798 799 self.series_dirty = 1
799 800 self.applied_dirty = 1
800 801 if msg:
801 802 msg = msg + "\n\n"
802 803 p.write(msg)
803 804 if commitfiles:
804 805 diffopts = self.diffopts()
805 806 if opts.get('git'): diffopts.git = True
806 807 parent = self.qparents(repo, n)
807 808 chunks = patch.diff(repo, node1=parent, node2=n,
808 809 match=match, opts=diffopts)
809 810 for chunk in chunks:
810 811 p.write(chunk)
811 812 p.close()
812 813 wlock.release()
813 814 wlock = None
814 815 r = self.qrepo()
815 816 if r: r.add([patchfn])
816 817 except:
817 818 repo.rollback()
818 819 raise
819 820 except Exception:
820 821 patchpath = self.join(patchfn)
821 822 try:
822 823 os.unlink(patchpath)
823 824 except:
824 825 self.ui.warn(_('error unlinking %s\n') % patchpath)
825 826 raise
826 827 self.removeundo(repo)
827 828 finally:
828 829 release(wlock)
829 830
830 831 def strip(self, repo, rev, update=True, backup="all", force=None):
831 832 wlock = lock = None
832 833 try:
833 834 wlock = repo.wlock()
834 835 lock = repo.lock()
835 836
836 837 if update:
837 838 self.check_localchanges(repo, force=force, refresh=False)
838 839 urev = self.qparents(repo, rev)
839 840 hg.clean(repo, urev)
840 841 repo.dirstate.write()
841 842
842 843 self.removeundo(repo)
843 844 repair.strip(self.ui, repo, rev, backup)
844 845 # strip may have unbundled a set of backed up revisions after
845 846 # the actual strip
846 847 self.removeundo(repo)
847 848 finally:
848 849 release(lock, wlock)
849 850
850 851 def isapplied(self, patch):
851 852 """returns (index, rev, patch)"""
852 853 for i, a in enumerate(self.applied):
853 854 if a.name == patch:
854 855 return (i, a.rev, a.name)
855 856 return None
856 857
857 858 # if the exact patch name does not exist, we try a few
858 859 # variations. If strict is passed, we try only #1
859 860 #
860 861 # 1) a number to indicate an offset in the series file
861 862 # 2) a unique substring of the patch name was given
862 863 # 3) patchname[-+]num to indicate an offset in the series file
863 864 def lookup(self, patch, strict=False):
864 865 patch = patch and str(patch)
865 866
866 867 def partial_name(s):
867 868 if s in self.series:
868 869 return s
869 870 matches = [x for x in self.series if s in x]
870 871 if len(matches) > 1:
871 872 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
872 873 for m in matches:
873 874 self.ui.warn(' %s\n' % m)
874 875 return None
875 876 if matches:
876 877 return matches[0]
877 878 if len(self.series) > 0 and len(self.applied) > 0:
878 879 if s == 'qtip':
879 880 return self.series[self.series_end(True)-1]
880 881 if s == 'qbase':
881 882 return self.series[0]
882 883 return None
883 884
884 885 if patch is None:
885 886 return None
886 887 if patch in self.series:
887 888 return patch
888 889
889 890 if not os.path.isfile(self.join(patch)):
890 891 try:
891 892 sno = int(patch)
892 893 except(ValueError, OverflowError):
893 894 pass
894 895 else:
895 896 if -len(self.series) <= sno < len(self.series):
896 897 return self.series[sno]
897 898
898 899 if not strict:
899 900 res = partial_name(patch)
900 901 if res:
901 902 return res
902 903 minus = patch.rfind('-')
903 904 if minus >= 0:
904 905 res = partial_name(patch[:minus])
905 906 if res:
906 907 i = self.series.index(res)
907 908 try:
908 909 off = int(patch[minus+1:] or 1)
909 910 except(ValueError, OverflowError):
910 911 pass
911 912 else:
912 913 if i - off >= 0:
913 914 return self.series[i - off]
914 915 plus = patch.rfind('+')
915 916 if plus >= 0:
916 917 res = partial_name(patch[:plus])
917 918 if res:
918 919 i = self.series.index(res)
919 920 try:
920 921 off = int(patch[plus+1:] or 1)
921 922 except(ValueError, OverflowError):
922 923 pass
923 924 else:
924 925 if i + off < len(self.series):
925 926 return self.series[i + off]
926 927 raise util.Abort(_("patch %s not in series") % patch)
927 928
928 929 def push(self, repo, patch=None, force=False, list=False,
929 930 mergeq=None, all=False):
930 931 wlock = repo.wlock()
931 932 try:
932 933 if repo.dirstate.parents()[0] not in repo.heads():
933 934 self.ui.status(_("(working directory not at a head)\n"))
934 935
935 936 if not self.series:
936 937 self.ui.warn(_('no patches in series\n'))
937 938 return 0
938 939
939 940 patch = self.lookup(patch)
940 941 # Suppose our series file is: A B C and the current 'top'
941 942 # patch is B. qpush C should be performed (moving forward)
942 943 # qpush B is a NOP (no change) qpush A is an error (can't
943 944 # go backwards with qpush)
944 945 if patch:
945 946 info = self.isapplied(patch)
946 947 if info:
947 948 if info[0] < len(self.applied) - 1:
948 949 raise util.Abort(
949 950 _("cannot push to a previous patch: %s") % patch)
950 951 self.ui.warn(
951 952 _('qpush: %s is already at the top\n') % patch)
952 953 return
953 954 pushable, reason = self.pushable(patch)
954 955 if not pushable:
955 956 if reason:
956 957 reason = _('guarded by %r') % reason
957 958 else:
958 959 reason = _('no matching guards')
959 960 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
960 961 return 1
961 962 elif all:
962 963 patch = self.series[-1]
963 964 if self.isapplied(patch):
964 965 self.ui.warn(_('all patches are currently applied\n'))
965 966 return 0
966 967
967 968 # Following the above example, starting at 'top' of B:
968 969 # qpush should be performed (pushes C), but a subsequent
969 970 # qpush without an argument is an error (nothing to
970 971 # apply). This allows a loop of "...while hg qpush..." to
971 972 # work as it detects an error when done
972 973 start = self.series_end()
973 974 if start == len(self.series):
974 975 self.ui.warn(_('patch series already fully applied\n'))
975 976 return 1
976 977 if not force:
977 978 self.check_localchanges(repo)
978 979
979 980 self.applied_dirty = 1
980 981 if start > 0:
981 982 self.check_toppatch(repo)
982 983 if not patch:
983 984 patch = self.series[start]
984 985 end = start + 1
985 986 else:
986 987 end = self.series.index(patch, start) + 1
987 988
988 989 s = self.series[start:end]
989 990 all_files = {}
990 991 try:
991 992 if mergeq:
992 993 ret = self.mergepatch(repo, mergeq, s)
993 994 else:
994 995 ret = self.apply(repo, s, list, all_files=all_files)
995 996 except:
996 997 self.ui.warn(_('cleaning up working directory...'))
997 998 node = repo.dirstate.parents()[0]
998 999 hg.revert(repo, node, None)
999 1000 unknown = repo.status(unknown=True)[4]
1000 1001 # only remove unknown files that we know we touched or
1001 1002 # created while patching
1002 1003 for f in unknown:
1003 1004 if f in all_files:
1004 1005 util.unlink(repo.wjoin(f))
1005 1006 self.ui.warn(_('done\n'))
1006 1007 raise
1007 1008
1008 1009 if not self.applied:
1009 1010 return ret[0]
1010 1011 top = self.applied[-1].name
1011 1012 if ret[0] and ret[0] > 1:
1012 1013 msg = _("errors during apply, please fix and refresh %s\n")
1013 1014 self.ui.write(msg % top)
1014 1015 else:
1015 1016 self.ui.write(_("now at: %s\n") % top)
1016 1017 return ret[0]
1017 1018
1018 1019 finally:
1019 1020 wlock.release()
1020 1021
1021 1022 def pop(self, repo, patch=None, force=False, update=True, all=False):
1022 1023 def getfile(f, rev, flags):
1023 1024 t = repo.file(f).read(rev)
1024 1025 repo.wwrite(f, t, flags)
1025 1026
1026 1027 wlock = repo.wlock()
1027 1028 try:
1028 1029 if patch:
1029 1030 # index, rev, patch
1030 1031 info = self.isapplied(patch)
1031 1032 if not info:
1032 1033 patch = self.lookup(patch)
1033 1034 info = self.isapplied(patch)
1034 1035 if not info:
1035 1036 raise util.Abort(_("patch %s is not applied") % patch)
1036 1037
1037 1038 if len(self.applied) == 0:
1038 1039 # Allow qpop -a to work repeatedly,
1039 1040 # but not qpop without an argument
1040 1041 self.ui.warn(_("no patches applied\n"))
1041 1042 return not all
1042 1043
1043 1044 if all:
1044 1045 start = 0
1045 1046 elif patch:
1046 1047 start = info[0] + 1
1047 1048 else:
1048 1049 start = len(self.applied) - 1
1049 1050
1050 1051 if start >= len(self.applied):
1051 1052 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1052 1053 return
1053 1054
1054 1055 if not update:
1055 1056 parents = repo.dirstate.parents()
1056 1057 rr = [ bin(x.rev) for x in self.applied ]
1057 1058 for p in parents:
1058 1059 if p in rr:
1059 1060 self.ui.warn(_("qpop: forcing dirstate update\n"))
1060 1061 update = True
1061 1062 else:
1062 1063 parents = [p.hex() for p in repo[None].parents()]
1063 1064 needupdate = False
1064 1065 for entry in self.applied[start:]:
1065 1066 if entry.rev in parents:
1066 1067 needupdate = True
1067 1068 break
1068 1069 update = needupdate
1069 1070
1070 1071 if not force and update:
1071 1072 self.check_localchanges(repo)
1072 1073
1073 1074 self.applied_dirty = 1
1074 1075 end = len(self.applied)
1075 1076 rev = bin(self.applied[start].rev)
1076 1077 if update:
1077 1078 top = self.check_toppatch(repo)
1078 1079
1079 1080 try:
1080 1081 heads = repo.changelog.heads(rev)
1081 1082 except error.LookupError:
1082 1083 node = short(rev)
1083 1084 raise util.Abort(_('trying to pop unknown node %s') % node)
1084 1085
1085 1086 if heads != [bin(self.applied[-1].rev)]:
1086 1087 raise util.Abort(_("popping would remove a revision not "
1087 1088 "managed by this patch queue"))
1088 1089
1089 1090 # we know there are no local changes, so we can make a simplified
1090 1091 # form of hg.update.
1091 1092 if update:
1092 1093 qp = self.qparents(repo, rev)
1093 1094 changes = repo.changelog.read(qp)
1094 1095 mmap = repo.manifest.read(changes[0])
1095 1096 m, a, r, d = repo.status(qp, top)[:4]
1096 1097 if d:
1097 1098 raise util.Abort(_("deletions found between repo revs"))
1098 1099 for f in m:
1099 1100 getfile(f, mmap[f], mmap.flags(f))
1100 1101 for f in r:
1101 1102 getfile(f, mmap[f], mmap.flags(f))
1102 1103 for f in m + r:
1103 1104 repo.dirstate.normal(f)
1104 1105 for f in a:
1105 1106 try:
1106 1107 os.unlink(repo.wjoin(f))
1107 1108 except OSError, e:
1108 1109 if e.errno != errno.ENOENT:
1109 1110 raise
1110 1111 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
1111 1112 except: pass
1112 1113 repo.dirstate.forget(f)
1113 1114 repo.dirstate.setparents(qp, nullid)
1114 1115 for patch in reversed(self.applied[start:end]):
1115 1116 self.ui.status(_("popping %s\n") % patch.name)
1116 1117 del self.applied[start:end]
1117 1118 self.strip(repo, rev, update=False, backup='strip')
1118 1119 if len(self.applied):
1119 1120 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1120 1121 else:
1121 1122 self.ui.write(_("patch queue now empty\n"))
1122 1123 finally:
1123 1124 wlock.release()
1124 1125
1125 1126 def diff(self, repo, pats, opts):
1126 1127 top = self.check_toppatch(repo)
1127 1128 if not top:
1128 1129 self.ui.write(_("no patches applied\n"))
1129 1130 return
1130 1131 qp = self.qparents(repo, top)
1131 1132 self._diffopts = patch.diffopts(self.ui, opts)
1132 1133 self.printdiff(repo, qp, files=pats, opts=opts)
1133 1134
1134 1135 def refresh(self, repo, pats=None, **opts):
1135 1136 if len(self.applied) == 0:
1136 1137 self.ui.write(_("no patches applied\n"))
1137 1138 return 1
1138 1139 msg = opts.get('msg', '').rstrip()
1139 1140 newuser = opts.get('user')
1140 1141 newdate = opts.get('date')
1141 1142 if newdate:
1142 1143 newdate = '%d %d' % util.parsedate(newdate)
1143 1144 wlock = repo.wlock()
1144 1145 try:
1145 1146 self.check_toppatch(repo)
1146 1147 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
1147 1148 top = bin(top)
1148 1149 if repo.changelog.heads(top) != [top]:
1149 1150 raise util.Abort(_("cannot refresh a revision with children"))
1150 1151 cparents = repo.changelog.parents(top)
1151 1152 patchparent = self.qparents(repo, top)
1152 1153 ph = patchheader(self.join(patchfn))
1153 1154
1154 1155 patchf = self.opener(patchfn, 'r')
1155 1156
1156 1157 # if the patch was a git patch, refresh it as a git patch
1157 1158 for line in patchf:
1158 1159 if line.startswith('diff --git'):
1159 1160 self.diffopts().git = True
1160 1161 break
1161 1162
1162 1163 if msg:
1163 1164 ph.setmessage(msg)
1164 1165 if newuser:
1165 1166 ph.setuser(newuser)
1166 1167 if newdate:
1167 1168 ph.setdate(newdate)
1168 1169
1169 1170 # only commit new patch when write is complete
1170 1171 patchf = self.opener(patchfn, 'w', atomictemp=True)
1171 1172
1172 1173 patchf.seek(0)
1173 1174 patchf.truncate()
1174 1175
1175 1176 comments = str(ph)
1176 1177 if comments:
1177 1178 patchf.write(comments)
1178 1179
1179 1180 if opts.get('git'):
1180 1181 self.diffopts().git = True
1181 1182 tip = repo.changelog.tip()
1182 1183 if top == tip:
1183 1184 # if the top of our patch queue is also the tip, there is an
1184 1185 # optimization here. We update the dirstate in place and strip
1185 1186 # off the tip commit. Then just commit the current directory
1186 1187 # tree. We can also send repo.commit the list of files
1187 1188 # changed to speed up the diff
1188 1189 #
1189 1190 # in short mode, we only diff the files included in the
1190 1191 # patch already plus specified files
1191 1192 #
1192 1193 # this should really read:
1193 1194 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1194 1195 # but we do it backwards to take advantage of manifest/chlog
1195 1196 # caching against the next repo.status call
1196 1197 #
1197 1198 mm, aa, dd, aa2 = repo.status(patchparent, tip)[:4]
1198 1199 changes = repo.changelog.read(tip)
1199 1200 man = repo.manifest.read(changes[0])
1200 1201 aaa = aa[:]
1201 1202 matchfn = cmdutil.match(repo, pats, opts)
1202 1203 if opts.get('short'):
1203 1204 # if amending a patch, we start with existing
1204 1205 # files plus specified files - unfiltered
1205 1206 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1206 1207 # filter with inc/exl options
1207 1208 matchfn = cmdutil.match(repo, opts=opts)
1208 1209 else:
1209 1210 match = cmdutil.matchall(repo)
1210 1211 m, a, r, d = repo.status(match=match)[:4]
1211 1212
1212 1213 # we might end up with files that were added between
1213 1214 # tip and the dirstate parent, but then changed in the
1214 1215 # local dirstate. in this case, we want them to only
1215 1216 # show up in the added section
1216 1217 for x in m:
1217 1218 if x not in aa:
1218 1219 mm.append(x)
1219 1220 # we might end up with files added by the local dirstate that
1220 1221 # were deleted by the patch. In this case, they should only
1221 1222 # show up in the changed section.
1222 1223 for x in a:
1223 1224 if x in dd:
1224 1225 del dd[dd.index(x)]
1225 1226 mm.append(x)
1226 1227 else:
1227 1228 aa.append(x)
1228 1229 # make sure any files deleted in the local dirstate
1229 1230 # are not in the add or change column of the patch
1230 1231 forget = []
1231 1232 for x in d + r:
1232 1233 if x in aa:
1233 1234 del aa[aa.index(x)]
1234 1235 forget.append(x)
1235 1236 continue
1236 1237 elif x in mm:
1237 1238 del mm[mm.index(x)]
1238 1239 dd.append(x)
1239 1240
1240 1241 m = list(set(mm))
1241 1242 r = list(set(dd))
1242 1243 a = list(set(aa))
1243 1244 c = [filter(matchfn, l) for l in (m, a, r)]
1244 1245 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1245 1246 chunks = patch.diff(repo, patchparent, match=match,
1246 1247 changes=c, opts=self.diffopts())
1247 1248 for chunk in chunks:
1248 1249 patchf.write(chunk)
1249 1250
1250 1251 try:
1251 1252 if self.diffopts().git:
1252 1253 copies = {}
1253 1254 for dst in a:
1254 1255 src = repo.dirstate.copied(dst)
1255 1256 # during qfold, the source file for copies may
1256 1257 # be removed. Treat this as a simple add.
1257 1258 if src is not None and src in repo.dirstate:
1258 1259 copies.setdefault(src, []).append(dst)
1259 1260 repo.dirstate.add(dst)
1260 1261 # remember the copies between patchparent and tip
1261 1262 for dst in aaa:
1262 1263 f = repo.file(dst)
1263 1264 src = f.renamed(man[dst])
1264 1265 if src:
1265 1266 copies.setdefault(src[0], []).extend(copies.get(dst, []))
1266 1267 if dst in a:
1267 1268 copies[src[0]].append(dst)
1268 1269 # we can't copy a file created by the patch itself
1269 1270 if dst in copies:
1270 1271 del copies[dst]
1271 1272 for src, dsts in copies.iteritems():
1272 1273 for dst in dsts:
1273 1274 repo.dirstate.copy(src, dst)
1274 1275 else:
1275 1276 for dst in a:
1276 1277 repo.dirstate.add(dst)
1277 1278 # Drop useless copy information
1278 1279 for f in list(repo.dirstate.copies()):
1279 1280 repo.dirstate.copy(None, f)
1280 1281 for f in r:
1281 1282 repo.dirstate.remove(f)
1282 1283 # if the patch excludes a modified file, mark that
1283 1284 # file with mtime=0 so status can see it.
1284 1285 mm = []
1285 1286 for i in xrange(len(m)-1, -1, -1):
1286 1287 if not matchfn(m[i]):
1287 1288 mm.append(m[i])
1288 1289 del m[i]
1289 1290 for f in m:
1290 1291 repo.dirstate.normal(f)
1291 1292 for f in mm:
1292 1293 repo.dirstate.normallookup(f)
1293 1294 for f in forget:
1294 1295 repo.dirstate.forget(f)
1295 1296
1296 1297 if not msg:
1297 1298 if not ph.message:
1298 1299 message = "[mq]: %s\n" % patchfn
1299 1300 else:
1300 1301 message = "\n".join(ph.message)
1301 1302 else:
1302 1303 message = msg
1303 1304
1304 1305 user = ph.user or changes[1]
1305 1306
1306 1307 # assumes strip can roll itself back if interrupted
1307 1308 repo.dirstate.setparents(*cparents)
1308 1309 self.applied.pop()
1309 1310 self.applied_dirty = 1
1310 1311 self.strip(repo, top, update=False,
1311 1312 backup='strip')
1312 1313 except:
1313 1314 repo.dirstate.invalidate()
1314 1315 raise
1315 1316
1316 1317 try:
1317 1318 # might be nice to attempt to roll back strip after this
1318 1319 patchf.rename()
1319 1320 n = repo.commit(message, user, ph.date, match=match,
1320 1321 force=True)
1321 1322 self.applied.append(statusentry(hex(n), patchfn))
1322 1323 except:
1323 1324 ctx = repo[cparents[0]]
1324 1325 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1325 1326 self.save_dirty()
1326 1327 self.ui.warn(_('refresh interrupted while patch was popped! '
1327 1328 '(revert --all, qpush to recover)\n'))
1328 1329 raise
1329 1330 else:
1330 1331 self.printdiff(repo, patchparent, fp=patchf)
1331 1332 patchf.rename()
1332 1333 added = repo.status()[1]
1333 1334 for a in added:
1334 1335 f = repo.wjoin(a)
1335 1336 try:
1336 1337 os.unlink(f)
1337 1338 except OSError, e:
1338 1339 if e.errno != errno.ENOENT:
1339 1340 raise
1340 1341 try: os.removedirs(os.path.dirname(f))
1341 1342 except: pass
1342 1343 # forget the file copies in the dirstate
1343 1344 # push should readd the files later on
1344 1345 repo.dirstate.forget(a)
1345 1346 self.pop(repo, force=True)
1346 1347 self.push(repo, force=True)
1347 1348 finally:
1348 1349 wlock.release()
1349 1350 self.removeundo(repo)
1350 1351
1351 1352 def init(self, repo, create=False):
1352 1353 if not create and os.path.isdir(self.path):
1353 1354 raise util.Abort(_("patch queue directory already exists"))
1354 1355 try:
1355 1356 os.mkdir(self.path)
1356 1357 except OSError, inst:
1357 1358 if inst.errno != errno.EEXIST or not create:
1358 1359 raise
1359 1360 if create:
1360 1361 return self.qrepo(create=True)
1361 1362
1362 1363 def unapplied(self, repo, patch=None):
1363 1364 if patch and patch not in self.series:
1364 1365 raise util.Abort(_("patch %s is not in series file") % patch)
1365 1366 if not patch:
1366 1367 start = self.series_end()
1367 1368 else:
1368 1369 start = self.series.index(patch) + 1
1369 1370 unapplied = []
1370 1371 for i in xrange(start, len(self.series)):
1371 1372 pushable, reason = self.pushable(i)
1372 1373 if pushable:
1373 1374 unapplied.append((i, self.series[i]))
1374 1375 self.explain_pushable(i)
1375 1376 return unapplied
1376 1377
1377 1378 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1378 1379 summary=False):
1379 1380 def displayname(pfx, patchname):
1380 1381 if summary:
1381 1382 ph = patchheader(self.join(patchname))
1382 1383 msg = ph.message
1383 1384 msg = msg and ': ' + msg[0] or ': '
1384 1385 else:
1385 1386 msg = ''
1386 1387 msg = "%s%s%s" % (pfx, patchname, msg)
1387 1388 if self.ui.interactive():
1388 1389 msg = util.ellipsis(msg, util.termwidth())
1389 1390 self.ui.write(msg + '\n')
1390 1391
1391 1392 applied = set([p.name for p in self.applied])
1392 1393 if length is None:
1393 1394 length = len(self.series) - start
1394 1395 if not missing:
1395 1396 if self.ui.verbose:
1396 1397 idxwidth = len(str(start+length - 1))
1397 1398 for i in xrange(start, start+length):
1398 1399 patch = self.series[i]
1399 1400 if patch in applied:
1400 1401 stat = 'A'
1401 1402 elif self.pushable(i)[0]:
1402 1403 stat = 'U'
1403 1404 else:
1404 1405 stat = 'G'
1405 1406 pfx = ''
1406 1407 if self.ui.verbose:
1407 1408 pfx = '%*d %s ' % (idxwidth, i, stat)
1408 1409 elif status and status != stat:
1409 1410 continue
1410 1411 displayname(pfx, patch)
1411 1412 else:
1412 1413 msng_list = []
1413 1414 for root, dirs, files in os.walk(self.path):
1414 1415 d = root[len(self.path) + 1:]
1415 1416 for f in files:
1416 1417 fl = os.path.join(d, f)
1417 1418 if (fl not in self.series and
1418 1419 fl not in (self.status_path, self.series_path,
1419 1420 self.guards_path)
1420 1421 and not fl.startswith('.')):
1421 1422 msng_list.append(fl)
1422 1423 for x in sorted(msng_list):
1423 1424 pfx = self.ui.verbose and ('D ') or ''
1424 1425 displayname(pfx, x)
1425 1426
1426 1427 def issaveline(self, l):
1427 1428 if l.name == '.hg.patches.save.line':
1428 1429 return True
1429 1430
1430 1431 def qrepo(self, create=False):
1431 1432 if create or os.path.isdir(self.join(".hg")):
1432 1433 return hg.repository(self.ui, path=self.path, create=create)
1433 1434
1434 1435 def restore(self, repo, rev, delete=None, qupdate=None):
1435 1436 c = repo.changelog.read(rev)
1436 1437 desc = c[4].strip()
1437 1438 lines = desc.splitlines()
1438 1439 i = 0
1439 1440 datastart = None
1440 1441 series = []
1441 1442 applied = []
1442 1443 qpp = None
1443 1444 for i, line in enumerate(lines):
1444 1445 if line == 'Patch Data:':
1445 1446 datastart = i + 1
1446 1447 elif line.startswith('Dirstate:'):
1447 1448 l = line.rstrip()
1448 1449 l = l[10:].split(' ')
1449 1450 qpp = [ bin(x) for x in l ]
1450 1451 elif datastart != None:
1451 1452 l = line.rstrip()
1452 1453 se = statusentry(l)
1453 1454 file_ = se.name
1454 1455 if se.rev:
1455 1456 applied.append(se)
1456 1457 else:
1457 1458 series.append(file_)
1458 1459 if datastart is None:
1459 1460 self.ui.warn(_("No saved patch data found\n"))
1460 1461 return 1
1461 1462 self.ui.warn(_("restoring status: %s\n") % lines[0])
1462 1463 self.full_series = series
1463 1464 self.applied = applied
1464 1465 self.parse_series()
1465 1466 self.series_dirty = 1
1466 1467 self.applied_dirty = 1
1467 1468 heads = repo.changelog.heads()
1468 1469 if delete:
1469 1470 if rev not in heads:
1470 1471 self.ui.warn(_("save entry has children, leaving it alone\n"))
1471 1472 else:
1472 1473 self.ui.warn(_("removing save entry %s\n") % short(rev))
1473 1474 pp = repo.dirstate.parents()
1474 1475 if rev in pp:
1475 1476 update = True
1476 1477 else:
1477 1478 update = False
1478 1479 self.strip(repo, rev, update=update, backup='strip')
1479 1480 if qpp:
1480 1481 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1481 1482 (short(qpp[0]), short(qpp[1])))
1482 1483 if qupdate:
1483 1484 self.ui.status(_("queue directory updating\n"))
1484 1485 r = self.qrepo()
1485 1486 if not r:
1486 1487 self.ui.warn(_("Unable to load queue repository\n"))
1487 1488 return 1
1488 1489 hg.clean(r, qpp[0])
1489 1490
1490 1491 def save(self, repo, msg=None):
1491 1492 if len(self.applied) == 0:
1492 1493 self.ui.warn(_("save: no patches applied, exiting\n"))
1493 1494 return 1
1494 1495 if self.issaveline(self.applied[-1]):
1495 1496 self.ui.warn(_("status is already saved\n"))
1496 1497 return 1
1497 1498
1498 1499 ar = [ ':' + x for x in self.full_series ]
1499 1500 if not msg:
1500 1501 msg = _("hg patches saved state")
1501 1502 else:
1502 1503 msg = "hg patches: " + msg.rstrip('\r\n')
1503 1504 r = self.qrepo()
1504 1505 if r:
1505 1506 pp = r.dirstate.parents()
1506 1507 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1507 1508 msg += "\n\nPatch Data:\n"
1508 1509 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1509 1510 "\n".join(ar) + '\n' or "")
1510 1511 n = repo.commit(text, force=True)
1511 1512 if not n:
1512 1513 self.ui.warn(_("repo commit failed\n"))
1513 1514 return 1
1514 1515 self.applied.append(statusentry(hex(n),'.hg.patches.save.line'))
1515 1516 self.applied_dirty = 1
1516 1517 self.removeundo(repo)
1517 1518
1518 1519 def full_series_end(self):
1519 1520 if len(self.applied) > 0:
1520 1521 p = self.applied[-1].name
1521 1522 end = self.find_series(p)
1522 1523 if end is None:
1523 1524 return len(self.full_series)
1524 1525 return end + 1
1525 1526 return 0
1526 1527
1527 1528 def series_end(self, all_patches=False):
1528 1529 """If all_patches is False, return the index of the next pushable patch
1529 1530 in the series, or the series length. If all_patches is True, return the
1530 1531 index of the first patch past the last applied one.
1531 1532 """
1532 1533 end = 0
1533 1534 def next(start):
1534 1535 if all_patches:
1535 1536 return start
1536 1537 i = start
1537 1538 while i < len(self.series):
1538 1539 p, reason = self.pushable(i)
1539 1540 if p:
1540 1541 break
1541 1542 self.explain_pushable(i)
1542 1543 i += 1
1543 1544 return i
1544 1545 if len(self.applied) > 0:
1545 1546 p = self.applied[-1].name
1546 1547 try:
1547 1548 end = self.series.index(p)
1548 1549 except ValueError:
1549 1550 return 0
1550 1551 return next(end + 1)
1551 1552 return next(end)
1552 1553
1553 1554 def appliedname(self, index):
1554 1555 pname = self.applied[index].name
1555 1556 if not self.ui.verbose:
1556 1557 p = pname
1557 1558 else:
1558 1559 p = str(self.series.index(pname)) + " " + pname
1559 1560 return p
1560 1561
1561 1562 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1562 1563 force=None, git=False):
1563 1564 def checkseries(patchname):
1564 1565 if patchname in self.series:
1565 1566 raise util.Abort(_('patch %s is already in the series file')
1566 1567 % patchname)
1567 1568 def checkfile(patchname):
1568 1569 if not force and os.path.exists(self.join(patchname)):
1569 1570 raise util.Abort(_('patch "%s" already exists')
1570 1571 % patchname)
1571 1572
1572 1573 if rev:
1573 1574 if files:
1574 1575 raise util.Abort(_('option "-r" not valid when importing '
1575 1576 'files'))
1576 1577 rev = cmdutil.revrange(repo, rev)
1577 1578 rev.sort(reverse=True)
1578 1579 if (len(files) > 1 or len(rev) > 1) and patchname:
1579 1580 raise util.Abort(_('option "-n" not valid when importing multiple '
1580 1581 'patches'))
1581 1582 i = 0
1582 1583 added = []
1583 1584 if rev:
1584 1585 # If mq patches are applied, we can only import revisions
1585 1586 # that form a linear path to qbase.
1586 1587 # Otherwise, they should form a linear path to a head.
1587 1588 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1588 1589 if len(heads) > 1:
1589 1590 raise util.Abort(_('revision %d is the root of more than one '
1590 1591 'branch') % rev[-1])
1591 1592 if self.applied:
1592 1593 base = hex(repo.changelog.node(rev[0]))
1593 1594 if base in [n.rev for n in self.applied]:
1594 1595 raise util.Abort(_('revision %d is already managed')
1595 1596 % rev[0])
1596 1597 if heads != [bin(self.applied[-1].rev)]:
1597 1598 raise util.Abort(_('revision %d is not the parent of '
1598 1599 'the queue') % rev[0])
1599 1600 base = repo.changelog.rev(bin(self.applied[0].rev))
1600 1601 lastparent = repo.changelog.parentrevs(base)[0]
1601 1602 else:
1602 1603 if heads != [repo.changelog.node(rev[0])]:
1603 1604 raise util.Abort(_('revision %d has unmanaged children')
1604 1605 % rev[0])
1605 1606 lastparent = None
1606 1607
1607 1608 if git:
1608 1609 self.diffopts().git = True
1609 1610
1610 1611 for r in rev:
1611 1612 p1, p2 = repo.changelog.parentrevs(r)
1612 1613 n = repo.changelog.node(r)
1613 1614 if p2 != nullrev:
1614 1615 raise util.Abort(_('cannot import merge revision %d') % r)
1615 1616 if lastparent and lastparent != r:
1616 1617 raise util.Abort(_('revision %d is not the parent of %d')
1617 1618 % (r, lastparent))
1618 1619 lastparent = p1
1619 1620
1620 1621 if not patchname:
1621 1622 patchname = normname('%d.diff' % r)
1622 1623 self.check_reserved_name(patchname)
1623 1624 checkseries(patchname)
1624 1625 checkfile(patchname)
1625 1626 self.full_series.insert(0, patchname)
1626 1627
1627 1628 patchf = self.opener(patchname, "w")
1628 1629 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1629 1630 patchf.close()
1630 1631
1631 1632 se = statusentry(hex(n), patchname)
1632 1633 self.applied.insert(0, se)
1633 1634
1634 1635 added.append(patchname)
1635 1636 patchname = None
1636 1637 self.parse_series()
1637 1638 self.applied_dirty = 1
1638 1639
1639 1640 for filename in files:
1640 1641 if existing:
1641 1642 if filename == '-':
1642 1643 raise util.Abort(_('-e is incompatible with import from -'))
1643 1644 if not patchname:
1644 1645 patchname = normname(filename)
1645 1646 self.check_reserved_name(patchname)
1646 1647 if not os.path.isfile(self.join(patchname)):
1647 1648 raise util.Abort(_("patch %s does not exist") % patchname)
1648 1649 else:
1649 1650 try:
1650 1651 if filename == '-':
1651 1652 if not patchname:
1652 1653 raise util.Abort(_('need --name to import a patch from -'))
1653 1654 text = sys.stdin.read()
1654 1655 else:
1655 1656 text = url.open(self.ui, filename).read()
1656 1657 except (OSError, IOError):
1657 1658 raise util.Abort(_("unable to read %s") % filename)
1658 1659 if not patchname:
1659 1660 patchname = normname(os.path.basename(filename))
1660 1661 self.check_reserved_name(patchname)
1661 1662 checkfile(patchname)
1662 1663 patchf = self.opener(patchname, "w")
1663 1664 patchf.write(text)
1664 1665 if not force:
1665 1666 checkseries(patchname)
1666 1667 if patchname not in self.series:
1667 1668 index = self.full_series_end() + i
1668 1669 self.full_series[index:index] = [patchname]
1669 1670 self.parse_series()
1670 1671 self.ui.warn(_("adding %s to series file\n") % patchname)
1671 1672 i += 1
1672 1673 added.append(patchname)
1673 1674 patchname = None
1674 1675 self.series_dirty = 1
1675 1676 qrepo = self.qrepo()
1676 1677 if qrepo:
1677 1678 qrepo.add(added)
1678 1679
1679 1680 def delete(ui, repo, *patches, **opts):
1680 1681 """remove patches from queue
1681 1682
1682 1683 The patches must not be applied, and at least one patch is required. With
1683 1684 -k/--keep, the patch files are preserved in the patch directory.
1684 1685
1685 1686 To stop managing a patch and move it into permanent history,
1686 1687 use the qfinish command."""
1687 1688 q = repo.mq
1688 1689 q.delete(repo, patches, opts)
1689 1690 q.save_dirty()
1690 1691 return 0
1691 1692
1692 1693 def applied(ui, repo, patch=None, **opts):
1693 1694 """print the patches already applied"""
1694 1695
1695 1696 q = repo.mq
1696 1697 l = len(q.applied)
1697 1698
1698 1699 if patch:
1699 1700 if patch not in q.series:
1700 1701 raise util.Abort(_("patch %s is not in series file") % patch)
1701 1702 end = q.series.index(patch) + 1
1702 1703 else:
1703 1704 end = q.series_end(True)
1704 1705
1705 1706 if opts.get('last') and not end:
1706 1707 ui.write(_("no patches applied\n"))
1707 1708 return 1
1708 1709 elif opts.get('last') and end == 1:
1709 1710 ui.write(_("only one patch applied\n"))
1710 1711 return 1
1711 1712 elif opts.get('last'):
1712 1713 start = end - 2
1713 1714 end = 1
1714 1715 else:
1715 1716 start = 0
1716 1717
1717 1718 return q.qseries(repo, length=end, start=start, status='A',
1718 1719 summary=opts.get('summary'))
1719 1720
1720 1721 def unapplied(ui, repo, patch=None, **opts):
1721 1722 """print the patches not yet applied"""
1722 1723
1723 1724 q = repo.mq
1724 1725 if patch:
1725 1726 if patch not in q.series:
1726 1727 raise util.Abort(_("patch %s is not in series file") % patch)
1727 1728 start = q.series.index(patch) + 1
1728 1729 else:
1729 1730 start = q.series_end(True)
1730 1731
1731 1732 if start == len(q.series) and opts.get('first'):
1732 1733 ui.write(_("all patches applied\n"))
1733 1734 return 1
1734 1735
1735 1736 length = opts.get('first') and 1 or None
1736 1737 return q.qseries(repo, start=start, length=length, status='U',
1737 1738 summary=opts.get('summary'))
1738 1739
1739 1740 def qimport(ui, repo, *filename, **opts):
1740 1741 """import a patch
1741 1742
1742 1743 The patch is inserted into the series after the last applied
1743 1744 patch. If no patches have been applied, qimport prepends the patch
1744 1745 to the series.
1745 1746
1746 1747 The patch will have the same name as its source file unless you
1747 1748 give it a new one with -n/--name.
1748 1749
1749 1750 You can register an existing patch inside the patch directory with
1750 1751 the -e/--existing flag.
1751 1752
1752 1753 With -f/--force, an existing patch of the same name will be
1753 1754 overwritten.
1754 1755
1755 1756 An existing changeset may be placed under mq control with -r/--rev
1756 1757 (e.g. qimport --rev tip -n patch will place tip under mq control).
1757 1758 With -g/--git, patches imported with --rev will use the git diff
1758 1759 format. See the diffs help topic for information on why this is
1759 1760 important for preserving rename/copy information and permission
1760 1761 changes.
1761 1762
1762 1763 To import a patch from standard input, pass - as the patch file.
1763 1764 When importing from standard input, a patch name must be specified
1764 1765 using the --name flag.
1765 1766 """
1766 1767 q = repo.mq
1767 1768 q.qimport(repo, filename, patchname=opts['name'],
1768 1769 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1769 1770 git=opts['git'])
1770 1771 q.save_dirty()
1771 1772
1772 1773 if opts.get('push') and not opts.get('rev'):
1773 1774 return q.push(repo, None)
1774 1775 return 0
1775 1776
1776 1777 def init(ui, repo, **opts):
1777 1778 """init a new queue repository
1778 1779
1779 1780 The queue repository is unversioned by default. If
1780 1781 -c/--create-repo is specified, qinit will create a separate nested
1781 1782 repository for patches (qinit -c may also be run later to convert
1782 1783 an unversioned patch repository into a versioned one). You can use
1783 1784 qcommit to commit changes to this queue repository."""
1784 1785 q = repo.mq
1785 1786 r = q.init(repo, create=opts['create_repo'])
1786 1787 q.save_dirty()
1787 1788 if r:
1788 1789 if not os.path.exists(r.wjoin('.hgignore')):
1789 1790 fp = r.wopener('.hgignore', 'w')
1790 1791 fp.write('^\\.hg\n')
1791 1792 fp.write('^\\.mq\n')
1792 1793 fp.write('syntax: glob\n')
1793 1794 fp.write('status\n')
1794 1795 fp.write('guards\n')
1795 1796 fp.close()
1796 1797 if not os.path.exists(r.wjoin('series')):
1797 1798 r.wopener('series', 'w').close()
1798 1799 r.add(['.hgignore', 'series'])
1799 1800 commands.add(ui, r)
1800 1801 return 0
1801 1802
1802 1803 def clone(ui, source, dest=None, **opts):
1803 1804 '''clone main and patch repository at same time
1804 1805
1805 1806 If source is local, destination will have no patches applied. If
1806 1807 source is remote, this command can not check if patches are
1807 1808 applied in source, so cannot guarantee that patches are not
1808 1809 applied in destination. If you clone remote repository, be sure
1809 1810 before that it has no patches applied.
1810 1811
1811 1812 Source patch repository is looked for in <src>/.hg/patches by
1812 1813 default. Use -p <url> to change.
1813 1814
1814 1815 The patch directory must be a nested Mercurial repository, as
1815 1816 would be created by qinit -c.
1816 1817 '''
1817 1818 def patchdir(repo):
1818 1819 url = repo.url()
1819 1820 if url.endswith('/'):
1820 1821 url = url[:-1]
1821 1822 return url + '/.hg/patches'
1822 1823 if dest is None:
1823 1824 dest = hg.defaultdest(source)
1824 1825 sr = hg.repository(cmdutil.remoteui(ui, opts), ui.expandpath(source))
1825 1826 if opts['patches']:
1826 1827 patchespath = ui.expandpath(opts['patches'])
1827 1828 else:
1828 1829 patchespath = patchdir(sr)
1829 1830 try:
1830 1831 hg.repository(ui, patchespath)
1831 1832 except error.RepoError:
1832 1833 raise util.Abort(_('versioned patch repository not found'
1833 1834 ' (see qinit -c)'))
1834 1835 qbase, destrev = None, None
1835 1836 if sr.local():
1836 1837 if sr.mq.applied:
1837 1838 qbase = bin(sr.mq.applied[0].rev)
1838 1839 if not hg.islocal(dest):
1839 1840 heads = set(sr.heads())
1840 1841 destrev = list(heads.difference(sr.heads(qbase)))
1841 1842 destrev.append(sr.changelog.parents(qbase)[0])
1842 1843 elif sr.capable('lookup'):
1843 1844 try:
1844 1845 qbase = sr.lookup('qbase')
1845 1846 except error.RepoError:
1846 1847 pass
1847 1848 ui.note(_('cloning main repository\n'))
1848 1849 sr, dr = hg.clone(ui, sr.url(), dest,
1849 1850 pull=opts['pull'],
1850 1851 rev=destrev,
1851 1852 update=False,
1852 1853 stream=opts['uncompressed'])
1853 1854 ui.note(_('cloning patch repository\n'))
1854 1855 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1855 1856 pull=opts['pull'], update=not opts['noupdate'],
1856 1857 stream=opts['uncompressed'])
1857 1858 if dr.local():
1858 1859 if qbase:
1859 1860 ui.note(_('stripping applied patches from destination '
1860 1861 'repository\n'))
1861 1862 dr.mq.strip(dr, qbase, update=False, backup=None)
1862 1863 if not opts['noupdate']:
1863 1864 ui.note(_('updating destination repository\n'))
1864 1865 hg.update(dr, dr.changelog.tip())
1865 1866
1866 1867 def commit(ui, repo, *pats, **opts):
1867 1868 """commit changes in the queue repository"""
1868 1869 q = repo.mq
1869 1870 r = q.qrepo()
1870 1871 if not r: raise util.Abort('no queue repository')
1871 1872 commands.commit(r.ui, r, *pats, **opts)
1872 1873
1873 1874 def series(ui, repo, **opts):
1874 1875 """print the entire series file"""
1875 1876 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1876 1877 return 0
1877 1878
1878 1879 def top(ui, repo, **opts):
1879 1880 """print the name of the current patch"""
1880 1881 q = repo.mq
1881 1882 t = q.applied and q.series_end(True) or 0
1882 1883 if t:
1883 1884 return q.qseries(repo, start=t-1, length=1, status='A',
1884 1885 summary=opts.get('summary'))
1885 1886 else:
1886 1887 ui.write(_("no patches applied\n"))
1887 1888 return 1
1888 1889
1889 1890 def next(ui, repo, **opts):
1890 1891 """print the name of the next patch"""
1891 1892 q = repo.mq
1892 1893 end = q.series_end()
1893 1894 if end == len(q.series):
1894 1895 ui.write(_("all patches applied\n"))
1895 1896 return 1
1896 1897 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1897 1898
1898 1899 def prev(ui, repo, **opts):
1899 1900 """print the name of the previous patch"""
1900 1901 q = repo.mq
1901 1902 l = len(q.applied)
1902 1903 if l == 1:
1903 1904 ui.write(_("only one patch applied\n"))
1904 1905 return 1
1905 1906 if not l:
1906 1907 ui.write(_("no patches applied\n"))
1907 1908 return 1
1908 1909 return q.qseries(repo, start=l-2, length=1, status='A',
1909 1910 summary=opts.get('summary'))
1910 1911
1911 1912 def setupheaderopts(ui, opts):
1912 1913 def do(opt, val):
1913 1914 if not opts[opt] and opts['current' + opt]:
1914 1915 opts[opt] = val
1915 1916 do('user', ui.username())
1916 1917 do('date', "%d %d" % util.makedate())
1917 1918
1918 1919 def new(ui, repo, patch, *args, **opts):
1919 1920 """create a new patch
1920 1921
1921 1922 qnew creates a new patch on top of the currently-applied patch (if
1922 1923 any). It will refuse to run if there are any outstanding changes
1923 1924 unless -f/--force is specified, in which case the patch will be
1924 1925 initialized with them. You may also use -I/--include,
1925 1926 -X/--exclude, and/or a list of files after the patch name to add
1926 1927 only changes to matching files to the new patch, leaving the rest
1927 1928 as uncommitted modifications.
1928 1929
1929 1930 -u/--user and -d/--date can be used to set the (given) user and
1930 1931 date, respectively. -U/--currentuser and -D/--currentdate set user
1931 1932 to current user and date to current date.
1932 1933
1933 1934 -e/--edit, -m/--message or -l/--logfile set the patch header as
1934 1935 well as the commit message. If none is specified, the header is
1935 1936 empty and the commit message is '[mq]: PATCH'.
1936 1937
1937 1938 Use the -g/--git option to keep the patch in the git extended diff
1938 1939 format. Read the diffs help topic for more information on why this
1939 1940 is important for preserving permission changes and copy/rename
1940 1941 information.
1941 1942 """
1942 1943 msg = cmdutil.logmessage(opts)
1943 1944 def getmsg(): return ui.edit(msg, ui.username())
1944 1945 q = repo.mq
1945 1946 opts['msg'] = msg
1946 1947 if opts.get('edit'):
1947 1948 opts['msg'] = getmsg
1948 1949 else:
1949 1950 opts['msg'] = msg
1950 1951 setupheaderopts(ui, opts)
1951 1952 q.new(repo, patch, *args, **opts)
1952 1953 q.save_dirty()
1953 1954 return 0
1954 1955
1955 1956 def refresh(ui, repo, *pats, **opts):
1956 1957 """update the current patch
1957 1958
1958 1959 If any file patterns are provided, the refreshed patch will
1959 1960 contain only the modifications that match those patterns; the
1960 1961 remaining modifications will remain in the working directory.
1961 1962
1962 1963 If -s/--short is specified, files currently included in the patch
1963 1964 will be refreshed just like matched files and remain in the patch.
1964 1965
1965 1966 hg add/remove/copy/rename work as usual, though you might want to
1966 1967 use git-style patches (-g/--git or [diff] git=1) to track copies
1967 1968 and renames. See the diffs help topic for more information on the
1968 1969 git diff format.
1969 1970 """
1970 1971 q = repo.mq
1971 1972 message = cmdutil.logmessage(opts)
1972 1973 if opts['edit']:
1973 1974 if not q.applied:
1974 1975 ui.write(_("no patches applied\n"))
1975 1976 return 1
1976 1977 if message:
1977 1978 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1978 1979 patch = q.applied[-1].name
1979 1980 ph = patchheader(q.join(patch))
1980 1981 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
1981 1982 setupheaderopts(ui, opts)
1982 1983 ret = q.refresh(repo, pats, msg=message, **opts)
1983 1984 q.save_dirty()
1984 1985 return ret
1985 1986
1986 1987 def diff(ui, repo, *pats, **opts):
1987 1988 """diff of the current patch and subsequent modifications
1988 1989
1989 1990 Shows a diff which includes the current patch as well as any
1990 1991 changes which have been made in the working directory since the
1991 1992 last refresh (thus showing what the current patch would become
1992 1993 after a qrefresh).
1993 1994
1994 1995 Use 'hg diff' if you only want to see the changes made since the
1995 1996 last qrefresh, or 'hg export qtip' if you want to see changes made
1996 1997 by the current patch without including changes made since the
1997 1998 qrefresh.
1998 1999 """
1999 2000 repo.mq.diff(repo, pats, opts)
2000 2001 return 0
2001 2002
2002 2003 def fold(ui, repo, *files, **opts):
2003 2004 """fold the named patches into the current patch
2004 2005
2005 2006 Patches must not yet be applied. Each patch will be successively
2006 2007 applied to the current patch in the order given. If all the
2007 2008 patches apply successfully, the current patch will be refreshed
2008 2009 with the new cumulative patch, and the folded patches will be
2009 2010 deleted. With -k/--keep, the folded patch files will not be
2010 2011 removed afterwards.
2011 2012
2012 2013 The header for each folded patch will be concatenated with the
2013 2014 current patch header, separated by a line of '* * *'."""
2014 2015
2015 2016 q = repo.mq
2016 2017
2017 2018 if not files:
2018 2019 raise util.Abort(_('qfold requires at least one patch name'))
2019 2020 if not q.check_toppatch(repo):
2020 2021 raise util.Abort(_('No patches applied'))
2021 2022 q.check_localchanges(repo)
2022 2023
2023 2024 message = cmdutil.logmessage(opts)
2024 2025 if opts['edit']:
2025 2026 if message:
2026 2027 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2027 2028
2028 2029 parent = q.lookup('qtip')
2029 2030 patches = []
2030 2031 messages = []
2031 2032 for f in files:
2032 2033 p = q.lookup(f)
2033 2034 if p in patches or p == parent:
2034 2035 ui.warn(_('Skipping already folded patch %s') % p)
2035 2036 if q.isapplied(p):
2036 2037 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2037 2038 patches.append(p)
2038 2039
2039 2040 for p in patches:
2040 2041 if not message:
2041 2042 ph = patchheader(q.join(p))
2042 2043 if ph.message:
2043 2044 messages.append(ph.message)
2044 2045 pf = q.join(p)
2045 2046 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2046 2047 if not patchsuccess:
2047 2048 raise util.Abort(_('Error folding patch %s') % p)
2048 2049 patch.updatedir(ui, repo, files)
2049 2050
2050 2051 if not message:
2051 2052 ph = patchheader(q.join(parent))
2052 2053 message, user = ph.message, ph.user
2053 2054 for msg in messages:
2054 2055 message.append('* * *')
2055 2056 message.extend(msg)
2056 2057 message = '\n'.join(message)
2057 2058
2058 2059 if opts['edit']:
2059 2060 message = ui.edit(message, user or ui.username())
2060 2061
2061 2062 q.refresh(repo, msg=message)
2062 2063 q.delete(repo, patches, opts)
2063 2064 q.save_dirty()
2064 2065
2065 2066 def goto(ui, repo, patch, **opts):
2066 2067 '''push or pop patches until named patch is at top of stack'''
2067 2068 q = repo.mq
2068 2069 patch = q.lookup(patch)
2069 2070 if q.isapplied(patch):
2070 2071 ret = q.pop(repo, patch, force=opts['force'])
2071 2072 else:
2072 2073 ret = q.push(repo, patch, force=opts['force'])
2073 2074 q.save_dirty()
2074 2075 return ret
2075 2076
2076 2077 def guard(ui, repo, *args, **opts):
2077 2078 '''set or print guards for a patch
2078 2079
2079 2080 Guards control whether a patch can be pushed. A patch with no
2080 2081 guards is always pushed. A patch with a positive guard ("+foo") is
2081 2082 pushed only if the qselect command has activated it. A patch with
2082 2083 a negative guard ("-foo") is never pushed if the qselect command
2083 2084 has activated it.
2084 2085
2085 2086 With no arguments, print the currently active guards.
2086 2087 With arguments, set guards for the named patch.
2087 2088 NOTE: Specifying negative guards now requires '--'.
2088 2089
2089 2090 To set guards on another patch:
2090 2091 hg qguard -- other.patch +2.6.17 -stable
2091 2092 '''
2092 2093 def status(idx):
2093 2094 guards = q.series_guards[idx] or ['unguarded']
2094 2095 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
2095 2096 q = repo.mq
2096 2097 patch = None
2097 2098 args = list(args)
2098 2099 if opts['list']:
2099 2100 if args or opts['none']:
2100 2101 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2101 2102 for i in xrange(len(q.series)):
2102 2103 status(i)
2103 2104 return
2104 2105 if not args or args[0][0:1] in '-+':
2105 2106 if not q.applied:
2106 2107 raise util.Abort(_('no patches applied'))
2107 2108 patch = q.applied[-1].name
2108 2109 if patch is None and args[0][0:1] not in '-+':
2109 2110 patch = args.pop(0)
2110 2111 if patch is None:
2111 2112 raise util.Abort(_('no patch to work with'))
2112 2113 if args or opts['none']:
2113 2114 idx = q.find_series(patch)
2114 2115 if idx is None:
2115 2116 raise util.Abort(_('no patch named %s') % patch)
2116 2117 q.set_guards(idx, args)
2117 2118 q.save_dirty()
2118 2119 else:
2119 2120 status(q.series.index(q.lookup(patch)))
2120 2121
2121 2122 def header(ui, repo, patch=None):
2122 2123 """print the header of the topmost or specified patch"""
2123 2124 q = repo.mq
2124 2125
2125 2126 if patch:
2126 2127 patch = q.lookup(patch)
2127 2128 else:
2128 2129 if not q.applied:
2129 2130 ui.write('no patches applied\n')
2130 2131 return 1
2131 2132 patch = q.lookup('qtip')
2132 2133 ph = patchheader(repo.mq.join(patch))
2133 2134
2134 2135 ui.write('\n'.join(ph.message) + '\n')
2135 2136
2136 2137 def lastsavename(path):
2137 2138 (directory, base) = os.path.split(path)
2138 2139 names = os.listdir(directory)
2139 2140 namere = re.compile("%s.([0-9]+)" % base)
2140 2141 maxindex = None
2141 2142 maxname = None
2142 2143 for f in names:
2143 2144 m = namere.match(f)
2144 2145 if m:
2145 2146 index = int(m.group(1))
2146 2147 if maxindex is None or index > maxindex:
2147 2148 maxindex = index
2148 2149 maxname = f
2149 2150 if maxname:
2150 2151 return (os.path.join(directory, maxname), maxindex)
2151 2152 return (None, None)
2152 2153
2153 2154 def savename(path):
2154 2155 (last, index) = lastsavename(path)
2155 2156 if last is None:
2156 2157 index = 0
2157 2158 newpath = path + ".%d" % (index + 1)
2158 2159 return newpath
2159 2160
2160 2161 def push(ui, repo, patch=None, **opts):
2161 2162 """push the next patch onto the stack
2162 2163
2163 2164 When -f/--force is applied, all local changes in patched files
2164 2165 will be lost.
2165 2166 """
2166 2167 q = repo.mq
2167 2168 mergeq = None
2168 2169
2169 2170 if opts['merge']:
2170 2171 if opts['name']:
2171 2172 newpath = repo.join(opts['name'])
2172 2173 else:
2173 2174 newpath, i = lastsavename(q.path)
2174 2175 if not newpath:
2175 2176 ui.warn(_("no saved queues found, please use -n\n"))
2176 2177 return 1
2177 2178 mergeq = queue(ui, repo.join(""), newpath)
2178 2179 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2179 2180 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2180 2181 mergeq=mergeq, all=opts.get('all'))
2181 2182 return ret
2182 2183
2183 2184 def pop(ui, repo, patch=None, **opts):
2184 2185 """pop the current patch off the stack
2185 2186
2186 2187 By default, pops off the top of the patch stack. If given a patch
2187 2188 name, keeps popping off patches until the named patch is at the
2188 2189 top of the stack.
2189 2190 """
2190 2191 localupdate = True
2191 2192 if opts['name']:
2192 2193 q = queue(ui, repo.join(""), repo.join(opts['name']))
2193 2194 ui.warn(_('using patch queue: %s\n') % q.path)
2194 2195 localupdate = False
2195 2196 else:
2196 2197 q = repo.mq
2197 2198 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2198 2199 all=opts['all'])
2199 2200 q.save_dirty()
2200 2201 return ret
2201 2202
2202 2203 def rename(ui, repo, patch, name=None, **opts):
2203 2204 """rename a patch
2204 2205
2205 2206 With one argument, renames the current patch to PATCH1.
2206 2207 With two arguments, renames PATCH1 to PATCH2."""
2207 2208
2208 2209 q = repo.mq
2209 2210
2210 2211 if not name:
2211 2212 name = patch
2212 2213 patch = None
2213 2214
2214 2215 if patch:
2215 2216 patch = q.lookup(patch)
2216 2217 else:
2217 2218 if not q.applied:
2218 2219 ui.write(_('no patches applied\n'))
2219 2220 return
2220 2221 patch = q.lookup('qtip')
2221 2222 absdest = q.join(name)
2222 2223 if os.path.isdir(absdest):
2223 2224 name = normname(os.path.join(name, os.path.basename(patch)))
2224 2225 absdest = q.join(name)
2225 2226 if os.path.exists(absdest):
2226 2227 raise util.Abort(_('%s already exists') % absdest)
2227 2228
2228 2229 if name in q.series:
2229 2230 raise util.Abort(_('A patch named %s already exists in the series file') % name)
2230 2231
2231 2232 if ui.verbose:
2232 2233 ui.write('renaming %s to %s\n' % (patch, name))
2233 2234 i = q.find_series(patch)
2234 2235 guards = q.guard_re.findall(q.full_series[i])
2235 2236 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2236 2237 q.parse_series()
2237 2238 q.series_dirty = 1
2238 2239
2239 2240 info = q.isapplied(patch)
2240 2241 if info:
2241 2242 q.applied[info[0]] = statusentry(info[1], name)
2242 2243 q.applied_dirty = 1
2243 2244
2244 2245 util.rename(q.join(patch), absdest)
2245 2246 r = q.qrepo()
2246 2247 if r:
2247 2248 wlock = r.wlock()
2248 2249 try:
2249 2250 if r.dirstate[patch] == 'a':
2250 2251 r.dirstate.forget(patch)
2251 2252 r.dirstate.add(name)
2252 2253 else:
2253 2254 if r.dirstate[name] == 'r':
2254 2255 r.undelete([name])
2255 2256 r.copy(patch, name)
2256 2257 r.remove([patch], False)
2257 2258 finally:
2258 2259 wlock.release()
2259 2260
2260 2261 q.save_dirty()
2261 2262
2262 2263 def restore(ui, repo, rev, **opts):
2263 2264 """restore the queue state saved by a revision"""
2264 2265 rev = repo.lookup(rev)
2265 2266 q = repo.mq
2266 2267 q.restore(repo, rev, delete=opts['delete'],
2267 2268 qupdate=opts['update'])
2268 2269 q.save_dirty()
2269 2270 return 0
2270 2271
2271 2272 def save(ui, repo, **opts):
2272 2273 """save current queue state"""
2273 2274 q = repo.mq
2274 2275 message = cmdutil.logmessage(opts)
2275 2276 ret = q.save(repo, msg=message)
2276 2277 if ret:
2277 2278 return ret
2278 2279 q.save_dirty()
2279 2280 if opts['copy']:
2280 2281 path = q.path
2281 2282 if opts['name']:
2282 2283 newpath = os.path.join(q.basepath, opts['name'])
2283 2284 if os.path.exists(newpath):
2284 2285 if not os.path.isdir(newpath):
2285 2286 raise util.Abort(_('destination %s exists and is not '
2286 2287 'a directory') % newpath)
2287 2288 if not opts['force']:
2288 2289 raise util.Abort(_('destination %s exists, '
2289 2290 'use -f to force') % newpath)
2290 2291 else:
2291 2292 newpath = savename(path)
2292 2293 ui.warn(_("copy %s to %s\n") % (path, newpath))
2293 2294 util.copyfiles(path, newpath)
2294 2295 if opts['empty']:
2295 2296 try:
2296 2297 os.unlink(q.join(q.status_path))
2297 2298 except:
2298 2299 pass
2299 2300 return 0
2300 2301
2301 2302 def strip(ui, repo, rev, **opts):
2302 2303 """strip a revision and all its descendants from the repository
2303 2304
2304 2305 If one of the working directory's parent revisions is stripped, the
2305 2306 working directory will be updated to the parent of the stripped
2306 2307 revision.
2307 2308 """
2308 2309 backup = 'all'
2309 2310 if opts['backup']:
2310 2311 backup = 'strip'
2311 2312 elif opts['nobackup']:
2312 2313 backup = 'none'
2313 2314
2314 2315 rev = repo.lookup(rev)
2315 2316 p = repo.dirstate.parents()
2316 2317 cl = repo.changelog
2317 2318 update = True
2318 2319 if p[0] == nullid:
2319 2320 update = False
2320 2321 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2321 2322 update = False
2322 2323 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2323 2324 update = False
2324 2325
2325 2326 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2326 2327 return 0
2327 2328
2328 2329 def select(ui, repo, *args, **opts):
2329 2330 '''set or print guarded patches to push
2330 2331
2331 2332 Use the qguard command to set or print guards on patch, then use
2332 2333 qselect to tell mq which guards to use. A patch will be pushed if
2333 2334 it has no guards or any positive guards match the currently
2334 2335 selected guard, but will not be pushed if any negative guards
2335 2336 match the current guard. For example:
2336 2337
2337 2338 qguard foo.patch -stable (negative guard)
2338 2339 qguard bar.patch +stable (positive guard)
2339 2340 qselect stable
2340 2341
2341 2342 This activates the "stable" guard. mq will skip foo.patch (because
2342 2343 it has a negative match) but push bar.patch (because it has a
2343 2344 positive match).
2344 2345
2345 2346 With no arguments, prints the currently active guards.
2346 2347 With one argument, sets the active guard.
2347 2348
2348 2349 Use -n/--none to deactivate guards (no other arguments needed).
2349 2350 When no guards are active, patches with positive guards are
2350 2351 skipped and patches with negative guards are pushed.
2351 2352
2352 2353 qselect can change the guards on applied patches. It does not pop
2353 2354 guarded patches by default. Use --pop to pop back to the last
2354 2355 applied patch that is not guarded. Use --reapply (which implies
2355 2356 --pop) to push back to the current patch afterwards, but skip
2356 2357 guarded patches.
2357 2358
2358 2359 Use -s/--series to print a list of all guards in the series file
2359 2360 (no other arguments needed). Use -v for more information.'''
2360 2361
2361 2362 q = repo.mq
2362 2363 guards = q.active()
2363 2364 if args or opts['none']:
2364 2365 old_unapplied = q.unapplied(repo)
2365 2366 old_guarded = [i for i in xrange(len(q.applied)) if
2366 2367 not q.pushable(i)[0]]
2367 2368 q.set_active(args)
2368 2369 q.save_dirty()
2369 2370 if not args:
2370 2371 ui.status(_('guards deactivated\n'))
2371 2372 if not opts['pop'] and not opts['reapply']:
2372 2373 unapplied = q.unapplied(repo)
2373 2374 guarded = [i for i in xrange(len(q.applied))
2374 2375 if not q.pushable(i)[0]]
2375 2376 if len(unapplied) != len(old_unapplied):
2376 2377 ui.status(_('number of unguarded, unapplied patches has '
2377 2378 'changed from %d to %d\n') %
2378 2379 (len(old_unapplied), len(unapplied)))
2379 2380 if len(guarded) != len(old_guarded):
2380 2381 ui.status(_('number of guarded, applied patches has changed '
2381 2382 'from %d to %d\n') %
2382 2383 (len(old_guarded), len(guarded)))
2383 2384 elif opts['series']:
2384 2385 guards = {}
2385 2386 noguards = 0
2386 2387 for gs in q.series_guards:
2387 2388 if not gs:
2388 2389 noguards += 1
2389 2390 for g in gs:
2390 2391 guards.setdefault(g, 0)
2391 2392 guards[g] += 1
2392 2393 if ui.verbose:
2393 2394 guards['NONE'] = noguards
2394 2395 guards = guards.items()
2395 2396 guards.sort(key=lambda x: x[0][1:])
2396 2397 if guards:
2397 2398 ui.note(_('guards in series file:\n'))
2398 2399 for guard, count in guards:
2399 2400 ui.note('%2d ' % count)
2400 2401 ui.write(guard, '\n')
2401 2402 else:
2402 2403 ui.note(_('no guards in series file\n'))
2403 2404 else:
2404 2405 if guards:
2405 2406 ui.note(_('active guards:\n'))
2406 2407 for g in guards:
2407 2408 ui.write(g, '\n')
2408 2409 else:
2409 2410 ui.write(_('no active guards\n'))
2410 2411 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2411 2412 popped = False
2412 2413 if opts['pop'] or opts['reapply']:
2413 2414 for i in xrange(len(q.applied)):
2414 2415 pushable, reason = q.pushable(i)
2415 2416 if not pushable:
2416 2417 ui.status(_('popping guarded patches\n'))
2417 2418 popped = True
2418 2419 if i == 0:
2419 2420 q.pop(repo, all=True)
2420 2421 else:
2421 2422 q.pop(repo, i-1)
2422 2423 break
2423 2424 if popped:
2424 2425 try:
2425 2426 if reapply:
2426 2427 ui.status(_('reapplying unguarded patches\n'))
2427 2428 q.push(repo, reapply)
2428 2429 finally:
2429 2430 q.save_dirty()
2430 2431
2431 2432 def finish(ui, repo, *revrange, **opts):
2432 2433 """move applied patches into repository history
2433 2434
2434 2435 Finishes the specified revisions (corresponding to applied
2435 2436 patches) by moving them out of mq control into regular repository
2436 2437 history.
2437 2438
2438 2439 Accepts a revision range or the -a/--applied option. If --applied
2439 2440 is specified, all applied mq revisions are removed from mq
2440 2441 control. Otherwise, the given revisions must be at the base of the
2441 2442 stack of applied patches.
2442 2443
2443 2444 This can be especially useful if your changes have been applied to
2444 2445 an upstream repository, or if you are about to push your changes
2445 2446 to upstream.
2446 2447 """
2447 2448 if not opts['applied'] and not revrange:
2448 2449 raise util.Abort(_('no revisions specified'))
2449 2450 elif opts['applied']:
2450 2451 revrange = ('qbase:qtip',) + revrange
2451 2452
2452 2453 q = repo.mq
2453 2454 if not q.applied:
2454 2455 ui.status(_('no patches applied\n'))
2455 2456 return 0
2456 2457
2457 2458 revs = cmdutil.revrange(repo, revrange)
2458 2459 q.finish(repo, revs)
2459 2460 q.save_dirty()
2460 2461 return 0
2461 2462
2462 2463 def reposetup(ui, repo):
2463 2464 class mqrepo(repo.__class__):
2464 2465 @util.propertycache
2465 2466 def mq(self):
2466 2467 return queue(self.ui, self.join(""))
2467 2468
2468 2469 def abort_if_wdir_patched(self, errmsg, force=False):
2469 2470 if self.mq.applied and not force:
2470 2471 parent = hex(self.dirstate.parents()[0])
2471 2472 if parent in [s.rev for s in self.mq.applied]:
2472 2473 raise util.Abort(errmsg)
2473 2474
2474 2475 def commit(self, text="", user=None, date=None, match=None,
2475 2476 force=False, editor=False, extra={}):
2476 2477 self.abort_if_wdir_patched(
2477 2478 _('cannot commit over an applied mq patch'),
2478 2479 force)
2479 2480
2480 2481 return super(mqrepo, self).commit(text, user, date, match, force,
2481 2482 editor, extra)
2482 2483
2483 2484 def push(self, remote, force=False, revs=None):
2484 2485 if self.mq.applied and not force and not revs:
2485 2486 raise util.Abort(_('source has mq patches applied'))
2486 2487 return super(mqrepo, self).push(remote, force, revs)
2487 2488
2488 2489 def _findtags(self):
2489 2490 '''augment tags from base class with patch tags'''
2490 2491 result = super(mqrepo, self)._findtags()
2491 2492
2492 2493 q = self.mq
2493 2494 if not q.applied:
2494 2495 return result
2495 2496
2496 2497 mqtags = [(bin(patch.rev), patch.name) for patch in q.applied]
2497 2498
2498 2499 if mqtags[-1][0] not in self.changelog.nodemap:
2499 2500 self.ui.warn(_('mq status file refers to unknown node %s\n')
2500 2501 % short(mqtags[-1][0]))
2501 2502 return result
2502 2503
2503 2504 mqtags.append((mqtags[-1][0], 'qtip'))
2504 2505 mqtags.append((mqtags[0][0], 'qbase'))
2505 2506 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2506 2507 tags = result[0]
2507 2508 for patch in mqtags:
2508 2509 if patch[1] in tags:
2509 2510 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2510 2511 % patch[1])
2511 2512 else:
2512 2513 tags[patch[1]] = patch[0]
2513 2514
2514 2515 return result
2515 2516
2516 2517 def _branchtags(self, partial, lrev):
2517 2518 q = self.mq
2518 2519 if not q.applied:
2519 2520 return super(mqrepo, self)._branchtags(partial, lrev)
2520 2521
2521 2522 cl = self.changelog
2522 2523 qbasenode = bin(q.applied[0].rev)
2523 2524 if qbasenode not in cl.nodemap:
2524 2525 self.ui.warn(_('mq status file refers to unknown node %s\n')
2525 2526 % short(qbasenode))
2526 2527 return super(mqrepo, self)._branchtags(partial, lrev)
2527 2528
2528 2529 qbase = cl.rev(qbasenode)
2529 2530 start = lrev + 1
2530 2531 if start < qbase:
2531 2532 # update the cache (excluding the patches) and save it
2532 2533 self._updatebranchcache(partial, lrev+1, qbase)
2533 2534 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2534 2535 start = qbase
2535 2536 # if start = qbase, the cache is as updated as it should be.
2536 2537 # if start > qbase, the cache includes (part of) the patches.
2537 2538 # we might as well use it, but we won't save it.
2538 2539
2539 2540 # update the cache up to the tip
2540 2541 self._updatebranchcache(partial, start, len(cl))
2541 2542
2542 2543 return partial
2543 2544
2544 2545 if repo.local():
2545 2546 repo.__class__ = mqrepo
2546 2547
2547 2548 def mqimport(orig, ui, repo, *args, **kwargs):
2548 2549 if hasattr(repo, 'abort_if_wdir_patched'):
2549 2550 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2550 2551 kwargs.get('force'))
2551 2552 return orig(ui, repo, *args, **kwargs)
2552 2553
2553 2554 def uisetup(ui):
2554 2555 extensions.wrapcommand(commands.table, 'import', mqimport)
2555 2556
2556 2557 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2557 2558
2558 2559 cmdtable = {
2559 2560 "qapplied":
2560 2561 (applied,
2561 2562 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
2562 2563 _('hg qapplied [-1] [-s] [PATCH]')),
2563 2564 "qclone":
2564 2565 (clone,
2565 2566 [('', 'pull', None, _('use pull protocol to copy metadata')),
2566 2567 ('U', 'noupdate', None, _('do not update the new working directories')),
2567 2568 ('', 'uncompressed', None,
2568 2569 _('use uncompressed transfer (fast over LAN)')),
2569 2570 ('p', 'patches', '', _('location of source patch repository')),
2570 2571 ] + commands.remoteopts,
2571 2572 _('hg qclone [OPTION]... SOURCE [DEST]')),
2572 2573 "qcommit|qci":
2573 2574 (commit,
2574 2575 commands.table["^commit|ci"][1],
2575 2576 _('hg qcommit [OPTION]... [FILE]...')),
2576 2577 "^qdiff":
2577 2578 (diff,
2578 2579 commands.diffopts + commands.diffopts2 + commands.walkopts,
2579 2580 _('hg qdiff [OPTION]... [FILE]...')),
2580 2581 "qdelete|qremove|qrm":
2581 2582 (delete,
2582 2583 [('k', 'keep', None, _('keep patch file')),
2583 2584 ('r', 'rev', [], _('stop managing a revision (DEPRECATED)'))],
2584 2585 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2585 2586 'qfold':
2586 2587 (fold,
2587 2588 [('e', 'edit', None, _('edit patch header')),
2588 2589 ('k', 'keep', None, _('keep folded patch files')),
2589 2590 ] + commands.commitopts,
2590 2591 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2591 2592 'qgoto':
2592 2593 (goto,
2593 2594 [('f', 'force', None, _('overwrite any local changes'))],
2594 2595 _('hg qgoto [OPTION]... PATCH')),
2595 2596 'qguard':
2596 2597 (guard,
2597 2598 [('l', 'list', None, _('list all patches and guards')),
2598 2599 ('n', 'none', None, _('drop all guards'))],
2599 2600 _('hg qguard [-l] [-n] -- [PATCH] [+GUARD]... [-GUARD]...')),
2600 2601 'qheader': (header, [], _('hg qheader [PATCH]')),
2601 2602 "^qimport":
2602 2603 (qimport,
2603 2604 [('e', 'existing', None, _('import file in patch directory')),
2604 2605 ('n', 'name', '', _('name of patch file')),
2605 2606 ('f', 'force', None, _('overwrite existing files')),
2606 2607 ('r', 'rev', [], _('place existing revisions under mq control')),
2607 2608 ('g', 'git', None, _('use git extended diff format')),
2608 2609 ('P', 'push', None, _('qpush after importing'))],
2609 2610 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2610 2611 "^qinit":
2611 2612 (init,
2612 2613 [('c', 'create-repo', None, _('create queue repository'))],
2613 2614 _('hg qinit [-c]')),
2614 2615 "qnew":
2615 2616 (new,
2616 2617 [('e', 'edit', None, _('edit commit message')),
2617 2618 ('f', 'force', None, _('import uncommitted changes into patch')),
2618 2619 ('g', 'git', None, _('use git extended diff format')),
2619 2620 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2620 2621 ('u', 'user', '', _('add "From: <given user>" to patch')),
2621 2622 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2622 2623 ('d', 'date', '', _('add "Date: <given date>" to patch'))
2623 2624 ] + commands.walkopts + commands.commitopts,
2624 2625 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2625 2626 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2626 2627 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2627 2628 "^qpop":
2628 2629 (pop,
2629 2630 [('a', 'all', None, _('pop all patches')),
2630 2631 ('n', 'name', '', _('queue name to pop')),
2631 2632 ('f', 'force', None, _('forget any local changes to patched files'))],
2632 2633 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2633 2634 "^qpush":
2634 2635 (push,
2635 2636 [('f', 'force', None, _('apply if the patch has rejects')),
2636 2637 ('l', 'list', None, _('list patch name in commit text')),
2637 2638 ('a', 'all', None, _('apply all patches')),
2638 2639 ('m', 'merge', None, _('merge from another queue')),
2639 2640 ('n', 'name', '', _('merge queue name'))],
2640 2641 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2641 2642 "^qrefresh":
2642 2643 (refresh,
2643 2644 [('e', 'edit', None, _('edit commit message')),
2644 2645 ('g', 'git', None, _('use git extended diff format')),
2645 2646 ('s', 'short', None, _('refresh only files already in the patch and specified files')),
2646 2647 ('U', 'currentuser', None, _('add/update author field in patch with current user')),
2647 2648 ('u', 'user', '', _('add/update author field in patch with given user')),
2648 2649 ('D', 'currentdate', None, _('add/update date field in patch with current date')),
2649 2650 ('d', 'date', '', _('add/update date field in patch with given date'))
2650 2651 ] + commands.walkopts + commands.commitopts,
2651 2652 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2652 2653 'qrename|qmv':
2653 2654 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2654 2655 "qrestore":
2655 2656 (restore,
2656 2657 [('d', 'delete', None, _('delete save entry')),
2657 2658 ('u', 'update', None, _('update queue working directory'))],
2658 2659 _('hg qrestore [-d] [-u] REV')),
2659 2660 "qsave":
2660 2661 (save,
2661 2662 [('c', 'copy', None, _('copy patch directory')),
2662 2663 ('n', 'name', '', _('copy directory name')),
2663 2664 ('e', 'empty', None, _('clear queue status file')),
2664 2665 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2665 2666 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2666 2667 "qselect":
2667 2668 (select,
2668 2669 [('n', 'none', None, _('disable all guards')),
2669 2670 ('s', 'series', None, _('list all guards in series file')),
2670 2671 ('', 'pop', None, _('pop to before first guarded applied patch')),
2671 2672 ('', 'reapply', None, _('pop, then reapply patches'))],
2672 2673 _('hg qselect [OPTION]... [GUARD]...')),
2673 2674 "qseries":
2674 2675 (series,
2675 2676 [('m', 'missing', None, _('print patches not in series')),
2676 2677 ] + seriesopts,
2677 2678 _('hg qseries [-ms]')),
2678 2679 "^strip":
2679 2680 (strip,
2680 2681 [('f', 'force', None, _('force removal with local changes')),
2681 2682 ('b', 'backup', None, _('bundle unrelated changesets')),
2682 2683 ('n', 'nobackup', None, _('no backups'))],
2683 2684 _('hg strip [-f] [-b] [-n] REV')),
2684 2685 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2685 2686 "qunapplied":
2686 2687 (unapplied,
2687 2688 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2688 2689 _('hg qunapplied [-1] [-s] [PATCH]')),
2689 2690 "qfinish":
2690 2691 (finish,
2691 2692 [('a', 'applied', None, _('finish all applied changesets'))],
2692 2693 _('hg qfinish [-a] [REV]...')),
2693 2694 }
@@ -1,3677 +1,3679 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, incorporated herein by reference.
7 7
8 8 from node import hex, nullid, nullrev, short
9 9 from lock import release
10 10 from i18n import _, gettext
11 11 import os, re, sys, subprocess, difflib, time, tempfile
12 12 import hg, util, revlog, bundlerepo, extensions, copies, context, error
13 13 import patch, help, mdiff, url, encoding
14 14 import archival, changegroup, cmdutil, sshserver, hbisect
15 15 from hgweb import server
16 16 import merge as merge_
17 17 import minirst
18 18
19 19 # Commands start here, listed alphabetically
20 20
21 21 def add(ui, repo, *pats, **opts):
22 22 """add the specified files on the next commit
23 23
24 24 Schedule files to be version controlled and added to the
25 25 repository.
26 26
27 27 The files will be added to the repository at the next commit. To
28 28 undo an add before that, see hg forget.
29 29
30 30 If no names are given, add all files to the repository.
31 31 """
32 32
33 33 bad = []
34 34 exacts = {}
35 35 names = []
36 36 m = cmdutil.match(repo, pats, opts)
37 37 oldbad = m.bad
38 38 m.bad = lambda x,y: bad.append(x) or oldbad(x,y)
39 39
40 40 for f in repo.walk(m):
41 41 exact = m.exact(f)
42 42 if exact or f not in repo.dirstate:
43 43 names.append(f)
44 44 if ui.verbose or not exact:
45 45 ui.status(_('adding %s\n') % m.rel(f))
46 46 if not opts.get('dry_run'):
47 47 bad += [f for f in repo.add(names) if f in m.files()]
48 48 return bad and 1 or 0
49 49
50 50 def addremove(ui, repo, *pats, **opts):
51 51 """add all new files, delete all missing files
52 52
53 53 Add all new files and remove all missing files from the
54 54 repository.
55 55
56 56 New files are ignored if they match any of the patterns in
57 57 .hgignore. As with add, these changes take effect at the next
58 58 commit.
59 59
60 60 Use the -s/--similarity option to detect renamed files. With a
61 61 parameter greater than 0, this compares every removed file with
62 62 every added file and records those similar enough as renames. This
63 63 option takes a percentage between 0 (disabled) and 100 (files must
64 64 be identical) as its parameter. Detecting renamed files this way
65 65 can be expensive.
66 66 """
67 67 try:
68 68 sim = float(opts.get('similarity') or 0)
69 69 except ValueError:
70 70 raise util.Abort(_('similarity must be a number'))
71 71 if sim < 0 or sim > 100:
72 72 raise util.Abort(_('similarity must be between 0 and 100'))
73 73 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
74 74
75 75 def annotate(ui, repo, *pats, **opts):
76 76 """show changeset information by line for each file
77 77
78 78 List changes in files, showing the revision id responsible for
79 79 each line
80 80
81 81 This command is useful for discovering when a change was made and
82 82 by whom.
83 83
84 84 Without the -a/--text option, annotate will avoid processing files
85 85 it detects as binary. With -a, annotate will annotate the file
86 86 anyway, although the results will probably be neither useful
87 87 nor desirable.
88 88 """
89 89 datefunc = ui.quiet and util.shortdate or util.datestr
90 90 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
91 91
92 92 if not pats:
93 93 raise util.Abort(_('at least one filename or pattern is required'))
94 94
95 95 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
96 96 ('number', lambda x: str(x[0].rev())),
97 97 ('changeset', lambda x: short(x[0].node())),
98 98 ('date', getdate),
99 99 ('follow', lambda x: x[0].path()),
100 100 ]
101 101
102 102 if (not opts.get('user') and not opts.get('changeset') and not opts.get('date')
103 103 and not opts.get('follow')):
104 104 opts['number'] = 1
105 105
106 106 linenumber = opts.get('line_number') is not None
107 107 if (linenumber and (not opts.get('changeset')) and (not opts.get('number'))):
108 108 raise util.Abort(_('at least one of -n/-c is required for -l'))
109 109
110 110 funcmap = [func for op, func in opmap if opts.get(op)]
111 111 if linenumber:
112 112 lastfunc = funcmap[-1]
113 113 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
114 114
115 115 ctx = repo[opts.get('rev')]
116 116
117 117 m = cmdutil.match(repo, pats, opts)
118 118 for abs in ctx.walk(m):
119 119 fctx = ctx[abs]
120 120 if not opts.get('text') and util.binary(fctx.data()):
121 121 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
122 122 continue
123 123
124 124 lines = fctx.annotate(follow=opts.get('follow'),
125 125 linenumber=linenumber)
126 126 pieces = []
127 127
128 128 for f in funcmap:
129 129 l = [f(n) for n, dummy in lines]
130 130 if l:
131 131 ml = max(map(len, l))
132 132 pieces.append(["%*s" % (ml, x) for x in l])
133 133
134 134 if pieces:
135 135 for p, l in zip(zip(*pieces), lines):
136 136 ui.write("%s: %s" % (" ".join(p), l[1]))
137 137
138 138 def archive(ui, repo, dest, **opts):
139 139 '''create an unversioned archive of a repository revision
140 140
141 141 By default, the revision used is the parent of the working
142 142 directory; use -r/--rev to specify a different revision.
143 143
144 144 To specify the type of archive to create, use -t/--type. Valid
145 145 types are::
146 146
147 147 "files" (default): a directory full of files
148 148 "tar": tar archive, uncompressed
149 149 "tbz2": tar archive, compressed using bzip2
150 150 "tgz": tar archive, compressed using gzip
151 151 "uzip": zip archive, uncompressed
152 152 "zip": zip archive, compressed using deflate
153 153
154 154 The exact name of the destination archive or directory is given
155 155 using a format string; see 'hg help export' for details.
156 156
157 157 Each member added to an archive file has a directory prefix
158 158 prepended. Use -p/--prefix to specify a format string for the
159 159 prefix. The default is the basename of the archive, with suffixes
160 160 removed.
161 161 '''
162 162
163 163 ctx = repo[opts.get('rev')]
164 164 if not ctx:
165 165 raise util.Abort(_('no working directory: please specify a revision'))
166 166 node = ctx.node()
167 167 dest = cmdutil.make_filename(repo, dest, node)
168 168 if os.path.realpath(dest) == repo.root:
169 169 raise util.Abort(_('repository root cannot be destination'))
170 170 matchfn = cmdutil.match(repo, [], opts)
171 171 kind = opts.get('type') or 'files'
172 172 prefix = opts.get('prefix')
173 173 if dest == '-':
174 174 if kind == 'files':
175 175 raise util.Abort(_('cannot archive plain files to stdout'))
176 176 dest = sys.stdout
177 177 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
178 178 prefix = cmdutil.make_filename(repo, prefix, node)
179 179 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
180 180 matchfn, prefix)
181 181
182 182 def backout(ui, repo, node=None, rev=None, **opts):
183 183 '''reverse effect of earlier changeset
184 184
185 185 Commit the backed out changes as a new changeset. The new
186 186 changeset is a child of the backed out changeset.
187 187
188 188 If you backout a changeset other than the tip, a new head is
189 189 created. This head will be the new tip and you should merge this
190 190 backout changeset with another head.
191 191
192 192 The --merge option remembers the parent of the working directory
193 193 before starting the backout, then merges the new head with that
194 194 changeset afterwards. This saves you from doing the merge by hand.
195 195 The result of this merge is not committed, as with a normal merge.
196 196
197 197 See 'hg help dates' for a list of formats valid for -d/--date.
198 198 '''
199 199 if rev and node:
200 200 raise util.Abort(_("please specify just one revision"))
201 201
202 202 if not rev:
203 203 rev = node
204 204
205 205 if not rev:
206 206 raise util.Abort(_("please specify a revision to backout"))
207 207
208 208 date = opts.get('date')
209 209 if date:
210 210 opts['date'] = util.parsedate(date)
211 211
212 212 cmdutil.bail_if_changed(repo)
213 213 node = repo.lookup(rev)
214 214
215 215 op1, op2 = repo.dirstate.parents()
216 216 a = repo.changelog.ancestor(op1, node)
217 217 if a != node:
218 218 raise util.Abort(_('cannot backout change on a different branch'))
219 219
220 220 p1, p2 = repo.changelog.parents(node)
221 221 if p1 == nullid:
222 222 raise util.Abort(_('cannot backout a change with no parents'))
223 223 if p2 != nullid:
224 224 if not opts.get('parent'):
225 225 raise util.Abort(_('cannot backout a merge changeset without '
226 226 '--parent'))
227 227 p = repo.lookup(opts['parent'])
228 228 if p not in (p1, p2):
229 229 raise util.Abort(_('%s is not a parent of %s') %
230 230 (short(p), short(node)))
231 231 parent = p
232 232 else:
233 233 if opts.get('parent'):
234 234 raise util.Abort(_('cannot use --parent on non-merge changeset'))
235 235 parent = p1
236 236
237 237 # the backout should appear on the same branch
238 238 branch = repo.dirstate.branch()
239 239 hg.clean(repo, node, show_stats=False)
240 240 repo.dirstate.setbranch(branch)
241 241 revert_opts = opts.copy()
242 242 revert_opts['date'] = None
243 243 revert_opts['all'] = True
244 244 revert_opts['rev'] = hex(parent)
245 245 revert_opts['no_backup'] = None
246 246 revert(ui, repo, **revert_opts)
247 247 commit_opts = opts.copy()
248 248 commit_opts['addremove'] = False
249 249 if not commit_opts['message'] and not commit_opts['logfile']:
250 250 # we don't translate commit messages
251 251 commit_opts['message'] = "Backed out changeset %s" % short(node)
252 252 commit_opts['force_editor'] = True
253 253 commit(ui, repo, **commit_opts)
254 254 def nice(node):
255 255 return '%d:%s' % (repo.changelog.rev(node), short(node))
256 256 ui.status(_('changeset %s backs out changeset %s\n') %
257 257 (nice(repo.changelog.tip()), nice(node)))
258 258 if op1 != node:
259 259 hg.clean(repo, op1, show_stats=False)
260 260 if opts.get('merge'):
261 261 ui.status(_('merging with changeset %s\n') % nice(repo.changelog.tip()))
262 262 hg.merge(repo, hex(repo.changelog.tip()))
263 263 else:
264 264 ui.status(_('the backout changeset is a new head - '
265 265 'do not forget to merge\n'))
266 266 ui.status(_('(use "backout --merge" '
267 267 'if you want to auto-merge)\n'))
268 268
269 269 def bisect(ui, repo, rev=None, extra=None, command=None,
270 270 reset=None, good=None, bad=None, skip=None, noupdate=None):
271 271 """subdivision search of changesets
272 272
273 273 This command helps to find changesets which introduce problems. To
274 274 use, mark the earliest changeset you know exhibits the problem as
275 275 bad, then mark the latest changeset which is free from the problem
276 276 as good. Bisect will update your working directory to a revision
277 277 for testing (unless the -U/--noupdate option is specified). Once
278 278 you have performed tests, mark the working directory as good or
279 279 bad, and bisect will either update to another candidate changeset
280 280 or announce that it has found the bad revision.
281 281
282 282 As a shortcut, you can also use the revision argument to mark a
283 283 revision as good or bad without checking it out first.
284 284
285 285 If you supply a command, it will be used for automatic bisection.
286 286 Its exit status will be used to mark revisions as good or bad:
287 287 status 0 means good, 125 means to skip the revision, 127
288 288 (command not found) will abort the bisection, and any other
289 289 non-zero exit status means the revision is bad.
290 290 """
291 291 def print_result(nodes, good):
292 292 displayer = cmdutil.show_changeset(ui, repo, {})
293 293 if len(nodes) == 1:
294 294 # narrowed it down to a single revision
295 295 if good:
296 296 ui.write(_("The first good revision is:\n"))
297 297 else:
298 298 ui.write(_("The first bad revision is:\n"))
299 299 displayer.show(repo[nodes[0]])
300 300 else:
301 301 # multiple possible revisions
302 302 if good:
303 303 ui.write(_("Due to skipped revisions, the first "
304 304 "good revision could be any of:\n"))
305 305 else:
306 306 ui.write(_("Due to skipped revisions, the first "
307 307 "bad revision could be any of:\n"))
308 308 for n in nodes:
309 309 displayer.show(repo[n])
310 310
311 311 def check_state(state, interactive=True):
312 312 if not state['good'] or not state['bad']:
313 313 if (good or bad or skip or reset) and interactive:
314 314 return
315 315 if not state['good']:
316 316 raise util.Abort(_('cannot bisect (no known good revisions)'))
317 317 else:
318 318 raise util.Abort(_('cannot bisect (no known bad revisions)'))
319 319 return True
320 320
321 321 # backward compatibility
322 322 if rev in "good bad reset init".split():
323 323 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
324 324 cmd, rev, extra = rev, extra, None
325 325 if cmd == "good":
326 326 good = True
327 327 elif cmd == "bad":
328 328 bad = True
329 329 else:
330 330 reset = True
331 331 elif extra or good + bad + skip + reset + bool(command) > 1:
332 332 raise util.Abort(_('incompatible arguments'))
333 333
334 334 if reset:
335 335 p = repo.join("bisect.state")
336 336 if os.path.exists(p):
337 337 os.unlink(p)
338 338 return
339 339
340 340 state = hbisect.load_state(repo)
341 341
342 342 if command:
343 343 changesets = 1
344 344 try:
345 345 while changesets:
346 346 # update state
347 347 status = util.system(command)
348 348 if status == 125:
349 349 transition = "skip"
350 350 elif status == 0:
351 351 transition = "good"
352 352 # status < 0 means process was killed
353 353 elif status == 127:
354 354 raise util.Abort(_("failed to execute %s") % command)
355 355 elif status < 0:
356 356 raise util.Abort(_("%s killed") % command)
357 357 else:
358 358 transition = "bad"
359 359 ctx = repo[rev or '.']
360 360 state[transition].append(ctx.node())
361 361 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
362 362 check_state(state, interactive=False)
363 363 # bisect
364 364 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
365 365 # update to next check
366 366 cmdutil.bail_if_changed(repo)
367 367 hg.clean(repo, nodes[0], show_stats=False)
368 368 finally:
369 369 hbisect.save_state(repo, state)
370 370 return print_result(nodes, good)
371 371
372 372 # update state
373 373 node = repo.lookup(rev or '.')
374 374 if good:
375 375 state['good'].append(node)
376 376 elif bad:
377 377 state['bad'].append(node)
378 378 elif skip:
379 379 state['skip'].append(node)
380 380
381 381 hbisect.save_state(repo, state)
382 382
383 383 if not check_state(state):
384 384 return
385 385
386 386 # actually bisect
387 387 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
388 388 if changesets == 0:
389 389 print_result(nodes, good)
390 390 else:
391 391 assert len(nodes) == 1 # only a single node can be tested next
392 392 node = nodes[0]
393 393 # compute the approximate number of remaining tests
394 394 tests, size = 0, 2
395 395 while size <= changesets:
396 396 tests, size = tests + 1, size * 2
397 397 rev = repo.changelog.rev(node)
398 398 ui.write(_("Testing changeset %d:%s "
399 399 "(%d changesets remaining, ~%d tests)\n")
400 400 % (rev, short(node), changesets, tests))
401 401 if not noupdate:
402 402 cmdutil.bail_if_changed(repo)
403 403 return hg.clean(repo, node)
404 404
405 405 def branch(ui, repo, label=None, **opts):
406 406 """set or show the current branch name
407 407
408 408 With no argument, show the current branch name. With one argument,
409 409 set the working directory branch name (the branch will not exist
410 410 in the repository until the next commit). Standard practice
411 411 recommends that primary development take place on the 'default'
412 412 branch.
413 413
414 414 Unless -f/--force is specified, branch will not let you set a
415 415 branch name that already exists, even if it's inactive.
416 416
417 417 Use -C/--clean to reset the working directory branch to that of
418 418 the parent of the working directory, negating a previous branch
419 419 change.
420 420
421 421 Use the command 'hg update' to switch to an existing branch. Use
422 422 'hg commit --close-branch' to mark this branch as closed.
423 423 """
424 424
425 425 if opts.get('clean'):
426 426 label = repo[None].parents()[0].branch()
427 427 repo.dirstate.setbranch(label)
428 428 ui.status(_('reset working directory to branch %s\n') % label)
429 429 elif label:
430 430 if not opts.get('force') and label in repo.branchtags():
431 431 if label not in [p.branch() for p in repo.parents()]:
432 432 raise util.Abort(_('a branch of the same name already exists'
433 433 ' (use --force to override)'))
434 434 repo.dirstate.setbranch(encoding.fromlocal(label))
435 435 ui.status(_('marked working directory as branch %s\n') % label)
436 436 else:
437 437 ui.write("%s\n" % encoding.tolocal(repo.dirstate.branch()))
438 438
439 439 def branches(ui, repo, active=False, closed=False):
440 440 """list repository named branches
441 441
442 442 List the repository's named branches, indicating which ones are
443 443 inactive. If -c/--closed is specified, also list branches which have
444 444 been marked closed (see hg commit --close-branch).
445 445
446 446 If -a/--active is specified, only show active branches. A branch
447 447 is considered active if it contains repository heads.
448 448
449 449 Use the command 'hg update' to switch to an existing branch.
450 450 """
451 451
452 452 hexfunc = ui.debugflag and hex or short
453 453 activebranches = [encoding.tolocal(repo[n].branch())
454 454 for n in repo.heads()]
455 455 def testactive(tag, node):
456 456 realhead = tag in activebranches
457 457 open = node in repo.branchheads(tag, closed=False)
458 458 return realhead and open
459 459 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
460 460 for tag, node in repo.branchtags().items()],
461 461 reverse=True)
462 462
463 463 for isactive, node, tag in branches:
464 464 if (not active) or isactive:
465 465 if ui.quiet:
466 466 ui.write("%s\n" % tag)
467 467 else:
468 468 hn = repo.lookup(node)
469 469 if isactive:
470 470 notice = ''
471 471 elif hn not in repo.branchheads(tag, closed=False):
472 472 if not closed:
473 473 continue
474 474 notice = ' (closed)'
475 475 else:
476 476 notice = ' (inactive)'
477 477 rev = str(node).rjust(31 - encoding.colwidth(tag))
478 478 data = tag, rev, hexfunc(hn), notice
479 479 ui.write("%s %s:%s%s\n" % data)
480 480
481 481 def bundle(ui, repo, fname, dest=None, **opts):
482 482 """create a changegroup file
483 483
484 484 Generate a compressed changegroup file collecting changesets not
485 485 known to be in another repository.
486 486
487 487 If no destination repository is specified the destination is
488 488 assumed to have all the nodes specified by one or more --base
489 489 parameters. To create a bundle containing all changesets, use
490 490 -a/--all (or --base null).
491 491
492 492 You can change compression method with the -t/--type option.
493 493 The available compression methods are: none, bzip2, and
494 494 gzip (by default, bundles are compressed using bzip2).
495 495
496 496 The bundle file can then be transferred using conventional means
497 497 and applied to another repository with the unbundle or pull
498 498 command. This is useful when direct push and pull are not
499 499 available or when exporting an entire repository is undesirable.
500 500
501 501 Applying bundles preserves all changeset contents including
502 502 permissions, copy/rename information, and revision history.
503 503 """
504 504 revs = opts.get('rev') or None
505 505 if revs:
506 506 revs = [repo.lookup(rev) for rev in revs]
507 507 if opts.get('all'):
508 508 base = ['null']
509 509 else:
510 510 base = opts.get('base')
511 511 if base:
512 512 if dest:
513 513 raise util.Abort(_("--base is incompatible with specifying "
514 514 "a destination"))
515 515 base = [repo.lookup(rev) for rev in base]
516 516 # create the right base
517 517 # XXX: nodesbetween / changegroup* should be "fixed" instead
518 518 o = []
519 519 has = set((nullid,))
520 520 for n in base:
521 521 has.update(repo.changelog.reachable(n))
522 522 if revs:
523 523 visit = list(revs)
524 524 else:
525 525 visit = repo.changelog.heads()
526 526 seen = {}
527 527 while visit:
528 528 n = visit.pop(0)
529 529 parents = [p for p in repo.changelog.parents(n) if p not in has]
530 530 if len(parents) == 0:
531 531 o.insert(0, n)
532 532 else:
533 533 for p in parents:
534 534 if p not in seen:
535 535 seen[p] = 1
536 536 visit.append(p)
537 537 else:
538 538 dest, revs, checkout = hg.parseurl(
539 539 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
540 540 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
541 541 o = repo.findoutgoing(other, force=opts.get('force'))
542 542
543 543 if revs:
544 544 cg = repo.changegroupsubset(o, revs, 'bundle')
545 545 else:
546 546 cg = repo.changegroup(o, 'bundle')
547 547
548 548 bundletype = opts.get('type', 'bzip2').lower()
549 549 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
550 550 bundletype = btypes.get(bundletype)
551 551 if bundletype not in changegroup.bundletypes:
552 552 raise util.Abort(_('unknown bundle type specified with --type'))
553 553
554 554 changegroup.writebundle(cg, fname, bundletype)
555 555
556 556 def cat(ui, repo, file1, *pats, **opts):
557 557 """output the current or given revision of files
558 558
559 559 Print the specified files as they were at the given revision. If
560 560 no revision is given, the parent of the working directory is used,
561 561 or tip if no revision is checked out.
562 562
563 563 Output may be to a file, in which case the name of the file is
564 564 given using a format string. The formatting rules are the same as
565 565 for the export command, with the following additions::
566 566
567 567 %s basename of file being printed
568 568 %d dirname of file being printed, or '.' if in repository root
569 569 %p root-relative path name of file being printed
570 570 """
571 571 ctx = repo[opts.get('rev')]
572 572 err = 1
573 573 m = cmdutil.match(repo, (file1,) + pats, opts)
574 574 for abs in ctx.walk(m):
575 575 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
576 576 data = ctx[abs].data()
577 577 if opts.get('decode'):
578 578 data = repo.wwritedata(abs, data)
579 579 fp.write(data)
580 580 err = 0
581 581 return err
582 582
583 583 def clone(ui, source, dest=None, **opts):
584 584 """make a copy of an existing repository
585 585
586 586 Create a copy of an existing repository in a new directory.
587 587
588 588 If no destination directory name is specified, it defaults to the
589 589 basename of the source.
590 590
591 591 The location of the source is added to the new repository's
592 592 .hg/hgrc file, as the default to be used for future pulls.
593 593
594 594 If you use the -r/--rev option to clone up to a specific revision,
595 595 no subsequent revisions (including subsequent tags) will be
596 596 present in the cloned repository. This option implies --pull, even
597 597 on local repositories.
598 598
599 599 By default, clone will check out the head of the 'default' branch.
600 600 If the -U/--noupdate option is used, the new clone will contain
601 601 only a repository (.hg) and no working copy (the working copy
602 602 parent is the null revision).
603 603
604 604 See 'hg help urls' for valid source format details.
605 605
606 606 It is possible to specify an ssh:// URL as the destination, but no
607 607 .hg/hgrc and working directory will be created on the remote side.
608 608 Please see 'hg help urls' for important details about ssh:// URLs.
609 609
610 610 For efficiency, hardlinks are used for cloning whenever the source
611 611 and destination are on the same filesystem (note this applies only
612 612 to the repository data, not to the checked out files). Some
613 613 filesystems, such as AFS, implement hardlinking incorrectly, but
614 614 do not report errors. In these cases, use the --pull option to
615 615 avoid hardlinking.
616 616
617 617 In some cases, you can clone repositories and checked out files
618 618 using full hardlinks with ::
619 619
620 620 $ cp -al REPO REPOCLONE
621 621
622 622 This is the fastest way to clone, but it is not always safe. The
623 623 operation is not atomic (making sure REPO is not modified during
624 624 the operation is up to you) and you have to make sure your editor
625 625 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
626 626 this is not compatible with certain extensions that place their
627 627 metadata under the .hg directory, such as mq.
628 628 """
629 629 hg.clone(cmdutil.remoteui(ui, opts), source, dest,
630 630 pull=opts.get('pull'),
631 631 stream=opts.get('uncompressed'),
632 632 rev=opts.get('rev'),
633 633 update=not opts.get('noupdate'))
634 634
635 635 def commit(ui, repo, *pats, **opts):
636 636 """commit the specified files or all outstanding changes
637 637
638 638 Commit changes to the given files into the repository. Unlike a
639 639 centralized RCS, this operation is a local operation. See hg push
640 640 for a way to actively distribute your changes.
641 641
642 642 If a list of files is omitted, all changes reported by "hg status"
643 643 will be committed.
644 644
645 645 If you are committing the result of a merge, do not provide any
646 646 filenames or -I/-X filters.
647 647
648 648 If no commit message is specified, the configured editor is
649 649 started to prompt you for a message.
650 650
651 651 See 'hg help dates' for a list of formats valid for -d/--date.
652 652 """
653 653 extra = {}
654 654 if opts.get('close_branch'):
655 655 extra['close'] = 1
656 656 e = cmdutil.commiteditor
657 657 if opts.get('force_editor'):
658 658 e = cmdutil.commitforceeditor
659 659
660 660 def commitfunc(ui, repo, message, match, opts):
661 661 return repo.commit(message, opts.get('user'), opts.get('date'), match,
662 662 editor=e, extra=extra)
663 663
664 664 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
665 665 if not node:
666 666 ui.status(_("nothing changed\n"))
667 667 return
668 668 cl = repo.changelog
669 669 rev = cl.rev(node)
670 670 parents = cl.parentrevs(rev)
671 671 if rev - 1 in parents:
672 672 # one of the parents was the old tip
673 673 pass
674 674 elif (parents == (nullrev, nullrev) or
675 675 len(cl.heads(cl.node(parents[0]))) > 1 and
676 676 (parents[1] == nullrev or len(cl.heads(cl.node(parents[1]))) > 1)):
677 677 ui.status(_('created new head\n'))
678 678
679 679 if ui.debugflag:
680 680 ui.write(_('committed changeset %d:%s\n') % (rev, hex(node)))
681 681 elif ui.verbose:
682 682 ui.write(_('committed changeset %d:%s\n') % (rev, short(node)))
683 683
684 684 def copy(ui, repo, *pats, **opts):
685 685 """mark files as copied for the next commit
686 686
687 687 Mark dest as having copies of source files. If dest is a
688 688 directory, copies are put in that directory. If dest is a file,
689 689 the source must be a single file.
690 690
691 691 By default, this command copies the contents of files as they
692 692 exist in the working directory. If invoked with -A/--after, the
693 693 operation is recorded, but no copying is performed.
694 694
695 695 This command takes effect with the next commit. To undo a copy
696 696 before that, see hg revert.
697 697 """
698 698 wlock = repo.wlock(False)
699 699 try:
700 700 return cmdutil.copy(ui, repo, pats, opts)
701 701 finally:
702 702 wlock.release()
703 703
704 704 def debugancestor(ui, repo, *args):
705 705 """find the ancestor revision of two revisions in a given index"""
706 706 if len(args) == 3:
707 707 index, rev1, rev2 = args
708 708 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
709 709 lookup = r.lookup
710 710 elif len(args) == 2:
711 711 if not repo:
712 712 raise util.Abort(_("There is no Mercurial repository here "
713 713 "(.hg not found)"))
714 714 rev1, rev2 = args
715 715 r = repo.changelog
716 716 lookup = repo.lookup
717 717 else:
718 718 raise util.Abort(_('either two or three arguments required'))
719 719 a = r.ancestor(lookup(rev1), lookup(rev2))
720 720 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
721 721
722 722 def debugcommands(ui, cmd='', *args):
723 723 for cmd, vals in sorted(table.iteritems()):
724 724 cmd = cmd.split('|')[0].strip('^')
725 725 opts = ', '.join([i[1] for i in vals[1]])
726 726 ui.write('%s: %s\n' % (cmd, opts))
727 727
728 728 def debugcomplete(ui, cmd='', **opts):
729 729 """returns the completion list associated with the given command"""
730 730
731 731 if opts.get('options'):
732 732 options = []
733 733 otables = [globalopts]
734 734 if cmd:
735 735 aliases, entry = cmdutil.findcmd(cmd, table, False)
736 736 otables.append(entry[1])
737 737 for t in otables:
738 738 for o in t:
739 739 if o[0]:
740 740 options.append('-%s' % o[0])
741 741 options.append('--%s' % o[1])
742 742 ui.write("%s\n" % "\n".join(options))
743 743 return
744 744
745 745 cmdlist = cmdutil.findpossible(cmd, table)
746 746 if ui.verbose:
747 747 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
748 748 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
749 749
750 750 def debugfsinfo(ui, path = "."):
751 751 open('.debugfsinfo', 'w').write('')
752 752 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
753 753 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
754 754 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
755 755 and 'yes' or 'no'))
756 756 os.unlink('.debugfsinfo')
757 757
758 758 def debugrebuildstate(ui, repo, rev="tip"):
759 759 """rebuild the dirstate as it would look like for the given revision"""
760 760 ctx = repo[rev]
761 761 wlock = repo.wlock()
762 762 try:
763 763 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
764 764 finally:
765 765 wlock.release()
766 766
767 767 def debugcheckstate(ui, repo):
768 768 """validate the correctness of the current dirstate"""
769 769 parent1, parent2 = repo.dirstate.parents()
770 770 m1 = repo[parent1].manifest()
771 771 m2 = repo[parent2].manifest()
772 772 errors = 0
773 773 for f in repo.dirstate:
774 774 state = repo.dirstate[f]
775 775 if state in "nr" and f not in m1:
776 776 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
777 777 errors += 1
778 778 if state in "a" and f in m1:
779 779 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
780 780 errors += 1
781 781 if state in "m" and f not in m1 and f not in m2:
782 782 ui.warn(_("%s in state %s, but not in either manifest\n") %
783 783 (f, state))
784 784 errors += 1
785 785 for f in m1:
786 786 state = repo.dirstate[f]
787 787 if state not in "nrm":
788 788 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
789 789 errors += 1
790 790 if errors:
791 791 error = _(".hg/dirstate inconsistent with current parent's manifest")
792 792 raise util.Abort(error)
793 793
794 794 def showconfig(ui, repo, *values, **opts):
795 795 """show combined config settings from all hgrc files
796 796
797 797 With no arguments, print names and values of all config items.
798 798
799 799 With one argument of the form section.name, print just the value
800 800 of that config item.
801 801
802 802 With multiple arguments, print names and values of all config
803 803 items with matching section names.
804 804
805 805 With --debug, the source (filename and line number) is printed
806 806 for each config item.
807 807 """
808 808
809 809 untrusted = bool(opts.get('untrusted'))
810 810 if values:
811 811 if len([v for v in values if '.' in v]) > 1:
812 812 raise util.Abort(_('only one config item permitted'))
813 813 for section, name, value in ui.walkconfig(untrusted=untrusted):
814 814 sectname = section + '.' + name
815 815 if values:
816 816 for v in values:
817 817 if v == section:
818 818 ui.debug('%s: ' %
819 819 ui.configsource(section, name, untrusted))
820 820 ui.write('%s=%s\n' % (sectname, value))
821 821 elif v == sectname:
822 822 ui.debug('%s: ' %
823 823 ui.configsource(section, name, untrusted))
824 824 ui.write(value, '\n')
825 825 else:
826 826 ui.debug('%s: ' %
827 827 ui.configsource(section, name, untrusted))
828 828 ui.write('%s=%s\n' % (sectname, value))
829 829
830 830 def debugsetparents(ui, repo, rev1, rev2=None):
831 831 """manually set the parents of the current working directory
832 832
833 833 This is useful for writing repository conversion tools, but should
834 834 be used with care.
835 835 """
836 836
837 837 if not rev2:
838 838 rev2 = hex(nullid)
839 839
840 840 wlock = repo.wlock()
841 841 try:
842 842 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
843 843 finally:
844 844 wlock.release()
845 845
846 846 def debugstate(ui, repo, nodates=None):
847 847 """show the contents of the current dirstate"""
848 848 timestr = ""
849 849 showdate = not nodates
850 850 for file_, ent in sorted(repo.dirstate._map.iteritems()):
851 851 if showdate:
852 852 if ent[3] == -1:
853 853 # Pad or slice to locale representation
854 854 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(0)))
855 855 timestr = 'unset'
856 856 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
857 857 else:
858 858 timestr = time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(ent[3]))
859 859 if ent[1] & 020000:
860 860 mode = 'lnk'
861 861 else:
862 862 mode = '%3o' % (ent[1] & 0777)
863 863 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
864 864 for f in repo.dirstate.copies():
865 865 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
866 866
867 867 def debugsub(ui, repo, rev=None):
868 868 if rev == '':
869 869 rev = None
870 870 for k,v in sorted(repo[rev].substate.items()):
871 871 ui.write('path %s\n' % k)
872 872 ui.write(' source %s\n' % v[0])
873 873 ui.write(' revision %s\n' % v[1])
874 874
875 875 def debugdata(ui, file_, rev):
876 876 """dump the contents of a data file revision"""
877 877 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
878 878 try:
879 879 ui.write(r.revision(r.lookup(rev)))
880 880 except KeyError:
881 881 raise util.Abort(_('invalid revision identifier %s') % rev)
882 882
883 883 def debugdate(ui, date, range=None, **opts):
884 884 """parse and display a date"""
885 885 if opts["extended"]:
886 886 d = util.parsedate(date, util.extendeddateformats)
887 887 else:
888 888 d = util.parsedate(date)
889 889 ui.write("internal: %s %s\n" % d)
890 890 ui.write("standard: %s\n" % util.datestr(d))
891 891 if range:
892 892 m = util.matchdate(range)
893 893 ui.write("match: %s\n" % m(d[0]))
894 894
895 895 def debugindex(ui, file_):
896 896 """dump the contents of an index file"""
897 897 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
898 898 ui.write(" rev offset length base linkrev"
899 899 " nodeid p1 p2\n")
900 900 for i in r:
901 901 node = r.node(i)
902 902 try:
903 903 pp = r.parents(node)
904 904 except:
905 905 pp = [nullid, nullid]
906 906 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
907 907 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
908 908 short(node), short(pp[0]), short(pp[1])))
909 909
910 910 def debugindexdot(ui, file_):
911 911 """dump an index DAG as a graphviz dot file"""
912 912 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
913 913 ui.write("digraph G {\n")
914 914 for i in r:
915 915 node = r.node(i)
916 916 pp = r.parents(node)
917 917 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
918 918 if pp[1] != nullid:
919 919 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
920 920 ui.write("}\n")
921 921
922 922 def debuginstall(ui):
923 923 '''test Mercurial installation'''
924 924
925 925 def writetemp(contents):
926 926 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
927 927 f = os.fdopen(fd, "wb")
928 928 f.write(contents)
929 929 f.close()
930 930 return name
931 931
932 932 problems = 0
933 933
934 934 # encoding
935 935 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
936 936 try:
937 937 encoding.fromlocal("test")
938 938 except util.Abort, inst:
939 939 ui.write(" %s\n" % inst)
940 940 ui.write(_(" (check that your locale is properly set)\n"))
941 941 problems += 1
942 942
943 943 # compiled modules
944 944 ui.status(_("Checking extensions...\n"))
945 945 try:
946 946 import bdiff, mpatch, base85
947 947 except Exception, inst:
948 948 ui.write(" %s\n" % inst)
949 949 ui.write(_(" One or more extensions could not be found"))
950 950 ui.write(_(" (check that you compiled the extensions)\n"))
951 951 problems += 1
952 952
953 953 # templates
954 954 ui.status(_("Checking templates...\n"))
955 955 try:
956 956 import templater
957 957 templater.templater(templater.templatepath("map-cmdline.default"))
958 958 except Exception, inst:
959 959 ui.write(" %s\n" % inst)
960 960 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
961 961 problems += 1
962 962
963 963 # patch
964 964 ui.status(_("Checking patch...\n"))
965 965 patchproblems = 0
966 966 a = "1\n2\n3\n4\n"
967 967 b = "1\n2\n3\ninsert\n4\n"
968 968 fa = writetemp(a)
969 969 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
970 970 os.path.basename(fa))
971 971 fd = writetemp(d)
972 972
973 973 files = {}
974 974 try:
975 975 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
976 976 except util.Abort, e:
977 977 ui.write(_(" patch call failed:\n"))
978 978 ui.write(" " + str(e) + "\n")
979 979 patchproblems += 1
980 980 else:
981 981 if list(files) != [os.path.basename(fa)]:
982 982 ui.write(_(" unexpected patch output!\n"))
983 983 patchproblems += 1
984 984 a = open(fa).read()
985 985 if a != b:
986 986 ui.write(_(" patch test failed!\n"))
987 987 patchproblems += 1
988 988
989 989 if patchproblems:
990 990 if ui.config('ui', 'patch'):
991 991 ui.write(_(" (Current patch tool may be incompatible with patch,"
992 992 " or misconfigured. Please check your .hgrc file)\n"))
993 993 else:
994 994 ui.write(_(" Internal patcher failure, please report this error"
995 995 " to http://mercurial.selenic.com/bts/\n"))
996 996 problems += patchproblems
997 997
998 998 os.unlink(fa)
999 999 os.unlink(fd)
1000 1000
1001 1001 # editor
1002 1002 ui.status(_("Checking commit editor...\n"))
1003 1003 editor = ui.geteditor()
1004 1004 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1005 1005 if not cmdpath:
1006 1006 if editor == 'vi':
1007 1007 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1008 1008 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
1009 1009 else:
1010 1010 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1011 1011 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
1012 1012 problems += 1
1013 1013
1014 1014 # check username
1015 1015 ui.status(_("Checking username...\n"))
1016 1016 user = os.environ.get("HGUSER")
1017 1017 if user is None:
1018 1018 user = ui.config("ui", "username")
1019 1019 if user is None:
1020 1020 user = os.environ.get("EMAIL")
1021 1021 if not user:
1022 1022 ui.warn(" ")
1023 1023 ui.username()
1024 1024 ui.write(_(" (specify a username in your .hgrc file)\n"))
1025 1025
1026 1026 if not problems:
1027 1027 ui.status(_("No problems detected\n"))
1028 1028 else:
1029 1029 ui.write(_("%s problems detected,"
1030 1030 " please check your install!\n") % problems)
1031 1031
1032 1032 return problems
1033 1033
1034 1034 def debugrename(ui, repo, file1, *pats, **opts):
1035 1035 """dump rename information"""
1036 1036
1037 1037 ctx = repo[opts.get('rev')]
1038 1038 m = cmdutil.match(repo, (file1,) + pats, opts)
1039 1039 for abs in ctx.walk(m):
1040 1040 fctx = ctx[abs]
1041 1041 o = fctx.filelog().renamed(fctx.filenode())
1042 1042 rel = m.rel(abs)
1043 1043 if o:
1044 1044 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1045 1045 else:
1046 1046 ui.write(_("%s not renamed\n") % rel)
1047 1047
1048 1048 def debugwalk(ui, repo, *pats, **opts):
1049 1049 """show how files match on given patterns"""
1050 1050 m = cmdutil.match(repo, pats, opts)
1051 1051 items = list(repo.walk(m))
1052 1052 if not items:
1053 1053 return
1054 1054 fmt = 'f %%-%ds %%-%ds %%s' % (
1055 1055 max([len(abs) for abs in items]),
1056 1056 max([len(m.rel(abs)) for abs in items]))
1057 1057 for abs in items:
1058 1058 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1059 1059 ui.write("%s\n" % line.rstrip())
1060 1060
1061 1061 def diff(ui, repo, *pats, **opts):
1062 1062 """diff repository (or selected files)
1063 1063
1064 1064 Show differences between revisions for the specified files.
1065 1065
1066 1066 Differences between files are shown using the unified diff format.
1067 1067
1068 1068 NOTE: diff may generate unexpected results for merges, as it will
1069 1069 default to comparing against the working directory's first parent
1070 1070 changeset if no revisions are specified.
1071 1071
1072 1072 When two revision arguments are given, then changes are shown
1073 1073 between those revisions. If only one revision is specified then
1074 1074 that revision is compared to the working directory, and, when no
1075 1075 revisions are specified, the working directory files are compared
1076 1076 to its parent.
1077 1077
1078 1078 Without the -a/--text option, diff will avoid generating diffs of
1079 1079 files it detects as binary. With -a, diff will generate a diff
1080 1080 anyway, probably with undesirable results.
1081 1081
1082 1082 Use the -g/--git option to generate diffs in the git extended diff
1083 1083 format. For more information, read 'hg help diffs'.
1084 1084 """
1085 1085
1086 1086 revs = opts.get('rev')
1087 1087 change = opts.get('change')
1088 1088 stat = opts.get('stat')
1089 1089
1090 1090 if revs and change:
1091 1091 msg = _('cannot specify --rev and --change at the same time')
1092 1092 raise util.Abort(msg)
1093 1093 elif change:
1094 1094 node2 = repo.lookup(change)
1095 1095 node1 = repo[node2].parents()[0].node()
1096 1096 else:
1097 1097 node1, node2 = cmdutil.revpair(repo, revs)
1098 1098
1099 1099 if stat:
1100 1100 opts['unified'] = '0'
1101 diffopts = patch.diffopts(ui, opts)
1101 1102
1102 1103 m = cmdutil.match(repo, pats, opts)
1103 it = patch.diff(repo, node1, node2, match=m, opts=patch.diffopts(ui, opts))
1104 it = patch.diff(repo, node1, node2, match=m, opts=diffopts)
1104 1105 if stat:
1105 1106 width = ui.interactive() and util.termwidth() or 80
1106 ui.write(patch.diffstat(util.iterlines(it), width=width))
1107 ui.write(patch.diffstat(util.iterlines(it), width=width,
1108 git=diffopts.git))
1107 1109 else:
1108 1110 for chunk in it:
1109 1111 ui.write(chunk)
1110 1112
1111 1113 def export(ui, repo, *changesets, **opts):
1112 1114 """dump the header and diffs for one or more changesets
1113 1115
1114 1116 Print the changeset header and diffs for one or more revisions.
1115 1117
1116 1118 The information shown in the changeset header is: author,
1117 1119 changeset hash, parent(s) and commit comment.
1118 1120
1119 1121 NOTE: export may generate unexpected diff output for merge
1120 1122 changesets, as it will compare the merge changeset against its
1121 1123 first parent only.
1122 1124
1123 1125 Output may be to a file, in which case the name of the file is
1124 1126 given using a format string. The formatting rules are as follows::
1125 1127
1126 1128 %% literal "%" character
1127 1129 %H changeset hash (40 bytes of hexadecimal)
1128 1130 %N number of patches being generated
1129 1131 %R changeset revision number
1130 1132 %b basename of the exporting repository
1131 1133 %h short-form changeset hash (12 bytes of hexadecimal)
1132 1134 %n zero-padded sequence number, starting at 1
1133 1135 %r zero-padded changeset revision number
1134 1136
1135 1137 Without the -a/--text option, export will avoid generating diffs
1136 1138 of files it detects as binary. With -a, export will generate a
1137 1139 diff anyway, probably with undesirable results.
1138 1140
1139 1141 Use the -g/--git option to generate diffs in the git extended diff
1140 1142 format. See 'hg help diffs' for more information.
1141 1143
1142 1144 With the --switch-parent option, the diff will be against the
1143 1145 second parent. It can be useful to review a merge.
1144 1146 """
1145 1147 if not changesets:
1146 1148 raise util.Abort(_("export requires at least one changeset"))
1147 1149 revs = cmdutil.revrange(repo, changesets)
1148 1150 if len(revs) > 1:
1149 1151 ui.note(_('exporting patches:\n'))
1150 1152 else:
1151 1153 ui.note(_('exporting patch:\n'))
1152 1154 patch.export(repo, revs, template=opts.get('output'),
1153 1155 switch_parent=opts.get('switch_parent'),
1154 1156 opts=patch.diffopts(ui, opts))
1155 1157
1156 1158 def forget(ui, repo, *pats, **opts):
1157 1159 """forget the specified files on the next commit
1158 1160
1159 1161 Mark the specified files so they will no longer be tracked
1160 1162 after the next commit.
1161 1163
1162 1164 This only removes files from the current branch, not from the
1163 1165 entire project history, and it does not delete them from the
1164 1166 working directory.
1165 1167
1166 1168 To undo a forget before the next commit, see hg add.
1167 1169 """
1168 1170
1169 1171 if not pats:
1170 1172 raise util.Abort(_('no files specified'))
1171 1173
1172 1174 m = cmdutil.match(repo, pats, opts)
1173 1175 s = repo.status(match=m, clean=True)
1174 1176 forget = sorted(s[0] + s[1] + s[3] + s[6])
1175 1177
1176 1178 for f in m.files():
1177 1179 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1178 1180 ui.warn(_('not removing %s: file is already untracked\n')
1179 1181 % m.rel(f))
1180 1182
1181 1183 for f in forget:
1182 1184 if ui.verbose or not m.exact(f):
1183 1185 ui.status(_('removing %s\n') % m.rel(f))
1184 1186
1185 1187 repo.remove(forget, unlink=False)
1186 1188
1187 1189 def grep(ui, repo, pattern, *pats, **opts):
1188 1190 """search for a pattern in specified files and revisions
1189 1191
1190 1192 Search revisions of files for a regular expression.
1191 1193
1192 1194 This command behaves differently than Unix grep. It only accepts
1193 1195 Python/Perl regexps. It searches repository history, not the
1194 1196 working directory. It always prints the revision number in which a
1195 1197 match appears.
1196 1198
1197 1199 By default, grep only prints output for the first revision of a
1198 1200 file in which it finds a match. To get it to print every revision
1199 1201 that contains a change in match status ("-" for a match that
1200 1202 becomes a non-match, or "+" for a non-match that becomes a match),
1201 1203 use the --all flag.
1202 1204 """
1203 1205 reflags = 0
1204 1206 if opts.get('ignore_case'):
1205 1207 reflags |= re.I
1206 1208 try:
1207 1209 regexp = re.compile(pattern, reflags)
1208 1210 except Exception, inst:
1209 1211 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1210 1212 return None
1211 1213 sep, eol = ':', '\n'
1212 1214 if opts.get('print0'):
1213 1215 sep = eol = '\0'
1214 1216
1215 1217 getfile = util.lrucachefunc(repo.file)
1216 1218
1217 1219 def matchlines(body):
1218 1220 begin = 0
1219 1221 linenum = 0
1220 1222 while True:
1221 1223 match = regexp.search(body, begin)
1222 1224 if not match:
1223 1225 break
1224 1226 mstart, mend = match.span()
1225 1227 linenum += body.count('\n', begin, mstart) + 1
1226 1228 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1227 1229 begin = body.find('\n', mend) + 1 or len(body)
1228 1230 lend = begin - 1
1229 1231 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1230 1232
1231 1233 class linestate(object):
1232 1234 def __init__(self, line, linenum, colstart, colend):
1233 1235 self.line = line
1234 1236 self.linenum = linenum
1235 1237 self.colstart = colstart
1236 1238 self.colend = colend
1237 1239
1238 1240 def __hash__(self):
1239 1241 return hash((self.linenum, self.line))
1240 1242
1241 1243 def __eq__(self, other):
1242 1244 return self.line == other.line
1243 1245
1244 1246 matches = {}
1245 1247 copies = {}
1246 1248 def grepbody(fn, rev, body):
1247 1249 matches[rev].setdefault(fn, [])
1248 1250 m = matches[rev][fn]
1249 1251 for lnum, cstart, cend, line in matchlines(body):
1250 1252 s = linestate(line, lnum, cstart, cend)
1251 1253 m.append(s)
1252 1254
1253 1255 def difflinestates(a, b):
1254 1256 sm = difflib.SequenceMatcher(None, a, b)
1255 1257 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1256 1258 if tag == 'insert':
1257 1259 for i in xrange(blo, bhi):
1258 1260 yield ('+', b[i])
1259 1261 elif tag == 'delete':
1260 1262 for i in xrange(alo, ahi):
1261 1263 yield ('-', a[i])
1262 1264 elif tag == 'replace':
1263 1265 for i in xrange(alo, ahi):
1264 1266 yield ('-', a[i])
1265 1267 for i in xrange(blo, bhi):
1266 1268 yield ('+', b[i])
1267 1269
1268 1270 def display(fn, r, pstates, states):
1269 1271 datefunc = ui.quiet and util.shortdate or util.datestr
1270 1272 found = False
1271 1273 filerevmatches = {}
1272 1274 if opts.get('all'):
1273 1275 iter = difflinestates(pstates, states)
1274 1276 else:
1275 1277 iter = [('', l) for l in states]
1276 1278 for change, l in iter:
1277 1279 cols = [fn, str(r)]
1278 1280 if opts.get('line_number'):
1279 1281 cols.append(str(l.linenum))
1280 1282 if opts.get('all'):
1281 1283 cols.append(change)
1282 1284 if opts.get('user'):
1283 1285 cols.append(ui.shortuser(get(r).user()))
1284 1286 if opts.get('date'):
1285 1287 cols.append(datefunc(get(r).date()))
1286 1288 if opts.get('files_with_matches'):
1287 1289 c = (fn, r)
1288 1290 if c in filerevmatches:
1289 1291 continue
1290 1292 filerevmatches[c] = 1
1291 1293 else:
1292 1294 cols.append(l.line)
1293 1295 ui.write(sep.join(cols), eol)
1294 1296 found = True
1295 1297 return found
1296 1298
1297 1299 skip = {}
1298 1300 revfiles = {}
1299 1301 get = util.cachefunc(lambda r: repo[r])
1300 1302 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1301 1303 found = False
1302 1304 follow = opts.get('follow')
1303 1305 for st, rev, fns in changeiter:
1304 1306 if st == 'window':
1305 1307 matches.clear()
1306 1308 revfiles.clear()
1307 1309 elif st == 'add':
1308 1310 ctx = get(rev)
1309 1311 pctx = ctx.parents()[0]
1310 1312 parent = pctx.rev()
1311 1313 matches.setdefault(rev, {})
1312 1314 matches.setdefault(parent, {})
1313 1315 files = revfiles.setdefault(rev, [])
1314 1316 for fn in fns:
1315 1317 flog = getfile(fn)
1316 1318 try:
1317 1319 fnode = ctx.filenode(fn)
1318 1320 except error.LookupError:
1319 1321 continue
1320 1322
1321 1323 copied = flog.renamed(fnode)
1322 1324 copy = follow and copied and copied[0]
1323 1325 if copy:
1324 1326 copies.setdefault(rev, {})[fn] = copy
1325 1327 if fn in skip:
1326 1328 if copy:
1327 1329 skip[copy] = True
1328 1330 continue
1329 1331 files.append(fn)
1330 1332
1331 1333 if fn not in matches[rev]:
1332 1334 grepbody(fn, rev, flog.read(fnode))
1333 1335
1334 1336 pfn = copy or fn
1335 1337 if pfn not in matches[parent]:
1336 1338 try:
1337 1339 fnode = pctx.filenode(pfn)
1338 1340 grepbody(pfn, parent, flog.read(fnode))
1339 1341 except error.LookupError:
1340 1342 pass
1341 1343 elif st == 'iter':
1342 1344 parent = get(rev).parents()[0].rev()
1343 1345 for fn in sorted(revfiles.get(rev, [])):
1344 1346 states = matches[rev][fn]
1345 1347 copy = copies.get(rev, {}).get(fn)
1346 1348 if fn in skip:
1347 1349 if copy:
1348 1350 skip[copy] = True
1349 1351 continue
1350 1352 pstates = matches.get(parent, {}).get(copy or fn, [])
1351 1353 if pstates or states:
1352 1354 r = display(fn, rev, pstates, states)
1353 1355 found = found or r
1354 1356 if r and not opts.get('all'):
1355 1357 skip[fn] = True
1356 1358 if copy:
1357 1359 skip[copy] = True
1358 1360
1359 1361 def heads(ui, repo, *branchrevs, **opts):
1360 1362 """show current repository heads or show branch heads
1361 1363
1362 1364 With no arguments, show all repository head changesets.
1363 1365
1364 1366 Repository "heads" are changesets with no child changesets. They are
1365 1367 where development generally takes place and are the usual targets
1366 1368 for update and merge operations.
1367 1369
1368 1370 If one or more REV is given, the "branch heads" will be shown for
1369 1371 the named branch associated with the specified changeset(s).
1370 1372
1371 1373 Branch heads are changesets on a named branch with no descendants on
1372 1374 the same branch. A branch head could be a "true" (repository) head,
1373 1375 or it could be the last changeset on that branch before it was
1374 1376 merged into another branch, or it could be the last changeset on the
1375 1377 branch before a new branch was created. If none of the branch heads
1376 1378 are true heads, the branch is considered inactive.
1377 1379
1378 1380 If -c/--closed is specified, also show branch heads marked closed
1379 1381 (see hg commit --close-branch).
1380 1382
1381 1383 If STARTREV is specified, only those heads that are descendants of
1382 1384 STARTREV will be displayed.
1383 1385 """
1384 1386 if opts.get('rev'):
1385 1387 start = repo.lookup(opts['rev'])
1386 1388 else:
1387 1389 start = None
1388 1390 closed = opts.get('closed')
1389 1391 hideinactive, _heads = opts.get('active'), None
1390 1392 if not branchrevs:
1391 1393 if closed:
1392 1394 raise error.Abort(_('you must specify a branch to use --closed'))
1393 1395 # Assume we're looking repo-wide heads if no revs were specified.
1394 1396 heads = repo.heads(start)
1395 1397 else:
1396 1398 if hideinactive:
1397 1399 _heads = repo.heads(start)
1398 1400 heads = []
1399 1401 visitedset = set()
1400 1402 for branchrev in branchrevs:
1401 1403 branch = repo[branchrev].branch()
1402 1404 if branch in visitedset:
1403 1405 continue
1404 1406 visitedset.add(branch)
1405 1407 bheads = repo.branchheads(branch, start, closed=closed)
1406 1408 if not bheads:
1407 1409 if not opts.get('rev'):
1408 1410 ui.warn(_("no open branch heads on branch %s\n") % branch)
1409 1411 elif branch != branchrev:
1410 1412 ui.warn(_("no changes on branch %s containing %s are "
1411 1413 "reachable from %s\n")
1412 1414 % (branch, branchrev, opts.get('rev')))
1413 1415 else:
1414 1416 ui.warn(_("no changes on branch %s are reachable from %s\n")
1415 1417 % (branch, opts.get('rev')))
1416 1418 if hideinactive:
1417 1419 bheads = [bhead for bhead in bheads if bhead in _heads]
1418 1420 heads.extend(bheads)
1419 1421 if not heads:
1420 1422 return 1
1421 1423 displayer = cmdutil.show_changeset(ui, repo, opts)
1422 1424 for n in heads:
1423 1425 displayer.show(repo[n])
1424 1426
1425 1427 def help_(ui, name=None, with_version=False):
1426 1428 """show help for a given topic or a help overview
1427 1429
1428 1430 With no arguments, print a list of commands with short help messages.
1429 1431
1430 1432 Given a topic, extension, or command name, print help for that
1431 1433 topic."""
1432 1434 option_lists = []
1433 1435 textwidth = util.termwidth() - 2
1434 1436
1435 1437 def addglobalopts(aliases):
1436 1438 if ui.verbose:
1437 1439 option_lists.append((_("global options:"), globalopts))
1438 1440 if name == 'shortlist':
1439 1441 option_lists.append((_('use "hg help" for the full list '
1440 1442 'of commands'), ()))
1441 1443 else:
1442 1444 if name == 'shortlist':
1443 1445 msg = _('use "hg help" for the full list of commands '
1444 1446 'or "hg -v" for details')
1445 1447 elif aliases:
1446 1448 msg = _('use "hg -v help%s" to show aliases and '
1447 1449 'global options') % (name and " " + name or "")
1448 1450 else:
1449 1451 msg = _('use "hg -v help %s" to show global options') % name
1450 1452 option_lists.append((msg, ()))
1451 1453
1452 1454 def helpcmd(name):
1453 1455 if with_version:
1454 1456 version_(ui)
1455 1457 ui.write('\n')
1456 1458
1457 1459 try:
1458 1460 aliases, i = cmdutil.findcmd(name, table, False)
1459 1461 except error.AmbiguousCommand, inst:
1460 1462 # py3k fix: except vars can't be used outside the scope of the
1461 1463 # except block, nor can be used inside a lambda. python issue4617
1462 1464 prefix = inst.args[0]
1463 1465 select = lambda c: c.lstrip('^').startswith(prefix)
1464 1466 helplist(_('list of commands:\n\n'), select)
1465 1467 return
1466 1468
1467 1469 # synopsis
1468 1470 if len(i) > 2:
1469 1471 if i[2].startswith('hg'):
1470 1472 ui.write("%s\n" % i[2])
1471 1473 else:
1472 1474 ui.write('hg %s %s\n' % (aliases[0], i[2]))
1473 1475 else:
1474 1476 ui.write('hg %s\n' % aliases[0])
1475 1477
1476 1478 # aliases
1477 1479 if not ui.quiet and len(aliases) > 1:
1478 1480 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1479 1481
1480 1482 # description
1481 1483 doc = gettext(i[0].__doc__)
1482 1484 if not doc:
1483 1485 doc = _("(no help text available)")
1484 1486 if ui.quiet:
1485 1487 doc = doc.splitlines()[0]
1486 1488 ui.write("\n%s\n" % minirst.format(doc, textwidth))
1487 1489
1488 1490 if not ui.quiet:
1489 1491 # options
1490 1492 if i[1]:
1491 1493 option_lists.append((_("options:\n"), i[1]))
1492 1494
1493 1495 addglobalopts(False)
1494 1496
1495 1497 def helplist(header, select=None):
1496 1498 h = {}
1497 1499 cmds = {}
1498 1500 for c, e in table.iteritems():
1499 1501 f = c.split("|", 1)[0]
1500 1502 if select and not select(f):
1501 1503 continue
1502 1504 if (not select and name != 'shortlist' and
1503 1505 e[0].__module__ != __name__):
1504 1506 continue
1505 1507 if name == "shortlist" and not f.startswith("^"):
1506 1508 continue
1507 1509 f = f.lstrip("^")
1508 1510 if not ui.debugflag and f.startswith("debug"):
1509 1511 continue
1510 1512 doc = e[0].__doc__
1511 1513 if doc and 'DEPRECATED' in doc and not ui.verbose:
1512 1514 continue
1513 1515 doc = gettext(doc)
1514 1516 if not doc:
1515 1517 doc = _("(no help text available)")
1516 1518 h[f] = doc.splitlines()[0].rstrip()
1517 1519 cmds[f] = c.lstrip("^")
1518 1520
1519 1521 if not h:
1520 1522 ui.status(_('no commands defined\n'))
1521 1523 return
1522 1524
1523 1525 ui.status(header)
1524 1526 fns = sorted(h)
1525 1527 m = max(map(len, fns))
1526 1528 for f in fns:
1527 1529 if ui.verbose:
1528 1530 commands = cmds[f].replace("|",", ")
1529 1531 ui.write(" %s:\n %s\n"%(commands, h[f]))
1530 1532 else:
1531 1533 ui.write(' %-*s %s\n' % (m, f, util.wrap(h[f], m + 4)))
1532 1534
1533 1535 if name != 'shortlist':
1534 1536 exts, maxlength = extensions.enabled()
1535 1537 text = help.listexts(_('enabled extensions:'), exts, maxlength)
1536 1538 if text:
1537 1539 ui.write("\n%s\n" % minirst.format(text, textwidth))
1538 1540
1539 1541 if not ui.quiet:
1540 1542 addglobalopts(True)
1541 1543
1542 1544 def helptopic(name):
1543 1545 for names, header, doc in help.helptable:
1544 1546 if name in names:
1545 1547 break
1546 1548 else:
1547 1549 raise error.UnknownCommand(name)
1548 1550
1549 1551 # description
1550 1552 if not doc:
1551 1553 doc = _("(no help text available)")
1552 1554 if hasattr(doc, '__call__'):
1553 1555 doc = doc()
1554 1556
1555 1557 ui.write("%s\n\n" % header)
1556 1558 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
1557 1559
1558 1560 def helpext(name):
1559 1561 try:
1560 1562 mod = extensions.find(name)
1561 1563 except KeyError:
1562 1564 raise error.UnknownCommand(name)
1563 1565
1564 1566 doc = gettext(mod.__doc__) or _('no help text available')
1565 1567 if '\n' not in doc:
1566 1568 head, tail = doc, ""
1567 1569 else:
1568 1570 head, tail = doc.split('\n', 1)
1569 1571 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
1570 1572 if tail:
1571 1573 ui.write(minirst.format(tail, textwidth))
1572 1574 ui.status('\n\n')
1573 1575
1574 1576 try:
1575 1577 ct = mod.cmdtable
1576 1578 except AttributeError:
1577 1579 ct = {}
1578 1580
1579 1581 modcmds = set([c.split('|', 1)[0] for c in ct])
1580 1582 helplist(_('list of commands:\n\n'), modcmds.__contains__)
1581 1583
1582 1584 if name and name != 'shortlist':
1583 1585 i = None
1584 1586 for f in (helptopic, helpcmd, helpext):
1585 1587 try:
1586 1588 f(name)
1587 1589 i = None
1588 1590 break
1589 1591 except error.UnknownCommand, inst:
1590 1592 i = inst
1591 1593 if i:
1592 1594 raise i
1593 1595
1594 1596 else:
1595 1597 # program name
1596 1598 if ui.verbose or with_version:
1597 1599 version_(ui)
1598 1600 else:
1599 1601 ui.status(_("Mercurial Distributed SCM\n"))
1600 1602 ui.status('\n')
1601 1603
1602 1604 # list of commands
1603 1605 if name == "shortlist":
1604 1606 header = _('basic commands:\n\n')
1605 1607 else:
1606 1608 header = _('list of commands:\n\n')
1607 1609
1608 1610 helplist(header)
1609 1611
1610 1612 # list all option lists
1611 1613 opt_output = []
1612 1614 for title, options in option_lists:
1613 1615 opt_output.append(("\n%s" % title, None))
1614 1616 for shortopt, longopt, default, desc in options:
1615 1617 if _("DEPRECATED") in desc and not ui.verbose: continue
1616 1618 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1617 1619 longopt and " --%s" % longopt),
1618 1620 "%s%s" % (desc,
1619 1621 default
1620 1622 and _(" (default: %s)") % default
1621 1623 or "")))
1622 1624
1623 1625 if not name:
1624 1626 ui.write(_("\nadditional help topics:\n\n"))
1625 1627 topics = []
1626 1628 for names, header, doc in help.helptable:
1627 1629 topics.append((sorted(names, key=len, reverse=True)[0], header))
1628 1630 topics_len = max([len(s[0]) for s in topics])
1629 1631 for t, desc in topics:
1630 1632 ui.write(" %-*s %s\n" % (topics_len, t, desc))
1631 1633
1632 1634 if opt_output:
1633 1635 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1634 1636 for first, second in opt_output:
1635 1637 if second:
1636 1638 second = util.wrap(second, opts_len + 3)
1637 1639 ui.write(" %-*s %s\n" % (opts_len, first, second))
1638 1640 else:
1639 1641 ui.write("%s\n" % first)
1640 1642
1641 1643 def identify(ui, repo, source=None,
1642 1644 rev=None, num=None, id=None, branch=None, tags=None):
1643 1645 """identify the working copy or specified revision
1644 1646
1645 1647 With no revision, print a summary of the current state of the
1646 1648 repository.
1647 1649
1648 1650 Specifying a path to a repository root or Mercurial bundle will
1649 1651 cause lookup to operate on that repository/bundle.
1650 1652
1651 1653 This summary identifies the repository state using one or two
1652 1654 parent hash identifiers, followed by a "+" if there are
1653 1655 uncommitted changes in the working directory, a list of tags for
1654 1656 this revision and a branch name for non-default branches.
1655 1657 """
1656 1658
1657 1659 if not repo and not source:
1658 1660 raise util.Abort(_("There is no Mercurial repository here "
1659 1661 "(.hg not found)"))
1660 1662
1661 1663 hexfunc = ui.debugflag and hex or short
1662 1664 default = not (num or id or branch or tags)
1663 1665 output = []
1664 1666
1665 1667 revs = []
1666 1668 if source:
1667 1669 source, revs, checkout = hg.parseurl(ui.expandpath(source), [])
1668 1670 repo = hg.repository(ui, source)
1669 1671
1670 1672 if not repo.local():
1671 1673 if not rev and revs:
1672 1674 rev = revs[0]
1673 1675 if not rev:
1674 1676 rev = "tip"
1675 1677 if num or branch or tags:
1676 1678 raise util.Abort(
1677 1679 "can't query remote revision number, branch, or tags")
1678 1680 output = [hexfunc(repo.lookup(rev))]
1679 1681 elif not rev:
1680 1682 ctx = repo[None]
1681 1683 parents = ctx.parents()
1682 1684 changed = False
1683 1685 if default or id or num:
1684 1686 changed = ctx.files() + ctx.deleted()
1685 1687 if default or id:
1686 1688 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1687 1689 (changed) and "+" or "")]
1688 1690 if num:
1689 1691 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1690 1692 (changed) and "+" or ""))
1691 1693 else:
1692 1694 ctx = repo[rev]
1693 1695 if default or id:
1694 1696 output = [hexfunc(ctx.node())]
1695 1697 if num:
1696 1698 output.append(str(ctx.rev()))
1697 1699
1698 1700 if repo.local() and default and not ui.quiet:
1699 1701 b = encoding.tolocal(ctx.branch())
1700 1702 if b != 'default':
1701 1703 output.append("(%s)" % b)
1702 1704
1703 1705 # multiple tags for a single parent separated by '/'
1704 1706 t = "/".join(ctx.tags())
1705 1707 if t:
1706 1708 output.append(t)
1707 1709
1708 1710 if branch:
1709 1711 output.append(encoding.tolocal(ctx.branch()))
1710 1712
1711 1713 if tags:
1712 1714 output.extend(ctx.tags())
1713 1715
1714 1716 ui.write("%s\n" % ' '.join(output))
1715 1717
1716 1718 def import_(ui, repo, patch1, *patches, **opts):
1717 1719 """import an ordered set of patches
1718 1720
1719 1721 Import a list of patches and commit them individually.
1720 1722
1721 1723 If there are outstanding changes in the working directory, import
1722 1724 will abort unless given the -f/--force flag.
1723 1725
1724 1726 You can import a patch straight from a mail message. Even patches
1725 1727 as attachments work (to use the body part, it must have type
1726 1728 text/plain or text/x-patch). From and Subject headers of email
1727 1729 message are used as default committer and commit message. All
1728 1730 text/plain body parts before first diff are added to commit
1729 1731 message.
1730 1732
1731 1733 If the imported patch was generated by hg export, user and
1732 1734 description from patch override values from message headers and
1733 1735 body. Values given on command line with -m/--message and -u/--user
1734 1736 override these.
1735 1737
1736 1738 If --exact is specified, import will set the working directory to
1737 1739 the parent of each patch before applying it, and will abort if the
1738 1740 resulting changeset has a different ID than the one recorded in
1739 1741 the patch. This may happen due to character set problems or other
1740 1742 deficiencies in the text patch format.
1741 1743
1742 1744 With -s/--similarity, hg will attempt to discover renames and
1743 1745 copies in the patch in the same way as 'addremove'.
1744 1746
1745 1747 To read a patch from standard input, use "-" as the patch name. If
1746 1748 a URL is specified, the patch will be downloaded from it.
1747 1749 See 'hg help dates' for a list of formats valid for -d/--date.
1748 1750 """
1749 1751 patches = (patch1,) + patches
1750 1752
1751 1753 date = opts.get('date')
1752 1754 if date:
1753 1755 opts['date'] = util.parsedate(date)
1754 1756
1755 1757 try:
1756 1758 sim = float(opts.get('similarity') or 0)
1757 1759 except ValueError:
1758 1760 raise util.Abort(_('similarity must be a number'))
1759 1761 if sim < 0 or sim > 100:
1760 1762 raise util.Abort(_('similarity must be between 0 and 100'))
1761 1763
1762 1764 if opts.get('exact') or not opts.get('force'):
1763 1765 cmdutil.bail_if_changed(repo)
1764 1766
1765 1767 d = opts["base"]
1766 1768 strip = opts["strip"]
1767 1769 wlock = lock = None
1768 1770 try:
1769 1771 wlock = repo.wlock()
1770 1772 lock = repo.lock()
1771 1773 for p in patches:
1772 1774 pf = os.path.join(d, p)
1773 1775
1774 1776 if pf == '-':
1775 1777 ui.status(_("applying patch from stdin\n"))
1776 1778 pf = sys.stdin
1777 1779 else:
1778 1780 ui.status(_("applying %s\n") % p)
1779 1781 pf = url.open(ui, pf)
1780 1782 data = patch.extract(ui, pf)
1781 1783 tmpname, message, user, date, branch, nodeid, p1, p2 = data
1782 1784
1783 1785 if tmpname is None:
1784 1786 raise util.Abort(_('no diffs found'))
1785 1787
1786 1788 try:
1787 1789 cmdline_message = cmdutil.logmessage(opts)
1788 1790 if cmdline_message:
1789 1791 # pickup the cmdline msg
1790 1792 message = cmdline_message
1791 1793 elif message:
1792 1794 # pickup the patch msg
1793 1795 message = message.strip()
1794 1796 else:
1795 1797 # launch the editor
1796 1798 message = None
1797 1799 ui.debug('message:\n%s\n' % message)
1798 1800
1799 1801 wp = repo.parents()
1800 1802 if opts.get('exact'):
1801 1803 if not nodeid or not p1:
1802 1804 raise util.Abort(_('not a Mercurial patch'))
1803 1805 p1 = repo.lookup(p1)
1804 1806 p2 = repo.lookup(p2 or hex(nullid))
1805 1807
1806 1808 if p1 != wp[0].node():
1807 1809 hg.clean(repo, p1)
1808 1810 repo.dirstate.setparents(p1, p2)
1809 1811 elif p2:
1810 1812 try:
1811 1813 p1 = repo.lookup(p1)
1812 1814 p2 = repo.lookup(p2)
1813 1815 if p1 == wp[0].node():
1814 1816 repo.dirstate.setparents(p1, p2)
1815 1817 except error.RepoError:
1816 1818 pass
1817 1819 if opts.get('exact') or opts.get('import_branch'):
1818 1820 repo.dirstate.setbranch(branch or 'default')
1819 1821
1820 1822 files = {}
1821 1823 try:
1822 1824 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1823 1825 files=files, eolmode=None)
1824 1826 finally:
1825 1827 files = patch.updatedir(ui, repo, files, similarity=sim/100.)
1826 1828 if not opts.get('no_commit'):
1827 1829 m = cmdutil.matchfiles(repo, files or [])
1828 1830 n = repo.commit(message, opts.get('user') or user,
1829 1831 opts.get('date') or date, match=m,
1830 1832 editor=cmdutil.commiteditor)
1831 1833 if opts.get('exact'):
1832 1834 if hex(n) != nodeid:
1833 1835 repo.rollback()
1834 1836 raise util.Abort(_('patch is damaged'
1835 1837 ' or loses information'))
1836 1838 # Force a dirstate write so that the next transaction
1837 1839 # backups an up-do-date file.
1838 1840 repo.dirstate.write()
1839 1841 finally:
1840 1842 os.unlink(tmpname)
1841 1843 finally:
1842 1844 release(lock, wlock)
1843 1845
1844 1846 def incoming(ui, repo, source="default", **opts):
1845 1847 """show new changesets found in source
1846 1848
1847 1849 Show new changesets found in the specified path/URL or the default
1848 1850 pull location. These are the changesets that would have been pulled
1849 1851 if a pull at the time you issued this command.
1850 1852
1851 1853 For remote repository, using --bundle avoids downloading the
1852 1854 changesets twice if the incoming is followed by a pull.
1853 1855
1854 1856 See pull for valid source format details.
1855 1857 """
1856 1858 limit = cmdutil.loglimit(opts)
1857 1859 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts.get('rev'))
1858 1860 other = hg.repository(cmdutil.remoteui(repo, opts), source)
1859 1861 ui.status(_('comparing with %s\n') % url.hidepassword(source))
1860 1862 if revs:
1861 1863 revs = [other.lookup(rev) for rev in revs]
1862 1864 common, incoming, rheads = repo.findcommonincoming(other, heads=revs,
1863 1865 force=opts["force"])
1864 1866 if not incoming:
1865 1867 try:
1866 1868 os.unlink(opts["bundle"])
1867 1869 except:
1868 1870 pass
1869 1871 ui.status(_("no changes found\n"))
1870 1872 return 1
1871 1873
1872 1874 cleanup = None
1873 1875 try:
1874 1876 fname = opts["bundle"]
1875 1877 if fname or not other.local():
1876 1878 # create a bundle (uncompressed if other repo is not local)
1877 1879
1878 1880 if revs is None and other.capable('changegroupsubset'):
1879 1881 revs = rheads
1880 1882
1881 1883 if revs is None:
1882 1884 cg = other.changegroup(incoming, "incoming")
1883 1885 else:
1884 1886 cg = other.changegroupsubset(incoming, revs, 'incoming')
1885 1887 bundletype = other.local() and "HG10BZ" or "HG10UN"
1886 1888 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1887 1889 # keep written bundle?
1888 1890 if opts["bundle"]:
1889 1891 cleanup = None
1890 1892 if not other.local():
1891 1893 # use the created uncompressed bundlerepo
1892 1894 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1893 1895
1894 1896 o = other.changelog.nodesbetween(incoming, revs)[0]
1895 1897 if opts.get('newest_first'):
1896 1898 o.reverse()
1897 1899 displayer = cmdutil.show_changeset(ui, other, opts)
1898 1900 count = 0
1899 1901 for n in o:
1900 1902 if count >= limit:
1901 1903 break
1902 1904 parents = [p for p in other.changelog.parents(n) if p != nullid]
1903 1905 if opts.get('no_merges') and len(parents) == 2:
1904 1906 continue
1905 1907 count += 1
1906 1908 displayer.show(other[n])
1907 1909 finally:
1908 1910 if hasattr(other, 'close'):
1909 1911 other.close()
1910 1912 if cleanup:
1911 1913 os.unlink(cleanup)
1912 1914
1913 1915 def init(ui, dest=".", **opts):
1914 1916 """create a new repository in the given directory
1915 1917
1916 1918 Initialize a new repository in the given directory. If the given
1917 1919 directory does not exist, it will be created.
1918 1920
1919 1921 If no directory is given, the current directory is used.
1920 1922
1921 1923 It is possible to specify an ssh:// URL as the destination.
1922 1924 See 'hg help urls' for more information.
1923 1925 """
1924 1926 hg.repository(cmdutil.remoteui(ui, opts), dest, create=1)
1925 1927
1926 1928 def locate(ui, repo, *pats, **opts):
1927 1929 """locate files matching specific patterns
1928 1930
1929 1931 Print files under Mercurial control in the working directory whose
1930 1932 names match the given patterns.
1931 1933
1932 1934 By default, this command searches all directories in the working
1933 1935 directory. To search just the current directory and its
1934 1936 subdirectories, use "--include .".
1935 1937
1936 1938 If no patterns are given to match, this command prints the names
1937 1939 of all files under Mercurial control in the working directory.
1938 1940
1939 1941 If you want to feed the output of this command into the "xargs"
1940 1942 command, use the -0 option to both this command and "xargs". This
1941 1943 will avoid the problem of "xargs" treating single filenames that
1942 1944 contain whitespace as multiple filenames.
1943 1945 """
1944 1946 end = opts.get('print0') and '\0' or '\n'
1945 1947 rev = opts.get('rev') or None
1946 1948
1947 1949 ret = 1
1948 1950 m = cmdutil.match(repo, pats, opts, default='relglob')
1949 1951 m.bad = lambda x,y: False
1950 1952 for abs in repo[rev].walk(m):
1951 1953 if not rev and abs not in repo.dirstate:
1952 1954 continue
1953 1955 if opts.get('fullpath'):
1954 1956 ui.write(repo.wjoin(abs), end)
1955 1957 else:
1956 1958 ui.write(((pats and m.rel(abs)) or abs), end)
1957 1959 ret = 0
1958 1960
1959 1961 return ret
1960 1962
1961 1963 def log(ui, repo, *pats, **opts):
1962 1964 """show revision history of entire repository or files
1963 1965
1964 1966 Print the revision history of the specified files or the entire
1965 1967 project.
1966 1968
1967 1969 File history is shown without following rename or copy history of
1968 1970 files. Use -f/--follow with a filename to follow history across
1969 1971 renames and copies. --follow without a filename will only show
1970 1972 ancestors or descendants of the starting revision. --follow-first
1971 1973 only follows the first parent of merge revisions.
1972 1974
1973 1975 If no revision range is specified, the default is tip:0 unless
1974 1976 --follow is set, in which case the working directory parent is
1975 1977 used as the starting revision.
1976 1978
1977 1979 See 'hg help dates' for a list of formats valid for -d/--date.
1978 1980
1979 1981 By default this command prints revision number and changeset id,
1980 1982 tags, non-trivial parents, user, date and time, and a summary for
1981 1983 each commit. When the -v/--verbose switch is used, the list of
1982 1984 changed files and full commit message are shown.
1983 1985
1984 1986 NOTE: log -p/--patch may generate unexpected diff output for merge
1985 1987 changesets, as it will only compare the merge changeset against
1986 1988 its first parent. Also, only files different from BOTH parents
1987 1989 will appear in files:.
1988 1990 """
1989 1991
1990 1992 get = util.cachefunc(lambda r: repo[r])
1991 1993 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1992 1994
1993 1995 limit = cmdutil.loglimit(opts)
1994 1996 count = 0
1995 1997
1996 1998 if opts.get('copies') and opts.get('rev'):
1997 1999 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
1998 2000 else:
1999 2001 endrev = len(repo)
2000 2002 rcache = {}
2001 2003 ncache = {}
2002 2004 def getrenamed(fn, rev):
2003 2005 '''looks up all renames for a file (up to endrev) the first
2004 2006 time the file is given. It indexes on the changerev and only
2005 2007 parses the manifest if linkrev != changerev.
2006 2008 Returns rename info for fn at changerev rev.'''
2007 2009 if fn not in rcache:
2008 2010 rcache[fn] = {}
2009 2011 ncache[fn] = {}
2010 2012 fl = repo.file(fn)
2011 2013 for i in fl:
2012 2014 node = fl.node(i)
2013 2015 lr = fl.linkrev(i)
2014 2016 renamed = fl.renamed(node)
2015 2017 rcache[fn][lr] = renamed
2016 2018 if renamed:
2017 2019 ncache[fn][node] = renamed
2018 2020 if lr >= endrev:
2019 2021 break
2020 2022 if rev in rcache[fn]:
2021 2023 return rcache[fn][rev]
2022 2024
2023 2025 # If linkrev != rev (i.e. rev not found in rcache) fallback to
2024 2026 # filectx logic.
2025 2027
2026 2028 try:
2027 2029 return repo[rev][fn].renamed()
2028 2030 except error.LookupError:
2029 2031 pass
2030 2032 return None
2031 2033
2032 2034 df = False
2033 2035 if opts["date"]:
2034 2036 df = util.matchdate(opts["date"])
2035 2037
2036 2038 only_branches = opts.get('only_branch')
2037 2039
2038 2040 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
2039 2041 for st, rev, fns in changeiter:
2040 2042 if st == 'add':
2041 2043 parents = [p for p in repo.changelog.parentrevs(rev)
2042 2044 if p != nullrev]
2043 2045 if opts.get('no_merges') and len(parents) == 2:
2044 2046 continue
2045 2047 if opts.get('only_merges') and len(parents) != 2:
2046 2048 continue
2047 2049
2048 2050 ctx = get(rev)
2049 2051 if only_branches and ctx.branch() not in only_branches:
2050 2052 continue
2051 2053
2052 2054 if df and not df(ctx.date()[0]):
2053 2055 continue
2054 2056
2055 2057 if opts.get('keyword'):
2056 2058 miss = 0
2057 2059 for k in [kw.lower() for kw in opts['keyword']]:
2058 2060 if not (k in ctx.user().lower() or
2059 2061 k in ctx.description().lower() or
2060 2062 k in " ".join(ctx.files()).lower()):
2061 2063 miss = 1
2062 2064 break
2063 2065 if miss:
2064 2066 continue
2065 2067
2066 2068 if opts['user']:
2067 2069 if not [k for k in opts['user'] if k in ctx.user()]:
2068 2070 continue
2069 2071
2070 2072 copies = []
2071 2073 if opts.get('copies') and rev:
2072 2074 for fn in ctx.files():
2073 2075 rename = getrenamed(fn, rev)
2074 2076 if rename:
2075 2077 copies.append((fn, rename[0]))
2076 2078
2077 2079 displayer.show(ctx, copies=copies)
2078 2080
2079 2081 elif st == 'iter':
2080 2082 if count == limit: break
2081 2083 if displayer.flush(rev):
2082 2084 count += 1
2083 2085
2084 2086 def manifest(ui, repo, node=None, rev=None):
2085 2087 """output the current or given revision of the project manifest
2086 2088
2087 2089 Print a list of version controlled files for the given revision.
2088 2090 If no revision is given, the first parent of the working directory
2089 2091 is used, or the null revision if no revision is checked out.
2090 2092
2091 2093 With -v, print file permissions, symlink and executable bits.
2092 2094 With --debug, print file revision hashes.
2093 2095 """
2094 2096
2095 2097 if rev and node:
2096 2098 raise util.Abort(_("please specify just one revision"))
2097 2099
2098 2100 if not node:
2099 2101 node = rev
2100 2102
2101 2103 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2102 2104 ctx = repo[node]
2103 2105 for f in ctx:
2104 2106 if ui.debugflag:
2105 2107 ui.write("%40s " % hex(ctx.manifest()[f]))
2106 2108 if ui.verbose:
2107 2109 ui.write(decor[ctx.flags(f)])
2108 2110 ui.write("%s\n" % f)
2109 2111
2110 2112 def merge(ui, repo, node=None, **opts):
2111 2113 """merge working directory with another revision
2112 2114
2113 2115 The current working directory is updated with all changes made in
2114 2116 the requested revision since the last common predecessor revision.
2115 2117
2116 2118 Files that changed between either parent are marked as changed for
2117 2119 the next commit and a commit must be performed before any further
2118 2120 updates to the repository are allowed. The next commit will have
2119 2121 two parents.
2120 2122
2121 2123 If no revision is specified, the working directory's parent is a
2122 2124 head revision, and the current branch contains exactly one other
2123 2125 head, the other head is merged with by default. Otherwise, an
2124 2126 explicit revision with which to merge with must be provided.
2125 2127 """
2126 2128
2127 2129 if opts.get('rev') and node:
2128 2130 raise util.Abort(_("please specify just one revision"))
2129 2131 if not node:
2130 2132 node = opts.get('rev')
2131 2133
2132 2134 if not node:
2133 2135 branch = repo.changectx(None).branch()
2134 2136 bheads = repo.branchheads(branch)
2135 2137 if len(bheads) > 2:
2136 2138 raise util.Abort(_("branch '%s' has %d heads - "
2137 2139 "please merge with an explicit rev") %
2138 2140 (branch, len(bheads)))
2139 2141
2140 2142 parent = repo.dirstate.parents()[0]
2141 2143 if len(bheads) == 1:
2142 2144 if len(repo.heads()) > 1:
2143 2145 raise util.Abort(_("branch '%s' has one head - "
2144 2146 "please merge with an explicit rev") %
2145 2147 branch)
2146 2148 msg = _('there is nothing to merge')
2147 2149 if parent != repo.lookup(repo[None].branch()):
2148 2150 msg = _('%s - use "hg update" instead') % msg
2149 2151 raise util.Abort(msg)
2150 2152
2151 2153 if parent not in bheads:
2152 2154 raise util.Abort(_('working dir not at a head rev - '
2153 2155 'use "hg update" or merge with an explicit rev'))
2154 2156 node = parent == bheads[0] and bheads[-1] or bheads[0]
2155 2157
2156 2158 if opts.get('preview'):
2157 2159 p1 = repo['.']
2158 2160 p2 = repo[node]
2159 2161 common = p1.ancestor(p2)
2160 2162 roots, heads = [common.node()], [p2.node()]
2161 2163 displayer = cmdutil.show_changeset(ui, repo, opts)
2162 2164 for node in repo.changelog.nodesbetween(roots=roots, heads=heads)[0]:
2163 2165 if node not in roots:
2164 2166 displayer.show(repo[node])
2165 2167 return 0
2166 2168
2167 2169 return hg.merge(repo, node, force=opts.get('force'))
2168 2170
2169 2171 def outgoing(ui, repo, dest=None, **opts):
2170 2172 """show changesets not found in destination
2171 2173
2172 2174 Show changesets not found in the specified destination repository
2173 2175 or the default push location. These are the changesets that would
2174 2176 be pushed if a push was requested.
2175 2177
2176 2178 See pull for valid destination format details.
2177 2179 """
2178 2180 limit = cmdutil.loglimit(opts)
2179 2181 dest, revs, checkout = hg.parseurl(
2180 2182 ui.expandpath(dest or 'default-push', dest or 'default'), opts.get('rev'))
2181 2183 if revs:
2182 2184 revs = [repo.lookup(rev) for rev in revs]
2183 2185
2184 2186 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
2185 2187 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2186 2188 o = repo.findoutgoing(other, force=opts.get('force'))
2187 2189 if not o:
2188 2190 ui.status(_("no changes found\n"))
2189 2191 return 1
2190 2192 o = repo.changelog.nodesbetween(o, revs)[0]
2191 2193 if opts.get('newest_first'):
2192 2194 o.reverse()
2193 2195 displayer = cmdutil.show_changeset(ui, repo, opts)
2194 2196 count = 0
2195 2197 for n in o:
2196 2198 if count >= limit:
2197 2199 break
2198 2200 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2199 2201 if opts.get('no_merges') and len(parents) == 2:
2200 2202 continue
2201 2203 count += 1
2202 2204 displayer.show(repo[n])
2203 2205
2204 2206 def parents(ui, repo, file_=None, **opts):
2205 2207 """show the parents of the working directory or revision
2206 2208
2207 2209 Print the working directory's parent revisions. If a revision is
2208 2210 given via -r/--rev, the parent of that revision will be printed.
2209 2211 If a file argument is given, the revision in which the file was
2210 2212 last changed (before the working directory revision or the
2211 2213 argument to --rev if given) is printed.
2212 2214 """
2213 2215 rev = opts.get('rev')
2214 2216 if rev:
2215 2217 ctx = repo[rev]
2216 2218 else:
2217 2219 ctx = repo[None]
2218 2220
2219 2221 if file_:
2220 2222 m = cmdutil.match(repo, (file_,), opts)
2221 2223 if m.anypats() or len(m.files()) != 1:
2222 2224 raise util.Abort(_('can only specify an explicit filename'))
2223 2225 file_ = m.files()[0]
2224 2226 filenodes = []
2225 2227 for cp in ctx.parents():
2226 2228 if not cp:
2227 2229 continue
2228 2230 try:
2229 2231 filenodes.append(cp.filenode(file_))
2230 2232 except error.LookupError:
2231 2233 pass
2232 2234 if not filenodes:
2233 2235 raise util.Abort(_("'%s' not found in manifest!") % file_)
2234 2236 fl = repo.file(file_)
2235 2237 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2236 2238 else:
2237 2239 p = [cp.node() for cp in ctx.parents()]
2238 2240
2239 2241 displayer = cmdutil.show_changeset(ui, repo, opts)
2240 2242 for n in p:
2241 2243 if n != nullid:
2242 2244 displayer.show(repo[n])
2243 2245
2244 2246 def paths(ui, repo, search=None):
2245 2247 """show aliases for remote repositories
2246 2248
2247 2249 Show definition of symbolic path name NAME. If no name is given,
2248 2250 show definition of all available names.
2249 2251
2250 2252 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2251 2253 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2252 2254
2253 2255 See 'hg help urls' for more information.
2254 2256 """
2255 2257 if search:
2256 2258 for name, path in ui.configitems("paths"):
2257 2259 if name == search:
2258 2260 ui.write("%s\n" % url.hidepassword(path))
2259 2261 return
2260 2262 ui.warn(_("not found!\n"))
2261 2263 return 1
2262 2264 else:
2263 2265 for name, path in ui.configitems("paths"):
2264 2266 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2265 2267
2266 2268 def postincoming(ui, repo, modheads, optupdate, checkout):
2267 2269 if modheads == 0:
2268 2270 return
2269 2271 if optupdate:
2270 2272 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2271 2273 return hg.update(repo, checkout)
2272 2274 else:
2273 2275 ui.status(_("not updating, since new heads added\n"))
2274 2276 if modheads > 1:
2275 2277 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2276 2278 else:
2277 2279 ui.status(_("(run 'hg update' to get a working copy)\n"))
2278 2280
2279 2281 def pull(ui, repo, source="default", **opts):
2280 2282 """pull changes from the specified source
2281 2283
2282 2284 Pull changes from a remote repository to a local one.
2283 2285
2284 2286 This finds all changes from the repository at the specified path
2285 2287 or URL and adds them to a local repository (the current one unless
2286 2288 -R is specified). By default, this does not update the copy of the
2287 2289 project in the working directory.
2288 2290
2289 2291 Use hg incoming if you want to see what would have been added by a
2290 2292 pull at the time you issued this command. If you then decide to
2291 2293 added those changes to the repository, you should use pull -r X
2292 2294 where X is the last changeset listed by hg incoming.
2293 2295
2294 2296 If SOURCE is omitted, the 'default' path will be used.
2295 2297 See 'hg help urls' for more information.
2296 2298 """
2297 2299 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts.get('rev'))
2298 2300 other = hg.repository(cmdutil.remoteui(repo, opts), source)
2299 2301 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2300 2302 if revs:
2301 2303 try:
2302 2304 revs = [other.lookup(rev) for rev in revs]
2303 2305 except error.CapabilityError:
2304 2306 err = _("Other repository doesn't support revision lookup, "
2305 2307 "so a rev cannot be specified.")
2306 2308 raise util.Abort(err)
2307 2309
2308 2310 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2309 2311 return postincoming(ui, repo, modheads, opts.get('update'), checkout)
2310 2312
2311 2313 def push(ui, repo, dest=None, **opts):
2312 2314 """push changes to the specified destination
2313 2315
2314 2316 Push changes from the local repository to the given destination.
2315 2317
2316 2318 This is the symmetrical operation for pull. It moves changes from
2317 2319 the current repository to a different one. If the destination is
2318 2320 local this is identical to a pull in that directory from the
2319 2321 current one.
2320 2322
2321 2323 By default, push will refuse to run if it detects the result would
2322 2324 increase the number of remote heads. This generally indicates the
2323 2325 user forgot to pull and merge before pushing.
2324 2326
2325 2327 If -r/--rev is used, the named revision and all its ancestors will
2326 2328 be pushed to the remote repository.
2327 2329
2328 2330 Please see 'hg help urls' for important details about ssh://
2329 2331 URLs. If DESTINATION is omitted, a default path will be used.
2330 2332 """
2331 2333 dest, revs, checkout = hg.parseurl(
2332 2334 ui.expandpath(dest or 'default-push', dest or 'default'), opts.get('rev'))
2333 2335 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
2334 2336 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
2335 2337 if revs:
2336 2338 revs = [repo.lookup(rev) for rev in revs]
2337 2339
2338 2340 # push subrepos depth-first for coherent ordering
2339 2341 c = repo['']
2340 2342 subs = c.substate # only repos that are committed
2341 2343 for s in sorted(subs):
2342 2344 c.sub(s).push(opts.get('force'))
2343 2345
2344 2346 r = repo.push(other, opts.get('force'), revs=revs)
2345 2347 return r == 0
2346 2348
2347 2349 def recover(ui, repo):
2348 2350 """roll back an interrupted transaction
2349 2351
2350 2352 Recover from an interrupted commit or pull.
2351 2353
2352 2354 This command tries to fix the repository status after an
2353 2355 interrupted operation. It should only be necessary when Mercurial
2354 2356 suggests it.
2355 2357 """
2356 2358 if repo.recover():
2357 2359 return hg.verify(repo)
2358 2360 return 1
2359 2361
2360 2362 def remove(ui, repo, *pats, **opts):
2361 2363 """remove the specified files on the next commit
2362 2364
2363 2365 Schedule the indicated files for removal from the repository.
2364 2366
2365 2367 This only removes files from the current branch, not from the
2366 2368 entire project history. -A/--after can be used to remove only
2367 2369 files that have already been deleted, -f/--force can be used to
2368 2370 force deletion, and -Af can be used to remove files from the next
2369 2371 revision without deleting them from the working directory.
2370 2372
2371 2373 The following table details the behavior of remove for different
2372 2374 file states (columns) and option combinations (rows). The file
2373 2375 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
2374 2376 reported by hg status). The actions are Warn, Remove (from branch)
2375 2377 and Delete (from disk)::
2376 2378
2377 2379 A C M !
2378 2380 none W RD W R
2379 2381 -f R RD RD R
2380 2382 -A W W W R
2381 2383 -Af R R R R
2382 2384
2383 2385 This command schedules the files to be removed at the next commit.
2384 2386 To undo a remove before that, see hg revert.
2385 2387 """
2386 2388
2387 2389 after, force = opts.get('after'), opts.get('force')
2388 2390 if not pats and not after:
2389 2391 raise util.Abort(_('no files specified'))
2390 2392
2391 2393 m = cmdutil.match(repo, pats, opts)
2392 2394 s = repo.status(match=m, clean=True)
2393 2395 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2394 2396
2395 2397 for f in m.files():
2396 2398 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2397 2399 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
2398 2400
2399 2401 def warn(files, reason):
2400 2402 for f in files:
2401 2403 ui.warn(_('not removing %s: file %s (use -f to force removal)\n')
2402 2404 % (m.rel(f), reason))
2403 2405
2404 2406 if force:
2405 2407 remove, forget = modified + deleted + clean, added
2406 2408 elif after:
2407 2409 remove, forget = deleted, []
2408 2410 warn(modified + added + clean, _('still exists'))
2409 2411 else:
2410 2412 remove, forget = deleted + clean, []
2411 2413 warn(modified, _('is modified'))
2412 2414 warn(added, _('has been marked for add'))
2413 2415
2414 2416 for f in sorted(remove + forget):
2415 2417 if ui.verbose or not m.exact(f):
2416 2418 ui.status(_('removing %s\n') % m.rel(f))
2417 2419
2418 2420 repo.forget(forget)
2419 2421 repo.remove(remove, unlink=not after)
2420 2422
2421 2423 def rename(ui, repo, *pats, **opts):
2422 2424 """rename files; equivalent of copy + remove
2423 2425
2424 2426 Mark dest as copies of sources; mark sources for deletion. If dest
2425 2427 is a directory, copies are put in that directory. If dest is a
2426 2428 file, there can only be one source.
2427 2429
2428 2430 By default, this command copies the contents of files as they
2429 2431 exist in the working directory. If invoked with -A/--after, the
2430 2432 operation is recorded, but no copying is performed.
2431 2433
2432 2434 This command takes effect at the next commit. To undo a rename
2433 2435 before that, see hg revert.
2434 2436 """
2435 2437 wlock = repo.wlock(False)
2436 2438 try:
2437 2439 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2438 2440 finally:
2439 2441 wlock.release()
2440 2442
2441 2443 def resolve(ui, repo, *pats, **opts):
2442 2444 """retry file merges from a merge or update
2443 2445
2444 2446 This command will cleanly retry unresolved file merges using file
2445 2447 revisions preserved from the last update or merge. To attempt to
2446 2448 resolve all unresolved files, use the -a/--all switch.
2447 2449
2448 2450 If a conflict is resolved manually, please note that the changes
2449 2451 will be overwritten if the merge is retried with resolve. The
2450 2452 -m/--mark switch should be used to mark the file as resolved.
2451 2453
2452 2454 This command also allows listing resolved files and manually
2453 2455 indicating whether or not files are resolved. All files must be
2454 2456 marked as resolved before a commit is permitted.
2455 2457
2456 2458 The codes used to show the status of files are::
2457 2459
2458 2460 U = unresolved
2459 2461 R = resolved
2460 2462 """
2461 2463
2462 2464 all, mark, unmark, show = [opts.get(o) for o in 'all mark unmark list'.split()]
2463 2465
2464 2466 if (show and (mark or unmark)) or (mark and unmark):
2465 2467 raise util.Abort(_("too many options specified"))
2466 2468 if pats and all:
2467 2469 raise util.Abort(_("can't specify --all and patterns"))
2468 2470 if not (all or pats or show or mark or unmark):
2469 2471 raise util.Abort(_('no files or directories specified; '
2470 2472 'use --all to remerge all files'))
2471 2473
2472 2474 ms = merge_.mergestate(repo)
2473 2475 m = cmdutil.match(repo, pats, opts)
2474 2476
2475 2477 for f in ms:
2476 2478 if m(f):
2477 2479 if show:
2478 2480 ui.write("%s %s\n" % (ms[f].upper(), f))
2479 2481 elif mark:
2480 2482 ms.mark(f, "r")
2481 2483 elif unmark:
2482 2484 ms.mark(f, "u")
2483 2485 else:
2484 2486 wctx = repo[None]
2485 2487 mctx = wctx.parents()[-1]
2486 2488
2487 2489 # backup pre-resolve (merge uses .orig for its own purposes)
2488 2490 a = repo.wjoin(f)
2489 2491 util.copyfile(a, a + ".resolve")
2490 2492
2491 2493 # resolve file
2492 2494 ms.resolve(f, wctx, mctx)
2493 2495
2494 2496 # replace filemerge's .orig file with our resolve file
2495 2497 util.rename(a + ".resolve", a + ".orig")
2496 2498
2497 2499 def revert(ui, repo, *pats, **opts):
2498 2500 """restore individual files or directories to an earlier state
2499 2501
2500 2502 (Use update -r to check out earlier revisions, revert does not
2501 2503 change the working directory parents.)
2502 2504
2503 2505 With no revision specified, revert the named files or directories
2504 2506 to the contents they had in the parent of the working directory.
2505 2507 This restores the contents of the affected files to an unmodified
2506 2508 state and unschedules adds, removes, copies, and renames. If the
2507 2509 working directory has two parents, you must explicitly specify the
2508 2510 revision to revert to.
2509 2511
2510 2512 Using the -r/--rev option, revert the given files or directories
2511 2513 to their contents as of a specific revision. This can be helpful
2512 2514 to "roll back" some or all of an earlier change. See 'hg help
2513 2515 dates' for a list of formats valid for -d/--date.
2514 2516
2515 2517 Revert modifies the working directory. It does not commit any
2516 2518 changes, or change the parent of the working directory. If you
2517 2519 revert to a revision other than the parent of the working
2518 2520 directory, the reverted files will thus appear modified
2519 2521 afterwards.
2520 2522
2521 2523 If a file has been deleted, it is restored. If the executable mode
2522 2524 of a file was changed, it is reset.
2523 2525
2524 2526 If names are given, all files matching the names are reverted.
2525 2527 If no arguments are given, no files are reverted.
2526 2528
2527 2529 Modified files are saved with a .orig suffix before reverting.
2528 2530 To disable these backups, use --no-backup.
2529 2531 """
2530 2532
2531 2533 if opts["date"]:
2532 2534 if opts["rev"]:
2533 2535 raise util.Abort(_("you can't specify a revision and a date"))
2534 2536 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2535 2537
2536 2538 if not pats and not opts.get('all'):
2537 2539 raise util.Abort(_('no files or directories specified; '
2538 2540 'use --all to revert the whole repo'))
2539 2541
2540 2542 parent, p2 = repo.dirstate.parents()
2541 2543 if not opts.get('rev') and p2 != nullid:
2542 2544 raise util.Abort(_('uncommitted merge - please provide a '
2543 2545 'specific revision'))
2544 2546 ctx = repo[opts.get('rev')]
2545 2547 node = ctx.node()
2546 2548 mf = ctx.manifest()
2547 2549 if node == parent:
2548 2550 pmf = mf
2549 2551 else:
2550 2552 pmf = None
2551 2553
2552 2554 # need all matching names in dirstate and manifest of target rev,
2553 2555 # so have to walk both. do not print errors if files exist in one
2554 2556 # but not other.
2555 2557
2556 2558 names = {}
2557 2559
2558 2560 wlock = repo.wlock()
2559 2561 try:
2560 2562 # walk dirstate.
2561 2563
2562 2564 m = cmdutil.match(repo, pats, opts)
2563 2565 m.bad = lambda x,y: False
2564 2566 for abs in repo.walk(m):
2565 2567 names[abs] = m.rel(abs), m.exact(abs)
2566 2568
2567 2569 # walk target manifest.
2568 2570
2569 2571 def badfn(path, msg):
2570 2572 if path in names:
2571 2573 return
2572 2574 path_ = path + '/'
2573 2575 for f in names:
2574 2576 if f.startswith(path_):
2575 2577 return
2576 2578 ui.warn("%s: %s\n" % (m.rel(path), msg))
2577 2579
2578 2580 m = cmdutil.match(repo, pats, opts)
2579 2581 m.bad = badfn
2580 2582 for abs in repo[node].walk(m):
2581 2583 if abs not in names:
2582 2584 names[abs] = m.rel(abs), m.exact(abs)
2583 2585
2584 2586 m = cmdutil.matchfiles(repo, names)
2585 2587 changes = repo.status(match=m)[:4]
2586 2588 modified, added, removed, deleted = map(set, changes)
2587 2589
2588 2590 # if f is a rename, also revert the source
2589 2591 cwd = repo.getcwd()
2590 2592 for f in added:
2591 2593 src = repo.dirstate.copied(f)
2592 2594 if src and src not in names and repo.dirstate[src] == 'r':
2593 2595 removed.add(src)
2594 2596 names[src] = (repo.pathto(src, cwd), True)
2595 2597
2596 2598 def removeforget(abs):
2597 2599 if repo.dirstate[abs] == 'a':
2598 2600 return _('forgetting %s\n')
2599 2601 return _('removing %s\n')
2600 2602
2601 2603 revert = ([], _('reverting %s\n'))
2602 2604 add = ([], _('adding %s\n'))
2603 2605 remove = ([], removeforget)
2604 2606 undelete = ([], _('undeleting %s\n'))
2605 2607
2606 2608 disptable = (
2607 2609 # dispatch table:
2608 2610 # file state
2609 2611 # action if in target manifest
2610 2612 # action if not in target manifest
2611 2613 # make backup if in target manifest
2612 2614 # make backup if not in target manifest
2613 2615 (modified, revert, remove, True, True),
2614 2616 (added, revert, remove, True, False),
2615 2617 (removed, undelete, None, False, False),
2616 2618 (deleted, revert, remove, False, False),
2617 2619 )
2618 2620
2619 2621 for abs, (rel, exact) in sorted(names.items()):
2620 2622 mfentry = mf.get(abs)
2621 2623 target = repo.wjoin(abs)
2622 2624 def handle(xlist, dobackup):
2623 2625 xlist[0].append(abs)
2624 2626 if dobackup and not opts.get('no_backup') and util.lexists(target):
2625 2627 bakname = "%s.orig" % rel
2626 2628 ui.note(_('saving current version of %s as %s\n') %
2627 2629 (rel, bakname))
2628 2630 if not opts.get('dry_run'):
2629 2631 util.copyfile(target, bakname)
2630 2632 if ui.verbose or not exact:
2631 2633 msg = xlist[1]
2632 2634 if not isinstance(msg, basestring):
2633 2635 msg = msg(abs)
2634 2636 ui.status(msg % rel)
2635 2637 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2636 2638 if abs not in table: continue
2637 2639 # file has changed in dirstate
2638 2640 if mfentry:
2639 2641 handle(hitlist, backuphit)
2640 2642 elif misslist is not None:
2641 2643 handle(misslist, backupmiss)
2642 2644 break
2643 2645 else:
2644 2646 if abs not in repo.dirstate:
2645 2647 if mfentry:
2646 2648 handle(add, True)
2647 2649 elif exact:
2648 2650 ui.warn(_('file not managed: %s\n') % rel)
2649 2651 continue
2650 2652 # file has not changed in dirstate
2651 2653 if node == parent:
2652 2654 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2653 2655 continue
2654 2656 if pmf is None:
2655 2657 # only need parent manifest in this unlikely case,
2656 2658 # so do not read by default
2657 2659 pmf = repo[parent].manifest()
2658 2660 if abs in pmf:
2659 2661 if mfentry:
2660 2662 # if version of file is same in parent and target
2661 2663 # manifests, do nothing
2662 2664 if (pmf[abs] != mfentry or
2663 2665 pmf.flags(abs) != mf.flags(abs)):
2664 2666 handle(revert, False)
2665 2667 else:
2666 2668 handle(remove, False)
2667 2669
2668 2670 if not opts.get('dry_run'):
2669 2671 def checkout(f):
2670 2672 fc = ctx[f]
2671 2673 repo.wwrite(f, fc.data(), fc.flags())
2672 2674
2673 2675 audit_path = util.path_auditor(repo.root)
2674 2676 for f in remove[0]:
2675 2677 if repo.dirstate[f] == 'a':
2676 2678 repo.dirstate.forget(f)
2677 2679 continue
2678 2680 audit_path(f)
2679 2681 try:
2680 2682 util.unlink(repo.wjoin(f))
2681 2683 except OSError:
2682 2684 pass
2683 2685 repo.dirstate.remove(f)
2684 2686
2685 2687 normal = None
2686 2688 if node == parent:
2687 2689 # We're reverting to our parent. If possible, we'd like status
2688 2690 # to report the file as clean. We have to use normallookup for
2689 2691 # merges to avoid losing information about merged/dirty files.
2690 2692 if p2 != nullid:
2691 2693 normal = repo.dirstate.normallookup
2692 2694 else:
2693 2695 normal = repo.dirstate.normal
2694 2696 for f in revert[0]:
2695 2697 checkout(f)
2696 2698 if normal:
2697 2699 normal(f)
2698 2700
2699 2701 for f in add[0]:
2700 2702 checkout(f)
2701 2703 repo.dirstate.add(f)
2702 2704
2703 2705 normal = repo.dirstate.normallookup
2704 2706 if node == parent and p2 == nullid:
2705 2707 normal = repo.dirstate.normal
2706 2708 for f in undelete[0]:
2707 2709 checkout(f)
2708 2710 normal(f)
2709 2711
2710 2712 finally:
2711 2713 wlock.release()
2712 2714
2713 2715 def rollback(ui, repo):
2714 2716 """roll back the last transaction
2715 2717
2716 2718 This command should be used with care. There is only one level of
2717 2719 rollback, and there is no way to undo a rollback. It will also
2718 2720 restore the dirstate at the time of the last transaction, losing
2719 2721 any dirstate changes since that time. This command does not alter
2720 2722 the working directory.
2721 2723
2722 2724 Transactions are used to encapsulate the effects of all commands
2723 2725 that create new changesets or propagate existing changesets into a
2724 2726 repository. For example, the following commands are transactional,
2725 2727 and their effects can be rolled back::
2726 2728
2727 2729 commit
2728 2730 import
2729 2731 pull
2730 2732 push (with this repository as destination)
2731 2733 unbundle
2732 2734
2733 2735 This command is not intended for use on public repositories. Once
2734 2736 changes are visible for pull by other users, rolling a transaction
2735 2737 back locally is ineffective (someone else may already have pulled
2736 2738 the changes). Furthermore, a race is possible with readers of the
2737 2739 repository; for example an in-progress pull from the repository
2738 2740 may fail if a rollback is performed.
2739 2741 """
2740 2742 repo.rollback()
2741 2743
2742 2744 def root(ui, repo):
2743 2745 """print the root (top) of the current working directory
2744 2746
2745 2747 Print the root directory of the current repository.
2746 2748 """
2747 2749 ui.write(repo.root + "\n")
2748 2750
2749 2751 def serve(ui, repo, **opts):
2750 2752 """export the repository via HTTP
2751 2753
2752 2754 Start a local HTTP repository browser and pull server.
2753 2755
2754 2756 By default, the server logs accesses to stdout and errors to
2755 2757 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
2756 2758 files.
2757 2759 """
2758 2760
2759 2761 if opts["stdio"]:
2760 2762 if repo is None:
2761 2763 raise error.RepoError(_("There is no Mercurial repository here"
2762 2764 " (.hg not found)"))
2763 2765 s = sshserver.sshserver(ui, repo)
2764 2766 s.serve_forever()
2765 2767
2766 2768 baseui = repo and repo.baseui or ui
2767 2769 optlist = ("name templates style address port prefix ipv6"
2768 2770 " accesslog errorlog webdir_conf certificate encoding")
2769 2771 for o in optlist.split():
2770 2772 if opts.get(o, None):
2771 2773 baseui.setconfig("web", o, str(opts[o]))
2772 2774 if (repo is not None) and (repo.ui != baseui):
2773 2775 repo.ui.setconfig("web", o, str(opts[o]))
2774 2776
2775 2777 if repo is None and not ui.config("web", "webdir_conf"):
2776 2778 raise error.RepoError(_("There is no Mercurial repository here"
2777 2779 " (.hg not found)"))
2778 2780
2779 2781 class service(object):
2780 2782 def init(self):
2781 2783 util.set_signal_handler()
2782 2784 self.httpd = server.create_server(baseui, repo)
2783 2785
2784 2786 if not ui.verbose: return
2785 2787
2786 2788 if self.httpd.prefix:
2787 2789 prefix = self.httpd.prefix.strip('/') + '/'
2788 2790 else:
2789 2791 prefix = ''
2790 2792
2791 2793 port = ':%d' % self.httpd.port
2792 2794 if port == ':80':
2793 2795 port = ''
2794 2796
2795 2797 bindaddr = self.httpd.addr
2796 2798 if bindaddr == '0.0.0.0':
2797 2799 bindaddr = '*'
2798 2800 elif ':' in bindaddr: # IPv6
2799 2801 bindaddr = '[%s]' % bindaddr
2800 2802
2801 2803 fqaddr = self.httpd.fqaddr
2802 2804 if ':' in fqaddr:
2803 2805 fqaddr = '[%s]' % fqaddr
2804 2806 ui.status(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
2805 2807 (fqaddr, port, prefix, bindaddr, self.httpd.port))
2806 2808
2807 2809 def run(self):
2808 2810 self.httpd.serve_forever()
2809 2811
2810 2812 service = service()
2811 2813
2812 2814 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2813 2815
2814 2816 def status(ui, repo, *pats, **opts):
2815 2817 """show changed files in the working directory
2816 2818
2817 2819 Show status of files in the repository. If names are given, only
2818 2820 files that match are shown. Files that are clean or ignored or
2819 2821 the source of a copy/move operation, are not listed unless
2820 2822 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
2821 2823 Unless options described with "show only ..." are given, the
2822 2824 options -mardu are used.
2823 2825
2824 2826 Option -q/--quiet hides untracked (unknown and ignored) files
2825 2827 unless explicitly requested with -u/--unknown or -i/--ignored.
2826 2828
2827 2829 NOTE: status may appear to disagree with diff if permissions have
2828 2830 changed or a merge has occurred. The standard diff format does not
2829 2831 report permission changes and diff only reports changes relative
2830 2832 to one merge parent.
2831 2833
2832 2834 If one revision is given, it is used as the base revision.
2833 2835 If two revisions are given, the differences between them are
2834 2836 shown.
2835 2837
2836 2838 The codes used to show the status of files are::
2837 2839
2838 2840 M = modified
2839 2841 A = added
2840 2842 R = removed
2841 2843 C = clean
2842 2844 ! = missing (deleted by non-hg command, but still tracked)
2843 2845 ? = not tracked
2844 2846 I = ignored
2845 2847 = origin of the previous file listed as A (added)
2846 2848 """
2847 2849
2848 2850 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2849 2851 cwd = (pats and repo.getcwd()) or ''
2850 2852 end = opts.get('print0') and '\0' or '\n'
2851 2853 copy = {}
2852 2854 states = 'modified added removed deleted unknown ignored clean'.split()
2853 2855 show = [k for k in states if opts.get(k)]
2854 2856 if opts.get('all'):
2855 2857 show += ui.quiet and (states[:4] + ['clean']) or states
2856 2858 if not show:
2857 2859 show = ui.quiet and states[:4] or states[:5]
2858 2860
2859 2861 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
2860 2862 'ignored' in show, 'clean' in show, 'unknown' in show)
2861 2863 changestates = zip(states, 'MAR!?IC', stat)
2862 2864
2863 2865 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
2864 2866 ctxn = repo[nullid]
2865 2867 ctx1 = repo[node1]
2866 2868 ctx2 = repo[node2]
2867 2869 added = stat[1]
2868 2870 if node2 is None:
2869 2871 added = stat[0] + stat[1] # merged?
2870 2872
2871 2873 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
2872 2874 if k in added:
2873 2875 copy[k] = v
2874 2876 elif v in added:
2875 2877 copy[v] = k
2876 2878
2877 2879 for state, char, files in changestates:
2878 2880 if state in show:
2879 2881 format = "%s %%s%s" % (char, end)
2880 2882 if opts.get('no_status'):
2881 2883 format = "%%s%s" % end
2882 2884
2883 2885 for f in files:
2884 2886 ui.write(format % repo.pathto(f, cwd))
2885 2887 if f in copy:
2886 2888 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end))
2887 2889
2888 2890 def summary(ui, repo, **opts):
2889 2891 """summarize working directory state
2890 2892
2891 2893 This generates a brief summary of the working directory state,
2892 2894 including parents, branch, commit status, and available updates.
2893 2895
2894 2896 With the --remote option, this will check the default paths for
2895 2897 incoming and outgoing changes. This can be time-consuming.
2896 2898 """
2897 2899
2898 2900 ctx = repo[None]
2899 2901 parents = ctx.parents()
2900 2902 pnode = parents[0].node()
2901 2903 tags = repo.tags()
2902 2904
2903 2905 for p in parents:
2904 2906 t = ' '.join([t for t in tags if tags[t] == p.node()])
2905 2907 if p.rev() == -1:
2906 2908 if not len(repo):
2907 2909 t += _(' (empty repository)')
2908 2910 else:
2909 2911 t += _(' (no revision checked out)')
2910 2912 ui.write(_('parent: %d:%s %s\n') % (p.rev(), str(p), t))
2911 2913 if p.description():
2912 2914 ui.status(' ' + p.description().splitlines()[0].strip() + '\n')
2913 2915
2914 2916 branch = ctx.branch()
2915 2917 bheads = repo.branchheads(branch)
2916 2918 ui.status(_('branch: %s\n') % branch)
2917 2919
2918 2920 st = list(repo.status(unknown=True))[:7]
2919 2921 ms = merge_.mergestate(repo)
2920 2922 st.append([f for f in ms if f == 'u'])
2921 2923 labels = [_('%d modified'), _('%d added'), _('%d removed'),
2922 2924 _('%d deleted'), _('%d unknown'), _('%d ignored'),
2923 2925 _('%d unresolved')]
2924 2926 t = []
2925 2927 for s,l in zip(st, labels):
2926 2928 if s:
2927 2929 t.append(l % len(s))
2928 2930
2929 2931 t = ', '.join(t)
2930 2932
2931 2933 if len(parents) > 1:
2932 2934 t += _(' (merge)')
2933 2935 elif branch != parents[0].branch():
2934 2936 t += _(' (new branch)')
2935 2937 elif (not st[0] and not st[1] and not st[2]):
2936 2938 t += _(' (clean)')
2937 2939 elif pnode not in bheads:
2938 2940 t += _(' (new branch head)')
2939 2941
2940 2942 if 'clean' in t:
2941 2943 ui.status(_('commit: %s\n') % t.strip())
2942 2944 else:
2943 2945 ui.write(_('commit: %s\n') % t.strip())
2944 2946
2945 2947 # all ancestors of branch heads - all ancestors of parent = new csets
2946 2948 new = [0] * len(repo)
2947 2949 cl = repo.changelog
2948 2950 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
2949 2951 new[a] = 1
2950 2952 for a in cl.ancestors(*[p.rev() for p in parents]):
2951 2953 new[a] = 0
2952 2954 new = sum(new)
2953 2955
2954 2956 if new == 0:
2955 2957 ui.status(_('update: (current)\n'))
2956 2958 elif pnode not in bheads:
2957 2959 ui.write(_('update: %d new changesets (update)\n') % new)
2958 2960 else:
2959 2961 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
2960 2962 (new, len(bheads)))
2961 2963
2962 2964 if opts.get('remote'):
2963 2965 t = []
2964 2966 source, revs, checkout = hg.parseurl(ui.expandpath('default'),
2965 2967 opts.get('rev'))
2966 2968 other = hg.repository(cmdutil.remoteui(repo, {}), source)
2967 2969 ui.debug('comparing with %s\n' % url.hidepassword(source))
2968 2970 repo.ui.pushbuffer()
2969 2971 common, incoming, rheads = repo.findcommonincoming(other)
2970 2972 repo.ui.popbuffer()
2971 2973 if incoming:
2972 2974 t.append(_('1 or more incoming'))
2973 2975
2974 2976 dest, revs, checkout = hg.parseurl(
2975 2977 ui.expandpath('default-push', 'default'))
2976 2978 other = hg.repository(cmdutil.remoteui(repo, {}), dest)
2977 2979 ui.debug('comparing with %s\n' % url.hidepassword(dest))
2978 2980 repo.ui.pushbuffer()
2979 2981 o = repo.findoutgoing(other)
2980 2982 repo.ui.popbuffer()
2981 2983 o = repo.changelog.nodesbetween(o, revs)[0]
2982 2984 if o:
2983 2985 t.append(_('%d outgoing') % len(o))
2984 2986
2985 2987 if t:
2986 2988 ui.write(_('remote: %s\n') % (', '.join(t)))
2987 2989 else:
2988 2990 ui.status(_('remote: (synced)\n'))
2989 2991
2990 2992 def tag(ui, repo, name1, *names, **opts):
2991 2993 """add one or more tags for the current or given revision
2992 2994
2993 2995 Name a particular revision using <name>.
2994 2996
2995 2997 Tags are used to name particular revisions of the repository and are
2996 2998 very useful to compare different revisions, to go back to significant
2997 2999 earlier versions or to mark branch points as releases, etc.
2998 3000
2999 3001 If no revision is given, the parent of the working directory is
3000 3002 used, or tip if no revision is checked out.
3001 3003
3002 3004 To facilitate version control, distribution, and merging of tags,
3003 3005 they are stored as a file named ".hgtags" which is managed
3004 3006 similarly to other project files and can be hand-edited if
3005 3007 necessary. The file '.hg/localtags' is used for local tags (not
3006 3008 shared among repositories).
3007 3009
3008 3010 See 'hg help dates' for a list of formats valid for -d/--date.
3009 3011 """
3010 3012
3011 3013 rev_ = "."
3012 3014 names = (name1,) + names
3013 3015 if len(names) != len(set(names)):
3014 3016 raise util.Abort(_('tag names must be unique'))
3015 3017 for n in names:
3016 3018 if n in ['tip', '.', 'null']:
3017 3019 raise util.Abort(_('the name \'%s\' is reserved') % n)
3018 3020 if opts.get('rev') and opts.get('remove'):
3019 3021 raise util.Abort(_("--rev and --remove are incompatible"))
3020 3022 if opts.get('rev'):
3021 3023 rev_ = opts['rev']
3022 3024 message = opts.get('message')
3023 3025 if opts.get('remove'):
3024 3026 expectedtype = opts.get('local') and 'local' or 'global'
3025 3027 for n in names:
3026 3028 if not repo.tagtype(n):
3027 3029 raise util.Abort(_('tag \'%s\' does not exist') % n)
3028 3030 if repo.tagtype(n) != expectedtype:
3029 3031 if expectedtype == 'global':
3030 3032 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
3031 3033 else:
3032 3034 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
3033 3035 rev_ = nullid
3034 3036 if not message:
3035 3037 # we don't translate commit messages
3036 3038 message = 'Removed tag %s' % ', '.join(names)
3037 3039 elif not opts.get('force'):
3038 3040 for n in names:
3039 3041 if n in repo.tags():
3040 3042 raise util.Abort(_('tag \'%s\' already exists '
3041 3043 '(use -f to force)') % n)
3042 3044 if not rev_ and repo.dirstate.parents()[1] != nullid:
3043 3045 raise util.Abort(_('uncommitted merge - please provide a '
3044 3046 'specific revision'))
3045 3047 r = repo[rev_].node()
3046 3048
3047 3049 if not message:
3048 3050 # we don't translate commit messages
3049 3051 message = ('Added tag %s for changeset %s' %
3050 3052 (', '.join(names), short(r)))
3051 3053
3052 3054 date = opts.get('date')
3053 3055 if date:
3054 3056 date = util.parsedate(date)
3055 3057
3056 3058 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
3057 3059
3058 3060 def tags(ui, repo):
3059 3061 """list repository tags
3060 3062
3061 3063 This lists both regular and local tags. When the -v/--verbose
3062 3064 switch is used, a third column "local" is printed for local tags.
3063 3065 """
3064 3066
3065 3067 hexfunc = ui.debugflag and hex or short
3066 3068 tagtype = ""
3067 3069
3068 3070 for t, n in reversed(repo.tagslist()):
3069 3071 if ui.quiet:
3070 3072 ui.write("%s\n" % t)
3071 3073 continue
3072 3074
3073 3075 try:
3074 3076 hn = hexfunc(n)
3075 3077 r = "%5d:%s" % (repo.changelog.rev(n), hn)
3076 3078 except error.LookupError:
3077 3079 r = " ?:%s" % hn
3078 3080 else:
3079 3081 spaces = " " * (30 - encoding.colwidth(t))
3080 3082 if ui.verbose:
3081 3083 if repo.tagtype(t) == 'local':
3082 3084 tagtype = " local"
3083 3085 else:
3084 3086 tagtype = ""
3085 3087 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
3086 3088
3087 3089 def tip(ui, repo, **opts):
3088 3090 """show the tip revision
3089 3091
3090 3092 The tip revision (usually just called the tip) is the changeset
3091 3093 most recently added to the repository (and therefore the most
3092 3094 recently changed head).
3093 3095
3094 3096 If you have just made a commit, that commit will be the tip. If
3095 3097 you have just pulled changes from another repository, the tip of
3096 3098 that repository becomes the current tip. The "tip" tag is special
3097 3099 and cannot be renamed or assigned to a different changeset.
3098 3100 """
3099 3101 cmdutil.show_changeset(ui, repo, opts).show(repo[len(repo) - 1])
3100 3102
3101 3103 def unbundle(ui, repo, fname1, *fnames, **opts):
3102 3104 """apply one or more changegroup files
3103 3105
3104 3106 Apply one or more compressed changegroup files generated by the
3105 3107 bundle command.
3106 3108 """
3107 3109 fnames = (fname1,) + fnames
3108 3110
3109 3111 lock = repo.lock()
3110 3112 try:
3111 3113 for fname in fnames:
3112 3114 f = url.open(ui, fname)
3113 3115 gen = changegroup.readbundle(f, fname)
3114 3116 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
3115 3117 finally:
3116 3118 lock.release()
3117 3119
3118 3120 return postincoming(ui, repo, modheads, opts.get('update'), None)
3119 3121
3120 3122 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
3121 3123 """update working directory
3122 3124
3123 3125 Update the repository's working directory to the specified
3124 3126 revision, or the tip of the current branch if none is specified.
3125 3127 Use null as the revision to remove the working copy (like 'hg
3126 3128 clone -U').
3127 3129
3128 3130 When the working directory contains no uncommitted changes, it
3129 3131 will be replaced by the state of the requested revision from the
3130 3132 repository. When the requested revision is on a different branch,
3131 3133 the working directory will additionally be switched to that
3132 3134 branch.
3133 3135
3134 3136 When there are uncommitted changes, use option -C/--clean to
3135 3137 discard them, forcibly replacing the state of the working
3136 3138 directory with the requested revision. Alternately, use -c/--check
3137 3139 to abort.
3138 3140
3139 3141 When there are uncommitted changes and option -C/--clean is not
3140 3142 used, and the parent revision and requested revision are on the
3141 3143 same branch, and one of them is an ancestor of the other, then the
3142 3144 new working directory will contain the requested revision merged
3143 3145 with the uncommitted changes. Otherwise, the update will fail with
3144 3146 a suggestion to use 'merge' or 'update -C' instead.
3145 3147
3146 3148 If you want to update just one file to an older revision, use
3147 3149 revert.
3148 3150
3149 3151 See 'hg help dates' for a list of formats valid for -d/--date.
3150 3152 """
3151 3153 if rev and node:
3152 3154 raise util.Abort(_("please specify just one revision"))
3153 3155
3154 3156 if not rev:
3155 3157 rev = node
3156 3158
3157 3159 if check and clean:
3158 3160 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
3159 3161
3160 3162 if check:
3161 3163 # we could use dirty() but we can ignore merge and branch trivia
3162 3164 c = repo[None]
3163 3165 if c.modified() or c.added() or c.removed():
3164 3166 raise util.Abort(_("uncommitted local changes"))
3165 3167
3166 3168 if date:
3167 3169 if rev:
3168 3170 raise util.Abort(_("you can't specify a revision and a date"))
3169 3171 rev = cmdutil.finddate(ui, repo, date)
3170 3172
3171 3173 if clean or check:
3172 3174 return hg.clean(repo, rev)
3173 3175 else:
3174 3176 return hg.update(repo, rev)
3175 3177
3176 3178 def verify(ui, repo):
3177 3179 """verify the integrity of the repository
3178 3180
3179 3181 Verify the integrity of the current repository.
3180 3182
3181 3183 This will perform an extensive check of the repository's
3182 3184 integrity, validating the hashes and checksums of each entry in
3183 3185 the changelog, manifest, and tracked files, as well as the
3184 3186 integrity of their crosslinks and indices.
3185 3187 """
3186 3188 return hg.verify(repo)
3187 3189
3188 3190 def version_(ui):
3189 3191 """output version and copyright information"""
3190 3192 ui.write(_("Mercurial Distributed SCM (version %s)\n")
3191 3193 % util.version())
3192 3194 ui.status(_(
3193 3195 "\nCopyright (C) 2005-2009 Matt Mackall <mpm@selenic.com> and others\n"
3194 3196 "This is free software; see the source for copying conditions. "
3195 3197 "There is NO\nwarranty; "
3196 3198 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
3197 3199 ))
3198 3200
3199 3201 # Command options and aliases are listed here, alphabetically
3200 3202
3201 3203 globalopts = [
3202 3204 ('R', 'repository', '',
3203 3205 _('repository root directory or name of overlay bundle file')),
3204 3206 ('', 'cwd', '', _('change working directory')),
3205 3207 ('y', 'noninteractive', None,
3206 3208 _('do not prompt, assume \'yes\' for any required answers')),
3207 3209 ('q', 'quiet', None, _('suppress output')),
3208 3210 ('v', 'verbose', None, _('enable additional output')),
3209 3211 ('', 'config', [], _('set/override config option')),
3210 3212 ('', 'debug', None, _('enable debugging output')),
3211 3213 ('', 'debugger', None, _('start debugger')),
3212 3214 ('', 'encoding', encoding.encoding, _('set the charset encoding')),
3213 3215 ('', 'encodingmode', encoding.encodingmode,
3214 3216 _('set the charset encoding mode')),
3215 3217 ('', 'traceback', None, _('print traceback on exception')),
3216 3218 ('', 'time', None, _('time how long the command takes')),
3217 3219 ('', 'profile', None, _('print command execution profile')),
3218 3220 ('', 'version', None, _('output version information and exit')),
3219 3221 ('h', 'help', None, _('display help and exit')),
3220 3222 ]
3221 3223
3222 3224 dryrunopts = [('n', 'dry-run', None,
3223 3225 _('do not perform actions, just print output'))]
3224 3226
3225 3227 remoteopts = [
3226 3228 ('e', 'ssh', '', _('specify ssh command to use')),
3227 3229 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
3228 3230 ]
3229 3231
3230 3232 walkopts = [
3231 3233 ('I', 'include', [], _('include names matching the given patterns')),
3232 3234 ('X', 'exclude', [], _('exclude names matching the given patterns')),
3233 3235 ]
3234 3236
3235 3237 commitopts = [
3236 3238 ('m', 'message', '', _('use <text> as commit message')),
3237 3239 ('l', 'logfile', '', _('read commit message from <file>')),
3238 3240 ]
3239 3241
3240 3242 commitopts2 = [
3241 3243 ('d', 'date', '', _('record datecode as commit date')),
3242 3244 ('u', 'user', '', _('record the specified user as committer')),
3243 3245 ]
3244 3246
3245 3247 templateopts = [
3246 3248 ('', 'style', '', _('display using template map file')),
3247 3249 ('', 'template', '', _('display with template')),
3248 3250 ]
3249 3251
3250 3252 logopts = [
3251 3253 ('p', 'patch', None, _('show patch')),
3252 3254 ('g', 'git', None, _('use git extended diff format')),
3253 3255 ('l', 'limit', '', _('limit number of changes displayed')),
3254 3256 ('M', 'no-merges', None, _('do not show merges')),
3255 3257 ] + templateopts
3256 3258
3257 3259 diffopts = [
3258 3260 ('a', 'text', None, _('treat all files as text')),
3259 3261 ('g', 'git', None, _('use git extended diff format')),
3260 3262 ('', 'nodates', None, _("don't include dates in diff headers"))
3261 3263 ]
3262 3264
3263 3265 diffopts2 = [
3264 3266 ('p', 'show-function', None, _('show which function each change is in')),
3265 3267 ('w', 'ignore-all-space', None,
3266 3268 _('ignore white space when comparing lines')),
3267 3269 ('b', 'ignore-space-change', None,
3268 3270 _('ignore changes in the amount of white space')),
3269 3271 ('B', 'ignore-blank-lines', None,
3270 3272 _('ignore changes whose lines are all blank')),
3271 3273 ('U', 'unified', '', _('number of lines of context to show')),
3272 3274 ('', 'stat', None, _('output diffstat-style summary of changes')),
3273 3275 ]
3274 3276
3275 3277 similarityopts = [
3276 3278 ('s', 'similarity', '',
3277 3279 _('guess renamed files by similarity (0<=s<=100)'))
3278 3280 ]
3279 3281
3280 3282 table = {
3281 3283 "^add": (add, walkopts + dryrunopts, _('[OPTION]... [FILE]...')),
3282 3284 "addremove":
3283 3285 (addremove, similarityopts + walkopts + dryrunopts,
3284 3286 _('[OPTION]... [FILE]...')),
3285 3287 "^annotate|blame":
3286 3288 (annotate,
3287 3289 [('r', 'rev', '', _('annotate the specified revision')),
3288 3290 ('f', 'follow', None, _('follow file copies and renames')),
3289 3291 ('a', 'text', None, _('treat all files as text')),
3290 3292 ('u', 'user', None, _('list the author (long with -v)')),
3291 3293 ('d', 'date', None, _('list the date (short with -q)')),
3292 3294 ('n', 'number', None, _('list the revision number (default)')),
3293 3295 ('c', 'changeset', None, _('list the changeset')),
3294 3296 ('l', 'line-number', None,
3295 3297 _('show line number at the first appearance'))
3296 3298 ] + walkopts,
3297 3299 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
3298 3300 "archive":
3299 3301 (archive,
3300 3302 [('', 'no-decode', None, _('do not pass files through decoders')),
3301 3303 ('p', 'prefix', '', _('directory prefix for files in archive')),
3302 3304 ('r', 'rev', '', _('revision to distribute')),
3303 3305 ('t', 'type', '', _('type of distribution to create')),
3304 3306 ] + walkopts,
3305 3307 _('[OPTION]... DEST')),
3306 3308 "backout":
3307 3309 (backout,
3308 3310 [('', 'merge', None,
3309 3311 _('merge with old dirstate parent after backout')),
3310 3312 ('', 'parent', '', _('parent to choose when backing out merge')),
3311 3313 ('r', 'rev', '', _('revision to backout')),
3312 3314 ] + walkopts + commitopts + commitopts2,
3313 3315 _('[OPTION]... [-r] REV')),
3314 3316 "bisect":
3315 3317 (bisect,
3316 3318 [('r', 'reset', False, _('reset bisect state')),
3317 3319 ('g', 'good', False, _('mark changeset good')),
3318 3320 ('b', 'bad', False, _('mark changeset bad')),
3319 3321 ('s', 'skip', False, _('skip testing changeset')),
3320 3322 ('c', 'command', '', _('use command to check changeset state')),
3321 3323 ('U', 'noupdate', False, _('do not update to target'))],
3322 3324 _("[-gbsr] [-c CMD] [REV]")),
3323 3325 "branch":
3324 3326 (branch,
3325 3327 [('f', 'force', None,
3326 3328 _('set branch name even if it shadows an existing branch')),
3327 3329 ('C', 'clean', None, _('reset branch name to parent branch name'))],
3328 3330 _('[-fC] [NAME]')),
3329 3331 "branches":
3330 3332 (branches,
3331 3333 [('a', 'active', False,
3332 3334 _('show only branches that have unmerged heads')),
3333 3335 ('c', 'closed', False,
3334 3336 _('show normal and closed branches'))],
3335 3337 _('[-a]')),
3336 3338 "bundle":
3337 3339 (bundle,
3338 3340 [('f', 'force', None,
3339 3341 _('run even when remote repository is unrelated')),
3340 3342 ('r', 'rev', [],
3341 3343 _('a changeset up to which you would like to bundle')),
3342 3344 ('', 'base', [],
3343 3345 _('a base changeset to specify instead of a destination')),
3344 3346 ('a', 'all', None, _('bundle all changesets in the repository')),
3345 3347 ('t', 'type', 'bzip2', _('bundle compression type to use')),
3346 3348 ] + remoteopts,
3347 3349 _('[-f] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
3348 3350 "cat":
3349 3351 (cat,
3350 3352 [('o', 'output', '', _('print output to file with formatted name')),
3351 3353 ('r', 'rev', '', _('print the given revision')),
3352 3354 ('', 'decode', None, _('apply any matching decode filter')),
3353 3355 ] + walkopts,
3354 3356 _('[OPTION]... FILE...')),
3355 3357 "^clone":
3356 3358 (clone,
3357 3359 [('U', 'noupdate', None,
3358 3360 _('the clone will only contain a repository (no working copy)')),
3359 3361 ('r', 'rev', [],
3360 3362 _('a changeset you would like to have after cloning')),
3361 3363 ('', 'pull', None, _('use pull protocol to copy metadata')),
3362 3364 ('', 'uncompressed', None,
3363 3365 _('use uncompressed transfer (fast over LAN)')),
3364 3366 ] + remoteopts,
3365 3367 _('[OPTION]... SOURCE [DEST]')),
3366 3368 "^commit|ci":
3367 3369 (commit,
3368 3370 [('A', 'addremove', None,
3369 3371 _('mark new/missing files as added/removed before committing')),
3370 3372 ('', 'close-branch', None,
3371 3373 _('mark a branch as closed, hiding it from the branch list')),
3372 3374 ] + walkopts + commitopts + commitopts2,
3373 3375 _('[OPTION]... [FILE]...')),
3374 3376 "copy|cp":
3375 3377 (copy,
3376 3378 [('A', 'after', None, _('record a copy that has already occurred')),
3377 3379 ('f', 'force', None,
3378 3380 _('forcibly copy over an existing managed file')),
3379 3381 ] + walkopts + dryrunopts,
3380 3382 _('[OPTION]... [SOURCE]... DEST')),
3381 3383 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
3382 3384 "debugcheckstate": (debugcheckstate, [], ''),
3383 3385 "debugcommands": (debugcommands, [], _('[COMMAND]')),
3384 3386 "debugcomplete":
3385 3387 (debugcomplete,
3386 3388 [('o', 'options', None, _('show the command options'))],
3387 3389 _('[-o] CMD')),
3388 3390 "debugdate":
3389 3391 (debugdate,
3390 3392 [('e', 'extended', None, _('try extended date formats'))],
3391 3393 _('[-e] DATE [RANGE]')),
3392 3394 "debugdata": (debugdata, [], _('FILE REV')),
3393 3395 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
3394 3396 "debugindex": (debugindex, [], _('FILE')),
3395 3397 "debugindexdot": (debugindexdot, [], _('FILE')),
3396 3398 "debuginstall": (debuginstall, [], ''),
3397 3399 "debugrebuildstate":
3398 3400 (debugrebuildstate,
3399 3401 [('r', 'rev', '', _('revision to rebuild to'))],
3400 3402 _('[-r REV] [REV]')),
3401 3403 "debugrename":
3402 3404 (debugrename,
3403 3405 [('r', 'rev', '', _('revision to debug'))],
3404 3406 _('[-r REV] FILE')),
3405 3407 "debugsetparents":
3406 3408 (debugsetparents, [], _('REV1 [REV2]')),
3407 3409 "debugstate":
3408 3410 (debugstate,
3409 3411 [('', 'nodates', None, _('do not display the saved mtime'))],
3410 3412 _('[OPTION]...')),
3411 3413 "debugsub":
3412 3414 (debugsub,
3413 3415 [('r', 'rev', '', _('revision to check'))],
3414 3416 _('[-r REV] [REV]')),
3415 3417 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
3416 3418 "^diff":
3417 3419 (diff,
3418 3420 [('r', 'rev', [], _('revision')),
3419 3421 ('c', 'change', '', _('change made by revision'))
3420 3422 ] + diffopts + diffopts2 + walkopts,
3421 3423 _('[OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
3422 3424 "^export":
3423 3425 (export,
3424 3426 [('o', 'output', '', _('print output to file with formatted name')),
3425 3427 ('', 'switch-parent', None, _('diff against the second parent'))
3426 3428 ] + diffopts,
3427 3429 _('[OPTION]... [-o OUTFILESPEC] REV...')),
3428 3430 "^forget":
3429 3431 (forget,
3430 3432 [] + walkopts,
3431 3433 _('[OPTION]... FILE...')),
3432 3434 "grep":
3433 3435 (grep,
3434 3436 [('0', 'print0', None, _('end fields with NUL')),
3435 3437 ('', 'all', None, _('print all revisions that match')),
3436 3438 ('f', 'follow', None,
3437 3439 _('follow changeset history, or file history across copies and renames')),
3438 3440 ('i', 'ignore-case', None, _('ignore case when matching')),
3439 3441 ('l', 'files-with-matches', None,
3440 3442 _('print only filenames and revisions that match')),
3441 3443 ('n', 'line-number', None, _('print matching line numbers')),
3442 3444 ('r', 'rev', [], _('search in given revision range')),
3443 3445 ('u', 'user', None, _('list the author (long with -v)')),
3444 3446 ('d', 'date', None, _('list the date (short with -q)')),
3445 3447 ] + walkopts,
3446 3448 _('[OPTION]... PATTERN [FILE]...')),
3447 3449 "heads":
3448 3450 (heads,
3449 3451 [('r', 'rev', '', _('show only heads which are descendants of REV')),
3450 3452 ('a', 'active', False,
3451 3453 _('show only the active branch heads from open branches')),
3452 3454 ('c', 'closed', False,
3453 3455 _('show normal and closed branch heads')),
3454 3456 ] + templateopts,
3455 3457 _('[-r STARTREV] [REV]...')),
3456 3458 "help": (help_, [], _('[TOPIC]')),
3457 3459 "identify|id":
3458 3460 (identify,
3459 3461 [('r', 'rev', '', _('identify the specified revision')),
3460 3462 ('n', 'num', None, _('show local revision number')),
3461 3463 ('i', 'id', None, _('show global revision id')),
3462 3464 ('b', 'branch', None, _('show branch')),
3463 3465 ('t', 'tags', None, _('show tags'))],
3464 3466 _('[-nibt] [-r REV] [SOURCE]')),
3465 3467 "import|patch":
3466 3468 (import_,
3467 3469 [('p', 'strip', 1,
3468 3470 _('directory strip option for patch. This has the same '
3469 3471 'meaning as the corresponding patch option')),
3470 3472 ('b', 'base', '', _('base path')),
3471 3473 ('f', 'force', None,
3472 3474 _('skip check for outstanding uncommitted changes')),
3473 3475 ('', 'no-commit', None, _("don't commit, just update the working directory")),
3474 3476 ('', 'exact', None,
3475 3477 _('apply patch to the nodes from which it was generated')),
3476 3478 ('', 'import-branch', None,
3477 3479 _('use any branch information in patch (implied by --exact)'))] +
3478 3480 commitopts + commitopts2 + similarityopts,
3479 3481 _('[OPTION]... PATCH...')),
3480 3482 "incoming|in":
3481 3483 (incoming,
3482 3484 [('f', 'force', None,
3483 3485 _('run even when remote repository is unrelated')),
3484 3486 ('n', 'newest-first', None, _('show newest record first')),
3485 3487 ('', 'bundle', '', _('file to store the bundles into')),
3486 3488 ('r', 'rev', [],
3487 3489 _('a specific revision up to which you would like to pull')),
3488 3490 ] + logopts + remoteopts,
3489 3491 _('[-p] [-n] [-M] [-f] [-r REV]...'
3490 3492 ' [--bundle FILENAME] [SOURCE]')),
3491 3493 "^init":
3492 3494 (init,
3493 3495 remoteopts,
3494 3496 _('[-e CMD] [--remotecmd CMD] [DEST]')),
3495 3497 "locate":
3496 3498 (locate,
3497 3499 [('r', 'rev', '', _('search the repository as it stood at REV')),
3498 3500 ('0', 'print0', None,
3499 3501 _('end filenames with NUL, for use with xargs')),
3500 3502 ('f', 'fullpath', None,
3501 3503 _('print complete paths from the filesystem root')),
3502 3504 ] + walkopts,
3503 3505 _('[OPTION]... [PATTERN]...')),
3504 3506 "^log|history":
3505 3507 (log,
3506 3508 [('f', 'follow', None,
3507 3509 _('follow changeset history, or file history across copies and renames')),
3508 3510 ('', 'follow-first', None,
3509 3511 _('only follow the first parent of merge changesets')),
3510 3512 ('d', 'date', '', _('show revisions matching date spec')),
3511 3513 ('C', 'copies', None, _('show copied files')),
3512 3514 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
3513 3515 ('r', 'rev', [], _('show the specified revision or range')),
3514 3516 ('', 'removed', None, _('include revisions where files were removed')),
3515 3517 ('m', 'only-merges', None, _('show only merges')),
3516 3518 ('u', 'user', [], _('revisions committed by user')),
3517 3519 ('b', 'only-branch', [],
3518 3520 _('show only changesets within the given named branch')),
3519 3521 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
3520 3522 ] + logopts + walkopts,
3521 3523 _('[OPTION]... [FILE]')),
3522 3524 "manifest":
3523 3525 (manifest,
3524 3526 [('r', 'rev', '', _('revision to display'))],
3525 3527 _('[-r REV]')),
3526 3528 "^merge":
3527 3529 (merge,
3528 3530 [('f', 'force', None, _('force a merge with outstanding changes')),
3529 3531 ('r', 'rev', '', _('revision to merge')),
3530 3532 ('P', 'preview', None,
3531 3533 _('review revisions to merge (no merge is performed)'))],
3532 3534 _('[-f] [[-r] REV]')),
3533 3535 "outgoing|out":
3534 3536 (outgoing,
3535 3537 [('f', 'force', None,
3536 3538 _('run even when remote repository is unrelated')),
3537 3539 ('r', 'rev', [],
3538 3540 _('a specific revision up to which you would like to push')),
3539 3541 ('n', 'newest-first', None, _('show newest record first')),
3540 3542 ] + logopts + remoteopts,
3541 3543 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3542 3544 "parents":
3543 3545 (parents,
3544 3546 [('r', 'rev', '', _('show parents from the specified revision')),
3545 3547 ] + templateopts,
3546 3548 _('[-r REV] [FILE]')),
3547 3549 "paths": (paths, [], _('[NAME]')),
3548 3550 "^pull":
3549 3551 (pull,
3550 3552 [('u', 'update', None,
3551 3553 _('update to new tip if changesets were pulled')),
3552 3554 ('f', 'force', None,
3553 3555 _('run even when remote repository is unrelated')),
3554 3556 ('r', 'rev', [],
3555 3557 _('a specific revision up to which you would like to pull')),
3556 3558 ] + remoteopts,
3557 3559 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3558 3560 "^push":
3559 3561 (push,
3560 3562 [('f', 'force', None, _('force push')),
3561 3563 ('r', 'rev', [],
3562 3564 _('a specific revision up to which you would like to push')),
3563 3565 ] + remoteopts,
3564 3566 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3565 3567 "recover": (recover, []),
3566 3568 "^remove|rm":
3567 3569 (remove,
3568 3570 [('A', 'after', None, _('record delete for missing files')),
3569 3571 ('f', 'force', None,
3570 3572 _('remove (and delete) file even if added or modified')),
3571 3573 ] + walkopts,
3572 3574 _('[OPTION]... FILE...')),
3573 3575 "rename|mv":
3574 3576 (rename,
3575 3577 [('A', 'after', None, _('record a rename that has already occurred')),
3576 3578 ('f', 'force', None,
3577 3579 _('forcibly copy over an existing managed file')),
3578 3580 ] + walkopts + dryrunopts,
3579 3581 _('[OPTION]... SOURCE... DEST')),
3580 3582 "resolve":
3581 3583 (resolve,
3582 3584 [('a', 'all', None, _('remerge all unresolved files')),
3583 3585 ('l', 'list', None, _('list state of files needing merge')),
3584 3586 ('m', 'mark', None, _('mark files as resolved')),
3585 3587 ('u', 'unmark', None, _('unmark files as resolved'))]
3586 3588 + walkopts,
3587 3589 _('[OPTION]... [FILE]...')),
3588 3590 "revert":
3589 3591 (revert,
3590 3592 [('a', 'all', None, _('revert all changes when no arguments given')),
3591 3593 ('d', 'date', '', _('tipmost revision matching date')),
3592 3594 ('r', 'rev', '', _('revision to revert to')),
3593 3595 ('', 'no-backup', None, _('do not save backup copies of files')),
3594 3596 ] + walkopts + dryrunopts,
3595 3597 _('[OPTION]... [-r REV] [NAME]...')),
3596 3598 "rollback": (rollback, []),
3597 3599 "root": (root, []),
3598 3600 "^serve":
3599 3601 (serve,
3600 3602 [('A', 'accesslog', '', _('name of access log file to write to')),
3601 3603 ('d', 'daemon', None, _('run server in background')),
3602 3604 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3603 3605 ('E', 'errorlog', '', _('name of error log file to write to')),
3604 3606 ('p', 'port', 0, _('port to listen on (default: 8000)')),
3605 3607 ('a', 'address', '', _('address to listen on (default: all interfaces)')),
3606 3608 ('', 'prefix', '', _('prefix path to serve from (default: server root)')),
3607 3609 ('n', 'name', '',
3608 3610 _('name to show in web pages (default: working directory)')),
3609 3611 ('', 'webdir-conf', '', _('name of the webdir config file'
3610 3612 ' (serve more than one repository)')),
3611 3613 ('', 'pid-file', '', _('name of file to write process ID to')),
3612 3614 ('', 'stdio', None, _('for remote clients')),
3613 3615 ('t', 'templates', '', _('web templates to use')),
3614 3616 ('', 'style', '', _('template style to use')),
3615 3617 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3616 3618 ('', 'certificate', '', _('SSL certificate file'))],
3617 3619 _('[OPTION]...')),
3618 3620 "showconfig|debugconfig":
3619 3621 (showconfig,
3620 3622 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3621 3623 _('[-u] [NAME]...')),
3622 3624 "^summary|sum":
3623 3625 (summary,
3624 3626 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
3625 3627 "^status|st":
3626 3628 (status,
3627 3629 [('A', 'all', None, _('show status of all files')),
3628 3630 ('m', 'modified', None, _('show only modified files')),
3629 3631 ('a', 'added', None, _('show only added files')),
3630 3632 ('r', 'removed', None, _('show only removed files')),
3631 3633 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3632 3634 ('c', 'clean', None, _('show only files without changes')),
3633 3635 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3634 3636 ('i', 'ignored', None, _('show only ignored files')),
3635 3637 ('n', 'no-status', None, _('hide status prefix')),
3636 3638 ('C', 'copies', None, _('show source of copied files')),
3637 3639 ('0', 'print0', None,
3638 3640 _('end filenames with NUL, for use with xargs')),
3639 3641 ('', 'rev', [], _('show difference from revision')),
3640 3642 ] + walkopts,
3641 3643 _('[OPTION]... [FILE]...')),
3642 3644 "tag":
3643 3645 (tag,
3644 3646 [('f', 'force', None, _('replace existing tag')),
3645 3647 ('l', 'local', None, _('make the tag local')),
3646 3648 ('r', 'rev', '', _('revision to tag')),
3647 3649 ('', 'remove', None, _('remove a tag')),
3648 3650 # -l/--local is already there, commitopts cannot be used
3649 3651 ('m', 'message', '', _('use <text> as commit message')),
3650 3652 ] + commitopts2,
3651 3653 _('[-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
3652 3654 "tags": (tags, [], ''),
3653 3655 "tip":
3654 3656 (tip,
3655 3657 [('p', 'patch', None, _('show patch')),
3656 3658 ('g', 'git', None, _('use git extended diff format')),
3657 3659 ] + templateopts,
3658 3660 _('[-p]')),
3659 3661 "unbundle":
3660 3662 (unbundle,
3661 3663 [('u', 'update', None,
3662 3664 _('update to new tip if changesets were unbundled'))],
3663 3665 _('[-u] FILE...')),
3664 3666 "^update|up|checkout|co":
3665 3667 (update,
3666 3668 [('C', 'clean', None, _('overwrite locally modified files (no backup)')),
3667 3669 ('c', 'check', None, _('check for uncommitted changes')),
3668 3670 ('d', 'date', '', _('tipmost revision matching date')),
3669 3671 ('r', 'rev', '', _('revision'))],
3670 3672 _('[-C] [-d DATE] [[-r] REV]')),
3671 3673 "verify": (verify, []),
3672 3674 "version": (version_, []),
3673 3675 }
3674 3676
3675 3677 norepo = ("clone init version help debugcommands debugcomplete debugdata"
3676 3678 " debugindex debugindexdot debugdate debuginstall debugfsinfo")
3677 3679 optionalrepo = ("identify paths serve showconfig debugancestor")
@@ -1,1417 +1,1428 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, incorporated herein by reference.
8 8
9 9 from i18n import _
10 10 from node import hex, nullid, short
11 11 import base85, cmdutil, mdiff, util, diffhelpers, copies
12 12 import cStringIO, email.Parser, os, re
13 13 import sys, tempfile, zlib
14 14
15 15 gitre = re.compile('diff --git a/(.*) b/(.*)')
16 16
17 17 class PatchError(Exception):
18 18 pass
19 19
20 20 class NoHunks(PatchError):
21 21 pass
22 22
23 23 # helper functions
24 24
25 25 def copyfile(src, dst, basedir):
26 26 abssrc, absdst = [util.canonpath(basedir, basedir, x) for x in [src, dst]]
27 27 if os.path.exists(absdst):
28 28 raise util.Abort(_("cannot create %s: destination already exists") %
29 29 dst)
30 30
31 31 dstdir = os.path.dirname(absdst)
32 32 if dstdir and not os.path.isdir(dstdir):
33 33 try:
34 34 os.makedirs(dstdir)
35 35 except IOError:
36 36 raise util.Abort(
37 37 _("cannot create %s: unable to create destination directory")
38 38 % dst)
39 39
40 40 util.copyfile(abssrc, absdst)
41 41
42 42 # public functions
43 43
44 44 def extract(ui, fileobj):
45 45 '''extract patch from data read from fileobj.
46 46
47 47 patch can be a normal patch or contained in an email message.
48 48
49 49 return tuple (filename, message, user, date, node, p1, p2).
50 50 Any item in the returned tuple can be None. If filename is None,
51 51 fileobj did not contain a patch. Caller must unlink filename when done.'''
52 52
53 53 # attempt to detect the start of a patch
54 54 # (this heuristic is borrowed from quilt)
55 55 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
56 56 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
57 57 r'(---|\*\*\*)[ \t])', re.MULTILINE)
58 58
59 59 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
60 60 tmpfp = os.fdopen(fd, 'w')
61 61 try:
62 62 msg = email.Parser.Parser().parse(fileobj)
63 63
64 64 subject = msg['Subject']
65 65 user = msg['From']
66 66 if not subject and not user:
67 67 # Not an email, restore parsed headers if any
68 68 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
69 69
70 70 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
71 71 # should try to parse msg['Date']
72 72 date = None
73 73 nodeid = None
74 74 branch = None
75 75 parents = []
76 76
77 77 if subject:
78 78 if subject.startswith('[PATCH'):
79 79 pend = subject.find(']')
80 80 if pend >= 0:
81 81 subject = subject[pend+1:].lstrip()
82 82 subject = subject.replace('\n\t', ' ')
83 83 ui.debug('Subject: %s\n' % subject)
84 84 if user:
85 85 ui.debug('From: %s\n' % user)
86 86 diffs_seen = 0
87 87 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
88 88 message = ''
89 89 for part in msg.walk():
90 90 content_type = part.get_content_type()
91 91 ui.debug('Content-Type: %s\n' % content_type)
92 92 if content_type not in ok_types:
93 93 continue
94 94 payload = part.get_payload(decode=True)
95 95 m = diffre.search(payload)
96 96 if m:
97 97 hgpatch = False
98 98 ignoretext = False
99 99
100 100 ui.debug('found patch at byte %d\n' % m.start(0))
101 101 diffs_seen += 1
102 102 cfp = cStringIO.StringIO()
103 103 for line in payload[:m.start(0)].splitlines():
104 104 if line.startswith('# HG changeset patch'):
105 105 ui.debug('patch generated by hg export\n')
106 106 hgpatch = True
107 107 # drop earlier commit message content
108 108 cfp.seek(0)
109 109 cfp.truncate()
110 110 subject = None
111 111 elif hgpatch:
112 112 if line.startswith('# User '):
113 113 user = line[7:]
114 114 ui.debug('From: %s\n' % user)
115 115 elif line.startswith("# Date "):
116 116 date = line[7:]
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 line.startswith("# Parent "):
122 122 parents.append(line[10:])
123 123 elif line == '---' and gitsendmail:
124 124 ignoretext = True
125 125 if not line.startswith('# ') and not ignoretext:
126 126 cfp.write(line)
127 127 cfp.write('\n')
128 128 message = cfp.getvalue()
129 129 if tmpfp:
130 130 tmpfp.write(payload)
131 131 if not payload.endswith('\n'):
132 132 tmpfp.write('\n')
133 133 elif not diffs_seen and message and content_type == 'text/plain':
134 134 message += '\n' + payload
135 135 except:
136 136 tmpfp.close()
137 137 os.unlink(tmpname)
138 138 raise
139 139
140 140 if subject and not message.startswith(subject):
141 141 message = '%s\n%s' % (subject, message)
142 142 tmpfp.close()
143 143 if not diffs_seen:
144 144 os.unlink(tmpname)
145 145 return None, message, user, date, branch, None, None, None
146 146 p1 = parents and parents.pop(0) or None
147 147 p2 = parents and parents.pop(0) or None
148 148 return tmpname, message, user, date, branch, nodeid, p1, p2
149 149
150 150 GP_PATCH = 1 << 0 # we have to run patch
151 151 GP_FILTER = 1 << 1 # there's some copy/rename operation
152 152 GP_BINARY = 1 << 2 # there's a binary patch
153 153
154 154 class patchmeta(object):
155 155 """Patched file metadata
156 156
157 157 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
158 158 or COPY. 'path' is patched file path. 'oldpath' is set to the
159 159 origin file when 'op' is either COPY or RENAME, None otherwise. If
160 160 file mode is changed, 'mode' is a tuple (islink, isexec) where
161 161 'islink' is True if the file is a symlink and 'isexec' is True if
162 162 the file is executable. Otherwise, 'mode' is None.
163 163 """
164 164 def __init__(self, path):
165 165 self.path = path
166 166 self.oldpath = None
167 167 self.mode = None
168 168 self.op = 'MODIFY'
169 169 self.lineno = 0
170 170 self.binary = False
171 171
172 172 def setmode(self, mode):
173 173 islink = mode & 020000
174 174 isexec = mode & 0100
175 175 self.mode = (islink, isexec)
176 176
177 177 def readgitpatch(lr):
178 178 """extract git-style metadata about patches from <patchname>"""
179 179
180 180 # Filter patch for git information
181 181 gp = None
182 182 gitpatches = []
183 183 # Can have a git patch with only metadata, causing patch to complain
184 184 dopatch = 0
185 185
186 186 lineno = 0
187 187 for line in lr:
188 188 lineno += 1
189 189 line = line.rstrip(' \r\n')
190 190 if line.startswith('diff --git'):
191 191 m = gitre.match(line)
192 192 if m:
193 193 if gp:
194 194 gitpatches.append(gp)
195 195 dst = m.group(2)
196 196 gp = patchmeta(dst)
197 197 gp.lineno = lineno
198 198 elif gp:
199 199 if line.startswith('--- '):
200 200 if gp.op in ('COPY', 'RENAME'):
201 201 dopatch |= GP_FILTER
202 202 gitpatches.append(gp)
203 203 gp = None
204 204 dopatch |= GP_PATCH
205 205 continue
206 206 if line.startswith('rename from '):
207 207 gp.op = 'RENAME'
208 208 gp.oldpath = line[12:]
209 209 elif line.startswith('rename to '):
210 210 gp.path = line[10:]
211 211 elif line.startswith('copy from '):
212 212 gp.op = 'COPY'
213 213 gp.oldpath = line[10:]
214 214 elif line.startswith('copy to '):
215 215 gp.path = line[8:]
216 216 elif line.startswith('deleted file'):
217 217 gp.op = 'DELETE'
218 218 # is the deleted file a symlink?
219 219 gp.setmode(int(line[-6:], 8))
220 220 elif line.startswith('new file mode '):
221 221 gp.op = 'ADD'
222 222 gp.setmode(int(line[-6:], 8))
223 223 elif line.startswith('new mode '):
224 224 gp.setmode(int(line[-6:], 8))
225 225 elif line.startswith('GIT binary patch'):
226 226 dopatch |= GP_BINARY
227 227 gp.binary = True
228 228 if gp:
229 229 gitpatches.append(gp)
230 230
231 231 if not gitpatches:
232 232 dopatch = GP_PATCH
233 233
234 234 return (dopatch, gitpatches)
235 235
236 236 class linereader(object):
237 237 # simple class to allow pushing lines back into the input stream
238 238 def __init__(self, fp, textmode=False):
239 239 self.fp = fp
240 240 self.buf = []
241 241 self.textmode = textmode
242 242
243 243 def push(self, line):
244 244 if line is not None:
245 245 self.buf.append(line)
246 246
247 247 def readline(self):
248 248 if self.buf:
249 249 l = self.buf[0]
250 250 del self.buf[0]
251 251 return l
252 252 l = self.fp.readline()
253 253 if self.textmode and l.endswith('\r\n'):
254 254 l = l[:-2] + '\n'
255 255 return l
256 256
257 257 def __iter__(self):
258 258 while 1:
259 259 l = self.readline()
260 260 if not l:
261 261 break
262 262 yield l
263 263
264 264 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
265 265 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
266 266 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
267 267
268 268 class patchfile(object):
269 269 def __init__(self, ui, fname, opener, missing=False, eol=None):
270 270 self.fname = fname
271 271 self.eol = eol
272 272 self.opener = opener
273 273 self.ui = ui
274 274 self.lines = []
275 275 self.exists = False
276 276 self.missing = missing
277 277 if not missing:
278 278 try:
279 279 self.lines = self.readlines(fname)
280 280 self.exists = True
281 281 except IOError:
282 282 pass
283 283 else:
284 284 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
285 285
286 286 self.hash = {}
287 287 self.dirty = 0
288 288 self.offset = 0
289 289 self.rej = []
290 290 self.fileprinted = False
291 291 self.printfile(False)
292 292 self.hunks = 0
293 293
294 294 def readlines(self, fname):
295 295 if os.path.islink(fname):
296 296 return [os.readlink(fname)]
297 297 fp = self.opener(fname, 'r')
298 298 try:
299 299 return list(linereader(fp, self.eol is not None))
300 300 finally:
301 301 fp.close()
302 302
303 303 def writelines(self, fname, lines):
304 304 # Ensure supplied data ends in fname, being a regular file or
305 305 # a symlink. updatedir() will -too magically- take care of
306 306 # setting it to the proper type afterwards.
307 307 islink = os.path.islink(fname)
308 308 if islink:
309 309 fp = cStringIO.StringIO()
310 310 else:
311 311 fp = self.opener(fname, 'w')
312 312 try:
313 313 if self.eol and self.eol != '\n':
314 314 for l in lines:
315 315 if l and l[-1] == '\n':
316 316 l = l[:-1] + self.eol
317 317 fp.write(l)
318 318 else:
319 319 fp.writelines(lines)
320 320 if islink:
321 321 self.opener.symlink(fp.getvalue(), fname)
322 322 finally:
323 323 fp.close()
324 324
325 325 def unlink(self, fname):
326 326 os.unlink(fname)
327 327
328 328 def printfile(self, warn):
329 329 if self.fileprinted:
330 330 return
331 331 if warn or self.ui.verbose:
332 332 self.fileprinted = True
333 333 s = _("patching file %s\n") % self.fname
334 334 if warn:
335 335 self.ui.warn(s)
336 336 else:
337 337 self.ui.note(s)
338 338
339 339
340 340 def findlines(self, l, linenum):
341 341 # looks through the hash and finds candidate lines. The
342 342 # result is a list of line numbers sorted based on distance
343 343 # from linenum
344 344
345 345 try:
346 346 cand = self.hash[l]
347 347 except:
348 348 return []
349 349
350 350 if len(cand) > 1:
351 351 # resort our list of potentials forward then back.
352 352 cand.sort(key=lambda x: abs(x - linenum))
353 353 return cand
354 354
355 355 def hashlines(self):
356 356 self.hash = {}
357 357 for x, s in enumerate(self.lines):
358 358 self.hash.setdefault(s, []).append(x)
359 359
360 360 def write_rej(self):
361 361 # our rejects are a little different from patch(1). This always
362 362 # creates rejects in the same form as the original patch. A file
363 363 # header is inserted so that you can run the reject through patch again
364 364 # without having to type the filename.
365 365
366 366 if not self.rej:
367 367 return
368 368
369 369 fname = self.fname + ".rej"
370 370 self.ui.warn(
371 371 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
372 372 (len(self.rej), self.hunks, fname))
373 373
374 374 def rejlines():
375 375 base = os.path.basename(self.fname)
376 376 yield "--- %s\n+++ %s\n" % (base, base)
377 377 for x in self.rej:
378 378 for l in x.hunk:
379 379 yield l
380 380 if l[-1] != '\n':
381 381 yield "\n\ No newline at end of file\n"
382 382
383 383 self.writelines(fname, rejlines())
384 384
385 385 def write(self, dest=None):
386 386 if not self.dirty:
387 387 return
388 388 if not dest:
389 389 dest = self.fname
390 390 self.writelines(dest, self.lines)
391 391
392 392 def close(self):
393 393 self.write()
394 394 self.write_rej()
395 395
396 396 def apply(self, h):
397 397 if not h.complete():
398 398 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
399 399 (h.number, h.desc, len(h.a), h.lena, len(h.b),
400 400 h.lenb))
401 401
402 402 self.hunks += 1
403 403
404 404 if self.missing:
405 405 self.rej.append(h)
406 406 return -1
407 407
408 408 if self.exists and h.createfile():
409 409 self.ui.warn(_("file %s already exists\n") % self.fname)
410 410 self.rej.append(h)
411 411 return -1
412 412
413 413 if isinstance(h, binhunk):
414 414 if h.rmfile():
415 415 self.unlink(self.fname)
416 416 else:
417 417 self.lines[:] = h.new()
418 418 self.offset += len(h.new())
419 419 self.dirty = 1
420 420 return 0
421 421
422 422 # fast case first, no offsets, no fuzz
423 423 old = h.old()
424 424 # patch starts counting at 1 unless we are adding the file
425 425 if h.starta == 0:
426 426 start = 0
427 427 else:
428 428 start = h.starta + self.offset - 1
429 429 orig_start = start
430 430 if diffhelpers.testhunk(old, self.lines, start) == 0:
431 431 if h.rmfile():
432 432 self.unlink(self.fname)
433 433 else:
434 434 self.lines[start : start + h.lena] = h.new()
435 435 self.offset += h.lenb - h.lena
436 436 self.dirty = 1
437 437 return 0
438 438
439 439 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
440 440 self.hashlines()
441 441 if h.hunk[-1][0] != ' ':
442 442 # if the hunk tried to put something at the bottom of the file
443 443 # override the start line and use eof here
444 444 search_start = len(self.lines)
445 445 else:
446 446 search_start = orig_start
447 447
448 448 for fuzzlen in xrange(3):
449 449 for toponly in [ True, False ]:
450 450 old = h.old(fuzzlen, toponly)
451 451
452 452 cand = self.findlines(old[0][1:], search_start)
453 453 for l in cand:
454 454 if diffhelpers.testhunk(old, self.lines, l) == 0:
455 455 newlines = h.new(fuzzlen, toponly)
456 456 self.lines[l : l + len(old)] = newlines
457 457 self.offset += len(newlines) - len(old)
458 458 self.dirty = 1
459 459 if fuzzlen:
460 460 fuzzstr = "with fuzz %d " % fuzzlen
461 461 f = self.ui.warn
462 462 self.printfile(True)
463 463 else:
464 464 fuzzstr = ""
465 465 f = self.ui.note
466 466 offset = l - orig_start - fuzzlen
467 467 if offset == 1:
468 468 msg = _("Hunk #%d succeeded at %d %s"
469 469 "(offset %d line).\n")
470 470 else:
471 471 msg = _("Hunk #%d succeeded at %d %s"
472 472 "(offset %d lines).\n")
473 473 f(msg % (h.number, l+1, fuzzstr, offset))
474 474 return fuzzlen
475 475 self.printfile(True)
476 476 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
477 477 self.rej.append(h)
478 478 return -1
479 479
480 480 class hunk(object):
481 481 def __init__(self, desc, num, lr, context, create=False, remove=False):
482 482 self.number = num
483 483 self.desc = desc
484 484 self.hunk = [ desc ]
485 485 self.a = []
486 486 self.b = []
487 487 if context:
488 488 self.read_context_hunk(lr)
489 489 else:
490 490 self.read_unified_hunk(lr)
491 491 self.create = create
492 492 self.remove = remove and not create
493 493
494 494 def read_unified_hunk(self, lr):
495 495 m = unidesc.match(self.desc)
496 496 if not m:
497 497 raise PatchError(_("bad hunk #%d") % self.number)
498 498 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
499 499 if self.lena is None:
500 500 self.lena = 1
501 501 else:
502 502 self.lena = int(self.lena)
503 503 if self.lenb is None:
504 504 self.lenb = 1
505 505 else:
506 506 self.lenb = int(self.lenb)
507 507 self.starta = int(self.starta)
508 508 self.startb = int(self.startb)
509 509 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
510 510 # if we hit eof before finishing out the hunk, the last line will
511 511 # be zero length. Lets try to fix it up.
512 512 while len(self.hunk[-1]) == 0:
513 513 del self.hunk[-1]
514 514 del self.a[-1]
515 515 del self.b[-1]
516 516 self.lena -= 1
517 517 self.lenb -= 1
518 518
519 519 def read_context_hunk(self, lr):
520 520 self.desc = lr.readline()
521 521 m = contextdesc.match(self.desc)
522 522 if not m:
523 523 raise PatchError(_("bad hunk #%d") % self.number)
524 524 foo, self.starta, foo2, aend, foo3 = m.groups()
525 525 self.starta = int(self.starta)
526 526 if aend is None:
527 527 aend = self.starta
528 528 self.lena = int(aend) - self.starta
529 529 if self.starta:
530 530 self.lena += 1
531 531 for x in xrange(self.lena):
532 532 l = lr.readline()
533 533 if l.startswith('---'):
534 534 lr.push(l)
535 535 break
536 536 s = l[2:]
537 537 if l.startswith('- ') or l.startswith('! '):
538 538 u = '-' + s
539 539 elif l.startswith(' '):
540 540 u = ' ' + s
541 541 else:
542 542 raise PatchError(_("bad hunk #%d old text line %d") %
543 543 (self.number, x))
544 544 self.a.append(u)
545 545 self.hunk.append(u)
546 546
547 547 l = lr.readline()
548 548 if l.startswith('\ '):
549 549 s = self.a[-1][:-1]
550 550 self.a[-1] = s
551 551 self.hunk[-1] = s
552 552 l = lr.readline()
553 553 m = contextdesc.match(l)
554 554 if not m:
555 555 raise PatchError(_("bad hunk #%d") % self.number)
556 556 foo, self.startb, foo2, bend, foo3 = m.groups()
557 557 self.startb = int(self.startb)
558 558 if bend is None:
559 559 bend = self.startb
560 560 self.lenb = int(bend) - self.startb
561 561 if self.startb:
562 562 self.lenb += 1
563 563 hunki = 1
564 564 for x in xrange(self.lenb):
565 565 l = lr.readline()
566 566 if l.startswith('\ '):
567 567 s = self.b[-1][:-1]
568 568 self.b[-1] = s
569 569 self.hunk[hunki-1] = s
570 570 continue
571 571 if not l:
572 572 lr.push(l)
573 573 break
574 574 s = l[2:]
575 575 if l.startswith('+ ') or l.startswith('! '):
576 576 u = '+' + s
577 577 elif l.startswith(' '):
578 578 u = ' ' + s
579 579 elif len(self.b) == 0:
580 580 # this can happen when the hunk does not add any lines
581 581 lr.push(l)
582 582 break
583 583 else:
584 584 raise PatchError(_("bad hunk #%d old text line %d") %
585 585 (self.number, x))
586 586 self.b.append(s)
587 587 while True:
588 588 if hunki >= len(self.hunk):
589 589 h = ""
590 590 else:
591 591 h = self.hunk[hunki]
592 592 hunki += 1
593 593 if h == u:
594 594 break
595 595 elif h.startswith('-'):
596 596 continue
597 597 else:
598 598 self.hunk.insert(hunki-1, u)
599 599 break
600 600
601 601 if not self.a:
602 602 # this happens when lines were only added to the hunk
603 603 for x in self.hunk:
604 604 if x.startswith('-') or x.startswith(' '):
605 605 self.a.append(x)
606 606 if not self.b:
607 607 # this happens when lines were only deleted from the hunk
608 608 for x in self.hunk:
609 609 if x.startswith('+') or x.startswith(' '):
610 610 self.b.append(x[1:])
611 611 # @@ -start,len +start,len @@
612 612 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
613 613 self.startb, self.lenb)
614 614 self.hunk[0] = self.desc
615 615
616 616 def fix_newline(self):
617 617 diffhelpers.fix_newline(self.hunk, self.a, self.b)
618 618
619 619 def complete(self):
620 620 return len(self.a) == self.lena and len(self.b) == self.lenb
621 621
622 622 def createfile(self):
623 623 return self.starta == 0 and self.lena == 0 and self.create
624 624
625 625 def rmfile(self):
626 626 return self.startb == 0 and self.lenb == 0 and self.remove
627 627
628 628 def fuzzit(self, l, fuzz, toponly):
629 629 # this removes context lines from the top and bottom of list 'l'. It
630 630 # checks the hunk to make sure only context lines are removed, and then
631 631 # returns a new shortened list of lines.
632 632 fuzz = min(fuzz, len(l)-1)
633 633 if fuzz:
634 634 top = 0
635 635 bot = 0
636 636 hlen = len(self.hunk)
637 637 for x in xrange(hlen-1):
638 638 # the hunk starts with the @@ line, so use x+1
639 639 if self.hunk[x+1][0] == ' ':
640 640 top += 1
641 641 else:
642 642 break
643 643 if not toponly:
644 644 for x in xrange(hlen-1):
645 645 if self.hunk[hlen-bot-1][0] == ' ':
646 646 bot += 1
647 647 else:
648 648 break
649 649
650 650 # top and bot now count context in the hunk
651 651 # adjust them if either one is short
652 652 context = max(top, bot, 3)
653 653 if bot < context:
654 654 bot = max(0, fuzz - (context - bot))
655 655 else:
656 656 bot = min(fuzz, bot)
657 657 if top < context:
658 658 top = max(0, fuzz - (context - top))
659 659 else:
660 660 top = min(fuzz, top)
661 661
662 662 return l[top:len(l)-bot]
663 663 return l
664 664
665 665 def old(self, fuzz=0, toponly=False):
666 666 return self.fuzzit(self.a, fuzz, toponly)
667 667
668 668 def newctrl(self):
669 669 res = []
670 670 for x in self.hunk:
671 671 c = x[0]
672 672 if c == ' ' or c == '+':
673 673 res.append(x)
674 674 return res
675 675
676 676 def new(self, fuzz=0, toponly=False):
677 677 return self.fuzzit(self.b, fuzz, toponly)
678 678
679 679 class binhunk:
680 680 'A binary patch file. Only understands literals so far.'
681 681 def __init__(self, gitpatch):
682 682 self.gitpatch = gitpatch
683 683 self.text = None
684 684 self.hunk = ['GIT binary patch\n']
685 685
686 686 def createfile(self):
687 687 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
688 688
689 689 def rmfile(self):
690 690 return self.gitpatch.op == 'DELETE'
691 691
692 692 def complete(self):
693 693 return self.text is not None
694 694
695 695 def new(self):
696 696 return [self.text]
697 697
698 698 def extract(self, lr):
699 699 line = lr.readline()
700 700 self.hunk.append(line)
701 701 while line and not line.startswith('literal '):
702 702 line = lr.readline()
703 703 self.hunk.append(line)
704 704 if not line:
705 705 raise PatchError(_('could not extract binary patch'))
706 706 size = int(line[8:].rstrip())
707 707 dec = []
708 708 line = lr.readline()
709 709 self.hunk.append(line)
710 710 while len(line) > 1:
711 711 l = line[0]
712 712 if l <= 'Z' and l >= 'A':
713 713 l = ord(l) - ord('A') + 1
714 714 else:
715 715 l = ord(l) - ord('a') + 27
716 716 dec.append(base85.b85decode(line[1:-1])[:l])
717 717 line = lr.readline()
718 718 self.hunk.append(line)
719 719 text = zlib.decompress(''.join(dec))
720 720 if len(text) != size:
721 721 raise PatchError(_('binary patch is %d bytes, not %d') %
722 722 len(text), size)
723 723 self.text = text
724 724
725 725 def parsefilename(str):
726 726 # --- filename \t|space stuff
727 727 s = str[4:].rstrip('\r\n')
728 728 i = s.find('\t')
729 729 if i < 0:
730 730 i = s.find(' ')
731 731 if i < 0:
732 732 return s
733 733 return s[:i]
734 734
735 735 def selectfile(afile_orig, bfile_orig, hunk, strip):
736 736 def pathstrip(path, count=1):
737 737 pathlen = len(path)
738 738 i = 0
739 739 if count == 0:
740 740 return '', path.rstrip()
741 741 while count > 0:
742 742 i = path.find('/', i)
743 743 if i == -1:
744 744 raise PatchError(_("unable to strip away %d dirs from %s") %
745 745 (count, path))
746 746 i += 1
747 747 # consume '//' in the path
748 748 while i < pathlen - 1 and path[i] == '/':
749 749 i += 1
750 750 count -= 1
751 751 return path[:i].lstrip(), path[i:].rstrip()
752 752
753 753 nulla = afile_orig == "/dev/null"
754 754 nullb = bfile_orig == "/dev/null"
755 755 abase, afile = pathstrip(afile_orig, strip)
756 756 gooda = not nulla and util.lexists(afile)
757 757 bbase, bfile = pathstrip(bfile_orig, strip)
758 758 if afile == bfile:
759 759 goodb = gooda
760 760 else:
761 761 goodb = not nullb and os.path.exists(bfile)
762 762 createfunc = hunk.createfile
763 763 missing = not goodb and not gooda and not createfunc()
764 764
765 765 # some diff programs apparently produce create patches where the
766 766 # afile is not /dev/null, but rather the same name as the bfile
767 767 if missing and afile == bfile:
768 768 # this isn't very pretty
769 769 hunk.create = True
770 770 if createfunc():
771 771 missing = False
772 772 else:
773 773 hunk.create = False
774 774
775 775 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
776 776 # diff is between a file and its backup. In this case, the original
777 777 # file should be patched (see original mpatch code).
778 778 isbackup = (abase == bbase and bfile.startswith(afile))
779 779 fname = None
780 780 if not missing:
781 781 if gooda and goodb:
782 782 fname = isbackup and afile or bfile
783 783 elif gooda:
784 784 fname = afile
785 785
786 786 if not fname:
787 787 if not nullb:
788 788 fname = isbackup and afile or bfile
789 789 elif not nulla:
790 790 fname = afile
791 791 else:
792 792 raise PatchError(_("undefined source and destination files"))
793 793
794 794 return fname, missing
795 795
796 796 def scangitpatch(lr, firstline):
797 797 """
798 798 Git patches can emit:
799 799 - rename a to b
800 800 - change b
801 801 - copy a to c
802 802 - change c
803 803
804 804 We cannot apply this sequence as-is, the renamed 'a' could not be
805 805 found for it would have been renamed already. And we cannot copy
806 806 from 'b' instead because 'b' would have been changed already. So
807 807 we scan the git patch for copy and rename commands so we can
808 808 perform the copies ahead of time.
809 809 """
810 810 pos = 0
811 811 try:
812 812 pos = lr.fp.tell()
813 813 fp = lr.fp
814 814 except IOError:
815 815 fp = cStringIO.StringIO(lr.fp.read())
816 816 gitlr = linereader(fp, lr.textmode)
817 817 gitlr.push(firstline)
818 818 (dopatch, gitpatches) = readgitpatch(gitlr)
819 819 fp.seek(pos)
820 820 return dopatch, gitpatches
821 821
822 822 def iterhunks(ui, fp, sourcefile=None, textmode=False):
823 823 """Read a patch and yield the following events:
824 824 - ("file", afile, bfile, firsthunk): select a new target file.
825 825 - ("hunk", hunk): a new hunk is ready to be applied, follows a
826 826 "file" event.
827 827 - ("git", gitchanges): current diff is in git format, gitchanges
828 828 maps filenames to gitpatch records. Unique event.
829 829
830 830 If textmode is True, input line-endings are normalized to LF.
831 831 """
832 832 changed = {}
833 833 current_hunk = None
834 834 afile = ""
835 835 bfile = ""
836 836 state = None
837 837 hunknum = 0
838 838 emitfile = False
839 839 git = False
840 840
841 841 # our states
842 842 BFILE = 1
843 843 context = None
844 844 lr = linereader(fp, textmode)
845 845 dopatch = True
846 846 # gitworkdone is True if a git operation (copy, rename, ...) was
847 847 # performed already for the current file. Useful when the file
848 848 # section may have no hunk.
849 849 gitworkdone = False
850 850
851 851 while True:
852 852 newfile = False
853 853 x = lr.readline()
854 854 if not x:
855 855 break
856 856 if current_hunk:
857 857 if x.startswith('\ '):
858 858 current_hunk.fix_newline()
859 859 yield 'hunk', current_hunk
860 860 current_hunk = None
861 861 gitworkdone = False
862 862 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
863 863 ((context is not False) and x.startswith('***************')))):
864 864 try:
865 865 if context is None and x.startswith('***************'):
866 866 context = True
867 867 gpatch = changed.get(bfile)
868 868 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
869 869 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
870 870 current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
871 871 except PatchError, err:
872 872 ui.debug(err)
873 873 current_hunk = None
874 874 continue
875 875 hunknum += 1
876 876 if emitfile:
877 877 emitfile = False
878 878 yield 'file', (afile, bfile, current_hunk)
879 879 elif state == BFILE and x.startswith('GIT binary patch'):
880 880 current_hunk = binhunk(changed[bfile])
881 881 hunknum += 1
882 882 if emitfile:
883 883 emitfile = False
884 884 yield 'file', ('a/' + afile, 'b/' + bfile, current_hunk)
885 885 current_hunk.extract(lr)
886 886 elif x.startswith('diff --git'):
887 887 # check for git diff, scanning the whole patch file if needed
888 888 m = gitre.match(x)
889 889 if m:
890 890 afile, bfile = m.group(1, 2)
891 891 if not git:
892 892 git = True
893 893 dopatch, gitpatches = scangitpatch(lr, x)
894 894 yield 'git', gitpatches
895 895 for gp in gitpatches:
896 896 changed[gp.path] = gp
897 897 # else error?
898 898 # copy/rename + modify should modify target, not source
899 899 gp = changed.get(bfile)
900 900 if gp and gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD'):
901 901 afile = bfile
902 902 gitworkdone = True
903 903 newfile = True
904 904 elif x.startswith('---'):
905 905 # check for a unified diff
906 906 l2 = lr.readline()
907 907 if not l2.startswith('+++'):
908 908 lr.push(l2)
909 909 continue
910 910 newfile = True
911 911 context = False
912 912 afile = parsefilename(x)
913 913 bfile = parsefilename(l2)
914 914 elif x.startswith('***'):
915 915 # check for a context diff
916 916 l2 = lr.readline()
917 917 if not l2.startswith('---'):
918 918 lr.push(l2)
919 919 continue
920 920 l3 = lr.readline()
921 921 lr.push(l3)
922 922 if not l3.startswith("***************"):
923 923 lr.push(l2)
924 924 continue
925 925 newfile = True
926 926 context = True
927 927 afile = parsefilename(x)
928 928 bfile = parsefilename(l2)
929 929
930 930 if newfile:
931 931 emitfile = True
932 932 state = BFILE
933 933 hunknum = 0
934 934 if current_hunk:
935 935 if current_hunk.complete():
936 936 yield 'hunk', current_hunk
937 937 else:
938 938 raise PatchError(_("malformed patch %s %s") % (afile,
939 939 current_hunk.desc))
940 940
941 941 if hunknum == 0 and dopatch and not gitworkdone:
942 942 raise NoHunks
943 943
944 944 def applydiff(ui, fp, changed, strip=1, sourcefile=None, eol=None):
945 945 """
946 946 Reads a patch from fp and tries to apply it.
947 947
948 948 The dict 'changed' is filled in with all of the filenames changed
949 949 by the patch. Returns 0 for a clean patch, -1 if any rejects were
950 950 found and 1 if there was any fuzz.
951 951
952 952 If 'eol' is None, the patch content and patched file are read in
953 953 binary mode. Otherwise, line endings are ignored when patching then
954 954 normalized to 'eol' (usually '\n' or \r\n').
955 955 """
956 956 rejects = 0
957 957 err = 0
958 958 current_file = None
959 959 gitpatches = None
960 960 opener = util.opener(os.getcwd())
961 961 textmode = eol is not None
962 962
963 963 def closefile():
964 964 if not current_file:
965 965 return 0
966 966 current_file.close()
967 967 return len(current_file.rej)
968 968
969 969 for state, values in iterhunks(ui, fp, sourcefile, textmode):
970 970 if state == 'hunk':
971 971 if not current_file:
972 972 continue
973 973 current_hunk = values
974 974 ret = current_file.apply(current_hunk)
975 975 if ret >= 0:
976 976 changed.setdefault(current_file.fname, None)
977 977 if ret > 0:
978 978 err = 1
979 979 elif state == 'file':
980 980 rejects += closefile()
981 981 afile, bfile, first_hunk = values
982 982 try:
983 983 if sourcefile:
984 984 current_file = patchfile(ui, sourcefile, opener, eol=eol)
985 985 else:
986 986 current_file, missing = selectfile(afile, bfile, first_hunk,
987 987 strip)
988 988 current_file = patchfile(ui, current_file, opener, missing, eol)
989 989 except PatchError, err:
990 990 ui.warn(str(err) + '\n')
991 991 current_file, current_hunk = None, None
992 992 rejects += 1
993 993 continue
994 994 elif state == 'git':
995 995 gitpatches = values
996 996 cwd = os.getcwd()
997 997 for gp in gitpatches:
998 998 if gp.op in ('COPY', 'RENAME'):
999 999 copyfile(gp.oldpath, gp.path, cwd)
1000 1000 changed[gp.path] = gp
1001 1001 else:
1002 1002 raise util.Abort(_('unsupported parser state: %s') % state)
1003 1003
1004 1004 rejects += closefile()
1005 1005
1006 1006 if rejects:
1007 1007 return -1
1008 1008 return err
1009 1009
1010 1010 def diffopts(ui, opts={}, untrusted=False):
1011 1011 def get(key, name=None, getter=ui.configbool):
1012 1012 return (opts.get(key) or
1013 1013 getter('diff', name or key, None, untrusted=untrusted))
1014 1014 return mdiff.diffopts(
1015 1015 text=opts.get('text'),
1016 1016 git=get('git'),
1017 1017 nodates=get('nodates'),
1018 1018 showfunc=get('show_function', 'showfunc'),
1019 1019 ignorews=get('ignore_all_space', 'ignorews'),
1020 1020 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1021 1021 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1022 1022 context=get('unified', getter=ui.config))
1023 1023
1024 1024 def updatedir(ui, repo, patches, similarity=0):
1025 1025 '''Update dirstate after patch application according to metadata'''
1026 1026 if not patches:
1027 1027 return
1028 1028 copies = []
1029 1029 removes = set()
1030 1030 cfiles = patches.keys()
1031 1031 cwd = repo.getcwd()
1032 1032 if cwd:
1033 1033 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1034 1034 for f in patches:
1035 1035 gp = patches[f]
1036 1036 if not gp:
1037 1037 continue
1038 1038 if gp.op == 'RENAME':
1039 1039 copies.append((gp.oldpath, gp.path))
1040 1040 removes.add(gp.oldpath)
1041 1041 elif gp.op == 'COPY':
1042 1042 copies.append((gp.oldpath, gp.path))
1043 1043 elif gp.op == 'DELETE':
1044 1044 removes.add(gp.path)
1045 1045 for src, dst in copies:
1046 1046 repo.copy(src, dst)
1047 1047 if (not similarity) and removes:
1048 1048 repo.remove(sorted(removes), True)
1049 1049 for f in patches:
1050 1050 gp = patches[f]
1051 1051 if gp and gp.mode:
1052 1052 islink, isexec = gp.mode
1053 1053 dst = repo.wjoin(gp.path)
1054 1054 # patch won't create empty files
1055 1055 if gp.op == 'ADD' and not os.path.exists(dst):
1056 1056 flags = (isexec and 'x' or '') + (islink and 'l' or '')
1057 1057 repo.wwrite(gp.path, '', flags)
1058 1058 elif gp.op != 'DELETE':
1059 1059 util.set_flags(dst, islink, isexec)
1060 1060 cmdutil.addremove(repo, cfiles, similarity=similarity)
1061 1061 files = patches.keys()
1062 1062 files.extend([r for r in removes if r not in files])
1063 1063 return sorted(files)
1064 1064
1065 1065 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
1066 1066 """use <patcher> to apply <patchname> to the working directory.
1067 1067 returns whether patch was applied with fuzz factor."""
1068 1068
1069 1069 fuzz = False
1070 1070 if cwd:
1071 1071 args.append('-d %s' % util.shellquote(cwd))
1072 1072 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1073 1073 util.shellquote(patchname)))
1074 1074
1075 1075 for line in fp:
1076 1076 line = line.rstrip()
1077 1077 ui.note(line + '\n')
1078 1078 if line.startswith('patching file '):
1079 1079 pf = util.parse_patch_output(line)
1080 1080 printed_file = False
1081 1081 files.setdefault(pf, None)
1082 1082 elif line.find('with fuzz') >= 0:
1083 1083 fuzz = True
1084 1084 if not printed_file:
1085 1085 ui.warn(pf + '\n')
1086 1086 printed_file = True
1087 1087 ui.warn(line + '\n')
1088 1088 elif line.find('saving rejects to file') >= 0:
1089 1089 ui.warn(line + '\n')
1090 1090 elif line.find('FAILED') >= 0:
1091 1091 if not printed_file:
1092 1092 ui.warn(pf + '\n')
1093 1093 printed_file = True
1094 1094 ui.warn(line + '\n')
1095 1095 code = fp.close()
1096 1096 if code:
1097 1097 raise PatchError(_("patch command failed: %s") %
1098 1098 util.explain_exit(code)[0])
1099 1099 return fuzz
1100 1100
1101 1101 def internalpatch(patchobj, ui, strip, cwd, files={}, eolmode='strict'):
1102 1102 """use builtin patch to apply <patchobj> to the working directory.
1103 1103 returns whether patch was applied with fuzz factor."""
1104 1104
1105 1105 if eolmode is None:
1106 1106 eolmode = ui.config('patch', 'eol', 'strict')
1107 1107 try:
1108 1108 eol = {'strict': None, 'crlf': '\r\n', 'lf': '\n'}[eolmode.lower()]
1109 1109 except KeyError:
1110 1110 raise util.Abort(_('Unsupported line endings type: %s') % eolmode)
1111 1111
1112 1112 try:
1113 1113 fp = open(patchobj, 'rb')
1114 1114 except TypeError:
1115 1115 fp = patchobj
1116 1116 if cwd:
1117 1117 curdir = os.getcwd()
1118 1118 os.chdir(cwd)
1119 1119 try:
1120 1120 ret = applydiff(ui, fp, files, strip=strip, eol=eol)
1121 1121 finally:
1122 1122 if cwd:
1123 1123 os.chdir(curdir)
1124 1124 if ret < 0:
1125 1125 raise PatchError
1126 1126 return ret > 0
1127 1127
1128 1128 def patch(patchname, ui, strip=1, cwd=None, files={}, eolmode='strict'):
1129 1129 """Apply <patchname> to the working directory.
1130 1130
1131 1131 'eolmode' specifies how end of lines should be handled. It can be:
1132 1132 - 'strict': inputs are read in binary mode, EOLs are preserved
1133 1133 - 'crlf': EOLs are ignored when patching and reset to CRLF
1134 1134 - 'lf': EOLs are ignored when patching and reset to LF
1135 1135 - None: get it from user settings, default to 'strict'
1136 1136 'eolmode' is ignored when using an external patcher program.
1137 1137
1138 1138 Returns whether patch was applied with fuzz factor.
1139 1139 """
1140 1140 patcher = ui.config('ui', 'patch')
1141 1141 args = []
1142 1142 try:
1143 1143 if patcher:
1144 1144 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1145 1145 files)
1146 1146 else:
1147 1147 try:
1148 1148 return internalpatch(patchname, ui, strip, cwd, files, eolmode)
1149 1149 except NoHunks:
1150 1150 patcher = util.find_exe('gpatch') or util.find_exe('patch') or 'patch'
1151 1151 ui.debug('no valid hunks found; trying with %r instead\n' %
1152 1152 patcher)
1153 1153 if util.needbinarypatch():
1154 1154 args.append('--binary')
1155 1155 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1156 1156 files)
1157 1157 except PatchError, err:
1158 1158 s = str(err)
1159 1159 if s:
1160 1160 raise util.Abort(s)
1161 1161 else:
1162 1162 raise util.Abort(_('patch failed to apply'))
1163 1163
1164 1164 def b85diff(to, tn):
1165 1165 '''print base85-encoded binary diff'''
1166 1166 def gitindex(text):
1167 1167 if not text:
1168 1168 return '0' * 40
1169 1169 l = len(text)
1170 1170 s = util.sha1('blob %d\0' % l)
1171 1171 s.update(text)
1172 1172 return s.hexdigest()
1173 1173
1174 1174 def fmtline(line):
1175 1175 l = len(line)
1176 1176 if l <= 26:
1177 1177 l = chr(ord('A') + l - 1)
1178 1178 else:
1179 1179 l = chr(l - 26 + ord('a') - 1)
1180 1180 return '%c%s\n' % (l, base85.b85encode(line, True))
1181 1181
1182 1182 def chunk(text, csize=52):
1183 1183 l = len(text)
1184 1184 i = 0
1185 1185 while i < l:
1186 1186 yield text[i:i+csize]
1187 1187 i += csize
1188 1188
1189 1189 tohash = gitindex(to)
1190 1190 tnhash = gitindex(tn)
1191 1191 if tohash == tnhash:
1192 1192 return ""
1193 1193
1194 1194 # TODO: deltas
1195 1195 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1196 1196 (tohash, tnhash, len(tn))]
1197 1197 for l in chunk(zlib.compress(tn)):
1198 1198 ret.append(fmtline(l))
1199 1199 ret.append('\n')
1200 1200 return ''.join(ret)
1201 1201
1202 1202 def _addmodehdr(header, omode, nmode):
1203 1203 if omode != nmode:
1204 1204 header.append('old mode %s\n' % omode)
1205 1205 header.append('new mode %s\n' % nmode)
1206 1206
1207 1207 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None):
1208 1208 '''yields diff of changes to files between two nodes, or node and
1209 1209 working directory.
1210 1210
1211 1211 if node1 is None, use first dirstate parent instead.
1212 1212 if node2 is None, compare node1 with working directory.'''
1213 1213
1214 1214 if opts is None:
1215 1215 opts = mdiff.defaultopts
1216 1216
1217 1217 if not node1:
1218 1218 node1 = repo.dirstate.parents()[0]
1219 1219
1220 1220 def lrugetfilectx():
1221 1221 cache = {}
1222 1222 order = []
1223 1223 def getfilectx(f, ctx):
1224 1224 fctx = ctx.filectx(f, filelog=cache.get(f))
1225 1225 if f not in cache:
1226 1226 if len(cache) > 20:
1227 1227 del cache[order.pop(0)]
1228 1228 cache[f] = fctx._filelog
1229 1229 else:
1230 1230 order.remove(f)
1231 1231 order.append(f)
1232 1232 return fctx
1233 1233 return getfilectx
1234 1234 getfilectx = lrugetfilectx()
1235 1235
1236 1236 ctx1 = repo[node1]
1237 1237 ctx2 = repo[node2]
1238 1238
1239 1239 if not changes:
1240 1240 changes = repo.status(ctx1, ctx2, match=match)
1241 1241 modified, added, removed = changes[:3]
1242 1242
1243 1243 if not modified and not added and not removed:
1244 1244 return
1245 1245
1246 1246 date1 = util.datestr(ctx1.date())
1247 1247 man1 = ctx1.manifest()
1248 1248
1249 1249 if repo.ui.quiet:
1250 1250 r = None
1251 1251 else:
1252 1252 hexfunc = repo.ui.debugflag and hex or short
1253 1253 r = [hexfunc(node) for node in [node1, node2] if node]
1254 1254
1255 1255 if opts.git:
1256 1256 copy, diverge = copies.copies(repo, ctx1, ctx2, repo[nullid])
1257 1257 copy = copy.copy()
1258 1258 for k, v in copy.items():
1259 1259 copy[v] = k
1260 1260
1261 1261 gone = set()
1262 1262 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1263 1263
1264 1264 for f in sorted(modified + added + removed):
1265 1265 to = None
1266 1266 tn = None
1267 1267 dodiff = True
1268 1268 header = []
1269 1269 if f in man1:
1270 1270 to = getfilectx(f, ctx1).data()
1271 1271 if f not in removed:
1272 1272 tn = getfilectx(f, ctx2).data()
1273 1273 a, b = f, f
1274 1274 if opts.git:
1275 1275 if f in added:
1276 1276 mode = gitmode[ctx2.flags(f)]
1277 1277 if f in copy:
1278 1278 a = copy[f]
1279 1279 omode = gitmode[man1.flags(a)]
1280 1280 _addmodehdr(header, omode, mode)
1281 1281 if a in removed and a not in gone:
1282 1282 op = 'rename'
1283 1283 gone.add(a)
1284 1284 else:
1285 1285 op = 'copy'
1286 1286 header.append('%s from %s\n' % (op, a))
1287 1287 header.append('%s to %s\n' % (op, f))
1288 1288 to = getfilectx(a, ctx1).data()
1289 1289 else:
1290 1290 header.append('new file mode %s\n' % mode)
1291 1291 if util.binary(tn):
1292 1292 dodiff = 'binary'
1293 1293 elif f in removed:
1294 1294 # have we already reported a copy above?
1295 1295 if f in copy and copy[f] in added and copy[copy[f]] == f:
1296 1296 dodiff = False
1297 1297 else:
1298 1298 header.append('deleted file mode %s\n' %
1299 1299 gitmode[man1.flags(f)])
1300 1300 else:
1301 1301 omode = gitmode[man1.flags(f)]
1302 1302 nmode = gitmode[ctx2.flags(f)]
1303 1303 _addmodehdr(header, omode, nmode)
1304 1304 if util.binary(to) or util.binary(tn):
1305 1305 dodiff = 'binary'
1306 1306 r = None
1307 1307 header.insert(0, mdiff.diffline(r, a, b, opts))
1308 1308 if dodiff:
1309 1309 if dodiff == 'binary':
1310 1310 text = b85diff(to, tn)
1311 1311 else:
1312 1312 text = mdiff.unidiff(to, date1,
1313 1313 # ctx2 date may be dynamic
1314 1314 tn, util.datestr(ctx2.date()),
1315 1315 a, b, r, opts=opts)
1316 1316 if header and (text or len(header) > 1):
1317 1317 yield ''.join(header)
1318 1318 if text:
1319 1319 yield text
1320 1320
1321 1321 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1322 1322 opts=None):
1323 1323 '''export changesets as hg patches.'''
1324 1324
1325 1325 total = len(revs)
1326 1326 revwidth = max([len(str(rev)) for rev in revs])
1327 1327
1328 1328 def single(rev, seqno, fp):
1329 1329 ctx = repo[rev]
1330 1330 node = ctx.node()
1331 1331 parents = [p.node() for p in ctx.parents() if p]
1332 1332 branch = ctx.branch()
1333 1333 if switch_parent:
1334 1334 parents.reverse()
1335 1335 prev = (parents and parents[0]) or nullid
1336 1336
1337 1337 if not fp:
1338 1338 fp = cmdutil.make_file(repo, template, node, total=total,
1339 1339 seqno=seqno, revwidth=revwidth,
1340 1340 mode='ab')
1341 1341 if fp != sys.stdout and hasattr(fp, 'name'):
1342 1342 repo.ui.note("%s\n" % fp.name)
1343 1343
1344 1344 fp.write("# HG changeset patch\n")
1345 1345 fp.write("# User %s\n" % ctx.user())
1346 1346 fp.write("# Date %d %d\n" % ctx.date())
1347 1347 if branch and (branch != 'default'):
1348 1348 fp.write("# Branch %s\n" % branch)
1349 1349 fp.write("# Node ID %s\n" % hex(node))
1350 1350 fp.write("# Parent %s\n" % hex(prev))
1351 1351 if len(parents) > 1:
1352 1352 fp.write("# Parent %s\n" % hex(parents[1]))
1353 1353 fp.write(ctx.description().rstrip())
1354 1354 fp.write("\n\n")
1355 1355
1356 1356 for chunk in diff(repo, prev, node, opts=opts):
1357 1357 fp.write(chunk)
1358 1358
1359 1359 for seqno, rev in enumerate(revs):
1360 1360 single(rev, seqno+1, fp)
1361 1361
1362 1362 def diffstatdata(lines):
1363 1363 filename, adds, removes = None, 0, 0
1364 1364 for line in lines:
1365 1365 if line.startswith('diff'):
1366 1366 if filename:
1367 yield (filename, adds, removes)
1367 isbinary = adds == 0 and removes == 0
1368 yield (filename, adds, removes, isbinary)
1368 1369 # set numbers to 0 anyway when starting new file
1369 1370 adds, removes = 0, 0
1370 1371 if line.startswith('diff --git'):
1371 1372 filename = gitre.search(line).group(1)
1372 1373 else:
1373 1374 # format: "diff -r ... -r ... filename"
1374 1375 filename = line.split(None, 5)[-1]
1375 1376 elif line.startswith('+') and not line.startswith('+++'):
1376 1377 adds += 1
1377 1378 elif line.startswith('-') and not line.startswith('---'):
1378 1379 removes += 1
1379 1380 if filename:
1380 yield (filename, adds, removes)
1381 isbinary = adds == 0 and removes == 0
1382 yield (filename, adds, removes, isbinary)
1381 1383
1382 def diffstat(lines, width=80):
1384 def diffstat(lines, width=80, git=False):
1383 1385 output = []
1384 1386 stats = list(diffstatdata(lines))
1385 1387
1386 1388 maxtotal, maxname = 0, 0
1387 1389 totaladds, totalremoves = 0, 0
1388 for filename, adds, removes in stats:
1390 hasbinary = False
1391 for filename, adds, removes, isbinary in stats:
1389 1392 totaladds += adds
1390 1393 totalremoves += removes
1391 1394 maxname = max(maxname, len(filename))
1392 1395 maxtotal = max(maxtotal, adds+removes)
1396 if isbinary:
1397 hasbinary = True
1393 1398
1394 1399 countwidth = len(str(maxtotal))
1400 if hasbinary and countwidth < 3:
1401 countwidth = 3
1395 1402 graphwidth = width - countwidth - maxname - 6
1396 1403 if graphwidth < 10:
1397 1404 graphwidth = 10
1398 1405
1399 1406 def scale(i):
1400 1407 if maxtotal <= graphwidth:
1401 1408 return i
1402 1409 # If diffstat runs out of room it doesn't print anything,
1403 1410 # which isn't very useful, so always print at least one + or -
1404 1411 # if there were at least some changes.
1405 1412 return max(i * graphwidth // maxtotal, int(bool(i)))
1406 1413
1407 for filename, adds, removes in stats:
1414 for filename, adds, removes, isbinary in stats:
1415 if git and isbinary:
1416 count = 'Bin'
1417 else:
1418 count = adds + removes
1408 1419 pluses = '+' * scale(adds)
1409 1420 minuses = '-' * scale(removes)
1410 1421 output.append(' %-*s | %*s %s%s\n' % (maxname, filename, countwidth,
1411 adds+removes, pluses, minuses))
1422 count, pluses, minuses))
1412 1423
1413 1424 if stats:
1414 1425 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1415 1426 % (len(stats), totaladds, totalremoves))
1416 1427
1417 1428 return ''.join(output)
@@ -1,31 +1,34 b''
1 1 #!/bin/sh
2 2
3 3 hg init repo
4 4 cd repo
5 5 i=0; while (( $i < 213 )); do echo a >> a; i=$(($i + 1)); done
6 6 hg add a
7 7
8 8 echo '% wide diffstat'
9 9 hg diff --stat
10 10
11 11 echo '% diffstat width'
12 12 COLUMNS=24 hg diff --config ui.interactive=true --stat
13 13
14 14 hg ci -m adda
15 15
16 16 cat >> a <<EOF
17 17 a
18 18 a
19 19 a
20 20 EOF
21 21
22 22 echo '% narrow diffstat'
23 23 hg diff --stat
24 24
25 25 hg ci -m appenda
26 26
27 27 printf '%b' '\x00' > b
28 28 hg add b
29 29
30 30 echo '% binary diffstat'
31 31 hg diff --stat
32
33 echo '% binary git diffstat'
34 hg diff --stat --git
@@ -1,12 +1,15 b''
1 1 % wide diffstat
2 2 a | 213 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 3 1 files changed, 213 insertions(+), 0 deletions(-)
4 4 % diffstat width
5 5 a | 213 ++++++++++++++
6 6 1 files changed, 213 insertions(+), 0 deletions(-)
7 7 % narrow diffstat
8 8 a | 3 +++
9 9 1 files changed, 3 insertions(+), 0 deletions(-)
10 10 % binary diffstat
11 b | 0
11 b | 0
12 12 1 files changed, 0 insertions(+), 0 deletions(-)
13 % binary git diffstat
14 b | Bin
15 1 files changed, 0 insertions(+), 0 deletions(-)
General Comments 0
You need to be logged in to leave comments. Login now