##// END OF EJS Templates
hg commit: user and date options...
mpm@selenic.com -
r317:b18ce742 default
parent child Browse files
Show More
@@ -1,670 +1,672 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
10 10 from demandload import *
11 11 demandload(globals(), "mdiff time hgweb traceback")
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] != os.sep: t += os.sep
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([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 [ os.path.normpath(os.path.join(p, x)) for x in args ]
33 33 return args
34 34
35 35 def dodiff(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 42 (c, a, d) = repo.diffrevs(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 47 (c, a, d, u) = repo.diffdir(path, node1)
48 48 if not node1:
49 49 node1 = repo.dirstate.parents()[0]
50 50 def read(f): return file(os.path.join(repo.root, f)).read()
51 51
52 52 change = repo.changelog.read(node1)
53 53 mmap = repo.manifest.read(change[0])
54 54 date1 = date(change)
55 55
56 56 if files:
57 57 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
58 58
59 59 for f in c:
60 60 to = None
61 61 if f in mmap:
62 62 to = repo.file(f).read(mmap[f])
63 63 tn = read(f)
64 64 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
65 65 for f in a:
66 66 to = None
67 67 tn = read(f)
68 68 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
69 69 for f in d:
70 70 to = repo.file(f).read(mmap[f])
71 71 tn = None
72 72 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
73 73
74 74 def help(ui, cmd=None):
75 75 '''show help for a given command or all commands'''
76 76 if cmd:
77 77 try:
78 78 i = find(cmd)
79 79 ui.write("%s\n\n" % i[2])
80 80
81 81 if i[1]:
82 82 for s, l, d, c in i[1]:
83 83 opt=' '
84 84 if s: opt = opt + '-' + s + ' '
85 85 if l: opt = opt + '--' + l + ' '
86 86 if d: opt = opt + '(' + str(d) + ')'
87 87 ui.write(opt, "\n")
88 88 if c: ui.write(' %s\n' % c)
89 89 ui.write("\n")
90 90
91 91 ui.write(i[0].__doc__, "\n")
92 92 except UnknownCommand:
93 93 ui.warn("hg: unknown command %s\n" % cmd)
94 94 sys.exit(0)
95 95 else:
96 96 ui.status('hg commands:\n\n')
97 97
98 98 h = {}
99 99 for e in table.values():
100 100 f = e[0]
101 101 if f.__name__.startswith("debug"): continue
102 102 d = ""
103 103 if f.__doc__:
104 104 d = f.__doc__.splitlines(0)[0].rstrip()
105 105 h[f.__name__] = d
106 106
107 107 fns = h.keys()
108 108 fns.sort()
109 109 m = max(map(len, fns))
110 110 for f in fns:
111 111 ui.status(' %-*s %s\n' % (m, f, h[f]))
112 112
113 113 # Commands start here, listed alphabetically
114 114
115 115 def add(ui, repo, file, *files):
116 116 '''add the specified files on the next commit'''
117 117 repo.add(relpath(repo, (file,) + files))
118 118
119 119 def addremove(ui, repo):
120 120 """add all new files, delete all missing files"""
121 121 (c, a, d, u) = repo.diffdir(repo.root)
122 122 repo.add(u)
123 123 repo.remove(d)
124 124
125 125 def annotate(u, repo, file, *files, **ops):
126 126 """show changeset information per file line"""
127 127 def getnode(rev):
128 128 return hg.short(repo.changelog.node(rev))
129 129
130 130 def getname(rev):
131 131 try:
132 132 return bcache[rev]
133 133 except KeyError:
134 134 cl = repo.changelog.read(repo.changelog.node(rev))
135 135 name = cl[1]
136 136 f = name.find('@')
137 137 if f >= 0:
138 138 name = name[:f]
139 139 bcache[rev] = name
140 140 return name
141 141
142 142 bcache = {}
143 143 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
144 144 if not ops['user'] and not ops['changeset']:
145 145 ops['number'] = 1
146 146
147 147 node = repo.dirstate.parents()[0]
148 148 if ops['revision']:
149 149 node = repo.changelog.lookup(ops['revision'])
150 150 change = repo.changelog.read(node)
151 151 mmap = repo.manifest.read(change[0])
152 152 maxuserlen = 0
153 153 maxchangelen = 0
154 154 for f in relpath(repo, (file,) + files):
155 155 lines = repo.file(f).annotate(mmap[f])
156 156 pieces = []
157 157
158 158 for o, f in opmap:
159 159 if ops[o]:
160 160 l = [ f(n) for n,t in lines ]
161 161 m = max(map(len, l))
162 162 pieces.append([ "%*s" % (m, x) for x in l])
163 163
164 164 for p,l in zip(zip(*pieces), lines):
165 165 u.write(" ".join(p) + ": " + l[1])
166 166
167 167 def cat(ui, repo, file, rev = []):
168 168 """output the latest or given revision of a file"""
169 169 r = repo.file(relpath(repo, [file])[0])
170 170 n = r.tip()
171 171 if rev: n = r.lookup(rev)
172 172 sys.stdout.write(r.read(n))
173 173
174 174 def commit(ui, repo, *files, **opts):
175 175 """commit the specified files or all outstanding changes"""
176 176 text = opts['text']
177 177 if not text and opts['logfile']:
178 178 try: text = open(opts['logfile']).read()
179 179 except IOError: pass
180 180
181 repo.commit(relpath(repo, files), text)
181 repo.commit(relpath(repo, files), text, opts['user'], opts['date'])
182 182
183 183 def debugaddchangegroup(ui, repo):
184 184 data = sys.stdin.read()
185 185 repo.addchangegroup(data)
186 186
187 187 def debugchangegroup(ui, repo, roots):
188 188 newer = repo.newer(map(repo.lookup, roots))
189 189 for chunk in repo.changegroup(newer):
190 190 sys.stdout.write(chunk)
191 191
192 192 def debugindex(ui, file):
193 193 r = hg.revlog(open, file, "")
194 194 print " rev offset length base linkrev"+\
195 195 " p1 p2 nodeid"
196 196 for i in range(r.count()):
197 197 e = r.index[i]
198 198 print "% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s.." % (
199 199 i, e[0], e[1], e[2], e[3],
200 200 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5]))
201 201
202 202 def debugindexdot(ui, file):
203 203 r = hg.revlog(open, file, "")
204 204 print "digraph G {"
205 205 for i in range(r.count()):
206 206 e = r.index[i]
207 207 print "\t%d -> %d" % (r.rev(e[4]), i)
208 208 if e[5] != hg.nullid:
209 209 print "\t%d -> %d" % (r.rev(e[5]), i)
210 210 print "}"
211 211
212 212 def diff(ui, repo, *files, **opts):
213 213 """diff working directory (or selected files)"""
214 214 revs = []
215 215 if opts['rev']:
216 216 revs = map(lambda x: repo.lookup(x), opts['rev'])
217 217
218 218 if len(revs) > 2:
219 219 self.ui.warn("too many revisions to diff\n")
220 220 sys.exit(1)
221 221
222 222 if files:
223 223 files = relpath(repo, files)
224 224 else:
225 225 files = relpath(repo, [""])
226 226
227 227 dodiff(repo, os.getcwd(), files, *revs)
228 228
229 229 def export(ui, repo, changeset):
230 230 """dump the changeset header and diffs for a revision"""
231 231 node = repo.lookup(changeset)
232 232 prev, other = repo.changelog.parents(node)
233 233 change = repo.changelog.read(node)
234 234 print "# HG changeset patch"
235 235 print "# User %s" % change[1]
236 236 print "# Node ID %s" % hg.hex(node)
237 237 print "# Parent %s" % hg.hex(prev)
238 238 print
239 239 if other != hg.nullid:
240 240 print "# Parent %s" % hg.hex(other)
241 241 print change[4].rstrip()
242 242 print
243 243
244 244 dodiff(repo, "", None, prev, node)
245 245
246 246 def forget(ui, repo, file, *files):
247 247 """don't add the specified files on the next commit"""
248 248 repo.forget(relpath(repo, (file,) + files))
249 249
250 250 def heads(ui, repo):
251 251 '''show current repository heads'''
252 252 for n in repo.changelog.heads():
253 253 i = repo.changelog.rev(n)
254 254 changes = repo.changelog.read(n)
255 255 (p1, p2) = repo.changelog.parents(n)
256 256 (h, h1, h2) = map(hg.hex, (n, p1, p2))
257 257 (i1, i2) = map(repo.changelog.rev, (p1, p2))
258 258 print "rev: %4d:%s" % (i, h)
259 259 print "parents: %4d:%s" % (i1, h1)
260 260 if i2: print " %4d:%s" % (i2, h2)
261 261 print "manifest: %4d:%s" % (repo.manifest.rev(changes[0]),
262 262 hg.hex(changes[0]))
263 263 print "user:", changes[1]
264 264 print "date:", time.asctime(
265 265 time.localtime(float(changes[2].split(' ')[0])))
266 266 if ui.verbose: print "files:", " ".join(changes[3])
267 267 print "description:"
268 268 print changes[4]
269 269
270 270 def history(ui, repo):
271 271 """show the changelog history"""
272 272 for i in range(repo.changelog.count() - 1, -1, -1):
273 273 n = repo.changelog.node(i)
274 274 changes = repo.changelog.read(n)
275 275 (p1, p2) = repo.changelog.parents(n)
276 276 (h, h1, h2) = map(hg.hex, (n, p1, p2))
277 277 (i1, i2) = map(repo.changelog.rev, (p1, p2))
278 278 print "rev: %4d:%s" % (i, h)
279 279 print "parents: %4d:%s" % (i1, h1)
280 280 if i2: print " %4d:%s" % (i2, h2)
281 281 print "manifest: %4d:%s" % (repo.manifest.rev(changes[0]),
282 282 hg.hex(changes[0]))
283 283 print "user:", changes[1]
284 284 print "date:", time.asctime(
285 285 time.localtime(float(changes[2].split(' ')[0])))
286 286 if ui.verbose: print "files:", " ".join(changes[3])
287 287 print "description:"
288 288 print changes[4]
289 289
290 290 def init(ui, source=None):
291 291 """create a new repository or copy an existing one"""
292 292
293 293 if source:
294 294 paths = {}
295 295 for name, path in ui.configitems("paths"):
296 296 paths[name] = path
297 297
298 298 if source in paths: source = paths[source]
299 299
300 300 link = 0
301 301 if not source.startswith("http://"):
302 302 d1 = os.stat(os.getcwd()).st_dev
303 303 d2 = os.stat(source).st_dev
304 304 if d1 == d2: link = 1
305 305
306 306 if link:
307 307 ui.debug("copying by hardlink\n")
308 308 os.system("cp -al %s/.hg .hg" % source)
309 309 try:
310 310 os.remove(".hg/dirstate")
311 311 except: pass
312 312 else:
313 313 repo = hg.repository(ui, ".", create=1)
314 314 other = hg.repository(ui, source)
315 315 cg = repo.getchangegroup(other)
316 316 repo.addchangegroup(cg)
317 317 else:
318 318 hg.repository(ui, ".", create=1)
319 319
320 320 def log(ui, repo, f):
321 321 """show the revision history of a single file"""
322 322 f = relpath(repo, [f])[0]
323 323
324 324 r = repo.file(f)
325 325 for i in range(r.count() - 1, -1, -1):
326 326 n = r.node(i)
327 327 (p1, p2) = r.parents(n)
328 328 (h, h1, h2) = map(hg.hex, (n, p1, p2))
329 329 (i1, i2) = map(r.rev, (p1, p2))
330 330 cr = r.linkrev(n)
331 331 cn = hg.hex(repo.changelog.node(cr))
332 332 print "rev: %4d:%s" % (i, h)
333 333 print "changeset: %4d:%s" % (cr, cn)
334 334 print "parents: %4d:%s" % (i1, h1)
335 335 if i2: print " %4d:%s" % (i2, h2)
336 336 changes = repo.changelog.read(repo.changelog.node(cr))
337 337 print "user: %s" % changes[1]
338 338 print "date: %s" % time.asctime(
339 339 time.localtime(float(changes[2].split(' ')[0])))
340 340 print "description:"
341 341 print changes[4].rstrip()
342 342 print
343 343
344 344 def manifest(ui, repo, rev = []):
345 345 """output the latest or given revision of the project manifest"""
346 346 n = repo.manifest.tip()
347 347 if rev:
348 348 n = repo.manifest.lookup(rev)
349 349 m = repo.manifest.read(n)
350 350 mf = repo.manifest.readflags(n)
351 351 files = m.keys()
352 352 files.sort()
353 353
354 354 for f in files:
355 355 ui.write("%40s %3s %s\n" % (hg.hex(m[f]), mf[f] and "755" or "644", f))
356 356
357 357 def parents(ui, repo, node = None):
358 358 '''show the parents of the current working dir'''
359 359 if node:
360 360 p = repo.changelog.parents(repo.lookup(hg.bin(node)))
361 361 else:
362 362 p = repo.dirstate.parents()
363 363
364 364 for n in p:
365 365 if n != hg.nullid:
366 366 ui.write("%d:%s\n" % (repo.changelog.rev(n), hg.hex(n)))
367 367
368 368 def patch(ui, repo, patch1, *patches, **opts):
369 369 """import an ordered set of patches"""
370 370 try:
371 371 import psyco
372 372 psyco.full()
373 373 except:
374 374 pass
375 375
376 376 patches = (patch1,) + patches
377 377
378 378 d = opts["base"]
379 379 strip = opts["strip"]
380 380 quiet = opts["quiet"] and "> /dev/null" or ""
381 381
382 382 for patch in patches:
383 383 ui.status("applying %s\n" % patch)
384 384 pf = os.path.join(d, patch)
385 385
386 386 text = ""
387 387 for l in file(pf):
388 388 if l[:4] == "--- ": break
389 389 text += l
390 390
391 391 # make sure text isn't empty
392 392 if not text: text = "imported patch %s\n" % patch
393 393
394 394 f = os.popen("lsdiff --strip %d %s" % (strip, pf))
395 395 files = filter(None, map(lambda x: x.rstrip(), f.read().splitlines()))
396 396 f.close()
397 397
398 398 if files:
399 399 if os.system("patch -p%d < %s %s" % (strip, pf, quiet)):
400 400 raise "patch failed!"
401 401 repo.commit(files, text)
402 402
403 403 def pull(ui, repo, source):
404 404 """pull changes from the specified source"""
405 405 paths = {}
406 406 for name, path in ui.configitems("paths"):
407 407 paths[name] = path
408 408
409 409 if source in paths: source = paths[source]
410 410
411 411 other = hg.repository(ui, source)
412 412 cg = repo.getchangegroup(other)
413 413 repo.addchangegroup(cg)
414 414
415 415 def rawcommit(ui, repo, files, **rc):
416 416 "raw commit interface"
417 417
418 418 text = rc['text']
419 419 if not text and rc['logfile']:
420 420 try: text = open(rc['logfile']).read()
421 421 except IOError: pass
422 422 if not text and not rc['logfile']:
423 423 print "missing commit text"
424 424 return 1
425 425
426 426 files = relpath(repo, files)
427 427 if rc['files']:
428 428 files += open(rc['files']).read().splitlines()
429 429
430 430 repo.rawcommit(files, text, rc['user'], rc['date'], *rc['parent'])
431 431
432 432 def recover(ui, repo):
433 433 """roll back an interrupted transaction"""
434 434 repo.recover()
435 435
436 436 def remove(ui, repo, file, *files):
437 437 """remove the specified files on the next commit"""
438 438 repo.remove(relpath(repo, (file,) + files))
439 439
440 440 def serve(ui, repo, **opts):
441 441 """export the repository via HTTP"""
442 442 hgweb.server(repo.root, opts["name"], opts["templates"],
443 443 opts["address"], opts["port"])
444 444
445 445 def status(ui, repo):
446 446 '''show changed files in the working directory
447 447
448 448 C = changed
449 449 A = added
450 450 R = removed
451 451 ? = not tracked'''
452 452
453 453 (c, a, d, u) = repo.diffdir(os.getcwd())
454 454 (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
455 455
456 456 for f in c: print "C", f
457 457 for f in a: print "A", f
458 458 for f in d: print "R", f
459 459 for f in u: print "?", f
460 460
461 461 def tags(ui, repo):
462 462 """list repository tags"""
463 463 repo.lookup(0) # prime the cache
464 464 i = repo.tags.items()
465 465 n = []
466 466 for e in i:
467 467 try:
468 468 l = repo.changelog.rev(e[1])
469 469 except KeyError:
470 470 l = -2
471 471 n.append((l, e))
472 472
473 473 n.sort()
474 474 n.reverse()
475 475 i = [ e[1] for e in n ]
476 476 for k, n in i:
477 477 try:
478 478 r = repo.changelog.rev(n)
479 479 except KeyError:
480 480 r = "?"
481 481 print "%-30s %5d:%s" % (k, repo.changelog.rev(n), hg.hex(n))
482 482
483 483 def tip(ui, repo):
484 484 """show the tip revision"""
485 485 n = repo.changelog.tip()
486 486 t = repo.changelog.rev(n)
487 487 ui.status("%d:%s\n" % (t, hg.hex(n)))
488 488
489 489 def undo(ui, repo):
490 490 """undo the last transaction"""
491 491 repo.undo()
492 492
493 493 def update(ui, repo, node=None, merge=False, clean=False):
494 494 '''update or merge working directory
495 495
496 496 If there are no outstanding changes in the working directory and
497 497 there is a linear relationship between the current version and the
498 498 requested version, the result is the requested version.
499 499
500 500 Otherwise the result is a merge between the contents of the
501 501 current working directory and the requested version. Files that
502 502 changed between either parent are marked as changed for the next
503 503 commit and a commit must be performed before any further updates
504 504 are allowed.
505 505 '''
506 506 node = node and repo.lookup(node) or repo.changelog.tip()
507 507 return repo.update(node, allow=merge, force=clean)
508 508
509 509 def verify(ui, repo):
510 510 """verify the integrity of the repository"""
511 511 return repo.verify()
512 512
513 513 # Command options and aliases are listed here, alphabetically
514 514
515 515 table = {
516 516 "add": (add, [], "hg add [files]"),
517 517 "addremove": (addremove, [], "hg addremove"),
518 518 "ann|annotate": (annotate,
519 519 [('r', 'revision', '', 'revision'),
520 520 ('u', 'user', None, 'show user'),
521 521 ('n', 'number', None, 'show revision number'),
522 522 ('c', 'changeset', None, 'show changeset')],
523 523 'hg annotate [-u] [-c] [-n] [-r id] [files]'),
524 524 "cat|dump": (cat, [], 'hg cat <file> [rev]'),
525 525 "commit|ci": (commit,
526 526 [('t', 'text', "", 'commit text'),
527 ('l', 'logfile', "", 'commit text file')],
527 ('l', 'logfile', "", 'commit text file'),
528 ('d', 'date', "", 'data'),
529 ('u', 'user', "", 'user')],
528 530 'hg commit [files]'),
529 531 "debugaddchangegroup": (debugaddchangegroup, [], 'debugaddchangegroup'),
530 532 "debugchangegroup": (debugchangegroup, [], 'debugchangegroup [roots]'),
531 533 "debugindex": (debugindex, [], 'debugindex <file>'),
532 534 "debugindexdot": (debugindexdot, [], 'debugindexdot <file>'),
533 535 "diff": (diff, [('r', 'rev', [], 'revision')],
534 536 'hg diff [-r A] [-r B] [files]'),
535 537 "export": (export, [], "hg export <changeset>"),
536 538 "forget": (forget, [], "hg forget [files]"),
537 539 "heads": (heads, [], 'hg heads'),
538 540 "history": (history, [], 'hg history'),
539 541 "help": (help, [], 'hg help [command]'),
540 542 "init": (init, [], 'hg init [url]'),
541 543 "log": (log, [], 'hg log <file>'),
542 544 "manifest|dumpmanifest": (manifest, [], 'hg manifest [rev]'),
543 545 "parents": (parents, [], 'hg parents [node]'),
544 546 "patch|import": (patch,
545 547 [('p', 'strip', 1, 'path strip'),
546 548 ('b', 'base', "", 'base path'),
547 549 ('q', 'quiet', "", 'silence diff')],
548 550 "hg import [options] patches"),
549 551 "pull|merge": (pull, [], 'hg pull [source]'),
550 552 "rawcommit": (rawcommit,
551 553 [('p', 'parent', [], 'parent'),
552 554 ('d', 'date', "", 'data'),
553 555 ('u', 'user', "", 'user'),
554 556 ('F', 'files', "", 'file list'),
555 557 ('t', 'text', "", 'commit text'),
556 558 ('l', 'logfile', "", 'commit text file')],
557 559 'hg rawcommit [options] [files]'),
558 560 "recover": (recover, [], "hg recover"),
559 561 "remove": (remove, [], "hg remove [files]"),
560 562 "serve": (serve, [('p', 'port', 8000, 'listen port'),
561 563 ('a', 'address', '', 'interface address'),
562 564 ('n', 'name', os.getcwd(), 'repository name'),
563 565 ('t', 'templates', "", 'template map')],
564 566 "hg serve [options]"),
565 567 "status": (status, [], 'hg status'),
566 568 "tags": (tags, [], 'hg tags'),
567 569 "tip": (tip, [], 'hg tip'),
568 570 "undo": (undo, [], 'hg undo'),
569 571 "update|up|checkout|co|resolve": (update,
570 572 [('m', 'merge', None,
571 573 'allow merging of conflicts'),
572 574 ('C', 'clean', None,
573 575 'overwrite locally modified files')],
574 576 'hg update [options] [node]'),
575 577 "verify": (verify, [], 'hg verify'),
576 578 }
577 579
578 580 norepo = "init branch help debugindex debugindexdot"
579 581
580 582 def find(cmd):
581 583 i = None
582 584 for e in table.keys():
583 585 if re.match(e + "$", cmd):
584 586 return table[e]
585 587
586 588 raise UnknownCommand(cmd)
587 589
588 590 class SignalInterrupt(Exception): pass
589 591
590 592 def catchterm(*args):
591 593 raise SignalInterrupt
592 594
593 595 def run():
594 596 sys.exit(dispatch(sys.argv[1:]))
595 597
596 598 def dispatch(args):
597 599 options = {}
598 600 opts = [('v', 'verbose', None, 'verbose'),
599 601 ('d', 'debug', None, 'debug'),
600 602 ('q', 'quiet', None, 'quiet'),
601 603 ('p', 'profile', None, 'profile'),
602 604 ('y', 'noninteractive', None, 'run non-interactively'),
603 605 ]
604 606
605 607 args = fancyopts.fancyopts(args, opts, options,
606 608 'hg [options] <command> [options] [files]')
607 609
608 610 if not args:
609 611 cmd = "help"
610 612 else:
611 613 cmd, args = args[0], args[1:]
612 614
613 615 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
614 616 not options["noninteractive"])
615 617
616 618 try:
617 619 i = find(cmd)
618 620 except UnknownCommand:
619 621 u.warn("hg: unknown command '%s'\n" % cmd)
620 622 help(u)
621 623 sys.exit(1)
622 624
623 625 signal.signal(signal.SIGTERM, catchterm)
624 626
625 627 cmdoptions = {}
626 628 try:
627 629 args = fancyopts.fancyopts(args, i[1], cmdoptions, i[2])
628 630 except fancyopts.getopt.GetoptError, inst:
629 631 u.warn("hg %s: %s\n" % (cmd, inst))
630 632 help(u, cmd)
631 633 sys.exit(-1)
632 634
633 635 if cmd not in norepo.split():
634 636 repo = hg.repository(ui = u)
635 637 d = lambda: i[0](u, repo, *args, **cmdoptions)
636 638 else:
637 639 d = lambda: i[0](u, *args, **cmdoptions)
638 640
639 641 try:
640 642 if options['profile']:
641 643 import hotshot, hotshot.stats
642 644 prof = hotshot.Profile("hg.prof")
643 645 r = prof.runcall(d)
644 646 prof.close()
645 647 stats = hotshot.stats.load("hg.prof")
646 648 stats.strip_dirs()
647 649 stats.sort_stats('time', 'calls')
648 650 stats.print_stats(40)
649 651 return r
650 652 else:
651 653 return d()
652 654 except SignalInterrupt:
653 655 u.warn("killed!\n")
654 656 except KeyboardInterrupt:
655 657 u.warn("interrupted!\n")
656 658 except IOError, inst:
657 659 if inst.errno == 32:
658 660 u.warn("broken pipe\n")
659 661 else:
660 662 raise
661 663 except TypeError, inst:
662 664 # was this an argument error?
663 665 tb = traceback.extract_tb(sys.exc_info()[2])
664 666 if len(tb) > 2: # no
665 667 raise
666 668 u.debug(inst, "\n")
667 669 u.warn("%s: invalid arguments\n" % i[0].__name__)
668 670 help(u, cmd)
669 671 sys.exit(-1)
670 672
@@ -1,1306 +1,1306 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 from revlog import *
10 10 from demandload import *
11 11 demandload(globals(), "re lock urllib urllib2 transaction time socket")
12 12 demandload(globals(), "tempfile byterange difflib")
13 13
14 14 def is_exec(f):
15 15 return (os.stat(f).st_mode & 0100 != 0)
16 16
17 17 def set_exec(f, mode):
18 18 s = os.stat(f).st_mode
19 19 if (s & 0100 != 0) == mode:
20 20 return
21 21 if mode:
22 22 # Turn on +x for every +r bit when making a file executable
23 23 # and obey umask.
24 24 umask = os.umask(0)
25 25 os.umask(umask)
26 26 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
27 27 else:
28 28 os.chmod(f, s & 0666)
29 29
30 30 class filelog(revlog):
31 31 def __init__(self, opener, path):
32 32 revlog.__init__(self, opener,
33 33 os.path.join("data", path + ".i"),
34 34 os.path.join("data", path + ".d"))
35 35
36 36 def read(self, node):
37 37 return self.revision(node)
38 38 def add(self, text, transaction, link, p1=None, p2=None):
39 39 return self.addrevision(text, transaction, link, p1, p2)
40 40
41 41 def annotate(self, node):
42 42
43 43 def decorate(text, rev):
44 44 return [(rev, l) for l in text.splitlines(1)]
45 45
46 46 def strip(annotation):
47 47 return [e[1] for e in annotation]
48 48
49 49 def pair(parent, child):
50 50 new = []
51 51 sm = difflib.SequenceMatcher(None, strip(parent), strip(child))
52 52 for o, m, n, s, t in sm.get_opcodes():
53 53 if o == 'equal':
54 54 new += parent[m:n]
55 55 else:
56 56 new += child[s:t]
57 57 return new
58 58
59 59 # find all ancestors
60 60 needed = {node:1}
61 61 visit = [node]
62 62 while visit:
63 63 n = visit.pop(0)
64 64 for p in self.parents(n):
65 65 if p not in needed:
66 66 needed[p] = 1
67 67 visit.append(p)
68 68 else:
69 69 # count how many times we'll use this
70 70 needed[p] += 1
71 71
72 72 # sort by revision which is a topological order
73 73 visit = needed.keys()
74 74 visit = [ (self.rev(n), n) for n in visit ]
75 75 visit.sort()
76 76 visit = [ p[1] for p in visit ]
77 77 hist = {}
78 78
79 79 for n in visit:
80 80 curr = decorate(self.read(n), self.linkrev(n))
81 81 for p in self.parents(n):
82 82 if p != nullid:
83 83 curr = pair(hist[p], curr)
84 84 # trim the history of unneeded revs
85 85 needed[p] -= 1
86 86 if not needed[p]:
87 87 del hist[p]
88 88 hist[n] = curr
89 89
90 90 return hist[n]
91 91
92 92 class manifest(revlog):
93 93 def __init__(self, opener):
94 94 self.mapcache = None
95 95 self.listcache = None
96 96 self.addlist = None
97 97 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
98 98
99 99 def read(self, node):
100 100 if node == nullid: return {} # don't upset local cache
101 101 if self.mapcache and self.mapcache[0] == node:
102 102 return self.mapcache[1].copy()
103 103 text = self.revision(node)
104 104 map = {}
105 105 flag = {}
106 106 self.listcache = (text, text.splitlines(1))
107 107 for l in self.listcache[1]:
108 108 (f, n) = l.split('\0')
109 109 map[f] = bin(n[:40])
110 110 flag[f] = (n[40:-1] == "x")
111 111 self.mapcache = (node, map, flag)
112 112 return map
113 113
114 114 def readflags(self, node):
115 115 if node == nullid: return {} # don't upset local cache
116 116 if self.mapcache or self.mapcache[0] != node:
117 117 self.read(node)
118 118 return self.mapcache[2]
119 119
120 120 def diff(self, a, b):
121 121 # this is sneaky, as we're not actually using a and b
122 122 if self.listcache and self.addlist and self.listcache[0] == a:
123 123 d = mdiff.diff(self.listcache[1], self.addlist, 1)
124 124 if mdiff.patch(a, d) != b:
125 125 sys.stderr.write("*** sortdiff failed, falling back ***\n")
126 126 return mdiff.textdiff(a, b)
127 127 return d
128 128 else:
129 129 return mdiff.textdiff(a, b)
130 130
131 131 def add(self, map, flags, transaction, link, p1=None, p2=None):
132 132 files = map.keys()
133 133 files.sort()
134 134
135 135 self.addlist = ["%s\000%s%s\n" %
136 136 (f, hex(map[f]), flags[f] and "x" or '')
137 137 for f in files]
138 138 text = "".join(self.addlist)
139 139
140 140 n = self.addrevision(text, transaction, link, p1, p2)
141 141 self.mapcache = (n, map, flags)
142 142 self.listcache = (text, self.addlist)
143 143 self.addlist = None
144 144
145 145 return n
146 146
147 147 class changelog(revlog):
148 148 def __init__(self, opener):
149 149 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
150 150
151 151 def extract(self, text):
152 152 if not text:
153 153 return (nullid, "", "0", [], "")
154 154 last = text.index("\n\n")
155 155 desc = text[last + 2:]
156 156 l = text[:last].splitlines()
157 157 manifest = bin(l[0])
158 158 user = l[1]
159 159 date = l[2]
160 160 files = l[3:]
161 161 return (manifest, user, date, files, desc)
162 162
163 163 def read(self, node):
164 164 return self.extract(self.revision(node))
165 165
166 166 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
167 167 user=None, date=None):
168 168 user = (user or
169 169 os.environ.get("HGUSER") or
170 170 os.environ.get("EMAIL") or
171 171 os.environ.get("LOGNAME", "unknown") + '@' + socket.getfqdn())
172 172 date = date or "%d %d" % (time.time(), time.timezone)
173 173 list.sort()
174 174 l = [hex(manifest), user, date] + list + ["", desc]
175 175 text = "\n".join(l)
176 176 return self.addrevision(text, transaction, self.count(), p1, p2)
177 177
178 178 class dirstate:
179 179 def __init__(self, opener, ui, root):
180 180 self.opener = opener
181 181 self.root = root
182 182 self.dirty = 0
183 183 self.ui = ui
184 184 self.map = None
185 185 self.pl = None
186 186
187 187 def __del__(self):
188 188 if self.dirty:
189 189 self.write()
190 190
191 191 def __getitem__(self, key):
192 192 try:
193 193 return self.map[key]
194 194 except TypeError:
195 195 self.read()
196 196 return self[key]
197 197
198 198 def __contains__(self, key):
199 199 if not self.map: self.read()
200 200 return key in self.map
201 201
202 202 def parents(self):
203 203 if not self.pl:
204 204 self.read()
205 205 return self.pl
206 206
207 207 def setparents(self, p1, p2 = nullid):
208 208 self.dirty = 1
209 209 self.pl = p1, p2
210 210
211 211 def state(self, key):
212 212 try:
213 213 return self[key][0]
214 214 except KeyError:
215 215 return "?"
216 216
217 217 def read(self):
218 218 if self.map is not None: return self.map
219 219
220 220 self.map = {}
221 221 self.pl = [nullid, nullid]
222 222 try:
223 223 st = self.opener("dirstate").read()
224 224 if not st: return
225 225 except: return
226 226
227 227 self.pl = [st[:20], st[20: 40]]
228 228
229 229 pos = 40
230 230 while pos < len(st):
231 231 e = struct.unpack(">cllll", st[pos:pos+17])
232 232 l = e[4]
233 233 pos += 17
234 234 f = st[pos:pos + l]
235 235 self.map[f] = e[:4]
236 236 pos += l
237 237
238 238 def update(self, files, state):
239 239 ''' current states:
240 240 n normal
241 241 m needs merging
242 242 r marked for removal
243 243 a marked for addition'''
244 244
245 245 if not files: return
246 246 self.read()
247 247 self.dirty = 1
248 248 for f in files:
249 249 if state == "r":
250 250 self.map[f] = ('r', 0, 0, 0)
251 251 else:
252 252 s = os.stat(os.path.join(self.root, f))
253 253 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
254 254
255 255 def forget(self, files):
256 256 if not files: return
257 257 self.read()
258 258 self.dirty = 1
259 259 for f in files:
260 260 try:
261 261 del self.map[f]
262 262 except KeyError:
263 263 self.ui.warn("not in dirstate: %s!\n" % f)
264 264 pass
265 265
266 266 def clear(self):
267 267 self.map = {}
268 268 self.dirty = 1
269 269
270 270 def write(self):
271 271 st = self.opener("dirstate", "w")
272 272 st.write("".join(self.pl))
273 273 for f, e in self.map.items():
274 274 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
275 275 st.write(e + f)
276 276 self.dirty = 0
277 277
278 278 def copy(self):
279 279 self.read()
280 280 return self.map.copy()
281 281
282 282 # used to avoid circular references so destructors work
283 283 def opener(base):
284 284 p = base
285 285 def o(path, mode="r"):
286 286 if p[:7] == "http://":
287 287 f = os.path.join(p, urllib.quote(path))
288 288 return httprangereader(f)
289 289
290 290 f = os.path.join(p, path)
291 291
292 292 mode += "b" # for that other OS
293 293
294 294 if mode[0] != "r":
295 295 try:
296 296 s = os.stat(f)
297 297 except OSError:
298 298 d = os.path.dirname(f)
299 299 if not os.path.isdir(d):
300 300 os.makedirs(d)
301 301 else:
302 302 if s.st_nlink > 1:
303 303 file(f + ".tmp", "w").write(file(f).read())
304 304 os.rename(f+".tmp", f)
305 305
306 306 return file(f, mode)
307 307
308 308 return o
309 309
310 310 class localrepository:
311 311 def __init__(self, ui, path=None, create=0):
312 312 self.remote = 0
313 313 if path and path[:7] == "http://":
314 314 self.remote = 1
315 315 self.path = path
316 316 else:
317 317 if not path:
318 318 p = os.getcwd()
319 319 while not os.path.isdir(os.path.join(p, ".hg")):
320 320 p = os.path.dirname(p)
321 321 if p == "/": raise "No repo found"
322 322 path = p
323 323 self.path = os.path.join(path, ".hg")
324 324
325 325 self.root = path
326 326 self.ui = ui
327 327
328 328 if create:
329 329 os.mkdir(self.path)
330 330 os.mkdir(self.join("data"))
331 331
332 332 self.opener = opener(self.path)
333 333 self.wopener = opener(self.root)
334 334 self.manifest = manifest(self.opener)
335 335 self.changelog = changelog(self.opener)
336 336 self.ignorelist = None
337 337 self.tags = None
338 338
339 339 if not self.remote:
340 340 self.dirstate = dirstate(self.opener, ui, self.root)
341 341
342 342 def ignore(self, f):
343 343 if self.ignorelist is None:
344 344 self.ignorelist = []
345 345 try:
346 346 l = self.wfile(".hgignore")
347 347 for pat in l:
348 348 if pat != "\n":
349 349 self.ignorelist.append(re.compile(pat[:-1]))
350 350 except IOError: pass
351 351 for pat in self.ignorelist:
352 352 if pat.search(f): return True
353 353 return False
354 354
355 355 def lookup(self, key):
356 356 if self.tags is None:
357 357 self.tags = {}
358 358 try:
359 359 # read each head of the tags file, ending with the tip
360 360 # and add each tag found to the map, with "newer" ones
361 361 # taking precedence
362 362 fl = self.file(".hgtags")
363 363 h = fl.heads()
364 364 h.reverse()
365 365 for r in h:
366 366 for l in fl.revision(r).splitlines():
367 367 if l:
368 368 n, k = l.split(" ")
369 369 self.tags[k] = bin(n)
370 370 except KeyError: pass
371 371 self.tags['tip'] = self.changelog.tip()
372 372 try:
373 373 return self.tags[key]
374 374 except KeyError:
375 375 return self.changelog.lookup(key)
376 376
377 377 def join(self, f):
378 378 return os.path.join(self.path, f)
379 379
380 380 def wjoin(self, f):
381 381 return os.path.join(self.root, f)
382 382
383 383 def file(self, f):
384 384 if f[0] == '/': f = f[1:]
385 385 return filelog(self.opener, f)
386 386
387 387 def wfile(self, f, mode='r'):
388 388 return self.wopener(f, mode)
389 389
390 390 def transaction(self):
391 391 # save dirstate for undo
392 392 try:
393 393 ds = self.opener("dirstate").read()
394 394 except IOError:
395 395 ds = ""
396 396 self.opener("undo.dirstate", "w").write(ds)
397 397
398 398 return transaction.transaction(self.opener, self.join("journal"),
399 399 self.join("undo"))
400 400
401 401 def recover(self):
402 402 lock = self.lock()
403 403 if os.path.exists(self.join("recover")):
404 404 self.ui.status("attempting to rollback interrupted transaction\n")
405 405 return transaction.rollback(self.opener, self.join("recover"))
406 406 else:
407 407 self.ui.warn("no interrupted transaction available\n")
408 408
409 409 def undo(self):
410 410 lock = self.lock()
411 411 if os.path.exists(self.join("undo")):
412 412 self.ui.status("attempting to rollback last transaction\n")
413 413 transaction.rollback(self.opener, self.join("undo"))
414 414 self.dirstate = None
415 415 os.rename(self.join("undo.dirstate"), self.join("dirstate"))
416 416 self.dirstate = dirstate(self.opener, self.ui, self.root)
417 417 else:
418 418 self.ui.warn("no undo information available\n")
419 419
420 420 def lock(self, wait = 1):
421 421 try:
422 422 return lock.lock(self.join("lock"), 0)
423 423 except lock.LockHeld, inst:
424 424 if wait:
425 425 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
426 426 return lock.lock(self.join("lock"), wait)
427 427 raise inst
428 428
429 429 def rawcommit(self, files, text, user, date, p1=None, p2=None):
430 430 p1 = p1 or self.dirstate.parents()[0] or nullid
431 431 p2 = p2 or self.dirstate.parents()[1] or nullid
432 432 c1 = self.changelog.read(p1)
433 433 c2 = self.changelog.read(p2)
434 434 m1 = self.manifest.read(c1[0])
435 435 mf1 = self.manifest.readflags(c1[0])
436 436 m2 = self.manifest.read(c2[0])
437 437
438 438 tr = self.transaction()
439 439 mm = m1.copy()
440 440 mfm = mf1.copy()
441 441 linkrev = self.changelog.count()
442 442 self.dirstate.setparents(p1, p2)
443 443 for f in files:
444 444 try:
445 445 t = self.wfile(f).read()
446 446 tm = is_exec(self.wjoin(f))
447 447 r = self.file(f)
448 448 mfm[f] = tm
449 449 mm[f] = r.add(t, tr, linkrev,
450 450 m1.get(f, nullid), m2.get(f, nullid))
451 451 self.dirstate.update([f], "n")
452 452 except IOError:
453 453 try:
454 454 del mm[f]
455 455 del mfm[f]
456 456 self.dirstate.forget([f])
457 457 except:
458 458 # deleted from p2?
459 459 pass
460 460
461 461 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
462 462 n = self.changelog.add(mnode, files, text, tr, p1, p2, user, date)
463 463 tr.close()
464 464
465 def commit(self, files = None, text = ""):
465 def commit(self, files = None, text = "", user = None, date = None):
466 466 commit = []
467 467 remove = []
468 468 if files:
469 469 for f in files:
470 470 s = self.dirstate.state(f)
471 471 if s in 'nmai':
472 472 commit.append(f)
473 473 elif s == 'r':
474 474 remove.append(f)
475 475 else:
476 476 self.ui.warn("%s not tracked!\n" % f)
477 477 else:
478 478 (c, a, d, u) = self.diffdir(self.root)
479 479 commit = c + a
480 480 remove = d
481 481
482 482 if not commit and not remove:
483 483 self.ui.status("nothing changed\n")
484 484 return
485 485
486 486 p1, p2 = self.dirstate.parents()
487 487 c1 = self.changelog.read(p1)
488 488 c2 = self.changelog.read(p2)
489 489 m1 = self.manifest.read(c1[0])
490 490 mf1 = self.manifest.readflags(c1[0])
491 491 m2 = self.manifest.read(c2[0])
492 492 lock = self.lock()
493 493 tr = self.transaction()
494 494
495 495 # check in files
496 496 new = {}
497 497 linkrev = self.changelog.count()
498 498 commit.sort()
499 499 for f in commit:
500 500 self.ui.note(f + "\n")
501 501 try:
502 502 fp = self.wjoin(f)
503 503 mf1[f] = is_exec(fp)
504 504 t = file(fp).read()
505 505 except IOError:
506 506 self.warn("trouble committing %s!\n" % f)
507 507 raise
508 508
509 509 r = self.file(f)
510 510 fp1 = m1.get(f, nullid)
511 511 fp2 = m2.get(f, nullid)
512 512 new[f] = r.add(t, tr, linkrev, fp1, fp2)
513 513
514 514 # update manifest
515 515 m1.update(new)
516 516 for f in remove: del m1[f]
517 517 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0])
518 518
519 519 # add changeset
520 520 new = new.keys()
521 521 new.sort()
522 522
523 523 if not text:
524 524 edittext = "\n" + "HG: manifest hash %s\n" % hex(mn)
525 525 edittext += "".join(["HG: changed %s\n" % f for f in new])
526 526 edittext += "".join(["HG: removed %s\n" % f for f in remove])
527 527 edittext = self.ui.edit(edittext)
528 528 if not edittext.rstrip():
529 529 return 1
530 530 text = edittext
531 531
532 n = self.changelog.add(mn, new, text, tr, p1, p2)
532 n = self.changelog.add(mn, new, text, tr, p1, p2, user, date)
533 533 tr.close()
534 534
535 535 self.dirstate.setparents(n)
536 536 self.dirstate.update(new, "n")
537 537 self.dirstate.forget(remove)
538 538
539 539 def diffdir(self, path, changeset = None):
540 540 changed = []
541 541 added = []
542 542 unknown = []
543 543 mf = {}
544 544
545 545 if changeset:
546 546 change = self.changelog.read(changeset)
547 547 mf = self.manifest.read(change[0])
548 548 dc = dict.fromkeys(mf)
549 549 else:
550 550 changeset = self.dirstate.parents()[0]
551 551 change = self.changelog.read(changeset)
552 552 mf = self.manifest.read(change[0])
553 553 dc = self.dirstate.copy()
554 554
555 555 def fcmp(fn):
556 556 t1 = self.wfile(fn).read()
557 557 t2 = self.file(fn).revision(mf[fn])
558 558 return cmp(t1, t2)
559 559
560 560 for dir, subdirs, files in os.walk(path):
561 561 d = dir[len(self.root)+1:]
562 562 if ".hg" in subdirs: subdirs.remove(".hg")
563 563
564 564 for f in files:
565 565 fn = os.path.join(d, f)
566 566 try: s = os.stat(os.path.join(self.root, fn))
567 567 except: continue
568 568 if fn in dc:
569 569 c = dc[fn]
570 570 del dc[fn]
571 571 if not c:
572 572 if fcmp(fn):
573 573 changed.append(fn)
574 574 elif c[0] == 'm':
575 575 changed.append(fn)
576 576 elif c[0] == 'a':
577 577 added.append(fn)
578 578 elif c[0] == 'r':
579 579 unknown.append(fn)
580 580 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
581 581 changed.append(fn)
582 582 elif c[1] != s.st_mode or c[3] != s.st_mtime:
583 583 if fcmp(fn):
584 584 changed.append(fn)
585 585 else:
586 586 if self.ignore(fn): continue
587 587 unknown.append(fn)
588 588
589 589 deleted = dc.keys()
590 590 deleted.sort()
591 591
592 592 return (changed, added, deleted, unknown)
593 593
594 594 def diffrevs(self, node1, node2):
595 595 changed, added = [], []
596 596
597 597 change = self.changelog.read(node1)
598 598 mf1 = self.manifest.read(change[0])
599 599 change = self.changelog.read(node2)
600 600 mf2 = self.manifest.read(change[0])
601 601
602 602 for fn in mf2:
603 603 if mf1.has_key(fn):
604 604 if mf1[fn] != mf2[fn]:
605 605 changed.append(fn)
606 606 del mf1[fn]
607 607 else:
608 608 added.append(fn)
609 609
610 610 deleted = mf1.keys()
611 611 deleted.sort()
612 612
613 613 return (changed, added, deleted)
614 614
615 615 def add(self, list):
616 616 for f in list:
617 617 p = self.wjoin(f)
618 618 if not os.path.isfile(p):
619 619 self.ui.warn("%s does not exist!\n" % f)
620 620 elif self.dirstate.state(f) == 'n':
621 621 self.ui.warn("%s already tracked!\n" % f)
622 622 else:
623 623 self.dirstate.update([f], "a")
624 624
625 625 def forget(self, list):
626 626 for f in list:
627 627 if self.dirstate.state(f) not in 'ai':
628 628 self.ui.warn("%s not added!\n" % f)
629 629 else:
630 630 self.dirstate.forget([f])
631 631
632 632 def remove(self, list):
633 633 for f in list:
634 634 p = self.wjoin(f)
635 635 if os.path.isfile(p):
636 636 self.ui.warn("%s still exists!\n" % f)
637 637 elif f not in self.dirstate:
638 638 self.ui.warn("%s not tracked!\n" % f)
639 639 else:
640 640 self.dirstate.update([f], "r")
641 641
642 642 def heads(self):
643 643 return self.changelog.heads()
644 644
645 645 def branches(self, nodes):
646 646 if not nodes: nodes = [self.changelog.tip()]
647 647 b = []
648 648 for n in nodes:
649 649 t = n
650 650 while n:
651 651 p = self.changelog.parents(n)
652 652 if p[1] != nullid or p[0] == nullid:
653 653 b.append((t, n, p[0], p[1]))
654 654 break
655 655 n = p[0]
656 656 return b
657 657
658 658 def between(self, pairs):
659 659 r = []
660 660
661 661 for top, bottom in pairs:
662 662 n, l, i = top, [], 0
663 663 f = 1
664 664
665 665 while n != bottom:
666 666 p = self.changelog.parents(n)[0]
667 667 if i == f:
668 668 l.append(n)
669 669 f = f * 2
670 670 n = p
671 671 i += 1
672 672
673 673 r.append(l)
674 674
675 675 return r
676 676
677 677 def newer(self, nodes):
678 678 m = {}
679 679 nl = []
680 680 pm = {}
681 681 cl = self.changelog
682 682 t = l = cl.count()
683 683
684 684 # find the lowest numbered node
685 685 for n in nodes:
686 686 l = min(l, cl.rev(n))
687 687 m[n] = 1
688 688
689 689 for i in xrange(l, t):
690 690 n = cl.node(i)
691 691 if n in m: # explicitly listed
692 692 pm[n] = 1
693 693 nl.append(n)
694 694 continue
695 695 for p in cl.parents(n):
696 696 if p in pm: # parent listed
697 697 pm[n] = 1
698 698 nl.append(n)
699 699 break
700 700
701 701 return nl
702 702
703 703 def getchangegroup(self, remote):
704 704 m = self.changelog.nodemap
705 705 search = []
706 706 fetch = []
707 707 seen = {}
708 708 seenbranch = {}
709 709
710 710 # if we have an empty repo, fetch everything
711 711 if self.changelog.tip() == nullid:
712 712 self.ui.status("requesting all changes\n")
713 713 return remote.changegroup([nullid])
714 714
715 715 # otherwise, assume we're closer to the tip than the root
716 716 self.ui.status("searching for changes\n")
717 717 heads = remote.heads()
718 718 unknown = []
719 719 for h in heads:
720 720 if h not in m:
721 721 unknown.append(h)
722 722
723 723 if not unknown:
724 724 self.ui.status("nothing to do!\n")
725 725 return None
726 726
727 727 unknown = remote.branches(unknown)
728 728 while unknown:
729 729 n = unknown.pop(0)
730 730 seen[n[0]] = 1
731 731
732 732 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
733 733 if n == nullid: break
734 734 if n in seenbranch:
735 735 self.ui.debug("branch already found\n")
736 736 continue
737 737 if n[1] and n[1] in m: # do we know the base?
738 738 self.ui.debug("found incomplete branch %s:%s\n"
739 739 % (short(n[0]), short(n[1])))
740 740 search.append(n) # schedule branch range for scanning
741 741 seenbranch[n] = 1
742 742 else:
743 743 if n[2] in m and n[3] in m:
744 744 if n[1] not in fetch:
745 745 self.ui.debug("found new changeset %s\n" %
746 746 short(n[1]))
747 747 fetch.append(n[1]) # earliest unknown
748 748 continue
749 749
750 750 r = []
751 751 for a in n[2:4]:
752 752 if a not in seen: r.append(a)
753 753
754 754 if r:
755 755 self.ui.debug("requesting %s\n" %
756 756 " ".join(map(short, r)))
757 757 for b in remote.branches(r):
758 758 self.ui.debug("received %s:%s\n" %
759 759 (short(b[0]), short(b[1])))
760 760 if b[0] not in m and b[0] not in seen:
761 761 unknown.append(b)
762 762
763 763 while search:
764 764 n = search.pop(0)
765 765 l = remote.between([(n[0], n[1])])[0]
766 766 p = n[0]
767 767 f = 1
768 768 for i in l + [n[1]]:
769 769 if i in m:
770 770 if f <= 2:
771 771 self.ui.debug("found new branch changeset %s\n" %
772 772 short(p))
773 773 fetch.append(p)
774 774 else:
775 775 self.ui.debug("narrowed branch search to %s:%s\n"
776 776 % (short(p), short(i)))
777 777 search.append((p, i))
778 778 break
779 779 p, f = i, f * 2
780 780
781 781 for f in fetch:
782 782 if f in m:
783 783 raise "already have", short(f[:4])
784 784
785 785 self.ui.note("adding new changesets starting at " +
786 786 " ".join([short(f) for f in fetch]) + "\n")
787 787
788 788 return remote.changegroup(fetch)
789 789
790 790 def changegroup(self, basenodes):
791 791 nodes = self.newer(basenodes)
792 792
793 793 # construct the link map
794 794 linkmap = {}
795 795 for n in nodes:
796 796 linkmap[self.changelog.rev(n)] = n
797 797
798 798 # construct a list of all changed files
799 799 changed = {}
800 800 for n in nodes:
801 801 c = self.changelog.read(n)
802 802 for f in c[3]:
803 803 changed[f] = 1
804 804 changed = changed.keys()
805 805 changed.sort()
806 806
807 807 # the changegroup is changesets + manifests + all file revs
808 808 revs = [ self.changelog.rev(n) for n in nodes ]
809 809
810 810 for y in self.changelog.group(linkmap): yield y
811 811 for y in self.manifest.group(linkmap): yield y
812 812 for f in changed:
813 813 yield struct.pack(">l", len(f) + 4) + f
814 814 g = self.file(f).group(linkmap)
815 815 for y in g:
816 816 yield y
817 817
818 818 def addchangegroup(self, generator):
819 819
820 820 class genread:
821 821 def __init__(self, generator):
822 822 self.g = generator
823 823 self.buf = ""
824 824 def read(self, l):
825 825 while l > len(self.buf):
826 826 try:
827 827 self.buf += self.g.next()
828 828 except StopIteration:
829 829 break
830 830 d, self.buf = self.buf[:l], self.buf[l:]
831 831 return d
832 832
833 833 def getchunk():
834 834 d = source.read(4)
835 835 if not d: return ""
836 836 l = struct.unpack(">l", d)[0]
837 837 if l <= 4: return ""
838 838 return source.read(l - 4)
839 839
840 840 def getgroup():
841 841 while 1:
842 842 c = getchunk()
843 843 if not c: break
844 844 yield c
845 845
846 846 def csmap(x):
847 847 self.ui.debug("add changeset %s\n" % short(x))
848 848 return self.changelog.count()
849 849
850 850 def revmap(x):
851 851 return self.changelog.rev(x)
852 852
853 853 if not generator: return
854 854 changesets = files = revisions = 0
855 855
856 856 source = genread(generator)
857 857 lock = self.lock()
858 858 tr = self.transaction()
859 859
860 860 # pull off the changeset group
861 861 self.ui.status("adding changesets\n")
862 862 co = self.changelog.tip()
863 863 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
864 864 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
865 865
866 866 # pull off the manifest group
867 867 self.ui.status("adding manifests\n")
868 868 mm = self.manifest.tip()
869 869 mo = self.manifest.addgroup(getgroup(), revmap, tr)
870 870
871 871 # process the files
872 872 self.ui.status("adding file revisions\n")
873 873 while 1:
874 874 f = getchunk()
875 875 if not f: break
876 876 self.ui.debug("adding %s revisions\n" % f)
877 877 fl = self.file(f)
878 878 o = fl.tip()
879 879 n = fl.addgroup(getgroup(), revmap, tr)
880 880 revisions += fl.rev(n) - fl.rev(o)
881 881 files += 1
882 882
883 883 self.ui.status(("modified %d files, added %d changesets" +
884 884 " and %d new revisions\n")
885 885 % (files, changesets, revisions))
886 886
887 887 tr.close()
888 888 return
889 889
890 890 def update(self, node, allow=False, force=False):
891 891 pl = self.dirstate.parents()
892 892 if not force and pl[1] != nullid:
893 893 self.ui.warn("aborting: outstanding uncommitted merges\n")
894 894 return
895 895
896 896 p1, p2 = pl[0], node
897 897 pa = self.changelog.ancestor(p1, p2)
898 898 m1n = self.changelog.read(p1)[0]
899 899 m2n = self.changelog.read(p2)[0]
900 900 man = self.manifest.ancestor(m1n, m2n)
901 901 m1 = self.manifest.read(m1n)
902 902 mf1 = self.manifest.readflags(m1n)
903 903 m2 = self.manifest.read(m2n)
904 904 mf2 = self.manifest.readflags(m2n)
905 905 ma = self.manifest.read(man)
906 906 mfa = self.manifest.readflags(m2n)
907 907
908 908 (c, a, d, u) = self.diffdir(self.root)
909 909
910 910 # resolve the manifest to determine which files
911 911 # we care about merging
912 912 self.ui.note("resolving manifests\n")
913 913 self.ui.debug(" ancestor %s local %s remote %s\n" %
914 914 (short(man), short(m1n), short(m2n)))
915 915
916 916 merge = {}
917 917 get = {}
918 918 remove = []
919 919 mark = {}
920 920
921 921 # construct a working dir manifest
922 922 mw = m1.copy()
923 923 mfw = mf1.copy()
924 924 for f in a + c + u:
925 925 mw[f] = ""
926 926 mfw[f] = is_exec(self.wjoin(f))
927 927 for f in d:
928 928 if f in mw: del mw[f]
929 929
930 930 for f, n in mw.iteritems():
931 931 if f in m2:
932 932 s = 0
933 933
934 934 # are files different?
935 935 if n != m2[f]:
936 936 a = ma.get(f, nullid)
937 937 # are both different from the ancestor?
938 938 if n != a and m2[f] != a:
939 939 self.ui.debug(" %s versions differ, resolve\n" % f)
940 940 merge[f] = (m1.get(f, nullid), m2[f])
941 941 # merge executable bits
942 942 # "if we changed or they changed, change in merge"
943 943 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
944 944 mode = ((a^b) | (a^c)) ^ a
945 945 merge[f] = (m1.get(f, nullid), m2[f], mode)
946 946 s = 1
947 947 # are we clobbering?
948 948 # is remote's version newer?
949 949 # or are we going back in time?
950 950 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
951 951 self.ui.debug(" remote %s is newer, get\n" % f)
952 952 get[f] = m2[f]
953 953 s = 1
954 954 else:
955 955 mark[f] = 1
956 956
957 957 if not s and mfw[f] != mf2[f]:
958 958 if force:
959 959 self.ui.debug(" updating permissions for %s\n" % f)
960 960 set_exec(self.wjoin(f), mf2[f])
961 961 else:
962 962 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
963 963 mode = ((a^b) | (a^c)) ^ a
964 964 if mode != b:
965 965 self.ui.debug(" updating permissions for %s\n" % f)
966 966 set_exec(self.wjoin(f), mode)
967 967 mark[f] = 1
968 968 del m2[f]
969 969 elif f in ma:
970 970 if not force and n != ma[f]:
971 971 r = self.ui.prompt(
972 972 (" local changed %s which remote deleted\n" % f) +
973 973 "(k)eep or (d)elete?", "[kd]", "k")
974 974 if r == "d":
975 975 remove.append(f)
976 976 else:
977 977 self.ui.debug("other deleted %s\n" % f)
978 978 remove.append(f) # other deleted it
979 979 else:
980 980 if n == m1.get(f, nullid): # same as parent
981 981 self.ui.debug("remote deleted %s\n" % f)
982 982 remove.append(f)
983 983 else:
984 984 self.ui.debug("working dir created %s, keeping\n" % f)
985 985
986 986 for f, n in m2.iteritems():
987 987 if f[0] == "/": continue
988 988 if not force and f in ma and n != ma[f]:
989 989 r = self.ui.prompt(
990 990 ("remote changed %s which local deleted\n" % f) +
991 991 "(k)eep or (d)elete?", "[kd]", "k")
992 992 if r == "d": remove.append(f)
993 993 else:
994 994 self.ui.debug("remote created %s\n" % f)
995 995 get[f] = n
996 996
997 997 del mw, m1, m2, ma
998 998
999 999 if force:
1000 1000 for f in merge:
1001 1001 get[f] = merge[f][1]
1002 1002 merge = {}
1003 1003
1004 1004 if pa == p1 or pa == p2:
1005 1005 # we don't need to do any magic, just jump to the new rev
1006 1006 mode = 'n'
1007 1007 p1, p2 = p2, nullid
1008 1008 else:
1009 1009 if not allow:
1010 1010 self.ui.status("this update spans a branch" +
1011 1011 " affecting the following files:\n")
1012 1012 fl = merge.keys() + get.keys()
1013 1013 fl.sort()
1014 1014 for f in fl:
1015 1015 cf = ""
1016 1016 if f in merge: cf = " (resolve)"
1017 1017 self.ui.status(" %s%s\n" % (f, cf))
1018 1018 self.ui.warn("aborting update spanning branches!\n")
1019 1019 self.ui.status("(use update -m to perform a branch merge)\n")
1020 1020 return 1
1021 1021 # we have to remember what files we needed to get/change
1022 1022 # because any file that's different from either one of its
1023 1023 # parents must be in the changeset
1024 1024 mode = 'm'
1025 1025 self.dirstate.update(mark.keys(), "m")
1026 1026
1027 1027 self.dirstate.setparents(p1, p2)
1028 1028
1029 1029 # get the files we don't need to change
1030 1030 files = get.keys()
1031 1031 files.sort()
1032 1032 for f in files:
1033 1033 if f[0] == "/": continue
1034 1034 self.ui.note("getting %s\n" % f)
1035 1035 t = self.file(f).read(get[f])
1036 1036 try:
1037 1037 self.wfile(f, "w").write(t)
1038 1038 except IOError:
1039 1039 os.makedirs(os.path.dirname(self.wjoin(f)))
1040 1040 self.wfile(f, "w").write(t)
1041 1041 set_exec(self.wjoin(f), mf2[f])
1042 1042 self.dirstate.update([f], mode)
1043 1043
1044 1044 # merge the tricky bits
1045 1045 files = merge.keys()
1046 1046 files.sort()
1047 1047 for f in files:
1048 1048 self.ui.status("merging %s\n" % f)
1049 1049 m, o, flag = merge[f]
1050 1050 self.merge3(f, m, o)
1051 1051 set_exec(self.wjoin(f), flag)
1052 1052 self.dirstate.update([f], 'm')
1053 1053
1054 1054 for f in remove:
1055 1055 self.ui.note("removing %s\n" % f)
1056 1056 os.unlink(f)
1057 1057 if mode == 'n':
1058 1058 self.dirstate.forget(remove)
1059 1059 else:
1060 1060 self.dirstate.update(remove, 'r')
1061 1061
1062 1062 def merge3(self, fn, my, other):
1063 1063 """perform a 3-way merge in the working directory"""
1064 1064
1065 1065 def temp(prefix, node):
1066 1066 pre = "%s~%s." % (os.path.basename(fn), prefix)
1067 1067 (fd, name) = tempfile.mkstemp("", pre)
1068 1068 f = os.fdopen(fd, "w")
1069 1069 f.write(fl.revision(node))
1070 1070 f.close()
1071 1071 return name
1072 1072
1073 1073 fl = self.file(fn)
1074 1074 base = fl.ancestor(my, other)
1075 1075 a = self.wjoin(fn)
1076 1076 b = temp("other", other)
1077 1077 c = temp("base", base)
1078 1078
1079 1079 self.ui.note("resolving %s\n" % fn)
1080 1080 self.ui.debug("file %s: other %s ancestor %s\n" %
1081 1081 (fn, short(other), short(base)))
1082 1082
1083 1083 cmd = os.environ.get("HGMERGE", "hgmerge")
1084 1084 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1085 1085 if r:
1086 1086 self.ui.warn("merging %s failed!\n" % fn)
1087 1087
1088 1088 os.unlink(b)
1089 1089 os.unlink(c)
1090 1090
1091 1091 def verify(self):
1092 1092 filelinkrevs = {}
1093 1093 filenodes = {}
1094 1094 changesets = revisions = files = 0
1095 1095 errors = 0
1096 1096
1097 1097 seen = {}
1098 1098 self.ui.status("checking changesets\n")
1099 1099 for i in range(self.changelog.count()):
1100 1100 changesets += 1
1101 1101 n = self.changelog.node(i)
1102 1102 if n in seen:
1103 1103 self.ui.warn("duplicate changeset at revision %d\n" % i)
1104 1104 errors += 1
1105 1105 seen[n] = 1
1106 1106
1107 1107 for p in self.changelog.parents(n):
1108 1108 if p not in self.changelog.nodemap:
1109 1109 self.ui.warn("changeset %s has unknown parent %s\n" %
1110 1110 (short(n), short(p)))
1111 1111 errors += 1
1112 1112 try:
1113 1113 changes = self.changelog.read(n)
1114 1114 except Exception, inst:
1115 1115 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1116 1116 errors += 1
1117 1117
1118 1118 for f in changes[3]:
1119 1119 filelinkrevs.setdefault(f, []).append(i)
1120 1120
1121 1121 seen = {}
1122 1122 self.ui.status("checking manifests\n")
1123 1123 for i in range(self.manifest.count()):
1124 1124 n = self.manifest.node(i)
1125 1125 if n in seen:
1126 1126 self.ui.warn("duplicate manifest at revision %d\n" % i)
1127 1127 errors += 1
1128 1128 seen[n] = 1
1129 1129
1130 1130 for p in self.manifest.parents(n):
1131 1131 if p not in self.manifest.nodemap:
1132 1132 self.ui.warn("manifest %s has unknown parent %s\n" %
1133 1133 (short(n), short(p)))
1134 1134 errors += 1
1135 1135
1136 1136 try:
1137 1137 delta = mdiff.patchtext(self.manifest.delta(n))
1138 1138 except KeyboardInterrupt:
1139 1139 print "aborted"
1140 1140 sys.exit(0)
1141 1141 except Exception, inst:
1142 1142 self.ui.warn("unpacking manifest %s: %s\n"
1143 1143 % (short(n), inst))
1144 1144 errors += 1
1145 1145
1146 1146 ff = [ l.split('\0') for l in delta.splitlines() ]
1147 1147 for f, fn in ff:
1148 1148 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1149 1149
1150 1150 self.ui.status("crosschecking files in changesets and manifests\n")
1151 1151 for f in filenodes:
1152 1152 if f not in filelinkrevs:
1153 1153 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1154 1154 errors += 1
1155 1155
1156 1156 for f in filelinkrevs:
1157 1157 if f not in filenodes:
1158 1158 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1159 1159 errors += 1
1160 1160
1161 1161 self.ui.status("checking files\n")
1162 1162 ff = filenodes.keys()
1163 1163 ff.sort()
1164 1164 for f in ff:
1165 1165 if f == "/dev/null": continue
1166 1166 files += 1
1167 1167 fl = self.file(f)
1168 1168 nodes = { nullid: 1 }
1169 1169 seen = {}
1170 1170 for i in range(fl.count()):
1171 1171 revisions += 1
1172 1172 n = fl.node(i)
1173 1173
1174 1174 if n in seen:
1175 1175 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1176 1176 errors += 1
1177 1177
1178 1178 if n not in filenodes[f]:
1179 1179 self.ui.warn("%s: %d:%s not in manifests\n"
1180 1180 % (f, i, short(n)))
1181 1181 print len(filenodes[f].keys()), fl.count(), f
1182 1182 errors += 1
1183 1183 else:
1184 1184 del filenodes[f][n]
1185 1185
1186 1186 flr = fl.linkrev(n)
1187 1187 if flr not in filelinkrevs[f]:
1188 1188 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1189 1189 % (f, short(n), fl.linkrev(n)))
1190 1190 errors += 1
1191 1191 else:
1192 1192 filelinkrevs[f].remove(flr)
1193 1193
1194 1194 # verify contents
1195 1195 try:
1196 1196 t = fl.read(n)
1197 1197 except Exception, inst:
1198 1198 self.ui.warn("unpacking file %s %s: %s\n"
1199 1199 % (f, short(n), inst))
1200 1200 errors += 1
1201 1201
1202 1202 # verify parents
1203 1203 (p1, p2) = fl.parents(n)
1204 1204 if p1 not in nodes:
1205 1205 self.ui.warn("file %s:%s unknown parent 1 %s" %
1206 1206 (f, short(n), short(p1)))
1207 1207 errors += 1
1208 1208 if p2 not in nodes:
1209 1209 self.ui.warn("file %s:%s unknown parent 2 %s" %
1210 1210 (f, short(n), short(p1)))
1211 1211 errors += 1
1212 1212 nodes[n] = 1
1213 1213
1214 1214 # cross-check
1215 1215 for node in filenodes[f]:
1216 1216 self.ui.warn("node %s in manifests not in %s\n"
1217 1217 % (hex(n), f))
1218 1218 errors += 1
1219 1219
1220 1220 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1221 1221 (files, changesets, revisions))
1222 1222
1223 1223 if errors:
1224 1224 self.ui.warn("%d integrity errors encountered!\n" % errors)
1225 1225 return 1
1226 1226
1227 1227 class remoterepository:
1228 1228 def __init__(self, ui, path):
1229 1229 self.url = path
1230 1230 self.ui = ui
1231 1231
1232 1232 def do_cmd(self, cmd, **args):
1233 1233 self.ui.debug("sending %s command\n" % cmd)
1234 1234 q = {"cmd": cmd}
1235 1235 q.update(args)
1236 1236 qs = urllib.urlencode(q)
1237 1237 cu = "%s?%s" % (self.url, qs)
1238 1238 return urllib.urlopen(cu)
1239 1239
1240 1240 def heads(self):
1241 1241 d = self.do_cmd("heads").read()
1242 1242 try:
1243 1243 return map(bin, d[:-1].split(" "))
1244 1244 except:
1245 1245 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1246 1246 raise
1247 1247
1248 1248 def branches(self, nodes):
1249 1249 n = " ".join(map(hex, nodes))
1250 1250 d = self.do_cmd("branches", nodes=n).read()
1251 1251 try:
1252 1252 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1253 1253 return br
1254 1254 except:
1255 1255 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1256 1256 raise
1257 1257
1258 1258 def between(self, pairs):
1259 1259 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1260 1260 d = self.do_cmd("between", pairs=n).read()
1261 1261 try:
1262 1262 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1263 1263 return p
1264 1264 except:
1265 1265 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1266 1266 raise
1267 1267
1268 1268 def changegroup(self, nodes):
1269 1269 n = " ".join(map(hex, nodes))
1270 1270 zd = zlib.decompressobj()
1271 1271 f = self.do_cmd("changegroup", roots=n)
1272 1272 bytes = 0
1273 1273 while 1:
1274 1274 d = f.read(4096)
1275 1275 bytes += len(d)
1276 1276 if not d:
1277 1277 yield zd.flush()
1278 1278 break
1279 1279 yield zd.decompress(d)
1280 1280 self.ui.note("%d bytes of data transfered\n" % bytes)
1281 1281
1282 1282 def repository(ui, path=None, create=0):
1283 1283 if path and path[:7] == "http://":
1284 1284 return remoterepository(ui, path)
1285 1285 if path and path[:5] == "hg://":
1286 1286 return remoterepository(ui, path.replace("hg://", "http://"))
1287 1287 if path and path[:11] == "old-http://":
1288 1288 return localrepository(ui, path.replace("old-http://", "http://"))
1289 1289 else:
1290 1290 return localrepository(ui, path, create)
1291 1291
1292 1292 class httprangereader:
1293 1293 def __init__(self, url):
1294 1294 self.url = url
1295 1295 self.pos = 0
1296 1296 def seek(self, pos):
1297 1297 self.pos = pos
1298 1298 def read(self, bytes=None):
1299 1299 opener = urllib2.build_opener(byterange.HTTPRangeHandler())
1300 1300 urllib2.install_opener(opener)
1301 1301 req = urllib2.Request(self.url)
1302 1302 end = ''
1303 1303 if bytes: end = self.pos + bytes
1304 1304 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
1305 1305 f = urllib2.urlopen(req)
1306 1306 return f.read()
@@ -1,95 +1,99 b''
1 1 # mdiff.py - diff and patch routines 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 difflib, struct
9 9 from mercurial.mpatch import *
10 10
11 11 def unidiff(a, ad, b, bd, fn):
12 12 if not a and not b: return ""
13 13
14 14 if a == None:
15 15 b = b.splitlines(1)
16 16 l1 = "--- %s\t%s\n" % ("/dev/null", ad)
17 17 l2 = "+++ %s\t%s\n" % ("b/" + fn, bd)
18 18 l3 = "@@ -0,0 +1,%d @@\n" % len(b)
19 19 l = [l1, l2, l3] + ["+" + e for e in b]
20 20 elif b == None:
21 21 a = a.splitlines(1)
22 22 l1 = "--- %s\t%s\n" % ("a/" + fn, ad)
23 23 l2 = "+++ %s\t%s\n" % ("/dev/null", bd)
24 24 l3 = "@@ -1,%d +0,0 @@\n" % len(a)
25 25 l = [l1, l2, l3] + ["-" + e for e in a]
26 26 else:
27 27 a = a.splitlines(1)
28 28 b = b.splitlines(1)
29 29 l = list(difflib.unified_diff(a, b, "a/" + fn, "b/" + fn))
30 30 if not l: return ""
31 31 # difflib uses a space, rather than a tab
32 32 l[0] = l[0][:-2] + "\t" + ad + "\n"
33 33 l[1] = l[1][:-2] + "\t" + bd + "\n"
34 34
35 35 for ln in xrange(len(l)):
36 36 if l[ln][-1] != '\n':
37 37 l[ln] += "\n\ No newline at end of file\n"
38 38
39 39 return "".join(l)
40 40
41 41 def textdiff(a, b):
42 42 return diff(a.splitlines(1), b.splitlines(1))
43 43
44 44 def sortdiff(a, b):
45 45 la = lb = 0
46 46 lena = len(a)
47 47 lenb = len(b)
48 48 while 1:
49 49 am, bm, = la, lb
50 50 while lb < lenb and la < len and a[la] == b[lb] :
51 51 la += 1
52 52 lb += 1
53 53 if la>am: yield (am, bm, la-am)
54 54 while lb < lenb and b[lb] < a[la]: lb += 1
55 55 if lb>=lenb: break
56 56 while la < lena and b[lb] > a[la]: la += 1
57 57 if la>=lena: break
58 58 yield (lena, lenb, 0)
59 59
60 60 def diff(a, b, sorted=0):
61 61 if not a:
62 62 s = "".join(b)
63 63 return s and (struct.pack(">lll", 0, 0, len(s)) + s)
64 64
65 65 bin = []
66 66 p = [0]
67 67 for i in a: p.append(p[-1] + len(i))
68 68
69 69 if sorted:
70 d = sortdiff(a, b)
70 try:
71 d = sortdiff(a, b)
72 except:
73 print a, b
74 raise
71 75 else:
72 76 d = difflib.SequenceMatcher(None, a, b).get_matching_blocks()
73 77 la = 0
74 78 lb = 0
75 79 for am, bm, size in d:
76 80 s = "".join(b[lb:bm])
77 81 if am > la or s:
78 82 bin.append(struct.pack(">lll", p[la], p[am], len(s)) + s)
79 83 la = am + size
80 84 lb = bm + size
81 85
82 86 return "".join(bin)
83 87
84 88 def patchtext(bin):
85 89 pos = 0
86 90 t = []
87 91 while pos < len(bin):
88 92 p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
89 93 pos += 12
90 94 t.append(bin[pos:pos + l])
91 95 pos += l
92 96 return "".join(t)
93 97
94 98 def patch(a, bin):
95 99 return patches(a, [bin])
General Comments 0
You need to be logged in to leave comments. Login now