##// END OF EJS Templates
util: qualify name properly.
Vadim Gelfer -
r2868:9a2a481e default
parent child Browse files
Show More
@@ -1,250 +1,251 b''
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from demandload import demandload
8 from demandload import demandload
9 from i18n import gettext as _
9 from i18n import gettext as _
10 demandload(globals(), "util")
10 demandload(globals(), "util")
11 demandload(globals(), "cStringIO email.Parser os re shutil tempfile")
11 demandload(globals(), "cStringIO email.Parser os re shutil tempfile")
12
12
13 def extract(ui, fileobj):
13 def extract(ui, fileobj):
14 '''extract patch from data read from fileobj.
14 '''extract patch from data read from fileobj.
15
15
16 patch can be normal patch or contained in email message.
16 patch can be normal patch or contained in email message.
17
17
18 return tuple (filename, message, user, date). any item in returned
18 return tuple (filename, message, user, date). any item in returned
19 tuple can be None. if filename is None, fileobj did not contain
19 tuple can be None. if filename is None, fileobj did not contain
20 patch. caller must unlink filename when done.'''
20 patch. caller must unlink filename when done.'''
21
21
22 # attempt to detect the start of a patch
22 # attempt to detect the start of a patch
23 # (this heuristic is borrowed from quilt)
23 # (this heuristic is borrowed from quilt)
24 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
24 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
25 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
25 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
26 '(---|\*\*\*)[ \t])', re.MULTILINE)
26 '(---|\*\*\*)[ \t])', re.MULTILINE)
27
27
28 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
28 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
29 tmpfp = os.fdopen(fd, 'w')
29 tmpfp = os.fdopen(fd, 'w')
30 try:
30 try:
31 hgpatch = False
31 hgpatch = False
32
32
33 msg = email.Parser.Parser().parse(fileobj)
33 msg = email.Parser.Parser().parse(fileobj)
34
34
35 message = msg['Subject']
35 message = msg['Subject']
36 user = msg['From']
36 user = msg['From']
37 # should try to parse msg['Date']
37 # should try to parse msg['Date']
38 date = None
38 date = None
39
39
40 if message:
40 if message:
41 message = message.replace('\n\t', ' ')
41 message = message.replace('\n\t', ' ')
42 ui.debug('Subject: %s\n' % message)
42 ui.debug('Subject: %s\n' % message)
43 if user:
43 if user:
44 ui.debug('From: %s\n' % user)
44 ui.debug('From: %s\n' % user)
45 diffs_seen = 0
45 diffs_seen = 0
46 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
46 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
47
47
48 for part in msg.walk():
48 for part in msg.walk():
49 content_type = part.get_content_type()
49 content_type = part.get_content_type()
50 ui.debug('Content-Type: %s\n' % content_type)
50 ui.debug('Content-Type: %s\n' % content_type)
51 if content_type not in ok_types:
51 if content_type not in ok_types:
52 continue
52 continue
53 payload = part.get_payload(decode=True)
53 payload = part.get_payload(decode=True)
54 m = diffre.search(payload)
54 m = diffre.search(payload)
55 if m:
55 if m:
56 ui.debug(_('found patch at byte %d\n') % m.start(0))
56 ui.debug(_('found patch at byte %d\n') % m.start(0))
57 diffs_seen += 1
57 diffs_seen += 1
58 cfp = cStringIO.StringIO()
58 cfp = cStringIO.StringIO()
59 if message:
59 if message:
60 cfp.write(message)
60 cfp.write(message)
61 cfp.write('\n')
61 cfp.write('\n')
62 for line in payload[:m.start(0)].splitlines():
62 for line in payload[:m.start(0)].splitlines():
63 if line.startswith('# HG changeset patch'):
63 if line.startswith('# HG changeset patch'):
64 ui.debug(_('patch generated by hg export\n'))
64 ui.debug(_('patch generated by hg export\n'))
65 hgpatch = True
65 hgpatch = True
66 # drop earlier commit message content
66 # drop earlier commit message content
67 cfp.seek(0)
67 cfp.seek(0)
68 cfp.truncate()
68 cfp.truncate()
69 elif hgpatch:
69 elif hgpatch:
70 if line.startswith('# User '):
70 if line.startswith('# User '):
71 user = line[7:]
71 user = line[7:]
72 ui.debug('From: %s\n' % user)
72 ui.debug('From: %s\n' % user)
73 elif line.startswith("# Date "):
73 elif line.startswith("# Date "):
74 date = line[7:]
74 date = line[7:]
75 if not line.startswith('# '):
75 if not line.startswith('# '):
76 cfp.write(line)
76 cfp.write(line)
77 cfp.write('\n')
77 cfp.write('\n')
78 message = cfp.getvalue()
78 message = cfp.getvalue()
79 if tmpfp:
79 if tmpfp:
80 tmpfp.write(payload)
80 tmpfp.write(payload)
81 if not payload.endswith('\n'):
81 if not payload.endswith('\n'):
82 tmpfp.write('\n')
82 tmpfp.write('\n')
83 elif not diffs_seen and message and content_type == 'text/plain':
83 elif not diffs_seen and message and content_type == 'text/plain':
84 message += '\n' + payload
84 message += '\n' + payload
85 except:
85 except:
86 tmpfp.close()
86 tmpfp.close()
87 os.unlink(tmpname)
87 os.unlink(tmpname)
88 raise
88 raise
89
89
90 tmpfp.close()
90 tmpfp.close()
91 if not diffs_seen:
91 if not diffs_seen:
92 os.unlink(tmpname)
92 os.unlink(tmpname)
93 return None, message, user, date
93 return None, message, user, date
94 return tmpname, message, user, date
94 return tmpname, message, user, date
95
95
96 def readgitpatch(patchname):
96 def readgitpatch(patchname):
97 """extract git-style metadata about patches from <patchname>"""
97 """extract git-style metadata about patches from <patchname>"""
98 class gitpatch:
98 class gitpatch:
99 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
99 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
100 def __init__(self, path):
100 def __init__(self, path):
101 self.path = path
101 self.path = path
102 self.oldpath = None
102 self.oldpath = None
103 self.mode = None
103 self.mode = None
104 self.op = 'MODIFY'
104 self.op = 'MODIFY'
105 self.copymod = False
105 self.copymod = False
106 self.lineno = 0
106 self.lineno = 0
107
107
108 # Filter patch for git information
108 # Filter patch for git information
109 gitre = re.compile('diff --git a/(.*) b/(.*)')
109 gitre = re.compile('diff --git a/(.*) b/(.*)')
110 pf = file(patchname)
110 pf = file(patchname)
111 gp = None
111 gp = None
112 gitpatches = []
112 gitpatches = []
113 # Can have a git patch with only metadata, causing patch to complain
113 # Can have a git patch with only metadata, causing patch to complain
114 dopatch = False
114 dopatch = False
115
115
116 lineno = 0
116 lineno = 0
117 for line in pf:
117 for line in pf:
118 lineno += 1
118 lineno += 1
119 if line.startswith('diff --git'):
119 if line.startswith('diff --git'):
120 m = gitre.match(line)
120 m = gitre.match(line)
121 if m:
121 if m:
122 if gp:
122 if gp:
123 gitpatches.append(gp)
123 gitpatches.append(gp)
124 src, dst = m.group(1,2)
124 src, dst = m.group(1,2)
125 gp = gitpatch(dst)
125 gp = gitpatch(dst)
126 gp.lineno = lineno
126 gp.lineno = lineno
127 elif gp:
127 elif gp:
128 if line.startswith('--- '):
128 if line.startswith('--- '):
129 if gp.op in ('COPY', 'RENAME'):
129 if gp.op in ('COPY', 'RENAME'):
130 gp.copymod = True
130 gp.copymod = True
131 dopatch = 'filter'
131 dopatch = 'filter'
132 gitpatches.append(gp)
132 gitpatches.append(gp)
133 gp = None
133 gp = None
134 if not dopatch:
134 if not dopatch:
135 dopatch = True
135 dopatch = True
136 continue
136 continue
137 if line.startswith('rename from '):
137 if line.startswith('rename from '):
138 gp.op = 'RENAME'
138 gp.op = 'RENAME'
139 gp.oldpath = line[12:].rstrip()
139 gp.oldpath = line[12:].rstrip()
140 elif line.startswith('rename to '):
140 elif line.startswith('rename to '):
141 gp.path = line[10:].rstrip()
141 gp.path = line[10:].rstrip()
142 elif line.startswith('copy from '):
142 elif line.startswith('copy from '):
143 gp.op = 'COPY'
143 gp.op = 'COPY'
144 gp.oldpath = line[10:].rstrip()
144 gp.oldpath = line[10:].rstrip()
145 elif line.startswith('copy to '):
145 elif line.startswith('copy to '):
146 gp.path = line[8:].rstrip()
146 gp.path = line[8:].rstrip()
147 elif line.startswith('deleted file'):
147 elif line.startswith('deleted file'):
148 gp.op = 'DELETE'
148 gp.op = 'DELETE'
149 elif line.startswith('new file mode '):
149 elif line.startswith('new file mode '):
150 gp.op = 'ADD'
150 gp.op = 'ADD'
151 gp.mode = int(line.rstrip()[-3:], 8)
151 gp.mode = int(line.rstrip()[-3:], 8)
152 elif line.startswith('new mode '):
152 elif line.startswith('new mode '):
153 gp.mode = int(line.rstrip()[-3:], 8)
153 gp.mode = int(line.rstrip()[-3:], 8)
154 if gp:
154 if gp:
155 gitpatches.append(gp)
155 gitpatches.append(gp)
156
156
157 if not gitpatches:
157 if not gitpatches:
158 dopatch = True
158 dopatch = True
159
159
160 return (dopatch, gitpatches)
160 return (dopatch, gitpatches)
161
161
162 def dogitpatch(patchname, gitpatches):
162 def dogitpatch(patchname, gitpatches):
163 """Preprocess git patch so that vanilla patch can handle it"""
163 """Preprocess git patch so that vanilla patch can handle it"""
164 pf = file(patchname)
164 pf = file(patchname)
165 pfline = 1
165 pfline = 1
166
166
167 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
167 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
168 tmpfp = os.fdopen(fd, 'w')
168 tmpfp = os.fdopen(fd, 'w')
169
169
170 try:
170 try:
171 for i in range(len(gitpatches)):
171 for i in range(len(gitpatches)):
172 p = gitpatches[i]
172 p = gitpatches[i]
173 if not p.copymod:
173 if not p.copymod:
174 continue
174 continue
175
175
176 if os.path.exists(p.path):
176 if os.path.exists(p.path):
177 raise util.Abort(_("cannot create %s: destination already exists") %
177 raise util.Abort(_("cannot create %s: destination already exists") %
178 p.path)
178 p.path)
179
179
180 (src, dst) = [os.path.join(os.getcwd(), n)
180 (src, dst) = [os.path.join(os.getcwd(), n)
181 for n in (p.oldpath, p.path)]
181 for n in (p.oldpath, p.path)]
182
182
183 targetdir = os.path.dirname(dst)
183 targetdir = os.path.dirname(dst)
184 if not os.path.isdir(targetdir):
184 if not os.path.isdir(targetdir):
185 os.makedirs(targetdir)
185 os.makedirs(targetdir)
186 try:
186 try:
187 shutil.copyfile(src, dst)
187 shutil.copyfile(src, dst)
188 shutil.copymode(src, dst)
188 shutil.copymode(src, dst)
189 except shutil.Error, inst:
189 except shutil.Error, inst:
190 raise util.Abort(str(inst))
190 raise util.Abort(str(inst))
191
191
192 # rewrite patch hunk
192 # rewrite patch hunk
193 while pfline < p.lineno:
193 while pfline < p.lineno:
194 tmpfp.write(pf.readline())
194 tmpfp.write(pf.readline())
195 pfline += 1
195 pfline += 1
196 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
196 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
197 line = pf.readline()
197 line = pf.readline()
198 pfline += 1
198 pfline += 1
199 while not line.startswith('--- a/'):
199 while not line.startswith('--- a/'):
200 tmpfp.write(line)
200 tmpfp.write(line)
201 line = pf.readline()
201 line = pf.readline()
202 pfline += 1
202 pfline += 1
203 tmpfp.write('--- a/%s\n' % p.path)
203 tmpfp.write('--- a/%s\n' % p.path)
204
204
205 line = pf.readline()
205 line = pf.readline()
206 while line:
206 while line:
207 tmpfp.write(line)
207 tmpfp.write(line)
208 line = pf.readline()
208 line = pf.readline()
209 except:
209 except:
210 tmpfp.close()
210 tmpfp.close()
211 os.unlink(patchname)
211 os.unlink(patchname)
212 raise
212 raise
213
213
214 tmpfp.close()
214 tmpfp.close()
215 return patchname
215 return patchname
216
216
217 def patch(strip, patchname, ui, cwd=None):
217 def patch(strip, patchname, ui, cwd=None):
218 """apply the patch <patchname> to the working directory.
218 """apply the patch <patchname> to the working directory.
219 a list of patched files is returned"""
219 a list of patched files is returned"""
220
220
221 (dopatch, gitpatches) = readgitpatch(patchname)
221 (dopatch, gitpatches) = readgitpatch(patchname)
222
222
223 files = {}
223 files = {}
224 if dopatch:
224 if dopatch:
225 if dopatch == 'filter':
225 if dopatch == 'filter':
226 patchname = dogitpatch(patchname, gitpatches)
226 patchname = dogitpatch(patchname, gitpatches)
227 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
227 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
228 args = []
228 args = []
229 if cwd:
229 if cwd:
230 args.append('-d %s' % util.shellquote(cwd))
230 args.append('-d %s' % util.shellquote(cwd))
231 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
231 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
232 util.shellquote(patchname)))
232 util.shellquote(patchname)))
233
233
234 if dopatch == 'filter':
234 if dopatch == 'filter':
235 False and os.unlink(patchname)
235 False and os.unlink(patchname)
236
236
237 for line in fp:
237 for line in fp:
238 line = line.rstrip()
238 line = line.rstrip()
239 ui.status("%s\n" % line)
239 ui.status("%s\n" % line)
240 if line.startswith('patching file '):
240 if line.startswith('patching file '):
241 pf = util.parse_patch_output(line)
241 pf = util.parse_patch_output(line)
242 files.setdefault(pf, (None, None))
242 files.setdefault(pf, (None, None))
243 code = fp.close()
243 code = fp.close()
244 if code:
244 if code:
245 raise util.Abort(_("patch command failed: %s") % explain_exit(code)[0])
245 raise util.Abort(_("patch command failed: %s") %
246 util.explain_exit(code)[0])
246
247
247 for gp in gitpatches:
248 for gp in gitpatches:
248 files[gp.path] = (gp.op, gp)
249 files[gp.path] = (gp.op, gp)
249
250
250 return files
251 return files
General Comments 0
You need to be logged in to leave comments. Login now