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