##// END OF EJS Templates
fix patch.patch.filterfiles....
Vadim Gelfer -
r2881:eab07a7b default
parent child Browse files
Show More
@@ -1,366 +1,365 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 from node import *
10 from node import *
11 demandload(globals(), "cmdutil mdiff util")
11 demandload(globals(), "cmdutil mdiff util")
12 demandload(globals(), "cStringIO email.Parser os re shutil sys tempfile")
12 demandload(globals(), "cStringIO email.Parser os re shutil sys tempfile")
13
13
14 def extract(ui, fileobj):
14 def extract(ui, fileobj):
15 '''extract patch from data read from fileobj.
15 '''extract patch from data read from fileobj.
16
16
17 patch can be normal patch or contained in email message.
17 patch can be normal patch or contained in email message.
18
18
19 return tuple (filename, message, user, date). any item in returned
19 return tuple (filename, message, user, date). any item in returned
20 tuple can be None. if filename is None, fileobj did not contain
20 tuple can be None. if filename is None, fileobj did not contain
21 patch. caller must unlink filename when done.'''
21 patch. caller must unlink filename when done.'''
22
22
23 # attempt to detect the start of a patch
23 # attempt to detect the start of a patch
24 # (this heuristic is borrowed from quilt)
24 # (this heuristic is borrowed from quilt)
25 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
25 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
26 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
26 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
27 '(---|\*\*\*)[ \t])', re.MULTILINE)
27 '(---|\*\*\*)[ \t])', re.MULTILINE)
28
28
29 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
29 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
30 tmpfp = os.fdopen(fd, 'w')
30 tmpfp = os.fdopen(fd, 'w')
31 try:
31 try:
32 hgpatch = False
32 hgpatch = False
33
33
34 msg = email.Parser.Parser().parse(fileobj)
34 msg = email.Parser.Parser().parse(fileobj)
35
35
36 message = msg['Subject']
36 message = msg['Subject']
37 user = msg['From']
37 user = msg['From']
38 # should try to parse msg['Date']
38 # should try to parse msg['Date']
39 date = None
39 date = None
40
40
41 if message:
41 if message:
42 message = message.replace('\n\t', ' ')
42 message = message.replace('\n\t', ' ')
43 ui.debug('Subject: %s\n' % message)
43 ui.debug('Subject: %s\n' % message)
44 if user:
44 if user:
45 ui.debug('From: %s\n' % user)
45 ui.debug('From: %s\n' % user)
46 diffs_seen = 0
46 diffs_seen = 0
47 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
47 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
48
48
49 for part in msg.walk():
49 for part in msg.walk():
50 content_type = part.get_content_type()
50 content_type = part.get_content_type()
51 ui.debug('Content-Type: %s\n' % content_type)
51 ui.debug('Content-Type: %s\n' % content_type)
52 if content_type not in ok_types:
52 if content_type not in ok_types:
53 continue
53 continue
54 payload = part.get_payload(decode=True)
54 payload = part.get_payload(decode=True)
55 m = diffre.search(payload)
55 m = diffre.search(payload)
56 if m:
56 if m:
57 ui.debug(_('found patch at byte %d\n') % m.start(0))
57 ui.debug(_('found patch at byte %d\n') % m.start(0))
58 diffs_seen += 1
58 diffs_seen += 1
59 cfp = cStringIO.StringIO()
59 cfp = cStringIO.StringIO()
60 if message:
60 if message:
61 cfp.write(message)
61 cfp.write(message)
62 cfp.write('\n')
62 cfp.write('\n')
63 for line in payload[:m.start(0)].splitlines():
63 for line in payload[:m.start(0)].splitlines():
64 if line.startswith('# HG changeset patch'):
64 if line.startswith('# HG changeset patch'):
65 ui.debug(_('patch generated by hg export\n'))
65 ui.debug(_('patch generated by hg export\n'))
66 hgpatch = True
66 hgpatch = True
67 # drop earlier commit message content
67 # drop earlier commit message content
68 cfp.seek(0)
68 cfp.seek(0)
69 cfp.truncate()
69 cfp.truncate()
70 elif hgpatch:
70 elif hgpatch:
71 if line.startswith('# User '):
71 if line.startswith('# User '):
72 user = line[7:]
72 user = line[7:]
73 ui.debug('From: %s\n' % user)
73 ui.debug('From: %s\n' % user)
74 elif line.startswith("# Date "):
74 elif line.startswith("# Date "):
75 date = line[7:]
75 date = line[7:]
76 if not line.startswith('# '):
76 if not line.startswith('# '):
77 cfp.write(line)
77 cfp.write(line)
78 cfp.write('\n')
78 cfp.write('\n')
79 message = cfp.getvalue()
79 message = cfp.getvalue()
80 if tmpfp:
80 if tmpfp:
81 tmpfp.write(payload)
81 tmpfp.write(payload)
82 if not payload.endswith('\n'):
82 if not payload.endswith('\n'):
83 tmpfp.write('\n')
83 tmpfp.write('\n')
84 elif not diffs_seen and message and content_type == 'text/plain':
84 elif not diffs_seen and message and content_type == 'text/plain':
85 message += '\n' + payload
85 message += '\n' + payload
86 except:
86 except:
87 tmpfp.close()
87 tmpfp.close()
88 os.unlink(tmpname)
88 os.unlink(tmpname)
89 raise
89 raise
90
90
91 tmpfp.close()
91 tmpfp.close()
92 if not diffs_seen:
92 if not diffs_seen:
93 os.unlink(tmpname)
93 os.unlink(tmpname)
94 return None, message, user, date
94 return None, message, user, date
95 return tmpname, message, user, date
95 return tmpname, message, user, date
96
96
97 def readgitpatch(patchname):
97 def readgitpatch(patchname):
98 """extract git-style metadata about patches from <patchname>"""
98 """extract git-style metadata about patches from <patchname>"""
99 class gitpatch:
99 class gitpatch:
100 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
100 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
101 def __init__(self, path):
101 def __init__(self, path):
102 self.path = path
102 self.path = path
103 self.oldpath = None
103 self.oldpath = None
104 self.mode = None
104 self.mode = None
105 self.op = 'MODIFY'
105 self.op = 'MODIFY'
106 self.copymod = False
106 self.copymod = False
107 self.lineno = 0
107 self.lineno = 0
108
108
109 # Filter patch for git information
109 # Filter patch for git information
110 gitre = re.compile('diff --git a/(.*) b/(.*)')
110 gitre = re.compile('diff --git a/(.*) b/(.*)')
111 pf = file(patchname)
111 pf = file(patchname)
112 gp = None
112 gp = None
113 gitpatches = []
113 gitpatches = []
114 # Can have a git patch with only metadata, causing patch to complain
114 # Can have a git patch with only metadata, causing patch to complain
115 dopatch = False
115 dopatch = False
116
116
117 lineno = 0
117 lineno = 0
118 for line in pf:
118 for line in pf:
119 lineno += 1
119 lineno += 1
120 if line.startswith('diff --git'):
120 if line.startswith('diff --git'):
121 m = gitre.match(line)
121 m = gitre.match(line)
122 if m:
122 if m:
123 if gp:
123 if gp:
124 gitpatches.append(gp)
124 gitpatches.append(gp)
125 src, dst = m.group(1,2)
125 src, dst = m.group(1,2)
126 gp = gitpatch(dst)
126 gp = gitpatch(dst)
127 gp.lineno = lineno
127 gp.lineno = lineno
128 elif gp:
128 elif gp:
129 if line.startswith('--- '):
129 if line.startswith('--- '):
130 if gp.op in ('COPY', 'RENAME'):
130 if gp.op in ('COPY', 'RENAME'):
131 gp.copymod = True
131 gp.copymod = True
132 dopatch = 'filter'
132 dopatch = 'filter'
133 gitpatches.append(gp)
133 gitpatches.append(gp)
134 gp = None
134 gp = None
135 if not dopatch:
135 if not dopatch:
136 dopatch = True
136 dopatch = True
137 continue
137 continue
138 if line.startswith('rename from '):
138 if line.startswith('rename from '):
139 gp.op = 'RENAME'
139 gp.op = 'RENAME'
140 gp.oldpath = line[12:].rstrip()
140 gp.oldpath = line[12:].rstrip()
141 elif line.startswith('rename to '):
141 elif line.startswith('rename to '):
142 gp.path = line[10:].rstrip()
142 gp.path = line[10:].rstrip()
143 elif line.startswith('copy from '):
143 elif line.startswith('copy from '):
144 gp.op = 'COPY'
144 gp.op = 'COPY'
145 gp.oldpath = line[10:].rstrip()
145 gp.oldpath = line[10:].rstrip()
146 elif line.startswith('copy to '):
146 elif line.startswith('copy to '):
147 gp.path = line[8:].rstrip()
147 gp.path = line[8:].rstrip()
148 elif line.startswith('deleted file'):
148 elif line.startswith('deleted file'):
149 gp.op = 'DELETE'
149 gp.op = 'DELETE'
150 elif line.startswith('new file mode '):
150 elif line.startswith('new file mode '):
151 gp.op = 'ADD'
151 gp.op = 'ADD'
152 gp.mode = int(line.rstrip()[-3:], 8)
152 gp.mode = int(line.rstrip()[-3:], 8)
153 elif line.startswith('new mode '):
153 elif line.startswith('new mode '):
154 gp.mode = int(line.rstrip()[-3:], 8)
154 gp.mode = int(line.rstrip()[-3:], 8)
155 if gp:
155 if gp:
156 gitpatches.append(gp)
156 gitpatches.append(gp)
157
157
158 if not gitpatches:
158 if not gitpatches:
159 dopatch = True
159 dopatch = True
160
160
161 return (dopatch, gitpatches)
161 return (dopatch, gitpatches)
162
162
163 def dogitpatch(patchname, gitpatches):
163 def dogitpatch(patchname, gitpatches):
164 """Preprocess git patch so that vanilla patch can handle it"""
164 """Preprocess git patch so that vanilla patch can handle it"""
165 pf = file(patchname)
165 pf = file(patchname)
166 pfline = 1
166 pfline = 1
167
167
168 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
168 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
169 tmpfp = os.fdopen(fd, 'w')
169 tmpfp = os.fdopen(fd, 'w')
170
170
171 try:
171 try:
172 for i in range(len(gitpatches)):
172 for i in range(len(gitpatches)):
173 p = gitpatches[i]
173 p = gitpatches[i]
174 if not p.copymod:
174 if not p.copymod:
175 continue
175 continue
176
176
177 if os.path.exists(p.path):
177 if os.path.exists(p.path):
178 raise util.Abort(_("cannot create %s: destination already exists") %
178 raise util.Abort(_("cannot create %s: destination already exists") %
179 p.path)
179 p.path)
180
180
181 (src, dst) = [os.path.join(os.getcwd(), n)
181 (src, dst) = [os.path.join(os.getcwd(), n)
182 for n in (p.oldpath, p.path)]
182 for n in (p.oldpath, p.path)]
183
183
184 targetdir = os.path.dirname(dst)
184 targetdir = os.path.dirname(dst)
185 if not os.path.isdir(targetdir):
185 if not os.path.isdir(targetdir):
186 os.makedirs(targetdir)
186 os.makedirs(targetdir)
187 try:
187 try:
188 shutil.copyfile(src, dst)
188 shutil.copyfile(src, dst)
189 shutil.copymode(src, dst)
189 shutil.copymode(src, dst)
190 except shutil.Error, inst:
190 except shutil.Error, inst:
191 raise util.Abort(str(inst))
191 raise util.Abort(str(inst))
192
192
193 # rewrite patch hunk
193 # rewrite patch hunk
194 while pfline < p.lineno:
194 while pfline < p.lineno:
195 tmpfp.write(pf.readline())
195 tmpfp.write(pf.readline())
196 pfline += 1
196 pfline += 1
197 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
197 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
198 line = pf.readline()
198 line = pf.readline()
199 pfline += 1
199 pfline += 1
200 while not line.startswith('--- a/'):
200 while not line.startswith('--- a/'):
201 tmpfp.write(line)
201 tmpfp.write(line)
202 line = pf.readline()
202 line = pf.readline()
203 pfline += 1
203 pfline += 1
204 tmpfp.write('--- a/%s\n' % p.path)
204 tmpfp.write('--- a/%s\n' % p.path)
205
205
206 line = pf.readline()
206 line = pf.readline()
207 while line:
207 while line:
208 tmpfp.write(line)
208 tmpfp.write(line)
209 line = pf.readline()
209 line = pf.readline()
210 except:
210 except:
211 tmpfp.close()
211 tmpfp.close()
212 os.unlink(patchname)
212 os.unlink(patchname)
213 raise
213 raise
214
214
215 tmpfp.close()
215 tmpfp.close()
216 return patchname
216 return patchname
217
217
218 def patch(strip, patchname, ui, cwd=None):
218 def patch(strip, patchname, ui, cwd=None):
219 """apply the patch <patchname> to the working directory.
219 """apply the patch <patchname> to the working directory.
220 a list of patched files is returned"""
220 a list of patched files is returned"""
221
221
222 (dopatch, gitpatches) = readgitpatch(patchname)
222 (dopatch, gitpatches) = readgitpatch(patchname)
223
223
224 files = {}
224 files = {}
225 if dopatch:
225 if dopatch:
226 if dopatch == 'filter':
226 if dopatch == 'filter':
227 patchname = dogitpatch(patchname, gitpatches)
227 patchname = dogitpatch(patchname, gitpatches)
228 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
228 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
229 args = []
229 args = []
230 if cwd:
230 if cwd:
231 args.append('-d %s' % util.shellquote(cwd))
231 args.append('-d %s' % util.shellquote(cwd))
232 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
232 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
233 util.shellquote(patchname)))
233 util.shellquote(patchname)))
234
234
235 if dopatch == 'filter':
235 if dopatch == 'filter':
236 False and os.unlink(patchname)
236 False and os.unlink(patchname)
237
237
238 for line in fp:
238 for line in fp:
239 line = line.rstrip()
239 line = line.rstrip()
240 ui.status("%s\n" % line)
240 ui.status("%s\n" % line)
241 if line.startswith('patching file '):
241 if line.startswith('patching file '):
242 pf = util.parse_patch_output(line)
242 pf = util.parse_patch_output(line)
243 files.setdefault(pf, (None, None))
243 files.setdefault(pf, (None, None))
244 code = fp.close()
244 code = fp.close()
245 if code:
245 if code:
246 raise util.Abort(_("patch command failed: %s") %
246 raise util.Abort(_("patch command failed: %s") %
247 util.explain_exit(code)[0])
247 util.explain_exit(code)[0])
248
248
249 for gp in gitpatches:
249 for gp in gitpatches:
250 files[gp.path] = (gp.op, gp)
250 files[gp.path] = (gp.op, gp)
251
251
252 return files
252 return files
253
253
254 def diff(repo, node1=None, node2=None, files=None, match=util.always,
254 def diff(repo, node1=None, node2=None, files=None, match=util.always,
255 fp=None, changes=None, opts=None):
255 fp=None, changes=None, opts=None):
256 '''print diff of changes to files between two nodes, or node and
256 '''print diff of changes to files between two nodes, or node and
257 working directory.
257 working directory.
258
258
259 if node1 is None, use first dirstate parent instead.
259 if node1 is None, use first dirstate parent instead.
260 if node2 is None, compare node1 with working directory.'''
260 if node2 is None, compare node1 with working directory.'''
261
261
262 if opts is None:
262 if opts is None:
263 opts = mdiff.defaultopts
263 opts = mdiff.defaultopts
264 if fp is None:
264 if fp is None:
265 fp = repo.ui
265 fp = repo.ui
266
266
267 if not node1:
267 if not node1:
268 node1 = repo.dirstate.parents()[0]
268 node1 = repo.dirstate.parents()[0]
269 # reading the data for node1 early allows it to play nicely
269 # reading the data for node1 early allows it to play nicely
270 # with repo.status and the revlog cache.
270 # with repo.status and the revlog cache.
271 change = repo.changelog.read(node1)
271 change = repo.changelog.read(node1)
272 mmap = repo.manifest.read(change[0])
272 mmap = repo.manifest.read(change[0])
273 date1 = util.datestr(change[2])
273 date1 = util.datestr(change[2])
274
274
275 if not changes:
275 if not changes:
276 changes = repo.status(node1, node2, files, match=match)[:5]
276 changes = repo.status(node1, node2, files, match=match)[:5]
277 modified, added, removed, deleted, unknown = changes
277 modified, added, removed, deleted, unknown = changes
278 if files:
278 if files:
279 def filterfiles(filters):
279 def filterfiles(filters):
280 l = [x for x in files if x in filters]
280 l = [x for x in filters if x in files]
281
281
282 for t in filters:
282 for t in files:
283 if t and t[-1] != "/":
283 if not t.endswith("/"):
284 t += "/"
284 t += "/"
285 l += [x for x in files if x.startswith(t)]
285 l += [x for x in filters if x.startswith(t)]
286 return l
286 return l
287
287
288 modified, added, removed = map(lambda x: filterfiles(x),
288 modified, added, removed = map(filterfiles, (modified, added, removed))
289 (modified, added, removed))
290
289
291 if not modified and not added and not removed:
290 if not modified and not added and not removed:
292 return
291 return
293
292
294 if node2:
293 if node2:
295 change = repo.changelog.read(node2)
294 change = repo.changelog.read(node2)
296 mmap2 = repo.manifest.read(change[0])
295 mmap2 = repo.manifest.read(change[0])
297 _date2 = util.datestr(change[2])
296 _date2 = util.datestr(change[2])
298 def date2(f):
297 def date2(f):
299 return _date2
298 return _date2
300 def read(f):
299 def read(f):
301 return repo.file(f).read(mmap2[f])
300 return repo.file(f).read(mmap2[f])
302 else:
301 else:
303 tz = util.makedate()[1]
302 tz = util.makedate()[1]
304 _date2 = util.datestr()
303 _date2 = util.datestr()
305 def date2(f):
304 def date2(f):
306 try:
305 try:
307 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
306 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
308 except OSError, err:
307 except OSError, err:
309 if err.errno != errno.ENOENT: raise
308 if err.errno != errno.ENOENT: raise
310 return _date2
309 return _date2
311 def read(f):
310 def read(f):
312 return repo.wread(f)
311 return repo.wread(f)
313
312
314 if repo.ui.quiet:
313 if repo.ui.quiet:
315 r = None
314 r = None
316 else:
315 else:
317 hexfunc = repo.ui.verbose and hex or short
316 hexfunc = repo.ui.verbose and hex or short
318 r = [hexfunc(node) for node in [node1, node2] if node]
317 r = [hexfunc(node) for node in [node1, node2] if node]
319
318
320 all = modified + added + removed
319 all = modified + added + removed
321 all.sort()
320 all.sort()
322 for f in all:
321 for f in all:
323 to = None
322 to = None
324 tn = None
323 tn = None
325 if f in mmap:
324 if f in mmap:
326 to = repo.file(f).read(mmap[f])
325 to = repo.file(f).read(mmap[f])
327 if f not in removed:
326 if f not in removed:
328 tn = read(f)
327 tn = read(f)
329 fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts))
328 fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts))
330
329
331 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
330 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
332 opts=None):
331 opts=None):
333 '''export changesets as hg patches.'''
332 '''export changesets as hg patches.'''
334
333
335 total = len(revs)
334 total = len(revs)
336 revwidth = max(map(len, revs))
335 revwidth = max(map(len, revs))
337
336
338 def single(node, seqno, fp):
337 def single(node, seqno, fp):
339 parents = [p for p in repo.changelog.parents(node) if p != nullid]
338 parents = [p for p in repo.changelog.parents(node) if p != nullid]
340 if switch_parent:
339 if switch_parent:
341 parents.reverse()
340 parents.reverse()
342 prev = (parents and parents[0]) or nullid
341 prev = (parents and parents[0]) or nullid
343 change = repo.changelog.read(node)
342 change = repo.changelog.read(node)
344
343
345 if not fp:
344 if not fp:
346 fp = cmdutil.make_file(repo, template, node, total=total,
345 fp = cmdutil.make_file(repo, template, node, total=total,
347 seqno=seqno, revwidth=revwidth)
346 seqno=seqno, revwidth=revwidth)
348 if fp not in (sys.stdout, repo.ui):
347 if fp not in (sys.stdout, repo.ui):
349 repo.ui.note("%s\n" % fp.name)
348 repo.ui.note("%s\n" % fp.name)
350
349
351 fp.write("# HG changeset patch\n")
350 fp.write("# HG changeset patch\n")
352 fp.write("# User %s\n" % change[1])
351 fp.write("# User %s\n" % change[1])
353 fp.write("# Date %d %d\n" % change[2])
352 fp.write("# Date %d %d\n" % change[2])
354 fp.write("# Node ID %s\n" % hex(node))
353 fp.write("# Node ID %s\n" % hex(node))
355 fp.write("# Parent %s\n" % hex(prev))
354 fp.write("# Parent %s\n" % hex(prev))
356 if len(parents) > 1:
355 if len(parents) > 1:
357 fp.write("# Parent %s\n" % hex(parents[1]))
356 fp.write("# Parent %s\n" % hex(parents[1]))
358 fp.write(change[4].rstrip())
357 fp.write(change[4].rstrip())
359 fp.write("\n\n")
358 fp.write("\n\n")
360
359
361 diff(repo, prev, node, fp=fp, opts=opts)
360 diff(repo, prev, node, fp=fp, opts=opts)
362 if fp not in (sys.stdout, repo.ui):
361 if fp not in (sys.stdout, repo.ui):
363 fp.close()
362 fp.close()
364
363
365 for seqno, cset in enumerate(revs):
364 for seqno, cset in enumerate(revs):
366 single(cset, seqno, fp)
365 single(cset, seqno, fp)
General Comments 0
You need to be logged in to leave comments. Login now