##// END OF EJS Templates
Improve some docstrings relating to changegroups and prepush().
Greg Ward -
r9437:1c4e4004 default
parent child Browse files
Show More
@@ -1,140 +1,142 b''
1 # changegroup.py - Mercurial changegroup manipulation functions
1 # changegroup.py - Mercurial changegroup manipulation functions
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 from i18n import _
8 from i18n import _
9 import util
9 import util
10 import struct, os, bz2, zlib, tempfile
10 import struct, os, bz2, zlib, tempfile
11
11
12 def getchunk(source):
12 def getchunk(source):
13 """get a chunk from a changegroup"""
13 """return the next chunk from changegroup 'source' as a string"""
14 d = source.read(4)
14 d = source.read(4)
15 if not d:
15 if not d:
16 return ""
16 return ""
17 l = struct.unpack(">l", d)[0]
17 l = struct.unpack(">l", d)[0]
18 if l <= 4:
18 if l <= 4:
19 return ""
19 return ""
20 d = source.read(l - 4)
20 d = source.read(l - 4)
21 if len(d) < l - 4:
21 if len(d) < l - 4:
22 raise util.Abort(_("premature EOF reading chunk"
22 raise util.Abort(_("premature EOF reading chunk"
23 " (got %d bytes, expected %d)")
23 " (got %d bytes, expected %d)")
24 % (len(d), l - 4))
24 % (len(d), l - 4))
25 return d
25 return d
26
26
27 def chunkiter(source):
27 def chunkiter(source):
28 """iterate through the chunks in source"""
28 """iterate through the chunks in source, yielding a sequence of chunks
29 (strings)"""
29 while 1:
30 while 1:
30 c = getchunk(source)
31 c = getchunk(source)
31 if not c:
32 if not c:
32 break
33 break
33 yield c
34 yield c
34
35
35 def chunkheader(length):
36 def chunkheader(length):
36 """build a changegroup chunk header"""
37 """return a changegroup chunk header (string)"""
37 return struct.pack(">l", length + 4)
38 return struct.pack(">l", length + 4)
38
39
39 def closechunk():
40 def closechunk():
41 """return a changegroup chunk header (string) for a zero-length chunk"""
40 return struct.pack(">l", 0)
42 return struct.pack(">l", 0)
41
43
42 class nocompress(object):
44 class nocompress(object):
43 def compress(self, x):
45 def compress(self, x):
44 return x
46 return x
45 def flush(self):
47 def flush(self):
46 return ""
48 return ""
47
49
48 bundletypes = {
50 bundletypes = {
49 "": ("", nocompress),
51 "": ("", nocompress),
50 "HG10UN": ("HG10UN", nocompress),
52 "HG10UN": ("HG10UN", nocompress),
51 "HG10BZ": ("HG10", lambda: bz2.BZ2Compressor()),
53 "HG10BZ": ("HG10", lambda: bz2.BZ2Compressor()),
52 "HG10GZ": ("HG10GZ", lambda: zlib.compressobj()),
54 "HG10GZ": ("HG10GZ", lambda: zlib.compressobj()),
53 }
55 }
54
56
55 # hgweb uses this list to communicate its preferred type
57 # hgweb uses this list to communicate its preferred type
56 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
58 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
57
59
58 def writebundle(cg, filename, bundletype):
60 def writebundle(cg, filename, bundletype):
59 """Write a bundle file and return its filename.
61 """Write a bundle file and return its filename.
60
62
61 Existing files will not be overwritten.
63 Existing files will not be overwritten.
62 If no filename is specified, a temporary file is created.
64 If no filename is specified, a temporary file is created.
63 bz2 compression can be turned off.
65 bz2 compression can be turned off.
64 The bundle file will be deleted in case of errors.
66 The bundle file will be deleted in case of errors.
65 """
67 """
66
68
67 fh = None
69 fh = None
68 cleanup = None
70 cleanup = None
69 try:
71 try:
70 if filename:
72 if filename:
71 fh = open(filename, "wb")
73 fh = open(filename, "wb")
72 else:
74 else:
73 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
75 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
74 fh = os.fdopen(fd, "wb")
76 fh = os.fdopen(fd, "wb")
75 cleanup = filename
77 cleanup = filename
76
78
77 header, compressor = bundletypes[bundletype]
79 header, compressor = bundletypes[bundletype]
78 fh.write(header)
80 fh.write(header)
79 z = compressor()
81 z = compressor()
80
82
81 # parse the changegroup data, otherwise we will block
83 # parse the changegroup data, otherwise we will block
82 # in case of sshrepo because we don't know the end of the stream
84 # in case of sshrepo because we don't know the end of the stream
83
85
84 # an empty chunkiter is the end of the changegroup
86 # an empty chunkiter is the end of the changegroup
85 # a changegroup has at least 2 chunkiters (changelog and manifest).
87 # a changegroup has at least 2 chunkiters (changelog and manifest).
86 # after that, an empty chunkiter is the end of the changegroup
88 # after that, an empty chunkiter is the end of the changegroup
87 empty = False
89 empty = False
88 count = 0
90 count = 0
89 while not empty or count <= 2:
91 while not empty or count <= 2:
90 empty = True
92 empty = True
91 count += 1
93 count += 1
92 for chunk in chunkiter(cg):
94 for chunk in chunkiter(cg):
93 empty = False
95 empty = False
94 fh.write(z.compress(chunkheader(len(chunk))))
96 fh.write(z.compress(chunkheader(len(chunk))))
95 pos = 0
97 pos = 0
96 while pos < len(chunk):
98 while pos < len(chunk):
97 next = pos + 2**20
99 next = pos + 2**20
98 fh.write(z.compress(chunk[pos:next]))
100 fh.write(z.compress(chunk[pos:next]))
99 pos = next
101 pos = next
100 fh.write(z.compress(closechunk()))
102 fh.write(z.compress(closechunk()))
101 fh.write(z.flush())
103 fh.write(z.flush())
102 cleanup = None
104 cleanup = None
103 return filename
105 return filename
104 finally:
106 finally:
105 if fh is not None:
107 if fh is not None:
106 fh.close()
108 fh.close()
107 if cleanup is not None:
109 if cleanup is not None:
108 os.unlink(cleanup)
110 os.unlink(cleanup)
109
111
110 def unbundle(header, fh):
112 def unbundle(header, fh):
111 if header == 'HG10UN':
113 if header == 'HG10UN':
112 return fh
114 return fh
113 elif not header.startswith('HG'):
115 elif not header.startswith('HG'):
114 # old client with uncompressed bundle
116 # old client with uncompressed bundle
115 def generator(f):
117 def generator(f):
116 yield header
118 yield header
117 for chunk in f:
119 for chunk in f:
118 yield chunk
120 yield chunk
119 elif header == 'HG10GZ':
121 elif header == 'HG10GZ':
120 def generator(f):
122 def generator(f):
121 zd = zlib.decompressobj()
123 zd = zlib.decompressobj()
122 for chunk in f:
124 for chunk in f:
123 yield zd.decompress(chunk)
125 yield zd.decompress(chunk)
124 elif header == 'HG10BZ':
126 elif header == 'HG10BZ':
125 def generator(f):
127 def generator(f):
126 zd = bz2.BZ2Decompressor()
128 zd = bz2.BZ2Decompressor()
127 zd.decompress("BZ")
129 zd.decompress("BZ")
128 for chunk in util.filechunkiter(f, 4096):
130 for chunk in util.filechunkiter(f, 4096):
129 yield zd.decompress(chunk)
131 yield zd.decompress(chunk)
130 return util.chunkbuffer(generator(fh))
132 return util.chunkbuffer(generator(fh))
131
133
132 def readbundle(fh, fname):
134 def readbundle(fh, fname):
133 header = fh.read(6)
135 header = fh.read(6)
134 if not header.startswith('HG'):
136 if not header.startswith('HG'):
135 raise util.Abort(_('%s: not a Mercurial bundle file') % fname)
137 raise util.Abort(_('%s: not a Mercurial bundle file') % fname)
136 if not header.startswith('HG10'):
138 if not header.startswith('HG10'):
137 raise util.Abort(_('%s: unknown bundle version') % fname)
139 raise util.Abort(_('%s: unknown bundle version') % fname)
138 elif header not in bundletypes:
140 elif header not in bundletypes:
139 raise util.Abort(_('%s: unknown bundle compression type') % fname)
141 raise util.Abort(_('%s: unknown bundle compression type') % fname)
140 return unbundle(header, fh)
142 return unbundle(header, fh)
@@ -1,2156 +1,2165 b''
1 # localrepo.py - read/write repository class for mercurial
1 # localrepo.py - read/write repository class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 from node import bin, hex, nullid, nullrev, short
8 from node import bin, hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import repo, changegroup, subrepo
10 import repo, changegroup, subrepo
11 import changelog, dirstate, filelog, manifest, context
11 import changelog, dirstate, filelog, manifest, context
12 import lock, transaction, store, encoding
12 import lock, transaction, store, encoding
13 import util, extensions, hook, error
13 import util, extensions, hook, error
14 import match as match_
14 import match as match_
15 import merge as merge_
15 import merge as merge_
16 import tags as tags_
16 import tags as tags_
17 from lock import release
17 from lock import release
18 import weakref, stat, errno, os, time, inspect
18 import weakref, stat, errno, os, time, inspect
19 propertycache = util.propertycache
19 propertycache = util.propertycache
20
20
21 class localrepository(repo.repository):
21 class localrepository(repo.repository):
22 capabilities = set(('lookup', 'changegroupsubset', 'branchmap'))
22 capabilities = set(('lookup', 'changegroupsubset', 'branchmap'))
23 supported = set('revlogv1 store fncache shared'.split())
23 supported = set('revlogv1 store fncache shared'.split())
24
24
25 def __init__(self, baseui, path=None, create=0):
25 def __init__(self, baseui, path=None, create=0):
26 repo.repository.__init__(self)
26 repo.repository.__init__(self)
27 self.root = os.path.realpath(path)
27 self.root = os.path.realpath(path)
28 self.path = os.path.join(self.root, ".hg")
28 self.path = os.path.join(self.root, ".hg")
29 self.origroot = path
29 self.origroot = path
30 self.opener = util.opener(self.path)
30 self.opener = util.opener(self.path)
31 self.wopener = util.opener(self.root)
31 self.wopener = util.opener(self.root)
32 self.baseui = baseui
32 self.baseui = baseui
33 self.ui = baseui.copy()
33 self.ui = baseui.copy()
34
34
35 try:
35 try:
36 self.ui.readconfig(self.join("hgrc"), self.root)
36 self.ui.readconfig(self.join("hgrc"), self.root)
37 extensions.loadall(self.ui)
37 extensions.loadall(self.ui)
38 except IOError:
38 except IOError:
39 pass
39 pass
40
40
41 if not os.path.isdir(self.path):
41 if not os.path.isdir(self.path):
42 if create:
42 if create:
43 if not os.path.exists(path):
43 if not os.path.exists(path):
44 os.mkdir(path)
44 os.mkdir(path)
45 os.mkdir(self.path)
45 os.mkdir(self.path)
46 requirements = ["revlogv1"]
46 requirements = ["revlogv1"]
47 if self.ui.configbool('format', 'usestore', True):
47 if self.ui.configbool('format', 'usestore', True):
48 os.mkdir(os.path.join(self.path, "store"))
48 os.mkdir(os.path.join(self.path, "store"))
49 requirements.append("store")
49 requirements.append("store")
50 if self.ui.configbool('format', 'usefncache', True):
50 if self.ui.configbool('format', 'usefncache', True):
51 requirements.append("fncache")
51 requirements.append("fncache")
52 # create an invalid changelog
52 # create an invalid changelog
53 self.opener("00changelog.i", "a").write(
53 self.opener("00changelog.i", "a").write(
54 '\0\0\0\2' # represents revlogv2
54 '\0\0\0\2' # represents revlogv2
55 ' dummy changelog to prevent using the old repo layout'
55 ' dummy changelog to prevent using the old repo layout'
56 )
56 )
57 reqfile = self.opener("requires", "w")
57 reqfile = self.opener("requires", "w")
58 for r in requirements:
58 for r in requirements:
59 reqfile.write("%s\n" % r)
59 reqfile.write("%s\n" % r)
60 reqfile.close()
60 reqfile.close()
61 else:
61 else:
62 raise error.RepoError(_("repository %s not found") % path)
62 raise error.RepoError(_("repository %s not found") % path)
63 elif create:
63 elif create:
64 raise error.RepoError(_("repository %s already exists") % path)
64 raise error.RepoError(_("repository %s already exists") % path)
65 else:
65 else:
66 # find requirements
66 # find requirements
67 requirements = set()
67 requirements = set()
68 try:
68 try:
69 requirements = set(self.opener("requires").read().splitlines())
69 requirements = set(self.opener("requires").read().splitlines())
70 except IOError, inst:
70 except IOError, inst:
71 if inst.errno != errno.ENOENT:
71 if inst.errno != errno.ENOENT:
72 raise
72 raise
73 for r in requirements - self.supported:
73 for r in requirements - self.supported:
74 raise error.RepoError(_("requirement '%s' not supported") % r)
74 raise error.RepoError(_("requirement '%s' not supported") % r)
75
75
76 self.sharedpath = self.path
76 self.sharedpath = self.path
77 try:
77 try:
78 s = os.path.realpath(self.opener("sharedpath").read())
78 s = os.path.realpath(self.opener("sharedpath").read())
79 if not os.path.exists(s):
79 if not os.path.exists(s):
80 raise error.RepoError(
80 raise error.RepoError(
81 _('.hg/sharedpath points to nonexistent directory %s') % s)
81 _('.hg/sharedpath points to nonexistent directory %s') % s)
82 self.sharedpath = s
82 self.sharedpath = s
83 except IOError, inst:
83 except IOError, inst:
84 if inst.errno != errno.ENOENT:
84 if inst.errno != errno.ENOENT:
85 raise
85 raise
86
86
87 self.store = store.store(requirements, self.sharedpath, util.opener)
87 self.store = store.store(requirements, self.sharedpath, util.opener)
88 self.spath = self.store.path
88 self.spath = self.store.path
89 self.sopener = self.store.opener
89 self.sopener = self.store.opener
90 self.sjoin = self.store.join
90 self.sjoin = self.store.join
91 self.opener.createmode = self.store.createmode
91 self.opener.createmode = self.store.createmode
92
92
93 # These two define the set of tags for this repository. _tags
93 # These two define the set of tags for this repository. _tags
94 # maps tag name to node; _tagtypes maps tag name to 'global' or
94 # maps tag name to node; _tagtypes maps tag name to 'global' or
95 # 'local'. (Global tags are defined by .hgtags across all
95 # 'local'. (Global tags are defined by .hgtags across all
96 # heads, and local tags are defined in .hg/localtags.) They
96 # heads, and local tags are defined in .hg/localtags.) They
97 # constitute the in-memory cache of tags.
97 # constitute the in-memory cache of tags.
98 self._tags = None
98 self._tags = None
99 self._tagtypes = None
99 self._tagtypes = None
100
100
101 self.branchcache = None
101 self.branchcache = None
102 self._ubranchcache = None # UTF-8 version of branchcache
102 self._ubranchcache = None # UTF-8 version of branchcache
103 self._branchcachetip = None
103 self._branchcachetip = None
104 self.nodetagscache = None
104 self.nodetagscache = None
105 self.filterpats = {}
105 self.filterpats = {}
106 self._datafilters = {}
106 self._datafilters = {}
107 self._transref = self._lockref = self._wlockref = None
107 self._transref = self._lockref = self._wlockref = None
108
108
109 @propertycache
109 @propertycache
110 def changelog(self):
110 def changelog(self):
111 c = changelog.changelog(self.sopener)
111 c = changelog.changelog(self.sopener)
112 if 'HG_PENDING' in os.environ:
112 if 'HG_PENDING' in os.environ:
113 p = os.environ['HG_PENDING']
113 p = os.environ['HG_PENDING']
114 if p.startswith(self.root):
114 if p.startswith(self.root):
115 c.readpending('00changelog.i.a')
115 c.readpending('00changelog.i.a')
116 self.sopener.defversion = c.version
116 self.sopener.defversion = c.version
117 return c
117 return c
118
118
119 @propertycache
119 @propertycache
120 def manifest(self):
120 def manifest(self):
121 return manifest.manifest(self.sopener)
121 return manifest.manifest(self.sopener)
122
122
123 @propertycache
123 @propertycache
124 def dirstate(self):
124 def dirstate(self):
125 return dirstate.dirstate(self.opener, self.ui, self.root)
125 return dirstate.dirstate(self.opener, self.ui, self.root)
126
126
127 def __getitem__(self, changeid):
127 def __getitem__(self, changeid):
128 if changeid is None:
128 if changeid is None:
129 return context.workingctx(self)
129 return context.workingctx(self)
130 return context.changectx(self, changeid)
130 return context.changectx(self, changeid)
131
131
132 def __nonzero__(self):
132 def __nonzero__(self):
133 return True
133 return True
134
134
135 def __len__(self):
135 def __len__(self):
136 return len(self.changelog)
136 return len(self.changelog)
137
137
138 def __iter__(self):
138 def __iter__(self):
139 for i in xrange(len(self)):
139 for i in xrange(len(self)):
140 yield i
140 yield i
141
141
142 def url(self):
142 def url(self):
143 return 'file:' + self.root
143 return 'file:' + self.root
144
144
145 def hook(self, name, throw=False, **args):
145 def hook(self, name, throw=False, **args):
146 return hook.hook(self.ui, self, name, throw, **args)
146 return hook.hook(self.ui, self, name, throw, **args)
147
147
148 tag_disallowed = ':\r\n'
148 tag_disallowed = ':\r\n'
149
149
150 def _tag(self, names, node, message, local, user, date, extra={}):
150 def _tag(self, names, node, message, local, user, date, extra={}):
151 if isinstance(names, str):
151 if isinstance(names, str):
152 allchars = names
152 allchars = names
153 names = (names,)
153 names = (names,)
154 else:
154 else:
155 allchars = ''.join(names)
155 allchars = ''.join(names)
156 for c in self.tag_disallowed:
156 for c in self.tag_disallowed:
157 if c in allchars:
157 if c in allchars:
158 raise util.Abort(_('%r cannot be used in a tag name') % c)
158 raise util.Abort(_('%r cannot be used in a tag name') % c)
159
159
160 for name in names:
160 for name in names:
161 self.hook('pretag', throw=True, node=hex(node), tag=name,
161 self.hook('pretag', throw=True, node=hex(node), tag=name,
162 local=local)
162 local=local)
163
163
164 def writetags(fp, names, munge, prevtags):
164 def writetags(fp, names, munge, prevtags):
165 fp.seek(0, 2)
165 fp.seek(0, 2)
166 if prevtags and prevtags[-1] != '\n':
166 if prevtags and prevtags[-1] != '\n':
167 fp.write('\n')
167 fp.write('\n')
168 for name in names:
168 for name in names:
169 m = munge and munge(name) or name
169 m = munge and munge(name) or name
170 if self._tagtypes and name in self._tagtypes:
170 if self._tagtypes and name in self._tagtypes:
171 old = self._tags.get(name, nullid)
171 old = self._tags.get(name, nullid)
172 fp.write('%s %s\n' % (hex(old), m))
172 fp.write('%s %s\n' % (hex(old), m))
173 fp.write('%s %s\n' % (hex(node), m))
173 fp.write('%s %s\n' % (hex(node), m))
174 fp.close()
174 fp.close()
175
175
176 prevtags = ''
176 prevtags = ''
177 if local:
177 if local:
178 try:
178 try:
179 fp = self.opener('localtags', 'r+')
179 fp = self.opener('localtags', 'r+')
180 except IOError:
180 except IOError:
181 fp = self.opener('localtags', 'a')
181 fp = self.opener('localtags', 'a')
182 else:
182 else:
183 prevtags = fp.read()
183 prevtags = fp.read()
184
184
185 # local tags are stored in the current charset
185 # local tags are stored in the current charset
186 writetags(fp, names, None, prevtags)
186 writetags(fp, names, None, prevtags)
187 for name in names:
187 for name in names:
188 self.hook('tag', node=hex(node), tag=name, local=local)
188 self.hook('tag', node=hex(node), tag=name, local=local)
189 return
189 return
190
190
191 try:
191 try:
192 fp = self.wfile('.hgtags', 'rb+')
192 fp = self.wfile('.hgtags', 'rb+')
193 except IOError:
193 except IOError:
194 fp = self.wfile('.hgtags', 'ab')
194 fp = self.wfile('.hgtags', 'ab')
195 else:
195 else:
196 prevtags = fp.read()
196 prevtags = fp.read()
197
197
198 # committed tags are stored in UTF-8
198 # committed tags are stored in UTF-8
199 writetags(fp, names, encoding.fromlocal, prevtags)
199 writetags(fp, names, encoding.fromlocal, prevtags)
200
200
201 if '.hgtags' not in self.dirstate:
201 if '.hgtags' not in self.dirstate:
202 self.add(['.hgtags'])
202 self.add(['.hgtags'])
203
203
204 m = match_.exact(self.root, '', ['.hgtags'])
204 m = match_.exact(self.root, '', ['.hgtags'])
205 tagnode = self.commit(message, user, date, extra=extra, match=m)
205 tagnode = self.commit(message, user, date, extra=extra, match=m)
206
206
207 for name in names:
207 for name in names:
208 self.hook('tag', node=hex(node), tag=name, local=local)
208 self.hook('tag', node=hex(node), tag=name, local=local)
209
209
210 return tagnode
210 return tagnode
211
211
212 def tag(self, names, node, message, local, user, date):
212 def tag(self, names, node, message, local, user, date):
213 '''tag a revision with one or more symbolic names.
213 '''tag a revision with one or more symbolic names.
214
214
215 names is a list of strings or, when adding a single tag, names may be a
215 names is a list of strings or, when adding a single tag, names may be a
216 string.
216 string.
217
217
218 if local is True, the tags are stored in a per-repository file.
218 if local is True, the tags are stored in a per-repository file.
219 otherwise, they are stored in the .hgtags file, and a new
219 otherwise, they are stored in the .hgtags file, and a new
220 changeset is committed with the change.
220 changeset is committed with the change.
221
221
222 keyword arguments:
222 keyword arguments:
223
223
224 local: whether to store tags in non-version-controlled file
224 local: whether to store tags in non-version-controlled file
225 (default False)
225 (default False)
226
226
227 message: commit message to use if committing
227 message: commit message to use if committing
228
228
229 user: name of user to use if committing
229 user: name of user to use if committing
230
230
231 date: date tuple to use if committing'''
231 date: date tuple to use if committing'''
232
232
233 for x in self.status()[:5]:
233 for x in self.status()[:5]:
234 if '.hgtags' in x:
234 if '.hgtags' in x:
235 raise util.Abort(_('working copy of .hgtags is changed '
235 raise util.Abort(_('working copy of .hgtags is changed '
236 '(please commit .hgtags manually)'))
236 '(please commit .hgtags manually)'))
237
237
238 self.tags() # instantiate the cache
238 self.tags() # instantiate the cache
239 self._tag(names, node, message, local, user, date)
239 self._tag(names, node, message, local, user, date)
240
240
241 def tags(self):
241 def tags(self):
242 '''return a mapping of tag to node'''
242 '''return a mapping of tag to node'''
243 if self._tags is None:
243 if self._tags is None:
244 (self._tags, self._tagtypes) = self._findtags()
244 (self._tags, self._tagtypes) = self._findtags()
245
245
246 return self._tags
246 return self._tags
247
247
248 def _findtags(self):
248 def _findtags(self):
249 '''Do the hard work of finding tags. Return a pair of dicts
249 '''Do the hard work of finding tags. Return a pair of dicts
250 (tags, tagtypes) where tags maps tag name to node, and tagtypes
250 (tags, tagtypes) where tags maps tag name to node, and tagtypes
251 maps tag name to a string like \'global\' or \'local\'.
251 maps tag name to a string like \'global\' or \'local\'.
252 Subclasses or extensions are free to add their own tags, but
252 Subclasses or extensions are free to add their own tags, but
253 should be aware that the returned dicts will be retained for the
253 should be aware that the returned dicts will be retained for the
254 duration of the localrepo object.'''
254 duration of the localrepo object.'''
255
255
256 # XXX what tagtype should subclasses/extensions use? Currently
256 # XXX what tagtype should subclasses/extensions use? Currently
257 # mq and bookmarks add tags, but do not set the tagtype at all.
257 # mq and bookmarks add tags, but do not set the tagtype at all.
258 # Should each extension invent its own tag type? Should there
258 # Should each extension invent its own tag type? Should there
259 # be one tagtype for all such "virtual" tags? Or is the status
259 # be one tagtype for all such "virtual" tags? Or is the status
260 # quo fine?
260 # quo fine?
261
261
262 alltags = {} # map tag name to (node, hist)
262 alltags = {} # map tag name to (node, hist)
263 tagtypes = {}
263 tagtypes = {}
264
264
265 tags_.findglobaltags(self.ui, self, alltags, tagtypes)
265 tags_.findglobaltags(self.ui, self, alltags, tagtypes)
266 tags_.readlocaltags(self.ui, self, alltags, tagtypes)
266 tags_.readlocaltags(self.ui, self, alltags, tagtypes)
267
267
268 # Build the return dicts. Have to re-encode tag names because
268 # Build the return dicts. Have to re-encode tag names because
269 # the tags module always uses UTF-8 (in order not to lose info
269 # the tags module always uses UTF-8 (in order not to lose info
270 # writing to the cache), but the rest of Mercurial wants them in
270 # writing to the cache), but the rest of Mercurial wants them in
271 # local encoding.
271 # local encoding.
272 tags = {}
272 tags = {}
273 for (name, (node, hist)) in alltags.iteritems():
273 for (name, (node, hist)) in alltags.iteritems():
274 if node != nullid:
274 if node != nullid:
275 tags[encoding.tolocal(name)] = node
275 tags[encoding.tolocal(name)] = node
276 tags['tip'] = self.changelog.tip()
276 tags['tip'] = self.changelog.tip()
277 tagtypes = dict([(encoding.tolocal(name), value)
277 tagtypes = dict([(encoding.tolocal(name), value)
278 for (name, value) in tagtypes.iteritems()])
278 for (name, value) in tagtypes.iteritems()])
279 return (tags, tagtypes)
279 return (tags, tagtypes)
280
280
281 def tagtype(self, tagname):
281 def tagtype(self, tagname):
282 '''
282 '''
283 return the type of the given tag. result can be:
283 return the type of the given tag. result can be:
284
284
285 'local' : a local tag
285 'local' : a local tag
286 'global' : a global tag
286 'global' : a global tag
287 None : tag does not exist
287 None : tag does not exist
288 '''
288 '''
289
289
290 self.tags()
290 self.tags()
291
291
292 return self._tagtypes.get(tagname)
292 return self._tagtypes.get(tagname)
293
293
294 def tagslist(self):
294 def tagslist(self):
295 '''return a list of tags ordered by revision'''
295 '''return a list of tags ordered by revision'''
296 l = []
296 l = []
297 for t, n in self.tags().iteritems():
297 for t, n in self.tags().iteritems():
298 try:
298 try:
299 r = self.changelog.rev(n)
299 r = self.changelog.rev(n)
300 except:
300 except:
301 r = -2 # sort to the beginning of the list if unknown
301 r = -2 # sort to the beginning of the list if unknown
302 l.append((r, t, n))
302 l.append((r, t, n))
303 return [(t, n) for r, t, n in sorted(l)]
303 return [(t, n) for r, t, n in sorted(l)]
304
304
305 def nodetags(self, node):
305 def nodetags(self, node):
306 '''return the tags associated with a node'''
306 '''return the tags associated with a node'''
307 if not self.nodetagscache:
307 if not self.nodetagscache:
308 self.nodetagscache = {}
308 self.nodetagscache = {}
309 for t, n in self.tags().iteritems():
309 for t, n in self.tags().iteritems():
310 self.nodetagscache.setdefault(n, []).append(t)
310 self.nodetagscache.setdefault(n, []).append(t)
311 return self.nodetagscache.get(node, [])
311 return self.nodetagscache.get(node, [])
312
312
313 def _branchtags(self, partial, lrev):
313 def _branchtags(self, partial, lrev):
314 # TODO: rename this function?
314 # TODO: rename this function?
315 tiprev = len(self) - 1
315 tiprev = len(self) - 1
316 if lrev != tiprev:
316 if lrev != tiprev:
317 self._updatebranchcache(partial, lrev+1, tiprev+1)
317 self._updatebranchcache(partial, lrev+1, tiprev+1)
318 self._writebranchcache(partial, self.changelog.tip(), tiprev)
318 self._writebranchcache(partial, self.changelog.tip(), tiprev)
319
319
320 return partial
320 return partial
321
321
322 def branchmap(self):
322 def branchmap(self):
323 tip = self.changelog.tip()
323 tip = self.changelog.tip()
324 if self.branchcache is not None and self._branchcachetip == tip:
324 if self.branchcache is not None and self._branchcachetip == tip:
325 return self.branchcache
325 return self.branchcache
326
326
327 oldtip = self._branchcachetip
327 oldtip = self._branchcachetip
328 self._branchcachetip = tip
328 self._branchcachetip = tip
329 if self.branchcache is None:
329 if self.branchcache is None:
330 self.branchcache = {} # avoid recursion in changectx
330 self.branchcache = {} # avoid recursion in changectx
331 else:
331 else:
332 self.branchcache.clear() # keep using the same dict
332 self.branchcache.clear() # keep using the same dict
333 if oldtip is None or oldtip not in self.changelog.nodemap:
333 if oldtip is None or oldtip not in self.changelog.nodemap:
334 partial, last, lrev = self._readbranchcache()
334 partial, last, lrev = self._readbranchcache()
335 else:
335 else:
336 lrev = self.changelog.rev(oldtip)
336 lrev = self.changelog.rev(oldtip)
337 partial = self._ubranchcache
337 partial = self._ubranchcache
338
338
339 self._branchtags(partial, lrev)
339 self._branchtags(partial, lrev)
340 # this private cache holds all heads (not just tips)
340 # this private cache holds all heads (not just tips)
341 self._ubranchcache = partial
341 self._ubranchcache = partial
342
342
343 # the branch cache is stored on disk as UTF-8, but in the local
343 # the branch cache is stored on disk as UTF-8, but in the local
344 # charset internally
344 # charset internally
345 for k, v in partial.iteritems():
345 for k, v in partial.iteritems():
346 self.branchcache[encoding.tolocal(k)] = v
346 self.branchcache[encoding.tolocal(k)] = v
347 return self.branchcache
347 return self.branchcache
348
348
349
349
350 def branchtags(self):
350 def branchtags(self):
351 '''return a dict where branch names map to the tipmost head of
351 '''return a dict where branch names map to the tipmost head of
352 the branch, open heads come before closed'''
352 the branch, open heads come before closed'''
353 bt = {}
353 bt = {}
354 for bn, heads in self.branchmap().iteritems():
354 for bn, heads in self.branchmap().iteritems():
355 head = None
355 head = None
356 for i in range(len(heads)-1, -1, -1):
356 for i in range(len(heads)-1, -1, -1):
357 h = heads[i]
357 h = heads[i]
358 if 'close' not in self.changelog.read(h)[5]:
358 if 'close' not in self.changelog.read(h)[5]:
359 head = h
359 head = h
360 break
360 break
361 # no open heads were found
361 # no open heads were found
362 if head is None:
362 if head is None:
363 head = heads[-1]
363 head = heads[-1]
364 bt[bn] = head
364 bt[bn] = head
365 return bt
365 return bt
366
366
367
367
368 def _readbranchcache(self):
368 def _readbranchcache(self):
369 partial = {}
369 partial = {}
370 try:
370 try:
371 f = self.opener("branchheads.cache")
371 f = self.opener("branchheads.cache")
372 lines = f.read().split('\n')
372 lines = f.read().split('\n')
373 f.close()
373 f.close()
374 except (IOError, OSError):
374 except (IOError, OSError):
375 return {}, nullid, nullrev
375 return {}, nullid, nullrev
376
376
377 try:
377 try:
378 last, lrev = lines.pop(0).split(" ", 1)
378 last, lrev = lines.pop(0).split(" ", 1)
379 last, lrev = bin(last), int(lrev)
379 last, lrev = bin(last), int(lrev)
380 if lrev >= len(self) or self[lrev].node() != last:
380 if lrev >= len(self) or self[lrev].node() != last:
381 # invalidate the cache
381 # invalidate the cache
382 raise ValueError('invalidating branch cache (tip differs)')
382 raise ValueError('invalidating branch cache (tip differs)')
383 for l in lines:
383 for l in lines:
384 if not l: continue
384 if not l: continue
385 node, label = l.split(" ", 1)
385 node, label = l.split(" ", 1)
386 partial.setdefault(label.strip(), []).append(bin(node))
386 partial.setdefault(label.strip(), []).append(bin(node))
387 except KeyboardInterrupt:
387 except KeyboardInterrupt:
388 raise
388 raise
389 except Exception, inst:
389 except Exception, inst:
390 if self.ui.debugflag:
390 if self.ui.debugflag:
391 self.ui.warn(str(inst), '\n')
391 self.ui.warn(str(inst), '\n')
392 partial, last, lrev = {}, nullid, nullrev
392 partial, last, lrev = {}, nullid, nullrev
393 return partial, last, lrev
393 return partial, last, lrev
394
394
395 def _writebranchcache(self, branches, tip, tiprev):
395 def _writebranchcache(self, branches, tip, tiprev):
396 try:
396 try:
397 f = self.opener("branchheads.cache", "w", atomictemp=True)
397 f = self.opener("branchheads.cache", "w", atomictemp=True)
398 f.write("%s %s\n" % (hex(tip), tiprev))
398 f.write("%s %s\n" % (hex(tip), tiprev))
399 for label, nodes in branches.iteritems():
399 for label, nodes in branches.iteritems():
400 for node in nodes:
400 for node in nodes:
401 f.write("%s %s\n" % (hex(node), label))
401 f.write("%s %s\n" % (hex(node), label))
402 f.rename()
402 f.rename()
403 except (IOError, OSError):
403 except (IOError, OSError):
404 pass
404 pass
405
405
406 def _updatebranchcache(self, partial, start, end):
406 def _updatebranchcache(self, partial, start, end):
407 # collect new branch entries
407 # collect new branch entries
408 newbranches = {}
408 newbranches = {}
409 for r in xrange(start, end):
409 for r in xrange(start, end):
410 c = self[r]
410 c = self[r]
411 newbranches.setdefault(c.branch(), []).append(c.node())
411 newbranches.setdefault(c.branch(), []).append(c.node())
412 # if older branchheads are reachable from new ones, they aren't
412 # if older branchheads are reachable from new ones, they aren't
413 # really branchheads. Note checking parents is insufficient:
413 # really branchheads. Note checking parents is insufficient:
414 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
414 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
415 for branch, newnodes in newbranches.iteritems():
415 for branch, newnodes in newbranches.iteritems():
416 bheads = partial.setdefault(branch, [])
416 bheads = partial.setdefault(branch, [])
417 bheads.extend(newnodes)
417 bheads.extend(newnodes)
418 if len(bheads) < 2:
418 if len(bheads) < 2:
419 continue
419 continue
420 newbheads = []
420 newbheads = []
421 # starting from tip means fewer passes over reachable
421 # starting from tip means fewer passes over reachable
422 while newnodes:
422 while newnodes:
423 latest = newnodes.pop()
423 latest = newnodes.pop()
424 if latest not in bheads:
424 if latest not in bheads:
425 continue
425 continue
426 minbhrev = self[min([self[bh].rev() for bh in bheads])].node()
426 minbhrev = self[min([self[bh].rev() for bh in bheads])].node()
427 reachable = self.changelog.reachable(latest, minbhrev)
427 reachable = self.changelog.reachable(latest, minbhrev)
428 bheads = [b for b in bheads if b not in reachable]
428 bheads = [b for b in bheads if b not in reachable]
429 newbheads.insert(0, latest)
429 newbheads.insert(0, latest)
430 bheads.extend(newbheads)
430 bheads.extend(newbheads)
431 partial[branch] = bheads
431 partial[branch] = bheads
432
432
433 def lookup(self, key):
433 def lookup(self, key):
434 if isinstance(key, int):
434 if isinstance(key, int):
435 return self.changelog.node(key)
435 return self.changelog.node(key)
436 elif key == '.':
436 elif key == '.':
437 return self.dirstate.parents()[0]
437 return self.dirstate.parents()[0]
438 elif key == 'null':
438 elif key == 'null':
439 return nullid
439 return nullid
440 elif key == 'tip':
440 elif key == 'tip':
441 return self.changelog.tip()
441 return self.changelog.tip()
442 n = self.changelog._match(key)
442 n = self.changelog._match(key)
443 if n:
443 if n:
444 return n
444 return n
445 if key in self.tags():
445 if key in self.tags():
446 return self.tags()[key]
446 return self.tags()[key]
447 if key in self.branchtags():
447 if key in self.branchtags():
448 return self.branchtags()[key]
448 return self.branchtags()[key]
449 n = self.changelog._partialmatch(key)
449 n = self.changelog._partialmatch(key)
450 if n:
450 if n:
451 return n
451 return n
452
452
453 # can't find key, check if it might have come from damaged dirstate
453 # can't find key, check if it might have come from damaged dirstate
454 if key in self.dirstate.parents():
454 if key in self.dirstate.parents():
455 raise error.Abort(_("working directory has unknown parent '%s'!")
455 raise error.Abort(_("working directory has unknown parent '%s'!")
456 % short(key))
456 % short(key))
457 try:
457 try:
458 if len(key) == 20:
458 if len(key) == 20:
459 key = hex(key)
459 key = hex(key)
460 except:
460 except:
461 pass
461 pass
462 raise error.RepoLookupError(_("unknown revision '%s'") % key)
462 raise error.RepoLookupError(_("unknown revision '%s'") % key)
463
463
464 def local(self):
464 def local(self):
465 return True
465 return True
466
466
467 def join(self, f):
467 def join(self, f):
468 return os.path.join(self.path, f)
468 return os.path.join(self.path, f)
469
469
470 def wjoin(self, f):
470 def wjoin(self, f):
471 return os.path.join(self.root, f)
471 return os.path.join(self.root, f)
472
472
473 def rjoin(self, f):
473 def rjoin(self, f):
474 return os.path.join(self.root, util.pconvert(f))
474 return os.path.join(self.root, util.pconvert(f))
475
475
476 def file(self, f):
476 def file(self, f):
477 if f[0] == '/':
477 if f[0] == '/':
478 f = f[1:]
478 f = f[1:]
479 return filelog.filelog(self.sopener, f)
479 return filelog.filelog(self.sopener, f)
480
480
481 def changectx(self, changeid):
481 def changectx(self, changeid):
482 return self[changeid]
482 return self[changeid]
483
483
484 def parents(self, changeid=None):
484 def parents(self, changeid=None):
485 '''get list of changectxs for parents of changeid'''
485 '''get list of changectxs for parents of changeid'''
486 return self[changeid].parents()
486 return self[changeid].parents()
487
487
488 def filectx(self, path, changeid=None, fileid=None):
488 def filectx(self, path, changeid=None, fileid=None):
489 """changeid can be a changeset revision, node, or tag.
489 """changeid can be a changeset revision, node, or tag.
490 fileid can be a file revision or node."""
490 fileid can be a file revision or node."""
491 return context.filectx(self, path, changeid, fileid)
491 return context.filectx(self, path, changeid, fileid)
492
492
493 def getcwd(self):
493 def getcwd(self):
494 return self.dirstate.getcwd()
494 return self.dirstate.getcwd()
495
495
496 def pathto(self, f, cwd=None):
496 def pathto(self, f, cwd=None):
497 return self.dirstate.pathto(f, cwd)
497 return self.dirstate.pathto(f, cwd)
498
498
499 def wfile(self, f, mode='r'):
499 def wfile(self, f, mode='r'):
500 return self.wopener(f, mode)
500 return self.wopener(f, mode)
501
501
502 def _link(self, f):
502 def _link(self, f):
503 return os.path.islink(self.wjoin(f))
503 return os.path.islink(self.wjoin(f))
504
504
505 def _filter(self, filter, filename, data):
505 def _filter(self, filter, filename, data):
506 if filter not in self.filterpats:
506 if filter not in self.filterpats:
507 l = []
507 l = []
508 for pat, cmd in self.ui.configitems(filter):
508 for pat, cmd in self.ui.configitems(filter):
509 if cmd == '!':
509 if cmd == '!':
510 continue
510 continue
511 mf = match_.match(self.root, '', [pat])
511 mf = match_.match(self.root, '', [pat])
512 fn = None
512 fn = None
513 params = cmd
513 params = cmd
514 for name, filterfn in self._datafilters.iteritems():
514 for name, filterfn in self._datafilters.iteritems():
515 if cmd.startswith(name):
515 if cmd.startswith(name):
516 fn = filterfn
516 fn = filterfn
517 params = cmd[len(name):].lstrip()
517 params = cmd[len(name):].lstrip()
518 break
518 break
519 if not fn:
519 if not fn:
520 fn = lambda s, c, **kwargs: util.filter(s, c)
520 fn = lambda s, c, **kwargs: util.filter(s, c)
521 # Wrap old filters not supporting keyword arguments
521 # Wrap old filters not supporting keyword arguments
522 if not inspect.getargspec(fn)[2]:
522 if not inspect.getargspec(fn)[2]:
523 oldfn = fn
523 oldfn = fn
524 fn = lambda s, c, **kwargs: oldfn(s, c)
524 fn = lambda s, c, **kwargs: oldfn(s, c)
525 l.append((mf, fn, params))
525 l.append((mf, fn, params))
526 self.filterpats[filter] = l
526 self.filterpats[filter] = l
527
527
528 for mf, fn, cmd in self.filterpats[filter]:
528 for mf, fn, cmd in self.filterpats[filter]:
529 if mf(filename):
529 if mf(filename):
530 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
530 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
531 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
531 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
532 break
532 break
533
533
534 return data
534 return data
535
535
536 def adddatafilter(self, name, filter):
536 def adddatafilter(self, name, filter):
537 self._datafilters[name] = filter
537 self._datafilters[name] = filter
538
538
539 def wread(self, filename):
539 def wread(self, filename):
540 if self._link(filename):
540 if self._link(filename):
541 data = os.readlink(self.wjoin(filename))
541 data = os.readlink(self.wjoin(filename))
542 else:
542 else:
543 data = self.wopener(filename, 'r').read()
543 data = self.wopener(filename, 'r').read()
544 return self._filter("encode", filename, data)
544 return self._filter("encode", filename, data)
545
545
546 def wwrite(self, filename, data, flags):
546 def wwrite(self, filename, data, flags):
547 data = self._filter("decode", filename, data)
547 data = self._filter("decode", filename, data)
548 try:
548 try:
549 os.unlink(self.wjoin(filename))
549 os.unlink(self.wjoin(filename))
550 except OSError:
550 except OSError:
551 pass
551 pass
552 if 'l' in flags:
552 if 'l' in flags:
553 self.wopener.symlink(data, filename)
553 self.wopener.symlink(data, filename)
554 else:
554 else:
555 self.wopener(filename, 'w').write(data)
555 self.wopener(filename, 'w').write(data)
556 if 'x' in flags:
556 if 'x' in flags:
557 util.set_flags(self.wjoin(filename), False, True)
557 util.set_flags(self.wjoin(filename), False, True)
558
558
559 def wwritedata(self, filename, data):
559 def wwritedata(self, filename, data):
560 return self._filter("decode", filename, data)
560 return self._filter("decode", filename, data)
561
561
562 def transaction(self):
562 def transaction(self):
563 tr = self._transref and self._transref() or None
563 tr = self._transref and self._transref() or None
564 if tr and tr.running():
564 if tr and tr.running():
565 return tr.nest()
565 return tr.nest()
566
566
567 # abort here if the journal already exists
567 # abort here if the journal already exists
568 if os.path.exists(self.sjoin("journal")):
568 if os.path.exists(self.sjoin("journal")):
569 raise error.RepoError(_("journal already exists - run hg recover"))
569 raise error.RepoError(_("journal already exists - run hg recover"))
570
570
571 # save dirstate for rollback
571 # save dirstate for rollback
572 try:
572 try:
573 ds = self.opener("dirstate").read()
573 ds = self.opener("dirstate").read()
574 except IOError:
574 except IOError:
575 ds = ""
575 ds = ""
576 self.opener("journal.dirstate", "w").write(ds)
576 self.opener("journal.dirstate", "w").write(ds)
577 self.opener("journal.branch", "w").write(self.dirstate.branch())
577 self.opener("journal.branch", "w").write(self.dirstate.branch())
578
578
579 renames = [(self.sjoin("journal"), self.sjoin("undo")),
579 renames = [(self.sjoin("journal"), self.sjoin("undo")),
580 (self.join("journal.dirstate"), self.join("undo.dirstate")),
580 (self.join("journal.dirstate"), self.join("undo.dirstate")),
581 (self.join("journal.branch"), self.join("undo.branch"))]
581 (self.join("journal.branch"), self.join("undo.branch"))]
582 tr = transaction.transaction(self.ui.warn, self.sopener,
582 tr = transaction.transaction(self.ui.warn, self.sopener,
583 self.sjoin("journal"),
583 self.sjoin("journal"),
584 aftertrans(renames),
584 aftertrans(renames),
585 self.store.createmode)
585 self.store.createmode)
586 self._transref = weakref.ref(tr)
586 self._transref = weakref.ref(tr)
587 return tr
587 return tr
588
588
589 def recover(self):
589 def recover(self):
590 lock = self.lock()
590 lock = self.lock()
591 try:
591 try:
592 if os.path.exists(self.sjoin("journal")):
592 if os.path.exists(self.sjoin("journal")):
593 self.ui.status(_("rolling back interrupted transaction\n"))
593 self.ui.status(_("rolling back interrupted transaction\n"))
594 transaction.rollback(self.sopener, self.sjoin("journal"), self.ui.warn)
594 transaction.rollback(self.sopener, self.sjoin("journal"), self.ui.warn)
595 self.invalidate()
595 self.invalidate()
596 return True
596 return True
597 else:
597 else:
598 self.ui.warn(_("no interrupted transaction available\n"))
598 self.ui.warn(_("no interrupted transaction available\n"))
599 return False
599 return False
600 finally:
600 finally:
601 lock.release()
601 lock.release()
602
602
603 def rollback(self):
603 def rollback(self):
604 wlock = lock = None
604 wlock = lock = None
605 try:
605 try:
606 wlock = self.wlock()
606 wlock = self.wlock()
607 lock = self.lock()
607 lock = self.lock()
608 if os.path.exists(self.sjoin("undo")):
608 if os.path.exists(self.sjoin("undo")):
609 self.ui.status(_("rolling back last transaction\n"))
609 self.ui.status(_("rolling back last transaction\n"))
610 transaction.rollback(self.sopener, self.sjoin("undo"), self.ui.warn)
610 transaction.rollback(self.sopener, self.sjoin("undo"), self.ui.warn)
611 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
611 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
612 try:
612 try:
613 branch = self.opener("undo.branch").read()
613 branch = self.opener("undo.branch").read()
614 self.dirstate.setbranch(branch)
614 self.dirstate.setbranch(branch)
615 except IOError:
615 except IOError:
616 self.ui.warn(_("Named branch could not be reset, "
616 self.ui.warn(_("Named branch could not be reset, "
617 "current branch still is: %s\n")
617 "current branch still is: %s\n")
618 % encoding.tolocal(self.dirstate.branch()))
618 % encoding.tolocal(self.dirstate.branch()))
619 self.invalidate()
619 self.invalidate()
620 self.dirstate.invalidate()
620 self.dirstate.invalidate()
621 self.destroyed()
621 self.destroyed()
622 else:
622 else:
623 self.ui.warn(_("no rollback information available\n"))
623 self.ui.warn(_("no rollback information available\n"))
624 finally:
624 finally:
625 release(lock, wlock)
625 release(lock, wlock)
626
626
627 def invalidate(self):
627 def invalidate(self):
628 for a in "changelog manifest".split():
628 for a in "changelog manifest".split():
629 if a in self.__dict__:
629 if a in self.__dict__:
630 delattr(self, a)
630 delattr(self, a)
631 self._tags = None
631 self._tags = None
632 self._tagtypes = None
632 self._tagtypes = None
633 self.nodetagscache = None
633 self.nodetagscache = None
634 self.branchcache = None
634 self.branchcache = None
635 self._ubranchcache = None
635 self._ubranchcache = None
636 self._branchcachetip = None
636 self._branchcachetip = None
637
637
638 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
638 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
639 try:
639 try:
640 l = lock.lock(lockname, 0, releasefn, desc=desc)
640 l = lock.lock(lockname, 0, releasefn, desc=desc)
641 except error.LockHeld, inst:
641 except error.LockHeld, inst:
642 if not wait:
642 if not wait:
643 raise
643 raise
644 self.ui.warn(_("waiting for lock on %s held by %r\n") %
644 self.ui.warn(_("waiting for lock on %s held by %r\n") %
645 (desc, inst.locker))
645 (desc, inst.locker))
646 # default to 600 seconds timeout
646 # default to 600 seconds timeout
647 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
647 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
648 releasefn, desc=desc)
648 releasefn, desc=desc)
649 if acquirefn:
649 if acquirefn:
650 acquirefn()
650 acquirefn()
651 return l
651 return l
652
652
653 def lock(self, wait=True):
653 def lock(self, wait=True):
654 '''Lock the repository store (.hg/store) and return a weak reference
654 '''Lock the repository store (.hg/store) and return a weak reference
655 to the lock. Use this before modifying the store (e.g. committing or
655 to the lock. Use this before modifying the store (e.g. committing or
656 stripping). If you are opening a transaction, get a lock as well.)'''
656 stripping). If you are opening a transaction, get a lock as well.)'''
657 l = self._lockref and self._lockref()
657 l = self._lockref and self._lockref()
658 if l is not None and l.held:
658 if l is not None and l.held:
659 l.lock()
659 l.lock()
660 return l
660 return l
661
661
662 l = self._lock(self.sjoin("lock"), wait, None, self.invalidate,
662 l = self._lock(self.sjoin("lock"), wait, None, self.invalidate,
663 _('repository %s') % self.origroot)
663 _('repository %s') % self.origroot)
664 self._lockref = weakref.ref(l)
664 self._lockref = weakref.ref(l)
665 return l
665 return l
666
666
667 def wlock(self, wait=True):
667 def wlock(self, wait=True):
668 '''Lock the non-store parts of the repository (everything under
668 '''Lock the non-store parts of the repository (everything under
669 .hg except .hg/store) and return a weak reference to the lock.
669 .hg except .hg/store) and return a weak reference to the lock.
670 Use this before modifying files in .hg.'''
670 Use this before modifying files in .hg.'''
671 l = self._wlockref and self._wlockref()
671 l = self._wlockref and self._wlockref()
672 if l is not None and l.held:
672 if l is not None and l.held:
673 l.lock()
673 l.lock()
674 return l
674 return l
675
675
676 l = self._lock(self.join("wlock"), wait, self.dirstate.write,
676 l = self._lock(self.join("wlock"), wait, self.dirstate.write,
677 self.dirstate.invalidate, _('working directory of %s') %
677 self.dirstate.invalidate, _('working directory of %s') %
678 self.origroot)
678 self.origroot)
679 self._wlockref = weakref.ref(l)
679 self._wlockref = weakref.ref(l)
680 return l
680 return l
681
681
682 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
682 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
683 """
683 """
684 commit an individual file as part of a larger transaction
684 commit an individual file as part of a larger transaction
685 """
685 """
686
686
687 fname = fctx.path()
687 fname = fctx.path()
688 text = fctx.data()
688 text = fctx.data()
689 flog = self.file(fname)
689 flog = self.file(fname)
690 fparent1 = manifest1.get(fname, nullid)
690 fparent1 = manifest1.get(fname, nullid)
691 fparent2 = fparent2o = manifest2.get(fname, nullid)
691 fparent2 = fparent2o = manifest2.get(fname, nullid)
692
692
693 meta = {}
693 meta = {}
694 copy = fctx.renamed()
694 copy = fctx.renamed()
695 if copy and copy[0] != fname:
695 if copy and copy[0] != fname:
696 # Mark the new revision of this file as a copy of another
696 # Mark the new revision of this file as a copy of another
697 # file. This copy data will effectively act as a parent
697 # file. This copy data will effectively act as a parent
698 # of this new revision. If this is a merge, the first
698 # of this new revision. If this is a merge, the first
699 # parent will be the nullid (meaning "look up the copy data")
699 # parent will be the nullid (meaning "look up the copy data")
700 # and the second one will be the other parent. For example:
700 # and the second one will be the other parent. For example:
701 #
701 #
702 # 0 --- 1 --- 3 rev1 changes file foo
702 # 0 --- 1 --- 3 rev1 changes file foo
703 # \ / rev2 renames foo to bar and changes it
703 # \ / rev2 renames foo to bar and changes it
704 # \- 2 -/ rev3 should have bar with all changes and
704 # \- 2 -/ rev3 should have bar with all changes and
705 # should record that bar descends from
705 # should record that bar descends from
706 # bar in rev2 and foo in rev1
706 # bar in rev2 and foo in rev1
707 #
707 #
708 # this allows this merge to succeed:
708 # this allows this merge to succeed:
709 #
709 #
710 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
710 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
711 # \ / merging rev3 and rev4 should use bar@rev2
711 # \ / merging rev3 and rev4 should use bar@rev2
712 # \- 2 --- 4 as the merge base
712 # \- 2 --- 4 as the merge base
713 #
713 #
714
714
715 cfname = copy[0]
715 cfname = copy[0]
716 crev = manifest1.get(cfname)
716 crev = manifest1.get(cfname)
717 newfparent = fparent2
717 newfparent = fparent2
718
718
719 if manifest2: # branch merge
719 if manifest2: # branch merge
720 if fparent2 == nullid or crev is None: # copied on remote side
720 if fparent2 == nullid or crev is None: # copied on remote side
721 if cfname in manifest2:
721 if cfname in manifest2:
722 crev = manifest2[cfname]
722 crev = manifest2[cfname]
723 newfparent = fparent1
723 newfparent = fparent1
724
724
725 # find source in nearest ancestor if we've lost track
725 # find source in nearest ancestor if we've lost track
726 if not crev:
726 if not crev:
727 self.ui.debug(_(" %s: searching for copy revision for %s\n") %
727 self.ui.debug(_(" %s: searching for copy revision for %s\n") %
728 (fname, cfname))
728 (fname, cfname))
729 for ancestor in self['.'].ancestors():
729 for ancestor in self['.'].ancestors():
730 if cfname in ancestor:
730 if cfname in ancestor:
731 crev = ancestor[cfname].filenode()
731 crev = ancestor[cfname].filenode()
732 break
732 break
733
733
734 self.ui.debug(_(" %s: copy %s:%s\n") % (fname, cfname, hex(crev)))
734 self.ui.debug(_(" %s: copy %s:%s\n") % (fname, cfname, hex(crev)))
735 meta["copy"] = cfname
735 meta["copy"] = cfname
736 meta["copyrev"] = hex(crev)
736 meta["copyrev"] = hex(crev)
737 fparent1, fparent2 = nullid, newfparent
737 fparent1, fparent2 = nullid, newfparent
738 elif fparent2 != nullid:
738 elif fparent2 != nullid:
739 # is one parent an ancestor of the other?
739 # is one parent an ancestor of the other?
740 fparentancestor = flog.ancestor(fparent1, fparent2)
740 fparentancestor = flog.ancestor(fparent1, fparent2)
741 if fparentancestor == fparent1:
741 if fparentancestor == fparent1:
742 fparent1, fparent2 = fparent2, nullid
742 fparent1, fparent2 = fparent2, nullid
743 elif fparentancestor == fparent2:
743 elif fparentancestor == fparent2:
744 fparent2 = nullid
744 fparent2 = nullid
745
745
746 # is the file changed?
746 # is the file changed?
747 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
747 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
748 changelist.append(fname)
748 changelist.append(fname)
749 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
749 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
750
750
751 # are just the flags changed during merge?
751 # are just the flags changed during merge?
752 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
752 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
753 changelist.append(fname)
753 changelist.append(fname)
754
754
755 return fparent1
755 return fparent1
756
756
757 def commit(self, text="", user=None, date=None, match=None, force=False,
757 def commit(self, text="", user=None, date=None, match=None, force=False,
758 editor=False, extra={}):
758 editor=False, extra={}):
759 """Add a new revision to current repository.
759 """Add a new revision to current repository.
760
760
761 Revision information is gathered from the working directory,
761 Revision information is gathered from the working directory,
762 match can be used to filter the committed files. If editor is
762 match can be used to filter the committed files. If editor is
763 supplied, it is called to get a commit message.
763 supplied, it is called to get a commit message.
764 """
764 """
765
765
766 def fail(f, msg):
766 def fail(f, msg):
767 raise util.Abort('%s: %s' % (f, msg))
767 raise util.Abort('%s: %s' % (f, msg))
768
768
769 if not match:
769 if not match:
770 match = match_.always(self.root, '')
770 match = match_.always(self.root, '')
771
771
772 if not force:
772 if not force:
773 vdirs = []
773 vdirs = []
774 match.dir = vdirs.append
774 match.dir = vdirs.append
775 match.bad = fail
775 match.bad = fail
776
776
777 wlock = self.wlock()
777 wlock = self.wlock()
778 try:
778 try:
779 p1, p2 = self.dirstate.parents()
779 p1, p2 = self.dirstate.parents()
780 wctx = self[None]
780 wctx = self[None]
781
781
782 if (not force and p2 != nullid and match and
782 if (not force and p2 != nullid and match and
783 (match.files() or match.anypats())):
783 (match.files() or match.anypats())):
784 raise util.Abort(_('cannot partially commit a merge '
784 raise util.Abort(_('cannot partially commit a merge '
785 '(do not specify files or patterns)'))
785 '(do not specify files or patterns)'))
786
786
787 changes = self.status(match=match, clean=force)
787 changes = self.status(match=match, clean=force)
788 if force:
788 if force:
789 changes[0].extend(changes[6]) # mq may commit unchanged files
789 changes[0].extend(changes[6]) # mq may commit unchanged files
790
790
791 # check subrepos
791 # check subrepos
792 subs = []
792 subs = []
793 for s in wctx.substate:
793 for s in wctx.substate:
794 if match(s) and wctx.sub(s).dirty():
794 if match(s) and wctx.sub(s).dirty():
795 subs.append(s)
795 subs.append(s)
796 if subs and '.hgsubstate' not in changes[0]:
796 if subs and '.hgsubstate' not in changes[0]:
797 changes[0].insert(0, '.hgsubstate')
797 changes[0].insert(0, '.hgsubstate')
798
798
799 # make sure all explicit patterns are matched
799 # make sure all explicit patterns are matched
800 if not force and match.files():
800 if not force and match.files():
801 matched = set(changes[0] + changes[1] + changes[2])
801 matched = set(changes[0] + changes[1] + changes[2])
802
802
803 for f in match.files():
803 for f in match.files():
804 if f == '.' or f in matched or f in wctx.substate:
804 if f == '.' or f in matched or f in wctx.substate:
805 continue
805 continue
806 if f in changes[3]: # missing
806 if f in changes[3]: # missing
807 fail(f, _('file not found!'))
807 fail(f, _('file not found!'))
808 if f in vdirs: # visited directory
808 if f in vdirs: # visited directory
809 d = f + '/'
809 d = f + '/'
810 for mf in matched:
810 for mf in matched:
811 if mf.startswith(d):
811 if mf.startswith(d):
812 break
812 break
813 else:
813 else:
814 fail(f, _("no match under directory!"))
814 fail(f, _("no match under directory!"))
815 elif f not in self.dirstate:
815 elif f not in self.dirstate:
816 fail(f, _("file not tracked!"))
816 fail(f, _("file not tracked!"))
817
817
818 if (not force and not extra.get("close") and p2 == nullid
818 if (not force and not extra.get("close") and p2 == nullid
819 and not (changes[0] or changes[1] or changes[2])
819 and not (changes[0] or changes[1] or changes[2])
820 and self[None].branch() == self['.'].branch()):
820 and self[None].branch() == self['.'].branch()):
821 return None
821 return None
822
822
823 ms = merge_.mergestate(self)
823 ms = merge_.mergestate(self)
824 for f in changes[0]:
824 for f in changes[0]:
825 if f in ms and ms[f] == 'u':
825 if f in ms and ms[f] == 'u':
826 raise util.Abort(_("unresolved merge conflicts "
826 raise util.Abort(_("unresolved merge conflicts "
827 "(see hg resolve)"))
827 "(see hg resolve)"))
828
828
829 cctx = context.workingctx(self, (p1, p2), text, user, date,
829 cctx = context.workingctx(self, (p1, p2), text, user, date,
830 extra, changes)
830 extra, changes)
831 if editor:
831 if editor:
832 cctx._text = editor(self, cctx, subs)
832 cctx._text = editor(self, cctx, subs)
833
833
834 # commit subs
834 # commit subs
835 if subs:
835 if subs:
836 state = wctx.substate.copy()
836 state = wctx.substate.copy()
837 for s in subs:
837 for s in subs:
838 self.ui.status(_('committing subrepository %s\n') % s)
838 self.ui.status(_('committing subrepository %s\n') % s)
839 sr = wctx.sub(s).commit(cctx._text, user, date)
839 sr = wctx.sub(s).commit(cctx._text, user, date)
840 state[s] = (state[s][0], sr)
840 state[s] = (state[s][0], sr)
841 subrepo.writestate(self, state)
841 subrepo.writestate(self, state)
842
842
843 ret = self.commitctx(cctx, True)
843 ret = self.commitctx(cctx, True)
844
844
845 # update dirstate and mergestate
845 # update dirstate and mergestate
846 for f in changes[0] + changes[1]:
846 for f in changes[0] + changes[1]:
847 self.dirstate.normal(f)
847 self.dirstate.normal(f)
848 for f in changes[2]:
848 for f in changes[2]:
849 self.dirstate.forget(f)
849 self.dirstate.forget(f)
850 self.dirstate.setparents(ret)
850 self.dirstate.setparents(ret)
851 ms.reset()
851 ms.reset()
852
852
853 return ret
853 return ret
854
854
855 finally:
855 finally:
856 wlock.release()
856 wlock.release()
857
857
858 def commitctx(self, ctx, error=False):
858 def commitctx(self, ctx, error=False):
859 """Add a new revision to current repository.
859 """Add a new revision to current repository.
860
860
861 Revision information is passed via the context argument.
861 Revision information is passed via the context argument.
862 """
862 """
863
863
864 tr = lock = None
864 tr = lock = None
865 removed = ctx.removed()
865 removed = ctx.removed()
866 p1, p2 = ctx.p1(), ctx.p2()
866 p1, p2 = ctx.p1(), ctx.p2()
867 m1 = p1.manifest().copy()
867 m1 = p1.manifest().copy()
868 m2 = p2.manifest()
868 m2 = p2.manifest()
869 user = ctx.user()
869 user = ctx.user()
870
870
871 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
871 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
872 self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
872 self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
873
873
874 lock = self.lock()
874 lock = self.lock()
875 try:
875 try:
876 tr = self.transaction()
876 tr = self.transaction()
877 trp = weakref.proxy(tr)
877 trp = weakref.proxy(tr)
878
878
879 # check in files
879 # check in files
880 new = {}
880 new = {}
881 changed = []
881 changed = []
882 linkrev = len(self)
882 linkrev = len(self)
883 for f in sorted(ctx.modified() + ctx.added()):
883 for f in sorted(ctx.modified() + ctx.added()):
884 self.ui.note(f + "\n")
884 self.ui.note(f + "\n")
885 try:
885 try:
886 fctx = ctx[f]
886 fctx = ctx[f]
887 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
887 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
888 changed)
888 changed)
889 m1.set(f, fctx.flags())
889 m1.set(f, fctx.flags())
890 except (OSError, IOError):
890 except (OSError, IOError):
891 if error:
891 if error:
892 self.ui.warn(_("trouble committing %s!\n") % f)
892 self.ui.warn(_("trouble committing %s!\n") % f)
893 raise
893 raise
894 else:
894 else:
895 removed.append(f)
895 removed.append(f)
896
896
897 # update manifest
897 # update manifest
898 m1.update(new)
898 m1.update(new)
899 removed = [f for f in sorted(removed) if f in m1 or f in m2]
899 removed = [f for f in sorted(removed) if f in m1 or f in m2]
900 drop = [f for f in removed if f in m1]
900 drop = [f for f in removed if f in m1]
901 for f in drop:
901 for f in drop:
902 del m1[f]
902 del m1[f]
903 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
903 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
904 p2.manifestnode(), (new, drop))
904 p2.manifestnode(), (new, drop))
905
905
906 # update changelog
906 # update changelog
907 self.changelog.delayupdate()
907 self.changelog.delayupdate()
908 n = self.changelog.add(mn, changed + removed, ctx.description(),
908 n = self.changelog.add(mn, changed + removed, ctx.description(),
909 trp, p1.node(), p2.node(),
909 trp, p1.node(), p2.node(),
910 user, ctx.date(), ctx.extra().copy())
910 user, ctx.date(), ctx.extra().copy())
911 p = lambda: self.changelog.writepending() and self.root or ""
911 p = lambda: self.changelog.writepending() and self.root or ""
912 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
912 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
913 parent2=xp2, pending=p)
913 parent2=xp2, pending=p)
914 self.changelog.finalize(trp)
914 self.changelog.finalize(trp)
915 tr.close()
915 tr.close()
916
916
917 if self.branchcache:
917 if self.branchcache:
918 self.branchtags()
918 self.branchtags()
919
919
920 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
920 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
921 return n
921 return n
922 finally:
922 finally:
923 del tr
923 del tr
924 lock.release()
924 lock.release()
925
925
926 def destroyed(self):
926 def destroyed(self):
927 '''Inform the repository that nodes have been destroyed.
927 '''Inform the repository that nodes have been destroyed.
928 Intended for use by strip and rollback, so there's a common
928 Intended for use by strip and rollback, so there's a common
929 place for anything that has to be done after destroying history.'''
929 place for anything that has to be done after destroying history.'''
930 # XXX it might be nice if we could take the list of destroyed
930 # XXX it might be nice if we could take the list of destroyed
931 # nodes, but I don't see an easy way for rollback() to do that
931 # nodes, but I don't see an easy way for rollback() to do that
932
932
933 # Ensure the persistent tag cache is updated. Doing it now
933 # Ensure the persistent tag cache is updated. Doing it now
934 # means that the tag cache only has to worry about destroyed
934 # means that the tag cache only has to worry about destroyed
935 # heads immediately after a strip/rollback. That in turn
935 # heads immediately after a strip/rollback. That in turn
936 # guarantees that "cachetip == currenttip" (comparing both rev
936 # guarantees that "cachetip == currenttip" (comparing both rev
937 # and node) always means no nodes have been added or destroyed.
937 # and node) always means no nodes have been added or destroyed.
938
938
939 # XXX this is suboptimal when qrefresh'ing: we strip the current
939 # XXX this is suboptimal when qrefresh'ing: we strip the current
940 # head, refresh the tag cache, then immediately add a new head.
940 # head, refresh the tag cache, then immediately add a new head.
941 # But I think doing it this way is necessary for the "instant
941 # But I think doing it this way is necessary for the "instant
942 # tag cache retrieval" case to work.
942 # tag cache retrieval" case to work.
943 tags_.findglobaltags(self.ui, self, {}, {})
943 tags_.findglobaltags(self.ui, self, {}, {})
944
944
945 def walk(self, match, node=None):
945 def walk(self, match, node=None):
946 '''
946 '''
947 walk recursively through the directory tree or a given
947 walk recursively through the directory tree or a given
948 changeset, finding all files matched by the match
948 changeset, finding all files matched by the match
949 function
949 function
950 '''
950 '''
951 return self[node].walk(match)
951 return self[node].walk(match)
952
952
953 def status(self, node1='.', node2=None, match=None,
953 def status(self, node1='.', node2=None, match=None,
954 ignored=False, clean=False, unknown=False):
954 ignored=False, clean=False, unknown=False):
955 """return status of files between two nodes or node and working directory
955 """return status of files between two nodes or node and working directory
956
956
957 If node1 is None, use the first dirstate parent instead.
957 If node1 is None, use the first dirstate parent instead.
958 If node2 is None, compare node1 with working directory.
958 If node2 is None, compare node1 with working directory.
959 """
959 """
960
960
961 def mfmatches(ctx):
961 def mfmatches(ctx):
962 mf = ctx.manifest().copy()
962 mf = ctx.manifest().copy()
963 for fn in mf.keys():
963 for fn in mf.keys():
964 if not match(fn):
964 if not match(fn):
965 del mf[fn]
965 del mf[fn]
966 return mf
966 return mf
967
967
968 if isinstance(node1, context.changectx):
968 if isinstance(node1, context.changectx):
969 ctx1 = node1
969 ctx1 = node1
970 else:
970 else:
971 ctx1 = self[node1]
971 ctx1 = self[node1]
972 if isinstance(node2, context.changectx):
972 if isinstance(node2, context.changectx):
973 ctx2 = node2
973 ctx2 = node2
974 else:
974 else:
975 ctx2 = self[node2]
975 ctx2 = self[node2]
976
976
977 working = ctx2.rev() is None
977 working = ctx2.rev() is None
978 parentworking = working and ctx1 == self['.']
978 parentworking = working and ctx1 == self['.']
979 match = match or match_.always(self.root, self.getcwd())
979 match = match or match_.always(self.root, self.getcwd())
980 listignored, listclean, listunknown = ignored, clean, unknown
980 listignored, listclean, listunknown = ignored, clean, unknown
981
981
982 # load earliest manifest first for caching reasons
982 # load earliest manifest first for caching reasons
983 if not working and ctx2.rev() < ctx1.rev():
983 if not working and ctx2.rev() < ctx1.rev():
984 ctx2.manifest()
984 ctx2.manifest()
985
985
986 if not parentworking:
986 if not parentworking:
987 def bad(f, msg):
987 def bad(f, msg):
988 if f not in ctx1:
988 if f not in ctx1:
989 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
989 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
990 match.bad = bad
990 match.bad = bad
991
991
992 if working: # we need to scan the working dir
992 if working: # we need to scan the working dir
993 s = self.dirstate.status(match, listignored, listclean, listunknown)
993 s = self.dirstate.status(match, listignored, listclean, listunknown)
994 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
994 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
995
995
996 # check for any possibly clean files
996 # check for any possibly clean files
997 if parentworking and cmp:
997 if parentworking and cmp:
998 fixup = []
998 fixup = []
999 # do a full compare of any files that might have changed
999 # do a full compare of any files that might have changed
1000 for f in sorted(cmp):
1000 for f in sorted(cmp):
1001 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
1001 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
1002 or ctx1[f].cmp(ctx2[f].data())):
1002 or ctx1[f].cmp(ctx2[f].data())):
1003 modified.append(f)
1003 modified.append(f)
1004 else:
1004 else:
1005 fixup.append(f)
1005 fixup.append(f)
1006
1006
1007 if listclean:
1007 if listclean:
1008 clean += fixup
1008 clean += fixup
1009
1009
1010 # update dirstate for files that are actually clean
1010 # update dirstate for files that are actually clean
1011 if fixup:
1011 if fixup:
1012 try:
1012 try:
1013 # updating the dirstate is optional
1013 # updating the dirstate is optional
1014 # so we don't wait on the lock
1014 # so we don't wait on the lock
1015 wlock = self.wlock(False)
1015 wlock = self.wlock(False)
1016 try:
1016 try:
1017 for f in fixup:
1017 for f in fixup:
1018 self.dirstate.normal(f)
1018 self.dirstate.normal(f)
1019 finally:
1019 finally:
1020 wlock.release()
1020 wlock.release()
1021 except error.LockError:
1021 except error.LockError:
1022 pass
1022 pass
1023
1023
1024 if not parentworking:
1024 if not parentworking:
1025 mf1 = mfmatches(ctx1)
1025 mf1 = mfmatches(ctx1)
1026 if working:
1026 if working:
1027 # we are comparing working dir against non-parent
1027 # we are comparing working dir against non-parent
1028 # generate a pseudo-manifest for the working dir
1028 # generate a pseudo-manifest for the working dir
1029 mf2 = mfmatches(self['.'])
1029 mf2 = mfmatches(self['.'])
1030 for f in cmp + modified + added:
1030 for f in cmp + modified + added:
1031 mf2[f] = None
1031 mf2[f] = None
1032 mf2.set(f, ctx2.flags(f))
1032 mf2.set(f, ctx2.flags(f))
1033 for f in removed:
1033 for f in removed:
1034 if f in mf2:
1034 if f in mf2:
1035 del mf2[f]
1035 del mf2[f]
1036 else:
1036 else:
1037 # we are comparing two revisions
1037 # we are comparing two revisions
1038 deleted, unknown, ignored = [], [], []
1038 deleted, unknown, ignored = [], [], []
1039 mf2 = mfmatches(ctx2)
1039 mf2 = mfmatches(ctx2)
1040
1040
1041 modified, added, clean = [], [], []
1041 modified, added, clean = [], [], []
1042 for fn in mf2:
1042 for fn in mf2:
1043 if fn in mf1:
1043 if fn in mf1:
1044 if (mf1.flags(fn) != mf2.flags(fn) or
1044 if (mf1.flags(fn) != mf2.flags(fn) or
1045 (mf1[fn] != mf2[fn] and
1045 (mf1[fn] != mf2[fn] and
1046 (mf2[fn] or ctx1[fn].cmp(ctx2[fn].data())))):
1046 (mf2[fn] or ctx1[fn].cmp(ctx2[fn].data())))):
1047 modified.append(fn)
1047 modified.append(fn)
1048 elif listclean:
1048 elif listclean:
1049 clean.append(fn)
1049 clean.append(fn)
1050 del mf1[fn]
1050 del mf1[fn]
1051 else:
1051 else:
1052 added.append(fn)
1052 added.append(fn)
1053 removed = mf1.keys()
1053 removed = mf1.keys()
1054
1054
1055 r = modified, added, removed, deleted, unknown, ignored, clean
1055 r = modified, added, removed, deleted, unknown, ignored, clean
1056 [l.sort() for l in r]
1056 [l.sort() for l in r]
1057 return r
1057 return r
1058
1058
1059 def add(self, list):
1059 def add(self, list):
1060 wlock = self.wlock()
1060 wlock = self.wlock()
1061 try:
1061 try:
1062 rejected = []
1062 rejected = []
1063 for f in list:
1063 for f in list:
1064 p = self.wjoin(f)
1064 p = self.wjoin(f)
1065 try:
1065 try:
1066 st = os.lstat(p)
1066 st = os.lstat(p)
1067 except:
1067 except:
1068 self.ui.warn(_("%s does not exist!\n") % f)
1068 self.ui.warn(_("%s does not exist!\n") % f)
1069 rejected.append(f)
1069 rejected.append(f)
1070 continue
1070 continue
1071 if st.st_size > 10000000:
1071 if st.st_size > 10000000:
1072 self.ui.warn(_("%s: files over 10MB may cause memory and"
1072 self.ui.warn(_("%s: files over 10MB may cause memory and"
1073 " performance problems\n"
1073 " performance problems\n"
1074 "(use 'hg revert %s' to unadd the file)\n")
1074 "(use 'hg revert %s' to unadd the file)\n")
1075 % (f, f))
1075 % (f, f))
1076 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1076 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1077 self.ui.warn(_("%s not added: only files and symlinks "
1077 self.ui.warn(_("%s not added: only files and symlinks "
1078 "supported currently\n") % f)
1078 "supported currently\n") % f)
1079 rejected.append(p)
1079 rejected.append(p)
1080 elif self.dirstate[f] in 'amn':
1080 elif self.dirstate[f] in 'amn':
1081 self.ui.warn(_("%s already tracked!\n") % f)
1081 self.ui.warn(_("%s already tracked!\n") % f)
1082 elif self.dirstate[f] == 'r':
1082 elif self.dirstate[f] == 'r':
1083 self.dirstate.normallookup(f)
1083 self.dirstate.normallookup(f)
1084 else:
1084 else:
1085 self.dirstate.add(f)
1085 self.dirstate.add(f)
1086 return rejected
1086 return rejected
1087 finally:
1087 finally:
1088 wlock.release()
1088 wlock.release()
1089
1089
1090 def forget(self, list):
1090 def forget(self, list):
1091 wlock = self.wlock()
1091 wlock = self.wlock()
1092 try:
1092 try:
1093 for f in list:
1093 for f in list:
1094 if self.dirstate[f] != 'a':
1094 if self.dirstate[f] != 'a':
1095 self.ui.warn(_("%s not added!\n") % f)
1095 self.ui.warn(_("%s not added!\n") % f)
1096 else:
1096 else:
1097 self.dirstate.forget(f)
1097 self.dirstate.forget(f)
1098 finally:
1098 finally:
1099 wlock.release()
1099 wlock.release()
1100
1100
1101 def remove(self, list, unlink=False):
1101 def remove(self, list, unlink=False):
1102 if unlink:
1102 if unlink:
1103 for f in list:
1103 for f in list:
1104 try:
1104 try:
1105 util.unlink(self.wjoin(f))
1105 util.unlink(self.wjoin(f))
1106 except OSError, inst:
1106 except OSError, inst:
1107 if inst.errno != errno.ENOENT:
1107 if inst.errno != errno.ENOENT:
1108 raise
1108 raise
1109 wlock = self.wlock()
1109 wlock = self.wlock()
1110 try:
1110 try:
1111 for f in list:
1111 for f in list:
1112 if unlink and os.path.exists(self.wjoin(f)):
1112 if unlink and os.path.exists(self.wjoin(f)):
1113 self.ui.warn(_("%s still exists!\n") % f)
1113 self.ui.warn(_("%s still exists!\n") % f)
1114 elif self.dirstate[f] == 'a':
1114 elif self.dirstate[f] == 'a':
1115 self.dirstate.forget(f)
1115 self.dirstate.forget(f)
1116 elif f not in self.dirstate:
1116 elif f not in self.dirstate:
1117 self.ui.warn(_("%s not tracked!\n") % f)
1117 self.ui.warn(_("%s not tracked!\n") % f)
1118 else:
1118 else:
1119 self.dirstate.remove(f)
1119 self.dirstate.remove(f)
1120 finally:
1120 finally:
1121 wlock.release()
1121 wlock.release()
1122
1122
1123 def undelete(self, list):
1123 def undelete(self, list):
1124 manifests = [self.manifest.read(self.changelog.read(p)[0])
1124 manifests = [self.manifest.read(self.changelog.read(p)[0])
1125 for p in self.dirstate.parents() if p != nullid]
1125 for p in self.dirstate.parents() if p != nullid]
1126 wlock = self.wlock()
1126 wlock = self.wlock()
1127 try:
1127 try:
1128 for f in list:
1128 for f in list:
1129 if self.dirstate[f] != 'r':
1129 if self.dirstate[f] != 'r':
1130 self.ui.warn(_("%s not removed!\n") % f)
1130 self.ui.warn(_("%s not removed!\n") % f)
1131 else:
1131 else:
1132 m = f in manifests[0] and manifests[0] or manifests[1]
1132 m = f in manifests[0] and manifests[0] or manifests[1]
1133 t = self.file(f).read(m[f])
1133 t = self.file(f).read(m[f])
1134 self.wwrite(f, t, m.flags(f))
1134 self.wwrite(f, t, m.flags(f))
1135 self.dirstate.normal(f)
1135 self.dirstate.normal(f)
1136 finally:
1136 finally:
1137 wlock.release()
1137 wlock.release()
1138
1138
1139 def copy(self, source, dest):
1139 def copy(self, source, dest):
1140 p = self.wjoin(dest)
1140 p = self.wjoin(dest)
1141 if not (os.path.exists(p) or os.path.islink(p)):
1141 if not (os.path.exists(p) or os.path.islink(p)):
1142 self.ui.warn(_("%s does not exist!\n") % dest)
1142 self.ui.warn(_("%s does not exist!\n") % dest)
1143 elif not (os.path.isfile(p) or os.path.islink(p)):
1143 elif not (os.path.isfile(p) or os.path.islink(p)):
1144 self.ui.warn(_("copy failed: %s is not a file or a "
1144 self.ui.warn(_("copy failed: %s is not a file or a "
1145 "symbolic link\n") % dest)
1145 "symbolic link\n") % dest)
1146 else:
1146 else:
1147 wlock = self.wlock()
1147 wlock = self.wlock()
1148 try:
1148 try:
1149 if self.dirstate[dest] in '?r':
1149 if self.dirstate[dest] in '?r':
1150 self.dirstate.add(dest)
1150 self.dirstate.add(dest)
1151 self.dirstate.copy(source, dest)
1151 self.dirstate.copy(source, dest)
1152 finally:
1152 finally:
1153 wlock.release()
1153 wlock.release()
1154
1154
1155 def heads(self, start=None):
1155 def heads(self, start=None):
1156 heads = self.changelog.heads(start)
1156 heads = self.changelog.heads(start)
1157 # sort the output in rev descending order
1157 # sort the output in rev descending order
1158 heads = [(-self.changelog.rev(h), h) for h in heads]
1158 heads = [(-self.changelog.rev(h), h) for h in heads]
1159 return [n for (r, n) in sorted(heads)]
1159 return [n for (r, n) in sorted(heads)]
1160
1160
1161 def branchheads(self, branch=None, start=None, closed=False):
1161 def branchheads(self, branch=None, start=None, closed=False):
1162 if branch is None:
1162 if branch is None:
1163 branch = self[None].branch()
1163 branch = self[None].branch()
1164 branches = self.branchmap()
1164 branches = self.branchmap()
1165 if branch not in branches:
1165 if branch not in branches:
1166 return []
1166 return []
1167 bheads = branches[branch]
1167 bheads = branches[branch]
1168 # the cache returns heads ordered lowest to highest
1168 # the cache returns heads ordered lowest to highest
1169 bheads.reverse()
1169 bheads.reverse()
1170 if start is not None:
1170 if start is not None:
1171 # filter out the heads that cannot be reached from startrev
1171 # filter out the heads that cannot be reached from startrev
1172 bheads = self.changelog.nodesbetween([start], bheads)[2]
1172 bheads = self.changelog.nodesbetween([start], bheads)[2]
1173 if not closed:
1173 if not closed:
1174 bheads = [h for h in bheads if
1174 bheads = [h for h in bheads if
1175 ('close' not in self.changelog.read(h)[5])]
1175 ('close' not in self.changelog.read(h)[5])]
1176 return bheads
1176 return bheads
1177
1177
1178 def branches(self, nodes):
1178 def branches(self, nodes):
1179 if not nodes:
1179 if not nodes:
1180 nodes = [self.changelog.tip()]
1180 nodes = [self.changelog.tip()]
1181 b = []
1181 b = []
1182 for n in nodes:
1182 for n in nodes:
1183 t = n
1183 t = n
1184 while 1:
1184 while 1:
1185 p = self.changelog.parents(n)
1185 p = self.changelog.parents(n)
1186 if p[1] != nullid or p[0] == nullid:
1186 if p[1] != nullid or p[0] == nullid:
1187 b.append((t, n, p[0], p[1]))
1187 b.append((t, n, p[0], p[1]))
1188 break
1188 break
1189 n = p[0]
1189 n = p[0]
1190 return b
1190 return b
1191
1191
1192 def between(self, pairs):
1192 def between(self, pairs):
1193 r = []
1193 r = []
1194
1194
1195 for top, bottom in pairs:
1195 for top, bottom in pairs:
1196 n, l, i = top, [], 0
1196 n, l, i = top, [], 0
1197 f = 1
1197 f = 1
1198
1198
1199 while n != bottom and n != nullid:
1199 while n != bottom and n != nullid:
1200 p = self.changelog.parents(n)[0]
1200 p = self.changelog.parents(n)[0]
1201 if i == f:
1201 if i == f:
1202 l.append(n)
1202 l.append(n)
1203 f = f * 2
1203 f = f * 2
1204 n = p
1204 n = p
1205 i += 1
1205 i += 1
1206
1206
1207 r.append(l)
1207 r.append(l)
1208
1208
1209 return r
1209 return r
1210
1210
1211 def findincoming(self, remote, base=None, heads=None, force=False):
1211 def findincoming(self, remote, base=None, heads=None, force=False):
1212 """Return list of roots of the subsets of missing nodes from remote
1212 """Return list of roots of the subsets of missing nodes from remote
1213
1213
1214 If base dict is specified, assume that these nodes and their parents
1214 If base dict is specified, assume that these nodes and their parents
1215 exist on the remote side and that no child of a node of base exists
1215 exist on the remote side and that no child of a node of base exists
1216 in both remote and self.
1216 in both remote and self.
1217 Furthermore base will be updated to include the nodes that exists
1217 Furthermore base will be updated to include the nodes that exists
1218 in self and remote but no children exists in self and remote.
1218 in self and remote but no children exists in self and remote.
1219 If a list of heads is specified, return only nodes which are heads
1219 If a list of heads is specified, return only nodes which are heads
1220 or ancestors of these heads.
1220 or ancestors of these heads.
1221
1221
1222 All the ancestors of base are in self and in remote.
1222 All the ancestors of base are in self and in remote.
1223 All the descendants of the list returned are missing in self.
1223 All the descendants of the list returned are missing in self.
1224 (and so we know that the rest of the nodes are missing in remote, see
1224 (and so we know that the rest of the nodes are missing in remote, see
1225 outgoing)
1225 outgoing)
1226 """
1226 """
1227 return self.findcommonincoming(remote, base, heads, force)[1]
1227 return self.findcommonincoming(remote, base, heads, force)[1]
1228
1228
1229 def findcommonincoming(self, remote, base=None, heads=None, force=False):
1229 def findcommonincoming(self, remote, base=None, heads=None, force=False):
1230 """Return a tuple (common, missing roots, heads) used to identify
1230 """Return a tuple (common, missing roots, heads) used to identify
1231 missing nodes from remote.
1231 missing nodes from remote.
1232
1232
1233 If base dict is specified, assume that these nodes and their parents
1233 If base dict is specified, assume that these nodes and their parents
1234 exist on the remote side and that no child of a node of base exists
1234 exist on the remote side and that no child of a node of base exists
1235 in both remote and self.
1235 in both remote and self.
1236 Furthermore base will be updated to include the nodes that exists
1236 Furthermore base will be updated to include the nodes that exists
1237 in self and remote but no children exists in self and remote.
1237 in self and remote but no children exists in self and remote.
1238 If a list of heads is specified, return only nodes which are heads
1238 If a list of heads is specified, return only nodes which are heads
1239 or ancestors of these heads.
1239 or ancestors of these heads.
1240
1240
1241 All the ancestors of base are in self and in remote.
1241 All the ancestors of base are in self and in remote.
1242 """
1242 """
1243 m = self.changelog.nodemap
1243 m = self.changelog.nodemap
1244 search = []
1244 search = []
1245 fetch = set()
1245 fetch = set()
1246 seen = set()
1246 seen = set()
1247 seenbranch = set()
1247 seenbranch = set()
1248 if base is None:
1248 if base is None:
1249 base = {}
1249 base = {}
1250
1250
1251 if not heads:
1251 if not heads:
1252 heads = remote.heads()
1252 heads = remote.heads()
1253
1253
1254 if self.changelog.tip() == nullid:
1254 if self.changelog.tip() == nullid:
1255 base[nullid] = 1
1255 base[nullid] = 1
1256 if heads != [nullid]:
1256 if heads != [nullid]:
1257 return [nullid], [nullid], list(heads)
1257 return [nullid], [nullid], list(heads)
1258 return [nullid], [], []
1258 return [nullid], [], []
1259
1259
1260 # assume we're closer to the tip than the root
1260 # assume we're closer to the tip than the root
1261 # and start by examining the heads
1261 # and start by examining the heads
1262 self.ui.status(_("searching for changes\n"))
1262 self.ui.status(_("searching for changes\n"))
1263
1263
1264 unknown = []
1264 unknown = []
1265 for h in heads:
1265 for h in heads:
1266 if h not in m:
1266 if h not in m:
1267 unknown.append(h)
1267 unknown.append(h)
1268 else:
1268 else:
1269 base[h] = 1
1269 base[h] = 1
1270
1270
1271 heads = unknown
1271 heads = unknown
1272 if not unknown:
1272 if not unknown:
1273 return base.keys(), [], []
1273 return base.keys(), [], []
1274
1274
1275 req = set(unknown)
1275 req = set(unknown)
1276 reqcnt = 0
1276 reqcnt = 0
1277
1277
1278 # search through remote branches
1278 # search through remote branches
1279 # a 'branch' here is a linear segment of history, with four parts:
1279 # a 'branch' here is a linear segment of history, with four parts:
1280 # head, root, first parent, second parent
1280 # head, root, first parent, second parent
1281 # (a branch always has two parents (or none) by definition)
1281 # (a branch always has two parents (or none) by definition)
1282 unknown = remote.branches(unknown)
1282 unknown = remote.branches(unknown)
1283 while unknown:
1283 while unknown:
1284 r = []
1284 r = []
1285 while unknown:
1285 while unknown:
1286 n = unknown.pop(0)
1286 n = unknown.pop(0)
1287 if n[0] in seen:
1287 if n[0] in seen:
1288 continue
1288 continue
1289
1289
1290 self.ui.debug(_("examining %s:%s\n")
1290 self.ui.debug(_("examining %s:%s\n")
1291 % (short(n[0]), short(n[1])))
1291 % (short(n[0]), short(n[1])))
1292 if n[0] == nullid: # found the end of the branch
1292 if n[0] == nullid: # found the end of the branch
1293 pass
1293 pass
1294 elif n in seenbranch:
1294 elif n in seenbranch:
1295 self.ui.debug(_("branch already found\n"))
1295 self.ui.debug(_("branch already found\n"))
1296 continue
1296 continue
1297 elif n[1] and n[1] in m: # do we know the base?
1297 elif n[1] and n[1] in m: # do we know the base?
1298 self.ui.debug(_("found incomplete branch %s:%s\n")
1298 self.ui.debug(_("found incomplete branch %s:%s\n")
1299 % (short(n[0]), short(n[1])))
1299 % (short(n[0]), short(n[1])))
1300 search.append(n[0:2]) # schedule branch range for scanning
1300 search.append(n[0:2]) # schedule branch range for scanning
1301 seenbranch.add(n)
1301 seenbranch.add(n)
1302 else:
1302 else:
1303 if n[1] not in seen and n[1] not in fetch:
1303 if n[1] not in seen and n[1] not in fetch:
1304 if n[2] in m and n[3] in m:
1304 if n[2] in m and n[3] in m:
1305 self.ui.debug(_("found new changeset %s\n") %
1305 self.ui.debug(_("found new changeset %s\n") %
1306 short(n[1]))
1306 short(n[1]))
1307 fetch.add(n[1]) # earliest unknown
1307 fetch.add(n[1]) # earliest unknown
1308 for p in n[2:4]:
1308 for p in n[2:4]:
1309 if p in m:
1309 if p in m:
1310 base[p] = 1 # latest known
1310 base[p] = 1 # latest known
1311
1311
1312 for p in n[2:4]:
1312 for p in n[2:4]:
1313 if p not in req and p not in m:
1313 if p not in req and p not in m:
1314 r.append(p)
1314 r.append(p)
1315 req.add(p)
1315 req.add(p)
1316 seen.add(n[0])
1316 seen.add(n[0])
1317
1317
1318 if r:
1318 if r:
1319 reqcnt += 1
1319 reqcnt += 1
1320 self.ui.debug(_("request %d: %s\n") %
1320 self.ui.debug(_("request %d: %s\n") %
1321 (reqcnt, " ".join(map(short, r))))
1321 (reqcnt, " ".join(map(short, r))))
1322 for p in xrange(0, len(r), 10):
1322 for p in xrange(0, len(r), 10):
1323 for b in remote.branches(r[p:p+10]):
1323 for b in remote.branches(r[p:p+10]):
1324 self.ui.debug(_("received %s:%s\n") %
1324 self.ui.debug(_("received %s:%s\n") %
1325 (short(b[0]), short(b[1])))
1325 (short(b[0]), short(b[1])))
1326 unknown.append(b)
1326 unknown.append(b)
1327
1327
1328 # do binary search on the branches we found
1328 # do binary search on the branches we found
1329 while search:
1329 while search:
1330 newsearch = []
1330 newsearch = []
1331 reqcnt += 1
1331 reqcnt += 1
1332 for n, l in zip(search, remote.between(search)):
1332 for n, l in zip(search, remote.between(search)):
1333 l.append(n[1])
1333 l.append(n[1])
1334 p = n[0]
1334 p = n[0]
1335 f = 1
1335 f = 1
1336 for i in l:
1336 for i in l:
1337 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
1337 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
1338 if i in m:
1338 if i in m:
1339 if f <= 2:
1339 if f <= 2:
1340 self.ui.debug(_("found new branch changeset %s\n") %
1340 self.ui.debug(_("found new branch changeset %s\n") %
1341 short(p))
1341 short(p))
1342 fetch.add(p)
1342 fetch.add(p)
1343 base[i] = 1
1343 base[i] = 1
1344 else:
1344 else:
1345 self.ui.debug(_("narrowed branch search to %s:%s\n")
1345 self.ui.debug(_("narrowed branch search to %s:%s\n")
1346 % (short(p), short(i)))
1346 % (short(p), short(i)))
1347 newsearch.append((p, i))
1347 newsearch.append((p, i))
1348 break
1348 break
1349 p, f = i, f * 2
1349 p, f = i, f * 2
1350 search = newsearch
1350 search = newsearch
1351
1351
1352 # sanity check our fetch list
1352 # sanity check our fetch list
1353 for f in fetch:
1353 for f in fetch:
1354 if f in m:
1354 if f in m:
1355 raise error.RepoError(_("already have changeset ")
1355 raise error.RepoError(_("already have changeset ")
1356 + short(f[:4]))
1356 + short(f[:4]))
1357
1357
1358 if base.keys() == [nullid]:
1358 if base.keys() == [nullid]:
1359 if force:
1359 if force:
1360 self.ui.warn(_("warning: repository is unrelated\n"))
1360 self.ui.warn(_("warning: repository is unrelated\n"))
1361 else:
1361 else:
1362 raise util.Abort(_("repository is unrelated"))
1362 raise util.Abort(_("repository is unrelated"))
1363
1363
1364 self.ui.debug(_("found new changesets starting at ") +
1364 self.ui.debug(_("found new changesets starting at ") +
1365 " ".join([short(f) for f in fetch]) + "\n")
1365 " ".join([short(f) for f in fetch]) + "\n")
1366
1366
1367 self.ui.debug(_("%d total queries\n") % reqcnt)
1367 self.ui.debug(_("%d total queries\n") % reqcnt)
1368
1368
1369 return base.keys(), list(fetch), heads
1369 return base.keys(), list(fetch), heads
1370
1370
1371 def findoutgoing(self, remote, base=None, heads=None, force=False):
1371 def findoutgoing(self, remote, base=None, heads=None, force=False):
1372 """Return list of nodes that are roots of subsets not in remote
1372 """Return list of nodes that are roots of subsets not in remote
1373
1373
1374 If base dict is specified, assume that these nodes and their parents
1374 If base dict is specified, assume that these nodes and their parents
1375 exist on the remote side.
1375 exist on the remote side.
1376 If a list of heads is specified, return only nodes which are heads
1376 If a list of heads is specified, return only nodes which are heads
1377 or ancestors of these heads, and return a second element which
1377 or ancestors of these heads, and return a second element which
1378 contains all remote heads which get new children.
1378 contains all remote heads which get new children.
1379 """
1379 """
1380 if base is None:
1380 if base is None:
1381 base = {}
1381 base = {}
1382 self.findincoming(remote, base, heads, force=force)
1382 self.findincoming(remote, base, heads, force=force)
1383
1383
1384 self.ui.debug(_("common changesets up to ")
1384 self.ui.debug(_("common changesets up to ")
1385 + " ".join(map(short, base.keys())) + "\n")
1385 + " ".join(map(short, base.keys())) + "\n")
1386
1386
1387 remain = set(self.changelog.nodemap)
1387 remain = set(self.changelog.nodemap)
1388
1388
1389 # prune everything remote has from the tree
1389 # prune everything remote has from the tree
1390 remain.remove(nullid)
1390 remain.remove(nullid)
1391 remove = base.keys()
1391 remove = base.keys()
1392 while remove:
1392 while remove:
1393 n = remove.pop(0)
1393 n = remove.pop(0)
1394 if n in remain:
1394 if n in remain:
1395 remain.remove(n)
1395 remain.remove(n)
1396 for p in self.changelog.parents(n):
1396 for p in self.changelog.parents(n):
1397 remove.append(p)
1397 remove.append(p)
1398
1398
1399 # find every node whose parents have been pruned
1399 # find every node whose parents have been pruned
1400 subset = []
1400 subset = []
1401 # find every remote head that will get new children
1401 # find every remote head that will get new children
1402 updated_heads = set()
1402 updated_heads = set()
1403 for n in remain:
1403 for n in remain:
1404 p1, p2 = self.changelog.parents(n)
1404 p1, p2 = self.changelog.parents(n)
1405 if p1 not in remain and p2 not in remain:
1405 if p1 not in remain and p2 not in remain:
1406 subset.append(n)
1406 subset.append(n)
1407 if heads:
1407 if heads:
1408 if p1 in heads:
1408 if p1 in heads:
1409 updated_heads.add(p1)
1409 updated_heads.add(p1)
1410 if p2 in heads:
1410 if p2 in heads:
1411 updated_heads.add(p2)
1411 updated_heads.add(p2)
1412
1412
1413 # this is the set of all roots we have to push
1413 # this is the set of all roots we have to push
1414 if heads:
1414 if heads:
1415 return subset, list(updated_heads)
1415 return subset, list(updated_heads)
1416 else:
1416 else:
1417 return subset
1417 return subset
1418
1418
1419 def pull(self, remote, heads=None, force=False):
1419 def pull(self, remote, heads=None, force=False):
1420 lock = self.lock()
1420 lock = self.lock()
1421 try:
1421 try:
1422 common, fetch, rheads = self.findcommonincoming(remote, heads=heads,
1422 common, fetch, rheads = self.findcommonincoming(remote, heads=heads,
1423 force=force)
1423 force=force)
1424 if fetch == [nullid]:
1424 if fetch == [nullid]:
1425 self.ui.status(_("requesting all changes\n"))
1425 self.ui.status(_("requesting all changes\n"))
1426
1426
1427 if not fetch:
1427 if not fetch:
1428 self.ui.status(_("no changes found\n"))
1428 self.ui.status(_("no changes found\n"))
1429 return 0
1429 return 0
1430
1430
1431 if heads is None and remote.capable('changegroupsubset'):
1431 if heads is None and remote.capable('changegroupsubset'):
1432 heads = rheads
1432 heads = rheads
1433
1433
1434 if heads is None:
1434 if heads is None:
1435 cg = remote.changegroup(fetch, 'pull')
1435 cg = remote.changegroup(fetch, 'pull')
1436 else:
1436 else:
1437 if not remote.capable('changegroupsubset'):
1437 if not remote.capable('changegroupsubset'):
1438 raise util.Abort(_("Partial pull cannot be done because "
1438 raise util.Abort(_("Partial pull cannot be done because "
1439 "other repository doesn't support "
1439 "other repository doesn't support "
1440 "changegroupsubset."))
1440 "changegroupsubset."))
1441 cg = remote.changegroupsubset(fetch, heads, 'pull')
1441 cg = remote.changegroupsubset(fetch, heads, 'pull')
1442 return self.addchangegroup(cg, 'pull', remote.url())
1442 return self.addchangegroup(cg, 'pull', remote.url())
1443 finally:
1443 finally:
1444 lock.release()
1444 lock.release()
1445
1445
1446 def push(self, remote, force=False, revs=None):
1446 def push(self, remote, force=False, revs=None):
1447 # there are two ways to push to remote repo:
1447 # there are two ways to push to remote repo:
1448 #
1448 #
1449 # addchangegroup assumes local user can lock remote
1449 # addchangegroup assumes local user can lock remote
1450 # repo (local filesystem, old ssh servers).
1450 # repo (local filesystem, old ssh servers).
1451 #
1451 #
1452 # unbundle assumes local user cannot lock remote repo (new ssh
1452 # unbundle assumes local user cannot lock remote repo (new ssh
1453 # servers, http servers).
1453 # servers, http servers).
1454
1454
1455 if remote.capable('unbundle'):
1455 if remote.capable('unbundle'):
1456 return self.push_unbundle(remote, force, revs)
1456 return self.push_unbundle(remote, force, revs)
1457 return self.push_addchangegroup(remote, force, revs)
1457 return self.push_addchangegroup(remote, force, revs)
1458
1458
1459 def prepush(self, remote, force, revs):
1459 def prepush(self, remote, force, revs):
1460 '''Analyze the local and remote repositories and determine which
1461 changesets need to be pushed to the remote. Return a tuple
1462 (changegroup, remoteheads). changegroup is a readable file-like
1463 object whose read() returns successive changegroup chunks ready to
1464 be sent over the wire. remoteheads is the list of remote heads.
1465 '''
1460 common = {}
1466 common = {}
1461 remote_heads = remote.heads()
1467 remote_heads = remote.heads()
1462 inc = self.findincoming(remote, common, remote_heads, force=force)
1468 inc = self.findincoming(remote, common, remote_heads, force=force)
1463
1469
1464 update, updated_heads = self.findoutgoing(remote, common, remote_heads)
1470 update, updated_heads = self.findoutgoing(remote, common, remote_heads)
1465 if revs is not None:
1471 if revs is not None:
1466 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
1472 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
1467 else:
1473 else:
1468 bases, heads = update, self.changelog.heads()
1474 bases, heads = update, self.changelog.heads()
1469
1475
1470 def checkbranch(lheads, rheads, updatelh):
1476 def checkbranch(lheads, rheads, updatelh):
1471 '''
1477 '''
1472 check whether there are more local heads than remote heads on
1478 check whether there are more local heads than remote heads on
1473 a specific branch.
1479 a specific branch.
1474
1480
1475 lheads: local branch heads
1481 lheads: local branch heads
1476 rheads: remote branch heads
1482 rheads: remote branch heads
1477 updatelh: outgoing local branch heads
1483 updatelh: outgoing local branch heads
1478 '''
1484 '''
1479
1485
1480 warn = 0
1486 warn = 0
1481
1487
1482 if not revs and len(lheads) > len(rheads):
1488 if not revs and len(lheads) > len(rheads):
1483 warn = 1
1489 warn = 1
1484 else:
1490 else:
1485 updatelheads = [self.changelog.heads(x, lheads)
1491 updatelheads = [self.changelog.heads(x, lheads)
1486 for x in updatelh]
1492 for x in updatelh]
1487 newheads = set(sum(updatelheads, [])) & set(lheads)
1493 newheads = set(sum(updatelheads, [])) & set(lheads)
1488
1494
1489 if not newheads:
1495 if not newheads:
1490 return True
1496 return True
1491
1497
1492 for r in rheads:
1498 for r in rheads:
1493 if r in self.changelog.nodemap:
1499 if r in self.changelog.nodemap:
1494 desc = self.changelog.heads(r, heads)
1500 desc = self.changelog.heads(r, heads)
1495 l = [h for h in heads if h in desc]
1501 l = [h for h in heads if h in desc]
1496 if not l:
1502 if not l:
1497 newheads.add(r)
1503 newheads.add(r)
1498 else:
1504 else:
1499 newheads.add(r)
1505 newheads.add(r)
1500 if len(newheads) > len(rheads):
1506 if len(newheads) > len(rheads):
1501 warn = 1
1507 warn = 1
1502
1508
1503 if warn:
1509 if warn:
1504 if not rheads: # new branch requires --force
1510 if not rheads: # new branch requires --force
1505 self.ui.warn(_("abort: push creates new"
1511 self.ui.warn(_("abort: push creates new"
1506 " remote branch '%s'!\n") %
1512 " remote branch '%s'!\n") %
1507 self[updatelh[0]].branch())
1513 self[updatelh[0]].branch())
1508 else:
1514 else:
1509 self.ui.warn(_("abort: push creates new remote heads!\n"))
1515 self.ui.warn(_("abort: push creates new remote heads!\n"))
1510
1516
1511 self.ui.status(_("(did you forget to merge?"
1517 self.ui.status(_("(did you forget to merge?"
1512 " use push -f to force)\n"))
1518 " use push -f to force)\n"))
1513 return False
1519 return False
1514 return True
1520 return True
1515
1521
1516 if not bases:
1522 if not bases:
1517 self.ui.status(_("no changes found\n"))
1523 self.ui.status(_("no changes found\n"))
1518 return None, 1
1524 return None, 1
1519 elif not force:
1525 elif not force:
1520 # Check for each named branch if we're creating new remote heads.
1526 # Check for each named branch if we're creating new remote heads.
1521 # To be a remote head after push, node must be either:
1527 # To be a remote head after push, node must be either:
1522 # - unknown locally
1528 # - unknown locally
1523 # - a local outgoing head descended from update
1529 # - a local outgoing head descended from update
1524 # - a remote head that's known locally and not
1530 # - a remote head that's known locally and not
1525 # ancestral to an outgoing head
1531 # ancestral to an outgoing head
1526 #
1532 #
1527 # New named branches cannot be created without --force.
1533 # New named branches cannot be created without --force.
1528
1534
1529 if remote_heads != [nullid]:
1535 if remote_heads != [nullid]:
1530 if remote.capable('branchmap'):
1536 if remote.capable('branchmap'):
1531 localhds = {}
1537 localhds = {}
1532 if not revs:
1538 if not revs:
1533 localhds = self.branchmap()
1539 localhds = self.branchmap()
1534 else:
1540 else:
1535 for n in heads:
1541 for n in heads:
1536 branch = self[n].branch()
1542 branch = self[n].branch()
1537 if branch in localhds:
1543 if branch in localhds:
1538 localhds[branch].append(n)
1544 localhds[branch].append(n)
1539 else:
1545 else:
1540 localhds[branch] = [n]
1546 localhds[branch] = [n]
1541
1547
1542 remotehds = remote.branchmap()
1548 remotehds = remote.branchmap()
1543
1549
1544 for lh in localhds:
1550 for lh in localhds:
1545 if lh in remotehds:
1551 if lh in remotehds:
1546 rheads = remotehds[lh]
1552 rheads = remotehds[lh]
1547 else:
1553 else:
1548 rheads = []
1554 rheads = []
1549 lheads = localhds[lh]
1555 lheads = localhds[lh]
1550 updatelh = [upd for upd in update
1556 updatelh = [upd for upd in update
1551 if self[upd].branch() == lh]
1557 if self[upd].branch() == lh]
1552 if not updatelh:
1558 if not updatelh:
1553 continue
1559 continue
1554 if not checkbranch(lheads, rheads, updatelh):
1560 if not checkbranch(lheads, rheads, updatelh):
1555 return None, 0
1561 return None, 0
1556 else:
1562 else:
1557 if not checkbranch(heads, remote_heads, update):
1563 if not checkbranch(heads, remote_heads, update):
1558 return None, 0
1564 return None, 0
1559
1565
1560 if inc:
1566 if inc:
1561 self.ui.warn(_("note: unsynced remote changes!\n"))
1567 self.ui.warn(_("note: unsynced remote changes!\n"))
1562
1568
1563
1569
1564 if revs is None:
1570 if revs is None:
1565 # use the fast path, no race possible on push
1571 # use the fast path, no race possible on push
1566 cg = self._changegroup(common.keys(), 'push')
1572 cg = self._changegroup(common.keys(), 'push')
1567 else:
1573 else:
1568 cg = self.changegroupsubset(update, revs, 'push')
1574 cg = self.changegroupsubset(update, revs, 'push')
1569 return cg, remote_heads
1575 return cg, remote_heads
1570
1576
1571 def push_addchangegroup(self, remote, force, revs):
1577 def push_addchangegroup(self, remote, force, revs):
1572 lock = remote.lock()
1578 lock = remote.lock()
1573 try:
1579 try:
1574 ret = self.prepush(remote, force, revs)
1580 ret = self.prepush(remote, force, revs)
1575 if ret[0] is not None:
1581 if ret[0] is not None:
1576 cg, remote_heads = ret
1582 cg, remote_heads = ret
1577 return remote.addchangegroup(cg, 'push', self.url())
1583 return remote.addchangegroup(cg, 'push', self.url())
1578 return ret[1]
1584 return ret[1]
1579 finally:
1585 finally:
1580 lock.release()
1586 lock.release()
1581
1587
1582 def push_unbundle(self, remote, force, revs):
1588 def push_unbundle(self, remote, force, revs):
1583 # local repo finds heads on server, finds out what revs it
1589 # local repo finds heads on server, finds out what revs it
1584 # must push. once revs transferred, if server finds it has
1590 # must push. once revs transferred, if server finds it has
1585 # different heads (someone else won commit/push race), server
1591 # different heads (someone else won commit/push race), server
1586 # aborts.
1592 # aborts.
1587
1593
1588 ret = self.prepush(remote, force, revs)
1594 ret = self.prepush(remote, force, revs)
1589 if ret[0] is not None:
1595 if ret[0] is not None:
1590 cg, remote_heads = ret
1596 cg, remote_heads = ret
1591 if force: remote_heads = ['force']
1597 if force: remote_heads = ['force']
1592 return remote.unbundle(cg, remote_heads, 'push')
1598 return remote.unbundle(cg, remote_heads, 'push')
1593 return ret[1]
1599 return ret[1]
1594
1600
1595 def changegroupinfo(self, nodes, source):
1601 def changegroupinfo(self, nodes, source):
1596 if self.ui.verbose or source == 'bundle':
1602 if self.ui.verbose or source == 'bundle':
1597 self.ui.status(_("%d changesets found\n") % len(nodes))
1603 self.ui.status(_("%d changesets found\n") % len(nodes))
1598 if self.ui.debugflag:
1604 if self.ui.debugflag:
1599 self.ui.debug(_("list of changesets:\n"))
1605 self.ui.debug(_("list of changesets:\n"))
1600 for node in nodes:
1606 for node in nodes:
1601 self.ui.debug("%s\n" % hex(node))
1607 self.ui.debug("%s\n" % hex(node))
1602
1608
1603 def changegroupsubset(self, bases, heads, source, extranodes=None):
1609 def changegroupsubset(self, bases, heads, source, extranodes=None):
1604 """This function generates a changegroup consisting of all the nodes
1610 """Compute a changegroup consisting of all the nodes that are
1605 that are descendents of any of the bases, and ancestors of any of
1611 descendents of any of the bases and ancestors of any of the heads.
1606 the heads.
1612 Return a chunkbuffer object whose read() method will return
1613 successive changegroup chunks.
1607
1614
1608 It is fairly complex as determining which filenodes and which
1615 It is fairly complex as determining which filenodes and which
1609 manifest nodes need to be included for the changeset to be complete
1616 manifest nodes need to be included for the changeset to be complete
1610 is non-trivial.
1617 is non-trivial.
1611
1618
1612 Another wrinkle is doing the reverse, figuring out which changeset in
1619 Another wrinkle is doing the reverse, figuring out which changeset in
1613 the changegroup a particular filenode or manifestnode belongs to.
1620 the changegroup a particular filenode or manifestnode belongs to.
1614
1621
1615 The caller can specify some nodes that must be included in the
1622 The caller can specify some nodes that must be included in the
1616 changegroup using the extranodes argument. It should be a dict
1623 changegroup using the extranodes argument. It should be a dict
1617 where the keys are the filenames (or 1 for the manifest), and the
1624 where the keys are the filenames (or 1 for the manifest), and the
1618 values are lists of (node, linknode) tuples, where node is a wanted
1625 values are lists of (node, linknode) tuples, where node is a wanted
1619 node and linknode is the changelog node that should be transmitted as
1626 node and linknode is the changelog node that should be transmitted as
1620 the linkrev.
1627 the linkrev.
1621 """
1628 """
1622
1629
1623 if extranodes is None:
1630 if extranodes is None:
1624 # can we go through the fast path ?
1631 # can we go through the fast path ?
1625 heads.sort()
1632 heads.sort()
1626 allheads = self.heads()
1633 allheads = self.heads()
1627 allheads.sort()
1634 allheads.sort()
1628 if heads == allheads:
1635 if heads == allheads:
1629 common = []
1636 common = []
1630 # parents of bases are known from both sides
1637 # parents of bases are known from both sides
1631 for n in bases:
1638 for n in bases:
1632 for p in self.changelog.parents(n):
1639 for p in self.changelog.parents(n):
1633 if p != nullid:
1640 if p != nullid:
1634 common.append(p)
1641 common.append(p)
1635 return self._changegroup(common, source)
1642 return self._changegroup(common, source)
1636
1643
1637 self.hook('preoutgoing', throw=True, source=source)
1644 self.hook('preoutgoing', throw=True, source=source)
1638
1645
1639 # Set up some initial variables
1646 # Set up some initial variables
1640 # Make it easy to refer to self.changelog
1647 # Make it easy to refer to self.changelog
1641 cl = self.changelog
1648 cl = self.changelog
1642 # msng is short for missing - compute the list of changesets in this
1649 # msng is short for missing - compute the list of changesets in this
1643 # changegroup.
1650 # changegroup.
1644 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1651 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1645 self.changegroupinfo(msng_cl_lst, source)
1652 self.changegroupinfo(msng_cl_lst, source)
1646 # Some bases may turn out to be superfluous, and some heads may be
1653 # Some bases may turn out to be superfluous, and some heads may be
1647 # too. nodesbetween will return the minimal set of bases and heads
1654 # too. nodesbetween will return the minimal set of bases and heads
1648 # necessary to re-create the changegroup.
1655 # necessary to re-create the changegroup.
1649
1656
1650 # Known heads are the list of heads that it is assumed the recipient
1657 # Known heads are the list of heads that it is assumed the recipient
1651 # of this changegroup will know about.
1658 # of this changegroup will know about.
1652 knownheads = set()
1659 knownheads = set()
1653 # We assume that all parents of bases are known heads.
1660 # We assume that all parents of bases are known heads.
1654 for n in bases:
1661 for n in bases:
1655 knownheads.update(cl.parents(n))
1662 knownheads.update(cl.parents(n))
1656 knownheads.discard(nullid)
1663 knownheads.discard(nullid)
1657 knownheads = list(knownheads)
1664 knownheads = list(knownheads)
1658 if knownheads:
1665 if knownheads:
1659 # Now that we know what heads are known, we can compute which
1666 # Now that we know what heads are known, we can compute which
1660 # changesets are known. The recipient must know about all
1667 # changesets are known. The recipient must know about all
1661 # changesets required to reach the known heads from the null
1668 # changesets required to reach the known heads from the null
1662 # changeset.
1669 # changeset.
1663 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1670 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1664 junk = None
1671 junk = None
1665 # Transform the list into a set.
1672 # Transform the list into a set.
1666 has_cl_set = set(has_cl_set)
1673 has_cl_set = set(has_cl_set)
1667 else:
1674 else:
1668 # If there were no known heads, the recipient cannot be assumed to
1675 # If there were no known heads, the recipient cannot be assumed to
1669 # know about any changesets.
1676 # know about any changesets.
1670 has_cl_set = set()
1677 has_cl_set = set()
1671
1678
1672 # Make it easy to refer to self.manifest
1679 # Make it easy to refer to self.manifest
1673 mnfst = self.manifest
1680 mnfst = self.manifest
1674 # We don't know which manifests are missing yet
1681 # We don't know which manifests are missing yet
1675 msng_mnfst_set = {}
1682 msng_mnfst_set = {}
1676 # Nor do we know which filenodes are missing.
1683 # Nor do we know which filenodes are missing.
1677 msng_filenode_set = {}
1684 msng_filenode_set = {}
1678
1685
1679 junk = mnfst.index[len(mnfst) - 1] # Get around a bug in lazyindex
1686 junk = mnfst.index[len(mnfst) - 1] # Get around a bug in lazyindex
1680 junk = None
1687 junk = None
1681
1688
1682 # A changeset always belongs to itself, so the changenode lookup
1689 # A changeset always belongs to itself, so the changenode lookup
1683 # function for a changenode is identity.
1690 # function for a changenode is identity.
1684 def identity(x):
1691 def identity(x):
1685 return x
1692 return x
1686
1693
1687 # If we determine that a particular file or manifest node must be a
1694 # If we determine that a particular file or manifest node must be a
1688 # node that the recipient of the changegroup will already have, we can
1695 # node that the recipient of the changegroup will already have, we can
1689 # also assume the recipient will have all the parents. This function
1696 # also assume the recipient will have all the parents. This function
1690 # prunes them from the set of missing nodes.
1697 # prunes them from the set of missing nodes.
1691 def prune_parents(revlog, hasset, msngset):
1698 def prune_parents(revlog, hasset, msngset):
1692 haslst = list(hasset)
1699 haslst = list(hasset)
1693 haslst.sort(key=revlog.rev)
1700 haslst.sort(key=revlog.rev)
1694 for node in haslst:
1701 for node in haslst:
1695 parentlst = [p for p in revlog.parents(node) if p != nullid]
1702 parentlst = [p for p in revlog.parents(node) if p != nullid]
1696 while parentlst:
1703 while parentlst:
1697 n = parentlst.pop()
1704 n = parentlst.pop()
1698 if n not in hasset:
1705 if n not in hasset:
1699 hasset.add(n)
1706 hasset.add(n)
1700 p = [p for p in revlog.parents(n) if p != nullid]
1707 p = [p for p in revlog.parents(n) if p != nullid]
1701 parentlst.extend(p)
1708 parentlst.extend(p)
1702 for n in hasset:
1709 for n in hasset:
1703 msngset.pop(n, None)
1710 msngset.pop(n, None)
1704
1711
1705 # This is a function generating function used to set up an environment
1712 # This is a function generating function used to set up an environment
1706 # for the inner function to execute in.
1713 # for the inner function to execute in.
1707 def manifest_and_file_collector(changedfileset):
1714 def manifest_and_file_collector(changedfileset):
1708 # This is an information gathering function that gathers
1715 # This is an information gathering function that gathers
1709 # information from each changeset node that goes out as part of
1716 # information from each changeset node that goes out as part of
1710 # the changegroup. The information gathered is a list of which
1717 # the changegroup. The information gathered is a list of which
1711 # manifest nodes are potentially required (the recipient may
1718 # manifest nodes are potentially required (the recipient may
1712 # already have them) and total list of all files which were
1719 # already have them) and total list of all files which were
1713 # changed in any changeset in the changegroup.
1720 # changed in any changeset in the changegroup.
1714 #
1721 #
1715 # We also remember the first changenode we saw any manifest
1722 # We also remember the first changenode we saw any manifest
1716 # referenced by so we can later determine which changenode 'owns'
1723 # referenced by so we can later determine which changenode 'owns'
1717 # the manifest.
1724 # the manifest.
1718 def collect_manifests_and_files(clnode):
1725 def collect_manifests_and_files(clnode):
1719 c = cl.read(clnode)
1726 c = cl.read(clnode)
1720 for f in c[3]:
1727 for f in c[3]:
1721 # This is to make sure we only have one instance of each
1728 # This is to make sure we only have one instance of each
1722 # filename string for each filename.
1729 # filename string for each filename.
1723 changedfileset.setdefault(f, f)
1730 changedfileset.setdefault(f, f)
1724 msng_mnfst_set.setdefault(c[0], clnode)
1731 msng_mnfst_set.setdefault(c[0], clnode)
1725 return collect_manifests_and_files
1732 return collect_manifests_and_files
1726
1733
1727 # Figure out which manifest nodes (of the ones we think might be part
1734 # Figure out which manifest nodes (of the ones we think might be part
1728 # of the changegroup) the recipient must know about and remove them
1735 # of the changegroup) the recipient must know about and remove them
1729 # from the changegroup.
1736 # from the changegroup.
1730 def prune_manifests():
1737 def prune_manifests():
1731 has_mnfst_set = set()
1738 has_mnfst_set = set()
1732 for n in msng_mnfst_set:
1739 for n in msng_mnfst_set:
1733 # If a 'missing' manifest thinks it belongs to a changenode
1740 # If a 'missing' manifest thinks it belongs to a changenode
1734 # the recipient is assumed to have, obviously the recipient
1741 # the recipient is assumed to have, obviously the recipient
1735 # must have that manifest.
1742 # must have that manifest.
1736 linknode = cl.node(mnfst.linkrev(mnfst.rev(n)))
1743 linknode = cl.node(mnfst.linkrev(mnfst.rev(n)))
1737 if linknode in has_cl_set:
1744 if linknode in has_cl_set:
1738 has_mnfst_set.add(n)
1745 has_mnfst_set.add(n)
1739 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1746 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1740
1747
1741 # Use the information collected in collect_manifests_and_files to say
1748 # Use the information collected in collect_manifests_and_files to say
1742 # which changenode any manifestnode belongs to.
1749 # which changenode any manifestnode belongs to.
1743 def lookup_manifest_link(mnfstnode):
1750 def lookup_manifest_link(mnfstnode):
1744 return msng_mnfst_set[mnfstnode]
1751 return msng_mnfst_set[mnfstnode]
1745
1752
1746 # A function generating function that sets up the initial environment
1753 # A function generating function that sets up the initial environment
1747 # the inner function.
1754 # the inner function.
1748 def filenode_collector(changedfiles):
1755 def filenode_collector(changedfiles):
1749 next_rev = [0]
1756 next_rev = [0]
1750 # This gathers information from each manifestnode included in the
1757 # This gathers information from each manifestnode included in the
1751 # changegroup about which filenodes the manifest node references
1758 # changegroup about which filenodes the manifest node references
1752 # so we can include those in the changegroup too.
1759 # so we can include those in the changegroup too.
1753 #
1760 #
1754 # It also remembers which changenode each filenode belongs to. It
1761 # It also remembers which changenode each filenode belongs to. It
1755 # does this by assuming the a filenode belongs to the changenode
1762 # does this by assuming the a filenode belongs to the changenode
1756 # the first manifest that references it belongs to.
1763 # the first manifest that references it belongs to.
1757 def collect_msng_filenodes(mnfstnode):
1764 def collect_msng_filenodes(mnfstnode):
1758 r = mnfst.rev(mnfstnode)
1765 r = mnfst.rev(mnfstnode)
1759 if r == next_rev[0]:
1766 if r == next_rev[0]:
1760 # If the last rev we looked at was the one just previous,
1767 # If the last rev we looked at was the one just previous,
1761 # we only need to see a diff.
1768 # we only need to see a diff.
1762 deltamf = mnfst.readdelta(mnfstnode)
1769 deltamf = mnfst.readdelta(mnfstnode)
1763 # For each line in the delta
1770 # For each line in the delta
1764 for f, fnode in deltamf.iteritems():
1771 for f, fnode in deltamf.iteritems():
1765 f = changedfiles.get(f, None)
1772 f = changedfiles.get(f, None)
1766 # And if the file is in the list of files we care
1773 # And if the file is in the list of files we care
1767 # about.
1774 # about.
1768 if f is not None:
1775 if f is not None:
1769 # Get the changenode this manifest belongs to
1776 # Get the changenode this manifest belongs to
1770 clnode = msng_mnfst_set[mnfstnode]
1777 clnode = msng_mnfst_set[mnfstnode]
1771 # Create the set of filenodes for the file if
1778 # Create the set of filenodes for the file if
1772 # there isn't one already.
1779 # there isn't one already.
1773 ndset = msng_filenode_set.setdefault(f, {})
1780 ndset = msng_filenode_set.setdefault(f, {})
1774 # And set the filenode's changelog node to the
1781 # And set the filenode's changelog node to the
1775 # manifest's if it hasn't been set already.
1782 # manifest's if it hasn't been set already.
1776 ndset.setdefault(fnode, clnode)
1783 ndset.setdefault(fnode, clnode)
1777 else:
1784 else:
1778 # Otherwise we need a full manifest.
1785 # Otherwise we need a full manifest.
1779 m = mnfst.read(mnfstnode)
1786 m = mnfst.read(mnfstnode)
1780 # For every file in we care about.
1787 # For every file in we care about.
1781 for f in changedfiles:
1788 for f in changedfiles:
1782 fnode = m.get(f, None)
1789 fnode = m.get(f, None)
1783 # If it's in the manifest
1790 # If it's in the manifest
1784 if fnode is not None:
1791 if fnode is not None:
1785 # See comments above.
1792 # See comments above.
1786 clnode = msng_mnfst_set[mnfstnode]
1793 clnode = msng_mnfst_set[mnfstnode]
1787 ndset = msng_filenode_set.setdefault(f, {})
1794 ndset = msng_filenode_set.setdefault(f, {})
1788 ndset.setdefault(fnode, clnode)
1795 ndset.setdefault(fnode, clnode)
1789 # Remember the revision we hope to see next.
1796 # Remember the revision we hope to see next.
1790 next_rev[0] = r + 1
1797 next_rev[0] = r + 1
1791 return collect_msng_filenodes
1798 return collect_msng_filenodes
1792
1799
1793 # We have a list of filenodes we think we need for a file, lets remove
1800 # We have a list of filenodes we think we need for a file, lets remove
1794 # all those we know the recipient must have.
1801 # all those we know the recipient must have.
1795 def prune_filenodes(f, filerevlog):
1802 def prune_filenodes(f, filerevlog):
1796 msngset = msng_filenode_set[f]
1803 msngset = msng_filenode_set[f]
1797 hasset = set()
1804 hasset = set()
1798 # If a 'missing' filenode thinks it belongs to a changenode we
1805 # If a 'missing' filenode thinks it belongs to a changenode we
1799 # assume the recipient must have, then the recipient must have
1806 # assume the recipient must have, then the recipient must have
1800 # that filenode.
1807 # that filenode.
1801 for n in msngset:
1808 for n in msngset:
1802 clnode = cl.node(filerevlog.linkrev(filerevlog.rev(n)))
1809 clnode = cl.node(filerevlog.linkrev(filerevlog.rev(n)))
1803 if clnode in has_cl_set:
1810 if clnode in has_cl_set:
1804 hasset.add(n)
1811 hasset.add(n)
1805 prune_parents(filerevlog, hasset, msngset)
1812 prune_parents(filerevlog, hasset, msngset)
1806
1813
1807 # A function generator function that sets up the a context for the
1814 # A function generator function that sets up the a context for the
1808 # inner function.
1815 # inner function.
1809 def lookup_filenode_link_func(fname):
1816 def lookup_filenode_link_func(fname):
1810 msngset = msng_filenode_set[fname]
1817 msngset = msng_filenode_set[fname]
1811 # Lookup the changenode the filenode belongs to.
1818 # Lookup the changenode the filenode belongs to.
1812 def lookup_filenode_link(fnode):
1819 def lookup_filenode_link(fnode):
1813 return msngset[fnode]
1820 return msngset[fnode]
1814 return lookup_filenode_link
1821 return lookup_filenode_link
1815
1822
1816 # Add the nodes that were explicitly requested.
1823 # Add the nodes that were explicitly requested.
1817 def add_extra_nodes(name, nodes):
1824 def add_extra_nodes(name, nodes):
1818 if not extranodes or name not in extranodes:
1825 if not extranodes or name not in extranodes:
1819 return
1826 return
1820
1827
1821 for node, linknode in extranodes[name]:
1828 for node, linknode in extranodes[name]:
1822 if node not in nodes:
1829 if node not in nodes:
1823 nodes[node] = linknode
1830 nodes[node] = linknode
1824
1831
1825 # Now that we have all theses utility functions to help out and
1832 # Now that we have all theses utility functions to help out and
1826 # logically divide up the task, generate the group.
1833 # logically divide up the task, generate the group.
1827 def gengroup():
1834 def gengroup():
1828 # The set of changed files starts empty.
1835 # The set of changed files starts empty.
1829 changedfiles = {}
1836 changedfiles = {}
1830 # Create a changenode group generator that will call our functions
1837 # Create a changenode group generator that will call our functions
1831 # back to lookup the owning changenode and collect information.
1838 # back to lookup the owning changenode and collect information.
1832 group = cl.group(msng_cl_lst, identity,
1839 group = cl.group(msng_cl_lst, identity,
1833 manifest_and_file_collector(changedfiles))
1840 manifest_and_file_collector(changedfiles))
1834 for chnk in group:
1841 for chnk in group:
1835 yield chnk
1842 yield chnk
1836
1843
1837 # The list of manifests has been collected by the generator
1844 # The list of manifests has been collected by the generator
1838 # calling our functions back.
1845 # calling our functions back.
1839 prune_manifests()
1846 prune_manifests()
1840 add_extra_nodes(1, msng_mnfst_set)
1847 add_extra_nodes(1, msng_mnfst_set)
1841 msng_mnfst_lst = msng_mnfst_set.keys()
1848 msng_mnfst_lst = msng_mnfst_set.keys()
1842 # Sort the manifestnodes by revision number.
1849 # Sort the manifestnodes by revision number.
1843 msng_mnfst_lst.sort(key=mnfst.rev)
1850 msng_mnfst_lst.sort(key=mnfst.rev)
1844 # Create a generator for the manifestnodes that calls our lookup
1851 # Create a generator for the manifestnodes that calls our lookup
1845 # and data collection functions back.
1852 # and data collection functions back.
1846 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1853 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1847 filenode_collector(changedfiles))
1854 filenode_collector(changedfiles))
1848 for chnk in group:
1855 for chnk in group:
1849 yield chnk
1856 yield chnk
1850
1857
1851 # These are no longer needed, dereference and toss the memory for
1858 # These are no longer needed, dereference and toss the memory for
1852 # them.
1859 # them.
1853 msng_mnfst_lst = None
1860 msng_mnfst_lst = None
1854 msng_mnfst_set.clear()
1861 msng_mnfst_set.clear()
1855
1862
1856 if extranodes:
1863 if extranodes:
1857 for fname in extranodes:
1864 for fname in extranodes:
1858 if isinstance(fname, int):
1865 if isinstance(fname, int):
1859 continue
1866 continue
1860 msng_filenode_set.setdefault(fname, {})
1867 msng_filenode_set.setdefault(fname, {})
1861 changedfiles[fname] = 1
1868 changedfiles[fname] = 1
1862 # Go through all our files in order sorted by name.
1869 # Go through all our files in order sorted by name.
1863 for fname in sorted(changedfiles):
1870 for fname in sorted(changedfiles):
1864 filerevlog = self.file(fname)
1871 filerevlog = self.file(fname)
1865 if not len(filerevlog):
1872 if not len(filerevlog):
1866 raise util.Abort(_("empty or missing revlog for %s") % fname)
1873 raise util.Abort(_("empty or missing revlog for %s") % fname)
1867 # Toss out the filenodes that the recipient isn't really
1874 # Toss out the filenodes that the recipient isn't really
1868 # missing.
1875 # missing.
1869 if fname in msng_filenode_set:
1876 if fname in msng_filenode_set:
1870 prune_filenodes(fname, filerevlog)
1877 prune_filenodes(fname, filerevlog)
1871 add_extra_nodes(fname, msng_filenode_set[fname])
1878 add_extra_nodes(fname, msng_filenode_set[fname])
1872 msng_filenode_lst = msng_filenode_set[fname].keys()
1879 msng_filenode_lst = msng_filenode_set[fname].keys()
1873 else:
1880 else:
1874 msng_filenode_lst = []
1881 msng_filenode_lst = []
1875 # If any filenodes are left, generate the group for them,
1882 # If any filenodes are left, generate the group for them,
1876 # otherwise don't bother.
1883 # otherwise don't bother.
1877 if len(msng_filenode_lst) > 0:
1884 if len(msng_filenode_lst) > 0:
1878 yield changegroup.chunkheader(len(fname))
1885 yield changegroup.chunkheader(len(fname))
1879 yield fname
1886 yield fname
1880 # Sort the filenodes by their revision #
1887 # Sort the filenodes by their revision #
1881 msng_filenode_lst.sort(key=filerevlog.rev)
1888 msng_filenode_lst.sort(key=filerevlog.rev)
1882 # Create a group generator and only pass in a changenode
1889 # Create a group generator and only pass in a changenode
1883 # lookup function as we need to collect no information
1890 # lookup function as we need to collect no information
1884 # from filenodes.
1891 # from filenodes.
1885 group = filerevlog.group(msng_filenode_lst,
1892 group = filerevlog.group(msng_filenode_lst,
1886 lookup_filenode_link_func(fname))
1893 lookup_filenode_link_func(fname))
1887 for chnk in group:
1894 for chnk in group:
1888 yield chnk
1895 yield chnk
1889 if fname in msng_filenode_set:
1896 if fname in msng_filenode_set:
1890 # Don't need this anymore, toss it to free memory.
1897 # Don't need this anymore, toss it to free memory.
1891 del msng_filenode_set[fname]
1898 del msng_filenode_set[fname]
1892 # Signal that no more groups are left.
1899 # Signal that no more groups are left.
1893 yield changegroup.closechunk()
1900 yield changegroup.closechunk()
1894
1901
1895 if msng_cl_lst:
1902 if msng_cl_lst:
1896 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1903 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1897
1904
1898 return util.chunkbuffer(gengroup())
1905 return util.chunkbuffer(gengroup())
1899
1906
1900 def changegroup(self, basenodes, source):
1907 def changegroup(self, basenodes, source):
1901 # to avoid a race we use changegroupsubset() (issue1320)
1908 # to avoid a race we use changegroupsubset() (issue1320)
1902 return self.changegroupsubset(basenodes, self.heads(), source)
1909 return self.changegroupsubset(basenodes, self.heads(), source)
1903
1910
1904 def _changegroup(self, common, source):
1911 def _changegroup(self, common, source):
1905 """Generate a changegroup of all nodes that we have that a recipient
1912 """Compute the changegroup of all nodes that we have that a recipient
1906 doesn't.
1913 doesn't. Return a chunkbuffer object whose read() method will return
1914 successive changegroup chunks.
1907
1915
1908 This is much easier than the previous function as we can assume that
1916 This is much easier than the previous function as we can assume that
1909 the recipient has any changenode we aren't sending them.
1917 the recipient has any changenode we aren't sending them.
1910
1918
1911 common is the set of common nodes between remote and self"""
1919 common is the set of common nodes between remote and self"""
1912
1920
1913 self.hook('preoutgoing', throw=True, source=source)
1921 self.hook('preoutgoing', throw=True, source=source)
1914
1922
1915 cl = self.changelog
1923 cl = self.changelog
1916 nodes = cl.findmissing(common)
1924 nodes = cl.findmissing(common)
1917 revset = set([cl.rev(n) for n in nodes])
1925 revset = set([cl.rev(n) for n in nodes])
1918 self.changegroupinfo(nodes, source)
1926 self.changegroupinfo(nodes, source)
1919
1927
1920 def identity(x):
1928 def identity(x):
1921 return x
1929 return x
1922
1930
1923 def gennodelst(log):
1931 def gennodelst(log):
1924 for r in log:
1932 for r in log:
1925 if log.linkrev(r) in revset:
1933 if log.linkrev(r) in revset:
1926 yield log.node(r)
1934 yield log.node(r)
1927
1935
1928 def changed_file_collector(changedfileset):
1936 def changed_file_collector(changedfileset):
1929 def collect_changed_files(clnode):
1937 def collect_changed_files(clnode):
1930 c = cl.read(clnode)
1938 c = cl.read(clnode)
1931 changedfileset.update(c[3])
1939 changedfileset.update(c[3])
1932 return collect_changed_files
1940 return collect_changed_files
1933
1941
1934 def lookuprevlink_func(revlog):
1942 def lookuprevlink_func(revlog):
1935 def lookuprevlink(n):
1943 def lookuprevlink(n):
1936 return cl.node(revlog.linkrev(revlog.rev(n)))
1944 return cl.node(revlog.linkrev(revlog.rev(n)))
1937 return lookuprevlink
1945 return lookuprevlink
1938
1946
1939 def gengroup():
1947 def gengroup():
1948 '''yield a sequence of changegroup chunks (strings)'''
1940 # construct a list of all changed files
1949 # construct a list of all changed files
1941 changedfiles = set()
1950 changedfiles = set()
1942
1951
1943 for chnk in cl.group(nodes, identity,
1952 for chnk in cl.group(nodes, identity,
1944 changed_file_collector(changedfiles)):
1953 changed_file_collector(changedfiles)):
1945 yield chnk
1954 yield chnk
1946
1955
1947 mnfst = self.manifest
1956 mnfst = self.manifest
1948 nodeiter = gennodelst(mnfst)
1957 nodeiter = gennodelst(mnfst)
1949 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1958 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1950 yield chnk
1959 yield chnk
1951
1960
1952 for fname in sorted(changedfiles):
1961 for fname in sorted(changedfiles):
1953 filerevlog = self.file(fname)
1962 filerevlog = self.file(fname)
1954 if not len(filerevlog):
1963 if not len(filerevlog):
1955 raise util.Abort(_("empty or missing revlog for %s") % fname)
1964 raise util.Abort(_("empty or missing revlog for %s") % fname)
1956 nodeiter = gennodelst(filerevlog)
1965 nodeiter = gennodelst(filerevlog)
1957 nodeiter = list(nodeiter)
1966 nodeiter = list(nodeiter)
1958 if nodeiter:
1967 if nodeiter:
1959 yield changegroup.chunkheader(len(fname))
1968 yield changegroup.chunkheader(len(fname))
1960 yield fname
1969 yield fname
1961 lookup = lookuprevlink_func(filerevlog)
1970 lookup = lookuprevlink_func(filerevlog)
1962 for chnk in filerevlog.group(nodeiter, lookup):
1971 for chnk in filerevlog.group(nodeiter, lookup):
1963 yield chnk
1972 yield chnk
1964
1973
1965 yield changegroup.closechunk()
1974 yield changegroup.closechunk()
1966
1975
1967 if nodes:
1976 if nodes:
1968 self.hook('outgoing', node=hex(nodes[0]), source=source)
1977 self.hook('outgoing', node=hex(nodes[0]), source=source)
1969
1978
1970 return util.chunkbuffer(gengroup())
1979 return util.chunkbuffer(gengroup())
1971
1980
1972 def addchangegroup(self, source, srctype, url, emptyok=False):
1981 def addchangegroup(self, source, srctype, url, emptyok=False):
1973 """add changegroup to repo.
1982 """add changegroup to repo.
1974
1983
1975 return values:
1984 return values:
1976 - nothing changed or no source: 0
1985 - nothing changed or no source: 0
1977 - more heads than before: 1+added heads (2..n)
1986 - more heads than before: 1+added heads (2..n)
1978 - less heads than before: -1-removed heads (-2..-n)
1987 - less heads than before: -1-removed heads (-2..-n)
1979 - number of heads stays the same: 1
1988 - number of heads stays the same: 1
1980 """
1989 """
1981 def csmap(x):
1990 def csmap(x):
1982 self.ui.debug(_("add changeset %s\n") % short(x))
1991 self.ui.debug(_("add changeset %s\n") % short(x))
1983 return len(cl)
1992 return len(cl)
1984
1993
1985 def revmap(x):
1994 def revmap(x):
1986 return cl.rev(x)
1995 return cl.rev(x)
1987
1996
1988 if not source:
1997 if not source:
1989 return 0
1998 return 0
1990
1999
1991 self.hook('prechangegroup', throw=True, source=srctype, url=url)
2000 self.hook('prechangegroup', throw=True, source=srctype, url=url)
1992
2001
1993 changesets = files = revisions = 0
2002 changesets = files = revisions = 0
1994
2003
1995 # write changelog data to temp files so concurrent readers will not see
2004 # write changelog data to temp files so concurrent readers will not see
1996 # inconsistent view
2005 # inconsistent view
1997 cl = self.changelog
2006 cl = self.changelog
1998 cl.delayupdate()
2007 cl.delayupdate()
1999 oldheads = len(cl.heads())
2008 oldheads = len(cl.heads())
2000
2009
2001 tr = self.transaction()
2010 tr = self.transaction()
2002 try:
2011 try:
2003 trp = weakref.proxy(tr)
2012 trp = weakref.proxy(tr)
2004 # pull off the changeset group
2013 # pull off the changeset group
2005 self.ui.status(_("adding changesets\n"))
2014 self.ui.status(_("adding changesets\n"))
2006 clstart = len(cl)
2015 clstart = len(cl)
2007 chunkiter = changegroup.chunkiter(source)
2016 chunkiter = changegroup.chunkiter(source)
2008 if cl.addgroup(chunkiter, csmap, trp) is None and not emptyok:
2017 if cl.addgroup(chunkiter, csmap, trp) is None and not emptyok:
2009 raise util.Abort(_("received changelog group is empty"))
2018 raise util.Abort(_("received changelog group is empty"))
2010 clend = len(cl)
2019 clend = len(cl)
2011 changesets = clend - clstart
2020 changesets = clend - clstart
2012
2021
2013 # pull off the manifest group
2022 # pull off the manifest group
2014 self.ui.status(_("adding manifests\n"))
2023 self.ui.status(_("adding manifests\n"))
2015 chunkiter = changegroup.chunkiter(source)
2024 chunkiter = changegroup.chunkiter(source)
2016 # no need to check for empty manifest group here:
2025 # no need to check for empty manifest group here:
2017 # if the result of the merge of 1 and 2 is the same in 3 and 4,
2026 # if the result of the merge of 1 and 2 is the same in 3 and 4,
2018 # no new manifest will be created and the manifest group will
2027 # no new manifest will be created and the manifest group will
2019 # be empty during the pull
2028 # be empty during the pull
2020 self.manifest.addgroup(chunkiter, revmap, trp)
2029 self.manifest.addgroup(chunkiter, revmap, trp)
2021
2030
2022 # process the files
2031 # process the files
2023 self.ui.status(_("adding file changes\n"))
2032 self.ui.status(_("adding file changes\n"))
2024 while 1:
2033 while 1:
2025 f = changegroup.getchunk(source)
2034 f = changegroup.getchunk(source)
2026 if not f:
2035 if not f:
2027 break
2036 break
2028 self.ui.debug(_("adding %s revisions\n") % f)
2037 self.ui.debug(_("adding %s revisions\n") % f)
2029 fl = self.file(f)
2038 fl = self.file(f)
2030 o = len(fl)
2039 o = len(fl)
2031 chunkiter = changegroup.chunkiter(source)
2040 chunkiter = changegroup.chunkiter(source)
2032 if fl.addgroup(chunkiter, revmap, trp) is None:
2041 if fl.addgroup(chunkiter, revmap, trp) is None:
2033 raise util.Abort(_("received file revlog group is empty"))
2042 raise util.Abort(_("received file revlog group is empty"))
2034 revisions += len(fl) - o
2043 revisions += len(fl) - o
2035 files += 1
2044 files += 1
2036
2045
2037 newheads = len(cl.heads())
2046 newheads = len(cl.heads())
2038 heads = ""
2047 heads = ""
2039 if oldheads and newheads != oldheads:
2048 if oldheads and newheads != oldheads:
2040 heads = _(" (%+d heads)") % (newheads - oldheads)
2049 heads = _(" (%+d heads)") % (newheads - oldheads)
2041
2050
2042 self.ui.status(_("added %d changesets"
2051 self.ui.status(_("added %d changesets"
2043 " with %d changes to %d files%s\n")
2052 " with %d changes to %d files%s\n")
2044 % (changesets, revisions, files, heads))
2053 % (changesets, revisions, files, heads))
2045
2054
2046 if changesets > 0:
2055 if changesets > 0:
2047 p = lambda: cl.writepending() and self.root or ""
2056 p = lambda: cl.writepending() and self.root or ""
2048 self.hook('pretxnchangegroup', throw=True,
2057 self.hook('pretxnchangegroup', throw=True,
2049 node=hex(cl.node(clstart)), source=srctype,
2058 node=hex(cl.node(clstart)), source=srctype,
2050 url=url, pending=p)
2059 url=url, pending=p)
2051
2060
2052 # make changelog see real files again
2061 # make changelog see real files again
2053 cl.finalize(trp)
2062 cl.finalize(trp)
2054
2063
2055 tr.close()
2064 tr.close()
2056 finally:
2065 finally:
2057 del tr
2066 del tr
2058
2067
2059 if changesets > 0:
2068 if changesets > 0:
2060 # forcefully update the on-disk branch cache
2069 # forcefully update the on-disk branch cache
2061 self.ui.debug(_("updating the branch cache\n"))
2070 self.ui.debug(_("updating the branch cache\n"))
2062 self.branchtags()
2071 self.branchtags()
2063 self.hook("changegroup", node=hex(cl.node(clstart)),
2072 self.hook("changegroup", node=hex(cl.node(clstart)),
2064 source=srctype, url=url)
2073 source=srctype, url=url)
2065
2074
2066 for i in xrange(clstart, clend):
2075 for i in xrange(clstart, clend):
2067 self.hook("incoming", node=hex(cl.node(i)),
2076 self.hook("incoming", node=hex(cl.node(i)),
2068 source=srctype, url=url)
2077 source=srctype, url=url)
2069
2078
2070 # never return 0 here:
2079 # never return 0 here:
2071 if newheads < oldheads:
2080 if newheads < oldheads:
2072 return newheads - oldheads - 1
2081 return newheads - oldheads - 1
2073 else:
2082 else:
2074 return newheads - oldheads + 1
2083 return newheads - oldheads + 1
2075
2084
2076
2085
2077 def stream_in(self, remote):
2086 def stream_in(self, remote):
2078 fp = remote.stream_out()
2087 fp = remote.stream_out()
2079 l = fp.readline()
2088 l = fp.readline()
2080 try:
2089 try:
2081 resp = int(l)
2090 resp = int(l)
2082 except ValueError:
2091 except ValueError:
2083 raise error.ResponseError(
2092 raise error.ResponseError(
2084 _('Unexpected response from remote server:'), l)
2093 _('Unexpected response from remote server:'), l)
2085 if resp == 1:
2094 if resp == 1:
2086 raise util.Abort(_('operation forbidden by server'))
2095 raise util.Abort(_('operation forbidden by server'))
2087 elif resp == 2:
2096 elif resp == 2:
2088 raise util.Abort(_('locking the remote repository failed'))
2097 raise util.Abort(_('locking the remote repository failed'))
2089 elif resp != 0:
2098 elif resp != 0:
2090 raise util.Abort(_('the server sent an unknown error code'))
2099 raise util.Abort(_('the server sent an unknown error code'))
2091 self.ui.status(_('streaming all changes\n'))
2100 self.ui.status(_('streaming all changes\n'))
2092 l = fp.readline()
2101 l = fp.readline()
2093 try:
2102 try:
2094 total_files, total_bytes = map(int, l.split(' ', 1))
2103 total_files, total_bytes = map(int, l.split(' ', 1))
2095 except (ValueError, TypeError):
2104 except (ValueError, TypeError):
2096 raise error.ResponseError(
2105 raise error.ResponseError(
2097 _('Unexpected response from remote server:'), l)
2106 _('Unexpected response from remote server:'), l)
2098 self.ui.status(_('%d files to transfer, %s of data\n') %
2107 self.ui.status(_('%d files to transfer, %s of data\n') %
2099 (total_files, util.bytecount(total_bytes)))
2108 (total_files, util.bytecount(total_bytes)))
2100 start = time.time()
2109 start = time.time()
2101 for i in xrange(total_files):
2110 for i in xrange(total_files):
2102 # XXX doesn't support '\n' or '\r' in filenames
2111 # XXX doesn't support '\n' or '\r' in filenames
2103 l = fp.readline()
2112 l = fp.readline()
2104 try:
2113 try:
2105 name, size = l.split('\0', 1)
2114 name, size = l.split('\0', 1)
2106 size = int(size)
2115 size = int(size)
2107 except (ValueError, TypeError):
2116 except (ValueError, TypeError):
2108 raise error.ResponseError(
2117 raise error.ResponseError(
2109 _('Unexpected response from remote server:'), l)
2118 _('Unexpected response from remote server:'), l)
2110 self.ui.debug(_('adding %s (%s)\n') % (name, util.bytecount(size)))
2119 self.ui.debug(_('adding %s (%s)\n') % (name, util.bytecount(size)))
2111 # for backwards compat, name was partially encoded
2120 # for backwards compat, name was partially encoded
2112 ofp = self.sopener(store.decodedir(name), 'w')
2121 ofp = self.sopener(store.decodedir(name), 'w')
2113 for chunk in util.filechunkiter(fp, limit=size):
2122 for chunk in util.filechunkiter(fp, limit=size):
2114 ofp.write(chunk)
2123 ofp.write(chunk)
2115 ofp.close()
2124 ofp.close()
2116 elapsed = time.time() - start
2125 elapsed = time.time() - start
2117 if elapsed <= 0:
2126 if elapsed <= 0:
2118 elapsed = 0.001
2127 elapsed = 0.001
2119 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
2128 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
2120 (util.bytecount(total_bytes), elapsed,
2129 (util.bytecount(total_bytes), elapsed,
2121 util.bytecount(total_bytes / elapsed)))
2130 util.bytecount(total_bytes / elapsed)))
2122 self.invalidate()
2131 self.invalidate()
2123 return len(self.heads()) + 1
2132 return len(self.heads()) + 1
2124
2133
2125 def clone(self, remote, heads=[], stream=False):
2134 def clone(self, remote, heads=[], stream=False):
2126 '''clone remote repository.
2135 '''clone remote repository.
2127
2136
2128 keyword arguments:
2137 keyword arguments:
2129 heads: list of revs to clone (forces use of pull)
2138 heads: list of revs to clone (forces use of pull)
2130 stream: use streaming clone if possible'''
2139 stream: use streaming clone if possible'''
2131
2140
2132 # now, all clients that can request uncompressed clones can
2141 # now, all clients that can request uncompressed clones can
2133 # read repo formats supported by all servers that can serve
2142 # read repo formats supported by all servers that can serve
2134 # them.
2143 # them.
2135
2144
2136 # if revlog format changes, client will have to check version
2145 # if revlog format changes, client will have to check version
2137 # and format flags on "stream" capability, and use
2146 # and format flags on "stream" capability, and use
2138 # uncompressed only if compatible.
2147 # uncompressed only if compatible.
2139
2148
2140 if stream and not heads and remote.capable('stream'):
2149 if stream and not heads and remote.capable('stream'):
2141 return self.stream_in(remote)
2150 return self.stream_in(remote)
2142 return self.pull(remote, heads)
2151 return self.pull(remote, heads)
2143
2152
2144 # used to avoid circular references so destructors work
2153 # used to avoid circular references so destructors work
2145 def aftertrans(files):
2154 def aftertrans(files):
2146 renamefiles = [tuple(t) for t in files]
2155 renamefiles = [tuple(t) for t in files]
2147 def a():
2156 def a():
2148 for src, dest in renamefiles:
2157 for src, dest in renamefiles:
2149 util.rename(src, dest)
2158 util.rename(src, dest)
2150 return a
2159 return a
2151
2160
2152 def instance(ui, path, create):
2161 def instance(ui, path, create):
2153 return localrepository(ui, util.drop_scheme('file', path), create)
2162 return localrepository(ui, util.drop_scheme('file', path), create)
2154
2163
2155 def islocal(path):
2164 def islocal(path):
2156 return True
2165 return True
@@ -1,1377 +1,1378 b''
1 # revlog.py - storage back-end for mercurial
1 # revlog.py - storage back-end for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 """Storage back-end for Mercurial.
8 """Storage back-end for Mercurial.
9
9
10 This provides efficient delta storage with O(1) retrieve and append
10 This provides efficient delta storage with O(1) retrieve and append
11 and O(changes) merge between branches.
11 and O(changes) merge between branches.
12 """
12 """
13
13
14 # import stuff from node for others to import from revlog
14 # import stuff from node for others to import from revlog
15 from node import bin, hex, nullid, nullrev, short #@UnusedImport
15 from node import bin, hex, nullid, nullrev, short #@UnusedImport
16 from i18n import _
16 from i18n import _
17 import changegroup, ancestor, mdiff, parsers, error, util
17 import changegroup, ancestor, mdiff, parsers, error, util
18 import struct, zlib, errno
18 import struct, zlib, errno
19
19
20 _pack = struct.pack
20 _pack = struct.pack
21 _unpack = struct.unpack
21 _unpack = struct.unpack
22 _compress = zlib.compress
22 _compress = zlib.compress
23 _decompress = zlib.decompress
23 _decompress = zlib.decompress
24 _sha = util.sha1
24 _sha = util.sha1
25
25
26 # revlog flags
26 # revlog flags
27 REVLOGV0 = 0
27 REVLOGV0 = 0
28 REVLOGNG = 1
28 REVLOGNG = 1
29 REVLOGNGINLINEDATA = (1 << 16)
29 REVLOGNGINLINEDATA = (1 << 16)
30 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
30 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
31 REVLOG_DEFAULT_FORMAT = REVLOGNG
31 REVLOG_DEFAULT_FORMAT = REVLOGNG
32 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
32 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
33
33
34 _prereadsize = 1048576
34 _prereadsize = 1048576
35
35
36 RevlogError = error.RevlogError
36 RevlogError = error.RevlogError
37 LookupError = error.LookupError
37 LookupError = error.LookupError
38
38
39 def getoffset(q):
39 def getoffset(q):
40 return int(q >> 16)
40 return int(q >> 16)
41
41
42 def gettype(q):
42 def gettype(q):
43 return int(q & 0xFFFF)
43 return int(q & 0xFFFF)
44
44
45 def offset_type(offset, type):
45 def offset_type(offset, type):
46 return long(long(offset) << 16 | type)
46 return long(long(offset) << 16 | type)
47
47
48 nullhash = _sha(nullid)
48 nullhash = _sha(nullid)
49
49
50 def hash(text, p1, p2):
50 def hash(text, p1, p2):
51 """generate a hash from the given text and its parent hashes
51 """generate a hash from the given text and its parent hashes
52
52
53 This hash combines both the current file contents and its history
53 This hash combines both the current file contents and its history
54 in a manner that makes it easy to distinguish nodes with the same
54 in a manner that makes it easy to distinguish nodes with the same
55 content in the revision graph.
55 content in the revision graph.
56 """
56 """
57 # As of now, if one of the parent node is null, p2 is null
57 # As of now, if one of the parent node is null, p2 is null
58 if p2 == nullid:
58 if p2 == nullid:
59 # deep copy of a hash is faster than creating one
59 # deep copy of a hash is faster than creating one
60 s = nullhash.copy()
60 s = nullhash.copy()
61 s.update(p1)
61 s.update(p1)
62 else:
62 else:
63 # none of the parent nodes are nullid
63 # none of the parent nodes are nullid
64 l = [p1, p2]
64 l = [p1, p2]
65 l.sort()
65 l.sort()
66 s = _sha(l[0])
66 s = _sha(l[0])
67 s.update(l[1])
67 s.update(l[1])
68 s.update(text)
68 s.update(text)
69 return s.digest()
69 return s.digest()
70
70
71 def compress(text):
71 def compress(text):
72 """ generate a possibly-compressed representation of text """
72 """ generate a possibly-compressed representation of text """
73 if not text:
73 if not text:
74 return ("", text)
74 return ("", text)
75 l = len(text)
75 l = len(text)
76 bin = None
76 bin = None
77 if l < 44:
77 if l < 44:
78 pass
78 pass
79 elif l > 1000000:
79 elif l > 1000000:
80 # zlib makes an internal copy, thus doubling memory usage for
80 # zlib makes an internal copy, thus doubling memory usage for
81 # large files, so lets do this in pieces
81 # large files, so lets do this in pieces
82 z = zlib.compressobj()
82 z = zlib.compressobj()
83 p = []
83 p = []
84 pos = 0
84 pos = 0
85 while pos < l:
85 while pos < l:
86 pos2 = pos + 2**20
86 pos2 = pos + 2**20
87 p.append(z.compress(text[pos:pos2]))
87 p.append(z.compress(text[pos:pos2]))
88 pos = pos2
88 pos = pos2
89 p.append(z.flush())
89 p.append(z.flush())
90 if sum(map(len, p)) < l:
90 if sum(map(len, p)) < l:
91 bin = "".join(p)
91 bin = "".join(p)
92 else:
92 else:
93 bin = _compress(text)
93 bin = _compress(text)
94 if bin is None or len(bin) > l:
94 if bin is None or len(bin) > l:
95 if text[0] == '\0':
95 if text[0] == '\0':
96 return ("", text)
96 return ("", text)
97 return ('u', text)
97 return ('u', text)
98 return ("", bin)
98 return ("", bin)
99
99
100 def decompress(bin):
100 def decompress(bin):
101 """ decompress the given input """
101 """ decompress the given input """
102 if not bin:
102 if not bin:
103 return bin
103 return bin
104 t = bin[0]
104 t = bin[0]
105 if t == '\0':
105 if t == '\0':
106 return bin
106 return bin
107 if t == 'x':
107 if t == 'x':
108 return _decompress(bin)
108 return _decompress(bin)
109 if t == 'u':
109 if t == 'u':
110 return bin[1:]
110 return bin[1:]
111 raise RevlogError(_("unknown compression type %r") % t)
111 raise RevlogError(_("unknown compression type %r") % t)
112
112
113 class lazyparser(object):
113 class lazyparser(object):
114 """
114 """
115 this class avoids the need to parse the entirety of large indices
115 this class avoids the need to parse the entirety of large indices
116 """
116 """
117
117
118 # lazyparser is not safe to use on windows if win32 extensions not
118 # lazyparser is not safe to use on windows if win32 extensions not
119 # available. it keeps file handle open, which make it not possible
119 # available. it keeps file handle open, which make it not possible
120 # to break hardlinks on local cloned repos.
120 # to break hardlinks on local cloned repos.
121
121
122 def __init__(self, dataf):
122 def __init__(self, dataf):
123 try:
123 try:
124 size = util.fstat(dataf).st_size
124 size = util.fstat(dataf).st_size
125 except AttributeError:
125 except AttributeError:
126 size = 0
126 size = 0
127 self.dataf = dataf
127 self.dataf = dataf
128 self.s = struct.calcsize(indexformatng)
128 self.s = struct.calcsize(indexformatng)
129 self.datasize = size
129 self.datasize = size
130 self.l = size/self.s
130 self.l = size/self.s
131 self.index = [None] * self.l
131 self.index = [None] * self.l
132 self.map = {nullid: nullrev}
132 self.map = {nullid: nullrev}
133 self.allmap = 0
133 self.allmap = 0
134 self.all = 0
134 self.all = 0
135 self.mapfind_count = 0
135 self.mapfind_count = 0
136
136
137 def loadmap(self):
137 def loadmap(self):
138 """
138 """
139 during a commit, we need to make sure the rev being added is
139 during a commit, we need to make sure the rev being added is
140 not a duplicate. This requires loading the entire index,
140 not a duplicate. This requires loading the entire index,
141 which is fairly slow. loadmap can load up just the node map,
141 which is fairly slow. loadmap can load up just the node map,
142 which takes much less time.
142 which takes much less time.
143 """
143 """
144 if self.allmap:
144 if self.allmap:
145 return
145 return
146 end = self.datasize
146 end = self.datasize
147 self.allmap = 1
147 self.allmap = 1
148 cur = 0
148 cur = 0
149 count = 0
149 count = 0
150 blocksize = self.s * 256
150 blocksize = self.s * 256
151 self.dataf.seek(0)
151 self.dataf.seek(0)
152 while cur < end:
152 while cur < end:
153 data = self.dataf.read(blocksize)
153 data = self.dataf.read(blocksize)
154 off = 0
154 off = 0
155 for x in xrange(256):
155 for x in xrange(256):
156 n = data[off + ngshaoffset:off + ngshaoffset + 20]
156 n = data[off + ngshaoffset:off + ngshaoffset + 20]
157 self.map[n] = count
157 self.map[n] = count
158 count += 1
158 count += 1
159 if count >= self.l:
159 if count >= self.l:
160 break
160 break
161 off += self.s
161 off += self.s
162 cur += blocksize
162 cur += blocksize
163
163
164 def loadblock(self, blockstart, blocksize, data=None):
164 def loadblock(self, blockstart, blocksize, data=None):
165 if self.all:
165 if self.all:
166 return
166 return
167 if data is None:
167 if data is None:
168 self.dataf.seek(blockstart)
168 self.dataf.seek(blockstart)
169 if blockstart + blocksize > self.datasize:
169 if blockstart + blocksize > self.datasize:
170 # the revlog may have grown since we've started running,
170 # the revlog may have grown since we've started running,
171 # but we don't have space in self.index for more entries.
171 # but we don't have space in self.index for more entries.
172 # limit blocksize so that we don't get too much data.
172 # limit blocksize so that we don't get too much data.
173 blocksize = max(self.datasize - blockstart, 0)
173 blocksize = max(self.datasize - blockstart, 0)
174 data = self.dataf.read(blocksize)
174 data = self.dataf.read(blocksize)
175 lend = len(data) / self.s
175 lend = len(data) / self.s
176 i = blockstart / self.s
176 i = blockstart / self.s
177 off = 0
177 off = 0
178 # lazyindex supports __delitem__
178 # lazyindex supports __delitem__
179 if lend > len(self.index) - i:
179 if lend > len(self.index) - i:
180 lend = len(self.index) - i
180 lend = len(self.index) - i
181 for x in xrange(lend):
181 for x in xrange(lend):
182 if self.index[i + x] is None:
182 if self.index[i + x] is None:
183 b = data[off : off + self.s]
183 b = data[off : off + self.s]
184 self.index[i + x] = b
184 self.index[i + x] = b
185 n = b[ngshaoffset:ngshaoffset + 20]
185 n = b[ngshaoffset:ngshaoffset + 20]
186 self.map[n] = i + x
186 self.map[n] = i + x
187 off += self.s
187 off += self.s
188
188
189 def findnode(self, node):
189 def findnode(self, node):
190 """search backwards through the index file for a specific node"""
190 """search backwards through the index file for a specific node"""
191 if self.allmap:
191 if self.allmap:
192 return None
192 return None
193
193
194 # hg log will cause many many searches for the manifest
194 # hg log will cause many many searches for the manifest
195 # nodes. After we get called a few times, just load the whole
195 # nodes. After we get called a few times, just load the whole
196 # thing.
196 # thing.
197 if self.mapfind_count > 8:
197 if self.mapfind_count > 8:
198 self.loadmap()
198 self.loadmap()
199 if node in self.map:
199 if node in self.map:
200 return node
200 return node
201 return None
201 return None
202 self.mapfind_count += 1
202 self.mapfind_count += 1
203 last = self.l - 1
203 last = self.l - 1
204 while self.index[last] != None:
204 while self.index[last] != None:
205 if last == 0:
205 if last == 0:
206 self.all = 1
206 self.all = 1
207 self.allmap = 1
207 self.allmap = 1
208 return None
208 return None
209 last -= 1
209 last -= 1
210 end = (last + 1) * self.s
210 end = (last + 1) * self.s
211 blocksize = self.s * 256
211 blocksize = self.s * 256
212 while end >= 0:
212 while end >= 0:
213 start = max(end - blocksize, 0)
213 start = max(end - blocksize, 0)
214 self.dataf.seek(start)
214 self.dataf.seek(start)
215 data = self.dataf.read(end - start)
215 data = self.dataf.read(end - start)
216 findend = end - start
216 findend = end - start
217 while True:
217 while True:
218 # we're searching backwards, so we have to make sure
218 # we're searching backwards, so we have to make sure
219 # we don't find a changeset where this node is a parent
219 # we don't find a changeset where this node is a parent
220 off = data.find(node, 0, findend)
220 off = data.find(node, 0, findend)
221 findend = off
221 findend = off
222 if off >= 0:
222 if off >= 0:
223 i = off / self.s
223 i = off / self.s
224 off = i * self.s
224 off = i * self.s
225 n = data[off + ngshaoffset:off + ngshaoffset + 20]
225 n = data[off + ngshaoffset:off + ngshaoffset + 20]
226 if n == node:
226 if n == node:
227 self.map[n] = i + start / self.s
227 self.map[n] = i + start / self.s
228 return node
228 return node
229 else:
229 else:
230 break
230 break
231 end -= blocksize
231 end -= blocksize
232 return None
232 return None
233
233
234 def loadindex(self, i=None, end=None):
234 def loadindex(self, i=None, end=None):
235 if self.all:
235 if self.all:
236 return
236 return
237 all = False
237 all = False
238 if i is None:
238 if i is None:
239 blockstart = 0
239 blockstart = 0
240 blocksize = (65536 / self.s) * self.s
240 blocksize = (65536 / self.s) * self.s
241 end = self.datasize
241 end = self.datasize
242 all = True
242 all = True
243 else:
243 else:
244 if end:
244 if end:
245 blockstart = i * self.s
245 blockstart = i * self.s
246 end = end * self.s
246 end = end * self.s
247 blocksize = end - blockstart
247 blocksize = end - blockstart
248 else:
248 else:
249 blockstart = (i & ~1023) * self.s
249 blockstart = (i & ~1023) * self.s
250 blocksize = self.s * 1024
250 blocksize = self.s * 1024
251 end = blockstart + blocksize
251 end = blockstart + blocksize
252 while blockstart < end:
252 while blockstart < end:
253 self.loadblock(blockstart, blocksize)
253 self.loadblock(blockstart, blocksize)
254 blockstart += blocksize
254 blockstart += blocksize
255 if all:
255 if all:
256 self.all = True
256 self.all = True
257
257
258 class lazyindex(object):
258 class lazyindex(object):
259 """a lazy version of the index array"""
259 """a lazy version of the index array"""
260 def __init__(self, parser):
260 def __init__(self, parser):
261 self.p = parser
261 self.p = parser
262 def __len__(self):
262 def __len__(self):
263 return len(self.p.index)
263 return len(self.p.index)
264 def load(self, pos):
264 def load(self, pos):
265 if pos < 0:
265 if pos < 0:
266 pos += len(self.p.index)
266 pos += len(self.p.index)
267 self.p.loadindex(pos)
267 self.p.loadindex(pos)
268 return self.p.index[pos]
268 return self.p.index[pos]
269 def __getitem__(self, pos):
269 def __getitem__(self, pos):
270 return _unpack(indexformatng, self.p.index[pos] or self.load(pos))
270 return _unpack(indexformatng, self.p.index[pos] or self.load(pos))
271 def __setitem__(self, pos, item):
271 def __setitem__(self, pos, item):
272 self.p.index[pos] = _pack(indexformatng, *item)
272 self.p.index[pos] = _pack(indexformatng, *item)
273 def __delitem__(self, pos):
273 def __delitem__(self, pos):
274 del self.p.index[pos]
274 del self.p.index[pos]
275 def insert(self, pos, e):
275 def insert(self, pos, e):
276 self.p.index.insert(pos, _pack(indexformatng, *e))
276 self.p.index.insert(pos, _pack(indexformatng, *e))
277 def append(self, e):
277 def append(self, e):
278 self.p.index.append(_pack(indexformatng, *e))
278 self.p.index.append(_pack(indexformatng, *e))
279
279
280 class lazymap(object):
280 class lazymap(object):
281 """a lazy version of the node map"""
281 """a lazy version of the node map"""
282 def __init__(self, parser):
282 def __init__(self, parser):
283 self.p = parser
283 self.p = parser
284 def load(self, key):
284 def load(self, key):
285 n = self.p.findnode(key)
285 n = self.p.findnode(key)
286 if n is None:
286 if n is None:
287 raise KeyError(key)
287 raise KeyError(key)
288 def __contains__(self, key):
288 def __contains__(self, key):
289 if key in self.p.map:
289 if key in self.p.map:
290 return True
290 return True
291 self.p.loadmap()
291 self.p.loadmap()
292 return key in self.p.map
292 return key in self.p.map
293 def __iter__(self):
293 def __iter__(self):
294 yield nullid
294 yield nullid
295 for i in xrange(self.p.l):
295 for i in xrange(self.p.l):
296 ret = self.p.index[i]
296 ret = self.p.index[i]
297 if not ret:
297 if not ret:
298 self.p.loadindex(i)
298 self.p.loadindex(i)
299 ret = self.p.index[i]
299 ret = self.p.index[i]
300 if isinstance(ret, str):
300 if isinstance(ret, str):
301 ret = _unpack(indexformatng, ret)
301 ret = _unpack(indexformatng, ret)
302 yield ret[7]
302 yield ret[7]
303 def __getitem__(self, key):
303 def __getitem__(self, key):
304 try:
304 try:
305 return self.p.map[key]
305 return self.p.map[key]
306 except KeyError:
306 except KeyError:
307 try:
307 try:
308 self.load(key)
308 self.load(key)
309 return self.p.map[key]
309 return self.p.map[key]
310 except KeyError:
310 except KeyError:
311 raise KeyError("node " + hex(key))
311 raise KeyError("node " + hex(key))
312 def __setitem__(self, key, val):
312 def __setitem__(self, key, val):
313 self.p.map[key] = val
313 self.p.map[key] = val
314 def __delitem__(self, key):
314 def __delitem__(self, key):
315 del self.p.map[key]
315 del self.p.map[key]
316
316
317 indexformatv0 = ">4l20s20s20s"
317 indexformatv0 = ">4l20s20s20s"
318 v0shaoffset = 56
318 v0shaoffset = 56
319
319
320 class revlogoldio(object):
320 class revlogoldio(object):
321 def __init__(self):
321 def __init__(self):
322 self.size = struct.calcsize(indexformatv0)
322 self.size = struct.calcsize(indexformatv0)
323
323
324 def parseindex(self, fp, data, inline):
324 def parseindex(self, fp, data, inline):
325 s = self.size
325 s = self.size
326 index = []
326 index = []
327 nodemap = {nullid: nullrev}
327 nodemap = {nullid: nullrev}
328 n = off = 0
328 n = off = 0
329 if len(data) == _prereadsize:
329 if len(data) == _prereadsize:
330 data += fp.read() # read the rest
330 data += fp.read() # read the rest
331 l = len(data)
331 l = len(data)
332 while off + s <= l:
332 while off + s <= l:
333 cur = data[off:off + s]
333 cur = data[off:off + s]
334 off += s
334 off += s
335 e = _unpack(indexformatv0, cur)
335 e = _unpack(indexformatv0, cur)
336 # transform to revlogv1 format
336 # transform to revlogv1 format
337 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
337 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
338 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
338 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
339 index.append(e2)
339 index.append(e2)
340 nodemap[e[6]] = n
340 nodemap[e[6]] = n
341 n += 1
341 n += 1
342
342
343 return index, nodemap, None
343 return index, nodemap, None
344
344
345 def packentry(self, entry, node, version, rev):
345 def packentry(self, entry, node, version, rev):
346 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
346 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
347 node(entry[5]), node(entry[6]), entry[7])
347 node(entry[5]), node(entry[6]), entry[7])
348 return _pack(indexformatv0, *e2)
348 return _pack(indexformatv0, *e2)
349
349
350 # index ng:
350 # index ng:
351 # 6 bytes offset
351 # 6 bytes offset
352 # 2 bytes flags
352 # 2 bytes flags
353 # 4 bytes compressed length
353 # 4 bytes compressed length
354 # 4 bytes uncompressed length
354 # 4 bytes uncompressed length
355 # 4 bytes: base rev
355 # 4 bytes: base rev
356 # 4 bytes link rev
356 # 4 bytes link rev
357 # 4 bytes parent 1 rev
357 # 4 bytes parent 1 rev
358 # 4 bytes parent 2 rev
358 # 4 bytes parent 2 rev
359 # 32 bytes: nodeid
359 # 32 bytes: nodeid
360 indexformatng = ">Qiiiiii20s12x"
360 indexformatng = ">Qiiiiii20s12x"
361 ngshaoffset = 32
361 ngshaoffset = 32
362 versionformat = ">I"
362 versionformat = ">I"
363
363
364 class revlogio(object):
364 class revlogio(object):
365 def __init__(self):
365 def __init__(self):
366 self.size = struct.calcsize(indexformatng)
366 self.size = struct.calcsize(indexformatng)
367
367
368 def parseindex(self, fp, data, inline):
368 def parseindex(self, fp, data, inline):
369 if len(data) == _prereadsize:
369 if len(data) == _prereadsize:
370 if util.openhardlinks() and not inline:
370 if util.openhardlinks() and not inline:
371 # big index, let's parse it on demand
371 # big index, let's parse it on demand
372 parser = lazyparser(fp)
372 parser = lazyparser(fp)
373 index = lazyindex(parser)
373 index = lazyindex(parser)
374 nodemap = lazymap(parser)
374 nodemap = lazymap(parser)
375 e = list(index[0])
375 e = list(index[0])
376 type = gettype(e[0])
376 type = gettype(e[0])
377 e[0] = offset_type(0, type)
377 e[0] = offset_type(0, type)
378 index[0] = e
378 index[0] = e
379 return index, nodemap, None
379 return index, nodemap, None
380 else:
380 else:
381 data += fp.read()
381 data += fp.read()
382
382
383 # call the C implementation to parse the index data
383 # call the C implementation to parse the index data
384 index, nodemap, cache = parsers.parse_index(data, inline)
384 index, nodemap, cache = parsers.parse_index(data, inline)
385 return index, nodemap, cache
385 return index, nodemap, cache
386
386
387 def packentry(self, entry, node, version, rev):
387 def packentry(self, entry, node, version, rev):
388 p = _pack(indexformatng, *entry)
388 p = _pack(indexformatng, *entry)
389 if rev == 0:
389 if rev == 0:
390 p = _pack(versionformat, version) + p[4:]
390 p = _pack(versionformat, version) + p[4:]
391 return p
391 return p
392
392
393 class revlog(object):
393 class revlog(object):
394 """
394 """
395 the underlying revision storage object
395 the underlying revision storage object
396
396
397 A revlog consists of two parts, an index and the revision data.
397 A revlog consists of two parts, an index and the revision data.
398
398
399 The index is a file with a fixed record size containing
399 The index is a file with a fixed record size containing
400 information on each revision, including its nodeid (hash), the
400 information on each revision, including its nodeid (hash), the
401 nodeids of its parents, the position and offset of its data within
401 nodeids of its parents, the position and offset of its data within
402 the data file, and the revision it's based on. Finally, each entry
402 the data file, and the revision it's based on. Finally, each entry
403 contains a linkrev entry that can serve as a pointer to external
403 contains a linkrev entry that can serve as a pointer to external
404 data.
404 data.
405
405
406 The revision data itself is a linear collection of data chunks.
406 The revision data itself is a linear collection of data chunks.
407 Each chunk represents a revision and is usually represented as a
407 Each chunk represents a revision and is usually represented as a
408 delta against the previous chunk. To bound lookup time, runs of
408 delta against the previous chunk. To bound lookup time, runs of
409 deltas are limited to about 2 times the length of the original
409 deltas are limited to about 2 times the length of the original
410 version data. This makes retrieval of a version proportional to
410 version data. This makes retrieval of a version proportional to
411 its size, or O(1) relative to the number of revisions.
411 its size, or O(1) relative to the number of revisions.
412
412
413 Both pieces of the revlog are written to in an append-only
413 Both pieces of the revlog are written to in an append-only
414 fashion, which means we never need to rewrite a file to insert or
414 fashion, which means we never need to rewrite a file to insert or
415 remove data, and can use some simple techniques to avoid the need
415 remove data, and can use some simple techniques to avoid the need
416 for locking while reading.
416 for locking while reading.
417 """
417 """
418 def __init__(self, opener, indexfile):
418 def __init__(self, opener, indexfile):
419 """
419 """
420 create a revlog object
420 create a revlog object
421
421
422 opener is a function that abstracts the file opening operation
422 opener is a function that abstracts the file opening operation
423 and can be used to implement COW semantics or the like.
423 and can be used to implement COW semantics or the like.
424 """
424 """
425 self.indexfile = indexfile
425 self.indexfile = indexfile
426 self.datafile = indexfile[:-2] + ".d"
426 self.datafile = indexfile[:-2] + ".d"
427 self.opener = opener
427 self.opener = opener
428 self._cache = None
428 self._cache = None
429 self._chunkcache = (0, '')
429 self._chunkcache = (0, '')
430 self.nodemap = {nullid: nullrev}
430 self.nodemap = {nullid: nullrev}
431 self.index = []
431 self.index = []
432
432
433 v = REVLOG_DEFAULT_VERSION
433 v = REVLOG_DEFAULT_VERSION
434 if hasattr(opener, "defversion"):
434 if hasattr(opener, "defversion"):
435 v = opener.defversion
435 v = opener.defversion
436 if v & REVLOGNG:
436 if v & REVLOGNG:
437 v |= REVLOGNGINLINEDATA
437 v |= REVLOGNGINLINEDATA
438
438
439 i = ''
439 i = ''
440 try:
440 try:
441 f = self.opener(self.indexfile)
441 f = self.opener(self.indexfile)
442 i = f.read(_prereadsize)
442 i = f.read(_prereadsize)
443 if len(i) > 0:
443 if len(i) > 0:
444 v = struct.unpack(versionformat, i[:4])[0]
444 v = struct.unpack(versionformat, i[:4])[0]
445 except IOError, inst:
445 except IOError, inst:
446 if inst.errno != errno.ENOENT:
446 if inst.errno != errno.ENOENT:
447 raise
447 raise
448
448
449 self.version = v
449 self.version = v
450 self._inline = v & REVLOGNGINLINEDATA
450 self._inline = v & REVLOGNGINLINEDATA
451 flags = v & ~0xFFFF
451 flags = v & ~0xFFFF
452 fmt = v & 0xFFFF
452 fmt = v & 0xFFFF
453 if fmt == REVLOGV0 and flags:
453 if fmt == REVLOGV0 and flags:
454 raise RevlogError(_("index %s unknown flags %#04x for format v0")
454 raise RevlogError(_("index %s unknown flags %#04x for format v0")
455 % (self.indexfile, flags >> 16))
455 % (self.indexfile, flags >> 16))
456 elif fmt == REVLOGNG and flags & ~REVLOGNGINLINEDATA:
456 elif fmt == REVLOGNG and flags & ~REVLOGNGINLINEDATA:
457 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
457 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
458 % (self.indexfile, flags >> 16))
458 % (self.indexfile, flags >> 16))
459 elif fmt > REVLOGNG:
459 elif fmt > REVLOGNG:
460 raise RevlogError(_("index %s unknown format %d")
460 raise RevlogError(_("index %s unknown format %d")
461 % (self.indexfile, fmt))
461 % (self.indexfile, fmt))
462
462
463 self._io = revlogio()
463 self._io = revlogio()
464 if self.version == REVLOGV0:
464 if self.version == REVLOGV0:
465 self._io = revlogoldio()
465 self._io = revlogoldio()
466 if i:
466 if i:
467 try:
467 try:
468 d = self._io.parseindex(f, i, self._inline)
468 d = self._io.parseindex(f, i, self._inline)
469 except (ValueError, IndexError), e:
469 except (ValueError, IndexError), e:
470 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
470 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
471 self.index, self.nodemap, self._chunkcache = d
471 self.index, self.nodemap, self._chunkcache = d
472 if not self._chunkcache:
472 if not self._chunkcache:
473 self._chunkclear()
473 self._chunkclear()
474
474
475 # add the magic null revision at -1 (if it hasn't been done already)
475 # add the magic null revision at -1 (if it hasn't been done already)
476 if (self.index == [] or isinstance(self.index, lazyindex) or
476 if (self.index == [] or isinstance(self.index, lazyindex) or
477 self.index[-1][7] != nullid) :
477 self.index[-1][7] != nullid) :
478 self.index.append((0, 0, 0, -1, -1, -1, -1, nullid))
478 self.index.append((0, 0, 0, -1, -1, -1, -1, nullid))
479
479
480 def _loadindex(self, start, end):
480 def _loadindex(self, start, end):
481 """load a block of indexes all at once from the lazy parser"""
481 """load a block of indexes all at once from the lazy parser"""
482 if isinstance(self.index, lazyindex):
482 if isinstance(self.index, lazyindex):
483 self.index.p.loadindex(start, end)
483 self.index.p.loadindex(start, end)
484
484
485 def _loadindexmap(self):
485 def _loadindexmap(self):
486 """loads both the map and the index from the lazy parser"""
486 """loads both the map and the index from the lazy parser"""
487 if isinstance(self.index, lazyindex):
487 if isinstance(self.index, lazyindex):
488 p = self.index.p
488 p = self.index.p
489 p.loadindex()
489 p.loadindex()
490 self.nodemap = p.map
490 self.nodemap = p.map
491
491
492 def _loadmap(self):
492 def _loadmap(self):
493 """loads the map from the lazy parser"""
493 """loads the map from the lazy parser"""
494 if isinstance(self.nodemap, lazymap):
494 if isinstance(self.nodemap, lazymap):
495 self.nodemap.p.loadmap()
495 self.nodemap.p.loadmap()
496 self.nodemap = self.nodemap.p.map
496 self.nodemap = self.nodemap.p.map
497
497
498 def tip(self):
498 def tip(self):
499 return self.node(len(self.index) - 2)
499 return self.node(len(self.index) - 2)
500 def __len__(self):
500 def __len__(self):
501 return len(self.index) - 1
501 return len(self.index) - 1
502 def __iter__(self):
502 def __iter__(self):
503 for i in xrange(len(self)):
503 for i in xrange(len(self)):
504 yield i
504 yield i
505 def rev(self, node):
505 def rev(self, node):
506 try:
506 try:
507 return self.nodemap[node]
507 return self.nodemap[node]
508 except KeyError:
508 except KeyError:
509 raise LookupError(node, self.indexfile, _('no node'))
509 raise LookupError(node, self.indexfile, _('no node'))
510 def node(self, rev):
510 def node(self, rev):
511 return self.index[rev][7]
511 return self.index[rev][7]
512 def linkrev(self, rev):
512 def linkrev(self, rev):
513 return self.index[rev][4]
513 return self.index[rev][4]
514 def parents(self, node):
514 def parents(self, node):
515 i = self.index
515 i = self.index
516 d = i[self.rev(node)]
516 d = i[self.rev(node)]
517 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
517 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
518 def parentrevs(self, rev):
518 def parentrevs(self, rev):
519 return self.index[rev][5:7]
519 return self.index[rev][5:7]
520 def start(self, rev):
520 def start(self, rev):
521 return int(self.index[rev][0] >> 16)
521 return int(self.index[rev][0] >> 16)
522 def end(self, rev):
522 def end(self, rev):
523 return self.start(rev) + self.length(rev)
523 return self.start(rev) + self.length(rev)
524 def length(self, rev):
524 def length(self, rev):
525 return self.index[rev][1]
525 return self.index[rev][1]
526 def base(self, rev):
526 def base(self, rev):
527 return self.index[rev][3]
527 return self.index[rev][3]
528
528
529 def size(self, rev):
529 def size(self, rev):
530 """return the length of the uncompressed text for a given revision"""
530 """return the length of the uncompressed text for a given revision"""
531 l = self.index[rev][2]
531 l = self.index[rev][2]
532 if l >= 0:
532 if l >= 0:
533 return l
533 return l
534
534
535 t = self.revision(self.node(rev))
535 t = self.revision(self.node(rev))
536 return len(t)
536 return len(t)
537
537
538 # Alternate implementation. The advantage to this code is it
538 # Alternate implementation. The advantage to this code is it
539 # will be faster for a single revision. However, the results
539 # will be faster for a single revision. However, the results
540 # are not cached, so finding the size of every revision will
540 # are not cached, so finding the size of every revision will
541 # be slower.
541 # be slower.
542 #
542 #
543 # if self.cache and self.cache[1] == rev:
543 # if self.cache and self.cache[1] == rev:
544 # return len(self.cache[2])
544 # return len(self.cache[2])
545 #
545 #
546 # base = self.base(rev)
546 # base = self.base(rev)
547 # if self.cache and self.cache[1] >= base and self.cache[1] < rev:
547 # if self.cache and self.cache[1] >= base and self.cache[1] < rev:
548 # base = self.cache[1]
548 # base = self.cache[1]
549 # text = self.cache[2]
549 # text = self.cache[2]
550 # else:
550 # else:
551 # text = self.revision(self.node(base))
551 # text = self.revision(self.node(base))
552 #
552 #
553 # l = len(text)
553 # l = len(text)
554 # for x in xrange(base + 1, rev + 1):
554 # for x in xrange(base + 1, rev + 1):
555 # l = mdiff.patchedsize(l, self._chunk(x))
555 # l = mdiff.patchedsize(l, self._chunk(x))
556 # return l
556 # return l
557
557
558 def reachable(self, node, stop=None):
558 def reachable(self, node, stop=None):
559 """return the set of all nodes ancestral to a given node, including
559 """return the set of all nodes ancestral to a given node, including
560 the node itself, stopping when stop is matched"""
560 the node itself, stopping when stop is matched"""
561 reachable = set((node,))
561 reachable = set((node,))
562 visit = [node]
562 visit = [node]
563 if stop:
563 if stop:
564 stopn = self.rev(stop)
564 stopn = self.rev(stop)
565 else:
565 else:
566 stopn = 0
566 stopn = 0
567 while visit:
567 while visit:
568 n = visit.pop(0)
568 n = visit.pop(0)
569 if n == stop:
569 if n == stop:
570 continue
570 continue
571 if n == nullid:
571 if n == nullid:
572 continue
572 continue
573 for p in self.parents(n):
573 for p in self.parents(n):
574 if self.rev(p) < stopn:
574 if self.rev(p) < stopn:
575 continue
575 continue
576 if p not in reachable:
576 if p not in reachable:
577 reachable.add(p)
577 reachable.add(p)
578 visit.append(p)
578 visit.append(p)
579 return reachable
579 return reachable
580
580
581 def ancestors(self, *revs):
581 def ancestors(self, *revs):
582 'Generate the ancestors of revs using a breadth-first visit'
582 'Generate the ancestors of revs using a breadth-first visit'
583 visit = list(revs)
583 visit = list(revs)
584 seen = set([nullrev])
584 seen = set([nullrev])
585 while visit:
585 while visit:
586 for parent in self.parentrevs(visit.pop(0)):
586 for parent in self.parentrevs(visit.pop(0)):
587 if parent not in seen:
587 if parent not in seen:
588 visit.append(parent)
588 visit.append(parent)
589 seen.add(parent)
589 seen.add(parent)
590 yield parent
590 yield parent
591
591
592 def descendants(self, *revs):
592 def descendants(self, *revs):
593 'Generate the descendants of revs in topological order'
593 'Generate the descendants of revs in topological order'
594 seen = set(revs)
594 seen = set(revs)
595 for i in xrange(min(revs) + 1, len(self)):
595 for i in xrange(min(revs) + 1, len(self)):
596 for x in self.parentrevs(i):
596 for x in self.parentrevs(i):
597 if x != nullrev and x in seen:
597 if x != nullrev and x in seen:
598 seen.add(i)
598 seen.add(i)
599 yield i
599 yield i
600 break
600 break
601
601
602 def findmissing(self, common=None, heads=None):
602 def findmissing(self, common=None, heads=None):
603 '''
603 '''
604 returns the topologically sorted list of nodes from the set:
604 returns the topologically sorted list of nodes from the set:
605 missing = (ancestors(heads) \ ancestors(common))
605 missing = (ancestors(heads) \ ancestors(common))
606
606
607 where ancestors() is the set of ancestors from heads, heads included
607 where ancestors() is the set of ancestors from heads, heads included
608
608
609 if heads is None, the heads of the revlog are used
609 if heads is None, the heads of the revlog are used
610 if common is None, nullid is assumed to be a common node
610 if common is None, nullid is assumed to be a common node
611 '''
611 '''
612 if common is None:
612 if common is None:
613 common = [nullid]
613 common = [nullid]
614 if heads is None:
614 if heads is None:
615 heads = self.heads()
615 heads = self.heads()
616
616
617 common = [self.rev(n) for n in common]
617 common = [self.rev(n) for n in common]
618 heads = [self.rev(n) for n in heads]
618 heads = [self.rev(n) for n in heads]
619
619
620 # we want the ancestors, but inclusive
620 # we want the ancestors, but inclusive
621 has = set(self.ancestors(*common))
621 has = set(self.ancestors(*common))
622 has.add(nullrev)
622 has.add(nullrev)
623 has.update(common)
623 has.update(common)
624
624
625 # take all ancestors from heads that aren't in has
625 # take all ancestors from heads that aren't in has
626 missing = set()
626 missing = set()
627 visit = [r for r in heads if r not in has]
627 visit = [r for r in heads if r not in has]
628 while visit:
628 while visit:
629 r = visit.pop(0)
629 r = visit.pop(0)
630 if r in missing:
630 if r in missing:
631 continue
631 continue
632 else:
632 else:
633 missing.add(r)
633 missing.add(r)
634 for p in self.parentrevs(r):
634 for p in self.parentrevs(r):
635 if p not in has:
635 if p not in has:
636 visit.append(p)
636 visit.append(p)
637 missing = list(missing)
637 missing = list(missing)
638 missing.sort()
638 missing.sort()
639 return [self.node(r) for r in missing]
639 return [self.node(r) for r in missing]
640
640
641 def nodesbetween(self, roots=None, heads=None):
641 def nodesbetween(self, roots=None, heads=None):
642 """Return a tuple containing three elements. Elements 1 and 2 contain
642 """Return a tuple containing three elements. Elements 1 and 2 contain
643 a final list bases and heads after all the unreachable ones have been
643 a final list bases and heads after all the unreachable ones have been
644 pruned. Element 0 contains a topologically sorted list of all
644 pruned. Element 0 contains a topologically sorted list of all
645
645
646 nodes that satisfy these constraints:
646 nodes that satisfy these constraints:
647 1. All nodes must be descended from a node in roots (the nodes on
647 1. All nodes must be descended from a node in roots (the nodes on
648 roots are considered descended from themselves).
648 roots are considered descended from themselves).
649 2. All nodes must also be ancestors of a node in heads (the nodes in
649 2. All nodes must also be ancestors of a node in heads (the nodes in
650 heads are considered to be their own ancestors).
650 heads are considered to be their own ancestors).
651
651
652 If roots is unspecified, nullid is assumed as the only root.
652 If roots is unspecified, nullid is assumed as the only root.
653 If heads is unspecified, it is taken to be the output of the
653 If heads is unspecified, it is taken to be the output of the
654 heads method (i.e. a list of all nodes in the repository that
654 heads method (i.e. a list of all nodes in the repository that
655 have no children)."""
655 have no children)."""
656 nonodes = ([], [], [])
656 nonodes = ([], [], [])
657 if roots is not None:
657 if roots is not None:
658 roots = list(roots)
658 roots = list(roots)
659 if not roots:
659 if not roots:
660 return nonodes
660 return nonodes
661 lowestrev = min([self.rev(n) for n in roots])
661 lowestrev = min([self.rev(n) for n in roots])
662 else:
662 else:
663 roots = [nullid] # Everybody's a descendent of nullid
663 roots = [nullid] # Everybody's a descendent of nullid
664 lowestrev = nullrev
664 lowestrev = nullrev
665 if (lowestrev == nullrev) and (heads is None):
665 if (lowestrev == nullrev) and (heads is None):
666 # We want _all_ the nodes!
666 # We want _all_ the nodes!
667 return ([self.node(r) for r in self], [nullid], list(self.heads()))
667 return ([self.node(r) for r in self], [nullid], list(self.heads()))
668 if heads is None:
668 if heads is None:
669 # All nodes are ancestors, so the latest ancestor is the last
669 # All nodes are ancestors, so the latest ancestor is the last
670 # node.
670 # node.
671 highestrev = len(self) - 1
671 highestrev = len(self) - 1
672 # Set ancestors to None to signal that every node is an ancestor.
672 # Set ancestors to None to signal that every node is an ancestor.
673 ancestors = None
673 ancestors = None
674 # Set heads to an empty dictionary for later discovery of heads
674 # Set heads to an empty dictionary for later discovery of heads
675 heads = {}
675 heads = {}
676 else:
676 else:
677 heads = list(heads)
677 heads = list(heads)
678 if not heads:
678 if not heads:
679 return nonodes
679 return nonodes
680 ancestors = set()
680 ancestors = set()
681 # Turn heads into a dictionary so we can remove 'fake' heads.
681 # Turn heads into a dictionary so we can remove 'fake' heads.
682 # Also, later we will be using it to filter out the heads we can't
682 # Also, later we will be using it to filter out the heads we can't
683 # find from roots.
683 # find from roots.
684 heads = dict.fromkeys(heads, 0)
684 heads = dict.fromkeys(heads, 0)
685 # Start at the top and keep marking parents until we're done.
685 # Start at the top and keep marking parents until we're done.
686 nodestotag = set(heads)
686 nodestotag = set(heads)
687 # Remember where the top was so we can use it as a limit later.
687 # Remember where the top was so we can use it as a limit later.
688 highestrev = max([self.rev(n) for n in nodestotag])
688 highestrev = max([self.rev(n) for n in nodestotag])
689 while nodestotag:
689 while nodestotag:
690 # grab a node to tag
690 # grab a node to tag
691 n = nodestotag.pop()
691 n = nodestotag.pop()
692 # Never tag nullid
692 # Never tag nullid
693 if n == nullid:
693 if n == nullid:
694 continue
694 continue
695 # A node's revision number represents its place in a
695 # A node's revision number represents its place in a
696 # topologically sorted list of nodes.
696 # topologically sorted list of nodes.
697 r = self.rev(n)
697 r = self.rev(n)
698 if r >= lowestrev:
698 if r >= lowestrev:
699 if n not in ancestors:
699 if n not in ancestors:
700 # If we are possibly a descendent of one of the roots
700 # If we are possibly a descendent of one of the roots
701 # and we haven't already been marked as an ancestor
701 # and we haven't already been marked as an ancestor
702 ancestors.add(n) # Mark as ancestor
702 ancestors.add(n) # Mark as ancestor
703 # Add non-nullid parents to list of nodes to tag.
703 # Add non-nullid parents to list of nodes to tag.
704 nodestotag.update([p for p in self.parents(n) if
704 nodestotag.update([p for p in self.parents(n) if
705 p != nullid])
705 p != nullid])
706 elif n in heads: # We've seen it before, is it a fake head?
706 elif n in heads: # We've seen it before, is it a fake head?
707 # So it is, real heads should not be the ancestors of
707 # So it is, real heads should not be the ancestors of
708 # any other heads.
708 # any other heads.
709 heads.pop(n)
709 heads.pop(n)
710 if not ancestors:
710 if not ancestors:
711 return nonodes
711 return nonodes
712 # Now that we have our set of ancestors, we want to remove any
712 # Now that we have our set of ancestors, we want to remove any
713 # roots that are not ancestors.
713 # roots that are not ancestors.
714
714
715 # If one of the roots was nullid, everything is included anyway.
715 # If one of the roots was nullid, everything is included anyway.
716 if lowestrev > nullrev:
716 if lowestrev > nullrev:
717 # But, since we weren't, let's recompute the lowest rev to not
717 # But, since we weren't, let's recompute the lowest rev to not
718 # include roots that aren't ancestors.
718 # include roots that aren't ancestors.
719
719
720 # Filter out roots that aren't ancestors of heads
720 # Filter out roots that aren't ancestors of heads
721 roots = [n for n in roots if n in ancestors]
721 roots = [n for n in roots if n in ancestors]
722 # Recompute the lowest revision
722 # Recompute the lowest revision
723 if roots:
723 if roots:
724 lowestrev = min([self.rev(n) for n in roots])
724 lowestrev = min([self.rev(n) for n in roots])
725 else:
725 else:
726 # No more roots? Return empty list
726 # No more roots? Return empty list
727 return nonodes
727 return nonodes
728 else:
728 else:
729 # We are descending from nullid, and don't need to care about
729 # We are descending from nullid, and don't need to care about
730 # any other roots.
730 # any other roots.
731 lowestrev = nullrev
731 lowestrev = nullrev
732 roots = [nullid]
732 roots = [nullid]
733 # Transform our roots list into a set.
733 # Transform our roots list into a set.
734 descendents = set(roots)
734 descendents = set(roots)
735 # Also, keep the original roots so we can filter out roots that aren't
735 # Also, keep the original roots so we can filter out roots that aren't
736 # 'real' roots (i.e. are descended from other roots).
736 # 'real' roots (i.e. are descended from other roots).
737 roots = descendents.copy()
737 roots = descendents.copy()
738 # Our topologically sorted list of output nodes.
738 # Our topologically sorted list of output nodes.
739 orderedout = []
739 orderedout = []
740 # Don't start at nullid since we don't want nullid in our output list,
740 # Don't start at nullid since we don't want nullid in our output list,
741 # and if nullid shows up in descedents, empty parents will look like
741 # and if nullid shows up in descedents, empty parents will look like
742 # they're descendents.
742 # they're descendents.
743 for r in xrange(max(lowestrev, 0), highestrev + 1):
743 for r in xrange(max(lowestrev, 0), highestrev + 1):
744 n = self.node(r)
744 n = self.node(r)
745 isdescendent = False
745 isdescendent = False
746 if lowestrev == nullrev: # Everybody is a descendent of nullid
746 if lowestrev == nullrev: # Everybody is a descendent of nullid
747 isdescendent = True
747 isdescendent = True
748 elif n in descendents:
748 elif n in descendents:
749 # n is already a descendent
749 # n is already a descendent
750 isdescendent = True
750 isdescendent = True
751 # This check only needs to be done here because all the roots
751 # This check only needs to be done here because all the roots
752 # will start being marked is descendents before the loop.
752 # will start being marked is descendents before the loop.
753 if n in roots:
753 if n in roots:
754 # If n was a root, check if it's a 'real' root.
754 # If n was a root, check if it's a 'real' root.
755 p = tuple(self.parents(n))
755 p = tuple(self.parents(n))
756 # If any of its parents are descendents, it's not a root.
756 # If any of its parents are descendents, it's not a root.
757 if (p[0] in descendents) or (p[1] in descendents):
757 if (p[0] in descendents) or (p[1] in descendents):
758 roots.remove(n)
758 roots.remove(n)
759 else:
759 else:
760 p = tuple(self.parents(n))
760 p = tuple(self.parents(n))
761 # A node is a descendent if either of its parents are
761 # A node is a descendent if either of its parents are
762 # descendents. (We seeded the dependents list with the roots
762 # descendents. (We seeded the dependents list with the roots
763 # up there, remember?)
763 # up there, remember?)
764 if (p[0] in descendents) or (p[1] in descendents):
764 if (p[0] in descendents) or (p[1] in descendents):
765 descendents.add(n)
765 descendents.add(n)
766 isdescendent = True
766 isdescendent = True
767 if isdescendent and ((ancestors is None) or (n in ancestors)):
767 if isdescendent and ((ancestors is None) or (n in ancestors)):
768 # Only include nodes that are both descendents and ancestors.
768 # Only include nodes that are both descendents and ancestors.
769 orderedout.append(n)
769 orderedout.append(n)
770 if (ancestors is not None) and (n in heads):
770 if (ancestors is not None) and (n in heads):
771 # We're trying to figure out which heads are reachable
771 # We're trying to figure out which heads are reachable
772 # from roots.
772 # from roots.
773 # Mark this head as having been reached
773 # Mark this head as having been reached
774 heads[n] = 1
774 heads[n] = 1
775 elif ancestors is None:
775 elif ancestors is None:
776 # Otherwise, we're trying to discover the heads.
776 # Otherwise, we're trying to discover the heads.
777 # Assume this is a head because if it isn't, the next step
777 # Assume this is a head because if it isn't, the next step
778 # will eventually remove it.
778 # will eventually remove it.
779 heads[n] = 1
779 heads[n] = 1
780 # But, obviously its parents aren't.
780 # But, obviously its parents aren't.
781 for p in self.parents(n):
781 for p in self.parents(n):
782 heads.pop(p, None)
782 heads.pop(p, None)
783 heads = [n for n in heads.iterkeys() if heads[n] != 0]
783 heads = [n for n in heads.iterkeys() if heads[n] != 0]
784 roots = list(roots)
784 roots = list(roots)
785 assert orderedout
785 assert orderedout
786 assert roots
786 assert roots
787 assert heads
787 assert heads
788 return (orderedout, roots, heads)
788 return (orderedout, roots, heads)
789
789
790 def heads(self, start=None, stop=None):
790 def heads(self, start=None, stop=None):
791 """return the list of all nodes that have no children
791 """return the list of all nodes that have no children
792
792
793 if start is specified, only heads that are descendants of
793 if start is specified, only heads that are descendants of
794 start will be returned
794 start will be returned
795 if stop is specified, it will consider all the revs from stop
795 if stop is specified, it will consider all the revs from stop
796 as if they had no children
796 as if they had no children
797 """
797 """
798 if start is None and stop is None:
798 if start is None and stop is None:
799 count = len(self)
799 count = len(self)
800 if not count:
800 if not count:
801 return [nullid]
801 return [nullid]
802 ishead = [1] * (count + 1)
802 ishead = [1] * (count + 1)
803 index = self.index
803 index = self.index
804 for r in xrange(count):
804 for r in xrange(count):
805 e = index[r]
805 e = index[r]
806 ishead[e[5]] = ishead[e[6]] = 0
806 ishead[e[5]] = ishead[e[6]] = 0
807 return [self.node(r) for r in xrange(count) if ishead[r]]
807 return [self.node(r) for r in xrange(count) if ishead[r]]
808
808
809 if start is None:
809 if start is None:
810 start = nullid
810 start = nullid
811 if stop is None:
811 if stop is None:
812 stop = []
812 stop = []
813 stoprevs = set([self.rev(n) for n in stop])
813 stoprevs = set([self.rev(n) for n in stop])
814 startrev = self.rev(start)
814 startrev = self.rev(start)
815 reachable = set((startrev,))
815 reachable = set((startrev,))
816 heads = set((startrev,))
816 heads = set((startrev,))
817
817
818 parentrevs = self.parentrevs
818 parentrevs = self.parentrevs
819 for r in xrange(startrev + 1, len(self)):
819 for r in xrange(startrev + 1, len(self)):
820 for p in parentrevs(r):
820 for p in parentrevs(r):
821 if p in reachable:
821 if p in reachable:
822 if r not in stoprevs:
822 if r not in stoprevs:
823 reachable.add(r)
823 reachable.add(r)
824 heads.add(r)
824 heads.add(r)
825 if p in heads and p not in stoprevs:
825 if p in heads and p not in stoprevs:
826 heads.remove(p)
826 heads.remove(p)
827
827
828 return [self.node(r) for r in heads]
828 return [self.node(r) for r in heads]
829
829
830 def children(self, node):
830 def children(self, node):
831 """find the children of a given node"""
831 """find the children of a given node"""
832 c = []
832 c = []
833 p = self.rev(node)
833 p = self.rev(node)
834 for r in range(p + 1, len(self)):
834 for r in range(p + 1, len(self)):
835 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
835 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
836 if prevs:
836 if prevs:
837 for pr in prevs:
837 for pr in prevs:
838 if pr == p:
838 if pr == p:
839 c.append(self.node(r))
839 c.append(self.node(r))
840 elif p == nullrev:
840 elif p == nullrev:
841 c.append(self.node(r))
841 c.append(self.node(r))
842 return c
842 return c
843
843
844 def _match(self, id):
844 def _match(self, id):
845 if isinstance(id, (long, int)):
845 if isinstance(id, (long, int)):
846 # rev
846 # rev
847 return self.node(id)
847 return self.node(id)
848 if len(id) == 20:
848 if len(id) == 20:
849 # possibly a binary node
849 # possibly a binary node
850 # odds of a binary node being all hex in ASCII are 1 in 10**25
850 # odds of a binary node being all hex in ASCII are 1 in 10**25
851 try:
851 try:
852 node = id
852 node = id
853 self.rev(node) # quick search the index
853 self.rev(node) # quick search the index
854 return node
854 return node
855 except LookupError:
855 except LookupError:
856 pass # may be partial hex id
856 pass # may be partial hex id
857 try:
857 try:
858 # str(rev)
858 # str(rev)
859 rev = int(id)
859 rev = int(id)
860 if str(rev) != id:
860 if str(rev) != id:
861 raise ValueError
861 raise ValueError
862 if rev < 0:
862 if rev < 0:
863 rev = len(self) + rev
863 rev = len(self) + rev
864 if rev < 0 or rev >= len(self):
864 if rev < 0 or rev >= len(self):
865 raise ValueError
865 raise ValueError
866 return self.node(rev)
866 return self.node(rev)
867 except (ValueError, OverflowError):
867 except (ValueError, OverflowError):
868 pass
868 pass
869 if len(id) == 40:
869 if len(id) == 40:
870 try:
870 try:
871 # a full hex nodeid?
871 # a full hex nodeid?
872 node = bin(id)
872 node = bin(id)
873 self.rev(node)
873 self.rev(node)
874 return node
874 return node
875 except (TypeError, LookupError):
875 except (TypeError, LookupError):
876 pass
876 pass
877
877
878 def _partialmatch(self, id):
878 def _partialmatch(self, id):
879 if len(id) < 40:
879 if len(id) < 40:
880 try:
880 try:
881 # hex(node)[:...]
881 # hex(node)[:...]
882 l = len(id) // 2 # grab an even number of digits
882 l = len(id) // 2 # grab an even number of digits
883 bin_id = bin(id[:l*2])
883 bin_id = bin(id[:l*2])
884 nl = [n for n in self.nodemap if n[:l] == bin_id]
884 nl = [n for n in self.nodemap if n[:l] == bin_id]
885 nl = [n for n in nl if hex(n).startswith(id)]
885 nl = [n for n in nl if hex(n).startswith(id)]
886 if len(nl) > 0:
886 if len(nl) > 0:
887 if len(nl) == 1:
887 if len(nl) == 1:
888 return nl[0]
888 return nl[0]
889 raise LookupError(id, self.indexfile,
889 raise LookupError(id, self.indexfile,
890 _('ambiguous identifier'))
890 _('ambiguous identifier'))
891 return None
891 return None
892 except TypeError:
892 except TypeError:
893 pass
893 pass
894
894
895 def lookup(self, id):
895 def lookup(self, id):
896 """locate a node based on:
896 """locate a node based on:
897 - revision number or str(revision number)
897 - revision number or str(revision number)
898 - nodeid or subset of hex nodeid
898 - nodeid or subset of hex nodeid
899 """
899 """
900 n = self._match(id)
900 n = self._match(id)
901 if n is not None:
901 if n is not None:
902 return n
902 return n
903 n = self._partialmatch(id)
903 n = self._partialmatch(id)
904 if n:
904 if n:
905 return n
905 return n
906
906
907 raise LookupError(id, self.indexfile, _('no match found'))
907 raise LookupError(id, self.indexfile, _('no match found'))
908
908
909 def cmp(self, node, text):
909 def cmp(self, node, text):
910 """compare text with a given file revision"""
910 """compare text with a given file revision"""
911 p1, p2 = self.parents(node)
911 p1, p2 = self.parents(node)
912 return hash(text, p1, p2) != node
912 return hash(text, p1, p2) != node
913
913
914 def _addchunk(self, offset, data):
914 def _addchunk(self, offset, data):
915 o, d = self._chunkcache
915 o, d = self._chunkcache
916 # try to add to existing cache
916 # try to add to existing cache
917 if o + len(d) == offset and len(d) + len(data) < _prereadsize:
917 if o + len(d) == offset and len(d) + len(data) < _prereadsize:
918 self._chunkcache = o, d + data
918 self._chunkcache = o, d + data
919 else:
919 else:
920 self._chunkcache = offset, data
920 self._chunkcache = offset, data
921
921
922 def _loadchunk(self, offset, length):
922 def _loadchunk(self, offset, length):
923 if self._inline:
923 if self._inline:
924 df = self.opener(self.indexfile)
924 df = self.opener(self.indexfile)
925 else:
925 else:
926 df = self.opener(self.datafile)
926 df = self.opener(self.datafile)
927
927
928 readahead = max(65536, length)
928 readahead = max(65536, length)
929 df.seek(offset)
929 df.seek(offset)
930 d = df.read(readahead)
930 d = df.read(readahead)
931 self._addchunk(offset, d)
931 self._addchunk(offset, d)
932 if readahead > length:
932 if readahead > length:
933 return d[:length]
933 return d[:length]
934 return d
934 return d
935
935
936 def _getchunk(self, offset, length):
936 def _getchunk(self, offset, length):
937 o, d = self._chunkcache
937 o, d = self._chunkcache
938 l = len(d)
938 l = len(d)
939
939
940 # is it in the cache?
940 # is it in the cache?
941 cachestart = offset - o
941 cachestart = offset - o
942 cacheend = cachestart + length
942 cacheend = cachestart + length
943 if cachestart >= 0 and cacheend <= l:
943 if cachestart >= 0 and cacheend <= l:
944 if cachestart == 0 and cacheend == l:
944 if cachestart == 0 and cacheend == l:
945 return d # avoid a copy
945 return d # avoid a copy
946 return d[cachestart:cacheend]
946 return d[cachestart:cacheend]
947
947
948 return self._loadchunk(offset, length)
948 return self._loadchunk(offset, length)
949
949
950 def _chunkraw(self, startrev, endrev):
950 def _chunkraw(self, startrev, endrev):
951 start = self.start(startrev)
951 start = self.start(startrev)
952 length = self.end(endrev) - start
952 length = self.end(endrev) - start
953 if self._inline:
953 if self._inline:
954 start += (startrev + 1) * self._io.size
954 start += (startrev + 1) * self._io.size
955 return self._getchunk(start, length)
955 return self._getchunk(start, length)
956
956
957 def _chunk(self, rev):
957 def _chunk(self, rev):
958 return decompress(self._chunkraw(rev, rev))
958 return decompress(self._chunkraw(rev, rev))
959
959
960 def _chunkclear(self):
960 def _chunkclear(self):
961 self._chunkcache = (0, '')
961 self._chunkcache = (0, '')
962
962
963 def revdiff(self, rev1, rev2):
963 def revdiff(self, rev1, rev2):
964 """return or calculate a delta between two revisions"""
964 """return or calculate a delta between two revisions"""
965 if rev1 + 1 == rev2 and self.base(rev1) == self.base(rev2):
965 if rev1 + 1 == rev2 and self.base(rev1) == self.base(rev2):
966 return self._chunk(rev2)
966 return self._chunk(rev2)
967
967
968 return mdiff.textdiff(self.revision(self.node(rev1)),
968 return mdiff.textdiff(self.revision(self.node(rev1)),
969 self.revision(self.node(rev2)))
969 self.revision(self.node(rev2)))
970
970
971 def revision(self, node):
971 def revision(self, node):
972 """return an uncompressed revision of a given node"""
972 """return an uncompressed revision of a given node"""
973 if node == nullid:
973 if node == nullid:
974 return ""
974 return ""
975 if self._cache and self._cache[0] == node:
975 if self._cache and self._cache[0] == node:
976 return self._cache[2]
976 return self._cache[2]
977
977
978 # look up what we need to read
978 # look up what we need to read
979 text = None
979 text = None
980 rev = self.rev(node)
980 rev = self.rev(node)
981 base = self.base(rev)
981 base = self.base(rev)
982
982
983 # check rev flags
983 # check rev flags
984 if self.index[rev][0] & 0xFFFF:
984 if self.index[rev][0] & 0xFFFF:
985 raise RevlogError(_('incompatible revision flag %x') %
985 raise RevlogError(_('incompatible revision flag %x') %
986 (self.index[rev][0] & 0xFFFF))
986 (self.index[rev][0] & 0xFFFF))
987
987
988 # do we have useful data cached?
988 # do we have useful data cached?
989 if self._cache and self._cache[1] >= base and self._cache[1] < rev:
989 if self._cache and self._cache[1] >= base and self._cache[1] < rev:
990 base = self._cache[1]
990 base = self._cache[1]
991 text = self._cache[2]
991 text = self._cache[2]
992
992
993 self._loadindex(base, rev + 1)
993 self._loadindex(base, rev + 1)
994 self._chunkraw(base, rev)
994 self._chunkraw(base, rev)
995 if text is None:
995 if text is None:
996 text = self._chunk(base)
996 text = self._chunk(base)
997
997
998 bins = [self._chunk(r) for r in xrange(base + 1, rev + 1)]
998 bins = [self._chunk(r) for r in xrange(base + 1, rev + 1)]
999 text = mdiff.patches(text, bins)
999 text = mdiff.patches(text, bins)
1000 p1, p2 = self.parents(node)
1000 p1, p2 = self.parents(node)
1001 if node != hash(text, p1, p2):
1001 if node != hash(text, p1, p2):
1002 raise RevlogError(_("integrity check failed on %s:%d")
1002 raise RevlogError(_("integrity check failed on %s:%d")
1003 % (self.indexfile, rev))
1003 % (self.indexfile, rev))
1004
1004
1005 self._cache = (node, rev, text)
1005 self._cache = (node, rev, text)
1006 return text
1006 return text
1007
1007
1008 def checkinlinesize(self, tr, fp=None):
1008 def checkinlinesize(self, tr, fp=None):
1009 if not self._inline or (self.start(-2) + self.length(-2)) < 131072:
1009 if not self._inline or (self.start(-2) + self.length(-2)) < 131072:
1010 return
1010 return
1011
1011
1012 trinfo = tr.find(self.indexfile)
1012 trinfo = tr.find(self.indexfile)
1013 if trinfo is None:
1013 if trinfo is None:
1014 raise RevlogError(_("%s not found in the transaction")
1014 raise RevlogError(_("%s not found in the transaction")
1015 % self.indexfile)
1015 % self.indexfile)
1016
1016
1017 trindex = trinfo[2]
1017 trindex = trinfo[2]
1018 dataoff = self.start(trindex)
1018 dataoff = self.start(trindex)
1019
1019
1020 tr.add(self.datafile, dataoff)
1020 tr.add(self.datafile, dataoff)
1021
1021
1022 if fp:
1022 if fp:
1023 fp.flush()
1023 fp.flush()
1024 fp.close()
1024 fp.close()
1025
1025
1026 df = self.opener(self.datafile, 'w')
1026 df = self.opener(self.datafile, 'w')
1027 try:
1027 try:
1028 for r in self:
1028 for r in self:
1029 df.write(self._chunkraw(r, r))
1029 df.write(self._chunkraw(r, r))
1030 finally:
1030 finally:
1031 df.close()
1031 df.close()
1032
1032
1033 fp = self.opener(self.indexfile, 'w', atomictemp=True)
1033 fp = self.opener(self.indexfile, 'w', atomictemp=True)
1034 self.version &= ~(REVLOGNGINLINEDATA)
1034 self.version &= ~(REVLOGNGINLINEDATA)
1035 self._inline = False
1035 self._inline = False
1036 for i in self:
1036 for i in self:
1037 e = self._io.packentry(self.index[i], self.node, self.version, i)
1037 e = self._io.packentry(self.index[i], self.node, self.version, i)
1038 fp.write(e)
1038 fp.write(e)
1039
1039
1040 # if we don't call rename, the temp file will never replace the
1040 # if we don't call rename, the temp file will never replace the
1041 # real index
1041 # real index
1042 fp.rename()
1042 fp.rename()
1043
1043
1044 tr.replace(self.indexfile, trindex * self._io.size)
1044 tr.replace(self.indexfile, trindex * self._io.size)
1045 self._chunkclear()
1045 self._chunkclear()
1046
1046
1047 def addrevision(self, text, transaction, link, p1, p2, d=None):
1047 def addrevision(self, text, transaction, link, p1, p2, d=None):
1048 """add a revision to the log
1048 """add a revision to the log
1049
1049
1050 text - the revision data to add
1050 text - the revision data to add
1051 transaction - the transaction object used for rollback
1051 transaction - the transaction object used for rollback
1052 link - the linkrev data to add
1052 link - the linkrev data to add
1053 p1, p2 - the parent nodeids of the revision
1053 p1, p2 - the parent nodeids of the revision
1054 d - an optional precomputed delta
1054 d - an optional precomputed delta
1055 """
1055 """
1056 dfh = None
1056 dfh = None
1057 if not self._inline:
1057 if not self._inline:
1058 dfh = self.opener(self.datafile, "a")
1058 dfh = self.opener(self.datafile, "a")
1059 ifh = self.opener(self.indexfile, "a+")
1059 ifh = self.opener(self.indexfile, "a+")
1060 try:
1060 try:
1061 return self._addrevision(text, transaction, link, p1, p2, d, ifh, dfh)
1061 return self._addrevision(text, transaction, link, p1, p2, d, ifh, dfh)
1062 finally:
1062 finally:
1063 if dfh:
1063 if dfh:
1064 dfh.close()
1064 dfh.close()
1065 ifh.close()
1065 ifh.close()
1066
1066
1067 def _addrevision(self, text, transaction, link, p1, p2, d, ifh, dfh):
1067 def _addrevision(self, text, transaction, link, p1, p2, d, ifh, dfh):
1068 node = hash(text, p1, p2)
1068 node = hash(text, p1, p2)
1069 if node in self.nodemap:
1069 if node in self.nodemap:
1070 return node
1070 return node
1071
1071
1072 curr = len(self)
1072 curr = len(self)
1073 prev = curr - 1
1073 prev = curr - 1
1074 base = self.base(prev)
1074 base = self.base(prev)
1075 offset = self.end(prev)
1075 offset = self.end(prev)
1076
1076
1077 if curr:
1077 if curr:
1078 if not d:
1078 if not d:
1079 ptext = self.revision(self.node(prev))
1079 ptext = self.revision(self.node(prev))
1080 d = mdiff.textdiff(ptext, text)
1080 d = mdiff.textdiff(ptext, text)
1081 data = compress(d)
1081 data = compress(d)
1082 l = len(data[1]) + len(data[0])
1082 l = len(data[1]) + len(data[0])
1083 dist = l + offset - self.start(base)
1083 dist = l + offset - self.start(base)
1084
1084
1085 # full versions are inserted when the needed deltas
1085 # full versions are inserted when the needed deltas
1086 # become comparable to the uncompressed text
1086 # become comparable to the uncompressed text
1087 if not curr or dist > len(text) * 2:
1087 if not curr or dist > len(text) * 2:
1088 data = compress(text)
1088 data = compress(text)
1089 l = len(data[1]) + len(data[0])
1089 l = len(data[1]) + len(data[0])
1090 base = curr
1090 base = curr
1091
1091
1092 e = (offset_type(offset, 0), l, len(text),
1092 e = (offset_type(offset, 0), l, len(text),
1093 base, link, self.rev(p1), self.rev(p2), node)
1093 base, link, self.rev(p1), self.rev(p2), node)
1094 self.index.insert(-1, e)
1094 self.index.insert(-1, e)
1095 self.nodemap[node] = curr
1095 self.nodemap[node] = curr
1096
1096
1097 entry = self._io.packentry(e, self.node, self.version, curr)
1097 entry = self._io.packentry(e, self.node, self.version, curr)
1098 if not self._inline:
1098 if not self._inline:
1099 transaction.add(self.datafile, offset)
1099 transaction.add(self.datafile, offset)
1100 transaction.add(self.indexfile, curr * len(entry))
1100 transaction.add(self.indexfile, curr * len(entry))
1101 if data[0]:
1101 if data[0]:
1102 dfh.write(data[0])
1102 dfh.write(data[0])
1103 dfh.write(data[1])
1103 dfh.write(data[1])
1104 dfh.flush()
1104 dfh.flush()
1105 ifh.write(entry)
1105 ifh.write(entry)
1106 else:
1106 else:
1107 offset += curr * self._io.size
1107 offset += curr * self._io.size
1108 transaction.add(self.indexfile, offset, curr)
1108 transaction.add(self.indexfile, offset, curr)
1109 ifh.write(entry)
1109 ifh.write(entry)
1110 ifh.write(data[0])
1110 ifh.write(data[0])
1111 ifh.write(data[1])
1111 ifh.write(data[1])
1112 self.checkinlinesize(transaction, ifh)
1112 self.checkinlinesize(transaction, ifh)
1113
1113
1114 if type(text) == str: # only accept immutable objects
1114 if type(text) == str: # only accept immutable objects
1115 self._cache = (node, curr, text)
1115 self._cache = (node, curr, text)
1116 return node
1116 return node
1117
1117
1118 def ancestor(self, a, b):
1118 def ancestor(self, a, b):
1119 """calculate the least common ancestor of nodes a and b"""
1119 """calculate the least common ancestor of nodes a and b"""
1120
1120
1121 def parents(rev):
1121 def parents(rev):
1122 return [p for p in self.parentrevs(rev) if p != nullrev]
1122 return [p for p in self.parentrevs(rev) if p != nullrev]
1123
1123
1124 c = ancestor.ancestor(self.rev(a), self.rev(b), parents)
1124 c = ancestor.ancestor(self.rev(a), self.rev(b), parents)
1125 if c is None:
1125 if c is None:
1126 return nullid
1126 return nullid
1127
1127
1128 return self.node(c)
1128 return self.node(c)
1129
1129
1130 def group(self, nodelist, lookup, infocollect=None):
1130 def group(self, nodelist, lookup, infocollect=None):
1131 """calculate a delta group
1131 """Calculate a delta group, yielding a sequence of changegroup chunks
1132 (strings).
1132
1133
1133 Given a list of changeset revs, return a set of deltas and
1134 Given a list of changeset revs, return a set of deltas and
1134 metadata corresponding to nodes. the first delta is
1135 metadata corresponding to nodes. the first delta is
1135 parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
1136 parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
1136 have this parent as it has all history before these
1137 have this parent as it has all history before these
1137 changesets. parent is parent[0]
1138 changesets. parent is parent[0]
1138 """
1139 """
1139
1140
1140 revs = [self.rev(n) for n in nodelist]
1141 revs = [self.rev(n) for n in nodelist]
1141
1142
1142 # if we don't have any revisions touched by these changesets, bail
1143 # if we don't have any revisions touched by these changesets, bail
1143 if not revs:
1144 if not revs:
1144 yield changegroup.closechunk()
1145 yield changegroup.closechunk()
1145 return
1146 return
1146
1147
1147 # add the parent of the first rev
1148 # add the parent of the first rev
1148 p = self.parentrevs(revs[0])[0]
1149 p = self.parentrevs(revs[0])[0]
1149 revs.insert(0, p)
1150 revs.insert(0, p)
1150
1151
1151 # build deltas
1152 # build deltas
1152 for d in xrange(len(revs) - 1):
1153 for d in xrange(len(revs) - 1):
1153 a, b = revs[d], revs[d + 1]
1154 a, b = revs[d], revs[d + 1]
1154 nb = self.node(b)
1155 nb = self.node(b)
1155
1156
1156 if infocollect is not None:
1157 if infocollect is not None:
1157 infocollect(nb)
1158 infocollect(nb)
1158
1159
1159 p = self.parents(nb)
1160 p = self.parents(nb)
1160 meta = nb + p[0] + p[1] + lookup(nb)
1161 meta = nb + p[0] + p[1] + lookup(nb)
1161 if a == -1:
1162 if a == -1:
1162 d = self.revision(nb)
1163 d = self.revision(nb)
1163 meta += mdiff.trivialdiffheader(len(d))
1164 meta += mdiff.trivialdiffheader(len(d))
1164 else:
1165 else:
1165 d = self.revdiff(a, b)
1166 d = self.revdiff(a, b)
1166 yield changegroup.chunkheader(len(meta) + len(d))
1167 yield changegroup.chunkheader(len(meta) + len(d))
1167 yield meta
1168 yield meta
1168 if len(d) > 2**20:
1169 if len(d) > 2**20:
1169 pos = 0
1170 pos = 0
1170 while pos < len(d):
1171 while pos < len(d):
1171 pos2 = pos + 2 ** 18
1172 pos2 = pos + 2 ** 18
1172 yield d[pos:pos2]
1173 yield d[pos:pos2]
1173 pos = pos2
1174 pos = pos2
1174 else:
1175 else:
1175 yield d
1176 yield d
1176
1177
1177 yield changegroup.closechunk()
1178 yield changegroup.closechunk()
1178
1179
1179 def addgroup(self, revs, linkmapper, transaction):
1180 def addgroup(self, revs, linkmapper, transaction):
1180 """
1181 """
1181 add a delta group
1182 add a delta group
1182
1183
1183 given a set of deltas, add them to the revision log. the
1184 given a set of deltas, add them to the revision log. the
1184 first delta is against its parent, which should be in our
1185 first delta is against its parent, which should be in our
1185 log, the rest are against the previous delta.
1186 log, the rest are against the previous delta.
1186 """
1187 """
1187
1188
1188 #track the base of the current delta log
1189 #track the base of the current delta log
1189 r = len(self)
1190 r = len(self)
1190 t = r - 1
1191 t = r - 1
1191 node = None
1192 node = None
1192
1193
1193 base = prev = nullrev
1194 base = prev = nullrev
1194 start = end = textlen = 0
1195 start = end = textlen = 0
1195 if r:
1196 if r:
1196 end = self.end(t)
1197 end = self.end(t)
1197
1198
1198 ifh = self.opener(self.indexfile, "a+")
1199 ifh = self.opener(self.indexfile, "a+")
1199 isize = r * self._io.size
1200 isize = r * self._io.size
1200 if self._inline:
1201 if self._inline:
1201 transaction.add(self.indexfile, end + isize, r)
1202 transaction.add(self.indexfile, end + isize, r)
1202 dfh = None
1203 dfh = None
1203 else:
1204 else:
1204 transaction.add(self.indexfile, isize, r)
1205 transaction.add(self.indexfile, isize, r)
1205 transaction.add(self.datafile, end)
1206 transaction.add(self.datafile, end)
1206 dfh = self.opener(self.datafile, "a")
1207 dfh = self.opener(self.datafile, "a")
1207
1208
1208 try:
1209 try:
1209 # loop through our set of deltas
1210 # loop through our set of deltas
1210 chain = None
1211 chain = None
1211 for chunk in revs:
1212 for chunk in revs:
1212 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
1213 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
1213 link = linkmapper(cs)
1214 link = linkmapper(cs)
1214 if node in self.nodemap:
1215 if node in self.nodemap:
1215 # this can happen if two branches make the same change
1216 # this can happen if two branches make the same change
1216 chain = node
1217 chain = node
1217 continue
1218 continue
1218 delta = buffer(chunk, 80)
1219 delta = buffer(chunk, 80)
1219 del chunk
1220 del chunk
1220
1221
1221 for p in (p1, p2):
1222 for p in (p1, p2):
1222 if not p in self.nodemap:
1223 if not p in self.nodemap:
1223 raise LookupError(p, self.indexfile, _('unknown parent'))
1224 raise LookupError(p, self.indexfile, _('unknown parent'))
1224
1225
1225 if not chain:
1226 if not chain:
1226 # retrieve the parent revision of the delta chain
1227 # retrieve the parent revision of the delta chain
1227 chain = p1
1228 chain = p1
1228 if not chain in self.nodemap:
1229 if not chain in self.nodemap:
1229 raise LookupError(chain, self.indexfile, _('unknown base'))
1230 raise LookupError(chain, self.indexfile, _('unknown base'))
1230
1231
1231 # full versions are inserted when the needed deltas become
1232 # full versions are inserted when the needed deltas become
1232 # comparable to the uncompressed text or when the previous
1233 # comparable to the uncompressed text or when the previous
1233 # version is not the one we have a delta against. We use
1234 # version is not the one we have a delta against. We use
1234 # the size of the previous full rev as a proxy for the
1235 # the size of the previous full rev as a proxy for the
1235 # current size.
1236 # current size.
1236
1237
1237 if chain == prev:
1238 if chain == prev:
1238 cdelta = compress(delta)
1239 cdelta = compress(delta)
1239 cdeltalen = len(cdelta[0]) + len(cdelta[1])
1240 cdeltalen = len(cdelta[0]) + len(cdelta[1])
1240 textlen = mdiff.patchedsize(textlen, delta)
1241 textlen = mdiff.patchedsize(textlen, delta)
1241
1242
1242 if chain != prev or (end - start + cdeltalen) > textlen * 2:
1243 if chain != prev or (end - start + cdeltalen) > textlen * 2:
1243 # flush our writes here so we can read it in revision
1244 # flush our writes here so we can read it in revision
1244 if dfh:
1245 if dfh:
1245 dfh.flush()
1246 dfh.flush()
1246 ifh.flush()
1247 ifh.flush()
1247 text = self.revision(chain)
1248 text = self.revision(chain)
1248 if len(text) == 0:
1249 if len(text) == 0:
1249 # skip over trivial delta header
1250 # skip over trivial delta header
1250 text = buffer(delta, 12)
1251 text = buffer(delta, 12)
1251 else:
1252 else:
1252 text = mdiff.patches(text, [delta])
1253 text = mdiff.patches(text, [delta])
1253 del delta
1254 del delta
1254 chk = self._addrevision(text, transaction, link, p1, p2, None,
1255 chk = self._addrevision(text, transaction, link, p1, p2, None,
1255 ifh, dfh)
1256 ifh, dfh)
1256 if not dfh and not self._inline:
1257 if not dfh and not self._inline:
1257 # addrevision switched from inline to conventional
1258 # addrevision switched from inline to conventional
1258 # reopen the index
1259 # reopen the index
1259 dfh = self.opener(self.datafile, "a")
1260 dfh = self.opener(self.datafile, "a")
1260 ifh = self.opener(self.indexfile, "a")
1261 ifh = self.opener(self.indexfile, "a")
1261 if chk != node:
1262 if chk != node:
1262 raise RevlogError(_("consistency error adding group"))
1263 raise RevlogError(_("consistency error adding group"))
1263 textlen = len(text)
1264 textlen = len(text)
1264 else:
1265 else:
1265 e = (offset_type(end, 0), cdeltalen, textlen, base,
1266 e = (offset_type(end, 0), cdeltalen, textlen, base,
1266 link, self.rev(p1), self.rev(p2), node)
1267 link, self.rev(p1), self.rev(p2), node)
1267 self.index.insert(-1, e)
1268 self.index.insert(-1, e)
1268 self.nodemap[node] = r
1269 self.nodemap[node] = r
1269 entry = self._io.packentry(e, self.node, self.version, r)
1270 entry = self._io.packentry(e, self.node, self.version, r)
1270 if self._inline:
1271 if self._inline:
1271 ifh.write(entry)
1272 ifh.write(entry)
1272 ifh.write(cdelta[0])
1273 ifh.write(cdelta[0])
1273 ifh.write(cdelta[1])
1274 ifh.write(cdelta[1])
1274 self.checkinlinesize(transaction, ifh)
1275 self.checkinlinesize(transaction, ifh)
1275 if not self._inline:
1276 if not self._inline:
1276 dfh = self.opener(self.datafile, "a")
1277 dfh = self.opener(self.datafile, "a")
1277 ifh = self.opener(self.indexfile, "a")
1278 ifh = self.opener(self.indexfile, "a")
1278 else:
1279 else:
1279 dfh.write(cdelta[0])
1280 dfh.write(cdelta[0])
1280 dfh.write(cdelta[1])
1281 dfh.write(cdelta[1])
1281 ifh.write(entry)
1282 ifh.write(entry)
1282
1283
1283 t, r, chain, prev = r, r + 1, node, node
1284 t, r, chain, prev = r, r + 1, node, node
1284 base = self.base(t)
1285 base = self.base(t)
1285 start = self.start(base)
1286 start = self.start(base)
1286 end = self.end(t)
1287 end = self.end(t)
1287 finally:
1288 finally:
1288 if dfh:
1289 if dfh:
1289 dfh.close()
1290 dfh.close()
1290 ifh.close()
1291 ifh.close()
1291
1292
1292 return node
1293 return node
1293
1294
1294 def strip(self, minlink, transaction):
1295 def strip(self, minlink, transaction):
1295 """truncate the revlog on the first revision with a linkrev >= minlink
1296 """truncate the revlog on the first revision with a linkrev >= minlink
1296
1297
1297 This function is called when we're stripping revision minlink and
1298 This function is called when we're stripping revision minlink and
1298 its descendants from the repository.
1299 its descendants from the repository.
1299
1300
1300 We have to remove all revisions with linkrev >= minlink, because
1301 We have to remove all revisions with linkrev >= minlink, because
1301 the equivalent changelog revisions will be renumbered after the
1302 the equivalent changelog revisions will be renumbered after the
1302 strip.
1303 strip.
1303
1304
1304 So we truncate the revlog on the first of these revisions, and
1305 So we truncate the revlog on the first of these revisions, and
1305 trust that the caller has saved the revisions that shouldn't be
1306 trust that the caller has saved the revisions that shouldn't be
1306 removed and that it'll readd them after this truncation.
1307 removed and that it'll readd them after this truncation.
1307 """
1308 """
1308 if len(self) == 0:
1309 if len(self) == 0:
1309 return
1310 return
1310
1311
1311 if isinstance(self.index, lazyindex):
1312 if isinstance(self.index, lazyindex):
1312 self._loadindexmap()
1313 self._loadindexmap()
1313
1314
1314 for rev in self:
1315 for rev in self:
1315 if self.index[rev][4] >= minlink:
1316 if self.index[rev][4] >= minlink:
1316 break
1317 break
1317 else:
1318 else:
1318 return
1319 return
1319
1320
1320 # first truncate the files on disk
1321 # first truncate the files on disk
1321 end = self.start(rev)
1322 end = self.start(rev)
1322 if not self._inline:
1323 if not self._inline:
1323 transaction.add(self.datafile, end)
1324 transaction.add(self.datafile, end)
1324 end = rev * self._io.size
1325 end = rev * self._io.size
1325 else:
1326 else:
1326 end += rev * self._io.size
1327 end += rev * self._io.size
1327
1328
1328 transaction.add(self.indexfile, end)
1329 transaction.add(self.indexfile, end)
1329
1330
1330 # then reset internal state in memory to forget those revisions
1331 # then reset internal state in memory to forget those revisions
1331 self._cache = None
1332 self._cache = None
1332 self._chunkclear()
1333 self._chunkclear()
1333 for x in xrange(rev, len(self)):
1334 for x in xrange(rev, len(self)):
1334 del self.nodemap[self.node(x)]
1335 del self.nodemap[self.node(x)]
1335
1336
1336 del self.index[rev:-1]
1337 del self.index[rev:-1]
1337
1338
1338 def checksize(self):
1339 def checksize(self):
1339 expected = 0
1340 expected = 0
1340 if len(self):
1341 if len(self):
1341 expected = max(0, self.end(len(self) - 1))
1342 expected = max(0, self.end(len(self) - 1))
1342
1343
1343 try:
1344 try:
1344 f = self.opener(self.datafile)
1345 f = self.opener(self.datafile)
1345 f.seek(0, 2)
1346 f.seek(0, 2)
1346 actual = f.tell()
1347 actual = f.tell()
1347 dd = actual - expected
1348 dd = actual - expected
1348 except IOError, inst:
1349 except IOError, inst:
1349 if inst.errno != errno.ENOENT:
1350 if inst.errno != errno.ENOENT:
1350 raise
1351 raise
1351 dd = 0
1352 dd = 0
1352
1353
1353 try:
1354 try:
1354 f = self.opener(self.indexfile)
1355 f = self.opener(self.indexfile)
1355 f.seek(0, 2)
1356 f.seek(0, 2)
1356 actual = f.tell()
1357 actual = f.tell()
1357 s = self._io.size
1358 s = self._io.size
1358 i = max(0, actual // s)
1359 i = max(0, actual // s)
1359 di = actual - (i * s)
1360 di = actual - (i * s)
1360 if self._inline:
1361 if self._inline:
1361 databytes = 0
1362 databytes = 0
1362 for r in self:
1363 for r in self:
1363 databytes += max(0, self.length(r))
1364 databytes += max(0, self.length(r))
1364 dd = 0
1365 dd = 0
1365 di = actual - len(self) * s - databytes
1366 di = actual - len(self) * s - databytes
1366 except IOError, inst:
1367 except IOError, inst:
1367 if inst.errno != errno.ENOENT:
1368 if inst.errno != errno.ENOENT:
1368 raise
1369 raise
1369 di = 0
1370 di = 0
1370
1371
1371 return (dd, di)
1372 return (dd, di)
1372
1373
1373 def files(self):
1374 def files(self):
1374 res = [ self.indexfile ]
1375 res = [ self.indexfile ]
1375 if not self._inline:
1376 if not self._inline:
1376 res.append(self.datafile)
1377 res.append(self.datafile)
1377 return res
1378 return res
General Comments 0
You need to be logged in to leave comments. Login now