##// END OF EJS Templates
scmutil: match no longer accepts repo objects
Matt Mackall -
r14672:785bbc86 default
parent child Browse files
Show More
@@ -1,711 +1,705
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 172 if audit:
173 173 self.auditor = pathauditor(base)
174 174 else:
175 175 self.auditor = util.always
176 176 self.createmode = None
177 177 self._trustnlink = None
178 178
179 179 @util.propertycache
180 180 def _cansymlink(self):
181 181 return util.checklink(self.base)
182 182
183 183 def _fixfilemode(self, name):
184 184 if self.createmode is None:
185 185 return
186 186 os.chmod(name, self.createmode & 0666)
187 187
188 188 def __call__(self, path, mode="r", text=False, atomictemp=False):
189 189 r = util.checkosfilename(path)
190 190 if r:
191 191 raise util.Abort("%s: %r" % (r, path))
192 192 self.auditor(path)
193 193 f = os.path.join(self.base, path)
194 194
195 195 if not text and "b" not in mode:
196 196 mode += "b" # for that other OS
197 197
198 198 nlink = -1
199 199 dirname, basename = os.path.split(f)
200 200 # If basename is empty, then the path is malformed because it points
201 201 # to a directory. Let the posixfile() call below raise IOError.
202 202 if basename and mode not in ('r', 'rb'):
203 203 if atomictemp:
204 204 if not os.path.isdir(dirname):
205 205 util.makedirs(dirname, self.createmode)
206 206 return util.atomictempfile(f, mode, self.createmode)
207 207 try:
208 208 if 'w' in mode:
209 209 util.unlink(f)
210 210 nlink = 0
211 211 else:
212 212 # nlinks() may behave differently for files on Windows
213 213 # shares if the file is open.
214 214 fd = util.posixfile(f)
215 215 nlink = util.nlinks(f)
216 216 if nlink < 1:
217 217 nlink = 2 # force mktempcopy (issue1922)
218 218 fd.close()
219 219 except (OSError, IOError), e:
220 220 if e.errno != errno.ENOENT:
221 221 raise
222 222 nlink = 0
223 223 if not os.path.isdir(dirname):
224 224 util.makedirs(dirname, self.createmode)
225 225 if nlink > 0:
226 226 if self._trustnlink is None:
227 227 self._trustnlink = nlink > 1 or util.checknlink(f)
228 228 if nlink > 1 or not self._trustnlink:
229 229 util.rename(util.mktempcopy(f), f)
230 230 fp = util.posixfile(f, mode)
231 231 if nlink == 0:
232 232 self._fixfilemode(f)
233 233 return fp
234 234
235 235 def symlink(self, src, dst):
236 236 self.auditor(dst)
237 237 linkname = os.path.join(self.base, dst)
238 238 try:
239 239 os.unlink(linkname)
240 240 except OSError:
241 241 pass
242 242
243 243 dirname = os.path.dirname(linkname)
244 244 if not os.path.exists(dirname):
245 245 util.makedirs(dirname, self.createmode)
246 246
247 247 if self._cansymlink:
248 248 try:
249 249 os.symlink(src, linkname)
250 250 except OSError, err:
251 251 raise OSError(err.errno, _('could not symlink to %r: %s') %
252 252 (src, err.strerror), linkname)
253 253 else:
254 254 f = self(dst, "w")
255 255 f.write(src)
256 256 f.close()
257 257 self._fixfilemode(dst)
258 258
259 259 def audit(self, path):
260 260 self.auditor(path)
261 261
262 262 class filteropener(abstractopener):
263 263 '''Wrapper opener for filtering filenames with a function.'''
264 264
265 265 def __init__(self, opener, filter):
266 266 self._filter = filter
267 267 self._orig = opener
268 268
269 269 def __call__(self, path, *args, **kwargs):
270 270 return self._orig(self._filter(path), *args, **kwargs)
271 271
272 272 def canonpath(root, cwd, myname, auditor=None):
273 273 '''return the canonical path of myname, given cwd and root'''
274 274 if util.endswithsep(root):
275 275 rootsep = root
276 276 else:
277 277 rootsep = root + os.sep
278 278 name = myname
279 279 if not os.path.isabs(name):
280 280 name = os.path.join(root, cwd, name)
281 281 name = os.path.normpath(name)
282 282 if auditor is None:
283 283 auditor = pathauditor(root)
284 284 if name != rootsep and name.startswith(rootsep):
285 285 name = name[len(rootsep):]
286 286 auditor(name)
287 287 return util.pconvert(name)
288 288 elif name == root:
289 289 return ''
290 290 else:
291 291 # Determine whether `name' is in the hierarchy at or beneath `root',
292 292 # by iterating name=dirname(name) until that causes no change (can't
293 293 # check name == '/', because that doesn't work on windows). For each
294 294 # `name', compare dev/inode numbers. If they match, the list `rel'
295 295 # holds the reversed list of components making up the relative file
296 296 # name we want.
297 297 root_st = os.stat(root)
298 298 rel = []
299 299 while True:
300 300 try:
301 301 name_st = os.stat(name)
302 302 except OSError:
303 303 break
304 304 if util.samestat(name_st, root_st):
305 305 if not rel:
306 306 # name was actually the same as root (maybe a symlink)
307 307 return ''
308 308 rel.reverse()
309 309 name = os.path.join(*rel)
310 310 auditor(name)
311 311 return util.pconvert(name)
312 312 dirname, basename = os.path.split(name)
313 313 rel.append(basename)
314 314 if dirname == name:
315 315 break
316 316 name = dirname
317 317
318 318 raise util.Abort('%s not under root' % myname)
319 319
320 320 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
321 321 '''yield every hg repository under path, recursively.'''
322 322 def errhandler(err):
323 323 if err.filename == path:
324 324 raise err
325 325 if followsym and hasattr(os.path, 'samestat'):
326 326 def adddir(dirlst, dirname):
327 327 match = False
328 328 samestat = os.path.samestat
329 329 dirstat = os.stat(dirname)
330 330 for lstdirstat in dirlst:
331 331 if samestat(dirstat, lstdirstat):
332 332 match = True
333 333 break
334 334 if not match:
335 335 dirlst.append(dirstat)
336 336 return not match
337 337 else:
338 338 followsym = False
339 339
340 340 if (seen_dirs is None) and followsym:
341 341 seen_dirs = []
342 342 adddir(seen_dirs, path)
343 343 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
344 344 dirs.sort()
345 345 if '.hg' in dirs:
346 346 yield root # found a repository
347 347 qroot = os.path.join(root, '.hg', 'patches')
348 348 if os.path.isdir(os.path.join(qroot, '.hg')):
349 349 yield qroot # we have a patch queue repo here
350 350 if recurse:
351 351 # avoid recursing inside the .hg directory
352 352 dirs.remove('.hg')
353 353 else:
354 354 dirs[:] = [] # don't descend further
355 355 elif followsym:
356 356 newdirs = []
357 357 for d in dirs:
358 358 fname = os.path.join(root, d)
359 359 if adddir(seen_dirs, fname):
360 360 if os.path.islink(fname):
361 361 for hgname in walkrepos(fname, True, seen_dirs):
362 362 yield hgname
363 363 else:
364 364 newdirs.append(d)
365 365 dirs[:] = newdirs
366 366
367 367 def osrcpath():
368 368 '''return default os-specific hgrc search path'''
369 369 path = systemrcpath()
370 370 path.extend(userrcpath())
371 371 path = [os.path.normpath(f) for f in path]
372 372 return path
373 373
374 374 _rcpath = None
375 375
376 376 def rcpath():
377 377 '''return hgrc search path. if env var HGRCPATH is set, use it.
378 378 for each item in path, if directory, use files ending in .rc,
379 379 else use item.
380 380 make HGRCPATH empty to only look in .hg/hgrc of current repo.
381 381 if no HGRCPATH, use default os-specific path.'''
382 382 global _rcpath
383 383 if _rcpath is None:
384 384 if 'HGRCPATH' in os.environ:
385 385 _rcpath = []
386 386 for p in os.environ['HGRCPATH'].split(os.pathsep):
387 387 if not p:
388 388 continue
389 389 p = util.expandpath(p)
390 390 if os.path.isdir(p):
391 391 for f, kind in osutil.listdir(p):
392 392 if f.endswith('.rc'):
393 393 _rcpath.append(os.path.join(p, f))
394 394 else:
395 395 _rcpath.append(p)
396 396 else:
397 397 _rcpath = osrcpath()
398 398 return _rcpath
399 399
400 400 if os.name != 'nt':
401 401
402 402 def rcfiles(path):
403 403 rcs = [os.path.join(path, 'hgrc')]
404 404 rcdir = os.path.join(path, 'hgrc.d')
405 405 try:
406 406 rcs.extend([os.path.join(rcdir, f)
407 407 for f, kind in osutil.listdir(rcdir)
408 408 if f.endswith(".rc")])
409 409 except OSError:
410 410 pass
411 411 return rcs
412 412
413 413 def systemrcpath():
414 414 path = []
415 415 # old mod_python does not set sys.argv
416 416 if len(getattr(sys, 'argv', [])) > 0:
417 417 p = os.path.dirname(os.path.dirname(sys.argv[0]))
418 418 path.extend(rcfiles(os.path.join(p, 'etc/mercurial')))
419 419 path.extend(rcfiles('/etc/mercurial'))
420 420 return path
421 421
422 422 def userrcpath():
423 423 return [os.path.expanduser('~/.hgrc')]
424 424
425 425 else:
426 426
427 427 _HKEY_LOCAL_MACHINE = 0x80000002L
428 428
429 429 def systemrcpath():
430 430 '''return default os-specific hgrc search path'''
431 431 rcpath = []
432 432 filename = util.executablepath()
433 433 # Use mercurial.ini found in directory with hg.exe
434 434 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
435 435 if os.path.isfile(progrc):
436 436 rcpath.append(progrc)
437 437 return rcpath
438 438 # Use hgrc.d found in directory with hg.exe
439 439 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
440 440 if os.path.isdir(progrcd):
441 441 for f, kind in osutil.listdir(progrcd):
442 442 if f.endswith('.rc'):
443 443 rcpath.append(os.path.join(progrcd, f))
444 444 return rcpath
445 445 # else look for a system rcpath in the registry
446 446 value = util.lookupreg('SOFTWARE\\Mercurial', None,
447 447 _HKEY_LOCAL_MACHINE)
448 448 if not isinstance(value, str) or not value:
449 449 return rcpath
450 450 value = value.replace('/', os.sep)
451 451 for p in value.split(os.pathsep):
452 452 if p.lower().endswith('mercurial.ini'):
453 453 rcpath.append(p)
454 454 elif os.path.isdir(p):
455 455 for f, kind in osutil.listdir(p):
456 456 if f.endswith('.rc'):
457 457 rcpath.append(os.path.join(p, f))
458 458 return rcpath
459 459
460 460 def userrcpath():
461 461 '''return os-specific hgrc search path to the user dir'''
462 462 home = os.path.expanduser('~')
463 463 path = [os.path.join(home, 'mercurial.ini'),
464 464 os.path.join(home, '.hgrc')]
465 465 userprofile = os.environ.get('USERPROFILE')
466 466 if userprofile:
467 467 path.append(os.path.join(userprofile, 'mercurial.ini'))
468 468 path.append(os.path.join(userprofile, '.hgrc'))
469 469 return path
470 470
471 471 def revsingle(repo, revspec, default='.'):
472 472 if not revspec:
473 473 return repo[default]
474 474
475 475 l = revrange(repo, [revspec])
476 476 if len(l) < 1:
477 477 raise util.Abort(_('empty revision set'))
478 478 return repo[l[-1]]
479 479
480 480 def revpair(repo, revs):
481 481 if not revs:
482 482 return repo.dirstate.p1(), None
483 483
484 484 l = revrange(repo, revs)
485 485
486 486 if len(l) == 0:
487 487 return repo.dirstate.p1(), None
488 488
489 489 if len(l) == 1:
490 490 return repo.lookup(l[0]), None
491 491
492 492 return repo.lookup(l[0]), repo.lookup(l[-1])
493 493
494 494 _revrangesep = ':'
495 495
496 496 def revrange(repo, revs):
497 497 """Yield revision as strings from a list of revision specifications."""
498 498
499 499 def revfix(repo, val, defval):
500 500 if not val and val != 0 and defval is not None:
501 501 return defval
502 502 return repo.changelog.rev(repo.lookup(val))
503 503
504 504 seen, l = set(), []
505 505 for spec in revs:
506 506 # attempt to parse old-style ranges first to deal with
507 507 # things like old-tag which contain query metacharacters
508 508 try:
509 509 if isinstance(spec, int):
510 510 seen.add(spec)
511 511 l.append(spec)
512 512 continue
513 513
514 514 if _revrangesep in spec:
515 515 start, end = spec.split(_revrangesep, 1)
516 516 start = revfix(repo, start, 0)
517 517 end = revfix(repo, end, len(repo) - 1)
518 518 step = start > end and -1 or 1
519 519 for rev in xrange(start, end + step, step):
520 520 if rev in seen:
521 521 continue
522 522 seen.add(rev)
523 523 l.append(rev)
524 524 continue
525 525 elif spec and spec in repo: # single unquoted rev
526 526 rev = revfix(repo, spec, None)
527 527 if rev in seen:
528 528 continue
529 529 seen.add(rev)
530 530 l.append(rev)
531 531 continue
532 532 except error.RepoLookupError:
533 533 pass
534 534
535 535 # fall through to new-style queries if old-style fails
536 536 m = revset.match(repo.ui, spec)
537 537 for r in m(repo, range(len(repo))):
538 538 if r not in seen:
539 539 l.append(r)
540 540 seen.update(l)
541 541
542 542 return l
543 543
544 544 def expandpats(pats):
545 545 if not util.expandglobs:
546 546 return list(pats)
547 547 ret = []
548 548 for p in pats:
549 549 kind, name = matchmod._patsplit(p, None)
550 550 if kind is None:
551 551 try:
552 552 globbed = glob.glob(name)
553 553 except re.error:
554 554 globbed = [name]
555 555 if globbed:
556 556 ret.extend(globbed)
557 557 continue
558 558 ret.append(p)
559 559 return ret
560 560
561 def match(ctxorrepo, pats=[], opts={}, globbed=False, default='relpath'):
561 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
562 562 if pats == ("",):
563 563 pats = []
564 564 if not globbed and default == 'relpath':
565 565 pats = expandpats(pats or [])
566 566
567 if hasattr(ctxorrepo, 'match'):
568 ctx = ctxorrepo
569 else:
570 # will eventually abort here
571 ctx = ctxorrepo[None]
572
573 567 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
574 568 default)
575 569 def badfn(f, msg):
576 570 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
577 571 m.bad = badfn
578 572 return m
579 573
580 574 def matchall(repo):
581 575 return matchmod.always(repo.root, repo.getcwd())
582 576
583 577 def matchfiles(repo, files):
584 578 return matchmod.exact(repo.root, repo.getcwd(), files)
585 579
586 580 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
587 581 if dry_run is None:
588 582 dry_run = opts.get('dry_run')
589 583 if similarity is None:
590 584 similarity = float(opts.get('similarity') or 0)
591 585 # we'd use status here, except handling of symlinks and ignore is tricky
592 586 added, unknown, deleted, removed = [], [], [], []
593 587 audit_path = pathauditor(repo.root)
594 588 m = match(repo[None], pats, opts)
595 589 for abs in repo.walk(m):
596 590 target = repo.wjoin(abs)
597 591 good = True
598 592 try:
599 593 audit_path(abs)
600 594 except (OSError, util.Abort):
601 595 good = False
602 596 rel = m.rel(abs)
603 597 exact = m.exact(abs)
604 598 if good and abs not in repo.dirstate:
605 599 unknown.append(abs)
606 600 if repo.ui.verbose or not exact:
607 601 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
608 602 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
609 603 or (os.path.isdir(target) and not os.path.islink(target))):
610 604 deleted.append(abs)
611 605 if repo.ui.verbose or not exact:
612 606 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
613 607 # for finding renames
614 608 elif repo.dirstate[abs] == 'r':
615 609 removed.append(abs)
616 610 elif repo.dirstate[abs] == 'a':
617 611 added.append(abs)
618 612 copies = {}
619 613 if similarity > 0:
620 614 for old, new, score in similar.findrenames(repo,
621 615 added + unknown, removed + deleted, similarity):
622 616 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
623 617 repo.ui.status(_('recording removal of %s as rename to %s '
624 618 '(%d%% similar)\n') %
625 619 (m.rel(old), m.rel(new), score * 100))
626 620 copies[new] = old
627 621
628 622 if not dry_run:
629 623 wctx = repo[None]
630 624 wlock = repo.wlock()
631 625 try:
632 626 wctx.forget(deleted)
633 627 wctx.add(unknown)
634 628 for new, old in copies.iteritems():
635 629 wctx.copy(old, new)
636 630 finally:
637 631 wlock.release()
638 632
639 633 def updatedir(ui, repo, patches, similarity=0):
640 634 '''Update dirstate after patch application according to metadata'''
641 635 if not patches:
642 636 return []
643 637 copies = []
644 638 removes = set()
645 639 cfiles = patches.keys()
646 640 cwd = repo.getcwd()
647 641 if cwd:
648 642 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
649 643 for f in patches:
650 644 gp = patches[f]
651 645 if not gp:
652 646 continue
653 647 if gp.op == 'RENAME':
654 648 copies.append((gp.oldpath, gp.path))
655 649 removes.add(gp.oldpath)
656 650 elif gp.op == 'COPY':
657 651 copies.append((gp.oldpath, gp.path))
658 652 elif gp.op == 'DELETE':
659 653 removes.add(gp.path)
660 654
661 655 wctx = repo[None]
662 656 for src, dst in copies:
663 657 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
664 658 if (not similarity) and removes:
665 659 wctx.remove(sorted(removes), True)
666 660
667 661 for f in patches:
668 662 gp = patches[f]
669 663 if gp and gp.mode:
670 664 islink, isexec = gp.mode
671 665 dst = repo.wjoin(gp.path)
672 666 # patch won't create empty files
673 667 if gp.op == 'ADD' and not os.path.lexists(dst):
674 668 flags = (isexec and 'x' or '') + (islink and 'l' or '')
675 669 repo.wwrite(gp.path, '', flags)
676 670 util.setflags(dst, islink, isexec)
677 671 addremove(repo, cfiles, similarity=similarity)
678 672 files = patches.keys()
679 673 files.extend([r for r in removes if r not in files])
680 674 return sorted(files)
681 675
682 676 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
683 677 """Update the dirstate to reflect the intent of copying src to dst. For
684 678 different reasons it might not end with dst being marked as copied from src.
685 679 """
686 680 origsrc = repo.dirstate.copied(src) or src
687 681 if dst == origsrc: # copying back a copy?
688 682 if repo.dirstate[dst] not in 'mn' and not dryrun:
689 683 repo.dirstate.normallookup(dst)
690 684 else:
691 685 if repo.dirstate[origsrc] == 'a' and origsrc == src:
692 686 if not ui.quiet:
693 687 ui.warn(_("%s has not been committed yet, so no copy "
694 688 "data will be stored for %s.\n")
695 689 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
696 690 if repo.dirstate[dst] in '?r' and not dryrun:
697 691 wctx.add([dst])
698 692 elif not dryrun:
699 693 wctx.copy(origsrc, dst)
700 694
701 695 def readrequires(opener, supported):
702 696 '''Reads and parses .hg/requires and checks if all entries found
703 697 are in the list of supported features.'''
704 698 requirements = set(opener.read("requires").splitlines())
705 699 for r in requirements:
706 700 if r not in supported:
707 701 if not r or not r[0].isalnum():
708 702 raise error.RequirementError(_(".hg/requires file is corrupt"))
709 703 raise error.RequirementError(_("unknown repository format: "
710 704 "requires feature '%s' (upgrade Mercurial)") % r)
711 705 return requirements
General Comments 0
You need to be logged in to leave comments. Login now