##// END OF EJS Templates
Add support for cloning with hardlinks on windows....
Stephen Darnell -
r1241:3b4f05ff default
parent child Browse files
Show More
@@ -1,2072 +1,2068 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import demandload
9 9 from node import *
10 10 demandload(globals(), "os re sys signal shutil imp urllib pdb")
11 11 demandload(globals(), "fancyopts ui hg util lock revlog")
12 12 demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
13 13 demandload(globals(), "errno socket version struct atexit sets bz2")
14 14
15 15 class UnknownCommand(Exception):
16 16 """Exception raised if command is not in the command table."""
17 17
18 18 def filterfiles(filters, files):
19 19 l = [x for x in files if x in filters]
20 20
21 21 for t in filters:
22 22 if t and t[-1] != "/":
23 23 t += "/"
24 24 l += [x for x in files if x.startswith(t)]
25 25 return l
26 26
27 27 def relpath(repo, args):
28 28 cwd = repo.getcwd()
29 29 if cwd:
30 30 return [util.normpath(os.path.join(cwd, x)) for x in args]
31 31 return args
32 32
33 33 def matchpats(repo, cwd, pats=[], opts={}, head=''):
34 34 return util.matcher(repo.root, cwd, pats or ['.'], opts.get('include'),
35 35 opts.get('exclude'), head)
36 36
37 37 def makewalk(repo, pats, opts, head=''):
38 38 cwd = repo.getcwd()
39 39 files, matchfn, anypats = matchpats(repo, cwd, pats, opts, head)
40 40 exact = dict(zip(files, files))
41 41 def walk():
42 42 for src, fn in repo.walk(files=files, match=matchfn):
43 43 yield src, fn, util.pathto(cwd, fn), fn in exact
44 44 return files, matchfn, walk()
45 45
46 46 def walk(repo, pats, opts, head=''):
47 47 files, matchfn, results = makewalk(repo, pats, opts, head)
48 48 for r in results:
49 49 yield r
50 50
51 51 def walkchangerevs(ui, repo, cwd, pats, opts):
52 52 '''Iterate over files and the revs they changed in.
53 53
54 54 Callers most commonly need to iterate backwards over the history
55 55 it is interested in. Doing so has awful (quadratic-looking)
56 56 performance, so we use iterators in a "windowed" way.
57 57
58 58 We walk a window of revisions in the desired order. Within the
59 59 window, we first walk forwards to gather data, then in the desired
60 60 order (usually backwards) to display it.
61 61
62 62 This function returns an (iterator, getchange) pair. The
63 63 getchange function returns the changelog entry for a numeric
64 64 revision. The iterator yields 3-tuples. They will be of one of
65 65 the following forms:
66 66
67 67 "window", incrementing, lastrev: stepping through a window,
68 68 positive if walking forwards through revs, last rev in the
69 69 sequence iterated over - use to reset state for the current window
70 70
71 71 "add", rev, fns: out-of-order traversal of the given file names
72 72 fns, which changed during revision rev - use to gather data for
73 73 possible display
74 74
75 75 "iter", rev, None: in-order traversal of the revs earlier iterated
76 76 over with "add" - use to display data'''
77 77 cwd = repo.getcwd()
78 78 if not pats and cwd:
79 79 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
80 80 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
81 81 files, matchfn, anypats = matchpats(repo, (pats and cwd) or '',
82 82 pats, opts)
83 83 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
84 84 wanted = {}
85 85 slowpath = anypats
86 86 window = 300
87 87 fncache = {}
88 88
89 89 chcache = {}
90 90 def getchange(rev):
91 91 ch = chcache.get(rev)
92 92 if ch is None:
93 93 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
94 94 return ch
95 95
96 96 if not slowpath and not files:
97 97 # No files, no patterns. Display all revs.
98 98 wanted = dict(zip(revs, revs))
99 99 if not slowpath:
100 100 # Only files, no patterns. Check the history of each file.
101 101 def filerevgen(filelog):
102 102 for i in xrange(filelog.count() - 1, -1, -window):
103 103 revs = []
104 104 for j in xrange(max(0, i - window), i + 1):
105 105 revs.append(filelog.linkrev(filelog.node(j)))
106 106 revs.reverse()
107 107 for rev in revs:
108 108 yield rev
109 109
110 110 minrev, maxrev = min(revs), max(revs)
111 111 for file in files:
112 112 filelog = repo.file(file)
113 113 # A zero count may be a directory or deleted file, so
114 114 # try to find matching entries on the slow path.
115 115 if filelog.count() == 0:
116 116 slowpath = True
117 117 break
118 118 for rev in filerevgen(filelog):
119 119 if rev <= maxrev:
120 120 if rev < minrev:
121 121 break
122 122 fncache.setdefault(rev, [])
123 123 fncache[rev].append(file)
124 124 wanted[rev] = 1
125 125 if slowpath:
126 126 # The slow path checks files modified in every changeset.
127 127 def changerevgen():
128 128 for i in xrange(repo.changelog.count() - 1, -1, -window):
129 129 for j in xrange(max(0, i - window), i + 1):
130 130 yield j, getchange(j)[3]
131 131
132 132 for rev, changefiles in changerevgen():
133 133 matches = filter(matchfn, changefiles)
134 134 if matches:
135 135 fncache[rev] = matches
136 136 wanted[rev] = 1
137 137
138 138 def iterate():
139 139 for i in xrange(0, len(revs), window):
140 140 yield 'window', revs[0] < revs[-1], revs[-1]
141 141 nrevs = [rev for rev in revs[i:min(i+window, len(revs))]
142 142 if rev in wanted]
143 143 srevs = list(nrevs)
144 144 srevs.sort()
145 145 for rev in srevs:
146 146 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
147 147 yield 'add', rev, fns
148 148 for rev in nrevs:
149 149 yield 'iter', rev, None
150 150 return iterate(), getchange
151 151
152 152 revrangesep = ':'
153 153
154 154 def revrange(ui, repo, revs, revlog=None):
155 155 """Yield revision as strings from a list of revision specifications."""
156 156 if revlog is None:
157 157 revlog = repo.changelog
158 158 revcount = revlog.count()
159 159 def fix(val, defval):
160 160 if not val:
161 161 return defval
162 162 try:
163 163 num = int(val)
164 164 if str(num) != val:
165 165 raise ValueError
166 166 if num < 0:
167 167 num += revcount
168 168 if not (0 <= num < revcount):
169 169 raise ValueError
170 170 except ValueError:
171 171 try:
172 172 num = repo.changelog.rev(repo.lookup(val))
173 173 except KeyError:
174 174 try:
175 175 num = revlog.rev(revlog.lookup(val))
176 176 except KeyError:
177 177 raise util.Abort('invalid revision identifier %s', val)
178 178 return num
179 179 seen = {}
180 180 for spec in revs:
181 181 if spec.find(revrangesep) >= 0:
182 182 start, end = spec.split(revrangesep, 1)
183 183 start = fix(start, 0)
184 184 end = fix(end, revcount - 1)
185 185 step = start > end and -1 or 1
186 186 for rev in xrange(start, end+step, step):
187 187 if rev in seen: continue
188 188 seen[rev] = 1
189 189 yield str(rev)
190 190 else:
191 191 rev = fix(spec, None)
192 192 if rev in seen: continue
193 193 seen[rev] = 1
194 194 yield str(rev)
195 195
196 196 def make_filename(repo, r, pat, node=None,
197 197 total=None, seqno=None, revwidth=None):
198 198 node_expander = {
199 199 'H': lambda: hex(node),
200 200 'R': lambda: str(r.rev(node)),
201 201 'h': lambda: short(node),
202 202 }
203 203 expander = {
204 204 '%': lambda: '%',
205 205 'b': lambda: os.path.basename(repo.root),
206 206 }
207 207
208 208 try:
209 209 if node:
210 210 expander.update(node_expander)
211 211 if node and revwidth is not None:
212 212 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
213 213 if total is not None:
214 214 expander['N'] = lambda: str(total)
215 215 if seqno is not None:
216 216 expander['n'] = lambda: str(seqno)
217 217 if total is not None and seqno is not None:
218 218 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
219 219
220 220 newname = []
221 221 patlen = len(pat)
222 222 i = 0
223 223 while i < patlen:
224 224 c = pat[i]
225 225 if c == '%':
226 226 i += 1
227 227 c = pat[i]
228 228 c = expander[c]()
229 229 newname.append(c)
230 230 i += 1
231 231 return ''.join(newname)
232 232 except KeyError, inst:
233 233 raise util.Abort("invalid format spec '%%%s' in output file name",
234 234 inst.args[0])
235 235
236 236 def make_file(repo, r, pat, node=None,
237 237 total=None, seqno=None, revwidth=None, mode='wb'):
238 238 if not pat or pat == '-':
239 239 return 'w' in mode and sys.stdout or sys.stdin
240 240 if hasattr(pat, 'write') and 'w' in mode:
241 241 return pat
242 242 if hasattr(pat, 'read') and 'r' in mode:
243 243 return pat
244 244 return open(make_filename(repo, r, pat, node, total, seqno, revwidth),
245 245 mode)
246 246
247 247 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
248 248 changes=None, text=False):
249 249 def date(c):
250 250 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
251 251
252 252 if not changes:
253 253 (c, a, d, u) = repo.changes(node1, node2, files, match=match)
254 254 else:
255 255 (c, a, d, u) = changes
256 256 if files:
257 257 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
258 258
259 259 if not c and not a and not d:
260 260 return
261 261
262 262 if node2:
263 263 change = repo.changelog.read(node2)
264 264 mmap2 = repo.manifest.read(change[0])
265 265 date2 = date(change)
266 266 def read(f):
267 267 return repo.file(f).read(mmap2[f])
268 268 else:
269 269 date2 = time.asctime()
270 270 if not node1:
271 271 node1 = repo.dirstate.parents()[0]
272 272 def read(f):
273 273 return repo.wfile(f).read()
274 274
275 275 if ui.quiet:
276 276 r = None
277 277 else:
278 278 hexfunc = ui.verbose and hex or short
279 279 r = [hexfunc(node) for node in [node1, node2] if node]
280 280
281 281 change = repo.changelog.read(node1)
282 282 mmap = repo.manifest.read(change[0])
283 283 date1 = date(change)
284 284
285 285 for f in c:
286 286 to = None
287 287 if f in mmap:
288 288 to = repo.file(f).read(mmap[f])
289 289 tn = read(f)
290 290 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
291 291 for f in a:
292 292 to = None
293 293 tn = read(f)
294 294 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
295 295 for f in d:
296 296 to = repo.file(f).read(mmap[f])
297 297 tn = None
298 298 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
299 299
300 300 def trimuser(ui, name, rev, revcache):
301 301 """trim the name of the user who committed a change"""
302 302 user = revcache.get(rev)
303 303 if user is None:
304 304 user = revcache[rev] = ui.shortuser(name)
305 305 return user
306 306
307 307 def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None):
308 308 """show a single changeset or file revision"""
309 309 log = repo.changelog
310 310 if changenode is None:
311 311 changenode = log.node(rev)
312 312 elif not rev:
313 313 rev = log.rev(changenode)
314 314
315 315 if ui.quiet:
316 316 ui.write("%d:%s\n" % (rev, short(changenode)))
317 317 return
318 318
319 319 changes = log.read(changenode)
320 320
321 321 t, tz = changes[2].split(' ')
322 322 # a conversion tool was sticking non-integer offsets into repos
323 323 try:
324 324 tz = int(tz)
325 325 except ValueError:
326 326 tz = 0
327 327 date = time.asctime(time.localtime(float(t))) + " %+05d" % (int(tz)/-36)
328 328
329 329 parents = [(log.rev(p), ui.verbose and hex(p) or short(p))
330 330 for p in log.parents(changenode)
331 331 if ui.debugflag or p != nullid]
332 332 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
333 333 parents = []
334 334
335 335 if ui.verbose:
336 336 ui.write("changeset: %d:%s\n" % (rev, hex(changenode)))
337 337 else:
338 338 ui.write("changeset: %d:%s\n" % (rev, short(changenode)))
339 339
340 340 for tag in repo.nodetags(changenode):
341 341 ui.status("tag: %s\n" % tag)
342 342 for parent in parents:
343 343 ui.write("parent: %d:%s\n" % parent)
344 344
345 345 if brinfo and changenode in brinfo:
346 346 br = brinfo[changenode]
347 347 ui.write("branch: %s\n" % " ".join(br))
348 348
349 349 ui.debug("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
350 350 hex(changes[0])))
351 351 ui.status("user: %s\n" % changes[1])
352 352 ui.status("date: %s\n" % date)
353 353
354 354 if ui.debugflag:
355 355 files = repo.changes(log.parents(changenode)[0], changenode)
356 356 for key, value in zip(["files:", "files+:", "files-:"], files):
357 357 if value:
358 358 ui.note("%-12s %s\n" % (key, " ".join(value)))
359 359 else:
360 360 ui.note("files: %s\n" % " ".join(changes[3]))
361 361
362 362 description = changes[4].strip()
363 363 if description:
364 364 if ui.verbose:
365 365 ui.status("description:\n")
366 366 ui.status(description)
367 367 ui.status("\n\n")
368 368 else:
369 369 ui.status("summary: %s\n" % description.splitlines()[0])
370 370 ui.status("\n")
371 371
372 372 def show_version(ui):
373 373 """output version and copyright information"""
374 374 ui.write("Mercurial Distributed SCM (version %s)\n"
375 375 % version.get_version())
376 376 ui.status(
377 377 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
378 378 "This is free software; see the source for copying conditions. "
379 379 "There is NO\nwarranty; "
380 380 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
381 381 )
382 382
383 383 def help_(ui, cmd=None, with_version=False):
384 384 """show help for a given command or all commands"""
385 385 option_lists = []
386 386 if cmd and cmd != 'shortlist':
387 387 if with_version:
388 388 show_version(ui)
389 389 ui.write('\n')
390 390 key, i = find(cmd)
391 391 # synopsis
392 392 ui.write("%s\n\n" % i[2])
393 393
394 394 # description
395 395 doc = i[0].__doc__
396 396 if ui.quiet:
397 397 doc = doc.splitlines(0)[0]
398 398 ui.write("%s\n" % doc.rstrip())
399 399
400 400 if not ui.quiet:
401 401 # aliases
402 402 aliases = ', '.join(key.split('|')[1:])
403 403 if aliases:
404 404 ui.write("\naliases: %s\n" % aliases)
405 405
406 406 # options
407 407 if i[1]:
408 408 option_lists.append(("options", i[1]))
409 409
410 410 else:
411 411 # program name
412 412 if ui.verbose or with_version:
413 413 show_version(ui)
414 414 else:
415 415 ui.status("Mercurial Distributed SCM\n")
416 416 ui.status('\n')
417 417
418 418 # list of commands
419 419 if cmd == "shortlist":
420 420 ui.status('basic commands (use "hg help" '
421 421 'for the full list or option "-v" for details):\n\n')
422 422 elif ui.verbose:
423 423 ui.status('list of commands:\n\n')
424 424 else:
425 425 ui.status('list of commands (use "hg help -v" '
426 426 'to show aliases and global options):\n\n')
427 427
428 428 h = {}
429 429 cmds = {}
430 430 for c, e in table.items():
431 431 f = c.split("|")[0]
432 432 if cmd == "shortlist" and not f.startswith("^"):
433 433 continue
434 434 f = f.lstrip("^")
435 435 if not ui.debugflag and f.startswith("debug"):
436 436 continue
437 437 d = ""
438 438 if e[0].__doc__:
439 439 d = e[0].__doc__.splitlines(0)[0].rstrip()
440 440 h[f] = d
441 441 cmds[f]=c.lstrip("^")
442 442
443 443 fns = h.keys()
444 444 fns.sort()
445 445 m = max(map(len, fns))
446 446 for f in fns:
447 447 if ui.verbose:
448 448 commands = cmds[f].replace("|",", ")
449 449 ui.write(" %s:\n %s\n"%(commands,h[f]))
450 450 else:
451 451 ui.write(' %-*s %s\n' % (m, f, h[f]))
452 452
453 453 # global options
454 454 if ui.verbose:
455 455 option_lists.append(("global options", globalopts))
456 456
457 457 # list all option lists
458 458 opt_output = []
459 459 for title, options in option_lists:
460 460 opt_output.append(("\n%s:\n" % title, None))
461 461 for shortopt, longopt, default, desc in options:
462 462 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
463 463 longopt and " --%s" % longopt),
464 464 "%s%s" % (desc,
465 465 default and " (default: %s)" % default
466 466 or "")))
467 467
468 468 if opt_output:
469 469 opts_len = max([len(line[0]) for line in opt_output if line[1]])
470 470 for first, second in opt_output:
471 471 if second:
472 472 ui.write(" %-*s %s\n" % (opts_len, first, second))
473 473 else:
474 474 ui.write("%s\n" % first)
475 475
476 476 # Commands start here, listed alphabetically
477 477
478 478 def add(ui, repo, *pats, **opts):
479 479 '''add the specified files on the next commit'''
480 480 names = []
481 481 for src, abs, rel, exact in walk(repo, pats, opts):
482 482 if exact:
483 483 names.append(abs)
484 484 elif repo.dirstate.state(abs) == '?':
485 485 ui.status('adding %s\n' % rel)
486 486 names.append(abs)
487 487 repo.add(names)
488 488
489 489 def addremove(ui, repo, *pats, **opts):
490 490 """add all new files, delete all missing files"""
491 491 add, remove = [], []
492 492 for src, abs, rel, exact in walk(repo, pats, opts):
493 493 if src == 'f' and repo.dirstate.state(abs) == '?':
494 494 add.append(abs)
495 495 if not exact:
496 496 ui.status('adding ', rel, '\n')
497 497 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
498 498 remove.append(abs)
499 499 if not exact:
500 500 ui.status('removing ', rel, '\n')
501 501 repo.add(add)
502 502 repo.remove(remove)
503 503
504 504 def annotate(ui, repo, *pats, **opts):
505 505 """show changeset information per file line"""
506 506 def getnode(rev):
507 507 return short(repo.changelog.node(rev))
508 508
509 509 ucache = {}
510 510 def getname(rev):
511 511 cl = repo.changelog.read(repo.changelog.node(rev))
512 512 return trimuser(ui, cl[1], rev, ucache)
513 513
514 514 if not pats:
515 515 raise util.Abort('at least one file name or pattern required')
516 516
517 517 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
518 518 if not opts['user'] and not opts['changeset']:
519 519 opts['number'] = 1
520 520
521 521 if opts['rev']:
522 522 node = repo.changelog.lookup(opts['rev'])
523 523 else:
524 524 node = repo.dirstate.parents()[0]
525 525 change = repo.changelog.read(node)
526 526 mmap = repo.manifest.read(change[0])
527 527
528 528 for src, abs, rel, exact in walk(repo, pats, opts):
529 529 if abs not in mmap:
530 530 ui.warn("warning: %s is not in the repository!\n" % rel)
531 531 continue
532 532
533 533 f = repo.file(abs)
534 534 if not opts['text'] and util.binary(f.read(mmap[abs])):
535 535 ui.write("%s: binary file\n" % rel)
536 536 continue
537 537
538 538 lines = f.annotate(mmap[abs])
539 539 pieces = []
540 540
541 541 for o, f in opmap:
542 542 if opts[o]:
543 543 l = [f(n) for n, dummy in lines]
544 544 if l:
545 545 m = max(map(len, l))
546 546 pieces.append(["%*s" % (m, x) for x in l])
547 547
548 548 if pieces:
549 549 for p, l in zip(zip(*pieces), lines):
550 550 ui.write("%s: %s" % (" ".join(p), l[1]))
551 551
552 552 def bundle(ui, repo, fname, dest="default-push", **opts):
553 553 """create a changegroup file"""
554 554 f = open(fname, "wb")
555 555 dest = ui.expandpath(dest)
556 556 other = hg.repository(ui, dest)
557 557 o = repo.findoutgoing(other)
558 558 cg = repo.changegroup(o)
559 559
560 560 try:
561 561 f.write("HG10")
562 562 z = bz2.BZ2Compressor(9)
563 563 while 1:
564 564 chunk = cg.read(4096)
565 565 if not chunk:
566 566 break
567 567 f.write(z.compress(chunk))
568 568 f.write(z.flush())
569 569 except:
570 570 os.unlink(fname)
571 571
572 572 def cat(ui, repo, file1, rev=None, **opts):
573 573 """output the latest or given revision of a file"""
574 574 r = repo.file(relpath(repo, [file1])[0])
575 575 if rev:
576 576 try:
577 577 # assume all revision numbers are for changesets
578 578 n = repo.lookup(rev)
579 579 change = repo.changelog.read(n)
580 580 m = repo.manifest.read(change[0])
581 581 n = m[relpath(repo, [file1])[0]]
582 582 except hg.RepoError, KeyError:
583 583 n = r.lookup(rev)
584 584 else:
585 585 n = r.tip()
586 586 fp = make_file(repo, r, opts['output'], node=n)
587 587 fp.write(r.read(n))
588 588
589 589 def clone(ui, source, dest=None, **opts):
590 590 """make a copy of an existing repository"""
591 591 if dest is None:
592 592 dest = os.path.basename(os.path.normpath(source))
593 593
594 594 if os.path.exists(dest):
595 595 raise util.Abort("destination '%s' already exists", dest)
596 596
597 597 dest = os.path.realpath(dest)
598 598
599 599 class Dircleanup:
600 600 def __init__(self, dir_):
601 601 self.rmtree = shutil.rmtree
602 602 self.dir_ = dir_
603 603 os.mkdir(dir_)
604 604 def close(self):
605 605 self.dir_ = None
606 606 def __del__(self):
607 607 if self.dir_:
608 608 self.rmtree(self.dir_, True)
609 609
610 610 if opts['ssh']:
611 611 ui.setconfig("ui", "ssh", opts['ssh'])
612 612 if opts['remotecmd']:
613 613 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
614 614
615 615 d = Dircleanup(dest)
616 616 if not os.path.exists(source):
617 617 source = ui.expandpath(source)
618 618 abspath = source
619 619 other = hg.repository(ui, source)
620 620
621 621 if other.dev() != -1:
622 622 abspath = os.path.abspath(source)
623 copyfile = (os.stat(dest).st_dev == other.dev()
624 and getattr(os, 'link', None) or shutil.copy2)
625 if copyfile is not shutil.copy2:
626 ui.note("cloning by hardlink\n")
627 623
628 624 # we use a lock here because if we race with commit, we can
629 625 # end up with extra data in the cloned revlogs that's not
630 626 # pointed to by changesets, thus causing verify to fail
631 627 l1 = lock.lock(os.path.join(source, ".hg", "lock"))
632 628
633 629 # and here to avoid premature writing to the target
634 630 os.mkdir(os.path.join(dest, ".hg"))
635 631 l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
636 632
637 633 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
638 634 for f in files.split():
639 635 src = os.path.join(source, ".hg", f)
640 636 dst = os.path.join(dest, ".hg", f)
641 util.copyfiles(src, dst, copyfile)
637 util.copyfiles(src, dst)
642 638
643 639 repo = hg.repository(ui, dest)
644 640
645 641 else:
646 642 repo = hg.repository(ui, dest, create=1)
647 643 repo.pull(other)
648 644
649 645 f = repo.opener("hgrc", "w")
650 646 f.write("[paths]\n")
651 647 f.write("default = %s\n" % abspath)
652 648
653 649 if not opts['noupdate']:
654 650 update(ui, repo)
655 651
656 652 d.close()
657 653
658 654 def commit(ui, repo, *pats, **opts):
659 655 """commit the specified files or all outstanding changes"""
660 656 if opts['text']:
661 657 ui.warn("Warning: -t and --text is deprecated,"
662 658 " please use -m or --message instead.\n")
663 659 message = opts['message'] or opts['text']
664 660 logfile = opts['logfile']
665 661 if not message and logfile:
666 662 try:
667 663 if logfile == '-':
668 664 message = sys.stdin.read()
669 665 else:
670 666 message = open(logfile).read()
671 667 except IOError, why:
672 668 ui.warn("Can't read commit message %s: %s\n" % (logfile, why))
673 669
674 670 if opts['addremove']:
675 671 addremove(ui, repo, *pats, **opts)
676 672 cwd = repo.getcwd()
677 673 if not pats and cwd:
678 674 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
679 675 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
680 676 fns, match, anypats = matchpats(repo, (pats and repo.getcwd()) or '',
681 677 pats, opts)
682 678 if pats:
683 679 c, a, d, u = repo.changes(files=fns, match=match)
684 680 files = c + a + [fn for fn in d if repo.dirstate.state(fn) == 'r']
685 681 else:
686 682 files = []
687 683 try:
688 684 repo.commit(files, message, opts['user'], opts['date'], match)
689 685 except ValueError, inst:
690 686 raise util.Abort(str(inst))
691 687
692 688 def copy(ui, repo, source, dest):
693 689 """mark a file as copied or renamed for the next commit"""
694 690 return repo.copy(*relpath(repo, (source, dest)))
695 691
696 692 def debugcheckstate(ui, repo):
697 693 """validate the correctness of the current dirstate"""
698 694 parent1, parent2 = repo.dirstate.parents()
699 695 repo.dirstate.read()
700 696 dc = repo.dirstate.map
701 697 keys = dc.keys()
702 698 keys.sort()
703 699 m1n = repo.changelog.read(parent1)[0]
704 700 m2n = repo.changelog.read(parent2)[0]
705 701 m1 = repo.manifest.read(m1n)
706 702 m2 = repo.manifest.read(m2n)
707 703 errors = 0
708 704 for f in dc:
709 705 state = repo.dirstate.state(f)
710 706 if state in "nr" and f not in m1:
711 707 ui.warn("%s in state %s, but not in manifest1\n" % (f, state))
712 708 errors += 1
713 709 if state in "a" and f in m1:
714 710 ui.warn("%s in state %s, but also in manifest1\n" % (f, state))
715 711 errors += 1
716 712 if state in "m" and f not in m1 and f not in m2:
717 713 ui.warn("%s in state %s, but not in either manifest\n" %
718 714 (f, state))
719 715 errors += 1
720 716 for f in m1:
721 717 state = repo.dirstate.state(f)
722 718 if state not in "nrm":
723 719 ui.warn("%s in manifest1, but listed as state %s" % (f, state))
724 720 errors += 1
725 721 if errors:
726 722 raise util.Abort(".hg/dirstate inconsistent with current parent's manifest")
727 723
728 724 def debugconfig(ui):
729 725 """show combined config settings from all hgrc files"""
730 726 try:
731 727 repo = hg.repository(ui)
732 728 except hg.RepoError:
733 729 pass
734 730 for section, name, value in ui.walkconfig():
735 731 ui.write('%s.%s=%s\n' % (section, name, value))
736 732
737 733 def debugstate(ui, repo):
738 734 """show the contents of the current dirstate"""
739 735 repo.dirstate.read()
740 736 dc = repo.dirstate.map
741 737 keys = dc.keys()
742 738 keys.sort()
743 739 for file_ in keys:
744 740 ui.write("%c %3o %10d %s %s\n"
745 741 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
746 742 time.strftime("%x %X",
747 743 time.localtime(dc[file_][3])), file_))
748 744 for f in repo.dirstate.copies:
749 745 ui.write("copy: %s -> %s\n" % (repo.dirstate.copies[f], f))
750 746
751 747 def debugdata(ui, file_, rev):
752 748 """dump the contents of an data file revision"""
753 749 r = revlog.revlog(file, file_[:-2] + ".i", file_)
754 750 ui.write(r.revision(r.lookup(rev)))
755 751
756 752 def debugindex(ui, file_):
757 753 """dump the contents of an index file"""
758 754 r = revlog.revlog(file, file_, "")
759 755 ui.write(" rev offset length base linkrev" +
760 756 " nodeid p1 p2\n")
761 757 for i in range(r.count()):
762 758 e = r.index[i]
763 759 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
764 760 i, e[0], e[1], e[2], e[3],
765 761 short(e[6]), short(e[4]), short(e[5])))
766 762
767 763 def debugindexdot(ui, file_):
768 764 """dump an index DAG as a .dot file"""
769 765 r = revlog.revlog(file, file_, "")
770 766 ui.write("digraph G {\n")
771 767 for i in range(r.count()):
772 768 e = r.index[i]
773 769 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
774 770 if e[5] != nullid:
775 771 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
776 772 ui.write("}\n")
777 773
778 774 def debugrename(ui, repo, file, rev=None):
779 775 """dump rename information"""
780 776 r = repo.file(relpath(repo, [file])[0])
781 777 if rev:
782 778 try:
783 779 # assume all revision numbers are for changesets
784 780 n = repo.lookup(rev)
785 781 change = repo.changelog.read(n)
786 782 m = repo.manifest.read(change[0])
787 783 n = m[relpath(repo, [file])[0]]
788 784 except hg.RepoError, KeyError:
789 785 n = r.lookup(rev)
790 786 else:
791 787 n = r.tip()
792 788 m = r.renamed(n)
793 789 if m:
794 790 ui.write("renamed from %s:%s\n" % (m[0], hex(m[1])))
795 791 else:
796 792 ui.write("not renamed\n")
797 793
798 794 def debugwalk(ui, repo, *pats, **opts):
799 795 """show how files match on given patterns"""
800 796 items = list(walk(repo, pats, opts))
801 797 if not items:
802 798 return
803 799 fmt = '%%s %%-%ds %%-%ds %%s\n' % (
804 800 max([len(abs) for (src, abs, rel, exact) in items]),
805 801 max([len(rel) for (src, abs, rel, exact) in items]))
806 802 for src, abs, rel, exact in items:
807 803 ui.write(fmt % (src, abs, rel, exact and 'exact' or ''))
808 804
809 805 def diff(ui, repo, *pats, **opts):
810 806 """diff working directory (or selected files)"""
811 807 node1, node2 = None, None
812 808 revs = [repo.lookup(x) for x in opts['rev']]
813 809
814 810 if len(revs) > 0:
815 811 node1 = revs[0]
816 812 if len(revs) > 1:
817 813 node2 = revs[1]
818 814 if len(revs) > 2:
819 815 raise util.Abort("too many revisions to diff")
820 816
821 817 files = []
822 818 match = util.always
823 819 if pats:
824 820 roots, match, results = makewalk(repo, pats, opts)
825 821 for src, abs, rel, exact in results:
826 822 files.append(abs)
827 823
828 824 dodiff(sys.stdout, ui, repo, node1, node2, files, match=match,
829 825 text=opts['text'])
830 826
831 827 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
832 828 node = repo.lookup(changeset)
833 829 prev, other = repo.changelog.parents(node)
834 830 change = repo.changelog.read(node)
835 831
836 832 fp = make_file(repo, repo.changelog, opts['output'],
837 833 node=node, total=total, seqno=seqno,
838 834 revwidth=revwidth)
839 835 if fp != sys.stdout:
840 836 ui.note("%s\n" % fp.name)
841 837
842 838 fp.write("# HG changeset patch\n")
843 839 fp.write("# User %s\n" % change[1])
844 840 fp.write("# Node ID %s\n" % hex(node))
845 841 fp.write("# Parent %s\n" % hex(prev))
846 842 if other != nullid:
847 843 fp.write("# Parent %s\n" % hex(other))
848 844 fp.write(change[4].rstrip())
849 845 fp.write("\n\n")
850 846
851 847 dodiff(fp, ui, repo, prev, node, text=opts['text'])
852 848 if fp != sys.stdout:
853 849 fp.close()
854 850
855 851 def export(ui, repo, *changesets, **opts):
856 852 """dump the header and diffs for one or more changesets"""
857 853 if not changesets:
858 854 raise util.Abort("export requires at least one changeset")
859 855 seqno = 0
860 856 revs = list(revrange(ui, repo, changesets))
861 857 total = len(revs)
862 858 revwidth = max(map(len, revs))
863 859 ui.note(len(revs) > 1 and "Exporting patches:\n" or "Exporting patch:\n")
864 860 for cset in revs:
865 861 seqno += 1
866 862 doexport(ui, repo, cset, seqno, total, revwidth, opts)
867 863
868 864 def forget(ui, repo, *pats, **opts):
869 865 """don't add the specified files on the next commit"""
870 866 forget = []
871 867 for src, abs, rel, exact in walk(repo, pats, opts):
872 868 if repo.dirstate.state(abs) == 'a':
873 869 forget.append(abs)
874 870 if not exact:
875 871 ui.status('forgetting ', rel, '\n')
876 872 repo.forget(forget)
877 873
878 874 def grep(ui, repo, pattern, *pats, **opts):
879 875 """search for a pattern in specified files and revisions"""
880 876 reflags = 0
881 877 if opts['ignore_case']:
882 878 reflags |= re.I
883 879 regexp = re.compile(pattern, reflags)
884 880 sep, eol = ':', '\n'
885 881 if opts['print0']:
886 882 sep = eol = '\0'
887 883
888 884 fcache = {}
889 885 def getfile(fn):
890 886 if fn not in fcache:
891 887 fcache[fn] = repo.file(fn)
892 888 return fcache[fn]
893 889
894 890 def matchlines(body):
895 891 begin = 0
896 892 linenum = 0
897 893 while True:
898 894 match = regexp.search(body, begin)
899 895 if not match:
900 896 break
901 897 mstart, mend = match.span()
902 898 linenum += body.count('\n', begin, mstart) + 1
903 899 lstart = body.rfind('\n', begin, mstart) + 1 or begin
904 900 lend = body.find('\n', mend)
905 901 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
906 902 begin = lend + 1
907 903
908 904 class linestate:
909 905 def __init__(self, line, linenum, colstart, colend):
910 906 self.line = line
911 907 self.linenum = linenum
912 908 self.colstart = colstart
913 909 self.colend = colend
914 910 def __eq__(self, other):
915 911 return self.line == other.line
916 912 def __hash__(self):
917 913 return hash(self.line)
918 914
919 915 matches = {}
920 916 def grepbody(fn, rev, body):
921 917 matches[rev].setdefault(fn, {})
922 918 m = matches[rev][fn]
923 919 for lnum, cstart, cend, line in matchlines(body):
924 920 s = linestate(line, lnum, cstart, cend)
925 921 m[s] = s
926 922
927 923 prev = {}
928 924 ucache = {}
929 925 def display(fn, rev, states, prevstates):
930 926 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
931 927 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
932 928 counts = {'-': 0, '+': 0}
933 929 filerevmatches = {}
934 930 for l in diff:
935 931 if incrementing or not opts['all']:
936 932 change = ((l in prevstates) and '-') or '+'
937 933 r = rev
938 934 else:
939 935 change = ((l in states) and '-') or '+'
940 936 r = prev[fn]
941 937 cols = [fn, str(rev)]
942 938 if opts['line_number']: cols.append(str(l.linenum))
943 939 if opts['all']: cols.append(change)
944 940 if opts['user']: cols.append(trimuser(ui, getchange(rev)[1], rev,
945 941 ucache))
946 942 if opts['files_with_matches']:
947 943 c = (fn, rev)
948 944 if c in filerevmatches: continue
949 945 filerevmatches[c] = 1
950 946 else:
951 947 cols.append(l.line)
952 948 ui.write(sep.join(cols), eol)
953 949 counts[change] += 1
954 950 return counts['+'], counts['-']
955 951
956 952 fstate = {}
957 953 skip = {}
958 954 changeiter, getchange = walkchangerevs(ui, repo, repo.getcwd(), pats, opts)
959 955 count = 0
960 956 for st, rev, fns in changeiter:
961 957 if st == 'window':
962 958 incrementing = rev
963 959 matches.clear()
964 960 elif st == 'add':
965 961 change = repo.changelog.read(repo.lookup(str(rev)))
966 962 mf = repo.manifest.read(change[0])
967 963 matches[rev] = {}
968 964 for fn in fns:
969 965 if fn in skip: continue
970 966 fstate.setdefault(fn, {})
971 967 try:
972 968 grepbody(fn, rev, getfile(fn).read(mf[fn]))
973 969 except KeyError:
974 970 pass
975 971 elif st == 'iter':
976 972 states = matches[rev].items()
977 973 states.sort()
978 974 for fn, m in states:
979 975 if fn in skip: continue
980 976 if incrementing or not opts['all'] or fstate[fn]:
981 977 pos, neg = display(fn, rev, m, fstate[fn])
982 978 count += pos + neg
983 979 if pos and not opts['all']:
984 980 skip[fn] = True
985 981 fstate[fn] = m
986 982 prev[fn] = rev
987 983
988 984 if not incrementing:
989 985 fstate = fstate.items()
990 986 fstate.sort()
991 987 for fn, state in fstate:
992 988 if fn in skip: continue
993 989 display(fn, rev, {}, state)
994 990 return (count == 0 and 1) or 0
995 991
996 992 def heads(ui, repo, **opts):
997 993 """show current repository heads"""
998 994 heads = repo.changelog.heads()
999 995 br = None
1000 996 if opts['branches']:
1001 997 br = repo.branchlookup(heads)
1002 998 for n in repo.changelog.heads():
1003 999 show_changeset(ui, repo, changenode=n, brinfo=br)
1004 1000
1005 1001 def identify(ui, repo):
1006 1002 """print information about the working copy"""
1007 1003 parents = [p for p in repo.dirstate.parents() if p != nullid]
1008 1004 if not parents:
1009 1005 ui.write("unknown\n")
1010 1006 return
1011 1007
1012 1008 hexfunc = ui.verbose and hex or short
1013 1009 (c, a, d, u) = repo.changes()
1014 1010 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
1015 1011 (c or a or d) and "+" or "")]
1016 1012
1017 1013 if not ui.quiet:
1018 1014 # multiple tags for a single parent separated by '/'
1019 1015 parenttags = ['/'.join(tags)
1020 1016 for tags in map(repo.nodetags, parents) if tags]
1021 1017 # tags for multiple parents separated by ' + '
1022 1018 if parenttags:
1023 1019 output.append(' + '.join(parenttags))
1024 1020
1025 1021 ui.write("%s\n" % ' '.join(output))
1026 1022
1027 1023 def import_(ui, repo, patch1, *patches, **opts):
1028 1024 """import an ordered set of patches"""
1029 1025 patches = (patch1,) + patches
1030 1026
1031 1027 if not opts['force']:
1032 1028 (c, a, d, u) = repo.changes()
1033 1029 if c or a or d:
1034 1030 raise util.Abort("outstanding uncommitted changes")
1035 1031
1036 1032 d = opts["base"]
1037 1033 strip = opts["strip"]
1038 1034
1039 1035 mailre = re.compile(r'(?:From |[\w-]+:)')
1040 1036 diffre = re.compile(r'(?:diff -|--- .*\s+\w+ \w+ +\d+ \d+:\d+:\d+ \d+)')
1041 1037
1042 1038 for patch in patches:
1043 1039 ui.status("applying %s\n" % patch)
1044 1040 pf = os.path.join(d, patch)
1045 1041
1046 1042 message = []
1047 1043 user = None
1048 1044 hgpatch = False
1049 1045 for line in file(pf):
1050 1046 line = line.rstrip()
1051 1047 if (not message and not hgpatch and
1052 1048 mailre.match(line) and not opts['force']):
1053 1049 if len(line) > 35: line = line[:32] + '...'
1054 1050 raise util.Abort('first line looks like a '
1055 1051 'mail header: ' + line)
1056 1052 if diffre.match(line):
1057 1053 break
1058 1054 elif hgpatch:
1059 1055 # parse values when importing the result of an hg export
1060 1056 if line.startswith("# User "):
1061 1057 user = line[7:]
1062 1058 ui.debug('User: %s\n' % user)
1063 1059 elif not line.startswith("# ") and line:
1064 1060 message.append(line)
1065 1061 hgpatch = False
1066 1062 elif line == '# HG changeset patch':
1067 1063 hgpatch = True
1068 1064 message = [] # We may have collected garbage
1069 1065 else:
1070 1066 message.append(line)
1071 1067
1072 1068 # make sure message isn't empty
1073 1069 if not message:
1074 1070 message = "imported patch %s\n" % patch
1075 1071 else:
1076 1072 message = "%s\n" % '\n'.join(message)
1077 1073 ui.debug('message:\n%s\n' % message)
1078 1074
1079 1075 f = os.popen("patch -p%d < '%s'" % (strip, pf))
1080 1076 files = []
1081 1077 for l in f.read().splitlines():
1082 1078 l.rstrip('\r\n');
1083 1079 ui.status("%s\n" % l)
1084 1080 if l.startswith('patching file '):
1085 1081 pf = l[14:]
1086 1082 if pf not in files:
1087 1083 files.append(pf)
1088 1084 patcherr = f.close()
1089 1085 if patcherr:
1090 1086 raise util.Abort("patch failed")
1091 1087
1092 1088 if len(files) > 0:
1093 1089 addremove(ui, repo, *files)
1094 1090 repo.commit(files, message, user)
1095 1091
1096 1092 def incoming(ui, repo, source="default", **opts):
1097 1093 """show new changesets found in source"""
1098 1094 source = ui.expandpath(source)
1099 1095 other = hg.repository(ui, source)
1100 1096 if not other.local():
1101 1097 raise util.Abort("incoming doesn't work for remote repositories yet")
1102 1098 o = repo.findincoming(other)
1103 1099 if not o:
1104 1100 return
1105 1101 o = other.newer(o)
1106 1102 o.reverse()
1107 1103 for n in o:
1108 1104 show_changeset(ui, other, changenode=n)
1109 1105 if opts['patch']:
1110 1106 prev = other.changelog.parents(n)[0]
1111 1107 dodiff(ui, ui, other, prev, n)
1112 1108 ui.write("\n")
1113 1109
1114 1110 def init(ui, dest="."):
1115 1111 """create a new repository in the given directory"""
1116 1112 if not os.path.exists(dest):
1117 1113 os.mkdir(dest)
1118 1114 hg.repository(ui, dest, create=1)
1119 1115
1120 1116 def locate(ui, repo, *pats, **opts):
1121 1117 """locate files matching specific patterns"""
1122 1118 end = opts['print0'] and '\0' or '\n'
1123 1119
1124 1120 for src, abs, rel, exact in walk(repo, pats, opts, '(?:.*/|)'):
1125 1121 if repo.dirstate.state(abs) == '?':
1126 1122 continue
1127 1123 if opts['fullpath']:
1128 1124 ui.write(os.path.join(repo.root, abs), end)
1129 1125 else:
1130 1126 ui.write(rel, end)
1131 1127
1132 1128 def log(ui, repo, *pats, **opts):
1133 1129 """show revision history of entire repository or files"""
1134 1130 class dui:
1135 1131 # Implement and delegate some ui protocol. Save hunks of
1136 1132 # output for later display in the desired order.
1137 1133 def __init__(self, ui):
1138 1134 self.ui = ui
1139 1135 self.hunk = {}
1140 1136 def bump(self, rev):
1141 1137 self.rev = rev
1142 1138 self.hunk[rev] = []
1143 1139 def note(self, *args):
1144 1140 if self.verbose:
1145 1141 self.write(*args)
1146 1142 def status(self, *args):
1147 1143 if not self.quiet:
1148 1144 self.write(*args)
1149 1145 def write(self, *args):
1150 1146 self.hunk[self.rev].append(args)
1151 1147 def __getattr__(self, key):
1152 1148 return getattr(self.ui, key)
1153 1149 cwd = repo.getcwd()
1154 1150 if not pats and cwd:
1155 1151 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
1156 1152 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
1157 1153 changeiter, getchange = walkchangerevs(ui, repo, (pats and cwd) or '',
1158 1154 pats, opts)
1159 1155 for st, rev, fns in changeiter:
1160 1156 if st == 'window':
1161 1157 du = dui(ui)
1162 1158 elif st == 'add':
1163 1159 du.bump(rev)
1164 1160 br = None
1165 1161 if opts['branch']:
1166 1162 br = repo.branchlookup([repo.changelog.node(rev)])
1167 1163 show_changeset(du, repo, rev, brinfo=br)
1168 1164 if opts['patch']:
1169 1165 changenode = repo.changelog.node(rev)
1170 1166 prev, other = repo.changelog.parents(changenode)
1171 1167 dodiff(du, du, repo, prev, changenode, fns)
1172 1168 du.write("\n\n")
1173 1169 elif st == 'iter':
1174 1170 for args in du.hunk[rev]:
1175 1171 ui.write(*args)
1176 1172
1177 1173 def manifest(ui, repo, rev=None):
1178 1174 """output the latest or given revision of the project manifest"""
1179 1175 if rev:
1180 1176 try:
1181 1177 # assume all revision numbers are for changesets
1182 1178 n = repo.lookup(rev)
1183 1179 change = repo.changelog.read(n)
1184 1180 n = change[0]
1185 1181 except hg.RepoError:
1186 1182 n = repo.manifest.lookup(rev)
1187 1183 else:
1188 1184 n = repo.manifest.tip()
1189 1185 m = repo.manifest.read(n)
1190 1186 mf = repo.manifest.readflags(n)
1191 1187 files = m.keys()
1192 1188 files.sort()
1193 1189
1194 1190 for f in files:
1195 1191 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
1196 1192
1197 1193 def outgoing(ui, repo, dest="default-push", **opts):
1198 1194 """show changesets not found in destination"""
1199 1195 dest = ui.expandpath(dest)
1200 1196 other = hg.repository(ui, dest)
1201 1197 o = repo.findoutgoing(other)
1202 1198 o = repo.newer(o)
1203 1199 o.reverse()
1204 1200 for n in o:
1205 1201 show_changeset(ui, repo, changenode=n)
1206 1202 if opts['patch']:
1207 1203 prev = repo.changelog.parents(n)[0]
1208 1204 dodiff(ui, ui, repo, prev, n)
1209 1205 ui.write("\n")
1210 1206
1211 1207 def parents(ui, repo, rev=None):
1212 1208 """show the parents of the working dir or revision"""
1213 1209 if rev:
1214 1210 p = repo.changelog.parents(repo.lookup(rev))
1215 1211 else:
1216 1212 p = repo.dirstate.parents()
1217 1213
1218 1214 for n in p:
1219 1215 if n != nullid:
1220 1216 show_changeset(ui, repo, changenode=n)
1221 1217
1222 1218 def paths(ui, search=None):
1223 1219 """show definition of symbolic path names"""
1224 1220 try:
1225 1221 repo = hg.repository(ui=ui)
1226 1222 except hg.RepoError:
1227 1223 pass
1228 1224
1229 1225 if search:
1230 1226 for name, path in ui.configitems("paths"):
1231 1227 if name == search:
1232 1228 ui.write("%s\n" % path)
1233 1229 return
1234 1230 ui.warn("not found!\n")
1235 1231 return 1
1236 1232 else:
1237 1233 for name, path in ui.configitems("paths"):
1238 1234 ui.write("%s = %s\n" % (name, path))
1239 1235
1240 1236 def pull(ui, repo, source="default", **opts):
1241 1237 """pull changes from the specified source"""
1242 1238 source = ui.expandpath(source)
1243 1239 ui.status('pulling from %s\n' % (source))
1244 1240
1245 1241 if opts['ssh']:
1246 1242 ui.setconfig("ui", "ssh", opts['ssh'])
1247 1243 if opts['remotecmd']:
1248 1244 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1249 1245
1250 1246 other = hg.repository(ui, source)
1251 1247 r = repo.pull(other)
1252 1248 if not r:
1253 1249 if opts['update']:
1254 1250 return update(ui, repo)
1255 1251 else:
1256 1252 ui.status("(run 'hg update' to get a working copy)\n")
1257 1253
1258 1254 return r
1259 1255
1260 1256 def push(ui, repo, dest="default-push", force=False, ssh=None, remotecmd=None):
1261 1257 """push changes to the specified destination"""
1262 1258 dest = ui.expandpath(dest)
1263 1259 ui.status('pushing to %s\n' % (dest))
1264 1260
1265 1261 if ssh:
1266 1262 ui.setconfig("ui", "ssh", ssh)
1267 1263 if remotecmd:
1268 1264 ui.setconfig("ui", "remotecmd", remotecmd)
1269 1265
1270 1266 other = hg.repository(ui, dest)
1271 1267 r = repo.push(other, force)
1272 1268 return r
1273 1269
1274 1270 def rawcommit(ui, repo, *flist, **rc):
1275 1271 "raw commit interface"
1276 1272 if rc['text']:
1277 1273 ui.warn("Warning: -t and --text is deprecated,"
1278 1274 " please use -m or --message instead.\n")
1279 1275 message = rc['message'] or rc['text']
1280 1276 if not message and rc['logfile']:
1281 1277 try:
1282 1278 message = open(rc['logfile']).read()
1283 1279 except IOError:
1284 1280 pass
1285 1281 if not message and not rc['logfile']:
1286 1282 raise util.Abort("missing commit message")
1287 1283
1288 1284 files = relpath(repo, list(flist))
1289 1285 if rc['files']:
1290 1286 files += open(rc['files']).read().splitlines()
1291 1287
1292 1288 rc['parent'] = map(repo.lookup, rc['parent'])
1293 1289
1294 1290 try:
1295 1291 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
1296 1292 except ValueError, inst:
1297 1293 raise util.Abort(str(inst))
1298 1294
1299 1295 def recover(ui, repo):
1300 1296 """roll back an interrupted transaction"""
1301 1297 repo.recover()
1302 1298
1303 1299 def remove(ui, repo, pat, *pats, **opts):
1304 1300 """remove the specified files on the next commit"""
1305 1301 names = []
1306 1302 def okaytoremove(abs, rel, exact):
1307 1303 c, a, d, u = repo.changes(files = [abs])
1308 1304 reason = None
1309 1305 if c: reason = 'is modified'
1310 1306 elif a: reason = 'has been marked for add'
1311 1307 elif u: reason = 'not managed'
1312 1308 if reason and exact:
1313 1309 ui.warn('not removing %s: file %s\n' % (rel, reason))
1314 1310 else:
1315 1311 return True
1316 1312 for src, abs, rel, exact in walk(repo, (pat,) + pats, opts):
1317 1313 if okaytoremove(abs, rel, exact):
1318 1314 if not exact: ui.status('removing %s\n' % rel)
1319 1315 names.append(abs)
1320 1316 repo.remove(names)
1321 1317
1322 1318 def revert(ui, repo, *names, **opts):
1323 1319 """revert modified files or dirs back to their unmodified states"""
1324 1320 node = opts['rev'] and repo.lookup(opts['rev']) or \
1325 1321 repo.dirstate.parents()[0]
1326 1322 root = os.path.realpath(repo.root)
1327 1323
1328 1324 def trimpath(p):
1329 1325 p = os.path.realpath(p)
1330 1326 if p.startswith(root):
1331 1327 rest = p[len(root):]
1332 1328 if not rest:
1333 1329 return rest
1334 1330 if p.startswith(os.sep):
1335 1331 return rest[1:]
1336 1332 return p
1337 1333
1338 1334 relnames = map(trimpath, names or [os.getcwd()])
1339 1335 chosen = {}
1340 1336
1341 1337 def choose(name):
1342 1338 def body(name):
1343 1339 for r in relnames:
1344 1340 if not name.startswith(r):
1345 1341 continue
1346 1342 rest = name[len(r):]
1347 1343 if not rest:
1348 1344 return r, True
1349 1345 depth = rest.count(os.sep)
1350 1346 if not r:
1351 1347 if depth == 0 or not opts['nonrecursive']:
1352 1348 return r, True
1353 1349 elif rest[0] == os.sep:
1354 1350 if depth == 1 or not opts['nonrecursive']:
1355 1351 return r, True
1356 1352 return None, False
1357 1353 relname, ret = body(name)
1358 1354 if ret:
1359 1355 chosen[relname] = 1
1360 1356 return ret
1361 1357
1362 1358 r = repo.update(node, False, True, choose, False)
1363 1359 for n in relnames:
1364 1360 if n not in chosen:
1365 1361 ui.warn('error: no matches for %s\n' % n)
1366 1362 r = 1
1367 1363 sys.stdout.flush()
1368 1364 return r
1369 1365
1370 1366 def root(ui, repo):
1371 1367 """print the root (top) of the current working dir"""
1372 1368 ui.write(repo.root + "\n")
1373 1369
1374 1370 def serve(ui, repo, **opts):
1375 1371 """export the repository via HTTP"""
1376 1372
1377 1373 if opts["stdio"]:
1378 1374 fin, fout = sys.stdin, sys.stdout
1379 1375 sys.stdout = sys.stderr
1380 1376
1381 1377 def getarg():
1382 1378 argline = fin.readline()[:-1]
1383 1379 arg, l = argline.split()
1384 1380 val = fin.read(int(l))
1385 1381 return arg, val
1386 1382 def respond(v):
1387 1383 fout.write("%d\n" % len(v))
1388 1384 fout.write(v)
1389 1385 fout.flush()
1390 1386
1391 1387 lock = None
1392 1388
1393 1389 while 1:
1394 1390 cmd = fin.readline()[:-1]
1395 1391 if cmd == '':
1396 1392 return
1397 1393 if cmd == "heads":
1398 1394 h = repo.heads()
1399 1395 respond(" ".join(map(hex, h)) + "\n")
1400 1396 if cmd == "lock":
1401 1397 lock = repo.lock()
1402 1398 respond("")
1403 1399 if cmd == "unlock":
1404 1400 if lock:
1405 1401 lock.release()
1406 1402 lock = None
1407 1403 respond("")
1408 1404 elif cmd == "branches":
1409 1405 arg, nodes = getarg()
1410 1406 nodes = map(bin, nodes.split(" "))
1411 1407 r = []
1412 1408 for b in repo.branches(nodes):
1413 1409 r.append(" ".join(map(hex, b)) + "\n")
1414 1410 respond("".join(r))
1415 1411 elif cmd == "between":
1416 1412 arg, pairs = getarg()
1417 1413 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
1418 1414 r = []
1419 1415 for b in repo.between(pairs):
1420 1416 r.append(" ".join(map(hex, b)) + "\n")
1421 1417 respond("".join(r))
1422 1418 elif cmd == "changegroup":
1423 1419 nodes = []
1424 1420 arg, roots = getarg()
1425 1421 nodes = map(bin, roots.split(" "))
1426 1422
1427 1423 cg = repo.changegroup(nodes)
1428 1424 while 1:
1429 1425 d = cg.read(4096)
1430 1426 if not d:
1431 1427 break
1432 1428 fout.write(d)
1433 1429
1434 1430 fout.flush()
1435 1431
1436 1432 elif cmd == "addchangegroup":
1437 1433 if not lock:
1438 1434 respond("not locked")
1439 1435 continue
1440 1436 respond("")
1441 1437
1442 1438 r = repo.addchangegroup(fin)
1443 1439 respond("")
1444 1440
1445 1441 optlist = "name templates style address port ipv6 accesslog errorlog"
1446 1442 for o in optlist.split():
1447 1443 if opts[o]:
1448 1444 ui.setconfig("web", o, opts[o])
1449 1445
1450 1446 try:
1451 1447 httpd = hgweb.create_server(repo)
1452 1448 except socket.error, inst:
1453 1449 raise util.Abort('cannot start server: ' + inst.args[1])
1454 1450
1455 1451 if ui.verbose:
1456 1452 addr, port = httpd.socket.getsockname()
1457 1453 if addr == '0.0.0.0':
1458 1454 addr = socket.gethostname()
1459 1455 else:
1460 1456 try:
1461 1457 addr = socket.gethostbyaddr(addr)[0]
1462 1458 except socket.error:
1463 1459 pass
1464 1460 if port != 80:
1465 1461 ui.status('listening at http://%s:%d/\n' % (addr, port))
1466 1462 else:
1467 1463 ui.status('listening at http://%s/\n' % addr)
1468 1464 httpd.serve_forever()
1469 1465
1470 1466 def status(ui, repo, *pats, **opts):
1471 1467 '''show changed files in the working directory
1472 1468
1473 1469 M = modified
1474 1470 A = added
1475 1471 R = removed
1476 1472 ? = not tracked
1477 1473 '''
1478 1474
1479 1475 cwd = repo.getcwd()
1480 1476 files, matchfn, anypats = matchpats(repo, cwd, pats, opts)
1481 1477 (c, a, d, u) = [[util.pathto(cwd, x) for x in n]
1482 1478 for n in repo.changes(files=files, match=matchfn)]
1483 1479
1484 1480 changetypes = [('modified', 'M', c),
1485 1481 ('added', 'A', a),
1486 1482 ('removed', 'R', d),
1487 1483 ('unknown', '?', u)]
1488 1484
1489 1485 end = opts['print0'] and '\0' or '\n'
1490 1486
1491 1487 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
1492 1488 or changetypes):
1493 1489 if opts['no_status']:
1494 1490 format = "%%s%s" % end
1495 1491 else:
1496 1492 format = "%s %%s%s" % (char, end);
1497 1493
1498 1494 for f in changes:
1499 1495 ui.write(format % f)
1500 1496
1501 1497 def tag(ui, repo, name, rev=None, **opts):
1502 1498 """add a tag for the current tip or a given revision"""
1503 1499 if opts['text']:
1504 1500 ui.warn("Warning: -t and --text is deprecated,"
1505 1501 " please use -m or --message instead.\n")
1506 1502 if name == "tip":
1507 1503 raise util.Abort("the name 'tip' is reserved")
1508 1504 if rev:
1509 1505 r = hex(repo.lookup(rev))
1510 1506 else:
1511 1507 r = hex(repo.changelog.tip())
1512 1508
1513 1509 if name.find(revrangesep) >= 0:
1514 1510 raise util.Abort("'%s' cannot be used in a tag name" % revrangesep)
1515 1511
1516 1512 if opts['local']:
1517 1513 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
1518 1514 return
1519 1515
1520 1516 (c, a, d, u) = repo.changes()
1521 1517 for x in (c, a, d, u):
1522 1518 if ".hgtags" in x:
1523 1519 raise util.Abort("working copy of .hgtags is changed "
1524 1520 "(please commit .hgtags manually)")
1525 1521
1526 1522 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
1527 1523 if repo.dirstate.state(".hgtags") == '?':
1528 1524 repo.add([".hgtags"])
1529 1525
1530 1526 message = (opts['message'] or opts['text'] or
1531 1527 "Added tag %s for changeset %s" % (name, r))
1532 1528 try:
1533 1529 repo.commit([".hgtags"], message, opts['user'], opts['date'])
1534 1530 except ValueError, inst:
1535 1531 raise util.Abort(str(inst))
1536 1532
1537 1533 def tags(ui, repo):
1538 1534 """list repository tags"""
1539 1535
1540 1536 l = repo.tagslist()
1541 1537 l.reverse()
1542 1538 for t, n in l:
1543 1539 try:
1544 1540 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
1545 1541 except KeyError:
1546 1542 r = " ?:?"
1547 1543 ui.write("%-30s %s\n" % (t, r))
1548 1544
1549 1545 def tip(ui, repo):
1550 1546 """show the tip revision"""
1551 1547 n = repo.changelog.tip()
1552 1548 show_changeset(ui, repo, changenode=n)
1553 1549
1554 1550 def unbundle(ui, repo, fname):
1555 1551 """apply a changegroup file"""
1556 1552 f = urllib.urlopen(fname)
1557 1553
1558 1554 if f.read(4) != "HG10":
1559 1555 raise util.Abort("%s: not a Mercurial bundle file" % fname)
1560 1556
1561 1557 class bzread:
1562 1558 def __init__(self, f):
1563 1559 self.zd = bz2.BZ2Decompressor()
1564 1560 self.f = f
1565 1561 self.buf = ""
1566 1562 def read(self, l):
1567 1563 while l > len(self.buf):
1568 1564 r = self.f.read(4096)
1569 1565 if r:
1570 1566 self.buf += self.zd.decompress(r)
1571 1567 else:
1572 1568 break
1573 1569 d, self.buf = self.buf[:l], self.buf[l:]
1574 1570 return d
1575 1571
1576 1572 repo.addchangegroup(bzread(f))
1577 1573
1578 1574 def undo(ui, repo):
1579 1575 """undo the last commit or pull
1580 1576
1581 1577 Roll back the last pull or commit transaction on the
1582 1578 repository, restoring the project to its earlier state.
1583 1579
1584 1580 This command should be used with care. There is only one level of
1585 1581 undo and there is no redo.
1586 1582
1587 1583 This command is not intended for use on public repositories. Once
1588 1584 a change is visible for pull by other users, undoing it locally is
1589 1585 ineffective.
1590 1586 """
1591 1587 repo.undo()
1592 1588
1593 1589 def update(ui, repo, node=None, merge=False, clean=False, branch=None):
1594 1590 '''update or merge working directory
1595 1591
1596 1592 If there are no outstanding changes in the working directory and
1597 1593 there is a linear relationship between the current version and the
1598 1594 requested version, the result is the requested version.
1599 1595
1600 1596 Otherwise the result is a merge between the contents of the
1601 1597 current working directory and the requested version. Files that
1602 1598 changed between either parent are marked as changed for the next
1603 1599 commit and a commit must be performed before any further updates
1604 1600 are allowed.
1605 1601 '''
1606 1602 if branch:
1607 1603 br = repo.branchlookup(branch=branch)
1608 1604 found = []
1609 1605 for x in br:
1610 1606 if branch in br[x]:
1611 1607 found.append(x)
1612 1608 if len(found) > 1:
1613 1609 ui.warn("Found multiple heads for %s\n" % branch)
1614 1610 for x in found:
1615 1611 show_changeset(ui, repo, changenode=x, brinfo=br)
1616 1612 return 1
1617 1613 if len(found) == 1:
1618 1614 node = found[0]
1619 1615 ui.warn("Using head %s for branch %s\n" % (short(node), branch))
1620 1616 else:
1621 1617 ui.warn("branch %s not found\n" % (branch))
1622 1618 return 1
1623 1619 else:
1624 1620 node = node and repo.lookup(node) or repo.changelog.tip()
1625 1621 return repo.update(node, allow=merge, force=clean)
1626 1622
1627 1623 def verify(ui, repo):
1628 1624 """verify the integrity of the repository"""
1629 1625 return repo.verify()
1630 1626
1631 1627 # Command options and aliases are listed here, alphabetically
1632 1628
1633 1629 table = {
1634 1630 "^add":
1635 1631 (add,
1636 1632 [('I', 'include', [], 'include path in search'),
1637 1633 ('X', 'exclude', [], 'exclude path from search')],
1638 1634 "hg add [OPTION]... [FILE]..."),
1639 1635 "addremove":
1640 1636 (addremove,
1641 1637 [('I', 'include', [], 'include path in search'),
1642 1638 ('X', 'exclude', [], 'exclude path from search')],
1643 1639 "hg addremove [OPTION]... [FILE]..."),
1644 1640 "^annotate":
1645 1641 (annotate,
1646 1642 [('r', 'rev', '', 'revision'),
1647 1643 ('a', 'text', None, 'treat all files as text'),
1648 1644 ('u', 'user', None, 'show user'),
1649 1645 ('n', 'number', None, 'show revision number'),
1650 1646 ('c', 'changeset', None, 'show changeset'),
1651 1647 ('I', 'include', [], 'include path in search'),
1652 1648 ('X', 'exclude', [], 'exclude path from search')],
1653 1649 'hg annotate [OPTION]... FILE...'),
1654 1650 "bundle":
1655 1651 (bundle,
1656 1652 [],
1657 1653 'hg bundle FILE DEST'),
1658 1654 "cat":
1659 1655 (cat,
1660 1656 [('o', 'output', "", 'output to file')],
1661 1657 'hg cat [-o OUTFILE] FILE [REV]'),
1662 1658 "^clone":
1663 1659 (clone,
1664 1660 [('U', 'noupdate', None, 'skip update after cloning'),
1665 1661 ('e', 'ssh', "", 'ssh command'),
1666 1662 ('', 'remotecmd', "", 'remote hg command')],
1667 1663 'hg clone [OPTION]... SOURCE [DEST]'),
1668 1664 "^commit|ci":
1669 1665 (commit,
1670 1666 [('A', 'addremove', None, 'run add/remove during commit'),
1671 1667 ('I', 'include', [], 'include path in search'),
1672 1668 ('X', 'exclude', [], 'exclude path from search'),
1673 1669 ('m', 'message', "", 'commit message'),
1674 1670 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1675 1671 ('l', 'logfile', "", 'commit message file'),
1676 1672 ('d', 'date', "", 'date code'),
1677 1673 ('u', 'user', "", 'user')],
1678 1674 'hg commit [OPTION]... [FILE]...'),
1679 1675 "copy": (copy, [], 'hg copy SOURCE DEST'),
1680 1676 "debugcheckstate": (debugcheckstate, [], 'debugcheckstate'),
1681 1677 "debugconfig": (debugconfig, [], 'debugconfig'),
1682 1678 "debugstate": (debugstate, [], 'debugstate'),
1683 1679 "debugdata": (debugdata, [], 'debugdata FILE REV'),
1684 1680 "debugindex": (debugindex, [], 'debugindex FILE'),
1685 1681 "debugindexdot": (debugindexdot, [], 'debugindexdot FILE'),
1686 1682 "debugrename": (debugrename, [], 'debugrename FILE [REV]'),
1687 1683 "debugwalk":
1688 1684 (debugwalk,
1689 1685 [('I', 'include', [], 'include path in search'),
1690 1686 ('X', 'exclude', [], 'exclude path from search')],
1691 1687 'debugwalk [OPTION]... [FILE]...'),
1692 1688 "^diff":
1693 1689 (diff,
1694 1690 [('r', 'rev', [], 'revision'),
1695 1691 ('a', 'text', None, 'treat all files as text'),
1696 1692 ('I', 'include', [], 'include path in search'),
1697 1693 ('X', 'exclude', [], 'exclude path from search')],
1698 1694 'hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...'),
1699 1695 "^export":
1700 1696 (export,
1701 1697 [('o', 'output', "", 'output to file'),
1702 1698 ('a', 'text', None, 'treat all files as text')],
1703 1699 "hg export [-a] [-o OUTFILE] REV..."),
1704 1700 "forget":
1705 1701 (forget,
1706 1702 [('I', 'include', [], 'include path in search'),
1707 1703 ('X', 'exclude', [], 'exclude path from search')],
1708 1704 "hg forget [OPTION]... FILE..."),
1709 1705 "grep":
1710 1706 (grep,
1711 1707 [('0', 'print0', None, 'end fields with NUL'),
1712 1708 ('I', 'include', [], 'include path in search'),
1713 1709 ('X', 'exclude', [], 'include path in search'),
1714 1710 ('', 'all', None, 'print all revisions with matches'),
1715 1711 ('i', 'ignore-case', None, 'ignore case when matching'),
1716 1712 ('l', 'files-with-matches', None, 'print names of files and revs with matches'),
1717 1713 ('n', 'line-number', None, 'print line numbers'),
1718 1714 ('r', 'rev', [], 'search in revision rev'),
1719 1715 ('u', 'user', None, 'print user who made change')],
1720 1716 "hg grep [OPTION]... PATTERN [FILE]..."),
1721 1717 "heads":
1722 1718 (heads,
1723 1719 [('b', 'branches', None, 'find branch info')],
1724 1720 'hg heads [-b]'),
1725 1721 "help": (help_, [], 'hg help [COMMAND]'),
1726 1722 "identify|id": (identify, [], 'hg identify'),
1727 1723 "import|patch":
1728 1724 (import_,
1729 1725 [('p', 'strip', 1, 'path strip'),
1730 1726 ('f', 'force', None, 'skip check for outstanding changes'),
1731 1727 ('b', 'base', "", 'base path')],
1732 1728 "hg import [-f] [-p NUM] [-b BASE] PATCH..."),
1733 1729 "incoming|in": (incoming,
1734 1730 [('p', 'patch', None, 'show patch')],
1735 1731 'hg incoming [-p] [SOURCE]'),
1736 1732 "^init": (init, [], 'hg init [DEST]'),
1737 1733 "locate":
1738 1734 (locate,
1739 1735 [('r', 'rev', '', 'revision'),
1740 1736 ('0', 'print0', None, 'end filenames with NUL'),
1741 1737 ('f', 'fullpath', None, 'print complete paths'),
1742 1738 ('I', 'include', [], 'include path in search'),
1743 1739 ('X', 'exclude', [], 'exclude path from search')],
1744 1740 'hg locate [OPTION]... [PATTERN]...'),
1745 1741 "^log|history":
1746 1742 (log,
1747 1743 [('I', 'include', [], 'include path in search'),
1748 1744 ('X', 'exclude', [], 'exclude path from search'),
1749 1745 ('b', 'branch', None, 'show branches'),
1750 1746 ('r', 'rev', [], 'revision'),
1751 1747 ('p', 'patch', None, 'show patch')],
1752 1748 'hg log [-I] [-X] [-r REV]... [-p] [FILE]'),
1753 1749 "manifest": (manifest, [], 'hg manifest [REV]'),
1754 1750 "outgoing|out": (outgoing,
1755 1751 [('p', 'patch', None, 'show patch')],
1756 1752 'hg outgoing [-p] [DEST]'),
1757 1753 "parents": (parents, [], 'hg parents [REV]'),
1758 1754 "paths": (paths, [], 'hg paths [NAME]'),
1759 1755 "^pull":
1760 1756 (pull,
1761 1757 [('u', 'update', None, 'update working directory'),
1762 1758 ('e', 'ssh', "", 'ssh command'),
1763 1759 ('', 'remotecmd', "", 'remote hg command')],
1764 1760 'hg pull [-u] [-e FILE] [--remotecmd FILE] [SOURCE]'),
1765 1761 "^push":
1766 1762 (push,
1767 1763 [('f', 'force', None, 'force push'),
1768 1764 ('e', 'ssh', "", 'ssh command'),
1769 1765 ('', 'remotecmd', "", 'remote hg command')],
1770 1766 'hg push [-f] [-e FILE] [--remotecmd FILE] [DEST]'),
1771 1767 "rawcommit":
1772 1768 (rawcommit,
1773 1769 [('p', 'parent', [], 'parent'),
1774 1770 ('d', 'date', "", 'date code'),
1775 1771 ('u', 'user', "", 'user'),
1776 1772 ('F', 'files', "", 'file list'),
1777 1773 ('m', 'message', "", 'commit message'),
1778 1774 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1779 1775 ('l', 'logfile', "", 'commit message file')],
1780 1776 'hg rawcommit [OPTION]... [FILE]...'),
1781 1777 "recover": (recover, [], "hg recover"),
1782 1778 "^remove|rm": (remove,
1783 1779 [('I', 'include', [], 'include path in search'),
1784 1780 ('X', 'exclude', [], 'exclude path from search')],
1785 1781 "hg remove [OPTION]... FILE..."),
1786 1782 "^revert":
1787 1783 (revert,
1788 1784 [("n", "nonrecursive", None, "don't recurse into subdirs"),
1789 1785 ("r", "rev", "", "revision")],
1790 1786 "hg revert [-n] [-r REV] [NAME]..."),
1791 1787 "root": (root, [], "hg root"),
1792 1788 "^serve":
1793 1789 (serve,
1794 1790 [('A', 'accesslog', '', 'access log file'),
1795 1791 ('E', 'errorlog', '', 'error log file'),
1796 1792 ('p', 'port', 0, 'listen port'),
1797 1793 ('a', 'address', '', 'interface address'),
1798 1794 ('n', 'name', "", 'repository name'),
1799 1795 ('', 'stdio', None, 'for remote clients'),
1800 1796 ('t', 'templates', "", 'template directory'),
1801 1797 ('', 'style', "", 'template style'),
1802 1798 ('6', 'ipv6', None, 'use IPv6 in addition to IPv4')],
1803 1799 "hg serve [OPTION]..."),
1804 1800 "^status":
1805 1801 (status,
1806 1802 [('m', 'modified', None, 'show only modified files'),
1807 1803 ('a', 'added', None, 'show only added files'),
1808 1804 ('r', 'removed', None, 'show only removed files'),
1809 1805 ('u', 'unknown', None, 'show only unknown (not tracked) files'),
1810 1806 ('n', 'no-status', None, 'hide status prefix'),
1811 1807 ('0', 'print0', None, 'end filenames with NUL'),
1812 1808 ('I', 'include', [], 'include path in search'),
1813 1809 ('X', 'exclude', [], 'exclude path from search')],
1814 1810 "hg status [OPTION]... [FILE]..."),
1815 1811 "tag":
1816 1812 (tag,
1817 1813 [('l', 'local', None, 'make the tag local'),
1818 1814 ('m', 'message', "", 'commit message'),
1819 1815 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1820 1816 ('d', 'date', "", 'date code'),
1821 1817 ('u', 'user', "", 'user')],
1822 1818 'hg tag [OPTION]... NAME [REV]'),
1823 1819 "tags": (tags, [], 'hg tags'),
1824 1820 "tip": (tip, [], 'hg tip'),
1825 1821 "unbundle":
1826 1822 (unbundle,
1827 1823 [],
1828 1824 'hg unbundle FILE'),
1829 1825 "undo": (undo, [], 'hg undo'),
1830 1826 "^update|up|checkout|co":
1831 1827 (update,
1832 1828 [('b', 'branch', "", 'checkout the head of a specific branch'),
1833 1829 ('m', 'merge', None, 'allow merging of conflicts'),
1834 1830 ('C', 'clean', None, 'overwrite locally modified files')],
1835 1831 'hg update [-b TAG] [-m] [-C] [REV]'),
1836 1832 "verify": (verify, [], 'hg verify'),
1837 1833 "version": (show_version, [], 'hg version'),
1838 1834 }
1839 1835
1840 1836 globalopts = [
1841 1837 ('R', 'repository', "", 'repository root directory'),
1842 1838 ('', 'cwd', '', 'change working directory'),
1843 1839 ('y', 'noninteractive', None, 'run non-interactively'),
1844 1840 ('q', 'quiet', None, 'quiet mode'),
1845 1841 ('v', 'verbose', None, 'verbose mode'),
1846 1842 ('', 'debug', None, 'debug mode'),
1847 1843 ('', 'debugger', None, 'start debugger'),
1848 1844 ('', 'traceback', None, 'print traceback on exception'),
1849 1845 ('', 'time', None, 'time how long the command takes'),
1850 1846 ('', 'profile', None, 'profile'),
1851 1847 ('', 'version', None, 'output version information and exit'),
1852 1848 ('h', 'help', None, 'display help and exit'),
1853 1849 ]
1854 1850
1855 1851 norepo = ("clone init version help debugconfig debugdata"
1856 1852 " debugindex debugindexdot paths")
1857 1853
1858 1854 def find(cmd):
1859 1855 for e in table.keys():
1860 1856 if re.match("(%s)$" % e, cmd):
1861 1857 return e, table[e]
1862 1858
1863 1859 raise UnknownCommand(cmd)
1864 1860
1865 1861 class SignalInterrupt(Exception):
1866 1862 """Exception raised on SIGTERM and SIGHUP."""
1867 1863
1868 1864 def catchterm(*args):
1869 1865 raise SignalInterrupt
1870 1866
1871 1867 def run():
1872 1868 sys.exit(dispatch(sys.argv[1:]))
1873 1869
1874 1870 class ParseError(Exception):
1875 1871 """Exception raised on errors in parsing the command line."""
1876 1872
1877 1873 def parse(args):
1878 1874 options = {}
1879 1875 cmdoptions = {}
1880 1876
1881 1877 try:
1882 1878 args = fancyopts.fancyopts(args, globalopts, options)
1883 1879 except fancyopts.getopt.GetoptError, inst:
1884 1880 raise ParseError(None, inst)
1885 1881
1886 1882 if args:
1887 1883 cmd, args = args[0], args[1:]
1888 1884 i = find(cmd)[1]
1889 1885 c = list(i[1])
1890 1886 else:
1891 1887 cmd = None
1892 1888 c = []
1893 1889
1894 1890 # combine global options into local
1895 1891 for o in globalopts:
1896 1892 c.append((o[0], o[1], options[o[1]], o[3]))
1897 1893
1898 1894 try:
1899 1895 args = fancyopts.fancyopts(args, c, cmdoptions)
1900 1896 except fancyopts.getopt.GetoptError, inst:
1901 1897 raise ParseError(cmd, inst)
1902 1898
1903 1899 # separate global options back out
1904 1900 for o in globalopts:
1905 1901 n = o[1]
1906 1902 options[n] = cmdoptions[n]
1907 1903 del cmdoptions[n]
1908 1904
1909 1905 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
1910 1906
1911 1907 def dispatch(args):
1912 1908 signal.signal(signal.SIGTERM, catchterm)
1913 1909 try:
1914 1910 signal.signal(signal.SIGHUP, catchterm)
1915 1911 except AttributeError:
1916 1912 pass
1917 1913
1918 1914 u = ui.ui()
1919 1915 external = []
1920 1916 for x in u.extensions():
1921 1917 if x[1]:
1922 1918 mod = imp.load_source(x[0], x[1])
1923 1919 else:
1924 1920 def importh(name):
1925 1921 mod = __import__(name)
1926 1922 components = name.split('.')
1927 1923 for comp in components[1:]:
1928 1924 mod = getattr(mod, comp)
1929 1925 return mod
1930 1926 mod = importh(x[0])
1931 1927 external.append(mod)
1932 1928 for x in external:
1933 1929 for t in x.cmdtable:
1934 1930 if t in table:
1935 1931 u.warn("module %s override %s\n" % (x.__name__, t))
1936 1932 table.update(x.cmdtable)
1937 1933
1938 1934 try:
1939 1935 cmd, func, args, options, cmdoptions = parse(args)
1940 1936 except ParseError, inst:
1941 1937 if inst.args[0]:
1942 1938 u.warn("hg %s: %s\n" % (inst.args[0], inst.args[1]))
1943 1939 help_(u, inst.args[0])
1944 1940 else:
1945 1941 u.warn("hg: %s\n" % inst.args[1])
1946 1942 help_(u, 'shortlist')
1947 1943 sys.exit(-1)
1948 1944 except UnknownCommand, inst:
1949 1945 u.warn("hg: unknown command '%s'\n" % inst.args[0])
1950 1946 help_(u, 'shortlist')
1951 1947 sys.exit(1)
1952 1948
1953 1949 if options["time"]:
1954 1950 def get_times():
1955 1951 t = os.times()
1956 1952 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
1957 1953 t = (t[0], t[1], t[2], t[3], time.clock())
1958 1954 return t
1959 1955 s = get_times()
1960 1956 def print_time():
1961 1957 t = get_times()
1962 1958 u.warn("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n" %
1963 1959 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
1964 1960 atexit.register(print_time)
1965 1961
1966 1962 u.updateopts(options["verbose"], options["debug"], options["quiet"],
1967 1963 not options["noninteractive"])
1968 1964
1969 1965 # enter the debugger before command execution
1970 1966 if options['debugger']:
1971 1967 pdb.set_trace()
1972 1968
1973 1969 try:
1974 1970 try:
1975 1971 if options['help']:
1976 1972 help_(u, cmd, options['version'])
1977 1973 sys.exit(0)
1978 1974 elif options['version']:
1979 1975 show_version(u)
1980 1976 sys.exit(0)
1981 1977 elif not cmd:
1982 1978 help_(u, 'shortlist')
1983 1979 sys.exit(0)
1984 1980
1985 1981 if options['cwd']:
1986 1982 try:
1987 1983 os.chdir(options['cwd'])
1988 1984 except OSError, inst:
1989 1985 raise util.Abort('%s: %s' %
1990 1986 (options['cwd'], inst.strerror))
1991 1987
1992 1988 if cmd not in norepo.split():
1993 1989 path = options["repository"] or ""
1994 1990 repo = hg.repository(ui=u, path=path)
1995 1991 for x in external:
1996 1992 x.reposetup(u, repo)
1997 1993 d = lambda: func(u, repo, *args, **cmdoptions)
1998 1994 else:
1999 1995 d = lambda: func(u, *args, **cmdoptions)
2000 1996
2001 1997 if options['profile']:
2002 1998 import hotshot, hotshot.stats
2003 1999 prof = hotshot.Profile("hg.prof")
2004 2000 r = prof.runcall(d)
2005 2001 prof.close()
2006 2002 stats = hotshot.stats.load("hg.prof")
2007 2003 stats.strip_dirs()
2008 2004 stats.sort_stats('time', 'calls')
2009 2005 stats.print_stats(40)
2010 2006 return r
2011 2007 else:
2012 2008 return d()
2013 2009 except:
2014 2010 # enter the debugger when we hit an exception
2015 2011 if options['debugger']:
2016 2012 pdb.post_mortem(sys.exc_info()[2])
2017 2013 if options['traceback']:
2018 2014 traceback.print_exc()
2019 2015 raise
2020 2016 except hg.RepoError, inst:
2021 2017 u.warn("abort: ", inst, "!\n")
2022 2018 except revlog.RevlogError, inst:
2023 2019 u.warn("abort: ", inst, "!\n")
2024 2020 except SignalInterrupt:
2025 2021 u.warn("killed!\n")
2026 2022 except KeyboardInterrupt:
2027 2023 try:
2028 2024 u.warn("interrupted!\n")
2029 2025 except IOError, inst:
2030 2026 if inst.errno == errno.EPIPE:
2031 2027 if u.debugflag:
2032 2028 u.warn("\nbroken pipe\n")
2033 2029 else:
2034 2030 raise
2035 2031 except IOError, inst:
2036 2032 if hasattr(inst, "code"):
2037 2033 u.warn("abort: %s\n" % inst)
2038 2034 elif hasattr(inst, "reason"):
2039 2035 u.warn("abort: error: %s\n" % inst.reason[1])
2040 2036 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
2041 2037 if u.debugflag:
2042 2038 u.warn("broken pipe\n")
2043 2039 else:
2044 2040 raise
2045 2041 except OSError, inst:
2046 2042 if hasattr(inst, "filename"):
2047 2043 u.warn("abort: %s: %s\n" % (inst.strerror, inst.filename))
2048 2044 else:
2049 2045 u.warn("abort: %s\n" % inst.strerror)
2050 2046 except util.Abort, inst:
2051 2047 u.warn('abort: ', inst.args[0] % inst.args[1:], '\n')
2052 2048 sys.exit(1)
2053 2049 except TypeError, inst:
2054 2050 # was this an argument error?
2055 2051 tb = traceback.extract_tb(sys.exc_info()[2])
2056 2052 if len(tb) > 2: # no
2057 2053 raise
2058 2054 u.debug(inst, "\n")
2059 2055 u.warn("%s: invalid arguments\n" % cmd)
2060 2056 help_(u, cmd)
2061 2057 except UnknownCommand, inst:
2062 2058 u.warn("hg: unknown command '%s'\n" % inst.args[0])
2063 2059 help_(u, 'shortlist')
2064 2060 except SystemExit:
2065 2061 # don't catch this in the catch-all below
2066 2062 raise
2067 2063 except:
2068 2064 u.warn("** unknown exception encountered, details follow\n")
2069 2065 u.warn("** report bug details to mercurial@selenic.com\n")
2070 2066 raise
2071 2067
2072 2068 sys.exit(-1)
@@ -1,407 +1,449 b''
1 1 """
2 2 util.py - Mercurial utility functions and platform specfic implementations
3 3
4 4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 5
6 6 This software may be used and distributed according to the terms
7 7 of the GNU General Public License, incorporated herein by reference.
8 8
9 9 This contains helper routines that are independent of the SCM core and hide
10 10 platform-specific details from the core.
11 11 """
12 12
13 13 import os, errno
14 14 from demandload import *
15 demandload(globals(), "re cStringIO")
15 demandload(globals(), "re cStringIO shutil")
16 16
17 17 def binary(s):
18 18 """return true if a string is binary data using diff's heuristic"""
19 19 if s and '\0' in s[:4096]:
20 20 return True
21 21 return False
22 22
23 23 def unique(g):
24 24 """return the uniq elements of iterable g"""
25 25 seen = {}
26 26 for f in g:
27 27 if f not in seen:
28 28 seen[f] = 1
29 29 yield f
30 30
31 31 class Abort(Exception):
32 32 """Raised if a command needs to print an error and exit."""
33 33
34 34 def always(fn): return True
35 35 def never(fn): return False
36 36
37 37 def globre(pat, head='^', tail='$'):
38 38 "convert a glob pattern into a regexp"
39 39 i, n = 0, len(pat)
40 40 res = ''
41 41 group = False
42 42 def peek(): return i < n and pat[i]
43 43 while i < n:
44 44 c = pat[i]
45 45 i = i+1
46 46 if c == '*':
47 47 if peek() == '*':
48 48 i += 1
49 49 res += '.*'
50 50 else:
51 51 res += '[^/]*'
52 52 elif c == '?':
53 53 res += '.'
54 54 elif c == '[':
55 55 j = i
56 56 if j < n and pat[j] in '!]':
57 57 j += 1
58 58 while j < n and pat[j] != ']':
59 59 j += 1
60 60 if j >= n:
61 61 res += '\\['
62 62 else:
63 63 stuff = pat[i:j].replace('\\','\\\\')
64 64 i = j + 1
65 65 if stuff[0] == '!':
66 66 stuff = '^' + stuff[1:]
67 67 elif stuff[0] == '^':
68 68 stuff = '\\' + stuff
69 69 res = '%s[%s]' % (res, stuff)
70 70 elif c == '{':
71 71 group = True
72 72 res += '(?:'
73 73 elif c == '}' and group:
74 74 res += ')'
75 75 group = False
76 76 elif c == ',' and group:
77 77 res += '|'
78 78 else:
79 79 res += re.escape(c)
80 80 return head + res + tail
81 81
82 82 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
83 83
84 84 def pathto(n1, n2):
85 85 '''return the relative path from one place to another.
86 86 this returns a path in the form used by the local filesystem, not hg.'''
87 87 if not n1: return localpath(n2)
88 88 a, b = n1.split('/'), n2.split('/')
89 89 a.reverse(), b.reverse()
90 90 while a and b and a[-1] == b[-1]:
91 91 a.pop(), b.pop()
92 92 b.reverse()
93 93 return os.sep.join((['..'] * len(a)) + b)
94 94
95 95 def canonpath(root, cwd, myname):
96 96 """return the canonical path of myname, given cwd and root"""
97 97 rootsep = root + os.sep
98 98 name = myname
99 99 if not name.startswith(os.sep):
100 100 name = os.path.join(root, cwd, name)
101 101 name = os.path.normpath(name)
102 102 if name.startswith(rootsep):
103 103 return pconvert(name[len(rootsep):])
104 104 elif name == root:
105 105 return ''
106 106 else:
107 107 raise Abort('%s not under root' % myname)
108 108
109 109 def matcher(canonroot, cwd, names, inc, exc, head=''):
110 110 """build a function to match a set of file patterns
111 111
112 112 arguments:
113 113 canonroot - the canonical root of the tree you're matching against
114 114 cwd - the current working directory, if relevant
115 115 names - patterns to find
116 116 inc - patterns to include
117 117 exc - patterns to exclude
118 118 head - a regex to prepend to patterns to control whether a match is rooted
119 119
120 120 a pattern is one of:
121 121 're:<regex>'
122 122 'glob:<shellglob>'
123 123 'path:<explicit path>'
124 124 'relpath:<relative path>'
125 125 '<relative path>'
126 126
127 127 returns:
128 128 a 3-tuple containing
129 129 - list of explicit non-pattern names passed in
130 130 - a bool match(filename) function
131 131 - a bool indicating if any patterns were passed in
132 132
133 133 todo:
134 134 make head regex a rooted bool
135 135 """
136 136
137 137 def patkind(name):
138 138 for prefix in 're:', 'glob:', 'path:', 'relpath:':
139 139 if name.startswith(prefix): return name.split(':', 1)
140 140 for c in name:
141 141 if c in _globchars: return 'glob', name
142 142 return 'relpath', name
143 143
144 144 def regex(kind, name, tail):
145 145 '''convert a pattern into a regular expression'''
146 146 if kind == 're':
147 147 return name
148 148 elif kind == 'path':
149 149 return '^' + re.escape(name) + '(?:/|$)'
150 150 elif kind == 'relpath':
151 151 return head + re.escape(name) + tail
152 152 return head + globre(name, '', tail)
153 153
154 154 def matchfn(pats, tail):
155 155 """build a matching function from a set of patterns"""
156 156 if pats:
157 157 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
158 158 return re.compile(pat).match
159 159
160 160 def globprefix(pat):
161 161 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
162 162 root = []
163 163 for p in pat.split(os.sep):
164 164 if patkind(p)[0] == 'glob': break
165 165 root.append(p)
166 166 return '/'.join(root)
167 167
168 168 pats = []
169 169 files = []
170 170 roots = []
171 171 for kind, name in map(patkind, names):
172 172 if kind in ('glob', 'relpath'):
173 173 name = canonpath(canonroot, cwd, name)
174 174 if name == '':
175 175 kind, name = 'glob', '**'
176 176 if kind in ('glob', 'path', 're'):
177 177 pats.append((kind, name))
178 178 if kind == 'glob':
179 179 root = globprefix(name)
180 180 if root: roots.append(root)
181 181 elif kind == 'relpath':
182 182 files.append((kind, name))
183 183 roots.append(name)
184 184
185 185 patmatch = matchfn(pats, '$') or always
186 186 filematch = matchfn(files, '(?:/|$)') or always
187 187 incmatch = always
188 188 if inc:
189 189 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
190 190 excmatch = lambda fn: False
191 191 if exc:
192 192 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
193 193
194 194 return (roots,
195 195 lambda fn: (incmatch(fn) and not excmatch(fn) and
196 196 (fn.endswith('/') or
197 197 (not pats and not files) or
198 198 (pats and patmatch(fn)) or
199 199 (files and filematch(fn)))),
200 200 (inc or exc or (pats and pats != [('glob', '**')])) and True)
201 201
202 202 def system(cmd, errprefix=None):
203 203 """execute a shell command that must succeed"""
204 204 rc = os.system(cmd)
205 205 if rc:
206 206 errmsg = "%s %s" % (os.path.basename(cmd.split(None, 1)[0]),
207 207 explain_exit(rc)[0])
208 208 if errprefix:
209 209 errmsg = "%s: %s" % (errprefix, errmsg)
210 210 raise Abort(errmsg)
211 211
212 212 def rename(src, dst):
213 213 """forcibly rename a file"""
214 214 try:
215 215 os.rename(src, dst)
216 216 except:
217 217 os.unlink(dst)
218 218 os.rename(src, dst)
219 219
220 def copyfiles(src, dst, copyfile):
221 """Copy a directory tree, files are copied using 'copyfile'."""
220 def copyfiles(src, dst, hardlink=None):
221 """Copy a directory tree using hardlinks if possible"""
222
223 if hardlink is None:
224 hardlink = (os.stat(src).st_dev ==
225 os.stat(os.path.dirname(dst)).st_dev)
222 226
223 227 if os.path.isdir(src):
224 228 os.mkdir(dst)
225 229 for name in os.listdir(src):
226 230 srcname = os.path.join(src, name)
227 231 dstname = os.path.join(dst, name)
228 copyfiles(srcname, dstname, copyfile)
232 copyfiles(srcname, dstname, hardlink)
229 233 else:
230 copyfile(src, dst)
234 if hardlink:
235 try:
236 os_link(src, dst)
237 except:
238 hardlink = False
239 shutil.copy2(src, dst)
240 else:
241 shutil.copy2(src, dst)
231 242
232 243 def opener(base):
233 244 """
234 245 return a function that opens files relative to base
235 246
236 247 this function is used to hide the details of COW semantics and
237 248 remote file access from higher level code.
238 249 """
239 250 p = base
240 251 def o(path, mode="r"):
241 252 f = os.path.join(p, path)
242 253
243 254 mode += "b" # for that other OS
244 255
245 256 if mode[0] != "r":
246 257 try:
247 s = os.stat(f)
258 nlink = nlinks(f)
248 259 except OSError:
249 260 d = os.path.dirname(f)
250 261 if not os.path.isdir(d):
251 262 os.makedirs(d)
252 263 else:
253 if s.st_nlink > 1:
264 if nlink > 1:
254 265 file(f + ".tmp", "wb").write(file(f, "rb").read())
255 266 rename(f+".tmp", f)
256 267
257 268 return file(f, mode)
258 269
259 270 return o
260 271
261 272 def _makelock_file(info, pathname):
262 273 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
263 274 os.write(ld, info)
264 275 os.close(ld)
265 276
266 277 def _readlock_file(pathname):
267 278 return file(pathname).read()
268 279
280 def nlinks(pathname):
281 """Return number of hardlinks for the given file."""
282 return os.stat(pathname).st_nlink
283
284 if hasattr(os, 'link'):
285 os_link = os.link
286 else:
287 def os_link(src, dst):
288 raise OSError(0, "Hardlinks not supported")
289
269 290 # Platform specific variants
270 291 if os.name == 'nt':
271 292 nulldev = 'NUL:'
272 293
294 try: # ActivePython can create hard links using win32file module
295 import win32file
296
297 def os_link(src, dst): # NB will only succeed on NTFS
298 win32file.CreateHardLink(dst, src)
299
300 def nlinks(pathname):
301 """Return number of hardlinks for the given file."""
302 try:
303 fh = win32file.CreateFile(pathname,
304 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
305 None, win32file.OPEN_EXISTING, 0, None)
306 res = win32file.GetFileInformationByHandle(fh)
307 fh.Close()
308 return res[7]
309 except:
310 return os.stat(pathname).st_nlink
311
312 except ImportError:
313 pass
314
273 315 def is_exec(f, last):
274 316 return last
275 317
276 318 def set_exec(f, mode):
277 319 pass
278 320
279 321 def pconvert(path):
280 322 return path.replace("\\", "/")
281 323
282 324 def localpath(path):
283 325 return path.replace('/', '\\')
284 326
285 327 def normpath(path):
286 328 return pconvert(os.path.normpath(path))
287 329
288 330 makelock = _makelock_file
289 331 readlock = _readlock_file
290 332
291 333 def explain_exit(code):
292 334 return "exited with status %d" % code, code
293 335
294 336 else:
295 337 nulldev = '/dev/null'
296 338
297 339 def is_exec(f, last):
298 340 """check whether a file is executable"""
299 341 return (os.stat(f).st_mode & 0100 != 0)
300 342
301 343 def set_exec(f, mode):
302 344 s = os.stat(f).st_mode
303 345 if (s & 0100 != 0) == mode:
304 346 return
305 347 if mode:
306 348 # Turn on +x for every +r bit when making a file executable
307 349 # and obey umask.
308 350 umask = os.umask(0)
309 351 os.umask(umask)
310 352 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
311 353 else:
312 354 os.chmod(f, s & 0666)
313 355
314 356 def pconvert(path):
315 357 return path
316 358
317 359 def localpath(path):
318 360 return path
319 361
320 362 normpath = os.path.normpath
321 363
322 364 def makelock(info, pathname):
323 365 try:
324 366 os.symlink(info, pathname)
325 367 except OSError, why:
326 368 if why.errno == errno.EEXIST:
327 369 raise
328 370 else:
329 371 _makelock_file(info, pathname)
330 372
331 373 def readlock(pathname):
332 374 try:
333 375 return os.readlink(pathname)
334 376 except OSError, why:
335 377 if why.errno == errno.EINVAL:
336 378 return _readlock_file(pathname)
337 379 else:
338 380 raise
339 381
340 382 def explain_exit(code):
341 383 """return a 2-tuple (desc, code) describing a process's status"""
342 384 if os.WIFEXITED(code):
343 385 val = os.WEXITSTATUS(code)
344 386 return "exited with status %d" % val, val
345 387 elif os.WIFSIGNALED(code):
346 388 val = os.WTERMSIG(code)
347 389 return "killed by signal %d" % val, val
348 390 elif os.WIFSTOPPED(code):
349 391 val = os.WSTOPSIG(code)
350 392 return "stopped by signal %d" % val, val
351 393 raise ValueError("invalid exit code")
352 394
353 395 class chunkbuffer(object):
354 396 """Allow arbitrary sized chunks of data to be efficiently read from an
355 397 iterator over chunks of arbitrary size."""
356 398
357 399 def __init__(self, in_iter, targetsize = 2**16):
358 400 """in_iter is the iterator that's iterating over the input chunks.
359 401 targetsize is how big a buffer to try to maintain."""
360 402 self.in_iter = iter(in_iter)
361 403 self.buf = ''
362 404 self.targetsize = int(targetsize)
363 405 if self.targetsize <= 0:
364 406 raise ValueError("targetsize must be greater than 0, was %d" %
365 407 targetsize)
366 408 self.iterempty = False
367 409
368 410 def fillbuf(self):
369 411 """Ignore target size; read every chunk from iterator until empty."""
370 412 if not self.iterempty:
371 413 collector = cStringIO.StringIO()
372 414 collector.write(self.buf)
373 415 for ch in self.in_iter:
374 416 collector.write(ch)
375 417 self.buf = collector.getvalue()
376 418 self.iterempty = True
377 419
378 420 def read(self, l):
379 421 """Read L bytes of data from the iterator of chunks of data.
380 422 Returns less than L bytes if the iterator runs dry."""
381 423 if l > len(self.buf) and not self.iterempty:
382 424 # Clamp to a multiple of self.targetsize
383 425 targetsize = self.targetsize * ((l // self.targetsize) + 1)
384 426 collector = cStringIO.StringIO()
385 427 collector.write(self.buf)
386 428 collected = len(self.buf)
387 429 for chunk in self.in_iter:
388 430 collector.write(chunk)
389 431 collected += len(chunk)
390 432 if collected >= targetsize:
391 433 break
392 434 if collected < targetsize:
393 435 self.iterempty = True
394 436 self.buf = collector.getvalue()
395 437 s, self.buf = self.buf[:l], buffer(self.buf, l)
396 438 return s
397 439
398 440 def filechunkiter(f, size = 65536):
399 441 """Create a generator that produces all the data in the file size
400 442 (default 65536) bytes at a time. Chunks may be less than size
401 443 bytes if the chunk is the last chunk in the file, or the file is a
402 444 socket or some other type of file that sometimes reads less data
403 445 than is requested."""
404 446 s = f.read(size)
405 447 while len(s) >= 0:
406 448 yield s
407 449 s = f.read(size)
General Comments 0
You need to be logged in to leave comments. Login now