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