##// END OF EJS Templates
walk: remove cmdutil.walk
Matt Mackall -
r6585:d3d1d39d default
parent child Browse files
Show More
@@ -1,1187 +1,1183
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import hex, nullid, nullrev, short
9 9 from i18n import _
10 10 import os, sys, bisect, stat
11 11 import mdiff, bdiff, util, templater, templatefilters, patch, errno
12 12 import match as _match
13 13
14 14 revrangesep = ':'
15 15
16 16 class UnknownCommand(Exception):
17 17 """Exception raised if command is not in the command table."""
18 18 class AmbiguousCommand(Exception):
19 19 """Exception raised if command shortcut matches more than one command."""
20 20
21 21 def findpossible(ui, cmd, table):
22 22 """
23 23 Return cmd -> (aliases, command table entry)
24 24 for each matching command.
25 25 Return debug commands (or their aliases) only if no normal command matches.
26 26 """
27 27 choice = {}
28 28 debugchoice = {}
29 29 for e in table.keys():
30 30 aliases = e.lstrip("^").split("|")
31 31 found = None
32 32 if cmd in aliases:
33 33 found = cmd
34 34 elif not ui.config("ui", "strict"):
35 35 for a in aliases:
36 36 if a.startswith(cmd):
37 37 found = a
38 38 break
39 39 if found is not None:
40 40 if aliases[0].startswith("debug") or found.startswith("debug"):
41 41 debugchoice[found] = (aliases, table[e])
42 42 else:
43 43 choice[found] = (aliases, table[e])
44 44
45 45 if not choice and debugchoice:
46 46 choice = debugchoice
47 47
48 48 return choice
49 49
50 50 def findcmd(ui, cmd, table):
51 51 """Return (aliases, command table entry) for command string."""
52 52 choice = findpossible(ui, cmd, table)
53 53
54 54 if cmd in choice:
55 55 return choice[cmd]
56 56
57 57 if len(choice) > 1:
58 58 clist = choice.keys()
59 59 clist.sort()
60 60 raise AmbiguousCommand(cmd, clist)
61 61
62 62 if choice:
63 63 return choice.values()[0]
64 64
65 65 raise UnknownCommand(cmd)
66 66
67 67 def bail_if_changed(repo):
68 68 if repo.dirstate.parents()[1] != nullid:
69 69 raise util.Abort(_('outstanding uncommitted merge'))
70 70 modified, added, removed, deleted = repo.status()[:4]
71 71 if modified or added or removed or deleted:
72 72 raise util.Abort(_("outstanding uncommitted changes"))
73 73
74 74 def logmessage(opts):
75 75 """ get the log message according to -m and -l option """
76 76 message = opts['message']
77 77 logfile = opts['logfile']
78 78
79 79 if message and logfile:
80 80 raise util.Abort(_('options --message and --logfile are mutually '
81 81 'exclusive'))
82 82 if not message and logfile:
83 83 try:
84 84 if logfile == '-':
85 85 message = sys.stdin.read()
86 86 else:
87 87 message = open(logfile).read()
88 88 except IOError, inst:
89 89 raise util.Abort(_("can't read commit message '%s': %s") %
90 90 (logfile, inst.strerror))
91 91 return message
92 92
93 93 def loglimit(opts):
94 94 """get the log limit according to option -l/--limit"""
95 95 limit = opts.get('limit')
96 96 if limit:
97 97 try:
98 98 limit = int(limit)
99 99 except ValueError:
100 100 raise util.Abort(_('limit must be a positive integer'))
101 101 if limit <= 0: raise util.Abort(_('limit must be positive'))
102 102 else:
103 103 limit = sys.maxint
104 104 return limit
105 105
106 106 def setremoteconfig(ui, opts):
107 107 "copy remote options to ui tree"
108 108 if opts.get('ssh'):
109 109 ui.setconfig("ui", "ssh", opts['ssh'])
110 110 if opts.get('remotecmd'):
111 111 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
112 112
113 113 def revpair(repo, revs):
114 114 '''return pair of nodes, given list of revisions. second item can
115 115 be None, meaning use working dir.'''
116 116
117 117 def revfix(repo, val, defval):
118 118 if not val and val != 0 and defval is not None:
119 119 val = defval
120 120 return repo.lookup(val)
121 121
122 122 if not revs:
123 123 return repo.dirstate.parents()[0], None
124 124 end = None
125 125 if len(revs) == 1:
126 126 if revrangesep in revs[0]:
127 127 start, end = revs[0].split(revrangesep, 1)
128 128 start = revfix(repo, start, 0)
129 129 end = revfix(repo, end, repo.changelog.count() - 1)
130 130 else:
131 131 start = revfix(repo, revs[0], None)
132 132 elif len(revs) == 2:
133 133 if revrangesep in revs[0] or revrangesep in revs[1]:
134 134 raise util.Abort(_('too many revisions specified'))
135 135 start = revfix(repo, revs[0], None)
136 136 end = revfix(repo, revs[1], None)
137 137 else:
138 138 raise util.Abort(_('too many revisions specified'))
139 139 return start, end
140 140
141 141 def revrange(repo, revs):
142 142 """Yield revision as strings from a list of revision specifications."""
143 143
144 144 def revfix(repo, val, defval):
145 145 if not val and val != 0 and defval is not None:
146 146 return defval
147 147 return repo.changelog.rev(repo.lookup(val))
148 148
149 149 seen, l = {}, []
150 150 for spec in revs:
151 151 if revrangesep in spec:
152 152 start, end = spec.split(revrangesep, 1)
153 153 start = revfix(repo, start, 0)
154 154 end = revfix(repo, end, repo.changelog.count() - 1)
155 155 step = start > end and -1 or 1
156 156 for rev in xrange(start, end+step, step):
157 157 if rev in seen:
158 158 continue
159 159 seen[rev] = 1
160 160 l.append(rev)
161 161 else:
162 162 rev = revfix(repo, spec, None)
163 163 if rev in seen:
164 164 continue
165 165 seen[rev] = 1
166 166 l.append(rev)
167 167
168 168 return l
169 169
170 170 def make_filename(repo, pat, node,
171 171 total=None, seqno=None, revwidth=None, pathname=None):
172 172 node_expander = {
173 173 'H': lambda: hex(node),
174 174 'R': lambda: str(repo.changelog.rev(node)),
175 175 'h': lambda: short(node),
176 176 }
177 177 expander = {
178 178 '%': lambda: '%',
179 179 'b': lambda: os.path.basename(repo.root),
180 180 }
181 181
182 182 try:
183 183 if node:
184 184 expander.update(node_expander)
185 185 if node:
186 186 expander['r'] = (lambda:
187 187 str(repo.changelog.rev(node)).zfill(revwidth or 0))
188 188 if total is not None:
189 189 expander['N'] = lambda: str(total)
190 190 if seqno is not None:
191 191 expander['n'] = lambda: str(seqno)
192 192 if total is not None and seqno is not None:
193 193 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
194 194 if pathname is not None:
195 195 expander['s'] = lambda: os.path.basename(pathname)
196 196 expander['d'] = lambda: os.path.dirname(pathname) or '.'
197 197 expander['p'] = lambda: pathname
198 198
199 199 newname = []
200 200 patlen = len(pat)
201 201 i = 0
202 202 while i < patlen:
203 203 c = pat[i]
204 204 if c == '%':
205 205 i += 1
206 206 c = pat[i]
207 207 c = expander[c]()
208 208 newname.append(c)
209 209 i += 1
210 210 return ''.join(newname)
211 211 except KeyError, inst:
212 212 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
213 213 inst.args[0])
214 214
215 215 def make_file(repo, pat, node=None,
216 216 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
217 217 if not pat or pat == '-':
218 218 return 'w' in mode and sys.stdout or sys.stdin
219 219 if hasattr(pat, 'write') and 'w' in mode:
220 220 return pat
221 221 if hasattr(pat, 'read') and 'r' in mode:
222 222 return pat
223 223 return open(make_filename(repo, pat, node, total, seqno, revwidth,
224 224 pathname),
225 225 mode)
226 226
227 227 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
228 228 if not globbed and default == 'relpath':
229 229 pats = util.expand_glob(pats or [])
230 230 m = _match.match(repo.root, repo.getcwd(), pats,
231 231 opts.get('include'), opts.get('exclude'), default)
232 232 def badfn(f, msg):
233 233 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
234 234 return False
235 235 m.bad = badfn
236 236 return m
237 237
238 def walk(repo, match, node=None):
239 for src, fn in repo.walk(node, match):
240 yield src, fn
241
242 238 def findrenames(repo, added=None, removed=None, threshold=0.5):
243 239 '''find renamed files -- yields (before, after, score) tuples'''
244 240 if added is None or removed is None:
245 241 added, removed = repo.status()[1:3]
246 242 ctx = repo.changectx()
247 243 for a in added:
248 244 aa = repo.wread(a)
249 245 bestname, bestscore = None, threshold
250 246 for r in removed:
251 247 rr = ctx.filectx(r).data()
252 248
253 249 # bdiff.blocks() returns blocks of matching lines
254 250 # count the number of bytes in each
255 251 equal = 0
256 252 alines = mdiff.splitnewlines(aa)
257 253 matches = bdiff.blocks(aa, rr)
258 254 for x1,x2,y1,y2 in matches:
259 255 for line in alines[x1:x2]:
260 256 equal += len(line)
261 257
262 258 lengths = len(aa) + len(rr)
263 259 if lengths:
264 260 myscore = equal*2.0 / lengths
265 261 if myscore >= bestscore:
266 262 bestname, bestscore = r, myscore
267 263 if bestname:
268 264 yield bestname, a, bestscore
269 265
270 266 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
271 267 if dry_run is None:
272 268 dry_run = opts.get('dry_run')
273 269 if similarity is None:
274 270 similarity = float(opts.get('similarity') or 0)
275 271 add, remove = [], []
276 272 mapping = {}
277 273 m = match(repo, pats, opts)
278 for src, abs in walk(repo, m):
274 for src, abs in repo.walk(m):
279 275 target = repo.wjoin(abs)
280 276 rel = m.rel(abs)
281 277 exact = m.exact(abs)
282 278 if src == 'f' and abs not in repo.dirstate:
283 279 add.append(abs)
284 280 mapping[abs] = rel, m.exact(abs)
285 281 if repo.ui.verbose or not exact:
286 282 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
287 283 if repo.dirstate[abs] != 'r' and (not util.lexists(target)
288 284 or (os.path.isdir(target) and not os.path.islink(target))):
289 285 remove.append(abs)
290 286 mapping[abs] = rel, exact
291 287 if repo.ui.verbose or not exact:
292 288 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
293 289 if not dry_run:
294 290 repo.remove(remove)
295 291 repo.add(add)
296 292 if similarity > 0:
297 293 for old, new, score in findrenames(repo, add, remove, similarity):
298 294 oldrel, oldexact = mapping[old]
299 295 newrel, newexact = mapping[new]
300 296 if repo.ui.verbose or not oldexact or not newexact:
301 297 repo.ui.status(_('recording removal of %s as rename to %s '
302 298 '(%d%% similar)\n') %
303 299 (oldrel, newrel, score * 100))
304 300 if not dry_run:
305 301 repo.copy(old, new)
306 302
307 303 def copy(ui, repo, pats, opts, rename=False):
308 304 # called with the repo lock held
309 305 #
310 306 # hgsep => pathname that uses "/" to separate directories
311 307 # ossep => pathname that uses os.sep to separate directories
312 308 cwd = repo.getcwd()
313 309 targets = {}
314 310 after = opts.get("after")
315 311 dryrun = opts.get("dry_run")
316 312
317 313 def walkpat(pat):
318 314 srcs = []
319 315 m = match(repo, [pat], opts, globbed=True)
320 for tag, abs in walk(repo, m):
316 for tag, abs in repo.walk(m):
321 317 state = repo.dirstate[abs]
322 318 rel = m.rel(abs)
323 319 exact = m.exact(abs)
324 320 if state in '?r':
325 321 if exact and state == '?':
326 322 ui.warn(_('%s: not copying - file is not managed\n') % rel)
327 323 if exact and state == 'r':
328 324 ui.warn(_('%s: not copying - file has been marked for'
329 325 ' remove\n') % rel)
330 326 continue
331 327 # abs: hgsep
332 328 # rel: ossep
333 329 srcs.append((abs, rel, exact))
334 330 return srcs
335 331
336 332 # abssrc: hgsep
337 333 # relsrc: ossep
338 334 # otarget: ossep
339 335 def copyfile(abssrc, relsrc, otarget, exact):
340 336 abstarget = util.canonpath(repo.root, cwd, otarget)
341 337 reltarget = repo.pathto(abstarget, cwd)
342 338 target = repo.wjoin(abstarget)
343 339 src = repo.wjoin(abssrc)
344 340 state = repo.dirstate[abstarget]
345 341
346 342 # check for collisions
347 343 prevsrc = targets.get(abstarget)
348 344 if prevsrc is not None:
349 345 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
350 346 (reltarget, repo.pathto(abssrc, cwd),
351 347 repo.pathto(prevsrc, cwd)))
352 348 return
353 349
354 350 # check for overwrites
355 351 exists = os.path.exists(target)
356 352 if (not after and exists or after and state in 'mn'):
357 353 if not opts['force']:
358 354 ui.warn(_('%s: not overwriting - file exists\n') %
359 355 reltarget)
360 356 return
361 357
362 358 if after:
363 359 if not exists:
364 360 return
365 361 elif not dryrun:
366 362 try:
367 363 if exists:
368 364 os.unlink(target)
369 365 targetdir = os.path.dirname(target) or '.'
370 366 if not os.path.isdir(targetdir):
371 367 os.makedirs(targetdir)
372 368 util.copyfile(src, target)
373 369 except IOError, inst:
374 370 if inst.errno == errno.ENOENT:
375 371 ui.warn(_('%s: deleted in working copy\n') % relsrc)
376 372 else:
377 373 ui.warn(_('%s: cannot copy - %s\n') %
378 374 (relsrc, inst.strerror))
379 375 return True # report a failure
380 376
381 377 if ui.verbose or not exact:
382 378 action = rename and "moving" or "copying"
383 379 ui.status(_('%s %s to %s\n') % (action, relsrc, reltarget))
384 380
385 381 targets[abstarget] = abssrc
386 382
387 383 # fix up dirstate
388 384 origsrc = repo.dirstate.copied(abssrc) or abssrc
389 385 if abstarget == origsrc: # copying back a copy?
390 386 if state not in 'mn' and not dryrun:
391 387 repo.dirstate.normallookup(abstarget)
392 388 else:
393 389 if repo.dirstate[origsrc] == 'a':
394 390 if not ui.quiet:
395 391 ui.warn(_("%s has not been committed yet, so no copy "
396 392 "data will be stored for %s.\n")
397 393 % (repo.pathto(origsrc, cwd), reltarget))
398 394 if abstarget not in repo.dirstate and not dryrun:
399 395 repo.add([abstarget])
400 396 elif not dryrun:
401 397 repo.copy(origsrc, abstarget)
402 398
403 399 if rename and not dryrun:
404 400 repo.remove([abssrc], not after)
405 401
406 402 # pat: ossep
407 403 # dest ossep
408 404 # srcs: list of (hgsep, hgsep, ossep, bool)
409 405 # return: function that takes hgsep and returns ossep
410 406 def targetpathfn(pat, dest, srcs):
411 407 if os.path.isdir(pat):
412 408 abspfx = util.canonpath(repo.root, cwd, pat)
413 409 abspfx = util.localpath(abspfx)
414 410 if destdirexists:
415 411 striplen = len(os.path.split(abspfx)[0])
416 412 else:
417 413 striplen = len(abspfx)
418 414 if striplen:
419 415 striplen += len(os.sep)
420 416 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
421 417 elif destdirexists:
422 418 res = lambda p: os.path.join(dest,
423 419 os.path.basename(util.localpath(p)))
424 420 else:
425 421 res = lambda p: dest
426 422 return res
427 423
428 424 # pat: ossep
429 425 # dest ossep
430 426 # srcs: list of (hgsep, hgsep, ossep, bool)
431 427 # return: function that takes hgsep and returns ossep
432 428 def targetpathafterfn(pat, dest, srcs):
433 429 if util.patkind(pat, None)[0]:
434 430 # a mercurial pattern
435 431 res = lambda p: os.path.join(dest,
436 432 os.path.basename(util.localpath(p)))
437 433 else:
438 434 abspfx = util.canonpath(repo.root, cwd, pat)
439 435 if len(abspfx) < len(srcs[0][0]):
440 436 # A directory. Either the target path contains the last
441 437 # component of the source path or it does not.
442 438 def evalpath(striplen):
443 439 score = 0
444 440 for s in srcs:
445 441 t = os.path.join(dest, util.localpath(s[0])[striplen:])
446 442 if os.path.exists(t):
447 443 score += 1
448 444 return score
449 445
450 446 abspfx = util.localpath(abspfx)
451 447 striplen = len(abspfx)
452 448 if striplen:
453 449 striplen += len(os.sep)
454 450 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
455 451 score = evalpath(striplen)
456 452 striplen1 = len(os.path.split(abspfx)[0])
457 453 if striplen1:
458 454 striplen1 += len(os.sep)
459 455 if evalpath(striplen1) > score:
460 456 striplen = striplen1
461 457 res = lambda p: os.path.join(dest,
462 458 util.localpath(p)[striplen:])
463 459 else:
464 460 # a file
465 461 if destdirexists:
466 462 res = lambda p: os.path.join(dest,
467 463 os.path.basename(util.localpath(p)))
468 464 else:
469 465 res = lambda p: dest
470 466 return res
471 467
472 468
473 469 pats = util.expand_glob(pats)
474 470 if not pats:
475 471 raise util.Abort(_('no source or destination specified'))
476 472 if len(pats) == 1:
477 473 raise util.Abort(_('no destination specified'))
478 474 dest = pats.pop()
479 475 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
480 476 if not destdirexists:
481 477 if len(pats) > 1 or util.patkind(pats[0], None)[0]:
482 478 raise util.Abort(_('with multiple sources, destination must be an '
483 479 'existing directory'))
484 480 if util.endswithsep(dest):
485 481 raise util.Abort(_('destination %s is not a directory') % dest)
486 482
487 483 tfn = targetpathfn
488 484 if after:
489 485 tfn = targetpathafterfn
490 486 copylist = []
491 487 for pat in pats:
492 488 srcs = walkpat(pat)
493 489 if not srcs:
494 490 continue
495 491 copylist.append((tfn(pat, dest, srcs), srcs))
496 492 if not copylist:
497 493 raise util.Abort(_('no files to copy'))
498 494
499 495 errors = 0
500 496 for targetpath, srcs in copylist:
501 497 for abssrc, relsrc, exact in srcs:
502 498 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
503 499 errors += 1
504 500
505 501 if errors:
506 502 ui.warn(_('(consider using --after)\n'))
507 503
508 504 return errors
509 505
510 506 def service(opts, parentfn=None, initfn=None, runfn=None):
511 507 '''Run a command as a service.'''
512 508
513 509 if opts['daemon'] and not opts['daemon_pipefds']:
514 510 rfd, wfd = os.pipe()
515 511 args = sys.argv[:]
516 512 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
517 513 # Don't pass --cwd to the child process, because we've already
518 514 # changed directory.
519 515 for i in xrange(1,len(args)):
520 516 if args[i].startswith('--cwd='):
521 517 del args[i]
522 518 break
523 519 elif args[i].startswith('--cwd'):
524 520 del args[i:i+2]
525 521 break
526 522 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
527 523 args[0], args)
528 524 os.close(wfd)
529 525 os.read(rfd, 1)
530 526 if parentfn:
531 527 return parentfn(pid)
532 528 else:
533 529 os._exit(0)
534 530
535 531 if initfn:
536 532 initfn()
537 533
538 534 if opts['pid_file']:
539 535 fp = open(opts['pid_file'], 'w')
540 536 fp.write(str(os.getpid()) + '\n')
541 537 fp.close()
542 538
543 539 if opts['daemon_pipefds']:
544 540 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
545 541 os.close(rfd)
546 542 try:
547 543 os.setsid()
548 544 except AttributeError:
549 545 pass
550 546 os.write(wfd, 'y')
551 547 os.close(wfd)
552 548 sys.stdout.flush()
553 549 sys.stderr.flush()
554 550 fd = os.open(util.nulldev, os.O_RDWR)
555 551 if fd != 0: os.dup2(fd, 0)
556 552 if fd != 1: os.dup2(fd, 1)
557 553 if fd != 2: os.dup2(fd, 2)
558 554 if fd not in (0, 1, 2): os.close(fd)
559 555
560 556 if runfn:
561 557 return runfn()
562 558
563 559 class changeset_printer(object):
564 560 '''show changeset information when templating not requested.'''
565 561
566 562 def __init__(self, ui, repo, patch, buffered):
567 563 self.ui = ui
568 564 self.repo = repo
569 565 self.buffered = buffered
570 566 self.patch = patch
571 567 self.header = {}
572 568 self.hunk = {}
573 569 self.lastheader = None
574 570
575 571 def flush(self, rev):
576 572 if rev in self.header:
577 573 h = self.header[rev]
578 574 if h != self.lastheader:
579 575 self.lastheader = h
580 576 self.ui.write(h)
581 577 del self.header[rev]
582 578 if rev in self.hunk:
583 579 self.ui.write(self.hunk[rev])
584 580 del self.hunk[rev]
585 581 return 1
586 582 return 0
587 583
588 584 def show(self, rev=0, changenode=None, copies=(), **props):
589 585 if self.buffered:
590 586 self.ui.pushbuffer()
591 587 self._show(rev, changenode, copies, props)
592 588 self.hunk[rev] = self.ui.popbuffer()
593 589 else:
594 590 self._show(rev, changenode, copies, props)
595 591
596 592 def _show(self, rev, changenode, copies, props):
597 593 '''show a single changeset or file revision'''
598 594 log = self.repo.changelog
599 595 if changenode is None:
600 596 changenode = log.node(rev)
601 597 elif not rev:
602 598 rev = log.rev(changenode)
603 599
604 600 if self.ui.quiet:
605 601 self.ui.write("%d:%s\n" % (rev, short(changenode)))
606 602 return
607 603
608 604 changes = log.read(changenode)
609 605 date = util.datestr(changes[2])
610 606 extra = changes[5]
611 607 branch = extra.get("branch")
612 608
613 609 hexfunc = self.ui.debugflag and hex or short
614 610
615 611 parents = [(p, hexfunc(log.node(p)))
616 612 for p in self._meaningful_parentrevs(log, rev)]
617 613
618 614 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
619 615
620 616 # don't show the default branch name
621 617 if branch != 'default':
622 618 branch = util.tolocal(branch)
623 619 self.ui.write(_("branch: %s\n") % branch)
624 620 for tag in self.repo.nodetags(changenode):
625 621 self.ui.write(_("tag: %s\n") % tag)
626 622 for parent in parents:
627 623 self.ui.write(_("parent: %d:%s\n") % parent)
628 624
629 625 if self.ui.debugflag:
630 626 self.ui.write(_("manifest: %d:%s\n") %
631 627 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
632 628 self.ui.write(_("user: %s\n") % changes[1])
633 629 self.ui.write(_("date: %s\n") % date)
634 630
635 631 if self.ui.debugflag:
636 632 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
637 633 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
638 634 files):
639 635 if value:
640 636 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
641 637 elif changes[3] and self.ui.verbose:
642 638 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
643 639 if copies and self.ui.verbose:
644 640 copies = ['%s (%s)' % c for c in copies]
645 641 self.ui.write(_("copies: %s\n") % ' '.join(copies))
646 642
647 643 if extra and self.ui.debugflag:
648 644 extraitems = extra.items()
649 645 extraitems.sort()
650 646 for key, value in extraitems:
651 647 self.ui.write(_("extra: %s=%s\n")
652 648 % (key, value.encode('string_escape')))
653 649
654 650 description = changes[4].strip()
655 651 if description:
656 652 if self.ui.verbose:
657 653 self.ui.write(_("description:\n"))
658 654 self.ui.write(description)
659 655 self.ui.write("\n\n")
660 656 else:
661 657 self.ui.write(_("summary: %s\n") %
662 658 description.splitlines()[0])
663 659 self.ui.write("\n")
664 660
665 661 self.showpatch(changenode)
666 662
667 663 def showpatch(self, node):
668 664 if self.patch:
669 665 prev = self.repo.changelog.parents(node)[0]
670 666 patch.diff(self.repo, prev, node, match=self.patch, fp=self.ui,
671 667 opts=patch.diffopts(self.ui))
672 668 self.ui.write("\n")
673 669
674 670 def _meaningful_parentrevs(self, log, rev):
675 671 """Return list of meaningful (or all if debug) parentrevs for rev.
676 672
677 673 For merges (two non-nullrev revisions) both parents are meaningful.
678 674 Otherwise the first parent revision is considered meaningful if it
679 675 is not the preceding revision.
680 676 """
681 677 parents = log.parentrevs(rev)
682 678 if not self.ui.debugflag and parents[1] == nullrev:
683 679 if parents[0] >= rev - 1:
684 680 parents = []
685 681 else:
686 682 parents = [parents[0]]
687 683 return parents
688 684
689 685
690 686 class changeset_templater(changeset_printer):
691 687 '''format changeset information.'''
692 688
693 689 def __init__(self, ui, repo, patch, mapfile, buffered):
694 690 changeset_printer.__init__(self, ui, repo, patch, buffered)
695 691 filters = templatefilters.filters.copy()
696 692 filters['formatnode'] = (ui.debugflag and (lambda x: x)
697 693 or (lambda x: x[:12]))
698 694 self.t = templater.templater(mapfile, filters,
699 695 cache={
700 696 'parent': '{rev}:{node|formatnode} ',
701 697 'manifest': '{rev}:{node|formatnode}',
702 698 'filecopy': '{name} ({source})'})
703 699
704 700 def use_template(self, t):
705 701 '''set template string to use'''
706 702 self.t.cache['changeset'] = t
707 703
708 704 def _show(self, rev, changenode, copies, props):
709 705 '''show a single changeset or file revision'''
710 706 log = self.repo.changelog
711 707 if changenode is None:
712 708 changenode = log.node(rev)
713 709 elif not rev:
714 710 rev = log.rev(changenode)
715 711
716 712 changes = log.read(changenode)
717 713
718 714 def showlist(name, values, plural=None, **args):
719 715 '''expand set of values.
720 716 name is name of key in template map.
721 717 values is list of strings or dicts.
722 718 plural is plural of name, if not simply name + 's'.
723 719
724 720 expansion works like this, given name 'foo'.
725 721
726 722 if values is empty, expand 'no_foos'.
727 723
728 724 if 'foo' not in template map, return values as a string,
729 725 joined by space.
730 726
731 727 expand 'start_foos'.
732 728
733 729 for each value, expand 'foo'. if 'last_foo' in template
734 730 map, expand it instead of 'foo' for last key.
735 731
736 732 expand 'end_foos'.
737 733 '''
738 734 if plural: names = plural
739 735 else: names = name + 's'
740 736 if not values:
741 737 noname = 'no_' + names
742 738 if noname in self.t:
743 739 yield self.t(noname, **args)
744 740 return
745 741 if name not in self.t:
746 742 if isinstance(values[0], str):
747 743 yield ' '.join(values)
748 744 else:
749 745 for v in values:
750 746 yield dict(v, **args)
751 747 return
752 748 startname = 'start_' + names
753 749 if startname in self.t:
754 750 yield self.t(startname, **args)
755 751 vargs = args.copy()
756 752 def one(v, tag=name):
757 753 try:
758 754 vargs.update(v)
759 755 except (AttributeError, ValueError):
760 756 try:
761 757 for a, b in v:
762 758 vargs[a] = b
763 759 except ValueError:
764 760 vargs[name] = v
765 761 return self.t(tag, **vargs)
766 762 lastname = 'last_' + name
767 763 if lastname in self.t:
768 764 last = values.pop()
769 765 else:
770 766 last = None
771 767 for v in values:
772 768 yield one(v)
773 769 if last is not None:
774 770 yield one(last, tag=lastname)
775 771 endname = 'end_' + names
776 772 if endname in self.t:
777 773 yield self.t(endname, **args)
778 774
779 775 def showbranches(**args):
780 776 branch = changes[5].get("branch")
781 777 if branch != 'default':
782 778 branch = util.tolocal(branch)
783 779 return showlist('branch', [branch], plural='branches', **args)
784 780
785 781 def showparents(**args):
786 782 parents = [[('rev', p), ('node', hex(log.node(p)))]
787 783 for p in self._meaningful_parentrevs(log, rev)]
788 784 return showlist('parent', parents, **args)
789 785
790 786 def showtags(**args):
791 787 return showlist('tag', self.repo.nodetags(changenode), **args)
792 788
793 789 def showextras(**args):
794 790 extras = changes[5].items()
795 791 extras.sort()
796 792 for key, value in extras:
797 793 args = args.copy()
798 794 args.update(dict(key=key, value=value))
799 795 yield self.t('extra', **args)
800 796
801 797 def showcopies(**args):
802 798 c = [{'name': x[0], 'source': x[1]} for x in copies]
803 799 return showlist('file_copy', c, plural='file_copies', **args)
804 800
805 801 files = []
806 802 def getfiles():
807 803 if not files:
808 804 files[:] = self.repo.status(
809 805 log.parents(changenode)[0], changenode)[:3]
810 806 return files
811 807 def showfiles(**args):
812 808 return showlist('file', changes[3], **args)
813 809 def showmods(**args):
814 810 return showlist('file_mod', getfiles()[0], **args)
815 811 def showadds(**args):
816 812 return showlist('file_add', getfiles()[1], **args)
817 813 def showdels(**args):
818 814 return showlist('file_del', getfiles()[2], **args)
819 815 def showmanifest(**args):
820 816 args = args.copy()
821 817 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
822 818 node=hex(changes[0])))
823 819 return self.t('manifest', **args)
824 820
825 821 defprops = {
826 822 'author': changes[1],
827 823 'branches': showbranches,
828 824 'date': changes[2],
829 825 'desc': changes[4].strip(),
830 826 'file_adds': showadds,
831 827 'file_dels': showdels,
832 828 'file_mods': showmods,
833 829 'files': showfiles,
834 830 'file_copies': showcopies,
835 831 'manifest': showmanifest,
836 832 'node': hex(changenode),
837 833 'parents': showparents,
838 834 'rev': rev,
839 835 'tags': showtags,
840 836 'extras': showextras,
841 837 }
842 838 props = props.copy()
843 839 props.update(defprops)
844 840
845 841 try:
846 842 if self.ui.debugflag and 'header_debug' in self.t:
847 843 key = 'header_debug'
848 844 elif self.ui.quiet and 'header_quiet' in self.t:
849 845 key = 'header_quiet'
850 846 elif self.ui.verbose and 'header_verbose' in self.t:
851 847 key = 'header_verbose'
852 848 elif 'header' in self.t:
853 849 key = 'header'
854 850 else:
855 851 key = ''
856 852 if key:
857 853 h = templater.stringify(self.t(key, **props))
858 854 if self.buffered:
859 855 self.header[rev] = h
860 856 else:
861 857 self.ui.write(h)
862 858 if self.ui.debugflag and 'changeset_debug' in self.t:
863 859 key = 'changeset_debug'
864 860 elif self.ui.quiet and 'changeset_quiet' in self.t:
865 861 key = 'changeset_quiet'
866 862 elif self.ui.verbose and 'changeset_verbose' in self.t:
867 863 key = 'changeset_verbose'
868 864 else:
869 865 key = 'changeset'
870 866 self.ui.write(templater.stringify(self.t(key, **props)))
871 867 self.showpatch(changenode)
872 868 except KeyError, inst:
873 869 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
874 870 inst.args[0]))
875 871 except SyntaxError, inst:
876 872 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
877 873
878 874 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
879 875 """show one changeset using template or regular display.
880 876
881 877 Display format will be the first non-empty hit of:
882 878 1. option 'template'
883 879 2. option 'style'
884 880 3. [ui] setting 'logtemplate'
885 881 4. [ui] setting 'style'
886 882 If all of these values are either the unset or the empty string,
887 883 regular display via changeset_printer() is done.
888 884 """
889 885 # options
890 886 patch = False
891 887 if opts.get('patch'):
892 888 patch = matchfn or util.always
893 889
894 890 tmpl = opts.get('template')
895 891 mapfile = None
896 892 if tmpl:
897 893 tmpl = templater.parsestring(tmpl, quoted=False)
898 894 else:
899 895 mapfile = opts.get('style')
900 896 # ui settings
901 897 if not mapfile:
902 898 tmpl = ui.config('ui', 'logtemplate')
903 899 if tmpl:
904 900 tmpl = templater.parsestring(tmpl)
905 901 else:
906 902 mapfile = ui.config('ui', 'style')
907 903
908 904 if tmpl or mapfile:
909 905 if mapfile:
910 906 if not os.path.split(mapfile)[0]:
911 907 mapname = (templater.templatepath('map-cmdline.' + mapfile)
912 908 or templater.templatepath(mapfile))
913 909 if mapname: mapfile = mapname
914 910 try:
915 911 t = changeset_templater(ui, repo, patch, mapfile, buffered)
916 912 except SyntaxError, inst:
917 913 raise util.Abort(inst.args[0])
918 914 if tmpl: t.use_template(tmpl)
919 915 return t
920 916 return changeset_printer(ui, repo, patch, buffered)
921 917
922 918 def finddate(ui, repo, date):
923 919 """Find the tipmost changeset that matches the given date spec"""
924 920 df = util.matchdate(date)
925 921 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
926 922 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
927 923 results = {}
928 924 for st, rev, fns in changeiter:
929 925 if st == 'add':
930 926 d = get(rev)[2]
931 927 if df(d[0]):
932 928 results[rev] = d
933 929 elif st == 'iter':
934 930 if rev in results:
935 931 ui.status("Found revision %s from %s\n" %
936 932 (rev, util.datestr(results[rev])))
937 933 return str(rev)
938 934
939 935 raise util.Abort(_("revision matching date not found"))
940 936
941 937 def walkchangerevs(ui, repo, pats, change, opts):
942 938 '''Iterate over files and the revs they changed in.
943 939
944 940 Callers most commonly need to iterate backwards over the history
945 941 it is interested in. Doing so has awful (quadratic-looking)
946 942 performance, so we use iterators in a "windowed" way.
947 943
948 944 We walk a window of revisions in the desired order. Within the
949 945 window, we first walk forwards to gather data, then in the desired
950 946 order (usually backwards) to display it.
951 947
952 948 This function returns an (iterator, matchfn) tuple. The iterator
953 949 yields 3-tuples. They will be of one of the following forms:
954 950
955 951 "window", incrementing, lastrev: stepping through a window,
956 952 positive if walking forwards through revs, last rev in the
957 953 sequence iterated over - use to reset state for the current window
958 954
959 955 "add", rev, fns: out-of-order traversal of the given file names
960 956 fns, which changed during revision rev - use to gather data for
961 957 possible display
962 958
963 959 "iter", rev, None: in-order traversal of the revs earlier iterated
964 960 over with "add" - use to display data'''
965 961
966 962 def increasing_windows(start, end, windowsize=8, sizelimit=512):
967 963 if start < end:
968 964 while start < end:
969 965 yield start, min(windowsize, end-start)
970 966 start += windowsize
971 967 if windowsize < sizelimit:
972 968 windowsize *= 2
973 969 else:
974 970 while start > end:
975 971 yield start, min(windowsize, start-end-1)
976 972 start -= windowsize
977 973 if windowsize < sizelimit:
978 974 windowsize *= 2
979 975
980 976 m = match(repo, pats, opts)
981 977 follow = opts.get('follow') or opts.get('follow_first')
982 978
983 979 if repo.changelog.count() == 0:
984 980 return [], m
985 981
986 982 if follow:
987 983 defrange = '%s:0' % repo.changectx().rev()
988 984 else:
989 985 defrange = '-1:0'
990 986 revs = revrange(repo, opts['rev'] or [defrange])
991 987 wanted = {}
992 988 slowpath = m.anypats() or opts.get('removed')
993 989 fncache = {}
994 990
995 991 if not slowpath and not m.files():
996 992 # No files, no patterns. Display all revs.
997 993 wanted = dict.fromkeys(revs)
998 994 copies = []
999 995 if not slowpath:
1000 996 # Only files, no patterns. Check the history of each file.
1001 997 def filerevgen(filelog, node):
1002 998 cl_count = repo.changelog.count()
1003 999 if node is None:
1004 1000 last = filelog.count() - 1
1005 1001 else:
1006 1002 last = filelog.rev(node)
1007 1003 for i, window in increasing_windows(last, nullrev):
1008 1004 revs = []
1009 1005 for j in xrange(i - window, i + 1):
1010 1006 n = filelog.node(j)
1011 1007 revs.append((filelog.linkrev(n),
1012 1008 follow and filelog.renamed(n)))
1013 1009 revs.reverse()
1014 1010 for rev in revs:
1015 1011 # only yield rev for which we have the changelog, it can
1016 1012 # happen while doing "hg log" during a pull or commit
1017 1013 if rev[0] < cl_count:
1018 1014 yield rev
1019 1015 def iterfiles():
1020 1016 for filename in m.files():
1021 1017 yield filename, None
1022 1018 for filename_node in copies:
1023 1019 yield filename_node
1024 1020 minrev, maxrev = min(revs), max(revs)
1025 1021 for file_, node in iterfiles():
1026 1022 filelog = repo.file(file_)
1027 1023 if filelog.count() == 0:
1028 1024 if node is None:
1029 1025 # A zero count may be a directory or deleted file, so
1030 1026 # try to find matching entries on the slow path.
1031 1027 slowpath = True
1032 1028 break
1033 1029 else:
1034 1030 ui.warn(_('%s:%s copy source revision cannot be found!\n')
1035 1031 % (file_, short(node)))
1036 1032 continue
1037 1033 for rev, copied in filerevgen(filelog, node):
1038 1034 if rev <= maxrev:
1039 1035 if rev < minrev:
1040 1036 break
1041 1037 fncache.setdefault(rev, [])
1042 1038 fncache[rev].append(file_)
1043 1039 wanted[rev] = 1
1044 1040 if follow and copied:
1045 1041 copies.append(copied)
1046 1042 if slowpath:
1047 1043 if follow:
1048 1044 raise util.Abort(_('can only follow copies/renames for explicit '
1049 1045 'file names'))
1050 1046
1051 1047 # The slow path checks files modified in every changeset.
1052 1048 def changerevgen():
1053 1049 for i, window in increasing_windows(repo.changelog.count()-1,
1054 1050 nullrev):
1055 1051 for j in xrange(i - window, i + 1):
1056 1052 yield j, change(j)[3]
1057 1053
1058 1054 for rev, changefiles in changerevgen():
1059 1055 matches = filter(m, changefiles)
1060 1056 if matches:
1061 1057 fncache[rev] = matches
1062 1058 wanted[rev] = 1
1063 1059
1064 1060 class followfilter:
1065 1061 def __init__(self, onlyfirst=False):
1066 1062 self.startrev = nullrev
1067 1063 self.roots = []
1068 1064 self.onlyfirst = onlyfirst
1069 1065
1070 1066 def match(self, rev):
1071 1067 def realparents(rev):
1072 1068 if self.onlyfirst:
1073 1069 return repo.changelog.parentrevs(rev)[0:1]
1074 1070 else:
1075 1071 return filter(lambda x: x != nullrev,
1076 1072 repo.changelog.parentrevs(rev))
1077 1073
1078 1074 if self.startrev == nullrev:
1079 1075 self.startrev = rev
1080 1076 return True
1081 1077
1082 1078 if rev > self.startrev:
1083 1079 # forward: all descendants
1084 1080 if not self.roots:
1085 1081 self.roots.append(self.startrev)
1086 1082 for parent in realparents(rev):
1087 1083 if parent in self.roots:
1088 1084 self.roots.append(rev)
1089 1085 return True
1090 1086 else:
1091 1087 # backwards: all parents
1092 1088 if not self.roots:
1093 1089 self.roots.extend(realparents(self.startrev))
1094 1090 if rev in self.roots:
1095 1091 self.roots.remove(rev)
1096 1092 self.roots.extend(realparents(rev))
1097 1093 return True
1098 1094
1099 1095 return False
1100 1096
1101 1097 # it might be worthwhile to do this in the iterator if the rev range
1102 1098 # is descending and the prune args are all within that range
1103 1099 for rev in opts.get('prune', ()):
1104 1100 rev = repo.changelog.rev(repo.lookup(rev))
1105 1101 ff = followfilter()
1106 1102 stop = min(revs[0], revs[-1])
1107 1103 for x in xrange(rev, stop-1, -1):
1108 1104 if ff.match(x) and x in wanted:
1109 1105 del wanted[x]
1110 1106
1111 1107 def iterate():
1112 1108 if follow and not m.files():
1113 1109 ff = followfilter(onlyfirst=opts.get('follow_first'))
1114 1110 def want(rev):
1115 1111 if ff.match(rev) and rev in wanted:
1116 1112 return True
1117 1113 return False
1118 1114 else:
1119 1115 def want(rev):
1120 1116 return rev in wanted
1121 1117
1122 1118 for i, window in increasing_windows(0, len(revs)):
1123 1119 yield 'window', revs[0] < revs[-1], revs[-1]
1124 1120 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1125 1121 srevs = list(nrevs)
1126 1122 srevs.sort()
1127 1123 for rev in srevs:
1128 1124 fns = fncache.get(rev)
1129 1125 if not fns:
1130 1126 def fns_generator():
1131 1127 for f in change(rev)[3]:
1132 1128 if m(f):
1133 1129 yield f
1134 1130 fns = fns_generator()
1135 1131 yield 'add', rev, fns
1136 1132 for rev in nrevs:
1137 1133 yield 'iter', rev, None
1138 1134 return iterate(), m
1139 1135
1140 1136 def commit(ui, repo, commitfunc, pats, opts):
1141 1137 '''commit the specified files or all outstanding changes'''
1142 1138 date = opts.get('date')
1143 1139 if date:
1144 1140 opts['date'] = util.parsedate(date)
1145 1141 message = logmessage(opts)
1146 1142
1147 1143 # extract addremove carefully -- this function can be called from a command
1148 1144 # that doesn't support addremove
1149 1145 if opts.get('addremove'):
1150 1146 addremove(repo, pats, opts)
1151 1147
1152 1148 m = match(repo, pats, opts)
1153 1149 if pats:
1154 1150 status = repo.status(files=m.files(), match=m)
1155 1151 modified, added, removed, deleted, unknown = status[:5]
1156 1152 files = modified + added + removed
1157 1153 slist = None
1158 1154 for f in m.files():
1159 1155 if f == '.':
1160 1156 continue
1161 1157 if f not in files:
1162 1158 rf = repo.wjoin(f)
1163 1159 rel = repo.pathto(f)
1164 1160 try:
1165 1161 mode = os.lstat(rf)[stat.ST_MODE]
1166 1162 except OSError:
1167 1163 raise util.Abort(_("file %s not found!") % rel)
1168 1164 if stat.S_ISDIR(mode):
1169 1165 name = f + '/'
1170 1166 if slist is None:
1171 1167 slist = list(files)
1172 1168 slist.sort()
1173 1169 i = bisect.bisect(slist, name)
1174 1170 if i >= len(slist) or not slist[i].startswith(name):
1175 1171 raise util.Abort(_("no match under directory %s!")
1176 1172 % rel)
1177 1173 elif not (stat.S_ISREG(mode) or stat.S_ISLNK(mode)):
1178 1174 raise util.Abort(_("can't commit %s: "
1179 1175 "unsupported file type!") % rel)
1180 1176 elif f not in repo.dirstate:
1181 1177 raise util.Abort(_("file %s not tracked!") % rel)
1182 1178 else:
1183 1179 files = []
1184 1180 try:
1185 1181 return commitfunc(ui, repo, files, message, m, opts)
1186 1182 except ValueError, inst:
1187 1183 raise util.Abort(str(inst))
@@ -1,3327 +1,3327
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
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import hex, nullid, nullrev, short
9 9 from repo import RepoError, NoCapability
10 10 from i18n import _
11 11 import os, re, sys, urllib
12 12 import hg, util, revlog, bundlerepo, extensions, copies
13 13 import difflib, patch, time, help, mdiff, tempfile
14 14 import version, socket
15 15 import archival, changegroup, cmdutil, hgweb.server, sshserver, hbisect
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 repository.
24 24
25 25 The files will be added to the repository at the next commit. To
26 26 undo an add before that, see hg revert.
27 27
28 28 If no names are given, add all files in the repository.
29 29 """
30 30
31 31 rejected = None
32 32 exacts = {}
33 33 names = []
34 34 m = cmdutil.match(repo, pats, opts)
35 35 m.bad = lambda x,y: True
36 for src, abs in cmdutil.walk(repo, m):
36 for src, abs in repo.walk(m):
37 37 if m.exact(abs):
38 38 if ui.verbose:
39 39 ui.status(_('adding %s\n') % m.rel(abs))
40 40 names.append(abs)
41 41 exacts[abs] = 1
42 42 elif abs not in repo.dirstate:
43 43 ui.status(_('adding %s\n') % m.rel(abs))
44 44 names.append(abs)
45 45 if not opts.get('dry_run'):
46 46 rejected = repo.add(names)
47 47 rejected = [p for p in rejected if p in exacts]
48 48 return rejected and 1 or 0
49 49
50 50 def addremove(ui, repo, *pats, **opts):
51 51 """add all new files, delete all missing files
52 52
53 53 Add all new files and remove all missing files from the repository.
54 54
55 55 New files are ignored if they match any of the patterns in .hgignore. As
56 56 with add, these changes take effect at the next commit.
57 57
58 58 Use the -s option to detect renamed files. With a parameter > 0,
59 59 this compares every removed file with every added file and records
60 60 those similar enough as renames. This option takes a percentage
61 61 between 0 (disabled) and 100 (files must be identical) as its
62 62 parameter. Detecting renamed files this way can be expensive.
63 63 """
64 64 try:
65 65 sim = float(opts.get('similarity') or 0)
66 66 except ValueError:
67 67 raise util.Abort(_('similarity must be a number'))
68 68 if sim < 0 or sim > 100:
69 69 raise util.Abort(_('similarity must be between 0 and 100'))
70 70 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
71 71
72 72 def annotate(ui, repo, *pats, **opts):
73 73 """show changeset information per file line
74 74
75 75 List changes in files, showing the revision id responsible for each line
76 76
77 77 This command is useful to discover who did a change or when a change took
78 78 place.
79 79
80 80 Without the -a option, annotate will avoid processing files it
81 81 detects as binary. With -a, annotate will generate an annotation
82 82 anyway, probably with undesirable results.
83 83 """
84 84 datefunc = ui.quiet and util.shortdate or util.datestr
85 85 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
86 86
87 87 if not pats:
88 88 raise util.Abort(_('at least one file name or pattern required'))
89 89
90 90 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
91 91 ('number', lambda x: str(x[0].rev())),
92 92 ('changeset', lambda x: short(x[0].node())),
93 93 ('date', getdate),
94 94 ('follow', lambda x: x[0].path()),
95 95 ]
96 96
97 97 if (not opts['user'] and not opts['changeset'] and not opts['date']
98 98 and not opts['follow']):
99 99 opts['number'] = 1
100 100
101 101 linenumber = opts.get('line_number') is not None
102 102 if (linenumber and (not opts['changeset']) and (not opts['number'])):
103 103 raise util.Abort(_('at least one of -n/-c is required for -l'))
104 104
105 105 funcmap = [func for op, func in opmap if opts.get(op)]
106 106 if linenumber:
107 107 lastfunc = funcmap[-1]
108 108 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
109 109
110 110 ctx = repo.changectx(opts['rev'])
111 111
112 112 m = cmdutil.match(repo, pats, opts)
113 for src, abs in cmdutil.walk(repo, m, ctx.node()):
113 for src, abs in repo.walk(m, ctx.node()):
114 114 fctx = ctx.filectx(abs)
115 115 if not opts['text'] and util.binary(fctx.data()):
116 116 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
117 117 continue
118 118
119 119 lines = fctx.annotate(follow=opts.get('follow'),
120 120 linenumber=linenumber)
121 121 pieces = []
122 122
123 123 for f in funcmap:
124 124 l = [f(n) for n, dummy in lines]
125 125 if l:
126 126 m = max(map(len, l))
127 127 pieces.append(["%*s" % (m, x) for x in l])
128 128
129 129 if pieces:
130 130 for p, l in zip(zip(*pieces), lines):
131 131 ui.write("%s: %s" % (" ".join(p), l[1]))
132 132
133 133 def archive(ui, repo, dest, **opts):
134 134 '''create unversioned archive of a repository revision
135 135
136 136 By default, the revision used is the parent of the working
137 137 directory; use "-r" to specify a different revision.
138 138
139 139 To specify the type of archive to create, use "-t". Valid
140 140 types are:
141 141
142 142 "files" (default): a directory full of files
143 143 "tar": tar archive, uncompressed
144 144 "tbz2": tar archive, compressed using bzip2
145 145 "tgz": tar archive, compressed using gzip
146 146 "uzip": zip archive, uncompressed
147 147 "zip": zip archive, compressed using deflate
148 148
149 149 The exact name of the destination archive or directory is given
150 150 using a format string; see "hg help export" for details.
151 151
152 152 Each member added to an archive file has a directory prefix
153 153 prepended. Use "-p" to specify a format string for the prefix.
154 154 The default is the basename of the archive, with suffixes removed.
155 155 '''
156 156
157 157 ctx = repo.changectx(opts['rev'])
158 158 if not ctx:
159 159 raise util.Abort(_('repository has no revisions'))
160 160 node = ctx.node()
161 161 dest = cmdutil.make_filename(repo, dest, node)
162 162 if os.path.realpath(dest) == repo.root:
163 163 raise util.Abort(_('repository root cannot be destination'))
164 164 matchfn = cmdutil.match(repo, [], opts)
165 165 kind = opts.get('type') or 'files'
166 166 prefix = opts['prefix']
167 167 if dest == '-':
168 168 if kind == 'files':
169 169 raise util.Abort(_('cannot archive plain files to stdout'))
170 170 dest = sys.stdout
171 171 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
172 172 prefix = cmdutil.make_filename(repo, prefix, node)
173 173 archival.archive(repo, dest, node, kind, not opts['no_decode'],
174 174 matchfn, prefix)
175 175
176 176 def backout(ui, repo, node=None, rev=None, **opts):
177 177 '''reverse effect of earlier changeset
178 178
179 179 Commit the backed out changes as a new changeset. The new
180 180 changeset is a child of the backed out changeset.
181 181
182 182 If you back out a changeset other than the tip, a new head is
183 183 created. This head will be the new tip and you should merge this
184 184 backout changeset with another head (current one by default).
185 185
186 186 The --merge option remembers the parent of the working directory
187 187 before starting the backout, then merges the new head with that
188 188 changeset afterwards. This saves you from doing the merge by
189 189 hand. The result of this merge is not committed, as for a normal
190 190 merge.
191 191
192 192 See 'hg help dates' for a list of formats valid for -d/--date.
193 193 '''
194 194 if rev and node:
195 195 raise util.Abort(_("please specify just one revision"))
196 196
197 197 if not rev:
198 198 rev = node
199 199
200 200 if not rev:
201 201 raise util.Abort(_("please specify a revision to backout"))
202 202
203 203 date = opts.get('date')
204 204 if date:
205 205 opts['date'] = util.parsedate(date)
206 206
207 207 cmdutil.bail_if_changed(repo)
208 208 node = repo.lookup(rev)
209 209
210 210 op1, op2 = repo.dirstate.parents()
211 211 a = repo.changelog.ancestor(op1, node)
212 212 if a != node:
213 213 raise util.Abort(_('cannot back out change on a different branch'))
214 214
215 215 p1, p2 = repo.changelog.parents(node)
216 216 if p1 == nullid:
217 217 raise util.Abort(_('cannot back out a change with no parents'))
218 218 if p2 != nullid:
219 219 if not opts['parent']:
220 220 raise util.Abort(_('cannot back out a merge changeset without '
221 221 '--parent'))
222 222 p = repo.lookup(opts['parent'])
223 223 if p not in (p1, p2):
224 224 raise util.Abort(_('%s is not a parent of %s') %
225 225 (short(p), short(node)))
226 226 parent = p
227 227 else:
228 228 if opts['parent']:
229 229 raise util.Abort(_('cannot use --parent on non-merge changeset'))
230 230 parent = p1
231 231
232 232 # the backout should appear on the same branch
233 233 branch = repo.dirstate.branch()
234 234 hg.clean(repo, node, show_stats=False)
235 235 repo.dirstate.setbranch(branch)
236 236 revert_opts = opts.copy()
237 237 revert_opts['date'] = None
238 238 revert_opts['all'] = True
239 239 revert_opts['rev'] = hex(parent)
240 240 revert_opts['no_backup'] = None
241 241 revert(ui, repo, **revert_opts)
242 242 commit_opts = opts.copy()
243 243 commit_opts['addremove'] = False
244 244 if not commit_opts['message'] and not commit_opts['logfile']:
245 245 commit_opts['message'] = _("Backed out changeset %s") % (short(node))
246 246 commit_opts['force_editor'] = True
247 247 commit(ui, repo, **commit_opts)
248 248 def nice(node):
249 249 return '%d:%s' % (repo.changelog.rev(node), short(node))
250 250 ui.status(_('changeset %s backs out changeset %s\n') %
251 251 (nice(repo.changelog.tip()), nice(node)))
252 252 if op1 != node:
253 253 hg.clean(repo, op1, show_stats=False)
254 254 if opts['merge']:
255 255 ui.status(_('merging with changeset %s\n') % nice(repo.changelog.tip()))
256 256 hg.merge(repo, hex(repo.changelog.tip()))
257 257 else:
258 258 ui.status(_('the backout changeset is a new head - '
259 259 'do not forget to merge\n'))
260 260 ui.status(_('(use "backout --merge" '
261 261 'if you want to auto-merge)\n'))
262 262
263 263 def bisect(ui, repo, rev=None, extra=None,
264 264 reset=None, good=None, bad=None, skip=None, noupdate=None):
265 265 """subdivision search of changesets
266 266
267 267 This command helps to find changesets which introduce problems.
268 268 To use, mark the earliest changeset you know exhibits the problem
269 269 as bad, then mark the latest changeset which is free from the
270 270 problem as good. Bisect will update your working directory to a
271 271 revision for testing. Once you have performed tests, mark the
272 272 working directory as bad or good and bisect will either update to
273 273 another candidate changeset or announce that it has found the bad
274 274 revision.
275 275 """
276 276 # backward compatibility
277 277 if rev in "good bad reset init".split():
278 278 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
279 279 cmd, rev, extra = rev, extra, None
280 280 if cmd == "good":
281 281 good = True
282 282 elif cmd == "bad":
283 283 bad = True
284 284 else:
285 285 reset = True
286 286 elif extra or good + bad + skip + reset > 1:
287 287 raise util.Abort("Incompatible arguments")
288 288
289 289 if reset:
290 290 p = repo.join("bisect.state")
291 291 if os.path.exists(p):
292 292 os.unlink(p)
293 293 return
294 294
295 295 # load state
296 296 state = {'good': [], 'bad': [], 'skip': []}
297 297 if os.path.exists(repo.join("bisect.state")):
298 298 for l in repo.opener("bisect.state"):
299 299 kind, node = l[:-1].split()
300 300 node = repo.lookup(node)
301 301 if kind not in state:
302 302 raise util.Abort(_("unknown bisect kind %s") % kind)
303 303 state[kind].append(node)
304 304
305 305 # update state
306 306 node = repo.lookup(rev or '.')
307 307 if good:
308 308 state['good'].append(node)
309 309 elif bad:
310 310 state['bad'].append(node)
311 311 elif skip:
312 312 state['skip'].append(node)
313 313
314 314 # save state
315 315 f = repo.opener("bisect.state", "w", atomictemp=True)
316 316 wlock = repo.wlock()
317 317 try:
318 318 for kind in state:
319 319 for node in state[kind]:
320 320 f.write("%s %s\n" % (kind, hex(node)))
321 321 f.rename()
322 322 finally:
323 323 del wlock
324 324
325 325 if not state['good'] or not state['bad']:
326 326 return
327 327
328 328 # actually bisect
329 329 node, changesets, good = hbisect.bisect(repo.changelog, state)
330 330 if changesets == 0:
331 331 ui.write(_("The first %s revision is:\n") % (good and "good" or "bad"))
332 332 displayer = cmdutil.show_changeset(ui, repo, {})
333 333 displayer.show(changenode=node)
334 334 elif node is not None:
335 335 # compute the approximate number of remaining tests
336 336 tests, size = 0, 2
337 337 while size <= changesets:
338 338 tests, size = tests + 1, size * 2
339 339 rev = repo.changelog.rev(node)
340 340 ui.write(_("Testing changeset %s:%s "
341 341 "(%s changesets remaining, ~%s tests)\n")
342 342 % (rev, short(node), changesets, tests))
343 343 if not noupdate:
344 344 cmdutil.bail_if_changed(repo)
345 345 return hg.clean(repo, node)
346 346
347 347 def branch(ui, repo, label=None, **opts):
348 348 """set or show the current branch name
349 349
350 350 With no argument, show the current branch name. With one argument,
351 351 set the working directory branch name (the branch does not exist in
352 352 the repository until the next commit).
353 353
354 354 Unless --force is specified, branch will not let you set a
355 355 branch name that shadows an existing branch.
356 356
357 357 Use the command 'hg update' to switch to an existing branch.
358 358 """
359 359
360 360 if label:
361 361 if not opts.get('force') and label in repo.branchtags():
362 362 if label not in [p.branch() for p in repo.workingctx().parents()]:
363 363 raise util.Abort(_('a branch of the same name already exists'
364 364 ' (use --force to override)'))
365 365 repo.dirstate.setbranch(util.fromlocal(label))
366 366 ui.status(_('marked working directory as branch %s\n') % label)
367 367 else:
368 368 ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
369 369
370 370 def branches(ui, repo, active=False):
371 371 """list repository named branches
372 372
373 373 List the repository's named branches, indicating which ones are
374 374 inactive. If active is specified, only show active branches.
375 375
376 376 A branch is considered active if it contains unmerged heads.
377 377
378 378 Use the command 'hg update' to switch to an existing branch.
379 379 """
380 380 b = repo.branchtags()
381 381 heads = dict.fromkeys(repo.heads(), 1)
382 382 l = [((n in heads), repo.changelog.rev(n), n, t) for t, n in b.items()]
383 383 l.sort()
384 384 l.reverse()
385 385 for ishead, r, n, t in l:
386 386 if active and not ishead:
387 387 # If we're only displaying active branches, abort the loop on
388 388 # encountering the first inactive head
389 389 break
390 390 else:
391 391 hexfunc = ui.debugflag and hex or short
392 392 if ui.quiet:
393 393 ui.write("%s\n" % t)
394 394 else:
395 395 spaces = " " * (30 - util.locallen(t))
396 396 # The code only gets here if inactive branches are being
397 397 # displayed or the branch is active.
398 398 isinactive = ((not ishead) and " (inactive)") or ''
399 399 ui.write("%s%s %s:%s%s\n" % (t, spaces, r, hexfunc(n), isinactive))
400 400
401 401 def bundle(ui, repo, fname, dest=None, **opts):
402 402 """create a changegroup file
403 403
404 404 Generate a compressed changegroup file collecting changesets not
405 405 found in the other repository.
406 406
407 407 If no destination repository is specified the destination is
408 408 assumed to have all the nodes specified by one or more --base
409 409 parameters. To create a bundle containing all changesets, use
410 410 --all (or --base null). To change the compression method applied,
411 411 use the -t option (by default, bundles are compressed using bz2).
412 412
413 413 The bundle file can then be transferred using conventional means and
414 414 applied to another repository with the unbundle or pull command.
415 415 This is useful when direct push and pull are not available or when
416 416 exporting an entire repository is undesirable.
417 417
418 418 Applying bundles preserves all changeset contents including
419 419 permissions, copy/rename information, and revision history.
420 420 """
421 421 revs = opts.get('rev') or None
422 422 if revs:
423 423 revs = [repo.lookup(rev) for rev in revs]
424 424 if opts.get('all'):
425 425 base = ['null']
426 426 else:
427 427 base = opts.get('base')
428 428 if base:
429 429 if dest:
430 430 raise util.Abort(_("--base is incompatible with specifiying "
431 431 "a destination"))
432 432 base = [repo.lookup(rev) for rev in base]
433 433 # create the right base
434 434 # XXX: nodesbetween / changegroup* should be "fixed" instead
435 435 o = []
436 436 has = {nullid: None}
437 437 for n in base:
438 438 has.update(repo.changelog.reachable(n))
439 439 if revs:
440 440 visit = list(revs)
441 441 else:
442 442 visit = repo.changelog.heads()
443 443 seen = {}
444 444 while visit:
445 445 n = visit.pop(0)
446 446 parents = [p for p in repo.changelog.parents(n) if p not in has]
447 447 if len(parents) == 0:
448 448 o.insert(0, n)
449 449 else:
450 450 for p in parents:
451 451 if p not in seen:
452 452 seen[p] = 1
453 453 visit.append(p)
454 454 else:
455 455 cmdutil.setremoteconfig(ui, opts)
456 456 dest, revs, checkout = hg.parseurl(
457 457 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
458 458 other = hg.repository(ui, dest)
459 459 o = repo.findoutgoing(other, force=opts['force'])
460 460
461 461 if revs:
462 462 cg = repo.changegroupsubset(o, revs, 'bundle')
463 463 else:
464 464 cg = repo.changegroup(o, 'bundle')
465 465
466 466 bundletype = opts.get('type', 'bzip2').lower()
467 467 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
468 468 bundletype = btypes.get(bundletype)
469 469 if bundletype not in changegroup.bundletypes:
470 470 raise util.Abort(_('unknown bundle type specified with --type'))
471 471
472 472 changegroup.writebundle(cg, fname, bundletype)
473 473
474 474 def cat(ui, repo, file1, *pats, **opts):
475 475 """output the current or given revision of files
476 476
477 477 Print the specified files as they were at the given revision.
478 478 If no revision is given, the parent of the working directory is used,
479 479 or tip if no revision is checked out.
480 480
481 481 Output may be to a file, in which case the name of the file is
482 482 given using a format string. The formatting rules are the same as
483 483 for the export command, with the following additions:
484 484
485 485 %s basename of file being printed
486 486 %d dirname of file being printed, or '.' if in repo root
487 487 %p root-relative path name of file being printed
488 488 """
489 489 ctx = repo.changectx(opts['rev'])
490 490 err = 1
491 491 m = cmdutil.match(repo, (file1,) + pats, opts)
492 for src, abs in cmdutil.walk(repo, m, ctx.node()):
492 for src, abs in repo.walk(m, ctx.node()):
493 493 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
494 494 data = ctx.filectx(abs).data()
495 495 if opts.get('decode'):
496 496 data = repo.wwritedata(abs, data)
497 497 fp.write(data)
498 498 err = 0
499 499 return err
500 500
501 501 def clone(ui, source, dest=None, **opts):
502 502 """make a copy of an existing repository
503 503
504 504 Create a copy of an existing repository in a new directory.
505 505
506 506 If no destination directory name is specified, it defaults to the
507 507 basename of the source.
508 508
509 509 The location of the source is added to the new repository's
510 510 .hg/hgrc file, as the default to be used for future pulls.
511 511
512 512 For efficiency, hardlinks are used for cloning whenever the source
513 513 and destination are on the same filesystem (note this applies only
514 514 to the repository data, not to the checked out files). Some
515 515 filesystems, such as AFS, implement hardlinking incorrectly, but
516 516 do not report errors. In these cases, use the --pull option to
517 517 avoid hardlinking.
518 518
519 519 In some cases, you can clone repositories and checked out files
520 520 using full hardlinks with
521 521
522 522 $ cp -al REPO REPOCLONE
523 523
524 524 This is the fastest way to clone, but it is not always safe. The
525 525 operation is not atomic (making sure REPO is not modified during
526 526 the operation is up to you) and you have to make sure your editor
527 527 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
528 528 this is not compatible with certain extensions that place their
529 529 metadata under the .hg directory, such as mq.
530 530
531 531 If you use the -r option to clone up to a specific revision, no
532 532 subsequent revisions will be present in the cloned repository.
533 533 This option implies --pull, even on local repositories.
534 534
535 535 See pull for valid source format details.
536 536
537 537 It is possible to specify an ssh:// URL as the destination, but no
538 538 .hg/hgrc and working directory will be created on the remote side.
539 539 Look at the help text for the pull command for important details
540 540 about ssh:// URLs.
541 541 """
542 542 cmdutil.setremoteconfig(ui, opts)
543 543 hg.clone(ui, source, dest,
544 544 pull=opts['pull'],
545 545 stream=opts['uncompressed'],
546 546 rev=opts['rev'],
547 547 update=not opts['noupdate'])
548 548
549 549 def commit(ui, repo, *pats, **opts):
550 550 """commit the specified files or all outstanding changes
551 551
552 552 Commit changes to the given files into the repository.
553 553
554 554 If a list of files is omitted, all changes reported by "hg status"
555 555 will be committed.
556 556
557 557 If you are committing the result of a merge, do not provide any
558 558 file names or -I/-X filters.
559 559
560 560 If no commit message is specified, the configured editor is started to
561 561 enter a message.
562 562
563 563 See 'hg help dates' for a list of formats valid for -d/--date.
564 564 """
565 565 def commitfunc(ui, repo, files, message, match, opts):
566 566 return repo.commit(files, message, opts['user'], opts['date'], match,
567 567 force_editor=opts.get('force_editor'))
568 568
569 569 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
570 570 if not node:
571 571 return
572 572 cl = repo.changelog
573 573 rev = cl.rev(node)
574 574 parents = cl.parentrevs(rev)
575 575 if rev - 1 in parents:
576 576 # one of the parents was the old tip
577 577 return
578 578 if (parents == (nullrev, nullrev) or
579 579 len(cl.heads(cl.node(parents[0]))) > 1 and
580 580 (parents[1] == nullrev or len(cl.heads(cl.node(parents[1]))) > 1)):
581 581 ui.status(_('created new head\n'))
582 582
583 583 def copy(ui, repo, *pats, **opts):
584 584 """mark files as copied for the next commit
585 585
586 586 Mark dest as having copies of source files. If dest is a
587 587 directory, copies are put in that directory. If dest is a file,
588 588 there can only be one source.
589 589
590 590 By default, this command copies the contents of files as they
591 591 stand in the working directory. If invoked with --after, the
592 592 operation is recorded, but no copying is performed.
593 593
594 594 This command takes effect in the next commit. To undo a copy
595 595 before that, see hg revert.
596 596 """
597 597 wlock = repo.wlock(False)
598 598 try:
599 599 return cmdutil.copy(ui, repo, pats, opts)
600 600 finally:
601 601 del wlock
602 602
603 603 def debugancestor(ui, repo, *args):
604 604 """find the ancestor revision of two revisions in a given index"""
605 605 if len(args) == 3:
606 606 index, rev1, rev2 = args
607 607 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
608 608 lookup = r.lookup
609 609 elif len(args) == 2:
610 610 if not repo:
611 611 raise util.Abort(_("There is no Mercurial repository here "
612 612 "(.hg not found)"))
613 613 rev1, rev2 = args
614 614 r = repo.changelog
615 615 lookup = repo.lookup
616 616 else:
617 617 raise util.Abort(_('either two or three arguments required'))
618 618 a = r.ancestor(lookup(rev1), lookup(rev2))
619 619 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
620 620
621 621 def debugcomplete(ui, cmd='', **opts):
622 622 """returns the completion list associated with the given command"""
623 623
624 624 if opts['options']:
625 625 options = []
626 626 otables = [globalopts]
627 627 if cmd:
628 628 aliases, entry = cmdutil.findcmd(ui, cmd, table)
629 629 otables.append(entry[1])
630 630 for t in otables:
631 631 for o in t:
632 632 if o[0]:
633 633 options.append('-%s' % o[0])
634 634 options.append('--%s' % o[1])
635 635 ui.write("%s\n" % "\n".join(options))
636 636 return
637 637
638 638 clist = cmdutil.findpossible(ui, cmd, table).keys()
639 639 clist.sort()
640 640 ui.write("%s\n" % "\n".join(clist))
641 641
642 642 def debugfsinfo(ui, path = "."):
643 643 file('.debugfsinfo', 'w').write('')
644 644 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
645 645 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
646 646 ui.write('case-sensitive: %s\n' % (util.checkfolding('.debugfsinfo')
647 647 and 'yes' or 'no'))
648 648 os.unlink('.debugfsinfo')
649 649
650 650 def debugrebuildstate(ui, repo, rev=""):
651 651 """rebuild the dirstate as it would look like for the given revision"""
652 652 if rev == "":
653 653 rev = repo.changelog.tip()
654 654 ctx = repo.changectx(rev)
655 655 files = ctx.manifest()
656 656 wlock = repo.wlock()
657 657 try:
658 658 repo.dirstate.rebuild(rev, files)
659 659 finally:
660 660 del wlock
661 661
662 662 def debugcheckstate(ui, repo):
663 663 """validate the correctness of the current dirstate"""
664 664 parent1, parent2 = repo.dirstate.parents()
665 665 m1 = repo.changectx(parent1).manifest()
666 666 m2 = repo.changectx(parent2).manifest()
667 667 errors = 0
668 668 for f in repo.dirstate:
669 669 state = repo.dirstate[f]
670 670 if state in "nr" and f not in m1:
671 671 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
672 672 errors += 1
673 673 if state in "a" and f in m1:
674 674 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
675 675 errors += 1
676 676 if state in "m" and f not in m1 and f not in m2:
677 677 ui.warn(_("%s in state %s, but not in either manifest\n") %
678 678 (f, state))
679 679 errors += 1
680 680 for f in m1:
681 681 state = repo.dirstate[f]
682 682 if state not in "nrm":
683 683 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
684 684 errors += 1
685 685 if errors:
686 686 error = _(".hg/dirstate inconsistent with current parent's manifest")
687 687 raise util.Abort(error)
688 688
689 689 def showconfig(ui, repo, *values, **opts):
690 690 """show combined config settings from all hgrc files
691 691
692 692 With no args, print names and values of all config items.
693 693
694 694 With one arg of the form section.name, print just the value of
695 695 that config item.
696 696
697 697 With multiple args, print names and values of all config items
698 698 with matching section names."""
699 699
700 700 untrusted = bool(opts.get('untrusted'))
701 701 if values:
702 702 if len([v for v in values if '.' in v]) > 1:
703 703 raise util.Abort(_('only one config item permitted'))
704 704 for section, name, value in ui.walkconfig(untrusted=untrusted):
705 705 sectname = section + '.' + name
706 706 if values:
707 707 for v in values:
708 708 if v == section:
709 709 ui.write('%s=%s\n' % (sectname, value))
710 710 elif v == sectname:
711 711 ui.write(value, '\n')
712 712 else:
713 713 ui.write('%s=%s\n' % (sectname, value))
714 714
715 715 def debugsetparents(ui, repo, rev1, rev2=None):
716 716 """manually set the parents of the current working directory
717 717
718 718 This is useful for writing repository conversion tools, but should
719 719 be used with care.
720 720 """
721 721
722 722 if not rev2:
723 723 rev2 = hex(nullid)
724 724
725 725 wlock = repo.wlock()
726 726 try:
727 727 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
728 728 finally:
729 729 del wlock
730 730
731 731 def debugstate(ui, repo, nodates=None):
732 732 """show the contents of the current dirstate"""
733 733 k = repo.dirstate._map.items()
734 734 k.sort()
735 735 timestr = ""
736 736 showdate = not nodates
737 737 for file_, ent in k:
738 738 if showdate:
739 739 if ent[3] == -1:
740 740 # Pad or slice to locale representation
741 741 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(0)))
742 742 timestr = 'unset'
743 743 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
744 744 else:
745 745 timestr = time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(ent[3]))
746 746 if ent[1] & 020000:
747 747 mode = 'lnk'
748 748 else:
749 749 mode = '%3o' % (ent[1] & 0777)
750 750 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
751 751 for f in repo.dirstate.copies():
752 752 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
753 753
754 754 def debugdata(ui, file_, rev):
755 755 """dump the contents of a data file revision"""
756 756 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
757 757 try:
758 758 ui.write(r.revision(r.lookup(rev)))
759 759 except KeyError:
760 760 raise util.Abort(_('invalid revision identifier %s') % rev)
761 761
762 762 def debugdate(ui, date, range=None, **opts):
763 763 """parse and display a date"""
764 764 if opts["extended"]:
765 765 d = util.parsedate(date, util.extendeddateformats)
766 766 else:
767 767 d = util.parsedate(date)
768 768 ui.write("internal: %s %s\n" % d)
769 769 ui.write("standard: %s\n" % util.datestr(d))
770 770 if range:
771 771 m = util.matchdate(range)
772 772 ui.write("match: %s\n" % m(d[0]))
773 773
774 774 def debugindex(ui, file_):
775 775 """dump the contents of an index file"""
776 776 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
777 777 ui.write(" rev offset length base linkrev" +
778 778 " nodeid p1 p2\n")
779 779 for i in xrange(r.count()):
780 780 node = r.node(i)
781 781 try:
782 782 pp = r.parents(node)
783 783 except:
784 784 pp = [nullid, nullid]
785 785 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
786 786 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
787 787 short(node), short(pp[0]), short(pp[1])))
788 788
789 789 def debugindexdot(ui, file_):
790 790 """dump an index DAG as a .dot file"""
791 791 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
792 792 ui.write("digraph G {\n")
793 793 for i in xrange(r.count()):
794 794 node = r.node(i)
795 795 pp = r.parents(node)
796 796 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
797 797 if pp[1] != nullid:
798 798 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
799 799 ui.write("}\n")
800 800
801 801 def debuginstall(ui):
802 802 '''test Mercurial installation'''
803 803
804 804 def writetemp(contents):
805 805 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
806 806 f = os.fdopen(fd, "wb")
807 807 f.write(contents)
808 808 f.close()
809 809 return name
810 810
811 811 problems = 0
812 812
813 813 # encoding
814 814 ui.status(_("Checking encoding (%s)...\n") % util._encoding)
815 815 try:
816 816 util.fromlocal("test")
817 817 except util.Abort, inst:
818 818 ui.write(" %s\n" % inst)
819 819 ui.write(_(" (check that your locale is properly set)\n"))
820 820 problems += 1
821 821
822 822 # compiled modules
823 823 ui.status(_("Checking extensions...\n"))
824 824 try:
825 825 import bdiff, mpatch, base85
826 826 except Exception, inst:
827 827 ui.write(" %s\n" % inst)
828 828 ui.write(_(" One or more extensions could not be found"))
829 829 ui.write(_(" (check that you compiled the extensions)\n"))
830 830 problems += 1
831 831
832 832 # templates
833 833 ui.status(_("Checking templates...\n"))
834 834 try:
835 835 import templater
836 836 t = templater.templater(templater.templatepath("map-cmdline.default"))
837 837 except Exception, inst:
838 838 ui.write(" %s\n" % inst)
839 839 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
840 840 problems += 1
841 841
842 842 # patch
843 843 ui.status(_("Checking patch...\n"))
844 844 patchproblems = 0
845 845 a = "1\n2\n3\n4\n"
846 846 b = "1\n2\n3\ninsert\n4\n"
847 847 fa = writetemp(a)
848 848 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
849 849 os.path.basename(fa))
850 850 fd = writetemp(d)
851 851
852 852 files = {}
853 853 try:
854 854 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
855 855 except util.Abort, e:
856 856 ui.write(_(" patch call failed:\n"))
857 857 ui.write(" " + str(e) + "\n")
858 858 patchproblems += 1
859 859 else:
860 860 if list(files) != [os.path.basename(fa)]:
861 861 ui.write(_(" unexpected patch output!\n"))
862 862 patchproblems += 1
863 863 a = file(fa).read()
864 864 if a != b:
865 865 ui.write(_(" patch test failed!\n"))
866 866 patchproblems += 1
867 867
868 868 if patchproblems:
869 869 if ui.config('ui', 'patch'):
870 870 ui.write(_(" (Current patch tool may be incompatible with patch,"
871 871 " or misconfigured. Please check your .hgrc file)\n"))
872 872 else:
873 873 ui.write(_(" Internal patcher failure, please report this error"
874 874 " to http://www.selenic.com/mercurial/bts\n"))
875 875 problems += patchproblems
876 876
877 877 os.unlink(fa)
878 878 os.unlink(fd)
879 879
880 880 # editor
881 881 ui.status(_("Checking commit editor...\n"))
882 882 editor = ui.geteditor()
883 883 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
884 884 if not cmdpath:
885 885 if editor == 'vi':
886 886 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
887 887 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
888 888 else:
889 889 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
890 890 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
891 891 problems += 1
892 892
893 893 # check username
894 894 ui.status(_("Checking username...\n"))
895 895 user = os.environ.get("HGUSER")
896 896 if user is None:
897 897 user = ui.config("ui", "username")
898 898 if user is None:
899 899 user = os.environ.get("EMAIL")
900 900 if not user:
901 901 ui.warn(" ")
902 902 ui.username()
903 903 ui.write(_(" (specify a username in your .hgrc file)\n"))
904 904
905 905 if not problems:
906 906 ui.status(_("No problems detected\n"))
907 907 else:
908 908 ui.write(_("%s problems detected,"
909 909 " please check your install!\n") % problems)
910 910
911 911 return problems
912 912
913 913 def debugrename(ui, repo, file1, *pats, **opts):
914 914 """dump rename information"""
915 915
916 916 ctx = repo.changectx(opts.get('rev', 'tip'))
917 917 m = cmdutil.match(repo, (file1,) + pats, opts)
918 for src, abs in cmdutil.walk(repo, m, ctx.node()):
918 for src, abs in repo.walk(m, ctx.node()):
919 919 fctx = ctx.filectx(abs)
920 920 o = fctx.filelog().renamed(fctx.filenode())
921 921 rel = m.rel(abs)
922 922 if o:
923 923 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
924 924 else:
925 925 ui.write(_("%s not renamed\n") % rel)
926 926
927 927 def debugwalk(ui, repo, *pats, **opts):
928 928 """show how files match on given patterns"""
929 929 m = cmdutil.match(repo, pats, opts)
930 items = list(cmdutil.walk(repo, m))
930 items = list(repo.walk(m))
931 931 if not items:
932 932 return
933 933 fmt = '%%s %%-%ds %%-%ds %%s' % (
934 934 max([len(abs) for (src, abs) in items]),
935 935 max([len(m.rel(abs)) for (src, abs) in items]))
936 936 for src, abs in items:
937 937 line = fmt % (src, abs, m.rel(abs), m.exact(abs) and 'exact' or '')
938 938 ui.write("%s\n" % line.rstrip())
939 939
940 940 def diff(ui, repo, *pats, **opts):
941 941 """diff repository (or selected files)
942 942
943 943 Show differences between revisions for the specified files.
944 944
945 945 Differences between files are shown using the unified diff format.
946 946
947 947 NOTE: diff may generate unexpected results for merges, as it will
948 948 default to comparing against the working directory's first parent
949 949 changeset if no revisions are specified.
950 950
951 951 When two revision arguments are given, then changes are shown
952 952 between those revisions. If only one revision is specified then
953 953 that revision is compared to the working directory, and, when no
954 954 revisions are specified, the working directory files are compared
955 955 to its parent.
956 956
957 957 Without the -a option, diff will avoid generating diffs of files
958 958 it detects as binary. With -a, diff will generate a diff anyway,
959 959 probably with undesirable results.
960 960 """
961 961 node1, node2 = cmdutil.revpair(repo, opts['rev'])
962 962
963 963 m = cmdutil.match(repo, pats, opts)
964 964 patch.diff(repo, node1, node2, m.files(), match=m,
965 965 opts=patch.diffopts(ui, opts))
966 966
967 967 def export(ui, repo, *changesets, **opts):
968 968 """dump the header and diffs for one or more changesets
969 969
970 970 Print the changeset header and diffs for one or more revisions.
971 971
972 972 The information shown in the changeset header is: author,
973 973 changeset hash, parent(s) and commit comment.
974 974
975 975 NOTE: export may generate unexpected diff output for merge changesets,
976 976 as it will compare the merge changeset against its first parent only.
977 977
978 978 Output may be to a file, in which case the name of the file is
979 979 given using a format string. The formatting rules are as follows:
980 980
981 981 %% literal "%" character
982 982 %H changeset hash (40 bytes of hexadecimal)
983 983 %N number of patches being generated
984 984 %R changeset revision number
985 985 %b basename of the exporting repository
986 986 %h short-form changeset hash (12 bytes of hexadecimal)
987 987 %n zero-padded sequence number, starting at 1
988 988 %r zero-padded changeset revision number
989 989
990 990 Without the -a option, export will avoid generating diffs of files
991 991 it detects as binary. With -a, export will generate a diff anyway,
992 992 probably with undesirable results.
993 993
994 994 With the --switch-parent option, the diff will be against the second
995 995 parent. It can be useful to review a merge.
996 996 """
997 997 if not changesets:
998 998 raise util.Abort(_("export requires at least one changeset"))
999 999 revs = cmdutil.revrange(repo, changesets)
1000 1000 if len(revs) > 1:
1001 1001 ui.note(_('exporting patches:\n'))
1002 1002 else:
1003 1003 ui.note(_('exporting patch:\n'))
1004 1004 patch.export(repo, revs, template=opts['output'],
1005 1005 switch_parent=opts['switch_parent'],
1006 1006 opts=patch.diffopts(ui, opts))
1007 1007
1008 1008 def grep(ui, repo, pattern, *pats, **opts):
1009 1009 """search for a pattern in specified files and revisions
1010 1010
1011 1011 Search revisions of files for a regular expression.
1012 1012
1013 1013 This command behaves differently than Unix grep. It only accepts
1014 1014 Python/Perl regexps. It searches repository history, not the
1015 1015 working directory. It always prints the revision number in which
1016 1016 a match appears.
1017 1017
1018 1018 By default, grep only prints output for the first revision of a
1019 1019 file in which it finds a match. To get it to print every revision
1020 1020 that contains a change in match status ("-" for a match that
1021 1021 becomes a non-match, or "+" for a non-match that becomes a match),
1022 1022 use the --all flag.
1023 1023 """
1024 1024 reflags = 0
1025 1025 if opts['ignore_case']:
1026 1026 reflags |= re.I
1027 1027 try:
1028 1028 regexp = re.compile(pattern, reflags)
1029 1029 except Exception, inst:
1030 1030 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1031 1031 return None
1032 1032 sep, eol = ':', '\n'
1033 1033 if opts['print0']:
1034 1034 sep = eol = '\0'
1035 1035
1036 1036 fcache = {}
1037 1037 def getfile(fn):
1038 1038 if fn not in fcache:
1039 1039 fcache[fn] = repo.file(fn)
1040 1040 return fcache[fn]
1041 1041
1042 1042 def matchlines(body):
1043 1043 begin = 0
1044 1044 linenum = 0
1045 1045 while True:
1046 1046 match = regexp.search(body, begin)
1047 1047 if not match:
1048 1048 break
1049 1049 mstart, mend = match.span()
1050 1050 linenum += body.count('\n', begin, mstart) + 1
1051 1051 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1052 1052 lend = body.find('\n', mend)
1053 1053 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1054 1054 begin = lend + 1
1055 1055
1056 1056 class linestate(object):
1057 1057 def __init__(self, line, linenum, colstart, colend):
1058 1058 self.line = line
1059 1059 self.linenum = linenum
1060 1060 self.colstart = colstart
1061 1061 self.colend = colend
1062 1062
1063 1063 def __hash__(self):
1064 1064 return hash((self.linenum, self.line))
1065 1065
1066 1066 def __eq__(self, other):
1067 1067 return self.line == other.line
1068 1068
1069 1069 matches = {}
1070 1070 copies = {}
1071 1071 def grepbody(fn, rev, body):
1072 1072 matches[rev].setdefault(fn, [])
1073 1073 m = matches[rev][fn]
1074 1074 for lnum, cstart, cend, line in matchlines(body):
1075 1075 s = linestate(line, lnum, cstart, cend)
1076 1076 m.append(s)
1077 1077
1078 1078 def difflinestates(a, b):
1079 1079 sm = difflib.SequenceMatcher(None, a, b)
1080 1080 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1081 1081 if tag == 'insert':
1082 1082 for i in xrange(blo, bhi):
1083 1083 yield ('+', b[i])
1084 1084 elif tag == 'delete':
1085 1085 for i in xrange(alo, ahi):
1086 1086 yield ('-', a[i])
1087 1087 elif tag == 'replace':
1088 1088 for i in xrange(alo, ahi):
1089 1089 yield ('-', a[i])
1090 1090 for i in xrange(blo, bhi):
1091 1091 yield ('+', b[i])
1092 1092
1093 1093 prev = {}
1094 1094 def display(fn, rev, states, prevstates):
1095 1095 datefunc = ui.quiet and util.shortdate or util.datestr
1096 1096 found = False
1097 1097 filerevmatches = {}
1098 1098 r = prev.get(fn, -1)
1099 1099 if opts['all']:
1100 1100 iter = difflinestates(states, prevstates)
1101 1101 else:
1102 1102 iter = [('', l) for l in prevstates]
1103 1103 for change, l in iter:
1104 1104 cols = [fn, str(r)]
1105 1105 if opts['line_number']:
1106 1106 cols.append(str(l.linenum))
1107 1107 if opts['all']:
1108 1108 cols.append(change)
1109 1109 if opts['user']:
1110 1110 cols.append(ui.shortuser(get(r)[1]))
1111 1111 if opts.get('date'):
1112 1112 cols.append(datefunc(get(r)[2]))
1113 1113 if opts['files_with_matches']:
1114 1114 c = (fn, r)
1115 1115 if c in filerevmatches:
1116 1116 continue
1117 1117 filerevmatches[c] = 1
1118 1118 else:
1119 1119 cols.append(l.line)
1120 1120 ui.write(sep.join(cols), eol)
1121 1121 found = True
1122 1122 return found
1123 1123
1124 1124 fstate = {}
1125 1125 skip = {}
1126 1126 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1127 1127 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1128 1128 found = False
1129 1129 follow = opts.get('follow')
1130 1130 for st, rev, fns in changeiter:
1131 1131 if st == 'window':
1132 1132 matches.clear()
1133 1133 elif st == 'add':
1134 1134 ctx = repo.changectx(rev)
1135 1135 matches[rev] = {}
1136 1136 for fn in fns:
1137 1137 if fn in skip:
1138 1138 continue
1139 1139 try:
1140 1140 grepbody(fn, rev, getfile(fn).read(ctx.filenode(fn)))
1141 1141 fstate.setdefault(fn, [])
1142 1142 if follow:
1143 1143 copied = getfile(fn).renamed(ctx.filenode(fn))
1144 1144 if copied:
1145 1145 copies.setdefault(rev, {})[fn] = copied[0]
1146 1146 except revlog.LookupError:
1147 1147 pass
1148 1148 elif st == 'iter':
1149 1149 states = matches[rev].items()
1150 1150 states.sort()
1151 1151 for fn, m in states:
1152 1152 copy = copies.get(rev, {}).get(fn)
1153 1153 if fn in skip:
1154 1154 if copy:
1155 1155 skip[copy] = True
1156 1156 continue
1157 1157 if fn in prev or fstate[fn]:
1158 1158 r = display(fn, rev, m, fstate[fn])
1159 1159 found = found or r
1160 1160 if r and not opts['all']:
1161 1161 skip[fn] = True
1162 1162 if copy:
1163 1163 skip[copy] = True
1164 1164 fstate[fn] = m
1165 1165 if copy:
1166 1166 fstate[copy] = m
1167 1167 prev[fn] = rev
1168 1168
1169 1169 fstate = fstate.items()
1170 1170 fstate.sort()
1171 1171 for fn, state in fstate:
1172 1172 if fn in skip:
1173 1173 continue
1174 1174 if fn not in copies.get(prev[fn], {}):
1175 1175 found = display(fn, rev, {}, state) or found
1176 1176 return (not found and 1) or 0
1177 1177
1178 1178 def heads(ui, repo, *branchrevs, **opts):
1179 1179 """show current repository heads or show branch heads
1180 1180
1181 1181 With no arguments, show all repository head changesets.
1182 1182
1183 1183 If branch or revisions names are given this will show the heads of
1184 1184 the specified branches or the branches those revisions are tagged
1185 1185 with.
1186 1186
1187 1187 Repository "heads" are changesets that don't have child
1188 1188 changesets. They are where development generally takes place and
1189 1189 are the usual targets for update and merge operations.
1190 1190
1191 1191 Branch heads are changesets that have a given branch tag, but have
1192 1192 no child changesets with that tag. They are usually where
1193 1193 development on the given branch takes place.
1194 1194 """
1195 1195 if opts['rev']:
1196 1196 start = repo.lookup(opts['rev'])
1197 1197 else:
1198 1198 start = None
1199 1199 if not branchrevs:
1200 1200 # Assume we're looking repo-wide heads if no revs were specified.
1201 1201 heads = repo.heads(start)
1202 1202 else:
1203 1203 heads = []
1204 1204 visitedset = util.set()
1205 1205 for branchrev in branchrevs:
1206 1206 branch = repo.changectx(branchrev).branch()
1207 1207 if branch in visitedset:
1208 1208 continue
1209 1209 visitedset.add(branch)
1210 1210 bheads = repo.branchheads(branch, start)
1211 1211 if not bheads:
1212 1212 if branch != branchrev:
1213 1213 ui.warn(_("no changes on branch %s containing %s are "
1214 1214 "reachable from %s\n")
1215 1215 % (branch, branchrev, opts['rev']))
1216 1216 else:
1217 1217 ui.warn(_("no changes on branch %s are reachable from %s\n")
1218 1218 % (branch, opts['rev']))
1219 1219 heads.extend(bheads)
1220 1220 if not heads:
1221 1221 return 1
1222 1222 displayer = cmdutil.show_changeset(ui, repo, opts)
1223 1223 for n in heads:
1224 1224 displayer.show(changenode=n)
1225 1225
1226 1226 def help_(ui, name=None, with_version=False):
1227 1227 """show help for a command, extension, or list of commands
1228 1228
1229 1229 With no arguments, print a list of commands and short help.
1230 1230
1231 1231 Given a command name, print help for that command.
1232 1232
1233 1233 Given an extension name, print help for that extension, and the
1234 1234 commands it provides."""
1235 1235 option_lists = []
1236 1236
1237 1237 def addglobalopts(aliases):
1238 1238 if ui.verbose:
1239 1239 option_lists.append((_("global options:"), globalopts))
1240 1240 if name == 'shortlist':
1241 1241 option_lists.append((_('use "hg help" for the full list '
1242 1242 'of commands'), ()))
1243 1243 else:
1244 1244 if name == 'shortlist':
1245 1245 msg = _('use "hg help" for the full list of commands '
1246 1246 'or "hg -v" for details')
1247 1247 elif aliases:
1248 1248 msg = _('use "hg -v help%s" to show aliases and '
1249 1249 'global options') % (name and " " + name or "")
1250 1250 else:
1251 1251 msg = _('use "hg -v help %s" to show global options') % name
1252 1252 option_lists.append((msg, ()))
1253 1253
1254 1254 def helpcmd(name):
1255 1255 if with_version:
1256 1256 version_(ui)
1257 1257 ui.write('\n')
1258 1258 aliases, i = cmdutil.findcmd(ui, name, table)
1259 1259 # synopsis
1260 1260 ui.write("%s\n" % i[2])
1261 1261
1262 1262 # aliases
1263 1263 if not ui.quiet and len(aliases) > 1:
1264 1264 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1265 1265
1266 1266 # description
1267 1267 doc = i[0].__doc__
1268 1268 if not doc:
1269 1269 doc = _("(No help text available)")
1270 1270 if ui.quiet:
1271 1271 doc = doc.splitlines(0)[0]
1272 1272 ui.write("\n%s\n" % doc.rstrip())
1273 1273
1274 1274 if not ui.quiet:
1275 1275 # options
1276 1276 if i[1]:
1277 1277 option_lists.append((_("options:\n"), i[1]))
1278 1278
1279 1279 addglobalopts(False)
1280 1280
1281 1281 def helplist(header, select=None):
1282 1282 h = {}
1283 1283 cmds = {}
1284 1284 for c, e in table.items():
1285 1285 f = c.split("|", 1)[0]
1286 1286 if select and not select(f):
1287 1287 continue
1288 1288 if name == "shortlist" and not f.startswith("^"):
1289 1289 continue
1290 1290 f = f.lstrip("^")
1291 1291 if not ui.debugflag and f.startswith("debug"):
1292 1292 continue
1293 1293 doc = e[0].__doc__
1294 1294 if not doc:
1295 1295 doc = _("(No help text available)")
1296 1296 h[f] = doc.splitlines(0)[0].rstrip()
1297 1297 cmds[f] = c.lstrip("^")
1298 1298
1299 1299 if not h:
1300 1300 ui.status(_('no commands defined\n'))
1301 1301 return
1302 1302
1303 1303 ui.status(header)
1304 1304 fns = h.keys()
1305 1305 fns.sort()
1306 1306 m = max(map(len, fns))
1307 1307 for f in fns:
1308 1308 if ui.verbose:
1309 1309 commands = cmds[f].replace("|",", ")
1310 1310 ui.write(" %s:\n %s\n"%(commands, h[f]))
1311 1311 else:
1312 1312 ui.write(' %-*s %s\n' % (m, f, h[f]))
1313 1313
1314 1314 if not ui.quiet:
1315 1315 addglobalopts(True)
1316 1316
1317 1317 def helptopic(name):
1318 1318 v = None
1319 1319 for i in help.helptable:
1320 1320 l = i.split('|')
1321 1321 if name in l:
1322 1322 v = i
1323 1323 header = l[-1]
1324 1324 if not v:
1325 1325 raise cmdutil.UnknownCommand(name)
1326 1326
1327 1327 # description
1328 1328 doc = help.helptable[v]
1329 1329 if not doc:
1330 1330 doc = _("(No help text available)")
1331 1331 if callable(doc):
1332 1332 doc = doc()
1333 1333
1334 1334 ui.write("%s\n" % header)
1335 1335 ui.write("%s\n" % doc.rstrip())
1336 1336
1337 1337 def helpext(name):
1338 1338 try:
1339 1339 mod = extensions.find(name)
1340 1340 except KeyError:
1341 1341 raise cmdutil.UnknownCommand(name)
1342 1342
1343 1343 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
1344 1344 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1345 1345 for d in doc[1:]:
1346 1346 ui.write(d, '\n')
1347 1347
1348 1348 ui.status('\n')
1349 1349
1350 1350 try:
1351 1351 ct = mod.cmdtable
1352 1352 except AttributeError:
1353 1353 ct = {}
1354 1354
1355 1355 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct])
1356 1356 helplist(_('list of commands:\n\n'), modcmds.has_key)
1357 1357
1358 1358 if name and name != 'shortlist':
1359 1359 i = None
1360 1360 for f in (helpcmd, helptopic, helpext):
1361 1361 try:
1362 1362 f(name)
1363 1363 i = None
1364 1364 break
1365 1365 except cmdutil.UnknownCommand, inst:
1366 1366 i = inst
1367 1367 if i:
1368 1368 raise i
1369 1369
1370 1370 else:
1371 1371 # program name
1372 1372 if ui.verbose or with_version:
1373 1373 version_(ui)
1374 1374 else:
1375 1375 ui.status(_("Mercurial Distributed SCM\n"))
1376 1376 ui.status('\n')
1377 1377
1378 1378 # list of commands
1379 1379 if name == "shortlist":
1380 1380 header = _('basic commands:\n\n')
1381 1381 else:
1382 1382 header = _('list of commands:\n\n')
1383 1383
1384 1384 helplist(header)
1385 1385
1386 1386 # list all option lists
1387 1387 opt_output = []
1388 1388 for title, options in option_lists:
1389 1389 opt_output.append(("\n%s" % title, None))
1390 1390 for shortopt, longopt, default, desc in options:
1391 1391 if "DEPRECATED" in desc and not ui.verbose: continue
1392 1392 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1393 1393 longopt and " --%s" % longopt),
1394 1394 "%s%s" % (desc,
1395 1395 default
1396 1396 and _(" (default: %s)") % default
1397 1397 or "")))
1398 1398
1399 1399 if opt_output:
1400 1400 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1401 1401 for first, second in opt_output:
1402 1402 if second:
1403 1403 ui.write(" %-*s %s\n" % (opts_len, first, second))
1404 1404 else:
1405 1405 ui.write("%s\n" % first)
1406 1406
1407 1407 def identify(ui, repo, source=None,
1408 1408 rev=None, num=None, id=None, branch=None, tags=None):
1409 1409 """identify the working copy or specified revision
1410 1410
1411 1411 With no revision, print a summary of the current state of the repo.
1412 1412
1413 1413 With a path, do a lookup in another repository.
1414 1414
1415 1415 This summary identifies the repository state using one or two parent
1416 1416 hash identifiers, followed by a "+" if there are uncommitted changes
1417 1417 in the working directory, a list of tags for this revision and a branch
1418 1418 name for non-default branches.
1419 1419 """
1420 1420
1421 1421 if not repo and not source:
1422 1422 raise util.Abort(_("There is no Mercurial repository here "
1423 1423 "(.hg not found)"))
1424 1424
1425 1425 hexfunc = ui.debugflag and hex or short
1426 1426 default = not (num or id or branch or tags)
1427 1427 output = []
1428 1428
1429 1429 if source:
1430 1430 source, revs, checkout = hg.parseurl(ui.expandpath(source), [])
1431 1431 srepo = hg.repository(ui, source)
1432 1432 if not rev and revs:
1433 1433 rev = revs[0]
1434 1434 if not rev:
1435 1435 rev = "tip"
1436 1436 if num or branch or tags:
1437 1437 raise util.Abort(
1438 1438 "can't query remote revision number, branch, or tags")
1439 1439 output = [hexfunc(srepo.lookup(rev))]
1440 1440 elif not rev:
1441 1441 ctx = repo.workingctx()
1442 1442 parents = ctx.parents()
1443 1443 changed = False
1444 1444 if default or id or num:
1445 1445 changed = ctx.files() + ctx.deleted()
1446 1446 if default or id:
1447 1447 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1448 1448 (changed) and "+" or "")]
1449 1449 if num:
1450 1450 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1451 1451 (changed) and "+" or ""))
1452 1452 else:
1453 1453 ctx = repo.changectx(rev)
1454 1454 if default or id:
1455 1455 output = [hexfunc(ctx.node())]
1456 1456 if num:
1457 1457 output.append(str(ctx.rev()))
1458 1458
1459 1459 if not source and default and not ui.quiet:
1460 1460 b = util.tolocal(ctx.branch())
1461 1461 if b != 'default':
1462 1462 output.append("(%s)" % b)
1463 1463
1464 1464 # multiple tags for a single parent separated by '/'
1465 1465 t = "/".join(ctx.tags())
1466 1466 if t:
1467 1467 output.append(t)
1468 1468
1469 1469 if branch:
1470 1470 output.append(util.tolocal(ctx.branch()))
1471 1471
1472 1472 if tags:
1473 1473 output.extend(ctx.tags())
1474 1474
1475 1475 ui.write("%s\n" % ' '.join(output))
1476 1476
1477 1477 def import_(ui, repo, patch1, *patches, **opts):
1478 1478 """import an ordered set of patches
1479 1479
1480 1480 Import a list of patches and commit them individually.
1481 1481
1482 1482 If there are outstanding changes in the working directory, import
1483 1483 will abort unless given the -f flag.
1484 1484
1485 1485 You can import a patch straight from a mail message. Even patches
1486 1486 as attachments work (body part must be type text/plain or
1487 1487 text/x-patch to be used). From and Subject headers of email
1488 1488 message are used as default committer and commit message. All
1489 1489 text/plain body parts before first diff are added to commit
1490 1490 message.
1491 1491
1492 1492 If the imported patch was generated by hg export, user and description
1493 1493 from patch override values from message headers and body. Values
1494 1494 given on command line with -m and -u override these.
1495 1495
1496 1496 If --exact is specified, import will set the working directory
1497 1497 to the parent of each patch before applying it, and will abort
1498 1498 if the resulting changeset has a different ID than the one
1499 1499 recorded in the patch. This may happen due to character set
1500 1500 problems or other deficiencies in the text patch format.
1501 1501
1502 1502 To read a patch from standard input, use patch name "-".
1503 1503 See 'hg help dates' for a list of formats valid for -d/--date.
1504 1504 """
1505 1505 patches = (patch1,) + patches
1506 1506
1507 1507 date = opts.get('date')
1508 1508 if date:
1509 1509 opts['date'] = util.parsedate(date)
1510 1510
1511 1511 if opts.get('exact') or not opts['force']:
1512 1512 cmdutil.bail_if_changed(repo)
1513 1513
1514 1514 d = opts["base"]
1515 1515 strip = opts["strip"]
1516 1516 wlock = lock = None
1517 1517 try:
1518 1518 wlock = repo.wlock()
1519 1519 lock = repo.lock()
1520 1520 for p in patches:
1521 1521 pf = os.path.join(d, p)
1522 1522
1523 1523 if pf == '-':
1524 1524 ui.status(_("applying patch from stdin\n"))
1525 1525 data = patch.extract(ui, sys.stdin)
1526 1526 else:
1527 1527 ui.status(_("applying %s\n") % p)
1528 1528 if os.path.exists(pf):
1529 1529 data = patch.extract(ui, file(pf, 'rb'))
1530 1530 else:
1531 1531 data = patch.extract(ui, urllib.urlopen(pf))
1532 1532 tmpname, message, user, date, branch, nodeid, p1, p2 = data
1533 1533
1534 1534 if tmpname is None:
1535 1535 raise util.Abort(_('no diffs found'))
1536 1536
1537 1537 try:
1538 1538 cmdline_message = cmdutil.logmessage(opts)
1539 1539 if cmdline_message:
1540 1540 # pickup the cmdline msg
1541 1541 message = cmdline_message
1542 1542 elif message:
1543 1543 # pickup the patch msg
1544 1544 message = message.strip()
1545 1545 else:
1546 1546 # launch the editor
1547 1547 message = None
1548 1548 ui.debug(_('message:\n%s\n') % message)
1549 1549
1550 1550 wp = repo.workingctx().parents()
1551 1551 if opts.get('exact'):
1552 1552 if not nodeid or not p1:
1553 1553 raise util.Abort(_('not a mercurial patch'))
1554 1554 p1 = repo.lookup(p1)
1555 1555 p2 = repo.lookup(p2 or hex(nullid))
1556 1556
1557 1557 if p1 != wp[0].node():
1558 1558 hg.clean(repo, p1)
1559 1559 repo.dirstate.setparents(p1, p2)
1560 1560 elif p2:
1561 1561 try:
1562 1562 p1 = repo.lookup(p1)
1563 1563 p2 = repo.lookup(p2)
1564 1564 if p1 == wp[0].node():
1565 1565 repo.dirstate.setparents(p1, p2)
1566 1566 except RepoError:
1567 1567 pass
1568 1568 if opts.get('exact') or opts.get('import_branch'):
1569 1569 repo.dirstate.setbranch(branch or 'default')
1570 1570
1571 1571 files = {}
1572 1572 try:
1573 1573 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1574 1574 files=files)
1575 1575 finally:
1576 1576 files = patch.updatedir(ui, repo, files)
1577 1577 if not opts.get('no_commit'):
1578 1578 n = repo.commit(files, message, opts.get('user') or user,
1579 1579 opts.get('date') or date)
1580 1580 if opts.get('exact'):
1581 1581 if hex(n) != nodeid:
1582 1582 repo.rollback()
1583 1583 raise util.Abort(_('patch is damaged'
1584 1584 ' or loses information'))
1585 1585 # Force a dirstate write so that the next transaction
1586 1586 # backups an up-do-date file.
1587 1587 repo.dirstate.write()
1588 1588 finally:
1589 1589 os.unlink(tmpname)
1590 1590 finally:
1591 1591 del lock, wlock
1592 1592
1593 1593 def incoming(ui, repo, source="default", **opts):
1594 1594 """show new changesets found in source
1595 1595
1596 1596 Show new changesets found in the specified path/URL or the default
1597 1597 pull location. These are the changesets that would be pulled if a pull
1598 1598 was requested.
1599 1599
1600 1600 For remote repository, using --bundle avoids downloading the changesets
1601 1601 twice if the incoming is followed by a pull.
1602 1602
1603 1603 See pull for valid source format details.
1604 1604 """
1605 1605 limit = cmdutil.loglimit(opts)
1606 1606 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
1607 1607 cmdutil.setremoteconfig(ui, opts)
1608 1608
1609 1609 other = hg.repository(ui, source)
1610 1610 ui.status(_('comparing with %s\n') % util.hidepassword(source))
1611 1611 if revs:
1612 1612 revs = [other.lookup(rev) for rev in revs]
1613 1613 incoming = repo.findincoming(other, heads=revs, force=opts["force"])
1614 1614 if not incoming:
1615 1615 try:
1616 1616 os.unlink(opts["bundle"])
1617 1617 except:
1618 1618 pass
1619 1619 ui.status(_("no changes found\n"))
1620 1620 return 1
1621 1621
1622 1622 cleanup = None
1623 1623 try:
1624 1624 fname = opts["bundle"]
1625 1625 if fname or not other.local():
1626 1626 # create a bundle (uncompressed if other repo is not local)
1627 1627 if revs is None:
1628 1628 cg = other.changegroup(incoming, "incoming")
1629 1629 else:
1630 1630 cg = other.changegroupsubset(incoming, revs, 'incoming')
1631 1631 bundletype = other.local() and "HG10BZ" or "HG10UN"
1632 1632 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1633 1633 # keep written bundle?
1634 1634 if opts["bundle"]:
1635 1635 cleanup = None
1636 1636 if not other.local():
1637 1637 # use the created uncompressed bundlerepo
1638 1638 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1639 1639
1640 1640 o = other.changelog.nodesbetween(incoming, revs)[0]
1641 1641 if opts['newest_first']:
1642 1642 o.reverse()
1643 1643 displayer = cmdutil.show_changeset(ui, other, opts)
1644 1644 count = 0
1645 1645 for n in o:
1646 1646 if count >= limit:
1647 1647 break
1648 1648 parents = [p for p in other.changelog.parents(n) if p != nullid]
1649 1649 if opts['no_merges'] and len(parents) == 2:
1650 1650 continue
1651 1651 count += 1
1652 1652 displayer.show(changenode=n)
1653 1653 finally:
1654 1654 if hasattr(other, 'close'):
1655 1655 other.close()
1656 1656 if cleanup:
1657 1657 os.unlink(cleanup)
1658 1658
1659 1659 def init(ui, dest=".", **opts):
1660 1660 """create a new repository in the given directory
1661 1661
1662 1662 Initialize a new repository in the given directory. If the given
1663 1663 directory does not exist, it is created.
1664 1664
1665 1665 If no directory is given, the current directory is used.
1666 1666
1667 1667 It is possible to specify an ssh:// URL as the destination.
1668 1668 Look at the help text for the pull command for important details
1669 1669 about ssh:// URLs.
1670 1670 """
1671 1671 cmdutil.setremoteconfig(ui, opts)
1672 1672 hg.repository(ui, dest, create=1)
1673 1673
1674 1674 def locate(ui, repo, *pats, **opts):
1675 1675 """locate files matching specific patterns
1676 1676
1677 1677 Print all files under Mercurial control whose names match the
1678 1678 given patterns.
1679 1679
1680 1680 This command searches the entire repository by default. To search
1681 1681 just the current directory and its subdirectories, use
1682 1682 "--include .".
1683 1683
1684 1684 If no patterns are given to match, this command prints all file
1685 1685 names.
1686 1686
1687 1687 If you want to feed the output of this command into the "xargs"
1688 1688 command, use the "-0" option to both this command and "xargs".
1689 1689 This will avoid the problem of "xargs" treating single filenames
1690 1690 that contain white space as multiple filenames.
1691 1691 """
1692 1692 end = opts['print0'] and '\0' or '\n'
1693 1693 rev = opts['rev']
1694 1694 if rev:
1695 1695 node = repo.lookup(rev)
1696 1696 else:
1697 1697 node = None
1698 1698
1699 1699 ret = 1
1700 1700 m = cmdutil.match(repo, pats, opts, default='relglob')
1701 1701 m.bad = lambda x,y: False
1702 for src, abs in cmdutil.walk(repo, m, node):
1702 for src, abs in repo.walk(m, node):
1703 1703 if not node and abs not in repo.dirstate:
1704 1704 continue
1705 1705 if opts['fullpath']:
1706 1706 ui.write(os.path.join(repo.root, abs), end)
1707 1707 else:
1708 1708 ui.write(((pats and m.rel(abs)) or abs), end)
1709 1709 ret = 0
1710 1710
1711 1711 return ret
1712 1712
1713 1713 def log(ui, repo, *pats, **opts):
1714 1714 """show revision history of entire repository or files
1715 1715
1716 1716 Print the revision history of the specified files or the entire
1717 1717 project.
1718 1718
1719 1719 File history is shown without following rename or copy history of
1720 1720 files. Use -f/--follow with a file name to follow history across
1721 1721 renames and copies. --follow without a file name will only show
1722 1722 ancestors or descendants of the starting revision. --follow-first
1723 1723 only follows the first parent of merge revisions.
1724 1724
1725 1725 If no revision range is specified, the default is tip:0 unless
1726 1726 --follow is set, in which case the working directory parent is
1727 1727 used as the starting revision.
1728 1728
1729 1729 See 'hg help dates' for a list of formats valid for -d/--date.
1730 1730
1731 1731 By default this command outputs: changeset id and hash, tags,
1732 1732 non-trivial parents, user, date and time, and a summary for each
1733 1733 commit. When the -v/--verbose switch is used, the list of changed
1734 1734 files and full commit message is shown.
1735 1735
1736 1736 NOTE: log -p may generate unexpected diff output for merge
1737 1737 changesets, as it will compare the merge changeset against its
1738 1738 first parent only. Also, the files: list will only reflect files
1739 1739 that are different from BOTH parents.
1740 1740
1741 1741 """
1742 1742
1743 1743 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1744 1744 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1745 1745
1746 1746 limit = cmdutil.loglimit(opts)
1747 1747 count = 0
1748 1748
1749 1749 if opts['copies'] and opts['rev']:
1750 1750 endrev = max(cmdutil.revrange(repo, opts['rev'])) + 1
1751 1751 else:
1752 1752 endrev = repo.changelog.count()
1753 1753 rcache = {}
1754 1754 ncache = {}
1755 1755 def getrenamed(fn, rev):
1756 1756 '''looks up all renames for a file (up to endrev) the first
1757 1757 time the file is given. It indexes on the changerev and only
1758 1758 parses the manifest if linkrev != changerev.
1759 1759 Returns rename info for fn at changerev rev.'''
1760 1760 if fn not in rcache:
1761 1761 rcache[fn] = {}
1762 1762 ncache[fn] = {}
1763 1763 fl = repo.file(fn)
1764 1764 for i in xrange(fl.count()):
1765 1765 node = fl.node(i)
1766 1766 lr = fl.linkrev(node)
1767 1767 renamed = fl.renamed(node)
1768 1768 rcache[fn][lr] = renamed
1769 1769 if renamed:
1770 1770 ncache[fn][node] = renamed
1771 1771 if lr >= endrev:
1772 1772 break
1773 1773 if rev in rcache[fn]:
1774 1774 return rcache[fn][rev]
1775 1775
1776 1776 # If linkrev != rev (i.e. rev not found in rcache) fallback to
1777 1777 # filectx logic.
1778 1778
1779 1779 try:
1780 1780 return repo.changectx(rev).filectx(fn).renamed()
1781 1781 except revlog.LookupError:
1782 1782 pass
1783 1783 return None
1784 1784
1785 1785 df = False
1786 1786 if opts["date"]:
1787 1787 df = util.matchdate(opts["date"])
1788 1788
1789 1789 only_branches = opts['only_branch']
1790 1790
1791 1791 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1792 1792 for st, rev, fns in changeiter:
1793 1793 if st == 'add':
1794 1794 changenode = repo.changelog.node(rev)
1795 1795 parents = [p for p in repo.changelog.parentrevs(rev)
1796 1796 if p != nullrev]
1797 1797 if opts['no_merges'] and len(parents) == 2:
1798 1798 continue
1799 1799 if opts['only_merges'] and len(parents) != 2:
1800 1800 continue
1801 1801
1802 1802 if only_branches:
1803 1803 revbranch = get(rev)[5]['branch']
1804 1804 if revbranch not in only_branches:
1805 1805 continue
1806 1806
1807 1807 if df:
1808 1808 changes = get(rev)
1809 1809 if not df(changes[2][0]):
1810 1810 continue
1811 1811
1812 1812 if opts['keyword']:
1813 1813 changes = get(rev)
1814 1814 miss = 0
1815 1815 for k in [kw.lower() for kw in opts['keyword']]:
1816 1816 if not (k in changes[1].lower() or
1817 1817 k in changes[4].lower() or
1818 1818 k in " ".join(changes[3]).lower()):
1819 1819 miss = 1
1820 1820 break
1821 1821 if miss:
1822 1822 continue
1823 1823
1824 1824 copies = []
1825 1825 if opts.get('copies') and rev:
1826 1826 for fn in get(rev)[3]:
1827 1827 rename = getrenamed(fn, rev)
1828 1828 if rename:
1829 1829 copies.append((fn, rename[0]))
1830 1830 displayer.show(rev, changenode, copies=copies)
1831 1831 elif st == 'iter':
1832 1832 if count == limit: break
1833 1833 if displayer.flush(rev):
1834 1834 count += 1
1835 1835
1836 1836 def manifest(ui, repo, node=None, rev=None):
1837 1837 """output the current or given revision of the project manifest
1838 1838
1839 1839 Print a list of version controlled files for the given revision.
1840 1840 If no revision is given, the parent of the working directory is used,
1841 1841 or tip if no revision is checked out.
1842 1842
1843 1843 The manifest is the list of files being version controlled. If no revision
1844 1844 is given then the first parent of the working directory is used.
1845 1845
1846 1846 With -v flag, print file permissions, symlink and executable bits. With
1847 1847 --debug flag, print file revision hashes.
1848 1848 """
1849 1849
1850 1850 if rev and node:
1851 1851 raise util.Abort(_("please specify just one revision"))
1852 1852
1853 1853 if not node:
1854 1854 node = rev
1855 1855
1856 1856 m = repo.changectx(node).manifest()
1857 1857 files = m.keys()
1858 1858 files.sort()
1859 1859
1860 1860 for f in files:
1861 1861 if ui.debugflag:
1862 1862 ui.write("%40s " % hex(m[f]))
1863 1863 if ui.verbose:
1864 1864 type = m.execf(f) and "*" or m.linkf(f) and "@" or " "
1865 1865 perm = m.execf(f) and "755" or "644"
1866 1866 ui.write("%3s %1s " % (perm, type))
1867 1867 ui.write("%s\n" % f)
1868 1868
1869 1869 def merge(ui, repo, node=None, force=None, rev=None):
1870 1870 """merge working directory with another revision
1871 1871
1872 1872 Merge the contents of the current working directory and the
1873 1873 requested revision. Files that changed between either parent are
1874 1874 marked as changed for the next commit and a commit must be
1875 1875 performed before any further updates are allowed.
1876 1876
1877 1877 If no revision is specified, the working directory's parent is a
1878 1878 head revision, and the repository contains exactly one other head,
1879 1879 the other head is merged with by default. Otherwise, an explicit
1880 1880 revision to merge with must be provided.
1881 1881 """
1882 1882
1883 1883 if rev and node:
1884 1884 raise util.Abort(_("please specify just one revision"))
1885 1885 if not node:
1886 1886 node = rev
1887 1887
1888 1888 if not node:
1889 1889 heads = repo.heads()
1890 1890 if len(heads) > 2:
1891 1891 raise util.Abort(_('repo has %d heads - '
1892 1892 'please merge with an explicit rev') %
1893 1893 len(heads))
1894 1894 parent = repo.dirstate.parents()[0]
1895 1895 if len(heads) == 1:
1896 1896 msg = _('there is nothing to merge')
1897 1897 if parent != repo.lookup(repo.workingctx().branch()):
1898 1898 msg = _('%s - use "hg update" instead') % msg
1899 1899 raise util.Abort(msg)
1900 1900
1901 1901 if parent not in heads:
1902 1902 raise util.Abort(_('working dir not at a head rev - '
1903 1903 'use "hg update" or merge with an explicit rev'))
1904 1904 node = parent == heads[0] and heads[-1] or heads[0]
1905 1905 return hg.merge(repo, node, force=force)
1906 1906
1907 1907 def outgoing(ui, repo, dest=None, **opts):
1908 1908 """show changesets not found in destination
1909 1909
1910 1910 Show changesets not found in the specified destination repository or
1911 1911 the default push location. These are the changesets that would be pushed
1912 1912 if a push was requested.
1913 1913
1914 1914 See pull for valid destination format details.
1915 1915 """
1916 1916 limit = cmdutil.loglimit(opts)
1917 1917 dest, revs, checkout = hg.parseurl(
1918 1918 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
1919 1919 cmdutil.setremoteconfig(ui, opts)
1920 1920 if revs:
1921 1921 revs = [repo.lookup(rev) for rev in revs]
1922 1922
1923 1923 other = hg.repository(ui, dest)
1924 1924 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
1925 1925 o = repo.findoutgoing(other, force=opts['force'])
1926 1926 if not o:
1927 1927 ui.status(_("no changes found\n"))
1928 1928 return 1
1929 1929 o = repo.changelog.nodesbetween(o, revs)[0]
1930 1930 if opts['newest_first']:
1931 1931 o.reverse()
1932 1932 displayer = cmdutil.show_changeset(ui, repo, opts)
1933 1933 count = 0
1934 1934 for n in o:
1935 1935 if count >= limit:
1936 1936 break
1937 1937 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1938 1938 if opts['no_merges'] and len(parents) == 2:
1939 1939 continue
1940 1940 count += 1
1941 1941 displayer.show(changenode=n)
1942 1942
1943 1943 def parents(ui, repo, file_=None, **opts):
1944 1944 """show the parents of the working dir or revision
1945 1945
1946 1946 Print the working directory's parent revisions. If a
1947 1947 revision is given via --rev, the parent of that revision
1948 1948 will be printed. If a file argument is given, revision in
1949 1949 which the file was last changed (before the working directory
1950 1950 revision or the argument to --rev if given) is printed.
1951 1951 """
1952 1952 rev = opts.get('rev')
1953 1953 if rev:
1954 1954 ctx = repo.changectx(rev)
1955 1955 else:
1956 1956 ctx = repo.workingctx()
1957 1957
1958 1958 if file_:
1959 1959 m = cmdutil.match(repo, (file_,), opts)
1960 1960 if m.anypats() or len(m.files()) != 1:
1961 1961 raise util.Abort(_('can only specify an explicit file name'))
1962 1962 file_ = m.files()[0]
1963 1963 filenodes = []
1964 1964 for cp in ctx.parents():
1965 1965 if not cp:
1966 1966 continue
1967 1967 try:
1968 1968 filenodes.append(cp.filenode(file_))
1969 1969 except revlog.LookupError:
1970 1970 pass
1971 1971 if not filenodes:
1972 1972 raise util.Abort(_("'%s' not found in manifest!") % file_)
1973 1973 fl = repo.file(file_)
1974 1974 p = [repo.lookup(fl.linkrev(fn)) for fn in filenodes]
1975 1975 else:
1976 1976 p = [cp.node() for cp in ctx.parents()]
1977 1977
1978 1978 displayer = cmdutil.show_changeset(ui, repo, opts)
1979 1979 for n in p:
1980 1980 if n != nullid:
1981 1981 displayer.show(changenode=n)
1982 1982
1983 1983 def paths(ui, repo, search=None):
1984 1984 """show definition of symbolic path names
1985 1985
1986 1986 Show definition of symbolic path name NAME. If no name is given, show
1987 1987 definition of available names.
1988 1988
1989 1989 Path names are defined in the [paths] section of /etc/mercurial/hgrc
1990 1990 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
1991 1991 """
1992 1992 if search:
1993 1993 for name, path in ui.configitems("paths"):
1994 1994 if name == search:
1995 1995 ui.write("%s\n" % util.hidepassword(path))
1996 1996 return
1997 1997 ui.warn(_("not found!\n"))
1998 1998 return 1
1999 1999 else:
2000 2000 for name, path in ui.configitems("paths"):
2001 2001 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
2002 2002
2003 2003 def postincoming(ui, repo, modheads, optupdate, checkout):
2004 2004 if modheads == 0:
2005 2005 return
2006 2006 if optupdate:
2007 2007 if modheads <= 1 or checkout:
2008 2008 return hg.update(repo, checkout)
2009 2009 else:
2010 2010 ui.status(_("not updating, since new heads added\n"))
2011 2011 if modheads > 1:
2012 2012 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2013 2013 else:
2014 2014 ui.status(_("(run 'hg update' to get a working copy)\n"))
2015 2015
2016 2016 def pull(ui, repo, source="default", **opts):
2017 2017 """pull changes from the specified source
2018 2018
2019 2019 Pull changes from a remote repository to a local one.
2020 2020
2021 2021 This finds all changes from the repository at the specified path
2022 2022 or URL and adds them to the local repository. By default, this
2023 2023 does not update the copy of the project in the working directory.
2024 2024
2025 2025 Valid URLs are of the form:
2026 2026
2027 2027 local/filesystem/path (or file://local/filesystem/path)
2028 2028 http://[user@]host[:port]/[path]
2029 2029 https://[user@]host[:port]/[path]
2030 2030 ssh://[user@]host[:port]/[path]
2031 2031 static-http://host[:port]/[path]
2032 2032
2033 2033 Paths in the local filesystem can either point to Mercurial
2034 2034 repositories or to bundle files (as created by 'hg bundle' or
2035 2035 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
2036 2036 allows access to a Mercurial repository where you simply use a web
2037 2037 server to publish the .hg directory as static content.
2038 2038
2039 2039 An optional identifier after # indicates a particular branch, tag,
2040 2040 or changeset to pull.
2041 2041
2042 2042 Some notes about using SSH with Mercurial:
2043 2043 - SSH requires an accessible shell account on the destination machine
2044 2044 and a copy of hg in the remote path or specified with as remotecmd.
2045 2045 - path is relative to the remote user's home directory by default.
2046 2046 Use an extra slash at the start of a path to specify an absolute path:
2047 2047 ssh://example.com//tmp/repository
2048 2048 - Mercurial doesn't use its own compression via SSH; the right thing
2049 2049 to do is to configure it in your ~/.ssh/config, e.g.:
2050 2050 Host *.mylocalnetwork.example.com
2051 2051 Compression no
2052 2052 Host *
2053 2053 Compression yes
2054 2054 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2055 2055 with the --ssh command line option.
2056 2056 """
2057 2057 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
2058 2058 cmdutil.setremoteconfig(ui, opts)
2059 2059
2060 2060 other = hg.repository(ui, source)
2061 2061 ui.status(_('pulling from %s\n') % util.hidepassword(source))
2062 2062 if revs:
2063 2063 try:
2064 2064 revs = [other.lookup(rev) for rev in revs]
2065 2065 except NoCapability:
2066 2066 error = _("Other repository doesn't support revision lookup, "
2067 2067 "so a rev cannot be specified.")
2068 2068 raise util.Abort(error)
2069 2069
2070 2070 modheads = repo.pull(other, heads=revs, force=opts['force'])
2071 2071 return postincoming(ui, repo, modheads, opts['update'], checkout)
2072 2072
2073 2073 def push(ui, repo, dest=None, **opts):
2074 2074 """push changes to the specified destination
2075 2075
2076 2076 Push changes from the local repository to the given destination.
2077 2077
2078 2078 This is the symmetrical operation for pull. It helps to move
2079 2079 changes from the current repository to a different one. If the
2080 2080 destination is local this is identical to a pull in that directory
2081 2081 from the current one.
2082 2082
2083 2083 By default, push will refuse to run if it detects the result would
2084 2084 increase the number of remote heads. This generally indicates the
2085 2085 the client has forgotten to sync and merge before pushing.
2086 2086
2087 2087 Valid URLs are of the form:
2088 2088
2089 2089 local/filesystem/path (or file://local/filesystem/path)
2090 2090 ssh://[user@]host[:port]/[path]
2091 2091 http://[user@]host[:port]/[path]
2092 2092 https://[user@]host[:port]/[path]
2093 2093
2094 2094 An optional identifier after # indicates a particular branch, tag,
2095 2095 or changeset to push.
2096 2096
2097 2097 Look at the help text for the pull command for important details
2098 2098 about ssh:// URLs.
2099 2099
2100 2100 Pushing to http:// and https:// URLs is only possible, if this
2101 2101 feature is explicitly enabled on the remote Mercurial server.
2102 2102 """
2103 2103 dest, revs, checkout = hg.parseurl(
2104 2104 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
2105 2105 cmdutil.setremoteconfig(ui, opts)
2106 2106
2107 2107 other = hg.repository(ui, dest)
2108 2108 ui.status('pushing to %s\n' % util.hidepassword(dest))
2109 2109 if revs:
2110 2110 revs = [repo.lookup(rev) for rev in revs]
2111 2111 r = repo.push(other, opts['force'], revs=revs)
2112 2112 return r == 0
2113 2113
2114 2114 def rawcommit(ui, repo, *pats, **opts):
2115 2115 """raw commit interface (DEPRECATED)
2116 2116
2117 2117 (DEPRECATED)
2118 2118 Lowlevel commit, for use in helper scripts.
2119 2119
2120 2120 This command is not intended to be used by normal users, as it is
2121 2121 primarily useful for importing from other SCMs.
2122 2122
2123 2123 This command is now deprecated and will be removed in a future
2124 2124 release, please use debugsetparents and commit instead.
2125 2125 """
2126 2126
2127 2127 ui.warn(_("(the rawcommit command is deprecated)\n"))
2128 2128
2129 2129 message = cmdutil.logmessage(opts)
2130 2130
2131 2131 files = cmdutil.match(repo, pats, opts).files()
2132 2132 if opts['files']:
2133 2133 files += open(opts['files']).read().splitlines()
2134 2134
2135 2135 parents = [repo.lookup(p) for p in opts['parent']]
2136 2136
2137 2137 try:
2138 2138 repo.rawcommit(files, message, opts['user'], opts['date'], *parents)
2139 2139 except ValueError, inst:
2140 2140 raise util.Abort(str(inst))
2141 2141
2142 2142 def recover(ui, repo):
2143 2143 """roll back an interrupted transaction
2144 2144
2145 2145 Recover from an interrupted commit or pull.
2146 2146
2147 2147 This command tries to fix the repository status after an interrupted
2148 2148 operation. It should only be necessary when Mercurial suggests it.
2149 2149 """
2150 2150 if repo.recover():
2151 2151 return hg.verify(repo)
2152 2152 return 1
2153 2153
2154 2154 def remove(ui, repo, *pats, **opts):
2155 2155 """remove the specified files on the next commit
2156 2156
2157 2157 Schedule the indicated files for removal from the repository.
2158 2158
2159 2159 This only removes files from the current branch, not from the entire
2160 2160 project history. -A can be used to remove only files that have already
2161 2161 been deleted, -f can be used to force deletion, and -Af can be used
2162 2162 to remove files from the next revision without deleting them.
2163 2163
2164 2164 The following table details the behavior of remove for different file
2165 2165 states (columns) and option combinations (rows). The file states are
2166 2166 Added, Clean, Modified and Missing (as reported by hg status). The
2167 2167 actions are Warn, Remove (from branch) and Delete (from disk).
2168 2168
2169 2169 A C M !
2170 2170 none W RD W R
2171 2171 -f R RD RD R
2172 2172 -A W W W R
2173 2173 -Af R R R R
2174 2174
2175 2175 This command schedules the files to be removed at the next commit.
2176 2176 To undo a remove before that, see hg revert.
2177 2177 """
2178 2178
2179 2179 after, force = opts.get('after'), opts.get('force')
2180 2180 if not pats and not after:
2181 2181 raise util.Abort(_('no files specified'))
2182 2182
2183 2183 m = cmdutil.match(repo, pats, opts)
2184 2184 mardu = map(dict.fromkeys, repo.status(files=m.files(), match=m))[:5]
2185 2185 modified, added, removed, deleted, unknown = mardu
2186 2186
2187 2187 remove, forget = [], []
2188 for src, abs in cmdutil.walk(repo, m):
2188 for src, abs in repo.walk(m):
2189 2189
2190 2190 reason = None
2191 2191 if abs in removed or abs in unknown:
2192 2192 continue
2193 2193
2194 2194 # last column
2195 2195 elif abs in deleted:
2196 2196 remove.append(abs)
2197 2197
2198 2198 # rest of the third row
2199 2199 elif after and not force:
2200 2200 reason = _('still exists (use -f to force removal)')
2201 2201
2202 2202 # rest of the first column
2203 2203 elif abs in added:
2204 2204 if not force:
2205 2205 reason = _('has been marked for add (use -f to force removal)')
2206 2206 else:
2207 2207 forget.append(abs)
2208 2208
2209 2209 # rest of the third column
2210 2210 elif abs in modified:
2211 2211 if not force:
2212 2212 reason = _('is modified (use -f to force removal)')
2213 2213 else:
2214 2214 remove.append(abs)
2215 2215
2216 2216 # rest of the second column
2217 2217 elif not reason:
2218 2218 remove.append(abs)
2219 2219
2220 2220 if reason:
2221 2221 ui.warn(_('not removing %s: file %s\n') % (m.rel(abs), reason))
2222 2222 elif ui.verbose or not m.exact(abs):
2223 2223 ui.status(_('removing %s\n') % m.rel(abs))
2224 2224
2225 2225 repo.forget(forget)
2226 2226 repo.remove(remove, unlink=not after)
2227 2227
2228 2228 def rename(ui, repo, *pats, **opts):
2229 2229 """rename files; equivalent of copy + remove
2230 2230
2231 2231 Mark dest as copies of sources; mark sources for deletion. If
2232 2232 dest is a directory, copies are put in that directory. If dest is
2233 2233 a file, there can only be one source.
2234 2234
2235 2235 By default, this command copies the contents of files as they
2236 2236 stand in the working directory. If invoked with --after, the
2237 2237 operation is recorded, but no copying is performed.
2238 2238
2239 2239 This command takes effect in the next commit. To undo a rename
2240 2240 before that, see hg revert.
2241 2241 """
2242 2242 wlock = repo.wlock(False)
2243 2243 try:
2244 2244 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2245 2245 finally:
2246 2246 del wlock
2247 2247
2248 2248 def resolve(ui, repo, *pats, **opts):
2249 2249 """resolve file merges from a branch merge or update
2250 2250
2251 2251 This command will attempt to resolve unresolved merges from the
2252 2252 last update or merge command. This will use the local file
2253 2253 revision preserved at the last update or merge to cleanly retry
2254 2254 the file merge attempt. With no file or options specified, this
2255 2255 command will attempt to resolve all unresolved files.
2256 2256 """
2257 2257
2258 2258 if len([x for x in opts if opts[x]]) > 1:
2259 2259 raise util.Abort(_("too many options specified"))
2260 2260
2261 2261 ms = merge_.mergestate(repo)
2262 2262 mf = util.matcher(repo.root, "", pats, [], [])[1]
2263 2263
2264 2264 for f in ms:
2265 2265 if mf(f):
2266 2266 if opts.get("list"):
2267 2267 ui.write("%s %s\n" % (ms[f].upper(), f))
2268 2268 elif opts.get("mark"):
2269 2269 ms.mark(f, "r")
2270 2270 elif opts.get("unmark"):
2271 2271 ms.mark(f, "u")
2272 2272 else:
2273 2273 wctx = repo.workingctx()
2274 2274 mctx = wctx.parents()[-1]
2275 2275 ms.resolve(f, wctx, mctx)
2276 2276
2277 2277 def revert(ui, repo, *pats, **opts):
2278 2278 """restore individual files or dirs to an earlier state
2279 2279
2280 2280 (use update -r to check out earlier revisions, revert does not
2281 2281 change the working dir parents)
2282 2282
2283 2283 With no revision specified, revert the named files or directories
2284 2284 to the contents they had in the parent of the working directory.
2285 2285 This restores the contents of the affected files to an unmodified
2286 2286 state and unschedules adds, removes, copies, and renames. If the
2287 2287 working directory has two parents, you must explicitly specify the
2288 2288 revision to revert to.
2289 2289
2290 2290 Using the -r option, revert the given files or directories to their
2291 2291 contents as of a specific revision. This can be helpful to "roll
2292 2292 back" some or all of an earlier change.
2293 2293 See 'hg help dates' for a list of formats valid for -d/--date.
2294 2294
2295 2295 Revert modifies the working directory. It does not commit any
2296 2296 changes, or change the parent of the working directory. If you
2297 2297 revert to a revision other than the parent of the working
2298 2298 directory, the reverted files will thus appear modified
2299 2299 afterwards.
2300 2300
2301 2301 If a file has been deleted, it is restored. If the executable
2302 2302 mode of a file was changed, it is reset.
2303 2303
2304 2304 If names are given, all files matching the names are reverted.
2305 2305 If no arguments are given, no files are reverted.
2306 2306
2307 2307 Modified files are saved with a .orig suffix before reverting.
2308 2308 To disable these backups, use --no-backup.
2309 2309 """
2310 2310
2311 2311 if opts["date"]:
2312 2312 if opts["rev"]:
2313 2313 raise util.Abort(_("you can't specify a revision and a date"))
2314 2314 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2315 2315
2316 2316 if not pats and not opts['all']:
2317 2317 raise util.Abort(_('no files or directories specified; '
2318 2318 'use --all to revert the whole repo'))
2319 2319
2320 2320 parent, p2 = repo.dirstate.parents()
2321 2321 if not opts['rev'] and p2 != nullid:
2322 2322 raise util.Abort(_('uncommitted merge - please provide a '
2323 2323 'specific revision'))
2324 2324 ctx = repo.changectx(opts['rev'])
2325 2325 node = ctx.node()
2326 2326 mf = ctx.manifest()
2327 2327 if node == parent:
2328 2328 pmf = mf
2329 2329 else:
2330 2330 pmf = None
2331 2331
2332 2332 # need all matching names in dirstate and manifest of target rev,
2333 2333 # so have to walk both. do not print errors if files exist in one
2334 2334 # but not other.
2335 2335
2336 2336 names = {}
2337 2337
2338 2338 wlock = repo.wlock()
2339 2339 try:
2340 2340 # walk dirstate.
2341 2341 files = []
2342 2342
2343 2343 m = cmdutil.match(repo, pats, opts)
2344 2344 m.bad = lambda x,y: False
2345 for src, abs in cmdutil.walk(repo, m):
2345 for src, abs in repo.walk(m):
2346 2346 names[abs] = m.rel(abs), m.exact(abs)
2347 2347
2348 2348 # walk target manifest.
2349 2349
2350 2350 def badfn(path, msg):
2351 2351 if path in names:
2352 2352 return False
2353 2353 path_ = path + '/'
2354 2354 for f in names:
2355 2355 if f.startswith(path_):
2356 2356 return False
2357 2357 repo.ui.warn("%s: %s\n" % (m.rel(path), msg))
2358 2358 return False
2359 2359
2360 2360 m = cmdutil.match(repo, pats, opts)
2361 2361 m.bad = badfn
2362 for src, abs in cmdutil.walk(repo, m, node=node):
2362 for src, abs in repo.walk(m, node=node):
2363 2363 if abs not in names:
2364 2364 names[abs] = m.rel(abs), m.exact(abs)
2365 2365
2366 2366 changes = repo.status(files=files, match=names.has_key)[:4]
2367 2367 modified, added, removed, deleted = map(dict.fromkeys, changes)
2368 2368
2369 2369 # if f is a rename, also revert the source
2370 2370 cwd = repo.getcwd()
2371 2371 for f in added:
2372 2372 src = repo.dirstate.copied(f)
2373 2373 if src and src not in names and repo.dirstate[src] == 'r':
2374 2374 removed[src] = None
2375 2375 names[src] = (repo.pathto(src, cwd), True)
2376 2376
2377 2377 def removeforget(abs):
2378 2378 if repo.dirstate[abs] == 'a':
2379 2379 return _('forgetting %s\n')
2380 2380 return _('removing %s\n')
2381 2381
2382 2382 revert = ([], _('reverting %s\n'))
2383 2383 add = ([], _('adding %s\n'))
2384 2384 remove = ([], removeforget)
2385 2385 undelete = ([], _('undeleting %s\n'))
2386 2386
2387 2387 disptable = (
2388 2388 # dispatch table:
2389 2389 # file state
2390 2390 # action if in target manifest
2391 2391 # action if not in target manifest
2392 2392 # make backup if in target manifest
2393 2393 # make backup if not in target manifest
2394 2394 (modified, revert, remove, True, True),
2395 2395 (added, revert, remove, True, False),
2396 2396 (removed, undelete, None, False, False),
2397 2397 (deleted, revert, remove, False, False),
2398 2398 )
2399 2399
2400 2400 entries = names.items()
2401 2401 entries.sort()
2402 2402
2403 2403 for abs, (rel, exact) in entries:
2404 2404 mfentry = mf.get(abs)
2405 2405 target = repo.wjoin(abs)
2406 2406 def handle(xlist, dobackup):
2407 2407 xlist[0].append(abs)
2408 2408 if dobackup and not opts['no_backup'] and util.lexists(target):
2409 2409 bakname = "%s.orig" % rel
2410 2410 ui.note(_('saving current version of %s as %s\n') %
2411 2411 (rel, bakname))
2412 2412 if not opts.get('dry_run'):
2413 2413 util.copyfile(target, bakname)
2414 2414 if ui.verbose or not exact:
2415 2415 msg = xlist[1]
2416 2416 if not isinstance(msg, basestring):
2417 2417 msg = msg(abs)
2418 2418 ui.status(msg % rel)
2419 2419 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2420 2420 if abs not in table: continue
2421 2421 # file has changed in dirstate
2422 2422 if mfentry:
2423 2423 handle(hitlist, backuphit)
2424 2424 elif misslist is not None:
2425 2425 handle(misslist, backupmiss)
2426 2426 break
2427 2427 else:
2428 2428 if abs not in repo.dirstate:
2429 2429 if mfentry:
2430 2430 handle(add, True)
2431 2431 elif exact:
2432 2432 ui.warn(_('file not managed: %s\n') % rel)
2433 2433 continue
2434 2434 # file has not changed in dirstate
2435 2435 if node == parent:
2436 2436 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2437 2437 continue
2438 2438 if pmf is None:
2439 2439 # only need parent manifest in this unlikely case,
2440 2440 # so do not read by default
2441 2441 pmf = repo.changectx(parent).manifest()
2442 2442 if abs in pmf:
2443 2443 if mfentry:
2444 2444 # if version of file is same in parent and target
2445 2445 # manifests, do nothing
2446 2446 if (pmf[abs] != mfentry or
2447 2447 pmf.flags(abs) != mf.flags(abs)):
2448 2448 handle(revert, False)
2449 2449 else:
2450 2450 handle(remove, False)
2451 2451
2452 2452 if not opts.get('dry_run'):
2453 2453 def checkout(f):
2454 2454 fc = ctx[f]
2455 2455 repo.wwrite(f, fc.data(), fc.fileflags())
2456 2456
2457 2457 audit_path = util.path_auditor(repo.root)
2458 2458 for f in remove[0]:
2459 2459 if repo.dirstate[f] == 'a':
2460 2460 repo.dirstate.forget(f)
2461 2461 continue
2462 2462 audit_path(f)
2463 2463 try:
2464 2464 util.unlink(repo.wjoin(f))
2465 2465 except OSError:
2466 2466 pass
2467 2467 repo.dirstate.remove(f)
2468 2468
2469 2469 normal = None
2470 2470 if node == parent:
2471 2471 # We're reverting to our parent. If possible, we'd like status
2472 2472 # to report the file as clean. We have to use normallookup for
2473 2473 # merges to avoid losing information about merged/dirty files.
2474 2474 if p2 != nullid:
2475 2475 normal = repo.dirstate.normallookup
2476 2476 else:
2477 2477 normal = repo.dirstate.normal
2478 2478 for f in revert[0]:
2479 2479 checkout(f)
2480 2480 if normal:
2481 2481 normal(f)
2482 2482
2483 2483 for f in add[0]:
2484 2484 checkout(f)
2485 2485 repo.dirstate.add(f)
2486 2486
2487 2487 normal = repo.dirstate.normallookup
2488 2488 if node == parent and p2 == nullid:
2489 2489 normal = repo.dirstate.normal
2490 2490 for f in undelete[0]:
2491 2491 checkout(f)
2492 2492 normal(f)
2493 2493
2494 2494 finally:
2495 2495 del wlock
2496 2496
2497 2497 def rollback(ui, repo):
2498 2498 """roll back the last transaction
2499 2499
2500 2500 This command should be used with care. There is only one level of
2501 2501 rollback, and there is no way to undo a rollback. It will also
2502 2502 restore the dirstate at the time of the last transaction, losing
2503 2503 any dirstate changes since that time.
2504 2504
2505 2505 Transactions are used to encapsulate the effects of all commands
2506 2506 that create new changesets or propagate existing changesets into a
2507 2507 repository. For example, the following commands are transactional,
2508 2508 and their effects can be rolled back:
2509 2509
2510 2510 commit
2511 2511 import
2512 2512 pull
2513 2513 push (with this repository as destination)
2514 2514 unbundle
2515 2515
2516 2516 This command is not intended for use on public repositories. Once
2517 2517 changes are visible for pull by other users, rolling a transaction
2518 2518 back locally is ineffective (someone else may already have pulled
2519 2519 the changes). Furthermore, a race is possible with readers of the
2520 2520 repository; for example an in-progress pull from the repository
2521 2521 may fail if a rollback is performed.
2522 2522 """
2523 2523 repo.rollback()
2524 2524
2525 2525 def root(ui, repo):
2526 2526 """print the root (top) of the current working dir
2527 2527
2528 2528 Print the root directory of the current repository.
2529 2529 """
2530 2530 ui.write(repo.root + "\n")
2531 2531
2532 2532 def serve(ui, repo, **opts):
2533 2533 """export the repository via HTTP
2534 2534
2535 2535 Start a local HTTP repository browser and pull server.
2536 2536
2537 2537 By default, the server logs accesses to stdout and errors to
2538 2538 stderr. Use the "-A" and "-E" options to log to files.
2539 2539 """
2540 2540
2541 2541 if opts["stdio"]:
2542 2542 if repo is None:
2543 2543 raise RepoError(_("There is no Mercurial repository here"
2544 2544 " (.hg not found)"))
2545 2545 s = sshserver.sshserver(ui, repo)
2546 2546 s.serve_forever()
2547 2547
2548 2548 parentui = ui.parentui or ui
2549 2549 optlist = ("name templates style address port prefix ipv6"
2550 2550 " accesslog errorlog webdir_conf certificate")
2551 2551 for o in optlist.split():
2552 2552 if opts[o]:
2553 2553 parentui.setconfig("web", o, str(opts[o]))
2554 2554 if (repo is not None) and (repo.ui != parentui):
2555 2555 repo.ui.setconfig("web", o, str(opts[o]))
2556 2556
2557 2557 if repo is None and not ui.config("web", "webdir_conf"):
2558 2558 raise RepoError(_("There is no Mercurial repository here"
2559 2559 " (.hg not found)"))
2560 2560
2561 2561 class service:
2562 2562 def init(self):
2563 2563 util.set_signal_handler()
2564 2564 self.httpd = hgweb.server.create_server(parentui, repo)
2565 2565
2566 2566 if not ui.verbose: return
2567 2567
2568 2568 if self.httpd.prefix:
2569 2569 prefix = self.httpd.prefix.strip('/') + '/'
2570 2570 else:
2571 2571 prefix = ''
2572 2572
2573 2573 port = ':%d' % self.httpd.port
2574 2574 if port == ':80':
2575 2575 port = ''
2576 2576
2577 2577 bindaddr = self.httpd.addr
2578 2578 if bindaddr == '0.0.0.0':
2579 2579 bindaddr = '*'
2580 2580 elif ':' in bindaddr: # IPv6
2581 2581 bindaddr = '[%s]' % bindaddr
2582 2582
2583 2583 fqaddr = self.httpd.fqaddr
2584 2584 if ':' in fqaddr:
2585 2585 fqaddr = '[%s]' % fqaddr
2586 2586 ui.status(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
2587 2587 (fqaddr, port, prefix, bindaddr, self.httpd.port))
2588 2588
2589 2589 def run(self):
2590 2590 self.httpd.serve_forever()
2591 2591
2592 2592 service = service()
2593 2593
2594 2594 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2595 2595
2596 2596 def status(ui, repo, *pats, **opts):
2597 2597 """show changed files in the working directory
2598 2598
2599 2599 Show status of files in the repository. If names are given, only
2600 2600 files that match are shown. Files that are clean or ignored or
2601 2601 source of a copy/move operation, are not listed unless -c (clean),
2602 2602 -i (ignored), -C (copies) or -A is given. Unless options described
2603 2603 with "show only ..." are given, the options -mardu are used.
2604 2604
2605 2605 Option -q/--quiet hides untracked (unknown and ignored) files
2606 2606 unless explicitly requested with -u/--unknown or -i/-ignored.
2607 2607
2608 2608 NOTE: status may appear to disagree with diff if permissions have
2609 2609 changed or a merge has occurred. The standard diff format does not
2610 2610 report permission changes and diff only reports changes relative
2611 2611 to one merge parent.
2612 2612
2613 2613 If one revision is given, it is used as the base revision.
2614 2614 If two revisions are given, the difference between them is shown.
2615 2615
2616 2616 The codes used to show the status of files are:
2617 2617 M = modified
2618 2618 A = added
2619 2619 R = removed
2620 2620 C = clean
2621 2621 ! = deleted, but still tracked
2622 2622 ? = not tracked
2623 2623 I = ignored
2624 2624 = the previous added file was copied from here
2625 2625 """
2626 2626
2627 2627 all = opts['all']
2628 2628 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2629 2629
2630 2630 matcher = cmdutil.match(repo, pats, opts)
2631 2631 cwd = (pats and repo.getcwd()) or ''
2632 2632 modified, added, removed, deleted, unknown, ignored, clean = [
2633 2633 n for n in repo.status(node1, node2, matcher.files(), matcher,
2634 2634 list_ignored=opts['ignored']
2635 2635 or all and not ui.quiet,
2636 2636 list_clean=opts['clean'] or all,
2637 2637 list_unknown=opts['unknown']
2638 2638 or not (ui.quiet or
2639 2639 opts['modified'] or
2640 2640 opts['added'] or
2641 2641 opts['removed'] or
2642 2642 opts['deleted'] or
2643 2643 opts['ignored']))]
2644 2644
2645 2645 changetypes = (('modified', 'M', modified),
2646 2646 ('added', 'A', added),
2647 2647 ('removed', 'R', removed),
2648 2648 ('deleted', '!', deleted),
2649 2649 ('unknown', '?', unknown),
2650 2650 ('ignored', 'I', ignored))
2651 2651
2652 2652 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2653 2653
2654 2654 copy = {}
2655 2655 showcopy = {}
2656 2656 if ((all or opts.get('copies')) and not opts.get('no_status')):
2657 2657 if opts.get('rev') == []:
2658 2658 # fast path, more correct with merge parents
2659 2659 showcopy = copy = repo.dirstate.copies().copy()
2660 2660 else:
2661 2661 ctxn = repo.changectx(nullid)
2662 2662 ctx1 = repo.changectx(node1)
2663 2663 ctx2 = repo.changectx(node2)
2664 2664 if node2 is None:
2665 2665 ctx2 = repo.workingctx()
2666 2666 copy, diverge = copies.copies(repo, ctx1, ctx2, ctxn)
2667 2667 for k, v in copy.items():
2668 2668 copy[v] = k
2669 2669
2670 2670 end = opts['print0'] and '\0' or '\n'
2671 2671
2672 2672 for opt, char, changes in ([ct for ct in explicit_changetypes
2673 2673 if all or opts[ct[0]]]
2674 2674 or changetypes):
2675 2675
2676 2676 if opts['no_status']:
2677 2677 format = "%%s%s" % end
2678 2678 else:
2679 2679 format = "%s %%s%s" % (char, end)
2680 2680
2681 2681 for f in changes:
2682 2682 ui.write(format % repo.pathto(f, cwd))
2683 2683 if f in copy and (f in added or f in showcopy):
2684 2684 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end))
2685 2685
2686 2686 def tag(ui, repo, name1, *names, **opts):
2687 2687 """add one or more tags for the current or given revision
2688 2688
2689 2689 Name a particular revision using <name>.
2690 2690
2691 2691 Tags are used to name particular revisions of the repository and are
2692 2692 very useful to compare different revisions, to go back to significant
2693 2693 earlier versions or to mark branch points as releases, etc.
2694 2694
2695 2695 If no revision is given, the parent of the working directory is used,
2696 2696 or tip if no revision is checked out.
2697 2697
2698 2698 To facilitate version control, distribution, and merging of tags,
2699 2699 they are stored as a file named ".hgtags" which is managed
2700 2700 similarly to other project files and can be hand-edited if
2701 2701 necessary. The file '.hg/localtags' is used for local tags (not
2702 2702 shared among repositories).
2703 2703
2704 2704 See 'hg help dates' for a list of formats valid for -d/--date.
2705 2705 """
2706 2706
2707 2707 rev_ = None
2708 2708 names = (name1,) + names
2709 2709 if len(names) != len(dict.fromkeys(names)):
2710 2710 raise util.Abort(_('tag names must be unique'))
2711 2711 for n in names:
2712 2712 if n in ['tip', '.', 'null']:
2713 2713 raise util.Abort(_('the name \'%s\' is reserved') % n)
2714 2714 if opts['rev'] and opts['remove']:
2715 2715 raise util.Abort(_("--rev and --remove are incompatible"))
2716 2716 if opts['rev']:
2717 2717 rev_ = opts['rev']
2718 2718 message = opts['message']
2719 2719 if opts['remove']:
2720 2720 expectedtype = opts['local'] and 'local' or 'global'
2721 2721 for n in names:
2722 2722 if not repo.tagtype(n):
2723 2723 raise util.Abort(_('tag \'%s\' does not exist') % n)
2724 2724 if repo.tagtype(n) != expectedtype:
2725 2725 raise util.Abort(_('tag \'%s\' is not a %s tag') %
2726 2726 (n, expectedtype))
2727 2727 rev_ = nullid
2728 2728 if not message:
2729 2729 message = _('Removed tag %s') % ', '.join(names)
2730 2730 elif not opts['force']:
2731 2731 for n in names:
2732 2732 if n in repo.tags():
2733 2733 raise util.Abort(_('tag \'%s\' already exists '
2734 2734 '(use -f to force)') % n)
2735 2735 if not rev_ and repo.dirstate.parents()[1] != nullid:
2736 2736 raise util.Abort(_('uncommitted merge - please provide a '
2737 2737 'specific revision'))
2738 2738 r = repo.changectx(rev_).node()
2739 2739
2740 2740 if not message:
2741 2741 message = (_('Added tag %s for changeset %s') %
2742 2742 (', '.join(names), short(r)))
2743 2743
2744 2744 date = opts.get('date')
2745 2745 if date:
2746 2746 date = util.parsedate(date)
2747 2747
2748 2748 repo.tag(names, r, message, opts['local'], opts['user'], date)
2749 2749
2750 2750 def tags(ui, repo):
2751 2751 """list repository tags
2752 2752
2753 2753 List the repository tags.
2754 2754
2755 2755 This lists both regular and local tags. When the -v/--verbose switch
2756 2756 is used, a third column "local" is printed for local tags.
2757 2757 """
2758 2758
2759 2759 l = repo.tagslist()
2760 2760 l.reverse()
2761 2761 hexfunc = ui.debugflag and hex or short
2762 2762 tagtype = ""
2763 2763
2764 2764 for t, n in l:
2765 2765 if ui.quiet:
2766 2766 ui.write("%s\n" % t)
2767 2767 continue
2768 2768
2769 2769 try:
2770 2770 hn = hexfunc(n)
2771 2771 r = "%5d:%s" % (repo.changelog.rev(n), hn)
2772 2772 except revlog.LookupError:
2773 2773 r = " ?:%s" % hn
2774 2774 else:
2775 2775 spaces = " " * (30 - util.locallen(t))
2776 2776 if ui.verbose:
2777 2777 if repo.tagtype(t) == 'local':
2778 2778 tagtype = " local"
2779 2779 else:
2780 2780 tagtype = ""
2781 2781 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
2782 2782
2783 2783 def tip(ui, repo, **opts):
2784 2784 """show the tip revision
2785 2785
2786 2786 The tip revision (usually just called the tip) is the most
2787 2787 recently added changeset in the repository, the most recently
2788 2788 changed head.
2789 2789
2790 2790 If you have just made a commit, that commit will be the tip. If
2791 2791 you have just pulled changes from another repository, the tip of
2792 2792 that repository becomes the current tip. The "tip" tag is special
2793 2793 and cannot be renamed or assigned to a different changeset.
2794 2794 """
2795 2795 cmdutil.show_changeset(ui, repo, opts).show(nullrev+repo.changelog.count())
2796 2796
2797 2797 def unbundle(ui, repo, fname1, *fnames, **opts):
2798 2798 """apply one or more changegroup files
2799 2799
2800 2800 Apply one or more compressed changegroup files generated by the
2801 2801 bundle command.
2802 2802 """
2803 2803 fnames = (fname1,) + fnames
2804 2804
2805 2805 lock = None
2806 2806 try:
2807 2807 lock = repo.lock()
2808 2808 for fname in fnames:
2809 2809 if os.path.exists(fname):
2810 2810 f = open(fname, "rb")
2811 2811 else:
2812 2812 f = urllib.urlopen(fname)
2813 2813 gen = changegroup.readbundle(f, fname)
2814 2814 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2815 2815 finally:
2816 2816 del lock
2817 2817
2818 2818 return postincoming(ui, repo, modheads, opts['update'], None)
2819 2819
2820 2820 def update(ui, repo, node=None, rev=None, clean=False, date=None):
2821 2821 """update working directory
2822 2822
2823 2823 Update the working directory to the specified revision, or the
2824 2824 tip of the current branch if none is specified.
2825 2825
2826 2826 If the requested revision is a descendant of the working
2827 2827 directory, any outstanding changes in the working directory will
2828 2828 be merged into the result. If it is not directly descended but is
2829 2829 on the same named branch, update aborts with a suggestion to use
2830 2830 merge or update -C instead.
2831 2831
2832 2832 If the requested revision is on a different named branch and the
2833 2833 working directory is clean, update quietly switches branches.
2834 2834
2835 2835 See 'hg help dates' for a list of formats valid for --date.
2836 2836 """
2837 2837 if rev and node:
2838 2838 raise util.Abort(_("please specify just one revision"))
2839 2839
2840 2840 if not rev:
2841 2841 rev = node
2842 2842
2843 2843 if date:
2844 2844 if rev:
2845 2845 raise util.Abort(_("you can't specify a revision and a date"))
2846 2846 rev = cmdutil.finddate(ui, repo, date)
2847 2847
2848 2848 if clean:
2849 2849 return hg.clean(repo, rev)
2850 2850 else:
2851 2851 return hg.update(repo, rev)
2852 2852
2853 2853 def verify(ui, repo):
2854 2854 """verify the integrity of the repository
2855 2855
2856 2856 Verify the integrity of the current repository.
2857 2857
2858 2858 This will perform an extensive check of the repository's
2859 2859 integrity, validating the hashes and checksums of each entry in
2860 2860 the changelog, manifest, and tracked files, as well as the
2861 2861 integrity of their crosslinks and indices.
2862 2862 """
2863 2863 return hg.verify(repo)
2864 2864
2865 2865 def version_(ui):
2866 2866 """output version and copyright information"""
2867 2867 ui.write(_("Mercurial Distributed SCM (version %s)\n")
2868 2868 % version.get_version())
2869 2869 ui.status(_(
2870 2870 "\nCopyright (C) 2005-2008 Matt Mackall <mpm@selenic.com> and others\n"
2871 2871 "This is free software; see the source for copying conditions. "
2872 2872 "There is NO\nwarranty; "
2873 2873 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
2874 2874 ))
2875 2875
2876 2876 # Command options and aliases are listed here, alphabetically
2877 2877
2878 2878 globalopts = [
2879 2879 ('R', 'repository', '',
2880 2880 _('repository root directory or symbolic path name')),
2881 2881 ('', 'cwd', '', _('change working directory')),
2882 2882 ('y', 'noninteractive', None,
2883 2883 _('do not prompt, assume \'yes\' for any required answers')),
2884 2884 ('q', 'quiet', None, _('suppress output')),
2885 2885 ('v', 'verbose', None, _('enable additional output')),
2886 2886 ('', 'config', [], _('set/override config option')),
2887 2887 ('', 'debug', None, _('enable debugging output')),
2888 2888 ('', 'debugger', None, _('start debugger')),
2889 2889 ('', 'encoding', util._encoding, _('set the charset encoding')),
2890 2890 ('', 'encodingmode', util._encodingmode, _('set the charset encoding mode')),
2891 2891 ('', 'lsprof', None, _('print improved command execution profile')),
2892 2892 ('', 'traceback', None, _('print traceback on exception')),
2893 2893 ('', 'time', None, _('time how long the command takes')),
2894 2894 ('', 'profile', None, _('print command execution profile')),
2895 2895 ('', 'version', None, _('output version information and exit')),
2896 2896 ('h', 'help', None, _('display help and exit')),
2897 2897 ]
2898 2898
2899 2899 dryrunopts = [('n', 'dry-run', None,
2900 2900 _('do not perform actions, just print output'))]
2901 2901
2902 2902 remoteopts = [
2903 2903 ('e', 'ssh', '', _('specify ssh command to use')),
2904 2904 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2905 2905 ]
2906 2906
2907 2907 walkopts = [
2908 2908 ('I', 'include', [], _('include names matching the given patterns')),
2909 2909 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2910 2910 ]
2911 2911
2912 2912 commitopts = [
2913 2913 ('m', 'message', '', _('use <text> as commit message')),
2914 2914 ('l', 'logfile', '', _('read commit message from <file>')),
2915 2915 ]
2916 2916
2917 2917 commitopts2 = [
2918 2918 ('d', 'date', '', _('record datecode as commit date')),
2919 2919 ('u', 'user', '', _('record user as committer')),
2920 2920 ]
2921 2921
2922 2922 templateopts = [
2923 2923 ('', 'style', '', _('display using template map file')),
2924 2924 ('', 'template', '', _('display with template')),
2925 2925 ]
2926 2926
2927 2927 logopts = [
2928 2928 ('p', 'patch', None, _('show patch')),
2929 2929 ('l', 'limit', '', _('limit number of changes displayed')),
2930 2930 ('M', 'no-merges', None, _('do not show merges')),
2931 2931 ] + templateopts
2932 2932
2933 2933 table = {
2934 2934 "^add": (add, walkopts + dryrunopts, _('hg add [OPTION]... [FILE]...')),
2935 2935 "addremove":
2936 2936 (addremove,
2937 2937 [('s', 'similarity', '',
2938 2938 _('guess renamed files by similarity (0<=s<=100)')),
2939 2939 ] + walkopts + dryrunopts,
2940 2940 _('hg addremove [OPTION]... [FILE]...')),
2941 2941 "^annotate|blame":
2942 2942 (annotate,
2943 2943 [('r', 'rev', '', _('annotate the specified revision')),
2944 2944 ('f', 'follow', None, _('follow file copies and renames')),
2945 2945 ('a', 'text', None, _('treat all files as text')),
2946 2946 ('u', 'user', None, _('list the author (long with -v)')),
2947 2947 ('d', 'date', None, _('list the date (short with -q)')),
2948 2948 ('n', 'number', None, _('list the revision number (default)')),
2949 2949 ('c', 'changeset', None, _('list the changeset')),
2950 2950 ('l', 'line-number', None,
2951 2951 _('show line number at the first appearance'))
2952 2952 ] + walkopts,
2953 2953 _('hg annotate [-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
2954 2954 "archive":
2955 2955 (archive,
2956 2956 [('', 'no-decode', None, _('do not pass files through decoders')),
2957 2957 ('p', 'prefix', '', _('directory prefix for files in archive')),
2958 2958 ('r', 'rev', '', _('revision to distribute')),
2959 2959 ('t', 'type', '', _('type of distribution to create')),
2960 2960 ] + walkopts,
2961 2961 _('hg archive [OPTION]... DEST')),
2962 2962 "backout":
2963 2963 (backout,
2964 2964 [('', 'merge', None,
2965 2965 _('merge with old dirstate parent after backout')),
2966 2966 ('', 'parent', '', _('parent to choose when backing out merge')),
2967 2967 ('r', 'rev', '', _('revision to backout')),
2968 2968 ] + walkopts + commitopts + commitopts2,
2969 2969 _('hg backout [OPTION]... [-r] REV')),
2970 2970 "bisect":
2971 2971 (bisect,
2972 2972 [('r', 'reset', False, _('reset bisect state')),
2973 2973 ('g', 'good', False, _('mark changeset good')),
2974 2974 ('b', 'bad', False, _('mark changeset bad')),
2975 2975 ('s', 'skip', False, _('skip testing changeset')),
2976 2976 ('U', 'noupdate', False, _('do not update to target'))],
2977 2977 _("hg bisect [-gbsr] [REV]")),
2978 2978 "branch":
2979 2979 (branch,
2980 2980 [('f', 'force', None,
2981 2981 _('set branch name even if it shadows an existing branch'))],
2982 2982 _('hg branch [-f] [NAME]')),
2983 2983 "branches":
2984 2984 (branches,
2985 2985 [('a', 'active', False,
2986 2986 _('show only branches that have unmerged heads'))],
2987 2987 _('hg branches [-a]')),
2988 2988 "bundle":
2989 2989 (bundle,
2990 2990 [('f', 'force', None,
2991 2991 _('run even when remote repository is unrelated')),
2992 2992 ('r', 'rev', [],
2993 2993 _('a changeset up to which you would like to bundle')),
2994 2994 ('', 'base', [],
2995 2995 _('a base changeset to specify instead of a destination')),
2996 2996 ('a', 'all', None, _('bundle all changesets in the repository')),
2997 2997 ('t', 'type', 'bzip2', _('bundle compression type to use')),
2998 2998 ] + remoteopts,
2999 2999 _('hg bundle [-f] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
3000 3000 "cat":
3001 3001 (cat,
3002 3002 [('o', 'output', '', _('print output to file with formatted name')),
3003 3003 ('r', 'rev', '', _('print the given revision')),
3004 3004 ('', 'decode', None, _('apply any matching decode filter')),
3005 3005 ] + walkopts,
3006 3006 _('hg cat [OPTION]... FILE...')),
3007 3007 "^clone":
3008 3008 (clone,
3009 3009 [('U', 'noupdate', None, _('do not update the new working directory')),
3010 3010 ('r', 'rev', [],
3011 3011 _('a changeset you would like to have after cloning')),
3012 3012 ('', 'pull', None, _('use pull protocol to copy metadata')),
3013 3013 ('', 'uncompressed', None,
3014 3014 _('use uncompressed transfer (fast over LAN)')),
3015 3015 ] + remoteopts,
3016 3016 _('hg clone [OPTION]... SOURCE [DEST]')),
3017 3017 "^commit|ci":
3018 3018 (commit,
3019 3019 [('A', 'addremove', None,
3020 3020 _('mark new/missing files as added/removed before committing')),
3021 3021 ] + walkopts + commitopts + commitopts2,
3022 3022 _('hg commit [OPTION]... [FILE]...')),
3023 3023 "copy|cp":
3024 3024 (copy,
3025 3025 [('A', 'after', None, _('record a copy that has already occurred')),
3026 3026 ('f', 'force', None,
3027 3027 _('forcibly copy over an existing managed file')),
3028 3028 ] + walkopts + dryrunopts,
3029 3029 _('hg copy [OPTION]... [SOURCE]... DEST')),
3030 3030 "debugancestor": (debugancestor, [],
3031 3031 _('hg debugancestor [INDEX] REV1 REV2')),
3032 3032 "debugcheckstate": (debugcheckstate, [], _('hg debugcheckstate')),
3033 3033 "debugcomplete":
3034 3034 (debugcomplete,
3035 3035 [('o', 'options', None, _('show the command options'))],
3036 3036 _('hg debugcomplete [-o] CMD')),
3037 3037 "debugdate":
3038 3038 (debugdate,
3039 3039 [('e', 'extended', None, _('try extended date formats'))],
3040 3040 _('hg debugdate [-e] DATE [RANGE]')),
3041 3041 "debugdata": (debugdata, [], _('hg debugdata FILE REV')),
3042 3042 "debugfsinfo": (debugfsinfo, [], _('hg debugfsinfo [PATH]')),
3043 3043 "debugindex": (debugindex, [], _('hg debugindex FILE')),
3044 3044 "debugindexdot": (debugindexdot, [], _('hg debugindexdot FILE')),
3045 3045 "debuginstall": (debuginstall, [], _('hg debuginstall')),
3046 3046 "debugrawcommit|rawcommit":
3047 3047 (rawcommit,
3048 3048 [('p', 'parent', [], _('parent')),
3049 3049 ('F', 'files', '', _('file list'))
3050 3050 ] + commitopts + commitopts2,
3051 3051 _('hg debugrawcommit [OPTION]... [FILE]...')),
3052 3052 "debugrebuildstate":
3053 3053 (debugrebuildstate,
3054 3054 [('r', 'rev', '', _('revision to rebuild to'))],
3055 3055 _('hg debugrebuildstate [-r REV] [REV]')),
3056 3056 "debugrename":
3057 3057 (debugrename,
3058 3058 [('r', 'rev', '', _('revision to debug'))],
3059 3059 _('hg debugrename [-r REV] FILE')),
3060 3060 "debugsetparents":
3061 3061 (debugsetparents,
3062 3062 [],
3063 3063 _('hg debugsetparents REV1 [REV2]')),
3064 3064 "debugstate":
3065 3065 (debugstate,
3066 3066 [('', 'nodates', None, _('do not display the saved mtime'))],
3067 3067 _('hg debugstate [OPTS]')),
3068 3068 "debugwalk": (debugwalk, walkopts, _('hg debugwalk [OPTION]... [FILE]...')),
3069 3069 "^diff":
3070 3070 (diff,
3071 3071 [('r', 'rev', [], _('revision')),
3072 3072 ('a', 'text', None, _('treat all files as text')),
3073 3073 ('p', 'show-function', None,
3074 3074 _('show which function each change is in')),
3075 3075 ('g', 'git', None, _('use git extended diff format')),
3076 3076 ('', 'nodates', None, _("don't include dates in diff headers")),
3077 3077 ('w', 'ignore-all-space', None,
3078 3078 _('ignore white space when comparing lines')),
3079 3079 ('b', 'ignore-space-change', None,
3080 3080 _('ignore changes in the amount of white space')),
3081 3081 ('B', 'ignore-blank-lines', None,
3082 3082 _('ignore changes whose lines are all blank')),
3083 3083 ('U', 'unified', '',
3084 3084 _('number of lines of context to show'))
3085 3085 ] + walkopts,
3086 3086 _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
3087 3087 "^export":
3088 3088 (export,
3089 3089 [('o', 'output', '', _('print output to file with formatted name')),
3090 3090 ('a', 'text', None, _('treat all files as text')),
3091 3091 ('g', 'git', None, _('use git extended diff format')),
3092 3092 ('', 'nodates', None, _("don't include dates in diff headers")),
3093 3093 ('', 'switch-parent', None, _('diff against the second parent'))],
3094 3094 _('hg export [OPTION]... [-o OUTFILESPEC] REV...')),
3095 3095 "grep":
3096 3096 (grep,
3097 3097 [('0', 'print0', None, _('end fields with NUL')),
3098 3098 ('', 'all', None, _('print all revisions that match')),
3099 3099 ('f', 'follow', None,
3100 3100 _('follow changeset history, or file history across copies and renames')),
3101 3101 ('i', 'ignore-case', None, _('ignore case when matching')),
3102 3102 ('l', 'files-with-matches', None,
3103 3103 _('print only filenames and revs that match')),
3104 3104 ('n', 'line-number', None, _('print matching line numbers')),
3105 3105 ('r', 'rev', [], _('search in given revision range')),
3106 3106 ('u', 'user', None, _('list the author (long with -v)')),
3107 3107 ('d', 'date', None, _('list the date (short with -q)')),
3108 3108 ] + walkopts,
3109 3109 _('hg grep [OPTION]... PATTERN [FILE]...')),
3110 3110 "heads":
3111 3111 (heads,
3112 3112 [('r', 'rev', '', _('show only heads which are descendants of rev')),
3113 3113 ] + templateopts,
3114 3114 _('hg heads [-r REV] [REV]...')),
3115 3115 "help": (help_, [], _('hg help [COMMAND]')),
3116 3116 "identify|id":
3117 3117 (identify,
3118 3118 [('r', 'rev', '', _('identify the specified rev')),
3119 3119 ('n', 'num', None, _('show local revision number')),
3120 3120 ('i', 'id', None, _('show global revision id')),
3121 3121 ('b', 'branch', None, _('show branch')),
3122 3122 ('t', 'tags', None, _('show tags'))],
3123 3123 _('hg identify [-nibt] [-r REV] [SOURCE]')),
3124 3124 "import|patch":
3125 3125 (import_,
3126 3126 [('p', 'strip', 1,
3127 3127 _('directory strip option for patch. This has the same\n'
3128 3128 'meaning as the corresponding patch option')),
3129 3129 ('b', 'base', '', _('base path')),
3130 3130 ('f', 'force', None,
3131 3131 _('skip check for outstanding uncommitted changes')),
3132 3132 ('', 'no-commit', None, _("don't commit, just update the working directory")),
3133 3133 ('', 'exact', None,
3134 3134 _('apply patch to the nodes from which it was generated')),
3135 3135 ('', 'import-branch', None,
3136 3136 _('Use any branch information in patch (implied by --exact)'))] +
3137 3137 commitopts + commitopts2,
3138 3138 _('hg import [OPTION]... PATCH...')),
3139 3139 "incoming|in":
3140 3140 (incoming,
3141 3141 [('f', 'force', None,
3142 3142 _('run even when remote repository is unrelated')),
3143 3143 ('n', 'newest-first', None, _('show newest record first')),
3144 3144 ('', 'bundle', '', _('file to store the bundles into')),
3145 3145 ('r', 'rev', [],
3146 3146 _('a specific revision up to which you would like to pull')),
3147 3147 ] + logopts + remoteopts,
3148 3148 _('hg incoming [-p] [-n] [-M] [-f] [-r REV]...'
3149 3149 ' [--bundle FILENAME] [SOURCE]')),
3150 3150 "^init":
3151 3151 (init,
3152 3152 remoteopts,
3153 3153 _('hg init [-e CMD] [--remotecmd CMD] [DEST]')),
3154 3154 "locate":
3155 3155 (locate,
3156 3156 [('r', 'rev', '', _('search the repository as it stood at rev')),
3157 3157 ('0', 'print0', None,
3158 3158 _('end filenames with NUL, for use with xargs')),
3159 3159 ('f', 'fullpath', None,
3160 3160 _('print complete paths from the filesystem root')),
3161 3161 ] + walkopts,
3162 3162 _('hg locate [OPTION]... [PATTERN]...')),
3163 3163 "^log|history":
3164 3164 (log,
3165 3165 [('f', 'follow', None,
3166 3166 _('follow changeset history, or file history across copies and renames')),
3167 3167 ('', 'follow-first', None,
3168 3168 _('only follow the first parent of merge changesets')),
3169 3169 ('d', 'date', '', _('show revs matching date spec')),
3170 3170 ('C', 'copies', None, _('show copied files')),
3171 3171 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
3172 3172 ('r', 'rev', [], _('show the specified revision or range')),
3173 3173 ('', 'removed', None, _('include revs where files were removed')),
3174 3174 ('m', 'only-merges', None, _('show only merges')),
3175 3175 ('b', 'only-branch', [],
3176 3176 _('show only changesets within the given named branch')),
3177 3177 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
3178 3178 ] + logopts + walkopts,
3179 3179 _('hg log [OPTION]... [FILE]')),
3180 3180 "manifest":
3181 3181 (manifest,
3182 3182 [('r', 'rev', '', _('revision to display'))],
3183 3183 _('hg manifest [-r REV]')),
3184 3184 "^merge":
3185 3185 (merge,
3186 3186 [('f', 'force', None, _('force a merge with outstanding changes')),
3187 3187 ('r', 'rev', '', _('revision to merge')),
3188 3188 ],
3189 3189 _('hg merge [-f] [[-r] REV]')),
3190 3190 "outgoing|out":
3191 3191 (outgoing,
3192 3192 [('f', 'force', None,
3193 3193 _('run even when remote repository is unrelated')),
3194 3194 ('r', 'rev', [],
3195 3195 _('a specific revision up to which you would like to push')),
3196 3196 ('n', 'newest-first', None, _('show newest record first')),
3197 3197 ] + logopts + remoteopts,
3198 3198 _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3199 3199 "^parents":
3200 3200 (parents,
3201 3201 [('r', 'rev', '', _('show parents from the specified rev')),
3202 3202 ] + templateopts,
3203 3203 _('hg parents [-r REV] [FILE]')),
3204 3204 "paths": (paths, [], _('hg paths [NAME]')),
3205 3205 "^pull":
3206 3206 (pull,
3207 3207 [('u', 'update', None,
3208 3208 _('update to new tip if changesets were pulled')),
3209 3209 ('f', 'force', None,
3210 3210 _('run even when remote repository is unrelated')),
3211 3211 ('r', 'rev', [],
3212 3212 _('a specific revision up to which you would like to pull')),
3213 3213 ] + remoteopts,
3214 3214 _('hg pull [-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3215 3215 "^push":
3216 3216 (push,
3217 3217 [('f', 'force', None, _('force push')),
3218 3218 ('r', 'rev', [],
3219 3219 _('a specific revision up to which you would like to push')),
3220 3220 ] + remoteopts,
3221 3221 _('hg push [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3222 3222 "recover": (recover, [], _('hg recover')),
3223 3223 "^remove|rm":
3224 3224 (remove,
3225 3225 [('A', 'after', None, _('record delete for missing files')),
3226 3226 ('f', 'force', None,
3227 3227 _('remove (and delete) file even if added or modified')),
3228 3228 ] + walkopts,
3229 3229 _('hg remove [OPTION]... FILE...')),
3230 3230 "rename|mv":
3231 3231 (rename,
3232 3232 [('A', 'after', None, _('record a rename that has already occurred')),
3233 3233 ('f', 'force', None,
3234 3234 _('forcibly copy over an existing managed file')),
3235 3235 ] + walkopts + dryrunopts,
3236 3236 _('hg rename [OPTION]... SOURCE... DEST')),
3237 3237 "resolve":
3238 3238 (resolve,
3239 3239 [('l', 'list', None, _('list state of files needing merge')),
3240 3240 ('m', 'mark', None, _('mark files as resolved')),
3241 3241 ('u', 'unmark', None, _('unmark files as resolved'))],
3242 3242 ('hg resolve [OPTION] [FILES...]')),
3243 3243 "revert":
3244 3244 (revert,
3245 3245 [('a', 'all', None, _('revert all changes when no arguments given')),
3246 3246 ('d', 'date', '', _('tipmost revision matching date')),
3247 3247 ('r', 'rev', '', _('revision to revert to')),
3248 3248 ('', 'no-backup', None, _('do not save backup copies of files')),
3249 3249 ] + walkopts + dryrunopts,
3250 3250 _('hg revert [OPTION]... [-r REV] [NAME]...')),
3251 3251 "rollback": (rollback, [], _('hg rollback')),
3252 3252 "root": (root, [], _('hg root')),
3253 3253 "^serve":
3254 3254 (serve,
3255 3255 [('A', 'accesslog', '', _('name of access log file to write to')),
3256 3256 ('d', 'daemon', None, _('run server in background')),
3257 3257 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3258 3258 ('E', 'errorlog', '', _('name of error log file to write to')),
3259 3259 ('p', 'port', 0, _('port to listen on (default: 8000)')),
3260 3260 ('a', 'address', '', _('address to listen on (default: all interfaces)')),
3261 3261 ('', 'prefix', '', _('prefix path to serve from (default: server root)')),
3262 3262 ('n', 'name', '',
3263 3263 _('name to show in web pages (default: working dir)')),
3264 3264 ('', 'webdir-conf', '', _('name of the webdir config file'
3265 3265 ' (serve more than one repo)')),
3266 3266 ('', 'pid-file', '', _('name of file to write process ID to')),
3267 3267 ('', 'stdio', None, _('for remote clients')),
3268 3268 ('t', 'templates', '', _('web templates to use')),
3269 3269 ('', 'style', '', _('template style to use')),
3270 3270 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3271 3271 ('', 'certificate', '', _('SSL certificate file'))],
3272 3272 _('hg serve [OPTION]...')),
3273 3273 "showconfig|debugconfig":
3274 3274 (showconfig,
3275 3275 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3276 3276 _('hg showconfig [-u] [NAME]...')),
3277 3277 "^status|st":
3278 3278 (status,
3279 3279 [('A', 'all', None, _('show status of all files')),
3280 3280 ('m', 'modified', None, _('show only modified files')),
3281 3281 ('a', 'added', None, _('show only added files')),
3282 3282 ('r', 'removed', None, _('show only removed files')),
3283 3283 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3284 3284 ('c', 'clean', None, _('show only files without changes')),
3285 3285 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3286 3286 ('i', 'ignored', None, _('show only ignored files')),
3287 3287 ('n', 'no-status', None, _('hide status prefix')),
3288 3288 ('C', 'copies', None, _('show source of copied files')),
3289 3289 ('0', 'print0', None,
3290 3290 _('end filenames with NUL, for use with xargs')),
3291 3291 ('', 'rev', [], _('show difference from revision')),
3292 3292 ] + walkopts,
3293 3293 _('hg status [OPTION]... [FILE]...')),
3294 3294 "tag":
3295 3295 (tag,
3296 3296 [('f', 'force', None, _('replace existing tag')),
3297 3297 ('l', 'local', None, _('make the tag local')),
3298 3298 ('r', 'rev', '', _('revision to tag')),
3299 3299 ('', 'remove', None, _('remove a tag')),
3300 3300 # -l/--local is already there, commitopts cannot be used
3301 3301 ('m', 'message', '', _('use <text> as commit message')),
3302 3302 ] + commitopts2,
3303 3303 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
3304 3304 "tags": (tags, [], _('hg tags')),
3305 3305 "tip":
3306 3306 (tip,
3307 3307 [('p', 'patch', None, _('show patch')),
3308 3308 ] + templateopts,
3309 3309 _('hg tip [-p]')),
3310 3310 "unbundle":
3311 3311 (unbundle,
3312 3312 [('u', 'update', None,
3313 3313 _('update to new tip if changesets were unbundled'))],
3314 3314 _('hg unbundle [-u] FILE...')),
3315 3315 "^update|up|checkout|co":
3316 3316 (update,
3317 3317 [('C', 'clean', None, _('overwrite locally modified files')),
3318 3318 ('d', 'date', '', _('tipmost revision matching date')),
3319 3319 ('r', 'rev', '', _('revision'))],
3320 3320 _('hg update [-C] [-d DATE] [[-r] REV]')),
3321 3321 "verify": (verify, [], _('hg verify')),
3322 3322 "version": (version_, [], _('hg version')),
3323 3323 }
3324 3324
3325 3325 norepo = ("clone init version help debugcomplete debugdata"
3326 3326 " debugindex debugindexdot debugdate debuginstall debugfsinfo")
3327 3327 optionalrepo = ("identify paths serve showconfig debugancestor")
@@ -1,2137 +1,2137
1 1 # localrepo.py - read/write repository class 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
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import bin, hex, nullid, nullrev, short
9 9 from i18n import _
10 10 import repo, changegroup
11 11 import changelog, dirstate, filelog, manifest, context, weakref
12 12 import lock, transaction, stat, errno, ui
13 13 import os, revlog, time, util, extensions, hook, inspect
14 14
15 15 class localrepository(repo.repository):
16 16 capabilities = util.set(('lookup', 'changegroupsubset'))
17 17 supported = ('revlogv1', 'store')
18 18
19 19 def __init__(self, parentui, path=None, create=0):
20 20 repo.repository.__init__(self)
21 21 self.root = os.path.realpath(path)
22 22 self.path = os.path.join(self.root, ".hg")
23 23 self.origroot = path
24 24 self.opener = util.opener(self.path)
25 25 self.wopener = util.opener(self.root)
26 26
27 27 if not os.path.isdir(self.path):
28 28 if create:
29 29 if not os.path.exists(path):
30 30 os.mkdir(path)
31 31 os.mkdir(self.path)
32 32 requirements = ["revlogv1"]
33 33 if parentui.configbool('format', 'usestore', True):
34 34 os.mkdir(os.path.join(self.path, "store"))
35 35 requirements.append("store")
36 36 # create an invalid changelog
37 37 self.opener("00changelog.i", "a").write(
38 38 '\0\0\0\2' # represents revlogv2
39 39 ' dummy changelog to prevent using the old repo layout'
40 40 )
41 41 reqfile = self.opener("requires", "w")
42 42 for r in requirements:
43 43 reqfile.write("%s\n" % r)
44 44 reqfile.close()
45 45 else:
46 46 raise repo.RepoError(_("repository %s not found") % path)
47 47 elif create:
48 48 raise repo.RepoError(_("repository %s already exists") % path)
49 49 else:
50 50 # find requirements
51 51 try:
52 52 requirements = self.opener("requires").read().splitlines()
53 53 except IOError, inst:
54 54 if inst.errno != errno.ENOENT:
55 55 raise
56 56 requirements = []
57 57 # check them
58 58 for r in requirements:
59 59 if r not in self.supported:
60 60 raise repo.RepoError(_("requirement '%s' not supported") % r)
61 61
62 62 # setup store
63 63 if "store" in requirements:
64 64 self.encodefn = util.encodefilename
65 65 self.decodefn = util.decodefilename
66 66 self.spath = os.path.join(self.path, "store")
67 67 else:
68 68 self.encodefn = lambda x: x
69 69 self.decodefn = lambda x: x
70 70 self.spath = self.path
71 71
72 72 try:
73 73 # files in .hg/ will be created using this mode
74 74 mode = os.stat(self.spath).st_mode
75 75 # avoid some useless chmods
76 76 if (0777 & ~util._umask) == (0777 & mode):
77 77 mode = None
78 78 except OSError:
79 79 mode = None
80 80
81 81 self._createmode = mode
82 82 self.opener.createmode = mode
83 83 sopener = util.opener(self.spath)
84 84 sopener.createmode = mode
85 85 self.sopener = util.encodedopener(sopener, self.encodefn)
86 86
87 87 self.ui = ui.ui(parentui=parentui)
88 88 try:
89 89 self.ui.readconfig(self.join("hgrc"), self.root)
90 90 extensions.loadall(self.ui)
91 91 except IOError:
92 92 pass
93 93
94 94 self.tagscache = None
95 95 self._tagstypecache = None
96 96 self.branchcache = None
97 97 self._ubranchcache = None # UTF-8 version of branchcache
98 98 self._branchcachetip = None
99 99 self.nodetagscache = None
100 100 self.filterpats = {}
101 101 self._datafilters = {}
102 102 self._transref = self._lockref = self._wlockref = None
103 103
104 104 def __getattr__(self, name):
105 105 if name == 'changelog':
106 106 self.changelog = changelog.changelog(self.sopener)
107 107 self.sopener.defversion = self.changelog.version
108 108 return self.changelog
109 109 if name == 'manifest':
110 110 self.changelog
111 111 self.manifest = manifest.manifest(self.sopener)
112 112 return self.manifest
113 113 if name == 'dirstate':
114 114 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
115 115 return self.dirstate
116 116 else:
117 117 raise AttributeError, name
118 118
119 119 def url(self):
120 120 return 'file:' + self.root
121 121
122 122 def hook(self, name, throw=False, **args):
123 123 return hook.hook(self.ui, self, name, throw, **args)
124 124
125 125 tag_disallowed = ':\r\n'
126 126
127 127 def _tag(self, names, node, message, local, user, date, parent=None,
128 128 extra={}):
129 129 use_dirstate = parent is None
130 130
131 131 if isinstance(names, str):
132 132 allchars = names
133 133 names = (names,)
134 134 else:
135 135 allchars = ''.join(names)
136 136 for c in self.tag_disallowed:
137 137 if c in allchars:
138 138 raise util.Abort(_('%r cannot be used in a tag name') % c)
139 139
140 140 for name in names:
141 141 self.hook('pretag', throw=True, node=hex(node), tag=name,
142 142 local=local)
143 143
144 144 def writetags(fp, names, munge, prevtags):
145 145 fp.seek(0, 2)
146 146 if prevtags and prevtags[-1] != '\n':
147 147 fp.write('\n')
148 148 for name in names:
149 149 fp.write('%s %s\n' % (hex(node), munge and munge(name) or name))
150 150 fp.close()
151 151
152 152 prevtags = ''
153 153 if local:
154 154 try:
155 155 fp = self.opener('localtags', 'r+')
156 156 except IOError, err:
157 157 fp = self.opener('localtags', 'a')
158 158 else:
159 159 prevtags = fp.read()
160 160
161 161 # local tags are stored in the current charset
162 162 writetags(fp, names, None, prevtags)
163 163 for name in names:
164 164 self.hook('tag', node=hex(node), tag=name, local=local)
165 165 return
166 166
167 167 if use_dirstate:
168 168 try:
169 169 fp = self.wfile('.hgtags', 'rb+')
170 170 except IOError, err:
171 171 fp = self.wfile('.hgtags', 'ab')
172 172 else:
173 173 prevtags = fp.read()
174 174 else:
175 175 try:
176 176 prevtags = self.filectx('.hgtags', parent).data()
177 177 except revlog.LookupError:
178 178 pass
179 179 fp = self.wfile('.hgtags', 'wb')
180 180 if prevtags:
181 181 fp.write(prevtags)
182 182
183 183 # committed tags are stored in UTF-8
184 184 writetags(fp, names, util.fromlocal, prevtags)
185 185
186 186 if use_dirstate and '.hgtags' not in self.dirstate:
187 187 self.add(['.hgtags'])
188 188
189 189 tagnode = self.commit(['.hgtags'], message, user, date, p1=parent,
190 190 extra=extra)
191 191
192 192 for name in names:
193 193 self.hook('tag', node=hex(node), tag=name, local=local)
194 194
195 195 return tagnode
196 196
197 197 def tag(self, names, node, message, local, user, date):
198 198 '''tag a revision with one or more symbolic names.
199 199
200 200 names is a list of strings or, when adding a single tag, names may be a
201 201 string.
202 202
203 203 if local is True, the tags are stored in a per-repository file.
204 204 otherwise, they are stored in the .hgtags file, and a new
205 205 changeset is committed with the change.
206 206
207 207 keyword arguments:
208 208
209 209 local: whether to store tags in non-version-controlled file
210 210 (default False)
211 211
212 212 message: commit message to use if committing
213 213
214 214 user: name of user to use if committing
215 215
216 216 date: date tuple to use if committing'''
217 217
218 218 for x in self.status()[:5]:
219 219 if '.hgtags' in x:
220 220 raise util.Abort(_('working copy of .hgtags is changed '
221 221 '(please commit .hgtags manually)'))
222 222
223 223 self._tag(names, node, message, local, user, date)
224 224
225 225 def tags(self):
226 226 '''return a mapping of tag to node'''
227 227 if self.tagscache:
228 228 return self.tagscache
229 229
230 230 globaltags = {}
231 231 tagtypes = {}
232 232
233 233 def readtags(lines, fn, tagtype):
234 234 filetags = {}
235 235 count = 0
236 236
237 237 def warn(msg):
238 238 self.ui.warn(_("%s, line %s: %s\n") % (fn, count, msg))
239 239
240 240 for l in lines:
241 241 count += 1
242 242 if not l:
243 243 continue
244 244 s = l.split(" ", 1)
245 245 if len(s) != 2:
246 246 warn(_("cannot parse entry"))
247 247 continue
248 248 node, key = s
249 249 key = util.tolocal(key.strip()) # stored in UTF-8
250 250 try:
251 251 bin_n = bin(node)
252 252 except TypeError:
253 253 warn(_("node '%s' is not well formed") % node)
254 254 continue
255 255 if bin_n not in self.changelog.nodemap:
256 256 warn(_("tag '%s' refers to unknown node") % key)
257 257 continue
258 258
259 259 h = []
260 260 if key in filetags:
261 261 n, h = filetags[key]
262 262 h.append(n)
263 263 filetags[key] = (bin_n, h)
264 264
265 265 for k, nh in filetags.items():
266 266 if k not in globaltags:
267 267 globaltags[k] = nh
268 268 tagtypes[k] = tagtype
269 269 continue
270 270
271 271 # we prefer the global tag if:
272 272 # it supercedes us OR
273 273 # mutual supercedes and it has a higher rank
274 274 # otherwise we win because we're tip-most
275 275 an, ah = nh
276 276 bn, bh = globaltags[k]
277 277 if (bn != an and an in bh and
278 278 (bn not in ah or len(bh) > len(ah))):
279 279 an = bn
280 280 ah.extend([n for n in bh if n not in ah])
281 281 globaltags[k] = an, ah
282 282 tagtypes[k] = tagtype
283 283
284 284 # read the tags file from each head, ending with the tip
285 285 f = None
286 286 for rev, node, fnode in self._hgtagsnodes():
287 287 f = (f and f.filectx(fnode) or
288 288 self.filectx('.hgtags', fileid=fnode))
289 289 readtags(f.data().splitlines(), f, "global")
290 290
291 291 try:
292 292 data = util.fromlocal(self.opener("localtags").read())
293 293 # localtags are stored in the local character set
294 294 # while the internal tag table is stored in UTF-8
295 295 readtags(data.splitlines(), "localtags", "local")
296 296 except IOError:
297 297 pass
298 298
299 299 self.tagscache = {}
300 300 self._tagstypecache = {}
301 301 for k,nh in globaltags.items():
302 302 n = nh[0]
303 303 if n != nullid:
304 304 self.tagscache[k] = n
305 305 self._tagstypecache[k] = tagtypes[k]
306 306 self.tagscache['tip'] = self.changelog.tip()
307 307
308 308 return self.tagscache
309 309
310 310 def tagtype(self, tagname):
311 311 '''
312 312 return the type of the given tag. result can be:
313 313
314 314 'local' : a local tag
315 315 'global' : a global tag
316 316 None : tag does not exist
317 317 '''
318 318
319 319 self.tags()
320 320
321 321 return self._tagstypecache.get(tagname)
322 322
323 323 def _hgtagsnodes(self):
324 324 heads = self.heads()
325 325 heads.reverse()
326 326 last = {}
327 327 ret = []
328 328 for node in heads:
329 329 c = self.changectx(node)
330 330 rev = c.rev()
331 331 try:
332 332 fnode = c.filenode('.hgtags')
333 333 except revlog.LookupError:
334 334 continue
335 335 ret.append((rev, node, fnode))
336 336 if fnode in last:
337 337 ret[last[fnode]] = None
338 338 last[fnode] = len(ret) - 1
339 339 return [item for item in ret if item]
340 340
341 341 def tagslist(self):
342 342 '''return a list of tags ordered by revision'''
343 343 l = []
344 344 for t, n in self.tags().items():
345 345 try:
346 346 r = self.changelog.rev(n)
347 347 except:
348 348 r = -2 # sort to the beginning of the list if unknown
349 349 l.append((r, t, n))
350 350 l.sort()
351 351 return [(t, n) for r, t, n in l]
352 352
353 353 def nodetags(self, node):
354 354 '''return the tags associated with a node'''
355 355 if not self.nodetagscache:
356 356 self.nodetagscache = {}
357 357 for t, n in self.tags().items():
358 358 self.nodetagscache.setdefault(n, []).append(t)
359 359 return self.nodetagscache.get(node, [])
360 360
361 361 def _branchtags(self, partial, lrev):
362 362 tiprev = self.changelog.count() - 1
363 363 if lrev != tiprev:
364 364 self._updatebranchcache(partial, lrev+1, tiprev+1)
365 365 self._writebranchcache(partial, self.changelog.tip(), tiprev)
366 366
367 367 return partial
368 368
369 369 def branchtags(self):
370 370 tip = self.changelog.tip()
371 371 if self.branchcache is not None and self._branchcachetip == tip:
372 372 return self.branchcache
373 373
374 374 oldtip = self._branchcachetip
375 375 self._branchcachetip = tip
376 376 if self.branchcache is None:
377 377 self.branchcache = {} # avoid recursion in changectx
378 378 else:
379 379 self.branchcache.clear() # keep using the same dict
380 380 if oldtip is None or oldtip not in self.changelog.nodemap:
381 381 partial, last, lrev = self._readbranchcache()
382 382 else:
383 383 lrev = self.changelog.rev(oldtip)
384 384 partial = self._ubranchcache
385 385
386 386 self._branchtags(partial, lrev)
387 387
388 388 # the branch cache is stored on disk as UTF-8, but in the local
389 389 # charset internally
390 390 for k, v in partial.items():
391 391 self.branchcache[util.tolocal(k)] = v
392 392 self._ubranchcache = partial
393 393 return self.branchcache
394 394
395 395 def _readbranchcache(self):
396 396 partial = {}
397 397 try:
398 398 f = self.opener("branch.cache")
399 399 lines = f.read().split('\n')
400 400 f.close()
401 401 except (IOError, OSError):
402 402 return {}, nullid, nullrev
403 403
404 404 try:
405 405 last, lrev = lines.pop(0).split(" ", 1)
406 406 last, lrev = bin(last), int(lrev)
407 407 if not (lrev < self.changelog.count() and
408 408 self.changelog.node(lrev) == last): # sanity check
409 409 # invalidate the cache
410 410 raise ValueError('invalidating branch cache (tip differs)')
411 411 for l in lines:
412 412 if not l: continue
413 413 node, label = l.split(" ", 1)
414 414 partial[label.strip()] = bin(node)
415 415 except (KeyboardInterrupt, util.SignalInterrupt):
416 416 raise
417 417 except Exception, inst:
418 418 if self.ui.debugflag:
419 419 self.ui.warn(str(inst), '\n')
420 420 partial, last, lrev = {}, nullid, nullrev
421 421 return partial, last, lrev
422 422
423 423 def _writebranchcache(self, branches, tip, tiprev):
424 424 try:
425 425 f = self.opener("branch.cache", "w", atomictemp=True)
426 426 f.write("%s %s\n" % (hex(tip), tiprev))
427 427 for label, node in branches.iteritems():
428 428 f.write("%s %s\n" % (hex(node), label))
429 429 f.rename()
430 430 except (IOError, OSError):
431 431 pass
432 432
433 433 def _updatebranchcache(self, partial, start, end):
434 434 for r in xrange(start, end):
435 435 c = self.changectx(r)
436 436 b = c.branch()
437 437 partial[b] = c.node()
438 438
439 439 def lookup(self, key):
440 440 if key == '.':
441 441 key, second = self.dirstate.parents()
442 442 if key == nullid:
443 443 raise repo.RepoError(_("no revision checked out"))
444 444 if second != nullid:
445 445 self.ui.warn(_("warning: working directory has two parents, "
446 446 "tag '.' uses the first\n"))
447 447 elif key == 'null':
448 448 return nullid
449 449 n = self.changelog._match(key)
450 450 if n:
451 451 return n
452 452 if key in self.tags():
453 453 return self.tags()[key]
454 454 if key in self.branchtags():
455 455 return self.branchtags()[key]
456 456 n = self.changelog._partialmatch(key)
457 457 if n:
458 458 return n
459 459 try:
460 460 if len(key) == 20:
461 461 key = hex(key)
462 462 except:
463 463 pass
464 464 raise repo.RepoError(_("unknown revision '%s'") % key)
465 465
466 466 def local(self):
467 467 return True
468 468
469 469 def join(self, f):
470 470 return os.path.join(self.path, f)
471 471
472 472 def sjoin(self, f):
473 473 f = self.encodefn(f)
474 474 return os.path.join(self.spath, f)
475 475
476 476 def wjoin(self, f):
477 477 return os.path.join(self.root, f)
478 478
479 479 def rjoin(self, f):
480 480 return os.path.join(self.root, util.pconvert(f))
481 481
482 482 def file(self, f):
483 483 if f[0] == '/':
484 484 f = f[1:]
485 485 return filelog.filelog(self.sopener, f)
486 486
487 487 def changectx(self, changeid=None):
488 488 return context.changectx(self, changeid)
489 489
490 490 def workingctx(self):
491 491 return context.workingctx(self)
492 492
493 493 def parents(self, changeid=None):
494 494 '''
495 495 get list of changectxs for parents of changeid or working directory
496 496 '''
497 497 if changeid is None:
498 498 pl = self.dirstate.parents()
499 499 else:
500 500 n = self.changelog.lookup(changeid)
501 501 pl = self.changelog.parents(n)
502 502 if pl[1] == nullid:
503 503 return [self.changectx(pl[0])]
504 504 return [self.changectx(pl[0]), self.changectx(pl[1])]
505 505
506 506 def filectx(self, path, changeid=None, fileid=None):
507 507 """changeid can be a changeset revision, node, or tag.
508 508 fileid can be a file revision or node."""
509 509 return context.filectx(self, path, changeid, fileid)
510 510
511 511 def getcwd(self):
512 512 return self.dirstate.getcwd()
513 513
514 514 def pathto(self, f, cwd=None):
515 515 return self.dirstate.pathto(f, cwd)
516 516
517 517 def wfile(self, f, mode='r'):
518 518 return self.wopener(f, mode)
519 519
520 520 def _link(self, f):
521 521 return os.path.islink(self.wjoin(f))
522 522
523 523 def _filter(self, filter, filename, data):
524 524 if filter not in self.filterpats:
525 525 l = []
526 526 for pat, cmd in self.ui.configitems(filter):
527 527 mf = util.matcher(self.root, "", [pat], [], [])[1]
528 528 fn = None
529 529 params = cmd
530 530 for name, filterfn in self._datafilters.iteritems():
531 531 if cmd.startswith(name):
532 532 fn = filterfn
533 533 params = cmd[len(name):].lstrip()
534 534 break
535 535 if not fn:
536 536 fn = lambda s, c, **kwargs: util.filter(s, c)
537 537 # Wrap old filters not supporting keyword arguments
538 538 if not inspect.getargspec(fn)[2]:
539 539 oldfn = fn
540 540 fn = lambda s, c, **kwargs: oldfn(s, c)
541 541 l.append((mf, fn, params))
542 542 self.filterpats[filter] = l
543 543
544 544 for mf, fn, cmd in self.filterpats[filter]:
545 545 if mf(filename):
546 546 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
547 547 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
548 548 break
549 549
550 550 return data
551 551
552 552 def adddatafilter(self, name, filter):
553 553 self._datafilters[name] = filter
554 554
555 555 def wread(self, filename):
556 556 if self._link(filename):
557 557 data = os.readlink(self.wjoin(filename))
558 558 else:
559 559 data = self.wopener(filename, 'r').read()
560 560 return self._filter("encode", filename, data)
561 561
562 562 def wwrite(self, filename, data, flags):
563 563 data = self._filter("decode", filename, data)
564 564 try:
565 565 os.unlink(self.wjoin(filename))
566 566 except OSError:
567 567 pass
568 568 self.wopener(filename, 'w').write(data)
569 569 util.set_flags(self.wjoin(filename), flags)
570 570
571 571 def wwritedata(self, filename, data):
572 572 return self._filter("decode", filename, data)
573 573
574 574 def transaction(self):
575 575 if self._transref and self._transref():
576 576 return self._transref().nest()
577 577
578 578 # abort here if the journal already exists
579 579 if os.path.exists(self.sjoin("journal")):
580 580 raise repo.RepoError(_("journal already exists - run hg recover"))
581 581
582 582 # save dirstate for rollback
583 583 try:
584 584 ds = self.opener("dirstate").read()
585 585 except IOError:
586 586 ds = ""
587 587 self.opener("journal.dirstate", "w").write(ds)
588 588 self.opener("journal.branch", "w").write(self.dirstate.branch())
589 589
590 590 renames = [(self.sjoin("journal"), self.sjoin("undo")),
591 591 (self.join("journal.dirstate"), self.join("undo.dirstate")),
592 592 (self.join("journal.branch"), self.join("undo.branch"))]
593 593 tr = transaction.transaction(self.ui.warn, self.sopener,
594 594 self.sjoin("journal"),
595 595 aftertrans(renames),
596 596 self._createmode)
597 597 self._transref = weakref.ref(tr)
598 598 return tr
599 599
600 600 def recover(self):
601 601 l = self.lock()
602 602 try:
603 603 if os.path.exists(self.sjoin("journal")):
604 604 self.ui.status(_("rolling back interrupted transaction\n"))
605 605 transaction.rollback(self.sopener, self.sjoin("journal"))
606 606 self.invalidate()
607 607 return True
608 608 else:
609 609 self.ui.warn(_("no interrupted transaction available\n"))
610 610 return False
611 611 finally:
612 612 del l
613 613
614 614 def rollback(self):
615 615 wlock = lock = None
616 616 try:
617 617 wlock = self.wlock()
618 618 lock = self.lock()
619 619 if os.path.exists(self.sjoin("undo")):
620 620 self.ui.status(_("rolling back last transaction\n"))
621 621 transaction.rollback(self.sopener, self.sjoin("undo"))
622 622 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
623 623 try:
624 624 branch = self.opener("undo.branch").read()
625 625 self.dirstate.setbranch(branch)
626 626 except IOError:
627 627 self.ui.warn(_("Named branch could not be reset, "
628 628 "current branch still is: %s\n")
629 629 % util.tolocal(self.dirstate.branch()))
630 630 self.invalidate()
631 631 self.dirstate.invalidate()
632 632 else:
633 633 self.ui.warn(_("no rollback information available\n"))
634 634 finally:
635 635 del lock, wlock
636 636
637 637 def invalidate(self):
638 638 for a in "changelog manifest".split():
639 639 if a in self.__dict__:
640 640 delattr(self, a)
641 641 self.tagscache = None
642 642 self._tagstypecache = None
643 643 self.nodetagscache = None
644 644 self.branchcache = None
645 645 self._ubranchcache = None
646 646 self._branchcachetip = None
647 647
648 648 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
649 649 try:
650 650 l = lock.lock(lockname, 0, releasefn, desc=desc)
651 651 except lock.LockHeld, inst:
652 652 if not wait:
653 653 raise
654 654 self.ui.warn(_("waiting for lock on %s held by %r\n") %
655 655 (desc, inst.locker))
656 656 # default to 600 seconds timeout
657 657 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
658 658 releasefn, desc=desc)
659 659 if acquirefn:
660 660 acquirefn()
661 661 return l
662 662
663 663 def lock(self, wait=True):
664 664 if self._lockref and self._lockref():
665 665 return self._lockref()
666 666
667 667 l = self._lock(self.sjoin("lock"), wait, None, self.invalidate,
668 668 _('repository %s') % self.origroot)
669 669 self._lockref = weakref.ref(l)
670 670 return l
671 671
672 672 def wlock(self, wait=True):
673 673 if self._wlockref and self._wlockref():
674 674 return self._wlockref()
675 675
676 676 l = self._lock(self.join("wlock"), wait, self.dirstate.write,
677 677 self.dirstate.invalidate, _('working directory of %s') %
678 678 self.origroot)
679 679 self._wlockref = weakref.ref(l)
680 680 return l
681 681
682 682 def filecommit(self, fn, manifest1, manifest2, linkrev, tr, changelist):
683 683 """
684 684 commit an individual file as part of a larger transaction
685 685 """
686 686
687 687 t = self.wread(fn)
688 688 fl = self.file(fn)
689 689 fp1 = manifest1.get(fn, nullid)
690 690 fp2 = manifest2.get(fn, nullid)
691 691
692 692 meta = {}
693 693 cp = self.dirstate.copied(fn)
694 694 if cp:
695 695 # Mark the new revision of this file as a copy of another
696 696 # file. This copy data will effectively act as a parent
697 697 # of this new revision. If this is a merge, the first
698 698 # parent will be the nullid (meaning "look up the copy data")
699 699 # and the second one will be the other parent. For example:
700 700 #
701 701 # 0 --- 1 --- 3 rev1 changes file foo
702 702 # \ / rev2 renames foo to bar and changes it
703 703 # \- 2 -/ rev3 should have bar with all changes and
704 704 # should record that bar descends from
705 705 # bar in rev2 and foo in rev1
706 706 #
707 707 # this allows this merge to succeed:
708 708 #
709 709 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
710 710 # \ / merging rev3 and rev4 should use bar@rev2
711 711 # \- 2 --- 4 as the merge base
712 712 #
713 713 meta["copy"] = cp
714 714 if not manifest2: # not a branch merge
715 715 meta["copyrev"] = hex(manifest1[cp])
716 716 fp2 = nullid
717 717 elif fp2 != nullid: # copied on remote side
718 718 meta["copyrev"] = hex(manifest1[cp])
719 719 elif fp1 != nullid: # copied on local side, reversed
720 720 meta["copyrev"] = hex(manifest2[cp])
721 721 fp2 = fp1
722 722 elif cp in manifest2: # directory rename on local side
723 723 meta["copyrev"] = hex(manifest2[cp])
724 724 else: # directory rename on remote side
725 725 meta["copyrev"] = hex(manifest1[cp])
726 726 self.ui.debug(_(" %s: copy %s:%s\n") %
727 727 (fn, cp, meta["copyrev"]))
728 728 fp1 = nullid
729 729 elif fp2 != nullid:
730 730 # is one parent an ancestor of the other?
731 731 fpa = fl.ancestor(fp1, fp2)
732 732 if fpa == fp1:
733 733 fp1, fp2 = fp2, nullid
734 734 elif fpa == fp2:
735 735 fp2 = nullid
736 736
737 737 # is the file unmodified from the parent? report existing entry
738 738 if fp2 == nullid and not fl.cmp(fp1, t) and not meta:
739 739 return fp1
740 740
741 741 changelist.append(fn)
742 742 return fl.add(t, meta, tr, linkrev, fp1, fp2)
743 743
744 744 def rawcommit(self, files, text, user, date, p1=None, p2=None, extra={}):
745 745 if p1 is None:
746 746 p1, p2 = self.dirstate.parents()
747 747 return self.commit(files=files, text=text, user=user, date=date,
748 748 p1=p1, p2=p2, extra=extra, empty_ok=True)
749 749
750 750 def commit(self, files=None, text="", user=None, date=None,
751 751 match=util.always, force=False, force_editor=False,
752 752 p1=None, p2=None, extra={}, empty_ok=False):
753 753 wlock = lock = tr = None
754 754 valid = 0 # don't save the dirstate if this isn't set
755 755 if files:
756 756 files = util.unique(files)
757 757 try:
758 758 wlock = self.wlock()
759 759 lock = self.lock()
760 760 commit = []
761 761 remove = []
762 762 changed = []
763 763 use_dirstate = (p1 is None) # not rawcommit
764 764 extra = extra.copy()
765 765
766 766 if use_dirstate:
767 767 if files:
768 768 for f in files:
769 769 s = self.dirstate[f]
770 770 if s in 'nma':
771 771 commit.append(f)
772 772 elif s == 'r':
773 773 remove.append(f)
774 774 else:
775 775 self.ui.warn(_("%s not tracked!\n") % f)
776 776 else:
777 777 changes = self.status(match=match)[:5]
778 778 modified, added, removed, deleted, unknown = changes
779 779 commit = modified + added
780 780 remove = removed
781 781 else:
782 782 commit = files
783 783
784 784 if use_dirstate:
785 785 p1, p2 = self.dirstate.parents()
786 786 update_dirstate = True
787 787
788 788 if (not force and p2 != nullid and
789 789 (match.files() or match.anypats())):
790 790 raise util.Abort(_('cannot partially commit a merge '
791 791 '(do not specify files or patterns)'))
792 792 else:
793 793 p1, p2 = p1, p2 or nullid
794 794 update_dirstate = (self.dirstate.parents()[0] == p1)
795 795
796 796 c1 = self.changelog.read(p1)
797 797 c2 = self.changelog.read(p2)
798 798 m1 = self.manifest.read(c1[0]).copy()
799 799 m2 = self.manifest.read(c2[0])
800 800
801 801 if use_dirstate:
802 802 branchname = self.workingctx().branch()
803 803 try:
804 804 branchname = branchname.decode('UTF-8').encode('UTF-8')
805 805 except UnicodeDecodeError:
806 806 raise util.Abort(_('branch name not in UTF-8!'))
807 807 else:
808 808 branchname = ""
809 809
810 810 if use_dirstate:
811 811 oldname = c1[5].get("branch") # stored in UTF-8
812 812 if (not commit and not remove and not force and p2 == nullid
813 813 and branchname == oldname):
814 814 self.ui.status(_("nothing changed\n"))
815 815 return None
816 816
817 817 xp1 = hex(p1)
818 818 if p2 == nullid: xp2 = ''
819 819 else: xp2 = hex(p2)
820 820
821 821 self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
822 822
823 823 tr = self.transaction()
824 824 trp = weakref.proxy(tr)
825 825
826 826 # check in files
827 827 new = {}
828 828 linkrev = self.changelog.count()
829 829 commit.sort()
830 830 is_exec = util.execfunc(self.root, m1.execf)
831 831 is_link = util.linkfunc(self.root, m1.linkf)
832 832 for f in commit:
833 833 self.ui.note(f + "\n")
834 834 try:
835 835 new[f] = self.filecommit(f, m1, m2, linkrev, trp, changed)
836 836 new_exec = is_exec(f)
837 837 new_link = is_link(f)
838 838 if ((not changed or changed[-1] != f) and
839 839 m2.get(f) != new[f]):
840 840 # mention the file in the changelog if some
841 841 # flag changed, even if there was no content
842 842 # change.
843 843 old_exec = m1.execf(f)
844 844 old_link = m1.linkf(f)
845 845 if old_exec != new_exec or old_link != new_link:
846 846 changed.append(f)
847 847 m1.set(f, new_exec, new_link)
848 848 if use_dirstate:
849 849 self.dirstate.normal(f)
850 850
851 851 except (OSError, IOError):
852 852 if use_dirstate:
853 853 self.ui.warn(_("trouble committing %s!\n") % f)
854 854 raise
855 855 else:
856 856 remove.append(f)
857 857
858 858 # update manifest
859 859 m1.update(new)
860 860 remove.sort()
861 861 removed = []
862 862
863 863 for f in remove:
864 864 if f in m1:
865 865 del m1[f]
866 866 removed.append(f)
867 867 elif f in m2:
868 868 removed.append(f)
869 869 mn = self.manifest.add(m1, trp, linkrev, c1[0], c2[0],
870 870 (new, removed))
871 871
872 872 # add changeset
873 873 new = new.keys()
874 874 new.sort()
875 875
876 876 user = user or self.ui.username()
877 877 if (not empty_ok and not text) or force_editor:
878 878 edittext = []
879 879 if text:
880 880 edittext.append(text)
881 881 edittext.append("")
882 882 edittext.append(_("HG: Enter commit message."
883 883 " Lines beginning with 'HG:' are removed."))
884 884 edittext.append("HG: --")
885 885 edittext.append("HG: user: %s" % user)
886 886 if p2 != nullid:
887 887 edittext.append("HG: branch merge")
888 888 if branchname:
889 889 edittext.append("HG: branch '%s'" % util.tolocal(branchname))
890 890 edittext.extend(["HG: changed %s" % f for f in changed])
891 891 edittext.extend(["HG: removed %s" % f for f in removed])
892 892 if not changed and not remove:
893 893 edittext.append("HG: no files changed")
894 894 edittext.append("")
895 895 # run editor in the repository root
896 896 olddir = os.getcwd()
897 897 os.chdir(self.root)
898 898 text = self.ui.edit("\n".join(edittext), user)
899 899 os.chdir(olddir)
900 900
901 901 if branchname:
902 902 extra["branch"] = branchname
903 903
904 904 lines = [line.rstrip() for line in text.rstrip().splitlines()]
905 905 while lines and not lines[0]:
906 906 del lines[0]
907 907 if not lines and use_dirstate:
908 908 raise util.Abort(_("empty commit message"))
909 909 text = '\n'.join(lines)
910 910
911 911 n = self.changelog.add(mn, changed + removed, text, trp, p1, p2,
912 912 user, date, extra)
913 913 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
914 914 parent2=xp2)
915 915 tr.close()
916 916
917 917 if self.branchcache:
918 918 self.branchtags()
919 919
920 920 if use_dirstate or update_dirstate:
921 921 self.dirstate.setparents(n)
922 922 if use_dirstate:
923 923 for f in removed:
924 924 self.dirstate.forget(f)
925 925 valid = 1 # our dirstate updates are complete
926 926
927 927 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
928 928 return n
929 929 finally:
930 930 if not valid: # don't save our updated dirstate
931 931 self.dirstate.invalidate()
932 932 del tr, lock, wlock
933 933
934 def walk(self, node, match):
934 def walk(self, match, node=None):
935 935 '''
936 936 walk recursively through the directory tree or a given
937 937 changeset, finding all files matched by the match
938 938 function
939 939
940 940 results are yielded in a tuple (src, filename), where src
941 941 is one of:
942 942 'f' the file was found in the directory tree
943 943 'm' the file was only in the dirstate and not in the tree
944 944 '''
945 945
946 946 if node:
947 947 fdict = dict.fromkeys(match.files())
948 948 # for dirstate.walk, files=['.'] means "walk the whole tree".
949 949 # follow that here, too
950 950 fdict.pop('.', None)
951 951 mdict = self.manifest.read(self.changelog.read(node)[0])
952 952 mfiles = mdict.keys()
953 953 mfiles.sort()
954 954 for fn in mfiles:
955 955 for ffn in fdict:
956 956 # match if the file is the exact name or a directory
957 957 if ffn == fn or fn.startswith("%s/" % ffn):
958 958 del fdict[ffn]
959 959 break
960 960 if match(fn):
961 961 yield 'm', fn
962 962 ffiles = fdict.keys()
963 963 ffiles.sort()
964 964 for fn in ffiles:
965 965 if match.bad(fn, 'No such file in rev ' + short(node)) \
966 966 and match(fn):
967 967 yield 'f', fn
968 968 else:
969 969 for src, fn in self.dirstate.walk(match):
970 970 yield src, fn
971 971
972 972 def status(self, node1=None, node2=None, files=[], match=util.always,
973 973 list_ignored=False, list_clean=False, list_unknown=True):
974 974 """return status of files between two nodes or node and working directory
975 975
976 976 If node1 is None, use the first dirstate parent instead.
977 977 If node2 is None, compare node1 with working directory.
978 978 """
979 979
980 980 def fcmp(fn, getnode):
981 981 t1 = self.wread(fn)
982 982 return self.file(fn).cmp(getnode(fn), t1)
983 983
984 984 def mfmatches(node):
985 985 change = self.changelog.read(node)
986 986 mf = self.manifest.read(change[0]).copy()
987 987 for fn in mf.keys():
988 988 if not match(fn):
989 989 del mf[fn]
990 990 return mf
991 991
992 992 modified, added, removed, deleted, unknown = [], [], [], [], []
993 993 ignored, clean = [], []
994 994
995 995 compareworking = False
996 996 if not node1 or (not node2 and node1 == self.dirstate.parents()[0]):
997 997 compareworking = True
998 998
999 999 if not compareworking:
1000 1000 # read the manifest from node1 before the manifest from node2,
1001 1001 # so that we'll hit the manifest cache if we're going through
1002 1002 # all the revisions in parent->child order.
1003 1003 mf1 = mfmatches(node1)
1004 1004
1005 1005 # are we comparing the working directory?
1006 1006 if not node2:
1007 1007 (lookup, modified, added, removed, deleted, unknown,
1008 1008 ignored, clean) = self.dirstate.status(files, match,
1009 1009 list_ignored, list_clean,
1010 1010 list_unknown)
1011 1011
1012 1012 # are we comparing working dir against its parent?
1013 1013 if compareworking:
1014 1014 if lookup:
1015 1015 fixup = []
1016 1016 # do a full compare of any files that might have changed
1017 1017 ctx = self.changectx()
1018 1018 mexec = lambda f: 'x' in ctx.fileflags(f)
1019 1019 mlink = lambda f: 'l' in ctx.fileflags(f)
1020 1020 is_exec = util.execfunc(self.root, mexec)
1021 1021 is_link = util.linkfunc(self.root, mlink)
1022 1022 def flags(f):
1023 1023 return is_link(f) and 'l' or is_exec(f) and 'x' or ''
1024 1024 for f in lookup:
1025 1025 if (f not in ctx or flags(f) != ctx.fileflags(f)
1026 1026 or ctx[f].cmp(self.wread(f))):
1027 1027 modified.append(f)
1028 1028 else:
1029 1029 fixup.append(f)
1030 1030 if list_clean:
1031 1031 clean.append(f)
1032 1032
1033 1033 # update dirstate for files that are actually clean
1034 1034 if fixup:
1035 1035 wlock = None
1036 1036 try:
1037 1037 try:
1038 1038 wlock = self.wlock(False)
1039 1039 except lock.LockException:
1040 1040 pass
1041 1041 if wlock:
1042 1042 for f in fixup:
1043 1043 self.dirstate.normal(f)
1044 1044 finally:
1045 1045 del wlock
1046 1046 else:
1047 1047 # we are comparing working dir against non-parent
1048 1048 # generate a pseudo-manifest for the working dir
1049 1049 # XXX: create it in dirstate.py ?
1050 1050 mf2 = mfmatches(self.dirstate.parents()[0])
1051 1051 is_exec = util.execfunc(self.root, mf2.execf)
1052 1052 is_link = util.linkfunc(self.root, mf2.linkf)
1053 1053 for f in lookup + modified + added:
1054 1054 mf2[f] = ""
1055 1055 mf2.set(f, is_exec(f), is_link(f))
1056 1056 for f in removed:
1057 1057 if f in mf2:
1058 1058 del mf2[f]
1059 1059
1060 1060 else:
1061 1061 # we are comparing two revisions
1062 1062 mf2 = mfmatches(node2)
1063 1063
1064 1064 if not compareworking:
1065 1065 # flush lists from dirstate before comparing manifests
1066 1066 modified, added, clean = [], [], []
1067 1067
1068 1068 # make sure to sort the files so we talk to the disk in a
1069 1069 # reasonable order
1070 1070 mf2keys = mf2.keys()
1071 1071 mf2keys.sort()
1072 1072 getnode = lambda fn: mf1.get(fn, nullid)
1073 1073 for fn in mf2keys:
1074 1074 if fn in mf1:
1075 1075 if (mf1.flags(fn) != mf2.flags(fn) or
1076 1076 (mf1[fn] != mf2[fn] and
1077 1077 (mf2[fn] != "" or fcmp(fn, getnode)))):
1078 1078 modified.append(fn)
1079 1079 elif list_clean:
1080 1080 clean.append(fn)
1081 1081 del mf1[fn]
1082 1082 else:
1083 1083 added.append(fn)
1084 1084
1085 1085 removed = mf1.keys()
1086 1086
1087 1087 # sort and return results:
1088 1088 for l in modified, added, removed, deleted, unknown, ignored, clean:
1089 1089 l.sort()
1090 1090 return (modified, added, removed, deleted, unknown, ignored, clean)
1091 1091
1092 1092 def add(self, list):
1093 1093 wlock = self.wlock()
1094 1094 try:
1095 1095 rejected = []
1096 1096 for f in list:
1097 1097 p = self.wjoin(f)
1098 1098 try:
1099 1099 st = os.lstat(p)
1100 1100 except:
1101 1101 self.ui.warn(_("%s does not exist!\n") % f)
1102 1102 rejected.append(f)
1103 1103 continue
1104 1104 if st.st_size > 10000000:
1105 1105 self.ui.warn(_("%s: files over 10MB may cause memory and"
1106 1106 " performance problems\n"
1107 1107 "(use 'hg revert %s' to unadd the file)\n")
1108 1108 % (f, f))
1109 1109 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1110 1110 self.ui.warn(_("%s not added: only files and symlinks "
1111 1111 "supported currently\n") % f)
1112 1112 rejected.append(p)
1113 1113 elif self.dirstate[f] in 'amn':
1114 1114 self.ui.warn(_("%s already tracked!\n") % f)
1115 1115 elif self.dirstate[f] == 'r':
1116 1116 self.dirstate.normallookup(f)
1117 1117 else:
1118 1118 self.dirstate.add(f)
1119 1119 return rejected
1120 1120 finally:
1121 1121 del wlock
1122 1122
1123 1123 def forget(self, list):
1124 1124 wlock = self.wlock()
1125 1125 try:
1126 1126 for f in list:
1127 1127 if self.dirstate[f] != 'a':
1128 1128 self.ui.warn(_("%s not added!\n") % f)
1129 1129 else:
1130 1130 self.dirstate.forget(f)
1131 1131 finally:
1132 1132 del wlock
1133 1133
1134 1134 def remove(self, list, unlink=False):
1135 1135 wlock = None
1136 1136 try:
1137 1137 if unlink:
1138 1138 for f in list:
1139 1139 try:
1140 1140 util.unlink(self.wjoin(f))
1141 1141 except OSError, inst:
1142 1142 if inst.errno != errno.ENOENT:
1143 1143 raise
1144 1144 wlock = self.wlock()
1145 1145 for f in list:
1146 1146 if unlink and os.path.exists(self.wjoin(f)):
1147 1147 self.ui.warn(_("%s still exists!\n") % f)
1148 1148 elif self.dirstate[f] == 'a':
1149 1149 self.dirstate.forget(f)
1150 1150 elif f not in self.dirstate:
1151 1151 self.ui.warn(_("%s not tracked!\n") % f)
1152 1152 else:
1153 1153 self.dirstate.remove(f)
1154 1154 finally:
1155 1155 del wlock
1156 1156
1157 1157 def undelete(self, list):
1158 1158 wlock = None
1159 1159 try:
1160 1160 manifests = [self.manifest.read(self.changelog.read(p)[0])
1161 1161 for p in self.dirstate.parents() if p != nullid]
1162 1162 wlock = self.wlock()
1163 1163 for f in list:
1164 1164 if self.dirstate[f] != 'r':
1165 1165 self.ui.warn("%s not removed!\n" % f)
1166 1166 else:
1167 1167 m = f in manifests[0] and manifests[0] or manifests[1]
1168 1168 t = self.file(f).read(m[f])
1169 1169 self.wwrite(f, t, m.flags(f))
1170 1170 self.dirstate.normal(f)
1171 1171 finally:
1172 1172 del wlock
1173 1173
1174 1174 def copy(self, source, dest):
1175 1175 wlock = None
1176 1176 try:
1177 1177 p = self.wjoin(dest)
1178 1178 if not (os.path.exists(p) or os.path.islink(p)):
1179 1179 self.ui.warn(_("%s does not exist!\n") % dest)
1180 1180 elif not (os.path.isfile(p) or os.path.islink(p)):
1181 1181 self.ui.warn(_("copy failed: %s is not a file or a "
1182 1182 "symbolic link\n") % dest)
1183 1183 else:
1184 1184 wlock = self.wlock()
1185 1185 if dest not in self.dirstate:
1186 1186 self.dirstate.add(dest)
1187 1187 self.dirstate.copy(source, dest)
1188 1188 finally:
1189 1189 del wlock
1190 1190
1191 1191 def heads(self, start=None):
1192 1192 heads = self.changelog.heads(start)
1193 1193 # sort the output in rev descending order
1194 1194 heads = [(-self.changelog.rev(h), h) for h in heads]
1195 1195 heads.sort()
1196 1196 return [n for (r, n) in heads]
1197 1197
1198 1198 def branchheads(self, branch, start=None):
1199 1199 branches = self.branchtags()
1200 1200 if branch not in branches:
1201 1201 return []
1202 1202 # The basic algorithm is this:
1203 1203 #
1204 1204 # Start from the branch tip since there are no later revisions that can
1205 1205 # possibly be in this branch, and the tip is a guaranteed head.
1206 1206 #
1207 1207 # Remember the tip's parents as the first ancestors, since these by
1208 1208 # definition are not heads.
1209 1209 #
1210 1210 # Step backwards from the brach tip through all the revisions. We are
1211 1211 # guaranteed by the rules of Mercurial that we will now be visiting the
1212 1212 # nodes in reverse topological order (children before parents).
1213 1213 #
1214 1214 # If a revision is one of the ancestors of a head then we can toss it
1215 1215 # out of the ancestors set (we've already found it and won't be
1216 1216 # visiting it again) and put its parents in the ancestors set.
1217 1217 #
1218 1218 # Otherwise, if a revision is in the branch it's another head, since it
1219 1219 # wasn't in the ancestor list of an existing head. So add it to the
1220 1220 # head list, and add its parents to the ancestor list.
1221 1221 #
1222 1222 # If it is not in the branch ignore it.
1223 1223 #
1224 1224 # Once we have a list of heads, use nodesbetween to filter out all the
1225 1225 # heads that cannot be reached from startrev. There may be a more
1226 1226 # efficient way to do this as part of the previous algorithm.
1227 1227
1228 1228 set = util.set
1229 1229 heads = [self.changelog.rev(branches[branch])]
1230 1230 # Don't care if ancestors contains nullrev or not.
1231 1231 ancestors = set(self.changelog.parentrevs(heads[0]))
1232 1232 for rev in xrange(heads[0] - 1, nullrev, -1):
1233 1233 if rev in ancestors:
1234 1234 ancestors.update(self.changelog.parentrevs(rev))
1235 1235 ancestors.remove(rev)
1236 1236 elif self.changectx(rev).branch() == branch:
1237 1237 heads.append(rev)
1238 1238 ancestors.update(self.changelog.parentrevs(rev))
1239 1239 heads = [self.changelog.node(rev) for rev in heads]
1240 1240 if start is not None:
1241 1241 heads = self.changelog.nodesbetween([start], heads)[2]
1242 1242 return heads
1243 1243
1244 1244 def branches(self, nodes):
1245 1245 if not nodes:
1246 1246 nodes = [self.changelog.tip()]
1247 1247 b = []
1248 1248 for n in nodes:
1249 1249 t = n
1250 1250 while 1:
1251 1251 p = self.changelog.parents(n)
1252 1252 if p[1] != nullid or p[0] == nullid:
1253 1253 b.append((t, n, p[0], p[1]))
1254 1254 break
1255 1255 n = p[0]
1256 1256 return b
1257 1257
1258 1258 def between(self, pairs):
1259 1259 r = []
1260 1260
1261 1261 for top, bottom in pairs:
1262 1262 n, l, i = top, [], 0
1263 1263 f = 1
1264 1264
1265 1265 while n != bottom:
1266 1266 p = self.changelog.parents(n)[0]
1267 1267 if i == f:
1268 1268 l.append(n)
1269 1269 f = f * 2
1270 1270 n = p
1271 1271 i += 1
1272 1272
1273 1273 r.append(l)
1274 1274
1275 1275 return r
1276 1276
1277 1277 def findincoming(self, remote, base=None, heads=None, force=False):
1278 1278 """Return list of roots of the subsets of missing nodes from remote
1279 1279
1280 1280 If base dict is specified, assume that these nodes and their parents
1281 1281 exist on the remote side and that no child of a node of base exists
1282 1282 in both remote and self.
1283 1283 Furthermore base will be updated to include the nodes that exists
1284 1284 in self and remote but no children exists in self and remote.
1285 1285 If a list of heads is specified, return only nodes which are heads
1286 1286 or ancestors of these heads.
1287 1287
1288 1288 All the ancestors of base are in self and in remote.
1289 1289 All the descendants of the list returned are missing in self.
1290 1290 (and so we know that the rest of the nodes are missing in remote, see
1291 1291 outgoing)
1292 1292 """
1293 1293 m = self.changelog.nodemap
1294 1294 search = []
1295 1295 fetch = {}
1296 1296 seen = {}
1297 1297 seenbranch = {}
1298 1298 if base == None:
1299 1299 base = {}
1300 1300
1301 1301 if not heads:
1302 1302 heads = remote.heads()
1303 1303
1304 1304 if self.changelog.tip() == nullid:
1305 1305 base[nullid] = 1
1306 1306 if heads != [nullid]:
1307 1307 return [nullid]
1308 1308 return []
1309 1309
1310 1310 # assume we're closer to the tip than the root
1311 1311 # and start by examining the heads
1312 1312 self.ui.status(_("searching for changes\n"))
1313 1313
1314 1314 unknown = []
1315 1315 for h in heads:
1316 1316 if h not in m:
1317 1317 unknown.append(h)
1318 1318 else:
1319 1319 base[h] = 1
1320 1320
1321 1321 if not unknown:
1322 1322 return []
1323 1323
1324 1324 req = dict.fromkeys(unknown)
1325 1325 reqcnt = 0
1326 1326
1327 1327 # search through remote branches
1328 1328 # a 'branch' here is a linear segment of history, with four parts:
1329 1329 # head, root, first parent, second parent
1330 1330 # (a branch always has two parents (or none) by definition)
1331 1331 unknown = remote.branches(unknown)
1332 1332 while unknown:
1333 1333 r = []
1334 1334 while unknown:
1335 1335 n = unknown.pop(0)
1336 1336 if n[0] in seen:
1337 1337 continue
1338 1338
1339 1339 self.ui.debug(_("examining %s:%s\n")
1340 1340 % (short(n[0]), short(n[1])))
1341 1341 if n[0] == nullid: # found the end of the branch
1342 1342 pass
1343 1343 elif n in seenbranch:
1344 1344 self.ui.debug(_("branch already found\n"))
1345 1345 continue
1346 1346 elif n[1] and n[1] in m: # do we know the base?
1347 1347 self.ui.debug(_("found incomplete branch %s:%s\n")
1348 1348 % (short(n[0]), short(n[1])))
1349 1349 search.append(n) # schedule branch range for scanning
1350 1350 seenbranch[n] = 1
1351 1351 else:
1352 1352 if n[1] not in seen and n[1] not in fetch:
1353 1353 if n[2] in m and n[3] in m:
1354 1354 self.ui.debug(_("found new changeset %s\n") %
1355 1355 short(n[1]))
1356 1356 fetch[n[1]] = 1 # earliest unknown
1357 1357 for p in n[2:4]:
1358 1358 if p in m:
1359 1359 base[p] = 1 # latest known
1360 1360
1361 1361 for p in n[2:4]:
1362 1362 if p not in req and p not in m:
1363 1363 r.append(p)
1364 1364 req[p] = 1
1365 1365 seen[n[0]] = 1
1366 1366
1367 1367 if r:
1368 1368 reqcnt += 1
1369 1369 self.ui.debug(_("request %d: %s\n") %
1370 1370 (reqcnt, " ".join(map(short, r))))
1371 1371 for p in xrange(0, len(r), 10):
1372 1372 for b in remote.branches(r[p:p+10]):
1373 1373 self.ui.debug(_("received %s:%s\n") %
1374 1374 (short(b[0]), short(b[1])))
1375 1375 unknown.append(b)
1376 1376
1377 1377 # do binary search on the branches we found
1378 1378 while search:
1379 1379 n = search.pop(0)
1380 1380 reqcnt += 1
1381 1381 l = remote.between([(n[0], n[1])])[0]
1382 1382 l.append(n[1])
1383 1383 p = n[0]
1384 1384 f = 1
1385 1385 for i in l:
1386 1386 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
1387 1387 if i in m:
1388 1388 if f <= 2:
1389 1389 self.ui.debug(_("found new branch changeset %s\n") %
1390 1390 short(p))
1391 1391 fetch[p] = 1
1392 1392 base[i] = 1
1393 1393 else:
1394 1394 self.ui.debug(_("narrowed branch search to %s:%s\n")
1395 1395 % (short(p), short(i)))
1396 1396 search.append((p, i))
1397 1397 break
1398 1398 p, f = i, f * 2
1399 1399
1400 1400 # sanity check our fetch list
1401 1401 for f in fetch.keys():
1402 1402 if f in m:
1403 1403 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
1404 1404
1405 1405 if base.keys() == [nullid]:
1406 1406 if force:
1407 1407 self.ui.warn(_("warning: repository is unrelated\n"))
1408 1408 else:
1409 1409 raise util.Abort(_("repository is unrelated"))
1410 1410
1411 1411 self.ui.debug(_("found new changesets starting at ") +
1412 1412 " ".join([short(f) for f in fetch]) + "\n")
1413 1413
1414 1414 self.ui.debug(_("%d total queries\n") % reqcnt)
1415 1415
1416 1416 return fetch.keys()
1417 1417
1418 1418 def findoutgoing(self, remote, base=None, heads=None, force=False):
1419 1419 """Return list of nodes that are roots of subsets not in remote
1420 1420
1421 1421 If base dict is specified, assume that these nodes and their parents
1422 1422 exist on the remote side.
1423 1423 If a list of heads is specified, return only nodes which are heads
1424 1424 or ancestors of these heads, and return a second element which
1425 1425 contains all remote heads which get new children.
1426 1426 """
1427 1427 if base == None:
1428 1428 base = {}
1429 1429 self.findincoming(remote, base, heads, force=force)
1430 1430
1431 1431 self.ui.debug(_("common changesets up to ")
1432 1432 + " ".join(map(short, base.keys())) + "\n")
1433 1433
1434 1434 remain = dict.fromkeys(self.changelog.nodemap)
1435 1435
1436 1436 # prune everything remote has from the tree
1437 1437 del remain[nullid]
1438 1438 remove = base.keys()
1439 1439 while remove:
1440 1440 n = remove.pop(0)
1441 1441 if n in remain:
1442 1442 del remain[n]
1443 1443 for p in self.changelog.parents(n):
1444 1444 remove.append(p)
1445 1445
1446 1446 # find every node whose parents have been pruned
1447 1447 subset = []
1448 1448 # find every remote head that will get new children
1449 1449 updated_heads = {}
1450 1450 for n in remain:
1451 1451 p1, p2 = self.changelog.parents(n)
1452 1452 if p1 not in remain and p2 not in remain:
1453 1453 subset.append(n)
1454 1454 if heads:
1455 1455 if p1 in heads:
1456 1456 updated_heads[p1] = True
1457 1457 if p2 in heads:
1458 1458 updated_heads[p2] = True
1459 1459
1460 1460 # this is the set of all roots we have to push
1461 1461 if heads:
1462 1462 return subset, updated_heads.keys()
1463 1463 else:
1464 1464 return subset
1465 1465
1466 1466 def pull(self, remote, heads=None, force=False):
1467 1467 lock = self.lock()
1468 1468 try:
1469 1469 fetch = self.findincoming(remote, heads=heads, force=force)
1470 1470 if fetch == [nullid]:
1471 1471 self.ui.status(_("requesting all changes\n"))
1472 1472
1473 1473 if not fetch:
1474 1474 self.ui.status(_("no changes found\n"))
1475 1475 return 0
1476 1476
1477 1477 if heads is None:
1478 1478 cg = remote.changegroup(fetch, 'pull')
1479 1479 else:
1480 1480 if 'changegroupsubset' not in remote.capabilities:
1481 1481 raise util.Abort(_("Partial pull cannot be done because other repository doesn't support changegroupsubset."))
1482 1482 cg = remote.changegroupsubset(fetch, heads, 'pull')
1483 1483 return self.addchangegroup(cg, 'pull', remote.url())
1484 1484 finally:
1485 1485 del lock
1486 1486
1487 1487 def push(self, remote, force=False, revs=None):
1488 1488 # there are two ways to push to remote repo:
1489 1489 #
1490 1490 # addchangegroup assumes local user can lock remote
1491 1491 # repo (local filesystem, old ssh servers).
1492 1492 #
1493 1493 # unbundle assumes local user cannot lock remote repo (new ssh
1494 1494 # servers, http servers).
1495 1495
1496 1496 if remote.capable('unbundle'):
1497 1497 return self.push_unbundle(remote, force, revs)
1498 1498 return self.push_addchangegroup(remote, force, revs)
1499 1499
1500 1500 def prepush(self, remote, force, revs):
1501 1501 base = {}
1502 1502 remote_heads = remote.heads()
1503 1503 inc = self.findincoming(remote, base, remote_heads, force=force)
1504 1504
1505 1505 update, updated_heads = self.findoutgoing(remote, base, remote_heads)
1506 1506 if revs is not None:
1507 1507 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
1508 1508 else:
1509 1509 bases, heads = update, self.changelog.heads()
1510 1510
1511 1511 if not bases:
1512 1512 self.ui.status(_("no changes found\n"))
1513 1513 return None, 1
1514 1514 elif not force:
1515 1515 # check if we're creating new remote heads
1516 1516 # to be a remote head after push, node must be either
1517 1517 # - unknown locally
1518 1518 # - a local outgoing head descended from update
1519 1519 # - a remote head that's known locally and not
1520 1520 # ancestral to an outgoing head
1521 1521
1522 1522 warn = 0
1523 1523
1524 1524 if remote_heads == [nullid]:
1525 1525 warn = 0
1526 1526 elif not revs and len(heads) > len(remote_heads):
1527 1527 warn = 1
1528 1528 else:
1529 1529 newheads = list(heads)
1530 1530 for r in remote_heads:
1531 1531 if r in self.changelog.nodemap:
1532 1532 desc = self.changelog.heads(r, heads)
1533 1533 l = [h for h in heads if h in desc]
1534 1534 if not l:
1535 1535 newheads.append(r)
1536 1536 else:
1537 1537 newheads.append(r)
1538 1538 if len(newheads) > len(remote_heads):
1539 1539 warn = 1
1540 1540
1541 1541 if warn:
1542 1542 self.ui.warn(_("abort: push creates new remote heads!\n"))
1543 1543 self.ui.status(_("(did you forget to merge?"
1544 1544 " use push -f to force)\n"))
1545 1545 return None, 0
1546 1546 elif inc:
1547 1547 self.ui.warn(_("note: unsynced remote changes!\n"))
1548 1548
1549 1549
1550 1550 if revs is None:
1551 1551 cg = self.changegroup(update, 'push')
1552 1552 else:
1553 1553 cg = self.changegroupsubset(update, revs, 'push')
1554 1554 return cg, remote_heads
1555 1555
1556 1556 def push_addchangegroup(self, remote, force, revs):
1557 1557 lock = remote.lock()
1558 1558 try:
1559 1559 ret = self.prepush(remote, force, revs)
1560 1560 if ret[0] is not None:
1561 1561 cg, remote_heads = ret
1562 1562 return remote.addchangegroup(cg, 'push', self.url())
1563 1563 return ret[1]
1564 1564 finally:
1565 1565 del lock
1566 1566
1567 1567 def push_unbundle(self, remote, force, revs):
1568 1568 # local repo finds heads on server, finds out what revs it
1569 1569 # must push. once revs transferred, if server finds it has
1570 1570 # different heads (someone else won commit/push race), server
1571 1571 # aborts.
1572 1572
1573 1573 ret = self.prepush(remote, force, revs)
1574 1574 if ret[0] is not None:
1575 1575 cg, remote_heads = ret
1576 1576 if force: remote_heads = ['force']
1577 1577 return remote.unbundle(cg, remote_heads, 'push')
1578 1578 return ret[1]
1579 1579
1580 1580 def changegroupinfo(self, nodes, source):
1581 1581 if self.ui.verbose or source == 'bundle':
1582 1582 self.ui.status(_("%d changesets found\n") % len(nodes))
1583 1583 if self.ui.debugflag:
1584 1584 self.ui.debug(_("List of changesets:\n"))
1585 1585 for node in nodes:
1586 1586 self.ui.debug("%s\n" % hex(node))
1587 1587
1588 1588 def changegroupsubset(self, bases, heads, source, extranodes=None):
1589 1589 """This function generates a changegroup consisting of all the nodes
1590 1590 that are descendents of any of the bases, and ancestors of any of
1591 1591 the heads.
1592 1592
1593 1593 It is fairly complex as determining which filenodes and which
1594 1594 manifest nodes need to be included for the changeset to be complete
1595 1595 is non-trivial.
1596 1596
1597 1597 Another wrinkle is doing the reverse, figuring out which changeset in
1598 1598 the changegroup a particular filenode or manifestnode belongs to.
1599 1599
1600 1600 The caller can specify some nodes that must be included in the
1601 1601 changegroup using the extranodes argument. It should be a dict
1602 1602 where the keys are the filenames (or 1 for the manifest), and the
1603 1603 values are lists of (node, linknode) tuples, where node is a wanted
1604 1604 node and linknode is the changelog node that should be transmitted as
1605 1605 the linkrev.
1606 1606 """
1607 1607
1608 1608 self.hook('preoutgoing', throw=True, source=source)
1609 1609
1610 1610 # Set up some initial variables
1611 1611 # Make it easy to refer to self.changelog
1612 1612 cl = self.changelog
1613 1613 # msng is short for missing - compute the list of changesets in this
1614 1614 # changegroup.
1615 1615 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1616 1616 self.changegroupinfo(msng_cl_lst, source)
1617 1617 # Some bases may turn out to be superfluous, and some heads may be
1618 1618 # too. nodesbetween will return the minimal set of bases and heads
1619 1619 # necessary to re-create the changegroup.
1620 1620
1621 1621 # Known heads are the list of heads that it is assumed the recipient
1622 1622 # of this changegroup will know about.
1623 1623 knownheads = {}
1624 1624 # We assume that all parents of bases are known heads.
1625 1625 for n in bases:
1626 1626 for p in cl.parents(n):
1627 1627 if p != nullid:
1628 1628 knownheads[p] = 1
1629 1629 knownheads = knownheads.keys()
1630 1630 if knownheads:
1631 1631 # Now that we know what heads are known, we can compute which
1632 1632 # changesets are known. The recipient must know about all
1633 1633 # changesets required to reach the known heads from the null
1634 1634 # changeset.
1635 1635 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1636 1636 junk = None
1637 1637 # Transform the list into an ersatz set.
1638 1638 has_cl_set = dict.fromkeys(has_cl_set)
1639 1639 else:
1640 1640 # If there were no known heads, the recipient cannot be assumed to
1641 1641 # know about any changesets.
1642 1642 has_cl_set = {}
1643 1643
1644 1644 # Make it easy to refer to self.manifest
1645 1645 mnfst = self.manifest
1646 1646 # We don't know which manifests are missing yet
1647 1647 msng_mnfst_set = {}
1648 1648 # Nor do we know which filenodes are missing.
1649 1649 msng_filenode_set = {}
1650 1650
1651 1651 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1652 1652 junk = None
1653 1653
1654 1654 # A changeset always belongs to itself, so the changenode lookup
1655 1655 # function for a changenode is identity.
1656 1656 def identity(x):
1657 1657 return x
1658 1658
1659 1659 # A function generating function. Sets up an environment for the
1660 1660 # inner function.
1661 1661 def cmp_by_rev_func(revlog):
1662 1662 # Compare two nodes by their revision number in the environment's
1663 1663 # revision history. Since the revision number both represents the
1664 1664 # most efficient order to read the nodes in, and represents a
1665 1665 # topological sorting of the nodes, this function is often useful.
1666 1666 def cmp_by_rev(a, b):
1667 1667 return cmp(revlog.rev(a), revlog.rev(b))
1668 1668 return cmp_by_rev
1669 1669
1670 1670 # If we determine that a particular file or manifest node must be a
1671 1671 # node that the recipient of the changegroup will already have, we can
1672 1672 # also assume the recipient will have all the parents. This function
1673 1673 # prunes them from the set of missing nodes.
1674 1674 def prune_parents(revlog, hasset, msngset):
1675 1675 haslst = hasset.keys()
1676 1676 haslst.sort(cmp_by_rev_func(revlog))
1677 1677 for node in haslst:
1678 1678 parentlst = [p for p in revlog.parents(node) if p != nullid]
1679 1679 while parentlst:
1680 1680 n = parentlst.pop()
1681 1681 if n not in hasset:
1682 1682 hasset[n] = 1
1683 1683 p = [p for p in revlog.parents(n) if p != nullid]
1684 1684 parentlst.extend(p)
1685 1685 for n in hasset:
1686 1686 msngset.pop(n, None)
1687 1687
1688 1688 # This is a function generating function used to set up an environment
1689 1689 # for the inner function to execute in.
1690 1690 def manifest_and_file_collector(changedfileset):
1691 1691 # This is an information gathering function that gathers
1692 1692 # information from each changeset node that goes out as part of
1693 1693 # the changegroup. The information gathered is a list of which
1694 1694 # manifest nodes are potentially required (the recipient may
1695 1695 # already have them) and total list of all files which were
1696 1696 # changed in any changeset in the changegroup.
1697 1697 #
1698 1698 # We also remember the first changenode we saw any manifest
1699 1699 # referenced by so we can later determine which changenode 'owns'
1700 1700 # the manifest.
1701 1701 def collect_manifests_and_files(clnode):
1702 1702 c = cl.read(clnode)
1703 1703 for f in c[3]:
1704 1704 # This is to make sure we only have one instance of each
1705 1705 # filename string for each filename.
1706 1706 changedfileset.setdefault(f, f)
1707 1707 msng_mnfst_set.setdefault(c[0], clnode)
1708 1708 return collect_manifests_and_files
1709 1709
1710 1710 # Figure out which manifest nodes (of the ones we think might be part
1711 1711 # of the changegroup) the recipient must know about and remove them
1712 1712 # from the changegroup.
1713 1713 def prune_manifests():
1714 1714 has_mnfst_set = {}
1715 1715 for n in msng_mnfst_set:
1716 1716 # If a 'missing' manifest thinks it belongs to a changenode
1717 1717 # the recipient is assumed to have, obviously the recipient
1718 1718 # must have that manifest.
1719 1719 linknode = cl.node(mnfst.linkrev(n))
1720 1720 if linknode in has_cl_set:
1721 1721 has_mnfst_set[n] = 1
1722 1722 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1723 1723
1724 1724 # Use the information collected in collect_manifests_and_files to say
1725 1725 # which changenode any manifestnode belongs to.
1726 1726 def lookup_manifest_link(mnfstnode):
1727 1727 return msng_mnfst_set[mnfstnode]
1728 1728
1729 1729 # A function generating function that sets up the initial environment
1730 1730 # the inner function.
1731 1731 def filenode_collector(changedfiles):
1732 1732 next_rev = [0]
1733 1733 # This gathers information from each manifestnode included in the
1734 1734 # changegroup about which filenodes the manifest node references
1735 1735 # so we can include those in the changegroup too.
1736 1736 #
1737 1737 # It also remembers which changenode each filenode belongs to. It
1738 1738 # does this by assuming the a filenode belongs to the changenode
1739 1739 # the first manifest that references it belongs to.
1740 1740 def collect_msng_filenodes(mnfstnode):
1741 1741 r = mnfst.rev(mnfstnode)
1742 1742 if r == next_rev[0]:
1743 1743 # If the last rev we looked at was the one just previous,
1744 1744 # we only need to see a diff.
1745 1745 deltamf = mnfst.readdelta(mnfstnode)
1746 1746 # For each line in the delta
1747 1747 for f, fnode in deltamf.items():
1748 1748 f = changedfiles.get(f, None)
1749 1749 # And if the file is in the list of files we care
1750 1750 # about.
1751 1751 if f is not None:
1752 1752 # Get the changenode this manifest belongs to
1753 1753 clnode = msng_mnfst_set[mnfstnode]
1754 1754 # Create the set of filenodes for the file if
1755 1755 # there isn't one already.
1756 1756 ndset = msng_filenode_set.setdefault(f, {})
1757 1757 # And set the filenode's changelog node to the
1758 1758 # manifest's if it hasn't been set already.
1759 1759 ndset.setdefault(fnode, clnode)
1760 1760 else:
1761 1761 # Otherwise we need a full manifest.
1762 1762 m = mnfst.read(mnfstnode)
1763 1763 # For every file in we care about.
1764 1764 for f in changedfiles:
1765 1765 fnode = m.get(f, None)
1766 1766 # If it's in the manifest
1767 1767 if fnode is not None:
1768 1768 # See comments above.
1769 1769 clnode = msng_mnfst_set[mnfstnode]
1770 1770 ndset = msng_filenode_set.setdefault(f, {})
1771 1771 ndset.setdefault(fnode, clnode)
1772 1772 # Remember the revision we hope to see next.
1773 1773 next_rev[0] = r + 1
1774 1774 return collect_msng_filenodes
1775 1775
1776 1776 # We have a list of filenodes we think we need for a file, lets remove
1777 1777 # all those we now the recipient must have.
1778 1778 def prune_filenodes(f, filerevlog):
1779 1779 msngset = msng_filenode_set[f]
1780 1780 hasset = {}
1781 1781 # If a 'missing' filenode thinks it belongs to a changenode we
1782 1782 # assume the recipient must have, then the recipient must have
1783 1783 # that filenode.
1784 1784 for n in msngset:
1785 1785 clnode = cl.node(filerevlog.linkrev(n))
1786 1786 if clnode in has_cl_set:
1787 1787 hasset[n] = 1
1788 1788 prune_parents(filerevlog, hasset, msngset)
1789 1789
1790 1790 # A function generator function that sets up the a context for the
1791 1791 # inner function.
1792 1792 def lookup_filenode_link_func(fname):
1793 1793 msngset = msng_filenode_set[fname]
1794 1794 # Lookup the changenode the filenode belongs to.
1795 1795 def lookup_filenode_link(fnode):
1796 1796 return msngset[fnode]
1797 1797 return lookup_filenode_link
1798 1798
1799 1799 # Add the nodes that were explicitly requested.
1800 1800 def add_extra_nodes(name, nodes):
1801 1801 if not extranodes or name not in extranodes:
1802 1802 return
1803 1803
1804 1804 for node, linknode in extranodes[name]:
1805 1805 if node not in nodes:
1806 1806 nodes[node] = linknode
1807 1807
1808 1808 # Now that we have all theses utility functions to help out and
1809 1809 # logically divide up the task, generate the group.
1810 1810 def gengroup():
1811 1811 # The set of changed files starts empty.
1812 1812 changedfiles = {}
1813 1813 # Create a changenode group generator that will call our functions
1814 1814 # back to lookup the owning changenode and collect information.
1815 1815 group = cl.group(msng_cl_lst, identity,
1816 1816 manifest_and_file_collector(changedfiles))
1817 1817 for chnk in group:
1818 1818 yield chnk
1819 1819
1820 1820 # The list of manifests has been collected by the generator
1821 1821 # calling our functions back.
1822 1822 prune_manifests()
1823 1823 add_extra_nodes(1, msng_mnfst_set)
1824 1824 msng_mnfst_lst = msng_mnfst_set.keys()
1825 1825 # Sort the manifestnodes by revision number.
1826 1826 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1827 1827 # Create a generator for the manifestnodes that calls our lookup
1828 1828 # and data collection functions back.
1829 1829 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1830 1830 filenode_collector(changedfiles))
1831 1831 for chnk in group:
1832 1832 yield chnk
1833 1833
1834 1834 # These are no longer needed, dereference and toss the memory for
1835 1835 # them.
1836 1836 msng_mnfst_lst = None
1837 1837 msng_mnfst_set.clear()
1838 1838
1839 1839 if extranodes:
1840 1840 for fname in extranodes:
1841 1841 if isinstance(fname, int):
1842 1842 continue
1843 1843 add_extra_nodes(fname,
1844 1844 msng_filenode_set.setdefault(fname, {}))
1845 1845 changedfiles[fname] = 1
1846 1846 changedfiles = changedfiles.keys()
1847 1847 changedfiles.sort()
1848 1848 # Go through all our files in order sorted by name.
1849 1849 for fname in changedfiles:
1850 1850 filerevlog = self.file(fname)
1851 1851 if filerevlog.count() == 0:
1852 1852 raise util.Abort(_("empty or missing revlog for %s") % fname)
1853 1853 # Toss out the filenodes that the recipient isn't really
1854 1854 # missing.
1855 1855 if fname in msng_filenode_set:
1856 1856 prune_filenodes(fname, filerevlog)
1857 1857 msng_filenode_lst = msng_filenode_set[fname].keys()
1858 1858 else:
1859 1859 msng_filenode_lst = []
1860 1860 # If any filenodes are left, generate the group for them,
1861 1861 # otherwise don't bother.
1862 1862 if len(msng_filenode_lst) > 0:
1863 1863 yield changegroup.chunkheader(len(fname))
1864 1864 yield fname
1865 1865 # Sort the filenodes by their revision #
1866 1866 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1867 1867 # Create a group generator and only pass in a changenode
1868 1868 # lookup function as we need to collect no information
1869 1869 # from filenodes.
1870 1870 group = filerevlog.group(msng_filenode_lst,
1871 1871 lookup_filenode_link_func(fname))
1872 1872 for chnk in group:
1873 1873 yield chnk
1874 1874 if fname in msng_filenode_set:
1875 1875 # Don't need this anymore, toss it to free memory.
1876 1876 del msng_filenode_set[fname]
1877 1877 # Signal that no more groups are left.
1878 1878 yield changegroup.closechunk()
1879 1879
1880 1880 if msng_cl_lst:
1881 1881 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1882 1882
1883 1883 return util.chunkbuffer(gengroup())
1884 1884
1885 1885 def changegroup(self, basenodes, source):
1886 1886 """Generate a changegroup of all nodes that we have that a recipient
1887 1887 doesn't.
1888 1888
1889 1889 This is much easier than the previous function as we can assume that
1890 1890 the recipient has any changenode we aren't sending them."""
1891 1891
1892 1892 self.hook('preoutgoing', throw=True, source=source)
1893 1893
1894 1894 cl = self.changelog
1895 1895 nodes = cl.nodesbetween(basenodes, None)[0]
1896 1896 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1897 1897 self.changegroupinfo(nodes, source)
1898 1898
1899 1899 def identity(x):
1900 1900 return x
1901 1901
1902 1902 def gennodelst(revlog):
1903 1903 for r in xrange(0, revlog.count()):
1904 1904 n = revlog.node(r)
1905 1905 if revlog.linkrev(n) in revset:
1906 1906 yield n
1907 1907
1908 1908 def changed_file_collector(changedfileset):
1909 1909 def collect_changed_files(clnode):
1910 1910 c = cl.read(clnode)
1911 1911 for fname in c[3]:
1912 1912 changedfileset[fname] = 1
1913 1913 return collect_changed_files
1914 1914
1915 1915 def lookuprevlink_func(revlog):
1916 1916 def lookuprevlink(n):
1917 1917 return cl.node(revlog.linkrev(n))
1918 1918 return lookuprevlink
1919 1919
1920 1920 def gengroup():
1921 1921 # construct a list of all changed files
1922 1922 changedfiles = {}
1923 1923
1924 1924 for chnk in cl.group(nodes, identity,
1925 1925 changed_file_collector(changedfiles)):
1926 1926 yield chnk
1927 1927 changedfiles = changedfiles.keys()
1928 1928 changedfiles.sort()
1929 1929
1930 1930 mnfst = self.manifest
1931 1931 nodeiter = gennodelst(mnfst)
1932 1932 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1933 1933 yield chnk
1934 1934
1935 1935 for fname in changedfiles:
1936 1936 filerevlog = self.file(fname)
1937 1937 if filerevlog.count() == 0:
1938 1938 raise util.Abort(_("empty or missing revlog for %s") % fname)
1939 1939 nodeiter = gennodelst(filerevlog)
1940 1940 nodeiter = list(nodeiter)
1941 1941 if nodeiter:
1942 1942 yield changegroup.chunkheader(len(fname))
1943 1943 yield fname
1944 1944 lookup = lookuprevlink_func(filerevlog)
1945 1945 for chnk in filerevlog.group(nodeiter, lookup):
1946 1946 yield chnk
1947 1947
1948 1948 yield changegroup.closechunk()
1949 1949
1950 1950 if nodes:
1951 1951 self.hook('outgoing', node=hex(nodes[0]), source=source)
1952 1952
1953 1953 return util.chunkbuffer(gengroup())
1954 1954
1955 1955 def addchangegroup(self, source, srctype, url, emptyok=False):
1956 1956 """add changegroup to repo.
1957 1957
1958 1958 return values:
1959 1959 - nothing changed or no source: 0
1960 1960 - more heads than before: 1+added heads (2..n)
1961 1961 - less heads than before: -1-removed heads (-2..-n)
1962 1962 - number of heads stays the same: 1
1963 1963 """
1964 1964 def csmap(x):
1965 1965 self.ui.debug(_("add changeset %s\n") % short(x))
1966 1966 return cl.count()
1967 1967
1968 1968 def revmap(x):
1969 1969 return cl.rev(x)
1970 1970
1971 1971 if not source:
1972 1972 return 0
1973 1973
1974 1974 self.hook('prechangegroup', throw=True, source=srctype, url=url)
1975 1975
1976 1976 changesets = files = revisions = 0
1977 1977
1978 1978 # write changelog data to temp files so concurrent readers will not see
1979 1979 # inconsistent view
1980 1980 cl = self.changelog
1981 1981 cl.delayupdate()
1982 1982 oldheads = len(cl.heads())
1983 1983
1984 1984 tr = self.transaction()
1985 1985 try:
1986 1986 trp = weakref.proxy(tr)
1987 1987 # pull off the changeset group
1988 1988 self.ui.status(_("adding changesets\n"))
1989 1989 cor = cl.count() - 1
1990 1990 chunkiter = changegroup.chunkiter(source)
1991 1991 if cl.addgroup(chunkiter, csmap, trp, 1) is None and not emptyok:
1992 1992 raise util.Abort(_("received changelog group is empty"))
1993 1993 cnr = cl.count() - 1
1994 1994 changesets = cnr - cor
1995 1995
1996 1996 # pull off the manifest group
1997 1997 self.ui.status(_("adding manifests\n"))
1998 1998 chunkiter = changegroup.chunkiter(source)
1999 1999 # no need to check for empty manifest group here:
2000 2000 # if the result of the merge of 1 and 2 is the same in 3 and 4,
2001 2001 # no new manifest will be created and the manifest group will
2002 2002 # be empty during the pull
2003 2003 self.manifest.addgroup(chunkiter, revmap, trp)
2004 2004
2005 2005 # process the files
2006 2006 self.ui.status(_("adding file changes\n"))
2007 2007 while 1:
2008 2008 f = changegroup.getchunk(source)
2009 2009 if not f:
2010 2010 break
2011 2011 self.ui.debug(_("adding %s revisions\n") % f)
2012 2012 fl = self.file(f)
2013 2013 o = fl.count()
2014 2014 chunkiter = changegroup.chunkiter(source)
2015 2015 if fl.addgroup(chunkiter, revmap, trp) is None:
2016 2016 raise util.Abort(_("received file revlog group is empty"))
2017 2017 revisions += fl.count() - o
2018 2018 files += 1
2019 2019
2020 2020 # make changelog see real files again
2021 2021 cl.finalize(trp)
2022 2022
2023 2023 newheads = len(self.changelog.heads())
2024 2024 heads = ""
2025 2025 if oldheads and newheads != oldheads:
2026 2026 heads = _(" (%+d heads)") % (newheads - oldheads)
2027 2027
2028 2028 self.ui.status(_("added %d changesets"
2029 2029 " with %d changes to %d files%s\n")
2030 2030 % (changesets, revisions, files, heads))
2031 2031
2032 2032 if changesets > 0:
2033 2033 self.hook('pretxnchangegroup', throw=True,
2034 2034 node=hex(self.changelog.node(cor+1)), source=srctype,
2035 2035 url=url)
2036 2036
2037 2037 tr.close()
2038 2038 finally:
2039 2039 del tr
2040 2040
2041 2041 if changesets > 0:
2042 2042 # forcefully update the on-disk branch cache
2043 2043 self.ui.debug(_("updating the branch cache\n"))
2044 2044 self.branchtags()
2045 2045 self.hook("changegroup", node=hex(self.changelog.node(cor+1)),
2046 2046 source=srctype, url=url)
2047 2047
2048 2048 for i in xrange(cor + 1, cnr + 1):
2049 2049 self.hook("incoming", node=hex(self.changelog.node(i)),
2050 2050 source=srctype, url=url)
2051 2051
2052 2052 # never return 0 here:
2053 2053 if newheads < oldheads:
2054 2054 return newheads - oldheads - 1
2055 2055 else:
2056 2056 return newheads - oldheads + 1
2057 2057
2058 2058
2059 2059 def stream_in(self, remote):
2060 2060 fp = remote.stream_out()
2061 2061 l = fp.readline()
2062 2062 try:
2063 2063 resp = int(l)
2064 2064 except ValueError:
2065 2065 raise util.UnexpectedOutput(
2066 2066 _('Unexpected response from remote server:'), l)
2067 2067 if resp == 1:
2068 2068 raise util.Abort(_('operation forbidden by server'))
2069 2069 elif resp == 2:
2070 2070 raise util.Abort(_('locking the remote repository failed'))
2071 2071 elif resp != 0:
2072 2072 raise util.Abort(_('the server sent an unknown error code'))
2073 2073 self.ui.status(_('streaming all changes\n'))
2074 2074 l = fp.readline()
2075 2075 try:
2076 2076 total_files, total_bytes = map(int, l.split(' ', 1))
2077 2077 except (ValueError, TypeError):
2078 2078 raise util.UnexpectedOutput(
2079 2079 _('Unexpected response from remote server:'), l)
2080 2080 self.ui.status(_('%d files to transfer, %s of data\n') %
2081 2081 (total_files, util.bytecount(total_bytes)))
2082 2082 start = time.time()
2083 2083 for i in xrange(total_files):
2084 2084 # XXX doesn't support '\n' or '\r' in filenames
2085 2085 l = fp.readline()
2086 2086 try:
2087 2087 name, size = l.split('\0', 1)
2088 2088 size = int(size)
2089 2089 except ValueError, TypeError:
2090 2090 raise util.UnexpectedOutput(
2091 2091 _('Unexpected response from remote server:'), l)
2092 2092 self.ui.debug('adding %s (%s)\n' % (name, util.bytecount(size)))
2093 2093 ofp = self.sopener(name, 'w')
2094 2094 for chunk in util.filechunkiter(fp, limit=size):
2095 2095 ofp.write(chunk)
2096 2096 ofp.close()
2097 2097 elapsed = time.time() - start
2098 2098 if elapsed <= 0:
2099 2099 elapsed = 0.001
2100 2100 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
2101 2101 (util.bytecount(total_bytes), elapsed,
2102 2102 util.bytecount(total_bytes / elapsed)))
2103 2103 self.invalidate()
2104 2104 return len(self.heads()) + 1
2105 2105
2106 2106 def clone(self, remote, heads=[], stream=False):
2107 2107 '''clone remote repository.
2108 2108
2109 2109 keyword arguments:
2110 2110 heads: list of revs to clone (forces use of pull)
2111 2111 stream: use streaming clone if possible'''
2112 2112
2113 2113 # now, all clients that can request uncompressed clones can
2114 2114 # read repo formats supported by all servers that can serve
2115 2115 # them.
2116 2116
2117 2117 # if revlog format changes, client will have to check version
2118 2118 # and format flags on "stream" capability, and use
2119 2119 # uncompressed only if compatible.
2120 2120
2121 2121 if stream and not heads and remote.capable('stream'):
2122 2122 return self.stream_in(remote)
2123 2123 return self.pull(remote, heads)
2124 2124
2125 2125 # used to avoid circular references so destructors work
2126 2126 def aftertrans(files):
2127 2127 renamefiles = [tuple(t) for t in files]
2128 2128 def a():
2129 2129 for src, dest in renamefiles:
2130 2130 util.rename(src, dest)
2131 2131 return a
2132 2132
2133 2133 def instance(ui, path, create):
2134 2134 return localrepository(ui, util.drop_scheme('file', path), create)
2135 2135
2136 2136 def islocal(path):
2137 2137 return True
General Comments 0
You need to be logged in to leave comments. Login now