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