##// END OF EJS Templates
Fix git patch application when cwd != repo.root
Brendan Cully -
r3055:efd26cee default
parent child Browse files
Show More
@@ -1,539 +1,539 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):
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)
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 (dopatch, gitpatches) = readgitpatch(patchname)
231
231
232 files = {}
232 files = {}
233 fuzz = False
233 fuzz = False
234 if dopatch:
234 if dopatch:
235 if dopatch == 'filter':
235 if dopatch == 'filter':
236 patchname = dogitpatch(patchname, gitpatches)
236 patchname = dogitpatch(patchname, gitpatches, cwd=cwd)
237 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''), '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 if dopatch == 'filter':
244 if dopatch == 'filter':
245 False and os.unlink(patchname)
245 False and os.unlink(patchname)
246
246
247 for line in fp:
247 for line in fp:
248 line = line.rstrip()
248 line = line.rstrip()
249 ui.note(line + '\n')
249 ui.note(line + '\n')
250 if line.startswith('patching file '):
250 if line.startswith('patching file '):
251 pf = util.parse_patch_output(line)
251 pf = util.parse_patch_output(line)
252 printed_file = False
252 printed_file = False
253 files.setdefault(pf, (None, None))
253 files.setdefault(pf, (None, None))
254 elif line.find('with fuzz') >= 0:
254 elif line.find('with fuzz') >= 0:
255 fuzz = True
255 fuzz = True
256 if not printed_file:
256 if not printed_file:
257 ui.warn(pf + '\n')
257 ui.warn(pf + '\n')
258 printed_file = True
258 printed_file = True
259 ui.warn(line + '\n')
259 ui.warn(line + '\n')
260 elif line.find('saving rejects to file') >= 0:
260 elif line.find('saving rejects to file') >= 0:
261 ui.warn(line + '\n')
261 ui.warn(line + '\n')
262 elif line.find('FAILED') >= 0:
262 elif line.find('FAILED') >= 0:
263 if not printed_file:
263 if not printed_file:
264 ui.warn(pf + '\n')
264 ui.warn(pf + '\n')
265 printed_file = True
265 printed_file = True
266 ui.warn(line + '\n')
266 ui.warn(line + '\n')
267
267
268 code = fp.close()
268 code = fp.close()
269 if code:
269 if code:
270 raise util.Abort(_("patch command failed: %s") %
270 raise util.Abort(_("patch command failed: %s") %
271 util.explain_exit(code)[0])
271 util.explain_exit(code)[0])
272
272
273 for gp in gitpatches:
273 for gp in gitpatches:
274 files[gp.path] = (gp.op, gp)
274 files[gp.path] = (gp.op, gp)
275
275
276 return (files, fuzz)
276 return (files, fuzz)
277
277
278 def diffopts(ui, opts={}):
278 def diffopts(ui, opts={}):
279 return mdiff.diffopts(
279 return mdiff.diffopts(
280 text=opts.get('text'),
280 text=opts.get('text'),
281 git=(opts.get('git') or
281 git=(opts.get('git') or
282 ui.configbool('diff', 'git', None)),
282 ui.configbool('diff', 'git', None)),
283 showfunc=(opts.get('show_function') or
283 showfunc=(opts.get('show_function') or
284 ui.configbool('diff', 'showfunc', None)),
284 ui.configbool('diff', 'showfunc', None)),
285 ignorews=(opts.get('ignore_all_space') or
285 ignorews=(opts.get('ignore_all_space') or
286 ui.configbool('diff', 'ignorews', None)),
286 ui.configbool('diff', 'ignorews', None)),
287 ignorewsamount=(opts.get('ignore_space_change') or
287 ignorewsamount=(opts.get('ignore_space_change') or
288 ui.configbool('diff', 'ignorewsamount', None)),
288 ui.configbool('diff', 'ignorewsamount', None)),
289 ignoreblanklines=(opts.get('ignore_blank_lines') or
289 ignoreblanklines=(opts.get('ignore_blank_lines') or
290 ui.configbool('diff', 'ignoreblanklines', None)))
290 ui.configbool('diff', 'ignoreblanklines', None)))
291
291
292 def updatedir(ui, repo, patches, wlock=None):
292 def updatedir(ui, repo, patches, wlock=None):
293 '''Update dirstate after patch application according to metadata'''
293 '''Update dirstate after patch application according to metadata'''
294 if not patches:
294 if not patches:
295 return
295 return
296 copies = []
296 copies = []
297 removes = []
297 removes = []
298 cfiles = patches.keys()
298 cfiles = patches.keys()
299 copts = {'after': False, 'force': False}
299 copts = {'after': False, 'force': False}
300 cwd = repo.getcwd()
300 cwd = repo.getcwd()
301 if cwd:
301 if cwd:
302 cfiles = [util.pathto(cwd, f) for f in patches.keys()]
302 cfiles = [util.pathto(cwd, f) for f in patches.keys()]
303 for f in patches:
303 for f in patches:
304 ctype, gp = patches[f]
304 ctype, gp = patches[f]
305 if ctype == 'RENAME':
305 if ctype == 'RENAME':
306 copies.append((gp.oldpath, gp.path, gp.copymod))
306 copies.append((gp.oldpath, gp.path, gp.copymod))
307 removes.append(gp.oldpath)
307 removes.append(gp.oldpath)
308 elif ctype == 'COPY':
308 elif ctype == 'COPY':
309 copies.append((gp.oldpath, gp.path, gp.copymod))
309 copies.append((gp.oldpath, gp.path, gp.copymod))
310 elif ctype == 'DELETE':
310 elif ctype == 'DELETE':
311 removes.append(gp.path)
311 removes.append(gp.path)
312 for src, dst, after in copies:
312 for src, dst, after in copies:
313 if not after:
313 if not after:
314 copyfile(src, dst, repo.root)
314 copyfile(src, dst, repo.root)
315 repo.copy(src, dst, wlock=wlock)
315 repo.copy(src, dst, wlock=wlock)
316 if removes:
316 if removes:
317 repo.remove(removes, True, wlock=wlock)
317 repo.remove(removes, True, wlock=wlock)
318 for f in patches:
318 for f in patches:
319 ctype, gp = patches[f]
319 ctype, gp = patches[f]
320 if gp and gp.mode:
320 if gp and gp.mode:
321 x = gp.mode & 0100 != 0
321 x = gp.mode & 0100 != 0
322 dst = os.path.join(repo.root, gp.path)
322 dst = os.path.join(repo.root, gp.path)
323 util.set_exec(dst, x)
323 util.set_exec(dst, x)
324 cmdutil.addremove(repo, cfiles, wlock=wlock)
324 cmdutil.addremove(repo, cfiles, wlock=wlock)
325 files = patches.keys()
325 files = patches.keys()
326 files.extend([r for r in removes if r not in files])
326 files.extend([r for r in removes if r not in files])
327 files.sort()
327 files.sort()
328
328
329 return files
329 return files
330
330
331 def diff(repo, node1=None, node2=None, files=None, match=util.always,
331 def diff(repo, node1=None, node2=None, files=None, match=util.always,
332 fp=None, changes=None, opts=None):
332 fp=None, changes=None, opts=None):
333 '''print diff of changes to files between two nodes, or node and
333 '''print diff of changes to files between two nodes, or node and
334 working directory.
334 working directory.
335
335
336 if node1 is None, use first dirstate parent instead.
336 if node1 is None, use first dirstate parent instead.
337 if node2 is None, compare node1 with working directory.'''
337 if node2 is None, compare node1 with working directory.'''
338
338
339 if opts is None:
339 if opts is None:
340 opts = mdiff.defaultopts
340 opts = mdiff.defaultopts
341 if fp is None:
341 if fp is None:
342 fp = repo.ui
342 fp = repo.ui
343
343
344 if not node1:
344 if not node1:
345 node1 = repo.dirstate.parents()[0]
345 node1 = repo.dirstate.parents()[0]
346
346
347 clcache = {}
347 clcache = {}
348 def getchangelog(n):
348 def getchangelog(n):
349 if n not in clcache:
349 if n not in clcache:
350 clcache[n] = repo.changelog.read(n)
350 clcache[n] = repo.changelog.read(n)
351 return clcache[n]
351 return clcache[n]
352 mcache = {}
352 mcache = {}
353 def getmanifest(n):
353 def getmanifest(n):
354 if n not in mcache:
354 if n not in mcache:
355 mcache[n] = repo.manifest.read(n)
355 mcache[n] = repo.manifest.read(n)
356 return mcache[n]
356 return mcache[n]
357 fcache = {}
357 fcache = {}
358 def getfile(f):
358 def getfile(f):
359 if f not in fcache:
359 if f not in fcache:
360 fcache[f] = repo.file(f)
360 fcache[f] = repo.file(f)
361 return fcache[f]
361 return fcache[f]
362
362
363 # reading the data for node1 early allows it to play nicely
363 # reading the data for node1 early allows it to play nicely
364 # with repo.status and the revlog cache.
364 # with repo.status and the revlog cache.
365 change = getchangelog(node1)
365 change = getchangelog(node1)
366 mmap = getmanifest(change[0])
366 mmap = getmanifest(change[0])
367 date1 = util.datestr(change[2])
367 date1 = util.datestr(change[2])
368
368
369 if not changes:
369 if not changes:
370 changes = repo.status(node1, node2, files, match=match)[:5]
370 changes = repo.status(node1, node2, files, match=match)[:5]
371 modified, added, removed, deleted, unknown = changes
371 modified, added, removed, deleted, unknown = changes
372 if files:
372 if files:
373 def filterfiles(filters):
373 def filterfiles(filters):
374 l = [x for x in filters if x in files]
374 l = [x for x in filters if x in files]
375
375
376 for t in files:
376 for t in files:
377 if not t.endswith("/"):
377 if not t.endswith("/"):
378 t += "/"
378 t += "/"
379 l += [x for x in filters if x.startswith(t)]
379 l += [x for x in filters if x.startswith(t)]
380 return l
380 return l
381
381
382 modified, added, removed = map(filterfiles, (modified, added, removed))
382 modified, added, removed = map(filterfiles, (modified, added, removed))
383
383
384 if not modified and not added and not removed:
384 if not modified and not added and not removed:
385 return
385 return
386
386
387 def renamedbetween(f, n1, n2):
387 def renamedbetween(f, n1, n2):
388 r1, r2 = map(repo.changelog.rev, (n1, n2))
388 r1, r2 = map(repo.changelog.rev, (n1, n2))
389 src = None
389 src = None
390 while r2 > r1:
390 while r2 > r1:
391 cl = getchangelog(n2)[0]
391 cl = getchangelog(n2)[0]
392 m = getmanifest(cl)
392 m = getmanifest(cl)
393 try:
393 try:
394 src = getfile(f).renamed(m[f])
394 src = getfile(f).renamed(m[f])
395 except KeyError:
395 except KeyError:
396 return None
396 return None
397 if src:
397 if src:
398 f = src[0]
398 f = src[0]
399 n2 = repo.changelog.parents(n2)[0]
399 n2 = repo.changelog.parents(n2)[0]
400 r2 = repo.changelog.rev(n2)
400 r2 = repo.changelog.rev(n2)
401 return src
401 return src
402
402
403 if node2:
403 if node2:
404 change = getchangelog(node2)
404 change = getchangelog(node2)
405 mmap2 = getmanifest(change[0])
405 mmap2 = getmanifest(change[0])
406 _date2 = util.datestr(change[2])
406 _date2 = util.datestr(change[2])
407 def date2(f):
407 def date2(f):
408 return _date2
408 return _date2
409 def read(f):
409 def read(f):
410 return getfile(f).read(mmap2[f])
410 return getfile(f).read(mmap2[f])
411 def renamed(f):
411 def renamed(f):
412 return renamedbetween(f, node1, node2)
412 return renamedbetween(f, node1, node2)
413 else:
413 else:
414 tz = util.makedate()[1]
414 tz = util.makedate()[1]
415 _date2 = util.datestr()
415 _date2 = util.datestr()
416 def date2(f):
416 def date2(f):
417 try:
417 try:
418 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
418 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
419 except OSError, err:
419 except OSError, err:
420 if err.errno != errno.ENOENT: raise
420 if err.errno != errno.ENOENT: raise
421 return _date2
421 return _date2
422 def read(f):
422 def read(f):
423 return repo.wread(f)
423 return repo.wread(f)
424 def renamed(f):
424 def renamed(f):
425 src = repo.dirstate.copies.get(f)
425 src = repo.dirstate.copies.get(f)
426 parent = repo.dirstate.parents()[0]
426 parent = repo.dirstate.parents()[0]
427 if src:
427 if src:
428 f = src[0]
428 f = src[0]
429 of = renamedbetween(f, node1, parent)
429 of = renamedbetween(f, node1, parent)
430 if of:
430 if of:
431 return of
431 return of
432 elif src:
432 elif src:
433 cl = getchangelog(parent)[0]
433 cl = getchangelog(parent)[0]
434 return (src, getmanifest(cl)[src])
434 return (src, getmanifest(cl)[src])
435 else:
435 else:
436 return None
436 return None
437
437
438 if repo.ui.quiet:
438 if repo.ui.quiet:
439 r = None
439 r = None
440 else:
440 else:
441 hexfunc = repo.ui.verbose and hex or short
441 hexfunc = repo.ui.verbose and hex or short
442 r = [hexfunc(node) for node in [node1, node2] if node]
442 r = [hexfunc(node) for node in [node1, node2] if node]
443
443
444 if opts.git:
444 if opts.git:
445 copied = {}
445 copied = {}
446 for f in added:
446 for f in added:
447 src = renamed(f)
447 src = renamed(f)
448 if src:
448 if src:
449 copied[f] = src
449 copied[f] = src
450 srcs = [x[1][0] for x in copied.items()]
450 srcs = [x[1][0] for x in copied.items()]
451
451
452 all = modified + added + removed
452 all = modified + added + removed
453 all.sort()
453 all.sort()
454 for f in all:
454 for f in all:
455 to = None
455 to = None
456 tn = None
456 tn = None
457 dodiff = True
457 dodiff = True
458 if f in mmap:
458 if f in mmap:
459 to = getfile(f).read(mmap[f])
459 to = getfile(f).read(mmap[f])
460 if f not in removed:
460 if f not in removed:
461 tn = read(f)
461 tn = read(f)
462 if opts.git:
462 if opts.git:
463 def gitmode(x):
463 def gitmode(x):
464 return x and '100755' or '100644'
464 return x and '100755' or '100644'
465 def addmodehdr(header, omode, nmode):
465 def addmodehdr(header, omode, nmode):
466 if omode != nmode:
466 if omode != nmode:
467 header.append('old mode %s\n' % omode)
467 header.append('old mode %s\n' % omode)
468 header.append('new mode %s\n' % nmode)
468 header.append('new mode %s\n' % nmode)
469
469
470 a, b = f, f
470 a, b = f, f
471 header = []
471 header = []
472 if f in added:
472 if f in added:
473 if node2:
473 if node2:
474 mode = gitmode(mmap2.execf(f))
474 mode = gitmode(mmap2.execf(f))
475 else:
475 else:
476 mode = gitmode(util.is_exec(repo.wjoin(f), None))
476 mode = gitmode(util.is_exec(repo.wjoin(f), None))
477 if f in copied:
477 if f in copied:
478 a, arev = copied[f]
478 a, arev = copied[f]
479 omode = gitmode(mmap.execf(a))
479 omode = gitmode(mmap.execf(a))
480 addmodehdr(header, omode, mode)
480 addmodehdr(header, omode, mode)
481 op = a in removed and 'rename' or 'copy'
481 op = a in removed and 'rename' or 'copy'
482 header.append('%s from %s\n' % (op, a))
482 header.append('%s from %s\n' % (op, a))
483 header.append('%s to %s\n' % (op, f))
483 header.append('%s to %s\n' % (op, f))
484 to = getfile(a).read(arev)
484 to = getfile(a).read(arev)
485 else:
485 else:
486 header.append('new file mode %s\n' % mode)
486 header.append('new file mode %s\n' % mode)
487 elif f in removed:
487 elif f in removed:
488 if f in srcs:
488 if f in srcs:
489 dodiff = False
489 dodiff = False
490 else:
490 else:
491 mode = gitmode(mmap.execf(f))
491 mode = gitmode(mmap.execf(f))
492 header.append('deleted file mode %s\n' % mode)
492 header.append('deleted file mode %s\n' % mode)
493 else:
493 else:
494 omode = gitmode(mmap.execf(f))
494 omode = gitmode(mmap.execf(f))
495 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
495 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
496 addmodehdr(header, omode, nmode)
496 addmodehdr(header, omode, nmode)
497 r = None
497 r = None
498 if dodiff:
498 if dodiff:
499 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
499 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
500 fp.write(''.join(header))
500 fp.write(''.join(header))
501 if dodiff:
501 if dodiff:
502 fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts))
502 fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts))
503
503
504 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
504 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
505 opts=None):
505 opts=None):
506 '''export changesets as hg patches.'''
506 '''export changesets as hg patches.'''
507
507
508 total = len(revs)
508 total = len(revs)
509 revwidth = max(map(len, revs))
509 revwidth = max(map(len, revs))
510
510
511 def single(node, seqno, fp):
511 def single(node, seqno, fp):
512 parents = [p for p in repo.changelog.parents(node) if p != nullid]
512 parents = [p for p in repo.changelog.parents(node) if p != nullid]
513 if switch_parent:
513 if switch_parent:
514 parents.reverse()
514 parents.reverse()
515 prev = (parents and parents[0]) or nullid
515 prev = (parents and parents[0]) or nullid
516 change = repo.changelog.read(node)
516 change = repo.changelog.read(node)
517
517
518 if not fp:
518 if not fp:
519 fp = cmdutil.make_file(repo, template, node, total=total,
519 fp = cmdutil.make_file(repo, template, node, total=total,
520 seqno=seqno, revwidth=revwidth)
520 seqno=seqno, revwidth=revwidth)
521 if fp not in (sys.stdout, repo.ui):
521 if fp not in (sys.stdout, repo.ui):
522 repo.ui.note("%s\n" % fp.name)
522 repo.ui.note("%s\n" % fp.name)
523
523
524 fp.write("# HG changeset patch\n")
524 fp.write("# HG changeset patch\n")
525 fp.write("# User %s\n" % change[1])
525 fp.write("# User %s\n" % change[1])
526 fp.write("# Date %d %d\n" % change[2])
526 fp.write("# Date %d %d\n" % change[2])
527 fp.write("# Node ID %s\n" % hex(node))
527 fp.write("# Node ID %s\n" % hex(node))
528 fp.write("# Parent %s\n" % hex(prev))
528 fp.write("# Parent %s\n" % hex(prev))
529 if len(parents) > 1:
529 if len(parents) > 1:
530 fp.write("# Parent %s\n" % hex(parents[1]))
530 fp.write("# Parent %s\n" % hex(parents[1]))
531 fp.write(change[4].rstrip())
531 fp.write(change[4].rstrip())
532 fp.write("\n\n")
532 fp.write("\n\n")
533
533
534 diff(repo, prev, node, fp=fp, opts=opts)
534 diff(repo, prev, node, fp=fp, opts=opts)
535 if fp not in (sys.stdout, repo.ui):
535 if fp not in (sys.stdout, repo.ui):
536 fp.close()
536 fp.close()
537
537
538 for seqno, cset in enumerate(revs):
538 for seqno, cset in enumerate(revs):
539 single(cset, seqno, fp)
539 single(cset, seqno, fp)
General Comments 0
You need to be logged in to leave comments. Login now