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