##// END OF EJS Templates
patch.diff: avoid calling workingctx().manifest()...
Alexis S. L. Carvalho -
r4496:b79cdb7f default
parent child Browse files
Show More
@@ -1,671 +1,674 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 i18n import _
8 from i18n import _
9 from node import *
9 from node import *
10 import base85, cmdutil, mdiff, util, context, revlog
10 import base85, cmdutil, mdiff, util, context, revlog
11 import cStringIO, email.Parser, os, popen2, re, sha
11 import cStringIO, email.Parser, os, popen2, re, sha
12 import sys, tempfile, zlib
12 import sys, tempfile, zlib
13
13
14 # helper functions
14 # helper functions
15
15
16 def copyfile(src, dst, basedir=None):
16 def copyfile(src, dst, basedir=None):
17 if not basedir:
17 if not basedir:
18 basedir = os.getcwd()
18 basedir = os.getcwd()
19
19
20 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
20 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
21 if os.path.exists(absdst):
21 if os.path.exists(absdst):
22 raise util.Abort(_("cannot create %s: destination already exists") %
22 raise util.Abort(_("cannot create %s: destination already exists") %
23 dst)
23 dst)
24
24
25 targetdir = os.path.dirname(absdst)
25 targetdir = os.path.dirname(absdst)
26 if not os.path.isdir(targetdir):
26 if not os.path.isdir(targetdir):
27 os.makedirs(targetdir)
27 os.makedirs(targetdir)
28
28
29 util.copyfile(abssrc, absdst)
29 util.copyfile(abssrc, absdst)
30
30
31 # public functions
31 # public functions
32
32
33 def extract(ui, fileobj):
33 def extract(ui, fileobj):
34 '''extract patch from data read from fileobj.
34 '''extract patch from data read from fileobj.
35
35
36 patch can be a normal patch or contained in an email message.
36 patch can be a normal patch or contained in an email message.
37
37
38 return tuple (filename, message, user, date, node, p1, p2).
38 return tuple (filename, message, user, date, node, p1, p2).
39 Any item in the returned tuple can be None. If filename is None,
39 Any item in the returned tuple can be None. If filename is None,
40 fileobj did not contain a patch. Caller must unlink filename when done.'''
40 fileobj did not contain a patch. Caller must unlink filename when done.'''
41
41
42 # attempt to detect the start of a patch
42 # attempt to detect the start of a patch
43 # (this heuristic is borrowed from quilt)
43 # (this heuristic is borrowed from quilt)
44 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
44 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
45 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
45 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
46 '(---|\*\*\*)[ \t])', re.MULTILINE)
46 '(---|\*\*\*)[ \t])', re.MULTILINE)
47
47
48 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
48 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
49 tmpfp = os.fdopen(fd, 'w')
49 tmpfp = os.fdopen(fd, 'w')
50 try:
50 try:
51 msg = email.Parser.Parser().parse(fileobj)
51 msg = email.Parser.Parser().parse(fileobj)
52
52
53 message = msg['Subject']
53 message = msg['Subject']
54 user = msg['From']
54 user = msg['From']
55 # should try to parse msg['Date']
55 # should try to parse msg['Date']
56 date = None
56 date = None
57 nodeid = None
57 nodeid = None
58 branch = None
58 branch = None
59 parents = []
59 parents = []
60
60
61 if message:
61 if message:
62 if message.startswith('[PATCH'):
62 if message.startswith('[PATCH'):
63 pend = message.find(']')
63 pend = message.find(']')
64 if pend >= 0:
64 if pend >= 0:
65 message = message[pend+1:].lstrip()
65 message = message[pend+1:].lstrip()
66 message = message.replace('\n\t', ' ')
66 message = message.replace('\n\t', ' ')
67 ui.debug('Subject: %s\n' % message)
67 ui.debug('Subject: %s\n' % message)
68 if user:
68 if user:
69 ui.debug('From: %s\n' % user)
69 ui.debug('From: %s\n' % user)
70 diffs_seen = 0
70 diffs_seen = 0
71 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
71 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
72
72
73 for part in msg.walk():
73 for part in msg.walk():
74 content_type = part.get_content_type()
74 content_type = part.get_content_type()
75 ui.debug('Content-Type: %s\n' % content_type)
75 ui.debug('Content-Type: %s\n' % content_type)
76 if content_type not in ok_types:
76 if content_type not in ok_types:
77 continue
77 continue
78 payload = part.get_payload(decode=True)
78 payload = part.get_payload(decode=True)
79 m = diffre.search(payload)
79 m = diffre.search(payload)
80 if m:
80 if m:
81 hgpatch = False
81 hgpatch = False
82 ignoretext = False
82 ignoretext = False
83
83
84 ui.debug(_('found patch at byte %d\n') % m.start(0))
84 ui.debug(_('found patch at byte %d\n') % m.start(0))
85 diffs_seen += 1
85 diffs_seen += 1
86 cfp = cStringIO.StringIO()
86 cfp = cStringIO.StringIO()
87 if message:
87 if message:
88 cfp.write(message)
88 cfp.write(message)
89 cfp.write('\n')
89 cfp.write('\n')
90 for line in payload[:m.start(0)].splitlines():
90 for line in payload[:m.start(0)].splitlines():
91 if line.startswith('# HG changeset patch'):
91 if line.startswith('# HG changeset patch'):
92 ui.debug(_('patch generated by hg export\n'))
92 ui.debug(_('patch generated by hg export\n'))
93 hgpatch = True
93 hgpatch = True
94 # drop earlier commit message content
94 # drop earlier commit message content
95 cfp.seek(0)
95 cfp.seek(0)
96 cfp.truncate()
96 cfp.truncate()
97 elif hgpatch:
97 elif hgpatch:
98 if line.startswith('# User '):
98 if line.startswith('# User '):
99 user = line[7:]
99 user = line[7:]
100 ui.debug('From: %s\n' % user)
100 ui.debug('From: %s\n' % user)
101 elif line.startswith("# Date "):
101 elif line.startswith("# Date "):
102 date = line[7:]
102 date = line[7:]
103 elif line.startswith("# Branch "):
103 elif line.startswith("# Branch "):
104 branch = line[9:]
104 branch = line[9:]
105 elif line.startswith("# Node ID "):
105 elif line.startswith("# Node ID "):
106 nodeid = line[10:]
106 nodeid = line[10:]
107 elif line.startswith("# Parent "):
107 elif line.startswith("# Parent "):
108 parents.append(line[10:])
108 parents.append(line[10:])
109 elif line == '---' and 'git-send-email' in msg['X-Mailer']:
109 elif line == '---' and 'git-send-email' in msg['X-Mailer']:
110 ignoretext = True
110 ignoretext = True
111 if not line.startswith('# ') and not ignoretext:
111 if not line.startswith('# ') and not ignoretext:
112 cfp.write(line)
112 cfp.write(line)
113 cfp.write('\n')
113 cfp.write('\n')
114 message = cfp.getvalue()
114 message = cfp.getvalue()
115 if tmpfp:
115 if tmpfp:
116 tmpfp.write(payload)
116 tmpfp.write(payload)
117 if not payload.endswith('\n'):
117 if not payload.endswith('\n'):
118 tmpfp.write('\n')
118 tmpfp.write('\n')
119 elif not diffs_seen and message and content_type == 'text/plain':
119 elif not diffs_seen and message and content_type == 'text/plain':
120 message += '\n' + payload
120 message += '\n' + payload
121 except:
121 except:
122 tmpfp.close()
122 tmpfp.close()
123 os.unlink(tmpname)
123 os.unlink(tmpname)
124 raise
124 raise
125
125
126 tmpfp.close()
126 tmpfp.close()
127 if not diffs_seen:
127 if not diffs_seen:
128 os.unlink(tmpname)
128 os.unlink(tmpname)
129 return None, message, user, date, branch, None, None, None
129 return None, message, user, date, branch, None, None, None
130 p1 = parents and parents.pop(0) or None
130 p1 = parents and parents.pop(0) or None
131 p2 = parents and parents.pop(0) or None
131 p2 = parents and parents.pop(0) or None
132 return tmpname, message, user, date, branch, nodeid, p1, p2
132 return tmpname, message, user, date, branch, nodeid, p1, p2
133
133
134 GP_PATCH = 1 << 0 # we have to run patch
134 GP_PATCH = 1 << 0 # we have to run patch
135 GP_FILTER = 1 << 1 # there's some copy/rename operation
135 GP_FILTER = 1 << 1 # there's some copy/rename operation
136 GP_BINARY = 1 << 2 # there's a binary patch
136 GP_BINARY = 1 << 2 # there's a binary patch
137
137
138 def readgitpatch(patchname):
138 def readgitpatch(patchname):
139 """extract git-style metadata about patches from <patchname>"""
139 """extract git-style metadata about patches from <patchname>"""
140 class gitpatch:
140 class gitpatch:
141 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
141 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
142 def __init__(self, path):
142 def __init__(self, path):
143 self.path = path
143 self.path = path
144 self.oldpath = None
144 self.oldpath = None
145 self.mode = None
145 self.mode = None
146 self.op = 'MODIFY'
146 self.op = 'MODIFY'
147 self.copymod = False
147 self.copymod = False
148 self.lineno = 0
148 self.lineno = 0
149 self.binary = False
149 self.binary = False
150
150
151 # Filter patch for git information
151 # Filter patch for git information
152 gitre = re.compile('diff --git a/(.*) b/(.*)')
152 gitre = re.compile('diff --git a/(.*) b/(.*)')
153 pf = file(patchname)
153 pf = file(patchname)
154 gp = None
154 gp = None
155 gitpatches = []
155 gitpatches = []
156 # Can have a git patch with only metadata, causing patch to complain
156 # Can have a git patch with only metadata, causing patch to complain
157 dopatch = 0
157 dopatch = 0
158
158
159 lineno = 0
159 lineno = 0
160 for line in pf:
160 for line in pf:
161 lineno += 1
161 lineno += 1
162 if line.startswith('diff --git'):
162 if line.startswith('diff --git'):
163 m = gitre.match(line)
163 m = gitre.match(line)
164 if m:
164 if m:
165 if gp:
165 if gp:
166 gitpatches.append(gp)
166 gitpatches.append(gp)
167 src, dst = m.group(1, 2)
167 src, dst = m.group(1, 2)
168 gp = gitpatch(dst)
168 gp = gitpatch(dst)
169 gp.lineno = lineno
169 gp.lineno = lineno
170 elif gp:
170 elif gp:
171 if line.startswith('--- '):
171 if line.startswith('--- '):
172 if gp.op in ('COPY', 'RENAME'):
172 if gp.op in ('COPY', 'RENAME'):
173 gp.copymod = True
173 gp.copymod = True
174 dopatch |= GP_FILTER
174 dopatch |= GP_FILTER
175 gitpatches.append(gp)
175 gitpatches.append(gp)
176 gp = None
176 gp = None
177 dopatch |= GP_PATCH
177 dopatch |= GP_PATCH
178 continue
178 continue
179 if line.startswith('rename from '):
179 if line.startswith('rename from '):
180 gp.op = 'RENAME'
180 gp.op = 'RENAME'
181 gp.oldpath = line[12:].rstrip()
181 gp.oldpath = line[12:].rstrip()
182 elif line.startswith('rename to '):
182 elif line.startswith('rename to '):
183 gp.path = line[10:].rstrip()
183 gp.path = line[10:].rstrip()
184 elif line.startswith('copy from '):
184 elif line.startswith('copy from '):
185 gp.op = 'COPY'
185 gp.op = 'COPY'
186 gp.oldpath = line[10:].rstrip()
186 gp.oldpath = line[10:].rstrip()
187 elif line.startswith('copy to '):
187 elif line.startswith('copy to '):
188 gp.path = line[8:].rstrip()
188 gp.path = line[8:].rstrip()
189 elif line.startswith('deleted file'):
189 elif line.startswith('deleted file'):
190 gp.op = 'DELETE'
190 gp.op = 'DELETE'
191 elif line.startswith('new file mode '):
191 elif line.startswith('new file mode '):
192 gp.op = 'ADD'
192 gp.op = 'ADD'
193 gp.mode = int(line.rstrip()[-3:], 8)
193 gp.mode = int(line.rstrip()[-3:], 8)
194 elif line.startswith('new mode '):
194 elif line.startswith('new mode '):
195 gp.mode = int(line.rstrip()[-3:], 8)
195 gp.mode = int(line.rstrip()[-3:], 8)
196 elif line.startswith('GIT binary patch'):
196 elif line.startswith('GIT binary patch'):
197 dopatch |= GP_BINARY
197 dopatch |= GP_BINARY
198 gp.binary = True
198 gp.binary = True
199 if gp:
199 if gp:
200 gitpatches.append(gp)
200 gitpatches.append(gp)
201
201
202 if not gitpatches:
202 if not gitpatches:
203 dopatch = GP_PATCH
203 dopatch = GP_PATCH
204
204
205 return (dopatch, gitpatches)
205 return (dopatch, gitpatches)
206
206
207 def dogitpatch(patchname, gitpatches, cwd=None):
207 def dogitpatch(patchname, gitpatches, cwd=None):
208 """Preprocess git patch so that vanilla patch can handle it"""
208 """Preprocess git patch so that vanilla patch can handle it"""
209 def extractbin(fp):
209 def extractbin(fp):
210 i = [0] # yuck
210 i = [0] # yuck
211 def readline():
211 def readline():
212 i[0] += 1
212 i[0] += 1
213 return fp.readline().rstrip()
213 return fp.readline().rstrip()
214 line = readline()
214 line = readline()
215 while line and not line.startswith('literal '):
215 while line and not line.startswith('literal '):
216 line = readline()
216 line = readline()
217 if not line:
217 if not line:
218 return None, i[0]
218 return None, i[0]
219 size = int(line[8:])
219 size = int(line[8:])
220 dec = []
220 dec = []
221 line = readline()
221 line = readline()
222 while line:
222 while line:
223 l = line[0]
223 l = line[0]
224 if l <= 'Z' and l >= 'A':
224 if l <= 'Z' and l >= 'A':
225 l = ord(l) - ord('A') + 1
225 l = ord(l) - ord('A') + 1
226 else:
226 else:
227 l = ord(l) - ord('a') + 27
227 l = ord(l) - ord('a') + 27
228 dec.append(base85.b85decode(line[1:])[:l])
228 dec.append(base85.b85decode(line[1:])[:l])
229 line = readline()
229 line = readline()
230 text = zlib.decompress(''.join(dec))
230 text = zlib.decompress(''.join(dec))
231 if len(text) != size:
231 if len(text) != size:
232 raise util.Abort(_('binary patch is %d bytes, not %d') %
232 raise util.Abort(_('binary patch is %d bytes, not %d') %
233 (len(text), size))
233 (len(text), size))
234 return text, i[0]
234 return text, i[0]
235
235
236 pf = file(patchname)
236 pf = file(patchname)
237 pfline = 1
237 pfline = 1
238
238
239 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
239 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
240 tmpfp = os.fdopen(fd, 'w')
240 tmpfp = os.fdopen(fd, 'w')
241
241
242 try:
242 try:
243 for i in xrange(len(gitpatches)):
243 for i in xrange(len(gitpatches)):
244 p = gitpatches[i]
244 p = gitpatches[i]
245 if not p.copymod and not p.binary:
245 if not p.copymod and not p.binary:
246 continue
246 continue
247
247
248 # rewrite patch hunk
248 # rewrite patch hunk
249 while pfline < p.lineno:
249 while pfline < p.lineno:
250 tmpfp.write(pf.readline())
250 tmpfp.write(pf.readline())
251 pfline += 1
251 pfline += 1
252
252
253 if p.binary:
253 if p.binary:
254 text, delta = extractbin(pf)
254 text, delta = extractbin(pf)
255 if not text:
255 if not text:
256 raise util.Abort(_('binary patch extraction failed'))
256 raise util.Abort(_('binary patch extraction failed'))
257 pfline += delta
257 pfline += delta
258 if not cwd:
258 if not cwd:
259 cwd = os.getcwd()
259 cwd = os.getcwd()
260 absdst = os.path.join(cwd, p.path)
260 absdst = os.path.join(cwd, p.path)
261 basedir = os.path.dirname(absdst)
261 basedir = os.path.dirname(absdst)
262 if not os.path.isdir(basedir):
262 if not os.path.isdir(basedir):
263 os.makedirs(basedir)
263 os.makedirs(basedir)
264 out = file(absdst, 'wb')
264 out = file(absdst, 'wb')
265 out.write(text)
265 out.write(text)
266 out.close()
266 out.close()
267 elif p.copymod:
267 elif p.copymod:
268 copyfile(p.oldpath, p.path, basedir=cwd)
268 copyfile(p.oldpath, p.path, basedir=cwd)
269 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
269 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
270 line = pf.readline()
270 line = pf.readline()
271 pfline += 1
271 pfline += 1
272 while not line.startswith('--- a/'):
272 while not line.startswith('--- a/'):
273 tmpfp.write(line)
273 tmpfp.write(line)
274 line = pf.readline()
274 line = pf.readline()
275 pfline += 1
275 pfline += 1
276 tmpfp.write('--- a/%s\n' % p.path)
276 tmpfp.write('--- a/%s\n' % p.path)
277
277
278 line = pf.readline()
278 line = pf.readline()
279 while line:
279 while line:
280 tmpfp.write(line)
280 tmpfp.write(line)
281 line = pf.readline()
281 line = pf.readline()
282 except:
282 except:
283 tmpfp.close()
283 tmpfp.close()
284 os.unlink(patchname)
284 os.unlink(patchname)
285 raise
285 raise
286
286
287 tmpfp.close()
287 tmpfp.close()
288 return patchname
288 return patchname
289
289
290 def patch(patchname, ui, strip=1, cwd=None, files={}):
290 def patch(patchname, ui, strip=1, cwd=None, files={}):
291 """apply the patch <patchname> to the working directory.
291 """apply the patch <patchname> to the working directory.
292 a list of patched files is returned"""
292 a list of patched files is returned"""
293
293
294 # helper function
294 # helper function
295 def __patch(patchname):
295 def __patch(patchname):
296 """patch and updates the files and fuzz variables"""
296 """patch and updates the files and fuzz variables"""
297 fuzz = False
297 fuzz = False
298
298
299 args = []
299 args = []
300 patcher = ui.config('ui', 'patch')
300 patcher = ui.config('ui', 'patch')
301 patcher = ((patcher and util.find_exe(patcher)) or
301 patcher = ((patcher and util.find_exe(patcher)) or
302 util.find_exe('gpatch') or
302 util.find_exe('gpatch') or
303 util.find_exe('patch'))
303 util.find_exe('patch'))
304 if not patcher:
304 if not patcher:
305 raise util.Abort(_('no patch command found in hgrc or PATH'))
305 raise util.Abort(_('no patch command found in hgrc or PATH'))
306 if util.needbinarypatch():
306 if util.needbinarypatch():
307 args.append('--binary')
307 args.append('--binary')
308
308
309 if cwd:
309 if cwd:
310 args.append('-d %s' % util.shellquote(cwd))
310 args.append('-d %s' % util.shellquote(cwd))
311 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
311 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
312 util.shellquote(patchname)))
312 util.shellquote(patchname)))
313
313
314 for line in fp:
314 for line in fp:
315 line = line.rstrip()
315 line = line.rstrip()
316 ui.note(line + '\n')
316 ui.note(line + '\n')
317 if line.startswith('patching file '):
317 if line.startswith('patching file '):
318 pf = util.parse_patch_output(line)
318 pf = util.parse_patch_output(line)
319 printed_file = False
319 printed_file = False
320 files.setdefault(pf, (None, None))
320 files.setdefault(pf, (None, None))
321 elif line.find('with fuzz') >= 0:
321 elif line.find('with fuzz') >= 0:
322 fuzz = True
322 fuzz = True
323 if not printed_file:
323 if not printed_file:
324 ui.warn(pf + '\n')
324 ui.warn(pf + '\n')
325 printed_file = True
325 printed_file = True
326 ui.warn(line + '\n')
326 ui.warn(line + '\n')
327 elif line.find('saving rejects to file') >= 0:
327 elif line.find('saving rejects to file') >= 0:
328 ui.warn(line + '\n')
328 ui.warn(line + '\n')
329 elif line.find('FAILED') >= 0:
329 elif line.find('FAILED') >= 0:
330 if not printed_file:
330 if not printed_file:
331 ui.warn(pf + '\n')
331 ui.warn(pf + '\n')
332 printed_file = True
332 printed_file = True
333 ui.warn(line + '\n')
333 ui.warn(line + '\n')
334 code = fp.close()
334 code = fp.close()
335 if code:
335 if code:
336 raise util.Abort(_("patch command failed: %s") %
336 raise util.Abort(_("patch command failed: %s") %
337 util.explain_exit(code)[0])
337 util.explain_exit(code)[0])
338 return fuzz
338 return fuzz
339
339
340 (dopatch, gitpatches) = readgitpatch(patchname)
340 (dopatch, gitpatches) = readgitpatch(patchname)
341 for gp in gitpatches:
341 for gp in gitpatches:
342 files[gp.path] = (gp.op, gp)
342 files[gp.path] = (gp.op, gp)
343
343
344 fuzz = False
344 fuzz = False
345 if dopatch:
345 if dopatch:
346 filterpatch = dopatch & (GP_FILTER | GP_BINARY)
346 filterpatch = dopatch & (GP_FILTER | GP_BINARY)
347 if filterpatch:
347 if filterpatch:
348 patchname = dogitpatch(patchname, gitpatches, cwd=cwd)
348 patchname = dogitpatch(patchname, gitpatches, cwd=cwd)
349 try:
349 try:
350 if dopatch & GP_PATCH:
350 if dopatch & GP_PATCH:
351 fuzz = __patch(patchname)
351 fuzz = __patch(patchname)
352 finally:
352 finally:
353 if filterpatch:
353 if filterpatch:
354 os.unlink(patchname)
354 os.unlink(patchname)
355
355
356 return fuzz
356 return fuzz
357
357
358 def diffopts(ui, opts={}, untrusted=False):
358 def diffopts(ui, opts={}, untrusted=False):
359 def get(key, name=None):
359 def get(key, name=None):
360 return (opts.get(key) or
360 return (opts.get(key) or
361 ui.configbool('diff', name or key, None, untrusted=untrusted))
361 ui.configbool('diff', name or key, None, untrusted=untrusted))
362 return mdiff.diffopts(
362 return mdiff.diffopts(
363 text=opts.get('text'),
363 text=opts.get('text'),
364 git=get('git'),
364 git=get('git'),
365 nodates=get('nodates'),
365 nodates=get('nodates'),
366 showfunc=get('show_function', 'showfunc'),
366 showfunc=get('show_function', 'showfunc'),
367 ignorews=get('ignore_all_space', 'ignorews'),
367 ignorews=get('ignore_all_space', 'ignorews'),
368 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
368 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
369 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'))
369 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'))
370
370
371 def updatedir(ui, repo, patches, wlock=None):
371 def updatedir(ui, repo, patches, wlock=None):
372 '''Update dirstate after patch application according to metadata'''
372 '''Update dirstate after patch application according to metadata'''
373 if not patches:
373 if not patches:
374 return
374 return
375 copies = []
375 copies = []
376 removes = {}
376 removes = {}
377 cfiles = patches.keys()
377 cfiles = patches.keys()
378 cwd = repo.getcwd()
378 cwd = repo.getcwd()
379 if cwd:
379 if cwd:
380 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
380 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
381 for f in patches:
381 for f in patches:
382 ctype, gp = patches[f]
382 ctype, gp = patches[f]
383 if ctype == 'RENAME':
383 if ctype == 'RENAME':
384 copies.append((gp.oldpath, gp.path, gp.copymod))
384 copies.append((gp.oldpath, gp.path, gp.copymod))
385 removes[gp.oldpath] = 1
385 removes[gp.oldpath] = 1
386 elif ctype == 'COPY':
386 elif ctype == 'COPY':
387 copies.append((gp.oldpath, gp.path, gp.copymod))
387 copies.append((gp.oldpath, gp.path, gp.copymod))
388 elif ctype == 'DELETE':
388 elif ctype == 'DELETE':
389 removes[gp.path] = 1
389 removes[gp.path] = 1
390 for src, dst, after in copies:
390 for src, dst, after in copies:
391 if not after:
391 if not after:
392 copyfile(src, dst, repo.root)
392 copyfile(src, dst, repo.root)
393 repo.copy(src, dst, wlock=wlock)
393 repo.copy(src, dst, wlock=wlock)
394 removes = removes.keys()
394 removes = removes.keys()
395 if removes:
395 if removes:
396 removes.sort()
396 removes.sort()
397 repo.remove(removes, True, wlock=wlock)
397 repo.remove(removes, True, wlock=wlock)
398 for f in patches:
398 for f in patches:
399 ctype, gp = patches[f]
399 ctype, gp = patches[f]
400 if gp and gp.mode:
400 if gp and gp.mode:
401 x = gp.mode & 0100 != 0
401 x = gp.mode & 0100 != 0
402 dst = os.path.join(repo.root, gp.path)
402 dst = os.path.join(repo.root, gp.path)
403 # patch won't create empty files
403 # patch won't create empty files
404 if ctype == 'ADD' and not os.path.exists(dst):
404 if ctype == 'ADD' and not os.path.exists(dst):
405 repo.wwrite(gp.path, '', x and 'x' or '')
405 repo.wwrite(gp.path, '', x and 'x' or '')
406 else:
406 else:
407 util.set_exec(dst, x)
407 util.set_exec(dst, x)
408 cmdutil.addremove(repo, cfiles, wlock=wlock)
408 cmdutil.addremove(repo, cfiles, wlock=wlock)
409 files = patches.keys()
409 files = patches.keys()
410 files.extend([r for r in removes if r not in files])
410 files.extend([r for r in removes if r not in files])
411 files.sort()
411 files.sort()
412
412
413 return files
413 return files
414
414
415 def b85diff(fp, to, tn):
415 def b85diff(fp, to, tn):
416 '''print base85-encoded binary diff'''
416 '''print base85-encoded binary diff'''
417 def gitindex(text):
417 def gitindex(text):
418 if not text:
418 if not text:
419 return '0' * 40
419 return '0' * 40
420 l = len(text)
420 l = len(text)
421 s = sha.new('blob %d\0' % l)
421 s = sha.new('blob %d\0' % l)
422 s.update(text)
422 s.update(text)
423 return s.hexdigest()
423 return s.hexdigest()
424
424
425 def fmtline(line):
425 def fmtline(line):
426 l = len(line)
426 l = len(line)
427 if l <= 26:
427 if l <= 26:
428 l = chr(ord('A') + l - 1)
428 l = chr(ord('A') + l - 1)
429 else:
429 else:
430 l = chr(l - 26 + ord('a') - 1)
430 l = chr(l - 26 + ord('a') - 1)
431 return '%c%s\n' % (l, base85.b85encode(line, True))
431 return '%c%s\n' % (l, base85.b85encode(line, True))
432
432
433 def chunk(text, csize=52):
433 def chunk(text, csize=52):
434 l = len(text)
434 l = len(text)
435 i = 0
435 i = 0
436 while i < l:
436 while i < l:
437 yield text[i:i+csize]
437 yield text[i:i+csize]
438 i += csize
438 i += csize
439
439
440 tohash = gitindex(to)
440 tohash = gitindex(to)
441 tnhash = gitindex(tn)
441 tnhash = gitindex(tn)
442 if tohash == tnhash:
442 if tohash == tnhash:
443 return ""
443 return ""
444
444
445 # TODO: deltas
445 # TODO: deltas
446 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
446 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
447 (tohash, tnhash, len(tn))]
447 (tohash, tnhash, len(tn))]
448 for l in chunk(zlib.compress(tn)):
448 for l in chunk(zlib.compress(tn)):
449 ret.append(fmtline(l))
449 ret.append(fmtline(l))
450 ret.append('\n')
450 ret.append('\n')
451 return ''.join(ret)
451 return ''.join(ret)
452
452
453 def diff(repo, node1=None, node2=None, files=None, match=util.always,
453 def diff(repo, node1=None, node2=None, files=None, match=util.always,
454 fp=None, changes=None, opts=None):
454 fp=None, changes=None, opts=None):
455 '''print diff of changes to files between two nodes, or node and
455 '''print diff of changes to files between two nodes, or node and
456 working directory.
456 working directory.
457
457
458 if node1 is None, use first dirstate parent instead.
458 if node1 is None, use first dirstate parent instead.
459 if node2 is None, compare node1 with working directory.'''
459 if node2 is None, compare node1 with working directory.'''
460
460
461 if opts is None:
461 if opts is None:
462 opts = mdiff.defaultopts
462 opts = mdiff.defaultopts
463 if fp is None:
463 if fp is None:
464 fp = repo.ui
464 fp = repo.ui
465
465
466 if not node1:
466 if not node1:
467 node1 = repo.dirstate.parents()[0]
467 node1 = repo.dirstate.parents()[0]
468
468
469 ccache = {}
469 ccache = {}
470 def getctx(r):
470 def getctx(r):
471 if r not in ccache:
471 if r not in ccache:
472 ccache[r] = context.changectx(repo, r)
472 ccache[r] = context.changectx(repo, r)
473 return ccache[r]
473 return ccache[r]
474
474
475 flcache = {}
475 flcache = {}
476 def getfilectx(f, ctx):
476 def getfilectx(f, ctx):
477 flctx = ctx.filectx(f, filelog=flcache.get(f))
477 flctx = ctx.filectx(f, filelog=flcache.get(f))
478 if f not in flcache:
478 if f not in flcache:
479 flcache[f] = flctx._filelog
479 flcache[f] = flctx._filelog
480 return flctx
480 return flctx
481
481
482 # reading the data for node1 early allows it to play nicely
482 # reading the data for node1 early allows it to play nicely
483 # with repo.status and the revlog cache.
483 # with repo.status and the revlog cache.
484 ctx1 = context.changectx(repo, node1)
484 ctx1 = context.changectx(repo, node1)
485 # force manifest reading
485 # force manifest reading
486 man1 = ctx1.manifest()
486 man1 = ctx1.manifest()
487 date1 = util.datestr(ctx1.date())
487 date1 = util.datestr(ctx1.date())
488
488
489 if not changes:
489 if not changes:
490 changes = repo.status(node1, node2, files, match=match)[:5]
490 changes = repo.status(node1, node2, files, match=match)[:5]
491 modified, added, removed, deleted, unknown = changes
491 modified, added, removed, deleted, unknown = changes
492
492
493 if not modified and not added and not removed:
493 if not modified and not added and not removed:
494 return
494 return
495
495
496 if node2:
496 if node2:
497 ctx2 = context.changectx(repo, node2)
497 ctx2 = context.changectx(repo, node2)
498 execf2 = ctx2.manifest().execf
498 else:
499 else:
499 ctx2 = context.workingctx(repo)
500 ctx2 = context.workingctx(repo)
500 man2 = ctx2.manifest()
501 execf2 = util.execfunc(repo.root, None)
502 if execf2 is None:
503 execf2 = ctx2.parents()[0].manifest().copy().execf
501
504
502 # returns False if there was no rename between ctx1 and ctx2
505 # returns False if there was no rename between ctx1 and ctx2
503 # returns None if the file was created between ctx1 and ctx2
506 # returns None if the file was created between ctx1 and ctx2
504 # returns the (file, node) present in ctx1 that was renamed to f in ctx2
507 # returns the (file, node) present in ctx1 that was renamed to f in ctx2
505 def renamed(f):
508 def renamed(f):
506 startrev = ctx1.rev()
509 startrev = ctx1.rev()
507 c = ctx2
510 c = ctx2
508 crev = c.rev()
511 crev = c.rev()
509 if crev is None:
512 if crev is None:
510 crev = repo.changelog.count()
513 crev = repo.changelog.count()
511 orig = f
514 orig = f
512 while crev > startrev:
515 while crev > startrev:
513 if f in c.files():
516 if f in c.files():
514 try:
517 try:
515 src = getfilectx(f, c).renamed()
518 src = getfilectx(f, c).renamed()
516 except revlog.LookupError:
519 except revlog.LookupError:
517 return None
520 return None
518 if src:
521 if src:
519 f = src[0]
522 f = src[0]
520 crev = c.parents()[0].rev()
523 crev = c.parents()[0].rev()
521 # try to reuse
524 # try to reuse
522 c = getctx(crev)
525 c = getctx(crev)
523 if f not in man1:
526 if f not in man1:
524 return None
527 return None
525 if f == orig:
528 if f == orig:
526 return False
529 return False
527 return f
530 return f
528
531
529 if repo.ui.quiet:
532 if repo.ui.quiet:
530 r = None
533 r = None
531 else:
534 else:
532 hexfunc = repo.ui.debugflag and hex or short
535 hexfunc = repo.ui.debugflag and hex or short
533 r = [hexfunc(node) for node in [node1, node2] if node]
536 r = [hexfunc(node) for node in [node1, node2] if node]
534
537
535 if opts.git:
538 if opts.git:
536 copied = {}
539 copied = {}
537 for f in added:
540 for f in added:
538 src = renamed(f)
541 src = renamed(f)
539 if src:
542 if src:
540 copied[f] = src
543 copied[f] = src
541 srcs = [x[1] for x in copied.items()]
544 srcs = [x[1] for x in copied.items()]
542
545
543 all = modified + added + removed
546 all = modified + added + removed
544 all.sort()
547 all.sort()
545 gone = {}
548 gone = {}
546
549
547 for f in all:
550 for f in all:
548 to = None
551 to = None
549 tn = None
552 tn = None
550 dodiff = True
553 dodiff = True
551 header = []
554 header = []
552 if f in man1:
555 if f in man1:
553 to = getfilectx(f, ctx1).data()
556 to = getfilectx(f, ctx1).data()
554 if f not in removed:
557 if f not in removed:
555 tn = getfilectx(f, ctx2).data()
558 tn = getfilectx(f, ctx2).data()
556 if opts.git:
559 if opts.git:
557 def gitmode(x):
560 def gitmode(x):
558 return x and '100755' or '100644'
561 return x and '100755' or '100644'
559 def addmodehdr(header, omode, nmode):
562 def addmodehdr(header, omode, nmode):
560 if omode != nmode:
563 if omode != nmode:
561 header.append('old mode %s\n' % omode)
564 header.append('old mode %s\n' % omode)
562 header.append('new mode %s\n' % nmode)
565 header.append('new mode %s\n' % nmode)
563
566
564 a, b = f, f
567 a, b = f, f
565 if f in added:
568 if f in added:
566 mode = gitmode(man2.execf(f))
569 mode = gitmode(execf2(f))
567 if f in copied:
570 if f in copied:
568 a = copied[f]
571 a = copied[f]
569 omode = gitmode(man1.execf(a))
572 omode = gitmode(man1.execf(a))
570 addmodehdr(header, omode, mode)
573 addmodehdr(header, omode, mode)
571 if a in removed and a not in gone:
574 if a in removed and a not in gone:
572 op = 'rename'
575 op = 'rename'
573 gone[a] = 1
576 gone[a] = 1
574 else:
577 else:
575 op = 'copy'
578 op = 'copy'
576 header.append('%s from %s\n' % (op, a))
579 header.append('%s from %s\n' % (op, a))
577 header.append('%s to %s\n' % (op, f))
580 header.append('%s to %s\n' % (op, f))
578 to = getfilectx(a, ctx1).data()
581 to = getfilectx(a, ctx1).data()
579 else:
582 else:
580 header.append('new file mode %s\n' % mode)
583 header.append('new file mode %s\n' % mode)
581 if util.binary(tn):
584 if util.binary(tn):
582 dodiff = 'binary'
585 dodiff = 'binary'
583 elif f in removed:
586 elif f in removed:
584 if f in srcs:
587 if f in srcs:
585 dodiff = False
588 dodiff = False
586 else:
589 else:
587 mode = gitmode(man1.execf(f))
590 mode = gitmode(man1.execf(f))
588 header.append('deleted file mode %s\n' % mode)
591 header.append('deleted file mode %s\n' % mode)
589 else:
592 else:
590 omode = gitmode(man1.execf(f))
593 omode = gitmode(man1.execf(f))
591 nmode = gitmode(man2.execf(f))
594 nmode = gitmode(execf2(f))
592 addmodehdr(header, omode, nmode)
595 addmodehdr(header, omode, nmode)
593 if util.binary(to) or util.binary(tn):
596 if util.binary(to) or util.binary(tn):
594 dodiff = 'binary'
597 dodiff = 'binary'
595 r = None
598 r = None
596 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
599 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
597 if dodiff:
600 if dodiff:
598 if dodiff == 'binary':
601 if dodiff == 'binary':
599 text = b85diff(fp, to, tn)
602 text = b85diff(fp, to, tn)
600 else:
603 else:
601 text = mdiff.unidiff(to, date1,
604 text = mdiff.unidiff(to, date1,
602 # ctx2 date may be dynamic
605 # ctx2 date may be dynamic
603 tn, util.datestr(ctx2.date()),
606 tn, util.datestr(ctx2.date()),
604 f, r, opts=opts)
607 f, r, opts=opts)
605 if text or len(header) > 1:
608 if text or len(header) > 1:
606 fp.write(''.join(header))
609 fp.write(''.join(header))
607 fp.write(text)
610 fp.write(text)
608
611
609 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
612 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
610 opts=None):
613 opts=None):
611 '''export changesets as hg patches.'''
614 '''export changesets as hg patches.'''
612
615
613 total = len(revs)
616 total = len(revs)
614 revwidth = max([len(str(rev)) for rev in revs])
617 revwidth = max([len(str(rev)) for rev in revs])
615
618
616 def single(rev, seqno, fp):
619 def single(rev, seqno, fp):
617 ctx = repo.changectx(rev)
620 ctx = repo.changectx(rev)
618 node = ctx.node()
621 node = ctx.node()
619 parents = [p.node() for p in ctx.parents() if p]
622 parents = [p.node() for p in ctx.parents() if p]
620 branch = ctx.branch()
623 branch = ctx.branch()
621 if switch_parent:
624 if switch_parent:
622 parents.reverse()
625 parents.reverse()
623 prev = (parents and parents[0]) or nullid
626 prev = (parents and parents[0]) or nullid
624
627
625 if not fp:
628 if not fp:
626 fp = cmdutil.make_file(repo, template, node, total=total,
629 fp = cmdutil.make_file(repo, template, node, total=total,
627 seqno=seqno, revwidth=revwidth)
630 seqno=seqno, revwidth=revwidth)
628 if fp != sys.stdout and hasattr(fp, 'name'):
631 if fp != sys.stdout and hasattr(fp, 'name'):
629 repo.ui.note("%s\n" % fp.name)
632 repo.ui.note("%s\n" % fp.name)
630
633
631 fp.write("# HG changeset patch\n")
634 fp.write("# HG changeset patch\n")
632 fp.write("# User %s\n" % ctx.user())
635 fp.write("# User %s\n" % ctx.user())
633 fp.write("# Date %d %d\n" % ctx.date())
636 fp.write("# Date %d %d\n" % ctx.date())
634 if branch and (branch != 'default'):
637 if branch and (branch != 'default'):
635 fp.write("# Branch %s\n" % branch)
638 fp.write("# Branch %s\n" % branch)
636 fp.write("# Node ID %s\n" % hex(node))
639 fp.write("# Node ID %s\n" % hex(node))
637 fp.write("# Parent %s\n" % hex(prev))
640 fp.write("# Parent %s\n" % hex(prev))
638 if len(parents) > 1:
641 if len(parents) > 1:
639 fp.write("# Parent %s\n" % hex(parents[1]))
642 fp.write("# Parent %s\n" % hex(parents[1]))
640 fp.write(ctx.description().rstrip())
643 fp.write(ctx.description().rstrip())
641 fp.write("\n\n")
644 fp.write("\n\n")
642
645
643 diff(repo, prev, node, fp=fp, opts=opts)
646 diff(repo, prev, node, fp=fp, opts=opts)
644 if fp not in (sys.stdout, repo.ui):
647 if fp not in (sys.stdout, repo.ui):
645 fp.close()
648 fp.close()
646
649
647 for seqno, rev in enumerate(revs):
650 for seqno, rev in enumerate(revs):
648 single(rev, seqno+1, fp)
651 single(rev, seqno+1, fp)
649
652
650 def diffstat(patchlines):
653 def diffstat(patchlines):
651 if not util.find_exe('diffstat'):
654 if not util.find_exe('diffstat'):
652 return
655 return
653 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
656 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
654 try:
657 try:
655 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
658 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
656 try:
659 try:
657 for line in patchlines: print >> p.tochild, line
660 for line in patchlines: print >> p.tochild, line
658 p.tochild.close()
661 p.tochild.close()
659 if p.wait(): return
662 if p.wait(): return
660 fp = os.fdopen(fd, 'r')
663 fp = os.fdopen(fd, 'r')
661 stat = []
664 stat = []
662 for line in fp: stat.append(line.lstrip())
665 for line in fp: stat.append(line.lstrip())
663 last = stat.pop()
666 last = stat.pop()
664 stat.insert(0, last)
667 stat.insert(0, last)
665 stat = ''.join(stat)
668 stat = ''.join(stat)
666 if stat.startswith('0 files'): raise ValueError
669 if stat.startswith('0 files'): raise ValueError
667 return stat
670 return stat
668 except: raise
671 except: raise
669 finally:
672 finally:
670 try: os.unlink(name)
673 try: os.unlink(name)
671 except: pass
674 except: pass
General Comments 0
You need to be logged in to leave comments. Login now