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