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