##// END OF EJS Templates
Move patch-related code into its own module.
Brendan Cully -
r2861:0f08f2c0 default
parent child Browse files
Show More
@@ -0,0 +1,165 b''
1 # patch.py - patch file parsing routines
2 #
3 # This software may be used and distributed according to the terms
4 # of the GNU General Public License, incorporated herein by reference.
5
6 from demandload import demandload
7 demandload(globals(), "util")
8 demandload(globals(), "os re shutil tempfile")
9
10 def readgitpatch(patchname):
11 """extract git-style metadata about patches from <patchname>"""
12 class gitpatch:
13 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
14 def __init__(self, path):
15 self.path = path
16 self.oldpath = None
17 self.mode = None
18 self.op = 'MODIFY'
19 self.copymod = False
20 self.lineno = 0
21
22 # Filter patch for git information
23 gitre = re.compile('diff --git a/(.*) b/(.*)')
24 pf = file(patchname)
25 gp = None
26 gitpatches = []
27 # Can have a git patch with only metadata, causing patch to complain
28 dopatch = False
29
30 lineno = 0
31 for line in pf:
32 lineno += 1
33 if line.startswith('diff --git'):
34 m = gitre.match(line)
35 if m:
36 if gp:
37 gitpatches.append(gp)
38 src, dst = m.group(1,2)
39 gp = gitpatch(dst)
40 gp.lineno = lineno
41 elif gp:
42 if line.startswith('--- '):
43 if gp.op in ('COPY', 'RENAME'):
44 gp.copymod = True
45 dopatch = 'filter'
46 gitpatches.append(gp)
47 gp = None
48 if not dopatch:
49 dopatch = True
50 continue
51 if line.startswith('rename from '):
52 gp.op = 'RENAME'
53 gp.oldpath = line[12:].rstrip()
54 elif line.startswith('rename to '):
55 gp.path = line[10:].rstrip()
56 elif line.startswith('copy from '):
57 gp.op = 'COPY'
58 gp.oldpath = line[10:].rstrip()
59 elif line.startswith('copy to '):
60 gp.path = line[8:].rstrip()
61 elif line.startswith('deleted file'):
62 gp.op = 'DELETE'
63 elif line.startswith('new file mode '):
64 gp.op = 'ADD'
65 gp.mode = int(line.rstrip()[-3:], 8)
66 elif line.startswith('new mode '):
67 gp.mode = int(line.rstrip()[-3:], 8)
68 if gp:
69 gitpatches.append(gp)
70
71 if not gitpatches:
72 dopatch = True
73
74 return (dopatch, gitpatches)
75
76 def dogitpatch(patchname, gitpatches):
77 """Preprocess git patch so that vanilla patch can handle it"""
78 pf = file(patchname)
79 pfline = 1
80
81 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
82 tmpfp = os.fdopen(fd, 'w')
83
84 try:
85 for i in range(len(gitpatches)):
86 p = gitpatches[i]
87 if not p.copymod:
88 continue
89
90 if os.path.exists(p.path):
91 raise util.Abort(_("cannot create %s: destination already exists") %
92 p.path)
93
94 (src, dst) = [os.path.join(os.getcwd(), n)
95 for n in (p.oldpath, p.path)]
96
97 print "copying %s to %s" % (src, dst)
98 targetdir = os.path.dirname(dst)
99 if not os.path.isdir(targetdir):
100 os.makedirs(targetdir)
101 try:
102 shutil.copyfile(src, dst)
103 shutil.copymode(src, dst)
104 except shutil.Error, inst:
105 raise util.Abort(str(inst))
106
107 # rewrite patch hunk
108 while pfline < p.lineno:
109 tmpfp.write(pf.readline())
110 pfline += 1
111 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
112 line = pf.readline()
113 pfline += 1
114 while not line.startswith('--- a/'):
115 tmpfp.write(line)
116 line = pf.readline()
117 pfline += 1
118 tmpfp.write('--- a/%s\n' % p.path)
119
120 line = pf.readline()
121 while line:
122 tmpfp.write(line)
123 line = pf.readline()
124 except:
125 tmpfp.close()
126 os.unlink(patchname)
127 raise
128
129 tmpfp.close()
130 return patchname
131
132 def patch(strip, patchname, ui, cwd=None):
133 """apply the patch <patchname> to the working directory.
134 a list of patched files is returned"""
135
136 (dopatch, gitpatches) = readgitpatch(patchname)
137
138 files = {}
139 if dopatch:
140 if dopatch == 'filter':
141 patchname = dogitpatch(patchname, gitpatches)
142 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
143 args = []
144 if cwd:
145 args.append('-d %s' % util.shellquote(cwd))
146 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
147 util.shellquote(patchname)))
148
149 if dopatch == 'filter':
150 False and os.unlink(patchname)
151
152 for line in fp:
153 line = line.rstrip()
154 ui.status("%s\n" % line)
155 if line.startswith('patching file '):
156 pf = util.parse_patch_output(line)
157 files.setdefault(pf, (None, None))
158 code = fp.close()
159 if code:
160 raise util.Abort(_("patch command failed: %s") % explain_exit(code)[0])
161
162 for gp in gitpatches:
163 files[gp.path] = (gp.op, gp)
164
165 return files
@@ -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")
@@ -1826,21 +1826,21 b' def import_(ui, repo, patch1, *patches, '
1826 lock = repo.lock()
1826 lock = repo.lock()
1827
1827
1828 wlock = repo.wlock()
1828 wlock = repo.wlock()
1829 for patch in patches:
1829 for p in patches:
1830 pf = os.path.join(d, patch)
1830 pf = os.path.join(d, p)
1831
1831
1832 message = None
1832 message = None
1833 user = None
1833 user = None
1834 date = None
1834 date = None
1835 hgpatch = False
1835 hgpatch = False
1836
1836
1837 p = email.Parser.Parser()
1837 parser = email.Parser.Parser()
1838 if pf == '-':
1838 if pf == '-':
1839 msg = p.parse(sys.stdin)
1839 msg = parser.parse(sys.stdin)
1840 ui.status(_("applying patch from stdin\n"))
1840 ui.status(_("applying patch from stdin\n"))
1841 else:
1841 else:
1842 msg = p.parse(file(pf))
1842 msg = parser.parse(file(pf))
1843 ui.status(_("applying %s\n") % patch)
1843 ui.status(_("applying %s\n") % p)
1844
1844
1845 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
1845 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
1846 tmpfp = os.fdopen(fd, 'w')
1846 tmpfp = os.fdopen(fd, 'w')
@@ -1908,7 +1908,7 b' def import_(ui, repo, patch1, *patches, '
1908 if not diffs_seen:
1908 if not diffs_seen:
1909 raise util.Abort(_('no diffs found'))
1909 raise util.Abort(_('no diffs found'))
1910
1910
1911 files = util.patch(strip, tmpname, ui, cwd=repo.root)
1911 files = patch.patch(strip, tmpname, ui, cwd=repo.root)
1912 removes = []
1912 removes = []
1913 if len(files) > 0:
1913 if len(files) > 0:
1914 cfiles = files.keys()
1914 cfiles = files.keys()
@@ -93,163 +93,6 b' def find_in_path(name, path, default=Non'
93 return p_name
93 return p_name
94 return default
94 return default
95
95
96 def readgitpatch(patchname):
97 """extract git-style metadata about patches from <patchname>"""
98 class gitpatch:
99 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
100 def __init__(self, path):
101 self.path = path
102 self.oldpath = None
103 self.mode = None
104 self.op = 'MODIFY'
105 self.copymod = False
106 self.lineno = 0
107
108 # Filter patch for git information
109 gitre = re.compile('diff --git a/(.*) b/(.*)')
110 pf = file(patchname)
111 gp = None
112 gitpatches = []
113 # Can have a git patch with only metadata, causing patch to complain
114 dopatch = False
115
116 lineno = 0
117 for line in pf:
118 lineno += 1
119 if line.startswith('diff --git'):
120 m = gitre.match(line)
121 if m:
122 if gp:
123 gitpatches.append(gp)
124 src, dst = m.group(1,2)
125 gp = gitpatch(dst)
126 gp.lineno = lineno
127 elif gp:
128 if line.startswith('--- '):
129 if gp.op in ('COPY', 'RENAME'):
130 gp.copymod = True
131 dopatch = 'filter'
132 gitpatches.append(gp)
133 gp = None
134 if not dopatch:
135 dopatch = True
136 continue
137 if line.startswith('rename from '):
138 gp.op = 'RENAME'
139 gp.oldpath = line[12:].rstrip()
140 elif line.startswith('rename to '):
141 gp.path = line[10:].rstrip()
142 elif line.startswith('copy from '):
143 gp.op = 'COPY'
144 gp.oldpath = line[10:].rstrip()
145 elif line.startswith('copy to '):
146 gp.path = line[8:].rstrip()
147 elif line.startswith('deleted file'):
148 gp.op = 'DELETE'
149 elif line.startswith('new file mode '):
150 gp.op = 'ADD'
151 gp.mode = int(line.rstrip()[-3:], 8)
152 elif line.startswith('new mode '):
153 gp.mode = int(line.rstrip()[-3:], 8)
154 if gp:
155 gitpatches.append(gp)
156
157 if not gitpatches:
158 dopatch = True
159
160 return (dopatch, gitpatches)
161
162 def dogitpatch(patchname, gitpatches):
163 """Preprocess git patch so that vanilla patch can handle it"""
164 pf = file(patchname)
165 pfline = 1
166
167 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
168 tmpfp = os.fdopen(fd, 'w')
169
170 try:
171 for i in range(len(gitpatches)):
172 p = gitpatches[i]
173 if not p.copymod:
174 continue
175
176 if os.path.exists(p.path):
177 raise Abort(_("cannot create %s: destination already exists") %
178 p.path)
179
180 (src, dst) = [os.path.join(os.getcwd(), n)
181 for n in (p.oldpath, p.path)]
182
183 print "copying %s to %s" % (src, dst)
184 targetdir = os.path.dirname(dst)
185 if not os.path.isdir(targetdir):
186 os.makedirs(targetdir)
187 try:
188 shutil.copyfile(src, dst)
189 shutil.copymode(src, dst)
190 except shutil.Error, inst:
191 raise Abort(str(inst))
192
193 # rewrite patch hunk
194 while pfline < p.lineno:
195 tmpfp.write(pf.readline())
196 pfline += 1
197 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
198 line = pf.readline()
199 pfline += 1
200 while not line.startswith('--- a/'):
201 tmpfp.write(line)
202 line = pf.readline()
203 pfline += 1
204 tmpfp.write('--- a/%s\n' % p.path)
205
206 line = pf.readline()
207 while line:
208 tmpfp.write(line)
209 line = pf.readline()
210 except:
211 tmpfp.close()
212 os.unlink(patchname)
213 raise
214
215 tmpfp.close()
216 return patchname
217
218 def patch(strip, patchname, ui, cwd=None):
219 """apply the patch <patchname> to the working directory.
220 a list of patched files is returned"""
221
222 (dopatch, gitpatches) = readgitpatch(patchname)
223
224 files = {}
225 if dopatch:
226 if dopatch == 'filter':
227 patchname = dogitpatch(patchname, gitpatches)
228 patcher = find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
229 args = []
230 if cwd:
231 args.append('-d %s' % shellquote(cwd))
232 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
233 shellquote(patchname)))
234
235 if dopatch == 'filter':
236 False and os.unlink(patchname)
237
238 for line in fp:
239 line = line.rstrip()
240 ui.status("%s\n" % line)
241 if line.startswith('patching file '):
242 pf = parse_patch_output(line)
243 files.setdefault(pf, (None, None))
244 code = fp.close()
245 if code:
246 raise Abort(_("patch command failed: %s") % explain_exit(code)[0])
247
248 for gp in gitpatches:
249 files[gp.path] = (gp.op, gp)
250
251 return files
252
253 def binary(s):
96 def binary(s):
254 """return true if a string is binary data using diff's heuristic"""
97 """return true if a string is binary data using diff's heuristic"""
255 if s and '\0' in s[:4096]:
98 if s and '\0' in s[:4096]:
General Comments 0
You need to be logged in to leave comments. Login now