##// END OF EJS Templates
revlog: flag processor...
Remi Chaintron -
r30745:c1b7b228 default
parent child Browse files
Show More
@@ -0,0 +1,137 b''
1 # coding=UTF-8
2
3 from __future__ import absolute_import
4
5 import base64
6 import zlib
7
8 from mercurial import (
9 changegroup,
10 extensions,
11 filelog,
12 revlog,
13 util,
14 )
15
16 # Test only: These flags are defined here only in the context of testing the
17 # behavior of the flag processor. The canonical way to add flags is to get in
18 # touch with the community and make them known in revlog.
19 REVIDX_NOOP = (1 << 3)
20 REVIDX_BASE64 = (1 << 2)
21 REVIDX_GZIP = (1 << 1)
22 REVIDX_FAIL = 1
23
24 def validatehash(self, text):
25 return True
26
27 def bypass(self, text):
28 return False
29
30 def noopdonothing(self, text):
31 return (text, True)
32
33 def b64encode(self, text):
34 return (base64.b64encode(text), False)
35
36 def b64decode(self, text):
37 return (base64.b64decode(text), True)
38
39 def gzipcompress(self, text):
40 return (zlib.compress(text), False)
41
42 def gzipdecompress(self, text):
43 return (zlib.decompress(text), True)
44
45 def supportedoutgoingversions(orig, repo):
46 versions = orig(repo)
47 versions.discard('01')
48 versions.discard('02')
49 versions.add('03')
50 return versions
51
52 def allsupportedversions(orig, ui):
53 versions = orig(ui)
54 versions.add('03')
55 return versions
56
57 def noopaddrevision(orig, self, text, transaction, link, p1, p2,
58 cachedelta=None, node=None,
59 flags=revlog.REVIDX_DEFAULT_FLAGS):
60 if '[NOOP]' in text:
61 flags |= REVIDX_NOOP
62 return orig(self, text, transaction, link, p1, p2, cachedelta=cachedelta,
63 node=node, flags=flags)
64
65 def b64addrevision(orig, self, text, transaction, link, p1, p2,
66 cachedelta=None, node=None,
67 flags=revlog.REVIDX_DEFAULT_FLAGS):
68 if '[BASE64]' in text:
69 flags |= REVIDX_BASE64
70 return orig(self, text, transaction, link, p1, p2, cachedelta=cachedelta,
71 node=node, flags=flags)
72
73 def gzipaddrevision(orig, self, text, transaction, link, p1, p2,
74 cachedelta=None, node=None,
75 flags=revlog.REVIDX_DEFAULT_FLAGS):
76 if '[GZIP]' in text:
77 flags |= REVIDX_GZIP
78 return orig(self, text, transaction, link, p1, p2, cachedelta=cachedelta,
79 node=node, flags=flags)
80
81 def failaddrevision(orig, self, text, transaction, link, p1, p2,
82 cachedelta=None, node=None,
83 flags=revlog.REVIDX_DEFAULT_FLAGS):
84 # This addrevision wrapper is meant to add a flag we will not have
85 # transforms registered for, ensuring we handle this error case.
86 if '[FAIL]' in text:
87 flags |= REVIDX_FAIL
88 return orig(self, text, transaction, link, p1, p2, cachedelta=cachedelta,
89 node=node, flags=flags)
90
91 def extsetup(ui):
92 # Enable changegroup3 for flags to be sent over the wire
93 wrapfunction = extensions.wrapfunction
94 wrapfunction(changegroup,
95 'supportedoutgoingversions',
96 supportedoutgoingversions)
97 wrapfunction(changegroup,
98 'allsupportedversions',
99 allsupportedversions)
100
101 # Teach revlog about our test flags
102 flags = [REVIDX_NOOP, REVIDX_BASE64, REVIDX_GZIP, REVIDX_FAIL]
103 revlog.REVIDX_KNOWN_FLAGS |= util.bitsfrom(flags)
104 revlog.REVIDX_FLAGS_ORDER.extend(flags)
105
106 # Add wrappers for addrevision, responsible to set flags depending on the
107 # revision data contents.
108 wrapfunction(filelog.filelog, 'addrevision', noopaddrevision)
109 wrapfunction(filelog.filelog, 'addrevision', b64addrevision)
110 wrapfunction(filelog.filelog, 'addrevision', gzipaddrevision)
111 wrapfunction(filelog.filelog, 'addrevision', failaddrevision)
112
113 # Register flag processors for each extension
114 revlog.addflagprocessor(
115 REVIDX_NOOP,
116 (
117 noopdonothing,
118 noopdonothing,
119 validatehash,
120 )
121 )
122 revlog.addflagprocessor(
123 REVIDX_BASE64,
124 (
125 b64decode,
126 b64encode,
127 bypass,
128 ),
129 )
130 revlog.addflagprocessor(
131 REVIDX_GZIP,
132 (
133 gzipdecompress,
134 gzipcompress,
135 bypass
136 )
137 )
@@ -0,0 +1,165 b''
1 # Create server
2 $ hg init server
3 $ cd server
4 $ cat >> .hg/hgrc << EOF
5 > [extensions]
6 > extension=$TESTDIR/flagprocessorext.py
7 > EOF
8 $ cd ../
9
10 # Clone server and enable extensions
11 $ hg clone -q server client
12 $ cd client
13 $ cat >> .hg/hgrc << EOF
14 > [extensions]
15 > extension=$TESTDIR/flagprocessorext.py
16 > EOF
17
18 # Commit file that will trigger the noop extension
19 $ echo '[NOOP]' > noop
20 $ hg commit -Aqm "noop"
21
22 # Commit file that will trigger the base64 extension
23 $ echo '[BASE64]' > base64
24 $ hg commit -Aqm 'base64'
25
26 # Commit file that will trigger the gzip extension
27 $ echo '[GZIP]' > gzip
28 $ hg commit -Aqm 'gzip'
29
30 # Commit file that will trigger noop and base64
31 $ echo '[NOOP][BASE64]' > noop-base64
32 $ hg commit -Aqm 'noop+base64'
33
34 # Commit file that will trigger noop and gzip
35 $ echo '[NOOP][GZIP]' > noop-gzip
36 $ hg commit -Aqm 'noop+gzip'
37
38 # Commit file that will trigger base64 and gzip
39 $ echo '[BASE64][GZIP]' > base64-gzip
40 $ hg commit -Aqm 'base64+gzip'
41
42 # Commit file that will trigger base64, gzip and noop
43 $ echo '[BASE64][GZIP][NOOP]' > base64-gzip-noop
44 $ hg commit -Aqm 'base64+gzip+noop'
45
46 # TEST: ensure the revision data is consistent
47 $ hg cat noop
48 [NOOP]
49 $ hg debugdata noop 0
50 [NOOP]
51
52 $ hg cat -r . base64
53 [BASE64]
54 $ hg debugdata base64 0
55 W0JBU0U2NF0K (no-eol)
56
57 $ hg cat -r . gzip
58 [GZIP]
59 $ hg debugdata gzip 0
60 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
61
62 $ hg cat -r . noop-base64
63 [NOOP][BASE64]
64 $ hg debugdata noop-base64 0
65 W05PT1BdW0JBU0U2NF0K (no-eol)
66
67 $ hg cat -r . noop-gzip
68 [NOOP][GZIP]
69 $ hg debugdata noop-gzip 0
70 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
71
72 $ hg cat -r . base64-gzip
73 [BASE64][GZIP]
74 $ hg debugdata base64-gzip 0
75 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
76
77 $ hg cat -r . base64-gzip-noop
78 [BASE64][GZIP][NOOP]
79 $ hg debugdata base64-gzip-noop 0
80 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
81
82 # Push to the server
83 $ hg push
84 pushing to $TESTTMP/server (glob)
85 searching for changes
86 adding changesets
87 adding manifests
88 adding file changes
89 added 7 changesets with 7 changes to 7 files
90
91 # Initialize new client (not cloning) and setup extension
92 $ cd ..
93 $ hg init client2
94 $ cd client2
95 $ cat >> .hg/hgrc << EOF
96 > [paths]
97 > default = $TESTTMP/server
98 > [extensions]
99 > extension=$TESTDIR/flagprocessorext.py
100 > EOF
101
102 # Pull from server and update to latest revision
103 $ hg pull default
104 pulling from $TESTTMP/server (glob)
105 requesting all changes
106 adding changesets
107 adding manifests
108 adding file changes
109 added 7 changesets with 7 changes to 7 files
110 (run 'hg update' to get a working copy)
111 $ hg update
112 7 files updated, 0 files merged, 0 files removed, 0 files unresolved
113
114 # TEST: ensure the revision data is consistent
115 $ hg cat noop
116 [NOOP]
117 $ hg debugdata noop 0
118 [NOOP]
119
120 $ hg cat -r . base64
121 [BASE64]
122 $ hg debugdata base64 0
123 W0JBU0U2NF0K (no-eol)
124
125 $ hg cat -r . gzip
126 [GZIP]
127 $ hg debugdata gzip 0
128 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
129
130 $ hg cat -r . noop-base64
131 [NOOP][BASE64]
132 $ hg debugdata noop-base64 0
133 W05PT1BdW0JBU0U2NF0K (no-eol)
134
135 $ hg cat -r . noop-gzip
136 [NOOP][GZIP]
137 $ hg debugdata noop-gzip 0
138 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
139
140 $ hg cat -r . base64-gzip
141 [BASE64][GZIP]
142 $ hg debugdata base64-gzip 0
143 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
144
145 $ hg cat -r . base64-gzip-noop
146 [BASE64][GZIP][NOOP]
147 $ hg debugdata base64-gzip-noop 0
148 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
149
150 # TEST: ensure a missing processor is handled
151 $ echo '[FAIL][BASE64][GZIP][NOOP]' > fail-base64-gzip-noop
152 $ hg commit -Aqm 'fail+base64+gzip+noop'
153 abort: missing processor for flag '0x1'!
154 [255]
155
156 # TEST: ensure we cannot register several flag processors on the same flag
157 $ cat >> .hg/hgrc << EOF
158 > [extensions]
159 > extension=$TESTDIR/flagprocessorext.py
160 > duplicate=$TESTDIR/flagprocessorext.py
161 > EOF
162 $ echo 'this should fail' > file
163 $ hg commit -Aqm 'add file'
164 abort: cannot register multiple processors on flag '0x8'.
165 [255]
@@ -1,554 +1,557 b''
1 # bundlerepo.py - repository class for viewing uncompressed bundles
1 # bundlerepo.py - repository class for viewing uncompressed bundles
2 #
2 #
3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.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 """Repository class for viewing uncompressed bundles.
8 """Repository class for viewing uncompressed bundles.
9
9
10 This provides a read-only repository interface to bundles as if they
10 This provides a read-only repository interface to bundles as if they
11 were part of the actual repository.
11 were part of the actual repository.
12 """
12 """
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import os
16 import os
17 import shutil
17 import shutil
18 import tempfile
18 import tempfile
19
19
20 from .i18n import _
20 from .i18n import _
21 from .node import nullid
21 from .node import nullid
22
22
23 from . import (
23 from . import (
24 bundle2,
24 bundle2,
25 changegroup,
25 changegroup,
26 changelog,
26 changelog,
27 cmdutil,
27 cmdutil,
28 discovery,
28 discovery,
29 error,
29 error,
30 exchange,
30 exchange,
31 filelog,
31 filelog,
32 localrepo,
32 localrepo,
33 manifest,
33 manifest,
34 mdiff,
34 mdiff,
35 node as nodemod,
35 node as nodemod,
36 pathutil,
36 pathutil,
37 phases,
37 phases,
38 pycompat,
38 pycompat,
39 revlog,
39 revlog,
40 scmutil,
40 scmutil,
41 util,
41 util,
42 )
42 )
43
43
44 class bundlerevlog(revlog.revlog):
44 class bundlerevlog(revlog.revlog):
45 def __init__(self, opener, indexfile, bundle, linkmapper):
45 def __init__(self, opener, indexfile, bundle, linkmapper):
46 # How it works:
46 # How it works:
47 # To retrieve a revision, we need to know the offset of the revision in
47 # To retrieve a revision, we need to know the offset of the revision in
48 # the bundle (an unbundle object). We store this offset in the index
48 # the bundle (an unbundle object). We store this offset in the index
49 # (start). The base of the delta is stored in the base field.
49 # (start). The base of the delta is stored in the base field.
50 #
50 #
51 # To differentiate a rev in the bundle from a rev in the revlog, we
51 # To differentiate a rev in the bundle from a rev in the revlog, we
52 # check revision against repotiprev.
52 # check revision against repotiprev.
53 opener = scmutil.readonlyvfs(opener)
53 opener = scmutil.readonlyvfs(opener)
54 revlog.revlog.__init__(self, opener, indexfile)
54 revlog.revlog.__init__(self, opener, indexfile)
55 self.bundle = bundle
55 self.bundle = bundle
56 n = len(self)
56 n = len(self)
57 self.repotiprev = n - 1
57 self.repotiprev = n - 1
58 chain = None
58 chain = None
59 self.bundlerevs = set() # used by 'bundle()' revset expression
59 self.bundlerevs = set() # used by 'bundle()' revset expression
60 getchunk = lambda: bundle.deltachunk(chain)
60 getchunk = lambda: bundle.deltachunk(chain)
61 for chunkdata in iter(getchunk, {}):
61 for chunkdata in iter(getchunk, {}):
62 node = chunkdata['node']
62 node = chunkdata['node']
63 p1 = chunkdata['p1']
63 p1 = chunkdata['p1']
64 p2 = chunkdata['p2']
64 p2 = chunkdata['p2']
65 cs = chunkdata['cs']
65 cs = chunkdata['cs']
66 deltabase = chunkdata['deltabase']
66 deltabase = chunkdata['deltabase']
67 delta = chunkdata['delta']
67 delta = chunkdata['delta']
68
68
69 size = len(delta)
69 size = len(delta)
70 start = bundle.tell() - size
70 start = bundle.tell() - size
71
71
72 link = linkmapper(cs)
72 link = linkmapper(cs)
73 if node in self.nodemap:
73 if node in self.nodemap:
74 # this can happen if two branches make the same change
74 # this can happen if two branches make the same change
75 chain = node
75 chain = node
76 self.bundlerevs.add(self.nodemap[node])
76 self.bundlerevs.add(self.nodemap[node])
77 continue
77 continue
78
78
79 for p in (p1, p2):
79 for p in (p1, p2):
80 if p not in self.nodemap:
80 if p not in self.nodemap:
81 raise error.LookupError(p, self.indexfile,
81 raise error.LookupError(p, self.indexfile,
82 _("unknown parent"))
82 _("unknown parent"))
83
83
84 if deltabase not in self.nodemap:
84 if deltabase not in self.nodemap:
85 raise LookupError(deltabase, self.indexfile,
85 raise LookupError(deltabase, self.indexfile,
86 _('unknown delta base'))
86 _('unknown delta base'))
87
87
88 baserev = self.rev(deltabase)
88 baserev = self.rev(deltabase)
89 # start, size, full unc. size, base (unused), link, p1, p2, node
89 # start, size, full unc. size, base (unused), link, p1, p2, node
90 e = (revlog.offset_type(start, 0), size, -1, baserev, link,
90 e = (revlog.offset_type(start, 0), size, -1, baserev, link,
91 self.rev(p1), self.rev(p2), node)
91 self.rev(p1), self.rev(p2), node)
92 self.index.insert(-1, e)
92 self.index.insert(-1, e)
93 self.nodemap[node] = n
93 self.nodemap[node] = n
94 self.bundlerevs.add(n)
94 self.bundlerevs.add(n)
95 chain = node
95 chain = node
96 n += 1
96 n += 1
97
97
98 def _chunk(self, rev):
98 def _chunk(self, rev):
99 # Warning: in case of bundle, the diff is against what we stored as
99 # Warning: in case of bundle, the diff is against what we stored as
100 # delta base, not against rev - 1
100 # delta base, not against rev - 1
101 # XXX: could use some caching
101 # XXX: could use some caching
102 if rev <= self.repotiprev:
102 if rev <= self.repotiprev:
103 return revlog.revlog._chunk(self, rev)
103 return revlog.revlog._chunk(self, rev)
104 self.bundle.seek(self.start(rev))
104 self.bundle.seek(self.start(rev))
105 return self.bundle.read(self.length(rev))
105 return self.bundle.read(self.length(rev))
106
106
107 def revdiff(self, rev1, rev2):
107 def revdiff(self, rev1, rev2):
108 """return or calculate a delta between two revisions"""
108 """return or calculate a delta between two revisions"""
109 if rev1 > self.repotiprev and rev2 > self.repotiprev:
109 if rev1 > self.repotiprev and rev2 > self.repotiprev:
110 # hot path for bundle
110 # hot path for bundle
111 revb = self.index[rev2][3]
111 revb = self.index[rev2][3]
112 if revb == rev1:
112 if revb == rev1:
113 return self._chunk(rev2)
113 return self._chunk(rev2)
114 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
114 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
115 return revlog.revlog.revdiff(self, rev1, rev2)
115 return revlog.revlog.revdiff(self, rev1, rev2)
116
116
117 return mdiff.textdiff(self.revision(self.node(rev1)),
117 return mdiff.textdiff(self.revision(self.node(rev1)),
118 self.revision(self.node(rev2)))
118 self.revision(self.node(rev2)))
119
119
120 def revision(self, nodeorrev, raw=False):
120 def revision(self, nodeorrev, raw=False):
121 """return an uncompressed revision of a given node or revision
121 """return an uncompressed revision of a given node or revision
122 number.
122 number.
123 """
123 """
124 if isinstance(nodeorrev, int):
124 if isinstance(nodeorrev, int):
125 rev = nodeorrev
125 rev = nodeorrev
126 node = self.node(rev)
126 node = self.node(rev)
127 else:
127 else:
128 node = nodeorrev
128 node = nodeorrev
129 rev = self.rev(node)
129 rev = self.rev(node)
130
130
131 if node == nullid:
131 if node == nullid:
132 return ""
132 return ""
133
133
134 text = None
134 text = None
135 chain = []
135 chain = []
136 iterrev = rev
136 iterrev = rev
137 # reconstruct the revision if it is from a changegroup
137 # reconstruct the revision if it is from a changegroup
138 while iterrev > self.repotiprev:
138 while iterrev > self.repotiprev:
139 if self._cache and self._cache[1] == iterrev:
139 if self._cache and self._cache[1] == iterrev:
140 text = self._cache[2]
140 text = self._cache[2]
141 break
141 break
142 chain.append(iterrev)
142 chain.append(iterrev)
143 iterrev = self.index[iterrev][3]
143 iterrev = self.index[iterrev][3]
144 if text is None:
144 if text is None:
145 text = self.baserevision(iterrev)
145 text = self.baserevision(iterrev)
146
146
147 while chain:
147 while chain:
148 delta = self._chunk(chain.pop())
148 delta = self._chunk(chain.pop())
149 text = mdiff.patches(text, [delta])
149 text = mdiff.patches(text, [delta])
150
150
151 self.checkhash(text, node, rev=rev)
151 text, validatehash = self._processflags(text, self.flags(rev),
152 'read', raw=raw)
153 if validatehash:
154 self.checkhash(text, node, rev=rev)
152 self._cache = (node, rev, text)
155 self._cache = (node, rev, text)
153 return text
156 return text
154
157
155 def baserevision(self, nodeorrev):
158 def baserevision(self, nodeorrev):
156 # Revlog subclasses may override 'revision' method to modify format of
159 # Revlog subclasses may override 'revision' method to modify format of
157 # content retrieved from revlog. To use bundlerevlog with such class one
160 # content retrieved from revlog. To use bundlerevlog with such class one
158 # needs to override 'baserevision' and make more specific call here.
161 # needs to override 'baserevision' and make more specific call here.
159 return revlog.revlog.revision(self, nodeorrev)
162 return revlog.revlog.revision(self, nodeorrev)
160
163
161 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
164 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
162 raise NotImplementedError
165 raise NotImplementedError
163 def addgroup(self, revs, linkmapper, transaction):
166 def addgroup(self, revs, linkmapper, transaction):
164 raise NotImplementedError
167 raise NotImplementedError
165 def strip(self, rev, minlink):
168 def strip(self, rev, minlink):
166 raise NotImplementedError
169 raise NotImplementedError
167 def checksize(self):
170 def checksize(self):
168 raise NotImplementedError
171 raise NotImplementedError
169
172
170 class bundlechangelog(bundlerevlog, changelog.changelog):
173 class bundlechangelog(bundlerevlog, changelog.changelog):
171 def __init__(self, opener, bundle):
174 def __init__(self, opener, bundle):
172 changelog.changelog.__init__(self, opener)
175 changelog.changelog.__init__(self, opener)
173 linkmapper = lambda x: x
176 linkmapper = lambda x: x
174 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
177 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
175 linkmapper)
178 linkmapper)
176
179
177 def baserevision(self, nodeorrev):
180 def baserevision(self, nodeorrev):
178 # Although changelog doesn't override 'revision' method, some extensions
181 # Although changelog doesn't override 'revision' method, some extensions
179 # may replace this class with another that does. Same story with
182 # may replace this class with another that does. Same story with
180 # manifest and filelog classes.
183 # manifest and filelog classes.
181
184
182 # This bypasses filtering on changelog.node() and rev() because we need
185 # This bypasses filtering on changelog.node() and rev() because we need
183 # revision text of the bundle base even if it is hidden.
186 # revision text of the bundle base even if it is hidden.
184 oldfilter = self.filteredrevs
187 oldfilter = self.filteredrevs
185 try:
188 try:
186 self.filteredrevs = ()
189 self.filteredrevs = ()
187 return changelog.changelog.revision(self, nodeorrev)
190 return changelog.changelog.revision(self, nodeorrev)
188 finally:
191 finally:
189 self.filteredrevs = oldfilter
192 self.filteredrevs = oldfilter
190
193
191 class bundlemanifest(bundlerevlog, manifest.manifestrevlog):
194 class bundlemanifest(bundlerevlog, manifest.manifestrevlog):
192 def __init__(self, opener, bundle, linkmapper, dirlogstarts=None, dir=''):
195 def __init__(self, opener, bundle, linkmapper, dirlogstarts=None, dir=''):
193 manifest.manifestrevlog.__init__(self, opener, dir=dir)
196 manifest.manifestrevlog.__init__(self, opener, dir=dir)
194 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
197 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
195 linkmapper)
198 linkmapper)
196 if dirlogstarts is None:
199 if dirlogstarts is None:
197 dirlogstarts = {}
200 dirlogstarts = {}
198 if self.bundle.version == "03":
201 if self.bundle.version == "03":
199 dirlogstarts = _getfilestarts(self.bundle)
202 dirlogstarts = _getfilestarts(self.bundle)
200 self._dirlogstarts = dirlogstarts
203 self._dirlogstarts = dirlogstarts
201 self._linkmapper = linkmapper
204 self._linkmapper = linkmapper
202
205
203 def baserevision(self, nodeorrev):
206 def baserevision(self, nodeorrev):
204 node = nodeorrev
207 node = nodeorrev
205 if isinstance(node, int):
208 if isinstance(node, int):
206 node = self.node(node)
209 node = self.node(node)
207
210
208 if node in self.fulltextcache:
211 if node in self.fulltextcache:
209 result = self.fulltextcache[node].tostring()
212 result = self.fulltextcache[node].tostring()
210 else:
213 else:
211 result = manifest.manifestrevlog.revision(self, nodeorrev)
214 result = manifest.manifestrevlog.revision(self, nodeorrev)
212 return result
215 return result
213
216
214 def dirlog(self, d):
217 def dirlog(self, d):
215 if d in self._dirlogstarts:
218 if d in self._dirlogstarts:
216 self.bundle.seek(self._dirlogstarts[d])
219 self.bundle.seek(self._dirlogstarts[d])
217 return bundlemanifest(
220 return bundlemanifest(
218 self.opener, self.bundle, self._linkmapper,
221 self.opener, self.bundle, self._linkmapper,
219 self._dirlogstarts, dir=d)
222 self._dirlogstarts, dir=d)
220 return super(bundlemanifest, self).dirlog(d)
223 return super(bundlemanifest, self).dirlog(d)
221
224
222 class bundlefilelog(bundlerevlog, filelog.filelog):
225 class bundlefilelog(bundlerevlog, filelog.filelog):
223 def __init__(self, opener, path, bundle, linkmapper):
226 def __init__(self, opener, path, bundle, linkmapper):
224 filelog.filelog.__init__(self, opener, path)
227 filelog.filelog.__init__(self, opener, path)
225 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
228 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
226 linkmapper)
229 linkmapper)
227
230
228 def baserevision(self, nodeorrev):
231 def baserevision(self, nodeorrev):
229 return filelog.filelog.revision(self, nodeorrev)
232 return filelog.filelog.revision(self, nodeorrev)
230
233
231 class bundlepeer(localrepo.localpeer):
234 class bundlepeer(localrepo.localpeer):
232 def canpush(self):
235 def canpush(self):
233 return False
236 return False
234
237
235 class bundlephasecache(phases.phasecache):
238 class bundlephasecache(phases.phasecache):
236 def __init__(self, *args, **kwargs):
239 def __init__(self, *args, **kwargs):
237 super(bundlephasecache, self).__init__(*args, **kwargs)
240 super(bundlephasecache, self).__init__(*args, **kwargs)
238 if util.safehasattr(self, 'opener'):
241 if util.safehasattr(self, 'opener'):
239 self.opener = scmutil.readonlyvfs(self.opener)
242 self.opener = scmutil.readonlyvfs(self.opener)
240
243
241 def write(self):
244 def write(self):
242 raise NotImplementedError
245 raise NotImplementedError
243
246
244 def _write(self, fp):
247 def _write(self, fp):
245 raise NotImplementedError
248 raise NotImplementedError
246
249
247 def _updateroots(self, phase, newroots, tr):
250 def _updateroots(self, phase, newroots, tr):
248 self.phaseroots[phase] = newroots
251 self.phaseroots[phase] = newroots
249 self.invalidate()
252 self.invalidate()
250 self.dirty = True
253 self.dirty = True
251
254
252 def _getfilestarts(bundle):
255 def _getfilestarts(bundle):
253 bundlefilespos = {}
256 bundlefilespos = {}
254 for chunkdata in iter(bundle.filelogheader, {}):
257 for chunkdata in iter(bundle.filelogheader, {}):
255 fname = chunkdata['filename']
258 fname = chunkdata['filename']
256 bundlefilespos[fname] = bundle.tell()
259 bundlefilespos[fname] = bundle.tell()
257 for chunk in iter(lambda: bundle.deltachunk(None), {}):
260 for chunk in iter(lambda: bundle.deltachunk(None), {}):
258 pass
261 pass
259 return bundlefilespos
262 return bundlefilespos
260
263
261 class bundlerepository(localrepo.localrepository):
264 class bundlerepository(localrepo.localrepository):
262 def __init__(self, ui, path, bundlename):
265 def __init__(self, ui, path, bundlename):
263 def _writetempbundle(read, suffix, header=''):
266 def _writetempbundle(read, suffix, header=''):
264 """Write a temporary file to disk
267 """Write a temporary file to disk
265
268
266 This is closure because we need to make sure this tracked by
269 This is closure because we need to make sure this tracked by
267 self.tempfile for cleanup purposes."""
270 self.tempfile for cleanup purposes."""
268 fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
271 fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
269 suffix=".hg10un")
272 suffix=".hg10un")
270 self.tempfile = temp
273 self.tempfile = temp
271
274
272 with os.fdopen(fdtemp, 'wb') as fptemp:
275 with os.fdopen(fdtemp, 'wb') as fptemp:
273 fptemp.write(header)
276 fptemp.write(header)
274 while True:
277 while True:
275 chunk = read(2**18)
278 chunk = read(2**18)
276 if not chunk:
279 if not chunk:
277 break
280 break
278 fptemp.write(chunk)
281 fptemp.write(chunk)
279
282
280 return self.vfs.open(self.tempfile, mode="rb")
283 return self.vfs.open(self.tempfile, mode="rb")
281 self._tempparent = None
284 self._tempparent = None
282 try:
285 try:
283 localrepo.localrepository.__init__(self, ui, path)
286 localrepo.localrepository.__init__(self, ui, path)
284 except error.RepoError:
287 except error.RepoError:
285 self._tempparent = tempfile.mkdtemp()
288 self._tempparent = tempfile.mkdtemp()
286 localrepo.instance(ui, self._tempparent, 1)
289 localrepo.instance(ui, self._tempparent, 1)
287 localrepo.localrepository.__init__(self, ui, self._tempparent)
290 localrepo.localrepository.__init__(self, ui, self._tempparent)
288 self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
291 self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
289
292
290 if path:
293 if path:
291 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
294 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
292 else:
295 else:
293 self._url = 'bundle:' + bundlename
296 self._url = 'bundle:' + bundlename
294
297
295 self.tempfile = None
298 self.tempfile = None
296 f = util.posixfile(bundlename, "rb")
299 f = util.posixfile(bundlename, "rb")
297 self.bundlefile = self.bundle = exchange.readbundle(ui, f, bundlename)
300 self.bundlefile = self.bundle = exchange.readbundle(ui, f, bundlename)
298
301
299 if isinstance(self.bundle, bundle2.unbundle20):
302 if isinstance(self.bundle, bundle2.unbundle20):
300 cgstream = None
303 cgstream = None
301 for part in self.bundle.iterparts():
304 for part in self.bundle.iterparts():
302 if part.type == 'changegroup':
305 if part.type == 'changegroup':
303 if cgstream is not None:
306 if cgstream is not None:
304 raise NotImplementedError("can't process "
307 raise NotImplementedError("can't process "
305 "multiple changegroups")
308 "multiple changegroups")
306 cgstream = part
309 cgstream = part
307 version = part.params.get('version', '01')
310 version = part.params.get('version', '01')
308 legalcgvers = changegroup.supportedincomingversions(self)
311 legalcgvers = changegroup.supportedincomingversions(self)
309 if version not in legalcgvers:
312 if version not in legalcgvers:
310 msg = _('Unsupported changegroup version: %s')
313 msg = _('Unsupported changegroup version: %s')
311 raise error.Abort(msg % version)
314 raise error.Abort(msg % version)
312 if self.bundle.compressed():
315 if self.bundle.compressed():
313 cgstream = _writetempbundle(part.read,
316 cgstream = _writetempbundle(part.read,
314 ".cg%sun" % version)
317 ".cg%sun" % version)
315
318
316 if cgstream is None:
319 if cgstream is None:
317 raise error.Abort(_('No changegroups found'))
320 raise error.Abort(_('No changegroups found'))
318 cgstream.seek(0)
321 cgstream.seek(0)
319
322
320 self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
323 self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
321
324
322 elif self.bundle.compressed():
325 elif self.bundle.compressed():
323 f = _writetempbundle(self.bundle.read, '.hg10un', header='HG10UN')
326 f = _writetempbundle(self.bundle.read, '.hg10un', header='HG10UN')
324 self.bundlefile = self.bundle = exchange.readbundle(ui, f,
327 self.bundlefile = self.bundle = exchange.readbundle(ui, f,
325 bundlename,
328 bundlename,
326 self.vfs)
329 self.vfs)
327
330
328 # dict with the mapping 'filename' -> position in the bundle
331 # dict with the mapping 'filename' -> position in the bundle
329 self.bundlefilespos = {}
332 self.bundlefilespos = {}
330
333
331 self.firstnewrev = self.changelog.repotiprev + 1
334 self.firstnewrev = self.changelog.repotiprev + 1
332 phases.retractboundary(self, None, phases.draft,
335 phases.retractboundary(self, None, phases.draft,
333 [ctx.node() for ctx in self[self.firstnewrev:]])
336 [ctx.node() for ctx in self[self.firstnewrev:]])
334
337
335 @localrepo.unfilteredpropertycache
338 @localrepo.unfilteredpropertycache
336 def _phasecache(self):
339 def _phasecache(self):
337 return bundlephasecache(self, self._phasedefaults)
340 return bundlephasecache(self, self._phasedefaults)
338
341
339 @localrepo.unfilteredpropertycache
342 @localrepo.unfilteredpropertycache
340 def changelog(self):
343 def changelog(self):
341 # consume the header if it exists
344 # consume the header if it exists
342 self.bundle.changelogheader()
345 self.bundle.changelogheader()
343 c = bundlechangelog(self.svfs, self.bundle)
346 c = bundlechangelog(self.svfs, self.bundle)
344 self.manstart = self.bundle.tell()
347 self.manstart = self.bundle.tell()
345 return c
348 return c
346
349
347 def _constructmanifest(self):
350 def _constructmanifest(self):
348 self.bundle.seek(self.manstart)
351 self.bundle.seek(self.manstart)
349 # consume the header if it exists
352 # consume the header if it exists
350 self.bundle.manifestheader()
353 self.bundle.manifestheader()
351 linkmapper = self.unfiltered().changelog.rev
354 linkmapper = self.unfiltered().changelog.rev
352 m = bundlemanifest(self.svfs, self.bundle, linkmapper)
355 m = bundlemanifest(self.svfs, self.bundle, linkmapper)
353 self.filestart = self.bundle.tell()
356 self.filestart = self.bundle.tell()
354 return m
357 return m
355
358
356 @localrepo.unfilteredpropertycache
359 @localrepo.unfilteredpropertycache
357 def manstart(self):
360 def manstart(self):
358 self.changelog
361 self.changelog
359 return self.manstart
362 return self.manstart
360
363
361 @localrepo.unfilteredpropertycache
364 @localrepo.unfilteredpropertycache
362 def filestart(self):
365 def filestart(self):
363 self.manifestlog
366 self.manifestlog
364 return self.filestart
367 return self.filestart
365
368
366 def url(self):
369 def url(self):
367 return self._url
370 return self._url
368
371
369 def file(self, f):
372 def file(self, f):
370 if not self.bundlefilespos:
373 if not self.bundlefilespos:
371 self.bundle.seek(self.filestart)
374 self.bundle.seek(self.filestart)
372 self.bundlefilespos = _getfilestarts(self.bundle)
375 self.bundlefilespos = _getfilestarts(self.bundle)
373
376
374 if f in self.bundlefilespos:
377 if f in self.bundlefilespos:
375 self.bundle.seek(self.bundlefilespos[f])
378 self.bundle.seek(self.bundlefilespos[f])
376 linkmapper = self.unfiltered().changelog.rev
379 linkmapper = self.unfiltered().changelog.rev
377 return bundlefilelog(self.svfs, f, self.bundle, linkmapper)
380 return bundlefilelog(self.svfs, f, self.bundle, linkmapper)
378 else:
381 else:
379 return filelog.filelog(self.svfs, f)
382 return filelog.filelog(self.svfs, f)
380
383
381 def close(self):
384 def close(self):
382 """Close assigned bundle file immediately."""
385 """Close assigned bundle file immediately."""
383 self.bundlefile.close()
386 self.bundlefile.close()
384 if self.tempfile is not None:
387 if self.tempfile is not None:
385 self.vfs.unlink(self.tempfile)
388 self.vfs.unlink(self.tempfile)
386 if self._tempparent:
389 if self._tempparent:
387 shutil.rmtree(self._tempparent, True)
390 shutil.rmtree(self._tempparent, True)
388
391
389 def cancopy(self):
392 def cancopy(self):
390 return False
393 return False
391
394
392 def peer(self):
395 def peer(self):
393 return bundlepeer(self)
396 return bundlepeer(self)
394
397
395 def getcwd(self):
398 def getcwd(self):
396 return pycompat.getcwd() # always outside the repo
399 return pycompat.getcwd() # always outside the repo
397
400
398 # Check if parents exist in localrepo before setting
401 # Check if parents exist in localrepo before setting
399 def setparents(self, p1, p2=nullid):
402 def setparents(self, p1, p2=nullid):
400 p1rev = self.changelog.rev(p1)
403 p1rev = self.changelog.rev(p1)
401 p2rev = self.changelog.rev(p2)
404 p2rev = self.changelog.rev(p2)
402 msg = _("setting parent to node %s that only exists in the bundle\n")
405 msg = _("setting parent to node %s that only exists in the bundle\n")
403 if self.changelog.repotiprev < p1rev:
406 if self.changelog.repotiprev < p1rev:
404 self.ui.warn(msg % nodemod.hex(p1))
407 self.ui.warn(msg % nodemod.hex(p1))
405 if self.changelog.repotiprev < p2rev:
408 if self.changelog.repotiprev < p2rev:
406 self.ui.warn(msg % nodemod.hex(p2))
409 self.ui.warn(msg % nodemod.hex(p2))
407 return super(bundlerepository, self).setparents(p1, p2)
410 return super(bundlerepository, self).setparents(p1, p2)
408
411
409 def instance(ui, path, create):
412 def instance(ui, path, create):
410 if create:
413 if create:
411 raise error.Abort(_('cannot create new bundle repository'))
414 raise error.Abort(_('cannot create new bundle repository'))
412 # internal config: bundle.mainreporoot
415 # internal config: bundle.mainreporoot
413 parentpath = ui.config("bundle", "mainreporoot", "")
416 parentpath = ui.config("bundle", "mainreporoot", "")
414 if not parentpath:
417 if not parentpath:
415 # try to find the correct path to the working directory repo
418 # try to find the correct path to the working directory repo
416 parentpath = cmdutil.findrepo(pycompat.getcwd())
419 parentpath = cmdutil.findrepo(pycompat.getcwd())
417 if parentpath is None:
420 if parentpath is None:
418 parentpath = ''
421 parentpath = ''
419 if parentpath:
422 if parentpath:
420 # Try to make the full path relative so we get a nice, short URL.
423 # Try to make the full path relative so we get a nice, short URL.
421 # In particular, we don't want temp dir names in test outputs.
424 # In particular, we don't want temp dir names in test outputs.
422 cwd = pycompat.getcwd()
425 cwd = pycompat.getcwd()
423 if parentpath == cwd:
426 if parentpath == cwd:
424 parentpath = ''
427 parentpath = ''
425 else:
428 else:
426 cwd = pathutil.normasprefix(cwd)
429 cwd = pathutil.normasprefix(cwd)
427 if parentpath.startswith(cwd):
430 if parentpath.startswith(cwd):
428 parentpath = parentpath[len(cwd):]
431 parentpath = parentpath[len(cwd):]
429 u = util.url(path)
432 u = util.url(path)
430 path = u.localpath()
433 path = u.localpath()
431 if u.scheme == 'bundle':
434 if u.scheme == 'bundle':
432 s = path.split("+", 1)
435 s = path.split("+", 1)
433 if len(s) == 1:
436 if len(s) == 1:
434 repopath, bundlename = parentpath, s[0]
437 repopath, bundlename = parentpath, s[0]
435 else:
438 else:
436 repopath, bundlename = s
439 repopath, bundlename = s
437 else:
440 else:
438 repopath, bundlename = parentpath, path
441 repopath, bundlename = parentpath, path
439 return bundlerepository(ui, repopath, bundlename)
442 return bundlerepository(ui, repopath, bundlename)
440
443
441 class bundletransactionmanager(object):
444 class bundletransactionmanager(object):
442 def transaction(self):
445 def transaction(self):
443 return None
446 return None
444
447
445 def close(self):
448 def close(self):
446 raise NotImplementedError
449 raise NotImplementedError
447
450
448 def release(self):
451 def release(self):
449 raise NotImplementedError
452 raise NotImplementedError
450
453
451 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
454 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
452 force=False):
455 force=False):
453 '''obtains a bundle of changes incoming from other
456 '''obtains a bundle of changes incoming from other
454
457
455 "onlyheads" restricts the returned changes to those reachable from the
458 "onlyheads" restricts the returned changes to those reachable from the
456 specified heads.
459 specified heads.
457 "bundlename", if given, stores the bundle to this file path permanently;
460 "bundlename", if given, stores the bundle to this file path permanently;
458 otherwise it's stored to a temp file and gets deleted again when you call
461 otherwise it's stored to a temp file and gets deleted again when you call
459 the returned "cleanupfn".
462 the returned "cleanupfn".
460 "force" indicates whether to proceed on unrelated repos.
463 "force" indicates whether to proceed on unrelated repos.
461
464
462 Returns a tuple (local, csets, cleanupfn):
465 Returns a tuple (local, csets, cleanupfn):
463
466
464 "local" is a local repo from which to obtain the actual incoming
467 "local" is a local repo from which to obtain the actual incoming
465 changesets; it is a bundlerepo for the obtained bundle when the
468 changesets; it is a bundlerepo for the obtained bundle when the
466 original "other" is remote.
469 original "other" is remote.
467 "csets" lists the incoming changeset node ids.
470 "csets" lists the incoming changeset node ids.
468 "cleanupfn" must be called without arguments when you're done processing
471 "cleanupfn" must be called without arguments when you're done processing
469 the changes; it closes both the original "other" and the one returned
472 the changes; it closes both the original "other" and the one returned
470 here.
473 here.
471 '''
474 '''
472 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
475 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
473 force=force)
476 force=force)
474 common, incoming, rheads = tmp
477 common, incoming, rheads = tmp
475 if not incoming:
478 if not incoming:
476 try:
479 try:
477 if bundlename:
480 if bundlename:
478 os.unlink(bundlename)
481 os.unlink(bundlename)
479 except OSError:
482 except OSError:
480 pass
483 pass
481 return repo, [], other.close
484 return repo, [], other.close
482
485
483 commonset = set(common)
486 commonset = set(common)
484 rheads = [x for x in rheads if x not in commonset]
487 rheads = [x for x in rheads if x not in commonset]
485
488
486 bundle = None
489 bundle = None
487 bundlerepo = None
490 bundlerepo = None
488 localrepo = other.local()
491 localrepo = other.local()
489 if bundlename or not localrepo:
492 if bundlename or not localrepo:
490 # create a bundle (uncompressed if other repo is not local)
493 # create a bundle (uncompressed if other repo is not local)
491
494
492 # developer config: devel.legacy.exchange
495 # developer config: devel.legacy.exchange
493 legexc = ui.configlist('devel', 'legacy.exchange')
496 legexc = ui.configlist('devel', 'legacy.exchange')
494 forcebundle1 = 'bundle2' not in legexc and 'bundle1' in legexc
497 forcebundle1 = 'bundle2' not in legexc and 'bundle1' in legexc
495 canbundle2 = (not forcebundle1
498 canbundle2 = (not forcebundle1
496 and other.capable('getbundle')
499 and other.capable('getbundle')
497 and other.capable('bundle2'))
500 and other.capable('bundle2'))
498 if canbundle2:
501 if canbundle2:
499 kwargs = {}
502 kwargs = {}
500 kwargs['common'] = common
503 kwargs['common'] = common
501 kwargs['heads'] = rheads
504 kwargs['heads'] = rheads
502 kwargs['bundlecaps'] = exchange.caps20to10(repo)
505 kwargs['bundlecaps'] = exchange.caps20to10(repo)
503 kwargs['cg'] = True
506 kwargs['cg'] = True
504 b2 = other.getbundle('incoming', **kwargs)
507 b2 = other.getbundle('incoming', **kwargs)
505 fname = bundle = changegroup.writechunks(ui, b2._forwardchunks(),
508 fname = bundle = changegroup.writechunks(ui, b2._forwardchunks(),
506 bundlename)
509 bundlename)
507 else:
510 else:
508 if other.capable('getbundle'):
511 if other.capable('getbundle'):
509 cg = other.getbundle('incoming', common=common, heads=rheads)
512 cg = other.getbundle('incoming', common=common, heads=rheads)
510 elif onlyheads is None and not other.capable('changegroupsubset'):
513 elif onlyheads is None and not other.capable('changegroupsubset'):
511 # compat with older servers when pulling all remote heads
514 # compat with older servers when pulling all remote heads
512 cg = other.changegroup(incoming, "incoming")
515 cg = other.changegroup(incoming, "incoming")
513 rheads = None
516 rheads = None
514 else:
517 else:
515 cg = other.changegroupsubset(incoming, rheads, 'incoming')
518 cg = other.changegroupsubset(incoming, rheads, 'incoming')
516 if localrepo:
519 if localrepo:
517 bundletype = "HG10BZ"
520 bundletype = "HG10BZ"
518 else:
521 else:
519 bundletype = "HG10UN"
522 bundletype = "HG10UN"
520 fname = bundle = bundle2.writebundle(ui, cg, bundlename,
523 fname = bundle = bundle2.writebundle(ui, cg, bundlename,
521 bundletype)
524 bundletype)
522 # keep written bundle?
525 # keep written bundle?
523 if bundlename:
526 if bundlename:
524 bundle = None
527 bundle = None
525 if not localrepo:
528 if not localrepo:
526 # use the created uncompressed bundlerepo
529 # use the created uncompressed bundlerepo
527 localrepo = bundlerepo = bundlerepository(repo.baseui, repo.root,
530 localrepo = bundlerepo = bundlerepository(repo.baseui, repo.root,
528 fname)
531 fname)
529 # this repo contains local and other now, so filter out local again
532 # this repo contains local and other now, so filter out local again
530 common = repo.heads()
533 common = repo.heads()
531 if localrepo:
534 if localrepo:
532 # Part of common may be remotely filtered
535 # Part of common may be remotely filtered
533 # So use an unfiltered version
536 # So use an unfiltered version
534 # The discovery process probably need cleanup to avoid that
537 # The discovery process probably need cleanup to avoid that
535 localrepo = localrepo.unfiltered()
538 localrepo = localrepo.unfiltered()
536
539
537 csets = localrepo.changelog.findmissing(common, rheads)
540 csets = localrepo.changelog.findmissing(common, rheads)
538
541
539 if bundlerepo:
542 if bundlerepo:
540 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev:]]
543 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev:]]
541 remotephases = other.listkeys('phases')
544 remotephases = other.listkeys('phases')
542
545
543 pullop = exchange.pulloperation(bundlerepo, other, heads=reponodes)
546 pullop = exchange.pulloperation(bundlerepo, other, heads=reponodes)
544 pullop.trmanager = bundletransactionmanager()
547 pullop.trmanager = bundletransactionmanager()
545 exchange._pullapplyphases(pullop, remotephases)
548 exchange._pullapplyphases(pullop, remotephases)
546
549
547 def cleanup():
550 def cleanup():
548 if bundlerepo:
551 if bundlerepo:
549 bundlerepo.close()
552 bundlerepo.close()
550 if bundle:
553 if bundle:
551 os.unlink(bundle)
554 os.unlink(bundle)
552 other.close()
555 other.close()
553
556
554 return (localrepo, csets, cleanup)
557 return (localrepo, csets, cleanup)
@@ -1,1833 +1,1948 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 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import collections
16 import collections
17 import errno
17 import errno
18 import hashlib
18 import hashlib
19 import os
19 import os
20 import struct
20 import struct
21 import zlib
21 import zlib
22
22
23 # import stuff from node for others to import from revlog
23 # import stuff from node for others to import from revlog
24 from .node import (
24 from .node import (
25 bin,
25 bin,
26 hex,
26 hex,
27 nullid,
27 nullid,
28 nullrev,
28 nullrev,
29 )
29 )
30 from .i18n import _
30 from .i18n import _
31 from . import (
31 from . import (
32 ancestor,
32 ancestor,
33 error,
33 error,
34 mdiff,
34 mdiff,
35 parsers,
35 parsers,
36 templatefilters,
36 templatefilters,
37 util,
37 util,
38 )
38 )
39
39
40 _pack = struct.pack
40 _pack = struct.pack
41 _unpack = struct.unpack
41 _unpack = struct.unpack
42 _compress = zlib.compress
42 _compress = zlib.compress
43 _decompress = zlib.decompress
43 _decompress = zlib.decompress
44
44
45 # revlog header flags
45 # revlog header flags
46 REVLOGV0 = 0
46 REVLOGV0 = 0
47 REVLOGNG = 1
47 REVLOGNG = 1
48 REVLOGNGINLINEDATA = (1 << 16)
48 REVLOGNGINLINEDATA = (1 << 16)
49 REVLOGGENERALDELTA = (1 << 17)
49 REVLOGGENERALDELTA = (1 << 17)
50 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
50 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
51 REVLOG_DEFAULT_FORMAT = REVLOGNG
51 REVLOG_DEFAULT_FORMAT = REVLOGNG
52 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
52 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
53 REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGGENERALDELTA
53 REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGGENERALDELTA
54
54
55 # revlog index flags
55 # revlog index flags
56 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
56 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
57 REVIDX_DEFAULT_FLAGS = 0
57 REVIDX_DEFAULT_FLAGS = 0
58 REVIDX_KNOWN_FLAGS = REVIDX_ISCENSORED
58 # stable order in which flags need to be processed and their processors applied
59 REVIDX_FLAGS_ORDER = [
60 REVIDX_ISCENSORED,
61 ]
62 REVIDX_KNOWN_FLAGS = util.bitsfrom(REVIDX_FLAGS_ORDER)
59
63
60 # max size of revlog with inline data
64 # max size of revlog with inline data
61 _maxinline = 131072
65 _maxinline = 131072
62 _chunksize = 1048576
66 _chunksize = 1048576
63
67
64 RevlogError = error.RevlogError
68 RevlogError = error.RevlogError
65 LookupError = error.LookupError
69 LookupError = error.LookupError
66 CensoredNodeError = error.CensoredNodeError
70 CensoredNodeError = error.CensoredNodeError
71 ProgrammingError = error.ProgrammingError
72
73 # Store flag processors (cf. 'addflagprocessor()' to register)
74 _flagprocessors = {
75 REVIDX_ISCENSORED: None,
76 }
77
78 def addflagprocessor(flag, processor):
79 """Register a flag processor on a revision data flag.
80
81 Invariant:
82 - Flags need to be defined in REVIDX_KNOWN_FLAGS and REVIDX_FLAGS_ORDER.
83 - Only one flag processor can be registered on a specific flag.
84 - flagprocessors must be 3-tuples of functions (read, write, raw) with the
85 following signatures:
86 - (read) f(self, text) -> newtext, bool
87 - (write) f(self, text) -> newtext, bool
88 - (raw) f(self, text) -> bool
89 The boolean returned by these transforms is used to determine whether
90 'newtext' can be used for hash integrity checking.
91
92 Note: The 'raw' transform is used for changegroup generation and in some
93 debug commands. In this case the transform only indicates whether the
94 contents can be used for hash integrity checks.
95 """
96 if not flag & REVIDX_KNOWN_FLAGS:
97 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
98 raise ProgrammingError(msg)
99 if flag not in REVIDX_FLAGS_ORDER:
100 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
101 raise ProgrammingError(msg)
102 if flag in _flagprocessors:
103 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
104 raise error.Abort(msg)
105 _flagprocessors[flag] = processor
67
106
68 def getoffset(q):
107 def getoffset(q):
69 return int(q >> 16)
108 return int(q >> 16)
70
109
71 def gettype(q):
110 def gettype(q):
72 return int(q & 0xFFFF)
111 return int(q & 0xFFFF)
73
112
74 def offset_type(offset, type):
113 def offset_type(offset, type):
75 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
114 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
76 raise ValueError('unknown revlog index flags')
115 raise ValueError('unknown revlog index flags')
77 return long(long(offset) << 16 | type)
116 return long(long(offset) << 16 | type)
78
117
79 _nullhash = hashlib.sha1(nullid)
118 _nullhash = hashlib.sha1(nullid)
80
119
81 def hash(text, p1, p2):
120 def hash(text, p1, p2):
82 """generate a hash from the given text and its parent hashes
121 """generate a hash from the given text and its parent hashes
83
122
84 This hash combines both the current file contents and its history
123 This hash combines both the current file contents and its history
85 in a manner that makes it easy to distinguish nodes with the same
124 in a manner that makes it easy to distinguish nodes with the same
86 content in the revision graph.
125 content in the revision graph.
87 """
126 """
88 # As of now, if one of the parent node is null, p2 is null
127 # As of now, if one of the parent node is null, p2 is null
89 if p2 == nullid:
128 if p2 == nullid:
90 # deep copy of a hash is faster than creating one
129 # deep copy of a hash is faster than creating one
91 s = _nullhash.copy()
130 s = _nullhash.copy()
92 s.update(p1)
131 s.update(p1)
93 else:
132 else:
94 # none of the parent nodes are nullid
133 # none of the parent nodes are nullid
95 l = [p1, p2]
134 l = [p1, p2]
96 l.sort()
135 l.sort()
97 s = hashlib.sha1(l[0])
136 s = hashlib.sha1(l[0])
98 s.update(l[1])
137 s.update(l[1])
99 s.update(text)
138 s.update(text)
100 return s.digest()
139 return s.digest()
101
140
102 def decompress(bin):
141 def decompress(bin):
103 """ decompress the given input """
142 """ decompress the given input """
104 if not bin:
143 if not bin:
105 return bin
144 return bin
106 t = bin[0]
145 t = bin[0]
107 if t == '\0':
146 if t == '\0':
108 return bin
147 return bin
109 if t == 'x':
148 if t == 'x':
110 try:
149 try:
111 return _decompress(bin)
150 return _decompress(bin)
112 except zlib.error as e:
151 except zlib.error as e:
113 raise RevlogError(_("revlog decompress error: %s") % str(e))
152 raise RevlogError(_("revlog decompress error: %s") % str(e))
114 if t == 'u':
153 if t == 'u':
115 return util.buffer(bin, 1)
154 return util.buffer(bin, 1)
116 raise RevlogError(_("unknown compression type %r") % t)
155 raise RevlogError(_("unknown compression type %r") % t)
117
156
118 # index v0:
157 # index v0:
119 # 4 bytes: offset
158 # 4 bytes: offset
120 # 4 bytes: compressed length
159 # 4 bytes: compressed length
121 # 4 bytes: base rev
160 # 4 bytes: base rev
122 # 4 bytes: link rev
161 # 4 bytes: link rev
123 # 20 bytes: parent 1 nodeid
162 # 20 bytes: parent 1 nodeid
124 # 20 bytes: parent 2 nodeid
163 # 20 bytes: parent 2 nodeid
125 # 20 bytes: nodeid
164 # 20 bytes: nodeid
126 indexformatv0 = ">4l20s20s20s"
165 indexformatv0 = ">4l20s20s20s"
127
166
128 class revlogoldio(object):
167 class revlogoldio(object):
129 def __init__(self):
168 def __init__(self):
130 self.size = struct.calcsize(indexformatv0)
169 self.size = struct.calcsize(indexformatv0)
131
170
132 def parseindex(self, data, inline):
171 def parseindex(self, data, inline):
133 s = self.size
172 s = self.size
134 index = []
173 index = []
135 nodemap = {nullid: nullrev}
174 nodemap = {nullid: nullrev}
136 n = off = 0
175 n = off = 0
137 l = len(data)
176 l = len(data)
138 while off + s <= l:
177 while off + s <= l:
139 cur = data[off:off + s]
178 cur = data[off:off + s]
140 off += s
179 off += s
141 e = _unpack(indexformatv0, cur)
180 e = _unpack(indexformatv0, cur)
142 # transform to revlogv1 format
181 # transform to revlogv1 format
143 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
182 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
144 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
183 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
145 index.append(e2)
184 index.append(e2)
146 nodemap[e[6]] = n
185 nodemap[e[6]] = n
147 n += 1
186 n += 1
148
187
149 # add the magic null revision at -1
188 # add the magic null revision at -1
150 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
189 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
151
190
152 return index, nodemap, None
191 return index, nodemap, None
153
192
154 def packentry(self, entry, node, version, rev):
193 def packentry(self, entry, node, version, rev):
155 if gettype(entry[0]):
194 if gettype(entry[0]):
156 raise RevlogError(_("index entry flags need RevlogNG"))
195 raise RevlogError(_("index entry flags need RevlogNG"))
157 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
196 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
158 node(entry[5]), node(entry[6]), entry[7])
197 node(entry[5]), node(entry[6]), entry[7])
159 return _pack(indexformatv0, *e2)
198 return _pack(indexformatv0, *e2)
160
199
161 # index ng:
200 # index ng:
162 # 6 bytes: offset
201 # 6 bytes: offset
163 # 2 bytes: flags
202 # 2 bytes: flags
164 # 4 bytes: compressed length
203 # 4 bytes: compressed length
165 # 4 bytes: uncompressed length
204 # 4 bytes: uncompressed length
166 # 4 bytes: base rev
205 # 4 bytes: base rev
167 # 4 bytes: link rev
206 # 4 bytes: link rev
168 # 4 bytes: parent 1 rev
207 # 4 bytes: parent 1 rev
169 # 4 bytes: parent 2 rev
208 # 4 bytes: parent 2 rev
170 # 32 bytes: nodeid
209 # 32 bytes: nodeid
171 indexformatng = ">Qiiiiii20s12x"
210 indexformatng = ">Qiiiiii20s12x"
172 versionformat = ">I"
211 versionformat = ">I"
173
212
174 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
213 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
175 # signed integer)
214 # signed integer)
176 _maxentrysize = 0x7fffffff
215 _maxentrysize = 0x7fffffff
177
216
178 class revlogio(object):
217 class revlogio(object):
179 def __init__(self):
218 def __init__(self):
180 self.size = struct.calcsize(indexformatng)
219 self.size = struct.calcsize(indexformatng)
181
220
182 def parseindex(self, data, inline):
221 def parseindex(self, data, inline):
183 # call the C implementation to parse the index data
222 # call the C implementation to parse the index data
184 index, cache = parsers.parse_index2(data, inline)
223 index, cache = parsers.parse_index2(data, inline)
185 return index, getattr(index, 'nodemap', None), cache
224 return index, getattr(index, 'nodemap', None), cache
186
225
187 def packentry(self, entry, node, version, rev):
226 def packentry(self, entry, node, version, rev):
188 p = _pack(indexformatng, *entry)
227 p = _pack(indexformatng, *entry)
189 if rev == 0:
228 if rev == 0:
190 p = _pack(versionformat, version) + p[4:]
229 p = _pack(versionformat, version) + p[4:]
191 return p
230 return p
192
231
193 class revlog(object):
232 class revlog(object):
194 """
233 """
195 the underlying revision storage object
234 the underlying revision storage object
196
235
197 A revlog consists of two parts, an index and the revision data.
236 A revlog consists of two parts, an index and the revision data.
198
237
199 The index is a file with a fixed record size containing
238 The index is a file with a fixed record size containing
200 information on each revision, including its nodeid (hash), the
239 information on each revision, including its nodeid (hash), the
201 nodeids of its parents, the position and offset of its data within
240 nodeids of its parents, the position and offset of its data within
202 the data file, and the revision it's based on. Finally, each entry
241 the data file, and the revision it's based on. Finally, each entry
203 contains a linkrev entry that can serve as a pointer to external
242 contains a linkrev entry that can serve as a pointer to external
204 data.
243 data.
205
244
206 The revision data itself is a linear collection of data chunks.
245 The revision data itself is a linear collection of data chunks.
207 Each chunk represents a revision and is usually represented as a
246 Each chunk represents a revision and is usually represented as a
208 delta against the previous chunk. To bound lookup time, runs of
247 delta against the previous chunk. To bound lookup time, runs of
209 deltas are limited to about 2 times the length of the original
248 deltas are limited to about 2 times the length of the original
210 version data. This makes retrieval of a version proportional to
249 version data. This makes retrieval of a version proportional to
211 its size, or O(1) relative to the number of revisions.
250 its size, or O(1) relative to the number of revisions.
212
251
213 Both pieces of the revlog are written to in an append-only
252 Both pieces of the revlog are written to in an append-only
214 fashion, which means we never need to rewrite a file to insert or
253 fashion, which means we never need to rewrite a file to insert or
215 remove data, and can use some simple techniques to avoid the need
254 remove data, and can use some simple techniques to avoid the need
216 for locking while reading.
255 for locking while reading.
217
256
218 If checkambig, indexfile is opened with checkambig=True at
257 If checkambig, indexfile is opened with checkambig=True at
219 writing, to avoid file stat ambiguity.
258 writing, to avoid file stat ambiguity.
220 """
259 """
221 def __init__(self, opener, indexfile, checkambig=False):
260 def __init__(self, opener, indexfile, checkambig=False):
222 """
261 """
223 create a revlog object
262 create a revlog object
224
263
225 opener is a function that abstracts the file opening operation
264 opener is a function that abstracts the file opening operation
226 and can be used to implement COW semantics or the like.
265 and can be used to implement COW semantics or the like.
227 """
266 """
228 self.indexfile = indexfile
267 self.indexfile = indexfile
229 self.datafile = indexfile[:-2] + ".d"
268 self.datafile = indexfile[:-2] + ".d"
230 self.opener = opener
269 self.opener = opener
231 # When True, indexfile is opened with checkambig=True at writing, to
270 # When True, indexfile is opened with checkambig=True at writing, to
232 # avoid file stat ambiguity.
271 # avoid file stat ambiguity.
233 self._checkambig = checkambig
272 self._checkambig = checkambig
234 # 3-tuple of (node, rev, text) for a raw revision.
273 # 3-tuple of (node, rev, text) for a raw revision.
235 self._cache = None
274 self._cache = None
236 # Maps rev to chain base rev.
275 # Maps rev to chain base rev.
237 self._chainbasecache = util.lrucachedict(100)
276 self._chainbasecache = util.lrucachedict(100)
238 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
277 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
239 self._chunkcache = (0, '')
278 self._chunkcache = (0, '')
240 # How much data to read and cache into the raw revlog data cache.
279 # How much data to read and cache into the raw revlog data cache.
241 self._chunkcachesize = 65536
280 self._chunkcachesize = 65536
242 self._maxchainlen = None
281 self._maxchainlen = None
243 self._aggressivemergedeltas = False
282 self._aggressivemergedeltas = False
244 self.index = []
283 self.index = []
245 # Mapping of partial identifiers to full nodes.
284 # Mapping of partial identifiers to full nodes.
246 self._pcache = {}
285 self._pcache = {}
247 # Mapping of revision integer to full node.
286 # Mapping of revision integer to full node.
248 self._nodecache = {nullid: nullrev}
287 self._nodecache = {nullid: nullrev}
249 self._nodepos = None
288 self._nodepos = None
250
289
251 v = REVLOG_DEFAULT_VERSION
290 v = REVLOG_DEFAULT_VERSION
252 opts = getattr(opener, 'options', None)
291 opts = getattr(opener, 'options', None)
253 if opts is not None:
292 if opts is not None:
254 if 'revlogv1' in opts:
293 if 'revlogv1' in opts:
255 if 'generaldelta' in opts:
294 if 'generaldelta' in opts:
256 v |= REVLOGGENERALDELTA
295 v |= REVLOGGENERALDELTA
257 else:
296 else:
258 v = 0
297 v = 0
259 if 'chunkcachesize' in opts:
298 if 'chunkcachesize' in opts:
260 self._chunkcachesize = opts['chunkcachesize']
299 self._chunkcachesize = opts['chunkcachesize']
261 if 'maxchainlen' in opts:
300 if 'maxchainlen' in opts:
262 self._maxchainlen = opts['maxchainlen']
301 self._maxchainlen = opts['maxchainlen']
263 if 'aggressivemergedeltas' in opts:
302 if 'aggressivemergedeltas' in opts:
264 self._aggressivemergedeltas = opts['aggressivemergedeltas']
303 self._aggressivemergedeltas = opts['aggressivemergedeltas']
265 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
304 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
266
305
267 if self._chunkcachesize <= 0:
306 if self._chunkcachesize <= 0:
268 raise RevlogError(_('revlog chunk cache size %r is not greater '
307 raise RevlogError(_('revlog chunk cache size %r is not greater '
269 'than 0') % self._chunkcachesize)
308 'than 0') % self._chunkcachesize)
270 elif self._chunkcachesize & (self._chunkcachesize - 1):
309 elif self._chunkcachesize & (self._chunkcachesize - 1):
271 raise RevlogError(_('revlog chunk cache size %r is not a power '
310 raise RevlogError(_('revlog chunk cache size %r is not a power '
272 'of 2') % self._chunkcachesize)
311 'of 2') % self._chunkcachesize)
273
312
274 indexdata = ''
313 indexdata = ''
275 self._initempty = True
314 self._initempty = True
276 try:
315 try:
277 f = self.opener(self.indexfile)
316 f = self.opener(self.indexfile)
278 indexdata = f.read()
317 indexdata = f.read()
279 f.close()
318 f.close()
280 if len(indexdata) > 0:
319 if len(indexdata) > 0:
281 v = struct.unpack(versionformat, indexdata[:4])[0]
320 v = struct.unpack(versionformat, indexdata[:4])[0]
282 self._initempty = False
321 self._initempty = False
283 except IOError as inst:
322 except IOError as inst:
284 if inst.errno != errno.ENOENT:
323 if inst.errno != errno.ENOENT:
285 raise
324 raise
286
325
287 self.version = v
326 self.version = v
288 self._inline = v & REVLOGNGINLINEDATA
327 self._inline = v & REVLOGNGINLINEDATA
289 self._generaldelta = v & REVLOGGENERALDELTA
328 self._generaldelta = v & REVLOGGENERALDELTA
290 flags = v & ~0xFFFF
329 flags = v & ~0xFFFF
291 fmt = v & 0xFFFF
330 fmt = v & 0xFFFF
292 if fmt == REVLOGV0 and flags:
331 if fmt == REVLOGV0 and flags:
293 raise RevlogError(_("index %s unknown flags %#04x for format v0")
332 raise RevlogError(_("index %s unknown flags %#04x for format v0")
294 % (self.indexfile, flags >> 16))
333 % (self.indexfile, flags >> 16))
295 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
334 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
296 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
335 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
297 % (self.indexfile, flags >> 16))
336 % (self.indexfile, flags >> 16))
298 elif fmt > REVLOGNG:
337 elif fmt > REVLOGNG:
299 raise RevlogError(_("index %s unknown format %d")
338 raise RevlogError(_("index %s unknown format %d")
300 % (self.indexfile, fmt))
339 % (self.indexfile, fmt))
301
340
302 self.storedeltachains = True
341 self.storedeltachains = True
303
342
304 self._io = revlogio()
343 self._io = revlogio()
305 if self.version == REVLOGV0:
344 if self.version == REVLOGV0:
306 self._io = revlogoldio()
345 self._io = revlogoldio()
307 try:
346 try:
308 d = self._io.parseindex(indexdata, self._inline)
347 d = self._io.parseindex(indexdata, self._inline)
309 except (ValueError, IndexError):
348 except (ValueError, IndexError):
310 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
349 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
311 self.index, nodemap, self._chunkcache = d
350 self.index, nodemap, self._chunkcache = d
312 if nodemap is not None:
351 if nodemap is not None:
313 self.nodemap = self._nodecache = nodemap
352 self.nodemap = self._nodecache = nodemap
314 if not self._chunkcache:
353 if not self._chunkcache:
315 self._chunkclear()
354 self._chunkclear()
316 # revnum -> (chain-length, sum-delta-length)
355 # revnum -> (chain-length, sum-delta-length)
317 self._chaininfocache = {}
356 self._chaininfocache = {}
318
357
319 def tip(self):
358 def tip(self):
320 return self.node(len(self.index) - 2)
359 return self.node(len(self.index) - 2)
321 def __contains__(self, rev):
360 def __contains__(self, rev):
322 return 0 <= rev < len(self)
361 return 0 <= rev < len(self)
323 def __len__(self):
362 def __len__(self):
324 return len(self.index) - 1
363 return len(self.index) - 1
325 def __iter__(self):
364 def __iter__(self):
326 return iter(xrange(len(self)))
365 return iter(xrange(len(self)))
327 def revs(self, start=0, stop=None):
366 def revs(self, start=0, stop=None):
328 """iterate over all rev in this revlog (from start to stop)"""
367 """iterate over all rev in this revlog (from start to stop)"""
329 step = 1
368 step = 1
330 if stop is not None:
369 if stop is not None:
331 if start > stop:
370 if start > stop:
332 step = -1
371 step = -1
333 stop += step
372 stop += step
334 else:
373 else:
335 stop = len(self)
374 stop = len(self)
336 return xrange(start, stop, step)
375 return xrange(start, stop, step)
337
376
338 @util.propertycache
377 @util.propertycache
339 def nodemap(self):
378 def nodemap(self):
340 self.rev(self.node(0))
379 self.rev(self.node(0))
341 return self._nodecache
380 return self._nodecache
342
381
343 def hasnode(self, node):
382 def hasnode(self, node):
344 try:
383 try:
345 self.rev(node)
384 self.rev(node)
346 return True
385 return True
347 except KeyError:
386 except KeyError:
348 return False
387 return False
349
388
350 def clearcaches(self):
389 def clearcaches(self):
351 self._cache = None
390 self._cache = None
352 self._chainbasecache.clear()
391 self._chainbasecache.clear()
353 self._chunkcache = (0, '')
392 self._chunkcache = (0, '')
354 self._pcache = {}
393 self._pcache = {}
355
394
356 try:
395 try:
357 self._nodecache.clearcaches()
396 self._nodecache.clearcaches()
358 except AttributeError:
397 except AttributeError:
359 self._nodecache = {nullid: nullrev}
398 self._nodecache = {nullid: nullrev}
360 self._nodepos = None
399 self._nodepos = None
361
400
362 def rev(self, node):
401 def rev(self, node):
363 try:
402 try:
364 return self._nodecache[node]
403 return self._nodecache[node]
365 except TypeError:
404 except TypeError:
366 raise
405 raise
367 except RevlogError:
406 except RevlogError:
368 # parsers.c radix tree lookup failed
407 # parsers.c radix tree lookup failed
369 raise LookupError(node, self.indexfile, _('no node'))
408 raise LookupError(node, self.indexfile, _('no node'))
370 except KeyError:
409 except KeyError:
371 # pure python cache lookup failed
410 # pure python cache lookup failed
372 n = self._nodecache
411 n = self._nodecache
373 i = self.index
412 i = self.index
374 p = self._nodepos
413 p = self._nodepos
375 if p is None:
414 if p is None:
376 p = len(i) - 2
415 p = len(i) - 2
377 for r in xrange(p, -1, -1):
416 for r in xrange(p, -1, -1):
378 v = i[r][7]
417 v = i[r][7]
379 n[v] = r
418 n[v] = r
380 if v == node:
419 if v == node:
381 self._nodepos = r - 1
420 self._nodepos = r - 1
382 return r
421 return r
383 raise LookupError(node, self.indexfile, _('no node'))
422 raise LookupError(node, self.indexfile, _('no node'))
384
423
385 # Accessors for index entries.
424 # Accessors for index entries.
386
425
387 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
426 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
388 # are flags.
427 # are flags.
389 def start(self, rev):
428 def start(self, rev):
390 return int(self.index[rev][0] >> 16)
429 return int(self.index[rev][0] >> 16)
391
430
392 def flags(self, rev):
431 def flags(self, rev):
393 return self.index[rev][0] & 0xFFFF
432 return self.index[rev][0] & 0xFFFF
394
433
395 def length(self, rev):
434 def length(self, rev):
396 return self.index[rev][1]
435 return self.index[rev][1]
397
436
398 def rawsize(self, rev):
437 def rawsize(self, rev):
399 """return the length of the uncompressed text for a given revision"""
438 """return the length of the uncompressed text for a given revision"""
400 l = self.index[rev][2]
439 l = self.index[rev][2]
401 if l >= 0:
440 if l >= 0:
402 return l
441 return l
403
442
404 t = self.revision(self.node(rev))
443 t = self.revision(self.node(rev))
405 return len(t)
444 return len(t)
406 size = rawsize
445 size = rawsize
407
446
408 def chainbase(self, rev):
447 def chainbase(self, rev):
409 base = self._chainbasecache.get(rev)
448 base = self._chainbasecache.get(rev)
410 if base is not None:
449 if base is not None:
411 return base
450 return base
412
451
413 index = self.index
452 index = self.index
414 base = index[rev][3]
453 base = index[rev][3]
415 while base != rev:
454 while base != rev:
416 rev = base
455 rev = base
417 base = index[rev][3]
456 base = index[rev][3]
418
457
419 self._chainbasecache[rev] = base
458 self._chainbasecache[rev] = base
420 return base
459 return base
421
460
422 def linkrev(self, rev):
461 def linkrev(self, rev):
423 return self.index[rev][4]
462 return self.index[rev][4]
424
463
425 def parentrevs(self, rev):
464 def parentrevs(self, rev):
426 return self.index[rev][5:7]
465 return self.index[rev][5:7]
427
466
428 def node(self, rev):
467 def node(self, rev):
429 return self.index[rev][7]
468 return self.index[rev][7]
430
469
431 # Derived from index values.
470 # Derived from index values.
432
471
433 def end(self, rev):
472 def end(self, rev):
434 return self.start(rev) + self.length(rev)
473 return self.start(rev) + self.length(rev)
435
474
436 def parents(self, node):
475 def parents(self, node):
437 i = self.index
476 i = self.index
438 d = i[self.rev(node)]
477 d = i[self.rev(node)]
439 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
478 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
440
479
441 def chainlen(self, rev):
480 def chainlen(self, rev):
442 return self._chaininfo(rev)[0]
481 return self._chaininfo(rev)[0]
443
482
444 def _chaininfo(self, rev):
483 def _chaininfo(self, rev):
445 chaininfocache = self._chaininfocache
484 chaininfocache = self._chaininfocache
446 if rev in chaininfocache:
485 if rev in chaininfocache:
447 return chaininfocache[rev]
486 return chaininfocache[rev]
448 index = self.index
487 index = self.index
449 generaldelta = self._generaldelta
488 generaldelta = self._generaldelta
450 iterrev = rev
489 iterrev = rev
451 e = index[iterrev]
490 e = index[iterrev]
452 clen = 0
491 clen = 0
453 compresseddeltalen = 0
492 compresseddeltalen = 0
454 while iterrev != e[3]:
493 while iterrev != e[3]:
455 clen += 1
494 clen += 1
456 compresseddeltalen += e[1]
495 compresseddeltalen += e[1]
457 if generaldelta:
496 if generaldelta:
458 iterrev = e[3]
497 iterrev = e[3]
459 else:
498 else:
460 iterrev -= 1
499 iterrev -= 1
461 if iterrev in chaininfocache:
500 if iterrev in chaininfocache:
462 t = chaininfocache[iterrev]
501 t = chaininfocache[iterrev]
463 clen += t[0]
502 clen += t[0]
464 compresseddeltalen += t[1]
503 compresseddeltalen += t[1]
465 break
504 break
466 e = index[iterrev]
505 e = index[iterrev]
467 else:
506 else:
468 # Add text length of base since decompressing that also takes
507 # Add text length of base since decompressing that also takes
469 # work. For cache hits the length is already included.
508 # work. For cache hits the length is already included.
470 compresseddeltalen += e[1]
509 compresseddeltalen += e[1]
471 r = (clen, compresseddeltalen)
510 r = (clen, compresseddeltalen)
472 chaininfocache[rev] = r
511 chaininfocache[rev] = r
473 return r
512 return r
474
513
475 def _deltachain(self, rev, stoprev=None):
514 def _deltachain(self, rev, stoprev=None):
476 """Obtain the delta chain for a revision.
515 """Obtain the delta chain for a revision.
477
516
478 ``stoprev`` specifies a revision to stop at. If not specified, we
517 ``stoprev`` specifies a revision to stop at. If not specified, we
479 stop at the base of the chain.
518 stop at the base of the chain.
480
519
481 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
520 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
482 revs in ascending order and ``stopped`` is a bool indicating whether
521 revs in ascending order and ``stopped`` is a bool indicating whether
483 ``stoprev`` was hit.
522 ``stoprev`` was hit.
484 """
523 """
485 chain = []
524 chain = []
486
525
487 # Alias to prevent attribute lookup in tight loop.
526 # Alias to prevent attribute lookup in tight loop.
488 index = self.index
527 index = self.index
489 generaldelta = self._generaldelta
528 generaldelta = self._generaldelta
490
529
491 iterrev = rev
530 iterrev = rev
492 e = index[iterrev]
531 e = index[iterrev]
493 while iterrev != e[3] and iterrev != stoprev:
532 while iterrev != e[3] and iterrev != stoprev:
494 chain.append(iterrev)
533 chain.append(iterrev)
495 if generaldelta:
534 if generaldelta:
496 iterrev = e[3]
535 iterrev = e[3]
497 else:
536 else:
498 iterrev -= 1
537 iterrev -= 1
499 e = index[iterrev]
538 e = index[iterrev]
500
539
501 if iterrev == stoprev:
540 if iterrev == stoprev:
502 stopped = True
541 stopped = True
503 else:
542 else:
504 chain.append(iterrev)
543 chain.append(iterrev)
505 stopped = False
544 stopped = False
506
545
507 chain.reverse()
546 chain.reverse()
508 return chain, stopped
547 return chain, stopped
509
548
510 def ancestors(self, revs, stoprev=0, inclusive=False):
549 def ancestors(self, revs, stoprev=0, inclusive=False):
511 """Generate the ancestors of 'revs' in reverse topological order.
550 """Generate the ancestors of 'revs' in reverse topological order.
512 Does not generate revs lower than stoprev.
551 Does not generate revs lower than stoprev.
513
552
514 See the documentation for ancestor.lazyancestors for more details."""
553 See the documentation for ancestor.lazyancestors for more details."""
515
554
516 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
555 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
517 inclusive=inclusive)
556 inclusive=inclusive)
518
557
519 def descendants(self, revs):
558 def descendants(self, revs):
520 """Generate the descendants of 'revs' in revision order.
559 """Generate the descendants of 'revs' in revision order.
521
560
522 Yield a sequence of revision numbers starting with a child of
561 Yield a sequence of revision numbers starting with a child of
523 some rev in revs, i.e., each revision is *not* considered a
562 some rev in revs, i.e., each revision is *not* considered a
524 descendant of itself. Results are ordered by revision number (a
563 descendant of itself. Results are ordered by revision number (a
525 topological sort)."""
564 topological sort)."""
526 first = min(revs)
565 first = min(revs)
527 if first == nullrev:
566 if first == nullrev:
528 for i in self:
567 for i in self:
529 yield i
568 yield i
530 return
569 return
531
570
532 seen = set(revs)
571 seen = set(revs)
533 for i in self.revs(start=first + 1):
572 for i in self.revs(start=first + 1):
534 for x in self.parentrevs(i):
573 for x in self.parentrevs(i):
535 if x != nullrev and x in seen:
574 if x != nullrev and x in seen:
536 seen.add(i)
575 seen.add(i)
537 yield i
576 yield i
538 break
577 break
539
578
540 def findcommonmissing(self, common=None, heads=None):
579 def findcommonmissing(self, common=None, heads=None):
541 """Return a tuple of the ancestors of common and the ancestors of heads
580 """Return a tuple of the ancestors of common and the ancestors of heads
542 that are not ancestors of common. In revset terminology, we return the
581 that are not ancestors of common. In revset terminology, we return the
543 tuple:
582 tuple:
544
583
545 ::common, (::heads) - (::common)
584 ::common, (::heads) - (::common)
546
585
547 The list is sorted by revision number, meaning it is
586 The list is sorted by revision number, meaning it is
548 topologically sorted.
587 topologically sorted.
549
588
550 'heads' and 'common' are both lists of node IDs. If heads is
589 'heads' and 'common' are both lists of node IDs. If heads is
551 not supplied, uses all of the revlog's heads. If common is not
590 not supplied, uses all of the revlog's heads. If common is not
552 supplied, uses nullid."""
591 supplied, uses nullid."""
553 if common is None:
592 if common is None:
554 common = [nullid]
593 common = [nullid]
555 if heads is None:
594 if heads is None:
556 heads = self.heads()
595 heads = self.heads()
557
596
558 common = [self.rev(n) for n in common]
597 common = [self.rev(n) for n in common]
559 heads = [self.rev(n) for n in heads]
598 heads = [self.rev(n) for n in heads]
560
599
561 # we want the ancestors, but inclusive
600 # we want the ancestors, but inclusive
562 class lazyset(object):
601 class lazyset(object):
563 def __init__(self, lazyvalues):
602 def __init__(self, lazyvalues):
564 self.addedvalues = set()
603 self.addedvalues = set()
565 self.lazyvalues = lazyvalues
604 self.lazyvalues = lazyvalues
566
605
567 def __contains__(self, value):
606 def __contains__(self, value):
568 return value in self.addedvalues or value in self.lazyvalues
607 return value in self.addedvalues or value in self.lazyvalues
569
608
570 def __iter__(self):
609 def __iter__(self):
571 added = self.addedvalues
610 added = self.addedvalues
572 for r in added:
611 for r in added:
573 yield r
612 yield r
574 for r in self.lazyvalues:
613 for r in self.lazyvalues:
575 if not r in added:
614 if not r in added:
576 yield r
615 yield r
577
616
578 def add(self, value):
617 def add(self, value):
579 self.addedvalues.add(value)
618 self.addedvalues.add(value)
580
619
581 def update(self, values):
620 def update(self, values):
582 self.addedvalues.update(values)
621 self.addedvalues.update(values)
583
622
584 has = lazyset(self.ancestors(common))
623 has = lazyset(self.ancestors(common))
585 has.add(nullrev)
624 has.add(nullrev)
586 has.update(common)
625 has.update(common)
587
626
588 # take all ancestors from heads that aren't in has
627 # take all ancestors from heads that aren't in has
589 missing = set()
628 missing = set()
590 visit = collections.deque(r for r in heads if r not in has)
629 visit = collections.deque(r for r in heads if r not in has)
591 while visit:
630 while visit:
592 r = visit.popleft()
631 r = visit.popleft()
593 if r in missing:
632 if r in missing:
594 continue
633 continue
595 else:
634 else:
596 missing.add(r)
635 missing.add(r)
597 for p in self.parentrevs(r):
636 for p in self.parentrevs(r):
598 if p not in has:
637 if p not in has:
599 visit.append(p)
638 visit.append(p)
600 missing = list(missing)
639 missing = list(missing)
601 missing.sort()
640 missing.sort()
602 return has, [self.node(miss) for miss in missing]
641 return has, [self.node(miss) for miss in missing]
603
642
604 def incrementalmissingrevs(self, common=None):
643 def incrementalmissingrevs(self, common=None):
605 """Return an object that can be used to incrementally compute the
644 """Return an object that can be used to incrementally compute the
606 revision numbers of the ancestors of arbitrary sets that are not
645 revision numbers of the ancestors of arbitrary sets that are not
607 ancestors of common. This is an ancestor.incrementalmissingancestors
646 ancestors of common. This is an ancestor.incrementalmissingancestors
608 object.
647 object.
609
648
610 'common' is a list of revision numbers. If common is not supplied, uses
649 'common' is a list of revision numbers. If common is not supplied, uses
611 nullrev.
650 nullrev.
612 """
651 """
613 if common is None:
652 if common is None:
614 common = [nullrev]
653 common = [nullrev]
615
654
616 return ancestor.incrementalmissingancestors(self.parentrevs, common)
655 return ancestor.incrementalmissingancestors(self.parentrevs, common)
617
656
618 def findmissingrevs(self, common=None, heads=None):
657 def findmissingrevs(self, common=None, heads=None):
619 """Return the revision numbers of the ancestors of heads that
658 """Return the revision numbers of the ancestors of heads that
620 are not ancestors of common.
659 are not ancestors of common.
621
660
622 More specifically, return a list of revision numbers corresponding to
661 More specifically, return a list of revision numbers corresponding to
623 nodes N such that every N satisfies the following constraints:
662 nodes N such that every N satisfies the following constraints:
624
663
625 1. N is an ancestor of some node in 'heads'
664 1. N is an ancestor of some node in 'heads'
626 2. N is not an ancestor of any node in 'common'
665 2. N is not an ancestor of any node in 'common'
627
666
628 The list is sorted by revision number, meaning it is
667 The list is sorted by revision number, meaning it is
629 topologically sorted.
668 topologically sorted.
630
669
631 'heads' and 'common' are both lists of revision numbers. If heads is
670 'heads' and 'common' are both lists of revision numbers. If heads is
632 not supplied, uses all of the revlog's heads. If common is not
671 not supplied, uses all of the revlog's heads. If common is not
633 supplied, uses nullid."""
672 supplied, uses nullid."""
634 if common is None:
673 if common is None:
635 common = [nullrev]
674 common = [nullrev]
636 if heads is None:
675 if heads is None:
637 heads = self.headrevs()
676 heads = self.headrevs()
638
677
639 inc = self.incrementalmissingrevs(common=common)
678 inc = self.incrementalmissingrevs(common=common)
640 return inc.missingancestors(heads)
679 return inc.missingancestors(heads)
641
680
642 def findmissing(self, common=None, heads=None):
681 def findmissing(self, common=None, heads=None):
643 """Return the ancestors of heads that are not ancestors of common.
682 """Return the ancestors of heads that are not ancestors of common.
644
683
645 More specifically, return a list of nodes N such that every N
684 More specifically, return a list of nodes N such that every N
646 satisfies the following constraints:
685 satisfies the following constraints:
647
686
648 1. N is an ancestor of some node in 'heads'
687 1. N is an ancestor of some node in 'heads'
649 2. N is not an ancestor of any node in 'common'
688 2. N is not an ancestor of any node in 'common'
650
689
651 The list is sorted by revision number, meaning it is
690 The list is sorted by revision number, meaning it is
652 topologically sorted.
691 topologically sorted.
653
692
654 'heads' and 'common' are both lists of node IDs. If heads is
693 'heads' and 'common' are both lists of node IDs. If heads is
655 not supplied, uses all of the revlog's heads. If common is not
694 not supplied, uses all of the revlog's heads. If common is not
656 supplied, uses nullid."""
695 supplied, uses nullid."""
657 if common is None:
696 if common is None:
658 common = [nullid]
697 common = [nullid]
659 if heads is None:
698 if heads is None:
660 heads = self.heads()
699 heads = self.heads()
661
700
662 common = [self.rev(n) for n in common]
701 common = [self.rev(n) for n in common]
663 heads = [self.rev(n) for n in heads]
702 heads = [self.rev(n) for n in heads]
664
703
665 inc = self.incrementalmissingrevs(common=common)
704 inc = self.incrementalmissingrevs(common=common)
666 return [self.node(r) for r in inc.missingancestors(heads)]
705 return [self.node(r) for r in inc.missingancestors(heads)]
667
706
668 def nodesbetween(self, roots=None, heads=None):
707 def nodesbetween(self, roots=None, heads=None):
669 """Return a topological path from 'roots' to 'heads'.
708 """Return a topological path from 'roots' to 'heads'.
670
709
671 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
710 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
672 topologically sorted list of all nodes N that satisfy both of
711 topologically sorted list of all nodes N that satisfy both of
673 these constraints:
712 these constraints:
674
713
675 1. N is a descendant of some node in 'roots'
714 1. N is a descendant of some node in 'roots'
676 2. N is an ancestor of some node in 'heads'
715 2. N is an ancestor of some node in 'heads'
677
716
678 Every node is considered to be both a descendant and an ancestor
717 Every node is considered to be both a descendant and an ancestor
679 of itself, so every reachable node in 'roots' and 'heads' will be
718 of itself, so every reachable node in 'roots' and 'heads' will be
680 included in 'nodes'.
719 included in 'nodes'.
681
720
682 'outroots' is the list of reachable nodes in 'roots', i.e., the
721 'outroots' is the list of reachable nodes in 'roots', i.e., the
683 subset of 'roots' that is returned in 'nodes'. Likewise,
722 subset of 'roots' that is returned in 'nodes'. Likewise,
684 'outheads' is the subset of 'heads' that is also in 'nodes'.
723 'outheads' is the subset of 'heads' that is also in 'nodes'.
685
724
686 'roots' and 'heads' are both lists of node IDs. If 'roots' is
725 'roots' and 'heads' are both lists of node IDs. If 'roots' is
687 unspecified, uses nullid as the only root. If 'heads' is
726 unspecified, uses nullid as the only root. If 'heads' is
688 unspecified, uses list of all of the revlog's heads."""
727 unspecified, uses list of all of the revlog's heads."""
689 nonodes = ([], [], [])
728 nonodes = ([], [], [])
690 if roots is not None:
729 if roots is not None:
691 roots = list(roots)
730 roots = list(roots)
692 if not roots:
731 if not roots:
693 return nonodes
732 return nonodes
694 lowestrev = min([self.rev(n) for n in roots])
733 lowestrev = min([self.rev(n) for n in roots])
695 else:
734 else:
696 roots = [nullid] # Everybody's a descendant of nullid
735 roots = [nullid] # Everybody's a descendant of nullid
697 lowestrev = nullrev
736 lowestrev = nullrev
698 if (lowestrev == nullrev) and (heads is None):
737 if (lowestrev == nullrev) and (heads is None):
699 # We want _all_ the nodes!
738 # We want _all_ the nodes!
700 return ([self.node(r) for r in self], [nullid], list(self.heads()))
739 return ([self.node(r) for r in self], [nullid], list(self.heads()))
701 if heads is None:
740 if heads is None:
702 # All nodes are ancestors, so the latest ancestor is the last
741 # All nodes are ancestors, so the latest ancestor is the last
703 # node.
742 # node.
704 highestrev = len(self) - 1
743 highestrev = len(self) - 1
705 # Set ancestors to None to signal that every node is an ancestor.
744 # Set ancestors to None to signal that every node is an ancestor.
706 ancestors = None
745 ancestors = None
707 # Set heads to an empty dictionary for later discovery of heads
746 # Set heads to an empty dictionary for later discovery of heads
708 heads = {}
747 heads = {}
709 else:
748 else:
710 heads = list(heads)
749 heads = list(heads)
711 if not heads:
750 if not heads:
712 return nonodes
751 return nonodes
713 ancestors = set()
752 ancestors = set()
714 # Turn heads into a dictionary so we can remove 'fake' heads.
753 # Turn heads into a dictionary so we can remove 'fake' heads.
715 # Also, later we will be using it to filter out the heads we can't
754 # Also, later we will be using it to filter out the heads we can't
716 # find from roots.
755 # find from roots.
717 heads = dict.fromkeys(heads, False)
756 heads = dict.fromkeys(heads, False)
718 # Start at the top and keep marking parents until we're done.
757 # Start at the top and keep marking parents until we're done.
719 nodestotag = set(heads)
758 nodestotag = set(heads)
720 # Remember where the top was so we can use it as a limit later.
759 # Remember where the top was so we can use it as a limit later.
721 highestrev = max([self.rev(n) for n in nodestotag])
760 highestrev = max([self.rev(n) for n in nodestotag])
722 while nodestotag:
761 while nodestotag:
723 # grab a node to tag
762 # grab a node to tag
724 n = nodestotag.pop()
763 n = nodestotag.pop()
725 # Never tag nullid
764 # Never tag nullid
726 if n == nullid:
765 if n == nullid:
727 continue
766 continue
728 # A node's revision number represents its place in a
767 # A node's revision number represents its place in a
729 # topologically sorted list of nodes.
768 # topologically sorted list of nodes.
730 r = self.rev(n)
769 r = self.rev(n)
731 if r >= lowestrev:
770 if r >= lowestrev:
732 if n not in ancestors:
771 if n not in ancestors:
733 # If we are possibly a descendant of one of the roots
772 # If we are possibly a descendant of one of the roots
734 # and we haven't already been marked as an ancestor
773 # and we haven't already been marked as an ancestor
735 ancestors.add(n) # Mark as ancestor
774 ancestors.add(n) # Mark as ancestor
736 # Add non-nullid parents to list of nodes to tag.
775 # Add non-nullid parents to list of nodes to tag.
737 nodestotag.update([p for p in self.parents(n) if
776 nodestotag.update([p for p in self.parents(n) if
738 p != nullid])
777 p != nullid])
739 elif n in heads: # We've seen it before, is it a fake head?
778 elif n in heads: # We've seen it before, is it a fake head?
740 # So it is, real heads should not be the ancestors of
779 # So it is, real heads should not be the ancestors of
741 # any other heads.
780 # any other heads.
742 heads.pop(n)
781 heads.pop(n)
743 if not ancestors:
782 if not ancestors:
744 return nonodes
783 return nonodes
745 # Now that we have our set of ancestors, we want to remove any
784 # Now that we have our set of ancestors, we want to remove any
746 # roots that are not ancestors.
785 # roots that are not ancestors.
747
786
748 # If one of the roots was nullid, everything is included anyway.
787 # If one of the roots was nullid, everything is included anyway.
749 if lowestrev > nullrev:
788 if lowestrev > nullrev:
750 # But, since we weren't, let's recompute the lowest rev to not
789 # But, since we weren't, let's recompute the lowest rev to not
751 # include roots that aren't ancestors.
790 # include roots that aren't ancestors.
752
791
753 # Filter out roots that aren't ancestors of heads
792 # Filter out roots that aren't ancestors of heads
754 roots = [root for root in roots if root in ancestors]
793 roots = [root for root in roots if root in ancestors]
755 # Recompute the lowest revision
794 # Recompute the lowest revision
756 if roots:
795 if roots:
757 lowestrev = min([self.rev(root) for root in roots])
796 lowestrev = min([self.rev(root) for root in roots])
758 else:
797 else:
759 # No more roots? Return empty list
798 # No more roots? Return empty list
760 return nonodes
799 return nonodes
761 else:
800 else:
762 # We are descending from nullid, and don't need to care about
801 # We are descending from nullid, and don't need to care about
763 # any other roots.
802 # any other roots.
764 lowestrev = nullrev
803 lowestrev = nullrev
765 roots = [nullid]
804 roots = [nullid]
766 # Transform our roots list into a set.
805 # Transform our roots list into a set.
767 descendants = set(roots)
806 descendants = set(roots)
768 # Also, keep the original roots so we can filter out roots that aren't
807 # Also, keep the original roots so we can filter out roots that aren't
769 # 'real' roots (i.e. are descended from other roots).
808 # 'real' roots (i.e. are descended from other roots).
770 roots = descendants.copy()
809 roots = descendants.copy()
771 # Our topologically sorted list of output nodes.
810 # Our topologically sorted list of output nodes.
772 orderedout = []
811 orderedout = []
773 # Don't start at nullid since we don't want nullid in our output list,
812 # Don't start at nullid since we don't want nullid in our output list,
774 # and if nullid shows up in descendants, empty parents will look like
813 # and if nullid shows up in descendants, empty parents will look like
775 # they're descendants.
814 # they're descendants.
776 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
815 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
777 n = self.node(r)
816 n = self.node(r)
778 isdescendant = False
817 isdescendant = False
779 if lowestrev == nullrev: # Everybody is a descendant of nullid
818 if lowestrev == nullrev: # Everybody is a descendant of nullid
780 isdescendant = True
819 isdescendant = True
781 elif n in descendants:
820 elif n in descendants:
782 # n is already a descendant
821 # n is already a descendant
783 isdescendant = True
822 isdescendant = True
784 # This check only needs to be done here because all the roots
823 # This check only needs to be done here because all the roots
785 # will start being marked is descendants before the loop.
824 # will start being marked is descendants before the loop.
786 if n in roots:
825 if n in roots:
787 # If n was a root, check if it's a 'real' root.
826 # If n was a root, check if it's a 'real' root.
788 p = tuple(self.parents(n))
827 p = tuple(self.parents(n))
789 # If any of its parents are descendants, it's not a root.
828 # If any of its parents are descendants, it's not a root.
790 if (p[0] in descendants) or (p[1] in descendants):
829 if (p[0] in descendants) or (p[1] in descendants):
791 roots.remove(n)
830 roots.remove(n)
792 else:
831 else:
793 p = tuple(self.parents(n))
832 p = tuple(self.parents(n))
794 # A node is a descendant if either of its parents are
833 # A node is a descendant if either of its parents are
795 # descendants. (We seeded the dependents list with the roots
834 # descendants. (We seeded the dependents list with the roots
796 # up there, remember?)
835 # up there, remember?)
797 if (p[0] in descendants) or (p[1] in descendants):
836 if (p[0] in descendants) or (p[1] in descendants):
798 descendants.add(n)
837 descendants.add(n)
799 isdescendant = True
838 isdescendant = True
800 if isdescendant and ((ancestors is None) or (n in ancestors)):
839 if isdescendant and ((ancestors is None) or (n in ancestors)):
801 # Only include nodes that are both descendants and ancestors.
840 # Only include nodes that are both descendants and ancestors.
802 orderedout.append(n)
841 orderedout.append(n)
803 if (ancestors is not None) and (n in heads):
842 if (ancestors is not None) and (n in heads):
804 # We're trying to figure out which heads are reachable
843 # We're trying to figure out which heads are reachable
805 # from roots.
844 # from roots.
806 # Mark this head as having been reached
845 # Mark this head as having been reached
807 heads[n] = True
846 heads[n] = True
808 elif ancestors is None:
847 elif ancestors is None:
809 # Otherwise, we're trying to discover the heads.
848 # Otherwise, we're trying to discover the heads.
810 # Assume this is a head because if it isn't, the next step
849 # Assume this is a head because if it isn't, the next step
811 # will eventually remove it.
850 # will eventually remove it.
812 heads[n] = True
851 heads[n] = True
813 # But, obviously its parents aren't.
852 # But, obviously its parents aren't.
814 for p in self.parents(n):
853 for p in self.parents(n):
815 heads.pop(p, None)
854 heads.pop(p, None)
816 heads = [head for head, flag in heads.iteritems() if flag]
855 heads = [head for head, flag in heads.iteritems() if flag]
817 roots = list(roots)
856 roots = list(roots)
818 assert orderedout
857 assert orderedout
819 assert roots
858 assert roots
820 assert heads
859 assert heads
821 return (orderedout, roots, heads)
860 return (orderedout, roots, heads)
822
861
823 def headrevs(self):
862 def headrevs(self):
824 try:
863 try:
825 return self.index.headrevs()
864 return self.index.headrevs()
826 except AttributeError:
865 except AttributeError:
827 return self._headrevs()
866 return self._headrevs()
828
867
829 def computephases(self, roots):
868 def computephases(self, roots):
830 return self.index.computephasesmapsets(roots)
869 return self.index.computephasesmapsets(roots)
831
870
832 def _headrevs(self):
871 def _headrevs(self):
833 count = len(self)
872 count = len(self)
834 if not count:
873 if not count:
835 return [nullrev]
874 return [nullrev]
836 # we won't iter over filtered rev so nobody is a head at start
875 # we won't iter over filtered rev so nobody is a head at start
837 ishead = [0] * (count + 1)
876 ishead = [0] * (count + 1)
838 index = self.index
877 index = self.index
839 for r in self:
878 for r in self:
840 ishead[r] = 1 # I may be an head
879 ishead[r] = 1 # I may be an head
841 e = index[r]
880 e = index[r]
842 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
881 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
843 return [r for r, val in enumerate(ishead) if val]
882 return [r for r, val in enumerate(ishead) if val]
844
883
845 def heads(self, start=None, stop=None):
884 def heads(self, start=None, stop=None):
846 """return the list of all nodes that have no children
885 """return the list of all nodes that have no children
847
886
848 if start is specified, only heads that are descendants of
887 if start is specified, only heads that are descendants of
849 start will be returned
888 start will be returned
850 if stop is specified, it will consider all the revs from stop
889 if stop is specified, it will consider all the revs from stop
851 as if they had no children
890 as if they had no children
852 """
891 """
853 if start is None and stop is None:
892 if start is None and stop is None:
854 if not len(self):
893 if not len(self):
855 return [nullid]
894 return [nullid]
856 return [self.node(r) for r in self.headrevs()]
895 return [self.node(r) for r in self.headrevs()]
857
896
858 if start is None:
897 if start is None:
859 start = nullid
898 start = nullid
860 if stop is None:
899 if stop is None:
861 stop = []
900 stop = []
862 stoprevs = set([self.rev(n) for n in stop])
901 stoprevs = set([self.rev(n) for n in stop])
863 startrev = self.rev(start)
902 startrev = self.rev(start)
864 reachable = set((startrev,))
903 reachable = set((startrev,))
865 heads = set((startrev,))
904 heads = set((startrev,))
866
905
867 parentrevs = self.parentrevs
906 parentrevs = self.parentrevs
868 for r in self.revs(start=startrev + 1):
907 for r in self.revs(start=startrev + 1):
869 for p in parentrevs(r):
908 for p in parentrevs(r):
870 if p in reachable:
909 if p in reachable:
871 if r not in stoprevs:
910 if r not in stoprevs:
872 reachable.add(r)
911 reachable.add(r)
873 heads.add(r)
912 heads.add(r)
874 if p in heads and p not in stoprevs:
913 if p in heads and p not in stoprevs:
875 heads.remove(p)
914 heads.remove(p)
876
915
877 return [self.node(r) for r in heads]
916 return [self.node(r) for r in heads]
878
917
879 def children(self, node):
918 def children(self, node):
880 """find the children of a given node"""
919 """find the children of a given node"""
881 c = []
920 c = []
882 p = self.rev(node)
921 p = self.rev(node)
883 for r in self.revs(start=p + 1):
922 for r in self.revs(start=p + 1):
884 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
923 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
885 if prevs:
924 if prevs:
886 for pr in prevs:
925 for pr in prevs:
887 if pr == p:
926 if pr == p:
888 c.append(self.node(r))
927 c.append(self.node(r))
889 elif p == nullrev:
928 elif p == nullrev:
890 c.append(self.node(r))
929 c.append(self.node(r))
891 return c
930 return c
892
931
893 def descendant(self, start, end):
932 def descendant(self, start, end):
894 if start == nullrev:
933 if start == nullrev:
895 return True
934 return True
896 for i in self.descendants([start]):
935 for i in self.descendants([start]):
897 if i == end:
936 if i == end:
898 return True
937 return True
899 elif i > end:
938 elif i > end:
900 break
939 break
901 return False
940 return False
902
941
903 def commonancestorsheads(self, a, b):
942 def commonancestorsheads(self, a, b):
904 """calculate all the heads of the common ancestors of nodes a and b"""
943 """calculate all the heads of the common ancestors of nodes a and b"""
905 a, b = self.rev(a), self.rev(b)
944 a, b = self.rev(a), self.rev(b)
906 try:
945 try:
907 ancs = self.index.commonancestorsheads(a, b)
946 ancs = self.index.commonancestorsheads(a, b)
908 except (AttributeError, OverflowError): # C implementation failed
947 except (AttributeError, OverflowError): # C implementation failed
909 ancs = ancestor.commonancestorsheads(self.parentrevs, a, b)
948 ancs = ancestor.commonancestorsheads(self.parentrevs, a, b)
910 return map(self.node, ancs)
949 return map(self.node, ancs)
911
950
912 def isancestor(self, a, b):
951 def isancestor(self, a, b):
913 """return True if node a is an ancestor of node b
952 """return True if node a is an ancestor of node b
914
953
915 The implementation of this is trivial but the use of
954 The implementation of this is trivial but the use of
916 commonancestorsheads is not."""
955 commonancestorsheads is not."""
917 return a in self.commonancestorsheads(a, b)
956 return a in self.commonancestorsheads(a, b)
918
957
919 def ancestor(self, a, b):
958 def ancestor(self, a, b):
920 """calculate the "best" common ancestor of nodes a and b"""
959 """calculate the "best" common ancestor of nodes a and b"""
921
960
922 a, b = self.rev(a), self.rev(b)
961 a, b = self.rev(a), self.rev(b)
923 try:
962 try:
924 ancs = self.index.ancestors(a, b)
963 ancs = self.index.ancestors(a, b)
925 except (AttributeError, OverflowError):
964 except (AttributeError, OverflowError):
926 ancs = ancestor.ancestors(self.parentrevs, a, b)
965 ancs = ancestor.ancestors(self.parentrevs, a, b)
927 if ancs:
966 if ancs:
928 # choose a consistent winner when there's a tie
967 # choose a consistent winner when there's a tie
929 return min(map(self.node, ancs))
968 return min(map(self.node, ancs))
930 return nullid
969 return nullid
931
970
932 def _match(self, id):
971 def _match(self, id):
933 if isinstance(id, int):
972 if isinstance(id, int):
934 # rev
973 # rev
935 return self.node(id)
974 return self.node(id)
936 if len(id) == 20:
975 if len(id) == 20:
937 # possibly a binary node
976 # possibly a binary node
938 # odds of a binary node being all hex in ASCII are 1 in 10**25
977 # odds of a binary node being all hex in ASCII are 1 in 10**25
939 try:
978 try:
940 node = id
979 node = id
941 self.rev(node) # quick search the index
980 self.rev(node) # quick search the index
942 return node
981 return node
943 except LookupError:
982 except LookupError:
944 pass # may be partial hex id
983 pass # may be partial hex id
945 try:
984 try:
946 # str(rev)
985 # str(rev)
947 rev = int(id)
986 rev = int(id)
948 if str(rev) != id:
987 if str(rev) != id:
949 raise ValueError
988 raise ValueError
950 if rev < 0:
989 if rev < 0:
951 rev = len(self) + rev
990 rev = len(self) + rev
952 if rev < 0 or rev >= len(self):
991 if rev < 0 or rev >= len(self):
953 raise ValueError
992 raise ValueError
954 return self.node(rev)
993 return self.node(rev)
955 except (ValueError, OverflowError):
994 except (ValueError, OverflowError):
956 pass
995 pass
957 if len(id) == 40:
996 if len(id) == 40:
958 try:
997 try:
959 # a full hex nodeid?
998 # a full hex nodeid?
960 node = bin(id)
999 node = bin(id)
961 self.rev(node)
1000 self.rev(node)
962 return node
1001 return node
963 except (TypeError, LookupError):
1002 except (TypeError, LookupError):
964 pass
1003 pass
965
1004
966 def _partialmatch(self, id):
1005 def _partialmatch(self, id):
967 try:
1006 try:
968 partial = self.index.partialmatch(id)
1007 partial = self.index.partialmatch(id)
969 if partial and self.hasnode(partial):
1008 if partial and self.hasnode(partial):
970 return partial
1009 return partial
971 return None
1010 return None
972 except RevlogError:
1011 except RevlogError:
973 # parsers.c radix tree lookup gave multiple matches
1012 # parsers.c radix tree lookup gave multiple matches
974 # fast path: for unfiltered changelog, radix tree is accurate
1013 # fast path: for unfiltered changelog, radix tree is accurate
975 if not getattr(self, 'filteredrevs', None):
1014 if not getattr(self, 'filteredrevs', None):
976 raise LookupError(id, self.indexfile,
1015 raise LookupError(id, self.indexfile,
977 _('ambiguous identifier'))
1016 _('ambiguous identifier'))
978 # fall through to slow path that filters hidden revisions
1017 # fall through to slow path that filters hidden revisions
979 except (AttributeError, ValueError):
1018 except (AttributeError, ValueError):
980 # we are pure python, or key was too short to search radix tree
1019 # we are pure python, or key was too short to search radix tree
981 pass
1020 pass
982
1021
983 if id in self._pcache:
1022 if id in self._pcache:
984 return self._pcache[id]
1023 return self._pcache[id]
985
1024
986 if len(id) < 40:
1025 if len(id) < 40:
987 try:
1026 try:
988 # hex(node)[:...]
1027 # hex(node)[:...]
989 l = len(id) // 2 # grab an even number of digits
1028 l = len(id) // 2 # grab an even number of digits
990 prefix = bin(id[:l * 2])
1029 prefix = bin(id[:l * 2])
991 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1030 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
992 nl = [n for n in nl if hex(n).startswith(id) and
1031 nl = [n for n in nl if hex(n).startswith(id) and
993 self.hasnode(n)]
1032 self.hasnode(n)]
994 if len(nl) > 0:
1033 if len(nl) > 0:
995 if len(nl) == 1:
1034 if len(nl) == 1:
996 self._pcache[id] = nl[0]
1035 self._pcache[id] = nl[0]
997 return nl[0]
1036 return nl[0]
998 raise LookupError(id, self.indexfile,
1037 raise LookupError(id, self.indexfile,
999 _('ambiguous identifier'))
1038 _('ambiguous identifier'))
1000 return None
1039 return None
1001 except TypeError:
1040 except TypeError:
1002 pass
1041 pass
1003
1042
1004 def lookup(self, id):
1043 def lookup(self, id):
1005 """locate a node based on:
1044 """locate a node based on:
1006 - revision number or str(revision number)
1045 - revision number or str(revision number)
1007 - nodeid or subset of hex nodeid
1046 - nodeid or subset of hex nodeid
1008 """
1047 """
1009 n = self._match(id)
1048 n = self._match(id)
1010 if n is not None:
1049 if n is not None:
1011 return n
1050 return n
1012 n = self._partialmatch(id)
1051 n = self._partialmatch(id)
1013 if n:
1052 if n:
1014 return n
1053 return n
1015
1054
1016 raise LookupError(id, self.indexfile, _('no match found'))
1055 raise LookupError(id, self.indexfile, _('no match found'))
1017
1056
1018 def cmp(self, node, text):
1057 def cmp(self, node, text):
1019 """compare text with a given file revision
1058 """compare text with a given file revision
1020
1059
1021 returns True if text is different than what is stored.
1060 returns True if text is different than what is stored.
1022 """
1061 """
1023 p1, p2 = self.parents(node)
1062 p1, p2 = self.parents(node)
1024 return hash(text, p1, p2) != node
1063 return hash(text, p1, p2) != node
1025
1064
1026 def _addchunk(self, offset, data):
1065 def _addchunk(self, offset, data):
1027 """Add a segment to the revlog cache.
1066 """Add a segment to the revlog cache.
1028
1067
1029 Accepts an absolute offset and the data that is at that location.
1068 Accepts an absolute offset and the data that is at that location.
1030 """
1069 """
1031 o, d = self._chunkcache
1070 o, d = self._chunkcache
1032 # try to add to existing cache
1071 # try to add to existing cache
1033 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1072 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1034 self._chunkcache = o, d + data
1073 self._chunkcache = o, d + data
1035 else:
1074 else:
1036 self._chunkcache = offset, data
1075 self._chunkcache = offset, data
1037
1076
1038 def _loadchunk(self, offset, length, df=None):
1077 def _loadchunk(self, offset, length, df=None):
1039 """Load a segment of raw data from the revlog.
1078 """Load a segment of raw data from the revlog.
1040
1079
1041 Accepts an absolute offset, length to read, and an optional existing
1080 Accepts an absolute offset, length to read, and an optional existing
1042 file handle to read from.
1081 file handle to read from.
1043
1082
1044 If an existing file handle is passed, it will be seeked and the
1083 If an existing file handle is passed, it will be seeked and the
1045 original seek position will NOT be restored.
1084 original seek position will NOT be restored.
1046
1085
1047 Returns a str or buffer of raw byte data.
1086 Returns a str or buffer of raw byte data.
1048 """
1087 """
1049 if df is not None:
1088 if df is not None:
1050 closehandle = False
1089 closehandle = False
1051 else:
1090 else:
1052 if self._inline:
1091 if self._inline:
1053 df = self.opener(self.indexfile)
1092 df = self.opener(self.indexfile)
1054 else:
1093 else:
1055 df = self.opener(self.datafile)
1094 df = self.opener(self.datafile)
1056 closehandle = True
1095 closehandle = True
1057
1096
1058 # Cache data both forward and backward around the requested
1097 # Cache data both forward and backward around the requested
1059 # data, in a fixed size window. This helps speed up operations
1098 # data, in a fixed size window. This helps speed up operations
1060 # involving reading the revlog backwards.
1099 # involving reading the revlog backwards.
1061 cachesize = self._chunkcachesize
1100 cachesize = self._chunkcachesize
1062 realoffset = offset & ~(cachesize - 1)
1101 realoffset = offset & ~(cachesize - 1)
1063 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1102 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1064 - realoffset)
1103 - realoffset)
1065 df.seek(realoffset)
1104 df.seek(realoffset)
1066 d = df.read(reallength)
1105 d = df.read(reallength)
1067 if closehandle:
1106 if closehandle:
1068 df.close()
1107 df.close()
1069 self._addchunk(realoffset, d)
1108 self._addchunk(realoffset, d)
1070 if offset != realoffset or reallength != length:
1109 if offset != realoffset or reallength != length:
1071 return util.buffer(d, offset - realoffset, length)
1110 return util.buffer(d, offset - realoffset, length)
1072 return d
1111 return d
1073
1112
1074 def _getchunk(self, offset, length, df=None):
1113 def _getchunk(self, offset, length, df=None):
1075 """Obtain a segment of raw data from the revlog.
1114 """Obtain a segment of raw data from the revlog.
1076
1115
1077 Accepts an absolute offset, length of bytes to obtain, and an
1116 Accepts an absolute offset, length of bytes to obtain, and an
1078 optional file handle to the already-opened revlog. If the file
1117 optional file handle to the already-opened revlog. If the file
1079 handle is used, it's original seek position will not be preserved.
1118 handle is used, it's original seek position will not be preserved.
1080
1119
1081 Requests for data may be returned from a cache.
1120 Requests for data may be returned from a cache.
1082
1121
1083 Returns a str or a buffer instance of raw byte data.
1122 Returns a str or a buffer instance of raw byte data.
1084 """
1123 """
1085 o, d = self._chunkcache
1124 o, d = self._chunkcache
1086 l = len(d)
1125 l = len(d)
1087
1126
1088 # is it in the cache?
1127 # is it in the cache?
1089 cachestart = offset - o
1128 cachestart = offset - o
1090 cacheend = cachestart + length
1129 cacheend = cachestart + length
1091 if cachestart >= 0 and cacheend <= l:
1130 if cachestart >= 0 and cacheend <= l:
1092 if cachestart == 0 and cacheend == l:
1131 if cachestart == 0 and cacheend == l:
1093 return d # avoid a copy
1132 return d # avoid a copy
1094 return util.buffer(d, cachestart, cacheend - cachestart)
1133 return util.buffer(d, cachestart, cacheend - cachestart)
1095
1134
1096 return self._loadchunk(offset, length, df=df)
1135 return self._loadchunk(offset, length, df=df)
1097
1136
1098 def _chunkraw(self, startrev, endrev, df=None):
1137 def _chunkraw(self, startrev, endrev, df=None):
1099 """Obtain a segment of raw data corresponding to a range of revisions.
1138 """Obtain a segment of raw data corresponding to a range of revisions.
1100
1139
1101 Accepts the start and end revisions and an optional already-open
1140 Accepts the start and end revisions and an optional already-open
1102 file handle to be used for reading. If the file handle is read, its
1141 file handle to be used for reading. If the file handle is read, its
1103 seek position will not be preserved.
1142 seek position will not be preserved.
1104
1143
1105 Requests for data may be satisfied by a cache.
1144 Requests for data may be satisfied by a cache.
1106
1145
1107 Returns a 2-tuple of (offset, data) for the requested range of
1146 Returns a 2-tuple of (offset, data) for the requested range of
1108 revisions. Offset is the integer offset from the beginning of the
1147 revisions. Offset is the integer offset from the beginning of the
1109 revlog and data is a str or buffer of the raw byte data.
1148 revlog and data is a str or buffer of the raw byte data.
1110
1149
1111 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1150 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1112 to determine where each revision's data begins and ends.
1151 to determine where each revision's data begins and ends.
1113 """
1152 """
1114 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1153 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1115 # (functions are expensive).
1154 # (functions are expensive).
1116 index = self.index
1155 index = self.index
1117 istart = index[startrev]
1156 istart = index[startrev]
1118 start = int(istart[0] >> 16)
1157 start = int(istart[0] >> 16)
1119 if startrev == endrev:
1158 if startrev == endrev:
1120 end = start + istart[1]
1159 end = start + istart[1]
1121 else:
1160 else:
1122 iend = index[endrev]
1161 iend = index[endrev]
1123 end = int(iend[0] >> 16) + iend[1]
1162 end = int(iend[0] >> 16) + iend[1]
1124
1163
1125 if self._inline:
1164 if self._inline:
1126 start += (startrev + 1) * self._io.size
1165 start += (startrev + 1) * self._io.size
1127 end += (endrev + 1) * self._io.size
1166 end += (endrev + 1) * self._io.size
1128 length = end - start
1167 length = end - start
1129
1168
1130 return start, self._getchunk(start, length, df=df)
1169 return start, self._getchunk(start, length, df=df)
1131
1170
1132 def _chunk(self, rev, df=None):
1171 def _chunk(self, rev, df=None):
1133 """Obtain a single decompressed chunk for a revision.
1172 """Obtain a single decompressed chunk for a revision.
1134
1173
1135 Accepts an integer revision and an optional already-open file handle
1174 Accepts an integer revision and an optional already-open file handle
1136 to be used for reading. If used, the seek position of the file will not
1175 to be used for reading. If used, the seek position of the file will not
1137 be preserved.
1176 be preserved.
1138
1177
1139 Returns a str holding uncompressed data for the requested revision.
1178 Returns a str holding uncompressed data for the requested revision.
1140 """
1179 """
1141 return decompress(self._chunkraw(rev, rev, df=df)[1])
1180 return decompress(self._chunkraw(rev, rev, df=df)[1])
1142
1181
1143 def _chunks(self, revs, df=None):
1182 def _chunks(self, revs, df=None):
1144 """Obtain decompressed chunks for the specified revisions.
1183 """Obtain decompressed chunks for the specified revisions.
1145
1184
1146 Accepts an iterable of numeric revisions that are assumed to be in
1185 Accepts an iterable of numeric revisions that are assumed to be in
1147 ascending order. Also accepts an optional already-open file handle
1186 ascending order. Also accepts an optional already-open file handle
1148 to be used for reading. If used, the seek position of the file will
1187 to be used for reading. If used, the seek position of the file will
1149 not be preserved.
1188 not be preserved.
1150
1189
1151 This function is similar to calling ``self._chunk()`` multiple times,
1190 This function is similar to calling ``self._chunk()`` multiple times,
1152 but is faster.
1191 but is faster.
1153
1192
1154 Returns a list with decompressed data for each requested revision.
1193 Returns a list with decompressed data for each requested revision.
1155 """
1194 """
1156 if not revs:
1195 if not revs:
1157 return []
1196 return []
1158 start = self.start
1197 start = self.start
1159 length = self.length
1198 length = self.length
1160 inline = self._inline
1199 inline = self._inline
1161 iosize = self._io.size
1200 iosize = self._io.size
1162 buffer = util.buffer
1201 buffer = util.buffer
1163
1202
1164 l = []
1203 l = []
1165 ladd = l.append
1204 ladd = l.append
1166
1205
1167 try:
1206 try:
1168 offset, data = self._chunkraw(revs[0], revs[-1], df=df)
1207 offset, data = self._chunkraw(revs[0], revs[-1], df=df)
1169 except OverflowError:
1208 except OverflowError:
1170 # issue4215 - we can't cache a run of chunks greater than
1209 # issue4215 - we can't cache a run of chunks greater than
1171 # 2G on Windows
1210 # 2G on Windows
1172 return [self._chunk(rev, df=df) for rev in revs]
1211 return [self._chunk(rev, df=df) for rev in revs]
1173
1212
1174 for rev in revs:
1213 for rev in revs:
1175 chunkstart = start(rev)
1214 chunkstart = start(rev)
1176 if inline:
1215 if inline:
1177 chunkstart += (rev + 1) * iosize
1216 chunkstart += (rev + 1) * iosize
1178 chunklength = length(rev)
1217 chunklength = length(rev)
1179 ladd(decompress(buffer(data, chunkstart - offset, chunklength)))
1218 ladd(decompress(buffer(data, chunkstart - offset, chunklength)))
1180
1219
1181 return l
1220 return l
1182
1221
1183 def _chunkclear(self):
1222 def _chunkclear(self):
1184 """Clear the raw chunk cache."""
1223 """Clear the raw chunk cache."""
1185 self._chunkcache = (0, '')
1224 self._chunkcache = (0, '')
1186
1225
1187 def deltaparent(self, rev):
1226 def deltaparent(self, rev):
1188 """return deltaparent of the given revision"""
1227 """return deltaparent of the given revision"""
1189 base = self.index[rev][3]
1228 base = self.index[rev][3]
1190 if base == rev:
1229 if base == rev:
1191 return nullrev
1230 return nullrev
1192 elif self._generaldelta:
1231 elif self._generaldelta:
1193 return base
1232 return base
1194 else:
1233 else:
1195 return rev - 1
1234 return rev - 1
1196
1235
1197 def revdiff(self, rev1, rev2):
1236 def revdiff(self, rev1, rev2):
1198 """return or calculate a delta between two revisions"""
1237 """return or calculate a delta between two revisions"""
1199 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1238 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1200 return str(self._chunk(rev2))
1239 return str(self._chunk(rev2))
1201
1240
1202 return mdiff.textdiff(self.revision(rev1),
1241 return mdiff.textdiff(self.revision(rev1),
1203 self.revision(rev2))
1242 self.revision(rev2))
1204
1243
1205 def revision(self, nodeorrev, _df=None, raw=False):
1244 def revision(self, nodeorrev, _df=None, raw=False):
1206 """return an uncompressed revision of a given node or revision
1245 """return an uncompressed revision of a given node or revision
1207 number.
1246 number.
1208
1247
1209 _df - an existing file handle to read from. (internal-only)
1248 _df - an existing file handle to read from. (internal-only)
1210 raw - an optional argument specifying if the revision data is to be
1249 raw - an optional argument specifying if the revision data is to be
1211 treated as raw data when applying flag transforms. 'raw' should be set
1250 treated as raw data when applying flag transforms. 'raw' should be set
1212 to True when generating changegroups or in debug commands.
1251 to True when generating changegroups or in debug commands.
1213 """
1252 """
1214 if isinstance(nodeorrev, int):
1253 if isinstance(nodeorrev, int):
1215 rev = nodeorrev
1254 rev = nodeorrev
1216 node = self.node(rev)
1255 node = self.node(rev)
1217 else:
1256 else:
1218 node = nodeorrev
1257 node = nodeorrev
1219 rev = None
1258 rev = None
1220
1259
1221 cachedrev = None
1260 cachedrev = None
1222 if node == nullid:
1261 if node == nullid:
1223 return ""
1262 return ""
1224 if self._cache:
1263 if self._cache:
1225 if self._cache[0] == node:
1264 if self._cache[0] == node:
1226 return self._cache[2]
1265 return self._cache[2]
1227 cachedrev = self._cache[1]
1266 cachedrev = self._cache[1]
1228
1267
1229 # look up what we need to read
1268 # look up what we need to read
1230 text = None
1269 text = None
1231 if rev is None:
1270 if rev is None:
1232 rev = self.rev(node)
1271 rev = self.rev(node)
1233
1272
1234 # check rev flags
1235 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
1236 raise RevlogError(_('incompatible revision flag %x') %
1237 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
1238
1239 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1273 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1240 if stopped:
1274 if stopped:
1241 text = self._cache[2]
1275 text = self._cache[2]
1242
1276
1243 # drop cache to save memory
1277 # drop cache to save memory
1244 self._cache = None
1278 self._cache = None
1245
1279
1246 bins = self._chunks(chain, df=_df)
1280 bins = self._chunks(chain, df=_df)
1247 if text is None:
1281 if text is None:
1248 text = str(bins[0])
1282 text = str(bins[0])
1249 bins = bins[1:]
1283 bins = bins[1:]
1250
1284
1251 text = mdiff.patches(text, bins)
1285 text = mdiff.patches(text, bins)
1252 self.checkhash(text, node, rev=rev)
1286
1287 text, validatehash = self._processflags(text, self.flags(rev), 'read',
1288 raw=raw)
1289 if validatehash:
1290 self.checkhash(text, node, rev=rev)
1291
1253 self._cache = (node, rev, text)
1292 self._cache = (node, rev, text)
1254 return text
1293 return text
1255
1294
1256 def hash(self, text, p1, p2):
1295 def hash(self, text, p1, p2):
1257 """Compute a node hash.
1296 """Compute a node hash.
1258
1297
1259 Available as a function so that subclasses can replace the hash
1298 Available as a function so that subclasses can replace the hash
1260 as needed.
1299 as needed.
1261 """
1300 """
1262 return hash(text, p1, p2)
1301 return hash(text, p1, p2)
1263
1302
1303 def _processflags(self, text, flags, operation, raw=False):
1304 """Inspect revision data flags and applies transforms defined by
1305 registered flag processors.
1306
1307 ``text`` - the revision data to process
1308 ``flags`` - the revision flags
1309 ``operation`` - the operation being performed (read or write)
1310 ``raw`` - an optional argument describing if the raw transform should be
1311 applied.
1312
1313 This method processes the flags in the order (or reverse order if
1314 ``operation`` is 'write') defined by REVIDX_FLAGS_ORDER, applying the
1315 flag processors registered for present flags. The order of flags defined
1316 in REVIDX_FLAGS_ORDER needs to be stable to allow non-commutativity.
1317
1318 Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
1319 processed text and ``validatehash`` is a bool indicating whether the
1320 returned text should be checked for hash integrity.
1321
1322 Note: If the ``raw`` argument is set, it has precedence over the
1323 operation and will only update the value of ``validatehash``.
1324 """
1325 if not operation in ('read', 'write'):
1326 raise ProgrammingError(_("invalid '%s' operation ") % (operation))
1327 # Check all flags are known.
1328 if flags & ~REVIDX_KNOWN_FLAGS:
1329 raise RevlogError(_("incompatible revision flag '%#x'") %
1330 (flags & ~REVIDX_KNOWN_FLAGS))
1331 validatehash = True
1332 # Depending on the operation (read or write), the order might be
1333 # reversed due to non-commutative transforms.
1334 orderedflags = REVIDX_FLAGS_ORDER
1335 if operation == 'write':
1336 orderedflags = reversed(orderedflags)
1337
1338 for flag in orderedflags:
1339 # If a flagprocessor has been registered for a known flag, apply the
1340 # related operation transform and update result tuple.
1341 if flag & flags:
1342 vhash = True
1343
1344 if flag not in _flagprocessors:
1345 message = _("missing processor for flag '%#x'") % (flag)
1346 raise RevlogError(message)
1347
1348 processor = _flagprocessors[flag]
1349 if processor is not None:
1350 readtransform, writetransform, rawtransform = processor
1351
1352 if raw:
1353 vhash = rawtransform(self, text)
1354 elif operation == 'read':
1355 text, vhash = readtransform(self, text)
1356 else: # write operation
1357 text, vhash = writetransform(self, text)
1358 validatehash = validatehash and vhash
1359
1360 return text, validatehash
1361
1264 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1362 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1265 """Check node hash integrity.
1363 """Check node hash integrity.
1266
1364
1267 Available as a function so that subclasses can extend hash mismatch
1365 Available as a function so that subclasses can extend hash mismatch
1268 behaviors as needed.
1366 behaviors as needed.
1269 """
1367 """
1270 if p1 is None and p2 is None:
1368 if p1 is None and p2 is None:
1271 p1, p2 = self.parents(node)
1369 p1, p2 = self.parents(node)
1272 if node != self.hash(text, p1, p2):
1370 if node != self.hash(text, p1, p2):
1273 revornode = rev
1371 revornode = rev
1274 if revornode is None:
1372 if revornode is None:
1275 revornode = templatefilters.short(hex(node))
1373 revornode = templatefilters.short(hex(node))
1276 raise RevlogError(_("integrity check failed on %s:%s")
1374 raise RevlogError(_("integrity check failed on %s:%s")
1277 % (self.indexfile, revornode))
1375 % (self.indexfile, revornode))
1278
1376
1279 def checkinlinesize(self, tr, fp=None):
1377 def checkinlinesize(self, tr, fp=None):
1280 """Check if the revlog is too big for inline and convert if so.
1378 """Check if the revlog is too big for inline and convert if so.
1281
1379
1282 This should be called after revisions are added to the revlog. If the
1380 This should be called after revisions are added to the revlog. If the
1283 revlog has grown too large to be an inline revlog, it will convert it
1381 revlog has grown too large to be an inline revlog, it will convert it
1284 to use multiple index and data files.
1382 to use multiple index and data files.
1285 """
1383 """
1286 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1384 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1287 return
1385 return
1288
1386
1289 trinfo = tr.find(self.indexfile)
1387 trinfo = tr.find(self.indexfile)
1290 if trinfo is None:
1388 if trinfo is None:
1291 raise RevlogError(_("%s not found in the transaction")
1389 raise RevlogError(_("%s not found in the transaction")
1292 % self.indexfile)
1390 % self.indexfile)
1293
1391
1294 trindex = trinfo[2]
1392 trindex = trinfo[2]
1295 if trindex is not None:
1393 if trindex is not None:
1296 dataoff = self.start(trindex)
1394 dataoff = self.start(trindex)
1297 else:
1395 else:
1298 # revlog was stripped at start of transaction, use all leftover data
1396 # revlog was stripped at start of transaction, use all leftover data
1299 trindex = len(self) - 1
1397 trindex = len(self) - 1
1300 dataoff = self.end(-2)
1398 dataoff = self.end(-2)
1301
1399
1302 tr.add(self.datafile, dataoff)
1400 tr.add(self.datafile, dataoff)
1303
1401
1304 if fp:
1402 if fp:
1305 fp.flush()
1403 fp.flush()
1306 fp.close()
1404 fp.close()
1307
1405
1308 df = self.opener(self.datafile, 'w')
1406 df = self.opener(self.datafile, 'w')
1309 try:
1407 try:
1310 for r in self:
1408 for r in self:
1311 df.write(self._chunkraw(r, r)[1])
1409 df.write(self._chunkraw(r, r)[1])
1312 finally:
1410 finally:
1313 df.close()
1411 df.close()
1314
1412
1315 fp = self.opener(self.indexfile, 'w', atomictemp=True,
1413 fp = self.opener(self.indexfile, 'w', atomictemp=True,
1316 checkambig=self._checkambig)
1414 checkambig=self._checkambig)
1317 self.version &= ~(REVLOGNGINLINEDATA)
1415 self.version &= ~(REVLOGNGINLINEDATA)
1318 self._inline = False
1416 self._inline = False
1319 for i in self:
1417 for i in self:
1320 e = self._io.packentry(self.index[i], self.node, self.version, i)
1418 e = self._io.packentry(self.index[i], self.node, self.version, i)
1321 fp.write(e)
1419 fp.write(e)
1322
1420
1323 # if we don't call close, the temp file will never replace the
1421 # if we don't call close, the temp file will never replace the
1324 # real index
1422 # real index
1325 fp.close()
1423 fp.close()
1326
1424
1327 tr.replace(self.indexfile, trindex * self._io.size)
1425 tr.replace(self.indexfile, trindex * self._io.size)
1328 self._chunkclear()
1426 self._chunkclear()
1329
1427
1330 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1428 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1331 node=None, flags=REVIDX_DEFAULT_FLAGS):
1429 node=None, flags=REVIDX_DEFAULT_FLAGS):
1332 """add a revision to the log
1430 """add a revision to the log
1333
1431
1334 text - the revision data to add
1432 text - the revision data to add
1335 transaction - the transaction object used for rollback
1433 transaction - the transaction object used for rollback
1336 link - the linkrev data to add
1434 link - the linkrev data to add
1337 p1, p2 - the parent nodeids of the revision
1435 p1, p2 - the parent nodeids of the revision
1338 cachedelta - an optional precomputed delta
1436 cachedelta - an optional precomputed delta
1339 node - nodeid of revision; typically node is not specified, and it is
1437 node - nodeid of revision; typically node is not specified, and it is
1340 computed by default as hash(text, p1, p2), however subclasses might
1438 computed by default as hash(text, p1, p2), however subclasses might
1341 use different hashing method (and override checkhash() in such case)
1439 use different hashing method (and override checkhash() in such case)
1342 flags - the known flags to set on the revision
1440 flags - the known flags to set on the revision
1343 """
1441 """
1344 if link == nullrev:
1442 if link == nullrev:
1345 raise RevlogError(_("attempted to add linkrev -1 to %s")
1443 raise RevlogError(_("attempted to add linkrev -1 to %s")
1346 % self.indexfile)
1444 % self.indexfile)
1347
1445
1446 if flags:
1447 node = node or self.hash(text, p1, p2)
1448
1449 newtext, validatehash = self._processflags(text, flags, 'write')
1450
1451 # If the flag processor modifies the revision data, ignore any provided
1452 # cachedelta.
1453 if newtext != text:
1454 cachedelta = None
1455 text = newtext
1456
1348 if len(text) > _maxentrysize:
1457 if len(text) > _maxentrysize:
1349 raise RevlogError(
1458 raise RevlogError(
1350 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1459 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1351 % (self.indexfile, len(text)))
1460 % (self.indexfile, len(text)))
1352
1461
1353 node = node or self.hash(text, p1, p2)
1462 node = node or self.hash(text, p1, p2)
1354 if node in self.nodemap:
1463 if node in self.nodemap:
1355 return node
1464 return node
1356
1465
1466 if validatehash:
1467 self.checkhash(text, node, p1=p1, p2=p2)
1468
1357 dfh = None
1469 dfh = None
1358 if not self._inline:
1470 if not self._inline:
1359 dfh = self.opener(self.datafile, "a+")
1471 dfh = self.opener(self.datafile, "a+")
1360 ifh = self.opener(self.indexfile, "a+", checkambig=self._checkambig)
1472 ifh = self.opener(self.indexfile, "a+", checkambig=self._checkambig)
1361 try:
1473 try:
1362 return self._addrevision(node, text, transaction, link, p1, p2,
1474 return self._addrevision(node, text, transaction, link, p1, p2,
1363 flags, cachedelta, ifh, dfh)
1475 flags, cachedelta, ifh, dfh)
1364 finally:
1476 finally:
1365 if dfh:
1477 if dfh:
1366 dfh.close()
1478 dfh.close()
1367 ifh.close()
1479 ifh.close()
1368
1480
1369 def compress(self, text):
1481 def compress(self, text):
1370 """ generate a possibly-compressed representation of text """
1482 """ generate a possibly-compressed representation of text """
1371 if not text:
1483 if not text:
1372 return ("", text)
1484 return ("", text)
1373 l = len(text)
1485 l = len(text)
1374 bin = None
1486 bin = None
1375 if l < 44:
1487 if l < 44:
1376 pass
1488 pass
1377 elif l > 1000000:
1489 elif l > 1000000:
1378 # zlib makes an internal copy, thus doubling memory usage for
1490 # zlib makes an internal copy, thus doubling memory usage for
1379 # large files, so lets do this in pieces
1491 # large files, so lets do this in pieces
1380 z = zlib.compressobj()
1492 z = zlib.compressobj()
1381 p = []
1493 p = []
1382 pos = 0
1494 pos = 0
1383 while pos < l:
1495 while pos < l:
1384 pos2 = pos + 2**20
1496 pos2 = pos + 2**20
1385 p.append(z.compress(text[pos:pos2]))
1497 p.append(z.compress(text[pos:pos2]))
1386 pos = pos2
1498 pos = pos2
1387 p.append(z.flush())
1499 p.append(z.flush())
1388 if sum(map(len, p)) < l:
1500 if sum(map(len, p)) < l:
1389 bin = "".join(p)
1501 bin = "".join(p)
1390 else:
1502 else:
1391 bin = _compress(text)
1503 bin = _compress(text)
1392 if bin is None or len(bin) > l:
1504 if bin is None or len(bin) > l:
1393 if text[0] == '\0':
1505 if text[0] == '\0':
1394 return ("", text)
1506 return ("", text)
1395 return ('u', text)
1507 return ('u', text)
1396 return ("", bin)
1508 return ("", bin)
1397
1509
1398 def _isgooddelta(self, d, textlen):
1510 def _isgooddelta(self, d, textlen):
1399 """Returns True if the given delta is good. Good means that it is within
1511 """Returns True if the given delta is good. Good means that it is within
1400 the disk span, disk size, and chain length bounds that we know to be
1512 the disk span, disk size, and chain length bounds that we know to be
1401 performant."""
1513 performant."""
1402 if d is None:
1514 if d is None:
1403 return False
1515 return False
1404
1516
1405 # - 'dist' is the distance from the base revision -- bounding it limits
1517 # - 'dist' is the distance from the base revision -- bounding it limits
1406 # the amount of I/O we need to do.
1518 # the amount of I/O we need to do.
1407 # - 'compresseddeltalen' is the sum of the total size of deltas we need
1519 # - 'compresseddeltalen' is the sum of the total size of deltas we need
1408 # to apply -- bounding it limits the amount of CPU we consume.
1520 # to apply -- bounding it limits the amount of CPU we consume.
1409 dist, l, data, base, chainbase, chainlen, compresseddeltalen = d
1521 dist, l, data, base, chainbase, chainlen, compresseddeltalen = d
1410 if (dist > textlen * 4 or l > textlen or
1522 if (dist > textlen * 4 or l > textlen or
1411 compresseddeltalen > textlen * 2 or
1523 compresseddeltalen > textlen * 2 or
1412 (self._maxchainlen and chainlen > self._maxchainlen)):
1524 (self._maxchainlen and chainlen > self._maxchainlen)):
1413 return False
1525 return False
1414
1526
1415 return True
1527 return True
1416
1528
1417 def _addrevision(self, node, text, transaction, link, p1, p2, flags,
1529 def _addrevision(self, node, text, transaction, link, p1, p2, flags,
1418 cachedelta, ifh, dfh, alwayscache=False, raw=False):
1530 cachedelta, ifh, dfh, alwayscache=False, raw=False):
1419 """internal function to add revisions to the log
1531 """internal function to add revisions to the log
1420
1532
1421 see addrevision for argument descriptions.
1533 see addrevision for argument descriptions.
1422 invariants:
1534 invariants:
1423 - text is optional (can be None); if not set, cachedelta must be set.
1535 - text is optional (can be None); if not set, cachedelta must be set.
1424 if both are set, they must correspond to each other.
1536 if both are set, they must correspond to each other.
1425 - raw is optional; if set to True, it indicates the revision data is to
1537 - raw is optional; if set to True, it indicates the revision data is to
1426 be treated by _processflags() as raw. It is usually set by changegroup
1538 be treated by _processflags() as raw. It is usually set by changegroup
1427 generation and debug commands.
1539 generation and debug commands.
1428 """
1540 """
1429 btext = [text]
1541 btext = [text]
1430 def buildtext():
1542 def buildtext():
1431 if btext[0] is not None:
1543 if btext[0] is not None:
1432 return btext[0]
1544 return btext[0]
1433 baserev = cachedelta[0]
1545 baserev = cachedelta[0]
1434 delta = cachedelta[1]
1546 delta = cachedelta[1]
1435 # special case deltas which replace entire base; no need to decode
1547 # special case deltas which replace entire base; no need to decode
1436 # base revision. this neatly avoids censored bases, which throw when
1548 # base revision. this neatly avoids censored bases, which throw when
1437 # they're decoded.
1549 # they're decoded.
1438 hlen = struct.calcsize(">lll")
1550 hlen = struct.calcsize(">lll")
1439 if delta[:hlen] == mdiff.replacediffheader(self.rawsize(baserev),
1551 if delta[:hlen] == mdiff.replacediffheader(self.rawsize(baserev),
1440 len(delta) - hlen):
1552 len(delta) - hlen):
1441 btext[0] = delta[hlen:]
1553 btext[0] = delta[hlen:]
1442 else:
1554 else:
1443 if self._inline:
1555 if self._inline:
1444 fh = ifh
1556 fh = ifh
1445 else:
1557 else:
1446 fh = dfh
1558 fh = dfh
1447 basetext = self.revision(self.node(baserev), _df=fh, raw=raw)
1559 basetext = self.revision(self.node(baserev), _df=fh, raw=raw)
1448 btext[0] = mdiff.patch(basetext, delta)
1560 btext[0] = mdiff.patch(basetext, delta)
1449
1561
1450 try:
1562 try:
1451 self.checkhash(btext[0], node, p1=p1, p2=p2)
1563 res = self._processflags(btext[0], flags, 'read', raw=raw)
1564 btext[0], validatehash = res
1565 if validatehash:
1566 self.checkhash(btext[0], node, p1=p1, p2=p2)
1452 if flags & REVIDX_ISCENSORED:
1567 if flags & REVIDX_ISCENSORED:
1453 raise RevlogError(_('node %s is not censored') % node)
1568 raise RevlogError(_('node %s is not censored') % node)
1454 except CensoredNodeError:
1569 except CensoredNodeError:
1455 # must pass the censored index flag to add censored revisions
1570 # must pass the censored index flag to add censored revisions
1456 if not flags & REVIDX_ISCENSORED:
1571 if not flags & REVIDX_ISCENSORED:
1457 raise
1572 raise
1458 return btext[0]
1573 return btext[0]
1459
1574
1460 def builddelta(rev):
1575 def builddelta(rev):
1461 # can we use the cached delta?
1576 # can we use the cached delta?
1462 if cachedelta and cachedelta[0] == rev:
1577 if cachedelta and cachedelta[0] == rev:
1463 delta = cachedelta[1]
1578 delta = cachedelta[1]
1464 else:
1579 else:
1465 t = buildtext()
1580 t = buildtext()
1466 if self.iscensored(rev):
1581 if self.iscensored(rev):
1467 # deltas based on a censored revision must replace the
1582 # deltas based on a censored revision must replace the
1468 # full content in one patch, so delta works everywhere
1583 # full content in one patch, so delta works everywhere
1469 header = mdiff.replacediffheader(self.rawsize(rev), len(t))
1584 header = mdiff.replacediffheader(self.rawsize(rev), len(t))
1470 delta = header + t
1585 delta = header + t
1471 else:
1586 else:
1472 if self._inline:
1587 if self._inline:
1473 fh = ifh
1588 fh = ifh
1474 else:
1589 else:
1475 fh = dfh
1590 fh = dfh
1476 ptext = self.revision(self.node(rev), _df=fh)
1591 ptext = self.revision(self.node(rev), _df=fh)
1477 delta = mdiff.textdiff(ptext, t)
1592 delta = mdiff.textdiff(ptext, t)
1478 header, data = self.compress(delta)
1593 header, data = self.compress(delta)
1479 deltalen = len(header) + len(data)
1594 deltalen = len(header) + len(data)
1480 chainbase = self.chainbase(rev)
1595 chainbase = self.chainbase(rev)
1481 dist = deltalen + offset - self.start(chainbase)
1596 dist = deltalen + offset - self.start(chainbase)
1482 if self._generaldelta:
1597 if self._generaldelta:
1483 base = rev
1598 base = rev
1484 else:
1599 else:
1485 base = chainbase
1600 base = chainbase
1486 chainlen, compresseddeltalen = self._chaininfo(rev)
1601 chainlen, compresseddeltalen = self._chaininfo(rev)
1487 chainlen += 1
1602 chainlen += 1
1488 compresseddeltalen += deltalen
1603 compresseddeltalen += deltalen
1489 return (dist, deltalen, (header, data), base,
1604 return (dist, deltalen, (header, data), base,
1490 chainbase, chainlen, compresseddeltalen)
1605 chainbase, chainlen, compresseddeltalen)
1491
1606
1492 curr = len(self)
1607 curr = len(self)
1493 prev = curr - 1
1608 prev = curr - 1
1494 offset = self.end(prev)
1609 offset = self.end(prev)
1495 delta = None
1610 delta = None
1496 p1r, p2r = self.rev(p1), self.rev(p2)
1611 p1r, p2r = self.rev(p1), self.rev(p2)
1497
1612
1498 # full versions are inserted when the needed deltas
1613 # full versions are inserted when the needed deltas
1499 # become comparable to the uncompressed text
1614 # become comparable to the uncompressed text
1500 if text is None:
1615 if text is None:
1501 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1616 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1502 cachedelta[1])
1617 cachedelta[1])
1503 else:
1618 else:
1504 textlen = len(text)
1619 textlen = len(text)
1505
1620
1506 # should we try to build a delta?
1621 # should we try to build a delta?
1507 if prev != nullrev and self.storedeltachains:
1622 if prev != nullrev and self.storedeltachains:
1508 tested = set()
1623 tested = set()
1509 # This condition is true most of the time when processing
1624 # This condition is true most of the time when processing
1510 # changegroup data into a generaldelta repo. The only time it
1625 # changegroup data into a generaldelta repo. The only time it
1511 # isn't true is if this is the first revision in a delta chain
1626 # isn't true is if this is the first revision in a delta chain
1512 # or if ``format.generaldelta=true`` disabled ``lazydeltabase``.
1627 # or if ``format.generaldelta=true`` disabled ``lazydeltabase``.
1513 if cachedelta and self._generaldelta and self._lazydeltabase:
1628 if cachedelta and self._generaldelta and self._lazydeltabase:
1514 # Assume what we received from the server is a good choice
1629 # Assume what we received from the server is a good choice
1515 # build delta will reuse the cache
1630 # build delta will reuse the cache
1516 candidatedelta = builddelta(cachedelta[0])
1631 candidatedelta = builddelta(cachedelta[0])
1517 tested.add(cachedelta[0])
1632 tested.add(cachedelta[0])
1518 if self._isgooddelta(candidatedelta, textlen):
1633 if self._isgooddelta(candidatedelta, textlen):
1519 delta = candidatedelta
1634 delta = candidatedelta
1520 if delta is None and self._generaldelta:
1635 if delta is None and self._generaldelta:
1521 # exclude already lazy tested base if any
1636 # exclude already lazy tested base if any
1522 parents = [p for p in (p1r, p2r)
1637 parents = [p for p in (p1r, p2r)
1523 if p != nullrev and p not in tested]
1638 if p != nullrev and p not in tested]
1524 if parents and not self._aggressivemergedeltas:
1639 if parents and not self._aggressivemergedeltas:
1525 # Pick whichever parent is closer to us (to minimize the
1640 # Pick whichever parent is closer to us (to minimize the
1526 # chance of having to build a fulltext).
1641 # chance of having to build a fulltext).
1527 parents = [max(parents)]
1642 parents = [max(parents)]
1528 tested.update(parents)
1643 tested.update(parents)
1529 pdeltas = []
1644 pdeltas = []
1530 for p in parents:
1645 for p in parents:
1531 pd = builddelta(p)
1646 pd = builddelta(p)
1532 if self._isgooddelta(pd, textlen):
1647 if self._isgooddelta(pd, textlen):
1533 pdeltas.append(pd)
1648 pdeltas.append(pd)
1534 if pdeltas:
1649 if pdeltas:
1535 delta = min(pdeltas, key=lambda x: x[1])
1650 delta = min(pdeltas, key=lambda x: x[1])
1536 if delta is None and prev not in tested:
1651 if delta is None and prev not in tested:
1537 # other approach failed try against prev to hopefully save us a
1652 # other approach failed try against prev to hopefully save us a
1538 # fulltext.
1653 # fulltext.
1539 candidatedelta = builddelta(prev)
1654 candidatedelta = builddelta(prev)
1540 if self._isgooddelta(candidatedelta, textlen):
1655 if self._isgooddelta(candidatedelta, textlen):
1541 delta = candidatedelta
1656 delta = candidatedelta
1542 if delta is not None:
1657 if delta is not None:
1543 dist, l, data, base, chainbase, chainlen, compresseddeltalen = delta
1658 dist, l, data, base, chainbase, chainlen, compresseddeltalen = delta
1544 else:
1659 else:
1545 text = buildtext()
1660 text = buildtext()
1546 data = self.compress(text)
1661 data = self.compress(text)
1547 l = len(data[1]) + len(data[0])
1662 l = len(data[1]) + len(data[0])
1548 base = chainbase = curr
1663 base = chainbase = curr
1549
1664
1550 e = (offset_type(offset, flags), l, textlen,
1665 e = (offset_type(offset, flags), l, textlen,
1551 base, link, p1r, p2r, node)
1666 base, link, p1r, p2r, node)
1552 self.index.insert(-1, e)
1667 self.index.insert(-1, e)
1553 self.nodemap[node] = curr
1668 self.nodemap[node] = curr
1554
1669
1555 entry = self._io.packentry(e, self.node, self.version, curr)
1670 entry = self._io.packentry(e, self.node, self.version, curr)
1556 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
1671 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
1557
1672
1558 if alwayscache and text is None:
1673 if alwayscache and text is None:
1559 text = buildtext()
1674 text = buildtext()
1560
1675
1561 if type(text) == str: # only accept immutable objects
1676 if type(text) == str: # only accept immutable objects
1562 self._cache = (node, curr, text)
1677 self._cache = (node, curr, text)
1563 self._chainbasecache[curr] = chainbase
1678 self._chainbasecache[curr] = chainbase
1564 return node
1679 return node
1565
1680
1566 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
1681 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
1567 # Files opened in a+ mode have inconsistent behavior on various
1682 # Files opened in a+ mode have inconsistent behavior on various
1568 # platforms. Windows requires that a file positioning call be made
1683 # platforms. Windows requires that a file positioning call be made
1569 # when the file handle transitions between reads and writes. See
1684 # when the file handle transitions between reads and writes. See
1570 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
1685 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
1571 # platforms, Python or the platform itself can be buggy. Some versions
1686 # platforms, Python or the platform itself can be buggy. Some versions
1572 # of Solaris have been observed to not append at the end of the file
1687 # of Solaris have been observed to not append at the end of the file
1573 # if the file was seeked to before the end. See issue4943 for more.
1688 # if the file was seeked to before the end. See issue4943 for more.
1574 #
1689 #
1575 # We work around this issue by inserting a seek() before writing.
1690 # We work around this issue by inserting a seek() before writing.
1576 # Note: This is likely not necessary on Python 3.
1691 # Note: This is likely not necessary on Python 3.
1577 ifh.seek(0, os.SEEK_END)
1692 ifh.seek(0, os.SEEK_END)
1578 if dfh:
1693 if dfh:
1579 dfh.seek(0, os.SEEK_END)
1694 dfh.seek(0, os.SEEK_END)
1580
1695
1581 curr = len(self) - 1
1696 curr = len(self) - 1
1582 if not self._inline:
1697 if not self._inline:
1583 transaction.add(self.datafile, offset)
1698 transaction.add(self.datafile, offset)
1584 transaction.add(self.indexfile, curr * len(entry))
1699 transaction.add(self.indexfile, curr * len(entry))
1585 if data[0]:
1700 if data[0]:
1586 dfh.write(data[0])
1701 dfh.write(data[0])
1587 dfh.write(data[1])
1702 dfh.write(data[1])
1588 ifh.write(entry)
1703 ifh.write(entry)
1589 else:
1704 else:
1590 offset += curr * self._io.size
1705 offset += curr * self._io.size
1591 transaction.add(self.indexfile, offset, curr)
1706 transaction.add(self.indexfile, offset, curr)
1592 ifh.write(entry)
1707 ifh.write(entry)
1593 ifh.write(data[0])
1708 ifh.write(data[0])
1594 ifh.write(data[1])
1709 ifh.write(data[1])
1595 self.checkinlinesize(transaction, ifh)
1710 self.checkinlinesize(transaction, ifh)
1596
1711
1597 def addgroup(self, cg, linkmapper, transaction, addrevisioncb=None):
1712 def addgroup(self, cg, linkmapper, transaction, addrevisioncb=None):
1598 """
1713 """
1599 add a delta group
1714 add a delta group
1600
1715
1601 given a set of deltas, add them to the revision log. the
1716 given a set of deltas, add them to the revision log. the
1602 first delta is against its parent, which should be in our
1717 first delta is against its parent, which should be in our
1603 log, the rest are against the previous delta.
1718 log, the rest are against the previous delta.
1604
1719
1605 If ``addrevisioncb`` is defined, it will be called with arguments of
1720 If ``addrevisioncb`` is defined, it will be called with arguments of
1606 this revlog and the node that was added.
1721 this revlog and the node that was added.
1607 """
1722 """
1608
1723
1609 # track the base of the current delta log
1724 # track the base of the current delta log
1610 content = []
1725 content = []
1611 node = None
1726 node = None
1612
1727
1613 r = len(self)
1728 r = len(self)
1614 end = 0
1729 end = 0
1615 if r:
1730 if r:
1616 end = self.end(r - 1)
1731 end = self.end(r - 1)
1617 ifh = self.opener(self.indexfile, "a+", checkambig=self._checkambig)
1732 ifh = self.opener(self.indexfile, "a+", checkambig=self._checkambig)
1618 isize = r * self._io.size
1733 isize = r * self._io.size
1619 if self._inline:
1734 if self._inline:
1620 transaction.add(self.indexfile, end + isize, r)
1735 transaction.add(self.indexfile, end + isize, r)
1621 dfh = None
1736 dfh = None
1622 else:
1737 else:
1623 transaction.add(self.indexfile, isize, r)
1738 transaction.add(self.indexfile, isize, r)
1624 transaction.add(self.datafile, end)
1739 transaction.add(self.datafile, end)
1625 dfh = self.opener(self.datafile, "a+")
1740 dfh = self.opener(self.datafile, "a+")
1626 def flush():
1741 def flush():
1627 if dfh:
1742 if dfh:
1628 dfh.flush()
1743 dfh.flush()
1629 ifh.flush()
1744 ifh.flush()
1630 try:
1745 try:
1631 # loop through our set of deltas
1746 # loop through our set of deltas
1632 chain = None
1747 chain = None
1633 for chunkdata in iter(lambda: cg.deltachunk(chain), {}):
1748 for chunkdata in iter(lambda: cg.deltachunk(chain), {}):
1634 node = chunkdata['node']
1749 node = chunkdata['node']
1635 p1 = chunkdata['p1']
1750 p1 = chunkdata['p1']
1636 p2 = chunkdata['p2']
1751 p2 = chunkdata['p2']
1637 cs = chunkdata['cs']
1752 cs = chunkdata['cs']
1638 deltabase = chunkdata['deltabase']
1753 deltabase = chunkdata['deltabase']
1639 delta = chunkdata['delta']
1754 delta = chunkdata['delta']
1640 flags = chunkdata['flags'] or REVIDX_DEFAULT_FLAGS
1755 flags = chunkdata['flags'] or REVIDX_DEFAULT_FLAGS
1641
1756
1642 content.append(node)
1757 content.append(node)
1643
1758
1644 link = linkmapper(cs)
1759 link = linkmapper(cs)
1645 if node in self.nodemap:
1760 if node in self.nodemap:
1646 # this can happen if two branches make the same change
1761 # this can happen if two branches make the same change
1647 chain = node
1762 chain = node
1648 continue
1763 continue
1649
1764
1650 for p in (p1, p2):
1765 for p in (p1, p2):
1651 if p not in self.nodemap:
1766 if p not in self.nodemap:
1652 raise LookupError(p, self.indexfile,
1767 raise LookupError(p, self.indexfile,
1653 _('unknown parent'))
1768 _('unknown parent'))
1654
1769
1655 if deltabase not in self.nodemap:
1770 if deltabase not in self.nodemap:
1656 raise LookupError(deltabase, self.indexfile,
1771 raise LookupError(deltabase, self.indexfile,
1657 _('unknown delta base'))
1772 _('unknown delta base'))
1658
1773
1659 baserev = self.rev(deltabase)
1774 baserev = self.rev(deltabase)
1660
1775
1661 if baserev != nullrev and self.iscensored(baserev):
1776 if baserev != nullrev and self.iscensored(baserev):
1662 # if base is censored, delta must be full replacement in a
1777 # if base is censored, delta must be full replacement in a
1663 # single patch operation
1778 # single patch operation
1664 hlen = struct.calcsize(">lll")
1779 hlen = struct.calcsize(">lll")
1665 oldlen = self.rawsize(baserev)
1780 oldlen = self.rawsize(baserev)
1666 newlen = len(delta) - hlen
1781 newlen = len(delta) - hlen
1667 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
1782 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
1668 raise error.CensoredBaseError(self.indexfile,
1783 raise error.CensoredBaseError(self.indexfile,
1669 self.node(baserev))
1784 self.node(baserev))
1670
1785
1671 if not flags and self._peek_iscensored(baserev, delta, flush):
1786 if not flags and self._peek_iscensored(baserev, delta, flush):
1672 flags |= REVIDX_ISCENSORED
1787 flags |= REVIDX_ISCENSORED
1673
1788
1674 # We assume consumers of addrevisioncb will want to retrieve
1789 # We assume consumers of addrevisioncb will want to retrieve
1675 # the added revision, which will require a call to
1790 # the added revision, which will require a call to
1676 # revision(). revision() will fast path if there is a cache
1791 # revision(). revision() will fast path if there is a cache
1677 # hit. So, we tell _addrevision() to always cache in this case.
1792 # hit. So, we tell _addrevision() to always cache in this case.
1678 # We're only using addgroup() in the context of changegroup
1793 # We're only using addgroup() in the context of changegroup
1679 # generation so the revision data can always be handled as raw
1794 # generation so the revision data can always be handled as raw
1680 # by the flagprocessor.
1795 # by the flagprocessor.
1681 chain = self._addrevision(node, None, transaction, link,
1796 chain = self._addrevision(node, None, transaction, link,
1682 p1, p2, flags, (baserev, delta),
1797 p1, p2, flags, (baserev, delta),
1683 ifh, dfh,
1798 ifh, dfh,
1684 alwayscache=bool(addrevisioncb),
1799 alwayscache=bool(addrevisioncb),
1685 raw=True)
1800 raw=True)
1686
1801
1687 if addrevisioncb:
1802 if addrevisioncb:
1688 addrevisioncb(self, chain)
1803 addrevisioncb(self, chain)
1689
1804
1690 if not dfh and not self._inline:
1805 if not dfh and not self._inline:
1691 # addrevision switched from inline to conventional
1806 # addrevision switched from inline to conventional
1692 # reopen the index
1807 # reopen the index
1693 ifh.close()
1808 ifh.close()
1694 dfh = self.opener(self.datafile, "a+")
1809 dfh = self.opener(self.datafile, "a+")
1695 ifh = self.opener(self.indexfile, "a+",
1810 ifh = self.opener(self.indexfile, "a+",
1696 checkambig=self._checkambig)
1811 checkambig=self._checkambig)
1697 finally:
1812 finally:
1698 if dfh:
1813 if dfh:
1699 dfh.close()
1814 dfh.close()
1700 ifh.close()
1815 ifh.close()
1701
1816
1702 return content
1817 return content
1703
1818
1704 def iscensored(self, rev):
1819 def iscensored(self, rev):
1705 """Check if a file revision is censored."""
1820 """Check if a file revision is censored."""
1706 return False
1821 return False
1707
1822
1708 def _peek_iscensored(self, baserev, delta, flush):
1823 def _peek_iscensored(self, baserev, delta, flush):
1709 """Quickly check if a delta produces a censored revision."""
1824 """Quickly check if a delta produces a censored revision."""
1710 return False
1825 return False
1711
1826
1712 def getstrippoint(self, minlink):
1827 def getstrippoint(self, minlink):
1713 """find the minimum rev that must be stripped to strip the linkrev
1828 """find the minimum rev that must be stripped to strip the linkrev
1714
1829
1715 Returns a tuple containing the minimum rev and a set of all revs that
1830 Returns a tuple containing the minimum rev and a set of all revs that
1716 have linkrevs that will be broken by this strip.
1831 have linkrevs that will be broken by this strip.
1717 """
1832 """
1718 brokenrevs = set()
1833 brokenrevs = set()
1719 strippoint = len(self)
1834 strippoint = len(self)
1720
1835
1721 heads = {}
1836 heads = {}
1722 futurelargelinkrevs = set()
1837 futurelargelinkrevs = set()
1723 for head in self.headrevs():
1838 for head in self.headrevs():
1724 headlinkrev = self.linkrev(head)
1839 headlinkrev = self.linkrev(head)
1725 heads[head] = headlinkrev
1840 heads[head] = headlinkrev
1726 if headlinkrev >= minlink:
1841 if headlinkrev >= minlink:
1727 futurelargelinkrevs.add(headlinkrev)
1842 futurelargelinkrevs.add(headlinkrev)
1728
1843
1729 # This algorithm involves walking down the rev graph, starting at the
1844 # This algorithm involves walking down the rev graph, starting at the
1730 # heads. Since the revs are topologically sorted according to linkrev,
1845 # heads. Since the revs are topologically sorted according to linkrev,
1731 # once all head linkrevs are below the minlink, we know there are
1846 # once all head linkrevs are below the minlink, we know there are
1732 # no more revs that could have a linkrev greater than minlink.
1847 # no more revs that could have a linkrev greater than minlink.
1733 # So we can stop walking.
1848 # So we can stop walking.
1734 while futurelargelinkrevs:
1849 while futurelargelinkrevs:
1735 strippoint -= 1
1850 strippoint -= 1
1736 linkrev = heads.pop(strippoint)
1851 linkrev = heads.pop(strippoint)
1737
1852
1738 if linkrev < minlink:
1853 if linkrev < minlink:
1739 brokenrevs.add(strippoint)
1854 brokenrevs.add(strippoint)
1740 else:
1855 else:
1741 futurelargelinkrevs.remove(linkrev)
1856 futurelargelinkrevs.remove(linkrev)
1742
1857
1743 for p in self.parentrevs(strippoint):
1858 for p in self.parentrevs(strippoint):
1744 if p != nullrev:
1859 if p != nullrev:
1745 plinkrev = self.linkrev(p)
1860 plinkrev = self.linkrev(p)
1746 heads[p] = plinkrev
1861 heads[p] = plinkrev
1747 if plinkrev >= minlink:
1862 if plinkrev >= minlink:
1748 futurelargelinkrevs.add(plinkrev)
1863 futurelargelinkrevs.add(plinkrev)
1749
1864
1750 return strippoint, brokenrevs
1865 return strippoint, brokenrevs
1751
1866
1752 def strip(self, minlink, transaction):
1867 def strip(self, minlink, transaction):
1753 """truncate the revlog on the first revision with a linkrev >= minlink
1868 """truncate the revlog on the first revision with a linkrev >= minlink
1754
1869
1755 This function is called when we're stripping revision minlink and
1870 This function is called when we're stripping revision minlink and
1756 its descendants from the repository.
1871 its descendants from the repository.
1757
1872
1758 We have to remove all revisions with linkrev >= minlink, because
1873 We have to remove all revisions with linkrev >= minlink, because
1759 the equivalent changelog revisions will be renumbered after the
1874 the equivalent changelog revisions will be renumbered after the
1760 strip.
1875 strip.
1761
1876
1762 So we truncate the revlog on the first of these revisions, and
1877 So we truncate the revlog on the first of these revisions, and
1763 trust that the caller has saved the revisions that shouldn't be
1878 trust that the caller has saved the revisions that shouldn't be
1764 removed and that it'll re-add them after this truncation.
1879 removed and that it'll re-add them after this truncation.
1765 """
1880 """
1766 if len(self) == 0:
1881 if len(self) == 0:
1767 return
1882 return
1768
1883
1769 rev, _ = self.getstrippoint(minlink)
1884 rev, _ = self.getstrippoint(minlink)
1770 if rev == len(self):
1885 if rev == len(self):
1771 return
1886 return
1772
1887
1773 # first truncate the files on disk
1888 # first truncate the files on disk
1774 end = self.start(rev)
1889 end = self.start(rev)
1775 if not self._inline:
1890 if not self._inline:
1776 transaction.add(self.datafile, end)
1891 transaction.add(self.datafile, end)
1777 end = rev * self._io.size
1892 end = rev * self._io.size
1778 else:
1893 else:
1779 end += rev * self._io.size
1894 end += rev * self._io.size
1780
1895
1781 transaction.add(self.indexfile, end)
1896 transaction.add(self.indexfile, end)
1782
1897
1783 # then reset internal state in memory to forget those revisions
1898 # then reset internal state in memory to forget those revisions
1784 self._cache = None
1899 self._cache = None
1785 self._chaininfocache = {}
1900 self._chaininfocache = {}
1786 self._chunkclear()
1901 self._chunkclear()
1787 for x in xrange(rev, len(self)):
1902 for x in xrange(rev, len(self)):
1788 del self.nodemap[self.node(x)]
1903 del self.nodemap[self.node(x)]
1789
1904
1790 del self.index[rev:-1]
1905 del self.index[rev:-1]
1791
1906
1792 def checksize(self):
1907 def checksize(self):
1793 expected = 0
1908 expected = 0
1794 if len(self):
1909 if len(self):
1795 expected = max(0, self.end(len(self) - 1))
1910 expected = max(0, self.end(len(self) - 1))
1796
1911
1797 try:
1912 try:
1798 f = self.opener(self.datafile)
1913 f = self.opener(self.datafile)
1799 f.seek(0, 2)
1914 f.seek(0, 2)
1800 actual = f.tell()
1915 actual = f.tell()
1801 f.close()
1916 f.close()
1802 dd = actual - expected
1917 dd = actual - expected
1803 except IOError as inst:
1918 except IOError as inst:
1804 if inst.errno != errno.ENOENT:
1919 if inst.errno != errno.ENOENT:
1805 raise
1920 raise
1806 dd = 0
1921 dd = 0
1807
1922
1808 try:
1923 try:
1809 f = self.opener(self.indexfile)
1924 f = self.opener(self.indexfile)
1810 f.seek(0, 2)
1925 f.seek(0, 2)
1811 actual = f.tell()
1926 actual = f.tell()
1812 f.close()
1927 f.close()
1813 s = self._io.size
1928 s = self._io.size
1814 i = max(0, actual // s)
1929 i = max(0, actual // s)
1815 di = actual - (i * s)
1930 di = actual - (i * s)
1816 if self._inline:
1931 if self._inline:
1817 databytes = 0
1932 databytes = 0
1818 for r in self:
1933 for r in self:
1819 databytes += max(0, self.length(r))
1934 databytes += max(0, self.length(r))
1820 dd = 0
1935 dd = 0
1821 di = actual - len(self) * s - databytes
1936 di = actual - len(self) * s - databytes
1822 except IOError as inst:
1937 except IOError as inst:
1823 if inst.errno != errno.ENOENT:
1938 if inst.errno != errno.ENOENT:
1824 raise
1939 raise
1825 di = 0
1940 di = 0
1826
1941
1827 return (dd, di)
1942 return (dd, di)
1828
1943
1829 def files(self):
1944 def files(self):
1830 res = [self.indexfile]
1945 res = [self.indexfile]
1831 if not self._inline:
1946 if not self._inline:
1832 res.append(self.datafile)
1947 res.append(self.datafile)
1833 return res
1948 return res
@@ -1,3250 +1,3256 b''
1 # util.py - Mercurial utility functions and platform specific implementations
1 # util.py - Mercurial utility functions and platform specific implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Mercurial utility functions and platform specific implementations.
10 """Mercurial utility functions and platform specific implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 from __future__ import absolute_import
16 from __future__ import absolute_import
17
17
18 import bz2
18 import bz2
19 import calendar
19 import calendar
20 import collections
20 import collections
21 import datetime
21 import datetime
22 import errno
22 import errno
23 import gc
23 import gc
24 import hashlib
24 import hashlib
25 import imp
25 import imp
26 import os
26 import os
27 import platform as pyplatform
27 import platform as pyplatform
28 import re as remod
28 import re as remod
29 import shutil
29 import shutil
30 import signal
30 import signal
31 import socket
31 import socket
32 import stat
32 import stat
33 import string
33 import string
34 import subprocess
34 import subprocess
35 import sys
35 import sys
36 import tempfile
36 import tempfile
37 import textwrap
37 import textwrap
38 import time
38 import time
39 import traceback
39 import traceback
40 import zlib
40 import zlib
41
41
42 from . import (
42 from . import (
43 encoding,
43 encoding,
44 error,
44 error,
45 i18n,
45 i18n,
46 osutil,
46 osutil,
47 parsers,
47 parsers,
48 pycompat,
48 pycompat,
49 )
49 )
50
50
51 empty = pycompat.empty
51 empty = pycompat.empty
52 httplib = pycompat.httplib
52 httplib = pycompat.httplib
53 httpserver = pycompat.httpserver
53 httpserver = pycompat.httpserver
54 pickle = pycompat.pickle
54 pickle = pycompat.pickle
55 queue = pycompat.queue
55 queue = pycompat.queue
56 socketserver = pycompat.socketserver
56 socketserver = pycompat.socketserver
57 stderr = pycompat.stderr
57 stderr = pycompat.stderr
58 stdin = pycompat.stdin
58 stdin = pycompat.stdin
59 stdout = pycompat.stdout
59 stdout = pycompat.stdout
60 stringio = pycompat.stringio
60 stringio = pycompat.stringio
61 urlerr = pycompat.urlerr
61 urlerr = pycompat.urlerr
62 urlparse = pycompat.urlparse
62 urlparse = pycompat.urlparse
63 urlreq = pycompat.urlreq
63 urlreq = pycompat.urlreq
64 xmlrpclib = pycompat.xmlrpclib
64 xmlrpclib = pycompat.xmlrpclib
65
65
66 if pycompat.osname == 'nt':
66 if pycompat.osname == 'nt':
67 from . import windows as platform
67 from . import windows as platform
68 stdout = platform.winstdout(pycompat.stdout)
68 stdout = platform.winstdout(pycompat.stdout)
69 else:
69 else:
70 from . import posix as platform
70 from . import posix as platform
71
71
72 _ = i18n._
72 _ = i18n._
73
73
74 bindunixsocket = platform.bindunixsocket
74 bindunixsocket = platform.bindunixsocket
75 cachestat = platform.cachestat
75 cachestat = platform.cachestat
76 checkexec = platform.checkexec
76 checkexec = platform.checkexec
77 checklink = platform.checklink
77 checklink = platform.checklink
78 copymode = platform.copymode
78 copymode = platform.copymode
79 executablepath = platform.executablepath
79 executablepath = platform.executablepath
80 expandglobs = platform.expandglobs
80 expandglobs = platform.expandglobs
81 explainexit = platform.explainexit
81 explainexit = platform.explainexit
82 findexe = platform.findexe
82 findexe = platform.findexe
83 gethgcmd = platform.gethgcmd
83 gethgcmd = platform.gethgcmd
84 getuser = platform.getuser
84 getuser = platform.getuser
85 getpid = os.getpid
85 getpid = os.getpid
86 groupmembers = platform.groupmembers
86 groupmembers = platform.groupmembers
87 groupname = platform.groupname
87 groupname = platform.groupname
88 hidewindow = platform.hidewindow
88 hidewindow = platform.hidewindow
89 isexec = platform.isexec
89 isexec = platform.isexec
90 isowner = platform.isowner
90 isowner = platform.isowner
91 localpath = platform.localpath
91 localpath = platform.localpath
92 lookupreg = platform.lookupreg
92 lookupreg = platform.lookupreg
93 makedir = platform.makedir
93 makedir = platform.makedir
94 nlinks = platform.nlinks
94 nlinks = platform.nlinks
95 normpath = platform.normpath
95 normpath = platform.normpath
96 normcase = platform.normcase
96 normcase = platform.normcase
97 normcasespec = platform.normcasespec
97 normcasespec = platform.normcasespec
98 normcasefallback = platform.normcasefallback
98 normcasefallback = platform.normcasefallback
99 openhardlinks = platform.openhardlinks
99 openhardlinks = platform.openhardlinks
100 oslink = platform.oslink
100 oslink = platform.oslink
101 parsepatchoutput = platform.parsepatchoutput
101 parsepatchoutput = platform.parsepatchoutput
102 pconvert = platform.pconvert
102 pconvert = platform.pconvert
103 poll = platform.poll
103 poll = platform.poll
104 popen = platform.popen
104 popen = platform.popen
105 posixfile = platform.posixfile
105 posixfile = platform.posixfile
106 quotecommand = platform.quotecommand
106 quotecommand = platform.quotecommand
107 readpipe = platform.readpipe
107 readpipe = platform.readpipe
108 rename = platform.rename
108 rename = platform.rename
109 removedirs = platform.removedirs
109 removedirs = platform.removedirs
110 samedevice = platform.samedevice
110 samedevice = platform.samedevice
111 samefile = platform.samefile
111 samefile = platform.samefile
112 samestat = platform.samestat
112 samestat = platform.samestat
113 setbinary = platform.setbinary
113 setbinary = platform.setbinary
114 setflags = platform.setflags
114 setflags = platform.setflags
115 setsignalhandler = platform.setsignalhandler
115 setsignalhandler = platform.setsignalhandler
116 shellquote = platform.shellquote
116 shellquote = platform.shellquote
117 spawndetached = platform.spawndetached
117 spawndetached = platform.spawndetached
118 split = platform.split
118 split = platform.split
119 sshargs = platform.sshargs
119 sshargs = platform.sshargs
120 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
120 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
121 statisexec = platform.statisexec
121 statisexec = platform.statisexec
122 statislink = platform.statislink
122 statislink = platform.statislink
123 testpid = platform.testpid
123 testpid = platform.testpid
124 umask = platform.umask
124 umask = platform.umask
125 unlink = platform.unlink
125 unlink = platform.unlink
126 unlinkpath = platform.unlinkpath
126 unlinkpath = platform.unlinkpath
127 username = platform.username
127 username = platform.username
128
128
129 # Python compatibility
129 # Python compatibility
130
130
131 _notset = object()
131 _notset = object()
132
132
133 # disable Python's problematic floating point timestamps (issue4836)
133 # disable Python's problematic floating point timestamps (issue4836)
134 # (Python hypocritically says you shouldn't change this behavior in
134 # (Python hypocritically says you shouldn't change this behavior in
135 # libraries, and sure enough Mercurial is not a library.)
135 # libraries, and sure enough Mercurial is not a library.)
136 os.stat_float_times(False)
136 os.stat_float_times(False)
137
137
138 def safehasattr(thing, attr):
138 def safehasattr(thing, attr):
139 return getattr(thing, attr, _notset) is not _notset
139 return getattr(thing, attr, _notset) is not _notset
140
140
141 def bitsfrom(container):
142 bits = 0
143 for bit in container:
144 bits |= bit
145 return bits
146
141 DIGESTS = {
147 DIGESTS = {
142 'md5': hashlib.md5,
148 'md5': hashlib.md5,
143 'sha1': hashlib.sha1,
149 'sha1': hashlib.sha1,
144 'sha512': hashlib.sha512,
150 'sha512': hashlib.sha512,
145 }
151 }
146 # List of digest types from strongest to weakest
152 # List of digest types from strongest to weakest
147 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
153 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
148
154
149 for k in DIGESTS_BY_STRENGTH:
155 for k in DIGESTS_BY_STRENGTH:
150 assert k in DIGESTS
156 assert k in DIGESTS
151
157
152 class digester(object):
158 class digester(object):
153 """helper to compute digests.
159 """helper to compute digests.
154
160
155 This helper can be used to compute one or more digests given their name.
161 This helper can be used to compute one or more digests given their name.
156
162
157 >>> d = digester(['md5', 'sha1'])
163 >>> d = digester(['md5', 'sha1'])
158 >>> d.update('foo')
164 >>> d.update('foo')
159 >>> [k for k in sorted(d)]
165 >>> [k for k in sorted(d)]
160 ['md5', 'sha1']
166 ['md5', 'sha1']
161 >>> d['md5']
167 >>> d['md5']
162 'acbd18db4cc2f85cedef654fccc4a4d8'
168 'acbd18db4cc2f85cedef654fccc4a4d8'
163 >>> d['sha1']
169 >>> d['sha1']
164 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
170 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
165 >>> digester.preferred(['md5', 'sha1'])
171 >>> digester.preferred(['md5', 'sha1'])
166 'sha1'
172 'sha1'
167 """
173 """
168
174
169 def __init__(self, digests, s=''):
175 def __init__(self, digests, s=''):
170 self._hashes = {}
176 self._hashes = {}
171 for k in digests:
177 for k in digests:
172 if k not in DIGESTS:
178 if k not in DIGESTS:
173 raise Abort(_('unknown digest type: %s') % k)
179 raise Abort(_('unknown digest type: %s') % k)
174 self._hashes[k] = DIGESTS[k]()
180 self._hashes[k] = DIGESTS[k]()
175 if s:
181 if s:
176 self.update(s)
182 self.update(s)
177
183
178 def update(self, data):
184 def update(self, data):
179 for h in self._hashes.values():
185 for h in self._hashes.values():
180 h.update(data)
186 h.update(data)
181
187
182 def __getitem__(self, key):
188 def __getitem__(self, key):
183 if key not in DIGESTS:
189 if key not in DIGESTS:
184 raise Abort(_('unknown digest type: %s') % k)
190 raise Abort(_('unknown digest type: %s') % k)
185 return self._hashes[key].hexdigest()
191 return self._hashes[key].hexdigest()
186
192
187 def __iter__(self):
193 def __iter__(self):
188 return iter(self._hashes)
194 return iter(self._hashes)
189
195
190 @staticmethod
196 @staticmethod
191 def preferred(supported):
197 def preferred(supported):
192 """returns the strongest digest type in both supported and DIGESTS."""
198 """returns the strongest digest type in both supported and DIGESTS."""
193
199
194 for k in DIGESTS_BY_STRENGTH:
200 for k in DIGESTS_BY_STRENGTH:
195 if k in supported:
201 if k in supported:
196 return k
202 return k
197 return None
203 return None
198
204
199 class digestchecker(object):
205 class digestchecker(object):
200 """file handle wrapper that additionally checks content against a given
206 """file handle wrapper that additionally checks content against a given
201 size and digests.
207 size and digests.
202
208
203 d = digestchecker(fh, size, {'md5': '...'})
209 d = digestchecker(fh, size, {'md5': '...'})
204
210
205 When multiple digests are given, all of them are validated.
211 When multiple digests are given, all of them are validated.
206 """
212 """
207
213
208 def __init__(self, fh, size, digests):
214 def __init__(self, fh, size, digests):
209 self._fh = fh
215 self._fh = fh
210 self._size = size
216 self._size = size
211 self._got = 0
217 self._got = 0
212 self._digests = dict(digests)
218 self._digests = dict(digests)
213 self._digester = digester(self._digests.keys())
219 self._digester = digester(self._digests.keys())
214
220
215 def read(self, length=-1):
221 def read(self, length=-1):
216 content = self._fh.read(length)
222 content = self._fh.read(length)
217 self._digester.update(content)
223 self._digester.update(content)
218 self._got += len(content)
224 self._got += len(content)
219 return content
225 return content
220
226
221 def validate(self):
227 def validate(self):
222 if self._size != self._got:
228 if self._size != self._got:
223 raise Abort(_('size mismatch: expected %d, got %d') %
229 raise Abort(_('size mismatch: expected %d, got %d') %
224 (self._size, self._got))
230 (self._size, self._got))
225 for k, v in self._digests.items():
231 for k, v in self._digests.items():
226 if v != self._digester[k]:
232 if v != self._digester[k]:
227 # i18n: first parameter is a digest name
233 # i18n: first parameter is a digest name
228 raise Abort(_('%s mismatch: expected %s, got %s') %
234 raise Abort(_('%s mismatch: expected %s, got %s') %
229 (k, v, self._digester[k]))
235 (k, v, self._digester[k]))
230
236
231 try:
237 try:
232 buffer = buffer
238 buffer = buffer
233 except NameError:
239 except NameError:
234 if not pycompat.ispy3:
240 if not pycompat.ispy3:
235 def buffer(sliceable, offset=0):
241 def buffer(sliceable, offset=0):
236 return sliceable[offset:]
242 return sliceable[offset:]
237 else:
243 else:
238 def buffer(sliceable, offset=0):
244 def buffer(sliceable, offset=0):
239 return memoryview(sliceable)[offset:]
245 return memoryview(sliceable)[offset:]
240
246
241 closefds = pycompat.osname == 'posix'
247 closefds = pycompat.osname == 'posix'
242
248
243 _chunksize = 4096
249 _chunksize = 4096
244
250
245 class bufferedinputpipe(object):
251 class bufferedinputpipe(object):
246 """a manually buffered input pipe
252 """a manually buffered input pipe
247
253
248 Python will not let us use buffered IO and lazy reading with 'polling' at
254 Python will not let us use buffered IO and lazy reading with 'polling' at
249 the same time. We cannot probe the buffer state and select will not detect
255 the same time. We cannot probe the buffer state and select will not detect
250 that data are ready to read if they are already buffered.
256 that data are ready to read if they are already buffered.
251
257
252 This class let us work around that by implementing its own buffering
258 This class let us work around that by implementing its own buffering
253 (allowing efficient readline) while offering a way to know if the buffer is
259 (allowing efficient readline) while offering a way to know if the buffer is
254 empty from the output (allowing collaboration of the buffer with polling).
260 empty from the output (allowing collaboration of the buffer with polling).
255
261
256 This class lives in the 'util' module because it makes use of the 'os'
262 This class lives in the 'util' module because it makes use of the 'os'
257 module from the python stdlib.
263 module from the python stdlib.
258 """
264 """
259
265
260 def __init__(self, input):
266 def __init__(self, input):
261 self._input = input
267 self._input = input
262 self._buffer = []
268 self._buffer = []
263 self._eof = False
269 self._eof = False
264 self._lenbuf = 0
270 self._lenbuf = 0
265
271
266 @property
272 @property
267 def hasbuffer(self):
273 def hasbuffer(self):
268 """True is any data is currently buffered
274 """True is any data is currently buffered
269
275
270 This will be used externally a pre-step for polling IO. If there is
276 This will be used externally a pre-step for polling IO. If there is
271 already data then no polling should be set in place."""
277 already data then no polling should be set in place."""
272 return bool(self._buffer)
278 return bool(self._buffer)
273
279
274 @property
280 @property
275 def closed(self):
281 def closed(self):
276 return self._input.closed
282 return self._input.closed
277
283
278 def fileno(self):
284 def fileno(self):
279 return self._input.fileno()
285 return self._input.fileno()
280
286
281 def close(self):
287 def close(self):
282 return self._input.close()
288 return self._input.close()
283
289
284 def read(self, size):
290 def read(self, size):
285 while (not self._eof) and (self._lenbuf < size):
291 while (not self._eof) and (self._lenbuf < size):
286 self._fillbuffer()
292 self._fillbuffer()
287 return self._frombuffer(size)
293 return self._frombuffer(size)
288
294
289 def readline(self, *args, **kwargs):
295 def readline(self, *args, **kwargs):
290 if 1 < len(self._buffer):
296 if 1 < len(self._buffer):
291 # this should not happen because both read and readline end with a
297 # this should not happen because both read and readline end with a
292 # _frombuffer call that collapse it.
298 # _frombuffer call that collapse it.
293 self._buffer = [''.join(self._buffer)]
299 self._buffer = [''.join(self._buffer)]
294 self._lenbuf = len(self._buffer[0])
300 self._lenbuf = len(self._buffer[0])
295 lfi = -1
301 lfi = -1
296 if self._buffer:
302 if self._buffer:
297 lfi = self._buffer[-1].find('\n')
303 lfi = self._buffer[-1].find('\n')
298 while (not self._eof) and lfi < 0:
304 while (not self._eof) and lfi < 0:
299 self._fillbuffer()
305 self._fillbuffer()
300 if self._buffer:
306 if self._buffer:
301 lfi = self._buffer[-1].find('\n')
307 lfi = self._buffer[-1].find('\n')
302 size = lfi + 1
308 size = lfi + 1
303 if lfi < 0: # end of file
309 if lfi < 0: # end of file
304 size = self._lenbuf
310 size = self._lenbuf
305 elif 1 < len(self._buffer):
311 elif 1 < len(self._buffer):
306 # we need to take previous chunks into account
312 # we need to take previous chunks into account
307 size += self._lenbuf - len(self._buffer[-1])
313 size += self._lenbuf - len(self._buffer[-1])
308 return self._frombuffer(size)
314 return self._frombuffer(size)
309
315
310 def _frombuffer(self, size):
316 def _frombuffer(self, size):
311 """return at most 'size' data from the buffer
317 """return at most 'size' data from the buffer
312
318
313 The data are removed from the buffer."""
319 The data are removed from the buffer."""
314 if size == 0 or not self._buffer:
320 if size == 0 or not self._buffer:
315 return ''
321 return ''
316 buf = self._buffer[0]
322 buf = self._buffer[0]
317 if 1 < len(self._buffer):
323 if 1 < len(self._buffer):
318 buf = ''.join(self._buffer)
324 buf = ''.join(self._buffer)
319
325
320 data = buf[:size]
326 data = buf[:size]
321 buf = buf[len(data):]
327 buf = buf[len(data):]
322 if buf:
328 if buf:
323 self._buffer = [buf]
329 self._buffer = [buf]
324 self._lenbuf = len(buf)
330 self._lenbuf = len(buf)
325 else:
331 else:
326 self._buffer = []
332 self._buffer = []
327 self._lenbuf = 0
333 self._lenbuf = 0
328 return data
334 return data
329
335
330 def _fillbuffer(self):
336 def _fillbuffer(self):
331 """read data to the buffer"""
337 """read data to the buffer"""
332 data = os.read(self._input.fileno(), _chunksize)
338 data = os.read(self._input.fileno(), _chunksize)
333 if not data:
339 if not data:
334 self._eof = True
340 self._eof = True
335 else:
341 else:
336 self._lenbuf += len(data)
342 self._lenbuf += len(data)
337 self._buffer.append(data)
343 self._buffer.append(data)
338
344
339 def popen2(cmd, env=None, newlines=False):
345 def popen2(cmd, env=None, newlines=False):
340 # Setting bufsize to -1 lets the system decide the buffer size.
346 # Setting bufsize to -1 lets the system decide the buffer size.
341 # The default for bufsize is 0, meaning unbuffered. This leads to
347 # The default for bufsize is 0, meaning unbuffered. This leads to
342 # poor performance on Mac OS X: http://bugs.python.org/issue4194
348 # poor performance on Mac OS X: http://bugs.python.org/issue4194
343 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
349 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
344 close_fds=closefds,
350 close_fds=closefds,
345 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
351 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
346 universal_newlines=newlines,
352 universal_newlines=newlines,
347 env=env)
353 env=env)
348 return p.stdin, p.stdout
354 return p.stdin, p.stdout
349
355
350 def popen3(cmd, env=None, newlines=False):
356 def popen3(cmd, env=None, newlines=False):
351 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
357 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
352 return stdin, stdout, stderr
358 return stdin, stdout, stderr
353
359
354 def popen4(cmd, env=None, newlines=False, bufsize=-1):
360 def popen4(cmd, env=None, newlines=False, bufsize=-1):
355 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
361 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
356 close_fds=closefds,
362 close_fds=closefds,
357 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
363 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
358 stderr=subprocess.PIPE,
364 stderr=subprocess.PIPE,
359 universal_newlines=newlines,
365 universal_newlines=newlines,
360 env=env)
366 env=env)
361 return p.stdin, p.stdout, p.stderr, p
367 return p.stdin, p.stdout, p.stderr, p
362
368
363 def version():
369 def version():
364 """Return version information if available."""
370 """Return version information if available."""
365 try:
371 try:
366 from . import __version__
372 from . import __version__
367 return __version__.version
373 return __version__.version
368 except ImportError:
374 except ImportError:
369 return 'unknown'
375 return 'unknown'
370
376
371 def versiontuple(v=None, n=4):
377 def versiontuple(v=None, n=4):
372 """Parses a Mercurial version string into an N-tuple.
378 """Parses a Mercurial version string into an N-tuple.
373
379
374 The version string to be parsed is specified with the ``v`` argument.
380 The version string to be parsed is specified with the ``v`` argument.
375 If it isn't defined, the current Mercurial version string will be parsed.
381 If it isn't defined, the current Mercurial version string will be parsed.
376
382
377 ``n`` can be 2, 3, or 4. Here is how some version strings map to
383 ``n`` can be 2, 3, or 4. Here is how some version strings map to
378 returned values:
384 returned values:
379
385
380 >>> v = '3.6.1+190-df9b73d2d444'
386 >>> v = '3.6.1+190-df9b73d2d444'
381 >>> versiontuple(v, 2)
387 >>> versiontuple(v, 2)
382 (3, 6)
388 (3, 6)
383 >>> versiontuple(v, 3)
389 >>> versiontuple(v, 3)
384 (3, 6, 1)
390 (3, 6, 1)
385 >>> versiontuple(v, 4)
391 >>> versiontuple(v, 4)
386 (3, 6, 1, '190-df9b73d2d444')
392 (3, 6, 1, '190-df9b73d2d444')
387
393
388 >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
394 >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
389 (3, 6, 1, '190-df9b73d2d444+20151118')
395 (3, 6, 1, '190-df9b73d2d444+20151118')
390
396
391 >>> v = '3.6'
397 >>> v = '3.6'
392 >>> versiontuple(v, 2)
398 >>> versiontuple(v, 2)
393 (3, 6)
399 (3, 6)
394 >>> versiontuple(v, 3)
400 >>> versiontuple(v, 3)
395 (3, 6, None)
401 (3, 6, None)
396 >>> versiontuple(v, 4)
402 >>> versiontuple(v, 4)
397 (3, 6, None, None)
403 (3, 6, None, None)
398
404
399 >>> v = '3.9-rc'
405 >>> v = '3.9-rc'
400 >>> versiontuple(v, 2)
406 >>> versiontuple(v, 2)
401 (3, 9)
407 (3, 9)
402 >>> versiontuple(v, 3)
408 >>> versiontuple(v, 3)
403 (3, 9, None)
409 (3, 9, None)
404 >>> versiontuple(v, 4)
410 >>> versiontuple(v, 4)
405 (3, 9, None, 'rc')
411 (3, 9, None, 'rc')
406
412
407 >>> v = '3.9-rc+2-02a8fea4289b'
413 >>> v = '3.9-rc+2-02a8fea4289b'
408 >>> versiontuple(v, 2)
414 >>> versiontuple(v, 2)
409 (3, 9)
415 (3, 9)
410 >>> versiontuple(v, 3)
416 >>> versiontuple(v, 3)
411 (3, 9, None)
417 (3, 9, None)
412 >>> versiontuple(v, 4)
418 >>> versiontuple(v, 4)
413 (3, 9, None, 'rc+2-02a8fea4289b')
419 (3, 9, None, 'rc+2-02a8fea4289b')
414 """
420 """
415 if not v:
421 if not v:
416 v = version()
422 v = version()
417 parts = remod.split('[\+-]', v, 1)
423 parts = remod.split('[\+-]', v, 1)
418 if len(parts) == 1:
424 if len(parts) == 1:
419 vparts, extra = parts[0], None
425 vparts, extra = parts[0], None
420 else:
426 else:
421 vparts, extra = parts
427 vparts, extra = parts
422
428
423 vints = []
429 vints = []
424 for i in vparts.split('.'):
430 for i in vparts.split('.'):
425 try:
431 try:
426 vints.append(int(i))
432 vints.append(int(i))
427 except ValueError:
433 except ValueError:
428 break
434 break
429 # (3, 6) -> (3, 6, None)
435 # (3, 6) -> (3, 6, None)
430 while len(vints) < 3:
436 while len(vints) < 3:
431 vints.append(None)
437 vints.append(None)
432
438
433 if n == 2:
439 if n == 2:
434 return (vints[0], vints[1])
440 return (vints[0], vints[1])
435 if n == 3:
441 if n == 3:
436 return (vints[0], vints[1], vints[2])
442 return (vints[0], vints[1], vints[2])
437 if n == 4:
443 if n == 4:
438 return (vints[0], vints[1], vints[2], extra)
444 return (vints[0], vints[1], vints[2], extra)
439
445
440 # used by parsedate
446 # used by parsedate
441 defaultdateformats = (
447 defaultdateformats = (
442 '%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601
448 '%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601
443 '%Y-%m-%dT%H:%M', # without seconds
449 '%Y-%m-%dT%H:%M', # without seconds
444 '%Y-%m-%dT%H%M%S', # another awful but legal variant without :
450 '%Y-%m-%dT%H%M%S', # another awful but legal variant without :
445 '%Y-%m-%dT%H%M', # without seconds
451 '%Y-%m-%dT%H%M', # without seconds
446 '%Y-%m-%d %H:%M:%S', # our common legal variant
452 '%Y-%m-%d %H:%M:%S', # our common legal variant
447 '%Y-%m-%d %H:%M', # without seconds
453 '%Y-%m-%d %H:%M', # without seconds
448 '%Y-%m-%d %H%M%S', # without :
454 '%Y-%m-%d %H%M%S', # without :
449 '%Y-%m-%d %H%M', # without seconds
455 '%Y-%m-%d %H%M', # without seconds
450 '%Y-%m-%d %I:%M:%S%p',
456 '%Y-%m-%d %I:%M:%S%p',
451 '%Y-%m-%d %H:%M',
457 '%Y-%m-%d %H:%M',
452 '%Y-%m-%d %I:%M%p',
458 '%Y-%m-%d %I:%M%p',
453 '%Y-%m-%d',
459 '%Y-%m-%d',
454 '%m-%d',
460 '%m-%d',
455 '%m/%d',
461 '%m/%d',
456 '%m/%d/%y',
462 '%m/%d/%y',
457 '%m/%d/%Y',
463 '%m/%d/%Y',
458 '%a %b %d %H:%M:%S %Y',
464 '%a %b %d %H:%M:%S %Y',
459 '%a %b %d %I:%M:%S%p %Y',
465 '%a %b %d %I:%M:%S%p %Y',
460 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
466 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
461 '%b %d %H:%M:%S %Y',
467 '%b %d %H:%M:%S %Y',
462 '%b %d %I:%M:%S%p %Y',
468 '%b %d %I:%M:%S%p %Y',
463 '%b %d %H:%M:%S',
469 '%b %d %H:%M:%S',
464 '%b %d %I:%M:%S%p',
470 '%b %d %I:%M:%S%p',
465 '%b %d %H:%M',
471 '%b %d %H:%M',
466 '%b %d %I:%M%p',
472 '%b %d %I:%M%p',
467 '%b %d %Y',
473 '%b %d %Y',
468 '%b %d',
474 '%b %d',
469 '%H:%M:%S',
475 '%H:%M:%S',
470 '%I:%M:%S%p',
476 '%I:%M:%S%p',
471 '%H:%M',
477 '%H:%M',
472 '%I:%M%p',
478 '%I:%M%p',
473 )
479 )
474
480
475 extendeddateformats = defaultdateformats + (
481 extendeddateformats = defaultdateformats + (
476 "%Y",
482 "%Y",
477 "%Y-%m",
483 "%Y-%m",
478 "%b",
484 "%b",
479 "%b %Y",
485 "%b %Y",
480 )
486 )
481
487
482 def cachefunc(func):
488 def cachefunc(func):
483 '''cache the result of function calls'''
489 '''cache the result of function calls'''
484 # XXX doesn't handle keywords args
490 # XXX doesn't handle keywords args
485 if func.__code__.co_argcount == 0:
491 if func.__code__.co_argcount == 0:
486 cache = []
492 cache = []
487 def f():
493 def f():
488 if len(cache) == 0:
494 if len(cache) == 0:
489 cache.append(func())
495 cache.append(func())
490 return cache[0]
496 return cache[0]
491 return f
497 return f
492 cache = {}
498 cache = {}
493 if func.__code__.co_argcount == 1:
499 if func.__code__.co_argcount == 1:
494 # we gain a small amount of time because
500 # we gain a small amount of time because
495 # we don't need to pack/unpack the list
501 # we don't need to pack/unpack the list
496 def f(arg):
502 def f(arg):
497 if arg not in cache:
503 if arg not in cache:
498 cache[arg] = func(arg)
504 cache[arg] = func(arg)
499 return cache[arg]
505 return cache[arg]
500 else:
506 else:
501 def f(*args):
507 def f(*args):
502 if args not in cache:
508 if args not in cache:
503 cache[args] = func(*args)
509 cache[args] = func(*args)
504 return cache[args]
510 return cache[args]
505
511
506 return f
512 return f
507
513
508 class sortdict(dict):
514 class sortdict(dict):
509 '''a simple sorted dictionary'''
515 '''a simple sorted dictionary'''
510 def __init__(self, data=None):
516 def __init__(self, data=None):
511 self._list = []
517 self._list = []
512 if data:
518 if data:
513 self.update(data)
519 self.update(data)
514 def copy(self):
520 def copy(self):
515 return sortdict(self)
521 return sortdict(self)
516 def __setitem__(self, key, val):
522 def __setitem__(self, key, val):
517 if key in self:
523 if key in self:
518 self._list.remove(key)
524 self._list.remove(key)
519 self._list.append(key)
525 self._list.append(key)
520 dict.__setitem__(self, key, val)
526 dict.__setitem__(self, key, val)
521 def __iter__(self):
527 def __iter__(self):
522 return self._list.__iter__()
528 return self._list.__iter__()
523 def update(self, src):
529 def update(self, src):
524 if isinstance(src, dict):
530 if isinstance(src, dict):
525 src = src.iteritems()
531 src = src.iteritems()
526 for k, v in src:
532 for k, v in src:
527 self[k] = v
533 self[k] = v
528 def clear(self):
534 def clear(self):
529 dict.clear(self)
535 dict.clear(self)
530 self._list = []
536 self._list = []
531 def items(self):
537 def items(self):
532 return [(k, self[k]) for k in self._list]
538 return [(k, self[k]) for k in self._list]
533 def __delitem__(self, key):
539 def __delitem__(self, key):
534 dict.__delitem__(self, key)
540 dict.__delitem__(self, key)
535 self._list.remove(key)
541 self._list.remove(key)
536 def pop(self, key, *args, **kwargs):
542 def pop(self, key, *args, **kwargs):
537 dict.pop(self, key, *args, **kwargs)
543 dict.pop(self, key, *args, **kwargs)
538 try:
544 try:
539 self._list.remove(key)
545 self._list.remove(key)
540 except ValueError:
546 except ValueError:
541 pass
547 pass
542 def keys(self):
548 def keys(self):
543 return self._list
549 return self._list
544 def iterkeys(self):
550 def iterkeys(self):
545 return self._list.__iter__()
551 return self._list.__iter__()
546 def iteritems(self):
552 def iteritems(self):
547 for k in self._list:
553 for k in self._list:
548 yield k, self[k]
554 yield k, self[k]
549 def insert(self, index, key, val):
555 def insert(self, index, key, val):
550 self._list.insert(index, key)
556 self._list.insert(index, key)
551 dict.__setitem__(self, key, val)
557 dict.__setitem__(self, key, val)
552 def __repr__(self):
558 def __repr__(self):
553 if not self:
559 if not self:
554 return '%s()' % self.__class__.__name__
560 return '%s()' % self.__class__.__name__
555 return '%s(%r)' % (self.__class__.__name__, self.items())
561 return '%s(%r)' % (self.__class__.__name__, self.items())
556
562
557 class _lrucachenode(object):
563 class _lrucachenode(object):
558 """A node in a doubly linked list.
564 """A node in a doubly linked list.
559
565
560 Holds a reference to nodes on either side as well as a key-value
566 Holds a reference to nodes on either side as well as a key-value
561 pair for the dictionary entry.
567 pair for the dictionary entry.
562 """
568 """
563 __slots__ = (u'next', u'prev', u'key', u'value')
569 __slots__ = (u'next', u'prev', u'key', u'value')
564
570
565 def __init__(self):
571 def __init__(self):
566 self.next = None
572 self.next = None
567 self.prev = None
573 self.prev = None
568
574
569 self.key = _notset
575 self.key = _notset
570 self.value = None
576 self.value = None
571
577
572 def markempty(self):
578 def markempty(self):
573 """Mark the node as emptied."""
579 """Mark the node as emptied."""
574 self.key = _notset
580 self.key = _notset
575
581
576 class lrucachedict(object):
582 class lrucachedict(object):
577 """Dict that caches most recent accesses and sets.
583 """Dict that caches most recent accesses and sets.
578
584
579 The dict consists of an actual backing dict - indexed by original
585 The dict consists of an actual backing dict - indexed by original
580 key - and a doubly linked circular list defining the order of entries in
586 key - and a doubly linked circular list defining the order of entries in
581 the cache.
587 the cache.
582
588
583 The head node is the newest entry in the cache. If the cache is full,
589 The head node is the newest entry in the cache. If the cache is full,
584 we recycle head.prev and make it the new head. Cache accesses result in
590 we recycle head.prev and make it the new head. Cache accesses result in
585 the node being moved to before the existing head and being marked as the
591 the node being moved to before the existing head and being marked as the
586 new head node.
592 new head node.
587 """
593 """
588 def __init__(self, max):
594 def __init__(self, max):
589 self._cache = {}
595 self._cache = {}
590
596
591 self._head = head = _lrucachenode()
597 self._head = head = _lrucachenode()
592 head.prev = head
598 head.prev = head
593 head.next = head
599 head.next = head
594 self._size = 1
600 self._size = 1
595 self._capacity = max
601 self._capacity = max
596
602
597 def __len__(self):
603 def __len__(self):
598 return len(self._cache)
604 return len(self._cache)
599
605
600 def __contains__(self, k):
606 def __contains__(self, k):
601 return k in self._cache
607 return k in self._cache
602
608
603 def __iter__(self):
609 def __iter__(self):
604 # We don't have to iterate in cache order, but why not.
610 # We don't have to iterate in cache order, but why not.
605 n = self._head
611 n = self._head
606 for i in range(len(self._cache)):
612 for i in range(len(self._cache)):
607 yield n.key
613 yield n.key
608 n = n.next
614 n = n.next
609
615
610 def __getitem__(self, k):
616 def __getitem__(self, k):
611 node = self._cache[k]
617 node = self._cache[k]
612 self._movetohead(node)
618 self._movetohead(node)
613 return node.value
619 return node.value
614
620
615 def __setitem__(self, k, v):
621 def __setitem__(self, k, v):
616 node = self._cache.get(k)
622 node = self._cache.get(k)
617 # Replace existing value and mark as newest.
623 # Replace existing value and mark as newest.
618 if node is not None:
624 if node is not None:
619 node.value = v
625 node.value = v
620 self._movetohead(node)
626 self._movetohead(node)
621 return
627 return
622
628
623 if self._size < self._capacity:
629 if self._size < self._capacity:
624 node = self._addcapacity()
630 node = self._addcapacity()
625 else:
631 else:
626 # Grab the last/oldest item.
632 # Grab the last/oldest item.
627 node = self._head.prev
633 node = self._head.prev
628
634
629 # At capacity. Kill the old entry.
635 # At capacity. Kill the old entry.
630 if node.key is not _notset:
636 if node.key is not _notset:
631 del self._cache[node.key]
637 del self._cache[node.key]
632
638
633 node.key = k
639 node.key = k
634 node.value = v
640 node.value = v
635 self._cache[k] = node
641 self._cache[k] = node
636 # And mark it as newest entry. No need to adjust order since it
642 # And mark it as newest entry. No need to adjust order since it
637 # is already self._head.prev.
643 # is already self._head.prev.
638 self._head = node
644 self._head = node
639
645
640 def __delitem__(self, k):
646 def __delitem__(self, k):
641 node = self._cache.pop(k)
647 node = self._cache.pop(k)
642 node.markempty()
648 node.markempty()
643
649
644 # Temporarily mark as newest item before re-adjusting head to make
650 # Temporarily mark as newest item before re-adjusting head to make
645 # this node the oldest item.
651 # this node the oldest item.
646 self._movetohead(node)
652 self._movetohead(node)
647 self._head = node.next
653 self._head = node.next
648
654
649 # Additional dict methods.
655 # Additional dict methods.
650
656
651 def get(self, k, default=None):
657 def get(self, k, default=None):
652 try:
658 try:
653 return self._cache[k].value
659 return self._cache[k].value
654 except KeyError:
660 except KeyError:
655 return default
661 return default
656
662
657 def clear(self):
663 def clear(self):
658 n = self._head
664 n = self._head
659 while n.key is not _notset:
665 while n.key is not _notset:
660 n.markempty()
666 n.markempty()
661 n = n.next
667 n = n.next
662
668
663 self._cache.clear()
669 self._cache.clear()
664
670
665 def copy(self):
671 def copy(self):
666 result = lrucachedict(self._capacity)
672 result = lrucachedict(self._capacity)
667 n = self._head.prev
673 n = self._head.prev
668 # Iterate in oldest-to-newest order, so the copy has the right ordering
674 # Iterate in oldest-to-newest order, so the copy has the right ordering
669 for i in range(len(self._cache)):
675 for i in range(len(self._cache)):
670 result[n.key] = n.value
676 result[n.key] = n.value
671 n = n.prev
677 n = n.prev
672 return result
678 return result
673
679
674 def _movetohead(self, node):
680 def _movetohead(self, node):
675 """Mark a node as the newest, making it the new head.
681 """Mark a node as the newest, making it the new head.
676
682
677 When a node is accessed, it becomes the freshest entry in the LRU
683 When a node is accessed, it becomes the freshest entry in the LRU
678 list, which is denoted by self._head.
684 list, which is denoted by self._head.
679
685
680 Visually, let's make ``N`` the new head node (* denotes head):
686 Visually, let's make ``N`` the new head node (* denotes head):
681
687
682 previous/oldest <-> head <-> next/next newest
688 previous/oldest <-> head <-> next/next newest
683
689
684 ----<->--- A* ---<->-----
690 ----<->--- A* ---<->-----
685 | |
691 | |
686 E <-> D <-> N <-> C <-> B
692 E <-> D <-> N <-> C <-> B
687
693
688 To:
694 To:
689
695
690 ----<->--- N* ---<->-----
696 ----<->--- N* ---<->-----
691 | |
697 | |
692 E <-> D <-> C <-> B <-> A
698 E <-> D <-> C <-> B <-> A
693
699
694 This requires the following moves:
700 This requires the following moves:
695
701
696 C.next = D (node.prev.next = node.next)
702 C.next = D (node.prev.next = node.next)
697 D.prev = C (node.next.prev = node.prev)
703 D.prev = C (node.next.prev = node.prev)
698 E.next = N (head.prev.next = node)
704 E.next = N (head.prev.next = node)
699 N.prev = E (node.prev = head.prev)
705 N.prev = E (node.prev = head.prev)
700 N.next = A (node.next = head)
706 N.next = A (node.next = head)
701 A.prev = N (head.prev = node)
707 A.prev = N (head.prev = node)
702 """
708 """
703 head = self._head
709 head = self._head
704 # C.next = D
710 # C.next = D
705 node.prev.next = node.next
711 node.prev.next = node.next
706 # D.prev = C
712 # D.prev = C
707 node.next.prev = node.prev
713 node.next.prev = node.prev
708 # N.prev = E
714 # N.prev = E
709 node.prev = head.prev
715 node.prev = head.prev
710 # N.next = A
716 # N.next = A
711 # It is tempting to do just "head" here, however if node is
717 # It is tempting to do just "head" here, however if node is
712 # adjacent to head, this will do bad things.
718 # adjacent to head, this will do bad things.
713 node.next = head.prev.next
719 node.next = head.prev.next
714 # E.next = N
720 # E.next = N
715 node.next.prev = node
721 node.next.prev = node
716 # A.prev = N
722 # A.prev = N
717 node.prev.next = node
723 node.prev.next = node
718
724
719 self._head = node
725 self._head = node
720
726
721 def _addcapacity(self):
727 def _addcapacity(self):
722 """Add a node to the circular linked list.
728 """Add a node to the circular linked list.
723
729
724 The new node is inserted before the head node.
730 The new node is inserted before the head node.
725 """
731 """
726 head = self._head
732 head = self._head
727 node = _lrucachenode()
733 node = _lrucachenode()
728 head.prev.next = node
734 head.prev.next = node
729 node.prev = head.prev
735 node.prev = head.prev
730 node.next = head
736 node.next = head
731 head.prev = node
737 head.prev = node
732 self._size += 1
738 self._size += 1
733 return node
739 return node
734
740
735 def lrucachefunc(func):
741 def lrucachefunc(func):
736 '''cache most recent results of function calls'''
742 '''cache most recent results of function calls'''
737 cache = {}
743 cache = {}
738 order = collections.deque()
744 order = collections.deque()
739 if func.__code__.co_argcount == 1:
745 if func.__code__.co_argcount == 1:
740 def f(arg):
746 def f(arg):
741 if arg not in cache:
747 if arg not in cache:
742 if len(cache) > 20:
748 if len(cache) > 20:
743 del cache[order.popleft()]
749 del cache[order.popleft()]
744 cache[arg] = func(arg)
750 cache[arg] = func(arg)
745 else:
751 else:
746 order.remove(arg)
752 order.remove(arg)
747 order.append(arg)
753 order.append(arg)
748 return cache[arg]
754 return cache[arg]
749 else:
755 else:
750 def f(*args):
756 def f(*args):
751 if args not in cache:
757 if args not in cache:
752 if len(cache) > 20:
758 if len(cache) > 20:
753 del cache[order.popleft()]
759 del cache[order.popleft()]
754 cache[args] = func(*args)
760 cache[args] = func(*args)
755 else:
761 else:
756 order.remove(args)
762 order.remove(args)
757 order.append(args)
763 order.append(args)
758 return cache[args]
764 return cache[args]
759
765
760 return f
766 return f
761
767
762 class propertycache(object):
768 class propertycache(object):
763 def __init__(self, func):
769 def __init__(self, func):
764 self.func = func
770 self.func = func
765 self.name = func.__name__
771 self.name = func.__name__
766 def __get__(self, obj, type=None):
772 def __get__(self, obj, type=None):
767 result = self.func(obj)
773 result = self.func(obj)
768 self.cachevalue(obj, result)
774 self.cachevalue(obj, result)
769 return result
775 return result
770
776
771 def cachevalue(self, obj, value):
777 def cachevalue(self, obj, value):
772 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
778 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
773 obj.__dict__[self.name] = value
779 obj.__dict__[self.name] = value
774
780
775 def pipefilter(s, cmd):
781 def pipefilter(s, cmd):
776 '''filter string S through command CMD, returning its output'''
782 '''filter string S through command CMD, returning its output'''
777 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
783 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
778 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
784 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
779 pout, perr = p.communicate(s)
785 pout, perr = p.communicate(s)
780 return pout
786 return pout
781
787
782 def tempfilter(s, cmd):
788 def tempfilter(s, cmd):
783 '''filter string S through a pair of temporary files with CMD.
789 '''filter string S through a pair of temporary files with CMD.
784 CMD is used as a template to create the real command to be run,
790 CMD is used as a template to create the real command to be run,
785 with the strings INFILE and OUTFILE replaced by the real names of
791 with the strings INFILE and OUTFILE replaced by the real names of
786 the temporary files generated.'''
792 the temporary files generated.'''
787 inname, outname = None, None
793 inname, outname = None, None
788 try:
794 try:
789 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
795 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
790 fp = os.fdopen(infd, 'wb')
796 fp = os.fdopen(infd, 'wb')
791 fp.write(s)
797 fp.write(s)
792 fp.close()
798 fp.close()
793 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
799 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
794 os.close(outfd)
800 os.close(outfd)
795 cmd = cmd.replace('INFILE', inname)
801 cmd = cmd.replace('INFILE', inname)
796 cmd = cmd.replace('OUTFILE', outname)
802 cmd = cmd.replace('OUTFILE', outname)
797 code = os.system(cmd)
803 code = os.system(cmd)
798 if pycompat.sysplatform == 'OpenVMS' and code & 1:
804 if pycompat.sysplatform == 'OpenVMS' and code & 1:
799 code = 0
805 code = 0
800 if code:
806 if code:
801 raise Abort(_("command '%s' failed: %s") %
807 raise Abort(_("command '%s' failed: %s") %
802 (cmd, explainexit(code)))
808 (cmd, explainexit(code)))
803 return readfile(outname)
809 return readfile(outname)
804 finally:
810 finally:
805 try:
811 try:
806 if inname:
812 if inname:
807 os.unlink(inname)
813 os.unlink(inname)
808 except OSError:
814 except OSError:
809 pass
815 pass
810 try:
816 try:
811 if outname:
817 if outname:
812 os.unlink(outname)
818 os.unlink(outname)
813 except OSError:
819 except OSError:
814 pass
820 pass
815
821
816 filtertable = {
822 filtertable = {
817 'tempfile:': tempfilter,
823 'tempfile:': tempfilter,
818 'pipe:': pipefilter,
824 'pipe:': pipefilter,
819 }
825 }
820
826
821 def filter(s, cmd):
827 def filter(s, cmd):
822 "filter a string through a command that transforms its input to its output"
828 "filter a string through a command that transforms its input to its output"
823 for name, fn in filtertable.iteritems():
829 for name, fn in filtertable.iteritems():
824 if cmd.startswith(name):
830 if cmd.startswith(name):
825 return fn(s, cmd[len(name):].lstrip())
831 return fn(s, cmd[len(name):].lstrip())
826 return pipefilter(s, cmd)
832 return pipefilter(s, cmd)
827
833
828 def binary(s):
834 def binary(s):
829 """return true if a string is binary data"""
835 """return true if a string is binary data"""
830 return bool(s and '\0' in s)
836 return bool(s and '\0' in s)
831
837
832 def increasingchunks(source, min=1024, max=65536):
838 def increasingchunks(source, min=1024, max=65536):
833 '''return no less than min bytes per chunk while data remains,
839 '''return no less than min bytes per chunk while data remains,
834 doubling min after each chunk until it reaches max'''
840 doubling min after each chunk until it reaches max'''
835 def log2(x):
841 def log2(x):
836 if not x:
842 if not x:
837 return 0
843 return 0
838 i = 0
844 i = 0
839 while x:
845 while x:
840 x >>= 1
846 x >>= 1
841 i += 1
847 i += 1
842 return i - 1
848 return i - 1
843
849
844 buf = []
850 buf = []
845 blen = 0
851 blen = 0
846 for chunk in source:
852 for chunk in source:
847 buf.append(chunk)
853 buf.append(chunk)
848 blen += len(chunk)
854 blen += len(chunk)
849 if blen >= min:
855 if blen >= min:
850 if min < max:
856 if min < max:
851 min = min << 1
857 min = min << 1
852 nmin = 1 << log2(blen)
858 nmin = 1 << log2(blen)
853 if nmin > min:
859 if nmin > min:
854 min = nmin
860 min = nmin
855 if min > max:
861 if min > max:
856 min = max
862 min = max
857 yield ''.join(buf)
863 yield ''.join(buf)
858 blen = 0
864 blen = 0
859 buf = []
865 buf = []
860 if buf:
866 if buf:
861 yield ''.join(buf)
867 yield ''.join(buf)
862
868
863 Abort = error.Abort
869 Abort = error.Abort
864
870
865 def always(fn):
871 def always(fn):
866 return True
872 return True
867
873
868 def never(fn):
874 def never(fn):
869 return False
875 return False
870
876
871 def nogc(func):
877 def nogc(func):
872 """disable garbage collector
878 """disable garbage collector
873
879
874 Python's garbage collector triggers a GC each time a certain number of
880 Python's garbage collector triggers a GC each time a certain number of
875 container objects (the number being defined by gc.get_threshold()) are
881 container objects (the number being defined by gc.get_threshold()) are
876 allocated even when marked not to be tracked by the collector. Tracking has
882 allocated even when marked not to be tracked by the collector. Tracking has
877 no effect on when GCs are triggered, only on what objects the GC looks
883 no effect on when GCs are triggered, only on what objects the GC looks
878 into. As a workaround, disable GC while building complex (huge)
884 into. As a workaround, disable GC while building complex (huge)
879 containers.
885 containers.
880
886
881 This garbage collector issue have been fixed in 2.7.
887 This garbage collector issue have been fixed in 2.7.
882 """
888 """
883 if sys.version_info >= (2, 7):
889 if sys.version_info >= (2, 7):
884 return func
890 return func
885 def wrapper(*args, **kwargs):
891 def wrapper(*args, **kwargs):
886 gcenabled = gc.isenabled()
892 gcenabled = gc.isenabled()
887 gc.disable()
893 gc.disable()
888 try:
894 try:
889 return func(*args, **kwargs)
895 return func(*args, **kwargs)
890 finally:
896 finally:
891 if gcenabled:
897 if gcenabled:
892 gc.enable()
898 gc.enable()
893 return wrapper
899 return wrapper
894
900
895 def pathto(root, n1, n2):
901 def pathto(root, n1, n2):
896 '''return the relative path from one place to another.
902 '''return the relative path from one place to another.
897 root should use os.sep to separate directories
903 root should use os.sep to separate directories
898 n1 should use os.sep to separate directories
904 n1 should use os.sep to separate directories
899 n2 should use "/" to separate directories
905 n2 should use "/" to separate directories
900 returns an os.sep-separated path.
906 returns an os.sep-separated path.
901
907
902 If n1 is a relative path, it's assumed it's
908 If n1 is a relative path, it's assumed it's
903 relative to root.
909 relative to root.
904 n2 should always be relative to root.
910 n2 should always be relative to root.
905 '''
911 '''
906 if not n1:
912 if not n1:
907 return localpath(n2)
913 return localpath(n2)
908 if os.path.isabs(n1):
914 if os.path.isabs(n1):
909 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
915 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
910 return os.path.join(root, localpath(n2))
916 return os.path.join(root, localpath(n2))
911 n2 = '/'.join((pconvert(root), n2))
917 n2 = '/'.join((pconvert(root), n2))
912 a, b = splitpath(n1), n2.split('/')
918 a, b = splitpath(n1), n2.split('/')
913 a.reverse()
919 a.reverse()
914 b.reverse()
920 b.reverse()
915 while a and b and a[-1] == b[-1]:
921 while a and b and a[-1] == b[-1]:
916 a.pop()
922 a.pop()
917 b.pop()
923 b.pop()
918 b.reverse()
924 b.reverse()
919 return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
925 return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
920
926
921 def mainfrozen():
927 def mainfrozen():
922 """return True if we are a frozen executable.
928 """return True if we are a frozen executable.
923
929
924 The code supports py2exe (most common, Windows only) and tools/freeze
930 The code supports py2exe (most common, Windows only) and tools/freeze
925 (portable, not much used).
931 (portable, not much used).
926 """
932 """
927 return (safehasattr(sys, "frozen") or # new py2exe
933 return (safehasattr(sys, "frozen") or # new py2exe
928 safehasattr(sys, "importers") or # old py2exe
934 safehasattr(sys, "importers") or # old py2exe
929 imp.is_frozen(u"__main__")) # tools/freeze
935 imp.is_frozen(u"__main__")) # tools/freeze
930
936
931 # the location of data files matching the source code
937 # the location of data files matching the source code
932 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
938 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
933 # executable version (py2exe) doesn't support __file__
939 # executable version (py2exe) doesn't support __file__
934 datapath = os.path.dirname(pycompat.sysexecutable)
940 datapath = os.path.dirname(pycompat.sysexecutable)
935 else:
941 else:
936 datapath = os.path.dirname(__file__)
942 datapath = os.path.dirname(__file__)
937
943
938 if not isinstance(datapath, bytes):
944 if not isinstance(datapath, bytes):
939 datapath = pycompat.fsencode(datapath)
945 datapath = pycompat.fsencode(datapath)
940
946
941 i18n.setdatapath(datapath)
947 i18n.setdatapath(datapath)
942
948
943 _hgexecutable = None
949 _hgexecutable = None
944
950
945 def hgexecutable():
951 def hgexecutable():
946 """return location of the 'hg' executable.
952 """return location of the 'hg' executable.
947
953
948 Defaults to $HG or 'hg' in the search path.
954 Defaults to $HG or 'hg' in the search path.
949 """
955 """
950 if _hgexecutable is None:
956 if _hgexecutable is None:
951 hg = encoding.environ.get('HG')
957 hg = encoding.environ.get('HG')
952 mainmod = sys.modules['__main__']
958 mainmod = sys.modules['__main__']
953 if hg:
959 if hg:
954 _sethgexecutable(hg)
960 _sethgexecutable(hg)
955 elif mainfrozen():
961 elif mainfrozen():
956 if getattr(sys, 'frozen', None) == 'macosx_app':
962 if getattr(sys, 'frozen', None) == 'macosx_app':
957 # Env variable set by py2app
963 # Env variable set by py2app
958 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
964 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
959 else:
965 else:
960 _sethgexecutable(pycompat.sysexecutable)
966 _sethgexecutable(pycompat.sysexecutable)
961 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
967 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
962 _sethgexecutable(mainmod.__file__)
968 _sethgexecutable(mainmod.__file__)
963 else:
969 else:
964 exe = findexe('hg') or os.path.basename(sys.argv[0])
970 exe = findexe('hg') or os.path.basename(sys.argv[0])
965 _sethgexecutable(exe)
971 _sethgexecutable(exe)
966 return _hgexecutable
972 return _hgexecutable
967
973
968 def _sethgexecutable(path):
974 def _sethgexecutable(path):
969 """set location of the 'hg' executable"""
975 """set location of the 'hg' executable"""
970 global _hgexecutable
976 global _hgexecutable
971 _hgexecutable = path
977 _hgexecutable = path
972
978
973 def _isstdout(f):
979 def _isstdout(f):
974 fileno = getattr(f, 'fileno', None)
980 fileno = getattr(f, 'fileno', None)
975 return fileno and fileno() == sys.__stdout__.fileno()
981 return fileno and fileno() == sys.__stdout__.fileno()
976
982
977 def shellenviron(environ=None):
983 def shellenviron(environ=None):
978 """return environ with optional override, useful for shelling out"""
984 """return environ with optional override, useful for shelling out"""
979 def py2shell(val):
985 def py2shell(val):
980 'convert python object into string that is useful to shell'
986 'convert python object into string that is useful to shell'
981 if val is None or val is False:
987 if val is None or val is False:
982 return '0'
988 return '0'
983 if val is True:
989 if val is True:
984 return '1'
990 return '1'
985 return str(val)
991 return str(val)
986 env = dict(encoding.environ)
992 env = dict(encoding.environ)
987 if environ:
993 if environ:
988 env.update((k, py2shell(v)) for k, v in environ.iteritems())
994 env.update((k, py2shell(v)) for k, v in environ.iteritems())
989 env['HG'] = hgexecutable()
995 env['HG'] = hgexecutable()
990 return env
996 return env
991
997
992 def system(cmd, environ=None, cwd=None, onerr=None, errprefix=None, out=None):
998 def system(cmd, environ=None, cwd=None, onerr=None, errprefix=None, out=None):
993 '''enhanced shell command execution.
999 '''enhanced shell command execution.
994 run with environment maybe modified, maybe in different dir.
1000 run with environment maybe modified, maybe in different dir.
995
1001
996 if command fails and onerr is None, return status, else raise onerr
1002 if command fails and onerr is None, return status, else raise onerr
997 object as exception.
1003 object as exception.
998
1004
999 if out is specified, it is assumed to be a file-like object that has a
1005 if out is specified, it is assumed to be a file-like object that has a
1000 write() method. stdout and stderr will be redirected to out.'''
1006 write() method. stdout and stderr will be redirected to out.'''
1001 try:
1007 try:
1002 stdout.flush()
1008 stdout.flush()
1003 except Exception:
1009 except Exception:
1004 pass
1010 pass
1005 origcmd = cmd
1011 origcmd = cmd
1006 cmd = quotecommand(cmd)
1012 cmd = quotecommand(cmd)
1007 if pycompat.sysplatform == 'plan9' and (sys.version_info[0] == 2
1013 if pycompat.sysplatform == 'plan9' and (sys.version_info[0] == 2
1008 and sys.version_info[1] < 7):
1014 and sys.version_info[1] < 7):
1009 # subprocess kludge to work around issues in half-baked Python
1015 # subprocess kludge to work around issues in half-baked Python
1010 # ports, notably bichued/python:
1016 # ports, notably bichued/python:
1011 if not cwd is None:
1017 if not cwd is None:
1012 os.chdir(cwd)
1018 os.chdir(cwd)
1013 rc = os.system(cmd)
1019 rc = os.system(cmd)
1014 else:
1020 else:
1015 env = shellenviron(environ)
1021 env = shellenviron(environ)
1016 if out is None or _isstdout(out):
1022 if out is None or _isstdout(out):
1017 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
1023 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
1018 env=env, cwd=cwd)
1024 env=env, cwd=cwd)
1019 else:
1025 else:
1020 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
1026 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
1021 env=env, cwd=cwd, stdout=subprocess.PIPE,
1027 env=env, cwd=cwd, stdout=subprocess.PIPE,
1022 stderr=subprocess.STDOUT)
1028 stderr=subprocess.STDOUT)
1023 for line in iter(proc.stdout.readline, ''):
1029 for line in iter(proc.stdout.readline, ''):
1024 out.write(line)
1030 out.write(line)
1025 proc.wait()
1031 proc.wait()
1026 rc = proc.returncode
1032 rc = proc.returncode
1027 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
1033 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
1028 rc = 0
1034 rc = 0
1029 if rc and onerr:
1035 if rc and onerr:
1030 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
1036 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
1031 explainexit(rc)[0])
1037 explainexit(rc)[0])
1032 if errprefix:
1038 if errprefix:
1033 errmsg = '%s: %s' % (errprefix, errmsg)
1039 errmsg = '%s: %s' % (errprefix, errmsg)
1034 raise onerr(errmsg)
1040 raise onerr(errmsg)
1035 return rc
1041 return rc
1036
1042
1037 def checksignature(func):
1043 def checksignature(func):
1038 '''wrap a function with code to check for calling errors'''
1044 '''wrap a function with code to check for calling errors'''
1039 def check(*args, **kwargs):
1045 def check(*args, **kwargs):
1040 try:
1046 try:
1041 return func(*args, **kwargs)
1047 return func(*args, **kwargs)
1042 except TypeError:
1048 except TypeError:
1043 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1049 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1044 raise error.SignatureError
1050 raise error.SignatureError
1045 raise
1051 raise
1046
1052
1047 return check
1053 return check
1048
1054
1049 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1055 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1050 '''copy a file, preserving mode and optionally other stat info like
1056 '''copy a file, preserving mode and optionally other stat info like
1051 atime/mtime
1057 atime/mtime
1052
1058
1053 checkambig argument is used with filestat, and is useful only if
1059 checkambig argument is used with filestat, and is useful only if
1054 destination file is guarded by any lock (e.g. repo.lock or
1060 destination file is guarded by any lock (e.g. repo.lock or
1055 repo.wlock).
1061 repo.wlock).
1056
1062
1057 copystat and checkambig should be exclusive.
1063 copystat and checkambig should be exclusive.
1058 '''
1064 '''
1059 assert not (copystat and checkambig)
1065 assert not (copystat and checkambig)
1060 oldstat = None
1066 oldstat = None
1061 if os.path.lexists(dest):
1067 if os.path.lexists(dest):
1062 if checkambig:
1068 if checkambig:
1063 oldstat = checkambig and filestat(dest)
1069 oldstat = checkambig and filestat(dest)
1064 unlink(dest)
1070 unlink(dest)
1065 # hardlinks are problematic on CIFS, quietly ignore this flag
1071 # hardlinks are problematic on CIFS, quietly ignore this flag
1066 # until we find a way to work around it cleanly (issue4546)
1072 # until we find a way to work around it cleanly (issue4546)
1067 if False and hardlink:
1073 if False and hardlink:
1068 try:
1074 try:
1069 oslink(src, dest)
1075 oslink(src, dest)
1070 return
1076 return
1071 except (IOError, OSError):
1077 except (IOError, OSError):
1072 pass # fall back to normal copy
1078 pass # fall back to normal copy
1073 if os.path.islink(src):
1079 if os.path.islink(src):
1074 os.symlink(os.readlink(src), dest)
1080 os.symlink(os.readlink(src), dest)
1075 # copytime is ignored for symlinks, but in general copytime isn't needed
1081 # copytime is ignored for symlinks, but in general copytime isn't needed
1076 # for them anyway
1082 # for them anyway
1077 else:
1083 else:
1078 try:
1084 try:
1079 shutil.copyfile(src, dest)
1085 shutil.copyfile(src, dest)
1080 if copystat:
1086 if copystat:
1081 # copystat also copies mode
1087 # copystat also copies mode
1082 shutil.copystat(src, dest)
1088 shutil.copystat(src, dest)
1083 else:
1089 else:
1084 shutil.copymode(src, dest)
1090 shutil.copymode(src, dest)
1085 if oldstat and oldstat.stat:
1091 if oldstat and oldstat.stat:
1086 newstat = filestat(dest)
1092 newstat = filestat(dest)
1087 if newstat.isambig(oldstat):
1093 if newstat.isambig(oldstat):
1088 # stat of copied file is ambiguous to original one
1094 # stat of copied file is ambiguous to original one
1089 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1095 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1090 os.utime(dest, (advanced, advanced))
1096 os.utime(dest, (advanced, advanced))
1091 except shutil.Error as inst:
1097 except shutil.Error as inst:
1092 raise Abort(str(inst))
1098 raise Abort(str(inst))
1093
1099
1094 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
1100 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
1095 """Copy a directory tree using hardlinks if possible."""
1101 """Copy a directory tree using hardlinks if possible."""
1096 num = 0
1102 num = 0
1097
1103
1098 if hardlink is None:
1104 if hardlink is None:
1099 hardlink = (os.stat(src).st_dev ==
1105 hardlink = (os.stat(src).st_dev ==
1100 os.stat(os.path.dirname(dst)).st_dev)
1106 os.stat(os.path.dirname(dst)).st_dev)
1101 if hardlink:
1107 if hardlink:
1102 topic = _('linking')
1108 topic = _('linking')
1103 else:
1109 else:
1104 topic = _('copying')
1110 topic = _('copying')
1105
1111
1106 if os.path.isdir(src):
1112 if os.path.isdir(src):
1107 os.mkdir(dst)
1113 os.mkdir(dst)
1108 for name, kind in osutil.listdir(src):
1114 for name, kind in osutil.listdir(src):
1109 srcname = os.path.join(src, name)
1115 srcname = os.path.join(src, name)
1110 dstname = os.path.join(dst, name)
1116 dstname = os.path.join(dst, name)
1111 def nprog(t, pos):
1117 def nprog(t, pos):
1112 if pos is not None:
1118 if pos is not None:
1113 return progress(t, pos + num)
1119 return progress(t, pos + num)
1114 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
1120 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
1115 num += n
1121 num += n
1116 else:
1122 else:
1117 if hardlink:
1123 if hardlink:
1118 try:
1124 try:
1119 oslink(src, dst)
1125 oslink(src, dst)
1120 except (IOError, OSError):
1126 except (IOError, OSError):
1121 hardlink = False
1127 hardlink = False
1122 shutil.copy(src, dst)
1128 shutil.copy(src, dst)
1123 else:
1129 else:
1124 shutil.copy(src, dst)
1130 shutil.copy(src, dst)
1125 num += 1
1131 num += 1
1126 progress(topic, num)
1132 progress(topic, num)
1127 progress(topic, None)
1133 progress(topic, None)
1128
1134
1129 return hardlink, num
1135 return hardlink, num
1130
1136
1131 _winreservednames = '''con prn aux nul
1137 _winreservednames = '''con prn aux nul
1132 com1 com2 com3 com4 com5 com6 com7 com8 com9
1138 com1 com2 com3 com4 com5 com6 com7 com8 com9
1133 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
1139 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
1134 _winreservedchars = ':*?"<>|'
1140 _winreservedchars = ':*?"<>|'
1135 def checkwinfilename(path):
1141 def checkwinfilename(path):
1136 r'''Check that the base-relative path is a valid filename on Windows.
1142 r'''Check that the base-relative path is a valid filename on Windows.
1137 Returns None if the path is ok, or a UI string describing the problem.
1143 Returns None if the path is ok, or a UI string describing the problem.
1138
1144
1139 >>> checkwinfilename("just/a/normal/path")
1145 >>> checkwinfilename("just/a/normal/path")
1140 >>> checkwinfilename("foo/bar/con.xml")
1146 >>> checkwinfilename("foo/bar/con.xml")
1141 "filename contains 'con', which is reserved on Windows"
1147 "filename contains 'con', which is reserved on Windows"
1142 >>> checkwinfilename("foo/con.xml/bar")
1148 >>> checkwinfilename("foo/con.xml/bar")
1143 "filename contains 'con', which is reserved on Windows"
1149 "filename contains 'con', which is reserved on Windows"
1144 >>> checkwinfilename("foo/bar/xml.con")
1150 >>> checkwinfilename("foo/bar/xml.con")
1145 >>> checkwinfilename("foo/bar/AUX/bla.txt")
1151 >>> checkwinfilename("foo/bar/AUX/bla.txt")
1146 "filename contains 'AUX', which is reserved on Windows"
1152 "filename contains 'AUX', which is reserved on Windows"
1147 >>> checkwinfilename("foo/bar/bla:.txt")
1153 >>> checkwinfilename("foo/bar/bla:.txt")
1148 "filename contains ':', which is reserved on Windows"
1154 "filename contains ':', which is reserved on Windows"
1149 >>> checkwinfilename("foo/bar/b\07la.txt")
1155 >>> checkwinfilename("foo/bar/b\07la.txt")
1150 "filename contains '\\x07', which is invalid on Windows"
1156 "filename contains '\\x07', which is invalid on Windows"
1151 >>> checkwinfilename("foo/bar/bla ")
1157 >>> checkwinfilename("foo/bar/bla ")
1152 "filename ends with ' ', which is not allowed on Windows"
1158 "filename ends with ' ', which is not allowed on Windows"
1153 >>> checkwinfilename("../bar")
1159 >>> checkwinfilename("../bar")
1154 >>> checkwinfilename("foo\\")
1160 >>> checkwinfilename("foo\\")
1155 "filename ends with '\\', which is invalid on Windows"
1161 "filename ends with '\\', which is invalid on Windows"
1156 >>> checkwinfilename("foo\\/bar")
1162 >>> checkwinfilename("foo\\/bar")
1157 "directory name ends with '\\', which is invalid on Windows"
1163 "directory name ends with '\\', which is invalid on Windows"
1158 '''
1164 '''
1159 if path.endswith('\\'):
1165 if path.endswith('\\'):
1160 return _("filename ends with '\\', which is invalid on Windows")
1166 return _("filename ends with '\\', which is invalid on Windows")
1161 if '\\/' in path:
1167 if '\\/' in path:
1162 return _("directory name ends with '\\', which is invalid on Windows")
1168 return _("directory name ends with '\\', which is invalid on Windows")
1163 for n in path.replace('\\', '/').split('/'):
1169 for n in path.replace('\\', '/').split('/'):
1164 if not n:
1170 if not n:
1165 continue
1171 continue
1166 for c in n:
1172 for c in n:
1167 if c in _winreservedchars:
1173 if c in _winreservedchars:
1168 return _("filename contains '%s', which is reserved "
1174 return _("filename contains '%s', which is reserved "
1169 "on Windows") % c
1175 "on Windows") % c
1170 if ord(c) <= 31:
1176 if ord(c) <= 31:
1171 return _("filename contains %r, which is invalid "
1177 return _("filename contains %r, which is invalid "
1172 "on Windows") % c
1178 "on Windows") % c
1173 base = n.split('.')[0]
1179 base = n.split('.')[0]
1174 if base and base.lower() in _winreservednames:
1180 if base and base.lower() in _winreservednames:
1175 return _("filename contains '%s', which is reserved "
1181 return _("filename contains '%s', which is reserved "
1176 "on Windows") % base
1182 "on Windows") % base
1177 t = n[-1]
1183 t = n[-1]
1178 if t in '. ' and n not in '..':
1184 if t in '. ' and n not in '..':
1179 return _("filename ends with '%s', which is not allowed "
1185 return _("filename ends with '%s', which is not allowed "
1180 "on Windows") % t
1186 "on Windows") % t
1181
1187
1182 if pycompat.osname == 'nt':
1188 if pycompat.osname == 'nt':
1183 checkosfilename = checkwinfilename
1189 checkosfilename = checkwinfilename
1184 else:
1190 else:
1185 checkosfilename = platform.checkosfilename
1191 checkosfilename = platform.checkosfilename
1186
1192
1187 def makelock(info, pathname):
1193 def makelock(info, pathname):
1188 try:
1194 try:
1189 return os.symlink(info, pathname)
1195 return os.symlink(info, pathname)
1190 except OSError as why:
1196 except OSError as why:
1191 if why.errno == errno.EEXIST:
1197 if why.errno == errno.EEXIST:
1192 raise
1198 raise
1193 except AttributeError: # no symlink in os
1199 except AttributeError: # no symlink in os
1194 pass
1200 pass
1195
1201
1196 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
1202 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
1197 os.write(ld, info)
1203 os.write(ld, info)
1198 os.close(ld)
1204 os.close(ld)
1199
1205
1200 def readlock(pathname):
1206 def readlock(pathname):
1201 try:
1207 try:
1202 return os.readlink(pathname)
1208 return os.readlink(pathname)
1203 except OSError as why:
1209 except OSError as why:
1204 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1210 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1205 raise
1211 raise
1206 except AttributeError: # no symlink in os
1212 except AttributeError: # no symlink in os
1207 pass
1213 pass
1208 fp = posixfile(pathname)
1214 fp = posixfile(pathname)
1209 r = fp.read()
1215 r = fp.read()
1210 fp.close()
1216 fp.close()
1211 return r
1217 return r
1212
1218
1213 def fstat(fp):
1219 def fstat(fp):
1214 '''stat file object that may not have fileno method.'''
1220 '''stat file object that may not have fileno method.'''
1215 try:
1221 try:
1216 return os.fstat(fp.fileno())
1222 return os.fstat(fp.fileno())
1217 except AttributeError:
1223 except AttributeError:
1218 return os.stat(fp.name)
1224 return os.stat(fp.name)
1219
1225
1220 # File system features
1226 # File system features
1221
1227
1222 def fscasesensitive(path):
1228 def fscasesensitive(path):
1223 """
1229 """
1224 Return true if the given path is on a case-sensitive filesystem
1230 Return true if the given path is on a case-sensitive filesystem
1225
1231
1226 Requires a path (like /foo/.hg) ending with a foldable final
1232 Requires a path (like /foo/.hg) ending with a foldable final
1227 directory component.
1233 directory component.
1228 """
1234 """
1229 s1 = os.lstat(path)
1235 s1 = os.lstat(path)
1230 d, b = os.path.split(path)
1236 d, b = os.path.split(path)
1231 b2 = b.upper()
1237 b2 = b.upper()
1232 if b == b2:
1238 if b == b2:
1233 b2 = b.lower()
1239 b2 = b.lower()
1234 if b == b2:
1240 if b == b2:
1235 return True # no evidence against case sensitivity
1241 return True # no evidence against case sensitivity
1236 p2 = os.path.join(d, b2)
1242 p2 = os.path.join(d, b2)
1237 try:
1243 try:
1238 s2 = os.lstat(p2)
1244 s2 = os.lstat(p2)
1239 if s2 == s1:
1245 if s2 == s1:
1240 return False
1246 return False
1241 return True
1247 return True
1242 except OSError:
1248 except OSError:
1243 return True
1249 return True
1244
1250
1245 try:
1251 try:
1246 import re2
1252 import re2
1247 _re2 = None
1253 _re2 = None
1248 except ImportError:
1254 except ImportError:
1249 _re2 = False
1255 _re2 = False
1250
1256
1251 class _re(object):
1257 class _re(object):
1252 def _checkre2(self):
1258 def _checkre2(self):
1253 global _re2
1259 global _re2
1254 try:
1260 try:
1255 # check if match works, see issue3964
1261 # check if match works, see issue3964
1256 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1262 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1257 except ImportError:
1263 except ImportError:
1258 _re2 = False
1264 _re2 = False
1259
1265
1260 def compile(self, pat, flags=0):
1266 def compile(self, pat, flags=0):
1261 '''Compile a regular expression, using re2 if possible
1267 '''Compile a regular expression, using re2 if possible
1262
1268
1263 For best performance, use only re2-compatible regexp features. The
1269 For best performance, use only re2-compatible regexp features. The
1264 only flags from the re module that are re2-compatible are
1270 only flags from the re module that are re2-compatible are
1265 IGNORECASE and MULTILINE.'''
1271 IGNORECASE and MULTILINE.'''
1266 if _re2 is None:
1272 if _re2 is None:
1267 self._checkre2()
1273 self._checkre2()
1268 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1274 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1269 if flags & remod.IGNORECASE:
1275 if flags & remod.IGNORECASE:
1270 pat = '(?i)' + pat
1276 pat = '(?i)' + pat
1271 if flags & remod.MULTILINE:
1277 if flags & remod.MULTILINE:
1272 pat = '(?m)' + pat
1278 pat = '(?m)' + pat
1273 try:
1279 try:
1274 return re2.compile(pat)
1280 return re2.compile(pat)
1275 except re2.error:
1281 except re2.error:
1276 pass
1282 pass
1277 return remod.compile(pat, flags)
1283 return remod.compile(pat, flags)
1278
1284
1279 @propertycache
1285 @propertycache
1280 def escape(self):
1286 def escape(self):
1281 '''Return the version of escape corresponding to self.compile.
1287 '''Return the version of escape corresponding to self.compile.
1282
1288
1283 This is imperfect because whether re2 or re is used for a particular
1289 This is imperfect because whether re2 or re is used for a particular
1284 function depends on the flags, etc, but it's the best we can do.
1290 function depends on the flags, etc, but it's the best we can do.
1285 '''
1291 '''
1286 global _re2
1292 global _re2
1287 if _re2 is None:
1293 if _re2 is None:
1288 self._checkre2()
1294 self._checkre2()
1289 if _re2:
1295 if _re2:
1290 return re2.escape
1296 return re2.escape
1291 else:
1297 else:
1292 return remod.escape
1298 return remod.escape
1293
1299
1294 re = _re()
1300 re = _re()
1295
1301
1296 _fspathcache = {}
1302 _fspathcache = {}
1297 def fspath(name, root):
1303 def fspath(name, root):
1298 '''Get name in the case stored in the filesystem
1304 '''Get name in the case stored in the filesystem
1299
1305
1300 The name should be relative to root, and be normcase-ed for efficiency.
1306 The name should be relative to root, and be normcase-ed for efficiency.
1301
1307
1302 Note that this function is unnecessary, and should not be
1308 Note that this function is unnecessary, and should not be
1303 called, for case-sensitive filesystems (simply because it's expensive).
1309 called, for case-sensitive filesystems (simply because it's expensive).
1304
1310
1305 The root should be normcase-ed, too.
1311 The root should be normcase-ed, too.
1306 '''
1312 '''
1307 def _makefspathcacheentry(dir):
1313 def _makefspathcacheentry(dir):
1308 return dict((normcase(n), n) for n in os.listdir(dir))
1314 return dict((normcase(n), n) for n in os.listdir(dir))
1309
1315
1310 seps = pycompat.ossep
1316 seps = pycompat.ossep
1311 if pycompat.osaltsep:
1317 if pycompat.osaltsep:
1312 seps = seps + pycompat.osaltsep
1318 seps = seps + pycompat.osaltsep
1313 # Protect backslashes. This gets silly very quickly.
1319 # Protect backslashes. This gets silly very quickly.
1314 seps.replace('\\','\\\\')
1320 seps.replace('\\','\\\\')
1315 pattern = remod.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
1321 pattern = remod.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
1316 dir = os.path.normpath(root)
1322 dir = os.path.normpath(root)
1317 result = []
1323 result = []
1318 for part, sep in pattern.findall(name):
1324 for part, sep in pattern.findall(name):
1319 if sep:
1325 if sep:
1320 result.append(sep)
1326 result.append(sep)
1321 continue
1327 continue
1322
1328
1323 if dir not in _fspathcache:
1329 if dir not in _fspathcache:
1324 _fspathcache[dir] = _makefspathcacheentry(dir)
1330 _fspathcache[dir] = _makefspathcacheentry(dir)
1325 contents = _fspathcache[dir]
1331 contents = _fspathcache[dir]
1326
1332
1327 found = contents.get(part)
1333 found = contents.get(part)
1328 if not found:
1334 if not found:
1329 # retry "once per directory" per "dirstate.walk" which
1335 # retry "once per directory" per "dirstate.walk" which
1330 # may take place for each patches of "hg qpush", for example
1336 # may take place for each patches of "hg qpush", for example
1331 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1337 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1332 found = contents.get(part)
1338 found = contents.get(part)
1333
1339
1334 result.append(found or part)
1340 result.append(found or part)
1335 dir = os.path.join(dir, part)
1341 dir = os.path.join(dir, part)
1336
1342
1337 return ''.join(result)
1343 return ''.join(result)
1338
1344
1339 def checknlink(testfile):
1345 def checknlink(testfile):
1340 '''check whether hardlink count reporting works properly'''
1346 '''check whether hardlink count reporting works properly'''
1341
1347
1342 # testfile may be open, so we need a separate file for checking to
1348 # testfile may be open, so we need a separate file for checking to
1343 # work around issue2543 (or testfile may get lost on Samba shares)
1349 # work around issue2543 (or testfile may get lost on Samba shares)
1344 f1 = testfile + ".hgtmp1"
1350 f1 = testfile + ".hgtmp1"
1345 if os.path.lexists(f1):
1351 if os.path.lexists(f1):
1346 return False
1352 return False
1347 try:
1353 try:
1348 posixfile(f1, 'w').close()
1354 posixfile(f1, 'w').close()
1349 except IOError:
1355 except IOError:
1350 try:
1356 try:
1351 os.unlink(f1)
1357 os.unlink(f1)
1352 except OSError:
1358 except OSError:
1353 pass
1359 pass
1354 return False
1360 return False
1355
1361
1356 f2 = testfile + ".hgtmp2"
1362 f2 = testfile + ".hgtmp2"
1357 fd = None
1363 fd = None
1358 try:
1364 try:
1359 oslink(f1, f2)
1365 oslink(f1, f2)
1360 # nlinks() may behave differently for files on Windows shares if
1366 # nlinks() may behave differently for files on Windows shares if
1361 # the file is open.
1367 # the file is open.
1362 fd = posixfile(f2)
1368 fd = posixfile(f2)
1363 return nlinks(f2) > 1
1369 return nlinks(f2) > 1
1364 except OSError:
1370 except OSError:
1365 return False
1371 return False
1366 finally:
1372 finally:
1367 if fd is not None:
1373 if fd is not None:
1368 fd.close()
1374 fd.close()
1369 for f in (f1, f2):
1375 for f in (f1, f2):
1370 try:
1376 try:
1371 os.unlink(f)
1377 os.unlink(f)
1372 except OSError:
1378 except OSError:
1373 pass
1379 pass
1374
1380
1375 def endswithsep(path):
1381 def endswithsep(path):
1376 '''Check path ends with os.sep or os.altsep.'''
1382 '''Check path ends with os.sep or os.altsep.'''
1377 return (path.endswith(pycompat.ossep)
1383 return (path.endswith(pycompat.ossep)
1378 or pycompat.osaltsep and path.endswith(pycompat.osaltsep))
1384 or pycompat.osaltsep and path.endswith(pycompat.osaltsep))
1379
1385
1380 def splitpath(path):
1386 def splitpath(path):
1381 '''Split path by os.sep.
1387 '''Split path by os.sep.
1382 Note that this function does not use os.altsep because this is
1388 Note that this function does not use os.altsep because this is
1383 an alternative of simple "xxx.split(os.sep)".
1389 an alternative of simple "xxx.split(os.sep)".
1384 It is recommended to use os.path.normpath() before using this
1390 It is recommended to use os.path.normpath() before using this
1385 function if need.'''
1391 function if need.'''
1386 return path.split(pycompat.ossep)
1392 return path.split(pycompat.ossep)
1387
1393
1388 def gui():
1394 def gui():
1389 '''Are we running in a GUI?'''
1395 '''Are we running in a GUI?'''
1390 if pycompat.sysplatform == 'darwin':
1396 if pycompat.sysplatform == 'darwin':
1391 if 'SSH_CONNECTION' in encoding.environ:
1397 if 'SSH_CONNECTION' in encoding.environ:
1392 # handle SSH access to a box where the user is logged in
1398 # handle SSH access to a box where the user is logged in
1393 return False
1399 return False
1394 elif getattr(osutil, 'isgui', None):
1400 elif getattr(osutil, 'isgui', None):
1395 # check if a CoreGraphics session is available
1401 # check if a CoreGraphics session is available
1396 return osutil.isgui()
1402 return osutil.isgui()
1397 else:
1403 else:
1398 # pure build; use a safe default
1404 # pure build; use a safe default
1399 return True
1405 return True
1400 else:
1406 else:
1401 return pycompat.osname == "nt" or encoding.environ.get("DISPLAY")
1407 return pycompat.osname == "nt" or encoding.environ.get("DISPLAY")
1402
1408
1403 def mktempcopy(name, emptyok=False, createmode=None):
1409 def mktempcopy(name, emptyok=False, createmode=None):
1404 """Create a temporary file with the same contents from name
1410 """Create a temporary file with the same contents from name
1405
1411
1406 The permission bits are copied from the original file.
1412 The permission bits are copied from the original file.
1407
1413
1408 If the temporary file is going to be truncated immediately, you
1414 If the temporary file is going to be truncated immediately, you
1409 can use emptyok=True as an optimization.
1415 can use emptyok=True as an optimization.
1410
1416
1411 Returns the name of the temporary file.
1417 Returns the name of the temporary file.
1412 """
1418 """
1413 d, fn = os.path.split(name)
1419 d, fn = os.path.split(name)
1414 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1420 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1415 os.close(fd)
1421 os.close(fd)
1416 # Temporary files are created with mode 0600, which is usually not
1422 # Temporary files are created with mode 0600, which is usually not
1417 # what we want. If the original file already exists, just copy
1423 # what we want. If the original file already exists, just copy
1418 # its mode. Otherwise, manually obey umask.
1424 # its mode. Otherwise, manually obey umask.
1419 copymode(name, temp, createmode)
1425 copymode(name, temp, createmode)
1420 if emptyok:
1426 if emptyok:
1421 return temp
1427 return temp
1422 try:
1428 try:
1423 try:
1429 try:
1424 ifp = posixfile(name, "rb")
1430 ifp = posixfile(name, "rb")
1425 except IOError as inst:
1431 except IOError as inst:
1426 if inst.errno == errno.ENOENT:
1432 if inst.errno == errno.ENOENT:
1427 return temp
1433 return temp
1428 if not getattr(inst, 'filename', None):
1434 if not getattr(inst, 'filename', None):
1429 inst.filename = name
1435 inst.filename = name
1430 raise
1436 raise
1431 ofp = posixfile(temp, "wb")
1437 ofp = posixfile(temp, "wb")
1432 for chunk in filechunkiter(ifp):
1438 for chunk in filechunkiter(ifp):
1433 ofp.write(chunk)
1439 ofp.write(chunk)
1434 ifp.close()
1440 ifp.close()
1435 ofp.close()
1441 ofp.close()
1436 except: # re-raises
1442 except: # re-raises
1437 try: os.unlink(temp)
1443 try: os.unlink(temp)
1438 except OSError: pass
1444 except OSError: pass
1439 raise
1445 raise
1440 return temp
1446 return temp
1441
1447
1442 class filestat(object):
1448 class filestat(object):
1443 """help to exactly detect change of a file
1449 """help to exactly detect change of a file
1444
1450
1445 'stat' attribute is result of 'os.stat()' if specified 'path'
1451 'stat' attribute is result of 'os.stat()' if specified 'path'
1446 exists. Otherwise, it is None. This can avoid preparative
1452 exists. Otherwise, it is None. This can avoid preparative
1447 'exists()' examination on client side of this class.
1453 'exists()' examination on client side of this class.
1448 """
1454 """
1449 def __init__(self, path):
1455 def __init__(self, path):
1450 try:
1456 try:
1451 self.stat = os.stat(path)
1457 self.stat = os.stat(path)
1452 except OSError as err:
1458 except OSError as err:
1453 if err.errno != errno.ENOENT:
1459 if err.errno != errno.ENOENT:
1454 raise
1460 raise
1455 self.stat = None
1461 self.stat = None
1456
1462
1457 __hash__ = object.__hash__
1463 __hash__ = object.__hash__
1458
1464
1459 def __eq__(self, old):
1465 def __eq__(self, old):
1460 try:
1466 try:
1461 # if ambiguity between stat of new and old file is
1467 # if ambiguity between stat of new and old file is
1462 # avoided, comparison of size, ctime and mtime is enough
1468 # avoided, comparison of size, ctime and mtime is enough
1463 # to exactly detect change of a file regardless of platform
1469 # to exactly detect change of a file regardless of platform
1464 return (self.stat.st_size == old.stat.st_size and
1470 return (self.stat.st_size == old.stat.st_size and
1465 self.stat.st_ctime == old.stat.st_ctime and
1471 self.stat.st_ctime == old.stat.st_ctime and
1466 self.stat.st_mtime == old.stat.st_mtime)
1472 self.stat.st_mtime == old.stat.st_mtime)
1467 except AttributeError:
1473 except AttributeError:
1468 return False
1474 return False
1469
1475
1470 def isambig(self, old):
1476 def isambig(self, old):
1471 """Examine whether new (= self) stat is ambiguous against old one
1477 """Examine whether new (= self) stat is ambiguous against old one
1472
1478
1473 "S[N]" below means stat of a file at N-th change:
1479 "S[N]" below means stat of a file at N-th change:
1474
1480
1475 - S[n-1].ctime < S[n].ctime: can detect change of a file
1481 - S[n-1].ctime < S[n].ctime: can detect change of a file
1476 - S[n-1].ctime == S[n].ctime
1482 - S[n-1].ctime == S[n].ctime
1477 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
1483 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
1478 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
1484 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
1479 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
1485 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
1480 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
1486 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
1481
1487
1482 Case (*2) above means that a file was changed twice or more at
1488 Case (*2) above means that a file was changed twice or more at
1483 same time in sec (= S[n-1].ctime), and comparison of timestamp
1489 same time in sec (= S[n-1].ctime), and comparison of timestamp
1484 is ambiguous.
1490 is ambiguous.
1485
1491
1486 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
1492 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
1487 timestamp is ambiguous".
1493 timestamp is ambiguous".
1488
1494
1489 But advancing mtime only in case (*2) doesn't work as
1495 But advancing mtime only in case (*2) doesn't work as
1490 expected, because naturally advanced S[n].mtime in case (*1)
1496 expected, because naturally advanced S[n].mtime in case (*1)
1491 might be equal to manually advanced S[n-1 or earlier].mtime.
1497 might be equal to manually advanced S[n-1 or earlier].mtime.
1492
1498
1493 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
1499 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
1494 treated as ambiguous regardless of mtime, to avoid overlooking
1500 treated as ambiguous regardless of mtime, to avoid overlooking
1495 by confliction between such mtime.
1501 by confliction between such mtime.
1496
1502
1497 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
1503 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
1498 S[n].mtime", even if size of a file isn't changed.
1504 S[n].mtime", even if size of a file isn't changed.
1499 """
1505 """
1500 try:
1506 try:
1501 return (self.stat.st_ctime == old.stat.st_ctime)
1507 return (self.stat.st_ctime == old.stat.st_ctime)
1502 except AttributeError:
1508 except AttributeError:
1503 return False
1509 return False
1504
1510
1505 def avoidambig(self, path, old):
1511 def avoidambig(self, path, old):
1506 """Change file stat of specified path to avoid ambiguity
1512 """Change file stat of specified path to avoid ambiguity
1507
1513
1508 'old' should be previous filestat of 'path'.
1514 'old' should be previous filestat of 'path'.
1509
1515
1510 This skips avoiding ambiguity, if a process doesn't have
1516 This skips avoiding ambiguity, if a process doesn't have
1511 appropriate privileges for 'path'.
1517 appropriate privileges for 'path'.
1512 """
1518 """
1513 advanced = (old.stat.st_mtime + 1) & 0x7fffffff
1519 advanced = (old.stat.st_mtime + 1) & 0x7fffffff
1514 try:
1520 try:
1515 os.utime(path, (advanced, advanced))
1521 os.utime(path, (advanced, advanced))
1516 except OSError as inst:
1522 except OSError as inst:
1517 if inst.errno == errno.EPERM:
1523 if inst.errno == errno.EPERM:
1518 # utime() on the file created by another user causes EPERM,
1524 # utime() on the file created by another user causes EPERM,
1519 # if a process doesn't have appropriate privileges
1525 # if a process doesn't have appropriate privileges
1520 return
1526 return
1521 raise
1527 raise
1522
1528
1523 def __ne__(self, other):
1529 def __ne__(self, other):
1524 return not self == other
1530 return not self == other
1525
1531
1526 class atomictempfile(object):
1532 class atomictempfile(object):
1527 '''writable file object that atomically updates a file
1533 '''writable file object that atomically updates a file
1528
1534
1529 All writes will go to a temporary copy of the original file. Call
1535 All writes will go to a temporary copy of the original file. Call
1530 close() when you are done writing, and atomictempfile will rename
1536 close() when you are done writing, and atomictempfile will rename
1531 the temporary copy to the original name, making the changes
1537 the temporary copy to the original name, making the changes
1532 visible. If the object is destroyed without being closed, all your
1538 visible. If the object is destroyed without being closed, all your
1533 writes are discarded.
1539 writes are discarded.
1534
1540
1535 checkambig argument of constructor is used with filestat, and is
1541 checkambig argument of constructor is used with filestat, and is
1536 useful only if target file is guarded by any lock (e.g. repo.lock
1542 useful only if target file is guarded by any lock (e.g. repo.lock
1537 or repo.wlock).
1543 or repo.wlock).
1538 '''
1544 '''
1539 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
1545 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
1540 self.__name = name # permanent name
1546 self.__name = name # permanent name
1541 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1547 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1542 createmode=createmode)
1548 createmode=createmode)
1543 self._fp = posixfile(self._tempname, mode)
1549 self._fp = posixfile(self._tempname, mode)
1544 self._checkambig = checkambig
1550 self._checkambig = checkambig
1545
1551
1546 # delegated methods
1552 # delegated methods
1547 self.read = self._fp.read
1553 self.read = self._fp.read
1548 self.write = self._fp.write
1554 self.write = self._fp.write
1549 self.seek = self._fp.seek
1555 self.seek = self._fp.seek
1550 self.tell = self._fp.tell
1556 self.tell = self._fp.tell
1551 self.fileno = self._fp.fileno
1557 self.fileno = self._fp.fileno
1552
1558
1553 def close(self):
1559 def close(self):
1554 if not self._fp.closed:
1560 if not self._fp.closed:
1555 self._fp.close()
1561 self._fp.close()
1556 filename = localpath(self.__name)
1562 filename = localpath(self.__name)
1557 oldstat = self._checkambig and filestat(filename)
1563 oldstat = self._checkambig and filestat(filename)
1558 if oldstat and oldstat.stat:
1564 if oldstat and oldstat.stat:
1559 rename(self._tempname, filename)
1565 rename(self._tempname, filename)
1560 newstat = filestat(filename)
1566 newstat = filestat(filename)
1561 if newstat.isambig(oldstat):
1567 if newstat.isambig(oldstat):
1562 # stat of changed file is ambiguous to original one
1568 # stat of changed file is ambiguous to original one
1563 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1569 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1564 os.utime(filename, (advanced, advanced))
1570 os.utime(filename, (advanced, advanced))
1565 else:
1571 else:
1566 rename(self._tempname, filename)
1572 rename(self._tempname, filename)
1567
1573
1568 def discard(self):
1574 def discard(self):
1569 if not self._fp.closed:
1575 if not self._fp.closed:
1570 try:
1576 try:
1571 os.unlink(self._tempname)
1577 os.unlink(self._tempname)
1572 except OSError:
1578 except OSError:
1573 pass
1579 pass
1574 self._fp.close()
1580 self._fp.close()
1575
1581
1576 def __del__(self):
1582 def __del__(self):
1577 if safehasattr(self, '_fp'): # constructor actually did something
1583 if safehasattr(self, '_fp'): # constructor actually did something
1578 self.discard()
1584 self.discard()
1579
1585
1580 def __enter__(self):
1586 def __enter__(self):
1581 return self
1587 return self
1582
1588
1583 def __exit__(self, exctype, excvalue, traceback):
1589 def __exit__(self, exctype, excvalue, traceback):
1584 if exctype is not None:
1590 if exctype is not None:
1585 self.discard()
1591 self.discard()
1586 else:
1592 else:
1587 self.close()
1593 self.close()
1588
1594
1589 def makedirs(name, mode=None, notindexed=False):
1595 def makedirs(name, mode=None, notindexed=False):
1590 """recursive directory creation with parent mode inheritance
1596 """recursive directory creation with parent mode inheritance
1591
1597
1592 Newly created directories are marked as "not to be indexed by
1598 Newly created directories are marked as "not to be indexed by
1593 the content indexing service", if ``notindexed`` is specified
1599 the content indexing service", if ``notindexed`` is specified
1594 for "write" mode access.
1600 for "write" mode access.
1595 """
1601 """
1596 try:
1602 try:
1597 makedir(name, notindexed)
1603 makedir(name, notindexed)
1598 except OSError as err:
1604 except OSError as err:
1599 if err.errno == errno.EEXIST:
1605 if err.errno == errno.EEXIST:
1600 return
1606 return
1601 if err.errno != errno.ENOENT or not name:
1607 if err.errno != errno.ENOENT or not name:
1602 raise
1608 raise
1603 parent = os.path.dirname(os.path.abspath(name))
1609 parent = os.path.dirname(os.path.abspath(name))
1604 if parent == name:
1610 if parent == name:
1605 raise
1611 raise
1606 makedirs(parent, mode, notindexed)
1612 makedirs(parent, mode, notindexed)
1607 try:
1613 try:
1608 makedir(name, notindexed)
1614 makedir(name, notindexed)
1609 except OSError as err:
1615 except OSError as err:
1610 # Catch EEXIST to handle races
1616 # Catch EEXIST to handle races
1611 if err.errno == errno.EEXIST:
1617 if err.errno == errno.EEXIST:
1612 return
1618 return
1613 raise
1619 raise
1614 if mode is not None:
1620 if mode is not None:
1615 os.chmod(name, mode)
1621 os.chmod(name, mode)
1616
1622
1617 def readfile(path):
1623 def readfile(path):
1618 with open(path, 'rb') as fp:
1624 with open(path, 'rb') as fp:
1619 return fp.read()
1625 return fp.read()
1620
1626
1621 def writefile(path, text):
1627 def writefile(path, text):
1622 with open(path, 'wb') as fp:
1628 with open(path, 'wb') as fp:
1623 fp.write(text)
1629 fp.write(text)
1624
1630
1625 def appendfile(path, text):
1631 def appendfile(path, text):
1626 with open(path, 'ab') as fp:
1632 with open(path, 'ab') as fp:
1627 fp.write(text)
1633 fp.write(text)
1628
1634
1629 class chunkbuffer(object):
1635 class chunkbuffer(object):
1630 """Allow arbitrary sized chunks of data to be efficiently read from an
1636 """Allow arbitrary sized chunks of data to be efficiently read from an
1631 iterator over chunks of arbitrary size."""
1637 iterator over chunks of arbitrary size."""
1632
1638
1633 def __init__(self, in_iter):
1639 def __init__(self, in_iter):
1634 """in_iter is the iterator that's iterating over the input chunks.
1640 """in_iter is the iterator that's iterating over the input chunks.
1635 targetsize is how big a buffer to try to maintain."""
1641 targetsize is how big a buffer to try to maintain."""
1636 def splitbig(chunks):
1642 def splitbig(chunks):
1637 for chunk in chunks:
1643 for chunk in chunks:
1638 if len(chunk) > 2**20:
1644 if len(chunk) > 2**20:
1639 pos = 0
1645 pos = 0
1640 while pos < len(chunk):
1646 while pos < len(chunk):
1641 end = pos + 2 ** 18
1647 end = pos + 2 ** 18
1642 yield chunk[pos:end]
1648 yield chunk[pos:end]
1643 pos = end
1649 pos = end
1644 else:
1650 else:
1645 yield chunk
1651 yield chunk
1646 self.iter = splitbig(in_iter)
1652 self.iter = splitbig(in_iter)
1647 self._queue = collections.deque()
1653 self._queue = collections.deque()
1648 self._chunkoffset = 0
1654 self._chunkoffset = 0
1649
1655
1650 def read(self, l=None):
1656 def read(self, l=None):
1651 """Read L bytes of data from the iterator of chunks of data.
1657 """Read L bytes of data from the iterator of chunks of data.
1652 Returns less than L bytes if the iterator runs dry.
1658 Returns less than L bytes if the iterator runs dry.
1653
1659
1654 If size parameter is omitted, read everything"""
1660 If size parameter is omitted, read everything"""
1655 if l is None:
1661 if l is None:
1656 return ''.join(self.iter)
1662 return ''.join(self.iter)
1657
1663
1658 left = l
1664 left = l
1659 buf = []
1665 buf = []
1660 queue = self._queue
1666 queue = self._queue
1661 while left > 0:
1667 while left > 0:
1662 # refill the queue
1668 # refill the queue
1663 if not queue:
1669 if not queue:
1664 target = 2**18
1670 target = 2**18
1665 for chunk in self.iter:
1671 for chunk in self.iter:
1666 queue.append(chunk)
1672 queue.append(chunk)
1667 target -= len(chunk)
1673 target -= len(chunk)
1668 if target <= 0:
1674 if target <= 0:
1669 break
1675 break
1670 if not queue:
1676 if not queue:
1671 break
1677 break
1672
1678
1673 # The easy way to do this would be to queue.popleft(), modify the
1679 # The easy way to do this would be to queue.popleft(), modify the
1674 # chunk (if necessary), then queue.appendleft(). However, for cases
1680 # chunk (if necessary), then queue.appendleft(). However, for cases
1675 # where we read partial chunk content, this incurs 2 dequeue
1681 # where we read partial chunk content, this incurs 2 dequeue
1676 # mutations and creates a new str for the remaining chunk in the
1682 # mutations and creates a new str for the remaining chunk in the
1677 # queue. Our code below avoids this overhead.
1683 # queue. Our code below avoids this overhead.
1678
1684
1679 chunk = queue[0]
1685 chunk = queue[0]
1680 chunkl = len(chunk)
1686 chunkl = len(chunk)
1681 offset = self._chunkoffset
1687 offset = self._chunkoffset
1682
1688
1683 # Use full chunk.
1689 # Use full chunk.
1684 if offset == 0 and left >= chunkl:
1690 if offset == 0 and left >= chunkl:
1685 left -= chunkl
1691 left -= chunkl
1686 queue.popleft()
1692 queue.popleft()
1687 buf.append(chunk)
1693 buf.append(chunk)
1688 # self._chunkoffset remains at 0.
1694 # self._chunkoffset remains at 0.
1689 continue
1695 continue
1690
1696
1691 chunkremaining = chunkl - offset
1697 chunkremaining = chunkl - offset
1692
1698
1693 # Use all of unconsumed part of chunk.
1699 # Use all of unconsumed part of chunk.
1694 if left >= chunkremaining:
1700 if left >= chunkremaining:
1695 left -= chunkremaining
1701 left -= chunkremaining
1696 queue.popleft()
1702 queue.popleft()
1697 # offset == 0 is enabled by block above, so this won't merely
1703 # offset == 0 is enabled by block above, so this won't merely
1698 # copy via ``chunk[0:]``.
1704 # copy via ``chunk[0:]``.
1699 buf.append(chunk[offset:])
1705 buf.append(chunk[offset:])
1700 self._chunkoffset = 0
1706 self._chunkoffset = 0
1701
1707
1702 # Partial chunk needed.
1708 # Partial chunk needed.
1703 else:
1709 else:
1704 buf.append(chunk[offset:offset + left])
1710 buf.append(chunk[offset:offset + left])
1705 self._chunkoffset += left
1711 self._chunkoffset += left
1706 left -= chunkremaining
1712 left -= chunkremaining
1707
1713
1708 return ''.join(buf)
1714 return ''.join(buf)
1709
1715
1710 def filechunkiter(f, size=131072, limit=None):
1716 def filechunkiter(f, size=131072, limit=None):
1711 """Create a generator that produces the data in the file size
1717 """Create a generator that produces the data in the file size
1712 (default 131072) bytes at a time, up to optional limit (default is
1718 (default 131072) bytes at a time, up to optional limit (default is
1713 to read all data). Chunks may be less than size bytes if the
1719 to read all data). Chunks may be less than size bytes if the
1714 chunk is the last chunk in the file, or the file is a socket or
1720 chunk is the last chunk in the file, or the file is a socket or
1715 some other type of file that sometimes reads less data than is
1721 some other type of file that sometimes reads less data than is
1716 requested."""
1722 requested."""
1717 assert size >= 0
1723 assert size >= 0
1718 assert limit is None or limit >= 0
1724 assert limit is None or limit >= 0
1719 while True:
1725 while True:
1720 if limit is None:
1726 if limit is None:
1721 nbytes = size
1727 nbytes = size
1722 else:
1728 else:
1723 nbytes = min(limit, size)
1729 nbytes = min(limit, size)
1724 s = nbytes and f.read(nbytes)
1730 s = nbytes and f.read(nbytes)
1725 if not s:
1731 if not s:
1726 break
1732 break
1727 if limit:
1733 if limit:
1728 limit -= len(s)
1734 limit -= len(s)
1729 yield s
1735 yield s
1730
1736
1731 def makedate(timestamp=None):
1737 def makedate(timestamp=None):
1732 '''Return a unix timestamp (or the current time) as a (unixtime,
1738 '''Return a unix timestamp (or the current time) as a (unixtime,
1733 offset) tuple based off the local timezone.'''
1739 offset) tuple based off the local timezone.'''
1734 if timestamp is None:
1740 if timestamp is None:
1735 timestamp = time.time()
1741 timestamp = time.time()
1736 if timestamp < 0:
1742 if timestamp < 0:
1737 hint = _("check your clock")
1743 hint = _("check your clock")
1738 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1744 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1739 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1745 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1740 datetime.datetime.fromtimestamp(timestamp))
1746 datetime.datetime.fromtimestamp(timestamp))
1741 tz = delta.days * 86400 + delta.seconds
1747 tz = delta.days * 86400 + delta.seconds
1742 return timestamp, tz
1748 return timestamp, tz
1743
1749
1744 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1750 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1745 """represent a (unixtime, offset) tuple as a localized time.
1751 """represent a (unixtime, offset) tuple as a localized time.
1746 unixtime is seconds since the epoch, and offset is the time zone's
1752 unixtime is seconds since the epoch, and offset is the time zone's
1747 number of seconds away from UTC.
1753 number of seconds away from UTC.
1748
1754
1749 >>> datestr((0, 0))
1755 >>> datestr((0, 0))
1750 'Thu Jan 01 00:00:00 1970 +0000'
1756 'Thu Jan 01 00:00:00 1970 +0000'
1751 >>> datestr((42, 0))
1757 >>> datestr((42, 0))
1752 'Thu Jan 01 00:00:42 1970 +0000'
1758 'Thu Jan 01 00:00:42 1970 +0000'
1753 >>> datestr((-42, 0))
1759 >>> datestr((-42, 0))
1754 'Wed Dec 31 23:59:18 1969 +0000'
1760 'Wed Dec 31 23:59:18 1969 +0000'
1755 >>> datestr((0x7fffffff, 0))
1761 >>> datestr((0x7fffffff, 0))
1756 'Tue Jan 19 03:14:07 2038 +0000'
1762 'Tue Jan 19 03:14:07 2038 +0000'
1757 >>> datestr((-0x80000000, 0))
1763 >>> datestr((-0x80000000, 0))
1758 'Fri Dec 13 20:45:52 1901 +0000'
1764 'Fri Dec 13 20:45:52 1901 +0000'
1759 """
1765 """
1760 t, tz = date or makedate()
1766 t, tz = date or makedate()
1761 if "%1" in format or "%2" in format or "%z" in format:
1767 if "%1" in format or "%2" in format or "%z" in format:
1762 sign = (tz > 0) and "-" or "+"
1768 sign = (tz > 0) and "-" or "+"
1763 minutes = abs(tz) // 60
1769 minutes = abs(tz) // 60
1764 q, r = divmod(minutes, 60)
1770 q, r = divmod(minutes, 60)
1765 format = format.replace("%z", "%1%2")
1771 format = format.replace("%z", "%1%2")
1766 format = format.replace("%1", "%c%02d" % (sign, q))
1772 format = format.replace("%1", "%c%02d" % (sign, q))
1767 format = format.replace("%2", "%02d" % r)
1773 format = format.replace("%2", "%02d" % r)
1768 d = t - tz
1774 d = t - tz
1769 if d > 0x7fffffff:
1775 if d > 0x7fffffff:
1770 d = 0x7fffffff
1776 d = 0x7fffffff
1771 elif d < -0x80000000:
1777 elif d < -0x80000000:
1772 d = -0x80000000
1778 d = -0x80000000
1773 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
1779 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
1774 # because they use the gmtime() system call which is buggy on Windows
1780 # because they use the gmtime() system call which is buggy on Windows
1775 # for negative values.
1781 # for negative values.
1776 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
1782 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
1777 s = t.strftime(format)
1783 s = t.strftime(format)
1778 return s
1784 return s
1779
1785
1780 def shortdate(date=None):
1786 def shortdate(date=None):
1781 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1787 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1782 return datestr(date, format='%Y-%m-%d')
1788 return datestr(date, format='%Y-%m-%d')
1783
1789
1784 def parsetimezone(s):
1790 def parsetimezone(s):
1785 """find a trailing timezone, if any, in string, and return a
1791 """find a trailing timezone, if any, in string, and return a
1786 (offset, remainder) pair"""
1792 (offset, remainder) pair"""
1787
1793
1788 if s.endswith("GMT") or s.endswith("UTC"):
1794 if s.endswith("GMT") or s.endswith("UTC"):
1789 return 0, s[:-3].rstrip()
1795 return 0, s[:-3].rstrip()
1790
1796
1791 # Unix-style timezones [+-]hhmm
1797 # Unix-style timezones [+-]hhmm
1792 if len(s) >= 5 and s[-5] in "+-" and s[-4:].isdigit():
1798 if len(s) >= 5 and s[-5] in "+-" and s[-4:].isdigit():
1793 sign = (s[-5] == "+") and 1 or -1
1799 sign = (s[-5] == "+") and 1 or -1
1794 hours = int(s[-4:-2])
1800 hours = int(s[-4:-2])
1795 minutes = int(s[-2:])
1801 minutes = int(s[-2:])
1796 return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip()
1802 return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip()
1797
1803
1798 # ISO8601 trailing Z
1804 # ISO8601 trailing Z
1799 if s.endswith("Z") and s[-2:-1].isdigit():
1805 if s.endswith("Z") and s[-2:-1].isdigit():
1800 return 0, s[:-1]
1806 return 0, s[:-1]
1801
1807
1802 # ISO8601-style [+-]hh:mm
1808 # ISO8601-style [+-]hh:mm
1803 if (len(s) >= 6 and s[-6] in "+-" and s[-3] == ":" and
1809 if (len(s) >= 6 and s[-6] in "+-" and s[-3] == ":" and
1804 s[-5:-3].isdigit() and s[-2:].isdigit()):
1810 s[-5:-3].isdigit() and s[-2:].isdigit()):
1805 sign = (s[-6] == "+") and 1 or -1
1811 sign = (s[-6] == "+") and 1 or -1
1806 hours = int(s[-5:-3])
1812 hours = int(s[-5:-3])
1807 minutes = int(s[-2:])
1813 minutes = int(s[-2:])
1808 return -sign * (hours * 60 + minutes) * 60, s[:-6]
1814 return -sign * (hours * 60 + minutes) * 60, s[:-6]
1809
1815
1810 return None, s
1816 return None, s
1811
1817
1812 def strdate(string, format, defaults=[]):
1818 def strdate(string, format, defaults=[]):
1813 """parse a localized time string and return a (unixtime, offset) tuple.
1819 """parse a localized time string and return a (unixtime, offset) tuple.
1814 if the string cannot be parsed, ValueError is raised."""
1820 if the string cannot be parsed, ValueError is raised."""
1815 # NOTE: unixtime = localunixtime + offset
1821 # NOTE: unixtime = localunixtime + offset
1816 offset, date = parsetimezone(string)
1822 offset, date = parsetimezone(string)
1817
1823
1818 # add missing elements from defaults
1824 # add missing elements from defaults
1819 usenow = False # default to using biased defaults
1825 usenow = False # default to using biased defaults
1820 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1826 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1821 found = [True for p in part if ("%"+p) in format]
1827 found = [True for p in part if ("%"+p) in format]
1822 if not found:
1828 if not found:
1823 date += "@" + defaults[part][usenow]
1829 date += "@" + defaults[part][usenow]
1824 format += "@%" + part[0]
1830 format += "@%" + part[0]
1825 else:
1831 else:
1826 # We've found a specific time element, less specific time
1832 # We've found a specific time element, less specific time
1827 # elements are relative to today
1833 # elements are relative to today
1828 usenow = True
1834 usenow = True
1829
1835
1830 timetuple = time.strptime(date, format)
1836 timetuple = time.strptime(date, format)
1831 localunixtime = int(calendar.timegm(timetuple))
1837 localunixtime = int(calendar.timegm(timetuple))
1832 if offset is None:
1838 if offset is None:
1833 # local timezone
1839 # local timezone
1834 unixtime = int(time.mktime(timetuple))
1840 unixtime = int(time.mktime(timetuple))
1835 offset = unixtime - localunixtime
1841 offset = unixtime - localunixtime
1836 else:
1842 else:
1837 unixtime = localunixtime + offset
1843 unixtime = localunixtime + offset
1838 return unixtime, offset
1844 return unixtime, offset
1839
1845
1840 def parsedate(date, formats=None, bias=None):
1846 def parsedate(date, formats=None, bias=None):
1841 """parse a localized date/time and return a (unixtime, offset) tuple.
1847 """parse a localized date/time and return a (unixtime, offset) tuple.
1842
1848
1843 The date may be a "unixtime offset" string or in one of the specified
1849 The date may be a "unixtime offset" string or in one of the specified
1844 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1850 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1845
1851
1846 >>> parsedate(' today ') == parsedate(\
1852 >>> parsedate(' today ') == parsedate(\
1847 datetime.date.today().strftime('%b %d'))
1853 datetime.date.today().strftime('%b %d'))
1848 True
1854 True
1849 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1855 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1850 datetime.timedelta(days=1)\
1856 datetime.timedelta(days=1)\
1851 ).strftime('%b %d'))
1857 ).strftime('%b %d'))
1852 True
1858 True
1853 >>> now, tz = makedate()
1859 >>> now, tz = makedate()
1854 >>> strnow, strtz = parsedate('now')
1860 >>> strnow, strtz = parsedate('now')
1855 >>> (strnow - now) < 1
1861 >>> (strnow - now) < 1
1856 True
1862 True
1857 >>> tz == strtz
1863 >>> tz == strtz
1858 True
1864 True
1859 """
1865 """
1860 if bias is None:
1866 if bias is None:
1861 bias = {}
1867 bias = {}
1862 if not date:
1868 if not date:
1863 return 0, 0
1869 return 0, 0
1864 if isinstance(date, tuple) and len(date) == 2:
1870 if isinstance(date, tuple) and len(date) == 2:
1865 return date
1871 return date
1866 if not formats:
1872 if not formats:
1867 formats = defaultdateformats
1873 formats = defaultdateformats
1868 date = date.strip()
1874 date = date.strip()
1869
1875
1870 if date == 'now' or date == _('now'):
1876 if date == 'now' or date == _('now'):
1871 return makedate()
1877 return makedate()
1872 if date == 'today' or date == _('today'):
1878 if date == 'today' or date == _('today'):
1873 date = datetime.date.today().strftime('%b %d')
1879 date = datetime.date.today().strftime('%b %d')
1874 elif date == 'yesterday' or date == _('yesterday'):
1880 elif date == 'yesterday' or date == _('yesterday'):
1875 date = (datetime.date.today() -
1881 date = (datetime.date.today() -
1876 datetime.timedelta(days=1)).strftime('%b %d')
1882 datetime.timedelta(days=1)).strftime('%b %d')
1877
1883
1878 try:
1884 try:
1879 when, offset = map(int, date.split(' '))
1885 when, offset = map(int, date.split(' '))
1880 except ValueError:
1886 except ValueError:
1881 # fill out defaults
1887 # fill out defaults
1882 now = makedate()
1888 now = makedate()
1883 defaults = {}
1889 defaults = {}
1884 for part in ("d", "mb", "yY", "HI", "M", "S"):
1890 for part in ("d", "mb", "yY", "HI", "M", "S"):
1885 # this piece is for rounding the specific end of unknowns
1891 # this piece is for rounding the specific end of unknowns
1886 b = bias.get(part)
1892 b = bias.get(part)
1887 if b is None:
1893 if b is None:
1888 if part[0] in "HMS":
1894 if part[0] in "HMS":
1889 b = "00"
1895 b = "00"
1890 else:
1896 else:
1891 b = "0"
1897 b = "0"
1892
1898
1893 # this piece is for matching the generic end to today's date
1899 # this piece is for matching the generic end to today's date
1894 n = datestr(now, "%" + part[0])
1900 n = datestr(now, "%" + part[0])
1895
1901
1896 defaults[part] = (b, n)
1902 defaults[part] = (b, n)
1897
1903
1898 for format in formats:
1904 for format in formats:
1899 try:
1905 try:
1900 when, offset = strdate(date, format, defaults)
1906 when, offset = strdate(date, format, defaults)
1901 except (ValueError, OverflowError):
1907 except (ValueError, OverflowError):
1902 pass
1908 pass
1903 else:
1909 else:
1904 break
1910 break
1905 else:
1911 else:
1906 raise Abort(_('invalid date: %r') % date)
1912 raise Abort(_('invalid date: %r') % date)
1907 # validate explicit (probably user-specified) date and
1913 # validate explicit (probably user-specified) date and
1908 # time zone offset. values must fit in signed 32 bits for
1914 # time zone offset. values must fit in signed 32 bits for
1909 # current 32-bit linux runtimes. timezones go from UTC-12
1915 # current 32-bit linux runtimes. timezones go from UTC-12
1910 # to UTC+14
1916 # to UTC+14
1911 if when < -0x80000000 or when > 0x7fffffff:
1917 if when < -0x80000000 or when > 0x7fffffff:
1912 raise Abort(_('date exceeds 32 bits: %d') % when)
1918 raise Abort(_('date exceeds 32 bits: %d') % when)
1913 if offset < -50400 or offset > 43200:
1919 if offset < -50400 or offset > 43200:
1914 raise Abort(_('impossible time zone offset: %d') % offset)
1920 raise Abort(_('impossible time zone offset: %d') % offset)
1915 return when, offset
1921 return when, offset
1916
1922
1917 def matchdate(date):
1923 def matchdate(date):
1918 """Return a function that matches a given date match specifier
1924 """Return a function that matches a given date match specifier
1919
1925
1920 Formats include:
1926 Formats include:
1921
1927
1922 '{date}' match a given date to the accuracy provided
1928 '{date}' match a given date to the accuracy provided
1923
1929
1924 '<{date}' on or before a given date
1930 '<{date}' on or before a given date
1925
1931
1926 '>{date}' on or after a given date
1932 '>{date}' on or after a given date
1927
1933
1928 >>> p1 = parsedate("10:29:59")
1934 >>> p1 = parsedate("10:29:59")
1929 >>> p2 = parsedate("10:30:00")
1935 >>> p2 = parsedate("10:30:00")
1930 >>> p3 = parsedate("10:30:59")
1936 >>> p3 = parsedate("10:30:59")
1931 >>> p4 = parsedate("10:31:00")
1937 >>> p4 = parsedate("10:31:00")
1932 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1938 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1933 >>> f = matchdate("10:30")
1939 >>> f = matchdate("10:30")
1934 >>> f(p1[0])
1940 >>> f(p1[0])
1935 False
1941 False
1936 >>> f(p2[0])
1942 >>> f(p2[0])
1937 True
1943 True
1938 >>> f(p3[0])
1944 >>> f(p3[0])
1939 True
1945 True
1940 >>> f(p4[0])
1946 >>> f(p4[0])
1941 False
1947 False
1942 >>> f(p5[0])
1948 >>> f(p5[0])
1943 False
1949 False
1944 """
1950 """
1945
1951
1946 def lower(date):
1952 def lower(date):
1947 d = {'mb': "1", 'd': "1"}
1953 d = {'mb': "1", 'd': "1"}
1948 return parsedate(date, extendeddateformats, d)[0]
1954 return parsedate(date, extendeddateformats, d)[0]
1949
1955
1950 def upper(date):
1956 def upper(date):
1951 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
1957 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
1952 for days in ("31", "30", "29"):
1958 for days in ("31", "30", "29"):
1953 try:
1959 try:
1954 d["d"] = days
1960 d["d"] = days
1955 return parsedate(date, extendeddateformats, d)[0]
1961 return parsedate(date, extendeddateformats, d)[0]
1956 except Abort:
1962 except Abort:
1957 pass
1963 pass
1958 d["d"] = "28"
1964 d["d"] = "28"
1959 return parsedate(date, extendeddateformats, d)[0]
1965 return parsedate(date, extendeddateformats, d)[0]
1960
1966
1961 date = date.strip()
1967 date = date.strip()
1962
1968
1963 if not date:
1969 if not date:
1964 raise Abort(_("dates cannot consist entirely of whitespace"))
1970 raise Abort(_("dates cannot consist entirely of whitespace"))
1965 elif date[0] == "<":
1971 elif date[0] == "<":
1966 if not date[1:]:
1972 if not date[1:]:
1967 raise Abort(_("invalid day spec, use '<DATE'"))
1973 raise Abort(_("invalid day spec, use '<DATE'"))
1968 when = upper(date[1:])
1974 when = upper(date[1:])
1969 return lambda x: x <= when
1975 return lambda x: x <= when
1970 elif date[0] == ">":
1976 elif date[0] == ">":
1971 if not date[1:]:
1977 if not date[1:]:
1972 raise Abort(_("invalid day spec, use '>DATE'"))
1978 raise Abort(_("invalid day spec, use '>DATE'"))
1973 when = lower(date[1:])
1979 when = lower(date[1:])
1974 return lambda x: x >= when
1980 return lambda x: x >= when
1975 elif date[0] == "-":
1981 elif date[0] == "-":
1976 try:
1982 try:
1977 days = int(date[1:])
1983 days = int(date[1:])
1978 except ValueError:
1984 except ValueError:
1979 raise Abort(_("invalid day spec: %s") % date[1:])
1985 raise Abort(_("invalid day spec: %s") % date[1:])
1980 if days < 0:
1986 if days < 0:
1981 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1987 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1982 % date[1:])
1988 % date[1:])
1983 when = makedate()[0] - days * 3600 * 24
1989 when = makedate()[0] - days * 3600 * 24
1984 return lambda x: x >= when
1990 return lambda x: x >= when
1985 elif " to " in date:
1991 elif " to " in date:
1986 a, b = date.split(" to ")
1992 a, b = date.split(" to ")
1987 start, stop = lower(a), upper(b)
1993 start, stop = lower(a), upper(b)
1988 return lambda x: x >= start and x <= stop
1994 return lambda x: x >= start and x <= stop
1989 else:
1995 else:
1990 start, stop = lower(date), upper(date)
1996 start, stop = lower(date), upper(date)
1991 return lambda x: x >= start and x <= stop
1997 return lambda x: x >= start and x <= stop
1992
1998
1993 def stringmatcher(pattern):
1999 def stringmatcher(pattern):
1994 """
2000 """
1995 accepts a string, possibly starting with 're:' or 'literal:' prefix.
2001 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1996 returns the matcher name, pattern, and matcher function.
2002 returns the matcher name, pattern, and matcher function.
1997 missing or unknown prefixes are treated as literal matches.
2003 missing or unknown prefixes are treated as literal matches.
1998
2004
1999 helper for tests:
2005 helper for tests:
2000 >>> def test(pattern, *tests):
2006 >>> def test(pattern, *tests):
2001 ... kind, pattern, matcher = stringmatcher(pattern)
2007 ... kind, pattern, matcher = stringmatcher(pattern)
2002 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2008 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2003
2009
2004 exact matching (no prefix):
2010 exact matching (no prefix):
2005 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
2011 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
2006 ('literal', 'abcdefg', [False, False, True])
2012 ('literal', 'abcdefg', [False, False, True])
2007
2013
2008 regex matching ('re:' prefix)
2014 regex matching ('re:' prefix)
2009 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
2015 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
2010 ('re', 'a.+b', [False, False, True])
2016 ('re', 'a.+b', [False, False, True])
2011
2017
2012 force exact matches ('literal:' prefix)
2018 force exact matches ('literal:' prefix)
2013 >>> test('literal:re:foobar', 'foobar', 're:foobar')
2019 >>> test('literal:re:foobar', 'foobar', 're:foobar')
2014 ('literal', 're:foobar', [False, True])
2020 ('literal', 're:foobar', [False, True])
2015
2021
2016 unknown prefixes are ignored and treated as literals
2022 unknown prefixes are ignored and treated as literals
2017 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
2023 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
2018 ('literal', 'foo:bar', [False, False, True])
2024 ('literal', 'foo:bar', [False, False, True])
2019 """
2025 """
2020 if pattern.startswith('re:'):
2026 if pattern.startswith('re:'):
2021 pattern = pattern[3:]
2027 pattern = pattern[3:]
2022 try:
2028 try:
2023 regex = remod.compile(pattern)
2029 regex = remod.compile(pattern)
2024 except remod.error as e:
2030 except remod.error as e:
2025 raise error.ParseError(_('invalid regular expression: %s')
2031 raise error.ParseError(_('invalid regular expression: %s')
2026 % e)
2032 % e)
2027 return 're', pattern, regex.search
2033 return 're', pattern, regex.search
2028 elif pattern.startswith('literal:'):
2034 elif pattern.startswith('literal:'):
2029 pattern = pattern[8:]
2035 pattern = pattern[8:]
2030 return 'literal', pattern, pattern.__eq__
2036 return 'literal', pattern, pattern.__eq__
2031
2037
2032 def shortuser(user):
2038 def shortuser(user):
2033 """Return a short representation of a user name or email address."""
2039 """Return a short representation of a user name or email address."""
2034 f = user.find('@')
2040 f = user.find('@')
2035 if f >= 0:
2041 if f >= 0:
2036 user = user[:f]
2042 user = user[:f]
2037 f = user.find('<')
2043 f = user.find('<')
2038 if f >= 0:
2044 if f >= 0:
2039 user = user[f + 1:]
2045 user = user[f + 1:]
2040 f = user.find(' ')
2046 f = user.find(' ')
2041 if f >= 0:
2047 if f >= 0:
2042 user = user[:f]
2048 user = user[:f]
2043 f = user.find('.')
2049 f = user.find('.')
2044 if f >= 0:
2050 if f >= 0:
2045 user = user[:f]
2051 user = user[:f]
2046 return user
2052 return user
2047
2053
2048 def emailuser(user):
2054 def emailuser(user):
2049 """Return the user portion of an email address."""
2055 """Return the user portion of an email address."""
2050 f = user.find('@')
2056 f = user.find('@')
2051 if f >= 0:
2057 if f >= 0:
2052 user = user[:f]
2058 user = user[:f]
2053 f = user.find('<')
2059 f = user.find('<')
2054 if f >= 0:
2060 if f >= 0:
2055 user = user[f + 1:]
2061 user = user[f + 1:]
2056 return user
2062 return user
2057
2063
2058 def email(author):
2064 def email(author):
2059 '''get email of author.'''
2065 '''get email of author.'''
2060 r = author.find('>')
2066 r = author.find('>')
2061 if r == -1:
2067 if r == -1:
2062 r = None
2068 r = None
2063 return author[author.find('<') + 1:r]
2069 return author[author.find('<') + 1:r]
2064
2070
2065 def ellipsis(text, maxlength=400):
2071 def ellipsis(text, maxlength=400):
2066 """Trim string to at most maxlength (default: 400) columns in display."""
2072 """Trim string to at most maxlength (default: 400) columns in display."""
2067 return encoding.trim(text, maxlength, ellipsis='...')
2073 return encoding.trim(text, maxlength, ellipsis='...')
2068
2074
2069 def unitcountfn(*unittable):
2075 def unitcountfn(*unittable):
2070 '''return a function that renders a readable count of some quantity'''
2076 '''return a function that renders a readable count of some quantity'''
2071
2077
2072 def go(count):
2078 def go(count):
2073 for multiplier, divisor, format in unittable:
2079 for multiplier, divisor, format in unittable:
2074 if count >= divisor * multiplier:
2080 if count >= divisor * multiplier:
2075 return format % (count / float(divisor))
2081 return format % (count / float(divisor))
2076 return unittable[-1][2] % count
2082 return unittable[-1][2] % count
2077
2083
2078 return go
2084 return go
2079
2085
2080 bytecount = unitcountfn(
2086 bytecount = unitcountfn(
2081 (100, 1 << 30, _('%.0f GB')),
2087 (100, 1 << 30, _('%.0f GB')),
2082 (10, 1 << 30, _('%.1f GB')),
2088 (10, 1 << 30, _('%.1f GB')),
2083 (1, 1 << 30, _('%.2f GB')),
2089 (1, 1 << 30, _('%.2f GB')),
2084 (100, 1 << 20, _('%.0f MB')),
2090 (100, 1 << 20, _('%.0f MB')),
2085 (10, 1 << 20, _('%.1f MB')),
2091 (10, 1 << 20, _('%.1f MB')),
2086 (1, 1 << 20, _('%.2f MB')),
2092 (1, 1 << 20, _('%.2f MB')),
2087 (100, 1 << 10, _('%.0f KB')),
2093 (100, 1 << 10, _('%.0f KB')),
2088 (10, 1 << 10, _('%.1f KB')),
2094 (10, 1 << 10, _('%.1f KB')),
2089 (1, 1 << 10, _('%.2f KB')),
2095 (1, 1 << 10, _('%.2f KB')),
2090 (1, 1, _('%.0f bytes')),
2096 (1, 1, _('%.0f bytes')),
2091 )
2097 )
2092
2098
2093 def uirepr(s):
2099 def uirepr(s):
2094 # Avoid double backslash in Windows path repr()
2100 # Avoid double backslash in Windows path repr()
2095 return repr(s).replace('\\\\', '\\')
2101 return repr(s).replace('\\\\', '\\')
2096
2102
2097 # delay import of textwrap
2103 # delay import of textwrap
2098 def MBTextWrapper(**kwargs):
2104 def MBTextWrapper(**kwargs):
2099 class tw(textwrap.TextWrapper):
2105 class tw(textwrap.TextWrapper):
2100 """
2106 """
2101 Extend TextWrapper for width-awareness.
2107 Extend TextWrapper for width-awareness.
2102
2108
2103 Neither number of 'bytes' in any encoding nor 'characters' is
2109 Neither number of 'bytes' in any encoding nor 'characters' is
2104 appropriate to calculate terminal columns for specified string.
2110 appropriate to calculate terminal columns for specified string.
2105
2111
2106 Original TextWrapper implementation uses built-in 'len()' directly,
2112 Original TextWrapper implementation uses built-in 'len()' directly,
2107 so overriding is needed to use width information of each characters.
2113 so overriding is needed to use width information of each characters.
2108
2114
2109 In addition, characters classified into 'ambiguous' width are
2115 In addition, characters classified into 'ambiguous' width are
2110 treated as wide in East Asian area, but as narrow in other.
2116 treated as wide in East Asian area, but as narrow in other.
2111
2117
2112 This requires use decision to determine width of such characters.
2118 This requires use decision to determine width of such characters.
2113 """
2119 """
2114 def _cutdown(self, ucstr, space_left):
2120 def _cutdown(self, ucstr, space_left):
2115 l = 0
2121 l = 0
2116 colwidth = encoding.ucolwidth
2122 colwidth = encoding.ucolwidth
2117 for i in xrange(len(ucstr)):
2123 for i in xrange(len(ucstr)):
2118 l += colwidth(ucstr[i])
2124 l += colwidth(ucstr[i])
2119 if space_left < l:
2125 if space_left < l:
2120 return (ucstr[:i], ucstr[i:])
2126 return (ucstr[:i], ucstr[i:])
2121 return ucstr, ''
2127 return ucstr, ''
2122
2128
2123 # overriding of base class
2129 # overriding of base class
2124 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
2130 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
2125 space_left = max(width - cur_len, 1)
2131 space_left = max(width - cur_len, 1)
2126
2132
2127 if self.break_long_words:
2133 if self.break_long_words:
2128 cut, res = self._cutdown(reversed_chunks[-1], space_left)
2134 cut, res = self._cutdown(reversed_chunks[-1], space_left)
2129 cur_line.append(cut)
2135 cur_line.append(cut)
2130 reversed_chunks[-1] = res
2136 reversed_chunks[-1] = res
2131 elif not cur_line:
2137 elif not cur_line:
2132 cur_line.append(reversed_chunks.pop())
2138 cur_line.append(reversed_chunks.pop())
2133
2139
2134 # this overriding code is imported from TextWrapper of Python 2.6
2140 # this overriding code is imported from TextWrapper of Python 2.6
2135 # to calculate columns of string by 'encoding.ucolwidth()'
2141 # to calculate columns of string by 'encoding.ucolwidth()'
2136 def _wrap_chunks(self, chunks):
2142 def _wrap_chunks(self, chunks):
2137 colwidth = encoding.ucolwidth
2143 colwidth = encoding.ucolwidth
2138
2144
2139 lines = []
2145 lines = []
2140 if self.width <= 0:
2146 if self.width <= 0:
2141 raise ValueError("invalid width %r (must be > 0)" % self.width)
2147 raise ValueError("invalid width %r (must be > 0)" % self.width)
2142
2148
2143 # Arrange in reverse order so items can be efficiently popped
2149 # Arrange in reverse order so items can be efficiently popped
2144 # from a stack of chucks.
2150 # from a stack of chucks.
2145 chunks.reverse()
2151 chunks.reverse()
2146
2152
2147 while chunks:
2153 while chunks:
2148
2154
2149 # Start the list of chunks that will make up the current line.
2155 # Start the list of chunks that will make up the current line.
2150 # cur_len is just the length of all the chunks in cur_line.
2156 # cur_len is just the length of all the chunks in cur_line.
2151 cur_line = []
2157 cur_line = []
2152 cur_len = 0
2158 cur_len = 0
2153
2159
2154 # Figure out which static string will prefix this line.
2160 # Figure out which static string will prefix this line.
2155 if lines:
2161 if lines:
2156 indent = self.subsequent_indent
2162 indent = self.subsequent_indent
2157 else:
2163 else:
2158 indent = self.initial_indent
2164 indent = self.initial_indent
2159
2165
2160 # Maximum width for this line.
2166 # Maximum width for this line.
2161 width = self.width - len(indent)
2167 width = self.width - len(indent)
2162
2168
2163 # First chunk on line is whitespace -- drop it, unless this
2169 # First chunk on line is whitespace -- drop it, unless this
2164 # is the very beginning of the text (i.e. no lines started yet).
2170 # is the very beginning of the text (i.e. no lines started yet).
2165 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
2171 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
2166 del chunks[-1]
2172 del chunks[-1]
2167
2173
2168 while chunks:
2174 while chunks:
2169 l = colwidth(chunks[-1])
2175 l = colwidth(chunks[-1])
2170
2176
2171 # Can at least squeeze this chunk onto the current line.
2177 # Can at least squeeze this chunk onto the current line.
2172 if cur_len + l <= width:
2178 if cur_len + l <= width:
2173 cur_line.append(chunks.pop())
2179 cur_line.append(chunks.pop())
2174 cur_len += l
2180 cur_len += l
2175
2181
2176 # Nope, this line is full.
2182 # Nope, this line is full.
2177 else:
2183 else:
2178 break
2184 break
2179
2185
2180 # The current line is full, and the next chunk is too big to
2186 # The current line is full, and the next chunk is too big to
2181 # fit on *any* line (not just this one).
2187 # fit on *any* line (not just this one).
2182 if chunks and colwidth(chunks[-1]) > width:
2188 if chunks and colwidth(chunks[-1]) > width:
2183 self._handle_long_word(chunks, cur_line, cur_len, width)
2189 self._handle_long_word(chunks, cur_line, cur_len, width)
2184
2190
2185 # If the last chunk on this line is all whitespace, drop it.
2191 # If the last chunk on this line is all whitespace, drop it.
2186 if (self.drop_whitespace and
2192 if (self.drop_whitespace and
2187 cur_line and cur_line[-1].strip() == ''):
2193 cur_line and cur_line[-1].strip() == ''):
2188 del cur_line[-1]
2194 del cur_line[-1]
2189
2195
2190 # Convert current line back to a string and store it in list
2196 # Convert current line back to a string and store it in list
2191 # of all lines (return value).
2197 # of all lines (return value).
2192 if cur_line:
2198 if cur_line:
2193 lines.append(indent + ''.join(cur_line))
2199 lines.append(indent + ''.join(cur_line))
2194
2200
2195 return lines
2201 return lines
2196
2202
2197 global MBTextWrapper
2203 global MBTextWrapper
2198 MBTextWrapper = tw
2204 MBTextWrapper = tw
2199 return tw(**kwargs)
2205 return tw(**kwargs)
2200
2206
2201 def wrap(line, width, initindent='', hangindent=''):
2207 def wrap(line, width, initindent='', hangindent=''):
2202 maxindent = max(len(hangindent), len(initindent))
2208 maxindent = max(len(hangindent), len(initindent))
2203 if width <= maxindent:
2209 if width <= maxindent:
2204 # adjust for weird terminal size
2210 # adjust for weird terminal size
2205 width = max(78, maxindent + 1)
2211 width = max(78, maxindent + 1)
2206 line = line.decode(encoding.encoding, encoding.encodingmode)
2212 line = line.decode(encoding.encoding, encoding.encodingmode)
2207 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
2213 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
2208 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
2214 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
2209 wrapper = MBTextWrapper(width=width,
2215 wrapper = MBTextWrapper(width=width,
2210 initial_indent=initindent,
2216 initial_indent=initindent,
2211 subsequent_indent=hangindent)
2217 subsequent_indent=hangindent)
2212 return wrapper.fill(line).encode(encoding.encoding)
2218 return wrapper.fill(line).encode(encoding.encoding)
2213
2219
2214 if (pyplatform.python_implementation() == 'CPython' and
2220 if (pyplatform.python_implementation() == 'CPython' and
2215 sys.version_info < (3, 0)):
2221 sys.version_info < (3, 0)):
2216 # There is an issue in CPython that some IO methods do not handle EINTR
2222 # There is an issue in CPython that some IO methods do not handle EINTR
2217 # correctly. The following table shows what CPython version (and functions)
2223 # correctly. The following table shows what CPython version (and functions)
2218 # are affected (buggy: has the EINTR bug, okay: otherwise):
2224 # are affected (buggy: has the EINTR bug, okay: otherwise):
2219 #
2225 #
2220 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2226 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2221 # --------------------------------------------------
2227 # --------------------------------------------------
2222 # fp.__iter__ | buggy | buggy | okay
2228 # fp.__iter__ | buggy | buggy | okay
2223 # fp.read* | buggy | okay [1] | okay
2229 # fp.read* | buggy | okay [1] | okay
2224 #
2230 #
2225 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2231 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2226 #
2232 #
2227 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2233 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2228 # like "read*" are ignored for now, as Python < 2.7.4 is a minority.
2234 # like "read*" are ignored for now, as Python < 2.7.4 is a minority.
2229 #
2235 #
2230 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2236 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2231 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2237 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2232 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2238 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2233 # fp.__iter__ but not other fp.read* methods.
2239 # fp.__iter__ but not other fp.read* methods.
2234 #
2240 #
2235 # On modern systems like Linux, the "read" syscall cannot be interrupted
2241 # On modern systems like Linux, the "read" syscall cannot be interrupted
2236 # when reading "fast" files like on-disk files. So the EINTR issue only
2242 # when reading "fast" files like on-disk files. So the EINTR issue only
2237 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2243 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2238 # files approximately as "fast" files and use the fast (unsafe) code path,
2244 # files approximately as "fast" files and use the fast (unsafe) code path,
2239 # to minimize the performance impact.
2245 # to minimize the performance impact.
2240 if sys.version_info >= (2, 7, 4):
2246 if sys.version_info >= (2, 7, 4):
2241 # fp.readline deals with EINTR correctly, use it as a workaround.
2247 # fp.readline deals with EINTR correctly, use it as a workaround.
2242 def _safeiterfile(fp):
2248 def _safeiterfile(fp):
2243 return iter(fp.readline, '')
2249 return iter(fp.readline, '')
2244 else:
2250 else:
2245 # fp.read* are broken too, manually deal with EINTR in a stupid way.
2251 # fp.read* are broken too, manually deal with EINTR in a stupid way.
2246 # note: this may block longer than necessary because of bufsize.
2252 # note: this may block longer than necessary because of bufsize.
2247 def _safeiterfile(fp, bufsize=4096):
2253 def _safeiterfile(fp, bufsize=4096):
2248 fd = fp.fileno()
2254 fd = fp.fileno()
2249 line = ''
2255 line = ''
2250 while True:
2256 while True:
2251 try:
2257 try:
2252 buf = os.read(fd, bufsize)
2258 buf = os.read(fd, bufsize)
2253 except OSError as ex:
2259 except OSError as ex:
2254 # os.read only raises EINTR before any data is read
2260 # os.read only raises EINTR before any data is read
2255 if ex.errno == errno.EINTR:
2261 if ex.errno == errno.EINTR:
2256 continue
2262 continue
2257 else:
2263 else:
2258 raise
2264 raise
2259 line += buf
2265 line += buf
2260 if '\n' in buf:
2266 if '\n' in buf:
2261 splitted = line.splitlines(True)
2267 splitted = line.splitlines(True)
2262 line = ''
2268 line = ''
2263 for l in splitted:
2269 for l in splitted:
2264 if l[-1] == '\n':
2270 if l[-1] == '\n':
2265 yield l
2271 yield l
2266 else:
2272 else:
2267 line = l
2273 line = l
2268 if not buf:
2274 if not buf:
2269 break
2275 break
2270 if line:
2276 if line:
2271 yield line
2277 yield line
2272
2278
2273 def iterfile(fp):
2279 def iterfile(fp):
2274 fastpath = True
2280 fastpath = True
2275 if type(fp) is file:
2281 if type(fp) is file:
2276 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2282 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2277 if fastpath:
2283 if fastpath:
2278 return fp
2284 return fp
2279 else:
2285 else:
2280 return _safeiterfile(fp)
2286 return _safeiterfile(fp)
2281 else:
2287 else:
2282 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2288 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2283 def iterfile(fp):
2289 def iterfile(fp):
2284 return fp
2290 return fp
2285
2291
2286 def iterlines(iterator):
2292 def iterlines(iterator):
2287 for chunk in iterator:
2293 for chunk in iterator:
2288 for line in chunk.splitlines():
2294 for line in chunk.splitlines():
2289 yield line
2295 yield line
2290
2296
2291 def expandpath(path):
2297 def expandpath(path):
2292 return os.path.expanduser(os.path.expandvars(path))
2298 return os.path.expanduser(os.path.expandvars(path))
2293
2299
2294 def hgcmd():
2300 def hgcmd():
2295 """Return the command used to execute current hg
2301 """Return the command used to execute current hg
2296
2302
2297 This is different from hgexecutable() because on Windows we want
2303 This is different from hgexecutable() because on Windows we want
2298 to avoid things opening new shell windows like batch files, so we
2304 to avoid things opening new shell windows like batch files, so we
2299 get either the python call or current executable.
2305 get either the python call or current executable.
2300 """
2306 """
2301 if mainfrozen():
2307 if mainfrozen():
2302 if getattr(sys, 'frozen', None) == 'macosx_app':
2308 if getattr(sys, 'frozen', None) == 'macosx_app':
2303 # Env variable set by py2app
2309 # Env variable set by py2app
2304 return [encoding.environ['EXECUTABLEPATH']]
2310 return [encoding.environ['EXECUTABLEPATH']]
2305 else:
2311 else:
2306 return [pycompat.sysexecutable]
2312 return [pycompat.sysexecutable]
2307 return gethgcmd()
2313 return gethgcmd()
2308
2314
2309 def rundetached(args, condfn):
2315 def rundetached(args, condfn):
2310 """Execute the argument list in a detached process.
2316 """Execute the argument list in a detached process.
2311
2317
2312 condfn is a callable which is called repeatedly and should return
2318 condfn is a callable which is called repeatedly and should return
2313 True once the child process is known to have started successfully.
2319 True once the child process is known to have started successfully.
2314 At this point, the child process PID is returned. If the child
2320 At this point, the child process PID is returned. If the child
2315 process fails to start or finishes before condfn() evaluates to
2321 process fails to start or finishes before condfn() evaluates to
2316 True, return -1.
2322 True, return -1.
2317 """
2323 """
2318 # Windows case is easier because the child process is either
2324 # Windows case is easier because the child process is either
2319 # successfully starting and validating the condition or exiting
2325 # successfully starting and validating the condition or exiting
2320 # on failure. We just poll on its PID. On Unix, if the child
2326 # on failure. We just poll on its PID. On Unix, if the child
2321 # process fails to start, it will be left in a zombie state until
2327 # process fails to start, it will be left in a zombie state until
2322 # the parent wait on it, which we cannot do since we expect a long
2328 # the parent wait on it, which we cannot do since we expect a long
2323 # running process on success. Instead we listen for SIGCHLD telling
2329 # running process on success. Instead we listen for SIGCHLD telling
2324 # us our child process terminated.
2330 # us our child process terminated.
2325 terminated = set()
2331 terminated = set()
2326 def handler(signum, frame):
2332 def handler(signum, frame):
2327 terminated.add(os.wait())
2333 terminated.add(os.wait())
2328 prevhandler = None
2334 prevhandler = None
2329 SIGCHLD = getattr(signal, 'SIGCHLD', None)
2335 SIGCHLD = getattr(signal, 'SIGCHLD', None)
2330 if SIGCHLD is not None:
2336 if SIGCHLD is not None:
2331 prevhandler = signal.signal(SIGCHLD, handler)
2337 prevhandler = signal.signal(SIGCHLD, handler)
2332 try:
2338 try:
2333 pid = spawndetached(args)
2339 pid = spawndetached(args)
2334 while not condfn():
2340 while not condfn():
2335 if ((pid in terminated or not testpid(pid))
2341 if ((pid in terminated or not testpid(pid))
2336 and not condfn()):
2342 and not condfn()):
2337 return -1
2343 return -1
2338 time.sleep(0.1)
2344 time.sleep(0.1)
2339 return pid
2345 return pid
2340 finally:
2346 finally:
2341 if prevhandler is not None:
2347 if prevhandler is not None:
2342 signal.signal(signal.SIGCHLD, prevhandler)
2348 signal.signal(signal.SIGCHLD, prevhandler)
2343
2349
2344 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2350 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2345 """Return the result of interpolating items in the mapping into string s.
2351 """Return the result of interpolating items in the mapping into string s.
2346
2352
2347 prefix is a single character string, or a two character string with
2353 prefix is a single character string, or a two character string with
2348 a backslash as the first character if the prefix needs to be escaped in
2354 a backslash as the first character if the prefix needs to be escaped in
2349 a regular expression.
2355 a regular expression.
2350
2356
2351 fn is an optional function that will be applied to the replacement text
2357 fn is an optional function that will be applied to the replacement text
2352 just before replacement.
2358 just before replacement.
2353
2359
2354 escape_prefix is an optional flag that allows using doubled prefix for
2360 escape_prefix is an optional flag that allows using doubled prefix for
2355 its escaping.
2361 its escaping.
2356 """
2362 """
2357 fn = fn or (lambda s: s)
2363 fn = fn or (lambda s: s)
2358 patterns = '|'.join(mapping.keys())
2364 patterns = '|'.join(mapping.keys())
2359 if escape_prefix:
2365 if escape_prefix:
2360 patterns += '|' + prefix
2366 patterns += '|' + prefix
2361 if len(prefix) > 1:
2367 if len(prefix) > 1:
2362 prefix_char = prefix[1:]
2368 prefix_char = prefix[1:]
2363 else:
2369 else:
2364 prefix_char = prefix
2370 prefix_char = prefix
2365 mapping[prefix_char] = prefix_char
2371 mapping[prefix_char] = prefix_char
2366 r = remod.compile(r'%s(%s)' % (prefix, patterns))
2372 r = remod.compile(r'%s(%s)' % (prefix, patterns))
2367 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2373 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2368
2374
2369 def getport(port):
2375 def getport(port):
2370 """Return the port for a given network service.
2376 """Return the port for a given network service.
2371
2377
2372 If port is an integer, it's returned as is. If it's a string, it's
2378 If port is an integer, it's returned as is. If it's a string, it's
2373 looked up using socket.getservbyname(). If there's no matching
2379 looked up using socket.getservbyname(). If there's no matching
2374 service, error.Abort is raised.
2380 service, error.Abort is raised.
2375 """
2381 """
2376 try:
2382 try:
2377 return int(port)
2383 return int(port)
2378 except ValueError:
2384 except ValueError:
2379 pass
2385 pass
2380
2386
2381 try:
2387 try:
2382 return socket.getservbyname(port)
2388 return socket.getservbyname(port)
2383 except socket.error:
2389 except socket.error:
2384 raise Abort(_("no port number associated with service '%s'") % port)
2390 raise Abort(_("no port number associated with service '%s'") % port)
2385
2391
2386 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
2392 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
2387 '0': False, 'no': False, 'false': False, 'off': False,
2393 '0': False, 'no': False, 'false': False, 'off': False,
2388 'never': False}
2394 'never': False}
2389
2395
2390 def parsebool(s):
2396 def parsebool(s):
2391 """Parse s into a boolean.
2397 """Parse s into a boolean.
2392
2398
2393 If s is not a valid boolean, returns None.
2399 If s is not a valid boolean, returns None.
2394 """
2400 """
2395 return _booleans.get(s.lower(), None)
2401 return _booleans.get(s.lower(), None)
2396
2402
2397 _hextochr = dict((a + b, chr(int(a + b, 16)))
2403 _hextochr = dict((a + b, chr(int(a + b, 16)))
2398 for a in string.hexdigits for b in string.hexdigits)
2404 for a in string.hexdigits for b in string.hexdigits)
2399
2405
2400 class url(object):
2406 class url(object):
2401 r"""Reliable URL parser.
2407 r"""Reliable URL parser.
2402
2408
2403 This parses URLs and provides attributes for the following
2409 This parses URLs and provides attributes for the following
2404 components:
2410 components:
2405
2411
2406 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2412 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2407
2413
2408 Missing components are set to None. The only exception is
2414 Missing components are set to None. The only exception is
2409 fragment, which is set to '' if present but empty.
2415 fragment, which is set to '' if present but empty.
2410
2416
2411 If parsefragment is False, fragment is included in query. If
2417 If parsefragment is False, fragment is included in query. If
2412 parsequery is False, query is included in path. If both are
2418 parsequery is False, query is included in path. If both are
2413 False, both fragment and query are included in path.
2419 False, both fragment and query are included in path.
2414
2420
2415 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2421 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2416
2422
2417 Note that for backward compatibility reasons, bundle URLs do not
2423 Note that for backward compatibility reasons, bundle URLs do not
2418 take host names. That means 'bundle://../' has a path of '../'.
2424 take host names. That means 'bundle://../' has a path of '../'.
2419
2425
2420 Examples:
2426 Examples:
2421
2427
2422 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
2428 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
2423 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2429 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2424 >>> url('ssh://[::1]:2200//home/joe/repo')
2430 >>> url('ssh://[::1]:2200//home/joe/repo')
2425 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2431 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2426 >>> url('file:///home/joe/repo')
2432 >>> url('file:///home/joe/repo')
2427 <url scheme: 'file', path: '/home/joe/repo'>
2433 <url scheme: 'file', path: '/home/joe/repo'>
2428 >>> url('file:///c:/temp/foo/')
2434 >>> url('file:///c:/temp/foo/')
2429 <url scheme: 'file', path: 'c:/temp/foo/'>
2435 <url scheme: 'file', path: 'c:/temp/foo/'>
2430 >>> url('bundle:foo')
2436 >>> url('bundle:foo')
2431 <url scheme: 'bundle', path: 'foo'>
2437 <url scheme: 'bundle', path: 'foo'>
2432 >>> url('bundle://../foo')
2438 >>> url('bundle://../foo')
2433 <url scheme: 'bundle', path: '../foo'>
2439 <url scheme: 'bundle', path: '../foo'>
2434 >>> url(r'c:\foo\bar')
2440 >>> url(r'c:\foo\bar')
2435 <url path: 'c:\\foo\\bar'>
2441 <url path: 'c:\\foo\\bar'>
2436 >>> url(r'\\blah\blah\blah')
2442 >>> url(r'\\blah\blah\blah')
2437 <url path: '\\\\blah\\blah\\blah'>
2443 <url path: '\\\\blah\\blah\\blah'>
2438 >>> url(r'\\blah\blah\blah#baz')
2444 >>> url(r'\\blah\blah\blah#baz')
2439 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2445 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2440 >>> url(r'file:///C:\users\me')
2446 >>> url(r'file:///C:\users\me')
2441 <url scheme: 'file', path: 'C:\\users\\me'>
2447 <url scheme: 'file', path: 'C:\\users\\me'>
2442
2448
2443 Authentication credentials:
2449 Authentication credentials:
2444
2450
2445 >>> url('ssh://joe:xyz@x/repo')
2451 >>> url('ssh://joe:xyz@x/repo')
2446 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2452 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2447 >>> url('ssh://joe@x/repo')
2453 >>> url('ssh://joe@x/repo')
2448 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2454 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2449
2455
2450 Query strings and fragments:
2456 Query strings and fragments:
2451
2457
2452 >>> url('http://host/a?b#c')
2458 >>> url('http://host/a?b#c')
2453 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2459 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2454 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2460 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2455 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2461 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2456
2462
2457 Empty path:
2463 Empty path:
2458
2464
2459 >>> url('')
2465 >>> url('')
2460 <url path: ''>
2466 <url path: ''>
2461 >>> url('#a')
2467 >>> url('#a')
2462 <url path: '', fragment: 'a'>
2468 <url path: '', fragment: 'a'>
2463 >>> url('http://host/')
2469 >>> url('http://host/')
2464 <url scheme: 'http', host: 'host', path: ''>
2470 <url scheme: 'http', host: 'host', path: ''>
2465 >>> url('http://host/#a')
2471 >>> url('http://host/#a')
2466 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
2472 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
2467
2473
2468 Only scheme:
2474 Only scheme:
2469
2475
2470 >>> url('http:')
2476 >>> url('http:')
2471 <url scheme: 'http'>
2477 <url scheme: 'http'>
2472 """
2478 """
2473
2479
2474 _safechars = "!~*'()+"
2480 _safechars = "!~*'()+"
2475 _safepchars = "/!~*'()+:\\"
2481 _safepchars = "/!~*'()+:\\"
2476 _matchscheme = remod.compile('^[a-zA-Z0-9+.\\-]+:').match
2482 _matchscheme = remod.compile('^[a-zA-Z0-9+.\\-]+:').match
2477
2483
2478 def __init__(self, path, parsequery=True, parsefragment=True):
2484 def __init__(self, path, parsequery=True, parsefragment=True):
2479 # We slowly chomp away at path until we have only the path left
2485 # We slowly chomp away at path until we have only the path left
2480 self.scheme = self.user = self.passwd = self.host = None
2486 self.scheme = self.user = self.passwd = self.host = None
2481 self.port = self.path = self.query = self.fragment = None
2487 self.port = self.path = self.query = self.fragment = None
2482 self._localpath = True
2488 self._localpath = True
2483 self._hostport = ''
2489 self._hostport = ''
2484 self._origpath = path
2490 self._origpath = path
2485
2491
2486 if parsefragment and '#' in path:
2492 if parsefragment and '#' in path:
2487 path, self.fragment = path.split('#', 1)
2493 path, self.fragment = path.split('#', 1)
2488
2494
2489 # special case for Windows drive letters and UNC paths
2495 # special case for Windows drive letters and UNC paths
2490 if hasdriveletter(path) or path.startswith('\\\\'):
2496 if hasdriveletter(path) or path.startswith('\\\\'):
2491 self.path = path
2497 self.path = path
2492 return
2498 return
2493
2499
2494 # For compatibility reasons, we can't handle bundle paths as
2500 # For compatibility reasons, we can't handle bundle paths as
2495 # normal URLS
2501 # normal URLS
2496 if path.startswith('bundle:'):
2502 if path.startswith('bundle:'):
2497 self.scheme = 'bundle'
2503 self.scheme = 'bundle'
2498 path = path[7:]
2504 path = path[7:]
2499 if path.startswith('//'):
2505 if path.startswith('//'):
2500 path = path[2:]
2506 path = path[2:]
2501 self.path = path
2507 self.path = path
2502 return
2508 return
2503
2509
2504 if self._matchscheme(path):
2510 if self._matchscheme(path):
2505 parts = path.split(':', 1)
2511 parts = path.split(':', 1)
2506 if parts[0]:
2512 if parts[0]:
2507 self.scheme, path = parts
2513 self.scheme, path = parts
2508 self._localpath = False
2514 self._localpath = False
2509
2515
2510 if not path:
2516 if not path:
2511 path = None
2517 path = None
2512 if self._localpath:
2518 if self._localpath:
2513 self.path = ''
2519 self.path = ''
2514 return
2520 return
2515 else:
2521 else:
2516 if self._localpath:
2522 if self._localpath:
2517 self.path = path
2523 self.path = path
2518 return
2524 return
2519
2525
2520 if parsequery and '?' in path:
2526 if parsequery and '?' in path:
2521 path, self.query = path.split('?', 1)
2527 path, self.query = path.split('?', 1)
2522 if not path:
2528 if not path:
2523 path = None
2529 path = None
2524 if not self.query:
2530 if not self.query:
2525 self.query = None
2531 self.query = None
2526
2532
2527 # // is required to specify a host/authority
2533 # // is required to specify a host/authority
2528 if path and path.startswith('//'):
2534 if path and path.startswith('//'):
2529 parts = path[2:].split('/', 1)
2535 parts = path[2:].split('/', 1)
2530 if len(parts) > 1:
2536 if len(parts) > 1:
2531 self.host, path = parts
2537 self.host, path = parts
2532 else:
2538 else:
2533 self.host = parts[0]
2539 self.host = parts[0]
2534 path = None
2540 path = None
2535 if not self.host:
2541 if not self.host:
2536 self.host = None
2542 self.host = None
2537 # path of file:///d is /d
2543 # path of file:///d is /d
2538 # path of file:///d:/ is d:/, not /d:/
2544 # path of file:///d:/ is d:/, not /d:/
2539 if path and not hasdriveletter(path):
2545 if path and not hasdriveletter(path):
2540 path = '/' + path
2546 path = '/' + path
2541
2547
2542 if self.host and '@' in self.host:
2548 if self.host and '@' in self.host:
2543 self.user, self.host = self.host.rsplit('@', 1)
2549 self.user, self.host = self.host.rsplit('@', 1)
2544 if ':' in self.user:
2550 if ':' in self.user:
2545 self.user, self.passwd = self.user.split(':', 1)
2551 self.user, self.passwd = self.user.split(':', 1)
2546 if not self.host:
2552 if not self.host:
2547 self.host = None
2553 self.host = None
2548
2554
2549 # Don't split on colons in IPv6 addresses without ports
2555 # Don't split on colons in IPv6 addresses without ports
2550 if (self.host and ':' in self.host and
2556 if (self.host and ':' in self.host and
2551 not (self.host.startswith('[') and self.host.endswith(']'))):
2557 not (self.host.startswith('[') and self.host.endswith(']'))):
2552 self._hostport = self.host
2558 self._hostport = self.host
2553 self.host, self.port = self.host.rsplit(':', 1)
2559 self.host, self.port = self.host.rsplit(':', 1)
2554 if not self.host:
2560 if not self.host:
2555 self.host = None
2561 self.host = None
2556
2562
2557 if (self.host and self.scheme == 'file' and
2563 if (self.host and self.scheme == 'file' and
2558 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2564 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2559 raise Abort(_('file:// URLs can only refer to localhost'))
2565 raise Abort(_('file:// URLs can only refer to localhost'))
2560
2566
2561 self.path = path
2567 self.path = path
2562
2568
2563 # leave the query string escaped
2569 # leave the query string escaped
2564 for a in ('user', 'passwd', 'host', 'port',
2570 for a in ('user', 'passwd', 'host', 'port',
2565 'path', 'fragment'):
2571 'path', 'fragment'):
2566 v = getattr(self, a)
2572 v = getattr(self, a)
2567 if v is not None:
2573 if v is not None:
2568 setattr(self, a, pycompat.urlunquote(v))
2574 setattr(self, a, pycompat.urlunquote(v))
2569
2575
2570 def __repr__(self):
2576 def __repr__(self):
2571 attrs = []
2577 attrs = []
2572 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2578 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2573 'query', 'fragment'):
2579 'query', 'fragment'):
2574 v = getattr(self, a)
2580 v = getattr(self, a)
2575 if v is not None:
2581 if v is not None:
2576 attrs.append('%s: %r' % (a, v))
2582 attrs.append('%s: %r' % (a, v))
2577 return '<url %s>' % ', '.join(attrs)
2583 return '<url %s>' % ', '.join(attrs)
2578
2584
2579 def __str__(self):
2585 def __str__(self):
2580 r"""Join the URL's components back into a URL string.
2586 r"""Join the URL's components back into a URL string.
2581
2587
2582 Examples:
2588 Examples:
2583
2589
2584 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2590 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2585 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2591 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2586 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2592 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2587 'http://user:pw@host:80/?foo=bar&baz=42'
2593 'http://user:pw@host:80/?foo=bar&baz=42'
2588 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2594 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2589 'http://user:pw@host:80/?foo=bar%3dbaz'
2595 'http://user:pw@host:80/?foo=bar%3dbaz'
2590 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2596 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2591 'ssh://user:pw@[::1]:2200//home/joe#'
2597 'ssh://user:pw@[::1]:2200//home/joe#'
2592 >>> str(url('http://localhost:80//'))
2598 >>> str(url('http://localhost:80//'))
2593 'http://localhost:80//'
2599 'http://localhost:80//'
2594 >>> str(url('http://localhost:80/'))
2600 >>> str(url('http://localhost:80/'))
2595 'http://localhost:80/'
2601 'http://localhost:80/'
2596 >>> str(url('http://localhost:80'))
2602 >>> str(url('http://localhost:80'))
2597 'http://localhost:80/'
2603 'http://localhost:80/'
2598 >>> str(url('bundle:foo'))
2604 >>> str(url('bundle:foo'))
2599 'bundle:foo'
2605 'bundle:foo'
2600 >>> str(url('bundle://../foo'))
2606 >>> str(url('bundle://../foo'))
2601 'bundle:../foo'
2607 'bundle:../foo'
2602 >>> str(url('path'))
2608 >>> str(url('path'))
2603 'path'
2609 'path'
2604 >>> str(url('file:///tmp/foo/bar'))
2610 >>> str(url('file:///tmp/foo/bar'))
2605 'file:///tmp/foo/bar'
2611 'file:///tmp/foo/bar'
2606 >>> str(url('file:///c:/tmp/foo/bar'))
2612 >>> str(url('file:///c:/tmp/foo/bar'))
2607 'file:///c:/tmp/foo/bar'
2613 'file:///c:/tmp/foo/bar'
2608 >>> print url(r'bundle:foo\bar')
2614 >>> print url(r'bundle:foo\bar')
2609 bundle:foo\bar
2615 bundle:foo\bar
2610 >>> print url(r'file:///D:\data\hg')
2616 >>> print url(r'file:///D:\data\hg')
2611 file:///D:\data\hg
2617 file:///D:\data\hg
2612 """
2618 """
2613 if self._localpath:
2619 if self._localpath:
2614 s = self.path
2620 s = self.path
2615 if self.scheme == 'bundle':
2621 if self.scheme == 'bundle':
2616 s = 'bundle:' + s
2622 s = 'bundle:' + s
2617 if self.fragment:
2623 if self.fragment:
2618 s += '#' + self.fragment
2624 s += '#' + self.fragment
2619 return s
2625 return s
2620
2626
2621 s = self.scheme + ':'
2627 s = self.scheme + ':'
2622 if self.user or self.passwd or self.host:
2628 if self.user or self.passwd or self.host:
2623 s += '//'
2629 s += '//'
2624 elif self.scheme and (not self.path or self.path.startswith('/')
2630 elif self.scheme and (not self.path or self.path.startswith('/')
2625 or hasdriveletter(self.path)):
2631 or hasdriveletter(self.path)):
2626 s += '//'
2632 s += '//'
2627 if hasdriveletter(self.path):
2633 if hasdriveletter(self.path):
2628 s += '/'
2634 s += '/'
2629 if self.user:
2635 if self.user:
2630 s += urlreq.quote(self.user, safe=self._safechars)
2636 s += urlreq.quote(self.user, safe=self._safechars)
2631 if self.passwd:
2637 if self.passwd:
2632 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2638 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2633 if self.user or self.passwd:
2639 if self.user or self.passwd:
2634 s += '@'
2640 s += '@'
2635 if self.host:
2641 if self.host:
2636 if not (self.host.startswith('[') and self.host.endswith(']')):
2642 if not (self.host.startswith('[') and self.host.endswith(']')):
2637 s += urlreq.quote(self.host)
2643 s += urlreq.quote(self.host)
2638 else:
2644 else:
2639 s += self.host
2645 s += self.host
2640 if self.port:
2646 if self.port:
2641 s += ':' + urlreq.quote(self.port)
2647 s += ':' + urlreq.quote(self.port)
2642 if self.host:
2648 if self.host:
2643 s += '/'
2649 s += '/'
2644 if self.path:
2650 if self.path:
2645 # TODO: similar to the query string, we should not unescape the
2651 # TODO: similar to the query string, we should not unescape the
2646 # path when we store it, the path might contain '%2f' = '/',
2652 # path when we store it, the path might contain '%2f' = '/',
2647 # which we should *not* escape.
2653 # which we should *not* escape.
2648 s += urlreq.quote(self.path, safe=self._safepchars)
2654 s += urlreq.quote(self.path, safe=self._safepchars)
2649 if self.query:
2655 if self.query:
2650 # we store the query in escaped form.
2656 # we store the query in escaped form.
2651 s += '?' + self.query
2657 s += '?' + self.query
2652 if self.fragment is not None:
2658 if self.fragment is not None:
2653 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2659 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2654 return s
2660 return s
2655
2661
2656 def authinfo(self):
2662 def authinfo(self):
2657 user, passwd = self.user, self.passwd
2663 user, passwd = self.user, self.passwd
2658 try:
2664 try:
2659 self.user, self.passwd = None, None
2665 self.user, self.passwd = None, None
2660 s = str(self)
2666 s = str(self)
2661 finally:
2667 finally:
2662 self.user, self.passwd = user, passwd
2668 self.user, self.passwd = user, passwd
2663 if not self.user:
2669 if not self.user:
2664 return (s, None)
2670 return (s, None)
2665 # authinfo[1] is passed to urllib2 password manager, and its
2671 # authinfo[1] is passed to urllib2 password manager, and its
2666 # URIs must not contain credentials. The host is passed in the
2672 # URIs must not contain credentials. The host is passed in the
2667 # URIs list because Python < 2.4.3 uses only that to search for
2673 # URIs list because Python < 2.4.3 uses only that to search for
2668 # a password.
2674 # a password.
2669 return (s, (None, (s, self.host),
2675 return (s, (None, (s, self.host),
2670 self.user, self.passwd or ''))
2676 self.user, self.passwd or ''))
2671
2677
2672 def isabs(self):
2678 def isabs(self):
2673 if self.scheme and self.scheme != 'file':
2679 if self.scheme and self.scheme != 'file':
2674 return True # remote URL
2680 return True # remote URL
2675 if hasdriveletter(self.path):
2681 if hasdriveletter(self.path):
2676 return True # absolute for our purposes - can't be joined()
2682 return True # absolute for our purposes - can't be joined()
2677 if self.path.startswith(r'\\'):
2683 if self.path.startswith(r'\\'):
2678 return True # Windows UNC path
2684 return True # Windows UNC path
2679 if self.path.startswith('/'):
2685 if self.path.startswith('/'):
2680 return True # POSIX-style
2686 return True # POSIX-style
2681 return False
2687 return False
2682
2688
2683 def localpath(self):
2689 def localpath(self):
2684 if self.scheme == 'file' or self.scheme == 'bundle':
2690 if self.scheme == 'file' or self.scheme == 'bundle':
2685 path = self.path or '/'
2691 path = self.path or '/'
2686 # For Windows, we need to promote hosts containing drive
2692 # For Windows, we need to promote hosts containing drive
2687 # letters to paths with drive letters.
2693 # letters to paths with drive letters.
2688 if hasdriveletter(self._hostport):
2694 if hasdriveletter(self._hostport):
2689 path = self._hostport + '/' + self.path
2695 path = self._hostport + '/' + self.path
2690 elif (self.host is not None and self.path
2696 elif (self.host is not None and self.path
2691 and not hasdriveletter(path)):
2697 and not hasdriveletter(path)):
2692 path = '/' + path
2698 path = '/' + path
2693 return path
2699 return path
2694 return self._origpath
2700 return self._origpath
2695
2701
2696 def islocal(self):
2702 def islocal(self):
2697 '''whether localpath will return something that posixfile can open'''
2703 '''whether localpath will return something that posixfile can open'''
2698 return (not self.scheme or self.scheme == 'file'
2704 return (not self.scheme or self.scheme == 'file'
2699 or self.scheme == 'bundle')
2705 or self.scheme == 'bundle')
2700
2706
2701 def hasscheme(path):
2707 def hasscheme(path):
2702 return bool(url(path).scheme)
2708 return bool(url(path).scheme)
2703
2709
2704 def hasdriveletter(path):
2710 def hasdriveletter(path):
2705 return path and path[1:2] == ':' and path[0:1].isalpha()
2711 return path and path[1:2] == ':' and path[0:1].isalpha()
2706
2712
2707 def urllocalpath(path):
2713 def urllocalpath(path):
2708 return url(path, parsequery=False, parsefragment=False).localpath()
2714 return url(path, parsequery=False, parsefragment=False).localpath()
2709
2715
2710 def hidepassword(u):
2716 def hidepassword(u):
2711 '''hide user credential in a url string'''
2717 '''hide user credential in a url string'''
2712 u = url(u)
2718 u = url(u)
2713 if u.passwd:
2719 if u.passwd:
2714 u.passwd = '***'
2720 u.passwd = '***'
2715 return str(u)
2721 return str(u)
2716
2722
2717 def removeauth(u):
2723 def removeauth(u):
2718 '''remove all authentication information from a url string'''
2724 '''remove all authentication information from a url string'''
2719 u = url(u)
2725 u = url(u)
2720 u.user = u.passwd = None
2726 u.user = u.passwd = None
2721 return str(u)
2727 return str(u)
2722
2728
2723 def isatty(fp):
2729 def isatty(fp):
2724 try:
2730 try:
2725 return fp.isatty()
2731 return fp.isatty()
2726 except AttributeError:
2732 except AttributeError:
2727 return False
2733 return False
2728
2734
2729 timecount = unitcountfn(
2735 timecount = unitcountfn(
2730 (1, 1e3, _('%.0f s')),
2736 (1, 1e3, _('%.0f s')),
2731 (100, 1, _('%.1f s')),
2737 (100, 1, _('%.1f s')),
2732 (10, 1, _('%.2f s')),
2738 (10, 1, _('%.2f s')),
2733 (1, 1, _('%.3f s')),
2739 (1, 1, _('%.3f s')),
2734 (100, 0.001, _('%.1f ms')),
2740 (100, 0.001, _('%.1f ms')),
2735 (10, 0.001, _('%.2f ms')),
2741 (10, 0.001, _('%.2f ms')),
2736 (1, 0.001, _('%.3f ms')),
2742 (1, 0.001, _('%.3f ms')),
2737 (100, 0.000001, _('%.1f us')),
2743 (100, 0.000001, _('%.1f us')),
2738 (10, 0.000001, _('%.2f us')),
2744 (10, 0.000001, _('%.2f us')),
2739 (1, 0.000001, _('%.3f us')),
2745 (1, 0.000001, _('%.3f us')),
2740 (100, 0.000000001, _('%.1f ns')),
2746 (100, 0.000000001, _('%.1f ns')),
2741 (10, 0.000000001, _('%.2f ns')),
2747 (10, 0.000000001, _('%.2f ns')),
2742 (1, 0.000000001, _('%.3f ns')),
2748 (1, 0.000000001, _('%.3f ns')),
2743 )
2749 )
2744
2750
2745 _timenesting = [0]
2751 _timenesting = [0]
2746
2752
2747 def timed(func):
2753 def timed(func):
2748 '''Report the execution time of a function call to stderr.
2754 '''Report the execution time of a function call to stderr.
2749
2755
2750 During development, use as a decorator when you need to measure
2756 During development, use as a decorator when you need to measure
2751 the cost of a function, e.g. as follows:
2757 the cost of a function, e.g. as follows:
2752
2758
2753 @util.timed
2759 @util.timed
2754 def foo(a, b, c):
2760 def foo(a, b, c):
2755 pass
2761 pass
2756 '''
2762 '''
2757
2763
2758 def wrapper(*args, **kwargs):
2764 def wrapper(*args, **kwargs):
2759 start = time.time()
2765 start = time.time()
2760 indent = 2
2766 indent = 2
2761 _timenesting[0] += indent
2767 _timenesting[0] += indent
2762 try:
2768 try:
2763 return func(*args, **kwargs)
2769 return func(*args, **kwargs)
2764 finally:
2770 finally:
2765 elapsed = time.time() - start
2771 elapsed = time.time() - start
2766 _timenesting[0] -= indent
2772 _timenesting[0] -= indent
2767 stderr.write('%s%s: %s\n' %
2773 stderr.write('%s%s: %s\n' %
2768 (' ' * _timenesting[0], func.__name__,
2774 (' ' * _timenesting[0], func.__name__,
2769 timecount(elapsed)))
2775 timecount(elapsed)))
2770 return wrapper
2776 return wrapper
2771
2777
2772 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2778 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2773 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2779 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2774
2780
2775 def sizetoint(s):
2781 def sizetoint(s):
2776 '''Convert a space specifier to a byte count.
2782 '''Convert a space specifier to a byte count.
2777
2783
2778 >>> sizetoint('30')
2784 >>> sizetoint('30')
2779 30
2785 30
2780 >>> sizetoint('2.2kb')
2786 >>> sizetoint('2.2kb')
2781 2252
2787 2252
2782 >>> sizetoint('6M')
2788 >>> sizetoint('6M')
2783 6291456
2789 6291456
2784 '''
2790 '''
2785 t = s.strip().lower()
2791 t = s.strip().lower()
2786 try:
2792 try:
2787 for k, u in _sizeunits:
2793 for k, u in _sizeunits:
2788 if t.endswith(k):
2794 if t.endswith(k):
2789 return int(float(t[:-len(k)]) * u)
2795 return int(float(t[:-len(k)]) * u)
2790 return int(t)
2796 return int(t)
2791 except ValueError:
2797 except ValueError:
2792 raise error.ParseError(_("couldn't parse size: %s") % s)
2798 raise error.ParseError(_("couldn't parse size: %s") % s)
2793
2799
2794 class hooks(object):
2800 class hooks(object):
2795 '''A collection of hook functions that can be used to extend a
2801 '''A collection of hook functions that can be used to extend a
2796 function's behavior. Hooks are called in lexicographic order,
2802 function's behavior. Hooks are called in lexicographic order,
2797 based on the names of their sources.'''
2803 based on the names of their sources.'''
2798
2804
2799 def __init__(self):
2805 def __init__(self):
2800 self._hooks = []
2806 self._hooks = []
2801
2807
2802 def add(self, source, hook):
2808 def add(self, source, hook):
2803 self._hooks.append((source, hook))
2809 self._hooks.append((source, hook))
2804
2810
2805 def __call__(self, *args):
2811 def __call__(self, *args):
2806 self._hooks.sort(key=lambda x: x[0])
2812 self._hooks.sort(key=lambda x: x[0])
2807 results = []
2813 results = []
2808 for source, hook in self._hooks:
2814 for source, hook in self._hooks:
2809 results.append(hook(*args))
2815 results.append(hook(*args))
2810 return results
2816 return results
2811
2817
2812 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%s'):
2818 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%s'):
2813 '''Yields lines for a nicely formatted stacktrace.
2819 '''Yields lines for a nicely formatted stacktrace.
2814 Skips the 'skip' last entries.
2820 Skips the 'skip' last entries.
2815 Each file+linenumber is formatted according to fileline.
2821 Each file+linenumber is formatted according to fileline.
2816 Each line is formatted according to line.
2822 Each line is formatted according to line.
2817 If line is None, it yields:
2823 If line is None, it yields:
2818 length of longest filepath+line number,
2824 length of longest filepath+line number,
2819 filepath+linenumber,
2825 filepath+linenumber,
2820 function
2826 function
2821
2827
2822 Not be used in production code but very convenient while developing.
2828 Not be used in production code but very convenient while developing.
2823 '''
2829 '''
2824 entries = [(fileline % (fn, ln), func)
2830 entries = [(fileline % (fn, ln), func)
2825 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]]
2831 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]]
2826 if entries:
2832 if entries:
2827 fnmax = max(len(entry[0]) for entry in entries)
2833 fnmax = max(len(entry[0]) for entry in entries)
2828 for fnln, func in entries:
2834 for fnln, func in entries:
2829 if line is None:
2835 if line is None:
2830 yield (fnmax, fnln, func)
2836 yield (fnmax, fnln, func)
2831 else:
2837 else:
2832 yield line % (fnmax, fnln, func)
2838 yield line % (fnmax, fnln, func)
2833
2839
2834 def debugstacktrace(msg='stacktrace', skip=0, f=stderr, otherf=stdout):
2840 def debugstacktrace(msg='stacktrace', skip=0, f=stderr, otherf=stdout):
2835 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
2841 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
2836 Skips the 'skip' last entries. By default it will flush stdout first.
2842 Skips the 'skip' last entries. By default it will flush stdout first.
2837 It can be used everywhere and intentionally does not require an ui object.
2843 It can be used everywhere and intentionally does not require an ui object.
2838 Not be used in production code but very convenient while developing.
2844 Not be used in production code but very convenient while developing.
2839 '''
2845 '''
2840 if otherf:
2846 if otherf:
2841 otherf.flush()
2847 otherf.flush()
2842 f.write('%s at:\n' % msg)
2848 f.write('%s at:\n' % msg)
2843 for line in getstackframes(skip + 1):
2849 for line in getstackframes(skip + 1):
2844 f.write(line)
2850 f.write(line)
2845 f.flush()
2851 f.flush()
2846
2852
2847 class dirs(object):
2853 class dirs(object):
2848 '''a multiset of directory names from a dirstate or manifest'''
2854 '''a multiset of directory names from a dirstate or manifest'''
2849
2855
2850 def __init__(self, map, skip=None):
2856 def __init__(self, map, skip=None):
2851 self._dirs = {}
2857 self._dirs = {}
2852 addpath = self.addpath
2858 addpath = self.addpath
2853 if safehasattr(map, 'iteritems') and skip is not None:
2859 if safehasattr(map, 'iteritems') and skip is not None:
2854 for f, s in map.iteritems():
2860 for f, s in map.iteritems():
2855 if s[0] != skip:
2861 if s[0] != skip:
2856 addpath(f)
2862 addpath(f)
2857 else:
2863 else:
2858 for f in map:
2864 for f in map:
2859 addpath(f)
2865 addpath(f)
2860
2866
2861 def addpath(self, path):
2867 def addpath(self, path):
2862 dirs = self._dirs
2868 dirs = self._dirs
2863 for base in finddirs(path):
2869 for base in finddirs(path):
2864 if base in dirs:
2870 if base in dirs:
2865 dirs[base] += 1
2871 dirs[base] += 1
2866 return
2872 return
2867 dirs[base] = 1
2873 dirs[base] = 1
2868
2874
2869 def delpath(self, path):
2875 def delpath(self, path):
2870 dirs = self._dirs
2876 dirs = self._dirs
2871 for base in finddirs(path):
2877 for base in finddirs(path):
2872 if dirs[base] > 1:
2878 if dirs[base] > 1:
2873 dirs[base] -= 1
2879 dirs[base] -= 1
2874 return
2880 return
2875 del dirs[base]
2881 del dirs[base]
2876
2882
2877 def __iter__(self):
2883 def __iter__(self):
2878 return self._dirs.iterkeys()
2884 return self._dirs.iterkeys()
2879
2885
2880 def __contains__(self, d):
2886 def __contains__(self, d):
2881 return d in self._dirs
2887 return d in self._dirs
2882
2888
2883 if safehasattr(parsers, 'dirs'):
2889 if safehasattr(parsers, 'dirs'):
2884 dirs = parsers.dirs
2890 dirs = parsers.dirs
2885
2891
2886 def finddirs(path):
2892 def finddirs(path):
2887 pos = path.rfind('/')
2893 pos = path.rfind('/')
2888 while pos != -1:
2894 while pos != -1:
2889 yield path[:pos]
2895 yield path[:pos]
2890 pos = path.rfind('/', 0, pos)
2896 pos = path.rfind('/', 0, pos)
2891
2897
2892 class ctxmanager(object):
2898 class ctxmanager(object):
2893 '''A context manager for use in 'with' blocks to allow multiple
2899 '''A context manager for use in 'with' blocks to allow multiple
2894 contexts to be entered at once. This is both safer and more
2900 contexts to be entered at once. This is both safer and more
2895 flexible than contextlib.nested.
2901 flexible than contextlib.nested.
2896
2902
2897 Once Mercurial supports Python 2.7+, this will become mostly
2903 Once Mercurial supports Python 2.7+, this will become mostly
2898 unnecessary.
2904 unnecessary.
2899 '''
2905 '''
2900
2906
2901 def __init__(self, *args):
2907 def __init__(self, *args):
2902 '''Accepts a list of no-argument functions that return context
2908 '''Accepts a list of no-argument functions that return context
2903 managers. These will be invoked at __call__ time.'''
2909 managers. These will be invoked at __call__ time.'''
2904 self._pending = args
2910 self._pending = args
2905 self._atexit = []
2911 self._atexit = []
2906
2912
2907 def __enter__(self):
2913 def __enter__(self):
2908 return self
2914 return self
2909
2915
2910 def enter(self):
2916 def enter(self):
2911 '''Create and enter context managers in the order in which they were
2917 '''Create and enter context managers in the order in which they were
2912 passed to the constructor.'''
2918 passed to the constructor.'''
2913 values = []
2919 values = []
2914 for func in self._pending:
2920 for func in self._pending:
2915 obj = func()
2921 obj = func()
2916 values.append(obj.__enter__())
2922 values.append(obj.__enter__())
2917 self._atexit.append(obj.__exit__)
2923 self._atexit.append(obj.__exit__)
2918 del self._pending
2924 del self._pending
2919 return values
2925 return values
2920
2926
2921 def atexit(self, func, *args, **kwargs):
2927 def atexit(self, func, *args, **kwargs):
2922 '''Add a function to call when this context manager exits. The
2928 '''Add a function to call when this context manager exits. The
2923 ordering of multiple atexit calls is unspecified, save that
2929 ordering of multiple atexit calls is unspecified, save that
2924 they will happen before any __exit__ functions.'''
2930 they will happen before any __exit__ functions.'''
2925 def wrapper(exc_type, exc_val, exc_tb):
2931 def wrapper(exc_type, exc_val, exc_tb):
2926 func(*args, **kwargs)
2932 func(*args, **kwargs)
2927 self._atexit.append(wrapper)
2933 self._atexit.append(wrapper)
2928 return func
2934 return func
2929
2935
2930 def __exit__(self, exc_type, exc_val, exc_tb):
2936 def __exit__(self, exc_type, exc_val, exc_tb):
2931 '''Context managers are exited in the reverse order from which
2937 '''Context managers are exited in the reverse order from which
2932 they were created.'''
2938 they were created.'''
2933 received = exc_type is not None
2939 received = exc_type is not None
2934 suppressed = False
2940 suppressed = False
2935 pending = None
2941 pending = None
2936 self._atexit.reverse()
2942 self._atexit.reverse()
2937 for exitfunc in self._atexit:
2943 for exitfunc in self._atexit:
2938 try:
2944 try:
2939 if exitfunc(exc_type, exc_val, exc_tb):
2945 if exitfunc(exc_type, exc_val, exc_tb):
2940 suppressed = True
2946 suppressed = True
2941 exc_type = None
2947 exc_type = None
2942 exc_val = None
2948 exc_val = None
2943 exc_tb = None
2949 exc_tb = None
2944 except BaseException:
2950 except BaseException:
2945 pending = sys.exc_info()
2951 pending = sys.exc_info()
2946 exc_type, exc_val, exc_tb = pending = sys.exc_info()
2952 exc_type, exc_val, exc_tb = pending = sys.exc_info()
2947 del self._atexit
2953 del self._atexit
2948 if pending:
2954 if pending:
2949 raise exc_val
2955 raise exc_val
2950 return received and suppressed
2956 return received and suppressed
2951
2957
2952 # compression code
2958 # compression code
2953
2959
2954 class compressormanager(object):
2960 class compressormanager(object):
2955 """Holds registrations of various compression engines.
2961 """Holds registrations of various compression engines.
2956
2962
2957 This class essentially abstracts the differences between compression
2963 This class essentially abstracts the differences between compression
2958 engines to allow new compression formats to be added easily, possibly from
2964 engines to allow new compression formats to be added easily, possibly from
2959 extensions.
2965 extensions.
2960
2966
2961 Compressors are registered against the global instance by calling its
2967 Compressors are registered against the global instance by calling its
2962 ``register()`` method.
2968 ``register()`` method.
2963 """
2969 """
2964 def __init__(self):
2970 def __init__(self):
2965 self._engines = {}
2971 self._engines = {}
2966 # Bundle spec human name to engine name.
2972 # Bundle spec human name to engine name.
2967 self._bundlenames = {}
2973 self._bundlenames = {}
2968 # Internal bundle identifier to engine name.
2974 # Internal bundle identifier to engine name.
2969 self._bundletypes = {}
2975 self._bundletypes = {}
2970
2976
2971 def __getitem__(self, key):
2977 def __getitem__(self, key):
2972 return self._engines[key]
2978 return self._engines[key]
2973
2979
2974 def __contains__(self, key):
2980 def __contains__(self, key):
2975 return key in self._engines
2981 return key in self._engines
2976
2982
2977 def __iter__(self):
2983 def __iter__(self):
2978 return iter(self._engines.keys())
2984 return iter(self._engines.keys())
2979
2985
2980 def register(self, engine):
2986 def register(self, engine):
2981 """Register a compression engine with the manager.
2987 """Register a compression engine with the manager.
2982
2988
2983 The argument must be a ``compressionengine`` instance.
2989 The argument must be a ``compressionengine`` instance.
2984 """
2990 """
2985 if not isinstance(engine, compressionengine):
2991 if not isinstance(engine, compressionengine):
2986 raise ValueError(_('argument must be a compressionengine'))
2992 raise ValueError(_('argument must be a compressionengine'))
2987
2993
2988 name = engine.name()
2994 name = engine.name()
2989
2995
2990 if name in self._engines:
2996 if name in self._engines:
2991 raise error.Abort(_('compression engine %s already registered') %
2997 raise error.Abort(_('compression engine %s already registered') %
2992 name)
2998 name)
2993
2999
2994 bundleinfo = engine.bundletype()
3000 bundleinfo = engine.bundletype()
2995 if bundleinfo:
3001 if bundleinfo:
2996 bundlename, bundletype = bundleinfo
3002 bundlename, bundletype = bundleinfo
2997
3003
2998 if bundlename in self._bundlenames:
3004 if bundlename in self._bundlenames:
2999 raise error.Abort(_('bundle name %s already registered') %
3005 raise error.Abort(_('bundle name %s already registered') %
3000 bundlename)
3006 bundlename)
3001 if bundletype in self._bundletypes:
3007 if bundletype in self._bundletypes:
3002 raise error.Abort(_('bundle type %s already registered by %s') %
3008 raise error.Abort(_('bundle type %s already registered by %s') %
3003 (bundletype, self._bundletypes[bundletype]))
3009 (bundletype, self._bundletypes[bundletype]))
3004
3010
3005 # No external facing name declared.
3011 # No external facing name declared.
3006 if bundlename:
3012 if bundlename:
3007 self._bundlenames[bundlename] = name
3013 self._bundlenames[bundlename] = name
3008
3014
3009 self._bundletypes[bundletype] = name
3015 self._bundletypes[bundletype] = name
3010
3016
3011 self._engines[name] = engine
3017 self._engines[name] = engine
3012
3018
3013 @property
3019 @property
3014 def supportedbundlenames(self):
3020 def supportedbundlenames(self):
3015 return set(self._bundlenames.keys())
3021 return set(self._bundlenames.keys())
3016
3022
3017 @property
3023 @property
3018 def supportedbundletypes(self):
3024 def supportedbundletypes(self):
3019 return set(self._bundletypes.keys())
3025 return set(self._bundletypes.keys())
3020
3026
3021 def forbundlename(self, bundlename):
3027 def forbundlename(self, bundlename):
3022 """Obtain a compression engine registered to a bundle name.
3028 """Obtain a compression engine registered to a bundle name.
3023
3029
3024 Will raise KeyError if the bundle type isn't registered.
3030 Will raise KeyError if the bundle type isn't registered.
3025
3031
3026 Will abort if the engine is known but not available.
3032 Will abort if the engine is known but not available.
3027 """
3033 """
3028 engine = self._engines[self._bundlenames[bundlename]]
3034 engine = self._engines[self._bundlenames[bundlename]]
3029 if not engine.available():
3035 if not engine.available():
3030 raise error.Abort(_('compression engine %s could not be loaded') %
3036 raise error.Abort(_('compression engine %s could not be loaded') %
3031 engine.name())
3037 engine.name())
3032 return engine
3038 return engine
3033
3039
3034 def forbundletype(self, bundletype):
3040 def forbundletype(self, bundletype):
3035 """Obtain a compression engine registered to a bundle type.
3041 """Obtain a compression engine registered to a bundle type.
3036
3042
3037 Will raise KeyError if the bundle type isn't registered.
3043 Will raise KeyError if the bundle type isn't registered.
3038
3044
3039 Will abort if the engine is known but not available.
3045 Will abort if the engine is known but not available.
3040 """
3046 """
3041 engine = self._engines[self._bundletypes[bundletype]]
3047 engine = self._engines[self._bundletypes[bundletype]]
3042 if not engine.available():
3048 if not engine.available():
3043 raise error.Abort(_('compression engine %s could not be loaded') %
3049 raise error.Abort(_('compression engine %s could not be loaded') %
3044 engine.name())
3050 engine.name())
3045 return engine
3051 return engine
3046
3052
3047 compengines = compressormanager()
3053 compengines = compressormanager()
3048
3054
3049 class compressionengine(object):
3055 class compressionengine(object):
3050 """Base class for compression engines.
3056 """Base class for compression engines.
3051
3057
3052 Compression engines must implement the interface defined by this class.
3058 Compression engines must implement the interface defined by this class.
3053 """
3059 """
3054 def name(self):
3060 def name(self):
3055 """Returns the name of the compression engine.
3061 """Returns the name of the compression engine.
3056
3062
3057 This is the key the engine is registered under.
3063 This is the key the engine is registered under.
3058
3064
3059 This method must be implemented.
3065 This method must be implemented.
3060 """
3066 """
3061 raise NotImplementedError()
3067 raise NotImplementedError()
3062
3068
3063 def available(self):
3069 def available(self):
3064 """Whether the compression engine is available.
3070 """Whether the compression engine is available.
3065
3071
3066 The intent of this method is to allow optional compression engines
3072 The intent of this method is to allow optional compression engines
3067 that may not be available in all installations (such as engines relying
3073 that may not be available in all installations (such as engines relying
3068 on C extensions that may not be present).
3074 on C extensions that may not be present).
3069 """
3075 """
3070 return True
3076 return True
3071
3077
3072 def bundletype(self):
3078 def bundletype(self):
3073 """Describes bundle identifiers for this engine.
3079 """Describes bundle identifiers for this engine.
3074
3080
3075 If this compression engine isn't supported for bundles, returns None.
3081 If this compression engine isn't supported for bundles, returns None.
3076
3082
3077 If this engine can be used for bundles, returns a 2-tuple of strings of
3083 If this engine can be used for bundles, returns a 2-tuple of strings of
3078 the user-facing "bundle spec" compression name and an internal
3084 the user-facing "bundle spec" compression name and an internal
3079 identifier used to denote the compression format within bundles. To
3085 identifier used to denote the compression format within bundles. To
3080 exclude the name from external usage, set the first element to ``None``.
3086 exclude the name from external usage, set the first element to ``None``.
3081
3087
3082 If bundle compression is supported, the class must also implement
3088 If bundle compression is supported, the class must also implement
3083 ``compressstream`` and `decompressorreader``.
3089 ``compressstream`` and `decompressorreader``.
3084 """
3090 """
3085 return None
3091 return None
3086
3092
3087 def compressstream(self, it, opts=None):
3093 def compressstream(self, it, opts=None):
3088 """Compress an iterator of chunks.
3094 """Compress an iterator of chunks.
3089
3095
3090 The method receives an iterator (ideally a generator) of chunks of
3096 The method receives an iterator (ideally a generator) of chunks of
3091 bytes to be compressed. It returns an iterator (ideally a generator)
3097 bytes to be compressed. It returns an iterator (ideally a generator)
3092 of bytes of chunks representing the compressed output.
3098 of bytes of chunks representing the compressed output.
3093
3099
3094 Optionally accepts an argument defining how to perform compression.
3100 Optionally accepts an argument defining how to perform compression.
3095 Each engine treats this argument differently.
3101 Each engine treats this argument differently.
3096 """
3102 """
3097 raise NotImplementedError()
3103 raise NotImplementedError()
3098
3104
3099 def decompressorreader(self, fh):
3105 def decompressorreader(self, fh):
3100 """Perform decompression on a file object.
3106 """Perform decompression on a file object.
3101
3107
3102 Argument is an object with a ``read(size)`` method that returns
3108 Argument is an object with a ``read(size)`` method that returns
3103 compressed data. Return value is an object with a ``read(size)`` that
3109 compressed data. Return value is an object with a ``read(size)`` that
3104 returns uncompressed data.
3110 returns uncompressed data.
3105 """
3111 """
3106 raise NotImplementedError()
3112 raise NotImplementedError()
3107
3113
3108 class _zlibengine(compressionengine):
3114 class _zlibengine(compressionengine):
3109 def name(self):
3115 def name(self):
3110 return 'zlib'
3116 return 'zlib'
3111
3117
3112 def bundletype(self):
3118 def bundletype(self):
3113 return 'gzip', 'GZ'
3119 return 'gzip', 'GZ'
3114
3120
3115 def compressstream(self, it, opts=None):
3121 def compressstream(self, it, opts=None):
3116 opts = opts or {}
3122 opts = opts or {}
3117
3123
3118 z = zlib.compressobj(opts.get('level', -1))
3124 z = zlib.compressobj(opts.get('level', -1))
3119 for chunk in it:
3125 for chunk in it:
3120 data = z.compress(chunk)
3126 data = z.compress(chunk)
3121 # Not all calls to compress emit data. It is cheaper to inspect
3127 # Not all calls to compress emit data. It is cheaper to inspect
3122 # here than to feed empty chunks through generator.
3128 # here than to feed empty chunks through generator.
3123 if data:
3129 if data:
3124 yield data
3130 yield data
3125
3131
3126 yield z.flush()
3132 yield z.flush()
3127
3133
3128 def decompressorreader(self, fh):
3134 def decompressorreader(self, fh):
3129 def gen():
3135 def gen():
3130 d = zlib.decompressobj()
3136 d = zlib.decompressobj()
3131 for chunk in filechunkiter(fh):
3137 for chunk in filechunkiter(fh):
3132 while chunk:
3138 while chunk:
3133 # Limit output size to limit memory.
3139 # Limit output size to limit memory.
3134 yield d.decompress(chunk, 2 ** 18)
3140 yield d.decompress(chunk, 2 ** 18)
3135 chunk = d.unconsumed_tail
3141 chunk = d.unconsumed_tail
3136
3142
3137 return chunkbuffer(gen())
3143 return chunkbuffer(gen())
3138
3144
3139 compengines.register(_zlibengine())
3145 compengines.register(_zlibengine())
3140
3146
3141 class _bz2engine(compressionengine):
3147 class _bz2engine(compressionengine):
3142 def name(self):
3148 def name(self):
3143 return 'bz2'
3149 return 'bz2'
3144
3150
3145 def bundletype(self):
3151 def bundletype(self):
3146 return 'bzip2', 'BZ'
3152 return 'bzip2', 'BZ'
3147
3153
3148 def compressstream(self, it, opts=None):
3154 def compressstream(self, it, opts=None):
3149 opts = opts or {}
3155 opts = opts or {}
3150 z = bz2.BZ2Compressor(opts.get('level', 9))
3156 z = bz2.BZ2Compressor(opts.get('level', 9))
3151 for chunk in it:
3157 for chunk in it:
3152 data = z.compress(chunk)
3158 data = z.compress(chunk)
3153 if data:
3159 if data:
3154 yield data
3160 yield data
3155
3161
3156 yield z.flush()
3162 yield z.flush()
3157
3163
3158 def decompressorreader(self, fh):
3164 def decompressorreader(self, fh):
3159 def gen():
3165 def gen():
3160 d = bz2.BZ2Decompressor()
3166 d = bz2.BZ2Decompressor()
3161 for chunk in filechunkiter(fh):
3167 for chunk in filechunkiter(fh):
3162 yield d.decompress(chunk)
3168 yield d.decompress(chunk)
3163
3169
3164 return chunkbuffer(gen())
3170 return chunkbuffer(gen())
3165
3171
3166 compengines.register(_bz2engine())
3172 compengines.register(_bz2engine())
3167
3173
3168 class _truncatedbz2engine(compressionengine):
3174 class _truncatedbz2engine(compressionengine):
3169 def name(self):
3175 def name(self):
3170 return 'bz2truncated'
3176 return 'bz2truncated'
3171
3177
3172 def bundletype(self):
3178 def bundletype(self):
3173 return None, '_truncatedBZ'
3179 return None, '_truncatedBZ'
3174
3180
3175 # We don't implement compressstream because it is hackily handled elsewhere.
3181 # We don't implement compressstream because it is hackily handled elsewhere.
3176
3182
3177 def decompressorreader(self, fh):
3183 def decompressorreader(self, fh):
3178 def gen():
3184 def gen():
3179 # The input stream doesn't have the 'BZ' header. So add it back.
3185 # The input stream doesn't have the 'BZ' header. So add it back.
3180 d = bz2.BZ2Decompressor()
3186 d = bz2.BZ2Decompressor()
3181 d.decompress('BZ')
3187 d.decompress('BZ')
3182 for chunk in filechunkiter(fh):
3188 for chunk in filechunkiter(fh):
3183 yield d.decompress(chunk)
3189 yield d.decompress(chunk)
3184
3190
3185 return chunkbuffer(gen())
3191 return chunkbuffer(gen())
3186
3192
3187 compengines.register(_truncatedbz2engine())
3193 compengines.register(_truncatedbz2engine())
3188
3194
3189 class _noopengine(compressionengine):
3195 class _noopengine(compressionengine):
3190 def name(self):
3196 def name(self):
3191 return 'none'
3197 return 'none'
3192
3198
3193 def bundletype(self):
3199 def bundletype(self):
3194 return 'none', 'UN'
3200 return 'none', 'UN'
3195
3201
3196 def compressstream(self, it, opts=None):
3202 def compressstream(self, it, opts=None):
3197 return it
3203 return it
3198
3204
3199 def decompressorreader(self, fh):
3205 def decompressorreader(self, fh):
3200 return fh
3206 return fh
3201
3207
3202 compengines.register(_noopengine())
3208 compengines.register(_noopengine())
3203
3209
3204 class _zstdengine(compressionengine):
3210 class _zstdengine(compressionengine):
3205 def name(self):
3211 def name(self):
3206 return 'zstd'
3212 return 'zstd'
3207
3213
3208 @propertycache
3214 @propertycache
3209 def _module(self):
3215 def _module(self):
3210 # Not all installs have the zstd module available. So defer importing
3216 # Not all installs have the zstd module available. So defer importing
3211 # until first access.
3217 # until first access.
3212 try:
3218 try:
3213 from . import zstd
3219 from . import zstd
3214 # Force delayed import.
3220 # Force delayed import.
3215 zstd.__version__
3221 zstd.__version__
3216 return zstd
3222 return zstd
3217 except ImportError:
3223 except ImportError:
3218 return None
3224 return None
3219
3225
3220 def available(self):
3226 def available(self):
3221 return bool(self._module)
3227 return bool(self._module)
3222
3228
3223 def bundletype(self):
3229 def bundletype(self):
3224 return 'zstd', 'ZS'
3230 return 'zstd', 'ZS'
3225
3231
3226 def compressstream(self, it, opts=None):
3232 def compressstream(self, it, opts=None):
3227 opts = opts or {}
3233 opts = opts or {}
3228 # zstd level 3 is almost always significantly faster than zlib
3234 # zstd level 3 is almost always significantly faster than zlib
3229 # while providing no worse compression. It strikes a good balance
3235 # while providing no worse compression. It strikes a good balance
3230 # between speed and compression.
3236 # between speed and compression.
3231 level = opts.get('level', 3)
3237 level = opts.get('level', 3)
3232
3238
3233 zstd = self._module
3239 zstd = self._module
3234 z = zstd.ZstdCompressor(level=level).compressobj()
3240 z = zstd.ZstdCompressor(level=level).compressobj()
3235 for chunk in it:
3241 for chunk in it:
3236 data = z.compress(chunk)
3242 data = z.compress(chunk)
3237 if data:
3243 if data:
3238 yield data
3244 yield data
3239
3245
3240 yield z.flush()
3246 yield z.flush()
3241
3247
3242 def decompressorreader(self, fh):
3248 def decompressorreader(self, fh):
3243 zstd = self._module
3249 zstd = self._module
3244 dctx = zstd.ZstdDecompressor()
3250 dctx = zstd.ZstdDecompressor()
3245 return chunkbuffer(dctx.read_from(fh))
3251 return chunkbuffer(dctx.read_from(fh))
3246
3252
3247 compengines.register(_zstdengine())
3253 compengines.register(_zstdengine())
3248
3254
3249 # convenient shortcut
3255 # convenient shortcut
3250 dst = debugstacktrace
3256 dst = debugstacktrace
General Comments 0
You need to be logged in to leave comments. Login now