##// END OF EJS Templates
pathutil: hint if a path is root relative instead of cwd relative (issue4663)...
Matt Harbison -
r25011:7d6a507a default
parent child Browse files
Show More
@@ -1,177 +1,187 b''
1 1 import os, errno, stat
2 2
3 3 import encoding
4 4 import util
5 5 from i18n import _
6 6
7 7 def _lowerclean(s):
8 8 return encoding.hfsignoreclean(s.lower())
9 9
10 10 class pathauditor(object):
11 11 '''ensure that a filesystem path contains no banned components.
12 12 the following properties of a path are checked:
13 13
14 14 - ends with a directory separator
15 15 - under top-level .hg
16 16 - starts at the root of a windows drive
17 17 - contains ".."
18 18 - traverses a symlink (e.g. a/symlink_here/b)
19 19 - inside a nested repository (a callback can be used to approve
20 20 some nested repositories, e.g., subrepositories)
21 21 '''
22 22
23 23 def __init__(self, root, callback=None):
24 24 self.audited = set()
25 25 self.auditeddir = set()
26 26 self.root = root
27 27 self.callback = callback
28 28 if os.path.lexists(root) and not util.checkcase(root):
29 29 self.normcase = util.normcase
30 30 else:
31 31 self.normcase = lambda x: x
32 32
33 33 def __call__(self, path):
34 34 '''Check the relative path.
35 35 path may contain a pattern (e.g. foodir/**.txt)'''
36 36
37 37 path = util.localpath(path)
38 38 normpath = self.normcase(path)
39 39 if normpath in self.audited:
40 40 return
41 41 # AIX ignores "/" at end of path, others raise EISDIR.
42 42 if util.endswithsep(path):
43 43 raise util.Abort(_("path ends in directory separator: %s") % path)
44 44 parts = util.splitpath(path)
45 45 if (os.path.splitdrive(path)[0]
46 46 or _lowerclean(parts[0]) in ('.hg', '.hg.', '')
47 47 or os.pardir in parts):
48 48 raise util.Abort(_("path contains illegal component: %s") % path)
49 49 # Windows shortname aliases
50 50 for p in parts:
51 51 if "~" in p:
52 52 first, last = p.split("~", 1)
53 53 if last.isdigit() and first.upper() in ["HG", "HG8B6C"]:
54 54 raise util.Abort(_("path contains illegal component: %s")
55 55 % path)
56 56 if '.hg' in _lowerclean(path):
57 57 lparts = [_lowerclean(p.lower()) for p in parts]
58 58 for p in '.hg', '.hg.':
59 59 if p in lparts[1:]:
60 60 pos = lparts.index(p)
61 61 base = os.path.join(*parts[:pos])
62 62 raise util.Abort(_("path '%s' is inside nested repo %r")
63 63 % (path, base))
64 64
65 65 normparts = util.splitpath(normpath)
66 66 assert len(parts) == len(normparts)
67 67
68 68 parts.pop()
69 69 normparts.pop()
70 70 prefixes = []
71 71 while parts:
72 72 prefix = os.sep.join(parts)
73 73 normprefix = os.sep.join(normparts)
74 74 if normprefix in self.auditeddir:
75 75 break
76 76 curpath = os.path.join(self.root, prefix)
77 77 try:
78 78 st = os.lstat(curpath)
79 79 except OSError, err:
80 80 # EINVAL can be raised as invalid path syntax under win32.
81 81 # They must be ignored for patterns can be checked too.
82 82 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
83 83 raise
84 84 else:
85 85 if stat.S_ISLNK(st.st_mode):
86 86 raise util.Abort(
87 87 _('path %r traverses symbolic link %r')
88 88 % (path, prefix))
89 89 elif (stat.S_ISDIR(st.st_mode) and
90 90 os.path.isdir(os.path.join(curpath, '.hg'))):
91 91 if not self.callback or not self.callback(curpath):
92 92 raise util.Abort(_("path '%s' is inside nested "
93 93 "repo %r")
94 94 % (path, prefix))
95 95 prefixes.append(normprefix)
96 96 parts.pop()
97 97 normparts.pop()
98 98
99 99 self.audited.add(normpath)
100 100 # only add prefixes to the cache after checking everything: we don't
101 101 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
102 102 self.auditeddir.update(prefixes)
103 103
104 104 def check(self, path):
105 105 try:
106 106 self(path)
107 107 return True
108 108 except (OSError, util.Abort):
109 109 return False
110 110
111 111 def canonpath(root, cwd, myname, auditor=None):
112 112 '''return the canonical path of myname, given cwd and root'''
113 113 if util.endswithsep(root):
114 114 rootsep = root
115 115 else:
116 116 rootsep = root + os.sep
117 117 name = myname
118 118 if not os.path.isabs(name):
119 119 name = os.path.join(root, cwd, name)
120 120 name = os.path.normpath(name)
121 121 if auditor is None:
122 122 auditor = pathauditor(root)
123 123 if name != rootsep and name.startswith(rootsep):
124 124 name = name[len(rootsep):]
125 125 auditor(name)
126 126 return util.pconvert(name)
127 127 elif name == root:
128 128 return ''
129 129 else:
130 130 # Determine whether `name' is in the hierarchy at or beneath `root',
131 131 # by iterating name=dirname(name) until that causes no change (can't
132 132 # check name == '/', because that doesn't work on windows). The list
133 133 # `rel' holds the reversed list of components making up the relative
134 134 # file name we want.
135 135 rel = []
136 136 while True:
137 137 try:
138 138 s = util.samefile(name, root)
139 139 except OSError:
140 140 s = False
141 141 if s:
142 142 if not rel:
143 143 # name was actually the same as root (maybe a symlink)
144 144 return ''
145 145 rel.reverse()
146 146 name = os.path.join(*rel)
147 147 auditor(name)
148 148 return util.pconvert(name)
149 149 dirname, basename = util.split(name)
150 150 rel.append(basename)
151 151 if dirname == name:
152 152 break
153 153 name = dirname
154 154
155 raise util.Abort(_("%s not under root '%s'") % (myname, root))
155 # A common mistake is to use -R, but specify a file relative to the repo
156 # instead of cwd. Detect that case, and provide a hint to the user.
157 hint = None
158 try:
159 canonpath(root, root, myname, auditor)
160 hint = _("consider using '--cwd %s'") % os.path.relpath(root, cwd)
161 except util.Abort:
162 pass
163
164 raise util.Abort(_("%s not under root '%s'") % (myname, root),
165 hint=hint)
156 166
157 167 def normasprefix(path):
158 168 '''normalize the specified path as path prefix
159 169
160 170 Returned value can be used safely for "p.startswith(prefix)",
161 171 "p[len(prefix):]", and so on.
162 172
163 173 For efficiency, this expects "path" argument to be already
164 174 normalized by "os.path.normpath", "os.path.realpath", and so on.
165 175
166 176 See also issue3033 for detail about need of this function.
167 177
168 178 >>> normasprefix('/foo/bar').replace(os.sep, '/')
169 179 '/foo/bar/'
170 180 >>> normasprefix('/').replace(os.sep, '/')
171 181 '/'
172 182 '''
173 183 d, p = os.path.splitdrive(path)
174 184 if len(p) != len(os.sep):
175 185 return path + os.sep
176 186 else:
177 187 return path
@@ -1,445 +1,446 b''
1 1 $ hg init a
2 2 $ cd a
3 3 $ echo a > a
4 4 $ hg ci -A -d'1 0' -m a
5 5 adding a
6 6
7 7 $ cd ..
8 8
9 9 $ hg init b
10 10 $ cd b
11 11 $ echo b > b
12 12 $ hg ci -A -d'1 0' -m b
13 13 adding b
14 14
15 15 $ cd ..
16 16
17 17 $ hg clone a c
18 18 updating to branch default
19 19 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
20 20 $ cd c
21 21 $ cat >> .hg/hgrc <<EOF
22 22 > [paths]
23 23 > relative = ../a
24 24 > EOF
25 25 $ hg pull -f ../b
26 26 pulling from ../b
27 27 searching for changes
28 28 warning: repository is unrelated
29 29 requesting all changes
30 30 adding changesets
31 31 adding manifests
32 32 adding file changes
33 33 added 1 changesets with 1 changes to 1 files (+1 heads)
34 34 (run 'hg heads' to see heads, 'hg merge' to merge)
35 35 $ hg merge
36 36 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
37 37 (branch merge, don't forget to commit)
38 38
39 39 $ cd ..
40 40
41 41 Testing -R/--repository:
42 42
43 43 $ hg -R a tip
44 44 changeset: 0:8580ff50825a
45 45 tag: tip
46 46 user: test
47 47 date: Thu Jan 01 00:00:01 1970 +0000
48 48 summary: a
49 49
50 50 $ hg --repository b tip
51 51 changeset: 0:b6c483daf290
52 52 tag: tip
53 53 user: test
54 54 date: Thu Jan 01 00:00:01 1970 +0000
55 55 summary: b
56 56
57 57
58 58 -R with a URL:
59 59
60 60 $ hg -R file:a identify
61 61 8580ff50825a tip
62 62 $ hg -R file://localhost/`pwd`/a/ identify
63 63 8580ff50825a tip
64 64
65 65 -R with path aliases:
66 66
67 67 $ cd c
68 68 $ hg -R default identify
69 69 8580ff50825a tip
70 70 $ hg -R relative identify
71 71 8580ff50825a tip
72 72 $ echo '[paths]' >> $HGRCPATH
73 73 $ echo 'relativetohome = a' >> $HGRCPATH
74 74 $ HOME=`pwd`/../ hg -R relativetohome identify
75 75 8580ff50825a tip
76 76 $ cd ..
77 77
78 78 #if no-outer-repo
79 79
80 80 Implicit -R:
81 81
82 82 $ hg ann a/a
83 83 0: a
84 84 $ hg ann a/a a/a
85 85 0: a
86 86 $ hg ann a/a b/b
87 87 abort: no repository found in '$TESTTMP' (.hg not found)!
88 88 [255]
89 89 $ hg -R b ann a/a
90 90 abort: a/a not under root '$TESTTMP/b' (glob)
91 (consider using '--cwd b')
91 92 [255]
92 93 $ hg log
93 94 abort: no repository found in '$TESTTMP' (.hg not found)!
94 95 [255]
95 96
96 97 #endif
97 98
98 99 Abbreviation of long option:
99 100
100 101 $ hg --repo c tip
101 102 changeset: 1:b6c483daf290
102 103 tag: tip
103 104 parent: -1:000000000000
104 105 user: test
105 106 date: Thu Jan 01 00:00:01 1970 +0000
106 107 summary: b
107 108
108 109
109 110 earlygetopt with duplicate options (36d23de02da1):
110 111
111 112 $ hg --cwd a --cwd b --cwd c tip
112 113 changeset: 1:b6c483daf290
113 114 tag: tip
114 115 parent: -1:000000000000
115 116 user: test
116 117 date: Thu Jan 01 00:00:01 1970 +0000
117 118 summary: b
118 119
119 120 $ hg --repo c --repository b -R a tip
120 121 changeset: 0:8580ff50825a
121 122 tag: tip
122 123 user: test
123 124 date: Thu Jan 01 00:00:01 1970 +0000
124 125 summary: a
125 126
126 127
127 128 earlygetopt short option without following space:
128 129
129 130 $ hg -q -Rb tip
130 131 0:b6c483daf290
131 132
132 133 earlygetopt with illegal abbreviations:
133 134
134 135 $ hg --confi "foo.bar=baz"
135 136 abort: option --config may not be abbreviated!
136 137 [255]
137 138 $ hg --cw a tip
138 139 abort: option --cwd may not be abbreviated!
139 140 [255]
140 141 $ hg --rep a tip
141 142 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
142 143 [255]
143 144 $ hg --repositor a tip
144 145 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
145 146 [255]
146 147 $ hg -qR a tip
147 148 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
148 149 [255]
149 150 $ hg -qRa tip
150 151 abort: option -R has to be separated from other options (e.g. not -qR) and --repository may only be abbreviated as --repo!
151 152 [255]
152 153
153 154 Testing --cwd:
154 155
155 156 $ hg --cwd a parents
156 157 changeset: 0:8580ff50825a
157 158 tag: tip
158 159 user: test
159 160 date: Thu Jan 01 00:00:01 1970 +0000
160 161 summary: a
161 162
162 163
163 164 Testing -y/--noninteractive - just be sure it is parsed:
164 165
165 166 $ hg --cwd a tip -q --noninteractive
166 167 0:8580ff50825a
167 168 $ hg --cwd a tip -q -y
168 169 0:8580ff50825a
169 170
170 171 Testing -q/--quiet:
171 172
172 173 $ hg -R a -q tip
173 174 0:8580ff50825a
174 175 $ hg -R b -q tip
175 176 0:b6c483daf290
176 177 $ hg -R c --quiet parents
177 178 0:8580ff50825a
178 179 1:b6c483daf290
179 180
180 181 Testing -v/--verbose:
181 182
182 183 $ hg --cwd c head -v
183 184 changeset: 1:b6c483daf290
184 185 tag: tip
185 186 parent: -1:000000000000
186 187 user: test
187 188 date: Thu Jan 01 00:00:01 1970 +0000
188 189 files: b
189 190 description:
190 191 b
191 192
192 193
193 194 changeset: 0:8580ff50825a
194 195 user: test
195 196 date: Thu Jan 01 00:00:01 1970 +0000
196 197 files: a
197 198 description:
198 199 a
199 200
200 201
201 202 $ hg --cwd b tip --verbose
202 203 changeset: 0:b6c483daf290
203 204 tag: tip
204 205 user: test
205 206 date: Thu Jan 01 00:00:01 1970 +0000
206 207 files: b
207 208 description:
208 209 b
209 210
210 211
211 212
212 213 Testing --config:
213 214
214 215 $ hg --cwd c --config paths.quuxfoo=bar paths | grep quuxfoo > /dev/null && echo quuxfoo
215 216 quuxfoo
216 217 $ hg --cwd c --config '' tip -q
217 218 abort: malformed --config option: '' (use --config section.name=value)
218 219 [255]
219 220 $ hg --cwd c --config a.b tip -q
220 221 abort: malformed --config option: 'a.b' (use --config section.name=value)
221 222 [255]
222 223 $ hg --cwd c --config a tip -q
223 224 abort: malformed --config option: 'a' (use --config section.name=value)
224 225 [255]
225 226 $ hg --cwd c --config a.= tip -q
226 227 abort: malformed --config option: 'a.=' (use --config section.name=value)
227 228 [255]
228 229 $ hg --cwd c --config .b= tip -q
229 230 abort: malformed --config option: '.b=' (use --config section.name=value)
230 231 [255]
231 232
232 233 Testing --debug:
233 234
234 235 $ hg --cwd c log --debug
235 236 changeset: 1:b6c483daf2907ce5825c0bb50f5716226281cc1a
236 237 tag: tip
237 238 phase: public
238 239 parent: -1:0000000000000000000000000000000000000000
239 240 parent: -1:0000000000000000000000000000000000000000
240 241 manifest: 1:23226e7a252cacdc2d99e4fbdc3653441056de49
241 242 user: test
242 243 date: Thu Jan 01 00:00:01 1970 +0000
243 244 files+: b
244 245 extra: branch=default
245 246 description:
246 247 b
247 248
248 249
249 250 changeset: 0:8580ff50825a50c8f716709acdf8de0deddcd6ab
250 251 phase: public
251 252 parent: -1:0000000000000000000000000000000000000000
252 253 parent: -1:0000000000000000000000000000000000000000
253 254 manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
254 255 user: test
255 256 date: Thu Jan 01 00:00:01 1970 +0000
256 257 files+: a
257 258 extra: branch=default
258 259 description:
259 260 a
260 261
261 262
262 263
263 264 Testing --traceback:
264 265
265 266 $ hg --cwd c --config x --traceback id 2>&1 | grep -i 'traceback'
266 267 Traceback (most recent call last):
267 268
268 269 Testing --time:
269 270
270 271 $ hg --cwd a --time id
271 272 8580ff50825a tip
272 273 time: real * (glob)
273 274
274 275 Testing --version:
275 276
276 277 $ hg --version -q
277 278 Mercurial Distributed SCM * (glob)
278 279
279 280 hide outer repo
280 281 $ hg init
281 282
282 283 Testing -h/--help:
283 284
284 285 $ hg -h
285 286 Mercurial Distributed SCM
286 287
287 288 list of commands:
288 289
289 290 add add the specified files on the next commit
290 291 addremove add all new files, delete all missing files
291 292 annotate show changeset information by line for each file
292 293 archive create an unversioned archive of a repository revision
293 294 backout reverse effect of earlier changeset
294 295 bisect subdivision search of changesets
295 296 bookmarks create a new bookmark or list existing bookmarks
296 297 branch set or show the current branch name
297 298 branches list repository named branches
298 299 bundle create a changegroup file
299 300 cat output the current or given revision of files
300 301 clone make a copy of an existing repository
301 302 commit commit the specified files or all outstanding changes
302 303 config show combined config settings from all hgrc files
303 304 copy mark files as copied for the next commit
304 305 diff diff repository (or selected files)
305 306 export dump the header and diffs for one or more changesets
306 307 files list tracked files
307 308 forget forget the specified files on the next commit
308 309 graft copy changes from other branches onto the current branch
309 310 grep search for a pattern in specified files and revisions
310 311 heads show branch heads
311 312 help show help for a given topic or a help overview
312 313 identify identify the working directory or specified revision
313 314 import import an ordered set of patches
314 315 incoming show new changesets found in source
315 316 init create a new repository in the given directory
316 317 log show revision history of entire repository or files
317 318 manifest output the current or given revision of the project manifest
318 319 merge merge another revision into working directory
319 320 outgoing show changesets not found in the destination
320 321 paths show aliases for remote repositories
321 322 phase set or show the current phase name
322 323 pull pull changes from the specified source
323 324 push push changes to the specified destination
324 325 recover roll back an interrupted transaction
325 326 remove remove the specified files on the next commit
326 327 rename rename files; equivalent of copy + remove
327 328 resolve redo merges or set/view the merge status of files
328 329 revert restore files to their checkout state
329 330 root print the root (top) of the current working directory
330 331 serve start stand-alone webserver
331 332 status show changed files in the working directory
332 333 summary summarize working directory state
333 334 tag add one or more tags for the current or given revision
334 335 tags list repository tags
335 336 unbundle apply one or more changegroup files
336 337 update update working directory (or switch revisions)
337 338 verify verify the integrity of the repository
338 339 version output version and copyright information
339 340
340 341 additional help topics:
341 342
342 343 config Configuration Files
343 344 dates Date Formats
344 345 diffs Diff Formats
345 346 environment Environment Variables
346 347 extensions Using Additional Features
347 348 filesets Specifying File Sets
348 349 glossary Glossary
349 350 hgignore Syntax for Mercurial Ignore Files
350 351 hgweb Configuring hgweb
351 352 merge-tools Merge Tools
352 353 multirevs Specifying Multiple Revisions
353 354 patterns File Name Patterns
354 355 phases Working with Phases
355 356 revisions Specifying Single Revisions
356 357 revsets Specifying Revision Sets
357 358 subrepos Subrepositories
358 359 templating Template Usage
359 360 urls URL Paths
360 361
361 362 (use "hg help -v" to show built-in aliases and global options)
362 363
363 364
364 365
365 366 $ hg --help
366 367 Mercurial Distributed SCM
367 368
368 369 list of commands:
369 370
370 371 add add the specified files on the next commit
371 372 addremove add all new files, delete all missing files
372 373 annotate show changeset information by line for each file
373 374 archive create an unversioned archive of a repository revision
374 375 backout reverse effect of earlier changeset
375 376 bisect subdivision search of changesets
376 377 bookmarks create a new bookmark or list existing bookmarks
377 378 branch set or show the current branch name
378 379 branches list repository named branches
379 380 bundle create a changegroup file
380 381 cat output the current or given revision of files
381 382 clone make a copy of an existing repository
382 383 commit commit the specified files or all outstanding changes
383 384 config show combined config settings from all hgrc files
384 385 copy mark files as copied for the next commit
385 386 diff diff repository (or selected files)
386 387 export dump the header and diffs for one or more changesets
387 388 files list tracked files
388 389 forget forget the specified files on the next commit
389 390 graft copy changes from other branches onto the current branch
390 391 grep search for a pattern in specified files and revisions
391 392 heads show branch heads
392 393 help show help for a given topic or a help overview
393 394 identify identify the working directory or specified revision
394 395 import import an ordered set of patches
395 396 incoming show new changesets found in source
396 397 init create a new repository in the given directory
397 398 log show revision history of entire repository or files
398 399 manifest output the current or given revision of the project manifest
399 400 merge merge another revision into working directory
400 401 outgoing show changesets not found in the destination
401 402 paths show aliases for remote repositories
402 403 phase set or show the current phase name
403 404 pull pull changes from the specified source
404 405 push push changes to the specified destination
405 406 recover roll back an interrupted transaction
406 407 remove remove the specified files on the next commit
407 408 rename rename files; equivalent of copy + remove
408 409 resolve redo merges or set/view the merge status of files
409 410 revert restore files to their checkout state
410 411 root print the root (top) of the current working directory
411 412 serve start stand-alone webserver
412 413 status show changed files in the working directory
413 414 summary summarize working directory state
414 415 tag add one or more tags for the current or given revision
415 416 tags list repository tags
416 417 unbundle apply one or more changegroup files
417 418 update update working directory (or switch revisions)
418 419 verify verify the integrity of the repository
419 420 version output version and copyright information
420 421
421 422 additional help topics:
422 423
423 424 config Configuration Files
424 425 dates Date Formats
425 426 diffs Diff Formats
426 427 environment Environment Variables
427 428 extensions Using Additional Features
428 429 filesets Specifying File Sets
429 430 glossary Glossary
430 431 hgignore Syntax for Mercurial Ignore Files
431 432 hgweb Configuring hgweb
432 433 merge-tools Merge Tools
433 434 multirevs Specifying Multiple Revisions
434 435 patterns File Name Patterns
435 436 phases Working with Phases
436 437 revisions Specifying Single Revisions
437 438 revsets Specifying Revision Sets
438 439 subrepos Subrepositories
439 440 templating Template Usage
440 441 urls URL Paths
441 442
442 443 (use "hg help -v" to show built-in aliases and global options)
443 444
444 445 Not tested: --debugger
445 446
General Comments 0
You need to be logged in to leave comments. Login now