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