##// END OF EJS Templates
Make the appendfile class inline-data index friendly...
mason@suse.com -
r2075:343aeefb default
parent child Browse files
Show More
@@ -1,172 +1,194
1 # appendfile.py - special classes to make repo updates atomic
1 # appendfile.py - special classes to make repo updates atomic
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.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 from demandload import *
8 from demandload import *
9 demandload(globals(), "cStringIO changelog manifest os tempfile")
9 demandload(globals(), "cStringIO changelog manifest os tempfile")
10
10
11 # writes to metadata files are ordered. reads: changelog, manifest,
11 # writes to metadata files are ordered. reads: changelog, manifest,
12 # normal files. writes: normal files, manifest, changelog.
12 # normal files. writes: normal files, manifest, changelog.
13
13
14 # manifest contains pointers to offsets in normal files. changelog
14 # manifest contains pointers to offsets in normal files. changelog
15 # contains pointers to offsets in manifest. if reader reads old
15 # contains pointers to offsets in manifest. if reader reads old
16 # changelog while manifest or normal files are written, it has no
16 # changelog while manifest or normal files are written, it has no
17 # pointers into new parts of those files that are maybe not consistent
17 # pointers into new parts of those files that are maybe not consistent
18 # yet, so will not read them.
18 # yet, so will not read them.
19
19
20 # localrepo.addchangegroup thinks it writes changelog first, then
20 # localrepo.addchangegroup thinks it writes changelog first, then
21 # manifest, then normal files (this is order they are available, and
21 # manifest, then normal files (this is order they are available, and
22 # needed for computing linkrev fields), but uses appendfile to hide
22 # needed for computing linkrev fields), but uses appendfile to hide
23 # updates from readers. data not written to manifest or changelog
23 # updates from readers. data not written to manifest or changelog
24 # until all normal files updated. write manifest first, then
24 # until all normal files updated. write manifest first, then
25 # changelog.
25 # changelog.
26
26
27 # with this write ordering, readers cannot see inconsistent view of
27 # with this write ordering, readers cannot see inconsistent view of
28 # repo during update.
28 # repo during update.
29
29
30 class appendfile(object):
30 class appendfile(object):
31 '''implement enough of file protocol to append to revlog file.
31 '''implement enough of file protocol to append to revlog file.
32 appended data is written to temp file. reads and seeks span real
32 appended data is written to temp file. reads and seeks span real
33 file and temp file. readers cannot see appended data until
33 file and temp file. readers cannot see appended data until
34 writedata called.'''
34 writedata called.'''
35
35
36 def __init__(self, fp):
36 def __init__(self, fp):
37 fd, self.tmpname = tempfile.mkstemp()
37 fd, self.tmpname = tempfile.mkstemp()
38 self.tmpfp = os.fdopen(fd, 'ab+')
38 self.tmpfp = os.fdopen(fd, 'ab+')
39 self.realfp = fp
39 self.realfp = fp
40 self.offset = fp.tell()
40 self.offset = fp.tell()
41 # real file is not written by anyone else. cache its size so
41 # real file is not written by anyone else. cache its size so
42 # seek and read can be fast.
42 # seek and read can be fast.
43 self.fpsize = os.fstat(fp.fileno()).st_size
43 self.fpsize = os.fstat(fp.fileno()).st_size
44
44
45 def seek(self, offset):
45 def end(self):
46 self.tmpfp.flush() # make sure the stat is correct
47 return self.fpsize + os.fstat(self.tmpfp.fileno()).st_size
48
49 def seek(self, offset, whence=0):
46 '''virtual file offset spans real file and temp file.'''
50 '''virtual file offset spans real file and temp file.'''
51 if whence == 0:
47 self.offset = offset
52 self.offset = offset
53 elif whence == 1:
54 self.offset += offset
55 elif whence == 2:
56 self.offset = self.end() + offset
57
48 if self.offset < self.fpsize:
58 if self.offset < self.fpsize:
49 self.realfp.seek(self.offset)
59 self.realfp.seek(self.offset)
50 else:
60 else:
51 self.tmpfp.seek(self.offset - self.fpsize)
61 self.tmpfp.seek(self.offset - self.fpsize)
52
62
53 def read(self, count=-1):
63 def read(self, count=-1):
54 '''only trick here is reads that span real file and temp file.'''
64 '''only trick here is reads that span real file and temp file.'''
55 fp = cStringIO.StringIO()
65 fp = cStringIO.StringIO()
56 old_offset = self.offset
66 old_offset = self.offset
57 if self.offset < self.fpsize:
67 if self.offset < self.fpsize:
58 s = self.realfp.read(count)
68 s = self.realfp.read(count)
59 fp.write(s)
69 fp.write(s)
60 self.offset += len(s)
70 self.offset += len(s)
61 if count > 0:
71 if count > 0:
62 count -= len(s)
72 count -= len(s)
63 if count != 0:
73 if count != 0:
64 if old_offset != self.offset:
74 if old_offset != self.offset:
65 self.tmpfp.seek(self.offset - self.fpsize)
75 self.tmpfp.seek(self.offset - self.fpsize)
66 s = self.tmpfp.read(count)
76 s = self.tmpfp.read(count)
67 fp.write(s)
77 fp.write(s)
68 self.offset += len(s)
78 self.offset += len(s)
69 return fp.getvalue()
79 return fp.getvalue()
70
80
71 def write(self, s):
81 def write(self, s):
72 '''append to temp file.'''
82 '''append to temp file.'''
73 self.tmpfp.seek(0, 2)
83 self.tmpfp.seek(0, 2)
74 self.tmpfp.write(s)
84 self.tmpfp.write(s)
75 # all writes are appends, so offset must go to end of file.
85 # all writes are appends, so offset must go to end of file.
76 self.offset = self.fpsize + self.tmpfp.tell()
86 self.offset = self.fpsize + self.tmpfp.tell()
77
87
78 def writedata(self):
88 def writedata(self):
79 '''copy data from temp file to real file.'''
89 '''copy data from temp file to real file.'''
80 self.tmpfp.seek(0)
90 self.tmpfp.seek(0)
81 s = self.tmpfp.read()
91 s = self.tmpfp.read()
82 self.tmpfp.close()
92 self.tmpfp.close()
83 self.realfp.seek(0, 2)
93 self.realfp.seek(0, 2)
84 # small race here. we write all new data in one call, but
94 # small race here. we write all new data in one call, but
85 # reader can see partial update due to python or os. file
95 # reader can see partial update due to python or os. file
86 # locking no help: slow, not portable, not reliable over nfs.
96 # locking no help: slow, not portable, not reliable over nfs.
87 # only safe thing is write to temp file every time and rename,
97 # only safe thing is write to temp file every time and rename,
88 # but performance bad when manifest or changelog gets big.
98 # but performance bad when manifest or changelog gets big.
89 self.realfp.write(s)
99 self.realfp.write(s)
90 self.realfp.close()
100 self.realfp.close()
91
101
92 def __del__(self):
102 def __del__(self):
93 '''delete temp file even if exception raised.'''
103 '''delete temp file even if exception raised.'''
94 try: os.unlink(self.tmpname)
104 try: os.unlink(self.tmpname)
95 except: pass
105 except: pass
96
106
97 class sharedfile(object):
107 class sharedfile(object):
98 '''let file objects share a single appendfile safely. each
108 '''let file objects share a single appendfile safely. each
99 sharedfile has own offset, syncs up with appendfile offset before
109 sharedfile has own offset, syncs up with appendfile offset before
100 read and after read and write.'''
110 read and after read and write.'''
101
111
102 def __init__(self, fp):
112 def __init__(self, fp):
103 self.fp = fp
113 self.fp = fp
104 self.offset = 0
114 self.offset = 0
105
115
106 def seek(self, offset):
116 def tell(self):
117 return self.offset
118
119 def seek(self, offset, whence=0):
120 if whence == 0:
107 self.offset = offset
121 self.offset = offset
122 elif whence == 1:
123 self.offset += offset
124 elif whence == 2:
125 self.offset = self.fp.end() + offset
108
126
109 def read(self, count=-1):
127 def read(self, count=-1):
110 try:
128 try:
111 if self.offset != self.fp.offset:
129 if self.offset != self.fp.offset:
112 self.fp.seek(self.offset)
130 self.fp.seek(self.offset)
113 return self.fp.read(count)
131 return self.fp.read(count)
114 finally:
132 finally:
115 self.offset = self.fp.offset
133 self.offset = self.fp.offset
116
134
117 def write(self, s):
135 def write(self, s):
118 try:
136 try:
119 return self.fp.write(s)
137 return self.fp.write(s)
120 finally:
138 finally:
121 self.offset = self.fp.offset
139 self.offset = self.fp.offset
122
140
123 def close(self):
141 def close(self):
124 # revlog wants this.
142 # revlog wants this.
125 pass
143 pass
126
144
127 def flush(self):
145 def flush(self):
128 # revlog wants this.
146 # revlog wants this.
129 pass
147 pass
130
148
131 def writedata(self):
149 def writedata(self):
132 self.fp.writedata()
150 self.fp.writedata()
133
151
134 class appendopener(object):
152 class appendopener(object):
135 '''special opener for files that only read or append.'''
153 '''special opener for files that only read or append.'''
136
154
137 def __init__(self, opener):
155 def __init__(self, opener):
138 self.realopener = opener
156 self.realopener = opener
139 # key: file name, value: appendfile object
157 # key: file name, value: appendfile object
140 self.fps = {}
158 self.fps = {}
141
159
142 def __call__(self, name, mode='r'):
160 def __call__(self, name, mode='r'):
143 '''open file. return same cached appendfile object for every
161 '''open file. return same cached appendfile object for every
144 later call.'''
162 later call.'''
145
163
146 assert mode in 'ra'
164 assert mode in 'ra+'
147 fp = self.fps.get(name)
165 fp = self.fps.get(name)
148 if fp is None:
166 if fp is None:
149 fp = appendfile(self.realopener(name, 'a+'))
167 fp = appendfile(self.realopener(name, 'a+'))
150 self.fps[name] = fp
168 self.fps[name] = fp
151 return sharedfile(fp)
169 return sharedfile(fp)
152
170
153 def writedata(self):
171 def writedata(self):
154 '''copy data from temp files to real files.'''
172 '''copy data from temp files to real files.'''
155 # write .d file before .i file.
173 # write .d file before .i file.
156 fps = self.fps.items()
174 fps = self.fps.items()
157 fps.sort()
175 fps.sort()
158 for name, fp in fps:
176 for name, fp in fps:
159 fp.writedata()
177 fp.writedata()
160
178
161 # files for changelog and manifest are in different appendopeners, so
179 # files for changelog and manifest are in different appendopeners, so
162 # not mixed up together.
180 # not mixed up together.
163
181
164 class appendchangelog(changelog.changelog, appendopener):
182 class appendchangelog(changelog.changelog, appendopener):
165 def __init__(self, opener):
183 def __init__(self, opener):
166 appendopener.__init__(self, opener)
184 appendopener.__init__(self, opener)
167 changelog.changelog.__init__(self, self)
185 changelog.changelog.__init__(self, self)
186 def checkinlinesize(self, fp, tr):
187 return
168
188
169 class appendmanifest(manifest.manifest, appendopener):
189 class appendmanifest(manifest.manifest, appendopener):
170 def __init__(self, opener):
190 def __init__(self, opener):
171 appendopener.__init__(self, opener)
191 appendopener.__init__(self, opener)
172 manifest.manifest.__init__(self, self)
192 manifest.manifest.__init__(self, self)
193 def checkinlinesize(self, fp, tr):
194 return
@@ -1,1974 +1,1977
1 # localrepo.py - read/write repository class for mercurial
1 # localrepo.py - read/write repository class for mercurial
2 #
2 #
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 os, util
8 import os, util
9 import filelog, manifest, changelog, dirstate, repo
9 import filelog, manifest, changelog, dirstate, repo
10 from node import *
10 from node import *
11 from i18n import gettext as _
11 from i18n import gettext as _
12 from demandload import *
12 from demandload import *
13 demandload(globals(), "appendfile changegroup")
13 demandload(globals(), "appendfile changegroup")
14 demandload(globals(), "re lock transaction tempfile stat mdiff errno ui revlog")
14 demandload(globals(), "re lock transaction tempfile stat mdiff errno ui revlog")
15
15
16 class localrepository(object):
16 class localrepository(object):
17 def __del__(self):
17 def __del__(self):
18 self.transhandle = None
18 self.transhandle = None
19 def __init__(self, parentui, path=None, create=0):
19 def __init__(self, parentui, path=None, create=0):
20 if not path:
20 if not path:
21 p = os.getcwd()
21 p = os.getcwd()
22 while not os.path.isdir(os.path.join(p, ".hg")):
22 while not os.path.isdir(os.path.join(p, ".hg")):
23 oldp = p
23 oldp = p
24 p = os.path.dirname(p)
24 p = os.path.dirname(p)
25 if p == oldp:
25 if p == oldp:
26 raise repo.RepoError(_("no repo found"))
26 raise repo.RepoError(_("no repo found"))
27 path = p
27 path = p
28 self.path = os.path.join(path, ".hg")
28 self.path = os.path.join(path, ".hg")
29
29
30 if not create and not os.path.isdir(self.path):
30 if not create and not os.path.isdir(self.path):
31 raise repo.RepoError(_("repository %s not found") % path)
31 raise repo.RepoError(_("repository %s not found") % path)
32
32
33 self.root = os.path.abspath(path)
33 self.root = os.path.abspath(path)
34 self.origroot = path
34 self.origroot = path
35 self.ui = ui.ui(parentui=parentui)
35 self.ui = ui.ui(parentui=parentui)
36 self.opener = util.opener(self.path)
36 self.opener = util.opener(self.path)
37 self.wopener = util.opener(self.root)
37 self.wopener = util.opener(self.root)
38
38
39 try:
39 try:
40 self.ui.readconfig(self.join("hgrc"), self.root)
40 self.ui.readconfig(self.join("hgrc"), self.root)
41 except IOError:
41 except IOError:
42 pass
42 pass
43
43
44 v = self.ui.revlogopts
44 v = self.ui.revlogopts
45 self.revlogversion = int(v.get('format', 0))
45 self.revlogversion = int(v.get('format', 0))
46 flags = 0
46 flags = 0
47 for x in v.get('flags', "").split():
47 for x in v.get('flags', "").split():
48 flags |= revlog.flagstr(x)
48 flags |= revlog.flagstr(x)
49
49
50 v = self.revlogversion | flags
50 v = self.revlogversion | flags
51 self.manifest = manifest.manifest(self.opener, v)
51 self.manifest = manifest.manifest(self.opener, v)
52 self.changelog = changelog.changelog(self.opener, v)
52 self.changelog = changelog.changelog(self.opener, v)
53
53
54 # the changelog might not have the inline index flag
54 # the changelog might not have the inline index flag
55 # on. If the format of the changelog is the same as found in
55 # on. If the format of the changelog is the same as found in
56 # .hgrc, apply any flags found in the .hgrc as well.
56 # .hgrc, apply any flags found in the .hgrc as well.
57 # Otherwise, just version from the changelog
57 # Otherwise, just version from the changelog
58 v = self.changelog.version
58 v = self.changelog.version
59 if v == self.revlogversion:
59 if v == self.revlogversion:
60 v |= flags
60 v |= flags
61 self.revlogversion = v
61 self.revlogversion = v
62
62
63 self.tagscache = None
63 self.tagscache = None
64 self.nodetagscache = None
64 self.nodetagscache = None
65 self.encodepats = None
65 self.encodepats = None
66 self.decodepats = None
66 self.decodepats = None
67 self.transhandle = None
67 self.transhandle = None
68
68
69 if create:
69 if create:
70 os.mkdir(self.path)
70 os.mkdir(self.path)
71 os.mkdir(self.join("data"))
71 os.mkdir(self.join("data"))
72
72
73 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
73 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
74 def hook(self, name, throw=False, **args):
74 def hook(self, name, throw=False, **args):
75 def runhook(name, cmd):
75 def runhook(name, cmd):
76 self.ui.note(_("running hook %s: %s\n") % (name, cmd))
76 self.ui.note(_("running hook %s: %s\n") % (name, cmd))
77 env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()] +
77 env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()] +
78 [(k.upper(), v) for k, v in args.iteritems()])
78 [(k.upper(), v) for k, v in args.iteritems()])
79 r = util.system(cmd, environ=env, cwd=self.root)
79 r = util.system(cmd, environ=env, cwd=self.root)
80 if r:
80 if r:
81 desc, r = util.explain_exit(r)
81 desc, r = util.explain_exit(r)
82 if throw:
82 if throw:
83 raise util.Abort(_('%s hook %s') % (name, desc))
83 raise util.Abort(_('%s hook %s') % (name, desc))
84 self.ui.warn(_('error: %s hook %s\n') % (name, desc))
84 self.ui.warn(_('error: %s hook %s\n') % (name, desc))
85 return False
85 return False
86 return True
86 return True
87
87
88 r = True
88 r = True
89 hooks = [(hname, cmd) for hname, cmd in self.ui.configitems("hooks")
89 hooks = [(hname, cmd) for hname, cmd in self.ui.configitems("hooks")
90 if hname.split(".", 1)[0] == name and cmd]
90 if hname.split(".", 1)[0] == name and cmd]
91 hooks.sort()
91 hooks.sort()
92 for hname, cmd in hooks:
92 for hname, cmd in hooks:
93 r = runhook(hname, cmd) and r
93 r = runhook(hname, cmd) and r
94 return r
94 return r
95
95
96 def tags(self):
96 def tags(self):
97 '''return a mapping of tag to node'''
97 '''return a mapping of tag to node'''
98 if not self.tagscache:
98 if not self.tagscache:
99 self.tagscache = {}
99 self.tagscache = {}
100
100
101 def parsetag(line, context):
101 def parsetag(line, context):
102 if not line:
102 if not line:
103 return
103 return
104 s = l.split(" ", 1)
104 s = l.split(" ", 1)
105 if len(s) != 2:
105 if len(s) != 2:
106 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
106 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
107 return
107 return
108 node, key = s
108 node, key = s
109 try:
109 try:
110 bin_n = bin(node)
110 bin_n = bin(node)
111 except TypeError:
111 except TypeError:
112 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
112 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
113 return
113 return
114 if bin_n not in self.changelog.nodemap:
114 if bin_n not in self.changelog.nodemap:
115 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
115 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
116 return
116 return
117 self.tagscache[key.strip()] = bin_n
117 self.tagscache[key.strip()] = bin_n
118
118
119 # read each head of the tags file, ending with the tip
119 # read each head of the tags file, ending with the tip
120 # and add each tag found to the map, with "newer" ones
120 # and add each tag found to the map, with "newer" ones
121 # taking precedence
121 # taking precedence
122 fl = self.file(".hgtags")
122 fl = self.file(".hgtags")
123 h = fl.heads()
123 h = fl.heads()
124 h.reverse()
124 h.reverse()
125 for r in h:
125 for r in h:
126 count = 0
126 count = 0
127 for l in fl.read(r).splitlines():
127 for l in fl.read(r).splitlines():
128 count += 1
128 count += 1
129 parsetag(l, ".hgtags:%d" % count)
129 parsetag(l, ".hgtags:%d" % count)
130
130
131 try:
131 try:
132 f = self.opener("localtags")
132 f = self.opener("localtags")
133 count = 0
133 count = 0
134 for l in f:
134 for l in f:
135 count += 1
135 count += 1
136 parsetag(l, "localtags:%d" % count)
136 parsetag(l, "localtags:%d" % count)
137 except IOError:
137 except IOError:
138 pass
138 pass
139
139
140 self.tagscache['tip'] = self.changelog.tip()
140 self.tagscache['tip'] = self.changelog.tip()
141
141
142 return self.tagscache
142 return self.tagscache
143
143
144 def tagslist(self):
144 def tagslist(self):
145 '''return a list of tags ordered by revision'''
145 '''return a list of tags ordered by revision'''
146 l = []
146 l = []
147 for t, n in self.tags().items():
147 for t, n in self.tags().items():
148 try:
148 try:
149 r = self.changelog.rev(n)
149 r = self.changelog.rev(n)
150 except:
150 except:
151 r = -2 # sort to the beginning of the list if unknown
151 r = -2 # sort to the beginning of the list if unknown
152 l.append((r, t, n))
152 l.append((r, t, n))
153 l.sort()
153 l.sort()
154 return [(t, n) for r, t, n in l]
154 return [(t, n) for r, t, n in l]
155
155
156 def nodetags(self, node):
156 def nodetags(self, node):
157 '''return the tags associated with a node'''
157 '''return the tags associated with a node'''
158 if not self.nodetagscache:
158 if not self.nodetagscache:
159 self.nodetagscache = {}
159 self.nodetagscache = {}
160 for t, n in self.tags().items():
160 for t, n in self.tags().items():
161 self.nodetagscache.setdefault(n, []).append(t)
161 self.nodetagscache.setdefault(n, []).append(t)
162 return self.nodetagscache.get(node, [])
162 return self.nodetagscache.get(node, [])
163
163
164 def lookup(self, key):
164 def lookup(self, key):
165 try:
165 try:
166 return self.tags()[key]
166 return self.tags()[key]
167 except KeyError:
167 except KeyError:
168 try:
168 try:
169 return self.changelog.lookup(key)
169 return self.changelog.lookup(key)
170 except:
170 except:
171 raise
171 raise repo.RepoError(_("unknown revision '%s'") % key)
172 raise repo.RepoError(_("unknown revision '%s'") % key)
172
173
173 def dev(self):
174 def dev(self):
174 return os.stat(self.path).st_dev
175 return os.stat(self.path).st_dev
175
176
176 def local(self):
177 def local(self):
177 return True
178 return True
178
179
179 def join(self, f):
180 def join(self, f):
180 return os.path.join(self.path, f)
181 return os.path.join(self.path, f)
181
182
182 def wjoin(self, f):
183 def wjoin(self, f):
183 return os.path.join(self.root, f)
184 return os.path.join(self.root, f)
184
185
185 def file(self, f):
186 def file(self, f):
186 if f[0] == '/':
187 if f[0] == '/':
187 f = f[1:]
188 f = f[1:]
188 return filelog.filelog(self.opener, f, self.revlogversion)
189 return filelog.filelog(self.opener, f, self.revlogversion)
189
190
190 def getcwd(self):
191 def getcwd(self):
191 return self.dirstate.getcwd()
192 return self.dirstate.getcwd()
192
193
193 def wfile(self, f, mode='r'):
194 def wfile(self, f, mode='r'):
194 return self.wopener(f, mode)
195 return self.wopener(f, mode)
195
196
196 def wread(self, filename):
197 def wread(self, filename):
197 if self.encodepats == None:
198 if self.encodepats == None:
198 l = []
199 l = []
199 for pat, cmd in self.ui.configitems("encode"):
200 for pat, cmd in self.ui.configitems("encode"):
200 mf = util.matcher(self.root, "", [pat], [], [])[1]
201 mf = util.matcher(self.root, "", [pat], [], [])[1]
201 l.append((mf, cmd))
202 l.append((mf, cmd))
202 self.encodepats = l
203 self.encodepats = l
203
204
204 data = self.wopener(filename, 'r').read()
205 data = self.wopener(filename, 'r').read()
205
206
206 for mf, cmd in self.encodepats:
207 for mf, cmd in self.encodepats:
207 if mf(filename):
208 if mf(filename):
208 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
209 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
209 data = util.filter(data, cmd)
210 data = util.filter(data, cmd)
210 break
211 break
211
212
212 return data
213 return data
213
214
214 def wwrite(self, filename, data, fd=None):
215 def wwrite(self, filename, data, fd=None):
215 if self.decodepats == None:
216 if self.decodepats == None:
216 l = []
217 l = []
217 for pat, cmd in self.ui.configitems("decode"):
218 for pat, cmd in self.ui.configitems("decode"):
218 mf = util.matcher(self.root, "", [pat], [], [])[1]
219 mf = util.matcher(self.root, "", [pat], [], [])[1]
219 l.append((mf, cmd))
220 l.append((mf, cmd))
220 self.decodepats = l
221 self.decodepats = l
221
222
222 for mf, cmd in self.decodepats:
223 for mf, cmd in self.decodepats:
223 if mf(filename):
224 if mf(filename):
224 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
225 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
225 data = util.filter(data, cmd)
226 data = util.filter(data, cmd)
226 break
227 break
227
228
228 if fd:
229 if fd:
229 return fd.write(data)
230 return fd.write(data)
230 return self.wopener(filename, 'w').write(data)
231 return self.wopener(filename, 'w').write(data)
231
232
232 def transaction(self):
233 def transaction(self):
233 tr = self.transhandle
234 tr = self.transhandle
234 if tr != None and tr.running():
235 if tr != None and tr.running():
235 return tr.nest()
236 return tr.nest()
236
237
237 # save dirstate for undo
238 # save dirstate for undo
238 try:
239 try:
239 ds = self.opener("dirstate").read()
240 ds = self.opener("dirstate").read()
240 except IOError:
241 except IOError:
241 ds = ""
242 ds = ""
242 self.opener("journal.dirstate", "w").write(ds)
243 self.opener("journal.dirstate", "w").write(ds)
243
244
244 tr = transaction.transaction(self.ui.warn, self.opener,
245 tr = transaction.transaction(self.ui.warn, self.opener,
245 self.join("journal"),
246 self.join("journal"),
246 aftertrans(self.path))
247 aftertrans(self.path))
247 self.transhandle = tr
248 self.transhandle = tr
248 return tr
249 return tr
249
250
250 def recover(self):
251 def recover(self):
251 l = self.lock()
252 l = self.lock()
252 if os.path.exists(self.join("journal")):
253 if os.path.exists(self.join("journal")):
253 self.ui.status(_("rolling back interrupted transaction\n"))
254 self.ui.status(_("rolling back interrupted transaction\n"))
254 transaction.rollback(self.opener, self.join("journal"))
255 transaction.rollback(self.opener, self.join("journal"))
255 self.reload()
256 self.reload()
256 return True
257 return True
257 else:
258 else:
258 self.ui.warn(_("no interrupted transaction available\n"))
259 self.ui.warn(_("no interrupted transaction available\n"))
259 return False
260 return False
260
261
261 def undo(self, wlock=None):
262 def undo(self, wlock=None):
262 if not wlock:
263 if not wlock:
263 wlock = self.wlock()
264 wlock = self.wlock()
264 l = self.lock()
265 l = self.lock()
265 if os.path.exists(self.join("undo")):
266 if os.path.exists(self.join("undo")):
266 self.ui.status(_("rolling back last transaction\n"))
267 self.ui.status(_("rolling back last transaction\n"))
267 transaction.rollback(self.opener, self.join("undo"))
268 transaction.rollback(self.opener, self.join("undo"))
268 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
269 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
269 self.reload()
270 self.reload()
270 self.wreload()
271 self.wreload()
271 else:
272 else:
272 self.ui.warn(_("no undo information available\n"))
273 self.ui.warn(_("no undo information available\n"))
273
274
274 def wreload(self):
275 def wreload(self):
275 self.dirstate.read()
276 self.dirstate.read()
276
277
277 def reload(self):
278 def reload(self):
278 self.changelog.load()
279 self.changelog.load()
279 self.manifest.load()
280 self.manifest.load()
280 self.tagscache = None
281 self.tagscache = None
281 self.nodetagscache = None
282 self.nodetagscache = None
282
283
283 def do_lock(self, lockname, wait, releasefn=None, acquirefn=None,
284 def do_lock(self, lockname, wait, releasefn=None, acquirefn=None,
284 desc=None):
285 desc=None):
285 try:
286 try:
286 l = lock.lock(self.join(lockname), 0, releasefn, desc=desc)
287 l = lock.lock(self.join(lockname), 0, releasefn, desc=desc)
287 except lock.LockHeld, inst:
288 except lock.LockHeld, inst:
288 if not wait:
289 if not wait:
289 raise
290 raise
290 self.ui.warn(_("waiting for lock on %s held by %s\n") %
291 self.ui.warn(_("waiting for lock on %s held by %s\n") %
291 (desc, inst.args[0]))
292 (desc, inst.args[0]))
292 # default to 600 seconds timeout
293 # default to 600 seconds timeout
293 l = lock.lock(self.join(lockname),
294 l = lock.lock(self.join(lockname),
294 int(self.ui.config("ui", "timeout") or 600),
295 int(self.ui.config("ui", "timeout") or 600),
295 releasefn, desc=desc)
296 releasefn, desc=desc)
296 if acquirefn:
297 if acquirefn:
297 acquirefn()
298 acquirefn()
298 return l
299 return l
299
300
300 def lock(self, wait=1):
301 def lock(self, wait=1):
301 return self.do_lock("lock", wait, acquirefn=self.reload,
302 return self.do_lock("lock", wait, acquirefn=self.reload,
302 desc=_('repository %s') % self.origroot)
303 desc=_('repository %s') % self.origroot)
303
304
304 def wlock(self, wait=1):
305 def wlock(self, wait=1):
305 return self.do_lock("wlock", wait, self.dirstate.write,
306 return self.do_lock("wlock", wait, self.dirstate.write,
306 self.wreload,
307 self.wreload,
307 desc=_('working directory of %s') % self.origroot)
308 desc=_('working directory of %s') % self.origroot)
308
309
309 def checkfilemerge(self, filename, text, filelog, manifest1, manifest2):
310 def checkfilemerge(self, filename, text, filelog, manifest1, manifest2):
310 "determine whether a new filenode is needed"
311 "determine whether a new filenode is needed"
311 fp1 = manifest1.get(filename, nullid)
312 fp1 = manifest1.get(filename, nullid)
312 fp2 = manifest2.get(filename, nullid)
313 fp2 = manifest2.get(filename, nullid)
313
314
314 if fp2 != nullid:
315 if fp2 != nullid:
315 # is one parent an ancestor of the other?
316 # is one parent an ancestor of the other?
316 fpa = filelog.ancestor(fp1, fp2)
317 fpa = filelog.ancestor(fp1, fp2)
317 if fpa == fp1:
318 if fpa == fp1:
318 fp1, fp2 = fp2, nullid
319 fp1, fp2 = fp2, nullid
319 elif fpa == fp2:
320 elif fpa == fp2:
320 fp2 = nullid
321 fp2 = nullid
321
322
322 # is the file unmodified from the parent? report existing entry
323 # is the file unmodified from the parent? report existing entry
323 if fp2 == nullid and text == filelog.read(fp1):
324 if fp2 == nullid and text == filelog.read(fp1):
324 return (fp1, None, None)
325 return (fp1, None, None)
325
326
326 return (None, fp1, fp2)
327 return (None, fp1, fp2)
327
328
328 def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None):
329 def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None):
329 orig_parent = self.dirstate.parents()[0] or nullid
330 orig_parent = self.dirstate.parents()[0] or nullid
330 p1 = p1 or self.dirstate.parents()[0] or nullid
331 p1 = p1 or self.dirstate.parents()[0] or nullid
331 p2 = p2 or self.dirstate.parents()[1] or nullid
332 p2 = p2 or self.dirstate.parents()[1] or nullid
332 c1 = self.changelog.read(p1)
333 c1 = self.changelog.read(p1)
333 c2 = self.changelog.read(p2)
334 c2 = self.changelog.read(p2)
334 m1 = self.manifest.read(c1[0])
335 m1 = self.manifest.read(c1[0])
335 mf1 = self.manifest.readflags(c1[0])
336 mf1 = self.manifest.readflags(c1[0])
336 m2 = self.manifest.read(c2[0])
337 m2 = self.manifest.read(c2[0])
337 changed = []
338 changed = []
338
339
339 if orig_parent == p1:
340 if orig_parent == p1:
340 update_dirstate = 1
341 update_dirstate = 1
341 else:
342 else:
342 update_dirstate = 0
343 update_dirstate = 0
343
344
344 if not wlock:
345 if not wlock:
345 wlock = self.wlock()
346 wlock = self.wlock()
346 l = self.lock()
347 l = self.lock()
347 tr = self.transaction()
348 tr = self.transaction()
348 mm = m1.copy()
349 mm = m1.copy()
349 mfm = mf1.copy()
350 mfm = mf1.copy()
350 linkrev = self.changelog.count()
351 linkrev = self.changelog.count()
351 for f in files:
352 for f in files:
352 try:
353 try:
353 t = self.wread(f)
354 t = self.wread(f)
354 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
355 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
355 r = self.file(f)
356 r = self.file(f)
356 mfm[f] = tm
357 mfm[f] = tm
357
358
358 (entry, fp1, fp2) = self.checkfilemerge(f, t, r, m1, m2)
359 (entry, fp1, fp2) = self.checkfilemerge(f, t, r, m1, m2)
359 if entry:
360 if entry:
360 mm[f] = entry
361 mm[f] = entry
361 continue
362 continue
362
363
363 mm[f] = r.add(t, {}, tr, linkrev, fp1, fp2)
364 mm[f] = r.add(t, {}, tr, linkrev, fp1, fp2)
364 changed.append(f)
365 changed.append(f)
365 if update_dirstate:
366 if update_dirstate:
366 self.dirstate.update([f], "n")
367 self.dirstate.update([f], "n")
367 except IOError:
368 except IOError:
368 try:
369 try:
369 del mm[f]
370 del mm[f]
370 del mfm[f]
371 del mfm[f]
371 if update_dirstate:
372 if update_dirstate:
372 self.dirstate.forget([f])
373 self.dirstate.forget([f])
373 except:
374 except:
374 # deleted from p2?
375 # deleted from p2?
375 pass
376 pass
376
377
377 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
378 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
378 user = user or self.ui.username()
379 user = user or self.ui.username()
379 n = self.changelog.add(mnode, changed, text, tr, p1, p2, user, date)
380 n = self.changelog.add(mnode, changed, text, tr, p1, p2, user, date)
380 tr.close()
381 tr.close()
381 if update_dirstate:
382 if update_dirstate:
382 self.dirstate.setparents(n, nullid)
383 self.dirstate.setparents(n, nullid)
383
384
384 def commit(self, files=None, text="", user=None, date=None,
385 def commit(self, files=None, text="", user=None, date=None,
385 match=util.always, force=False, lock=None, wlock=None):
386 match=util.always, force=False, lock=None, wlock=None):
386 commit = []
387 commit = []
387 remove = []
388 remove = []
388 changed = []
389 changed = []
389
390
390 if files:
391 if files:
391 for f in files:
392 for f in files:
392 s = self.dirstate.state(f)
393 s = self.dirstate.state(f)
393 if s in 'nmai':
394 if s in 'nmai':
394 commit.append(f)
395 commit.append(f)
395 elif s == 'r':
396 elif s == 'r':
396 remove.append(f)
397 remove.append(f)
397 else:
398 else:
398 self.ui.warn(_("%s not tracked!\n") % f)
399 self.ui.warn(_("%s not tracked!\n") % f)
399 else:
400 else:
400 modified, added, removed, deleted, unknown = self.changes(match=match)
401 modified, added, removed, deleted, unknown = self.changes(match=match)
401 commit = modified + added
402 commit = modified + added
402 remove = removed
403 remove = removed
403
404
404 p1, p2 = self.dirstate.parents()
405 p1, p2 = self.dirstate.parents()
405 c1 = self.changelog.read(p1)
406 c1 = self.changelog.read(p1)
406 c2 = self.changelog.read(p2)
407 c2 = self.changelog.read(p2)
407 m1 = self.manifest.read(c1[0])
408 m1 = self.manifest.read(c1[0])
408 mf1 = self.manifest.readflags(c1[0])
409 mf1 = self.manifest.readflags(c1[0])
409 m2 = self.manifest.read(c2[0])
410 m2 = self.manifest.read(c2[0])
410
411
411 if not commit and not remove and not force and p2 == nullid:
412 if not commit and not remove and not force and p2 == nullid:
412 self.ui.status(_("nothing changed\n"))
413 self.ui.status(_("nothing changed\n"))
413 return None
414 return None
414
415
415 xp1 = hex(p1)
416 xp1 = hex(p1)
416 if p2 == nullid: xp2 = ''
417 if p2 == nullid: xp2 = ''
417 else: xp2 = hex(p2)
418 else: xp2 = hex(p2)
418
419
419 self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
420 self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
420
421
421 if not wlock:
422 if not wlock:
422 wlock = self.wlock()
423 wlock = self.wlock()
423 if not lock:
424 if not lock:
424 lock = self.lock()
425 lock = self.lock()
425 tr = self.transaction()
426 tr = self.transaction()
426
427
427 # check in files
428 # check in files
428 new = {}
429 new = {}
429 linkrev = self.changelog.count()
430 linkrev = self.changelog.count()
430 commit.sort()
431 commit.sort()
431 for f in commit:
432 for f in commit:
432 self.ui.note(f + "\n")
433 self.ui.note(f + "\n")
433 try:
434 try:
434 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
435 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
435 t = self.wread(f)
436 t = self.wread(f)
436 except IOError:
437 except IOError:
437 self.ui.warn(_("trouble committing %s!\n") % f)
438 self.ui.warn(_("trouble committing %s!\n") % f)
438 raise
439 raise
439
440
440 r = self.file(f)
441 r = self.file(f)
441
442
442 meta = {}
443 meta = {}
443 cp = self.dirstate.copied(f)
444 cp = self.dirstate.copied(f)
444 if cp:
445 if cp:
445 meta["copy"] = cp
446 meta["copy"] = cp
446 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
447 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
447 self.ui.debug(_(" %s: copy %s:%s\n") % (f, cp, meta["copyrev"]))
448 self.ui.debug(_(" %s: copy %s:%s\n") % (f, cp, meta["copyrev"]))
448 fp1, fp2 = nullid, nullid
449 fp1, fp2 = nullid, nullid
449 else:
450 else:
450 entry, fp1, fp2 = self.checkfilemerge(f, t, r, m1, m2)
451 entry, fp1, fp2 = self.checkfilemerge(f, t, r, m1, m2)
451 if entry:
452 if entry:
452 new[f] = entry
453 new[f] = entry
453 continue
454 continue
454
455
455 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
456 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
456 # remember what we've added so that we can later calculate
457 # remember what we've added so that we can later calculate
457 # the files to pull from a set of changesets
458 # the files to pull from a set of changesets
458 changed.append(f)
459 changed.append(f)
459
460
460 # update manifest
461 # update manifest
461 m1 = m1.copy()
462 m1 = m1.copy()
462 m1.update(new)
463 m1.update(new)
463 for f in remove:
464 for f in remove:
464 if f in m1:
465 if f in m1:
465 del m1[f]
466 del m1[f]
466 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0],
467 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0],
467 (new, remove))
468 (new, remove))
468
469
469 # add changeset
470 # add changeset
470 new = new.keys()
471 new = new.keys()
471 new.sort()
472 new.sort()
472
473
473 user = user or self.ui.username()
474 user = user or self.ui.username()
474 if not text:
475 if not text:
475 edittext = [""]
476 edittext = [""]
476 if p2 != nullid:
477 if p2 != nullid:
477 edittext.append("HG: branch merge")
478 edittext.append("HG: branch merge")
478 edittext.extend(["HG: changed %s" % f for f in changed])
479 edittext.extend(["HG: changed %s" % f for f in changed])
479 edittext.extend(["HG: removed %s" % f for f in remove])
480 edittext.extend(["HG: removed %s" % f for f in remove])
480 if not changed and not remove:
481 if not changed and not remove:
481 edittext.append("HG: no files changed")
482 edittext.append("HG: no files changed")
482 edittext.append("")
483 edittext.append("")
483 # run editor in the repository root
484 # run editor in the repository root
484 olddir = os.getcwd()
485 olddir = os.getcwd()
485 os.chdir(self.root)
486 os.chdir(self.root)
486 edittext = self.ui.edit("\n".join(edittext), user)
487 edittext = self.ui.edit("\n".join(edittext), user)
487 os.chdir(olddir)
488 os.chdir(olddir)
488 if not edittext.rstrip():
489 if not edittext.rstrip():
489 return None
490 return None
490 text = edittext
491 text = edittext
491
492
492 n = self.changelog.add(mn, changed + remove, text, tr, p1, p2, user, date)
493 n = self.changelog.add(mn, changed + remove, text, tr, p1, p2, user, date)
493 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
494 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
494 parent2=xp2)
495 parent2=xp2)
495 tr.close()
496 tr.close()
496
497
497 self.dirstate.setparents(n)
498 self.dirstate.setparents(n)
498 self.dirstate.update(new, "n")
499 self.dirstate.update(new, "n")
499 self.dirstate.forget(remove)
500 self.dirstate.forget(remove)
500
501
501 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
502 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
502 return n
503 return n
503
504
504 def walk(self, node=None, files=[], match=util.always, badmatch=None):
505 def walk(self, node=None, files=[], match=util.always, badmatch=None):
505 if node:
506 if node:
506 fdict = dict.fromkeys(files)
507 fdict = dict.fromkeys(files)
507 for fn in self.manifest.read(self.changelog.read(node)[0]):
508 for fn in self.manifest.read(self.changelog.read(node)[0]):
508 fdict.pop(fn, None)
509 fdict.pop(fn, None)
509 if match(fn):
510 if match(fn):
510 yield 'm', fn
511 yield 'm', fn
511 for fn in fdict:
512 for fn in fdict:
512 if badmatch and badmatch(fn):
513 if badmatch and badmatch(fn):
513 if match(fn):
514 if match(fn):
514 yield 'b', fn
515 yield 'b', fn
515 else:
516 else:
516 self.ui.warn(_('%s: No such file in rev %s\n') % (
517 self.ui.warn(_('%s: No such file in rev %s\n') % (
517 util.pathto(self.getcwd(), fn), short(node)))
518 util.pathto(self.getcwd(), fn), short(node)))
518 else:
519 else:
519 for src, fn in self.dirstate.walk(files, match, badmatch=badmatch):
520 for src, fn in self.dirstate.walk(files, match, badmatch=badmatch):
520 yield src, fn
521 yield src, fn
521
522
522 def changes(self, node1=None, node2=None, files=[], match=util.always,
523 def changes(self, node1=None, node2=None, files=[], match=util.always,
523 wlock=None, show_ignored=None):
524 wlock=None, show_ignored=None):
524 """return changes between two nodes or node and working directory
525 """return changes between two nodes or node and working directory
525
526
526 If node1 is None, use the first dirstate parent instead.
527 If node1 is None, use the first dirstate parent instead.
527 If node2 is None, compare node1 with working directory.
528 If node2 is None, compare node1 with working directory.
528 """
529 """
529
530
530 def fcmp(fn, mf):
531 def fcmp(fn, mf):
531 t1 = self.wread(fn)
532 t1 = self.wread(fn)
532 t2 = self.file(fn).read(mf.get(fn, nullid))
533 t2 = self.file(fn).read(mf.get(fn, nullid))
533 return cmp(t1, t2)
534 return cmp(t1, t2)
534
535
535 def mfmatches(node):
536 def mfmatches(node):
536 change = self.changelog.read(node)
537 change = self.changelog.read(node)
537 mf = dict(self.manifest.read(change[0]))
538 mf = dict(self.manifest.read(change[0]))
538 for fn in mf.keys():
539 for fn in mf.keys():
539 if not match(fn):
540 if not match(fn):
540 del mf[fn]
541 del mf[fn]
541 return mf
542 return mf
542
543
543 if node1:
544 if node1:
544 # read the manifest from node1 before the manifest from node2,
545 # read the manifest from node1 before the manifest from node2,
545 # so that we'll hit the manifest cache if we're going through
546 # so that we'll hit the manifest cache if we're going through
546 # all the revisions in parent->child order.
547 # all the revisions in parent->child order.
547 mf1 = mfmatches(node1)
548 mf1 = mfmatches(node1)
548
549
549 # are we comparing the working directory?
550 # are we comparing the working directory?
550 if not node2:
551 if not node2:
551 if not wlock:
552 if not wlock:
552 try:
553 try:
553 wlock = self.wlock(wait=0)
554 wlock = self.wlock(wait=0)
554 except lock.LockException:
555 except lock.LockException:
555 wlock = None
556 wlock = None
556 lookup, modified, added, removed, deleted, unknown, ignored = (
557 lookup, modified, added, removed, deleted, unknown, ignored = (
557 self.dirstate.changes(files, match, show_ignored))
558 self.dirstate.changes(files, match, show_ignored))
558
559
559 # are we comparing working dir against its parent?
560 # are we comparing working dir against its parent?
560 if not node1:
561 if not node1:
561 if lookup:
562 if lookup:
562 # do a full compare of any files that might have changed
563 # do a full compare of any files that might have changed
563 mf2 = mfmatches(self.dirstate.parents()[0])
564 mf2 = mfmatches(self.dirstate.parents()[0])
564 for f in lookup:
565 for f in lookup:
565 if fcmp(f, mf2):
566 if fcmp(f, mf2):
566 modified.append(f)
567 modified.append(f)
567 elif wlock is not None:
568 elif wlock is not None:
568 self.dirstate.update([f], "n")
569 self.dirstate.update([f], "n")
569 else:
570 else:
570 # we are comparing working dir against non-parent
571 # we are comparing working dir against non-parent
571 # generate a pseudo-manifest for the working dir
572 # generate a pseudo-manifest for the working dir
572 mf2 = mfmatches(self.dirstate.parents()[0])
573 mf2 = mfmatches(self.dirstate.parents()[0])
573 for f in lookup + modified + added:
574 for f in lookup + modified + added:
574 mf2[f] = ""
575 mf2[f] = ""
575 for f in removed:
576 for f in removed:
576 if f in mf2:
577 if f in mf2:
577 del mf2[f]
578 del mf2[f]
578 else:
579 else:
579 # we are comparing two revisions
580 # we are comparing two revisions
580 deleted, unknown, ignored = [], [], []
581 deleted, unknown, ignored = [], [], []
581 mf2 = mfmatches(node2)
582 mf2 = mfmatches(node2)
582
583
583 if node1:
584 if node1:
584 # flush lists from dirstate before comparing manifests
585 # flush lists from dirstate before comparing manifests
585 modified, added = [], []
586 modified, added = [], []
586
587
587 for fn in mf2:
588 for fn in mf2:
588 if mf1.has_key(fn):
589 if mf1.has_key(fn):
589 if mf1[fn] != mf2[fn] and (mf2[fn] != "" or fcmp(fn, mf1)):
590 if mf1[fn] != mf2[fn] and (mf2[fn] != "" or fcmp(fn, mf1)):
590 modified.append(fn)
591 modified.append(fn)
591 del mf1[fn]
592 del mf1[fn]
592 else:
593 else:
593 added.append(fn)
594 added.append(fn)
594
595
595 removed = mf1.keys()
596 removed = mf1.keys()
596
597
597 # sort and return results:
598 # sort and return results:
598 for l in modified, added, removed, deleted, unknown, ignored:
599 for l in modified, added, removed, deleted, unknown, ignored:
599 l.sort()
600 l.sort()
600 if show_ignored is None:
601 if show_ignored is None:
601 return (modified, added, removed, deleted, unknown)
602 return (modified, added, removed, deleted, unknown)
602 else:
603 else:
603 return (modified, added, removed, deleted, unknown, ignored)
604 return (modified, added, removed, deleted, unknown, ignored)
604
605
605 def add(self, list, wlock=None):
606 def add(self, list, wlock=None):
606 if not wlock:
607 if not wlock:
607 wlock = self.wlock()
608 wlock = self.wlock()
608 for f in list:
609 for f in list:
609 p = self.wjoin(f)
610 p = self.wjoin(f)
610 if not os.path.exists(p):
611 if not os.path.exists(p):
611 self.ui.warn(_("%s does not exist!\n") % f)
612 self.ui.warn(_("%s does not exist!\n") % f)
612 elif not os.path.isfile(p):
613 elif not os.path.isfile(p):
613 self.ui.warn(_("%s not added: only files supported currently\n")
614 self.ui.warn(_("%s not added: only files supported currently\n")
614 % f)
615 % f)
615 elif self.dirstate.state(f) in 'an':
616 elif self.dirstate.state(f) in 'an':
616 self.ui.warn(_("%s already tracked!\n") % f)
617 self.ui.warn(_("%s already tracked!\n") % f)
617 else:
618 else:
618 self.dirstate.update([f], "a")
619 self.dirstate.update([f], "a")
619
620
620 def forget(self, list, wlock=None):
621 def forget(self, list, wlock=None):
621 if not wlock:
622 if not wlock:
622 wlock = self.wlock()
623 wlock = self.wlock()
623 for f in list:
624 for f in list:
624 if self.dirstate.state(f) not in 'ai':
625 if self.dirstate.state(f) not in 'ai':
625 self.ui.warn(_("%s not added!\n") % f)
626 self.ui.warn(_("%s not added!\n") % f)
626 else:
627 else:
627 self.dirstate.forget([f])
628 self.dirstate.forget([f])
628
629
629 def remove(self, list, unlink=False, wlock=None):
630 def remove(self, list, unlink=False, wlock=None):
630 if unlink:
631 if unlink:
631 for f in list:
632 for f in list:
632 try:
633 try:
633 util.unlink(self.wjoin(f))
634 util.unlink(self.wjoin(f))
634 except OSError, inst:
635 except OSError, inst:
635 if inst.errno != errno.ENOENT:
636 if inst.errno != errno.ENOENT:
636 raise
637 raise
637 if not wlock:
638 if not wlock:
638 wlock = self.wlock()
639 wlock = self.wlock()
639 for f in list:
640 for f in list:
640 p = self.wjoin(f)
641 p = self.wjoin(f)
641 if os.path.exists(p):
642 if os.path.exists(p):
642 self.ui.warn(_("%s still exists!\n") % f)
643 self.ui.warn(_("%s still exists!\n") % f)
643 elif self.dirstate.state(f) == 'a':
644 elif self.dirstate.state(f) == 'a':
644 self.dirstate.forget([f])
645 self.dirstate.forget([f])
645 elif f not in self.dirstate:
646 elif f not in self.dirstate:
646 self.ui.warn(_("%s not tracked!\n") % f)
647 self.ui.warn(_("%s not tracked!\n") % f)
647 else:
648 else:
648 self.dirstate.update([f], "r")
649 self.dirstate.update([f], "r")
649
650
650 def undelete(self, list, wlock=None):
651 def undelete(self, list, wlock=None):
651 p = self.dirstate.parents()[0]
652 p = self.dirstate.parents()[0]
652 mn = self.changelog.read(p)[0]
653 mn = self.changelog.read(p)[0]
653 mf = self.manifest.readflags(mn)
654 mf = self.manifest.readflags(mn)
654 m = self.manifest.read(mn)
655 m = self.manifest.read(mn)
655 if not wlock:
656 if not wlock:
656 wlock = self.wlock()
657 wlock = self.wlock()
657 for f in list:
658 for f in list:
658 if self.dirstate.state(f) not in "r":
659 if self.dirstate.state(f) not in "r":
659 self.ui.warn("%s not removed!\n" % f)
660 self.ui.warn("%s not removed!\n" % f)
660 else:
661 else:
661 t = self.file(f).read(m[f])
662 t = self.file(f).read(m[f])
662 self.wwrite(f, t)
663 self.wwrite(f, t)
663 util.set_exec(self.wjoin(f), mf[f])
664 util.set_exec(self.wjoin(f), mf[f])
664 self.dirstate.update([f], "n")
665 self.dirstate.update([f], "n")
665
666
666 def copy(self, source, dest, wlock=None):
667 def copy(self, source, dest, wlock=None):
667 p = self.wjoin(dest)
668 p = self.wjoin(dest)
668 if not os.path.exists(p):
669 if not os.path.exists(p):
669 self.ui.warn(_("%s does not exist!\n") % dest)
670 self.ui.warn(_("%s does not exist!\n") % dest)
670 elif not os.path.isfile(p):
671 elif not os.path.isfile(p):
671 self.ui.warn(_("copy failed: %s is not a file\n") % dest)
672 self.ui.warn(_("copy failed: %s is not a file\n") % dest)
672 else:
673 else:
673 if not wlock:
674 if not wlock:
674 wlock = self.wlock()
675 wlock = self.wlock()
675 if self.dirstate.state(dest) == '?':
676 if self.dirstate.state(dest) == '?':
676 self.dirstate.update([dest], "a")
677 self.dirstate.update([dest], "a")
677 self.dirstate.copy(source, dest)
678 self.dirstate.copy(source, dest)
678
679
679 def heads(self, start=None):
680 def heads(self, start=None):
680 heads = self.changelog.heads(start)
681 heads = self.changelog.heads(start)
681 # sort the output in rev descending order
682 # sort the output in rev descending order
682 heads = [(-self.changelog.rev(h), h) for h in heads]
683 heads = [(-self.changelog.rev(h), h) for h in heads]
683 heads.sort()
684 heads.sort()
684 return [n for (r, n) in heads]
685 return [n for (r, n) in heads]
685
686
686 # branchlookup returns a dict giving a list of branches for
687 # branchlookup returns a dict giving a list of branches for
687 # each head. A branch is defined as the tag of a node or
688 # each head. A branch is defined as the tag of a node or
688 # the branch of the node's parents. If a node has multiple
689 # the branch of the node's parents. If a node has multiple
689 # branch tags, tags are eliminated if they are visible from other
690 # branch tags, tags are eliminated if they are visible from other
690 # branch tags.
691 # branch tags.
691 #
692 #
692 # So, for this graph: a->b->c->d->e
693 # So, for this graph: a->b->c->d->e
693 # \ /
694 # \ /
694 # aa -----/
695 # aa -----/
695 # a has tag 2.6.12
696 # a has tag 2.6.12
696 # d has tag 2.6.13
697 # d has tag 2.6.13
697 # e would have branch tags for 2.6.12 and 2.6.13. Because the node
698 # e would have branch tags for 2.6.12 and 2.6.13. Because the node
698 # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
699 # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
699 # from the list.
700 # from the list.
700 #
701 #
701 # It is possible that more than one head will have the same branch tag.
702 # It is possible that more than one head will have the same branch tag.
702 # callers need to check the result for multiple heads under the same
703 # callers need to check the result for multiple heads under the same
703 # branch tag if that is a problem for them (ie checkout of a specific
704 # branch tag if that is a problem for them (ie checkout of a specific
704 # branch).
705 # branch).
705 #
706 #
706 # passing in a specific branch will limit the depth of the search
707 # passing in a specific branch will limit the depth of the search
707 # through the parents. It won't limit the branches returned in the
708 # through the parents. It won't limit the branches returned in the
708 # result though.
709 # result though.
709 def branchlookup(self, heads=None, branch=None):
710 def branchlookup(self, heads=None, branch=None):
710 if not heads:
711 if not heads:
711 heads = self.heads()
712 heads = self.heads()
712 headt = [ h for h in heads ]
713 headt = [ h for h in heads ]
713 chlog = self.changelog
714 chlog = self.changelog
714 branches = {}
715 branches = {}
715 merges = []
716 merges = []
716 seenmerge = {}
717 seenmerge = {}
717
718
718 # traverse the tree once for each head, recording in the branches
719 # traverse the tree once for each head, recording in the branches
719 # dict which tags are visible from this head. The branches
720 # dict which tags are visible from this head. The branches
720 # dict also records which tags are visible from each tag
721 # dict also records which tags are visible from each tag
721 # while we traverse.
722 # while we traverse.
722 while headt or merges:
723 while headt or merges:
723 if merges:
724 if merges:
724 n, found = merges.pop()
725 n, found = merges.pop()
725 visit = [n]
726 visit = [n]
726 else:
727 else:
727 h = headt.pop()
728 h = headt.pop()
728 visit = [h]
729 visit = [h]
729 found = [h]
730 found = [h]
730 seen = {}
731 seen = {}
731 while visit:
732 while visit:
732 n = visit.pop()
733 n = visit.pop()
733 if n in seen:
734 if n in seen:
734 continue
735 continue
735 pp = chlog.parents(n)
736 pp = chlog.parents(n)
736 tags = self.nodetags(n)
737 tags = self.nodetags(n)
737 if tags:
738 if tags:
738 for x in tags:
739 for x in tags:
739 if x == 'tip':
740 if x == 'tip':
740 continue
741 continue
741 for f in found:
742 for f in found:
742 branches.setdefault(f, {})[n] = 1
743 branches.setdefault(f, {})[n] = 1
743 branches.setdefault(n, {})[n] = 1
744 branches.setdefault(n, {})[n] = 1
744 break
745 break
745 if n not in found:
746 if n not in found:
746 found.append(n)
747 found.append(n)
747 if branch in tags:
748 if branch in tags:
748 continue
749 continue
749 seen[n] = 1
750 seen[n] = 1
750 if pp[1] != nullid and n not in seenmerge:
751 if pp[1] != nullid and n not in seenmerge:
751 merges.append((pp[1], [x for x in found]))
752 merges.append((pp[1], [x for x in found]))
752 seenmerge[n] = 1
753 seenmerge[n] = 1
753 if pp[0] != nullid:
754 if pp[0] != nullid:
754 visit.append(pp[0])
755 visit.append(pp[0])
755 # traverse the branches dict, eliminating branch tags from each
756 # traverse the branches dict, eliminating branch tags from each
756 # head that are visible from another branch tag for that head.
757 # head that are visible from another branch tag for that head.
757 out = {}
758 out = {}
758 viscache = {}
759 viscache = {}
759 for h in heads:
760 for h in heads:
760 def visible(node):
761 def visible(node):
761 if node in viscache:
762 if node in viscache:
762 return viscache[node]
763 return viscache[node]
763 ret = {}
764 ret = {}
764 visit = [node]
765 visit = [node]
765 while visit:
766 while visit:
766 x = visit.pop()
767 x = visit.pop()
767 if x in viscache:
768 if x in viscache:
768 ret.update(viscache[x])
769 ret.update(viscache[x])
769 elif x not in ret:
770 elif x not in ret:
770 ret[x] = 1
771 ret[x] = 1
771 if x in branches:
772 if x in branches:
772 visit[len(visit):] = branches[x].keys()
773 visit[len(visit):] = branches[x].keys()
773 viscache[node] = ret
774 viscache[node] = ret
774 return ret
775 return ret
775 if h not in branches:
776 if h not in branches:
776 continue
777 continue
777 # O(n^2), but somewhat limited. This only searches the
778 # O(n^2), but somewhat limited. This only searches the
778 # tags visible from a specific head, not all the tags in the
779 # tags visible from a specific head, not all the tags in the
779 # whole repo.
780 # whole repo.
780 for b in branches[h]:
781 for b in branches[h]:
781 vis = False
782 vis = False
782 for bb in branches[h].keys():
783 for bb in branches[h].keys():
783 if b != bb:
784 if b != bb:
784 if b in visible(bb):
785 if b in visible(bb):
785 vis = True
786 vis = True
786 break
787 break
787 if not vis:
788 if not vis:
788 l = out.setdefault(h, [])
789 l = out.setdefault(h, [])
789 l[len(l):] = self.nodetags(b)
790 l[len(l):] = self.nodetags(b)
790 return out
791 return out
791
792
792 def branches(self, nodes):
793 def branches(self, nodes):
793 if not nodes:
794 if not nodes:
794 nodes = [self.changelog.tip()]
795 nodes = [self.changelog.tip()]
795 b = []
796 b = []
796 for n in nodes:
797 for n in nodes:
797 t = n
798 t = n
798 while n:
799 while n:
799 p = self.changelog.parents(n)
800 p = self.changelog.parents(n)
800 if p[1] != nullid or p[0] == nullid:
801 if p[1] != nullid or p[0] == nullid:
801 b.append((t, n, p[0], p[1]))
802 b.append((t, n, p[0], p[1]))
802 break
803 break
803 n = p[0]
804 n = p[0]
804 return b
805 return b
805
806
806 def between(self, pairs):
807 def between(self, pairs):
807 r = []
808 r = []
808
809
809 for top, bottom in pairs:
810 for top, bottom in pairs:
810 n, l, i = top, [], 0
811 n, l, i = top, [], 0
811 f = 1
812 f = 1
812
813
813 while n != bottom:
814 while n != bottom:
814 p = self.changelog.parents(n)[0]
815 p = self.changelog.parents(n)[0]
815 if i == f:
816 if i == f:
816 l.append(n)
817 l.append(n)
817 f = f * 2
818 f = f * 2
818 n = p
819 n = p
819 i += 1
820 i += 1
820
821
821 r.append(l)
822 r.append(l)
822
823
823 return r
824 return r
824
825
825 def findincoming(self, remote, base=None, heads=None, force=False):
826 def findincoming(self, remote, base=None, heads=None, force=False):
826 m = self.changelog.nodemap
827 m = self.changelog.nodemap
827 search = []
828 search = []
828 fetch = {}
829 fetch = {}
829 seen = {}
830 seen = {}
830 seenbranch = {}
831 seenbranch = {}
831 if base == None:
832 if base == None:
832 base = {}
833 base = {}
833
834
834 # assume we're closer to the tip than the root
835 # assume we're closer to the tip than the root
835 # and start by examining the heads
836 # and start by examining the heads
836 self.ui.status(_("searching for changes\n"))
837 self.ui.status(_("searching for changes\n"))
837
838
838 if not heads:
839 if not heads:
839 heads = remote.heads()
840 heads = remote.heads()
840
841
841 unknown = []
842 unknown = []
842 for h in heads:
843 for h in heads:
843 if h not in m:
844 if h not in m:
844 unknown.append(h)
845 unknown.append(h)
845 else:
846 else:
846 base[h] = 1
847 base[h] = 1
847
848
848 if not unknown:
849 if not unknown:
849 return []
850 return []
850
851
851 rep = {}
852 rep = {}
852 reqcnt = 0
853 reqcnt = 0
853
854
854 # search through remote branches
855 # search through remote branches
855 # a 'branch' here is a linear segment of history, with four parts:
856 # a 'branch' here is a linear segment of history, with four parts:
856 # head, root, first parent, second parent
857 # head, root, first parent, second parent
857 # (a branch always has two parents (or none) by definition)
858 # (a branch always has two parents (or none) by definition)
858 unknown = remote.branches(unknown)
859 unknown = remote.branches(unknown)
859 while unknown:
860 while unknown:
860 r = []
861 r = []
861 while unknown:
862 while unknown:
862 n = unknown.pop(0)
863 n = unknown.pop(0)
863 if n[0] in seen:
864 if n[0] in seen:
864 continue
865 continue
865
866
866 self.ui.debug(_("examining %s:%s\n")
867 self.ui.debug(_("examining %s:%s\n")
867 % (short(n[0]), short(n[1])))
868 % (short(n[0]), short(n[1])))
868 if n[0] == nullid:
869 if n[0] == nullid:
869 break
870 break
870 if n in seenbranch:
871 if n in seenbranch:
871 self.ui.debug(_("branch already found\n"))
872 self.ui.debug(_("branch already found\n"))
872 continue
873 continue
873 if n[1] and n[1] in m: # do we know the base?
874 if n[1] and n[1] in m: # do we know the base?
874 self.ui.debug(_("found incomplete branch %s:%s\n")
875 self.ui.debug(_("found incomplete branch %s:%s\n")
875 % (short(n[0]), short(n[1])))
876 % (short(n[0]), short(n[1])))
876 search.append(n) # schedule branch range for scanning
877 search.append(n) # schedule branch range for scanning
877 seenbranch[n] = 1
878 seenbranch[n] = 1
878 else:
879 else:
879 if n[1] not in seen and n[1] not in fetch:
880 if n[1] not in seen and n[1] not in fetch:
880 if n[2] in m and n[3] in m:
881 if n[2] in m and n[3] in m:
881 self.ui.debug(_("found new changeset %s\n") %
882 self.ui.debug(_("found new changeset %s\n") %
882 short(n[1]))
883 short(n[1]))
883 fetch[n[1]] = 1 # earliest unknown
884 fetch[n[1]] = 1 # earliest unknown
884 base[n[2]] = 1 # latest known
885 base[n[2]] = 1 # latest known
885 continue
886 continue
886
887
887 for a in n[2:4]:
888 for a in n[2:4]:
888 if a not in rep:
889 if a not in rep:
889 r.append(a)
890 r.append(a)
890 rep[a] = 1
891 rep[a] = 1
891
892
892 seen[n[0]] = 1
893 seen[n[0]] = 1
893
894
894 if r:
895 if r:
895 reqcnt += 1
896 reqcnt += 1
896 self.ui.debug(_("request %d: %s\n") %
897 self.ui.debug(_("request %d: %s\n") %
897 (reqcnt, " ".join(map(short, r))))
898 (reqcnt, " ".join(map(short, r))))
898 for p in range(0, len(r), 10):
899 for p in range(0, len(r), 10):
899 for b in remote.branches(r[p:p+10]):
900 for b in remote.branches(r[p:p+10]):
900 self.ui.debug(_("received %s:%s\n") %
901 self.ui.debug(_("received %s:%s\n") %
901 (short(b[0]), short(b[1])))
902 (short(b[0]), short(b[1])))
902 if b[0] in m:
903 if b[0] in m:
903 self.ui.debug(_("found base node %s\n")
904 self.ui.debug(_("found base node %s\n")
904 % short(b[0]))
905 % short(b[0]))
905 base[b[0]] = 1
906 base[b[0]] = 1
906 elif b[0] not in seen:
907 elif b[0] not in seen:
907 unknown.append(b)
908 unknown.append(b)
908
909
909 # do binary search on the branches we found
910 # do binary search on the branches we found
910 while search:
911 while search:
911 n = search.pop(0)
912 n = search.pop(0)
912 reqcnt += 1
913 reqcnt += 1
913 l = remote.between([(n[0], n[1])])[0]
914 l = remote.between([(n[0], n[1])])[0]
914 l.append(n[1])
915 l.append(n[1])
915 p = n[0]
916 p = n[0]
916 f = 1
917 f = 1
917 for i in l:
918 for i in l:
918 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
919 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
919 if i in m:
920 if i in m:
920 if f <= 2:
921 if f <= 2:
921 self.ui.debug(_("found new branch changeset %s\n") %
922 self.ui.debug(_("found new branch changeset %s\n") %
922 short(p))
923 short(p))
923 fetch[p] = 1
924 fetch[p] = 1
924 base[i] = 1
925 base[i] = 1
925 else:
926 else:
926 self.ui.debug(_("narrowed branch search to %s:%s\n")
927 self.ui.debug(_("narrowed branch search to %s:%s\n")
927 % (short(p), short(i)))
928 % (short(p), short(i)))
928 search.append((p, i))
929 search.append((p, i))
929 break
930 break
930 p, f = i, f * 2
931 p, f = i, f * 2
931
932
932 # sanity check our fetch list
933 # sanity check our fetch list
933 for f in fetch.keys():
934 for f in fetch.keys():
934 if f in m:
935 if f in m:
935 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
936 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
936
937
937 if base.keys() == [nullid]:
938 if base.keys() == [nullid]:
938 if force:
939 if force:
939 self.ui.warn(_("warning: repository is unrelated\n"))
940 self.ui.warn(_("warning: repository is unrelated\n"))
940 else:
941 else:
941 raise util.Abort(_("repository is unrelated"))
942 raise util.Abort(_("repository is unrelated"))
942
943
943 self.ui.note(_("found new changesets starting at ") +
944 self.ui.note(_("found new changesets starting at ") +
944 " ".join([short(f) for f in fetch]) + "\n")
945 " ".join([short(f) for f in fetch]) + "\n")
945
946
946 self.ui.debug(_("%d total queries\n") % reqcnt)
947 self.ui.debug(_("%d total queries\n") % reqcnt)
947
948
948 return fetch.keys()
949 return fetch.keys()
949
950
950 def findoutgoing(self, remote, base=None, heads=None, force=False):
951 def findoutgoing(self, remote, base=None, heads=None, force=False):
951 """Return list of nodes that are roots of subsets not in remote
952 """Return list of nodes that are roots of subsets not in remote
952
953
953 If base dict is specified, assume that these nodes and their parents
954 If base dict is specified, assume that these nodes and their parents
954 exist on the remote side.
955 exist on the remote side.
955 If a list of heads is specified, return only nodes which are heads
956 If a list of heads is specified, return only nodes which are heads
956 or ancestors of these heads, and return a second element which
957 or ancestors of these heads, and return a second element which
957 contains all remote heads which get new children.
958 contains all remote heads which get new children.
958 """
959 """
959 if base == None:
960 if base == None:
960 base = {}
961 base = {}
961 self.findincoming(remote, base, heads, force=force)
962 self.findincoming(remote, base, heads, force=force)
962
963
963 self.ui.debug(_("common changesets up to ")
964 self.ui.debug(_("common changesets up to ")
964 + " ".join(map(short, base.keys())) + "\n")
965 + " ".join(map(short, base.keys())) + "\n")
965
966
966 remain = dict.fromkeys(self.changelog.nodemap)
967 remain = dict.fromkeys(self.changelog.nodemap)
967
968
968 # prune everything remote has from the tree
969 # prune everything remote has from the tree
969 del remain[nullid]
970 del remain[nullid]
970 remove = base.keys()
971 remove = base.keys()
971 while remove:
972 while remove:
972 n = remove.pop(0)
973 n = remove.pop(0)
973 if n in remain:
974 if n in remain:
974 del remain[n]
975 del remain[n]
975 for p in self.changelog.parents(n):
976 for p in self.changelog.parents(n):
976 remove.append(p)
977 remove.append(p)
977
978
978 # find every node whose parents have been pruned
979 # find every node whose parents have been pruned
979 subset = []
980 subset = []
980 # find every remote head that will get new children
981 # find every remote head that will get new children
981 updated_heads = {}
982 updated_heads = {}
982 for n in remain:
983 for n in remain:
983 p1, p2 = self.changelog.parents(n)
984 p1, p2 = self.changelog.parents(n)
984 if p1 not in remain and p2 not in remain:
985 if p1 not in remain and p2 not in remain:
985 subset.append(n)
986 subset.append(n)
986 if heads:
987 if heads:
987 if p1 in heads:
988 if p1 in heads:
988 updated_heads[p1] = True
989 updated_heads[p1] = True
989 if p2 in heads:
990 if p2 in heads:
990 updated_heads[p2] = True
991 updated_heads[p2] = True
991
992
992 # this is the set of all roots we have to push
993 # this is the set of all roots we have to push
993 if heads:
994 if heads:
994 return subset, updated_heads.keys()
995 return subset, updated_heads.keys()
995 else:
996 else:
996 return subset
997 return subset
997
998
998 def pull(self, remote, heads=None, force=False):
999 def pull(self, remote, heads=None, force=False):
999 l = self.lock()
1000 l = self.lock()
1000
1001
1001 # if we have an empty repo, fetch everything
1002 # if we have an empty repo, fetch everything
1002 if self.changelog.tip() == nullid:
1003 if self.changelog.tip() == nullid:
1003 self.ui.status(_("requesting all changes\n"))
1004 self.ui.status(_("requesting all changes\n"))
1004 fetch = [nullid]
1005 fetch = [nullid]
1005 else:
1006 else:
1006 fetch = self.findincoming(remote, force=force)
1007 fetch = self.findincoming(remote, force=force)
1007
1008
1008 if not fetch:
1009 if not fetch:
1009 self.ui.status(_("no changes found\n"))
1010 self.ui.status(_("no changes found\n"))
1010 return 0
1011 return 0
1011
1012
1012 if heads is None:
1013 if heads is None:
1013 cg = remote.changegroup(fetch, 'pull')
1014 cg = remote.changegroup(fetch, 'pull')
1014 else:
1015 else:
1015 cg = remote.changegroupsubset(fetch, heads, 'pull')
1016 cg = remote.changegroupsubset(fetch, heads, 'pull')
1016 return self.addchangegroup(cg)
1017 return self.addchangegroup(cg)
1017
1018
1018 def push(self, remote, force=False, revs=None):
1019 def push(self, remote, force=False, revs=None):
1019 lock = remote.lock()
1020 lock = remote.lock()
1020
1021
1021 base = {}
1022 base = {}
1022 remote_heads = remote.heads()
1023 remote_heads = remote.heads()
1023 inc = self.findincoming(remote, base, remote_heads, force=force)
1024 inc = self.findincoming(remote, base, remote_heads, force=force)
1024 if not force and inc:
1025 if not force and inc:
1025 self.ui.warn(_("abort: unsynced remote changes!\n"))
1026 self.ui.warn(_("abort: unsynced remote changes!\n"))
1026 self.ui.status(_("(did you forget to sync?"
1027 self.ui.status(_("(did you forget to sync?"
1027 " use push -f to force)\n"))
1028 " use push -f to force)\n"))
1028 return 1
1029 return 1
1029
1030
1030 update, updated_heads = self.findoutgoing(remote, base, remote_heads)
1031 update, updated_heads = self.findoutgoing(remote, base, remote_heads)
1031 if revs is not None:
1032 if revs is not None:
1032 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
1033 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
1033 else:
1034 else:
1034 bases, heads = update, self.changelog.heads()
1035 bases, heads = update, self.changelog.heads()
1035
1036
1036 if not bases:
1037 if not bases:
1037 self.ui.status(_("no changes found\n"))
1038 self.ui.status(_("no changes found\n"))
1038 return 1
1039 return 1
1039 elif not force:
1040 elif not force:
1040 if revs is not None:
1041 if revs is not None:
1041 updated_heads = {}
1042 updated_heads = {}
1042 for base in msng_cl:
1043 for base in msng_cl:
1043 for parent in self.changelog.parents(base):
1044 for parent in self.changelog.parents(base):
1044 if parent in remote_heads:
1045 if parent in remote_heads:
1045 updated_heads[parent] = True
1046 updated_heads[parent] = True
1046 updated_heads = updated_heads.keys()
1047 updated_heads = updated_heads.keys()
1047 if len(updated_heads) < len(heads):
1048 if len(updated_heads) < len(heads):
1048 self.ui.warn(_("abort: push creates new remote branches!\n"))
1049 self.ui.warn(_("abort: push creates new remote branches!\n"))
1049 self.ui.status(_("(did you forget to merge?"
1050 self.ui.status(_("(did you forget to merge?"
1050 " use push -f to force)\n"))
1051 " use push -f to force)\n"))
1051 return 1
1052 return 1
1052
1053
1053 if revs is None:
1054 if revs is None:
1054 cg = self.changegroup(update, 'push')
1055 cg = self.changegroup(update, 'push')
1055 else:
1056 else:
1056 cg = self.changegroupsubset(update, revs, 'push')
1057 cg = self.changegroupsubset(update, revs, 'push')
1057 return remote.addchangegroup(cg)
1058 return remote.addchangegroup(cg)
1058
1059
1059 def changegroupsubset(self, bases, heads, source):
1060 def changegroupsubset(self, bases, heads, source):
1060 """This function generates a changegroup consisting of all the nodes
1061 """This function generates a changegroup consisting of all the nodes
1061 that are descendents of any of the bases, and ancestors of any of
1062 that are descendents of any of the bases, and ancestors of any of
1062 the heads.
1063 the heads.
1063
1064
1064 It is fairly complex as determining which filenodes and which
1065 It is fairly complex as determining which filenodes and which
1065 manifest nodes need to be included for the changeset to be complete
1066 manifest nodes need to be included for the changeset to be complete
1066 is non-trivial.
1067 is non-trivial.
1067
1068
1068 Another wrinkle is doing the reverse, figuring out which changeset in
1069 Another wrinkle is doing the reverse, figuring out which changeset in
1069 the changegroup a particular filenode or manifestnode belongs to."""
1070 the changegroup a particular filenode or manifestnode belongs to."""
1070
1071
1071 self.hook('preoutgoing', throw=True, source=source)
1072 self.hook('preoutgoing', throw=True, source=source)
1072
1073
1073 # Set up some initial variables
1074 # Set up some initial variables
1074 # Make it easy to refer to self.changelog
1075 # Make it easy to refer to self.changelog
1075 cl = self.changelog
1076 cl = self.changelog
1076 # msng is short for missing - compute the list of changesets in this
1077 # msng is short for missing - compute the list of changesets in this
1077 # changegroup.
1078 # changegroup.
1078 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1079 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1079 # Some bases may turn out to be superfluous, and some heads may be
1080 # Some bases may turn out to be superfluous, and some heads may be
1080 # too. nodesbetween will return the minimal set of bases and heads
1081 # too. nodesbetween will return the minimal set of bases and heads
1081 # necessary to re-create the changegroup.
1082 # necessary to re-create the changegroup.
1082
1083
1083 # Known heads are the list of heads that it is assumed the recipient
1084 # Known heads are the list of heads that it is assumed the recipient
1084 # of this changegroup will know about.
1085 # of this changegroup will know about.
1085 knownheads = {}
1086 knownheads = {}
1086 # We assume that all parents of bases are known heads.
1087 # We assume that all parents of bases are known heads.
1087 for n in bases:
1088 for n in bases:
1088 for p in cl.parents(n):
1089 for p in cl.parents(n):
1089 if p != nullid:
1090 if p != nullid:
1090 knownheads[p] = 1
1091 knownheads[p] = 1
1091 knownheads = knownheads.keys()
1092 knownheads = knownheads.keys()
1092 if knownheads:
1093 if knownheads:
1093 # Now that we know what heads are known, we can compute which
1094 # Now that we know what heads are known, we can compute which
1094 # changesets are known. The recipient must know about all
1095 # changesets are known. The recipient must know about all
1095 # changesets required to reach the known heads from the null
1096 # changesets required to reach the known heads from the null
1096 # changeset.
1097 # changeset.
1097 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1098 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1098 junk = None
1099 junk = None
1099 # Transform the list into an ersatz set.
1100 # Transform the list into an ersatz set.
1100 has_cl_set = dict.fromkeys(has_cl_set)
1101 has_cl_set = dict.fromkeys(has_cl_set)
1101 else:
1102 else:
1102 # If there were no known heads, the recipient cannot be assumed to
1103 # If there were no known heads, the recipient cannot be assumed to
1103 # know about any changesets.
1104 # know about any changesets.
1104 has_cl_set = {}
1105 has_cl_set = {}
1105
1106
1106 # Make it easy to refer to self.manifest
1107 # Make it easy to refer to self.manifest
1107 mnfst = self.manifest
1108 mnfst = self.manifest
1108 # We don't know which manifests are missing yet
1109 # We don't know which manifests are missing yet
1109 msng_mnfst_set = {}
1110 msng_mnfst_set = {}
1110 # Nor do we know which filenodes are missing.
1111 # Nor do we know which filenodes are missing.
1111 msng_filenode_set = {}
1112 msng_filenode_set = {}
1112
1113
1113 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1114 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1114 junk = None
1115 junk = None
1115
1116
1116 # A changeset always belongs to itself, so the changenode lookup
1117 # A changeset always belongs to itself, so the changenode lookup
1117 # function for a changenode is identity.
1118 # function for a changenode is identity.
1118 def identity(x):
1119 def identity(x):
1119 return x
1120 return x
1120
1121
1121 # A function generating function. Sets up an environment for the
1122 # A function generating function. Sets up an environment for the
1122 # inner function.
1123 # inner function.
1123 def cmp_by_rev_func(revlog):
1124 def cmp_by_rev_func(revlog):
1124 # Compare two nodes by their revision number in the environment's
1125 # Compare two nodes by their revision number in the environment's
1125 # revision history. Since the revision number both represents the
1126 # revision history. Since the revision number both represents the
1126 # most efficient order to read the nodes in, and represents a
1127 # most efficient order to read the nodes in, and represents a
1127 # topological sorting of the nodes, this function is often useful.
1128 # topological sorting of the nodes, this function is often useful.
1128 def cmp_by_rev(a, b):
1129 def cmp_by_rev(a, b):
1129 return cmp(revlog.rev(a), revlog.rev(b))
1130 return cmp(revlog.rev(a), revlog.rev(b))
1130 return cmp_by_rev
1131 return cmp_by_rev
1131
1132
1132 # If we determine that a particular file or manifest node must be a
1133 # If we determine that a particular file or manifest node must be a
1133 # node that the recipient of the changegroup will already have, we can
1134 # node that the recipient of the changegroup will already have, we can
1134 # also assume the recipient will have all the parents. This function
1135 # also assume the recipient will have all the parents. This function
1135 # prunes them from the set of missing nodes.
1136 # prunes them from the set of missing nodes.
1136 def prune_parents(revlog, hasset, msngset):
1137 def prune_parents(revlog, hasset, msngset):
1137 haslst = hasset.keys()
1138 haslst = hasset.keys()
1138 haslst.sort(cmp_by_rev_func(revlog))
1139 haslst.sort(cmp_by_rev_func(revlog))
1139 for node in haslst:
1140 for node in haslst:
1140 parentlst = [p for p in revlog.parents(node) if p != nullid]
1141 parentlst = [p for p in revlog.parents(node) if p != nullid]
1141 while parentlst:
1142 while parentlst:
1142 n = parentlst.pop()
1143 n = parentlst.pop()
1143 if n not in hasset:
1144 if n not in hasset:
1144 hasset[n] = 1
1145 hasset[n] = 1
1145 p = [p for p in revlog.parents(n) if p != nullid]
1146 p = [p for p in revlog.parents(n) if p != nullid]
1146 parentlst.extend(p)
1147 parentlst.extend(p)
1147 for n in hasset:
1148 for n in hasset:
1148 msngset.pop(n, None)
1149 msngset.pop(n, None)
1149
1150
1150 # This is a function generating function used to set up an environment
1151 # This is a function generating function used to set up an environment
1151 # for the inner function to execute in.
1152 # for the inner function to execute in.
1152 def manifest_and_file_collector(changedfileset):
1153 def manifest_and_file_collector(changedfileset):
1153 # This is an information gathering function that gathers
1154 # This is an information gathering function that gathers
1154 # information from each changeset node that goes out as part of
1155 # information from each changeset node that goes out as part of
1155 # the changegroup. The information gathered is a list of which
1156 # the changegroup. The information gathered is a list of which
1156 # manifest nodes are potentially required (the recipient may
1157 # manifest nodes are potentially required (the recipient may
1157 # already have them) and total list of all files which were
1158 # already have them) and total list of all files which were
1158 # changed in any changeset in the changegroup.
1159 # changed in any changeset in the changegroup.
1159 #
1160 #
1160 # We also remember the first changenode we saw any manifest
1161 # We also remember the first changenode we saw any manifest
1161 # referenced by so we can later determine which changenode 'owns'
1162 # referenced by so we can later determine which changenode 'owns'
1162 # the manifest.
1163 # the manifest.
1163 def collect_manifests_and_files(clnode):
1164 def collect_manifests_and_files(clnode):
1164 c = cl.read(clnode)
1165 c = cl.read(clnode)
1165 for f in c[3]:
1166 for f in c[3]:
1166 # This is to make sure we only have one instance of each
1167 # This is to make sure we only have one instance of each
1167 # filename string for each filename.
1168 # filename string for each filename.
1168 changedfileset.setdefault(f, f)
1169 changedfileset.setdefault(f, f)
1169 msng_mnfst_set.setdefault(c[0], clnode)
1170 msng_mnfst_set.setdefault(c[0], clnode)
1170 return collect_manifests_and_files
1171 return collect_manifests_and_files
1171
1172
1172 # Figure out which manifest nodes (of the ones we think might be part
1173 # Figure out which manifest nodes (of the ones we think might be part
1173 # of the changegroup) the recipient must know about and remove them
1174 # of the changegroup) the recipient must know about and remove them
1174 # from the changegroup.
1175 # from the changegroup.
1175 def prune_manifests():
1176 def prune_manifests():
1176 has_mnfst_set = {}
1177 has_mnfst_set = {}
1177 for n in msng_mnfst_set:
1178 for n in msng_mnfst_set:
1178 # If a 'missing' manifest thinks it belongs to a changenode
1179 # If a 'missing' manifest thinks it belongs to a changenode
1179 # the recipient is assumed to have, obviously the recipient
1180 # the recipient is assumed to have, obviously the recipient
1180 # must have that manifest.
1181 # must have that manifest.
1181 linknode = cl.node(mnfst.linkrev(n))
1182 linknode = cl.node(mnfst.linkrev(n))
1182 if linknode in has_cl_set:
1183 if linknode in has_cl_set:
1183 has_mnfst_set[n] = 1
1184 has_mnfst_set[n] = 1
1184 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1185 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1185
1186
1186 # Use the information collected in collect_manifests_and_files to say
1187 # Use the information collected in collect_manifests_and_files to say
1187 # which changenode any manifestnode belongs to.
1188 # which changenode any manifestnode belongs to.
1188 def lookup_manifest_link(mnfstnode):
1189 def lookup_manifest_link(mnfstnode):
1189 return msng_mnfst_set[mnfstnode]
1190 return msng_mnfst_set[mnfstnode]
1190
1191
1191 # A function generating function that sets up the initial environment
1192 # A function generating function that sets up the initial environment
1192 # the inner function.
1193 # the inner function.
1193 def filenode_collector(changedfiles):
1194 def filenode_collector(changedfiles):
1194 next_rev = [0]
1195 next_rev = [0]
1195 # This gathers information from each manifestnode included in the
1196 # This gathers information from each manifestnode included in the
1196 # changegroup about which filenodes the manifest node references
1197 # changegroup about which filenodes the manifest node references
1197 # so we can include those in the changegroup too.
1198 # so we can include those in the changegroup too.
1198 #
1199 #
1199 # It also remembers which changenode each filenode belongs to. It
1200 # It also remembers which changenode each filenode belongs to. It
1200 # does this by assuming the a filenode belongs to the changenode
1201 # does this by assuming the a filenode belongs to the changenode
1201 # the first manifest that references it belongs to.
1202 # the first manifest that references it belongs to.
1202 def collect_msng_filenodes(mnfstnode):
1203 def collect_msng_filenodes(mnfstnode):
1203 r = mnfst.rev(mnfstnode)
1204 r = mnfst.rev(mnfstnode)
1204 if r == next_rev[0]:
1205 if r == next_rev[0]:
1205 # If the last rev we looked at was the one just previous,
1206 # If the last rev we looked at was the one just previous,
1206 # we only need to see a diff.
1207 # we only need to see a diff.
1207 delta = mdiff.patchtext(mnfst.delta(mnfstnode))
1208 delta = mdiff.patchtext(mnfst.delta(mnfstnode))
1208 # For each line in the delta
1209 # For each line in the delta
1209 for dline in delta.splitlines():
1210 for dline in delta.splitlines():
1210 # get the filename and filenode for that line
1211 # get the filename and filenode for that line
1211 f, fnode = dline.split('\0')
1212 f, fnode = dline.split('\0')
1212 fnode = bin(fnode[:40])
1213 fnode = bin(fnode[:40])
1213 f = changedfiles.get(f, None)
1214 f = changedfiles.get(f, None)
1214 # And if the file is in the list of files we care
1215 # And if the file is in the list of files we care
1215 # about.
1216 # about.
1216 if f is not None:
1217 if f is not None:
1217 # Get the changenode this manifest belongs to
1218 # Get the changenode this manifest belongs to
1218 clnode = msng_mnfst_set[mnfstnode]
1219 clnode = msng_mnfst_set[mnfstnode]
1219 # Create the set of filenodes for the file if
1220 # Create the set of filenodes for the file if
1220 # there isn't one already.
1221 # there isn't one already.
1221 ndset = msng_filenode_set.setdefault(f, {})
1222 ndset = msng_filenode_set.setdefault(f, {})
1222 # And set the filenode's changelog node to the
1223 # And set the filenode's changelog node to the
1223 # manifest's if it hasn't been set already.
1224 # manifest's if it hasn't been set already.
1224 ndset.setdefault(fnode, clnode)
1225 ndset.setdefault(fnode, clnode)
1225 else:
1226 else:
1226 # Otherwise we need a full manifest.
1227 # Otherwise we need a full manifest.
1227 m = mnfst.read(mnfstnode)
1228 m = mnfst.read(mnfstnode)
1228 # For every file in we care about.
1229 # For every file in we care about.
1229 for f in changedfiles:
1230 for f in changedfiles:
1230 fnode = m.get(f, None)
1231 fnode = m.get(f, None)
1231 # If it's in the manifest
1232 # If it's in the manifest
1232 if fnode is not None:
1233 if fnode is not None:
1233 # See comments above.
1234 # See comments above.
1234 clnode = msng_mnfst_set[mnfstnode]
1235 clnode = msng_mnfst_set[mnfstnode]
1235 ndset = msng_filenode_set.setdefault(f, {})
1236 ndset = msng_filenode_set.setdefault(f, {})
1236 ndset.setdefault(fnode, clnode)
1237 ndset.setdefault(fnode, clnode)
1237 # Remember the revision we hope to see next.
1238 # Remember the revision we hope to see next.
1238 next_rev[0] = r + 1
1239 next_rev[0] = r + 1
1239 return collect_msng_filenodes
1240 return collect_msng_filenodes
1240
1241
1241 # We have a list of filenodes we think we need for a file, lets remove
1242 # We have a list of filenodes we think we need for a file, lets remove
1242 # all those we now the recipient must have.
1243 # all those we now the recipient must have.
1243 def prune_filenodes(f, filerevlog):
1244 def prune_filenodes(f, filerevlog):
1244 msngset = msng_filenode_set[f]
1245 msngset = msng_filenode_set[f]
1245 hasset = {}
1246 hasset = {}
1246 # If a 'missing' filenode thinks it belongs to a changenode we
1247 # If a 'missing' filenode thinks it belongs to a changenode we
1247 # assume the recipient must have, then the recipient must have
1248 # assume the recipient must have, then the recipient must have
1248 # that filenode.
1249 # that filenode.
1249 for n in msngset:
1250 for n in msngset:
1250 clnode = cl.node(filerevlog.linkrev(n))
1251 clnode = cl.node(filerevlog.linkrev(n))
1251 if clnode in has_cl_set:
1252 if clnode in has_cl_set:
1252 hasset[n] = 1
1253 hasset[n] = 1
1253 prune_parents(filerevlog, hasset, msngset)
1254 prune_parents(filerevlog, hasset, msngset)
1254
1255
1255 # A function generator function that sets up the a context for the
1256 # A function generator function that sets up the a context for the
1256 # inner function.
1257 # inner function.
1257 def lookup_filenode_link_func(fname):
1258 def lookup_filenode_link_func(fname):
1258 msngset = msng_filenode_set[fname]
1259 msngset = msng_filenode_set[fname]
1259 # Lookup the changenode the filenode belongs to.
1260 # Lookup the changenode the filenode belongs to.
1260 def lookup_filenode_link(fnode):
1261 def lookup_filenode_link(fnode):
1261 return msngset[fnode]
1262 return msngset[fnode]
1262 return lookup_filenode_link
1263 return lookup_filenode_link
1263
1264
1264 # Now that we have all theses utility functions to help out and
1265 # Now that we have all theses utility functions to help out and
1265 # logically divide up the task, generate the group.
1266 # logically divide up the task, generate the group.
1266 def gengroup():
1267 def gengroup():
1267 # The set of changed files starts empty.
1268 # The set of changed files starts empty.
1268 changedfiles = {}
1269 changedfiles = {}
1269 # Create a changenode group generator that will call our functions
1270 # Create a changenode group generator that will call our functions
1270 # back to lookup the owning changenode and collect information.
1271 # back to lookup the owning changenode and collect information.
1271 group = cl.group(msng_cl_lst, identity,
1272 group = cl.group(msng_cl_lst, identity,
1272 manifest_and_file_collector(changedfiles))
1273 manifest_and_file_collector(changedfiles))
1273 for chnk in group:
1274 for chnk in group:
1274 yield chnk
1275 yield chnk
1275
1276
1276 # The list of manifests has been collected by the generator
1277 # The list of manifests has been collected by the generator
1277 # calling our functions back.
1278 # calling our functions back.
1278 prune_manifests()
1279 prune_manifests()
1279 msng_mnfst_lst = msng_mnfst_set.keys()
1280 msng_mnfst_lst = msng_mnfst_set.keys()
1280 # Sort the manifestnodes by revision number.
1281 # Sort the manifestnodes by revision number.
1281 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1282 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1282 # Create a generator for the manifestnodes that calls our lookup
1283 # Create a generator for the manifestnodes that calls our lookup
1283 # and data collection functions back.
1284 # and data collection functions back.
1284 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1285 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1285 filenode_collector(changedfiles))
1286 filenode_collector(changedfiles))
1286 for chnk in group:
1287 for chnk in group:
1287 yield chnk
1288 yield chnk
1288
1289
1289 # These are no longer needed, dereference and toss the memory for
1290 # These are no longer needed, dereference and toss the memory for
1290 # them.
1291 # them.
1291 msng_mnfst_lst = None
1292 msng_mnfst_lst = None
1292 msng_mnfst_set.clear()
1293 msng_mnfst_set.clear()
1293
1294
1294 changedfiles = changedfiles.keys()
1295 changedfiles = changedfiles.keys()
1295 changedfiles.sort()
1296 changedfiles.sort()
1296 # Go through all our files in order sorted by name.
1297 # Go through all our files in order sorted by name.
1297 for fname in changedfiles:
1298 for fname in changedfiles:
1298 filerevlog = self.file(fname)
1299 filerevlog = self.file(fname)
1299 # Toss out the filenodes that the recipient isn't really
1300 # Toss out the filenodes that the recipient isn't really
1300 # missing.
1301 # missing.
1301 if msng_filenode_set.has_key(fname):
1302 if msng_filenode_set.has_key(fname):
1302 prune_filenodes(fname, filerevlog)
1303 prune_filenodes(fname, filerevlog)
1303 msng_filenode_lst = msng_filenode_set[fname].keys()
1304 msng_filenode_lst = msng_filenode_set[fname].keys()
1304 else:
1305 else:
1305 msng_filenode_lst = []
1306 msng_filenode_lst = []
1306 # If any filenodes are left, generate the group for them,
1307 # If any filenodes are left, generate the group for them,
1307 # otherwise don't bother.
1308 # otherwise don't bother.
1308 if len(msng_filenode_lst) > 0:
1309 if len(msng_filenode_lst) > 0:
1309 yield changegroup.genchunk(fname)
1310 yield changegroup.genchunk(fname)
1310 # Sort the filenodes by their revision #
1311 # Sort the filenodes by their revision #
1311 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1312 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1312 # Create a group generator and only pass in a changenode
1313 # Create a group generator and only pass in a changenode
1313 # lookup function as we need to collect no information
1314 # lookup function as we need to collect no information
1314 # from filenodes.
1315 # from filenodes.
1315 group = filerevlog.group(msng_filenode_lst,
1316 group = filerevlog.group(msng_filenode_lst,
1316 lookup_filenode_link_func(fname))
1317 lookup_filenode_link_func(fname))
1317 for chnk in group:
1318 for chnk in group:
1318 yield chnk
1319 yield chnk
1319 if msng_filenode_set.has_key(fname):
1320 if msng_filenode_set.has_key(fname):
1320 # Don't need this anymore, toss it to free memory.
1321 # Don't need this anymore, toss it to free memory.
1321 del msng_filenode_set[fname]
1322 del msng_filenode_set[fname]
1322 # Signal that no more groups are left.
1323 # Signal that no more groups are left.
1323 yield changegroup.closechunk()
1324 yield changegroup.closechunk()
1324
1325
1325 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1326 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1326
1327
1327 return util.chunkbuffer(gengroup())
1328 return util.chunkbuffer(gengroup())
1328
1329
1329 def changegroup(self, basenodes, source):
1330 def changegroup(self, basenodes, source):
1330 """Generate a changegroup of all nodes that we have that a recipient
1331 """Generate a changegroup of all nodes that we have that a recipient
1331 doesn't.
1332 doesn't.
1332
1333
1333 This is much easier than the previous function as we can assume that
1334 This is much easier than the previous function as we can assume that
1334 the recipient has any changenode we aren't sending them."""
1335 the recipient has any changenode we aren't sending them."""
1335
1336
1336 self.hook('preoutgoing', throw=True, source=source)
1337 self.hook('preoutgoing', throw=True, source=source)
1337
1338
1338 cl = self.changelog
1339 cl = self.changelog
1339 nodes = cl.nodesbetween(basenodes, None)[0]
1340 nodes = cl.nodesbetween(basenodes, None)[0]
1340 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1341 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1341
1342
1342 def identity(x):
1343 def identity(x):
1343 return x
1344 return x
1344
1345
1345 def gennodelst(revlog):
1346 def gennodelst(revlog):
1346 for r in xrange(0, revlog.count()):
1347 for r in xrange(0, revlog.count()):
1347 n = revlog.node(r)
1348 n = revlog.node(r)
1348 if revlog.linkrev(n) in revset:
1349 if revlog.linkrev(n) in revset:
1349 yield n
1350 yield n
1350
1351
1351 def changed_file_collector(changedfileset):
1352 def changed_file_collector(changedfileset):
1352 def collect_changed_files(clnode):
1353 def collect_changed_files(clnode):
1353 c = cl.read(clnode)
1354 c = cl.read(clnode)
1354 for fname in c[3]:
1355 for fname in c[3]:
1355 changedfileset[fname] = 1
1356 changedfileset[fname] = 1
1356 return collect_changed_files
1357 return collect_changed_files
1357
1358
1358 def lookuprevlink_func(revlog):
1359 def lookuprevlink_func(revlog):
1359 def lookuprevlink(n):
1360 def lookuprevlink(n):
1360 return cl.node(revlog.linkrev(n))
1361 return cl.node(revlog.linkrev(n))
1361 return lookuprevlink
1362 return lookuprevlink
1362
1363
1363 def gengroup():
1364 def gengroup():
1364 # construct a list of all changed files
1365 # construct a list of all changed files
1365 changedfiles = {}
1366 changedfiles = {}
1366
1367
1367 for chnk in cl.group(nodes, identity,
1368 for chnk in cl.group(nodes, identity,
1368 changed_file_collector(changedfiles)):
1369 changed_file_collector(changedfiles)):
1369 yield chnk
1370 yield chnk
1370 changedfiles = changedfiles.keys()
1371 changedfiles = changedfiles.keys()
1371 changedfiles.sort()
1372 changedfiles.sort()
1372
1373
1373 mnfst = self.manifest
1374 mnfst = self.manifest
1374 nodeiter = gennodelst(mnfst)
1375 nodeiter = gennodelst(mnfst)
1375 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1376 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1376 yield chnk
1377 yield chnk
1377
1378
1378 for fname in changedfiles:
1379 for fname in changedfiles:
1379 filerevlog = self.file(fname)
1380 filerevlog = self.file(fname)
1380 nodeiter = gennodelst(filerevlog)
1381 nodeiter = gennodelst(filerevlog)
1381 nodeiter = list(nodeiter)
1382 nodeiter = list(nodeiter)
1382 if nodeiter:
1383 if nodeiter:
1383 yield changegroup.genchunk(fname)
1384 yield changegroup.genchunk(fname)
1384 lookup = lookuprevlink_func(filerevlog)
1385 lookup = lookuprevlink_func(filerevlog)
1385 for chnk in filerevlog.group(nodeiter, lookup):
1386 for chnk in filerevlog.group(nodeiter, lookup):
1386 yield chnk
1387 yield chnk
1387
1388
1388 yield changegroup.closechunk()
1389 yield changegroup.closechunk()
1389 self.hook('outgoing', node=hex(nodes[0]), source=source)
1390 self.hook('outgoing', node=hex(nodes[0]), source=source)
1390
1391
1391 return util.chunkbuffer(gengroup())
1392 return util.chunkbuffer(gengroup())
1392
1393
1393 def addchangegroup(self, source):
1394 def addchangegroup(self, source):
1394 """add changegroup to repo.
1395 """add changegroup to repo.
1395 returns number of heads modified or added + 1."""
1396 returns number of heads modified or added + 1."""
1396
1397
1397 def csmap(x):
1398 def csmap(x):
1398 self.ui.debug(_("add changeset %s\n") % short(x))
1399 self.ui.debug(_("add changeset %s\n") % short(x))
1399 return cl.count()
1400 return cl.count()
1400
1401
1401 def revmap(x):
1402 def revmap(x):
1402 return cl.rev(x)
1403 return cl.rev(x)
1403
1404
1404 if not source:
1405 if not source:
1405 return 0
1406 return 0
1406
1407
1407 self.hook('prechangegroup', throw=True)
1408 self.hook('prechangegroup', throw=True)
1408
1409
1409 changesets = files = revisions = 0
1410 changesets = files = revisions = 0
1410
1411
1411 tr = self.transaction()
1412 tr = self.transaction()
1412
1413
1413 # write changelog and manifest data to temp files so
1414 # write changelog and manifest data to temp files so
1414 # concurrent readers will not see inconsistent view
1415 # concurrent readers will not see inconsistent view
1415 cl = appendfile.appendchangelog(self.opener)
1416 cl = appendfile.appendchangelog(self.opener)
1416
1417
1417 oldheads = len(cl.heads())
1418 oldheads = len(cl.heads())
1418
1419
1419 # pull off the changeset group
1420 # pull off the changeset group
1420 self.ui.status(_("adding changesets\n"))
1421 self.ui.status(_("adding changesets\n"))
1421 co = cl.tip()
1422 co = cl.tip()
1422 chunkiter = changegroup.chunkiter(source)
1423 chunkiter = changegroup.chunkiter(source)
1423 cn = cl.addgroup(chunkiter, csmap, tr, 1) # unique
1424 cn = cl.addgroup(chunkiter, csmap, tr, 1) # unique
1424 cnr, cor = map(cl.rev, (cn, co))
1425 cnr, cor = map(cl.rev, (cn, co))
1425 if cn == nullid:
1426 if cn == nullid:
1426 cnr = cor
1427 cnr = cor
1427 changesets = cnr - cor
1428 changesets = cnr - cor
1428
1429
1429 mf = appendfile.appendmanifest(self.opener)
1430 mf = appendfile.appendmanifest(self.opener)
1430
1431
1431 # pull off the manifest group
1432 # pull off the manifest group
1432 self.ui.status(_("adding manifests\n"))
1433 self.ui.status(_("adding manifests\n"))
1433 mm = mf.tip()
1434 mm = mf.tip()
1434 chunkiter = changegroup.chunkiter(source)
1435 chunkiter = changegroup.chunkiter(source)
1435 mo = mf.addgroup(chunkiter, revmap, tr)
1436 mo = mf.addgroup(chunkiter, revmap, tr)
1436
1437
1437 # process the files
1438 # process the files
1438 self.ui.status(_("adding file changes\n"))
1439 self.ui.status(_("adding file changes\n"))
1439 while 1:
1440 while 1:
1440 f = changegroup.getchunk(source)
1441 f = changegroup.getchunk(source)
1441 if not f:
1442 if not f:
1442 break
1443 break
1443 self.ui.debug(_("adding %s revisions\n") % f)
1444 self.ui.debug(_("adding %s revisions\n") % f)
1444 fl = self.file(f)
1445 fl = self.file(f)
1445 o = fl.count()
1446 o = fl.count()
1446 chunkiter = changegroup.chunkiter(source)
1447 chunkiter = changegroup.chunkiter(source)
1447 n = fl.addgroup(chunkiter, revmap, tr)
1448 n = fl.addgroup(chunkiter, revmap, tr)
1448 revisions += fl.count() - o
1449 revisions += fl.count() - o
1449 files += 1
1450 files += 1
1450
1451
1451 # write order here is important so concurrent readers will see
1452 # write order here is important so concurrent readers will see
1452 # consistent view of repo
1453 # consistent view of repo
1453 mf.writedata()
1454 mf.writedata()
1454 cl.writedata()
1455 cl.writedata()
1455
1456
1456 # make changelog and manifest see real files again
1457 # make changelog and manifest see real files again
1457 self.changelog = changelog.changelog(self.opener)
1458 self.changelog = changelog.changelog(self.opener)
1458 self.manifest = manifest.manifest(self.opener)
1459 self.manifest = manifest.manifest(self.opener)
1460 self.changelog.checkinlinesize(tr)
1461 self.changelog.checkinlinesize(tr)
1459
1462
1460 newheads = len(self.changelog.heads())
1463 newheads = len(self.changelog.heads())
1461 heads = ""
1464 heads = ""
1462 if oldheads and newheads > oldheads:
1465 if oldheads and newheads > oldheads:
1463 heads = _(" (+%d heads)") % (newheads - oldheads)
1466 heads = _(" (+%d heads)") % (newheads - oldheads)
1464
1467
1465 self.ui.status(_("added %d changesets"
1468 self.ui.status(_("added %d changesets"
1466 " with %d changes to %d files%s\n")
1469 " with %d changes to %d files%s\n")
1467 % (changesets, revisions, files, heads))
1470 % (changesets, revisions, files, heads))
1468
1471
1469 self.hook('pretxnchangegroup', throw=True,
1472 self.hook('pretxnchangegroup', throw=True,
1470 node=hex(self.changelog.node(cor+1)))
1473 node=hex(self.changelog.node(cor+1)))
1471
1474
1472 tr.close()
1475 tr.close()
1473
1476
1474 if changesets > 0:
1477 if changesets > 0:
1475 self.hook("changegroup", node=hex(self.changelog.node(cor+1)))
1478 self.hook("changegroup", node=hex(self.changelog.node(cor+1)))
1476
1479
1477 for i in range(cor + 1, cnr + 1):
1480 for i in range(cor + 1, cnr + 1):
1478 self.hook("incoming", node=hex(self.changelog.node(i)))
1481 self.hook("incoming", node=hex(self.changelog.node(i)))
1479
1482
1480 return newheads - oldheads + 1
1483 return newheads - oldheads + 1
1481
1484
1482 def update(self, node, allow=False, force=False, choose=None,
1485 def update(self, node, allow=False, force=False, choose=None,
1483 moddirstate=True, forcemerge=False, wlock=None):
1486 moddirstate=True, forcemerge=False, wlock=None):
1484 pl = self.dirstate.parents()
1487 pl = self.dirstate.parents()
1485 if not force and pl[1] != nullid:
1488 if not force and pl[1] != nullid:
1486 self.ui.warn(_("aborting: outstanding uncommitted merges\n"))
1489 self.ui.warn(_("aborting: outstanding uncommitted merges\n"))
1487 return 1
1490 return 1
1488
1491
1489 err = False
1492 err = False
1490
1493
1491 p1, p2 = pl[0], node
1494 p1, p2 = pl[0], node
1492 pa = self.changelog.ancestor(p1, p2)
1495 pa = self.changelog.ancestor(p1, p2)
1493 m1n = self.changelog.read(p1)[0]
1496 m1n = self.changelog.read(p1)[0]
1494 m2n = self.changelog.read(p2)[0]
1497 m2n = self.changelog.read(p2)[0]
1495 man = self.manifest.ancestor(m1n, m2n)
1498 man = self.manifest.ancestor(m1n, m2n)
1496 m1 = self.manifest.read(m1n)
1499 m1 = self.manifest.read(m1n)
1497 mf1 = self.manifest.readflags(m1n)
1500 mf1 = self.manifest.readflags(m1n)
1498 m2 = self.manifest.read(m2n).copy()
1501 m2 = self.manifest.read(m2n).copy()
1499 mf2 = self.manifest.readflags(m2n)
1502 mf2 = self.manifest.readflags(m2n)
1500 ma = self.manifest.read(man)
1503 ma = self.manifest.read(man)
1501 mfa = self.manifest.readflags(man)
1504 mfa = self.manifest.readflags(man)
1502
1505
1503 modified, added, removed, deleted, unknown = self.changes()
1506 modified, added, removed, deleted, unknown = self.changes()
1504
1507
1505 # is this a jump, or a merge? i.e. is there a linear path
1508 # is this a jump, or a merge? i.e. is there a linear path
1506 # from p1 to p2?
1509 # from p1 to p2?
1507 linear_path = (pa == p1 or pa == p2)
1510 linear_path = (pa == p1 or pa == p2)
1508
1511
1509 if allow and linear_path:
1512 if allow and linear_path:
1510 raise util.Abort(_("there is nothing to merge, "
1513 raise util.Abort(_("there is nothing to merge, "
1511 "just use 'hg update'"))
1514 "just use 'hg update'"))
1512 if allow and not forcemerge:
1515 if allow and not forcemerge:
1513 if modified or added or removed:
1516 if modified or added or removed:
1514 raise util.Abort(_("outstanding uncommitted changes"))
1517 raise util.Abort(_("outstanding uncommitted changes"))
1515 if not forcemerge and not force:
1518 if not forcemerge and not force:
1516 for f in unknown:
1519 for f in unknown:
1517 if f in m2:
1520 if f in m2:
1518 t1 = self.wread(f)
1521 t1 = self.wread(f)
1519 t2 = self.file(f).read(m2[f])
1522 t2 = self.file(f).read(m2[f])
1520 if cmp(t1, t2) != 0:
1523 if cmp(t1, t2) != 0:
1521 raise util.Abort(_("'%s' already exists in the working"
1524 raise util.Abort(_("'%s' already exists in the working"
1522 " dir and differs from remote") % f)
1525 " dir and differs from remote") % f)
1523
1526
1524 # resolve the manifest to determine which files
1527 # resolve the manifest to determine which files
1525 # we care about merging
1528 # we care about merging
1526 self.ui.note(_("resolving manifests\n"))
1529 self.ui.note(_("resolving manifests\n"))
1527 self.ui.debug(_(" force %s allow %s moddirstate %s linear %s\n") %
1530 self.ui.debug(_(" force %s allow %s moddirstate %s linear %s\n") %
1528 (force, allow, moddirstate, linear_path))
1531 (force, allow, moddirstate, linear_path))
1529 self.ui.debug(_(" ancestor %s local %s remote %s\n") %
1532 self.ui.debug(_(" ancestor %s local %s remote %s\n") %
1530 (short(man), short(m1n), short(m2n)))
1533 (short(man), short(m1n), short(m2n)))
1531
1534
1532 merge = {}
1535 merge = {}
1533 get = {}
1536 get = {}
1534 remove = []
1537 remove = []
1535
1538
1536 # construct a working dir manifest
1539 # construct a working dir manifest
1537 mw = m1.copy()
1540 mw = m1.copy()
1538 mfw = mf1.copy()
1541 mfw = mf1.copy()
1539 umap = dict.fromkeys(unknown)
1542 umap = dict.fromkeys(unknown)
1540
1543
1541 for f in added + modified + unknown:
1544 for f in added + modified + unknown:
1542 mw[f] = ""
1545 mw[f] = ""
1543 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1546 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1544
1547
1545 if moddirstate and not wlock:
1548 if moddirstate and not wlock:
1546 wlock = self.wlock()
1549 wlock = self.wlock()
1547
1550
1548 for f in deleted + removed:
1551 for f in deleted + removed:
1549 if f in mw:
1552 if f in mw:
1550 del mw[f]
1553 del mw[f]
1551
1554
1552 # If we're jumping between revisions (as opposed to merging),
1555 # If we're jumping between revisions (as opposed to merging),
1553 # and if neither the working directory nor the target rev has
1556 # and if neither the working directory nor the target rev has
1554 # the file, then we need to remove it from the dirstate, to
1557 # the file, then we need to remove it from the dirstate, to
1555 # prevent the dirstate from listing the file when it is no
1558 # prevent the dirstate from listing the file when it is no
1556 # longer in the manifest.
1559 # longer in the manifest.
1557 if moddirstate and linear_path and f not in m2:
1560 if moddirstate and linear_path and f not in m2:
1558 self.dirstate.forget((f,))
1561 self.dirstate.forget((f,))
1559
1562
1560 # Compare manifests
1563 # Compare manifests
1561 for f, n in mw.iteritems():
1564 for f, n in mw.iteritems():
1562 if choose and not choose(f):
1565 if choose and not choose(f):
1563 continue
1566 continue
1564 if f in m2:
1567 if f in m2:
1565 s = 0
1568 s = 0
1566
1569
1567 # is the wfile new since m1, and match m2?
1570 # is the wfile new since m1, and match m2?
1568 if f not in m1:
1571 if f not in m1:
1569 t1 = self.wread(f)
1572 t1 = self.wread(f)
1570 t2 = self.file(f).read(m2[f])
1573 t2 = self.file(f).read(m2[f])
1571 if cmp(t1, t2) == 0:
1574 if cmp(t1, t2) == 0:
1572 n = m2[f]
1575 n = m2[f]
1573 del t1, t2
1576 del t1, t2
1574
1577
1575 # are files different?
1578 # are files different?
1576 if n != m2[f]:
1579 if n != m2[f]:
1577 a = ma.get(f, nullid)
1580 a = ma.get(f, nullid)
1578 # are both different from the ancestor?
1581 # are both different from the ancestor?
1579 if n != a and m2[f] != a:
1582 if n != a and m2[f] != a:
1580 self.ui.debug(_(" %s versions differ, resolve\n") % f)
1583 self.ui.debug(_(" %s versions differ, resolve\n") % f)
1581 # merge executable bits
1584 # merge executable bits
1582 # "if we changed or they changed, change in merge"
1585 # "if we changed or they changed, change in merge"
1583 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1586 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1584 mode = ((a^b) | (a^c)) ^ a
1587 mode = ((a^b) | (a^c)) ^ a
1585 merge[f] = (m1.get(f, nullid), m2[f], mode)
1588 merge[f] = (m1.get(f, nullid), m2[f], mode)
1586 s = 1
1589 s = 1
1587 # are we clobbering?
1590 # are we clobbering?
1588 # is remote's version newer?
1591 # is remote's version newer?
1589 # or are we going back in time?
1592 # or are we going back in time?
1590 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1593 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1591 self.ui.debug(_(" remote %s is newer, get\n") % f)
1594 self.ui.debug(_(" remote %s is newer, get\n") % f)
1592 get[f] = m2[f]
1595 get[f] = m2[f]
1593 s = 1
1596 s = 1
1594 elif f in umap:
1597 elif f in umap:
1595 # this unknown file is the same as the checkout
1598 # this unknown file is the same as the checkout
1596 get[f] = m2[f]
1599 get[f] = m2[f]
1597
1600
1598 if not s and mfw[f] != mf2[f]:
1601 if not s and mfw[f] != mf2[f]:
1599 if force:
1602 if force:
1600 self.ui.debug(_(" updating permissions for %s\n") % f)
1603 self.ui.debug(_(" updating permissions for %s\n") % f)
1601 util.set_exec(self.wjoin(f), mf2[f])
1604 util.set_exec(self.wjoin(f), mf2[f])
1602 else:
1605 else:
1603 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1606 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1604 mode = ((a^b) | (a^c)) ^ a
1607 mode = ((a^b) | (a^c)) ^ a
1605 if mode != b:
1608 if mode != b:
1606 self.ui.debug(_(" updating permissions for %s\n")
1609 self.ui.debug(_(" updating permissions for %s\n")
1607 % f)
1610 % f)
1608 util.set_exec(self.wjoin(f), mode)
1611 util.set_exec(self.wjoin(f), mode)
1609 del m2[f]
1612 del m2[f]
1610 elif f in ma:
1613 elif f in ma:
1611 if n != ma[f]:
1614 if n != ma[f]:
1612 r = _("d")
1615 r = _("d")
1613 if not force and (linear_path or allow):
1616 if not force and (linear_path or allow):
1614 r = self.ui.prompt(
1617 r = self.ui.prompt(
1615 (_(" local changed %s which remote deleted\n") % f) +
1618 (_(" local changed %s which remote deleted\n") % f) +
1616 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1619 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1617 if r == _("d"):
1620 if r == _("d"):
1618 remove.append(f)
1621 remove.append(f)
1619 else:
1622 else:
1620 self.ui.debug(_("other deleted %s\n") % f)
1623 self.ui.debug(_("other deleted %s\n") % f)
1621 remove.append(f) # other deleted it
1624 remove.append(f) # other deleted it
1622 else:
1625 else:
1623 # file is created on branch or in working directory
1626 # file is created on branch or in working directory
1624 if force and f not in umap:
1627 if force and f not in umap:
1625 self.ui.debug(_("remote deleted %s, clobbering\n") % f)
1628 self.ui.debug(_("remote deleted %s, clobbering\n") % f)
1626 remove.append(f)
1629 remove.append(f)
1627 elif n == m1.get(f, nullid): # same as parent
1630 elif n == m1.get(f, nullid): # same as parent
1628 if p2 == pa: # going backwards?
1631 if p2 == pa: # going backwards?
1629 self.ui.debug(_("remote deleted %s\n") % f)
1632 self.ui.debug(_("remote deleted %s\n") % f)
1630 remove.append(f)
1633 remove.append(f)
1631 else:
1634 else:
1632 self.ui.debug(_("local modified %s, keeping\n") % f)
1635 self.ui.debug(_("local modified %s, keeping\n") % f)
1633 else:
1636 else:
1634 self.ui.debug(_("working dir created %s, keeping\n") % f)
1637 self.ui.debug(_("working dir created %s, keeping\n") % f)
1635
1638
1636 for f, n in m2.iteritems():
1639 for f, n in m2.iteritems():
1637 if choose and not choose(f):
1640 if choose and not choose(f):
1638 continue
1641 continue
1639 if f[0] == "/":
1642 if f[0] == "/":
1640 continue
1643 continue
1641 if f in ma and n != ma[f]:
1644 if f in ma and n != ma[f]:
1642 r = _("k")
1645 r = _("k")
1643 if not force and (linear_path or allow):
1646 if not force and (linear_path or allow):
1644 r = self.ui.prompt(
1647 r = self.ui.prompt(
1645 (_("remote changed %s which local deleted\n") % f) +
1648 (_("remote changed %s which local deleted\n") % f) +
1646 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1649 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1647 if r == _("k"):
1650 if r == _("k"):
1648 get[f] = n
1651 get[f] = n
1649 elif f not in ma:
1652 elif f not in ma:
1650 self.ui.debug(_("remote created %s\n") % f)
1653 self.ui.debug(_("remote created %s\n") % f)
1651 get[f] = n
1654 get[f] = n
1652 else:
1655 else:
1653 if force or p2 == pa: # going backwards?
1656 if force or p2 == pa: # going backwards?
1654 self.ui.debug(_("local deleted %s, recreating\n") % f)
1657 self.ui.debug(_("local deleted %s, recreating\n") % f)
1655 get[f] = n
1658 get[f] = n
1656 else:
1659 else:
1657 self.ui.debug(_("local deleted %s\n") % f)
1660 self.ui.debug(_("local deleted %s\n") % f)
1658
1661
1659 del mw, m1, m2, ma
1662 del mw, m1, m2, ma
1660
1663
1661 if force:
1664 if force:
1662 for f in merge:
1665 for f in merge:
1663 get[f] = merge[f][1]
1666 get[f] = merge[f][1]
1664 merge = {}
1667 merge = {}
1665
1668
1666 if linear_path or force:
1669 if linear_path or force:
1667 # we don't need to do any magic, just jump to the new rev
1670 # we don't need to do any magic, just jump to the new rev
1668 branch_merge = False
1671 branch_merge = False
1669 p1, p2 = p2, nullid
1672 p1, p2 = p2, nullid
1670 else:
1673 else:
1671 if not allow:
1674 if not allow:
1672 self.ui.status(_("this update spans a branch"
1675 self.ui.status(_("this update spans a branch"
1673 " affecting the following files:\n"))
1676 " affecting the following files:\n"))
1674 fl = merge.keys() + get.keys()
1677 fl = merge.keys() + get.keys()
1675 fl.sort()
1678 fl.sort()
1676 for f in fl:
1679 for f in fl:
1677 cf = ""
1680 cf = ""
1678 if f in merge:
1681 if f in merge:
1679 cf = _(" (resolve)")
1682 cf = _(" (resolve)")
1680 self.ui.status(" %s%s\n" % (f, cf))
1683 self.ui.status(" %s%s\n" % (f, cf))
1681 self.ui.warn(_("aborting update spanning branches!\n"))
1684 self.ui.warn(_("aborting update spanning branches!\n"))
1682 self.ui.status(_("(use 'hg merge' to merge across branches"
1685 self.ui.status(_("(use 'hg merge' to merge across branches"
1683 " or 'hg update -C' to lose changes)\n"))
1686 " or 'hg update -C' to lose changes)\n"))
1684 return 1
1687 return 1
1685 branch_merge = True
1688 branch_merge = True
1686
1689
1687 # get the files we don't need to change
1690 # get the files we don't need to change
1688 files = get.keys()
1691 files = get.keys()
1689 files.sort()
1692 files.sort()
1690 for f in files:
1693 for f in files:
1691 if f[0] == "/":
1694 if f[0] == "/":
1692 continue
1695 continue
1693 self.ui.note(_("getting %s\n") % f)
1696 self.ui.note(_("getting %s\n") % f)
1694 t = self.file(f).read(get[f])
1697 t = self.file(f).read(get[f])
1695 self.wwrite(f, t)
1698 self.wwrite(f, t)
1696 util.set_exec(self.wjoin(f), mf2[f])
1699 util.set_exec(self.wjoin(f), mf2[f])
1697 if moddirstate:
1700 if moddirstate:
1698 if branch_merge:
1701 if branch_merge:
1699 self.dirstate.update([f], 'n', st_mtime=-1)
1702 self.dirstate.update([f], 'n', st_mtime=-1)
1700 else:
1703 else:
1701 self.dirstate.update([f], 'n')
1704 self.dirstate.update([f], 'n')
1702
1705
1703 # merge the tricky bits
1706 # merge the tricky bits
1704 failedmerge = []
1707 failedmerge = []
1705 files = merge.keys()
1708 files = merge.keys()
1706 files.sort()
1709 files.sort()
1707 xp1 = hex(p1)
1710 xp1 = hex(p1)
1708 xp2 = hex(p2)
1711 xp2 = hex(p2)
1709 for f in files:
1712 for f in files:
1710 self.ui.status(_("merging %s\n") % f)
1713 self.ui.status(_("merging %s\n") % f)
1711 my, other, flag = merge[f]
1714 my, other, flag = merge[f]
1712 ret = self.merge3(f, my, other, xp1, xp2)
1715 ret = self.merge3(f, my, other, xp1, xp2)
1713 if ret:
1716 if ret:
1714 err = True
1717 err = True
1715 failedmerge.append(f)
1718 failedmerge.append(f)
1716 util.set_exec(self.wjoin(f), flag)
1719 util.set_exec(self.wjoin(f), flag)
1717 if moddirstate:
1720 if moddirstate:
1718 if branch_merge:
1721 if branch_merge:
1719 # We've done a branch merge, mark this file as merged
1722 # We've done a branch merge, mark this file as merged
1720 # so that we properly record the merger later
1723 # so that we properly record the merger later
1721 self.dirstate.update([f], 'm')
1724 self.dirstate.update([f], 'm')
1722 else:
1725 else:
1723 # We've update-merged a locally modified file, so
1726 # We've update-merged a locally modified file, so
1724 # we set the dirstate to emulate a normal checkout
1727 # we set the dirstate to emulate a normal checkout
1725 # of that file some time in the past. Thus our
1728 # of that file some time in the past. Thus our
1726 # merge will appear as a normal local file
1729 # merge will appear as a normal local file
1727 # modification.
1730 # modification.
1728 f_len = len(self.file(f).read(other))
1731 f_len = len(self.file(f).read(other))
1729 self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
1732 self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
1730
1733
1731 remove.sort()
1734 remove.sort()
1732 for f in remove:
1735 for f in remove:
1733 self.ui.note(_("removing %s\n") % f)
1736 self.ui.note(_("removing %s\n") % f)
1734 util.audit_path(f)
1737 util.audit_path(f)
1735 try:
1738 try:
1736 util.unlink(self.wjoin(f))
1739 util.unlink(self.wjoin(f))
1737 except OSError, inst:
1740 except OSError, inst:
1738 if inst.errno != errno.ENOENT:
1741 if inst.errno != errno.ENOENT:
1739 self.ui.warn(_("update failed to remove %s: %s!\n") %
1742 self.ui.warn(_("update failed to remove %s: %s!\n") %
1740 (f, inst.strerror))
1743 (f, inst.strerror))
1741 if moddirstate:
1744 if moddirstate:
1742 if branch_merge:
1745 if branch_merge:
1743 self.dirstate.update(remove, 'r')
1746 self.dirstate.update(remove, 'r')
1744 else:
1747 else:
1745 self.dirstate.forget(remove)
1748 self.dirstate.forget(remove)
1746
1749
1747 if moddirstate:
1750 if moddirstate:
1748 self.dirstate.setparents(p1, p2)
1751 self.dirstate.setparents(p1, p2)
1749
1752
1750 stat = ((len(get), _("updated")),
1753 stat = ((len(get), _("updated")),
1751 (len(merge) - len(failedmerge), _("merged")),
1754 (len(merge) - len(failedmerge), _("merged")),
1752 (len(remove), _("removed")),
1755 (len(remove), _("removed")),
1753 (len(failedmerge), _("unresolved")))
1756 (len(failedmerge), _("unresolved")))
1754 note = ", ".join([_("%d files %s") % s for s in stat])
1757 note = ", ".join([_("%d files %s") % s for s in stat])
1755 self.ui.note("%s\n" % note)
1758 self.ui.note("%s\n" % note)
1756 if moddirstate and branch_merge:
1759 if moddirstate and branch_merge:
1757 self.ui.note(_("(branch merge, don't forget to commit)\n"))
1760 self.ui.note(_("(branch merge, don't forget to commit)\n"))
1758
1761
1759 return err
1762 return err
1760
1763
1761 def merge3(self, fn, my, other, p1, p2):
1764 def merge3(self, fn, my, other, p1, p2):
1762 """perform a 3-way merge in the working directory"""
1765 """perform a 3-way merge in the working directory"""
1763
1766
1764 def temp(prefix, node):
1767 def temp(prefix, node):
1765 pre = "%s~%s." % (os.path.basename(fn), prefix)
1768 pre = "%s~%s." % (os.path.basename(fn), prefix)
1766 (fd, name) = tempfile.mkstemp("", pre)
1769 (fd, name) = tempfile.mkstemp("", pre)
1767 f = os.fdopen(fd, "wb")
1770 f = os.fdopen(fd, "wb")
1768 self.wwrite(fn, fl.read(node), f)
1771 self.wwrite(fn, fl.read(node), f)
1769 f.close()
1772 f.close()
1770 return name
1773 return name
1771
1774
1772 fl = self.file(fn)
1775 fl = self.file(fn)
1773 base = fl.ancestor(my, other)
1776 base = fl.ancestor(my, other)
1774 a = self.wjoin(fn)
1777 a = self.wjoin(fn)
1775 b = temp("base", base)
1778 b = temp("base", base)
1776 c = temp("other", other)
1779 c = temp("other", other)
1777
1780
1778 self.ui.note(_("resolving %s\n") % fn)
1781 self.ui.note(_("resolving %s\n") % fn)
1779 self.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
1782 self.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
1780 (fn, short(my), short(other), short(base)))
1783 (fn, short(my), short(other), short(base)))
1781
1784
1782 cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
1785 cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
1783 or "hgmerge")
1786 or "hgmerge")
1784 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=self.root,
1787 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=self.root,
1785 environ={'HG_FILE': fn,
1788 environ={'HG_FILE': fn,
1786 'HG_MY_NODE': p1,
1789 'HG_MY_NODE': p1,
1787 'HG_OTHER_NODE': p2,
1790 'HG_OTHER_NODE': p2,
1788 'HG_FILE_MY_NODE': hex(my),
1791 'HG_FILE_MY_NODE': hex(my),
1789 'HG_FILE_OTHER_NODE': hex(other),
1792 'HG_FILE_OTHER_NODE': hex(other),
1790 'HG_FILE_BASE_NODE': hex(base)})
1793 'HG_FILE_BASE_NODE': hex(base)})
1791 if r:
1794 if r:
1792 self.ui.warn(_("merging %s failed!\n") % fn)
1795 self.ui.warn(_("merging %s failed!\n") % fn)
1793
1796
1794 os.unlink(b)
1797 os.unlink(b)
1795 os.unlink(c)
1798 os.unlink(c)
1796 return r
1799 return r
1797
1800
1798 def verify(self):
1801 def verify(self):
1799 filelinkrevs = {}
1802 filelinkrevs = {}
1800 filenodes = {}
1803 filenodes = {}
1801 changesets = revisions = files = 0
1804 changesets = revisions = files = 0
1802 errors = [0]
1805 errors = [0]
1803 neededmanifests = {}
1806 neededmanifests = {}
1804
1807
1805 def err(msg):
1808 def err(msg):
1806 self.ui.warn(msg + "\n")
1809 self.ui.warn(msg + "\n")
1807 errors[0] += 1
1810 errors[0] += 1
1808
1811
1809 def checksize(obj, name):
1812 def checksize(obj, name):
1810 d = obj.checksize()
1813 d = obj.checksize()
1811 if d[0]:
1814 if d[0]:
1812 err(_("%s data length off by %d bytes") % (name, d[0]))
1815 err(_("%s data length off by %d bytes") % (name, d[0]))
1813 if d[1]:
1816 if d[1]:
1814 err(_("%s index contains %d extra bytes") % (name, d[1]))
1817 err(_("%s index contains %d extra bytes") % (name, d[1]))
1815
1818
1816 seen = {}
1819 seen = {}
1817 self.ui.status(_("checking changesets\n"))
1820 self.ui.status(_("checking changesets\n"))
1818 checksize(self.changelog, "changelog")
1821 checksize(self.changelog, "changelog")
1819
1822
1820 for i in range(self.changelog.count()):
1823 for i in range(self.changelog.count()):
1821 changesets += 1
1824 changesets += 1
1822 n = self.changelog.node(i)
1825 n = self.changelog.node(i)
1823 l = self.changelog.linkrev(n)
1826 l = self.changelog.linkrev(n)
1824 if l != i:
1827 if l != i:
1825 err(_("incorrect link (%d) for changeset revision %d") %(l, i))
1828 err(_("incorrect link (%d) for changeset revision %d") %(l, i))
1826 if n in seen:
1829 if n in seen:
1827 err(_("duplicate changeset at revision %d") % i)
1830 err(_("duplicate changeset at revision %d") % i)
1828 seen[n] = 1
1831 seen[n] = 1
1829
1832
1830 for p in self.changelog.parents(n):
1833 for p in self.changelog.parents(n):
1831 if p not in self.changelog.nodemap:
1834 if p not in self.changelog.nodemap:
1832 err(_("changeset %s has unknown parent %s") %
1835 err(_("changeset %s has unknown parent %s") %
1833 (short(n), short(p)))
1836 (short(n), short(p)))
1834 try:
1837 try:
1835 changes = self.changelog.read(n)
1838 changes = self.changelog.read(n)
1836 except KeyboardInterrupt:
1839 except KeyboardInterrupt:
1837 self.ui.warn(_("interrupted"))
1840 self.ui.warn(_("interrupted"))
1838 raise
1841 raise
1839 except Exception, inst:
1842 except Exception, inst:
1840 err(_("unpacking changeset %s: %s") % (short(n), inst))
1843 err(_("unpacking changeset %s: %s") % (short(n), inst))
1841 continue
1844 continue
1842
1845
1843 neededmanifests[changes[0]] = n
1846 neededmanifests[changes[0]] = n
1844
1847
1845 for f in changes[3]:
1848 for f in changes[3]:
1846 filelinkrevs.setdefault(f, []).append(i)
1849 filelinkrevs.setdefault(f, []).append(i)
1847
1850
1848 seen = {}
1851 seen = {}
1849 self.ui.status(_("checking manifests\n"))
1852 self.ui.status(_("checking manifests\n"))
1850 checksize(self.manifest, "manifest")
1853 checksize(self.manifest, "manifest")
1851
1854
1852 for i in range(self.manifest.count()):
1855 for i in range(self.manifest.count()):
1853 n = self.manifest.node(i)
1856 n = self.manifest.node(i)
1854 l = self.manifest.linkrev(n)
1857 l = self.manifest.linkrev(n)
1855
1858
1856 if l < 0 or l >= self.changelog.count():
1859 if l < 0 or l >= self.changelog.count():
1857 err(_("bad manifest link (%d) at revision %d") % (l, i))
1860 err(_("bad manifest link (%d) at revision %d") % (l, i))
1858
1861
1859 if n in neededmanifests:
1862 if n in neededmanifests:
1860 del neededmanifests[n]
1863 del neededmanifests[n]
1861
1864
1862 if n in seen:
1865 if n in seen:
1863 err(_("duplicate manifest at revision %d") % i)
1866 err(_("duplicate manifest at revision %d") % i)
1864
1867
1865 seen[n] = 1
1868 seen[n] = 1
1866
1869
1867 for p in self.manifest.parents(n):
1870 for p in self.manifest.parents(n):
1868 if p not in self.manifest.nodemap:
1871 if p not in self.manifest.nodemap:
1869 err(_("manifest %s has unknown parent %s") %
1872 err(_("manifest %s has unknown parent %s") %
1870 (short(n), short(p)))
1873 (short(n), short(p)))
1871
1874
1872 try:
1875 try:
1873 delta = mdiff.patchtext(self.manifest.delta(n))
1876 delta = mdiff.patchtext(self.manifest.delta(n))
1874 except KeyboardInterrupt:
1877 except KeyboardInterrupt:
1875 self.ui.warn(_("interrupted"))
1878 self.ui.warn(_("interrupted"))
1876 raise
1879 raise
1877 except Exception, inst:
1880 except Exception, inst:
1878 err(_("unpacking manifest %s: %s") % (short(n), inst))
1881 err(_("unpacking manifest %s: %s") % (short(n), inst))
1879 continue
1882 continue
1880
1883
1881 try:
1884 try:
1882 ff = [ l.split('\0') for l in delta.splitlines() ]
1885 ff = [ l.split('\0') for l in delta.splitlines() ]
1883 for f, fn in ff:
1886 for f, fn in ff:
1884 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1887 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1885 except (ValueError, TypeError), inst:
1888 except (ValueError, TypeError), inst:
1886 err(_("broken delta in manifest %s: %s") % (short(n), inst))
1889 err(_("broken delta in manifest %s: %s") % (short(n), inst))
1887
1890
1888 self.ui.status(_("crosschecking files in changesets and manifests\n"))
1891 self.ui.status(_("crosschecking files in changesets and manifests\n"))
1889
1892
1890 for m, c in neededmanifests.items():
1893 for m, c in neededmanifests.items():
1891 err(_("Changeset %s refers to unknown manifest %s") %
1894 err(_("Changeset %s refers to unknown manifest %s") %
1892 (short(m), short(c)))
1895 (short(m), short(c)))
1893 del neededmanifests
1896 del neededmanifests
1894
1897
1895 for f in filenodes:
1898 for f in filenodes:
1896 if f not in filelinkrevs:
1899 if f not in filelinkrevs:
1897 err(_("file %s in manifest but not in changesets") % f)
1900 err(_("file %s in manifest but not in changesets") % f)
1898
1901
1899 for f in filelinkrevs:
1902 for f in filelinkrevs:
1900 if f not in filenodes:
1903 if f not in filenodes:
1901 err(_("file %s in changeset but not in manifest") % f)
1904 err(_("file %s in changeset but not in manifest") % f)
1902
1905
1903 self.ui.status(_("checking files\n"))
1906 self.ui.status(_("checking files\n"))
1904 ff = filenodes.keys()
1907 ff = filenodes.keys()
1905 ff.sort()
1908 ff.sort()
1906 for f in ff:
1909 for f in ff:
1907 if f == "/dev/null":
1910 if f == "/dev/null":
1908 continue
1911 continue
1909 files += 1
1912 files += 1
1910 if not f:
1913 if not f:
1911 err(_("file without name in manifest %s") % short(n))
1914 err(_("file without name in manifest %s") % short(n))
1912 continue
1915 continue
1913 fl = self.file(f)
1916 fl = self.file(f)
1914 checksize(fl, f)
1917 checksize(fl, f)
1915
1918
1916 nodes = {nullid: 1}
1919 nodes = {nullid: 1}
1917 seen = {}
1920 seen = {}
1918 for i in range(fl.count()):
1921 for i in range(fl.count()):
1919 revisions += 1
1922 revisions += 1
1920 n = fl.node(i)
1923 n = fl.node(i)
1921
1924
1922 if n in seen:
1925 if n in seen:
1923 err(_("%s: duplicate revision %d") % (f, i))
1926 err(_("%s: duplicate revision %d") % (f, i))
1924 if n not in filenodes[f]:
1927 if n not in filenodes[f]:
1925 err(_("%s: %d:%s not in manifests") % (f, i, short(n)))
1928 err(_("%s: %d:%s not in manifests") % (f, i, short(n)))
1926 else:
1929 else:
1927 del filenodes[f][n]
1930 del filenodes[f][n]
1928
1931
1929 flr = fl.linkrev(n)
1932 flr = fl.linkrev(n)
1930 if flr not in filelinkrevs.get(f, []):
1933 if flr not in filelinkrevs.get(f, []):
1931 err(_("%s:%s points to unexpected changeset %d")
1934 err(_("%s:%s points to unexpected changeset %d")
1932 % (f, short(n), flr))
1935 % (f, short(n), flr))
1933 else:
1936 else:
1934 filelinkrevs[f].remove(flr)
1937 filelinkrevs[f].remove(flr)
1935
1938
1936 # verify contents
1939 # verify contents
1937 try:
1940 try:
1938 t = fl.read(n)
1941 t = fl.read(n)
1939 except KeyboardInterrupt:
1942 except KeyboardInterrupt:
1940 self.ui.warn(_("interrupted"))
1943 self.ui.warn(_("interrupted"))
1941 raise
1944 raise
1942 except Exception, inst:
1945 except Exception, inst:
1943 err(_("unpacking file %s %s: %s") % (f, short(n), inst))
1946 err(_("unpacking file %s %s: %s") % (f, short(n), inst))
1944
1947
1945 # verify parents
1948 # verify parents
1946 (p1, p2) = fl.parents(n)
1949 (p1, p2) = fl.parents(n)
1947 if p1 not in nodes:
1950 if p1 not in nodes:
1948 err(_("file %s:%s unknown parent 1 %s") %
1951 err(_("file %s:%s unknown parent 1 %s") %
1949 (f, short(n), short(p1)))
1952 (f, short(n), short(p1)))
1950 if p2 not in nodes:
1953 if p2 not in nodes:
1951 err(_("file %s:%s unknown parent 2 %s") %
1954 err(_("file %s:%s unknown parent 2 %s") %
1952 (f, short(n), short(p1)))
1955 (f, short(n), short(p1)))
1953 nodes[n] = 1
1956 nodes[n] = 1
1954
1957
1955 # cross-check
1958 # cross-check
1956 for node in filenodes[f]:
1959 for node in filenodes[f]:
1957 err(_("node %s in manifests not in %s") % (hex(node), f))
1960 err(_("node %s in manifests not in %s") % (hex(node), f))
1958
1961
1959 self.ui.status(_("%d files, %d changesets, %d total revisions\n") %
1962 self.ui.status(_("%d files, %d changesets, %d total revisions\n") %
1960 (files, changesets, revisions))
1963 (files, changesets, revisions))
1961
1964
1962 if errors[0]:
1965 if errors[0]:
1963 self.ui.warn(_("%d integrity errors encountered!\n") % errors[0])
1966 self.ui.warn(_("%d integrity errors encountered!\n") % errors[0])
1964 return 1
1967 return 1
1965
1968
1966 # used to avoid circular references so destructors work
1969 # used to avoid circular references so destructors work
1967 def aftertrans(base):
1970 def aftertrans(base):
1968 p = base
1971 p = base
1969 def a():
1972 def a():
1970 util.rename(os.path.join(p, "journal"), os.path.join(p, "undo"))
1973 util.rename(os.path.join(p, "journal"), os.path.join(p, "undo"))
1971 util.rename(os.path.join(p, "journal.dirstate"),
1974 util.rename(os.path.join(p, "journal.dirstate"),
1972 os.path.join(p, "undo.dirstate"))
1975 os.path.join(p, "undo.dirstate"))
1973 return a
1976 return a
1974
1977
@@ -1,1062 +1,1064
1 """
1 """
2 revlog.py - storage back-end for mercurial
2 revlog.py - storage back-end for mercurial
3
3
4 This provides efficient delta storage with O(1) retrieve and append
4 This provides efficient delta storage with O(1) retrieve and append
5 and O(changes) merge between branches
5 and O(changes) merge between branches
6
6
7 Copyright 2005 Matt Mackall <mpm@selenic.com>
7 Copyright 2005 Matt Mackall <mpm@selenic.com>
8
8
9 This software may be used and distributed according to the terms
9 This software may be used and distributed according to the terms
10 of the GNU General Public License, incorporated herein by reference.
10 of the GNU General Public License, incorporated herein by reference.
11 """
11 """
12
12
13 from node import *
13 from node import *
14 from i18n import gettext as _
14 from i18n import gettext as _
15 from demandload import demandload
15 from demandload import demandload
16 demandload(globals(), "binascii changegroup errno heapq mdiff os")
16 demandload(globals(), "binascii changegroup errno heapq mdiff os")
17 demandload(globals(), "sha struct zlib")
17 demandload(globals(), "sha struct zlib")
18
18
19 # revlog version strings
19 # revlog version strings
20 REVLOGV0 = 0
20 REVLOGV0 = 0
21 REVLOGNG = 1
21 REVLOGNG = 1
22
22
23 # revlog flags
23 # revlog flags
24 REVLOGNGINLINEDATA = (1 << 16)
24 REVLOGNGINLINEDATA = (1 << 16)
25
25
26 def flagstr(flag):
26 def flagstr(flag):
27 if flag == "inline":
27 if flag == "inline":
28 return REVLOGNGINLINEDATA
28 return REVLOGNGINLINEDATA
29 raise RevlogError(_("unknown revlog flag %s" % flag))
29 raise RevlogError(_("unknown revlog flag %s" % flag))
30
30
31 def hash(text, p1, p2):
31 def hash(text, p1, p2):
32 """generate a hash from the given text and its parent hashes
32 """generate a hash from the given text and its parent hashes
33
33
34 This hash combines both the current file contents and its history
34 This hash combines both the current file contents and its history
35 in a manner that makes it easy to distinguish nodes with the same
35 in a manner that makes it easy to distinguish nodes with the same
36 content in the revision graph.
36 content in the revision graph.
37 """
37 """
38 l = [p1, p2]
38 l = [p1, p2]
39 l.sort()
39 l.sort()
40 s = sha.new(l[0])
40 s = sha.new(l[0])
41 s.update(l[1])
41 s.update(l[1])
42 s.update(text)
42 s.update(text)
43 return s.digest()
43 return s.digest()
44
44
45 def compress(text):
45 def compress(text):
46 """ generate a possibly-compressed representation of text """
46 """ generate a possibly-compressed representation of text """
47 if not text: return ("", text)
47 if not text: return ("", text)
48 if len(text) < 44:
48 if len(text) < 44:
49 if text[0] == '\0': return ("", text)
49 if text[0] == '\0': return ("", text)
50 return ('u', text)
50 return ('u', text)
51 bin = zlib.compress(text)
51 bin = zlib.compress(text)
52 if len(bin) > len(text):
52 if len(bin) > len(text):
53 if text[0] == '\0': return ("", text)
53 if text[0] == '\0': return ("", text)
54 return ('u', text)
54 return ('u', text)
55 return ("", bin)
55 return ("", bin)
56
56
57 def decompress(bin):
57 def decompress(bin):
58 """ decompress the given input """
58 """ decompress the given input """
59 if not bin: return bin
59 if not bin: return bin
60 t = bin[0]
60 t = bin[0]
61 if t == '\0': return bin
61 if t == '\0': return bin
62 if t == 'x': return zlib.decompress(bin)
62 if t == 'x': return zlib.decompress(bin)
63 if t == 'u': return bin[1:]
63 if t == 'u': return bin[1:]
64 raise RevlogError(_("unknown compression type %r") % t)
64 raise RevlogError(_("unknown compression type %r") % t)
65
65
66 indexformatv0 = ">4l20s20s20s"
66 indexformatv0 = ">4l20s20s20s"
67 # index ng:
67 # index ng:
68 # 6 bytes offset
68 # 6 bytes offset
69 # 2 bytes flags
69 # 2 bytes flags
70 # 4 bytes compressed length
70 # 4 bytes compressed length
71 # 4 bytes uncompressed length
71 # 4 bytes uncompressed length
72 # 4 bytes: base rev
72 # 4 bytes: base rev
73 # 4 bytes link rev
73 # 4 bytes link rev
74 # 4 bytes parent 1 rev
74 # 4 bytes parent 1 rev
75 # 4 bytes parent 2 rev
75 # 4 bytes parent 2 rev
76 # 32 bytes: nodeid
76 # 32 bytes: nodeid
77 indexformatng = ">Qiiiiii20s12x"
77 indexformatng = ">Qiiiiii20s12x"
78 versionformat = ">i"
78 versionformat = ">i"
79
79
80 class lazyparser(object):
80 class lazyparser(object):
81 """
81 """
82 this class avoids the need to parse the entirety of large indices
82 this class avoids the need to parse the entirety of large indices
83
83
84 By default we parse and load 1000 entries at a time.
84 By default we parse and load 1000 entries at a time.
85
85
86 If no position is specified, we load the whole index, and replace
86 If no position is specified, we load the whole index, and replace
87 the lazy objects in revlog with the underlying objects for
87 the lazy objects in revlog with the underlying objects for
88 efficiency in cases where we look at most of the nodes.
88 efficiency in cases where we look at most of the nodes.
89 """
89 """
90 def __init__(self, data, revlog, indexformat):
90 def __init__(self, data, revlog, indexformat):
91 self.data = data
91 self.data = data
92 self.s = struct.calcsize(indexformat)
92 self.s = struct.calcsize(indexformat)
93 self.indexformat = indexformat
93 self.indexformat = indexformat
94 self.l = len(data)/self.s
94 self.l = len(data)/self.s
95 self.index = [None] * self.l
95 self.index = [None] * self.l
96 self.map = {nullid: -1}
96 self.map = {nullid: -1}
97 self.all = 0
97 self.all = 0
98 self.revlog = revlog
98 self.revlog = revlog
99
99
100 def load(self, pos=None):
100 def load(self, pos=None):
101 if self.all: return
101 if self.all: return
102 if pos is not None:
102 if pos is not None:
103 block = pos / 1000
103 block = pos / 1000
104 i = block * 1000
104 i = block * 1000
105 end = min(self.l, i + 1000)
105 end = min(self.l, i + 1000)
106 else:
106 else:
107 self.all = 1
107 self.all = 1
108 i = 0
108 i = 0
109 end = self.l
109 end = self.l
110 self.revlog.index = self.index
110 self.revlog.index = self.index
111 self.revlog.nodemap = self.map
111 self.revlog.nodemap = self.map
112
112
113 while i < end:
113 while i < end:
114 if not self.index[i]:
114 if not self.index[i]:
115 d = self.data[i * self.s: (i + 1) * self.s]
115 d = self.data[i * self.s: (i + 1) * self.s]
116 e = struct.unpack(self.indexformat, d)
116 e = struct.unpack(self.indexformat, d)
117 self.index[i] = e
117 self.index[i] = e
118 self.map[e[-1]] = i
118 self.map[e[-1]] = i
119 i += 1
119 i += 1
120
120
121 class lazyindex(object):
121 class lazyindex(object):
122 """a lazy version of the index array"""
122 """a lazy version of the index array"""
123 def __init__(self, parser):
123 def __init__(self, parser):
124 self.p = parser
124 self.p = parser
125 def __len__(self):
125 def __len__(self):
126 return len(self.p.index)
126 return len(self.p.index)
127 def load(self, pos):
127 def load(self, pos):
128 if pos < 0:
128 if pos < 0:
129 pos += len(self.p.index)
129 pos += len(self.p.index)
130 self.p.load(pos)
130 self.p.load(pos)
131 return self.p.index[pos]
131 return self.p.index[pos]
132 def __getitem__(self, pos):
132 def __getitem__(self, pos):
133 return self.p.index[pos] or self.load(pos)
133 return self.p.index[pos] or self.load(pos)
134 def __setitem__(self, pos, item):
134 def __setitem__(self, pos, item):
135 self.p.index[pos] = item
135 self.p.index[pos] = item
136 def __delitem__(self, pos):
136 def __delitem__(self, pos):
137 del self.p.index[pos]
137 del self.p.index[pos]
138 def append(self, e):
138 def append(self, e):
139 self.p.index.append(e)
139 self.p.index.append(e)
140
140
141 class lazymap(object):
141 class lazymap(object):
142 """a lazy version of the node map"""
142 """a lazy version of the node map"""
143 def __init__(self, parser):
143 def __init__(self, parser):
144 self.p = parser
144 self.p = parser
145 def load(self, key):
145 def load(self, key):
146 if self.p.all: return
146 if self.p.all: return
147 n = self.p.data.find(key)
147 n = self.p.data.find(key)
148 if n < 0:
148 if n < 0:
149 raise KeyError(key)
149 raise KeyError(key)
150 pos = n / self.p.s
150 pos = n / self.p.s
151 self.p.load(pos)
151 self.p.load(pos)
152 def __contains__(self, key):
152 def __contains__(self, key):
153 self.p.load()
153 self.p.load()
154 return key in self.p.map
154 return key in self.p.map
155 def __iter__(self):
155 def __iter__(self):
156 yield nullid
156 yield nullid
157 for i in xrange(self.p.l):
157 for i in xrange(self.p.l):
158 try:
158 try:
159 yield self.p.index[i][-1]
159 yield self.p.index[i][-1]
160 except:
160 except:
161 self.p.load(i)
161 self.p.load(i)
162 yield self.p.index[i][-1]
162 yield self.p.index[i][-1]
163 def __getitem__(self, key):
163 def __getitem__(self, key):
164 try:
164 try:
165 return self.p.map[key]
165 return self.p.map[key]
166 except KeyError:
166 except KeyError:
167 try:
167 try:
168 self.load(key)
168 self.load(key)
169 return self.p.map[key]
169 return self.p.map[key]
170 except KeyError:
170 except KeyError:
171 raise KeyError("node " + hex(key))
171 raise KeyError("node " + hex(key))
172 def __setitem__(self, key, val):
172 def __setitem__(self, key, val):
173 self.p.map[key] = val
173 self.p.map[key] = val
174 def __delitem__(self, key):
174 def __delitem__(self, key):
175 del self.p.map[key]
175 del self.p.map[key]
176
176
177 class RevlogError(Exception): pass
177 class RevlogError(Exception): pass
178
178
179 class revlog(object):
179 class revlog(object):
180 """
180 """
181 the underlying revision storage object
181 the underlying revision storage object
182
182
183 A revlog consists of two parts, an index and the revision data.
183 A revlog consists of two parts, an index and the revision data.
184
184
185 The index is a file with a fixed record size containing
185 The index is a file with a fixed record size containing
186 information on each revision, includings its nodeid (hash), the
186 information on each revision, includings its nodeid (hash), the
187 nodeids of its parents, the position and offset of its data within
187 nodeids of its parents, the position and offset of its data within
188 the data file, and the revision it's based on. Finally, each entry
188 the data file, and the revision it's based on. Finally, each entry
189 contains a linkrev entry that can serve as a pointer to external
189 contains a linkrev entry that can serve as a pointer to external
190 data.
190 data.
191
191
192 The revision data itself is a linear collection of data chunks.
192 The revision data itself is a linear collection of data chunks.
193 Each chunk represents a revision and is usually represented as a
193 Each chunk represents a revision and is usually represented as a
194 delta against the previous chunk. To bound lookup time, runs of
194 delta against the previous chunk. To bound lookup time, runs of
195 deltas are limited to about 2 times the length of the original
195 deltas are limited to about 2 times the length of the original
196 version data. This makes retrieval of a version proportional to
196 version data. This makes retrieval of a version proportional to
197 its size, or O(1) relative to the number of revisions.
197 its size, or O(1) relative to the number of revisions.
198
198
199 Both pieces of the revlog are written to in an append-only
199 Both pieces of the revlog are written to in an append-only
200 fashion, which means we never need to rewrite a file to insert or
200 fashion, which means we never need to rewrite a file to insert or
201 remove data, and can use some simple techniques to avoid the need
201 remove data, and can use some simple techniques to avoid the need
202 for locking while reading.
202 for locking while reading.
203 """
203 """
204 def __init__(self, opener, indexfile, datafile, defversion=0):
204 def __init__(self, opener, indexfile, datafile, defversion=0):
205 """
205 """
206 create a revlog object
206 create a revlog object
207
207
208 opener is a function that abstracts the file opening operation
208 opener is a function that abstracts the file opening operation
209 and can be used to implement COW semantics or the like.
209 and can be used to implement COW semantics or the like.
210 """
210 """
211 self.indexfile = indexfile
211 self.indexfile = indexfile
212 self.datafile = datafile
212 self.datafile = datafile
213 self.opener = opener
213 self.opener = opener
214
214
215 self.indexstat = None
215 self.indexstat = None
216 self.cache = None
216 self.cache = None
217 self.chunkcache = None
217 self.chunkcache = None
218 self.defversion = defversion
218 self.defversion = defversion
219 self.load()
219 self.load()
220
220
221 def load(self):
221 def load(self):
222 v = self.defversion
222 v = self.defversion
223 try:
223 try:
224 f = self.opener(self.indexfile)
224 f = self.opener(self.indexfile)
225 i = f.read()
225 i = f.read()
226 except IOError, inst:
226 except IOError, inst:
227 if inst.errno != errno.ENOENT:
227 if inst.errno != errno.ENOENT:
228 raise
228 raise
229 i = ""
229 i = ""
230 else:
230 else:
231 try:
231 try:
232 st = os.fstat(f.fileno())
232 st = os.fstat(f.fileno())
233 except AttributeError, inst:
233 except AttributeError, inst:
234 st = None
234 st = None
235 else:
235 else:
236 oldst = self.indexstat
236 oldst = self.indexstat
237 if (oldst and st.st_dev == oldst.st_dev
237 if (oldst and st.st_dev == oldst.st_dev
238 and st.st_ino == oldst.st_ino
238 and st.st_ino == oldst.st_ino
239 and st.st_mtime == oldst.st_mtime
239 and st.st_mtime == oldst.st_mtime
240 and st.st_ctime == oldst.st_ctime):
240 and st.st_ctime == oldst.st_ctime):
241 return
241 return
242 self.indexstat = st
242 self.indexstat = st
243 if len(i) > 0:
243 if len(i) > 0:
244 v = struct.unpack(versionformat, i[:4])[0]
244 v = struct.unpack(versionformat, i[:4])[0]
245 flags = v & ~0xFFFF
245 flags = v & ~0xFFFF
246 fmt = v & 0xFFFF
246 fmt = v & 0xFFFF
247 if fmt == 0:
247 if fmt == 0:
248 if flags:
248 if flags:
249 raise RevlogError(_("index %s invalid flags %x for format v0" %
249 raise RevlogError(_("index %s invalid flags %x for format v0" %
250 (self.indexfile, flags)))
250 (self.indexfile, flags)))
251 elif fmt == REVLOGNG:
251 elif fmt == REVLOGNG:
252 if flags & ~REVLOGNGINLINEDATA:
252 if flags & ~REVLOGNGINLINEDATA:
253 raise RevlogError(_("index %s invalid flags %x for revlogng" %
253 raise RevlogError(_("index %s invalid flags %x for revlogng" %
254 (self.indexfile, flags)))
254 (self.indexfile, flags)))
255 else:
255 else:
256 raise RevlogError(_("index %s invalid format %d" %
256 raise RevlogError(_("index %s invalid format %d" %
257 (self.indexfile, fmt)))
257 (self.indexfile, fmt)))
258 self.version = v
258 self.version = v
259 if v == 0:
259 if v == 0:
260 self.indexformat = indexformatv0
260 self.indexformat = indexformatv0
261 else:
261 else:
262 self.indexformat = indexformatng
262 self.indexformat = indexformatng
263
263
264 if i:
264 if i:
265 if not self.inlinedata() and st and st.st_size > 10000:
265 if not self.inlinedata() and st and st.st_size > 10000:
266 # big index, let's parse it on demand
266 # big index, let's parse it on demand
267 parser = lazyparser(i, self, self.indexformat)
267 parser = lazyparser(i, self, self.indexformat)
268 self.index = lazyindex(parser)
268 self.index = lazyindex(parser)
269 self.nodemap = lazymap(parser)
269 self.nodemap = lazymap(parser)
270 else:
270 else:
271 self.parseindex(i)
271 self.parseindex(i)
272 if self.inlinedata():
272 if self.inlinedata():
273 # we've already got the entire data file read in, save it
273 # we've already got the entire data file read in, save it
274 # in the chunk data
274 # in the chunk data
275 self.chunkcache = (0, i)
275 self.chunkcache = (0, i)
276 if self.version != 0:
276 if self.version != 0:
277 e = list(self.index[0])
277 e = list(self.index[0])
278 type = self.ngtype(e[0])
278 type = self.ngtype(e[0])
279 e[0] = self.offset_type(0, type)
279 e[0] = self.offset_type(0, type)
280 self.index[0] = e
280 self.index[0] = e
281 else:
281 else:
282 self.nodemap = { nullid: -1}
282 self.nodemap = { nullid: -1}
283 self.index = []
283 self.index = []
284
284
285
285
286 def parseindex(self, data):
286 def parseindex(self, data):
287 s = struct.calcsize(self.indexformat)
287 s = struct.calcsize(self.indexformat)
288 l = len(data)
288 l = len(data)
289 self.index = []
289 self.index = []
290 self.nodemap = {nullid: -1}
290 self.nodemap = {nullid: -1}
291 inline = self.inlinedata()
291 inline = self.inlinedata()
292 off = 0
292 off = 0
293 n = 0
293 n = 0
294 while off < l:
294 while off < l:
295 e = struct.unpack(self.indexformat, data[off:off + s])
295 e = struct.unpack(self.indexformat, data[off:off + s])
296 self.index.append(e)
296 self.index.append(e)
297 self.nodemap[e[-1]] = n
297 self.nodemap[e[-1]] = n
298 n += 1
298 n += 1
299 off += s
299 off += s
300 if inline:
300 if inline:
301 off += e[1]
301 off += e[1]
302
302
303 def ngoffset(self, q):
303 def ngoffset(self, q):
304 if q & 0xFFFF:
304 if q & 0xFFFF:
305 raise RevlogError(_('%s: incompatible revision flag %x') %
305 raise RevlogError(_('%s: incompatible revision flag %x') %
306 (self.indexfile, type))
306 (self.indexfile, type))
307 return long(q >> 16)
307 return long(q >> 16)
308
308
309 def ngtype(self, q):
309 def ngtype(self, q):
310 return int(q & 0xFFFF)
310 return int(q & 0xFFFF)
311
311
312 def offset_type(self, offset, type):
312 def offset_type(self, offset, type):
313 return long(long(offset) << 16 | type)
313 return long(long(offset) << 16 | type)
314
314
315 def loadindexmap(self):
315 def loadindexmap(self):
316 """loads both the map and the index from the lazy parser"""
316 """loads both the map and the index from the lazy parser"""
317 if isinstance(self.index, lazyindex):
317 if isinstance(self.index, lazyindex):
318 p = self.index.p
318 p = self.index.p
319 p.load()
319 p.load()
320
320
321 def inlinedata(self): return self.version & REVLOGNGINLINEDATA
321 def inlinedata(self): return self.version & REVLOGNGINLINEDATA
322 def tip(self): return self.node(len(self.index) - 1)
322 def tip(self): return self.node(len(self.index) - 1)
323 def count(self): return len(self.index)
323 def count(self): return len(self.index)
324 def node(self, rev):
324 def node(self, rev):
325 return (rev < 0) and nullid or self.index[rev][-1]
325 return (rev < 0) and nullid or self.index[rev][-1]
326 def rev(self, node):
326 def rev(self, node):
327 try:
327 try:
328 return self.nodemap[node]
328 return self.nodemap[node]
329 except KeyError:
329 except KeyError:
330 raise RevlogError(_('%s: no node %s') % (self.indexfile, hex(node)))
330 raise RevlogError(_('%s: no node %s') % (self.indexfile, hex(node)))
331 def linkrev(self, node): return self.index[self.rev(node)][-4]
331 def linkrev(self, node): return self.index[self.rev(node)][-4]
332 def parents(self, node):
332 def parents(self, node):
333 if node == nullid: return (nullid, nullid)
333 if node == nullid: return (nullid, nullid)
334 r = self.rev(node)
334 r = self.rev(node)
335 d = self.index[r][-3:-1]
335 d = self.index[r][-3:-1]
336 if self.version == 0:
336 if self.version == 0:
337 return d
337 return d
338 return [ self.node(x) for x in d ]
338 return [ self.node(x) for x in d ]
339 def start(self, rev):
339 def start(self, rev):
340 if rev < 0:
340 if rev < 0:
341 return -1
341 return -1
342 if self.version != 0:
342 if self.version != 0:
343 return self.ngoffset(self.index[rev][0])
343 return self.ngoffset(self.index[rev][0])
344 return self.index[rev][0]
344 return self.index[rev][0]
345 def end(self, rev): return self.start(rev) + self.length(rev)
345 def end(self, rev): return self.start(rev) + self.length(rev)
346
346
347 def length(self, rev):
347 def length(self, rev):
348 if rev < 0:
348 if rev < 0:
349 return 0
349 return 0
350 else:
350 else:
351 return self.index[rev][1]
351 return self.index[rev][1]
352 def base(self, rev): return (rev < 0) and rev or self.index[rev][-5]
352 def base(self, rev): return (rev < 0) and rev or self.index[rev][-5]
353
353
354 def reachable(self, rev, stop=None):
354 def reachable(self, rev, stop=None):
355 reachable = {}
355 reachable = {}
356 visit = [rev]
356 visit = [rev]
357 reachable[rev] = 1
357 reachable[rev] = 1
358 if stop:
358 if stop:
359 stopn = self.rev(stop)
359 stopn = self.rev(stop)
360 else:
360 else:
361 stopn = 0
361 stopn = 0
362 while visit:
362 while visit:
363 n = visit.pop(0)
363 n = visit.pop(0)
364 if n == stop:
364 if n == stop:
365 continue
365 continue
366 if n == nullid:
366 if n == nullid:
367 continue
367 continue
368 for p in self.parents(n):
368 for p in self.parents(n):
369 if self.rev(p) < stopn:
369 if self.rev(p) < stopn:
370 continue
370 continue
371 if p not in reachable:
371 if p not in reachable:
372 reachable[p] = 1
372 reachable[p] = 1
373 visit.append(p)
373 visit.append(p)
374 return reachable
374 return reachable
375
375
376 def nodesbetween(self, roots=None, heads=None):
376 def nodesbetween(self, roots=None, heads=None):
377 """Return a tuple containing three elements. Elements 1 and 2 contain
377 """Return a tuple containing three elements. Elements 1 and 2 contain
378 a final list bases and heads after all the unreachable ones have been
378 a final list bases and heads after all the unreachable ones have been
379 pruned. Element 0 contains a topologically sorted list of all
379 pruned. Element 0 contains a topologically sorted list of all
380
380
381 nodes that satisfy these constraints:
381 nodes that satisfy these constraints:
382 1. All nodes must be descended from a node in roots (the nodes on
382 1. All nodes must be descended from a node in roots (the nodes on
383 roots are considered descended from themselves).
383 roots are considered descended from themselves).
384 2. All nodes must also be ancestors of a node in heads (the nodes in
384 2. All nodes must also be ancestors of a node in heads (the nodes in
385 heads are considered to be their own ancestors).
385 heads are considered to be their own ancestors).
386
386
387 If roots is unspecified, nullid is assumed as the only root.
387 If roots is unspecified, nullid is assumed as the only root.
388 If heads is unspecified, it is taken to be the output of the
388 If heads is unspecified, it is taken to be the output of the
389 heads method (i.e. a list of all nodes in the repository that
389 heads method (i.e. a list of all nodes in the repository that
390 have no children)."""
390 have no children)."""
391 nonodes = ([], [], [])
391 nonodes = ([], [], [])
392 if roots is not None:
392 if roots is not None:
393 roots = list(roots)
393 roots = list(roots)
394 if not roots:
394 if not roots:
395 return nonodes
395 return nonodes
396 lowestrev = min([self.rev(n) for n in roots])
396 lowestrev = min([self.rev(n) for n in roots])
397 else:
397 else:
398 roots = [nullid] # Everybody's a descendent of nullid
398 roots = [nullid] # Everybody's a descendent of nullid
399 lowestrev = -1
399 lowestrev = -1
400 if (lowestrev == -1) and (heads is None):
400 if (lowestrev == -1) and (heads is None):
401 # We want _all_ the nodes!
401 # We want _all_ the nodes!
402 return ([self.node(r) for r in xrange(0, self.count())],
402 return ([self.node(r) for r in xrange(0, self.count())],
403 [nullid], list(self.heads()))
403 [nullid], list(self.heads()))
404 if heads is None:
404 if heads is None:
405 # All nodes are ancestors, so the latest ancestor is the last
405 # All nodes are ancestors, so the latest ancestor is the last
406 # node.
406 # node.
407 highestrev = self.count() - 1
407 highestrev = self.count() - 1
408 # Set ancestors to None to signal that every node is an ancestor.
408 # Set ancestors to None to signal that every node is an ancestor.
409 ancestors = None
409 ancestors = None
410 # Set heads to an empty dictionary for later discovery of heads
410 # Set heads to an empty dictionary for later discovery of heads
411 heads = {}
411 heads = {}
412 else:
412 else:
413 heads = list(heads)
413 heads = list(heads)
414 if not heads:
414 if not heads:
415 return nonodes
415 return nonodes
416 ancestors = {}
416 ancestors = {}
417 # Start at the top and keep marking parents until we're done.
417 # Start at the top and keep marking parents until we're done.
418 nodestotag = heads[:]
418 nodestotag = heads[:]
419 # Turn heads into a dictionary so we can remove 'fake' heads.
419 # Turn heads into a dictionary so we can remove 'fake' heads.
420 # Also, later we will be using it to filter out the heads we can't
420 # Also, later we will be using it to filter out the heads we can't
421 # find from roots.
421 # find from roots.
422 heads = dict.fromkeys(heads, 0)
422 heads = dict.fromkeys(heads, 0)
423 # Remember where the top was so we can use it as a limit later.
423 # Remember where the top was so we can use it as a limit later.
424 highestrev = max([self.rev(n) for n in nodestotag])
424 highestrev = max([self.rev(n) for n in nodestotag])
425 while nodestotag:
425 while nodestotag:
426 # grab a node to tag
426 # grab a node to tag
427 n = nodestotag.pop()
427 n = nodestotag.pop()
428 # Never tag nullid
428 # Never tag nullid
429 if n == nullid:
429 if n == nullid:
430 continue
430 continue
431 # A node's revision number represents its place in a
431 # A node's revision number represents its place in a
432 # topologically sorted list of nodes.
432 # topologically sorted list of nodes.
433 r = self.rev(n)
433 r = self.rev(n)
434 if r >= lowestrev:
434 if r >= lowestrev:
435 if n not in ancestors:
435 if n not in ancestors:
436 # If we are possibly a descendent of one of the roots
436 # If we are possibly a descendent of one of the roots
437 # and we haven't already been marked as an ancestor
437 # and we haven't already been marked as an ancestor
438 ancestors[n] = 1 # Mark as ancestor
438 ancestors[n] = 1 # Mark as ancestor
439 # Add non-nullid parents to list of nodes to tag.
439 # Add non-nullid parents to list of nodes to tag.
440 nodestotag.extend([p for p in self.parents(n) if
440 nodestotag.extend([p for p in self.parents(n) if
441 p != nullid])
441 p != nullid])
442 elif n in heads: # We've seen it before, is it a fake head?
442 elif n in heads: # We've seen it before, is it a fake head?
443 # So it is, real heads should not be the ancestors of
443 # So it is, real heads should not be the ancestors of
444 # any other heads.
444 # any other heads.
445 heads.pop(n)
445 heads.pop(n)
446 if not ancestors:
446 if not ancestors:
447 return nonodes
447 return nonodes
448 # Now that we have our set of ancestors, we want to remove any
448 # Now that we have our set of ancestors, we want to remove any
449 # roots that are not ancestors.
449 # roots that are not ancestors.
450
450
451 # If one of the roots was nullid, everything is included anyway.
451 # If one of the roots was nullid, everything is included anyway.
452 if lowestrev > -1:
452 if lowestrev > -1:
453 # But, since we weren't, let's recompute the lowest rev to not
453 # But, since we weren't, let's recompute the lowest rev to not
454 # include roots that aren't ancestors.
454 # include roots that aren't ancestors.
455
455
456 # Filter out roots that aren't ancestors of heads
456 # Filter out roots that aren't ancestors of heads
457 roots = [n for n in roots if n in ancestors]
457 roots = [n for n in roots if n in ancestors]
458 # Recompute the lowest revision
458 # Recompute the lowest revision
459 if roots:
459 if roots:
460 lowestrev = min([self.rev(n) for n in roots])
460 lowestrev = min([self.rev(n) for n in roots])
461 else:
461 else:
462 # No more roots? Return empty list
462 # No more roots? Return empty list
463 return nonodes
463 return nonodes
464 else:
464 else:
465 # We are descending from nullid, and don't need to care about
465 # We are descending from nullid, and don't need to care about
466 # any other roots.
466 # any other roots.
467 lowestrev = -1
467 lowestrev = -1
468 roots = [nullid]
468 roots = [nullid]
469 # Transform our roots list into a 'set' (i.e. a dictionary where the
469 # Transform our roots list into a 'set' (i.e. a dictionary where the
470 # values don't matter.
470 # values don't matter.
471 descendents = dict.fromkeys(roots, 1)
471 descendents = dict.fromkeys(roots, 1)
472 # Also, keep the original roots so we can filter out roots that aren't
472 # Also, keep the original roots so we can filter out roots that aren't
473 # 'real' roots (i.e. are descended from other roots).
473 # 'real' roots (i.e. are descended from other roots).
474 roots = descendents.copy()
474 roots = descendents.copy()
475 # Our topologically sorted list of output nodes.
475 # Our topologically sorted list of output nodes.
476 orderedout = []
476 orderedout = []
477 # Don't start at nullid since we don't want nullid in our output list,
477 # Don't start at nullid since we don't want nullid in our output list,
478 # and if nullid shows up in descedents, empty parents will look like
478 # and if nullid shows up in descedents, empty parents will look like
479 # they're descendents.
479 # they're descendents.
480 for r in xrange(max(lowestrev, 0), highestrev + 1):
480 for r in xrange(max(lowestrev, 0), highestrev + 1):
481 n = self.node(r)
481 n = self.node(r)
482 isdescendent = False
482 isdescendent = False
483 if lowestrev == -1: # Everybody is a descendent of nullid
483 if lowestrev == -1: # Everybody is a descendent of nullid
484 isdescendent = True
484 isdescendent = True
485 elif n in descendents:
485 elif n in descendents:
486 # n is already a descendent
486 # n is already a descendent
487 isdescendent = True
487 isdescendent = True
488 # This check only needs to be done here because all the roots
488 # This check only needs to be done here because all the roots
489 # will start being marked is descendents before the loop.
489 # will start being marked is descendents before the loop.
490 if n in roots:
490 if n in roots:
491 # If n was a root, check if it's a 'real' root.
491 # If n was a root, check if it's a 'real' root.
492 p = tuple(self.parents(n))
492 p = tuple(self.parents(n))
493 # If any of its parents are descendents, it's not a root.
493 # If any of its parents are descendents, it's not a root.
494 if (p[0] in descendents) or (p[1] in descendents):
494 if (p[0] in descendents) or (p[1] in descendents):
495 roots.pop(n)
495 roots.pop(n)
496 else:
496 else:
497 p = tuple(self.parents(n))
497 p = tuple(self.parents(n))
498 # A node is a descendent if either of its parents are
498 # A node is a descendent if either of its parents are
499 # descendents. (We seeded the dependents list with the roots
499 # descendents. (We seeded the dependents list with the roots
500 # up there, remember?)
500 # up there, remember?)
501 if (p[0] in descendents) or (p[1] in descendents):
501 if (p[0] in descendents) or (p[1] in descendents):
502 descendents[n] = 1
502 descendents[n] = 1
503 isdescendent = True
503 isdescendent = True
504 if isdescendent and ((ancestors is None) or (n in ancestors)):
504 if isdescendent and ((ancestors is None) or (n in ancestors)):
505 # Only include nodes that are both descendents and ancestors.
505 # Only include nodes that are both descendents and ancestors.
506 orderedout.append(n)
506 orderedout.append(n)
507 if (ancestors is not None) and (n in heads):
507 if (ancestors is not None) and (n in heads):
508 # We're trying to figure out which heads are reachable
508 # We're trying to figure out which heads are reachable
509 # from roots.
509 # from roots.
510 # Mark this head as having been reached
510 # Mark this head as having been reached
511 heads[n] = 1
511 heads[n] = 1
512 elif ancestors is None:
512 elif ancestors is None:
513 # Otherwise, we're trying to discover the heads.
513 # Otherwise, we're trying to discover the heads.
514 # Assume this is a head because if it isn't, the next step
514 # Assume this is a head because if it isn't, the next step
515 # will eventually remove it.
515 # will eventually remove it.
516 heads[n] = 1
516 heads[n] = 1
517 # But, obviously its parents aren't.
517 # But, obviously its parents aren't.
518 for p in self.parents(n):
518 for p in self.parents(n):
519 heads.pop(p, None)
519 heads.pop(p, None)
520 heads = [n for n in heads.iterkeys() if heads[n] != 0]
520 heads = [n for n in heads.iterkeys() if heads[n] != 0]
521 roots = roots.keys()
521 roots = roots.keys()
522 assert orderedout
522 assert orderedout
523 assert roots
523 assert roots
524 assert heads
524 assert heads
525 return (orderedout, roots, heads)
525 return (orderedout, roots, heads)
526
526
527 def heads(self, start=None):
527 def heads(self, start=None):
528 """return the list of all nodes that have no children
528 """return the list of all nodes that have no children
529
529
530 if start is specified, only heads that are descendants of
530 if start is specified, only heads that are descendants of
531 start will be returned
531 start will be returned
532
532
533 """
533 """
534 if start is None:
534 if start is None:
535 start = nullid
535 start = nullid
536 reachable = {start: 1}
536 reachable = {start: 1}
537 heads = {start: 1}
537 heads = {start: 1}
538 startrev = self.rev(start)
538 startrev = self.rev(start)
539
539
540 for r in xrange(startrev + 1, self.count()):
540 for r in xrange(startrev + 1, self.count()):
541 n = self.node(r)
541 n = self.node(r)
542 for pn in self.parents(n):
542 for pn in self.parents(n):
543 if pn in reachable:
543 if pn in reachable:
544 reachable[n] = 1
544 reachable[n] = 1
545 heads[n] = 1
545 heads[n] = 1
546 if pn in heads:
546 if pn in heads:
547 del heads[pn]
547 del heads[pn]
548 return heads.keys()
548 return heads.keys()
549
549
550 def children(self, node):
550 def children(self, node):
551 """find the children of a given node"""
551 """find the children of a given node"""
552 c = []
552 c = []
553 p = self.rev(node)
553 p = self.rev(node)
554 for r in range(p + 1, self.count()):
554 for r in range(p + 1, self.count()):
555 n = self.node(r)
555 n = self.node(r)
556 for pn in self.parents(n):
556 for pn in self.parents(n):
557 if pn == node:
557 if pn == node:
558 c.append(n)
558 c.append(n)
559 continue
559 continue
560 elif pn == nullid:
560 elif pn == nullid:
561 continue
561 continue
562 return c
562 return c
563
563
564 def lookup(self, id):
564 def lookup(self, id):
565 """locate a node based on revision number or subset of hex nodeid"""
565 """locate a node based on revision number or subset of hex nodeid"""
566 try:
566 try:
567 rev = int(id)
567 rev = int(id)
568 if str(rev) != id: raise ValueError
568 if str(rev) != id: raise ValueError
569 if rev < 0: rev = self.count() + rev
569 if rev < 0: rev = self.count() + rev
570 if rev < 0 or rev >= self.count(): raise ValueError
570 if rev < 0 or rev >= self.count(): raise ValueError
571 return self.node(rev)
571 return self.node(rev)
572 except (ValueError, OverflowError):
572 except (ValueError, OverflowError):
573 c = []
573 c = []
574 for n in self.nodemap:
574 for n in self.nodemap:
575 if hex(n).startswith(id):
575 if hex(n).startswith(id):
576 c.append(n)
576 c.append(n)
577 if len(c) > 1: raise RevlogError(_("Ambiguous identifier"))
577 if len(c) > 1: raise RevlogError(_("Ambiguous identifier"))
578 if len(c) < 1: raise RevlogError(_("No match found"))
578 if len(c) < 1: raise RevlogError(_("No match found"))
579 return c[0]
579 return c[0]
580
580
581 return None
581 return None
582
582
583 def diff(self, a, b):
583 def diff(self, a, b):
584 """return a delta between two revisions"""
584 """return a delta between two revisions"""
585 return mdiff.textdiff(a, b)
585 return mdiff.textdiff(a, b)
586
586
587 def patches(self, t, pl):
587 def patches(self, t, pl):
588 """apply a list of patches to a string"""
588 """apply a list of patches to a string"""
589 return mdiff.patches(t, pl)
589 return mdiff.patches(t, pl)
590
590
591 def chunk(self, rev, df=None, cachelen=4096):
591 def chunk(self, rev, df=None, cachelen=4096):
592 start, length = self.start(rev), self.length(rev)
592 start, length = self.start(rev), self.length(rev)
593 inline = self.inlinedata()
593 inline = self.inlinedata()
594 if inline:
594 if inline:
595 start += (rev + 1) * struct.calcsize(self.indexformat)
595 start += (rev + 1) * struct.calcsize(self.indexformat)
596 end = start + length
596 end = start + length
597 def loadcache(df):
597 def loadcache(df):
598 cache_length = max(cachelen, length) # 4k
598 cache_length = max(cachelen, length) # 4k
599 if not df:
599 if not df:
600 if inline:
600 if inline:
601 df = self.opener(self.indexfile)
601 df = self.opener(self.indexfile)
602 else:
602 else:
603 df = self.opener(self.datafile)
603 df = self.opener(self.datafile)
604 df.seek(start)
604 df.seek(start)
605 self.chunkcache = (start, df.read(cache_length))
605 self.chunkcache = (start, df.read(cache_length))
606
606
607 if not self.chunkcache:
607 if not self.chunkcache:
608 loadcache(df)
608 loadcache(df)
609
609
610 cache_start = self.chunkcache[0]
610 cache_start = self.chunkcache[0]
611 cache_end = cache_start + len(self.chunkcache[1])
611 cache_end = cache_start + len(self.chunkcache[1])
612 if start >= cache_start and end <= cache_end:
612 if start >= cache_start and end <= cache_end:
613 # it is cached
613 # it is cached
614 offset = start - cache_start
614 offset = start - cache_start
615 else:
615 else:
616 loadcache(df)
616 loadcache(df)
617 offset = 0
617 offset = 0
618
618
619 #def checkchunk():
619 #def checkchunk():
620 # df = self.opener(self.datafile)
620 # df = self.opener(self.datafile)
621 # df.seek(start)
621 # df.seek(start)
622 # return df.read(length)
622 # return df.read(length)
623 #assert s == checkchunk()
623 #assert s == checkchunk()
624 return decompress(self.chunkcache[1][offset:offset + length])
624 return decompress(self.chunkcache[1][offset:offset + length])
625
625
626 def delta(self, node):
626 def delta(self, node):
627 """return or calculate a delta between a node and its predecessor"""
627 """return or calculate a delta between a node and its predecessor"""
628 r = self.rev(node)
628 r = self.rev(node)
629 return self.revdiff(r - 1, r)
629 return self.revdiff(r - 1, r)
630
630
631 def revdiff(self, rev1, rev2):
631 def revdiff(self, rev1, rev2):
632 """return or calculate a delta between two revisions"""
632 """return or calculate a delta between two revisions"""
633 b1 = self.base(rev1)
633 b1 = self.base(rev1)
634 b2 = self.base(rev2)
634 b2 = self.base(rev2)
635 if b1 == b2 and rev1 + 1 == rev2:
635 if b1 == b2 and rev1 + 1 == rev2:
636 return self.chunk(rev2)
636 return self.chunk(rev2)
637 else:
637 else:
638 return self.diff(self.revision(self.node(rev1)),
638 return self.diff(self.revision(self.node(rev1)),
639 self.revision(self.node(rev2)))
639 self.revision(self.node(rev2)))
640
640
641 def revision(self, node):
641 def revision(self, node):
642 """return an uncompressed revision of a given"""
642 """return an uncompressed revision of a given"""
643 if node == nullid: return ""
643 if node == nullid: return ""
644 if self.cache and self.cache[0] == node: return self.cache[2]
644 if self.cache and self.cache[0] == node: return self.cache[2]
645
645
646 # look up what we need to read
646 # look up what we need to read
647 text = None
647 text = None
648 rev = self.rev(node)
648 rev = self.rev(node)
649 base = self.base(rev)
649 base = self.base(rev)
650
650
651 if self.inlinedata():
651 if self.inlinedata():
652 # we probably have the whole chunk cached
652 # we probably have the whole chunk cached
653 df = None
653 df = None
654 else:
654 else:
655 df = self.opener(self.datafile)
655 df = self.opener(self.datafile)
656
656
657 # do we have useful data cached?
657 # do we have useful data cached?
658 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
658 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
659 base = self.cache[1]
659 base = self.cache[1]
660 text = self.cache[2]
660 text = self.cache[2]
661 else:
661 else:
662 text = self.chunk(base, df=df)
662 text = self.chunk(base, df=df)
663
663
664 bins = []
664 bins = []
665 for r in xrange(base + 1, rev + 1):
665 for r in xrange(base + 1, rev + 1):
666 bins.append(self.chunk(r, df=df))
666 bins.append(self.chunk(r, df=df))
667
667
668 text = self.patches(text, bins)
668 text = self.patches(text, bins)
669
669
670 p1, p2 = self.parents(node)
670 p1, p2 = self.parents(node)
671 if node != hash(text, p1, p2):
671 if node != hash(text, p1, p2):
672 raise RevlogError(_("integrity check failed on %s:%d")
672 raise RevlogError(_("integrity check failed on %s:%d")
673 % (self.datafile, rev))
673 % (self.datafile, rev))
674
674
675 self.cache = (node, rev, text)
675 self.cache = (node, rev, text)
676 return text
676 return text
677
677
678 def checkinlinesize(self, fp, tr):
678 def checkinlinesize(self, tr, fp=None):
679 if not self.inlinedata():
679 if not self.inlinedata():
680 return
680 return
681 if not fp:
682 fp = self.opener(self.indexfile, 'r')
681 size = fp.tell()
683 size = fp.tell()
682 if size < 131072:
684 if size < 131072:
683 return
685 return
684 tr.add(self.datafile, 0)
686 tr.add(self.datafile, 0)
685 df = self.opener(self.datafile, 'w')
687 df = self.opener(self.datafile, 'w')
686 calc = struct.calcsize(self.indexformat)
688 calc = struct.calcsize(self.indexformat)
687 for r in xrange(self.count()):
689 for r in xrange(self.count()):
688 start = self.start(r) + (r + 1) * calc
690 start = self.start(r) + (r + 1) * calc
689 length = self.length(r)
691 length = self.length(r)
690 fp.seek(start)
692 fp.seek(start)
691 d = fp.read(length)
693 d = fp.read(length)
692 df.write(d)
694 df.write(d)
693 fp.close()
695 fp.close()
694 df.close()
696 df.close()
695 fp = self.opener(self.indexfile, 'w', atomic=True)
697 fp = self.opener(self.indexfile, 'w', atomic=True)
696 self.version &= ~(REVLOGNGINLINEDATA)
698 self.version &= ~(REVLOGNGINLINEDATA)
697 if self.count():
699 if self.count():
698 x = self.index[0]
700 x = self.index[0]
699 e = struct.pack(self.indexformat, *x)[4:]
701 e = struct.pack(self.indexformat, *x)[4:]
700 l = struct.pack(versionformat, self.version)
702 l = struct.pack(versionformat, self.version)
701 fp.write(l)
703 fp.write(l)
702 fp.write(e)
704 fp.write(e)
703
705
704 for i in xrange(1, self.count()):
706 for i in xrange(1, self.count()):
705 x = self.index[i]
707 x = self.index[i]
706 e = struct.pack(self.indexformat, *x)
708 e = struct.pack(self.indexformat, *x)
707 fp.write(e)
709 fp.write(e)
708
710
709 fp.close()
711 fp.close()
710 self.chunkcache = None
712 self.chunkcache = None
711
713
712 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
714 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
713 """add a revision to the log
715 """add a revision to the log
714
716
715 text - the revision data to add
717 text - the revision data to add
716 transaction - the transaction object used for rollback
718 transaction - the transaction object used for rollback
717 link - the linkrev data to add
719 link - the linkrev data to add
718 p1, p2 - the parent nodeids of the revision
720 p1, p2 - the parent nodeids of the revision
719 d - an optional precomputed delta
721 d - an optional precomputed delta
720 """
722 """
721 if text is None: text = ""
723 if text is None: text = ""
722 if p1 is None: p1 = self.tip()
724 if p1 is None: p1 = self.tip()
723 if p2 is None: p2 = nullid
725 if p2 is None: p2 = nullid
724
726
725 node = hash(text, p1, p2)
727 node = hash(text, p1, p2)
726
728
727 if node in self.nodemap:
729 if node in self.nodemap:
728 return node
730 return node
729
731
730 n = self.count()
732 n = self.count()
731 t = n - 1
733 t = n - 1
732
734
733 if n:
735 if n:
734 base = self.base(t)
736 base = self.base(t)
735 start = self.start(base)
737 start = self.start(base)
736 end = self.end(t)
738 end = self.end(t)
737 if not d:
739 if not d:
738 prev = self.revision(self.tip())
740 prev = self.revision(self.tip())
739 d = self.diff(prev, str(text))
741 d = self.diff(prev, str(text))
740 data = compress(d)
742 data = compress(d)
741 l = len(data[1]) + len(data[0])
743 l = len(data[1]) + len(data[0])
742 dist = end - start + l
744 dist = end - start + l
743
745
744 # full versions are inserted when the needed deltas
746 # full versions are inserted when the needed deltas
745 # become comparable to the uncompressed text
747 # become comparable to the uncompressed text
746 if not n or dist > len(text) * 2:
748 if not n or dist > len(text) * 2:
747 data = compress(text)
749 data = compress(text)
748 l = len(data[1]) + len(data[0])
750 l = len(data[1]) + len(data[0])
749 base = n
751 base = n
750 else:
752 else:
751 base = self.base(t)
753 base = self.base(t)
752
754
753 offset = 0
755 offset = 0
754 if t >= 0:
756 if t >= 0:
755 offset = self.end(t)
757 offset = self.end(t)
756
758
757 if self.version == 0:
759 if self.version == 0:
758 e = (offset, l, base, link, p1, p2, node)
760 e = (offset, l, base, link, p1, p2, node)
759 else:
761 else:
760 e = (self.offset_type(offset, 0), l, len(text),
762 e = (self.offset_type(offset, 0), l, len(text),
761 base, link, self.rev(p1), self.rev(p2), node)
763 base, link, self.rev(p1), self.rev(p2), node)
762
764
763 self.index.append(e)
765 self.index.append(e)
764 self.nodemap[node] = n
766 self.nodemap[node] = n
765 entry = struct.pack(self.indexformat, *e)
767 entry = struct.pack(self.indexformat, *e)
766
768
767 if not self.inlinedata():
769 if not self.inlinedata():
768 transaction.add(self.datafile, offset)
770 transaction.add(self.datafile, offset)
769 transaction.add(self.indexfile, n * len(entry))
771 transaction.add(self.indexfile, n * len(entry))
770 f = self.opener(self.datafile, "a")
772 f = self.opener(self.datafile, "a")
771 if data[0]:
773 if data[0]:
772 f.write(data[0])
774 f.write(data[0])
773 f.write(data[1])
775 f.write(data[1])
774 f = self.opener(self.indexfile, "a")
776 f = self.opener(self.indexfile, "a")
775 else:
777 else:
776 f = self.opener(self.indexfile, "a+")
778 f = self.opener(self.indexfile, "a+")
777 transaction.add(self.indexfile, f.tell())
779 transaction.add(self.indexfile, f.tell())
778
780
779 if len(self.index) == 1 and self.version != 0:
781 if len(self.index) == 1 and self.version != 0:
780 l = struct.pack(versionformat, self.version)
782 l = struct.pack(versionformat, self.version)
781 f.write(l)
783 f.write(l)
782 entry = entry[4:]
784 entry = entry[4:]
783
785
784 f.write(entry)
786 f.write(entry)
785
787
786 if self.inlinedata():
788 if self.inlinedata():
787 f.write(data[0])
789 f.write(data[0])
788 f.write(data[1])
790 f.write(data[1])
789 self.checkinlinesize(f, transaction)
791 self.checkinlinesize(transaction, f)
790
792
791 self.cache = (node, n, text)
793 self.cache = (node, n, text)
792 return node
794 return node
793
795
794 def ancestor(self, a, b):
796 def ancestor(self, a, b):
795 """calculate the least common ancestor of nodes a and b"""
797 """calculate the least common ancestor of nodes a and b"""
796 # calculate the distance of every node from root
798 # calculate the distance of every node from root
797 dist = {nullid: 0}
799 dist = {nullid: 0}
798 for i in xrange(self.count()):
800 for i in xrange(self.count()):
799 n = self.node(i)
801 n = self.node(i)
800 p1, p2 = self.parents(n)
802 p1, p2 = self.parents(n)
801 dist[n] = max(dist[p1], dist[p2]) + 1
803 dist[n] = max(dist[p1], dist[p2]) + 1
802
804
803 # traverse ancestors in order of decreasing distance from root
805 # traverse ancestors in order of decreasing distance from root
804 def ancestors(node):
806 def ancestors(node):
805 # we store negative distances because heap returns smallest member
807 # we store negative distances because heap returns smallest member
806 h = [(-dist[node], node)]
808 h = [(-dist[node], node)]
807 seen = {}
809 seen = {}
808 while h:
810 while h:
809 d, n = heapq.heappop(h)
811 d, n = heapq.heappop(h)
810 if n not in seen:
812 if n not in seen:
811 seen[n] = 1
813 seen[n] = 1
812 yield (-d, n)
814 yield (-d, n)
813 for p in self.parents(n):
815 for p in self.parents(n):
814 heapq.heappush(h, (-dist[p], p))
816 heapq.heappush(h, (-dist[p], p))
815
817
816 def generations(node):
818 def generations(node):
817 sg, s = None, {}
819 sg, s = None, {}
818 for g,n in ancestors(node):
820 for g,n in ancestors(node):
819 if g != sg:
821 if g != sg:
820 if sg:
822 if sg:
821 yield sg, s
823 yield sg, s
822 sg, s = g, {n:1}
824 sg, s = g, {n:1}
823 else:
825 else:
824 s[n] = 1
826 s[n] = 1
825 yield sg, s
827 yield sg, s
826
828
827 x = generations(a)
829 x = generations(a)
828 y = generations(b)
830 y = generations(b)
829 gx = x.next()
831 gx = x.next()
830 gy = y.next()
832 gy = y.next()
831
833
832 # increment each ancestor list until it is closer to root than
834 # increment each ancestor list until it is closer to root than
833 # the other, or they match
835 # the other, or they match
834 while 1:
836 while 1:
835 #print "ancestor gen %s %s" % (gx[0], gy[0])
837 #print "ancestor gen %s %s" % (gx[0], gy[0])
836 if gx[0] == gy[0]:
838 if gx[0] == gy[0]:
837 # find the intersection
839 # find the intersection
838 i = [ n for n in gx[1] if n in gy[1] ]
840 i = [ n for n in gx[1] if n in gy[1] ]
839 if i:
841 if i:
840 return i[0]
842 return i[0]
841 else:
843 else:
842 #print "next"
844 #print "next"
843 gy = y.next()
845 gy = y.next()
844 gx = x.next()
846 gx = x.next()
845 elif gx[0] < gy[0]:
847 elif gx[0] < gy[0]:
846 #print "next y"
848 #print "next y"
847 gy = y.next()
849 gy = y.next()
848 else:
850 else:
849 #print "next x"
851 #print "next x"
850 gx = x.next()
852 gx = x.next()
851
853
852 def group(self, nodelist, lookup, infocollect=None):
854 def group(self, nodelist, lookup, infocollect=None):
853 """calculate a delta group
855 """calculate a delta group
854
856
855 Given a list of changeset revs, return a set of deltas and
857 Given a list of changeset revs, return a set of deltas and
856 metadata corresponding to nodes. the first delta is
858 metadata corresponding to nodes. the first delta is
857 parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
859 parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
858 have this parent as it has all history before these
860 have this parent as it has all history before these
859 changesets. parent is parent[0]
861 changesets. parent is parent[0]
860 """
862 """
861 revs = [self.rev(n) for n in nodelist]
863 revs = [self.rev(n) for n in nodelist]
862
864
863 # if we don't have any revisions touched by these changesets, bail
865 # if we don't have any revisions touched by these changesets, bail
864 if not revs:
866 if not revs:
865 yield changegroup.closechunk()
867 yield changegroup.closechunk()
866 return
868 return
867
869
868 # add the parent of the first rev
870 # add the parent of the first rev
869 p = self.parents(self.node(revs[0]))[0]
871 p = self.parents(self.node(revs[0]))[0]
870 revs.insert(0, self.rev(p))
872 revs.insert(0, self.rev(p))
871
873
872 # build deltas
874 # build deltas
873 for d in xrange(0, len(revs) - 1):
875 for d in xrange(0, len(revs) - 1):
874 a, b = revs[d], revs[d + 1]
876 a, b = revs[d], revs[d + 1]
875 nb = self.node(b)
877 nb = self.node(b)
876
878
877 if infocollect is not None:
879 if infocollect is not None:
878 infocollect(nb)
880 infocollect(nb)
879
881
880 d = self.revdiff(a, b)
882 d = self.revdiff(a, b)
881 p = self.parents(nb)
883 p = self.parents(nb)
882 meta = nb + p[0] + p[1] + lookup(nb)
884 meta = nb + p[0] + p[1] + lookup(nb)
883 yield changegroup.genchunk("%s%s" % (meta, d))
885 yield changegroup.genchunk("%s%s" % (meta, d))
884
886
885 yield changegroup.closechunk()
887 yield changegroup.closechunk()
886
888
887 def addgroup(self, revs, linkmapper, transaction, unique=0):
889 def addgroup(self, revs, linkmapper, transaction, unique=0):
888 """
890 """
889 add a delta group
891 add a delta group
890
892
891 given a set of deltas, add them to the revision log. the
893 given a set of deltas, add them to the revision log. the
892 first delta is against its parent, which should be in our
894 first delta is against its parent, which should be in our
893 log, the rest are against the previous delta.
895 log, the rest are against the previous delta.
894 """
896 """
895
897
896 #track the base of the current delta log
898 #track the base of the current delta log
897 r = self.count()
899 r = self.count()
898 t = r - 1
900 t = r - 1
899 node = None
901 node = None
900
902
901 base = prev = -1
903 base = prev = -1
902 start = end = measure = 0
904 start = end = measure = 0
903 if r:
905 if r:
904 end = self.end(t)
906 end = self.end(t)
905
907
906 ifh = self.opener(self.indexfile, "a+")
908 ifh = self.opener(self.indexfile, "a+")
907 transaction.add(self.indexfile, ifh.tell())
909 transaction.add(self.indexfile, ifh.tell())
908 if self.inlinedata():
910 if self.inlinedata():
909 dfh = None
911 dfh = None
910 else:
912 else:
911 transaction.add(self.datafile, end)
913 transaction.add(self.datafile, end)
912 dfh = self.opener(self.datafile, "a")
914 dfh = self.opener(self.datafile, "a")
913
915
914 # loop through our set of deltas
916 # loop through our set of deltas
915 chain = None
917 chain = None
916 for chunk in revs:
918 for chunk in revs:
917 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
919 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
918 link = linkmapper(cs)
920 link = linkmapper(cs)
919 if node in self.nodemap:
921 if node in self.nodemap:
920 # this can happen if two branches make the same change
922 # this can happen if two branches make the same change
921 # if unique:
923 # if unique:
922 # raise RevlogError(_("already have %s") % hex(node[:4]))
924 # raise RevlogError(_("already have %s") % hex(node[:4]))
923 chain = node
925 chain = node
924 continue
926 continue
925 delta = chunk[80:]
927 delta = chunk[80:]
926
928
927 for p in (p1, p2):
929 for p in (p1, p2):
928 if not p in self.nodemap:
930 if not p in self.nodemap:
929 raise RevlogError(_("unknown parent %s") % short(p1))
931 raise RevlogError(_("unknown parent %s") % short(p1))
930
932
931 if not chain:
933 if not chain:
932 # retrieve the parent revision of the delta chain
934 # retrieve the parent revision of the delta chain
933 chain = p1
935 chain = p1
934 if not chain in self.nodemap:
936 if not chain in self.nodemap:
935 raise RevlogError(_("unknown base %s") % short(chain[:4]))
937 raise RevlogError(_("unknown base %s") % short(chain[:4]))
936
938
937 # full versions are inserted when the needed deltas become
939 # full versions are inserted when the needed deltas become
938 # comparable to the uncompressed text or when the previous
940 # comparable to the uncompressed text or when the previous
939 # version is not the one we have a delta against. We use
941 # version is not the one we have a delta against. We use
940 # the size of the previous full rev as a proxy for the
942 # the size of the previous full rev as a proxy for the
941 # current size.
943 # current size.
942
944
943 if chain == prev:
945 if chain == prev:
944 tempd = compress(delta)
946 tempd = compress(delta)
945 cdelta = tempd[0] + tempd[1]
947 cdelta = tempd[0] + tempd[1]
946
948
947 if chain != prev or (end - start + len(cdelta)) > measure * 2:
949 if chain != prev or (end - start + len(cdelta)) > measure * 2:
948 # flush our writes here so we can read it in revision
950 # flush our writes here so we can read it in revision
949 if dfh:
951 if dfh:
950 dfh.flush()
952 dfh.flush()
951 ifh.flush()
953 ifh.flush()
952 text = self.revision(chain)
954 text = self.revision(chain)
953 text = self.patches(text, [delta])
955 text = self.patches(text, [delta])
954 chk = self.addrevision(text, transaction, link, p1, p2)
956 chk = self.addrevision(text, transaction, link, p1, p2)
955 if chk != node:
957 if chk != node:
956 raise RevlogError(_("consistency error adding group"))
958 raise RevlogError(_("consistency error adding group"))
957 measure = len(text)
959 measure = len(text)
958 else:
960 else:
959 if self.version == 0:
961 if self.version == 0:
960 e = (end, len(cdelta), base, link, p1, p2, node)
962 e = (end, len(cdelta), base, link, p1, p2, node)
961 else:
963 else:
962 e = (self.offset_type(end, 0), len(cdelta), -1, base,
964 e = (self.offset_type(end, 0), len(cdelta), -1, base,
963 link, self.rev(p1), self.rev(p2), node)
965 link, self.rev(p1), self.rev(p2), node)
964 self.index.append(e)
966 self.index.append(e)
965 self.nodemap[node] = r
967 self.nodemap[node] = r
966 if self.inlinedata():
968 if self.inlinedata():
967 ifh.write(struct.pack(self.indexformat, *e))
969 ifh.write(struct.pack(self.indexformat, *e))
968 ifh.write(cdelta)
970 ifh.write(cdelta)
969 self.checkinlinesize(ifh, transaction)
971 self.checkinlinesize(transaction, ifh)
970 if not self.inlinedata():
972 if not self.inlinedata():
971 dfh = self.opener(self.datafile, "a")
973 dfh = self.opener(self.datafile, "a")
972 ifh = self.opener(self.indexfile, "a")
974 ifh = self.opener(self.indexfile, "a")
973 else:
975 else:
974 if not dfh:
976 if not dfh:
975 # addrevision switched from inline to conventional
977 # addrevision switched from inline to conventional
976 # reopen the index
978 # reopen the index
977 dfh = self.opener(self.datafile, "a")
979 dfh = self.opener(self.datafile, "a")
978 ifh = self.opener(self.indexfile, "a")
980 ifh = self.opener(self.indexfile, "a")
979 dfh.write(cdelta)
981 dfh.write(cdelta)
980 ifh.write(struct.pack(self.indexformat, *e))
982 ifh.write(struct.pack(self.indexformat, *e))
981
983
982 t, r, chain, prev = r, r + 1, node, node
984 t, r, chain, prev = r, r + 1, node, node
983 base = self.base(t)
985 base = self.base(t)
984 start = self.start(base)
986 start = self.start(base)
985 end = self.end(t)
987 end = self.end(t)
986
988
987 if node is None:
989 if node is None:
988 raise RevlogError(_("group to be added is empty"))
990 raise RevlogError(_("group to be added is empty"))
989 return node
991 return node
990
992
991 def strip(self, rev, minlink):
993 def strip(self, rev, minlink):
992 if self.count() == 0 or rev >= self.count():
994 if self.count() == 0 or rev >= self.count():
993 return
995 return
994
996
995 if isinstance(self.index, lazyindex):
997 if isinstance(self.index, lazyindex):
996 self.loadindexmap()
998 self.loadindexmap()
997
999
998 # When stripping away a revision, we need to make sure it
1000 # When stripping away a revision, we need to make sure it
999 # does not actually belong to an older changeset.
1001 # does not actually belong to an older changeset.
1000 # The minlink parameter defines the oldest revision
1002 # The minlink parameter defines the oldest revision
1001 # we're allowed to strip away.
1003 # we're allowed to strip away.
1002 while minlink > self.index[rev][-4]:
1004 while minlink > self.index[rev][-4]:
1003 rev += 1
1005 rev += 1
1004 if rev >= self.count():
1006 if rev >= self.count():
1005 return
1007 return
1006
1008
1007 # first truncate the files on disk
1009 # first truncate the files on disk
1008 end = self.start(rev)
1010 end = self.start(rev)
1009 if not self.inlinedata():
1011 if not self.inlinedata():
1010 df = self.opener(self.datafile, "a")
1012 df = self.opener(self.datafile, "a")
1011 df.truncate(end)
1013 df.truncate(end)
1012 end = rev * struct.calcsize(self.indexformat)
1014 end = rev * struct.calcsize(self.indexformat)
1013 else:
1015 else:
1014 end += rev * struct.calcsize(self.indexformat)
1016 end += rev * struct.calcsize(self.indexformat)
1015
1017
1016 indexf = self.opener(self.indexfile, "a")
1018 indexf = self.opener(self.indexfile, "a")
1017 indexf.truncate(end)
1019 indexf.truncate(end)
1018
1020
1019 # then reset internal state in memory to forget those revisions
1021 # then reset internal state in memory to forget those revisions
1020 self.cache = None
1022 self.cache = None
1021 self.chunkcache = None
1023 self.chunkcache = None
1022 for x in xrange(rev, self.count()):
1024 for x in xrange(rev, self.count()):
1023 del self.nodemap[self.node(x)]
1025 del self.nodemap[self.node(x)]
1024
1026
1025 del self.index[rev:]
1027 del self.index[rev:]
1026
1028
1027 def checksize(self):
1029 def checksize(self):
1028 expected = 0
1030 expected = 0
1029 if self.count():
1031 if self.count():
1030 expected = self.end(self.count() - 1)
1032 expected = self.end(self.count() - 1)
1031
1033
1032 try:
1034 try:
1033 f = self.opener(self.datafile)
1035 f = self.opener(self.datafile)
1034 f.seek(0, 2)
1036 f.seek(0, 2)
1035 actual = f.tell()
1037 actual = f.tell()
1036 dd = actual - expected
1038 dd = actual - expected
1037 except IOError, inst:
1039 except IOError, inst:
1038 if inst.errno != errno.ENOENT:
1040 if inst.errno != errno.ENOENT:
1039 raise
1041 raise
1040 dd = 0
1042 dd = 0
1041
1043
1042 try:
1044 try:
1043 f = self.opener(self.indexfile)
1045 f = self.opener(self.indexfile)
1044 f.seek(0, 2)
1046 f.seek(0, 2)
1045 actual = f.tell()
1047 actual = f.tell()
1046 s = struct.calcsize(self.indexformat)
1048 s = struct.calcsize(self.indexformat)
1047 i = actual / s
1049 i = actual / s
1048 di = actual - (i * s)
1050 di = actual - (i * s)
1049 if self.inlinedata():
1051 if self.inlinedata():
1050 databytes = 0
1052 databytes = 0
1051 for r in xrange(self.count()):
1053 for r in xrange(self.count()):
1052 databytes += self.length(r)
1054 databytes += self.length(r)
1053 dd = 0
1055 dd = 0
1054 di = actual - self.count() * s - databytes
1056 di = actual - self.count() * s - databytes
1055 except IOError, inst:
1057 except IOError, inst:
1056 if inst.errno != errno.ENOENT:
1058 if inst.errno != errno.ENOENT:
1057 raise
1059 raise
1058 di = 0
1060 di = 0
1059
1061
1060 return (dd, di)
1062 return (dd, di)
1061
1063
1062
1064
General Comments 0
You need to be logged in to leave comments. Login now