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