merge git patch code.
--- a/mercurial/commands.py Sat Aug 12 12:30:02 2006 -0700
+++ b/mercurial/commands.py Sat Aug 12 12:47:18 2006 -0700
@@ -10,7 +10,7 @@
from i18n import gettext as _
demandload(globals(), "os re sys signal shutil imp urllib pdb")
demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo")
-demandload(globals(), "fnmatch mdiff random signal tempfile time")
+demandload(globals(), "fnmatch mdiff patch random signal tempfile time")
demandload(globals(), "traceback errno socket version struct atexit sets bz2")
demandload(globals(), "archival cStringIO changegroup email.Parser")
demandload(globals(), "hgweb.server sshserver")
@@ -1825,21 +1825,21 @@
wlock = repo.wlock()
lock = repo.lock()
- for patch in patches:
- pf = os.path.join(d, patch)
+ for p in patches:
+ pf = os.path.join(d, p)
message = None
user = None
date = None
hgpatch = False
- p = email.Parser.Parser()
+ parser = email.Parser.Parser()
if pf == '-':
- msg = p.parse(sys.stdin)
+ msg = parser.parse(sys.stdin)
ui.status(_("applying patch from stdin\n"))
else:
- msg = p.parse(file(pf))
- ui.status(_("applying %s\n") % patch)
+ msg = parser.parse(file(pf))
+ ui.status(_("applying %s\n") % p)
fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
tmpfp = os.fdopen(fd, 'w')
@@ -1907,13 +1907,45 @@
if not diffs_seen:
raise util.Abort(_('no diffs found'))
- files = util.patch(strip, tmpname, ui, cwd=repo.root)
+ files = patch.patch(strip, tmpname, ui, cwd=repo.root)
+ removes = []
if len(files) > 0:
- cfiles = files
+ cfiles = files.keys()
+ copies = []
+ copts = {'after': False, 'force': False}
cwd = repo.getcwd()
if cwd:
- cfiles = [util.pathto(cwd, f) for f in files]
+ cfiles = [util.pathto(cwd, f) for f in files.keys()]
+ for f in files:
+ ctype, gp = files[f]
+ if ctype == 'RENAME':
+ copies.append((gp.oldpath, gp.path, gp.copymod))
+ removes.append(gp.oldpath)
+ elif ctype == 'COPY':
+ copies.append((gp.oldpath, gp.path, gp.copymod))
+ elif ctype == 'DELETE':
+ removes.append(gp.path)
+ for src, dst, after in copies:
+ absdst = os.path.join(repo.root, dst)
+ if not after and os.path.exists(absdst):
+ raise util.Abort(_('patch creates existing file %s') % dst)
+ if cwd:
+ src, dst = [util.pathto(cwd, f) for f in (src, dst)]
+ copts['after'] = after
+ errs, copied = docopy(ui, repo, (src, dst), copts, wlock=wlock)
+ if errs:
+ raise util.Abort(errs)
+ if removes:
+ repo.remove(removes, True, wlock=wlock)
+ for f in files:
+ ctype, gp = files[f]
+ if gp and gp.mode:
+ x = gp.mode & 0100 != 0
+ dst = os.path.join(repo.root, gp.path)
+ util.set_exec(dst, x)
addremove_lock(ui, repo, cfiles, {}, wlock=wlock)
+ files = files.keys()
+ files.extend([r for r in removes if r not in files])
repo.commit(files, message, user, date, wlock=wlock, lock=lock)
finally:
os.unlink(tmpname)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/patch.py Sat Aug 12 12:47:18 2006 -0700
@@ -0,0 +1,166 @@
+# patch.py - patch file parsing routines
+#
+# Copyright 2006 Brendan Cully <brendan@kublai.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+from demandload import demandload
+demandload(globals(), "util")
+demandload(globals(), "os re shutil tempfile")
+
+def readgitpatch(patchname):
+ """extract git-style metadata about patches from <patchname>"""
+ class gitpatch:
+ "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
+ def __init__(self, path):
+ self.path = path
+ self.oldpath = None
+ self.mode = None
+ self.op = 'MODIFY'
+ self.copymod = False
+ self.lineno = 0
+
+ # Filter patch for git information
+ gitre = re.compile('diff --git a/(.*) b/(.*)')
+ pf = file(patchname)
+ gp = None
+ gitpatches = []
+ # Can have a git patch with only metadata, causing patch to complain
+ dopatch = False
+
+ lineno = 0
+ for line in pf:
+ lineno += 1
+ if line.startswith('diff --git'):
+ m = gitre.match(line)
+ if m:
+ if gp:
+ gitpatches.append(gp)
+ src, dst = m.group(1,2)
+ gp = gitpatch(dst)
+ gp.lineno = lineno
+ elif gp:
+ if line.startswith('--- '):
+ if gp.op in ('COPY', 'RENAME'):
+ gp.copymod = True
+ dopatch = 'filter'
+ gitpatches.append(gp)
+ gp = None
+ if not dopatch:
+ dopatch = True
+ continue
+ if line.startswith('rename from '):
+ gp.op = 'RENAME'
+ gp.oldpath = line[12:].rstrip()
+ elif line.startswith('rename to '):
+ gp.path = line[10:].rstrip()
+ elif line.startswith('copy from '):
+ gp.op = 'COPY'
+ gp.oldpath = line[10:].rstrip()
+ elif line.startswith('copy to '):
+ gp.path = line[8:].rstrip()
+ elif line.startswith('deleted file'):
+ gp.op = 'DELETE'
+ elif line.startswith('new file mode '):
+ gp.op = 'ADD'
+ gp.mode = int(line.rstrip()[-3:], 8)
+ elif line.startswith('new mode '):
+ gp.mode = int(line.rstrip()[-3:], 8)
+ if gp:
+ gitpatches.append(gp)
+
+ if not gitpatches:
+ dopatch = True
+
+ return (dopatch, gitpatches)
+
+def dogitpatch(patchname, gitpatches):
+ """Preprocess git patch so that vanilla patch can handle it"""
+ pf = file(patchname)
+ pfline = 1
+
+ fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
+ tmpfp = os.fdopen(fd, 'w')
+
+ try:
+ for i in range(len(gitpatches)):
+ p = gitpatches[i]
+ if not p.copymod:
+ continue
+
+ if os.path.exists(p.path):
+ raise util.Abort(_("cannot create %s: destination already exists") %
+ p.path)
+
+ (src, dst) = [os.path.join(os.getcwd(), n)
+ for n in (p.oldpath, p.path)]
+
+ targetdir = os.path.dirname(dst)
+ if not os.path.isdir(targetdir):
+ os.makedirs(targetdir)
+ try:
+ shutil.copyfile(src, dst)
+ shutil.copymode(src, dst)
+ except shutil.Error, inst:
+ raise util.Abort(str(inst))
+
+ # rewrite patch hunk
+ while pfline < p.lineno:
+ tmpfp.write(pf.readline())
+ pfline += 1
+ tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
+ line = pf.readline()
+ pfline += 1
+ while not line.startswith('--- a/'):
+ tmpfp.write(line)
+ line = pf.readline()
+ pfline += 1
+ tmpfp.write('--- a/%s\n' % p.path)
+
+ line = pf.readline()
+ while line:
+ tmpfp.write(line)
+ line = pf.readline()
+ except:
+ tmpfp.close()
+ os.unlink(patchname)
+ raise
+
+ tmpfp.close()
+ return patchname
+
+def patch(strip, patchname, ui, cwd=None):
+ """apply the patch <patchname> to the working directory.
+ a list of patched files is returned"""
+
+ (dopatch, gitpatches) = readgitpatch(patchname)
+
+ files = {}
+ if dopatch:
+ if dopatch == 'filter':
+ patchname = dogitpatch(patchname, gitpatches)
+ patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
+ args = []
+ if cwd:
+ args.append('-d %s' % util.shellquote(cwd))
+ fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
+ util.shellquote(patchname)))
+
+ if dopatch == 'filter':
+ False and os.unlink(patchname)
+
+ for line in fp:
+ line = line.rstrip()
+ ui.status("%s\n" % line)
+ if line.startswith('patching file '):
+ pf = util.parse_patch_output(line)
+ files.setdefault(pf, (None, None))
+ code = fp.close()
+ if code:
+ raise util.Abort(_("patch command failed: %s") % explain_exit(code)[0])
+
+ for gp in gitpatches:
+ files[gp.path] = (gp.op, gp)
+
+ return files
--- a/mercurial/util.py Sat Aug 12 12:30:02 2006 -0700
+++ b/mercurial/util.py Sat Aug 12 12:47:18 2006 -0700
@@ -95,27 +95,6 @@
return p_name
return default
-def patch(strip, patchname, ui, cwd=None):
- """apply the patch <patchname> to the working directory.
- a list of patched files is returned"""
- patcher = find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
- args = []
- if cwd:
- args.append('-d %s' % shellquote(cwd))
- fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
- shellquote(patchname)))
- files = {}
- for line in fp:
- line = line.rstrip()
- ui.status("%s\n" % line)
- if line.startswith('patching file '):
- pf = parse_patch_output(line)
- files.setdefault(pf, 1)
- code = fp.close()
- if code:
- raise Abort(_("patch command failed: %s") % explain_exit(code)[0])
- return files.keys()
-
def binary(s):
"""return true if a string is binary data using diff's heuristic"""
if s and '\0' in s[:4096]:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-git-import Sat Aug 12 12:47:18 2006 -0700
@@ -0,0 +1,122 @@
+#!/bin/sh
+
+hg init a
+cd a
+
+echo % new file
+hg import -mnew - <<EOF
+diff --git a/new b/new
+new file mode 100644
+index 0000000..7898192
+--- /dev/null
++++ b/new
+@@ -0,0 +1 @@
++a
+EOF
+
+echo % chmod +x
+hg import -msetx - <<EOF
+diff --git a/new b/new
+old mode 100644
+new mode 100755
+EOF
+
+test -x new || echo failed
+
+echo % copy
+hg import -mcopy - <<EOF
+diff --git a/new b/copy
+old mode 100755
+new mode 100644
+similarity index 100%
+copy from new
+copy to copy
+diff --git a/new b/copyx
+similarity index 100%
+copy from new
+copy to copyx
+EOF
+
+test -f copy -a ! -x copy || echo failed
+test -x copyx || echo failed
+cat copy
+hg cat copy
+
+echo % rename
+hg import -mrename - <<EOF
+diff --git a/copy b/rename
+similarity index 100%
+rename from copy
+rename to rename
+EOF
+
+hg locate
+
+echo % delete
+hg import -mdelete - <<EOF
+diff --git a/copyx b/copyx
+deleted file mode 100755
+index 7898192..0000000
+--- a/copyx
++++ /dev/null
+@@ -1 +0,0 @@
+-a
+EOF
+
+hg locate
+test -f copyx && echo failed || true
+
+echo % regular diff
+hg import -mregular - <<EOF
+diff --git a/rename b/rename
+index 7898192..72e1fe3 100644
+--- a/rename
++++ b/rename
+@@ -1 +1,5 @@
+ a
++a
++a
++a
++a
+EOF
+
+echo % copy and modify
+hg import -mcopymod - <<EOF
+diff --git a/rename b/copy2
+similarity index 80%
+copy from rename
+copy to copy2
+index 72e1fe3..b53c148 100644
+--- a/rename
++++ b/copy2
+@@ -1,5 +1,5 @@
+ a
+ a
+-a
++b
+ a
+ a
+EOF
+
+hg cat copy2
+
+echo % rename and modify
+hg import -mrenamemod - <<EOF
+diff --git a/copy2 b/rename2
+similarity index 80%
+rename from copy2
+rename to rename2
+index b53c148..8f81e29 100644
+--- a/copy2
++++ b/rename2
+@@ -1,5 +1,5 @@
+ a
+ a
+ b
+-a
++c
+ a
+EOF
+
+hg locate copy2
+hg cat rename2
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-git-import.out Sat Aug 12 12:47:18 2006 -0700
@@ -0,0 +1,39 @@
+% new file
+applying patch from stdin
+patching file new
+% chmod +x
+applying patch from stdin
+% copy
+applying patch from stdin
+a
+a
+% rename
+applying patch from stdin
+copyx
+new
+rename
+% delete
+applying patch from stdin
+patching file copyx
+new
+rename
+% regular diff
+applying patch from stdin
+patching file rename
+% copy and modify
+applying patch from stdin
+patching file copy2
+a
+a
+b
+a
+a
+% rename and modify
+applying patch from stdin
+patching file rename2
+copy2: No such file or directory
+a
+a
+b
+c
+a