##// END OF EJS Templates
Make revrange return a list of ints so that callers don't have to convert
Matt Mackall -
r3526:68341c06 default
parent child Browse files
Show More
@@ -1,2157 +1,2157 b''
1 1 # queue.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
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 '''patch management and development
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 print name of top applied patch qtop
26 26
27 27 add known patch to applied stack qpush
28 28 remove patch from applied stack qpop
29 29 refresh contents of top applied patch qrefresh
30 30 '''
31 31
32 32 from mercurial.demandload import *
33 33 from mercurial.i18n import gettext as _
34 34 from mercurial import commands
35 35 demandload(globals(), "os sys re struct traceback errno bz2")
36 36 demandload(globals(), "mercurial:cmdutil,hg,patch,revlog,ui,util")
37 37
38 38 commands.norepo += " qclone qversion"
39 39
40 40 class statusentry:
41 41 def __init__(self, rev, name=None):
42 42 if not name:
43 43 fields = rev.split(':', 1)
44 44 if len(fields) == 2:
45 45 self.rev, self.name = fields
46 46 else:
47 47 self.rev, self.name = None, None
48 48 else:
49 49 self.rev, self.name = rev, name
50 50
51 51 def __str__(self):
52 52 return self.rev + ':' + self.name
53 53
54 54 class queue:
55 55 def __init__(self, ui, path, patchdir=None):
56 56 self.basepath = path
57 57 self.path = patchdir or os.path.join(path, "patches")
58 58 self.opener = util.opener(self.path)
59 59 self.ui = ui
60 60 self.applied = []
61 61 self.full_series = []
62 62 self.applied_dirty = 0
63 63 self.series_dirty = 0
64 64 self.series_path = "series"
65 65 self.status_path = "status"
66 66 self.guards_path = "guards"
67 67 self.active_guards = None
68 68 self.guards_dirty = False
69 69 self._diffopts = None
70 70
71 71 if os.path.exists(self.join(self.series_path)):
72 72 self.full_series = self.opener(self.series_path).read().splitlines()
73 73 self.parse_series()
74 74
75 75 if os.path.exists(self.join(self.status_path)):
76 76 lines = self.opener(self.status_path).read().splitlines()
77 77 self.applied = [statusentry(l) for l in lines]
78 78
79 79 def diffopts(self):
80 80 if self._diffopts is None:
81 81 self._diffopts = patch.diffopts(self.ui)
82 82 return self._diffopts
83 83
84 84 def join(self, *p):
85 85 return os.path.join(self.path, *p)
86 86
87 87 def find_series(self, patch):
88 88 pre = re.compile("(\s*)([^#]+)")
89 89 index = 0
90 90 for l in self.full_series:
91 91 m = pre.match(l)
92 92 if m:
93 93 s = m.group(2)
94 94 s = s.rstrip()
95 95 if s == patch:
96 96 return index
97 97 index += 1
98 98 return None
99 99
100 100 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
101 101
102 102 def parse_series(self):
103 103 self.series = []
104 104 self.series_guards = []
105 105 for l in self.full_series:
106 106 h = l.find('#')
107 107 if h == -1:
108 108 patch = l
109 109 comment = ''
110 110 elif h == 0:
111 111 continue
112 112 else:
113 113 patch = l[:h]
114 114 comment = l[h:]
115 115 patch = patch.strip()
116 116 if patch:
117 117 if patch in self.series:
118 118 raise util.Abort(_('%s appears more than once in %s') %
119 119 (patch, self.join(self.series_path)))
120 120 self.series.append(patch)
121 121 self.series_guards.append(self.guard_re.findall(comment))
122 122
123 123 def check_guard(self, guard):
124 124 bad_chars = '# \t\r\n\f'
125 125 first = guard[0]
126 126 for c in '-+':
127 127 if first == c:
128 128 return (_('guard %r starts with invalid character: %r') %
129 129 (guard, c))
130 130 for c in bad_chars:
131 131 if c in guard:
132 132 return _('invalid character in guard %r: %r') % (guard, c)
133 133
134 134 def set_active(self, guards):
135 135 for guard in guards:
136 136 bad = self.check_guard(guard)
137 137 if bad:
138 138 raise util.Abort(bad)
139 139 guards = dict.fromkeys(guards).keys()
140 140 guards.sort()
141 141 self.ui.debug('active guards: %s\n' % ' '.join(guards))
142 142 self.active_guards = guards
143 143 self.guards_dirty = True
144 144
145 145 def active(self):
146 146 if self.active_guards is None:
147 147 self.active_guards = []
148 148 try:
149 149 guards = self.opener(self.guards_path).read().split()
150 150 except IOError, err:
151 151 if err.errno != errno.ENOENT: raise
152 152 guards = []
153 153 for i, guard in enumerate(guards):
154 154 bad = self.check_guard(guard)
155 155 if bad:
156 156 self.ui.warn('%s:%d: %s\n' %
157 157 (self.join(self.guards_path), i + 1, bad))
158 158 else:
159 159 self.active_guards.append(guard)
160 160 return self.active_guards
161 161
162 162 def set_guards(self, idx, guards):
163 163 for g in guards:
164 164 if len(g) < 2:
165 165 raise util.Abort(_('guard %r too short') % g)
166 166 if g[0] not in '-+':
167 167 raise util.Abort(_('guard %r starts with invalid char') % g)
168 168 bad = self.check_guard(g[1:])
169 169 if bad:
170 170 raise util.Abort(bad)
171 171 drop = self.guard_re.sub('', self.full_series[idx])
172 172 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
173 173 self.parse_series()
174 174 self.series_dirty = True
175 175
176 176 def pushable(self, idx):
177 177 if isinstance(idx, str):
178 178 idx = self.series.index(idx)
179 179 patchguards = self.series_guards[idx]
180 180 if not patchguards:
181 181 return True, None
182 182 default = False
183 183 guards = self.active()
184 184 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
185 185 if exactneg:
186 186 return False, exactneg[0]
187 187 pos = [g for g in patchguards if g[0] == '+']
188 188 exactpos = [g for g in pos if g[1:] in guards]
189 189 if pos:
190 190 if exactpos:
191 191 return True, exactpos[0]
192 192 return False, pos
193 193 return True, ''
194 194
195 195 def explain_pushable(self, idx, all_patches=False):
196 196 write = all_patches and self.ui.write or self.ui.warn
197 197 if all_patches or self.ui.verbose:
198 198 if isinstance(idx, str):
199 199 idx = self.series.index(idx)
200 200 pushable, why = self.pushable(idx)
201 201 if all_patches and pushable:
202 202 if why is None:
203 203 write(_('allowing %s - no guards in effect\n') %
204 204 self.series[idx])
205 205 else:
206 206 if not why:
207 207 write(_('allowing %s - no matching negative guards\n') %
208 208 self.series[idx])
209 209 else:
210 210 write(_('allowing %s - guarded by %r\n') %
211 211 (self.series[idx], why))
212 212 if not pushable:
213 213 if why:
214 214 write(_('skipping %s - guarded by %r\n') %
215 215 (self.series[idx], ' '.join(why)))
216 216 else:
217 217 write(_('skipping %s - no matching guards\n') %
218 218 self.series[idx])
219 219
220 220 def save_dirty(self):
221 221 def write_list(items, path):
222 222 fp = self.opener(path, 'w')
223 223 for i in items:
224 224 print >> fp, i
225 225 fp.close()
226 226 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
227 227 if self.series_dirty: write_list(self.full_series, self.series_path)
228 228 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
229 229
230 230 def readheaders(self, patch):
231 231 def eatdiff(lines):
232 232 while lines:
233 233 l = lines[-1]
234 234 if (l.startswith("diff -") or
235 235 l.startswith("Index:") or
236 236 l.startswith("===========")):
237 237 del lines[-1]
238 238 else:
239 239 break
240 240 def eatempty(lines):
241 241 while lines:
242 242 l = lines[-1]
243 243 if re.match('\s*$', l):
244 244 del lines[-1]
245 245 else:
246 246 break
247 247
248 248 pf = self.join(patch)
249 249 message = []
250 250 comments = []
251 251 user = None
252 252 date = None
253 253 format = None
254 254 subject = None
255 255 diffstart = 0
256 256
257 257 for line in file(pf):
258 258 line = line.rstrip()
259 259 if line.startswith('diff --git'):
260 260 diffstart = 2
261 261 break
262 262 if diffstart:
263 263 if line.startswith('+++ '):
264 264 diffstart = 2
265 265 break
266 266 if line.startswith("--- "):
267 267 diffstart = 1
268 268 continue
269 269 elif format == "hgpatch":
270 270 # parse values when importing the result of an hg export
271 271 if line.startswith("# User "):
272 272 user = line[7:]
273 273 elif line.startswith("# Date "):
274 274 date = line[7:]
275 275 elif not line.startswith("# ") and line:
276 276 message.append(line)
277 277 format = None
278 278 elif line == '# HG changeset patch':
279 279 format = "hgpatch"
280 280 elif (format != "tagdone" and (line.startswith("Subject: ") or
281 281 line.startswith("subject: "))):
282 282 subject = line[9:]
283 283 format = "tag"
284 284 elif (format != "tagdone" and (line.startswith("From: ") or
285 285 line.startswith("from: "))):
286 286 user = line[6:]
287 287 format = "tag"
288 288 elif format == "tag" and line == "":
289 289 # when looking for tags (subject: from: etc) they
290 290 # end once you find a blank line in the source
291 291 format = "tagdone"
292 292 elif message or line:
293 293 message.append(line)
294 294 comments.append(line)
295 295
296 296 eatdiff(message)
297 297 eatdiff(comments)
298 298 eatempty(message)
299 299 eatempty(comments)
300 300
301 301 # make sure message isn't empty
302 302 if format and format.startswith("tag") and subject:
303 303 message.insert(0, "")
304 304 message.insert(0, subject)
305 305 return (message, comments, user, date, diffstart > 1)
306 306
307 307 def printdiff(self, repo, node1, node2=None, files=None,
308 308 fp=None, changes=None, opts={}):
309 309 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
310 310
311 311 patch.diff(repo, node1, node2, fns, match=matchfn,
312 312 fp=fp, changes=changes, opts=self.diffopts())
313 313
314 314 def mergeone(self, repo, mergeq, head, patch, rev, wlock):
315 315 # first try just applying the patch
316 316 (err, n) = self.apply(repo, [ patch ], update_status=False,
317 317 strict=True, merge=rev, wlock=wlock)
318 318
319 319 if err == 0:
320 320 return (err, n)
321 321
322 322 if n is None:
323 323 raise util.Abort(_("apply failed for patch %s") % patch)
324 324
325 325 self.ui.warn("patch didn't work out, merging %s\n" % patch)
326 326
327 327 # apply failed, strip away that rev and merge.
328 328 hg.clean(repo, head, wlock=wlock)
329 329 self.strip(repo, n, update=False, backup='strip', wlock=wlock)
330 330
331 331 c = repo.changelog.read(rev)
332 332 ret = hg.merge(repo, rev, wlock=wlock)
333 333 if ret:
334 334 raise util.Abort(_("update returned %d") % ret)
335 335 n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
336 336 if n == None:
337 337 raise util.Abort(_("repo commit failed"))
338 338 try:
339 339 message, comments, user, date, patchfound = mergeq.readheaders(patch)
340 340 except:
341 341 raise util.Abort(_("unable to read %s") % patch)
342 342
343 343 patchf = self.opener(patch, "w")
344 344 if comments:
345 345 comments = "\n".join(comments) + '\n\n'
346 346 patchf.write(comments)
347 347 self.printdiff(repo, head, n, fp=patchf)
348 348 patchf.close()
349 349 return (0, n)
350 350
351 351 def qparents(self, repo, rev=None):
352 352 if rev is None:
353 353 (p1, p2) = repo.dirstate.parents()
354 354 if p2 == revlog.nullid:
355 355 return p1
356 356 if len(self.applied) == 0:
357 357 return None
358 358 return revlog.bin(self.applied[-1].rev)
359 359 pp = repo.changelog.parents(rev)
360 360 if pp[1] != revlog.nullid:
361 361 arevs = [ x.rev for x in self.applied ]
362 362 p0 = revlog.hex(pp[0])
363 363 p1 = revlog.hex(pp[1])
364 364 if p0 in arevs:
365 365 return pp[0]
366 366 if p1 in arevs:
367 367 return pp[1]
368 368 return pp[0]
369 369
370 370 def mergepatch(self, repo, mergeq, series, wlock):
371 371 if len(self.applied) == 0:
372 372 # each of the patches merged in will have two parents. This
373 373 # can confuse the qrefresh, qdiff, and strip code because it
374 374 # needs to know which parent is actually in the patch queue.
375 375 # so, we insert a merge marker with only one parent. This way
376 376 # the first patch in the queue is never a merge patch
377 377 #
378 378 pname = ".hg.patches.merge.marker"
379 379 n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
380 380 wlock=wlock)
381 381 self.applied.append(statusentry(revlog.hex(n), pname))
382 382 self.applied_dirty = 1
383 383
384 384 head = self.qparents(repo)
385 385
386 386 for patch in series:
387 387 patch = mergeq.lookup(patch, strict=True)
388 388 if not patch:
389 389 self.ui.warn("patch %s does not exist\n" % patch)
390 390 return (1, None)
391 391 pushable, reason = self.pushable(patch)
392 392 if not pushable:
393 393 self.explain_pushable(patch, all_patches=True)
394 394 continue
395 395 info = mergeq.isapplied(patch)
396 396 if not info:
397 397 self.ui.warn("patch %s is not applied\n" % patch)
398 398 return (1, None)
399 399 rev = revlog.bin(info[1])
400 400 (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
401 401 if head:
402 402 self.applied.append(statusentry(revlog.hex(head), patch))
403 403 self.applied_dirty = 1
404 404 if err:
405 405 return (err, head)
406 406 return (0, head)
407 407
408 408 def patch(self, repo, patchfile):
409 409 '''Apply patchfile to the working directory.
410 410 patchfile: file name of patch'''
411 411 files = {}
412 412 try:
413 413 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
414 414 files=files)
415 415 except Exception, inst:
416 416 self.ui.note(str(inst) + '\n')
417 417 if not self.ui.verbose:
418 418 self.ui.warn("patch failed, unable to continue (try -v)\n")
419 419 return (False, files, False)
420 420
421 421 return (True, files, fuzz)
422 422
423 423 def apply(self, repo, series, list=False, update_status=True,
424 424 strict=False, patchdir=None, merge=None, wlock=None):
425 425 # TODO unify with commands.py
426 426 if not patchdir:
427 427 patchdir = self.path
428 428 err = 0
429 429 if not wlock:
430 430 wlock = repo.wlock()
431 431 lock = repo.lock()
432 432 tr = repo.transaction()
433 433 n = None
434 434 for patchname in series:
435 435 pushable, reason = self.pushable(patchname)
436 436 if not pushable:
437 437 self.explain_pushable(patchname, all_patches=True)
438 438 continue
439 439 self.ui.warn("applying %s\n" % patchname)
440 440 pf = os.path.join(patchdir, patchname)
441 441
442 442 try:
443 443 message, comments, user, date, patchfound = self.readheaders(patchname)
444 444 except:
445 445 self.ui.warn("Unable to read %s\n" % patchname)
446 446 err = 1
447 447 break
448 448
449 449 if not message:
450 450 message = "imported patch %s\n" % patchname
451 451 else:
452 452 if list:
453 453 message.append("\nimported patch %s" % patchname)
454 454 message = '\n'.join(message)
455 455
456 456 (patcherr, files, fuzz) = self.patch(repo, pf)
457 457 patcherr = not patcherr
458 458
459 459 if merge and files:
460 460 # Mark as merged and update dirstate parent info
461 461 repo.dirstate.update(repo.dirstate.filterfiles(files.keys()), 'm')
462 462 p1, p2 = repo.dirstate.parents()
463 463 repo.dirstate.setparents(p1, merge)
464 464 files = patch.updatedir(self.ui, repo, files, wlock=wlock)
465 465 n = repo.commit(files, message, user, date, force=1, lock=lock,
466 466 wlock=wlock)
467 467
468 468 if n == None:
469 469 raise util.Abort(_("repo commit failed"))
470 470
471 471 if update_status:
472 472 self.applied.append(statusentry(revlog.hex(n), patchname))
473 473
474 474 if patcherr:
475 475 if not patchfound:
476 476 self.ui.warn("patch %s is empty\n" % patchname)
477 477 err = 0
478 478 else:
479 479 self.ui.warn("patch failed, rejects left in working dir\n")
480 480 err = 1
481 481 break
482 482
483 483 if fuzz and strict:
484 484 self.ui.warn("fuzz found when applying patch, stopping\n")
485 485 err = 1
486 486 break
487 487 tr.close()
488 488 return (err, n)
489 489
490 490 def delete(self, repo, patches, opts):
491 491 realpatches = []
492 492 for patch in patches:
493 493 patch = self.lookup(patch, strict=True)
494 494 info = self.isapplied(patch)
495 495 if info:
496 496 raise util.Abort(_("cannot delete applied patch %s") % patch)
497 497 if patch not in self.series:
498 498 raise util.Abort(_("patch %s not in series file") % patch)
499 499 realpatches.append(patch)
500 500
501 501 appliedbase = 0
502 502 if opts.get('rev'):
503 503 if not self.applied:
504 504 raise util.Abort(_('no patches applied'))
505 revs = [int(r) for r in cmdutil.revrange(ui, repo, opts['rev'])]
505 revs = cmdutil.revrange(ui, repo, opts['rev'])
506 506 if len(revs) > 1 and revs[0] > revs[1]:
507 507 revs.reverse()
508 508 for rev in revs:
509 509 if appliedbase >= len(self.applied):
510 510 raise util.Abort(_("revision %d is not managed") % rev)
511 511
512 512 base = revlog.bin(self.applied[appliedbase].rev)
513 513 node = repo.changelog.node(rev)
514 514 if node != base:
515 515 raise util.Abort(_("cannot delete revision %d above "
516 516 "applied patches") % rev)
517 517 realpatches.append(self.applied[appliedbase].name)
518 518 appliedbase += 1
519 519
520 520 if not opts.get('keep'):
521 521 r = self.qrepo()
522 522 if r:
523 523 r.remove(realpatches, True)
524 524 else:
525 525 for p in realpatches:
526 526 os.unlink(self.join(p))
527 527
528 528 if appliedbase:
529 529 del self.applied[:appliedbase]
530 530 self.applied_dirty = 1
531 531 indices = [self.find_series(p) for p in realpatches]
532 532 indices.sort()
533 533 for i in indices[-1::-1]:
534 534 del self.full_series[i]
535 535 self.parse_series()
536 536 self.series_dirty = 1
537 537
538 538 def check_toppatch(self, repo):
539 539 if len(self.applied) > 0:
540 540 top = revlog.bin(self.applied[-1].rev)
541 541 pp = repo.dirstate.parents()
542 542 if top not in pp:
543 543 raise util.Abort(_("queue top not at same revision as working directory"))
544 544 return top
545 545 return None
546 546 def check_localchanges(self, repo, force=False, refresh=True):
547 547 m, a, r, d = repo.status()[:4]
548 548 if m or a or r or d:
549 549 if not force:
550 550 if refresh:
551 551 raise util.Abort(_("local changes found, refresh first"))
552 552 else:
553 553 raise util.Abort(_("local changes found"))
554 554 return m, a, r, d
555 555 def new(self, repo, patch, msg=None, force=None):
556 556 if os.path.exists(self.join(patch)):
557 557 raise util.Abort(_('patch "%s" already exists') % patch)
558 558 m, a, r, d = self.check_localchanges(repo, force)
559 559 commitfiles = m + a + r
560 560 self.check_toppatch(repo)
561 561 wlock = repo.wlock()
562 562 insert = self.full_series_end()
563 563 if msg:
564 564 n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True,
565 565 wlock=wlock)
566 566 else:
567 567 n = repo.commit(commitfiles,
568 568 "New patch: %s" % patch, force=True, wlock=wlock)
569 569 if n == None:
570 570 raise util.Abort(_("repo commit failed"))
571 571 self.full_series[insert:insert] = [patch]
572 572 self.applied.append(statusentry(revlog.hex(n), patch))
573 573 self.parse_series()
574 574 self.series_dirty = 1
575 575 self.applied_dirty = 1
576 576 p = self.opener(patch, "w")
577 577 if msg:
578 578 msg = msg + "\n"
579 579 p.write(msg)
580 580 p.close()
581 581 wlock = None
582 582 r = self.qrepo()
583 583 if r: r.add([patch])
584 584 if commitfiles:
585 585 self.refresh(repo, short=True)
586 586
587 587 def strip(self, repo, rev, update=True, backup="all", wlock=None):
588 588 def limitheads(chlog, stop):
589 589 """return the list of all nodes that have no children"""
590 590 p = {}
591 591 h = []
592 592 stoprev = 0
593 593 if stop in chlog.nodemap:
594 594 stoprev = chlog.rev(stop)
595 595
596 596 for r in xrange(chlog.count() - 1, -1, -1):
597 597 n = chlog.node(r)
598 598 if n not in p:
599 599 h.append(n)
600 600 if n == stop:
601 601 break
602 602 if r < stoprev:
603 603 break
604 604 for pn in chlog.parents(n):
605 605 p[pn] = 1
606 606 return h
607 607
608 608 def bundle(cg):
609 609 backupdir = repo.join("strip-backup")
610 610 if not os.path.isdir(backupdir):
611 611 os.mkdir(backupdir)
612 612 name = os.path.join(backupdir, "%s" % revlog.short(rev))
613 613 name = savename(name)
614 614 self.ui.warn("saving bundle to %s\n" % name)
615 615 # TODO, exclusive open
616 616 f = open(name, "wb")
617 617 try:
618 618 f.write("HG10")
619 619 z = bz2.BZ2Compressor(9)
620 620 while 1:
621 621 chunk = cg.read(4096)
622 622 if not chunk:
623 623 break
624 624 f.write(z.compress(chunk))
625 625 f.write(z.flush())
626 626 except:
627 627 os.unlink(name)
628 628 raise
629 629 f.close()
630 630 return name
631 631
632 632 def stripall(rev, revnum):
633 633 cl = repo.changelog
634 634 c = cl.read(rev)
635 635 mm = repo.manifest.read(c[0])
636 636 seen = {}
637 637
638 638 for x in xrange(revnum, cl.count()):
639 639 c = cl.read(cl.node(x))
640 640 for f in c[3]:
641 641 if f in seen:
642 642 continue
643 643 seen[f] = 1
644 644 if f in mm:
645 645 filerev = mm[f]
646 646 else:
647 647 filerev = 0
648 648 seen[f] = filerev
649 649 # we go in two steps here so the strip loop happens in a
650 650 # sensible order. When stripping many files, this helps keep
651 651 # our disk access patterns under control.
652 652 seen_list = seen.keys()
653 653 seen_list.sort()
654 654 for f in seen_list:
655 655 ff = repo.file(f)
656 656 filerev = seen[f]
657 657 if filerev != 0:
658 658 if filerev in ff.nodemap:
659 659 filerev = ff.rev(filerev)
660 660 else:
661 661 filerev = 0
662 662 ff.strip(filerev, revnum)
663 663
664 664 if not wlock:
665 665 wlock = repo.wlock()
666 666 lock = repo.lock()
667 667 chlog = repo.changelog
668 668 # TODO delete the undo files, and handle undo of merge sets
669 669 pp = chlog.parents(rev)
670 670 revnum = chlog.rev(rev)
671 671
672 672 if update:
673 673 self.check_localchanges(repo, refresh=False)
674 674 urev = self.qparents(repo, rev)
675 675 hg.clean(repo, urev, wlock=wlock)
676 676 repo.dirstate.write()
677 677
678 678 # save is a list of all the branches we are truncating away
679 679 # that we actually want to keep. changegroup will be used
680 680 # to preserve them and add them back after the truncate
681 681 saveheads = []
682 682 savebases = {}
683 683
684 684 heads = limitheads(chlog, rev)
685 685 seen = {}
686 686
687 687 # search through all the heads, finding those where the revision
688 688 # we want to strip away is an ancestor. Also look for merges
689 689 # that might be turned into new heads by the strip.
690 690 while heads:
691 691 h = heads.pop()
692 692 n = h
693 693 while True:
694 694 seen[n] = 1
695 695 pp = chlog.parents(n)
696 696 if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
697 697 if pp[1] not in seen:
698 698 heads.append(pp[1])
699 699 if pp[0] == revlog.nullid:
700 700 break
701 701 if chlog.rev(pp[0]) < revnum:
702 702 break
703 703 n = pp[0]
704 704 if n == rev:
705 705 break
706 706 r = chlog.reachable(h, rev)
707 707 if rev not in r:
708 708 saveheads.append(h)
709 709 for x in r:
710 710 if chlog.rev(x) > revnum:
711 711 savebases[x] = 1
712 712
713 713 # create a changegroup for all the branches we need to keep
714 714 if backup == "all":
715 715 backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
716 716 bundle(backupch)
717 717 if saveheads:
718 718 backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
719 719 chgrpfile = bundle(backupch)
720 720
721 721 stripall(rev, revnum)
722 722
723 723 change = chlog.read(rev)
724 724 chlog.strip(revnum, revnum)
725 725 repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
726 726 if saveheads:
727 727 self.ui.status("adding branch\n")
728 728 commands.unbundle(self.ui, repo, chgrpfile, update=False)
729 729 if backup != "strip":
730 730 os.unlink(chgrpfile)
731 731
732 732 def isapplied(self, patch):
733 733 """returns (index, rev, patch)"""
734 734 for i in xrange(len(self.applied)):
735 735 a = self.applied[i]
736 736 if a.name == patch:
737 737 return (i, a.rev, a.name)
738 738 return None
739 739
740 740 # if the exact patch name does not exist, we try a few
741 741 # variations. If strict is passed, we try only #1
742 742 #
743 743 # 1) a number to indicate an offset in the series file
744 744 # 2) a unique substring of the patch name was given
745 745 # 3) patchname[-+]num to indicate an offset in the series file
746 746 def lookup(self, patch, strict=False):
747 747 patch = patch and str(patch)
748 748
749 749 def partial_name(s):
750 750 if s in self.series:
751 751 return s
752 752 matches = [x for x in self.series if s in x]
753 753 if len(matches) > 1:
754 754 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
755 755 for m in matches:
756 756 self.ui.warn(' %s\n' % m)
757 757 return None
758 758 if matches:
759 759 return matches[0]
760 760 if len(self.series) > 0 and len(self.applied) > 0:
761 761 if s == 'qtip':
762 762 return self.series[self.series_end()-1]
763 763 if s == 'qbase':
764 764 return self.series[0]
765 765 return None
766 766 if patch == None:
767 767 return None
768 768
769 769 # we don't want to return a partial match until we make
770 770 # sure the file name passed in does not exist (checked below)
771 771 res = partial_name(patch)
772 772 if res and res == patch:
773 773 return res
774 774
775 775 if not os.path.isfile(self.join(patch)):
776 776 try:
777 777 sno = int(patch)
778 778 except(ValueError, OverflowError):
779 779 pass
780 780 else:
781 781 if sno < len(self.series):
782 782 return self.series[sno]
783 783 if not strict:
784 784 # return any partial match made above
785 785 if res:
786 786 return res
787 787 minus = patch.rfind('-')
788 788 if minus >= 0:
789 789 res = partial_name(patch[:minus])
790 790 if res:
791 791 i = self.series.index(res)
792 792 try:
793 793 off = int(patch[minus+1:] or 1)
794 794 except(ValueError, OverflowError):
795 795 pass
796 796 else:
797 797 if i - off >= 0:
798 798 return self.series[i - off]
799 799 plus = patch.rfind('+')
800 800 if plus >= 0:
801 801 res = partial_name(patch[:plus])
802 802 if res:
803 803 i = self.series.index(res)
804 804 try:
805 805 off = int(patch[plus+1:] or 1)
806 806 except(ValueError, OverflowError):
807 807 pass
808 808 else:
809 809 if i + off < len(self.series):
810 810 return self.series[i + off]
811 811 raise util.Abort(_("patch %s not in series") % patch)
812 812
813 813 def push(self, repo, patch=None, force=False, list=False,
814 814 mergeq=None, wlock=None):
815 815 if not wlock:
816 816 wlock = repo.wlock()
817 817 patch = self.lookup(patch)
818 818 if patch and self.isapplied(patch):
819 819 raise util.Abort(_("patch %s is already applied") % patch)
820 820 if self.series_end() == len(self.series):
821 821 raise util.Abort(_("patch series fully applied"))
822 822 if not force:
823 823 self.check_localchanges(repo)
824 824
825 825 self.applied_dirty = 1;
826 826 start = self.series_end()
827 827 if start > 0:
828 828 self.check_toppatch(repo)
829 829 if not patch:
830 830 patch = self.series[start]
831 831 end = start + 1
832 832 else:
833 833 end = self.series.index(patch, start) + 1
834 834 s = self.series[start:end]
835 835 if mergeq:
836 836 ret = self.mergepatch(repo, mergeq, s, wlock)
837 837 else:
838 838 ret = self.apply(repo, s, list, wlock=wlock)
839 839 top = self.applied[-1].name
840 840 if ret[0]:
841 841 self.ui.write("Errors during apply, please fix and refresh %s\n" %
842 842 top)
843 843 else:
844 844 self.ui.write("Now at: %s\n" % top)
845 845 return ret[0]
846 846
847 847 def pop(self, repo, patch=None, force=False, update=True, all=False,
848 848 wlock=None):
849 849 def getfile(f, rev):
850 850 t = repo.file(f).read(rev)
851 851 try:
852 852 repo.wfile(f, "w").write(t)
853 853 except IOError:
854 854 try:
855 855 os.makedirs(os.path.dirname(repo.wjoin(f)))
856 856 except OSError, err:
857 857 if err.errno != errno.EEXIST: raise
858 858 repo.wfile(f, "w").write(t)
859 859
860 860 if not wlock:
861 861 wlock = repo.wlock()
862 862 if patch:
863 863 # index, rev, patch
864 864 info = self.isapplied(patch)
865 865 if not info:
866 866 patch = self.lookup(patch)
867 867 info = self.isapplied(patch)
868 868 if not info:
869 869 raise util.Abort(_("patch %s is not applied") % patch)
870 870 if len(self.applied) == 0:
871 871 raise util.Abort(_("no patches applied"))
872 872
873 873 if not update:
874 874 parents = repo.dirstate.parents()
875 875 rr = [ revlog.bin(x.rev) for x in self.applied ]
876 876 for p in parents:
877 877 if p in rr:
878 878 self.ui.warn("qpop: forcing dirstate update\n")
879 879 update = True
880 880
881 881 if not force and update:
882 882 self.check_localchanges(repo)
883 883
884 884 self.applied_dirty = 1;
885 885 end = len(self.applied)
886 886 if not patch:
887 887 if all:
888 888 popi = 0
889 889 else:
890 890 popi = len(self.applied) - 1
891 891 else:
892 892 popi = info[0] + 1
893 893 if popi >= end:
894 894 self.ui.warn("qpop: %s is already at the top\n" % patch)
895 895 return
896 896 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
897 897
898 898 start = info[0]
899 899 rev = revlog.bin(info[1])
900 900
901 901 # we know there are no local changes, so we can make a simplified
902 902 # form of hg.update.
903 903 if update:
904 904 top = self.check_toppatch(repo)
905 905 qp = self.qparents(repo, rev)
906 906 changes = repo.changelog.read(qp)
907 907 mmap = repo.manifest.read(changes[0])
908 908 m, a, r, d, u = repo.status(qp, top)[:5]
909 909 if d:
910 910 raise util.Abort("deletions found between repo revs")
911 911 for f in m:
912 912 getfile(f, mmap[f])
913 913 for f in r:
914 914 getfile(f, mmap[f])
915 915 util.set_exec(repo.wjoin(f), mmap.execf(f))
916 916 repo.dirstate.update(m + r, 'n')
917 917 for f in a:
918 918 try: os.unlink(repo.wjoin(f))
919 919 except: raise
920 920 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
921 921 except: pass
922 922 if a:
923 923 repo.dirstate.forget(a)
924 924 repo.dirstate.setparents(qp, revlog.nullid)
925 925 self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
926 926 del self.applied[start:end]
927 927 if len(self.applied):
928 928 self.ui.write("Now at: %s\n" % self.applied[-1].name)
929 929 else:
930 930 self.ui.write("Patch queue now empty\n")
931 931
932 932 def diff(self, repo, pats, opts):
933 933 top = self.check_toppatch(repo)
934 934 if not top:
935 935 self.ui.write("No patches applied\n")
936 936 return
937 937 qp = self.qparents(repo, top)
938 938 self.printdiff(repo, qp, files=pats, opts=opts)
939 939
940 940 def refresh(self, repo, pats=None, **opts):
941 941 if len(self.applied) == 0:
942 942 self.ui.write("No patches applied\n")
943 943 return 1
944 944 wlock = repo.wlock()
945 945 self.check_toppatch(repo)
946 946 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
947 947 top = revlog.bin(top)
948 948 cparents = repo.changelog.parents(top)
949 949 patchparent = self.qparents(repo, top)
950 950 message, comments, user, date, patchfound = self.readheaders(patchfn)
951 951
952 952 patchf = self.opener(patchfn, "w")
953 953 msg = opts.get('msg', '').rstrip()
954 954 if msg:
955 955 if comments:
956 956 # Remove existing message.
957 957 ci = 0
958 958 for mi in xrange(len(message)):
959 959 while message[mi] != comments[ci]:
960 960 ci += 1
961 961 del comments[ci]
962 962 comments.append(msg)
963 963 if comments:
964 964 comments = "\n".join(comments) + '\n\n'
965 965 patchf.write(comments)
966 966
967 967 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
968 968 tip = repo.changelog.tip()
969 969 if top == tip:
970 970 # if the top of our patch queue is also the tip, there is an
971 971 # optimization here. We update the dirstate in place and strip
972 972 # off the tip commit. Then just commit the current directory
973 973 # tree. We can also send repo.commit the list of files
974 974 # changed to speed up the diff
975 975 #
976 976 # in short mode, we only diff the files included in the
977 977 # patch already
978 978 #
979 979 # this should really read:
980 980 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
981 981 # but we do it backwards to take advantage of manifest/chlog
982 982 # caching against the next repo.status call
983 983 #
984 984 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
985 985 if opts.get('short'):
986 986 filelist = mm + aa + dd
987 987 else:
988 988 filelist = None
989 989 m, a, r, d, u = repo.status(files=filelist)[:5]
990 990
991 991 # we might end up with files that were added between tip and
992 992 # the dirstate parent, but then changed in the local dirstate.
993 993 # in this case, we want them to only show up in the added section
994 994 for x in m:
995 995 if x not in aa:
996 996 mm.append(x)
997 997 # we might end up with files added by the local dirstate that
998 998 # were deleted by the patch. In this case, they should only
999 999 # show up in the changed section.
1000 1000 for x in a:
1001 1001 if x in dd:
1002 1002 del dd[dd.index(x)]
1003 1003 mm.append(x)
1004 1004 else:
1005 1005 aa.append(x)
1006 1006 # make sure any files deleted in the local dirstate
1007 1007 # are not in the add or change column of the patch
1008 1008 forget = []
1009 1009 for x in d + r:
1010 1010 if x in aa:
1011 1011 del aa[aa.index(x)]
1012 1012 forget.append(x)
1013 1013 continue
1014 1014 elif x in mm:
1015 1015 del mm[mm.index(x)]
1016 1016 dd.append(x)
1017 1017
1018 1018 m = list(util.unique(mm))
1019 1019 r = list(util.unique(dd))
1020 1020 a = list(util.unique(aa))
1021 1021 filelist = filter(matchfn, util.unique(m + r + a))
1022 1022 if opts.get('git'):
1023 1023 self.diffopts().git = True
1024 1024 patch.diff(repo, patchparent, files=filelist, match=matchfn,
1025 1025 fp=patchf, changes=(m, a, r, [], u),
1026 1026 opts=self.diffopts())
1027 1027 patchf.close()
1028 1028
1029 1029 changes = repo.changelog.read(tip)
1030 1030 repo.dirstate.setparents(*cparents)
1031 1031 copies = [(f, repo.dirstate.copied(f)) for f in a]
1032 1032 repo.dirstate.update(a, 'a')
1033 1033 for dst, src in copies:
1034 1034 repo.dirstate.copy(src, dst)
1035 1035 repo.dirstate.update(r, 'r')
1036 1036 # if the patch excludes a modified file, mark that file with mtime=0
1037 1037 # so status can see it.
1038 1038 mm = []
1039 1039 for i in xrange(len(m)-1, -1, -1):
1040 1040 if not matchfn(m[i]):
1041 1041 mm.append(m[i])
1042 1042 del m[i]
1043 1043 repo.dirstate.update(m, 'n')
1044 1044 repo.dirstate.update(mm, 'n', st_mtime=0)
1045 1045 repo.dirstate.forget(forget)
1046 1046
1047 1047 if not msg:
1048 1048 if not message:
1049 1049 message = "patch queue: %s\n" % patchfn
1050 1050 else:
1051 1051 message = "\n".join(message)
1052 1052 else:
1053 1053 message = msg
1054 1054
1055 1055 self.strip(repo, top, update=False, backup='strip', wlock=wlock)
1056 1056 n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
1057 1057 self.applied[-1] = statusentry(revlog.hex(n), patchfn)
1058 1058 self.applied_dirty = 1
1059 1059 else:
1060 1060 self.printdiff(repo, patchparent, fp=patchf)
1061 1061 patchf.close()
1062 1062 self.pop(repo, force=True, wlock=wlock)
1063 1063 self.push(repo, force=True, wlock=wlock)
1064 1064
1065 1065 def init(self, repo, create=False):
1066 1066 if os.path.isdir(self.path):
1067 1067 raise util.Abort(_("patch queue directory already exists"))
1068 1068 os.mkdir(self.path)
1069 1069 if create:
1070 1070 return self.qrepo(create=True)
1071 1071
1072 1072 def unapplied(self, repo, patch=None):
1073 1073 if patch and patch not in self.series:
1074 1074 raise util.Abort(_("patch %s is not in series file") % patch)
1075 1075 if not patch:
1076 1076 start = self.series_end()
1077 1077 else:
1078 1078 start = self.series.index(patch) + 1
1079 1079 unapplied = []
1080 1080 for i in xrange(start, len(self.series)):
1081 1081 pushable, reason = self.pushable(i)
1082 1082 if pushable:
1083 1083 unapplied.append((i, self.series[i]))
1084 1084 self.explain_pushable(i)
1085 1085 return unapplied
1086 1086
1087 1087 def qseries(self, repo, missing=None, start=0, length=0, status=None,
1088 1088 summary=False):
1089 1089 def displayname(patchname):
1090 1090 if summary:
1091 1091 msg = self.readheaders(patchname)[0]
1092 1092 msg = msg and ': ' + msg[0] or ': '
1093 1093 else:
1094 1094 msg = ''
1095 1095 return '%s%s' % (patchname, msg)
1096 1096
1097 1097 def pname(i):
1098 1098 if status == 'A':
1099 1099 return self.applied[i].name
1100 1100 else:
1101 1101 return self.series[i]
1102 1102
1103 1103 unapplied = self.series_end(all_patches=True)
1104 1104 if not length:
1105 1105 length = len(self.series) - start
1106 1106 if not missing:
1107 1107 for i in xrange(start, start+length):
1108 1108 pfx = ''
1109 1109 patch = pname(i)
1110 1110 if self.ui.verbose:
1111 1111 if i < unapplied:
1112 1112 status = 'A'
1113 1113 elif self.pushable(i)[0]:
1114 1114 status = 'U'
1115 1115 else:
1116 1116 status = 'G'
1117 1117 pfx = '%d %s ' % (i, status)
1118 1118 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1119 1119 else:
1120 1120 msng_list = []
1121 1121 for root, dirs, files in os.walk(self.path):
1122 1122 d = root[len(self.path) + 1:]
1123 1123 for f in files:
1124 1124 fl = os.path.join(d, f)
1125 1125 if (fl not in self.series and
1126 1126 fl not in (self.status_path, self.series_path)
1127 1127 and not fl.startswith('.')):
1128 1128 msng_list.append(fl)
1129 1129 msng_list.sort()
1130 1130 for x in msng_list:
1131 1131 pfx = self.ui.verbose and ('D ') or ''
1132 1132 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1133 1133
1134 1134 def issaveline(self, l):
1135 1135 if l.name == '.hg.patches.save.line':
1136 1136 return True
1137 1137
1138 1138 def qrepo(self, create=False):
1139 1139 if create or os.path.isdir(self.join(".hg")):
1140 1140 return hg.repository(self.ui, path=self.path, create=create)
1141 1141
1142 1142 def restore(self, repo, rev, delete=None, qupdate=None):
1143 1143 c = repo.changelog.read(rev)
1144 1144 desc = c[4].strip()
1145 1145 lines = desc.splitlines()
1146 1146 i = 0
1147 1147 datastart = None
1148 1148 series = []
1149 1149 applied = []
1150 1150 qpp = None
1151 1151 for i in xrange(0, len(lines)):
1152 1152 if lines[i] == 'Patch Data:':
1153 1153 datastart = i + 1
1154 1154 elif lines[i].startswith('Dirstate:'):
1155 1155 l = lines[i].rstrip()
1156 1156 l = l[10:].split(' ')
1157 1157 qpp = [ hg.bin(x) for x in l ]
1158 1158 elif datastart != None:
1159 1159 l = lines[i].rstrip()
1160 1160 se = statusentry(l)
1161 1161 file_ = se.name
1162 1162 if se.rev:
1163 1163 applied.append(se)
1164 1164 else:
1165 1165 series.append(file_)
1166 1166 if datastart == None:
1167 1167 self.ui.warn("No saved patch data found\n")
1168 1168 return 1
1169 1169 self.ui.warn("restoring status: %s\n" % lines[0])
1170 1170 self.full_series = series
1171 1171 self.applied = applied
1172 1172 self.parse_series()
1173 1173 self.series_dirty = 1
1174 1174 self.applied_dirty = 1
1175 1175 heads = repo.changelog.heads()
1176 1176 if delete:
1177 1177 if rev not in heads:
1178 1178 self.ui.warn("save entry has children, leaving it alone\n")
1179 1179 else:
1180 1180 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1181 1181 pp = repo.dirstate.parents()
1182 1182 if rev in pp:
1183 1183 update = True
1184 1184 else:
1185 1185 update = False
1186 1186 self.strip(repo, rev, update=update, backup='strip')
1187 1187 if qpp:
1188 1188 self.ui.warn("saved queue repository parents: %s %s\n" %
1189 1189 (hg.short(qpp[0]), hg.short(qpp[1])))
1190 1190 if qupdate:
1191 1191 print "queue directory updating"
1192 1192 r = self.qrepo()
1193 1193 if not r:
1194 1194 self.ui.warn("Unable to load queue repository\n")
1195 1195 return 1
1196 1196 hg.clean(r, qpp[0])
1197 1197
1198 1198 def save(self, repo, msg=None):
1199 1199 if len(self.applied) == 0:
1200 1200 self.ui.warn("save: no patches applied, exiting\n")
1201 1201 return 1
1202 1202 if self.issaveline(self.applied[-1]):
1203 1203 self.ui.warn("status is already saved\n")
1204 1204 return 1
1205 1205
1206 1206 ar = [ ':' + x for x in self.full_series ]
1207 1207 if not msg:
1208 1208 msg = "hg patches saved state"
1209 1209 else:
1210 1210 msg = "hg patches: " + msg.rstrip('\r\n')
1211 1211 r = self.qrepo()
1212 1212 if r:
1213 1213 pp = r.dirstate.parents()
1214 1214 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1215 1215 msg += "\n\nPatch Data:\n"
1216 1216 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1217 1217 "\n".join(ar) + '\n' or "")
1218 1218 n = repo.commit(None, text, user=None, force=1)
1219 1219 if not n:
1220 1220 self.ui.warn("repo commit failed\n")
1221 1221 return 1
1222 1222 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1223 1223 self.applied_dirty = 1
1224 1224
1225 1225 def full_series_end(self):
1226 1226 if len(self.applied) > 0:
1227 1227 p = self.applied[-1].name
1228 1228 end = self.find_series(p)
1229 1229 if end == None:
1230 1230 return len(self.full_series)
1231 1231 return end + 1
1232 1232 return 0
1233 1233
1234 1234 def series_end(self, all_patches=False):
1235 1235 end = 0
1236 1236 def next(start):
1237 1237 if all_patches:
1238 1238 return start
1239 1239 i = start
1240 1240 while i < len(self.series):
1241 1241 p, reason = self.pushable(i)
1242 1242 if p:
1243 1243 break
1244 1244 self.explain_pushable(i)
1245 1245 i += 1
1246 1246 return i
1247 1247 if len(self.applied) > 0:
1248 1248 p = self.applied[-1].name
1249 1249 try:
1250 1250 end = self.series.index(p)
1251 1251 except ValueError:
1252 1252 return 0
1253 1253 return next(end + 1)
1254 1254 return next(end)
1255 1255
1256 1256 def appliedname(self, index):
1257 1257 pname = self.applied[index].name
1258 1258 if not self.ui.verbose:
1259 1259 p = pname
1260 1260 else:
1261 1261 p = str(self.series.index(pname)) + " " + pname
1262 1262 return p
1263 1263
1264 1264 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1265 1265 force=None):
1266 1266 def checkseries(patchname):
1267 1267 if patchname in self.series:
1268 1268 raise util.Abort(_('patch %s is already in the series file')
1269 1269 % patchname)
1270 1270 def checkfile(patchname):
1271 1271 if not force and os.path.exists(self.join(patchname)):
1272 1272 raise util.Abort(_('patch "%s" already exists')
1273 1273 % patchname)
1274 1274
1275 1275 if rev:
1276 1276 if files:
1277 1277 raise util.Abort(_('option "-r" not valid when importing '
1278 1278 'files'))
1279 rev = [int(r) for r in cmdutil.revrange(self.ui, repo, rev)]
1279 rev = cmdutil.revrange(self.ui, repo, rev)
1280 1280 rev.sort(lambda x, y: cmp(y, x))
1281 1281 if (len(files) > 1 or len(rev) > 1) and patchname:
1282 1282 raise util.Abort(_('option "-n" not valid when importing multiple '
1283 1283 'patches'))
1284 1284 i = 0
1285 1285 added = []
1286 1286 if rev:
1287 1287 # If mq patches are applied, we can only import revisions
1288 1288 # that form a linear path to qbase.
1289 1289 # Otherwise, they should form a linear path to a head.
1290 1290 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1291 1291 if len(heads) > 1:
1292 1292 raise util.Abort(_('revision %d is the root of more than one '
1293 1293 'branch') % rev[-1])
1294 1294 if self.applied:
1295 1295 base = revlog.hex(repo.changelog.node(rev[0]))
1296 1296 if base in [n.rev for n in self.applied]:
1297 1297 raise util.Abort(_('revision %d is already managed')
1298 1298 % rev[0])
1299 1299 if heads != [revlog.bin(self.applied[-1].rev)]:
1300 1300 raise util.Abort(_('revision %d is not the parent of '
1301 1301 'the queue') % rev[0])
1302 1302 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1303 1303 lastparent = repo.changelog.parentrevs(base)[0]
1304 1304 else:
1305 1305 if heads != [repo.changelog.node(rev[0])]:
1306 1306 raise util.Abort(_('revision %d has unmanaged children')
1307 1307 % rev[0])
1308 1308 lastparent = None
1309 1309
1310 1310 for r in rev:
1311 1311 p1, p2 = repo.changelog.parentrevs(r)
1312 1312 n = repo.changelog.node(r)
1313 1313 if p2 != -1:
1314 1314 raise util.Abort(_('cannot import merge revision %d') % r)
1315 1315 if lastparent and lastparent != r:
1316 1316 raise util.Abort(_('revision %d is not the parent of %d')
1317 1317 % (r, lastparent))
1318 1318 lastparent = p1
1319 1319
1320 1320 if not patchname:
1321 1321 patchname = '%d.diff' % r
1322 1322 checkseries(patchname)
1323 1323 checkfile(patchname)
1324 1324 self.full_series.insert(0, patchname)
1325 1325
1326 1326 patchf = self.opener(patchname, "w")
1327 1327 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1328 1328 patchf.close()
1329 1329
1330 1330 se = statusentry(revlog.hex(n), patchname)
1331 1331 self.applied.insert(0, se)
1332 1332
1333 1333 added.append(patchname)
1334 1334 patchname = None
1335 1335 self.parse_series()
1336 1336 self.applied_dirty = 1
1337 1337
1338 1338 for filename in files:
1339 1339 if existing:
1340 1340 if not patchname:
1341 1341 patchname = filename
1342 1342 if not os.path.isfile(self.join(patchname)):
1343 1343 raise util.Abort(_("patch %s does not exist") % patchname)
1344 1344 else:
1345 1345 try:
1346 1346 text = file(filename).read()
1347 1347 except IOError:
1348 1348 raise util.Abort(_("unable to read %s") % patchname)
1349 1349 if not patchname:
1350 1350 patchname = os.path.basename(filename)
1351 1351 checkfile(patchname)
1352 1352 patchf = self.opener(patchname, "w")
1353 1353 patchf.write(text)
1354 1354 checkseries(patchname)
1355 1355 index = self.full_series_end() + i
1356 1356 self.full_series[index:index] = [patchname]
1357 1357 self.parse_series()
1358 1358 self.ui.warn("adding %s to series file\n" % patchname)
1359 1359 i += 1
1360 1360 added.append(patchname)
1361 1361 patchname = None
1362 1362 self.series_dirty = 1
1363 1363 qrepo = self.qrepo()
1364 1364 if qrepo:
1365 1365 qrepo.add(added)
1366 1366
1367 1367 def delete(ui, repo, *patches, **opts):
1368 1368 """remove patches from queue
1369 1369
1370 1370 With --rev, mq will stop managing the named revisions. The
1371 1371 patches must be applied and at the base of the stack. This option
1372 1372 is useful when the patches have been applied upstream.
1373 1373
1374 1374 Otherwise, the patches must not be applied.
1375 1375
1376 1376 With --keep, the patch files are preserved in the patch directory."""
1377 1377 q = repo.mq
1378 1378 q.delete(repo, patches, opts)
1379 1379 q.save_dirty()
1380 1380 return 0
1381 1381
1382 1382 def applied(ui, repo, patch=None, **opts):
1383 1383 """print the patches already applied"""
1384 1384 q = repo.mq
1385 1385 if patch:
1386 1386 if patch not in q.series:
1387 1387 raise util.Abort(_("patch %s is not in series file") % patch)
1388 1388 end = q.series.index(patch) + 1
1389 1389 else:
1390 1390 end = len(q.applied)
1391 1391 if not end:
1392 1392 return
1393 1393
1394 1394 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1395 1395
1396 1396 def unapplied(ui, repo, patch=None, **opts):
1397 1397 """print the patches not yet applied"""
1398 1398 q = repo.mq
1399 1399 if patch:
1400 1400 if patch not in q.series:
1401 1401 raise util.Abort(_("patch %s is not in series file") % patch)
1402 1402 start = q.series.index(patch) + 1
1403 1403 else:
1404 1404 start = q.series_end()
1405 1405 q.qseries(repo, start=start, summary=opts.get('summary'))
1406 1406
1407 1407 def qimport(ui, repo, *filename, **opts):
1408 1408 """import a patch
1409 1409
1410 1410 The patch will have the same name as its source file unless you
1411 1411 give it a new one with --name.
1412 1412
1413 1413 You can register an existing patch inside the patch directory
1414 1414 with the --existing flag.
1415 1415
1416 1416 With --force, an existing patch of the same name will be overwritten.
1417 1417
1418 1418 An existing changeset may be placed under mq control with --rev
1419 1419 (e.g. qimport --rev tip -n patch will place tip under mq control).
1420 1420 """
1421 1421 q = repo.mq
1422 1422 q.qimport(repo, filename, patchname=opts['name'],
1423 1423 existing=opts['existing'], force=opts['force'], rev=opts['rev'])
1424 1424 q.save_dirty()
1425 1425 return 0
1426 1426
1427 1427 def init(ui, repo, **opts):
1428 1428 """init a new queue repository
1429 1429
1430 1430 The queue repository is unversioned by default. If -c is
1431 1431 specified, qinit will create a separate nested repository
1432 1432 for patches. Use qcommit to commit changes to this queue
1433 1433 repository."""
1434 1434 q = repo.mq
1435 1435 r = q.init(repo, create=opts['create_repo'])
1436 1436 q.save_dirty()
1437 1437 if r:
1438 1438 fp = r.wopener('.hgignore', 'w')
1439 1439 print >> fp, 'syntax: glob'
1440 1440 print >> fp, 'status'
1441 1441 fp.close()
1442 1442 r.wopener('series', 'w').close()
1443 1443 r.add(['.hgignore', 'series'])
1444 1444 return 0
1445 1445
1446 1446 def clone(ui, source, dest=None, **opts):
1447 1447 '''clone main and patch repository at same time
1448 1448
1449 1449 If source is local, destination will have no patches applied. If
1450 1450 source is remote, this command can not check if patches are
1451 1451 applied in source, so cannot guarantee that patches are not
1452 1452 applied in destination. If you clone remote repository, be sure
1453 1453 before that it has no patches applied.
1454 1454
1455 1455 Source patch repository is looked for in <src>/.hg/patches by
1456 1456 default. Use -p <url> to change.
1457 1457 '''
1458 1458 commands.setremoteconfig(ui, opts)
1459 1459 if dest is None:
1460 1460 dest = hg.defaultdest(source)
1461 1461 sr = hg.repository(ui, ui.expandpath(source))
1462 1462 qbase, destrev = None, None
1463 1463 if sr.local():
1464 1464 reposetup(ui, sr)
1465 1465 if sr.mq.applied:
1466 1466 qbase = revlog.bin(sr.mq.applied[0].rev)
1467 1467 if not hg.islocal(dest):
1468 1468 destrev = sr.parents(qbase)[0]
1469 1469 ui.note(_('cloning main repo\n'))
1470 1470 sr, dr = hg.clone(ui, sr, dest,
1471 1471 pull=opts['pull'],
1472 1472 rev=destrev,
1473 1473 update=False,
1474 1474 stream=opts['uncompressed'])
1475 1475 ui.note(_('cloning patch repo\n'))
1476 1476 spr, dpr = hg.clone(ui, opts['patches'] or (sr.url() + '/.hg/patches'),
1477 1477 dr.url() + '/.hg/patches',
1478 1478 pull=opts['pull'],
1479 1479 update=not opts['noupdate'],
1480 1480 stream=opts['uncompressed'])
1481 1481 if dr.local():
1482 1482 if qbase:
1483 1483 ui.note(_('stripping applied patches from destination repo\n'))
1484 1484 reposetup(ui, dr)
1485 1485 dr.mq.strip(dr, qbase, update=False, backup=None)
1486 1486 if not opts['noupdate']:
1487 1487 ui.note(_('updating destination repo\n'))
1488 1488 hg.update(dr, dr.changelog.tip())
1489 1489
1490 1490 def commit(ui, repo, *pats, **opts):
1491 1491 """commit changes in the queue repository"""
1492 1492 q = repo.mq
1493 1493 r = q.qrepo()
1494 1494 if not r: raise util.Abort('no queue repository')
1495 1495 commands.commit(r.ui, r, *pats, **opts)
1496 1496
1497 1497 def series(ui, repo, **opts):
1498 1498 """print the entire series file"""
1499 1499 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1500 1500 return 0
1501 1501
1502 1502 def top(ui, repo, **opts):
1503 1503 """print the name of the current patch"""
1504 1504 q = repo.mq
1505 1505 t = len(q.applied)
1506 1506 if t:
1507 1507 return q.qseries(repo, start=t-1, length=1, status='A',
1508 1508 summary=opts.get('summary'))
1509 1509 else:
1510 1510 ui.write("No patches applied\n")
1511 1511 return 1
1512 1512
1513 1513 def next(ui, repo, **opts):
1514 1514 """print the name of the next patch"""
1515 1515 q = repo.mq
1516 1516 end = q.series_end()
1517 1517 if end == len(q.series):
1518 1518 ui.write("All patches applied\n")
1519 1519 return 1
1520 1520 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1521 1521
1522 1522 def prev(ui, repo, **opts):
1523 1523 """print the name of the previous patch"""
1524 1524 q = repo.mq
1525 1525 l = len(q.applied)
1526 1526 if l == 1:
1527 1527 ui.write("Only one patch applied\n")
1528 1528 return 1
1529 1529 if not l:
1530 1530 ui.write("No patches applied\n")
1531 1531 return 1
1532 1532 return q.qseries(repo, start=l-2, length=1, status='A',
1533 1533 summary=opts.get('summary'))
1534 1534
1535 1535 def new(ui, repo, patch, **opts):
1536 1536 """create a new patch
1537 1537
1538 1538 qnew creates a new patch on top of the currently-applied patch
1539 1539 (if any). It will refuse to run if there are any outstanding
1540 1540 changes unless -f is specified, in which case the patch will
1541 1541 be initialised with them.
1542 1542
1543 1543 -e, -m or -l set the patch header as well as the commit message.
1544 1544 If none is specified, the patch header is empty and the
1545 1545 commit message is 'New patch: PATCH'"""
1546 1546 q = repo.mq
1547 1547 message = commands.logmessage(opts)
1548 1548 if opts['edit']:
1549 1549 message = ui.edit(message, ui.username())
1550 1550 q.new(repo, patch, msg=message, force=opts['force'])
1551 1551 q.save_dirty()
1552 1552 return 0
1553 1553
1554 1554 def refresh(ui, repo, *pats, **opts):
1555 1555 """update the current patch
1556 1556
1557 1557 If any file patterns are provided, the refreshed patch will contain only
1558 1558 the modifications that match those patterns; the remaining modifications
1559 1559 will remain in the working directory.
1560 1560 """
1561 1561 q = repo.mq
1562 1562 message = commands.logmessage(opts)
1563 1563 if opts['edit']:
1564 1564 if message:
1565 1565 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1566 1566 patch = q.applied[-1].name
1567 1567 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1568 1568 message = ui.edit('\n'.join(message), user or ui.username())
1569 1569 ret = q.refresh(repo, pats, msg=message, **opts)
1570 1570 q.save_dirty()
1571 1571 return ret
1572 1572
1573 1573 def diff(ui, repo, *pats, **opts):
1574 1574 """diff of the current patch"""
1575 1575 repo.mq.diff(repo, pats, opts)
1576 1576 return 0
1577 1577
1578 1578 def fold(ui, repo, *files, **opts):
1579 1579 """fold the named patches into the current patch
1580 1580
1581 1581 Patches must not yet be applied. Each patch will be successively
1582 1582 applied to the current patch in the order given. If all the
1583 1583 patches apply successfully, the current patch will be refreshed
1584 1584 with the new cumulative patch, and the folded patches will
1585 1585 be deleted. With -k/--keep, the folded patch files will not
1586 1586 be removed afterwards.
1587 1587
1588 1588 The header for each folded patch will be concatenated with
1589 1589 the current patch header, separated by a line of '* * *'."""
1590 1590
1591 1591 q = repo.mq
1592 1592
1593 1593 if not files:
1594 1594 raise util.Abort(_('qfold requires at least one patch name'))
1595 1595 if not q.check_toppatch(repo):
1596 1596 raise util.Abort(_('No patches applied'))
1597 1597
1598 1598 message = commands.logmessage(opts)
1599 1599 if opts['edit']:
1600 1600 if message:
1601 1601 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1602 1602
1603 1603 parent = q.lookup('qtip')
1604 1604 patches = []
1605 1605 messages = []
1606 1606 for f in files:
1607 1607 p = q.lookup(f)
1608 1608 if p in patches or p == parent:
1609 1609 ui.warn(_('Skipping already folded patch %s') % p)
1610 1610 if q.isapplied(p):
1611 1611 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1612 1612 patches.append(p)
1613 1613
1614 1614 for p in patches:
1615 1615 if not message:
1616 1616 messages.append(q.readheaders(p)[0])
1617 1617 pf = q.join(p)
1618 1618 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1619 1619 if not patchsuccess:
1620 1620 raise util.Abort(_('Error folding patch %s') % p)
1621 1621 patch.updatedir(ui, repo, files)
1622 1622
1623 1623 if not message:
1624 1624 message, comments, user = q.readheaders(parent)[0:3]
1625 1625 for msg in messages:
1626 1626 message.append('* * *')
1627 1627 message.extend(msg)
1628 1628 message = '\n'.join(message)
1629 1629
1630 1630 if opts['edit']:
1631 1631 message = ui.edit(message, user or ui.username())
1632 1632
1633 1633 q.refresh(repo, msg=message)
1634 1634 q.delete(repo, patches, opts)
1635 1635 q.save_dirty()
1636 1636
1637 1637 def guard(ui, repo, *args, **opts):
1638 1638 '''set or print guards for a patch
1639 1639
1640 1640 Guards control whether a patch can be pushed. A patch with no
1641 1641 guards is always pushed. A patch with a positive guard ("+foo") is
1642 1642 pushed only if the qselect command has activated it. A patch with
1643 1643 a negative guard ("-foo") is never pushed if the qselect command
1644 1644 has activated it.
1645 1645
1646 1646 With no arguments, print the currently active guards.
1647 1647 With arguments, set guards for the named patch.
1648 1648
1649 1649 To set a negative guard "-foo" on topmost patch ("--" is needed so
1650 1650 hg will not interpret "-foo" as an option):
1651 1651 hg qguard -- -foo
1652 1652
1653 1653 To set guards on another patch:
1654 1654 hg qguard other.patch +2.6.17 -stable
1655 1655 '''
1656 1656 def status(idx):
1657 1657 guards = q.series_guards[idx] or ['unguarded']
1658 1658 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1659 1659 q = repo.mq
1660 1660 patch = None
1661 1661 args = list(args)
1662 1662 if opts['list']:
1663 1663 if args or opts['none']:
1664 1664 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1665 1665 for i in xrange(len(q.series)):
1666 1666 status(i)
1667 1667 return
1668 1668 if not args or args[0][0:1] in '-+':
1669 1669 if not q.applied:
1670 1670 raise util.Abort(_('no patches applied'))
1671 1671 patch = q.applied[-1].name
1672 1672 if patch is None and args[0][0:1] not in '-+':
1673 1673 patch = args.pop(0)
1674 1674 if patch is None:
1675 1675 raise util.Abort(_('no patch to work with'))
1676 1676 if args or opts['none']:
1677 1677 q.set_guards(q.find_series(patch), args)
1678 1678 q.save_dirty()
1679 1679 else:
1680 1680 status(q.series.index(q.lookup(patch)))
1681 1681
1682 1682 def header(ui, repo, patch=None):
1683 1683 """Print the header of the topmost or specified patch"""
1684 1684 q = repo.mq
1685 1685
1686 1686 if patch:
1687 1687 patch = q.lookup(patch)
1688 1688 else:
1689 1689 if not q.applied:
1690 1690 ui.write('No patches applied\n')
1691 1691 return 1
1692 1692 patch = q.lookup('qtip')
1693 1693 message = repo.mq.readheaders(patch)[0]
1694 1694
1695 1695 ui.write('\n'.join(message) + '\n')
1696 1696
1697 1697 def lastsavename(path):
1698 1698 (directory, base) = os.path.split(path)
1699 1699 names = os.listdir(directory)
1700 1700 namere = re.compile("%s.([0-9]+)" % base)
1701 1701 maxindex = None
1702 1702 maxname = None
1703 1703 for f in names:
1704 1704 m = namere.match(f)
1705 1705 if m:
1706 1706 index = int(m.group(1))
1707 1707 if maxindex == None or index > maxindex:
1708 1708 maxindex = index
1709 1709 maxname = f
1710 1710 if maxname:
1711 1711 return (os.path.join(directory, maxname), maxindex)
1712 1712 return (None, None)
1713 1713
1714 1714 def savename(path):
1715 1715 (last, index) = lastsavename(path)
1716 1716 if last is None:
1717 1717 index = 0
1718 1718 newpath = path + ".%d" % (index + 1)
1719 1719 return newpath
1720 1720
1721 1721 def push(ui, repo, patch=None, **opts):
1722 1722 """push the next patch onto the stack"""
1723 1723 q = repo.mq
1724 1724 mergeq = None
1725 1725
1726 1726 if opts['all']:
1727 1727 patch = q.series[-1]
1728 1728 if opts['merge']:
1729 1729 if opts['name']:
1730 1730 newpath = opts['name']
1731 1731 else:
1732 1732 newpath, i = lastsavename(q.path)
1733 1733 if not newpath:
1734 1734 ui.warn("no saved queues found, please use -n\n")
1735 1735 return 1
1736 1736 mergeq = queue(ui, repo.join(""), newpath)
1737 1737 ui.warn("merging with queue at: %s\n" % mergeq.path)
1738 1738 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1739 1739 mergeq=mergeq)
1740 1740 q.save_dirty()
1741 1741 return ret
1742 1742
1743 1743 def pop(ui, repo, patch=None, **opts):
1744 1744 """pop the current patch off the stack"""
1745 1745 localupdate = True
1746 1746 if opts['name']:
1747 1747 q = queue(ui, repo.join(""), repo.join(opts['name']))
1748 1748 ui.warn('using patch queue: %s\n' % q.path)
1749 1749 localupdate = False
1750 1750 else:
1751 1751 q = repo.mq
1752 1752 q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
1753 1753 q.save_dirty()
1754 1754 return 0
1755 1755
1756 1756 def rename(ui, repo, patch, name=None, **opts):
1757 1757 """rename a patch
1758 1758
1759 1759 With one argument, renames the current patch to PATCH1.
1760 1760 With two arguments, renames PATCH1 to PATCH2."""
1761 1761
1762 1762 q = repo.mq
1763 1763
1764 1764 if not name:
1765 1765 name = patch
1766 1766 patch = None
1767 1767
1768 1768 if patch:
1769 1769 patch = q.lookup(patch)
1770 1770 else:
1771 1771 if not q.applied:
1772 1772 ui.write(_('No patches applied\n'))
1773 1773 return
1774 1774 patch = q.lookup('qtip')
1775 1775 absdest = q.join(name)
1776 1776 if os.path.isdir(absdest):
1777 1777 name = os.path.join(name, os.path.basename(patch))
1778 1778 absdest = q.join(name)
1779 1779 if os.path.exists(absdest):
1780 1780 raise util.Abort(_('%s already exists') % absdest)
1781 1781
1782 1782 if name in q.series:
1783 1783 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1784 1784
1785 1785 if ui.verbose:
1786 1786 ui.write('Renaming %s to %s\n' % (patch, name))
1787 1787 i = q.find_series(patch)
1788 1788 q.full_series[i] = name
1789 1789 q.parse_series()
1790 1790 q.series_dirty = 1
1791 1791
1792 1792 info = q.isapplied(patch)
1793 1793 if info:
1794 1794 q.applied[info[0]] = statusentry(info[1], name)
1795 1795 q.applied_dirty = 1
1796 1796
1797 1797 util.rename(q.join(patch), absdest)
1798 1798 r = q.qrepo()
1799 1799 if r:
1800 1800 wlock = r.wlock()
1801 1801 if r.dirstate.state(name) == 'r':
1802 1802 r.undelete([name], wlock)
1803 1803 r.copy(patch, name, wlock)
1804 1804 r.remove([patch], False, wlock)
1805 1805
1806 1806 q.save_dirty()
1807 1807
1808 1808 def restore(ui, repo, rev, **opts):
1809 1809 """restore the queue state saved by a rev"""
1810 1810 rev = repo.lookup(rev)
1811 1811 q = repo.mq
1812 1812 q.restore(repo, rev, delete=opts['delete'],
1813 1813 qupdate=opts['update'])
1814 1814 q.save_dirty()
1815 1815 return 0
1816 1816
1817 1817 def save(ui, repo, **opts):
1818 1818 """save current queue state"""
1819 1819 q = repo.mq
1820 1820 message = commands.logmessage(opts)
1821 1821 ret = q.save(repo, msg=message)
1822 1822 if ret:
1823 1823 return ret
1824 1824 q.save_dirty()
1825 1825 if opts['copy']:
1826 1826 path = q.path
1827 1827 if opts['name']:
1828 1828 newpath = os.path.join(q.basepath, opts['name'])
1829 1829 if os.path.exists(newpath):
1830 1830 if not os.path.isdir(newpath):
1831 1831 raise util.Abort(_('destination %s exists and is not '
1832 1832 'a directory') % newpath)
1833 1833 if not opts['force']:
1834 1834 raise util.Abort(_('destination %s exists, '
1835 1835 'use -f to force') % newpath)
1836 1836 else:
1837 1837 newpath = savename(path)
1838 1838 ui.warn("copy %s to %s\n" % (path, newpath))
1839 1839 util.copyfiles(path, newpath)
1840 1840 if opts['empty']:
1841 1841 try:
1842 1842 os.unlink(q.join(q.status_path))
1843 1843 except:
1844 1844 pass
1845 1845 return 0
1846 1846
1847 1847 def strip(ui, repo, rev, **opts):
1848 1848 """strip a revision and all later revs on the same branch"""
1849 1849 rev = repo.lookup(rev)
1850 1850 backup = 'all'
1851 1851 if opts['backup']:
1852 1852 backup = 'strip'
1853 1853 elif opts['nobackup']:
1854 1854 backup = 'none'
1855 1855 update = repo.dirstate.parents()[0] != revlog.nullid
1856 1856 repo.mq.strip(repo, rev, backup=backup, update=update)
1857 1857 return 0
1858 1858
1859 1859 def select(ui, repo, *args, **opts):
1860 1860 '''set or print guarded patches to push
1861 1861
1862 1862 Use the qguard command to set or print guards on patch, then use
1863 1863 qselect to tell mq which guards to use. A patch will be pushed if it
1864 1864 has no guards or any positive guards match the currently selected guard,
1865 1865 but will not be pushed if any negative guards match the current guard.
1866 1866 For example:
1867 1867
1868 1868 qguard foo.patch -stable (negative guard)
1869 1869 qguard bar.patch +stable (positive guard)
1870 1870 qselect stable
1871 1871
1872 1872 This activates the "stable" guard. mq will skip foo.patch (because
1873 1873 it has a negative match) but push bar.patch (because it
1874 1874 has a positive match).
1875 1875
1876 1876 With no arguments, prints the currently active guards.
1877 1877 With one argument, sets the active guard.
1878 1878
1879 1879 Use -n/--none to deactivate guards (no other arguments needed).
1880 1880 When no guards are active, patches with positive guards are skipped
1881 1881 and patches with negative guards are pushed.
1882 1882
1883 1883 qselect can change the guards on applied patches. It does not pop
1884 1884 guarded patches by default. Use --pop to pop back to the last applied
1885 1885 patch that is not guarded. Use --reapply (which implies --pop) to push
1886 1886 back to the current patch afterwards, but skip guarded patches.
1887 1887
1888 1888 Use -s/--series to print a list of all guards in the series file (no
1889 1889 other arguments needed). Use -v for more information.'''
1890 1890
1891 1891 q = repo.mq
1892 1892 guards = q.active()
1893 1893 if args or opts['none']:
1894 1894 old_unapplied = q.unapplied(repo)
1895 1895 old_guarded = [i for i in xrange(len(q.applied)) if
1896 1896 not q.pushable(i)[0]]
1897 1897 q.set_active(args)
1898 1898 q.save_dirty()
1899 1899 if not args:
1900 1900 ui.status(_('guards deactivated\n'))
1901 1901 if not opts['pop'] and not opts['reapply']:
1902 1902 unapplied = q.unapplied(repo)
1903 1903 guarded = [i for i in xrange(len(q.applied))
1904 1904 if not q.pushable(i)[0]]
1905 1905 if len(unapplied) != len(old_unapplied):
1906 1906 ui.status(_('number of unguarded, unapplied patches has '
1907 1907 'changed from %d to %d\n') %
1908 1908 (len(old_unapplied), len(unapplied)))
1909 1909 if len(guarded) != len(old_guarded):
1910 1910 ui.status(_('number of guarded, applied patches has changed '
1911 1911 'from %d to %d\n') %
1912 1912 (len(old_guarded), len(guarded)))
1913 1913 elif opts['series']:
1914 1914 guards = {}
1915 1915 noguards = 0
1916 1916 for gs in q.series_guards:
1917 1917 if not gs:
1918 1918 noguards += 1
1919 1919 for g in gs:
1920 1920 guards.setdefault(g, 0)
1921 1921 guards[g] += 1
1922 1922 if ui.verbose:
1923 1923 guards['NONE'] = noguards
1924 1924 guards = guards.items()
1925 1925 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
1926 1926 if guards:
1927 1927 ui.note(_('guards in series file:\n'))
1928 1928 for guard, count in guards:
1929 1929 ui.note('%2d ' % count)
1930 1930 ui.write(guard, '\n')
1931 1931 else:
1932 1932 ui.note(_('no guards in series file\n'))
1933 1933 else:
1934 1934 if guards:
1935 1935 ui.note(_('active guards:\n'))
1936 1936 for g in guards:
1937 1937 ui.write(g, '\n')
1938 1938 else:
1939 1939 ui.write(_('no active guards\n'))
1940 1940 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
1941 1941 popped = False
1942 1942 if opts['pop'] or opts['reapply']:
1943 1943 for i in xrange(len(q.applied)):
1944 1944 pushable, reason = q.pushable(i)
1945 1945 if not pushable:
1946 1946 ui.status(_('popping guarded patches\n'))
1947 1947 popped = True
1948 1948 if i == 0:
1949 1949 q.pop(repo, all=True)
1950 1950 else:
1951 1951 q.pop(repo, i-1)
1952 1952 break
1953 1953 if popped:
1954 1954 try:
1955 1955 if reapply:
1956 1956 ui.status(_('reapplying unguarded patches\n'))
1957 1957 q.push(repo, reapply)
1958 1958 finally:
1959 1959 q.save_dirty()
1960 1960
1961 1961 def reposetup(ui, repo):
1962 1962 class mqrepo(repo.__class__):
1963 1963 def abort_if_wdir_patched(self, errmsg, force=False):
1964 1964 if self.mq.applied and not force:
1965 1965 parent = revlog.hex(self.dirstate.parents()[0])
1966 1966 if parent in [s.rev for s in self.mq.applied]:
1967 1967 raise util.Abort(errmsg)
1968 1968
1969 1969 def commit(self, *args, **opts):
1970 1970 if len(args) >= 6:
1971 1971 force = args[5]
1972 1972 else:
1973 1973 force = opts.get('force')
1974 1974 self.abort_if_wdir_patched(
1975 1975 _('cannot commit over an applied mq patch'),
1976 1976 force)
1977 1977
1978 1978 return super(mqrepo, self).commit(*args, **opts)
1979 1979
1980 1980 def push(self, remote, force=False, revs=None):
1981 1981 if self.mq.applied and not force:
1982 1982 raise util.Abort(_('source has mq patches applied'))
1983 1983 return super(mqrepo, self).push(remote, force, revs)
1984 1984
1985 1985 def tags(self):
1986 1986 if self.tagscache:
1987 1987 return self.tagscache
1988 1988
1989 1989 tagscache = super(mqrepo, self).tags()
1990 1990
1991 1991 q = self.mq
1992 1992 if not q.applied:
1993 1993 return tagscache
1994 1994
1995 1995 mqtags = [(patch.rev, patch.name) for patch in q.applied]
1996 1996 mqtags.append((mqtags[-1][0], 'qtip'))
1997 1997 mqtags.append((mqtags[0][0], 'qbase'))
1998 1998 for patch in mqtags:
1999 1999 if patch[1] in tagscache:
2000 2000 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2001 2001 else:
2002 2002 tagscache[patch[1]] = revlog.bin(patch[0])
2003 2003
2004 2004 return tagscache
2005 2005
2006 2006 def branchtags(self):
2007 2007 if self.branchcache != None:
2008 2008 return self.branchcache
2009 2009
2010 2010 q = self.mq
2011 2011 if not q.applied:
2012 2012 return super(mqrepo, self).branchtags()
2013 2013
2014 2014 self.branchcache = {} # avoid recursion in changectx
2015 2015 cl = self.changelog
2016 2016 partial, last, lrev = self._readbranchcache()
2017 2017
2018 2018 qbase = cl.rev(revlog.bin(q.applied[0].rev))
2019 2019 start = lrev + 1
2020 2020 if start < qbase:
2021 2021 # update the cache (excluding the patches) and save it
2022 2022 self._updatebranchcache(partial, lrev+1, qbase)
2023 2023 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2024 2024 start = qbase
2025 2025 # if start = qbase, the cache is as updated as it should be.
2026 2026 # if start > qbase, the cache includes (part of) the patches.
2027 2027 # we might as well use it, but we won't save it.
2028 2028
2029 2029 # update the cache up to the tip
2030 2030 self._updatebranchcache(partial, start, cl.count())
2031 2031
2032 2032 self.branchcache = partial
2033 2033 return self.branchcache
2034 2034
2035 2035 if repo.local():
2036 2036 repo.__class__ = mqrepo
2037 2037 repo.mq = queue(ui, repo.join(""))
2038 2038
2039 2039 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2040 2040
2041 2041 cmdtable = {
2042 2042 "qapplied": (applied, [] + seriesopts, 'hg qapplied [-s] [PATCH]'),
2043 2043 "qclone": (clone,
2044 2044 [('', 'pull', None, _('use pull protocol to copy metadata')),
2045 2045 ('U', 'noupdate', None, _('do not update the new working directories')),
2046 2046 ('', 'uncompressed', None,
2047 2047 _('use uncompressed transfer (fast over LAN)')),
2048 2048 ('e', 'ssh', '', _('specify ssh command to use')),
2049 2049 ('p', 'patches', '', _('location of source patch repo')),
2050 2050 ('', 'remotecmd', '',
2051 2051 _('specify hg command to run on the remote side'))],
2052 2052 'hg qclone [OPTION]... SOURCE [DEST]'),
2053 2053 "qcommit|qci":
2054 2054 (commit,
2055 2055 commands.table["^commit|ci"][1],
2056 2056 'hg qcommit [OPTION]... [FILE]...'),
2057 2057 "^qdiff": (diff,
2058 2058 [('I', 'include', [], _('include names matching the given patterns')),
2059 2059 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2060 2060 'hg qdiff [-I] [-X] [FILE]...'),
2061 2061 "qdelete|qremove|qrm":
2062 2062 (delete,
2063 2063 [('k', 'keep', None, _('keep patch file')),
2064 2064 ('r', 'rev', [], _('stop managing a revision'))],
2065 2065 'hg qdelete [-k] [-r REV]... PATCH...'),
2066 2066 'qfold':
2067 2067 (fold,
2068 2068 [('e', 'edit', None, _('edit patch header')),
2069 2069 ('k', 'keep', None, _('keep folded patch files')),
2070 2070 ('m', 'message', '', _('set patch header to <text>')),
2071 2071 ('l', 'logfile', '', _('set patch header to contents of <file>'))],
2072 2072 'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
2073 2073 'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
2074 2074 ('n', 'none', None, _('drop all guards'))],
2075 2075 'hg qguard [PATCH] [+GUARD...] [-GUARD...]'),
2076 2076 'qheader': (header, [],
2077 2077 _('hg qheader [PATCH]')),
2078 2078 "^qimport":
2079 2079 (qimport,
2080 2080 [('e', 'existing', None, 'import file in patch dir'),
2081 2081 ('n', 'name', '', 'patch file name'),
2082 2082 ('f', 'force', None, 'overwrite existing files'),
2083 2083 ('r', 'rev', [], 'place existing revisions under mq control')],
2084 2084 'hg qimport [-e] [-n NAME] [-f] [-r REV]... FILE...'),
2085 2085 "^qinit":
2086 2086 (init,
2087 2087 [('c', 'create-repo', None, 'create queue repository')],
2088 2088 'hg qinit [-c]'),
2089 2089 "qnew":
2090 2090 (new,
2091 2091 [('e', 'edit', None, _('edit commit message')),
2092 2092 ('m', 'message', '', _('use <text> as commit message')),
2093 2093 ('l', 'logfile', '', _('read the commit message from <file>')),
2094 2094 ('f', 'force', None, _('import uncommitted changes into patch'))],
2095 2095 'hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH'),
2096 2096 "qnext": (next, [] + seriesopts, 'hg qnext [-s]'),
2097 2097 "qprev": (prev, [] + seriesopts, 'hg qprev [-s]'),
2098 2098 "^qpop":
2099 2099 (pop,
2100 2100 [('a', 'all', None, 'pop all patches'),
2101 2101 ('n', 'name', '', 'queue name to pop'),
2102 2102 ('f', 'force', None, 'forget any local changes')],
2103 2103 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'),
2104 2104 "^qpush":
2105 2105 (push,
2106 2106 [('f', 'force', None, 'apply if the patch has rejects'),
2107 2107 ('l', 'list', None, 'list patch name in commit text'),
2108 2108 ('a', 'all', None, 'apply all patches'),
2109 2109 ('m', 'merge', None, 'merge from another queue'),
2110 2110 ('n', 'name', '', 'merge queue name')],
2111 2111 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'),
2112 2112 "^qrefresh":
2113 2113 (refresh,
2114 2114 [('e', 'edit', None, _('edit commit message')),
2115 2115 ('m', 'message', '', _('change commit message with <text>')),
2116 2116 ('l', 'logfile', '', _('change commit message with <file> content')),
2117 2117 ('g', 'git', None, _('use git extended diff format')),
2118 2118 ('s', 'short', None, 'short refresh'),
2119 2119 ('I', 'include', [], _('include names matching the given patterns')),
2120 2120 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2121 2121 'hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] FILES...'),
2122 2122 'qrename|qmv':
2123 2123 (rename, [], 'hg qrename PATCH1 [PATCH2]'),
2124 2124 "qrestore":
2125 2125 (restore,
2126 2126 [('d', 'delete', None, 'delete save entry'),
2127 2127 ('u', 'update', None, 'update queue working dir')],
2128 2128 'hg qrestore [-d] [-u] REV'),
2129 2129 "qsave":
2130 2130 (save,
2131 2131 [('m', 'message', '', _('use <text> as commit message')),
2132 2132 ('l', 'logfile', '', _('read the commit message from <file>')),
2133 2133 ('c', 'copy', None, 'copy patch directory'),
2134 2134 ('n', 'name', '', 'copy directory name'),
2135 2135 ('e', 'empty', None, 'clear queue status file'),
2136 2136 ('f', 'force', None, 'force copy')],
2137 2137 'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
2138 2138 "qselect": (select,
2139 2139 [('n', 'none', None, _('disable all guards')),
2140 2140 ('s', 'series', None, _('list all guards in series file')),
2141 2141 ('', 'pop', None,
2142 2142 _('pop to before first guarded applied patch')),
2143 2143 ('', 'reapply', None, _('pop, then reapply patches'))],
2144 2144 'hg qselect [OPTION...] [GUARD...]'),
2145 2145 "qseries":
2146 2146 (series,
2147 2147 [('m', 'missing', None, 'print patches not in series')] + seriesopts,
2148 2148 'hg qseries [-ms]'),
2149 2149 "^strip":
2150 2150 (strip,
2151 2151 [('f', 'force', None, 'force multi-head removal'),
2152 2152 ('b', 'backup', None, 'bundle unrelated changesets'),
2153 2153 ('n', 'nobackup', None, 'no backups')],
2154 2154 'hg strip [-f] [-b] [-n] REV'),
2155 2155 "qtop": (top, [] + seriesopts, 'hg qtop [-s]'),
2156 2156 "qunapplied": (unapplied, [] + seriesopts, 'hg qunapplied [-s] [PATCH]'),
2157 2157 }
@@ -1,202 +1,204 b''
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import demandload
9 9 from node import *
10 10 from i18n import gettext as _
11 11 demandload(globals(), 'mdiff util')
12 12 demandload(globals(), 'os sys')
13 13
14 14 revrangesep = ':'
15 15
16 16 def revpair(ui, repo, revs):
17 17 '''return pair of nodes, given list of revisions. second item can
18 18 be None, meaning use working dir.'''
19 19
20 20 def revfix(repo, val, defval):
21 21 if not val and val != 0:
22 22 val = defval
23 23 return repo.lookup(val)
24 24
25 25 if not revs:
26 26 return repo.dirstate.parents()[0], None
27 27 end = None
28 28 if len(revs) == 1:
29 29 if revrangesep in revs[0]:
30 30 start, end = revs[0].split(revrangesep, 1)
31 31 start = revfix(repo, start, 0)
32 32 end = revfix(repo, end, repo.changelog.count() - 1)
33 33 else:
34 34 start = revfix(repo, revs[0], None)
35 35 elif len(revs) == 2:
36 36 if revrangesep in revs[0] or revrangesep in revs[1]:
37 37 raise util.Abort(_('too many revisions specified'))
38 38 start = revfix(repo, revs[0], None)
39 39 end = revfix(repo, revs[1], None)
40 40 else:
41 41 raise util.Abort(_('too many revisions specified'))
42 42 return start, end
43 43
44 44 def revrange(ui, repo, revs):
45 45 """Yield revision as strings from a list of revision specifications."""
46 46
47 47 def revfix(repo, val, defval):
48 48 if not val and val != 0:
49 49 return defval
50 50 return repo.changelog.rev(repo.lookup(val))
51 51
52 seen = {}
52 seen, l = {}, []
53 53 for spec in revs:
54 54 if revrangesep in spec:
55 55 start, end = spec.split(revrangesep, 1)
56 56 start = revfix(repo, start, 0)
57 57 end = revfix(repo, end, repo.changelog.count() - 1)
58 58 step = start > end and -1 or 1
59 59 for rev in xrange(start, end+step, step):
60 60 if rev in seen:
61 61 continue
62 62 seen[rev] = 1
63 yield rev
63 l.append(rev)
64 64 else:
65 65 rev = revfix(repo, spec, None)
66 66 if rev in seen:
67 67 continue
68 68 seen[rev] = 1
69 yield rev
69 l.append(rev)
70
71 return l
70 72
71 73 def make_filename(repo, pat, node,
72 74 total=None, seqno=None, revwidth=None, pathname=None):
73 75 node_expander = {
74 76 'H': lambda: hex(node),
75 77 'R': lambda: str(repo.changelog.rev(node)),
76 78 'h': lambda: short(node),
77 79 }
78 80 expander = {
79 81 '%': lambda: '%',
80 82 'b': lambda: os.path.basename(repo.root),
81 83 }
82 84
83 85 try:
84 86 if node:
85 87 expander.update(node_expander)
86 88 if node and revwidth is not None:
87 89 expander['r'] = (lambda:
88 90 str(repo.changelog.rev(node)).zfill(revwidth))
89 91 if total is not None:
90 92 expander['N'] = lambda: str(total)
91 93 if seqno is not None:
92 94 expander['n'] = lambda: str(seqno)
93 95 if total is not None and seqno is not None:
94 96 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
95 97 if pathname is not None:
96 98 expander['s'] = lambda: os.path.basename(pathname)
97 99 expander['d'] = lambda: os.path.dirname(pathname) or '.'
98 100 expander['p'] = lambda: pathname
99 101
100 102 newname = []
101 103 patlen = len(pat)
102 104 i = 0
103 105 while i < patlen:
104 106 c = pat[i]
105 107 if c == '%':
106 108 i += 1
107 109 c = pat[i]
108 110 c = expander[c]()
109 111 newname.append(c)
110 112 i += 1
111 113 return ''.join(newname)
112 114 except KeyError, inst:
113 115 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
114 116 inst.args[0])
115 117
116 118 def make_file(repo, pat, node=None,
117 119 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
118 120 if not pat or pat == '-':
119 121 return 'w' in mode and sys.stdout or sys.stdin
120 122 if hasattr(pat, 'write') and 'w' in mode:
121 123 return pat
122 124 if hasattr(pat, 'read') and 'r' in mode:
123 125 return pat
124 126 return open(make_filename(repo, pat, node, total, seqno, revwidth,
125 127 pathname),
126 128 mode)
127 129
128 130 def matchpats(repo, pats=[], opts={}, head=''):
129 131 cwd = repo.getcwd()
130 132 if not pats and cwd:
131 133 opts['include'] = [os.path.join(cwd, i)
132 134 for i in opts.get('include', [])]
133 135 opts['exclude'] = [os.path.join(cwd, x)
134 136 for x in opts.get('exclude', [])]
135 137 cwd = ''
136 138 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
137 139 opts.get('exclude'), head)
138 140
139 141 def makewalk(repo, pats=[], opts={}, node=None, head='', badmatch=None):
140 142 files, matchfn, anypats = matchpats(repo, pats, opts, head)
141 143 exact = dict(zip(files, files))
142 144 def walk():
143 145 for src, fn in repo.walk(node=node, files=files, match=matchfn,
144 146 badmatch=badmatch):
145 147 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
146 148 return files, matchfn, walk()
147 149
148 150 def walk(repo, pats=[], opts={}, node=None, head='', badmatch=None):
149 151 files, matchfn, results = makewalk(repo, pats, opts, node, head, badmatch)
150 152 for r in results:
151 153 yield r
152 154
153 155 def findrenames(repo, added=None, removed=None, threshold=0.5):
154 156 if added is None or removed is None:
155 157 added, removed = repo.status()[1:3]
156 158 changes = repo.changelog.read(repo.dirstate.parents()[0])
157 159 mf = repo.manifest.read(changes[0])
158 160 for a in added:
159 161 aa = repo.wread(a)
160 162 bestscore, bestname = None, None
161 163 for r in removed:
162 164 rr = repo.file(r).read(mf[r])
163 165 delta = mdiff.textdiff(aa, rr)
164 166 if len(delta) < len(aa):
165 167 myscore = 1.0 - (float(len(delta)) / len(aa))
166 168 if bestscore is None or myscore > bestscore:
167 169 bestscore, bestname = myscore, r
168 170 if bestname and bestscore >= threshold:
169 171 yield bestname, a, bestscore
170 172
171 173 def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None,
172 174 similarity=None):
173 175 if dry_run is None:
174 176 dry_run = opts.get('dry_run')
175 177 if similarity is None:
176 178 similarity = float(opts.get('similarity') or 0)
177 179 add, remove = [], []
178 180 mapping = {}
179 181 for src, abs, rel, exact in walk(repo, pats, opts):
180 182 if src == 'f' and repo.dirstate.state(abs) == '?':
181 183 add.append(abs)
182 184 mapping[abs] = rel, exact
183 185 if repo.ui.verbose or not exact:
184 186 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
185 187 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
186 188 remove.append(abs)
187 189 mapping[abs] = rel, exact
188 190 if repo.ui.verbose or not exact:
189 191 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
190 192 if not dry_run:
191 193 repo.add(add, wlock=wlock)
192 194 repo.remove(remove, wlock=wlock)
193 195 if similarity > 0:
194 196 for old, new, score in findrenames(repo, add, remove, similarity):
195 197 oldrel, oldexact = mapping[old]
196 198 newrel, newexact = mapping[new]
197 199 if repo.ui.verbose or not oldexact or not newexact:
198 200 repo.ui.status(_('recording removal of %s as rename to %s '
199 201 '(%d%% similar)\n') %
200 202 (oldrel, newrel, score * 100))
201 203 if not dry_run:
202 204 repo.copy(old, new, wlock=wlock)
@@ -1,3522 +1,3521 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import demandload
9 9 from node import *
10 10 from i18n import gettext as _
11 11 demandload(globals(), "os re sys signal shutil imp urllib pdb shlex")
12 12 demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo")
13 13 demandload(globals(), "fnmatch difflib patch random signal tempfile time")
14 14 demandload(globals(), "traceback errno socket version struct atexit sets bz2")
15 15 demandload(globals(), "archival cStringIO changegroup")
16 16 demandload(globals(), "cmdutil hgweb.server sshserver")
17 17
18 18 class UnknownCommand(Exception):
19 19 """Exception raised if command is not in the command table."""
20 20 class AmbiguousCommand(Exception):
21 21 """Exception raised if command shortcut matches more than one command."""
22 22
23 23 def bail_if_changed(repo):
24 24 modified, added, removed, deleted = repo.status()[:4]
25 25 if modified or added or removed or deleted:
26 26 raise util.Abort(_("outstanding uncommitted changes"))
27 27
28 28 def relpath(repo, args):
29 29 cwd = repo.getcwd()
30 30 if cwd:
31 31 return [util.normpath(os.path.join(cwd, x)) for x in args]
32 32 return args
33 33
34 34 def logmessage(opts):
35 35 """ get the log message according to -m and -l option """
36 36 message = opts['message']
37 37 logfile = opts['logfile']
38 38
39 39 if message and logfile:
40 40 raise util.Abort(_('options --message and --logfile are mutually '
41 41 'exclusive'))
42 42 if not message and logfile:
43 43 try:
44 44 if logfile == '-':
45 45 message = sys.stdin.read()
46 46 else:
47 47 message = open(logfile).read()
48 48 except IOError, inst:
49 49 raise util.Abort(_("can't read commit message '%s': %s") %
50 50 (logfile, inst.strerror))
51 51 return message
52 52
53 53 def walkchangerevs(ui, repo, pats, opts):
54 54 '''Iterate over files and the revs they changed in.
55 55
56 56 Callers most commonly need to iterate backwards over the history
57 57 it is interested in. Doing so has awful (quadratic-looking)
58 58 performance, so we use iterators in a "windowed" way.
59 59
60 60 We walk a window of revisions in the desired order. Within the
61 61 window, we first walk forwards to gather data, then in the desired
62 62 order (usually backwards) to display it.
63 63
64 64 This function returns an (iterator, getchange, matchfn) tuple. The
65 65 getchange function returns the changelog entry for a numeric
66 66 revision. The iterator yields 3-tuples. They will be of one of
67 67 the following forms:
68 68
69 69 "window", incrementing, lastrev: stepping through a window,
70 70 positive if walking forwards through revs, last rev in the
71 71 sequence iterated over - use to reset state for the current window
72 72
73 73 "add", rev, fns: out-of-order traversal of the given file names
74 74 fns, which changed during revision rev - use to gather data for
75 75 possible display
76 76
77 77 "iter", rev, None: in-order traversal of the revs earlier iterated
78 78 over with "add" - use to display data'''
79 79
80 80 def increasing_windows(start, end, windowsize=8, sizelimit=512):
81 81 if start < end:
82 82 while start < end:
83 83 yield start, min(windowsize, end-start)
84 84 start += windowsize
85 85 if windowsize < sizelimit:
86 86 windowsize *= 2
87 87 else:
88 88 while start > end:
89 89 yield start, min(windowsize, start-end-1)
90 90 start -= windowsize
91 91 if windowsize < sizelimit:
92 92 windowsize *= 2
93 93
94 94
95 95 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
96 96 follow = opts.get('follow') or opts.get('follow_first')
97 97
98 98 if repo.changelog.count() == 0:
99 99 return [], False, matchfn
100 100
101 101 if follow:
102 102 defrange = '%s:0' % repo.changectx().rev()
103 103 else:
104 104 defrange = 'tip:0'
105 revs = map(int, cmdutil.revrange(ui, repo, opts['rev'] or [defrange]))
105 revs = cmdutil.revrange(ui, repo, opts['rev'] or [defrange])
106 106 wanted = {}
107 107 slowpath = anypats
108 108 fncache = {}
109 109
110 110 chcache = {}
111 111 def getchange(rev):
112 112 ch = chcache.get(rev)
113 113 if ch is None:
114 114 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
115 115 return ch
116 116
117 117 if not slowpath and not files:
118 118 # No files, no patterns. Display all revs.
119 119 wanted = dict(zip(revs, revs))
120 120 copies = []
121 121 if not slowpath:
122 122 # Only files, no patterns. Check the history of each file.
123 123 def filerevgen(filelog, node):
124 124 cl_count = repo.changelog.count()
125 125 if node is None:
126 126 last = filelog.count() - 1
127 127 else:
128 128 last = filelog.rev(node)
129 129 for i, window in increasing_windows(last, -1):
130 130 revs = []
131 131 for j in xrange(i - window, i + 1):
132 132 n = filelog.node(j)
133 133 revs.append((filelog.linkrev(n),
134 134 follow and filelog.renamed(n)))
135 135 revs.reverse()
136 136 for rev in revs:
137 137 # only yield rev for which we have the changelog, it can
138 138 # happen while doing "hg log" during a pull or commit
139 139 if rev[0] < cl_count:
140 140 yield rev
141 141 def iterfiles():
142 142 for filename in files:
143 143 yield filename, None
144 144 for filename_node in copies:
145 145 yield filename_node
146 146 minrev, maxrev = min(revs), max(revs)
147 147 for file_, node in iterfiles():
148 148 filelog = repo.file(file_)
149 149 # A zero count may be a directory or deleted file, so
150 150 # try to find matching entries on the slow path.
151 151 if filelog.count() == 0:
152 152 slowpath = True
153 153 break
154 154 for rev, copied in filerevgen(filelog, node):
155 155 if rev <= maxrev:
156 156 if rev < minrev:
157 157 break
158 158 fncache.setdefault(rev, [])
159 159 fncache[rev].append(file_)
160 160 wanted[rev] = 1
161 161 if follow and copied:
162 162 copies.append(copied)
163 163 if slowpath:
164 164 if follow:
165 165 raise util.Abort(_('can only follow copies/renames for explicit '
166 166 'file names'))
167 167
168 168 # The slow path checks files modified in every changeset.
169 169 def changerevgen():
170 170 for i, window in increasing_windows(repo.changelog.count()-1, -1):
171 171 for j in xrange(i - window, i + 1):
172 172 yield j, getchange(j)[3]
173 173
174 174 for rev, changefiles in changerevgen():
175 175 matches = filter(matchfn, changefiles)
176 176 if matches:
177 177 fncache[rev] = matches
178 178 wanted[rev] = 1
179 179
180 180 class followfilter:
181 181 def __init__(self, onlyfirst=False):
182 182 self.startrev = -1
183 183 self.roots = []
184 184 self.onlyfirst = onlyfirst
185 185
186 186 def match(self, rev):
187 187 def realparents(rev):
188 188 if self.onlyfirst:
189 189 return repo.changelog.parentrevs(rev)[0:1]
190 190 else:
191 191 return filter(lambda x: x != -1, repo.changelog.parentrevs(rev))
192 192
193 193 if self.startrev == -1:
194 194 self.startrev = rev
195 195 return True
196 196
197 197 if rev > self.startrev:
198 198 # forward: all descendants
199 199 if not self.roots:
200 200 self.roots.append(self.startrev)
201 201 for parent in realparents(rev):
202 202 if parent in self.roots:
203 203 self.roots.append(rev)
204 204 return True
205 205 else:
206 206 # backwards: all parents
207 207 if not self.roots:
208 208 self.roots.extend(realparents(self.startrev))
209 209 if rev in self.roots:
210 210 self.roots.remove(rev)
211 211 self.roots.extend(realparents(rev))
212 212 return True
213 213
214 214 return False
215 215
216 216 # it might be worthwhile to do this in the iterator if the rev range
217 217 # is descending and the prune args are all within that range
218 218 for rev in opts.get('prune', ()):
219 219 rev = repo.changelog.rev(repo.lookup(rev))
220 220 ff = followfilter()
221 221 stop = min(revs[0], revs[-1])
222 222 for x in xrange(rev, stop-1, -1):
223 223 if ff.match(x) and wanted.has_key(x):
224 224 del wanted[x]
225 225
226 226 def iterate():
227 227 if follow and not files:
228 228 ff = followfilter(onlyfirst=opts.get('follow_first'))
229 229 def want(rev):
230 230 if ff.match(rev) and rev in wanted:
231 231 return True
232 232 return False
233 233 else:
234 234 def want(rev):
235 235 return rev in wanted
236 236
237 237 for i, window in increasing_windows(0, len(revs)):
238 238 yield 'window', revs[0] < revs[-1], revs[-1]
239 239 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
240 240 srevs = list(nrevs)
241 241 srevs.sort()
242 242 for rev in srevs:
243 243 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
244 244 yield 'add', rev, fns
245 245 for rev in nrevs:
246 246 yield 'iter', rev, None
247 247 return iterate(), getchange, matchfn
248 248
249 249 def write_bundle(cg, filename=None, compress=True):
250 250 """Write a bundle file and return its filename.
251 251
252 252 Existing files will not be overwritten.
253 253 If no filename is specified, a temporary file is created.
254 254 bz2 compression can be turned off.
255 255 The bundle file will be deleted in case of errors.
256 256 """
257 257 class nocompress(object):
258 258 def compress(self, x):
259 259 return x
260 260 def flush(self):
261 261 return ""
262 262
263 263 fh = None
264 264 cleanup = None
265 265 try:
266 266 if filename:
267 267 if os.path.exists(filename):
268 268 raise util.Abort(_("file '%s' already exists") % filename)
269 269 fh = open(filename, "wb")
270 270 else:
271 271 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
272 272 fh = os.fdopen(fd, "wb")
273 273 cleanup = filename
274 274
275 275 if compress:
276 276 fh.write("HG10")
277 277 z = bz2.BZ2Compressor(9)
278 278 else:
279 279 fh.write("HG10UN")
280 280 z = nocompress()
281 281 # parse the changegroup data, otherwise we will block
282 282 # in case of sshrepo because we don't know the end of the stream
283 283
284 284 # an empty chunkiter is the end of the changegroup
285 285 empty = False
286 286 while not empty:
287 287 empty = True
288 288 for chunk in changegroup.chunkiter(cg):
289 289 empty = False
290 290 fh.write(z.compress(changegroup.genchunk(chunk)))
291 291 fh.write(z.compress(changegroup.closechunk()))
292 292 fh.write(z.flush())
293 293 cleanup = None
294 294 return filename
295 295 finally:
296 296 if fh is not None:
297 297 fh.close()
298 298 if cleanup is not None:
299 299 os.unlink(cleanup)
300 300
301 301 class changeset_printer(object):
302 302 '''show changeset information when templating not requested.'''
303 303
304 304 def __init__(self, ui, repo):
305 305 self.ui = ui
306 306 self.repo = repo
307 307
308 308 def show(self, rev=0, changenode=None, brinfo=None, copies=None):
309 309 '''show a single changeset or file revision'''
310 310 log = self.repo.changelog
311 311 if changenode is None:
312 312 changenode = log.node(rev)
313 313 elif not rev:
314 314 rev = log.rev(changenode)
315 315
316 316 if self.ui.quiet:
317 317 self.ui.write("%d:%s\n" % (rev, short(changenode)))
318 318 return
319 319
320 320 changes = log.read(changenode)
321 321 date = util.datestr(changes[2])
322 322 extra = changes[5]
323 323 branch = extra.get("branch")
324 324
325 325 hexfunc = self.ui.debugflag and hex or short
326 326
327 327 parents = [(log.rev(p), hexfunc(p)) for p in log.parents(changenode)
328 328 if self.ui.debugflag or p != nullid]
329 329 if (not self.ui.debugflag and len(parents) == 1 and
330 330 parents[0][0] == rev-1):
331 331 parents = []
332 332
333 333 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
334 334
335 335 if branch:
336 336 self.ui.status(_("branch: %s\n") % branch)
337 337 for tag in self.repo.nodetags(changenode):
338 338 self.ui.status(_("tag: %s\n") % tag)
339 339 for parent in parents:
340 340 self.ui.write(_("parent: %d:%s\n") % parent)
341 341
342 342 if brinfo and changenode in brinfo:
343 343 br = brinfo[changenode]
344 344 self.ui.write(_("branch: %s\n") % " ".join(br))
345 345
346 346 self.ui.debug(_("manifest: %d:%s\n") %
347 347 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
348 348 self.ui.status(_("user: %s\n") % changes[1])
349 349 self.ui.status(_("date: %s\n") % date)
350 350
351 351 if self.ui.debugflag:
352 352 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
353 353 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
354 354 files):
355 355 if value:
356 356 self.ui.note("%-12s %s\n" % (key, " ".join(value)))
357 357 elif changes[3]:
358 358 self.ui.note(_("files: %s\n") % " ".join(changes[3]))
359 359 if copies:
360 360 copies = ['%s (%s)' % c for c in copies]
361 361 self.ui.note(_("copies: %s\n") % ' '.join(copies))
362 362
363 363 if extra and self.ui.debugflag:
364 364 extraitems = extra.items()
365 365 extraitems.sort()
366 366 for key, value in extraitems:
367 367 self.ui.debug(_("extra: %s=%s\n")
368 368 % (key, value.encode('string_escape')))
369 369
370 370 description = changes[4].strip()
371 371 if description:
372 372 if self.ui.verbose:
373 373 self.ui.status(_("description:\n"))
374 374 self.ui.status(description)
375 375 self.ui.status("\n\n")
376 376 else:
377 377 self.ui.status(_("summary: %s\n") %
378 378 description.splitlines()[0])
379 379 self.ui.status("\n")
380 380
381 381 def show_changeset(ui, repo, opts):
382 382 """show one changeset using template or regular display.
383 383
384 384 Display format will be the first non-empty hit of:
385 385 1. option 'template'
386 386 2. option 'style'
387 387 3. [ui] setting 'logtemplate'
388 388 4. [ui] setting 'style'
389 389 If all of these values are either the unset or the empty string,
390 390 regular display via changeset_printer() is done.
391 391 """
392 392 # options
393 393 tmpl = opts.get('template')
394 394 mapfile = None
395 395 if tmpl:
396 396 tmpl = templater.parsestring(tmpl, quoted=False)
397 397 else:
398 398 mapfile = opts.get('style')
399 399 # ui settings
400 400 if not mapfile:
401 401 tmpl = ui.config('ui', 'logtemplate')
402 402 if tmpl:
403 403 tmpl = templater.parsestring(tmpl)
404 404 else:
405 405 mapfile = ui.config('ui', 'style')
406 406
407 407 if tmpl or mapfile:
408 408 if mapfile:
409 409 if not os.path.split(mapfile)[0]:
410 410 mapname = (templater.templatepath('map-cmdline.' + mapfile)
411 411 or templater.templatepath(mapfile))
412 412 if mapname: mapfile = mapname
413 413 try:
414 414 t = templater.changeset_templater(ui, repo, mapfile)
415 415 except SyntaxError, inst:
416 416 raise util.Abort(inst.args[0])
417 417 if tmpl: t.use_template(tmpl)
418 418 return t
419 419 return changeset_printer(ui, repo)
420 420
421 421 def setremoteconfig(ui, opts):
422 422 "copy remote options to ui tree"
423 423 if opts.get('ssh'):
424 424 ui.setconfig("ui", "ssh", opts['ssh'])
425 425 if opts.get('remotecmd'):
426 426 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
427 427
428 428 def show_version(ui):
429 429 """output version and copyright information"""
430 430 ui.write(_("Mercurial Distributed SCM (version %s)\n")
431 431 % version.get_version())
432 432 ui.status(_(
433 433 "\nCopyright (C) 2005, 2006 Matt Mackall <mpm@selenic.com>\n"
434 434 "This is free software; see the source for copying conditions. "
435 435 "There is NO\nwarranty; "
436 436 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
437 437 ))
438 438
439 439 def help_(ui, name=None, with_version=False):
440 440 """show help for a command, extension, or list of commands
441 441
442 442 With no arguments, print a list of commands and short help.
443 443
444 444 Given a command name, print help for that command.
445 445
446 446 Given an extension name, print help for that extension, and the
447 447 commands it provides."""
448 448 option_lists = []
449 449
450 450 def helpcmd(name):
451 451 if with_version:
452 452 show_version(ui)
453 453 ui.write('\n')
454 454 aliases, i = findcmd(ui, name)
455 455 # synopsis
456 456 ui.write("%s\n\n" % i[2])
457 457
458 458 # description
459 459 doc = i[0].__doc__
460 460 if not doc:
461 461 doc = _("(No help text available)")
462 462 if ui.quiet:
463 463 doc = doc.splitlines(0)[0]
464 464 ui.write("%s\n" % doc.rstrip())
465 465
466 466 if not ui.quiet:
467 467 # aliases
468 468 if len(aliases) > 1:
469 469 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
470 470
471 471 # options
472 472 if i[1]:
473 473 option_lists.append(("options", i[1]))
474 474
475 475 def helplist(select=None):
476 476 h = {}
477 477 cmds = {}
478 478 for c, e in table.items():
479 479 f = c.split("|", 1)[0]
480 480 if select and not select(f):
481 481 continue
482 482 if name == "shortlist" and not f.startswith("^"):
483 483 continue
484 484 f = f.lstrip("^")
485 485 if not ui.debugflag and f.startswith("debug"):
486 486 continue
487 487 doc = e[0].__doc__
488 488 if not doc:
489 489 doc = _("(No help text available)")
490 490 h[f] = doc.splitlines(0)[0].rstrip()
491 491 cmds[f] = c.lstrip("^")
492 492
493 493 fns = h.keys()
494 494 fns.sort()
495 495 m = max(map(len, fns))
496 496 for f in fns:
497 497 if ui.verbose:
498 498 commands = cmds[f].replace("|",", ")
499 499 ui.write(" %s:\n %s\n"%(commands, h[f]))
500 500 else:
501 501 ui.write(' %-*s %s\n' % (m, f, h[f]))
502 502
503 503 def helpext(name):
504 504 try:
505 505 mod = findext(name)
506 506 except KeyError:
507 507 raise UnknownCommand(name)
508 508
509 509 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
510 510 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
511 511 for d in doc[1:]:
512 512 ui.write(d, '\n')
513 513
514 514 ui.status('\n')
515 515 if ui.verbose:
516 516 ui.status(_('list of commands:\n\n'))
517 517 else:
518 518 ui.status(_('list of commands (use "hg help -v %s" '
519 519 'to show aliases and global options):\n\n') % name)
520 520
521 521 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in mod.cmdtable])
522 522 helplist(modcmds.has_key)
523 523
524 524 if name and name != 'shortlist':
525 525 try:
526 526 helpcmd(name)
527 527 except UnknownCommand:
528 528 helpext(name)
529 529
530 530 else:
531 531 # program name
532 532 if ui.verbose or with_version:
533 533 show_version(ui)
534 534 else:
535 535 ui.status(_("Mercurial Distributed SCM\n"))
536 536 ui.status('\n')
537 537
538 538 # list of commands
539 539 if name == "shortlist":
540 540 ui.status(_('basic commands (use "hg help" '
541 541 'for the full list or option "-v" for details):\n\n'))
542 542 elif ui.verbose:
543 543 ui.status(_('list of commands:\n\n'))
544 544 else:
545 545 ui.status(_('list of commands (use "hg help -v" '
546 546 'to show aliases and global options):\n\n'))
547 547
548 548 helplist()
549 549
550 550 # global options
551 551 if ui.verbose:
552 552 option_lists.append(("global options", globalopts))
553 553
554 554 # list all option lists
555 555 opt_output = []
556 556 for title, options in option_lists:
557 557 opt_output.append(("\n%s:\n" % title, None))
558 558 for shortopt, longopt, default, desc in options:
559 559 if "DEPRECATED" in desc and not ui.verbose: continue
560 560 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
561 561 longopt and " --%s" % longopt),
562 562 "%s%s" % (desc,
563 563 default
564 564 and _(" (default: %s)") % default
565 565 or "")))
566 566
567 567 if opt_output:
568 568 opts_len = max([len(line[0]) for line in opt_output if line[1]])
569 569 for first, second in opt_output:
570 570 if second:
571 571 ui.write(" %-*s %s\n" % (opts_len, first, second))
572 572 else:
573 573 ui.write("%s\n" % first)
574 574
575 575 # Commands start here, listed alphabetically
576 576
577 577 def add(ui, repo, *pats, **opts):
578 578 """add the specified files on the next commit
579 579
580 580 Schedule files to be version controlled and added to the repository.
581 581
582 582 The files will be added to the repository at the next commit.
583 583
584 584 If no names are given, add all files in the repository.
585 585 """
586 586
587 587 names = []
588 588 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
589 589 if exact:
590 590 if ui.verbose:
591 591 ui.status(_('adding %s\n') % rel)
592 592 names.append(abs)
593 593 elif repo.dirstate.state(abs) == '?':
594 594 ui.status(_('adding %s\n') % rel)
595 595 names.append(abs)
596 596 if not opts.get('dry_run'):
597 597 repo.add(names)
598 598
599 599 def addremove(ui, repo, *pats, **opts):
600 600 """add all new files, delete all missing files
601 601
602 602 Add all new files and remove all missing files from the repository.
603 603
604 604 New files are ignored if they match any of the patterns in .hgignore. As
605 605 with add, these changes take effect at the next commit.
606 606
607 607 Use the -s option to detect renamed files. With a parameter > 0,
608 608 this compares every removed file with every added file and records
609 609 those similar enough as renames. This option takes a percentage
610 610 between 0 (disabled) and 100 (files must be identical) as its
611 611 parameter. Detecting renamed files this way can be expensive.
612 612 """
613 613 sim = float(opts.get('similarity') or 0)
614 614 if sim < 0 or sim > 100:
615 615 raise util.Abort(_('similarity must be between 0 and 100'))
616 616 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
617 617
618 618 def annotate(ui, repo, *pats, **opts):
619 619 """show changeset information per file line
620 620
621 621 List changes in files, showing the revision id responsible for each line
622 622
623 623 This command is useful to discover who did a change or when a change took
624 624 place.
625 625
626 626 Without the -a option, annotate will avoid processing files it
627 627 detects as binary. With -a, annotate will generate an annotation
628 628 anyway, probably with undesirable results.
629 629 """
630 630 getdate = util.cachefunc(lambda x: util.datestr(x.date()))
631 631
632 632 if not pats:
633 633 raise util.Abort(_('at least one file name or pattern required'))
634 634
635 635 opmap = [['user', lambda x: ui.shortuser(x.user())],
636 636 ['number', lambda x: str(x.rev())],
637 637 ['changeset', lambda x: short(x.node())],
638 638 ['date', getdate], ['follow', lambda x: x.path()]]
639 639 if (not opts['user'] and not opts['changeset'] and not opts['date']
640 640 and not opts['follow']):
641 641 opts['number'] = 1
642 642
643 643 ctx = repo.changectx(opts['rev'])
644 644
645 645 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
646 646 node=ctx.node()):
647 647 fctx = ctx.filectx(abs)
648 648 if not opts['text'] and util.binary(fctx.data()):
649 649 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
650 650 continue
651 651
652 652 lines = fctx.annotate(follow=opts.get('follow'))
653 653 pieces = []
654 654
655 655 for o, f in opmap:
656 656 if opts[o]:
657 657 l = [f(n) for n, dummy in lines]
658 658 if l:
659 659 m = max(map(len, l))
660 660 pieces.append(["%*s" % (m, x) for x in l])
661 661
662 662 if pieces:
663 663 for p, l in zip(zip(*pieces), lines):
664 664 ui.write("%s: %s" % (" ".join(p), l[1]))
665 665
666 666 def archive(ui, repo, dest, **opts):
667 667 '''create unversioned archive of a repository revision
668 668
669 669 By default, the revision used is the parent of the working
670 670 directory; use "-r" to specify a different revision.
671 671
672 672 To specify the type of archive to create, use "-t". Valid
673 673 types are:
674 674
675 675 "files" (default): a directory full of files
676 676 "tar": tar archive, uncompressed
677 677 "tbz2": tar archive, compressed using bzip2
678 678 "tgz": tar archive, compressed using gzip
679 679 "uzip": zip archive, uncompressed
680 680 "zip": zip archive, compressed using deflate
681 681
682 682 The exact name of the destination archive or directory is given
683 683 using a format string; see "hg help export" for details.
684 684
685 685 Each member added to an archive file has a directory prefix
686 686 prepended. Use "-p" to specify a format string for the prefix.
687 687 The default is the basename of the archive, with suffixes removed.
688 688 '''
689 689
690 690 node = repo.changectx(opts['rev']).node()
691 691 dest = cmdutil.make_filename(repo, dest, node)
692 692 if os.path.realpath(dest) == repo.root:
693 693 raise util.Abort(_('repository root cannot be destination'))
694 694 dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
695 695 kind = opts.get('type') or 'files'
696 696 prefix = opts['prefix']
697 697 if dest == '-':
698 698 if kind == 'files':
699 699 raise util.Abort(_('cannot archive plain files to stdout'))
700 700 dest = sys.stdout
701 701 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
702 702 prefix = cmdutil.make_filename(repo, prefix, node)
703 703 archival.archive(repo, dest, node, kind, not opts['no_decode'],
704 704 matchfn, prefix)
705 705
706 706 def backout(ui, repo, rev, **opts):
707 707 '''reverse effect of earlier changeset
708 708
709 709 Commit the backed out changes as a new changeset. The new
710 710 changeset is a child of the backed out changeset.
711 711
712 712 If you back out a changeset other than the tip, a new head is
713 713 created. This head is the parent of the working directory. If
714 714 you back out an old changeset, your working directory will appear
715 715 old after the backout. You should merge the backout changeset
716 716 with another head.
717 717
718 718 The --merge option remembers the parent of the working directory
719 719 before starting the backout, then merges the new head with that
720 720 changeset afterwards. This saves you from doing the merge by
721 721 hand. The result of this merge is not committed, as for a normal
722 722 merge.'''
723 723
724 724 bail_if_changed(repo)
725 725 op1, op2 = repo.dirstate.parents()
726 726 if op2 != nullid:
727 727 raise util.Abort(_('outstanding uncommitted merge'))
728 728 node = repo.lookup(rev)
729 729 p1, p2 = repo.changelog.parents(node)
730 730 if p1 == nullid:
731 731 raise util.Abort(_('cannot back out a change with no parents'))
732 732 if p2 != nullid:
733 733 if not opts['parent']:
734 734 raise util.Abort(_('cannot back out a merge changeset without '
735 735 '--parent'))
736 736 p = repo.lookup(opts['parent'])
737 737 if p not in (p1, p2):
738 738 raise util.Abort(_('%s is not a parent of %s' %
739 739 (short(p), short(node))))
740 740 parent = p
741 741 else:
742 742 if opts['parent']:
743 743 raise util.Abort(_('cannot use --parent on non-merge changeset'))
744 744 parent = p1
745 745 hg.clean(repo, node, show_stats=False)
746 746 revert_opts = opts.copy()
747 747 revert_opts['all'] = True
748 748 revert_opts['rev'] = hex(parent)
749 749 revert(ui, repo, **revert_opts)
750 750 commit_opts = opts.copy()
751 751 commit_opts['addremove'] = False
752 752 if not commit_opts['message'] and not commit_opts['logfile']:
753 753 commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
754 754 commit_opts['force_editor'] = True
755 755 commit(ui, repo, **commit_opts)
756 756 def nice(node):
757 757 return '%d:%s' % (repo.changelog.rev(node), short(node))
758 758 ui.status(_('changeset %s backs out changeset %s\n') %
759 759 (nice(repo.changelog.tip()), nice(node)))
760 760 if op1 != node:
761 761 if opts['merge']:
762 762 ui.status(_('merging with changeset %s\n') % nice(op1))
763 763 n = _lookup(repo, hex(op1))
764 764 hg.merge(repo, n)
765 765 else:
766 766 ui.status(_('the backout changeset is a new head - '
767 767 'do not forget to merge\n'))
768 768 ui.status(_('(use "backout --merge" '
769 769 'if you want to auto-merge)\n'))
770 770
771 771 def branch(ui, repo, label=None):
772 772 """set or show the current branch name
773 773
774 774 With <name>, set the current branch name. Otherwise, show the
775 775 current branch name.
776 776 """
777 777
778 778 if label is not None:
779 779 repo.opener("branch", "w").write(label)
780 780 else:
781 781 b = repo.workingctx().branch()
782 782 if b:
783 783 ui.write("%s\n" % b)
784 784
785 785 def branches(ui, repo):
786 786 """list repository named branches
787 787
788 788 List the repository's named branches.
789 789 """
790 790 b = repo.branchtags()
791 791 l = [(-repo.changelog.rev(n), n, t) for t,n in b.items()]
792 792 l.sort()
793 793 for r, n, t in l:
794 794 hexfunc = ui.debugflag and hex or short
795 795 if ui.quiet:
796 796 ui.write("%s\n" % t)
797 797 else:
798 798 ui.write("%-30s %s:%s\n" % (t, -r, hexfunc(n)))
799 799
800 800 def bundle(ui, repo, fname, dest=None, **opts):
801 801 """create a changegroup file
802 802
803 803 Generate a compressed changegroup file collecting changesets not
804 804 found in the other repository.
805 805
806 806 If no destination repository is specified the destination is assumed
807 807 to have all the nodes specified by one or more --base parameters.
808 808
809 809 The bundle file can then be transferred using conventional means and
810 810 applied to another repository with the unbundle or pull command.
811 811 This is useful when direct push and pull are not available or when
812 812 exporting an entire repository is undesirable.
813 813
814 814 Applying bundles preserves all changeset contents including
815 815 permissions, copy/rename information, and revision history.
816 816 """
817 817 revs = opts.get('rev') or None
818 818 if revs:
819 819 revs = [repo.lookup(rev) for rev in revs]
820 820 base = opts.get('base')
821 821 if base:
822 822 if dest:
823 823 raise util.Abort(_("--base is incompatible with specifiying "
824 824 "a destination"))
825 825 base = [repo.lookup(rev) for rev in base]
826 826 # create the right base
827 827 # XXX: nodesbetween / changegroup* should be "fixed" instead
828 828 o = []
829 829 has_set = sets.Set(base)
830 830 for n in base:
831 831 has_set.update(repo.changelog.reachable(n))
832 832 if revs:
833 833 visit = list(revs)
834 834 else:
835 835 visit = repo.changelog.heads()
836 836 seen = sets.Set(visit)
837 837 while visit:
838 838 n = visit.pop(0)
839 839 parents = [p for p in repo.changelog.parents(n)
840 840 if p != nullid and p not in has_set]
841 841 if len(parents) == 0:
842 842 o.insert(0, n)
843 843 else:
844 844 for p in parents:
845 845 if p not in seen:
846 846 seen.add(p)
847 847 visit.append(p)
848 848 else:
849 849 setremoteconfig(ui, opts)
850 850 dest = ui.expandpath(dest or 'default-push', dest or 'default')
851 851 other = hg.repository(ui, dest)
852 852 o = repo.findoutgoing(other, force=opts['force'])
853 853
854 854 if revs:
855 855 cg = repo.changegroupsubset(o, revs, 'bundle')
856 856 else:
857 857 cg = repo.changegroup(o, 'bundle')
858 858 write_bundle(cg, fname)
859 859
860 860 def cat(ui, repo, file1, *pats, **opts):
861 861 """output the latest or given revisions of files
862 862
863 863 Print the specified files as they were at the given revision.
864 864 If no revision is given then working dir parent is used, or tip
865 865 if no revision is checked out.
866 866
867 867 Output may be to a file, in which case the name of the file is
868 868 given using a format string. The formatting rules are the same as
869 869 for the export command, with the following additions:
870 870
871 871 %s basename of file being printed
872 872 %d dirname of file being printed, or '.' if in repo root
873 873 %p root-relative path name of file being printed
874 874 """
875 875 ctx = repo.changectx(opts['rev'])
876 876 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
877 877 ctx.node()):
878 878 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
879 879 fp.write(ctx.filectx(abs).data())
880 880
881 881 def clone(ui, source, dest=None, **opts):
882 882 """make a copy of an existing repository
883 883
884 884 Create a copy of an existing repository in a new directory.
885 885
886 886 If no destination directory name is specified, it defaults to the
887 887 basename of the source.
888 888
889 889 The location of the source is added to the new repository's
890 890 .hg/hgrc file, as the default to be used for future pulls.
891 891
892 892 For efficiency, hardlinks are used for cloning whenever the source
893 893 and destination are on the same filesystem (note this applies only
894 894 to the repository data, not to the checked out files). Some
895 895 filesystems, such as AFS, implement hardlinking incorrectly, but
896 896 do not report errors. In these cases, use the --pull option to
897 897 avoid hardlinking.
898 898
899 899 You can safely clone repositories and checked out files using full
900 900 hardlinks with
901 901
902 902 $ cp -al REPO REPOCLONE
903 903
904 904 which is the fastest way to clone. However, the operation is not
905 905 atomic (making sure REPO is not modified during the operation is
906 906 up to you) and you have to make sure your editor breaks hardlinks
907 907 (Emacs and most Linux Kernel tools do so).
908 908
909 909 If you use the -r option to clone up to a specific revision, no
910 910 subsequent revisions will be present in the cloned repository.
911 911 This option implies --pull, even on local repositories.
912 912
913 913 See pull for valid source format details.
914 914
915 915 It is possible to specify an ssh:// URL as the destination, but no
916 916 .hg/hgrc will be created on the remote side. Look at the help text
917 917 for the pull command for important details about ssh:// URLs.
918 918 """
919 919 setremoteconfig(ui, opts)
920 920 hg.clone(ui, ui.expandpath(source), dest,
921 921 pull=opts['pull'],
922 922 stream=opts['uncompressed'],
923 923 rev=opts['rev'],
924 924 update=not opts['noupdate'])
925 925
926 926 def commit(ui, repo, *pats, **opts):
927 927 """commit the specified files or all outstanding changes
928 928
929 929 Commit changes to the given files into the repository.
930 930
931 931 If a list of files is omitted, all changes reported by "hg status"
932 932 will be committed.
933 933
934 934 If no commit message is specified, the editor configured in your hgrc
935 935 or in the EDITOR environment variable is started to enter a message.
936 936 """
937 937 message = logmessage(opts)
938 938
939 939 if opts['addremove']:
940 940 cmdutil.addremove(repo, pats, opts)
941 941 fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
942 942 if pats:
943 943 modified, added, removed = repo.status(files=fns, match=match)[:3]
944 944 files = modified + added + removed
945 945 else:
946 946 files = []
947 947 try:
948 948 repo.commit(files, message, opts['user'], opts['date'], match,
949 949 force_editor=opts.get('force_editor'))
950 950 except ValueError, inst:
951 951 raise util.Abort(str(inst))
952 952
953 953 def docopy(ui, repo, pats, opts, wlock):
954 954 # called with the repo lock held
955 955 cwd = repo.getcwd()
956 956 errors = 0
957 957 copied = []
958 958 targets = {}
959 959
960 960 def okaytocopy(abs, rel, exact):
961 961 reasons = {'?': _('is not managed'),
962 962 'a': _('has been marked for add'),
963 963 'r': _('has been marked for remove')}
964 964 state = repo.dirstate.state(abs)
965 965 reason = reasons.get(state)
966 966 if reason:
967 967 if state == 'a':
968 968 origsrc = repo.dirstate.copied(abs)
969 969 if origsrc is not None:
970 970 return origsrc
971 971 if exact:
972 972 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
973 973 else:
974 974 return abs
975 975
976 976 def copy(origsrc, abssrc, relsrc, target, exact):
977 977 abstarget = util.canonpath(repo.root, cwd, target)
978 978 reltarget = util.pathto(cwd, abstarget)
979 979 prevsrc = targets.get(abstarget)
980 980 if prevsrc is not None:
981 981 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
982 982 (reltarget, abssrc, prevsrc))
983 983 return
984 984 if (not opts['after'] and os.path.exists(reltarget) or
985 985 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
986 986 if not opts['force']:
987 987 ui.warn(_('%s: not overwriting - file exists\n') %
988 988 reltarget)
989 989 return
990 990 if not opts['after'] and not opts.get('dry_run'):
991 991 os.unlink(reltarget)
992 992 if opts['after']:
993 993 if not os.path.exists(reltarget):
994 994 return
995 995 else:
996 996 targetdir = os.path.dirname(reltarget) or '.'
997 997 if not os.path.isdir(targetdir) and not opts.get('dry_run'):
998 998 os.makedirs(targetdir)
999 999 try:
1000 1000 restore = repo.dirstate.state(abstarget) == 'r'
1001 1001 if restore and not opts.get('dry_run'):
1002 1002 repo.undelete([abstarget], wlock)
1003 1003 try:
1004 1004 if not opts.get('dry_run'):
1005 1005 shutil.copyfile(relsrc, reltarget)
1006 1006 shutil.copymode(relsrc, reltarget)
1007 1007 restore = False
1008 1008 finally:
1009 1009 if restore:
1010 1010 repo.remove([abstarget], wlock)
1011 1011 except shutil.Error, inst:
1012 1012 raise util.Abort(str(inst))
1013 1013 except IOError, inst:
1014 1014 if inst.errno == errno.ENOENT:
1015 1015 ui.warn(_('%s: deleted in working copy\n') % relsrc)
1016 1016 else:
1017 1017 ui.warn(_('%s: cannot copy - %s\n') %
1018 1018 (relsrc, inst.strerror))
1019 1019 errors += 1
1020 1020 return
1021 1021 if ui.verbose or not exact:
1022 1022 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1023 1023 targets[abstarget] = abssrc
1024 1024 if abstarget != origsrc and not opts.get('dry_run'):
1025 1025 repo.copy(origsrc, abstarget, wlock)
1026 1026 copied.append((abssrc, relsrc, exact))
1027 1027
1028 1028 def targetpathfn(pat, dest, srcs):
1029 1029 if os.path.isdir(pat):
1030 1030 abspfx = util.canonpath(repo.root, cwd, pat)
1031 1031 if destdirexists:
1032 1032 striplen = len(os.path.split(abspfx)[0])
1033 1033 else:
1034 1034 striplen = len(abspfx)
1035 1035 if striplen:
1036 1036 striplen += len(os.sep)
1037 1037 res = lambda p: os.path.join(dest, p[striplen:])
1038 1038 elif destdirexists:
1039 1039 res = lambda p: os.path.join(dest, os.path.basename(p))
1040 1040 else:
1041 1041 res = lambda p: dest
1042 1042 return res
1043 1043
1044 1044 def targetpathafterfn(pat, dest, srcs):
1045 1045 if util.patkind(pat, None)[0]:
1046 1046 # a mercurial pattern
1047 1047 res = lambda p: os.path.join(dest, os.path.basename(p))
1048 1048 else:
1049 1049 abspfx = util.canonpath(repo.root, cwd, pat)
1050 1050 if len(abspfx) < len(srcs[0][0]):
1051 1051 # A directory. Either the target path contains the last
1052 1052 # component of the source path or it does not.
1053 1053 def evalpath(striplen):
1054 1054 score = 0
1055 1055 for s in srcs:
1056 1056 t = os.path.join(dest, s[0][striplen:])
1057 1057 if os.path.exists(t):
1058 1058 score += 1
1059 1059 return score
1060 1060
1061 1061 striplen = len(abspfx)
1062 1062 if striplen:
1063 1063 striplen += len(os.sep)
1064 1064 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1065 1065 score = evalpath(striplen)
1066 1066 striplen1 = len(os.path.split(abspfx)[0])
1067 1067 if striplen1:
1068 1068 striplen1 += len(os.sep)
1069 1069 if evalpath(striplen1) > score:
1070 1070 striplen = striplen1
1071 1071 res = lambda p: os.path.join(dest, p[striplen:])
1072 1072 else:
1073 1073 # a file
1074 1074 if destdirexists:
1075 1075 res = lambda p: os.path.join(dest, os.path.basename(p))
1076 1076 else:
1077 1077 res = lambda p: dest
1078 1078 return res
1079 1079
1080 1080
1081 1081 pats = list(pats)
1082 1082 if not pats:
1083 1083 raise util.Abort(_('no source or destination specified'))
1084 1084 if len(pats) == 1:
1085 1085 raise util.Abort(_('no destination specified'))
1086 1086 dest = pats.pop()
1087 1087 destdirexists = os.path.isdir(dest)
1088 1088 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
1089 1089 raise util.Abort(_('with multiple sources, destination must be an '
1090 1090 'existing directory'))
1091 1091 if opts['after']:
1092 1092 tfn = targetpathafterfn
1093 1093 else:
1094 1094 tfn = targetpathfn
1095 1095 copylist = []
1096 1096 for pat in pats:
1097 1097 srcs = []
1098 1098 for tag, abssrc, relsrc, exact in cmdutil.walk(repo, [pat], opts):
1099 1099 origsrc = okaytocopy(abssrc, relsrc, exact)
1100 1100 if origsrc:
1101 1101 srcs.append((origsrc, abssrc, relsrc, exact))
1102 1102 if not srcs:
1103 1103 continue
1104 1104 copylist.append((tfn(pat, dest, srcs), srcs))
1105 1105 if not copylist:
1106 1106 raise util.Abort(_('no files to copy'))
1107 1107
1108 1108 for targetpath, srcs in copylist:
1109 1109 for origsrc, abssrc, relsrc, exact in srcs:
1110 1110 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
1111 1111
1112 1112 if errors:
1113 1113 ui.warn(_('(consider using --after)\n'))
1114 1114 return errors, copied
1115 1115
1116 1116 def copy(ui, repo, *pats, **opts):
1117 1117 """mark files as copied for the next commit
1118 1118
1119 1119 Mark dest as having copies of source files. If dest is a
1120 1120 directory, copies are put in that directory. If dest is a file,
1121 1121 there can only be one source.
1122 1122
1123 1123 By default, this command copies the contents of files as they
1124 1124 stand in the working directory. If invoked with --after, the
1125 1125 operation is recorded, but no copying is performed.
1126 1126
1127 1127 This command takes effect in the next commit.
1128 1128
1129 1129 NOTE: This command should be treated as experimental. While it
1130 1130 should properly record copied files, this information is not yet
1131 1131 fully used by merge, nor fully reported by log.
1132 1132 """
1133 1133 wlock = repo.wlock(0)
1134 1134 errs, copied = docopy(ui, repo, pats, opts, wlock)
1135 1135 return errs
1136 1136
1137 1137 def debugancestor(ui, index, rev1, rev2):
1138 1138 """find the ancestor revision of two revisions in a given index"""
1139 1139 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index, "", 0)
1140 1140 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
1141 1141 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1142 1142
1143 1143 def debugcomplete(ui, cmd='', **opts):
1144 1144 """returns the completion list associated with the given command"""
1145 1145
1146 1146 if opts['options']:
1147 1147 options = []
1148 1148 otables = [globalopts]
1149 1149 if cmd:
1150 1150 aliases, entry = findcmd(ui, cmd)
1151 1151 otables.append(entry[1])
1152 1152 for t in otables:
1153 1153 for o in t:
1154 1154 if o[0]:
1155 1155 options.append('-%s' % o[0])
1156 1156 options.append('--%s' % o[1])
1157 1157 ui.write("%s\n" % "\n".join(options))
1158 1158 return
1159 1159
1160 1160 clist = findpossible(ui, cmd).keys()
1161 1161 clist.sort()
1162 1162 ui.write("%s\n" % "\n".join(clist))
1163 1163
1164 1164 def debugrebuildstate(ui, repo, rev=None):
1165 1165 """rebuild the dirstate as it would look like for the given revision"""
1166 1166 if not rev:
1167 1167 rev = repo.changelog.tip()
1168 1168 else:
1169 1169 rev = repo.lookup(rev)
1170 1170 change = repo.changelog.read(rev)
1171 1171 n = change[0]
1172 1172 files = repo.manifest.read(n)
1173 1173 wlock = repo.wlock()
1174 1174 repo.dirstate.rebuild(rev, files)
1175 1175
1176 1176 def debugcheckstate(ui, repo):
1177 1177 """validate the correctness of the current dirstate"""
1178 1178 parent1, parent2 = repo.dirstate.parents()
1179 1179 repo.dirstate.read()
1180 1180 dc = repo.dirstate.map
1181 1181 keys = dc.keys()
1182 1182 keys.sort()
1183 1183 m1n = repo.changelog.read(parent1)[0]
1184 1184 m2n = repo.changelog.read(parent2)[0]
1185 1185 m1 = repo.manifest.read(m1n)
1186 1186 m2 = repo.manifest.read(m2n)
1187 1187 errors = 0
1188 1188 for f in dc:
1189 1189 state = repo.dirstate.state(f)
1190 1190 if state in "nr" and f not in m1:
1191 1191 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1192 1192 errors += 1
1193 1193 if state in "a" and f in m1:
1194 1194 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1195 1195 errors += 1
1196 1196 if state in "m" and f not in m1 and f not in m2:
1197 1197 ui.warn(_("%s in state %s, but not in either manifest\n") %
1198 1198 (f, state))
1199 1199 errors += 1
1200 1200 for f in m1:
1201 1201 state = repo.dirstate.state(f)
1202 1202 if state not in "nrm":
1203 1203 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1204 1204 errors += 1
1205 1205 if errors:
1206 1206 error = _(".hg/dirstate inconsistent with current parent's manifest")
1207 1207 raise util.Abort(error)
1208 1208
1209 1209 def showconfig(ui, repo, *values):
1210 1210 """show combined config settings from all hgrc files
1211 1211
1212 1212 With no args, print names and values of all config items.
1213 1213
1214 1214 With one arg of the form section.name, print just the value of
1215 1215 that config item.
1216 1216
1217 1217 With multiple args, print names and values of all config items
1218 1218 with matching section names."""
1219 1219
1220 1220 if values:
1221 1221 if len([v for v in values if '.' in v]) > 1:
1222 1222 raise util.Abort(_('only one config item permitted'))
1223 1223 for section, name, value in ui.walkconfig():
1224 1224 sectname = section + '.' + name
1225 1225 if values:
1226 1226 for v in values:
1227 1227 if v == section:
1228 1228 ui.write('%s=%s\n' % (sectname, value))
1229 1229 elif v == sectname:
1230 1230 ui.write(value, '\n')
1231 1231 else:
1232 1232 ui.write('%s=%s\n' % (sectname, value))
1233 1233
1234 1234 def debugsetparents(ui, repo, rev1, rev2=None):
1235 1235 """manually set the parents of the current working directory
1236 1236
1237 1237 This is useful for writing repository conversion tools, but should
1238 1238 be used with care.
1239 1239 """
1240 1240
1241 1241 if not rev2:
1242 1242 rev2 = hex(nullid)
1243 1243
1244 1244 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1245 1245
1246 1246 def debugstate(ui, repo):
1247 1247 """show the contents of the current dirstate"""
1248 1248 repo.dirstate.read()
1249 1249 dc = repo.dirstate.map
1250 1250 keys = dc.keys()
1251 1251 keys.sort()
1252 1252 for file_ in keys:
1253 1253 ui.write("%c %3o %10d %s %s\n"
1254 1254 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1255 1255 time.strftime("%x %X",
1256 1256 time.localtime(dc[file_][3])), file_))
1257 1257 for f in repo.dirstate.copies():
1258 1258 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1259 1259
1260 1260 def debugdata(ui, file_, rev):
1261 1261 """dump the contents of an data file revision"""
1262 1262 r = revlog.revlog(util.opener(os.getcwd(), audit=False),
1263 1263 file_[:-2] + ".i", file_, 0)
1264 1264 try:
1265 1265 ui.write(r.revision(r.lookup(rev)))
1266 1266 except KeyError:
1267 1267 raise util.Abort(_('invalid revision identifier %s') % rev)
1268 1268
1269 1269 def debugindex(ui, file_):
1270 1270 """dump the contents of an index file"""
1271 1271 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1272 1272 ui.write(" rev offset length base linkrev" +
1273 1273 " nodeid p1 p2\n")
1274 1274 for i in xrange(r.count()):
1275 1275 node = r.node(i)
1276 1276 pp = r.parents(node)
1277 1277 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1278 1278 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
1279 1279 short(node), short(pp[0]), short(pp[1])))
1280 1280
1281 1281 def debugindexdot(ui, file_):
1282 1282 """dump an index DAG as a .dot file"""
1283 1283 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1284 1284 ui.write("digraph G {\n")
1285 1285 for i in xrange(r.count()):
1286 1286 node = r.node(i)
1287 1287 pp = r.parents(node)
1288 1288 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1289 1289 if pp[1] != nullid:
1290 1290 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1291 1291 ui.write("}\n")
1292 1292
1293 1293 def debugrename(ui, repo, file, rev=None):
1294 1294 """dump rename information"""
1295 1295 r = repo.file(relpath(repo, [file])[0])
1296 1296 if rev:
1297 1297 try:
1298 1298 # assume all revision numbers are for changesets
1299 1299 n = repo.lookup(rev)
1300 1300 change = repo.changelog.read(n)
1301 1301 m = repo.manifest.read(change[0])
1302 1302 n = m[relpath(repo, [file])[0]]
1303 1303 except (hg.RepoError, KeyError):
1304 1304 n = r.lookup(rev)
1305 1305 else:
1306 1306 n = r.tip()
1307 1307 m = r.renamed(n)
1308 1308 if m:
1309 1309 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1310 1310 else:
1311 1311 ui.write(_("not renamed\n"))
1312 1312
1313 1313 def debugwalk(ui, repo, *pats, **opts):
1314 1314 """show how files match on given patterns"""
1315 1315 items = list(cmdutil.walk(repo, pats, opts))
1316 1316 if not items:
1317 1317 return
1318 1318 fmt = '%%s %%-%ds %%-%ds %%s' % (
1319 1319 max([len(abs) for (src, abs, rel, exact) in items]),
1320 1320 max([len(rel) for (src, abs, rel, exact) in items]))
1321 1321 for src, abs, rel, exact in items:
1322 1322 line = fmt % (src, abs, rel, exact and 'exact' or '')
1323 1323 ui.write("%s\n" % line.rstrip())
1324 1324
1325 1325 def diff(ui, repo, *pats, **opts):
1326 1326 """diff repository (or selected files)
1327 1327
1328 1328 Show differences between revisions for the specified files.
1329 1329
1330 1330 Differences between files are shown using the unified diff format.
1331 1331
1332 1332 When two revision arguments are given, then changes are shown
1333 1333 between those revisions. If only one revision is specified then
1334 1334 that revision is compared to the working directory, and, when no
1335 1335 revisions are specified, the working directory files are compared
1336 1336 to its parent.
1337 1337
1338 1338 Without the -a option, diff will avoid generating diffs of files
1339 1339 it detects as binary. With -a, diff will generate a diff anyway,
1340 1340 probably with undesirable results.
1341 1341 """
1342 1342 node1, node2 = cmdutil.revpair(ui, repo, opts['rev'])
1343 1343
1344 1344 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1345 1345
1346 1346 patch.diff(repo, node1, node2, fns, match=matchfn,
1347 1347 opts=patch.diffopts(ui, opts))
1348 1348
1349 1349 def export(ui, repo, *changesets, **opts):
1350 1350 """dump the header and diffs for one or more changesets
1351 1351
1352 1352 Print the changeset header and diffs for one or more revisions.
1353 1353
1354 1354 The information shown in the changeset header is: author,
1355 1355 changeset hash, parent and commit comment.
1356 1356
1357 1357 Output may be to a file, in which case the name of the file is
1358 1358 given using a format string. The formatting rules are as follows:
1359 1359
1360 1360 %% literal "%" character
1361 1361 %H changeset hash (40 bytes of hexadecimal)
1362 1362 %N number of patches being generated
1363 1363 %R changeset revision number
1364 1364 %b basename of the exporting repository
1365 1365 %h short-form changeset hash (12 bytes of hexadecimal)
1366 1366 %n zero-padded sequence number, starting at 1
1367 1367 %r zero-padded changeset revision number
1368 1368
1369 1369 Without the -a option, export will avoid generating diffs of files
1370 1370 it detects as binary. With -a, export will generate a diff anyway,
1371 1371 probably with undesirable results.
1372 1372
1373 1373 With the --switch-parent option, the diff will be against the second
1374 1374 parent. It can be useful to review a merge.
1375 1375 """
1376 1376 if not changesets:
1377 1377 raise util.Abort(_("export requires at least one changeset"))
1378 revs = list(cmdutil.revrange(ui, repo, changesets))
1378 revs = cmdutil.revrange(ui, repo, changesets)
1379 1379 if len(revs) > 1:
1380 1380 ui.note(_('exporting patches:\n'))
1381 1381 else:
1382 1382 ui.note(_('exporting patch:\n'))
1383 1383 patch.export(repo, map(repo.lookup, revs), template=opts['output'],
1384 1384 switch_parent=opts['switch_parent'],
1385 1385 opts=patch.diffopts(ui, opts))
1386 1386
1387 1387 def grep(ui, repo, pattern, *pats, **opts):
1388 1388 """search for a pattern in specified files and revisions
1389 1389
1390 1390 Search revisions of files for a regular expression.
1391 1391
1392 1392 This command behaves differently than Unix grep. It only accepts
1393 1393 Python/Perl regexps. It searches repository history, not the
1394 1394 working directory. It always prints the revision number in which
1395 1395 a match appears.
1396 1396
1397 1397 By default, grep only prints output for the first revision of a
1398 1398 file in which it finds a match. To get it to print every revision
1399 1399 that contains a change in match status ("-" for a match that
1400 1400 becomes a non-match, or "+" for a non-match that becomes a match),
1401 1401 use the --all flag.
1402 1402 """
1403 1403 reflags = 0
1404 1404 if opts['ignore_case']:
1405 1405 reflags |= re.I
1406 1406 regexp = re.compile(pattern, reflags)
1407 1407 sep, eol = ':', '\n'
1408 1408 if opts['print0']:
1409 1409 sep = eol = '\0'
1410 1410
1411 1411 fcache = {}
1412 1412 def getfile(fn):
1413 1413 if fn not in fcache:
1414 1414 fcache[fn] = repo.file(fn)
1415 1415 return fcache[fn]
1416 1416
1417 1417 def matchlines(body):
1418 1418 begin = 0
1419 1419 linenum = 0
1420 1420 while True:
1421 1421 match = regexp.search(body, begin)
1422 1422 if not match:
1423 1423 break
1424 1424 mstart, mend = match.span()
1425 1425 linenum += body.count('\n', begin, mstart) + 1
1426 1426 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1427 1427 lend = body.find('\n', mend)
1428 1428 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1429 1429 begin = lend + 1
1430 1430
1431 1431 class linestate(object):
1432 1432 def __init__(self, line, linenum, colstart, colend):
1433 1433 self.line = line
1434 1434 self.linenum = linenum
1435 1435 self.colstart = colstart
1436 1436 self.colend = colend
1437 1437
1438 1438 def __eq__(self, other):
1439 1439 return self.line == other.line
1440 1440
1441 1441 matches = {}
1442 1442 copies = {}
1443 1443 def grepbody(fn, rev, body):
1444 1444 matches[rev].setdefault(fn, [])
1445 1445 m = matches[rev][fn]
1446 1446 for lnum, cstart, cend, line in matchlines(body):
1447 1447 s = linestate(line, lnum, cstart, cend)
1448 1448 m.append(s)
1449 1449
1450 1450 def difflinestates(a, b):
1451 1451 sm = difflib.SequenceMatcher(None, a, b)
1452 1452 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1453 1453 if tag == 'insert':
1454 1454 for i in xrange(blo, bhi):
1455 1455 yield ('+', b[i])
1456 1456 elif tag == 'delete':
1457 1457 for i in xrange(alo, ahi):
1458 1458 yield ('-', a[i])
1459 1459 elif tag == 'replace':
1460 1460 for i in xrange(alo, ahi):
1461 1461 yield ('-', a[i])
1462 1462 for i in xrange(blo, bhi):
1463 1463 yield ('+', b[i])
1464 1464
1465 1465 prev = {}
1466 1466 def display(fn, rev, states, prevstates):
1467 1467 counts = {'-': 0, '+': 0}
1468 1468 filerevmatches = {}
1469 1469 if incrementing or not opts['all']:
1470 1470 a, b, r = prevstates, states, rev
1471 1471 else:
1472 1472 a, b, r = states, prevstates, prev.get(fn, -1)
1473 1473 for change, l in difflinestates(a, b):
1474 1474 cols = [fn, str(r)]
1475 1475 if opts['line_number']:
1476 1476 cols.append(str(l.linenum))
1477 1477 if opts['all']:
1478 1478 cols.append(change)
1479 1479 if opts['user']:
1480 1480 cols.append(ui.shortuser(getchange(r)[1]))
1481 1481 if opts['files_with_matches']:
1482 1482 c = (fn, r)
1483 1483 if c in filerevmatches:
1484 1484 continue
1485 1485 filerevmatches[c] = 1
1486 1486 else:
1487 1487 cols.append(l.line)
1488 1488 ui.write(sep.join(cols), eol)
1489 1489 counts[change] += 1
1490 1490 return counts['+'], counts['-']
1491 1491
1492 1492 fstate = {}
1493 1493 skip = {}
1494 1494 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1495 1495 count = 0
1496 1496 incrementing = False
1497 1497 follow = opts.get('follow')
1498 1498 for st, rev, fns in changeiter:
1499 1499 if st == 'window':
1500 1500 incrementing = rev
1501 1501 matches.clear()
1502 1502 elif st == 'add':
1503 1503 change = repo.changelog.read(repo.lookup(str(rev)))
1504 1504 mf = repo.manifest.read(change[0])
1505 1505 matches[rev] = {}
1506 1506 for fn in fns:
1507 1507 if fn in skip:
1508 1508 continue
1509 1509 fstate.setdefault(fn, {})
1510 1510 try:
1511 1511 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1512 1512 if follow:
1513 1513 copied = getfile(fn).renamed(mf[fn])
1514 1514 if copied:
1515 1515 copies.setdefault(rev, {})[fn] = copied[0]
1516 1516 except KeyError:
1517 1517 pass
1518 1518 elif st == 'iter':
1519 1519 states = matches[rev].items()
1520 1520 states.sort()
1521 1521 for fn, m in states:
1522 1522 copy = copies.get(rev, {}).get(fn)
1523 1523 if fn in skip:
1524 1524 if copy:
1525 1525 skip[copy] = True
1526 1526 continue
1527 1527 if incrementing or not opts['all'] or fstate[fn]:
1528 1528 pos, neg = display(fn, rev, m, fstate[fn])
1529 1529 count += pos + neg
1530 1530 if pos and not opts['all']:
1531 1531 skip[fn] = True
1532 1532 if copy:
1533 1533 skip[copy] = True
1534 1534 fstate[fn] = m
1535 1535 if copy:
1536 1536 fstate[copy] = m
1537 1537 prev[fn] = rev
1538 1538
1539 1539 if not incrementing:
1540 1540 fstate = fstate.items()
1541 1541 fstate.sort()
1542 1542 for fn, state in fstate:
1543 1543 if fn in skip:
1544 1544 continue
1545 1545 if fn not in copies.get(prev[fn], {}):
1546 1546 display(fn, rev, {}, state)
1547 1547 return (count == 0 and 1) or 0
1548 1548
1549 1549 def heads(ui, repo, **opts):
1550 1550 """show current repository heads
1551 1551
1552 1552 Show all repository head changesets.
1553 1553
1554 1554 Repository "heads" are changesets that don't have children
1555 1555 changesets. They are where development generally takes place and
1556 1556 are the usual targets for update and merge operations.
1557 1557 """
1558 1558 if opts['rev']:
1559 1559 heads = repo.heads(repo.lookup(opts['rev']))
1560 1560 else:
1561 1561 heads = repo.heads()
1562 1562 br = None
1563 1563 if opts['branches']:
1564 1564 ui.warn(_("the --branches option is deprecated, "
1565 1565 "please use 'hg branches' instead\n"))
1566 1566 br = repo.branchlookup(heads)
1567 1567 displayer = show_changeset(ui, repo, opts)
1568 1568 for n in heads:
1569 1569 displayer.show(changenode=n, brinfo=br)
1570 1570
1571 1571 def identify(ui, repo):
1572 1572 """print information about the working copy
1573 1573
1574 1574 Print a short summary of the current state of the repo.
1575 1575
1576 1576 This summary identifies the repository state using one or two parent
1577 1577 hash identifiers, followed by a "+" if there are uncommitted changes
1578 1578 in the working directory, followed by a list of tags for this revision.
1579 1579 """
1580 1580 parents = [p for p in repo.dirstate.parents() if p != nullid]
1581 1581 if not parents:
1582 1582 ui.write(_("unknown\n"))
1583 1583 return
1584 1584
1585 1585 hexfunc = ui.debugflag and hex or short
1586 1586 modified, added, removed, deleted = repo.status()[:4]
1587 1587 output = ["%s%s" %
1588 1588 ('+'.join([hexfunc(parent) for parent in parents]),
1589 1589 (modified or added or removed or deleted) and "+" or "")]
1590 1590
1591 1591 if not ui.quiet:
1592 1592
1593 1593 branch = repo.workingctx().branch()
1594 1594 if branch:
1595 1595 output.append("(%s)" % branch)
1596 1596
1597 1597 # multiple tags for a single parent separated by '/'
1598 1598 parenttags = ['/'.join(tags)
1599 1599 for tags in map(repo.nodetags, parents) if tags]
1600 1600 # tags for multiple parents separated by ' + '
1601 1601 if parenttags:
1602 1602 output.append(' + '.join(parenttags))
1603 1603
1604 1604 ui.write("%s\n" % ' '.join(output))
1605 1605
1606 1606 def import_(ui, repo, patch1, *patches, **opts):
1607 1607 """import an ordered set of patches
1608 1608
1609 1609 Import a list of patches and commit them individually.
1610 1610
1611 1611 If there are outstanding changes in the working directory, import
1612 1612 will abort unless given the -f flag.
1613 1613
1614 1614 You can import a patch straight from a mail message. Even patches
1615 1615 as attachments work (body part must be type text/plain or
1616 1616 text/x-patch to be used). From and Subject headers of email
1617 1617 message are used as default committer and commit message. All
1618 1618 text/plain body parts before first diff are added to commit
1619 1619 message.
1620 1620
1621 1621 If imported patch was generated by hg export, user and description
1622 1622 from patch override values from message headers and body. Values
1623 1623 given on command line with -m and -u override these.
1624 1624
1625 1625 To read a patch from standard input, use patch name "-".
1626 1626 """
1627 1627 patches = (patch1,) + patches
1628 1628
1629 1629 if not opts['force']:
1630 1630 bail_if_changed(repo)
1631 1631
1632 1632 d = opts["base"]
1633 1633 strip = opts["strip"]
1634 1634
1635 1635 wlock = repo.wlock()
1636 1636 lock = repo.lock()
1637 1637
1638 1638 for p in patches:
1639 1639 pf = os.path.join(d, p)
1640 1640
1641 1641 if pf == '-':
1642 1642 ui.status(_("applying patch from stdin\n"))
1643 1643 tmpname, message, user, date = patch.extract(ui, sys.stdin)
1644 1644 else:
1645 1645 ui.status(_("applying %s\n") % p)
1646 1646 tmpname, message, user, date = patch.extract(ui, file(pf))
1647 1647
1648 1648 if tmpname is None:
1649 1649 raise util.Abort(_('no diffs found'))
1650 1650
1651 1651 try:
1652 1652 if opts['message']:
1653 1653 # pickup the cmdline msg
1654 1654 message = opts['message']
1655 1655 elif message:
1656 1656 # pickup the patch msg
1657 1657 message = message.strip()
1658 1658 else:
1659 1659 # launch the editor
1660 1660 message = None
1661 1661 ui.debug(_('message:\n%s\n') % message)
1662 1662
1663 1663 files = {}
1664 1664 try:
1665 1665 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1666 1666 files=files)
1667 1667 finally:
1668 1668 files = patch.updatedir(ui, repo, files, wlock=wlock)
1669 1669 repo.commit(files, message, user, date, wlock=wlock, lock=lock)
1670 1670 finally:
1671 1671 os.unlink(tmpname)
1672 1672
1673 1673 def incoming(ui, repo, source="default", **opts):
1674 1674 """show new changesets found in source
1675 1675
1676 1676 Show new changesets found in the specified path/URL or the default
1677 1677 pull location. These are the changesets that would be pulled if a pull
1678 1678 was requested.
1679 1679
1680 1680 For remote repository, using --bundle avoids downloading the changesets
1681 1681 twice if the incoming is followed by a pull.
1682 1682
1683 1683 See pull for valid source format details.
1684 1684 """
1685 1685 source = ui.expandpath(source)
1686 1686 setremoteconfig(ui, opts)
1687 1687
1688 1688 other = hg.repository(ui, source)
1689 1689 incoming = repo.findincoming(other, force=opts["force"])
1690 1690 if not incoming:
1691 1691 ui.status(_("no changes found\n"))
1692 1692 return
1693 1693
1694 1694 cleanup = None
1695 1695 try:
1696 1696 fname = opts["bundle"]
1697 1697 if fname or not other.local():
1698 1698 # create a bundle (uncompressed if other repo is not local)
1699 1699 cg = other.changegroup(incoming, "incoming")
1700 1700 fname = cleanup = write_bundle(cg, fname, compress=other.local())
1701 1701 # keep written bundle?
1702 1702 if opts["bundle"]:
1703 1703 cleanup = None
1704 1704 if not other.local():
1705 1705 # use the created uncompressed bundlerepo
1706 1706 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1707 1707
1708 1708 revs = None
1709 1709 if opts['rev']:
1710 1710 revs = [other.lookup(rev) for rev in opts['rev']]
1711 1711 o = other.changelog.nodesbetween(incoming, revs)[0]
1712 1712 if opts['newest_first']:
1713 1713 o.reverse()
1714 1714 displayer = show_changeset(ui, other, opts)
1715 1715 for n in o:
1716 1716 parents = [p for p in other.changelog.parents(n) if p != nullid]
1717 1717 if opts['no_merges'] and len(parents) == 2:
1718 1718 continue
1719 1719 displayer.show(changenode=n)
1720 1720 if opts['patch']:
1721 1721 prev = (parents and parents[0]) or nullid
1722 1722 patch.diff(other, prev, n, fp=repo.ui)
1723 1723 ui.write("\n")
1724 1724 finally:
1725 1725 if hasattr(other, 'close'):
1726 1726 other.close()
1727 1727 if cleanup:
1728 1728 os.unlink(cleanup)
1729 1729
1730 1730 def init(ui, dest=".", **opts):
1731 1731 """create a new repository in the given directory
1732 1732
1733 1733 Initialize a new repository in the given directory. If the given
1734 1734 directory does not exist, it is created.
1735 1735
1736 1736 If no directory is given, the current directory is used.
1737 1737
1738 1738 It is possible to specify an ssh:// URL as the destination.
1739 1739 Look at the help text for the pull command for important details
1740 1740 about ssh:// URLs.
1741 1741 """
1742 1742 setremoteconfig(ui, opts)
1743 1743 hg.repository(ui, dest, create=1)
1744 1744
1745 1745 def locate(ui, repo, *pats, **opts):
1746 1746 """locate files matching specific patterns
1747 1747
1748 1748 Print all files under Mercurial control whose names match the
1749 1749 given patterns.
1750 1750
1751 1751 This command searches the current directory and its
1752 1752 subdirectories. To search an entire repository, move to the root
1753 1753 of the repository.
1754 1754
1755 1755 If no patterns are given to match, this command prints all file
1756 1756 names.
1757 1757
1758 1758 If you want to feed the output of this command into the "xargs"
1759 1759 command, use the "-0" option to both this command and "xargs".
1760 1760 This will avoid the problem of "xargs" treating single filenames
1761 1761 that contain white space as multiple filenames.
1762 1762 """
1763 1763 end = opts['print0'] and '\0' or '\n'
1764 1764 rev = opts['rev']
1765 1765 if rev:
1766 1766 node = repo.lookup(rev)
1767 1767 else:
1768 1768 node = None
1769 1769
1770 1770 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
1771 1771 head='(?:.*/|)'):
1772 1772 if not node and repo.dirstate.state(abs) == '?':
1773 1773 continue
1774 1774 if opts['fullpath']:
1775 1775 ui.write(os.path.join(repo.root, abs), end)
1776 1776 else:
1777 1777 ui.write(((pats and rel) or abs), end)
1778 1778
1779 1779 def log(ui, repo, *pats, **opts):
1780 1780 """show revision history of entire repository or files
1781 1781
1782 1782 Print the revision history of the specified files or the entire
1783 1783 project.
1784 1784
1785 1785 File history is shown without following rename or copy history of
1786 1786 files. Use -f/--follow with a file name to follow history across
1787 1787 renames and copies. --follow without a file name will only show
1788 1788 ancestors or descendants of the starting revision. --follow-first
1789 1789 only follows the first parent of merge revisions.
1790 1790
1791 1791 If no revision range is specified, the default is tip:0 unless
1792 1792 --follow is set, in which case the working directory parent is
1793 1793 used as the starting revision.
1794 1794
1795 1795 By default this command outputs: changeset id and hash, tags,
1796 1796 non-trivial parents, user, date and time, and a summary for each
1797 1797 commit. When the -v/--verbose switch is used, the list of changed
1798 1798 files and full commit message is shown.
1799 1799 """
1800 1800 class dui(object):
1801 1801 # Implement and delegate some ui protocol. Save hunks of
1802 1802 # output for later display in the desired order.
1803 1803 def __init__(self, ui):
1804 1804 self.ui = ui
1805 1805 self.hunk = {}
1806 1806 self.header = {}
1807 1807 def bump(self, rev):
1808 1808 self.rev = rev
1809 1809 self.hunk[rev] = []
1810 1810 self.header[rev] = []
1811 1811 def note(self, *args):
1812 1812 if self.verbose:
1813 1813 self.write(*args)
1814 1814 def status(self, *args):
1815 1815 if not self.quiet:
1816 1816 self.write(*args)
1817 1817 def write(self, *args):
1818 1818 self.hunk[self.rev].append(args)
1819 1819 def write_header(self, *args):
1820 1820 self.header[self.rev].append(args)
1821 1821 def debug(self, *args):
1822 1822 if self.debugflag:
1823 1823 self.write(*args)
1824 1824 def __getattr__(self, key):
1825 1825 return getattr(self.ui, key)
1826 1826
1827 1827 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1828 1828
1829 1829 if opts['branches']:
1830 1830 ui.warn(_("the --branches option is deprecated, "
1831 1831 "please use 'hg branches' instead\n"))
1832 1832
1833 1833 if opts['limit']:
1834 1834 try:
1835 1835 limit = int(opts['limit'])
1836 1836 except ValueError:
1837 1837 raise util.Abort(_('limit must be a positive integer'))
1838 1838 if limit <= 0: raise util.Abort(_('limit must be positive'))
1839 1839 else:
1840 1840 limit = sys.maxint
1841 1841 count = 0
1842 1842
1843 1843 if opts['copies'] and opts['rev']:
1844 endrev = max([int(i)
1845 for i in cmdutil.revrange(ui, repo, opts['rev'])]) + 1
1844 endrev = max(cmdutil.revrange(ui, repo, opts['rev'])) + 1
1846 1845 else:
1847 1846 endrev = repo.changelog.count()
1848 1847 rcache = {}
1849 1848 ncache = {}
1850 1849 dcache = []
1851 1850 def getrenamed(fn, rev, man):
1852 1851 '''looks up all renames for a file (up to endrev) the first
1853 1852 time the file is given. It indexes on the changerev and only
1854 1853 parses the manifest if linkrev != changerev.
1855 1854 Returns rename info for fn at changerev rev.'''
1856 1855 if fn not in rcache:
1857 1856 rcache[fn] = {}
1858 1857 ncache[fn] = {}
1859 1858 fl = repo.file(fn)
1860 1859 for i in xrange(fl.count()):
1861 1860 node = fl.node(i)
1862 1861 lr = fl.linkrev(node)
1863 1862 renamed = fl.renamed(node)
1864 1863 rcache[fn][lr] = renamed
1865 1864 if renamed:
1866 1865 ncache[fn][node] = renamed
1867 1866 if lr >= endrev:
1868 1867 break
1869 1868 if rev in rcache[fn]:
1870 1869 return rcache[fn][rev]
1871 1870 mr = repo.manifest.rev(man)
1872 1871 if repo.manifest.parentrevs(mr) != (mr - 1, -1):
1873 1872 return ncache[fn].get(repo.manifest.find(man, fn)[0])
1874 1873 if not dcache or dcache[0] != man:
1875 1874 dcache[:] = [man, repo.manifest.readdelta(man)]
1876 1875 if fn in dcache[1]:
1877 1876 return ncache[fn].get(dcache[1][fn])
1878 1877 return None
1879 1878
1880 1879 displayer = show_changeset(ui, repo, opts)
1881 1880 for st, rev, fns in changeiter:
1882 1881 if st == 'window':
1883 1882 du = dui(ui)
1884 1883 displayer.ui = du
1885 1884 elif st == 'add':
1886 1885 du.bump(rev)
1887 1886 changenode = repo.changelog.node(rev)
1888 1887 parents = [p for p in repo.changelog.parents(changenode)
1889 1888 if p != nullid]
1890 1889 if opts['no_merges'] and len(parents) == 2:
1891 1890 continue
1892 1891 if opts['only_merges'] and len(parents) != 2:
1893 1892 continue
1894 1893
1895 1894 if opts['keyword']:
1896 1895 changes = getchange(rev)
1897 1896 miss = 0
1898 1897 for k in [kw.lower() for kw in opts['keyword']]:
1899 1898 if not (k in changes[1].lower() or
1900 1899 k in changes[4].lower() or
1901 1900 k in " ".join(changes[3][:20]).lower()):
1902 1901 miss = 1
1903 1902 break
1904 1903 if miss:
1905 1904 continue
1906 1905
1907 1906 br = None
1908 1907 if opts['branches']:
1909 1908 br = repo.branchlookup([repo.changelog.node(rev)])
1910 1909
1911 1910 copies = []
1912 1911 if opts.get('copies') and rev:
1913 1912 mf = getchange(rev)[0]
1914 1913 for fn in getchange(rev)[3]:
1915 1914 rename = getrenamed(fn, rev, mf)
1916 1915 if rename:
1917 1916 copies.append((fn, rename[0]))
1918 1917 displayer.show(rev, brinfo=br, copies=copies)
1919 1918 if opts['patch']:
1920 1919 prev = (parents and parents[0]) or nullid
1921 1920 patch.diff(repo, prev, changenode, match=matchfn, fp=du)
1922 1921 du.write("\n\n")
1923 1922 elif st == 'iter':
1924 1923 if count == limit: break
1925 1924 if du.header[rev]:
1926 1925 for args in du.header[rev]:
1927 1926 ui.write_header(*args)
1928 1927 if du.hunk[rev]:
1929 1928 count += 1
1930 1929 for args in du.hunk[rev]:
1931 1930 ui.write(*args)
1932 1931
1933 1932 def manifest(ui, repo, rev=None):
1934 1933 """output the latest or given revision of the project manifest
1935 1934
1936 1935 Print a list of version controlled files for the given revision.
1937 1936
1938 1937 The manifest is the list of files being version controlled. If no revision
1939 1938 is given then the tip is used.
1940 1939 """
1941 1940 if rev:
1942 1941 try:
1943 1942 # assume all revision numbers are for changesets
1944 1943 n = repo.lookup(rev)
1945 1944 change = repo.changelog.read(n)
1946 1945 n = change[0]
1947 1946 except hg.RepoError:
1948 1947 n = repo.manifest.lookup(rev)
1949 1948 else:
1950 1949 n = repo.manifest.tip()
1951 1950 m = repo.manifest.read(n)
1952 1951 files = m.keys()
1953 1952 files.sort()
1954 1953
1955 1954 for f in files:
1956 1955 ui.write("%40s %3s %s\n" % (hex(m[f]),
1957 1956 m.execf(f) and "755" or "644", f))
1958 1957
1959 1958 def merge(ui, repo, node=None, force=None, branch=None):
1960 1959 """Merge working directory with another revision
1961 1960
1962 1961 Merge the contents of the current working directory and the
1963 1962 requested revision. Files that changed between either parent are
1964 1963 marked as changed for the next commit and a commit must be
1965 1964 performed before any further updates are allowed.
1966 1965
1967 1966 If no revision is specified, the working directory's parent is a
1968 1967 head revision, and the repository contains exactly one other head,
1969 1968 the other head is merged with by default. Otherwise, an explicit
1970 1969 revision to merge with must be provided.
1971 1970 """
1972 1971
1973 1972 if node or branch:
1974 1973 node = _lookup(repo, node, branch)
1975 1974 else:
1976 1975 heads = repo.heads()
1977 1976 if len(heads) > 2:
1978 1977 raise util.Abort(_('repo has %d heads - '
1979 1978 'please merge with an explicit rev') %
1980 1979 len(heads))
1981 1980 if len(heads) == 1:
1982 1981 raise util.Abort(_('there is nothing to merge - '
1983 1982 'use "hg update" instead'))
1984 1983 parent = repo.dirstate.parents()[0]
1985 1984 if parent not in heads:
1986 1985 raise util.Abort(_('working dir not at a head rev - '
1987 1986 'use "hg update" or merge with an explicit rev'))
1988 1987 node = parent == heads[0] and heads[-1] or heads[0]
1989 1988 return hg.merge(repo, node, force=force)
1990 1989
1991 1990 def outgoing(ui, repo, dest=None, **opts):
1992 1991 """show changesets not found in destination
1993 1992
1994 1993 Show changesets not found in the specified destination repository or
1995 1994 the default push location. These are the changesets that would be pushed
1996 1995 if a push was requested.
1997 1996
1998 1997 See pull for valid destination format details.
1999 1998 """
2000 1999 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2001 2000 setremoteconfig(ui, opts)
2002 2001 revs = None
2003 2002 if opts['rev']:
2004 2003 revs = [repo.lookup(rev) for rev in opts['rev']]
2005 2004
2006 2005 other = hg.repository(ui, dest)
2007 2006 o = repo.findoutgoing(other, force=opts['force'])
2008 2007 if not o:
2009 2008 ui.status(_("no changes found\n"))
2010 2009 return
2011 2010 o = repo.changelog.nodesbetween(o, revs)[0]
2012 2011 if opts['newest_first']:
2013 2012 o.reverse()
2014 2013 displayer = show_changeset(ui, repo, opts)
2015 2014 for n in o:
2016 2015 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2017 2016 if opts['no_merges'] and len(parents) == 2:
2018 2017 continue
2019 2018 displayer.show(changenode=n)
2020 2019 if opts['patch']:
2021 2020 prev = (parents and parents[0]) or nullid
2022 2021 patch.diff(repo, prev, n)
2023 2022 ui.write("\n")
2024 2023
2025 2024 def parents(ui, repo, file_=None, rev=None, branches=None, **opts):
2026 2025 """show the parents of the working dir or revision
2027 2026
2028 2027 Print the working directory's parent revisions.
2029 2028 """
2030 2029 # legacy
2031 2030 if file_ and not rev:
2032 2031 try:
2033 2032 rev = repo.lookup(file_)
2034 2033 file_ = None
2035 2034 except hg.RepoError:
2036 2035 pass
2037 2036 else:
2038 2037 ui.warn(_("'hg parent REV' is deprecated, "
2039 2038 "please use 'hg parents -r REV instead\n"))
2040 2039
2041 2040 if rev:
2042 2041 if file_:
2043 2042 ctx = repo.filectx(file_, changeid=rev)
2044 2043 else:
2045 2044 ctx = repo.changectx(rev)
2046 2045 p = [cp.node() for cp in ctx.parents()]
2047 2046 else:
2048 2047 p = repo.dirstate.parents()
2049 2048
2050 2049 br = None
2051 2050 if branches is not None:
2052 2051 ui.warn(_("the --branches option is deprecated, "
2053 2052 "please use 'hg branches' instead\n"))
2054 2053 br = repo.branchlookup(p)
2055 2054 displayer = show_changeset(ui, repo, opts)
2056 2055 for n in p:
2057 2056 if n != nullid:
2058 2057 displayer.show(changenode=n, brinfo=br)
2059 2058
2060 2059 def paths(ui, repo, search=None):
2061 2060 """show definition of symbolic path names
2062 2061
2063 2062 Show definition of symbolic path name NAME. If no name is given, show
2064 2063 definition of available names.
2065 2064
2066 2065 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2067 2066 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2068 2067 """
2069 2068 if search:
2070 2069 for name, path in ui.configitems("paths"):
2071 2070 if name == search:
2072 2071 ui.write("%s\n" % path)
2073 2072 return
2074 2073 ui.warn(_("not found!\n"))
2075 2074 return 1
2076 2075 else:
2077 2076 for name, path in ui.configitems("paths"):
2078 2077 ui.write("%s = %s\n" % (name, path))
2079 2078
2080 2079 def postincoming(ui, repo, modheads, optupdate):
2081 2080 if modheads == 0:
2082 2081 return
2083 2082 if optupdate:
2084 2083 if modheads == 1:
2085 2084 return hg.update(repo, repo.changelog.tip()) # update
2086 2085 else:
2087 2086 ui.status(_("not updating, since new heads added\n"))
2088 2087 if modheads > 1:
2089 2088 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2090 2089 else:
2091 2090 ui.status(_("(run 'hg update' to get a working copy)\n"))
2092 2091
2093 2092 def pull(ui, repo, source="default", **opts):
2094 2093 """pull changes from the specified source
2095 2094
2096 2095 Pull changes from a remote repository to a local one.
2097 2096
2098 2097 This finds all changes from the repository at the specified path
2099 2098 or URL and adds them to the local repository. By default, this
2100 2099 does not update the copy of the project in the working directory.
2101 2100
2102 2101 Valid URLs are of the form:
2103 2102
2104 2103 local/filesystem/path
2105 2104 http://[user@]host[:port]/[path]
2106 2105 https://[user@]host[:port]/[path]
2107 2106 ssh://[user@]host[:port]/[path]
2108 2107
2109 2108 Some notes about using SSH with Mercurial:
2110 2109 - SSH requires an accessible shell account on the destination machine
2111 2110 and a copy of hg in the remote path or specified with as remotecmd.
2112 2111 - path is relative to the remote user's home directory by default.
2113 2112 Use an extra slash at the start of a path to specify an absolute path:
2114 2113 ssh://example.com//tmp/repository
2115 2114 - Mercurial doesn't use its own compression via SSH; the right thing
2116 2115 to do is to configure it in your ~/.ssh/config, e.g.:
2117 2116 Host *.mylocalnetwork.example.com
2118 2117 Compression no
2119 2118 Host *
2120 2119 Compression yes
2121 2120 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2122 2121 with the --ssh command line option.
2123 2122 """
2124 2123 source = ui.expandpath(source)
2125 2124 setremoteconfig(ui, opts)
2126 2125
2127 2126 other = hg.repository(ui, source)
2128 2127 ui.status(_('pulling from %s\n') % (source))
2129 2128 revs = None
2130 2129 if opts['rev']:
2131 2130 if 'lookup' in other.capabilities:
2132 2131 revs = [other.lookup(rev) for rev in opts['rev']]
2133 2132 else:
2134 2133 error = _("Other repository doesn't support revision lookup, so a rev cannot be specified.")
2135 2134 raise util.Abort(error)
2136 2135 modheads = repo.pull(other, heads=revs, force=opts['force'])
2137 2136 return postincoming(ui, repo, modheads, opts['update'])
2138 2137
2139 2138 def push(ui, repo, dest=None, **opts):
2140 2139 """push changes to the specified destination
2141 2140
2142 2141 Push changes from the local repository to the given destination.
2143 2142
2144 2143 This is the symmetrical operation for pull. It helps to move
2145 2144 changes from the current repository to a different one. If the
2146 2145 destination is local this is identical to a pull in that directory
2147 2146 from the current one.
2148 2147
2149 2148 By default, push will refuse to run if it detects the result would
2150 2149 increase the number of remote heads. This generally indicates the
2151 2150 the client has forgotten to sync and merge before pushing.
2152 2151
2153 2152 Valid URLs are of the form:
2154 2153
2155 2154 local/filesystem/path
2156 2155 ssh://[user@]host[:port]/[path]
2157 2156
2158 2157 Look at the help text for the pull command for important details
2159 2158 about ssh:// URLs.
2160 2159
2161 2160 Pushing to http:// and https:// URLs is possible, too, if this
2162 2161 feature is enabled on the remote Mercurial server.
2163 2162 """
2164 2163 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2165 2164 setremoteconfig(ui, opts)
2166 2165
2167 2166 other = hg.repository(ui, dest)
2168 2167 ui.status('pushing to %s\n' % (dest))
2169 2168 revs = None
2170 2169 if opts['rev']:
2171 2170 revs = [repo.lookup(rev) for rev in opts['rev']]
2172 2171 r = repo.push(other, opts['force'], revs=revs)
2173 2172 return r == 0
2174 2173
2175 2174 def rawcommit(ui, repo, *flist, **rc):
2176 2175 """raw commit interface (DEPRECATED)
2177 2176
2178 2177 (DEPRECATED)
2179 2178 Lowlevel commit, for use in helper scripts.
2180 2179
2181 2180 This command is not intended to be used by normal users, as it is
2182 2181 primarily useful for importing from other SCMs.
2183 2182
2184 2183 This command is now deprecated and will be removed in a future
2185 2184 release, please use debugsetparents and commit instead.
2186 2185 """
2187 2186
2188 2187 ui.warn(_("(the rawcommit command is deprecated)\n"))
2189 2188
2190 2189 message = rc['message']
2191 2190 if not message and rc['logfile']:
2192 2191 try:
2193 2192 message = open(rc['logfile']).read()
2194 2193 except IOError:
2195 2194 pass
2196 2195 if not message and not rc['logfile']:
2197 2196 raise util.Abort(_("missing commit message"))
2198 2197
2199 2198 files = relpath(repo, list(flist))
2200 2199 if rc['files']:
2201 2200 files += open(rc['files']).read().splitlines()
2202 2201
2203 2202 rc['parent'] = map(repo.lookup, rc['parent'])
2204 2203
2205 2204 try:
2206 2205 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
2207 2206 except ValueError, inst:
2208 2207 raise util.Abort(str(inst))
2209 2208
2210 2209 def recover(ui, repo):
2211 2210 """roll back an interrupted transaction
2212 2211
2213 2212 Recover from an interrupted commit or pull.
2214 2213
2215 2214 This command tries to fix the repository status after an interrupted
2216 2215 operation. It should only be necessary when Mercurial suggests it.
2217 2216 """
2218 2217 if repo.recover():
2219 2218 return hg.verify(repo)
2220 2219 return 1
2221 2220
2222 2221 def remove(ui, repo, *pats, **opts):
2223 2222 """remove the specified files on the next commit
2224 2223
2225 2224 Schedule the indicated files for removal from the repository.
2226 2225
2227 2226 This command schedules the files to be removed at the next commit.
2228 2227 This only removes files from the current branch, not from the
2229 2228 entire project history. If the files still exist in the working
2230 2229 directory, they will be deleted from it. If invoked with --after,
2231 2230 files that have been manually deleted are marked as removed.
2232 2231
2233 2232 Modified files and added files are not removed by default. To
2234 2233 remove them, use the -f/--force option.
2235 2234 """
2236 2235 names = []
2237 2236 if not opts['after'] and not pats:
2238 2237 raise util.Abort(_('no files specified'))
2239 2238 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2240 2239 exact = dict.fromkeys(files)
2241 2240 mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5]
2242 2241 modified, added, removed, deleted, unknown = mardu
2243 2242 remove, forget = [], []
2244 2243 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
2245 2244 reason = None
2246 2245 if abs not in deleted and opts['after']:
2247 2246 reason = _('is still present')
2248 2247 elif abs in modified and not opts['force']:
2249 2248 reason = _('is modified (use -f to force removal)')
2250 2249 elif abs in added:
2251 2250 if opts['force']:
2252 2251 forget.append(abs)
2253 2252 continue
2254 2253 reason = _('has been marked for add (use -f to force removal)')
2255 2254 elif abs in unknown:
2256 2255 reason = _('is not managed')
2257 2256 elif abs in removed:
2258 2257 continue
2259 2258 if reason:
2260 2259 if exact:
2261 2260 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2262 2261 else:
2263 2262 if ui.verbose or not exact:
2264 2263 ui.status(_('removing %s\n') % rel)
2265 2264 remove.append(abs)
2266 2265 repo.forget(forget)
2267 2266 repo.remove(remove, unlink=not opts['after'])
2268 2267
2269 2268 def rename(ui, repo, *pats, **opts):
2270 2269 """rename files; equivalent of copy + remove
2271 2270
2272 2271 Mark dest as copies of sources; mark sources for deletion. If
2273 2272 dest is a directory, copies are put in that directory. If dest is
2274 2273 a file, there can only be one source.
2275 2274
2276 2275 By default, this command copies the contents of files as they
2277 2276 stand in the working directory. If invoked with --after, the
2278 2277 operation is recorded, but no copying is performed.
2279 2278
2280 2279 This command takes effect in the next commit.
2281 2280
2282 2281 NOTE: This command should be treated as experimental. While it
2283 2282 should properly record rename files, this information is not yet
2284 2283 fully used by merge, nor fully reported by log.
2285 2284 """
2286 2285 wlock = repo.wlock(0)
2287 2286 errs, copied = docopy(ui, repo, pats, opts, wlock)
2288 2287 names = []
2289 2288 for abs, rel, exact in copied:
2290 2289 if ui.verbose or not exact:
2291 2290 ui.status(_('removing %s\n') % rel)
2292 2291 names.append(abs)
2293 2292 if not opts.get('dry_run'):
2294 2293 repo.remove(names, True, wlock)
2295 2294 return errs
2296 2295
2297 2296 def revert(ui, repo, *pats, **opts):
2298 2297 """revert files or dirs to their states as of some revision
2299 2298
2300 2299 With no revision specified, revert the named files or directories
2301 2300 to the contents they had in the parent of the working directory.
2302 2301 This restores the contents of the affected files to an unmodified
2303 2302 state. If the working directory has two parents, you must
2304 2303 explicitly specify the revision to revert to.
2305 2304
2306 2305 Modified files are saved with a .orig suffix before reverting.
2307 2306 To disable these backups, use --no-backup.
2308 2307
2309 2308 Using the -r option, revert the given files or directories to their
2310 2309 contents as of a specific revision. This can be helpful to "roll
2311 2310 back" some or all of a change that should not have been committed.
2312 2311
2313 2312 Revert modifies the working directory. It does not commit any
2314 2313 changes, or change the parent of the working directory. If you
2315 2314 revert to a revision other than the parent of the working
2316 2315 directory, the reverted files will thus appear modified
2317 2316 afterwards.
2318 2317
2319 2318 If a file has been deleted, it is recreated. If the executable
2320 2319 mode of a file was changed, it is reset.
2321 2320
2322 2321 If names are given, all files matching the names are reverted.
2323 2322
2324 2323 If no arguments are given, no files are reverted.
2325 2324 """
2326 2325
2327 2326 if not pats and not opts['all']:
2328 2327 raise util.Abort(_('no files or directories specified; '
2329 2328 'use --all to revert the whole repo'))
2330 2329
2331 2330 parent, p2 = repo.dirstate.parents()
2332 2331 if not opts['rev'] and p2 != nullid:
2333 2332 raise util.Abort(_('uncommitted merge - please provide a '
2334 2333 'specific revision'))
2335 2334 node = repo.changectx(opts['rev']).node()
2336 2335 mf = repo.manifest.read(repo.changelog.read(node)[0])
2337 2336 if node == parent:
2338 2337 pmf = mf
2339 2338 else:
2340 2339 pmf = None
2341 2340
2342 2341 wlock = repo.wlock()
2343 2342
2344 2343 # need all matching names in dirstate and manifest of target rev,
2345 2344 # so have to walk both. do not print errors if files exist in one
2346 2345 # but not other.
2347 2346
2348 2347 names = {}
2349 2348 target_only = {}
2350 2349
2351 2350 # walk dirstate.
2352 2351
2353 2352 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
2354 2353 badmatch=mf.has_key):
2355 2354 names[abs] = (rel, exact)
2356 2355 if src == 'b':
2357 2356 target_only[abs] = True
2358 2357
2359 2358 # walk target manifest.
2360 2359
2361 2360 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
2362 2361 badmatch=names.has_key):
2363 2362 if abs in names: continue
2364 2363 names[abs] = (rel, exact)
2365 2364 target_only[abs] = True
2366 2365
2367 2366 changes = repo.status(match=names.has_key, wlock=wlock)[:5]
2368 2367 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2369 2368
2370 2369 revert = ([], _('reverting %s\n'))
2371 2370 add = ([], _('adding %s\n'))
2372 2371 remove = ([], _('removing %s\n'))
2373 2372 forget = ([], _('forgetting %s\n'))
2374 2373 undelete = ([], _('undeleting %s\n'))
2375 2374 update = {}
2376 2375
2377 2376 disptable = (
2378 2377 # dispatch table:
2379 2378 # file state
2380 2379 # action if in target manifest
2381 2380 # action if not in target manifest
2382 2381 # make backup if in target manifest
2383 2382 # make backup if not in target manifest
2384 2383 (modified, revert, remove, True, True),
2385 2384 (added, revert, forget, True, False),
2386 2385 (removed, undelete, None, False, False),
2387 2386 (deleted, revert, remove, False, False),
2388 2387 (unknown, add, None, True, False),
2389 2388 (target_only, add, None, False, False),
2390 2389 )
2391 2390
2392 2391 entries = names.items()
2393 2392 entries.sort()
2394 2393
2395 2394 for abs, (rel, exact) in entries:
2396 2395 mfentry = mf.get(abs)
2397 2396 def handle(xlist, dobackup):
2398 2397 xlist[0].append(abs)
2399 2398 update[abs] = 1
2400 2399 if dobackup and not opts['no_backup'] and os.path.exists(rel):
2401 2400 bakname = "%s.orig" % rel
2402 2401 ui.note(_('saving current version of %s as %s\n') %
2403 2402 (rel, bakname))
2404 2403 if not opts.get('dry_run'):
2405 2404 shutil.copyfile(rel, bakname)
2406 2405 shutil.copymode(rel, bakname)
2407 2406 if ui.verbose or not exact:
2408 2407 ui.status(xlist[1] % rel)
2409 2408 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2410 2409 if abs not in table: continue
2411 2410 # file has changed in dirstate
2412 2411 if mfentry:
2413 2412 handle(hitlist, backuphit)
2414 2413 elif misslist is not None:
2415 2414 handle(misslist, backupmiss)
2416 2415 else:
2417 2416 if exact: ui.warn(_('file not managed: %s\n' % rel))
2418 2417 break
2419 2418 else:
2420 2419 # file has not changed in dirstate
2421 2420 if node == parent:
2422 2421 if exact: ui.warn(_('no changes needed to %s\n' % rel))
2423 2422 continue
2424 2423 if pmf is None:
2425 2424 # only need parent manifest in this unlikely case,
2426 2425 # so do not read by default
2427 2426 pmf = repo.manifest.read(repo.changelog.read(parent)[0])
2428 2427 if abs in pmf:
2429 2428 if mfentry:
2430 2429 # if version of file is same in parent and target
2431 2430 # manifests, do nothing
2432 2431 if pmf[abs] != mfentry:
2433 2432 handle(revert, False)
2434 2433 else:
2435 2434 handle(remove, False)
2436 2435
2437 2436 if not opts.get('dry_run'):
2438 2437 repo.dirstate.forget(forget[0])
2439 2438 r = hg.revert(repo, node, update.has_key, wlock)
2440 2439 repo.dirstate.update(add[0], 'a')
2441 2440 repo.dirstate.update(undelete[0], 'n')
2442 2441 repo.dirstate.update(remove[0], 'r')
2443 2442 return r
2444 2443
2445 2444 def rollback(ui, repo):
2446 2445 """roll back the last transaction in this repository
2447 2446
2448 2447 Roll back the last transaction in this repository, restoring the
2449 2448 project to its state prior to the transaction.
2450 2449
2451 2450 Transactions are used to encapsulate the effects of all commands
2452 2451 that create new changesets or propagate existing changesets into a
2453 2452 repository. For example, the following commands are transactional,
2454 2453 and their effects can be rolled back:
2455 2454
2456 2455 commit
2457 2456 import
2458 2457 pull
2459 2458 push (with this repository as destination)
2460 2459 unbundle
2461 2460
2462 2461 This command should be used with care. There is only one level of
2463 2462 rollback, and there is no way to undo a rollback.
2464 2463
2465 2464 This command is not intended for use on public repositories. Once
2466 2465 changes are visible for pull by other users, rolling a transaction
2467 2466 back locally is ineffective (someone else may already have pulled
2468 2467 the changes). Furthermore, a race is possible with readers of the
2469 2468 repository; for example an in-progress pull from the repository
2470 2469 may fail if a rollback is performed.
2471 2470 """
2472 2471 repo.rollback()
2473 2472
2474 2473 def root(ui, repo):
2475 2474 """print the root (top) of the current working dir
2476 2475
2477 2476 Print the root directory of the current repository.
2478 2477 """
2479 2478 ui.write(repo.root + "\n")
2480 2479
2481 2480 def serve(ui, repo, **opts):
2482 2481 """export the repository via HTTP
2483 2482
2484 2483 Start a local HTTP repository browser and pull server.
2485 2484
2486 2485 By default, the server logs accesses to stdout and errors to
2487 2486 stderr. Use the "-A" and "-E" options to log to files.
2488 2487 """
2489 2488
2490 2489 if opts["stdio"]:
2491 2490 if repo is None:
2492 2491 raise hg.RepoError(_("There is no Mercurial repository here"
2493 2492 " (.hg not found)"))
2494 2493 s = sshserver.sshserver(ui, repo)
2495 2494 s.serve_forever()
2496 2495
2497 2496 optlist = ("name templates style address port ipv6"
2498 2497 " accesslog errorlog webdir_conf")
2499 2498 for o in optlist.split():
2500 2499 if opts[o]:
2501 2500 ui.setconfig("web", o, str(opts[o]))
2502 2501
2503 2502 if repo is None and not ui.config("web", "webdir_conf"):
2504 2503 raise hg.RepoError(_("There is no Mercurial repository here"
2505 2504 " (.hg not found)"))
2506 2505
2507 2506 if opts['daemon'] and not opts['daemon_pipefds']:
2508 2507 rfd, wfd = os.pipe()
2509 2508 args = sys.argv[:]
2510 2509 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
2511 2510 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
2512 2511 args[0], args)
2513 2512 os.close(wfd)
2514 2513 os.read(rfd, 1)
2515 2514 os._exit(0)
2516 2515
2517 2516 try:
2518 2517 httpd = hgweb.server.create_server(ui, repo)
2519 2518 except socket.error, inst:
2520 2519 raise util.Abort(_('cannot start server: %s') % inst.args[1])
2521 2520
2522 2521 if ui.verbose:
2523 2522 addr, port = httpd.socket.getsockname()
2524 2523 if addr == '0.0.0.0':
2525 2524 addr = socket.gethostname()
2526 2525 else:
2527 2526 try:
2528 2527 addr = socket.gethostbyaddr(addr)[0]
2529 2528 except socket.error:
2530 2529 pass
2531 2530 if port != 80:
2532 2531 ui.status(_('listening at http://%s:%d/\n') % (addr, port))
2533 2532 else:
2534 2533 ui.status(_('listening at http://%s/\n') % addr)
2535 2534
2536 2535 if opts['pid_file']:
2537 2536 fp = open(opts['pid_file'], 'w')
2538 2537 fp.write(str(os.getpid()) + '\n')
2539 2538 fp.close()
2540 2539
2541 2540 if opts['daemon_pipefds']:
2542 2541 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
2543 2542 os.close(rfd)
2544 2543 os.write(wfd, 'y')
2545 2544 os.close(wfd)
2546 2545 sys.stdout.flush()
2547 2546 sys.stderr.flush()
2548 2547 fd = os.open(util.nulldev, os.O_RDWR)
2549 2548 if fd != 0: os.dup2(fd, 0)
2550 2549 if fd != 1: os.dup2(fd, 1)
2551 2550 if fd != 2: os.dup2(fd, 2)
2552 2551 if fd not in (0, 1, 2): os.close(fd)
2553 2552
2554 2553 httpd.serve_forever()
2555 2554
2556 2555 def status(ui, repo, *pats, **opts):
2557 2556 """show changed files in the working directory
2558 2557
2559 2558 Show status of files in the repository. If names are given, only
2560 2559 files that match are shown. Files that are clean or ignored, are
2561 2560 not listed unless -c (clean), -i (ignored) or -A is given.
2562 2561
2563 2562 If one revision is given, it is used as the base revision.
2564 2563 If two revisions are given, the difference between them is shown.
2565 2564
2566 2565 The codes used to show the status of files are:
2567 2566 M = modified
2568 2567 A = added
2569 2568 R = removed
2570 2569 C = clean
2571 2570 ! = deleted, but still tracked
2572 2571 ? = not tracked
2573 2572 I = ignored (not shown by default)
2574 2573 = the previous added file was copied from here
2575 2574 """
2576 2575
2577 2576 all = opts['all']
2578 2577 node1, node2 = cmdutil.revpair(ui, repo, opts.get('rev'))
2579 2578
2580 2579 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2581 2580 cwd = (pats and repo.getcwd()) or ''
2582 2581 modified, added, removed, deleted, unknown, ignored, clean = [
2583 2582 [util.pathto(cwd, x) for x in n]
2584 2583 for n in repo.status(node1=node1, node2=node2, files=files,
2585 2584 match=matchfn,
2586 2585 list_ignored=all or opts['ignored'],
2587 2586 list_clean=all or opts['clean'])]
2588 2587
2589 2588 changetypes = (('modified', 'M', modified),
2590 2589 ('added', 'A', added),
2591 2590 ('removed', 'R', removed),
2592 2591 ('deleted', '!', deleted),
2593 2592 ('unknown', '?', unknown),
2594 2593 ('ignored', 'I', ignored))
2595 2594
2596 2595 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2597 2596
2598 2597 end = opts['print0'] and '\0' or '\n'
2599 2598
2600 2599 for opt, char, changes in ([ct for ct in explicit_changetypes
2601 2600 if all or opts[ct[0]]]
2602 2601 or changetypes):
2603 2602 if opts['no_status']:
2604 2603 format = "%%s%s" % end
2605 2604 else:
2606 2605 format = "%s %%s%s" % (char, end)
2607 2606
2608 2607 for f in changes:
2609 2608 ui.write(format % f)
2610 2609 if ((all or opts.get('copies')) and not opts.get('no_status')):
2611 2610 copied = repo.dirstate.copied(f)
2612 2611 if copied:
2613 2612 ui.write(' %s%s' % (copied, end))
2614 2613
2615 2614 def tag(ui, repo, name, rev_=None, **opts):
2616 2615 """add a tag for the current tip or a given revision
2617 2616
2618 2617 Name a particular revision using <name>.
2619 2618
2620 2619 Tags are used to name particular revisions of the repository and are
2621 2620 very useful to compare different revision, to go back to significant
2622 2621 earlier versions or to mark branch points as releases, etc.
2623 2622
2624 2623 If no revision is given, the parent of the working directory is used.
2625 2624
2626 2625 To facilitate version control, distribution, and merging of tags,
2627 2626 they are stored as a file named ".hgtags" which is managed
2628 2627 similarly to other project files and can be hand-edited if
2629 2628 necessary. The file '.hg/localtags' is used for local tags (not
2630 2629 shared among repositories).
2631 2630 """
2632 2631 if name in ['tip', '.']:
2633 2632 raise util.Abort(_("the name '%s' is reserved") % name)
2634 2633 if rev_ is not None:
2635 2634 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2636 2635 "please use 'hg tag [-r REV] NAME' instead\n"))
2637 2636 if opts['rev']:
2638 2637 raise util.Abort(_("use only one form to specify the revision"))
2639 2638 if opts['rev']:
2640 2639 rev_ = opts['rev']
2641 2640 if not rev_ and repo.dirstate.parents()[1] != nullid:
2642 2641 raise util.Abort(_('uncommitted merge - please provide a '
2643 2642 'specific revision'))
2644 2643 r = repo.changectx(rev_).node()
2645 2644
2646 2645 message = opts['message']
2647 2646 if not message:
2648 2647 message = _('Added tag %s for changeset %s') % (name, short(r))
2649 2648
2650 2649 repo.tag(name, r, message, opts['local'], opts['user'], opts['date'])
2651 2650
2652 2651 def tags(ui, repo):
2653 2652 """list repository tags
2654 2653
2655 2654 List the repository tags.
2656 2655
2657 2656 This lists both regular and local tags.
2658 2657 """
2659 2658
2660 2659 l = repo.tagslist()
2661 2660 l.reverse()
2662 2661 hexfunc = ui.debugflag and hex or short
2663 2662 for t, n in l:
2664 2663 try:
2665 2664 r = "%5d:%s" % (repo.changelog.rev(n), hexfunc(n))
2666 2665 except KeyError:
2667 2666 r = " ?:?"
2668 2667 if ui.quiet:
2669 2668 ui.write("%s\n" % t)
2670 2669 else:
2671 2670 ui.write("%-30s %s\n" % (t, r))
2672 2671
2673 2672 def tip(ui, repo, **opts):
2674 2673 """show the tip revision
2675 2674
2676 2675 Show the tip revision.
2677 2676 """
2678 2677 n = repo.changelog.tip()
2679 2678 br = None
2680 2679 if opts['branches']:
2681 2680 ui.warn(_("the --branches option is deprecated, "
2682 2681 "please use 'hg branches' instead\n"))
2683 2682 br = repo.branchlookup([n])
2684 2683 show_changeset(ui, repo, opts).show(changenode=n, brinfo=br)
2685 2684 if opts['patch']:
2686 2685 patch.diff(repo, repo.changelog.parents(n)[0], n)
2687 2686
2688 2687 def unbundle(ui, repo, fname, **opts):
2689 2688 """apply a changegroup file
2690 2689
2691 2690 Apply a compressed changegroup file generated by the bundle
2692 2691 command.
2693 2692 """
2694 2693 f = urllib.urlopen(fname)
2695 2694
2696 2695 header = f.read(6)
2697 2696 if not header.startswith("HG"):
2698 2697 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2699 2698 elif not header.startswith("HG10"):
2700 2699 raise util.Abort(_("%s: unknown bundle version") % fname)
2701 2700 elif header == "HG10BZ":
2702 2701 def generator(f):
2703 2702 zd = bz2.BZ2Decompressor()
2704 2703 zd.decompress("BZ")
2705 2704 for chunk in f:
2706 2705 yield zd.decompress(chunk)
2707 2706 elif header == "HG10UN":
2708 2707 def generator(f):
2709 2708 for chunk in f:
2710 2709 yield chunk
2711 2710 else:
2712 2711 raise util.Abort(_("%s: unknown bundle compression type")
2713 2712 % fname)
2714 2713 gen = generator(util.filechunkiter(f, 4096))
2715 2714 modheads = repo.addchangegroup(util.chunkbuffer(gen), 'unbundle',
2716 2715 'bundle:' + fname)
2717 2716 return postincoming(ui, repo, modheads, opts['update'])
2718 2717
2719 2718 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2720 2719 branch=None):
2721 2720 """update or merge working directory
2722 2721
2723 2722 Update the working directory to the specified revision.
2724 2723
2725 2724 If there are no outstanding changes in the working directory and
2726 2725 there is a linear relationship between the current version and the
2727 2726 requested version, the result is the requested version.
2728 2727
2729 2728 To merge the working directory with another revision, use the
2730 2729 merge command.
2731 2730
2732 2731 By default, update will refuse to run if doing so would require
2733 2732 merging or discarding local changes.
2734 2733 """
2735 2734 node = _lookup(repo, node, branch)
2736 2735 if clean:
2737 2736 return hg.clean(repo, node)
2738 2737 else:
2739 2738 return hg.update(repo, node)
2740 2739
2741 2740 def _lookup(repo, node, branch=None):
2742 2741 if branch:
2743 2742 repo.ui.warn(_("the --branch option is deprecated, "
2744 2743 "please use 'hg branch' instead\n"))
2745 2744 br = repo.branchlookup(branch=branch)
2746 2745 found = []
2747 2746 for x in br:
2748 2747 if branch in br[x]:
2749 2748 found.append(x)
2750 2749 if len(found) > 1:
2751 2750 repo.ui.warn(_("Found multiple heads for %s\n") % branch)
2752 2751 for x in found:
2753 2752 show_changeset(ui, repo, {}).show(changenode=x, brinfo=br)
2754 2753 raise util.Abort("")
2755 2754 if len(found) == 1:
2756 2755 node = found[0]
2757 2756 repo.ui.warn(_("Using head %s for branch %s\n")
2758 2757 % (short(node), branch))
2759 2758 else:
2760 2759 raise util.Abort(_("branch %s not found") % branch)
2761 2760 else:
2762 2761 node = node and repo.lookup(node) or repo.changelog.tip()
2763 2762 return node
2764 2763
2765 2764 def verify(ui, repo):
2766 2765 """verify the integrity of the repository
2767 2766
2768 2767 Verify the integrity of the current repository.
2769 2768
2770 2769 This will perform an extensive check of the repository's
2771 2770 integrity, validating the hashes and checksums of each entry in
2772 2771 the changelog, manifest, and tracked files, as well as the
2773 2772 integrity of their crosslinks and indices.
2774 2773 """
2775 2774 return hg.verify(repo)
2776 2775
2777 2776 # Command options and aliases are listed here, alphabetically
2778 2777
2779 2778 globalopts = [
2780 2779 ('R', 'repository', '',
2781 2780 _('repository root directory or symbolic path name')),
2782 2781 ('', 'cwd', '', _('change working directory')),
2783 2782 ('y', 'noninteractive', None,
2784 2783 _('do not prompt, assume \'yes\' for any required answers')),
2785 2784 ('q', 'quiet', None, _('suppress output')),
2786 2785 ('v', 'verbose', None, _('enable additional output')),
2787 2786 ('', 'config', [], _('set/override config option')),
2788 2787 ('', 'debug', None, _('enable debugging output')),
2789 2788 ('', 'debugger', None, _('start debugger')),
2790 2789 ('', 'lsprof', None, _('print improved command execution profile')),
2791 2790 ('', 'traceback', None, _('print traceback on exception')),
2792 2791 ('', 'time', None, _('time how long the command takes')),
2793 2792 ('', 'profile', None, _('print command execution profile')),
2794 2793 ('', 'version', None, _('output version information and exit')),
2795 2794 ('h', 'help', None, _('display help and exit')),
2796 2795 ]
2797 2796
2798 2797 dryrunopts = [('n', 'dry-run', None,
2799 2798 _('do not perform actions, just print output'))]
2800 2799
2801 2800 remoteopts = [
2802 2801 ('e', 'ssh', '', _('specify ssh command to use')),
2803 2802 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2804 2803 ]
2805 2804
2806 2805 walkopts = [
2807 2806 ('I', 'include', [], _('include names matching the given patterns')),
2808 2807 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2809 2808 ]
2810 2809
2811 2810 table = {
2812 2811 "^add":
2813 2812 (add,
2814 2813 walkopts + dryrunopts,
2815 2814 _('hg add [OPTION]... [FILE]...')),
2816 2815 "addremove":
2817 2816 (addremove,
2818 2817 [('s', 'similarity', '',
2819 2818 _('guess renamed files by similarity (0<=s<=100)')),
2820 2819 ] + walkopts + dryrunopts,
2821 2820 _('hg addremove [OPTION]... [FILE]...')),
2822 2821 "^annotate":
2823 2822 (annotate,
2824 2823 [('r', 'rev', '', _('annotate the specified revision')),
2825 2824 ('f', 'follow', None, _('follow file copies and renames')),
2826 2825 ('a', 'text', None, _('treat all files as text')),
2827 2826 ('u', 'user', None, _('list the author')),
2828 2827 ('d', 'date', None, _('list the date')),
2829 2828 ('n', 'number', None, _('list the revision number (default)')),
2830 2829 ('c', 'changeset', None, _('list the changeset')),
2831 2830 ] + walkopts,
2832 2831 _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')),
2833 2832 "archive":
2834 2833 (archive,
2835 2834 [('', 'no-decode', None, _('do not pass files through decoders')),
2836 2835 ('p', 'prefix', '', _('directory prefix for files in archive')),
2837 2836 ('r', 'rev', '', _('revision to distribute')),
2838 2837 ('t', 'type', '', _('type of distribution to create')),
2839 2838 ] + walkopts,
2840 2839 _('hg archive [OPTION]... DEST')),
2841 2840 "backout":
2842 2841 (backout,
2843 2842 [('', 'merge', None,
2844 2843 _('merge with old dirstate parent after backout')),
2845 2844 ('m', 'message', '', _('use <text> as commit message')),
2846 2845 ('l', 'logfile', '', _('read commit message from <file>')),
2847 2846 ('d', 'date', '', _('record datecode as commit date')),
2848 2847 ('', 'parent', '', _('parent to choose when backing out merge')),
2849 2848 ('u', 'user', '', _('record user as committer')),
2850 2849 ] + walkopts,
2851 2850 _('hg backout [OPTION]... REV')),
2852 2851 "branch": (branch, [], _('hg branch [NAME]')),
2853 2852 "branches": (branches, [], _('hg branches')),
2854 2853 "bundle":
2855 2854 (bundle,
2856 2855 [('f', 'force', None,
2857 2856 _('run even when remote repository is unrelated')),
2858 2857 ('r', 'rev', [],
2859 2858 _('a changeset you would like to bundle')),
2860 2859 ('', 'base', [],
2861 2860 _('a base changeset to specify instead of a destination')),
2862 2861 ] + remoteopts,
2863 2862 _('hg bundle [--base REV]... [--rev REV]... FILE [DEST]')),
2864 2863 "cat":
2865 2864 (cat,
2866 2865 [('o', 'output', '', _('print output to file with formatted name')),
2867 2866 ('r', 'rev', '', _('print the given revision')),
2868 2867 ] + walkopts,
2869 2868 _('hg cat [OPTION]... FILE...')),
2870 2869 "^clone":
2871 2870 (clone,
2872 2871 [('U', 'noupdate', None, _('do not update the new working directory')),
2873 2872 ('r', 'rev', [],
2874 2873 _('a changeset you would like to have after cloning')),
2875 2874 ('', 'pull', None, _('use pull protocol to copy metadata')),
2876 2875 ('', 'uncompressed', None,
2877 2876 _('use uncompressed transfer (fast over LAN)')),
2878 2877 ] + remoteopts,
2879 2878 _('hg clone [OPTION]... SOURCE [DEST]')),
2880 2879 "^commit|ci":
2881 2880 (commit,
2882 2881 [('A', 'addremove', None,
2883 2882 _('mark new/missing files as added/removed before committing')),
2884 2883 ('m', 'message', '', _('use <text> as commit message')),
2885 2884 ('l', 'logfile', '', _('read the commit message from <file>')),
2886 2885 ('d', 'date', '', _('record datecode as commit date')),
2887 2886 ('u', 'user', '', _('record user as commiter')),
2888 2887 ] + walkopts,
2889 2888 _('hg commit [OPTION]... [FILE]...')),
2890 2889 "copy|cp":
2891 2890 (copy,
2892 2891 [('A', 'after', None, _('record a copy that has already occurred')),
2893 2892 ('f', 'force', None,
2894 2893 _('forcibly copy over an existing managed file')),
2895 2894 ] + walkopts + dryrunopts,
2896 2895 _('hg copy [OPTION]... [SOURCE]... DEST')),
2897 2896 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2898 2897 "debugcomplete":
2899 2898 (debugcomplete,
2900 2899 [('o', 'options', None, _('show the command options'))],
2901 2900 _('debugcomplete [-o] CMD')),
2902 2901 "debugrebuildstate":
2903 2902 (debugrebuildstate,
2904 2903 [('r', 'rev', '', _('revision to rebuild to'))],
2905 2904 _('debugrebuildstate [-r REV] [REV]')),
2906 2905 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2907 2906 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2908 2907 "debugstate": (debugstate, [], _('debugstate')),
2909 2908 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2910 2909 "debugindex": (debugindex, [], _('debugindex FILE')),
2911 2910 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2912 2911 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2913 2912 "debugwalk":
2914 2913 (debugwalk, walkopts, _('debugwalk [OPTION]... [FILE]...')),
2915 2914 "^diff":
2916 2915 (diff,
2917 2916 [('r', 'rev', [], _('revision')),
2918 2917 ('a', 'text', None, _('treat all files as text')),
2919 2918 ('p', 'show-function', None,
2920 2919 _('show which function each change is in')),
2921 2920 ('g', 'git', None, _('use git extended diff format')),
2922 2921 ('', 'nodates', None, _("don't include dates in diff headers")),
2923 2922 ('w', 'ignore-all-space', None,
2924 2923 _('ignore white space when comparing lines')),
2925 2924 ('b', 'ignore-space-change', None,
2926 2925 _('ignore changes in the amount of white space')),
2927 2926 ('B', 'ignore-blank-lines', None,
2928 2927 _('ignore changes whose lines are all blank')),
2929 2928 ] + walkopts,
2930 2929 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
2931 2930 "^export":
2932 2931 (export,
2933 2932 [('o', 'output', '', _('print output to file with formatted name')),
2934 2933 ('a', 'text', None, _('treat all files as text')),
2935 2934 ('g', 'git', None, _('use git extended diff format')),
2936 2935 ('', 'nodates', None, _("don't include dates in diff headers")),
2937 2936 ('', 'switch-parent', None, _('diff against the second parent'))],
2938 2937 _('hg export [-a] [-o OUTFILESPEC] REV...')),
2939 2938 "grep":
2940 2939 (grep,
2941 2940 [('0', 'print0', None, _('end fields with NUL')),
2942 2941 ('', 'all', None, _('print all revisions that match')),
2943 2942 ('f', 'follow', None,
2944 2943 _('follow changeset history, or file history across copies and renames')),
2945 2944 ('i', 'ignore-case', None, _('ignore case when matching')),
2946 2945 ('l', 'files-with-matches', None,
2947 2946 _('print only filenames and revs that match')),
2948 2947 ('n', 'line-number', None, _('print matching line numbers')),
2949 2948 ('r', 'rev', [], _('search in given revision range')),
2950 2949 ('u', 'user', None, _('print user who committed change')),
2951 2950 ] + walkopts,
2952 2951 _('hg grep [OPTION]... PATTERN [FILE]...')),
2953 2952 "heads":
2954 2953 (heads,
2955 2954 [('b', 'branches', None, _('show branches (DEPRECATED)')),
2956 2955 ('', 'style', '', _('display using template map file')),
2957 2956 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2958 2957 ('', 'template', '', _('display with template'))],
2959 2958 _('hg heads [-r REV]')),
2960 2959 "help": (help_, [], _('hg help [COMMAND]')),
2961 2960 "identify|id": (identify, [], _('hg identify')),
2962 2961 "import|patch":
2963 2962 (import_,
2964 2963 [('p', 'strip', 1,
2965 2964 _('directory strip option for patch. This has the same\n'
2966 2965 'meaning as the corresponding patch option')),
2967 2966 ('m', 'message', '', _('use <text> as commit message')),
2968 2967 ('b', 'base', '', _('base path (DEPRECATED)')),
2969 2968 ('f', 'force', None,
2970 2969 _('skip check for outstanding uncommitted changes'))],
2971 2970 _('hg import [-p NUM] [-m MESSAGE] [-f] PATCH...')),
2972 2971 "incoming|in": (incoming,
2973 2972 [('M', 'no-merges', None, _('do not show merges')),
2974 2973 ('f', 'force', None,
2975 2974 _('run even when remote repository is unrelated')),
2976 2975 ('', 'style', '', _('display using template map file')),
2977 2976 ('n', 'newest-first', None, _('show newest record first')),
2978 2977 ('', 'bundle', '', _('file to store the bundles into')),
2979 2978 ('p', 'patch', None, _('show patch')),
2980 2979 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
2981 2980 ('', 'template', '', _('display with template')),
2982 2981 ] + remoteopts,
2983 2982 _('hg incoming [-p] [-n] [-M] [-r REV]...'
2984 2983 ' [--bundle FILENAME] [SOURCE]')),
2985 2984 "^init":
2986 2985 (init, remoteopts, _('hg init [-e FILE] [--remotecmd FILE] [DEST]')),
2987 2986 "locate":
2988 2987 (locate,
2989 2988 [('r', 'rev', '', _('search the repository as it stood at rev')),
2990 2989 ('0', 'print0', None,
2991 2990 _('end filenames with NUL, for use with xargs')),
2992 2991 ('f', 'fullpath', None,
2993 2992 _('print complete paths from the filesystem root')),
2994 2993 ] + walkopts,
2995 2994 _('hg locate [OPTION]... [PATTERN]...')),
2996 2995 "^log|history":
2997 2996 (log,
2998 2997 [('b', 'branches', None, _('show branches (DEPRECATED)')),
2999 2998 ('f', 'follow', None,
3000 2999 _('follow changeset history, or file history across copies and renames')),
3001 3000 ('', 'follow-first', None,
3002 3001 _('only follow the first parent of merge changesets')),
3003 3002 ('C', 'copies', None, _('show copied files')),
3004 3003 ('k', 'keyword', [], _('search for a keyword')),
3005 3004 ('l', 'limit', '', _('limit number of changes displayed')),
3006 3005 ('r', 'rev', [], _('show the specified revision or range')),
3007 3006 ('M', 'no-merges', None, _('do not show merges')),
3008 3007 ('', 'style', '', _('display using template map file')),
3009 3008 ('m', 'only-merges', None, _('show only merges')),
3010 3009 ('p', 'patch', None, _('show patch')),
3011 3010 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
3012 3011 ('', 'template', '', _('display with template')),
3013 3012 ] + walkopts,
3014 3013 _('hg log [OPTION]... [FILE]')),
3015 3014 "manifest": (manifest, [], _('hg manifest [REV]')),
3016 3015 "merge":
3017 3016 (merge,
3018 3017 [('b', 'branch', '', _('merge with head of a specific branch (DEPRECATED)')),
3019 3018 ('f', 'force', None, _('force a merge with outstanding changes'))],
3020 3019 _('hg merge [-f] [REV]')),
3021 3020 "outgoing|out": (outgoing,
3022 3021 [('M', 'no-merges', None, _('do not show merges')),
3023 3022 ('f', 'force', None,
3024 3023 _('run even when remote repository is unrelated')),
3025 3024 ('p', 'patch', None, _('show patch')),
3026 3025 ('', 'style', '', _('display using template map file')),
3027 3026 ('r', 'rev', [], _('a specific revision you would like to push')),
3028 3027 ('n', 'newest-first', None, _('show newest record first')),
3029 3028 ('', 'template', '', _('display with template')),
3030 3029 ] + remoteopts,
3031 3030 _('hg outgoing [-M] [-p] [-n] [-r REV]... [DEST]')),
3032 3031 "^parents":
3033 3032 (parents,
3034 3033 [('b', 'branches', None, _('show branches (DEPRECATED)')),
3035 3034 ('r', 'rev', '', _('show parents from the specified rev')),
3036 3035 ('', 'style', '', _('display using template map file')),
3037 3036 ('', 'template', '', _('display with template'))],
3038 3037 _('hg parents [-r REV] [FILE]')),
3039 3038 "paths": (paths, [], _('hg paths [NAME]')),
3040 3039 "^pull":
3041 3040 (pull,
3042 3041 [('u', 'update', None,
3043 3042 _('update to new tip if changesets were pulled')),
3044 3043 ('f', 'force', None,
3045 3044 _('run even when remote repository is unrelated')),
3046 3045 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
3047 3046 ] + remoteopts,
3048 3047 _('hg pull [-u] [-r REV]... [-e FILE] [--remotecmd FILE] [SOURCE]')),
3049 3048 "^push":
3050 3049 (push,
3051 3050 [('f', 'force', None, _('force push')),
3052 3051 ('r', 'rev', [], _('a specific revision you would like to push')),
3053 3052 ] + remoteopts,
3054 3053 _('hg push [-f] [-r REV]... [-e FILE] [--remotecmd FILE] [DEST]')),
3055 3054 "debugrawcommit|rawcommit":
3056 3055 (rawcommit,
3057 3056 [('p', 'parent', [], _('parent')),
3058 3057 ('d', 'date', '', _('date code')),
3059 3058 ('u', 'user', '', _('user')),
3060 3059 ('F', 'files', '', _('file list')),
3061 3060 ('m', 'message', '', _('commit message')),
3062 3061 ('l', 'logfile', '', _('commit message file'))],
3063 3062 _('hg debugrawcommit [OPTION]... [FILE]...')),
3064 3063 "recover": (recover, [], _('hg recover')),
3065 3064 "^remove|rm":
3066 3065 (remove,
3067 3066 [('A', 'after', None, _('record remove that has already occurred')),
3068 3067 ('f', 'force', None, _('remove file even if modified')),
3069 3068 ] + walkopts,
3070 3069 _('hg remove [OPTION]... FILE...')),
3071 3070 "rename|mv":
3072 3071 (rename,
3073 3072 [('A', 'after', None, _('record a rename that has already occurred')),
3074 3073 ('f', 'force', None,
3075 3074 _('forcibly copy over an existing managed file')),
3076 3075 ] + walkopts + dryrunopts,
3077 3076 _('hg rename [OPTION]... SOURCE... DEST')),
3078 3077 "^revert":
3079 3078 (revert,
3080 3079 [('a', 'all', None, _('revert all changes when no arguments given')),
3081 3080 ('r', 'rev', '', _('revision to revert to')),
3082 3081 ('', 'no-backup', None, _('do not save backup copies of files')),
3083 3082 ] + walkopts + dryrunopts,
3084 3083 _('hg revert [-r REV] [NAME]...')),
3085 3084 "rollback": (rollback, [], _('hg rollback')),
3086 3085 "root": (root, [], _('hg root')),
3087 3086 "showconfig|debugconfig": (showconfig, [], _('showconfig [NAME]...')),
3088 3087 "^serve":
3089 3088 (serve,
3090 3089 [('A', 'accesslog', '', _('name of access log file to write to')),
3091 3090 ('d', 'daemon', None, _('run server in background')),
3092 3091 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3093 3092 ('E', 'errorlog', '', _('name of error log file to write to')),
3094 3093 ('p', 'port', 0, _('port to use (default: 8000)')),
3095 3094 ('a', 'address', '', _('address to use')),
3096 3095 ('n', 'name', '',
3097 3096 _('name to show in web pages (default: working dir)')),
3098 3097 ('', 'webdir-conf', '', _('name of the webdir config file'
3099 3098 ' (serve more than one repo)')),
3100 3099 ('', 'pid-file', '', _('name of file to write process ID to')),
3101 3100 ('', 'stdio', None, _('for remote clients')),
3102 3101 ('t', 'templates', '', _('web templates to use')),
3103 3102 ('', 'style', '', _('template style to use')),
3104 3103 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
3105 3104 _('hg serve [OPTION]...')),
3106 3105 "^status|st":
3107 3106 (status,
3108 3107 [('A', 'all', None, _('show status of all files')),
3109 3108 ('m', 'modified', None, _('show only modified files')),
3110 3109 ('a', 'added', None, _('show only added files')),
3111 3110 ('r', 'removed', None, _('show only removed files')),
3112 3111 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3113 3112 ('c', 'clean', None, _('show only files without changes')),
3114 3113 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3115 3114 ('i', 'ignored', None, _('show ignored files')),
3116 3115 ('n', 'no-status', None, _('hide status prefix')),
3117 3116 ('C', 'copies', None, _('show source of copied files')),
3118 3117 ('0', 'print0', None,
3119 3118 _('end filenames with NUL, for use with xargs')),
3120 3119 ('', 'rev', [], _('show difference from revision')),
3121 3120 ] + walkopts,
3122 3121 _('hg status [OPTION]... [FILE]...')),
3123 3122 "tag":
3124 3123 (tag,
3125 3124 [('l', 'local', None, _('make the tag local')),
3126 3125 ('m', 'message', '', _('message for tag commit log entry')),
3127 3126 ('d', 'date', '', _('record datecode as commit date')),
3128 3127 ('u', 'user', '', _('record user as commiter')),
3129 3128 ('r', 'rev', '', _('revision to tag'))],
3130 3129 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3131 3130 "tags": (tags, [], _('hg tags')),
3132 3131 "tip":
3133 3132 (tip,
3134 3133 [('b', 'branches', None, _('show branches (DEPRECATED)')),
3135 3134 ('', 'style', '', _('display using template map file')),
3136 3135 ('p', 'patch', None, _('show patch')),
3137 3136 ('', 'template', '', _('display with template'))],
3138 3137 _('hg tip [-p]')),
3139 3138 "unbundle":
3140 3139 (unbundle,
3141 3140 [('u', 'update', None,
3142 3141 _('update to new tip if changesets were unbundled'))],
3143 3142 _('hg unbundle [-u] FILE')),
3144 3143 "^update|up|checkout|co":
3145 3144 (update,
3146 3145 [('b', 'branch', '',
3147 3146 _('checkout the head of a specific branch (DEPRECATED)')),
3148 3147 ('m', 'merge', None, _('allow merging of branches (DEPRECATED)')),
3149 3148 ('C', 'clean', None, _('overwrite locally modified files')),
3150 3149 ('f', 'force', None, _('force a merge with outstanding changes'))],
3151 3150 _('hg update [-C] [-f] [REV]')),
3152 3151 "verify": (verify, [], _('hg verify')),
3153 3152 "version": (show_version, [], _('hg version')),
3154 3153 }
3155 3154
3156 3155 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3157 3156 " debugindex debugindexdot")
3158 3157 optionalrepo = ("paths serve showconfig")
3159 3158
3160 3159 def findpossible(ui, cmd):
3161 3160 """
3162 3161 Return cmd -> (aliases, command table entry)
3163 3162 for each matching command.
3164 3163 Return debug commands (or their aliases) only if no normal command matches.
3165 3164 """
3166 3165 choice = {}
3167 3166 debugchoice = {}
3168 3167 for e in table.keys():
3169 3168 aliases = e.lstrip("^").split("|")
3170 3169 found = None
3171 3170 if cmd in aliases:
3172 3171 found = cmd
3173 3172 elif not ui.config("ui", "strict"):
3174 3173 for a in aliases:
3175 3174 if a.startswith(cmd):
3176 3175 found = a
3177 3176 break
3178 3177 if found is not None:
3179 3178 if aliases[0].startswith("debug") or found.startswith("debug"):
3180 3179 debugchoice[found] = (aliases, table[e])
3181 3180 else:
3182 3181 choice[found] = (aliases, table[e])
3183 3182
3184 3183 if not choice and debugchoice:
3185 3184 choice = debugchoice
3186 3185
3187 3186 return choice
3188 3187
3189 3188 def findcmd(ui, cmd):
3190 3189 """Return (aliases, command table entry) for command string."""
3191 3190 choice = findpossible(ui, cmd)
3192 3191
3193 3192 if choice.has_key(cmd):
3194 3193 return choice[cmd]
3195 3194
3196 3195 if len(choice) > 1:
3197 3196 clist = choice.keys()
3198 3197 clist.sort()
3199 3198 raise AmbiguousCommand(cmd, clist)
3200 3199
3201 3200 if choice:
3202 3201 return choice.values()[0]
3203 3202
3204 3203 raise UnknownCommand(cmd)
3205 3204
3206 3205 def catchterm(*args):
3207 3206 raise util.SignalInterrupt
3208 3207
3209 3208 def run():
3210 3209 sys.exit(dispatch(sys.argv[1:]))
3211 3210
3212 3211 class ParseError(Exception):
3213 3212 """Exception raised on errors in parsing the command line."""
3214 3213
3215 3214 def parse(ui, args):
3216 3215 options = {}
3217 3216 cmdoptions = {}
3218 3217
3219 3218 try:
3220 3219 args = fancyopts.fancyopts(args, globalopts, options)
3221 3220 except fancyopts.getopt.GetoptError, inst:
3222 3221 raise ParseError(None, inst)
3223 3222
3224 3223 if args:
3225 3224 cmd, args = args[0], args[1:]
3226 3225 aliases, i = findcmd(ui, cmd)
3227 3226 cmd = aliases[0]
3228 3227 defaults = ui.config("defaults", cmd)
3229 3228 if defaults:
3230 3229 args = shlex.split(defaults) + args
3231 3230 c = list(i[1])
3232 3231 else:
3233 3232 cmd = None
3234 3233 c = []
3235 3234
3236 3235 # combine global options into local
3237 3236 for o in globalopts:
3238 3237 c.append((o[0], o[1], options[o[1]], o[3]))
3239 3238
3240 3239 try:
3241 3240 args = fancyopts.fancyopts(args, c, cmdoptions)
3242 3241 except fancyopts.getopt.GetoptError, inst:
3243 3242 raise ParseError(cmd, inst)
3244 3243
3245 3244 # separate global options back out
3246 3245 for o in globalopts:
3247 3246 n = o[1]
3248 3247 options[n] = cmdoptions[n]
3249 3248 del cmdoptions[n]
3250 3249
3251 3250 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
3252 3251
3253 3252 external = {}
3254 3253
3255 3254 def findext(name):
3256 3255 '''return module with given extension name'''
3257 3256 try:
3258 3257 return sys.modules[external[name]]
3259 3258 except KeyError:
3260 3259 for k, v in external.iteritems():
3261 3260 if k.endswith('.' + name) or k.endswith('/' + name) or v == name:
3262 3261 return sys.modules[v]
3263 3262 raise KeyError(name)
3264 3263
3265 3264 def load_extensions(ui):
3266 3265 added = []
3267 3266 for ext_name, load_from_name in ui.extensions():
3268 3267 if ext_name in external:
3269 3268 continue
3270 3269 try:
3271 3270 if load_from_name:
3272 3271 # the module will be loaded in sys.modules
3273 3272 # choose an unique name so that it doesn't
3274 3273 # conflicts with other modules
3275 3274 module_name = "hgext_%s" % ext_name.replace('.', '_')
3276 3275 mod = imp.load_source(module_name, load_from_name)
3277 3276 else:
3278 3277 def importh(name):
3279 3278 mod = __import__(name)
3280 3279 components = name.split('.')
3281 3280 for comp in components[1:]:
3282 3281 mod = getattr(mod, comp)
3283 3282 return mod
3284 3283 try:
3285 3284 mod = importh("hgext.%s" % ext_name)
3286 3285 except ImportError:
3287 3286 mod = importh(ext_name)
3288 3287 external[ext_name] = mod.__name__
3289 3288 added.append((mod, ext_name))
3290 3289 except (util.SignalInterrupt, KeyboardInterrupt):
3291 3290 raise
3292 3291 except Exception, inst:
3293 3292 ui.warn(_("*** failed to import extension %s: %s\n") %
3294 3293 (ext_name, inst))
3295 3294 if ui.print_exc():
3296 3295 return 1
3297 3296
3298 3297 for mod, name in added:
3299 3298 uisetup = getattr(mod, 'uisetup', None)
3300 3299 if uisetup:
3301 3300 uisetup(ui)
3302 3301 cmdtable = getattr(mod, 'cmdtable', {})
3303 3302 for t in cmdtable:
3304 3303 if t in table:
3305 3304 ui.warn(_("module %s overrides %s\n") % (name, t))
3306 3305 table.update(cmdtable)
3307 3306
3308 3307 def parseconfig(config):
3309 3308 """parse the --config options from the command line"""
3310 3309 parsed = []
3311 3310 for cfg in config:
3312 3311 try:
3313 3312 name, value = cfg.split('=', 1)
3314 3313 section, name = name.split('.', 1)
3315 3314 if not section or not name:
3316 3315 raise IndexError
3317 3316 parsed.append((section, name, value))
3318 3317 except (IndexError, ValueError):
3319 3318 raise util.Abort(_('malformed --config option: %s') % cfg)
3320 3319 return parsed
3321 3320
3322 3321 def dispatch(args):
3323 3322 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
3324 3323 num = getattr(signal, name, None)
3325 3324 if num: signal.signal(num, catchterm)
3326 3325
3327 3326 try:
3328 3327 u = ui.ui(traceback='--traceback' in sys.argv[1:])
3329 3328 except util.Abort, inst:
3330 3329 sys.stderr.write(_("abort: %s\n") % inst)
3331 3330 return -1
3332 3331
3333 3332 load_extensions(u)
3334 3333 u.addreadhook(load_extensions)
3335 3334
3336 3335 try:
3337 3336 cmd, func, args, options, cmdoptions = parse(u, args)
3338 3337 if options["time"]:
3339 3338 def get_times():
3340 3339 t = os.times()
3341 3340 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
3342 3341 t = (t[0], t[1], t[2], t[3], time.clock())
3343 3342 return t
3344 3343 s = get_times()
3345 3344 def print_time():
3346 3345 t = get_times()
3347 3346 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
3348 3347 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
3349 3348 atexit.register(print_time)
3350 3349
3351 3350 # enter the debugger before command execution
3352 3351 if options['debugger']:
3353 3352 pdb.set_trace()
3354 3353
3355 3354 try:
3356 3355 if options['cwd']:
3357 3356 try:
3358 3357 os.chdir(options['cwd'])
3359 3358 except OSError, inst:
3360 3359 raise util.Abort('%s: %s' %
3361 3360 (options['cwd'], inst.strerror))
3362 3361
3363 3362 u.updateopts(options["verbose"], options["debug"], options["quiet"],
3364 3363 not options["noninteractive"], options["traceback"],
3365 3364 parseconfig(options["config"]))
3366 3365
3367 3366 path = u.expandpath(options["repository"]) or ""
3368 3367 repo = path and hg.repository(u, path=path) or None
3369 3368 if repo and not repo.local():
3370 3369 raise util.Abort(_("repository '%s' is not local") % path)
3371 3370
3372 3371 if options['help']:
3373 3372 return help_(u, cmd, options['version'])
3374 3373 elif options['version']:
3375 3374 return show_version(u)
3376 3375 elif not cmd:
3377 3376 return help_(u, 'shortlist')
3378 3377
3379 3378 if cmd not in norepo.split():
3380 3379 try:
3381 3380 if not repo:
3382 3381 repo = hg.repository(u, path=path)
3383 3382 u = repo.ui
3384 3383 for name in external.itervalues():
3385 3384 mod = sys.modules[name]
3386 3385 if hasattr(mod, 'reposetup'):
3387 3386 mod.reposetup(u, repo)
3388 3387 hg.repo_setup_hooks.append(mod.reposetup)
3389 3388 except hg.RepoError:
3390 3389 if cmd not in optionalrepo.split():
3391 3390 raise
3392 3391 d = lambda: func(u, repo, *args, **cmdoptions)
3393 3392 else:
3394 3393 d = lambda: func(u, *args, **cmdoptions)
3395 3394
3396 3395 try:
3397 3396 if options['profile']:
3398 3397 import hotshot, hotshot.stats
3399 3398 prof = hotshot.Profile("hg.prof")
3400 3399 try:
3401 3400 try:
3402 3401 return prof.runcall(d)
3403 3402 except:
3404 3403 try:
3405 3404 u.warn(_('exception raised - generating '
3406 3405 'profile anyway\n'))
3407 3406 except:
3408 3407 pass
3409 3408 raise
3410 3409 finally:
3411 3410 prof.close()
3412 3411 stats = hotshot.stats.load("hg.prof")
3413 3412 stats.strip_dirs()
3414 3413 stats.sort_stats('time', 'calls')
3415 3414 stats.print_stats(40)
3416 3415 elif options['lsprof']:
3417 3416 try:
3418 3417 from mercurial import lsprof
3419 3418 except ImportError:
3420 3419 raise util.Abort(_(
3421 3420 'lsprof not available - install from '
3422 3421 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
3423 3422 p = lsprof.Profiler()
3424 3423 p.enable(subcalls=True)
3425 3424 try:
3426 3425 return d()
3427 3426 finally:
3428 3427 p.disable()
3429 3428 stats = lsprof.Stats(p.getstats())
3430 3429 stats.sort()
3431 3430 stats.pprint(top=10, file=sys.stderr, climit=5)
3432 3431 else:
3433 3432 return d()
3434 3433 finally:
3435 3434 u.flush()
3436 3435 except:
3437 3436 # enter the debugger when we hit an exception
3438 3437 if options['debugger']:
3439 3438 pdb.post_mortem(sys.exc_info()[2])
3440 3439 u.print_exc()
3441 3440 raise
3442 3441 except ParseError, inst:
3443 3442 if inst.args[0]:
3444 3443 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
3445 3444 help_(u, inst.args[0])
3446 3445 else:
3447 3446 u.warn(_("hg: %s\n") % inst.args[1])
3448 3447 help_(u, 'shortlist')
3449 3448 except AmbiguousCommand, inst:
3450 3449 u.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
3451 3450 (inst.args[0], " ".join(inst.args[1])))
3452 3451 except UnknownCommand, inst:
3453 3452 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
3454 3453 help_(u, 'shortlist')
3455 3454 except hg.RepoError, inst:
3456 3455 u.warn(_("abort: %s!\n") % inst)
3457 3456 except lock.LockHeld, inst:
3458 3457 if inst.errno == errno.ETIMEDOUT:
3459 3458 reason = _('timed out waiting for lock held by %s') % inst.locker
3460 3459 else:
3461 3460 reason = _('lock held by %s') % inst.locker
3462 3461 u.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
3463 3462 except lock.LockUnavailable, inst:
3464 3463 u.warn(_("abort: could not lock %s: %s\n") %
3465 3464 (inst.desc or inst.filename, inst.strerror))
3466 3465 except revlog.RevlogError, inst:
3467 3466 u.warn(_("abort: %s!\n") % inst)
3468 3467 except util.SignalInterrupt:
3469 3468 u.warn(_("killed!\n"))
3470 3469 except KeyboardInterrupt:
3471 3470 try:
3472 3471 u.warn(_("interrupted!\n"))
3473 3472 except IOError, inst:
3474 3473 if inst.errno == errno.EPIPE:
3475 3474 if u.debugflag:
3476 3475 u.warn(_("\nbroken pipe\n"))
3477 3476 else:
3478 3477 raise
3479 3478 except IOError, inst:
3480 3479 if hasattr(inst, "code"):
3481 3480 u.warn(_("abort: %s\n") % inst)
3482 3481 elif hasattr(inst, "reason"):
3483 3482 u.warn(_("abort: error: %s\n") % inst.reason[1])
3484 3483 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
3485 3484 if u.debugflag:
3486 3485 u.warn(_("broken pipe\n"))
3487 3486 elif getattr(inst, "strerror", None):
3488 3487 if getattr(inst, "filename", None):
3489 3488 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3490 3489 else:
3491 3490 u.warn(_("abort: %s\n") % inst.strerror)
3492 3491 else:
3493 3492 raise
3494 3493 except OSError, inst:
3495 3494 if getattr(inst, "filename", None):
3496 3495 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3497 3496 else:
3498 3497 u.warn(_("abort: %s\n") % inst.strerror)
3499 3498 except util.Abort, inst:
3500 3499 u.warn(_("abort: %s\n") % inst)
3501 3500 except TypeError, inst:
3502 3501 # was this an argument error?
3503 3502 tb = traceback.extract_tb(sys.exc_info()[2])
3504 3503 if len(tb) > 2: # no
3505 3504 raise
3506 3505 u.debug(inst, "\n")
3507 3506 u.warn(_("%s: invalid arguments\n") % cmd)
3508 3507 help_(u, cmd)
3509 3508 except SystemExit, inst:
3510 3509 # Commands shouldn't sys.exit directly, but give a return code.
3511 3510 # Just in case catch this and and pass exit code to caller.
3512 3511 return inst.code
3513 3512 except:
3514 3513 u.warn(_("** unknown exception encountered, details follow\n"))
3515 3514 u.warn(_("** report bug details to "
3516 3515 "http://www.selenic.com/mercurial/bts\n"))
3517 3516 u.warn(_("** or mercurial@selenic.com\n"))
3518 3517 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
3519 3518 % version.get_version())
3520 3519 raise
3521 3520
3522 3521 return -1
General Comments 0
You need to be logged in to leave comments. Login now