patch: move updatedir() from cmdutil into patch
Also, create an artificial wdutil.py to avoid import cycles between patch.py
and cmdutil.py.
--- a/hgext/mq.py Sun May 08 17:48:29 2011 +0200
+++ b/hgext/mq.py Sun May 08 17:48:30 2011 +0200
@@ -618,7 +618,7 @@
fuzz = patchmod.patch(patchfile, self.ui, strip=1,
cwd=repo.root, files=files, eolmode=None)
finally:
- files = cmdutil.updatedir(self.ui, repo, files)
+ files = patchmod.updatedir(self.ui, repo, files)
return (True, files, fuzz)
except Exception, inst:
self.ui.note(str(inst) + '\n')
--- a/hgext/record.py Sun May 08 17:48:29 2011 +0200
+++ b/hgext/record.py Sun May 08 17:48:30 2011 +0200
@@ -482,7 +482,7 @@
patch.internalpatch(fp, ui, 1, repo.root, files=pfiles,
eolmode=None)
finally:
- cmdutil.updatedir(ui, repo, pfiles)
+ patch.updatedir(ui, repo, pfiles)
except patch.PatchError, err:
raise util.Abort(str(err))
del fp
--- a/hgext/transplant.py Sun May 08 17:48:29 2011 +0200
+++ b/hgext/transplant.py Sun May 08 17:48:30 2011 +0200
@@ -232,7 +232,7 @@
% revlog.hex(node))
return None
finally:
- files = cmdutil.updatedir(self.ui, repo, files)
+ files = patch.updatedir(self.ui, repo, files)
except Exception, inst:
seriespath = os.path.join(self.path, 'series')
if os.path.exists(seriespath):
--- a/mercurial/cmdutil.py Sun May 08 17:48:29 2011 +0200
+++ b/mercurial/cmdutil.py Sun May 08 17:48:30 2011 +0200
@@ -8,10 +8,17 @@
from node import hex, nullid, nullrev, short
from i18n import _
import os, sys, errno, re, glob, tempfile
-import util, scmutil, templater, patch, error, templatekw
+import util, scmutil, templater, patch, error, templatekw, wdutil
import match as matchmod
import similar, revset, subrepo
+expandpats = wdutil.expandpats
+match = wdutil.match
+matchall = wdutil.matchall
+matchfiles = wdutil.matchfiles
+addremove = wdutil.addremove
+dirstatecopy = wdutil.dirstatecopy
+
revrangesep = ':'
def parsealiases(cmd):
@@ -243,157 +250,6 @@
pathname),
mode)
-def expandpats(pats):
- if not util.expandglobs:
- return list(pats)
- ret = []
- for p in pats:
- kind, name = matchmod._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 pats == ("",):
- pats = []
- if not globbed and default == 'relpath':
- pats = expandpats(pats or [])
- m = matchmod.match(repo.root, repo.getcwd(), pats,
- opts.get('include'), opts.get('exclude'), default,
- auditor=repo.auditor)
- def badfn(f, msg):
- repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
- m.bad = badfn
- return m
-
-def matchall(repo):
- return matchmod.always(repo.root, repo.getcwd())
-
-def matchfiles(repo, files):
- return matchmod.exact(repo.root, repo.getcwd(), files)
-
-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 = scmutil.pathauditor(repo.root)
- m = match(repo, pats, opts)
- for abs in repo.walk(m):
- target = repo.wjoin(abs)
- good = True
- try:
- audit_path(abs)
- except (OSError, util.Abort):
- 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 os.path.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)
- copies = {}
- if similarity > 0:
- for old, new, score in similar.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))
- copies[new] = old
-
- if not dry_run:
- wctx = repo[None]
- wlock = repo.wlock()
- try:
- wctx.remove(deleted)
- wctx.add(unknown)
- for new, old in copies.iteritems():
- wctx.copy(old, new)
- finally:
- wlock.release()
-
-def updatedir(ui, repo, patches, similarity=0):
- '''Update dirstate after patch application according to metadata'''
- if not patches:
- return []
- copies = []
- removes = set()
- cfiles = patches.keys()
- cwd = repo.getcwd()
- if cwd:
- cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
- for f in patches:
- gp = patches[f]
- if not gp:
- continue
- if gp.op == 'RENAME':
- copies.append((gp.oldpath, gp.path))
- removes.add(gp.oldpath)
- elif gp.op == 'COPY':
- copies.append((gp.oldpath, gp.path))
- elif gp.op == 'DELETE':
- removes.add(gp.path)
-
- wctx = repo[None]
- for src, dst in copies:
- dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
- if (not similarity) and removes:
- wctx.remove(sorted(removes), True)
-
- for f in patches:
- gp = patches[f]
- if gp and gp.mode:
- islink, isexec = gp.mode
- dst = repo.wjoin(gp.path)
- # patch won't create empty files
- if gp.op == 'ADD' and not os.path.lexists(dst):
- flags = (isexec and 'x' or '') + (islink and 'l' or '')
- repo.wwrite(gp.path, '', flags)
- util.setflags(dst, islink, isexec)
- addremove(repo, cfiles, similarity=similarity)
- files = patches.keys()
- files.extend([r for r in removes if r not in files])
- return sorted(files)
-
-def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
- """Update the dirstate to reflect the intent of copying src to dst. For
- different reasons it might not end with dst being marked as copied from src.
- """
- origsrc = repo.dirstate.copied(src) or src
- if dst == origsrc: # copying back a copy?
- if repo.dirstate[dst] not in 'mn' and not dryrun:
- repo.dirstate.normallookup(dst)
- else:
- if repo.dirstate[origsrc] == 'a' and origsrc == src:
- 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), repo.pathto(dst, cwd)))
- if repo.dirstate[dst] in '?r' and not dryrun:
- wctx.add([dst])
- elif not dryrun:
- wctx.copy(origsrc, dst)
-
def copy(ui, repo, pats, opts, rename=False):
# called with the repo lock held
#
--- a/mercurial/commands.py Sun May 08 17:48:29 2011 +0200
+++ b/mercurial/commands.py Sun May 08 17:48:30 2011 +0200
@@ -2627,8 +2627,8 @@
patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
files=files, eolmode=None)
finally:
- files = cmdutil.updatedir(ui, repo, files,
- similarity=sim / 100.0)
+ files = patch.updatedir(ui, repo, files,
+ similarity=sim / 100.0)
if opts.get('no_commit'):
if message:
msgs.append(message)
--- a/mercurial/patch.py Sun May 08 17:48:29 2011 +0200
+++ b/mercurial/patch.py Sun May 08 17:48:30 2011 +0200
@@ -11,7 +11,7 @@
from i18n import _
from node import hex, nullid, short
-import base85, mdiff, scmutil, util, diffhelpers, copies, encoding
+import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, wdutil
gitre = re.compile('diff --git a/(.*) b/(.*)')
@@ -1157,6 +1157,49 @@
return -1
return err
+def updatedir(ui, repo, patches, similarity=0):
+ '''Update dirstate after patch application according to metadata'''
+ if not patches:
+ return []
+ copies = []
+ removes = set()
+ cfiles = patches.keys()
+ cwd = repo.getcwd()
+ if cwd:
+ cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
+ for f in patches:
+ gp = patches[f]
+ if not gp:
+ continue
+ if gp.op == 'RENAME':
+ copies.append((gp.oldpath, gp.path))
+ removes.add(gp.oldpath)
+ elif gp.op == 'COPY':
+ copies.append((gp.oldpath, gp.path))
+ elif gp.op == 'DELETE':
+ removes.add(gp.path)
+
+ wctx = repo[None]
+ for src, dst in copies:
+ wdutil.dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
+ if (not similarity) and removes:
+ wctx.remove(sorted(removes), True)
+
+ for f in patches:
+ gp = patches[f]
+ if gp and gp.mode:
+ islink, isexec = gp.mode
+ dst = repo.wjoin(gp.path)
+ # patch won't create empty files
+ if gp.op == 'ADD' and not os.path.lexists(dst):
+ flags = (isexec and 'x' or '') + (islink and 'l' or '')
+ repo.wwrite(gp.path, '', flags)
+ util.setflags(dst, islink, isexec)
+ wdutil.addremove(repo, cfiles, similarity=similarity)
+ files = patches.keys()
+ files.extend([r for r in removes if r not in files])
+ return sorted(files)
+
def _externalpatch(patcher, patchname, ui, strip, cwd, files):
"""use <patcher> to apply <patchname> to the working directory.
returns whether patch was applied with fuzz factor."""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/wdutil.py Sun May 08 17:48:30 2011 +0200
@@ -0,0 +1,162 @@
+# wdutil.py - working dir utilities
+#
+# Copyright 2011 Patrick Mezard <pmezard@gmail.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+import glob, os
+import util, similar, scmutil
+import match as matchmod
+from i18n import _
+
+def expandpats(pats):
+ if not util.expandglobs:
+ return list(pats)
+ ret = []
+ for p in pats:
+ kind, name = matchmod._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 pats == ("",):
+ pats = []
+ if not globbed and default == 'relpath':
+ pats = expandpats(pats or [])
+ m = matchmod.match(repo.root, repo.getcwd(), pats,
+ opts.get('include'), opts.get('exclude'), default,
+ auditor=repo.auditor)
+ def badfn(f, msg):
+ repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
+ m.bad = badfn
+ return m
+
+def matchall(repo):
+ return matchmod.always(repo.root, repo.getcwd())
+
+def matchfiles(repo, files):
+ return matchmod.exact(repo.root, repo.getcwd(), files)
+
+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 = scmutil.pathauditor(repo.root)
+ m = match(repo, pats, opts)
+ for abs in repo.walk(m):
+ target = repo.wjoin(abs)
+ good = True
+ try:
+ audit_path(abs)
+ except (OSError, util.Abort):
+ 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 os.path.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)
+ copies = {}
+ if similarity > 0:
+ for old, new, score in similar.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))
+ copies[new] = old
+
+ if not dry_run:
+ wctx = repo[None]
+ wlock = repo.wlock()
+ try:
+ wctx.remove(deleted)
+ wctx.add(unknown)
+ for new, old in copies.iteritems():
+ wctx.copy(old, new)
+ finally:
+ wlock.release()
+
+def updatedir(ui, repo, patches, similarity=0):
+ '''Update dirstate after patch application according to metadata'''
+ if not patches:
+ return []
+ copies = []
+ removes = set()
+ cfiles = patches.keys()
+ cwd = repo.getcwd()
+ if cwd:
+ cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
+ for f in patches:
+ gp = patches[f]
+ if not gp:
+ continue
+ if gp.op == 'RENAME':
+ copies.append((gp.oldpath, gp.path))
+ removes.add(gp.oldpath)
+ elif gp.op == 'COPY':
+ copies.append((gp.oldpath, gp.path))
+ elif gp.op == 'DELETE':
+ removes.add(gp.path)
+
+ wctx = repo[None]
+ for src, dst in copies:
+ dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
+ if (not similarity) and removes:
+ wctx.remove(sorted(removes), True)
+
+ for f in patches:
+ gp = patches[f]
+ if gp and gp.mode:
+ islink, isexec = gp.mode
+ dst = repo.wjoin(gp.path)
+ # patch won't create empty files
+ if gp.op == 'ADD' and not os.path.lexists(dst):
+ flags = (isexec and 'x' or '') + (islink and 'l' or '')
+ repo.wwrite(gp.path, '', flags)
+ util.setflags(dst, islink, isexec)
+ addremove(repo, cfiles, similarity=similarity)
+ files = patches.keys()
+ files.extend([r for r in removes if r not in files])
+ return sorted(files)
+
+def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
+ """Update the dirstate to reflect the intent of copying src to dst. For
+ different reasons it might not end with dst being marked as copied from src.
+ """
+ origsrc = repo.dirstate.copied(src) or src
+ if dst == origsrc: # copying back a copy?
+ if repo.dirstate[dst] not in 'mn' and not dryrun:
+ repo.dirstate.normallookup(dst)
+ else:
+ if repo.dirstate[origsrc] == 'a' and origsrc == src:
+ 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), repo.pathto(dst, cwd)))
+ if repo.dirstate[dst] in '?r' and not dryrun:
+ wctx.add([dst])
+ elif not dryrun:
+ wctx.copy(origsrc, dst)