##// END OF EJS Templates
bundle-ng: move group into the bundler...
Sune Foldager -
r19200:4cfdec94 default
parent child Browse files
Show More
@@ -1,295 +1,295 b''
1 """reorder a revlog (the manifest by default) to save space
1 """reorder a revlog (the manifest by default) to save space
2
2
3 Specifically, this topologically sorts the revisions in the revlog so that
3 Specifically, this topologically sorts the revisions in the revlog so that
4 revisions on the same branch are adjacent as much as possible. This is a
4 revisions on the same branch are adjacent as much as possible. This is a
5 workaround for the fact that Mercurial computes deltas relative to the
5 workaround for the fact that Mercurial computes deltas relative to the
6 previous revision rather than relative to a parent revision.
6 previous revision rather than relative to a parent revision.
7
7
8 This is *not* safe to run on a changelog.
8 This is *not* safe to run on a changelog.
9 """
9 """
10
10
11 # Originally written by Benoit Boissinot <benoit.boissinot at ens-lyon.org>
11 # Originally written by Benoit Boissinot <benoit.boissinot at ens-lyon.org>
12 # as a patch to rewrite-log. Cleaned up, refactored, documented, and
12 # as a patch to rewrite-log. Cleaned up, refactored, documented, and
13 # renamed by Greg Ward <greg at gerg.ca>.
13 # renamed by Greg Ward <greg at gerg.ca>.
14
14
15 # XXX would be nice to have a way to verify the repository after shrinking,
15 # XXX would be nice to have a way to verify the repository after shrinking,
16 # e.g. by comparing "before" and "after" states of random changesets
16 # e.g. by comparing "before" and "after" states of random changesets
17 # (maybe: export before, shrink, export after, diff).
17 # (maybe: export before, shrink, export after, diff).
18
18
19 import os, errno
19 import os, errno
20 from mercurial import revlog, transaction, node, util, scmutil
20 from mercurial import revlog, transaction, node, util, scmutil
21 from mercurial import changegroup
21 from mercurial import changegroup
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23
23
24
24
25 def postorder(start, edges):
25 def postorder(start, edges):
26 result = []
26 result = []
27 visit = list(start)
27 visit = list(start)
28 finished = set()
28 finished = set()
29
29
30 while visit:
30 while visit:
31 cur = visit[-1]
31 cur = visit[-1]
32 for p in edges[cur]:
32 for p in edges[cur]:
33 # defend against node.nullrev because it's occasionally
33 # defend against node.nullrev because it's occasionally
34 # possible for a node to have parents (null, something)
34 # possible for a node to have parents (null, something)
35 # rather than (something, null)
35 # rather than (something, null)
36 if p not in finished and p != node.nullrev:
36 if p not in finished and p != node.nullrev:
37 visit.append(p)
37 visit.append(p)
38 break
38 break
39 else:
39 else:
40 result.append(cur)
40 result.append(cur)
41 finished.add(cur)
41 finished.add(cur)
42 visit.pop()
42 visit.pop()
43
43
44 return result
44 return result
45
45
46 def toposort_reversepostorder(ui, rl):
46 def toposort_reversepostorder(ui, rl):
47 # postorder of the reverse directed graph
47 # postorder of the reverse directed graph
48
48
49 # map rev to list of parent revs (p2 first)
49 # map rev to list of parent revs (p2 first)
50 parents = {}
50 parents = {}
51 heads = set()
51 heads = set()
52 ui.status(_('reading revs\n'))
52 ui.status(_('reading revs\n'))
53 try:
53 try:
54 for rev in rl:
54 for rev in rl:
55 ui.progress(_('reading'), rev, total=len(rl))
55 ui.progress(_('reading'), rev, total=len(rl))
56 (p1, p2) = rl.parentrevs(rev)
56 (p1, p2) = rl.parentrevs(rev)
57 if p1 == p2 == node.nullrev:
57 if p1 == p2 == node.nullrev:
58 parents[rev] = () # root node
58 parents[rev] = () # root node
59 elif p1 == p2 or p2 == node.nullrev:
59 elif p1 == p2 or p2 == node.nullrev:
60 parents[rev] = (p1,) # normal node
60 parents[rev] = (p1,) # normal node
61 else:
61 else:
62 parents[rev] = (p2, p1) # merge node
62 parents[rev] = (p2, p1) # merge node
63 heads.add(rev)
63 heads.add(rev)
64 for p in parents[rev]:
64 for p in parents[rev]:
65 heads.discard(p)
65 heads.discard(p)
66 finally:
66 finally:
67 ui.progress(_('reading'), None)
67 ui.progress(_('reading'), None)
68
68
69 heads = list(heads)
69 heads = list(heads)
70 heads.sort(reverse=True)
70 heads.sort(reverse=True)
71
71
72 ui.status(_('sorting revs\n'))
72 ui.status(_('sorting revs\n'))
73 return postorder(heads, parents)
73 return postorder(heads, parents)
74
74
75 def toposort_postorderreverse(ui, rl):
75 def toposort_postorderreverse(ui, rl):
76 # reverse-postorder of the reverse directed graph
76 # reverse-postorder of the reverse directed graph
77
77
78 children = {}
78 children = {}
79 roots = set()
79 roots = set()
80 ui.status(_('reading revs\n'))
80 ui.status(_('reading revs\n'))
81 try:
81 try:
82 for rev in rl:
82 for rev in rl:
83 ui.progress(_('reading'), rev, total=len(rl))
83 ui.progress(_('reading'), rev, total=len(rl))
84 (p1, p2) = rl.parentrevs(rev)
84 (p1, p2) = rl.parentrevs(rev)
85 if p1 == p2 == node.nullrev:
85 if p1 == p2 == node.nullrev:
86 roots.add(rev)
86 roots.add(rev)
87 children[rev] = []
87 children[rev] = []
88 if p1 != node.nullrev:
88 if p1 != node.nullrev:
89 children[p1].append(rev)
89 children[p1].append(rev)
90 if p2 != node.nullrev:
90 if p2 != node.nullrev:
91 children[p2].append(rev)
91 children[p2].append(rev)
92 finally:
92 finally:
93 ui.progress(_('reading'), None)
93 ui.progress(_('reading'), None)
94
94
95 roots = list(roots)
95 roots = list(roots)
96 roots.sort()
96 roots.sort()
97
97
98 ui.status(_('sorting revs\n'))
98 ui.status(_('sorting revs\n'))
99 result = postorder(roots, children)
99 result = postorder(roots, children)
100 result.reverse()
100 result.reverse()
101 return result
101 return result
102
102
103 def writerevs(ui, r1, r2, order, tr):
103 def writerevs(ui, r1, r2, order, tr):
104
104
105 ui.status(_('writing revs\n'))
105 ui.status(_('writing revs\n'))
106
106
107
107
108 order = [r1.node(r) for r in order]
108 order = [r1.node(r) for r in order]
109
109
110 # this is a bit ugly, but it works
110 # this is a bit ugly, but it works
111 count = [0]
111 count = [0]
112 def lookup(revl, x):
112 def lookup(revl, x):
113 count[0] += 1
113 count[0] += 1
114 ui.progress(_('writing'), count[0], total=len(order))
114 ui.progress(_('writing'), count[0], total=len(order))
115 return "%020d" % revl.linkrev(revl.rev(x))
115 return "%020d" % revl.linkrev(revl.rev(x))
116
116
117 unlookup = lambda x: int(x, 10)
117 unlookup = lambda x: int(x, 10)
118
118
119 try:
119 try:
120 bundler = changegroup.bundle10()
120 bundler = changegroup.bundle10()
121 bundler.start(lookup)
121 bundler.start(lookup)
122 group = util.chunkbuffer(r1.group(order, bundler))
122 group = util.chunkbuffer(bundler.group(order, r1))
123 group = changegroup.unbundle10(group, "UN")
123 group = changegroup.unbundle10(group, "UN")
124 r2.addgroup(group, unlookup, tr)
124 r2.addgroup(group, unlookup, tr)
125 finally:
125 finally:
126 ui.progress(_('writing'), None)
126 ui.progress(_('writing'), None)
127
127
128 def report(ui, r1, r2):
128 def report(ui, r1, r2):
129 def getsize(r):
129 def getsize(r):
130 s = 0
130 s = 0
131 for fn in (r.indexfile, r.datafile):
131 for fn in (r.indexfile, r.datafile):
132 try:
132 try:
133 s += os.stat(fn).st_size
133 s += os.stat(fn).st_size
134 except OSError, inst:
134 except OSError, inst:
135 if inst.errno != errno.ENOENT:
135 if inst.errno != errno.ENOENT:
136 raise
136 raise
137 return s
137 return s
138
138
139 oldsize = float(getsize(r1))
139 oldsize = float(getsize(r1))
140 newsize = float(getsize(r2))
140 newsize = float(getsize(r2))
141
141
142 # argh: have to pass an int to %d, because a float >= 2^32
142 # argh: have to pass an int to %d, because a float >= 2^32
143 # blows up under Python 2.5 or earlier
143 # blows up under Python 2.5 or earlier
144 ui.write(_('old file size: %12d bytes (%6.1f MiB)\n')
144 ui.write(_('old file size: %12d bytes (%6.1f MiB)\n')
145 % (int(oldsize), oldsize / 1024 / 1024))
145 % (int(oldsize), oldsize / 1024 / 1024))
146 ui.write(_('new file size: %12d bytes (%6.1f MiB)\n')
146 ui.write(_('new file size: %12d bytes (%6.1f MiB)\n')
147 % (int(newsize), newsize / 1024 / 1024))
147 % (int(newsize), newsize / 1024 / 1024))
148
148
149 shrink_percent = (oldsize - newsize) / oldsize * 100
149 shrink_percent = (oldsize - newsize) / oldsize * 100
150 shrink_factor = oldsize / newsize
150 shrink_factor = oldsize / newsize
151 ui.write(_('shrinkage: %.1f%% (%.1fx)\n')
151 ui.write(_('shrinkage: %.1f%% (%.1fx)\n')
152 % (shrink_percent, shrink_factor))
152 % (shrink_percent, shrink_factor))
153
153
154 def shrink(ui, repo, **opts):
154 def shrink(ui, repo, **opts):
155 """shrink a revlog by reordering revisions
155 """shrink a revlog by reordering revisions
156
156
157 Rewrites all the entries in some revlog of the current repository
157 Rewrites all the entries in some revlog of the current repository
158 (by default, the manifest log) to save space.
158 (by default, the manifest log) to save space.
159
159
160 Different sort algorithms have different performance
160 Different sort algorithms have different performance
161 characteristics. Use ``--sort`` to select a sort algorithm so you
161 characteristics. Use ``--sort`` to select a sort algorithm so you
162 can determine which works best for your data.
162 can determine which works best for your data.
163 """
163 """
164
164
165 if not repo.local():
165 if not repo.local():
166 raise util.Abort(_('not a local repository: %s') % repo.root)
166 raise util.Abort(_('not a local repository: %s') % repo.root)
167
167
168 fn = opts.get('revlog')
168 fn = opts.get('revlog')
169 if not fn:
169 if not fn:
170 indexfn = repo.sjoin('00manifest.i')
170 indexfn = repo.sjoin('00manifest.i')
171 else:
171 else:
172 if not fn.endswith('.i'):
172 if not fn.endswith('.i'):
173 raise util.Abort(_('--revlog option must specify the revlog index '
173 raise util.Abort(_('--revlog option must specify the revlog index '
174 'file (*.i), not %s') % opts.get('revlog'))
174 'file (*.i), not %s') % opts.get('revlog'))
175
175
176 indexfn = os.path.realpath(fn)
176 indexfn = os.path.realpath(fn)
177 store = repo.sjoin('')
177 store = repo.sjoin('')
178 if not indexfn.startswith(store):
178 if not indexfn.startswith(store):
179 raise util.Abort(_('--revlog option must specify a revlog in %s, '
179 raise util.Abort(_('--revlog option must specify a revlog in %s, '
180 'not %s') % (store, indexfn))
180 'not %s') % (store, indexfn))
181
181
182 sortname = opts['sort']
182 sortname = opts['sort']
183 try:
183 try:
184 toposort = globals()['toposort_' + sortname]
184 toposort = globals()['toposort_' + sortname]
185 except KeyError:
185 except KeyError:
186 raise util.Abort(_('no such toposort algorithm: %s') % sortname)
186 raise util.Abort(_('no such toposort algorithm: %s') % sortname)
187
187
188 if not os.path.exists(indexfn):
188 if not os.path.exists(indexfn):
189 raise util.Abort(_('no such file: %s') % indexfn)
189 raise util.Abort(_('no such file: %s') % indexfn)
190 if '00changelog' in indexfn:
190 if '00changelog' in indexfn:
191 raise util.Abort(_('shrinking the changelog '
191 raise util.Abort(_('shrinking the changelog '
192 'will corrupt your repository'))
192 'will corrupt your repository'))
193
193
194 ui.write(_('shrinking %s\n') % indexfn)
194 ui.write(_('shrinking %s\n') % indexfn)
195 tmpindexfn = util.mktempcopy(indexfn, emptyok=True)
195 tmpindexfn = util.mktempcopy(indexfn, emptyok=True)
196
196
197 r1 = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), indexfn)
197 r1 = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), indexfn)
198 r2 = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), tmpindexfn)
198 r2 = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), tmpindexfn)
199
199
200 datafn, tmpdatafn = r1.datafile, r2.datafile
200 datafn, tmpdatafn = r1.datafile, r2.datafile
201
201
202 oldindexfn = indexfn + '.old'
202 oldindexfn = indexfn + '.old'
203 olddatafn = datafn + '.old'
203 olddatafn = datafn + '.old'
204 if os.path.exists(oldindexfn) or os.path.exists(olddatafn):
204 if os.path.exists(oldindexfn) or os.path.exists(olddatafn):
205 raise util.Abort(_('one or both of\n'
205 raise util.Abort(_('one or both of\n'
206 ' %s\n'
206 ' %s\n'
207 ' %s\n'
207 ' %s\n'
208 'exists from a previous run; please clean up '
208 'exists from a previous run; please clean up '
209 'before running again') % (oldindexfn, olddatafn))
209 'before running again') % (oldindexfn, olddatafn))
210
210
211 # Don't use repo.transaction(), because then things get hairy with
211 # Don't use repo.transaction(), because then things get hairy with
212 # paths: some need to be relative to .hg, and some need to be
212 # paths: some need to be relative to .hg, and some need to be
213 # absolute. Doing it this way keeps things simple: everything is an
213 # absolute. Doing it this way keeps things simple: everything is an
214 # absolute path.
214 # absolute path.
215 lock = repo.lock(wait=False)
215 lock = repo.lock(wait=False)
216 tr = transaction.transaction(ui.warn,
216 tr = transaction.transaction(ui.warn,
217 open,
217 open,
218 repo.sjoin('journal'))
218 repo.sjoin('journal'))
219
219
220 def ignoremissing(func):
220 def ignoremissing(func):
221 def f(*args, **kw):
221 def f(*args, **kw):
222 try:
222 try:
223 return func(*args, **kw)
223 return func(*args, **kw)
224 except OSError, inst:
224 except OSError, inst:
225 if inst.errno != errno.ENOENT:
225 if inst.errno != errno.ENOENT:
226 raise
226 raise
227 return f
227 return f
228
228
229 try:
229 try:
230 try:
230 try:
231 order = toposort(ui, r1)
231 order = toposort(ui, r1)
232
232
233 suboptimal = 0
233 suboptimal = 0
234 for i in xrange(1, len(order)):
234 for i in xrange(1, len(order)):
235 parents = [p for p in r1.parentrevs(order[i])
235 parents = [p for p in r1.parentrevs(order[i])
236 if p != node.nullrev]
236 if p != node.nullrev]
237 if parents and order[i - 1] not in parents:
237 if parents and order[i - 1] not in parents:
238 suboptimal += 1
238 suboptimal += 1
239 ui.note(_('%d suboptimal nodes\n') % suboptimal)
239 ui.note(_('%d suboptimal nodes\n') % suboptimal)
240
240
241 writerevs(ui, r1, r2, order, tr)
241 writerevs(ui, r1, r2, order, tr)
242 report(ui, r1, r2)
242 report(ui, r1, r2)
243 tr.close()
243 tr.close()
244 except: # re-raises
244 except: # re-raises
245 # Abort transaction first, so we truncate the files before
245 # Abort transaction first, so we truncate the files before
246 # deleting them.
246 # deleting them.
247 tr.abort()
247 tr.abort()
248 for fn in (tmpindexfn, tmpdatafn):
248 for fn in (tmpindexfn, tmpdatafn):
249 ignoremissing(os.unlink)(fn)
249 ignoremissing(os.unlink)(fn)
250 raise
250 raise
251 if not opts.get('dry_run'):
251 if not opts.get('dry_run'):
252 # racy, both files cannot be renamed atomically
252 # racy, both files cannot be renamed atomically
253 # copy files
253 # copy files
254 util.oslink(indexfn, oldindexfn)
254 util.oslink(indexfn, oldindexfn)
255 ignoremissing(util.oslink)(datafn, olddatafn)
255 ignoremissing(util.oslink)(datafn, olddatafn)
256
256
257 # rename
257 # rename
258 util.rename(tmpindexfn, indexfn)
258 util.rename(tmpindexfn, indexfn)
259 try:
259 try:
260 os.chmod(tmpdatafn, os.stat(datafn).st_mode)
260 os.chmod(tmpdatafn, os.stat(datafn).st_mode)
261 util.rename(tmpdatafn, datafn)
261 util.rename(tmpdatafn, datafn)
262 except OSError, inst:
262 except OSError, inst:
263 if inst.errno != errno.ENOENT:
263 if inst.errno != errno.ENOENT:
264 raise
264 raise
265 ignoremissing(os.unlink)(datafn)
265 ignoremissing(os.unlink)(datafn)
266 else:
266 else:
267 for fn in (tmpindexfn, tmpdatafn):
267 for fn in (tmpindexfn, tmpdatafn):
268 ignoremissing(os.unlink)(fn)
268 ignoremissing(os.unlink)(fn)
269 finally:
269 finally:
270 lock.release()
270 lock.release()
271
271
272 if not opts.get('dry_run'):
272 if not opts.get('dry_run'):
273 ui.write(
273 ui.write(
274 _('note: old revlog saved in:\n'
274 _('note: old revlog saved in:\n'
275 ' %s\n'
275 ' %s\n'
276 ' %s\n'
276 ' %s\n'
277 '(You can delete those files when you are satisfied that your\n'
277 '(You can delete those files when you are satisfied that your\n'
278 'repository is still sane. '
278 'repository is still sane. '
279 'Running \'hg verify\' is strongly recommended.)\n')
279 'Running \'hg verify\' is strongly recommended.)\n')
280 % (oldindexfn, olddatafn))
280 % (oldindexfn, olddatafn))
281
281
282 cmdtable = {
282 cmdtable = {
283 'shrink': (shrink,
283 'shrink': (shrink,
284 [('', 'revlog', '',
284 [('', 'revlog', '',
285 _('the revlog to shrink (.i)')),
285 _('the revlog to shrink (.i)')),
286 ('n', 'dry-run', None,
286 ('n', 'dry-run', None,
287 _('do not shrink, simulate only')),
287 _('do not shrink, simulate only')),
288 ('', 'sort', 'reversepostorder',
288 ('', 'sort', 'reversepostorder',
289 _('name of sort algorithm to use')),
289 _('name of sort algorithm to use')),
290 ],
290 ],
291 _('hg shrink [--revlog PATH]'))
291 _('hg shrink [--revlog PATH]'))
292 }
292 }
293
293
294 if __name__ == "__main__":
294 if __name__ == "__main__":
295 print "shrink-revlog.py is now an extension (see hg help extensions)"
295 print "shrink-revlog.py is now an extension (see hg help extensions)"
@@ -1,258 +1,299 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 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 from node import nullrev
9 from node import nullrev
10 import mdiff, util
10 import mdiff, util, dagutil
11 import struct, os, bz2, zlib, tempfile
11 import struct, os, bz2, zlib, tempfile
12
12
13 _BUNDLE10_DELTA_HEADER = "20s20s20s20s"
13 _BUNDLE10_DELTA_HEADER = "20s20s20s20s"
14
14
15 def readexactly(stream, n):
15 def readexactly(stream, n):
16 '''read n bytes from stream.read and abort if less was available'''
16 '''read n bytes from stream.read and abort if less was available'''
17 s = stream.read(n)
17 s = stream.read(n)
18 if len(s) < n:
18 if len(s) < n:
19 raise util.Abort(_("stream ended unexpectedly"
19 raise util.Abort(_("stream ended unexpectedly"
20 " (got %d bytes, expected %d)")
20 " (got %d bytes, expected %d)")
21 % (len(s), n))
21 % (len(s), n))
22 return s
22 return s
23
23
24 def getchunk(stream):
24 def getchunk(stream):
25 """return the next chunk from stream as a string"""
25 """return the next chunk from stream as a string"""
26 d = readexactly(stream, 4)
26 d = readexactly(stream, 4)
27 l = struct.unpack(">l", d)[0]
27 l = struct.unpack(">l", d)[0]
28 if l <= 4:
28 if l <= 4:
29 if l:
29 if l:
30 raise util.Abort(_("invalid chunk length %d") % l)
30 raise util.Abort(_("invalid chunk length %d") % l)
31 return ""
31 return ""
32 return readexactly(stream, l - 4)
32 return readexactly(stream, l - 4)
33
33
34 def chunkheader(length):
34 def chunkheader(length):
35 """return a changegroup chunk header (string)"""
35 """return a changegroup chunk header (string)"""
36 return struct.pack(">l", length + 4)
36 return struct.pack(">l", length + 4)
37
37
38 def closechunk():
38 def closechunk():
39 """return a changegroup chunk header (string) for a zero-length chunk"""
39 """return a changegroup chunk header (string) for a zero-length chunk"""
40 return struct.pack(">l", 0)
40 return struct.pack(">l", 0)
41
41
42 class nocompress(object):
42 class nocompress(object):
43 def compress(self, x):
43 def compress(self, x):
44 return x
44 return x
45 def flush(self):
45 def flush(self):
46 return ""
46 return ""
47
47
48 bundletypes = {
48 bundletypes = {
49 "": ("", nocompress), # only when using unbundle on ssh and old http servers
49 "": ("", nocompress), # only when using unbundle on ssh and old http servers
50 # since the unification ssh accepts a header but there
50 # since the unification ssh accepts a header but there
51 # is no capability signaling it.
51 # is no capability signaling it.
52 "HG10UN": ("HG10UN", nocompress),
52 "HG10UN": ("HG10UN", nocompress),
53 "HG10BZ": ("HG10", lambda: bz2.BZ2Compressor()),
53 "HG10BZ": ("HG10", lambda: bz2.BZ2Compressor()),
54 "HG10GZ": ("HG10GZ", lambda: zlib.compressobj()),
54 "HG10GZ": ("HG10GZ", lambda: zlib.compressobj()),
55 }
55 }
56
56
57 # hgweb uses this list to communicate its preferred type
57 # hgweb uses this list to communicate its preferred type
58 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
58 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
59
59
60 def writebundle(cg, filename, bundletype):
60 def writebundle(cg, filename, bundletype):
61 """Write a bundle file and return its filename.
61 """Write a bundle file and return its filename.
62
62
63 Existing files will not be overwritten.
63 Existing files will not be overwritten.
64 If no filename is specified, a temporary file is created.
64 If no filename is specified, a temporary file is created.
65 bz2 compression can be turned off.
65 bz2 compression can be turned off.
66 The bundle file will be deleted in case of errors.
66 The bundle file will be deleted in case of errors.
67 """
67 """
68
68
69 fh = None
69 fh = None
70 cleanup = None
70 cleanup = None
71 try:
71 try:
72 if filename:
72 if filename:
73 fh = open(filename, "wb")
73 fh = open(filename, "wb")
74 else:
74 else:
75 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
75 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
76 fh = os.fdopen(fd, "wb")
76 fh = os.fdopen(fd, "wb")
77 cleanup = filename
77 cleanup = filename
78
78
79 header, compressor = bundletypes[bundletype]
79 header, compressor = bundletypes[bundletype]
80 fh.write(header)
80 fh.write(header)
81 z = compressor()
81 z = compressor()
82
82
83 # parse the changegroup data, otherwise we will block
83 # parse the changegroup data, otherwise we will block
84 # 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
85
85
86 # an empty chunkgroup is the end of the changegroup
86 # an empty chunkgroup is the end of the changegroup
87 # a changegroup has at least 2 chunkgroups (changelog and manifest).
87 # a changegroup has at least 2 chunkgroups (changelog and manifest).
88 # after that, an empty chunkgroup is the end of the changegroup
88 # after that, an empty chunkgroup is the end of the changegroup
89 empty = False
89 empty = False
90 count = 0
90 count = 0
91 while not empty or count <= 2:
91 while not empty or count <= 2:
92 empty = True
92 empty = True
93 count += 1
93 count += 1
94 while True:
94 while True:
95 chunk = getchunk(cg)
95 chunk = getchunk(cg)
96 if not chunk:
96 if not chunk:
97 break
97 break
98 empty = False
98 empty = False
99 fh.write(z.compress(chunkheader(len(chunk))))
99 fh.write(z.compress(chunkheader(len(chunk))))
100 pos = 0
100 pos = 0
101 while pos < len(chunk):
101 while pos < len(chunk):
102 next = pos + 2**20
102 next = pos + 2**20
103 fh.write(z.compress(chunk[pos:next]))
103 fh.write(z.compress(chunk[pos:next]))
104 pos = next
104 pos = next
105 fh.write(z.compress(closechunk()))
105 fh.write(z.compress(closechunk()))
106 fh.write(z.flush())
106 fh.write(z.flush())
107 cleanup = None
107 cleanup = None
108 return filename
108 return filename
109 finally:
109 finally:
110 if fh is not None:
110 if fh is not None:
111 fh.close()
111 fh.close()
112 if cleanup is not None:
112 if cleanup is not None:
113 os.unlink(cleanup)
113 os.unlink(cleanup)
114
114
115 def decompressor(fh, alg):
115 def decompressor(fh, alg):
116 if alg == 'UN':
116 if alg == 'UN':
117 return fh
117 return fh
118 elif alg == 'GZ':
118 elif alg == 'GZ':
119 def generator(f):
119 def generator(f):
120 zd = zlib.decompressobj()
120 zd = zlib.decompressobj()
121 for chunk in util.filechunkiter(f):
121 for chunk in util.filechunkiter(f):
122 yield zd.decompress(chunk)
122 yield zd.decompress(chunk)
123 elif alg == 'BZ':
123 elif alg == 'BZ':
124 def generator(f):
124 def generator(f):
125 zd = bz2.BZ2Decompressor()
125 zd = bz2.BZ2Decompressor()
126 zd.decompress("BZ")
126 zd.decompress("BZ")
127 for chunk in util.filechunkiter(f, 4096):
127 for chunk in util.filechunkiter(f, 4096):
128 yield zd.decompress(chunk)
128 yield zd.decompress(chunk)
129 else:
129 else:
130 raise util.Abort("unknown bundle compression '%s'" % alg)
130 raise util.Abort("unknown bundle compression '%s'" % alg)
131 return util.chunkbuffer(generator(fh))
131 return util.chunkbuffer(generator(fh))
132
132
133 class unbundle10(object):
133 class unbundle10(object):
134 deltaheader = _BUNDLE10_DELTA_HEADER
134 deltaheader = _BUNDLE10_DELTA_HEADER
135 deltaheadersize = struct.calcsize(deltaheader)
135 deltaheadersize = struct.calcsize(deltaheader)
136 def __init__(self, fh, alg):
136 def __init__(self, fh, alg):
137 self._stream = decompressor(fh, alg)
137 self._stream = decompressor(fh, alg)
138 self._type = alg
138 self._type = alg
139 self.callback = None
139 self.callback = None
140 def compressed(self):
140 def compressed(self):
141 return self._type != 'UN'
141 return self._type != 'UN'
142 def read(self, l):
142 def read(self, l):
143 return self._stream.read(l)
143 return self._stream.read(l)
144 def seek(self, pos):
144 def seek(self, pos):
145 return self._stream.seek(pos)
145 return self._stream.seek(pos)
146 def tell(self):
146 def tell(self):
147 return self._stream.tell()
147 return self._stream.tell()
148 def close(self):
148 def close(self):
149 return self._stream.close()
149 return self._stream.close()
150
150
151 def chunklength(self):
151 def chunklength(self):
152 d = readexactly(self._stream, 4)
152 d = readexactly(self._stream, 4)
153 l = struct.unpack(">l", d)[0]
153 l = struct.unpack(">l", d)[0]
154 if l <= 4:
154 if l <= 4:
155 if l:
155 if l:
156 raise util.Abort(_("invalid chunk length %d") % l)
156 raise util.Abort(_("invalid chunk length %d") % l)
157 return 0
157 return 0
158 if self.callback:
158 if self.callback:
159 self.callback()
159 self.callback()
160 return l - 4
160 return l - 4
161
161
162 def changelogheader(self):
162 def changelogheader(self):
163 """v10 does not have a changelog header chunk"""
163 """v10 does not have a changelog header chunk"""
164 return {}
164 return {}
165
165
166 def manifestheader(self):
166 def manifestheader(self):
167 """v10 does not have a manifest header chunk"""
167 """v10 does not have a manifest header chunk"""
168 return {}
168 return {}
169
169
170 def filelogheader(self):
170 def filelogheader(self):
171 """return the header of the filelogs chunk, v10 only has the filename"""
171 """return the header of the filelogs chunk, v10 only has the filename"""
172 l = self.chunklength()
172 l = self.chunklength()
173 if not l:
173 if not l:
174 return {}
174 return {}
175 fname = readexactly(self._stream, l)
175 fname = readexactly(self._stream, l)
176 return dict(filename=fname)
176 return dict(filename=fname)
177
177
178 def _deltaheader(self, headertuple, prevnode):
178 def _deltaheader(self, headertuple, prevnode):
179 node, p1, p2, cs = headertuple
179 node, p1, p2, cs = headertuple
180 if prevnode is None:
180 if prevnode is None:
181 deltabase = p1
181 deltabase = p1
182 else:
182 else:
183 deltabase = prevnode
183 deltabase = prevnode
184 return node, p1, p2, deltabase, cs
184 return node, p1, p2, deltabase, cs
185
185
186 def deltachunk(self, prevnode):
186 def deltachunk(self, prevnode):
187 l = self.chunklength()
187 l = self.chunklength()
188 if not l:
188 if not l:
189 return {}
189 return {}
190 headerdata = readexactly(self._stream, self.deltaheadersize)
190 headerdata = readexactly(self._stream, self.deltaheadersize)
191 header = struct.unpack(self.deltaheader, headerdata)
191 header = struct.unpack(self.deltaheader, headerdata)
192 delta = readexactly(self._stream, l - self.deltaheadersize)
192 delta = readexactly(self._stream, l - self.deltaheadersize)
193 node, p1, p2, deltabase, cs = self._deltaheader(header, prevnode)
193 node, p1, p2, deltabase, cs = self._deltaheader(header, prevnode)
194 return dict(node=node, p1=p1, p2=p2, cs=cs,
194 return dict(node=node, p1=p1, p2=p2, cs=cs,
195 deltabase=deltabase, delta=delta)
195 deltabase=deltabase, delta=delta)
196
196
197 class headerlessfixup(object):
197 class headerlessfixup(object):
198 def __init__(self, fh, h):
198 def __init__(self, fh, h):
199 self._h = h
199 self._h = h
200 self._fh = fh
200 self._fh = fh
201 def read(self, n):
201 def read(self, n):
202 if self._h:
202 if self._h:
203 d, self._h = self._h[:n], self._h[n:]
203 d, self._h = self._h[:n], self._h[n:]
204 if len(d) < n:
204 if len(d) < n:
205 d += readexactly(self._fh, n - len(d))
205 d += readexactly(self._fh, n - len(d))
206 return d
206 return d
207 return readexactly(self._fh, n)
207 return readexactly(self._fh, n)
208
208
209 def readbundle(fh, fname):
209 def readbundle(fh, fname):
210 header = readexactly(fh, 6)
210 header = readexactly(fh, 6)
211
211
212 if not fname:
212 if not fname:
213 fname = "stream"
213 fname = "stream"
214 if not header.startswith('HG') and header.startswith('\0'):
214 if not header.startswith('HG') and header.startswith('\0'):
215 fh = headerlessfixup(fh, header)
215 fh = headerlessfixup(fh, header)
216 header = "HG10UN"
216 header = "HG10UN"
217
217
218 magic, version, alg = header[0:2], header[2:4], header[4:6]
218 magic, version, alg = header[0:2], header[2:4], header[4:6]
219
219
220 if magic != 'HG':
220 if magic != 'HG':
221 raise util.Abort(_('%s: not a Mercurial bundle') % fname)
221 raise util.Abort(_('%s: not a Mercurial bundle') % fname)
222 if version != '10':
222 if version != '10':
223 raise util.Abort(_('%s: unknown bundle version %s') % (fname, version))
223 raise util.Abort(_('%s: unknown bundle version %s') % (fname, version))
224 return unbundle10(fh, alg)
224 return unbundle10(fh, alg)
225
225
226 class bundle10(object):
226 class bundle10(object):
227 deltaheader = _BUNDLE10_DELTA_HEADER
227 deltaheader = _BUNDLE10_DELTA_HEADER
228 def __init__(self):
228 def __init__(self):
229 pass
229 pass
230 def start(self, lookup):
230 def start(self, lookup):
231 self._lookup = lookup
231 self._lookup = lookup
232 def close(self):
232 def close(self):
233 return closechunk()
233 return closechunk()
234
234 def fileheader(self, fname):
235 def fileheader(self, fname):
235 return chunkheader(len(fname)) + fname
236 return chunkheader(len(fname)) + fname
237
238 def group(self, nodelist, revlog, reorder=None):
239 """Calculate a delta group, yielding a sequence of changegroup chunks
240 (strings).
241
242 Given a list of changeset revs, return a set of deltas and
243 metadata corresponding to nodes. The first delta is
244 first parent(nodelist[0]) -> nodelist[0], the receiver is
245 guaranteed to have this parent as it has all history before
246 these changesets. In the case firstparent is nullrev the
247 changegroup starts with a full revision.
248 """
249
250 # if we don't have any revisions touched by these changesets, bail
251 if len(nodelist) == 0:
252 yield self.close()
253 return
254
255 # for generaldelta revlogs, we linearize the revs; this will both be
256 # much quicker and generate a much smaller bundle
257 if (revlog._generaldelta and reorder is not False) or reorder:
258 dag = dagutil.revlogdag(revlog)
259 revs = set(revlog.rev(n) for n in nodelist)
260 revs = dag.linearize(revs)
261 else:
262 revs = sorted([revlog.rev(n) for n in nodelist])
263
264 # add the parent of the first rev
265 p = revlog.parentrevs(revs[0])[0]
266 revs.insert(0, p)
267
268 # build deltas
269 for r in xrange(len(revs) - 1):
270 prev, curr = revs[r], revs[r + 1]
271 for c in self.revchunk(revlog, curr, prev):
272 yield c
273
274 yield self.close()
275
276
236 def revchunk(self, revlog, rev, prev):
277 def revchunk(self, revlog, rev, prev):
237 node = revlog.node(rev)
278 node = revlog.node(rev)
238 p1, p2 = revlog.parentrevs(rev)
279 p1, p2 = revlog.parentrevs(rev)
239 base = prev
280 base = prev
240
281
241 prefix = ''
282 prefix = ''
242 if base == nullrev:
283 if base == nullrev:
243 delta = revlog.revision(node)
284 delta = revlog.revision(node)
244 prefix = mdiff.trivialdiffheader(len(delta))
285 prefix = mdiff.trivialdiffheader(len(delta))
245 else:
286 else:
246 delta = revlog.revdiff(base, rev)
287 delta = revlog.revdiff(base, rev)
247 linknode = self._lookup(revlog, node)
288 linknode = self._lookup(revlog, node)
248 p1n, p2n = revlog.parents(node)
289 p1n, p2n = revlog.parents(node)
249 basenode = revlog.node(base)
290 basenode = revlog.node(base)
250 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode)
291 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode)
251 meta += prefix
292 meta += prefix
252 l = len(meta) + len(delta)
293 l = len(meta) + len(delta)
253 yield chunkheader(l)
294 yield chunkheader(l)
254 yield meta
295 yield meta
255 yield delta
296 yield delta
256 def builddeltaheader(self, node, p1n, p2n, basenode, linknode):
297 def builddeltaheader(self, node, p1n, p2n, basenode, linknode):
257 # do nothing with basenode, it is implicitly the previous one in HG10
298 # do nothing with basenode, it is implicitly the previous one in HG10
258 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
299 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
@@ -1,2613 +1,2613 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 or any later version.
6 # GNU General Public License version 2 or any later version.
7 from node import hex, nullid, short
7 from node import hex, nullid, short
8 from i18n import _
8 from i18n import _
9 import peer, changegroup, subrepo, discovery, pushkey, obsolete, repoview
9 import peer, changegroup, subrepo, discovery, pushkey, obsolete, repoview
10 import changelog, dirstate, filelog, manifest, context, bookmarks, phases
10 import changelog, dirstate, filelog, manifest, context, bookmarks, phases
11 import lock, transaction, store, encoding
11 import lock, transaction, store, encoding
12 import scmutil, util, extensions, hook, error, revset
12 import scmutil, util, extensions, hook, error, revset
13 import match as matchmod
13 import match as matchmod
14 import merge as mergemod
14 import merge as mergemod
15 import tags as tagsmod
15 import tags as tagsmod
16 from lock import release
16 from lock import release
17 import weakref, errno, os, time, inspect
17 import weakref, errno, os, time, inspect
18 import branchmap
18 import branchmap
19 propertycache = util.propertycache
19 propertycache = util.propertycache
20 filecache = scmutil.filecache
20 filecache = scmutil.filecache
21
21
22 class repofilecache(filecache):
22 class repofilecache(filecache):
23 """All filecache usage on repo are done for logic that should be unfiltered
23 """All filecache usage on repo are done for logic that should be unfiltered
24 """
24 """
25
25
26 def __get__(self, repo, type=None):
26 def __get__(self, repo, type=None):
27 return super(repofilecache, self).__get__(repo.unfiltered(), type)
27 return super(repofilecache, self).__get__(repo.unfiltered(), type)
28 def __set__(self, repo, value):
28 def __set__(self, repo, value):
29 return super(repofilecache, self).__set__(repo.unfiltered(), value)
29 return super(repofilecache, self).__set__(repo.unfiltered(), value)
30 def __delete__(self, repo):
30 def __delete__(self, repo):
31 return super(repofilecache, self).__delete__(repo.unfiltered())
31 return super(repofilecache, self).__delete__(repo.unfiltered())
32
32
33 class storecache(repofilecache):
33 class storecache(repofilecache):
34 """filecache for files in the store"""
34 """filecache for files in the store"""
35 def join(self, obj, fname):
35 def join(self, obj, fname):
36 return obj.sjoin(fname)
36 return obj.sjoin(fname)
37
37
38 class unfilteredpropertycache(propertycache):
38 class unfilteredpropertycache(propertycache):
39 """propertycache that apply to unfiltered repo only"""
39 """propertycache that apply to unfiltered repo only"""
40
40
41 def __get__(self, repo, type=None):
41 def __get__(self, repo, type=None):
42 return super(unfilteredpropertycache, self).__get__(repo.unfiltered())
42 return super(unfilteredpropertycache, self).__get__(repo.unfiltered())
43
43
44 class filteredpropertycache(propertycache):
44 class filteredpropertycache(propertycache):
45 """propertycache that must take filtering in account"""
45 """propertycache that must take filtering in account"""
46
46
47 def cachevalue(self, obj, value):
47 def cachevalue(self, obj, value):
48 object.__setattr__(obj, self.name, value)
48 object.__setattr__(obj, self.name, value)
49
49
50
50
51 def hasunfilteredcache(repo, name):
51 def hasunfilteredcache(repo, name):
52 """check if a repo has an unfilteredpropertycache value for <name>"""
52 """check if a repo has an unfilteredpropertycache value for <name>"""
53 return name in vars(repo.unfiltered())
53 return name in vars(repo.unfiltered())
54
54
55 def unfilteredmethod(orig):
55 def unfilteredmethod(orig):
56 """decorate method that always need to be run on unfiltered version"""
56 """decorate method that always need to be run on unfiltered version"""
57 def wrapper(repo, *args, **kwargs):
57 def wrapper(repo, *args, **kwargs):
58 return orig(repo.unfiltered(), *args, **kwargs)
58 return orig(repo.unfiltered(), *args, **kwargs)
59 return wrapper
59 return wrapper
60
60
61 MODERNCAPS = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle'))
61 MODERNCAPS = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle'))
62 LEGACYCAPS = MODERNCAPS.union(set(['changegroupsubset']))
62 LEGACYCAPS = MODERNCAPS.union(set(['changegroupsubset']))
63
63
64 class localpeer(peer.peerrepository):
64 class localpeer(peer.peerrepository):
65 '''peer for a local repo; reflects only the most recent API'''
65 '''peer for a local repo; reflects only the most recent API'''
66
66
67 def __init__(self, repo, caps=MODERNCAPS):
67 def __init__(self, repo, caps=MODERNCAPS):
68 peer.peerrepository.__init__(self)
68 peer.peerrepository.__init__(self)
69 self._repo = repo.filtered('served')
69 self._repo = repo.filtered('served')
70 self.ui = repo.ui
70 self.ui = repo.ui
71 self._caps = repo._restrictcapabilities(caps)
71 self._caps = repo._restrictcapabilities(caps)
72 self.requirements = repo.requirements
72 self.requirements = repo.requirements
73 self.supportedformats = repo.supportedformats
73 self.supportedformats = repo.supportedformats
74
74
75 def close(self):
75 def close(self):
76 self._repo.close()
76 self._repo.close()
77
77
78 def _capabilities(self):
78 def _capabilities(self):
79 return self._caps
79 return self._caps
80
80
81 def local(self):
81 def local(self):
82 return self._repo
82 return self._repo
83
83
84 def canpush(self):
84 def canpush(self):
85 return True
85 return True
86
86
87 def url(self):
87 def url(self):
88 return self._repo.url()
88 return self._repo.url()
89
89
90 def lookup(self, key):
90 def lookup(self, key):
91 return self._repo.lookup(key)
91 return self._repo.lookup(key)
92
92
93 def branchmap(self):
93 def branchmap(self):
94 return self._repo.branchmap()
94 return self._repo.branchmap()
95
95
96 def heads(self):
96 def heads(self):
97 return self._repo.heads()
97 return self._repo.heads()
98
98
99 def known(self, nodes):
99 def known(self, nodes):
100 return self._repo.known(nodes)
100 return self._repo.known(nodes)
101
101
102 def getbundle(self, source, heads=None, common=None):
102 def getbundle(self, source, heads=None, common=None):
103 return self._repo.getbundle(source, heads=heads, common=common)
103 return self._repo.getbundle(source, heads=heads, common=common)
104
104
105 # TODO We might want to move the next two calls into legacypeer and add
105 # TODO We might want to move the next two calls into legacypeer and add
106 # unbundle instead.
106 # unbundle instead.
107
107
108 def lock(self):
108 def lock(self):
109 return self._repo.lock()
109 return self._repo.lock()
110
110
111 def addchangegroup(self, cg, source, url):
111 def addchangegroup(self, cg, source, url):
112 return self._repo.addchangegroup(cg, source, url)
112 return self._repo.addchangegroup(cg, source, url)
113
113
114 def pushkey(self, namespace, key, old, new):
114 def pushkey(self, namespace, key, old, new):
115 return self._repo.pushkey(namespace, key, old, new)
115 return self._repo.pushkey(namespace, key, old, new)
116
116
117 def listkeys(self, namespace):
117 def listkeys(self, namespace):
118 return self._repo.listkeys(namespace)
118 return self._repo.listkeys(namespace)
119
119
120 def debugwireargs(self, one, two, three=None, four=None, five=None):
120 def debugwireargs(self, one, two, three=None, four=None, five=None):
121 '''used to test argument passing over the wire'''
121 '''used to test argument passing over the wire'''
122 return "%s %s %s %s %s" % (one, two, three, four, five)
122 return "%s %s %s %s %s" % (one, two, three, four, five)
123
123
124 class locallegacypeer(localpeer):
124 class locallegacypeer(localpeer):
125 '''peer extension which implements legacy methods too; used for tests with
125 '''peer extension which implements legacy methods too; used for tests with
126 restricted capabilities'''
126 restricted capabilities'''
127
127
128 def __init__(self, repo):
128 def __init__(self, repo):
129 localpeer.__init__(self, repo, caps=LEGACYCAPS)
129 localpeer.__init__(self, repo, caps=LEGACYCAPS)
130
130
131 def branches(self, nodes):
131 def branches(self, nodes):
132 return self._repo.branches(nodes)
132 return self._repo.branches(nodes)
133
133
134 def between(self, pairs):
134 def between(self, pairs):
135 return self._repo.between(pairs)
135 return self._repo.between(pairs)
136
136
137 def changegroup(self, basenodes, source):
137 def changegroup(self, basenodes, source):
138 return self._repo.changegroup(basenodes, source)
138 return self._repo.changegroup(basenodes, source)
139
139
140 def changegroupsubset(self, bases, heads, source):
140 def changegroupsubset(self, bases, heads, source):
141 return self._repo.changegroupsubset(bases, heads, source)
141 return self._repo.changegroupsubset(bases, heads, source)
142
142
143 class localrepository(object):
143 class localrepository(object):
144
144
145 supportedformats = set(('revlogv1', 'generaldelta'))
145 supportedformats = set(('revlogv1', 'generaldelta'))
146 supported = supportedformats | set(('store', 'fncache', 'shared',
146 supported = supportedformats | set(('store', 'fncache', 'shared',
147 'dotencode'))
147 'dotencode'))
148 openerreqs = set(('revlogv1', 'generaldelta'))
148 openerreqs = set(('revlogv1', 'generaldelta'))
149 requirements = ['revlogv1']
149 requirements = ['revlogv1']
150 filtername = None
150 filtername = None
151
151
152 def _baserequirements(self, create):
152 def _baserequirements(self, create):
153 return self.requirements[:]
153 return self.requirements[:]
154
154
155 def __init__(self, baseui, path=None, create=False):
155 def __init__(self, baseui, path=None, create=False):
156 self.wvfs = scmutil.vfs(path, expandpath=True, realpath=True)
156 self.wvfs = scmutil.vfs(path, expandpath=True, realpath=True)
157 self.wopener = self.wvfs
157 self.wopener = self.wvfs
158 self.root = self.wvfs.base
158 self.root = self.wvfs.base
159 self.path = self.wvfs.join(".hg")
159 self.path = self.wvfs.join(".hg")
160 self.origroot = path
160 self.origroot = path
161 self.auditor = scmutil.pathauditor(self.root, self._checknested)
161 self.auditor = scmutil.pathauditor(self.root, self._checknested)
162 self.vfs = scmutil.vfs(self.path)
162 self.vfs = scmutil.vfs(self.path)
163 self.opener = self.vfs
163 self.opener = self.vfs
164 self.baseui = baseui
164 self.baseui = baseui
165 self.ui = baseui.copy()
165 self.ui = baseui.copy()
166 # A list of callback to shape the phase if no data were found.
166 # A list of callback to shape the phase if no data were found.
167 # Callback are in the form: func(repo, roots) --> processed root.
167 # Callback are in the form: func(repo, roots) --> processed root.
168 # This list it to be filled by extension during repo setup
168 # This list it to be filled by extension during repo setup
169 self._phasedefaults = []
169 self._phasedefaults = []
170 try:
170 try:
171 self.ui.readconfig(self.join("hgrc"), self.root)
171 self.ui.readconfig(self.join("hgrc"), self.root)
172 extensions.loadall(self.ui)
172 extensions.loadall(self.ui)
173 except IOError:
173 except IOError:
174 pass
174 pass
175
175
176 if not self.vfs.isdir():
176 if not self.vfs.isdir():
177 if create:
177 if create:
178 if not self.wvfs.exists():
178 if not self.wvfs.exists():
179 self.wvfs.makedirs()
179 self.wvfs.makedirs()
180 self.vfs.makedir(notindexed=True)
180 self.vfs.makedir(notindexed=True)
181 requirements = self._baserequirements(create)
181 requirements = self._baserequirements(create)
182 if self.ui.configbool('format', 'usestore', True):
182 if self.ui.configbool('format', 'usestore', True):
183 self.vfs.mkdir("store")
183 self.vfs.mkdir("store")
184 requirements.append("store")
184 requirements.append("store")
185 if self.ui.configbool('format', 'usefncache', True):
185 if self.ui.configbool('format', 'usefncache', True):
186 requirements.append("fncache")
186 requirements.append("fncache")
187 if self.ui.configbool('format', 'dotencode', True):
187 if self.ui.configbool('format', 'dotencode', True):
188 requirements.append('dotencode')
188 requirements.append('dotencode')
189 # create an invalid changelog
189 # create an invalid changelog
190 self.vfs.append(
190 self.vfs.append(
191 "00changelog.i",
191 "00changelog.i",
192 '\0\0\0\2' # represents revlogv2
192 '\0\0\0\2' # represents revlogv2
193 ' dummy changelog to prevent using the old repo layout'
193 ' dummy changelog to prevent using the old repo layout'
194 )
194 )
195 if self.ui.configbool('format', 'generaldelta', False):
195 if self.ui.configbool('format', 'generaldelta', False):
196 requirements.append("generaldelta")
196 requirements.append("generaldelta")
197 requirements = set(requirements)
197 requirements = set(requirements)
198 else:
198 else:
199 raise error.RepoError(_("repository %s not found") % path)
199 raise error.RepoError(_("repository %s not found") % path)
200 elif create:
200 elif create:
201 raise error.RepoError(_("repository %s already exists") % path)
201 raise error.RepoError(_("repository %s already exists") % path)
202 else:
202 else:
203 try:
203 try:
204 requirements = scmutil.readrequires(self.vfs, self.supported)
204 requirements = scmutil.readrequires(self.vfs, self.supported)
205 except IOError, inst:
205 except IOError, inst:
206 if inst.errno != errno.ENOENT:
206 if inst.errno != errno.ENOENT:
207 raise
207 raise
208 requirements = set()
208 requirements = set()
209
209
210 self.sharedpath = self.path
210 self.sharedpath = self.path
211 try:
211 try:
212 vfs = scmutil.vfs(self.vfs.read("sharedpath").rstrip('\n'),
212 vfs = scmutil.vfs(self.vfs.read("sharedpath").rstrip('\n'),
213 realpath=True)
213 realpath=True)
214 s = vfs.base
214 s = vfs.base
215 if not vfs.exists():
215 if not vfs.exists():
216 raise error.RepoError(
216 raise error.RepoError(
217 _('.hg/sharedpath points to nonexistent directory %s') % s)
217 _('.hg/sharedpath points to nonexistent directory %s') % s)
218 self.sharedpath = s
218 self.sharedpath = s
219 except IOError, inst:
219 except IOError, inst:
220 if inst.errno != errno.ENOENT:
220 if inst.errno != errno.ENOENT:
221 raise
221 raise
222
222
223 self.store = store.store(requirements, self.sharedpath, scmutil.vfs)
223 self.store = store.store(requirements, self.sharedpath, scmutil.vfs)
224 self.spath = self.store.path
224 self.spath = self.store.path
225 self.svfs = self.store.vfs
225 self.svfs = self.store.vfs
226 self.sopener = self.svfs
226 self.sopener = self.svfs
227 self.sjoin = self.store.join
227 self.sjoin = self.store.join
228 self.vfs.createmode = self.store.createmode
228 self.vfs.createmode = self.store.createmode
229 self._applyrequirements(requirements)
229 self._applyrequirements(requirements)
230 if create:
230 if create:
231 self._writerequirements()
231 self._writerequirements()
232
232
233
233
234 self._branchcaches = {}
234 self._branchcaches = {}
235 self.filterpats = {}
235 self.filterpats = {}
236 self._datafilters = {}
236 self._datafilters = {}
237 self._transref = self._lockref = self._wlockref = None
237 self._transref = self._lockref = self._wlockref = None
238
238
239 # A cache for various files under .hg/ that tracks file changes,
239 # A cache for various files under .hg/ that tracks file changes,
240 # (used by the filecache decorator)
240 # (used by the filecache decorator)
241 #
241 #
242 # Maps a property name to its util.filecacheentry
242 # Maps a property name to its util.filecacheentry
243 self._filecache = {}
243 self._filecache = {}
244
244
245 # hold sets of revision to be filtered
245 # hold sets of revision to be filtered
246 # should be cleared when something might have changed the filter value:
246 # should be cleared when something might have changed the filter value:
247 # - new changesets,
247 # - new changesets,
248 # - phase change,
248 # - phase change,
249 # - new obsolescence marker,
249 # - new obsolescence marker,
250 # - working directory parent change,
250 # - working directory parent change,
251 # - bookmark changes
251 # - bookmark changes
252 self.filteredrevcache = {}
252 self.filteredrevcache = {}
253
253
254 def close(self):
254 def close(self):
255 pass
255 pass
256
256
257 def _restrictcapabilities(self, caps):
257 def _restrictcapabilities(self, caps):
258 return caps
258 return caps
259
259
260 def _applyrequirements(self, requirements):
260 def _applyrequirements(self, requirements):
261 self.requirements = requirements
261 self.requirements = requirements
262 self.sopener.options = dict((r, 1) for r in requirements
262 self.sopener.options = dict((r, 1) for r in requirements
263 if r in self.openerreqs)
263 if r in self.openerreqs)
264
264
265 def _writerequirements(self):
265 def _writerequirements(self):
266 reqfile = self.opener("requires", "w")
266 reqfile = self.opener("requires", "w")
267 for r in sorted(self.requirements):
267 for r in sorted(self.requirements):
268 reqfile.write("%s\n" % r)
268 reqfile.write("%s\n" % r)
269 reqfile.close()
269 reqfile.close()
270
270
271 def _checknested(self, path):
271 def _checknested(self, path):
272 """Determine if path is a legal nested repository."""
272 """Determine if path is a legal nested repository."""
273 if not path.startswith(self.root):
273 if not path.startswith(self.root):
274 return False
274 return False
275 subpath = path[len(self.root) + 1:]
275 subpath = path[len(self.root) + 1:]
276 normsubpath = util.pconvert(subpath)
276 normsubpath = util.pconvert(subpath)
277
277
278 # XXX: Checking against the current working copy is wrong in
278 # XXX: Checking against the current working copy is wrong in
279 # the sense that it can reject things like
279 # the sense that it can reject things like
280 #
280 #
281 # $ hg cat -r 10 sub/x.txt
281 # $ hg cat -r 10 sub/x.txt
282 #
282 #
283 # if sub/ is no longer a subrepository in the working copy
283 # if sub/ is no longer a subrepository in the working copy
284 # parent revision.
284 # parent revision.
285 #
285 #
286 # However, it can of course also allow things that would have
286 # However, it can of course also allow things that would have
287 # been rejected before, such as the above cat command if sub/
287 # been rejected before, such as the above cat command if sub/
288 # is a subrepository now, but was a normal directory before.
288 # is a subrepository now, but was a normal directory before.
289 # The old path auditor would have rejected by mistake since it
289 # The old path auditor would have rejected by mistake since it
290 # panics when it sees sub/.hg/.
290 # panics when it sees sub/.hg/.
291 #
291 #
292 # All in all, checking against the working copy seems sensible
292 # All in all, checking against the working copy seems sensible
293 # since we want to prevent access to nested repositories on
293 # since we want to prevent access to nested repositories on
294 # the filesystem *now*.
294 # the filesystem *now*.
295 ctx = self[None]
295 ctx = self[None]
296 parts = util.splitpath(subpath)
296 parts = util.splitpath(subpath)
297 while parts:
297 while parts:
298 prefix = '/'.join(parts)
298 prefix = '/'.join(parts)
299 if prefix in ctx.substate:
299 if prefix in ctx.substate:
300 if prefix == normsubpath:
300 if prefix == normsubpath:
301 return True
301 return True
302 else:
302 else:
303 sub = ctx.sub(prefix)
303 sub = ctx.sub(prefix)
304 return sub.checknested(subpath[len(prefix) + 1:])
304 return sub.checknested(subpath[len(prefix) + 1:])
305 else:
305 else:
306 parts.pop()
306 parts.pop()
307 return False
307 return False
308
308
309 def peer(self):
309 def peer(self):
310 return localpeer(self) # not cached to avoid reference cycle
310 return localpeer(self) # not cached to avoid reference cycle
311
311
312 def unfiltered(self):
312 def unfiltered(self):
313 """Return unfiltered version of the repository
313 """Return unfiltered version of the repository
314
314
315 Intended to be overwritten by filtered repo."""
315 Intended to be overwritten by filtered repo."""
316 return self
316 return self
317
317
318 def filtered(self, name):
318 def filtered(self, name):
319 """Return a filtered version of a repository"""
319 """Return a filtered version of a repository"""
320 # build a new class with the mixin and the current class
320 # build a new class with the mixin and the current class
321 # (possibly subclass of the repo)
321 # (possibly subclass of the repo)
322 class proxycls(repoview.repoview, self.unfiltered().__class__):
322 class proxycls(repoview.repoview, self.unfiltered().__class__):
323 pass
323 pass
324 return proxycls(self, name)
324 return proxycls(self, name)
325
325
326 @repofilecache('bookmarks')
326 @repofilecache('bookmarks')
327 def _bookmarks(self):
327 def _bookmarks(self):
328 return bookmarks.bmstore(self)
328 return bookmarks.bmstore(self)
329
329
330 @repofilecache('bookmarks.current')
330 @repofilecache('bookmarks.current')
331 def _bookmarkcurrent(self):
331 def _bookmarkcurrent(self):
332 return bookmarks.readcurrent(self)
332 return bookmarks.readcurrent(self)
333
333
334 def bookmarkheads(self, bookmark):
334 def bookmarkheads(self, bookmark):
335 name = bookmark.split('@', 1)[0]
335 name = bookmark.split('@', 1)[0]
336 heads = []
336 heads = []
337 for mark, n in self._bookmarks.iteritems():
337 for mark, n in self._bookmarks.iteritems():
338 if mark.split('@', 1)[0] == name:
338 if mark.split('@', 1)[0] == name:
339 heads.append(n)
339 heads.append(n)
340 return heads
340 return heads
341
341
342 @storecache('phaseroots')
342 @storecache('phaseroots')
343 def _phasecache(self):
343 def _phasecache(self):
344 return phases.phasecache(self, self._phasedefaults)
344 return phases.phasecache(self, self._phasedefaults)
345
345
346 @storecache('obsstore')
346 @storecache('obsstore')
347 def obsstore(self):
347 def obsstore(self):
348 store = obsolete.obsstore(self.sopener)
348 store = obsolete.obsstore(self.sopener)
349 if store and not obsolete._enabled:
349 if store and not obsolete._enabled:
350 # message is rare enough to not be translated
350 # message is rare enough to not be translated
351 msg = 'obsolete feature not enabled but %i markers found!\n'
351 msg = 'obsolete feature not enabled but %i markers found!\n'
352 self.ui.warn(msg % len(list(store)))
352 self.ui.warn(msg % len(list(store)))
353 return store
353 return store
354
354
355 @storecache('00changelog.i')
355 @storecache('00changelog.i')
356 def changelog(self):
356 def changelog(self):
357 c = changelog.changelog(self.sopener)
357 c = changelog.changelog(self.sopener)
358 if 'HG_PENDING' in os.environ:
358 if 'HG_PENDING' in os.environ:
359 p = os.environ['HG_PENDING']
359 p = os.environ['HG_PENDING']
360 if p.startswith(self.root):
360 if p.startswith(self.root):
361 c.readpending('00changelog.i.a')
361 c.readpending('00changelog.i.a')
362 return c
362 return c
363
363
364 @storecache('00manifest.i')
364 @storecache('00manifest.i')
365 def manifest(self):
365 def manifest(self):
366 return manifest.manifest(self.sopener)
366 return manifest.manifest(self.sopener)
367
367
368 @repofilecache('dirstate')
368 @repofilecache('dirstate')
369 def dirstate(self):
369 def dirstate(self):
370 warned = [0]
370 warned = [0]
371 def validate(node):
371 def validate(node):
372 try:
372 try:
373 self.changelog.rev(node)
373 self.changelog.rev(node)
374 return node
374 return node
375 except error.LookupError:
375 except error.LookupError:
376 if not warned[0]:
376 if not warned[0]:
377 warned[0] = True
377 warned[0] = True
378 self.ui.warn(_("warning: ignoring unknown"
378 self.ui.warn(_("warning: ignoring unknown"
379 " working parent %s!\n") % short(node))
379 " working parent %s!\n") % short(node))
380 return nullid
380 return nullid
381
381
382 return dirstate.dirstate(self.opener, self.ui, self.root, validate)
382 return dirstate.dirstate(self.opener, self.ui, self.root, validate)
383
383
384 def __getitem__(self, changeid):
384 def __getitem__(self, changeid):
385 if changeid is None:
385 if changeid is None:
386 return context.workingctx(self)
386 return context.workingctx(self)
387 return context.changectx(self, changeid)
387 return context.changectx(self, changeid)
388
388
389 def __contains__(self, changeid):
389 def __contains__(self, changeid):
390 try:
390 try:
391 return bool(self.lookup(changeid))
391 return bool(self.lookup(changeid))
392 except error.RepoLookupError:
392 except error.RepoLookupError:
393 return False
393 return False
394
394
395 def __nonzero__(self):
395 def __nonzero__(self):
396 return True
396 return True
397
397
398 def __len__(self):
398 def __len__(self):
399 return len(self.changelog)
399 return len(self.changelog)
400
400
401 def __iter__(self):
401 def __iter__(self):
402 return iter(self.changelog)
402 return iter(self.changelog)
403
403
404 def revs(self, expr, *args):
404 def revs(self, expr, *args):
405 '''Return a list of revisions matching the given revset'''
405 '''Return a list of revisions matching the given revset'''
406 expr = revset.formatspec(expr, *args)
406 expr = revset.formatspec(expr, *args)
407 m = revset.match(None, expr)
407 m = revset.match(None, expr)
408 return [r for r in m(self, list(self))]
408 return [r for r in m(self, list(self))]
409
409
410 def set(self, expr, *args):
410 def set(self, expr, *args):
411 '''
411 '''
412 Yield a context for each matching revision, after doing arg
412 Yield a context for each matching revision, after doing arg
413 replacement via revset.formatspec
413 replacement via revset.formatspec
414 '''
414 '''
415 for r in self.revs(expr, *args):
415 for r in self.revs(expr, *args):
416 yield self[r]
416 yield self[r]
417
417
418 def url(self):
418 def url(self):
419 return 'file:' + self.root
419 return 'file:' + self.root
420
420
421 def hook(self, name, throw=False, **args):
421 def hook(self, name, throw=False, **args):
422 return hook.hook(self.ui, self, name, throw, **args)
422 return hook.hook(self.ui, self, name, throw, **args)
423
423
424 @unfilteredmethod
424 @unfilteredmethod
425 def _tag(self, names, node, message, local, user, date, extra={}):
425 def _tag(self, names, node, message, local, user, date, extra={}):
426 if isinstance(names, str):
426 if isinstance(names, str):
427 names = (names,)
427 names = (names,)
428
428
429 branches = self.branchmap()
429 branches = self.branchmap()
430 for name in names:
430 for name in names:
431 self.hook('pretag', throw=True, node=hex(node), tag=name,
431 self.hook('pretag', throw=True, node=hex(node), tag=name,
432 local=local)
432 local=local)
433 if name in branches:
433 if name in branches:
434 self.ui.warn(_("warning: tag %s conflicts with existing"
434 self.ui.warn(_("warning: tag %s conflicts with existing"
435 " branch name\n") % name)
435 " branch name\n") % name)
436
436
437 def writetags(fp, names, munge, prevtags):
437 def writetags(fp, names, munge, prevtags):
438 fp.seek(0, 2)
438 fp.seek(0, 2)
439 if prevtags and prevtags[-1] != '\n':
439 if prevtags and prevtags[-1] != '\n':
440 fp.write('\n')
440 fp.write('\n')
441 for name in names:
441 for name in names:
442 m = munge and munge(name) or name
442 m = munge and munge(name) or name
443 if (self._tagscache.tagtypes and
443 if (self._tagscache.tagtypes and
444 name in self._tagscache.tagtypes):
444 name in self._tagscache.tagtypes):
445 old = self.tags().get(name, nullid)
445 old = self.tags().get(name, nullid)
446 fp.write('%s %s\n' % (hex(old), m))
446 fp.write('%s %s\n' % (hex(old), m))
447 fp.write('%s %s\n' % (hex(node), m))
447 fp.write('%s %s\n' % (hex(node), m))
448 fp.close()
448 fp.close()
449
449
450 prevtags = ''
450 prevtags = ''
451 if local:
451 if local:
452 try:
452 try:
453 fp = self.opener('localtags', 'r+')
453 fp = self.opener('localtags', 'r+')
454 except IOError:
454 except IOError:
455 fp = self.opener('localtags', 'a')
455 fp = self.opener('localtags', 'a')
456 else:
456 else:
457 prevtags = fp.read()
457 prevtags = fp.read()
458
458
459 # local tags are stored in the current charset
459 # local tags are stored in the current charset
460 writetags(fp, names, None, prevtags)
460 writetags(fp, names, None, prevtags)
461 for name in names:
461 for name in names:
462 self.hook('tag', node=hex(node), tag=name, local=local)
462 self.hook('tag', node=hex(node), tag=name, local=local)
463 return
463 return
464
464
465 try:
465 try:
466 fp = self.wfile('.hgtags', 'rb+')
466 fp = self.wfile('.hgtags', 'rb+')
467 except IOError, e:
467 except IOError, e:
468 if e.errno != errno.ENOENT:
468 if e.errno != errno.ENOENT:
469 raise
469 raise
470 fp = self.wfile('.hgtags', 'ab')
470 fp = self.wfile('.hgtags', 'ab')
471 else:
471 else:
472 prevtags = fp.read()
472 prevtags = fp.read()
473
473
474 # committed tags are stored in UTF-8
474 # committed tags are stored in UTF-8
475 writetags(fp, names, encoding.fromlocal, prevtags)
475 writetags(fp, names, encoding.fromlocal, prevtags)
476
476
477 fp.close()
477 fp.close()
478
478
479 self.invalidatecaches()
479 self.invalidatecaches()
480
480
481 if '.hgtags' not in self.dirstate:
481 if '.hgtags' not in self.dirstate:
482 self[None].add(['.hgtags'])
482 self[None].add(['.hgtags'])
483
483
484 m = matchmod.exact(self.root, '', ['.hgtags'])
484 m = matchmod.exact(self.root, '', ['.hgtags'])
485 tagnode = self.commit(message, user, date, extra=extra, match=m)
485 tagnode = self.commit(message, user, date, extra=extra, match=m)
486
486
487 for name in names:
487 for name in names:
488 self.hook('tag', node=hex(node), tag=name, local=local)
488 self.hook('tag', node=hex(node), tag=name, local=local)
489
489
490 return tagnode
490 return tagnode
491
491
492 def tag(self, names, node, message, local, user, date):
492 def tag(self, names, node, message, local, user, date):
493 '''tag a revision with one or more symbolic names.
493 '''tag a revision with one or more symbolic names.
494
494
495 names is a list of strings or, when adding a single tag, names may be a
495 names is a list of strings or, when adding a single tag, names may be a
496 string.
496 string.
497
497
498 if local is True, the tags are stored in a per-repository file.
498 if local is True, the tags are stored in a per-repository file.
499 otherwise, they are stored in the .hgtags file, and a new
499 otherwise, they are stored in the .hgtags file, and a new
500 changeset is committed with the change.
500 changeset is committed with the change.
501
501
502 keyword arguments:
502 keyword arguments:
503
503
504 local: whether to store tags in non-version-controlled file
504 local: whether to store tags in non-version-controlled file
505 (default False)
505 (default False)
506
506
507 message: commit message to use if committing
507 message: commit message to use if committing
508
508
509 user: name of user to use if committing
509 user: name of user to use if committing
510
510
511 date: date tuple to use if committing'''
511 date: date tuple to use if committing'''
512
512
513 if not local:
513 if not local:
514 for x in self.status()[:5]:
514 for x in self.status()[:5]:
515 if '.hgtags' in x:
515 if '.hgtags' in x:
516 raise util.Abort(_('working copy of .hgtags is changed '
516 raise util.Abort(_('working copy of .hgtags is changed '
517 '(please commit .hgtags manually)'))
517 '(please commit .hgtags manually)'))
518
518
519 self.tags() # instantiate the cache
519 self.tags() # instantiate the cache
520 self._tag(names, node, message, local, user, date)
520 self._tag(names, node, message, local, user, date)
521
521
522 @filteredpropertycache
522 @filteredpropertycache
523 def _tagscache(self):
523 def _tagscache(self):
524 '''Returns a tagscache object that contains various tags related
524 '''Returns a tagscache object that contains various tags related
525 caches.'''
525 caches.'''
526
526
527 # This simplifies its cache management by having one decorated
527 # This simplifies its cache management by having one decorated
528 # function (this one) and the rest simply fetch things from it.
528 # function (this one) and the rest simply fetch things from it.
529 class tagscache(object):
529 class tagscache(object):
530 def __init__(self):
530 def __init__(self):
531 # These two define the set of tags for this repository. tags
531 # These two define the set of tags for this repository. tags
532 # maps tag name to node; tagtypes maps tag name to 'global' or
532 # maps tag name to node; tagtypes maps tag name to 'global' or
533 # 'local'. (Global tags are defined by .hgtags across all
533 # 'local'. (Global tags are defined by .hgtags across all
534 # heads, and local tags are defined in .hg/localtags.)
534 # heads, and local tags are defined in .hg/localtags.)
535 # They constitute the in-memory cache of tags.
535 # They constitute the in-memory cache of tags.
536 self.tags = self.tagtypes = None
536 self.tags = self.tagtypes = None
537
537
538 self.nodetagscache = self.tagslist = None
538 self.nodetagscache = self.tagslist = None
539
539
540 cache = tagscache()
540 cache = tagscache()
541 cache.tags, cache.tagtypes = self._findtags()
541 cache.tags, cache.tagtypes = self._findtags()
542
542
543 return cache
543 return cache
544
544
545 def tags(self):
545 def tags(self):
546 '''return a mapping of tag to node'''
546 '''return a mapping of tag to node'''
547 t = {}
547 t = {}
548 if self.changelog.filteredrevs:
548 if self.changelog.filteredrevs:
549 tags, tt = self._findtags()
549 tags, tt = self._findtags()
550 else:
550 else:
551 tags = self._tagscache.tags
551 tags = self._tagscache.tags
552 for k, v in tags.iteritems():
552 for k, v in tags.iteritems():
553 try:
553 try:
554 # ignore tags to unknown nodes
554 # ignore tags to unknown nodes
555 self.changelog.rev(v)
555 self.changelog.rev(v)
556 t[k] = v
556 t[k] = v
557 except (error.LookupError, ValueError):
557 except (error.LookupError, ValueError):
558 pass
558 pass
559 return t
559 return t
560
560
561 def _findtags(self):
561 def _findtags(self):
562 '''Do the hard work of finding tags. Return a pair of dicts
562 '''Do the hard work of finding tags. Return a pair of dicts
563 (tags, tagtypes) where tags maps tag name to node, and tagtypes
563 (tags, tagtypes) where tags maps tag name to node, and tagtypes
564 maps tag name to a string like \'global\' or \'local\'.
564 maps tag name to a string like \'global\' or \'local\'.
565 Subclasses or extensions are free to add their own tags, but
565 Subclasses or extensions are free to add their own tags, but
566 should be aware that the returned dicts will be retained for the
566 should be aware that the returned dicts will be retained for the
567 duration of the localrepo object.'''
567 duration of the localrepo object.'''
568
568
569 # XXX what tagtype should subclasses/extensions use? Currently
569 # XXX what tagtype should subclasses/extensions use? Currently
570 # mq and bookmarks add tags, but do not set the tagtype at all.
570 # mq and bookmarks add tags, but do not set the tagtype at all.
571 # Should each extension invent its own tag type? Should there
571 # Should each extension invent its own tag type? Should there
572 # be one tagtype for all such "virtual" tags? Or is the status
572 # be one tagtype for all such "virtual" tags? Or is the status
573 # quo fine?
573 # quo fine?
574
574
575 alltags = {} # map tag name to (node, hist)
575 alltags = {} # map tag name to (node, hist)
576 tagtypes = {}
576 tagtypes = {}
577
577
578 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
578 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
579 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
579 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
580
580
581 # Build the return dicts. Have to re-encode tag names because
581 # Build the return dicts. Have to re-encode tag names because
582 # the tags module always uses UTF-8 (in order not to lose info
582 # the tags module always uses UTF-8 (in order not to lose info
583 # writing to the cache), but the rest of Mercurial wants them in
583 # writing to the cache), but the rest of Mercurial wants them in
584 # local encoding.
584 # local encoding.
585 tags = {}
585 tags = {}
586 for (name, (node, hist)) in alltags.iteritems():
586 for (name, (node, hist)) in alltags.iteritems():
587 if node != nullid:
587 if node != nullid:
588 tags[encoding.tolocal(name)] = node
588 tags[encoding.tolocal(name)] = node
589 tags['tip'] = self.changelog.tip()
589 tags['tip'] = self.changelog.tip()
590 tagtypes = dict([(encoding.tolocal(name), value)
590 tagtypes = dict([(encoding.tolocal(name), value)
591 for (name, value) in tagtypes.iteritems()])
591 for (name, value) in tagtypes.iteritems()])
592 return (tags, tagtypes)
592 return (tags, tagtypes)
593
593
594 def tagtype(self, tagname):
594 def tagtype(self, tagname):
595 '''
595 '''
596 return the type of the given tag. result can be:
596 return the type of the given tag. result can be:
597
597
598 'local' : a local tag
598 'local' : a local tag
599 'global' : a global tag
599 'global' : a global tag
600 None : tag does not exist
600 None : tag does not exist
601 '''
601 '''
602
602
603 return self._tagscache.tagtypes.get(tagname)
603 return self._tagscache.tagtypes.get(tagname)
604
604
605 def tagslist(self):
605 def tagslist(self):
606 '''return a list of tags ordered by revision'''
606 '''return a list of tags ordered by revision'''
607 if not self._tagscache.tagslist:
607 if not self._tagscache.tagslist:
608 l = []
608 l = []
609 for t, n in self.tags().iteritems():
609 for t, n in self.tags().iteritems():
610 r = self.changelog.rev(n)
610 r = self.changelog.rev(n)
611 l.append((r, t, n))
611 l.append((r, t, n))
612 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
612 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
613
613
614 return self._tagscache.tagslist
614 return self._tagscache.tagslist
615
615
616 def nodetags(self, node):
616 def nodetags(self, node):
617 '''return the tags associated with a node'''
617 '''return the tags associated with a node'''
618 if not self._tagscache.nodetagscache:
618 if not self._tagscache.nodetagscache:
619 nodetagscache = {}
619 nodetagscache = {}
620 for t, n in self._tagscache.tags.iteritems():
620 for t, n in self._tagscache.tags.iteritems():
621 nodetagscache.setdefault(n, []).append(t)
621 nodetagscache.setdefault(n, []).append(t)
622 for tags in nodetagscache.itervalues():
622 for tags in nodetagscache.itervalues():
623 tags.sort()
623 tags.sort()
624 self._tagscache.nodetagscache = nodetagscache
624 self._tagscache.nodetagscache = nodetagscache
625 return self._tagscache.nodetagscache.get(node, [])
625 return self._tagscache.nodetagscache.get(node, [])
626
626
627 def nodebookmarks(self, node):
627 def nodebookmarks(self, node):
628 marks = []
628 marks = []
629 for bookmark, n in self._bookmarks.iteritems():
629 for bookmark, n in self._bookmarks.iteritems():
630 if n == node:
630 if n == node:
631 marks.append(bookmark)
631 marks.append(bookmark)
632 return sorted(marks)
632 return sorted(marks)
633
633
634 def branchmap(self):
634 def branchmap(self):
635 '''returns a dictionary {branch: [branchheads]}'''
635 '''returns a dictionary {branch: [branchheads]}'''
636 branchmap.updatecache(self)
636 branchmap.updatecache(self)
637 return self._branchcaches[self.filtername]
637 return self._branchcaches[self.filtername]
638
638
639
639
640 def _branchtip(self, heads):
640 def _branchtip(self, heads):
641 '''return the tipmost branch head in heads'''
641 '''return the tipmost branch head in heads'''
642 tip = heads[-1]
642 tip = heads[-1]
643 for h in reversed(heads):
643 for h in reversed(heads):
644 if not self[h].closesbranch():
644 if not self[h].closesbranch():
645 tip = h
645 tip = h
646 break
646 break
647 return tip
647 return tip
648
648
649 def branchtip(self, branch):
649 def branchtip(self, branch):
650 '''return the tip node for a given branch'''
650 '''return the tip node for a given branch'''
651 if branch not in self.branchmap():
651 if branch not in self.branchmap():
652 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
652 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
653 return self._branchtip(self.branchmap()[branch])
653 return self._branchtip(self.branchmap()[branch])
654
654
655 def branchtags(self):
655 def branchtags(self):
656 '''return a dict where branch names map to the tipmost head of
656 '''return a dict where branch names map to the tipmost head of
657 the branch, open heads come before closed'''
657 the branch, open heads come before closed'''
658 bt = {}
658 bt = {}
659 for bn, heads in self.branchmap().iteritems():
659 for bn, heads in self.branchmap().iteritems():
660 bt[bn] = self._branchtip(heads)
660 bt[bn] = self._branchtip(heads)
661 return bt
661 return bt
662
662
663 def lookup(self, key):
663 def lookup(self, key):
664 return self[key].node()
664 return self[key].node()
665
665
666 def lookupbranch(self, key, remote=None):
666 def lookupbranch(self, key, remote=None):
667 repo = remote or self
667 repo = remote or self
668 if key in repo.branchmap():
668 if key in repo.branchmap():
669 return key
669 return key
670
670
671 repo = (remote and remote.local()) and remote or self
671 repo = (remote and remote.local()) and remote or self
672 return repo[key].branch()
672 return repo[key].branch()
673
673
674 def known(self, nodes):
674 def known(self, nodes):
675 nm = self.changelog.nodemap
675 nm = self.changelog.nodemap
676 pc = self._phasecache
676 pc = self._phasecache
677 result = []
677 result = []
678 for n in nodes:
678 for n in nodes:
679 r = nm.get(n)
679 r = nm.get(n)
680 resp = not (r is None or pc.phase(self, r) >= phases.secret)
680 resp = not (r is None or pc.phase(self, r) >= phases.secret)
681 result.append(resp)
681 result.append(resp)
682 return result
682 return result
683
683
684 def local(self):
684 def local(self):
685 return self
685 return self
686
686
687 def cancopy(self):
687 def cancopy(self):
688 return self.local() # so statichttprepo's override of local() works
688 return self.local() # so statichttprepo's override of local() works
689
689
690 def join(self, f):
690 def join(self, f):
691 return os.path.join(self.path, f)
691 return os.path.join(self.path, f)
692
692
693 def wjoin(self, f):
693 def wjoin(self, f):
694 return os.path.join(self.root, f)
694 return os.path.join(self.root, f)
695
695
696 def file(self, f):
696 def file(self, f):
697 if f[0] == '/':
697 if f[0] == '/':
698 f = f[1:]
698 f = f[1:]
699 return filelog.filelog(self.sopener, f)
699 return filelog.filelog(self.sopener, f)
700
700
701 def changectx(self, changeid):
701 def changectx(self, changeid):
702 return self[changeid]
702 return self[changeid]
703
703
704 def parents(self, changeid=None):
704 def parents(self, changeid=None):
705 '''get list of changectxs for parents of changeid'''
705 '''get list of changectxs for parents of changeid'''
706 return self[changeid].parents()
706 return self[changeid].parents()
707
707
708 def setparents(self, p1, p2=nullid):
708 def setparents(self, p1, p2=nullid):
709 copies = self.dirstate.setparents(p1, p2)
709 copies = self.dirstate.setparents(p1, p2)
710 pctx = self[p1]
710 pctx = self[p1]
711 if copies:
711 if copies:
712 # Adjust copy records, the dirstate cannot do it, it
712 # Adjust copy records, the dirstate cannot do it, it
713 # requires access to parents manifests. Preserve them
713 # requires access to parents manifests. Preserve them
714 # only for entries added to first parent.
714 # only for entries added to first parent.
715 for f in copies:
715 for f in copies:
716 if f not in pctx and copies[f] in pctx:
716 if f not in pctx and copies[f] in pctx:
717 self.dirstate.copy(copies[f], f)
717 self.dirstate.copy(copies[f], f)
718 if p2 == nullid:
718 if p2 == nullid:
719 for f, s in sorted(self.dirstate.copies().items()):
719 for f, s in sorted(self.dirstate.copies().items()):
720 if f not in pctx and s not in pctx:
720 if f not in pctx and s not in pctx:
721 self.dirstate.copy(None, f)
721 self.dirstate.copy(None, f)
722
722
723 def filectx(self, path, changeid=None, fileid=None):
723 def filectx(self, path, changeid=None, fileid=None):
724 """changeid can be a changeset revision, node, or tag.
724 """changeid can be a changeset revision, node, or tag.
725 fileid can be a file revision or node."""
725 fileid can be a file revision or node."""
726 return context.filectx(self, path, changeid, fileid)
726 return context.filectx(self, path, changeid, fileid)
727
727
728 def getcwd(self):
728 def getcwd(self):
729 return self.dirstate.getcwd()
729 return self.dirstate.getcwd()
730
730
731 def pathto(self, f, cwd=None):
731 def pathto(self, f, cwd=None):
732 return self.dirstate.pathto(f, cwd)
732 return self.dirstate.pathto(f, cwd)
733
733
734 def wfile(self, f, mode='r'):
734 def wfile(self, f, mode='r'):
735 return self.wopener(f, mode)
735 return self.wopener(f, mode)
736
736
737 def _link(self, f):
737 def _link(self, f):
738 return self.wvfs.islink(f)
738 return self.wvfs.islink(f)
739
739
740 def _loadfilter(self, filter):
740 def _loadfilter(self, filter):
741 if filter not in self.filterpats:
741 if filter not in self.filterpats:
742 l = []
742 l = []
743 for pat, cmd in self.ui.configitems(filter):
743 for pat, cmd in self.ui.configitems(filter):
744 if cmd == '!':
744 if cmd == '!':
745 continue
745 continue
746 mf = matchmod.match(self.root, '', [pat])
746 mf = matchmod.match(self.root, '', [pat])
747 fn = None
747 fn = None
748 params = cmd
748 params = cmd
749 for name, filterfn in self._datafilters.iteritems():
749 for name, filterfn in self._datafilters.iteritems():
750 if cmd.startswith(name):
750 if cmd.startswith(name):
751 fn = filterfn
751 fn = filterfn
752 params = cmd[len(name):].lstrip()
752 params = cmd[len(name):].lstrip()
753 break
753 break
754 if not fn:
754 if not fn:
755 fn = lambda s, c, **kwargs: util.filter(s, c)
755 fn = lambda s, c, **kwargs: util.filter(s, c)
756 # Wrap old filters not supporting keyword arguments
756 # Wrap old filters not supporting keyword arguments
757 if not inspect.getargspec(fn)[2]:
757 if not inspect.getargspec(fn)[2]:
758 oldfn = fn
758 oldfn = fn
759 fn = lambda s, c, **kwargs: oldfn(s, c)
759 fn = lambda s, c, **kwargs: oldfn(s, c)
760 l.append((mf, fn, params))
760 l.append((mf, fn, params))
761 self.filterpats[filter] = l
761 self.filterpats[filter] = l
762 return self.filterpats[filter]
762 return self.filterpats[filter]
763
763
764 def _filter(self, filterpats, filename, data):
764 def _filter(self, filterpats, filename, data):
765 for mf, fn, cmd in filterpats:
765 for mf, fn, cmd in filterpats:
766 if mf(filename):
766 if mf(filename):
767 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
767 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
768 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
768 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
769 break
769 break
770
770
771 return data
771 return data
772
772
773 @unfilteredpropertycache
773 @unfilteredpropertycache
774 def _encodefilterpats(self):
774 def _encodefilterpats(self):
775 return self._loadfilter('encode')
775 return self._loadfilter('encode')
776
776
777 @unfilteredpropertycache
777 @unfilteredpropertycache
778 def _decodefilterpats(self):
778 def _decodefilterpats(self):
779 return self._loadfilter('decode')
779 return self._loadfilter('decode')
780
780
781 def adddatafilter(self, name, filter):
781 def adddatafilter(self, name, filter):
782 self._datafilters[name] = filter
782 self._datafilters[name] = filter
783
783
784 def wread(self, filename):
784 def wread(self, filename):
785 if self._link(filename):
785 if self._link(filename):
786 data = self.wvfs.readlink(filename)
786 data = self.wvfs.readlink(filename)
787 else:
787 else:
788 data = self.wopener.read(filename)
788 data = self.wopener.read(filename)
789 return self._filter(self._encodefilterpats, filename, data)
789 return self._filter(self._encodefilterpats, filename, data)
790
790
791 def wwrite(self, filename, data, flags):
791 def wwrite(self, filename, data, flags):
792 data = self._filter(self._decodefilterpats, filename, data)
792 data = self._filter(self._decodefilterpats, filename, data)
793 if 'l' in flags:
793 if 'l' in flags:
794 self.wopener.symlink(data, filename)
794 self.wopener.symlink(data, filename)
795 else:
795 else:
796 self.wopener.write(filename, data)
796 self.wopener.write(filename, data)
797 if 'x' in flags:
797 if 'x' in flags:
798 self.wvfs.setflags(filename, False, True)
798 self.wvfs.setflags(filename, False, True)
799
799
800 def wwritedata(self, filename, data):
800 def wwritedata(self, filename, data):
801 return self._filter(self._decodefilterpats, filename, data)
801 return self._filter(self._decodefilterpats, filename, data)
802
802
803 def transaction(self, desc):
803 def transaction(self, desc):
804 tr = self._transref and self._transref() or None
804 tr = self._transref and self._transref() or None
805 if tr and tr.running():
805 if tr and tr.running():
806 return tr.nest()
806 return tr.nest()
807
807
808 # abort here if the journal already exists
808 # abort here if the journal already exists
809 if self.svfs.exists("journal"):
809 if self.svfs.exists("journal"):
810 raise error.RepoError(
810 raise error.RepoError(
811 _("abandoned transaction found - run hg recover"))
811 _("abandoned transaction found - run hg recover"))
812
812
813 self._writejournal(desc)
813 self._writejournal(desc)
814 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
814 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
815
815
816 tr = transaction.transaction(self.ui.warn, self.sopener,
816 tr = transaction.transaction(self.ui.warn, self.sopener,
817 self.sjoin("journal"),
817 self.sjoin("journal"),
818 aftertrans(renames),
818 aftertrans(renames),
819 self.store.createmode)
819 self.store.createmode)
820 self._transref = weakref.ref(tr)
820 self._transref = weakref.ref(tr)
821 return tr
821 return tr
822
822
823 def _journalfiles(self):
823 def _journalfiles(self):
824 return ((self.svfs, 'journal'),
824 return ((self.svfs, 'journal'),
825 (self.vfs, 'journal.dirstate'),
825 (self.vfs, 'journal.dirstate'),
826 (self.vfs, 'journal.branch'),
826 (self.vfs, 'journal.branch'),
827 (self.vfs, 'journal.desc'),
827 (self.vfs, 'journal.desc'),
828 (self.vfs, 'journal.bookmarks'),
828 (self.vfs, 'journal.bookmarks'),
829 (self.svfs, 'journal.phaseroots'))
829 (self.svfs, 'journal.phaseroots'))
830
830
831 def undofiles(self):
831 def undofiles(self):
832 return [vfs.join(undoname(x)) for vfs, x in self._journalfiles()]
832 return [vfs.join(undoname(x)) for vfs, x in self._journalfiles()]
833
833
834 def _writejournal(self, desc):
834 def _writejournal(self, desc):
835 self.opener.write("journal.dirstate",
835 self.opener.write("journal.dirstate",
836 self.opener.tryread("dirstate"))
836 self.opener.tryread("dirstate"))
837 self.opener.write("journal.branch",
837 self.opener.write("journal.branch",
838 encoding.fromlocal(self.dirstate.branch()))
838 encoding.fromlocal(self.dirstate.branch()))
839 self.opener.write("journal.desc",
839 self.opener.write("journal.desc",
840 "%d\n%s\n" % (len(self), desc))
840 "%d\n%s\n" % (len(self), desc))
841 self.opener.write("journal.bookmarks",
841 self.opener.write("journal.bookmarks",
842 self.opener.tryread("bookmarks"))
842 self.opener.tryread("bookmarks"))
843 self.sopener.write("journal.phaseroots",
843 self.sopener.write("journal.phaseroots",
844 self.sopener.tryread("phaseroots"))
844 self.sopener.tryread("phaseroots"))
845
845
846 def recover(self):
846 def recover(self):
847 lock = self.lock()
847 lock = self.lock()
848 try:
848 try:
849 if self.svfs.exists("journal"):
849 if self.svfs.exists("journal"):
850 self.ui.status(_("rolling back interrupted transaction\n"))
850 self.ui.status(_("rolling back interrupted transaction\n"))
851 transaction.rollback(self.sopener, self.sjoin("journal"),
851 transaction.rollback(self.sopener, self.sjoin("journal"),
852 self.ui.warn)
852 self.ui.warn)
853 self.invalidate()
853 self.invalidate()
854 return True
854 return True
855 else:
855 else:
856 self.ui.warn(_("no interrupted transaction available\n"))
856 self.ui.warn(_("no interrupted transaction available\n"))
857 return False
857 return False
858 finally:
858 finally:
859 lock.release()
859 lock.release()
860
860
861 def rollback(self, dryrun=False, force=False):
861 def rollback(self, dryrun=False, force=False):
862 wlock = lock = None
862 wlock = lock = None
863 try:
863 try:
864 wlock = self.wlock()
864 wlock = self.wlock()
865 lock = self.lock()
865 lock = self.lock()
866 if self.svfs.exists("undo"):
866 if self.svfs.exists("undo"):
867 return self._rollback(dryrun, force)
867 return self._rollback(dryrun, force)
868 else:
868 else:
869 self.ui.warn(_("no rollback information available\n"))
869 self.ui.warn(_("no rollback information available\n"))
870 return 1
870 return 1
871 finally:
871 finally:
872 release(lock, wlock)
872 release(lock, wlock)
873
873
874 @unfilteredmethod # Until we get smarter cache management
874 @unfilteredmethod # Until we get smarter cache management
875 def _rollback(self, dryrun, force):
875 def _rollback(self, dryrun, force):
876 ui = self.ui
876 ui = self.ui
877 try:
877 try:
878 args = self.opener.read('undo.desc').splitlines()
878 args = self.opener.read('undo.desc').splitlines()
879 (oldlen, desc, detail) = (int(args[0]), args[1], None)
879 (oldlen, desc, detail) = (int(args[0]), args[1], None)
880 if len(args) >= 3:
880 if len(args) >= 3:
881 detail = args[2]
881 detail = args[2]
882 oldtip = oldlen - 1
882 oldtip = oldlen - 1
883
883
884 if detail and ui.verbose:
884 if detail and ui.verbose:
885 msg = (_('repository tip rolled back to revision %s'
885 msg = (_('repository tip rolled back to revision %s'
886 ' (undo %s: %s)\n')
886 ' (undo %s: %s)\n')
887 % (oldtip, desc, detail))
887 % (oldtip, desc, detail))
888 else:
888 else:
889 msg = (_('repository tip rolled back to revision %s'
889 msg = (_('repository tip rolled back to revision %s'
890 ' (undo %s)\n')
890 ' (undo %s)\n')
891 % (oldtip, desc))
891 % (oldtip, desc))
892 except IOError:
892 except IOError:
893 msg = _('rolling back unknown transaction\n')
893 msg = _('rolling back unknown transaction\n')
894 desc = None
894 desc = None
895
895
896 if not force and self['.'] != self['tip'] and desc == 'commit':
896 if not force and self['.'] != self['tip'] and desc == 'commit':
897 raise util.Abort(
897 raise util.Abort(
898 _('rollback of last commit while not checked out '
898 _('rollback of last commit while not checked out '
899 'may lose data'), hint=_('use -f to force'))
899 'may lose data'), hint=_('use -f to force'))
900
900
901 ui.status(msg)
901 ui.status(msg)
902 if dryrun:
902 if dryrun:
903 return 0
903 return 0
904
904
905 parents = self.dirstate.parents()
905 parents = self.dirstate.parents()
906 self.destroying()
906 self.destroying()
907 transaction.rollback(self.sopener, self.sjoin('undo'), ui.warn)
907 transaction.rollback(self.sopener, self.sjoin('undo'), ui.warn)
908 if self.vfs.exists('undo.bookmarks'):
908 if self.vfs.exists('undo.bookmarks'):
909 self.vfs.rename('undo.bookmarks', 'bookmarks')
909 self.vfs.rename('undo.bookmarks', 'bookmarks')
910 if self.svfs.exists('undo.phaseroots'):
910 if self.svfs.exists('undo.phaseroots'):
911 self.svfs.rename('undo.phaseroots', 'phaseroots')
911 self.svfs.rename('undo.phaseroots', 'phaseroots')
912 self.invalidate()
912 self.invalidate()
913
913
914 parentgone = (parents[0] not in self.changelog.nodemap or
914 parentgone = (parents[0] not in self.changelog.nodemap or
915 parents[1] not in self.changelog.nodemap)
915 parents[1] not in self.changelog.nodemap)
916 if parentgone:
916 if parentgone:
917 self.vfs.rename('undo.dirstate', 'dirstate')
917 self.vfs.rename('undo.dirstate', 'dirstate')
918 try:
918 try:
919 branch = self.opener.read('undo.branch')
919 branch = self.opener.read('undo.branch')
920 self.dirstate.setbranch(encoding.tolocal(branch))
920 self.dirstate.setbranch(encoding.tolocal(branch))
921 except IOError:
921 except IOError:
922 ui.warn(_('named branch could not be reset: '
922 ui.warn(_('named branch could not be reset: '
923 'current branch is still \'%s\'\n')
923 'current branch is still \'%s\'\n')
924 % self.dirstate.branch())
924 % self.dirstate.branch())
925
925
926 self.dirstate.invalidate()
926 self.dirstate.invalidate()
927 parents = tuple([p.rev() for p in self.parents()])
927 parents = tuple([p.rev() for p in self.parents()])
928 if len(parents) > 1:
928 if len(parents) > 1:
929 ui.status(_('working directory now based on '
929 ui.status(_('working directory now based on '
930 'revisions %d and %d\n') % parents)
930 'revisions %d and %d\n') % parents)
931 else:
931 else:
932 ui.status(_('working directory now based on '
932 ui.status(_('working directory now based on '
933 'revision %d\n') % parents)
933 'revision %d\n') % parents)
934 # TODO: if we know which new heads may result from this rollback, pass
934 # TODO: if we know which new heads may result from this rollback, pass
935 # them to destroy(), which will prevent the branchhead cache from being
935 # them to destroy(), which will prevent the branchhead cache from being
936 # invalidated.
936 # invalidated.
937 self.destroyed()
937 self.destroyed()
938 return 0
938 return 0
939
939
940 def invalidatecaches(self):
940 def invalidatecaches(self):
941
941
942 if '_tagscache' in vars(self):
942 if '_tagscache' in vars(self):
943 # can't use delattr on proxy
943 # can't use delattr on proxy
944 del self.__dict__['_tagscache']
944 del self.__dict__['_tagscache']
945
945
946 self.unfiltered()._branchcaches.clear()
946 self.unfiltered()._branchcaches.clear()
947 self.invalidatevolatilesets()
947 self.invalidatevolatilesets()
948
948
949 def invalidatevolatilesets(self):
949 def invalidatevolatilesets(self):
950 self.filteredrevcache.clear()
950 self.filteredrevcache.clear()
951 obsolete.clearobscaches(self)
951 obsolete.clearobscaches(self)
952
952
953 def invalidatedirstate(self):
953 def invalidatedirstate(self):
954 '''Invalidates the dirstate, causing the next call to dirstate
954 '''Invalidates the dirstate, causing the next call to dirstate
955 to check if it was modified since the last time it was read,
955 to check if it was modified since the last time it was read,
956 rereading it if it has.
956 rereading it if it has.
957
957
958 This is different to dirstate.invalidate() that it doesn't always
958 This is different to dirstate.invalidate() that it doesn't always
959 rereads the dirstate. Use dirstate.invalidate() if you want to
959 rereads the dirstate. Use dirstate.invalidate() if you want to
960 explicitly read the dirstate again (i.e. restoring it to a previous
960 explicitly read the dirstate again (i.e. restoring it to a previous
961 known good state).'''
961 known good state).'''
962 if hasunfilteredcache(self, 'dirstate'):
962 if hasunfilteredcache(self, 'dirstate'):
963 for k in self.dirstate._filecache:
963 for k in self.dirstate._filecache:
964 try:
964 try:
965 delattr(self.dirstate, k)
965 delattr(self.dirstate, k)
966 except AttributeError:
966 except AttributeError:
967 pass
967 pass
968 delattr(self.unfiltered(), 'dirstate')
968 delattr(self.unfiltered(), 'dirstate')
969
969
970 def invalidate(self):
970 def invalidate(self):
971 unfiltered = self.unfiltered() # all file caches are stored unfiltered
971 unfiltered = self.unfiltered() # all file caches are stored unfiltered
972 for k in self._filecache:
972 for k in self._filecache:
973 # dirstate is invalidated separately in invalidatedirstate()
973 # dirstate is invalidated separately in invalidatedirstate()
974 if k == 'dirstate':
974 if k == 'dirstate':
975 continue
975 continue
976
976
977 try:
977 try:
978 delattr(unfiltered, k)
978 delattr(unfiltered, k)
979 except AttributeError:
979 except AttributeError:
980 pass
980 pass
981 self.invalidatecaches()
981 self.invalidatecaches()
982
982
983 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
983 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
984 try:
984 try:
985 l = lock.lock(lockname, 0, releasefn, desc=desc)
985 l = lock.lock(lockname, 0, releasefn, desc=desc)
986 except error.LockHeld, inst:
986 except error.LockHeld, inst:
987 if not wait:
987 if not wait:
988 raise
988 raise
989 self.ui.warn(_("waiting for lock on %s held by %r\n") %
989 self.ui.warn(_("waiting for lock on %s held by %r\n") %
990 (desc, inst.locker))
990 (desc, inst.locker))
991 # default to 600 seconds timeout
991 # default to 600 seconds timeout
992 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
992 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
993 releasefn, desc=desc)
993 releasefn, desc=desc)
994 if acquirefn:
994 if acquirefn:
995 acquirefn()
995 acquirefn()
996 return l
996 return l
997
997
998 def _afterlock(self, callback):
998 def _afterlock(self, callback):
999 """add a callback to the current repository lock.
999 """add a callback to the current repository lock.
1000
1000
1001 The callback will be executed on lock release."""
1001 The callback will be executed on lock release."""
1002 l = self._lockref and self._lockref()
1002 l = self._lockref and self._lockref()
1003 if l:
1003 if l:
1004 l.postrelease.append(callback)
1004 l.postrelease.append(callback)
1005 else:
1005 else:
1006 callback()
1006 callback()
1007
1007
1008 def lock(self, wait=True):
1008 def lock(self, wait=True):
1009 '''Lock the repository store (.hg/store) and return a weak reference
1009 '''Lock the repository store (.hg/store) and return a weak reference
1010 to the lock. Use this before modifying the store (e.g. committing or
1010 to the lock. Use this before modifying the store (e.g. committing or
1011 stripping). If you are opening a transaction, get a lock as well.)'''
1011 stripping). If you are opening a transaction, get a lock as well.)'''
1012 l = self._lockref and self._lockref()
1012 l = self._lockref and self._lockref()
1013 if l is not None and l.held:
1013 if l is not None and l.held:
1014 l.lock()
1014 l.lock()
1015 return l
1015 return l
1016
1016
1017 def unlock():
1017 def unlock():
1018 self.store.write()
1018 self.store.write()
1019 if hasunfilteredcache(self, '_phasecache'):
1019 if hasunfilteredcache(self, '_phasecache'):
1020 self._phasecache.write()
1020 self._phasecache.write()
1021 for k, ce in self._filecache.items():
1021 for k, ce in self._filecache.items():
1022 if k == 'dirstate' or k not in self.__dict__:
1022 if k == 'dirstate' or k not in self.__dict__:
1023 continue
1023 continue
1024 ce.refresh()
1024 ce.refresh()
1025
1025
1026 l = self._lock(self.sjoin("lock"), wait, unlock,
1026 l = self._lock(self.sjoin("lock"), wait, unlock,
1027 self.invalidate, _('repository %s') % self.origroot)
1027 self.invalidate, _('repository %s') % self.origroot)
1028 self._lockref = weakref.ref(l)
1028 self._lockref = weakref.ref(l)
1029 return l
1029 return l
1030
1030
1031 def wlock(self, wait=True):
1031 def wlock(self, wait=True):
1032 '''Lock the non-store parts of the repository (everything under
1032 '''Lock the non-store parts of the repository (everything under
1033 .hg except .hg/store) and return a weak reference to the lock.
1033 .hg except .hg/store) and return a weak reference to the lock.
1034 Use this before modifying files in .hg.'''
1034 Use this before modifying files in .hg.'''
1035 l = self._wlockref and self._wlockref()
1035 l = self._wlockref and self._wlockref()
1036 if l is not None and l.held:
1036 if l is not None and l.held:
1037 l.lock()
1037 l.lock()
1038 return l
1038 return l
1039
1039
1040 def unlock():
1040 def unlock():
1041 self.dirstate.write()
1041 self.dirstate.write()
1042 self._filecache['dirstate'].refresh()
1042 self._filecache['dirstate'].refresh()
1043
1043
1044 l = self._lock(self.join("wlock"), wait, unlock,
1044 l = self._lock(self.join("wlock"), wait, unlock,
1045 self.invalidatedirstate, _('working directory of %s') %
1045 self.invalidatedirstate, _('working directory of %s') %
1046 self.origroot)
1046 self.origroot)
1047 self._wlockref = weakref.ref(l)
1047 self._wlockref = weakref.ref(l)
1048 return l
1048 return l
1049
1049
1050 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1050 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1051 """
1051 """
1052 commit an individual file as part of a larger transaction
1052 commit an individual file as part of a larger transaction
1053 """
1053 """
1054
1054
1055 fname = fctx.path()
1055 fname = fctx.path()
1056 text = fctx.data()
1056 text = fctx.data()
1057 flog = self.file(fname)
1057 flog = self.file(fname)
1058 fparent1 = manifest1.get(fname, nullid)
1058 fparent1 = manifest1.get(fname, nullid)
1059 fparent2 = fparent2o = manifest2.get(fname, nullid)
1059 fparent2 = fparent2o = manifest2.get(fname, nullid)
1060
1060
1061 meta = {}
1061 meta = {}
1062 copy = fctx.renamed()
1062 copy = fctx.renamed()
1063 if copy and copy[0] != fname:
1063 if copy and copy[0] != fname:
1064 # Mark the new revision of this file as a copy of another
1064 # Mark the new revision of this file as a copy of another
1065 # file. This copy data will effectively act as a parent
1065 # file. This copy data will effectively act as a parent
1066 # of this new revision. If this is a merge, the first
1066 # of this new revision. If this is a merge, the first
1067 # parent will be the nullid (meaning "look up the copy data")
1067 # parent will be the nullid (meaning "look up the copy data")
1068 # and the second one will be the other parent. For example:
1068 # and the second one will be the other parent. For example:
1069 #
1069 #
1070 # 0 --- 1 --- 3 rev1 changes file foo
1070 # 0 --- 1 --- 3 rev1 changes file foo
1071 # \ / rev2 renames foo to bar and changes it
1071 # \ / rev2 renames foo to bar and changes it
1072 # \- 2 -/ rev3 should have bar with all changes and
1072 # \- 2 -/ rev3 should have bar with all changes and
1073 # should record that bar descends from
1073 # should record that bar descends from
1074 # bar in rev2 and foo in rev1
1074 # bar in rev2 and foo in rev1
1075 #
1075 #
1076 # this allows this merge to succeed:
1076 # this allows this merge to succeed:
1077 #
1077 #
1078 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1078 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1079 # \ / merging rev3 and rev4 should use bar@rev2
1079 # \ / merging rev3 and rev4 should use bar@rev2
1080 # \- 2 --- 4 as the merge base
1080 # \- 2 --- 4 as the merge base
1081 #
1081 #
1082
1082
1083 cfname = copy[0]
1083 cfname = copy[0]
1084 crev = manifest1.get(cfname)
1084 crev = manifest1.get(cfname)
1085 newfparent = fparent2
1085 newfparent = fparent2
1086
1086
1087 if manifest2: # branch merge
1087 if manifest2: # branch merge
1088 if fparent2 == nullid or crev is None: # copied on remote side
1088 if fparent2 == nullid or crev is None: # copied on remote side
1089 if cfname in manifest2:
1089 if cfname in manifest2:
1090 crev = manifest2[cfname]
1090 crev = manifest2[cfname]
1091 newfparent = fparent1
1091 newfparent = fparent1
1092
1092
1093 # find source in nearest ancestor if we've lost track
1093 # find source in nearest ancestor if we've lost track
1094 if not crev:
1094 if not crev:
1095 self.ui.debug(" %s: searching for copy revision for %s\n" %
1095 self.ui.debug(" %s: searching for copy revision for %s\n" %
1096 (fname, cfname))
1096 (fname, cfname))
1097 for ancestor in self[None].ancestors():
1097 for ancestor in self[None].ancestors():
1098 if cfname in ancestor:
1098 if cfname in ancestor:
1099 crev = ancestor[cfname].filenode()
1099 crev = ancestor[cfname].filenode()
1100 break
1100 break
1101
1101
1102 if crev:
1102 if crev:
1103 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1103 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1104 meta["copy"] = cfname
1104 meta["copy"] = cfname
1105 meta["copyrev"] = hex(crev)
1105 meta["copyrev"] = hex(crev)
1106 fparent1, fparent2 = nullid, newfparent
1106 fparent1, fparent2 = nullid, newfparent
1107 else:
1107 else:
1108 self.ui.warn(_("warning: can't find ancestor for '%s' "
1108 self.ui.warn(_("warning: can't find ancestor for '%s' "
1109 "copied from '%s'!\n") % (fname, cfname))
1109 "copied from '%s'!\n") % (fname, cfname))
1110
1110
1111 elif fparent2 != nullid:
1111 elif fparent2 != nullid:
1112 # is one parent an ancestor of the other?
1112 # is one parent an ancestor of the other?
1113 fparentancestor = flog.ancestor(fparent1, fparent2)
1113 fparentancestor = flog.ancestor(fparent1, fparent2)
1114 if fparentancestor == fparent1:
1114 if fparentancestor == fparent1:
1115 fparent1, fparent2 = fparent2, nullid
1115 fparent1, fparent2 = fparent2, nullid
1116 elif fparentancestor == fparent2:
1116 elif fparentancestor == fparent2:
1117 fparent2 = nullid
1117 fparent2 = nullid
1118
1118
1119 # is the file changed?
1119 # is the file changed?
1120 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1120 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1121 changelist.append(fname)
1121 changelist.append(fname)
1122 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1122 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1123
1123
1124 # are just the flags changed during merge?
1124 # are just the flags changed during merge?
1125 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
1125 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
1126 changelist.append(fname)
1126 changelist.append(fname)
1127
1127
1128 return fparent1
1128 return fparent1
1129
1129
1130 @unfilteredmethod
1130 @unfilteredmethod
1131 def commit(self, text="", user=None, date=None, match=None, force=False,
1131 def commit(self, text="", user=None, date=None, match=None, force=False,
1132 editor=False, extra={}):
1132 editor=False, extra={}):
1133 """Add a new revision to current repository.
1133 """Add a new revision to current repository.
1134
1134
1135 Revision information is gathered from the working directory,
1135 Revision information is gathered from the working directory,
1136 match can be used to filter the committed files. If editor is
1136 match can be used to filter the committed files. If editor is
1137 supplied, it is called to get a commit message.
1137 supplied, it is called to get a commit message.
1138 """
1138 """
1139
1139
1140 def fail(f, msg):
1140 def fail(f, msg):
1141 raise util.Abort('%s: %s' % (f, msg))
1141 raise util.Abort('%s: %s' % (f, msg))
1142
1142
1143 if not match:
1143 if not match:
1144 match = matchmod.always(self.root, '')
1144 match = matchmod.always(self.root, '')
1145
1145
1146 if not force:
1146 if not force:
1147 vdirs = []
1147 vdirs = []
1148 match.explicitdir = vdirs.append
1148 match.explicitdir = vdirs.append
1149 match.bad = fail
1149 match.bad = fail
1150
1150
1151 wlock = self.wlock()
1151 wlock = self.wlock()
1152 try:
1152 try:
1153 wctx = self[None]
1153 wctx = self[None]
1154 merge = len(wctx.parents()) > 1
1154 merge = len(wctx.parents()) > 1
1155
1155
1156 if (not force and merge and match and
1156 if (not force and merge and match and
1157 (match.files() or match.anypats())):
1157 (match.files() or match.anypats())):
1158 raise util.Abort(_('cannot partially commit a merge '
1158 raise util.Abort(_('cannot partially commit a merge '
1159 '(do not specify files or patterns)'))
1159 '(do not specify files or patterns)'))
1160
1160
1161 changes = self.status(match=match, clean=force)
1161 changes = self.status(match=match, clean=force)
1162 if force:
1162 if force:
1163 changes[0].extend(changes[6]) # mq may commit unchanged files
1163 changes[0].extend(changes[6]) # mq may commit unchanged files
1164
1164
1165 # check subrepos
1165 # check subrepos
1166 subs = []
1166 subs = []
1167 commitsubs = set()
1167 commitsubs = set()
1168 newstate = wctx.substate.copy()
1168 newstate = wctx.substate.copy()
1169 # only manage subrepos and .hgsubstate if .hgsub is present
1169 # only manage subrepos and .hgsubstate if .hgsub is present
1170 if '.hgsub' in wctx:
1170 if '.hgsub' in wctx:
1171 # we'll decide whether to track this ourselves, thanks
1171 # we'll decide whether to track this ourselves, thanks
1172 if '.hgsubstate' in changes[0]:
1172 if '.hgsubstate' in changes[0]:
1173 changes[0].remove('.hgsubstate')
1173 changes[0].remove('.hgsubstate')
1174 if '.hgsubstate' in changes[2]:
1174 if '.hgsubstate' in changes[2]:
1175 changes[2].remove('.hgsubstate')
1175 changes[2].remove('.hgsubstate')
1176
1176
1177 # compare current state to last committed state
1177 # compare current state to last committed state
1178 # build new substate based on last committed state
1178 # build new substate based on last committed state
1179 oldstate = wctx.p1().substate
1179 oldstate = wctx.p1().substate
1180 for s in sorted(newstate.keys()):
1180 for s in sorted(newstate.keys()):
1181 if not match(s):
1181 if not match(s):
1182 # ignore working copy, use old state if present
1182 # ignore working copy, use old state if present
1183 if s in oldstate:
1183 if s in oldstate:
1184 newstate[s] = oldstate[s]
1184 newstate[s] = oldstate[s]
1185 continue
1185 continue
1186 if not force:
1186 if not force:
1187 raise util.Abort(
1187 raise util.Abort(
1188 _("commit with new subrepo %s excluded") % s)
1188 _("commit with new subrepo %s excluded") % s)
1189 if wctx.sub(s).dirty(True):
1189 if wctx.sub(s).dirty(True):
1190 if not self.ui.configbool('ui', 'commitsubrepos'):
1190 if not self.ui.configbool('ui', 'commitsubrepos'):
1191 raise util.Abort(
1191 raise util.Abort(
1192 _("uncommitted changes in subrepo %s") % s,
1192 _("uncommitted changes in subrepo %s") % s,
1193 hint=_("use --subrepos for recursive commit"))
1193 hint=_("use --subrepos for recursive commit"))
1194 subs.append(s)
1194 subs.append(s)
1195 commitsubs.add(s)
1195 commitsubs.add(s)
1196 else:
1196 else:
1197 bs = wctx.sub(s).basestate()
1197 bs = wctx.sub(s).basestate()
1198 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1198 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1199 if oldstate.get(s, (None, None, None))[1] != bs:
1199 if oldstate.get(s, (None, None, None))[1] != bs:
1200 subs.append(s)
1200 subs.append(s)
1201
1201
1202 # check for removed subrepos
1202 # check for removed subrepos
1203 for p in wctx.parents():
1203 for p in wctx.parents():
1204 r = [s for s in p.substate if s not in newstate]
1204 r = [s for s in p.substate if s not in newstate]
1205 subs += [s for s in r if match(s)]
1205 subs += [s for s in r if match(s)]
1206 if subs:
1206 if subs:
1207 if (not match('.hgsub') and
1207 if (not match('.hgsub') and
1208 '.hgsub' in (wctx.modified() + wctx.added())):
1208 '.hgsub' in (wctx.modified() + wctx.added())):
1209 raise util.Abort(
1209 raise util.Abort(
1210 _("can't commit subrepos without .hgsub"))
1210 _("can't commit subrepos without .hgsub"))
1211 changes[0].insert(0, '.hgsubstate')
1211 changes[0].insert(0, '.hgsubstate')
1212
1212
1213 elif '.hgsub' in changes[2]:
1213 elif '.hgsub' in changes[2]:
1214 # clean up .hgsubstate when .hgsub is removed
1214 # clean up .hgsubstate when .hgsub is removed
1215 if ('.hgsubstate' in wctx and
1215 if ('.hgsubstate' in wctx and
1216 '.hgsubstate' not in changes[0] + changes[1] + changes[2]):
1216 '.hgsubstate' not in changes[0] + changes[1] + changes[2]):
1217 changes[2].insert(0, '.hgsubstate')
1217 changes[2].insert(0, '.hgsubstate')
1218
1218
1219 # make sure all explicit patterns are matched
1219 # make sure all explicit patterns are matched
1220 if not force and match.files():
1220 if not force and match.files():
1221 matched = set(changes[0] + changes[1] + changes[2])
1221 matched = set(changes[0] + changes[1] + changes[2])
1222
1222
1223 for f in match.files():
1223 for f in match.files():
1224 f = self.dirstate.normalize(f)
1224 f = self.dirstate.normalize(f)
1225 if f == '.' or f in matched or f in wctx.substate:
1225 if f == '.' or f in matched or f in wctx.substate:
1226 continue
1226 continue
1227 if f in changes[3]: # missing
1227 if f in changes[3]: # missing
1228 fail(f, _('file not found!'))
1228 fail(f, _('file not found!'))
1229 if f in vdirs: # visited directory
1229 if f in vdirs: # visited directory
1230 d = f + '/'
1230 d = f + '/'
1231 for mf in matched:
1231 for mf in matched:
1232 if mf.startswith(d):
1232 if mf.startswith(d):
1233 break
1233 break
1234 else:
1234 else:
1235 fail(f, _("no match under directory!"))
1235 fail(f, _("no match under directory!"))
1236 elif f not in self.dirstate:
1236 elif f not in self.dirstate:
1237 fail(f, _("file not tracked!"))
1237 fail(f, _("file not tracked!"))
1238
1238
1239 cctx = context.workingctx(self, text, user, date, extra, changes)
1239 cctx = context.workingctx(self, text, user, date, extra, changes)
1240
1240
1241 if (not force and not extra.get("close") and not merge
1241 if (not force and not extra.get("close") and not merge
1242 and not cctx.files()
1242 and not cctx.files()
1243 and wctx.branch() == wctx.p1().branch()):
1243 and wctx.branch() == wctx.p1().branch()):
1244 return None
1244 return None
1245
1245
1246 if merge and cctx.deleted():
1246 if merge and cctx.deleted():
1247 raise util.Abort(_("cannot commit merge with missing files"))
1247 raise util.Abort(_("cannot commit merge with missing files"))
1248
1248
1249 ms = mergemod.mergestate(self)
1249 ms = mergemod.mergestate(self)
1250 for f in changes[0]:
1250 for f in changes[0]:
1251 if f in ms and ms[f] == 'u':
1251 if f in ms and ms[f] == 'u':
1252 raise util.Abort(_("unresolved merge conflicts "
1252 raise util.Abort(_("unresolved merge conflicts "
1253 "(see hg help resolve)"))
1253 "(see hg help resolve)"))
1254
1254
1255 if editor:
1255 if editor:
1256 cctx._text = editor(self, cctx, subs)
1256 cctx._text = editor(self, cctx, subs)
1257 edited = (text != cctx._text)
1257 edited = (text != cctx._text)
1258
1258
1259 # commit subs and write new state
1259 # commit subs and write new state
1260 if subs:
1260 if subs:
1261 for s in sorted(commitsubs):
1261 for s in sorted(commitsubs):
1262 sub = wctx.sub(s)
1262 sub = wctx.sub(s)
1263 self.ui.status(_('committing subrepository %s\n') %
1263 self.ui.status(_('committing subrepository %s\n') %
1264 subrepo.subrelpath(sub))
1264 subrepo.subrelpath(sub))
1265 sr = sub.commit(cctx._text, user, date)
1265 sr = sub.commit(cctx._text, user, date)
1266 newstate[s] = (newstate[s][0], sr)
1266 newstate[s] = (newstate[s][0], sr)
1267 subrepo.writestate(self, newstate)
1267 subrepo.writestate(self, newstate)
1268
1268
1269 # Save commit message in case this transaction gets rolled back
1269 # Save commit message in case this transaction gets rolled back
1270 # (e.g. by a pretxncommit hook). Leave the content alone on
1270 # (e.g. by a pretxncommit hook). Leave the content alone on
1271 # the assumption that the user will use the same editor again.
1271 # the assumption that the user will use the same editor again.
1272 msgfn = self.savecommitmessage(cctx._text)
1272 msgfn = self.savecommitmessage(cctx._text)
1273
1273
1274 p1, p2 = self.dirstate.parents()
1274 p1, p2 = self.dirstate.parents()
1275 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1275 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1276 try:
1276 try:
1277 self.hook("precommit", throw=True, parent1=hookp1,
1277 self.hook("precommit", throw=True, parent1=hookp1,
1278 parent2=hookp2)
1278 parent2=hookp2)
1279 ret = self.commitctx(cctx, True)
1279 ret = self.commitctx(cctx, True)
1280 except: # re-raises
1280 except: # re-raises
1281 if edited:
1281 if edited:
1282 self.ui.write(
1282 self.ui.write(
1283 _('note: commit message saved in %s\n') % msgfn)
1283 _('note: commit message saved in %s\n') % msgfn)
1284 raise
1284 raise
1285
1285
1286 # update bookmarks, dirstate and mergestate
1286 # update bookmarks, dirstate and mergestate
1287 bookmarks.update(self, [p1, p2], ret)
1287 bookmarks.update(self, [p1, p2], ret)
1288 cctx.markcommitted(ret)
1288 cctx.markcommitted(ret)
1289 ms.reset()
1289 ms.reset()
1290 finally:
1290 finally:
1291 wlock.release()
1291 wlock.release()
1292
1292
1293 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1293 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1294 self.hook("commit", node=node, parent1=parent1, parent2=parent2)
1294 self.hook("commit", node=node, parent1=parent1, parent2=parent2)
1295 self._afterlock(commithook)
1295 self._afterlock(commithook)
1296 return ret
1296 return ret
1297
1297
1298 @unfilteredmethod
1298 @unfilteredmethod
1299 def commitctx(self, ctx, error=False):
1299 def commitctx(self, ctx, error=False):
1300 """Add a new revision to current repository.
1300 """Add a new revision to current repository.
1301 Revision information is passed via the context argument.
1301 Revision information is passed via the context argument.
1302 """
1302 """
1303
1303
1304 tr = lock = None
1304 tr = lock = None
1305 removed = list(ctx.removed())
1305 removed = list(ctx.removed())
1306 p1, p2 = ctx.p1(), ctx.p2()
1306 p1, p2 = ctx.p1(), ctx.p2()
1307 user = ctx.user()
1307 user = ctx.user()
1308
1308
1309 lock = self.lock()
1309 lock = self.lock()
1310 try:
1310 try:
1311 tr = self.transaction("commit")
1311 tr = self.transaction("commit")
1312 trp = weakref.proxy(tr)
1312 trp = weakref.proxy(tr)
1313
1313
1314 if ctx.files():
1314 if ctx.files():
1315 m1 = p1.manifest().copy()
1315 m1 = p1.manifest().copy()
1316 m2 = p2.manifest()
1316 m2 = p2.manifest()
1317
1317
1318 # check in files
1318 # check in files
1319 new = {}
1319 new = {}
1320 changed = []
1320 changed = []
1321 linkrev = len(self)
1321 linkrev = len(self)
1322 for f in sorted(ctx.modified() + ctx.added()):
1322 for f in sorted(ctx.modified() + ctx.added()):
1323 self.ui.note(f + "\n")
1323 self.ui.note(f + "\n")
1324 try:
1324 try:
1325 fctx = ctx[f]
1325 fctx = ctx[f]
1326 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
1326 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
1327 changed)
1327 changed)
1328 m1.set(f, fctx.flags())
1328 m1.set(f, fctx.flags())
1329 except OSError, inst:
1329 except OSError, inst:
1330 self.ui.warn(_("trouble committing %s!\n") % f)
1330 self.ui.warn(_("trouble committing %s!\n") % f)
1331 raise
1331 raise
1332 except IOError, inst:
1332 except IOError, inst:
1333 errcode = getattr(inst, 'errno', errno.ENOENT)
1333 errcode = getattr(inst, 'errno', errno.ENOENT)
1334 if error or errcode and errcode != errno.ENOENT:
1334 if error or errcode and errcode != errno.ENOENT:
1335 self.ui.warn(_("trouble committing %s!\n") % f)
1335 self.ui.warn(_("trouble committing %s!\n") % f)
1336 raise
1336 raise
1337 else:
1337 else:
1338 removed.append(f)
1338 removed.append(f)
1339
1339
1340 # update manifest
1340 # update manifest
1341 m1.update(new)
1341 m1.update(new)
1342 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1342 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1343 drop = [f for f in removed if f in m1]
1343 drop = [f for f in removed if f in m1]
1344 for f in drop:
1344 for f in drop:
1345 del m1[f]
1345 del m1[f]
1346 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
1346 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
1347 p2.manifestnode(), (new, drop))
1347 p2.manifestnode(), (new, drop))
1348 files = changed + removed
1348 files = changed + removed
1349 else:
1349 else:
1350 mn = p1.manifestnode()
1350 mn = p1.manifestnode()
1351 files = []
1351 files = []
1352
1352
1353 # update changelog
1353 # update changelog
1354 self.changelog.delayupdate()
1354 self.changelog.delayupdate()
1355 n = self.changelog.add(mn, files, ctx.description(),
1355 n = self.changelog.add(mn, files, ctx.description(),
1356 trp, p1.node(), p2.node(),
1356 trp, p1.node(), p2.node(),
1357 user, ctx.date(), ctx.extra().copy())
1357 user, ctx.date(), ctx.extra().copy())
1358 p = lambda: self.changelog.writepending() and self.root or ""
1358 p = lambda: self.changelog.writepending() and self.root or ""
1359 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1359 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1360 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1360 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1361 parent2=xp2, pending=p)
1361 parent2=xp2, pending=p)
1362 self.changelog.finalize(trp)
1362 self.changelog.finalize(trp)
1363 # set the new commit is proper phase
1363 # set the new commit is proper phase
1364 targetphase = phases.newcommitphase(self.ui)
1364 targetphase = phases.newcommitphase(self.ui)
1365 if targetphase:
1365 if targetphase:
1366 # retract boundary do not alter parent changeset.
1366 # retract boundary do not alter parent changeset.
1367 # if a parent have higher the resulting phase will
1367 # if a parent have higher the resulting phase will
1368 # be compliant anyway
1368 # be compliant anyway
1369 #
1369 #
1370 # if minimal phase was 0 we don't need to retract anything
1370 # if minimal phase was 0 we don't need to retract anything
1371 phases.retractboundary(self, targetphase, [n])
1371 phases.retractboundary(self, targetphase, [n])
1372 tr.close()
1372 tr.close()
1373 branchmap.updatecache(self.filtered('served'))
1373 branchmap.updatecache(self.filtered('served'))
1374 return n
1374 return n
1375 finally:
1375 finally:
1376 if tr:
1376 if tr:
1377 tr.release()
1377 tr.release()
1378 lock.release()
1378 lock.release()
1379
1379
1380 @unfilteredmethod
1380 @unfilteredmethod
1381 def destroying(self):
1381 def destroying(self):
1382 '''Inform the repository that nodes are about to be destroyed.
1382 '''Inform the repository that nodes are about to be destroyed.
1383 Intended for use by strip and rollback, so there's a common
1383 Intended for use by strip and rollback, so there's a common
1384 place for anything that has to be done before destroying history.
1384 place for anything that has to be done before destroying history.
1385
1385
1386 This is mostly useful for saving state that is in memory and waiting
1386 This is mostly useful for saving state that is in memory and waiting
1387 to be flushed when the current lock is released. Because a call to
1387 to be flushed when the current lock is released. Because a call to
1388 destroyed is imminent, the repo will be invalidated causing those
1388 destroyed is imminent, the repo will be invalidated causing those
1389 changes to stay in memory (waiting for the next unlock), or vanish
1389 changes to stay in memory (waiting for the next unlock), or vanish
1390 completely.
1390 completely.
1391 '''
1391 '''
1392 # When using the same lock to commit and strip, the phasecache is left
1392 # When using the same lock to commit and strip, the phasecache is left
1393 # dirty after committing. Then when we strip, the repo is invalidated,
1393 # dirty after committing. Then when we strip, the repo is invalidated,
1394 # causing those changes to disappear.
1394 # causing those changes to disappear.
1395 if '_phasecache' in vars(self):
1395 if '_phasecache' in vars(self):
1396 self._phasecache.write()
1396 self._phasecache.write()
1397
1397
1398 @unfilteredmethod
1398 @unfilteredmethod
1399 def destroyed(self):
1399 def destroyed(self):
1400 '''Inform the repository that nodes have been destroyed.
1400 '''Inform the repository that nodes have been destroyed.
1401 Intended for use by strip and rollback, so there's a common
1401 Intended for use by strip and rollback, so there's a common
1402 place for anything that has to be done after destroying history.
1402 place for anything that has to be done after destroying history.
1403 '''
1403 '''
1404 # When one tries to:
1404 # When one tries to:
1405 # 1) destroy nodes thus calling this method (e.g. strip)
1405 # 1) destroy nodes thus calling this method (e.g. strip)
1406 # 2) use phasecache somewhere (e.g. commit)
1406 # 2) use phasecache somewhere (e.g. commit)
1407 #
1407 #
1408 # then 2) will fail because the phasecache contains nodes that were
1408 # then 2) will fail because the phasecache contains nodes that were
1409 # removed. We can either remove phasecache from the filecache,
1409 # removed. We can either remove phasecache from the filecache,
1410 # causing it to reload next time it is accessed, or simply filter
1410 # causing it to reload next time it is accessed, or simply filter
1411 # the removed nodes now and write the updated cache.
1411 # the removed nodes now and write the updated cache.
1412 self._phasecache.filterunknown(self)
1412 self._phasecache.filterunknown(self)
1413 self._phasecache.write()
1413 self._phasecache.write()
1414
1414
1415 # update the 'served' branch cache to help read only server process
1415 # update the 'served' branch cache to help read only server process
1416 # Thanks to branchcache collaboration this is done from the nearest
1416 # Thanks to branchcache collaboration this is done from the nearest
1417 # filtered subset and it is expected to be fast.
1417 # filtered subset and it is expected to be fast.
1418 branchmap.updatecache(self.filtered('served'))
1418 branchmap.updatecache(self.filtered('served'))
1419
1419
1420 # Ensure the persistent tag cache is updated. Doing it now
1420 # Ensure the persistent tag cache is updated. Doing it now
1421 # means that the tag cache only has to worry about destroyed
1421 # means that the tag cache only has to worry about destroyed
1422 # heads immediately after a strip/rollback. That in turn
1422 # heads immediately after a strip/rollback. That in turn
1423 # guarantees that "cachetip == currenttip" (comparing both rev
1423 # guarantees that "cachetip == currenttip" (comparing both rev
1424 # and node) always means no nodes have been added or destroyed.
1424 # and node) always means no nodes have been added or destroyed.
1425
1425
1426 # XXX this is suboptimal when qrefresh'ing: we strip the current
1426 # XXX this is suboptimal when qrefresh'ing: we strip the current
1427 # head, refresh the tag cache, then immediately add a new head.
1427 # head, refresh the tag cache, then immediately add a new head.
1428 # But I think doing it this way is necessary for the "instant
1428 # But I think doing it this way is necessary for the "instant
1429 # tag cache retrieval" case to work.
1429 # tag cache retrieval" case to work.
1430 self.invalidate()
1430 self.invalidate()
1431
1431
1432 def walk(self, match, node=None):
1432 def walk(self, match, node=None):
1433 '''
1433 '''
1434 walk recursively through the directory tree or a given
1434 walk recursively through the directory tree or a given
1435 changeset, finding all files matched by the match
1435 changeset, finding all files matched by the match
1436 function
1436 function
1437 '''
1437 '''
1438 return self[node].walk(match)
1438 return self[node].walk(match)
1439
1439
1440 def status(self, node1='.', node2=None, match=None,
1440 def status(self, node1='.', node2=None, match=None,
1441 ignored=False, clean=False, unknown=False,
1441 ignored=False, clean=False, unknown=False,
1442 listsubrepos=False):
1442 listsubrepos=False):
1443 """return status of files between two nodes or node and working
1443 """return status of files between two nodes or node and working
1444 directory.
1444 directory.
1445
1445
1446 If node1 is None, use the first dirstate parent instead.
1446 If node1 is None, use the first dirstate parent instead.
1447 If node2 is None, compare node1 with working directory.
1447 If node2 is None, compare node1 with working directory.
1448 """
1448 """
1449
1449
1450 def mfmatches(ctx):
1450 def mfmatches(ctx):
1451 mf = ctx.manifest().copy()
1451 mf = ctx.manifest().copy()
1452 if match.always():
1452 if match.always():
1453 return mf
1453 return mf
1454 for fn in mf.keys():
1454 for fn in mf.keys():
1455 if not match(fn):
1455 if not match(fn):
1456 del mf[fn]
1456 del mf[fn]
1457 return mf
1457 return mf
1458
1458
1459 if isinstance(node1, context.changectx):
1459 if isinstance(node1, context.changectx):
1460 ctx1 = node1
1460 ctx1 = node1
1461 else:
1461 else:
1462 ctx1 = self[node1]
1462 ctx1 = self[node1]
1463 if isinstance(node2, context.changectx):
1463 if isinstance(node2, context.changectx):
1464 ctx2 = node2
1464 ctx2 = node2
1465 else:
1465 else:
1466 ctx2 = self[node2]
1466 ctx2 = self[node2]
1467
1467
1468 working = ctx2.rev() is None
1468 working = ctx2.rev() is None
1469 parentworking = working and ctx1 == self['.']
1469 parentworking = working and ctx1 == self['.']
1470 match = match or matchmod.always(self.root, self.getcwd())
1470 match = match or matchmod.always(self.root, self.getcwd())
1471 listignored, listclean, listunknown = ignored, clean, unknown
1471 listignored, listclean, listunknown = ignored, clean, unknown
1472
1472
1473 # load earliest manifest first for caching reasons
1473 # load earliest manifest first for caching reasons
1474 if not working and ctx2.rev() < ctx1.rev():
1474 if not working and ctx2.rev() < ctx1.rev():
1475 ctx2.manifest()
1475 ctx2.manifest()
1476
1476
1477 if not parentworking:
1477 if not parentworking:
1478 def bad(f, msg):
1478 def bad(f, msg):
1479 # 'f' may be a directory pattern from 'match.files()',
1479 # 'f' may be a directory pattern from 'match.files()',
1480 # so 'f not in ctx1' is not enough
1480 # so 'f not in ctx1' is not enough
1481 if f not in ctx1 and f not in ctx1.dirs():
1481 if f not in ctx1 and f not in ctx1.dirs():
1482 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
1482 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
1483 match.bad = bad
1483 match.bad = bad
1484
1484
1485 if working: # we need to scan the working dir
1485 if working: # we need to scan the working dir
1486 subrepos = []
1486 subrepos = []
1487 if '.hgsub' in self.dirstate:
1487 if '.hgsub' in self.dirstate:
1488 subrepos = sorted(ctx2.substate)
1488 subrepos = sorted(ctx2.substate)
1489 s = self.dirstate.status(match, subrepos, listignored,
1489 s = self.dirstate.status(match, subrepos, listignored,
1490 listclean, listunknown)
1490 listclean, listunknown)
1491 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1491 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1492
1492
1493 # check for any possibly clean files
1493 # check for any possibly clean files
1494 if parentworking and cmp:
1494 if parentworking and cmp:
1495 fixup = []
1495 fixup = []
1496 # do a full compare of any files that might have changed
1496 # do a full compare of any files that might have changed
1497 for f in sorted(cmp):
1497 for f in sorted(cmp):
1498 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
1498 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
1499 or ctx1[f].cmp(ctx2[f])):
1499 or ctx1[f].cmp(ctx2[f])):
1500 modified.append(f)
1500 modified.append(f)
1501 else:
1501 else:
1502 fixup.append(f)
1502 fixup.append(f)
1503
1503
1504 # update dirstate for files that are actually clean
1504 # update dirstate for files that are actually clean
1505 if fixup:
1505 if fixup:
1506 if listclean:
1506 if listclean:
1507 clean += fixup
1507 clean += fixup
1508
1508
1509 try:
1509 try:
1510 # updating the dirstate is optional
1510 # updating the dirstate is optional
1511 # so we don't wait on the lock
1511 # so we don't wait on the lock
1512 wlock = self.wlock(False)
1512 wlock = self.wlock(False)
1513 try:
1513 try:
1514 for f in fixup:
1514 for f in fixup:
1515 self.dirstate.normal(f)
1515 self.dirstate.normal(f)
1516 finally:
1516 finally:
1517 wlock.release()
1517 wlock.release()
1518 except error.LockError:
1518 except error.LockError:
1519 pass
1519 pass
1520
1520
1521 if not parentworking:
1521 if not parentworking:
1522 mf1 = mfmatches(ctx1)
1522 mf1 = mfmatches(ctx1)
1523 if working:
1523 if working:
1524 # we are comparing working dir against non-parent
1524 # we are comparing working dir against non-parent
1525 # generate a pseudo-manifest for the working dir
1525 # generate a pseudo-manifest for the working dir
1526 mf2 = mfmatches(self['.'])
1526 mf2 = mfmatches(self['.'])
1527 for f in cmp + modified + added:
1527 for f in cmp + modified + added:
1528 mf2[f] = None
1528 mf2[f] = None
1529 mf2.set(f, ctx2.flags(f))
1529 mf2.set(f, ctx2.flags(f))
1530 for f in removed:
1530 for f in removed:
1531 if f in mf2:
1531 if f in mf2:
1532 del mf2[f]
1532 del mf2[f]
1533 else:
1533 else:
1534 # we are comparing two revisions
1534 # we are comparing two revisions
1535 deleted, unknown, ignored = [], [], []
1535 deleted, unknown, ignored = [], [], []
1536 mf2 = mfmatches(ctx2)
1536 mf2 = mfmatches(ctx2)
1537
1537
1538 modified, added, clean = [], [], []
1538 modified, added, clean = [], [], []
1539 withflags = mf1.withflags() | mf2.withflags()
1539 withflags = mf1.withflags() | mf2.withflags()
1540 for fn, mf2node in mf2.iteritems():
1540 for fn, mf2node in mf2.iteritems():
1541 if fn in mf1:
1541 if fn in mf1:
1542 if (fn not in deleted and
1542 if (fn not in deleted and
1543 ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or
1543 ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or
1544 (mf1[fn] != mf2node and
1544 (mf1[fn] != mf2node and
1545 (mf2node or ctx1[fn].cmp(ctx2[fn]))))):
1545 (mf2node or ctx1[fn].cmp(ctx2[fn]))))):
1546 modified.append(fn)
1546 modified.append(fn)
1547 elif listclean:
1547 elif listclean:
1548 clean.append(fn)
1548 clean.append(fn)
1549 del mf1[fn]
1549 del mf1[fn]
1550 elif fn not in deleted:
1550 elif fn not in deleted:
1551 added.append(fn)
1551 added.append(fn)
1552 removed = mf1.keys()
1552 removed = mf1.keys()
1553
1553
1554 if working and modified and not self.dirstate._checklink:
1554 if working and modified and not self.dirstate._checklink:
1555 # Symlink placeholders may get non-symlink-like contents
1555 # Symlink placeholders may get non-symlink-like contents
1556 # via user error or dereferencing by NFS or Samba servers,
1556 # via user error or dereferencing by NFS or Samba servers,
1557 # so we filter out any placeholders that don't look like a
1557 # so we filter out any placeholders that don't look like a
1558 # symlink
1558 # symlink
1559 sane = []
1559 sane = []
1560 for f in modified:
1560 for f in modified:
1561 if ctx2.flags(f) == 'l':
1561 if ctx2.flags(f) == 'l':
1562 d = ctx2[f].data()
1562 d = ctx2[f].data()
1563 if len(d) >= 1024 or '\n' in d or util.binary(d):
1563 if len(d) >= 1024 or '\n' in d or util.binary(d):
1564 self.ui.debug('ignoring suspect symlink placeholder'
1564 self.ui.debug('ignoring suspect symlink placeholder'
1565 ' "%s"\n' % f)
1565 ' "%s"\n' % f)
1566 continue
1566 continue
1567 sane.append(f)
1567 sane.append(f)
1568 modified = sane
1568 modified = sane
1569
1569
1570 r = modified, added, removed, deleted, unknown, ignored, clean
1570 r = modified, added, removed, deleted, unknown, ignored, clean
1571
1571
1572 if listsubrepos:
1572 if listsubrepos:
1573 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
1573 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
1574 if working:
1574 if working:
1575 rev2 = None
1575 rev2 = None
1576 else:
1576 else:
1577 rev2 = ctx2.substate[subpath][1]
1577 rev2 = ctx2.substate[subpath][1]
1578 try:
1578 try:
1579 submatch = matchmod.narrowmatcher(subpath, match)
1579 submatch = matchmod.narrowmatcher(subpath, match)
1580 s = sub.status(rev2, match=submatch, ignored=listignored,
1580 s = sub.status(rev2, match=submatch, ignored=listignored,
1581 clean=listclean, unknown=listunknown,
1581 clean=listclean, unknown=listunknown,
1582 listsubrepos=True)
1582 listsubrepos=True)
1583 for rfiles, sfiles in zip(r, s):
1583 for rfiles, sfiles in zip(r, s):
1584 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
1584 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
1585 except error.LookupError:
1585 except error.LookupError:
1586 self.ui.status(_("skipping missing subrepository: %s\n")
1586 self.ui.status(_("skipping missing subrepository: %s\n")
1587 % subpath)
1587 % subpath)
1588
1588
1589 for l in r:
1589 for l in r:
1590 l.sort()
1590 l.sort()
1591 return r
1591 return r
1592
1592
1593 def heads(self, start=None):
1593 def heads(self, start=None):
1594 heads = self.changelog.heads(start)
1594 heads = self.changelog.heads(start)
1595 # sort the output in rev descending order
1595 # sort the output in rev descending order
1596 return sorted(heads, key=self.changelog.rev, reverse=True)
1596 return sorted(heads, key=self.changelog.rev, reverse=True)
1597
1597
1598 def branchheads(self, branch=None, start=None, closed=False):
1598 def branchheads(self, branch=None, start=None, closed=False):
1599 '''return a (possibly filtered) list of heads for the given branch
1599 '''return a (possibly filtered) list of heads for the given branch
1600
1600
1601 Heads are returned in topological order, from newest to oldest.
1601 Heads are returned in topological order, from newest to oldest.
1602 If branch is None, use the dirstate branch.
1602 If branch is None, use the dirstate branch.
1603 If start is not None, return only heads reachable from start.
1603 If start is not None, return only heads reachable from start.
1604 If closed is True, return heads that are marked as closed as well.
1604 If closed is True, return heads that are marked as closed as well.
1605 '''
1605 '''
1606 if branch is None:
1606 if branch is None:
1607 branch = self[None].branch()
1607 branch = self[None].branch()
1608 branches = self.branchmap()
1608 branches = self.branchmap()
1609 if branch not in branches:
1609 if branch not in branches:
1610 return []
1610 return []
1611 # the cache returns heads ordered lowest to highest
1611 # the cache returns heads ordered lowest to highest
1612 bheads = list(reversed(branches[branch]))
1612 bheads = list(reversed(branches[branch]))
1613 if start is not None:
1613 if start is not None:
1614 # filter out the heads that cannot be reached from startrev
1614 # filter out the heads that cannot be reached from startrev
1615 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1615 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1616 bheads = [h for h in bheads if h in fbheads]
1616 bheads = [h for h in bheads if h in fbheads]
1617 if not closed:
1617 if not closed:
1618 bheads = [h for h in bheads if not self[h].closesbranch()]
1618 bheads = [h for h in bheads if not self[h].closesbranch()]
1619 return bheads
1619 return bheads
1620
1620
1621 def branches(self, nodes):
1621 def branches(self, nodes):
1622 if not nodes:
1622 if not nodes:
1623 nodes = [self.changelog.tip()]
1623 nodes = [self.changelog.tip()]
1624 b = []
1624 b = []
1625 for n in nodes:
1625 for n in nodes:
1626 t = n
1626 t = n
1627 while True:
1627 while True:
1628 p = self.changelog.parents(n)
1628 p = self.changelog.parents(n)
1629 if p[1] != nullid or p[0] == nullid:
1629 if p[1] != nullid or p[0] == nullid:
1630 b.append((t, n, p[0], p[1]))
1630 b.append((t, n, p[0], p[1]))
1631 break
1631 break
1632 n = p[0]
1632 n = p[0]
1633 return b
1633 return b
1634
1634
1635 def between(self, pairs):
1635 def between(self, pairs):
1636 r = []
1636 r = []
1637
1637
1638 for top, bottom in pairs:
1638 for top, bottom in pairs:
1639 n, l, i = top, [], 0
1639 n, l, i = top, [], 0
1640 f = 1
1640 f = 1
1641
1641
1642 while n != bottom and n != nullid:
1642 while n != bottom and n != nullid:
1643 p = self.changelog.parents(n)[0]
1643 p = self.changelog.parents(n)[0]
1644 if i == f:
1644 if i == f:
1645 l.append(n)
1645 l.append(n)
1646 f = f * 2
1646 f = f * 2
1647 n = p
1647 n = p
1648 i += 1
1648 i += 1
1649
1649
1650 r.append(l)
1650 r.append(l)
1651
1651
1652 return r
1652 return r
1653
1653
1654 def pull(self, remote, heads=None, force=False):
1654 def pull(self, remote, heads=None, force=False):
1655 # don't open transaction for nothing or you break future useful
1655 # don't open transaction for nothing or you break future useful
1656 # rollback call
1656 # rollback call
1657 tr = None
1657 tr = None
1658 trname = 'pull\n' + util.hidepassword(remote.url())
1658 trname = 'pull\n' + util.hidepassword(remote.url())
1659 lock = self.lock()
1659 lock = self.lock()
1660 try:
1660 try:
1661 tmp = discovery.findcommonincoming(self, remote, heads=heads,
1661 tmp = discovery.findcommonincoming(self, remote, heads=heads,
1662 force=force)
1662 force=force)
1663 common, fetch, rheads = tmp
1663 common, fetch, rheads = tmp
1664 if not fetch:
1664 if not fetch:
1665 self.ui.status(_("no changes found\n"))
1665 self.ui.status(_("no changes found\n"))
1666 added = []
1666 added = []
1667 result = 0
1667 result = 0
1668 else:
1668 else:
1669 tr = self.transaction(trname)
1669 tr = self.transaction(trname)
1670 if heads is None and list(common) == [nullid]:
1670 if heads is None and list(common) == [nullid]:
1671 self.ui.status(_("requesting all changes\n"))
1671 self.ui.status(_("requesting all changes\n"))
1672 elif heads is None and remote.capable('changegroupsubset'):
1672 elif heads is None and remote.capable('changegroupsubset'):
1673 # issue1320, avoid a race if remote changed after discovery
1673 # issue1320, avoid a race if remote changed after discovery
1674 heads = rheads
1674 heads = rheads
1675
1675
1676 if remote.capable('getbundle'):
1676 if remote.capable('getbundle'):
1677 cg = remote.getbundle('pull', common=common,
1677 cg = remote.getbundle('pull', common=common,
1678 heads=heads or rheads)
1678 heads=heads or rheads)
1679 elif heads is None:
1679 elif heads is None:
1680 cg = remote.changegroup(fetch, 'pull')
1680 cg = remote.changegroup(fetch, 'pull')
1681 elif not remote.capable('changegroupsubset'):
1681 elif not remote.capable('changegroupsubset'):
1682 raise util.Abort(_("partial pull cannot be done because "
1682 raise util.Abort(_("partial pull cannot be done because "
1683 "other repository doesn't support "
1683 "other repository doesn't support "
1684 "changegroupsubset."))
1684 "changegroupsubset."))
1685 else:
1685 else:
1686 cg = remote.changegroupsubset(fetch, heads, 'pull')
1686 cg = remote.changegroupsubset(fetch, heads, 'pull')
1687 # we use unfiltered changelog here because hidden revision must
1687 # we use unfiltered changelog here because hidden revision must
1688 # be taken in account for phase synchronization. They may
1688 # be taken in account for phase synchronization. They may
1689 # becomes public and becomes visible again.
1689 # becomes public and becomes visible again.
1690 cl = self.unfiltered().changelog
1690 cl = self.unfiltered().changelog
1691 clstart = len(cl)
1691 clstart = len(cl)
1692 result = self.addchangegroup(cg, 'pull', remote.url())
1692 result = self.addchangegroup(cg, 'pull', remote.url())
1693 clend = len(cl)
1693 clend = len(cl)
1694 added = [cl.node(r) for r in xrange(clstart, clend)]
1694 added = [cl.node(r) for r in xrange(clstart, clend)]
1695
1695
1696 # compute target subset
1696 # compute target subset
1697 if heads is None:
1697 if heads is None:
1698 # We pulled every thing possible
1698 # We pulled every thing possible
1699 # sync on everything common
1699 # sync on everything common
1700 subset = common + added
1700 subset = common + added
1701 else:
1701 else:
1702 # We pulled a specific subset
1702 # We pulled a specific subset
1703 # sync on this subset
1703 # sync on this subset
1704 subset = heads
1704 subset = heads
1705
1705
1706 # Get remote phases data from remote
1706 # Get remote phases data from remote
1707 remotephases = remote.listkeys('phases')
1707 remotephases = remote.listkeys('phases')
1708 publishing = bool(remotephases.get('publishing', False))
1708 publishing = bool(remotephases.get('publishing', False))
1709 if remotephases and not publishing:
1709 if remotephases and not publishing:
1710 # remote is new and unpublishing
1710 # remote is new and unpublishing
1711 pheads, _dr = phases.analyzeremotephases(self, subset,
1711 pheads, _dr = phases.analyzeremotephases(self, subset,
1712 remotephases)
1712 remotephases)
1713 phases.advanceboundary(self, phases.public, pheads)
1713 phases.advanceboundary(self, phases.public, pheads)
1714 phases.advanceboundary(self, phases.draft, subset)
1714 phases.advanceboundary(self, phases.draft, subset)
1715 else:
1715 else:
1716 # Remote is old or publishing all common changesets
1716 # Remote is old or publishing all common changesets
1717 # should be seen as public
1717 # should be seen as public
1718 phases.advanceboundary(self, phases.public, subset)
1718 phases.advanceboundary(self, phases.public, subset)
1719
1719
1720 def gettransaction():
1720 def gettransaction():
1721 if tr is None:
1721 if tr is None:
1722 return self.transaction(trname)
1722 return self.transaction(trname)
1723 return tr
1723 return tr
1724
1724
1725 obstr = obsolete.syncpull(self, remote, gettransaction)
1725 obstr = obsolete.syncpull(self, remote, gettransaction)
1726 if obstr is not None:
1726 if obstr is not None:
1727 tr = obstr
1727 tr = obstr
1728
1728
1729 if tr is not None:
1729 if tr is not None:
1730 tr.close()
1730 tr.close()
1731 finally:
1731 finally:
1732 if tr is not None:
1732 if tr is not None:
1733 tr.release()
1733 tr.release()
1734 lock.release()
1734 lock.release()
1735
1735
1736 return result
1736 return result
1737
1737
1738 def checkpush(self, force, revs):
1738 def checkpush(self, force, revs):
1739 """Extensions can override this function if additional checks have
1739 """Extensions can override this function if additional checks have
1740 to be performed before pushing, or call it if they override push
1740 to be performed before pushing, or call it if they override push
1741 command.
1741 command.
1742 """
1742 """
1743 pass
1743 pass
1744
1744
1745 def push(self, remote, force=False, revs=None, newbranch=False):
1745 def push(self, remote, force=False, revs=None, newbranch=False):
1746 '''Push outgoing changesets (limited by revs) from the current
1746 '''Push outgoing changesets (limited by revs) from the current
1747 repository to remote. Return an integer:
1747 repository to remote. Return an integer:
1748 - None means nothing to push
1748 - None means nothing to push
1749 - 0 means HTTP error
1749 - 0 means HTTP error
1750 - 1 means we pushed and remote head count is unchanged *or*
1750 - 1 means we pushed and remote head count is unchanged *or*
1751 we have outgoing changesets but refused to push
1751 we have outgoing changesets but refused to push
1752 - other values as described by addchangegroup()
1752 - other values as described by addchangegroup()
1753 '''
1753 '''
1754 # there are two ways to push to remote repo:
1754 # there are two ways to push to remote repo:
1755 #
1755 #
1756 # addchangegroup assumes local user can lock remote
1756 # addchangegroup assumes local user can lock remote
1757 # repo (local filesystem, old ssh servers).
1757 # repo (local filesystem, old ssh servers).
1758 #
1758 #
1759 # unbundle assumes local user cannot lock remote repo (new ssh
1759 # unbundle assumes local user cannot lock remote repo (new ssh
1760 # servers, http servers).
1760 # servers, http servers).
1761
1761
1762 if not remote.canpush():
1762 if not remote.canpush():
1763 raise util.Abort(_("destination does not support push"))
1763 raise util.Abort(_("destination does not support push"))
1764 unfi = self.unfiltered()
1764 unfi = self.unfiltered()
1765 def localphasemove(nodes, phase=phases.public):
1765 def localphasemove(nodes, phase=phases.public):
1766 """move <nodes> to <phase> in the local source repo"""
1766 """move <nodes> to <phase> in the local source repo"""
1767 if locallock is not None:
1767 if locallock is not None:
1768 phases.advanceboundary(self, phase, nodes)
1768 phases.advanceboundary(self, phase, nodes)
1769 else:
1769 else:
1770 # repo is not locked, do not change any phases!
1770 # repo is not locked, do not change any phases!
1771 # Informs the user that phases should have been moved when
1771 # Informs the user that phases should have been moved when
1772 # applicable.
1772 # applicable.
1773 actualmoves = [n for n in nodes if phase < self[n].phase()]
1773 actualmoves = [n for n in nodes if phase < self[n].phase()]
1774 phasestr = phases.phasenames[phase]
1774 phasestr = phases.phasenames[phase]
1775 if actualmoves:
1775 if actualmoves:
1776 self.ui.status(_('cannot lock source repo, skipping local'
1776 self.ui.status(_('cannot lock source repo, skipping local'
1777 ' %s phase update\n') % phasestr)
1777 ' %s phase update\n') % phasestr)
1778 # get local lock as we might write phase data
1778 # get local lock as we might write phase data
1779 locallock = None
1779 locallock = None
1780 try:
1780 try:
1781 locallock = self.lock()
1781 locallock = self.lock()
1782 except IOError, err:
1782 except IOError, err:
1783 if err.errno != errno.EACCES:
1783 if err.errno != errno.EACCES:
1784 raise
1784 raise
1785 # source repo cannot be locked.
1785 # source repo cannot be locked.
1786 # We do not abort the push, but just disable the local phase
1786 # We do not abort the push, but just disable the local phase
1787 # synchronisation.
1787 # synchronisation.
1788 msg = 'cannot lock source repository: %s\n' % err
1788 msg = 'cannot lock source repository: %s\n' % err
1789 self.ui.debug(msg)
1789 self.ui.debug(msg)
1790 try:
1790 try:
1791 self.checkpush(force, revs)
1791 self.checkpush(force, revs)
1792 lock = None
1792 lock = None
1793 unbundle = remote.capable('unbundle')
1793 unbundle = remote.capable('unbundle')
1794 if not unbundle:
1794 if not unbundle:
1795 lock = remote.lock()
1795 lock = remote.lock()
1796 try:
1796 try:
1797 # discovery
1797 # discovery
1798 fci = discovery.findcommonincoming
1798 fci = discovery.findcommonincoming
1799 commoninc = fci(unfi, remote, force=force)
1799 commoninc = fci(unfi, remote, force=force)
1800 common, inc, remoteheads = commoninc
1800 common, inc, remoteheads = commoninc
1801 fco = discovery.findcommonoutgoing
1801 fco = discovery.findcommonoutgoing
1802 outgoing = fco(unfi, remote, onlyheads=revs,
1802 outgoing = fco(unfi, remote, onlyheads=revs,
1803 commoninc=commoninc, force=force)
1803 commoninc=commoninc, force=force)
1804
1804
1805
1805
1806 if not outgoing.missing:
1806 if not outgoing.missing:
1807 # nothing to push
1807 # nothing to push
1808 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
1808 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
1809 ret = None
1809 ret = None
1810 else:
1810 else:
1811 # something to push
1811 # something to push
1812 if not force:
1812 if not force:
1813 # if self.obsstore == False --> no obsolete
1813 # if self.obsstore == False --> no obsolete
1814 # then, save the iteration
1814 # then, save the iteration
1815 if unfi.obsstore:
1815 if unfi.obsstore:
1816 # this message are here for 80 char limit reason
1816 # this message are here for 80 char limit reason
1817 mso = _("push includes obsolete changeset: %s!")
1817 mso = _("push includes obsolete changeset: %s!")
1818 mst = "push includes %s changeset: %s!"
1818 mst = "push includes %s changeset: %s!"
1819 # plain versions for i18n tool to detect them
1819 # plain versions for i18n tool to detect them
1820 _("push includes unstable changeset: %s!")
1820 _("push includes unstable changeset: %s!")
1821 _("push includes bumped changeset: %s!")
1821 _("push includes bumped changeset: %s!")
1822 _("push includes divergent changeset: %s!")
1822 _("push includes divergent changeset: %s!")
1823 # If we are to push if there is at least one
1823 # If we are to push if there is at least one
1824 # obsolete or unstable changeset in missing, at
1824 # obsolete or unstable changeset in missing, at
1825 # least one of the missinghead will be obsolete or
1825 # least one of the missinghead will be obsolete or
1826 # unstable. So checking heads only is ok
1826 # unstable. So checking heads only is ok
1827 for node in outgoing.missingheads:
1827 for node in outgoing.missingheads:
1828 ctx = unfi[node]
1828 ctx = unfi[node]
1829 if ctx.obsolete():
1829 if ctx.obsolete():
1830 raise util.Abort(mso % ctx)
1830 raise util.Abort(mso % ctx)
1831 elif ctx.troubled():
1831 elif ctx.troubled():
1832 raise util.Abort(_(mst)
1832 raise util.Abort(_(mst)
1833 % (ctx.troubles()[0],
1833 % (ctx.troubles()[0],
1834 ctx))
1834 ctx))
1835 discovery.checkheads(unfi, remote, outgoing,
1835 discovery.checkheads(unfi, remote, outgoing,
1836 remoteheads, newbranch,
1836 remoteheads, newbranch,
1837 bool(inc))
1837 bool(inc))
1838
1838
1839 # create a changegroup from local
1839 # create a changegroup from local
1840 if revs is None and not outgoing.excluded:
1840 if revs is None and not outgoing.excluded:
1841 # push everything,
1841 # push everything,
1842 # use the fast path, no race possible on push
1842 # use the fast path, no race possible on push
1843 bundler = changegroup.bundle10()
1843 bundler = changegroup.bundle10()
1844 cg = self._changegroup(outgoing.missing, bundler,
1844 cg = self._changegroup(outgoing.missing, bundler,
1845 'push')
1845 'push')
1846 else:
1846 else:
1847 cg = self.getlocalbundle('push', outgoing)
1847 cg = self.getlocalbundle('push', outgoing)
1848
1848
1849 # apply changegroup to remote
1849 # apply changegroup to remote
1850 if unbundle:
1850 if unbundle:
1851 # local repo finds heads on server, finds out what
1851 # local repo finds heads on server, finds out what
1852 # revs it must push. once revs transferred, if server
1852 # revs it must push. once revs transferred, if server
1853 # finds it has different heads (someone else won
1853 # finds it has different heads (someone else won
1854 # commit/push race), server aborts.
1854 # commit/push race), server aborts.
1855 if force:
1855 if force:
1856 remoteheads = ['force']
1856 remoteheads = ['force']
1857 # ssh: return remote's addchangegroup()
1857 # ssh: return remote's addchangegroup()
1858 # http: return remote's addchangegroup() or 0 for error
1858 # http: return remote's addchangegroup() or 0 for error
1859 ret = remote.unbundle(cg, remoteheads, 'push')
1859 ret = remote.unbundle(cg, remoteheads, 'push')
1860 else:
1860 else:
1861 # we return an integer indicating remote head count
1861 # we return an integer indicating remote head count
1862 # change
1862 # change
1863 ret = remote.addchangegroup(cg, 'push', self.url())
1863 ret = remote.addchangegroup(cg, 'push', self.url())
1864
1864
1865 if ret:
1865 if ret:
1866 # push succeed, synchronize target of the push
1866 # push succeed, synchronize target of the push
1867 cheads = outgoing.missingheads
1867 cheads = outgoing.missingheads
1868 elif revs is None:
1868 elif revs is None:
1869 # All out push fails. synchronize all common
1869 # All out push fails. synchronize all common
1870 cheads = outgoing.commonheads
1870 cheads = outgoing.commonheads
1871 else:
1871 else:
1872 # I want cheads = heads(::missingheads and ::commonheads)
1872 # I want cheads = heads(::missingheads and ::commonheads)
1873 # (missingheads is revs with secret changeset filtered out)
1873 # (missingheads is revs with secret changeset filtered out)
1874 #
1874 #
1875 # This can be expressed as:
1875 # This can be expressed as:
1876 # cheads = ( (missingheads and ::commonheads)
1876 # cheads = ( (missingheads and ::commonheads)
1877 # + (commonheads and ::missingheads))"
1877 # + (commonheads and ::missingheads))"
1878 # )
1878 # )
1879 #
1879 #
1880 # while trying to push we already computed the following:
1880 # while trying to push we already computed the following:
1881 # common = (::commonheads)
1881 # common = (::commonheads)
1882 # missing = ((commonheads::missingheads) - commonheads)
1882 # missing = ((commonheads::missingheads) - commonheads)
1883 #
1883 #
1884 # We can pick:
1884 # We can pick:
1885 # * missingheads part of common (::commonheads)
1885 # * missingheads part of common (::commonheads)
1886 common = set(outgoing.common)
1886 common = set(outgoing.common)
1887 cheads = [node for node in revs if node in common]
1887 cheads = [node for node in revs if node in common]
1888 # and
1888 # and
1889 # * commonheads parents on missing
1889 # * commonheads parents on missing
1890 revset = unfi.set('%ln and parents(roots(%ln))',
1890 revset = unfi.set('%ln and parents(roots(%ln))',
1891 outgoing.commonheads,
1891 outgoing.commonheads,
1892 outgoing.missing)
1892 outgoing.missing)
1893 cheads.extend(c.node() for c in revset)
1893 cheads.extend(c.node() for c in revset)
1894 # even when we don't push, exchanging phase data is useful
1894 # even when we don't push, exchanging phase data is useful
1895 remotephases = remote.listkeys('phases')
1895 remotephases = remote.listkeys('phases')
1896 if (self.ui.configbool('ui', '_usedassubrepo', False)
1896 if (self.ui.configbool('ui', '_usedassubrepo', False)
1897 and remotephases # server supports phases
1897 and remotephases # server supports phases
1898 and ret is None # nothing was pushed
1898 and ret is None # nothing was pushed
1899 and remotephases.get('publishing', False)):
1899 and remotephases.get('publishing', False)):
1900 # When:
1900 # When:
1901 # - this is a subrepo push
1901 # - this is a subrepo push
1902 # - and remote support phase
1902 # - and remote support phase
1903 # - and no changeset was pushed
1903 # - and no changeset was pushed
1904 # - and remote is publishing
1904 # - and remote is publishing
1905 # We may be in issue 3871 case!
1905 # We may be in issue 3871 case!
1906 # We drop the possible phase synchronisation done by
1906 # We drop the possible phase synchronisation done by
1907 # courtesy to publish changesets possibly locally draft
1907 # courtesy to publish changesets possibly locally draft
1908 # on the remote.
1908 # on the remote.
1909 remotephases = {'publishing': 'True'}
1909 remotephases = {'publishing': 'True'}
1910 if not remotephases: # old server or public only repo
1910 if not remotephases: # old server or public only repo
1911 localphasemove(cheads)
1911 localphasemove(cheads)
1912 # don't push any phase data as there is nothing to push
1912 # don't push any phase data as there is nothing to push
1913 else:
1913 else:
1914 ana = phases.analyzeremotephases(self, cheads, remotephases)
1914 ana = phases.analyzeremotephases(self, cheads, remotephases)
1915 pheads, droots = ana
1915 pheads, droots = ana
1916 ### Apply remote phase on local
1916 ### Apply remote phase on local
1917 if remotephases.get('publishing', False):
1917 if remotephases.get('publishing', False):
1918 localphasemove(cheads)
1918 localphasemove(cheads)
1919 else: # publish = False
1919 else: # publish = False
1920 localphasemove(pheads)
1920 localphasemove(pheads)
1921 localphasemove(cheads, phases.draft)
1921 localphasemove(cheads, phases.draft)
1922 ### Apply local phase on remote
1922 ### Apply local phase on remote
1923
1923
1924 # Get the list of all revs draft on remote by public here.
1924 # Get the list of all revs draft on remote by public here.
1925 # XXX Beware that revset break if droots is not strictly
1925 # XXX Beware that revset break if droots is not strictly
1926 # XXX root we may want to ensure it is but it is costly
1926 # XXX root we may want to ensure it is but it is costly
1927 outdated = unfi.set('heads((%ln::%ln) and public())',
1927 outdated = unfi.set('heads((%ln::%ln) and public())',
1928 droots, cheads)
1928 droots, cheads)
1929 for newremotehead in outdated:
1929 for newremotehead in outdated:
1930 r = remote.pushkey('phases',
1930 r = remote.pushkey('phases',
1931 newremotehead.hex(),
1931 newremotehead.hex(),
1932 str(phases.draft),
1932 str(phases.draft),
1933 str(phases.public))
1933 str(phases.public))
1934 if not r:
1934 if not r:
1935 self.ui.warn(_('updating %s to public failed!\n')
1935 self.ui.warn(_('updating %s to public failed!\n')
1936 % newremotehead)
1936 % newremotehead)
1937 self.ui.debug('try to push obsolete markers to remote\n')
1937 self.ui.debug('try to push obsolete markers to remote\n')
1938 obsolete.syncpush(self, remote)
1938 obsolete.syncpush(self, remote)
1939 finally:
1939 finally:
1940 if lock is not None:
1940 if lock is not None:
1941 lock.release()
1941 lock.release()
1942 finally:
1942 finally:
1943 if locallock is not None:
1943 if locallock is not None:
1944 locallock.release()
1944 locallock.release()
1945
1945
1946 self.ui.debug("checking for updated bookmarks\n")
1946 self.ui.debug("checking for updated bookmarks\n")
1947 rb = remote.listkeys('bookmarks')
1947 rb = remote.listkeys('bookmarks')
1948 for k in rb.keys():
1948 for k in rb.keys():
1949 if k in unfi._bookmarks:
1949 if k in unfi._bookmarks:
1950 nr, nl = rb[k], hex(self._bookmarks[k])
1950 nr, nl = rb[k], hex(self._bookmarks[k])
1951 if nr in unfi:
1951 if nr in unfi:
1952 cr = unfi[nr]
1952 cr = unfi[nr]
1953 cl = unfi[nl]
1953 cl = unfi[nl]
1954 if bookmarks.validdest(unfi, cr, cl):
1954 if bookmarks.validdest(unfi, cr, cl):
1955 r = remote.pushkey('bookmarks', k, nr, nl)
1955 r = remote.pushkey('bookmarks', k, nr, nl)
1956 if r:
1956 if r:
1957 self.ui.status(_("updating bookmark %s\n") % k)
1957 self.ui.status(_("updating bookmark %s\n") % k)
1958 else:
1958 else:
1959 self.ui.warn(_('updating bookmark %s'
1959 self.ui.warn(_('updating bookmark %s'
1960 ' failed!\n') % k)
1960 ' failed!\n') % k)
1961
1961
1962 return ret
1962 return ret
1963
1963
1964 def changegroupinfo(self, nodes, source):
1964 def changegroupinfo(self, nodes, source):
1965 if self.ui.verbose or source == 'bundle':
1965 if self.ui.verbose or source == 'bundle':
1966 self.ui.status(_("%d changesets found\n") % len(nodes))
1966 self.ui.status(_("%d changesets found\n") % len(nodes))
1967 if self.ui.debugflag:
1967 if self.ui.debugflag:
1968 self.ui.debug("list of changesets:\n")
1968 self.ui.debug("list of changesets:\n")
1969 for node in nodes:
1969 for node in nodes:
1970 self.ui.debug("%s\n" % hex(node))
1970 self.ui.debug("%s\n" % hex(node))
1971
1971
1972 def changegroupsubset(self, bases, heads, source):
1972 def changegroupsubset(self, bases, heads, source):
1973 """Compute a changegroup consisting of all the nodes that are
1973 """Compute a changegroup consisting of all the nodes that are
1974 descendants of any of the bases and ancestors of any of the heads.
1974 descendants of any of the bases and ancestors of any of the heads.
1975 Return a chunkbuffer object whose read() method will return
1975 Return a chunkbuffer object whose read() method will return
1976 successive changegroup chunks.
1976 successive changegroup chunks.
1977
1977
1978 It is fairly complex as determining which filenodes and which
1978 It is fairly complex as determining which filenodes and which
1979 manifest nodes need to be included for the changeset to be complete
1979 manifest nodes need to be included for the changeset to be complete
1980 is non-trivial.
1980 is non-trivial.
1981
1981
1982 Another wrinkle is doing the reverse, figuring out which changeset in
1982 Another wrinkle is doing the reverse, figuring out which changeset in
1983 the changegroup a particular filenode or manifestnode belongs to.
1983 the changegroup a particular filenode or manifestnode belongs to.
1984 """
1984 """
1985 cl = self.changelog
1985 cl = self.changelog
1986 if not bases:
1986 if not bases:
1987 bases = [nullid]
1987 bases = [nullid]
1988 csets, bases, heads = cl.nodesbetween(bases, heads)
1988 csets, bases, heads = cl.nodesbetween(bases, heads)
1989 # We assume that all ancestors of bases are known
1989 # We assume that all ancestors of bases are known
1990 common = cl.ancestors([cl.rev(n) for n in bases])
1990 common = cl.ancestors([cl.rev(n) for n in bases])
1991 bundler = changegroup.bundle10()
1991 bundler = changegroup.bundle10()
1992 return self._changegroupsubset(common, csets, heads, bundler, source)
1992 return self._changegroupsubset(common, csets, heads, bundler, source)
1993
1993
1994 def getlocalbundle(self, source, outgoing):
1994 def getlocalbundle(self, source, outgoing):
1995 """Like getbundle, but taking a discovery.outgoing as an argument.
1995 """Like getbundle, but taking a discovery.outgoing as an argument.
1996
1996
1997 This is only implemented for local repos and reuses potentially
1997 This is only implemented for local repos and reuses potentially
1998 precomputed sets in outgoing."""
1998 precomputed sets in outgoing."""
1999 if not outgoing.missing:
1999 if not outgoing.missing:
2000 return None
2000 return None
2001 bundler = changegroup.bundle10()
2001 bundler = changegroup.bundle10()
2002 return self._changegroupsubset(outgoing.common,
2002 return self._changegroupsubset(outgoing.common,
2003 outgoing.missing,
2003 outgoing.missing,
2004 outgoing.missingheads,
2004 outgoing.missingheads,
2005 bundler,
2005 bundler,
2006 source)
2006 source)
2007
2007
2008 def getbundle(self, source, heads=None, common=None):
2008 def getbundle(self, source, heads=None, common=None):
2009 """Like changegroupsubset, but returns the set difference between the
2009 """Like changegroupsubset, but returns the set difference between the
2010 ancestors of heads and the ancestors common.
2010 ancestors of heads and the ancestors common.
2011
2011
2012 If heads is None, use the local heads. If common is None, use [nullid].
2012 If heads is None, use the local heads. If common is None, use [nullid].
2013
2013
2014 The nodes in common might not all be known locally due to the way the
2014 The nodes in common might not all be known locally due to the way the
2015 current discovery protocol works.
2015 current discovery protocol works.
2016 """
2016 """
2017 cl = self.changelog
2017 cl = self.changelog
2018 if common:
2018 if common:
2019 hasnode = cl.hasnode
2019 hasnode = cl.hasnode
2020 common = [n for n in common if hasnode(n)]
2020 common = [n for n in common if hasnode(n)]
2021 else:
2021 else:
2022 common = [nullid]
2022 common = [nullid]
2023 if not heads:
2023 if not heads:
2024 heads = cl.heads()
2024 heads = cl.heads()
2025 return self.getlocalbundle(source,
2025 return self.getlocalbundle(source,
2026 discovery.outgoing(cl, common, heads))
2026 discovery.outgoing(cl, common, heads))
2027
2027
2028 @unfilteredmethod
2028 @unfilteredmethod
2029 def _changegroupsubset(self, commonrevs, csets, heads, bundler, source):
2029 def _changegroupsubset(self, commonrevs, csets, heads, bundler, source):
2030
2030
2031 cl = self.changelog
2031 cl = self.changelog
2032 mf = self.manifest
2032 mf = self.manifest
2033 mfs = {} # needed manifests
2033 mfs = {} # needed manifests
2034 fnodes = {} # needed file nodes
2034 fnodes = {} # needed file nodes
2035 changedfiles = set()
2035 changedfiles = set()
2036 fstate = ['', {}]
2036 fstate = ['', {}]
2037 count = [0, 0]
2037 count = [0, 0]
2038
2038
2039 # can we go through the fast path ?
2039 # can we go through the fast path ?
2040 heads.sort()
2040 heads.sort()
2041 if heads == sorted(self.heads()):
2041 if heads == sorted(self.heads()):
2042 return self._changegroup(csets, bundler, source)
2042 return self._changegroup(csets, bundler, source)
2043
2043
2044 # slow path
2044 # slow path
2045 self.hook('preoutgoing', throw=True, source=source)
2045 self.hook('preoutgoing', throw=True, source=source)
2046 self.changegroupinfo(csets, source)
2046 self.changegroupinfo(csets, source)
2047
2047
2048 # filter any nodes that claim to be part of the known set
2048 # filter any nodes that claim to be part of the known set
2049 def prune(revlog, missing):
2049 def prune(revlog, missing):
2050 rr, rl = revlog.rev, revlog.linkrev
2050 rr, rl = revlog.rev, revlog.linkrev
2051 return [n for n in missing
2051 return [n for n in missing
2052 if rl(rr(n)) not in commonrevs]
2052 if rl(rr(n)) not in commonrevs]
2053
2053
2054 progress = self.ui.progress
2054 progress = self.ui.progress
2055 _bundling = _('bundling')
2055 _bundling = _('bundling')
2056 _changesets = _('changesets')
2056 _changesets = _('changesets')
2057 _manifests = _('manifests')
2057 _manifests = _('manifests')
2058 _files = _('files')
2058 _files = _('files')
2059
2059
2060 def lookup(revlog, x):
2060 def lookup(revlog, x):
2061 if revlog == cl:
2061 if revlog == cl:
2062 c = cl.read(x)
2062 c = cl.read(x)
2063 changedfiles.update(c[3])
2063 changedfiles.update(c[3])
2064 mfs.setdefault(c[0], x)
2064 mfs.setdefault(c[0], x)
2065 count[0] += 1
2065 count[0] += 1
2066 progress(_bundling, count[0],
2066 progress(_bundling, count[0],
2067 unit=_changesets, total=count[1])
2067 unit=_changesets, total=count[1])
2068 return x
2068 return x
2069 elif revlog == mf:
2069 elif revlog == mf:
2070 clnode = mfs[x]
2070 clnode = mfs[x]
2071 mdata = mf.readfast(x)
2071 mdata = mf.readfast(x)
2072 for f, n in mdata.iteritems():
2072 for f, n in mdata.iteritems():
2073 if f in changedfiles:
2073 if f in changedfiles:
2074 fnodes[f].setdefault(n, clnode)
2074 fnodes[f].setdefault(n, clnode)
2075 count[0] += 1
2075 count[0] += 1
2076 progress(_bundling, count[0],
2076 progress(_bundling, count[0],
2077 unit=_manifests, total=count[1])
2077 unit=_manifests, total=count[1])
2078 return clnode
2078 return clnode
2079 else:
2079 else:
2080 progress(_bundling, count[0], item=fstate[0],
2080 progress(_bundling, count[0], item=fstate[0],
2081 unit=_files, total=count[1])
2081 unit=_files, total=count[1])
2082 return fstate[1][x]
2082 return fstate[1][x]
2083
2083
2084 bundler.start(lookup)
2084 bundler.start(lookup)
2085 reorder = self.ui.config('bundle', 'reorder', 'auto')
2085 reorder = self.ui.config('bundle', 'reorder', 'auto')
2086 if reorder == 'auto':
2086 if reorder == 'auto':
2087 reorder = None
2087 reorder = None
2088 else:
2088 else:
2089 reorder = util.parsebool(reorder)
2089 reorder = util.parsebool(reorder)
2090
2090
2091 def gengroup():
2091 def gengroup():
2092 # Create a changenode group generator that will call our functions
2092 # Create a changenode group generator that will call our functions
2093 # back to lookup the owning changenode and collect information.
2093 # back to lookup the owning changenode and collect information.
2094 count[:] = [0, len(csets)]
2094 count[:] = [0, len(csets)]
2095 for chunk in cl.group(csets, bundler, reorder=reorder):
2095 for chunk in bundler.group(csets, cl, reorder=reorder):
2096 yield chunk
2096 yield chunk
2097 progress(_bundling, None)
2097 progress(_bundling, None)
2098
2098
2099 # Create a generator for the manifestnodes that calls our lookup
2099 # Create a generator for the manifestnodes that calls our lookup
2100 # and data collection functions back.
2100 # and data collection functions back.
2101 for f in changedfiles:
2101 for f in changedfiles:
2102 fnodes[f] = {}
2102 fnodes[f] = {}
2103 count[:] = [0, len(mfs)]
2103 count[:] = [0, len(mfs)]
2104 for chunk in mf.group(prune(mf, mfs), bundler, reorder=reorder):
2104 for chunk in bundler.group(prune(mf, mfs), mf, reorder=reorder):
2105 yield chunk
2105 yield chunk
2106 progress(_bundling, None)
2106 progress(_bundling, None)
2107
2107
2108 mfs.clear()
2108 mfs.clear()
2109
2109
2110 # Go through all our files in order sorted by name.
2110 # Go through all our files in order sorted by name.
2111 count[:] = [0, len(changedfiles)]
2111 count[:] = [0, len(changedfiles)]
2112 for fname in sorted(changedfiles):
2112 for fname in sorted(changedfiles):
2113 filerevlog = self.file(fname)
2113 filerevlog = self.file(fname)
2114 if not len(filerevlog):
2114 if not len(filerevlog):
2115 raise util.Abort(_("empty or missing revlog for %s")
2115 raise util.Abort(_("empty or missing revlog for %s")
2116 % fname)
2116 % fname)
2117 fstate[0] = fname
2117 fstate[0] = fname
2118 fstate[1] = fnodes.pop(fname, {})
2118 fstate[1] = fnodes.pop(fname, {})
2119
2119
2120 nodelist = prune(filerevlog, fstate[1])
2120 nodelist = prune(filerevlog, fstate[1])
2121 if nodelist:
2121 if nodelist:
2122 count[0] += 1
2122 count[0] += 1
2123 yield bundler.fileheader(fname)
2123 yield bundler.fileheader(fname)
2124 for chunk in filerevlog.group(nodelist, bundler, reorder):
2124 for chunk in bundler.group(nodelist, filerevlog, reorder):
2125 yield chunk
2125 yield chunk
2126
2126
2127 # Signal that no more groups are left.
2127 # Signal that no more groups are left.
2128 yield bundler.close()
2128 yield bundler.close()
2129 progress(_bundling, None)
2129 progress(_bundling, None)
2130
2130
2131 if csets:
2131 if csets:
2132 self.hook('outgoing', node=hex(csets[0]), source=source)
2132 self.hook('outgoing', node=hex(csets[0]), source=source)
2133
2133
2134 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
2134 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
2135
2135
2136 def changegroup(self, basenodes, source):
2136 def changegroup(self, basenodes, source):
2137 # to avoid a race we use changegroupsubset() (issue1320)
2137 # to avoid a race we use changegroupsubset() (issue1320)
2138 return self.changegroupsubset(basenodes, self.heads(), source)
2138 return self.changegroupsubset(basenodes, self.heads(), source)
2139
2139
2140 @unfilteredmethod
2140 @unfilteredmethod
2141 def _changegroup(self, nodes, bundler, source):
2141 def _changegroup(self, nodes, bundler, source):
2142 """Compute the changegroup of all nodes that we have that a recipient
2142 """Compute the changegroup of all nodes that we have that a recipient
2143 doesn't. Return a chunkbuffer object whose read() method will return
2143 doesn't. Return a chunkbuffer object whose read() method will return
2144 successive changegroup chunks.
2144 successive changegroup chunks.
2145
2145
2146 This is much easier than the previous function as we can assume that
2146 This is much easier than the previous function as we can assume that
2147 the recipient has any changenode we aren't sending them.
2147 the recipient has any changenode we aren't sending them.
2148
2148
2149 nodes is the set of nodes to send"""
2149 nodes is the set of nodes to send"""
2150
2150
2151 cl = self.changelog
2151 cl = self.changelog
2152 mf = self.manifest
2152 mf = self.manifest
2153 mfs = {}
2153 mfs = {}
2154 changedfiles = set()
2154 changedfiles = set()
2155 fstate = ['']
2155 fstate = ['']
2156 count = [0, 0]
2156 count = [0, 0]
2157
2157
2158 self.hook('preoutgoing', throw=True, source=source)
2158 self.hook('preoutgoing', throw=True, source=source)
2159 self.changegroupinfo(nodes, source)
2159 self.changegroupinfo(nodes, source)
2160
2160
2161 revset = set([cl.rev(n) for n in nodes])
2161 revset = set([cl.rev(n) for n in nodes])
2162
2162
2163 def gennodelst(log):
2163 def gennodelst(log):
2164 ln, llr = log.node, log.linkrev
2164 ln, llr = log.node, log.linkrev
2165 return [ln(r) for r in log if llr(r) in revset]
2165 return [ln(r) for r in log if llr(r) in revset]
2166
2166
2167 progress = self.ui.progress
2167 progress = self.ui.progress
2168 _bundling = _('bundling')
2168 _bundling = _('bundling')
2169 _changesets = _('changesets')
2169 _changesets = _('changesets')
2170 _manifests = _('manifests')
2170 _manifests = _('manifests')
2171 _files = _('files')
2171 _files = _('files')
2172
2172
2173 def lookup(revlog, x):
2173 def lookup(revlog, x):
2174 if revlog == cl:
2174 if revlog == cl:
2175 c = cl.read(x)
2175 c = cl.read(x)
2176 changedfiles.update(c[3])
2176 changedfiles.update(c[3])
2177 mfs.setdefault(c[0], x)
2177 mfs.setdefault(c[0], x)
2178 count[0] += 1
2178 count[0] += 1
2179 progress(_bundling, count[0],
2179 progress(_bundling, count[0],
2180 unit=_changesets, total=count[1])
2180 unit=_changesets, total=count[1])
2181 return x
2181 return x
2182 elif revlog == mf:
2182 elif revlog == mf:
2183 count[0] += 1
2183 count[0] += 1
2184 progress(_bundling, count[0],
2184 progress(_bundling, count[0],
2185 unit=_manifests, total=count[1])
2185 unit=_manifests, total=count[1])
2186 return cl.node(revlog.linkrev(revlog.rev(x)))
2186 return cl.node(revlog.linkrev(revlog.rev(x)))
2187 else:
2187 else:
2188 progress(_bundling, count[0], item=fstate[0],
2188 progress(_bundling, count[0], item=fstate[0],
2189 total=count[1], unit=_files)
2189 total=count[1], unit=_files)
2190 return cl.node(revlog.linkrev(revlog.rev(x)))
2190 return cl.node(revlog.linkrev(revlog.rev(x)))
2191
2191
2192 bundler.start(lookup)
2192 bundler.start(lookup)
2193 reorder = self.ui.config('bundle', 'reorder', 'auto')
2193 reorder = self.ui.config('bundle', 'reorder', 'auto')
2194 if reorder == 'auto':
2194 if reorder == 'auto':
2195 reorder = None
2195 reorder = None
2196 else:
2196 else:
2197 reorder = util.parsebool(reorder)
2197 reorder = util.parsebool(reorder)
2198
2198
2199 def gengroup():
2199 def gengroup():
2200 '''yield a sequence of changegroup chunks (strings)'''
2200 '''yield a sequence of changegroup chunks (strings)'''
2201 # construct a list of all changed files
2201 # construct a list of all changed files
2202
2202
2203 count[:] = [0, len(nodes)]
2203 count[:] = [0, len(nodes)]
2204 for chunk in cl.group(nodes, bundler, reorder=reorder):
2204 for chunk in bundler.group(nodes, cl, reorder=reorder):
2205 yield chunk
2205 yield chunk
2206 progress(_bundling, None)
2206 progress(_bundling, None)
2207
2207
2208 count[:] = [0, len(mfs)]
2208 count[:] = [0, len(mfs)]
2209 for chunk in mf.group(gennodelst(mf), bundler, reorder=reorder):
2209 for chunk in bundler.group(gennodelst(mf), mf, reorder=reorder):
2210 yield chunk
2210 yield chunk
2211 progress(_bundling, None)
2211 progress(_bundling, None)
2212
2212
2213 count[:] = [0, len(changedfiles)]
2213 count[:] = [0, len(changedfiles)]
2214 for fname in sorted(changedfiles):
2214 for fname in sorted(changedfiles):
2215 filerevlog = self.file(fname)
2215 filerevlog = self.file(fname)
2216 if not len(filerevlog):
2216 if not len(filerevlog):
2217 raise util.Abort(_("empty or missing revlog for %s")
2217 raise util.Abort(_("empty or missing revlog for %s")
2218 % fname)
2218 % fname)
2219 fstate[0] = fname
2219 fstate[0] = fname
2220 nodelist = gennodelst(filerevlog)
2220 nodelist = gennodelst(filerevlog)
2221 if nodelist:
2221 if nodelist:
2222 count[0] += 1
2222 count[0] += 1
2223 yield bundler.fileheader(fname)
2223 yield bundler.fileheader(fname)
2224 for chunk in filerevlog.group(nodelist, bundler, reorder):
2224 for chunk in bundler.group(nodelist, filerevlog, reorder):
2225 yield chunk
2225 yield chunk
2226 yield bundler.close()
2226 yield bundler.close()
2227 progress(_bundling, None)
2227 progress(_bundling, None)
2228
2228
2229 if nodes:
2229 if nodes:
2230 self.hook('outgoing', node=hex(nodes[0]), source=source)
2230 self.hook('outgoing', node=hex(nodes[0]), source=source)
2231
2231
2232 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
2232 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
2233
2233
2234 @unfilteredmethod
2234 @unfilteredmethod
2235 def addchangegroup(self, source, srctype, url, emptyok=False):
2235 def addchangegroup(self, source, srctype, url, emptyok=False):
2236 """Add the changegroup returned by source.read() to this repo.
2236 """Add the changegroup returned by source.read() to this repo.
2237 srctype is a string like 'push', 'pull', or 'unbundle'. url is
2237 srctype is a string like 'push', 'pull', or 'unbundle'. url is
2238 the URL of the repo where this changegroup is coming from.
2238 the URL of the repo where this changegroup is coming from.
2239
2239
2240 Return an integer summarizing the change to this repo:
2240 Return an integer summarizing the change to this repo:
2241 - nothing changed or no source: 0
2241 - nothing changed or no source: 0
2242 - more heads than before: 1+added heads (2..n)
2242 - more heads than before: 1+added heads (2..n)
2243 - fewer heads than before: -1-removed heads (-2..-n)
2243 - fewer heads than before: -1-removed heads (-2..-n)
2244 - number of heads stays the same: 1
2244 - number of heads stays the same: 1
2245 """
2245 """
2246 def csmap(x):
2246 def csmap(x):
2247 self.ui.debug("add changeset %s\n" % short(x))
2247 self.ui.debug("add changeset %s\n" % short(x))
2248 return len(cl)
2248 return len(cl)
2249
2249
2250 def revmap(x):
2250 def revmap(x):
2251 return cl.rev(x)
2251 return cl.rev(x)
2252
2252
2253 if not source:
2253 if not source:
2254 return 0
2254 return 0
2255
2255
2256 self.hook('prechangegroup', throw=True, source=srctype, url=url)
2256 self.hook('prechangegroup', throw=True, source=srctype, url=url)
2257
2257
2258 changesets = files = revisions = 0
2258 changesets = files = revisions = 0
2259 efiles = set()
2259 efiles = set()
2260
2260
2261 # write changelog data to temp files so concurrent readers will not see
2261 # write changelog data to temp files so concurrent readers will not see
2262 # inconsistent view
2262 # inconsistent view
2263 cl = self.changelog
2263 cl = self.changelog
2264 cl.delayupdate()
2264 cl.delayupdate()
2265 oldheads = cl.heads()
2265 oldheads = cl.heads()
2266
2266
2267 tr = self.transaction("\n".join([srctype, util.hidepassword(url)]))
2267 tr = self.transaction("\n".join([srctype, util.hidepassword(url)]))
2268 try:
2268 try:
2269 trp = weakref.proxy(tr)
2269 trp = weakref.proxy(tr)
2270 # pull off the changeset group
2270 # pull off the changeset group
2271 self.ui.status(_("adding changesets\n"))
2271 self.ui.status(_("adding changesets\n"))
2272 clstart = len(cl)
2272 clstart = len(cl)
2273 class prog(object):
2273 class prog(object):
2274 step = _('changesets')
2274 step = _('changesets')
2275 count = 1
2275 count = 1
2276 ui = self.ui
2276 ui = self.ui
2277 total = None
2277 total = None
2278 def __call__(self):
2278 def __call__(self):
2279 self.ui.progress(self.step, self.count, unit=_('chunks'),
2279 self.ui.progress(self.step, self.count, unit=_('chunks'),
2280 total=self.total)
2280 total=self.total)
2281 self.count += 1
2281 self.count += 1
2282 pr = prog()
2282 pr = prog()
2283 source.callback = pr
2283 source.callback = pr
2284
2284
2285 source.changelogheader()
2285 source.changelogheader()
2286 srccontent = cl.addgroup(source, csmap, trp)
2286 srccontent = cl.addgroup(source, csmap, trp)
2287 if not (srccontent or emptyok):
2287 if not (srccontent or emptyok):
2288 raise util.Abort(_("received changelog group is empty"))
2288 raise util.Abort(_("received changelog group is empty"))
2289 clend = len(cl)
2289 clend = len(cl)
2290 changesets = clend - clstart
2290 changesets = clend - clstart
2291 for c in xrange(clstart, clend):
2291 for c in xrange(clstart, clend):
2292 efiles.update(self[c].files())
2292 efiles.update(self[c].files())
2293 efiles = len(efiles)
2293 efiles = len(efiles)
2294 self.ui.progress(_('changesets'), None)
2294 self.ui.progress(_('changesets'), None)
2295
2295
2296 # pull off the manifest group
2296 # pull off the manifest group
2297 self.ui.status(_("adding manifests\n"))
2297 self.ui.status(_("adding manifests\n"))
2298 pr.step = _('manifests')
2298 pr.step = _('manifests')
2299 pr.count = 1
2299 pr.count = 1
2300 pr.total = changesets # manifests <= changesets
2300 pr.total = changesets # manifests <= changesets
2301 # no need to check for empty manifest group here:
2301 # no need to check for empty manifest group here:
2302 # if the result of the merge of 1 and 2 is the same in 3 and 4,
2302 # if the result of the merge of 1 and 2 is the same in 3 and 4,
2303 # no new manifest will be created and the manifest group will
2303 # no new manifest will be created and the manifest group will
2304 # be empty during the pull
2304 # be empty during the pull
2305 source.manifestheader()
2305 source.manifestheader()
2306 self.manifest.addgroup(source, revmap, trp)
2306 self.manifest.addgroup(source, revmap, trp)
2307 self.ui.progress(_('manifests'), None)
2307 self.ui.progress(_('manifests'), None)
2308
2308
2309 needfiles = {}
2309 needfiles = {}
2310 if self.ui.configbool('server', 'validate', default=False):
2310 if self.ui.configbool('server', 'validate', default=False):
2311 # validate incoming csets have their manifests
2311 # validate incoming csets have their manifests
2312 for cset in xrange(clstart, clend):
2312 for cset in xrange(clstart, clend):
2313 mfest = self.changelog.read(self.changelog.node(cset))[0]
2313 mfest = self.changelog.read(self.changelog.node(cset))[0]
2314 mfest = self.manifest.readdelta(mfest)
2314 mfest = self.manifest.readdelta(mfest)
2315 # store file nodes we must see
2315 # store file nodes we must see
2316 for f, n in mfest.iteritems():
2316 for f, n in mfest.iteritems():
2317 needfiles.setdefault(f, set()).add(n)
2317 needfiles.setdefault(f, set()).add(n)
2318
2318
2319 # process the files
2319 # process the files
2320 self.ui.status(_("adding file changes\n"))
2320 self.ui.status(_("adding file changes\n"))
2321 pr.step = _('files')
2321 pr.step = _('files')
2322 pr.count = 1
2322 pr.count = 1
2323 pr.total = efiles
2323 pr.total = efiles
2324 source.callback = None
2324 source.callback = None
2325
2325
2326 while True:
2326 while True:
2327 chunkdata = source.filelogheader()
2327 chunkdata = source.filelogheader()
2328 if not chunkdata:
2328 if not chunkdata:
2329 break
2329 break
2330 f = chunkdata["filename"]
2330 f = chunkdata["filename"]
2331 self.ui.debug("adding %s revisions\n" % f)
2331 self.ui.debug("adding %s revisions\n" % f)
2332 pr()
2332 pr()
2333 fl = self.file(f)
2333 fl = self.file(f)
2334 o = len(fl)
2334 o = len(fl)
2335 if not fl.addgroup(source, revmap, trp):
2335 if not fl.addgroup(source, revmap, trp):
2336 raise util.Abort(_("received file revlog group is empty"))
2336 raise util.Abort(_("received file revlog group is empty"))
2337 revisions += len(fl) - o
2337 revisions += len(fl) - o
2338 files += 1
2338 files += 1
2339 if f in needfiles:
2339 if f in needfiles:
2340 needs = needfiles[f]
2340 needs = needfiles[f]
2341 for new in xrange(o, len(fl)):
2341 for new in xrange(o, len(fl)):
2342 n = fl.node(new)
2342 n = fl.node(new)
2343 if n in needs:
2343 if n in needs:
2344 needs.remove(n)
2344 needs.remove(n)
2345 else:
2345 else:
2346 raise util.Abort(
2346 raise util.Abort(
2347 _("received spurious file revlog entry"))
2347 _("received spurious file revlog entry"))
2348 if not needs:
2348 if not needs:
2349 del needfiles[f]
2349 del needfiles[f]
2350 self.ui.progress(_('files'), None)
2350 self.ui.progress(_('files'), None)
2351
2351
2352 for f, needs in needfiles.iteritems():
2352 for f, needs in needfiles.iteritems():
2353 fl = self.file(f)
2353 fl = self.file(f)
2354 for n in needs:
2354 for n in needs:
2355 try:
2355 try:
2356 fl.rev(n)
2356 fl.rev(n)
2357 except error.LookupError:
2357 except error.LookupError:
2358 raise util.Abort(
2358 raise util.Abort(
2359 _('missing file data for %s:%s - run hg verify') %
2359 _('missing file data for %s:%s - run hg verify') %
2360 (f, hex(n)))
2360 (f, hex(n)))
2361
2361
2362 dh = 0
2362 dh = 0
2363 if oldheads:
2363 if oldheads:
2364 heads = cl.heads()
2364 heads = cl.heads()
2365 dh = len(heads) - len(oldheads)
2365 dh = len(heads) - len(oldheads)
2366 for h in heads:
2366 for h in heads:
2367 if h not in oldheads and self[h].closesbranch():
2367 if h not in oldheads and self[h].closesbranch():
2368 dh -= 1
2368 dh -= 1
2369 htext = ""
2369 htext = ""
2370 if dh:
2370 if dh:
2371 htext = _(" (%+d heads)") % dh
2371 htext = _(" (%+d heads)") % dh
2372
2372
2373 self.ui.status(_("added %d changesets"
2373 self.ui.status(_("added %d changesets"
2374 " with %d changes to %d files%s\n")
2374 " with %d changes to %d files%s\n")
2375 % (changesets, revisions, files, htext))
2375 % (changesets, revisions, files, htext))
2376 self.invalidatevolatilesets()
2376 self.invalidatevolatilesets()
2377
2377
2378 if changesets > 0:
2378 if changesets > 0:
2379 p = lambda: cl.writepending() and self.root or ""
2379 p = lambda: cl.writepending() and self.root or ""
2380 self.hook('pretxnchangegroup', throw=True,
2380 self.hook('pretxnchangegroup', throw=True,
2381 node=hex(cl.node(clstart)), source=srctype,
2381 node=hex(cl.node(clstart)), source=srctype,
2382 url=url, pending=p)
2382 url=url, pending=p)
2383
2383
2384 added = [cl.node(r) for r in xrange(clstart, clend)]
2384 added = [cl.node(r) for r in xrange(clstart, clend)]
2385 publishing = self.ui.configbool('phases', 'publish', True)
2385 publishing = self.ui.configbool('phases', 'publish', True)
2386 if srctype == 'push':
2386 if srctype == 'push':
2387 # Old server can not push the boundary themself.
2387 # Old server can not push the boundary themself.
2388 # New server won't push the boundary if changeset already
2388 # New server won't push the boundary if changeset already
2389 # existed locally as secrete
2389 # existed locally as secrete
2390 #
2390 #
2391 # We should not use added here but the list of all change in
2391 # We should not use added here but the list of all change in
2392 # the bundle
2392 # the bundle
2393 if publishing:
2393 if publishing:
2394 phases.advanceboundary(self, phases.public, srccontent)
2394 phases.advanceboundary(self, phases.public, srccontent)
2395 else:
2395 else:
2396 phases.advanceboundary(self, phases.draft, srccontent)
2396 phases.advanceboundary(self, phases.draft, srccontent)
2397 phases.retractboundary(self, phases.draft, added)
2397 phases.retractboundary(self, phases.draft, added)
2398 elif srctype != 'strip':
2398 elif srctype != 'strip':
2399 # publishing only alter behavior during push
2399 # publishing only alter behavior during push
2400 #
2400 #
2401 # strip should not touch boundary at all
2401 # strip should not touch boundary at all
2402 phases.retractboundary(self, phases.draft, added)
2402 phases.retractboundary(self, phases.draft, added)
2403
2403
2404 # make changelog see real files again
2404 # make changelog see real files again
2405 cl.finalize(trp)
2405 cl.finalize(trp)
2406
2406
2407 tr.close()
2407 tr.close()
2408
2408
2409 if changesets > 0:
2409 if changesets > 0:
2410 if srctype != 'strip':
2410 if srctype != 'strip':
2411 # During strip, branchcache is invalid but coming call to
2411 # During strip, branchcache is invalid but coming call to
2412 # `destroyed` will repair it.
2412 # `destroyed` will repair it.
2413 # In other case we can safely update cache on disk.
2413 # In other case we can safely update cache on disk.
2414 branchmap.updatecache(self.filtered('served'))
2414 branchmap.updatecache(self.filtered('served'))
2415 def runhooks():
2415 def runhooks():
2416 # forcefully update the on-disk branch cache
2416 # forcefully update the on-disk branch cache
2417 self.ui.debug("updating the branch cache\n")
2417 self.ui.debug("updating the branch cache\n")
2418 self.hook("changegroup", node=hex(cl.node(clstart)),
2418 self.hook("changegroup", node=hex(cl.node(clstart)),
2419 source=srctype, url=url)
2419 source=srctype, url=url)
2420
2420
2421 for n in added:
2421 for n in added:
2422 self.hook("incoming", node=hex(n), source=srctype,
2422 self.hook("incoming", node=hex(n), source=srctype,
2423 url=url)
2423 url=url)
2424
2424
2425 newheads = [h for h in self.heads() if h not in oldheads]
2425 newheads = [h for h in self.heads() if h not in oldheads]
2426 self.ui.log("incoming",
2426 self.ui.log("incoming",
2427 "%s incoming changes - new heads: %s\n",
2427 "%s incoming changes - new heads: %s\n",
2428 len(added),
2428 len(added),
2429 ', '.join([hex(c[:6]) for c in newheads]))
2429 ', '.join([hex(c[:6]) for c in newheads]))
2430 self._afterlock(runhooks)
2430 self._afterlock(runhooks)
2431
2431
2432 finally:
2432 finally:
2433 tr.release()
2433 tr.release()
2434 # never return 0 here:
2434 # never return 0 here:
2435 if dh < 0:
2435 if dh < 0:
2436 return dh - 1
2436 return dh - 1
2437 else:
2437 else:
2438 return dh + 1
2438 return dh + 1
2439
2439
2440 def stream_in(self, remote, requirements):
2440 def stream_in(self, remote, requirements):
2441 lock = self.lock()
2441 lock = self.lock()
2442 try:
2442 try:
2443 # Save remote branchmap. We will use it later
2443 # Save remote branchmap. We will use it later
2444 # to speed up branchcache creation
2444 # to speed up branchcache creation
2445 rbranchmap = None
2445 rbranchmap = None
2446 if remote.capable("branchmap"):
2446 if remote.capable("branchmap"):
2447 rbranchmap = remote.branchmap()
2447 rbranchmap = remote.branchmap()
2448
2448
2449 fp = remote.stream_out()
2449 fp = remote.stream_out()
2450 l = fp.readline()
2450 l = fp.readline()
2451 try:
2451 try:
2452 resp = int(l)
2452 resp = int(l)
2453 except ValueError:
2453 except ValueError:
2454 raise error.ResponseError(
2454 raise error.ResponseError(
2455 _('unexpected response from remote server:'), l)
2455 _('unexpected response from remote server:'), l)
2456 if resp == 1:
2456 if resp == 1:
2457 raise util.Abort(_('operation forbidden by server'))
2457 raise util.Abort(_('operation forbidden by server'))
2458 elif resp == 2:
2458 elif resp == 2:
2459 raise util.Abort(_('locking the remote repository failed'))
2459 raise util.Abort(_('locking the remote repository failed'))
2460 elif resp != 0:
2460 elif resp != 0:
2461 raise util.Abort(_('the server sent an unknown error code'))
2461 raise util.Abort(_('the server sent an unknown error code'))
2462 self.ui.status(_('streaming all changes\n'))
2462 self.ui.status(_('streaming all changes\n'))
2463 l = fp.readline()
2463 l = fp.readline()
2464 try:
2464 try:
2465 total_files, total_bytes = map(int, l.split(' ', 1))
2465 total_files, total_bytes = map(int, l.split(' ', 1))
2466 except (ValueError, TypeError):
2466 except (ValueError, TypeError):
2467 raise error.ResponseError(
2467 raise error.ResponseError(
2468 _('unexpected response from remote server:'), l)
2468 _('unexpected response from remote server:'), l)
2469 self.ui.status(_('%d files to transfer, %s of data\n') %
2469 self.ui.status(_('%d files to transfer, %s of data\n') %
2470 (total_files, util.bytecount(total_bytes)))
2470 (total_files, util.bytecount(total_bytes)))
2471 handled_bytes = 0
2471 handled_bytes = 0
2472 self.ui.progress(_('clone'), 0, total=total_bytes)
2472 self.ui.progress(_('clone'), 0, total=total_bytes)
2473 start = time.time()
2473 start = time.time()
2474 for i in xrange(total_files):
2474 for i in xrange(total_files):
2475 # XXX doesn't support '\n' or '\r' in filenames
2475 # XXX doesn't support '\n' or '\r' in filenames
2476 l = fp.readline()
2476 l = fp.readline()
2477 try:
2477 try:
2478 name, size = l.split('\0', 1)
2478 name, size = l.split('\0', 1)
2479 size = int(size)
2479 size = int(size)
2480 except (ValueError, TypeError):
2480 except (ValueError, TypeError):
2481 raise error.ResponseError(
2481 raise error.ResponseError(
2482 _('unexpected response from remote server:'), l)
2482 _('unexpected response from remote server:'), l)
2483 if self.ui.debugflag:
2483 if self.ui.debugflag:
2484 self.ui.debug('adding %s (%s)\n' %
2484 self.ui.debug('adding %s (%s)\n' %
2485 (name, util.bytecount(size)))
2485 (name, util.bytecount(size)))
2486 # for backwards compat, name was partially encoded
2486 # for backwards compat, name was partially encoded
2487 ofp = self.sopener(store.decodedir(name), 'w')
2487 ofp = self.sopener(store.decodedir(name), 'w')
2488 for chunk in util.filechunkiter(fp, limit=size):
2488 for chunk in util.filechunkiter(fp, limit=size):
2489 handled_bytes += len(chunk)
2489 handled_bytes += len(chunk)
2490 self.ui.progress(_('clone'), handled_bytes,
2490 self.ui.progress(_('clone'), handled_bytes,
2491 total=total_bytes)
2491 total=total_bytes)
2492 ofp.write(chunk)
2492 ofp.write(chunk)
2493 ofp.close()
2493 ofp.close()
2494 elapsed = time.time() - start
2494 elapsed = time.time() - start
2495 if elapsed <= 0:
2495 if elapsed <= 0:
2496 elapsed = 0.001
2496 elapsed = 0.001
2497 self.ui.progress(_('clone'), None)
2497 self.ui.progress(_('clone'), None)
2498 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
2498 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
2499 (util.bytecount(total_bytes), elapsed,
2499 (util.bytecount(total_bytes), elapsed,
2500 util.bytecount(total_bytes / elapsed)))
2500 util.bytecount(total_bytes / elapsed)))
2501
2501
2502 # new requirements = old non-format requirements +
2502 # new requirements = old non-format requirements +
2503 # new format-related
2503 # new format-related
2504 # requirements from the streamed-in repository
2504 # requirements from the streamed-in repository
2505 requirements.update(set(self.requirements) - self.supportedformats)
2505 requirements.update(set(self.requirements) - self.supportedformats)
2506 self._applyrequirements(requirements)
2506 self._applyrequirements(requirements)
2507 self._writerequirements()
2507 self._writerequirements()
2508
2508
2509 if rbranchmap:
2509 if rbranchmap:
2510 rbheads = []
2510 rbheads = []
2511 for bheads in rbranchmap.itervalues():
2511 for bheads in rbranchmap.itervalues():
2512 rbheads.extend(bheads)
2512 rbheads.extend(bheads)
2513
2513
2514 if rbheads:
2514 if rbheads:
2515 rtiprev = max((int(self.changelog.rev(node))
2515 rtiprev = max((int(self.changelog.rev(node))
2516 for node in rbheads))
2516 for node in rbheads))
2517 cache = branchmap.branchcache(rbranchmap,
2517 cache = branchmap.branchcache(rbranchmap,
2518 self[rtiprev].node(),
2518 self[rtiprev].node(),
2519 rtiprev)
2519 rtiprev)
2520 # Try to stick it as low as possible
2520 # Try to stick it as low as possible
2521 # filter above served are unlikely to be fetch from a clone
2521 # filter above served are unlikely to be fetch from a clone
2522 for candidate in ('base', 'immutable', 'served'):
2522 for candidate in ('base', 'immutable', 'served'):
2523 rview = self.filtered(candidate)
2523 rview = self.filtered(candidate)
2524 if cache.validfor(rview):
2524 if cache.validfor(rview):
2525 self._branchcaches[candidate] = cache
2525 self._branchcaches[candidate] = cache
2526 cache.write(rview)
2526 cache.write(rview)
2527 break
2527 break
2528 self.invalidate()
2528 self.invalidate()
2529 return len(self.heads()) + 1
2529 return len(self.heads()) + 1
2530 finally:
2530 finally:
2531 lock.release()
2531 lock.release()
2532
2532
2533 def clone(self, remote, heads=[], stream=False):
2533 def clone(self, remote, heads=[], stream=False):
2534 '''clone remote repository.
2534 '''clone remote repository.
2535
2535
2536 keyword arguments:
2536 keyword arguments:
2537 heads: list of revs to clone (forces use of pull)
2537 heads: list of revs to clone (forces use of pull)
2538 stream: use streaming clone if possible'''
2538 stream: use streaming clone if possible'''
2539
2539
2540 # now, all clients that can request uncompressed clones can
2540 # now, all clients that can request uncompressed clones can
2541 # read repo formats supported by all servers that can serve
2541 # read repo formats supported by all servers that can serve
2542 # them.
2542 # them.
2543
2543
2544 # if revlog format changes, client will have to check version
2544 # if revlog format changes, client will have to check version
2545 # and format flags on "stream" capability, and use
2545 # and format flags on "stream" capability, and use
2546 # uncompressed only if compatible.
2546 # uncompressed only if compatible.
2547
2547
2548 if not stream:
2548 if not stream:
2549 # if the server explicitly prefers to stream (for fast LANs)
2549 # if the server explicitly prefers to stream (for fast LANs)
2550 stream = remote.capable('stream-preferred')
2550 stream = remote.capable('stream-preferred')
2551
2551
2552 if stream and not heads:
2552 if stream and not heads:
2553 # 'stream' means remote revlog format is revlogv1 only
2553 # 'stream' means remote revlog format is revlogv1 only
2554 if remote.capable('stream'):
2554 if remote.capable('stream'):
2555 return self.stream_in(remote, set(('revlogv1',)))
2555 return self.stream_in(remote, set(('revlogv1',)))
2556 # otherwise, 'streamreqs' contains the remote revlog format
2556 # otherwise, 'streamreqs' contains the remote revlog format
2557 streamreqs = remote.capable('streamreqs')
2557 streamreqs = remote.capable('streamreqs')
2558 if streamreqs:
2558 if streamreqs:
2559 streamreqs = set(streamreqs.split(','))
2559 streamreqs = set(streamreqs.split(','))
2560 # if we support it, stream in and adjust our requirements
2560 # if we support it, stream in and adjust our requirements
2561 if not streamreqs - self.supportedformats:
2561 if not streamreqs - self.supportedformats:
2562 return self.stream_in(remote, streamreqs)
2562 return self.stream_in(remote, streamreqs)
2563 return self.pull(remote, heads)
2563 return self.pull(remote, heads)
2564
2564
2565 def pushkey(self, namespace, key, old, new):
2565 def pushkey(self, namespace, key, old, new):
2566 self.hook('prepushkey', throw=True, namespace=namespace, key=key,
2566 self.hook('prepushkey', throw=True, namespace=namespace, key=key,
2567 old=old, new=new)
2567 old=old, new=new)
2568 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
2568 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
2569 ret = pushkey.push(self, namespace, key, old, new)
2569 ret = pushkey.push(self, namespace, key, old, new)
2570 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
2570 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
2571 ret=ret)
2571 ret=ret)
2572 return ret
2572 return ret
2573
2573
2574 def listkeys(self, namespace):
2574 def listkeys(self, namespace):
2575 self.hook('prelistkeys', throw=True, namespace=namespace)
2575 self.hook('prelistkeys', throw=True, namespace=namespace)
2576 self.ui.debug('listing keys for "%s"\n' % namespace)
2576 self.ui.debug('listing keys for "%s"\n' % namespace)
2577 values = pushkey.list(self, namespace)
2577 values = pushkey.list(self, namespace)
2578 self.hook('listkeys', namespace=namespace, values=values)
2578 self.hook('listkeys', namespace=namespace, values=values)
2579 return values
2579 return values
2580
2580
2581 def debugwireargs(self, one, two, three=None, four=None, five=None):
2581 def debugwireargs(self, one, two, three=None, four=None, five=None):
2582 '''used to test argument passing over the wire'''
2582 '''used to test argument passing over the wire'''
2583 return "%s %s %s %s %s" % (one, two, three, four, five)
2583 return "%s %s %s %s %s" % (one, two, three, four, five)
2584
2584
2585 def savecommitmessage(self, text):
2585 def savecommitmessage(self, text):
2586 fp = self.opener('last-message.txt', 'wb')
2586 fp = self.opener('last-message.txt', 'wb')
2587 try:
2587 try:
2588 fp.write(text)
2588 fp.write(text)
2589 finally:
2589 finally:
2590 fp.close()
2590 fp.close()
2591 return self.pathto(fp.name[len(self.root) + 1:])
2591 return self.pathto(fp.name[len(self.root) + 1:])
2592
2592
2593 # used to avoid circular references so destructors work
2593 # used to avoid circular references so destructors work
2594 def aftertrans(files):
2594 def aftertrans(files):
2595 renamefiles = [tuple(t) for t in files]
2595 renamefiles = [tuple(t) for t in files]
2596 def a():
2596 def a():
2597 for vfs, src, dest in renamefiles:
2597 for vfs, src, dest in renamefiles:
2598 try:
2598 try:
2599 vfs.rename(src, dest)
2599 vfs.rename(src, dest)
2600 except OSError: # journal file does not yet exist
2600 except OSError: # journal file does not yet exist
2601 pass
2601 pass
2602 return a
2602 return a
2603
2603
2604 def undoname(fn):
2604 def undoname(fn):
2605 base, name = os.path.split(fn)
2605 base, name = os.path.split(fn)
2606 assert name.startswith('journal')
2606 assert name.startswith('journal')
2607 return os.path.join(base, name.replace('journal', 'undo', 1))
2607 return os.path.join(base, name.replace('journal', 'undo', 1))
2608
2608
2609 def instance(ui, path, create):
2609 def instance(ui, path, create):
2610 return localrepository(ui, util.urllocalpath(path), create)
2610 return localrepository(ui, util.urllocalpath(path), create)
2611
2611
2612 def islocal(path):
2612 def islocal(path):
2613 return True
2613 return True
@@ -1,1340 +1,1302 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 or any later version.
6 # GNU General Public License version 2 or any later version.
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
15 from node import bin, hex, nullid, nullrev
16 from i18n import _
16 from i18n import _
17 import ancestor, mdiff, parsers, error, util, dagutil
17 import 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 header flags
26 # revlog header flags
27 REVLOGV0 = 0
27 REVLOGV0 = 0
28 REVLOGNG = 1
28 REVLOGNG = 1
29 REVLOGNGINLINEDATA = (1 << 16)
29 REVLOGNGINLINEDATA = (1 << 16)
30 REVLOGGENERALDELTA = (1 << 17)
30 REVLOGGENERALDELTA = (1 << 17)
31 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
31 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
32 REVLOG_DEFAULT_FORMAT = REVLOGNG
32 REVLOG_DEFAULT_FORMAT = REVLOGNG
33 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
33 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
34 REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGGENERALDELTA
34 REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGGENERALDELTA
35
35
36 # revlog index flags
36 # revlog index flags
37 REVIDX_KNOWN_FLAGS = 0
37 REVIDX_KNOWN_FLAGS = 0
38
38
39 # max size of revlog with inline data
39 # max size of revlog with inline data
40 _maxinline = 131072
40 _maxinline = 131072
41 _chunksize = 1048576
41 _chunksize = 1048576
42
42
43 RevlogError = error.RevlogError
43 RevlogError = error.RevlogError
44 LookupError = error.LookupError
44 LookupError = error.LookupError
45
45
46 def getoffset(q):
46 def getoffset(q):
47 return int(q >> 16)
47 return int(q >> 16)
48
48
49 def gettype(q):
49 def gettype(q):
50 return int(q & 0xFFFF)
50 return int(q & 0xFFFF)
51
51
52 def offset_type(offset, type):
52 def offset_type(offset, type):
53 return long(long(offset) << 16 | type)
53 return long(long(offset) << 16 | type)
54
54
55 nullhash = _sha(nullid)
55 nullhash = _sha(nullid)
56
56
57 def hash(text, p1, p2):
57 def hash(text, p1, p2):
58 """generate a hash from the given text and its parent hashes
58 """generate a hash from the given text and its parent hashes
59
59
60 This hash combines both the current file contents and its history
60 This hash combines both the current file contents and its history
61 in a manner that makes it easy to distinguish nodes with the same
61 in a manner that makes it easy to distinguish nodes with the same
62 content in the revision graph.
62 content in the revision graph.
63 """
63 """
64 # As of now, if one of the parent node is null, p2 is null
64 # As of now, if one of the parent node is null, p2 is null
65 if p2 == nullid:
65 if p2 == nullid:
66 # deep copy of a hash is faster than creating one
66 # deep copy of a hash is faster than creating one
67 s = nullhash.copy()
67 s = nullhash.copy()
68 s.update(p1)
68 s.update(p1)
69 else:
69 else:
70 # none of the parent nodes are nullid
70 # none of the parent nodes are nullid
71 l = [p1, p2]
71 l = [p1, p2]
72 l.sort()
72 l.sort()
73 s = _sha(l[0])
73 s = _sha(l[0])
74 s.update(l[1])
74 s.update(l[1])
75 s.update(text)
75 s.update(text)
76 return s.digest()
76 return s.digest()
77
77
78 def decompress(bin):
78 def decompress(bin):
79 """ decompress the given input """
79 """ decompress the given input """
80 if not bin:
80 if not bin:
81 return bin
81 return bin
82 t = bin[0]
82 t = bin[0]
83 if t == '\0':
83 if t == '\0':
84 return bin
84 return bin
85 if t == 'x':
85 if t == 'x':
86 try:
86 try:
87 return _decompress(bin)
87 return _decompress(bin)
88 except zlib.error, e:
88 except zlib.error, e:
89 raise RevlogError(_("revlog decompress error: %s") % str(e))
89 raise RevlogError(_("revlog decompress error: %s") % str(e))
90 if t == 'u':
90 if t == 'u':
91 return bin[1:]
91 return bin[1:]
92 raise RevlogError(_("unknown compression type %r") % t)
92 raise RevlogError(_("unknown compression type %r") % t)
93
93
94 # index v0:
94 # index v0:
95 # 4 bytes: offset
95 # 4 bytes: offset
96 # 4 bytes: compressed length
96 # 4 bytes: compressed length
97 # 4 bytes: base rev
97 # 4 bytes: base rev
98 # 4 bytes: link rev
98 # 4 bytes: link rev
99 # 32 bytes: parent 1 nodeid
99 # 32 bytes: parent 1 nodeid
100 # 32 bytes: parent 2 nodeid
100 # 32 bytes: parent 2 nodeid
101 # 32 bytes: nodeid
101 # 32 bytes: nodeid
102 indexformatv0 = ">4l20s20s20s"
102 indexformatv0 = ">4l20s20s20s"
103 v0shaoffset = 56
103 v0shaoffset = 56
104
104
105 class revlogoldio(object):
105 class revlogoldio(object):
106 def __init__(self):
106 def __init__(self):
107 self.size = struct.calcsize(indexformatv0)
107 self.size = struct.calcsize(indexformatv0)
108
108
109 def parseindex(self, data, inline):
109 def parseindex(self, data, inline):
110 s = self.size
110 s = self.size
111 index = []
111 index = []
112 nodemap = {nullid: nullrev}
112 nodemap = {nullid: nullrev}
113 n = off = 0
113 n = off = 0
114 l = len(data)
114 l = len(data)
115 while off + s <= l:
115 while off + s <= l:
116 cur = data[off:off + s]
116 cur = data[off:off + s]
117 off += s
117 off += s
118 e = _unpack(indexformatv0, cur)
118 e = _unpack(indexformatv0, cur)
119 # transform to revlogv1 format
119 # transform to revlogv1 format
120 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
120 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
121 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
121 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
122 index.append(e2)
122 index.append(e2)
123 nodemap[e[6]] = n
123 nodemap[e[6]] = n
124 n += 1
124 n += 1
125
125
126 # add the magic null revision at -1
126 # add the magic null revision at -1
127 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
127 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
128
128
129 return index, nodemap, None
129 return index, nodemap, None
130
130
131 def packentry(self, entry, node, version, rev):
131 def packentry(self, entry, node, version, rev):
132 if gettype(entry[0]):
132 if gettype(entry[0]):
133 raise RevlogError(_("index entry flags need RevlogNG"))
133 raise RevlogError(_("index entry flags need RevlogNG"))
134 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
134 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
135 node(entry[5]), node(entry[6]), entry[7])
135 node(entry[5]), node(entry[6]), entry[7])
136 return _pack(indexformatv0, *e2)
136 return _pack(indexformatv0, *e2)
137
137
138 # index ng:
138 # index ng:
139 # 6 bytes: offset
139 # 6 bytes: offset
140 # 2 bytes: flags
140 # 2 bytes: flags
141 # 4 bytes: compressed length
141 # 4 bytes: compressed length
142 # 4 bytes: uncompressed length
142 # 4 bytes: uncompressed length
143 # 4 bytes: base rev
143 # 4 bytes: base rev
144 # 4 bytes: link rev
144 # 4 bytes: link rev
145 # 4 bytes: parent 1 rev
145 # 4 bytes: parent 1 rev
146 # 4 bytes: parent 2 rev
146 # 4 bytes: parent 2 rev
147 # 32 bytes: nodeid
147 # 32 bytes: nodeid
148 indexformatng = ">Qiiiiii20s12x"
148 indexformatng = ">Qiiiiii20s12x"
149 ngshaoffset = 32
149 ngshaoffset = 32
150 versionformat = ">I"
150 versionformat = ">I"
151
151
152 class revlogio(object):
152 class revlogio(object):
153 def __init__(self):
153 def __init__(self):
154 self.size = struct.calcsize(indexformatng)
154 self.size = struct.calcsize(indexformatng)
155
155
156 def parseindex(self, data, inline):
156 def parseindex(self, data, inline):
157 # call the C implementation to parse the index data
157 # call the C implementation to parse the index data
158 index, cache = parsers.parse_index2(data, inline)
158 index, cache = parsers.parse_index2(data, inline)
159 return index, getattr(index, 'nodemap', None), cache
159 return index, getattr(index, 'nodemap', None), cache
160
160
161 def packentry(self, entry, node, version, rev):
161 def packentry(self, entry, node, version, rev):
162 p = _pack(indexformatng, *entry)
162 p = _pack(indexformatng, *entry)
163 if rev == 0:
163 if rev == 0:
164 p = _pack(versionformat, version) + p[4:]
164 p = _pack(versionformat, version) + p[4:]
165 return p
165 return p
166
166
167 class revlog(object):
167 class revlog(object):
168 """
168 """
169 the underlying revision storage object
169 the underlying revision storage object
170
170
171 A revlog consists of two parts, an index and the revision data.
171 A revlog consists of two parts, an index and the revision data.
172
172
173 The index is a file with a fixed record size containing
173 The index is a file with a fixed record size containing
174 information on each revision, including its nodeid (hash), the
174 information on each revision, including its nodeid (hash), the
175 nodeids of its parents, the position and offset of its data within
175 nodeids of its parents, the position and offset of its data within
176 the data file, and the revision it's based on. Finally, each entry
176 the data file, and the revision it's based on. Finally, each entry
177 contains a linkrev entry that can serve as a pointer to external
177 contains a linkrev entry that can serve as a pointer to external
178 data.
178 data.
179
179
180 The revision data itself is a linear collection of data chunks.
180 The revision data itself is a linear collection of data chunks.
181 Each chunk represents a revision and is usually represented as a
181 Each chunk represents a revision and is usually represented as a
182 delta against the previous chunk. To bound lookup time, runs of
182 delta against the previous chunk. To bound lookup time, runs of
183 deltas are limited to about 2 times the length of the original
183 deltas are limited to about 2 times the length of the original
184 version data. This makes retrieval of a version proportional to
184 version data. This makes retrieval of a version proportional to
185 its size, or O(1) relative to the number of revisions.
185 its size, or O(1) relative to the number of revisions.
186
186
187 Both pieces of the revlog are written to in an append-only
187 Both pieces of the revlog are written to in an append-only
188 fashion, which means we never need to rewrite a file to insert or
188 fashion, which means we never need to rewrite a file to insert or
189 remove data, and can use some simple techniques to avoid the need
189 remove data, and can use some simple techniques to avoid the need
190 for locking while reading.
190 for locking while reading.
191 """
191 """
192 def __init__(self, opener, indexfile):
192 def __init__(self, opener, indexfile):
193 """
193 """
194 create a revlog object
194 create a revlog object
195
195
196 opener is a function that abstracts the file opening operation
196 opener is a function that abstracts the file opening operation
197 and can be used to implement COW semantics or the like.
197 and can be used to implement COW semantics or the like.
198 """
198 """
199 self.indexfile = indexfile
199 self.indexfile = indexfile
200 self.datafile = indexfile[:-2] + ".d"
200 self.datafile = indexfile[:-2] + ".d"
201 self.opener = opener
201 self.opener = opener
202 self._cache = None
202 self._cache = None
203 self._basecache = (0, 0)
203 self._basecache = (0, 0)
204 self._chunkcache = (0, '')
204 self._chunkcache = (0, '')
205 self.index = []
205 self.index = []
206 self._pcache = {}
206 self._pcache = {}
207 self._nodecache = {nullid: nullrev}
207 self._nodecache = {nullid: nullrev}
208 self._nodepos = None
208 self._nodepos = None
209
209
210 v = REVLOG_DEFAULT_VERSION
210 v = REVLOG_DEFAULT_VERSION
211 opts = getattr(opener, 'options', None)
211 opts = getattr(opener, 'options', None)
212 if opts is not None:
212 if opts is not None:
213 if 'revlogv1' in opts:
213 if 'revlogv1' in opts:
214 if 'generaldelta' in opts:
214 if 'generaldelta' in opts:
215 v |= REVLOGGENERALDELTA
215 v |= REVLOGGENERALDELTA
216 else:
216 else:
217 v = 0
217 v = 0
218
218
219 i = ''
219 i = ''
220 self._initempty = True
220 self._initempty = True
221 try:
221 try:
222 f = self.opener(self.indexfile)
222 f = self.opener(self.indexfile)
223 i = f.read()
223 i = f.read()
224 f.close()
224 f.close()
225 if len(i) > 0:
225 if len(i) > 0:
226 v = struct.unpack(versionformat, i[:4])[0]
226 v = struct.unpack(versionformat, i[:4])[0]
227 self._initempty = False
227 self._initempty = False
228 except IOError, inst:
228 except IOError, inst:
229 if inst.errno != errno.ENOENT:
229 if inst.errno != errno.ENOENT:
230 raise
230 raise
231
231
232 self.version = v
232 self.version = v
233 self._inline = v & REVLOGNGINLINEDATA
233 self._inline = v & REVLOGNGINLINEDATA
234 self._generaldelta = v & REVLOGGENERALDELTA
234 self._generaldelta = v & REVLOGGENERALDELTA
235 flags = v & ~0xFFFF
235 flags = v & ~0xFFFF
236 fmt = v & 0xFFFF
236 fmt = v & 0xFFFF
237 if fmt == REVLOGV0 and flags:
237 if fmt == REVLOGV0 and flags:
238 raise RevlogError(_("index %s unknown flags %#04x for format v0")
238 raise RevlogError(_("index %s unknown flags %#04x for format v0")
239 % (self.indexfile, flags >> 16))
239 % (self.indexfile, flags >> 16))
240 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
240 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
241 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
241 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
242 % (self.indexfile, flags >> 16))
242 % (self.indexfile, flags >> 16))
243 elif fmt > REVLOGNG:
243 elif fmt > REVLOGNG:
244 raise RevlogError(_("index %s unknown format %d")
244 raise RevlogError(_("index %s unknown format %d")
245 % (self.indexfile, fmt))
245 % (self.indexfile, fmt))
246
246
247 self._io = revlogio()
247 self._io = revlogio()
248 if self.version == REVLOGV0:
248 if self.version == REVLOGV0:
249 self._io = revlogoldio()
249 self._io = revlogoldio()
250 try:
250 try:
251 d = self._io.parseindex(i, self._inline)
251 d = self._io.parseindex(i, self._inline)
252 except (ValueError, IndexError):
252 except (ValueError, IndexError):
253 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
253 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
254 self.index, nodemap, self._chunkcache = d
254 self.index, nodemap, self._chunkcache = d
255 if nodemap is not None:
255 if nodemap is not None:
256 self.nodemap = self._nodecache = nodemap
256 self.nodemap = self._nodecache = nodemap
257 if not self._chunkcache:
257 if not self._chunkcache:
258 self._chunkclear()
258 self._chunkclear()
259
259
260 def tip(self):
260 def tip(self):
261 return self.node(len(self.index) - 2)
261 return self.node(len(self.index) - 2)
262 def __len__(self):
262 def __len__(self):
263 return len(self.index) - 1
263 return len(self.index) - 1
264 def __iter__(self):
264 def __iter__(self):
265 return iter(xrange(len(self)))
265 return iter(xrange(len(self)))
266 def revs(self, start=0, stop=None):
266 def revs(self, start=0, stop=None):
267 """iterate over all rev in this revlog (from start to stop)"""
267 """iterate over all rev in this revlog (from start to stop)"""
268 step = 1
268 step = 1
269 if stop is not None:
269 if stop is not None:
270 if start > stop:
270 if start > stop:
271 step = -1
271 step = -1
272 stop += step
272 stop += step
273 else:
273 else:
274 stop = len(self)
274 stop = len(self)
275 return xrange(start, stop, step)
275 return xrange(start, stop, step)
276
276
277 @util.propertycache
277 @util.propertycache
278 def nodemap(self):
278 def nodemap(self):
279 self.rev(self.node(0))
279 self.rev(self.node(0))
280 return self._nodecache
280 return self._nodecache
281
281
282 def hasnode(self, node):
282 def hasnode(self, node):
283 try:
283 try:
284 self.rev(node)
284 self.rev(node)
285 return True
285 return True
286 except KeyError:
286 except KeyError:
287 return False
287 return False
288
288
289 def clearcaches(self):
289 def clearcaches(self):
290 try:
290 try:
291 self._nodecache.clearcaches()
291 self._nodecache.clearcaches()
292 except AttributeError:
292 except AttributeError:
293 self._nodecache = {nullid: nullrev}
293 self._nodecache = {nullid: nullrev}
294 self._nodepos = None
294 self._nodepos = None
295
295
296 def rev(self, node):
296 def rev(self, node):
297 try:
297 try:
298 return self._nodecache[node]
298 return self._nodecache[node]
299 except RevlogError:
299 except RevlogError:
300 # parsers.c radix tree lookup failed
300 # parsers.c radix tree lookup failed
301 raise LookupError(node, self.indexfile, _('no node'))
301 raise LookupError(node, self.indexfile, _('no node'))
302 except KeyError:
302 except KeyError:
303 # pure python cache lookup failed
303 # pure python cache lookup failed
304 n = self._nodecache
304 n = self._nodecache
305 i = self.index
305 i = self.index
306 p = self._nodepos
306 p = self._nodepos
307 if p is None:
307 if p is None:
308 p = len(i) - 2
308 p = len(i) - 2
309 for r in xrange(p, -1, -1):
309 for r in xrange(p, -1, -1):
310 v = i[r][7]
310 v = i[r][7]
311 n[v] = r
311 n[v] = r
312 if v == node:
312 if v == node:
313 self._nodepos = r - 1
313 self._nodepos = r - 1
314 return r
314 return r
315 raise LookupError(node, self.indexfile, _('no node'))
315 raise LookupError(node, self.indexfile, _('no node'))
316
316
317 def node(self, rev):
317 def node(self, rev):
318 return self.index[rev][7]
318 return self.index[rev][7]
319 def linkrev(self, rev):
319 def linkrev(self, rev):
320 return self.index[rev][4]
320 return self.index[rev][4]
321 def parents(self, node):
321 def parents(self, node):
322 i = self.index
322 i = self.index
323 d = i[self.rev(node)]
323 d = i[self.rev(node)]
324 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
324 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
325 def parentrevs(self, rev):
325 def parentrevs(self, rev):
326 return self.index[rev][5:7]
326 return self.index[rev][5:7]
327 def start(self, rev):
327 def start(self, rev):
328 return int(self.index[rev][0] >> 16)
328 return int(self.index[rev][0] >> 16)
329 def end(self, rev):
329 def end(self, rev):
330 return self.start(rev) + self.length(rev)
330 return self.start(rev) + self.length(rev)
331 def length(self, rev):
331 def length(self, rev):
332 return self.index[rev][1]
332 return self.index[rev][1]
333 def chainbase(self, rev):
333 def chainbase(self, rev):
334 index = self.index
334 index = self.index
335 base = index[rev][3]
335 base = index[rev][3]
336 while base != rev:
336 while base != rev:
337 rev = base
337 rev = base
338 base = index[rev][3]
338 base = index[rev][3]
339 return base
339 return base
340 def flags(self, rev):
340 def flags(self, rev):
341 return self.index[rev][0] & 0xFFFF
341 return self.index[rev][0] & 0xFFFF
342 def rawsize(self, rev):
342 def rawsize(self, rev):
343 """return the length of the uncompressed text for a given revision"""
343 """return the length of the uncompressed text for a given revision"""
344 l = self.index[rev][2]
344 l = self.index[rev][2]
345 if l >= 0:
345 if l >= 0:
346 return l
346 return l
347
347
348 t = self.revision(self.node(rev))
348 t = self.revision(self.node(rev))
349 return len(t)
349 return len(t)
350 size = rawsize
350 size = rawsize
351
351
352 def ancestors(self, revs, stoprev=0, inclusive=False):
352 def ancestors(self, revs, stoprev=0, inclusive=False):
353 """Generate the ancestors of 'revs' in reverse topological order.
353 """Generate the ancestors of 'revs' in reverse topological order.
354 Does not generate revs lower than stoprev.
354 Does not generate revs lower than stoprev.
355
355
356 See the documentation for ancestor.lazyancestors for more details."""
356 See the documentation for ancestor.lazyancestors for more details."""
357
357
358 return ancestor.lazyancestors(self, revs, stoprev=stoprev,
358 return ancestor.lazyancestors(self, revs, stoprev=stoprev,
359 inclusive=inclusive)
359 inclusive=inclusive)
360
360
361 def descendants(self, revs):
361 def descendants(self, revs):
362 """Generate the descendants of 'revs' in revision order.
362 """Generate the descendants of 'revs' in revision order.
363
363
364 Yield a sequence of revision numbers starting with a child of
364 Yield a sequence of revision numbers starting with a child of
365 some rev in revs, i.e., each revision is *not* considered a
365 some rev in revs, i.e., each revision is *not* considered a
366 descendant of itself. Results are ordered by revision number (a
366 descendant of itself. Results are ordered by revision number (a
367 topological sort)."""
367 topological sort)."""
368 first = min(revs)
368 first = min(revs)
369 if first == nullrev:
369 if first == nullrev:
370 for i in self:
370 for i in self:
371 yield i
371 yield i
372 return
372 return
373
373
374 seen = set(revs)
374 seen = set(revs)
375 for i in self.revs(start=first + 1):
375 for i in self.revs(start=first + 1):
376 for x in self.parentrevs(i):
376 for x in self.parentrevs(i):
377 if x != nullrev and x in seen:
377 if x != nullrev and x in seen:
378 seen.add(i)
378 seen.add(i)
379 yield i
379 yield i
380 break
380 break
381
381
382 def findcommonmissing(self, common=None, heads=None):
382 def findcommonmissing(self, common=None, heads=None):
383 """Return a tuple of the ancestors of common and the ancestors of heads
383 """Return a tuple of the ancestors of common and the ancestors of heads
384 that are not ancestors of common. In revset terminology, we return the
384 that are not ancestors of common. In revset terminology, we return the
385 tuple:
385 tuple:
386
386
387 ::common, (::heads) - (::common)
387 ::common, (::heads) - (::common)
388
388
389 The list is sorted by revision number, meaning it is
389 The list is sorted by revision number, meaning it is
390 topologically sorted.
390 topologically sorted.
391
391
392 'heads' and 'common' are both lists of node IDs. If heads is
392 'heads' and 'common' are both lists of node IDs. If heads is
393 not supplied, uses all of the revlog's heads. If common is not
393 not supplied, uses all of the revlog's heads. If common is not
394 supplied, uses nullid."""
394 supplied, uses nullid."""
395 if common is None:
395 if common is None:
396 common = [nullid]
396 common = [nullid]
397 if heads is None:
397 if heads is None:
398 heads = self.heads()
398 heads = self.heads()
399
399
400 common = [self.rev(n) for n in common]
400 common = [self.rev(n) for n in common]
401 heads = [self.rev(n) for n in heads]
401 heads = [self.rev(n) for n in heads]
402
402
403 # we want the ancestors, but inclusive
403 # we want the ancestors, but inclusive
404 has = set(self.ancestors(common))
404 has = set(self.ancestors(common))
405 has.add(nullrev)
405 has.add(nullrev)
406 has.update(common)
406 has.update(common)
407
407
408 # take all ancestors from heads that aren't in has
408 # take all ancestors from heads that aren't in has
409 missing = set()
409 missing = set()
410 visit = util.deque(r for r in heads if r not in has)
410 visit = util.deque(r for r in heads if r not in has)
411 while visit:
411 while visit:
412 r = visit.popleft()
412 r = visit.popleft()
413 if r in missing:
413 if r in missing:
414 continue
414 continue
415 else:
415 else:
416 missing.add(r)
416 missing.add(r)
417 for p in self.parentrevs(r):
417 for p in self.parentrevs(r):
418 if p not in has:
418 if p not in has:
419 visit.append(p)
419 visit.append(p)
420 missing = list(missing)
420 missing = list(missing)
421 missing.sort()
421 missing.sort()
422 return has, [self.node(r) for r in missing]
422 return has, [self.node(r) for r in missing]
423
423
424 def findmissingrevs(self, common=None, heads=None):
424 def findmissingrevs(self, common=None, heads=None):
425 """Return the revision numbers of the ancestors of heads that
425 """Return the revision numbers of the ancestors of heads that
426 are not ancestors of common.
426 are not ancestors of common.
427
427
428 More specifically, return a list of revision numbers corresponding to
428 More specifically, return a list of revision numbers corresponding to
429 nodes N such that every N satisfies the following constraints:
429 nodes N such that every N satisfies the following constraints:
430
430
431 1. N is an ancestor of some node in 'heads'
431 1. N is an ancestor of some node in 'heads'
432 2. N is not an ancestor of any node in 'common'
432 2. N is not an ancestor of any node in 'common'
433
433
434 The list is sorted by revision number, meaning it is
434 The list is sorted by revision number, meaning it is
435 topologically sorted.
435 topologically sorted.
436
436
437 'heads' and 'common' are both lists of revision numbers. If heads is
437 'heads' and 'common' are both lists of revision numbers. If heads is
438 not supplied, uses all of the revlog's heads. If common is not
438 not supplied, uses all of the revlog's heads. If common is not
439 supplied, uses nullid."""
439 supplied, uses nullid."""
440 if common is None:
440 if common is None:
441 common = [nullrev]
441 common = [nullrev]
442 if heads is None:
442 if heads is None:
443 heads = self.headrevs()
443 heads = self.headrevs()
444
444
445 return ancestor.missingancestors(heads, common, self.parentrevs)
445 return ancestor.missingancestors(heads, common, self.parentrevs)
446
446
447 def findmissing(self, common=None, heads=None):
447 def findmissing(self, common=None, heads=None):
448 """Return the ancestors of heads that are not ancestors of common.
448 """Return the ancestors of heads that are not ancestors of common.
449
449
450 More specifically, return a list of nodes N such that every N
450 More specifically, return a list of nodes N such that every N
451 satisfies the following constraints:
451 satisfies the following constraints:
452
452
453 1. N is an ancestor of some node in 'heads'
453 1. N is an ancestor of some node in 'heads'
454 2. N is not an ancestor of any node in 'common'
454 2. N is not an ancestor of any node in 'common'
455
455
456 The list is sorted by revision number, meaning it is
456 The list is sorted by revision number, meaning it is
457 topologically sorted.
457 topologically sorted.
458
458
459 'heads' and 'common' are both lists of node IDs. If heads is
459 'heads' and 'common' are both lists of node IDs. If heads is
460 not supplied, uses all of the revlog's heads. If common is not
460 not supplied, uses all of the revlog's heads. If common is not
461 supplied, uses nullid."""
461 supplied, uses nullid."""
462 if common is None:
462 if common is None:
463 common = [nullid]
463 common = [nullid]
464 if heads is None:
464 if heads is None:
465 heads = self.heads()
465 heads = self.heads()
466
466
467 common = [self.rev(n) for n in common]
467 common = [self.rev(n) for n in common]
468 heads = [self.rev(n) for n in heads]
468 heads = [self.rev(n) for n in heads]
469
469
470 return [self.node(r) for r in
470 return [self.node(r) for r in
471 ancestor.missingancestors(heads, common, self.parentrevs)]
471 ancestor.missingancestors(heads, common, self.parentrevs)]
472
472
473 def nodesbetween(self, roots=None, heads=None):
473 def nodesbetween(self, roots=None, heads=None):
474 """Return a topological path from 'roots' to 'heads'.
474 """Return a topological path from 'roots' to 'heads'.
475
475
476 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
476 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
477 topologically sorted list of all nodes N that satisfy both of
477 topologically sorted list of all nodes N that satisfy both of
478 these constraints:
478 these constraints:
479
479
480 1. N is a descendant of some node in 'roots'
480 1. N is a descendant of some node in 'roots'
481 2. N is an ancestor of some node in 'heads'
481 2. N is an ancestor of some node in 'heads'
482
482
483 Every node is considered to be both a descendant and an ancestor
483 Every node is considered to be both a descendant and an ancestor
484 of itself, so every reachable node in 'roots' and 'heads' will be
484 of itself, so every reachable node in 'roots' and 'heads' will be
485 included in 'nodes'.
485 included in 'nodes'.
486
486
487 'outroots' is the list of reachable nodes in 'roots', i.e., the
487 'outroots' is the list of reachable nodes in 'roots', i.e., the
488 subset of 'roots' that is returned in 'nodes'. Likewise,
488 subset of 'roots' that is returned in 'nodes'. Likewise,
489 'outheads' is the subset of 'heads' that is also in 'nodes'.
489 'outheads' is the subset of 'heads' that is also in 'nodes'.
490
490
491 'roots' and 'heads' are both lists of node IDs. If 'roots' is
491 'roots' and 'heads' are both lists of node IDs. If 'roots' is
492 unspecified, uses nullid as the only root. If 'heads' is
492 unspecified, uses nullid as the only root. If 'heads' is
493 unspecified, uses list of all of the revlog's heads."""
493 unspecified, uses list of all of the revlog's heads."""
494 nonodes = ([], [], [])
494 nonodes = ([], [], [])
495 if roots is not None:
495 if roots is not None:
496 roots = list(roots)
496 roots = list(roots)
497 if not roots:
497 if not roots:
498 return nonodes
498 return nonodes
499 lowestrev = min([self.rev(n) for n in roots])
499 lowestrev = min([self.rev(n) for n in roots])
500 else:
500 else:
501 roots = [nullid] # Everybody's a descendant of nullid
501 roots = [nullid] # Everybody's a descendant of nullid
502 lowestrev = nullrev
502 lowestrev = nullrev
503 if (lowestrev == nullrev) and (heads is None):
503 if (lowestrev == nullrev) and (heads is None):
504 # We want _all_ the nodes!
504 # We want _all_ the nodes!
505 return ([self.node(r) for r in self], [nullid], list(self.heads()))
505 return ([self.node(r) for r in self], [nullid], list(self.heads()))
506 if heads is None:
506 if heads is None:
507 # All nodes are ancestors, so the latest ancestor is the last
507 # All nodes are ancestors, so the latest ancestor is the last
508 # node.
508 # node.
509 highestrev = len(self) - 1
509 highestrev = len(self) - 1
510 # Set ancestors to None to signal that every node is an ancestor.
510 # Set ancestors to None to signal that every node is an ancestor.
511 ancestors = None
511 ancestors = None
512 # Set heads to an empty dictionary for later discovery of heads
512 # Set heads to an empty dictionary for later discovery of heads
513 heads = {}
513 heads = {}
514 else:
514 else:
515 heads = list(heads)
515 heads = list(heads)
516 if not heads:
516 if not heads:
517 return nonodes
517 return nonodes
518 ancestors = set()
518 ancestors = set()
519 # Turn heads into a dictionary so we can remove 'fake' heads.
519 # Turn heads into a dictionary so we can remove 'fake' heads.
520 # Also, later we will be using it to filter out the heads we can't
520 # Also, later we will be using it to filter out the heads we can't
521 # find from roots.
521 # find from roots.
522 heads = dict.fromkeys(heads, False)
522 heads = dict.fromkeys(heads, False)
523 # Start at the top and keep marking parents until we're done.
523 # Start at the top and keep marking parents until we're done.
524 nodestotag = set(heads)
524 nodestotag = set(heads)
525 # Remember where the top was so we can use it as a limit later.
525 # Remember where the top was so we can use it as a limit later.
526 highestrev = max([self.rev(n) for n in nodestotag])
526 highestrev = max([self.rev(n) for n in nodestotag])
527 while nodestotag:
527 while nodestotag:
528 # grab a node to tag
528 # grab a node to tag
529 n = nodestotag.pop()
529 n = nodestotag.pop()
530 # Never tag nullid
530 # Never tag nullid
531 if n == nullid:
531 if n == nullid:
532 continue
532 continue
533 # A node's revision number represents its place in a
533 # A node's revision number represents its place in a
534 # topologically sorted list of nodes.
534 # topologically sorted list of nodes.
535 r = self.rev(n)
535 r = self.rev(n)
536 if r >= lowestrev:
536 if r >= lowestrev:
537 if n not in ancestors:
537 if n not in ancestors:
538 # If we are possibly a descendant of one of the roots
538 # If we are possibly a descendant of one of the roots
539 # and we haven't already been marked as an ancestor
539 # and we haven't already been marked as an ancestor
540 ancestors.add(n) # Mark as ancestor
540 ancestors.add(n) # Mark as ancestor
541 # Add non-nullid parents to list of nodes to tag.
541 # Add non-nullid parents to list of nodes to tag.
542 nodestotag.update([p for p in self.parents(n) if
542 nodestotag.update([p for p in self.parents(n) if
543 p != nullid])
543 p != nullid])
544 elif n in heads: # We've seen it before, is it a fake head?
544 elif n in heads: # We've seen it before, is it a fake head?
545 # So it is, real heads should not be the ancestors of
545 # So it is, real heads should not be the ancestors of
546 # any other heads.
546 # any other heads.
547 heads.pop(n)
547 heads.pop(n)
548 if not ancestors:
548 if not ancestors:
549 return nonodes
549 return nonodes
550 # Now that we have our set of ancestors, we want to remove any
550 # Now that we have our set of ancestors, we want to remove any
551 # roots that are not ancestors.
551 # roots that are not ancestors.
552
552
553 # If one of the roots was nullid, everything is included anyway.
553 # If one of the roots was nullid, everything is included anyway.
554 if lowestrev > nullrev:
554 if lowestrev > nullrev:
555 # But, since we weren't, let's recompute the lowest rev to not
555 # But, since we weren't, let's recompute the lowest rev to not
556 # include roots that aren't ancestors.
556 # include roots that aren't ancestors.
557
557
558 # Filter out roots that aren't ancestors of heads
558 # Filter out roots that aren't ancestors of heads
559 roots = [n for n in roots if n in ancestors]
559 roots = [n for n in roots if n in ancestors]
560 # Recompute the lowest revision
560 # Recompute the lowest revision
561 if roots:
561 if roots:
562 lowestrev = min([self.rev(n) for n in roots])
562 lowestrev = min([self.rev(n) for n in roots])
563 else:
563 else:
564 # No more roots? Return empty list
564 # No more roots? Return empty list
565 return nonodes
565 return nonodes
566 else:
566 else:
567 # We are descending from nullid, and don't need to care about
567 # We are descending from nullid, and don't need to care about
568 # any other roots.
568 # any other roots.
569 lowestrev = nullrev
569 lowestrev = nullrev
570 roots = [nullid]
570 roots = [nullid]
571 # Transform our roots list into a set.
571 # Transform our roots list into a set.
572 descendants = set(roots)
572 descendants = set(roots)
573 # Also, keep the original roots so we can filter out roots that aren't
573 # Also, keep the original roots so we can filter out roots that aren't
574 # 'real' roots (i.e. are descended from other roots).
574 # 'real' roots (i.e. are descended from other roots).
575 roots = descendants.copy()
575 roots = descendants.copy()
576 # Our topologically sorted list of output nodes.
576 # Our topologically sorted list of output nodes.
577 orderedout = []
577 orderedout = []
578 # Don't start at nullid since we don't want nullid in our output list,
578 # Don't start at nullid since we don't want nullid in our output list,
579 # and if nullid shows up in descendants, empty parents will look like
579 # and if nullid shows up in descendants, empty parents will look like
580 # they're descendants.
580 # they're descendants.
581 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
581 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
582 n = self.node(r)
582 n = self.node(r)
583 isdescendant = False
583 isdescendant = False
584 if lowestrev == nullrev: # Everybody is a descendant of nullid
584 if lowestrev == nullrev: # Everybody is a descendant of nullid
585 isdescendant = True
585 isdescendant = True
586 elif n in descendants:
586 elif n in descendants:
587 # n is already a descendant
587 # n is already a descendant
588 isdescendant = True
588 isdescendant = True
589 # This check only needs to be done here because all the roots
589 # This check only needs to be done here because all the roots
590 # will start being marked is descendants before the loop.
590 # will start being marked is descendants before the loop.
591 if n in roots:
591 if n in roots:
592 # If n was a root, check if it's a 'real' root.
592 # If n was a root, check if it's a 'real' root.
593 p = tuple(self.parents(n))
593 p = tuple(self.parents(n))
594 # If any of its parents are descendants, it's not a root.
594 # If any of its parents are descendants, it's not a root.
595 if (p[0] in descendants) or (p[1] in descendants):
595 if (p[0] in descendants) or (p[1] in descendants):
596 roots.remove(n)
596 roots.remove(n)
597 else:
597 else:
598 p = tuple(self.parents(n))
598 p = tuple(self.parents(n))
599 # A node is a descendant if either of its parents are
599 # A node is a descendant if either of its parents are
600 # descendants. (We seeded the dependents list with the roots
600 # descendants. (We seeded the dependents list with the roots
601 # up there, remember?)
601 # up there, remember?)
602 if (p[0] in descendants) or (p[1] in descendants):
602 if (p[0] in descendants) or (p[1] in descendants):
603 descendants.add(n)
603 descendants.add(n)
604 isdescendant = True
604 isdescendant = True
605 if isdescendant and ((ancestors is None) or (n in ancestors)):
605 if isdescendant and ((ancestors is None) or (n in ancestors)):
606 # Only include nodes that are both descendants and ancestors.
606 # Only include nodes that are both descendants and ancestors.
607 orderedout.append(n)
607 orderedout.append(n)
608 if (ancestors is not None) and (n in heads):
608 if (ancestors is not None) and (n in heads):
609 # We're trying to figure out which heads are reachable
609 # We're trying to figure out which heads are reachable
610 # from roots.
610 # from roots.
611 # Mark this head as having been reached
611 # Mark this head as having been reached
612 heads[n] = True
612 heads[n] = True
613 elif ancestors is None:
613 elif ancestors is None:
614 # Otherwise, we're trying to discover the heads.
614 # Otherwise, we're trying to discover the heads.
615 # Assume this is a head because if it isn't, the next step
615 # Assume this is a head because if it isn't, the next step
616 # will eventually remove it.
616 # will eventually remove it.
617 heads[n] = True
617 heads[n] = True
618 # But, obviously its parents aren't.
618 # But, obviously its parents aren't.
619 for p in self.parents(n):
619 for p in self.parents(n):
620 heads.pop(p, None)
620 heads.pop(p, None)
621 heads = [n for n, flag in heads.iteritems() if flag]
621 heads = [n for n, flag in heads.iteritems() if flag]
622 roots = list(roots)
622 roots = list(roots)
623 assert orderedout
623 assert orderedout
624 assert roots
624 assert roots
625 assert heads
625 assert heads
626 return (orderedout, roots, heads)
626 return (orderedout, roots, heads)
627
627
628 def headrevs(self):
628 def headrevs(self):
629 try:
629 try:
630 return self.index.headrevs()
630 return self.index.headrevs()
631 except AttributeError:
631 except AttributeError:
632 return self._headrevs()
632 return self._headrevs()
633
633
634 def _headrevs(self):
634 def _headrevs(self):
635 count = len(self)
635 count = len(self)
636 if not count:
636 if not count:
637 return [nullrev]
637 return [nullrev]
638 # we won't iter over filtered rev so nobody is a head at start
638 # we won't iter over filtered rev so nobody is a head at start
639 ishead = [0] * (count + 1)
639 ishead = [0] * (count + 1)
640 index = self.index
640 index = self.index
641 for r in self:
641 for r in self:
642 ishead[r] = 1 # I may be an head
642 ishead[r] = 1 # I may be an head
643 e = index[r]
643 e = index[r]
644 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
644 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
645 return [r for r, val in enumerate(ishead) if val]
645 return [r for r, val in enumerate(ishead) if val]
646
646
647 def heads(self, start=None, stop=None):
647 def heads(self, start=None, stop=None):
648 """return the list of all nodes that have no children
648 """return the list of all nodes that have no children
649
649
650 if start is specified, only heads that are descendants of
650 if start is specified, only heads that are descendants of
651 start will be returned
651 start will be returned
652 if stop is specified, it will consider all the revs from stop
652 if stop is specified, it will consider all the revs from stop
653 as if they had no children
653 as if they had no children
654 """
654 """
655 if start is None and stop is None:
655 if start is None and stop is None:
656 if not len(self):
656 if not len(self):
657 return [nullid]
657 return [nullid]
658 return [self.node(r) for r in self.headrevs()]
658 return [self.node(r) for r in self.headrevs()]
659
659
660 if start is None:
660 if start is None:
661 start = nullid
661 start = nullid
662 if stop is None:
662 if stop is None:
663 stop = []
663 stop = []
664 stoprevs = set([self.rev(n) for n in stop])
664 stoprevs = set([self.rev(n) for n in stop])
665 startrev = self.rev(start)
665 startrev = self.rev(start)
666 reachable = set((startrev,))
666 reachable = set((startrev,))
667 heads = set((startrev,))
667 heads = set((startrev,))
668
668
669 parentrevs = self.parentrevs
669 parentrevs = self.parentrevs
670 for r in self.revs(start=startrev + 1):
670 for r in self.revs(start=startrev + 1):
671 for p in parentrevs(r):
671 for p in parentrevs(r):
672 if p in reachable:
672 if p in reachable:
673 if r not in stoprevs:
673 if r not in stoprevs:
674 reachable.add(r)
674 reachable.add(r)
675 heads.add(r)
675 heads.add(r)
676 if p in heads and p not in stoprevs:
676 if p in heads and p not in stoprevs:
677 heads.remove(p)
677 heads.remove(p)
678
678
679 return [self.node(r) for r in heads]
679 return [self.node(r) for r in heads]
680
680
681 def children(self, node):
681 def children(self, node):
682 """find the children of a given node"""
682 """find the children of a given node"""
683 c = []
683 c = []
684 p = self.rev(node)
684 p = self.rev(node)
685 for r in self.revs(start=p + 1):
685 for r in self.revs(start=p + 1):
686 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
686 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
687 if prevs:
687 if prevs:
688 for pr in prevs:
688 for pr in prevs:
689 if pr == p:
689 if pr == p:
690 c.append(self.node(r))
690 c.append(self.node(r))
691 elif p == nullrev:
691 elif p == nullrev:
692 c.append(self.node(r))
692 c.append(self.node(r))
693 return c
693 return c
694
694
695 def descendant(self, start, end):
695 def descendant(self, start, end):
696 if start == nullrev:
696 if start == nullrev:
697 return True
697 return True
698 for i in self.descendants([start]):
698 for i in self.descendants([start]):
699 if i == end:
699 if i == end:
700 return True
700 return True
701 elif i > end:
701 elif i > end:
702 break
702 break
703 return False
703 return False
704
704
705 def ancestor(self, a, b):
705 def ancestor(self, a, b):
706 """calculate the least common ancestor of nodes a and b"""
706 """calculate the least common ancestor of nodes a and b"""
707
707
708 a, b = self.rev(a), self.rev(b)
708 a, b = self.rev(a), self.rev(b)
709 try:
709 try:
710 ancs = self.index.ancestors(a, b)
710 ancs = self.index.ancestors(a, b)
711 except (AttributeError, OverflowError):
711 except (AttributeError, OverflowError):
712 ancs = ancestor.ancestors(self.parentrevs, a, b)
712 ancs = ancestor.ancestors(self.parentrevs, a, b)
713 if ancs:
713 if ancs:
714 # choose a consistent winner when there's a tie
714 # choose a consistent winner when there's a tie
715 return min(map(self.node, ancs))
715 return min(map(self.node, ancs))
716 return nullid
716 return nullid
717
717
718 def _match(self, id):
718 def _match(self, id):
719 if isinstance(id, int):
719 if isinstance(id, int):
720 # rev
720 # rev
721 return self.node(id)
721 return self.node(id)
722 if len(id) == 20:
722 if len(id) == 20:
723 # possibly a binary node
723 # possibly a binary node
724 # odds of a binary node being all hex in ASCII are 1 in 10**25
724 # odds of a binary node being all hex in ASCII are 1 in 10**25
725 try:
725 try:
726 node = id
726 node = id
727 self.rev(node) # quick search the index
727 self.rev(node) # quick search the index
728 return node
728 return node
729 except LookupError:
729 except LookupError:
730 pass # may be partial hex id
730 pass # may be partial hex id
731 try:
731 try:
732 # str(rev)
732 # str(rev)
733 rev = int(id)
733 rev = int(id)
734 if str(rev) != id:
734 if str(rev) != id:
735 raise ValueError
735 raise ValueError
736 if rev < 0:
736 if rev < 0:
737 rev = len(self) + rev
737 rev = len(self) + rev
738 if rev < 0 or rev >= len(self):
738 if rev < 0 or rev >= len(self):
739 raise ValueError
739 raise ValueError
740 return self.node(rev)
740 return self.node(rev)
741 except (ValueError, OverflowError):
741 except (ValueError, OverflowError):
742 pass
742 pass
743 if len(id) == 40:
743 if len(id) == 40:
744 try:
744 try:
745 # a full hex nodeid?
745 # a full hex nodeid?
746 node = bin(id)
746 node = bin(id)
747 self.rev(node)
747 self.rev(node)
748 return node
748 return node
749 except (TypeError, LookupError):
749 except (TypeError, LookupError):
750 pass
750 pass
751
751
752 def _partialmatch(self, id):
752 def _partialmatch(self, id):
753 try:
753 try:
754 return self.index.partialmatch(id)
754 return self.index.partialmatch(id)
755 except RevlogError:
755 except RevlogError:
756 # parsers.c radix tree lookup gave multiple matches
756 # parsers.c radix tree lookup gave multiple matches
757 raise LookupError(id, self.indexfile, _("ambiguous identifier"))
757 raise LookupError(id, self.indexfile, _("ambiguous identifier"))
758 except (AttributeError, ValueError):
758 except (AttributeError, ValueError):
759 # we are pure python, or key was too short to search radix tree
759 # we are pure python, or key was too short to search radix tree
760 pass
760 pass
761
761
762 if id in self._pcache:
762 if id in self._pcache:
763 return self._pcache[id]
763 return self._pcache[id]
764
764
765 if len(id) < 40:
765 if len(id) < 40:
766 try:
766 try:
767 # hex(node)[:...]
767 # hex(node)[:...]
768 l = len(id) // 2 # grab an even number of digits
768 l = len(id) // 2 # grab an even number of digits
769 prefix = bin(id[:l * 2])
769 prefix = bin(id[:l * 2])
770 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
770 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
771 nl = [n for n in nl if hex(n).startswith(id)]
771 nl = [n for n in nl if hex(n).startswith(id)]
772 if len(nl) > 0:
772 if len(nl) > 0:
773 if len(nl) == 1:
773 if len(nl) == 1:
774 self._pcache[id] = nl[0]
774 self._pcache[id] = nl[0]
775 return nl[0]
775 return nl[0]
776 raise LookupError(id, self.indexfile,
776 raise LookupError(id, self.indexfile,
777 _('ambiguous identifier'))
777 _('ambiguous identifier'))
778 return None
778 return None
779 except TypeError:
779 except TypeError:
780 pass
780 pass
781
781
782 def lookup(self, id):
782 def lookup(self, id):
783 """locate a node based on:
783 """locate a node based on:
784 - revision number or str(revision number)
784 - revision number or str(revision number)
785 - nodeid or subset of hex nodeid
785 - nodeid or subset of hex nodeid
786 """
786 """
787 n = self._match(id)
787 n = self._match(id)
788 if n is not None:
788 if n is not None:
789 return n
789 return n
790 n = self._partialmatch(id)
790 n = self._partialmatch(id)
791 if n:
791 if n:
792 return n
792 return n
793
793
794 raise LookupError(id, self.indexfile, _('no match found'))
794 raise LookupError(id, self.indexfile, _('no match found'))
795
795
796 def cmp(self, node, text):
796 def cmp(self, node, text):
797 """compare text with a given file revision
797 """compare text with a given file revision
798
798
799 returns True if text is different than what is stored.
799 returns True if text is different than what is stored.
800 """
800 """
801 p1, p2 = self.parents(node)
801 p1, p2 = self.parents(node)
802 return hash(text, p1, p2) != node
802 return hash(text, p1, p2) != node
803
803
804 def _addchunk(self, offset, data):
804 def _addchunk(self, offset, data):
805 o, d = self._chunkcache
805 o, d = self._chunkcache
806 # try to add to existing cache
806 # try to add to existing cache
807 if o + len(d) == offset and len(d) + len(data) < _chunksize:
807 if o + len(d) == offset and len(d) + len(data) < _chunksize:
808 self._chunkcache = o, d + data
808 self._chunkcache = o, d + data
809 else:
809 else:
810 self._chunkcache = offset, data
810 self._chunkcache = offset, data
811
811
812 def _loadchunk(self, offset, length):
812 def _loadchunk(self, offset, length):
813 if self._inline:
813 if self._inline:
814 df = self.opener(self.indexfile)
814 df = self.opener(self.indexfile)
815 else:
815 else:
816 df = self.opener(self.datafile)
816 df = self.opener(self.datafile)
817
817
818 readahead = max(65536, length)
818 readahead = max(65536, length)
819 df.seek(offset)
819 df.seek(offset)
820 d = df.read(readahead)
820 d = df.read(readahead)
821 df.close()
821 df.close()
822 self._addchunk(offset, d)
822 self._addchunk(offset, d)
823 if readahead > length:
823 if readahead > length:
824 return util.buffer(d, 0, length)
824 return util.buffer(d, 0, length)
825 return d
825 return d
826
826
827 def _getchunk(self, offset, length):
827 def _getchunk(self, offset, length):
828 o, d = self._chunkcache
828 o, d = self._chunkcache
829 l = len(d)
829 l = len(d)
830
830
831 # is it in the cache?
831 # is it in the cache?
832 cachestart = offset - o
832 cachestart = offset - o
833 cacheend = cachestart + length
833 cacheend = cachestart + length
834 if cachestart >= 0 and cacheend <= l:
834 if cachestart >= 0 and cacheend <= l:
835 if cachestart == 0 and cacheend == l:
835 if cachestart == 0 and cacheend == l:
836 return d # avoid a copy
836 return d # avoid a copy
837 return util.buffer(d, cachestart, cacheend - cachestart)
837 return util.buffer(d, cachestart, cacheend - cachestart)
838
838
839 return self._loadchunk(offset, length)
839 return self._loadchunk(offset, length)
840
840
841 def _chunkraw(self, startrev, endrev):
841 def _chunkraw(self, startrev, endrev):
842 start = self.start(startrev)
842 start = self.start(startrev)
843 length = self.end(endrev) - start
843 length = self.end(endrev) - start
844 if self._inline:
844 if self._inline:
845 start += (startrev + 1) * self._io.size
845 start += (startrev + 1) * self._io.size
846 return self._getchunk(start, length)
846 return self._getchunk(start, length)
847
847
848 def _chunk(self, rev):
848 def _chunk(self, rev):
849 return decompress(self._chunkraw(rev, rev))
849 return decompress(self._chunkraw(rev, rev))
850
850
851 def _chunkbase(self, rev):
851 def _chunkbase(self, rev):
852 return self._chunk(rev)
852 return self._chunk(rev)
853
853
854 def _chunkclear(self):
854 def _chunkclear(self):
855 self._chunkcache = (0, '')
855 self._chunkcache = (0, '')
856
856
857 def deltaparent(self, rev):
857 def deltaparent(self, rev):
858 """return deltaparent of the given revision"""
858 """return deltaparent of the given revision"""
859 base = self.index[rev][3]
859 base = self.index[rev][3]
860 if base == rev:
860 if base == rev:
861 return nullrev
861 return nullrev
862 elif self._generaldelta:
862 elif self._generaldelta:
863 return base
863 return base
864 else:
864 else:
865 return rev - 1
865 return rev - 1
866
866
867 def revdiff(self, rev1, rev2):
867 def revdiff(self, rev1, rev2):
868 """return or calculate a delta between two revisions"""
868 """return or calculate a delta between two revisions"""
869 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
869 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
870 return str(self._chunk(rev2))
870 return str(self._chunk(rev2))
871
871
872 return mdiff.textdiff(self.revision(rev1),
872 return mdiff.textdiff(self.revision(rev1),
873 self.revision(rev2))
873 self.revision(rev2))
874
874
875 def revision(self, nodeorrev):
875 def revision(self, nodeorrev):
876 """return an uncompressed revision of a given node or revision
876 """return an uncompressed revision of a given node or revision
877 number.
877 number.
878 """
878 """
879 if isinstance(nodeorrev, int):
879 if isinstance(nodeorrev, int):
880 rev = nodeorrev
880 rev = nodeorrev
881 node = self.node(rev)
881 node = self.node(rev)
882 else:
882 else:
883 node = nodeorrev
883 node = nodeorrev
884 rev = None
884 rev = None
885
885
886 cachedrev = None
886 cachedrev = None
887 if node == nullid:
887 if node == nullid:
888 return ""
888 return ""
889 if self._cache:
889 if self._cache:
890 if self._cache[0] == node:
890 if self._cache[0] == node:
891 return self._cache[2]
891 return self._cache[2]
892 cachedrev = self._cache[1]
892 cachedrev = self._cache[1]
893
893
894 # look up what we need to read
894 # look up what we need to read
895 text = None
895 text = None
896 if rev is None:
896 if rev is None:
897 rev = self.rev(node)
897 rev = self.rev(node)
898
898
899 # check rev flags
899 # check rev flags
900 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
900 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
901 raise RevlogError(_('incompatible revision flag %x') %
901 raise RevlogError(_('incompatible revision flag %x') %
902 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
902 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
903
903
904 # build delta chain
904 # build delta chain
905 chain = []
905 chain = []
906 index = self.index # for performance
906 index = self.index # for performance
907 generaldelta = self._generaldelta
907 generaldelta = self._generaldelta
908 iterrev = rev
908 iterrev = rev
909 e = index[iterrev]
909 e = index[iterrev]
910 while iterrev != e[3] and iterrev != cachedrev:
910 while iterrev != e[3] and iterrev != cachedrev:
911 chain.append(iterrev)
911 chain.append(iterrev)
912 if generaldelta:
912 if generaldelta:
913 iterrev = e[3]
913 iterrev = e[3]
914 else:
914 else:
915 iterrev -= 1
915 iterrev -= 1
916 e = index[iterrev]
916 e = index[iterrev]
917 chain.reverse()
917 chain.reverse()
918 base = iterrev
918 base = iterrev
919
919
920 if iterrev == cachedrev:
920 if iterrev == cachedrev:
921 # cache hit
921 # cache hit
922 text = self._cache[2]
922 text = self._cache[2]
923
923
924 # drop cache to save memory
924 # drop cache to save memory
925 self._cache = None
925 self._cache = None
926
926
927 self._chunkraw(base, rev)
927 self._chunkraw(base, rev)
928 if text is None:
928 if text is None:
929 text = str(self._chunkbase(base))
929 text = str(self._chunkbase(base))
930
930
931 bins = [self._chunk(r) for r in chain]
931 bins = [self._chunk(r) for r in chain]
932 text = mdiff.patches(text, bins)
932 text = mdiff.patches(text, bins)
933
933
934 text = self._checkhash(text, node, rev)
934 text = self._checkhash(text, node, rev)
935
935
936 self._cache = (node, rev, text)
936 self._cache = (node, rev, text)
937 return text
937 return text
938
938
939 def _checkhash(self, text, node, rev):
939 def _checkhash(self, text, node, rev):
940 p1, p2 = self.parents(node)
940 p1, p2 = self.parents(node)
941 if node != hash(text, p1, p2):
941 if node != hash(text, p1, p2):
942 raise RevlogError(_("integrity check failed on %s:%d")
942 raise RevlogError(_("integrity check failed on %s:%d")
943 % (self.indexfile, rev))
943 % (self.indexfile, rev))
944 return text
944 return text
945
945
946 def checkinlinesize(self, tr, fp=None):
946 def checkinlinesize(self, tr, fp=None):
947 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
947 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
948 return
948 return
949
949
950 trinfo = tr.find(self.indexfile)
950 trinfo = tr.find(self.indexfile)
951 if trinfo is None:
951 if trinfo is None:
952 raise RevlogError(_("%s not found in the transaction")
952 raise RevlogError(_("%s not found in the transaction")
953 % self.indexfile)
953 % self.indexfile)
954
954
955 trindex = trinfo[2]
955 trindex = trinfo[2]
956 dataoff = self.start(trindex)
956 dataoff = self.start(trindex)
957
957
958 tr.add(self.datafile, dataoff)
958 tr.add(self.datafile, dataoff)
959
959
960 if fp:
960 if fp:
961 fp.flush()
961 fp.flush()
962 fp.close()
962 fp.close()
963
963
964 df = self.opener(self.datafile, 'w')
964 df = self.opener(self.datafile, 'w')
965 try:
965 try:
966 for r in self:
966 for r in self:
967 df.write(self._chunkraw(r, r))
967 df.write(self._chunkraw(r, r))
968 finally:
968 finally:
969 df.close()
969 df.close()
970
970
971 fp = self.opener(self.indexfile, 'w', atomictemp=True)
971 fp = self.opener(self.indexfile, 'w', atomictemp=True)
972 self.version &= ~(REVLOGNGINLINEDATA)
972 self.version &= ~(REVLOGNGINLINEDATA)
973 self._inline = False
973 self._inline = False
974 for i in self:
974 for i in self:
975 e = self._io.packentry(self.index[i], self.node, self.version, i)
975 e = self._io.packentry(self.index[i], self.node, self.version, i)
976 fp.write(e)
976 fp.write(e)
977
977
978 # if we don't call close, the temp file will never replace the
978 # if we don't call close, the temp file will never replace the
979 # real index
979 # real index
980 fp.close()
980 fp.close()
981
981
982 tr.replace(self.indexfile, trindex * self._io.size)
982 tr.replace(self.indexfile, trindex * self._io.size)
983 self._chunkclear()
983 self._chunkclear()
984
984
985 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None):
985 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None):
986 """add a revision to the log
986 """add a revision to the log
987
987
988 text - the revision data to add
988 text - the revision data to add
989 transaction - the transaction object used for rollback
989 transaction - the transaction object used for rollback
990 link - the linkrev data to add
990 link - the linkrev data to add
991 p1, p2 - the parent nodeids of the revision
991 p1, p2 - the parent nodeids of the revision
992 cachedelta - an optional precomputed delta
992 cachedelta - an optional precomputed delta
993 """
993 """
994 node = hash(text, p1, p2)
994 node = hash(text, p1, p2)
995 if node in self.nodemap:
995 if node in self.nodemap:
996 return node
996 return node
997
997
998 dfh = None
998 dfh = None
999 if not self._inline:
999 if not self._inline:
1000 dfh = self.opener(self.datafile, "a")
1000 dfh = self.opener(self.datafile, "a")
1001 ifh = self.opener(self.indexfile, "a+")
1001 ifh = self.opener(self.indexfile, "a+")
1002 try:
1002 try:
1003 return self._addrevision(node, text, transaction, link, p1, p2,
1003 return self._addrevision(node, text, transaction, link, p1, p2,
1004 cachedelta, ifh, dfh)
1004 cachedelta, ifh, dfh)
1005 finally:
1005 finally:
1006 if dfh:
1006 if dfh:
1007 dfh.close()
1007 dfh.close()
1008 ifh.close()
1008 ifh.close()
1009
1009
1010 def compress(self, text):
1010 def compress(self, text):
1011 """ generate a possibly-compressed representation of text """
1011 """ generate a possibly-compressed representation of text """
1012 if not text:
1012 if not text:
1013 return ("", text)
1013 return ("", text)
1014 l = len(text)
1014 l = len(text)
1015 bin = None
1015 bin = None
1016 if l < 44:
1016 if l < 44:
1017 pass
1017 pass
1018 elif l > 1000000:
1018 elif l > 1000000:
1019 # zlib makes an internal copy, thus doubling memory usage for
1019 # zlib makes an internal copy, thus doubling memory usage for
1020 # large files, so lets do this in pieces
1020 # large files, so lets do this in pieces
1021 z = zlib.compressobj()
1021 z = zlib.compressobj()
1022 p = []
1022 p = []
1023 pos = 0
1023 pos = 0
1024 while pos < l:
1024 while pos < l:
1025 pos2 = pos + 2**20
1025 pos2 = pos + 2**20
1026 p.append(z.compress(text[pos:pos2]))
1026 p.append(z.compress(text[pos:pos2]))
1027 pos = pos2
1027 pos = pos2
1028 p.append(z.flush())
1028 p.append(z.flush())
1029 if sum(map(len, p)) < l:
1029 if sum(map(len, p)) < l:
1030 bin = "".join(p)
1030 bin = "".join(p)
1031 else:
1031 else:
1032 bin = _compress(text)
1032 bin = _compress(text)
1033 if bin is None or len(bin) > l:
1033 if bin is None or len(bin) > l:
1034 if text[0] == '\0':
1034 if text[0] == '\0':
1035 return ("", text)
1035 return ("", text)
1036 return ('u', text)
1036 return ('u', text)
1037 return ("", bin)
1037 return ("", bin)
1038
1038
1039 def _addrevision(self, node, text, transaction, link, p1, p2,
1039 def _addrevision(self, node, text, transaction, link, p1, p2,
1040 cachedelta, ifh, dfh):
1040 cachedelta, ifh, dfh):
1041 """internal function to add revisions to the log
1041 """internal function to add revisions to the log
1042
1042
1043 see addrevision for argument descriptions.
1043 see addrevision for argument descriptions.
1044 invariants:
1044 invariants:
1045 - text is optional (can be None); if not set, cachedelta must be set.
1045 - text is optional (can be None); if not set, cachedelta must be set.
1046 if both are set, they must correspond to each other.
1046 if both are set, they must correspond to each other.
1047 """
1047 """
1048 btext = [text]
1048 btext = [text]
1049 def buildtext():
1049 def buildtext():
1050 if btext[0] is not None:
1050 if btext[0] is not None:
1051 return btext[0]
1051 return btext[0]
1052 # flush any pending writes here so we can read it in revision
1052 # flush any pending writes here so we can read it in revision
1053 if dfh:
1053 if dfh:
1054 dfh.flush()
1054 dfh.flush()
1055 ifh.flush()
1055 ifh.flush()
1056 basetext = self.revision(self.node(cachedelta[0]))
1056 basetext = self.revision(self.node(cachedelta[0]))
1057 btext[0] = mdiff.patch(basetext, cachedelta[1])
1057 btext[0] = mdiff.patch(basetext, cachedelta[1])
1058 chk = hash(btext[0], p1, p2)
1058 chk = hash(btext[0], p1, p2)
1059 if chk != node:
1059 if chk != node:
1060 raise RevlogError(_("consistency error in delta"))
1060 raise RevlogError(_("consistency error in delta"))
1061 return btext[0]
1061 return btext[0]
1062
1062
1063 def builddelta(rev):
1063 def builddelta(rev):
1064 # can we use the cached delta?
1064 # can we use the cached delta?
1065 if cachedelta and cachedelta[0] == rev:
1065 if cachedelta and cachedelta[0] == rev:
1066 delta = cachedelta[1]
1066 delta = cachedelta[1]
1067 else:
1067 else:
1068 t = buildtext()
1068 t = buildtext()
1069 ptext = self.revision(self.node(rev))
1069 ptext = self.revision(self.node(rev))
1070 delta = mdiff.textdiff(ptext, t)
1070 delta = mdiff.textdiff(ptext, t)
1071 data = self.compress(delta)
1071 data = self.compress(delta)
1072 l = len(data[1]) + len(data[0])
1072 l = len(data[1]) + len(data[0])
1073 if basecache[0] == rev:
1073 if basecache[0] == rev:
1074 chainbase = basecache[1]
1074 chainbase = basecache[1]
1075 else:
1075 else:
1076 chainbase = self.chainbase(rev)
1076 chainbase = self.chainbase(rev)
1077 dist = l + offset - self.start(chainbase)
1077 dist = l + offset - self.start(chainbase)
1078 if self._generaldelta:
1078 if self._generaldelta:
1079 base = rev
1079 base = rev
1080 else:
1080 else:
1081 base = chainbase
1081 base = chainbase
1082 return dist, l, data, base, chainbase
1082 return dist, l, data, base, chainbase
1083
1083
1084 curr = len(self)
1084 curr = len(self)
1085 prev = curr - 1
1085 prev = curr - 1
1086 base = chainbase = curr
1086 base = chainbase = curr
1087 offset = self.end(prev)
1087 offset = self.end(prev)
1088 flags = 0
1088 flags = 0
1089 d = None
1089 d = None
1090 basecache = self._basecache
1090 basecache = self._basecache
1091 p1r, p2r = self.rev(p1), self.rev(p2)
1091 p1r, p2r = self.rev(p1), self.rev(p2)
1092
1092
1093 # should we try to build a delta?
1093 # should we try to build a delta?
1094 if prev != nullrev:
1094 if prev != nullrev:
1095 if self._generaldelta:
1095 if self._generaldelta:
1096 if p1r >= basecache[1]:
1096 if p1r >= basecache[1]:
1097 d = builddelta(p1r)
1097 d = builddelta(p1r)
1098 elif p2r >= basecache[1]:
1098 elif p2r >= basecache[1]:
1099 d = builddelta(p2r)
1099 d = builddelta(p2r)
1100 else:
1100 else:
1101 d = builddelta(prev)
1101 d = builddelta(prev)
1102 else:
1102 else:
1103 d = builddelta(prev)
1103 d = builddelta(prev)
1104 dist, l, data, base, chainbase = d
1104 dist, l, data, base, chainbase = d
1105
1105
1106 # full versions are inserted when the needed deltas
1106 # full versions are inserted when the needed deltas
1107 # become comparable to the uncompressed text
1107 # become comparable to the uncompressed text
1108 if text is None:
1108 if text is None:
1109 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1109 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1110 cachedelta[1])
1110 cachedelta[1])
1111 else:
1111 else:
1112 textlen = len(text)
1112 textlen = len(text)
1113 if d is None or dist > textlen * 2:
1113 if d is None or dist > textlen * 2:
1114 text = buildtext()
1114 text = buildtext()
1115 data = self.compress(text)
1115 data = self.compress(text)
1116 l = len(data[1]) + len(data[0])
1116 l = len(data[1]) + len(data[0])
1117 base = chainbase = curr
1117 base = chainbase = curr
1118
1118
1119 e = (offset_type(offset, flags), l, textlen,
1119 e = (offset_type(offset, flags), l, textlen,
1120 base, link, p1r, p2r, node)
1120 base, link, p1r, p2r, node)
1121 self.index.insert(-1, e)
1121 self.index.insert(-1, e)
1122 self.nodemap[node] = curr
1122 self.nodemap[node] = curr
1123
1123
1124 entry = self._io.packentry(e, self.node, self.version, curr)
1124 entry = self._io.packentry(e, self.node, self.version, curr)
1125 if not self._inline:
1125 if not self._inline:
1126 transaction.add(self.datafile, offset)
1126 transaction.add(self.datafile, offset)
1127 transaction.add(self.indexfile, curr * len(entry))
1127 transaction.add(self.indexfile, curr * len(entry))
1128 if data[0]:
1128 if data[0]:
1129 dfh.write(data[0])
1129 dfh.write(data[0])
1130 dfh.write(data[1])
1130 dfh.write(data[1])
1131 dfh.flush()
1131 dfh.flush()
1132 ifh.write(entry)
1132 ifh.write(entry)
1133 else:
1133 else:
1134 offset += curr * self._io.size
1134 offset += curr * self._io.size
1135 transaction.add(self.indexfile, offset, curr)
1135 transaction.add(self.indexfile, offset, curr)
1136 ifh.write(entry)
1136 ifh.write(entry)
1137 ifh.write(data[0])
1137 ifh.write(data[0])
1138 ifh.write(data[1])
1138 ifh.write(data[1])
1139 self.checkinlinesize(transaction, ifh)
1139 self.checkinlinesize(transaction, ifh)
1140
1140
1141 if type(text) == str: # only accept immutable objects
1141 if type(text) == str: # only accept immutable objects
1142 self._cache = (node, curr, text)
1142 self._cache = (node, curr, text)
1143 self._basecache = (curr, chainbase)
1143 self._basecache = (curr, chainbase)
1144 return node
1144 return node
1145
1145
1146 def group(self, nodelist, bundler, reorder=None):
1147 """Calculate a delta group, yielding a sequence of changegroup chunks
1148 (strings).
1149
1150 Given a list of changeset revs, return a set of deltas and
1151 metadata corresponding to nodes. The first delta is
1152 first parent(nodelist[0]) -> nodelist[0], the receiver is
1153 guaranteed to have this parent as it has all history before
1154 these changesets. In the case firstparent is nullrev the
1155 changegroup starts with a full revision.
1156 """
1157
1158 # if we don't have any revisions touched by these changesets, bail
1159 if len(nodelist) == 0:
1160 yield bundler.close()
1161 return
1162
1163 # for generaldelta revlogs, we linearize the revs; this will both be
1164 # much quicker and generate a much smaller bundle
1165 if (self._generaldelta and reorder is not False) or reorder:
1166 dag = dagutil.revlogdag(self)
1167 revs = set(self.rev(n) for n in nodelist)
1168 revs = dag.linearize(revs)
1169 else:
1170 revs = sorted([self.rev(n) for n in nodelist])
1171
1172 # add the parent of the first rev
1173 p = self.parentrevs(revs[0])[0]
1174 revs.insert(0, p)
1175
1176 # build deltas
1177 for r in xrange(len(revs) - 1):
1178 prev, curr = revs[r], revs[r + 1]
1179 for c in bundler.revchunk(self, curr, prev):
1180 yield c
1181
1182 yield bundler.close()
1183
1184 def addgroup(self, bundle, linkmapper, transaction):
1146 def addgroup(self, bundle, linkmapper, transaction):
1185 """
1147 """
1186 add a delta group
1148 add a delta group
1187
1149
1188 given a set of deltas, add them to the revision log. the
1150 given a set of deltas, add them to the revision log. the
1189 first delta is against its parent, which should be in our
1151 first delta is against its parent, which should be in our
1190 log, the rest are against the previous delta.
1152 log, the rest are against the previous delta.
1191 """
1153 """
1192
1154
1193 # track the base of the current delta log
1155 # track the base of the current delta log
1194 content = []
1156 content = []
1195 node = None
1157 node = None
1196
1158
1197 r = len(self)
1159 r = len(self)
1198 end = 0
1160 end = 0
1199 if r:
1161 if r:
1200 end = self.end(r - 1)
1162 end = self.end(r - 1)
1201 ifh = self.opener(self.indexfile, "a+")
1163 ifh = self.opener(self.indexfile, "a+")
1202 isize = r * self._io.size
1164 isize = r * self._io.size
1203 if self._inline:
1165 if self._inline:
1204 transaction.add(self.indexfile, end + isize, r)
1166 transaction.add(self.indexfile, end + isize, r)
1205 dfh = None
1167 dfh = None
1206 else:
1168 else:
1207 transaction.add(self.indexfile, isize, r)
1169 transaction.add(self.indexfile, isize, r)
1208 transaction.add(self.datafile, end)
1170 transaction.add(self.datafile, end)
1209 dfh = self.opener(self.datafile, "a")
1171 dfh = self.opener(self.datafile, "a")
1210
1172
1211 try:
1173 try:
1212 # loop through our set of deltas
1174 # loop through our set of deltas
1213 chain = None
1175 chain = None
1214 while True:
1176 while True:
1215 chunkdata = bundle.deltachunk(chain)
1177 chunkdata = bundle.deltachunk(chain)
1216 if not chunkdata:
1178 if not chunkdata:
1217 break
1179 break
1218 node = chunkdata['node']
1180 node = chunkdata['node']
1219 p1 = chunkdata['p1']
1181 p1 = chunkdata['p1']
1220 p2 = chunkdata['p2']
1182 p2 = chunkdata['p2']
1221 cs = chunkdata['cs']
1183 cs = chunkdata['cs']
1222 deltabase = chunkdata['deltabase']
1184 deltabase = chunkdata['deltabase']
1223 delta = chunkdata['delta']
1185 delta = chunkdata['delta']
1224
1186
1225 content.append(node)
1187 content.append(node)
1226
1188
1227 link = linkmapper(cs)
1189 link = linkmapper(cs)
1228 if node in self.nodemap:
1190 if node in self.nodemap:
1229 # this can happen if two branches make the same change
1191 # this can happen if two branches make the same change
1230 chain = node
1192 chain = node
1231 continue
1193 continue
1232
1194
1233 for p in (p1, p2):
1195 for p in (p1, p2):
1234 if p not in self.nodemap:
1196 if p not in self.nodemap:
1235 raise LookupError(p, self.indexfile,
1197 raise LookupError(p, self.indexfile,
1236 _('unknown parent'))
1198 _('unknown parent'))
1237
1199
1238 if deltabase not in self.nodemap:
1200 if deltabase not in self.nodemap:
1239 raise LookupError(deltabase, self.indexfile,
1201 raise LookupError(deltabase, self.indexfile,
1240 _('unknown delta base'))
1202 _('unknown delta base'))
1241
1203
1242 baserev = self.rev(deltabase)
1204 baserev = self.rev(deltabase)
1243 chain = self._addrevision(node, None, transaction, link,
1205 chain = self._addrevision(node, None, transaction, link,
1244 p1, p2, (baserev, delta), ifh, dfh)
1206 p1, p2, (baserev, delta), ifh, dfh)
1245 if not dfh and not self._inline:
1207 if not dfh and not self._inline:
1246 # addrevision switched from inline to conventional
1208 # addrevision switched from inline to conventional
1247 # reopen the index
1209 # reopen the index
1248 ifh.close()
1210 ifh.close()
1249 dfh = self.opener(self.datafile, "a")
1211 dfh = self.opener(self.datafile, "a")
1250 ifh = self.opener(self.indexfile, "a")
1212 ifh = self.opener(self.indexfile, "a")
1251 finally:
1213 finally:
1252 if dfh:
1214 if dfh:
1253 dfh.close()
1215 dfh.close()
1254 ifh.close()
1216 ifh.close()
1255
1217
1256 return content
1218 return content
1257
1219
1258 def strip(self, minlink, transaction):
1220 def strip(self, minlink, transaction):
1259 """truncate the revlog on the first revision with a linkrev >= minlink
1221 """truncate the revlog on the first revision with a linkrev >= minlink
1260
1222
1261 This function is called when we're stripping revision minlink and
1223 This function is called when we're stripping revision minlink and
1262 its descendants from the repository.
1224 its descendants from the repository.
1263
1225
1264 We have to remove all revisions with linkrev >= minlink, because
1226 We have to remove all revisions with linkrev >= minlink, because
1265 the equivalent changelog revisions will be renumbered after the
1227 the equivalent changelog revisions will be renumbered after the
1266 strip.
1228 strip.
1267
1229
1268 So we truncate the revlog on the first of these revisions, and
1230 So we truncate the revlog on the first of these revisions, and
1269 trust that the caller has saved the revisions that shouldn't be
1231 trust that the caller has saved the revisions that shouldn't be
1270 removed and that it'll re-add them after this truncation.
1232 removed and that it'll re-add them after this truncation.
1271 """
1233 """
1272 if len(self) == 0:
1234 if len(self) == 0:
1273 return
1235 return
1274
1236
1275 for rev in self:
1237 for rev in self:
1276 if self.index[rev][4] >= minlink:
1238 if self.index[rev][4] >= minlink:
1277 break
1239 break
1278 else:
1240 else:
1279 return
1241 return
1280
1242
1281 # first truncate the files on disk
1243 # first truncate the files on disk
1282 end = self.start(rev)
1244 end = self.start(rev)
1283 if not self._inline:
1245 if not self._inline:
1284 transaction.add(self.datafile, end)
1246 transaction.add(self.datafile, end)
1285 end = rev * self._io.size
1247 end = rev * self._io.size
1286 else:
1248 else:
1287 end += rev * self._io.size
1249 end += rev * self._io.size
1288
1250
1289 transaction.add(self.indexfile, end)
1251 transaction.add(self.indexfile, end)
1290
1252
1291 # then reset internal state in memory to forget those revisions
1253 # then reset internal state in memory to forget those revisions
1292 self._cache = None
1254 self._cache = None
1293 self._chunkclear()
1255 self._chunkclear()
1294 for x in xrange(rev, len(self)):
1256 for x in xrange(rev, len(self)):
1295 del self.nodemap[self.node(x)]
1257 del self.nodemap[self.node(x)]
1296
1258
1297 del self.index[rev:-1]
1259 del self.index[rev:-1]
1298
1260
1299 def checksize(self):
1261 def checksize(self):
1300 expected = 0
1262 expected = 0
1301 if len(self):
1263 if len(self):
1302 expected = max(0, self.end(len(self) - 1))
1264 expected = max(0, self.end(len(self) - 1))
1303
1265
1304 try:
1266 try:
1305 f = self.opener(self.datafile)
1267 f = self.opener(self.datafile)
1306 f.seek(0, 2)
1268 f.seek(0, 2)
1307 actual = f.tell()
1269 actual = f.tell()
1308 f.close()
1270 f.close()
1309 dd = actual - expected
1271 dd = actual - expected
1310 except IOError, inst:
1272 except IOError, inst:
1311 if inst.errno != errno.ENOENT:
1273 if inst.errno != errno.ENOENT:
1312 raise
1274 raise
1313 dd = 0
1275 dd = 0
1314
1276
1315 try:
1277 try:
1316 f = self.opener(self.indexfile)
1278 f = self.opener(self.indexfile)
1317 f.seek(0, 2)
1279 f.seek(0, 2)
1318 actual = f.tell()
1280 actual = f.tell()
1319 f.close()
1281 f.close()
1320 s = self._io.size
1282 s = self._io.size
1321 i = max(0, actual // s)
1283 i = max(0, actual // s)
1322 di = actual - (i * s)
1284 di = actual - (i * s)
1323 if self._inline:
1285 if self._inline:
1324 databytes = 0
1286 databytes = 0
1325 for r in self:
1287 for r in self:
1326 databytes += max(0, self.length(r))
1288 databytes += max(0, self.length(r))
1327 dd = 0
1289 dd = 0
1328 di = actual - len(self) * s - databytes
1290 di = actual - len(self) * s - databytes
1329 except IOError, inst:
1291 except IOError, inst:
1330 if inst.errno != errno.ENOENT:
1292 if inst.errno != errno.ENOENT:
1331 raise
1293 raise
1332 di = 0
1294 di = 0
1333
1295
1334 return (dd, di)
1296 return (dd, di)
1335
1297
1336 def files(self):
1298 def files(self):
1337 res = [self.indexfile]
1299 res = [self.indexfile]
1338 if not self._inline:
1300 if not self._inline:
1339 res.append(self.datafile)
1301 res.append(self.datafile)
1340 return res
1302 return res
General Comments 0
You need to be logged in to leave comments. Login now