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