##// END OF EJS Templates
Introduce find_exe. Use instead of find_in_path for programs....
Bryan O'Sullivan -
r4488:62019c44 default
parent child Browse files
Show More
@@ -1,3377 +1,3372 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import demandimport; demandimport.enable()
9 9 from node import *
10 10 from i18n import _
11 11 import bisect, os, re, sys, signal, imp, urllib, pdb, shlex, stat
12 12 import fancyopts, ui, hg, util, lock, revlog, bundlerepo
13 13 import difflib, patch, time, help, mdiff, tempfile
14 14 import traceback, errno, version, atexit, socket
15 15 import archival, changegroup, cmdutil, hgweb.server, sshserver
16 16
17 17 class UnknownCommand(Exception):
18 18 """Exception raised if command is not in the command table."""
19 19 class AmbiguousCommand(Exception):
20 20 """Exception raised if command shortcut matches more than one command."""
21 21
22 22 def bail_if_changed(repo):
23 23 modified, added, removed, deleted = repo.status()[:4]
24 24 if modified or added or removed or deleted:
25 25 raise util.Abort(_("outstanding uncommitted changes"))
26 26
27 27 def logmessage(opts):
28 28 """ get the log message according to -m and -l option """
29 29 message = opts['message']
30 30 logfile = opts['logfile']
31 31
32 32 if message and logfile:
33 33 raise util.Abort(_('options --message and --logfile are mutually '
34 34 'exclusive'))
35 35 if not message and logfile:
36 36 try:
37 37 if logfile == '-':
38 38 message = sys.stdin.read()
39 39 else:
40 40 message = open(logfile).read()
41 41 except IOError, inst:
42 42 raise util.Abort(_("can't read commit message '%s': %s") %
43 43 (logfile, inst.strerror))
44 44 return message
45 45
46 46 def setremoteconfig(ui, opts):
47 47 "copy remote options to ui tree"
48 48 if opts.get('ssh'):
49 49 ui.setconfig("ui", "ssh", opts['ssh'])
50 50 if opts.get('remotecmd'):
51 51 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
52 52
53 53 # Commands start here, listed alphabetically
54 54
55 55 def add(ui, repo, *pats, **opts):
56 56 """add the specified files on the next commit
57 57
58 58 Schedule files to be version controlled and added to the repository.
59 59
60 60 The files will be added to the repository at the next commit. To
61 61 undo an add before that, see hg revert.
62 62
63 63 If no names are given, add all files in the repository.
64 64 """
65 65
66 66 names = []
67 67 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
68 68 if exact:
69 69 if ui.verbose:
70 70 ui.status(_('adding %s\n') % rel)
71 71 names.append(abs)
72 72 elif repo.dirstate.state(abs) == '?':
73 73 ui.status(_('adding %s\n') % rel)
74 74 names.append(abs)
75 75 if not opts.get('dry_run'):
76 76 repo.add(names)
77 77
78 78 def addremove(ui, repo, *pats, **opts):
79 79 """add all new files, delete all missing files
80 80
81 81 Add all new files and remove all missing files from the repository.
82 82
83 83 New files are ignored if they match any of the patterns in .hgignore. As
84 84 with add, these changes take effect at the next commit.
85 85
86 86 Use the -s option to detect renamed files. With a parameter > 0,
87 87 this compares every removed file with every added file and records
88 88 those similar enough as renames. This option takes a percentage
89 89 between 0 (disabled) and 100 (files must be identical) as its
90 90 parameter. Detecting renamed files this way can be expensive.
91 91 """
92 92 sim = float(opts.get('similarity') or 0)
93 93 if sim < 0 or sim > 100:
94 94 raise util.Abort(_('similarity must be between 0 and 100'))
95 95 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
96 96
97 97 def annotate(ui, repo, *pats, **opts):
98 98 """show changeset information per file line
99 99
100 100 List changes in files, showing the revision id responsible for each line
101 101
102 102 This command is useful to discover who did a change or when a change took
103 103 place.
104 104
105 105 Without the -a option, annotate will avoid processing files it
106 106 detects as binary. With -a, annotate will generate an annotation
107 107 anyway, probably with undesirable results.
108 108 """
109 109 getdate = util.cachefunc(lambda x: util.datestr(x.date()))
110 110
111 111 if not pats:
112 112 raise util.Abort(_('at least one file name or pattern required'))
113 113
114 114 opmap = [['user', lambda x: ui.shortuser(x.user())],
115 115 ['number', lambda x: str(x.rev())],
116 116 ['changeset', lambda x: short(x.node())],
117 117 ['date', getdate], ['follow', lambda x: x.path()]]
118 118 if (not opts['user'] and not opts['changeset'] and not opts['date']
119 119 and not opts['follow']):
120 120 opts['number'] = 1
121 121
122 122 ctx = repo.changectx(opts['rev'])
123 123
124 124 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
125 125 node=ctx.node()):
126 126 fctx = ctx.filectx(abs)
127 127 if not opts['text'] and util.binary(fctx.data()):
128 128 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
129 129 continue
130 130
131 131 lines = fctx.annotate(follow=opts.get('follow'))
132 132 pieces = []
133 133
134 134 for o, f in opmap:
135 135 if opts[o]:
136 136 l = [f(n) for n, dummy in lines]
137 137 if l:
138 138 m = max(map(len, l))
139 139 pieces.append(["%*s" % (m, x) for x in l])
140 140
141 141 if pieces:
142 142 for p, l in zip(zip(*pieces), lines):
143 143 ui.write("%s: %s" % (" ".join(p), l[1]))
144 144
145 145 def archive(ui, repo, dest, **opts):
146 146 '''create unversioned archive of a repository revision
147 147
148 148 By default, the revision used is the parent of the working
149 149 directory; use "-r" to specify a different revision.
150 150
151 151 To specify the type of archive to create, use "-t". Valid
152 152 types are:
153 153
154 154 "files" (default): a directory full of files
155 155 "tar": tar archive, uncompressed
156 156 "tbz2": tar archive, compressed using bzip2
157 157 "tgz": tar archive, compressed using gzip
158 158 "uzip": zip archive, uncompressed
159 159 "zip": zip archive, compressed using deflate
160 160
161 161 The exact name of the destination archive or directory is given
162 162 using a format string; see "hg help export" for details.
163 163
164 164 Each member added to an archive file has a directory prefix
165 165 prepended. Use "-p" to specify a format string for the prefix.
166 166 The default is the basename of the archive, with suffixes removed.
167 167 '''
168 168
169 169 node = repo.changectx(opts['rev']).node()
170 170 dest = cmdutil.make_filename(repo, dest, node)
171 171 if os.path.realpath(dest) == repo.root:
172 172 raise util.Abort(_('repository root cannot be destination'))
173 173 dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
174 174 kind = opts.get('type') or 'files'
175 175 prefix = opts['prefix']
176 176 if dest == '-':
177 177 if kind == 'files':
178 178 raise util.Abort(_('cannot archive plain files to stdout'))
179 179 dest = sys.stdout
180 180 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
181 181 prefix = cmdutil.make_filename(repo, prefix, node)
182 182 archival.archive(repo, dest, node, kind, not opts['no_decode'],
183 183 matchfn, prefix)
184 184
185 185 def backout(ui, repo, rev, **opts):
186 186 '''reverse effect of earlier changeset
187 187
188 188 Commit the backed out changes as a new changeset. The new
189 189 changeset is a child of the backed out changeset.
190 190
191 191 If you back out a changeset other than the tip, a new head is
192 192 created. This head is the parent of the working directory. If
193 193 you back out an old changeset, your working directory will appear
194 194 old after the backout. You should merge the backout changeset
195 195 with another head.
196 196
197 197 The --merge option remembers the parent of the working directory
198 198 before starting the backout, then merges the new head with that
199 199 changeset afterwards. This saves you from doing the merge by
200 200 hand. The result of this merge is not committed, as for a normal
201 201 merge.'''
202 202
203 203 bail_if_changed(repo)
204 204 op1, op2 = repo.dirstate.parents()
205 205 if op2 != nullid:
206 206 raise util.Abort(_('outstanding uncommitted merge'))
207 207 node = repo.lookup(rev)
208 208 p1, p2 = repo.changelog.parents(node)
209 209 if p1 == nullid:
210 210 raise util.Abort(_('cannot back out a change with no parents'))
211 211 if p2 != nullid:
212 212 if not opts['parent']:
213 213 raise util.Abort(_('cannot back out a merge changeset without '
214 214 '--parent'))
215 215 p = repo.lookup(opts['parent'])
216 216 if p not in (p1, p2):
217 217 raise util.Abort(_('%s is not a parent of %s') %
218 218 (short(p), short(node)))
219 219 parent = p
220 220 else:
221 221 if opts['parent']:
222 222 raise util.Abort(_('cannot use --parent on non-merge changeset'))
223 223 parent = p1
224 224 hg.clean(repo, node, show_stats=False)
225 225 revert_opts = opts.copy()
226 226 revert_opts['date'] = None
227 227 revert_opts['all'] = True
228 228 revert_opts['rev'] = hex(parent)
229 229 revert(ui, repo, **revert_opts)
230 230 commit_opts = opts.copy()
231 231 commit_opts['addremove'] = False
232 232 if not commit_opts['message'] and not commit_opts['logfile']:
233 233 commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
234 234 commit_opts['force_editor'] = True
235 235 commit(ui, repo, **commit_opts)
236 236 def nice(node):
237 237 return '%d:%s' % (repo.changelog.rev(node), short(node))
238 238 ui.status(_('changeset %s backs out changeset %s\n') %
239 239 (nice(repo.changelog.tip()), nice(node)))
240 240 if op1 != node:
241 241 if opts['merge']:
242 242 ui.status(_('merging with changeset %s\n') % nice(op1))
243 243 hg.merge(repo, hex(op1))
244 244 else:
245 245 ui.status(_('the backout changeset is a new head - '
246 246 'do not forget to merge\n'))
247 247 ui.status(_('(use "backout --merge" '
248 248 'if you want to auto-merge)\n'))
249 249
250 250 def branch(ui, repo, label=None, **opts):
251 251 """set or show the current branch name
252 252
253 253 With <name>, set the current branch name. Otherwise, show the
254 254 current branch name.
255 255
256 256 Unless --force is specified, branch will not let you set a
257 257 branch name that shadows an existing branch.
258 258 """
259 259
260 260 if label:
261 261 if not opts.get('force') and label in repo.branchtags():
262 262 if label not in [p.branch() for p in repo.workingctx().parents()]:
263 263 raise util.Abort(_('a branch of the same name already exists'
264 264 ' (use --force to override)'))
265 265 repo.dirstate.setbranch(util.fromlocal(label))
266 266 else:
267 267 ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
268 268
269 269 def branches(ui, repo):
270 270 """list repository named branches
271 271
272 272 List the repository's named branches.
273 273 """
274 274 b = repo.branchtags()
275 275 l = [(-repo.changelog.rev(n), n, t) for t, n in b.items()]
276 276 l.sort()
277 277 for r, n, t in l:
278 278 hexfunc = ui.debugflag and hex or short
279 279 if ui.quiet:
280 280 ui.write("%s\n" % t)
281 281 else:
282 282 spaces = " " * (30 - util.locallen(t))
283 283 ui.write("%s%s %s:%s\n" % (t, spaces, -r, hexfunc(n)))
284 284
285 285 def bundle(ui, repo, fname, dest=None, **opts):
286 286 """create a changegroup file
287 287
288 288 Generate a compressed changegroup file collecting changesets not
289 289 found in the other repository.
290 290
291 291 If no destination repository is specified the destination is assumed
292 292 to have all the nodes specified by one or more --base parameters.
293 293
294 294 The bundle file can then be transferred using conventional means and
295 295 applied to another repository with the unbundle or pull command.
296 296 This is useful when direct push and pull are not available or when
297 297 exporting an entire repository is undesirable.
298 298
299 299 Applying bundles preserves all changeset contents including
300 300 permissions, copy/rename information, and revision history.
301 301 """
302 302 revs = opts.get('rev') or None
303 303 if revs:
304 304 revs = [repo.lookup(rev) for rev in revs]
305 305 base = opts.get('base')
306 306 if base:
307 307 if dest:
308 308 raise util.Abort(_("--base is incompatible with specifiying "
309 309 "a destination"))
310 310 base = [repo.lookup(rev) for rev in base]
311 311 # create the right base
312 312 # XXX: nodesbetween / changegroup* should be "fixed" instead
313 313 o = []
314 314 has = {nullid: None}
315 315 for n in base:
316 316 has.update(repo.changelog.reachable(n))
317 317 if revs:
318 318 visit = list(revs)
319 319 else:
320 320 visit = repo.changelog.heads()
321 321 seen = {}
322 322 while visit:
323 323 n = visit.pop(0)
324 324 parents = [p for p in repo.changelog.parents(n) if p not in has]
325 325 if len(parents) == 0:
326 326 o.insert(0, n)
327 327 else:
328 328 for p in parents:
329 329 if p not in seen:
330 330 seen[p] = 1
331 331 visit.append(p)
332 332 else:
333 333 setremoteconfig(ui, opts)
334 334 dest = ui.expandpath(dest or 'default-push', dest or 'default')
335 335 other = hg.repository(ui, dest)
336 336 o = repo.findoutgoing(other, force=opts['force'])
337 337
338 338 if revs:
339 339 cg = repo.changegroupsubset(o, revs, 'bundle')
340 340 else:
341 341 cg = repo.changegroup(o, 'bundle')
342 342 changegroup.writebundle(cg, fname, "HG10BZ")
343 343
344 344 def cat(ui, repo, file1, *pats, **opts):
345 345 """output the current or given revision of files
346 346
347 347 Print the specified files as they were at the given revision.
348 348 If no revision is given, the parent of the working directory is used,
349 349 or tip if no revision is checked out.
350 350
351 351 Output may be to a file, in which case the name of the file is
352 352 given using a format string. The formatting rules are the same as
353 353 for the export command, with the following additions:
354 354
355 355 %s basename of file being printed
356 356 %d dirname of file being printed, or '.' if in repo root
357 357 %p root-relative path name of file being printed
358 358 """
359 359 ctx = repo.changectx(opts['rev'])
360 360 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
361 361 ctx.node()):
362 362 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
363 363 fp.write(ctx.filectx(abs).data())
364 364
365 365 def clone(ui, source, dest=None, **opts):
366 366 """make a copy of an existing repository
367 367
368 368 Create a copy of an existing repository in a new directory.
369 369
370 370 If no destination directory name is specified, it defaults to the
371 371 basename of the source.
372 372
373 373 The location of the source is added to the new repository's
374 374 .hg/hgrc file, as the default to be used for future pulls.
375 375
376 376 For efficiency, hardlinks are used for cloning whenever the source
377 377 and destination are on the same filesystem (note this applies only
378 378 to the repository data, not to the checked out files). Some
379 379 filesystems, such as AFS, implement hardlinking incorrectly, but
380 380 do not report errors. In these cases, use the --pull option to
381 381 avoid hardlinking.
382 382
383 383 You can safely clone repositories and checked out files using full
384 384 hardlinks with
385 385
386 386 $ cp -al REPO REPOCLONE
387 387
388 388 which is the fastest way to clone. However, the operation is not
389 389 atomic (making sure REPO is not modified during the operation is
390 390 up to you) and you have to make sure your editor breaks hardlinks
391 391 (Emacs and most Linux Kernel tools do so).
392 392
393 393 If you use the -r option to clone up to a specific revision, no
394 394 subsequent revisions will be present in the cloned repository.
395 395 This option implies --pull, even on local repositories.
396 396
397 397 See pull for valid source format details.
398 398
399 399 It is possible to specify an ssh:// URL as the destination, but no
400 400 .hg/hgrc and working directory will be created on the remote side.
401 401 Look at the help text for the pull command for important details
402 402 about ssh:// URLs.
403 403 """
404 404 setremoteconfig(ui, opts)
405 405 hg.clone(ui, ui.expandpath(source), dest,
406 406 pull=opts['pull'],
407 407 stream=opts['uncompressed'],
408 408 rev=opts['rev'],
409 409 update=not opts['noupdate'])
410 410
411 411 def commit(ui, repo, *pats, **opts):
412 412 """commit the specified files or all outstanding changes
413 413
414 414 Commit changes to the given files into the repository.
415 415
416 416 If a list of files is omitted, all changes reported by "hg status"
417 417 will be committed.
418 418
419 419 If no commit message is specified, the editor configured in your hgrc
420 420 or in the EDITOR environment variable is started to enter a message.
421 421 """
422 422 message = logmessage(opts)
423 423
424 424 if opts['addremove']:
425 425 cmdutil.addremove(repo, pats, opts)
426 426 fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
427 427 if pats:
428 428 status = repo.status(files=fns, match=match)
429 429 modified, added, removed, deleted, unknown = status[:5]
430 430 files = modified + added + removed
431 431 slist = None
432 432 for f in fns:
433 433 if f == '.':
434 434 continue
435 435 if f not in files:
436 436 rf = repo.wjoin(f)
437 437 if f in unknown:
438 438 raise util.Abort(_("file %s not tracked!") % rf)
439 439 try:
440 440 mode = os.lstat(rf)[stat.ST_MODE]
441 441 except OSError:
442 442 raise util.Abort(_("file %s not found!") % rf)
443 443 if stat.S_ISDIR(mode):
444 444 name = f + '/'
445 445 if slist is None:
446 446 slist = list(files)
447 447 slist.sort()
448 448 i = bisect.bisect(slist, name)
449 449 if i >= len(slist) or not slist[i].startswith(name):
450 450 raise util.Abort(_("no match under directory %s!")
451 451 % rf)
452 452 elif not stat.S_ISREG(mode):
453 453 raise util.Abort(_("can't commit %s: "
454 454 "unsupported file type!") % rf)
455 455 else:
456 456 files = []
457 457 try:
458 458 repo.commit(files, message, opts['user'], opts['date'], match,
459 459 force_editor=opts.get('force_editor'))
460 460 except ValueError, inst:
461 461 raise util.Abort(str(inst))
462 462
463 463 def docopy(ui, repo, pats, opts, wlock):
464 464 # called with the repo lock held
465 465 #
466 466 # hgsep => pathname that uses "/" to separate directories
467 467 # ossep => pathname that uses os.sep to separate directories
468 468 cwd = repo.getcwd()
469 469 errors = 0
470 470 copied = []
471 471 targets = {}
472 472
473 473 # abs: hgsep
474 474 # rel: ossep
475 475 # return: hgsep
476 476 def okaytocopy(abs, rel, exact):
477 477 reasons = {'?': _('is not managed'),
478 478 'a': _('has been marked for add'),
479 479 'r': _('has been marked for remove')}
480 480 state = repo.dirstate.state(abs)
481 481 reason = reasons.get(state)
482 482 if reason:
483 483 if state == 'a':
484 484 origsrc = repo.dirstate.copied(abs)
485 485 if origsrc is not None:
486 486 return origsrc
487 487 if exact:
488 488 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
489 489 else:
490 490 return abs
491 491
492 492 # origsrc: hgsep
493 493 # abssrc: hgsep
494 494 # relsrc: ossep
495 495 # target: ossep
496 496 def copy(origsrc, abssrc, relsrc, target, exact):
497 497 abstarget = util.canonpath(repo.root, cwd, target)
498 498 reltarget = util.pathto(repo.root, cwd, abstarget)
499 499 prevsrc = targets.get(abstarget)
500 500 if prevsrc is not None:
501 501 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
502 502 (reltarget, util.localpath(abssrc),
503 503 util.localpath(prevsrc)))
504 504 return
505 505 if (not opts['after'] and os.path.exists(reltarget) or
506 506 opts['after'] and repo.dirstate.state(abstarget) not in '?ar'):
507 507 if not opts['force']:
508 508 ui.warn(_('%s: not overwriting - file exists\n') %
509 509 reltarget)
510 510 return
511 511 if not opts['after'] and not opts.get('dry_run'):
512 512 os.unlink(reltarget)
513 513 if opts['after']:
514 514 if not os.path.exists(reltarget):
515 515 return
516 516 else:
517 517 targetdir = os.path.dirname(reltarget) or '.'
518 518 if not os.path.isdir(targetdir) and not opts.get('dry_run'):
519 519 os.makedirs(targetdir)
520 520 try:
521 521 restore = repo.dirstate.state(abstarget) == 'r'
522 522 if restore and not opts.get('dry_run'):
523 523 repo.undelete([abstarget], wlock)
524 524 try:
525 525 if not opts.get('dry_run'):
526 526 util.copyfile(relsrc, reltarget)
527 527 restore = False
528 528 finally:
529 529 if restore:
530 530 repo.remove([abstarget], wlock=wlock)
531 531 except IOError, inst:
532 532 if inst.errno == errno.ENOENT:
533 533 ui.warn(_('%s: deleted in working copy\n') % relsrc)
534 534 else:
535 535 ui.warn(_('%s: cannot copy - %s\n') %
536 536 (relsrc, inst.strerror))
537 537 errors += 1
538 538 return
539 539 if ui.verbose or not exact:
540 540 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
541 541 targets[abstarget] = abssrc
542 542 if abstarget != origsrc and not opts.get('dry_run'):
543 543 repo.copy(origsrc, abstarget, wlock)
544 544 copied.append((abssrc, relsrc, exact))
545 545
546 546 # pat: ossep
547 547 # dest ossep
548 548 # srcs: list of (hgsep, hgsep, ossep, bool)
549 549 # return: function that takes hgsep and returns ossep
550 550 def targetpathfn(pat, dest, srcs):
551 551 if os.path.isdir(pat):
552 552 abspfx = util.canonpath(repo.root, cwd, pat)
553 553 abspfx = util.localpath(abspfx)
554 554 if destdirexists:
555 555 striplen = len(os.path.split(abspfx)[0])
556 556 else:
557 557 striplen = len(abspfx)
558 558 if striplen:
559 559 striplen += len(os.sep)
560 560 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
561 561 elif destdirexists:
562 562 res = lambda p: os.path.join(dest,
563 563 os.path.basename(util.localpath(p)))
564 564 else:
565 565 res = lambda p: dest
566 566 return res
567 567
568 568 # pat: ossep
569 569 # dest ossep
570 570 # srcs: list of (hgsep, hgsep, ossep, bool)
571 571 # return: function that takes hgsep and returns ossep
572 572 def targetpathafterfn(pat, dest, srcs):
573 573 if util.patkind(pat, None)[0]:
574 574 # a mercurial pattern
575 575 res = lambda p: os.path.join(dest,
576 576 os.path.basename(util.localpath(p)))
577 577 else:
578 578 abspfx = util.canonpath(repo.root, cwd, pat)
579 579 if len(abspfx) < len(srcs[0][0]):
580 580 # A directory. Either the target path contains the last
581 581 # component of the source path or it does not.
582 582 def evalpath(striplen):
583 583 score = 0
584 584 for s in srcs:
585 585 t = os.path.join(dest, util.localpath(s[0])[striplen:])
586 586 if os.path.exists(t):
587 587 score += 1
588 588 return score
589 589
590 590 abspfx = util.localpath(abspfx)
591 591 striplen = len(abspfx)
592 592 if striplen:
593 593 striplen += len(os.sep)
594 594 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
595 595 score = evalpath(striplen)
596 596 striplen1 = len(os.path.split(abspfx)[0])
597 597 if striplen1:
598 598 striplen1 += len(os.sep)
599 599 if evalpath(striplen1) > score:
600 600 striplen = striplen1
601 601 res = lambda p: os.path.join(dest,
602 602 util.localpath(p)[striplen:])
603 603 else:
604 604 # a file
605 605 if destdirexists:
606 606 res = lambda p: os.path.join(dest,
607 607 os.path.basename(util.localpath(p)))
608 608 else:
609 609 res = lambda p: dest
610 610 return res
611 611
612 612
613 613 pats = util.expand_glob(pats)
614 614 if not pats:
615 615 raise util.Abort(_('no source or destination specified'))
616 616 if len(pats) == 1:
617 617 raise util.Abort(_('no destination specified'))
618 618 dest = pats.pop()
619 619 destdirexists = os.path.isdir(dest)
620 620 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
621 621 raise util.Abort(_('with multiple sources, destination must be an '
622 622 'existing directory'))
623 623 if opts['after']:
624 624 tfn = targetpathafterfn
625 625 else:
626 626 tfn = targetpathfn
627 627 copylist = []
628 628 for pat in pats:
629 629 srcs = []
630 630 for tag, abssrc, relsrc, exact in cmdutil.walk(repo, [pat], opts,
631 631 globbed=True):
632 632 origsrc = okaytocopy(abssrc, relsrc, exact)
633 633 if origsrc:
634 634 srcs.append((origsrc, abssrc, relsrc, exact))
635 635 if not srcs:
636 636 continue
637 637 copylist.append((tfn(pat, dest, srcs), srcs))
638 638 if not copylist:
639 639 raise util.Abort(_('no files to copy'))
640 640
641 641 for targetpath, srcs in copylist:
642 642 for origsrc, abssrc, relsrc, exact in srcs:
643 643 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
644 644
645 645 if errors:
646 646 ui.warn(_('(consider using --after)\n'))
647 647 return errors, copied
648 648
649 649 def copy(ui, repo, *pats, **opts):
650 650 """mark files as copied for the next commit
651 651
652 652 Mark dest as having copies of source files. If dest is a
653 653 directory, copies are put in that directory. If dest is a file,
654 654 there can only be one source.
655 655
656 656 By default, this command copies the contents of files as they
657 657 stand in the working directory. If invoked with --after, the
658 658 operation is recorded, but no copying is performed.
659 659
660 660 This command takes effect in the next commit. To undo a copy
661 661 before that, see hg revert.
662 662 """
663 663 wlock = repo.wlock(0)
664 664 errs, copied = docopy(ui, repo, pats, opts, wlock)
665 665 return errs
666 666
667 667 def debugancestor(ui, index, rev1, rev2):
668 668 """find the ancestor revision of two revisions in a given index"""
669 669 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
670 670 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
671 671 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
672 672
673 673 def debugcomplete(ui, cmd='', **opts):
674 674 """returns the completion list associated with the given command"""
675 675
676 676 if opts['options']:
677 677 options = []
678 678 otables = [globalopts]
679 679 if cmd:
680 680 aliases, entry = findcmd(ui, cmd)
681 681 otables.append(entry[1])
682 682 for t in otables:
683 683 for o in t:
684 684 if o[0]:
685 685 options.append('-%s' % o[0])
686 686 options.append('--%s' % o[1])
687 687 ui.write("%s\n" % "\n".join(options))
688 688 return
689 689
690 690 clist = findpossible(ui, cmd).keys()
691 691 clist.sort()
692 692 ui.write("%s\n" % "\n".join(clist))
693 693
694 694 def debugrebuildstate(ui, repo, rev=""):
695 695 """rebuild the dirstate as it would look like for the given revision"""
696 696 if rev == "":
697 697 rev = repo.changelog.tip()
698 698 ctx = repo.changectx(rev)
699 699 files = ctx.manifest()
700 700 wlock = repo.wlock()
701 701 repo.dirstate.rebuild(rev, files)
702 702
703 703 def debugcheckstate(ui, repo):
704 704 """validate the correctness of the current dirstate"""
705 705 parent1, parent2 = repo.dirstate.parents()
706 706 repo.dirstate.read()
707 707 dc = repo.dirstate.map
708 708 keys = dc.keys()
709 709 keys.sort()
710 710 m1 = repo.changectx(parent1).manifest()
711 711 m2 = repo.changectx(parent2).manifest()
712 712 errors = 0
713 713 for f in dc:
714 714 state = repo.dirstate.state(f)
715 715 if state in "nr" and f not in m1:
716 716 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
717 717 errors += 1
718 718 if state in "a" and f in m1:
719 719 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
720 720 errors += 1
721 721 if state in "m" and f not in m1 and f not in m2:
722 722 ui.warn(_("%s in state %s, but not in either manifest\n") %
723 723 (f, state))
724 724 errors += 1
725 725 for f in m1:
726 726 state = repo.dirstate.state(f)
727 727 if state not in "nrm":
728 728 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
729 729 errors += 1
730 730 if errors:
731 731 error = _(".hg/dirstate inconsistent with current parent's manifest")
732 732 raise util.Abort(error)
733 733
734 734 def showconfig(ui, repo, *values, **opts):
735 735 """show combined config settings from all hgrc files
736 736
737 737 With no args, print names and values of all config items.
738 738
739 739 With one arg of the form section.name, print just the value of
740 740 that config item.
741 741
742 742 With multiple args, print names and values of all config items
743 743 with matching section names."""
744 744
745 745 untrusted = bool(opts.get('untrusted'))
746 746 if values:
747 747 if len([v for v in values if '.' in v]) > 1:
748 748 raise util.Abort(_('only one config item permitted'))
749 749 for section, name, value in ui.walkconfig(untrusted=untrusted):
750 750 sectname = section + '.' + name
751 751 if values:
752 752 for v in values:
753 753 if v == section:
754 754 ui.write('%s=%s\n' % (sectname, value))
755 755 elif v == sectname:
756 756 ui.write(value, '\n')
757 757 else:
758 758 ui.write('%s=%s\n' % (sectname, value))
759 759
760 760 def debugsetparents(ui, repo, rev1, rev2=None):
761 761 """manually set the parents of the current working directory
762 762
763 763 This is useful for writing repository conversion tools, but should
764 764 be used with care.
765 765 """
766 766
767 767 if not rev2:
768 768 rev2 = hex(nullid)
769 769
770 770 wlock = repo.wlock()
771 771 try:
772 772 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
773 773 finally:
774 774 wlock.release()
775 775
776 776 def debugstate(ui, repo):
777 777 """show the contents of the current dirstate"""
778 778 repo.dirstate.read()
779 779 dc = repo.dirstate.map
780 780 keys = dc.keys()
781 781 keys.sort()
782 782 for file_ in keys:
783 783 if dc[file_][3] == -1:
784 784 # Pad or slice to locale representation
785 785 locale_len = len(time.strftime("%x %X", time.localtime(0)))
786 786 timestr = 'unset'
787 787 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
788 788 else:
789 789 timestr = time.strftime("%x %X", time.localtime(dc[file_][3]))
790 790 ui.write("%c %3o %10d %s %s\n"
791 791 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
792 792 timestr, file_))
793 793 for f in repo.dirstate.copies():
794 794 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
795 795
796 796 def debugdata(ui, file_, rev):
797 797 """dump the contents of a data file revision"""
798 798 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
799 799 try:
800 800 ui.write(r.revision(r.lookup(rev)))
801 801 except KeyError:
802 802 raise util.Abort(_('invalid revision identifier %s') % rev)
803 803
804 804 def debugdate(ui, date, range=None, **opts):
805 805 """parse and display a date"""
806 806 if opts["extended"]:
807 807 d = util.parsedate(date, util.extendeddateformats)
808 808 else:
809 809 d = util.parsedate(date)
810 810 ui.write("internal: %s %s\n" % d)
811 811 ui.write("standard: %s\n" % util.datestr(d))
812 812 if range:
813 813 m = util.matchdate(range)
814 814 ui.write("match: %s\n" % m(d[0]))
815 815
816 816 def debugindex(ui, file_):
817 817 """dump the contents of an index file"""
818 818 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
819 819 ui.write(" rev offset length base linkrev" +
820 820 " nodeid p1 p2\n")
821 821 for i in xrange(r.count()):
822 822 node = r.node(i)
823 823 pp = r.parents(node)
824 824 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
825 825 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
826 826 short(node), short(pp[0]), short(pp[1])))
827 827
828 828 def debugindexdot(ui, file_):
829 829 """dump an index DAG as a .dot file"""
830 830 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
831 831 ui.write("digraph G {\n")
832 832 for i in xrange(r.count()):
833 833 node = r.node(i)
834 834 pp = r.parents(node)
835 835 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
836 836 if pp[1] != nullid:
837 837 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
838 838 ui.write("}\n")
839 839
840 840 def debuginstall(ui):
841 841 '''test Mercurial installation'''
842 842
843 843 def writetemp(contents):
844 844 (fd, name) = tempfile.mkstemp()
845 845 f = os.fdopen(fd, "wb")
846 846 f.write(contents)
847 847 f.close()
848 848 return name
849 849
850 850 problems = 0
851 851
852 852 # encoding
853 853 ui.status(_("Checking encoding (%s)...\n") % util._encoding)
854 854 try:
855 855 util.fromlocal("test")
856 856 except util.Abort, inst:
857 857 ui.write(" %s\n" % inst)
858 858 ui.write(_(" (check that your locale is properly set)\n"))
859 859 problems += 1
860 860
861 861 # compiled modules
862 862 ui.status(_("Checking extensions...\n"))
863 863 try:
864 864 import bdiff, mpatch, base85
865 865 except Exception, inst:
866 866 ui.write(" %s\n" % inst)
867 867 ui.write(_(" One or more extensions could not be found"))
868 868 ui.write(_(" (check that you compiled the extensions)\n"))
869 869 problems += 1
870 870
871 871 # templates
872 872 ui.status(_("Checking templates...\n"))
873 873 try:
874 874 import templater
875 875 t = templater.templater(templater.templatepath("map-cmdline.default"))
876 876 except Exception, inst:
877 877 ui.write(" %s\n" % inst)
878 878 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
879 879 problems += 1
880 880
881 881 # patch
882 882 ui.status(_("Checking patch...\n"))
883 path = os.environ.get('PATH', '')
884 883 patcher = ui.config('ui', 'patch')
885 if not patcher:
886 patcher = util.find_in_path('gpatch', path,
887 util.find_in_path('patch', path, None))
884 patcher = ((patcher and util.find_exe(patcher)) or
885 util.find_exe('gpatch') or
886 util.find_exe('patch'))
888 887 if not patcher:
889 888 ui.write(_(" Can't find patch or gpatch in PATH\n"))
890 889 ui.write(_(" (specify a patch utility in your .hgrc file)\n"))
891 890 problems += 1
892 891 else:
893 892 # actually attempt a patch here
894 893 a = "1\n2\n3\n4\n"
895 894 b = "1\n2\n3\ninsert\n4\n"
896 895 fa = writetemp(a)
897 896 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa))
898 897 fd = writetemp(d)
899 898
900 899 files = {}
901 900 try:
902 901 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
903 902 except util.Abort, e:
904 903 ui.write(_(" patch call failed:\n"))
905 904 ui.write(" " + str(e) + "\n")
906 905 problems += 1
907 906 else:
908 907 if list(files) != [os.path.basename(fa)]:
909 908 ui.write(_(" unexpected patch output!"))
910 909 ui.write(_(" (you may have an incompatible version of patch)\n"))
911 910 problems += 1
912 911 a = file(fa).read()
913 912 if a != b:
914 913 ui.write(_(" patch test failed!"))
915 914 ui.write(_(" (you may have an incompatible version of patch)\n"))
916 915 problems += 1
917 916
918 917 os.unlink(fa)
919 918 os.unlink(fd)
920 919
921 920 # merge helper
922 921 ui.status(_("Checking merge helper...\n"))
923 922 cmd = (os.environ.get("HGMERGE") or ui.config("ui", "merge")
924 923 or "hgmerge")
925 cmdpath = util.find_in_path(cmd, path)
926 if not cmdpath:
927 cmdpath = util.find_in_path(cmd.split()[0], path)
924 cmdpath = util.find_exe(cmd) or util.find_exe(cmd.split()[0])
928 925 if not cmdpath:
929 926 if cmd == 'hgmerge':
930 927 ui.write(_(" No merge helper set and can't find default"
931 928 " hgmerge script in PATH\n"))
932 929 ui.write(_(" (specify a merge helper in your .hgrc file)\n"))
933 930 else:
934 931 ui.write(_(" Can't find merge helper '%s' in PATH\n") % cmd)
935 932 ui.write(_(" (specify a merge helper in your .hgrc file)\n"))
936 933 problems += 1
937 934 else:
938 935 # actually attempt a patch here
939 936 fa = writetemp("1\n2\n3\n4\n")
940 937 fl = writetemp("1\n2\n3\ninsert\n4\n")
941 938 fr = writetemp("begin\n1\n2\n3\n4\n")
942 939 r = os.system('%s %s %s %s' % (cmd, fl, fa, fr))
943 940 if r:
944 941 ui.write(_(" got unexpected merge error %d!") % r)
945 942 problems += 1
946 943 m = file(fl).read()
947 944 if m != "begin\n1\n2\n3\ninsert\n4\n":
948 945 ui.write(_(" got unexpected merge results!") % r)
949 946 ui.write(_(" (your merge helper may have the"
950 947 " wrong argument order)\n"))
951 948 ui.write(m)
952 949 os.unlink(fa)
953 950 os.unlink(fl)
954 951 os.unlink(fr)
955 952
956 953 # editor
957 954 ui.status(_("Checking commit editor...\n"))
958 955 editor = (os.environ.get("HGEDITOR") or
959 956 ui.config("ui", "editor") or
960 957 os.environ.get("EDITOR", "vi"))
961 cmdpath = util.find_in_path(editor, path)
962 if not cmdpath:
963 cmdpath = util.find_in_path(editor.split()[0], path)
958 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
964 959 if not cmdpath:
965 960 if editor == 'vi':
966 961 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
967 962 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
968 963 else:
969 964 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
970 965 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
971 966 problems += 1
972 967
973 968 # check username
974 969 ui.status(_("Checking username...\n"))
975 970 user = os.environ.get("HGUSER")
976 971 if user is None:
977 972 user = ui.config("ui", "username")
978 973 if user is None:
979 974 user = os.environ.get("EMAIL")
980 975 if not user:
981 976 ui.warn(" ")
982 977 ui.username()
983 978 ui.write(_(" (specify a username in your .hgrc file)\n"))
984 979
985 980 if not problems:
986 981 ui.status(_("No problems detected\n"))
987 982 else:
988 983 ui.write(_("%s problems detected,"
989 984 " please check your install!\n") % problems)
990 985
991 986 return problems
992 987
993 988 def debugrename(ui, repo, file1, *pats, **opts):
994 989 """dump rename information"""
995 990
996 991 ctx = repo.changectx(opts.get('rev', 'tip'))
997 992 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
998 993 ctx.node()):
999 994 m = ctx.filectx(abs).renamed()
1000 995 if m:
1001 996 ui.write(_("%s renamed from %s:%s\n") % (rel, m[0], hex(m[1])))
1002 997 else:
1003 998 ui.write(_("%s not renamed\n") % rel)
1004 999
1005 1000 def debugwalk(ui, repo, *pats, **opts):
1006 1001 """show how files match on given patterns"""
1007 1002 items = list(cmdutil.walk(repo, pats, opts))
1008 1003 if not items:
1009 1004 return
1010 1005 fmt = '%%s %%-%ds %%-%ds %%s' % (
1011 1006 max([len(abs) for (src, abs, rel, exact) in items]),
1012 1007 max([len(rel) for (src, abs, rel, exact) in items]))
1013 1008 for src, abs, rel, exact in items:
1014 1009 line = fmt % (src, abs, rel, exact and 'exact' or '')
1015 1010 ui.write("%s\n" % line.rstrip())
1016 1011
1017 1012 def diff(ui, repo, *pats, **opts):
1018 1013 """diff repository (or selected files)
1019 1014
1020 1015 Show differences between revisions for the specified files.
1021 1016
1022 1017 Differences between files are shown using the unified diff format.
1023 1018
1024 1019 NOTE: diff may generate unexpected results for merges, as it will
1025 1020 default to comparing against the working directory's first parent
1026 1021 changeset if no revisions are specified.
1027 1022
1028 1023 When two revision arguments are given, then changes are shown
1029 1024 between those revisions. If only one revision is specified then
1030 1025 that revision is compared to the working directory, and, when no
1031 1026 revisions are specified, the working directory files are compared
1032 1027 to its parent.
1033 1028
1034 1029 Without the -a option, diff will avoid generating diffs of files
1035 1030 it detects as binary. With -a, diff will generate a diff anyway,
1036 1031 probably with undesirable results.
1037 1032 """
1038 1033 node1, node2 = cmdutil.revpair(repo, opts['rev'])
1039 1034
1040 1035 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1041 1036
1042 1037 patch.diff(repo, node1, node2, fns, match=matchfn,
1043 1038 opts=patch.diffopts(ui, opts))
1044 1039
1045 1040 def export(ui, repo, *changesets, **opts):
1046 1041 """dump the header and diffs for one or more changesets
1047 1042
1048 1043 Print the changeset header and diffs for one or more revisions.
1049 1044
1050 1045 The information shown in the changeset header is: author,
1051 1046 changeset hash, parent(s) and commit comment.
1052 1047
1053 1048 NOTE: export may generate unexpected diff output for merge changesets,
1054 1049 as it will compare the merge changeset against its first parent only.
1055 1050
1056 1051 Output may be to a file, in which case the name of the file is
1057 1052 given using a format string. The formatting rules are as follows:
1058 1053
1059 1054 %% literal "%" character
1060 1055 %H changeset hash (40 bytes of hexadecimal)
1061 1056 %N number of patches being generated
1062 1057 %R changeset revision number
1063 1058 %b basename of the exporting repository
1064 1059 %h short-form changeset hash (12 bytes of hexadecimal)
1065 1060 %n zero-padded sequence number, starting at 1
1066 1061 %r zero-padded changeset revision number
1067 1062
1068 1063 Without the -a option, export will avoid generating diffs of files
1069 1064 it detects as binary. With -a, export will generate a diff anyway,
1070 1065 probably with undesirable results.
1071 1066
1072 1067 With the --switch-parent option, the diff will be against the second
1073 1068 parent. It can be useful to review a merge.
1074 1069 """
1075 1070 if not changesets:
1076 1071 raise util.Abort(_("export requires at least one changeset"))
1077 1072 revs = cmdutil.revrange(repo, changesets)
1078 1073 if len(revs) > 1:
1079 1074 ui.note(_('exporting patches:\n'))
1080 1075 else:
1081 1076 ui.note(_('exporting patch:\n'))
1082 1077 patch.export(repo, revs, template=opts['output'],
1083 1078 switch_parent=opts['switch_parent'],
1084 1079 opts=patch.diffopts(ui, opts))
1085 1080
1086 1081 def grep(ui, repo, pattern, *pats, **opts):
1087 1082 """search for a pattern in specified files and revisions
1088 1083
1089 1084 Search revisions of files for a regular expression.
1090 1085
1091 1086 This command behaves differently than Unix grep. It only accepts
1092 1087 Python/Perl regexps. It searches repository history, not the
1093 1088 working directory. It always prints the revision number in which
1094 1089 a match appears.
1095 1090
1096 1091 By default, grep only prints output for the first revision of a
1097 1092 file in which it finds a match. To get it to print every revision
1098 1093 that contains a change in match status ("-" for a match that
1099 1094 becomes a non-match, or "+" for a non-match that becomes a match),
1100 1095 use the --all flag.
1101 1096 """
1102 1097 reflags = 0
1103 1098 if opts['ignore_case']:
1104 1099 reflags |= re.I
1105 1100 regexp = re.compile(pattern, reflags)
1106 1101 sep, eol = ':', '\n'
1107 1102 if opts['print0']:
1108 1103 sep = eol = '\0'
1109 1104
1110 1105 fcache = {}
1111 1106 def getfile(fn):
1112 1107 if fn not in fcache:
1113 1108 fcache[fn] = repo.file(fn)
1114 1109 return fcache[fn]
1115 1110
1116 1111 def matchlines(body):
1117 1112 begin = 0
1118 1113 linenum = 0
1119 1114 while True:
1120 1115 match = regexp.search(body, begin)
1121 1116 if not match:
1122 1117 break
1123 1118 mstart, mend = match.span()
1124 1119 linenum += body.count('\n', begin, mstart) + 1
1125 1120 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1126 1121 lend = body.find('\n', mend)
1127 1122 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1128 1123 begin = lend + 1
1129 1124
1130 1125 class linestate(object):
1131 1126 def __init__(self, line, linenum, colstart, colend):
1132 1127 self.line = line
1133 1128 self.linenum = linenum
1134 1129 self.colstart = colstart
1135 1130 self.colend = colend
1136 1131
1137 1132 def __eq__(self, other):
1138 1133 return self.line == other.line
1139 1134
1140 1135 matches = {}
1141 1136 copies = {}
1142 1137 def grepbody(fn, rev, body):
1143 1138 matches[rev].setdefault(fn, [])
1144 1139 m = matches[rev][fn]
1145 1140 for lnum, cstart, cend, line in matchlines(body):
1146 1141 s = linestate(line, lnum, cstart, cend)
1147 1142 m.append(s)
1148 1143
1149 1144 def difflinestates(a, b):
1150 1145 sm = difflib.SequenceMatcher(None, a, b)
1151 1146 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1152 1147 if tag == 'insert':
1153 1148 for i in xrange(blo, bhi):
1154 1149 yield ('+', b[i])
1155 1150 elif tag == 'delete':
1156 1151 for i in xrange(alo, ahi):
1157 1152 yield ('-', a[i])
1158 1153 elif tag == 'replace':
1159 1154 for i in xrange(alo, ahi):
1160 1155 yield ('-', a[i])
1161 1156 for i in xrange(blo, bhi):
1162 1157 yield ('+', b[i])
1163 1158
1164 1159 prev = {}
1165 1160 def display(fn, rev, states, prevstates):
1166 1161 found = False
1167 1162 filerevmatches = {}
1168 1163 r = prev.get(fn, -1)
1169 1164 if opts['all']:
1170 1165 iter = difflinestates(states, prevstates)
1171 1166 else:
1172 1167 iter = [('', l) for l in prevstates]
1173 1168 for change, l in iter:
1174 1169 cols = [fn, str(r)]
1175 1170 if opts['line_number']:
1176 1171 cols.append(str(l.linenum))
1177 1172 if opts['all']:
1178 1173 cols.append(change)
1179 1174 if opts['user']:
1180 1175 cols.append(ui.shortuser(get(r)[1]))
1181 1176 if opts['files_with_matches']:
1182 1177 c = (fn, r)
1183 1178 if c in filerevmatches:
1184 1179 continue
1185 1180 filerevmatches[c] = 1
1186 1181 else:
1187 1182 cols.append(l.line)
1188 1183 ui.write(sep.join(cols), eol)
1189 1184 found = True
1190 1185 return found
1191 1186
1192 1187 fstate = {}
1193 1188 skip = {}
1194 1189 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1195 1190 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1196 1191 found = False
1197 1192 follow = opts.get('follow')
1198 1193 for st, rev, fns in changeiter:
1199 1194 if st == 'window':
1200 1195 matches.clear()
1201 1196 elif st == 'add':
1202 1197 mf = repo.changectx(rev).manifest()
1203 1198 matches[rev] = {}
1204 1199 for fn in fns:
1205 1200 if fn in skip:
1206 1201 continue
1207 1202 fstate.setdefault(fn, {})
1208 1203 try:
1209 1204 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1210 1205 if follow:
1211 1206 copied = getfile(fn).renamed(mf[fn])
1212 1207 if copied:
1213 1208 copies.setdefault(rev, {})[fn] = copied[0]
1214 1209 except KeyError:
1215 1210 pass
1216 1211 elif st == 'iter':
1217 1212 states = matches[rev].items()
1218 1213 states.sort()
1219 1214 for fn, m in states:
1220 1215 copy = copies.get(rev, {}).get(fn)
1221 1216 if fn in skip:
1222 1217 if copy:
1223 1218 skip[copy] = True
1224 1219 continue
1225 1220 if fn in prev or fstate[fn]:
1226 1221 r = display(fn, rev, m, fstate[fn])
1227 1222 found = found or r
1228 1223 if r and not opts['all']:
1229 1224 skip[fn] = True
1230 1225 if copy:
1231 1226 skip[copy] = True
1232 1227 fstate[fn] = m
1233 1228 if copy:
1234 1229 fstate[copy] = m
1235 1230 prev[fn] = rev
1236 1231
1237 1232 fstate = fstate.items()
1238 1233 fstate.sort()
1239 1234 for fn, state in fstate:
1240 1235 if fn in skip:
1241 1236 continue
1242 1237 if fn not in copies.get(prev[fn], {}):
1243 1238 found = display(fn, rev, {}, state) or found
1244 1239 return (not found and 1) or 0
1245 1240
1246 1241 def heads(ui, repo, **opts):
1247 1242 """show current repository heads
1248 1243
1249 1244 Show all repository head changesets.
1250 1245
1251 1246 Repository "heads" are changesets that don't have children
1252 1247 changesets. They are where development generally takes place and
1253 1248 are the usual targets for update and merge operations.
1254 1249 """
1255 1250 if opts['rev']:
1256 1251 heads = repo.heads(repo.lookup(opts['rev']))
1257 1252 else:
1258 1253 heads = repo.heads()
1259 1254 displayer = cmdutil.show_changeset(ui, repo, opts)
1260 1255 for n in heads:
1261 1256 displayer.show(changenode=n)
1262 1257
1263 1258 def help_(ui, name=None, with_version=False):
1264 1259 """show help for a command, extension, or list of commands
1265 1260
1266 1261 With no arguments, print a list of commands and short help.
1267 1262
1268 1263 Given a command name, print help for that command.
1269 1264
1270 1265 Given an extension name, print help for that extension, and the
1271 1266 commands it provides."""
1272 1267 option_lists = []
1273 1268
1274 1269 def addglobalopts(aliases):
1275 1270 if ui.verbose:
1276 1271 option_lists.append((_("global options:"), globalopts))
1277 1272 if name == 'shortlist':
1278 1273 option_lists.append((_('use "hg help" for the full list '
1279 1274 'of commands'), ()))
1280 1275 else:
1281 1276 if name == 'shortlist':
1282 1277 msg = _('use "hg help" for the full list of commands '
1283 1278 'or "hg -v" for details')
1284 1279 elif aliases:
1285 1280 msg = _('use "hg -v help%s" to show aliases and '
1286 1281 'global options') % (name and " " + name or "")
1287 1282 else:
1288 1283 msg = _('use "hg -v help %s" to show global options') % name
1289 1284 option_lists.append((msg, ()))
1290 1285
1291 1286 def helpcmd(name):
1292 1287 if with_version:
1293 1288 version_(ui)
1294 1289 ui.write('\n')
1295 1290 aliases, i = findcmd(ui, name)
1296 1291 # synopsis
1297 1292 ui.write("%s\n\n" % i[2])
1298 1293
1299 1294 # description
1300 1295 doc = i[0].__doc__
1301 1296 if not doc:
1302 1297 doc = _("(No help text available)")
1303 1298 if ui.quiet:
1304 1299 doc = doc.splitlines(0)[0]
1305 1300 ui.write("%s\n" % doc.rstrip())
1306 1301
1307 1302 if not ui.quiet:
1308 1303 # aliases
1309 1304 if len(aliases) > 1:
1310 1305 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1311 1306
1312 1307 # options
1313 1308 if i[1]:
1314 1309 option_lists.append((_("options:\n"), i[1]))
1315 1310
1316 1311 addglobalopts(False)
1317 1312
1318 1313 def helplist(select=None):
1319 1314 h = {}
1320 1315 cmds = {}
1321 1316 for c, e in table.items():
1322 1317 f = c.split("|", 1)[0]
1323 1318 if select and not select(f):
1324 1319 continue
1325 1320 if name == "shortlist" and not f.startswith("^"):
1326 1321 continue
1327 1322 f = f.lstrip("^")
1328 1323 if not ui.debugflag and f.startswith("debug"):
1329 1324 continue
1330 1325 doc = e[0].__doc__
1331 1326 if not doc:
1332 1327 doc = _("(No help text available)")
1333 1328 h[f] = doc.splitlines(0)[0].rstrip()
1334 1329 cmds[f] = c.lstrip("^")
1335 1330
1336 1331 fns = h.keys()
1337 1332 fns.sort()
1338 1333 m = max(map(len, fns))
1339 1334 for f in fns:
1340 1335 if ui.verbose:
1341 1336 commands = cmds[f].replace("|",", ")
1342 1337 ui.write(" %s:\n %s\n"%(commands, h[f]))
1343 1338 else:
1344 1339 ui.write(' %-*s %s\n' % (m, f, h[f]))
1345 1340
1346 1341 if not ui.quiet:
1347 1342 addglobalopts(True)
1348 1343
1349 1344 def helptopic(name):
1350 1345 v = None
1351 1346 for i in help.helptable:
1352 1347 l = i.split('|')
1353 1348 if name in l:
1354 1349 v = i
1355 1350 header = l[-1]
1356 1351 if not v:
1357 1352 raise UnknownCommand(name)
1358 1353
1359 1354 # description
1360 1355 doc = help.helptable[v]
1361 1356 if not doc:
1362 1357 doc = _("(No help text available)")
1363 1358 if callable(doc):
1364 1359 doc = doc()
1365 1360
1366 1361 ui.write("%s\n" % header)
1367 1362 ui.write("%s\n" % doc.rstrip())
1368 1363
1369 1364 def helpext(name):
1370 1365 try:
1371 1366 mod = findext(name)
1372 1367 except KeyError:
1373 1368 raise UnknownCommand(name)
1374 1369
1375 1370 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
1376 1371 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1377 1372 for d in doc[1:]:
1378 1373 ui.write(d, '\n')
1379 1374
1380 1375 ui.status('\n')
1381 1376
1382 1377 try:
1383 1378 ct = mod.cmdtable
1384 1379 except AttributeError:
1385 1380 ui.status(_('no commands defined\n'))
1386 1381 return
1387 1382
1388 1383 ui.status(_('list of commands:\n\n'))
1389 1384 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct])
1390 1385 helplist(modcmds.has_key)
1391 1386
1392 1387 if name and name != 'shortlist':
1393 1388 i = None
1394 1389 for f in (helpcmd, helptopic, helpext):
1395 1390 try:
1396 1391 f(name)
1397 1392 i = None
1398 1393 break
1399 1394 except UnknownCommand, inst:
1400 1395 i = inst
1401 1396 if i:
1402 1397 raise i
1403 1398
1404 1399 else:
1405 1400 # program name
1406 1401 if ui.verbose or with_version:
1407 1402 version_(ui)
1408 1403 else:
1409 1404 ui.status(_("Mercurial Distributed SCM\n"))
1410 1405 ui.status('\n')
1411 1406
1412 1407 # list of commands
1413 1408 if name == "shortlist":
1414 1409 ui.status(_('basic commands:\n\n'))
1415 1410 else:
1416 1411 ui.status(_('list of commands:\n\n'))
1417 1412
1418 1413 helplist()
1419 1414
1420 1415 # list all option lists
1421 1416 opt_output = []
1422 1417 for title, options in option_lists:
1423 1418 opt_output.append(("\n%s" % title, None))
1424 1419 for shortopt, longopt, default, desc in options:
1425 1420 if "DEPRECATED" in desc and not ui.verbose: continue
1426 1421 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1427 1422 longopt and " --%s" % longopt),
1428 1423 "%s%s" % (desc,
1429 1424 default
1430 1425 and _(" (default: %s)") % default
1431 1426 or "")))
1432 1427
1433 1428 if opt_output:
1434 1429 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1435 1430 for first, second in opt_output:
1436 1431 if second:
1437 1432 ui.write(" %-*s %s\n" % (opts_len, first, second))
1438 1433 else:
1439 1434 ui.write("%s\n" % first)
1440 1435
1441 1436 def identify(ui, repo):
1442 1437 """print information about the working copy
1443 1438
1444 1439 Print a short summary of the current state of the repo.
1445 1440
1446 1441 This summary identifies the repository state using one or two parent
1447 1442 hash identifiers, followed by a "+" if there are uncommitted changes
1448 1443 in the working directory, followed by a list of tags for this revision.
1449 1444 """
1450 1445 parents = [p for p in repo.dirstate.parents() if p != nullid]
1451 1446 if not parents:
1452 1447 ui.write(_("unknown\n"))
1453 1448 return
1454 1449
1455 1450 hexfunc = ui.debugflag and hex or short
1456 1451 modified, added, removed, deleted = repo.status()[:4]
1457 1452 output = ["%s%s" %
1458 1453 ('+'.join([hexfunc(parent) for parent in parents]),
1459 1454 (modified or added or removed or deleted) and "+" or "")]
1460 1455
1461 1456 if not ui.quiet:
1462 1457
1463 1458 branch = util.tolocal(repo.workingctx().branch())
1464 1459 if branch != 'default':
1465 1460 output.append("(%s)" % branch)
1466 1461
1467 1462 # multiple tags for a single parent separated by '/'
1468 1463 parenttags = ['/'.join(tags)
1469 1464 for tags in map(repo.nodetags, parents) if tags]
1470 1465 # tags for multiple parents separated by ' + '
1471 1466 if parenttags:
1472 1467 output.append(' + '.join(parenttags))
1473 1468
1474 1469 ui.write("%s\n" % ' '.join(output))
1475 1470
1476 1471 def import_(ui, repo, patch1, *patches, **opts):
1477 1472 """import an ordered set of patches
1478 1473
1479 1474 Import a list of patches and commit them individually.
1480 1475
1481 1476 If there are outstanding changes in the working directory, import
1482 1477 will abort unless given the -f flag.
1483 1478
1484 1479 You can import a patch straight from a mail message. Even patches
1485 1480 as attachments work (body part must be type text/plain or
1486 1481 text/x-patch to be used). From and Subject headers of email
1487 1482 message are used as default committer and commit message. All
1488 1483 text/plain body parts before first diff are added to commit
1489 1484 message.
1490 1485
1491 1486 If the imported patch was generated by hg export, user and description
1492 1487 from patch override values from message headers and body. Values
1493 1488 given on command line with -m and -u override these.
1494 1489
1495 1490 If --exact is specified, import will set the working directory
1496 1491 to the parent of each patch before applying it, and will abort
1497 1492 if the resulting changeset has a different ID than the one
1498 1493 recorded in the patch. This may happen due to character set
1499 1494 problems or other deficiencies in the text patch format.
1500 1495
1501 1496 To read a patch from standard input, use patch name "-".
1502 1497 """
1503 1498 patches = (patch1,) + patches
1504 1499
1505 1500 if opts.get('exact') or not opts['force']:
1506 1501 bail_if_changed(repo)
1507 1502
1508 1503 d = opts["base"]
1509 1504 strip = opts["strip"]
1510 1505
1511 1506 wlock = repo.wlock()
1512 1507 lock = repo.lock()
1513 1508
1514 1509 for p in patches:
1515 1510 pf = os.path.join(d, p)
1516 1511
1517 1512 if pf == '-':
1518 1513 ui.status(_("applying patch from stdin\n"))
1519 1514 tmpname, message, user, date, nodeid, p1, p2 = patch.extract(ui, sys.stdin)
1520 1515 else:
1521 1516 ui.status(_("applying %s\n") % p)
1522 1517 tmpname, message, user, date, nodeid, p1, p2 = patch.extract(ui, file(pf))
1523 1518
1524 1519 if tmpname is None:
1525 1520 raise util.Abort(_('no diffs found'))
1526 1521
1527 1522 try:
1528 1523 cmdline_message = logmessage(opts)
1529 1524 if cmdline_message:
1530 1525 # pickup the cmdline msg
1531 1526 message = cmdline_message
1532 1527 elif message:
1533 1528 # pickup the patch msg
1534 1529 message = message.strip()
1535 1530 else:
1536 1531 # launch the editor
1537 1532 message = None
1538 1533 ui.debug(_('message:\n%s\n') % message)
1539 1534
1540 1535 wp = repo.workingctx().parents()
1541 1536 if opts.get('exact'):
1542 1537 if not nodeid or not p1:
1543 1538 raise util.Abort(_('not a mercurial patch'))
1544 1539 p1 = repo.lookup(p1)
1545 1540 p2 = repo.lookup(p2 or hex(nullid))
1546 1541
1547 1542 if p1 != wp[0].node():
1548 1543 hg.clean(repo, p1, wlock=wlock)
1549 1544 repo.dirstate.setparents(p1, p2)
1550 1545 elif p2:
1551 1546 try:
1552 1547 p1 = repo.lookup(p1)
1553 1548 p2 = repo.lookup(p2)
1554 1549 if p1 == wp[0].node():
1555 1550 repo.dirstate.setparents(p1, p2)
1556 1551 except hg.RepoError:
1557 1552 pass
1558 1553
1559 1554 files = {}
1560 1555 try:
1561 1556 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1562 1557 files=files)
1563 1558 finally:
1564 1559 files = patch.updatedir(ui, repo, files, wlock=wlock)
1565 1560 n = repo.commit(files, message, user, date, wlock=wlock, lock=lock)
1566 1561 if opts.get('exact'):
1567 1562 if hex(n) != nodeid:
1568 1563 repo.rollback(wlock=wlock, lock=lock)
1569 1564 raise util.Abort(_('patch is damaged or loses information'))
1570 1565 finally:
1571 1566 os.unlink(tmpname)
1572 1567
1573 1568 def incoming(ui, repo, source="default", **opts):
1574 1569 """show new changesets found in source
1575 1570
1576 1571 Show new changesets found in the specified path/URL or the default
1577 1572 pull location. These are the changesets that would be pulled if a pull
1578 1573 was requested.
1579 1574
1580 1575 For remote repository, using --bundle avoids downloading the changesets
1581 1576 twice if the incoming is followed by a pull.
1582 1577
1583 1578 See pull for valid source format details.
1584 1579 """
1585 1580 source = ui.expandpath(source)
1586 1581 setremoteconfig(ui, opts)
1587 1582
1588 1583 other = hg.repository(ui, source)
1589 1584 ui.status(_('comparing with %s\n') % source)
1590 1585 incoming = repo.findincoming(other, force=opts["force"])
1591 1586 if not incoming:
1592 1587 try:
1593 1588 os.unlink(opts["bundle"])
1594 1589 except:
1595 1590 pass
1596 1591 ui.status(_("no changes found\n"))
1597 1592 return 1
1598 1593
1599 1594 cleanup = None
1600 1595 try:
1601 1596 fname = opts["bundle"]
1602 1597 if fname or not other.local():
1603 1598 # create a bundle (uncompressed if other repo is not local)
1604 1599 cg = other.changegroup(incoming, "incoming")
1605 1600 bundletype = other.local() and "HG10BZ" or "HG10UN"
1606 1601 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1607 1602 # keep written bundle?
1608 1603 if opts["bundle"]:
1609 1604 cleanup = None
1610 1605 if not other.local():
1611 1606 # use the created uncompressed bundlerepo
1612 1607 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1613 1608
1614 1609 revs = None
1615 1610 if opts['rev']:
1616 1611 revs = [other.lookup(rev) for rev in opts['rev']]
1617 1612 o = other.changelog.nodesbetween(incoming, revs)[0]
1618 1613 if opts['newest_first']:
1619 1614 o.reverse()
1620 1615 displayer = cmdutil.show_changeset(ui, other, opts)
1621 1616 for n in o:
1622 1617 parents = [p for p in other.changelog.parents(n) if p != nullid]
1623 1618 if opts['no_merges'] and len(parents) == 2:
1624 1619 continue
1625 1620 displayer.show(changenode=n)
1626 1621 finally:
1627 1622 if hasattr(other, 'close'):
1628 1623 other.close()
1629 1624 if cleanup:
1630 1625 os.unlink(cleanup)
1631 1626
1632 1627 def init(ui, dest=".", **opts):
1633 1628 """create a new repository in the given directory
1634 1629
1635 1630 Initialize a new repository in the given directory. If the given
1636 1631 directory does not exist, it is created.
1637 1632
1638 1633 If no directory is given, the current directory is used.
1639 1634
1640 1635 It is possible to specify an ssh:// URL as the destination.
1641 1636 Look at the help text for the pull command for important details
1642 1637 about ssh:// URLs.
1643 1638 """
1644 1639 setremoteconfig(ui, opts)
1645 1640 hg.repository(ui, dest, create=1)
1646 1641
1647 1642 def locate(ui, repo, *pats, **opts):
1648 1643 """locate files matching specific patterns
1649 1644
1650 1645 Print all files under Mercurial control whose names match the
1651 1646 given patterns.
1652 1647
1653 1648 This command searches the entire repository by default. To search
1654 1649 just the current directory and its subdirectories, use "--include .".
1655 1650
1656 1651 If no patterns are given to match, this command prints all file
1657 1652 names.
1658 1653
1659 1654 If you want to feed the output of this command into the "xargs"
1660 1655 command, use the "-0" option to both this command and "xargs".
1661 1656 This will avoid the problem of "xargs" treating single filenames
1662 1657 that contain white space as multiple filenames.
1663 1658 """
1664 1659 end = opts['print0'] and '\0' or '\n'
1665 1660 rev = opts['rev']
1666 1661 if rev:
1667 1662 node = repo.lookup(rev)
1668 1663 else:
1669 1664 node = None
1670 1665
1671 1666 ret = 1
1672 1667 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
1673 1668 badmatch=util.always,
1674 1669 default='relglob'):
1675 1670 if src == 'b':
1676 1671 continue
1677 1672 if not node and repo.dirstate.state(abs) == '?':
1678 1673 continue
1679 1674 if opts['fullpath']:
1680 1675 ui.write(os.path.join(repo.root, abs), end)
1681 1676 else:
1682 1677 ui.write(((pats and rel) or abs), end)
1683 1678 ret = 0
1684 1679
1685 1680 return ret
1686 1681
1687 1682 def log(ui, repo, *pats, **opts):
1688 1683 """show revision history of entire repository or files
1689 1684
1690 1685 Print the revision history of the specified files or the entire
1691 1686 project.
1692 1687
1693 1688 File history is shown without following rename or copy history of
1694 1689 files. Use -f/--follow with a file name to follow history across
1695 1690 renames and copies. --follow without a file name will only show
1696 1691 ancestors or descendants of the starting revision. --follow-first
1697 1692 only follows the first parent of merge revisions.
1698 1693
1699 1694 If no revision range is specified, the default is tip:0 unless
1700 1695 --follow is set, in which case the working directory parent is
1701 1696 used as the starting revision.
1702 1697
1703 1698 By default this command outputs: changeset id and hash, tags,
1704 1699 non-trivial parents, user, date and time, and a summary for each
1705 1700 commit. When the -v/--verbose switch is used, the list of changed
1706 1701 files and full commit message is shown.
1707 1702
1708 1703 NOTE: log -p may generate unexpected diff output for merge
1709 1704 changesets, as it will compare the merge changeset against its
1710 1705 first parent only. Also, the files: list will only reflect files
1711 1706 that are different from BOTH parents.
1712 1707
1713 1708 """
1714 1709
1715 1710 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1716 1711 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1717 1712
1718 1713 if opts['limit']:
1719 1714 try:
1720 1715 limit = int(opts['limit'])
1721 1716 except ValueError:
1722 1717 raise util.Abort(_('limit must be a positive integer'))
1723 1718 if limit <= 0: raise util.Abort(_('limit must be positive'))
1724 1719 else:
1725 1720 limit = sys.maxint
1726 1721 count = 0
1727 1722
1728 1723 if opts['copies'] and opts['rev']:
1729 1724 endrev = max(cmdutil.revrange(repo, opts['rev'])) + 1
1730 1725 else:
1731 1726 endrev = repo.changelog.count()
1732 1727 rcache = {}
1733 1728 ncache = {}
1734 1729 dcache = []
1735 1730 def getrenamed(fn, rev, man):
1736 1731 '''looks up all renames for a file (up to endrev) the first
1737 1732 time the file is given. It indexes on the changerev and only
1738 1733 parses the manifest if linkrev != changerev.
1739 1734 Returns rename info for fn at changerev rev.'''
1740 1735 if fn not in rcache:
1741 1736 rcache[fn] = {}
1742 1737 ncache[fn] = {}
1743 1738 fl = repo.file(fn)
1744 1739 for i in xrange(fl.count()):
1745 1740 node = fl.node(i)
1746 1741 lr = fl.linkrev(node)
1747 1742 renamed = fl.renamed(node)
1748 1743 rcache[fn][lr] = renamed
1749 1744 if renamed:
1750 1745 ncache[fn][node] = renamed
1751 1746 if lr >= endrev:
1752 1747 break
1753 1748 if rev in rcache[fn]:
1754 1749 return rcache[fn][rev]
1755 1750 mr = repo.manifest.rev(man)
1756 1751 if repo.manifest.parentrevs(mr) != (mr - 1, nullrev):
1757 1752 return ncache[fn].get(repo.manifest.find(man, fn)[0])
1758 1753 if not dcache or dcache[0] != man:
1759 1754 dcache[:] = [man, repo.manifest.readdelta(man)]
1760 1755 if fn in dcache[1]:
1761 1756 return ncache[fn].get(dcache[1][fn])
1762 1757 return None
1763 1758
1764 1759 df = False
1765 1760 if opts["date"]:
1766 1761 df = util.matchdate(opts["date"])
1767 1762
1768 1763 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1769 1764 for st, rev, fns in changeiter:
1770 1765 if st == 'add':
1771 1766 changenode = repo.changelog.node(rev)
1772 1767 parents = [p for p in repo.changelog.parentrevs(rev)
1773 1768 if p != nullrev]
1774 1769 if opts['no_merges'] and len(parents) == 2:
1775 1770 continue
1776 1771 if opts['only_merges'] and len(parents) != 2:
1777 1772 continue
1778 1773
1779 1774 if df:
1780 1775 changes = get(rev)
1781 1776 if not df(changes[2][0]):
1782 1777 continue
1783 1778
1784 1779 if opts['keyword']:
1785 1780 changes = get(rev)
1786 1781 miss = 0
1787 1782 for k in [kw.lower() for kw in opts['keyword']]:
1788 1783 if not (k in changes[1].lower() or
1789 1784 k in changes[4].lower() or
1790 1785 k in " ".join(changes[3]).lower()):
1791 1786 miss = 1
1792 1787 break
1793 1788 if miss:
1794 1789 continue
1795 1790
1796 1791 copies = []
1797 1792 if opts.get('copies') and rev:
1798 1793 mf = get(rev)[0]
1799 1794 for fn in get(rev)[3]:
1800 1795 rename = getrenamed(fn, rev, mf)
1801 1796 if rename:
1802 1797 copies.append((fn, rename[0]))
1803 1798 displayer.show(rev, changenode, copies=copies)
1804 1799 elif st == 'iter':
1805 1800 if count == limit: break
1806 1801 if displayer.flush(rev):
1807 1802 count += 1
1808 1803
1809 1804 def manifest(ui, repo, rev=None):
1810 1805 """output the current or given revision of the project manifest
1811 1806
1812 1807 Print a list of version controlled files for the given revision.
1813 1808 If no revision is given, the parent of the working directory is used,
1814 1809 or tip if no revision is checked out.
1815 1810
1816 1811 The manifest is the list of files being version controlled. If no revision
1817 1812 is given then the first parent of the working directory is used.
1818 1813
1819 1814 With -v flag, print file permissions. With --debug flag, print
1820 1815 file revision hashes.
1821 1816 """
1822 1817
1823 1818 m = repo.changectx(rev).manifest()
1824 1819 files = m.keys()
1825 1820 files.sort()
1826 1821
1827 1822 for f in files:
1828 1823 if ui.debugflag:
1829 1824 ui.write("%40s " % hex(m[f]))
1830 1825 if ui.verbose:
1831 1826 ui.write("%3s " % (m.execf(f) and "755" or "644"))
1832 1827 ui.write("%s\n" % f)
1833 1828
1834 1829 def merge(ui, repo, node=None, force=None):
1835 1830 """merge working directory with another revision
1836 1831
1837 1832 Merge the contents of the current working directory and the
1838 1833 requested revision. Files that changed between either parent are
1839 1834 marked as changed for the next commit and a commit must be
1840 1835 performed before any further updates are allowed.
1841 1836
1842 1837 If no revision is specified, the working directory's parent is a
1843 1838 head revision, and the repository contains exactly one other head,
1844 1839 the other head is merged with by default. Otherwise, an explicit
1845 1840 revision to merge with must be provided.
1846 1841 """
1847 1842
1848 1843 if not node:
1849 1844 heads = repo.heads()
1850 1845 if len(heads) > 2:
1851 1846 raise util.Abort(_('repo has %d heads - '
1852 1847 'please merge with an explicit rev') %
1853 1848 len(heads))
1854 1849 if len(heads) == 1:
1855 1850 raise util.Abort(_('there is nothing to merge - '
1856 1851 'use "hg update" instead'))
1857 1852 parent = repo.dirstate.parents()[0]
1858 1853 if parent not in heads:
1859 1854 raise util.Abort(_('working dir not at a head rev - '
1860 1855 'use "hg update" or merge with an explicit rev'))
1861 1856 node = parent == heads[0] and heads[-1] or heads[0]
1862 1857 return hg.merge(repo, node, force=force)
1863 1858
1864 1859 def outgoing(ui, repo, dest=None, **opts):
1865 1860 """show changesets not found in destination
1866 1861
1867 1862 Show changesets not found in the specified destination repository or
1868 1863 the default push location. These are the changesets that would be pushed
1869 1864 if a push was requested.
1870 1865
1871 1866 See pull for valid destination format details.
1872 1867 """
1873 1868 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1874 1869 setremoteconfig(ui, opts)
1875 1870 revs = None
1876 1871 if opts['rev']:
1877 1872 revs = [repo.lookup(rev) for rev in opts['rev']]
1878 1873
1879 1874 other = hg.repository(ui, dest)
1880 1875 ui.status(_('comparing with %s\n') % dest)
1881 1876 o = repo.findoutgoing(other, force=opts['force'])
1882 1877 if not o:
1883 1878 ui.status(_("no changes found\n"))
1884 1879 return 1
1885 1880 o = repo.changelog.nodesbetween(o, revs)[0]
1886 1881 if opts['newest_first']:
1887 1882 o.reverse()
1888 1883 displayer = cmdutil.show_changeset(ui, repo, opts)
1889 1884 for n in o:
1890 1885 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1891 1886 if opts['no_merges'] and len(parents) == 2:
1892 1887 continue
1893 1888 displayer.show(changenode=n)
1894 1889
1895 1890 def parents(ui, repo, file_=None, **opts):
1896 1891 """show the parents of the working dir or revision
1897 1892
1898 1893 Print the working directory's parent revisions.
1899 1894 """
1900 1895 rev = opts.get('rev')
1901 1896 if rev:
1902 1897 if file_:
1903 1898 ctx = repo.filectx(file_, changeid=rev)
1904 1899 else:
1905 1900 ctx = repo.changectx(rev)
1906 1901 p = [cp.node() for cp in ctx.parents()]
1907 1902 else:
1908 1903 p = repo.dirstate.parents()
1909 1904
1910 1905 displayer = cmdutil.show_changeset(ui, repo, opts)
1911 1906 for n in p:
1912 1907 if n != nullid:
1913 1908 displayer.show(changenode=n)
1914 1909
1915 1910 def paths(ui, repo, search=None):
1916 1911 """show definition of symbolic path names
1917 1912
1918 1913 Show definition of symbolic path name NAME. If no name is given, show
1919 1914 definition of available names.
1920 1915
1921 1916 Path names are defined in the [paths] section of /etc/mercurial/hgrc
1922 1917 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
1923 1918 """
1924 1919 if search:
1925 1920 for name, path in ui.configitems("paths"):
1926 1921 if name == search:
1927 1922 ui.write("%s\n" % path)
1928 1923 return
1929 1924 ui.warn(_("not found!\n"))
1930 1925 return 1
1931 1926 else:
1932 1927 for name, path in ui.configitems("paths"):
1933 1928 ui.write("%s = %s\n" % (name, path))
1934 1929
1935 1930 def postincoming(ui, repo, modheads, optupdate):
1936 1931 if modheads == 0:
1937 1932 return
1938 1933 if optupdate:
1939 1934 if modheads == 1:
1940 1935 return hg.update(repo, repo.changelog.tip()) # update
1941 1936 else:
1942 1937 ui.status(_("not updating, since new heads added\n"))
1943 1938 if modheads > 1:
1944 1939 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
1945 1940 else:
1946 1941 ui.status(_("(run 'hg update' to get a working copy)\n"))
1947 1942
1948 1943 def pull(ui, repo, source="default", **opts):
1949 1944 """pull changes from the specified source
1950 1945
1951 1946 Pull changes from a remote repository to a local one.
1952 1947
1953 1948 This finds all changes from the repository at the specified path
1954 1949 or URL and adds them to the local repository. By default, this
1955 1950 does not update the copy of the project in the working directory.
1956 1951
1957 1952 Valid URLs are of the form:
1958 1953
1959 1954 local/filesystem/path (or file://local/filesystem/path)
1960 1955 http://[user@]host[:port]/[path]
1961 1956 https://[user@]host[:port]/[path]
1962 1957 ssh://[user@]host[:port]/[path]
1963 1958 static-http://host[:port]/[path]
1964 1959
1965 1960 Paths in the local filesystem can either point to Mercurial
1966 1961 repositories or to bundle files (as created by 'hg bundle' or
1967 1962 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
1968 1963 allows access to a Mercurial repository where you simply use a web
1969 1964 server to publish the .hg directory as static content.
1970 1965
1971 1966 Some notes about using SSH with Mercurial:
1972 1967 - SSH requires an accessible shell account on the destination machine
1973 1968 and a copy of hg in the remote path or specified with as remotecmd.
1974 1969 - path is relative to the remote user's home directory by default.
1975 1970 Use an extra slash at the start of a path to specify an absolute path:
1976 1971 ssh://example.com//tmp/repository
1977 1972 - Mercurial doesn't use its own compression via SSH; the right thing
1978 1973 to do is to configure it in your ~/.ssh/config, e.g.:
1979 1974 Host *.mylocalnetwork.example.com
1980 1975 Compression no
1981 1976 Host *
1982 1977 Compression yes
1983 1978 Alternatively specify "ssh -C" as your ssh command in your hgrc or
1984 1979 with the --ssh command line option.
1985 1980 """
1986 1981 source = ui.expandpath(source)
1987 1982 setremoteconfig(ui, opts)
1988 1983
1989 1984 other = hg.repository(ui, source)
1990 1985 ui.status(_('pulling from %s\n') % (source))
1991 1986 revs = None
1992 1987 if opts['rev']:
1993 1988 if 'lookup' in other.capabilities:
1994 1989 revs = [other.lookup(rev) for rev in opts['rev']]
1995 1990 else:
1996 1991 error = _("Other repository doesn't support revision lookup, so a rev cannot be specified.")
1997 1992 raise util.Abort(error)
1998 1993 modheads = repo.pull(other, heads=revs, force=opts['force'])
1999 1994 return postincoming(ui, repo, modheads, opts['update'])
2000 1995
2001 1996 def push(ui, repo, dest=None, **opts):
2002 1997 """push changes to the specified destination
2003 1998
2004 1999 Push changes from the local repository to the given destination.
2005 2000
2006 2001 This is the symmetrical operation for pull. It helps to move
2007 2002 changes from the current repository to a different one. If the
2008 2003 destination is local this is identical to a pull in that directory
2009 2004 from the current one.
2010 2005
2011 2006 By default, push will refuse to run if it detects the result would
2012 2007 increase the number of remote heads. This generally indicates the
2013 2008 the client has forgotten to sync and merge before pushing.
2014 2009
2015 2010 Valid URLs are of the form:
2016 2011
2017 2012 local/filesystem/path (or file://local/filesystem/path)
2018 2013 ssh://[user@]host[:port]/[path]
2019 2014 http://[user@]host[:port]/[path]
2020 2015 https://[user@]host[:port]/[path]
2021 2016
2022 2017 Look at the help text for the pull command for important details
2023 2018 about ssh:// URLs.
2024 2019
2025 2020 Pushing to http:// and https:// URLs is only possible, if this
2026 2021 feature is explicitly enabled on the remote Mercurial server.
2027 2022 """
2028 2023 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2029 2024 setremoteconfig(ui, opts)
2030 2025
2031 2026 other = hg.repository(ui, dest)
2032 2027 ui.status('pushing to %s\n' % (dest))
2033 2028 revs = None
2034 2029 if opts['rev']:
2035 2030 revs = [repo.lookup(rev) for rev in opts['rev']]
2036 2031 r = repo.push(other, opts['force'], revs=revs)
2037 2032 return r == 0
2038 2033
2039 2034 def rawcommit(ui, repo, *pats, **opts):
2040 2035 """raw commit interface (DEPRECATED)
2041 2036
2042 2037 (DEPRECATED)
2043 2038 Lowlevel commit, for use in helper scripts.
2044 2039
2045 2040 This command is not intended to be used by normal users, as it is
2046 2041 primarily useful for importing from other SCMs.
2047 2042
2048 2043 This command is now deprecated and will be removed in a future
2049 2044 release, please use debugsetparents and commit instead.
2050 2045 """
2051 2046
2052 2047 ui.warn(_("(the rawcommit command is deprecated)\n"))
2053 2048
2054 2049 message = logmessage(opts)
2055 2050
2056 2051 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
2057 2052 if opts['files']:
2058 2053 files += open(opts['files']).read().splitlines()
2059 2054
2060 2055 parents = [repo.lookup(p) for p in opts['parent']]
2061 2056
2062 2057 try:
2063 2058 repo.rawcommit(files, message, opts['user'], opts['date'], *parents)
2064 2059 except ValueError, inst:
2065 2060 raise util.Abort(str(inst))
2066 2061
2067 2062 def recover(ui, repo):
2068 2063 """roll back an interrupted transaction
2069 2064
2070 2065 Recover from an interrupted commit or pull.
2071 2066
2072 2067 This command tries to fix the repository status after an interrupted
2073 2068 operation. It should only be necessary when Mercurial suggests it.
2074 2069 """
2075 2070 if repo.recover():
2076 2071 return hg.verify(repo)
2077 2072 return 1
2078 2073
2079 2074 def remove(ui, repo, *pats, **opts):
2080 2075 """remove the specified files on the next commit
2081 2076
2082 2077 Schedule the indicated files for removal from the repository.
2083 2078
2084 2079 This only removes files from the current branch, not from the
2085 2080 entire project history. If the files still exist in the working
2086 2081 directory, they will be deleted from it. If invoked with --after,
2087 2082 files are marked as removed, but not actually unlinked unless --force
2088 2083 is also given. Without exact file names, --after will only mark
2089 2084 files as removed if they are no longer in the working directory.
2090 2085
2091 2086 This command schedules the files to be removed at the next commit.
2092 2087 To undo a remove before that, see hg revert.
2093 2088
2094 2089 Modified files and added files are not removed by default. To
2095 2090 remove them, use the -f/--force option.
2096 2091 """
2097 2092 names = []
2098 2093 if not opts['after'] and not pats:
2099 2094 raise util.Abort(_('no files specified'))
2100 2095 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2101 2096 exact = dict.fromkeys(files)
2102 2097 mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5]
2103 2098 modified, added, removed, deleted, unknown = mardu
2104 2099 remove, forget = [], []
2105 2100 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
2106 2101 reason = None
2107 2102 if abs in modified and not opts['force']:
2108 2103 reason = _('is modified (use -f to force removal)')
2109 2104 elif abs in added:
2110 2105 if opts['force']:
2111 2106 forget.append(abs)
2112 2107 continue
2113 2108 reason = _('has been marked for add (use -f to force removal)')
2114 2109 elif abs in unknown:
2115 2110 reason = _('is not managed')
2116 2111 elif opts['after'] and not exact and abs not in deleted:
2117 2112 continue
2118 2113 elif abs in removed:
2119 2114 continue
2120 2115 if reason:
2121 2116 if exact:
2122 2117 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2123 2118 else:
2124 2119 if ui.verbose or not exact:
2125 2120 ui.status(_('removing %s\n') % rel)
2126 2121 remove.append(abs)
2127 2122 repo.forget(forget)
2128 2123 repo.remove(remove, unlink=opts['force'] or not opts['after'])
2129 2124
2130 2125 def rename(ui, repo, *pats, **opts):
2131 2126 """rename files; equivalent of copy + remove
2132 2127
2133 2128 Mark dest as copies of sources; mark sources for deletion. If
2134 2129 dest is a directory, copies are put in that directory. If dest is
2135 2130 a file, there can only be one source.
2136 2131
2137 2132 By default, this command copies the contents of files as they
2138 2133 stand in the working directory. If invoked with --after, the
2139 2134 operation is recorded, but no copying is performed.
2140 2135
2141 2136 This command takes effect in the next commit. To undo a rename
2142 2137 before that, see hg revert.
2143 2138 """
2144 2139 wlock = repo.wlock(0)
2145 2140 errs, copied = docopy(ui, repo, pats, opts, wlock)
2146 2141 names = []
2147 2142 for abs, rel, exact in copied:
2148 2143 if ui.verbose or not exact:
2149 2144 ui.status(_('removing %s\n') % rel)
2150 2145 names.append(abs)
2151 2146 if not opts.get('dry_run'):
2152 2147 repo.remove(names, True, wlock=wlock)
2153 2148 return errs
2154 2149
2155 2150 def revert(ui, repo, *pats, **opts):
2156 2151 """revert files or dirs to their states as of some revision
2157 2152
2158 2153 With no revision specified, revert the named files or directories
2159 2154 to the contents they had in the parent of the working directory.
2160 2155 This restores the contents of the affected files to an unmodified
2161 2156 state and unschedules adds, removes, copies, and renames. If the
2162 2157 working directory has two parents, you must explicitly specify the
2163 2158 revision to revert to.
2164 2159
2165 2160 Modified files are saved with a .orig suffix before reverting.
2166 2161 To disable these backups, use --no-backup.
2167 2162
2168 2163 Using the -r option, revert the given files or directories to their
2169 2164 contents as of a specific revision. This can be helpful to "roll
2170 2165 back" some or all of a change that should not have been committed.
2171 2166
2172 2167 Revert modifies the working directory. It does not commit any
2173 2168 changes, or change the parent of the working directory. If you
2174 2169 revert to a revision other than the parent of the working
2175 2170 directory, the reverted files will thus appear modified
2176 2171 afterwards.
2177 2172
2178 2173 If a file has been deleted, it is recreated. If the executable
2179 2174 mode of a file was changed, it is reset.
2180 2175
2181 2176 If names are given, all files matching the names are reverted.
2182 2177
2183 2178 If no arguments are given, no files are reverted.
2184 2179 """
2185 2180
2186 2181 if opts["date"]:
2187 2182 if opts["rev"]:
2188 2183 raise util.Abort(_("you can't specify a revision and a date"))
2189 2184 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2190 2185
2191 2186 if not pats and not opts['all']:
2192 2187 raise util.Abort(_('no files or directories specified; '
2193 2188 'use --all to revert the whole repo'))
2194 2189
2195 2190 parent, p2 = repo.dirstate.parents()
2196 2191 if not opts['rev'] and p2 != nullid:
2197 2192 raise util.Abort(_('uncommitted merge - please provide a '
2198 2193 'specific revision'))
2199 2194 ctx = repo.changectx(opts['rev'])
2200 2195 node = ctx.node()
2201 2196 mf = ctx.manifest()
2202 2197 if node == parent:
2203 2198 pmf = mf
2204 2199 else:
2205 2200 pmf = None
2206 2201
2207 2202 wlock = repo.wlock()
2208 2203
2209 2204 # need all matching names in dirstate and manifest of target rev,
2210 2205 # so have to walk both. do not print errors if files exist in one
2211 2206 # but not other.
2212 2207
2213 2208 names = {}
2214 2209 target_only = {}
2215 2210
2216 2211 # walk dirstate.
2217 2212
2218 2213 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
2219 2214 badmatch=mf.has_key):
2220 2215 names[abs] = (rel, exact)
2221 2216 if src == 'b':
2222 2217 target_only[abs] = True
2223 2218
2224 2219 # walk target manifest.
2225 2220
2226 2221 def badmatch(path):
2227 2222 if path in names:
2228 2223 return True
2229 2224 path_ = path + '/'
2230 2225 for f in names:
2231 2226 if f.startswith(path_):
2232 2227 return True
2233 2228 return False
2234 2229
2235 2230 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
2236 2231 badmatch=badmatch):
2237 2232 if abs in names or src == 'b':
2238 2233 continue
2239 2234 names[abs] = (rel, exact)
2240 2235 target_only[abs] = True
2241 2236
2242 2237 changes = repo.status(match=names.has_key, wlock=wlock)[:5]
2243 2238 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2244 2239
2245 2240 revert = ([], _('reverting %s\n'))
2246 2241 add = ([], _('adding %s\n'))
2247 2242 remove = ([], _('removing %s\n'))
2248 2243 forget = ([], _('forgetting %s\n'))
2249 2244 undelete = ([], _('undeleting %s\n'))
2250 2245 update = {}
2251 2246
2252 2247 disptable = (
2253 2248 # dispatch table:
2254 2249 # file state
2255 2250 # action if in target manifest
2256 2251 # action if not in target manifest
2257 2252 # make backup if in target manifest
2258 2253 # make backup if not in target manifest
2259 2254 (modified, revert, remove, True, True),
2260 2255 (added, revert, forget, True, False),
2261 2256 (removed, undelete, None, False, False),
2262 2257 (deleted, revert, remove, False, False),
2263 2258 (unknown, add, None, True, False),
2264 2259 (target_only, add, None, False, False),
2265 2260 )
2266 2261
2267 2262 entries = names.items()
2268 2263 entries.sort()
2269 2264
2270 2265 for abs, (rel, exact) in entries:
2271 2266 mfentry = mf.get(abs)
2272 2267 def handle(xlist, dobackup):
2273 2268 xlist[0].append(abs)
2274 2269 update[abs] = 1
2275 2270 if (dobackup and not opts['no_backup'] and
2276 2271 (os.path.islink(rel) or os.path.exists(rel))):
2277 2272 bakname = "%s.orig" % rel
2278 2273 ui.note(_('saving current version of %s as %s\n') %
2279 2274 (rel, bakname))
2280 2275 if not opts.get('dry_run'):
2281 2276 util.copyfile(rel, bakname)
2282 2277 if ui.verbose or not exact:
2283 2278 ui.status(xlist[1] % rel)
2284 2279 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2285 2280 if abs not in table: continue
2286 2281 # file has changed in dirstate
2287 2282 if mfentry:
2288 2283 handle(hitlist, backuphit)
2289 2284 elif misslist is not None:
2290 2285 handle(misslist, backupmiss)
2291 2286 else:
2292 2287 if exact: ui.warn(_('file not managed: %s\n') % rel)
2293 2288 break
2294 2289 else:
2295 2290 # file has not changed in dirstate
2296 2291 if node == parent:
2297 2292 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2298 2293 continue
2299 2294 if pmf is None:
2300 2295 # only need parent manifest in this unlikely case,
2301 2296 # so do not read by default
2302 2297 pmf = repo.changectx(parent).manifest()
2303 2298 if abs in pmf:
2304 2299 if mfentry:
2305 2300 # if version of file is same in parent and target
2306 2301 # manifests, do nothing
2307 2302 if pmf[abs] != mfentry:
2308 2303 handle(revert, False)
2309 2304 else:
2310 2305 handle(remove, False)
2311 2306
2312 2307 if not opts.get('dry_run'):
2313 2308 repo.dirstate.forget(forget[0])
2314 2309 r = hg.revert(repo, node, update.has_key, wlock)
2315 2310 repo.dirstate.update(add[0], 'a')
2316 2311 repo.dirstate.update(undelete[0], 'n')
2317 2312 repo.dirstate.update(remove[0], 'r')
2318 2313 return r
2319 2314
2320 2315 def rollback(ui, repo):
2321 2316 """roll back the last transaction in this repository
2322 2317
2323 2318 Roll back the last transaction in this repository, restoring the
2324 2319 project to its state prior to the transaction.
2325 2320
2326 2321 Transactions are used to encapsulate the effects of all commands
2327 2322 that create new changesets or propagate existing changesets into a
2328 2323 repository. For example, the following commands are transactional,
2329 2324 and their effects can be rolled back:
2330 2325
2331 2326 commit
2332 2327 import
2333 2328 pull
2334 2329 push (with this repository as destination)
2335 2330 unbundle
2336 2331
2337 2332 This command should be used with care. There is only one level of
2338 2333 rollback, and there is no way to undo a rollback.
2339 2334
2340 2335 This command is not intended for use on public repositories. Once
2341 2336 changes are visible for pull by other users, rolling a transaction
2342 2337 back locally is ineffective (someone else may already have pulled
2343 2338 the changes). Furthermore, a race is possible with readers of the
2344 2339 repository; for example an in-progress pull from the repository
2345 2340 may fail if a rollback is performed.
2346 2341 """
2347 2342 repo.rollback()
2348 2343
2349 2344 def root(ui, repo):
2350 2345 """print the root (top) of the current working dir
2351 2346
2352 2347 Print the root directory of the current repository.
2353 2348 """
2354 2349 ui.write(repo.root + "\n")
2355 2350
2356 2351 def serve(ui, repo, **opts):
2357 2352 """export the repository via HTTP
2358 2353
2359 2354 Start a local HTTP repository browser and pull server.
2360 2355
2361 2356 By default, the server logs accesses to stdout and errors to
2362 2357 stderr. Use the "-A" and "-E" options to log to files.
2363 2358 """
2364 2359
2365 2360 if opts["stdio"]:
2366 2361 if repo is None:
2367 2362 raise hg.RepoError(_("There is no Mercurial repository here"
2368 2363 " (.hg not found)"))
2369 2364 s = sshserver.sshserver(ui, repo)
2370 2365 s.serve_forever()
2371 2366
2372 2367 parentui = ui.parentui or ui
2373 2368 optlist = ("name templates style address port ipv6"
2374 2369 " accesslog errorlog webdir_conf")
2375 2370 for o in optlist.split():
2376 2371 if opts[o]:
2377 2372 parentui.setconfig("web", o, str(opts[o]))
2378 2373
2379 2374 if repo is None and not ui.config("web", "webdir_conf"):
2380 2375 raise hg.RepoError(_("There is no Mercurial repository here"
2381 2376 " (.hg not found)"))
2382 2377
2383 2378 class service:
2384 2379 def init(self):
2385 2380 try:
2386 2381 self.httpd = hgweb.server.create_server(parentui, repo)
2387 2382 except socket.error, inst:
2388 2383 raise util.Abort(_('cannot start server: ') + inst.args[1])
2389 2384
2390 2385 if not ui.verbose: return
2391 2386
2392 2387 if httpd.port != 80:
2393 2388 ui.status(_('listening at http://%s:%d/\n') %
2394 2389 (httpd.addr, httpd.port))
2395 2390 else:
2396 2391 ui.status(_('listening at http://%s/\n') % httpd.addr)
2397 2392
2398 2393 def run(self):
2399 2394 self.httpd.serve_forever()
2400 2395
2401 2396 service = service()
2402 2397
2403 2398 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2404 2399
2405 2400 def status(ui, repo, *pats, **opts):
2406 2401 """show changed files in the working directory
2407 2402
2408 2403 Show status of files in the repository. If names are given, only
2409 2404 files that match are shown. Files that are clean or ignored, are
2410 2405 not listed unless -c (clean), -i (ignored) or -A is given.
2411 2406
2412 2407 NOTE: status may appear to disagree with diff if permissions have
2413 2408 changed or a merge has occurred. The standard diff format does not
2414 2409 report permission changes and diff only reports changes relative
2415 2410 to one merge parent.
2416 2411
2417 2412 If one revision is given, it is used as the base revision.
2418 2413 If two revisions are given, the difference between them is shown.
2419 2414
2420 2415 The codes used to show the status of files are:
2421 2416 M = modified
2422 2417 A = added
2423 2418 R = removed
2424 2419 C = clean
2425 2420 ! = deleted, but still tracked
2426 2421 ? = not tracked
2427 2422 I = ignored (not shown by default)
2428 2423 = the previous added file was copied from here
2429 2424 """
2430 2425
2431 2426 all = opts['all']
2432 2427 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2433 2428
2434 2429 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2435 2430 cwd = (pats and repo.getcwd()) or ''
2436 2431 modified, added, removed, deleted, unknown, ignored, clean = [
2437 2432 n for n in repo.status(node1=node1, node2=node2, files=files,
2438 2433 match=matchfn,
2439 2434 list_ignored=all or opts['ignored'],
2440 2435 list_clean=all or opts['clean'])]
2441 2436
2442 2437 changetypes = (('modified', 'M', modified),
2443 2438 ('added', 'A', added),
2444 2439 ('removed', 'R', removed),
2445 2440 ('deleted', '!', deleted),
2446 2441 ('unknown', '?', unknown),
2447 2442 ('ignored', 'I', ignored))
2448 2443
2449 2444 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2450 2445
2451 2446 end = opts['print0'] and '\0' or '\n'
2452 2447
2453 2448 for opt, char, changes in ([ct for ct in explicit_changetypes
2454 2449 if all or opts[ct[0]]]
2455 2450 or changetypes):
2456 2451 if opts['no_status']:
2457 2452 format = "%%s%s" % end
2458 2453 else:
2459 2454 format = "%s %%s%s" % (char, end)
2460 2455
2461 2456 for f in changes:
2462 2457 ui.write(format % util.pathto(repo.root, cwd, f))
2463 2458 if ((all or opts.get('copies')) and not opts.get('no_status')):
2464 2459 copied = repo.dirstate.copied(f)
2465 2460 if copied:
2466 2461 ui.write(' %s%s' % (util.pathto(repo.root, cwd, copied),
2467 2462 end))
2468 2463
2469 2464 def tag(ui, repo, name, rev_=None, **opts):
2470 2465 """add a tag for the current or given revision
2471 2466
2472 2467 Name a particular revision using <name>.
2473 2468
2474 2469 Tags are used to name particular revisions of the repository and are
2475 2470 very useful to compare different revision, to go back to significant
2476 2471 earlier versions or to mark branch points as releases, etc.
2477 2472
2478 2473 If no revision is given, the parent of the working directory is used,
2479 2474 or tip if no revision is checked out.
2480 2475
2481 2476 To facilitate version control, distribution, and merging of tags,
2482 2477 they are stored as a file named ".hgtags" which is managed
2483 2478 similarly to other project files and can be hand-edited if
2484 2479 necessary. The file '.hg/localtags' is used for local tags (not
2485 2480 shared among repositories).
2486 2481 """
2487 2482 if name in ['tip', '.', 'null']:
2488 2483 raise util.Abort(_("the name '%s' is reserved") % name)
2489 2484 if rev_ is not None:
2490 2485 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2491 2486 "please use 'hg tag [-r REV] NAME' instead\n"))
2492 2487 if opts['rev']:
2493 2488 raise util.Abort(_("use only one form to specify the revision"))
2494 2489 if opts['rev'] and opts['remove']:
2495 2490 raise util.Abort(_("--rev and --remove are incompatible"))
2496 2491 if opts['rev']:
2497 2492 rev_ = opts['rev']
2498 2493 message = opts['message']
2499 2494 if opts['remove']:
2500 2495 rev_ = nullid
2501 2496 if not message:
2502 2497 message = _('Removed tag %s') % name
2503 2498 elif name in repo.tags() and not opts['force']:
2504 2499 raise util.Abort(_('a tag named %s already exists (use -f to force)')
2505 2500 % name)
2506 2501 if not rev_ and repo.dirstate.parents()[1] != nullid:
2507 2502 raise util.Abort(_('uncommitted merge - please provide a '
2508 2503 'specific revision'))
2509 2504 r = repo.changectx(rev_).node()
2510 2505
2511 2506 if not message:
2512 2507 message = _('Added tag %s for changeset %s') % (name, short(r))
2513 2508
2514 2509 repo.tag(name, r, message, opts['local'], opts['user'], opts['date'])
2515 2510
2516 2511 def tags(ui, repo):
2517 2512 """list repository tags
2518 2513
2519 2514 List the repository tags.
2520 2515
2521 2516 This lists both regular and local tags.
2522 2517 """
2523 2518
2524 2519 l = repo.tagslist()
2525 2520 l.reverse()
2526 2521 hexfunc = ui.debugflag and hex or short
2527 2522 for t, n in l:
2528 2523 try:
2529 2524 hn = hexfunc(n)
2530 2525 r = "%5d:%s" % (repo.changelog.rev(n), hexfunc(n))
2531 2526 except revlog.LookupError:
2532 2527 r = " ?:%s" % hn
2533 2528 if ui.quiet:
2534 2529 ui.write("%s\n" % t)
2535 2530 else:
2536 2531 spaces = " " * (30 - util.locallen(t))
2537 2532 ui.write("%s%s %s\n" % (t, spaces, r))
2538 2533
2539 2534 def tip(ui, repo, **opts):
2540 2535 """show the tip revision
2541 2536
2542 2537 Show the tip revision.
2543 2538 """
2544 2539 cmdutil.show_changeset(ui, repo, opts).show(nullrev+repo.changelog.count())
2545 2540
2546 2541 def unbundle(ui, repo, fname, **opts):
2547 2542 """apply a changegroup file
2548 2543
2549 2544 Apply a compressed changegroup file generated by the bundle
2550 2545 command.
2551 2546 """
2552 2547 if os.path.exists(fname):
2553 2548 f = open(fname, "rb")
2554 2549 else:
2555 2550 f = urllib.urlopen(fname)
2556 2551 gen = changegroup.readbundle(f, fname)
2557 2552 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2558 2553 return postincoming(ui, repo, modheads, opts['update'])
2559 2554
2560 2555 def update(ui, repo, node=None, clean=False, date=None):
2561 2556 """update working directory
2562 2557
2563 2558 Update the working directory to the specified revision, or the
2564 2559 tip of the current branch if none is specified.
2565 2560
2566 2561 If there are no outstanding changes in the working directory and
2567 2562 there is a linear relationship between the current version and the
2568 2563 requested version, the result is the requested version.
2569 2564
2570 2565 To merge the working directory with another revision, use the
2571 2566 merge command.
2572 2567
2573 2568 By default, update will refuse to run if doing so would require
2574 2569 discarding local changes.
2575 2570 """
2576 2571 if date:
2577 2572 if node:
2578 2573 raise util.Abort(_("you can't specify a revision and a date"))
2579 2574 node = cmdutil.finddate(ui, repo, date)
2580 2575
2581 2576 if clean:
2582 2577 return hg.clean(repo, node)
2583 2578 else:
2584 2579 return hg.update(repo, node)
2585 2580
2586 2581 def verify(ui, repo):
2587 2582 """verify the integrity of the repository
2588 2583
2589 2584 Verify the integrity of the current repository.
2590 2585
2591 2586 This will perform an extensive check of the repository's
2592 2587 integrity, validating the hashes and checksums of each entry in
2593 2588 the changelog, manifest, and tracked files, as well as the
2594 2589 integrity of their crosslinks and indices.
2595 2590 """
2596 2591 return hg.verify(repo)
2597 2592
2598 2593 def version_(ui):
2599 2594 """output version and copyright information"""
2600 2595 ui.write(_("Mercurial Distributed SCM (version %s)\n")
2601 2596 % version.get_version())
2602 2597 ui.status(_(
2603 2598 "\nCopyright (C) 2005, 2006 Matt Mackall <mpm@selenic.com>\n"
2604 2599 "This is free software; see the source for copying conditions. "
2605 2600 "There is NO\nwarranty; "
2606 2601 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
2607 2602 ))
2608 2603
2609 2604 # Command options and aliases are listed here, alphabetically
2610 2605
2611 2606 globalopts = [
2612 2607 ('R', 'repository', '',
2613 2608 _('repository root directory or symbolic path name')),
2614 2609 ('', 'cwd', '', _('change working directory')),
2615 2610 ('y', 'noninteractive', None,
2616 2611 _('do not prompt, assume \'yes\' for any required answers')),
2617 2612 ('q', 'quiet', None, _('suppress output')),
2618 2613 ('v', 'verbose', None, _('enable additional output')),
2619 2614 ('', 'config', [], _('set/override config option')),
2620 2615 ('', 'debug', None, _('enable debugging output')),
2621 2616 ('', 'debugger', None, _('start debugger')),
2622 2617 ('', 'encoding', util._encoding, _('set the charset encoding')),
2623 2618 ('', 'encodingmode', util._encodingmode, _('set the charset encoding mode')),
2624 2619 ('', 'lsprof', None, _('print improved command execution profile')),
2625 2620 ('', 'traceback', None, _('print traceback on exception')),
2626 2621 ('', 'time', None, _('time how long the command takes')),
2627 2622 ('', 'profile', None, _('print command execution profile')),
2628 2623 ('', 'version', None, _('output version information and exit')),
2629 2624 ('h', 'help', None, _('display help and exit')),
2630 2625 ]
2631 2626
2632 2627 dryrunopts = [('n', 'dry-run', None,
2633 2628 _('do not perform actions, just print output'))]
2634 2629
2635 2630 remoteopts = [
2636 2631 ('e', 'ssh', '', _('specify ssh command to use')),
2637 2632 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2638 2633 ]
2639 2634
2640 2635 walkopts = [
2641 2636 ('I', 'include', [], _('include names matching the given patterns')),
2642 2637 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2643 2638 ]
2644 2639
2645 2640 commitopts = [
2646 2641 ('m', 'message', '', _('use <text> as commit message')),
2647 2642 ('l', 'logfile', '', _('read commit message from <file>')),
2648 2643 ]
2649 2644
2650 2645 table = {
2651 2646 "^add": (add, walkopts + dryrunopts, _('hg add [OPTION]... [FILE]...')),
2652 2647 "addremove":
2653 2648 (addremove,
2654 2649 [('s', 'similarity', '',
2655 2650 _('guess renamed files by similarity (0<=s<=100)')),
2656 2651 ] + walkopts + dryrunopts,
2657 2652 _('hg addremove [OPTION]... [FILE]...')),
2658 2653 "^annotate":
2659 2654 (annotate,
2660 2655 [('r', 'rev', '', _('annotate the specified revision')),
2661 2656 ('f', 'follow', None, _('follow file copies and renames')),
2662 2657 ('a', 'text', None, _('treat all files as text')),
2663 2658 ('u', 'user', None, _('list the author')),
2664 2659 ('d', 'date', None, _('list the date')),
2665 2660 ('n', 'number', None, _('list the revision number (default)')),
2666 2661 ('c', 'changeset', None, _('list the changeset')),
2667 2662 ] + walkopts,
2668 2663 _('hg annotate [-r REV] [-f] [-a] [-u] [-d] [-n] [-c] FILE...')),
2669 2664 "archive":
2670 2665 (archive,
2671 2666 [('', 'no-decode', None, _('do not pass files through decoders')),
2672 2667 ('p', 'prefix', '', _('directory prefix for files in archive')),
2673 2668 ('r', 'rev', '', _('revision to distribute')),
2674 2669 ('t', 'type', '', _('type of distribution to create')),
2675 2670 ] + walkopts,
2676 2671 _('hg archive [OPTION]... DEST')),
2677 2672 "backout":
2678 2673 (backout,
2679 2674 [('', 'merge', None,
2680 2675 _('merge with old dirstate parent after backout')),
2681 2676 ('d', 'date', '', _('record datecode as commit date')),
2682 2677 ('', 'parent', '', _('parent to choose when backing out merge')),
2683 2678 ('u', 'user', '', _('record user as committer')),
2684 2679 ] + walkopts + commitopts,
2685 2680 _('hg backout [OPTION]... REV')),
2686 2681 "branch": (branch,
2687 2682 [('f', 'force', None,
2688 2683 _('set branch name even if it shadows an existing branch'))],
2689 2684 _('hg branch [NAME]')),
2690 2685 "branches": (branches, [], _('hg branches')),
2691 2686 "bundle":
2692 2687 (bundle,
2693 2688 [('f', 'force', None,
2694 2689 _('run even when remote repository is unrelated')),
2695 2690 ('r', 'rev', [],
2696 2691 _('a changeset you would like to bundle')),
2697 2692 ('', 'base', [],
2698 2693 _('a base changeset to specify instead of a destination')),
2699 2694 ] + remoteopts,
2700 2695 _('hg bundle [-f] [-r REV]... [--base REV]... FILE [DEST]')),
2701 2696 "cat":
2702 2697 (cat,
2703 2698 [('o', 'output', '', _('print output to file with formatted name')),
2704 2699 ('r', 'rev', '', _('print the given revision')),
2705 2700 ] + walkopts,
2706 2701 _('hg cat [OPTION]... FILE...')),
2707 2702 "^clone":
2708 2703 (clone,
2709 2704 [('U', 'noupdate', None, _('do not update the new working directory')),
2710 2705 ('r', 'rev', [],
2711 2706 _('a changeset you would like to have after cloning')),
2712 2707 ('', 'pull', None, _('use pull protocol to copy metadata')),
2713 2708 ('', 'uncompressed', None,
2714 2709 _('use uncompressed transfer (fast over LAN)')),
2715 2710 ] + remoteopts,
2716 2711 _('hg clone [OPTION]... SOURCE [DEST]')),
2717 2712 "^commit|ci":
2718 2713 (commit,
2719 2714 [('A', 'addremove', None,
2720 2715 _('mark new/missing files as added/removed before committing')),
2721 2716 ('d', 'date', '', _('record datecode as commit date')),
2722 2717 ('u', 'user', '', _('record user as commiter')),
2723 2718 ] + walkopts + commitopts,
2724 2719 _('hg commit [OPTION]... [FILE]...')),
2725 2720 "copy|cp":
2726 2721 (copy,
2727 2722 [('A', 'after', None, _('record a copy that has already occurred')),
2728 2723 ('f', 'force', None,
2729 2724 _('forcibly copy over an existing managed file')),
2730 2725 ] + walkopts + dryrunopts,
2731 2726 _('hg copy [OPTION]... [SOURCE]... DEST')),
2732 2727 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2733 2728 "debugcomplete":
2734 2729 (debugcomplete,
2735 2730 [('o', 'options', None, _('show the command options'))],
2736 2731 _('debugcomplete [-o] CMD')),
2737 2732 "debuginstall": (debuginstall, [], _('debuginstall')),
2738 2733 "debugrebuildstate":
2739 2734 (debugrebuildstate,
2740 2735 [('r', 'rev', '', _('revision to rebuild to'))],
2741 2736 _('debugrebuildstate [-r REV] [REV]')),
2742 2737 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2743 2738 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2744 2739 "debugstate": (debugstate, [], _('debugstate')),
2745 2740 "debugdate":
2746 2741 (debugdate,
2747 2742 [('e', 'extended', None, _('try extended date formats'))],
2748 2743 _('debugdate [-e] DATE [RANGE]')),
2749 2744 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2750 2745 "debugindex": (debugindex, [], _('debugindex FILE')),
2751 2746 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2752 2747 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2753 2748 "debugwalk": (debugwalk, walkopts, _('debugwalk [OPTION]... [FILE]...')),
2754 2749 "^diff":
2755 2750 (diff,
2756 2751 [('r', 'rev', [], _('revision')),
2757 2752 ('a', 'text', None, _('treat all files as text')),
2758 2753 ('p', 'show-function', None,
2759 2754 _('show which function each change is in')),
2760 2755 ('g', 'git', None, _('use git extended diff format')),
2761 2756 ('', 'nodates', None, _("don't include dates in diff headers")),
2762 2757 ('w', 'ignore-all-space', None,
2763 2758 _('ignore white space when comparing lines')),
2764 2759 ('b', 'ignore-space-change', None,
2765 2760 _('ignore changes in the amount of white space')),
2766 2761 ('B', 'ignore-blank-lines', None,
2767 2762 _('ignore changes whose lines are all blank')),
2768 2763 ] + walkopts,
2769 2764 _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
2770 2765 "^export":
2771 2766 (export,
2772 2767 [('o', 'output', '', _('print output to file with formatted name')),
2773 2768 ('a', 'text', None, _('treat all files as text')),
2774 2769 ('g', 'git', None, _('use git extended diff format')),
2775 2770 ('', 'nodates', None, _("don't include dates in diff headers")),
2776 2771 ('', 'switch-parent', None, _('diff against the second parent'))],
2777 2772 _('hg export [OPTION]... [-o OUTFILESPEC] REV...')),
2778 2773 "grep":
2779 2774 (grep,
2780 2775 [('0', 'print0', None, _('end fields with NUL')),
2781 2776 ('', 'all', None, _('print all revisions that match')),
2782 2777 ('f', 'follow', None,
2783 2778 _('follow changeset history, or file history across copies and renames')),
2784 2779 ('i', 'ignore-case', None, _('ignore case when matching')),
2785 2780 ('l', 'files-with-matches', None,
2786 2781 _('print only filenames and revs that match')),
2787 2782 ('n', 'line-number', None, _('print matching line numbers')),
2788 2783 ('r', 'rev', [], _('search in given revision range')),
2789 2784 ('u', 'user', None, _('print user who committed change')),
2790 2785 ] + walkopts,
2791 2786 _('hg grep [OPTION]... PATTERN [FILE]...')),
2792 2787 "heads":
2793 2788 (heads,
2794 2789 [('', 'style', '', _('display using template map file')),
2795 2790 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2796 2791 ('', 'template', '', _('display with template'))],
2797 2792 _('hg heads [-r REV]')),
2798 2793 "help": (help_, [], _('hg help [COMMAND]')),
2799 2794 "identify|id": (identify, [], _('hg identify')),
2800 2795 "import|patch":
2801 2796 (import_,
2802 2797 [('p', 'strip', 1,
2803 2798 _('directory strip option for patch. This has the same\n'
2804 2799 'meaning as the corresponding patch option')),
2805 2800 ('b', 'base', '', _('base path')),
2806 2801 ('f', 'force', None,
2807 2802 _('skip check for outstanding uncommitted changes')),
2808 2803 ('', 'exact', None,
2809 2804 _('apply patch to the nodes from which it was generated'))] + commitopts,
2810 2805 _('hg import [-p NUM] [-m MESSAGE] [-f] PATCH...')),
2811 2806 "incoming|in": (incoming,
2812 2807 [('M', 'no-merges', None, _('do not show merges')),
2813 2808 ('f', 'force', None,
2814 2809 _('run even when remote repository is unrelated')),
2815 2810 ('', 'style', '', _('display using template map file')),
2816 2811 ('n', 'newest-first', None, _('show newest record first')),
2817 2812 ('', 'bundle', '', _('file to store the bundles into')),
2818 2813 ('p', 'patch', None, _('show patch')),
2819 2814 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
2820 2815 ('', 'template', '', _('display with template')),
2821 2816 ] + remoteopts,
2822 2817 _('hg incoming [-p] [-n] [-M] [-f] [-r REV]...'
2823 2818 ' [--bundle FILENAME] [SOURCE]')),
2824 2819 "^init":
2825 2820 (init,
2826 2821 remoteopts,
2827 2822 _('hg init [-e CMD] [--remotecmd CMD] [DEST]')),
2828 2823 "locate":
2829 2824 (locate,
2830 2825 [('r', 'rev', '', _('search the repository as it stood at rev')),
2831 2826 ('0', 'print0', None,
2832 2827 _('end filenames with NUL, for use with xargs')),
2833 2828 ('f', 'fullpath', None,
2834 2829 _('print complete paths from the filesystem root')),
2835 2830 ] + walkopts,
2836 2831 _('hg locate [OPTION]... [PATTERN]...')),
2837 2832 "^log|history":
2838 2833 (log,
2839 2834 [('f', 'follow', None,
2840 2835 _('follow changeset history, or file history across copies and renames')),
2841 2836 ('', 'follow-first', None,
2842 2837 _('only follow the first parent of merge changesets')),
2843 2838 ('d', 'date', '', _('show revs matching date spec')),
2844 2839 ('C', 'copies', None, _('show copied files')),
2845 2840 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
2846 2841 ('l', 'limit', '', _('limit number of changes displayed')),
2847 2842 ('r', 'rev', [], _('show the specified revision or range')),
2848 2843 ('', 'removed', None, _('include revs where files were removed')),
2849 2844 ('M', 'no-merges', None, _('do not show merges')),
2850 2845 ('', 'style', '', _('display using template map file')),
2851 2846 ('m', 'only-merges', None, _('show only merges')),
2852 2847 ('p', 'patch', None, _('show patch')),
2853 2848 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
2854 2849 ('', 'template', '', _('display with template')),
2855 2850 ] + walkopts,
2856 2851 _('hg log [OPTION]... [FILE]')),
2857 2852 "manifest": (manifest, [], _('hg manifest [REV]')),
2858 2853 "^merge":
2859 2854 (merge,
2860 2855 [('f', 'force', None, _('force a merge with outstanding changes'))],
2861 2856 _('hg merge [-f] [REV]')),
2862 2857 "outgoing|out": (outgoing,
2863 2858 [('M', 'no-merges', None, _('do not show merges')),
2864 2859 ('f', 'force', None,
2865 2860 _('run even when remote repository is unrelated')),
2866 2861 ('p', 'patch', None, _('show patch')),
2867 2862 ('', 'style', '', _('display using template map file')),
2868 2863 ('r', 'rev', [], _('a specific revision you would like to push')),
2869 2864 ('n', 'newest-first', None, _('show newest record first')),
2870 2865 ('', 'template', '', _('display with template')),
2871 2866 ] + remoteopts,
2872 2867 _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
2873 2868 "^parents":
2874 2869 (parents,
2875 2870 [('r', 'rev', '', _('show parents from the specified rev')),
2876 2871 ('', 'style', '', _('display using template map file')),
2877 2872 ('', 'template', '', _('display with template'))],
2878 2873 _('hg parents [-r REV] [FILE]')),
2879 2874 "paths": (paths, [], _('hg paths [NAME]')),
2880 2875 "^pull":
2881 2876 (pull,
2882 2877 [('u', 'update', None,
2883 2878 _('update to new tip if changesets were pulled')),
2884 2879 ('f', 'force', None,
2885 2880 _('run even when remote repository is unrelated')),
2886 2881 ('r', 'rev', [],
2887 2882 _('a specific revision up to which you would like to pull')),
2888 2883 ] + remoteopts,
2889 2884 _('hg pull [-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
2890 2885 "^push":
2891 2886 (push,
2892 2887 [('f', 'force', None, _('force push')),
2893 2888 ('r', 'rev', [], _('a specific revision you would like to push')),
2894 2889 ] + remoteopts,
2895 2890 _('hg push [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
2896 2891 "debugrawcommit|rawcommit":
2897 2892 (rawcommit,
2898 2893 [('p', 'parent', [], _('parent')),
2899 2894 ('d', 'date', '', _('date code')),
2900 2895 ('u', 'user', '', _('user')),
2901 2896 ('F', 'files', '', _('file list'))
2902 2897 ] + commitopts,
2903 2898 _('hg debugrawcommit [OPTION]... [FILE]...')),
2904 2899 "recover": (recover, [], _('hg recover')),
2905 2900 "^remove|rm":
2906 2901 (remove,
2907 2902 [('A', 'after', None, _('record remove that has already occurred')),
2908 2903 ('f', 'force', None, _('remove file even if modified')),
2909 2904 ] + walkopts,
2910 2905 _('hg remove [OPTION]... FILE...')),
2911 2906 "rename|mv":
2912 2907 (rename,
2913 2908 [('A', 'after', None, _('record a rename that has already occurred')),
2914 2909 ('f', 'force', None,
2915 2910 _('forcibly copy over an existing managed file')),
2916 2911 ] + walkopts + dryrunopts,
2917 2912 _('hg rename [OPTION]... SOURCE... DEST')),
2918 2913 "^revert":
2919 2914 (revert,
2920 2915 [('a', 'all', None, _('revert all changes when no arguments given')),
2921 2916 ('d', 'date', '', _('tipmost revision matching date')),
2922 2917 ('r', 'rev', '', _('revision to revert to')),
2923 2918 ('', 'no-backup', None, _('do not save backup copies of files')),
2924 2919 ] + walkopts + dryrunopts,
2925 2920 _('hg revert [OPTION]... [-r REV] [NAME]...')),
2926 2921 "rollback": (rollback, [], _('hg rollback')),
2927 2922 "root": (root, [], _('hg root')),
2928 2923 "showconfig|debugconfig":
2929 2924 (showconfig,
2930 2925 [('u', 'untrusted', None, _('show untrusted configuration options'))],
2931 2926 _('showconfig [-u] [NAME]...')),
2932 2927 "^serve":
2933 2928 (serve,
2934 2929 [('A', 'accesslog', '', _('name of access log file to write to')),
2935 2930 ('d', 'daemon', None, _('run server in background')),
2936 2931 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
2937 2932 ('E', 'errorlog', '', _('name of error log file to write to')),
2938 2933 ('p', 'port', 0, _('port to use (default: 8000)')),
2939 2934 ('a', 'address', '', _('address to use')),
2940 2935 ('n', 'name', '',
2941 2936 _('name to show in web pages (default: working dir)')),
2942 2937 ('', 'webdir-conf', '', _('name of the webdir config file'
2943 2938 ' (serve more than one repo)')),
2944 2939 ('', 'pid-file', '', _('name of file to write process ID to')),
2945 2940 ('', 'stdio', None, _('for remote clients')),
2946 2941 ('t', 'templates', '', _('web templates to use')),
2947 2942 ('', 'style', '', _('template style to use')),
2948 2943 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
2949 2944 _('hg serve [OPTION]...')),
2950 2945 "^status|st":
2951 2946 (status,
2952 2947 [('A', 'all', None, _('show status of all files')),
2953 2948 ('m', 'modified', None, _('show only modified files')),
2954 2949 ('a', 'added', None, _('show only added files')),
2955 2950 ('r', 'removed', None, _('show only removed files')),
2956 2951 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
2957 2952 ('c', 'clean', None, _('show only files without changes')),
2958 2953 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
2959 2954 ('i', 'ignored', None, _('show only ignored files')),
2960 2955 ('n', 'no-status', None, _('hide status prefix')),
2961 2956 ('C', 'copies', None, _('show source of copied files')),
2962 2957 ('0', 'print0', None,
2963 2958 _('end filenames with NUL, for use with xargs')),
2964 2959 ('', 'rev', [], _('show difference from revision')),
2965 2960 ] + walkopts,
2966 2961 _('hg status [OPTION]... [FILE]...')),
2967 2962 "tag":
2968 2963 (tag,
2969 2964 [('f', 'force', None, _('replace existing tag')),
2970 2965 ('l', 'local', None, _('make the tag local')),
2971 2966 ('m', 'message', '', _('message for tag commit log entry')),
2972 2967 ('d', 'date', '', _('record datecode as commit date')),
2973 2968 ('u', 'user', '', _('record user as commiter')),
2974 2969 ('r', 'rev', '', _('revision to tag')),
2975 2970 ('', 'remove', None, _('remove a tag'))],
2976 2971 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
2977 2972 "tags": (tags, [], _('hg tags')),
2978 2973 "tip":
2979 2974 (tip,
2980 2975 [('', 'style', '', _('display using template map file')),
2981 2976 ('p', 'patch', None, _('show patch')),
2982 2977 ('', 'template', '', _('display with template'))],
2983 2978 _('hg tip [-p]')),
2984 2979 "unbundle":
2985 2980 (unbundle,
2986 2981 [('u', 'update', None,
2987 2982 _('update to new tip if changesets were unbundled'))],
2988 2983 _('hg unbundle [-u] FILE')),
2989 2984 "^update|up|checkout|co":
2990 2985 (update,
2991 2986 [('C', 'clean', None, _('overwrite locally modified files')),
2992 2987 ('d', 'date', '', _('tipmost revision matching date'))],
2993 2988 _('hg update [-C] [-d DATE] [REV]')),
2994 2989 "verify": (verify, [], _('hg verify')),
2995 2990 "version": (version_, [], _('hg version')),
2996 2991 }
2997 2992
2998 2993 norepo = ("clone init version help debugancestor debugcomplete debugdata"
2999 2994 " debugindex debugindexdot debugdate debuginstall")
3000 2995 optionalrepo = ("paths serve showconfig")
3001 2996
3002 2997 def findpossible(ui, cmd):
3003 2998 """
3004 2999 Return cmd -> (aliases, command table entry)
3005 3000 for each matching command.
3006 3001 Return debug commands (or their aliases) only if no normal command matches.
3007 3002 """
3008 3003 choice = {}
3009 3004 debugchoice = {}
3010 3005 for e in table.keys():
3011 3006 aliases = e.lstrip("^").split("|")
3012 3007 found = None
3013 3008 if cmd in aliases:
3014 3009 found = cmd
3015 3010 elif not ui.config("ui", "strict"):
3016 3011 for a in aliases:
3017 3012 if a.startswith(cmd):
3018 3013 found = a
3019 3014 break
3020 3015 if found is not None:
3021 3016 if aliases[0].startswith("debug") or found.startswith("debug"):
3022 3017 debugchoice[found] = (aliases, table[e])
3023 3018 else:
3024 3019 choice[found] = (aliases, table[e])
3025 3020
3026 3021 if not choice and debugchoice:
3027 3022 choice = debugchoice
3028 3023
3029 3024 return choice
3030 3025
3031 3026 def findcmd(ui, cmd):
3032 3027 """Return (aliases, command table entry) for command string."""
3033 3028 choice = findpossible(ui, cmd)
3034 3029
3035 3030 if choice.has_key(cmd):
3036 3031 return choice[cmd]
3037 3032
3038 3033 if len(choice) > 1:
3039 3034 clist = choice.keys()
3040 3035 clist.sort()
3041 3036 raise AmbiguousCommand(cmd, clist)
3042 3037
3043 3038 if choice:
3044 3039 return choice.values()[0]
3045 3040
3046 3041 raise UnknownCommand(cmd)
3047 3042
3048 3043 def catchterm(*args):
3049 3044 raise util.SignalInterrupt
3050 3045
3051 3046 def run():
3052 3047 sys.exit(dispatch(sys.argv[1:]))
3053 3048
3054 3049 class ParseError(Exception):
3055 3050 """Exception raised on errors in parsing the command line."""
3056 3051
3057 3052 def parse(ui, args):
3058 3053 options = {}
3059 3054 cmdoptions = {}
3060 3055
3061 3056 try:
3062 3057 args = fancyopts.fancyopts(args, globalopts, options)
3063 3058 except fancyopts.getopt.GetoptError, inst:
3064 3059 raise ParseError(None, inst)
3065 3060
3066 3061 if args:
3067 3062 cmd, args = args[0], args[1:]
3068 3063 aliases, i = findcmd(ui, cmd)
3069 3064 cmd = aliases[0]
3070 3065 defaults = ui.config("defaults", cmd)
3071 3066 if defaults:
3072 3067 args = shlex.split(defaults) + args
3073 3068 c = list(i[1])
3074 3069 else:
3075 3070 cmd = None
3076 3071 c = []
3077 3072
3078 3073 # combine global options into local
3079 3074 for o in globalopts:
3080 3075 c.append((o[0], o[1], options[o[1]], o[3]))
3081 3076
3082 3077 try:
3083 3078 args = fancyopts.fancyopts(args, c, cmdoptions)
3084 3079 except fancyopts.getopt.GetoptError, inst:
3085 3080 raise ParseError(cmd, inst)
3086 3081
3087 3082 # separate global options back out
3088 3083 for o in globalopts:
3089 3084 n = o[1]
3090 3085 options[n] = cmdoptions[n]
3091 3086 del cmdoptions[n]
3092 3087
3093 3088 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
3094 3089
3095 3090 external = {}
3096 3091
3097 3092 def findext(name):
3098 3093 '''return module with given extension name'''
3099 3094 try:
3100 3095 return sys.modules[external[name]]
3101 3096 except KeyError:
3102 3097 for k, v in external.iteritems():
3103 3098 if k.endswith('.' + name) or k.endswith('/' + name) or v == name:
3104 3099 return sys.modules[v]
3105 3100 raise KeyError(name)
3106 3101
3107 3102 def load_extensions(ui):
3108 3103 added = []
3109 3104 for ext_name, load_from_name in ui.extensions():
3110 3105 if ext_name in external:
3111 3106 continue
3112 3107 try:
3113 3108 if load_from_name:
3114 3109 # the module will be loaded in sys.modules
3115 3110 # choose an unique name so that it doesn't
3116 3111 # conflicts with other modules
3117 3112 module_name = "hgext_%s" % ext_name.replace('.', '_')
3118 3113 mod = imp.load_source(module_name, load_from_name)
3119 3114 else:
3120 3115 def importh(name):
3121 3116 mod = __import__(name)
3122 3117 components = name.split('.')
3123 3118 for comp in components[1:]:
3124 3119 mod = getattr(mod, comp)
3125 3120 return mod
3126 3121 try:
3127 3122 mod = importh("hgext.%s" % ext_name)
3128 3123 except ImportError:
3129 3124 mod = importh(ext_name)
3130 3125 external[ext_name] = mod.__name__
3131 3126 added.append((mod, ext_name))
3132 3127 except (util.SignalInterrupt, KeyboardInterrupt):
3133 3128 raise
3134 3129 except Exception, inst:
3135 3130 ui.warn(_("*** failed to import extension %s: %s\n") %
3136 3131 (ext_name, inst))
3137 3132 if ui.print_exc():
3138 3133 return 1
3139 3134
3140 3135 for mod, name in added:
3141 3136 uisetup = getattr(mod, 'uisetup', None)
3142 3137 if uisetup:
3143 3138 uisetup(ui)
3144 3139 reposetup = getattr(mod, 'reposetup', None)
3145 3140 if reposetup:
3146 3141 hg.repo_setup_hooks.append(reposetup)
3147 3142 cmdtable = getattr(mod, 'cmdtable', {})
3148 3143 overrides = [cmd for cmd in cmdtable if cmd in table]
3149 3144 if overrides:
3150 3145 ui.warn(_("extension '%s' overrides commands: %s\n")
3151 3146 % (name, " ".join(overrides)))
3152 3147 table.update(cmdtable)
3153 3148
3154 3149 def parseconfig(config):
3155 3150 """parse the --config options from the command line"""
3156 3151 parsed = []
3157 3152 for cfg in config:
3158 3153 try:
3159 3154 name, value = cfg.split('=', 1)
3160 3155 section, name = name.split('.', 1)
3161 3156 if not section or not name:
3162 3157 raise IndexError
3163 3158 parsed.append((section, name, value))
3164 3159 except (IndexError, ValueError):
3165 3160 raise util.Abort(_('malformed --config option: %s') % cfg)
3166 3161 return parsed
3167 3162
3168 3163 def dispatch(args):
3169 3164 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
3170 3165 num = getattr(signal, name, None)
3171 3166 if num: signal.signal(num, catchterm)
3172 3167
3173 3168 try:
3174 3169 u = ui.ui(traceback='--traceback' in sys.argv[1:])
3175 3170 except util.Abort, inst:
3176 3171 sys.stderr.write(_("abort: %s\n") % inst)
3177 3172 return -1
3178 3173
3179 3174 load_extensions(u)
3180 3175 u.addreadhook(load_extensions)
3181 3176
3182 3177 try:
3183 3178 cmd, func, args, options, cmdoptions = parse(u, args)
3184 3179 if options["encoding"]:
3185 3180 util._encoding = options["encoding"]
3186 3181 if options["encodingmode"]:
3187 3182 util._encodingmode = options["encodingmode"]
3188 3183 if options["time"]:
3189 3184 def get_times():
3190 3185 t = os.times()
3191 3186 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
3192 3187 t = (t[0], t[1], t[2], t[3], time.clock())
3193 3188 return t
3194 3189 s = get_times()
3195 3190 def print_time():
3196 3191 t = get_times()
3197 3192 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
3198 3193 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
3199 3194 atexit.register(print_time)
3200 3195
3201 3196 # enter the debugger before command execution
3202 3197 if options['debugger']:
3203 3198 pdb.set_trace()
3204 3199
3205 3200 try:
3206 3201 if options['cwd']:
3207 3202 os.chdir(options['cwd'])
3208 3203
3209 3204 u.updateopts(options["verbose"], options["debug"], options["quiet"],
3210 3205 not options["noninteractive"], options["traceback"],
3211 3206 parseconfig(options["config"]))
3212 3207
3213 3208 path = u.expandpath(options["repository"]) or ""
3214 3209 repo = path and hg.repository(u, path=path) or None
3215 3210 if repo and not repo.local():
3216 3211 raise util.Abort(_("repository '%s' is not local") % path)
3217 3212
3218 3213 if options['help']:
3219 3214 return help_(u, cmd, options['version'])
3220 3215 elif options['version']:
3221 3216 return version_(u)
3222 3217 elif not cmd:
3223 3218 return help_(u, 'shortlist')
3224 3219
3225 3220 if cmd not in norepo.split():
3226 3221 try:
3227 3222 if not repo:
3228 3223 repo = hg.repository(u, path=path)
3229 3224 u = repo.ui
3230 3225 except hg.RepoError:
3231 3226 if cmd not in optionalrepo.split():
3232 3227 raise
3233 3228 d = lambda: func(u, repo, *args, **cmdoptions)
3234 3229 else:
3235 3230 d = lambda: func(u, *args, **cmdoptions)
3236 3231
3237 3232 try:
3238 3233 if options['profile']:
3239 3234 import hotshot, hotshot.stats
3240 3235 prof = hotshot.Profile("hg.prof")
3241 3236 try:
3242 3237 try:
3243 3238 return prof.runcall(d)
3244 3239 except:
3245 3240 try:
3246 3241 u.warn(_('exception raised - generating '
3247 3242 'profile anyway\n'))
3248 3243 except:
3249 3244 pass
3250 3245 raise
3251 3246 finally:
3252 3247 prof.close()
3253 3248 stats = hotshot.stats.load("hg.prof")
3254 3249 stats.strip_dirs()
3255 3250 stats.sort_stats('time', 'calls')
3256 3251 stats.print_stats(40)
3257 3252 elif options['lsprof']:
3258 3253 try:
3259 3254 from mercurial import lsprof
3260 3255 except ImportError:
3261 3256 raise util.Abort(_(
3262 3257 'lsprof not available - install from '
3263 3258 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
3264 3259 p = lsprof.Profiler()
3265 3260 p.enable(subcalls=True)
3266 3261 try:
3267 3262 return d()
3268 3263 finally:
3269 3264 p.disable()
3270 3265 stats = lsprof.Stats(p.getstats())
3271 3266 stats.sort()
3272 3267 stats.pprint(top=10, file=sys.stderr, climit=5)
3273 3268 else:
3274 3269 return d()
3275 3270 finally:
3276 3271 u.flush()
3277 3272 except:
3278 3273 # enter the debugger when we hit an exception
3279 3274 if options['debugger']:
3280 3275 pdb.post_mortem(sys.exc_info()[2])
3281 3276 u.print_exc()
3282 3277 raise
3283 3278 except ParseError, inst:
3284 3279 if inst.args[0]:
3285 3280 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
3286 3281 help_(u, inst.args[0])
3287 3282 else:
3288 3283 u.warn(_("hg: %s\n") % inst.args[1])
3289 3284 help_(u, 'shortlist')
3290 3285 except AmbiguousCommand, inst:
3291 3286 u.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
3292 3287 (inst.args[0], " ".join(inst.args[1])))
3293 3288 except UnknownCommand, inst:
3294 3289 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
3295 3290 help_(u, 'shortlist')
3296 3291 except hg.RepoError, inst:
3297 3292 u.warn(_("abort: %s!\n") % inst)
3298 3293 except lock.LockHeld, inst:
3299 3294 if inst.errno == errno.ETIMEDOUT:
3300 3295 reason = _('timed out waiting for lock held by %s') % inst.locker
3301 3296 else:
3302 3297 reason = _('lock held by %s') % inst.locker
3303 3298 u.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
3304 3299 except lock.LockUnavailable, inst:
3305 3300 u.warn(_("abort: could not lock %s: %s\n") %
3306 3301 (inst.desc or inst.filename, inst.strerror))
3307 3302 except revlog.RevlogError, inst:
3308 3303 u.warn(_("abort: %s!\n") % inst)
3309 3304 except util.SignalInterrupt:
3310 3305 u.warn(_("killed!\n"))
3311 3306 except KeyboardInterrupt:
3312 3307 try:
3313 3308 u.warn(_("interrupted!\n"))
3314 3309 except IOError, inst:
3315 3310 if inst.errno == errno.EPIPE:
3316 3311 if u.debugflag:
3317 3312 u.warn(_("\nbroken pipe\n"))
3318 3313 else:
3319 3314 raise
3320 3315 except socket.error, inst:
3321 3316 u.warn(_("abort: %s\n") % inst[1])
3322 3317 except IOError, inst:
3323 3318 if hasattr(inst, "code"):
3324 3319 u.warn(_("abort: %s\n") % inst)
3325 3320 elif hasattr(inst, "reason"):
3326 3321 try: # usually it is in the form (errno, strerror)
3327 3322 reason = inst.reason.args[1]
3328 3323 except: # it might be anything, for example a string
3329 3324 reason = inst.reason
3330 3325 u.warn(_("abort: error: %s\n") % reason)
3331 3326 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
3332 3327 if u.debugflag:
3333 3328 u.warn(_("broken pipe\n"))
3334 3329 elif getattr(inst, "strerror", None):
3335 3330 if getattr(inst, "filename", None):
3336 3331 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3337 3332 else:
3338 3333 u.warn(_("abort: %s\n") % inst.strerror)
3339 3334 else:
3340 3335 raise
3341 3336 except OSError, inst:
3342 3337 if getattr(inst, "filename", None):
3343 3338 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3344 3339 else:
3345 3340 u.warn(_("abort: %s\n") % inst.strerror)
3346 3341 except util.UnexpectedOutput, inst:
3347 3342 u.warn(_("abort: %s") % inst[0])
3348 3343 if not isinstance(inst[1], basestring):
3349 3344 u.warn(" %r\n" % (inst[1],))
3350 3345 elif not inst[1]:
3351 3346 u.warn(_(" empty string\n"))
3352 3347 else:
3353 3348 u.warn("\n%r\n" % util.ellipsis(inst[1]))
3354 3349 except util.Abort, inst:
3355 3350 u.warn(_("abort: %s\n") % inst)
3356 3351 except TypeError, inst:
3357 3352 # was this an argument error?
3358 3353 tb = traceback.extract_tb(sys.exc_info()[2])
3359 3354 if len(tb) > 2: # no
3360 3355 raise
3361 3356 u.debug(inst, "\n")
3362 3357 u.warn(_("%s: invalid arguments\n") % cmd)
3363 3358 help_(u, cmd)
3364 3359 except SystemExit, inst:
3365 3360 # Commands shouldn't sys.exit directly, but give a return code.
3366 3361 # Just in case catch this and and pass exit code to caller.
3367 3362 return inst.code
3368 3363 except:
3369 3364 u.warn(_("** unknown exception encountered, details follow\n"))
3370 3365 u.warn(_("** report bug details to "
3371 3366 "http://www.selenic.com/mercurial/bts\n"))
3372 3367 u.warn(_("** or mercurial@selenic.com\n"))
3373 3368 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
3374 3369 % version.get_version())
3375 3370 raise
3376 3371
3377 3372 return -1
@@ -1,666 +1,668 b''
1 1 # patch.py - patch file parsing routines
2 2 #
3 3 # Copyright 2006 Brendan Cully <brendan@kublai.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 i18n import _
9 9 from node import *
10 10 import base85, cmdutil, mdiff, util, context, revlog
11 11 import cStringIO, email.Parser, os, popen2, re, sha
12 12 import sys, tempfile, zlib
13 13
14 14 # helper functions
15 15
16 16 def copyfile(src, dst, basedir=None):
17 17 if not basedir:
18 18 basedir = os.getcwd()
19 19
20 20 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
21 21 if os.path.exists(absdst):
22 22 raise util.Abort(_("cannot create %s: destination already exists") %
23 23 dst)
24 24
25 25 targetdir = os.path.dirname(absdst)
26 26 if not os.path.isdir(targetdir):
27 27 os.makedirs(targetdir)
28 28
29 29 util.copyfile(abssrc, absdst)
30 30
31 31 # public functions
32 32
33 33 def extract(ui, fileobj):
34 34 '''extract patch from data read from fileobj.
35 35
36 36 patch can be a normal patch or contained in an email message.
37 37
38 38 return tuple (filename, message, user, date, node, p1, p2).
39 39 Any item in the returned tuple can be None. If filename is None,
40 40 fileobj did not contain a patch. Caller must unlink filename when done.'''
41 41
42 42 # attempt to detect the start of a patch
43 43 # (this heuristic is borrowed from quilt)
44 44 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
45 45 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
46 46 '(---|\*\*\*)[ \t])', re.MULTILINE)
47 47
48 48 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
49 49 tmpfp = os.fdopen(fd, 'w')
50 50 try:
51 51 msg = email.Parser.Parser().parse(fileobj)
52 52
53 53 message = msg['Subject']
54 54 user = msg['From']
55 55 # should try to parse msg['Date']
56 56 date = None
57 57 nodeid = None
58 58 parents = []
59 59
60 60 if message:
61 61 if message.startswith('[PATCH'):
62 62 pend = message.find(']')
63 63 if pend >= 0:
64 64 message = message[pend+1:].lstrip()
65 65 message = message.replace('\n\t', ' ')
66 66 ui.debug('Subject: %s\n' % message)
67 67 if user:
68 68 ui.debug('From: %s\n' % user)
69 69 diffs_seen = 0
70 70 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
71 71
72 72 for part in msg.walk():
73 73 content_type = part.get_content_type()
74 74 ui.debug('Content-Type: %s\n' % content_type)
75 75 if content_type not in ok_types:
76 76 continue
77 77 payload = part.get_payload(decode=True)
78 78 m = diffre.search(payload)
79 79 if m:
80 80 hgpatch = False
81 81 ignoretext = False
82 82
83 83 ui.debug(_('found patch at byte %d\n') % m.start(0))
84 84 diffs_seen += 1
85 85 cfp = cStringIO.StringIO()
86 86 if message:
87 87 cfp.write(message)
88 88 cfp.write('\n')
89 89 for line in payload[:m.start(0)].splitlines():
90 90 if line.startswith('# HG changeset patch'):
91 91 ui.debug(_('patch generated by hg export\n'))
92 92 hgpatch = True
93 93 # drop earlier commit message content
94 94 cfp.seek(0)
95 95 cfp.truncate()
96 96 elif hgpatch:
97 97 if line.startswith('# User '):
98 98 user = line[7:]
99 99 ui.debug('From: %s\n' % user)
100 100 elif line.startswith("# Date "):
101 101 date = line[7:]
102 102 elif line.startswith("# Node ID "):
103 103 nodeid = line[10:]
104 104 elif line.startswith("# Parent "):
105 105 parents.append(line[10:])
106 106 elif line == '---' and 'git-send-email' in msg['X-Mailer']:
107 107 ignoretext = True
108 108 if not line.startswith('# ') and not ignoretext:
109 109 cfp.write(line)
110 110 cfp.write('\n')
111 111 message = cfp.getvalue()
112 112 if tmpfp:
113 113 tmpfp.write(payload)
114 114 if not payload.endswith('\n'):
115 115 tmpfp.write('\n')
116 116 elif not diffs_seen and message and content_type == 'text/plain':
117 117 message += '\n' + payload
118 118 except:
119 119 tmpfp.close()
120 120 os.unlink(tmpname)
121 121 raise
122 122
123 123 tmpfp.close()
124 124 if not diffs_seen:
125 125 os.unlink(tmpname)
126 126 return None, message, user, date, None, None, None
127 127 p1 = parents and parents.pop(0) or None
128 128 p2 = parents and parents.pop(0) or None
129 129 return tmpname, message, user, date, nodeid, p1, p2
130 130
131 131 GP_PATCH = 1 << 0 # we have to run patch
132 132 GP_FILTER = 1 << 1 # there's some copy/rename operation
133 133 GP_BINARY = 1 << 2 # there's a binary patch
134 134
135 135 def readgitpatch(patchname):
136 136 """extract git-style metadata about patches from <patchname>"""
137 137 class gitpatch:
138 138 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
139 139 def __init__(self, path):
140 140 self.path = path
141 141 self.oldpath = None
142 142 self.mode = None
143 143 self.op = 'MODIFY'
144 144 self.copymod = False
145 145 self.lineno = 0
146 146 self.binary = False
147 147
148 148 # Filter patch for git information
149 149 gitre = re.compile('diff --git a/(.*) b/(.*)')
150 150 pf = file(patchname)
151 151 gp = None
152 152 gitpatches = []
153 153 # Can have a git patch with only metadata, causing patch to complain
154 154 dopatch = 0
155 155
156 156 lineno = 0
157 157 for line in pf:
158 158 lineno += 1
159 159 if line.startswith('diff --git'):
160 160 m = gitre.match(line)
161 161 if m:
162 162 if gp:
163 163 gitpatches.append(gp)
164 164 src, dst = m.group(1, 2)
165 165 gp = gitpatch(dst)
166 166 gp.lineno = lineno
167 167 elif gp:
168 168 if line.startswith('--- '):
169 169 if gp.op in ('COPY', 'RENAME'):
170 170 gp.copymod = True
171 171 dopatch |= GP_FILTER
172 172 gitpatches.append(gp)
173 173 gp = None
174 174 dopatch |= GP_PATCH
175 175 continue
176 176 if line.startswith('rename from '):
177 177 gp.op = 'RENAME'
178 178 gp.oldpath = line[12:].rstrip()
179 179 elif line.startswith('rename to '):
180 180 gp.path = line[10:].rstrip()
181 181 elif line.startswith('copy from '):
182 182 gp.op = 'COPY'
183 183 gp.oldpath = line[10:].rstrip()
184 184 elif line.startswith('copy to '):
185 185 gp.path = line[8:].rstrip()
186 186 elif line.startswith('deleted file'):
187 187 gp.op = 'DELETE'
188 188 elif line.startswith('new file mode '):
189 189 gp.op = 'ADD'
190 190 gp.mode = int(line.rstrip()[-3:], 8)
191 191 elif line.startswith('new mode '):
192 192 gp.mode = int(line.rstrip()[-3:], 8)
193 193 elif line.startswith('GIT binary patch'):
194 194 dopatch |= GP_BINARY
195 195 gp.binary = True
196 196 if gp:
197 197 gitpatches.append(gp)
198 198
199 199 if not gitpatches:
200 200 dopatch = GP_PATCH
201 201
202 202 return (dopatch, gitpatches)
203 203
204 204 def dogitpatch(patchname, gitpatches, cwd=None):
205 205 """Preprocess git patch so that vanilla patch can handle it"""
206 206 def extractbin(fp):
207 207 i = [0] # yuck
208 208 def readline():
209 209 i[0] += 1
210 210 return fp.readline().rstrip()
211 211 line = readline()
212 212 while line and not line.startswith('literal '):
213 213 line = readline()
214 214 if not line:
215 215 return None, i[0]
216 216 size = int(line[8:])
217 217 dec = []
218 218 line = readline()
219 219 while line:
220 220 l = line[0]
221 221 if l <= 'Z' and l >= 'A':
222 222 l = ord(l) - ord('A') + 1
223 223 else:
224 224 l = ord(l) - ord('a') + 27
225 225 dec.append(base85.b85decode(line[1:])[:l])
226 226 line = readline()
227 227 text = zlib.decompress(''.join(dec))
228 228 if len(text) != size:
229 229 raise util.Abort(_('binary patch is %d bytes, not %d') %
230 230 (len(text), size))
231 231 return text, i[0]
232 232
233 233 pf = file(patchname)
234 234 pfline = 1
235 235
236 236 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
237 237 tmpfp = os.fdopen(fd, 'w')
238 238
239 239 try:
240 240 for i in xrange(len(gitpatches)):
241 241 p = gitpatches[i]
242 242 if not p.copymod and not p.binary:
243 243 continue
244 244
245 245 # rewrite patch hunk
246 246 while pfline < p.lineno:
247 247 tmpfp.write(pf.readline())
248 248 pfline += 1
249 249
250 250 if p.binary:
251 251 text, delta = extractbin(pf)
252 252 if not text:
253 253 raise util.Abort(_('binary patch extraction failed'))
254 254 pfline += delta
255 255 if not cwd:
256 256 cwd = os.getcwd()
257 257 absdst = os.path.join(cwd, p.path)
258 258 basedir = os.path.dirname(absdst)
259 259 if not os.path.isdir(basedir):
260 260 os.makedirs(basedir)
261 261 out = file(absdst, 'wb')
262 262 out.write(text)
263 263 out.close()
264 264 elif p.copymod:
265 265 copyfile(p.oldpath, p.path, basedir=cwd)
266 266 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
267 267 line = pf.readline()
268 268 pfline += 1
269 269 while not line.startswith('--- a/'):
270 270 tmpfp.write(line)
271 271 line = pf.readline()
272 272 pfline += 1
273 273 tmpfp.write('--- a/%s\n' % p.path)
274 274
275 275 line = pf.readline()
276 276 while line:
277 277 tmpfp.write(line)
278 278 line = pf.readline()
279 279 except:
280 280 tmpfp.close()
281 281 os.unlink(patchname)
282 282 raise
283 283
284 284 tmpfp.close()
285 285 return patchname
286 286
287 287 def patch(patchname, ui, strip=1, cwd=None, files={}):
288 288 """apply the patch <patchname> to the working directory.
289 289 a list of patched files is returned"""
290 290
291 291 # helper function
292 292 def __patch(patchname):
293 293 """patch and updates the files and fuzz variables"""
294 294 fuzz = False
295 295
296 296 args = []
297 297 patcher = ui.config('ui', 'patch')
298 patcher = ((patcher and util.find_exe(patcher)) or
299 util.find_exe('gpatch') or
300 util.find_exe('patch'))
298 301 if not patcher:
299 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''),
300 'patch')
301 if util.needbinarypatch():
302 args.append('--binary')
302 raise util.Abort(_('no patch command found in hgrc or PATH'))
303 if util.needbinarypatch():
304 args.append('--binary')
303 305
304 306 if cwd:
305 307 args.append('-d %s' % util.shellquote(cwd))
306 308 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
307 309 util.shellquote(patchname)))
308 310
309 311 for line in fp:
310 312 line = line.rstrip()
311 313 ui.note(line + '\n')
312 314 if line.startswith('patching file '):
313 315 pf = util.parse_patch_output(line)
314 316 printed_file = False
315 317 files.setdefault(pf, (None, None))
316 318 elif line.find('with fuzz') >= 0:
317 319 fuzz = True
318 320 if not printed_file:
319 321 ui.warn(pf + '\n')
320 322 printed_file = True
321 323 ui.warn(line + '\n')
322 324 elif line.find('saving rejects to file') >= 0:
323 325 ui.warn(line + '\n')
324 326 elif line.find('FAILED') >= 0:
325 327 if not printed_file:
326 328 ui.warn(pf + '\n')
327 329 printed_file = True
328 330 ui.warn(line + '\n')
329 331 code = fp.close()
330 332 if code:
331 333 raise util.Abort(_("patch command failed: %s") %
332 334 util.explain_exit(code)[0])
333 335 return fuzz
334 336
335 337 (dopatch, gitpatches) = readgitpatch(patchname)
336 338 for gp in gitpatches:
337 339 files[gp.path] = (gp.op, gp)
338 340
339 341 fuzz = False
340 342 if dopatch:
341 343 filterpatch = dopatch & (GP_FILTER | GP_BINARY)
342 344 if filterpatch:
343 345 patchname = dogitpatch(patchname, gitpatches, cwd=cwd)
344 346 try:
345 347 if dopatch & GP_PATCH:
346 348 fuzz = __patch(patchname)
347 349 finally:
348 350 if filterpatch:
349 351 os.unlink(patchname)
350 352
351 353 return fuzz
352 354
353 355 def diffopts(ui, opts={}, untrusted=False):
354 356 def get(key, name=None):
355 357 return (opts.get(key) or
356 358 ui.configbool('diff', name or key, None, untrusted=untrusted))
357 359 return mdiff.diffopts(
358 360 text=opts.get('text'),
359 361 git=get('git'),
360 362 nodates=get('nodates'),
361 363 showfunc=get('show_function', 'showfunc'),
362 364 ignorews=get('ignore_all_space', 'ignorews'),
363 365 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
364 366 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'))
365 367
366 368 def updatedir(ui, repo, patches, wlock=None):
367 369 '''Update dirstate after patch application according to metadata'''
368 370 if not patches:
369 371 return
370 372 copies = []
371 373 removes = {}
372 374 cfiles = patches.keys()
373 375 cwd = repo.getcwd()
374 376 if cwd:
375 377 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
376 378 for f in patches:
377 379 ctype, gp = patches[f]
378 380 if ctype == 'RENAME':
379 381 copies.append((gp.oldpath, gp.path, gp.copymod))
380 382 removes[gp.oldpath] = 1
381 383 elif ctype == 'COPY':
382 384 copies.append((gp.oldpath, gp.path, gp.copymod))
383 385 elif ctype == 'DELETE':
384 386 removes[gp.path] = 1
385 387 for src, dst, after in copies:
386 388 if not after:
387 389 copyfile(src, dst, repo.root)
388 390 repo.copy(src, dst, wlock=wlock)
389 391 removes = removes.keys()
390 392 if removes:
391 393 removes.sort()
392 394 repo.remove(removes, True, wlock=wlock)
393 395 for f in patches:
394 396 ctype, gp = patches[f]
395 397 if gp and gp.mode:
396 398 x = gp.mode & 0100 != 0
397 399 dst = os.path.join(repo.root, gp.path)
398 400 # patch won't create empty files
399 401 if ctype == 'ADD' and not os.path.exists(dst):
400 402 repo.wwrite(gp.path, '', x and 'x' or '')
401 403 else:
402 404 util.set_exec(dst, x)
403 405 cmdutil.addremove(repo, cfiles, wlock=wlock)
404 406 files = patches.keys()
405 407 files.extend([r for r in removes if r not in files])
406 408 files.sort()
407 409
408 410 return files
409 411
410 412 def b85diff(fp, to, tn):
411 413 '''print base85-encoded binary diff'''
412 414 def gitindex(text):
413 415 if not text:
414 416 return '0' * 40
415 417 l = len(text)
416 418 s = sha.new('blob %d\0' % l)
417 419 s.update(text)
418 420 return s.hexdigest()
419 421
420 422 def fmtline(line):
421 423 l = len(line)
422 424 if l <= 26:
423 425 l = chr(ord('A') + l - 1)
424 426 else:
425 427 l = chr(l - 26 + ord('a') - 1)
426 428 return '%c%s\n' % (l, base85.b85encode(line, True))
427 429
428 430 def chunk(text, csize=52):
429 431 l = len(text)
430 432 i = 0
431 433 while i < l:
432 434 yield text[i:i+csize]
433 435 i += csize
434 436
435 437 tohash = gitindex(to)
436 438 tnhash = gitindex(tn)
437 439 if tohash == tnhash:
438 440 return ""
439 441
440 442 # TODO: deltas
441 443 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
442 444 (tohash, tnhash, len(tn))]
443 445 for l in chunk(zlib.compress(tn)):
444 446 ret.append(fmtline(l))
445 447 ret.append('\n')
446 448 return ''.join(ret)
447 449
448 450 def diff(repo, node1=None, node2=None, files=None, match=util.always,
449 451 fp=None, changes=None, opts=None):
450 452 '''print diff of changes to files between two nodes, or node and
451 453 working directory.
452 454
453 455 if node1 is None, use first dirstate parent instead.
454 456 if node2 is None, compare node1 with working directory.'''
455 457
456 458 if opts is None:
457 459 opts = mdiff.defaultopts
458 460 if fp is None:
459 461 fp = repo.ui
460 462
461 463 if not node1:
462 464 node1 = repo.dirstate.parents()[0]
463 465
464 466 ccache = {}
465 467 def getctx(r):
466 468 if r not in ccache:
467 469 ccache[r] = context.changectx(repo, r)
468 470 return ccache[r]
469 471
470 472 flcache = {}
471 473 def getfilectx(f, ctx):
472 474 flctx = ctx.filectx(f, filelog=flcache.get(f))
473 475 if f not in flcache:
474 476 flcache[f] = flctx._filelog
475 477 return flctx
476 478
477 479 # reading the data for node1 early allows it to play nicely
478 480 # with repo.status and the revlog cache.
479 481 ctx1 = context.changectx(repo, node1)
480 482 # force manifest reading
481 483 man1 = ctx1.manifest()
482 484 date1 = util.datestr(ctx1.date())
483 485
484 486 if not changes:
485 487 changes = repo.status(node1, node2, files, match=match)[:5]
486 488 modified, added, removed, deleted, unknown = changes
487 489
488 490 if not modified and not added and not removed:
489 491 return
490 492
491 493 if node2:
492 494 ctx2 = context.changectx(repo, node2)
493 495 else:
494 496 ctx2 = context.workingctx(repo)
495 497 man2 = ctx2.manifest()
496 498
497 499 # returns False if there was no rename between ctx1 and ctx2
498 500 # returns None if the file was created between ctx1 and ctx2
499 501 # returns the (file, node) present in ctx1 that was renamed to f in ctx2
500 502 def renamed(f):
501 503 startrev = ctx1.rev()
502 504 c = ctx2
503 505 crev = c.rev()
504 506 if crev is None:
505 507 crev = repo.changelog.count()
506 508 orig = f
507 509 while crev > startrev:
508 510 if f in c.files():
509 511 try:
510 512 src = getfilectx(f, c).renamed()
511 513 except revlog.LookupError:
512 514 return None
513 515 if src:
514 516 f = src[0]
515 517 crev = c.parents()[0].rev()
516 518 # try to reuse
517 519 c = getctx(crev)
518 520 if f not in man1:
519 521 return None
520 522 if f == orig:
521 523 return False
522 524 return f
523 525
524 526 if repo.ui.quiet:
525 527 r = None
526 528 else:
527 529 hexfunc = repo.ui.debugflag and hex or short
528 530 r = [hexfunc(node) for node in [node1, node2] if node]
529 531
530 532 if opts.git:
531 533 copied = {}
532 534 for f in added:
533 535 src = renamed(f)
534 536 if src:
535 537 copied[f] = src
536 538 srcs = [x[1] for x in copied.items()]
537 539
538 540 all = modified + added + removed
539 541 all.sort()
540 542 gone = {}
541 543
542 544 for f in all:
543 545 to = None
544 546 tn = None
545 547 dodiff = True
546 548 header = []
547 549 if f in man1:
548 550 to = getfilectx(f, ctx1).data()
549 551 if f not in removed:
550 552 tn = getfilectx(f, ctx2).data()
551 553 if opts.git:
552 554 def gitmode(x):
553 555 return x and '100755' or '100644'
554 556 def addmodehdr(header, omode, nmode):
555 557 if omode != nmode:
556 558 header.append('old mode %s\n' % omode)
557 559 header.append('new mode %s\n' % nmode)
558 560
559 561 a, b = f, f
560 562 if f in added:
561 563 mode = gitmode(man2.execf(f))
562 564 if f in copied:
563 565 a = copied[f]
564 566 omode = gitmode(man1.execf(a))
565 567 addmodehdr(header, omode, mode)
566 568 if a in removed and a not in gone:
567 569 op = 'rename'
568 570 gone[a] = 1
569 571 else:
570 572 op = 'copy'
571 573 header.append('%s from %s\n' % (op, a))
572 574 header.append('%s to %s\n' % (op, f))
573 575 to = getfilectx(a, ctx1).data()
574 576 else:
575 577 header.append('new file mode %s\n' % mode)
576 578 if util.binary(tn):
577 579 dodiff = 'binary'
578 580 elif f in removed:
579 581 if f in srcs:
580 582 dodiff = False
581 583 else:
582 584 mode = gitmode(man1.execf(f))
583 585 header.append('deleted file mode %s\n' % mode)
584 586 else:
585 587 omode = gitmode(man1.execf(f))
586 588 nmode = gitmode(man2.execf(f))
587 589 addmodehdr(header, omode, nmode)
588 590 if util.binary(to) or util.binary(tn):
589 591 dodiff = 'binary'
590 592 r = None
591 593 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
592 594 if dodiff:
593 595 if dodiff == 'binary':
594 596 text = b85diff(fp, to, tn)
595 597 else:
596 598 text = mdiff.unidiff(to, date1,
597 599 # ctx2 date may be dynamic
598 600 tn, util.datestr(ctx2.date()),
599 601 f, r, opts=opts)
600 602 if text or len(header) > 1:
601 603 fp.write(''.join(header))
602 604 fp.write(text)
603 605
604 606 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
605 607 opts=None):
606 608 '''export changesets as hg patches.'''
607 609
608 610 total = len(revs)
609 611 revwidth = max([len(str(rev)) for rev in revs])
610 612
611 613 def single(rev, seqno, fp):
612 614 ctx = repo.changectx(rev)
613 615 node = ctx.node()
614 616 parents = [p.node() for p in ctx.parents() if p]
615 617 branch = ctx.branch()
616 618 if switch_parent:
617 619 parents.reverse()
618 620 prev = (parents and parents[0]) or nullid
619 621
620 622 if not fp:
621 623 fp = cmdutil.make_file(repo, template, node, total=total,
622 624 seqno=seqno, revwidth=revwidth)
623 625 if fp != sys.stdout and hasattr(fp, 'name'):
624 626 repo.ui.note("%s\n" % fp.name)
625 627
626 628 fp.write("# HG changeset patch\n")
627 629 fp.write("# User %s\n" % ctx.user())
628 630 fp.write("# Date %d %d\n" % ctx.date())
629 631 if branch and (branch != 'default'):
630 632 fp.write("# Branch %s\n" % branch)
631 633 fp.write("# Node ID %s\n" % hex(node))
632 634 fp.write("# Parent %s\n" % hex(prev))
633 635 if len(parents) > 1:
634 636 fp.write("# Parent %s\n" % hex(parents[1]))
635 637 fp.write(ctx.description().rstrip())
636 638 fp.write("\n\n")
637 639
638 640 diff(repo, prev, node, fp=fp, opts=opts)
639 641 if fp not in (sys.stdout, repo.ui):
640 642 fp.close()
641 643
642 644 for seqno, rev in enumerate(revs):
643 645 single(rev, seqno+1, fp)
644 646
645 647 def diffstat(patchlines):
646 if not util.find_in_path('diffstat', os.environ.get('PATH', '')):
648 if not util.find_exe('diffstat'):
647 649 return
648 650 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
649 651 try:
650 652 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
651 653 try:
652 654 for line in patchlines: print >> p.tochild, line
653 655 p.tochild.close()
654 656 if p.wait(): return
655 657 fp = os.fdopen(fd, 'r')
656 658 stat = []
657 659 for line in fp: stat.append(line.lstrip())
658 660 last = stat.pop()
659 661 stat.insert(0, last)
660 662 stat = ''.join(stat)
661 663 if stat.startswith('0 files'): raise ValueError
662 664 return stat
663 665 except: raise
664 666 finally:
665 667 try: os.unlink(name)
666 668 except: pass
@@ -1,1531 +1,1543 b''
1 1 """
2 2 util.py - Mercurial utility functions and platform specfic implementations
3 3
4 4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 5 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
6 6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7 7
8 8 This software may be used and distributed according to the terms
9 9 of the GNU General Public License, incorporated herein by reference.
10 10
11 11 This contains helper routines that are independent of the SCM core and hide
12 12 platform-specific details from the core.
13 13 """
14 14
15 15 from i18n import _
16 16 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile
17 17 import os, threading, time, calendar, ConfigParser, locale, glob
18 18
19 19 try:
20 20 _encoding = os.environ.get("HGENCODING") or locale.getpreferredencoding() \
21 21 or "ascii"
22 22 except locale.Error:
23 23 _encoding = 'ascii'
24 24 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
25 25 _fallbackencoding = 'ISO-8859-1'
26 26
27 27 def tolocal(s):
28 28 """
29 29 Convert a string from internal UTF-8 to local encoding
30 30
31 31 All internal strings should be UTF-8 but some repos before the
32 32 implementation of locale support may contain latin1 or possibly
33 33 other character sets. We attempt to decode everything strictly
34 34 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
35 35 replace unknown characters.
36 36 """
37 37 for e in ('UTF-8', _fallbackencoding):
38 38 try:
39 39 u = s.decode(e) # attempt strict decoding
40 40 return u.encode(_encoding, "replace")
41 41 except LookupError, k:
42 42 raise Abort(_("%s, please check your locale settings") % k)
43 43 except UnicodeDecodeError:
44 44 pass
45 45 u = s.decode("utf-8", "replace") # last ditch
46 46 return u.encode(_encoding, "replace")
47 47
48 48 def fromlocal(s):
49 49 """
50 50 Convert a string from the local character encoding to UTF-8
51 51
52 52 We attempt to decode strings using the encoding mode set by
53 53 HG_ENCODINGMODE, which defaults to 'strict'. In this mode, unknown
54 54 characters will cause an error message. Other modes include
55 55 'replace', which replaces unknown characters with a special
56 56 Unicode character, and 'ignore', which drops the character.
57 57 """
58 58 try:
59 59 return s.decode(_encoding, _encodingmode).encode("utf-8")
60 60 except UnicodeDecodeError, inst:
61 61 sub = s[max(0, inst.start-10):inst.start+10]
62 62 raise Abort("decoding near '%s': %s!" % (sub, inst))
63 63 except LookupError, k:
64 64 raise Abort(_("%s, please check your locale settings") % k)
65 65
66 66 def locallen(s):
67 67 """Find the length in characters of a local string"""
68 68 return len(s.decode(_encoding, "replace"))
69 69
70 70 def localsub(s, a, b=None):
71 71 try:
72 72 u = s.decode(_encoding, _encodingmode)
73 73 if b is not None:
74 74 u = u[a:b]
75 75 else:
76 76 u = u[:a]
77 77 return u.encode(_encoding, _encodingmode)
78 78 except UnicodeDecodeError, inst:
79 79 sub = s[max(0, inst.start-10), inst.start+10]
80 80 raise Abort(_("decoding near '%s': %s!\n") % (sub, inst))
81 81
82 82 # used by parsedate
83 83 defaultdateformats = (
84 84 '%Y-%m-%d %H:%M:%S',
85 85 '%Y-%m-%d %I:%M:%S%p',
86 86 '%Y-%m-%d %H:%M',
87 87 '%Y-%m-%d %I:%M%p',
88 88 '%Y-%m-%d',
89 89 '%m-%d',
90 90 '%m/%d',
91 91 '%m/%d/%y',
92 92 '%m/%d/%Y',
93 93 '%a %b %d %H:%M:%S %Y',
94 94 '%a %b %d %I:%M:%S%p %Y',
95 95 '%b %d %H:%M:%S %Y',
96 96 '%b %d %I:%M:%S%p %Y',
97 97 '%b %d %H:%M:%S',
98 98 '%b %d %I:%M:%S%p',
99 99 '%b %d %H:%M',
100 100 '%b %d %I:%M%p',
101 101 '%b %d %Y',
102 102 '%b %d',
103 103 '%H:%M:%S',
104 104 '%I:%M:%SP',
105 105 '%H:%M',
106 106 '%I:%M%p',
107 107 )
108 108
109 109 extendeddateformats = defaultdateformats + (
110 110 "%Y",
111 111 "%Y-%m",
112 112 "%b",
113 113 "%b %Y",
114 114 )
115 115
116 116 class SignalInterrupt(Exception):
117 117 """Exception raised on SIGTERM and SIGHUP."""
118 118
119 119 # differences from SafeConfigParser:
120 120 # - case-sensitive keys
121 121 # - allows values that are not strings (this means that you may not
122 122 # be able to save the configuration to a file)
123 123 class configparser(ConfigParser.SafeConfigParser):
124 124 def optionxform(self, optionstr):
125 125 return optionstr
126 126
127 127 def set(self, section, option, value):
128 128 return ConfigParser.ConfigParser.set(self, section, option, value)
129 129
130 130 def _interpolate(self, section, option, rawval, vars):
131 131 if not isinstance(rawval, basestring):
132 132 return rawval
133 133 return ConfigParser.SafeConfigParser._interpolate(self, section,
134 134 option, rawval, vars)
135 135
136 136 def cachefunc(func):
137 137 '''cache the result of function calls'''
138 138 # XXX doesn't handle keywords args
139 139 cache = {}
140 140 if func.func_code.co_argcount == 1:
141 141 # we gain a small amount of time because
142 142 # we don't need to pack/unpack the list
143 143 def f(arg):
144 144 if arg not in cache:
145 145 cache[arg] = func(arg)
146 146 return cache[arg]
147 147 else:
148 148 def f(*args):
149 149 if args not in cache:
150 150 cache[args] = func(*args)
151 151 return cache[args]
152 152
153 153 return f
154 154
155 155 def pipefilter(s, cmd):
156 156 '''filter string S through command CMD, returning its output'''
157 157 (pout, pin) = popen2.popen2(cmd, -1, 'b')
158 158 def writer():
159 159 try:
160 160 pin.write(s)
161 161 pin.close()
162 162 except IOError, inst:
163 163 if inst.errno != errno.EPIPE:
164 164 raise
165 165
166 166 # we should use select instead on UNIX, but this will work on most
167 167 # systems, including Windows
168 168 w = threading.Thread(target=writer)
169 169 w.start()
170 170 f = pout.read()
171 171 pout.close()
172 172 w.join()
173 173 return f
174 174
175 175 def tempfilter(s, cmd):
176 176 '''filter string S through a pair of temporary files with CMD.
177 177 CMD is used as a template to create the real command to be run,
178 178 with the strings INFILE and OUTFILE replaced by the real names of
179 179 the temporary files generated.'''
180 180 inname, outname = None, None
181 181 try:
182 182 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
183 183 fp = os.fdopen(infd, 'wb')
184 184 fp.write(s)
185 185 fp.close()
186 186 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
187 187 os.close(outfd)
188 188 cmd = cmd.replace('INFILE', inname)
189 189 cmd = cmd.replace('OUTFILE', outname)
190 190 code = os.system(cmd)
191 191 if code: raise Abort(_("command '%s' failed: %s") %
192 192 (cmd, explain_exit(code)))
193 193 return open(outname, 'rb').read()
194 194 finally:
195 195 try:
196 196 if inname: os.unlink(inname)
197 197 except: pass
198 198 try:
199 199 if outname: os.unlink(outname)
200 200 except: pass
201 201
202 202 filtertable = {
203 203 'tempfile:': tempfilter,
204 204 'pipe:': pipefilter,
205 205 }
206 206
207 207 def filter(s, cmd):
208 208 "filter a string through a command that transforms its input to its output"
209 209 for name, fn in filtertable.iteritems():
210 210 if cmd.startswith(name):
211 211 return fn(s, cmd[len(name):].lstrip())
212 212 return pipefilter(s, cmd)
213 213
214 214 def binary(s):
215 215 """return true if a string is binary data using diff's heuristic"""
216 216 if s and '\0' in s[:4096]:
217 217 return True
218 218 return False
219 219
220 220 def unique(g):
221 221 """return the uniq elements of iterable g"""
222 222 seen = {}
223 223 l = []
224 224 for f in g:
225 225 if f not in seen:
226 226 seen[f] = 1
227 227 l.append(f)
228 228 return l
229 229
230 230 class Abort(Exception):
231 231 """Raised if a command needs to print an error and exit."""
232 232
233 233 class UnexpectedOutput(Abort):
234 234 """Raised to print an error with part of output and exit."""
235 235
236 236 def always(fn): return True
237 237 def never(fn): return False
238 238
239 239 def expand_glob(pats):
240 240 '''On Windows, expand the implicit globs in a list of patterns'''
241 241 if os.name != 'nt':
242 242 return list(pats)
243 243 ret = []
244 244 for p in pats:
245 245 kind, name = patkind(p, None)
246 246 if kind is None:
247 247 globbed = glob.glob(name)
248 248 if globbed:
249 249 ret.extend(globbed)
250 250 continue
251 251 # if we couldn't expand the glob, just keep it around
252 252 ret.append(p)
253 253 return ret
254 254
255 255 def patkind(name, dflt_pat='glob'):
256 256 """Split a string into an optional pattern kind prefix and the
257 257 actual pattern."""
258 258 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
259 259 if name.startswith(prefix + ':'): return name.split(':', 1)
260 260 return dflt_pat, name
261 261
262 262 def globre(pat, head='^', tail='$'):
263 263 "convert a glob pattern into a regexp"
264 264 i, n = 0, len(pat)
265 265 res = ''
266 266 group = False
267 267 def peek(): return i < n and pat[i]
268 268 while i < n:
269 269 c = pat[i]
270 270 i = i+1
271 271 if c == '*':
272 272 if peek() == '*':
273 273 i += 1
274 274 res += '.*'
275 275 else:
276 276 res += '[^/]*'
277 277 elif c == '?':
278 278 res += '.'
279 279 elif c == '[':
280 280 j = i
281 281 if j < n and pat[j] in '!]':
282 282 j += 1
283 283 while j < n and pat[j] != ']':
284 284 j += 1
285 285 if j >= n:
286 286 res += '\\['
287 287 else:
288 288 stuff = pat[i:j].replace('\\','\\\\')
289 289 i = j + 1
290 290 if stuff[0] == '!':
291 291 stuff = '^' + stuff[1:]
292 292 elif stuff[0] == '^':
293 293 stuff = '\\' + stuff
294 294 res = '%s[%s]' % (res, stuff)
295 295 elif c == '{':
296 296 group = True
297 297 res += '(?:'
298 298 elif c == '}' and group:
299 299 res += ')'
300 300 group = False
301 301 elif c == ',' and group:
302 302 res += '|'
303 303 elif c == '\\':
304 304 p = peek()
305 305 if p:
306 306 i += 1
307 307 res += re.escape(p)
308 308 else:
309 309 res += re.escape(c)
310 310 else:
311 311 res += re.escape(c)
312 312 return head + res + tail
313 313
314 314 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
315 315
316 316 def pathto(root, n1, n2):
317 317 '''return the relative path from one place to another.
318 318 root should use os.sep to separate directories
319 319 n1 should use os.sep to separate directories
320 320 n2 should use "/" to separate directories
321 321 returns an os.sep-separated path.
322 322
323 323 If n1 is a relative path, it's assumed it's
324 324 relative to root.
325 325 n2 should always be relative to root.
326 326 '''
327 327 if not n1: return localpath(n2)
328 328 if os.path.isabs(n1):
329 329 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
330 330 return os.path.join(root, localpath(n2))
331 331 n2 = '/'.join((pconvert(root), n2))
332 332 a, b = n1.split(os.sep), n2.split('/')
333 333 a.reverse()
334 334 b.reverse()
335 335 while a and b and a[-1] == b[-1]:
336 336 a.pop()
337 337 b.pop()
338 338 b.reverse()
339 339 return os.sep.join((['..'] * len(a)) + b)
340 340
341 341 def canonpath(root, cwd, myname):
342 342 """return the canonical path of myname, given cwd and root"""
343 343 if root == os.sep:
344 344 rootsep = os.sep
345 345 elif root.endswith(os.sep):
346 346 rootsep = root
347 347 else:
348 348 rootsep = root + os.sep
349 349 name = myname
350 350 if not os.path.isabs(name):
351 351 name = os.path.join(root, cwd, name)
352 352 name = os.path.normpath(name)
353 353 if name != rootsep and name.startswith(rootsep):
354 354 name = name[len(rootsep):]
355 355 audit_path(name)
356 356 return pconvert(name)
357 357 elif name == root:
358 358 return ''
359 359 else:
360 360 # Determine whether `name' is in the hierarchy at or beneath `root',
361 361 # by iterating name=dirname(name) until that causes no change (can't
362 362 # check name == '/', because that doesn't work on windows). For each
363 363 # `name', compare dev/inode numbers. If they match, the list `rel'
364 364 # holds the reversed list of components making up the relative file
365 365 # name we want.
366 366 root_st = os.stat(root)
367 367 rel = []
368 368 while True:
369 369 try:
370 370 name_st = os.stat(name)
371 371 except OSError:
372 372 break
373 373 if samestat(name_st, root_st):
374 374 if not rel:
375 375 # name was actually the same as root (maybe a symlink)
376 376 return ''
377 377 rel.reverse()
378 378 name = os.path.join(*rel)
379 379 audit_path(name)
380 380 return pconvert(name)
381 381 dirname, basename = os.path.split(name)
382 382 rel.append(basename)
383 383 if dirname == name:
384 384 break
385 385 name = dirname
386 386
387 387 raise Abort('%s not under root' % myname)
388 388
389 389 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None):
390 390 return _matcher(canonroot, cwd, names, inc, exc, 'glob', src)
391 391
392 392 def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None,
393 393 globbed=False, default=None):
394 394 default = default or 'relpath'
395 395 if default == 'relpath' and not globbed:
396 396 names = expand_glob(names)
397 397 return _matcher(canonroot, cwd, names, inc, exc, default, src)
398 398
399 399 def _matcher(canonroot, cwd, names, inc, exc, dflt_pat, src):
400 400 """build a function to match a set of file patterns
401 401
402 402 arguments:
403 403 canonroot - the canonical root of the tree you're matching against
404 404 cwd - the current working directory, if relevant
405 405 names - patterns to find
406 406 inc - patterns to include
407 407 exc - patterns to exclude
408 408 dflt_pat - if a pattern in names has no explicit type, assume this one
409 409 src - where these patterns came from (e.g. .hgignore)
410 410
411 411 a pattern is one of:
412 412 'glob:<glob>' - a glob relative to cwd
413 413 're:<regexp>' - a regular expression
414 414 'path:<path>' - a path relative to canonroot
415 415 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
416 416 'relpath:<path>' - a path relative to cwd
417 417 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
418 418 '<something>' - one of the cases above, selected by the dflt_pat argument
419 419
420 420 returns:
421 421 a 3-tuple containing
422 422 - list of roots (places where one should start a recursive walk of the fs);
423 423 this often matches the explicit non-pattern names passed in, but also
424 424 includes the initial part of glob: patterns that has no glob characters
425 425 - a bool match(filename) function
426 426 - a bool indicating if any patterns were passed in
427 427 """
428 428
429 429 # a common case: no patterns at all
430 430 if not names and not inc and not exc:
431 431 return [], always, False
432 432
433 433 def contains_glob(name):
434 434 for c in name:
435 435 if c in _globchars: return True
436 436 return False
437 437
438 438 def regex(kind, name, tail):
439 439 '''convert a pattern into a regular expression'''
440 440 if not name:
441 441 return ''
442 442 if kind == 're':
443 443 return name
444 444 elif kind == 'path':
445 445 return '^' + re.escape(name) + '(?:/|$)'
446 446 elif kind == 'relglob':
447 447 return globre(name, '(?:|.*/)', tail)
448 448 elif kind == 'relpath':
449 449 return re.escape(name) + '(?:/|$)'
450 450 elif kind == 'relre':
451 451 if name.startswith('^'):
452 452 return name
453 453 return '.*' + name
454 454 return globre(name, '', tail)
455 455
456 456 def matchfn(pats, tail):
457 457 """build a matching function from a set of patterns"""
458 458 if not pats:
459 459 return
460 460 try:
461 461 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
462 462 return re.compile(pat).match
463 463 except re.error:
464 464 for k, p in pats:
465 465 try:
466 466 re.compile('(?:%s)' % regex(k, p, tail))
467 467 except re.error:
468 468 if src:
469 469 raise Abort("%s: invalid pattern (%s): %s" %
470 470 (src, k, p))
471 471 else:
472 472 raise Abort("invalid pattern (%s): %s" % (k, p))
473 473 raise Abort("invalid pattern")
474 474
475 475 def globprefix(pat):
476 476 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
477 477 root = []
478 478 for p in pat.split('/'):
479 479 if contains_glob(p): break
480 480 root.append(p)
481 481 return '/'.join(root) or '.'
482 482
483 483 def normalizepats(names, default):
484 484 pats = []
485 485 roots = []
486 486 anypats = False
487 487 for kind, name in [patkind(p, default) for p in names]:
488 488 if kind in ('glob', 'relpath'):
489 489 name = canonpath(canonroot, cwd, name)
490 490 elif kind in ('relglob', 'path'):
491 491 name = normpath(name)
492 492
493 493 pats.append((kind, name))
494 494
495 495 if kind in ('glob', 're', 'relglob', 'relre'):
496 496 anypats = True
497 497
498 498 if kind == 'glob':
499 499 root = globprefix(name)
500 500 roots.append(root)
501 501 elif kind in ('relpath', 'path'):
502 502 roots.append(name or '.')
503 503 elif kind == 'relglob':
504 504 roots.append('.')
505 505 return roots, pats, anypats
506 506
507 507 roots, pats, anypats = normalizepats(names, dflt_pat)
508 508
509 509 patmatch = matchfn(pats, '$') or always
510 510 incmatch = always
511 511 if inc:
512 512 dummy, inckinds, dummy = normalizepats(inc, 'glob')
513 513 incmatch = matchfn(inckinds, '(?:/|$)')
514 514 excmatch = lambda fn: False
515 515 if exc:
516 516 dummy, exckinds, dummy = normalizepats(exc, 'glob')
517 517 excmatch = matchfn(exckinds, '(?:/|$)')
518 518
519 519 if not names and inc and not exc:
520 520 # common case: hgignore patterns
521 521 match = incmatch
522 522 else:
523 523 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
524 524
525 525 return (roots, match, (inc or exc or anypats) and True)
526 526
527 527 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
528 528 '''enhanced shell command execution.
529 529 run with environment maybe modified, maybe in different dir.
530 530
531 531 if command fails and onerr is None, return status. if ui object,
532 532 print error message and return status, else raise onerr object as
533 533 exception.'''
534 534 def py2shell(val):
535 535 'convert python object into string that is useful to shell'
536 536 if val in (None, False):
537 537 return '0'
538 538 if val == True:
539 539 return '1'
540 540 return str(val)
541 541 oldenv = {}
542 542 for k in environ:
543 543 oldenv[k] = os.environ.get(k)
544 544 if cwd is not None:
545 545 oldcwd = os.getcwd()
546 546 origcmd = cmd
547 547 if os.name == 'nt':
548 548 cmd = '"%s"' % cmd
549 549 try:
550 550 for k, v in environ.iteritems():
551 551 os.environ[k] = py2shell(v)
552 552 if cwd is not None and oldcwd != cwd:
553 553 os.chdir(cwd)
554 554 rc = os.system(cmd)
555 555 if rc and onerr:
556 556 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
557 557 explain_exit(rc)[0])
558 558 if errprefix:
559 559 errmsg = '%s: %s' % (errprefix, errmsg)
560 560 try:
561 561 onerr.warn(errmsg + '\n')
562 562 except AttributeError:
563 563 raise onerr(errmsg)
564 564 return rc
565 565 finally:
566 566 for k, v in oldenv.iteritems():
567 567 if v is None:
568 568 del os.environ[k]
569 569 else:
570 570 os.environ[k] = v
571 571 if cwd is not None and oldcwd != cwd:
572 572 os.chdir(oldcwd)
573 573
574 574 # os.path.lexists is not available on python2.3
575 575 def lexists(filename):
576 576 "test whether a file with this name exists. does not follow symlinks"
577 577 try:
578 578 os.lstat(filename)
579 579 except:
580 580 return False
581 581 return True
582 582
583 583 def rename(src, dst):
584 584 """forcibly rename a file"""
585 585 try:
586 586 os.rename(src, dst)
587 587 except OSError, err:
588 588 # on windows, rename to existing file is not allowed, so we
589 589 # must delete destination first. but if file is open, unlink
590 590 # schedules it for delete but does not delete it. rename
591 591 # happens immediately even for open files, so we create
592 592 # temporary file, delete it, rename destination to that name,
593 593 # then delete that. then rename is safe to do.
594 594 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
595 595 os.close(fd)
596 596 os.unlink(temp)
597 597 os.rename(dst, temp)
598 598 os.unlink(temp)
599 599 os.rename(src, dst)
600 600
601 601 def unlink(f):
602 602 """unlink and remove the directory if it is empty"""
603 603 os.unlink(f)
604 604 # try removing directories that might now be empty
605 605 try:
606 606 os.removedirs(os.path.dirname(f))
607 607 except OSError:
608 608 pass
609 609
610 610 def copyfile(src, dest):
611 611 "copy a file, preserving mode"
612 612 if os.path.islink(src):
613 613 try:
614 614 os.unlink(dest)
615 615 except:
616 616 pass
617 617 os.symlink(os.readlink(src), dest)
618 618 else:
619 619 try:
620 620 shutil.copyfile(src, dest)
621 621 shutil.copymode(src, dest)
622 622 except shutil.Error, inst:
623 623 raise Abort(str(inst))
624 624
625 625 def copyfiles(src, dst, hardlink=None):
626 626 """Copy a directory tree using hardlinks if possible"""
627 627
628 628 if hardlink is None:
629 629 hardlink = (os.stat(src).st_dev ==
630 630 os.stat(os.path.dirname(dst)).st_dev)
631 631
632 632 if os.path.isdir(src):
633 633 os.mkdir(dst)
634 634 for name in os.listdir(src):
635 635 srcname = os.path.join(src, name)
636 636 dstname = os.path.join(dst, name)
637 637 copyfiles(srcname, dstname, hardlink)
638 638 else:
639 639 if hardlink:
640 640 try:
641 641 os_link(src, dst)
642 642 except (IOError, OSError):
643 643 hardlink = False
644 644 shutil.copy(src, dst)
645 645 else:
646 646 shutil.copy(src, dst)
647 647
648 648 def audit_path(path):
649 649 """Abort if path contains dangerous components"""
650 650 parts = os.path.normcase(path).split(os.sep)
651 651 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
652 652 or os.pardir in parts):
653 653 raise Abort(_("path contains illegal component: %s\n") % path)
654 654
655 655 def _makelock_file(info, pathname):
656 656 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
657 657 os.write(ld, info)
658 658 os.close(ld)
659 659
660 660 def _readlock_file(pathname):
661 661 return posixfile(pathname).read()
662 662
663 663 def nlinks(pathname):
664 664 """Return number of hardlinks for the given file."""
665 665 return os.lstat(pathname).st_nlink
666 666
667 667 if hasattr(os, 'link'):
668 668 os_link = os.link
669 669 else:
670 670 def os_link(src, dst):
671 671 raise OSError(0, _("Hardlinks not supported"))
672 672
673 673 def fstat(fp):
674 674 '''stat file object that may not have fileno method.'''
675 675 try:
676 676 return os.fstat(fp.fileno())
677 677 except AttributeError:
678 678 return os.stat(fp.name)
679 679
680 680 posixfile = file
681 681
682 682 def is_win_9x():
683 683 '''return true if run on windows 95, 98 or me.'''
684 684 try:
685 685 return sys.getwindowsversion()[3] == 1
686 686 except AttributeError:
687 687 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
688 688
689 689 getuser_fallback = None
690 690
691 691 def getuser():
692 692 '''return name of current user'''
693 693 try:
694 694 return getpass.getuser()
695 695 except ImportError:
696 696 # import of pwd will fail on windows - try fallback
697 697 if getuser_fallback:
698 698 return getuser_fallback()
699 699 # raised if win32api not available
700 700 raise Abort(_('user name not available - set USERNAME '
701 701 'environment variable'))
702 702
703 703 def username(uid=None):
704 704 """Return the name of the user with the given uid.
705 705
706 706 If uid is None, return the name of the current user."""
707 707 try:
708 708 import pwd
709 709 if uid is None:
710 710 uid = os.getuid()
711 711 try:
712 712 return pwd.getpwuid(uid)[0]
713 713 except KeyError:
714 714 return str(uid)
715 715 except ImportError:
716 716 return None
717 717
718 718 def groupname(gid=None):
719 719 """Return the name of the group with the given gid.
720 720
721 721 If gid is None, return the name of the current group."""
722 722 try:
723 723 import grp
724 724 if gid is None:
725 725 gid = os.getgid()
726 726 try:
727 727 return grp.getgrgid(gid)[0]
728 728 except KeyError:
729 729 return str(gid)
730 730 except ImportError:
731 731 return None
732 732
733 733 # File system features
734 734
735 735 def checkfolding(path):
736 736 """
737 737 Check whether the given path is on a case-sensitive filesystem
738 738
739 739 Requires a path (like /foo/.hg) ending with a foldable final
740 740 directory component.
741 741 """
742 742 s1 = os.stat(path)
743 743 d, b = os.path.split(path)
744 744 p2 = os.path.join(d, b.upper())
745 745 if path == p2:
746 746 p2 = os.path.join(d, b.lower())
747 747 try:
748 748 s2 = os.stat(p2)
749 749 if s2 == s1:
750 750 return False
751 751 return True
752 752 except:
753 753 return True
754 754
755 755 def checkexec(path):
756 756 """
757 757 Check whether the given path is on a filesystem with UNIX-like exec flags
758 758
759 759 Requires a directory (like /foo/.hg)
760 760 """
761 761 fh, fn = tempfile.mkstemp("", "", path)
762 762 os.close(fh)
763 763 m = os.stat(fn).st_mode
764 764 os.chmod(fn, m ^ 0111)
765 765 r = (os.stat(fn).st_mode != m)
766 766 os.unlink(fn)
767 767 return r
768 768
769 769 def execfunc(path, fallback):
770 770 '''return an is_exec() function with default to fallback'''
771 771 if checkexec(path):
772 772 return lambda x: is_exec(os.path.join(path, x))
773 773 return fallback
774 774
775 775 def checklink(path):
776 776 """check whether the given path is on a symlink-capable filesystem"""
777 777 # mktemp is not racy because symlink creation will fail if the
778 778 # file already exists
779 779 name = tempfile.mktemp(dir=path)
780 780 try:
781 781 os.symlink(".", name)
782 782 os.unlink(name)
783 783 return True
784 784 except (OSError, AttributeError):
785 785 return False
786 786
787 787 def linkfunc(path, fallback):
788 788 '''return an is_link() function with default to fallback'''
789 789 if checklink(path):
790 790 return lambda x: os.path.islink(os.path.join(path, x))
791 791 return fallback
792 792
793 793 _umask = os.umask(0)
794 794 os.umask(_umask)
795 795
796 796 def needbinarypatch():
797 797 """return True if patches should be applied in binary mode by default."""
798 798 return os.name == 'nt'
799 799
800 800 # Platform specific variants
801 801 if os.name == 'nt':
802 802 import msvcrt
803 803 nulldev = 'NUL:'
804 804
805 805 class winstdout:
806 806 '''stdout on windows misbehaves if sent through a pipe'''
807 807
808 808 def __init__(self, fp):
809 809 self.fp = fp
810 810
811 811 def __getattr__(self, key):
812 812 return getattr(self.fp, key)
813 813
814 814 def close(self):
815 815 try:
816 816 self.fp.close()
817 817 except: pass
818 818
819 819 def write(self, s):
820 820 try:
821 821 return self.fp.write(s)
822 822 except IOError, inst:
823 823 if inst.errno != 0: raise
824 824 self.close()
825 825 raise IOError(errno.EPIPE, 'Broken pipe')
826 826
827 827 def flush(self):
828 828 try:
829 829 return self.fp.flush()
830 830 except IOError, inst:
831 831 if inst.errno != errno.EINVAL: raise
832 832 self.close()
833 833 raise IOError(errno.EPIPE, 'Broken pipe')
834 834
835 835 sys.stdout = winstdout(sys.stdout)
836 836
837 837 def system_rcpath():
838 838 try:
839 839 return system_rcpath_win32()
840 840 except:
841 841 return [r'c:\mercurial\mercurial.ini']
842 842
843 843 def user_rcpath():
844 844 '''return os-specific hgrc search path to the user dir'''
845 845 try:
846 846 userrc = user_rcpath_win32()
847 847 except:
848 848 userrc = os.path.join(os.path.expanduser('~'), 'mercurial.ini')
849 849 path = [userrc]
850 850 userprofile = os.environ.get('USERPROFILE')
851 851 if userprofile:
852 852 path.append(os.path.join(userprofile, 'mercurial.ini'))
853 853 return path
854 854
855 855 def parse_patch_output(output_line):
856 856 """parses the output produced by patch and returns the file name"""
857 857 pf = output_line[14:]
858 858 if pf[0] == '`':
859 859 pf = pf[1:-1] # Remove the quotes
860 860 return pf
861 861
862 862 def testpid(pid):
863 863 '''return False if pid dead, True if running or not known'''
864 864 return True
865 865
866 866 def set_exec(f, mode):
867 867 pass
868 868
869 869 def set_link(f, mode):
870 870 pass
871 871
872 872 def set_binary(fd):
873 873 msvcrt.setmode(fd.fileno(), os.O_BINARY)
874 874
875 875 def pconvert(path):
876 876 return path.replace("\\", "/")
877 877
878 878 def localpath(path):
879 879 return path.replace('/', '\\')
880 880
881 881 def normpath(path):
882 882 return pconvert(os.path.normpath(path))
883 883
884 884 makelock = _makelock_file
885 885 readlock = _readlock_file
886 886
887 887 def samestat(s1, s2):
888 888 return False
889 889
890 890 # A sequence of backslashes is special iff it precedes a double quote:
891 891 # - if there's an even number of backslashes, the double quote is not
892 892 # quoted (i.e. it ends the quoted region)
893 893 # - if there's an odd number of backslashes, the double quote is quoted
894 894 # - in both cases, every pair of backslashes is unquoted into a single
895 895 # backslash
896 896 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
897 897 # So, to quote a string, we must surround it in double quotes, double
898 898 # the number of backslashes that preceed double quotes and add another
899 899 # backslash before every double quote (being careful with the double
900 900 # quote we've appended to the end)
901 901 _quotere = None
902 902 def shellquote(s):
903 903 global _quotere
904 904 if _quotere is None:
905 905 _quotere = re.compile(r'(\\*)("|\\$)')
906 906 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
907 907
908 908 def explain_exit(code):
909 909 return _("exited with status %d") % code, code
910 910
911 911 # if you change this stub into a real check, please try to implement the
912 912 # username and groupname functions above, too.
913 913 def isowner(fp, st=None):
914 914 return True
915 915
916 916 def find_in_path(name, path, default=None):
917 917 '''find name in search path. path can be string (will be split
918 918 with os.pathsep), or iterable thing that returns strings. if name
919 919 found, return path to name. else return default. name is looked up
920 920 using cmd.exe rules, using PATHEXT.'''
921 921 if isinstance(path, str):
922 922 path = path.split(os.pathsep)
923 923
924 924 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
925 925 pathext = pathext.lower().split(os.pathsep)
926 926 isexec = os.path.splitext(name)[1].lower() in pathext
927 927
928 928 for p in path:
929 929 p_name = os.path.join(p, name)
930 930
931 931 if isexec and os.path.exists(p_name):
932 932 return p_name
933 933
934 934 for ext in pathext:
935 935 p_name_ext = p_name + ext
936 936 if os.path.exists(p_name_ext):
937 937 return p_name_ext
938 938 return default
939 939
940 940 try:
941 941 # override functions with win32 versions if possible
942 942 from util_win32 import *
943 943 if not is_win_9x():
944 944 posixfile = posixfile_nt
945 945 except ImportError:
946 946 pass
947 947
948 948 else:
949 949 nulldev = '/dev/null'
950 950
951 951 def rcfiles(path):
952 952 rcs = [os.path.join(path, 'hgrc')]
953 953 rcdir = os.path.join(path, 'hgrc.d')
954 954 try:
955 955 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
956 956 if f.endswith(".rc")])
957 957 except OSError:
958 958 pass
959 959 return rcs
960 960
961 961 def system_rcpath():
962 962 path = []
963 963 # old mod_python does not set sys.argv
964 964 if len(getattr(sys, 'argv', [])) > 0:
965 965 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
966 966 '/../etc/mercurial'))
967 967 path.extend(rcfiles('/etc/mercurial'))
968 968 return path
969 969
970 970 def user_rcpath():
971 971 return [os.path.expanduser('~/.hgrc')]
972 972
973 973 def parse_patch_output(output_line):
974 974 """parses the output produced by patch and returns the file name"""
975 975 pf = output_line[14:]
976 976 if pf.startswith("'") and pf.endswith("'") and " " in pf:
977 977 pf = pf[1:-1] # Remove the quotes
978 978 return pf
979 979
980 980 def is_exec(f):
981 981 """check whether a file is executable"""
982 982 return (os.lstat(f).st_mode & 0100 != 0)
983 983
984 984 def set_exec(f, mode):
985 985 s = os.lstat(f).st_mode
986 986 if (s & 0100 != 0) == mode:
987 987 return
988 988 if mode:
989 989 # Turn on +x for every +r bit when making a file executable
990 990 # and obey umask.
991 991 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
992 992 else:
993 993 os.chmod(f, s & 0666)
994 994
995 995 def set_link(f, mode):
996 996 """make a file a symbolic link/regular file
997 997
998 998 if a file is changed to a link, its contents become the link data
999 999 if a link is changed to a file, its link data become its contents
1000 1000 """
1001 1001
1002 1002 m = os.path.islink(f)
1003 1003 if m == bool(mode):
1004 1004 return
1005 1005
1006 1006 if mode: # switch file to link
1007 1007 data = file(f).read()
1008 1008 os.unlink(f)
1009 1009 os.symlink(data, f)
1010 1010 else:
1011 1011 data = os.readlink(f)
1012 1012 os.unlink(f)
1013 1013 file(f, "w").write(data)
1014 1014
1015 1015 def set_binary(fd):
1016 1016 pass
1017 1017
1018 1018 def pconvert(path):
1019 1019 return path
1020 1020
1021 1021 def localpath(path):
1022 1022 return path
1023 1023
1024 1024 normpath = os.path.normpath
1025 1025 samestat = os.path.samestat
1026 1026
1027 1027 def makelock(info, pathname):
1028 1028 try:
1029 1029 os.symlink(info, pathname)
1030 1030 except OSError, why:
1031 1031 if why.errno == errno.EEXIST:
1032 1032 raise
1033 1033 else:
1034 1034 _makelock_file(info, pathname)
1035 1035
1036 1036 def readlock(pathname):
1037 1037 try:
1038 1038 return os.readlink(pathname)
1039 1039 except OSError, why:
1040 1040 if why.errno == errno.EINVAL:
1041 1041 return _readlock_file(pathname)
1042 1042 else:
1043 1043 raise
1044 1044
1045 1045 def shellquote(s):
1046 1046 return "'%s'" % s.replace("'", "'\\''")
1047 1047
1048 1048 def testpid(pid):
1049 1049 '''return False if pid dead, True if running or not sure'''
1050 1050 try:
1051 1051 os.kill(pid, 0)
1052 1052 return True
1053 1053 except OSError, inst:
1054 1054 return inst.errno != errno.ESRCH
1055 1055
1056 1056 def explain_exit(code):
1057 1057 """return a 2-tuple (desc, code) describing a process's status"""
1058 1058 if os.WIFEXITED(code):
1059 1059 val = os.WEXITSTATUS(code)
1060 1060 return _("exited with status %d") % val, val
1061 1061 elif os.WIFSIGNALED(code):
1062 1062 val = os.WTERMSIG(code)
1063 1063 return _("killed by signal %d") % val, val
1064 1064 elif os.WIFSTOPPED(code):
1065 1065 val = os.WSTOPSIG(code)
1066 1066 return _("stopped by signal %d") % val, val
1067 1067 raise ValueError(_("invalid exit code"))
1068 1068
1069 1069 def isowner(fp, st=None):
1070 1070 """Return True if the file object f belongs to the current user.
1071 1071
1072 1072 The return value of a util.fstat(f) may be passed as the st argument.
1073 1073 """
1074 1074 if st is None:
1075 1075 st = fstat(fp)
1076 1076 return st.st_uid == os.getuid()
1077 1077
1078 1078 def find_in_path(name, path, default=None):
1079 1079 '''find name in search path. path can be string (will be split
1080 1080 with os.pathsep), or iterable thing that returns strings. if name
1081 1081 found, return path to name. else return default.'''
1082 1082 if isinstance(path, str):
1083 1083 path = path.split(os.pathsep)
1084 1084 for p in path:
1085 1085 p_name = os.path.join(p, name)
1086 1086 if os.path.exists(p_name):
1087 1087 return p_name
1088 1088 return default
1089 1089
1090 def find_exe(name, default=None):
1091 '''find path of an executable.
1092 if name contains a path component, return it as is. otherwise,
1093 use normal executable search path.'''
1094
1095 if os.sep in name:
1096 # don't check the executable bit. if the file isn't
1097 # executable, whoever tries to actually run it will give a
1098 # much more useful error message.
1099 return name
1100 return find_in_path(name, os.environ.get('PATH', ''), default=default)
1101
1090 1102 def _buildencodefun():
1091 1103 e = '_'
1092 1104 win_reserved = [ord(x) for x in '\\:*?"<>|']
1093 1105 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
1094 1106 for x in (range(32) + range(126, 256) + win_reserved):
1095 1107 cmap[chr(x)] = "~%02x" % x
1096 1108 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
1097 1109 cmap[chr(x)] = e + chr(x).lower()
1098 1110 dmap = {}
1099 1111 for k, v in cmap.iteritems():
1100 1112 dmap[v] = k
1101 1113 def decode(s):
1102 1114 i = 0
1103 1115 while i < len(s):
1104 1116 for l in xrange(1, 4):
1105 1117 try:
1106 1118 yield dmap[s[i:i+l]]
1107 1119 i += l
1108 1120 break
1109 1121 except KeyError:
1110 1122 pass
1111 1123 else:
1112 1124 raise KeyError
1113 1125 return (lambda s: "".join([cmap[c] for c in s]),
1114 1126 lambda s: "".join(list(decode(s))))
1115 1127
1116 1128 encodefilename, decodefilename = _buildencodefun()
1117 1129
1118 1130 def encodedopener(openerfn, fn):
1119 1131 def o(path, *args, **kw):
1120 1132 return openerfn(fn(path), *args, **kw)
1121 1133 return o
1122 1134
1123 1135 def opener(base, audit=True):
1124 1136 """
1125 1137 return a function that opens files relative to base
1126 1138
1127 1139 this function is used to hide the details of COW semantics and
1128 1140 remote file access from higher level code.
1129 1141 """
1130 1142 p = base
1131 1143 audit_p = audit
1132 1144
1133 1145 def mktempcopy(name, emptyok=False):
1134 1146 d, fn = os.path.split(name)
1135 1147 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1136 1148 os.close(fd)
1137 1149 # Temporary files are created with mode 0600, which is usually not
1138 1150 # what we want. If the original file already exists, just copy
1139 1151 # its mode. Otherwise, manually obey umask.
1140 1152 try:
1141 1153 st_mode = os.lstat(name).st_mode
1142 1154 except OSError, inst:
1143 1155 if inst.errno != errno.ENOENT:
1144 1156 raise
1145 1157 st_mode = 0666 & ~_umask
1146 1158 os.chmod(temp, st_mode)
1147 1159 if emptyok:
1148 1160 return temp
1149 1161 try:
1150 1162 try:
1151 1163 ifp = posixfile(name, "rb")
1152 1164 except IOError, inst:
1153 1165 if inst.errno == errno.ENOENT:
1154 1166 return temp
1155 1167 if not getattr(inst, 'filename', None):
1156 1168 inst.filename = name
1157 1169 raise
1158 1170 ofp = posixfile(temp, "wb")
1159 1171 for chunk in filechunkiter(ifp):
1160 1172 ofp.write(chunk)
1161 1173 ifp.close()
1162 1174 ofp.close()
1163 1175 except:
1164 1176 try: os.unlink(temp)
1165 1177 except: pass
1166 1178 raise
1167 1179 return temp
1168 1180
1169 1181 class atomictempfile(posixfile):
1170 1182 """the file will only be copied when rename is called"""
1171 1183 def __init__(self, name, mode):
1172 1184 self.__name = name
1173 1185 self.temp = mktempcopy(name, emptyok=('w' in mode))
1174 1186 posixfile.__init__(self, self.temp, mode)
1175 1187 def rename(self):
1176 1188 if not self.closed:
1177 1189 posixfile.close(self)
1178 1190 rename(self.temp, localpath(self.__name))
1179 1191 def __del__(self):
1180 1192 if not self.closed:
1181 1193 try:
1182 1194 os.unlink(self.temp)
1183 1195 except: pass
1184 1196 posixfile.close(self)
1185 1197
1186 1198 class atomicfile(atomictempfile):
1187 1199 """the file will only be copied on close"""
1188 1200 def __init__(self, name, mode):
1189 1201 self._err = False
1190 1202 atomictempfile.__init__(self, name, mode)
1191 1203 def write(self, s):
1192 1204 try:
1193 1205 atomictempfile.write(self, s)
1194 1206 except:
1195 1207 self._err = True
1196 1208 raise
1197 1209 def close(self):
1198 1210 self.rename()
1199 1211 def __del__(self):
1200 1212 if not self._err:
1201 1213 self.rename()
1202 1214
1203 1215 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
1204 1216 if audit_p:
1205 1217 audit_path(path)
1206 1218 f = os.path.join(p, path)
1207 1219
1208 1220 if not text:
1209 1221 mode += "b" # for that other OS
1210 1222
1211 1223 if mode[0] != "r":
1212 1224 try:
1213 1225 nlink = nlinks(f)
1214 1226 except OSError:
1215 1227 nlink = 0
1216 1228 d = os.path.dirname(f)
1217 1229 if not os.path.isdir(d):
1218 1230 os.makedirs(d)
1219 1231 if atomic:
1220 1232 return atomicfile(f, mode)
1221 1233 elif atomictemp:
1222 1234 return atomictempfile(f, mode)
1223 1235 if nlink > 1:
1224 1236 rename(mktempcopy(f), f)
1225 1237 return posixfile(f, mode)
1226 1238
1227 1239 return o
1228 1240
1229 1241 class chunkbuffer(object):
1230 1242 """Allow arbitrary sized chunks of data to be efficiently read from an
1231 1243 iterator over chunks of arbitrary size."""
1232 1244
1233 1245 def __init__(self, in_iter, targetsize = 2**16):
1234 1246 """in_iter is the iterator that's iterating over the input chunks.
1235 1247 targetsize is how big a buffer to try to maintain."""
1236 1248 self.in_iter = iter(in_iter)
1237 1249 self.buf = ''
1238 1250 self.targetsize = int(targetsize)
1239 1251 if self.targetsize <= 0:
1240 1252 raise ValueError(_("targetsize must be greater than 0, was %d") %
1241 1253 targetsize)
1242 1254 self.iterempty = False
1243 1255
1244 1256 def fillbuf(self):
1245 1257 """Ignore target size; read every chunk from iterator until empty."""
1246 1258 if not self.iterempty:
1247 1259 collector = cStringIO.StringIO()
1248 1260 collector.write(self.buf)
1249 1261 for ch in self.in_iter:
1250 1262 collector.write(ch)
1251 1263 self.buf = collector.getvalue()
1252 1264 self.iterempty = True
1253 1265
1254 1266 def read(self, l):
1255 1267 """Read L bytes of data from the iterator of chunks of data.
1256 1268 Returns less than L bytes if the iterator runs dry."""
1257 1269 if l > len(self.buf) and not self.iterempty:
1258 1270 # Clamp to a multiple of self.targetsize
1259 1271 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1260 1272 collector = cStringIO.StringIO()
1261 1273 collector.write(self.buf)
1262 1274 collected = len(self.buf)
1263 1275 for chunk in self.in_iter:
1264 1276 collector.write(chunk)
1265 1277 collected += len(chunk)
1266 1278 if collected >= targetsize:
1267 1279 break
1268 1280 if collected < targetsize:
1269 1281 self.iterempty = True
1270 1282 self.buf = collector.getvalue()
1271 1283 s, self.buf = self.buf[:l], buffer(self.buf, l)
1272 1284 return s
1273 1285
1274 1286 def filechunkiter(f, size=65536, limit=None):
1275 1287 """Create a generator that produces the data in the file size
1276 1288 (default 65536) bytes at a time, up to optional limit (default is
1277 1289 to read all data). Chunks may be less than size bytes if the
1278 1290 chunk is the last chunk in the file, or the file is a socket or
1279 1291 some other type of file that sometimes reads less data than is
1280 1292 requested."""
1281 1293 assert size >= 0
1282 1294 assert limit is None or limit >= 0
1283 1295 while True:
1284 1296 if limit is None: nbytes = size
1285 1297 else: nbytes = min(limit, size)
1286 1298 s = nbytes and f.read(nbytes)
1287 1299 if not s: break
1288 1300 if limit: limit -= len(s)
1289 1301 yield s
1290 1302
1291 1303 def makedate():
1292 1304 lt = time.localtime()
1293 1305 if lt[8] == 1 and time.daylight:
1294 1306 tz = time.altzone
1295 1307 else:
1296 1308 tz = time.timezone
1297 1309 return time.mktime(lt), tz
1298 1310
1299 1311 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1300 1312 """represent a (unixtime, offset) tuple as a localized time.
1301 1313 unixtime is seconds since the epoch, and offset is the time zone's
1302 1314 number of seconds away from UTC. if timezone is false, do not
1303 1315 append time zone to string."""
1304 1316 t, tz = date or makedate()
1305 1317 s = time.strftime(format, time.gmtime(float(t) - tz))
1306 1318 if timezone:
1307 1319 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1308 1320 return s
1309 1321
1310 1322 def strdate(string, format, defaults):
1311 1323 """parse a localized time string and return a (unixtime, offset) tuple.
1312 1324 if the string cannot be parsed, ValueError is raised."""
1313 1325 def timezone(string):
1314 1326 tz = string.split()[-1]
1315 1327 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1316 1328 tz = int(tz)
1317 1329 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1318 1330 return offset
1319 1331 if tz == "GMT" or tz == "UTC":
1320 1332 return 0
1321 1333 return None
1322 1334
1323 1335 # NOTE: unixtime = localunixtime + offset
1324 1336 offset, date = timezone(string), string
1325 1337 if offset != None:
1326 1338 date = " ".join(string.split()[:-1])
1327 1339
1328 1340 # add missing elements from defaults
1329 1341 for part in defaults:
1330 1342 found = [True for p in part if ("%"+p) in format]
1331 1343 if not found:
1332 1344 date += "@" + defaults[part]
1333 1345 format += "@%" + part[0]
1334 1346
1335 1347 timetuple = time.strptime(date, format)
1336 1348 localunixtime = int(calendar.timegm(timetuple))
1337 1349 if offset is None:
1338 1350 # local timezone
1339 1351 unixtime = int(time.mktime(timetuple))
1340 1352 offset = unixtime - localunixtime
1341 1353 else:
1342 1354 unixtime = localunixtime + offset
1343 1355 return unixtime, offset
1344 1356
1345 1357 def parsedate(string, formats=None, defaults=None):
1346 1358 """parse a localized time string and return a (unixtime, offset) tuple.
1347 1359 The date may be a "unixtime offset" string or in one of the specified
1348 1360 formats."""
1349 1361 if not string:
1350 1362 return 0, 0
1351 1363 if not formats:
1352 1364 formats = defaultdateformats
1353 1365 string = string.strip()
1354 1366 try:
1355 1367 when, offset = map(int, string.split(' '))
1356 1368 except ValueError:
1357 1369 # fill out defaults
1358 1370 if not defaults:
1359 1371 defaults = {}
1360 1372 now = makedate()
1361 1373 for part in "d mb yY HI M S".split():
1362 1374 if part not in defaults:
1363 1375 if part[0] in "HMS":
1364 1376 defaults[part] = "00"
1365 1377 elif part[0] in "dm":
1366 1378 defaults[part] = "1"
1367 1379 else:
1368 1380 defaults[part] = datestr(now, "%" + part[0], False)
1369 1381
1370 1382 for format in formats:
1371 1383 try:
1372 1384 when, offset = strdate(string, format, defaults)
1373 1385 except ValueError:
1374 1386 pass
1375 1387 else:
1376 1388 break
1377 1389 else:
1378 1390 raise Abort(_('invalid date: %r ') % string)
1379 1391 # validate explicit (probably user-specified) date and
1380 1392 # time zone offset. values must fit in signed 32 bits for
1381 1393 # current 32-bit linux runtimes. timezones go from UTC-12
1382 1394 # to UTC+14
1383 1395 if abs(when) > 0x7fffffff:
1384 1396 raise Abort(_('date exceeds 32 bits: %d') % when)
1385 1397 if offset < -50400 or offset > 43200:
1386 1398 raise Abort(_('impossible time zone offset: %d') % offset)
1387 1399 return when, offset
1388 1400
1389 1401 def matchdate(date):
1390 1402 """Return a function that matches a given date match specifier
1391 1403
1392 1404 Formats include:
1393 1405
1394 1406 '{date}' match a given date to the accuracy provided
1395 1407
1396 1408 '<{date}' on or before a given date
1397 1409
1398 1410 '>{date}' on or after a given date
1399 1411
1400 1412 """
1401 1413
1402 1414 def lower(date):
1403 1415 return parsedate(date, extendeddateformats)[0]
1404 1416
1405 1417 def upper(date):
1406 1418 d = dict(mb="12", HI="23", M="59", S="59")
1407 1419 for days in "31 30 29".split():
1408 1420 try:
1409 1421 d["d"] = days
1410 1422 return parsedate(date, extendeddateformats, d)[0]
1411 1423 except:
1412 1424 pass
1413 1425 d["d"] = "28"
1414 1426 return parsedate(date, extendeddateformats, d)[0]
1415 1427
1416 1428 if date[0] == "<":
1417 1429 when = upper(date[1:])
1418 1430 return lambda x: x <= when
1419 1431 elif date[0] == ">":
1420 1432 when = lower(date[1:])
1421 1433 return lambda x: x >= when
1422 1434 elif date[0] == "-":
1423 1435 try:
1424 1436 days = int(date[1:])
1425 1437 except ValueError:
1426 1438 raise Abort(_("invalid day spec: %s") % date[1:])
1427 1439 when = makedate()[0] - days * 3600 * 24
1428 1440 return lambda x: x >= when
1429 1441 elif " to " in date:
1430 1442 a, b = date.split(" to ")
1431 1443 start, stop = lower(a), upper(b)
1432 1444 return lambda x: x >= start and x <= stop
1433 1445 else:
1434 1446 start, stop = lower(date), upper(date)
1435 1447 return lambda x: x >= start and x <= stop
1436 1448
1437 1449 def shortuser(user):
1438 1450 """Return a short representation of a user name or email address."""
1439 1451 f = user.find('@')
1440 1452 if f >= 0:
1441 1453 user = user[:f]
1442 1454 f = user.find('<')
1443 1455 if f >= 0:
1444 1456 user = user[f+1:]
1445 1457 f = user.find(' ')
1446 1458 if f >= 0:
1447 1459 user = user[:f]
1448 1460 f = user.find('.')
1449 1461 if f >= 0:
1450 1462 user = user[:f]
1451 1463 return user
1452 1464
1453 1465 def ellipsis(text, maxlength=400):
1454 1466 """Trim string to at most maxlength (default: 400) characters."""
1455 1467 if len(text) <= maxlength:
1456 1468 return text
1457 1469 else:
1458 1470 return "%s..." % (text[:maxlength-3])
1459 1471
1460 1472 def walkrepos(path):
1461 1473 '''yield every hg repository under path, recursively.'''
1462 1474 def errhandler(err):
1463 1475 if err.filename == path:
1464 1476 raise err
1465 1477
1466 1478 for root, dirs, files in os.walk(path, onerror=errhandler):
1467 1479 for d in dirs:
1468 1480 if d == '.hg':
1469 1481 yield root
1470 1482 dirs[:] = []
1471 1483 break
1472 1484
1473 1485 _rcpath = None
1474 1486
1475 1487 def os_rcpath():
1476 1488 '''return default os-specific hgrc search path'''
1477 1489 path = system_rcpath()
1478 1490 path.extend(user_rcpath())
1479 1491 path = [os.path.normpath(f) for f in path]
1480 1492 return path
1481 1493
1482 1494 def rcpath():
1483 1495 '''return hgrc search path. if env var HGRCPATH is set, use it.
1484 1496 for each item in path, if directory, use files ending in .rc,
1485 1497 else use item.
1486 1498 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1487 1499 if no HGRCPATH, use default os-specific path.'''
1488 1500 global _rcpath
1489 1501 if _rcpath is None:
1490 1502 if 'HGRCPATH' in os.environ:
1491 1503 _rcpath = []
1492 1504 for p in os.environ['HGRCPATH'].split(os.pathsep):
1493 1505 if not p: continue
1494 1506 if os.path.isdir(p):
1495 1507 for f in os.listdir(p):
1496 1508 if f.endswith('.rc'):
1497 1509 _rcpath.append(os.path.join(p, f))
1498 1510 else:
1499 1511 _rcpath.append(p)
1500 1512 else:
1501 1513 _rcpath = os_rcpath()
1502 1514 return _rcpath
1503 1515
1504 1516 def bytecount(nbytes):
1505 1517 '''return byte count formatted as readable string, with units'''
1506 1518
1507 1519 units = (
1508 1520 (100, 1<<30, _('%.0f GB')),
1509 1521 (10, 1<<30, _('%.1f GB')),
1510 1522 (1, 1<<30, _('%.2f GB')),
1511 1523 (100, 1<<20, _('%.0f MB')),
1512 1524 (10, 1<<20, _('%.1f MB')),
1513 1525 (1, 1<<20, _('%.2f MB')),
1514 1526 (100, 1<<10, _('%.0f KB')),
1515 1527 (10, 1<<10, _('%.1f KB')),
1516 1528 (1, 1<<10, _('%.2f KB')),
1517 1529 (1, 1, _('%.0f bytes')),
1518 1530 )
1519 1531
1520 1532 for multiplier, divisor, format in units:
1521 1533 if nbytes >= divisor * multiplier:
1522 1534 return format % (nbytes / float(divisor))
1523 1535 return units[-1][2] % nbytes
1524 1536
1525 1537 def drop_scheme(scheme, path):
1526 1538 sc = scheme + ':'
1527 1539 if path.startswith(sc):
1528 1540 path = path[len(sc):]
1529 1541 if path.startswith('//'):
1530 1542 path = path[2:]
1531 1543 return path
General Comments 0
You need to be logged in to leave comments. Login now