##// END OF EJS Templates
[PATCH] Get "hg serve" to print the URL being served...
mpm@selenic.com -
r603:bc5d058e default
parent child Browse files
Show More
@@ -1,1129 +1,1142 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 import os, re, sys, signal
9 9 import fancyopts, ui, hg, util
10 10 from demandload import *
11 demandload(globals(), "mdiff time hgweb traceback random signal errno version")
11 demandload(globals(), "mdiff time hgweb traceback random signal socket errno version")
12 12
13 13 class UnknownCommand(Exception): pass
14 14
15 15 def filterfiles(filters, files):
16 16 l = [ x for x in files if x in filters ]
17 17
18 18 for t in filters:
19 19 if t and t[-1] != "/": t += "/"
20 20 l += [ x for x in files if x.startswith(t) ]
21 21 return l
22 22
23 23 def relfilter(repo, files):
24 24 if os.getcwd() != repo.root:
25 25 p = os.getcwd()[len(repo.root) + 1: ]
26 26 return filterfiles([util.pconvert(p)], files)
27 27 return files
28 28
29 29 def relpath(repo, args):
30 30 if os.getcwd() != repo.root:
31 31 p = os.getcwd()[len(repo.root) + 1: ]
32 32 return [ util.pconvert(os.path.normpath(os.path.join(p, x)))
33 33 for x in args ]
34 34 return args
35 35
36 36 revrangesep = ':'
37 37
38 38 def revrange(ui, repo, revs = [], revlog = None):
39 39 if revlog is None:
40 40 revlog = repo.changelog
41 41 revcount = revlog.count()
42 42 def fix(val, defval):
43 43 if not val: return defval
44 44 try:
45 45 num = int(val)
46 46 if str(num) != val: raise ValueError
47 47 if num < 0: num += revcount
48 48 if not (0 <= num < revcount):
49 49 raise ValueError
50 50 except ValueError:
51 51 try:
52 52 num = repo.changelog.rev(repo.lookup(val))
53 53 except KeyError:
54 54 try:
55 55 num = revlog.rev(revlog.lookup(val))
56 56 except KeyError:
57 57 ui.warn('abort: invalid revision identifier %s\n' % val)
58 58 sys.exit(1)
59 59 return num
60 60 for spec in revs:
61 61 if spec.find(revrangesep) >= 0:
62 62 start, end = spec.split(revrangesep, 1)
63 63 start = fix(start, 0)
64 64 end = fix(end, revcount - 1)
65 65 if end > start:
66 66 end += 1
67 67 step = 1
68 68 else:
69 69 end -= 1
70 70 step = -1
71 71 for rev in xrange(start, end, step):
72 72 yield str(rev)
73 73 else:
74 74 yield spec
75 75
76 76 def dodiff(fp, ui, repo, files = None, node1 = None, node2 = None):
77 77 def date(c):
78 78 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
79 79
80 80 (c, a, d, u) = repo.changes(node1, node2, files)
81 81 if files:
82 82 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
83 83
84 84 if not c and not a and not d:
85 85 return
86 86
87 87 if node2:
88 88 change = repo.changelog.read(node2)
89 89 mmap2 = repo.manifest.read(change[0])
90 90 def read(f): return repo.file(f).read(mmap2[f])
91 91 date2 = date(change)
92 92 else:
93 93 date2 = time.asctime()
94 94 if not node1:
95 95 node1 = repo.dirstate.parents()[0]
96 96 def read(f): return repo.wfile(f).read()
97 97
98 98 if ui.quiet:
99 99 r = None
100 100 else:
101 101 hexfunc = ui.verbose and hg.hex or hg.short
102 102 r = [hexfunc(node) for node in [node1, node2] if node]
103 103
104 104 change = repo.changelog.read(node1)
105 105 mmap = repo.manifest.read(change[0])
106 106 date1 = date(change)
107 107
108 108 for f in c:
109 109 to = None
110 110 if f in mmap:
111 111 to = repo.file(f).read(mmap[f])
112 112 tn = read(f)
113 113 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
114 114 for f in a:
115 115 to = None
116 116 tn = read(f)
117 117 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
118 118 for f in d:
119 119 to = repo.file(f).read(mmap[f])
120 120 tn = None
121 121 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
122 122
123 123 def show_changeset(ui, repo, rev=0, changenode=None, filelog=None):
124 124 """show a single changeset or file revision"""
125 125 changelog = repo.changelog
126 126 if filelog:
127 127 log = filelog
128 128 filerev = rev
129 129 node = filenode = filelog.node(filerev)
130 130 changerev = filelog.linkrev(filenode)
131 131 changenode = changenode or changelog.node(changerev)
132 132 else:
133 133 log = changelog
134 134 changerev = rev
135 135 if changenode is None:
136 136 changenode = changelog.node(changerev)
137 137 elif not changerev:
138 138 rev = changerev = changelog.rev(changenode)
139 139 node = changenode
140 140
141 141 if ui.quiet:
142 142 ui.write("%d:%s\n" % (rev, hg.hex(node)))
143 143 return
144 144
145 145 changes = changelog.read(changenode)
146 146
147 147 parents = [(log.rev(parent), hg.hex(parent))
148 148 for parent in log.parents(node)
149 149 if ui.debugflag or parent != hg.nullid]
150 150 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
151 151 parents = []
152 152
153 153 if filelog:
154 154 ui.write("revision: %d:%s\n" % (filerev, hg.hex(filenode)))
155 155 for parent in parents:
156 156 ui.write("parent: %d:%s\n" % parent)
157 157 ui.status("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
158 158 else:
159 159 ui.write("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
160 160 for tag in repo.nodetags(changenode):
161 161 ui.status("tag: %s\n" % tag)
162 162 for parent in parents:
163 163 ui.write("parent: %d:%s\n" % parent)
164 164 ui.note("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
165 165 hg.hex(changes[0])))
166 166 ui.status("user: %s\n" % changes[1])
167 167 ui.status("date: %s\n" % time.asctime(
168 168 time.localtime(float(changes[2].split(' ')[0]))))
169 169 if ui.debugflag:
170 170 files = repo.changes(changelog.parents(changenode)[0], changenode)
171 171 for key, value in zip(["files:", "files+:", "files-:"], files):
172 172 if value:
173 173 ui.note("%-12s %s\n" % (key, " ".join(value)))
174 174 else:
175 175 ui.note("files: %s\n" % " ".join(changes[3]))
176 176 description = changes[4].strip()
177 177 if description:
178 178 if ui.verbose:
179 179 ui.status("description:\n")
180 180 ui.status(description)
181 181 ui.status("\n\n")
182 182 else:
183 183 ui.status("summary: %s\n" % description.splitlines()[0])
184 184 ui.status("\n")
185 185
186 186 def show_version(ui):
187 187 """output version and copyright information"""
188 188 ui.write("Mercurial version %s\n" % version.get_version())
189 189 ui.status(
190 190 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
191 191 "This is free software; see the source for copying conditions. "
192 192 "There is NO\nwarranty; "
193 193 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
194 194 )
195 195
196 196 def help(ui, cmd=None):
197 197 '''show help for a given command or all commands'''
198 198 if cmd:
199 199 try:
200 200 i = find(cmd)
201 201 ui.write("%s\n\n" % i[2])
202 202
203 203 if i[1]:
204 204 for s, l, d, c in i[1]:
205 205 opt=' '
206 206 if s: opt = opt + '-' + s + ' '
207 207 if l: opt = opt + '--' + l + ' '
208 208 if d: opt = opt + '(' + str(d) + ')'
209 209 ui.write(opt, "\n")
210 210 if c: ui.write(' %s\n' % c)
211 211 ui.write("\n")
212 212
213 213 ui.write(i[0].__doc__, "\n")
214 214 except UnknownCommand:
215 215 ui.warn("hg: unknown command %s\n" % cmd)
216 216 sys.exit(0)
217 217 else:
218 218 if ui.verbose:
219 219 show_version(ui)
220 220 ui.write('\n')
221 221 if ui.verbose:
222 222 ui.write('hg commands:\n\n')
223 223 else:
224 224 ui.write('basic hg commands (use "hg help -v" for more):\n\n')
225 225
226 226 h = {}
227 227 for c, e in table.items():
228 228 f = c.split("|")[0]
229 229 if not ui.verbose and not f.startswith("^"):
230 230 continue
231 231 if not ui.debugflag and f.startswith("debug"):
232 232 continue
233 233 f = f.lstrip("^")
234 234 d = ""
235 235 if e[0].__doc__:
236 236 d = e[0].__doc__.splitlines(0)[0].rstrip()
237 237 h[f] = d
238 238
239 239 fns = h.keys()
240 240 fns.sort()
241 241 m = max(map(len, fns))
242 242 for f in fns:
243 243 ui.write(' %-*s %s\n' % (m, f, h[f]))
244 244
245 245 # Commands start here, listed alphabetically
246 246
247 247 def add(ui, repo, file, *files):
248 248 '''add the specified files on the next commit'''
249 249 repo.add(relpath(repo, (file,) + files))
250 250
251 251 def addremove(ui, repo, *files):
252 252 """add all new files, delete all missing files"""
253 253 if files:
254 254 files = relpath(repo, files)
255 255 d = []
256 256 u = []
257 257 for f in files:
258 258 p = repo.wjoin(f)
259 259 s = repo.dirstate.state(f)
260 260 isfile = os.path.isfile(p)
261 261 if s != 'r' and not isfile:
262 262 d.append(f)
263 263 elif s not in 'nmai' and isfile:
264 264 u.append(f)
265 265 else:
266 266 (c, a, d, u) = repo.changes(None, None)
267 267 repo.add(u)
268 268 repo.remove(d)
269 269
270 270 def annotate(u, repo, file, *files, **ops):
271 271 """show changeset information per file line"""
272 272 def getnode(rev):
273 273 return hg.short(repo.changelog.node(rev))
274 274
275 275 def getname(rev):
276 276 try:
277 277 return bcache[rev]
278 278 except KeyError:
279 279 cl = repo.changelog.read(repo.changelog.node(rev))
280 280 name = cl[1]
281 281 f = name.find('@')
282 282 if f >= 0:
283 283 name = name[:f]
284 284 f = name.find('<')
285 285 if f >= 0:
286 286 name = name[f+1:]
287 287 bcache[rev] = name
288 288 return name
289 289
290 290 bcache = {}
291 291 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
292 292 if not ops['user'] and not ops['changeset']:
293 293 ops['number'] = 1
294 294
295 295 node = repo.dirstate.parents()[0]
296 296 if ops['revision']:
297 297 node = repo.changelog.lookup(ops['revision'])
298 298 change = repo.changelog.read(node)
299 299 mmap = repo.manifest.read(change[0])
300 300 for f in relpath(repo, (file,) + files):
301 301 lines = repo.file(f).annotate(mmap[f])
302 302 pieces = []
303 303
304 304 for o, f in opmap:
305 305 if ops[o]:
306 306 l = [ f(n) for n,t in lines ]
307 307 m = max(map(len, l))
308 308 pieces.append([ "%*s" % (m, x) for x in l])
309 309
310 310 for p,l in zip(zip(*pieces), lines):
311 311 u.write(" ".join(p) + ": " + l[1])
312 312
313 313 def cat(ui, repo, file, rev = []):
314 314 """output the latest or given revision of a file"""
315 315 r = repo.file(relpath(repo, [file])[0])
316 316 n = r.tip()
317 317 if rev: n = r.lookup(rev)
318 318 sys.stdout.write(r.read(n))
319 319
320 320 def clone(ui, source, dest = None, **opts):
321 321 """make a copy of an existing repository"""
322 322 source = ui.expandpath(source)
323 323
324 324 if dest is None:
325 325 dest = os.path.basename(os.path.normpath(source))
326 326
327 327 if os.path.exists(dest):
328 328 ui.warn("abort: destination '%s' already exists\n" % dest)
329 329 return 1
330 330
331 331 class dircleanup:
332 332 def __init__(self, dir):
333 333 self.dir = dir
334 334 os.mkdir(dir)
335 335 def close(self):
336 336 self.dir = None
337 337 def __del__(self):
338 338 if self.dir:
339 339 import shutil
340 340 shutil.rmtree(self.dir, True)
341 341
342 342 d = dircleanup(dest)
343 343
344 344 link = 0
345 345 abspath = source
346 346 if not (source.startswith("http://") or
347 347 source.startswith("hg://") or
348 348 source.startswith("old-http://")):
349 349 abspath = os.path.abspath(source)
350 350 d1 = os.stat(dest).st_dev
351 351 d2 = os.stat(source).st_dev
352 352 if d1 == d2: link = 1
353 353
354 354 if link:
355 355 ui.note("copying by hardlink\n")
356 356 util.system("cp -al '%s'/.hg '%s'/.hg" % (source, dest))
357 357 try:
358 358 os.remove(os.path.join(dest, ".hg", "dirstate"))
359 359 except: pass
360 360
361 361 repo = hg.repository(ui, dest)
362 362
363 363 else:
364 364 repo = hg.repository(ui, dest, create=1)
365 365 other = hg.repository(ui, source)
366 366 fetch = repo.findincoming(other)
367 367 if fetch:
368 368 cg = other.changegroup(fetch)
369 369 repo.addchangegroup(cg)
370 370
371 371 f = repo.opener("hgrc", "w")
372 372 f.write("[paths]\n")
373 373 f.write("default = %s\n" % abspath)
374 374
375 375 if not opts['noupdate']:
376 376 update(ui, repo)
377 377
378 378 d.close()
379 379
380 380 def commit(ui, repo, *files, **opts):
381 381 """commit the specified files or all outstanding changes"""
382 382 text = opts['text']
383 383 if not text and opts['logfile']:
384 384 try: text = open(opts['logfile']).read()
385 385 except IOError: pass
386 386
387 387 if opts['addremove']:
388 388 addremove(ui, repo, *files)
389 389 repo.commit(relpath(repo, files), text, opts['user'], opts['date'])
390 390
391 391 def copy(ui, repo, source, dest):
392 392 """mark a file as copied or renamed for the next commit"""
393 393 return repo.copy(*relpath(repo, (source, dest)))
394 394
395 395 def debugcheckstate(ui, repo):
396 396 """validate the correctness of the current dirstate"""
397 397 parent1, parent2 = repo.dirstate.parents()
398 398 repo.dirstate.read()
399 399 dc = repo.dirstate.map
400 400 keys = dc.keys()
401 401 keys.sort()
402 402 m1n = repo.changelog.read(parent1)[0]
403 403 m2n = repo.changelog.read(parent2)[0]
404 404 m1 = repo.manifest.read(m1n)
405 405 m2 = repo.manifest.read(m2n)
406 406 errors = 0
407 407 for f in dc:
408 408 state = repo.dirstate.state(f)
409 409 if state in "nr" and f not in m1:
410 410 ui.warn("%s in state %s, but not in manifest1\n" % (f, state))
411 411 errors += 1
412 412 if state in "a" and f in m1:
413 413 ui.warn("%s in state %s, but also in manifest1\n" % (f, state))
414 414 errors += 1
415 415 if state in "m" and f not in m1 and f not in m2:
416 416 ui.warn("%s in state %s, but not in either manifest\n" %
417 417 (f, state))
418 418 errors += 1
419 419 for f in m1:
420 420 state = repo.dirstate.state(f)
421 421 if state not in "nrm":
422 422 ui.warn("%s in manifest1, but listed as state %s" % (f, state))
423 423 errors += 1
424 424 if errors:
425 425 ui.warn(".hg/dirstate inconsistent with current parent's manifest\n")
426 426 sys.exit(1)
427 427
428 428 def debugstate(ui, repo):
429 429 """show the contents of the current dirstate"""
430 430 repo.dirstate.read()
431 431 dc = repo.dirstate.map
432 432 keys = dc.keys()
433 433 keys.sort()
434 434 for file in keys:
435 435 ui.write("%c %s\n" % (dc[file][0], file))
436 436
437 437 def debugindex(ui, file):
438 438 """dump the contents of an index file"""
439 439 r = hg.revlog(hg.opener(""), file, "")
440 440 ui.write(" rev offset length base linkrev" +
441 441 " p1 p2 nodeid\n")
442 442 for i in range(r.count()):
443 443 e = r.index[i]
444 444 ui.write("% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s..\n" % (
445 445 i, e[0], e[1], e[2], e[3],
446 446 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5])))
447 447
448 448 def debugindexdot(ui, file):
449 449 """dump an index DAG as a .dot file"""
450 450 r = hg.revlog(hg.opener(""), file, "")
451 451 ui.write("digraph G {\n")
452 452 for i in range(r.count()):
453 453 e = r.index[i]
454 454 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
455 455 if e[5] != hg.nullid:
456 456 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
457 457 ui.write("}\n")
458 458
459 459 def diff(ui, repo, *files, **opts):
460 460 """diff working directory (or selected files)"""
461 461 revs = []
462 462 if opts['rev']:
463 463 revs = map(lambda x: repo.lookup(x), opts['rev'])
464 464
465 465 if len(revs) > 2:
466 466 ui.warn("too many revisions to diff\n")
467 467 sys.exit(1)
468 468
469 469 if files:
470 470 files = relpath(repo, files)
471 471 else:
472 472 files = relpath(repo, [""])
473 473
474 474 dodiff(sys.stdout, ui, repo, files, *revs)
475 475
476 476 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
477 477 node = repo.lookup(changeset)
478 478 prev, other = repo.changelog.parents(node)
479 479 change = repo.changelog.read(node)
480 480
481 481 def expand(name):
482 482 expansions = {
483 483 '%': lambda: '%',
484 484 'H': lambda: hg.hex(node),
485 485 'N': lambda: str(total),
486 486 'R': lambda: str(repo.changelog.rev(node)),
487 487 'b': lambda: os.path.basename(repo.root),
488 488 'h': lambda: hg.short(node),
489 489 'n': lambda: str(seqno).zfill(len(str(total))),
490 490 'r': lambda: str(repo.changelog.rev(node)).zfill(revwidth),
491 491 }
492 492 newname = []
493 493 namelen = len(name)
494 494 i = 0
495 495 while i < namelen:
496 496 c = name[i]
497 497 if c == '%':
498 498 i += 1
499 499 c = name[i]
500 500 c = expansions[c]()
501 501 newname.append(c)
502 502 i += 1
503 503 return ''.join(newname)
504 504
505 505 if opts['output'] and opts['output'] != '-':
506 506 try:
507 507 fp = open(expand(opts['output']), 'w')
508 508 except KeyError, inst:
509 509 ui.warn("error: invalid format spec '%%%s' in output file name\n" %
510 510 inst.args[0])
511 511 sys.exit(1)
512 512 else:
513 513 fp = sys.stdout
514 514
515 515 fp.write("# HG changeset patch\n")
516 516 fp.write("# User %s\n" % change[1])
517 517 fp.write("# Node ID %s\n" % hg.hex(node))
518 518 fp.write("# Parent %s\n" % hg.hex(prev))
519 519 if other != hg.nullid:
520 520 fp.write("# Parent %s\n" % hg.hex(other))
521 521 fp.write(change[4].rstrip())
522 522 fp.write("\n\n")
523 523
524 524 dodiff(fp, ui, repo, None, prev, node)
525 525
526 526 def export(ui, repo, *changesets, **opts):
527 527 """dump the header and diffs for one or more changesets"""
528 528 seqno = 0
529 529 revs = list(revrange(ui, repo, changesets))
530 530 total = len(revs)
531 531 revwidth = max(len(revs[0]), len(revs[-1]))
532 532 for cset in revs:
533 533 seqno += 1
534 534 doexport(ui, repo, cset, seqno, total, revwidth, opts)
535 535
536 536 def forget(ui, repo, file, *files):
537 537 """don't add the specified files on the next commit"""
538 538 repo.forget(relpath(repo, (file,) + files))
539 539
540 540 def heads(ui, repo):
541 541 """show current repository heads"""
542 542 for n in repo.changelog.heads():
543 543 show_changeset(ui, repo, changenode=n)
544 544
545 545 def identify(ui, repo):
546 546 """print information about the working copy"""
547 547 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
548 548 if not parents:
549 549 ui.write("unknown\n")
550 550 return
551 551
552 552 hexfunc = ui.verbose and hg.hex or hg.short
553 553 (c, a, d, u) = repo.changes(None, None)
554 554 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
555 555 (c or a or d) and "+" or "")]
556 556
557 557 if not ui.quiet:
558 558 # multiple tags for a single parent separated by '/'
559 559 parenttags = ['/'.join(tags)
560 560 for tags in map(repo.nodetags, parents) if tags]
561 561 # tags for multiple parents separated by ' + '
562 562 output.append(' + '.join(parenttags))
563 563
564 564 ui.write("%s\n" % ' '.join(output))
565 565
566 566 def import_(ui, repo, patch1, *patches, **opts):
567 567 """import an ordered set of patches"""
568 568 try:
569 569 import psyco
570 570 psyco.full()
571 571 except:
572 572 pass
573 573
574 574 patches = (patch1,) + patches
575 575
576 576 d = opts["base"]
577 577 strip = opts["strip"]
578 578
579 579 for patch in patches:
580 580 ui.status("applying %s\n" % patch)
581 581 pf = os.path.join(d, patch)
582 582
583 583 text = ""
584 584 for l in file(pf):
585 585 if l[:4] == "--- ": break
586 586 text += l
587 587
588 588 # make sure text isn't empty
589 589 if not text: text = "imported patch %s\n" % patch
590 590
591 591 f = os.popen("patch -p%d < %s" % (strip, pf))
592 592 files = []
593 593 for l in f.read().splitlines():
594 594 l.rstrip('\r\n');
595 595 ui.status("%s\n" % l)
596 596 if l[:14] == 'patching file ':
597 597 pf = l[14:]
598 598 if pf not in files:
599 599 files.append(pf)
600 600 patcherr = f.close()
601 601 if patcherr:
602 602 sys.stderr.write("patch failed")
603 603 sys.exit(1)
604 604
605 605 if len(files) > 0:
606 606 addremove(ui, repo, *files)
607 607 repo.commit(files, text)
608 608
609 609 def init(ui, source=None):
610 610 """create a new repository in the current directory"""
611 611
612 612 if source:
613 613 ui.warn("no longer supported: use \"hg clone\" instead\n")
614 614 sys.exit(1)
615 615 repo = hg.repository(ui, ".", create=1)
616 616
617 617 def log(ui, repo, f=None, **opts):
618 618 """show the revision history of the repository or a single file"""
619 619 if f:
620 620 filelog = repo.file(relpath(repo, [f])[0])
621 621 log = filelog
622 622 lookup = filelog.lookup
623 623 else:
624 624 filelog = None
625 625 log = repo.changelog
626 626 lookup = repo.lookup
627 627 revlist = []
628 628 revs = [log.rev(lookup(rev)) for rev in opts['rev']]
629 629 while revs:
630 630 if len(revs) == 1:
631 631 revlist.append(revs.pop(0))
632 632 else:
633 633 a = revs.pop(0)
634 634 b = revs.pop(0)
635 635 off = a > b and -1 or 1
636 636 revlist.extend(range(a, b + off, off))
637 637 for i in revlist or range(log.count() - 1, -1, -1):
638 638 show_changeset(ui, repo, filelog=filelog, rev=i)
639 639
640 640 def manifest(ui, repo, rev = []):
641 641 """output the latest or given revision of the project manifest"""
642 642 n = repo.manifest.tip()
643 643 if rev:
644 644 n = repo.manifest.lookup(rev)
645 645 m = repo.manifest.read(n)
646 646 mf = repo.manifest.readflags(n)
647 647 files = m.keys()
648 648 files.sort()
649 649
650 650 for f in files:
651 651 ui.write("%40s %3s %s\n" % (hg.hex(m[f]), mf[f] and "755" or "644", f))
652 652
653 653 def parents(ui, repo, node = None):
654 654 '''show the parents of the current working dir'''
655 655 if node:
656 656 p = repo.changelog.parents(repo.lookup(hg.bin(node)))
657 657 else:
658 658 p = repo.dirstate.parents()
659 659
660 660 for n in p:
661 661 if n != hg.nullid:
662 662 show_changeset(ui, repo, changenode=n)
663 663
664 664 def pull(ui, repo, source="default", **opts):
665 665 """pull changes from the specified source"""
666 666 source = ui.expandpath(source)
667 667
668 668 ui.status('pulling from %s\n' % (source))
669 669
670 670 other = hg.repository(ui, source)
671 671 fetch = repo.findincoming(other)
672 672 if not fetch:
673 673 ui.status("no changes found\n")
674 674 return
675 675
676 676 cg = other.changegroup(fetch)
677 677 r = repo.addchangegroup(cg)
678 678 if cg and not r:
679 679 if opts['update']:
680 680 return update(ui, repo)
681 681 else:
682 682 ui.status("(run 'hg update' to get a working copy)\n")
683 683
684 684 return r
685 685
686 686 def push(ui, repo, dest="default-push"):
687 687 """push changes to the specified destination"""
688 688 dest = ui.expandpath(dest)
689 689
690 690 if not dest.startswith("ssh://"):
691 691 ui.warn("abort: can only push to ssh:// destinations currently\n")
692 692 return 1
693 693
694 694 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', dest)
695 695 if not m:
696 696 ui.warn("abort: couldn't parse destination %s\n" % dest)
697 697 return 1
698 698
699 699 user, host, port, path = map(m.group, (2, 3, 5, 7))
700 700 uhost = user and ("%s@%s" % (user, host)) or host
701 701 port = port and (" -p %s") % port or ""
702 702 path = path or ""
703 703
704 704 sport = random.randrange(30000, 60000)
705 705 cmd = "ssh %s%s -R %d:localhost:%d 'cd %s; hg pull http://localhost:%d/'"
706 706 cmd = cmd % (uhost, port, sport+1, sport, path, sport+1)
707 707
708 708 child = os.fork()
709 709 if not child:
710 710 sys.stdout = file("/dev/null", "w")
711 711 sys.stderr = sys.stdout
712 712 hgweb.server(repo.root, "pull", "", "localhost", sport)
713 713 else:
714 714 ui.status("connecting to %s\n" % host)
715 715 r = os.system(cmd)
716 716 os.kill(child, signal.SIGTERM)
717 717 return r
718 718
719 719 def rawcommit(ui, repo, *flist, **rc):
720 720 "raw commit interface"
721 721
722 722 text = rc['text']
723 723 if not text and rc['logfile']:
724 724 try: text = open(rc['logfile']).read()
725 725 except IOError: pass
726 726 if not text and not rc['logfile']:
727 727 ui.warn("abort: missing commit text\n")
728 728 return 1
729 729
730 730 files = relpath(repo, list(flist))
731 731 if rc['files']:
732 732 files += open(rc['files']).read().splitlines()
733 733
734 734 rc['parent'] = map(repo.lookup, rc['parent'])
735 735
736 736 repo.rawcommit(files, text, rc['user'], rc['date'], *rc['parent'])
737 737
738 738 def recover(ui, repo):
739 739 """roll back an interrupted transaction"""
740 740 repo.recover()
741 741
742 742 def remove(ui, repo, file, *files):
743 743 """remove the specified files on the next commit"""
744 744 repo.remove(relpath(repo, (file,) + files))
745 745
746 746 def revert(ui, repo, *names, **opts):
747 747 """revert modified files or dirs back to their unmodified states"""
748 748 node = opts['rev'] and repo.lookup(opts['rev']) or \
749 749 repo.dirstate.parents()[0]
750 750 root = os.path.realpath(repo.root)
751 751
752 752 def trimpath(p):
753 753 p = os.path.realpath(p)
754 754 if p.startswith(root):
755 755 rest = p[len(root):]
756 756 if not rest:
757 757 return rest
758 758 if p.startswith(os.sep):
759 759 return rest[1:]
760 760 return p
761 761
762 762 relnames = map(trimpath, names or [os.getcwd()])
763 763 chosen = {}
764 764
765 765 def choose(name):
766 766 def body(name):
767 767 for r in relnames:
768 768 if not name.startswith(r): continue
769 769 rest = name[len(r):]
770 770 if not rest: return r, True
771 771 depth = rest.count(os.sep)
772 772 if not r:
773 773 if depth == 0 or not opts['nonrecursive']: return r, True
774 774 elif rest[0] == os.sep:
775 775 if depth == 1 or not opts['nonrecursive']: return r, True
776 776 return None, False
777 777 relname, ret = body(name)
778 778 if ret:
779 779 chosen[relname] = 1
780 780 return ret
781 781
782 782 r = repo.update(node, False, True, choose, False)
783 783 for n in relnames:
784 784 if n not in chosen:
785 785 ui.warn('error: no matches for %s\n' % n)
786 786 r = 1
787 787 sys.stdout.flush()
788 788 return r
789 789
790 790 def root(ui, repo):
791 791 """print the root (top) of the current working dir"""
792 792 ui.write(repo.root + "\n")
793 793
794 794 def serve(ui, repo, **opts):
795 795 """export the repository via HTTP"""
796 hgweb.server(repo.root, opts["name"], opts["templates"],
796 httpd = hgweb.create_server(repo.root, opts["name"], opts["templates"],
797 797 opts["address"], opts["port"])
798 if ui.verbose:
799 addr, port = httpd.socket.getsockname()
800 if addr == '0.0.0.0':
801 addr = socket.gethostname()
802 else:
803 try:
804 addr = socket.gethostbyaddr(addr)[0]
805 except: pass
806 if port != 80:
807 ui.status('listening on http://%s:%d/\n' % (addr, port))
808 else:
809 ui.status('listening on http://%s/\n' % addr)
810 httpd.serve_forever()
798 811
799 812 def status(ui, repo):
800 813 '''show changed files in the working directory
801 814
802 815 C = changed
803 816 A = added
804 817 R = removed
805 818 ? = not tracked'''
806 819
807 820 (c, a, d, u) = repo.changes(None, None)
808 821 (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
809 822
810 823 for f in c: ui.write("C ", f, "\n")
811 824 for f in a: ui.write("A ", f, "\n")
812 825 for f in d: ui.write("R ", f, "\n")
813 826 for f in u: ui.write("? ", f, "\n")
814 827
815 828 def tag(ui, repo, name, rev = None, **opts):
816 829 """add a tag for the current tip or a given revision"""
817 830
818 831 if name == "tip":
819 832 ui.warn("abort: 'tip' is a reserved name!\n")
820 833 return -1
821 834 if name.find(revrangesep) >= 0:
822 835 ui.warn("abort: '%s' cannot be used in a tag name\n" % revrangesep)
823 836 return -1
824 837
825 838 (c, a, d, u) = repo.changes(None, None)
826 839 for x in (c, a, d, u):
827 840 if ".hgtags" in x:
828 841 ui.warn("abort: working copy of .hgtags is changed!\n")
829 842 ui.status("(please commit .hgtags manually)\n")
830 843 return -1
831 844
832 845 if rev:
833 846 r = hg.hex(repo.lookup(rev))
834 847 else:
835 848 r = hg.hex(repo.changelog.tip())
836 849
837 850 add = 0
838 851 if not os.path.exists(repo.wjoin(".hgtags")): add = 1
839 852 repo.wfile(".hgtags", "a").write("%s %s\n" % (r, name))
840 853 if add: repo.add([".hgtags"])
841 854
842 855 if not opts['text']:
843 856 opts['text'] = "Added tag %s for changeset %s" % (name, r)
844 857
845 858 repo.commit([".hgtags"], opts['text'], opts['user'], opts['date'])
846 859
847 860 def tags(ui, repo):
848 861 """list repository tags"""
849 862
850 863 l = repo.tagslist()
851 864 l.reverse()
852 865 for t, n in l:
853 866 try:
854 867 r = "%5d:%s" % (repo.changelog.rev(n), hg.hex(n))
855 868 except KeyError:
856 869 r = " ?:?"
857 870 ui.write("%-30s %s\n" % (t, r))
858 871
859 872 def tip(ui, repo):
860 873 """show the tip revision"""
861 874 n = repo.changelog.tip()
862 875 show_changeset(ui, repo, changenode=n)
863 876
864 877 def undo(ui, repo):
865 878 """undo the last commit or pull
866 879
867 880 Roll back the last pull or commit transaction on the
868 881 repository, restoring the project to its earlier state.
869 882
870 883 This command should be used with care. There is only one level of
871 884 undo and there is no redo.
872 885
873 886 This command is not intended for use on public repositories. Once
874 887 a change is visible for pull by other users, undoing it locally is
875 888 ineffective.
876 889 """
877 890 repo.undo()
878 891
879 892 def update(ui, repo, node=None, merge=False, clean=False):
880 893 '''update or merge working directory
881 894
882 895 If there are no outstanding changes in the working directory and
883 896 there is a linear relationship between the current version and the
884 897 requested version, the result is the requested version.
885 898
886 899 Otherwise the result is a merge between the contents of the
887 900 current working directory and the requested version. Files that
888 901 changed between either parent are marked as changed for the next
889 902 commit and a commit must be performed before any further updates
890 903 are allowed.
891 904 '''
892 905 node = node and repo.lookup(node) or repo.changelog.tip()
893 906 return repo.update(node, allow=merge, force=clean)
894 907
895 908 def verify(ui, repo):
896 909 """verify the integrity of the repository"""
897 910 return repo.verify()
898 911
899 912 # Command options and aliases are listed here, alphabetically
900 913
901 914 table = {
902 915 "^add": (add, [], "hg add [files]"),
903 916 "addremove": (addremove, [], "hg addremove [files]"),
904 917 "^annotate": (annotate,
905 918 [('r', 'revision', '', 'revision'),
906 919 ('u', 'user', None, 'show user'),
907 920 ('n', 'number', None, 'show revision number'),
908 921 ('c', 'changeset', None, 'show changeset')],
909 922 'hg annotate [-u] [-c] [-n] [-r id] [files]'),
910 923 "cat": (cat, [], 'hg cat <file> [rev]'),
911 924 "^clone": (clone, [('U', 'noupdate', None, 'skip update after cloning')],
912 925 'hg clone [options] <source> [dest]'),
913 926 "^commit|ci": (commit,
914 927 [('t', 'text', "", 'commit text'),
915 928 ('A', 'addremove', None, 'run add/remove during commit'),
916 929 ('l', 'logfile', "", 'commit text file'),
917 930 ('d', 'date', "", 'date code'),
918 931 ('u', 'user', "", 'user')],
919 932 'hg commit [files]'),
920 933 "copy": (copy, [], 'hg copy <source> <dest>'),
921 934 "debugcheckstate": (debugcheckstate, [], 'debugcheckstate'),
922 935 "debugstate": (debugstate, [], 'debugstate'),
923 936 "debugindex": (debugindex, [], 'debugindex <file>'),
924 937 "debugindexdot": (debugindexdot, [], 'debugindexdot <file>'),
925 938 "^diff": (diff, [('r', 'rev', [], 'revision')],
926 939 'hg diff [-r A] [-r B] [files]'),
927 940 "^export": (export, [('o', 'output', "", 'output to file')],
928 941 "hg export [-o file] <changeset> ..."),
929 942 "forget": (forget, [], "hg forget [files]"),
930 943 "heads": (heads, [], 'hg heads'),
931 944 "help": (help, [], 'hg help [command]'),
932 945 "identify|id": (identify, [], 'hg identify'),
933 946 "import|patch": (import_,
934 947 [('p', 'strip', 1, 'path strip'),
935 948 ('b', 'base', "", 'base path')],
936 949 "hg import [options] <patches>"),
937 950 "^init": (init, [], 'hg init'),
938 951 "^log|history": (log,
939 952 [('r', 'rev', [], 'revision')],
940 953 'hg log [-r A] [-r B] [file]'),
941 954 "manifest": (manifest, [], 'hg manifest [rev]'),
942 955 "parents": (parents, [], 'hg parents [node]'),
943 956 "^pull": (pull,
944 957 [('u', 'update', None, 'update working directory')],
945 958 'hg pull [options] [source]'),
946 959 "^push": (push, [], 'hg push <destination>'),
947 960 "rawcommit": (rawcommit,
948 961 [('p', 'parent', [], 'parent'),
949 962 ('d', 'date', "", 'date code'),
950 963 ('u', 'user', "", 'user'),
951 964 ('F', 'files', "", 'file list'),
952 965 ('t', 'text', "", 'commit text'),
953 966 ('l', 'logfile', "", 'commit text file')],
954 967 'hg rawcommit [options] [files]'),
955 968 "recover": (recover, [], "hg recover"),
956 969 "^remove|rm": (remove, [], "hg remove [files]"),
957 970 "^revert": (revert,
958 971 [("n", "nonrecursive", None, "don't recurse into subdirs"),
959 972 ("r", "rev", "", "revision")],
960 973 "hg revert [files|dirs]"),
961 974 "root": (root, [], "hg root"),
962 975 "^serve": (serve, [('p', 'port', 8000, 'listen port'),
963 976 ('a', 'address', '', 'interface address'),
964 977 ('n', 'name', os.getcwd(), 'repository name'),
965 978 ('t', 'templates', "", 'template map')],
966 979 "hg serve [options]"),
967 980 "^status": (status, [], 'hg status'),
968 981 "tag": (tag, [('t', 'text', "", 'commit text'),
969 982 ('d', 'date', "", 'date code'),
970 983 ('u', 'user', "", 'user')],
971 984 'hg tag [options] <name> [rev]'),
972 985 "tags": (tags, [], 'hg tags'),
973 986 "tip": (tip, [], 'hg tip'),
974 987 "undo": (undo, [], 'hg undo'),
975 988 "^update|up|checkout|co":
976 989 (update,
977 990 [('m', 'merge', None, 'allow merging of conflicts'),
978 991 ('C', 'clean', None, 'overwrite locally modified files')],
979 992 'hg update [options] [node]'),
980 993 "verify": (verify, [], 'hg verify'),
981 994 "version": (show_version, [], 'hg version'),
982 995 }
983 996
984 997 globalopts = [('v', 'verbose', None, 'verbose'),
985 998 ('', 'debug', None, 'debug'),
986 999 ('q', 'quiet', None, 'quiet'),
987 1000 ('', 'profile', None, 'profile'),
988 1001 ('R', 'repository', "", 'repository root directory'),
989 1002 ('', 'traceback', None, 'print traceback on exception'),
990 1003 ('y', 'noninteractive', None, 'run non-interactively'),
991 1004 ('', 'version', None, 'output version information and exit'),
992 1005 ]
993 1006
994 1007 norepo = "clone init version help debugindex debugindexdot"
995 1008
996 1009 def find(cmd):
997 1010 for e in table.keys():
998 1011 if re.match("(%s)$" % e, cmd):
999 1012 return table[e]
1000 1013
1001 1014 raise UnknownCommand(cmd)
1002 1015
1003 1016 class SignalInterrupt(Exception): pass
1004 1017
1005 1018 def catchterm(*args):
1006 1019 raise SignalInterrupt
1007 1020
1008 1021 def run():
1009 1022 sys.exit(dispatch(sys.argv[1:]))
1010 1023
1011 1024 class ParseError(Exception): pass
1012 1025
1013 1026 def parse(args):
1014 1027 options = {}
1015 1028 cmdoptions = {}
1016 1029
1017 1030 try:
1018 1031 args = fancyopts.fancyopts(args, globalopts, options)
1019 1032 except fancyopts.getopt.GetoptError, inst:
1020 1033 raise ParseError(cmd, inst)
1021 1034
1022 1035 if options["version"]:
1023 1036 return ("version", show_version, [], options, cmdoptions)
1024 1037 elif not args:
1025 1038 return ("help", help, [], options, cmdoptions)
1026 1039 else:
1027 1040 cmd, args = args[0], args[1:]
1028 1041
1029 1042 i = find(cmd)
1030 1043
1031 1044 # combine global options into local
1032 1045 c = list(i[1])
1033 1046 l = len(c)
1034 1047 for o in globalopts:
1035 1048 c.append((o[0], o[1], options[o[1]], o[3]))
1036 1049
1037 1050 try:
1038 1051 args = fancyopts.fancyopts(args, c, cmdoptions)
1039 1052 except fancyopts.getopt.GetoptError, inst:
1040 1053 raise ParseError(cmd, inst)
1041 1054
1042 1055 # separate global options back out
1043 1056 for o in globalopts:
1044 1057 n = o[1]
1045 1058 options[n] = cmdoptions[n]
1046 1059 del cmdoptions[n]
1047 1060
1048 1061 return (cmd, i[0], args, options, cmdoptions)
1049 1062
1050 1063 def dispatch(args):
1051 1064 signal.signal(signal.SIGTERM, catchterm)
1052 1065
1053 1066 try:
1054 1067 cmd, func, args, options, cmdoptions = parse(args)
1055 1068 except ParseError, inst:
1056 1069 u = ui.ui()
1057 1070 if inst.args[0]:
1058 1071 u.warn("hg %s: %s\n" % (inst.args[0], inst.args[1]))
1059 1072 help(u, inst.args[0])
1060 1073 else:
1061 1074 u.warn("hg: %s\n" % inst.args[1])
1062 1075 help(u)
1063 1076 sys.exit(-1)
1064 1077 except UnknownCommand, inst:
1065 1078 u = ui.ui()
1066 1079 u.warn("hg: unknown command '%s'\n" % inst.args[0])
1067 1080 help(u)
1068 1081 sys.exit(1)
1069 1082
1070 1083 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
1071 1084 not options["noninteractive"])
1072 1085
1073 1086 try:
1074 1087 try:
1075 1088 if cmd not in norepo.split():
1076 1089 path = options["repository"] or ""
1077 1090 repo = hg.repository(ui=u, path=path)
1078 1091 d = lambda: func(u, repo, *args, **cmdoptions)
1079 1092 else:
1080 1093 d = lambda: func(u, *args, **cmdoptions)
1081 1094
1082 1095 if options['profile']:
1083 1096 import hotshot, hotshot.stats
1084 1097 prof = hotshot.Profile("hg.prof")
1085 1098 r = prof.runcall(d)
1086 1099 prof.close()
1087 1100 stats = hotshot.stats.load("hg.prof")
1088 1101 stats.strip_dirs()
1089 1102 stats.sort_stats('time', 'calls')
1090 1103 stats.print_stats(40)
1091 1104 return r
1092 1105 else:
1093 1106 return d()
1094 1107 except:
1095 1108 if options['traceback']:
1096 1109 traceback.print_exc()
1097 1110 raise
1098 1111 except util.CommandError, inst:
1099 1112 u.warn("abort: %s\n" % inst.args)
1100 1113 except hg.RepoError, inst:
1101 1114 u.warn("abort: ", inst, "!\n")
1102 1115 except SignalInterrupt:
1103 1116 u.warn("killed!\n")
1104 1117 except KeyboardInterrupt:
1105 1118 u.warn("interrupted!\n")
1106 1119 except IOError, inst:
1107 1120 if hasattr(inst, "code"):
1108 1121 u.warn("abort: %s\n" % inst)
1109 1122 elif hasattr(inst, "reason"):
1110 1123 u.warn("abort: error %d: %s\n" % (inst.reason[0], inst.reason[1]))
1111 1124 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
1112 1125 u.warn("broken pipe\n")
1113 1126 else:
1114 1127 raise
1115 1128 except OSError, inst:
1116 1129 if hasattr(inst, "filename"):
1117 1130 u.warn("abort: %s: %s\n" % (inst.strerror, inst.filename))
1118 1131 else:
1119 1132 u.warn("abort: %s\n" % inst.strerror)
1120 1133 except TypeError, inst:
1121 1134 # was this an argument error?
1122 1135 tb = traceback.extract_tb(sys.exc_info()[2])
1123 1136 if len(tb) > 2: # no
1124 1137 raise
1125 1138 u.debug(inst, "\n")
1126 1139 u.warn("%s: invalid arguments\n" % cmd)
1127 1140 help(u, cmd)
1128 1141
1129 1142 sys.exit(-1)
@@ -1,763 +1,766 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, time, re, difflib, sys, zlib
10 10 from mercurial.hg import *
11 11 from mercurial.ui import *
12 12
13 13 def templatepath():
14 14 for f in "templates", "../templates":
15 15 p = os.path.join(os.path.dirname(__file__), f)
16 16 if os.path.isdir(p): return p
17 17
18 18 def age(t):
19 19 def plural(t, c):
20 20 if c == 1: return t
21 21 return t + "s"
22 22 def fmt(t, c):
23 23 return "%d %s" % (c, plural(t, c))
24 24
25 25 now = time.time()
26 26 delta = max(1, int(now - t))
27 27
28 28 scales = [["second", 1],
29 29 ["minute", 60],
30 30 ["hour", 3600],
31 31 ["day", 3600 * 24],
32 32 ["week", 3600 * 24 * 7],
33 33 ["month", 3600 * 24 * 30],
34 34 ["year", 3600 * 24 * 365]]
35 35
36 36 scales.reverse()
37 37
38 38 for t, s in scales:
39 39 n = delta / s
40 40 if n >= 2 or s == 1: return fmt(t, n)
41 41
42 42 def nl2br(text):
43 43 return text.replace('\n', '<br/>\n')
44 44
45 45 def obfuscate(text):
46 46 return ''.join([ '&#%d;' % ord(c) for c in text ])
47 47
48 48 def up(p):
49 49 if p[0] != "/": p = "/" + p
50 50 if p[-1] == "/": p = p[:-1]
51 51 up = os.path.dirname(p)
52 52 if up == "/":
53 53 return "/"
54 54 return up + "/"
55 55
56 56 def httphdr(type):
57 57 sys.stdout.write('Content-type: %s\n\n' % type)
58 58
59 59 def write(*things):
60 60 for thing in things:
61 61 if hasattr(thing, "__iter__"):
62 62 for part in thing:
63 63 write(part)
64 64 else:
65 65 sys.stdout.write(str(thing))
66 66
67 67 def template(tmpl, filters = {}, **map):
68 68 while tmpl:
69 69 m = re.search(r"#([a-zA-Z0-9]+)((\|[a-zA-Z0-9]+)*)#", tmpl)
70 70 if m:
71 71 yield tmpl[:m.start(0)]
72 72 v = map.get(m.group(1), "")
73 73 v = callable(v) and v() or v
74 74
75 75 fl = m.group(2)
76 76 if fl:
77 77 for f in fl.split("|")[1:]:
78 78 v = filters[f](v)
79 79
80 80 yield v
81 81 tmpl = tmpl[m.end(0):]
82 82 else:
83 83 yield tmpl
84 84 return
85 85
86 86 class templater:
87 87 def __init__(self, mapfile, filters = {}, defaults = {}):
88 88 self.cache = {}
89 89 self.map = {}
90 90 self.base = os.path.dirname(mapfile)
91 91 self.filters = filters
92 92 self.defaults = defaults
93 93
94 94 for l in file(mapfile):
95 95 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
96 96 if m:
97 97 self.cache[m.group(1)] = m.group(2)
98 98 else:
99 99 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
100 100 if m:
101 101 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
102 102 else:
103 103 raise "unknown map entry '%s'" % l
104 104
105 105 def __call__(self, t, **map):
106 106 m = self.defaults.copy()
107 107 m.update(map)
108 108 try:
109 109 tmpl = self.cache[t]
110 110 except KeyError:
111 111 tmpl = self.cache[t] = file(self.map[t]).read()
112 112 return template(tmpl, self.filters, **m)
113 113
114 114 def rfc822date(x):
115 115 return time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(x))
116 116
117 117 class hgweb:
118 118 maxchanges = 10
119 119 maxfiles = 10
120 120
121 121 def __init__(self, path, name, templates = ""):
122 122 self.templates = templates or templatepath()
123 123 self.reponame = name
124 124 self.path = path
125 125 self.mtime = -1
126 126 self.viewonly = 0
127 127
128 128 self.filters = {
129 129 "escape": cgi.escape,
130 130 "age": age,
131 131 "date": (lambda x: time.asctime(time.gmtime(x))),
132 132 "addbreaks": nl2br,
133 133 "obfuscate": obfuscate,
134 134 "short": (lambda x: x[:12]),
135 135 "firstline": (lambda x: x.splitlines(1)[0]),
136 136 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
137 137 "rfc822date": rfc822date,
138 138 }
139 139
140 140 def refresh(self):
141 141 s = os.stat(os.path.join(self.path, ".hg", "00changelog.i"))
142 142 if s.st_mtime != self.mtime:
143 143 self.mtime = s.st_mtime
144 144 self.repo = repository(ui(), self.path)
145 145
146 146 def date(self, cs):
147 147 return time.asctime(time.gmtime(float(cs[2].split(' ')[0])))
148 148
149 149 def listfiles(self, files, mf):
150 150 for f in files[:self.maxfiles]:
151 151 yield self.t("filenodelink", node = hex(mf[f]), file = f)
152 152 if len(files) > self.maxfiles:
153 153 yield self.t("fileellipses")
154 154
155 155 def listfilediffs(self, files, changeset):
156 156 for f in files[:self.maxfiles]:
157 157 yield self.t("filedifflink", node = hex(changeset), file = f)
158 158 if len(files) > self.maxfiles:
159 159 yield self.t("fileellipses")
160 160
161 161 def parents(self, t1, nodes=[], rev=None,**args):
162 162 if not rev: rev = lambda x: ""
163 163 for node in nodes:
164 164 if node != nullid:
165 165 yield self.t(t1, node = hex(node), rev = rev(node), **args)
166 166
167 167 def showtag(self, t1, node=nullid, **args):
168 168 for t in self.repo.nodetags(node):
169 169 yield self.t(t1, tag = t, **args)
170 170
171 171 def diff(self, node1, node2, files):
172 172 def filterfiles(list, files):
173 173 l = [ x for x in list if x in files ]
174 174
175 175 for f in files:
176 176 if f[-1] != os.sep: f += os.sep
177 177 l += [ x for x in list if x.startswith(f) ]
178 178 return l
179 179
180 180 parity = [0]
181 181 def diffblock(diff, f, fn):
182 182 yield self.t("diffblock",
183 183 lines = prettyprintlines(diff),
184 184 parity = parity[0],
185 185 file = f,
186 186 filenode = hex(fn or nullid))
187 187 parity[0] = 1 - parity[0]
188 188
189 189 def prettyprintlines(diff):
190 190 for l in diff.splitlines(1):
191 191 if l.startswith('+'):
192 192 yield self.t("difflineplus", line = l)
193 193 elif l.startswith('-'):
194 194 yield self.t("difflineminus", line = l)
195 195 elif l.startswith('@'):
196 196 yield self.t("difflineat", line = l)
197 197 else:
198 198 yield self.t("diffline", line = l)
199 199
200 200 r = self.repo
201 201 cl = r.changelog
202 202 mf = r.manifest
203 203 change1 = cl.read(node1)
204 204 change2 = cl.read(node2)
205 205 mmap1 = mf.read(change1[0])
206 206 mmap2 = mf.read(change2[0])
207 207 date1 = self.date(change1)
208 208 date2 = self.date(change2)
209 209
210 210 c, a, d, u = r.changes(node1, node2)
211 211 c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
212 212
213 213 for f in c:
214 214 to = r.file(f).read(mmap1[f])
215 215 tn = r.file(f).read(mmap2[f])
216 216 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
217 217 for f in a:
218 218 to = None
219 219 tn = r.file(f).read(mmap2[f])
220 220 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
221 221 for f in d:
222 222 to = r.file(f).read(mmap1[f])
223 223 tn = None
224 224 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
225 225
226 226 def header(self):
227 227 yield self.t("header")
228 228
229 229 def footer(self):
230 230 yield self.t("footer")
231 231
232 232 def changelog(self, pos):
233 233 def changenav():
234 234 def seq(factor = 1):
235 235 yield 1 * factor
236 236 yield 3 * factor
237 237 #yield 5 * factor
238 238 for f in seq(factor * 10):
239 239 yield f
240 240
241 241 l = []
242 242 for f in seq():
243 243 if f < self.maxchanges / 2: continue
244 244 if f > count: break
245 245 r = "%d" % f
246 246 if pos + f < count: l.append(("+" + r, pos + f))
247 247 if pos - f >= 0: l.insert(0, ("-" + r, pos - f))
248 248
249 249 yield self.t("naventry", rev = 0, label="(0)")
250 250
251 251 for label, rev in l:
252 252 yield self.t("naventry", label = label, rev = rev)
253 253
254 254 yield self.t("naventry", label="tip")
255 255
256 256 def changelist():
257 257 parity = (start - end) & 1
258 258 cl = self.repo.changelog
259 259 l = [] # build a list in forward order for efficiency
260 260 for i in range(start, end):
261 261 n = cl.node(i)
262 262 changes = cl.read(n)
263 263 hn = hex(n)
264 264 t = float(changes[2].split(' ')[0])
265 265
266 266 l.insert(0, self.t(
267 267 'changelogentry',
268 268 parity = parity,
269 269 author = changes[1],
270 270 parent = self.parents("changelogparent",
271 271 cl.parents(n), cl.rev),
272 272 changelogtag = self.showtag("changelogtag",n),
273 273 manifest = hex(changes[0]),
274 274 desc = changes[4],
275 275 date = t,
276 276 files = self.listfilediffs(changes[3], n),
277 277 rev = i,
278 278 node = hn))
279 279 parity = 1 - parity
280 280
281 281 yield l
282 282
283 283 cl = self.repo.changelog
284 284 mf = cl.read(cl.tip())[0]
285 285 count = cl.count()
286 286 start = max(0, pos - self.maxchanges + 1)
287 287 end = min(count, start + self.maxchanges)
288 288 pos = end - 1
289 289
290 290 yield self.t('changelog',
291 291 changenav = changenav,
292 292 manifest = hex(mf),
293 293 rev = pos, changesets = count, entries = changelist)
294 294
295 295 def search(self, query):
296 296
297 297 def changelist():
298 298 cl = self.repo.changelog
299 299 count = 0
300 300 qw = query.lower().split()
301 301
302 302 def revgen():
303 303 for i in range(cl.count() - 1, 0, -100):
304 304 l = []
305 305 for j in range(max(0, i - 100), i):
306 306 n = cl.node(j)
307 307 changes = cl.read(n)
308 308 l.insert(0, (n, j, changes))
309 309 for e in l:
310 310 yield e
311 311
312 312 for n, i, changes in revgen():
313 313 miss = 0
314 314 for q in qw:
315 315 if not (q in changes[1].lower() or
316 316 q in changes[4].lower() or
317 317 q in " ".join(changes[3][:20]).lower()):
318 318 miss = 1
319 319 break
320 320 if miss: continue
321 321
322 322 count += 1
323 323 hn = hex(n)
324 324 t = float(changes[2].split(' ')[0])
325 325
326 326 yield self.t(
327 327 'searchentry',
328 328 parity = count & 1,
329 329 author = changes[1],
330 330 parent = self.parents("changelogparent",
331 331 cl.parents(n), cl.rev),
332 332 changelogtag = self.showtag("changelogtag",n),
333 333 manifest = hex(changes[0]),
334 334 desc = changes[4],
335 335 date = t,
336 336 files = self.listfilediffs(changes[3], n),
337 337 rev = i,
338 338 node = hn)
339 339
340 340 if count >= self.maxchanges: break
341 341
342 342 cl = self.repo.changelog
343 343 mf = cl.read(cl.tip())[0]
344 344
345 345 yield self.t('search',
346 346 query = query,
347 347 manifest = hex(mf),
348 348 entries = changelist)
349 349
350 350 def changeset(self, nodeid):
351 351 n = bin(nodeid)
352 352 cl = self.repo.changelog
353 353 changes = cl.read(n)
354 354 p1 = cl.parents(n)[0]
355 355 t = float(changes[2].split(' ')[0])
356 356
357 357 files = []
358 358 mf = self.repo.manifest.read(changes[0])
359 359 for f in changes[3]:
360 360 files.append(self.t("filenodelink",
361 361 filenode = hex(mf.get(f, nullid)), file = f))
362 362
363 363 def diff():
364 364 yield self.diff(p1, n, changes[3])
365 365
366 366 yield self.t('changeset',
367 367 diff = diff,
368 368 rev = cl.rev(n),
369 369 node = nodeid,
370 370 parent = self.parents("changesetparent",
371 371 cl.parents(n), cl.rev),
372 372 changesettag = self.showtag("changesettag",n),
373 373 manifest = hex(changes[0]),
374 374 author = changes[1],
375 375 desc = changes[4],
376 376 date = t,
377 377 files = files)
378 378
379 379 def filelog(self, f, filenode):
380 380 cl = self.repo.changelog
381 381 fl = self.repo.file(f)
382 382 count = fl.count()
383 383
384 384 def entries():
385 385 l = []
386 386 parity = (count - 1) & 1
387 387
388 388 for i in range(count):
389 389
390 390 n = fl.node(i)
391 391 lr = fl.linkrev(n)
392 392 cn = cl.node(lr)
393 393 cs = cl.read(cl.node(lr))
394 394 t = float(cs[2].split(' ')[0])
395 395
396 396 l.insert(0, self.t("filelogentry",
397 397 parity = parity,
398 398 filenode = hex(n),
399 399 filerev = i,
400 400 file = f,
401 401 node = hex(cn),
402 402 author = cs[1],
403 403 date = t,
404 404 parent = self.parents("filelogparent",
405 405 fl.parents(n), fl.rev, file=f),
406 406 desc = cs[4]))
407 407 parity = 1 - parity
408 408
409 409 yield l
410 410
411 411 yield self.t("filelog",
412 412 file = f,
413 413 filenode = filenode,
414 414 entries = entries)
415 415
416 416 def filerevision(self, f, node):
417 417 fl = self.repo.file(f)
418 418 n = bin(node)
419 419 text = fl.read(n)
420 420 changerev = fl.linkrev(n)
421 421 cl = self.repo.changelog
422 422 cn = cl.node(changerev)
423 423 cs = cl.read(cn)
424 424 t = float(cs[2].split(' ')[0])
425 425 mfn = cs[0]
426 426
427 427 def lines():
428 428 for l, t in enumerate(text.splitlines(1)):
429 429 yield self.t("fileline", line = t,
430 430 linenumber = "% 6d" % (l + 1),
431 431 parity = l & 1)
432 432
433 433 yield self.t("filerevision", file = f,
434 434 filenode = node,
435 435 path = up(f),
436 436 text = lines(),
437 437 rev = changerev,
438 438 node = hex(cn),
439 439 manifest = hex(mfn),
440 440 author = cs[1],
441 441 date = t,
442 442 parent = self.parents("filerevparent",
443 443 fl.parents(n), fl.rev, file=f),
444 444 permissions = self.repo.manifest.readflags(mfn)[f])
445 445
446 446 def fileannotate(self, f, node):
447 447 bcache = {}
448 448 ncache = {}
449 449 fl = self.repo.file(f)
450 450 n = bin(node)
451 451 changerev = fl.linkrev(n)
452 452
453 453 cl = self.repo.changelog
454 454 cn = cl.node(changerev)
455 455 cs = cl.read(cn)
456 456 t = float(cs[2].split(' ')[0])
457 457 mfn = cs[0]
458 458
459 459 def annotate():
460 460 parity = 1
461 461 last = None
462 462 for r, l in fl.annotate(n):
463 463 try:
464 464 cnode = ncache[r]
465 465 except KeyError:
466 466 cnode = ncache[r] = self.repo.changelog.node(r)
467 467
468 468 try:
469 469 name = bcache[r]
470 470 except KeyError:
471 471 cl = self.repo.changelog.read(cnode)
472 472 name = cl[1]
473 473 f = name.find('@')
474 474 if f >= 0:
475 475 name = name[:f]
476 476 f = name.find('<')
477 477 if f >= 0:
478 478 name = name[f+1:]
479 479 bcache[r] = name
480 480
481 481 if last != cnode:
482 482 parity = 1 - parity
483 483 last = cnode
484 484
485 485 yield self.t("annotateline",
486 486 parity = parity,
487 487 node = hex(cnode),
488 488 rev = r,
489 489 author = name,
490 490 file = f,
491 491 line = l)
492 492
493 493 yield self.t("fileannotate",
494 494 file = f,
495 495 filenode = node,
496 496 annotate = annotate,
497 497 path = up(f),
498 498 rev = changerev,
499 499 node = hex(cn),
500 500 manifest = hex(mfn),
501 501 author = cs[1],
502 502 date = t,
503 503 parent = self.parents("fileannotateparent",
504 504 fl.parents(n), fl.rev, file=f),
505 505 permissions = self.repo.manifest.readflags(mfn)[f])
506 506
507 507 def manifest(self, mnode, path):
508 508 mf = self.repo.manifest.read(bin(mnode))
509 509 rev = self.repo.manifest.rev(bin(mnode))
510 510 node = self.repo.changelog.node(rev)
511 511 mff=self.repo.manifest.readflags(bin(mnode))
512 512
513 513 files = {}
514 514
515 515 p = path[1:]
516 516 l = len(p)
517 517
518 518 for f,n in mf.items():
519 519 if f[:l] != p:
520 520 continue
521 521 remain = f[l:]
522 522 if "/" in remain:
523 523 short = remain[:remain.find("/") + 1] # bleah
524 524 files[short] = (f, None)
525 525 else:
526 526 short = os.path.basename(remain)
527 527 files[short] = (f, n)
528 528
529 529 def filelist():
530 530 parity = 0
531 531 fl = files.keys()
532 532 fl.sort()
533 533 for f in fl:
534 534 full, fnode = files[f]
535 535 if fnode:
536 536 yield self.t("manifestfileentry",
537 537 file = full,
538 538 manifest = mnode,
539 539 filenode = hex(fnode),
540 540 parity = parity,
541 541 basename = f,
542 542 permissions = mff[full])
543 543 else:
544 544 yield self.t("manifestdirentry",
545 545 parity = parity,
546 546 path = os.path.join(path, f),
547 547 manifest = mnode, basename = f[:-1])
548 548 parity = 1 - parity
549 549
550 550 yield self.t("manifest",
551 551 manifest = mnode,
552 552 rev = rev,
553 553 node = hex(node),
554 554 path = path,
555 555 up = up(path),
556 556 entries = filelist)
557 557
558 558 def tags(self):
559 559 cl = self.repo.changelog
560 560 mf = cl.read(cl.tip())[0]
561 561
562 562 i = self.repo.tagslist()
563 563 i.reverse()
564 564
565 565 def entries():
566 566 parity = 0
567 567 for k,n in i:
568 568 yield self.t("tagentry",
569 569 parity = parity,
570 570 tag = k,
571 571 node = hex(n))
572 572 parity = 1 - parity
573 573
574 574 yield self.t("tags",
575 575 manifest = hex(mf),
576 576 entries = entries)
577 577
578 578 def filediff(self, file, changeset):
579 579 n = bin(changeset)
580 580 cl = self.repo.changelog
581 581 p1 = cl.parents(n)[0]
582 582 cs = cl.read(n)
583 583 mf = self.repo.manifest.read(cs[0])
584 584
585 585 def diff():
586 586 yield self.diff(p1, n, file)
587 587
588 588 yield self.t("filediff",
589 589 file = file,
590 590 filenode = hex(mf.get(file, nullid)),
591 591 node = changeset,
592 592 rev = self.repo.changelog.rev(n),
593 593 parent = self.parents("filediffparent",
594 594 cl.parents(n), cl.rev),
595 595 diff = diff)
596 596
597 597 # add tags to things
598 598 # tags -> list of changesets corresponding to tags
599 599 # find tag, changeset, file
600 600
601 601 def run(self):
602 602 self.refresh()
603 603 args = cgi.parse()
604 604
605 605 m = os.path.join(self.templates, "map")
606 606 if args.has_key('style'):
607 607 b = os.path.basename("map-" + args['style'][0])
608 608 p = os.path.join(self.templates, b)
609 609 if os.path.isfile(p): m = p
610 610
611 611 port = os.environ["SERVER_PORT"]
612 612 port = port != "80" and (":" + port) or ""
613 613 url = "http://%s%s%s" % \
614 614 (os.environ["SERVER_NAME"], port, os.environ["REQUEST_URI"])
615 615
616 616 self.t = templater(m, self.filters,
617 617 {"url":url,
618 618 "repo":self.reponame,
619 619 "header":self.header(),
620 620 "footer":self.footer(),
621 621 })
622 622
623 623 if not args.has_key('cmd') or args['cmd'][0] == 'changelog':
624 624 c = self.repo.changelog.count() - 1
625 625 hi = c
626 626 if args.has_key('rev'):
627 627 hi = args['rev'][0]
628 628 try:
629 629 hi = self.repo.changelog.rev(self.repo.lookup(hi))
630 630 except KeyError:
631 631 write(self.search(hi))
632 632 return
633 633
634 634 write(self.changelog(hi))
635 635
636 636 elif args['cmd'][0] == 'changeset':
637 637 write(self.changeset(args['node'][0]))
638 638
639 639 elif args['cmd'][0] == 'manifest':
640 640 write(self.manifest(args['manifest'][0], args['path'][0]))
641 641
642 642 elif args['cmd'][0] == 'tags':
643 643 write(self.tags())
644 644
645 645 elif args['cmd'][0] == 'filediff':
646 646 write(self.filediff(args['file'][0], args['node'][0]))
647 647
648 648 elif args['cmd'][0] == 'file':
649 649 write(self.filerevision(args['file'][0], args['filenode'][0]))
650 650
651 651 elif args['cmd'][0] == 'annotate':
652 652 write(self.fileannotate(args['file'][0], args['filenode'][0]))
653 653
654 654 elif args['cmd'][0] == 'filelog':
655 655 write(self.filelog(args['file'][0], args['filenode'][0]))
656 656
657 657 elif args['cmd'][0] == 'heads':
658 658 httphdr("text/plain")
659 659 h = self.repo.heads()
660 660 sys.stdout.write(" ".join(map(hex, h)) + "\n")
661 661
662 662 elif args['cmd'][0] == 'branches':
663 663 httphdr("text/plain")
664 664 nodes = []
665 665 if args.has_key('nodes'):
666 666 nodes = map(bin, args['nodes'][0].split(" "))
667 667 for b in self.repo.branches(nodes):
668 668 sys.stdout.write(" ".join(map(hex, b)) + "\n")
669 669
670 670 elif args['cmd'][0] == 'between':
671 671 httphdr("text/plain")
672 672 nodes = []
673 673 if args.has_key('pairs'):
674 674 pairs = [ map(bin, p.split("-"))
675 675 for p in args['pairs'][0].split(" ") ]
676 676 for b in self.repo.between(pairs):
677 677 sys.stdout.write(" ".join(map(hex, b)) + "\n")
678 678
679 679 elif args['cmd'][0] == 'changegroup':
680 680 httphdr("application/hg-changegroup")
681 681 nodes = []
682 682 if self.viewonly:
683 683 return
684 684
685 685 if args.has_key('roots'):
686 686 nodes = map(bin, args['roots'][0].split(" "))
687 687
688 688 z = zlib.compressobj()
689 689 for chunk in self.repo.changegroup(nodes):
690 690 sys.stdout.write(z.compress(chunk))
691 691
692 692 sys.stdout.write(z.flush())
693 693
694 694 else:
695 695 write(self.t("error"))
696 696
697 def server(path, name, templates, address, port):
697 def create_server(path, name, templates, address, port):
698 698
699 699 import BaseHTTPServer
700 700 import sys, os
701 701
702 702 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
703 703 def do_POST(self):
704 704 try:
705 705 self.do_hgweb()
706 706 except socket.error, inst:
707 707 if inst.args[0] != 32: raise
708 708
709 709 def do_GET(self):
710 710 self.do_POST()
711 711
712 712 def do_hgweb(self):
713 713 query = ""
714 714 p = self.path.find("?")
715 715 if p:
716 716 query = self.path[p + 1:]
717 717 query = query.replace('+', ' ')
718 718
719 719 env = {}
720 720 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
721 721 env['REQUEST_METHOD'] = self.command
722 722 env['SERVER_NAME'] = self.server.server_name
723 723 env['SERVER_PORT'] = str(self.server.server_port)
724 724 env['REQUEST_URI'] = "/"
725 725 if query:
726 726 env['QUERY_STRING'] = query
727 727 host = self.address_string()
728 728 if host != self.client_address[0]:
729 729 env['REMOTE_HOST'] = host
730 730 env['REMOTE_ADDR'] = self.client_address[0]
731 731
732 732 if self.headers.typeheader is None:
733 733 env['CONTENT_TYPE'] = self.headers.type
734 734 else:
735 735 env['CONTENT_TYPE'] = self.headers.typeheader
736 736 length = self.headers.getheader('content-length')
737 737 if length:
738 738 env['CONTENT_LENGTH'] = length
739 739 accept = []
740 740 for line in self.headers.getallmatchingheaders('accept'):
741 741 if line[:1] in "\t\n\r ":
742 742 accept.append(line.strip())
743 743 else:
744 744 accept = accept + line[7:].split(',')
745 745 env['HTTP_ACCEPT'] = ','.join(accept)
746 746
747 747 os.environ.update(env)
748 748
749 749 save = sys.argv, sys.stdin, sys.stdout, sys.stderr
750 750 try:
751 751 sys.stdin = self.rfile
752 752 sys.stdout = self.wfile
753 753 sys.argv = ["hgweb.py"]
754 754 if '=' not in query:
755 755 sys.argv.append(query)
756 756 self.send_response(200, "Script output follows")
757 757 hg.run()
758 758 finally:
759 759 sys.argv, sys.stdin, sys.stdout, sys.stderr = save
760 760
761 761 hg = hgweb(path, name, templates)
762 httpd = BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
762 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
763
764 def server(path, name, templates, address, port):
765 httpd = create_server(path, name, templates, address, port)
763 766 httpd.serve_forever()
General Comments 0
You need to be logged in to leave comments. Login now