##// END OF EJS Templates
patch: fix fuzzing of hunks without previous lines (issue3264)...
Patrick Mezard -
r16123:b0c7525f stable
parent child Browse files
Show More
@@ -1,1877 +1,1878 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 of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import cStringIO, email.Parser, os, errno, re
9 import cStringIO, email.Parser, os, errno, re
10 import tempfile, zlib, shutil
10 import tempfile, zlib, shutil
11
11
12 from i18n import _
12 from i18n import _
13 from node import hex, nullid, short
13 from node import hex, nullid, short
14 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
14 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
15 import context
15 import context
16
16
17 gitre = re.compile('diff --git a/(.*) b/(.*)')
17 gitre = re.compile('diff --git a/(.*) b/(.*)')
18
18
19 class PatchError(Exception):
19 class PatchError(Exception):
20 pass
20 pass
21
21
22
22
23 # public functions
23 # public functions
24
24
25 def split(stream):
25 def split(stream):
26 '''return an iterator of individual patches from a stream'''
26 '''return an iterator of individual patches from a stream'''
27 def isheader(line, inheader):
27 def isheader(line, inheader):
28 if inheader and line[0] in (' ', '\t'):
28 if inheader and line[0] in (' ', '\t'):
29 # continuation
29 # continuation
30 return True
30 return True
31 if line[0] in (' ', '-', '+'):
31 if line[0] in (' ', '-', '+'):
32 # diff line - don't check for header pattern in there
32 # diff line - don't check for header pattern in there
33 return False
33 return False
34 l = line.split(': ', 1)
34 l = line.split(': ', 1)
35 return len(l) == 2 and ' ' not in l[0]
35 return len(l) == 2 and ' ' not in l[0]
36
36
37 def chunk(lines):
37 def chunk(lines):
38 return cStringIO.StringIO(''.join(lines))
38 return cStringIO.StringIO(''.join(lines))
39
39
40 def hgsplit(stream, cur):
40 def hgsplit(stream, cur):
41 inheader = True
41 inheader = True
42
42
43 for line in stream:
43 for line in stream:
44 if not line.strip():
44 if not line.strip():
45 inheader = False
45 inheader = False
46 if not inheader and line.startswith('# HG changeset patch'):
46 if not inheader and line.startswith('# HG changeset patch'):
47 yield chunk(cur)
47 yield chunk(cur)
48 cur = []
48 cur = []
49 inheader = True
49 inheader = True
50
50
51 cur.append(line)
51 cur.append(line)
52
52
53 if cur:
53 if cur:
54 yield chunk(cur)
54 yield chunk(cur)
55
55
56 def mboxsplit(stream, cur):
56 def mboxsplit(stream, cur):
57 for line in stream:
57 for line in stream:
58 if line.startswith('From '):
58 if line.startswith('From '):
59 for c in split(chunk(cur[1:])):
59 for c in split(chunk(cur[1:])):
60 yield c
60 yield c
61 cur = []
61 cur = []
62
62
63 cur.append(line)
63 cur.append(line)
64
64
65 if cur:
65 if cur:
66 for c in split(chunk(cur[1:])):
66 for c in split(chunk(cur[1:])):
67 yield c
67 yield c
68
68
69 def mimesplit(stream, cur):
69 def mimesplit(stream, cur):
70 def msgfp(m):
70 def msgfp(m):
71 fp = cStringIO.StringIO()
71 fp = cStringIO.StringIO()
72 g = email.Generator.Generator(fp, mangle_from_=False)
72 g = email.Generator.Generator(fp, mangle_from_=False)
73 g.flatten(m)
73 g.flatten(m)
74 fp.seek(0)
74 fp.seek(0)
75 return fp
75 return fp
76
76
77 for line in stream:
77 for line in stream:
78 cur.append(line)
78 cur.append(line)
79 c = chunk(cur)
79 c = chunk(cur)
80
80
81 m = email.Parser.Parser().parse(c)
81 m = email.Parser.Parser().parse(c)
82 if not m.is_multipart():
82 if not m.is_multipart():
83 yield msgfp(m)
83 yield msgfp(m)
84 else:
84 else:
85 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
85 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
86 for part in m.walk():
86 for part in m.walk():
87 ct = part.get_content_type()
87 ct = part.get_content_type()
88 if ct not in ok_types:
88 if ct not in ok_types:
89 continue
89 continue
90 yield msgfp(part)
90 yield msgfp(part)
91
91
92 def headersplit(stream, cur):
92 def headersplit(stream, cur):
93 inheader = False
93 inheader = False
94
94
95 for line in stream:
95 for line in stream:
96 if not inheader and isheader(line, inheader):
96 if not inheader and isheader(line, inheader):
97 yield chunk(cur)
97 yield chunk(cur)
98 cur = []
98 cur = []
99 inheader = True
99 inheader = True
100 if inheader and not isheader(line, inheader):
100 if inheader and not isheader(line, inheader):
101 inheader = False
101 inheader = False
102
102
103 cur.append(line)
103 cur.append(line)
104
104
105 if cur:
105 if cur:
106 yield chunk(cur)
106 yield chunk(cur)
107
107
108 def remainder(cur):
108 def remainder(cur):
109 yield chunk(cur)
109 yield chunk(cur)
110
110
111 class fiter(object):
111 class fiter(object):
112 def __init__(self, fp):
112 def __init__(self, fp):
113 self.fp = fp
113 self.fp = fp
114
114
115 def __iter__(self):
115 def __iter__(self):
116 return self
116 return self
117
117
118 def next(self):
118 def next(self):
119 l = self.fp.readline()
119 l = self.fp.readline()
120 if not l:
120 if not l:
121 raise StopIteration
121 raise StopIteration
122 return l
122 return l
123
123
124 inheader = False
124 inheader = False
125 cur = []
125 cur = []
126
126
127 mimeheaders = ['content-type']
127 mimeheaders = ['content-type']
128
128
129 if not util.safehasattr(stream, 'next'):
129 if not util.safehasattr(stream, 'next'):
130 # http responses, for example, have readline but not next
130 # http responses, for example, have readline but not next
131 stream = fiter(stream)
131 stream = fiter(stream)
132
132
133 for line in stream:
133 for line in stream:
134 cur.append(line)
134 cur.append(line)
135 if line.startswith('# HG changeset patch'):
135 if line.startswith('# HG changeset patch'):
136 return hgsplit(stream, cur)
136 return hgsplit(stream, cur)
137 elif line.startswith('From '):
137 elif line.startswith('From '):
138 return mboxsplit(stream, cur)
138 return mboxsplit(stream, cur)
139 elif isheader(line, inheader):
139 elif isheader(line, inheader):
140 inheader = True
140 inheader = True
141 if line.split(':', 1)[0].lower() in mimeheaders:
141 if line.split(':', 1)[0].lower() in mimeheaders:
142 # let email parser handle this
142 # let email parser handle this
143 return mimesplit(stream, cur)
143 return mimesplit(stream, cur)
144 elif line.startswith('--- ') and inheader:
144 elif line.startswith('--- ') and inheader:
145 # No evil headers seen by diff start, split by hand
145 # No evil headers seen by diff start, split by hand
146 return headersplit(stream, cur)
146 return headersplit(stream, cur)
147 # Not enough info, keep reading
147 # Not enough info, keep reading
148
148
149 # if we are here, we have a very plain patch
149 # if we are here, we have a very plain patch
150 return remainder(cur)
150 return remainder(cur)
151
151
152 def extract(ui, fileobj):
152 def extract(ui, fileobj):
153 '''extract patch from data read from fileobj.
153 '''extract patch from data read from fileobj.
154
154
155 patch can be a normal patch or contained in an email message.
155 patch can be a normal patch or contained in an email message.
156
156
157 return tuple (filename, message, user, date, branch, node, p1, p2).
157 return tuple (filename, message, user, date, branch, node, p1, p2).
158 Any item in the returned tuple can be None. If filename is None,
158 Any item in the returned tuple can be None. If filename is None,
159 fileobj did not contain a patch. Caller must unlink filename when done.'''
159 fileobj did not contain a patch. Caller must unlink filename when done.'''
160
160
161 # attempt to detect the start of a patch
161 # attempt to detect the start of a patch
162 # (this heuristic is borrowed from quilt)
162 # (this heuristic is borrowed from quilt)
163 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
163 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
164 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
164 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
165 r'---[ \t].*?^\+\+\+[ \t]|'
165 r'---[ \t].*?^\+\+\+[ \t]|'
166 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
166 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
167
167
168 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
168 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
169 tmpfp = os.fdopen(fd, 'w')
169 tmpfp = os.fdopen(fd, 'w')
170 try:
170 try:
171 msg = email.Parser.Parser().parse(fileobj)
171 msg = email.Parser.Parser().parse(fileobj)
172
172
173 subject = msg['Subject']
173 subject = msg['Subject']
174 user = msg['From']
174 user = msg['From']
175 if not subject and not user:
175 if not subject and not user:
176 # Not an email, restore parsed headers if any
176 # Not an email, restore parsed headers if any
177 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
177 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
178
178
179 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
179 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
180 # should try to parse msg['Date']
180 # should try to parse msg['Date']
181 date = None
181 date = None
182 nodeid = None
182 nodeid = None
183 branch = None
183 branch = None
184 parents = []
184 parents = []
185
185
186 if subject:
186 if subject:
187 if subject.startswith('[PATCH'):
187 if subject.startswith('[PATCH'):
188 pend = subject.find(']')
188 pend = subject.find(']')
189 if pend >= 0:
189 if pend >= 0:
190 subject = subject[pend + 1:].lstrip()
190 subject = subject[pend + 1:].lstrip()
191 subject = re.sub(r'\n[ \t]+', ' ', subject)
191 subject = re.sub(r'\n[ \t]+', ' ', subject)
192 ui.debug('Subject: %s\n' % subject)
192 ui.debug('Subject: %s\n' % subject)
193 if user:
193 if user:
194 ui.debug('From: %s\n' % user)
194 ui.debug('From: %s\n' % user)
195 diffs_seen = 0
195 diffs_seen = 0
196 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
196 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
197 message = ''
197 message = ''
198 for part in msg.walk():
198 for part in msg.walk():
199 content_type = part.get_content_type()
199 content_type = part.get_content_type()
200 ui.debug('Content-Type: %s\n' % content_type)
200 ui.debug('Content-Type: %s\n' % content_type)
201 if content_type not in ok_types:
201 if content_type not in ok_types:
202 continue
202 continue
203 payload = part.get_payload(decode=True)
203 payload = part.get_payload(decode=True)
204 m = diffre.search(payload)
204 m = diffre.search(payload)
205 if m:
205 if m:
206 hgpatch = False
206 hgpatch = False
207 hgpatchheader = False
207 hgpatchheader = False
208 ignoretext = False
208 ignoretext = False
209
209
210 ui.debug('found patch at byte %d\n' % m.start(0))
210 ui.debug('found patch at byte %d\n' % m.start(0))
211 diffs_seen += 1
211 diffs_seen += 1
212 cfp = cStringIO.StringIO()
212 cfp = cStringIO.StringIO()
213 for line in payload[:m.start(0)].splitlines():
213 for line in payload[:m.start(0)].splitlines():
214 if line.startswith('# HG changeset patch') and not hgpatch:
214 if line.startswith('# HG changeset patch') and not hgpatch:
215 ui.debug('patch generated by hg export\n')
215 ui.debug('patch generated by hg export\n')
216 hgpatch = True
216 hgpatch = True
217 hgpatchheader = True
217 hgpatchheader = True
218 # drop earlier commit message content
218 # drop earlier commit message content
219 cfp.seek(0)
219 cfp.seek(0)
220 cfp.truncate()
220 cfp.truncate()
221 subject = None
221 subject = None
222 elif hgpatchheader:
222 elif hgpatchheader:
223 if line.startswith('# User '):
223 if line.startswith('# User '):
224 user = line[7:]
224 user = line[7:]
225 ui.debug('From: %s\n' % user)
225 ui.debug('From: %s\n' % user)
226 elif line.startswith("# Date "):
226 elif line.startswith("# Date "):
227 date = line[7:]
227 date = line[7:]
228 elif line.startswith("# Branch "):
228 elif line.startswith("# Branch "):
229 branch = line[9:]
229 branch = line[9:]
230 elif line.startswith("# Node ID "):
230 elif line.startswith("# Node ID "):
231 nodeid = line[10:]
231 nodeid = line[10:]
232 elif line.startswith("# Parent "):
232 elif line.startswith("# Parent "):
233 parents.append(line[10:])
233 parents.append(line[10:])
234 elif not line.startswith("# "):
234 elif not line.startswith("# "):
235 hgpatchheader = False
235 hgpatchheader = False
236 elif line == '---' and gitsendmail:
236 elif line == '---' and gitsendmail:
237 ignoretext = True
237 ignoretext = True
238 if not hgpatchheader and not ignoretext:
238 if not hgpatchheader and not ignoretext:
239 cfp.write(line)
239 cfp.write(line)
240 cfp.write('\n')
240 cfp.write('\n')
241 message = cfp.getvalue()
241 message = cfp.getvalue()
242 if tmpfp:
242 if tmpfp:
243 tmpfp.write(payload)
243 tmpfp.write(payload)
244 if not payload.endswith('\n'):
244 if not payload.endswith('\n'):
245 tmpfp.write('\n')
245 tmpfp.write('\n')
246 elif not diffs_seen and message and content_type == 'text/plain':
246 elif not diffs_seen and message and content_type == 'text/plain':
247 message += '\n' + payload
247 message += '\n' + payload
248 except:
248 except:
249 tmpfp.close()
249 tmpfp.close()
250 os.unlink(tmpname)
250 os.unlink(tmpname)
251 raise
251 raise
252
252
253 if subject and not message.startswith(subject):
253 if subject and not message.startswith(subject):
254 message = '%s\n%s' % (subject, message)
254 message = '%s\n%s' % (subject, message)
255 tmpfp.close()
255 tmpfp.close()
256 if not diffs_seen:
256 if not diffs_seen:
257 os.unlink(tmpname)
257 os.unlink(tmpname)
258 return None, message, user, date, branch, None, None, None
258 return None, message, user, date, branch, None, None, None
259 p1 = parents and parents.pop(0) or None
259 p1 = parents and parents.pop(0) or None
260 p2 = parents and parents.pop(0) or None
260 p2 = parents and parents.pop(0) or None
261 return tmpname, message, user, date, branch, nodeid, p1, p2
261 return tmpname, message, user, date, branch, nodeid, p1, p2
262
262
263 class patchmeta(object):
263 class patchmeta(object):
264 """Patched file metadata
264 """Patched file metadata
265
265
266 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
266 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
267 or COPY. 'path' is patched file path. 'oldpath' is set to the
267 or COPY. 'path' is patched file path. 'oldpath' is set to the
268 origin file when 'op' is either COPY or RENAME, None otherwise. If
268 origin file when 'op' is either COPY or RENAME, None otherwise. If
269 file mode is changed, 'mode' is a tuple (islink, isexec) where
269 file mode is changed, 'mode' is a tuple (islink, isexec) where
270 'islink' is True if the file is a symlink and 'isexec' is True if
270 'islink' is True if the file is a symlink and 'isexec' is True if
271 the file is executable. Otherwise, 'mode' is None.
271 the file is executable. Otherwise, 'mode' is None.
272 """
272 """
273 def __init__(self, path):
273 def __init__(self, path):
274 self.path = path
274 self.path = path
275 self.oldpath = None
275 self.oldpath = None
276 self.mode = None
276 self.mode = None
277 self.op = 'MODIFY'
277 self.op = 'MODIFY'
278 self.binary = False
278 self.binary = False
279
279
280 def setmode(self, mode):
280 def setmode(self, mode):
281 islink = mode & 020000
281 islink = mode & 020000
282 isexec = mode & 0100
282 isexec = mode & 0100
283 self.mode = (islink, isexec)
283 self.mode = (islink, isexec)
284
284
285 def copy(self):
285 def copy(self):
286 other = patchmeta(self.path)
286 other = patchmeta(self.path)
287 other.oldpath = self.oldpath
287 other.oldpath = self.oldpath
288 other.mode = self.mode
288 other.mode = self.mode
289 other.op = self.op
289 other.op = self.op
290 other.binary = self.binary
290 other.binary = self.binary
291 return other
291 return other
292
292
293 def __repr__(self):
293 def __repr__(self):
294 return "<patchmeta %s %r>" % (self.op, self.path)
294 return "<patchmeta %s %r>" % (self.op, self.path)
295
295
296 def readgitpatch(lr):
296 def readgitpatch(lr):
297 """extract git-style metadata about patches from <patchname>"""
297 """extract git-style metadata about patches from <patchname>"""
298
298
299 # Filter patch for git information
299 # Filter patch for git information
300 gp = None
300 gp = None
301 gitpatches = []
301 gitpatches = []
302 for line in lr:
302 for line in lr:
303 line = line.rstrip(' \r\n')
303 line = line.rstrip(' \r\n')
304 if line.startswith('diff --git'):
304 if line.startswith('diff --git'):
305 m = gitre.match(line)
305 m = gitre.match(line)
306 if m:
306 if m:
307 if gp:
307 if gp:
308 gitpatches.append(gp)
308 gitpatches.append(gp)
309 dst = m.group(2)
309 dst = m.group(2)
310 gp = patchmeta(dst)
310 gp = patchmeta(dst)
311 elif gp:
311 elif gp:
312 if line.startswith('--- '):
312 if line.startswith('--- '):
313 gitpatches.append(gp)
313 gitpatches.append(gp)
314 gp = None
314 gp = None
315 continue
315 continue
316 if line.startswith('rename from '):
316 if line.startswith('rename from '):
317 gp.op = 'RENAME'
317 gp.op = 'RENAME'
318 gp.oldpath = line[12:]
318 gp.oldpath = line[12:]
319 elif line.startswith('rename to '):
319 elif line.startswith('rename to '):
320 gp.path = line[10:]
320 gp.path = line[10:]
321 elif line.startswith('copy from '):
321 elif line.startswith('copy from '):
322 gp.op = 'COPY'
322 gp.op = 'COPY'
323 gp.oldpath = line[10:]
323 gp.oldpath = line[10:]
324 elif line.startswith('copy to '):
324 elif line.startswith('copy to '):
325 gp.path = line[8:]
325 gp.path = line[8:]
326 elif line.startswith('deleted file'):
326 elif line.startswith('deleted file'):
327 gp.op = 'DELETE'
327 gp.op = 'DELETE'
328 elif line.startswith('new file mode '):
328 elif line.startswith('new file mode '):
329 gp.op = 'ADD'
329 gp.op = 'ADD'
330 gp.setmode(int(line[-6:], 8))
330 gp.setmode(int(line[-6:], 8))
331 elif line.startswith('new mode '):
331 elif line.startswith('new mode '):
332 gp.setmode(int(line[-6:], 8))
332 gp.setmode(int(line[-6:], 8))
333 elif line.startswith('GIT binary patch'):
333 elif line.startswith('GIT binary patch'):
334 gp.binary = True
334 gp.binary = True
335 if gp:
335 if gp:
336 gitpatches.append(gp)
336 gitpatches.append(gp)
337
337
338 return gitpatches
338 return gitpatches
339
339
340 class linereader(object):
340 class linereader(object):
341 # simple class to allow pushing lines back into the input stream
341 # simple class to allow pushing lines back into the input stream
342 def __init__(self, fp):
342 def __init__(self, fp):
343 self.fp = fp
343 self.fp = fp
344 self.buf = []
344 self.buf = []
345
345
346 def push(self, line):
346 def push(self, line):
347 if line is not None:
347 if line is not None:
348 self.buf.append(line)
348 self.buf.append(line)
349
349
350 def readline(self):
350 def readline(self):
351 if self.buf:
351 if self.buf:
352 l = self.buf[0]
352 l = self.buf[0]
353 del self.buf[0]
353 del self.buf[0]
354 return l
354 return l
355 return self.fp.readline()
355 return self.fp.readline()
356
356
357 def __iter__(self):
357 def __iter__(self):
358 while True:
358 while True:
359 l = self.readline()
359 l = self.readline()
360 if not l:
360 if not l:
361 break
361 break
362 yield l
362 yield l
363
363
364 class abstractbackend(object):
364 class abstractbackend(object):
365 def __init__(self, ui):
365 def __init__(self, ui):
366 self.ui = ui
366 self.ui = ui
367
367
368 def getfile(self, fname):
368 def getfile(self, fname):
369 """Return target file data and flags as a (data, (islink,
369 """Return target file data and flags as a (data, (islink,
370 isexec)) tuple.
370 isexec)) tuple.
371 """
371 """
372 raise NotImplementedError
372 raise NotImplementedError
373
373
374 def setfile(self, fname, data, mode, copysource):
374 def setfile(self, fname, data, mode, copysource):
375 """Write data to target file fname and set its mode. mode is a
375 """Write data to target file fname and set its mode. mode is a
376 (islink, isexec) tuple. If data is None, the file content should
376 (islink, isexec) tuple. If data is None, the file content should
377 be left unchanged. If the file is modified after being copied,
377 be left unchanged. If the file is modified after being copied,
378 copysource is set to the original file name.
378 copysource is set to the original file name.
379 """
379 """
380 raise NotImplementedError
380 raise NotImplementedError
381
381
382 def unlink(self, fname):
382 def unlink(self, fname):
383 """Unlink target file."""
383 """Unlink target file."""
384 raise NotImplementedError
384 raise NotImplementedError
385
385
386 def writerej(self, fname, failed, total, lines):
386 def writerej(self, fname, failed, total, lines):
387 """Write rejected lines for fname. total is the number of hunks
387 """Write rejected lines for fname. total is the number of hunks
388 which failed to apply and total the total number of hunks for this
388 which failed to apply and total the total number of hunks for this
389 files.
389 files.
390 """
390 """
391 pass
391 pass
392
392
393 def exists(self, fname):
393 def exists(self, fname):
394 raise NotImplementedError
394 raise NotImplementedError
395
395
396 class fsbackend(abstractbackend):
396 class fsbackend(abstractbackend):
397 def __init__(self, ui, basedir):
397 def __init__(self, ui, basedir):
398 super(fsbackend, self).__init__(ui)
398 super(fsbackend, self).__init__(ui)
399 self.opener = scmutil.opener(basedir)
399 self.opener = scmutil.opener(basedir)
400
400
401 def _join(self, f):
401 def _join(self, f):
402 return os.path.join(self.opener.base, f)
402 return os.path.join(self.opener.base, f)
403
403
404 def getfile(self, fname):
404 def getfile(self, fname):
405 path = self._join(fname)
405 path = self._join(fname)
406 if os.path.islink(path):
406 if os.path.islink(path):
407 return (os.readlink(path), (True, False))
407 return (os.readlink(path), (True, False))
408 isexec = False
408 isexec = False
409 try:
409 try:
410 isexec = os.lstat(path).st_mode & 0100 != 0
410 isexec = os.lstat(path).st_mode & 0100 != 0
411 except OSError, e:
411 except OSError, e:
412 if e.errno != errno.ENOENT:
412 if e.errno != errno.ENOENT:
413 raise
413 raise
414 return (self.opener.read(fname), (False, isexec))
414 return (self.opener.read(fname), (False, isexec))
415
415
416 def setfile(self, fname, data, mode, copysource):
416 def setfile(self, fname, data, mode, copysource):
417 islink, isexec = mode
417 islink, isexec = mode
418 if data is None:
418 if data is None:
419 util.setflags(self._join(fname), islink, isexec)
419 util.setflags(self._join(fname), islink, isexec)
420 return
420 return
421 if islink:
421 if islink:
422 self.opener.symlink(data, fname)
422 self.opener.symlink(data, fname)
423 else:
423 else:
424 self.opener.write(fname, data)
424 self.opener.write(fname, data)
425 if isexec:
425 if isexec:
426 util.setflags(self._join(fname), False, True)
426 util.setflags(self._join(fname), False, True)
427
427
428 def unlink(self, fname):
428 def unlink(self, fname):
429 try:
429 try:
430 util.unlinkpath(self._join(fname))
430 util.unlinkpath(self._join(fname))
431 except OSError, inst:
431 except OSError, inst:
432 if inst.errno != errno.ENOENT:
432 if inst.errno != errno.ENOENT:
433 raise
433 raise
434
434
435 def writerej(self, fname, failed, total, lines):
435 def writerej(self, fname, failed, total, lines):
436 fname = fname + ".rej"
436 fname = fname + ".rej"
437 self.ui.warn(
437 self.ui.warn(
438 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
438 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
439 (failed, total, fname))
439 (failed, total, fname))
440 fp = self.opener(fname, 'w')
440 fp = self.opener(fname, 'w')
441 fp.writelines(lines)
441 fp.writelines(lines)
442 fp.close()
442 fp.close()
443
443
444 def exists(self, fname):
444 def exists(self, fname):
445 return os.path.lexists(self._join(fname))
445 return os.path.lexists(self._join(fname))
446
446
447 class workingbackend(fsbackend):
447 class workingbackend(fsbackend):
448 def __init__(self, ui, repo, similarity):
448 def __init__(self, ui, repo, similarity):
449 super(workingbackend, self).__init__(ui, repo.root)
449 super(workingbackend, self).__init__(ui, repo.root)
450 self.repo = repo
450 self.repo = repo
451 self.similarity = similarity
451 self.similarity = similarity
452 self.removed = set()
452 self.removed = set()
453 self.changed = set()
453 self.changed = set()
454 self.copied = []
454 self.copied = []
455
455
456 def _checkknown(self, fname):
456 def _checkknown(self, fname):
457 if self.repo.dirstate[fname] == '?' and self.exists(fname):
457 if self.repo.dirstate[fname] == '?' and self.exists(fname):
458 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
458 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
459
459
460 def setfile(self, fname, data, mode, copysource):
460 def setfile(self, fname, data, mode, copysource):
461 self._checkknown(fname)
461 self._checkknown(fname)
462 super(workingbackend, self).setfile(fname, data, mode, copysource)
462 super(workingbackend, self).setfile(fname, data, mode, copysource)
463 if copysource is not None:
463 if copysource is not None:
464 self.copied.append((copysource, fname))
464 self.copied.append((copysource, fname))
465 self.changed.add(fname)
465 self.changed.add(fname)
466
466
467 def unlink(self, fname):
467 def unlink(self, fname):
468 self._checkknown(fname)
468 self._checkknown(fname)
469 super(workingbackend, self).unlink(fname)
469 super(workingbackend, self).unlink(fname)
470 self.removed.add(fname)
470 self.removed.add(fname)
471 self.changed.add(fname)
471 self.changed.add(fname)
472
472
473 def close(self):
473 def close(self):
474 wctx = self.repo[None]
474 wctx = self.repo[None]
475 addremoved = set(self.changed)
475 addremoved = set(self.changed)
476 for src, dst in self.copied:
476 for src, dst in self.copied:
477 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
477 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
478 if self.removed:
478 if self.removed:
479 wctx.forget(sorted(self.removed))
479 wctx.forget(sorted(self.removed))
480 for f in self.removed:
480 for f in self.removed:
481 if f not in self.repo.dirstate:
481 if f not in self.repo.dirstate:
482 # File was deleted and no longer belongs to the
482 # File was deleted and no longer belongs to the
483 # dirstate, it was probably marked added then
483 # dirstate, it was probably marked added then
484 # deleted, and should not be considered by
484 # deleted, and should not be considered by
485 # addremove().
485 # addremove().
486 addremoved.discard(f)
486 addremoved.discard(f)
487 if addremoved:
487 if addremoved:
488 cwd = self.repo.getcwd()
488 cwd = self.repo.getcwd()
489 if cwd:
489 if cwd:
490 addremoved = [util.pathto(self.repo.root, cwd, f)
490 addremoved = [util.pathto(self.repo.root, cwd, f)
491 for f in addremoved]
491 for f in addremoved]
492 scmutil.addremove(self.repo, addremoved, similarity=self.similarity)
492 scmutil.addremove(self.repo, addremoved, similarity=self.similarity)
493 return sorted(self.changed)
493 return sorted(self.changed)
494
494
495 class filestore(object):
495 class filestore(object):
496 def __init__(self, maxsize=None):
496 def __init__(self, maxsize=None):
497 self.opener = None
497 self.opener = None
498 self.files = {}
498 self.files = {}
499 self.created = 0
499 self.created = 0
500 self.maxsize = maxsize
500 self.maxsize = maxsize
501 if self.maxsize is None:
501 if self.maxsize is None:
502 self.maxsize = 4*(2**20)
502 self.maxsize = 4*(2**20)
503 self.size = 0
503 self.size = 0
504 self.data = {}
504 self.data = {}
505
505
506 def setfile(self, fname, data, mode, copied=None):
506 def setfile(self, fname, data, mode, copied=None):
507 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
507 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
508 self.data[fname] = (data, mode, copied)
508 self.data[fname] = (data, mode, copied)
509 self.size += len(data)
509 self.size += len(data)
510 else:
510 else:
511 if self.opener is None:
511 if self.opener is None:
512 root = tempfile.mkdtemp(prefix='hg-patch-')
512 root = tempfile.mkdtemp(prefix='hg-patch-')
513 self.opener = scmutil.opener(root)
513 self.opener = scmutil.opener(root)
514 # Avoid filename issues with these simple names
514 # Avoid filename issues with these simple names
515 fn = str(self.created)
515 fn = str(self.created)
516 self.opener.write(fn, data)
516 self.opener.write(fn, data)
517 self.created += 1
517 self.created += 1
518 self.files[fname] = (fn, mode, copied)
518 self.files[fname] = (fn, mode, copied)
519
519
520 def getfile(self, fname):
520 def getfile(self, fname):
521 if fname in self.data:
521 if fname in self.data:
522 return self.data[fname]
522 return self.data[fname]
523 if not self.opener or fname not in self.files:
523 if not self.opener or fname not in self.files:
524 raise IOError()
524 raise IOError()
525 fn, mode, copied = self.files[fname]
525 fn, mode, copied = self.files[fname]
526 return self.opener.read(fn), mode, copied
526 return self.opener.read(fn), mode, copied
527
527
528 def close(self):
528 def close(self):
529 if self.opener:
529 if self.opener:
530 shutil.rmtree(self.opener.base)
530 shutil.rmtree(self.opener.base)
531
531
532 class repobackend(abstractbackend):
532 class repobackend(abstractbackend):
533 def __init__(self, ui, repo, ctx, store):
533 def __init__(self, ui, repo, ctx, store):
534 super(repobackend, self).__init__(ui)
534 super(repobackend, self).__init__(ui)
535 self.repo = repo
535 self.repo = repo
536 self.ctx = ctx
536 self.ctx = ctx
537 self.store = store
537 self.store = store
538 self.changed = set()
538 self.changed = set()
539 self.removed = set()
539 self.removed = set()
540 self.copied = {}
540 self.copied = {}
541
541
542 def _checkknown(self, fname):
542 def _checkknown(self, fname):
543 if fname not in self.ctx:
543 if fname not in self.ctx:
544 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
544 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
545
545
546 def getfile(self, fname):
546 def getfile(self, fname):
547 try:
547 try:
548 fctx = self.ctx[fname]
548 fctx = self.ctx[fname]
549 except error.LookupError:
549 except error.LookupError:
550 raise IOError()
550 raise IOError()
551 flags = fctx.flags()
551 flags = fctx.flags()
552 return fctx.data(), ('l' in flags, 'x' in flags)
552 return fctx.data(), ('l' in flags, 'x' in flags)
553
553
554 def setfile(self, fname, data, mode, copysource):
554 def setfile(self, fname, data, mode, copysource):
555 if copysource:
555 if copysource:
556 self._checkknown(copysource)
556 self._checkknown(copysource)
557 if data is None:
557 if data is None:
558 data = self.ctx[fname].data()
558 data = self.ctx[fname].data()
559 self.store.setfile(fname, data, mode, copysource)
559 self.store.setfile(fname, data, mode, copysource)
560 self.changed.add(fname)
560 self.changed.add(fname)
561 if copysource:
561 if copysource:
562 self.copied[fname] = copysource
562 self.copied[fname] = copysource
563
563
564 def unlink(self, fname):
564 def unlink(self, fname):
565 self._checkknown(fname)
565 self._checkknown(fname)
566 self.removed.add(fname)
566 self.removed.add(fname)
567
567
568 def exists(self, fname):
568 def exists(self, fname):
569 return fname in self.ctx
569 return fname in self.ctx
570
570
571 def close(self):
571 def close(self):
572 return self.changed | self.removed
572 return self.changed | self.removed
573
573
574 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
574 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
575 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
575 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
576 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
576 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
577 eolmodes = ['strict', 'crlf', 'lf', 'auto']
577 eolmodes = ['strict', 'crlf', 'lf', 'auto']
578
578
579 class patchfile(object):
579 class patchfile(object):
580 def __init__(self, ui, gp, backend, store, eolmode='strict'):
580 def __init__(self, ui, gp, backend, store, eolmode='strict'):
581 self.fname = gp.path
581 self.fname = gp.path
582 self.eolmode = eolmode
582 self.eolmode = eolmode
583 self.eol = None
583 self.eol = None
584 self.backend = backend
584 self.backend = backend
585 self.ui = ui
585 self.ui = ui
586 self.lines = []
586 self.lines = []
587 self.exists = False
587 self.exists = False
588 self.missing = True
588 self.missing = True
589 self.mode = gp.mode
589 self.mode = gp.mode
590 self.copysource = gp.oldpath
590 self.copysource = gp.oldpath
591 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
591 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
592 self.remove = gp.op == 'DELETE'
592 self.remove = gp.op == 'DELETE'
593 try:
593 try:
594 if self.copysource is None:
594 if self.copysource is None:
595 data, mode = backend.getfile(self.fname)
595 data, mode = backend.getfile(self.fname)
596 self.exists = True
596 self.exists = True
597 else:
597 else:
598 data, mode = store.getfile(self.copysource)[:2]
598 data, mode = store.getfile(self.copysource)[:2]
599 self.exists = backend.exists(self.fname)
599 self.exists = backend.exists(self.fname)
600 self.missing = False
600 self.missing = False
601 if data:
601 if data:
602 self.lines = mdiff.splitnewlines(data)
602 self.lines = mdiff.splitnewlines(data)
603 if self.mode is None:
603 if self.mode is None:
604 self.mode = mode
604 self.mode = mode
605 if self.lines:
605 if self.lines:
606 # Normalize line endings
606 # Normalize line endings
607 if self.lines[0].endswith('\r\n'):
607 if self.lines[0].endswith('\r\n'):
608 self.eol = '\r\n'
608 self.eol = '\r\n'
609 elif self.lines[0].endswith('\n'):
609 elif self.lines[0].endswith('\n'):
610 self.eol = '\n'
610 self.eol = '\n'
611 if eolmode != 'strict':
611 if eolmode != 'strict':
612 nlines = []
612 nlines = []
613 for l in self.lines:
613 for l in self.lines:
614 if l.endswith('\r\n'):
614 if l.endswith('\r\n'):
615 l = l[:-2] + '\n'
615 l = l[:-2] + '\n'
616 nlines.append(l)
616 nlines.append(l)
617 self.lines = nlines
617 self.lines = nlines
618 except IOError:
618 except IOError:
619 if self.create:
619 if self.create:
620 self.missing = False
620 self.missing = False
621 if self.mode is None:
621 if self.mode is None:
622 self.mode = (False, False)
622 self.mode = (False, False)
623 if self.missing:
623 if self.missing:
624 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
624 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
625
625
626 self.hash = {}
626 self.hash = {}
627 self.dirty = 0
627 self.dirty = 0
628 self.offset = 0
628 self.offset = 0
629 self.skew = 0
629 self.skew = 0
630 self.rej = []
630 self.rej = []
631 self.fileprinted = False
631 self.fileprinted = False
632 self.printfile(False)
632 self.printfile(False)
633 self.hunks = 0
633 self.hunks = 0
634
634
635 def writelines(self, fname, lines, mode):
635 def writelines(self, fname, lines, mode):
636 if self.eolmode == 'auto':
636 if self.eolmode == 'auto':
637 eol = self.eol
637 eol = self.eol
638 elif self.eolmode == 'crlf':
638 elif self.eolmode == 'crlf':
639 eol = '\r\n'
639 eol = '\r\n'
640 else:
640 else:
641 eol = '\n'
641 eol = '\n'
642
642
643 if self.eolmode != 'strict' and eol and eol != '\n':
643 if self.eolmode != 'strict' and eol and eol != '\n':
644 rawlines = []
644 rawlines = []
645 for l in lines:
645 for l in lines:
646 if l and l[-1] == '\n':
646 if l and l[-1] == '\n':
647 l = l[:-1] + eol
647 l = l[:-1] + eol
648 rawlines.append(l)
648 rawlines.append(l)
649 lines = rawlines
649 lines = rawlines
650
650
651 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
651 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
652
652
653 def printfile(self, warn):
653 def printfile(self, warn):
654 if self.fileprinted:
654 if self.fileprinted:
655 return
655 return
656 if warn or self.ui.verbose:
656 if warn or self.ui.verbose:
657 self.fileprinted = True
657 self.fileprinted = True
658 s = _("patching file %s\n") % self.fname
658 s = _("patching file %s\n") % self.fname
659 if warn:
659 if warn:
660 self.ui.warn(s)
660 self.ui.warn(s)
661 else:
661 else:
662 self.ui.note(s)
662 self.ui.note(s)
663
663
664
664
665 def findlines(self, l, linenum):
665 def findlines(self, l, linenum):
666 # looks through the hash and finds candidate lines. The
666 # looks through the hash and finds candidate lines. The
667 # result is a list of line numbers sorted based on distance
667 # result is a list of line numbers sorted based on distance
668 # from linenum
668 # from linenum
669
669
670 cand = self.hash.get(l, [])
670 cand = self.hash.get(l, [])
671 if len(cand) > 1:
671 if len(cand) > 1:
672 # resort our list of potentials forward then back.
672 # resort our list of potentials forward then back.
673 cand.sort(key=lambda x: abs(x - linenum))
673 cand.sort(key=lambda x: abs(x - linenum))
674 return cand
674 return cand
675
675
676 def write_rej(self):
676 def write_rej(self):
677 # our rejects are a little different from patch(1). This always
677 # our rejects are a little different from patch(1). This always
678 # creates rejects in the same form as the original patch. A file
678 # creates rejects in the same form as the original patch. A file
679 # header is inserted so that you can run the reject through patch again
679 # header is inserted so that you can run the reject through patch again
680 # without having to type the filename.
680 # without having to type the filename.
681 if not self.rej:
681 if not self.rej:
682 return
682 return
683 base = os.path.basename(self.fname)
683 base = os.path.basename(self.fname)
684 lines = ["--- %s\n+++ %s\n" % (base, base)]
684 lines = ["--- %s\n+++ %s\n" % (base, base)]
685 for x in self.rej:
685 for x in self.rej:
686 for l in x.hunk:
686 for l in x.hunk:
687 lines.append(l)
687 lines.append(l)
688 if l[-1] != '\n':
688 if l[-1] != '\n':
689 lines.append("\n\ No newline at end of file\n")
689 lines.append("\n\ No newline at end of file\n")
690 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
690 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
691
691
692 def apply(self, h):
692 def apply(self, h):
693 if not h.complete():
693 if not h.complete():
694 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
694 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
695 (h.number, h.desc, len(h.a), h.lena, len(h.b),
695 (h.number, h.desc, len(h.a), h.lena, len(h.b),
696 h.lenb))
696 h.lenb))
697
697
698 self.hunks += 1
698 self.hunks += 1
699
699
700 if self.missing:
700 if self.missing:
701 self.rej.append(h)
701 self.rej.append(h)
702 return -1
702 return -1
703
703
704 if self.exists and self.create:
704 if self.exists and self.create:
705 if self.copysource:
705 if self.copysource:
706 self.ui.warn(_("cannot create %s: destination already "
706 self.ui.warn(_("cannot create %s: destination already "
707 "exists\n" % self.fname))
707 "exists\n" % self.fname))
708 else:
708 else:
709 self.ui.warn(_("file %s already exists\n") % self.fname)
709 self.ui.warn(_("file %s already exists\n") % self.fname)
710 self.rej.append(h)
710 self.rej.append(h)
711 return -1
711 return -1
712
712
713 if isinstance(h, binhunk):
713 if isinstance(h, binhunk):
714 if self.remove:
714 if self.remove:
715 self.backend.unlink(self.fname)
715 self.backend.unlink(self.fname)
716 else:
716 else:
717 self.lines[:] = h.new()
717 self.lines[:] = h.new()
718 self.offset += len(h.new())
718 self.offset += len(h.new())
719 self.dirty = True
719 self.dirty = True
720 return 0
720 return 0
721
721
722 horig = h
722 horig = h
723 if (self.eolmode in ('crlf', 'lf')
723 if (self.eolmode in ('crlf', 'lf')
724 or self.eolmode == 'auto' and self.eol):
724 or self.eolmode == 'auto' and self.eol):
725 # If new eols are going to be normalized, then normalize
725 # If new eols are going to be normalized, then normalize
726 # hunk data before patching. Otherwise, preserve input
726 # hunk data before patching. Otherwise, preserve input
727 # line-endings.
727 # line-endings.
728 h = h.getnormalized()
728 h = h.getnormalized()
729
729
730 # fast case first, no offsets, no fuzz
730 # fast case first, no offsets, no fuzz
731 old, oldstart, new, newstart = h.fuzzit(0, False)
731 old, oldstart, new, newstart = h.fuzzit(0, False)
732 oldstart += self.offset
732 oldstart += self.offset
733 orig_start = oldstart
733 orig_start = oldstart
734 # if there's skew we want to emit the "(offset %d lines)" even
734 # if there's skew we want to emit the "(offset %d lines)" even
735 # when the hunk cleanly applies at start + skew, so skip the
735 # when the hunk cleanly applies at start + skew, so skip the
736 # fast case code
736 # fast case code
737 if (self.skew == 0 and
737 if (self.skew == 0 and
738 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
738 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
739 if self.remove:
739 if self.remove:
740 self.backend.unlink(self.fname)
740 self.backend.unlink(self.fname)
741 else:
741 else:
742 self.lines[oldstart:oldstart + len(old)] = new
742 self.lines[oldstart:oldstart + len(old)] = new
743 self.offset += len(new) - len(old)
743 self.offset += len(new) - len(old)
744 self.dirty = True
744 self.dirty = True
745 return 0
745 return 0
746
746
747 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
747 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
748 self.hash = {}
748 self.hash = {}
749 for x, s in enumerate(self.lines):
749 for x, s in enumerate(self.lines):
750 self.hash.setdefault(s, []).append(x)
750 self.hash.setdefault(s, []).append(x)
751 if h.hunk[-1][0] != ' ':
752 # if the hunk tried to put something at the bottom of the file
753 # override the start line and use eof here
754 search_start = len(self.lines)
755 else:
756 search_start = orig_start + self.skew
757
751
758 for fuzzlen in xrange(3):
752 for fuzzlen in xrange(3):
759 for toponly in [True, False]:
753 for toponly in [True, False]:
760 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
754 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
755 oldstart = oldstart + self.offset + self.skew
756 oldstart = min(oldstart, len(self.lines))
757 if old:
758 cand = self.findlines(old[0][1:], oldstart)
759 else:
760 # Only adding lines with no or fuzzed context, just
761 # take the skew in account
762 cand = [oldstart]
761
763
762 cand = self.findlines(old[0][1:], search_start)
763 for l in cand:
764 for l in cand:
764 if diffhelpers.testhunk(old, self.lines, l) == 0:
765 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
765 self.lines[l : l + len(old)] = new
766 self.lines[l : l + len(old)] = new
766 self.offset += len(new) - len(old)
767 self.offset += len(new) - len(old)
767 self.skew = l - orig_start
768 self.skew = l - orig_start
768 self.dirty = True
769 self.dirty = True
769 offset = l - orig_start - fuzzlen
770 offset = l - orig_start - fuzzlen
770 if fuzzlen:
771 if fuzzlen:
771 msg = _("Hunk #%d succeeded at %d "
772 msg = _("Hunk #%d succeeded at %d "
772 "with fuzz %d "
773 "with fuzz %d "
773 "(offset %d lines).\n")
774 "(offset %d lines).\n")
774 self.printfile(True)
775 self.printfile(True)
775 self.ui.warn(msg %
776 self.ui.warn(msg %
776 (h.number, l + 1, fuzzlen, offset))
777 (h.number, l + 1, fuzzlen, offset))
777 else:
778 else:
778 msg = _("Hunk #%d succeeded at %d "
779 msg = _("Hunk #%d succeeded at %d "
779 "(offset %d lines).\n")
780 "(offset %d lines).\n")
780 self.ui.note(msg % (h.number, l + 1, offset))
781 self.ui.note(msg % (h.number, l + 1, offset))
781 return fuzzlen
782 return fuzzlen
782 self.printfile(True)
783 self.printfile(True)
783 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
784 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
784 self.rej.append(horig)
785 self.rej.append(horig)
785 return -1
786 return -1
786
787
787 def close(self):
788 def close(self):
788 if self.dirty:
789 if self.dirty:
789 self.writelines(self.fname, self.lines, self.mode)
790 self.writelines(self.fname, self.lines, self.mode)
790 self.write_rej()
791 self.write_rej()
791 return len(self.rej)
792 return len(self.rej)
792
793
793 class hunk(object):
794 class hunk(object):
794 def __init__(self, desc, num, lr, context):
795 def __init__(self, desc, num, lr, context):
795 self.number = num
796 self.number = num
796 self.desc = desc
797 self.desc = desc
797 self.hunk = [desc]
798 self.hunk = [desc]
798 self.a = []
799 self.a = []
799 self.b = []
800 self.b = []
800 self.starta = self.lena = None
801 self.starta = self.lena = None
801 self.startb = self.lenb = None
802 self.startb = self.lenb = None
802 if lr is not None:
803 if lr is not None:
803 if context:
804 if context:
804 self.read_context_hunk(lr)
805 self.read_context_hunk(lr)
805 else:
806 else:
806 self.read_unified_hunk(lr)
807 self.read_unified_hunk(lr)
807
808
808 def getnormalized(self):
809 def getnormalized(self):
809 """Return a copy with line endings normalized to LF."""
810 """Return a copy with line endings normalized to LF."""
810
811
811 def normalize(lines):
812 def normalize(lines):
812 nlines = []
813 nlines = []
813 for line in lines:
814 for line in lines:
814 if line.endswith('\r\n'):
815 if line.endswith('\r\n'):
815 line = line[:-2] + '\n'
816 line = line[:-2] + '\n'
816 nlines.append(line)
817 nlines.append(line)
817 return nlines
818 return nlines
818
819
819 # Dummy object, it is rebuilt manually
820 # Dummy object, it is rebuilt manually
820 nh = hunk(self.desc, self.number, None, None)
821 nh = hunk(self.desc, self.number, None, None)
821 nh.number = self.number
822 nh.number = self.number
822 nh.desc = self.desc
823 nh.desc = self.desc
823 nh.hunk = self.hunk
824 nh.hunk = self.hunk
824 nh.a = normalize(self.a)
825 nh.a = normalize(self.a)
825 nh.b = normalize(self.b)
826 nh.b = normalize(self.b)
826 nh.starta = self.starta
827 nh.starta = self.starta
827 nh.startb = self.startb
828 nh.startb = self.startb
828 nh.lena = self.lena
829 nh.lena = self.lena
829 nh.lenb = self.lenb
830 nh.lenb = self.lenb
830 return nh
831 return nh
831
832
832 def read_unified_hunk(self, lr):
833 def read_unified_hunk(self, lr):
833 m = unidesc.match(self.desc)
834 m = unidesc.match(self.desc)
834 if not m:
835 if not m:
835 raise PatchError(_("bad hunk #%d") % self.number)
836 raise PatchError(_("bad hunk #%d") % self.number)
836 self.starta, self.lena, self.startb, self.lenb = m.groups()
837 self.starta, self.lena, self.startb, self.lenb = m.groups()
837 if self.lena is None:
838 if self.lena is None:
838 self.lena = 1
839 self.lena = 1
839 else:
840 else:
840 self.lena = int(self.lena)
841 self.lena = int(self.lena)
841 if self.lenb is None:
842 if self.lenb is None:
842 self.lenb = 1
843 self.lenb = 1
843 else:
844 else:
844 self.lenb = int(self.lenb)
845 self.lenb = int(self.lenb)
845 self.starta = int(self.starta)
846 self.starta = int(self.starta)
846 self.startb = int(self.startb)
847 self.startb = int(self.startb)
847 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
848 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
848 # if we hit eof before finishing out the hunk, the last line will
849 # if we hit eof before finishing out the hunk, the last line will
849 # be zero length. Lets try to fix it up.
850 # be zero length. Lets try to fix it up.
850 while len(self.hunk[-1]) == 0:
851 while len(self.hunk[-1]) == 0:
851 del self.hunk[-1]
852 del self.hunk[-1]
852 del self.a[-1]
853 del self.a[-1]
853 del self.b[-1]
854 del self.b[-1]
854 self.lena -= 1
855 self.lena -= 1
855 self.lenb -= 1
856 self.lenb -= 1
856 self._fixnewline(lr)
857 self._fixnewline(lr)
857
858
858 def read_context_hunk(self, lr):
859 def read_context_hunk(self, lr):
859 self.desc = lr.readline()
860 self.desc = lr.readline()
860 m = contextdesc.match(self.desc)
861 m = contextdesc.match(self.desc)
861 if not m:
862 if not m:
862 raise PatchError(_("bad hunk #%d") % self.number)
863 raise PatchError(_("bad hunk #%d") % self.number)
863 self.starta, aend = m.groups()
864 self.starta, aend = m.groups()
864 self.starta = int(self.starta)
865 self.starta = int(self.starta)
865 if aend is None:
866 if aend is None:
866 aend = self.starta
867 aend = self.starta
867 self.lena = int(aend) - self.starta
868 self.lena = int(aend) - self.starta
868 if self.starta:
869 if self.starta:
869 self.lena += 1
870 self.lena += 1
870 for x in xrange(self.lena):
871 for x in xrange(self.lena):
871 l = lr.readline()
872 l = lr.readline()
872 if l.startswith('---'):
873 if l.startswith('---'):
873 # lines addition, old block is empty
874 # lines addition, old block is empty
874 lr.push(l)
875 lr.push(l)
875 break
876 break
876 s = l[2:]
877 s = l[2:]
877 if l.startswith('- ') or l.startswith('! '):
878 if l.startswith('- ') or l.startswith('! '):
878 u = '-' + s
879 u = '-' + s
879 elif l.startswith(' '):
880 elif l.startswith(' '):
880 u = ' ' + s
881 u = ' ' + s
881 else:
882 else:
882 raise PatchError(_("bad hunk #%d old text line %d") %
883 raise PatchError(_("bad hunk #%d old text line %d") %
883 (self.number, x))
884 (self.number, x))
884 self.a.append(u)
885 self.a.append(u)
885 self.hunk.append(u)
886 self.hunk.append(u)
886
887
887 l = lr.readline()
888 l = lr.readline()
888 if l.startswith('\ '):
889 if l.startswith('\ '):
889 s = self.a[-1][:-1]
890 s = self.a[-1][:-1]
890 self.a[-1] = s
891 self.a[-1] = s
891 self.hunk[-1] = s
892 self.hunk[-1] = s
892 l = lr.readline()
893 l = lr.readline()
893 m = contextdesc.match(l)
894 m = contextdesc.match(l)
894 if not m:
895 if not m:
895 raise PatchError(_("bad hunk #%d") % self.number)
896 raise PatchError(_("bad hunk #%d") % self.number)
896 self.startb, bend = m.groups()
897 self.startb, bend = m.groups()
897 self.startb = int(self.startb)
898 self.startb = int(self.startb)
898 if bend is None:
899 if bend is None:
899 bend = self.startb
900 bend = self.startb
900 self.lenb = int(bend) - self.startb
901 self.lenb = int(bend) - self.startb
901 if self.startb:
902 if self.startb:
902 self.lenb += 1
903 self.lenb += 1
903 hunki = 1
904 hunki = 1
904 for x in xrange(self.lenb):
905 for x in xrange(self.lenb):
905 l = lr.readline()
906 l = lr.readline()
906 if l.startswith('\ '):
907 if l.startswith('\ '):
907 # XXX: the only way to hit this is with an invalid line range.
908 # XXX: the only way to hit this is with an invalid line range.
908 # The no-eol marker is not counted in the line range, but I
909 # The no-eol marker is not counted in the line range, but I
909 # guess there are diff(1) out there which behave differently.
910 # guess there are diff(1) out there which behave differently.
910 s = self.b[-1][:-1]
911 s = self.b[-1][:-1]
911 self.b[-1] = s
912 self.b[-1] = s
912 self.hunk[hunki - 1] = s
913 self.hunk[hunki - 1] = s
913 continue
914 continue
914 if not l:
915 if not l:
915 # line deletions, new block is empty and we hit EOF
916 # line deletions, new block is empty and we hit EOF
916 lr.push(l)
917 lr.push(l)
917 break
918 break
918 s = l[2:]
919 s = l[2:]
919 if l.startswith('+ ') or l.startswith('! '):
920 if l.startswith('+ ') or l.startswith('! '):
920 u = '+' + s
921 u = '+' + s
921 elif l.startswith(' '):
922 elif l.startswith(' '):
922 u = ' ' + s
923 u = ' ' + s
923 elif len(self.b) == 0:
924 elif len(self.b) == 0:
924 # line deletions, new block is empty
925 # line deletions, new block is empty
925 lr.push(l)
926 lr.push(l)
926 break
927 break
927 else:
928 else:
928 raise PatchError(_("bad hunk #%d old text line %d") %
929 raise PatchError(_("bad hunk #%d old text line %d") %
929 (self.number, x))
930 (self.number, x))
930 self.b.append(s)
931 self.b.append(s)
931 while True:
932 while True:
932 if hunki >= len(self.hunk):
933 if hunki >= len(self.hunk):
933 h = ""
934 h = ""
934 else:
935 else:
935 h = self.hunk[hunki]
936 h = self.hunk[hunki]
936 hunki += 1
937 hunki += 1
937 if h == u:
938 if h == u:
938 break
939 break
939 elif h.startswith('-'):
940 elif h.startswith('-'):
940 continue
941 continue
941 else:
942 else:
942 self.hunk.insert(hunki - 1, u)
943 self.hunk.insert(hunki - 1, u)
943 break
944 break
944
945
945 if not self.a:
946 if not self.a:
946 # this happens when lines were only added to the hunk
947 # this happens when lines were only added to the hunk
947 for x in self.hunk:
948 for x in self.hunk:
948 if x.startswith('-') or x.startswith(' '):
949 if x.startswith('-') or x.startswith(' '):
949 self.a.append(x)
950 self.a.append(x)
950 if not self.b:
951 if not self.b:
951 # this happens when lines were only deleted from the hunk
952 # this happens when lines were only deleted from the hunk
952 for x in self.hunk:
953 for x in self.hunk:
953 if x.startswith('+') or x.startswith(' '):
954 if x.startswith('+') or x.startswith(' '):
954 self.b.append(x[1:])
955 self.b.append(x[1:])
955 # @@ -start,len +start,len @@
956 # @@ -start,len +start,len @@
956 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
957 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
957 self.startb, self.lenb)
958 self.startb, self.lenb)
958 self.hunk[0] = self.desc
959 self.hunk[0] = self.desc
959 self._fixnewline(lr)
960 self._fixnewline(lr)
960
961
961 def _fixnewline(self, lr):
962 def _fixnewline(self, lr):
962 l = lr.readline()
963 l = lr.readline()
963 if l.startswith('\ '):
964 if l.startswith('\ '):
964 diffhelpers.fix_newline(self.hunk, self.a, self.b)
965 diffhelpers.fix_newline(self.hunk, self.a, self.b)
965 else:
966 else:
966 lr.push(l)
967 lr.push(l)
967
968
968 def complete(self):
969 def complete(self):
969 return len(self.a) == self.lena and len(self.b) == self.lenb
970 return len(self.a) == self.lena and len(self.b) == self.lenb
970
971
971 def _fuzzit(self, old, new, fuzz, toponly):
972 def _fuzzit(self, old, new, fuzz, toponly):
972 # this removes context lines from the top and bottom of list 'l'. It
973 # this removes context lines from the top and bottom of list 'l'. It
973 # checks the hunk to make sure only context lines are removed, and then
974 # checks the hunk to make sure only context lines are removed, and then
974 # returns a new shortened list of lines.
975 # returns a new shortened list of lines.
975 fuzz = min(fuzz, len(old)-1)
976 fuzz = min(fuzz, len(old)-1)
976 if fuzz:
977 if fuzz:
977 top = 0
978 top = 0
978 bot = 0
979 bot = 0
979 hlen = len(self.hunk)
980 hlen = len(self.hunk)
980 for x in xrange(hlen - 1):
981 for x in xrange(hlen - 1):
981 # the hunk starts with the @@ line, so use x+1
982 # the hunk starts with the @@ line, so use x+1
982 if self.hunk[x + 1][0] == ' ':
983 if self.hunk[x + 1][0] == ' ':
983 top += 1
984 top += 1
984 else:
985 else:
985 break
986 break
986 if not toponly:
987 if not toponly:
987 for x in xrange(hlen - 1):
988 for x in xrange(hlen - 1):
988 if self.hunk[hlen - bot - 1][0] == ' ':
989 if self.hunk[hlen - bot - 1][0] == ' ':
989 bot += 1
990 bot += 1
990 else:
991 else:
991 break
992 break
992
993
993 # top and bot now count context in the hunk
994 # top and bot now count context in the hunk
994 # adjust them if either one is short
995 # adjust them if either one is short
995 context = max(top, bot, 3)
996 context = max(top, bot, 3)
996 if bot < context:
997 if bot < context:
997 bot = max(0, fuzz - (context - bot))
998 bot = max(0, fuzz - (context - bot))
998 else:
999 else:
999 bot = min(fuzz, bot)
1000 bot = min(fuzz, bot)
1000 if top < context:
1001 if top < context:
1001 top = max(0, fuzz - (context - top))
1002 top = max(0, fuzz - (context - top))
1002 else:
1003 else:
1003 top = min(fuzz, top)
1004 top = min(fuzz, top)
1004
1005
1005 return old[top:len(old)-bot], new[top:len(new)-bot], top
1006 return old[top:len(old)-bot], new[top:len(new)-bot], top
1006 return old, new, 0
1007 return old, new, 0
1007
1008
1008 def fuzzit(self, fuzz, toponly):
1009 def fuzzit(self, fuzz, toponly):
1009 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1010 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1010 oldstart = self.starta + top
1011 oldstart = self.starta + top
1011 newstart = self.startb + top
1012 newstart = self.startb + top
1012 # zero length hunk ranges already have their start decremented
1013 # zero length hunk ranges already have their start decremented
1013 if self.lena:
1014 if self.lena:
1014 oldstart -= 1
1015 oldstart -= 1
1015 if self.lenb:
1016 if self.lenb:
1016 newstart -= 1
1017 newstart -= 1
1017 return old, oldstart, new, newstart
1018 return old, oldstart, new, newstart
1018
1019
1019 class binhunk(object):
1020 class binhunk(object):
1020 'A binary patch file. Only understands literals so far.'
1021 'A binary patch file. Only understands literals so far.'
1021 def __init__(self, lr):
1022 def __init__(self, lr):
1022 self.text = None
1023 self.text = None
1023 self.hunk = ['GIT binary patch\n']
1024 self.hunk = ['GIT binary patch\n']
1024 self._read(lr)
1025 self._read(lr)
1025
1026
1026 def complete(self):
1027 def complete(self):
1027 return self.text is not None
1028 return self.text is not None
1028
1029
1029 def new(self):
1030 def new(self):
1030 return [self.text]
1031 return [self.text]
1031
1032
1032 def _read(self, lr):
1033 def _read(self, lr):
1033 line = lr.readline()
1034 line = lr.readline()
1034 self.hunk.append(line)
1035 self.hunk.append(line)
1035 while line and not line.startswith('literal '):
1036 while line and not line.startswith('literal '):
1036 line = lr.readline()
1037 line = lr.readline()
1037 self.hunk.append(line)
1038 self.hunk.append(line)
1038 if not line:
1039 if not line:
1039 raise PatchError(_('could not extract binary patch'))
1040 raise PatchError(_('could not extract binary patch'))
1040 size = int(line[8:].rstrip())
1041 size = int(line[8:].rstrip())
1041 dec = []
1042 dec = []
1042 line = lr.readline()
1043 line = lr.readline()
1043 self.hunk.append(line)
1044 self.hunk.append(line)
1044 while len(line) > 1:
1045 while len(line) > 1:
1045 l = line[0]
1046 l = line[0]
1046 if l <= 'Z' and l >= 'A':
1047 if l <= 'Z' and l >= 'A':
1047 l = ord(l) - ord('A') + 1
1048 l = ord(l) - ord('A') + 1
1048 else:
1049 else:
1049 l = ord(l) - ord('a') + 27
1050 l = ord(l) - ord('a') + 27
1050 dec.append(base85.b85decode(line[1:-1])[:l])
1051 dec.append(base85.b85decode(line[1:-1])[:l])
1051 line = lr.readline()
1052 line = lr.readline()
1052 self.hunk.append(line)
1053 self.hunk.append(line)
1053 text = zlib.decompress(''.join(dec))
1054 text = zlib.decompress(''.join(dec))
1054 if len(text) != size:
1055 if len(text) != size:
1055 raise PatchError(_('binary patch is %d bytes, not %d') %
1056 raise PatchError(_('binary patch is %d bytes, not %d') %
1056 len(text), size)
1057 len(text), size)
1057 self.text = text
1058 self.text = text
1058
1059
1059 def parsefilename(str):
1060 def parsefilename(str):
1060 # --- filename \t|space stuff
1061 # --- filename \t|space stuff
1061 s = str[4:].rstrip('\r\n')
1062 s = str[4:].rstrip('\r\n')
1062 i = s.find('\t')
1063 i = s.find('\t')
1063 if i < 0:
1064 if i < 0:
1064 i = s.find(' ')
1065 i = s.find(' ')
1065 if i < 0:
1066 if i < 0:
1066 return s
1067 return s
1067 return s[:i]
1068 return s[:i]
1068
1069
1069 def pathstrip(path, strip):
1070 def pathstrip(path, strip):
1070 pathlen = len(path)
1071 pathlen = len(path)
1071 i = 0
1072 i = 0
1072 if strip == 0:
1073 if strip == 0:
1073 return '', path.rstrip()
1074 return '', path.rstrip()
1074 count = strip
1075 count = strip
1075 while count > 0:
1076 while count > 0:
1076 i = path.find('/', i)
1077 i = path.find('/', i)
1077 if i == -1:
1078 if i == -1:
1078 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1079 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1079 (count, strip, path))
1080 (count, strip, path))
1080 i += 1
1081 i += 1
1081 # consume '//' in the path
1082 # consume '//' in the path
1082 while i < pathlen - 1 and path[i] == '/':
1083 while i < pathlen - 1 and path[i] == '/':
1083 i += 1
1084 i += 1
1084 count -= 1
1085 count -= 1
1085 return path[:i].lstrip(), path[i:].rstrip()
1086 return path[:i].lstrip(), path[i:].rstrip()
1086
1087
1087 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip):
1088 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip):
1088 nulla = afile_orig == "/dev/null"
1089 nulla = afile_orig == "/dev/null"
1089 nullb = bfile_orig == "/dev/null"
1090 nullb = bfile_orig == "/dev/null"
1090 create = nulla and hunk.starta == 0 and hunk.lena == 0
1091 create = nulla and hunk.starta == 0 and hunk.lena == 0
1091 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1092 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1092 abase, afile = pathstrip(afile_orig, strip)
1093 abase, afile = pathstrip(afile_orig, strip)
1093 gooda = not nulla and backend.exists(afile)
1094 gooda = not nulla and backend.exists(afile)
1094 bbase, bfile = pathstrip(bfile_orig, strip)
1095 bbase, bfile = pathstrip(bfile_orig, strip)
1095 if afile == bfile:
1096 if afile == bfile:
1096 goodb = gooda
1097 goodb = gooda
1097 else:
1098 else:
1098 goodb = not nullb and backend.exists(bfile)
1099 goodb = not nullb and backend.exists(bfile)
1099 missing = not goodb and not gooda and not create
1100 missing = not goodb and not gooda and not create
1100
1101
1101 # some diff programs apparently produce patches where the afile is
1102 # some diff programs apparently produce patches where the afile is
1102 # not /dev/null, but afile starts with bfile
1103 # not /dev/null, but afile starts with bfile
1103 abasedir = afile[:afile.rfind('/') + 1]
1104 abasedir = afile[:afile.rfind('/') + 1]
1104 bbasedir = bfile[:bfile.rfind('/') + 1]
1105 bbasedir = bfile[:bfile.rfind('/') + 1]
1105 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1106 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1106 and hunk.starta == 0 and hunk.lena == 0):
1107 and hunk.starta == 0 and hunk.lena == 0):
1107 create = True
1108 create = True
1108 missing = False
1109 missing = False
1109
1110
1110 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1111 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1111 # diff is between a file and its backup. In this case, the original
1112 # diff is between a file and its backup. In this case, the original
1112 # file should be patched (see original mpatch code).
1113 # file should be patched (see original mpatch code).
1113 isbackup = (abase == bbase and bfile.startswith(afile))
1114 isbackup = (abase == bbase and bfile.startswith(afile))
1114 fname = None
1115 fname = None
1115 if not missing:
1116 if not missing:
1116 if gooda and goodb:
1117 if gooda and goodb:
1117 fname = isbackup and afile or bfile
1118 fname = isbackup and afile or bfile
1118 elif gooda:
1119 elif gooda:
1119 fname = afile
1120 fname = afile
1120
1121
1121 if not fname:
1122 if not fname:
1122 if not nullb:
1123 if not nullb:
1123 fname = isbackup and afile or bfile
1124 fname = isbackup and afile or bfile
1124 elif not nulla:
1125 elif not nulla:
1125 fname = afile
1126 fname = afile
1126 else:
1127 else:
1127 raise PatchError(_("undefined source and destination files"))
1128 raise PatchError(_("undefined source and destination files"))
1128
1129
1129 gp = patchmeta(fname)
1130 gp = patchmeta(fname)
1130 if create:
1131 if create:
1131 gp.op = 'ADD'
1132 gp.op = 'ADD'
1132 elif remove:
1133 elif remove:
1133 gp.op = 'DELETE'
1134 gp.op = 'DELETE'
1134 return gp
1135 return gp
1135
1136
1136 def scangitpatch(lr, firstline):
1137 def scangitpatch(lr, firstline):
1137 """
1138 """
1138 Git patches can emit:
1139 Git patches can emit:
1139 - rename a to b
1140 - rename a to b
1140 - change b
1141 - change b
1141 - copy a to c
1142 - copy a to c
1142 - change c
1143 - change c
1143
1144
1144 We cannot apply this sequence as-is, the renamed 'a' could not be
1145 We cannot apply this sequence as-is, the renamed 'a' could not be
1145 found for it would have been renamed already. And we cannot copy
1146 found for it would have been renamed already. And we cannot copy
1146 from 'b' instead because 'b' would have been changed already. So
1147 from 'b' instead because 'b' would have been changed already. So
1147 we scan the git patch for copy and rename commands so we can
1148 we scan the git patch for copy and rename commands so we can
1148 perform the copies ahead of time.
1149 perform the copies ahead of time.
1149 """
1150 """
1150 pos = 0
1151 pos = 0
1151 try:
1152 try:
1152 pos = lr.fp.tell()
1153 pos = lr.fp.tell()
1153 fp = lr.fp
1154 fp = lr.fp
1154 except IOError:
1155 except IOError:
1155 fp = cStringIO.StringIO(lr.fp.read())
1156 fp = cStringIO.StringIO(lr.fp.read())
1156 gitlr = linereader(fp)
1157 gitlr = linereader(fp)
1157 gitlr.push(firstline)
1158 gitlr.push(firstline)
1158 gitpatches = readgitpatch(gitlr)
1159 gitpatches = readgitpatch(gitlr)
1159 fp.seek(pos)
1160 fp.seek(pos)
1160 return gitpatches
1161 return gitpatches
1161
1162
1162 def iterhunks(fp):
1163 def iterhunks(fp):
1163 """Read a patch and yield the following events:
1164 """Read a patch and yield the following events:
1164 - ("file", afile, bfile, firsthunk): select a new target file.
1165 - ("file", afile, bfile, firsthunk): select a new target file.
1165 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1166 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1166 "file" event.
1167 "file" event.
1167 - ("git", gitchanges): current diff is in git format, gitchanges
1168 - ("git", gitchanges): current diff is in git format, gitchanges
1168 maps filenames to gitpatch records. Unique event.
1169 maps filenames to gitpatch records. Unique event.
1169 """
1170 """
1170 afile = ""
1171 afile = ""
1171 bfile = ""
1172 bfile = ""
1172 state = None
1173 state = None
1173 hunknum = 0
1174 hunknum = 0
1174 emitfile = newfile = False
1175 emitfile = newfile = False
1175 gitpatches = None
1176 gitpatches = None
1176
1177
1177 # our states
1178 # our states
1178 BFILE = 1
1179 BFILE = 1
1179 context = None
1180 context = None
1180 lr = linereader(fp)
1181 lr = linereader(fp)
1181
1182
1182 while True:
1183 while True:
1183 x = lr.readline()
1184 x = lr.readline()
1184 if not x:
1185 if not x:
1185 break
1186 break
1186 if state == BFILE and (
1187 if state == BFILE and (
1187 (not context and x[0] == '@')
1188 (not context and x[0] == '@')
1188 or (context is not False and x.startswith('***************'))
1189 or (context is not False and x.startswith('***************'))
1189 or x.startswith('GIT binary patch')):
1190 or x.startswith('GIT binary patch')):
1190 gp = None
1191 gp = None
1191 if (gitpatches and
1192 if (gitpatches and
1192 (gitpatches[-1][0] == afile or gitpatches[-1][1] == bfile)):
1193 (gitpatches[-1][0] == afile or gitpatches[-1][1] == bfile)):
1193 gp = gitpatches.pop()[2]
1194 gp = gitpatches.pop()[2]
1194 if x.startswith('GIT binary patch'):
1195 if x.startswith('GIT binary patch'):
1195 h = binhunk(lr)
1196 h = binhunk(lr)
1196 else:
1197 else:
1197 if context is None and x.startswith('***************'):
1198 if context is None and x.startswith('***************'):
1198 context = True
1199 context = True
1199 h = hunk(x, hunknum + 1, lr, context)
1200 h = hunk(x, hunknum + 1, lr, context)
1200 hunknum += 1
1201 hunknum += 1
1201 if emitfile:
1202 if emitfile:
1202 emitfile = False
1203 emitfile = False
1203 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1204 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1204 yield 'hunk', h
1205 yield 'hunk', h
1205 elif x.startswith('diff --git'):
1206 elif x.startswith('diff --git'):
1206 m = gitre.match(x)
1207 m = gitre.match(x)
1207 if not m:
1208 if not m:
1208 continue
1209 continue
1209 if not gitpatches:
1210 if not gitpatches:
1210 # scan whole input for git metadata
1211 # scan whole input for git metadata
1211 gitpatches = [('a/' + gp.path, 'b/' + gp.path, gp) for gp
1212 gitpatches = [('a/' + gp.path, 'b/' + gp.path, gp) for gp
1212 in scangitpatch(lr, x)]
1213 in scangitpatch(lr, x)]
1213 yield 'git', [g[2].copy() for g in gitpatches
1214 yield 'git', [g[2].copy() for g in gitpatches
1214 if g[2].op in ('COPY', 'RENAME')]
1215 if g[2].op in ('COPY', 'RENAME')]
1215 gitpatches.reverse()
1216 gitpatches.reverse()
1216 afile = 'a/' + m.group(1)
1217 afile = 'a/' + m.group(1)
1217 bfile = 'b/' + m.group(2)
1218 bfile = 'b/' + m.group(2)
1218 while afile != gitpatches[-1][0] and bfile != gitpatches[-1][1]:
1219 while afile != gitpatches[-1][0] and bfile != gitpatches[-1][1]:
1219 gp = gitpatches.pop()[2]
1220 gp = gitpatches.pop()[2]
1220 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1221 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1221 gp = gitpatches[-1][2]
1222 gp = gitpatches[-1][2]
1222 # copy/rename + modify should modify target, not source
1223 # copy/rename + modify should modify target, not source
1223 if gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD') or gp.mode:
1224 if gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD') or gp.mode:
1224 afile = bfile
1225 afile = bfile
1225 newfile = True
1226 newfile = True
1226 elif x.startswith('---'):
1227 elif x.startswith('---'):
1227 # check for a unified diff
1228 # check for a unified diff
1228 l2 = lr.readline()
1229 l2 = lr.readline()
1229 if not l2.startswith('+++'):
1230 if not l2.startswith('+++'):
1230 lr.push(l2)
1231 lr.push(l2)
1231 continue
1232 continue
1232 newfile = True
1233 newfile = True
1233 context = False
1234 context = False
1234 afile = parsefilename(x)
1235 afile = parsefilename(x)
1235 bfile = parsefilename(l2)
1236 bfile = parsefilename(l2)
1236 elif x.startswith('***'):
1237 elif x.startswith('***'):
1237 # check for a context diff
1238 # check for a context diff
1238 l2 = lr.readline()
1239 l2 = lr.readline()
1239 if not l2.startswith('---'):
1240 if not l2.startswith('---'):
1240 lr.push(l2)
1241 lr.push(l2)
1241 continue
1242 continue
1242 l3 = lr.readline()
1243 l3 = lr.readline()
1243 lr.push(l3)
1244 lr.push(l3)
1244 if not l3.startswith("***************"):
1245 if not l3.startswith("***************"):
1245 lr.push(l2)
1246 lr.push(l2)
1246 continue
1247 continue
1247 newfile = True
1248 newfile = True
1248 context = True
1249 context = True
1249 afile = parsefilename(x)
1250 afile = parsefilename(x)
1250 bfile = parsefilename(l2)
1251 bfile = parsefilename(l2)
1251
1252
1252 if newfile:
1253 if newfile:
1253 newfile = False
1254 newfile = False
1254 emitfile = True
1255 emitfile = True
1255 state = BFILE
1256 state = BFILE
1256 hunknum = 0
1257 hunknum = 0
1257
1258
1258 while gitpatches:
1259 while gitpatches:
1259 gp = gitpatches.pop()[2]
1260 gp = gitpatches.pop()[2]
1260 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1261 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1261
1262
1262 def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'):
1263 def applydiff(ui, fp, backend, store, strip=1, eolmode='strict'):
1263 """Reads a patch from fp and tries to apply it.
1264 """Reads a patch from fp and tries to apply it.
1264
1265
1265 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1266 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1266 there was any fuzz.
1267 there was any fuzz.
1267
1268
1268 If 'eolmode' is 'strict', the patch content and patched file are
1269 If 'eolmode' is 'strict', the patch content and patched file are
1269 read in binary mode. Otherwise, line endings are ignored when
1270 read in binary mode. Otherwise, line endings are ignored when
1270 patching then normalized according to 'eolmode'.
1271 patching then normalized according to 'eolmode'.
1271 """
1272 """
1272 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1273 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1273 eolmode=eolmode)
1274 eolmode=eolmode)
1274
1275
1275 def _applydiff(ui, fp, patcher, backend, store, strip=1,
1276 def _applydiff(ui, fp, patcher, backend, store, strip=1,
1276 eolmode='strict'):
1277 eolmode='strict'):
1277
1278
1278 def pstrip(p):
1279 def pstrip(p):
1279 return pathstrip(p, strip - 1)[1]
1280 return pathstrip(p, strip - 1)[1]
1280
1281
1281 rejects = 0
1282 rejects = 0
1282 err = 0
1283 err = 0
1283 current_file = None
1284 current_file = None
1284
1285
1285 for state, values in iterhunks(fp):
1286 for state, values in iterhunks(fp):
1286 if state == 'hunk':
1287 if state == 'hunk':
1287 if not current_file:
1288 if not current_file:
1288 continue
1289 continue
1289 ret = current_file.apply(values)
1290 ret = current_file.apply(values)
1290 if ret > 0:
1291 if ret > 0:
1291 err = 1
1292 err = 1
1292 elif state == 'file':
1293 elif state == 'file':
1293 if current_file:
1294 if current_file:
1294 rejects += current_file.close()
1295 rejects += current_file.close()
1295 current_file = None
1296 current_file = None
1296 afile, bfile, first_hunk, gp = values
1297 afile, bfile, first_hunk, gp = values
1297 if gp:
1298 if gp:
1298 path = pstrip(gp.path)
1299 path = pstrip(gp.path)
1299 gp.path = pstrip(gp.path)
1300 gp.path = pstrip(gp.path)
1300 if gp.oldpath:
1301 if gp.oldpath:
1301 gp.oldpath = pstrip(gp.oldpath)
1302 gp.oldpath = pstrip(gp.oldpath)
1302 else:
1303 else:
1303 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1304 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1304 if gp.op == 'RENAME':
1305 if gp.op == 'RENAME':
1305 backend.unlink(gp.oldpath)
1306 backend.unlink(gp.oldpath)
1306 if not first_hunk:
1307 if not first_hunk:
1307 if gp.op == 'DELETE':
1308 if gp.op == 'DELETE':
1308 backend.unlink(gp.path)
1309 backend.unlink(gp.path)
1309 continue
1310 continue
1310 data, mode = None, None
1311 data, mode = None, None
1311 if gp.op in ('RENAME', 'COPY'):
1312 if gp.op in ('RENAME', 'COPY'):
1312 data, mode = store.getfile(gp.oldpath)[:2]
1313 data, mode = store.getfile(gp.oldpath)[:2]
1313 if gp.mode:
1314 if gp.mode:
1314 mode = gp.mode
1315 mode = gp.mode
1315 if gp.op == 'ADD':
1316 if gp.op == 'ADD':
1316 # Added files without content have no hunk and
1317 # Added files without content have no hunk and
1317 # must be created
1318 # must be created
1318 data = ''
1319 data = ''
1319 if data or mode:
1320 if data or mode:
1320 if (gp.op in ('ADD', 'RENAME', 'COPY')
1321 if (gp.op in ('ADD', 'RENAME', 'COPY')
1321 and backend.exists(gp.path)):
1322 and backend.exists(gp.path)):
1322 raise PatchError(_("cannot create %s: destination "
1323 raise PatchError(_("cannot create %s: destination "
1323 "already exists") % gp.path)
1324 "already exists") % gp.path)
1324 backend.setfile(gp.path, data, mode, gp.oldpath)
1325 backend.setfile(gp.path, data, mode, gp.oldpath)
1325 continue
1326 continue
1326 try:
1327 try:
1327 current_file = patcher(ui, gp, backend, store,
1328 current_file = patcher(ui, gp, backend, store,
1328 eolmode=eolmode)
1329 eolmode=eolmode)
1329 except PatchError, inst:
1330 except PatchError, inst:
1330 ui.warn(str(inst) + '\n')
1331 ui.warn(str(inst) + '\n')
1331 current_file = None
1332 current_file = None
1332 rejects += 1
1333 rejects += 1
1333 continue
1334 continue
1334 elif state == 'git':
1335 elif state == 'git':
1335 for gp in values:
1336 for gp in values:
1336 path = pstrip(gp.oldpath)
1337 path = pstrip(gp.oldpath)
1337 data, mode = backend.getfile(path)
1338 data, mode = backend.getfile(path)
1338 store.setfile(path, data, mode)
1339 store.setfile(path, data, mode)
1339 else:
1340 else:
1340 raise util.Abort(_('unsupported parser state: %s') % state)
1341 raise util.Abort(_('unsupported parser state: %s') % state)
1341
1342
1342 if current_file:
1343 if current_file:
1343 rejects += current_file.close()
1344 rejects += current_file.close()
1344
1345
1345 if rejects:
1346 if rejects:
1346 return -1
1347 return -1
1347 return err
1348 return err
1348
1349
1349 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1350 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1350 similarity):
1351 similarity):
1351 """use <patcher> to apply <patchname> to the working directory.
1352 """use <patcher> to apply <patchname> to the working directory.
1352 returns whether patch was applied with fuzz factor."""
1353 returns whether patch was applied with fuzz factor."""
1353
1354
1354 fuzz = False
1355 fuzz = False
1355 args = []
1356 args = []
1356 cwd = repo.root
1357 cwd = repo.root
1357 if cwd:
1358 if cwd:
1358 args.append('-d %s' % util.shellquote(cwd))
1359 args.append('-d %s' % util.shellquote(cwd))
1359 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1360 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1360 util.shellquote(patchname)))
1361 util.shellquote(patchname)))
1361 try:
1362 try:
1362 for line in fp:
1363 for line in fp:
1363 line = line.rstrip()
1364 line = line.rstrip()
1364 ui.note(line + '\n')
1365 ui.note(line + '\n')
1365 if line.startswith('patching file '):
1366 if line.startswith('patching file '):
1366 pf = util.parsepatchoutput(line)
1367 pf = util.parsepatchoutput(line)
1367 printed_file = False
1368 printed_file = False
1368 files.add(pf)
1369 files.add(pf)
1369 elif line.find('with fuzz') >= 0:
1370 elif line.find('with fuzz') >= 0:
1370 fuzz = True
1371 fuzz = True
1371 if not printed_file:
1372 if not printed_file:
1372 ui.warn(pf + '\n')
1373 ui.warn(pf + '\n')
1373 printed_file = True
1374 printed_file = True
1374 ui.warn(line + '\n')
1375 ui.warn(line + '\n')
1375 elif line.find('saving rejects to file') >= 0:
1376 elif line.find('saving rejects to file') >= 0:
1376 ui.warn(line + '\n')
1377 ui.warn(line + '\n')
1377 elif line.find('FAILED') >= 0:
1378 elif line.find('FAILED') >= 0:
1378 if not printed_file:
1379 if not printed_file:
1379 ui.warn(pf + '\n')
1380 ui.warn(pf + '\n')
1380 printed_file = True
1381 printed_file = True
1381 ui.warn(line + '\n')
1382 ui.warn(line + '\n')
1382 finally:
1383 finally:
1383 if files:
1384 if files:
1384 cfiles = list(files)
1385 cfiles = list(files)
1385 cwd = repo.getcwd()
1386 cwd = repo.getcwd()
1386 if cwd:
1387 if cwd:
1387 cfiles = [util.pathto(repo.root, cwd, f)
1388 cfiles = [util.pathto(repo.root, cwd, f)
1388 for f in cfiles]
1389 for f in cfiles]
1389 scmutil.addremove(repo, cfiles, similarity=similarity)
1390 scmutil.addremove(repo, cfiles, similarity=similarity)
1390 code = fp.close()
1391 code = fp.close()
1391 if code:
1392 if code:
1392 raise PatchError(_("patch command failed: %s") %
1393 raise PatchError(_("patch command failed: %s") %
1393 util.explainexit(code)[0])
1394 util.explainexit(code)[0])
1394 return fuzz
1395 return fuzz
1395
1396
1396 def patchbackend(ui, backend, patchobj, strip, files=None, eolmode='strict'):
1397 def patchbackend(ui, backend, patchobj, strip, files=None, eolmode='strict'):
1397 if files is None:
1398 if files is None:
1398 files = set()
1399 files = set()
1399 if eolmode is None:
1400 if eolmode is None:
1400 eolmode = ui.config('patch', 'eol', 'strict')
1401 eolmode = ui.config('patch', 'eol', 'strict')
1401 if eolmode.lower() not in eolmodes:
1402 if eolmode.lower() not in eolmodes:
1402 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1403 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1403 eolmode = eolmode.lower()
1404 eolmode = eolmode.lower()
1404
1405
1405 store = filestore()
1406 store = filestore()
1406 try:
1407 try:
1407 fp = open(patchobj, 'rb')
1408 fp = open(patchobj, 'rb')
1408 except TypeError:
1409 except TypeError:
1409 fp = patchobj
1410 fp = patchobj
1410 try:
1411 try:
1411 ret = applydiff(ui, fp, backend, store, strip=strip,
1412 ret = applydiff(ui, fp, backend, store, strip=strip,
1412 eolmode=eolmode)
1413 eolmode=eolmode)
1413 finally:
1414 finally:
1414 if fp != patchobj:
1415 if fp != patchobj:
1415 fp.close()
1416 fp.close()
1416 files.update(backend.close())
1417 files.update(backend.close())
1417 store.close()
1418 store.close()
1418 if ret < 0:
1419 if ret < 0:
1419 raise PatchError(_('patch failed to apply'))
1420 raise PatchError(_('patch failed to apply'))
1420 return ret > 0
1421 return ret > 0
1421
1422
1422 def internalpatch(ui, repo, patchobj, strip, files=None, eolmode='strict',
1423 def internalpatch(ui, repo, patchobj, strip, files=None, eolmode='strict',
1423 similarity=0):
1424 similarity=0):
1424 """use builtin patch to apply <patchobj> to the working directory.
1425 """use builtin patch to apply <patchobj> to the working directory.
1425 returns whether patch was applied with fuzz factor."""
1426 returns whether patch was applied with fuzz factor."""
1426 backend = workingbackend(ui, repo, similarity)
1427 backend = workingbackend(ui, repo, similarity)
1427 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1428 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1428
1429
1429 def patchrepo(ui, repo, ctx, store, patchobj, strip, files=None,
1430 def patchrepo(ui, repo, ctx, store, patchobj, strip, files=None,
1430 eolmode='strict'):
1431 eolmode='strict'):
1431 backend = repobackend(ui, repo, ctx, store)
1432 backend = repobackend(ui, repo, ctx, store)
1432 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1433 return patchbackend(ui, backend, patchobj, strip, files, eolmode)
1433
1434
1434 def makememctx(repo, parents, text, user, date, branch, files, store,
1435 def makememctx(repo, parents, text, user, date, branch, files, store,
1435 editor=None):
1436 editor=None):
1436 def getfilectx(repo, memctx, path):
1437 def getfilectx(repo, memctx, path):
1437 data, (islink, isexec), copied = store.getfile(path)
1438 data, (islink, isexec), copied = store.getfile(path)
1438 return context.memfilectx(path, data, islink=islink, isexec=isexec,
1439 return context.memfilectx(path, data, islink=islink, isexec=isexec,
1439 copied=copied)
1440 copied=copied)
1440 extra = {}
1441 extra = {}
1441 if branch:
1442 if branch:
1442 extra['branch'] = encoding.fromlocal(branch)
1443 extra['branch'] = encoding.fromlocal(branch)
1443 ctx = context.memctx(repo, parents, text, files, getfilectx, user,
1444 ctx = context.memctx(repo, parents, text, files, getfilectx, user,
1444 date, extra)
1445 date, extra)
1445 if editor:
1446 if editor:
1446 ctx._text = editor(repo, ctx, [])
1447 ctx._text = editor(repo, ctx, [])
1447 return ctx
1448 return ctx
1448
1449
1449 def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
1450 def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
1450 similarity=0):
1451 similarity=0):
1451 """Apply <patchname> to the working directory.
1452 """Apply <patchname> to the working directory.
1452
1453
1453 'eolmode' specifies how end of lines should be handled. It can be:
1454 'eolmode' specifies how end of lines should be handled. It can be:
1454 - 'strict': inputs are read in binary mode, EOLs are preserved
1455 - 'strict': inputs are read in binary mode, EOLs are preserved
1455 - 'crlf': EOLs are ignored when patching and reset to CRLF
1456 - 'crlf': EOLs are ignored when patching and reset to CRLF
1456 - 'lf': EOLs are ignored when patching and reset to LF
1457 - 'lf': EOLs are ignored when patching and reset to LF
1457 - None: get it from user settings, default to 'strict'
1458 - None: get it from user settings, default to 'strict'
1458 'eolmode' is ignored when using an external patcher program.
1459 'eolmode' is ignored when using an external patcher program.
1459
1460
1460 Returns whether patch was applied with fuzz factor.
1461 Returns whether patch was applied with fuzz factor.
1461 """
1462 """
1462 patcher = ui.config('ui', 'patch')
1463 patcher = ui.config('ui', 'patch')
1463 if files is None:
1464 if files is None:
1464 files = set()
1465 files = set()
1465 try:
1466 try:
1466 if patcher:
1467 if patcher:
1467 return _externalpatch(ui, repo, patcher, patchname, strip,
1468 return _externalpatch(ui, repo, patcher, patchname, strip,
1468 files, similarity)
1469 files, similarity)
1469 return internalpatch(ui, repo, patchname, strip, files, eolmode,
1470 return internalpatch(ui, repo, patchname, strip, files, eolmode,
1470 similarity)
1471 similarity)
1471 except PatchError, err:
1472 except PatchError, err:
1472 raise util.Abort(str(err))
1473 raise util.Abort(str(err))
1473
1474
1474 def changedfiles(ui, repo, patchpath, strip=1):
1475 def changedfiles(ui, repo, patchpath, strip=1):
1475 backend = fsbackend(ui, repo.root)
1476 backend = fsbackend(ui, repo.root)
1476 fp = open(patchpath, 'rb')
1477 fp = open(patchpath, 'rb')
1477 try:
1478 try:
1478 changed = set()
1479 changed = set()
1479 for state, values in iterhunks(fp):
1480 for state, values in iterhunks(fp):
1480 if state == 'file':
1481 if state == 'file':
1481 afile, bfile, first_hunk, gp = values
1482 afile, bfile, first_hunk, gp = values
1482 if gp:
1483 if gp:
1483 gp.path = pathstrip(gp.path, strip - 1)[1]
1484 gp.path = pathstrip(gp.path, strip - 1)[1]
1484 if gp.oldpath:
1485 if gp.oldpath:
1485 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1486 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1486 else:
1487 else:
1487 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1488 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip)
1488 changed.add(gp.path)
1489 changed.add(gp.path)
1489 if gp.op == 'RENAME':
1490 if gp.op == 'RENAME':
1490 changed.add(gp.oldpath)
1491 changed.add(gp.oldpath)
1491 elif state not in ('hunk', 'git'):
1492 elif state not in ('hunk', 'git'):
1492 raise util.Abort(_('unsupported parser state: %s') % state)
1493 raise util.Abort(_('unsupported parser state: %s') % state)
1493 return changed
1494 return changed
1494 finally:
1495 finally:
1495 fp.close()
1496 fp.close()
1496
1497
1497 def b85diff(to, tn):
1498 def b85diff(to, tn):
1498 '''print base85-encoded binary diff'''
1499 '''print base85-encoded binary diff'''
1499 def gitindex(text):
1500 def gitindex(text):
1500 if not text:
1501 if not text:
1501 return hex(nullid)
1502 return hex(nullid)
1502 l = len(text)
1503 l = len(text)
1503 s = util.sha1('blob %d\0' % l)
1504 s = util.sha1('blob %d\0' % l)
1504 s.update(text)
1505 s.update(text)
1505 return s.hexdigest()
1506 return s.hexdigest()
1506
1507
1507 def fmtline(line):
1508 def fmtline(line):
1508 l = len(line)
1509 l = len(line)
1509 if l <= 26:
1510 if l <= 26:
1510 l = chr(ord('A') + l - 1)
1511 l = chr(ord('A') + l - 1)
1511 else:
1512 else:
1512 l = chr(l - 26 + ord('a') - 1)
1513 l = chr(l - 26 + ord('a') - 1)
1513 return '%c%s\n' % (l, base85.b85encode(line, True))
1514 return '%c%s\n' % (l, base85.b85encode(line, True))
1514
1515
1515 def chunk(text, csize=52):
1516 def chunk(text, csize=52):
1516 l = len(text)
1517 l = len(text)
1517 i = 0
1518 i = 0
1518 while i < l:
1519 while i < l:
1519 yield text[i:i + csize]
1520 yield text[i:i + csize]
1520 i += csize
1521 i += csize
1521
1522
1522 tohash = gitindex(to)
1523 tohash = gitindex(to)
1523 tnhash = gitindex(tn)
1524 tnhash = gitindex(tn)
1524 if tohash == tnhash:
1525 if tohash == tnhash:
1525 return ""
1526 return ""
1526
1527
1527 # TODO: deltas
1528 # TODO: deltas
1528 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1529 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1529 (tohash, tnhash, len(tn))]
1530 (tohash, tnhash, len(tn))]
1530 for l in chunk(zlib.compress(tn)):
1531 for l in chunk(zlib.compress(tn)):
1531 ret.append(fmtline(l))
1532 ret.append(fmtline(l))
1532 ret.append('\n')
1533 ret.append('\n')
1533 return ''.join(ret)
1534 return ''.join(ret)
1534
1535
1535 class GitDiffRequired(Exception):
1536 class GitDiffRequired(Exception):
1536 pass
1537 pass
1537
1538
1538 def diffopts(ui, opts=None, untrusted=False, section='diff'):
1539 def diffopts(ui, opts=None, untrusted=False, section='diff'):
1539 def get(key, name=None, getter=ui.configbool):
1540 def get(key, name=None, getter=ui.configbool):
1540 return ((opts and opts.get(key)) or
1541 return ((opts and opts.get(key)) or
1541 getter(section, name or key, None, untrusted=untrusted))
1542 getter(section, name or key, None, untrusted=untrusted))
1542 return mdiff.diffopts(
1543 return mdiff.diffopts(
1543 text=opts and opts.get('text'),
1544 text=opts and opts.get('text'),
1544 git=get('git'),
1545 git=get('git'),
1545 nodates=get('nodates'),
1546 nodates=get('nodates'),
1546 showfunc=get('show_function', 'showfunc'),
1547 showfunc=get('show_function', 'showfunc'),
1547 ignorews=get('ignore_all_space', 'ignorews'),
1548 ignorews=get('ignore_all_space', 'ignorews'),
1548 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1549 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1549 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1550 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1550 context=get('unified', getter=ui.config))
1551 context=get('unified', getter=ui.config))
1551
1552
1552 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1553 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1553 losedatafn=None, prefix=''):
1554 losedatafn=None, prefix=''):
1554 '''yields diff of changes to files between two nodes, or node and
1555 '''yields diff of changes to files between two nodes, or node and
1555 working directory.
1556 working directory.
1556
1557
1557 if node1 is None, use first dirstate parent instead.
1558 if node1 is None, use first dirstate parent instead.
1558 if node2 is None, compare node1 with working directory.
1559 if node2 is None, compare node1 with working directory.
1559
1560
1560 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1561 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1561 every time some change cannot be represented with the current
1562 every time some change cannot be represented with the current
1562 patch format. Return False to upgrade to git patch format, True to
1563 patch format. Return False to upgrade to git patch format, True to
1563 accept the loss or raise an exception to abort the diff. It is
1564 accept the loss or raise an exception to abort the diff. It is
1564 called with the name of current file being diffed as 'fn'. If set
1565 called with the name of current file being diffed as 'fn'. If set
1565 to None, patches will always be upgraded to git format when
1566 to None, patches will always be upgraded to git format when
1566 necessary.
1567 necessary.
1567
1568
1568 prefix is a filename prefix that is prepended to all filenames on
1569 prefix is a filename prefix that is prepended to all filenames on
1569 display (used for subrepos).
1570 display (used for subrepos).
1570 '''
1571 '''
1571
1572
1572 if opts is None:
1573 if opts is None:
1573 opts = mdiff.defaultopts
1574 opts = mdiff.defaultopts
1574
1575
1575 if not node1 and not node2:
1576 if not node1 and not node2:
1576 node1 = repo.dirstate.p1()
1577 node1 = repo.dirstate.p1()
1577
1578
1578 def lrugetfilectx():
1579 def lrugetfilectx():
1579 cache = {}
1580 cache = {}
1580 order = []
1581 order = []
1581 def getfilectx(f, ctx):
1582 def getfilectx(f, ctx):
1582 fctx = ctx.filectx(f, filelog=cache.get(f))
1583 fctx = ctx.filectx(f, filelog=cache.get(f))
1583 if f not in cache:
1584 if f not in cache:
1584 if len(cache) > 20:
1585 if len(cache) > 20:
1585 del cache[order.pop(0)]
1586 del cache[order.pop(0)]
1586 cache[f] = fctx.filelog()
1587 cache[f] = fctx.filelog()
1587 else:
1588 else:
1588 order.remove(f)
1589 order.remove(f)
1589 order.append(f)
1590 order.append(f)
1590 return fctx
1591 return fctx
1591 return getfilectx
1592 return getfilectx
1592 getfilectx = lrugetfilectx()
1593 getfilectx = lrugetfilectx()
1593
1594
1594 ctx1 = repo[node1]
1595 ctx1 = repo[node1]
1595 ctx2 = repo[node2]
1596 ctx2 = repo[node2]
1596
1597
1597 if not changes:
1598 if not changes:
1598 changes = repo.status(ctx1, ctx2, match=match)
1599 changes = repo.status(ctx1, ctx2, match=match)
1599 modified, added, removed = changes[:3]
1600 modified, added, removed = changes[:3]
1600
1601
1601 if not modified and not added and not removed:
1602 if not modified and not added and not removed:
1602 return []
1603 return []
1603
1604
1604 revs = None
1605 revs = None
1605 if not repo.ui.quiet:
1606 if not repo.ui.quiet:
1606 hexfunc = repo.ui.debugflag and hex or short
1607 hexfunc = repo.ui.debugflag and hex or short
1607 revs = [hexfunc(node) for node in [node1, node2] if node]
1608 revs = [hexfunc(node) for node in [node1, node2] if node]
1608
1609
1609 copy = {}
1610 copy = {}
1610 if opts.git or opts.upgrade:
1611 if opts.git or opts.upgrade:
1611 copy = copies.pathcopies(ctx1, ctx2)
1612 copy = copies.pathcopies(ctx1, ctx2)
1612
1613
1613 difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2,
1614 difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2,
1614 modified, added, removed, copy, getfilectx, opts, losedata, prefix)
1615 modified, added, removed, copy, getfilectx, opts, losedata, prefix)
1615 if opts.upgrade and not opts.git:
1616 if opts.upgrade and not opts.git:
1616 try:
1617 try:
1617 def losedata(fn):
1618 def losedata(fn):
1618 if not losedatafn or not losedatafn(fn=fn):
1619 if not losedatafn or not losedatafn(fn=fn):
1619 raise GitDiffRequired()
1620 raise GitDiffRequired()
1620 # Buffer the whole output until we are sure it can be generated
1621 # Buffer the whole output until we are sure it can be generated
1621 return list(difffn(opts.copy(git=False), losedata))
1622 return list(difffn(opts.copy(git=False), losedata))
1622 except GitDiffRequired:
1623 except GitDiffRequired:
1623 return difffn(opts.copy(git=True), None)
1624 return difffn(opts.copy(git=True), None)
1624 else:
1625 else:
1625 return difffn(opts, None)
1626 return difffn(opts, None)
1626
1627
1627 def difflabel(func, *args, **kw):
1628 def difflabel(func, *args, **kw):
1628 '''yields 2-tuples of (output, label) based on the output of func()'''
1629 '''yields 2-tuples of (output, label) based on the output of func()'''
1629 headprefixes = [('diff', 'diff.diffline'),
1630 headprefixes = [('diff', 'diff.diffline'),
1630 ('copy', 'diff.extended'),
1631 ('copy', 'diff.extended'),
1631 ('rename', 'diff.extended'),
1632 ('rename', 'diff.extended'),
1632 ('old', 'diff.extended'),
1633 ('old', 'diff.extended'),
1633 ('new', 'diff.extended'),
1634 ('new', 'diff.extended'),
1634 ('deleted', 'diff.extended'),
1635 ('deleted', 'diff.extended'),
1635 ('---', 'diff.file_a'),
1636 ('---', 'diff.file_a'),
1636 ('+++', 'diff.file_b')]
1637 ('+++', 'diff.file_b')]
1637 textprefixes = [('@', 'diff.hunk'),
1638 textprefixes = [('@', 'diff.hunk'),
1638 ('-', 'diff.deleted'),
1639 ('-', 'diff.deleted'),
1639 ('+', 'diff.inserted')]
1640 ('+', 'diff.inserted')]
1640 head = False
1641 head = False
1641 for chunk in func(*args, **kw):
1642 for chunk in func(*args, **kw):
1642 lines = chunk.split('\n')
1643 lines = chunk.split('\n')
1643 for i, line in enumerate(lines):
1644 for i, line in enumerate(lines):
1644 if i != 0:
1645 if i != 0:
1645 yield ('\n', '')
1646 yield ('\n', '')
1646 if head:
1647 if head:
1647 if line.startswith('@'):
1648 if line.startswith('@'):
1648 head = False
1649 head = False
1649 else:
1650 else:
1650 if line and not line[0] in ' +-@\\':
1651 if line and not line[0] in ' +-@\\':
1651 head = True
1652 head = True
1652 stripline = line
1653 stripline = line
1653 if not head and line and line[0] in '+-':
1654 if not head and line and line[0] in '+-':
1654 # highlight trailing whitespace, but only in changed lines
1655 # highlight trailing whitespace, but only in changed lines
1655 stripline = line.rstrip()
1656 stripline = line.rstrip()
1656 prefixes = textprefixes
1657 prefixes = textprefixes
1657 if head:
1658 if head:
1658 prefixes = headprefixes
1659 prefixes = headprefixes
1659 for prefix, label in prefixes:
1660 for prefix, label in prefixes:
1660 if stripline.startswith(prefix):
1661 if stripline.startswith(prefix):
1661 yield (stripline, label)
1662 yield (stripline, label)
1662 break
1663 break
1663 else:
1664 else:
1664 yield (line, '')
1665 yield (line, '')
1665 if line != stripline:
1666 if line != stripline:
1666 yield (line[len(stripline):], 'diff.trailingwhitespace')
1667 yield (line[len(stripline):], 'diff.trailingwhitespace')
1667
1668
1668 def diffui(*args, **kw):
1669 def diffui(*args, **kw):
1669 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1670 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1670 return difflabel(diff, *args, **kw)
1671 return difflabel(diff, *args, **kw)
1671
1672
1672
1673
1673 def _addmodehdr(header, omode, nmode):
1674 def _addmodehdr(header, omode, nmode):
1674 if omode != nmode:
1675 if omode != nmode:
1675 header.append('old mode %s\n' % omode)
1676 header.append('old mode %s\n' % omode)
1676 header.append('new mode %s\n' % nmode)
1677 header.append('new mode %s\n' % nmode)
1677
1678
1678 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1679 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1679 copy, getfilectx, opts, losedatafn, prefix):
1680 copy, getfilectx, opts, losedatafn, prefix):
1680
1681
1681 def join(f):
1682 def join(f):
1682 return os.path.join(prefix, f)
1683 return os.path.join(prefix, f)
1683
1684
1684 date1 = util.datestr(ctx1.date())
1685 date1 = util.datestr(ctx1.date())
1685 man1 = ctx1.manifest()
1686 man1 = ctx1.manifest()
1686
1687
1687 gone = set()
1688 gone = set()
1688 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1689 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1689
1690
1690 copyto = dict([(v, k) for k, v in copy.items()])
1691 copyto = dict([(v, k) for k, v in copy.items()])
1691
1692
1692 if opts.git:
1693 if opts.git:
1693 revs = None
1694 revs = None
1694
1695
1695 for f in sorted(modified + added + removed):
1696 for f in sorted(modified + added + removed):
1696 to = None
1697 to = None
1697 tn = None
1698 tn = None
1698 dodiff = True
1699 dodiff = True
1699 header = []
1700 header = []
1700 if f in man1:
1701 if f in man1:
1701 to = getfilectx(f, ctx1).data()
1702 to = getfilectx(f, ctx1).data()
1702 if f not in removed:
1703 if f not in removed:
1703 tn = getfilectx(f, ctx2).data()
1704 tn = getfilectx(f, ctx2).data()
1704 a, b = f, f
1705 a, b = f, f
1705 if opts.git or losedatafn:
1706 if opts.git or losedatafn:
1706 if f in added:
1707 if f in added:
1707 mode = gitmode[ctx2.flags(f)]
1708 mode = gitmode[ctx2.flags(f)]
1708 if f in copy or f in copyto:
1709 if f in copy or f in copyto:
1709 if opts.git:
1710 if opts.git:
1710 if f in copy:
1711 if f in copy:
1711 a = copy[f]
1712 a = copy[f]
1712 else:
1713 else:
1713 a = copyto[f]
1714 a = copyto[f]
1714 omode = gitmode[man1.flags(a)]
1715 omode = gitmode[man1.flags(a)]
1715 _addmodehdr(header, omode, mode)
1716 _addmodehdr(header, omode, mode)
1716 if a in removed and a not in gone:
1717 if a in removed and a not in gone:
1717 op = 'rename'
1718 op = 'rename'
1718 gone.add(a)
1719 gone.add(a)
1719 else:
1720 else:
1720 op = 'copy'
1721 op = 'copy'
1721 header.append('%s from %s\n' % (op, join(a)))
1722 header.append('%s from %s\n' % (op, join(a)))
1722 header.append('%s to %s\n' % (op, join(f)))
1723 header.append('%s to %s\n' % (op, join(f)))
1723 to = getfilectx(a, ctx1).data()
1724 to = getfilectx(a, ctx1).data()
1724 else:
1725 else:
1725 losedatafn(f)
1726 losedatafn(f)
1726 else:
1727 else:
1727 if opts.git:
1728 if opts.git:
1728 header.append('new file mode %s\n' % mode)
1729 header.append('new file mode %s\n' % mode)
1729 elif ctx2.flags(f):
1730 elif ctx2.flags(f):
1730 losedatafn(f)
1731 losedatafn(f)
1731 # In theory, if tn was copied or renamed we should check
1732 # In theory, if tn was copied or renamed we should check
1732 # if the source is binary too but the copy record already
1733 # if the source is binary too but the copy record already
1733 # forces git mode.
1734 # forces git mode.
1734 if util.binary(tn):
1735 if util.binary(tn):
1735 if opts.git:
1736 if opts.git:
1736 dodiff = 'binary'
1737 dodiff = 'binary'
1737 else:
1738 else:
1738 losedatafn(f)
1739 losedatafn(f)
1739 if not opts.git and not tn:
1740 if not opts.git and not tn:
1740 # regular diffs cannot represent new empty file
1741 # regular diffs cannot represent new empty file
1741 losedatafn(f)
1742 losedatafn(f)
1742 elif f in removed:
1743 elif f in removed:
1743 if opts.git:
1744 if opts.git:
1744 # have we already reported a copy above?
1745 # have we already reported a copy above?
1745 if ((f in copy and copy[f] in added
1746 if ((f in copy and copy[f] in added
1746 and copyto[copy[f]] == f) or
1747 and copyto[copy[f]] == f) or
1747 (f in copyto and copyto[f] in added
1748 (f in copyto and copyto[f] in added
1748 and copy[copyto[f]] == f)):
1749 and copy[copyto[f]] == f)):
1749 dodiff = False
1750 dodiff = False
1750 else:
1751 else:
1751 header.append('deleted file mode %s\n' %
1752 header.append('deleted file mode %s\n' %
1752 gitmode[man1.flags(f)])
1753 gitmode[man1.flags(f)])
1753 elif not to or util.binary(to):
1754 elif not to or util.binary(to):
1754 # regular diffs cannot represent empty file deletion
1755 # regular diffs cannot represent empty file deletion
1755 losedatafn(f)
1756 losedatafn(f)
1756 else:
1757 else:
1757 oflag = man1.flags(f)
1758 oflag = man1.flags(f)
1758 nflag = ctx2.flags(f)
1759 nflag = ctx2.flags(f)
1759 binary = util.binary(to) or util.binary(tn)
1760 binary = util.binary(to) or util.binary(tn)
1760 if opts.git:
1761 if opts.git:
1761 _addmodehdr(header, gitmode[oflag], gitmode[nflag])
1762 _addmodehdr(header, gitmode[oflag], gitmode[nflag])
1762 if binary:
1763 if binary:
1763 dodiff = 'binary'
1764 dodiff = 'binary'
1764 elif binary or nflag != oflag:
1765 elif binary or nflag != oflag:
1765 losedatafn(f)
1766 losedatafn(f)
1766 if opts.git:
1767 if opts.git:
1767 header.insert(0, mdiff.diffline(revs, join(a), join(b), opts))
1768 header.insert(0, mdiff.diffline(revs, join(a), join(b), opts))
1768
1769
1769 if dodiff:
1770 if dodiff:
1770 if dodiff == 'binary':
1771 if dodiff == 'binary':
1771 text = b85diff(to, tn)
1772 text = b85diff(to, tn)
1772 else:
1773 else:
1773 text = mdiff.unidiff(to, date1,
1774 text = mdiff.unidiff(to, date1,
1774 # ctx2 date may be dynamic
1775 # ctx2 date may be dynamic
1775 tn, util.datestr(ctx2.date()),
1776 tn, util.datestr(ctx2.date()),
1776 join(a), join(b), revs, opts=opts)
1777 join(a), join(b), revs, opts=opts)
1777 if header and (text or len(header) > 1):
1778 if header and (text or len(header) > 1):
1778 yield ''.join(header)
1779 yield ''.join(header)
1779 if text:
1780 if text:
1780 yield text
1781 yield text
1781
1782
1782 def diffstatsum(stats):
1783 def diffstatsum(stats):
1783 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
1784 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
1784 for f, a, r, b in stats:
1785 for f, a, r, b in stats:
1785 maxfile = max(maxfile, encoding.colwidth(f))
1786 maxfile = max(maxfile, encoding.colwidth(f))
1786 maxtotal = max(maxtotal, a + r)
1787 maxtotal = max(maxtotal, a + r)
1787 addtotal += a
1788 addtotal += a
1788 removetotal += r
1789 removetotal += r
1789 binary = binary or b
1790 binary = binary or b
1790
1791
1791 return maxfile, maxtotal, addtotal, removetotal, binary
1792 return maxfile, maxtotal, addtotal, removetotal, binary
1792
1793
1793 def diffstatdata(lines):
1794 def diffstatdata(lines):
1794 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1795 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1795
1796
1796 results = []
1797 results = []
1797 filename, adds, removes, isbinary = None, 0, 0, False
1798 filename, adds, removes, isbinary = None, 0, 0, False
1798
1799
1799 def addresult():
1800 def addresult():
1800 if filename:
1801 if filename:
1801 results.append((filename, adds, removes, isbinary))
1802 results.append((filename, adds, removes, isbinary))
1802
1803
1803 for line in lines:
1804 for line in lines:
1804 if line.startswith('diff'):
1805 if line.startswith('diff'):
1805 addresult()
1806 addresult()
1806 # set numbers to 0 anyway when starting new file
1807 # set numbers to 0 anyway when starting new file
1807 adds, removes, isbinary = 0, 0, False
1808 adds, removes, isbinary = 0, 0, False
1808 if line.startswith('diff --git'):
1809 if line.startswith('diff --git'):
1809 filename = gitre.search(line).group(1)
1810 filename = gitre.search(line).group(1)
1810 elif line.startswith('diff -r'):
1811 elif line.startswith('diff -r'):
1811 # format: "diff -r ... -r ... filename"
1812 # format: "diff -r ... -r ... filename"
1812 filename = diffre.search(line).group(1)
1813 filename = diffre.search(line).group(1)
1813 elif line.startswith('+') and not line.startswith('+++ '):
1814 elif line.startswith('+') and not line.startswith('+++ '):
1814 adds += 1
1815 adds += 1
1815 elif line.startswith('-') and not line.startswith('--- '):
1816 elif line.startswith('-') and not line.startswith('--- '):
1816 removes += 1
1817 removes += 1
1817 elif (line.startswith('GIT binary patch') or
1818 elif (line.startswith('GIT binary patch') or
1818 line.startswith('Binary file')):
1819 line.startswith('Binary file')):
1819 isbinary = True
1820 isbinary = True
1820 addresult()
1821 addresult()
1821 return results
1822 return results
1822
1823
1823 def diffstat(lines, width=80, git=False):
1824 def diffstat(lines, width=80, git=False):
1824 output = []
1825 output = []
1825 stats = diffstatdata(lines)
1826 stats = diffstatdata(lines)
1826 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
1827 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
1827
1828
1828 countwidth = len(str(maxtotal))
1829 countwidth = len(str(maxtotal))
1829 if hasbinary and countwidth < 3:
1830 if hasbinary and countwidth < 3:
1830 countwidth = 3
1831 countwidth = 3
1831 graphwidth = width - countwidth - maxname - 6
1832 graphwidth = width - countwidth - maxname - 6
1832 if graphwidth < 10:
1833 if graphwidth < 10:
1833 graphwidth = 10
1834 graphwidth = 10
1834
1835
1835 def scale(i):
1836 def scale(i):
1836 if maxtotal <= graphwidth:
1837 if maxtotal <= graphwidth:
1837 return i
1838 return i
1838 # If diffstat runs out of room it doesn't print anything,
1839 # If diffstat runs out of room it doesn't print anything,
1839 # which isn't very useful, so always print at least one + or -
1840 # which isn't very useful, so always print at least one + or -
1840 # if there were at least some changes.
1841 # if there were at least some changes.
1841 return max(i * graphwidth // maxtotal, int(bool(i)))
1842 return max(i * graphwidth // maxtotal, int(bool(i)))
1842
1843
1843 for filename, adds, removes, isbinary in stats:
1844 for filename, adds, removes, isbinary in stats:
1844 if isbinary:
1845 if isbinary:
1845 count = 'Bin'
1846 count = 'Bin'
1846 else:
1847 else:
1847 count = adds + removes
1848 count = adds + removes
1848 pluses = '+' * scale(adds)
1849 pluses = '+' * scale(adds)
1849 minuses = '-' * scale(removes)
1850 minuses = '-' * scale(removes)
1850 output.append(' %s%s | %*s %s%s\n' %
1851 output.append(' %s%s | %*s %s%s\n' %
1851 (filename, ' ' * (maxname - encoding.colwidth(filename)),
1852 (filename, ' ' * (maxname - encoding.colwidth(filename)),
1852 countwidth, count, pluses, minuses))
1853 countwidth, count, pluses, minuses))
1853
1854
1854 if stats:
1855 if stats:
1855 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1856 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1856 % (len(stats), totaladds, totalremoves))
1857 % (len(stats), totaladds, totalremoves))
1857
1858
1858 return ''.join(output)
1859 return ''.join(output)
1859
1860
1860 def diffstatui(*args, **kw):
1861 def diffstatui(*args, **kw):
1861 '''like diffstat(), but yields 2-tuples of (output, label) for
1862 '''like diffstat(), but yields 2-tuples of (output, label) for
1862 ui.write()
1863 ui.write()
1863 '''
1864 '''
1864
1865
1865 for line in diffstat(*args, **kw).splitlines():
1866 for line in diffstat(*args, **kw).splitlines():
1866 if line and line[-1] in '+-':
1867 if line and line[-1] in '+-':
1867 name, graph = line.rsplit(' ', 1)
1868 name, graph = line.rsplit(' ', 1)
1868 yield (name + ' ', '')
1869 yield (name + ' ', '')
1869 m = re.search(r'\++', graph)
1870 m = re.search(r'\++', graph)
1870 if m:
1871 if m:
1871 yield (m.group(0), 'diffstat.inserted')
1872 yield (m.group(0), 'diffstat.inserted')
1872 m = re.search(r'-+', graph)
1873 m = re.search(r'-+', graph)
1873 if m:
1874 if m:
1874 yield (m.group(0), 'diffstat.deleted')
1875 yield (m.group(0), 'diffstat.deleted')
1875 else:
1876 else:
1876 yield (line, '')
1877 yield (line, '')
1877 yield ('\n', '')
1878 yield ('\n', '')
@@ -1,998 +1,1074 b''
1 $ "$TESTDIR/hghave" unix-permissions || exit 80
1 $ "$TESTDIR/hghave" unix-permissions || exit 80
2
2
3 $ hg init a
3 $ hg init a
4 $ mkdir a/d1
4 $ mkdir a/d1
5 $ mkdir a/d1/d2
5 $ mkdir a/d1/d2
6 $ echo line 1 > a/a
6 $ echo line 1 > a/a
7 $ echo line 1 > a/d1/d2/a
7 $ echo line 1 > a/d1/d2/a
8 $ hg --cwd a ci -Ama
8 $ hg --cwd a ci -Ama
9 adding a
9 adding a
10 adding d1/d2/a
10 adding d1/d2/a
11
11
12 $ echo line 2 >> a/a
12 $ echo line 2 >> a/a
13 $ hg --cwd a ci -u someone -d '1 0' -m'second change'
13 $ hg --cwd a ci -u someone -d '1 0' -m'second change'
14
14
15 import with no args:
15 import with no args:
16
16
17 $ hg --cwd a import
17 $ hg --cwd a import
18 abort: need at least one patch to import
18 abort: need at least one patch to import
19 [255]
19 [255]
20
20
21 generate patches for the test
21 generate patches for the test
22
22
23 $ hg --cwd a export tip > exported-tip.patch
23 $ hg --cwd a export tip > exported-tip.patch
24 $ hg --cwd a diff -r0:1 > diffed-tip.patch
24 $ hg --cwd a diff -r0:1 > diffed-tip.patch
25
25
26
26
27 import exported patch
27 import exported patch
28
28
29 $ hg clone -r0 a b
29 $ hg clone -r0 a b
30 adding changesets
30 adding changesets
31 adding manifests
31 adding manifests
32 adding file changes
32 adding file changes
33 added 1 changesets with 2 changes to 2 files
33 added 1 changesets with 2 changes to 2 files
34 updating to branch default
34 updating to branch default
35 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
35 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 $ hg --cwd b import ../exported-tip.patch
36 $ hg --cwd b import ../exported-tip.patch
37 applying ../exported-tip.patch
37 applying ../exported-tip.patch
38
38
39 message and committer should be same
39 message and committer should be same
40
40
41 $ hg --cwd b tip
41 $ hg --cwd b tip
42 changeset: 1:1d4bd90af0e4
42 changeset: 1:1d4bd90af0e4
43 tag: tip
43 tag: tip
44 user: someone
44 user: someone
45 date: Thu Jan 01 00:00:01 1970 +0000
45 date: Thu Jan 01 00:00:01 1970 +0000
46 summary: second change
46 summary: second change
47
47
48 $ rm -r b
48 $ rm -r b
49
49
50
50
51 import exported patch with external patcher
51 import exported patch with external patcher
52
52
53 $ cat > dummypatch.py <<EOF
53 $ cat > dummypatch.py <<EOF
54 > print 'patching file a'
54 > print 'patching file a'
55 > file('a', 'wb').write('line2\n')
55 > file('a', 'wb').write('line2\n')
56 > EOF
56 > EOF
57 $ chmod +x dummypatch.py
57 $ chmod +x dummypatch.py
58 $ hg clone -r0 a b
58 $ hg clone -r0 a b
59 adding changesets
59 adding changesets
60 adding manifests
60 adding manifests
61 adding file changes
61 adding file changes
62 added 1 changesets with 2 changes to 2 files
62 added 1 changesets with 2 changes to 2 files
63 updating to branch default
63 updating to branch default
64 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
64 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
65 $ hg --config ui.patch='python ../dummypatch.py' --cwd b import ../exported-tip.patch
65 $ hg --config ui.patch='python ../dummypatch.py' --cwd b import ../exported-tip.patch
66 applying ../exported-tip.patch
66 applying ../exported-tip.patch
67 $ cat b/a
67 $ cat b/a
68 line2
68 line2
69 $ rm -r b
69 $ rm -r b
70
70
71
71
72 import of plain diff should fail without message
72 import of plain diff should fail without message
73
73
74 $ hg clone -r0 a b
74 $ hg clone -r0 a b
75 adding changesets
75 adding changesets
76 adding manifests
76 adding manifests
77 adding file changes
77 adding file changes
78 added 1 changesets with 2 changes to 2 files
78 added 1 changesets with 2 changes to 2 files
79 updating to branch default
79 updating to branch default
80 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
80 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
81 $ hg --cwd b import ../diffed-tip.patch
81 $ hg --cwd b import ../diffed-tip.patch
82 applying ../diffed-tip.patch
82 applying ../diffed-tip.patch
83 abort: empty commit message
83 abort: empty commit message
84 [255]
84 [255]
85 $ rm -r b
85 $ rm -r b
86
86
87
87
88 import of plain diff should be ok with message
88 import of plain diff should be ok with message
89
89
90 $ hg clone -r0 a b
90 $ hg clone -r0 a b
91 adding changesets
91 adding changesets
92 adding manifests
92 adding manifests
93 adding file changes
93 adding file changes
94 added 1 changesets with 2 changes to 2 files
94 added 1 changesets with 2 changes to 2 files
95 updating to branch default
95 updating to branch default
96 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
96 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
97 $ hg --cwd b import -mpatch ../diffed-tip.patch
97 $ hg --cwd b import -mpatch ../diffed-tip.patch
98 applying ../diffed-tip.patch
98 applying ../diffed-tip.patch
99 $ rm -r b
99 $ rm -r b
100
100
101
101
102 import of plain diff with specific date and user
102 import of plain diff with specific date and user
103
103
104 $ hg clone -r0 a b
104 $ hg clone -r0 a b
105 adding changesets
105 adding changesets
106 adding manifests
106 adding manifests
107 adding file changes
107 adding file changes
108 added 1 changesets with 2 changes to 2 files
108 added 1 changesets with 2 changes to 2 files
109 updating to branch default
109 updating to branch default
110 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
110 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
111 $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch
111 $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch
112 applying ../diffed-tip.patch
112 applying ../diffed-tip.patch
113 $ hg -R b tip -pv
113 $ hg -R b tip -pv
114 changeset: 1:ca68f19f3a40
114 changeset: 1:ca68f19f3a40
115 tag: tip
115 tag: tip
116 user: user@nowhere.net
116 user: user@nowhere.net
117 date: Thu Jan 01 00:00:01 1970 +0000
117 date: Thu Jan 01 00:00:01 1970 +0000
118 files: a
118 files: a
119 description:
119 description:
120 patch
120 patch
121
121
122
122
123 diff -r 80971e65b431 -r ca68f19f3a40 a
123 diff -r 80971e65b431 -r ca68f19f3a40 a
124 --- a/a Thu Jan 01 00:00:00 1970 +0000
124 --- a/a Thu Jan 01 00:00:00 1970 +0000
125 +++ b/a Thu Jan 01 00:00:01 1970 +0000
125 +++ b/a Thu Jan 01 00:00:01 1970 +0000
126 @@ -1,1 +1,2 @@
126 @@ -1,1 +1,2 @@
127 line 1
127 line 1
128 +line 2
128 +line 2
129
129
130 $ rm -r b
130 $ rm -r b
131
131
132
132
133 import of plain diff should be ok with --no-commit
133 import of plain diff should be ok with --no-commit
134
134
135 $ hg clone -r0 a b
135 $ hg clone -r0 a b
136 adding changesets
136 adding changesets
137 adding manifests
137 adding manifests
138 adding file changes
138 adding file changes
139 added 1 changesets with 2 changes to 2 files
139 added 1 changesets with 2 changes to 2 files
140 updating to branch default
140 updating to branch default
141 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
141 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
142 $ hg --cwd b import --no-commit ../diffed-tip.patch
142 $ hg --cwd b import --no-commit ../diffed-tip.patch
143 applying ../diffed-tip.patch
143 applying ../diffed-tip.patch
144 $ hg --cwd b diff --nodates
144 $ hg --cwd b diff --nodates
145 diff -r 80971e65b431 a
145 diff -r 80971e65b431 a
146 --- a/a
146 --- a/a
147 +++ b/a
147 +++ b/a
148 @@ -1,1 +1,2 @@
148 @@ -1,1 +1,2 @@
149 line 1
149 line 1
150 +line 2
150 +line 2
151 $ rm -r b
151 $ rm -r b
152
152
153
153
154 import of malformed plain diff should fail
154 import of malformed plain diff should fail
155
155
156 $ hg clone -r0 a b
156 $ hg clone -r0 a b
157 adding changesets
157 adding changesets
158 adding manifests
158 adding manifests
159 adding file changes
159 adding file changes
160 added 1 changesets with 2 changes to 2 files
160 added 1 changesets with 2 changes to 2 files
161 updating to branch default
161 updating to branch default
162 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
162 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
163 $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch
163 $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch
164 $ hg --cwd b import -mpatch ../broken.patch
164 $ hg --cwd b import -mpatch ../broken.patch
165 applying ../broken.patch
165 applying ../broken.patch
166 abort: bad hunk #1
166 abort: bad hunk #1
167 [255]
167 [255]
168 $ rm -r b
168 $ rm -r b
169
169
170
170
171 hg -R repo import
171 hg -R repo import
172 put the clone in a subdir - having a directory named "a"
172 put the clone in a subdir - having a directory named "a"
173 used to hide a bug.
173 used to hide a bug.
174
174
175 $ mkdir dir
175 $ mkdir dir
176 $ hg clone -r0 a dir/b
176 $ hg clone -r0 a dir/b
177 adding changesets
177 adding changesets
178 adding manifests
178 adding manifests
179 adding file changes
179 adding file changes
180 added 1 changesets with 2 changes to 2 files
180 added 1 changesets with 2 changes to 2 files
181 updating to branch default
181 updating to branch default
182 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
182 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
183 $ cd dir
183 $ cd dir
184 $ hg -R b import ../exported-tip.patch
184 $ hg -R b import ../exported-tip.patch
185 applying ../exported-tip.patch
185 applying ../exported-tip.patch
186 $ cd ..
186 $ cd ..
187 $ rm -r dir
187 $ rm -r dir
188
188
189
189
190 import from stdin
190 import from stdin
191
191
192 $ hg clone -r0 a b
192 $ hg clone -r0 a b
193 adding changesets
193 adding changesets
194 adding manifests
194 adding manifests
195 adding file changes
195 adding file changes
196 added 1 changesets with 2 changes to 2 files
196 added 1 changesets with 2 changes to 2 files
197 updating to branch default
197 updating to branch default
198 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
198 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
199 $ hg --cwd b import - < exported-tip.patch
199 $ hg --cwd b import - < exported-tip.patch
200 applying patch from stdin
200 applying patch from stdin
201 $ rm -r b
201 $ rm -r b
202
202
203
203
204 import two patches in one stream
204 import two patches in one stream
205
205
206 $ hg init b
206 $ hg init b
207 $ hg --cwd a export 0:tip | hg --cwd b import -
207 $ hg --cwd a export 0:tip | hg --cwd b import -
208 applying patch from stdin
208 applying patch from stdin
209 $ hg --cwd a id
209 $ hg --cwd a id
210 1d4bd90af0e4 tip
210 1d4bd90af0e4 tip
211 $ hg --cwd b id
211 $ hg --cwd b id
212 1d4bd90af0e4 tip
212 1d4bd90af0e4 tip
213 $ rm -r b
213 $ rm -r b
214
214
215
215
216 override commit message
216 override commit message
217
217
218 $ hg clone -r0 a b
218 $ hg clone -r0 a b
219 adding changesets
219 adding changesets
220 adding manifests
220 adding manifests
221 adding file changes
221 adding file changes
222 added 1 changesets with 2 changes to 2 files
222 added 1 changesets with 2 changes to 2 files
223 updating to branch default
223 updating to branch default
224 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
224 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
225 $ hg --cwd b import -m 'override' - < exported-tip.patch
225 $ hg --cwd b import -m 'override' - < exported-tip.patch
226 applying patch from stdin
226 applying patch from stdin
227 $ hg --cwd b tip | grep override
227 $ hg --cwd b tip | grep override
228 summary: override
228 summary: override
229 $ rm -r b
229 $ rm -r b
230
230
231 $ cat > mkmsg.py <<EOF
231 $ cat > mkmsg.py <<EOF
232 > import email.Message, sys
232 > import email.Message, sys
233 > msg = email.Message.Message()
233 > msg = email.Message.Message()
234 > patch = open(sys.argv[1], 'rb').read()
234 > patch = open(sys.argv[1], 'rb').read()
235 > msg.set_payload('email commit message\n' + patch)
235 > msg.set_payload('email commit message\n' + patch)
236 > msg['Subject'] = 'email patch'
236 > msg['Subject'] = 'email patch'
237 > msg['From'] = 'email patcher'
237 > msg['From'] = 'email patcher'
238 > file(sys.argv[2], 'wb').write(msg.as_string())
238 > file(sys.argv[2], 'wb').write(msg.as_string())
239 > EOF
239 > EOF
240
240
241
241
242 plain diff in email, subject, message body
242 plain diff in email, subject, message body
243
243
244 $ hg clone -r0 a b
244 $ hg clone -r0 a b
245 adding changesets
245 adding changesets
246 adding manifests
246 adding manifests
247 adding file changes
247 adding file changes
248 added 1 changesets with 2 changes to 2 files
248 added 1 changesets with 2 changes to 2 files
249 updating to branch default
249 updating to branch default
250 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
250 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
251 $ python mkmsg.py diffed-tip.patch msg.patch
251 $ python mkmsg.py diffed-tip.patch msg.patch
252 $ hg --cwd b import ../msg.patch
252 $ hg --cwd b import ../msg.patch
253 applying ../msg.patch
253 applying ../msg.patch
254 $ hg --cwd b tip | grep email
254 $ hg --cwd b tip | grep email
255 user: email patcher
255 user: email patcher
256 summary: email patch
256 summary: email patch
257 $ rm -r b
257 $ rm -r b
258
258
259
259
260 plain diff in email, no subject, message body
260 plain diff in email, no subject, message body
261
261
262 $ hg clone -r0 a b
262 $ hg clone -r0 a b
263 adding changesets
263 adding changesets
264 adding manifests
264 adding manifests
265 adding file changes
265 adding file changes
266 added 1 changesets with 2 changes to 2 files
266 added 1 changesets with 2 changes to 2 files
267 updating to branch default
267 updating to branch default
268 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
268 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
269 $ grep -v '^Subject:' msg.patch | hg --cwd b import -
269 $ grep -v '^Subject:' msg.patch | hg --cwd b import -
270 applying patch from stdin
270 applying patch from stdin
271 $ rm -r b
271 $ rm -r b
272
272
273
273
274 plain diff in email, subject, no message body
274 plain diff in email, subject, no message body
275
275
276 $ hg clone -r0 a b
276 $ hg clone -r0 a b
277 adding changesets
277 adding changesets
278 adding manifests
278 adding manifests
279 adding file changes
279 adding file changes
280 added 1 changesets with 2 changes to 2 files
280 added 1 changesets with 2 changes to 2 files
281 updating to branch default
281 updating to branch default
282 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
282 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
283 $ grep -v '^email ' msg.patch | hg --cwd b import -
283 $ grep -v '^email ' msg.patch | hg --cwd b import -
284 applying patch from stdin
284 applying patch from stdin
285 $ rm -r b
285 $ rm -r b
286
286
287
287
288 plain diff in email, no subject, no message body, should fail
288 plain diff in email, no subject, no message body, should fail
289
289
290 $ hg clone -r0 a b
290 $ hg clone -r0 a b
291 adding changesets
291 adding changesets
292 adding manifests
292 adding manifests
293 adding file changes
293 adding file changes
294 added 1 changesets with 2 changes to 2 files
294 added 1 changesets with 2 changes to 2 files
295 updating to branch default
295 updating to branch default
296 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
296 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
297 $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
297 $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
298 applying patch from stdin
298 applying patch from stdin
299 abort: empty commit message
299 abort: empty commit message
300 [255]
300 [255]
301 $ rm -r b
301 $ rm -r b
302
302
303
303
304 hg export in email, should use patch header
304 hg export in email, should use patch header
305
305
306 $ hg clone -r0 a b
306 $ hg clone -r0 a b
307 adding changesets
307 adding changesets
308 adding manifests
308 adding manifests
309 adding file changes
309 adding file changes
310 added 1 changesets with 2 changes to 2 files
310 added 1 changesets with 2 changes to 2 files
311 updating to branch default
311 updating to branch default
312 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
312 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
313 $ python mkmsg.py exported-tip.patch msg.patch
313 $ python mkmsg.py exported-tip.patch msg.patch
314 $ cat msg.patch | hg --cwd b import -
314 $ cat msg.patch | hg --cwd b import -
315 applying patch from stdin
315 applying patch from stdin
316 $ hg --cwd b tip | grep second
316 $ hg --cwd b tip | grep second
317 summary: second change
317 summary: second change
318 $ rm -r b
318 $ rm -r b
319
319
320
320
321 subject: duplicate detection, removal of [PATCH]
321 subject: duplicate detection, removal of [PATCH]
322 The '---' tests the gitsendmail handling without proper mail headers
322 The '---' tests the gitsendmail handling without proper mail headers
323
323
324 $ cat > mkmsg2.py <<EOF
324 $ cat > mkmsg2.py <<EOF
325 > import email.Message, sys
325 > import email.Message, sys
326 > msg = email.Message.Message()
326 > msg = email.Message.Message()
327 > patch = open(sys.argv[1], 'rb').read()
327 > patch = open(sys.argv[1], 'rb').read()
328 > msg.set_payload('email patch\n\nnext line\n---\n' + patch)
328 > msg.set_payload('email patch\n\nnext line\n---\n' + patch)
329 > msg['Subject'] = '[PATCH] email patch'
329 > msg['Subject'] = '[PATCH] email patch'
330 > msg['From'] = 'email patcher'
330 > msg['From'] = 'email patcher'
331 > file(sys.argv[2], 'wb').write(msg.as_string())
331 > file(sys.argv[2], 'wb').write(msg.as_string())
332 > EOF
332 > EOF
333
333
334
334
335 plain diff in email, [PATCH] subject, message body with subject
335 plain diff in email, [PATCH] subject, message body with subject
336
336
337 $ hg clone -r0 a b
337 $ hg clone -r0 a b
338 adding changesets
338 adding changesets
339 adding manifests
339 adding manifests
340 adding file changes
340 adding file changes
341 added 1 changesets with 2 changes to 2 files
341 added 1 changesets with 2 changes to 2 files
342 updating to branch default
342 updating to branch default
343 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
343 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
344 $ python mkmsg2.py diffed-tip.patch msg.patch
344 $ python mkmsg2.py diffed-tip.patch msg.patch
345 $ cat msg.patch | hg --cwd b import -
345 $ cat msg.patch | hg --cwd b import -
346 applying patch from stdin
346 applying patch from stdin
347 $ hg --cwd b tip --template '{desc}\n'
347 $ hg --cwd b tip --template '{desc}\n'
348 email patch
348 email patch
349
349
350 next line
350 next line
351 ---
351 ---
352 $ rm -r b
352 $ rm -r b
353
353
354
354
355 Issue963: Parent of working dir incorrect after import of multiple
355 Issue963: Parent of working dir incorrect after import of multiple
356 patches and rollback
356 patches and rollback
357
357
358 We weren't backing up the correct dirstate file when importing many
358 We weren't backing up the correct dirstate file when importing many
359 patches: import patch1 patch2; rollback
359 patches: import patch1 patch2; rollback
360
360
361 $ echo line 3 >> a/a
361 $ echo line 3 >> a/a
362 $ hg --cwd a ci -m'third change'
362 $ hg --cwd a ci -m'third change'
363 $ hg --cwd a export -o '../patch%R' 1 2
363 $ hg --cwd a export -o '../patch%R' 1 2
364 $ hg clone -qr0 a b
364 $ hg clone -qr0 a b
365 $ hg --cwd b parents --template 'parent: {rev}\n'
365 $ hg --cwd b parents --template 'parent: {rev}\n'
366 parent: 0
366 parent: 0
367 $ hg --cwd b import -v ../patch1 ../patch2
367 $ hg --cwd b import -v ../patch1 ../patch2
368 applying ../patch1
368 applying ../patch1
369 patching file a
369 patching file a
370 a
370 a
371 created 1d4bd90af0e4
371 created 1d4bd90af0e4
372 applying ../patch2
372 applying ../patch2
373 patching file a
373 patching file a
374 a
374 a
375 created 6d019af21222
375 created 6d019af21222
376 $ hg --cwd b rollback
376 $ hg --cwd b rollback
377 repository tip rolled back to revision 0 (undo import)
377 repository tip rolled back to revision 0 (undo import)
378 working directory now based on revision 0
378 working directory now based on revision 0
379 $ hg --cwd b parents --template 'parent: {rev}\n'
379 $ hg --cwd b parents --template 'parent: {rev}\n'
380 parent: 0
380 parent: 0
381 $ rm -r b
381 $ rm -r b
382
382
383
383
384 importing a patch in a subdirectory failed at the commit stage
384 importing a patch in a subdirectory failed at the commit stage
385
385
386 $ echo line 2 >> a/d1/d2/a
386 $ echo line 2 >> a/d1/d2/a
387 $ hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change'
387 $ hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change'
388
388
389 hg import in a subdirectory
389 hg import in a subdirectory
390
390
391 $ hg clone -r0 a b
391 $ hg clone -r0 a b
392 adding changesets
392 adding changesets
393 adding manifests
393 adding manifests
394 adding file changes
394 adding file changes
395 added 1 changesets with 2 changes to 2 files
395 added 1 changesets with 2 changes to 2 files
396 updating to branch default
396 updating to branch default
397 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
397 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
398 $ hg --cwd a export tip > tmp
398 $ hg --cwd a export tip > tmp
399 $ sed -e 's/d1\/d2\///' < tmp > subdir-tip.patch
399 $ sed -e 's/d1\/d2\///' < tmp > subdir-tip.patch
400 $ dir=`pwd`
400 $ dir=`pwd`
401 $ cd b/d1/d2 2>&1 > /dev/null
401 $ cd b/d1/d2 2>&1 > /dev/null
402 $ hg import ../../../subdir-tip.patch
402 $ hg import ../../../subdir-tip.patch
403 applying ../../../subdir-tip.patch
403 applying ../../../subdir-tip.patch
404 $ cd "$dir"
404 $ cd "$dir"
405
405
406 message should be 'subdir change'
406 message should be 'subdir change'
407 committer should be 'someoneelse'
407 committer should be 'someoneelse'
408
408
409 $ hg --cwd b tip
409 $ hg --cwd b tip
410 changeset: 1:3577f5aea227
410 changeset: 1:3577f5aea227
411 tag: tip
411 tag: tip
412 user: someoneelse
412 user: someoneelse
413 date: Thu Jan 01 00:00:01 1970 +0000
413 date: Thu Jan 01 00:00:01 1970 +0000
414 summary: subdir change
414 summary: subdir change
415
415
416
416
417 should be empty
417 should be empty
418
418
419 $ hg --cwd b status
419 $ hg --cwd b status
420
420
421
421
422 Test fuzziness (ambiguous patch location, fuzz=2)
422 Test fuzziness (ambiguous patch location, fuzz=2)
423
423
424 $ hg init fuzzy
424 $ hg init fuzzy
425 $ cd fuzzy
425 $ cd fuzzy
426 $ echo line1 > a
426 $ echo line1 > a
427 $ echo line0 >> a
427 $ echo line0 >> a
428 $ echo line3 >> a
428 $ echo line3 >> a
429 $ hg ci -Am adda
429 $ hg ci -Am adda
430 adding a
430 adding a
431 $ echo line1 > a
431 $ echo line1 > a
432 $ echo line2 >> a
432 $ echo line2 >> a
433 $ echo line0 >> a
433 $ echo line0 >> a
434 $ echo line3 >> a
434 $ echo line3 >> a
435 $ hg ci -m change a
435 $ hg ci -m change a
436 $ hg export tip > fuzzy-tip.patch
436 $ hg export tip > fuzzy-tip.patch
437 $ hg up -C 0
437 $ hg up -C 0
438 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
438 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
439 $ echo line1 > a
439 $ echo line1 > a
440 $ echo line0 >> a
440 $ echo line0 >> a
441 $ echo line1 >> a
441 $ echo line1 >> a
442 $ echo line0 >> a
442 $ echo line0 >> a
443 $ hg ci -m brancha
443 $ hg ci -m brancha
444 created new head
444 created new head
445 $ hg import --no-commit -v fuzzy-tip.patch
445 $ hg import --no-commit -v fuzzy-tip.patch
446 applying fuzzy-tip.patch
446 applying fuzzy-tip.patch
447 patching file a
447 patching file a
448 Hunk #1 succeeded at 1 with fuzz 2 (offset -2 lines).
448 Hunk #1 succeeded at 1 with fuzz 2 (offset -2 lines).
449 applied to working directory
449 applied to working directory
450 $ hg revert -a
450 $ hg revert -a
451 reverting a
451 reverting a
452
452
453
453
454 import with --no-commit should have written .hg/last-message.txt
454 import with --no-commit should have written .hg/last-message.txt
455
455
456 $ cat .hg/last-message.txt
456 $ cat .hg/last-message.txt
457 change (no-eol)
457 change (no-eol)
458
458
459
459
460 test fuzziness with eol=auto
460 test fuzziness with eol=auto
461
461
462 $ hg --config patch.eol=auto import --no-commit -v fuzzy-tip.patch
462 $ hg --config patch.eol=auto import --no-commit -v fuzzy-tip.patch
463 applying fuzzy-tip.patch
463 applying fuzzy-tip.patch
464 patching file a
464 patching file a
465 Hunk #1 succeeded at 1 with fuzz 2 (offset -2 lines).
465 Hunk #1 succeeded at 1 with fuzz 2 (offset -2 lines).
466 applied to working directory
466 applied to working directory
467 $ cd ..
467 $ cd ..
468
468
469
469
470 Test hunk touching empty files (issue906)
470 Test hunk touching empty files (issue906)
471
471
472 $ hg init empty
472 $ hg init empty
473 $ cd empty
473 $ cd empty
474 $ touch a
474 $ touch a
475 $ touch b1
475 $ touch b1
476 $ touch c1
476 $ touch c1
477 $ echo d > d
477 $ echo d > d
478 $ hg ci -Am init
478 $ hg ci -Am init
479 adding a
479 adding a
480 adding b1
480 adding b1
481 adding c1
481 adding c1
482 adding d
482 adding d
483 $ echo a > a
483 $ echo a > a
484 $ echo b > b1
484 $ echo b > b1
485 $ hg mv b1 b2
485 $ hg mv b1 b2
486 $ echo c > c1
486 $ echo c > c1
487 $ hg copy c1 c2
487 $ hg copy c1 c2
488 $ rm d
488 $ rm d
489 $ touch d
489 $ touch d
490 $ hg diff --git
490 $ hg diff --git
491 diff --git a/a b/a
491 diff --git a/a b/a
492 --- a/a
492 --- a/a
493 +++ b/a
493 +++ b/a
494 @@ -0,0 +1,1 @@
494 @@ -0,0 +1,1 @@
495 +a
495 +a
496 diff --git a/b1 b/b2
496 diff --git a/b1 b/b2
497 rename from b1
497 rename from b1
498 rename to b2
498 rename to b2
499 --- a/b1
499 --- a/b1
500 +++ b/b2
500 +++ b/b2
501 @@ -0,0 +1,1 @@
501 @@ -0,0 +1,1 @@
502 +b
502 +b
503 diff --git a/c1 b/c1
503 diff --git a/c1 b/c1
504 --- a/c1
504 --- a/c1
505 +++ b/c1
505 +++ b/c1
506 @@ -0,0 +1,1 @@
506 @@ -0,0 +1,1 @@
507 +c
507 +c
508 diff --git a/c1 b/c2
508 diff --git a/c1 b/c2
509 copy from c1
509 copy from c1
510 copy to c2
510 copy to c2
511 --- a/c1
511 --- a/c1
512 +++ b/c2
512 +++ b/c2
513 @@ -0,0 +1,1 @@
513 @@ -0,0 +1,1 @@
514 +c
514 +c
515 diff --git a/d b/d
515 diff --git a/d b/d
516 --- a/d
516 --- a/d
517 +++ b/d
517 +++ b/d
518 @@ -1,1 +0,0 @@
518 @@ -1,1 +0,0 @@
519 -d
519 -d
520 $ hg ci -m empty
520 $ hg ci -m empty
521 $ hg export --git tip > empty.diff
521 $ hg export --git tip > empty.diff
522 $ hg up -C 0
522 $ hg up -C 0
523 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
523 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
524 $ hg import empty.diff
524 $ hg import empty.diff
525 applying empty.diff
525 applying empty.diff
526 $ for name in a b1 b2 c1 c2 d; do
526 $ for name in a b1 b2 c1 c2 d; do
527 > echo % $name file
527 > echo % $name file
528 > test -f $name && cat $name
528 > test -f $name && cat $name
529 > done
529 > done
530 % a file
530 % a file
531 a
531 a
532 % b1 file
532 % b1 file
533 % b2 file
533 % b2 file
534 b
534 b
535 % c1 file
535 % c1 file
536 c
536 c
537 % c2 file
537 % c2 file
538 c
538 c
539 % d file
539 % d file
540 $ cd ..
540 $ cd ..
541
541
542
542
543 Test importing a patch ending with a binary file removal
543 Test importing a patch ending with a binary file removal
544
544
545 $ hg init binaryremoval
545 $ hg init binaryremoval
546 $ cd binaryremoval
546 $ cd binaryremoval
547 $ echo a > a
547 $ echo a > a
548 $ python -c "file('b', 'wb').write('a\x00b')"
548 $ python -c "file('b', 'wb').write('a\x00b')"
549 $ hg ci -Am addall
549 $ hg ci -Am addall
550 adding a
550 adding a
551 adding b
551 adding b
552 $ hg rm a
552 $ hg rm a
553 $ hg rm b
553 $ hg rm b
554 $ hg st
554 $ hg st
555 R a
555 R a
556 R b
556 R b
557 $ hg ci -m remove
557 $ hg ci -m remove
558 $ hg export --git . > remove.diff
558 $ hg export --git . > remove.diff
559 $ cat remove.diff | grep git
559 $ cat remove.diff | grep git
560 diff --git a/a b/a
560 diff --git a/a b/a
561 diff --git a/b b/b
561 diff --git a/b b/b
562 $ hg up -C 0
562 $ hg up -C 0
563 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
563 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
564 $ hg import remove.diff
564 $ hg import remove.diff
565 applying remove.diff
565 applying remove.diff
566 $ hg manifest
566 $ hg manifest
567 $ cd ..
567 $ cd ..
568
568
569
569
570 Issue927: test update+rename with common name
570 Issue927: test update+rename with common name
571
571
572 $ hg init t
572 $ hg init t
573 $ cd t
573 $ cd t
574 $ touch a
574 $ touch a
575 $ hg ci -Am t
575 $ hg ci -Am t
576 adding a
576 adding a
577 $ echo a > a
577 $ echo a > a
578
578
579 Here, bfile.startswith(afile)
579 Here, bfile.startswith(afile)
580
580
581 $ hg copy a a2
581 $ hg copy a a2
582 $ hg ci -m copya
582 $ hg ci -m copya
583 $ hg export --git tip > copy.diff
583 $ hg export --git tip > copy.diff
584 $ hg up -C 0
584 $ hg up -C 0
585 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
585 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
586 $ hg import copy.diff
586 $ hg import copy.diff
587 applying copy.diff
587 applying copy.diff
588
588
589 a should contain an 'a'
589 a should contain an 'a'
590
590
591 $ cat a
591 $ cat a
592 a
592 a
593
593
594 and a2 should have duplicated it
594 and a2 should have duplicated it
595
595
596 $ cat a2
596 $ cat a2
597 a
597 a
598 $ cd ..
598 $ cd ..
599
599
600
600
601 test -p0
601 test -p0
602
602
603 $ hg init p0
603 $ hg init p0
604 $ cd p0
604 $ cd p0
605 $ echo a > a
605 $ echo a > a
606 $ hg ci -Am t
606 $ hg ci -Am t
607 adding a
607 adding a
608 $ hg import -p0 - << EOF
608 $ hg import -p0 - << EOF
609 > foobar
609 > foobar
610 > --- a Sat Apr 12 22:43:58 2008 -0400
610 > --- a Sat Apr 12 22:43:58 2008 -0400
611 > +++ a Sat Apr 12 22:44:05 2008 -0400
611 > +++ a Sat Apr 12 22:44:05 2008 -0400
612 > @@ -1,1 +1,1 @@
612 > @@ -1,1 +1,1 @@
613 > -a
613 > -a
614 > +bb
614 > +bb
615 > EOF
615 > EOF
616 applying patch from stdin
616 applying patch from stdin
617 $ hg status
617 $ hg status
618 $ cat a
618 $ cat a
619 bb
619 bb
620 $ cd ..
620 $ cd ..
621
621
622
622
623 test paths outside repo root
623 test paths outside repo root
624
624
625 $ mkdir outside
625 $ mkdir outside
626 $ touch outside/foo
626 $ touch outside/foo
627 $ hg init inside
627 $ hg init inside
628 $ cd inside
628 $ cd inside
629 $ hg import - <<EOF
629 $ hg import - <<EOF
630 > diff --git a/a b/b
630 > diff --git a/a b/b
631 > rename from ../outside/foo
631 > rename from ../outside/foo
632 > rename to bar
632 > rename to bar
633 > EOF
633 > EOF
634 applying patch from stdin
634 applying patch from stdin
635 abort: path contains illegal component: ../outside/foo
635 abort: path contains illegal component: ../outside/foo
636 [255]
636 [255]
637 $ cd ..
637 $ cd ..
638
638
639
639
640 test import with similarity and git and strip (issue295 et al.)
640 test import with similarity and git and strip (issue295 et al.)
641
641
642 $ hg init sim
642 $ hg init sim
643 $ cd sim
643 $ cd sim
644 $ echo 'this is a test' > a
644 $ echo 'this is a test' > a
645 $ hg ci -Ama
645 $ hg ci -Ama
646 adding a
646 adding a
647 $ cat > ../rename.diff <<EOF
647 $ cat > ../rename.diff <<EOF
648 > diff --git a/foo/a b/foo/a
648 > diff --git a/foo/a b/foo/a
649 > deleted file mode 100644
649 > deleted file mode 100644
650 > --- a/foo/a
650 > --- a/foo/a
651 > +++ /dev/null
651 > +++ /dev/null
652 > @@ -1,1 +0,0 @@
652 > @@ -1,1 +0,0 @@
653 > -this is a test
653 > -this is a test
654 > diff --git a/foo/b b/foo/b
654 > diff --git a/foo/b b/foo/b
655 > new file mode 100644
655 > new file mode 100644
656 > --- /dev/null
656 > --- /dev/null
657 > +++ b/foo/b
657 > +++ b/foo/b
658 > @@ -0,0 +1,2 @@
658 > @@ -0,0 +1,2 @@
659 > +this is a test
659 > +this is a test
660 > +foo
660 > +foo
661 > EOF
661 > EOF
662 $ hg import --no-commit -v -s 1 ../rename.diff -p2
662 $ hg import --no-commit -v -s 1 ../rename.diff -p2
663 applying ../rename.diff
663 applying ../rename.diff
664 patching file a
664 patching file a
665 patching file b
665 patching file b
666 adding b
666 adding b
667 recording removal of a as rename to b (88% similar)
667 recording removal of a as rename to b (88% similar)
668 applied to working directory
668 applied to working directory
669 $ hg st -C
669 $ hg st -C
670 A b
670 A b
671 a
671 a
672 R a
672 R a
673 $ hg revert -a
673 $ hg revert -a
674 undeleting a
674 undeleting a
675 forgetting b
675 forgetting b
676 $ rm b
676 $ rm b
677 $ hg import --no-commit -v -s 100 ../rename.diff -p2
677 $ hg import --no-commit -v -s 100 ../rename.diff -p2
678 applying ../rename.diff
678 applying ../rename.diff
679 patching file a
679 patching file a
680 patching file b
680 patching file b
681 adding b
681 adding b
682 applied to working directory
682 applied to working directory
683 $ hg st -C
683 $ hg st -C
684 A b
684 A b
685 R a
685 R a
686 $ cd ..
686 $ cd ..
687
687
688
688
689 Issue1495: add empty file from the end of patch
689 Issue1495: add empty file from the end of patch
690
690
691 $ hg init addemptyend
691 $ hg init addemptyend
692 $ cd addemptyend
692 $ cd addemptyend
693 $ touch a
693 $ touch a
694 $ hg addremove
694 $ hg addremove
695 adding a
695 adding a
696 $ hg ci -m "commit"
696 $ hg ci -m "commit"
697 $ cat > a.patch <<EOF
697 $ cat > a.patch <<EOF
698 > add a, b
698 > add a, b
699 > diff --git a/a b/a
699 > diff --git a/a b/a
700 > --- a/a
700 > --- a/a
701 > +++ b/a
701 > +++ b/a
702 > @@ -0,0 +1,1 @@
702 > @@ -0,0 +1,1 @@
703 > +a
703 > +a
704 > diff --git a/b b/b
704 > diff --git a/b b/b
705 > new file mode 100644
705 > new file mode 100644
706 > EOF
706 > EOF
707 $ hg import --no-commit a.patch
707 $ hg import --no-commit a.patch
708 applying a.patch
708 applying a.patch
709
709
710 apply a good patch followed by an empty patch (mainly to ensure
710 apply a good patch followed by an empty patch (mainly to ensure
711 that dirstate is *not* updated when import crashes)
711 that dirstate is *not* updated when import crashes)
712 $ hg update -q -C .
712 $ hg update -q -C .
713 $ rm b
713 $ rm b
714 $ touch empty.patch
714 $ touch empty.patch
715 $ hg import a.patch empty.patch
715 $ hg import a.patch empty.patch
716 applying a.patch
716 applying a.patch
717 applying empty.patch
717 applying empty.patch
718 transaction abort!
718 transaction abort!
719 rollback completed
719 rollback completed
720 abort: empty.patch: no diffs found
720 abort: empty.patch: no diffs found
721 [255]
721 [255]
722 $ hg tip --template '{rev} {desc|firstline}\n'
722 $ hg tip --template '{rev} {desc|firstline}\n'
723 0 commit
723 0 commit
724 $ hg -q status
724 $ hg -q status
725 M a
725 M a
726 $ cd ..
726 $ cd ..
727
727
728 create file when source is not /dev/null
728 create file when source is not /dev/null
729
729
730 $ cat > create.patch <<EOF
730 $ cat > create.patch <<EOF
731 > diff -Naur proj-orig/foo proj-new/foo
731 > diff -Naur proj-orig/foo proj-new/foo
732 > --- proj-orig/foo 1969-12-31 16:00:00.000000000 -0800
732 > --- proj-orig/foo 1969-12-31 16:00:00.000000000 -0800
733 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
733 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
734 > @@ -0,0 +1,1 @@
734 > @@ -0,0 +1,1 @@
735 > +a
735 > +a
736 > EOF
736 > EOF
737
737
738 some people have patches like the following too
738 some people have patches like the following too
739
739
740 $ cat > create2.patch <<EOF
740 $ cat > create2.patch <<EOF
741 > diff -Naur proj-orig/foo proj-new/foo
741 > diff -Naur proj-orig/foo proj-new/foo
742 > --- proj-orig/foo.orig 1969-12-31 16:00:00.000000000 -0800
742 > --- proj-orig/foo.orig 1969-12-31 16:00:00.000000000 -0800
743 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
743 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
744 > @@ -0,0 +1,1 @@
744 > @@ -0,0 +1,1 @@
745 > +a
745 > +a
746 > EOF
746 > EOF
747 $ hg init oddcreate
747 $ hg init oddcreate
748 $ cd oddcreate
748 $ cd oddcreate
749 $ hg import --no-commit ../create.patch
749 $ hg import --no-commit ../create.patch
750 applying ../create.patch
750 applying ../create.patch
751 $ cat foo
751 $ cat foo
752 a
752 a
753 $ rm foo
753 $ rm foo
754 $ hg revert foo
754 $ hg revert foo
755 $ hg import --no-commit ../create2.patch
755 $ hg import --no-commit ../create2.patch
756 applying ../create2.patch
756 applying ../create2.patch
757 $ cat foo
757 $ cat foo
758 a
758 a
759
759
760
760
761 Issue1859: first line mistaken for email headers
761 Issue1859: first line mistaken for email headers
762
762
763 $ hg init emailconfusion
763 $ hg init emailconfusion
764 $ cd emailconfusion
764 $ cd emailconfusion
765 $ cat > a.patch <<EOF
765 $ cat > a.patch <<EOF
766 > module: summary
766 > module: summary
767 >
767 >
768 > description
768 > description
769 >
769 >
770 >
770 >
771 > diff -r 000000000000 -r 9b4c1e343b55 test.txt
771 > diff -r 000000000000 -r 9b4c1e343b55 test.txt
772 > --- /dev/null
772 > --- /dev/null
773 > +++ b/a
773 > +++ b/a
774 > @@ -0,0 +1,1 @@
774 > @@ -0,0 +1,1 @@
775 > +a
775 > +a
776 > EOF
776 > EOF
777 $ hg import -d '0 0' a.patch
777 $ hg import -d '0 0' a.patch
778 applying a.patch
778 applying a.patch
779 $ hg parents -v
779 $ hg parents -v
780 changeset: 0:5a681217c0ad
780 changeset: 0:5a681217c0ad
781 tag: tip
781 tag: tip
782 user: test
782 user: test
783 date: Thu Jan 01 00:00:00 1970 +0000
783 date: Thu Jan 01 00:00:00 1970 +0000
784 files: a
784 files: a
785 description:
785 description:
786 module: summary
786 module: summary
787
787
788 description
788 description
789
789
790
790
791 $ cd ..
791 $ cd ..
792
792
793
793
794 --- in commit message
794 --- in commit message
795
795
796 $ hg init commitconfusion
796 $ hg init commitconfusion
797 $ cd commitconfusion
797 $ cd commitconfusion
798 $ cat > a.patch <<EOF
798 $ cat > a.patch <<EOF
799 > module: summary
799 > module: summary
800 >
800 >
801 > --- description
801 > --- description
802 >
802 >
803 > diff --git a/a b/a
803 > diff --git a/a b/a
804 > new file mode 100644
804 > new file mode 100644
805 > --- /dev/null
805 > --- /dev/null
806 > +++ b/a
806 > +++ b/a
807 > @@ -0,0 +1,1 @@
807 > @@ -0,0 +1,1 @@
808 > +a
808 > +a
809 > EOF
809 > EOF
810 > hg import -d '0 0' a.patch
810 > hg import -d '0 0' a.patch
811 > hg parents -v
811 > hg parents -v
812 > cd ..
812 > cd ..
813 >
813 >
814 > echo '% tricky header splitting'
814 > echo '% tricky header splitting'
815 > cat > trickyheaders.patch <<EOF
815 > cat > trickyheaders.patch <<EOF
816 > From: User A <user@a>
816 > From: User A <user@a>
817 > Subject: [PATCH] from: tricky!
817 > Subject: [PATCH] from: tricky!
818 >
818 >
819 > # HG changeset patch
819 > # HG changeset patch
820 > # User User B
820 > # User User B
821 > # Date 1266264441 18000
821 > # Date 1266264441 18000
822 > # Branch stable
822 > # Branch stable
823 > # Node ID f2be6a1170ac83bf31cb4ae0bad00d7678115bc0
823 > # Node ID f2be6a1170ac83bf31cb4ae0bad00d7678115bc0
824 > # Parent 0000000000000000000000000000000000000000
824 > # Parent 0000000000000000000000000000000000000000
825 > from: tricky!
825 > from: tricky!
826 >
826 >
827 > That is not a header.
827 > That is not a header.
828 >
828 >
829 > diff -r 000000000000 -r f2be6a1170ac foo
829 > diff -r 000000000000 -r f2be6a1170ac foo
830 > --- /dev/null
830 > --- /dev/null
831 > +++ b/foo
831 > +++ b/foo
832 > @@ -0,0 +1,1 @@
832 > @@ -0,0 +1,1 @@
833 > +foo
833 > +foo
834 > EOF
834 > EOF
835 applying a.patch
835 applying a.patch
836 changeset: 0:f34d9187897d
836 changeset: 0:f34d9187897d
837 tag: tip
837 tag: tip
838 user: test
838 user: test
839 date: Thu Jan 01 00:00:00 1970 +0000
839 date: Thu Jan 01 00:00:00 1970 +0000
840 files: a
840 files: a
841 description:
841 description:
842 module: summary
842 module: summary
843
843
844
844
845 % tricky header splitting
845 % tricky header splitting
846
846
847 $ hg init trickyheaders
847 $ hg init trickyheaders
848 $ cd trickyheaders
848 $ cd trickyheaders
849 $ hg import -d '0 0' ../trickyheaders.patch
849 $ hg import -d '0 0' ../trickyheaders.patch
850 applying ../trickyheaders.patch
850 applying ../trickyheaders.patch
851 $ hg export --git tip
851 $ hg export --git tip
852 # HG changeset patch
852 # HG changeset patch
853 # User User B
853 # User User B
854 # Date 0 0
854 # Date 0 0
855 # Node ID eb56ab91903632294ac504838508cb370c0901d2
855 # Node ID eb56ab91903632294ac504838508cb370c0901d2
856 # Parent 0000000000000000000000000000000000000000
856 # Parent 0000000000000000000000000000000000000000
857 from: tricky!
857 from: tricky!
858
858
859 That is not a header.
859 That is not a header.
860
860
861 diff --git a/foo b/foo
861 diff --git a/foo b/foo
862 new file mode 100644
862 new file mode 100644
863 --- /dev/null
863 --- /dev/null
864 +++ b/foo
864 +++ b/foo
865 @@ -0,0 +1,1 @@
865 @@ -0,0 +1,1 @@
866 +foo
866 +foo
867 $ cd ..
867 $ cd ..
868
868
869
869
870 Issue2102: hg export and hg import speak different languages
870 Issue2102: hg export and hg import speak different languages
871
871
872 $ hg init issue2102
872 $ hg init issue2102
873 $ cd issue2102
873 $ cd issue2102
874 $ mkdir -p src/cmd/gc
874 $ mkdir -p src/cmd/gc
875 $ touch src/cmd/gc/mksys.bash
875 $ touch src/cmd/gc/mksys.bash
876 $ hg ci -Am init
876 $ hg ci -Am init
877 adding src/cmd/gc/mksys.bash
877 adding src/cmd/gc/mksys.bash
878 $ hg import - <<EOF
878 $ hg import - <<EOF
879 > # HG changeset patch
879 > # HG changeset patch
880 > # User Rob Pike
880 > # User Rob Pike
881 > # Date 1216685449 25200
881 > # Date 1216685449 25200
882 > # Node ID 03aa2b206f499ad6eb50e6e207b9e710d6409c98
882 > # Node ID 03aa2b206f499ad6eb50e6e207b9e710d6409c98
883 > # Parent 93d10138ad8df586827ca90b4ddb5033e21a3a84
883 > # Parent 93d10138ad8df586827ca90b4ddb5033e21a3a84
884 > help management of empty pkg and lib directories in perforce
884 > help management of empty pkg and lib directories in perforce
885 >
885 >
886 > R=gri
886 > R=gri
887 > DELTA=4 (4 added, 0 deleted, 0 changed)
887 > DELTA=4 (4 added, 0 deleted, 0 changed)
888 > OCL=13328
888 > OCL=13328
889 > CL=13328
889 > CL=13328
890 >
890 >
891 > diff --git a/lib/place-holder b/lib/place-holder
891 > diff --git a/lib/place-holder b/lib/place-holder
892 > new file mode 100644
892 > new file mode 100644
893 > --- /dev/null
893 > --- /dev/null
894 > +++ b/lib/place-holder
894 > +++ b/lib/place-holder
895 > @@ -0,0 +1,2 @@
895 > @@ -0,0 +1,2 @@
896 > +perforce does not maintain empty directories.
896 > +perforce does not maintain empty directories.
897 > +this file helps.
897 > +this file helps.
898 > diff --git a/pkg/place-holder b/pkg/place-holder
898 > diff --git a/pkg/place-holder b/pkg/place-holder
899 > new file mode 100644
899 > new file mode 100644
900 > --- /dev/null
900 > --- /dev/null
901 > +++ b/pkg/place-holder
901 > +++ b/pkg/place-holder
902 > @@ -0,0 +1,2 @@
902 > @@ -0,0 +1,2 @@
903 > +perforce does not maintain empty directories.
903 > +perforce does not maintain empty directories.
904 > +this file helps.
904 > +this file helps.
905 > diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
905 > diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
906 > old mode 100644
906 > old mode 100644
907 > new mode 100755
907 > new mode 100755
908 > EOF
908 > EOF
909 applying patch from stdin
909 applying patch from stdin
910 $ hg sum
910 $ hg sum
911 parent: 1:d59915696727 tip
911 parent: 1:d59915696727 tip
912 help management of empty pkg and lib directories in perforce
912 help management of empty pkg and lib directories in perforce
913 branch: default
913 branch: default
914 commit: (clean)
914 commit: (clean)
915 update: (current)
915 update: (current)
916 $ hg diff --git -c tip
916 $ hg diff --git -c tip
917 diff --git a/lib/place-holder b/lib/place-holder
917 diff --git a/lib/place-holder b/lib/place-holder
918 new file mode 100644
918 new file mode 100644
919 --- /dev/null
919 --- /dev/null
920 +++ b/lib/place-holder
920 +++ b/lib/place-holder
921 @@ -0,0 +1,2 @@
921 @@ -0,0 +1,2 @@
922 +perforce does not maintain empty directories.
922 +perforce does not maintain empty directories.
923 +this file helps.
923 +this file helps.
924 diff --git a/pkg/place-holder b/pkg/place-holder
924 diff --git a/pkg/place-holder b/pkg/place-holder
925 new file mode 100644
925 new file mode 100644
926 --- /dev/null
926 --- /dev/null
927 +++ b/pkg/place-holder
927 +++ b/pkg/place-holder
928 @@ -0,0 +1,2 @@
928 @@ -0,0 +1,2 @@
929 +perforce does not maintain empty directories.
929 +perforce does not maintain empty directories.
930 +this file helps.
930 +this file helps.
931 diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
931 diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
932 old mode 100644
932 old mode 100644
933 new mode 100755
933 new mode 100755
934 $ cd ..
934 $ cd ..
935
935
936
936
937 diff lines looking like headers
937 diff lines looking like headers
938
938
939 $ hg init difflineslikeheaders
939 $ hg init difflineslikeheaders
940 $ cd difflineslikeheaders
940 $ cd difflineslikeheaders
941 $ echo a >a
941 $ echo a >a
942 $ echo b >b
942 $ echo b >b
943 $ echo c >c
943 $ echo c >c
944 $ hg ci -Am1
944 $ hg ci -Am1
945 adding a
945 adding a
946 adding b
946 adding b
947 adding c
947 adding c
948
948
949 $ echo "key: value" >>a
949 $ echo "key: value" >>a
950 $ echo "key: value" >>b
950 $ echo "key: value" >>b
951 $ echo "foo" >>c
951 $ echo "foo" >>c
952 $ hg ci -m2
952 $ hg ci -m2
953
953
954 $ hg up -C 0
954 $ hg up -C 0
955 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
955 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
956 $ hg diff --git -c1 >want
956 $ hg diff --git -c1 >want
957 $ hg diff -c1 | hg import --no-commit -
957 $ hg diff -c1 | hg import --no-commit -
958 applying patch from stdin
958 applying patch from stdin
959 $ hg diff --git >have
959 $ hg diff --git >have
960 $ diff want have
960 $ diff want have
961 $ cd ..
961 $ cd ..
962
962
963 import a unified diff with no lines of context (diff -U0)
963 import a unified diff with no lines of context (diff -U0)
964
964
965 $ hg init diffzero
965 $ hg init diffzero
966 $ cd diffzero
966 $ cd diffzero
967 $ cat > f << EOF
967 $ cat > f << EOF
968 > c2
968 > c2
969 > c4
969 > c4
970 > c5
970 > c5
971 > EOF
971 > EOF
972 $ hg commit -Am0
972 $ hg commit -Am0
973 adding f
973 adding f
974
974
975 $ hg import --no-commit - << EOF
975 $ hg import --no-commit - << EOF
976 > # HG changeset patch
976 > # HG changeset patch
977 > # User test
977 > # User test
978 > # Date 0 0
978 > # Date 0 0
979 > # Node ID f4974ab632f3dee767567b0576c0ec9a4508575c
979 > # Node ID f4974ab632f3dee767567b0576c0ec9a4508575c
980 > # Parent 8679a12a975b819fae5f7ad3853a2886d143d794
980 > # Parent 8679a12a975b819fae5f7ad3853a2886d143d794
981 > 1
981 > 1
982 > diff -r 8679a12a975b -r f4974ab632f3 f
982 > diff -r 8679a12a975b -r f4974ab632f3 f
983 > --- a/f Thu Jan 01 00:00:00 1970 +0000
983 > --- a/f Thu Jan 01 00:00:00 1970 +0000
984 > +++ b/f Thu Jan 01 00:00:00 1970 +0000
984 > +++ b/f Thu Jan 01 00:00:00 1970 +0000
985 > @@ -0,0 +1,1 @@
985 > @@ -0,0 +1,1 @@
986 > +c1
986 > +c1
987 > @@ -1,0 +3,1 @@
987 > @@ -1,0 +3,1 @@
988 > +c3
988 > +c3
989 > @@ -3,1 +4,0 @@
989 > @@ -3,1 +4,0 @@
990 > -c5
990 > -c5
991 > EOF
991 > EOF
992 applying patch from stdin
992 applying patch from stdin
993
993
994 $ cat f
994 $ cat f
995 c1
995 c1
996 c2
996 c2
997 c3
997 c3
998 c4
998 c4
999
1000 Test corner case involving fuzz and skew
1001
1002 $ hg init morecornercases
1003 $ cd morecornercases
1004
1005 $ cat > 01-no-context-beginning-of-file.diff <<EOF
1006 > diff --git a/a b/a
1007 > --- a/a
1008 > +++ b/a
1009 > @@ -1,0 +1,1 @@
1010 > +line
1011 > EOF
1012
1013 $ cat > 02-no-context-middle-of-file.diff <<EOF
1014 > diff --git a/a b/a
1015 > --- a/a
1016 > +++ b/a
1017 > @@ -1,1 +1,1 @@
1018 > -2
1019 > +add some skew
1020 > @@ -2,0 +2,1 @@
1021 > +line
1022 > EOF
1023
1024 $ cat > 03-no-context-end-of-file.diff <<EOF
1025 > diff --git a/a b/a
1026 > --- a/a
1027 > +++ b/a
1028 > @@ -10,0 +10,1 @@
1029 > +line
1030 > EOF
1031
1032 $ cat > a <<EOF
1033 > 1
1034 > 2
1035 > 3
1036 > 4
1037 > EOF
1038 $ hg ci -Am adda a
1039 $ for p in *.diff; do
1040 > hg import -v --no-commit $p
1041 > cat a
1042 > hg revert -aqC a
1043 > # patch -p1 < $p
1044 > # cat a
1045 > # hg revert -aC a
1046 > done
1047 applying 01-no-context-beginning-of-file.diff
1048 patching file a
1049 applied to working directory
1050 1
1051 line
1052 2
1053 3
1054 4
1055 applying 02-no-context-middle-of-file.diff
1056 patching file a
1057 Hunk #1 succeeded at 2 (offset 1 lines).
1058 Hunk #2 succeeded at 4 (offset 1 lines).
1059 applied to working directory
1060 1
1061 add some skew
1062 3
1063 line
1064 4
1065 applying 03-no-context-end-of-file.diff
1066 patching file a
1067 Hunk #1 succeeded at 5 (offset -6 lines).
1068 applied to working directory
1069 1
1070 2
1071 3
1072 4
1073 line
1074
General Comments 0
You need to be logged in to leave comments. Login now