##// END OF EJS Templates
Refactor diffrevs/diffdir into changes...
mpm@selenic.com -
r536:c15b4bc0 default
parent child Browse files
Show More
@@ -0,0 +1,12 b''
1 #!/bin/sh
2
3 hg init
4 touch a
5 hg add a
6 hg ci -t "a" -u test -d "0 0"
7
8 echo 123 > b
9 hg add b
10 hg diff | sed "s/\(\(---\|+++\).*\)\t.*/\1/"
11
12 hg diff -r tip | sed "s/\(\(---\|+++\).*\)\t.*/\1/"
@@ -0,0 +1,10 b''
1 diff -r 3903775176ed b
2 --- /dev/null
3 +++ b/b
4 @@ -0,0 +1,1 @@
5 +123
6 diff -r 3903775176ed b
7 --- /dev/null
8 +++ b/b
9 @@ -0,0 +1,1 @@
10 +123
@@ -1,917 +1,917 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 11 demandload(globals(), "mdiff time hgweb traceback random signal 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))) for x in args ]
33 33 return args
34 34
35 35 def dodiff(ui, repo, path, files = None, node1 = None, node2 = None):
36 36 def date(c):
37 37 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
38 38
39 39 if node2:
40 40 change = repo.changelog.read(node2)
41 41 mmap2 = repo.manifest.read(change[0])
42 (c, a, d) = repo.diffrevs(node1, node2)
42 (c, a, d, u) = repo.changes(node1, node2)
43 43 def read(f): return repo.file(f).read(mmap2[f])
44 44 date2 = date(change)
45 45 else:
46 46 date2 = time.asctime()
47 (c, a, d, u) = repo.diffdir(path, node1)
47 (c, a, d, u) = repo.changes(None, node1, path)
48 48 if not node1:
49 49 node1 = repo.dirstate.parents()[0]
50 50 def read(f): return repo.wfile(f).read()
51 51
52 52 if ui.quiet:
53 53 r = None
54 54 else:
55 55 hexfunc = ui.verbose and hg.hex or hg.short
56 56 r = [hexfunc(node) for node in [node1, node2] if node]
57 57
58 58 change = repo.changelog.read(node1)
59 59 mmap = repo.manifest.read(change[0])
60 60 date1 = date(change)
61 61
62 62 if files:
63 63 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
64 64
65 65 for f in c:
66 66 to = None
67 67 if f in mmap:
68 68 to = repo.file(f).read(mmap[f])
69 69 tn = read(f)
70 70 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f, r))
71 71 for f in a:
72 72 to = None
73 73 tn = read(f)
74 74 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f, r))
75 75 for f in d:
76 76 to = repo.file(f).read(mmap[f])
77 77 tn = None
78 78 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f, r))
79 79
80 80 def show_changeset(ui, repo, rev=0, changenode=None, filelog=None):
81 81 """show a single changeset or file revision"""
82 82 changelog = repo.changelog
83 83 if filelog:
84 84 log = filelog
85 85 filerev = rev
86 86 node = filenode = filelog.node(filerev)
87 87 changerev = filelog.linkrev(filenode)
88 88 changenode = changenode or changelog.node(changerev)
89 89 else:
90 90 log = changelog
91 91 changerev = rev
92 92 if changenode is None:
93 93 changenode = changelog.node(changerev)
94 94 elif not changerev:
95 95 rev = changerev = changelog.rev(changenode)
96 96 node = changenode
97 97
98 98 if ui.quiet:
99 99 ui.write("%d:%s\n" % (rev, hg.hex(node)))
100 100 return
101 101
102 102 changes = changelog.read(changenode)
103 103
104 104 parents = [(log.rev(parent), hg.hex(parent))
105 105 for parent in log.parents(node)
106 106 if ui.debugflag or parent != hg.nullid]
107 107 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
108 108 parents = []
109 109
110 110 if filelog:
111 111 ui.write("revision: %d:%s\n" % (filerev, hg.hex(filenode)))
112 112 for parent in parents:
113 113 ui.write("parent: %d:%s\n" % parent)
114 114 ui.status("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
115 115 else:
116 116 ui.write("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
117 117 for tag in repo.nodetags(changenode):
118 118 ui.status("tag: %s\n" % tag)
119 119 for parent in parents:
120 120 ui.write("parent: %d:%s\n" % parent)
121 121 ui.note("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
122 122 hg.hex(changes[0])))
123 123 ui.status("user: %s\n" % changes[1])
124 124 ui.status("date: %s\n" % time.asctime(
125 125 time.localtime(float(changes[2].split(' ')[0]))))
126 126 if ui.debugflag:
127 files = repo.diffrevs(changelog.parents(changenode)[0], changenode)
127 files = repo.changes(changelog.parents(changenode)[0], changenode)
128 128 for key, value in zip(["files:", "files+:", "files-:"], files):
129 129 if value:
130 130 ui.note("%-12s %s\n" % (key, " ".join(value)))
131 131 else:
132 132 ui.note("files: %s\n" % " ".join(changes[3]))
133 133 description = changes[4].strip()
134 134 if description:
135 135 if ui.verbose:
136 136 ui.status("description:\n")
137 137 ui.status(description)
138 138 ui.status("\n")
139 139 else:
140 140 ui.status("summary: %s\n" % description.splitlines()[0])
141 141 ui.status("\n")
142 142
143 143 def show_version(ui):
144 144 """output version and copyright information"""
145 145 ui.write("Mercurial version %s\n" % version.get_version())
146 146 ui.status(
147 147 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
148 148 "This is free software; see the source for copying conditions. "
149 149 "There is NO\nwarranty; "
150 150 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
151 151 )
152 152
153 153 def help(ui, cmd=None):
154 154 '''show help for a given command or all commands'''
155 155 if cmd:
156 156 try:
157 157 i = find(cmd)
158 158 ui.write("%s\n\n" % i[2])
159 159
160 160 if i[1]:
161 161 for s, l, d, c in i[1]:
162 162 opt=' '
163 163 if s: opt = opt + '-' + s + ' '
164 164 if l: opt = opt + '--' + l + ' '
165 165 if d: opt = opt + '(' + str(d) + ')'
166 166 ui.write(opt, "\n")
167 167 if c: ui.write(' %s\n' % c)
168 168 ui.write("\n")
169 169
170 170 ui.write(i[0].__doc__, "\n")
171 171 except UnknownCommand:
172 172 ui.warn("hg: unknown command %s\n" % cmd)
173 173 sys.exit(0)
174 174 else:
175 175 if not ui.quiet:
176 176 show_version(ui)
177 177 ui.write('\n')
178 178 ui.write('hg commands:\n\n')
179 179
180 180 h = {}
181 181 for c, e in table.items():
182 182 f = c.split("|")[0]
183 183 if f.startswith("debug"):
184 184 continue
185 185 d = ""
186 186 if e[0].__doc__:
187 187 d = e[0].__doc__.splitlines(0)[0].rstrip()
188 188 h[f] = d
189 189
190 190 fns = h.keys()
191 191 fns.sort()
192 192 m = max(map(len, fns))
193 193 for f in fns:
194 194 ui.write(' %-*s %s\n' % (m, f, h[f]))
195 195
196 196 # Commands start here, listed alphabetically
197 197
198 198 def add(ui, repo, file, *files):
199 199 '''add the specified files on the next commit'''
200 200 repo.add(relpath(repo, (file,) + files))
201 201
202 202 def addremove(ui, repo, *files):
203 203 """add all new files, delete all missing files"""
204 204 if files:
205 205 files = relpath(repo, files)
206 206 d = []
207 207 u = []
208 208 for f in files:
209 209 p = repo.wjoin(f)
210 210 s = repo.dirstate.state(f)
211 211 isfile = os.path.isfile(p)
212 212 if s != 'r' and not isfile:
213 213 d.append(f)
214 214 elif s not in 'nmai' and isfile:
215 215 u.append(f)
216 216 else:
217 (c, a, d, u) = repo.diffdir(repo.root)
217 (c, a, d, u) = repo.changes(None, None)
218 218 repo.add(u)
219 219 repo.remove(d)
220 220
221 221 def annotate(u, repo, file, *files, **ops):
222 222 """show changeset information per file line"""
223 223 def getnode(rev):
224 224 return hg.short(repo.changelog.node(rev))
225 225
226 226 def getname(rev):
227 227 try:
228 228 return bcache[rev]
229 229 except KeyError:
230 230 cl = repo.changelog.read(repo.changelog.node(rev))
231 231 name = cl[1]
232 232 f = name.find('@')
233 233 if f >= 0:
234 234 name = name[:f]
235 235 f = name.find('<')
236 236 if f >= 0:
237 237 name = name[f+1:]
238 238 bcache[rev] = name
239 239 return name
240 240
241 241 bcache = {}
242 242 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
243 243 if not ops['user'] and not ops['changeset']:
244 244 ops['number'] = 1
245 245
246 246 node = repo.dirstate.parents()[0]
247 247 if ops['revision']:
248 248 node = repo.changelog.lookup(ops['revision'])
249 249 change = repo.changelog.read(node)
250 250 mmap = repo.manifest.read(change[0])
251 251 for f in relpath(repo, (file,) + files):
252 252 lines = repo.file(f).annotate(mmap[f])
253 253 pieces = []
254 254
255 255 for o, f in opmap:
256 256 if ops[o]:
257 257 l = [ f(n) for n,t in lines ]
258 258 m = max(map(len, l))
259 259 pieces.append([ "%*s" % (m, x) for x in l])
260 260
261 261 for p,l in zip(zip(*pieces), lines):
262 262 u.write(" ".join(p) + ": " + l[1])
263 263
264 264 def cat(ui, repo, file, rev = []):
265 265 """output the latest or given revision of a file"""
266 266 r = repo.file(relpath(repo, [file])[0])
267 267 n = r.tip()
268 268 if rev: n = r.lookup(rev)
269 269 sys.stdout.write(r.read(n))
270 270
271 271 def clone(ui, source, dest = None, **opts):
272 272 """make a copy of an existing repository"""
273 273 source = ui.expandpath(source)
274 274
275 275 if dest is None:
276 276 dest = os.path.basename(os.path.normpath(source))
277 277
278 278 if os.path.exists(dest):
279 279 ui.warn("abort: destination '%s' already exists\n" % dest)
280 280 return 1
281 281
282 282 class dircleanup:
283 283 def __init__(self, dir):
284 284 self.dir = dir
285 285 os.mkdir(dir)
286 286 def close(self):
287 287 self.dir = None
288 288 def __del__(self):
289 289 if self.dir:
290 290 import shutil
291 291 shutil.rmtree(self.dir, True)
292 292
293 293 d = dircleanup(dest)
294 294
295 295 link = 0
296 296 if not source.startswith("http://"):
297 297 d1 = os.stat(dest).st_dev
298 298 d2 = os.stat(source).st_dev
299 299 if d1 == d2: link = 1
300 300
301 301 if link:
302 302 ui.note("copying by hardlink\n")
303 303 util.system("cp -al '%s'/.hg '%s'/.hg" % (source, dest))
304 304 try:
305 305 os.remove(os.path.join(dest, ".hg", "dirstate"))
306 306 except: pass
307 307
308 308 repo = hg.repository(ui, dest)
309 309
310 310 else:
311 311 repo = hg.repository(ui, dest, create=1)
312 312 other = hg.repository(ui, source)
313 313 fetch = repo.findincoming(other)
314 314 if fetch:
315 315 cg = other.changegroup(fetch)
316 316 repo.addchangegroup(cg)
317 317
318 318 f = repo.opener("hgrc", "w")
319 319 f.write("[paths]\n")
320 320 f.write("default = %s\n" % source)
321 321
322 322 if not opts['noupdate']:
323 323 update(ui, repo)
324 324
325 325 d.close()
326 326
327 327 def commit(ui, repo, *files, **opts):
328 328 """commit the specified files or all outstanding changes"""
329 329 text = opts['text']
330 330 if not text and opts['logfile']:
331 331 try: text = open(opts['logfile']).read()
332 332 except IOError: pass
333 333
334 334 if opts['addremove']:
335 335 addremove(ui, repo, *files)
336 336 repo.commit(relpath(repo, files), text, opts['user'], opts['date'])
337 337
338 338 def copy(ui, repo, source, dest):
339 339 """mark a file as copied or renamed for the next commit"""
340 340 return repo.copy(*relpath(repo, (source, dest)))
341 341
342 342 def debugcheckdirstate(ui, repo):
343 343 parent1, parent2 = repo.dirstate.parents()
344 344 dc = repo.dirstate.dup()
345 345 keys = dc.keys()
346 346 keys.sort()
347 347 m1n = repo.changelog.read(parent1)[0]
348 348 m2n = repo.changelog.read(parent2)[0]
349 349 m1 = repo.manifest.read(m1n)
350 350 m2 = repo.manifest.read(m2n)
351 351 errors = 0
352 352 for f in dc:
353 353 state = repo.dirstate.state(f)
354 354 if state in "nr" and f not in m1:
355 355 print "%s in state %s, but not listed in manifest1" % (f, state)
356 356 errors += 1
357 357 if state in "a" and f in m1:
358 358 print "%s in state %s, but also listed in manifest1" % (f, state)
359 359 errors += 1
360 360 if state in "m" and f not in m1 and f not in m2:
361 361 print "%s in state %s, but not listed in either manifest" % (f, state)
362 362 errors += 1
363 363 for f in m1:
364 364 state = repo.dirstate.state(f)
365 365 if state not in "nrm":
366 366 print "%s in manifest1, but listed as state %s" % (f, state)
367 367 errors += 1
368 368 if errors:
369 369 print ".hg/dirstate inconsistent with current parent's manifest, aborting"
370 370 sys.exit(1)
371 371
372 372 def debugdumpdirstate(ui, repo):
373 373 dc = repo.dirstate.dup()
374 374 keys = dc.keys()
375 375 keys.sort()
376 376 for file in keys:
377 377 print "%s => %c" % (file, dc[file][0])
378 378
379 379 def debugindex(ui, file):
380 380 r = hg.revlog(hg.opener(""), file, "")
381 381 print " rev offset length base linkrev"+\
382 382 " p1 p2 nodeid"
383 383 for i in range(r.count()):
384 384 e = r.index[i]
385 385 print "% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s.." % (
386 386 i, e[0], e[1], e[2], e[3],
387 387 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5]))
388 388
389 389 def debugindexdot(ui, file):
390 390 r = hg.revlog(hg.opener(""), file, "")
391 391 print "digraph G {"
392 392 for i in range(r.count()):
393 393 e = r.index[i]
394 394 print "\t%d -> %d" % (r.rev(e[4]), i)
395 395 if e[5] != hg.nullid:
396 396 print "\t%d -> %d" % (r.rev(e[5]), i)
397 397 print "}"
398 398
399 399 def diff(ui, repo, *files, **opts):
400 400 """diff working directory (or selected files)"""
401 401 revs = []
402 402 if opts['rev']:
403 403 revs = map(lambda x: repo.lookup(x), opts['rev'])
404 404
405 405 if len(revs) > 2:
406 406 ui.warn("too many revisions to diff\n")
407 407 sys.exit(1)
408 408
409 409 if files:
410 410 files = relpath(repo, files)
411 411 else:
412 412 files = relpath(repo, [""])
413 413
414 414 dodiff(ui, repo, os.getcwd(), files, *revs)
415 415
416 416 def export(ui, repo, changeset):
417 417 """dump the changeset header and diffs for a revision"""
418 418 node = repo.lookup(changeset)
419 419 prev, other = repo.changelog.parents(node)
420 420 change = repo.changelog.read(node)
421 421 print "# HG changeset patch"
422 422 print "# User %s" % change[1]
423 423 print "# Node ID %s" % hg.hex(node)
424 424 print "# Parent %s" % hg.hex(prev)
425 425 print
426 426 if other != hg.nullid:
427 427 print "# Parent %s" % hg.hex(other)
428 428 print change[4].rstrip()
429 429 print
430 430
431 431 dodiff(ui, repo, "", None, prev, node)
432 432
433 433 def forget(ui, repo, file, *files):
434 434 """don't add the specified files on the next commit"""
435 435 repo.forget(relpath(repo, (file,) + files))
436 436
437 437 def heads(ui, repo):
438 438 """show current repository heads"""
439 439 for n in repo.changelog.heads():
440 440 show_changeset(ui, repo, changenode=n)
441 441
442 442 def identify(ui, repo):
443 443 """print information about the working copy"""
444 444 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
445 445 if not parents:
446 446 ui.write("unknown\n")
447 447 return
448 448
449 449 hexfunc = ui.verbose and hg.hex or hg.short
450 (c, a, d, u) = repo.diffdir(repo.root)
450 (c, a, d, u) = repo.changes(None, None)
451 451 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
452 452 (c or a or d) and "+" or "")]
453 453
454 454 if not ui.quiet:
455 455 # multiple tags for a single parent separated by '/'
456 456 parenttags = ['/'.join(tags)
457 457 for tags in map(repo.nodetags, parents) if tags]
458 458 # tags for multiple parents separated by ' + '
459 459 output.append(' + '.join(parenttags))
460 460
461 461 ui.write("%s\n" % ' '.join(output))
462 462
463 463 def import_(ui, repo, patch1, *patches, **opts):
464 464 """import an ordered set of patches"""
465 465 try:
466 466 import psyco
467 467 psyco.full()
468 468 except:
469 469 pass
470 470
471 471 patches = (patch1,) + patches
472 472
473 473 d = opts["base"]
474 474 strip = opts["strip"]
475 475
476 476 for patch in patches:
477 477 ui.status("applying %s\n" % patch)
478 478 pf = os.path.join(d, patch)
479 479
480 480 text = ""
481 481 for l in file(pf):
482 482 if l[:4] == "--- ": break
483 483 text += l
484 484
485 485 # make sure text isn't empty
486 486 if not text: text = "imported patch %s\n" % patch
487 487
488 488 f = os.popen("patch -p%d < %s" % (strip, pf))
489 489 files = []
490 490 for l in f.read().splitlines():
491 491 l.rstrip('\r\n');
492 492 ui.status("%s\n" % l)
493 493 if l[:14] == 'patching file ':
494 494 pf = l[14:]
495 495 if pf not in files:
496 496 files.append(pf)
497 497 patcherr = f.close()
498 498 if patcherr:
499 499 sys.stderr.write("patch failed")
500 500 sys.exit(1)
501 501
502 502 if len(files) > 0:
503 503 addremove(ui, repo, *files)
504 504 repo.commit(files, text)
505 505
506 506 def init(ui, source=None):
507 507 """create a new repository in the current directory"""
508 508
509 509 if source:
510 510 ui.warn("no longer supported: use \"hg clone\" instead\n")
511 511 sys.exit(1)
512 512 repo = hg.repository(ui, ".", create=1)
513 513
514 514 def log(ui, repo, f = None):
515 515 """show the revision history of the repository or a single file"""
516 516 if f:
517 517 f = relpath(repo, [f])[0]
518 518 r = repo.file(f)
519 519 for i in range(r.count() - 1, -1, -1):
520 520 show_changeset(ui, repo, filelog=r, rev=i)
521 521 else:
522 522 for i in range(repo.changelog.count() - 1, -1, -1):
523 523 show_changeset(ui, repo, rev=i)
524 524
525 525 def manifest(ui, repo, rev = []):
526 526 """output the latest or given revision of the project manifest"""
527 527 n = repo.manifest.tip()
528 528 if rev:
529 529 n = repo.manifest.lookup(rev)
530 530 m = repo.manifest.read(n)
531 531 mf = repo.manifest.readflags(n)
532 532 files = m.keys()
533 533 files.sort()
534 534
535 535 for f in files:
536 536 ui.write("%40s %3s %s\n" % (hg.hex(m[f]), mf[f] and "755" or "644", f))
537 537
538 538 def parents(ui, repo, node = None):
539 539 '''show the parents of the current working dir'''
540 540 if node:
541 541 p = repo.changelog.parents(repo.lookup(hg.bin(node)))
542 542 else:
543 543 p = repo.dirstate.parents()
544 544
545 545 for n in p:
546 546 if n != hg.nullid:
547 547 show_changeset(ui, repo, changenode=n)
548 548
549 549 def pull(ui, repo, source="default", **opts):
550 550 """pull changes from the specified source"""
551 551 source = ui.expandpath(source)
552 552
553 553 ui.status('pulling from %s\n' % (source))
554 554
555 555 other = hg.repository(ui, source)
556 556 fetch = repo.findincoming(other)
557 557 if not fetch:
558 558 ui.status("no changes found\n")
559 559 return
560 560
561 561 cg = other.changegroup(fetch)
562 562 r = repo.addchangegroup(cg)
563 563 if cg and not r:
564 564 if opts['update']:
565 565 return update(ui, repo)
566 566 else:
567 567 ui.status("(run 'hg update' to get a working copy)\n")
568 568
569 569 return r
570 570
571 571 def push(ui, repo, dest="default-push"):
572 572 """push changes to the specified destination"""
573 573 dest = ui.expandpath(dest)
574 574
575 575 if not dest.startswith("ssh://"):
576 576 ui.warn("abort: can only push to ssh:// destinations currently\n")
577 577 return 1
578 578
579 579 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', dest)
580 580 if not m:
581 581 ui.warn("abort: couldn't parse destination %s\n" % dest)
582 582 return 1
583 583
584 584 user, host, port, path = map(m.group, (2, 3, 5, 7))
585 585 uhost = user and ("%s@%s" % (user, host)) or host
586 586 port = port and (" -p %s") % port or ""
587 587 path = path or ""
588 588
589 589 sport = random.randrange(30000, 60000)
590 590 cmd = "ssh %s%s -R %d:localhost:%d 'cd %s; hg pull http://localhost:%d/'"
591 591 cmd = cmd % (uhost, port, sport+1, sport, path, sport+1)
592 592
593 593 child = os.fork()
594 594 if not child:
595 595 sys.stdout = file("/dev/null", "w")
596 596 sys.stderr = sys.stdout
597 597 hgweb.server(repo.root, "pull", "", "localhost", sport)
598 598 else:
599 599 ui.status("connecting to %s\n" % host)
600 600 r = os.system(cmd)
601 601 os.kill(child, signal.SIGTERM)
602 602 return r
603 603
604 604 def rawcommit(ui, repo, *flist, **rc):
605 605 "raw commit interface"
606 606
607 607 text = rc['text']
608 608 if not text and rc['logfile']:
609 609 try: text = open(rc['logfile']).read()
610 610 except IOError: pass
611 611 if not text and not rc['logfile']:
612 612 print "missing commit text"
613 613 return 1
614 614
615 615 files = relpath(repo, list(flist))
616 616 if rc['files']:
617 617 files += open(rc['files']).read().splitlines()
618 618
619 619 rc['parent'] = map(repo.lookup, rc['parent'])
620 620
621 621 repo.rawcommit(files, text, rc['user'], rc['date'], *rc['parent'])
622 622
623 623 def recover(ui, repo):
624 624 """roll back an interrupted transaction"""
625 625 repo.recover()
626 626
627 627 def remove(ui, repo, file, *files):
628 628 """remove the specified files on the next commit"""
629 629 repo.remove(relpath(repo, (file,) + files))
630 630
631 631 def root(ui, repo):
632 632 """print the root (top) of the current working dir"""
633 633 ui.write(repo.root + "\n")
634 634
635 635 def serve(ui, repo, **opts):
636 636 """export the repository via HTTP"""
637 637 hgweb.server(repo.root, opts["name"], opts["templates"],
638 638 opts["address"], opts["port"])
639 639
640 640 def status(ui, repo):
641 641 '''show changed files in the working directory
642 642
643 643 C = changed
644 644 A = added
645 645 R = removed
646 646 ? = not tracked'''
647 647
648 (c, a, d, u) = repo.diffdir(os.getcwd())
648 (c, a, d, u) = repo.changes(None, None, os.getcwd())
649 649 (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
650 650
651 651 for f in c: print "C", f
652 652 for f in a: print "A", f
653 653 for f in d: print "R", f
654 654 for f in u: print "?", f
655 655
656 656 def tag(ui, repo, name, rev = None, **opts):
657 657 """add a tag for the current tip or a given revision"""
658 658
659 659 if name == "tip":
660 660 ui.warn("abort: 'tip' is a reserved name!\n")
661 661 return -1
662 662
663 (c, a, d, u) = repo.diffdir(repo.root)
663 (c, a, d, u) = repo.changes(None, None)
664 664 for x in (c, a, d, u):
665 665 if ".hgtags" in x:
666 666 ui.warn("abort: working copy of .hgtags is changed!\n")
667 667 ui.status("(please commit .hgtags manually)\n")
668 668 return -1
669 669
670 670 if rev:
671 671 r = hg.hex(repo.lookup(rev))
672 672 else:
673 673 r = hg.hex(repo.changelog.tip())
674 674
675 675 add = 0
676 676 if not os.path.exists(repo.wjoin(".hgtags")): add = 1
677 677 repo.wfile(".hgtags", "a").write("%s %s\n" % (r, name))
678 678 if add: repo.add([".hgtags"])
679 679
680 680 if not opts['text']:
681 681 opts['text'] = "Added tag %s for changeset %s" % (name, r)
682 682
683 683 repo.commit([".hgtags"], opts['text'], opts['user'], opts['date'])
684 684
685 685 def tags(ui, repo):
686 686 """list repository tags"""
687 687
688 688 l = repo.tagslist()
689 689 l.reverse()
690 690 for t, n in l:
691 691 try:
692 692 r = "%5d:%s" % (repo.changelog.rev(n), hg.hex(n))
693 693 except KeyError:
694 694 r = " ?:?"
695 695 ui.write("%-30s %s\n" % (t, r))
696 696
697 697 def tip(ui, repo):
698 698 """show the tip revision"""
699 699 n = repo.changelog.tip()
700 700 show_changeset(ui, repo, changenode=n)
701 701
702 702 def undo(ui, repo):
703 703 """undo the last transaction"""
704 704 repo.undo()
705 705
706 706 def update(ui, repo, node=None, merge=False, clean=False):
707 707 '''update or merge working directory
708 708
709 709 If there are no outstanding changes in the working directory and
710 710 there is a linear relationship between the current version and the
711 711 requested version, the result is the requested version.
712 712
713 713 Otherwise the result is a merge between the contents of the
714 714 current working directory and the requested version. Files that
715 715 changed between either parent are marked as changed for the next
716 716 commit and a commit must be performed before any further updates
717 717 are allowed.
718 718 '''
719 719 node = node and repo.lookup(node) or repo.changelog.tip()
720 720 return repo.update(node, allow=merge, force=clean)
721 721
722 722 def verify(ui, repo):
723 723 """verify the integrity of the repository"""
724 724 return repo.verify()
725 725
726 726 # Command options and aliases are listed here, alphabetically
727 727
728 728 table = {
729 729 "add": (add, [], "hg add [files]"),
730 730 "addremove": (addremove, [], "hg addremove [files]"),
731 731 "annotate": (annotate,
732 732 [('r', 'revision', '', 'revision'),
733 733 ('u', 'user', None, 'show user'),
734 734 ('n', 'number', None, 'show revision number'),
735 735 ('c', 'changeset', None, 'show changeset')],
736 736 'hg annotate [-u] [-c] [-n] [-r id] [files]'),
737 737 "cat": (cat, [], 'hg cat <file> [rev]'),
738 738 "clone": (clone, [('U', 'noupdate', None, 'skip update after cloning')],
739 739 'hg clone [options] <source> [dest]'),
740 740 "commit|ci": (commit,
741 741 [('t', 'text', "", 'commit text'),
742 742 ('A', 'addremove', None, 'run add/remove during commit'),
743 743 ('l', 'logfile', "", 'commit text file'),
744 744 ('d', 'date', "", 'data'),
745 745 ('u', 'user', "", 'user')],
746 746 'hg commit [files]'),
747 747 "copy": (copy, [], 'hg copy <source> <dest>'),
748 748 "debugcheckdirstate": (debugcheckdirstate, [], 'debugcheckdirstate'),
749 749 "debugdumpdirstate": (debugdumpdirstate, [], 'debugdumpdirstate'),
750 750 "debugindex": (debugindex, [], 'debugindex <file>'),
751 751 "debugindexdot": (debugindexdot, [], 'debugindexdot <file>'),
752 752 "diff": (diff, [('r', 'rev', [], 'revision')],
753 753 'hg diff [-r A] [-r B] [files]'),
754 754 "export": (export, [], "hg export <changeset>"),
755 755 "forget": (forget, [], "hg forget [files]"),
756 756 "heads": (heads, [], 'hg heads'),
757 757 "help": (help, [], 'hg help [command]'),
758 758 "identify|id": (identify, [], 'hg identify'),
759 759 "import|patch": (import_,
760 760 [('p', 'strip', 1, 'path strip'),
761 761 ('b', 'base', "", 'base path')],
762 762 "hg import [options] <patches>"),
763 763 "init": (init, [], 'hg init'),
764 764 "log|history": (log, [], 'hg log [file]'),
765 765 "manifest": (manifest, [], 'hg manifest [rev]'),
766 766 "parents": (parents, [], 'hg parents [node]'),
767 767 "pull": (pull,
768 768 [('u', 'update', None, 'update working directory')],
769 769 'hg pull [options] [source]'),
770 770 "push": (push, [], 'hg push <destination>'),
771 771 "rawcommit": (rawcommit,
772 772 [('p', 'parent', [], 'parent'),
773 773 ('d', 'date', "", 'data'),
774 774 ('u', 'user', "", 'user'),
775 775 ('F', 'files', "", 'file list'),
776 776 ('t', 'text', "", 'commit text'),
777 777 ('l', 'logfile', "", 'commit text file')],
778 778 'hg rawcommit [options] [files]'),
779 779 "recover": (recover, [], "hg recover"),
780 780 "remove|rm": (remove, [], "hg remove [files]"),
781 781 "root": (root, [], "hg root"),
782 782 "serve": (serve, [('p', 'port', 8000, 'listen port'),
783 783 ('a', 'address', '', 'interface address'),
784 784 ('n', 'name', os.getcwd(), 'repository name'),
785 785 ('t', 'templates', "", 'template map')],
786 786 "hg serve [options]"),
787 787 "status": (status, [], 'hg status'),
788 788 "tag": (tag, [('t', 'text', "", 'commit text'),
789 789 ('d', 'date', "", 'date'),
790 790 ('u', 'user', "", 'user')],
791 791 'hg tag [options] <name> [rev]'),
792 792 "tags": (tags, [], 'hg tags'),
793 793 "tip": (tip, [], 'hg tip'),
794 794 "undo": (undo, [], 'hg undo'),
795 795 "update|up|checkout|co":
796 796 (update,
797 797 [('m', 'merge', None, 'allow merging of conflicts'),
798 798 ('C', 'clean', None, 'overwrite locally modified files')],
799 799 'hg update [options] [node]'),
800 800 "verify": (verify, [], 'hg verify'),
801 801 "version": (show_version, [], 'hg version'),
802 802 }
803 803
804 804 norepo = "clone init version help debugindex debugindexdot"
805 805
806 806 def find(cmd):
807 807 for e in table.keys():
808 808 if re.match("(%s)$" % e, cmd):
809 809 return table[e]
810 810
811 811 raise UnknownCommand(cmd)
812 812
813 813 class SignalInterrupt(Exception): pass
814 814
815 815 def catchterm(*args):
816 816 raise SignalInterrupt
817 817
818 818 def run():
819 819 sys.exit(dispatch(sys.argv[1:]))
820 820
821 821 def dispatch(args):
822 822 options = {}
823 823 opts = [('v', 'verbose', None, 'verbose'),
824 824 ('d', 'debug', None, 'debug'),
825 825 ('q', 'quiet', None, 'quiet'),
826 826 ('p', 'profile', None, 'profile'),
827 827 ('R', 'repository', "", 'repository root directory'),
828 828 ('', 'traceback', None, 'print traceback on exception'),
829 829 ('y', 'noninteractive', None, 'run non-interactively'),
830 830 ('', 'version', None, 'output version information and exit'),
831 831 ]
832 832
833 833 args = fancyopts.fancyopts(args, opts, options,
834 834 'hg [options] <command> [options] [files]')
835 835
836 836 if not args:
837 837 cmd = "help"
838 838 else:
839 839 cmd, args = args[0], args[1:]
840 840
841 841 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
842 842 not options["noninteractive"])
843 843
844 844 if options["version"]:
845 845 show_version(u)
846 846 sys.exit(0)
847 847
848 848 try:
849 849 i = find(cmd)
850 850 except UnknownCommand:
851 851 u.warn("hg: unknown command '%s'\n" % cmd)
852 852 help(u)
853 853 sys.exit(1)
854 854
855 855 signal.signal(signal.SIGTERM, catchterm)
856 856
857 857 cmdoptions = {}
858 858 try:
859 859 args = fancyopts.fancyopts(args, i[1], cmdoptions, i[2])
860 860 except fancyopts.getopt.GetoptError, inst:
861 861 u.warn("hg %s: %s\n" % (cmd, inst))
862 862 help(u, cmd)
863 863 sys.exit(-1)
864 864
865 865 try:
866 866 try:
867 867 if cmd not in norepo.split():
868 868 path = options["repository"] or ""
869 869 repo = hg.repository(ui=u, path=path)
870 870 d = lambda: i[0](u, repo, *args, **cmdoptions)
871 871 else:
872 872 d = lambda: i[0](u, *args, **cmdoptions)
873 873
874 874 if options['profile']:
875 875 import hotshot, hotshot.stats
876 876 prof = hotshot.Profile("hg.prof")
877 877 r = prof.runcall(d)
878 878 prof.close()
879 879 stats = hotshot.stats.load("hg.prof")
880 880 stats.strip_dirs()
881 881 stats.sort_stats('time', 'calls')
882 882 stats.print_stats(40)
883 883 return r
884 884 else:
885 885 return d()
886 886 except:
887 887 if options['traceback']:
888 888 traceback.print_exc()
889 889 raise
890 890 except util.CommandError, inst:
891 891 u.warn("abort: %s\n" % inst.args)
892 892 except hg.RepoError, inst:
893 893 u.warn("abort: ", inst, "!\n")
894 894 except SignalInterrupt:
895 895 u.warn("killed!\n")
896 896 except KeyboardInterrupt:
897 897 u.warn("interrupted!\n")
898 898 except IOError, inst:
899 899 if hasattr(inst, "code"):
900 900 u.warn("abort: %s\n" % inst)
901 901 elif hasattr(inst, "reason"):
902 902 u.warn("abort: error %d: %s\n" % (inst.reason[0], inst.reason[1]))
903 903 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
904 904 u.warn("broken pipe\n")
905 905 else:
906 906 raise
907 907 except TypeError, inst:
908 908 # was this an argument error?
909 909 tb = traceback.extract_tb(sys.exc_info()[2])
910 910 if len(tb) > 2: # no
911 911 raise
912 912 u.debug(inst, "\n")
913 913 u.warn("%s: invalid arguments\n" % i[0].__name__)
914 914 help(u, cmd)
915 915
916 916 sys.exit(-1)
917 917
@@ -1,1496 +1,1522 b''
1 1 # hg.py - repository classes 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 sys, struct, os
9 9 import util
10 10 from revlog import *
11 11 from demandload import *
12 12 demandload(globals(), "re lock urllib urllib2 transaction time socket")
13 13 demandload(globals(), "tempfile httprangereader bdiff")
14 14
15 15 class filelog(revlog):
16 16 def __init__(self, opener, path):
17 17 revlog.__init__(self, opener,
18 18 os.path.join("data", path + ".i"),
19 19 os.path.join("data", path + ".d"))
20 20
21 21 def read(self, node):
22 22 t = self.revision(node)
23 23 if t[:2] != '\1\n':
24 24 return t
25 25 s = t.find('\1\n', 2)
26 26 return t[s+2:]
27 27
28 28 def readmeta(self, node):
29 29 t = self.revision(node)
30 30 if t[:2] != '\1\n':
31 31 return t
32 32 s = t.find('\1\n', 2)
33 33 mt = t[2:s]
34 34 for l in mt.splitlines():
35 35 k, v = l.split(": ", 1)
36 36 m[k] = v
37 37 return m
38 38
39 39 def add(self, text, meta, transaction, link, p1=None, p2=None):
40 40 if meta or text[:2] == '\1\n':
41 41 mt = ""
42 42 if meta:
43 43 mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ]
44 44 text = "\1\n" + "".join(mt) + "\1\n" + text
45 45 return self.addrevision(text, transaction, link, p1, p2)
46 46
47 47 def annotate(self, node):
48 48
49 49 def decorate(text, rev):
50 50 return ([rev] * len(text.splitlines()), text)
51 51
52 52 def pair(parent, child):
53 53 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
54 54 child[0][b1:b2] = parent[0][a1:a2]
55 55 return child
56 56
57 57 # find all ancestors
58 58 needed = {node:1}
59 59 visit = [node]
60 60 while visit:
61 61 n = visit.pop(0)
62 62 for p in self.parents(n):
63 63 if p not in needed:
64 64 needed[p] = 1
65 65 visit.append(p)
66 66 else:
67 67 # count how many times we'll use this
68 68 needed[p] += 1
69 69
70 70 # sort by revision which is a topological order
71 71 visit = [ (self.rev(n), n) for n in needed.keys() ]
72 72 visit.sort()
73 73 hist = {}
74 74
75 75 for r,n in visit:
76 76 curr = decorate(self.read(n), self.linkrev(n))
77 77 for p in self.parents(n):
78 78 if p != nullid:
79 79 curr = pair(hist[p], curr)
80 80 # trim the history of unneeded revs
81 81 needed[p] -= 1
82 82 if not needed[p]:
83 83 del hist[p]
84 84 hist[n] = curr
85 85
86 86 return zip(hist[n][0], hist[n][1].splitlines(1))
87 87
88 88 class manifest(revlog):
89 89 def __init__(self, opener):
90 90 self.mapcache = None
91 91 self.listcache = None
92 92 self.addlist = None
93 93 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
94 94
95 95 def read(self, node):
96 96 if node == nullid: return {} # don't upset local cache
97 97 if self.mapcache and self.mapcache[0] == node:
98 98 return self.mapcache[1].copy()
99 99 text = self.revision(node)
100 100 map = {}
101 101 flag = {}
102 102 self.listcache = (text, text.splitlines(1))
103 103 for l in self.listcache[1]:
104 104 (f, n) = l.split('\0')
105 105 map[f] = bin(n[:40])
106 106 flag[f] = (n[40:-1] == "x")
107 107 self.mapcache = (node, map, flag)
108 108 return map
109 109
110 110 def readflags(self, node):
111 111 if node == nullid: return {} # don't upset local cache
112 112 if not self.mapcache or self.mapcache[0] != node:
113 113 self.read(node)
114 114 return self.mapcache[2]
115 115
116 116 def diff(self, a, b):
117 117 # this is sneaky, as we're not actually using a and b
118 118 if self.listcache and self.addlist and self.listcache[0] == a:
119 119 d = mdiff.diff(self.listcache[1], self.addlist, 1)
120 120 if mdiff.patch(a, d) != b:
121 121 sys.stderr.write("*** sortdiff failed, falling back ***\n")
122 122 return mdiff.textdiff(a, b)
123 123 return d
124 124 else:
125 125 return mdiff.textdiff(a, b)
126 126
127 127 def add(self, map, flags, transaction, link, p1=None, p2=None):
128 128 files = map.keys()
129 129 files.sort()
130 130
131 131 self.addlist = ["%s\000%s%s\n" %
132 132 (f, hex(map[f]), flags[f] and "x" or '')
133 133 for f in files]
134 134 text = "".join(self.addlist)
135 135
136 136 n = self.addrevision(text, transaction, link, p1, p2)
137 137 self.mapcache = (n, map, flags)
138 138 self.listcache = (text, self.addlist)
139 139 self.addlist = None
140 140
141 141 return n
142 142
143 143 class changelog(revlog):
144 144 def __init__(self, opener):
145 145 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
146 146
147 147 def extract(self, text):
148 148 if not text:
149 149 return (nullid, "", "0", [], "")
150 150 last = text.index("\n\n")
151 151 desc = text[last + 2:]
152 152 l = text[:last].splitlines()
153 153 manifest = bin(l[0])
154 154 user = l[1]
155 155 date = l[2]
156 156 files = l[3:]
157 157 return (manifest, user, date, files, desc)
158 158
159 159 def read(self, node):
160 160 return self.extract(self.revision(node))
161 161
162 162 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
163 163 user=None, date=None):
164 164 user = (user or
165 165 os.environ.get("HGUSER") or
166 166 os.environ.get("EMAIL") or
167 167 (os.environ.get("LOGNAME",
168 168 os.environ.get("USERNAME", "unknown"))
169 169 + '@' + socket.getfqdn()))
170 170 date = date or "%d %d" % (time.time(), time.timezone)
171 171 list.sort()
172 172 l = [hex(manifest), user, date] + list + ["", desc]
173 173 text = "\n".join(l)
174 174 return self.addrevision(text, transaction, self.count(), p1, p2)
175 175
176 176 class dirstate:
177 177 def __init__(self, opener, ui, root):
178 178 self.opener = opener
179 179 self.root = root
180 180 self.dirty = 0
181 181 self.ui = ui
182 182 self.map = None
183 183 self.pl = None
184 184 self.copies = {}
185 185
186 186 def __del__(self):
187 187 if self.dirty:
188 188 self.write()
189 189
190 190 def __getitem__(self, key):
191 191 try:
192 192 return self.map[key]
193 193 except TypeError:
194 194 self.read()
195 195 return self[key]
196 196
197 197 def __contains__(self, key):
198 198 if not self.map: self.read()
199 199 return key in self.map
200 200
201 201 def parents(self):
202 202 if not self.pl:
203 203 self.read()
204 204 return self.pl
205 205
206 206 def setparents(self, p1, p2 = nullid):
207 207 self.dirty = 1
208 208 self.pl = p1, p2
209 209
210 210 def state(self, key):
211 211 try:
212 212 return self[key][0]
213 213 except KeyError:
214 214 return "?"
215 215
216 216 def read(self):
217 217 if self.map is not None: return self.map
218 218
219 219 self.map = {}
220 220 self.pl = [nullid, nullid]
221 221 try:
222 222 st = self.opener("dirstate").read()
223 223 if not st: return
224 224 except: return
225 225
226 226 self.pl = [st[:20], st[20: 40]]
227 227
228 228 pos = 40
229 229 while pos < len(st):
230 230 e = struct.unpack(">cllll", st[pos:pos+17])
231 231 l = e[4]
232 232 pos += 17
233 233 f = st[pos:pos + l]
234 234 if '\0' in f:
235 235 f, c = f.split('\0')
236 236 self.copies[f] = c
237 237 self.map[f] = e[:4]
238 238 pos += l
239 239
240 240 def copy(self, source, dest):
241 241 self.read()
242 242 self.dirty = 1
243 243 self.copies[dest] = source
244 244
245 245 def copied(self, file):
246 246 return self.copies.get(file, None)
247 247
248 248 def update(self, files, state):
249 249 ''' current states:
250 250 n normal
251 251 m needs merging
252 252 r marked for removal
253 253 a marked for addition'''
254 254
255 255 if not files: return
256 256 self.read()
257 257 self.dirty = 1
258 258 for f in files:
259 259 if state == "r":
260 260 self.map[f] = ('r', 0, 0, 0)
261 261 else:
262 262 s = os.stat(os.path.join(self.root, f))
263 263 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
264 264
265 265 def forget(self, files):
266 266 if not files: return
267 267 self.read()
268 268 self.dirty = 1
269 269 for f in files:
270 270 try:
271 271 del self.map[f]
272 272 except KeyError:
273 273 self.ui.warn("not in dirstate: %s!\n" % f)
274 274 pass
275 275
276 276 def clear(self):
277 277 self.map = {}
278 278 self.dirty = 1
279 279
280 280 def write(self):
281 281 st = self.opener("dirstate", "w")
282 282 st.write("".join(self.pl))
283 283 for f, e in self.map.items():
284 284 c = self.copied(f)
285 285 if c:
286 286 f = f + "\0" + c
287 287 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
288 288 st.write(e + f)
289 289 self.dirty = 0
290 290
291 def dup(self):
291 def changes(self, files, ignore):
292 292 self.read()
293 return self.map.copy()
293 dc = self.map.copy()
294 lookup, changed, added, unknown = [], [], [], []
295
296 # compare all files by default
297 if not files: files = [self.root]
298
299 def uniq(g):
300 seen = {}
301 for f in g:
302 if f not in seen:
303 seen[f] = 1
304 yield f
305
306 # recursive generator of all files listed
307 def walk(files):
308 for f in uniq(files):
309 if os.path.isdir(f):
310 for dir, subdirs, fl in os.walk(f):
311 d = dir[len(self.root) + 1:]
312 if ".hg" in subdirs: subdirs.remove(".hg")
313 for fn in fl:
314 fn = util.pconvert(os.path.join(d, fn))
315 yield fn
316 else:
317 yield f[len(self.root) + 1:]
318
319 for fn in uniq(walk(files)):
320 try: s = os.stat(os.path.join(self.root, fn))
321 except: continue
322
323 if fn in dc:
324 c = dc[fn]
325 del dc[fn]
326
327 if c[0] == 'm':
328 changed.append(fn)
329 elif c[0] == 'a':
330 added.append(fn)
331 elif c[0] == 'r':
332 unknown.append(fn)
333 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
334 changed.append(fn)
335 elif c[1] != s.st_mode or c[3] != s.st_mtime:
336 lookup.append(fn)
337 else:
338 if not ignore(fn): unknown.append(fn)
339
340 return (lookup, changed, added, dc.keys(), unknown)
294 341
295 342 # used to avoid circular references so destructors work
296 343 def opener(base):
297 344 p = base
298 345 def o(path, mode="r"):
299 346 if p[:7] == "http://":
300 347 f = os.path.join(p, urllib.quote(path))
301 348 return httprangereader.httprangereader(f)
302 349
303 350 f = os.path.join(p, path)
304 351
305 352 mode += "b" # for that other OS
306 353
307 354 if mode[0] != "r":
308 355 try:
309 356 s = os.stat(f)
310 357 except OSError:
311 358 d = os.path.dirname(f)
312 359 if not os.path.isdir(d):
313 360 os.makedirs(d)
314 361 else:
315 362 if s.st_nlink > 1:
316 363 file(f + ".tmp", "wb").write(file(f, "rb").read())
317 364 util.rename(f+".tmp", f)
318 365
319 366 return file(f, mode)
320 367
321 368 return o
322 369
323 370 class RepoError(Exception): pass
324 371
325 372 class localrepository:
326 373 def __init__(self, ui, path=None, create=0):
327 374 self.remote = 0
328 375 if path and path[:7] == "http://":
329 376 self.remote = 1
330 377 self.path = path
331 378 else:
332 379 if not path:
333 380 p = os.getcwd()
334 381 while not os.path.isdir(os.path.join(p, ".hg")):
335 382 oldp = p
336 383 p = os.path.dirname(p)
337 384 if p == oldp: raise RepoError("no repo found")
338 385 path = p
339 386 self.path = os.path.join(path, ".hg")
340 387
341 388 if not create and not os.path.isdir(self.path):
342 389 raise RepoError("repository %s not found" % self.path)
343 390
344 391 self.root = path
345 392 self.ui = ui
346 393
347 394 if create:
348 395 os.mkdir(self.path)
349 396 os.mkdir(self.join("data"))
350 397
351 398 self.opener = opener(self.path)
352 399 self.wopener = opener(self.root)
353 400 self.manifest = manifest(self.opener)
354 401 self.changelog = changelog(self.opener)
355 402 self.ignorelist = None
356 403 self.tagscache = None
357 404 self.nodetagscache = None
358 405
359 406 if not self.remote:
360 407 self.dirstate = dirstate(self.opener, ui, self.root)
361 408 try:
362 409 self.ui.readconfig(self.opener("hgrc"))
363 410 except IOError: pass
364 411
365 412 def ignore(self, f):
366 413 if self.ignorelist is None:
367 414 self.ignorelist = []
368 415 try:
369 416 l = file(self.wjoin(".hgignore"))
370 417 for pat in l:
371 418 if pat != "\n":
372 419 self.ignorelist.append(re.compile(util.pconvert(pat[:-1])))
373 420 except IOError: pass
374 421 for pat in self.ignorelist:
375 422 if pat.search(f): return True
376 423 return False
377 424
378 425 def hook(self, name, **args):
379 426 s = self.ui.config("hooks", name)
380 427 if s:
381 428 self.ui.note("running hook %s: %s\n" % (name, s))
382 429 old = {}
383 430 for k, v in args.items():
384 431 k = k.upper()
385 432 old[k] = os.environ.get(k, None)
386 433 os.environ[k] = v
387 434
388 435 r = os.system(s)
389 436
390 437 for k, v in old.items():
391 438 if v != None:
392 439 os.environ[k] = v
393 440 else:
394 441 del os.environ[k]
395 442
396 443 if r:
397 444 self.ui.warn("abort: %s hook failed with status %d!\n" %
398 445 (name, r))
399 446 return False
400 447 return True
401 448
402 449 def tags(self):
403 450 '''return a mapping of tag to node'''
404 451 if not self.tagscache:
405 452 self.tagscache = {}
406 453 try:
407 454 # read each head of the tags file, ending with the tip
408 455 # and add each tag found to the map, with "newer" ones
409 456 # taking precedence
410 457 fl = self.file(".hgtags")
411 458 h = fl.heads()
412 459 h.reverse()
413 460 for r in h:
414 461 for l in fl.revision(r).splitlines():
415 462 if l:
416 463 n, k = l.split(" ", 1)
417 464 try:
418 465 bin_n = bin(n)
419 466 except TypeError:
420 467 bin_n = ''
421 468 self.tagscache[k.strip()] = bin_n
422 469 except KeyError:
423 470 pass
424 471 for k, n in self.ui.configitems("tags"):
425 472 try:
426 473 bin_n = bin(n)
427 474 except TypeError:
428 475 bin_n = ''
429 476 self.tagscache[k] = bin_n
430 477
431 478 self.tagscache['tip'] = self.changelog.tip()
432 479
433 480 return self.tagscache
434 481
435 482 def tagslist(self):
436 483 '''return a list of tags ordered by revision'''
437 484 l = []
438 485 for t, n in self.tags().items():
439 486 try:
440 487 r = self.changelog.rev(n)
441 488 except:
442 489 r = -2 # sort to the beginning of the list if unknown
443 490 l.append((r,t,n))
444 491 l.sort()
445 492 return [(t,n) for r,t,n in l]
446 493
447 494 def nodetags(self, node):
448 495 '''return the tags associated with a node'''
449 496 if not self.nodetagscache:
450 497 self.nodetagscache = {}
451 498 for t,n in self.tags().items():
452 499 self.nodetagscache.setdefault(n,[]).append(t)
453 500 return self.nodetagscache.get(node, [])
454 501
455 502 def lookup(self, key):
456 503 try:
457 504 return self.tags()[key]
458 505 except KeyError:
459 506 return self.changelog.lookup(key)
460 507
461 508 def join(self, f):
462 509 return os.path.join(self.path, f)
463 510
464 511 def wjoin(self, f):
465 512 return os.path.join(self.root, f)
466 513
467 514 def file(self, f):
468 515 if f[0] == '/': f = f[1:]
469 516 return filelog(self.opener, f)
470 517
471 518 def wfile(self, f, mode='r'):
472 519 return self.wopener(f, mode)
473 520
474 521 def transaction(self):
475 522 # save dirstate for undo
476 523 try:
477 524 ds = self.opener("dirstate").read()
478 525 except IOError:
479 526 ds = ""
480 527 self.opener("undo.dirstate", "w").write(ds)
481 528
482 529 return transaction.transaction(self.opener, self.join("journal"),
483 530 self.join("undo"))
484 531
485 532 def recover(self):
486 533 lock = self.lock()
487 534 if os.path.exists(self.join("recover")):
488 535 self.ui.status("rolling back interrupted transaction\n")
489 536 return transaction.rollback(self.opener, self.join("recover"))
490 537 else:
491 538 self.ui.warn("no interrupted transaction available\n")
492 539
493 540 def undo(self):
494 541 lock = self.lock()
495 542 if os.path.exists(self.join("undo")):
496 543 self.ui.status("rolling back last transaction\n")
497 544 transaction.rollback(self.opener, self.join("undo"))
498 545 self.dirstate = None
499 546 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
500 547 self.dirstate = dirstate(self.opener, self.ui, self.root)
501 548 else:
502 549 self.ui.warn("no undo information available\n")
503 550
504 551 def lock(self, wait = 1):
505 552 try:
506 553 return lock.lock(self.join("lock"), 0)
507 554 except lock.LockHeld, inst:
508 555 if wait:
509 556 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
510 557 return lock.lock(self.join("lock"), wait)
511 558 raise inst
512 559
513 560 def rawcommit(self, files, text, user, date, p1=None, p2=None):
514 561 orig_parent = self.dirstate.parents()[0] or nullid
515 562 p1 = p1 or self.dirstate.parents()[0] or nullid
516 563 p2 = p2 or self.dirstate.parents()[1] or nullid
517 564 c1 = self.changelog.read(p1)
518 565 c2 = self.changelog.read(p2)
519 566 m1 = self.manifest.read(c1[0])
520 567 mf1 = self.manifest.readflags(c1[0])
521 568 m2 = self.manifest.read(c2[0])
522 569
523 570 if orig_parent == p1:
524 571 update_dirstate = 1
525 572 else:
526 573 update_dirstate = 0
527 574
528 575 tr = self.transaction()
529 576 mm = m1.copy()
530 577 mfm = mf1.copy()
531 578 linkrev = self.changelog.count()
532 579 for f in files:
533 580 try:
534 581 t = self.wfile(f).read()
535 582 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
536 583 r = self.file(f)
537 584 mfm[f] = tm
538 585 mm[f] = r.add(t, {}, tr, linkrev,
539 586 m1.get(f, nullid), m2.get(f, nullid))
540 587 if update_dirstate:
541 588 self.dirstate.update([f], "n")
542 589 except IOError:
543 590 try:
544 591 del mm[f]
545 592 del mfm[f]
546 593 if update_dirstate:
547 594 self.dirstate.forget([f])
548 595 except:
549 596 # deleted from p2?
550 597 pass
551 598
552 599 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
553 600 n = self.changelog.add(mnode, files, text, tr, p1, p2, user, date)
554 601 tr.close()
555 602 if update_dirstate:
556 603 self.dirstate.setparents(n, nullid)
557 604
558 605 def commit(self, files = None, text = "", user = None, date = None):
559 606 commit = []
560 607 remove = []
561 608 if files:
562 609 for f in files:
563 610 s = self.dirstate.state(f)
564 611 if s in 'nmai':
565 612 commit.append(f)
566 613 elif s == 'r':
567 614 remove.append(f)
568 615 else:
569 616 self.ui.warn("%s not tracked!\n" % f)
570 617 else:
571 (c, a, d, u) = self.diffdir(self.root)
618 (c, a, d, u) = self.changes(None, None)
572 619 commit = c + a
573 620 remove = d
574 621
575 622 if not commit and not remove:
576 623 self.ui.status("nothing changed\n")
577 624 return
578 625
579 626 if not self.hook("precommit"):
580 627 return 1
581 628
582 629 p1, p2 = self.dirstate.parents()
583 630 c1 = self.changelog.read(p1)
584 631 c2 = self.changelog.read(p2)
585 632 m1 = self.manifest.read(c1[0])
586 633 mf1 = self.manifest.readflags(c1[0])
587 634 m2 = self.manifest.read(c2[0])
588 635 lock = self.lock()
589 636 tr = self.transaction()
590 637
591 638 # check in files
592 639 new = {}
593 640 linkrev = self.changelog.count()
594 641 commit.sort()
595 642 for f in commit:
596 643 self.ui.note(f + "\n")
597 644 try:
598 645 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
599 646 t = self.wfile(f).read()
600 647 except IOError:
601 648 self.warn("trouble committing %s!\n" % f)
602 649 raise
603 650
604 651 meta = {}
605 652 cp = self.dirstate.copied(f)
606 653 if cp:
607 654 meta["copy"] = cp
608 655 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
609 656 self.ui.debug(" %s: copy %s:%s\n" % (f, cp, meta["copyrev"]))
610 657
611 658 r = self.file(f)
612 659 fp1 = m1.get(f, nullid)
613 660 fp2 = m2.get(f, nullid)
614 661 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
615 662
616 663 # update manifest
617 664 m1.update(new)
618 665 for f in remove:
619 666 if f in m1:
620 667 del m1[f]
621 668 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0])
622 669
623 670 # add changeset
624 671 new = new.keys()
625 672 new.sort()
626 673
627 674 if not text:
628 675 edittext = "\n" + "HG: manifest hash %s\n" % hex(mn)
629 676 edittext += "".join(["HG: changed %s\n" % f for f in new])
630 677 edittext += "".join(["HG: removed %s\n" % f for f in remove])
631 678 edittext = self.ui.edit(edittext)
632 679 if not edittext.rstrip():
633 680 return 1
634 681 text = edittext
635 682
636 683 n = self.changelog.add(mn, new, text, tr, p1, p2, user, date)
637 684
638 685 if not self.hook("commit", node=hex(n)):
639 686 return 1
640 687
641 688 tr.close()
642 689
643 690 self.dirstate.setparents(n)
644 691 self.dirstate.update(new, "n")
645 692 self.dirstate.forget(remove)
646 693
647 def diffdir(self, path, changeset = None):
648 changed = []
649 added = []
650 unknown = []
651 mf = {}
694 def changes(self, node1, node2, *files):
695 # changed, added, deleted, unknown
696 c, a, d, u, mf1 = [], [], [], [], None
652 697
653 if changeset:
654 change = self.changelog.read(changeset)
655 mf = self.manifest.read(change[0])
656 dc = dict.fromkeys(mf)
657 else:
658 changeset = self.dirstate.parents()[0]
659 change = self.changelog.read(changeset)
660 mf = self.manifest.read(change[0])
661 dc = self.dirstate.dup()
662
663 def fcmp(fn):
698 def fcmp(fn, mf):
664 699 t1 = self.wfile(fn).read()
665 700 t2 = self.file(fn).revision(mf[fn])
666 701 return cmp(t1, t2)
667 702
668 for dir, subdirs, files in os.walk(path):
669 d = dir[len(self.root)+1:]
670 if ".hg" in subdirs: subdirs.remove(".hg")
703 # are we comparing the working directory?
704 if not node1:
705 l, c, a, d, u = self.dirstate.changes(files, self.ignore)
706
707 # are we comparing working dir against its parent?
708 if not node2:
709 if l:
710 # do a full compare of any files that might have changed
711 change = self.changelog.read(self.dirstate.parents()[0])
712 mf1 = self.manifest.read(change[0])
713 for f in lookup:
714 if fcmp(f, mf):
715 c.append(f)
716 return (c, a, d, u)
671 717
672 for f in files:
673 fn = util.pconvert(os.path.join(d, f))
674 try: s = os.stat(os.path.join(self.root, fn))
675 except: continue
676 if fn in dc:
677 c = dc[fn]
678 del dc[fn]
679 if not c:
680 if fcmp(fn):
681 changed.append(fn)
682 elif c[0] == 'm':
683 changed.append(fn)
684 elif c[0] == 'a':
685 added.append(fn)
686 elif c[0] == 'r':
687 unknown.append(fn)
688 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
689 changed.append(fn)
690 elif c[1] != s.st_mode or c[3] != s.st_mtime:
691 if fcmp(fn):
692 changed.append(fn)
693 else:
694 if self.ignore(fn): continue
695 unknown.append(fn)
718 # are we comparing working dir against non-tip?
719 # generate a pseudo-manifest for the working dir
720 if not node1:
721 if not mf1:
722 change = self.changelog.read(self.dirstate.parents()[0])
723 mf1 = self.manifest.read(change[0])
724 for f in a + c + l:
725 mf1[f] = ""
726 for f in d:
727 if f in mf1: del mf1[f]
728 else:
729 change = self.changelog.read(node1)
730 mf1 = self.manifest.read(change[0])
696 731
697 deleted = dc.keys()
698 deleted.sort()
699
700 return (changed, added, deleted, unknown)
701
702 def diffrevs(self, node1, node2):
703 changed, added = [], []
704
705 change = self.changelog.read(node1)
706 mf1 = self.manifest.read(change[0])
707 732 change = self.changelog.read(node2)
708 733 mf2 = self.manifest.read(change[0])
709 734
710 735 for fn in mf2:
711 736 if mf1.has_key(fn):
712 737 if mf1[fn] != mf2[fn]:
713 changed.append(fn)
738 if mf1[fn] != "" or fcmp(fn, mf2):
739 c.append(fn)
714 740 del mf1[fn]
715 741 else:
716 added.append(fn)
742 a.append(fn)
717 743
718 deleted = mf1.keys()
719 deleted.sort()
744 d = mf1.keys()
745 d.sort()
720 746
721 return (changed, added, deleted)
747 return (c, a, d, u)
722 748
723 749 def add(self, list):
724 750 for f in list:
725 751 p = self.wjoin(f)
726 752 if not os.path.isfile(p):
727 753 self.ui.warn("%s does not exist!\n" % f)
728 754 elif self.dirstate.state(f) == 'n':
729 755 self.ui.warn("%s already tracked!\n" % f)
730 756 else:
731 757 self.dirstate.update([f], "a")
732 758
733 759 def forget(self, list):
734 760 for f in list:
735 761 if self.dirstate.state(f) not in 'ai':
736 762 self.ui.warn("%s not added!\n" % f)
737 763 else:
738 764 self.dirstate.forget([f])
739 765
740 766 def remove(self, list):
741 767 for f in list:
742 768 p = self.wjoin(f)
743 769 if os.path.isfile(p):
744 770 self.ui.warn("%s still exists!\n" % f)
745 771 elif self.dirstate.state(f) == 'a':
746 772 self.ui.warn("%s never committed!\n" % f)
747 773 self.dirstate.forget(f)
748 774 elif f not in self.dirstate:
749 775 self.ui.warn("%s not tracked!\n" % f)
750 776 else:
751 777 self.dirstate.update([f], "r")
752 778
753 779 def copy(self, source, dest):
754 780 p = self.wjoin(dest)
755 781 if not os.path.isfile(dest):
756 782 self.ui.warn("%s does not exist!\n" % dest)
757 783 else:
758 784 if self.dirstate.state(dest) == '?':
759 785 self.dirstate.update([dest], "a")
760 786 self.dirstate.copy(source, dest)
761 787
762 788 def heads(self):
763 789 return self.changelog.heads()
764 790
765 791 def branches(self, nodes):
766 792 if not nodes: nodes = [self.changelog.tip()]
767 793 b = []
768 794 for n in nodes:
769 795 t = n
770 796 while n:
771 797 p = self.changelog.parents(n)
772 798 if p[1] != nullid or p[0] == nullid:
773 799 b.append((t, n, p[0], p[1]))
774 800 break
775 801 n = p[0]
776 802 return b
777 803
778 804 def between(self, pairs):
779 805 r = []
780 806
781 807 for top, bottom in pairs:
782 808 n, l, i = top, [], 0
783 809 f = 1
784 810
785 811 while n != bottom:
786 812 p = self.changelog.parents(n)[0]
787 813 if i == f:
788 814 l.append(n)
789 815 f = f * 2
790 816 n = p
791 817 i += 1
792 818
793 819 r.append(l)
794 820
795 821 return r
796 822
797 823 def newer(self, nodes):
798 824 m = {}
799 825 nl = []
800 826 pm = {}
801 827 cl = self.changelog
802 828 t = l = cl.count()
803 829
804 830 # find the lowest numbered node
805 831 for n in nodes:
806 832 l = min(l, cl.rev(n))
807 833 m[n] = 1
808 834
809 835 for i in xrange(l, t):
810 836 n = cl.node(i)
811 837 if n in m: # explicitly listed
812 838 pm[n] = 1
813 839 nl.append(n)
814 840 continue
815 841 for p in cl.parents(n):
816 842 if p in pm: # parent listed
817 843 pm[n] = 1
818 844 nl.append(n)
819 845 break
820 846
821 847 return nl
822 848
823 849 def findincoming(self, remote):
824 850 m = self.changelog.nodemap
825 851 search = []
826 852 fetch = []
827 853 seen = {}
828 854 seenbranch = {}
829 855
830 856 # if we have an empty repo, fetch everything
831 857 if self.changelog.tip() == nullid:
832 858 self.ui.status("requesting all changes\n")
833 859 return [nullid]
834 860
835 861 # otherwise, assume we're closer to the tip than the root
836 862 self.ui.status("searching for changes\n")
837 863 heads = remote.heads()
838 864 unknown = []
839 865 for h in heads:
840 866 if h not in m:
841 867 unknown.append(h)
842 868
843 869 if not unknown:
844 870 return None
845 871
846 872 rep = {}
847 873 reqcnt = 0
848 874
849 875 unknown = remote.branches(unknown)
850 876 while unknown:
851 877 r = []
852 878 while unknown:
853 879 n = unknown.pop(0)
854 880 if n[0] in seen:
855 881 continue
856 882
857 883 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
858 884 if n[0] == nullid:
859 885 break
860 886 if n in seenbranch:
861 887 self.ui.debug("branch already found\n")
862 888 continue
863 889 if n[1] and n[1] in m: # do we know the base?
864 890 self.ui.debug("found incomplete branch %s:%s\n"
865 891 % (short(n[0]), short(n[1])))
866 892 search.append(n) # schedule branch range for scanning
867 893 seenbranch[n] = 1
868 894 else:
869 895 if n[1] not in seen and n[1] not in fetch:
870 896 if n[2] in m and n[3] in m:
871 897 self.ui.debug("found new changeset %s\n" %
872 898 short(n[1]))
873 899 fetch.append(n[1]) # earliest unknown
874 900 continue
875 901
876 902 for a in n[2:4]:
877 903 if a not in rep:
878 904 r.append(a)
879 905 rep[a] = 1
880 906
881 907 seen[n[0]] = 1
882 908
883 909 if r:
884 910 reqcnt += 1
885 911 self.ui.debug("request %d: %s\n" %
886 912 (reqcnt, " ".join(map(short, r))))
887 913 for p in range(0, len(r), 10):
888 914 for b in remote.branches(r[p:p+10]):
889 915 self.ui.debug("received %s:%s\n" %
890 916 (short(b[0]), short(b[1])))
891 917 if b[0] not in m and b[0] not in seen:
892 918 unknown.append(b)
893 919
894 920 while search:
895 921 n = search.pop(0)
896 922 reqcnt += 1
897 923 l = remote.between([(n[0], n[1])])[0]
898 924 l.append(n[1])
899 925 p = n[0]
900 926 f = 1
901 927 for i in l:
902 928 self.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
903 929 if i in m:
904 930 if f <= 2:
905 931 self.ui.debug("found new branch changeset %s\n" %
906 932 short(p))
907 933 fetch.append(p)
908 934 else:
909 935 self.ui.debug("narrowed branch search to %s:%s\n"
910 936 % (short(p), short(i)))
911 937 search.append((p, i))
912 938 break
913 939 p, f = i, f * 2
914 940
915 941 for f in fetch:
916 942 if f in m:
917 943 raise RepoError("already have changeset " + short(f[:4]))
918 944
919 945 if fetch == [nullid]:
920 946 self.ui.warn("warning: pulling from an unrelated repository!\n")
921 947
922 948 self.ui.note("adding new changesets starting at " +
923 949 " ".join([short(f) for f in fetch]) + "\n")
924 950
925 951 self.ui.debug("%d total queries\n" % reqcnt)
926 952
927 953 return fetch
928 954
929 955 def changegroup(self, basenodes):
930 956 nodes = self.newer(basenodes)
931 957
932 958 # construct the link map
933 959 linkmap = {}
934 960 for n in nodes:
935 961 linkmap[self.changelog.rev(n)] = n
936 962
937 963 # construct a list of all changed files
938 964 changed = {}
939 965 for n in nodes:
940 966 c = self.changelog.read(n)
941 967 for f in c[3]:
942 968 changed[f] = 1
943 969 changed = changed.keys()
944 970 changed.sort()
945 971
946 972 # the changegroup is changesets + manifests + all file revs
947 973 revs = [ self.changelog.rev(n) for n in nodes ]
948 974
949 975 for y in self.changelog.group(linkmap): yield y
950 976 for y in self.manifest.group(linkmap): yield y
951 977 for f in changed:
952 978 yield struct.pack(">l", len(f) + 4) + f
953 979 g = self.file(f).group(linkmap)
954 980 for y in g:
955 981 yield y
956 982
957 983 def addchangegroup(self, generator):
958 984
959 985 class genread:
960 986 def __init__(self, generator):
961 987 self.g = generator
962 988 self.buf = ""
963 989 def read(self, l):
964 990 while l > len(self.buf):
965 991 try:
966 992 self.buf += self.g.next()
967 993 except StopIteration:
968 994 break
969 995 d, self.buf = self.buf[:l], self.buf[l:]
970 996 return d
971 997
972 998 def getchunk():
973 999 d = source.read(4)
974 1000 if not d: return ""
975 1001 l = struct.unpack(">l", d)[0]
976 1002 if l <= 4: return ""
977 1003 return source.read(l - 4)
978 1004
979 1005 def getgroup():
980 1006 while 1:
981 1007 c = getchunk()
982 1008 if not c: break
983 1009 yield c
984 1010
985 1011 def csmap(x):
986 1012 self.ui.debug("add changeset %s\n" % short(x))
987 1013 return self.changelog.count()
988 1014
989 1015 def revmap(x):
990 1016 return self.changelog.rev(x)
991 1017
992 1018 if not generator: return
993 1019 changesets = files = revisions = 0
994 1020
995 1021 source = genread(generator)
996 1022 lock = self.lock()
997 1023 tr = self.transaction()
998 1024
999 1025 # pull off the changeset group
1000 1026 self.ui.status("adding changesets\n")
1001 1027 co = self.changelog.tip()
1002 1028 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
1003 1029 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
1004 1030
1005 1031 # pull off the manifest group
1006 1032 self.ui.status("adding manifests\n")
1007 1033 mm = self.manifest.tip()
1008 1034 mo = self.manifest.addgroup(getgroup(), revmap, tr)
1009 1035
1010 1036 # process the files
1011 1037 self.ui.status("adding file revisions\n")
1012 1038 while 1:
1013 1039 f = getchunk()
1014 1040 if not f: break
1015 1041 self.ui.debug("adding %s revisions\n" % f)
1016 1042 fl = self.file(f)
1017 1043 o = fl.count()
1018 1044 n = fl.addgroup(getgroup(), revmap, tr)
1019 1045 revisions += fl.count() - o
1020 1046 files += 1
1021 1047
1022 1048 self.ui.status(("modified %d files, added %d changesets" +
1023 1049 " and %d new revisions\n")
1024 1050 % (files, changesets, revisions))
1025 1051
1026 1052 tr.close()
1027 1053 return
1028 1054
1029 1055 def update(self, node, allow=False, force=False):
1030 1056 pl = self.dirstate.parents()
1031 1057 if not force and pl[1] != nullid:
1032 1058 self.ui.warn("aborting: outstanding uncommitted merges\n")
1033 1059 return
1034 1060
1035 1061 p1, p2 = pl[0], node
1036 1062 pa = self.changelog.ancestor(p1, p2)
1037 1063 m1n = self.changelog.read(p1)[0]
1038 1064 m2n = self.changelog.read(p2)[0]
1039 1065 man = self.manifest.ancestor(m1n, m2n)
1040 1066 m1 = self.manifest.read(m1n)
1041 1067 mf1 = self.manifest.readflags(m1n)
1042 1068 m2 = self.manifest.read(m2n)
1043 1069 mf2 = self.manifest.readflags(m2n)
1044 1070 ma = self.manifest.read(man)
1045 1071 mfa = self.manifest.readflags(man)
1046 1072
1047 (c, a, d, u) = self.diffdir(self.root)
1073 (c, a, d, u) = self.changes(None, None)
1048 1074
1049 1075 # is this a jump, or a merge? i.e. is there a linear path
1050 1076 # from p1 to p2?
1051 1077 linear_path = (pa == p1 or pa == p2)
1052 1078
1053 1079 # resolve the manifest to determine which files
1054 1080 # we care about merging
1055 1081 self.ui.note("resolving manifests\n")
1056 1082 self.ui.debug(" ancestor %s local %s remote %s\n" %
1057 1083 (short(man), short(m1n), short(m2n)))
1058 1084
1059 1085 merge = {}
1060 1086 get = {}
1061 1087 remove = []
1062 1088 mark = {}
1063 1089
1064 1090 # construct a working dir manifest
1065 1091 mw = m1.copy()
1066 1092 mfw = mf1.copy()
1067 1093 for f in a + c + u:
1068 1094 mw[f] = ""
1069 1095 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1070 1096 for f in d:
1071 1097 if f in mw: del mw[f]
1072 1098
1073 1099 # If we're jumping between revisions (as opposed to merging),
1074 1100 # and if neither the working directory nor the target rev has
1075 1101 # the file, then we need to remove it from the dirstate, to
1076 1102 # prevent the dirstate from listing the file when it is no
1077 1103 # longer in the manifest.
1078 1104 if linear_path and f not in m2:
1079 1105 self.dirstate.forget((f,))
1080 1106
1081 1107 for f, n in mw.iteritems():
1082 1108 if f in m2:
1083 1109 s = 0
1084 1110
1085 1111 # is the wfile new since m1, and match m2?
1086 1112 if f not in m1:
1087 1113 t1 = self.wfile(f).read()
1088 1114 t2 = self.file(f).revision(m2[f])
1089 1115 if cmp(t1, t2) == 0:
1090 1116 mark[f] = 1
1091 1117 n = m2[f]
1092 1118 del t1, t2
1093 1119
1094 1120 # are files different?
1095 1121 if n != m2[f]:
1096 1122 a = ma.get(f, nullid)
1097 1123 # are both different from the ancestor?
1098 1124 if n != a and m2[f] != a:
1099 1125 self.ui.debug(" %s versions differ, resolve\n" % f)
1100 1126 # merge executable bits
1101 1127 # "if we changed or they changed, change in merge"
1102 1128 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1103 1129 mode = ((a^b) | (a^c)) ^ a
1104 1130 merge[f] = (m1.get(f, nullid), m2[f], mode)
1105 1131 s = 1
1106 1132 # are we clobbering?
1107 1133 # is remote's version newer?
1108 1134 # or are we going back in time?
1109 1135 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1110 1136 self.ui.debug(" remote %s is newer, get\n" % f)
1111 1137 get[f] = m2[f]
1112 1138 s = 1
1113 1139 else:
1114 1140 mark[f] = 1
1115 1141
1116 1142 if not s and mfw[f] != mf2[f]:
1117 1143 if force:
1118 1144 self.ui.debug(" updating permissions for %s\n" % f)
1119 1145 util.set_exec(self.wjoin(f), mf2[f])
1120 1146 else:
1121 1147 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1122 1148 mode = ((a^b) | (a^c)) ^ a
1123 1149 if mode != b:
1124 1150 self.ui.debug(" updating permissions for %s\n" % f)
1125 1151 util.set_exec(self.wjoin(f), mode)
1126 1152 mark[f] = 1
1127 1153 del m2[f]
1128 1154 elif f in ma:
1129 1155 if not force and n != ma[f]:
1130 1156 r = ""
1131 1157 if linear_path or allow:
1132 1158 r = self.ui.prompt(
1133 1159 (" local changed %s which remote deleted\n" % f) +
1134 1160 "(k)eep or (d)elete?", "[kd]", "k")
1135 1161 if r == "d":
1136 1162 remove.append(f)
1137 1163 else:
1138 1164 self.ui.debug("other deleted %s\n" % f)
1139 1165 remove.append(f) # other deleted it
1140 1166 else:
1141 1167 if n == m1.get(f, nullid): # same as parent
1142 1168 if p2 == pa: # going backwards?
1143 1169 self.ui.debug("remote deleted %s\n" % f)
1144 1170 remove.append(f)
1145 1171 else:
1146 1172 self.ui.debug("local created %s, keeping\n" % f)
1147 1173 else:
1148 1174 self.ui.debug("working dir created %s, keeping\n" % f)
1149 1175
1150 1176 for f, n in m2.iteritems():
1151 1177 if f[0] == "/": continue
1152 1178 if not force and f in ma and n != ma[f]:
1153 1179 r = ""
1154 1180 if linear_path or allow:
1155 1181 r = self.ui.prompt(
1156 1182 ("remote changed %s which local deleted\n" % f) +
1157 1183 "(k)eep or (d)elete?", "[kd]", "k")
1158 1184 if r == "d": remove.append(f)
1159 1185 else:
1160 1186 self.ui.debug("remote created %s\n" % f)
1161 1187 get[f] = n
1162 1188
1163 1189 del mw, m1, m2, ma
1164 1190
1165 1191 if force:
1166 1192 for f in merge:
1167 1193 get[f] = merge[f][1]
1168 1194 merge = {}
1169 1195
1170 1196 if linear_path:
1171 1197 # we don't need to do any magic, just jump to the new rev
1172 1198 mode = 'n'
1173 1199 p1, p2 = p2, nullid
1174 1200 else:
1175 1201 if not allow:
1176 1202 self.ui.status("this update spans a branch" +
1177 1203 " affecting the following files:\n")
1178 1204 fl = merge.keys() + get.keys()
1179 1205 fl.sort()
1180 1206 for f in fl:
1181 1207 cf = ""
1182 1208 if f in merge: cf = " (resolve)"
1183 1209 self.ui.status(" %s%s\n" % (f, cf))
1184 1210 self.ui.warn("aborting update spanning branches!\n")
1185 1211 self.ui.status("(use update -m to perform a branch merge)\n")
1186 1212 return 1
1187 1213 # we have to remember what files we needed to get/change
1188 1214 # because any file that's different from either one of its
1189 1215 # parents must be in the changeset
1190 1216 mode = 'm'
1191 1217 self.dirstate.update(mark.keys(), "m")
1192 1218
1193 1219 self.dirstate.setparents(p1, p2)
1194 1220
1195 1221 # get the files we don't need to change
1196 1222 files = get.keys()
1197 1223 files.sort()
1198 1224 for f in files:
1199 1225 if f[0] == "/": continue
1200 1226 self.ui.note("getting %s\n" % f)
1201 1227 t = self.file(f).read(get[f])
1202 1228 try:
1203 1229 self.wfile(f, "w").write(t)
1204 1230 except IOError:
1205 1231 os.makedirs(os.path.dirname(self.wjoin(f)))
1206 1232 self.wfile(f, "w").write(t)
1207 1233 util.set_exec(self.wjoin(f), mf2[f])
1208 1234 self.dirstate.update([f], mode)
1209 1235
1210 1236 # merge the tricky bits
1211 1237 files = merge.keys()
1212 1238 files.sort()
1213 1239 for f in files:
1214 1240 self.ui.status("merging %s\n" % f)
1215 1241 m, o, flag = merge[f]
1216 1242 self.merge3(f, m, o)
1217 1243 util.set_exec(self.wjoin(f), flag)
1218 1244 self.dirstate.update([f], 'm')
1219 1245
1220 1246 for f in remove:
1221 1247 self.ui.note("removing %s\n" % f)
1222 1248 os.unlink(f)
1223 1249 if mode == 'n':
1224 1250 self.dirstate.forget(remove)
1225 1251 else:
1226 1252 self.dirstate.update(remove, 'r')
1227 1253
1228 1254 def merge3(self, fn, my, other):
1229 1255 """perform a 3-way merge in the working directory"""
1230 1256
1231 1257 def temp(prefix, node):
1232 1258 pre = "%s~%s." % (os.path.basename(fn), prefix)
1233 1259 (fd, name) = tempfile.mkstemp("", pre)
1234 1260 f = os.fdopen(fd, "wb")
1235 1261 f.write(fl.revision(node))
1236 1262 f.close()
1237 1263 return name
1238 1264
1239 1265 fl = self.file(fn)
1240 1266 base = fl.ancestor(my, other)
1241 1267 a = self.wjoin(fn)
1242 1268 b = temp("base", base)
1243 1269 c = temp("other", other)
1244 1270
1245 1271 self.ui.note("resolving %s\n" % fn)
1246 1272 self.ui.debug("file %s: other %s ancestor %s\n" %
1247 1273 (fn, short(other), short(base)))
1248 1274
1249 1275 cmd = os.environ.get("HGMERGE", "hgmerge")
1250 1276 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1251 1277 if r:
1252 1278 self.ui.warn("merging %s failed!\n" % fn)
1253 1279
1254 1280 os.unlink(b)
1255 1281 os.unlink(c)
1256 1282
1257 1283 def verify(self):
1258 1284 filelinkrevs = {}
1259 1285 filenodes = {}
1260 1286 changesets = revisions = files = 0
1261 1287 errors = 0
1262 1288
1263 1289 seen = {}
1264 1290 self.ui.status("checking changesets\n")
1265 1291 for i in range(self.changelog.count()):
1266 1292 changesets += 1
1267 1293 n = self.changelog.node(i)
1268 1294 if n in seen:
1269 1295 self.ui.warn("duplicate changeset at revision %d\n" % i)
1270 1296 errors += 1
1271 1297 seen[n] = 1
1272 1298
1273 1299 for p in self.changelog.parents(n):
1274 1300 if p not in self.changelog.nodemap:
1275 1301 self.ui.warn("changeset %s has unknown parent %s\n" %
1276 1302 (short(n), short(p)))
1277 1303 errors += 1
1278 1304 try:
1279 1305 changes = self.changelog.read(n)
1280 1306 except Exception, inst:
1281 1307 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1282 1308 errors += 1
1283 1309
1284 1310 for f in changes[3]:
1285 1311 filelinkrevs.setdefault(f, []).append(i)
1286 1312
1287 1313 seen = {}
1288 1314 self.ui.status("checking manifests\n")
1289 1315 for i in range(self.manifest.count()):
1290 1316 n = self.manifest.node(i)
1291 1317 if n in seen:
1292 1318 self.ui.warn("duplicate manifest at revision %d\n" % i)
1293 1319 errors += 1
1294 1320 seen[n] = 1
1295 1321
1296 1322 for p in self.manifest.parents(n):
1297 1323 if p not in self.manifest.nodemap:
1298 1324 self.ui.warn("manifest %s has unknown parent %s\n" %
1299 1325 (short(n), short(p)))
1300 1326 errors += 1
1301 1327
1302 1328 try:
1303 1329 delta = mdiff.patchtext(self.manifest.delta(n))
1304 1330 except KeyboardInterrupt:
1305 1331 print "aborted"
1306 1332 sys.exit(0)
1307 1333 except Exception, inst:
1308 1334 self.ui.warn("unpacking manifest %s: %s\n"
1309 1335 % (short(n), inst))
1310 1336 errors += 1
1311 1337
1312 1338 ff = [ l.split('\0') for l in delta.splitlines() ]
1313 1339 for f, fn in ff:
1314 1340 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1315 1341
1316 1342 self.ui.status("crosschecking files in changesets and manifests\n")
1317 1343 for f in filenodes:
1318 1344 if f not in filelinkrevs:
1319 1345 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1320 1346 errors += 1
1321 1347
1322 1348 for f in filelinkrevs:
1323 1349 if f not in filenodes:
1324 1350 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1325 1351 errors += 1
1326 1352
1327 1353 self.ui.status("checking files\n")
1328 1354 ff = filenodes.keys()
1329 1355 ff.sort()
1330 1356 for f in ff:
1331 1357 if f == "/dev/null": continue
1332 1358 files += 1
1333 1359 fl = self.file(f)
1334 1360 nodes = { nullid: 1 }
1335 1361 seen = {}
1336 1362 for i in range(fl.count()):
1337 1363 revisions += 1
1338 1364 n = fl.node(i)
1339 1365
1340 1366 if n in seen:
1341 1367 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1342 1368 errors += 1
1343 1369
1344 1370 if n not in filenodes[f]:
1345 1371 self.ui.warn("%s: %d:%s not in manifests\n"
1346 1372 % (f, i, short(n)))
1347 1373 print len(filenodes[f].keys()), fl.count(), f
1348 1374 errors += 1
1349 1375 else:
1350 1376 del filenodes[f][n]
1351 1377
1352 1378 flr = fl.linkrev(n)
1353 1379 if flr not in filelinkrevs[f]:
1354 1380 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1355 1381 % (f, short(n), fl.linkrev(n)))
1356 1382 errors += 1
1357 1383 else:
1358 1384 filelinkrevs[f].remove(flr)
1359 1385
1360 1386 # verify contents
1361 1387 try:
1362 1388 t = fl.read(n)
1363 1389 except Exception, inst:
1364 1390 self.ui.warn("unpacking file %s %s: %s\n"
1365 1391 % (f, short(n), inst))
1366 1392 errors += 1
1367 1393
1368 1394 # verify parents
1369 1395 (p1, p2) = fl.parents(n)
1370 1396 if p1 not in nodes:
1371 1397 self.ui.warn("file %s:%s unknown parent 1 %s" %
1372 1398 (f, short(n), short(p1)))
1373 1399 errors += 1
1374 1400 if p2 not in nodes:
1375 1401 self.ui.warn("file %s:%s unknown parent 2 %s" %
1376 1402 (f, short(n), short(p1)))
1377 1403 errors += 1
1378 1404 nodes[n] = 1
1379 1405
1380 1406 # cross-check
1381 1407 for node in filenodes[f]:
1382 1408 self.ui.warn("node %s in manifests not in %s\n"
1383 1409 % (hex(n), f))
1384 1410 errors += 1
1385 1411
1386 1412 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1387 1413 (files, changesets, revisions))
1388 1414
1389 1415 if errors:
1390 1416 self.ui.warn("%d integrity errors encountered!\n" % errors)
1391 1417 return 1
1392 1418
1393 1419 class remoterepository:
1394 1420 def __init__(self, ui, path):
1395 1421 self.url = path
1396 1422 self.ui = ui
1397 1423 no_list = [ "localhost", "127.0.0.1" ]
1398 1424 host = ui.config("http_proxy", "host")
1399 1425 if host is None:
1400 1426 host = os.environ.get("http_proxy")
1401 1427 if host and host.startswith('http://'):
1402 1428 host = host[7:]
1403 1429 user = ui.config("http_proxy", "user")
1404 1430 passwd = ui.config("http_proxy", "passwd")
1405 1431 no = ui.config("http_proxy", "no")
1406 1432 if no is None:
1407 1433 no = os.environ.get("no_proxy")
1408 1434 if no:
1409 1435 no_list = no_list + no.split(",")
1410 1436
1411 1437 no_proxy = 0
1412 1438 for h in no_list:
1413 1439 if (path.startswith("http://" + h + "/") or
1414 1440 path.startswith("http://" + h + ":") or
1415 1441 path == "http://" + h):
1416 1442 no_proxy = 1
1417 1443
1418 1444 # Note: urllib2 takes proxy values from the environment and those will
1419 1445 # take precedence
1420 1446 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
1421 1447 if os.environ.has_key(env):
1422 1448 del os.environ[env]
1423 1449
1424 1450 proxy_handler = urllib2.BaseHandler()
1425 1451 if host and not no_proxy:
1426 1452 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
1427 1453
1428 1454 authinfo = None
1429 1455 if user and passwd:
1430 1456 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
1431 1457 passmgr.add_password(None, host, user, passwd)
1432 1458 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
1433 1459
1434 1460 opener = urllib2.build_opener(proxy_handler, authinfo)
1435 1461 urllib2.install_opener(opener)
1436 1462
1437 1463 def do_cmd(self, cmd, **args):
1438 1464 self.ui.debug("sending %s command\n" % cmd)
1439 1465 q = {"cmd": cmd}
1440 1466 q.update(args)
1441 1467 qs = urllib.urlencode(q)
1442 1468 cu = "%s?%s" % (self.url, qs)
1443 1469 return urllib2.urlopen(cu)
1444 1470
1445 1471 def heads(self):
1446 1472 d = self.do_cmd("heads").read()
1447 1473 try:
1448 1474 return map(bin, d[:-1].split(" "))
1449 1475 except:
1450 1476 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1451 1477 raise
1452 1478
1453 1479 def branches(self, nodes):
1454 1480 n = " ".join(map(hex, nodes))
1455 1481 d = self.do_cmd("branches", nodes=n).read()
1456 1482 try:
1457 1483 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1458 1484 return br
1459 1485 except:
1460 1486 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1461 1487 raise
1462 1488
1463 1489 def between(self, pairs):
1464 1490 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1465 1491 d = self.do_cmd("between", pairs=n).read()
1466 1492 try:
1467 1493 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1468 1494 return p
1469 1495 except:
1470 1496 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1471 1497 raise
1472 1498
1473 1499 def changegroup(self, nodes):
1474 1500 n = " ".join(map(hex, nodes))
1475 1501 zd = zlib.decompressobj()
1476 1502 f = self.do_cmd("changegroup", roots=n)
1477 1503 bytes = 0
1478 1504 while 1:
1479 1505 d = f.read(4096)
1480 1506 bytes += len(d)
1481 1507 if not d:
1482 1508 yield zd.flush()
1483 1509 break
1484 1510 yield zd.decompress(d)
1485 1511 self.ui.note("%d bytes of data transfered\n" % bytes)
1486 1512
1487 1513 def repository(ui, path=None, create=0):
1488 1514 if path and path[:7] == "http://":
1489 1515 return remoterepository(ui, path)
1490 1516 if path and path[:5] == "hg://":
1491 1517 return remoterepository(ui, path.replace("hg://", "http://"))
1492 1518 if path and path[:11] == "old-http://":
1493 1519 return localrepository(ui, path.replace("old-http://", "http://"))
1494 1520 else:
1495 1521 return localrepository(ui, path, create)
1496 1522
@@ -1,722 +1,722 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 print 'Content-type: %s\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 = {}):
88 88 self.cache = {}
89 89 self.map = {}
90 90 self.base = os.path.dirname(mapfile)
91 91 self.filters = filters
92 92
93 93 for l in file(mapfile):
94 94 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
95 95 if m:
96 96 self.cache[m.group(1)] = m.group(2)
97 97 else:
98 98 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
99 99 if m:
100 100 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
101 101 else:
102 102 raise "unknown map entry '%s'" % l
103 103
104 104 def __call__(self, t, **map):
105 105 try:
106 106 tmpl = self.cache[t]
107 107 except KeyError:
108 108 tmpl = self.cache[t] = file(self.map[t]).read()
109 109 return template(tmpl, self.filters, **map)
110 110
111 111 class hgweb:
112 112 maxchanges = 10
113 113 maxfiles = 10
114 114
115 115 def __init__(self, path, name, templates = ""):
116 116 self.templates = templates or templatepath()
117 117 self.reponame = name
118 118 self.path = path
119 119 self.mtime = -1
120 120 self.viewonly = 0
121 121
122 122 self.filters = {
123 123 "escape": cgi.escape,
124 124 "age": age,
125 125 "date": (lambda x: time.asctime(time.gmtime(x))),
126 126 "addbreaks": nl2br,
127 127 "obfuscate": obfuscate,
128 128 "short": (lambda x: x[:12]),
129 129 "firstline": (lambda x: x.splitlines(1)[0]),
130 130 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--")
131 131 }
132 132
133 133 def refresh(self):
134 134 s = os.stat(os.path.join(self.path, ".hg", "00changelog.i"))
135 135 if s.st_mtime != self.mtime:
136 136 self.mtime = s.st_mtime
137 137 self.repo = repository(ui(), self.path)
138 138
139 139 def date(self, cs):
140 140 return time.asctime(time.gmtime(float(cs[2].split(' ')[0])))
141 141
142 142 def listfiles(self, files, mf):
143 143 for f in files[:self.maxfiles]:
144 144 yield self.t("filenodelink", node = hex(mf[f]), file = f)
145 145 if len(files) > self.maxfiles:
146 146 yield self.t("fileellipses")
147 147
148 148 def listfilediffs(self, files, changeset):
149 149 for f in files[:self.maxfiles]:
150 150 yield self.t("filedifflink", node = hex(changeset), file = f)
151 151 if len(files) > self.maxfiles:
152 152 yield self.t("fileellipses")
153 153
154 154 def parent(self, t1, node=nullid, rev=-1, **args):
155 155 if node != hex(nullid):
156 156 yield self.t(t1, node = node, rev = rev, **args)
157 157
158 158 def diff(self, node1, node2, files):
159 159 def filterfiles(list, files):
160 160 l = [ x for x in list if x in files ]
161 161
162 162 for f in files:
163 163 if f[-1] != os.sep: f += os.sep
164 164 l += [ x for x in list if x.startswith(f) ]
165 165 return l
166 166
167 167 parity = [0]
168 168 def diffblock(diff, f, fn):
169 169 yield self.t("diffblock",
170 170 lines = prettyprintlines(diff),
171 171 parity = parity[0],
172 172 file = f,
173 173 filenode = hex(fn or nullid))
174 174 parity[0] = 1 - parity[0]
175 175
176 176 def prettyprintlines(diff):
177 177 for l in diff.splitlines(1):
178 178 if l.startswith('+'):
179 179 yield self.t("difflineplus", line = l)
180 180 elif l.startswith('-'):
181 181 yield self.t("difflineminus", line = l)
182 182 elif l.startswith('@'):
183 183 yield self.t("difflineat", line = l)
184 184 else:
185 185 yield self.t("diffline", line = l)
186 186
187 187 r = self.repo
188 188 cl = r.changelog
189 189 mf = r.manifest
190 190 change1 = cl.read(node1)
191 191 change2 = cl.read(node2)
192 192 mmap1 = mf.read(change1[0])
193 193 mmap2 = mf.read(change2[0])
194 194 date1 = self.date(change1)
195 195 date2 = self.date(change2)
196 196
197 c, a, d = r.diffrevs(node1, node2)
197 c, a, d = r.changes(node1, node2)
198 198 c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
199 199
200 200 for f in c:
201 201 to = r.file(f).read(mmap1[f])
202 202 tn = r.file(f).read(mmap2[f])
203 203 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
204 204 for f in a:
205 205 to = None
206 206 tn = r.file(f).read(mmap2[f])
207 207 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
208 208 for f in d:
209 209 to = r.file(f).read(mmap1[f])
210 210 tn = None
211 211 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
212 212
213 213 def header(self):
214 214 yield self.t("header", repo = self.reponame)
215 215
216 216 def footer(self):
217 217 yield self.t("footer", repo = self.reponame)
218 218
219 219 def changelog(self, pos):
220 220 def changenav():
221 221 def seq(factor = 1):
222 222 yield 1 * factor
223 223 yield 3 * factor
224 224 #yield 5 * factor
225 225 for f in seq(factor * 10):
226 226 yield f
227 227
228 228 l = []
229 229 for f in seq():
230 230 if f < self.maxchanges / 2: continue
231 231 if f > count: break
232 232 r = "%d" % f
233 233 if pos + f < count: l.append(("+" + r, pos + f))
234 234 if pos - f >= 0: l.insert(0, ("-" + r, pos - f))
235 235
236 236 yield self.t("naventry", rev = 0, label="(0)")
237 237
238 238 for label, rev in l:
239 239 yield self.t("naventry", label = label, rev = rev)
240 240
241 241 yield self.t("naventry", label="tip")
242 242
243 243 def changelist():
244 244 parity = (start - end) & 1
245 245 cl = self.repo.changelog
246 246 l = [] # build a list in forward order for efficiency
247 247 for i in range(start, end):
248 248 n = cl.node(i)
249 249 changes = cl.read(n)
250 250 hn = hex(n)
251 251 p1, p2 = cl.parents(n)
252 252 t = float(changes[2].split(' ')[0])
253 253
254 254 l.insert(0, self.t(
255 255 'changelogentry',
256 256 parity = parity,
257 257 author = changes[1],
258 258 parent1 = self.parent("changelogparent",
259 259 hex(p1), cl.rev(p1)),
260 260 parent2 = self.parent("changelogparent",
261 261 hex(p2), cl.rev(p2)),
262 262 p1 = hex(p1), p2 = hex(p2),
263 263 p1rev = cl.rev(p1), p2rev = cl.rev(p2),
264 264 manifest = hex(changes[0]),
265 265 desc = changes[4],
266 266 date = t,
267 267 files = self.listfilediffs(changes[3], n),
268 268 rev = i,
269 269 node = hn))
270 270 parity = 1 - parity
271 271
272 272 yield l
273 273
274 274 cl = self.repo.changelog
275 275 mf = cl.read(cl.tip())[0]
276 276 count = cl.count()
277 277 start = max(0, pos - self.maxchanges + 1)
278 278 end = min(count, start + self.maxchanges)
279 279 pos = end - 1
280 280
281 281 yield self.t('changelog',
282 282 header = self.header(),
283 283 footer = self.footer(),
284 284 repo = self.reponame,
285 285 changenav = changenav,
286 286 manifest = hex(mf),
287 287 rev = pos, changesets = count, entries = changelist)
288 288
289 289 def changeset(self, nodeid):
290 290 n = bin(nodeid)
291 291 cl = self.repo.changelog
292 292 changes = cl.read(n)
293 293 p1, p2 = cl.parents(n)
294 294 p1rev, p2rev = cl.rev(p1), cl.rev(p2)
295 295 t = float(changes[2].split(' ')[0])
296 296
297 297 files = []
298 298 mf = self.repo.manifest.read(changes[0])
299 299 for f in changes[3]:
300 300 files.append(self.t("filenodelink",
301 301 filenode = hex(mf.get(f, nullid)), file = f))
302 302
303 303 def diff():
304 304 yield self.diff(p1, n, changes[3])
305 305
306 306 yield self.t('changeset',
307 307 header = self.header(),
308 308 footer = self.footer(),
309 309 repo = self.reponame,
310 310 diff = diff,
311 311 rev = cl.rev(n),
312 312 node = nodeid,
313 313 parent1 = self.parent("changesetparent",
314 314 hex(p1), cl.rev(p1)),
315 315 parent2 = self.parent("changesetparent",
316 316 hex(p2), cl.rev(p2)),
317 317 p1 = hex(p1), p2 = hex(p2),
318 318 p1rev = cl.rev(p1), p2rev = cl.rev(p2),
319 319 manifest = hex(changes[0]),
320 320 author = changes[1],
321 321 desc = changes[4],
322 322 date = t,
323 323 files = files)
324 324
325 325 def filelog(self, f, filenode):
326 326 cl = self.repo.changelog
327 327 fl = self.repo.file(f)
328 328 count = fl.count()
329 329
330 330 def entries():
331 331 l = []
332 332 parity = (count - 1) & 1
333 333
334 334 for i in range(count):
335 335
336 336 n = fl.node(i)
337 337 lr = fl.linkrev(n)
338 338 cn = cl.node(lr)
339 339 cs = cl.read(cl.node(lr))
340 340 p1, p2 = fl.parents(n)
341 341 t = float(cs[2].split(' ')[0])
342 342
343 343 l.insert(0, self.t("filelogentry",
344 344 parity = parity,
345 345 filenode = hex(n),
346 346 filerev = i,
347 347 file = f,
348 348 node = hex(cn),
349 349 author = cs[1],
350 350 date = t,
351 351 desc = cs[4],
352 352 p1 = hex(p1), p2 = hex(p2),
353 353 p1rev = fl.rev(p1), p2rev = fl.rev(p2)))
354 354 parity = 1 - parity
355 355
356 356 yield l
357 357
358 358 yield self.t("filelog",
359 359 header = self.header(),
360 360 footer = self.footer(),
361 361 repo = self.reponame,
362 362 file = f,
363 363 filenode = filenode,
364 364 entries = entries)
365 365
366 366 def filerevision(self, f, node):
367 367 fl = self.repo.file(f)
368 368 n = bin(node)
369 369 text = fl.read(n)
370 370 changerev = fl.linkrev(n)
371 371 cl = self.repo.changelog
372 372 cn = cl.node(changerev)
373 373 cs = cl.read(cn)
374 374 p1, p2 = fl.parents(n)
375 375 t = float(cs[2].split(' ')[0])
376 376 mfn = cs[0]
377 377
378 378 def lines():
379 379 for l, t in enumerate(text.splitlines(1)):
380 380 yield self.t("fileline", line = t,
381 381 linenumber = "% 6d" % (l + 1),
382 382 parity = l & 1)
383 383
384 384 yield self.t("filerevision", file = f,
385 385 header = self.header(),
386 386 footer = self.footer(),
387 387 repo = self.reponame,
388 388 filenode = node,
389 389 path = up(f),
390 390 text = lines(),
391 391 rev = changerev,
392 392 node = hex(cn),
393 393 manifest = hex(mfn),
394 394 author = cs[1],
395 395 date = t,
396 396 parent1 = self.parent("filerevparent",
397 397 hex(p1), fl.rev(p1), file=f),
398 398 parent2 = self.parent("filerevparent",
399 399 hex(p2), fl.rev(p2), file=f),
400 400 p1 = hex(p1), p2 = hex(p2),
401 401 permissions = self.repo.manifest.readflags(mfn)[f],
402 402 p1rev = fl.rev(p1), p2rev = fl.rev(p2))
403 403
404 404 def fileannotate(self, f, node):
405 405 bcache = {}
406 406 ncache = {}
407 407 fl = self.repo.file(f)
408 408 n = bin(node)
409 409 changerev = fl.linkrev(n)
410 410
411 411 cl = self.repo.changelog
412 412 cn = cl.node(changerev)
413 413 cs = cl.read(cn)
414 414 p1, p2 = fl.parents(n)
415 415 t = float(cs[2].split(' ')[0])
416 416 mfn = cs[0]
417 417
418 418 def annotate():
419 419 parity = 1
420 420 last = None
421 421 for r, l in fl.annotate(n):
422 422 try:
423 423 cnode = ncache[r]
424 424 except KeyError:
425 425 cnode = ncache[r] = self.repo.changelog.node(r)
426 426
427 427 try:
428 428 name = bcache[r]
429 429 except KeyError:
430 430 cl = self.repo.changelog.read(cnode)
431 431 name = cl[1]
432 432 f = name.find('@')
433 433 if f >= 0:
434 434 name = name[:f]
435 435 f = name.find('<')
436 436 if f >= 0:
437 437 name = name[f+1:]
438 438 bcache[r] = name
439 439
440 440 if last != cnode:
441 441 parity = 1 - parity
442 442 last = cnode
443 443
444 444 yield self.t("annotateline",
445 445 parity = parity,
446 446 node = hex(cnode),
447 447 rev = r,
448 448 author = name,
449 449 file = f,
450 450 line = l)
451 451
452 452 yield self.t("fileannotate",
453 453 header = self.header(),
454 454 footer = self.footer(),
455 455 repo = self.reponame,
456 456 file = f,
457 457 filenode = node,
458 458 annotate = annotate,
459 459 path = up(f),
460 460 rev = changerev,
461 461 node = hex(cn),
462 462 manifest = hex(mfn),
463 463 author = cs[1],
464 464 date = t,
465 465 parent1 = self.parent("fileannotateparent",
466 466 hex(p1), fl.rev(p1), file=f),
467 467 parent2 = self.parent("fileannotateparent",
468 468 hex(p2), fl.rev(p2), file=f),
469 469 p1 = hex(p1), p2 = hex(p2),
470 470 permissions = self.repo.manifest.readflags(mfn)[f],
471 471 p1rev = fl.rev(p1), p2rev = fl.rev(p2))
472 472
473 473 def manifest(self, mnode, path):
474 474 mf = self.repo.manifest.read(bin(mnode))
475 475 rev = self.repo.manifest.rev(bin(mnode))
476 476 node = self.repo.changelog.node(rev)
477 477 mff=self.repo.manifest.readflags(bin(mnode))
478 478
479 479 files = {}
480 480
481 481 p = path[1:]
482 482 l = len(p)
483 483
484 484 for f,n in mf.items():
485 485 if f[:l] != p:
486 486 continue
487 487 remain = f[l:]
488 488 if "/" in remain:
489 489 short = remain[:remain.find("/") + 1] # bleah
490 490 files[short] = (f, None)
491 491 else:
492 492 short = os.path.basename(remain)
493 493 files[short] = (f, n)
494 494
495 495 def filelist():
496 496 parity = 0
497 497 fl = files.keys()
498 498 fl.sort()
499 499 for f in fl:
500 500 full, fnode = files[f]
501 501 if fnode:
502 502 yield self.t("manifestfileentry",
503 503 file = full,
504 504 manifest = mnode,
505 505 filenode = hex(fnode),
506 506 parity = parity,
507 507 basename = f,
508 508 permissions = mff[full])
509 509 else:
510 510 yield self.t("manifestdirentry",
511 511 parity = parity,
512 512 path = os.path.join(path, f),
513 513 manifest = mnode, basename = f[:-1])
514 514 parity = 1 - parity
515 515
516 516 yield self.t("manifest",
517 517 header = self.header(),
518 518 footer = self.footer(),
519 519 repo = self.reponame,
520 520 manifest = mnode,
521 521 rev = rev,
522 522 node = hex(node),
523 523 path = path,
524 524 up = up(path),
525 525 entries = filelist)
526 526
527 527 def tags(self):
528 528 cl = self.repo.changelog
529 529 mf = cl.read(cl.tip())[0]
530 530
531 531 i = self.repo.tagslist()
532 532 i.reverse()
533 533
534 534 def entries():
535 535 parity = 0
536 536 for k,n in i:
537 537 yield self.t("tagentry",
538 538 parity = parity,
539 539 tag = k,
540 540 node = hex(n))
541 541 parity = 1 - parity
542 542
543 543 yield self.t("tags",
544 544 header = self.header(),
545 545 footer = self.footer(),
546 546 repo = self.reponame,
547 547 manifest = hex(mf),
548 548 entries = entries)
549 549
550 550 def filediff(self, file, changeset):
551 551 n = bin(changeset)
552 552 cl = self.repo.changelog
553 553 p1 = cl.parents(n)[0]
554 554 cs = cl.read(n)
555 555 mf = self.repo.manifest.read(cs[0])
556 556
557 557 def diff():
558 558 yield self.diff(p1, n, file)
559 559
560 560 yield self.t("filediff",
561 561 header = self.header(),
562 562 footer = self.footer(),
563 563 repo = self.reponame,
564 564 file = file,
565 565 filenode = hex(mf.get(file, nullid)),
566 566 node = changeset,
567 567 rev = self.repo.changelog.rev(n),
568 568 p1 = hex(p1),
569 569 p1rev = self.repo.changelog.rev(p1),
570 570 diff = diff)
571 571
572 572 # add tags to things
573 573 # tags -> list of changesets corresponding to tags
574 574 # find tag, changeset, file
575 575
576 576 def run(self):
577 577 self.refresh()
578 578 args = cgi.parse()
579 579
580 580 m = os.path.join(self.templates, "map")
581 581 if args.has_key('style'):
582 582 b = os.path.basename("map-" + args['style'][0])
583 583 p = os.path.join(self.templates, b)
584 584 if os.path.isfile(p): m = p
585 585
586 586 self.t = templater(m, self.filters)
587 587
588 588 if not args.has_key('cmd') or args['cmd'][0] == 'changelog':
589 589 hi = self.repo.changelog.count() - 1
590 590 if args.has_key('rev'):
591 591 hi = args['rev'][0]
592 592 try:
593 593 hi = self.repo.changelog.rev(self.repo.lookup(hi))
594 594 except KeyError: pass
595 595
596 596 write(self.changelog(hi))
597 597
598 598 elif args['cmd'][0] == 'changeset':
599 599 write(self.changeset(args['node'][0]))
600 600
601 601 elif args['cmd'][0] == 'manifest':
602 602 write(self.manifest(args['manifest'][0], args['path'][0]))
603 603
604 604 elif args['cmd'][0] == 'tags':
605 605 write(self.tags())
606 606
607 607 elif args['cmd'][0] == 'filediff':
608 608 write(self.filediff(args['file'][0], args['node'][0]))
609 609
610 610 elif args['cmd'][0] == 'file':
611 611 write(self.filerevision(args['file'][0], args['filenode'][0]))
612 612
613 613 elif args['cmd'][0] == 'annotate':
614 614 write(self.fileannotate(args['file'][0], args['filenode'][0]))
615 615
616 616 elif args['cmd'][0] == 'filelog':
617 617 write(self.filelog(args['file'][0], args['filenode'][0]))
618 618
619 619 elif args['cmd'][0] == 'heads':
620 620 httphdr("text/plain")
621 621 h = self.repo.heads()
622 622 sys.stdout.write(" ".join(map(hex, h)) + "\n")
623 623
624 624 elif args['cmd'][0] == 'branches':
625 625 httphdr("text/plain")
626 626 nodes = []
627 627 if args.has_key('nodes'):
628 628 nodes = map(bin, args['nodes'][0].split(" "))
629 629 for b in self.repo.branches(nodes):
630 630 sys.stdout.write(" ".join(map(hex, b)) + "\n")
631 631
632 632 elif args['cmd'][0] == 'between':
633 633 httphdr("text/plain")
634 634 nodes = []
635 635 if args.has_key('pairs'):
636 636 pairs = [ map(bin, p.split("-"))
637 637 for p in args['pairs'][0].split(" ") ]
638 638 for b in self.repo.between(pairs):
639 639 sys.stdout.write(" ".join(map(hex, b)) + "\n")
640 640
641 641 elif args['cmd'][0] == 'changegroup':
642 642 httphdr("application/hg-changegroup")
643 643 nodes = []
644 644 if self.viewonly:
645 645 return
646 646
647 647 if args.has_key('roots'):
648 648 nodes = map(bin, args['roots'][0].split(" "))
649 649
650 650 z = zlib.compressobj()
651 651 for chunk in self.repo.changegroup(nodes):
652 652 sys.stdout.write(z.compress(chunk))
653 653
654 654 sys.stdout.write(z.flush())
655 655
656 656 else:
657 657 write(self.t("error"))
658 658
659 659 def server(path, name, templates, address, port):
660 660
661 661 import BaseHTTPServer
662 662 import sys, os
663 663
664 664 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
665 665 def do_POST(self):
666 666 try:
667 667 self.do_hgweb()
668 668 except socket.error, inst:
669 669 if inst.args[0] != 32: raise
670 670
671 671 def do_GET(self):
672 672 self.do_POST()
673 673
674 674 def do_hgweb(self):
675 675 query = ""
676 676 p = self.path.find("?")
677 677 if p:
678 678 query = self.path[p + 1:]
679 679 query = query.replace('+', ' ')
680 680
681 681 env = {}
682 682 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
683 683 env['REQUEST_METHOD'] = self.command
684 684 if query:
685 685 env['QUERY_STRING'] = query
686 686 host = self.address_string()
687 687 if host != self.client_address[0]:
688 688 env['REMOTE_HOST'] = host
689 689 env['REMOTE_ADDR'] = self.client_address[0]
690 690
691 691 if self.headers.typeheader is None:
692 692 env['CONTENT_TYPE'] = self.headers.type
693 693 else:
694 694 env['CONTENT_TYPE'] = self.headers.typeheader
695 695 length = self.headers.getheader('content-length')
696 696 if length:
697 697 env['CONTENT_LENGTH'] = length
698 698 accept = []
699 699 for line in self.headers.getallmatchingheaders('accept'):
700 700 if line[:1] in "\t\n\r ":
701 701 accept.append(line.strip())
702 702 else:
703 703 accept = accept + line[7:].split(',')
704 704 env['HTTP_ACCEPT'] = ','.join(accept)
705 705
706 706 os.environ.update(env)
707 707
708 708 save = sys.argv, sys.stdin, sys.stdout, sys.stderr
709 709 try:
710 710 sys.stdin = self.rfile
711 711 sys.stdout = self.wfile
712 712 sys.argv = ["hgweb.py"]
713 713 if '=' not in query:
714 714 sys.argv.append(query)
715 715 self.send_response(200, "Script output follows")
716 716 hg.run()
717 717 finally:
718 718 sys.argv, sys.stdin, sys.stdout, sys.stderr = save
719 719
720 720 hg = hgweb(path, name, templates)
721 721 httpd = BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
722 722 httpd.serve_forever()
@@ -1,20 +1,21 b''
1 1 + hg clone http://localhost:20059/ copy
2 2 requesting all changes
3 3 adding changesets
4 4 abort: error 111: Connection refused
5 5 transaction abort!
6 6 rollback completed
7 7 + echo 255
8 8 255
9 9 + ls copy
10 ls: copy: No such file or directory
10 11 + cat
11 12 + python dumb.py
12 13 + hg clone http://localhost:20059/foo copy2
13 14 requesting all changes
14 15 adding changesets
15 16 abort: HTTP Error 404: File not found
16 17 transaction abort!
17 18 rollback completed
18 19 + echo 255
19 20 255
20 21 + set +x
General Comments 0
You need to be logged in to leave comments. Login now