##// END OF EJS Templates
narrow: keep narrowspec backup in store...
Martin von Zweigbergk -
r41070:1e8d9f47 default
parent child Browse files
Show More
@@ -1,228 +1,228 b''
1 1 # narrowspec.py - methods for working with a narrow view of a repository
2 2 #
3 3 # Copyright 2017 Google, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11
12 12 from .i18n import _
13 13 from . import (
14 14 error,
15 15 match as matchmod,
16 16 repository,
17 17 sparse,
18 18 util,
19 19 )
20 20
21 21 FILENAME = 'narrowspec'
22 22
23 23 # Pattern prefixes that are allowed in narrow patterns. This list MUST
24 24 # only contain patterns that are fast and safe to evaluate. Keep in mind
25 25 # that patterns are supplied by clients and executed on remote servers
26 26 # as part of wire protocol commands. That means that changes to this
27 27 # data structure influence the wire protocol and should not be taken
28 28 # lightly - especially removals.
29 29 VALID_PREFIXES = (
30 30 b'path:',
31 31 b'rootfilesin:',
32 32 )
33 33
34 34 def normalizesplitpattern(kind, pat):
35 35 """Returns the normalized version of a pattern and kind.
36 36
37 37 Returns a tuple with the normalized kind and normalized pattern.
38 38 """
39 39 pat = pat.rstrip('/')
40 40 _validatepattern(pat)
41 41 return kind, pat
42 42
43 43 def _numlines(s):
44 44 """Returns the number of lines in s, including ending empty lines."""
45 45 # We use splitlines because it is Unicode-friendly and thus Python 3
46 46 # compatible. However, it does not count empty lines at the end, so trick
47 47 # it by adding a character at the end.
48 48 return len((s + 'x').splitlines())
49 49
50 50 def _validatepattern(pat):
51 51 """Validates the pattern and aborts if it is invalid.
52 52
53 53 Patterns are stored in the narrowspec as newline-separated
54 54 POSIX-style bytestring paths. There's no escaping.
55 55 """
56 56
57 57 # We use newlines as separators in the narrowspec file, so don't allow them
58 58 # in patterns.
59 59 if _numlines(pat) > 1:
60 60 raise error.Abort(_('newlines are not allowed in narrowspec paths'))
61 61
62 62 components = pat.split('/')
63 63 if '.' in components or '..' in components:
64 64 raise error.Abort(_('"." and ".." are not allowed in narrowspec paths'))
65 65
66 66 def normalizepattern(pattern, defaultkind='path'):
67 67 """Returns the normalized version of a text-format pattern.
68 68
69 69 If the pattern has no kind, the default will be added.
70 70 """
71 71 kind, pat = matchmod._patsplit(pattern, defaultkind)
72 72 return '%s:%s' % normalizesplitpattern(kind, pat)
73 73
74 74 def parsepatterns(pats):
75 75 """Parses an iterable of patterns into a typed pattern set.
76 76
77 77 Patterns are assumed to be ``path:`` if no prefix is present.
78 78 For safety and performance reasons, only some prefixes are allowed.
79 79 See ``validatepatterns()``.
80 80
81 81 This function should be used on patterns that come from the user to
82 82 normalize and validate them to the internal data structure used for
83 83 representing patterns.
84 84 """
85 85 res = {normalizepattern(orig) for orig in pats}
86 86 validatepatterns(res)
87 87 return res
88 88
89 89 def validatepatterns(pats):
90 90 """Validate that patterns are in the expected data structure and format.
91 91
92 92 And that is a set of normalized patterns beginning with ``path:`` or
93 93 ``rootfilesin:``.
94 94
95 95 This function should be used to validate internal data structures
96 96 and patterns that are loaded from sources that use the internal,
97 97 prefixed pattern representation (but can't necessarily be fully trusted).
98 98 """
99 99 if not isinstance(pats, set):
100 100 raise error.ProgrammingError('narrow patterns should be a set; '
101 101 'got %r' % pats)
102 102
103 103 for pat in pats:
104 104 if not pat.startswith(VALID_PREFIXES):
105 105 # Use a Mercurial exception because this can happen due to user
106 106 # bugs (e.g. manually updating spec file).
107 107 raise error.Abort(_('invalid prefix on narrow pattern: %s') % pat,
108 108 hint=_('narrow patterns must begin with one of '
109 109 'the following: %s') %
110 110 ', '.join(VALID_PREFIXES))
111 111
112 112 def format(includes, excludes):
113 113 output = '[include]\n'
114 114 for i in sorted(includes - excludes):
115 115 output += i + '\n'
116 116 output += '[exclude]\n'
117 117 for e in sorted(excludes):
118 118 output += e + '\n'
119 119 return output
120 120
121 121 def match(root, include=None, exclude=None):
122 122 if not include:
123 123 # Passing empty include and empty exclude to matchmod.match()
124 124 # gives a matcher that matches everything, so explicitly use
125 125 # the nevermatcher.
126 126 return matchmod.never(root, '')
127 127 return matchmod.match(root, '', [], include=include or [],
128 128 exclude=exclude or [])
129 129
130 130 def parseconfig(ui, spec):
131 131 # maybe we should care about the profiles returned too
132 132 includepats, excludepats, profiles = sparse.parseconfig(ui, spec, 'narrow')
133 133 if profiles:
134 134 raise error.Abort(_("including other spec files using '%include' is not"
135 135 " supported in narrowspec"))
136 136
137 137 validatepatterns(includepats)
138 138 validatepatterns(excludepats)
139 139
140 140 return includepats, excludepats
141 141
142 142 def load(repo):
143 143 try:
144 144 spec = repo.svfs.read(FILENAME)
145 145 except IOError as e:
146 146 # Treat "narrowspec does not exist" the same as "narrowspec file exists
147 147 # and is empty".
148 148 if e.errno == errno.ENOENT:
149 149 return set(), set()
150 150 raise
151 151
152 152 return parseconfig(repo.ui, spec)
153 153
154 154 def save(repo, includepats, excludepats):
155 155 validatepatterns(includepats)
156 156 validatepatterns(excludepats)
157 157 spec = format(includepats, excludepats)
158 158 repo.svfs.write(FILENAME, spec)
159 159
160 160 def savebackup(repo, backupname):
161 161 if repository.NARROW_REQUIREMENT not in repo.requirements:
162 162 return
163 vfs = repo.vfs
164 vfs.tryunlink(backupname)
165 util.copyfile(repo.svfs.join(FILENAME), vfs.join(backupname), hardlink=True)
163 svfs = repo.svfs
164 svfs.tryunlink(backupname)
165 util.copyfile(svfs.join(FILENAME), svfs.join(backupname), hardlink=True)
166 166
167 167 def restorebackup(repo, backupname):
168 168 if repository.NARROW_REQUIREMENT not in repo.requirements:
169 169 return
170 util.rename(repo.vfs.join(backupname), repo.svfs.join(FILENAME))
170 util.rename(repo.svfs.join(backupname), repo.svfs.join(FILENAME))
171 171
172 172 def clearbackup(repo, backupname):
173 173 if repository.NARROW_REQUIREMENT not in repo.requirements:
174 174 return
175 repo.vfs.unlink(backupname)
175 repo.svfs.unlink(backupname)
176 176
177 177 def restrictpatterns(req_includes, req_excludes, repo_includes, repo_excludes):
178 178 r""" Restricts the patterns according to repo settings,
179 179 results in a logical AND operation
180 180
181 181 :param req_includes: requested includes
182 182 :param req_excludes: requested excludes
183 183 :param repo_includes: repo includes
184 184 :param repo_excludes: repo excludes
185 185 :return: include patterns, exclude patterns, and invalid include patterns.
186 186
187 187 >>> restrictpatterns({'f1','f2'}, {}, ['f1'], [])
188 188 (set(['f1']), {}, [])
189 189 >>> restrictpatterns({'f1'}, {}, ['f1','f2'], [])
190 190 (set(['f1']), {}, [])
191 191 >>> restrictpatterns({'f1/fc1', 'f3/fc3'}, {}, ['f1','f2'], [])
192 192 (set(['f1/fc1']), {}, [])
193 193 >>> restrictpatterns({'f1_fc1'}, {}, ['f1','f2'], [])
194 194 ([], set(['path:.']), [])
195 195 >>> restrictpatterns({'f1/../f2/fc2'}, {}, ['f1','f2'], [])
196 196 (set(['f2/fc2']), {}, [])
197 197 >>> restrictpatterns({'f1/../f3/fc3'}, {}, ['f1','f2'], [])
198 198 ([], set(['path:.']), [])
199 199 >>> restrictpatterns({'f1/$non_exitent_var'}, {}, ['f1','f2'], [])
200 200 (set(['f1/$non_exitent_var']), {}, [])
201 201 """
202 202 res_excludes = set(req_excludes)
203 203 res_excludes.update(repo_excludes)
204 204 invalid_includes = []
205 205 if not req_includes:
206 206 res_includes = set(repo_includes)
207 207 elif 'path:.' not in repo_includes:
208 208 res_includes = []
209 209 for req_include in req_includes:
210 210 req_include = util.expandpath(util.normpath(req_include))
211 211 if req_include in repo_includes:
212 212 res_includes.append(req_include)
213 213 continue
214 214 valid = False
215 215 for repo_include in repo_includes:
216 216 if req_include.startswith(repo_include + '/'):
217 217 valid = True
218 218 res_includes.append(req_include)
219 219 break
220 220 if not valid:
221 221 invalid_includes.append(req_include)
222 222 if len(res_includes) == 0:
223 223 res_excludes = {'path:.'}
224 224 else:
225 225 res_includes = set(res_includes)
226 226 else:
227 227 res_includes = set(req_includes)
228 228 return res_includes, res_excludes, invalid_includes
@@ -1,94 +1,95 b''
1 1 #testcases tree flat-fncache flat-nofncache
2 2
3 3 Tests narrow stream clones
4 4
5 5 $ . "$TESTDIR/narrow-library.sh"
6 6
7 7 #if tree
8 8 $ cat << EOF >> $HGRCPATH
9 9 > [experimental]
10 10 > treemanifest = 1
11 11 > EOF
12 12 #endif
13 13
14 14 #if flat-nofncache
15 15 $ cat << EOF >> $HGRCPATH
16 16 > [format]
17 17 > usefncache = 0
18 18 > EOF
19 19 #endif
20 20
21 21 Server setup
22 22
23 23 $ hg init master
24 24 $ cd master
25 25 $ mkdir dir
26 26 $ mkdir dir/src
27 27 $ cd dir/src
28 28 $ for x in `$TESTDIR/seq.py 20`; do echo $x > "F$x"; hg add "F$x"; hg commit -m "Commit src $x"; done
29 29
30 30 $ cd ..
31 31 $ mkdir tests
32 32 $ cd tests
33 33 $ for x in `$TESTDIR/seq.py 20`; do echo $x > "F$x"; hg add "F$x"; hg commit -m "Commit src $x"; done
34 34 $ cd ../../..
35 35
36 36 Trying to stream clone when the server does not support it
37 37
38 38 $ hg clone --narrow ssh://user@dummy/master narrow --noupdate --include "dir/src/F10" --stream
39 39 streaming all changes
40 40 remote: abort: server does not support narrow stream clones
41 41 abort: pull failed on remote
42 42 [255]
43 43
44 44 Enable stream clone on the server
45 45
46 46 $ echo "[experimental]" >> master/.hg/hgrc
47 47 $ echo "server.stream-narrow-clones=True" >> master/.hg/hgrc
48 48
49 49 Cloning a specific file when stream clone is supported
50 50
51 51 $ hg clone --narrow ssh://user@dummy/master narrow --noupdate --include "dir/src/F10" --stream
52 52 streaming all changes
53 53 * files to transfer, * KB of data (glob)
54 54 transferred * KB in * seconds (* */sec) (glob)
55 55
56 56 $ cd narrow
57 57 $ ls
58 58 $ hg tracked
59 59 I path:dir/src/F10
60 60
61 61 Making sure we have the correct set of requirements
62 62
63 63 $ cat .hg/requires
64 64 dotencode (tree flat-fncache !)
65 65 fncache (tree flat-fncache !)
66 66 generaldelta
67 67 narrowhg-experimental
68 68 revlogv1
69 69 sparserevlog
70 70 store
71 71 treemanifest (tree !)
72 72
73 73 Making sure store has the required files
74 74
75 75 $ ls .hg/store/
76 76 00changelog.i
77 77 00manifest.i
78 78 data (tree flat-fncache !)
79 79 fncache (tree flat-fncache !)
80 journal.narrowspec
80 81 meta (tree !)
81 82 narrowspec
82 83 undo
83 84 undo.backupfiles
84 85 undo.phaseroots
85 86
86 87 Checking that repository has all the required data and not broken
87 88
88 89 $ hg verify
89 90 checking changesets
90 91 checking manifests
91 92 checking directory manifests (tree !)
92 93 crosschecking files in changesets and manifests
93 94 checking files
94 95 checked 40 changesets with 1 changes to 1 files
General Comments 0
You need to be logged in to leave comments. Login now