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