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