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