1254 lines · 43.5 KB · Python
RawDownload
# cmdutil.py - help for command processing in mercurial## Copyright 2005-2007 Matt Mackall <mpm@selenic.com>## This software may be used and distributed according to the terms of the# GNU General Public License version 2, incorporated herein by reference. from node import hex, nullid, nullrev, shortfrom i18n import _import os, sys, errno, re, globimport mdiff, bdiff, util, templater, patch, error, encodingimport match as _match revrangesep = ':' def findpossible(cmd, table, strict=False):    """    Return cmd -> (aliases, command table entry)    for each matching command.    Return debug commands (or their aliases) only if no normal command matches.    """    choice = {}    debugchoice = {}    for e in table.keys():        aliases = e.lstrip("^").split("|")        found = None        if cmd in aliases:            found = cmd        elif not strict:            for a in aliases:                if a.startswith(cmd):                    found = a                    break        if found is not None:            if aliases[0].startswith("debug") or found.startswith("debug"):                debugchoice[found] = (aliases, table[e])            else:                choice[found] = (aliases, table[e])     if not choice and debugchoice:        choice = debugchoice     return choice def findcmd(cmd, table, strict=True):    """Return (aliases, command table entry) for command string."""    choice = findpossible(cmd, table, strict)     if cmd in choice:        return choice[cmd]     if len(choice) > 1:        clist = choice.keys()        clist.sort()        raise error.AmbiguousCommand(cmd, clist)     if choice:        return choice.values()[0]     raise error.UnknownCommand(cmd) def bail_if_changed(repo):    if repo.dirstate.parents()[1] != nullid:        raise util.Abort(_('outstanding uncommitted merge'))    modified, added, removed, deleted = repo.status()[:4]    if modified or added or removed or deleted:        raise util.Abort(_("outstanding uncommitted changes")) def logmessage(opts):    """ get the log message according to -m and -l option """    message = opts.get('message')    logfile = opts.get('logfile')     if message and logfile:        raise util.Abort(_('options --message and --logfile are mutually '                           'exclusive'))    if not message and logfile:        try:            if logfile == '-':                message = sys.stdin.read()            else:                message = open(logfile).read()        except IOError, inst:            raise util.Abort(_("can't read commit message '%s': %s") %                             (logfile, inst.strerror))    return message def loglimit(opts):    """get the log limit according to option -l/--limit"""    limit = opts.get('limit')    if limit:        try:            limit = int(limit)        except ValueError:            raise util.Abort(_('limit must be a positive integer'))        if limit <= 0: raise util.Abort(_('limit must be positive'))    else:        limit = sys.maxint    return limit def remoteui(src, opts):    'build a remote ui from ui or repo and opts'    if hasattr(src, 'baseui'): # looks like a repository        dst = src.baseui.copy() # drop repo-specific config        src = src.ui # copy target options from repo    else: # assume it's a global ui object        dst = src.copy() # keep all global options     # copy ssh-specific options    for o in 'ssh', 'remotecmd':        v = opts.get(o) or src.config('ui', o)        if v:            dst.setconfig("ui", o, v)    # copy bundle-specific options    r = src.config('bundle', 'mainreporoot')    if r:        dst.setconfig('bundle', 'mainreporoot', r)     return dst def revpair(repo, revs):    '''return pair of nodes, given list of revisions. second item can    be None, meaning use working dir.'''     def revfix(repo, val, defval):        if not val and val != 0 and defval is not None:            val = defval        return repo.lookup(val)     if not revs:        return repo.dirstate.parents()[0], None    end = None    if len(revs) == 1:        if revrangesep in revs[0]:            start, end = revs[0].split(revrangesep, 1)            start = revfix(repo, start, 0)            end = revfix(repo, end, len(repo) - 1)        else:            start = revfix(repo, revs[0], None)    elif len(revs) == 2:        if revrangesep in revs[0] or revrangesep in revs[1]:            raise util.Abort(_('too many revisions specified'))        start = revfix(repo, revs[0], None)        end = revfix(repo, revs[1], None)    else:        raise util.Abort(_('too many revisions specified'))    return start, end def revrange(repo, revs):    """Yield revision as strings from a list of revision specifications."""     def revfix(repo, val, defval):        if not val and val != 0 and defval is not None:            return defval        return repo.changelog.rev(repo.lookup(val))     seen, l = set(), []    for spec in revs:        if revrangesep in spec:            start, end = spec.split(revrangesep, 1)            start = revfix(repo, start, 0)            end = revfix(repo, end, len(repo) - 1)            step = start > end and -1 or 1            for rev in xrange(start, end+step, step):                if rev in seen:                    continue                seen.add(rev)                l.append(rev)        else:            rev = revfix(repo, spec, None)            if rev in seen:                continue            seen.add(rev)            l.append(rev)     return l def make_filename(repo, pat, node,                  total=None, seqno=None, revwidth=None, pathname=None):    node_expander = {        'H': lambda: hex(node),        'R': lambda: str(repo.changelog.rev(node)),        'h': lambda: short(node),        }    expander = {        '%': lambda: '%',        'b': lambda: os.path.basename(repo.root),        }     try:        if node:            expander.update(node_expander)        if node:            expander['r'] = (lambda:                    str(repo.changelog.rev(node)).zfill(revwidth or 0))        if total is not None:            expander['N'] = lambda: str(total)        if seqno is not None:            expander['n'] = lambda: str(seqno)        if total is not None and seqno is not None:            expander['n'] = lambda: str(seqno).zfill(len(str(total)))        if pathname is not None:            expander['s'] = lambda: os.path.basename(pathname)            expander['d'] = lambda: os.path.dirname(pathname) or '.'            expander['p'] = lambda: pathname         newname = []        patlen = len(pat)        i = 0        while i < patlen:            c = pat[i]            if c == '%':                i += 1                c = pat[i]                c = expander[c]()            newname.append(c)            i += 1        return ''.join(newname)    except KeyError, inst:        raise util.Abort(_("invalid format spec '%%%s' in output filename") %                         inst.args[0]) def make_file(repo, pat, node=None,              total=None, seqno=None, revwidth=None, mode='wb', pathname=None):     writable = 'w' in mode or 'a' in mode     if not pat or pat == '-':        return writable and sys.stdout or sys.stdin    if hasattr(pat, 'write') and writable:        return pat    if hasattr(pat, 'read') and 'r' in mode:        return pat    return open(make_filename(repo, pat, node, total, seqno, revwidth,                              pathname),                mode) def expandpats(pats):    if not util.expandglobs:        return list(pats)    ret = []    for p in pats:        kind, name = _match._patsplit(p, None)        if kind is None:            try:                globbed = glob.glob(name)            except re.error:                globbed = [name]            if globbed:                ret.extend(globbed)                continue        ret.append(p)    return ret def match(repo, pats=[], opts={}, globbed=False, default='relpath'):    if not globbed and default == 'relpath':        pats = expandpats(pats or [])    m = _match.match(repo.root, repo.getcwd(), pats,                    opts.get('include'), opts.get('exclude'), default)    def badfn(f, msg):        repo.ui.warn("%s: %s\n" % (m.rel(f), msg))    m.bad = badfn    return m def matchall(repo):    return _match.always(repo.root, repo.getcwd()) def matchfiles(repo, files):    return _match.exact(repo.root, repo.getcwd(), files) def findrenames(repo, added, removed, threshold):    '''find renamed files -- yields (before, after, score) tuples'''    ctx = repo['.']    for a in added:        aa = repo.wread(a)        bestname, bestscore = None, threshold        for r in removed:            if r not in ctx:                continue            rr = ctx.filectx(r).data()             # bdiff.blocks() returns blocks of matching lines            # count the number of bytes in each            equal = 0            alines = mdiff.splitnewlines(aa)            matches = bdiff.blocks(aa, rr)            for x1,x2,y1,y2 in matches:                for line in alines[x1:x2]:                    equal += len(line)             lengths = len(aa) + len(rr)            if lengths:                myscore = equal*2.0 / lengths                if myscore >= bestscore:                    bestname, bestscore = r, myscore        if bestname:            yield bestname, a, bestscore def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):    if dry_run is None:        dry_run = opts.get('dry_run')    if similarity is None:        similarity = float(opts.get('similarity') or 0)    # we'd use status here, except handling of symlinks and ignore is tricky    added, unknown, deleted, removed = [], [], [], []    audit_path = util.path_auditor(repo.root)    m = match(repo, pats, opts)    for abs in repo.walk(m):        target = repo.wjoin(abs)        good = True        try:            audit_path(abs)        except:            good = False        rel = m.rel(abs)        exact = m.exact(abs)        if good and abs not in repo.dirstate:            unknown.append(abs)            if repo.ui.verbose or not exact:                repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))        elif repo.dirstate[abs] != 'r' and (not good or not util.lexists(target)            or (os.path.isdir(target) and not os.path.islink(target))):            deleted.append(abs)            if repo.ui.verbose or not exact:                repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))        # for finding renames        elif repo.dirstate[abs] == 'r':            removed.append(abs)        elif repo.dirstate[abs] == 'a':            added.append(abs)    if not dry_run:        repo.remove(deleted)        repo.add(unknown)    if similarity > 0:        for old, new, score in findrenames(repo, added + unknown,                                           removed + deleted, similarity):            if repo.ui.verbose or not m.exact(old) or not m.exact(new):                repo.ui.status(_('recording removal of %s as rename to %s '                                 '(%d%% similar)\n') %                               (m.rel(old), m.rel(new), score * 100))            if not dry_run:                repo.copy(old, new) def copy(ui, repo, pats, opts, rename=False):    # called with the repo lock held    #    # hgsep => pathname that uses "/" to separate directories    # ossep => pathname that uses os.sep to separate directories    cwd = repo.getcwd()    targets = {}    after = opts.get("after")    dryrun = opts.get("dry_run")     def walkpat(pat):        srcs = []        m = match(repo, [pat], opts, globbed=True)        for abs in repo.walk(m):            state = repo.dirstate[abs]            rel = m.rel(abs)            exact = m.exact(abs)            if state in '?r':                if exact and state == '?':                    ui.warn(_('%s: not copying - file is not managed\n') % rel)                if exact and state == 'r':                    ui.warn(_('%s: not copying - file has been marked for'                              ' remove\n') % rel)                continue            # abs: hgsep            # rel: ossep            srcs.append((abs, rel, exact))        return srcs     # abssrc: hgsep    # relsrc: ossep    # otarget: ossep    def copyfile(abssrc, relsrc, otarget, exact):        abstarget = util.canonpath(repo.root, cwd, otarget)        reltarget = repo.pathto(abstarget, cwd)        target = repo.wjoin(abstarget)        src = repo.wjoin(abssrc)        state = repo.dirstate[abstarget]         # check for collisions        prevsrc = targets.get(abstarget)        if prevsrc is not None:            ui.warn(_('%s: not overwriting - %s collides with %s\n') %                    (reltarget, repo.pathto(abssrc, cwd),                     repo.pathto(prevsrc, cwd)))            return         # check for overwrites        exists = os.path.exists(target)        if not after and exists or after and state in 'mn':            if not opts['force']:                ui.warn(_('%s: not overwriting - file exists\n') %                        reltarget)                return         if after:            if not exists:                return        elif not dryrun:            try:                if exists:                    os.unlink(target)                targetdir = os.path.dirname(target) or '.'                if not os.path.isdir(targetdir):                    os.makedirs(targetdir)                util.copyfile(src, target)            except IOError, inst:                if inst.errno == errno.ENOENT:                    ui.warn(_('%s: deleted in working copy\n') % relsrc)                else:                    ui.warn(_('%s: cannot copy - %s\n') %                            (relsrc, inst.strerror))                    return True # report a failure         if ui.verbose or not exact:            if rename:                ui.status(_('moving %s to %s\n') % (relsrc, reltarget))            else:                ui.status(_('copying %s to %s\n') % (relsrc, reltarget))         targets[abstarget] = abssrc         # fix up dirstate        origsrc = repo.dirstate.copied(abssrc) or abssrc        if abstarget == origsrc: # copying back a copy?            if state not in 'mn' and not dryrun:                repo.dirstate.normallookup(abstarget)        else:            if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:                if not ui.quiet:                    ui.warn(_("%s has not been committed yet, so no copy "                              "data will be stored for %s.\n")                            % (repo.pathto(origsrc, cwd), reltarget))                if repo.dirstate[abstarget] in '?r' and not dryrun:                    repo.add([abstarget])            elif not dryrun:                repo.copy(origsrc, abstarget)         if rename and not dryrun:            repo.remove([abssrc], not after)     # pat: ossep    # dest ossep    # srcs: list of (hgsep, hgsep, ossep, bool)    # return: function that takes hgsep and returns ossep    def targetpathfn(pat, dest, srcs):        if os.path.isdir(pat):            abspfx = util.canonpath(repo.root, cwd, pat)            abspfx = util.localpath(abspfx)            if destdirexists:                striplen = len(os.path.split(abspfx)[0])            else:                striplen = len(abspfx)            if striplen:                striplen += len(os.sep)            res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])        elif destdirexists:            res = lambda p: os.path.join(dest,                                         os.path.basename(util.localpath(p)))        else:            res = lambda p: dest        return res     # pat: ossep    # dest ossep    # srcs: list of (hgsep, hgsep, ossep, bool)    # return: function that takes hgsep and returns ossep    def targetpathafterfn(pat, dest, srcs):        if _match.patkind(pat):            # a mercurial pattern            res = lambda p: os.path.join(dest,                                         os.path.basename(util.localpath(p)))        else:            abspfx = util.canonpath(repo.root, cwd, pat)            if len(abspfx) < len(srcs[0][0]):                # A directory. Either the target path contains the last                # component of the source path or it does not.                def evalpath(striplen):                    score = 0                    for s in srcs:                        t = os.path.join(dest, util.localpath(s[0])[striplen:])                        if os.path.exists(t):                            score += 1                    return score                 abspfx = util.localpath(abspfx)                striplen = len(abspfx)                if striplen:                    striplen += len(os.sep)                if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):                    score = evalpath(striplen)                    striplen1 = len(os.path.split(abspfx)[0])                    if striplen1:                        striplen1 += len(os.sep)                    if evalpath(striplen1) > score:                        striplen = striplen1                res = lambda p: os.path.join(dest,

Lines 1–500 of 1254.