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