diff -r 083b6e3142a2 -r 9981b6b19ecf mercurial/cmdutil.py --- a/mercurial/cmdutil.py Sun Dec 02 17:04:16 2007 -0600 +++ b/mercurial/cmdutil.py Sun Dec 02 18:11:59 2007 -0600 @@ -8,7 +8,7 @@ from node import * from i18n import _ import os, sys, bisect, stat -import mdiff, bdiff, util, templater, patch +import mdiff, bdiff, util, templater, patch, errno revrangesep = ':' @@ -286,6 +286,204 @@ if not dry_run: repo.copy(old, new) +def copy(ui, repo, pats, opts): + # 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() + errors = 0 + copied = [] + targets = {} + + # abs: hgsep + # rel: ossep + # return: hgsep + def okaytocopy(abs, rel, exact): + reasons = {'?': _('is not managed'), + 'r': _('has been marked for remove')} + state = repo.dirstate[abs] + reason = reasons.get(state) + if reason: + if exact: + ui.warn(_('%s: not copying - file %s\n') % (rel, reason)) + else: + if state == 'a': + origsrc = repo.dirstate.copied(abs) + if origsrc is not None: + return origsrc + return abs + + # origsrc: hgsep + # abssrc: hgsep + # relsrc: ossep + # otarget: ossep + def copy(origsrc, abssrc, relsrc, otarget, exact): + abstarget = util.canonpath(repo.root, cwd, otarget) + reltarget = repo.pathto(abstarget, cwd) + prevsrc = targets.get(abstarget) + src = repo.wjoin(abssrc) + target = repo.wjoin(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 + if (not opts['after'] and os.path.exists(target) or + opts['after'] and repo.dirstate[abstarget] in 'mn'): + if not opts['force']: + ui.warn(_('%s: not overwriting - file exists\n') % + reltarget) + return + if not opts['after'] and not opts.get('dry_run'): + os.unlink(target) + if opts['after']: + if not os.path.exists(target): + return + else: + targetdir = os.path.dirname(target) or '.' + if not os.path.isdir(targetdir) and not opts.get('dry_run'): + os.makedirs(targetdir) + try: + restore = repo.dirstate[abstarget] == 'r' + if restore and not opts.get('dry_run'): + repo.undelete([abstarget]) + try: + if not opts.get('dry_run'): + util.copyfile(src, target) + restore = False + finally: + if restore: + repo.remove([abstarget]) + 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)) + errors += 1 + return + if ui.verbose or not exact: + ui.status(_('copying %s to %s\n') % (relsrc, reltarget)) + targets[abstarget] = abssrc + if abstarget != origsrc: + if repo.dirstate[origsrc] == 'a': + 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 abstarget not in repo.dirstate and not opts.get('dry_run'): + repo.add([abstarget]) + elif not opts.get('dry_run'): + repo.copy(origsrc, abstarget) + copied.append((abssrc, relsrc, exact)) + + # 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 util.patkind(pat, None)[0]: + # 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, + util.localpath(p)[striplen:]) + else: + # a file + if destdirexists: + res = lambda p: os.path.join(dest, + os.path.basename(util.localpath(p))) + else: + res = lambda p: dest + return res + + + pats = util.expand_glob(pats) + if not pats: + raise util.Abort(_('no source or destination specified')) + if len(pats) == 1: + raise util.Abort(_('no destination specified')) + dest = pats.pop() + destdirexists = os.path.isdir(dest) + if not destdirexists: + if len(pats) > 1 or util.patkind(pats[0], None)[0]: + raise util.Abort(_('with multiple sources, destination must be an ' + 'existing directory')) + if dest.endswith(os.sep) or os.altsep and dest.endswith(os.altsep): + raise util.Abort(_('destination %s is not a directory') % dest) + if opts['after']: + tfn = targetpathafterfn + else: + tfn = targetpathfn + copylist = [] + for pat in pats: + srcs = [] + for tag, abssrc, relsrc, exact in walk(repo, [pat], opts, + globbed=True): + origsrc = okaytocopy(abssrc, relsrc, exact) + if origsrc: + srcs.append((origsrc, abssrc, relsrc, exact)) + if not srcs: + continue + copylist.append((tfn(pat, dest, srcs), srcs)) + if not copylist: + raise util.Abort(_('no files to copy')) + + for targetpath, srcs in copylist: + for origsrc, abssrc, relsrc, exact in srcs: + copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact) + + if errors: + ui.warn(_('(consider using --after)\n')) + return errors, copied + def service(opts, parentfn=None, initfn=None, runfn=None): '''Run a command as a service.'''