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