##// END OF EJS Templates
export: only close files which export itself has opened
Waqas Hussain -
r13467:31aa2e5b stable
parent child Browse files
Show More
@@ -1,1386 +1,1387 b''
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import hex, nullid, nullrev, short
9 9 from i18n import _
10 10 import os, sys, errno, re, glob, tempfile
11 11 import util, templater, patch, error, encoding, templatekw
12 12 import match as matchmod
13 13 import similar, revset, subrepo
14 14
15 15 revrangesep = ':'
16 16
17 17 def parsealiases(cmd):
18 18 return cmd.lstrip("^").split("|")
19 19
20 20 def findpossible(cmd, table, strict=False):
21 21 """
22 22 Return cmd -> (aliases, command table entry)
23 23 for each matching command.
24 24 Return debug commands (or their aliases) only if no normal command matches.
25 25 """
26 26 choice = {}
27 27 debugchoice = {}
28 28 for e in table.keys():
29 29 aliases = parsealiases(e)
30 30 found = None
31 31 if cmd in aliases:
32 32 found = cmd
33 33 elif not strict:
34 34 for a in aliases:
35 35 if a.startswith(cmd):
36 36 found = a
37 37 break
38 38 if found is not None:
39 39 if aliases[0].startswith("debug") or found.startswith("debug"):
40 40 debugchoice[found] = (aliases, table[e])
41 41 else:
42 42 choice[found] = (aliases, table[e])
43 43
44 44 if not choice and debugchoice:
45 45 choice = debugchoice
46 46
47 47 return choice
48 48
49 49 def findcmd(cmd, table, strict=True):
50 50 """Return (aliases, command table entry) for command string."""
51 51 choice = findpossible(cmd, table, strict)
52 52
53 53 if cmd in choice:
54 54 return choice[cmd]
55 55
56 56 if len(choice) > 1:
57 57 clist = choice.keys()
58 58 clist.sort()
59 59 raise error.AmbiguousCommand(cmd, clist)
60 60
61 61 if choice:
62 62 return choice.values()[0]
63 63
64 64 raise error.UnknownCommand(cmd)
65 65
66 66 def findrepo(p):
67 67 while not os.path.isdir(os.path.join(p, ".hg")):
68 68 oldp, p = p, os.path.dirname(p)
69 69 if p == oldp:
70 70 return None
71 71
72 72 return p
73 73
74 74 def bail_if_changed(repo):
75 75 if repo.dirstate.parents()[1] != nullid:
76 76 raise util.Abort(_('outstanding uncommitted merge'))
77 77 modified, added, removed, deleted = repo.status()[:4]
78 78 if modified or added or removed or deleted:
79 79 raise util.Abort(_("outstanding uncommitted changes"))
80 80
81 81 def logmessage(opts):
82 82 """ get the log message according to -m and -l option """
83 83 message = opts.get('message')
84 84 logfile = opts.get('logfile')
85 85
86 86 if message and logfile:
87 87 raise util.Abort(_('options --message and --logfile are mutually '
88 88 'exclusive'))
89 89 if not message and logfile:
90 90 try:
91 91 if logfile == '-':
92 92 message = sys.stdin.read()
93 93 else:
94 94 message = open(logfile).read()
95 95 except IOError, inst:
96 96 raise util.Abort(_("can't read commit message '%s': %s") %
97 97 (logfile, inst.strerror))
98 98 return message
99 99
100 100 def loglimit(opts):
101 101 """get the log limit according to option -l/--limit"""
102 102 limit = opts.get('limit')
103 103 if limit:
104 104 try:
105 105 limit = int(limit)
106 106 except ValueError:
107 107 raise util.Abort(_('limit must be a positive integer'))
108 108 if limit <= 0:
109 109 raise util.Abort(_('limit must be positive'))
110 110 else:
111 111 limit = None
112 112 return limit
113 113
114 114 def revsingle(repo, revspec, default='.'):
115 115 if not revspec:
116 116 return repo[default]
117 117
118 118 l = revrange(repo, [revspec])
119 119 if len(l) < 1:
120 120 raise util.Abort(_('empty revision set'))
121 121 return repo[l[-1]]
122 122
123 123 def revpair(repo, revs):
124 124 if not revs:
125 125 return repo.dirstate.parents()[0], None
126 126
127 127 l = revrange(repo, revs)
128 128
129 129 if len(l) == 0:
130 130 return repo.dirstate.parents()[0], None
131 131
132 132 if len(l) == 1:
133 133 return repo.lookup(l[0]), None
134 134
135 135 return repo.lookup(l[0]), repo.lookup(l[-1])
136 136
137 137 def revrange(repo, revs):
138 138 """Yield revision as strings from a list of revision specifications."""
139 139
140 140 def revfix(repo, val, defval):
141 141 if not val and val != 0 and defval is not None:
142 142 return defval
143 143 return repo.changelog.rev(repo.lookup(val))
144 144
145 145 seen, l = set(), []
146 146 for spec in revs:
147 147 # attempt to parse old-style ranges first to deal with
148 148 # things like old-tag which contain query metacharacters
149 149 try:
150 150 if isinstance(spec, int):
151 151 seen.add(spec)
152 152 l.append(spec)
153 153 continue
154 154
155 155 if revrangesep in spec:
156 156 start, end = spec.split(revrangesep, 1)
157 157 start = revfix(repo, start, 0)
158 158 end = revfix(repo, end, len(repo) - 1)
159 159 step = start > end and -1 or 1
160 160 for rev in xrange(start, end + step, step):
161 161 if rev in seen:
162 162 continue
163 163 seen.add(rev)
164 164 l.append(rev)
165 165 continue
166 166 elif spec and spec in repo: # single unquoted rev
167 167 rev = revfix(repo, spec, None)
168 168 if rev in seen:
169 169 continue
170 170 seen.add(rev)
171 171 l.append(rev)
172 172 continue
173 173 except error.RepoLookupError:
174 174 pass
175 175
176 176 # fall through to new-style queries if old-style fails
177 177 m = revset.match(spec)
178 178 for r in m(repo, range(len(repo))):
179 179 if r not in seen:
180 180 l.append(r)
181 181 seen.update(l)
182 182
183 183 return l
184 184
185 185 def make_filename(repo, pat, node,
186 186 total=None, seqno=None, revwidth=None, pathname=None):
187 187 node_expander = {
188 188 'H': lambda: hex(node),
189 189 'R': lambda: str(repo.changelog.rev(node)),
190 190 'h': lambda: short(node),
191 191 }
192 192 expander = {
193 193 '%': lambda: '%',
194 194 'b': lambda: os.path.basename(repo.root),
195 195 }
196 196
197 197 try:
198 198 if node:
199 199 expander.update(node_expander)
200 200 if node:
201 201 expander['r'] = (lambda:
202 202 str(repo.changelog.rev(node)).zfill(revwidth or 0))
203 203 if total is not None:
204 204 expander['N'] = lambda: str(total)
205 205 if seqno is not None:
206 206 expander['n'] = lambda: str(seqno)
207 207 if total is not None and seqno is not None:
208 208 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
209 209 if pathname is not None:
210 210 expander['s'] = lambda: os.path.basename(pathname)
211 211 expander['d'] = lambda: os.path.dirname(pathname) or '.'
212 212 expander['p'] = lambda: pathname
213 213
214 214 newname = []
215 215 patlen = len(pat)
216 216 i = 0
217 217 while i < patlen:
218 218 c = pat[i]
219 219 if c == '%':
220 220 i += 1
221 221 c = pat[i]
222 222 c = expander[c]()
223 223 newname.append(c)
224 224 i += 1
225 225 return ''.join(newname)
226 226 except KeyError, inst:
227 227 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
228 228 inst.args[0])
229 229
230 230 def make_file(repo, pat, node=None,
231 231 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
232 232
233 233 writable = 'w' in mode or 'a' in mode
234 234
235 235 if not pat or pat == '-':
236 236 fp = writable and sys.stdout or sys.stdin
237 237 return os.fdopen(os.dup(fp.fileno()), mode)
238 238 if hasattr(pat, 'write') and writable:
239 239 return pat
240 240 if hasattr(pat, 'read') and 'r' in mode:
241 241 return pat
242 242 return open(make_filename(repo, pat, node, total, seqno, revwidth,
243 243 pathname),
244 244 mode)
245 245
246 246 def expandpats(pats):
247 247 if not util.expandglobs:
248 248 return list(pats)
249 249 ret = []
250 250 for p in pats:
251 251 kind, name = matchmod._patsplit(p, None)
252 252 if kind is None:
253 253 try:
254 254 globbed = glob.glob(name)
255 255 except re.error:
256 256 globbed = [name]
257 257 if globbed:
258 258 ret.extend(globbed)
259 259 continue
260 260 ret.append(p)
261 261 return ret
262 262
263 263 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
264 264 if not globbed and default == 'relpath':
265 265 pats = expandpats(pats or [])
266 266 m = matchmod.match(repo.root, repo.getcwd(), pats,
267 267 opts.get('include'), opts.get('exclude'), default,
268 268 auditor=repo.auditor)
269 269 def badfn(f, msg):
270 270 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
271 271 m.bad = badfn
272 272 return m
273 273
274 274 def matchall(repo):
275 275 return matchmod.always(repo.root, repo.getcwd())
276 276
277 277 def matchfiles(repo, files):
278 278 return matchmod.exact(repo.root, repo.getcwd(), files)
279 279
280 280 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
281 281 if dry_run is None:
282 282 dry_run = opts.get('dry_run')
283 283 if similarity is None:
284 284 similarity = float(opts.get('similarity') or 0)
285 285 # we'd use status here, except handling of symlinks and ignore is tricky
286 286 added, unknown, deleted, removed = [], [], [], []
287 287 audit_path = util.path_auditor(repo.root)
288 288 m = match(repo, pats, opts)
289 289 for abs in repo.walk(m):
290 290 target = repo.wjoin(abs)
291 291 good = True
292 292 try:
293 293 audit_path(abs)
294 294 except:
295 295 good = False
296 296 rel = m.rel(abs)
297 297 exact = m.exact(abs)
298 298 if good and abs not in repo.dirstate:
299 299 unknown.append(abs)
300 300 if repo.ui.verbose or not exact:
301 301 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
302 302 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
303 303 or (os.path.isdir(target) and not os.path.islink(target))):
304 304 deleted.append(abs)
305 305 if repo.ui.verbose or not exact:
306 306 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
307 307 # for finding renames
308 308 elif repo.dirstate[abs] == 'r':
309 309 removed.append(abs)
310 310 elif repo.dirstate[abs] == 'a':
311 311 added.append(abs)
312 312 copies = {}
313 313 if similarity > 0:
314 314 for old, new, score in similar.findrenames(repo,
315 315 added + unknown, removed + deleted, similarity):
316 316 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
317 317 repo.ui.status(_('recording removal of %s as rename to %s '
318 318 '(%d%% similar)\n') %
319 319 (m.rel(old), m.rel(new), score * 100))
320 320 copies[new] = old
321 321
322 322 if not dry_run:
323 323 wctx = repo[None]
324 324 wlock = repo.wlock()
325 325 try:
326 326 wctx.remove(deleted)
327 327 wctx.add(unknown)
328 328 for new, old in copies.iteritems():
329 329 wctx.copy(old, new)
330 330 finally:
331 331 wlock.release()
332 332
333 333 def updatedir(ui, repo, patches, similarity=0):
334 334 '''Update dirstate after patch application according to metadata'''
335 335 if not patches:
336 336 return
337 337 copies = []
338 338 removes = set()
339 339 cfiles = patches.keys()
340 340 cwd = repo.getcwd()
341 341 if cwd:
342 342 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
343 343 for f in patches:
344 344 gp = patches[f]
345 345 if not gp:
346 346 continue
347 347 if gp.op == 'RENAME':
348 348 copies.append((gp.oldpath, gp.path))
349 349 removes.add(gp.oldpath)
350 350 elif gp.op == 'COPY':
351 351 copies.append((gp.oldpath, gp.path))
352 352 elif gp.op == 'DELETE':
353 353 removes.add(gp.path)
354 354
355 355 wctx = repo[None]
356 356 for src, dst in copies:
357 357 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
358 358 if (not similarity) and removes:
359 359 wctx.remove(sorted(removes), True)
360 360
361 361 for f in patches:
362 362 gp = patches[f]
363 363 if gp and gp.mode:
364 364 islink, isexec = gp.mode
365 365 dst = repo.wjoin(gp.path)
366 366 # patch won't create empty files
367 367 if gp.op == 'ADD' and not os.path.lexists(dst):
368 368 flags = (isexec and 'x' or '') + (islink and 'l' or '')
369 369 repo.wwrite(gp.path, '', flags)
370 370 util.set_flags(dst, islink, isexec)
371 371 addremove(repo, cfiles, similarity=similarity)
372 372 files = patches.keys()
373 373 files.extend([r for r in removes if r not in files])
374 374 return sorted(files)
375 375
376 376 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
377 377 """Update the dirstate to reflect the intent of copying src to dst. For
378 378 different reasons it might not end with dst being marked as copied from src.
379 379 """
380 380 origsrc = repo.dirstate.copied(src) or src
381 381 if dst == origsrc: # copying back a copy?
382 382 if repo.dirstate[dst] not in 'mn' and not dryrun:
383 383 repo.dirstate.normallookup(dst)
384 384 else:
385 385 if repo.dirstate[origsrc] == 'a' and origsrc == src:
386 386 if not ui.quiet:
387 387 ui.warn(_("%s has not been committed yet, so no copy "
388 388 "data will be stored for %s.\n")
389 389 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
390 390 if repo.dirstate[dst] in '?r' and not dryrun:
391 391 wctx.add([dst])
392 392 elif not dryrun:
393 393 wctx.copy(origsrc, dst)
394 394
395 395 def copy(ui, repo, pats, opts, rename=False):
396 396 # called with the repo lock held
397 397 #
398 398 # hgsep => pathname that uses "/" to separate directories
399 399 # ossep => pathname that uses os.sep to separate directories
400 400 cwd = repo.getcwd()
401 401 targets = {}
402 402 after = opts.get("after")
403 403 dryrun = opts.get("dry_run")
404 404 wctx = repo[None]
405 405
406 406 def walkpat(pat):
407 407 srcs = []
408 408 badstates = after and '?' or '?r'
409 409 m = match(repo, [pat], opts, globbed=True)
410 410 for abs in repo.walk(m):
411 411 state = repo.dirstate[abs]
412 412 rel = m.rel(abs)
413 413 exact = m.exact(abs)
414 414 if state in badstates:
415 415 if exact and state == '?':
416 416 ui.warn(_('%s: not copying - file is not managed\n') % rel)
417 417 if exact and state == 'r':
418 418 ui.warn(_('%s: not copying - file has been marked for'
419 419 ' remove\n') % rel)
420 420 continue
421 421 # abs: hgsep
422 422 # rel: ossep
423 423 srcs.append((abs, rel, exact))
424 424 return srcs
425 425
426 426 # abssrc: hgsep
427 427 # relsrc: ossep
428 428 # otarget: ossep
429 429 def copyfile(abssrc, relsrc, otarget, exact):
430 430 abstarget = util.canonpath(repo.root, cwd, otarget)
431 431 reltarget = repo.pathto(abstarget, cwd)
432 432 target = repo.wjoin(abstarget)
433 433 src = repo.wjoin(abssrc)
434 434 state = repo.dirstate[abstarget]
435 435
436 436 # check for collisions
437 437 prevsrc = targets.get(abstarget)
438 438 if prevsrc is not None:
439 439 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
440 440 (reltarget, repo.pathto(abssrc, cwd),
441 441 repo.pathto(prevsrc, cwd)))
442 442 return
443 443
444 444 # check for overwrites
445 445 exists = os.path.lexists(target)
446 446 if not after and exists or after and state in 'mn':
447 447 if not opts['force']:
448 448 ui.warn(_('%s: not overwriting - file exists\n') %
449 449 reltarget)
450 450 return
451 451
452 452 if after:
453 453 if not exists:
454 454 if rename:
455 455 ui.warn(_('%s: not recording move - %s does not exist\n') %
456 456 (relsrc, reltarget))
457 457 else:
458 458 ui.warn(_('%s: not recording copy - %s does not exist\n') %
459 459 (relsrc, reltarget))
460 460 return
461 461 elif not dryrun:
462 462 try:
463 463 if exists:
464 464 os.unlink(target)
465 465 targetdir = os.path.dirname(target) or '.'
466 466 if not os.path.isdir(targetdir):
467 467 os.makedirs(targetdir)
468 468 util.copyfile(src, target)
469 469 except IOError, inst:
470 470 if inst.errno == errno.ENOENT:
471 471 ui.warn(_('%s: deleted in working copy\n') % relsrc)
472 472 else:
473 473 ui.warn(_('%s: cannot copy - %s\n') %
474 474 (relsrc, inst.strerror))
475 475 return True # report a failure
476 476
477 477 if ui.verbose or not exact:
478 478 if rename:
479 479 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
480 480 else:
481 481 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
482 482
483 483 targets[abstarget] = abssrc
484 484
485 485 # fix up dirstate
486 486 dirstatecopy(ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd)
487 487 if rename and not dryrun:
488 488 wctx.remove([abssrc], not after)
489 489
490 490 # pat: ossep
491 491 # dest ossep
492 492 # srcs: list of (hgsep, hgsep, ossep, bool)
493 493 # return: function that takes hgsep and returns ossep
494 494 def targetpathfn(pat, dest, srcs):
495 495 if os.path.isdir(pat):
496 496 abspfx = util.canonpath(repo.root, cwd, pat)
497 497 abspfx = util.localpath(abspfx)
498 498 if destdirexists:
499 499 striplen = len(os.path.split(abspfx)[0])
500 500 else:
501 501 striplen = len(abspfx)
502 502 if striplen:
503 503 striplen += len(os.sep)
504 504 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
505 505 elif destdirexists:
506 506 res = lambda p: os.path.join(dest,
507 507 os.path.basename(util.localpath(p)))
508 508 else:
509 509 res = lambda p: dest
510 510 return res
511 511
512 512 # pat: ossep
513 513 # dest ossep
514 514 # srcs: list of (hgsep, hgsep, ossep, bool)
515 515 # return: function that takes hgsep and returns ossep
516 516 def targetpathafterfn(pat, dest, srcs):
517 517 if matchmod.patkind(pat):
518 518 # a mercurial pattern
519 519 res = lambda p: os.path.join(dest,
520 520 os.path.basename(util.localpath(p)))
521 521 else:
522 522 abspfx = util.canonpath(repo.root, cwd, pat)
523 523 if len(abspfx) < len(srcs[0][0]):
524 524 # A directory. Either the target path contains the last
525 525 # component of the source path or it does not.
526 526 def evalpath(striplen):
527 527 score = 0
528 528 for s in srcs:
529 529 t = os.path.join(dest, util.localpath(s[0])[striplen:])
530 530 if os.path.lexists(t):
531 531 score += 1
532 532 return score
533 533
534 534 abspfx = util.localpath(abspfx)
535 535 striplen = len(abspfx)
536 536 if striplen:
537 537 striplen += len(os.sep)
538 538 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
539 539 score = evalpath(striplen)
540 540 striplen1 = len(os.path.split(abspfx)[0])
541 541 if striplen1:
542 542 striplen1 += len(os.sep)
543 543 if evalpath(striplen1) > score:
544 544 striplen = striplen1
545 545 res = lambda p: os.path.join(dest,
546 546 util.localpath(p)[striplen:])
547 547 else:
548 548 # a file
549 549 if destdirexists:
550 550 res = lambda p: os.path.join(dest,
551 551 os.path.basename(util.localpath(p)))
552 552 else:
553 553 res = lambda p: dest
554 554 return res
555 555
556 556
557 557 pats = expandpats(pats)
558 558 if not pats:
559 559 raise util.Abort(_('no source or destination specified'))
560 560 if len(pats) == 1:
561 561 raise util.Abort(_('no destination specified'))
562 562 dest = pats.pop()
563 563 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
564 564 if not destdirexists:
565 565 if len(pats) > 1 or matchmod.patkind(pats[0]):
566 566 raise util.Abort(_('with multiple sources, destination must be an '
567 567 'existing directory'))
568 568 if util.endswithsep(dest):
569 569 raise util.Abort(_('destination %s is not a directory') % dest)
570 570
571 571 tfn = targetpathfn
572 572 if after:
573 573 tfn = targetpathafterfn
574 574 copylist = []
575 575 for pat in pats:
576 576 srcs = walkpat(pat)
577 577 if not srcs:
578 578 continue
579 579 copylist.append((tfn(pat, dest, srcs), srcs))
580 580 if not copylist:
581 581 raise util.Abort(_('no files to copy'))
582 582
583 583 errors = 0
584 584 for targetpath, srcs in copylist:
585 585 for abssrc, relsrc, exact in srcs:
586 586 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
587 587 errors += 1
588 588
589 589 if errors:
590 590 ui.warn(_('(consider using --after)\n'))
591 591
592 592 return errors != 0
593 593
594 594 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
595 595 runargs=None, appendpid=False):
596 596 '''Run a command as a service.'''
597 597
598 598 if opts['daemon'] and not opts['daemon_pipefds']:
599 599 # Signal child process startup with file removal
600 600 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
601 601 os.close(lockfd)
602 602 try:
603 603 if not runargs:
604 604 runargs = util.hgcmd() + sys.argv[1:]
605 605 runargs.append('--daemon-pipefds=%s' % lockpath)
606 606 # Don't pass --cwd to the child process, because we've already
607 607 # changed directory.
608 608 for i in xrange(1, len(runargs)):
609 609 if runargs[i].startswith('--cwd='):
610 610 del runargs[i]
611 611 break
612 612 elif runargs[i].startswith('--cwd'):
613 613 del runargs[i:i + 2]
614 614 break
615 615 def condfn():
616 616 return not os.path.exists(lockpath)
617 617 pid = util.rundetached(runargs, condfn)
618 618 if pid < 0:
619 619 raise util.Abort(_('child process failed to start'))
620 620 finally:
621 621 try:
622 622 os.unlink(lockpath)
623 623 except OSError, e:
624 624 if e.errno != errno.ENOENT:
625 625 raise
626 626 if parentfn:
627 627 return parentfn(pid)
628 628 else:
629 629 return
630 630
631 631 if initfn:
632 632 initfn()
633 633
634 634 if opts['pid_file']:
635 635 mode = appendpid and 'a' or 'w'
636 636 fp = open(opts['pid_file'], mode)
637 637 fp.write(str(os.getpid()) + '\n')
638 638 fp.close()
639 639
640 640 if opts['daemon_pipefds']:
641 641 lockpath = opts['daemon_pipefds']
642 642 try:
643 643 os.setsid()
644 644 except AttributeError:
645 645 pass
646 646 os.unlink(lockpath)
647 647 util.hidewindow()
648 648 sys.stdout.flush()
649 649 sys.stderr.flush()
650 650
651 651 nullfd = os.open(util.nulldev, os.O_RDWR)
652 652 logfilefd = nullfd
653 653 if logfile:
654 654 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
655 655 os.dup2(nullfd, 0)
656 656 os.dup2(logfilefd, 1)
657 657 os.dup2(logfilefd, 2)
658 658 if nullfd not in (0, 1, 2):
659 659 os.close(nullfd)
660 660 if logfile and logfilefd not in (0, 1, 2):
661 661 os.close(logfilefd)
662 662
663 663 if runfn:
664 664 return runfn()
665 665
666 666 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
667 667 opts=None):
668 668 '''export changesets as hg patches.'''
669 669
670 670 total = len(revs)
671 671 revwidth = max([len(str(rev)) for rev in revs])
672 672
673 673 def single(rev, seqno, fp):
674 674 ctx = repo[rev]
675 675 node = ctx.node()
676 676 parents = [p.node() for p in ctx.parents() if p]
677 677 branch = ctx.branch()
678 678 if switch_parent:
679 679 parents.reverse()
680 680 prev = (parents and parents[0]) or nullid
681 681
682 682 shouldclose = False
683 683 if not fp:
684 shouldclose = True
685 684 fp = make_file(repo, template, node, total=total, seqno=seqno,
686 685 revwidth=revwidth, mode='ab')
686 if fp != template:
687 shouldclose = True
687 688 if fp != sys.stdout and hasattr(fp, 'name'):
688 689 repo.ui.note("%s\n" % fp.name)
689 690
690 691 fp.write("# HG changeset patch\n")
691 692 fp.write("# User %s\n" % ctx.user())
692 693 fp.write("# Date %d %d\n" % ctx.date())
693 694 if branch and branch != 'default':
694 695 fp.write("# Branch %s\n" % branch)
695 696 fp.write("# Node ID %s\n" % hex(node))
696 697 fp.write("# Parent %s\n" % hex(prev))
697 698 if len(parents) > 1:
698 699 fp.write("# Parent %s\n" % hex(parents[1]))
699 700 fp.write(ctx.description().rstrip())
700 701 fp.write("\n\n")
701 702
702 703 for chunk in patch.diff(repo, prev, node, opts=opts):
703 704 fp.write(chunk)
704 705
705 706 if shouldclose:
706 707 fp.close()
707 708
708 709 for seqno, rev in enumerate(revs):
709 710 single(rev, seqno + 1, fp)
710 711
711 712 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
712 713 changes=None, stat=False, fp=None, prefix='',
713 714 listsubrepos=False):
714 715 '''show diff or diffstat.'''
715 716 if fp is None:
716 717 write = ui.write
717 718 else:
718 719 def write(s, **kw):
719 720 fp.write(s)
720 721
721 722 if stat:
722 723 diffopts = diffopts.copy(context=0)
723 724 width = 80
724 725 if not ui.plain():
725 726 width = ui.termwidth()
726 727 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
727 728 prefix=prefix)
728 729 for chunk, label in patch.diffstatui(util.iterlines(chunks),
729 730 width=width,
730 731 git=diffopts.git):
731 732 write(chunk, label=label)
732 733 else:
733 734 for chunk, label in patch.diffui(repo, node1, node2, match,
734 735 changes, diffopts, prefix=prefix):
735 736 write(chunk, label=label)
736 737
737 738 if listsubrepos:
738 739 ctx1 = repo[node1]
739 740 ctx2 = repo[node2]
740 741 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
741 742 if node2 is not None:
742 743 node2 = ctx2.substate[subpath][1]
743 744 submatch = matchmod.narrowmatcher(subpath, match)
744 745 sub.diff(diffopts, node2, submatch, changes=changes,
745 746 stat=stat, fp=fp, prefix=prefix)
746 747
747 748 class changeset_printer(object):
748 749 '''show changeset information when templating not requested.'''
749 750
750 751 def __init__(self, ui, repo, patch, diffopts, buffered):
751 752 self.ui = ui
752 753 self.repo = repo
753 754 self.buffered = buffered
754 755 self.patch = patch
755 756 self.diffopts = diffopts
756 757 self.header = {}
757 758 self.hunk = {}
758 759 self.lastheader = None
759 760 self.footer = None
760 761
761 762 def flush(self, rev):
762 763 if rev in self.header:
763 764 h = self.header[rev]
764 765 if h != self.lastheader:
765 766 self.lastheader = h
766 767 self.ui.write(h)
767 768 del self.header[rev]
768 769 if rev in self.hunk:
769 770 self.ui.write(self.hunk[rev])
770 771 del self.hunk[rev]
771 772 return 1
772 773 return 0
773 774
774 775 def close(self):
775 776 if self.footer:
776 777 self.ui.write(self.footer)
777 778
778 779 def show(self, ctx, copies=None, matchfn=None, **props):
779 780 if self.buffered:
780 781 self.ui.pushbuffer()
781 782 self._show(ctx, copies, matchfn, props)
782 783 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
783 784 else:
784 785 self._show(ctx, copies, matchfn, props)
785 786
786 787 def _show(self, ctx, copies, matchfn, props):
787 788 '''show a single changeset or file revision'''
788 789 changenode = ctx.node()
789 790 rev = ctx.rev()
790 791
791 792 if self.ui.quiet:
792 793 self.ui.write("%d:%s\n" % (rev, short(changenode)),
793 794 label='log.node')
794 795 return
795 796
796 797 log = self.repo.changelog
797 798 date = util.datestr(ctx.date())
798 799
799 800 hexfunc = self.ui.debugflag and hex or short
800 801
801 802 parents = [(p, hexfunc(log.node(p)))
802 803 for p in self._meaningful_parentrevs(log, rev)]
803 804
804 805 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
805 806 label='log.changeset')
806 807
807 808 branch = ctx.branch()
808 809 # don't show the default branch name
809 810 if branch != 'default':
810 811 self.ui.write(_("branch: %s\n") % branch,
811 812 label='log.branch')
812 813 for bookmark in self.repo.nodebookmarks(changenode):
813 814 self.ui.write(_("bookmark: %s\n") % bookmark,
814 815 label='log.bookmark')
815 816 for tag in self.repo.nodetags(changenode):
816 817 self.ui.write(_("tag: %s\n") % tag,
817 818 label='log.tag')
818 819 for parent in parents:
819 820 self.ui.write(_("parent: %d:%s\n") % parent,
820 821 label='log.parent')
821 822
822 823 if self.ui.debugflag:
823 824 mnode = ctx.manifestnode()
824 825 self.ui.write(_("manifest: %d:%s\n") %
825 826 (self.repo.manifest.rev(mnode), hex(mnode)),
826 827 label='ui.debug log.manifest')
827 828 self.ui.write(_("user: %s\n") % ctx.user(),
828 829 label='log.user')
829 830 self.ui.write(_("date: %s\n") % date,
830 831 label='log.date')
831 832
832 833 if self.ui.debugflag:
833 834 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
834 835 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
835 836 files):
836 837 if value:
837 838 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
838 839 label='ui.debug log.files')
839 840 elif ctx.files() and self.ui.verbose:
840 841 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
841 842 label='ui.note log.files')
842 843 if copies and self.ui.verbose:
843 844 copies = ['%s (%s)' % c for c in copies]
844 845 self.ui.write(_("copies: %s\n") % ' '.join(copies),
845 846 label='ui.note log.copies')
846 847
847 848 extra = ctx.extra()
848 849 if extra and self.ui.debugflag:
849 850 for key, value in sorted(extra.items()):
850 851 self.ui.write(_("extra: %s=%s\n")
851 852 % (key, value.encode('string_escape')),
852 853 label='ui.debug log.extra')
853 854
854 855 description = ctx.description().strip()
855 856 if description:
856 857 if self.ui.verbose:
857 858 self.ui.write(_("description:\n"),
858 859 label='ui.note log.description')
859 860 self.ui.write(description,
860 861 label='ui.note log.description')
861 862 self.ui.write("\n\n")
862 863 else:
863 864 self.ui.write(_("summary: %s\n") %
864 865 description.splitlines()[0],
865 866 label='log.summary')
866 867 self.ui.write("\n")
867 868
868 869 self.showpatch(changenode, matchfn)
869 870
870 871 def showpatch(self, node, matchfn):
871 872 if not matchfn:
872 873 matchfn = self.patch
873 874 if matchfn:
874 875 stat = self.diffopts.get('stat')
875 876 diff = self.diffopts.get('patch')
876 877 diffopts = patch.diffopts(self.ui, self.diffopts)
877 878 prev = self.repo.changelog.parents(node)[0]
878 879 if stat:
879 880 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
880 881 match=matchfn, stat=True)
881 882 if diff:
882 883 if stat:
883 884 self.ui.write("\n")
884 885 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
885 886 match=matchfn, stat=False)
886 887 self.ui.write("\n")
887 888
888 889 def _meaningful_parentrevs(self, log, rev):
889 890 """Return list of meaningful (or all if debug) parentrevs for rev.
890 891
891 892 For merges (two non-nullrev revisions) both parents are meaningful.
892 893 Otherwise the first parent revision is considered meaningful if it
893 894 is not the preceding revision.
894 895 """
895 896 parents = log.parentrevs(rev)
896 897 if not self.ui.debugflag and parents[1] == nullrev:
897 898 if parents[0] >= rev - 1:
898 899 parents = []
899 900 else:
900 901 parents = [parents[0]]
901 902 return parents
902 903
903 904
904 905 class changeset_templater(changeset_printer):
905 906 '''format changeset information.'''
906 907
907 908 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
908 909 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
909 910 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
910 911 defaulttempl = {
911 912 'parent': '{rev}:{node|formatnode} ',
912 913 'manifest': '{rev}:{node|formatnode}',
913 914 'file_copy': '{name} ({source})',
914 915 'extra': '{key}={value|stringescape}'
915 916 }
916 917 # filecopy is preserved for compatibility reasons
917 918 defaulttempl['filecopy'] = defaulttempl['file_copy']
918 919 self.t = templater.templater(mapfile, {'formatnode': formatnode},
919 920 cache=defaulttempl)
920 921 self.cache = {}
921 922
922 923 def use_template(self, t):
923 924 '''set template string to use'''
924 925 self.t.cache['changeset'] = t
925 926
926 927 def _meaningful_parentrevs(self, ctx):
927 928 """Return list of meaningful (or all if debug) parentrevs for rev.
928 929 """
929 930 parents = ctx.parents()
930 931 if len(parents) > 1:
931 932 return parents
932 933 if self.ui.debugflag:
933 934 return [parents[0], self.repo['null']]
934 935 if parents[0].rev() >= ctx.rev() - 1:
935 936 return []
936 937 return parents
937 938
938 939 def _show(self, ctx, copies, matchfn, props):
939 940 '''show a single changeset or file revision'''
940 941
941 942 showlist = templatekw.showlist
942 943
943 944 # showparents() behaviour depends on ui trace level which
944 945 # causes unexpected behaviours at templating level and makes
945 946 # it harder to extract it in a standalone function. Its
946 947 # behaviour cannot be changed so leave it here for now.
947 948 def showparents(**args):
948 949 ctx = args['ctx']
949 950 parents = [[('rev', p.rev()), ('node', p.hex())]
950 951 for p in self._meaningful_parentrevs(ctx)]
951 952 return showlist('parent', parents, **args)
952 953
953 954 props = props.copy()
954 955 props.update(templatekw.keywords)
955 956 props['parents'] = showparents
956 957 props['templ'] = self.t
957 958 props['ctx'] = ctx
958 959 props['repo'] = self.repo
959 960 props['revcache'] = {'copies': copies}
960 961 props['cache'] = self.cache
961 962
962 963 # find correct templates for current mode
963 964
964 965 tmplmodes = [
965 966 (True, None),
966 967 (self.ui.verbose, 'verbose'),
967 968 (self.ui.quiet, 'quiet'),
968 969 (self.ui.debugflag, 'debug'),
969 970 ]
970 971
971 972 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
972 973 for mode, postfix in tmplmodes:
973 974 for type in types:
974 975 cur = postfix and ('%s_%s' % (type, postfix)) or type
975 976 if mode and cur in self.t:
976 977 types[type] = cur
977 978
978 979 try:
979 980
980 981 # write header
981 982 if types['header']:
982 983 h = templater.stringify(self.t(types['header'], **props))
983 984 if self.buffered:
984 985 self.header[ctx.rev()] = h
985 986 else:
986 987 if self.lastheader != h:
987 988 self.lastheader = h
988 989 self.ui.write(h)
989 990
990 991 # write changeset metadata, then patch if requested
991 992 key = types['changeset']
992 993 self.ui.write(templater.stringify(self.t(key, **props)))
993 994 self.showpatch(ctx.node(), matchfn)
994 995
995 996 if types['footer']:
996 997 if not self.footer:
997 998 self.footer = templater.stringify(self.t(types['footer'],
998 999 **props))
999 1000
1000 1001 except KeyError, inst:
1001 1002 msg = _("%s: no key named '%s'")
1002 1003 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1003 1004 except SyntaxError, inst:
1004 1005 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1005 1006
1006 1007 def show_changeset(ui, repo, opts, buffered=False):
1007 1008 """show one changeset using template or regular display.
1008 1009
1009 1010 Display format will be the first non-empty hit of:
1010 1011 1. option 'template'
1011 1012 2. option 'style'
1012 1013 3. [ui] setting 'logtemplate'
1013 1014 4. [ui] setting 'style'
1014 1015 If all of these values are either the unset or the empty string,
1015 1016 regular display via changeset_printer() is done.
1016 1017 """
1017 1018 # options
1018 1019 patch = False
1019 1020 if opts.get('patch') or opts.get('stat'):
1020 1021 patch = matchall(repo)
1021 1022
1022 1023 tmpl = opts.get('template')
1023 1024 style = None
1024 1025 if tmpl:
1025 1026 tmpl = templater.parsestring(tmpl, quoted=False)
1026 1027 else:
1027 1028 style = opts.get('style')
1028 1029
1029 1030 # ui settings
1030 1031 if not (tmpl or style):
1031 1032 tmpl = ui.config('ui', 'logtemplate')
1032 1033 if tmpl:
1033 1034 tmpl = templater.parsestring(tmpl)
1034 1035 else:
1035 1036 style = util.expandpath(ui.config('ui', 'style', ''))
1036 1037
1037 1038 if not (tmpl or style):
1038 1039 return changeset_printer(ui, repo, patch, opts, buffered)
1039 1040
1040 1041 mapfile = None
1041 1042 if style and not tmpl:
1042 1043 mapfile = style
1043 1044 if not os.path.split(mapfile)[0]:
1044 1045 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1045 1046 or templater.templatepath(mapfile))
1046 1047 if mapname:
1047 1048 mapfile = mapname
1048 1049
1049 1050 try:
1050 1051 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
1051 1052 except SyntaxError, inst:
1052 1053 raise util.Abort(inst.args[0])
1053 1054 if tmpl:
1054 1055 t.use_template(tmpl)
1055 1056 return t
1056 1057
1057 1058 def finddate(ui, repo, date):
1058 1059 """Find the tipmost changeset that matches the given date spec"""
1059 1060
1060 1061 df = util.matchdate(date)
1061 1062 m = matchall(repo)
1062 1063 results = {}
1063 1064
1064 1065 def prep(ctx, fns):
1065 1066 d = ctx.date()
1066 1067 if df(d[0]):
1067 1068 results[ctx.rev()] = d
1068 1069
1069 1070 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1070 1071 rev = ctx.rev()
1071 1072 if rev in results:
1072 1073 ui.status(_("Found revision %s from %s\n") %
1073 1074 (rev, util.datestr(results[rev])))
1074 1075 return str(rev)
1075 1076
1076 1077 raise util.Abort(_("revision matching date not found"))
1077 1078
1078 1079 def walkchangerevs(repo, match, opts, prepare):
1079 1080 '''Iterate over files and the revs in which they changed.
1080 1081
1081 1082 Callers most commonly need to iterate backwards over the history
1082 1083 in which they are interested. Doing so has awful (quadratic-looking)
1083 1084 performance, so we use iterators in a "windowed" way.
1084 1085
1085 1086 We walk a window of revisions in the desired order. Within the
1086 1087 window, we first walk forwards to gather data, then in the desired
1087 1088 order (usually backwards) to display it.
1088 1089
1089 1090 This function returns an iterator yielding contexts. Before
1090 1091 yielding each context, the iterator will first call the prepare
1091 1092 function on each context in the window in forward order.'''
1092 1093
1093 1094 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1094 1095 if start < end:
1095 1096 while start < end:
1096 1097 yield start, min(windowsize, end - start)
1097 1098 start += windowsize
1098 1099 if windowsize < sizelimit:
1099 1100 windowsize *= 2
1100 1101 else:
1101 1102 while start > end:
1102 1103 yield start, min(windowsize, start - end - 1)
1103 1104 start -= windowsize
1104 1105 if windowsize < sizelimit:
1105 1106 windowsize *= 2
1106 1107
1107 1108 follow = opts.get('follow') or opts.get('follow_first')
1108 1109
1109 1110 if not len(repo):
1110 1111 return []
1111 1112
1112 1113 if follow:
1113 1114 defrange = '%s:0' % repo['.'].rev()
1114 1115 else:
1115 1116 defrange = '-1:0'
1116 1117 revs = revrange(repo, opts['rev'] or [defrange])
1117 1118 if not revs:
1118 1119 return []
1119 1120 wanted = set()
1120 1121 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1121 1122 fncache = {}
1122 1123 change = util.cachefunc(repo.changectx)
1123 1124
1124 1125 # First step is to fill wanted, the set of revisions that we want to yield.
1125 1126 # When it does not induce extra cost, we also fill fncache for revisions in
1126 1127 # wanted: a cache of filenames that were changed (ctx.files()) and that
1127 1128 # match the file filtering conditions.
1128 1129
1129 1130 if not slowpath and not match.files():
1130 1131 # No files, no patterns. Display all revs.
1131 1132 wanted = set(revs)
1132 1133 copies = []
1133 1134
1134 1135 if not slowpath:
1135 1136 # We only have to read through the filelog to find wanted revisions
1136 1137
1137 1138 minrev, maxrev = min(revs), max(revs)
1138 1139 def filerevgen(filelog, last):
1139 1140 """
1140 1141 Only files, no patterns. Check the history of each file.
1141 1142
1142 1143 Examines filelog entries within minrev, maxrev linkrev range
1143 1144 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1144 1145 tuples in backwards order
1145 1146 """
1146 1147 cl_count = len(repo)
1147 1148 revs = []
1148 1149 for j in xrange(0, last + 1):
1149 1150 linkrev = filelog.linkrev(j)
1150 1151 if linkrev < minrev:
1151 1152 continue
1152 1153 # only yield rev for which we have the changelog, it can
1153 1154 # happen while doing "hg log" during a pull or commit
1154 1155 if linkrev >= cl_count:
1155 1156 break
1156 1157
1157 1158 parentlinkrevs = []
1158 1159 for p in filelog.parentrevs(j):
1159 1160 if p != nullrev:
1160 1161 parentlinkrevs.append(filelog.linkrev(p))
1161 1162 n = filelog.node(j)
1162 1163 revs.append((linkrev, parentlinkrevs,
1163 1164 follow and filelog.renamed(n)))
1164 1165
1165 1166 return reversed(revs)
1166 1167 def iterfiles():
1167 1168 for filename in match.files():
1168 1169 yield filename, None
1169 1170 for filename_node in copies:
1170 1171 yield filename_node
1171 1172 for file_, node in iterfiles():
1172 1173 filelog = repo.file(file_)
1173 1174 if not len(filelog):
1174 1175 if node is None:
1175 1176 # A zero count may be a directory or deleted file, so
1176 1177 # try to find matching entries on the slow path.
1177 1178 if follow:
1178 1179 raise util.Abort(
1179 1180 _('cannot follow nonexistent file: "%s"') % file_)
1180 1181 slowpath = True
1181 1182 break
1182 1183 else:
1183 1184 continue
1184 1185
1185 1186 if node is None:
1186 1187 last = len(filelog) - 1
1187 1188 else:
1188 1189 last = filelog.rev(node)
1189 1190
1190 1191
1191 1192 # keep track of all ancestors of the file
1192 1193 ancestors = set([filelog.linkrev(last)])
1193 1194
1194 1195 # iterate from latest to oldest revision
1195 1196 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1196 1197 if not follow:
1197 1198 if rev > maxrev:
1198 1199 continue
1199 1200 else:
1200 1201 # Note that last might not be the first interesting
1201 1202 # rev to us:
1202 1203 # if the file has been changed after maxrev, we'll
1203 1204 # have linkrev(last) > maxrev, and we still need
1204 1205 # to explore the file graph
1205 1206 if rev not in ancestors:
1206 1207 continue
1207 1208 # XXX insert 1327 fix here
1208 1209 if flparentlinkrevs:
1209 1210 ancestors.update(flparentlinkrevs)
1210 1211
1211 1212 fncache.setdefault(rev, []).append(file_)
1212 1213 wanted.add(rev)
1213 1214 if copied:
1214 1215 copies.append(copied)
1215 1216 if slowpath:
1216 1217 # We have to read the changelog to match filenames against
1217 1218 # changed files
1218 1219
1219 1220 if follow:
1220 1221 raise util.Abort(_('can only follow copies/renames for explicit '
1221 1222 'filenames'))
1222 1223
1223 1224 # The slow path checks files modified in every changeset.
1224 1225 for i in sorted(revs):
1225 1226 ctx = change(i)
1226 1227 matches = filter(match, ctx.files())
1227 1228 if matches:
1228 1229 fncache[i] = matches
1229 1230 wanted.add(i)
1230 1231
1231 1232 class followfilter(object):
1232 1233 def __init__(self, onlyfirst=False):
1233 1234 self.startrev = nullrev
1234 1235 self.roots = set()
1235 1236 self.onlyfirst = onlyfirst
1236 1237
1237 1238 def match(self, rev):
1238 1239 def realparents(rev):
1239 1240 if self.onlyfirst:
1240 1241 return repo.changelog.parentrevs(rev)[0:1]
1241 1242 else:
1242 1243 return filter(lambda x: x != nullrev,
1243 1244 repo.changelog.parentrevs(rev))
1244 1245
1245 1246 if self.startrev == nullrev:
1246 1247 self.startrev = rev
1247 1248 return True
1248 1249
1249 1250 if rev > self.startrev:
1250 1251 # forward: all descendants
1251 1252 if not self.roots:
1252 1253 self.roots.add(self.startrev)
1253 1254 for parent in realparents(rev):
1254 1255 if parent in self.roots:
1255 1256 self.roots.add(rev)
1256 1257 return True
1257 1258 else:
1258 1259 # backwards: all parents
1259 1260 if not self.roots:
1260 1261 self.roots.update(realparents(self.startrev))
1261 1262 if rev in self.roots:
1262 1263 self.roots.remove(rev)
1263 1264 self.roots.update(realparents(rev))
1264 1265 return True
1265 1266
1266 1267 return False
1267 1268
1268 1269 # it might be worthwhile to do this in the iterator if the rev range
1269 1270 # is descending and the prune args are all within that range
1270 1271 for rev in opts.get('prune', ()):
1271 1272 rev = repo.changelog.rev(repo.lookup(rev))
1272 1273 ff = followfilter()
1273 1274 stop = min(revs[0], revs[-1])
1274 1275 for x in xrange(rev, stop - 1, -1):
1275 1276 if ff.match(x):
1276 1277 wanted.discard(x)
1277 1278
1278 1279 # Now that wanted is correctly initialized, we can iterate over the
1279 1280 # revision range, yielding only revisions in wanted.
1280 1281 def iterate():
1281 1282 if follow and not match.files():
1282 1283 ff = followfilter(onlyfirst=opts.get('follow_first'))
1283 1284 def want(rev):
1284 1285 return ff.match(rev) and rev in wanted
1285 1286 else:
1286 1287 def want(rev):
1287 1288 return rev in wanted
1288 1289
1289 1290 for i, window in increasing_windows(0, len(revs)):
1290 1291 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1291 1292 for rev in sorted(nrevs):
1292 1293 fns = fncache.get(rev)
1293 1294 ctx = change(rev)
1294 1295 if not fns:
1295 1296 def fns_generator():
1296 1297 for f in ctx.files():
1297 1298 if match(f):
1298 1299 yield f
1299 1300 fns = fns_generator()
1300 1301 prepare(ctx, fns)
1301 1302 for rev in nrevs:
1302 1303 yield change(rev)
1303 1304 return iterate()
1304 1305
1305 1306 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1306 1307 join = lambda f: os.path.join(prefix, f)
1307 1308 bad = []
1308 1309 oldbad = match.bad
1309 1310 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1310 1311 names = []
1311 1312 wctx = repo[None]
1312 1313 for f in repo.walk(match):
1313 1314 exact = match.exact(f)
1314 1315 if exact or f not in repo.dirstate:
1315 1316 names.append(f)
1316 1317 if ui.verbose or not exact:
1317 1318 ui.status(_('adding %s\n') % match.rel(join(f)))
1318 1319
1319 1320 if listsubrepos:
1320 1321 for subpath in wctx.substate:
1321 1322 sub = wctx.sub(subpath)
1322 1323 try:
1323 1324 submatch = matchmod.narrowmatcher(subpath, match)
1324 1325 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1325 1326 except error.LookupError:
1326 1327 ui.status(_("skipping missing subrepository: %s\n")
1327 1328 % join(subpath))
1328 1329
1329 1330 if not dryrun:
1330 1331 rejected = wctx.add(names, prefix)
1331 1332 bad.extend(f for f in rejected if f in match.files())
1332 1333 return bad
1333 1334
1334 1335 def commit(ui, repo, commitfunc, pats, opts):
1335 1336 '''commit the specified files or all outstanding changes'''
1336 1337 date = opts.get('date')
1337 1338 if date:
1338 1339 opts['date'] = util.parsedate(date)
1339 1340 message = logmessage(opts)
1340 1341
1341 1342 # extract addremove carefully -- this function can be called from a command
1342 1343 # that doesn't support addremove
1343 1344 if opts.get('addremove'):
1344 1345 addremove(repo, pats, opts)
1345 1346
1346 1347 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1347 1348
1348 1349 def commiteditor(repo, ctx, subs):
1349 1350 if ctx.description():
1350 1351 return ctx.description()
1351 1352 return commitforceeditor(repo, ctx, subs)
1352 1353
1353 1354 def commitforceeditor(repo, ctx, subs):
1354 1355 edittext = []
1355 1356 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1356 1357 if ctx.description():
1357 1358 edittext.append(ctx.description())
1358 1359 edittext.append("")
1359 1360 edittext.append("") # Empty line between message and comments.
1360 1361 edittext.append(_("HG: Enter commit message."
1361 1362 " Lines beginning with 'HG:' are removed."))
1362 1363 edittext.append(_("HG: Leave message empty to abort commit."))
1363 1364 edittext.append("HG: --")
1364 1365 edittext.append(_("HG: user: %s") % ctx.user())
1365 1366 if ctx.p2():
1366 1367 edittext.append(_("HG: branch merge"))
1367 1368 if ctx.branch():
1368 1369 edittext.append(_("HG: branch '%s'") % ctx.branch())
1369 1370 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1370 1371 edittext.extend([_("HG: added %s") % f for f in added])
1371 1372 edittext.extend([_("HG: changed %s") % f for f in modified])
1372 1373 edittext.extend([_("HG: removed %s") % f for f in removed])
1373 1374 if not added and not modified and not removed:
1374 1375 edittext.append(_("HG: no files changed"))
1375 1376 edittext.append("")
1376 1377 # run editor in the repository root
1377 1378 olddir = os.getcwd()
1378 1379 os.chdir(repo.root)
1379 1380 text = repo.ui.edit("\n".join(edittext), ctx.user())
1380 1381 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1381 1382 os.chdir(olddir)
1382 1383
1383 1384 if not text.strip():
1384 1385 raise util.Abort(_("empty commit message"))
1385 1386
1386 1387 return text
General Comments 0
You need to be logged in to leave comments. Login now