Mercurial > hg
changeset 2865:71e78f2ca5ae
merge git patch code.
author | Vadim Gelfer <vadim.gelfer@gmail.com> |
---|---|
date | Sat, 12 Aug 2006 12:47:18 -0700 |
parents | 345bac2bc4ec (current diff) e2b69dbb2daa (diff) |
children | 2893e51407a4 |
files | mercurial/commands.py mercurial/patch.py mercurial/util.py |
diffstat | 5 files changed, 369 insertions(+), 31 deletions(-) [+] |
line wrap: on
line diff
--- 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