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