##// END OF EJS Templates
unlink temporary patch files even when an exception is raised
Benoit Boissinot -
r3057:d16b93f4 default
parent child Browse files
Show More
@@ -1,539 +1,549 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(), "cmdutil mdiff util")
11 demandload(globals(), "cmdutil mdiff util")
12 demandload(globals(), "cStringIO email.Parser errno os re shutil sys tempfile")
12 demandload(globals(), "cStringIO email.Parser errno os re shutil sys tempfile")
13
13
14 # helper functions
14 # helper functions
15
15
16 def copyfile(src, dst, basedir=None):
16 def copyfile(src, dst, basedir=None):
17 if not basedir:
17 if not basedir:
18 basedir = os.getcwd()
18 basedir = os.getcwd()
19
19
20 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
20 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
21 if os.path.exists(absdst):
21 if os.path.exists(absdst):
22 raise util.Abort(_("cannot create %s: destination already exists") %
22 raise util.Abort(_("cannot create %s: destination already exists") %
23 dst)
23 dst)
24
24
25 targetdir = os.path.dirname(absdst)
25 targetdir = os.path.dirname(absdst)
26 if not os.path.isdir(targetdir):
26 if not os.path.isdir(targetdir):
27 os.makedirs(targetdir)
27 os.makedirs(targetdir)
28 try:
28 try:
29 shutil.copyfile(abssrc, absdst)
29 shutil.copyfile(abssrc, absdst)
30 shutil.copymode(abssrc, absdst)
30 shutil.copymode(abssrc, absdst)
31 except shutil.Error, inst:
31 except shutil.Error, inst:
32 raise util.Abort(str(inst))
32 raise util.Abort(str(inst))
33
33
34 # public functions
34 # public functions
35
35
36 def extract(ui, fileobj):
36 def extract(ui, fileobj):
37 '''extract patch from data read from fileobj.
37 '''extract patch from data read from fileobj.
38
38
39 patch can be normal patch or contained in email message.
39 patch can be normal patch or contained in email message.
40
40
41 return tuple (filename, message, user, date). any item in returned
41 return tuple (filename, message, user, date). any item in returned
42 tuple can be None. if filename is None, fileobj did not contain
42 tuple can be None. if filename is None, fileobj did not contain
43 patch. caller must unlink filename when done.'''
43 patch. caller must unlink filename when done.'''
44
44
45 # attempt to detect the start of a patch
45 # attempt to detect the start of a patch
46 # (this heuristic is borrowed from quilt)
46 # (this heuristic is borrowed from quilt)
47 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
47 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
48 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
48 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
49 '(---|\*\*\*)[ \t])', re.MULTILINE)
49 '(---|\*\*\*)[ \t])', re.MULTILINE)
50
50
51 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
51 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
52 tmpfp = os.fdopen(fd, 'w')
52 tmpfp = os.fdopen(fd, 'w')
53 try:
53 try:
54 hgpatch = False
54 hgpatch = False
55
55
56 msg = email.Parser.Parser().parse(fileobj)
56 msg = email.Parser.Parser().parse(fileobj)
57
57
58 message = msg['Subject']
58 message = msg['Subject']
59 user = msg['From']
59 user = msg['From']
60 # should try to parse msg['Date']
60 # should try to parse msg['Date']
61 date = None
61 date = None
62
62
63 if message:
63 if message:
64 message = message.replace('\n\t', ' ')
64 message = message.replace('\n\t', ' ')
65 ui.debug('Subject: %s\n' % message)
65 ui.debug('Subject: %s\n' % message)
66 if user:
66 if user:
67 ui.debug('From: %s\n' % user)
67 ui.debug('From: %s\n' % user)
68 diffs_seen = 0
68 diffs_seen = 0
69 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
69 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
70
70
71 for part in msg.walk():
71 for part in msg.walk():
72 content_type = part.get_content_type()
72 content_type = part.get_content_type()
73 ui.debug('Content-Type: %s\n' % content_type)
73 ui.debug('Content-Type: %s\n' % content_type)
74 if content_type not in ok_types:
74 if content_type not in ok_types:
75 continue
75 continue
76 payload = part.get_payload(decode=True)
76 payload = part.get_payload(decode=True)
77 m = diffre.search(payload)
77 m = diffre.search(payload)
78 if m:
78 if m:
79 ui.debug(_('found patch at byte %d\n') % m.start(0))
79 ui.debug(_('found patch at byte %d\n') % m.start(0))
80 diffs_seen += 1
80 diffs_seen += 1
81 cfp = cStringIO.StringIO()
81 cfp = cStringIO.StringIO()
82 if message:
82 if message:
83 cfp.write(message)
83 cfp.write(message)
84 cfp.write('\n')
84 cfp.write('\n')
85 for line in payload[:m.start(0)].splitlines():
85 for line in payload[:m.start(0)].splitlines():
86 if line.startswith('# HG changeset patch'):
86 if line.startswith('# HG changeset patch'):
87 ui.debug(_('patch generated by hg export\n'))
87 ui.debug(_('patch generated by hg export\n'))
88 hgpatch = True
88 hgpatch = True
89 # drop earlier commit message content
89 # drop earlier commit message content
90 cfp.seek(0)
90 cfp.seek(0)
91 cfp.truncate()
91 cfp.truncate()
92 elif hgpatch:
92 elif hgpatch:
93 if line.startswith('# User '):
93 if line.startswith('# User '):
94 user = line[7:]
94 user = line[7:]
95 ui.debug('From: %s\n' % user)
95 ui.debug('From: %s\n' % user)
96 elif line.startswith("# Date "):
96 elif line.startswith("# Date "):
97 date = line[7:]
97 date = line[7:]
98 if not line.startswith('# '):
98 if not line.startswith('# '):
99 cfp.write(line)
99 cfp.write(line)
100 cfp.write('\n')
100 cfp.write('\n')
101 message = cfp.getvalue()
101 message = cfp.getvalue()
102 if tmpfp:
102 if tmpfp:
103 tmpfp.write(payload)
103 tmpfp.write(payload)
104 if not payload.endswith('\n'):
104 if not payload.endswith('\n'):
105 tmpfp.write('\n')
105 tmpfp.write('\n')
106 elif not diffs_seen and message and content_type == 'text/plain':
106 elif not diffs_seen and message and content_type == 'text/plain':
107 message += '\n' + payload
107 message += '\n' + payload
108 except:
108 except:
109 tmpfp.close()
109 tmpfp.close()
110 os.unlink(tmpname)
110 os.unlink(tmpname)
111 raise
111 raise
112
112
113 tmpfp.close()
113 tmpfp.close()
114 if not diffs_seen:
114 if not diffs_seen:
115 os.unlink(tmpname)
115 os.unlink(tmpname)
116 return None, message, user, date
116 return None, message, user, date
117 return tmpname, message, user, date
117 return tmpname, message, user, date
118
118
119 def readgitpatch(patchname):
119 def readgitpatch(patchname):
120 """extract git-style metadata about patches from <patchname>"""
120 """extract git-style metadata about patches from <patchname>"""
121 class gitpatch:
121 class gitpatch:
122 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
122 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
123 def __init__(self, path):
123 def __init__(self, path):
124 self.path = path
124 self.path = path
125 self.oldpath = None
125 self.oldpath = None
126 self.mode = None
126 self.mode = None
127 self.op = 'MODIFY'
127 self.op = 'MODIFY'
128 self.copymod = False
128 self.copymod = False
129 self.lineno = 0
129 self.lineno = 0
130
130
131 # Filter patch for git information
131 # Filter patch for git information
132 gitre = re.compile('diff --git a/(.*) b/(.*)')
132 gitre = re.compile('diff --git a/(.*) b/(.*)')
133 pf = file(patchname)
133 pf = file(patchname)
134 gp = None
134 gp = None
135 gitpatches = []
135 gitpatches = []
136 # Can have a git patch with only metadata, causing patch to complain
136 # Can have a git patch with only metadata, causing patch to complain
137 dopatch = False
137 dopatch = False
138
138
139 lineno = 0
139 lineno = 0
140 for line in pf:
140 for line in pf:
141 lineno += 1
141 lineno += 1
142 if line.startswith('diff --git'):
142 if line.startswith('diff --git'):
143 m = gitre.match(line)
143 m = gitre.match(line)
144 if m:
144 if m:
145 if gp:
145 if gp:
146 gitpatches.append(gp)
146 gitpatches.append(gp)
147 src, dst = m.group(1,2)
147 src, dst = m.group(1,2)
148 gp = gitpatch(dst)
148 gp = gitpatch(dst)
149 gp.lineno = lineno
149 gp.lineno = lineno
150 elif gp:
150 elif gp:
151 if line.startswith('--- '):
151 if line.startswith('--- '):
152 if gp.op in ('COPY', 'RENAME'):
152 if gp.op in ('COPY', 'RENAME'):
153 gp.copymod = True
153 gp.copymod = True
154 dopatch = 'filter'
154 dopatch = 'filter'
155 gitpatches.append(gp)
155 gitpatches.append(gp)
156 gp = None
156 gp = None
157 if not dopatch:
157 if not dopatch:
158 dopatch = True
158 dopatch = True
159 continue
159 continue
160 if line.startswith('rename from '):
160 if line.startswith('rename from '):
161 gp.op = 'RENAME'
161 gp.op = 'RENAME'
162 gp.oldpath = line[12:].rstrip()
162 gp.oldpath = line[12:].rstrip()
163 elif line.startswith('rename to '):
163 elif line.startswith('rename to '):
164 gp.path = line[10:].rstrip()
164 gp.path = line[10:].rstrip()
165 elif line.startswith('copy from '):
165 elif line.startswith('copy from '):
166 gp.op = 'COPY'
166 gp.op = 'COPY'
167 gp.oldpath = line[10:].rstrip()
167 gp.oldpath = line[10:].rstrip()
168 elif line.startswith('copy to '):
168 elif line.startswith('copy to '):
169 gp.path = line[8:].rstrip()
169 gp.path = line[8:].rstrip()
170 elif line.startswith('deleted file'):
170 elif line.startswith('deleted file'):
171 gp.op = 'DELETE'
171 gp.op = 'DELETE'
172 elif line.startswith('new file mode '):
172 elif line.startswith('new file mode '):
173 gp.op = 'ADD'
173 gp.op = 'ADD'
174 gp.mode = int(line.rstrip()[-3:], 8)
174 gp.mode = int(line.rstrip()[-3:], 8)
175 elif line.startswith('new mode '):
175 elif line.startswith('new mode '):
176 gp.mode = int(line.rstrip()[-3:], 8)
176 gp.mode = int(line.rstrip()[-3:], 8)
177 if gp:
177 if gp:
178 gitpatches.append(gp)
178 gitpatches.append(gp)
179
179
180 if not gitpatches:
180 if not gitpatches:
181 dopatch = True
181 dopatch = True
182
182
183 return (dopatch, gitpatches)
183 return (dopatch, gitpatches)
184
184
185 def dogitpatch(patchname, gitpatches, cwd=None):
185 def dogitpatch(patchname, gitpatches, cwd=None):
186 """Preprocess git patch so that vanilla patch can handle it"""
186 """Preprocess git patch so that vanilla patch can handle it"""
187 pf = file(patchname)
187 pf = file(patchname)
188 pfline = 1
188 pfline = 1
189
189
190 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
190 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
191 tmpfp = os.fdopen(fd, 'w')
191 tmpfp = os.fdopen(fd, 'w')
192
192
193 try:
193 try:
194 for i in range(len(gitpatches)):
194 for i in range(len(gitpatches)):
195 p = gitpatches[i]
195 p = gitpatches[i]
196 if not p.copymod:
196 if not p.copymod:
197 continue
197 continue
198
198
199 copyfile(p.oldpath, p.path, basedir=cwd)
199 copyfile(p.oldpath, p.path, basedir=cwd)
200
200
201 # rewrite patch hunk
201 # rewrite patch hunk
202 while pfline < p.lineno:
202 while pfline < p.lineno:
203 tmpfp.write(pf.readline())
203 tmpfp.write(pf.readline())
204 pfline += 1
204 pfline += 1
205 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
205 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
206 line = pf.readline()
206 line = pf.readline()
207 pfline += 1
207 pfline += 1
208 while not line.startswith('--- a/'):
208 while not line.startswith('--- a/'):
209 tmpfp.write(line)
209 tmpfp.write(line)
210 line = pf.readline()
210 line = pf.readline()
211 pfline += 1
211 pfline += 1
212 tmpfp.write('--- a/%s\n' % p.path)
212 tmpfp.write('--- a/%s\n' % p.path)
213
213
214 line = pf.readline()
214 line = pf.readline()
215 while line:
215 while line:
216 tmpfp.write(line)
216 tmpfp.write(line)
217 line = pf.readline()
217 line = pf.readline()
218 except:
218 except:
219 tmpfp.close()
219 tmpfp.close()
220 os.unlink(patchname)
220 os.unlink(patchname)
221 raise
221 raise
222
222
223 tmpfp.close()
223 tmpfp.close()
224 return patchname
224 return patchname
225
225
226 def patch(patchname, ui, strip=1, cwd=None):
226 def patch(patchname, ui, strip=1, cwd=None):
227 """apply the patch <patchname> to the working directory.
227 """apply the patch <patchname> to the working directory.
228 a list of patched files is returned"""
228 a list of patched files is returned"""
229
229
230 (dopatch, gitpatches) = readgitpatch(patchname)
230 # helper function
231
231 def __patch(patchname):
232 """patch and updates the files and fuzz variables"""
232 files = {}
233 files = {}
233 fuzz = False
234 fuzz = False
234 if dopatch:
235
235 if dopatch == 'filter':
236 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''),
236 patchname = dogitpatch(patchname, gitpatches, cwd=cwd)
237 'patch')
237 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
238 args = []
238 args = []
239 if cwd:
239 if cwd:
240 args.append('-d %s' % util.shellquote(cwd))
240 args.append('-d %s' % util.shellquote(cwd))
241 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
241 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
242 util.shellquote(patchname)))
242 util.shellquote(patchname)))
243
243
244 for line in fp:
244 for line in fp:
245 line = line.rstrip()
245 line = line.rstrip()
246 ui.note(line + '\n')
246 ui.note(line + '\n')
247 if line.startswith('patching file '):
247 if line.startswith('patching file '):
248 pf = util.parse_patch_output(line)
248 pf = util.parse_patch_output(line)
249 printed_file = False
249 printed_file = False
250 files.setdefault(pf, (None, None))
250 files.setdefault(pf, (None, None))
251 elif line.find('with fuzz') >= 0:
251 elif line.find('with fuzz') >= 0:
252 fuzz = True
252 fuzz = True
253 if not printed_file:
253 if not printed_file:
254 ui.warn(pf + '\n')
254 ui.warn(pf + '\n')
255 printed_file = True
255 printed_file = True
256 ui.warn(line + '\n')
256 ui.warn(line + '\n')
257 elif line.find('saving rejects to file') >= 0:
257 elif line.find('saving rejects to file') >= 0:
258 ui.warn(line + '\n')
258 ui.warn(line + '\n')
259 elif line.find('FAILED') >= 0:
259 elif line.find('FAILED') >= 0:
260 if not printed_file:
260 if not printed_file:
261 ui.warn(pf + '\n')
261 ui.warn(pf + '\n')
262 printed_file = True
262 printed_file = True
263 ui.warn(line + '\n')
263 ui.warn(line + '\n')
264
265 if dopatch == 'filter':
266 os.unlink(patchname)
267
268 code = fp.close()
264 code = fp.close()
269 if code:
265 if code:
270 raise util.Abort(_("patch command failed: %s") %
266 raise util.Abort(_("patch command failed: %s") %
271 util.explain_exit(code)[0])
267 util.explain_exit(code)[0])
268 return files, fuzz
269
270 (dopatch, gitpatches) = readgitpatch(patchname)
271
272 if dopatch:
273 if dopatch == 'filter':
274 patchname = dogitpatch(patchname, gitpatches, cwd=cwd)
275 try:
276 files, fuzz = __patch(patchname)
277 finally:
278 if dopatch == 'filter':
279 os.unlink(patchname)
280 else:
281 files, fuzz = {}, False
272
282
273 for gp in gitpatches:
283 for gp in gitpatches:
274 files[gp.path] = (gp.op, gp)
284 files[gp.path] = (gp.op, gp)
275
285
276 return (files, fuzz)
286 return (files, fuzz)
277
287
278 def diffopts(ui, opts={}):
288 def diffopts(ui, opts={}):
279 return mdiff.diffopts(
289 return mdiff.diffopts(
280 text=opts.get('text'),
290 text=opts.get('text'),
281 git=(opts.get('git') or
291 git=(opts.get('git') or
282 ui.configbool('diff', 'git', None)),
292 ui.configbool('diff', 'git', None)),
283 showfunc=(opts.get('show_function') or
293 showfunc=(opts.get('show_function') or
284 ui.configbool('diff', 'showfunc', None)),
294 ui.configbool('diff', 'showfunc', None)),
285 ignorews=(opts.get('ignore_all_space') or
295 ignorews=(opts.get('ignore_all_space') or
286 ui.configbool('diff', 'ignorews', None)),
296 ui.configbool('diff', 'ignorews', None)),
287 ignorewsamount=(opts.get('ignore_space_change') or
297 ignorewsamount=(opts.get('ignore_space_change') or
288 ui.configbool('diff', 'ignorewsamount', None)),
298 ui.configbool('diff', 'ignorewsamount', None)),
289 ignoreblanklines=(opts.get('ignore_blank_lines') or
299 ignoreblanklines=(opts.get('ignore_blank_lines') or
290 ui.configbool('diff', 'ignoreblanklines', None)))
300 ui.configbool('diff', 'ignoreblanklines', None)))
291
301
292 def updatedir(ui, repo, patches, wlock=None):
302 def updatedir(ui, repo, patches, wlock=None):
293 '''Update dirstate after patch application according to metadata'''
303 '''Update dirstate after patch application according to metadata'''
294 if not patches:
304 if not patches:
295 return
305 return
296 copies = []
306 copies = []
297 removes = []
307 removes = []
298 cfiles = patches.keys()
308 cfiles = patches.keys()
299 copts = {'after': False, 'force': False}
309 copts = {'after': False, 'force': False}
300 cwd = repo.getcwd()
310 cwd = repo.getcwd()
301 if cwd:
311 if cwd:
302 cfiles = [util.pathto(cwd, f) for f in patches.keys()]
312 cfiles = [util.pathto(cwd, f) for f in patches.keys()]
303 for f in patches:
313 for f in patches:
304 ctype, gp = patches[f]
314 ctype, gp = patches[f]
305 if ctype == 'RENAME':
315 if ctype == 'RENAME':
306 copies.append((gp.oldpath, gp.path, gp.copymod))
316 copies.append((gp.oldpath, gp.path, gp.copymod))
307 removes.append(gp.oldpath)
317 removes.append(gp.oldpath)
308 elif ctype == 'COPY':
318 elif ctype == 'COPY':
309 copies.append((gp.oldpath, gp.path, gp.copymod))
319 copies.append((gp.oldpath, gp.path, gp.copymod))
310 elif ctype == 'DELETE':
320 elif ctype == 'DELETE':
311 removes.append(gp.path)
321 removes.append(gp.path)
312 for src, dst, after in copies:
322 for src, dst, after in copies:
313 if not after:
323 if not after:
314 copyfile(src, dst, repo.root)
324 copyfile(src, dst, repo.root)
315 repo.copy(src, dst, wlock=wlock)
325 repo.copy(src, dst, wlock=wlock)
316 if removes:
326 if removes:
317 repo.remove(removes, True, wlock=wlock)
327 repo.remove(removes, True, wlock=wlock)
318 for f in patches:
328 for f in patches:
319 ctype, gp = patches[f]
329 ctype, gp = patches[f]
320 if gp and gp.mode:
330 if gp and gp.mode:
321 x = gp.mode & 0100 != 0
331 x = gp.mode & 0100 != 0
322 dst = os.path.join(repo.root, gp.path)
332 dst = os.path.join(repo.root, gp.path)
323 util.set_exec(dst, x)
333 util.set_exec(dst, x)
324 cmdutil.addremove(repo, cfiles, wlock=wlock)
334 cmdutil.addremove(repo, cfiles, wlock=wlock)
325 files = patches.keys()
335 files = patches.keys()
326 files.extend([r for r in removes if r not in files])
336 files.extend([r for r in removes if r not in files])
327 files.sort()
337 files.sort()
328
338
329 return files
339 return files
330
340
331 def diff(repo, node1=None, node2=None, files=None, match=util.always,
341 def diff(repo, node1=None, node2=None, files=None, match=util.always,
332 fp=None, changes=None, opts=None):
342 fp=None, changes=None, opts=None):
333 '''print diff of changes to files between two nodes, or node and
343 '''print diff of changes to files between two nodes, or node and
334 working directory.
344 working directory.
335
345
336 if node1 is None, use first dirstate parent instead.
346 if node1 is None, use first dirstate parent instead.
337 if node2 is None, compare node1 with working directory.'''
347 if node2 is None, compare node1 with working directory.'''
338
348
339 if opts is None:
349 if opts is None:
340 opts = mdiff.defaultopts
350 opts = mdiff.defaultopts
341 if fp is None:
351 if fp is None:
342 fp = repo.ui
352 fp = repo.ui
343
353
344 if not node1:
354 if not node1:
345 node1 = repo.dirstate.parents()[0]
355 node1 = repo.dirstate.parents()[0]
346
356
347 clcache = {}
357 clcache = {}
348 def getchangelog(n):
358 def getchangelog(n):
349 if n not in clcache:
359 if n not in clcache:
350 clcache[n] = repo.changelog.read(n)
360 clcache[n] = repo.changelog.read(n)
351 return clcache[n]
361 return clcache[n]
352 mcache = {}
362 mcache = {}
353 def getmanifest(n):
363 def getmanifest(n):
354 if n not in mcache:
364 if n not in mcache:
355 mcache[n] = repo.manifest.read(n)
365 mcache[n] = repo.manifest.read(n)
356 return mcache[n]
366 return mcache[n]
357 fcache = {}
367 fcache = {}
358 def getfile(f):
368 def getfile(f):
359 if f not in fcache:
369 if f not in fcache:
360 fcache[f] = repo.file(f)
370 fcache[f] = repo.file(f)
361 return fcache[f]
371 return fcache[f]
362
372
363 # reading the data for node1 early allows it to play nicely
373 # reading the data for node1 early allows it to play nicely
364 # with repo.status and the revlog cache.
374 # with repo.status and the revlog cache.
365 change = getchangelog(node1)
375 change = getchangelog(node1)
366 mmap = getmanifest(change[0])
376 mmap = getmanifest(change[0])
367 date1 = util.datestr(change[2])
377 date1 = util.datestr(change[2])
368
378
369 if not changes:
379 if not changes:
370 changes = repo.status(node1, node2, files, match=match)[:5]
380 changes = repo.status(node1, node2, files, match=match)[:5]
371 modified, added, removed, deleted, unknown = changes
381 modified, added, removed, deleted, unknown = changes
372 if files:
382 if files:
373 def filterfiles(filters):
383 def filterfiles(filters):
374 l = [x for x in filters if x in files]
384 l = [x for x in filters if x in files]
375
385
376 for t in files:
386 for t in files:
377 if not t.endswith("/"):
387 if not t.endswith("/"):
378 t += "/"
388 t += "/"
379 l += [x for x in filters if x.startswith(t)]
389 l += [x for x in filters if x.startswith(t)]
380 return l
390 return l
381
391
382 modified, added, removed = map(filterfiles, (modified, added, removed))
392 modified, added, removed = map(filterfiles, (modified, added, removed))
383
393
384 if not modified and not added and not removed:
394 if not modified and not added and not removed:
385 return
395 return
386
396
387 def renamedbetween(f, n1, n2):
397 def renamedbetween(f, n1, n2):
388 r1, r2 = map(repo.changelog.rev, (n1, n2))
398 r1, r2 = map(repo.changelog.rev, (n1, n2))
389 src = None
399 src = None
390 while r2 > r1:
400 while r2 > r1:
391 cl = getchangelog(n2)[0]
401 cl = getchangelog(n2)[0]
392 m = getmanifest(cl)
402 m = getmanifest(cl)
393 try:
403 try:
394 src = getfile(f).renamed(m[f])
404 src = getfile(f).renamed(m[f])
395 except KeyError:
405 except KeyError:
396 return None
406 return None
397 if src:
407 if src:
398 f = src[0]
408 f = src[0]
399 n2 = repo.changelog.parents(n2)[0]
409 n2 = repo.changelog.parents(n2)[0]
400 r2 = repo.changelog.rev(n2)
410 r2 = repo.changelog.rev(n2)
401 return src
411 return src
402
412
403 if node2:
413 if node2:
404 change = getchangelog(node2)
414 change = getchangelog(node2)
405 mmap2 = getmanifest(change[0])
415 mmap2 = getmanifest(change[0])
406 _date2 = util.datestr(change[2])
416 _date2 = util.datestr(change[2])
407 def date2(f):
417 def date2(f):
408 return _date2
418 return _date2
409 def read(f):
419 def read(f):
410 return getfile(f).read(mmap2[f])
420 return getfile(f).read(mmap2[f])
411 def renamed(f):
421 def renamed(f):
412 return renamedbetween(f, node1, node2)
422 return renamedbetween(f, node1, node2)
413 else:
423 else:
414 tz = util.makedate()[1]
424 tz = util.makedate()[1]
415 _date2 = util.datestr()
425 _date2 = util.datestr()
416 def date2(f):
426 def date2(f):
417 try:
427 try:
418 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
428 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
419 except OSError, err:
429 except OSError, err:
420 if err.errno != errno.ENOENT: raise
430 if err.errno != errno.ENOENT: raise
421 return _date2
431 return _date2
422 def read(f):
432 def read(f):
423 return repo.wread(f)
433 return repo.wread(f)
424 def renamed(f):
434 def renamed(f):
425 src = repo.dirstate.copies.get(f)
435 src = repo.dirstate.copies.get(f)
426 parent = repo.dirstate.parents()[0]
436 parent = repo.dirstate.parents()[0]
427 if src:
437 if src:
428 f = src[0]
438 f = src[0]
429 of = renamedbetween(f, node1, parent)
439 of = renamedbetween(f, node1, parent)
430 if of:
440 if of:
431 return of
441 return of
432 elif src:
442 elif src:
433 cl = getchangelog(parent)[0]
443 cl = getchangelog(parent)[0]
434 return (src, getmanifest(cl)[src])
444 return (src, getmanifest(cl)[src])
435 else:
445 else:
436 return None
446 return None
437
447
438 if repo.ui.quiet:
448 if repo.ui.quiet:
439 r = None
449 r = None
440 else:
450 else:
441 hexfunc = repo.ui.verbose and hex or short
451 hexfunc = repo.ui.verbose and hex or short
442 r = [hexfunc(node) for node in [node1, node2] if node]
452 r = [hexfunc(node) for node in [node1, node2] if node]
443
453
444 if opts.git:
454 if opts.git:
445 copied = {}
455 copied = {}
446 for f in added:
456 for f in added:
447 src = renamed(f)
457 src = renamed(f)
448 if src:
458 if src:
449 copied[f] = src
459 copied[f] = src
450 srcs = [x[1][0] for x in copied.items()]
460 srcs = [x[1][0] for x in copied.items()]
451
461
452 all = modified + added + removed
462 all = modified + added + removed
453 all.sort()
463 all.sort()
454 for f in all:
464 for f in all:
455 to = None
465 to = None
456 tn = None
466 tn = None
457 dodiff = True
467 dodiff = True
458 if f in mmap:
468 if f in mmap:
459 to = getfile(f).read(mmap[f])
469 to = getfile(f).read(mmap[f])
460 if f not in removed:
470 if f not in removed:
461 tn = read(f)
471 tn = read(f)
462 if opts.git:
472 if opts.git:
463 def gitmode(x):
473 def gitmode(x):
464 return x and '100755' or '100644'
474 return x and '100755' or '100644'
465 def addmodehdr(header, omode, nmode):
475 def addmodehdr(header, omode, nmode):
466 if omode != nmode:
476 if omode != nmode:
467 header.append('old mode %s\n' % omode)
477 header.append('old mode %s\n' % omode)
468 header.append('new mode %s\n' % nmode)
478 header.append('new mode %s\n' % nmode)
469
479
470 a, b = f, f
480 a, b = f, f
471 header = []
481 header = []
472 if f in added:
482 if f in added:
473 if node2:
483 if node2:
474 mode = gitmode(mmap2.execf(f))
484 mode = gitmode(mmap2.execf(f))
475 else:
485 else:
476 mode = gitmode(util.is_exec(repo.wjoin(f), None))
486 mode = gitmode(util.is_exec(repo.wjoin(f), None))
477 if f in copied:
487 if f in copied:
478 a, arev = copied[f]
488 a, arev = copied[f]
479 omode = gitmode(mmap.execf(a))
489 omode = gitmode(mmap.execf(a))
480 addmodehdr(header, omode, mode)
490 addmodehdr(header, omode, mode)
481 op = a in removed and 'rename' or 'copy'
491 op = a in removed and 'rename' or 'copy'
482 header.append('%s from %s\n' % (op, a))
492 header.append('%s from %s\n' % (op, a))
483 header.append('%s to %s\n' % (op, f))
493 header.append('%s to %s\n' % (op, f))
484 to = getfile(a).read(arev)
494 to = getfile(a).read(arev)
485 else:
495 else:
486 header.append('new file mode %s\n' % mode)
496 header.append('new file mode %s\n' % mode)
487 elif f in removed:
497 elif f in removed:
488 if f in srcs:
498 if f in srcs:
489 dodiff = False
499 dodiff = False
490 else:
500 else:
491 mode = gitmode(mmap.execf(f))
501 mode = gitmode(mmap.execf(f))
492 header.append('deleted file mode %s\n' % mode)
502 header.append('deleted file mode %s\n' % mode)
493 else:
503 else:
494 omode = gitmode(mmap.execf(f))
504 omode = gitmode(mmap.execf(f))
495 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
505 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
496 addmodehdr(header, omode, nmode)
506 addmodehdr(header, omode, nmode)
497 r = None
507 r = None
498 if dodiff:
508 if dodiff:
499 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
509 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
500 fp.write(''.join(header))
510 fp.write(''.join(header))
501 if dodiff:
511 if dodiff:
502 fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts))
512 fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts))
503
513
504 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
514 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
505 opts=None):
515 opts=None):
506 '''export changesets as hg patches.'''
516 '''export changesets as hg patches.'''
507
517
508 total = len(revs)
518 total = len(revs)
509 revwidth = max(map(len, revs))
519 revwidth = max(map(len, revs))
510
520
511 def single(node, seqno, fp):
521 def single(node, seqno, fp):
512 parents = [p for p in repo.changelog.parents(node) if p != nullid]
522 parents = [p for p in repo.changelog.parents(node) if p != nullid]
513 if switch_parent:
523 if switch_parent:
514 parents.reverse()
524 parents.reverse()
515 prev = (parents and parents[0]) or nullid
525 prev = (parents and parents[0]) or nullid
516 change = repo.changelog.read(node)
526 change = repo.changelog.read(node)
517
527
518 if not fp:
528 if not fp:
519 fp = cmdutil.make_file(repo, template, node, total=total,
529 fp = cmdutil.make_file(repo, template, node, total=total,
520 seqno=seqno, revwidth=revwidth)
530 seqno=seqno, revwidth=revwidth)
521 if fp not in (sys.stdout, repo.ui):
531 if fp not in (sys.stdout, repo.ui):
522 repo.ui.note("%s\n" % fp.name)
532 repo.ui.note("%s\n" % fp.name)
523
533
524 fp.write("# HG changeset patch\n")
534 fp.write("# HG changeset patch\n")
525 fp.write("# User %s\n" % change[1])
535 fp.write("# User %s\n" % change[1])
526 fp.write("# Date %d %d\n" % change[2])
536 fp.write("# Date %d %d\n" % change[2])
527 fp.write("# Node ID %s\n" % hex(node))
537 fp.write("# Node ID %s\n" % hex(node))
528 fp.write("# Parent %s\n" % hex(prev))
538 fp.write("# Parent %s\n" % hex(prev))
529 if len(parents) > 1:
539 if len(parents) > 1:
530 fp.write("# Parent %s\n" % hex(parents[1]))
540 fp.write("# Parent %s\n" % hex(parents[1]))
531 fp.write(change[4].rstrip())
541 fp.write(change[4].rstrip())
532 fp.write("\n\n")
542 fp.write("\n\n")
533
543
534 diff(repo, prev, node, fp=fp, opts=opts)
544 diff(repo, prev, node, fp=fp, opts=opts)
535 if fp not in (sys.stdout, repo.ui):
545 if fp not in (sys.stdout, repo.ui):
536 fp.close()
546 fp.close()
537
547
538 for seqno, cset in enumerate(revs):
548 for seqno, cset in enumerate(revs):
539 single(cset, seqno, fp)
549 single(cset, seqno, fp)
General Comments 0
You need to be logged in to leave comments. Login now