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