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