##// END OF EJS Templates
patch: pass linereader to scangitpatch(), extract from iterhunks()...
Patrick Mezard -
r7152:f0055cec default
parent child Browse files
Show More
@@ -1,1335 +1,1345 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 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 from i18n import _
9 from i18n import _
10 from node import hex, nullid, short
10 from node import hex, nullid, short
11 import base85, cmdutil, mdiff, util, revlog, diffhelpers, copies
11 import base85, cmdutil, mdiff, util, revlog, diffhelpers, copies
12 import cStringIO, email.Parser, os, re, errno
12 import cStringIO, email.Parser, os, re, errno
13 import sys, tempfile, zlib
13 import sys, tempfile, zlib
14
14
15 class PatchError(Exception):
15 class PatchError(Exception):
16 pass
16 pass
17
17
18 class NoHunks(PatchError):
18 class NoHunks(PatchError):
19 pass
19 pass
20
20
21 # helper functions
21 # helper functions
22
22
23 def copyfile(src, dst, basedir=None):
23 def copyfile(src, dst, basedir=None):
24 if not basedir:
24 if not basedir:
25 basedir = os.getcwd()
25 basedir = os.getcwd()
26
26
27 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
27 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
28 if os.path.exists(absdst):
28 if os.path.exists(absdst):
29 raise util.Abort(_("cannot create %s: destination already exists") %
29 raise util.Abort(_("cannot create %s: destination already exists") %
30 dst)
30 dst)
31
31
32 targetdir = os.path.dirname(absdst)
32 targetdir = os.path.dirname(absdst)
33 if not os.path.isdir(targetdir):
33 if not os.path.isdir(targetdir):
34 os.makedirs(targetdir)
34 os.makedirs(targetdir)
35
35
36 util.copyfile(abssrc, absdst)
36 util.copyfile(abssrc, absdst)
37
37
38 # public functions
38 # public functions
39
39
40 def extract(ui, fileobj):
40 def extract(ui, fileobj):
41 '''extract patch from data read from fileobj.
41 '''extract patch from data read from fileobj.
42
42
43 patch can be a normal patch or contained in an email message.
43 patch can be a normal patch or contained in an email message.
44
44
45 return tuple (filename, message, user, date, node, p1, p2).
45 return tuple (filename, message, user, date, node, p1, p2).
46 Any item in the returned tuple can be None. If filename is None,
46 Any item in the returned tuple can be None. If filename is None,
47 fileobj did not contain a patch. Caller must unlink filename when done.'''
47 fileobj did not contain a patch. Caller must unlink filename when done.'''
48
48
49 # attempt to detect the start of a patch
49 # attempt to detect the start of a patch
50 # (this heuristic is borrowed from quilt)
50 # (this heuristic is borrowed from quilt)
51 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
51 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
52 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
52 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
53 '(---|\*\*\*)[ \t])', re.MULTILINE)
53 '(---|\*\*\*)[ \t])', re.MULTILINE)
54
54
55 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
55 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
56 tmpfp = os.fdopen(fd, 'w')
56 tmpfp = os.fdopen(fd, 'w')
57 try:
57 try:
58 msg = email.Parser.Parser().parse(fileobj)
58 msg = email.Parser.Parser().parse(fileobj)
59
59
60 subject = msg['Subject']
60 subject = msg['Subject']
61 user = msg['From']
61 user = msg['From']
62 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
62 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
63 # should try to parse msg['Date']
63 # should try to parse msg['Date']
64 date = None
64 date = None
65 nodeid = None
65 nodeid = None
66 branch = None
66 branch = None
67 parents = []
67 parents = []
68
68
69 if subject:
69 if subject:
70 if subject.startswith('[PATCH'):
70 if subject.startswith('[PATCH'):
71 pend = subject.find(']')
71 pend = subject.find(']')
72 if pend >= 0:
72 if pend >= 0:
73 subject = subject[pend+1:].lstrip()
73 subject = subject[pend+1:].lstrip()
74 subject = subject.replace('\n\t', ' ')
74 subject = subject.replace('\n\t', ' ')
75 ui.debug('Subject: %s\n' % subject)
75 ui.debug('Subject: %s\n' % subject)
76 if user:
76 if user:
77 ui.debug('From: %s\n' % user)
77 ui.debug('From: %s\n' % user)
78 diffs_seen = 0
78 diffs_seen = 0
79 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
79 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
80 message = ''
80 message = ''
81 for part in msg.walk():
81 for part in msg.walk():
82 content_type = part.get_content_type()
82 content_type = part.get_content_type()
83 ui.debug('Content-Type: %s\n' % content_type)
83 ui.debug('Content-Type: %s\n' % content_type)
84 if content_type not in ok_types:
84 if content_type not in ok_types:
85 continue
85 continue
86 payload = part.get_payload(decode=True)
86 payload = part.get_payload(decode=True)
87 m = diffre.search(payload)
87 m = diffre.search(payload)
88 if m:
88 if m:
89 hgpatch = False
89 hgpatch = False
90 ignoretext = False
90 ignoretext = False
91
91
92 ui.debug(_('found patch at byte %d\n') % m.start(0))
92 ui.debug(_('found patch at byte %d\n') % m.start(0))
93 diffs_seen += 1
93 diffs_seen += 1
94 cfp = cStringIO.StringIO()
94 cfp = cStringIO.StringIO()
95 for line in payload[:m.start(0)].splitlines():
95 for line in payload[:m.start(0)].splitlines():
96 if line.startswith('# HG changeset patch'):
96 if line.startswith('# HG changeset patch'):
97 ui.debug(_('patch generated by hg export\n'))
97 ui.debug(_('patch generated by hg export\n'))
98 hgpatch = True
98 hgpatch = True
99 # drop earlier commit message content
99 # drop earlier commit message content
100 cfp.seek(0)
100 cfp.seek(0)
101 cfp.truncate()
101 cfp.truncate()
102 subject = None
102 subject = None
103 elif hgpatch:
103 elif hgpatch:
104 if line.startswith('# User '):
104 if line.startswith('# User '):
105 user = line[7:]
105 user = line[7:]
106 ui.debug('From: %s\n' % user)
106 ui.debug('From: %s\n' % user)
107 elif line.startswith("# Date "):
107 elif line.startswith("# Date "):
108 date = line[7:]
108 date = line[7:]
109 elif line.startswith("# Branch "):
109 elif line.startswith("# Branch "):
110 branch = line[9:]
110 branch = line[9:]
111 elif line.startswith("# Node ID "):
111 elif line.startswith("# Node ID "):
112 nodeid = line[10:]
112 nodeid = line[10:]
113 elif line.startswith("# Parent "):
113 elif line.startswith("# Parent "):
114 parents.append(line[10:])
114 parents.append(line[10:])
115 elif line == '---' and gitsendmail:
115 elif line == '---' and gitsendmail:
116 ignoretext = True
116 ignoretext = True
117 if not line.startswith('# ') and not ignoretext:
117 if not line.startswith('# ') and not ignoretext:
118 cfp.write(line)
118 cfp.write(line)
119 cfp.write('\n')
119 cfp.write('\n')
120 message = cfp.getvalue()
120 message = cfp.getvalue()
121 if tmpfp:
121 if tmpfp:
122 tmpfp.write(payload)
122 tmpfp.write(payload)
123 if not payload.endswith('\n'):
123 if not payload.endswith('\n'):
124 tmpfp.write('\n')
124 tmpfp.write('\n')
125 elif not diffs_seen and message and content_type == 'text/plain':
125 elif not diffs_seen and message and content_type == 'text/plain':
126 message += '\n' + payload
126 message += '\n' + payload
127 except:
127 except:
128 tmpfp.close()
128 tmpfp.close()
129 os.unlink(tmpname)
129 os.unlink(tmpname)
130 raise
130 raise
131
131
132 if subject and not message.startswith(subject):
132 if subject and not message.startswith(subject):
133 message = '%s\n%s' % (subject, message)
133 message = '%s\n%s' % (subject, message)
134 tmpfp.close()
134 tmpfp.close()
135 if not diffs_seen:
135 if not diffs_seen:
136 os.unlink(tmpname)
136 os.unlink(tmpname)
137 return None, message, user, date, branch, None, None, None
137 return None, message, user, date, branch, None, None, None
138 p1 = parents and parents.pop(0) or None
138 p1 = parents and parents.pop(0) or None
139 p2 = parents and parents.pop(0) or None
139 p2 = parents and parents.pop(0) or None
140 return tmpname, message, user, date, branch, nodeid, p1, p2
140 return tmpname, message, user, date, branch, nodeid, p1, p2
141
141
142 GP_PATCH = 1 << 0 # we have to run patch
142 GP_PATCH = 1 << 0 # we have to run patch
143 GP_FILTER = 1 << 1 # there's some copy/rename operation
143 GP_FILTER = 1 << 1 # there's some copy/rename operation
144 GP_BINARY = 1 << 2 # there's a binary patch
144 GP_BINARY = 1 << 2 # there's a binary patch
145
145
146 class patchmeta:
146 class patchmeta:
147 """Patched file metadata
147 """Patched file metadata
148
148
149 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
149 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
150 or COPY. 'path' is patched file path. 'oldpath' is set to the
150 or COPY. 'path' is patched file path. 'oldpath' is set to the
151 origin file when 'op' is either COPY or RENAME, None otherwise. If
151 origin file when 'op' is either COPY or RENAME, None otherwise. If
152 file mode is changed, 'mode' is a tuple (islink, isexec) where
152 file mode is changed, 'mode' is a tuple (islink, isexec) where
153 'islink' is True if the file is a symlink and 'isexec' is True if
153 'islink' is True if the file is a symlink and 'isexec' is True if
154 the file is executable. Otherwise, 'mode' is None.
154 the file is executable. Otherwise, 'mode' is None.
155 """
155 """
156 def __init__(self, path):
156 def __init__(self, path):
157 self.path = path
157 self.path = path
158 self.oldpath = None
158 self.oldpath = None
159 self.mode = None
159 self.mode = None
160 self.op = 'MODIFY'
160 self.op = 'MODIFY'
161 self.lineno = 0
161 self.lineno = 0
162 self.binary = False
162 self.binary = False
163
163
164 def setmode(self, mode):
164 def setmode(self, mode):
165 islink = mode & 020000
165 islink = mode & 020000
166 isexec = mode & 0100
166 isexec = mode & 0100
167 self.mode = (islink, isexec)
167 self.mode = (islink, isexec)
168
168
169 def readgitpatch(fp, firstline=None):
169 def readgitpatch(lr):
170 """extract git-style metadata about patches from <patchname>"""
170 """extract git-style metadata about patches from <patchname>"""
171
171
172 def reader(fp, firstline):
173 if firstline is not None:
174 yield firstline
175 for line in fp:
176 yield line
177
178 # Filter patch for git information
172 # Filter patch for git information
179 gitre = re.compile('diff --git a/(.*) b/(.*)')
173 gitre = re.compile('diff --git a/(.*) b/(.*)')
180 gp = None
174 gp = None
181 gitpatches = []
175 gitpatches = []
182 # Can have a git patch with only metadata, causing patch to complain
176 # Can have a git patch with only metadata, causing patch to complain
183 dopatch = 0
177 dopatch = 0
184
178
185 lineno = 0
179 lineno = 0
186 for line in reader(fp, firstline):
180 for line in lr:
187 lineno += 1
181 lineno += 1
188 if line.startswith('diff --git'):
182 if line.startswith('diff --git'):
189 m = gitre.match(line)
183 m = gitre.match(line)
190 if m:
184 if m:
191 if gp:
185 if gp:
192 gitpatches.append(gp)
186 gitpatches.append(gp)
193 src, dst = m.group(1, 2)
187 src, dst = m.group(1, 2)
194 gp = patchmeta(dst)
188 gp = patchmeta(dst)
195 gp.lineno = lineno
189 gp.lineno = lineno
196 elif gp:
190 elif gp:
197 if line.startswith('--- '):
191 if line.startswith('--- '):
198 if gp.op in ('COPY', 'RENAME'):
192 if gp.op in ('COPY', 'RENAME'):
199 dopatch |= GP_FILTER
193 dopatch |= GP_FILTER
200 gitpatches.append(gp)
194 gitpatches.append(gp)
201 gp = None
195 gp = None
202 dopatch |= GP_PATCH
196 dopatch |= GP_PATCH
203 continue
197 continue
204 if line.startswith('rename from '):
198 if line.startswith('rename from '):
205 gp.op = 'RENAME'
199 gp.op = 'RENAME'
206 gp.oldpath = line[12:].rstrip()
200 gp.oldpath = line[12:].rstrip()
207 elif line.startswith('rename to '):
201 elif line.startswith('rename to '):
208 gp.path = line[10:].rstrip()
202 gp.path = line[10:].rstrip()
209 elif line.startswith('copy from '):
203 elif line.startswith('copy from '):
210 gp.op = 'COPY'
204 gp.op = 'COPY'
211 gp.oldpath = line[10:].rstrip()
205 gp.oldpath = line[10:].rstrip()
212 elif line.startswith('copy to '):
206 elif line.startswith('copy to '):
213 gp.path = line[8:].rstrip()
207 gp.path = line[8:].rstrip()
214 elif line.startswith('deleted file'):
208 elif line.startswith('deleted file'):
215 gp.op = 'DELETE'
209 gp.op = 'DELETE'
216 elif line.startswith('new file mode '):
210 elif line.startswith('new file mode '):
217 gp.op = 'ADD'
211 gp.op = 'ADD'
218 gp.setmode(int(line.rstrip()[-6:], 8))
212 gp.setmode(int(line.rstrip()[-6:], 8))
219 elif line.startswith('new mode '):
213 elif line.startswith('new mode '):
220 gp.setmode(int(line.rstrip()[-6:], 8))
214 gp.setmode(int(line.rstrip()[-6:], 8))
221 elif line.startswith('GIT binary patch'):
215 elif line.startswith('GIT binary patch'):
222 dopatch |= GP_BINARY
216 dopatch |= GP_BINARY
223 gp.binary = True
217 gp.binary = True
224 if gp:
218 if gp:
225 gitpatches.append(gp)
219 gitpatches.append(gp)
226
220
227 if not gitpatches:
221 if not gitpatches:
228 dopatch = GP_PATCH
222 dopatch = GP_PATCH
229
223
230 return (dopatch, gitpatches)
224 return (dopatch, gitpatches)
231
225
232 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
226 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
233 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
227 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
234 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
228 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
235
229
236 class patchfile:
230 class patchfile:
237 def __init__(self, ui, fname, missing=False):
231 def __init__(self, ui, fname, missing=False):
238 self.fname = fname
232 self.fname = fname
239 self.ui = ui
233 self.ui = ui
240 self.lines = []
234 self.lines = []
241 self.exists = False
235 self.exists = False
242 self.missing = missing
236 self.missing = missing
243 if not missing:
237 if not missing:
244 try:
238 try:
245 fp = file(fname, 'rb')
239 fp = file(fname, 'rb')
246 self.lines = fp.readlines()
240 self.lines = fp.readlines()
247 self.exists = True
241 self.exists = True
248 except IOError:
242 except IOError:
249 pass
243 pass
250 else:
244 else:
251 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
245 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
252
246
253 if not self.exists:
247 if not self.exists:
254 dirname = os.path.dirname(fname)
248 dirname = os.path.dirname(fname)
255 if dirname and not os.path.isdir(dirname):
249 if dirname and not os.path.isdir(dirname):
256 os.makedirs(dirname)
250 os.makedirs(dirname)
257
251
258 self.hash = {}
252 self.hash = {}
259 self.dirty = 0
253 self.dirty = 0
260 self.offset = 0
254 self.offset = 0
261 self.rej = []
255 self.rej = []
262 self.fileprinted = False
256 self.fileprinted = False
263 self.printfile(False)
257 self.printfile(False)
264 self.hunks = 0
258 self.hunks = 0
265
259
266 def printfile(self, warn):
260 def printfile(self, warn):
267 if self.fileprinted:
261 if self.fileprinted:
268 return
262 return
269 if warn or self.ui.verbose:
263 if warn or self.ui.verbose:
270 self.fileprinted = True
264 self.fileprinted = True
271 s = _("patching file %s\n") % self.fname
265 s = _("patching file %s\n") % self.fname
272 if warn:
266 if warn:
273 self.ui.warn(s)
267 self.ui.warn(s)
274 else:
268 else:
275 self.ui.note(s)
269 self.ui.note(s)
276
270
277
271
278 def findlines(self, l, linenum):
272 def findlines(self, l, linenum):
279 # looks through the hash and finds candidate lines. The
273 # looks through the hash and finds candidate lines. The
280 # result is a list of line numbers sorted based on distance
274 # result is a list of line numbers sorted based on distance
281 # from linenum
275 # from linenum
282 def sorter(a, b):
276 def sorter(a, b):
283 vala = abs(a - linenum)
277 vala = abs(a - linenum)
284 valb = abs(b - linenum)
278 valb = abs(b - linenum)
285 return cmp(vala, valb)
279 return cmp(vala, valb)
286
280
287 try:
281 try:
288 cand = self.hash[l]
282 cand = self.hash[l]
289 except:
283 except:
290 return []
284 return []
291
285
292 if len(cand) > 1:
286 if len(cand) > 1:
293 # resort our list of potentials forward then back.
287 # resort our list of potentials forward then back.
294 cand.sort(sorter)
288 cand.sort(sorter)
295 return cand
289 return cand
296
290
297 def hashlines(self):
291 def hashlines(self):
298 self.hash = {}
292 self.hash = {}
299 for x in xrange(len(self.lines)):
293 for x in xrange(len(self.lines)):
300 s = self.lines[x]
294 s = self.lines[x]
301 self.hash.setdefault(s, []).append(x)
295 self.hash.setdefault(s, []).append(x)
302
296
303 def write_rej(self):
297 def write_rej(self):
304 # our rejects are a little different from patch(1). This always
298 # our rejects are a little different from patch(1). This always
305 # creates rejects in the same form as the original patch. A file
299 # creates rejects in the same form as the original patch. A file
306 # header is inserted so that you can run the reject through patch again
300 # header is inserted so that you can run the reject through patch again
307 # without having to type the filename.
301 # without having to type the filename.
308
302
309 if not self.rej:
303 if not self.rej:
310 return
304 return
311
305
312 fname = self.fname + ".rej"
306 fname = self.fname + ".rej"
313 self.ui.warn(
307 self.ui.warn(
314 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
308 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
315 (len(self.rej), self.hunks, fname))
309 (len(self.rej), self.hunks, fname))
316 try: os.unlink(fname)
310 try: os.unlink(fname)
317 except:
311 except:
318 pass
312 pass
319 fp = file(fname, 'wb')
313 fp = file(fname, 'wb')
320 base = os.path.basename(self.fname)
314 base = os.path.basename(self.fname)
321 fp.write("--- %s\n+++ %s\n" % (base, base))
315 fp.write("--- %s\n+++ %s\n" % (base, base))
322 for x in self.rej:
316 for x in self.rej:
323 for l in x.hunk:
317 for l in x.hunk:
324 fp.write(l)
318 fp.write(l)
325 if l[-1] != '\n':
319 if l[-1] != '\n':
326 fp.write("\n\ No newline at end of file\n")
320 fp.write("\n\ No newline at end of file\n")
327
321
328 def write(self, dest=None):
322 def write(self, dest=None):
329 if self.dirty:
323 if self.dirty:
330 if not dest:
324 if not dest:
331 dest = self.fname
325 dest = self.fname
332 st = None
326 st = None
333 try:
327 try:
334 st = os.lstat(dest)
328 st = os.lstat(dest)
335 except OSError, inst:
329 except OSError, inst:
336 if inst.errno != errno.ENOENT:
330 if inst.errno != errno.ENOENT:
337 raise
331 raise
338 if st and st.st_nlink > 1:
332 if st and st.st_nlink > 1:
339 os.unlink(dest)
333 os.unlink(dest)
340 fp = file(dest, 'wb')
334 fp = file(dest, 'wb')
341 if st and st.st_nlink > 1:
335 if st and st.st_nlink > 1:
342 os.chmod(dest, st.st_mode)
336 os.chmod(dest, st.st_mode)
343 fp.writelines(self.lines)
337 fp.writelines(self.lines)
344 fp.close()
338 fp.close()
345
339
346 def close(self):
340 def close(self):
347 self.write()
341 self.write()
348 self.write_rej()
342 self.write_rej()
349
343
350 def apply(self, h, reverse):
344 def apply(self, h, reverse):
351 if not h.complete():
345 if not h.complete():
352 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
346 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
353 (h.number, h.desc, len(h.a), h.lena, len(h.b),
347 (h.number, h.desc, len(h.a), h.lena, len(h.b),
354 h.lenb))
348 h.lenb))
355
349
356 self.hunks += 1
350 self.hunks += 1
357 if reverse:
351 if reverse:
358 h.reverse()
352 h.reverse()
359
353
360 if self.missing:
354 if self.missing:
361 self.rej.append(h)
355 self.rej.append(h)
362 return -1
356 return -1
363
357
364 if self.exists and h.createfile():
358 if self.exists and h.createfile():
365 self.ui.warn(_("file %s already exists\n") % self.fname)
359 self.ui.warn(_("file %s already exists\n") % self.fname)
366 self.rej.append(h)
360 self.rej.append(h)
367 return -1
361 return -1
368
362
369 if isinstance(h, binhunk):
363 if isinstance(h, binhunk):
370 if h.rmfile():
364 if h.rmfile():
371 os.unlink(self.fname)
365 os.unlink(self.fname)
372 else:
366 else:
373 self.lines[:] = h.new()
367 self.lines[:] = h.new()
374 self.offset += len(h.new())
368 self.offset += len(h.new())
375 self.dirty = 1
369 self.dirty = 1
376 return 0
370 return 0
377
371
378 # fast case first, no offsets, no fuzz
372 # fast case first, no offsets, no fuzz
379 old = h.old()
373 old = h.old()
380 # patch starts counting at 1 unless we are adding the file
374 # patch starts counting at 1 unless we are adding the file
381 if h.starta == 0:
375 if h.starta == 0:
382 start = 0
376 start = 0
383 else:
377 else:
384 start = h.starta + self.offset - 1
378 start = h.starta + self.offset - 1
385 orig_start = start
379 orig_start = start
386 if diffhelpers.testhunk(old, self.lines, start) == 0:
380 if diffhelpers.testhunk(old, self.lines, start) == 0:
387 if h.rmfile():
381 if h.rmfile():
388 os.unlink(self.fname)
382 os.unlink(self.fname)
389 else:
383 else:
390 self.lines[start : start + h.lena] = h.new()
384 self.lines[start : start + h.lena] = h.new()
391 self.offset += h.lenb - h.lena
385 self.offset += h.lenb - h.lena
392 self.dirty = 1
386 self.dirty = 1
393 return 0
387 return 0
394
388
395 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
389 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
396 self.hashlines()
390 self.hashlines()
397 if h.hunk[-1][0] != ' ':
391 if h.hunk[-1][0] != ' ':
398 # if the hunk tried to put something at the bottom of the file
392 # if the hunk tried to put something at the bottom of the file
399 # override the start line and use eof here
393 # override the start line and use eof here
400 search_start = len(self.lines)
394 search_start = len(self.lines)
401 else:
395 else:
402 search_start = orig_start
396 search_start = orig_start
403
397
404 for fuzzlen in xrange(3):
398 for fuzzlen in xrange(3):
405 for toponly in [ True, False ]:
399 for toponly in [ True, False ]:
406 old = h.old(fuzzlen, toponly)
400 old = h.old(fuzzlen, toponly)
407
401
408 cand = self.findlines(old[0][1:], search_start)
402 cand = self.findlines(old[0][1:], search_start)
409 for l in cand:
403 for l in cand:
410 if diffhelpers.testhunk(old, self.lines, l) == 0:
404 if diffhelpers.testhunk(old, self.lines, l) == 0:
411 newlines = h.new(fuzzlen, toponly)
405 newlines = h.new(fuzzlen, toponly)
412 self.lines[l : l + len(old)] = newlines
406 self.lines[l : l + len(old)] = newlines
413 self.offset += len(newlines) - len(old)
407 self.offset += len(newlines) - len(old)
414 self.dirty = 1
408 self.dirty = 1
415 if fuzzlen:
409 if fuzzlen:
416 fuzzstr = "with fuzz %d " % fuzzlen
410 fuzzstr = "with fuzz %d " % fuzzlen
417 f = self.ui.warn
411 f = self.ui.warn
418 self.printfile(True)
412 self.printfile(True)
419 else:
413 else:
420 fuzzstr = ""
414 fuzzstr = ""
421 f = self.ui.note
415 f = self.ui.note
422 offset = l - orig_start - fuzzlen
416 offset = l - orig_start - fuzzlen
423 if offset == 1:
417 if offset == 1:
424 linestr = "line"
418 linestr = "line"
425 else:
419 else:
426 linestr = "lines"
420 linestr = "lines"
427 f(_("Hunk #%d succeeded at %d %s(offset %d %s).\n") %
421 f(_("Hunk #%d succeeded at %d %s(offset %d %s).\n") %
428 (h.number, l+1, fuzzstr, offset, linestr))
422 (h.number, l+1, fuzzstr, offset, linestr))
429 return fuzzlen
423 return fuzzlen
430 self.printfile(True)
424 self.printfile(True)
431 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
425 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
432 self.rej.append(h)
426 self.rej.append(h)
433 return -1
427 return -1
434
428
435 class hunk:
429 class hunk:
436 def __init__(self, desc, num, lr, context, create=False, remove=False):
430 def __init__(self, desc, num, lr, context, create=False, remove=False):
437 self.number = num
431 self.number = num
438 self.desc = desc
432 self.desc = desc
439 self.hunk = [ desc ]
433 self.hunk = [ desc ]
440 self.a = []
434 self.a = []
441 self.b = []
435 self.b = []
442 if context:
436 if context:
443 self.read_context_hunk(lr)
437 self.read_context_hunk(lr)
444 else:
438 else:
445 self.read_unified_hunk(lr)
439 self.read_unified_hunk(lr)
446 self.create = create
440 self.create = create
447 self.remove = remove and not create
441 self.remove = remove and not create
448
442
449 def read_unified_hunk(self, lr):
443 def read_unified_hunk(self, lr):
450 m = unidesc.match(self.desc)
444 m = unidesc.match(self.desc)
451 if not m:
445 if not m:
452 raise PatchError(_("bad hunk #%d") % self.number)
446 raise PatchError(_("bad hunk #%d") % self.number)
453 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
447 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
454 if self.lena == None:
448 if self.lena == None:
455 self.lena = 1
449 self.lena = 1
456 else:
450 else:
457 self.lena = int(self.lena)
451 self.lena = int(self.lena)
458 if self.lenb == None:
452 if self.lenb == None:
459 self.lenb = 1
453 self.lenb = 1
460 else:
454 else:
461 self.lenb = int(self.lenb)
455 self.lenb = int(self.lenb)
462 self.starta = int(self.starta)
456 self.starta = int(self.starta)
463 self.startb = int(self.startb)
457 self.startb = int(self.startb)
464 diffhelpers.addlines(lr.fp, self.hunk, self.lena, self.lenb, self.a, self.b)
458 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
465 # if we hit eof before finishing out the hunk, the last line will
459 # if we hit eof before finishing out the hunk, the last line will
466 # be zero length. Lets try to fix it up.
460 # be zero length. Lets try to fix it up.
467 while len(self.hunk[-1]) == 0:
461 while len(self.hunk[-1]) == 0:
468 del self.hunk[-1]
462 del self.hunk[-1]
469 del self.a[-1]
463 del self.a[-1]
470 del self.b[-1]
464 del self.b[-1]
471 self.lena -= 1
465 self.lena -= 1
472 self.lenb -= 1
466 self.lenb -= 1
473
467
474 def read_context_hunk(self, lr):
468 def read_context_hunk(self, lr):
475 self.desc = lr.readline()
469 self.desc = lr.readline()
476 m = contextdesc.match(self.desc)
470 m = contextdesc.match(self.desc)
477 if not m:
471 if not m:
478 raise PatchError(_("bad hunk #%d") % self.number)
472 raise PatchError(_("bad hunk #%d") % self.number)
479 foo, self.starta, foo2, aend, foo3 = m.groups()
473 foo, self.starta, foo2, aend, foo3 = m.groups()
480 self.starta = int(self.starta)
474 self.starta = int(self.starta)
481 if aend == None:
475 if aend == None:
482 aend = self.starta
476 aend = self.starta
483 self.lena = int(aend) - self.starta
477 self.lena = int(aend) - self.starta
484 if self.starta:
478 if self.starta:
485 self.lena += 1
479 self.lena += 1
486 for x in xrange(self.lena):
480 for x in xrange(self.lena):
487 l = lr.readline()
481 l = lr.readline()
488 if l.startswith('---'):
482 if l.startswith('---'):
489 lr.push(l)
483 lr.push(l)
490 break
484 break
491 s = l[2:]
485 s = l[2:]
492 if l.startswith('- ') or l.startswith('! '):
486 if l.startswith('- ') or l.startswith('! '):
493 u = '-' + s
487 u = '-' + s
494 elif l.startswith(' '):
488 elif l.startswith(' '):
495 u = ' ' + s
489 u = ' ' + s
496 else:
490 else:
497 raise PatchError(_("bad hunk #%d old text line %d") %
491 raise PatchError(_("bad hunk #%d old text line %d") %
498 (self.number, x))
492 (self.number, x))
499 self.a.append(u)
493 self.a.append(u)
500 self.hunk.append(u)
494 self.hunk.append(u)
501
495
502 l = lr.readline()
496 l = lr.readline()
503 if l.startswith('\ '):
497 if l.startswith('\ '):
504 s = self.a[-1][:-1]
498 s = self.a[-1][:-1]
505 self.a[-1] = s
499 self.a[-1] = s
506 self.hunk[-1] = s
500 self.hunk[-1] = s
507 l = lr.readline()
501 l = lr.readline()
508 m = contextdesc.match(l)
502 m = contextdesc.match(l)
509 if not m:
503 if not m:
510 raise PatchError(_("bad hunk #%d") % self.number)
504 raise PatchError(_("bad hunk #%d") % self.number)
511 foo, self.startb, foo2, bend, foo3 = m.groups()
505 foo, self.startb, foo2, bend, foo3 = m.groups()
512 self.startb = int(self.startb)
506 self.startb = int(self.startb)
513 if bend == None:
507 if bend == None:
514 bend = self.startb
508 bend = self.startb
515 self.lenb = int(bend) - self.startb
509 self.lenb = int(bend) - self.startb
516 if self.startb:
510 if self.startb:
517 self.lenb += 1
511 self.lenb += 1
518 hunki = 1
512 hunki = 1
519 for x in xrange(self.lenb):
513 for x in xrange(self.lenb):
520 l = lr.readline()
514 l = lr.readline()
521 if l.startswith('\ '):
515 if l.startswith('\ '):
522 s = self.b[-1][:-1]
516 s = self.b[-1][:-1]
523 self.b[-1] = s
517 self.b[-1] = s
524 self.hunk[hunki-1] = s
518 self.hunk[hunki-1] = s
525 continue
519 continue
526 if not l:
520 if not l:
527 lr.push(l)
521 lr.push(l)
528 break
522 break
529 s = l[2:]
523 s = l[2:]
530 if l.startswith('+ ') or l.startswith('! '):
524 if l.startswith('+ ') or l.startswith('! '):
531 u = '+' + s
525 u = '+' + s
532 elif l.startswith(' '):
526 elif l.startswith(' '):
533 u = ' ' + s
527 u = ' ' + s
534 elif len(self.b) == 0:
528 elif len(self.b) == 0:
535 # this can happen when the hunk does not add any lines
529 # this can happen when the hunk does not add any lines
536 lr.push(l)
530 lr.push(l)
537 break
531 break
538 else:
532 else:
539 raise PatchError(_("bad hunk #%d old text line %d") %
533 raise PatchError(_("bad hunk #%d old text line %d") %
540 (self.number, x))
534 (self.number, x))
541 self.b.append(s)
535 self.b.append(s)
542 while True:
536 while True:
543 if hunki >= len(self.hunk):
537 if hunki >= len(self.hunk):
544 h = ""
538 h = ""
545 else:
539 else:
546 h = self.hunk[hunki]
540 h = self.hunk[hunki]
547 hunki += 1
541 hunki += 1
548 if h == u:
542 if h == u:
549 break
543 break
550 elif h.startswith('-'):
544 elif h.startswith('-'):
551 continue
545 continue
552 else:
546 else:
553 self.hunk.insert(hunki-1, u)
547 self.hunk.insert(hunki-1, u)
554 break
548 break
555
549
556 if not self.a:
550 if not self.a:
557 # this happens when lines were only added to the hunk
551 # this happens when lines were only added to the hunk
558 for x in self.hunk:
552 for x in self.hunk:
559 if x.startswith('-') or x.startswith(' '):
553 if x.startswith('-') or x.startswith(' '):
560 self.a.append(x)
554 self.a.append(x)
561 if not self.b:
555 if not self.b:
562 # this happens when lines were only deleted from the hunk
556 # this happens when lines were only deleted from the hunk
563 for x in self.hunk:
557 for x in self.hunk:
564 if x.startswith('+') or x.startswith(' '):
558 if x.startswith('+') or x.startswith(' '):
565 self.b.append(x[1:])
559 self.b.append(x[1:])
566 # @@ -start,len +start,len @@
560 # @@ -start,len +start,len @@
567 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
561 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
568 self.startb, self.lenb)
562 self.startb, self.lenb)
569 self.hunk[0] = self.desc
563 self.hunk[0] = self.desc
570
564
571 def reverse(self):
565 def reverse(self):
572 self.create, self.remove = self.remove, self.create
566 self.create, self.remove = self.remove, self.create
573 origlena = self.lena
567 origlena = self.lena
574 origstarta = self.starta
568 origstarta = self.starta
575 self.lena = self.lenb
569 self.lena = self.lenb
576 self.starta = self.startb
570 self.starta = self.startb
577 self.lenb = origlena
571 self.lenb = origlena
578 self.startb = origstarta
572 self.startb = origstarta
579 self.a = []
573 self.a = []
580 self.b = []
574 self.b = []
581 # self.hunk[0] is the @@ description
575 # self.hunk[0] is the @@ description
582 for x in xrange(1, len(self.hunk)):
576 for x in xrange(1, len(self.hunk)):
583 o = self.hunk[x]
577 o = self.hunk[x]
584 if o.startswith('-'):
578 if o.startswith('-'):
585 n = '+' + o[1:]
579 n = '+' + o[1:]
586 self.b.append(o[1:])
580 self.b.append(o[1:])
587 elif o.startswith('+'):
581 elif o.startswith('+'):
588 n = '-' + o[1:]
582 n = '-' + o[1:]
589 self.a.append(n)
583 self.a.append(n)
590 else:
584 else:
591 n = o
585 n = o
592 self.b.append(o[1:])
586 self.b.append(o[1:])
593 self.a.append(o)
587 self.a.append(o)
594 self.hunk[x] = o
588 self.hunk[x] = o
595
589
596 def fix_newline(self):
590 def fix_newline(self):
597 diffhelpers.fix_newline(self.hunk, self.a, self.b)
591 diffhelpers.fix_newline(self.hunk, self.a, self.b)
598
592
599 def complete(self):
593 def complete(self):
600 return len(self.a) == self.lena and len(self.b) == self.lenb
594 return len(self.a) == self.lena and len(self.b) == self.lenb
601
595
602 def createfile(self):
596 def createfile(self):
603 return self.starta == 0 and self.lena == 0 and self.create
597 return self.starta == 0 and self.lena == 0 and self.create
604
598
605 def rmfile(self):
599 def rmfile(self):
606 return self.startb == 0 and self.lenb == 0 and self.remove
600 return self.startb == 0 and self.lenb == 0 and self.remove
607
601
608 def fuzzit(self, l, fuzz, toponly):
602 def fuzzit(self, l, fuzz, toponly):
609 # this removes context lines from the top and bottom of list 'l'. It
603 # this removes context lines from the top and bottom of list 'l'. It
610 # checks the hunk to make sure only context lines are removed, and then
604 # checks the hunk to make sure only context lines are removed, and then
611 # returns a new shortened list of lines.
605 # returns a new shortened list of lines.
612 fuzz = min(fuzz, len(l)-1)
606 fuzz = min(fuzz, len(l)-1)
613 if fuzz:
607 if fuzz:
614 top = 0
608 top = 0
615 bot = 0
609 bot = 0
616 hlen = len(self.hunk)
610 hlen = len(self.hunk)
617 for x in xrange(hlen-1):
611 for x in xrange(hlen-1):
618 # the hunk starts with the @@ line, so use x+1
612 # the hunk starts with the @@ line, so use x+1
619 if self.hunk[x+1][0] == ' ':
613 if self.hunk[x+1][0] == ' ':
620 top += 1
614 top += 1
621 else:
615 else:
622 break
616 break
623 if not toponly:
617 if not toponly:
624 for x in xrange(hlen-1):
618 for x in xrange(hlen-1):
625 if self.hunk[hlen-bot-1][0] == ' ':
619 if self.hunk[hlen-bot-1][0] == ' ':
626 bot += 1
620 bot += 1
627 else:
621 else:
628 break
622 break
629
623
630 # top and bot now count context in the hunk
624 # top and bot now count context in the hunk
631 # adjust them if either one is short
625 # adjust them if either one is short
632 context = max(top, bot, 3)
626 context = max(top, bot, 3)
633 if bot < context:
627 if bot < context:
634 bot = max(0, fuzz - (context - bot))
628 bot = max(0, fuzz - (context - bot))
635 else:
629 else:
636 bot = min(fuzz, bot)
630 bot = min(fuzz, bot)
637 if top < context:
631 if top < context:
638 top = max(0, fuzz - (context - top))
632 top = max(0, fuzz - (context - top))
639 else:
633 else:
640 top = min(fuzz, top)
634 top = min(fuzz, top)
641
635
642 return l[top:len(l)-bot]
636 return l[top:len(l)-bot]
643 return l
637 return l
644
638
645 def old(self, fuzz=0, toponly=False):
639 def old(self, fuzz=0, toponly=False):
646 return self.fuzzit(self.a, fuzz, toponly)
640 return self.fuzzit(self.a, fuzz, toponly)
647
641
648 def newctrl(self):
642 def newctrl(self):
649 res = []
643 res = []
650 for x in self.hunk:
644 for x in self.hunk:
651 c = x[0]
645 c = x[0]
652 if c == ' ' or c == '+':
646 if c == ' ' or c == '+':
653 res.append(x)
647 res.append(x)
654 return res
648 return res
655
649
656 def new(self, fuzz=0, toponly=False):
650 def new(self, fuzz=0, toponly=False):
657 return self.fuzzit(self.b, fuzz, toponly)
651 return self.fuzzit(self.b, fuzz, toponly)
658
652
659 class binhunk:
653 class binhunk:
660 'A binary patch file. Only understands literals so far.'
654 'A binary patch file. Only understands literals so far.'
661 def __init__(self, gitpatch):
655 def __init__(self, gitpatch):
662 self.gitpatch = gitpatch
656 self.gitpatch = gitpatch
663 self.text = None
657 self.text = None
664 self.hunk = ['GIT binary patch\n']
658 self.hunk = ['GIT binary patch\n']
665
659
666 def createfile(self):
660 def createfile(self):
667 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
661 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
668
662
669 def rmfile(self):
663 def rmfile(self):
670 return self.gitpatch.op == 'DELETE'
664 return self.gitpatch.op == 'DELETE'
671
665
672 def complete(self):
666 def complete(self):
673 return self.text is not None
667 return self.text is not None
674
668
675 def new(self):
669 def new(self):
676 return [self.text]
670 return [self.text]
677
671
678 def extract(self, fp):
672 def extract(self, fp):
679 line = fp.readline()
673 line = fp.readline()
680 self.hunk.append(line)
674 self.hunk.append(line)
681 while line and not line.startswith('literal '):
675 while line and not line.startswith('literal '):
682 line = fp.readline()
676 line = fp.readline()
683 self.hunk.append(line)
677 self.hunk.append(line)
684 if not line:
678 if not line:
685 raise PatchError(_('could not extract binary patch'))
679 raise PatchError(_('could not extract binary patch'))
686 size = int(line[8:].rstrip())
680 size = int(line[8:].rstrip())
687 dec = []
681 dec = []
688 line = fp.readline()
682 line = fp.readline()
689 self.hunk.append(line)
683 self.hunk.append(line)
690 while len(line) > 1:
684 while len(line) > 1:
691 l = line[0]
685 l = line[0]
692 if l <= 'Z' and l >= 'A':
686 if l <= 'Z' and l >= 'A':
693 l = ord(l) - ord('A') + 1
687 l = ord(l) - ord('A') + 1
694 else:
688 else:
695 l = ord(l) - ord('a') + 27
689 l = ord(l) - ord('a') + 27
696 dec.append(base85.b85decode(line[1:-1])[:l])
690 dec.append(base85.b85decode(line[1:-1])[:l])
697 line = fp.readline()
691 line = fp.readline()
698 self.hunk.append(line)
692 self.hunk.append(line)
699 text = zlib.decompress(''.join(dec))
693 text = zlib.decompress(''.join(dec))
700 if len(text) != size:
694 if len(text) != size:
701 raise PatchError(_('binary patch is %d bytes, not %d') %
695 raise PatchError(_('binary patch is %d bytes, not %d') %
702 len(text), size)
696 len(text), size)
703 self.text = text
697 self.text = text
704
698
705 def parsefilename(str):
699 def parsefilename(str):
706 # --- filename \t|space stuff
700 # --- filename \t|space stuff
707 s = str[4:].rstrip('\r\n')
701 s = str[4:].rstrip('\r\n')
708 i = s.find('\t')
702 i = s.find('\t')
709 if i < 0:
703 if i < 0:
710 i = s.find(' ')
704 i = s.find(' ')
711 if i < 0:
705 if i < 0:
712 return s
706 return s
713 return s[:i]
707 return s[:i]
714
708
715 def selectfile(afile_orig, bfile_orig, hunk, strip, reverse):
709 def selectfile(afile_orig, bfile_orig, hunk, strip, reverse):
716 def pathstrip(path, count=1):
710 def pathstrip(path, count=1):
717 pathlen = len(path)
711 pathlen = len(path)
718 i = 0
712 i = 0
719 if count == 0:
713 if count == 0:
720 return '', path.rstrip()
714 return '', path.rstrip()
721 while count > 0:
715 while count > 0:
722 i = path.find('/', i)
716 i = path.find('/', i)
723 if i == -1:
717 if i == -1:
724 raise PatchError(_("unable to strip away %d dirs from %s") %
718 raise PatchError(_("unable to strip away %d dirs from %s") %
725 (count, path))
719 (count, path))
726 i += 1
720 i += 1
727 # consume '//' in the path
721 # consume '//' in the path
728 while i < pathlen - 1 and path[i] == '/':
722 while i < pathlen - 1 and path[i] == '/':
729 i += 1
723 i += 1
730 count -= 1
724 count -= 1
731 return path[:i].lstrip(), path[i:].rstrip()
725 return path[:i].lstrip(), path[i:].rstrip()
732
726
733 nulla = afile_orig == "/dev/null"
727 nulla = afile_orig == "/dev/null"
734 nullb = bfile_orig == "/dev/null"
728 nullb = bfile_orig == "/dev/null"
735 abase, afile = pathstrip(afile_orig, strip)
729 abase, afile = pathstrip(afile_orig, strip)
736 gooda = not nulla and os.path.exists(afile)
730 gooda = not nulla and os.path.exists(afile)
737 bbase, bfile = pathstrip(bfile_orig, strip)
731 bbase, bfile = pathstrip(bfile_orig, strip)
738 if afile == bfile:
732 if afile == bfile:
739 goodb = gooda
733 goodb = gooda
740 else:
734 else:
741 goodb = not nullb and os.path.exists(bfile)
735 goodb = not nullb and os.path.exists(bfile)
742 createfunc = hunk.createfile
736 createfunc = hunk.createfile
743 if reverse:
737 if reverse:
744 createfunc = hunk.rmfile
738 createfunc = hunk.rmfile
745 missing = not goodb and not gooda and not createfunc()
739 missing = not goodb and not gooda and not createfunc()
746 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
740 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
747 # diff is between a file and its backup. In this case, the original
741 # diff is between a file and its backup. In this case, the original
748 # file should be patched (see original mpatch code).
742 # file should be patched (see original mpatch code).
749 isbackup = (abase == bbase and bfile.startswith(afile))
743 isbackup = (abase == bbase and bfile.startswith(afile))
750 fname = None
744 fname = None
751 if not missing:
745 if not missing:
752 if gooda and goodb:
746 if gooda and goodb:
753 fname = isbackup and afile or bfile
747 fname = isbackup and afile or bfile
754 elif gooda:
748 elif gooda:
755 fname = afile
749 fname = afile
756
750
757 if not fname:
751 if not fname:
758 if not nullb:
752 if not nullb:
759 fname = isbackup and afile or bfile
753 fname = isbackup and afile or bfile
760 elif not nulla:
754 elif not nulla:
761 fname = afile
755 fname = afile
762 else:
756 else:
763 raise PatchError(_("undefined source and destination files"))
757 raise PatchError(_("undefined source and destination files"))
764
758
765 return fname, missing
759 return fname, missing
766
760
767 class linereader:
761 class linereader:
768 # simple class to allow pushing lines back into the input stream
762 # simple class to allow pushing lines back into the input stream
769 def __init__(self, fp):
763 def __init__(self, fp):
770 self.fp = fp
764 self.fp = fp
771 self.buf = []
765 self.buf = []
772
766
773 def push(self, line):
767 def push(self, line):
774 self.buf.append(line)
768 if line is not None:
769 self.buf.append(line)
775
770
776 def readline(self):
771 def readline(self):
777 if self.buf:
772 if self.buf:
778 l = self.buf[0]
773 l = self.buf[0]
779 del self.buf[0]
774 del self.buf[0]
780 return l
775 return l
781 return self.fp.readline()
776 return self.fp.readline()
782
777
778 def __iter__(self):
779 while 1:
780 l = self.readline()
781 if not l:
782 break
783 yield l
784
785 def scangitpatch(lr, firstline):
786 """
787 Git patches can emit:
788 - rename a to b
789 - change b
790 - copy a to c
791 - change c
792
793 We cannot apply this sequence as-is, the renamed 'a' could not be
794 found for it would have been renamed already. And we cannot copy
795 from 'b' instead because 'b' would have been changed already. So
796 we scan the git patch for copy and rename commands so we can
797 perform the copies ahead of time.
798 """
799 pos = 0
800 try:
801 pos = lr.fp.tell()
802 fp = lr.fp
803 except IOError:
804 fp = cStringIO.StringIO(lr.fp.read())
805 gitlr = linereader(fp)
806 gitlr.push(firstline)
807 (dopatch, gitpatches) = readgitpatch(gitlr)
808 fp.seek(pos)
809 return fp, dopatch, gitpatches
810
783 def iterhunks(ui, fp, sourcefile=None):
811 def iterhunks(ui, fp, sourcefile=None):
784 """Read a patch and yield the following events:
812 """Read a patch and yield the following events:
785 - ("file", afile, bfile, firsthunk): select a new target file.
813 - ("file", afile, bfile, firsthunk): select a new target file.
786 - ("hunk", hunk): a new hunk is ready to be applied, follows a
814 - ("hunk", hunk): a new hunk is ready to be applied, follows a
787 "file" event.
815 "file" event.
788 - ("git", gitchanges): current diff is in git format, gitchanges
816 - ("git", gitchanges): current diff is in git format, gitchanges
789 maps filenames to gitpatch records. Unique event.
817 maps filenames to gitpatch records. Unique event.
790 """
818 """
791
792 def scangitpatch(fp, firstline):
793 '''git patches can modify a file, then copy that file to
794 a new file, but expect the source to be the unmodified form.
795 So we scan the patch looking for that case so we can do
796 the copies ahead of time.'''
797
798 pos = 0
799 try:
800 pos = fp.tell()
801 except IOError:
802 fp = cStringIO.StringIO(fp.read())
803
804 (dopatch, gitpatches) = readgitpatch(fp, firstline)
805 fp.seek(pos)
806
807 return fp, dopatch, gitpatches
808
809 changed = {}
819 changed = {}
810 current_hunk = None
820 current_hunk = None
811 afile = ""
821 afile = ""
812 bfile = ""
822 bfile = ""
813 state = None
823 state = None
814 hunknum = 0
824 hunknum = 0
815 emitfile = False
825 emitfile = False
816
826
817 git = False
827 git = False
818 gitre = re.compile('diff --git (a/.*) (b/.*)')
828 gitre = re.compile('diff --git (a/.*) (b/.*)')
819
829
820 # our states
830 # our states
821 BFILE = 1
831 BFILE = 1
822 context = None
832 context = None
823 lr = linereader(fp)
833 lr = linereader(fp)
824 dopatch = True
834 dopatch = True
825 # gitworkdone is True if a git operation (copy, rename, ...) was
835 # gitworkdone is True if a git operation (copy, rename, ...) was
826 # performed already for the current file. Useful when the file
836 # performed already for the current file. Useful when the file
827 # section may have no hunk.
837 # section may have no hunk.
828 gitworkdone = False
838 gitworkdone = False
829
839
830 while True:
840 while True:
831 newfile = False
841 newfile = False
832 x = lr.readline()
842 x = lr.readline()
833 if not x:
843 if not x:
834 break
844 break
835 if current_hunk:
845 if current_hunk:
836 if x.startswith('\ '):
846 if x.startswith('\ '):
837 current_hunk.fix_newline()
847 current_hunk.fix_newline()
838 yield 'hunk', current_hunk
848 yield 'hunk', current_hunk
839 current_hunk = None
849 current_hunk = None
840 gitworkdone = False
850 gitworkdone = False
841 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
851 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
842 ((context or context == None) and x.startswith('***************')))):
852 ((context or context == None) and x.startswith('***************')))):
843 try:
853 try:
844 if context == None and x.startswith('***************'):
854 if context == None and x.startswith('***************'):
845 context = True
855 context = True
846 gpatch = changed.get(bfile[2:])
856 gpatch = changed.get(bfile[2:])
847 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
857 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
848 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
858 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
849 current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
859 current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
850 except PatchError, err:
860 except PatchError, err:
851 ui.debug(err)
861 ui.debug(err)
852 current_hunk = None
862 current_hunk = None
853 continue
863 continue
854 hunknum += 1
864 hunknum += 1
855 if emitfile:
865 if emitfile:
856 emitfile = False
866 emitfile = False
857 yield 'file', (afile, bfile, current_hunk)
867 yield 'file', (afile, bfile, current_hunk)
858 elif state == BFILE and x.startswith('GIT binary patch'):
868 elif state == BFILE and x.startswith('GIT binary patch'):
859 current_hunk = binhunk(changed[bfile[2:]])
869 current_hunk = binhunk(changed[bfile[2:]])
860 hunknum += 1
870 hunknum += 1
861 if emitfile:
871 if emitfile:
862 emitfile = False
872 emitfile = False
863 yield 'file', (afile, bfile, current_hunk)
873 yield 'file', (afile, bfile, current_hunk)
864 current_hunk.extract(fp)
874 current_hunk.extract(fp)
865 elif x.startswith('diff --git'):
875 elif x.startswith('diff --git'):
866 # check for git diff, scanning the whole patch file if needed
876 # check for git diff, scanning the whole patch file if needed
867 m = gitre.match(x)
877 m = gitre.match(x)
868 if m:
878 if m:
869 afile, bfile = m.group(1, 2)
879 afile, bfile = m.group(1, 2)
870 if not git:
880 if not git:
871 git = True
881 git = True
872 fp, dopatch, gitpatches = scangitpatch(fp, x)
882 fp, dopatch, gitpatches = scangitpatch(lr, x)
873 yield 'git', gitpatches
883 yield 'git', gitpatches
874 for gp in gitpatches:
884 for gp in gitpatches:
875 changed[gp.path] = gp
885 changed[gp.path] = gp
876 # else error?
886 # else error?
877 # copy/rename + modify should modify target, not source
887 # copy/rename + modify should modify target, not source
878 gp = changed.get(bfile[2:])
888 gp = changed.get(bfile[2:])
879 if gp and gp.op in ('COPY', 'DELETE', 'RENAME'):
889 if gp and gp.op in ('COPY', 'DELETE', 'RENAME'):
880 afile = bfile
890 afile = bfile
881 gitworkdone = True
891 gitworkdone = True
882 newfile = True
892 newfile = True
883 elif x.startswith('---'):
893 elif x.startswith('---'):
884 # check for a unified diff
894 # check for a unified diff
885 l2 = lr.readline()
895 l2 = lr.readline()
886 if not l2.startswith('+++'):
896 if not l2.startswith('+++'):
887 lr.push(l2)
897 lr.push(l2)
888 continue
898 continue
889 newfile = True
899 newfile = True
890 context = False
900 context = False
891 afile = parsefilename(x)
901 afile = parsefilename(x)
892 bfile = parsefilename(l2)
902 bfile = parsefilename(l2)
893 elif x.startswith('***'):
903 elif x.startswith('***'):
894 # check for a context diff
904 # check for a context diff
895 l2 = lr.readline()
905 l2 = lr.readline()
896 if not l2.startswith('---'):
906 if not l2.startswith('---'):
897 lr.push(l2)
907 lr.push(l2)
898 continue
908 continue
899 l3 = lr.readline()
909 l3 = lr.readline()
900 lr.push(l3)
910 lr.push(l3)
901 if not l3.startswith("***************"):
911 if not l3.startswith("***************"):
902 lr.push(l2)
912 lr.push(l2)
903 continue
913 continue
904 newfile = True
914 newfile = True
905 context = True
915 context = True
906 afile = parsefilename(x)
916 afile = parsefilename(x)
907 bfile = parsefilename(l2)
917 bfile = parsefilename(l2)
908
918
909 if newfile:
919 if newfile:
910 emitfile = True
920 emitfile = True
911 state = BFILE
921 state = BFILE
912 hunknum = 0
922 hunknum = 0
913 if current_hunk:
923 if current_hunk:
914 if current_hunk.complete():
924 if current_hunk.complete():
915 yield 'hunk', current_hunk
925 yield 'hunk', current_hunk
916 else:
926 else:
917 raise PatchError(_("malformed patch %s %s") % (afile,
927 raise PatchError(_("malformed patch %s %s") % (afile,
918 current_hunk.desc))
928 current_hunk.desc))
919
929
920 if hunknum == 0 and dopatch and not gitworkdone:
930 if hunknum == 0 and dopatch and not gitworkdone:
921 raise NoHunks
931 raise NoHunks
922
932
923 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False):
933 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False):
924 """reads a patch from fp and tries to apply it. The dict 'changed' is
934 """reads a patch from fp and tries to apply it. The dict 'changed' is
925 filled in with all of the filenames changed by the patch. Returns 0
935 filled in with all of the filenames changed by the patch. Returns 0
926 for a clean patch, -1 if any rejects were found and 1 if there was
936 for a clean patch, -1 if any rejects were found and 1 if there was
927 any fuzz."""
937 any fuzz."""
928
938
929 rejects = 0
939 rejects = 0
930 err = 0
940 err = 0
931 current_file = None
941 current_file = None
932 gitpatches = None
942 gitpatches = None
933
943
934 def closefile():
944 def closefile():
935 if not current_file:
945 if not current_file:
936 return 0
946 return 0
937 current_file.close()
947 current_file.close()
938 return len(current_file.rej)
948 return len(current_file.rej)
939
949
940 for state, values in iterhunks(ui, fp, sourcefile):
950 for state, values in iterhunks(ui, fp, sourcefile):
941 if state == 'hunk':
951 if state == 'hunk':
942 if not current_file:
952 if not current_file:
943 continue
953 continue
944 current_hunk = values
954 current_hunk = values
945 ret = current_file.apply(current_hunk, reverse)
955 ret = current_file.apply(current_hunk, reverse)
946 if ret >= 0:
956 if ret >= 0:
947 changed.setdefault(current_file.fname, None)
957 changed.setdefault(current_file.fname, None)
948 if ret > 0:
958 if ret > 0:
949 err = 1
959 err = 1
950 elif state == 'file':
960 elif state == 'file':
951 rejects += closefile()
961 rejects += closefile()
952 afile, bfile, first_hunk = values
962 afile, bfile, first_hunk = values
953 try:
963 try:
954 if sourcefile:
964 if sourcefile:
955 current_file = patchfile(ui, sourcefile)
965 current_file = patchfile(ui, sourcefile)
956 else:
966 else:
957 current_file, missing = selectfile(afile, bfile, first_hunk,
967 current_file, missing = selectfile(afile, bfile, first_hunk,
958 strip, reverse)
968 strip, reverse)
959 current_file = patchfile(ui, current_file, missing)
969 current_file = patchfile(ui, current_file, missing)
960 except PatchError, err:
970 except PatchError, err:
961 ui.warn(str(err) + '\n')
971 ui.warn(str(err) + '\n')
962 current_file, current_hunk = None, None
972 current_file, current_hunk = None, None
963 rejects += 1
973 rejects += 1
964 continue
974 continue
965 elif state == 'git':
975 elif state == 'git':
966 gitpatches = values
976 gitpatches = values
967 cwd = os.getcwd()
977 cwd = os.getcwd()
968 for gp in gitpatches:
978 for gp in gitpatches:
969 if gp.op in ('COPY', 'RENAME'):
979 if gp.op in ('COPY', 'RENAME'):
970 src, dst = [util.canonpath(cwd, cwd, x)
980 src, dst = [util.canonpath(cwd, cwd, x)
971 for x in [gp.oldpath, gp.path]]
981 for x in [gp.oldpath, gp.path]]
972 copyfile(src, dst)
982 copyfile(src, dst)
973 changed[gp.path] = gp
983 changed[gp.path] = gp
974 else:
984 else:
975 raise util.Abort(_('unsupported parser state: %s') % state)
985 raise util.Abort(_('unsupported parser state: %s') % state)
976
986
977 rejects += closefile()
987 rejects += closefile()
978
988
979 if rejects:
989 if rejects:
980 return -1
990 return -1
981 return err
991 return err
982
992
983 def diffopts(ui, opts={}, untrusted=False):
993 def diffopts(ui, opts={}, untrusted=False):
984 def get(key, name=None, getter=ui.configbool):
994 def get(key, name=None, getter=ui.configbool):
985 return (opts.get(key) or
995 return (opts.get(key) or
986 getter('diff', name or key, None, untrusted=untrusted))
996 getter('diff', name or key, None, untrusted=untrusted))
987 return mdiff.diffopts(
997 return mdiff.diffopts(
988 text=opts.get('text'),
998 text=opts.get('text'),
989 git=get('git'),
999 git=get('git'),
990 nodates=get('nodates'),
1000 nodates=get('nodates'),
991 showfunc=get('show_function', 'showfunc'),
1001 showfunc=get('show_function', 'showfunc'),
992 ignorews=get('ignore_all_space', 'ignorews'),
1002 ignorews=get('ignore_all_space', 'ignorews'),
993 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1003 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
994 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1004 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
995 context=get('unified', getter=ui.config))
1005 context=get('unified', getter=ui.config))
996
1006
997 def updatedir(ui, repo, patches):
1007 def updatedir(ui, repo, patches):
998 '''Update dirstate after patch application according to metadata'''
1008 '''Update dirstate after patch application according to metadata'''
999 if not patches:
1009 if not patches:
1000 return
1010 return
1001 copies = []
1011 copies = []
1002 removes = {}
1012 removes = {}
1003 cfiles = patches.keys()
1013 cfiles = patches.keys()
1004 cwd = repo.getcwd()
1014 cwd = repo.getcwd()
1005 if cwd:
1015 if cwd:
1006 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1016 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1007 for f in patches:
1017 for f in patches:
1008 gp = patches[f]
1018 gp = patches[f]
1009 if not gp:
1019 if not gp:
1010 continue
1020 continue
1011 if gp.op == 'RENAME':
1021 if gp.op == 'RENAME':
1012 copies.append((gp.oldpath, gp.path))
1022 copies.append((gp.oldpath, gp.path))
1013 removes[gp.oldpath] = 1
1023 removes[gp.oldpath] = 1
1014 elif gp.op == 'COPY':
1024 elif gp.op == 'COPY':
1015 copies.append((gp.oldpath, gp.path))
1025 copies.append((gp.oldpath, gp.path))
1016 elif gp.op == 'DELETE':
1026 elif gp.op == 'DELETE':
1017 removes[gp.path] = 1
1027 removes[gp.path] = 1
1018 for src, dst in copies:
1028 for src, dst in copies:
1019 repo.copy(src, dst)
1029 repo.copy(src, dst)
1020 removes = removes.keys()
1030 removes = removes.keys()
1021 if removes:
1031 if removes:
1022 repo.remove(util.sort(removes), True)
1032 repo.remove(util.sort(removes), True)
1023 for f in patches:
1033 for f in patches:
1024 gp = patches[f]
1034 gp = patches[f]
1025 if gp and gp.mode:
1035 if gp and gp.mode:
1026 islink, isexec = gp.mode
1036 islink, isexec = gp.mode
1027 dst = os.path.join(repo.root, gp.path)
1037 dst = os.path.join(repo.root, gp.path)
1028 # patch won't create empty files
1038 # patch won't create empty files
1029 if gp.op == 'ADD' and not os.path.exists(dst):
1039 if gp.op == 'ADD' and not os.path.exists(dst):
1030 flags = (isexec and 'x' or '') + (islink and 'l' or '')
1040 flags = (isexec and 'x' or '') + (islink and 'l' or '')
1031 repo.wwrite(gp.path, '', flags)
1041 repo.wwrite(gp.path, '', flags)
1032 else:
1042 else:
1033 util.set_flags(dst, islink, isexec)
1043 util.set_flags(dst, islink, isexec)
1034 cmdutil.addremove(repo, cfiles)
1044 cmdutil.addremove(repo, cfiles)
1035 files = patches.keys()
1045 files = patches.keys()
1036 files.extend([r for r in removes if r not in files])
1046 files.extend([r for r in removes if r not in files])
1037 return util.sort(files)
1047 return util.sort(files)
1038
1048
1039 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
1049 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
1040 """use <patcher> to apply <patchname> to the working directory.
1050 """use <patcher> to apply <patchname> to the working directory.
1041 returns whether patch was applied with fuzz factor."""
1051 returns whether patch was applied with fuzz factor."""
1042
1052
1043 fuzz = False
1053 fuzz = False
1044 if cwd:
1054 if cwd:
1045 args.append('-d %s' % util.shellquote(cwd))
1055 args.append('-d %s' % util.shellquote(cwd))
1046 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1056 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1047 util.shellquote(patchname)))
1057 util.shellquote(patchname)))
1048
1058
1049 for line in fp:
1059 for line in fp:
1050 line = line.rstrip()
1060 line = line.rstrip()
1051 ui.note(line + '\n')
1061 ui.note(line + '\n')
1052 if line.startswith('patching file '):
1062 if line.startswith('patching file '):
1053 pf = util.parse_patch_output(line)
1063 pf = util.parse_patch_output(line)
1054 printed_file = False
1064 printed_file = False
1055 files.setdefault(pf, (None, None))
1065 files.setdefault(pf, (None, None))
1056 elif line.find('with fuzz') >= 0:
1066 elif line.find('with fuzz') >= 0:
1057 fuzz = True
1067 fuzz = True
1058 if not printed_file:
1068 if not printed_file:
1059 ui.warn(pf + '\n')
1069 ui.warn(pf + '\n')
1060 printed_file = True
1070 printed_file = True
1061 ui.warn(line + '\n')
1071 ui.warn(line + '\n')
1062 elif line.find('saving rejects to file') >= 0:
1072 elif line.find('saving rejects to file') >= 0:
1063 ui.warn(line + '\n')
1073 ui.warn(line + '\n')
1064 elif line.find('FAILED') >= 0:
1074 elif line.find('FAILED') >= 0:
1065 if not printed_file:
1075 if not printed_file:
1066 ui.warn(pf + '\n')
1076 ui.warn(pf + '\n')
1067 printed_file = True
1077 printed_file = True
1068 ui.warn(line + '\n')
1078 ui.warn(line + '\n')
1069 code = fp.close()
1079 code = fp.close()
1070 if code:
1080 if code:
1071 raise PatchError(_("patch command failed: %s") %
1081 raise PatchError(_("patch command failed: %s") %
1072 util.explain_exit(code)[0])
1082 util.explain_exit(code)[0])
1073 return fuzz
1083 return fuzz
1074
1084
1075 def internalpatch(patchobj, ui, strip, cwd, files={}):
1085 def internalpatch(patchobj, ui, strip, cwd, files={}):
1076 """use builtin patch to apply <patchobj> to the working directory.
1086 """use builtin patch to apply <patchobj> to the working directory.
1077 returns whether patch was applied with fuzz factor."""
1087 returns whether patch was applied with fuzz factor."""
1078 try:
1088 try:
1079 fp = file(patchobj, 'rb')
1089 fp = file(patchobj, 'rb')
1080 except TypeError:
1090 except TypeError:
1081 fp = patchobj
1091 fp = patchobj
1082 if cwd:
1092 if cwd:
1083 curdir = os.getcwd()
1093 curdir = os.getcwd()
1084 os.chdir(cwd)
1094 os.chdir(cwd)
1085 try:
1095 try:
1086 ret = applydiff(ui, fp, files, strip=strip)
1096 ret = applydiff(ui, fp, files, strip=strip)
1087 finally:
1097 finally:
1088 if cwd:
1098 if cwd:
1089 os.chdir(curdir)
1099 os.chdir(curdir)
1090 if ret < 0:
1100 if ret < 0:
1091 raise PatchError
1101 raise PatchError
1092 return ret > 0
1102 return ret > 0
1093
1103
1094 def patch(patchname, ui, strip=1, cwd=None, files={}):
1104 def patch(patchname, ui, strip=1, cwd=None, files={}):
1095 """apply <patchname> to the working directory.
1105 """apply <patchname> to the working directory.
1096 returns whether patch was applied with fuzz factor."""
1106 returns whether patch was applied with fuzz factor."""
1097 patcher = ui.config('ui', 'patch')
1107 patcher = ui.config('ui', 'patch')
1098 args = []
1108 args = []
1099 try:
1109 try:
1100 if patcher:
1110 if patcher:
1101 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1111 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1102 files)
1112 files)
1103 else:
1113 else:
1104 try:
1114 try:
1105 return internalpatch(patchname, ui, strip, cwd, files)
1115 return internalpatch(patchname, ui, strip, cwd, files)
1106 except NoHunks:
1116 except NoHunks:
1107 patcher = util.find_exe('gpatch') or util.find_exe('patch')
1117 patcher = util.find_exe('gpatch') or util.find_exe('patch')
1108 ui.debug(_('no valid hunks found; trying with %r instead\n') %
1118 ui.debug(_('no valid hunks found; trying with %r instead\n') %
1109 patcher)
1119 patcher)
1110 if util.needbinarypatch():
1120 if util.needbinarypatch():
1111 args.append('--binary')
1121 args.append('--binary')
1112 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1122 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1113 files)
1123 files)
1114 except PatchError, err:
1124 except PatchError, err:
1115 s = str(err)
1125 s = str(err)
1116 if s:
1126 if s:
1117 raise util.Abort(s)
1127 raise util.Abort(s)
1118 else:
1128 else:
1119 raise util.Abort(_('patch failed to apply'))
1129 raise util.Abort(_('patch failed to apply'))
1120
1130
1121 def b85diff(to, tn):
1131 def b85diff(to, tn):
1122 '''print base85-encoded binary diff'''
1132 '''print base85-encoded binary diff'''
1123 def gitindex(text):
1133 def gitindex(text):
1124 if not text:
1134 if not text:
1125 return '0' * 40
1135 return '0' * 40
1126 l = len(text)
1136 l = len(text)
1127 s = util.sha1('blob %d\0' % l)
1137 s = util.sha1('blob %d\0' % l)
1128 s.update(text)
1138 s.update(text)
1129 return s.hexdigest()
1139 return s.hexdigest()
1130
1140
1131 def fmtline(line):
1141 def fmtline(line):
1132 l = len(line)
1142 l = len(line)
1133 if l <= 26:
1143 if l <= 26:
1134 l = chr(ord('A') + l - 1)
1144 l = chr(ord('A') + l - 1)
1135 else:
1145 else:
1136 l = chr(l - 26 + ord('a') - 1)
1146 l = chr(l - 26 + ord('a') - 1)
1137 return '%c%s\n' % (l, base85.b85encode(line, True))
1147 return '%c%s\n' % (l, base85.b85encode(line, True))
1138
1148
1139 def chunk(text, csize=52):
1149 def chunk(text, csize=52):
1140 l = len(text)
1150 l = len(text)
1141 i = 0
1151 i = 0
1142 while i < l:
1152 while i < l:
1143 yield text[i:i+csize]
1153 yield text[i:i+csize]
1144 i += csize
1154 i += csize
1145
1155
1146 tohash = gitindex(to)
1156 tohash = gitindex(to)
1147 tnhash = gitindex(tn)
1157 tnhash = gitindex(tn)
1148 if tohash == tnhash:
1158 if tohash == tnhash:
1149 return ""
1159 return ""
1150
1160
1151 # TODO: deltas
1161 # TODO: deltas
1152 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1162 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1153 (tohash, tnhash, len(tn))]
1163 (tohash, tnhash, len(tn))]
1154 for l in chunk(zlib.compress(tn)):
1164 for l in chunk(zlib.compress(tn)):
1155 ret.append(fmtline(l))
1165 ret.append(fmtline(l))
1156 ret.append('\n')
1166 ret.append('\n')
1157 return ''.join(ret)
1167 return ''.join(ret)
1158
1168
1159 def diff(repo, node1=None, node2=None, match=None,
1169 def diff(repo, node1=None, node2=None, match=None,
1160 fp=None, changes=None, opts=None):
1170 fp=None, changes=None, opts=None):
1161 '''print diff of changes to files between two nodes, or node and
1171 '''print diff of changes to files between two nodes, or node and
1162 working directory.
1172 working directory.
1163
1173
1164 if node1 is None, use first dirstate parent instead.
1174 if node1 is None, use first dirstate parent instead.
1165 if node2 is None, compare node1 with working directory.'''
1175 if node2 is None, compare node1 with working directory.'''
1166
1176
1167 if not match:
1177 if not match:
1168 match = cmdutil.matchall(repo)
1178 match = cmdutil.matchall(repo)
1169
1179
1170 if opts is None:
1180 if opts is None:
1171 opts = mdiff.defaultopts
1181 opts = mdiff.defaultopts
1172 if fp is None:
1182 if fp is None:
1173 fp = repo.ui
1183 fp = repo.ui
1174
1184
1175 if not node1:
1185 if not node1:
1176 node1 = repo.dirstate.parents()[0]
1186 node1 = repo.dirstate.parents()[0]
1177
1187
1178 flcache = {}
1188 flcache = {}
1179 def getfilectx(f, ctx):
1189 def getfilectx(f, ctx):
1180 flctx = ctx.filectx(f, filelog=flcache.get(f))
1190 flctx = ctx.filectx(f, filelog=flcache.get(f))
1181 if f not in flcache:
1191 if f not in flcache:
1182 flcache[f] = flctx._filelog
1192 flcache[f] = flctx._filelog
1183 return flctx
1193 return flctx
1184
1194
1185 ctx1 = repo[node1]
1195 ctx1 = repo[node1]
1186 ctx2 = repo[node2]
1196 ctx2 = repo[node2]
1187
1197
1188 if not changes:
1198 if not changes:
1189 changes = repo.status(ctx1, ctx2, match=match)
1199 changes = repo.status(ctx1, ctx2, match=match)
1190 modified, added, removed = changes[:3]
1200 modified, added, removed = changes[:3]
1191
1201
1192 if not modified and not added and not removed:
1202 if not modified and not added and not removed:
1193 return
1203 return
1194
1204
1195 date1 = util.datestr(ctx1.date())
1205 date1 = util.datestr(ctx1.date())
1196 man1 = ctx1.manifest()
1206 man1 = ctx1.manifest()
1197
1207
1198 if repo.ui.quiet:
1208 if repo.ui.quiet:
1199 r = None
1209 r = None
1200 else:
1210 else:
1201 hexfunc = repo.ui.debugflag and hex or short
1211 hexfunc = repo.ui.debugflag and hex or short
1202 r = [hexfunc(node) for node in [node1, node2] if node]
1212 r = [hexfunc(node) for node in [node1, node2] if node]
1203
1213
1204 if opts.git:
1214 if opts.git:
1205 copy, diverge = copies.copies(repo, ctx1, ctx2, repo[nullid])
1215 copy, diverge = copies.copies(repo, ctx1, ctx2, repo[nullid])
1206 for k, v in copy.items():
1216 for k, v in copy.items():
1207 copy[v] = k
1217 copy[v] = k
1208
1218
1209 gone = {}
1219 gone = {}
1210 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1220 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1211
1221
1212 for f in util.sort(modified + added + removed):
1222 for f in util.sort(modified + added + removed):
1213 to = None
1223 to = None
1214 tn = None
1224 tn = None
1215 dodiff = True
1225 dodiff = True
1216 header = []
1226 header = []
1217 if f in man1:
1227 if f in man1:
1218 to = getfilectx(f, ctx1).data()
1228 to = getfilectx(f, ctx1).data()
1219 if f not in removed:
1229 if f not in removed:
1220 tn = getfilectx(f, ctx2).data()
1230 tn = getfilectx(f, ctx2).data()
1221 a, b = f, f
1231 a, b = f, f
1222 if opts.git:
1232 if opts.git:
1223 def addmodehdr(header, omode, nmode):
1233 def addmodehdr(header, omode, nmode):
1224 if omode != nmode:
1234 if omode != nmode:
1225 header.append('old mode %s\n' % omode)
1235 header.append('old mode %s\n' % omode)
1226 header.append('new mode %s\n' % nmode)
1236 header.append('new mode %s\n' % nmode)
1227
1237
1228 if f in added:
1238 if f in added:
1229 mode = gitmode[ctx2.flags(f)]
1239 mode = gitmode[ctx2.flags(f)]
1230 if f in copy:
1240 if f in copy:
1231 a = copy[f]
1241 a = copy[f]
1232 omode = gitmode[man1.flags(a)]
1242 omode = gitmode[man1.flags(a)]
1233 addmodehdr(header, omode, mode)
1243 addmodehdr(header, omode, mode)
1234 if a in removed and a not in gone:
1244 if a in removed and a not in gone:
1235 op = 'rename'
1245 op = 'rename'
1236 gone[a] = 1
1246 gone[a] = 1
1237 else:
1247 else:
1238 op = 'copy'
1248 op = 'copy'
1239 header.append('%s from %s\n' % (op, a))
1249 header.append('%s from %s\n' % (op, a))
1240 header.append('%s to %s\n' % (op, f))
1250 header.append('%s to %s\n' % (op, f))
1241 to = getfilectx(a, ctx1).data()
1251 to = getfilectx(a, ctx1).data()
1242 else:
1252 else:
1243 header.append('new file mode %s\n' % mode)
1253 header.append('new file mode %s\n' % mode)
1244 if util.binary(tn):
1254 if util.binary(tn):
1245 dodiff = 'binary'
1255 dodiff = 'binary'
1246 elif f in removed:
1256 elif f in removed:
1247 # have we already reported a copy above?
1257 # have we already reported a copy above?
1248 if f in copy and copy[f] in added and copy[copy[f]] == f:
1258 if f in copy and copy[f] in added and copy[copy[f]] == f:
1249 dodiff = False
1259 dodiff = False
1250 else:
1260 else:
1251 header.append('deleted file mode %s\n' %
1261 header.append('deleted file mode %s\n' %
1252 gitmode[man1.flags(f)])
1262 gitmode[man1.flags(f)])
1253 else:
1263 else:
1254 omode = gitmode[man1.flags(f)]
1264 omode = gitmode[man1.flags(f)]
1255 nmode = gitmode[ctx2.flags(f)]
1265 nmode = gitmode[ctx2.flags(f)]
1256 addmodehdr(header, omode, nmode)
1266 addmodehdr(header, omode, nmode)
1257 if util.binary(to) or util.binary(tn):
1267 if util.binary(to) or util.binary(tn):
1258 dodiff = 'binary'
1268 dodiff = 'binary'
1259 r = None
1269 r = None
1260 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
1270 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
1261 if dodiff:
1271 if dodiff:
1262 if dodiff == 'binary':
1272 if dodiff == 'binary':
1263 text = b85diff(to, tn)
1273 text = b85diff(to, tn)
1264 else:
1274 else:
1265 text = mdiff.unidiff(to, date1,
1275 text = mdiff.unidiff(to, date1,
1266 # ctx2 date may be dynamic
1276 # ctx2 date may be dynamic
1267 tn, util.datestr(ctx2.date()),
1277 tn, util.datestr(ctx2.date()),
1268 a, b, r, opts=opts)
1278 a, b, r, opts=opts)
1269 if text or len(header) > 1:
1279 if text or len(header) > 1:
1270 fp.write(''.join(header))
1280 fp.write(''.join(header))
1271 fp.write(text)
1281 fp.write(text)
1272
1282
1273 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1283 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1274 opts=None):
1284 opts=None):
1275 '''export changesets as hg patches.'''
1285 '''export changesets as hg patches.'''
1276
1286
1277 total = len(revs)
1287 total = len(revs)
1278 revwidth = max([len(str(rev)) for rev in revs])
1288 revwidth = max([len(str(rev)) for rev in revs])
1279
1289
1280 def single(rev, seqno, fp):
1290 def single(rev, seqno, fp):
1281 ctx = repo[rev]
1291 ctx = repo[rev]
1282 node = ctx.node()
1292 node = ctx.node()
1283 parents = [p.node() for p in ctx.parents() if p]
1293 parents = [p.node() for p in ctx.parents() if p]
1284 branch = ctx.branch()
1294 branch = ctx.branch()
1285 if switch_parent:
1295 if switch_parent:
1286 parents.reverse()
1296 parents.reverse()
1287 prev = (parents and parents[0]) or nullid
1297 prev = (parents and parents[0]) or nullid
1288
1298
1289 if not fp:
1299 if not fp:
1290 fp = cmdutil.make_file(repo, template, node, total=total,
1300 fp = cmdutil.make_file(repo, template, node, total=total,
1291 seqno=seqno, revwidth=revwidth)
1301 seqno=seqno, revwidth=revwidth)
1292 if fp != sys.stdout and hasattr(fp, 'name'):
1302 if fp != sys.stdout and hasattr(fp, 'name'):
1293 repo.ui.note("%s\n" % fp.name)
1303 repo.ui.note("%s\n" % fp.name)
1294
1304
1295 fp.write("# HG changeset patch\n")
1305 fp.write("# HG changeset patch\n")
1296 fp.write("# User %s\n" % ctx.user())
1306 fp.write("# User %s\n" % ctx.user())
1297 fp.write("# Date %d %d\n" % ctx.date())
1307 fp.write("# Date %d %d\n" % ctx.date())
1298 if branch and (branch != 'default'):
1308 if branch and (branch != 'default'):
1299 fp.write("# Branch %s\n" % branch)
1309 fp.write("# Branch %s\n" % branch)
1300 fp.write("# Node ID %s\n" % hex(node))
1310 fp.write("# Node ID %s\n" % hex(node))
1301 fp.write("# Parent %s\n" % hex(prev))
1311 fp.write("# Parent %s\n" % hex(prev))
1302 if len(parents) > 1:
1312 if len(parents) > 1:
1303 fp.write("# Parent %s\n" % hex(parents[1]))
1313 fp.write("# Parent %s\n" % hex(parents[1]))
1304 fp.write(ctx.description().rstrip())
1314 fp.write(ctx.description().rstrip())
1305 fp.write("\n\n")
1315 fp.write("\n\n")
1306
1316
1307 diff(repo, prev, node, fp=fp, opts=opts)
1317 diff(repo, prev, node, fp=fp, opts=opts)
1308 if fp not in (sys.stdout, repo.ui):
1318 if fp not in (sys.stdout, repo.ui):
1309 fp.close()
1319 fp.close()
1310
1320
1311 for seqno, rev in enumerate(revs):
1321 for seqno, rev in enumerate(revs):
1312 single(rev, seqno+1, fp)
1322 single(rev, seqno+1, fp)
1313
1323
1314 def diffstat(patchlines):
1324 def diffstat(patchlines):
1315 if not util.find_exe('diffstat'):
1325 if not util.find_exe('diffstat'):
1316 return
1326 return
1317 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
1327 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
1318 try:
1328 try:
1319 p = util.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
1329 p = util.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
1320 try:
1330 try:
1321 for line in patchlines:
1331 for line in patchlines:
1322 p.tochild.write(line + "\n")
1332 p.tochild.write(line + "\n")
1323 p.tochild.close()
1333 p.tochild.close()
1324 if p.wait(): return
1334 if p.wait(): return
1325 fp = os.fdopen(fd, 'r')
1335 fp = os.fdopen(fd, 'r')
1326 stat = []
1336 stat = []
1327 for line in fp: stat.append(line.lstrip())
1337 for line in fp: stat.append(line.lstrip())
1328 last = stat.pop()
1338 last = stat.pop()
1329 stat.insert(0, last)
1339 stat.insert(0, last)
1330 stat = ''.join(stat)
1340 stat = ''.join(stat)
1331 return stat
1341 return stat
1332 except: raise
1342 except: raise
1333 finally:
1343 finally:
1334 try: os.unlink(name)
1344 try: os.unlink(name)
1335 except: pass
1345 except: pass
General Comments 0
You need to be logged in to leave comments. Login now