##// END OF EJS Templates
merge: update permissions even if file contents didn't change...
mpm@selenic.com -
r277:79279550 default
parent child Browse files
Show More
@@ -1,1236 +1,1253 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 os.chmod(f, s & 0666 | (mode * 0111))
22 22
23 23 class filelog(revlog):
24 24 def __init__(self, opener, path):
25 25 revlog.__init__(self, opener,
26 26 os.path.join("data", path + ".i"),
27 27 os.path.join("data", path + ".d"))
28 28
29 29 def read(self, node):
30 30 return self.revision(node)
31 31 def add(self, text, transaction, link, p1=None, p2=None):
32 32 return self.addrevision(text, transaction, link, p1, p2)
33 33
34 34 def annotate(self, node):
35 35
36 36 def decorate(text, rev):
37 37 return [(rev, l) for l in text.splitlines(1)]
38 38
39 39 def strip(annotation):
40 40 return [e[1] for e in annotation]
41 41
42 42 def pair(parent, child):
43 43 new = []
44 44 sm = difflib.SequenceMatcher(None, strip(parent), strip(child))
45 45 for o, m, n, s, t in sm.get_opcodes():
46 46 if o == 'equal':
47 47 new += parent[m:n]
48 48 else:
49 49 new += child[s:t]
50 50 return new
51 51
52 52 # find all ancestors
53 53 needed = {node:1}
54 54 visit = [node]
55 55 while visit:
56 56 n = visit.pop(0)
57 57 for p in self.parents(n):
58 58 if p not in needed:
59 59 needed[p] = 1
60 60 visit.append(p)
61 61 else:
62 62 # count how many times we'll use this
63 63 needed[p] += 1
64 64
65 65 # sort by revision which is a topological order
66 66 visit = needed.keys()
67 67 visit = [ (self.rev(n), n) for n in visit ]
68 68 visit.sort()
69 69 visit = [ p[1] for p in visit ]
70 70 hist = {}
71 71
72 72 for n in visit:
73 73 curr = decorate(self.read(n), self.linkrev(n))
74 74 for p in self.parents(n):
75 75 if p != nullid:
76 76 curr = pair(hist[p], curr)
77 77 # trim the history of unneeded revs
78 78 needed[p] -= 1
79 79 if not needed[p]:
80 80 del hist[p]
81 81 hist[n] = curr
82 82
83 83 return hist[n]
84 84
85 85 class manifest(revlog):
86 86 def __init__(self, opener):
87 87 self.mapcache = None
88 88 self.listcache = None
89 89 self.addlist = None
90 90 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
91 91
92 92 def read(self, node):
93 93 if self.mapcache and self.mapcache[0] == node:
94 94 return self.mapcache[1].copy()
95 95 text = self.revision(node)
96 96 map = {}
97 97 flag = {}
98 98 self.listcache = (text, text.splitlines(1))
99 99 for l in self.listcache[1]:
100 100 (f, n) = l.split('\0')
101 101 map[f] = bin(n[:40])
102 102 flag[f] = (n[40:-1] == "x")
103 103 self.mapcache = (node, map, flag)
104 104 return map
105 105
106 106 def readflags(self, node):
107 107 if self.mapcache or self.mapcache[0] != node:
108 108 self.read(node)
109 109 return self.mapcache[2]
110 110
111 111 def diff(self, a, b):
112 112 # this is sneaky, as we're not actually using a and b
113 113 if self.listcache and self.addlist and self.listcache[0] == a:
114 114 d = mdiff.diff(self.listcache[1], self.addlist, 1)
115 115 if mdiff.patch(a, d) != b:
116 116 sys.stderr.write("*** sortdiff failed, falling back ***\n")
117 117 return mdiff.textdiff(a, b)
118 118 return d
119 119 else:
120 120 return mdiff.textdiff(a, b)
121 121
122 122 def add(self, map, flags, transaction, link, p1=None, p2=None):
123 123 files = map.keys()
124 124 files.sort()
125 125
126 126 self.addlist = ["%s\000%s%s\n" %
127 127 (f, hex(map[f]), flags[f] and "x" or '')
128 128 for f in files]
129 129 text = "".join(self.addlist)
130 130
131 131 n = self.addrevision(text, transaction, link, p1, p2)
132 132 self.mapcache = (n, map)
133 133 self.listcache = (text, self.addlist)
134 134 self.addlist = None
135 135
136 136 return n
137 137
138 138 class changelog(revlog):
139 139 def __init__(self, opener):
140 140 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
141 141
142 142 def extract(self, text):
143 143 if not text:
144 144 return (nullid, "", "0", [], "")
145 145 last = text.index("\n\n")
146 146 desc = text[last + 2:]
147 147 l = text[:last].splitlines()
148 148 manifest = bin(l[0])
149 149 user = l[1]
150 150 date = l[2]
151 151 files = l[3:]
152 152 return (manifest, user, date, files, desc)
153 153
154 154 def read(self, node):
155 155 return self.extract(self.revision(node))
156 156
157 157 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
158 158 user=None, date=None):
159 159 user = (user or
160 160 os.environ.get("HGUSER") or
161 161 os.environ.get("EMAIL") or
162 162 os.environ.get("LOGNAME", "unknown") + '@' + socket.getfqdn())
163 163 date = date or "%d %d" % (time.time(), time.timezone)
164 164 list.sort()
165 165 l = [hex(manifest), user, date] + list + ["", desc]
166 166 text = "\n".join(l)
167 167 return self.addrevision(text, transaction, self.count(), p1, p2)
168 168
169 169 class dirstate:
170 170 def __init__(self, opener, ui, root):
171 171 self.opener = opener
172 172 self.root = root
173 173 self.dirty = 0
174 174 self.ui = ui
175 175 self.map = None
176 176 self.pl = None
177 177
178 178 def __del__(self):
179 179 if self.dirty:
180 180 self.write()
181 181
182 182 def __getitem__(self, key):
183 183 try:
184 184 return self.map[key]
185 185 except TypeError:
186 186 self.read()
187 187 return self[key]
188 188
189 189 def __contains__(self, key):
190 190 if not self.map: self.read()
191 191 return key in self.map
192 192
193 193 def parents(self):
194 194 if not self.pl:
195 195 self.read()
196 196 return self.pl
197 197
198 198 def setparents(self, p1, p2 = nullid):
199 199 self.dirty = 1
200 200 self.pl = p1, p2
201 201
202 202 def state(self, key):
203 203 try:
204 204 return self[key][0]
205 205 except KeyError:
206 206 return "?"
207 207
208 208 def read(self):
209 209 if self.map is not None: return self.map
210 210
211 211 self.map = {}
212 212 self.pl = [nullid, nullid]
213 213 try:
214 214 st = self.opener("dirstate").read()
215 215 except: return
216 216
217 217 self.pl = [st[:20], st[20: 40]]
218 218
219 219 pos = 40
220 220 while pos < len(st):
221 221 e = struct.unpack(">cllll", st[pos:pos+17])
222 222 l = e[4]
223 223 pos += 17
224 224 f = st[pos:pos + l]
225 225 self.map[f] = e[:4]
226 226 pos += l
227 227
228 228 def update(self, files, state):
229 229 ''' current states:
230 230 n normal
231 231 m needs merging
232 232 r marked for removal
233 233 a marked for addition'''
234 234
235 235 if not files: return
236 236 self.read()
237 237 self.dirty = 1
238 238 for f in files:
239 239 if state == "r":
240 240 self.map[f] = ('r', 0, 0, 0)
241 241 else:
242 242 s = os.stat(os.path.join(self.root, f))
243 243 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
244 244
245 245 def forget(self, files):
246 246 if not files: return
247 247 self.read()
248 248 self.dirty = 1
249 249 for f in files:
250 250 try:
251 251 del self.map[f]
252 252 except KeyError:
253 253 self.ui.warn("not in dirstate: %s!\n" % f)
254 254 pass
255 255
256 256 def clear(self):
257 257 self.map = {}
258 258 self.dirty = 1
259 259
260 260 def write(self):
261 261 st = self.opener("dirstate", "w")
262 262 st.write("".join(self.pl))
263 263 for f, e in self.map.items():
264 264 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
265 265 st.write(e + f)
266 266 self.dirty = 0
267 267
268 268 def copy(self):
269 269 self.read()
270 270 return self.map.copy()
271 271
272 272 # used to avoid circular references so destructors work
273 273 def opener(base):
274 274 p = base
275 275 def o(path, mode="r"):
276 276 if p[:7] == "http://":
277 277 f = os.path.join(p, urllib.quote(path))
278 278 return httprangereader(f)
279 279
280 280 f = os.path.join(p, path)
281 281
282 282 if mode != "r":
283 283 try:
284 284 s = os.stat(f)
285 285 except OSError:
286 286 d = os.path.dirname(f)
287 287 if not os.path.isdir(d):
288 288 os.makedirs(d)
289 289 else:
290 290 if s.st_nlink > 1:
291 291 file(f + ".tmp", "w").write(file(f).read())
292 292 os.rename(f+".tmp", f)
293 293
294 294 return file(f, mode)
295 295
296 296 return o
297 297
298 298 class localrepository:
299 299 def __init__(self, ui, path=None, create=0):
300 300 self.remote = 0
301 301 if path and path[:7] == "http://":
302 302 self.remote = 1
303 303 self.path = path
304 304 else:
305 305 if not path:
306 306 p = os.getcwd()
307 307 while not os.path.isdir(os.path.join(p, ".hg")):
308 308 p = os.path.dirname(p)
309 309 if p == "/": raise "No repo found"
310 310 path = p
311 311 self.path = os.path.join(path, ".hg")
312 312
313 313 self.root = path
314 314 self.ui = ui
315 315
316 316 if create:
317 317 os.mkdir(self.path)
318 318 os.mkdir(self.join("data"))
319 319
320 320 self.opener = opener(self.path)
321 321 self.manifest = manifest(self.opener)
322 322 self.changelog = changelog(self.opener)
323 323 self.ignorelist = None
324 324 self.tags = None
325 325
326 326 if not self.remote:
327 327 self.dirstate = dirstate(self.opener, ui, self.root)
328 328
329 329 def ignore(self, f):
330 330 if self.ignorelist is None:
331 331 self.ignorelist = []
332 332 try:
333 333 l = open(os.path.join(self.root, ".hgignore"))
334 334 for pat in l:
335 335 if pat != "\n":
336 336 self.ignorelist.append(re.compile(pat[:-1]))
337 337 except IOError: pass
338 338 for pat in self.ignorelist:
339 339 if pat.search(f): return True
340 340 return False
341 341
342 342 def lookup(self, key):
343 343 if self.tags is None:
344 344 self.tags = {}
345 345 try:
346 346 # read each head of the tags file, ending with the tip
347 347 # and add each tag found to the map, with "newer" ones
348 348 # taking precedence
349 349 fl = self.file(".hgtags")
350 350 h = fl.heads()
351 351 h.reverse()
352 352 for r in h:
353 353 for l in fl.revision(r).splitlines():
354 354 if l:
355 355 n, k = l.split(" ")
356 356 self.tags[k] = bin(n)
357 357 except KeyError: pass
358 358 try:
359 359 return self.tags[key]
360 360 except KeyError:
361 361 return self.changelog.lookup(key)
362 362
363 363 def join(self, f):
364 364 return os.path.join(self.path, f)
365 365
366 366 def wjoin(self, f):
367 367 return os.path.join(self.root, f)
368 368
369 369 def file(self, f):
370 370 if f[0] == '/': f = f[1:]
371 371 return filelog(self.opener, f)
372 372
373 373 def transaction(self):
374 374 # save dirstate for undo
375 375 try:
376 376 ds = self.opener("dirstate").read()
377 377 except IOError:
378 378 ds = ""
379 379 self.opener("undo.dirstate", "w").write(ds)
380 380
381 381 return transaction.transaction(self.opener, self.join("journal"),
382 382 self.join("undo"))
383 383
384 384 def recover(self):
385 385 lock = self.lock()
386 386 if os.path.exists(self.join("recover")):
387 387 self.ui.status("attempting to rollback interrupted transaction\n")
388 388 return transaction.rollback(self.opener, self.join("recover"))
389 389 else:
390 390 self.ui.warn("no interrupted transaction available\n")
391 391
392 392 def undo(self):
393 393 lock = self.lock()
394 394 if os.path.exists(self.join("undo")):
395 395 self.ui.status("attempting to rollback last transaction\n")
396 396 transaction.rollback(self.opener, self.join("undo"))
397 397 self.dirstate = None
398 398 os.rename(self.join("undo.dirstate"), self.join("dirstate"))
399 399 self.dirstate = dirstate(self.opener, self.ui, self.root)
400 400 else:
401 401 self.ui.warn("no undo information available\n")
402 402
403 403 def lock(self, wait = 1):
404 404 try:
405 405 return lock.lock(self.join("lock"), 0)
406 406 except lock.LockHeld, inst:
407 407 if wait:
408 408 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
409 409 return lock.lock(self.join("lock"), wait)
410 410 raise inst
411 411
412 412 def rawcommit(self, files, text, user, date, p1=None, p2=None):
413 413 p1 = p1 or self.dirstate.parents()[0] or nullid
414 414 p2 = p2 or self.dirstate.parents()[1] or nullid
415 415 pchange = self.changelog.read(p1)
416 416 pmmap = self.manifest.read(pchange[0])
417 417 tr = self.transaction()
418 418 mmap = {}
419 419 linkrev = self.changelog.count()
420 420 for f in files:
421 421 try:
422 422 t = file(f).read()
423 423 except IOError:
424 424 self.ui.warn("Read file %s error, skipped\n" % f)
425 425 continue
426 426 r = self.file(f)
427 427 # FIXME - need to find both parents properly
428 428 prev = pmmap.get(f, nullid)
429 429 mmap[f] = r.add(t, tr, linkrev, prev)
430 430
431 431 mnode = self.manifest.add(mmap, tr, linkrev, pchange[0])
432 432 n = self.changelog.add(mnode, files, text, tr, p1, p2, user ,date, )
433 433 tr.close()
434 434 self.dirstate.setparents(p1, p2)
435 435 self.dirstate.clear()
436 436 self.dirstate.update(mmap.keys(), "n")
437 437
438 438 def commit(self, files = None, text = ""):
439 439 commit = []
440 440 remove = []
441 441 if files:
442 442 for f in files:
443 443 s = self.dirstate.state(f)
444 444 if s in 'nmai':
445 445 commit.append(f)
446 446 elif s == 'r':
447 447 remove.append(f)
448 448 else:
449 449 self.ui.warn("%s not tracked!\n" % f)
450 450 else:
451 451 (c, a, d, u) = self.diffdir(self.root)
452 452 commit = c + a
453 453 remove = d
454 454
455 455 if not commit and not remove:
456 456 self.ui.status("nothing changed\n")
457 457 return
458 458
459 459 p1, p2 = self.dirstate.parents()
460 460 c1 = self.changelog.read(p1)
461 461 c2 = self.changelog.read(p2)
462 462 m1 = self.manifest.read(c1[0])
463 463 mf1 = self.manifest.readflags(c1[0])
464 464 m2 = self.manifest.read(c2[0])
465 465 lock = self.lock()
466 466 tr = self.transaction()
467 467
468 468 # check in files
469 469 new = {}
470 470 linkrev = self.changelog.count()
471 471 commit.sort()
472 472 for f in commit:
473 473 self.ui.note(f + "\n")
474 474 try:
475 475 fp = self.wjoin(f)
476 476 mf1[f] = is_exec(fp)
477 477 t = file(fp).read()
478 478 except IOError:
479 479 self.warn("trouble committing %s!\n" % f)
480 480 raise
481 481
482 482 r = self.file(f)
483 483 fp1 = m1.get(f, nullid)
484 484 fp2 = m2.get(f, nullid)
485 485 new[f] = r.add(t, tr, linkrev, fp1, fp2)
486 486
487 487 # update manifest
488 488 m1.update(new)
489 489 for f in remove: del m1[f]
490 490 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0])
491 491
492 492 # add changeset
493 493 new = new.keys()
494 494 new.sort()
495 495
496 496 edittext = text + "\n" + "HG: manifest hash %s\n" % hex(mn)
497 497 edittext += "".join(["HG: changed %s\n" % f for f in new])
498 498 edittext += "".join(["HG: removed %s\n" % f for f in remove])
499 499 edittext = self.ui.edit(edittext)
500 500
501 501 n = self.changelog.add(mn, new, edittext, tr, p1, p2)
502 502 tr.close()
503 503
504 504 self.dirstate.setparents(n)
505 505 self.dirstate.update(new, "n")
506 506 self.dirstate.forget(remove)
507 507
508 508 def diffdir(self, path, changeset = None):
509 509 changed = []
510 510 added = []
511 511 unknown = []
512 512 mf = {}
513 513
514 514 if changeset:
515 515 change = self.changelog.read(changeset)
516 516 mf = self.manifest.read(change[0])
517 517 dc = dict.fromkeys(mf)
518 518 else:
519 519 changeset = self.dirstate.parents()[0]
520 520 change = self.changelog.read(changeset)
521 521 mf = self.manifest.read(change[0])
522 522 dc = self.dirstate.copy()
523 523
524 524 def fcmp(fn):
525 525 t1 = file(self.wjoin(fn)).read()
526 526 t2 = self.file(fn).revision(mf[fn])
527 527 return cmp(t1, t2)
528 528
529 529 for dir, subdirs, files in os.walk(self.root):
530 530 d = dir[len(self.root)+1:]
531 531 if ".hg" in subdirs: subdirs.remove(".hg")
532 532
533 533 for f in files:
534 534 fn = os.path.join(d, f)
535 535 try: s = os.stat(os.path.join(self.root, fn))
536 536 except: continue
537 537 if fn in dc:
538 538 c = dc[fn]
539 539 del dc[fn]
540 540 if not c:
541 541 if fcmp(fn):
542 542 changed.append(fn)
543 543 elif c[0] == 'm':
544 544 changed.append(fn)
545 545 elif c[0] == 'a':
546 546 added.append(fn)
547 547 elif c[0] == 'r':
548 548 unknown.append(fn)
549 549 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
550 550 changed.append(fn)
551 551 elif c[1] != s.st_mode or c[3] != s.st_mtime:
552 552 if fcmp(fn):
553 553 changed.append(fn)
554 554 else:
555 555 if self.ignore(fn): continue
556 556 unknown.append(fn)
557 557
558 558 deleted = dc.keys()
559 559 deleted.sort()
560 560
561 561 return (changed, added, deleted, unknown)
562 562
563 563 def diffrevs(self, node1, node2):
564 564 changed, added = [], []
565 565
566 566 change = self.changelog.read(node1)
567 567 mf1 = self.manifest.read(change[0])
568 568 change = self.changelog.read(node2)
569 569 mf2 = self.manifest.read(change[0])
570 570
571 571 for fn in mf2:
572 572 if mf1.has_key(fn):
573 573 if mf1[fn] != mf2[fn]:
574 574 changed.append(fn)
575 575 del mf1[fn]
576 576 else:
577 577 added.append(fn)
578 578
579 579 deleted = mf1.keys()
580 580 deleted.sort()
581 581
582 582 return (changed, added, deleted)
583 583
584 584 def add(self, list):
585 585 for f in list:
586 586 p = self.wjoin(f)
587 587 if not os.path.isfile(p):
588 588 self.ui.warn("%s does not exist!\n" % f)
589 589 elif self.dirstate.state(f) == 'n':
590 590 self.ui.warn("%s already tracked!\n" % f)
591 591 else:
592 592 self.dirstate.update([f], "a")
593 593
594 594 def forget(self, list):
595 595 for f in list:
596 596 if self.dirstate.state(f) not in 'ai':
597 597 self.ui.warn("%s not added!\n" % f)
598 598 else:
599 599 self.dirstate.forget([f])
600 600
601 601 def remove(self, list):
602 602 for f in list:
603 603 p = self.wjoin(f)
604 604 if os.path.isfile(p):
605 605 self.ui.warn("%s still exists!\n" % f)
606 606 elif f not in self.dirstate:
607 607 self.ui.warn("%s not tracked!\n" % f)
608 608 else:
609 609 self.dirstate.update([f], "r")
610 610
611 611 def heads(self):
612 612 return self.changelog.heads()
613 613
614 614 def branches(self, nodes):
615 615 if not nodes: nodes = [self.changelog.tip()]
616 616 b = []
617 617 for n in nodes:
618 618 t = n
619 619 while n:
620 620 p = self.changelog.parents(n)
621 621 if p[1] != nullid or p[0] == nullid:
622 622 b.append((t, n, p[0], p[1]))
623 623 break
624 624 n = p[0]
625 625 return b
626 626
627 627 def between(self, pairs):
628 628 r = []
629 629
630 630 for top, bottom in pairs:
631 631 n, l, i = top, [], 0
632 632 f = 1
633 633
634 634 while n != bottom:
635 635 p = self.changelog.parents(n)[0]
636 636 if i == f:
637 637 l.append(n)
638 638 f = f * 2
639 639 n = p
640 640 i += 1
641 641
642 642 r.append(l)
643 643
644 644 return r
645 645
646 646 def newer(self, nodes):
647 647 m = {}
648 648 nl = []
649 649 pm = {}
650 650 cl = self.changelog
651 651 t = l = cl.count()
652 652
653 653 # find the lowest numbered node
654 654 for n in nodes:
655 655 l = min(l, cl.rev(n))
656 656 m[n] = 1
657 657
658 658 for i in xrange(l, t):
659 659 n = cl.node(i)
660 660 if n in m: # explicitly listed
661 661 pm[n] = 1
662 662 nl.append(n)
663 663 continue
664 664 for p in cl.parents(n):
665 665 if p in pm: # parent listed
666 666 pm[n] = 1
667 667 nl.append(n)
668 668 break
669 669
670 670 return nl
671 671
672 672 def getchangegroup(self, remote):
673 673 m = self.changelog.nodemap
674 674 search = []
675 675 fetch = []
676 676 seen = {}
677 677 seenbranch = {}
678 678
679 679 # if we have an empty repo, fetch everything
680 680 if self.changelog.tip() == nullid:
681 681 self.ui.status("requesting all changes\n")
682 682 return remote.changegroup([nullid])
683 683
684 684 # otherwise, assume we're closer to the tip than the root
685 685 self.ui.status("searching for changes\n")
686 686 heads = remote.heads()
687 687 unknown = []
688 688 for h in heads:
689 689 if h not in m:
690 690 unknown.append(h)
691 691
692 692 if not unknown:
693 693 self.ui.status("nothing to do!\n")
694 694 return None
695 695
696 696 unknown = remote.branches(unknown)
697 697 while unknown:
698 698 n = unknown.pop(0)
699 699 seen[n[0]] = 1
700 700
701 701 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
702 702 if n == nullid: break
703 703 if n in seenbranch:
704 704 self.ui.debug("branch already found\n")
705 705 continue
706 706 if n[1] and n[1] in m: # do we know the base?
707 707 self.ui.debug("found incomplete branch %s:%s\n"
708 708 % (short(n[0]), short(n[1])))
709 709 search.append(n) # schedule branch range for scanning
710 710 seenbranch[n] = 1
711 711 else:
712 712 if n[2] in m and n[3] in m:
713 713 if n[1] not in fetch:
714 714 self.ui.debug("found new changeset %s\n" %
715 715 short(n[1]))
716 716 fetch.append(n[1]) # earliest unknown
717 717 continue
718 718
719 719 r = []
720 720 for a in n[2:4]:
721 721 if a not in seen: r.append(a)
722 722
723 723 if r:
724 724 self.ui.debug("requesting %s\n" %
725 725 " ".join(map(short, r)))
726 726 for b in remote.branches(r):
727 727 self.ui.debug("received %s:%s\n" %
728 728 (short(b[0]), short(b[1])))
729 729 if b[0] not in m and b[0] not in seen:
730 730 unknown.append(b)
731 731
732 732 while search:
733 733 n = search.pop(0)
734 734 l = remote.between([(n[0], n[1])])[0]
735 735 p = n[0]
736 736 f = 1
737 737 for i in l + [n[1]]:
738 738 if i in m:
739 739 if f <= 2:
740 740 self.ui.debug("found new branch changeset %s\n" %
741 741 short(p))
742 742 fetch.append(p)
743 743 else:
744 744 self.ui.debug("narrowed branch search to %s:%s\n"
745 745 % (short(p), short(i)))
746 746 search.append((p, i))
747 747 break
748 748 p, f = i, f * 2
749 749
750 750 for f in fetch:
751 751 if f in m:
752 752 raise "already have", short(f[:4])
753 753
754 754 self.ui.note("adding new changesets starting at " +
755 755 " ".join([short(f) for f in fetch]) + "\n")
756 756
757 757 return remote.changegroup(fetch)
758 758
759 759 def changegroup(self, basenodes):
760 760 nodes = self.newer(basenodes)
761 761
762 762 # construct the link map
763 763 linkmap = {}
764 764 for n in nodes:
765 765 linkmap[self.changelog.rev(n)] = n
766 766
767 767 # construct a list of all changed files
768 768 changed = {}
769 769 for n in nodes:
770 770 c = self.changelog.read(n)
771 771 for f in c[3]:
772 772 changed[f] = 1
773 773 changed = changed.keys()
774 774 changed.sort()
775 775
776 776 # the changegroup is changesets + manifests + all file revs
777 777 revs = [ self.changelog.rev(n) for n in nodes ]
778 778
779 779 for y in self.changelog.group(linkmap): yield y
780 780 for y in self.manifest.group(linkmap): yield y
781 781 for f in changed:
782 782 yield struct.pack(">l", len(f) + 4) + f
783 783 g = self.file(f).group(linkmap)
784 784 for y in g:
785 785 yield y
786 786
787 787 def addchangegroup(self, generator):
788 788
789 789 class genread:
790 790 def __init__(self, generator):
791 791 self.g = generator
792 792 self.buf = ""
793 793 def read(self, l):
794 794 while l > len(self.buf):
795 795 try:
796 796 self.buf += self.g.next()
797 797 except StopIteration:
798 798 break
799 799 d, self.buf = self.buf[:l], self.buf[l:]
800 800 return d
801 801
802 802 def getchunk():
803 803 d = source.read(4)
804 804 if not d: return ""
805 805 l = struct.unpack(">l", d)[0]
806 806 if l <= 4: return ""
807 807 return source.read(l - 4)
808 808
809 809 def getgroup():
810 810 while 1:
811 811 c = getchunk()
812 812 if not c: break
813 813 yield c
814 814
815 815 def csmap(x):
816 816 self.ui.debug("add changeset %s\n" % short(x))
817 817 return self.changelog.count()
818 818
819 819 def revmap(x):
820 820 return self.changelog.rev(x)
821 821
822 822 if not generator: return
823 823 changesets = files = revisions = 0
824 824
825 825 source = genread(generator)
826 826 lock = self.lock()
827 827 tr = self.transaction()
828 828
829 829 # pull off the changeset group
830 830 self.ui.status("adding changesets\n")
831 831 co = self.changelog.tip()
832 832 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
833 833 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
834 834
835 835 # pull off the manifest group
836 836 self.ui.status("adding manifests\n")
837 837 mm = self.manifest.tip()
838 838 mo = self.manifest.addgroup(getgroup(), revmap, tr)
839 839
840 840 # process the files
841 841 self.ui.status("adding file revisions\n")
842 842 while 1:
843 843 f = getchunk()
844 844 if not f: break
845 845 self.ui.debug("adding %s revisions\n" % f)
846 846 fl = self.file(f)
847 847 o = fl.tip()
848 848 n = fl.addgroup(getgroup(), revmap, tr)
849 849 revisions += fl.rev(n) - fl.rev(o)
850 850 files += 1
851 851
852 852 self.ui.status(("modified %d files, added %d changesets" +
853 853 " and %d new revisions\n")
854 854 % (files, changesets, revisions))
855 855
856 856 tr.close()
857 857 return
858 858
859 859 def update(self, node, allow=False, force=False):
860 860 pl = self.dirstate.parents()
861 861 if not force and pl[1] != nullid:
862 862 self.ui.warn("aborting: outstanding uncommitted merges\n")
863 863 return
864 864
865 865 p1, p2 = pl[0], node
866 866 m1n = self.changelog.read(p1)[0]
867 867 m2n = self.changelog.read(p2)[0]
868 868 man = self.manifest.ancestor(m1n, m2n)
869 869 m1 = self.manifest.read(m1n)
870 870 mf1 = self.manifest.readflags(m1n)
871 871 m2 = self.manifest.read(m2n)
872 872 mf2 = self.manifest.readflags(m2n)
873 873 ma = self.manifest.read(man)
874 874 mfa = self.manifest.readflags(m2n)
875 875
876 876 (c, a, d, u) = self.diffdir(self.root)
877 877
878 878 # resolve the manifest to determine which files
879 879 # we care about merging
880 880 self.ui.note("resolving manifests\n")
881 881 self.ui.debug(" ancestor %s local %s remote %s\n" %
882 882 (short(man), short(m1n), short(m2n)))
883 883
884 884 merge = {}
885 885 get = {}
886 886 remove = []
887 887
888 888 # construct a working dir manifest
889 889 mw = m1.copy()
890 890 mfw = mf1.copy()
891 891 for f in a + c + u:
892 892 mw[f] = ""
893 893 mfw[f] = is_exec(self.wjoin(f))
894 894 for f in d:
895 895 if f in mw: del mw[f]
896 896
897 897 for f, n in mw.iteritems():
898 898 if f in m2:
899 s = 0
900
899 901 if n != m2[f]:
900 902 a = ma.get(f, nullid)
901 903 if n != a and m2[f] != a:
902 904 self.ui.debug(" %s versions differ, resolve\n" % f)
903 905 merge[f] = (m1.get(f, nullid), m2[f])
904 906 # merge executable bits
905 907 # "if we changed or they changed, change in merge"
906 908 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
907 909 mode = ((a^b) | (a^c)) ^ a
908 910 merge[f] = (m1.get(f, nullid), m2[f], mode)
911 s = 1
909 912 elif m2[f] != a:
910 913 self.ui.debug(" remote %s is newer, get\n" % f)
911 914 get[f] = m2[f]
915 s = 1
916
917 if not s and mfw[f] != mf2[f]:
918 if force:
919 self.ui.debug(" updating permissions for %s\n" % f)
920 set_exec(self.wjoin(f), mf2[f])
921 else:
922 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
923 mode = ((a^b) | (a^c)) ^ a
924 print a, b, c, mode
925 if mode != b:
926 self.ui.debug(" updating permissions for %s\n" % f)
927 set_exec(self.wjoin(f), mode)
928
912 929 del m2[f]
913 930 elif f in ma:
914 931 if not force and n != ma[f]:
915 932 r = self.ui.prompt(
916 933 (" local changed %s which remote deleted\n" % f) +
917 934 "(k)eep or (d)elete?", "[kd]", "k")
918 935 if r == "d":
919 936 remove.append(f)
920 937 else:
921 938 self.ui.debug("other deleted %s\n" % f)
922 939 remove.append(f) # other deleted it
923 940 else:
924 941 if n == m1.get(f, nullid): # same as parent
925 942 self.ui.debug("remote deleted %s\n" % f)
926 943 remove.append(f)
927 944 else:
928 945 self.ui.debug("working dir created %s, keeping\n" % f)
929 946
930 947 for f, n in m2.iteritems():
931 948 if f[0] == "/": continue
932 949 if not force and f in ma and n != ma[f]:
933 950 r = self.ui.prompt(
934 951 ("remote changed %s which local deleted\n" % f) +
935 952 "(k)eep or (d)elete?", "[kd]", "k")
936 953 if r == "d": remove.append(f)
937 954 else:
938 955 self.ui.debug("remote created %s\n" % f)
939 956 get[f] = n
940 957
941 958 del mw, m1, m2, ma
942 959
943 960 if force:
944 961 for f in merge:
945 962 get[f] = merge[f][1]
946 963 merge = {}
947 964
948 965 if not merge:
949 966 # we don't need to do any magic, just jump to the new rev
950 967 mode = 'n'
951 968 p1, p2 = p2, nullid
952 969 else:
953 970 if not allow:
954 971 self.ui.status("the following files conflict:\n")
955 972 for f in merge:
956 973 self.ui.status(" %s\n" % f)
957 974 self.ui.warn("aborting update due to conflicting files!\n")
958 975 self.ui.status("(use update -m to allow a merge)\n")
959 976 return 1
960 977 # we have to remember what files we needed to get/change
961 978 # because any file that's different from either one of its
962 979 # parents must be in the changeset
963 980 mode = 'm'
964 981
965 982 self.dirstate.setparents(p1, p2)
966 983
967 984 # get the files we don't need to change
968 985 files = get.keys()
969 986 files.sort()
970 987 for f in files:
971 988 if f[0] == "/": continue
972 989 self.ui.note("getting %s\n" % f)
973 990 t = self.file(f).read(get[f])
974 991 wp = self.wjoin(f)
975 992 try:
976 993 file(wp, "w").write(t)
977 994 except IOError:
978 995 os.makedirs(os.path.dirname(wp))
979 996 file(wp, "w").write(t)
980 997 set_exec(wp, mf2[f])
981 998 self.dirstate.update([f], mode)
982 999
983 1000 # merge the tricky bits
984 1001 files = merge.keys()
985 1002 files.sort()
986 1003 for f in files:
987 1004 self.ui.status("merging %s\n" % f)
988 1005 m, o, flag = merge[f]
989 1006 self.merge3(f, m, o)
990 1007 set_exec(wp, flag)
991 1008 self.dirstate.update([f], 'm')
992 1009
993 1010 for f in remove:
994 1011 self.ui.note("removing %s\n" % f)
995 1012 os.unlink(f)
996 1013 if mode == 'n':
997 1014 self.dirstate.forget(remove)
998 1015 else:
999 1016 self.dirstate.update(remove, 'r')
1000 1017
1001 1018 def merge3(self, fn, my, other):
1002 1019 """perform a 3-way merge in the working directory"""
1003 1020
1004 1021 def temp(prefix, node):
1005 1022 pre = "%s~%s." % (os.path.basename(fn), prefix)
1006 1023 (fd, name) = tempfile.mkstemp("", pre)
1007 1024 f = os.fdopen(fd, "w")
1008 1025 f.write(fl.revision(node))
1009 1026 f.close()
1010 1027 return name
1011 1028
1012 1029 fl = self.file(fn)
1013 1030 base = fl.ancestor(my, other)
1014 1031 a = self.wjoin(fn)
1015 1032 b = temp("other", other)
1016 1033 c = temp("base", base)
1017 1034
1018 1035 self.ui.note("resolving %s\n" % fn)
1019 1036 self.ui.debug("file %s: other %s ancestor %s\n" %
1020 1037 (fn, short(other), short(base)))
1021 1038
1022 1039 cmd = os.environ.get("HGMERGE", "hgmerge")
1023 1040 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1024 1041 if r:
1025 1042 self.ui.warn("merging %s failed!\n" % fn)
1026 1043
1027 1044 os.unlink(b)
1028 1045 os.unlink(c)
1029 1046
1030 1047 def verify(self):
1031 1048 filelinkrevs = {}
1032 1049 filenodes = {}
1033 1050 manifestchangeset = {}
1034 1051 changesets = revisions = files = 0
1035 1052 errors = 0
1036 1053
1037 1054 self.ui.status("checking changesets\n")
1038 1055 for i in range(self.changelog.count()):
1039 1056 changesets += 1
1040 1057 n = self.changelog.node(i)
1041 1058 for p in self.changelog.parents(n):
1042 1059 if p not in self.changelog.nodemap:
1043 1060 self.ui.warn("changeset %s has unknown parent %s\n" %
1044 1061 (short(n), short(p)))
1045 1062 errors += 1
1046 1063 try:
1047 1064 changes = self.changelog.read(n)
1048 1065 except Exception, inst:
1049 1066 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1050 1067 errors += 1
1051 1068
1052 1069 manifestchangeset[changes[0]] = n
1053 1070 for f in changes[3]:
1054 1071 filelinkrevs.setdefault(f, []).append(i)
1055 1072
1056 1073 self.ui.status("checking manifests\n")
1057 1074 for i in range(self.manifest.count()):
1058 1075 n = self.manifest.node(i)
1059 1076 for p in self.manifest.parents(n):
1060 1077 if p not in self.manifest.nodemap:
1061 1078 self.ui.warn("manifest %s has unknown parent %s\n" %
1062 1079 (short(n), short(p)))
1063 1080 errors += 1
1064 1081 ca = self.changelog.node(self.manifest.linkrev(n))
1065 1082 cc = manifestchangeset[n]
1066 1083 if ca != cc:
1067 1084 self.ui.warn("manifest %s points to %s, not %s\n" %
1068 1085 (hex(n), hex(ca), hex(cc)))
1069 1086 errors += 1
1070 1087
1071 1088 try:
1072 1089 delta = mdiff.patchtext(self.manifest.delta(n))
1073 1090 except KeyboardInterrupt:
1074 1091 print "aborted"
1075 1092 sys.exit(0)
1076 1093 except Exception, inst:
1077 1094 self.ui.warn("unpacking manifest %s: %s\n"
1078 1095 % (short(n), inst))
1079 1096 errors += 1
1080 1097
1081 1098 ff = [ l.split('\0') for l in delta.splitlines() ]
1082 1099 for f, fn in ff:
1083 1100 filenodes.setdefault(f, {})[bin(fn)] = 1
1084 1101
1085 1102 self.ui.status("crosschecking files in changesets and manifests\n")
1086 1103 for f in filenodes:
1087 1104 if f not in filelinkrevs:
1088 1105 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1089 1106 errors += 1
1090 1107
1091 1108 for f in filelinkrevs:
1092 1109 if f not in filenodes:
1093 1110 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1094 1111 errors += 1
1095 1112
1096 1113 self.ui.status("checking files\n")
1097 1114 ff = filenodes.keys()
1098 1115 ff.sort()
1099 1116 for f in ff:
1100 1117 if f == "/dev/null": continue
1101 1118 files += 1
1102 1119 fl = self.file(f)
1103 1120 nodes = { nullid: 1 }
1104 1121 for i in range(fl.count()):
1105 1122 revisions += 1
1106 1123 n = fl.node(i)
1107 1124
1108 1125 if n not in filenodes[f]:
1109 1126 self.ui.warn("%s: %d:%s not in manifests\n"
1110 1127 % (f, i, short(n)))
1111 1128 print len(filenodes[f].keys()), fl.count(), f
1112 1129 errors += 1
1113 1130 else:
1114 1131 del filenodes[f][n]
1115 1132
1116 1133 flr = fl.linkrev(n)
1117 1134 if flr not in filelinkrevs[f]:
1118 1135 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1119 1136 % (f, short(n), fl.linkrev(n)))
1120 1137 errors += 1
1121 1138 else:
1122 1139 filelinkrevs[f].remove(flr)
1123 1140
1124 1141 # verify contents
1125 1142 try:
1126 1143 t = fl.read(n)
1127 1144 except Exception, inst:
1128 1145 self.ui.warn("unpacking file %s %s: %s\n"
1129 1146 % (f, short(n), inst))
1130 1147 errors += 1
1131 1148
1132 1149 # verify parents
1133 1150 (p1, p2) = fl.parents(n)
1134 1151 if p1 not in nodes:
1135 1152 self.ui.warn("file %s:%s unknown parent 1 %s" %
1136 1153 (f, short(n), short(p1)))
1137 1154 errors += 1
1138 1155 if p2 not in nodes:
1139 1156 self.ui.warn("file %s:%s unknown parent 2 %s" %
1140 1157 (f, short(n), short(p1)))
1141 1158 errors += 1
1142 1159 nodes[n] = 1
1143 1160
1144 1161 # cross-check
1145 1162 for node in filenodes[f]:
1146 1163 self.ui.warn("node %s in manifests not in %s\n"
1147 1164 % (hex(n), f))
1148 1165 errors += 1
1149 1166
1150 1167 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1151 1168 (files, changesets, revisions))
1152 1169
1153 1170 if errors:
1154 1171 self.ui.warn("%d integrity errors encountered!\n" % errors)
1155 1172 return 1
1156 1173
1157 1174 class remoterepository:
1158 1175 def __init__(self, ui, path):
1159 1176 self.url = path
1160 1177 self.ui = ui
1161 1178
1162 1179 def do_cmd(self, cmd, **args):
1163 1180 self.ui.debug("sending %s command\n" % cmd)
1164 1181 q = {"cmd": cmd}
1165 1182 q.update(args)
1166 1183 qs = urllib.urlencode(q)
1167 1184 cu = "%s?%s" % (self.url, qs)
1168 1185 return urllib.urlopen(cu)
1169 1186
1170 1187 def heads(self):
1171 1188 d = self.do_cmd("heads").read()
1172 1189 try:
1173 1190 return map(bin, d[:-1].split(" "))
1174 1191 except:
1175 1192 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1176 1193 raise
1177 1194
1178 1195 def branches(self, nodes):
1179 1196 n = " ".join(map(hex, nodes))
1180 1197 d = self.do_cmd("branches", nodes=n).read()
1181 1198 try:
1182 1199 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1183 1200 return br
1184 1201 except:
1185 1202 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1186 1203 raise
1187 1204
1188 1205 def between(self, pairs):
1189 1206 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1190 1207 d = self.do_cmd("between", pairs=n).read()
1191 1208 try:
1192 1209 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1193 1210 return p
1194 1211 except:
1195 1212 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1196 1213 raise
1197 1214
1198 1215 def changegroup(self, nodes):
1199 1216 n = " ".join(map(hex, nodes))
1200 1217 zd = zlib.decompressobj()
1201 1218 f = self.do_cmd("changegroup", roots=n)
1202 1219 bytes = 0
1203 1220 while 1:
1204 1221 d = f.read(4096)
1205 1222 bytes += len(d)
1206 1223 if not d:
1207 1224 yield zd.flush()
1208 1225 break
1209 1226 yield zd.decompress(d)
1210 1227 self.ui.note("%d bytes of data transfered\n" % bytes)
1211 1228
1212 1229 def repository(ui, path=None, create=0):
1213 1230 if path and path[:7] == "http://":
1214 1231 return remoterepository(ui, path)
1215 1232 if path and path[:5] == "hg://":
1216 1233 return remoterepository(ui, path.replace("hg://", "http://"))
1217 1234 if path and path[:11] == "old-http://":
1218 1235 return localrepository(ui, path.replace("old-http://", "http://"))
1219 1236 else:
1220 1237 return localrepository(ui, path, create)
1221 1238
1222 1239 class httprangereader:
1223 1240 def __init__(self, url):
1224 1241 self.url = url
1225 1242 self.pos = 0
1226 1243 def seek(self, pos):
1227 1244 self.pos = pos
1228 1245 def read(self, bytes=None):
1229 1246 opener = urllib2.build_opener(byterange.HTTPRangeHandler())
1230 1247 urllib2.install_opener(opener)
1231 1248 req = urllib2.Request(self.url)
1232 1249 end = ''
1233 1250 if bytes: end = self.pos + bytes
1234 1251 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
1235 1252 f = urllib2.urlopen(req)
1236 1253 return f.read()
General Comments 0
You need to be logged in to leave comments. Login now