mercurial/scmutil.py
changeset 14320 3438417a6657
parent 14319 b33f3e35efb0
child 14404 69b60edfd76c
equal deleted inserted replaced
14319:b33f3e35efb0 14320:3438417a6657
     4 #
     4 #
     5 # This software may be used and distributed according to the terms of the
     5 # This software may be used and distributed according to the terms of the
     6 # GNU General Public License version 2 or any later version.
     6 # GNU General Public License version 2 or any later version.
     7 
     7 
     8 from i18n import _
     8 from i18n import _
     9 import util, error, osutil, revset
     9 import util, error, osutil, revset, similar
    10 import os, errno, stat, sys
    10 import match as matchmod
       
    11 import os, errno, stat, sys, glob
    11 
    12 
    12 def checkfilename(f):
    13 def checkfilename(f):
    13     '''Check that the filename f is an acceptable filename for a tracked file'''
    14     '''Check that the filename f is an acceptable filename for a tracked file'''
    14     if '\r' in f or '\n' in f:
    15     if '\r' in f or '\n' in f:
    15         raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
    16         raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
   534             if r not in seen:
   535             if r not in seen:
   535                 l.append(r)
   536                 l.append(r)
   536         seen.update(l)
   537         seen.update(l)
   537 
   538 
   538     return l
   539     return l
       
   540 
       
   541 def expandpats(pats):
       
   542     if not util.expandglobs:
       
   543         return list(pats)
       
   544     ret = []
       
   545     for p in pats:
       
   546         kind, name = matchmod._patsplit(p, None)
       
   547         if kind is None:
       
   548             try:
       
   549                 globbed = glob.glob(name)
       
   550             except re.error:
       
   551                 globbed = [name]
       
   552             if globbed:
       
   553                 ret.extend(globbed)
       
   554                 continue
       
   555         ret.append(p)
       
   556     return ret
       
   557 
       
   558 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
       
   559     if pats == ("",):
       
   560         pats = []
       
   561     if not globbed and default == 'relpath':
       
   562         pats = expandpats(pats or [])
       
   563     m = matchmod.match(repo.root, repo.getcwd(), pats,
       
   564                        opts.get('include'), opts.get('exclude'), default,
       
   565                        auditor=repo.auditor)
       
   566     def badfn(f, msg):
       
   567         repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
       
   568     m.bad = badfn
       
   569     return m
       
   570 
       
   571 def matchall(repo):
       
   572     return matchmod.always(repo.root, repo.getcwd())
       
   573 
       
   574 def matchfiles(repo, files):
       
   575     return matchmod.exact(repo.root, repo.getcwd(), files)
       
   576 
       
   577 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
       
   578     if dry_run is None:
       
   579         dry_run = opts.get('dry_run')
       
   580     if similarity is None:
       
   581         similarity = float(opts.get('similarity') or 0)
       
   582     # we'd use status here, except handling of symlinks and ignore is tricky
       
   583     added, unknown, deleted, removed = [], [], [], []
       
   584     audit_path = pathauditor(repo.root)
       
   585     m = match(repo, pats, opts)
       
   586     for abs in repo.walk(m):
       
   587         target = repo.wjoin(abs)
       
   588         good = True
       
   589         try:
       
   590             audit_path(abs)
       
   591         except (OSError, util.Abort):
       
   592             good = False
       
   593         rel = m.rel(abs)
       
   594         exact = m.exact(abs)
       
   595         if good and abs not in repo.dirstate:
       
   596             unknown.append(abs)
       
   597             if repo.ui.verbose or not exact:
       
   598                 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
       
   599         elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
       
   600             or (os.path.isdir(target) and not os.path.islink(target))):
       
   601             deleted.append(abs)
       
   602             if repo.ui.verbose or not exact:
       
   603                 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
       
   604         # for finding renames
       
   605         elif repo.dirstate[abs] == 'r':
       
   606             removed.append(abs)
       
   607         elif repo.dirstate[abs] == 'a':
       
   608             added.append(abs)
       
   609     copies = {}
       
   610     if similarity > 0:
       
   611         for old, new, score in similar.findrenames(repo,
       
   612                 added + unknown, removed + deleted, similarity):
       
   613             if repo.ui.verbose or not m.exact(old) or not m.exact(new):
       
   614                 repo.ui.status(_('recording removal of %s as rename to %s '
       
   615                                  '(%d%% similar)\n') %
       
   616                                (m.rel(old), m.rel(new), score * 100))
       
   617             copies[new] = old
       
   618 
       
   619     if not dry_run:
       
   620         wctx = repo[None]
       
   621         wlock = repo.wlock()
       
   622         try:
       
   623             wctx.remove(deleted)
       
   624             wctx.add(unknown)
       
   625             for new, old in copies.iteritems():
       
   626                 wctx.copy(old, new)
       
   627         finally:
       
   628             wlock.release()
       
   629 
       
   630 def updatedir(ui, repo, patches, similarity=0):
       
   631     '''Update dirstate after patch application according to metadata'''
       
   632     if not patches:
       
   633         return []
       
   634     copies = []
       
   635     removes = set()
       
   636     cfiles = patches.keys()
       
   637     cwd = repo.getcwd()
       
   638     if cwd:
       
   639         cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
       
   640     for f in patches:
       
   641         gp = patches[f]
       
   642         if not gp:
       
   643             continue
       
   644         if gp.op == 'RENAME':
       
   645             copies.append((gp.oldpath, gp.path))
       
   646             removes.add(gp.oldpath)
       
   647         elif gp.op == 'COPY':
       
   648             copies.append((gp.oldpath, gp.path))
       
   649         elif gp.op == 'DELETE':
       
   650             removes.add(gp.path)
       
   651 
       
   652     wctx = repo[None]
       
   653     for src, dst in copies:
       
   654         dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
       
   655     if (not similarity) and removes:
       
   656         wctx.remove(sorted(removes), True)
       
   657 
       
   658     for f in patches:
       
   659         gp = patches[f]
       
   660         if gp and gp.mode:
       
   661             islink, isexec = gp.mode
       
   662             dst = repo.wjoin(gp.path)
       
   663             # patch won't create empty files
       
   664             if gp.op == 'ADD' and not os.path.lexists(dst):
       
   665                 flags = (isexec and 'x' or '') + (islink and 'l' or '')
       
   666                 repo.wwrite(gp.path, '', flags)
       
   667             util.setflags(dst, islink, isexec)
       
   668     addremove(repo, cfiles, similarity=similarity)
       
   669     files = patches.keys()
       
   670     files.extend([r for r in removes if r not in files])
       
   671     return sorted(files)
       
   672 
       
   673 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
       
   674     """Update the dirstate to reflect the intent of copying src to dst. For
       
   675     different reasons it might not end with dst being marked as copied from src.
       
   676     """
       
   677     origsrc = repo.dirstate.copied(src) or src
       
   678     if dst == origsrc: # copying back a copy?
       
   679         if repo.dirstate[dst] not in 'mn' and not dryrun:
       
   680             repo.dirstate.normallookup(dst)
       
   681     else:
       
   682         if repo.dirstate[origsrc] == 'a' and origsrc == src:
       
   683             if not ui.quiet:
       
   684                 ui.warn(_("%s has not been committed yet, so no copy "
       
   685                           "data will be stored for %s.\n")
       
   686                         % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
       
   687             if repo.dirstate[dst] in '?r' and not dryrun:
       
   688                 wctx.add([dst])
       
   689         elif not dryrun:
       
   690             wctx.copy(origsrc, dst)