##// END OF EJS Templates
opener: add self._audit (issue2862)
Adrian Buehlmann -
r14720:36283a7b stable
parent child Browse files
Show More
@@ -1,705 +1,707
1 1 # scmutil.py - Mercurial core utility functions
2 2 #
3 3 # Copyright Matt Mackall <mpm@selenic.com>
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 i18n import _
9 9 import util, error, osutil, revset, similar
10 10 import match as matchmod
11 11 import os, errno, stat, sys, glob
12 12
13 13 def checkfilename(f):
14 14 '''Check that the filename f is an acceptable filename for a tracked file'''
15 15 if '\r' in f or '\n' in f:
16 16 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
17 17
18 18 def checkportable(ui, f):
19 19 '''Check if filename f is portable and warn or abort depending on config'''
20 20 checkfilename(f)
21 21 abort, warn = checkportabilityalert(ui)
22 22 if abort or warn:
23 23 msg = util.checkwinfilename(f)
24 24 if msg:
25 25 msg = "%s: %r" % (msg, f)
26 26 if abort:
27 27 raise util.Abort(msg)
28 28 ui.warn(_("warning: %s\n") % msg)
29 29
30 30 def checkportabilityalert(ui):
31 31 '''check if the user's config requests nothing, a warning, or abort for
32 32 non-portable filenames'''
33 33 val = ui.config('ui', 'portablefilenames', 'warn')
34 34 lval = val.lower()
35 35 bval = util.parsebool(val)
36 36 abort = os.name == 'nt' or lval == 'abort'
37 37 warn = bval or lval == 'warn'
38 38 if bval is None and not (warn or abort or lval == 'ignore'):
39 39 raise error.ConfigError(
40 40 _("ui.portablefilenames value is invalid ('%s')") % val)
41 41 return abort, warn
42 42
43 43 class casecollisionauditor(object):
44 44 def __init__(self, ui, abort, existingiter):
45 45 self._ui = ui
46 46 self._abort = abort
47 47 self._map = {}
48 48 for f in existingiter:
49 49 self._map[f.lower()] = f
50 50
51 51 def __call__(self, f):
52 52 fl = f.lower()
53 53 map = self._map
54 54 if fl in map and map[fl] != f:
55 55 msg = _('possible case-folding collision for %s') % f
56 56 if self._abort:
57 57 raise util.Abort(msg)
58 58 self._ui.warn(_("warning: %s\n") % msg)
59 59 map[fl] = f
60 60
61 61 class pathauditor(object):
62 62 '''ensure that a filesystem path contains no banned components.
63 63 the following properties of a path are checked:
64 64
65 65 - ends with a directory separator
66 66 - under top-level .hg
67 67 - starts at the root of a windows drive
68 68 - contains ".."
69 69 - traverses a symlink (e.g. a/symlink_here/b)
70 70 - inside a nested repository (a callback can be used to approve
71 71 some nested repositories, e.g., subrepositories)
72 72 '''
73 73
74 74 def __init__(self, root, callback=None):
75 75 self.audited = set()
76 76 self.auditeddir = set()
77 77 self.root = root
78 78 self.callback = callback
79 79
80 80 def __call__(self, path):
81 81 '''Check the relative path.
82 82 path may contain a pattern (e.g. foodir/**.txt)'''
83 83
84 84 if path in self.audited:
85 85 return
86 86 # AIX ignores "/" at end of path, others raise EISDIR.
87 87 if util.endswithsep(path):
88 88 raise util.Abort(_("path ends in directory separator: %s") % path)
89 89 normpath = os.path.normcase(path)
90 90 parts = util.splitpath(normpath)
91 91 if (os.path.splitdrive(path)[0]
92 92 or parts[0].lower() in ('.hg', '.hg.', '')
93 93 or os.pardir in parts):
94 94 raise util.Abort(_("path contains illegal component: %s") % path)
95 95 if '.hg' in path.lower():
96 96 lparts = [p.lower() for p in parts]
97 97 for p in '.hg', '.hg.':
98 98 if p in lparts[1:]:
99 99 pos = lparts.index(p)
100 100 base = os.path.join(*parts[:pos])
101 101 raise util.Abort(_('path %r is inside nested repo %r')
102 102 % (path, base))
103 103
104 104 parts.pop()
105 105 prefixes = []
106 106 while parts:
107 107 prefix = os.sep.join(parts)
108 108 if prefix in self.auditeddir:
109 109 break
110 110 curpath = os.path.join(self.root, prefix)
111 111 try:
112 112 st = os.lstat(curpath)
113 113 except OSError, err:
114 114 # EINVAL can be raised as invalid path syntax under win32.
115 115 # They must be ignored for patterns can be checked too.
116 116 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
117 117 raise
118 118 else:
119 119 if stat.S_ISLNK(st.st_mode):
120 120 raise util.Abort(
121 121 _('path %r traverses symbolic link %r')
122 122 % (path, prefix))
123 123 elif (stat.S_ISDIR(st.st_mode) and
124 124 os.path.isdir(os.path.join(curpath, '.hg'))):
125 125 if not self.callback or not self.callback(curpath):
126 126 raise util.Abort(_('path %r is inside nested repo %r') %
127 127 (path, prefix))
128 128 prefixes.append(prefix)
129 129 parts.pop()
130 130
131 131 self.audited.add(path)
132 132 # only add prefixes to the cache after checking everything: we don't
133 133 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
134 134 self.auditeddir.update(prefixes)
135 135
136 136 class abstractopener(object):
137 137 """Abstract base class; cannot be instantiated"""
138 138
139 139 def __init__(self, *args, **kwargs):
140 140 '''Prevent instantiation; don't call this from subclasses.'''
141 141 raise NotImplementedError('attempted instantiating ' + str(type(self)))
142 142
143 143 def read(self, path):
144 144 fp = self(path, 'rb')
145 145 try:
146 146 return fp.read()
147 147 finally:
148 148 fp.close()
149 149
150 150 def write(self, path, data):
151 151 fp = self(path, 'wb')
152 152 try:
153 153 return fp.write(data)
154 154 finally:
155 155 fp.close()
156 156
157 157 def append(self, path, data):
158 158 fp = self(path, 'ab')
159 159 try:
160 160 return fp.write(data)
161 161 finally:
162 162 fp.close()
163 163
164 164 class opener(abstractopener):
165 165 '''Open files relative to a base directory
166 166
167 167 This class is used to hide the details of COW semantics and
168 168 remote file access from higher level code.
169 169 '''
170 170 def __init__(self, base, audit=True):
171 171 self.base = base
172 self._audit = audit
172 173 if audit:
173 174 self.auditor = pathauditor(base)
174 175 else:
175 176 self.auditor = util.always
176 177 self.createmode = None
177 178 self._trustnlink = None
178 179
179 180 @util.propertycache
180 181 def _cansymlink(self):
181 182 return util.checklink(self.base)
182 183
183 184 def _fixfilemode(self, name):
184 185 if self.createmode is None:
185 186 return
186 187 os.chmod(name, self.createmode & 0666)
187 188
188 189 def __call__(self, path, mode="r", text=False, atomictemp=False):
189 r = util.checkosfilename(path)
190 if r:
191 raise util.Abort("%s: %r" % (r, path))
190 if self._audit:
191 r = util.checkosfilename(path)
192 if r:
193 raise util.Abort("%s: %r" % (r, path))
192 194 self.auditor(path)
193 195 f = os.path.join(self.base, path)
194 196
195 197 if not text and "b" not in mode:
196 198 mode += "b" # for that other OS
197 199
198 200 nlink = -1
199 201 dirname, basename = os.path.split(f)
200 202 # If basename is empty, then the path is malformed because it points
201 203 # to a directory. Let the posixfile() call below raise IOError.
202 204 if basename and mode not in ('r', 'rb'):
203 205 if atomictemp:
204 206 if not os.path.isdir(dirname):
205 207 util.makedirs(dirname, self.createmode)
206 208 return util.atomictempfile(f, mode, self.createmode)
207 209 try:
208 210 if 'w' in mode:
209 211 util.unlink(f)
210 212 nlink = 0
211 213 else:
212 214 # nlinks() may behave differently for files on Windows
213 215 # shares if the file is open.
214 216 fd = util.posixfile(f)
215 217 nlink = util.nlinks(f)
216 218 if nlink < 1:
217 219 nlink = 2 # force mktempcopy (issue1922)
218 220 fd.close()
219 221 except (OSError, IOError), e:
220 222 if e.errno != errno.ENOENT:
221 223 raise
222 224 nlink = 0
223 225 if not os.path.isdir(dirname):
224 226 util.makedirs(dirname, self.createmode)
225 227 if nlink > 0:
226 228 if self._trustnlink is None:
227 229 self._trustnlink = nlink > 1 or util.checknlink(f)
228 230 if nlink > 1 or not self._trustnlink:
229 231 util.rename(util.mktempcopy(f), f)
230 232 fp = util.posixfile(f, mode)
231 233 if nlink == 0:
232 234 self._fixfilemode(f)
233 235 return fp
234 236
235 237 def symlink(self, src, dst):
236 238 self.auditor(dst)
237 239 linkname = os.path.join(self.base, dst)
238 240 try:
239 241 os.unlink(linkname)
240 242 except OSError:
241 243 pass
242 244
243 245 dirname = os.path.dirname(linkname)
244 246 if not os.path.exists(dirname):
245 247 util.makedirs(dirname, self.createmode)
246 248
247 249 if self._cansymlink:
248 250 try:
249 251 os.symlink(src, linkname)
250 252 except OSError, err:
251 253 raise OSError(err.errno, _('could not symlink to %r: %s') %
252 254 (src, err.strerror), linkname)
253 255 else:
254 256 f = self(dst, "w")
255 257 f.write(src)
256 258 f.close()
257 259 self._fixfilemode(dst)
258 260
259 261 def audit(self, path):
260 262 self.auditor(path)
261 263
262 264 class filteropener(abstractopener):
263 265 '''Wrapper opener for filtering filenames with a function.'''
264 266
265 267 def __init__(self, opener, filter):
266 268 self._filter = filter
267 269 self._orig = opener
268 270
269 271 def __call__(self, path, *args, **kwargs):
270 272 return self._orig(self._filter(path), *args, **kwargs)
271 273
272 274 def canonpath(root, cwd, myname, auditor=None):
273 275 '''return the canonical path of myname, given cwd and root'''
274 276 if util.endswithsep(root):
275 277 rootsep = root
276 278 else:
277 279 rootsep = root + os.sep
278 280 name = myname
279 281 if not os.path.isabs(name):
280 282 name = os.path.join(root, cwd, name)
281 283 name = os.path.normpath(name)
282 284 if auditor is None:
283 285 auditor = pathauditor(root)
284 286 if name != rootsep and name.startswith(rootsep):
285 287 name = name[len(rootsep):]
286 288 auditor(name)
287 289 return util.pconvert(name)
288 290 elif name == root:
289 291 return ''
290 292 else:
291 293 # Determine whether `name' is in the hierarchy at or beneath `root',
292 294 # by iterating name=dirname(name) until that causes no change (can't
293 295 # check name == '/', because that doesn't work on windows). For each
294 296 # `name', compare dev/inode numbers. If they match, the list `rel'
295 297 # holds the reversed list of components making up the relative file
296 298 # name we want.
297 299 root_st = os.stat(root)
298 300 rel = []
299 301 while True:
300 302 try:
301 303 name_st = os.stat(name)
302 304 except OSError:
303 305 break
304 306 if util.samestat(name_st, root_st):
305 307 if not rel:
306 308 # name was actually the same as root (maybe a symlink)
307 309 return ''
308 310 rel.reverse()
309 311 name = os.path.join(*rel)
310 312 auditor(name)
311 313 return util.pconvert(name)
312 314 dirname, basename = os.path.split(name)
313 315 rel.append(basename)
314 316 if dirname == name:
315 317 break
316 318 name = dirname
317 319
318 320 raise util.Abort('%s not under root' % myname)
319 321
320 322 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
321 323 '''yield every hg repository under path, recursively.'''
322 324 def errhandler(err):
323 325 if err.filename == path:
324 326 raise err
325 327 if followsym and hasattr(os.path, 'samestat'):
326 328 def adddir(dirlst, dirname):
327 329 match = False
328 330 samestat = os.path.samestat
329 331 dirstat = os.stat(dirname)
330 332 for lstdirstat in dirlst:
331 333 if samestat(dirstat, lstdirstat):
332 334 match = True
333 335 break
334 336 if not match:
335 337 dirlst.append(dirstat)
336 338 return not match
337 339 else:
338 340 followsym = False
339 341
340 342 if (seen_dirs is None) and followsym:
341 343 seen_dirs = []
342 344 adddir(seen_dirs, path)
343 345 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
344 346 dirs.sort()
345 347 if '.hg' in dirs:
346 348 yield root # found a repository
347 349 qroot = os.path.join(root, '.hg', 'patches')
348 350 if os.path.isdir(os.path.join(qroot, '.hg')):
349 351 yield qroot # we have a patch queue repo here
350 352 if recurse:
351 353 # avoid recursing inside the .hg directory
352 354 dirs.remove('.hg')
353 355 else:
354 356 dirs[:] = [] # don't descend further
355 357 elif followsym:
356 358 newdirs = []
357 359 for d in dirs:
358 360 fname = os.path.join(root, d)
359 361 if adddir(seen_dirs, fname):
360 362 if os.path.islink(fname):
361 363 for hgname in walkrepos(fname, True, seen_dirs):
362 364 yield hgname
363 365 else:
364 366 newdirs.append(d)
365 367 dirs[:] = newdirs
366 368
367 369 def osrcpath():
368 370 '''return default os-specific hgrc search path'''
369 371 path = systemrcpath()
370 372 path.extend(userrcpath())
371 373 path = [os.path.normpath(f) for f in path]
372 374 return path
373 375
374 376 _rcpath = None
375 377
376 378 def rcpath():
377 379 '''return hgrc search path. if env var HGRCPATH is set, use it.
378 380 for each item in path, if directory, use files ending in .rc,
379 381 else use item.
380 382 make HGRCPATH empty to only look in .hg/hgrc of current repo.
381 383 if no HGRCPATH, use default os-specific path.'''
382 384 global _rcpath
383 385 if _rcpath is None:
384 386 if 'HGRCPATH' in os.environ:
385 387 _rcpath = []
386 388 for p in os.environ['HGRCPATH'].split(os.pathsep):
387 389 if not p:
388 390 continue
389 391 p = util.expandpath(p)
390 392 if os.path.isdir(p):
391 393 for f, kind in osutil.listdir(p):
392 394 if f.endswith('.rc'):
393 395 _rcpath.append(os.path.join(p, f))
394 396 else:
395 397 _rcpath.append(p)
396 398 else:
397 399 _rcpath = osrcpath()
398 400 return _rcpath
399 401
400 402 if os.name != 'nt':
401 403
402 404 def rcfiles(path):
403 405 rcs = [os.path.join(path, 'hgrc')]
404 406 rcdir = os.path.join(path, 'hgrc.d')
405 407 try:
406 408 rcs.extend([os.path.join(rcdir, f)
407 409 for f, kind in osutil.listdir(rcdir)
408 410 if f.endswith(".rc")])
409 411 except OSError:
410 412 pass
411 413 return rcs
412 414
413 415 def systemrcpath():
414 416 path = []
415 417 # old mod_python does not set sys.argv
416 418 if len(getattr(sys, 'argv', [])) > 0:
417 419 p = os.path.dirname(os.path.dirname(sys.argv[0]))
418 420 path.extend(rcfiles(os.path.join(p, 'etc/mercurial')))
419 421 path.extend(rcfiles('/etc/mercurial'))
420 422 return path
421 423
422 424 def userrcpath():
423 425 return [os.path.expanduser('~/.hgrc')]
424 426
425 427 else:
426 428
427 429 _HKEY_LOCAL_MACHINE = 0x80000002L
428 430
429 431 def systemrcpath():
430 432 '''return default os-specific hgrc search path'''
431 433 rcpath = []
432 434 filename = util.executablepath()
433 435 # Use mercurial.ini found in directory with hg.exe
434 436 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
435 437 if os.path.isfile(progrc):
436 438 rcpath.append(progrc)
437 439 return rcpath
438 440 # Use hgrc.d found in directory with hg.exe
439 441 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
440 442 if os.path.isdir(progrcd):
441 443 for f, kind in osutil.listdir(progrcd):
442 444 if f.endswith('.rc'):
443 445 rcpath.append(os.path.join(progrcd, f))
444 446 return rcpath
445 447 # else look for a system rcpath in the registry
446 448 value = util.lookupreg('SOFTWARE\\Mercurial', None,
447 449 _HKEY_LOCAL_MACHINE)
448 450 if not isinstance(value, str) or not value:
449 451 return rcpath
450 452 value = value.replace('/', os.sep)
451 453 for p in value.split(os.pathsep):
452 454 if p.lower().endswith('mercurial.ini'):
453 455 rcpath.append(p)
454 456 elif os.path.isdir(p):
455 457 for f, kind in osutil.listdir(p):
456 458 if f.endswith('.rc'):
457 459 rcpath.append(os.path.join(p, f))
458 460 return rcpath
459 461
460 462 def userrcpath():
461 463 '''return os-specific hgrc search path to the user dir'''
462 464 home = os.path.expanduser('~')
463 465 path = [os.path.join(home, 'mercurial.ini'),
464 466 os.path.join(home, '.hgrc')]
465 467 userprofile = os.environ.get('USERPROFILE')
466 468 if userprofile:
467 469 path.append(os.path.join(userprofile, 'mercurial.ini'))
468 470 path.append(os.path.join(userprofile, '.hgrc'))
469 471 return path
470 472
471 473 def revsingle(repo, revspec, default='.'):
472 474 if not revspec:
473 475 return repo[default]
474 476
475 477 l = revrange(repo, [revspec])
476 478 if len(l) < 1:
477 479 raise util.Abort(_('empty revision set'))
478 480 return repo[l[-1]]
479 481
480 482 def revpair(repo, revs):
481 483 if not revs:
482 484 return repo.dirstate.p1(), None
483 485
484 486 l = revrange(repo, revs)
485 487
486 488 if len(l) == 0:
487 489 return repo.dirstate.p1(), None
488 490
489 491 if len(l) == 1:
490 492 return repo.lookup(l[0]), None
491 493
492 494 return repo.lookup(l[0]), repo.lookup(l[-1])
493 495
494 496 _revrangesep = ':'
495 497
496 498 def revrange(repo, revs):
497 499 """Yield revision as strings from a list of revision specifications."""
498 500
499 501 def revfix(repo, val, defval):
500 502 if not val and val != 0 and defval is not None:
501 503 return defval
502 504 return repo.changelog.rev(repo.lookup(val))
503 505
504 506 seen, l = set(), []
505 507 for spec in revs:
506 508 # attempt to parse old-style ranges first to deal with
507 509 # things like old-tag which contain query metacharacters
508 510 try:
509 511 if isinstance(spec, int):
510 512 seen.add(spec)
511 513 l.append(spec)
512 514 continue
513 515
514 516 if _revrangesep in spec:
515 517 start, end = spec.split(_revrangesep, 1)
516 518 start = revfix(repo, start, 0)
517 519 end = revfix(repo, end, len(repo) - 1)
518 520 step = start > end and -1 or 1
519 521 for rev in xrange(start, end + step, step):
520 522 if rev in seen:
521 523 continue
522 524 seen.add(rev)
523 525 l.append(rev)
524 526 continue
525 527 elif spec and spec in repo: # single unquoted rev
526 528 rev = revfix(repo, spec, None)
527 529 if rev in seen:
528 530 continue
529 531 seen.add(rev)
530 532 l.append(rev)
531 533 continue
532 534 except error.RepoLookupError:
533 535 pass
534 536
535 537 # fall through to new-style queries if old-style fails
536 538 m = revset.match(repo.ui, spec)
537 539 for r in m(repo, range(len(repo))):
538 540 if r not in seen:
539 541 l.append(r)
540 542 seen.update(l)
541 543
542 544 return l
543 545
544 546 def expandpats(pats):
545 547 if not util.expandglobs:
546 548 return list(pats)
547 549 ret = []
548 550 for p in pats:
549 551 kind, name = matchmod._patsplit(p, None)
550 552 if kind is None:
551 553 try:
552 554 globbed = glob.glob(name)
553 555 except re.error:
554 556 globbed = [name]
555 557 if globbed:
556 558 ret.extend(globbed)
557 559 continue
558 560 ret.append(p)
559 561 return ret
560 562
561 563 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
562 564 if pats == ("",):
563 565 pats = []
564 566 if not globbed and default == 'relpath':
565 567 pats = expandpats(pats or [])
566 568
567 569 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
568 570 default)
569 571 def badfn(f, msg):
570 572 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
571 573 m.bad = badfn
572 574 return m
573 575
574 576 def matchall(repo):
575 577 return matchmod.always(repo.root, repo.getcwd())
576 578
577 579 def matchfiles(repo, files):
578 580 return matchmod.exact(repo.root, repo.getcwd(), files)
579 581
580 582 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
581 583 if dry_run is None:
582 584 dry_run = opts.get('dry_run')
583 585 if similarity is None:
584 586 similarity = float(opts.get('similarity') or 0)
585 587 # we'd use status here, except handling of symlinks and ignore is tricky
586 588 added, unknown, deleted, removed = [], [], [], []
587 589 audit_path = pathauditor(repo.root)
588 590 m = match(repo[None], pats, opts)
589 591 for abs in repo.walk(m):
590 592 target = repo.wjoin(abs)
591 593 good = True
592 594 try:
593 595 audit_path(abs)
594 596 except (OSError, util.Abort):
595 597 good = False
596 598 rel = m.rel(abs)
597 599 exact = m.exact(abs)
598 600 if good and abs not in repo.dirstate:
599 601 unknown.append(abs)
600 602 if repo.ui.verbose or not exact:
601 603 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
602 604 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
603 605 or (os.path.isdir(target) and not os.path.islink(target))):
604 606 deleted.append(abs)
605 607 if repo.ui.verbose or not exact:
606 608 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
607 609 # for finding renames
608 610 elif repo.dirstate[abs] == 'r':
609 611 removed.append(abs)
610 612 elif repo.dirstate[abs] == 'a':
611 613 added.append(abs)
612 614 copies = {}
613 615 if similarity > 0:
614 616 for old, new, score in similar.findrenames(repo,
615 617 added + unknown, removed + deleted, similarity):
616 618 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
617 619 repo.ui.status(_('recording removal of %s as rename to %s '
618 620 '(%d%% similar)\n') %
619 621 (m.rel(old), m.rel(new), score * 100))
620 622 copies[new] = old
621 623
622 624 if not dry_run:
623 625 wctx = repo[None]
624 626 wlock = repo.wlock()
625 627 try:
626 628 wctx.forget(deleted)
627 629 wctx.add(unknown)
628 630 for new, old in copies.iteritems():
629 631 wctx.copy(old, new)
630 632 finally:
631 633 wlock.release()
632 634
633 635 def updatedir(ui, repo, patches, similarity=0):
634 636 '''Update dirstate after patch application according to metadata'''
635 637 if not patches:
636 638 return []
637 639 copies = []
638 640 removes = set()
639 641 cfiles = patches.keys()
640 642 cwd = repo.getcwd()
641 643 if cwd:
642 644 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
643 645 for f in patches:
644 646 gp = patches[f]
645 647 if not gp:
646 648 continue
647 649 if gp.op == 'RENAME':
648 650 copies.append((gp.oldpath, gp.path))
649 651 removes.add(gp.oldpath)
650 652 elif gp.op == 'COPY':
651 653 copies.append((gp.oldpath, gp.path))
652 654 elif gp.op == 'DELETE':
653 655 removes.add(gp.path)
654 656
655 657 wctx = repo[None]
656 658 for src, dst in copies:
657 659 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
658 660 if (not similarity) and removes:
659 661 wctx.remove(sorted(removes), True)
660 662
661 663 for f in patches:
662 664 gp = patches[f]
663 665 if gp and gp.mode:
664 666 islink, isexec = gp.mode
665 667 dst = repo.wjoin(gp.path)
666 668 # patch won't create empty files
667 669 if gp.op == 'ADD' and not os.path.lexists(dst):
668 670 flags = (isexec and 'x' or '') + (islink and 'l' or '')
669 671 repo.wwrite(gp.path, '', flags)
670 672 util.setflags(dst, islink, isexec)
671 673 addremove(repo, cfiles, similarity=similarity)
672 674 files = patches.keys()
673 675 files.extend([r for r in removes if r not in files])
674 676 return sorted(files)
675 677
676 678 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
677 679 """Update the dirstate to reflect the intent of copying src to dst. For
678 680 different reasons it might not end with dst being marked as copied from src.
679 681 """
680 682 origsrc = repo.dirstate.copied(src) or src
681 683 if dst == origsrc: # copying back a copy?
682 684 if repo.dirstate[dst] not in 'mn' and not dryrun:
683 685 repo.dirstate.normallookup(dst)
684 686 else:
685 687 if repo.dirstate[origsrc] == 'a' and origsrc == src:
686 688 if not ui.quiet:
687 689 ui.warn(_("%s has not been committed yet, so no copy "
688 690 "data will be stored for %s.\n")
689 691 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
690 692 if repo.dirstate[dst] in '?r' and not dryrun:
691 693 wctx.add([dst])
692 694 elif not dryrun:
693 695 wctx.copy(origsrc, dst)
694 696
695 697 def readrequires(opener, supported):
696 698 '''Reads and parses .hg/requires and checks if all entries found
697 699 are in the list of supported features.'''
698 700 requirements = set(opener.read("requires").splitlines())
699 701 for r in requirements:
700 702 if r not in supported:
701 703 if not r or not r[0].isalnum():
702 704 raise error.RequirementError(_(".hg/requires file is corrupt"))
703 705 raise error.RequirementError(_("unknown repository format: "
704 706 "requires feature '%s' (upgrade Mercurial)") % r)
705 707 return requirements
General Comments 0
You need to be logged in to leave comments. Login now