comparison mercurial/hg.py @ 1089:142b5d5ec9cc

Break apart hg.py - move the various parts of hg.py into their own files - create node.py to store node manipulation functions
author mpm@selenic.com
date Sat, 27 Aug 2005 14:21:25 -0700
parents 05dc7aba22eb
children 1bca39b85615
comparison
equal deleted inserted replaced
1088:39b916b1d8e4 1089:142b5d5ec9cc
3 # Copyright 2005 Matt Mackall <mpm@selenic.com> 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 # 4 #
5 # This software may be used and distributed according to the terms 5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference. 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 import sys, struct, os 8 import os
9 import util 9 import util
10 from node import *
10 from revlog import * 11 from revlog import *
12 from repo import *
11 from demandload import * 13 from demandload import *
12 demandload(globals(), "re lock urllib urllib2 transaction time socket") 14 demandload(globals(), "localrepo httprepo sshrepo")
13 demandload(globals(), "tempfile httprangereader bdiff urlparse")
14 demandload(globals(), "bisect errno select stat")
15
16 class filelog(revlog):
17 def __init__(self, opener, path):
18 revlog.__init__(self, opener,
19 os.path.join("data", self.encodedir(path + ".i")),
20 os.path.join("data", self.encodedir(path + ".d")))
21
22 # This avoids a collision between a file named foo and a dir named
23 # foo.i or foo.d
24 def encodedir(self, path):
25 return (path
26 .replace(".hg/", ".hg.hg/")
27 .replace(".i/", ".i.hg/")
28 .replace(".d/", ".d.hg/"))
29
30 def decodedir(self, path):
31 return (path
32 .replace(".d.hg/", ".d/")
33 .replace(".i.hg/", ".i/")
34 .replace(".hg.hg/", ".hg/"))
35
36 def read(self, node):
37 t = self.revision(node)
38 if not t.startswith('\1\n'):
39 return t
40 s = t.find('\1\n', 2)
41 return t[s+2:]
42
43 def readmeta(self, node):
44 t = self.revision(node)
45 if not t.startswith('\1\n'):
46 return t
47 s = t.find('\1\n', 2)
48 mt = t[2:s]
49 for l in mt.splitlines():
50 k, v = l.split(": ", 1)
51 m[k] = v
52 return m
53
54 def add(self, text, meta, transaction, link, p1=None, p2=None):
55 if meta or text.startswith('\1\n'):
56 mt = ""
57 if meta:
58 mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ]
59 text = "\1\n" + "".join(mt) + "\1\n" + text
60 return self.addrevision(text, transaction, link, p1, p2)
61
62 def annotate(self, node):
63
64 def decorate(text, rev):
65 return ([rev] * len(text.splitlines()), text)
66
67 def pair(parent, child):
68 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
69 child[0][b1:b2] = parent[0][a1:a2]
70 return child
71
72 # find all ancestors
73 needed = {node:1}
74 visit = [node]
75 while visit:
76 n = visit.pop(0)
77 for p in self.parents(n):
78 if p not in needed:
79 needed[p] = 1
80 visit.append(p)
81 else:
82 # count how many times we'll use this
83 needed[p] += 1
84
85 # sort by revision which is a topological order
86 visit = [ (self.rev(n), n) for n in needed.keys() ]
87 visit.sort()
88 hist = {}
89
90 for r,n in visit:
91 curr = decorate(self.read(n), self.linkrev(n))
92 for p in self.parents(n):
93 if p != nullid:
94 curr = pair(hist[p], curr)
95 # trim the history of unneeded revs
96 needed[p] -= 1
97 if not needed[p]:
98 del hist[p]
99 hist[n] = curr
100
101 return zip(hist[n][0], hist[n][1].splitlines(1))
102
103 class manifest(revlog):
104 def __init__(self, opener):
105 self.mapcache = None
106 self.listcache = None
107 self.addlist = None
108 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
109
110 def read(self, node):
111 if node == nullid: return {} # don't upset local cache
112 if self.mapcache and self.mapcache[0] == node:
113 return self.mapcache[1]
114 text = self.revision(node)
115 map = {}
116 flag = {}
117 self.listcache = (text, text.splitlines(1))
118 for l in self.listcache[1]:
119 (f, n) = l.split('\0')
120 map[f] = bin(n[:40])
121 flag[f] = (n[40:-1] == "x")
122 self.mapcache = (node, map, flag)
123 return map
124
125 def readflags(self, node):
126 if node == nullid: return {} # don't upset local cache
127 if not self.mapcache or self.mapcache[0] != node:
128 self.read(node)
129 return self.mapcache[2]
130
131 def diff(self, a, b):
132 # this is sneaky, as we're not actually using a and b
133 if self.listcache and self.addlist and self.listcache[0] == a:
134 d = mdiff.diff(self.listcache[1], self.addlist, 1)
135 if mdiff.patch(a, d) != b:
136 sys.stderr.write("*** sortdiff failed, falling back ***\n")
137 return mdiff.textdiff(a, b)
138 return d
139 else:
140 return mdiff.textdiff(a, b)
141
142 def add(self, map, flags, transaction, link, p1=None, p2=None,
143 changed=None):
144 # directly generate the mdiff delta from the data collected during
145 # the bisect loop below
146 def gendelta(delta):
147 i = 0
148 result = []
149 while i < len(delta):
150 start = delta[i][2]
151 end = delta[i][3]
152 l = delta[i][4]
153 if l == None:
154 l = ""
155 while i < len(delta) - 1 and start <= delta[i+1][2] \
156 and end >= delta[i+1][2]:
157 if delta[i+1][3] > end:
158 end = delta[i+1][3]
159 if delta[i+1][4]:
160 l += delta[i+1][4]
161 i += 1
162 result.append(struct.pack(">lll", start, end, len(l)) + l)
163 i += 1
164 return result
165
166 # apply the changes collected during the bisect loop to our addlist
167 def addlistdelta(addlist, delta):
168 # apply the deltas to the addlist. start from the bottom up
169 # so changes to the offsets don't mess things up.
170 i = len(delta)
171 while i > 0:
172 i -= 1
173 start = delta[i][0]
174 end = delta[i][1]
175 if delta[i][4]:
176 addlist[start:end] = [delta[i][4]]
177 else:
178 del addlist[start:end]
179 return addlist
180
181 # calculate the byte offset of the start of each line in the
182 # manifest
183 def calcoffsets(addlist):
184 offsets = [0] * (len(addlist) + 1)
185 offset = 0
186 i = 0
187 while i < len(addlist):
188 offsets[i] = offset
189 offset += len(addlist[i])
190 i += 1
191 offsets[i] = offset
192 return offsets
193
194 # if we're using the listcache, make sure it is valid and
195 # parented by the same node we're diffing against
196 if not changed or not self.listcache or not p1 or \
197 self.mapcache[0] != p1:
198 files = map.keys()
199 files.sort()
200
201 self.addlist = ["%s\000%s%s\n" %
202 (f, hex(map[f]), flags[f] and "x" or '')
203 for f in files]
204 cachedelta = None
205 else:
206 addlist = self.listcache[1]
207
208 # find the starting offset for each line in the add list
209 offsets = calcoffsets(addlist)
210
211 # combine the changed lists into one list for sorting
212 work = [[x, 0] for x in changed[0]]
213 work[len(work):] = [[x, 1] for x in changed[1]]
214 work.sort()
215
216 delta = []
217 bs = 0
218
219 for w in work:
220 f = w[0]
221 # bs will either be the index of the item or the insert point
222 bs = bisect.bisect(addlist, f, bs)
223 if bs < len(addlist):
224 fn = addlist[bs][:addlist[bs].index('\0')]
225 else:
226 fn = None
227 if w[1] == 0:
228 l = "%s\000%s%s\n" % (f, hex(map[f]),
229 flags[f] and "x" or '')
230 else:
231 l = None
232 start = bs
233 if fn != f:
234 # item not found, insert a new one
235 end = bs
236 if w[1] == 1:
237 sys.stderr.write("failed to remove %s from manifest\n"
238 % f)
239 sys.exit(1)
240 else:
241 # item is found, replace/delete the existing line
242 end = bs + 1
243 delta.append([start, end, offsets[start], offsets[end], l])
244
245 self.addlist = addlistdelta(addlist, delta)
246 if self.mapcache[0] == self.tip():
247 cachedelta = "".join(gendelta(delta))
248 else:
249 cachedelta = None
250
251 text = "".join(self.addlist)
252 if cachedelta and mdiff.patch(self.listcache[0], cachedelta) != text:
253 sys.stderr.write("manifest delta failure\n")
254 sys.exit(1)
255 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
256 self.mapcache = (n, map, flags)
257 self.listcache = (text, self.addlist)
258 self.addlist = None
259
260 return n
261
262 class changelog(revlog):
263 def __init__(self, opener):
264 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
265
266 def extract(self, text):
267 if not text:
268 return (nullid, "", "0", [], "")
269 last = text.index("\n\n")
270 desc = text[last + 2:]
271 l = text[:last].splitlines()
272 manifest = bin(l[0])
273 user = l[1]
274 date = l[2]
275 if " " not in date:
276 date += " 0" # some tools used -d without a timezone
277 files = l[3:]
278 return (manifest, user, date, files, desc)
279
280 def read(self, node):
281 return self.extract(self.revision(node))
282
283 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
284 user=None, date=None):
285 if not date:
286 if time.daylight: offset = time.altzone
287 else: offset = time.timezone
288 date = "%d %d" % (time.time(), offset)
289 list.sort()
290 l = [hex(manifest), user, date] + list + ["", desc]
291 text = "\n".join(l)
292 return self.addrevision(text, transaction, self.count(), p1, p2)
293
294 class dirstate:
295 def __init__(self, opener, ui, root):
296 self.opener = opener
297 self.root = root
298 self.dirty = 0
299 self.ui = ui
300 self.map = None
301 self.pl = None
302 self.copies = {}
303 self.ignorefunc = None
304
305 def wjoin(self, f):
306 return os.path.join(self.root, f)
307
308 def getcwd(self):
309 cwd = os.getcwd()
310 if cwd == self.root: return ''
311 return cwd[len(self.root) + 1:]
312
313 def ignore(self, f):
314 if not self.ignorefunc:
315 bigpat = []
316 try:
317 l = file(self.wjoin(".hgignore"))
318 for pat in l:
319 p = pat.rstrip()
320 if p:
321 try:
322 re.compile(p)
323 except:
324 self.ui.warn("ignoring invalid ignore"
325 + " regular expression '%s'\n" % p)
326 else:
327 bigpat.append(p)
328 except IOError: pass
329
330 if bigpat:
331 s = "(?:%s)" % (")|(?:".join(bigpat))
332 r = re.compile(s)
333 self.ignorefunc = r.search
334 else:
335 self.ignorefunc = util.never
336
337 return self.ignorefunc(f)
338
339 def __del__(self):
340 if self.dirty:
341 self.write()
342
343 def __getitem__(self, key):
344 try:
345 return self.map[key]
346 except TypeError:
347 self.read()
348 return self[key]
349
350 def __contains__(self, key):
351 if not self.map: self.read()
352 return key in self.map
353
354 def parents(self):
355 if not self.pl:
356 self.read()
357 return self.pl
358
359 def markdirty(self):
360 if not self.dirty:
361 self.dirty = 1
362
363 def setparents(self, p1, p2=nullid):
364 self.markdirty()
365 self.pl = p1, p2
366
367 def state(self, key):
368 try:
369 return self[key][0]
370 except KeyError:
371 return "?"
372
373 def read(self):
374 if self.map is not None: return self.map
375
376 self.map = {}
377 self.pl = [nullid, nullid]
378 try:
379 st = self.opener("dirstate").read()
380 if not st: return
381 except: return
382
383 self.pl = [st[:20], st[20: 40]]
384
385 pos = 40
386 while pos < len(st):
387 e = struct.unpack(">cllll", st[pos:pos+17])
388 l = e[4]
389 pos += 17
390 f = st[pos:pos + l]
391 if '\0' in f:
392 f, c = f.split('\0')
393 self.copies[f] = c
394 self.map[f] = e[:4]
395 pos += l
396
397 def copy(self, source, dest):
398 self.read()
399 self.markdirty()
400 self.copies[dest] = source
401
402 def copied(self, file):
403 return self.copies.get(file, None)
404
405 def update(self, files, state, **kw):
406 ''' current states:
407 n normal
408 m needs merging
409 r marked for removal
410 a marked for addition'''
411
412 if not files: return
413 self.read()
414 self.markdirty()
415 for f in files:
416 if state == "r":
417 self.map[f] = ('r', 0, 0, 0)
418 else:
419 s = os.stat(os.path.join(self.root, f))
420 st_size = kw.get('st_size', s.st_size)
421 st_mtime = kw.get('st_mtime', s.st_mtime)
422 self.map[f] = (state, s.st_mode, st_size, st_mtime)
423
424 def forget(self, files):
425 if not files: return
426 self.read()
427 self.markdirty()
428 for f in files:
429 try:
430 del self.map[f]
431 except KeyError:
432 self.ui.warn("not in dirstate: %s!\n" % f)
433 pass
434
435 def clear(self):
436 self.map = {}
437 self.markdirty()
438
439 def write(self):
440 st = self.opener("dirstate", "w")
441 st.write("".join(self.pl))
442 for f, e in self.map.items():
443 c = self.copied(f)
444 if c:
445 f = f + "\0" + c
446 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
447 st.write(e + f)
448 self.dirty = 0
449
450 def filterfiles(self, files):
451 ret = {}
452 unknown = []
453
454 for x in files:
455 if x is '.':
456 return self.map.copy()
457 if x not in self.map:
458 unknown.append(x)
459 else:
460 ret[x] = self.map[x]
461
462 if not unknown:
463 return ret
464
465 b = self.map.keys()
466 b.sort()
467 blen = len(b)
468
469 for x in unknown:
470 bs = bisect.bisect(b, x)
471 if bs != 0 and b[bs-1] == x:
472 ret[x] = self.map[x]
473 continue
474 while bs < blen:
475 s = b[bs]
476 if len(s) > len(x) and s.startswith(x) and s[len(x)] == '/':
477 ret[s] = self.map[s]
478 else:
479 break
480 bs += 1
481 return ret
482
483 def walk(self, files=None, match=util.always, dc=None):
484 self.read()
485
486 # walk all files by default
487 if not files:
488 files = [self.root]
489 if not dc:
490 dc = self.map.copy()
491 elif not dc:
492 dc = self.filterfiles(files)
493
494 known = {'.hg': 1}
495 def seen(fn):
496 if fn in known: return True
497 known[fn] = 1
498 def traverse():
499 for ff in util.unique(files):
500 f = os.path.join(self.root, ff)
501 try:
502 st = os.stat(f)
503 except OSError, inst:
504 if ff not in dc: self.ui.warn('%s: %s\n' % (
505 util.pathto(self.getcwd(), ff),
506 inst.strerror))
507 continue
508 if stat.S_ISDIR(st.st_mode):
509 for dir, subdirs, fl in os.walk(f):
510 d = dir[len(self.root) + 1:]
511 nd = util.normpath(d)
512 if nd == '.': nd = ''
513 if seen(nd):
514 subdirs[:] = []
515 continue
516 for sd in subdirs:
517 ds = os.path.join(nd, sd +'/')
518 if self.ignore(ds) or not match(ds):
519 subdirs.remove(sd)
520 subdirs.sort()
521 fl.sort()
522 for fn in fl:
523 fn = util.pconvert(os.path.join(d, fn))
524 yield 'f', fn
525 elif stat.S_ISREG(st.st_mode):
526 yield 'f', ff
527 else:
528 kind = 'unknown'
529 if stat.S_ISCHR(st.st_mode): kind = 'character device'
530 elif stat.S_ISBLK(st.st_mode): kind = 'block device'
531 elif stat.S_ISFIFO(st.st_mode): kind = 'fifo'
532 elif stat.S_ISLNK(st.st_mode): kind = 'symbolic link'
533 elif stat.S_ISSOCK(st.st_mode): kind = 'socket'
534 self.ui.warn('%s: unsupported file type (type is %s)\n' % (
535 util.pathto(self.getcwd(), ff),
536 kind))
537
538 ks = dc.keys()
539 ks.sort()
540 for k in ks:
541 yield 'm', k
542
543 # yield only files that match: all in dirstate, others only if
544 # not in .hgignore
545
546 for src, fn in util.unique(traverse()):
547 fn = util.normpath(fn)
548 if seen(fn): continue
549 if fn not in dc and self.ignore(fn):
550 continue
551 if match(fn):
552 yield src, fn
553
554 def changes(self, files=None, match=util.always):
555 self.read()
556 if not files:
557 dc = self.map.copy()
558 else:
559 dc = self.filterfiles(files)
560 lookup, modified, added, unknown = [], [], [], []
561 removed, deleted = [], []
562
563 for src, fn in self.walk(files, match, dc=dc):
564 try:
565 s = os.stat(os.path.join(self.root, fn))
566 except OSError:
567 continue
568 if not stat.S_ISREG(s.st_mode):
569 continue
570 c = dc.get(fn)
571 if c:
572 del dc[fn]
573 if c[0] == 'm':
574 modified.append(fn)
575 elif c[0] == 'a':
576 added.append(fn)
577 elif c[0] == 'r':
578 unknown.append(fn)
579 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
580 modified.append(fn)
581 elif c[3] != s.st_mtime:
582 lookup.append(fn)
583 else:
584 unknown.append(fn)
585
586 for fn, c in [(fn, c) for fn, c in dc.items() if match(fn)]:
587 if c[0] == 'r':
588 removed.append(fn)
589 else:
590 deleted.append(fn)
591 return (lookup, modified, added, removed + deleted, unknown)
592 15
593 # used to avoid circular references so destructors work 16 # used to avoid circular references so destructors work
594 def opener(base): 17 def opener(base):
595 p = base 18 p = base
596 def o(path, mode="r"): 19 def o(path, mode="r"):
616 39
617 return file(f, mode) 40 return file(f, mode)
618 41
619 return o 42 return o
620 43
621 class RepoError(Exception): pass
622
623 class localrepository:
624 def __init__(self, ui, path=None, create=0):
625 self.remote = 0
626 if path and path.startswith("http://"):
627 self.remote = 1
628 self.path = path
629 else:
630 if not path:
631 p = os.getcwd()
632 while not os.path.isdir(os.path.join(p, ".hg")):
633 oldp = p
634 p = os.path.dirname(p)
635 if p == oldp: raise RepoError("no repo found")
636 path = p
637 self.path = os.path.join(path, ".hg")
638
639 if not create and not os.path.isdir(self.path):
640 raise RepoError("repository %s not found" % self.path)
641
642 self.root = os.path.abspath(path)
643 self.ui = ui
644
645 if create:
646 os.mkdir(self.path)
647 os.mkdir(self.join("data"))
648
649 self.opener = opener(self.path)
650 self.wopener = opener(self.root)
651 self.manifest = manifest(self.opener)
652 self.changelog = changelog(self.opener)
653 self.tagscache = None
654 self.nodetagscache = None
655
656 if not self.remote:
657 self.dirstate = dirstate(self.opener, ui, self.root)
658 try:
659 self.ui.readconfig(self.opener("hgrc"))
660 except IOError: pass
661
662 def hook(self, name, **args):
663 s = self.ui.config("hooks", name)
664 if s:
665 self.ui.note("running hook %s: %s\n" % (name, s))
666 old = {}
667 for k, v in args.items():
668 k = k.upper()
669 old[k] = os.environ.get(k, None)
670 os.environ[k] = v
671
672 r = os.system(s)
673
674 for k, v in old.items():
675 if v != None:
676 os.environ[k] = v
677 else:
678 del os.environ[k]
679
680 if r:
681 self.ui.warn("abort: %s hook failed with status %d!\n" %
682 (name, r))
683 return False
684 return True
685
686 def tags(self):
687 '''return a mapping of tag to node'''
688 if not self.tagscache:
689 self.tagscache = {}
690 def addtag(self, k, n):
691 try:
692 bin_n = bin(n)
693 except TypeError:
694 bin_n = ''
695 self.tagscache[k.strip()] = bin_n
696
697 try:
698 # read each head of the tags file, ending with the tip
699 # and add each tag found to the map, with "newer" ones
700 # taking precedence
701 fl = self.file(".hgtags")
702 h = fl.heads()
703 h.reverse()
704 for r in h:
705 for l in fl.read(r).splitlines():
706 if l:
707 n, k = l.split(" ", 1)
708 addtag(self, k, n)
709 except KeyError:
710 pass
711
712 try:
713 f = self.opener("localtags")
714 for l in f:
715 n, k = l.split(" ", 1)
716 addtag(self, k, n)
717 except IOError:
718 pass
719
720 self.tagscache['tip'] = self.changelog.tip()
721
722 return self.tagscache
723
724 def tagslist(self):
725 '''return a list of tags ordered by revision'''
726 l = []
727 for t, n in self.tags().items():
728 try:
729 r = self.changelog.rev(n)
730 except:
731 r = -2 # sort to the beginning of the list if unknown
732 l.append((r,t,n))
733 l.sort()
734 return [(t,n) for r,t,n in l]
735
736 def nodetags(self, node):
737 '''return the tags associated with a node'''
738 if not self.nodetagscache:
739 self.nodetagscache = {}
740 for t,n in self.tags().items():
741 self.nodetagscache.setdefault(n,[]).append(t)
742 return self.nodetagscache.get(node, [])
743
744 def lookup(self, key):
745 try:
746 return self.tags()[key]
747 except KeyError:
748 try:
749 return self.changelog.lookup(key)
750 except:
751 raise RepoError("unknown revision '%s'" % key)
752
753 def dev(self):
754 if self.remote: return -1
755 return os.stat(self.path).st_dev
756
757 def local(self):
758 return not self.remote
759
760 def join(self, f):
761 return os.path.join(self.path, f)
762
763 def wjoin(self, f):
764 return os.path.join(self.root, f)
765
766 def file(self, f):
767 if f[0] == '/': f = f[1:]
768 return filelog(self.opener, f)
769
770 def getcwd(self):
771 return self.dirstate.getcwd()
772
773 def wfile(self, f, mode='r'):
774 return self.wopener(f, mode)
775
776 def wread(self, filename):
777 return self.wopener(filename, 'r').read()
778
779 def wwrite(self, filename, data, fd=None):
780 if fd:
781 return fd.write(data)
782 return self.wopener(filename, 'w').write(data)
783
784 def transaction(self):
785 # save dirstate for undo
786 try:
787 ds = self.opener("dirstate").read()
788 except IOError:
789 ds = ""
790 self.opener("journal.dirstate", "w").write(ds)
791
792 def after():
793 util.rename(self.join("journal"), self.join("undo"))
794 util.rename(self.join("journal.dirstate"),
795 self.join("undo.dirstate"))
796
797 return transaction.transaction(self.ui.warn, self.opener,
798 self.join("journal"), after)
799
800 def recover(self):
801 lock = self.lock()
802 if os.path.exists(self.join("journal")):
803 self.ui.status("rolling back interrupted transaction\n")
804 return transaction.rollback(self.opener, self.join("journal"))
805 else:
806 self.ui.warn("no interrupted transaction available\n")
807
808 def undo(self):
809 lock = self.lock()
810 if os.path.exists(self.join("undo")):
811 self.ui.status("rolling back last transaction\n")
812 transaction.rollback(self.opener, self.join("undo"))
813 self.dirstate = None
814 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
815 self.dirstate = dirstate(self.opener, self.ui, self.root)
816 else:
817 self.ui.warn("no undo information available\n")
818
819 def lock(self, wait=1):
820 try:
821 return lock.lock(self.join("lock"), 0)
822 except lock.LockHeld, inst:
823 if wait:
824 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
825 return lock.lock(self.join("lock"), wait)
826 raise inst
827
828 def rawcommit(self, files, text, user, date, p1=None, p2=None):
829 orig_parent = self.dirstate.parents()[0] or nullid
830 p1 = p1 or self.dirstate.parents()[0] or nullid
831 p2 = p2 or self.dirstate.parents()[1] or nullid
832 c1 = self.changelog.read(p1)
833 c2 = self.changelog.read(p2)
834 m1 = self.manifest.read(c1[0])
835 mf1 = self.manifest.readflags(c1[0])
836 m2 = self.manifest.read(c2[0])
837 changed = []
838
839 if orig_parent == p1:
840 update_dirstate = 1
841 else:
842 update_dirstate = 0
843
844 tr = self.transaction()
845 mm = m1.copy()
846 mfm = mf1.copy()
847 linkrev = self.changelog.count()
848 for f in files:
849 try:
850 t = self.wread(f)
851 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
852 r = self.file(f)
853 mfm[f] = tm
854
855 fp1 = m1.get(f, nullid)
856 fp2 = m2.get(f, nullid)
857
858 # is the same revision on two branches of a merge?
859 if fp2 == fp1:
860 fp2 = nullid
861
862 if fp2 != nullid:
863 # is one parent an ancestor of the other?
864 fpa = r.ancestor(fp1, fp2)
865 if fpa == fp1:
866 fp1, fp2 = fp2, nullid
867 elif fpa == fp2:
868 fp2 = nullid
869
870 # is the file unmodified from the parent?
871 if t == r.read(fp1):
872 # record the proper existing parent in manifest
873 # no need to add a revision
874 mm[f] = fp1
875 continue
876
877 mm[f] = r.add(t, {}, tr, linkrev, fp1, fp2)
878 changed.append(f)
879 if update_dirstate:
880 self.dirstate.update([f], "n")
881 except IOError:
882 try:
883 del mm[f]
884 del mfm[f]
885 if update_dirstate:
886 self.dirstate.forget([f])
887 except:
888 # deleted from p2?
889 pass
890
891 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
892 user = user or self.ui.username()
893 n = self.changelog.add(mnode, changed, text, tr, p1, p2, user, date)
894 tr.close()
895 if update_dirstate:
896 self.dirstate.setparents(n, nullid)
897
898 def commit(self, files = None, text = "", user = None, date = None,
899 match = util.always, force=False):
900 commit = []
901 remove = []
902 changed = []
903
904 if files:
905 for f in files:
906 s = self.dirstate.state(f)
907 if s in 'nmai':
908 commit.append(f)
909 elif s == 'r':
910 remove.append(f)
911 else:
912 self.ui.warn("%s not tracked!\n" % f)
913 else:
914 (c, a, d, u) = self.changes(match=match)
915 commit = c + a
916 remove = d
917
918 p1, p2 = self.dirstate.parents()
919 c1 = self.changelog.read(p1)
920 c2 = self.changelog.read(p2)
921 m1 = self.manifest.read(c1[0])
922 mf1 = self.manifest.readflags(c1[0])
923 m2 = self.manifest.read(c2[0])
924
925 if not commit and not remove and not force and p2 == nullid:
926 self.ui.status("nothing changed\n")
927 return None
928
929 if not self.hook("precommit"):
930 return None
931
932 lock = self.lock()
933 tr = self.transaction()
934
935 # check in files
936 new = {}
937 linkrev = self.changelog.count()
938 commit.sort()
939 for f in commit:
940 self.ui.note(f + "\n")
941 try:
942 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
943 t = self.wread(f)
944 except IOError:
945 self.ui.warn("trouble committing %s!\n" % f)
946 raise
947
948 meta = {}
949 cp = self.dirstate.copied(f)
950 if cp:
951 meta["copy"] = cp
952 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
953 self.ui.debug(" %s: copy %s:%s\n" % (f, cp, meta["copyrev"]))
954
955 r = self.file(f)
956 fp1 = m1.get(f, nullid)
957 fp2 = m2.get(f, nullid)
958
959 # is the same revision on two branches of a merge?
960 if fp2 == fp1:
961 fp2 = nullid
962
963 if fp2 != nullid:
964 # is one parent an ancestor of the other?
965 fpa = r.ancestor(fp1, fp2)
966 if fpa == fp1:
967 fp1, fp2 = fp2, nullid
968 elif fpa == fp2:
969 fp2 = nullid
970
971 # is the file unmodified from the parent?
972 if not meta and t == r.read(fp1):
973 # record the proper existing parent in manifest
974 # no need to add a revision
975 new[f] = fp1
976 continue
977
978 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
979 # remember what we've added so that we can later calculate
980 # the files to pull from a set of changesets
981 changed.append(f)
982
983 # update manifest
984 m1.update(new)
985 for f in remove:
986 if f in m1:
987 del m1[f]
988 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0],
989 (new, remove))
990
991 # add changeset
992 new = new.keys()
993 new.sort()
994
995 if not text:
996 edittext = ""
997 if p2 != nullid:
998 edittext += "HG: branch merge\n"
999 edittext += "\n" + "HG: manifest hash %s\n" % hex(mn)
1000 edittext += "".join(["HG: changed %s\n" % f for f in changed])
1001 edittext += "".join(["HG: removed %s\n" % f for f in remove])
1002 if not changed and not remove:
1003 edittext += "HG: no files changed\n"
1004 edittext = self.ui.edit(edittext)
1005 if not edittext.rstrip():
1006 return None
1007 text = edittext
1008
1009 user = user or self.ui.username()
1010 n = self.changelog.add(mn, changed, text, tr, p1, p2, user, date)
1011 tr.close()
1012
1013 self.dirstate.setparents(n)
1014 self.dirstate.update(new, "n")
1015 self.dirstate.forget(remove)
1016
1017 if not self.hook("commit", node=hex(n)):
1018 return None
1019 return n
1020
1021 def walk(self, node=None, files=[], match=util.always):
1022 if node:
1023 for fn in self.manifest.read(self.changelog.read(node)[0]):
1024 if match(fn): yield 'm', fn
1025 else:
1026 for src, fn in self.dirstate.walk(files, match):
1027 yield src, fn
1028
1029 def changes(self, node1 = None, node2 = None, files = [],
1030 match = util.always):
1031 mf2, u = None, []
1032
1033 def fcmp(fn, mf):
1034 t1 = self.wread(fn)
1035 t2 = self.file(fn).read(mf.get(fn, nullid))
1036 return cmp(t1, t2)
1037
1038 def mfmatches(node):
1039 mf = dict(self.manifest.read(node))
1040 for fn in mf.keys():
1041 if not match(fn):
1042 del mf[fn]
1043 return mf
1044
1045 # are we comparing the working directory?
1046 if not node2:
1047 l, c, a, d, u = self.dirstate.changes(files, match)
1048
1049 # are we comparing working dir against its parent?
1050 if not node1:
1051 if l:
1052 # do a full compare of any files that might have changed
1053 change = self.changelog.read(self.dirstate.parents()[0])
1054 mf2 = mfmatches(change[0])
1055 for f in l:
1056 if fcmp(f, mf2):
1057 c.append(f)
1058
1059 for l in c, a, d, u:
1060 l.sort()
1061
1062 return (c, a, d, u)
1063
1064 # are we comparing working dir against non-tip?
1065 # generate a pseudo-manifest for the working dir
1066 if not node2:
1067 if not mf2:
1068 change = self.changelog.read(self.dirstate.parents()[0])
1069 mf2 = mfmatches(change[0])
1070 for f in a + c + l:
1071 mf2[f] = ""
1072 for f in d:
1073 if f in mf2: del mf2[f]
1074 else:
1075 change = self.changelog.read(node2)
1076 mf2 = mfmatches(change[0])
1077
1078 # flush lists from dirstate before comparing manifests
1079 c, a = [], []
1080
1081 change = self.changelog.read(node1)
1082 mf1 = mfmatches(change[0])
1083
1084 for fn in mf2:
1085 if mf1.has_key(fn):
1086 if mf1[fn] != mf2[fn]:
1087 if mf2[fn] != "" or fcmp(fn, mf1):
1088 c.append(fn)
1089 del mf1[fn]
1090 else:
1091 a.append(fn)
1092
1093 d = mf1.keys()
1094
1095 for l in c, a, d, u:
1096 l.sort()
1097
1098 return (c, a, d, u)
1099
1100 def add(self, list):
1101 for f in list:
1102 p = self.wjoin(f)
1103 if not os.path.exists(p):
1104 self.ui.warn("%s does not exist!\n" % f)
1105 elif not os.path.isfile(p):
1106 self.ui.warn("%s not added: only files supported currently\n" % f)
1107 elif self.dirstate.state(f) in 'an':
1108 self.ui.warn("%s already tracked!\n" % f)
1109 else:
1110 self.dirstate.update([f], "a")
1111
1112 def forget(self, list):
1113 for f in list:
1114 if self.dirstate.state(f) not in 'ai':
1115 self.ui.warn("%s not added!\n" % f)
1116 else:
1117 self.dirstate.forget([f])
1118
1119 def remove(self, list):
1120 for f in list:
1121 p = self.wjoin(f)
1122 if os.path.exists(p):
1123 self.ui.warn("%s still exists!\n" % f)
1124 elif self.dirstate.state(f) == 'a':
1125 self.ui.warn("%s never committed!\n" % f)
1126 self.dirstate.forget([f])
1127 elif f not in self.dirstate:
1128 self.ui.warn("%s not tracked!\n" % f)
1129 else:
1130 self.dirstate.update([f], "r")
1131
1132 def copy(self, source, dest):
1133 p = self.wjoin(dest)
1134 if not os.path.exists(p):
1135 self.ui.warn("%s does not exist!\n" % dest)
1136 elif not os.path.isfile(p):
1137 self.ui.warn("copy failed: %s is not a file\n" % dest)
1138 else:
1139 if self.dirstate.state(dest) == '?':
1140 self.dirstate.update([dest], "a")
1141 self.dirstate.copy(source, dest)
1142
1143 def heads(self):
1144 return self.changelog.heads()
1145
1146 # branchlookup returns a dict giving a list of branches for
1147 # each head. A branch is defined as the tag of a node or
1148 # the branch of the node's parents. If a node has multiple
1149 # branch tags, tags are eliminated if they are visible from other
1150 # branch tags.
1151 #
1152 # So, for this graph: a->b->c->d->e
1153 # \ /
1154 # aa -----/
1155 # a has tag 2.6.12
1156 # d has tag 2.6.13
1157 # e would have branch tags for 2.6.12 and 2.6.13. Because the node
1158 # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
1159 # from the list.
1160 #
1161 # It is possible that more than one head will have the same branch tag.
1162 # callers need to check the result for multiple heads under the same
1163 # branch tag if that is a problem for them (ie checkout of a specific
1164 # branch).
1165 #
1166 # passing in a specific branch will limit the depth of the search
1167 # through the parents. It won't limit the branches returned in the
1168 # result though.
1169 def branchlookup(self, heads=None, branch=None):
1170 if not heads:
1171 heads = self.heads()
1172 headt = [ h for h in heads ]
1173 chlog = self.changelog
1174 branches = {}
1175 merges = []
1176 seenmerge = {}
1177
1178 # traverse the tree once for each head, recording in the branches
1179 # dict which tags are visible from this head. The branches
1180 # dict also records which tags are visible from each tag
1181 # while we traverse.
1182 while headt or merges:
1183 if merges:
1184 n, found = merges.pop()
1185 visit = [n]
1186 else:
1187 h = headt.pop()
1188 visit = [h]
1189 found = [h]
1190 seen = {}
1191 while visit:
1192 n = visit.pop()
1193 if n in seen:
1194 continue
1195 pp = chlog.parents(n)
1196 tags = self.nodetags(n)
1197 if tags:
1198 for x in tags:
1199 if x == 'tip':
1200 continue
1201 for f in found:
1202 branches.setdefault(f, {})[n] = 1
1203 branches.setdefault(n, {})[n] = 1
1204 break
1205 if n not in found:
1206 found.append(n)
1207 if branch in tags:
1208 continue
1209 seen[n] = 1
1210 if pp[1] != nullid and n not in seenmerge:
1211 merges.append((pp[1], [x for x in found]))
1212 seenmerge[n] = 1
1213 if pp[0] != nullid:
1214 visit.append(pp[0])
1215 # traverse the branches dict, eliminating branch tags from each
1216 # head that are visible from another branch tag for that head.
1217 out = {}
1218 viscache = {}
1219 for h in heads:
1220 def visible(node):
1221 if node in viscache:
1222 return viscache[node]
1223 ret = {}
1224 visit = [node]
1225 while visit:
1226 x = visit.pop()
1227 if x in viscache:
1228 ret.update(viscache[x])
1229 elif x not in ret:
1230 ret[x] = 1
1231 if x in branches:
1232 visit[len(visit):] = branches[x].keys()
1233 viscache[node] = ret
1234 return ret
1235 if h not in branches:
1236 continue
1237 # O(n^2), but somewhat limited. This only searches the
1238 # tags visible from a specific head, not all the tags in the
1239 # whole repo.
1240 for b in branches[h]:
1241 vis = False
1242 for bb in branches[h].keys():
1243 if b != bb:
1244 if b in visible(bb):
1245 vis = True
1246 break
1247 if not vis:
1248 l = out.setdefault(h, [])
1249 l[len(l):] = self.nodetags(b)
1250 return out
1251
1252 def branches(self, nodes):
1253 if not nodes: nodes = [self.changelog.tip()]
1254 b = []
1255 for n in nodes:
1256 t = n
1257 while n:
1258 p = self.changelog.parents(n)
1259 if p[1] != nullid or p[0] == nullid:
1260 b.append((t, n, p[0], p[1]))
1261 break
1262 n = p[0]
1263 return b
1264
1265 def between(self, pairs):
1266 r = []
1267
1268 for top, bottom in pairs:
1269 n, l, i = top, [], 0
1270 f = 1
1271
1272 while n != bottom:
1273 p = self.changelog.parents(n)[0]
1274 if i == f:
1275 l.append(n)
1276 f = f * 2
1277 n = p
1278 i += 1
1279
1280 r.append(l)
1281
1282 return r
1283
1284 def newer(self, nodes):
1285 m = {}
1286 nl = []
1287 pm = {}
1288 cl = self.changelog
1289 t = l = cl.count()
1290
1291 # find the lowest numbered node
1292 for n in nodes:
1293 l = min(l, cl.rev(n))
1294 m[n] = 1
1295
1296 for i in xrange(l, t):
1297 n = cl.node(i)
1298 if n in m: # explicitly listed
1299 pm[n] = 1
1300 nl.append(n)
1301 continue
1302 for p in cl.parents(n):
1303 if p in pm: # parent listed
1304 pm[n] = 1
1305 nl.append(n)
1306 break
1307
1308 return nl
1309
1310 def findincoming(self, remote, base=None, heads=None):
1311 m = self.changelog.nodemap
1312 search = []
1313 fetch = {}
1314 seen = {}
1315 seenbranch = {}
1316 if base == None:
1317 base = {}
1318
1319 # assume we're closer to the tip than the root
1320 # and start by examining the heads
1321 self.ui.status("searching for changes\n")
1322
1323 if not heads:
1324 heads = remote.heads()
1325
1326 unknown = []
1327 for h in heads:
1328 if h not in m:
1329 unknown.append(h)
1330 else:
1331 base[h] = 1
1332
1333 if not unknown:
1334 return None
1335
1336 rep = {}
1337 reqcnt = 0
1338
1339 # search through remote branches
1340 # a 'branch' here is a linear segment of history, with four parts:
1341 # head, root, first parent, second parent
1342 # (a branch always has two parents (or none) by definition)
1343 unknown = remote.branches(unknown)
1344 while unknown:
1345 r = []
1346 while unknown:
1347 n = unknown.pop(0)
1348 if n[0] in seen:
1349 continue
1350
1351 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
1352 if n[0] == nullid:
1353 break
1354 if n in seenbranch:
1355 self.ui.debug("branch already found\n")
1356 continue
1357 if n[1] and n[1] in m: # do we know the base?
1358 self.ui.debug("found incomplete branch %s:%s\n"
1359 % (short(n[0]), short(n[1])))
1360 search.append(n) # schedule branch range for scanning
1361 seenbranch[n] = 1
1362 else:
1363 if n[1] not in seen and n[1] not in fetch:
1364 if n[2] in m and n[3] in m:
1365 self.ui.debug("found new changeset %s\n" %
1366 short(n[1]))
1367 fetch[n[1]] = 1 # earliest unknown
1368 base[n[2]] = 1 # latest known
1369 continue
1370
1371 for a in n[2:4]:
1372 if a not in rep:
1373 r.append(a)
1374 rep[a] = 1
1375
1376 seen[n[0]] = 1
1377
1378 if r:
1379 reqcnt += 1
1380 self.ui.debug("request %d: %s\n" %
1381 (reqcnt, " ".join(map(short, r))))
1382 for p in range(0, len(r), 10):
1383 for b in remote.branches(r[p:p+10]):
1384 self.ui.debug("received %s:%s\n" %
1385 (short(b[0]), short(b[1])))
1386 if b[0] in m:
1387 self.ui.debug("found base node %s\n" % short(b[0]))
1388 base[b[0]] = 1
1389 elif b[0] not in seen:
1390 unknown.append(b)
1391
1392 # do binary search on the branches we found
1393 while search:
1394 n = search.pop(0)
1395 reqcnt += 1
1396 l = remote.between([(n[0], n[1])])[0]
1397 l.append(n[1])
1398 p = n[0]
1399 f = 1
1400 for i in l:
1401 self.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
1402 if i in m:
1403 if f <= 2:
1404 self.ui.debug("found new branch changeset %s\n" %
1405 short(p))
1406 fetch[p] = 1
1407 base[i] = 1
1408 else:
1409 self.ui.debug("narrowed branch search to %s:%s\n"
1410 % (short(p), short(i)))
1411 search.append((p, i))
1412 break
1413 p, f = i, f * 2
1414
1415 # sanity check our fetch list
1416 for f in fetch.keys():
1417 if f in m:
1418 raise RepoError("already have changeset " + short(f[:4]))
1419
1420 if base.keys() == [nullid]:
1421 self.ui.warn("warning: pulling from an unrelated repository!\n")
1422
1423 self.ui.note("found new changesets starting at " +
1424 " ".join([short(f) for f in fetch]) + "\n")
1425
1426 self.ui.debug("%d total queries\n" % reqcnt)
1427
1428 return fetch.keys()
1429
1430 def findoutgoing(self, remote, base=None, heads=None):
1431 if base == None:
1432 base = {}
1433 self.findincoming(remote, base, heads)
1434
1435 self.ui.debug("common changesets up to "
1436 + " ".join(map(short, base.keys())) + "\n")
1437
1438 remain = dict.fromkeys(self.changelog.nodemap)
1439
1440 # prune everything remote has from the tree
1441 del remain[nullid]
1442 remove = base.keys()
1443 while remove:
1444 n = remove.pop(0)
1445 if n in remain:
1446 del remain[n]
1447 for p in self.changelog.parents(n):
1448 remove.append(p)
1449
1450 # find every node whose parents have been pruned
1451 subset = []
1452 for n in remain:
1453 p1, p2 = self.changelog.parents(n)
1454 if p1 not in remain and p2 not in remain:
1455 subset.append(n)
1456
1457 # this is the set of all roots we have to push
1458 return subset
1459
1460 def pull(self, remote):
1461 lock = self.lock()
1462
1463 # if we have an empty repo, fetch everything
1464 if self.changelog.tip() == nullid:
1465 self.ui.status("requesting all changes\n")
1466 fetch = [nullid]
1467 else:
1468 fetch = self.findincoming(remote)
1469
1470 if not fetch:
1471 self.ui.status("no changes found\n")
1472 return 1
1473
1474 cg = remote.changegroup(fetch)
1475 return self.addchangegroup(cg)
1476
1477 def push(self, remote, force=False):
1478 lock = remote.lock()
1479
1480 base = {}
1481 heads = remote.heads()
1482 inc = self.findincoming(remote, base, heads)
1483 if not force and inc:
1484 self.ui.warn("abort: unsynced remote changes!\n")
1485 self.ui.status("(did you forget to sync? use push -f to force)\n")
1486 return 1
1487
1488 update = self.findoutgoing(remote, base)
1489 if not update:
1490 self.ui.status("no changes found\n")
1491 return 1
1492 elif not force:
1493 if len(heads) < len(self.changelog.heads()):
1494 self.ui.warn("abort: push creates new remote branches!\n")
1495 self.ui.status("(did you forget to merge?" +
1496 " use push -f to force)\n")
1497 return 1
1498
1499 cg = self.changegroup(update)
1500 return remote.addchangegroup(cg)
1501
1502 def changegroup(self, basenodes):
1503 class genread:
1504 def __init__(self, generator):
1505 self.g = generator
1506 self.buf = ""
1507 def fillbuf(self):
1508 self.buf += "".join(self.g)
1509
1510 def read(self, l):
1511 while l > len(self.buf):
1512 try:
1513 self.buf += self.g.next()
1514 except StopIteration:
1515 break
1516 d, self.buf = self.buf[:l], self.buf[l:]
1517 return d
1518
1519 def gengroup():
1520 nodes = self.newer(basenodes)
1521
1522 # construct the link map
1523 linkmap = {}
1524 for n in nodes:
1525 linkmap[self.changelog.rev(n)] = n
1526
1527 # construct a list of all changed files
1528 changed = {}
1529 for n in nodes:
1530 c = self.changelog.read(n)
1531 for f in c[3]:
1532 changed[f] = 1
1533 changed = changed.keys()
1534 changed.sort()
1535
1536 # the changegroup is changesets + manifests + all file revs
1537 revs = [ self.changelog.rev(n) for n in nodes ]
1538
1539 for y in self.changelog.group(linkmap): yield y
1540 for y in self.manifest.group(linkmap): yield y
1541 for f in changed:
1542 yield struct.pack(">l", len(f) + 4) + f
1543 g = self.file(f).group(linkmap)
1544 for y in g:
1545 yield y
1546
1547 yield struct.pack(">l", 0)
1548
1549 return genread(gengroup())
1550
1551 def addchangegroup(self, source):
1552
1553 def getchunk():
1554 d = source.read(4)
1555 if not d: return ""
1556 l = struct.unpack(">l", d)[0]
1557 if l <= 4: return ""
1558 return source.read(l - 4)
1559
1560 def getgroup():
1561 while 1:
1562 c = getchunk()
1563 if not c: break
1564 yield c
1565
1566 def csmap(x):
1567 self.ui.debug("add changeset %s\n" % short(x))
1568 return self.changelog.count()
1569
1570 def revmap(x):
1571 return self.changelog.rev(x)
1572
1573 if not source: return
1574 changesets = files = revisions = 0
1575
1576 tr = self.transaction()
1577
1578 oldheads = len(self.changelog.heads())
1579
1580 # pull off the changeset group
1581 self.ui.status("adding changesets\n")
1582 co = self.changelog.tip()
1583 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
1584 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
1585
1586 # pull off the manifest group
1587 self.ui.status("adding manifests\n")
1588 mm = self.manifest.tip()
1589 mo = self.manifest.addgroup(getgroup(), revmap, tr)
1590
1591 # process the files
1592 self.ui.status("adding file changes\n")
1593 while 1:
1594 f = getchunk()
1595 if not f: break
1596 self.ui.debug("adding %s revisions\n" % f)
1597 fl = self.file(f)
1598 o = fl.count()
1599 n = fl.addgroup(getgroup(), revmap, tr)
1600 revisions += fl.count() - o
1601 files += 1
1602
1603 newheads = len(self.changelog.heads())
1604 heads = ""
1605 if oldheads and newheads > oldheads:
1606 heads = " (+%d heads)" % (newheads - oldheads)
1607
1608 self.ui.status(("added %d changesets" +
1609 " with %d changes to %d files%s\n")
1610 % (changesets, revisions, files, heads))
1611
1612 tr.close()
1613
1614 if not self.hook("changegroup"):
1615 return 1
1616
1617 return
1618
1619 def update(self, node, allow=False, force=False, choose=None,
1620 moddirstate=True):
1621 pl = self.dirstate.parents()
1622 if not force and pl[1] != nullid:
1623 self.ui.warn("aborting: outstanding uncommitted merges\n")
1624 return 1
1625
1626 p1, p2 = pl[0], node
1627 pa = self.changelog.ancestor(p1, p2)
1628 m1n = self.changelog.read(p1)[0]
1629 m2n = self.changelog.read(p2)[0]
1630 man = self.manifest.ancestor(m1n, m2n)
1631 m1 = self.manifest.read(m1n)
1632 mf1 = self.manifest.readflags(m1n)
1633 m2 = self.manifest.read(m2n)
1634 mf2 = self.manifest.readflags(m2n)
1635 ma = self.manifest.read(man)
1636 mfa = self.manifest.readflags(man)
1637
1638 (c, a, d, u) = self.changes()
1639
1640 # is this a jump, or a merge? i.e. is there a linear path
1641 # from p1 to p2?
1642 linear_path = (pa == p1 or pa == p2)
1643
1644 # resolve the manifest to determine which files
1645 # we care about merging
1646 self.ui.note("resolving manifests\n")
1647 self.ui.debug(" force %s allow %s moddirstate %s linear %s\n" %
1648 (force, allow, moddirstate, linear_path))
1649 self.ui.debug(" ancestor %s local %s remote %s\n" %
1650 (short(man), short(m1n), short(m2n)))
1651
1652 merge = {}
1653 get = {}
1654 remove = []
1655
1656 # construct a working dir manifest
1657 mw = m1.copy()
1658 mfw = mf1.copy()
1659 umap = dict.fromkeys(u)
1660
1661 for f in a + c + u:
1662 mw[f] = ""
1663 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1664
1665 for f in d:
1666 if f in mw: del mw[f]
1667
1668 # If we're jumping between revisions (as opposed to merging),
1669 # and if neither the working directory nor the target rev has
1670 # the file, then we need to remove it from the dirstate, to
1671 # prevent the dirstate from listing the file when it is no
1672 # longer in the manifest.
1673 if moddirstate and linear_path and f not in m2:
1674 self.dirstate.forget((f,))
1675
1676 # Compare manifests
1677 for f, n in mw.iteritems():
1678 if choose and not choose(f): continue
1679 if f in m2:
1680 s = 0
1681
1682 # is the wfile new since m1, and match m2?
1683 if f not in m1:
1684 t1 = self.wread(f)
1685 t2 = self.file(f).read(m2[f])
1686 if cmp(t1, t2) == 0:
1687 n = m2[f]
1688 del t1, t2
1689
1690 # are files different?
1691 if n != m2[f]:
1692 a = ma.get(f, nullid)
1693 # are both different from the ancestor?
1694 if n != a and m2[f] != a:
1695 self.ui.debug(" %s versions differ, resolve\n" % f)
1696 # merge executable bits
1697 # "if we changed or they changed, change in merge"
1698 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1699 mode = ((a^b) | (a^c)) ^ a
1700 merge[f] = (m1.get(f, nullid), m2[f], mode)
1701 s = 1
1702 # are we clobbering?
1703 # is remote's version newer?
1704 # or are we going back in time?
1705 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1706 self.ui.debug(" remote %s is newer, get\n" % f)
1707 get[f] = m2[f]
1708 s = 1
1709 elif f in umap:
1710 # this unknown file is the same as the checkout
1711 get[f] = m2[f]
1712
1713 if not s and mfw[f] != mf2[f]:
1714 if force:
1715 self.ui.debug(" updating permissions for %s\n" % f)
1716 util.set_exec(self.wjoin(f), mf2[f])
1717 else:
1718 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1719 mode = ((a^b) | (a^c)) ^ a
1720 if mode != b:
1721 self.ui.debug(" updating permissions for %s\n" % f)
1722 util.set_exec(self.wjoin(f), mode)
1723 del m2[f]
1724 elif f in ma:
1725 if n != ma[f]:
1726 r = "d"
1727 if not force and (linear_path or allow):
1728 r = self.ui.prompt(
1729 (" local changed %s which remote deleted\n" % f) +
1730 "(k)eep or (d)elete?", "[kd]", "k")
1731 if r == "d":
1732 remove.append(f)
1733 else:
1734 self.ui.debug("other deleted %s\n" % f)
1735 remove.append(f) # other deleted it
1736 else:
1737 if n == m1.get(f, nullid): # same as parent
1738 if p2 == pa: # going backwards?
1739 self.ui.debug("remote deleted %s\n" % f)
1740 remove.append(f)
1741 else:
1742 self.ui.debug("local created %s, keeping\n" % f)
1743 else:
1744 self.ui.debug("working dir created %s, keeping\n" % f)
1745
1746 for f, n in m2.iteritems():
1747 if choose and not choose(f): continue
1748 if f[0] == "/": continue
1749 if f in ma and n != ma[f]:
1750 r = "k"
1751 if not force and (linear_path or allow):
1752 r = self.ui.prompt(
1753 ("remote changed %s which local deleted\n" % f) +
1754 "(k)eep or (d)elete?", "[kd]", "k")
1755 if r == "k": get[f] = n
1756 elif f not in ma:
1757 self.ui.debug("remote created %s\n" % f)
1758 get[f] = n
1759 else:
1760 if force or p2 == pa: # going backwards?
1761 self.ui.debug("local deleted %s, recreating\n" % f)
1762 get[f] = n
1763 else:
1764 self.ui.debug("local deleted %s\n" % f)
1765
1766 del mw, m1, m2, ma
1767
1768 if force:
1769 for f in merge:
1770 get[f] = merge[f][1]
1771 merge = {}
1772
1773 if linear_path or force:
1774 # we don't need to do any magic, just jump to the new rev
1775 branch_merge = False
1776 p1, p2 = p2, nullid
1777 else:
1778 if not allow:
1779 self.ui.status("this update spans a branch" +
1780 " affecting the following files:\n")
1781 fl = merge.keys() + get.keys()
1782 fl.sort()
1783 for f in fl:
1784 cf = ""
1785 if f in merge: cf = " (resolve)"
1786 self.ui.status(" %s%s\n" % (f, cf))
1787 self.ui.warn("aborting update spanning branches!\n")
1788 self.ui.status("(use update -m to merge across branches" +
1789 " or -C to lose changes)\n")
1790 return 1
1791 branch_merge = True
1792
1793 if moddirstate:
1794 self.dirstate.setparents(p1, p2)
1795
1796 # get the files we don't need to change
1797 files = get.keys()
1798 files.sort()
1799 for f in files:
1800 if f[0] == "/": continue
1801 self.ui.note("getting %s\n" % f)
1802 t = self.file(f).read(get[f])
1803 try:
1804 self.wwrite(f, t)
1805 except IOError:
1806 os.makedirs(os.path.dirname(self.wjoin(f)))
1807 self.wwrite(f, t)
1808 util.set_exec(self.wjoin(f), mf2[f])
1809 if moddirstate:
1810 if branch_merge:
1811 self.dirstate.update([f], 'n', st_mtime=-1)
1812 else:
1813 self.dirstate.update([f], 'n')
1814
1815 # merge the tricky bits
1816 files = merge.keys()
1817 files.sort()
1818 for f in files:
1819 self.ui.status("merging %s\n" % f)
1820 my, other, flag = merge[f]
1821 self.merge3(f, my, other)
1822 util.set_exec(self.wjoin(f), flag)
1823 if moddirstate:
1824 if branch_merge:
1825 # We've done a branch merge, mark this file as merged
1826 # so that we properly record the merger later
1827 self.dirstate.update([f], 'm')
1828 else:
1829 # We've update-merged a locally modified file, so
1830 # we set the dirstate to emulate a normal checkout
1831 # of that file some time in the past. Thus our
1832 # merge will appear as a normal local file
1833 # modification.
1834 f_len = len(self.file(f).read(other))
1835 self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
1836
1837 remove.sort()
1838 for f in remove:
1839 self.ui.note("removing %s\n" % f)
1840 try:
1841 os.unlink(self.wjoin(f))
1842 except OSError, inst:
1843 self.ui.warn("update failed to remove %s: %s!\n" % (f, inst))
1844 # try removing directories that might now be empty
1845 try: os.removedirs(os.path.dirname(self.wjoin(f)))
1846 except: pass
1847 if moddirstate:
1848 if branch_merge:
1849 self.dirstate.update(remove, 'r')
1850 else:
1851 self.dirstate.forget(remove)
1852
1853 def merge3(self, fn, my, other):
1854 """perform a 3-way merge in the working directory"""
1855
1856 def temp(prefix, node):
1857 pre = "%s~%s." % (os.path.basename(fn), prefix)
1858 (fd, name) = tempfile.mkstemp("", pre)
1859 f = os.fdopen(fd, "wb")
1860 self.wwrite(fn, fl.read(node), f)
1861 f.close()
1862 return name
1863
1864 fl = self.file(fn)
1865 base = fl.ancestor(my, other)
1866 a = self.wjoin(fn)
1867 b = temp("base", base)
1868 c = temp("other", other)
1869
1870 self.ui.note("resolving %s\n" % fn)
1871 self.ui.debug("file %s: other %s ancestor %s\n" %
1872 (fn, short(other), short(base)))
1873
1874 cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
1875 or "hgmerge")
1876 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1877 if r:
1878 self.ui.warn("merging %s failed!\n" % fn)
1879
1880 os.unlink(b)
1881 os.unlink(c)
1882
1883 def verify(self):
1884 filelinkrevs = {}
1885 filenodes = {}
1886 changesets = revisions = files = 0
1887 errors = 0
1888
1889 seen = {}
1890 self.ui.status("checking changesets\n")
1891 for i in range(self.changelog.count()):
1892 changesets += 1
1893 n = self.changelog.node(i)
1894 if n in seen:
1895 self.ui.warn("duplicate changeset at revision %d\n" % i)
1896 errors += 1
1897 seen[n] = 1
1898
1899 for p in self.changelog.parents(n):
1900 if p not in self.changelog.nodemap:
1901 self.ui.warn("changeset %s has unknown parent %s\n" %
1902 (short(n), short(p)))
1903 errors += 1
1904 try:
1905 changes = self.changelog.read(n)
1906 except Exception, inst:
1907 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1908 errors += 1
1909
1910 for f in changes[3]:
1911 filelinkrevs.setdefault(f, []).append(i)
1912
1913 seen = {}
1914 self.ui.status("checking manifests\n")
1915 for i in range(self.manifest.count()):
1916 n = self.manifest.node(i)
1917 if n in seen:
1918 self.ui.warn("duplicate manifest at revision %d\n" % i)
1919 errors += 1
1920 seen[n] = 1
1921
1922 for p in self.manifest.parents(n):
1923 if p not in self.manifest.nodemap:
1924 self.ui.warn("manifest %s has unknown parent %s\n" %
1925 (short(n), short(p)))
1926 errors += 1
1927
1928 try:
1929 delta = mdiff.patchtext(self.manifest.delta(n))
1930 except KeyboardInterrupt:
1931 self.ui.warn("aborted")
1932 sys.exit(0)
1933 except Exception, inst:
1934 self.ui.warn("unpacking manifest %s: %s\n"
1935 % (short(n), inst))
1936 errors += 1
1937
1938 ff = [ l.split('\0') for l in delta.splitlines() ]
1939 for f, fn in ff:
1940 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1941
1942 self.ui.status("crosschecking files in changesets and manifests\n")
1943 for f in filenodes:
1944 if f not in filelinkrevs:
1945 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1946 errors += 1
1947
1948 for f in filelinkrevs:
1949 if f not in filenodes:
1950 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1951 errors += 1
1952
1953 self.ui.status("checking files\n")
1954 ff = filenodes.keys()
1955 ff.sort()
1956 for f in ff:
1957 if f == "/dev/null": continue
1958 files += 1
1959 fl = self.file(f)
1960 nodes = { nullid: 1 }
1961 seen = {}
1962 for i in range(fl.count()):
1963 revisions += 1
1964 n = fl.node(i)
1965
1966 if n in seen:
1967 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1968 errors += 1
1969
1970 if n not in filenodes[f]:
1971 self.ui.warn("%s: %d:%s not in manifests\n"
1972 % (f, i, short(n)))
1973 errors += 1
1974 else:
1975 del filenodes[f][n]
1976
1977 flr = fl.linkrev(n)
1978 if flr not in filelinkrevs[f]:
1979 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1980 % (f, short(n), fl.linkrev(n)))
1981 errors += 1
1982 else:
1983 filelinkrevs[f].remove(flr)
1984
1985 # verify contents
1986 try:
1987 t = fl.read(n)
1988 except Exception, inst:
1989 self.ui.warn("unpacking file %s %s: %s\n"
1990 % (f, short(n), inst))
1991 errors += 1
1992
1993 # verify parents
1994 (p1, p2) = fl.parents(n)
1995 if p1 not in nodes:
1996 self.ui.warn("file %s:%s unknown parent 1 %s" %
1997 (f, short(n), short(p1)))
1998 errors += 1
1999 if p2 not in nodes:
2000 self.ui.warn("file %s:%s unknown parent 2 %s" %
2001 (f, short(n), short(p1)))
2002 errors += 1
2003 nodes[n] = 1
2004
2005 # cross-check
2006 for node in filenodes[f]:
2007 self.ui.warn("node %s in manifests not in %s\n"
2008 % (hex(node), f))
2009 errors += 1
2010
2011 self.ui.status("%d files, %d changesets, %d total revisions\n" %
2012 (files, changesets, revisions))
2013
2014 if errors:
2015 self.ui.warn("%d integrity errors encountered!\n" % errors)
2016 return 1
2017
2018 class remoterepository:
2019 def local(self):
2020 return False
2021
2022 class httprepository(remoterepository):
2023 def __init__(self, ui, path):
2024 # fix missing / after hostname
2025 s = urlparse.urlsplit(path)
2026 partial = s[2]
2027 if not partial: partial = "/"
2028 self.url = urlparse.urlunsplit((s[0], s[1], partial, '', ''))
2029 self.ui = ui
2030 no_list = [ "localhost", "127.0.0.1" ]
2031 host = ui.config("http_proxy", "host")
2032 if host is None:
2033 host = os.environ.get("http_proxy")
2034 if host and host.startswith('http://'):
2035 host = host[7:]
2036 user = ui.config("http_proxy", "user")
2037 passwd = ui.config("http_proxy", "passwd")
2038 no = ui.config("http_proxy", "no")
2039 if no is None:
2040 no = os.environ.get("no_proxy")
2041 if no:
2042 no_list = no_list + no.split(",")
2043
2044 no_proxy = 0
2045 for h in no_list:
2046 if (path.startswith("http://" + h + "/") or
2047 path.startswith("http://" + h + ":") or
2048 path == "http://" + h):
2049 no_proxy = 1
2050
2051 # Note: urllib2 takes proxy values from the environment and those will
2052 # take precedence
2053 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
2054 try:
2055 if os.environ.has_key(env):
2056 del os.environ[env]
2057 except OSError:
2058 pass
2059
2060 proxy_handler = urllib2.BaseHandler()
2061 if host and not no_proxy:
2062 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
2063
2064 authinfo = None
2065 if user and passwd:
2066 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
2067 passmgr.add_password(None, host, user, passwd)
2068 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
2069
2070 opener = urllib2.build_opener(proxy_handler, authinfo)
2071 urllib2.install_opener(opener)
2072
2073 def dev(self):
2074 return -1
2075
2076 def do_cmd(self, cmd, **args):
2077 self.ui.debug("sending %s command\n" % cmd)
2078 q = {"cmd": cmd}
2079 q.update(args)
2080 qs = urllib.urlencode(q)
2081 cu = "%s?%s" % (self.url, qs)
2082 resp = urllib2.urlopen(cu)
2083 proto = resp.headers['content-type']
2084
2085 # accept old "text/plain" and "application/hg-changegroup" for now
2086 if not proto.startswith('application/mercurial') and \
2087 not proto.startswith('text/plain') and \
2088 not proto.startswith('application/hg-changegroup'):
2089 raise RepoError("'%s' does not appear to be an hg repository"
2090 % self.url)
2091
2092 if proto.startswith('application/mercurial'):
2093 version = proto[22:]
2094 if float(version) > 0.1:
2095 raise RepoError("'%s' uses newer protocol %s" %
2096 (self.url, version))
2097
2098 return resp
2099
2100 def heads(self):
2101 d = self.do_cmd("heads").read()
2102 try:
2103 return map(bin, d[:-1].split(" "))
2104 except:
2105 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
2106 raise
2107
2108 def branches(self, nodes):
2109 n = " ".join(map(hex, nodes))
2110 d = self.do_cmd("branches", nodes=n).read()
2111 try:
2112 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
2113 return br
2114 except:
2115 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
2116 raise
2117
2118 def between(self, pairs):
2119 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
2120 d = self.do_cmd("between", pairs=n).read()
2121 try:
2122 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
2123 return p
2124 except:
2125 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
2126 raise
2127
2128 def changegroup(self, nodes):
2129 n = " ".join(map(hex, nodes))
2130 f = self.do_cmd("changegroup", roots=n)
2131 bytes = 0
2132
2133 class zread:
2134 def __init__(self, f):
2135 self.zd = zlib.decompressobj()
2136 self.f = f
2137 self.buf = ""
2138 def read(self, l):
2139 while l > len(self.buf):
2140 r = self.f.read(4096)
2141 if r:
2142 self.buf += self.zd.decompress(r)
2143 else:
2144 self.buf += self.zd.flush()
2145 break
2146 d, self.buf = self.buf[:l], self.buf[l:]
2147 return d
2148
2149 return zread(f)
2150
2151 class remotelock:
2152 def __init__(self, repo):
2153 self.repo = repo
2154 def release(self):
2155 self.repo.unlock()
2156 self.repo = None
2157 def __del__(self):
2158 if self.repo:
2159 self.release()
2160
2161 class sshrepository(remoterepository):
2162 def __init__(self, ui, path):
2163 self.url = path
2164 self.ui = ui
2165
2166 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', path)
2167 if not m:
2168 raise RepoError("couldn't parse destination %s" % path)
2169
2170 self.user = m.group(2)
2171 self.host = m.group(3)
2172 self.port = m.group(5)
2173 self.path = m.group(7) or "."
2174
2175 args = self.user and ("%s@%s" % (self.user, self.host)) or self.host
2176 args = self.port and ("%s -p %s") % (args, self.port) or args
2177
2178 sshcmd = self.ui.config("ui", "ssh", "ssh")
2179 remotecmd = self.ui.config("ui", "remotecmd", "hg")
2180 cmd = "%s %s '%s -R %s serve --stdio'"
2181 cmd = cmd % (sshcmd, args, remotecmd, self.path)
2182
2183 self.pipeo, self.pipei, self.pipee = os.popen3(cmd)
2184
2185 def readerr(self):
2186 while 1:
2187 r,w,x = select.select([self.pipee], [], [], 0)
2188 if not r: break
2189 l = self.pipee.readline()
2190 if not l: break
2191 self.ui.status("remote: ", l)
2192
2193 def __del__(self):
2194 try:
2195 self.pipeo.close()
2196 self.pipei.close()
2197 for l in self.pipee:
2198 self.ui.status("remote: ", l)
2199 self.pipee.close()
2200 except:
2201 pass
2202
2203 def dev(self):
2204 return -1
2205
2206 def do_cmd(self, cmd, **args):
2207 self.ui.debug("sending %s command\n" % cmd)
2208 self.pipeo.write("%s\n" % cmd)
2209 for k, v in args.items():
2210 self.pipeo.write("%s %d\n" % (k, len(v)))
2211 self.pipeo.write(v)
2212 self.pipeo.flush()
2213
2214 return self.pipei
2215
2216 def call(self, cmd, **args):
2217 r = self.do_cmd(cmd, **args)
2218 l = r.readline()
2219 self.readerr()
2220 try:
2221 l = int(l)
2222 except:
2223 raise RepoError("unexpected response '%s'" % l)
2224 return r.read(l)
2225
2226 def lock(self):
2227 self.call("lock")
2228 return remotelock(self)
2229
2230 def unlock(self):
2231 self.call("unlock")
2232
2233 def heads(self):
2234 d = self.call("heads")
2235 try:
2236 return map(bin, d[:-1].split(" "))
2237 except:
2238 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
2239
2240 def branches(self, nodes):
2241 n = " ".join(map(hex, nodes))
2242 d = self.call("branches", nodes=n)
2243 try:
2244 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
2245 return br
2246 except:
2247 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
2248
2249 def between(self, pairs):
2250 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
2251 d = self.call("between", pairs=n)
2252 try:
2253 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
2254 return p
2255 except:
2256 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
2257
2258 def changegroup(self, nodes):
2259 n = " ".join(map(hex, nodes))
2260 f = self.do_cmd("changegroup", roots=n)
2261 return self.pipei
2262
2263 def addchangegroup(self, cg):
2264 d = self.call("addchangegroup")
2265 if d:
2266 raise RepoError("push refused: %s", d)
2267
2268 while 1:
2269 d = cg.read(4096)
2270 if not d: break
2271 self.pipeo.write(d)
2272 self.readerr()
2273
2274 self.pipeo.flush()
2275
2276 self.readerr()
2277 l = int(self.pipei.readline())
2278 return self.pipei.read(l) != ""
2279
2280 class httpsrepository(httprepository):
2281 pass
2282
2283 def repository(ui, path=None, create=0): 44 def repository(ui, path=None, create=0):
2284 if path: 45 if path:
2285 if path.startswith("http://"): 46 if path.startswith("http://"):
2286 return httprepository(ui, path) 47 return httprepo.httprepository(ui, path)
2287 if path.startswith("https://"): 48 if path.startswith("https://"):
2288 return httpsrepository(ui, path) 49 return httprepo.httpsrepository(ui, path)
2289 if path.startswith("hg://"): 50 if path.startswith("hg://"):
2290 return httprepository(ui, path.replace("hg://", "http://")) 51 return httprepo.httprepository(
52 ui, path.replace("hg://", "http://"))
2291 if path.startswith("old-http://"): 53 if path.startswith("old-http://"):
2292 return localrepository(ui, path.replace("old-http://", "http://")) 54 return localrepo.localrepository(
55 ui, opener, path.replace("old-http://", "http://"))
2293 if path.startswith("ssh://"): 56 if path.startswith("ssh://"):
2294 return sshrepository(ui, path) 57 return sshrepo.sshrepository(ui, path)
2295 58
2296 return localrepository(ui, path, create) 59 return localrepo.localrepository(ui, opener, path, create)