##// END OF EJS Templates
Make sequence number on hg export start at 1 (as documented for %n). Add test.
Thomas Arendsen Hein -
r3899:504dee0a default
parent child Browse files
Show More
@@ -0,0 +1,15 b''
1 #!/bin/sh
2
3 hg init repo
4 cd repo
5 touch foo
6 hg add foo
7 for i in 0 1 2 3 4 5 6 7 8 9 10 11; do
8 echo "foo-$i" >> foo
9 hg ci -m "foo-$i" -d "0 0"
10 done
11
12 for out in "%nof%N" "%%%H" "%b-%R" "%h" "%r"; do
13 echo "# foo-$out.patch"
14 hg export -v -o "foo-$out.patch" 2:tip
15 done
@@ -0,0 +1,60 b''
1 # foo-%nof%N.patch
2 exporting patches:
3 foo-01of10.patch
4 foo-02of10.patch
5 foo-03of10.patch
6 foo-04of10.patch
7 foo-05of10.patch
8 foo-06of10.patch
9 foo-07of10.patch
10 foo-08of10.patch
11 foo-09of10.patch
12 foo-10of10.patch
13 # foo-%%%H.patch
14 exporting patches:
15 foo-%617188a1c80f869a7b66c85134da88a6fb145f67.patch
16 foo-%dd41a5ff707a5225204105611ba49cc5c229d55f.patch
17 foo-%f95a5410f8664b6e1490a4af654e4b7d41a7b321.patch
18 foo-%4346bcfde53b4d9042489078bcfa9c3e28201db2.patch
19 foo-%afda8c3a009cc99449a05ad8aa4655648c4ecd34.patch
20 foo-%35284ce2b6b99c9d2ac66268fe99e68e1974e1aa.patch
21 foo-%9688c41894e6931305fa7165a37f6568050b4e9b.patch
22 foo-%747d3c68f8ec44bb35816bfcd59aeb50b9654c2f.patch
23 foo-%5f17a83f5fbd9414006a5e563eab4c8a00729efd.patch
24 foo-%f3acbafac161ec68f1598af38f794f28847ca5d3.patch
25 # foo-%b-%R.patch
26 exporting patches:
27 foo-repo-2.patch
28 foo-repo-3.patch
29 foo-repo-4.patch
30 foo-repo-5.patch
31 foo-repo-6.patch
32 foo-repo-7.patch
33 foo-repo-8.patch
34 foo-repo-9.patch
35 foo-repo-10.patch
36 foo-repo-11.patch
37 # foo-%h.patch
38 exporting patches:
39 foo-617188a1c80f.patch
40 foo-dd41a5ff707a.patch
41 foo-f95a5410f866.patch
42 foo-4346bcfde53b.patch
43 foo-afda8c3a009c.patch
44 foo-35284ce2b6b9.patch
45 foo-9688c41894e6.patch
46 foo-747d3c68f8ec.patch
47 foo-5f17a83f5fbd.patch
48 foo-f3acbafac161.patch
49 # foo-%r.patch
50 exporting patches:
51 foo-00000000000000000002.patch
52 foo-00000000000000000003.patch
53 foo-00000000000000000004.patch
54 foo-00000000000000000005.patch
55 foo-00000000000000000006.patch
56 foo-00000000000000000007.patch
57 foo-00000000000000000008.patch
58 foo-00000000000000000009.patch
59 foo-00000000000000000010.patch
60 foo-00000000000000000011.patch
@@ -1,680 +1,680 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 # TODO: deltas
415 # TODO: deltas
416 l = len(tn)
416 l = len(tn)
417 fp.write('index %s..%s\nGIT binary patch\nliteral %s\n' %
417 fp.write('index %s..%s\nGIT binary patch\nliteral %s\n' %
418 (gitindex(to), gitindex(tn), len(tn)))
418 (gitindex(to), gitindex(tn), len(tn)))
419
419
420 tn = ''.join([fmtline(l) for l in chunk(zlib.compress(tn))])
420 tn = ''.join([fmtline(l) for l in chunk(zlib.compress(tn))])
421 fp.write(tn)
421 fp.write(tn)
422 fp.write('\n')
422 fp.write('\n')
423
423
424 def diff(repo, node1=None, node2=None, files=None, match=util.always,
424 def diff(repo, node1=None, node2=None, files=None, match=util.always,
425 fp=None, changes=None, opts=None):
425 fp=None, changes=None, opts=None):
426 '''print diff of changes to files between two nodes, or node and
426 '''print diff of changes to files between two nodes, or node and
427 working directory.
427 working directory.
428
428
429 if node1 is None, use first dirstate parent instead.
429 if node1 is None, use first dirstate parent instead.
430 if node2 is None, compare node1 with working directory.'''
430 if node2 is None, compare node1 with working directory.'''
431
431
432 if opts is None:
432 if opts is None:
433 opts = mdiff.defaultopts
433 opts = mdiff.defaultopts
434 if fp is None:
434 if fp is None:
435 fp = repo.ui
435 fp = repo.ui
436
436
437 if not node1:
437 if not node1:
438 node1 = repo.dirstate.parents()[0]
438 node1 = repo.dirstate.parents()[0]
439
439
440 clcache = {}
440 clcache = {}
441 def getchangelog(n):
441 def getchangelog(n):
442 if n not in clcache:
442 if n not in clcache:
443 clcache[n] = repo.changelog.read(n)
443 clcache[n] = repo.changelog.read(n)
444 return clcache[n]
444 return clcache[n]
445 mcache = {}
445 mcache = {}
446 def getmanifest(n):
446 def getmanifest(n):
447 if n not in mcache:
447 if n not in mcache:
448 mcache[n] = repo.manifest.read(n)
448 mcache[n] = repo.manifest.read(n)
449 return mcache[n]
449 return mcache[n]
450 fcache = {}
450 fcache = {}
451 def getfile(f):
451 def getfile(f):
452 if f not in fcache:
452 if f not in fcache:
453 fcache[f] = repo.file(f)
453 fcache[f] = repo.file(f)
454 return fcache[f]
454 return fcache[f]
455
455
456 # reading the data for node1 early allows it to play nicely
456 # reading the data for node1 early allows it to play nicely
457 # with repo.status and the revlog cache.
457 # with repo.status and the revlog cache.
458 change = getchangelog(node1)
458 change = getchangelog(node1)
459 mmap = getmanifest(change[0])
459 mmap = getmanifest(change[0])
460 date1 = util.datestr(change[2])
460 date1 = util.datestr(change[2])
461
461
462 if not changes:
462 if not changes:
463 changes = repo.status(node1, node2, files, match=match)[:5]
463 changes = repo.status(node1, node2, files, match=match)[:5]
464 modified, added, removed, deleted, unknown = changes
464 modified, added, removed, deleted, unknown = changes
465 if files:
465 if files:
466 def filterfiles(filters):
466 def filterfiles(filters):
467 l = [x for x in filters if x in files]
467 l = [x for x in filters if x in files]
468
468
469 for t in files:
469 for t in files:
470 if not t.endswith("/"):
470 if not t.endswith("/"):
471 t += "/"
471 t += "/"
472 l += [x for x in filters if x.startswith(t)]
472 l += [x for x in filters if x.startswith(t)]
473 return l
473 return l
474
474
475 modified, added, removed = map(filterfiles, (modified, added, removed))
475 modified, added, removed = map(filterfiles, (modified, added, removed))
476
476
477 if not modified and not added and not removed:
477 if not modified and not added and not removed:
478 return
478 return
479
479
480 # returns False if there was no rename between n1 and n2
480 # returns False if there was no rename between n1 and n2
481 # returns None if the file was created between n1 and n2
481 # returns None if the file was created between n1 and n2
482 # returns the (file, node) present in n1 that was renamed to f in n2
482 # returns the (file, node) present in n1 that was renamed to f in n2
483 def renamedbetween(f, n1, n2):
483 def renamedbetween(f, n1, n2):
484 r1, r2 = map(repo.changelog.rev, (n1, n2))
484 r1, r2 = map(repo.changelog.rev, (n1, n2))
485 orig = f
485 orig = f
486 src = None
486 src = None
487 while r2 > r1:
487 while r2 > r1:
488 cl = getchangelog(n2)
488 cl = getchangelog(n2)
489 if f in cl[3]:
489 if f in cl[3]:
490 m = getmanifest(cl[0])
490 m = getmanifest(cl[0])
491 try:
491 try:
492 src = getfile(f).renamed(m[f])
492 src = getfile(f).renamed(m[f])
493 except KeyError:
493 except KeyError:
494 return None
494 return None
495 if src:
495 if src:
496 f = src[0]
496 f = src[0]
497 n2 = repo.changelog.parents(n2)[0]
497 n2 = repo.changelog.parents(n2)[0]
498 r2 = repo.changelog.rev(n2)
498 r2 = repo.changelog.rev(n2)
499 cl = getchangelog(n1)
499 cl = getchangelog(n1)
500 m = getmanifest(cl[0])
500 m = getmanifest(cl[0])
501 if f not in m:
501 if f not in m:
502 return None
502 return None
503 if f == orig:
503 if f == orig:
504 return False
504 return False
505 return f, m[f]
505 return f, m[f]
506
506
507 if node2:
507 if node2:
508 change = getchangelog(node2)
508 change = getchangelog(node2)
509 mmap2 = getmanifest(change[0])
509 mmap2 = getmanifest(change[0])
510 _date2 = util.datestr(change[2])
510 _date2 = util.datestr(change[2])
511 def date2(f):
511 def date2(f):
512 return _date2
512 return _date2
513 def read(f):
513 def read(f):
514 return getfile(f).read(mmap2[f])
514 return getfile(f).read(mmap2[f])
515 def renamed(f):
515 def renamed(f):
516 return renamedbetween(f, node1, node2)
516 return renamedbetween(f, node1, node2)
517 else:
517 else:
518 tz = util.makedate()[1]
518 tz = util.makedate()[1]
519 _date2 = util.datestr()
519 _date2 = util.datestr()
520 def date2(f):
520 def date2(f):
521 try:
521 try:
522 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
522 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
523 except OSError, err:
523 except OSError, err:
524 if err.errno != errno.ENOENT: raise
524 if err.errno != errno.ENOENT: raise
525 return _date2
525 return _date2
526 def read(f):
526 def read(f):
527 return repo.wread(f)
527 return repo.wread(f)
528 def renamed(f):
528 def renamed(f):
529 src = repo.dirstate.copied(f)
529 src = repo.dirstate.copied(f)
530 parent = repo.dirstate.parents()[0]
530 parent = repo.dirstate.parents()[0]
531 if src:
531 if src:
532 f = src
532 f = src
533 of = renamedbetween(f, node1, parent)
533 of = renamedbetween(f, node1, parent)
534 if of or of is None:
534 if of or of is None:
535 return of
535 return of
536 elif src:
536 elif src:
537 cl = getchangelog(parent)[0]
537 cl = getchangelog(parent)[0]
538 return (src, getmanifest(cl)[src])
538 return (src, getmanifest(cl)[src])
539 else:
539 else:
540 return None
540 return None
541
541
542 if repo.ui.quiet:
542 if repo.ui.quiet:
543 r = None
543 r = None
544 else:
544 else:
545 hexfunc = repo.ui.debugflag and hex or short
545 hexfunc = repo.ui.debugflag and hex or short
546 r = [hexfunc(node) for node in [node1, node2] if node]
546 r = [hexfunc(node) for node in [node1, node2] if node]
547
547
548 if opts.git:
548 if opts.git:
549 copied = {}
549 copied = {}
550 for f in added:
550 for f in added:
551 src = renamed(f)
551 src = renamed(f)
552 if src:
552 if src:
553 copied[f] = src
553 copied[f] = src
554 srcs = [x[1][0] for x in copied.items()]
554 srcs = [x[1][0] for x in copied.items()]
555
555
556 all = modified + added + removed
556 all = modified + added + removed
557 all.sort()
557 all.sort()
558 gone = {}
558 gone = {}
559 for f in all:
559 for f in all:
560 to = None
560 to = None
561 tn = None
561 tn = None
562 dodiff = True
562 dodiff = True
563 header = []
563 header = []
564 if f in mmap:
564 if f in mmap:
565 to = getfile(f).read(mmap[f])
565 to = getfile(f).read(mmap[f])
566 if f not in removed:
566 if f not in removed:
567 tn = read(f)
567 tn = read(f)
568 if opts.git:
568 if opts.git:
569 def gitmode(x):
569 def gitmode(x):
570 return x and '100755' or '100644'
570 return x and '100755' or '100644'
571 def addmodehdr(header, omode, nmode):
571 def addmodehdr(header, omode, nmode):
572 if omode != nmode:
572 if omode != nmode:
573 header.append('old mode %s\n' % omode)
573 header.append('old mode %s\n' % omode)
574 header.append('new mode %s\n' % nmode)
574 header.append('new mode %s\n' % nmode)
575
575
576 a, b = f, f
576 a, b = f, f
577 if f in added:
577 if f in added:
578 if node2:
578 if node2:
579 mode = gitmode(mmap2.execf(f))
579 mode = gitmode(mmap2.execf(f))
580 else:
580 else:
581 mode = gitmode(util.is_exec(repo.wjoin(f), None))
581 mode = gitmode(util.is_exec(repo.wjoin(f), None))
582 if f in copied:
582 if f in copied:
583 a, arev = copied[f]
583 a, arev = copied[f]
584 omode = gitmode(mmap.execf(a))
584 omode = gitmode(mmap.execf(a))
585 addmodehdr(header, omode, mode)
585 addmodehdr(header, omode, mode)
586 if a in removed and a not in gone:
586 if a in removed and a not in gone:
587 op = 'rename'
587 op = 'rename'
588 gone[a] = 1
588 gone[a] = 1
589 else:
589 else:
590 op = 'copy'
590 op = 'copy'
591 header.append('%s from %s\n' % (op, a))
591 header.append('%s from %s\n' % (op, a))
592 header.append('%s to %s\n' % (op, f))
592 header.append('%s to %s\n' % (op, f))
593 to = getfile(a).read(arev)
593 to = getfile(a).read(arev)
594 else:
594 else:
595 header.append('new file mode %s\n' % mode)
595 header.append('new file mode %s\n' % mode)
596 if util.binary(tn):
596 if util.binary(tn):
597 dodiff = 'binary'
597 dodiff = 'binary'
598 elif f in removed:
598 elif f in removed:
599 if f in srcs:
599 if f in srcs:
600 dodiff = False
600 dodiff = False
601 else:
601 else:
602 mode = gitmode(mmap.execf(f))
602 mode = gitmode(mmap.execf(f))
603 header.append('deleted file mode %s\n' % mode)
603 header.append('deleted file mode %s\n' % mode)
604 else:
604 else:
605 omode = gitmode(mmap.execf(f))
605 omode = gitmode(mmap.execf(f))
606 if node2:
606 if node2:
607 nmode = gitmode(mmap2.execf(f))
607 nmode = gitmode(mmap2.execf(f))
608 else:
608 else:
609 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
609 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
610 addmodehdr(header, omode, nmode)
610 addmodehdr(header, omode, nmode)
611 if util.binary(to) or util.binary(tn):
611 if util.binary(to) or util.binary(tn):
612 dodiff = 'binary'
612 dodiff = 'binary'
613 r = None
613 r = None
614 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
614 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
615 if dodiff == 'binary':
615 if dodiff == 'binary':
616 fp.write(''.join(header))
616 fp.write(''.join(header))
617 b85diff(fp, to, tn)
617 b85diff(fp, to, tn)
618 elif dodiff:
618 elif dodiff:
619 text = mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts)
619 text = mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts)
620 if text or len(header) > 1:
620 if text or len(header) > 1:
621 fp.write(''.join(header))
621 fp.write(''.join(header))
622 fp.write(text)
622 fp.write(text)
623
623
624 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
624 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
625 opts=None):
625 opts=None):
626 '''export changesets as hg patches.'''
626 '''export changesets as hg patches.'''
627
627
628 total = len(revs)
628 total = len(revs)
629 revwidth = max(map(len, revs))
629 revwidth = max(map(len, revs))
630
630
631 def single(node, seqno, fp):
631 def single(node, seqno, fp):
632 parents = [p for p in repo.changelog.parents(node) if p != nullid]
632 parents = [p for p in repo.changelog.parents(node) if p != nullid]
633 if switch_parent:
633 if switch_parent:
634 parents.reverse()
634 parents.reverse()
635 prev = (parents and parents[0]) or nullid
635 prev = (parents and parents[0]) or nullid
636 change = repo.changelog.read(node)
636 change = repo.changelog.read(node)
637
637
638 if not fp:
638 if not fp:
639 fp = cmdutil.make_file(repo, template, node, total=total,
639 fp = cmdutil.make_file(repo, template, node, total=total,
640 seqno=seqno, revwidth=revwidth)
640 seqno=seqno, revwidth=revwidth)
641 if fp not in (sys.stdout, repo.ui):
641 if fp not in (sys.stdout, repo.ui):
642 repo.ui.note("%s\n" % fp.name)
642 repo.ui.note("%s\n" % fp.name)
643
643
644 fp.write("# HG changeset patch\n")
644 fp.write("# HG changeset patch\n")
645 fp.write("# User %s\n" % change[1])
645 fp.write("# User %s\n" % change[1])
646 fp.write("# Date %d %d\n" % change[2])
646 fp.write("# Date %d %d\n" % change[2])
647 fp.write("# Node ID %s\n" % hex(node))
647 fp.write("# Node ID %s\n" % hex(node))
648 fp.write("# Parent %s\n" % hex(prev))
648 fp.write("# Parent %s\n" % hex(prev))
649 if len(parents) > 1:
649 if len(parents) > 1:
650 fp.write("# Parent %s\n" % hex(parents[1]))
650 fp.write("# Parent %s\n" % hex(parents[1]))
651 fp.write(change[4].rstrip())
651 fp.write(change[4].rstrip())
652 fp.write("\n\n")
652 fp.write("\n\n")
653
653
654 diff(repo, prev, node, fp=fp, opts=opts)
654 diff(repo, prev, node, fp=fp, opts=opts)
655 if fp not in (sys.stdout, repo.ui):
655 if fp not in (sys.stdout, repo.ui):
656 fp.close()
656 fp.close()
657
657
658 for seqno, cset in enumerate(revs):
658 for seqno, cset in enumerate(revs):
659 single(cset, seqno, fp)
659 single(cset, seqno+1, fp)
660
660
661 def diffstat(patchlines):
661 def diffstat(patchlines):
662 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
662 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
663 try:
663 try:
664 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
664 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
665 try:
665 try:
666 for line in patchlines: print >> p.tochild, line
666 for line in patchlines: print >> p.tochild, line
667 p.tochild.close()
667 p.tochild.close()
668 if p.wait(): return
668 if p.wait(): return
669 fp = os.fdopen(fd, 'r')
669 fp = os.fdopen(fd, 'r')
670 stat = []
670 stat = []
671 for line in fp: stat.append(line.lstrip())
671 for line in fp: stat.append(line.lstrip())
672 last = stat.pop()
672 last = stat.pop()
673 stat.insert(0, last)
673 stat.insert(0, last)
674 stat = ''.join(stat)
674 stat = ''.join(stat)
675 if stat.startswith('0 files'): raise ValueError
675 if stat.startswith('0 files'): raise ValueError
676 return stat
676 return stat
677 except: raise
677 except: raise
678 finally:
678 finally:
679 try: os.unlink(name)
679 try: os.unlink(name)
680 except: pass
680 except: pass
General Comments 0
You need to be logged in to leave comments. Login now