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