##// END OF EJS Templates
remove obsolete code from patch.diff...
Alexis S. L. Carvalho -
r4184:da058899 default
parent child Browse files
Show More
@@ -1,684 +1,673 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(cwd, f) for f in patches.keys()]
356 cfiles = [util.pathto(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:
470 def filterfiles(filters):
471 l = [x for x in filters if x in files]
472
473 for t in files:
474 if not t.endswith("/"):
475 t += "/"
476 l += [x for x in filters if x.startswith(t)]
477 return l
478
479 modified, added, removed = map(filterfiles, (modified, added, removed))
480
469
481 if not modified and not added and not removed:
470 if not modified and not added and not removed:
482 return
471 return
483
472
484 # returns False if there was no rename between n1 and n2
473 # returns False if there was no rename between n1 and n2
485 # returns None if the file was created between n1 and n2
474 # 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
475 # returns the (file, node) present in n1 that was renamed to f in n2
487 def renamedbetween(f, n1, n2):
476 def renamedbetween(f, n1, n2):
488 r1, r2 = map(repo.changelog.rev, (n1, n2))
477 r1, r2 = map(repo.changelog.rev, (n1, n2))
489 orig = f
478 orig = f
490 src = None
479 src = None
491 while r2 > r1:
480 while r2 > r1:
492 cl = getchangelog(n2)
481 cl = getchangelog(n2)
493 if f in cl[3]:
482 if f in cl[3]:
494 m = getmanifest(cl[0])
483 m = getmanifest(cl[0])
495 try:
484 try:
496 src = getfile(f).renamed(m[f])
485 src = getfile(f).renamed(m[f])
497 except KeyError:
486 except KeyError:
498 return None
487 return None
499 if src:
488 if src:
500 f = src[0]
489 f = src[0]
501 n2 = repo.changelog.parents(n2)[0]
490 n2 = repo.changelog.parents(n2)[0]
502 r2 = repo.changelog.rev(n2)
491 r2 = repo.changelog.rev(n2)
503 cl = getchangelog(n1)
492 cl = getchangelog(n1)
504 m = getmanifest(cl[0])
493 m = getmanifest(cl[0])
505 if f not in m:
494 if f not in m:
506 return None
495 return None
507 if f == orig:
496 if f == orig:
508 return False
497 return False
509 return f, m[f]
498 return f, m[f]
510
499
511 if node2:
500 if node2:
512 change = getchangelog(node2)
501 change = getchangelog(node2)
513 mmap2 = getmanifest(change[0])
502 mmap2 = getmanifest(change[0])
514 _date2 = util.datestr(change[2])
503 _date2 = util.datestr(change[2])
515 def date2(f):
504 def date2(f):
516 return _date2
505 return _date2
517 def read(f):
506 def read(f):
518 return getfile(f).read(mmap2[f])
507 return getfile(f).read(mmap2[f])
519 def renamed(f):
508 def renamed(f):
520 return renamedbetween(f, node1, node2)
509 return renamedbetween(f, node1, node2)
521 else:
510 else:
522 tz = util.makedate()[1]
511 tz = util.makedate()[1]
523 _date2 = util.datestr()
512 _date2 = util.datestr()
524 def date2(f):
513 def date2(f):
525 try:
514 try:
526 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
515 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
527 except OSError, err:
516 except OSError, err:
528 if err.errno != errno.ENOENT: raise
517 if err.errno != errno.ENOENT: raise
529 return _date2
518 return _date2
530 def read(f):
519 def read(f):
531 return repo.wread(f)
520 return repo.wread(f)
532 def renamed(f):
521 def renamed(f):
533 src = repo.dirstate.copied(f)
522 src = repo.dirstate.copied(f)
534 parent = repo.dirstate.parents()[0]
523 parent = repo.dirstate.parents()[0]
535 if src:
524 if src:
536 f = src
525 f = src
537 of = renamedbetween(f, node1, parent)
526 of = renamedbetween(f, node1, parent)
538 if of or of is None:
527 if of or of is None:
539 return of
528 return of
540 elif src:
529 elif src:
541 cl = getchangelog(parent)[0]
530 cl = getchangelog(parent)[0]
542 return (src, getmanifest(cl)[src])
531 return (src, getmanifest(cl)[src])
543 else:
532 else:
544 return None
533 return None
545
534
546 if repo.ui.quiet:
535 if repo.ui.quiet:
547 r = None
536 r = None
548 else:
537 else:
549 hexfunc = repo.ui.debugflag and hex or short
538 hexfunc = repo.ui.debugflag and hex or short
550 r = [hexfunc(node) for node in [node1, node2] if node]
539 r = [hexfunc(node) for node in [node1, node2] if node]
551
540
552 if opts.git:
541 if opts.git:
553 copied = {}
542 copied = {}
554 for f in added:
543 for f in added:
555 src = renamed(f)
544 src = renamed(f)
556 if src:
545 if src:
557 copied[f] = src
546 copied[f] = src
558 srcs = [x[1][0] for x in copied.items()]
547 srcs = [x[1][0] for x in copied.items()]
559
548
560 all = modified + added + removed
549 all = modified + added + removed
561 all.sort()
550 all.sort()
562 gone = {}
551 gone = {}
563 for f in all:
552 for f in all:
564 to = None
553 to = None
565 tn = None
554 tn = None
566 dodiff = True
555 dodiff = True
567 header = []
556 header = []
568 if f in mmap:
557 if f in mmap:
569 to = getfile(f).read(mmap[f])
558 to = getfile(f).read(mmap[f])
570 if f not in removed:
559 if f not in removed:
571 tn = read(f)
560 tn = read(f)
572 if opts.git:
561 if opts.git:
573 def gitmode(x):
562 def gitmode(x):
574 return x and '100755' or '100644'
563 return x and '100755' or '100644'
575 def addmodehdr(header, omode, nmode):
564 def addmodehdr(header, omode, nmode):
576 if omode != nmode:
565 if omode != nmode:
577 header.append('old mode %s\n' % omode)
566 header.append('old mode %s\n' % omode)
578 header.append('new mode %s\n' % nmode)
567 header.append('new mode %s\n' % nmode)
579
568
580 a, b = f, f
569 a, b = f, f
581 if f in added:
570 if f in added:
582 if node2:
571 if node2:
583 mode = gitmode(mmap2.execf(f))
572 mode = gitmode(mmap2.execf(f))
584 else:
573 else:
585 mode = gitmode(util.is_exec(repo.wjoin(f), None))
574 mode = gitmode(util.is_exec(repo.wjoin(f), None))
586 if f in copied:
575 if f in copied:
587 a, arev = copied[f]
576 a, arev = copied[f]
588 omode = gitmode(mmap.execf(a))
577 omode = gitmode(mmap.execf(a))
589 addmodehdr(header, omode, mode)
578 addmodehdr(header, omode, mode)
590 if a in removed and a not in gone:
579 if a in removed and a not in gone:
591 op = 'rename'
580 op = 'rename'
592 gone[a] = 1
581 gone[a] = 1
593 else:
582 else:
594 op = 'copy'
583 op = 'copy'
595 header.append('%s from %s\n' % (op, a))
584 header.append('%s from %s\n' % (op, a))
596 header.append('%s to %s\n' % (op, f))
585 header.append('%s to %s\n' % (op, f))
597 to = getfile(a).read(arev)
586 to = getfile(a).read(arev)
598 else:
587 else:
599 header.append('new file mode %s\n' % mode)
588 header.append('new file mode %s\n' % mode)
600 if util.binary(tn):
589 if util.binary(tn):
601 dodiff = 'binary'
590 dodiff = 'binary'
602 elif f in removed:
591 elif f in removed:
603 if f in srcs:
592 if f in srcs:
604 dodiff = False
593 dodiff = False
605 else:
594 else:
606 mode = gitmode(mmap.execf(f))
595 mode = gitmode(mmap.execf(f))
607 header.append('deleted file mode %s\n' % mode)
596 header.append('deleted file mode %s\n' % mode)
608 else:
597 else:
609 omode = gitmode(mmap.execf(f))
598 omode = gitmode(mmap.execf(f))
610 if node2:
599 if node2:
611 nmode = gitmode(mmap2.execf(f))
600 nmode = gitmode(mmap2.execf(f))
612 else:
601 else:
613 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
602 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
614 addmodehdr(header, omode, nmode)
603 addmodehdr(header, omode, nmode)
615 if util.binary(to) or util.binary(tn):
604 if util.binary(to) or util.binary(tn):
616 dodiff = 'binary'
605 dodiff = 'binary'
617 r = None
606 r = None
618 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
607 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
619 if dodiff:
608 if dodiff:
620 if dodiff == 'binary':
609 if dodiff == 'binary':
621 text = b85diff(fp, to, tn)
610 text = b85diff(fp, to, tn)
622 else:
611 else:
623 text = mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts)
612 text = mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts)
624 if text or len(header) > 1:
613 if text or len(header) > 1:
625 fp.write(''.join(header))
614 fp.write(''.join(header))
626 fp.write(text)
615 fp.write(text)
627
616
628 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
617 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
629 opts=None):
618 opts=None):
630 '''export changesets as hg patches.'''
619 '''export changesets as hg patches.'''
631
620
632 total = len(revs)
621 total = len(revs)
633 revwidth = max([len(str(rev)) for rev in revs])
622 revwidth = max([len(str(rev)) for rev in revs])
634
623
635 def single(node, seqno, fp):
624 def single(node, seqno, fp):
636 parents = [p for p in repo.changelog.parents(node) if p != nullid]
625 parents = [p for p in repo.changelog.parents(node) if p != nullid]
637 if switch_parent:
626 if switch_parent:
638 parents.reverse()
627 parents.reverse()
639 prev = (parents and parents[0]) or nullid
628 prev = (parents and parents[0]) or nullid
640 change = repo.changelog.read(node)
629 change = repo.changelog.read(node)
641
630
642 if not fp:
631 if not fp:
643 fp = cmdutil.make_file(repo, template, node, total=total,
632 fp = cmdutil.make_file(repo, template, node, total=total,
644 seqno=seqno, revwidth=revwidth)
633 seqno=seqno, revwidth=revwidth)
645 if fp not in (sys.stdout, repo.ui):
634 if fp not in (sys.stdout, repo.ui):
646 repo.ui.note("%s\n" % fp.name)
635 repo.ui.note("%s\n" % fp.name)
647
636
648 fp.write("# HG changeset patch\n")
637 fp.write("# HG changeset patch\n")
649 fp.write("# User %s\n" % change[1])
638 fp.write("# User %s\n" % change[1])
650 fp.write("# Date %d %d\n" % change[2])
639 fp.write("# Date %d %d\n" % change[2])
651 fp.write("# Node ID %s\n" % hex(node))
640 fp.write("# Node ID %s\n" % hex(node))
652 fp.write("# Parent %s\n" % hex(prev))
641 fp.write("# Parent %s\n" % hex(prev))
653 if len(parents) > 1:
642 if len(parents) > 1:
654 fp.write("# Parent %s\n" % hex(parents[1]))
643 fp.write("# Parent %s\n" % hex(parents[1]))
655 fp.write(change[4].rstrip())
644 fp.write(change[4].rstrip())
656 fp.write("\n\n")
645 fp.write("\n\n")
657
646
658 diff(repo, prev, node, fp=fp, opts=opts)
647 diff(repo, prev, node, fp=fp, opts=opts)
659 if fp not in (sys.stdout, repo.ui):
648 if fp not in (sys.stdout, repo.ui):
660 fp.close()
649 fp.close()
661
650
662 for seqno, rev in enumerate(revs):
651 for seqno, rev in enumerate(revs):
663 single(repo.lookup(rev), seqno+1, fp)
652 single(repo.lookup(rev), seqno+1, fp)
664
653
665 def diffstat(patchlines):
654 def diffstat(patchlines):
666 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
655 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
667 try:
656 try:
668 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
657 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
669 try:
658 try:
670 for line in patchlines: print >> p.tochild, line
659 for line in patchlines: print >> p.tochild, line
671 p.tochild.close()
660 p.tochild.close()
672 if p.wait(): return
661 if p.wait(): return
673 fp = os.fdopen(fd, 'r')
662 fp = os.fdopen(fd, 'r')
674 stat = []
663 stat = []
675 for line in fp: stat.append(line.lstrip())
664 for line in fp: stat.append(line.lstrip())
676 last = stat.pop()
665 last = stat.pop()
677 stat.insert(0, last)
666 stat.insert(0, last)
678 stat = ''.join(stat)
667 stat = ''.join(stat)
679 if stat.startswith('0 files'): raise ValueError
668 if stat.startswith('0 files'): raise ValueError
680 return stat
669 return stat
681 except: raise
670 except: raise
682 finally:
671 finally:
683 try: os.unlink(name)
672 try: os.unlink(name)
684 except: pass
673 except: pass
General Comments 0
You need to be logged in to leave comments. Login now