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