##// END OF EJS Templates
merge git patch code.
Vadim Gelfer -
r2865:71e78f2c merge default
parent child Browse files
Show More
@@ -0,0 +1,166 b''
1 # patch.py - patch file parsing routines
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 #
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
7
8 from demandload import demandload
9 demandload(globals(), "util")
10 demandload(globals(), "os re shutil tempfile")
11
12 def readgitpatch(patchname):
13 """extract git-style metadata about patches from <patchname>"""
14 class gitpatch:
15 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
16 def __init__(self, path):
17 self.path = path
18 self.oldpath = None
19 self.mode = None
20 self.op = 'MODIFY'
21 self.copymod = False
22 self.lineno = 0
23
24 # Filter patch for git information
25 gitre = re.compile('diff --git a/(.*) b/(.*)')
26 pf = file(patchname)
27 gp = None
28 gitpatches = []
29 # Can have a git patch with only metadata, causing patch to complain
30 dopatch = False
31
32 lineno = 0
33 for line in pf:
34 lineno += 1
35 if line.startswith('diff --git'):
36 m = gitre.match(line)
37 if m:
38 if gp:
39 gitpatches.append(gp)
40 src, dst = m.group(1,2)
41 gp = gitpatch(dst)
42 gp.lineno = lineno
43 elif gp:
44 if line.startswith('--- '):
45 if gp.op in ('COPY', 'RENAME'):
46 gp.copymod = True
47 dopatch = 'filter'
48 gitpatches.append(gp)
49 gp = None
50 if not dopatch:
51 dopatch = True
52 continue
53 if line.startswith('rename from '):
54 gp.op = 'RENAME'
55 gp.oldpath = line[12:].rstrip()
56 elif line.startswith('rename to '):
57 gp.path = line[10:].rstrip()
58 elif line.startswith('copy from '):
59 gp.op = 'COPY'
60 gp.oldpath = line[10:].rstrip()
61 elif line.startswith('copy to '):
62 gp.path = line[8:].rstrip()
63 elif line.startswith('deleted file'):
64 gp.op = 'DELETE'
65 elif line.startswith('new file mode '):
66 gp.op = 'ADD'
67 gp.mode = int(line.rstrip()[-3:], 8)
68 elif line.startswith('new mode '):
69 gp.mode = int(line.rstrip()[-3:], 8)
70 if gp:
71 gitpatches.append(gp)
72
73 if not gitpatches:
74 dopatch = True
75
76 return (dopatch, gitpatches)
77
78 def dogitpatch(patchname, gitpatches):
79 """Preprocess git patch so that vanilla patch can handle it"""
80 pf = file(patchname)
81 pfline = 1
82
83 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
84 tmpfp = os.fdopen(fd, 'w')
85
86 try:
87 for i in range(len(gitpatches)):
88 p = gitpatches[i]
89 if not p.copymod:
90 continue
91
92 if os.path.exists(p.path):
93 raise util.Abort(_("cannot create %s: destination already exists") %
94 p.path)
95
96 (src, dst) = [os.path.join(os.getcwd(), n)
97 for n in (p.oldpath, p.path)]
98
99 targetdir = os.path.dirname(dst)
100 if not os.path.isdir(targetdir):
101 os.makedirs(targetdir)
102 try:
103 shutil.copyfile(src, dst)
104 shutil.copymode(src, dst)
105 except shutil.Error, inst:
106 raise util.Abort(str(inst))
107
108 # rewrite patch hunk
109 while pfline < p.lineno:
110 tmpfp.write(pf.readline())
111 pfline += 1
112 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
113 line = pf.readline()
114 pfline += 1
115 while not line.startswith('--- a/'):
116 tmpfp.write(line)
117 line = pf.readline()
118 pfline += 1
119 tmpfp.write('--- a/%s\n' % p.path)
120
121 line = pf.readline()
122 while line:
123 tmpfp.write(line)
124 line = pf.readline()
125 except:
126 tmpfp.close()
127 os.unlink(patchname)
128 raise
129
130 tmpfp.close()
131 return patchname
132
133 def patch(strip, patchname, ui, cwd=None):
134 """apply the patch <patchname> to the working directory.
135 a list of patched files is returned"""
136
137 (dopatch, gitpatches) = readgitpatch(patchname)
138
139 files = {}
140 if dopatch:
141 if dopatch == 'filter':
142 patchname = dogitpatch(patchname, gitpatches)
143 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
144 args = []
145 if cwd:
146 args.append('-d %s' % util.shellquote(cwd))
147 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
148 util.shellquote(patchname)))
149
150 if dopatch == 'filter':
151 False and os.unlink(patchname)
152
153 for line in fp:
154 line = line.rstrip()
155 ui.status("%s\n" % line)
156 if line.startswith('patching file '):
157 pf = util.parse_patch_output(line)
158 files.setdefault(pf, (None, None))
159 code = fp.close()
160 if code:
161 raise util.Abort(_("patch command failed: %s") % explain_exit(code)[0])
162
163 for gp in gitpatches:
164 files[gp.path] = (gp.op, gp)
165
166 return files
@@ -0,0 +1,122 b''
1 #!/bin/sh
2
3 hg init a
4 cd a
5
6 echo % new file
7 hg import -mnew - <<EOF
8 diff --git a/new b/new
9 new file mode 100644
10 index 0000000..7898192
11 --- /dev/null
12 +++ b/new
13 @@ -0,0 +1 @@
14 +a
15 EOF
16
17 echo % chmod +x
18 hg import -msetx - <<EOF
19 diff --git a/new b/new
20 old mode 100644
21 new mode 100755
22 EOF
23
24 test -x new || echo failed
25
26 echo % copy
27 hg import -mcopy - <<EOF
28 diff --git a/new b/copy
29 old mode 100755
30 new mode 100644
31 similarity index 100%
32 copy from new
33 copy to copy
34 diff --git a/new b/copyx
35 similarity index 100%
36 copy from new
37 copy to copyx
38 EOF
39
40 test -f copy -a ! -x copy || echo failed
41 test -x copyx || echo failed
42 cat copy
43 hg cat copy
44
45 echo % rename
46 hg import -mrename - <<EOF
47 diff --git a/copy b/rename
48 similarity index 100%
49 rename from copy
50 rename to rename
51 EOF
52
53 hg locate
54
55 echo % delete
56 hg import -mdelete - <<EOF
57 diff --git a/copyx b/copyx
58 deleted file mode 100755
59 index 7898192..0000000
60 --- a/copyx
61 +++ /dev/null
62 @@ -1 +0,0 @@
63 -a
64 EOF
65
66 hg locate
67 test -f copyx && echo failed || true
68
69 echo % regular diff
70 hg import -mregular - <<EOF
71 diff --git a/rename b/rename
72 index 7898192..72e1fe3 100644
73 --- a/rename
74 +++ b/rename
75 @@ -1 +1,5 @@
76 a
77 +a
78 +a
79 +a
80 +a
81 EOF
82
83 echo % copy and modify
84 hg import -mcopymod - <<EOF
85 diff --git a/rename b/copy2
86 similarity index 80%
87 copy from rename
88 copy to copy2
89 index 72e1fe3..b53c148 100644
90 --- a/rename
91 +++ b/copy2
92 @@ -1,5 +1,5 @@
93 a
94 a
95 -a
96 +b
97 a
98 a
99 EOF
100
101 hg cat copy2
102
103 echo % rename and modify
104 hg import -mrenamemod - <<EOF
105 diff --git a/copy2 b/rename2
106 similarity index 80%
107 rename from copy2
108 rename to rename2
109 index b53c148..8f81e29 100644
110 --- a/copy2
111 +++ b/rename2
112 @@ -1,5 +1,5 @@
113 a
114 a
115 b
116 -a
117 +c
118 a
119 EOF
120
121 hg locate copy2
122 hg cat rename2
@@ -0,0 +1,39 b''
1 % new file
2 applying patch from stdin
3 patching file new
4 % chmod +x
5 applying patch from stdin
6 % copy
7 applying patch from stdin
8 a
9 a
10 % rename
11 applying patch from stdin
12 copyx
13 new
14 rename
15 % delete
16 applying patch from stdin
17 patching file copyx
18 new
19 rename
20 % regular diff
21 applying patch from stdin
22 patching file rename
23 % copy and modify
24 applying patch from stdin
25 patching file copy2
26 a
27 a
28 b
29 a
30 a
31 % rename and modify
32 applying patch from stdin
33 patching file rename2
34 copy2: No such file or directory
35 a
36 a
37 b
38 c
39 a
@@ -10,7 +10,7 b' from node import *'
10 from i18n import gettext as _
10 from i18n import gettext as _
11 demandload(globals(), "os re sys signal shutil imp urllib pdb")
11 demandload(globals(), "os re sys signal shutil imp urllib pdb")
12 demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo")
12 demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo")
13 demandload(globals(), "fnmatch mdiff random signal tempfile time")
13 demandload(globals(), "fnmatch mdiff patch random signal tempfile time")
14 demandload(globals(), "traceback errno socket version struct atexit sets bz2")
14 demandload(globals(), "traceback errno socket version struct atexit sets bz2")
15 demandload(globals(), "archival cStringIO changegroup email.Parser")
15 demandload(globals(), "archival cStringIO changegroup email.Parser")
16 demandload(globals(), "hgweb.server sshserver")
16 demandload(globals(), "hgweb.server sshserver")
@@ -1825,21 +1825,21 b' def import_(ui, repo, patch1, *patches, '
1825 wlock = repo.wlock()
1825 wlock = repo.wlock()
1826 lock = repo.lock()
1826 lock = repo.lock()
1827
1827
1828 for patch in patches:
1828 for p in patches:
1829 pf = os.path.join(d, patch)
1829 pf = os.path.join(d, p)
1830
1830
1831 message = None
1831 message = None
1832 user = None
1832 user = None
1833 date = None
1833 date = None
1834 hgpatch = False
1834 hgpatch = False
1835
1835
1836 p = email.Parser.Parser()
1836 parser = email.Parser.Parser()
1837 if pf == '-':
1837 if pf == '-':
1838 msg = p.parse(sys.stdin)
1838 msg = parser.parse(sys.stdin)
1839 ui.status(_("applying patch from stdin\n"))
1839 ui.status(_("applying patch from stdin\n"))
1840 else:
1840 else:
1841 msg = p.parse(file(pf))
1841 msg = parser.parse(file(pf))
1842 ui.status(_("applying %s\n") % patch)
1842 ui.status(_("applying %s\n") % p)
1843
1843
1844 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
1844 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
1845 tmpfp = os.fdopen(fd, 'w')
1845 tmpfp = os.fdopen(fd, 'w')
@@ -1907,13 +1907,45 b' def import_(ui, repo, patch1, *patches, '
1907 if not diffs_seen:
1907 if not diffs_seen:
1908 raise util.Abort(_('no diffs found'))
1908 raise util.Abort(_('no diffs found'))
1909
1909
1910 files = util.patch(strip, tmpname, ui, cwd=repo.root)
1910 files = patch.patch(strip, tmpname, ui, cwd=repo.root)
1911 removes = []
1911 if len(files) > 0:
1912 if len(files) > 0:
1912 cfiles = files
1913 cfiles = files.keys()
1914 copies = []
1915 copts = {'after': False, 'force': False}
1913 cwd = repo.getcwd()
1916 cwd = repo.getcwd()
1914 if cwd:
1917 if cwd:
1915 cfiles = [util.pathto(cwd, f) for f in files]
1918 cfiles = [util.pathto(cwd, f) for f in files.keys()]
1919 for f in files:
1920 ctype, gp = files[f]
1921 if ctype == 'RENAME':
1922 copies.append((gp.oldpath, gp.path, gp.copymod))
1923 removes.append(gp.oldpath)
1924 elif ctype == 'COPY':
1925 copies.append((gp.oldpath, gp.path, gp.copymod))
1926 elif ctype == 'DELETE':
1927 removes.append(gp.path)
1928 for src, dst, after in copies:
1929 absdst = os.path.join(repo.root, dst)
1930 if not after and os.path.exists(absdst):
1931 raise util.Abort(_('patch creates existing file %s') % dst)
1932 if cwd:
1933 src, dst = [util.pathto(cwd, f) for f in (src, dst)]
1934 copts['after'] = after
1935 errs, copied = docopy(ui, repo, (src, dst), copts, wlock=wlock)
1936 if errs:
1937 raise util.Abort(errs)
1938 if removes:
1939 repo.remove(removes, True, wlock=wlock)
1940 for f in files:
1941 ctype, gp = files[f]
1942 if gp and gp.mode:
1943 x = gp.mode & 0100 != 0
1944 dst = os.path.join(repo.root, gp.path)
1945 util.set_exec(dst, x)
1916 addremove_lock(ui, repo, cfiles, {}, wlock=wlock)
1946 addremove_lock(ui, repo, cfiles, {}, wlock=wlock)
1947 files = files.keys()
1948 files.extend([r for r in removes if r not in files])
1917 repo.commit(files, message, user, date, wlock=wlock, lock=lock)
1949 repo.commit(files, message, user, date, wlock=wlock, lock=lock)
1918 finally:
1950 finally:
1919 os.unlink(tmpname)
1951 os.unlink(tmpname)
@@ -95,27 +95,6 b' def find_in_path(name, path, default=Non'
95 return p_name
95 return p_name
96 return default
96 return default
97
97
98 def patch(strip, patchname, ui, cwd=None):
99 """apply the patch <patchname> to the working directory.
100 a list of patched files is returned"""
101 patcher = find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
102 args = []
103 if cwd:
104 args.append('-d %s' % shellquote(cwd))
105 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
106 shellquote(patchname)))
107 files = {}
108 for line in fp:
109 line = line.rstrip()
110 ui.status("%s\n" % line)
111 if line.startswith('patching file '):
112 pf = parse_patch_output(line)
113 files.setdefault(pf, 1)
114 code = fp.close()
115 if code:
116 raise Abort(_("patch command failed: %s") % explain_exit(code)[0])
117 return files.keys()
118
119 def binary(s):
98 def binary(s):
120 """return true if a string is binary data using diff's heuristic"""
99 """return true if a string is binary data using diff's heuristic"""
121 if s and '\0' in s[:4096]:
100 if s and '\0' in s[:4096]:
General Comments 0
You need to be logged in to leave comments. Login now