##// END OF EJS Templates
patch: error out if reached to EOF while reading hunk...
Yuya Nishihara -
r37591:49b82cdb default
parent child Browse files
Show More
@@ -1,69 +1,77 b''
1 # diffhelpers.py - helper routines for patch
1 # diffhelpers.py - helper routines for patch
2 #
2 #
3 # Copyright 2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from .i18n import _
11
12 from . import (
13 error,
14 )
15
10 def addlines(fp, hunk, lena, lenb, a, b):
16 def addlines(fp, hunk, lena, lenb, a, b):
11 """Read lines from fp into the hunk
17 """Read lines from fp into the hunk
12
18
13 The hunk is parsed into two arrays, a and b. a gets the old state of
19 The hunk is parsed into two arrays, a and b. a gets the old state of
14 the text, b gets the new state. The control char from the hunk is saved
20 the text, b gets the new state. The control char from the hunk is saved
15 when inserting into a, but not b (for performance while deleting files.)
21 when inserting into a, but not b (for performance while deleting files.)
16 """
22 """
17 while True:
23 while True:
18 todoa = lena - len(a)
24 todoa = lena - len(a)
19 todob = lenb - len(b)
25 todob = lenb - len(b)
20 num = max(todoa, todob)
26 num = max(todoa, todob)
21 if num == 0:
27 if num == 0:
22 break
28 break
23 for i in xrange(num):
29 for i in xrange(num):
24 s = fp.readline()
30 s = fp.readline()
31 if not s:
32 raise error.ParseError(_('incomplete hunk'))
25 if s == "\\ No newline at end of file\n":
33 if s == "\\ No newline at end of file\n":
26 fixnewline(hunk, a, b)
34 fixnewline(hunk, a, b)
27 continue
35 continue
28 if s == "\n":
36 if s == "\n":
29 # Some patches may be missing the control char
37 # Some patches may be missing the control char
30 # on empty lines. Supply a leading space.
38 # on empty lines. Supply a leading space.
31 s = " \n"
39 s = " \n"
32 hunk.append(s)
40 hunk.append(s)
33 if s.startswith('+'):
41 if s.startswith('+'):
34 b.append(s[1:])
42 b.append(s[1:])
35 elif s.startswith('-'):
43 elif s.startswith('-'):
36 a.append(s)
44 a.append(s)
37 else:
45 else:
38 b.append(s[1:])
46 b.append(s[1:])
39 a.append(s)
47 a.append(s)
40
48
41 def fixnewline(hunk, a, b):
49 def fixnewline(hunk, a, b):
42 """Fix up the last lines of a and b when the patch has no newline at EOF"""
50 """Fix up the last lines of a and b when the patch has no newline at EOF"""
43 l = hunk[-1]
51 l = hunk[-1]
44 # tolerate CRLF in last line
52 # tolerate CRLF in last line
45 if l.endswith('\r\n'):
53 if l.endswith('\r\n'):
46 hline = l[:-2]
54 hline = l[:-2]
47 else:
55 else:
48 hline = l[:-1]
56 hline = l[:-1]
49
57
50 if hline.startswith((' ', '+')):
58 if hline.startswith((' ', '+')):
51 b[-1] = hline[1:]
59 b[-1] = hline[1:]
52 if hline.startswith((' ', '-')):
60 if hline.startswith((' ', '-')):
53 a[-1] = hline
61 a[-1] = hline
54 hunk[-1] = hline
62 hunk[-1] = hline
55
63
56 def testhunk(a, b, bstart):
64 def testhunk(a, b, bstart):
57 """Compare the lines in a with the lines in b
65 """Compare the lines in a with the lines in b
58
66
59 a is assumed to have a control char at the start of each line, this char
67 a is assumed to have a control char at the start of each line, this char
60 is ignored in the compare.
68 is ignored in the compare.
61 """
69 """
62 alen = len(a)
70 alen = len(a)
63 blen = len(b)
71 blen = len(b)
64 if alen > blen - bstart:
72 if alen > blen - bstart:
65 return -1
73 return -1
66 for i in xrange(alen):
74 for i in xrange(alen):
67 if a[i][1:] != b[i + bstart]:
75 if a[i][1:] != b[i + bstart]:
68 return -1
76 return -1
69 return 0
77 return 0
@@ -1,2913 +1,2916 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 from __future__ import absolute_import, print_function
9 from __future__ import absolute_import, print_function
10
10
11 import collections
11 import collections
12 import copy
12 import copy
13 import difflib
13 import difflib
14 import email
14 import email
15 import errno
15 import errno
16 import hashlib
16 import hashlib
17 import os
17 import os
18 import posixpath
18 import posixpath
19 import re
19 import re
20 import shutil
20 import shutil
21 import tempfile
21 import tempfile
22 import zlib
22 import zlib
23
23
24 from .i18n import _
24 from .i18n import _
25 from .node import (
25 from .node import (
26 hex,
26 hex,
27 short,
27 short,
28 )
28 )
29 from . import (
29 from . import (
30 copies,
30 copies,
31 diffhelpers,
31 diffhelpers,
32 encoding,
32 encoding,
33 error,
33 error,
34 mail,
34 mail,
35 mdiff,
35 mdiff,
36 pathutil,
36 pathutil,
37 pycompat,
37 pycompat,
38 scmutil,
38 scmutil,
39 similar,
39 similar,
40 util,
40 util,
41 vfs as vfsmod,
41 vfs as vfsmod,
42 )
42 )
43 from .utils import (
43 from .utils import (
44 dateutil,
44 dateutil,
45 procutil,
45 procutil,
46 stringutil,
46 stringutil,
47 )
47 )
48
48
49 stringio = util.stringio
49 stringio = util.stringio
50
50
51 gitre = re.compile(br'diff --git a/(.*) b/(.*)')
51 gitre = re.compile(br'diff --git a/(.*) b/(.*)')
52 tabsplitter = re.compile(br'(\t+|[^\t]+)')
52 tabsplitter = re.compile(br'(\t+|[^\t]+)')
53 _nonwordre = re.compile(br'([^a-zA-Z0-9_\x80-\xff])')
53 _nonwordre = re.compile(br'([^a-zA-Z0-9_\x80-\xff])')
54
54
55 PatchError = error.PatchError
55 PatchError = error.PatchError
56
56
57 # public functions
57 # public functions
58
58
59 def split(stream):
59 def split(stream):
60 '''return an iterator of individual patches from a stream'''
60 '''return an iterator of individual patches from a stream'''
61 def isheader(line, inheader):
61 def isheader(line, inheader):
62 if inheader and line.startswith((' ', '\t')):
62 if inheader and line.startswith((' ', '\t')):
63 # continuation
63 # continuation
64 return True
64 return True
65 if line.startswith((' ', '-', '+')):
65 if line.startswith((' ', '-', '+')):
66 # diff line - don't check for header pattern in there
66 # diff line - don't check for header pattern in there
67 return False
67 return False
68 l = line.split(': ', 1)
68 l = line.split(': ', 1)
69 return len(l) == 2 and ' ' not in l[0]
69 return len(l) == 2 and ' ' not in l[0]
70
70
71 def chunk(lines):
71 def chunk(lines):
72 return stringio(''.join(lines))
72 return stringio(''.join(lines))
73
73
74 def hgsplit(stream, cur):
74 def hgsplit(stream, cur):
75 inheader = True
75 inheader = True
76
76
77 for line in stream:
77 for line in stream:
78 if not line.strip():
78 if not line.strip():
79 inheader = False
79 inheader = False
80 if not inheader and line.startswith('# HG changeset patch'):
80 if not inheader and line.startswith('# HG changeset patch'):
81 yield chunk(cur)
81 yield chunk(cur)
82 cur = []
82 cur = []
83 inheader = True
83 inheader = True
84
84
85 cur.append(line)
85 cur.append(line)
86
86
87 if cur:
87 if cur:
88 yield chunk(cur)
88 yield chunk(cur)
89
89
90 def mboxsplit(stream, cur):
90 def mboxsplit(stream, cur):
91 for line in stream:
91 for line in stream:
92 if line.startswith('From '):
92 if line.startswith('From '):
93 for c in split(chunk(cur[1:])):
93 for c in split(chunk(cur[1:])):
94 yield c
94 yield c
95 cur = []
95 cur = []
96
96
97 cur.append(line)
97 cur.append(line)
98
98
99 if cur:
99 if cur:
100 for c in split(chunk(cur[1:])):
100 for c in split(chunk(cur[1:])):
101 yield c
101 yield c
102
102
103 def mimesplit(stream, cur):
103 def mimesplit(stream, cur):
104 def msgfp(m):
104 def msgfp(m):
105 fp = stringio()
105 fp = stringio()
106 g = email.Generator.Generator(fp, mangle_from_=False)
106 g = email.Generator.Generator(fp, mangle_from_=False)
107 g.flatten(m)
107 g.flatten(m)
108 fp.seek(0)
108 fp.seek(0)
109 return fp
109 return fp
110
110
111 for line in stream:
111 for line in stream:
112 cur.append(line)
112 cur.append(line)
113 c = chunk(cur)
113 c = chunk(cur)
114
114
115 m = pycompat.emailparser().parse(c)
115 m = pycompat.emailparser().parse(c)
116 if not m.is_multipart():
116 if not m.is_multipart():
117 yield msgfp(m)
117 yield msgfp(m)
118 else:
118 else:
119 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
119 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
120 for part in m.walk():
120 for part in m.walk():
121 ct = part.get_content_type()
121 ct = part.get_content_type()
122 if ct not in ok_types:
122 if ct not in ok_types:
123 continue
123 continue
124 yield msgfp(part)
124 yield msgfp(part)
125
125
126 def headersplit(stream, cur):
126 def headersplit(stream, cur):
127 inheader = False
127 inheader = False
128
128
129 for line in stream:
129 for line in stream:
130 if not inheader and isheader(line, inheader):
130 if not inheader and isheader(line, inheader):
131 yield chunk(cur)
131 yield chunk(cur)
132 cur = []
132 cur = []
133 inheader = True
133 inheader = True
134 if inheader and not isheader(line, inheader):
134 if inheader and not isheader(line, inheader):
135 inheader = False
135 inheader = False
136
136
137 cur.append(line)
137 cur.append(line)
138
138
139 if cur:
139 if cur:
140 yield chunk(cur)
140 yield chunk(cur)
141
141
142 def remainder(cur):
142 def remainder(cur):
143 yield chunk(cur)
143 yield chunk(cur)
144
144
145 class fiter(object):
145 class fiter(object):
146 def __init__(self, fp):
146 def __init__(self, fp):
147 self.fp = fp
147 self.fp = fp
148
148
149 def __iter__(self):
149 def __iter__(self):
150 return self
150 return self
151
151
152 def next(self):
152 def next(self):
153 l = self.fp.readline()
153 l = self.fp.readline()
154 if not l:
154 if not l:
155 raise StopIteration
155 raise StopIteration
156 return l
156 return l
157
157
158 __next__ = next
158 __next__ = next
159
159
160 inheader = False
160 inheader = False
161 cur = []
161 cur = []
162
162
163 mimeheaders = ['content-type']
163 mimeheaders = ['content-type']
164
164
165 if not util.safehasattr(stream, 'next'):
165 if not util.safehasattr(stream, 'next'):
166 # http responses, for example, have readline but not next
166 # http responses, for example, have readline but not next
167 stream = fiter(stream)
167 stream = fiter(stream)
168
168
169 for line in stream:
169 for line in stream:
170 cur.append(line)
170 cur.append(line)
171 if line.startswith('# HG changeset patch'):
171 if line.startswith('# HG changeset patch'):
172 return hgsplit(stream, cur)
172 return hgsplit(stream, cur)
173 elif line.startswith('From '):
173 elif line.startswith('From '):
174 return mboxsplit(stream, cur)
174 return mboxsplit(stream, cur)
175 elif isheader(line, inheader):
175 elif isheader(line, inheader):
176 inheader = True
176 inheader = True
177 if line.split(':', 1)[0].lower() in mimeheaders:
177 if line.split(':', 1)[0].lower() in mimeheaders:
178 # let email parser handle this
178 # let email parser handle this
179 return mimesplit(stream, cur)
179 return mimesplit(stream, cur)
180 elif line.startswith('--- ') and inheader:
180 elif line.startswith('--- ') and inheader:
181 # No evil headers seen by diff start, split by hand
181 # No evil headers seen by diff start, split by hand
182 return headersplit(stream, cur)
182 return headersplit(stream, cur)
183 # Not enough info, keep reading
183 # Not enough info, keep reading
184
184
185 # if we are here, we have a very plain patch
185 # if we are here, we have a very plain patch
186 return remainder(cur)
186 return remainder(cur)
187
187
188 ## Some facility for extensible patch parsing:
188 ## Some facility for extensible patch parsing:
189 # list of pairs ("header to match", "data key")
189 # list of pairs ("header to match", "data key")
190 patchheadermap = [('Date', 'date'),
190 patchheadermap = [('Date', 'date'),
191 ('Branch', 'branch'),
191 ('Branch', 'branch'),
192 ('Node ID', 'nodeid'),
192 ('Node ID', 'nodeid'),
193 ]
193 ]
194
194
195 def extract(ui, fileobj):
195 def extract(ui, fileobj):
196 '''extract patch from data read from fileobj.
196 '''extract patch from data read from fileobj.
197
197
198 patch can be a normal patch or contained in an email message.
198 patch can be a normal patch or contained in an email message.
199
199
200 return a dictionary. Standard keys are:
200 return a dictionary. Standard keys are:
201 - filename,
201 - filename,
202 - message,
202 - message,
203 - user,
203 - user,
204 - date,
204 - date,
205 - branch,
205 - branch,
206 - node,
206 - node,
207 - p1,
207 - p1,
208 - p2.
208 - p2.
209 Any item can be missing from the dictionary. If filename is missing,
209 Any item can be missing from the dictionary. If filename is missing,
210 fileobj did not contain a patch. Caller must unlink filename when done.'''
210 fileobj did not contain a patch. Caller must unlink filename when done.'''
211
211
212 # attempt to detect the start of a patch
212 # attempt to detect the start of a patch
213 # (this heuristic is borrowed from quilt)
213 # (this heuristic is borrowed from quilt)
214 diffre = re.compile(br'^(?:Index:[ \t]|diff[ \t]-|RCS file: |'
214 diffre = re.compile(br'^(?:Index:[ \t]|diff[ \t]-|RCS file: |'
215 br'retrieving revision [0-9]+(\.[0-9]+)*$|'
215 br'retrieving revision [0-9]+(\.[0-9]+)*$|'
216 br'---[ \t].*?^\+\+\+[ \t]|'
216 br'---[ \t].*?^\+\+\+[ \t]|'
217 br'\*\*\*[ \t].*?^---[ \t])',
217 br'\*\*\*[ \t].*?^---[ \t])',
218 re.MULTILINE | re.DOTALL)
218 re.MULTILINE | re.DOTALL)
219
219
220 data = {}
220 data = {}
221 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
221 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
222 tmpfp = os.fdopen(fd, r'wb')
222 tmpfp = os.fdopen(fd, r'wb')
223 try:
223 try:
224 msg = pycompat.emailparser().parse(fileobj)
224 msg = pycompat.emailparser().parse(fileobj)
225
225
226 subject = msg[r'Subject'] and mail.headdecode(msg[r'Subject'])
226 subject = msg[r'Subject'] and mail.headdecode(msg[r'Subject'])
227 data['user'] = msg[r'From'] and mail.headdecode(msg[r'From'])
227 data['user'] = msg[r'From'] and mail.headdecode(msg[r'From'])
228 if not subject and not data['user']:
228 if not subject and not data['user']:
229 # Not an email, restore parsed headers if any
229 # Not an email, restore parsed headers if any
230 subject = '\n'.join(': '.join(map(encoding.strtolocal, h))
230 subject = '\n'.join(': '.join(map(encoding.strtolocal, h))
231 for h in msg.items()) + '\n'
231 for h in msg.items()) + '\n'
232
232
233 # should try to parse msg['Date']
233 # should try to parse msg['Date']
234 parents = []
234 parents = []
235
235
236 if subject:
236 if subject:
237 if subject.startswith('[PATCH'):
237 if subject.startswith('[PATCH'):
238 pend = subject.find(']')
238 pend = subject.find(']')
239 if pend >= 0:
239 if pend >= 0:
240 subject = subject[pend + 1:].lstrip()
240 subject = subject[pend + 1:].lstrip()
241 subject = re.sub(br'\n[ \t]+', ' ', subject)
241 subject = re.sub(br'\n[ \t]+', ' ', subject)
242 ui.debug('Subject: %s\n' % subject)
242 ui.debug('Subject: %s\n' % subject)
243 if data['user']:
243 if data['user']:
244 ui.debug('From: %s\n' % data['user'])
244 ui.debug('From: %s\n' % data['user'])
245 diffs_seen = 0
245 diffs_seen = 0
246 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
246 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
247 message = ''
247 message = ''
248 for part in msg.walk():
248 for part in msg.walk():
249 content_type = pycompat.bytestr(part.get_content_type())
249 content_type = pycompat.bytestr(part.get_content_type())
250 ui.debug('Content-Type: %s\n' % content_type)
250 ui.debug('Content-Type: %s\n' % content_type)
251 if content_type not in ok_types:
251 if content_type not in ok_types:
252 continue
252 continue
253 payload = part.get_payload(decode=True)
253 payload = part.get_payload(decode=True)
254 m = diffre.search(payload)
254 m = diffre.search(payload)
255 if m:
255 if m:
256 hgpatch = False
256 hgpatch = False
257 hgpatchheader = False
257 hgpatchheader = False
258 ignoretext = False
258 ignoretext = False
259
259
260 ui.debug('found patch at byte %d\n' % m.start(0))
260 ui.debug('found patch at byte %d\n' % m.start(0))
261 diffs_seen += 1
261 diffs_seen += 1
262 cfp = stringio()
262 cfp = stringio()
263 for line in payload[:m.start(0)].splitlines():
263 for line in payload[:m.start(0)].splitlines():
264 if line.startswith('# HG changeset patch') and not hgpatch:
264 if line.startswith('# HG changeset patch') and not hgpatch:
265 ui.debug('patch generated by hg export\n')
265 ui.debug('patch generated by hg export\n')
266 hgpatch = True
266 hgpatch = True
267 hgpatchheader = True
267 hgpatchheader = True
268 # drop earlier commit message content
268 # drop earlier commit message content
269 cfp.seek(0)
269 cfp.seek(0)
270 cfp.truncate()
270 cfp.truncate()
271 subject = None
271 subject = None
272 elif hgpatchheader:
272 elif hgpatchheader:
273 if line.startswith('# User '):
273 if line.startswith('# User '):
274 data['user'] = line[7:]
274 data['user'] = line[7:]
275 ui.debug('From: %s\n' % data['user'])
275 ui.debug('From: %s\n' % data['user'])
276 elif line.startswith("# Parent "):
276 elif line.startswith("# Parent "):
277 parents.append(line[9:].lstrip())
277 parents.append(line[9:].lstrip())
278 elif line.startswith("# "):
278 elif line.startswith("# "):
279 for header, key in patchheadermap:
279 for header, key in patchheadermap:
280 prefix = '# %s ' % header
280 prefix = '# %s ' % header
281 if line.startswith(prefix):
281 if line.startswith(prefix):
282 data[key] = line[len(prefix):]
282 data[key] = line[len(prefix):]
283 else:
283 else:
284 hgpatchheader = False
284 hgpatchheader = False
285 elif line == '---':
285 elif line == '---':
286 ignoretext = True
286 ignoretext = True
287 if not hgpatchheader and not ignoretext:
287 if not hgpatchheader and not ignoretext:
288 cfp.write(line)
288 cfp.write(line)
289 cfp.write('\n')
289 cfp.write('\n')
290 message = cfp.getvalue()
290 message = cfp.getvalue()
291 if tmpfp:
291 if tmpfp:
292 tmpfp.write(payload)
292 tmpfp.write(payload)
293 if not payload.endswith('\n'):
293 if not payload.endswith('\n'):
294 tmpfp.write('\n')
294 tmpfp.write('\n')
295 elif not diffs_seen and message and content_type == 'text/plain':
295 elif not diffs_seen and message and content_type == 'text/plain':
296 message += '\n' + payload
296 message += '\n' + payload
297 except: # re-raises
297 except: # re-raises
298 tmpfp.close()
298 tmpfp.close()
299 os.unlink(tmpname)
299 os.unlink(tmpname)
300 raise
300 raise
301
301
302 if subject and not message.startswith(subject):
302 if subject and not message.startswith(subject):
303 message = '%s\n%s' % (subject, message)
303 message = '%s\n%s' % (subject, message)
304 data['message'] = message
304 data['message'] = message
305 tmpfp.close()
305 tmpfp.close()
306 if parents:
306 if parents:
307 data['p1'] = parents.pop(0)
307 data['p1'] = parents.pop(0)
308 if parents:
308 if parents:
309 data['p2'] = parents.pop(0)
309 data['p2'] = parents.pop(0)
310
310
311 if diffs_seen:
311 if diffs_seen:
312 data['filename'] = tmpname
312 data['filename'] = tmpname
313 else:
313 else:
314 os.unlink(tmpname)
314 os.unlink(tmpname)
315 return data
315 return data
316
316
317 class patchmeta(object):
317 class patchmeta(object):
318 """Patched file metadata
318 """Patched file metadata
319
319
320 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
320 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
321 or COPY. 'path' is patched file path. 'oldpath' is set to the
321 or COPY. 'path' is patched file path. 'oldpath' is set to the
322 origin file when 'op' is either COPY or RENAME, None otherwise. If
322 origin file when 'op' is either COPY or RENAME, None otherwise. If
323 file mode is changed, 'mode' is a tuple (islink, isexec) where
323 file mode is changed, 'mode' is a tuple (islink, isexec) where
324 'islink' is True if the file is a symlink and 'isexec' is True if
324 'islink' is True if the file is a symlink and 'isexec' is True if
325 the file is executable. Otherwise, 'mode' is None.
325 the file is executable. Otherwise, 'mode' is None.
326 """
326 """
327 def __init__(self, path):
327 def __init__(self, path):
328 self.path = path
328 self.path = path
329 self.oldpath = None
329 self.oldpath = None
330 self.mode = None
330 self.mode = None
331 self.op = 'MODIFY'
331 self.op = 'MODIFY'
332 self.binary = False
332 self.binary = False
333
333
334 def setmode(self, mode):
334 def setmode(self, mode):
335 islink = mode & 0o20000
335 islink = mode & 0o20000
336 isexec = mode & 0o100
336 isexec = mode & 0o100
337 self.mode = (islink, isexec)
337 self.mode = (islink, isexec)
338
338
339 def copy(self):
339 def copy(self):
340 other = patchmeta(self.path)
340 other = patchmeta(self.path)
341 other.oldpath = self.oldpath
341 other.oldpath = self.oldpath
342 other.mode = self.mode
342 other.mode = self.mode
343 other.op = self.op
343 other.op = self.op
344 other.binary = self.binary
344 other.binary = self.binary
345 return other
345 return other
346
346
347 def _ispatchinga(self, afile):
347 def _ispatchinga(self, afile):
348 if afile == '/dev/null':
348 if afile == '/dev/null':
349 return self.op == 'ADD'
349 return self.op == 'ADD'
350 return afile == 'a/' + (self.oldpath or self.path)
350 return afile == 'a/' + (self.oldpath or self.path)
351
351
352 def _ispatchingb(self, bfile):
352 def _ispatchingb(self, bfile):
353 if bfile == '/dev/null':
353 if bfile == '/dev/null':
354 return self.op == 'DELETE'
354 return self.op == 'DELETE'
355 return bfile == 'b/' + self.path
355 return bfile == 'b/' + self.path
356
356
357 def ispatching(self, afile, bfile):
357 def ispatching(self, afile, bfile):
358 return self._ispatchinga(afile) and self._ispatchingb(bfile)
358 return self._ispatchinga(afile) and self._ispatchingb(bfile)
359
359
360 def __repr__(self):
360 def __repr__(self):
361 return "<patchmeta %s %r>" % (self.op, self.path)
361 return "<patchmeta %s %r>" % (self.op, self.path)
362
362
363 def readgitpatch(lr):
363 def readgitpatch(lr):
364 """extract git-style metadata about patches from <patchname>"""
364 """extract git-style metadata about patches from <patchname>"""
365
365
366 # Filter patch for git information
366 # Filter patch for git information
367 gp = None
367 gp = None
368 gitpatches = []
368 gitpatches = []
369 for line in lr:
369 for line in lr:
370 line = line.rstrip(' \r\n')
370 line = line.rstrip(' \r\n')
371 if line.startswith('diff --git a/'):
371 if line.startswith('diff --git a/'):
372 m = gitre.match(line)
372 m = gitre.match(line)
373 if m:
373 if m:
374 if gp:
374 if gp:
375 gitpatches.append(gp)
375 gitpatches.append(gp)
376 dst = m.group(2)
376 dst = m.group(2)
377 gp = patchmeta(dst)
377 gp = patchmeta(dst)
378 elif gp:
378 elif gp:
379 if line.startswith('--- '):
379 if line.startswith('--- '):
380 gitpatches.append(gp)
380 gitpatches.append(gp)
381 gp = None
381 gp = None
382 continue
382 continue
383 if line.startswith('rename from '):
383 if line.startswith('rename from '):
384 gp.op = 'RENAME'
384 gp.op = 'RENAME'
385 gp.oldpath = line[12:]
385 gp.oldpath = line[12:]
386 elif line.startswith('rename to '):
386 elif line.startswith('rename to '):
387 gp.path = line[10:]
387 gp.path = line[10:]
388 elif line.startswith('copy from '):
388 elif line.startswith('copy from '):
389 gp.op = 'COPY'
389 gp.op = 'COPY'
390 gp.oldpath = line[10:]
390 gp.oldpath = line[10:]
391 elif line.startswith('copy to '):
391 elif line.startswith('copy to '):
392 gp.path = line[8:]
392 gp.path = line[8:]
393 elif line.startswith('deleted file'):
393 elif line.startswith('deleted file'):
394 gp.op = 'DELETE'
394 gp.op = 'DELETE'
395 elif line.startswith('new file mode '):
395 elif line.startswith('new file mode '):
396 gp.op = 'ADD'
396 gp.op = 'ADD'
397 gp.setmode(int(line[-6:], 8))
397 gp.setmode(int(line[-6:], 8))
398 elif line.startswith('new mode '):
398 elif line.startswith('new mode '):
399 gp.setmode(int(line[-6:], 8))
399 gp.setmode(int(line[-6:], 8))
400 elif line.startswith('GIT binary patch'):
400 elif line.startswith('GIT binary patch'):
401 gp.binary = True
401 gp.binary = True
402 if gp:
402 if gp:
403 gitpatches.append(gp)
403 gitpatches.append(gp)
404
404
405 return gitpatches
405 return gitpatches
406
406
407 class linereader(object):
407 class linereader(object):
408 # simple class to allow pushing lines back into the input stream
408 # simple class to allow pushing lines back into the input stream
409 def __init__(self, fp):
409 def __init__(self, fp):
410 self.fp = fp
410 self.fp = fp
411 self.buf = []
411 self.buf = []
412
412
413 def push(self, line):
413 def push(self, line):
414 if line is not None:
414 if line is not None:
415 self.buf.append(line)
415 self.buf.append(line)
416
416
417 def readline(self):
417 def readline(self):
418 if self.buf:
418 if self.buf:
419 l = self.buf[0]
419 l = self.buf[0]
420 del self.buf[0]
420 del self.buf[0]
421 return l
421 return l
422 return self.fp.readline()
422 return self.fp.readline()
423
423
424 def __iter__(self):
424 def __iter__(self):
425 return iter(self.readline, '')
425 return iter(self.readline, '')
426
426
427 class abstractbackend(object):
427 class abstractbackend(object):
428 def __init__(self, ui):
428 def __init__(self, ui):
429 self.ui = ui
429 self.ui = ui
430
430
431 def getfile(self, fname):
431 def getfile(self, fname):
432 """Return target file data and flags as a (data, (islink,
432 """Return target file data and flags as a (data, (islink,
433 isexec)) tuple. Data is None if file is missing/deleted.
433 isexec)) tuple. Data is None if file is missing/deleted.
434 """
434 """
435 raise NotImplementedError
435 raise NotImplementedError
436
436
437 def setfile(self, fname, data, mode, copysource):
437 def setfile(self, fname, data, mode, copysource):
438 """Write data to target file fname and set its mode. mode is a
438 """Write data to target file fname and set its mode. mode is a
439 (islink, isexec) tuple. If data is None, the file content should
439 (islink, isexec) tuple. If data is None, the file content should
440 be left unchanged. If the file is modified after being copied,
440 be left unchanged. If the file is modified after being copied,
441 copysource is set to the original file name.
441 copysource is set to the original file name.
442 """
442 """
443 raise NotImplementedError
443 raise NotImplementedError
444
444
445 def unlink(self, fname):
445 def unlink(self, fname):
446 """Unlink target file."""
446 """Unlink target file."""
447 raise NotImplementedError
447 raise NotImplementedError
448
448
449 def writerej(self, fname, failed, total, lines):
449 def writerej(self, fname, failed, total, lines):
450 """Write rejected lines for fname. total is the number of hunks
450 """Write rejected lines for fname. total is the number of hunks
451 which failed to apply and total the total number of hunks for this
451 which failed to apply and total the total number of hunks for this
452 files.
452 files.
453 """
453 """
454
454
455 def exists(self, fname):
455 def exists(self, fname):
456 raise NotImplementedError
456 raise NotImplementedError
457
457
458 def close(self):
458 def close(self):
459 raise NotImplementedError
459 raise NotImplementedError
460
460
461 class fsbackend(abstractbackend):
461 class fsbackend(abstractbackend):
462 def __init__(self, ui, basedir):
462 def __init__(self, ui, basedir):
463 super(fsbackend, self).__init__(ui)
463 super(fsbackend, self).__init__(ui)
464 self.opener = vfsmod.vfs(basedir)
464 self.opener = vfsmod.vfs(basedir)
465
465
466 def getfile(self, fname):
466 def getfile(self, fname):
467 if self.opener.islink(fname):
467 if self.opener.islink(fname):
468 return (self.opener.readlink(fname), (True, False))
468 return (self.opener.readlink(fname), (True, False))
469
469
470 isexec = False
470 isexec = False
471 try:
471 try:
472 isexec = self.opener.lstat(fname).st_mode & 0o100 != 0
472 isexec = self.opener.lstat(fname).st_mode & 0o100 != 0
473 except OSError as e:
473 except OSError as e:
474 if e.errno != errno.ENOENT:
474 if e.errno != errno.ENOENT:
475 raise
475 raise
476 try:
476 try:
477 return (self.opener.read(fname), (False, isexec))
477 return (self.opener.read(fname), (False, isexec))
478 except IOError as e:
478 except IOError as e:
479 if e.errno != errno.ENOENT:
479 if e.errno != errno.ENOENT:
480 raise
480 raise
481 return None, None
481 return None, None
482
482
483 def setfile(self, fname, data, mode, copysource):
483 def setfile(self, fname, data, mode, copysource):
484 islink, isexec = mode
484 islink, isexec = mode
485 if data is None:
485 if data is None:
486 self.opener.setflags(fname, islink, isexec)
486 self.opener.setflags(fname, islink, isexec)
487 return
487 return
488 if islink:
488 if islink:
489 self.opener.symlink(data, fname)
489 self.opener.symlink(data, fname)
490 else:
490 else:
491 self.opener.write(fname, data)
491 self.opener.write(fname, data)
492 if isexec:
492 if isexec:
493 self.opener.setflags(fname, False, True)
493 self.opener.setflags(fname, False, True)
494
494
495 def unlink(self, fname):
495 def unlink(self, fname):
496 self.opener.unlinkpath(fname, ignoremissing=True)
496 self.opener.unlinkpath(fname, ignoremissing=True)
497
497
498 def writerej(self, fname, failed, total, lines):
498 def writerej(self, fname, failed, total, lines):
499 fname = fname + ".rej"
499 fname = fname + ".rej"
500 self.ui.warn(
500 self.ui.warn(
501 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
501 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
502 (failed, total, fname))
502 (failed, total, fname))
503 fp = self.opener(fname, 'w')
503 fp = self.opener(fname, 'w')
504 fp.writelines(lines)
504 fp.writelines(lines)
505 fp.close()
505 fp.close()
506
506
507 def exists(self, fname):
507 def exists(self, fname):
508 return self.opener.lexists(fname)
508 return self.opener.lexists(fname)
509
509
510 class workingbackend(fsbackend):
510 class workingbackend(fsbackend):
511 def __init__(self, ui, repo, similarity):
511 def __init__(self, ui, repo, similarity):
512 super(workingbackend, self).__init__(ui, repo.root)
512 super(workingbackend, self).__init__(ui, repo.root)
513 self.repo = repo
513 self.repo = repo
514 self.similarity = similarity
514 self.similarity = similarity
515 self.removed = set()
515 self.removed = set()
516 self.changed = set()
516 self.changed = set()
517 self.copied = []
517 self.copied = []
518
518
519 def _checkknown(self, fname):
519 def _checkknown(self, fname):
520 if self.repo.dirstate[fname] == '?' and self.exists(fname):
520 if self.repo.dirstate[fname] == '?' and self.exists(fname):
521 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
521 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
522
522
523 def setfile(self, fname, data, mode, copysource):
523 def setfile(self, fname, data, mode, copysource):
524 self._checkknown(fname)
524 self._checkknown(fname)
525 super(workingbackend, self).setfile(fname, data, mode, copysource)
525 super(workingbackend, self).setfile(fname, data, mode, copysource)
526 if copysource is not None:
526 if copysource is not None:
527 self.copied.append((copysource, fname))
527 self.copied.append((copysource, fname))
528 self.changed.add(fname)
528 self.changed.add(fname)
529
529
530 def unlink(self, fname):
530 def unlink(self, fname):
531 self._checkknown(fname)
531 self._checkknown(fname)
532 super(workingbackend, self).unlink(fname)
532 super(workingbackend, self).unlink(fname)
533 self.removed.add(fname)
533 self.removed.add(fname)
534 self.changed.add(fname)
534 self.changed.add(fname)
535
535
536 def close(self):
536 def close(self):
537 wctx = self.repo[None]
537 wctx = self.repo[None]
538 changed = set(self.changed)
538 changed = set(self.changed)
539 for src, dst in self.copied:
539 for src, dst in self.copied:
540 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
540 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
541 if self.removed:
541 if self.removed:
542 wctx.forget(sorted(self.removed))
542 wctx.forget(sorted(self.removed))
543 for f in self.removed:
543 for f in self.removed:
544 if f not in self.repo.dirstate:
544 if f not in self.repo.dirstate:
545 # File was deleted and no longer belongs to the
545 # File was deleted and no longer belongs to the
546 # dirstate, it was probably marked added then
546 # dirstate, it was probably marked added then
547 # deleted, and should not be considered by
547 # deleted, and should not be considered by
548 # marktouched().
548 # marktouched().
549 changed.discard(f)
549 changed.discard(f)
550 if changed:
550 if changed:
551 scmutil.marktouched(self.repo, changed, self.similarity)
551 scmutil.marktouched(self.repo, changed, self.similarity)
552 return sorted(self.changed)
552 return sorted(self.changed)
553
553
554 class filestore(object):
554 class filestore(object):
555 def __init__(self, maxsize=None):
555 def __init__(self, maxsize=None):
556 self.opener = None
556 self.opener = None
557 self.files = {}
557 self.files = {}
558 self.created = 0
558 self.created = 0
559 self.maxsize = maxsize
559 self.maxsize = maxsize
560 if self.maxsize is None:
560 if self.maxsize is None:
561 self.maxsize = 4*(2**20)
561 self.maxsize = 4*(2**20)
562 self.size = 0
562 self.size = 0
563 self.data = {}
563 self.data = {}
564
564
565 def setfile(self, fname, data, mode, copied=None):
565 def setfile(self, fname, data, mode, copied=None):
566 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
566 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
567 self.data[fname] = (data, mode, copied)
567 self.data[fname] = (data, mode, copied)
568 self.size += len(data)
568 self.size += len(data)
569 else:
569 else:
570 if self.opener is None:
570 if self.opener is None:
571 root = tempfile.mkdtemp(prefix='hg-patch-')
571 root = tempfile.mkdtemp(prefix='hg-patch-')
572 self.opener = vfsmod.vfs(root)
572 self.opener = vfsmod.vfs(root)
573 # Avoid filename issues with these simple names
573 # Avoid filename issues with these simple names
574 fn = '%d' % self.created
574 fn = '%d' % self.created
575 self.opener.write(fn, data)
575 self.opener.write(fn, data)
576 self.created += 1
576 self.created += 1
577 self.files[fname] = (fn, mode, copied)
577 self.files[fname] = (fn, mode, copied)
578
578
579 def getfile(self, fname):
579 def getfile(self, fname):
580 if fname in self.data:
580 if fname in self.data:
581 return self.data[fname]
581 return self.data[fname]
582 if not self.opener or fname not in self.files:
582 if not self.opener or fname not in self.files:
583 return None, None, None
583 return None, None, None
584 fn, mode, copied = self.files[fname]
584 fn, mode, copied = self.files[fname]
585 return self.opener.read(fn), mode, copied
585 return self.opener.read(fn), mode, copied
586
586
587 def close(self):
587 def close(self):
588 if self.opener:
588 if self.opener:
589 shutil.rmtree(self.opener.base)
589 shutil.rmtree(self.opener.base)
590
590
591 class repobackend(abstractbackend):
591 class repobackend(abstractbackend):
592 def __init__(self, ui, repo, ctx, store):
592 def __init__(self, ui, repo, ctx, store):
593 super(repobackend, self).__init__(ui)
593 super(repobackend, self).__init__(ui)
594 self.repo = repo
594 self.repo = repo
595 self.ctx = ctx
595 self.ctx = ctx
596 self.store = store
596 self.store = store
597 self.changed = set()
597 self.changed = set()
598 self.removed = set()
598 self.removed = set()
599 self.copied = {}
599 self.copied = {}
600
600
601 def _checkknown(self, fname):
601 def _checkknown(self, fname):
602 if fname not in self.ctx:
602 if fname not in self.ctx:
603 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
603 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
604
604
605 def getfile(self, fname):
605 def getfile(self, fname):
606 try:
606 try:
607 fctx = self.ctx[fname]
607 fctx = self.ctx[fname]
608 except error.LookupError:
608 except error.LookupError:
609 return None, None
609 return None, None
610 flags = fctx.flags()
610 flags = fctx.flags()
611 return fctx.data(), ('l' in flags, 'x' in flags)
611 return fctx.data(), ('l' in flags, 'x' in flags)
612
612
613 def setfile(self, fname, data, mode, copysource):
613 def setfile(self, fname, data, mode, copysource):
614 if copysource:
614 if copysource:
615 self._checkknown(copysource)
615 self._checkknown(copysource)
616 if data is None:
616 if data is None:
617 data = self.ctx[fname].data()
617 data = self.ctx[fname].data()
618 self.store.setfile(fname, data, mode, copysource)
618 self.store.setfile(fname, data, mode, copysource)
619 self.changed.add(fname)
619 self.changed.add(fname)
620 if copysource:
620 if copysource:
621 self.copied[fname] = copysource
621 self.copied[fname] = copysource
622
622
623 def unlink(self, fname):
623 def unlink(self, fname):
624 self._checkknown(fname)
624 self._checkknown(fname)
625 self.removed.add(fname)
625 self.removed.add(fname)
626
626
627 def exists(self, fname):
627 def exists(self, fname):
628 return fname in self.ctx
628 return fname in self.ctx
629
629
630 def close(self):
630 def close(self):
631 return self.changed | self.removed
631 return self.changed | self.removed
632
632
633 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
633 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
634 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
634 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
635 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
635 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
636 eolmodes = ['strict', 'crlf', 'lf', 'auto']
636 eolmodes = ['strict', 'crlf', 'lf', 'auto']
637
637
638 class patchfile(object):
638 class patchfile(object):
639 def __init__(self, ui, gp, backend, store, eolmode='strict'):
639 def __init__(self, ui, gp, backend, store, eolmode='strict'):
640 self.fname = gp.path
640 self.fname = gp.path
641 self.eolmode = eolmode
641 self.eolmode = eolmode
642 self.eol = None
642 self.eol = None
643 self.backend = backend
643 self.backend = backend
644 self.ui = ui
644 self.ui = ui
645 self.lines = []
645 self.lines = []
646 self.exists = False
646 self.exists = False
647 self.missing = True
647 self.missing = True
648 self.mode = gp.mode
648 self.mode = gp.mode
649 self.copysource = gp.oldpath
649 self.copysource = gp.oldpath
650 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
650 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
651 self.remove = gp.op == 'DELETE'
651 self.remove = gp.op == 'DELETE'
652 if self.copysource is None:
652 if self.copysource is None:
653 data, mode = backend.getfile(self.fname)
653 data, mode = backend.getfile(self.fname)
654 else:
654 else:
655 data, mode = store.getfile(self.copysource)[:2]
655 data, mode = store.getfile(self.copysource)[:2]
656 if data is not None:
656 if data is not None:
657 self.exists = self.copysource is None or backend.exists(self.fname)
657 self.exists = self.copysource is None or backend.exists(self.fname)
658 self.missing = False
658 self.missing = False
659 if data:
659 if data:
660 self.lines = mdiff.splitnewlines(data)
660 self.lines = mdiff.splitnewlines(data)
661 if self.mode is None:
661 if self.mode is None:
662 self.mode = mode
662 self.mode = mode
663 if self.lines:
663 if self.lines:
664 # Normalize line endings
664 # Normalize line endings
665 if self.lines[0].endswith('\r\n'):
665 if self.lines[0].endswith('\r\n'):
666 self.eol = '\r\n'
666 self.eol = '\r\n'
667 elif self.lines[0].endswith('\n'):
667 elif self.lines[0].endswith('\n'):
668 self.eol = '\n'
668 self.eol = '\n'
669 if eolmode != 'strict':
669 if eolmode != 'strict':
670 nlines = []
670 nlines = []
671 for l in self.lines:
671 for l in self.lines:
672 if l.endswith('\r\n'):
672 if l.endswith('\r\n'):
673 l = l[:-2] + '\n'
673 l = l[:-2] + '\n'
674 nlines.append(l)
674 nlines.append(l)
675 self.lines = nlines
675 self.lines = nlines
676 else:
676 else:
677 if self.create:
677 if self.create:
678 self.missing = False
678 self.missing = False
679 if self.mode is None:
679 if self.mode is None:
680 self.mode = (False, False)
680 self.mode = (False, False)
681 if self.missing:
681 if self.missing:
682 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
682 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
683 self.ui.warn(_("(use '--prefix' to apply patch relative to the "
683 self.ui.warn(_("(use '--prefix' to apply patch relative to the "
684 "current directory)\n"))
684 "current directory)\n"))
685
685
686 self.hash = {}
686 self.hash = {}
687 self.dirty = 0
687 self.dirty = 0
688 self.offset = 0
688 self.offset = 0
689 self.skew = 0
689 self.skew = 0
690 self.rej = []
690 self.rej = []
691 self.fileprinted = False
691 self.fileprinted = False
692 self.printfile(False)
692 self.printfile(False)
693 self.hunks = 0
693 self.hunks = 0
694
694
695 def writelines(self, fname, lines, mode):
695 def writelines(self, fname, lines, mode):
696 if self.eolmode == 'auto':
696 if self.eolmode == 'auto':
697 eol = self.eol
697 eol = self.eol
698 elif self.eolmode == 'crlf':
698 elif self.eolmode == 'crlf':
699 eol = '\r\n'
699 eol = '\r\n'
700 else:
700 else:
701 eol = '\n'
701 eol = '\n'
702
702
703 if self.eolmode != 'strict' and eol and eol != '\n':
703 if self.eolmode != 'strict' and eol and eol != '\n':
704 rawlines = []
704 rawlines = []
705 for l in lines:
705 for l in lines:
706 if l and l[-1] == '\n':
706 if l and l[-1] == '\n':
707 l = l[:-1] + eol
707 l = l[:-1] + eol
708 rawlines.append(l)
708 rawlines.append(l)
709 lines = rawlines
709 lines = rawlines
710
710
711 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
711 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
712
712
713 def printfile(self, warn):
713 def printfile(self, warn):
714 if self.fileprinted:
714 if self.fileprinted:
715 return
715 return
716 if warn or self.ui.verbose:
716 if warn or self.ui.verbose:
717 self.fileprinted = True
717 self.fileprinted = True
718 s = _("patching file %s\n") % self.fname
718 s = _("patching file %s\n") % self.fname
719 if warn:
719 if warn:
720 self.ui.warn(s)
720 self.ui.warn(s)
721 else:
721 else:
722 self.ui.note(s)
722 self.ui.note(s)
723
723
724
724
725 def findlines(self, l, linenum):
725 def findlines(self, l, linenum):
726 # looks through the hash and finds candidate lines. The
726 # looks through the hash and finds candidate lines. The
727 # result is a list of line numbers sorted based on distance
727 # result is a list of line numbers sorted based on distance
728 # from linenum
728 # from linenum
729
729
730 cand = self.hash.get(l, [])
730 cand = self.hash.get(l, [])
731 if len(cand) > 1:
731 if len(cand) > 1:
732 # resort our list of potentials forward then back.
732 # resort our list of potentials forward then back.
733 cand.sort(key=lambda x: abs(x - linenum))
733 cand.sort(key=lambda x: abs(x - linenum))
734 return cand
734 return cand
735
735
736 def write_rej(self):
736 def write_rej(self):
737 # our rejects are a little different from patch(1). This always
737 # our rejects are a little different from patch(1). This always
738 # creates rejects in the same form as the original patch. A file
738 # creates rejects in the same form as the original patch. A file
739 # header is inserted so that you can run the reject through patch again
739 # header is inserted so that you can run the reject through patch again
740 # without having to type the filename.
740 # without having to type the filename.
741 if not self.rej:
741 if not self.rej:
742 return
742 return
743 base = os.path.basename(self.fname)
743 base = os.path.basename(self.fname)
744 lines = ["--- %s\n+++ %s\n" % (base, base)]
744 lines = ["--- %s\n+++ %s\n" % (base, base)]
745 for x in self.rej:
745 for x in self.rej:
746 for l in x.hunk:
746 for l in x.hunk:
747 lines.append(l)
747 lines.append(l)
748 if l[-1:] != '\n':
748 if l[-1:] != '\n':
749 lines.append("\n\ No newline at end of file\n")
749 lines.append("\n\ No newline at end of file\n")
750 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
750 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
751
751
752 def apply(self, h):
752 def apply(self, h):
753 if not h.complete():
753 if not h.complete():
754 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
754 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
755 (h.number, h.desc, len(h.a), h.lena, len(h.b),
755 (h.number, h.desc, len(h.a), h.lena, len(h.b),
756 h.lenb))
756 h.lenb))
757
757
758 self.hunks += 1
758 self.hunks += 1
759
759
760 if self.missing:
760 if self.missing:
761 self.rej.append(h)
761 self.rej.append(h)
762 return -1
762 return -1
763
763
764 if self.exists and self.create:
764 if self.exists and self.create:
765 if self.copysource:
765 if self.copysource:
766 self.ui.warn(_("cannot create %s: destination already "
766 self.ui.warn(_("cannot create %s: destination already "
767 "exists\n") % self.fname)
767 "exists\n") % self.fname)
768 else:
768 else:
769 self.ui.warn(_("file %s already exists\n") % self.fname)
769 self.ui.warn(_("file %s already exists\n") % self.fname)
770 self.rej.append(h)
770 self.rej.append(h)
771 return -1
771 return -1
772
772
773 if isinstance(h, binhunk):
773 if isinstance(h, binhunk):
774 if self.remove:
774 if self.remove:
775 self.backend.unlink(self.fname)
775 self.backend.unlink(self.fname)
776 else:
776 else:
777 l = h.new(self.lines)
777 l = h.new(self.lines)
778 self.lines[:] = l
778 self.lines[:] = l
779 self.offset += len(l)
779 self.offset += len(l)
780 self.dirty = True
780 self.dirty = True
781 return 0
781 return 0
782
782
783 horig = h
783 horig = h
784 if (self.eolmode in ('crlf', 'lf')
784 if (self.eolmode in ('crlf', 'lf')
785 or self.eolmode == 'auto' and self.eol):
785 or self.eolmode == 'auto' and self.eol):
786 # If new eols are going to be normalized, then normalize
786 # If new eols are going to be normalized, then normalize
787 # hunk data before patching. Otherwise, preserve input
787 # hunk data before patching. Otherwise, preserve input
788 # line-endings.
788 # line-endings.
789 h = h.getnormalized()
789 h = h.getnormalized()
790
790
791 # fast case first, no offsets, no fuzz
791 # fast case first, no offsets, no fuzz
792 old, oldstart, new, newstart = h.fuzzit(0, False)
792 old, oldstart, new, newstart = h.fuzzit(0, False)
793 oldstart += self.offset
793 oldstart += self.offset
794 orig_start = oldstart
794 orig_start = oldstart
795 # if there's skew we want to emit the "(offset %d lines)" even
795 # if there's skew we want to emit the "(offset %d lines)" even
796 # when the hunk cleanly applies at start + skew, so skip the
796 # when the hunk cleanly applies at start + skew, so skip the
797 # fast case code
797 # fast case code
798 if (self.skew == 0 and
798 if (self.skew == 0 and
799 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
799 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
800 if self.remove:
800 if self.remove:
801 self.backend.unlink(self.fname)
801 self.backend.unlink(self.fname)
802 else:
802 else:
803 self.lines[oldstart:oldstart + len(old)] = new
803 self.lines[oldstart:oldstart + len(old)] = new
804 self.offset += len(new) - len(old)
804 self.offset += len(new) - len(old)
805 self.dirty = True
805 self.dirty = True
806 return 0
806 return 0
807
807
808 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
808 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
809 self.hash = {}
809 self.hash = {}
810 for x, s in enumerate(self.lines):
810 for x, s in enumerate(self.lines):
811 self.hash.setdefault(s, []).append(x)
811 self.hash.setdefault(s, []).append(x)
812
812
813 for fuzzlen in xrange(self.ui.configint("patch", "fuzz") + 1):
813 for fuzzlen in xrange(self.ui.configint("patch", "fuzz") + 1):
814 for toponly in [True, False]:
814 for toponly in [True, False]:
815 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
815 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
816 oldstart = oldstart + self.offset + self.skew
816 oldstart = oldstart + self.offset + self.skew
817 oldstart = min(oldstart, len(self.lines))
817 oldstart = min(oldstart, len(self.lines))
818 if old:
818 if old:
819 cand = self.findlines(old[0][1:], oldstart)
819 cand = self.findlines(old[0][1:], oldstart)
820 else:
820 else:
821 # Only adding lines with no or fuzzed context, just
821 # Only adding lines with no or fuzzed context, just
822 # take the skew in account
822 # take the skew in account
823 cand = [oldstart]
823 cand = [oldstart]
824
824
825 for l in cand:
825 for l in cand:
826 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
826 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
827 self.lines[l : l + len(old)] = new
827 self.lines[l : l + len(old)] = new
828 self.offset += len(new) - len(old)
828 self.offset += len(new) - len(old)
829 self.skew = l - orig_start
829 self.skew = l - orig_start
830 self.dirty = True
830 self.dirty = True
831 offset = l - orig_start - fuzzlen
831 offset = l - orig_start - fuzzlen
832 if fuzzlen:
832 if fuzzlen:
833 msg = _("Hunk #%d succeeded at %d "
833 msg = _("Hunk #%d succeeded at %d "
834 "with fuzz %d "
834 "with fuzz %d "
835 "(offset %d lines).\n")
835 "(offset %d lines).\n")
836 self.printfile(True)
836 self.printfile(True)
837 self.ui.warn(msg %
837 self.ui.warn(msg %
838 (h.number, l + 1, fuzzlen, offset))
838 (h.number, l + 1, fuzzlen, offset))
839 else:
839 else:
840 msg = _("Hunk #%d succeeded at %d "
840 msg = _("Hunk #%d succeeded at %d "
841 "(offset %d lines).\n")
841 "(offset %d lines).\n")
842 self.ui.note(msg % (h.number, l + 1, offset))
842 self.ui.note(msg % (h.number, l + 1, offset))
843 return fuzzlen
843 return fuzzlen
844 self.printfile(True)
844 self.printfile(True)
845 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
845 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
846 self.rej.append(horig)
846 self.rej.append(horig)
847 return -1
847 return -1
848
848
849 def close(self):
849 def close(self):
850 if self.dirty:
850 if self.dirty:
851 self.writelines(self.fname, self.lines, self.mode)
851 self.writelines(self.fname, self.lines, self.mode)
852 self.write_rej()
852 self.write_rej()
853 return len(self.rej)
853 return len(self.rej)
854
854
855 class header(object):
855 class header(object):
856 """patch header
856 """patch header
857 """
857 """
858 diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
858 diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
859 diff_re = re.compile('diff -r .* (.*)$')
859 diff_re = re.compile('diff -r .* (.*)$')
860 allhunks_re = re.compile('(?:index|deleted file) ')
860 allhunks_re = re.compile('(?:index|deleted file) ')
861 pretty_re = re.compile('(?:new file|deleted file) ')
861 pretty_re = re.compile('(?:new file|deleted file) ')
862 special_re = re.compile('(?:index|deleted|copy|rename) ')
862 special_re = re.compile('(?:index|deleted|copy|rename) ')
863 newfile_re = re.compile('(?:new file)')
863 newfile_re = re.compile('(?:new file)')
864
864
865 def __init__(self, header):
865 def __init__(self, header):
866 self.header = header
866 self.header = header
867 self.hunks = []
867 self.hunks = []
868
868
869 def binary(self):
869 def binary(self):
870 return any(h.startswith('index ') for h in self.header)
870 return any(h.startswith('index ') for h in self.header)
871
871
872 def pretty(self, fp):
872 def pretty(self, fp):
873 for h in self.header:
873 for h in self.header:
874 if h.startswith('index '):
874 if h.startswith('index '):
875 fp.write(_('this modifies a binary file (all or nothing)\n'))
875 fp.write(_('this modifies a binary file (all or nothing)\n'))
876 break
876 break
877 if self.pretty_re.match(h):
877 if self.pretty_re.match(h):
878 fp.write(h)
878 fp.write(h)
879 if self.binary():
879 if self.binary():
880 fp.write(_('this is a binary file\n'))
880 fp.write(_('this is a binary file\n'))
881 break
881 break
882 if h.startswith('---'):
882 if h.startswith('---'):
883 fp.write(_('%d hunks, %d lines changed\n') %
883 fp.write(_('%d hunks, %d lines changed\n') %
884 (len(self.hunks),
884 (len(self.hunks),
885 sum([max(h.added, h.removed) for h in self.hunks])))
885 sum([max(h.added, h.removed) for h in self.hunks])))
886 break
886 break
887 fp.write(h)
887 fp.write(h)
888
888
889 def write(self, fp):
889 def write(self, fp):
890 fp.write(''.join(self.header))
890 fp.write(''.join(self.header))
891
891
892 def allhunks(self):
892 def allhunks(self):
893 return any(self.allhunks_re.match(h) for h in self.header)
893 return any(self.allhunks_re.match(h) for h in self.header)
894
894
895 def files(self):
895 def files(self):
896 match = self.diffgit_re.match(self.header[0])
896 match = self.diffgit_re.match(self.header[0])
897 if match:
897 if match:
898 fromfile, tofile = match.groups()
898 fromfile, tofile = match.groups()
899 if fromfile == tofile:
899 if fromfile == tofile:
900 return [fromfile]
900 return [fromfile]
901 return [fromfile, tofile]
901 return [fromfile, tofile]
902 else:
902 else:
903 return self.diff_re.match(self.header[0]).groups()
903 return self.diff_re.match(self.header[0]).groups()
904
904
905 def filename(self):
905 def filename(self):
906 return self.files()[-1]
906 return self.files()[-1]
907
907
908 def __repr__(self):
908 def __repr__(self):
909 return '<header %s>' % (' '.join(map(repr, self.files())))
909 return '<header %s>' % (' '.join(map(repr, self.files())))
910
910
911 def isnewfile(self):
911 def isnewfile(self):
912 return any(self.newfile_re.match(h) for h in self.header)
912 return any(self.newfile_re.match(h) for h in self.header)
913
913
914 def special(self):
914 def special(self):
915 # Special files are shown only at the header level and not at the hunk
915 # Special files are shown only at the header level and not at the hunk
916 # level for example a file that has been deleted is a special file.
916 # level for example a file that has been deleted is a special file.
917 # The user cannot change the content of the operation, in the case of
917 # The user cannot change the content of the operation, in the case of
918 # the deleted file he has to take the deletion or not take it, he
918 # the deleted file he has to take the deletion or not take it, he
919 # cannot take some of it.
919 # cannot take some of it.
920 # Newly added files are special if they are empty, they are not special
920 # Newly added files are special if they are empty, they are not special
921 # if they have some content as we want to be able to change it
921 # if they have some content as we want to be able to change it
922 nocontent = len(self.header) == 2
922 nocontent = len(self.header) == 2
923 emptynewfile = self.isnewfile() and nocontent
923 emptynewfile = self.isnewfile() and nocontent
924 return emptynewfile or \
924 return emptynewfile or \
925 any(self.special_re.match(h) for h in self.header)
925 any(self.special_re.match(h) for h in self.header)
926
926
927 class recordhunk(object):
927 class recordhunk(object):
928 """patch hunk
928 """patch hunk
929
929
930 XXX shouldn't we merge this with the other hunk class?
930 XXX shouldn't we merge this with the other hunk class?
931 """
931 """
932
932
933 def __init__(self, header, fromline, toline, proc, before, hunk, after,
933 def __init__(self, header, fromline, toline, proc, before, hunk, after,
934 maxcontext=None):
934 maxcontext=None):
935 def trimcontext(lines, reverse=False):
935 def trimcontext(lines, reverse=False):
936 if maxcontext is not None:
936 if maxcontext is not None:
937 delta = len(lines) - maxcontext
937 delta = len(lines) - maxcontext
938 if delta > 0:
938 if delta > 0:
939 if reverse:
939 if reverse:
940 return delta, lines[delta:]
940 return delta, lines[delta:]
941 else:
941 else:
942 return delta, lines[:maxcontext]
942 return delta, lines[:maxcontext]
943 return 0, lines
943 return 0, lines
944
944
945 self.header = header
945 self.header = header
946 trimedbefore, self.before = trimcontext(before, True)
946 trimedbefore, self.before = trimcontext(before, True)
947 self.fromline = fromline + trimedbefore
947 self.fromline = fromline + trimedbefore
948 self.toline = toline + trimedbefore
948 self.toline = toline + trimedbefore
949 _trimedafter, self.after = trimcontext(after, False)
949 _trimedafter, self.after = trimcontext(after, False)
950 self.proc = proc
950 self.proc = proc
951 self.hunk = hunk
951 self.hunk = hunk
952 self.added, self.removed = self.countchanges(self.hunk)
952 self.added, self.removed = self.countchanges(self.hunk)
953
953
954 def __eq__(self, v):
954 def __eq__(self, v):
955 if not isinstance(v, recordhunk):
955 if not isinstance(v, recordhunk):
956 return False
956 return False
957
957
958 return ((v.hunk == self.hunk) and
958 return ((v.hunk == self.hunk) and
959 (v.proc == self.proc) and
959 (v.proc == self.proc) and
960 (self.fromline == v.fromline) and
960 (self.fromline == v.fromline) and
961 (self.header.files() == v.header.files()))
961 (self.header.files() == v.header.files()))
962
962
963 def __hash__(self):
963 def __hash__(self):
964 return hash((tuple(self.hunk),
964 return hash((tuple(self.hunk),
965 tuple(self.header.files()),
965 tuple(self.header.files()),
966 self.fromline,
966 self.fromline,
967 self.proc))
967 self.proc))
968
968
969 def countchanges(self, hunk):
969 def countchanges(self, hunk):
970 """hunk -> (n+,n-)"""
970 """hunk -> (n+,n-)"""
971 add = len([h for h in hunk if h.startswith('+')])
971 add = len([h for h in hunk if h.startswith('+')])
972 rem = len([h for h in hunk if h.startswith('-')])
972 rem = len([h for h in hunk if h.startswith('-')])
973 return add, rem
973 return add, rem
974
974
975 def reversehunk(self):
975 def reversehunk(self):
976 """return another recordhunk which is the reverse of the hunk
976 """return another recordhunk which is the reverse of the hunk
977
977
978 If this hunk is diff(A, B), the returned hunk is diff(B, A). To do
978 If this hunk is diff(A, B), the returned hunk is diff(B, A). To do
979 that, swap fromline/toline and +/- signs while keep other things
979 that, swap fromline/toline and +/- signs while keep other things
980 unchanged.
980 unchanged.
981 """
981 """
982 m = {'+': '-', '-': '+', '\\': '\\'}
982 m = {'+': '-', '-': '+', '\\': '\\'}
983 hunk = ['%s%s' % (m[l[0:1]], l[1:]) for l in self.hunk]
983 hunk = ['%s%s' % (m[l[0:1]], l[1:]) for l in self.hunk]
984 return recordhunk(self.header, self.toline, self.fromline, self.proc,
984 return recordhunk(self.header, self.toline, self.fromline, self.proc,
985 self.before, hunk, self.after)
985 self.before, hunk, self.after)
986
986
987 def write(self, fp):
987 def write(self, fp):
988 delta = len(self.before) + len(self.after)
988 delta = len(self.before) + len(self.after)
989 if self.after and self.after[-1] == '\\ No newline at end of file\n':
989 if self.after and self.after[-1] == '\\ No newline at end of file\n':
990 delta -= 1
990 delta -= 1
991 fromlen = delta + self.removed
991 fromlen = delta + self.removed
992 tolen = delta + self.added
992 tolen = delta + self.added
993 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
993 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
994 (self.fromline, fromlen, self.toline, tolen,
994 (self.fromline, fromlen, self.toline, tolen,
995 self.proc and (' ' + self.proc)))
995 self.proc and (' ' + self.proc)))
996 fp.write(''.join(self.before + self.hunk + self.after))
996 fp.write(''.join(self.before + self.hunk + self.after))
997
997
998 pretty = write
998 pretty = write
999
999
1000 def filename(self):
1000 def filename(self):
1001 return self.header.filename()
1001 return self.header.filename()
1002
1002
1003 def __repr__(self):
1003 def __repr__(self):
1004 return '<hunk %r@%d>' % (self.filename(), self.fromline)
1004 return '<hunk %r@%d>' % (self.filename(), self.fromline)
1005
1005
1006 def getmessages():
1006 def getmessages():
1007 return {
1007 return {
1008 'multiple': {
1008 'multiple': {
1009 'apply': _("apply change %d/%d to '%s'?"),
1009 'apply': _("apply change %d/%d to '%s'?"),
1010 'discard': _("discard change %d/%d to '%s'?"),
1010 'discard': _("discard change %d/%d to '%s'?"),
1011 'record': _("record change %d/%d to '%s'?"),
1011 'record': _("record change %d/%d to '%s'?"),
1012 },
1012 },
1013 'single': {
1013 'single': {
1014 'apply': _("apply this change to '%s'?"),
1014 'apply': _("apply this change to '%s'?"),
1015 'discard': _("discard this change to '%s'?"),
1015 'discard': _("discard this change to '%s'?"),
1016 'record': _("record this change to '%s'?"),
1016 'record': _("record this change to '%s'?"),
1017 },
1017 },
1018 'help': {
1018 'help': {
1019 'apply': _('[Ynesfdaq?]'
1019 'apply': _('[Ynesfdaq?]'
1020 '$$ &Yes, apply this change'
1020 '$$ &Yes, apply this change'
1021 '$$ &No, skip this change'
1021 '$$ &No, skip this change'
1022 '$$ &Edit this change manually'
1022 '$$ &Edit this change manually'
1023 '$$ &Skip remaining changes to this file'
1023 '$$ &Skip remaining changes to this file'
1024 '$$ Apply remaining changes to this &file'
1024 '$$ Apply remaining changes to this &file'
1025 '$$ &Done, skip remaining changes and files'
1025 '$$ &Done, skip remaining changes and files'
1026 '$$ Apply &all changes to all remaining files'
1026 '$$ Apply &all changes to all remaining files'
1027 '$$ &Quit, applying no changes'
1027 '$$ &Quit, applying no changes'
1028 '$$ &? (display help)'),
1028 '$$ &? (display help)'),
1029 'discard': _('[Ynesfdaq?]'
1029 'discard': _('[Ynesfdaq?]'
1030 '$$ &Yes, discard this change'
1030 '$$ &Yes, discard this change'
1031 '$$ &No, skip this change'
1031 '$$ &No, skip this change'
1032 '$$ &Edit this change manually'
1032 '$$ &Edit this change manually'
1033 '$$ &Skip remaining changes to this file'
1033 '$$ &Skip remaining changes to this file'
1034 '$$ Discard remaining changes to this &file'
1034 '$$ Discard remaining changes to this &file'
1035 '$$ &Done, skip remaining changes and files'
1035 '$$ &Done, skip remaining changes and files'
1036 '$$ Discard &all changes to all remaining files'
1036 '$$ Discard &all changes to all remaining files'
1037 '$$ &Quit, discarding no changes'
1037 '$$ &Quit, discarding no changes'
1038 '$$ &? (display help)'),
1038 '$$ &? (display help)'),
1039 'record': _('[Ynesfdaq?]'
1039 'record': _('[Ynesfdaq?]'
1040 '$$ &Yes, record this change'
1040 '$$ &Yes, record this change'
1041 '$$ &No, skip this change'
1041 '$$ &No, skip this change'
1042 '$$ &Edit this change manually'
1042 '$$ &Edit this change manually'
1043 '$$ &Skip remaining changes to this file'
1043 '$$ &Skip remaining changes to this file'
1044 '$$ Record remaining changes to this &file'
1044 '$$ Record remaining changes to this &file'
1045 '$$ &Done, skip remaining changes and files'
1045 '$$ &Done, skip remaining changes and files'
1046 '$$ Record &all changes to all remaining files'
1046 '$$ Record &all changes to all remaining files'
1047 '$$ &Quit, recording no changes'
1047 '$$ &Quit, recording no changes'
1048 '$$ &? (display help)'),
1048 '$$ &? (display help)'),
1049 }
1049 }
1050 }
1050 }
1051
1051
1052 def filterpatch(ui, headers, operation=None):
1052 def filterpatch(ui, headers, operation=None):
1053 """Interactively filter patch chunks into applied-only chunks"""
1053 """Interactively filter patch chunks into applied-only chunks"""
1054 messages = getmessages()
1054 messages = getmessages()
1055
1055
1056 if operation is None:
1056 if operation is None:
1057 operation = 'record'
1057 operation = 'record'
1058
1058
1059 def prompt(skipfile, skipall, query, chunk):
1059 def prompt(skipfile, skipall, query, chunk):
1060 """prompt query, and process base inputs
1060 """prompt query, and process base inputs
1061
1061
1062 - y/n for the rest of file
1062 - y/n for the rest of file
1063 - y/n for the rest
1063 - y/n for the rest
1064 - ? (help)
1064 - ? (help)
1065 - q (quit)
1065 - q (quit)
1066
1066
1067 Return True/False and possibly updated skipfile and skipall.
1067 Return True/False and possibly updated skipfile and skipall.
1068 """
1068 """
1069 newpatches = None
1069 newpatches = None
1070 if skipall is not None:
1070 if skipall is not None:
1071 return skipall, skipfile, skipall, newpatches
1071 return skipall, skipfile, skipall, newpatches
1072 if skipfile is not None:
1072 if skipfile is not None:
1073 return skipfile, skipfile, skipall, newpatches
1073 return skipfile, skipfile, skipall, newpatches
1074 while True:
1074 while True:
1075 resps = messages['help'][operation]
1075 resps = messages['help'][operation]
1076 r = ui.promptchoice("%s %s" % (query, resps))
1076 r = ui.promptchoice("%s %s" % (query, resps))
1077 ui.write("\n")
1077 ui.write("\n")
1078 if r == 8: # ?
1078 if r == 8: # ?
1079 for c, t in ui.extractchoices(resps)[1]:
1079 for c, t in ui.extractchoices(resps)[1]:
1080 ui.write('%s - %s\n' % (c, encoding.lower(t)))
1080 ui.write('%s - %s\n' % (c, encoding.lower(t)))
1081 continue
1081 continue
1082 elif r == 0: # yes
1082 elif r == 0: # yes
1083 ret = True
1083 ret = True
1084 elif r == 1: # no
1084 elif r == 1: # no
1085 ret = False
1085 ret = False
1086 elif r == 2: # Edit patch
1086 elif r == 2: # Edit patch
1087 if chunk is None:
1087 if chunk is None:
1088 ui.write(_('cannot edit patch for whole file'))
1088 ui.write(_('cannot edit patch for whole file'))
1089 ui.write("\n")
1089 ui.write("\n")
1090 continue
1090 continue
1091 if chunk.header.binary():
1091 if chunk.header.binary():
1092 ui.write(_('cannot edit patch for binary file'))
1092 ui.write(_('cannot edit patch for binary file'))
1093 ui.write("\n")
1093 ui.write("\n")
1094 continue
1094 continue
1095 # Patch comment based on the Git one (based on comment at end of
1095 # Patch comment based on the Git one (based on comment at end of
1096 # https://mercurial-scm.org/wiki/RecordExtension)
1096 # https://mercurial-scm.org/wiki/RecordExtension)
1097 phelp = '---' + _("""
1097 phelp = '---' + _("""
1098 To remove '-' lines, make them ' ' lines (context).
1098 To remove '-' lines, make them ' ' lines (context).
1099 To remove '+' lines, delete them.
1099 To remove '+' lines, delete them.
1100 Lines starting with # will be removed from the patch.
1100 Lines starting with # will be removed from the patch.
1101
1101
1102 If the patch applies cleanly, the edited hunk will immediately be
1102 If the patch applies cleanly, the edited hunk will immediately be
1103 added to the record list. If it does not apply cleanly, a rejects
1103 added to the record list. If it does not apply cleanly, a rejects
1104 file will be generated: you can use that when you try again. If
1104 file will be generated: you can use that when you try again. If
1105 all lines of the hunk are removed, then the edit is aborted and
1105 all lines of the hunk are removed, then the edit is aborted and
1106 the hunk is left unchanged.
1106 the hunk is left unchanged.
1107 """)
1107 """)
1108 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
1108 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
1109 suffix=".diff")
1109 suffix=".diff")
1110 ncpatchfp = None
1110 ncpatchfp = None
1111 try:
1111 try:
1112 # Write the initial patch
1112 # Write the initial patch
1113 f = util.nativeeolwriter(os.fdopen(patchfd, r'wb'))
1113 f = util.nativeeolwriter(os.fdopen(patchfd, r'wb'))
1114 chunk.header.write(f)
1114 chunk.header.write(f)
1115 chunk.write(f)
1115 chunk.write(f)
1116 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
1116 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
1117 f.close()
1117 f.close()
1118 # Start the editor and wait for it to complete
1118 # Start the editor and wait for it to complete
1119 editor = ui.geteditor()
1119 editor = ui.geteditor()
1120 ret = ui.system("%s \"%s\"" % (editor, patchfn),
1120 ret = ui.system("%s \"%s\"" % (editor, patchfn),
1121 environ={'HGUSER': ui.username()},
1121 environ={'HGUSER': ui.username()},
1122 blockedtag='filterpatch')
1122 blockedtag='filterpatch')
1123 if ret != 0:
1123 if ret != 0:
1124 ui.warn(_("editor exited with exit code %d\n") % ret)
1124 ui.warn(_("editor exited with exit code %d\n") % ret)
1125 continue
1125 continue
1126 # Remove comment lines
1126 # Remove comment lines
1127 patchfp = open(patchfn, r'rb')
1127 patchfp = open(patchfn, r'rb')
1128 ncpatchfp = stringio()
1128 ncpatchfp = stringio()
1129 for line in util.iterfile(patchfp):
1129 for line in util.iterfile(patchfp):
1130 line = util.fromnativeeol(line)
1130 line = util.fromnativeeol(line)
1131 if not line.startswith('#'):
1131 if not line.startswith('#'):
1132 ncpatchfp.write(line)
1132 ncpatchfp.write(line)
1133 patchfp.close()
1133 patchfp.close()
1134 ncpatchfp.seek(0)
1134 ncpatchfp.seek(0)
1135 newpatches = parsepatch(ncpatchfp)
1135 newpatches = parsepatch(ncpatchfp)
1136 finally:
1136 finally:
1137 os.unlink(patchfn)
1137 os.unlink(patchfn)
1138 del ncpatchfp
1138 del ncpatchfp
1139 # Signal that the chunk shouldn't be applied as-is, but
1139 # Signal that the chunk shouldn't be applied as-is, but
1140 # provide the new patch to be used instead.
1140 # provide the new patch to be used instead.
1141 ret = False
1141 ret = False
1142 elif r == 3: # Skip
1142 elif r == 3: # Skip
1143 ret = skipfile = False
1143 ret = skipfile = False
1144 elif r == 4: # file (Record remaining)
1144 elif r == 4: # file (Record remaining)
1145 ret = skipfile = True
1145 ret = skipfile = True
1146 elif r == 5: # done, skip remaining
1146 elif r == 5: # done, skip remaining
1147 ret = skipall = False
1147 ret = skipall = False
1148 elif r == 6: # all
1148 elif r == 6: # all
1149 ret = skipall = True
1149 ret = skipall = True
1150 elif r == 7: # quit
1150 elif r == 7: # quit
1151 raise error.Abort(_('user quit'))
1151 raise error.Abort(_('user quit'))
1152 return ret, skipfile, skipall, newpatches
1152 return ret, skipfile, skipall, newpatches
1153
1153
1154 seen = set()
1154 seen = set()
1155 applied = {} # 'filename' -> [] of chunks
1155 applied = {} # 'filename' -> [] of chunks
1156 skipfile, skipall = None, None
1156 skipfile, skipall = None, None
1157 pos, total = 1, sum(len(h.hunks) for h in headers)
1157 pos, total = 1, sum(len(h.hunks) for h in headers)
1158 for h in headers:
1158 for h in headers:
1159 pos += len(h.hunks)
1159 pos += len(h.hunks)
1160 skipfile = None
1160 skipfile = None
1161 fixoffset = 0
1161 fixoffset = 0
1162 hdr = ''.join(h.header)
1162 hdr = ''.join(h.header)
1163 if hdr in seen:
1163 if hdr in seen:
1164 continue
1164 continue
1165 seen.add(hdr)
1165 seen.add(hdr)
1166 if skipall is None:
1166 if skipall is None:
1167 h.pretty(ui)
1167 h.pretty(ui)
1168 msg = (_('examine changes to %s?') %
1168 msg = (_('examine changes to %s?') %
1169 _(' and ').join("'%s'" % f for f in h.files()))
1169 _(' and ').join("'%s'" % f for f in h.files()))
1170 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
1170 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
1171 if not r:
1171 if not r:
1172 continue
1172 continue
1173 applied[h.filename()] = [h]
1173 applied[h.filename()] = [h]
1174 if h.allhunks():
1174 if h.allhunks():
1175 applied[h.filename()] += h.hunks
1175 applied[h.filename()] += h.hunks
1176 continue
1176 continue
1177 for i, chunk in enumerate(h.hunks):
1177 for i, chunk in enumerate(h.hunks):
1178 if skipfile is None and skipall is None:
1178 if skipfile is None and skipall is None:
1179 chunk.pretty(ui)
1179 chunk.pretty(ui)
1180 if total == 1:
1180 if total == 1:
1181 msg = messages['single'][operation] % chunk.filename()
1181 msg = messages['single'][operation] % chunk.filename()
1182 else:
1182 else:
1183 idx = pos - len(h.hunks) + i
1183 idx = pos - len(h.hunks) + i
1184 msg = messages['multiple'][operation] % (idx, total,
1184 msg = messages['multiple'][operation] % (idx, total,
1185 chunk.filename())
1185 chunk.filename())
1186 r, skipfile, skipall, newpatches = prompt(skipfile,
1186 r, skipfile, skipall, newpatches = prompt(skipfile,
1187 skipall, msg, chunk)
1187 skipall, msg, chunk)
1188 if r:
1188 if r:
1189 if fixoffset:
1189 if fixoffset:
1190 chunk = copy.copy(chunk)
1190 chunk = copy.copy(chunk)
1191 chunk.toline += fixoffset
1191 chunk.toline += fixoffset
1192 applied[chunk.filename()].append(chunk)
1192 applied[chunk.filename()].append(chunk)
1193 elif newpatches is not None:
1193 elif newpatches is not None:
1194 for newpatch in newpatches:
1194 for newpatch in newpatches:
1195 for newhunk in newpatch.hunks:
1195 for newhunk in newpatch.hunks:
1196 if fixoffset:
1196 if fixoffset:
1197 newhunk.toline += fixoffset
1197 newhunk.toline += fixoffset
1198 applied[newhunk.filename()].append(newhunk)
1198 applied[newhunk.filename()].append(newhunk)
1199 else:
1199 else:
1200 fixoffset += chunk.removed - chunk.added
1200 fixoffset += chunk.removed - chunk.added
1201 return (sum([h for h in applied.itervalues()
1201 return (sum([h for h in applied.itervalues()
1202 if h[0].special() or len(h) > 1], []), {})
1202 if h[0].special() or len(h) > 1], []), {})
1203 class hunk(object):
1203 class hunk(object):
1204 def __init__(self, desc, num, lr, context):
1204 def __init__(self, desc, num, lr, context):
1205 self.number = num
1205 self.number = num
1206 self.desc = desc
1206 self.desc = desc
1207 self.hunk = [desc]
1207 self.hunk = [desc]
1208 self.a = []
1208 self.a = []
1209 self.b = []
1209 self.b = []
1210 self.starta = self.lena = None
1210 self.starta = self.lena = None
1211 self.startb = self.lenb = None
1211 self.startb = self.lenb = None
1212 if lr is not None:
1212 if lr is not None:
1213 if context:
1213 if context:
1214 self.read_context_hunk(lr)
1214 self.read_context_hunk(lr)
1215 else:
1215 else:
1216 self.read_unified_hunk(lr)
1216 self.read_unified_hunk(lr)
1217
1217
1218 def getnormalized(self):
1218 def getnormalized(self):
1219 """Return a copy with line endings normalized to LF."""
1219 """Return a copy with line endings normalized to LF."""
1220
1220
1221 def normalize(lines):
1221 def normalize(lines):
1222 nlines = []
1222 nlines = []
1223 for line in lines:
1223 for line in lines:
1224 if line.endswith('\r\n'):
1224 if line.endswith('\r\n'):
1225 line = line[:-2] + '\n'
1225 line = line[:-2] + '\n'
1226 nlines.append(line)
1226 nlines.append(line)
1227 return nlines
1227 return nlines
1228
1228
1229 # Dummy object, it is rebuilt manually
1229 # Dummy object, it is rebuilt manually
1230 nh = hunk(self.desc, self.number, None, None)
1230 nh = hunk(self.desc, self.number, None, None)
1231 nh.number = self.number
1231 nh.number = self.number
1232 nh.desc = self.desc
1232 nh.desc = self.desc
1233 nh.hunk = self.hunk
1233 nh.hunk = self.hunk
1234 nh.a = normalize(self.a)
1234 nh.a = normalize(self.a)
1235 nh.b = normalize(self.b)
1235 nh.b = normalize(self.b)
1236 nh.starta = self.starta
1236 nh.starta = self.starta
1237 nh.startb = self.startb
1237 nh.startb = self.startb
1238 nh.lena = self.lena
1238 nh.lena = self.lena
1239 nh.lenb = self.lenb
1239 nh.lenb = self.lenb
1240 return nh
1240 return nh
1241
1241
1242 def read_unified_hunk(self, lr):
1242 def read_unified_hunk(self, lr):
1243 m = unidesc.match(self.desc)
1243 m = unidesc.match(self.desc)
1244 if not m:
1244 if not m:
1245 raise PatchError(_("bad hunk #%d") % self.number)
1245 raise PatchError(_("bad hunk #%d") % self.number)
1246 self.starta, self.lena, self.startb, self.lenb = m.groups()
1246 self.starta, self.lena, self.startb, self.lenb = m.groups()
1247 if self.lena is None:
1247 if self.lena is None:
1248 self.lena = 1
1248 self.lena = 1
1249 else:
1249 else:
1250 self.lena = int(self.lena)
1250 self.lena = int(self.lena)
1251 if self.lenb is None:
1251 if self.lenb is None:
1252 self.lenb = 1
1252 self.lenb = 1
1253 else:
1253 else:
1254 self.lenb = int(self.lenb)
1254 self.lenb = int(self.lenb)
1255 self.starta = int(self.starta)
1255 self.starta = int(self.starta)
1256 self.startb = int(self.startb)
1256 self.startb = int(self.startb)
1257 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a,
1257 try:
1258 self.b)
1258 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb,
1259 self.a, self.b)
1260 except error.ParseError as e:
1261 raise PatchError(_("bad hunk #%d: %s") % (self.number, e))
1259 # if we hit eof before finishing out the hunk, the last line will
1262 # if we hit eof before finishing out the hunk, the last line will
1260 # be zero length. Lets try to fix it up.
1263 # be zero length. Lets try to fix it up.
1261 while len(self.hunk[-1]) == 0:
1264 while len(self.hunk[-1]) == 0:
1262 del self.hunk[-1]
1265 del self.hunk[-1]
1263 del self.a[-1]
1266 del self.a[-1]
1264 del self.b[-1]
1267 del self.b[-1]
1265 self.lena -= 1
1268 self.lena -= 1
1266 self.lenb -= 1
1269 self.lenb -= 1
1267 self._fixnewline(lr)
1270 self._fixnewline(lr)
1268
1271
1269 def read_context_hunk(self, lr):
1272 def read_context_hunk(self, lr):
1270 self.desc = lr.readline()
1273 self.desc = lr.readline()
1271 m = contextdesc.match(self.desc)
1274 m = contextdesc.match(self.desc)
1272 if not m:
1275 if not m:
1273 raise PatchError(_("bad hunk #%d") % self.number)
1276 raise PatchError(_("bad hunk #%d") % self.number)
1274 self.starta, aend = m.groups()
1277 self.starta, aend = m.groups()
1275 self.starta = int(self.starta)
1278 self.starta = int(self.starta)
1276 if aend is None:
1279 if aend is None:
1277 aend = self.starta
1280 aend = self.starta
1278 self.lena = int(aend) - self.starta
1281 self.lena = int(aend) - self.starta
1279 if self.starta:
1282 if self.starta:
1280 self.lena += 1
1283 self.lena += 1
1281 for x in xrange(self.lena):
1284 for x in xrange(self.lena):
1282 l = lr.readline()
1285 l = lr.readline()
1283 if l.startswith('---'):
1286 if l.startswith('---'):
1284 # lines addition, old block is empty
1287 # lines addition, old block is empty
1285 lr.push(l)
1288 lr.push(l)
1286 break
1289 break
1287 s = l[2:]
1290 s = l[2:]
1288 if l.startswith('- ') or l.startswith('! '):
1291 if l.startswith('- ') or l.startswith('! '):
1289 u = '-' + s
1292 u = '-' + s
1290 elif l.startswith(' '):
1293 elif l.startswith(' '):
1291 u = ' ' + s
1294 u = ' ' + s
1292 else:
1295 else:
1293 raise PatchError(_("bad hunk #%d old text line %d") %
1296 raise PatchError(_("bad hunk #%d old text line %d") %
1294 (self.number, x))
1297 (self.number, x))
1295 self.a.append(u)
1298 self.a.append(u)
1296 self.hunk.append(u)
1299 self.hunk.append(u)
1297
1300
1298 l = lr.readline()
1301 l = lr.readline()
1299 if l.startswith('\ '):
1302 if l.startswith('\ '):
1300 s = self.a[-1][:-1]
1303 s = self.a[-1][:-1]
1301 self.a[-1] = s
1304 self.a[-1] = s
1302 self.hunk[-1] = s
1305 self.hunk[-1] = s
1303 l = lr.readline()
1306 l = lr.readline()
1304 m = contextdesc.match(l)
1307 m = contextdesc.match(l)
1305 if not m:
1308 if not m:
1306 raise PatchError(_("bad hunk #%d") % self.number)
1309 raise PatchError(_("bad hunk #%d") % self.number)
1307 self.startb, bend = m.groups()
1310 self.startb, bend = m.groups()
1308 self.startb = int(self.startb)
1311 self.startb = int(self.startb)
1309 if bend is None:
1312 if bend is None:
1310 bend = self.startb
1313 bend = self.startb
1311 self.lenb = int(bend) - self.startb
1314 self.lenb = int(bend) - self.startb
1312 if self.startb:
1315 if self.startb:
1313 self.lenb += 1
1316 self.lenb += 1
1314 hunki = 1
1317 hunki = 1
1315 for x in xrange(self.lenb):
1318 for x in xrange(self.lenb):
1316 l = lr.readline()
1319 l = lr.readline()
1317 if l.startswith('\ '):
1320 if l.startswith('\ '):
1318 # XXX: the only way to hit this is with an invalid line range.
1321 # XXX: the only way to hit this is with an invalid line range.
1319 # The no-eol marker is not counted in the line range, but I
1322 # The no-eol marker is not counted in the line range, but I
1320 # guess there are diff(1) out there which behave differently.
1323 # guess there are diff(1) out there which behave differently.
1321 s = self.b[-1][:-1]
1324 s = self.b[-1][:-1]
1322 self.b[-1] = s
1325 self.b[-1] = s
1323 self.hunk[hunki - 1] = s
1326 self.hunk[hunki - 1] = s
1324 continue
1327 continue
1325 if not l:
1328 if not l:
1326 # line deletions, new block is empty and we hit EOF
1329 # line deletions, new block is empty and we hit EOF
1327 lr.push(l)
1330 lr.push(l)
1328 break
1331 break
1329 s = l[2:]
1332 s = l[2:]
1330 if l.startswith('+ ') or l.startswith('! '):
1333 if l.startswith('+ ') or l.startswith('! '):
1331 u = '+' + s
1334 u = '+' + s
1332 elif l.startswith(' '):
1335 elif l.startswith(' '):
1333 u = ' ' + s
1336 u = ' ' + s
1334 elif len(self.b) == 0:
1337 elif len(self.b) == 0:
1335 # line deletions, new block is empty
1338 # line deletions, new block is empty
1336 lr.push(l)
1339 lr.push(l)
1337 break
1340 break
1338 else:
1341 else:
1339 raise PatchError(_("bad hunk #%d old text line %d") %
1342 raise PatchError(_("bad hunk #%d old text line %d") %
1340 (self.number, x))
1343 (self.number, x))
1341 self.b.append(s)
1344 self.b.append(s)
1342 while True:
1345 while True:
1343 if hunki >= len(self.hunk):
1346 if hunki >= len(self.hunk):
1344 h = ""
1347 h = ""
1345 else:
1348 else:
1346 h = self.hunk[hunki]
1349 h = self.hunk[hunki]
1347 hunki += 1
1350 hunki += 1
1348 if h == u:
1351 if h == u:
1349 break
1352 break
1350 elif h.startswith('-'):
1353 elif h.startswith('-'):
1351 continue
1354 continue
1352 else:
1355 else:
1353 self.hunk.insert(hunki - 1, u)
1356 self.hunk.insert(hunki - 1, u)
1354 break
1357 break
1355
1358
1356 if not self.a:
1359 if not self.a:
1357 # this happens when lines were only added to the hunk
1360 # this happens when lines were only added to the hunk
1358 for x in self.hunk:
1361 for x in self.hunk:
1359 if x.startswith('-') or x.startswith(' '):
1362 if x.startswith('-') or x.startswith(' '):
1360 self.a.append(x)
1363 self.a.append(x)
1361 if not self.b:
1364 if not self.b:
1362 # this happens when lines were only deleted from the hunk
1365 # this happens when lines were only deleted from the hunk
1363 for x in self.hunk:
1366 for x in self.hunk:
1364 if x.startswith('+') or x.startswith(' '):
1367 if x.startswith('+') or x.startswith(' '):
1365 self.b.append(x[1:])
1368 self.b.append(x[1:])
1366 # @@ -start,len +start,len @@
1369 # @@ -start,len +start,len @@
1367 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
1370 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
1368 self.startb, self.lenb)
1371 self.startb, self.lenb)
1369 self.hunk[0] = self.desc
1372 self.hunk[0] = self.desc
1370 self._fixnewline(lr)
1373 self._fixnewline(lr)
1371
1374
1372 def _fixnewline(self, lr):
1375 def _fixnewline(self, lr):
1373 l = lr.readline()
1376 l = lr.readline()
1374 if l.startswith('\ '):
1377 if l.startswith('\ '):
1375 diffhelpers.fixnewline(self.hunk, self.a, self.b)
1378 diffhelpers.fixnewline(self.hunk, self.a, self.b)
1376 else:
1379 else:
1377 lr.push(l)
1380 lr.push(l)
1378
1381
1379 def complete(self):
1382 def complete(self):
1380 return len(self.a) == self.lena and len(self.b) == self.lenb
1383 return len(self.a) == self.lena and len(self.b) == self.lenb
1381
1384
1382 def _fuzzit(self, old, new, fuzz, toponly):
1385 def _fuzzit(self, old, new, fuzz, toponly):
1383 # this removes context lines from the top and bottom of list 'l'. It
1386 # this removes context lines from the top and bottom of list 'l'. It
1384 # checks the hunk to make sure only context lines are removed, and then
1387 # checks the hunk to make sure only context lines are removed, and then
1385 # returns a new shortened list of lines.
1388 # returns a new shortened list of lines.
1386 fuzz = min(fuzz, len(old))
1389 fuzz = min(fuzz, len(old))
1387 if fuzz:
1390 if fuzz:
1388 top = 0
1391 top = 0
1389 bot = 0
1392 bot = 0
1390 hlen = len(self.hunk)
1393 hlen = len(self.hunk)
1391 for x in xrange(hlen - 1):
1394 for x in xrange(hlen - 1):
1392 # the hunk starts with the @@ line, so use x+1
1395 # the hunk starts with the @@ line, so use x+1
1393 if self.hunk[x + 1].startswith(' '):
1396 if self.hunk[x + 1].startswith(' '):
1394 top += 1
1397 top += 1
1395 else:
1398 else:
1396 break
1399 break
1397 if not toponly:
1400 if not toponly:
1398 for x in xrange(hlen - 1):
1401 for x in xrange(hlen - 1):
1399 if self.hunk[hlen - bot - 1].startswith(' '):
1402 if self.hunk[hlen - bot - 1].startswith(' '):
1400 bot += 1
1403 bot += 1
1401 else:
1404 else:
1402 break
1405 break
1403
1406
1404 bot = min(fuzz, bot)
1407 bot = min(fuzz, bot)
1405 top = min(fuzz, top)
1408 top = min(fuzz, top)
1406 return old[top:len(old) - bot], new[top:len(new) - bot], top
1409 return old[top:len(old) - bot], new[top:len(new) - bot], top
1407 return old, new, 0
1410 return old, new, 0
1408
1411
1409 def fuzzit(self, fuzz, toponly):
1412 def fuzzit(self, fuzz, toponly):
1410 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1413 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1411 oldstart = self.starta + top
1414 oldstart = self.starta + top
1412 newstart = self.startb + top
1415 newstart = self.startb + top
1413 # zero length hunk ranges already have their start decremented
1416 # zero length hunk ranges already have their start decremented
1414 if self.lena and oldstart > 0:
1417 if self.lena and oldstart > 0:
1415 oldstart -= 1
1418 oldstart -= 1
1416 if self.lenb and newstart > 0:
1419 if self.lenb and newstart > 0:
1417 newstart -= 1
1420 newstart -= 1
1418 return old, oldstart, new, newstart
1421 return old, oldstart, new, newstart
1419
1422
1420 class binhunk(object):
1423 class binhunk(object):
1421 'A binary patch file.'
1424 'A binary patch file.'
1422 def __init__(self, lr, fname):
1425 def __init__(self, lr, fname):
1423 self.text = None
1426 self.text = None
1424 self.delta = False
1427 self.delta = False
1425 self.hunk = ['GIT binary patch\n']
1428 self.hunk = ['GIT binary patch\n']
1426 self._fname = fname
1429 self._fname = fname
1427 self._read(lr)
1430 self._read(lr)
1428
1431
1429 def complete(self):
1432 def complete(self):
1430 return self.text is not None
1433 return self.text is not None
1431
1434
1432 def new(self, lines):
1435 def new(self, lines):
1433 if self.delta:
1436 if self.delta:
1434 return [applybindelta(self.text, ''.join(lines))]
1437 return [applybindelta(self.text, ''.join(lines))]
1435 return [self.text]
1438 return [self.text]
1436
1439
1437 def _read(self, lr):
1440 def _read(self, lr):
1438 def getline(lr, hunk):
1441 def getline(lr, hunk):
1439 l = lr.readline()
1442 l = lr.readline()
1440 hunk.append(l)
1443 hunk.append(l)
1441 return l.rstrip('\r\n')
1444 return l.rstrip('\r\n')
1442
1445
1443 size = 0
1446 size = 0
1444 while True:
1447 while True:
1445 line = getline(lr, self.hunk)
1448 line = getline(lr, self.hunk)
1446 if not line:
1449 if not line:
1447 raise PatchError(_('could not extract "%s" binary data')
1450 raise PatchError(_('could not extract "%s" binary data')
1448 % self._fname)
1451 % self._fname)
1449 if line.startswith('literal '):
1452 if line.startswith('literal '):
1450 size = int(line[8:].rstrip())
1453 size = int(line[8:].rstrip())
1451 break
1454 break
1452 if line.startswith('delta '):
1455 if line.startswith('delta '):
1453 size = int(line[6:].rstrip())
1456 size = int(line[6:].rstrip())
1454 self.delta = True
1457 self.delta = True
1455 break
1458 break
1456 dec = []
1459 dec = []
1457 line = getline(lr, self.hunk)
1460 line = getline(lr, self.hunk)
1458 while len(line) > 1:
1461 while len(line) > 1:
1459 l = line[0:1]
1462 l = line[0:1]
1460 if l <= 'Z' and l >= 'A':
1463 if l <= 'Z' and l >= 'A':
1461 l = ord(l) - ord('A') + 1
1464 l = ord(l) - ord('A') + 1
1462 else:
1465 else:
1463 l = ord(l) - ord('a') + 27
1466 l = ord(l) - ord('a') + 27
1464 try:
1467 try:
1465 dec.append(util.b85decode(line[1:])[:l])
1468 dec.append(util.b85decode(line[1:])[:l])
1466 except ValueError as e:
1469 except ValueError as e:
1467 raise PatchError(_('could not decode "%s" binary patch: %s')
1470 raise PatchError(_('could not decode "%s" binary patch: %s')
1468 % (self._fname, stringutil.forcebytestr(e)))
1471 % (self._fname, stringutil.forcebytestr(e)))
1469 line = getline(lr, self.hunk)
1472 line = getline(lr, self.hunk)
1470 text = zlib.decompress(''.join(dec))
1473 text = zlib.decompress(''.join(dec))
1471 if len(text) != size:
1474 if len(text) != size:
1472 raise PatchError(_('"%s" length is %d bytes, should be %d')
1475 raise PatchError(_('"%s" length is %d bytes, should be %d')
1473 % (self._fname, len(text), size))
1476 % (self._fname, len(text), size))
1474 self.text = text
1477 self.text = text
1475
1478
1476 def parsefilename(str):
1479 def parsefilename(str):
1477 # --- filename \t|space stuff
1480 # --- filename \t|space stuff
1478 s = str[4:].rstrip('\r\n')
1481 s = str[4:].rstrip('\r\n')
1479 i = s.find('\t')
1482 i = s.find('\t')
1480 if i < 0:
1483 if i < 0:
1481 i = s.find(' ')
1484 i = s.find(' ')
1482 if i < 0:
1485 if i < 0:
1483 return s
1486 return s
1484 return s[:i]
1487 return s[:i]
1485
1488
1486 def reversehunks(hunks):
1489 def reversehunks(hunks):
1487 '''reverse the signs in the hunks given as argument
1490 '''reverse the signs in the hunks given as argument
1488
1491
1489 This function operates on hunks coming out of patch.filterpatch, that is
1492 This function operates on hunks coming out of patch.filterpatch, that is
1490 a list of the form: [header1, hunk1, hunk2, header2...]. Example usage:
1493 a list of the form: [header1, hunk1, hunk2, header2...]. Example usage:
1491
1494
1492 >>> rawpatch = b"""diff --git a/folder1/g b/folder1/g
1495 >>> rawpatch = b"""diff --git a/folder1/g b/folder1/g
1493 ... --- a/folder1/g
1496 ... --- a/folder1/g
1494 ... +++ b/folder1/g
1497 ... +++ b/folder1/g
1495 ... @@ -1,7 +1,7 @@
1498 ... @@ -1,7 +1,7 @@
1496 ... +firstline
1499 ... +firstline
1497 ... c
1500 ... c
1498 ... 1
1501 ... 1
1499 ... 2
1502 ... 2
1500 ... + 3
1503 ... + 3
1501 ... -4
1504 ... -4
1502 ... 5
1505 ... 5
1503 ... d
1506 ... d
1504 ... +lastline"""
1507 ... +lastline"""
1505 >>> hunks = parsepatch([rawpatch])
1508 >>> hunks = parsepatch([rawpatch])
1506 >>> hunkscomingfromfilterpatch = []
1509 >>> hunkscomingfromfilterpatch = []
1507 >>> for h in hunks:
1510 >>> for h in hunks:
1508 ... hunkscomingfromfilterpatch.append(h)
1511 ... hunkscomingfromfilterpatch.append(h)
1509 ... hunkscomingfromfilterpatch.extend(h.hunks)
1512 ... hunkscomingfromfilterpatch.extend(h.hunks)
1510
1513
1511 >>> reversedhunks = reversehunks(hunkscomingfromfilterpatch)
1514 >>> reversedhunks = reversehunks(hunkscomingfromfilterpatch)
1512 >>> from . import util
1515 >>> from . import util
1513 >>> fp = util.stringio()
1516 >>> fp = util.stringio()
1514 >>> for c in reversedhunks:
1517 >>> for c in reversedhunks:
1515 ... c.write(fp)
1518 ... c.write(fp)
1516 >>> fp.seek(0) or None
1519 >>> fp.seek(0) or None
1517 >>> reversedpatch = fp.read()
1520 >>> reversedpatch = fp.read()
1518 >>> print(pycompat.sysstr(reversedpatch))
1521 >>> print(pycompat.sysstr(reversedpatch))
1519 diff --git a/folder1/g b/folder1/g
1522 diff --git a/folder1/g b/folder1/g
1520 --- a/folder1/g
1523 --- a/folder1/g
1521 +++ b/folder1/g
1524 +++ b/folder1/g
1522 @@ -1,4 +1,3 @@
1525 @@ -1,4 +1,3 @@
1523 -firstline
1526 -firstline
1524 c
1527 c
1525 1
1528 1
1526 2
1529 2
1527 @@ -2,6 +1,6 @@
1530 @@ -2,6 +1,6 @@
1528 c
1531 c
1529 1
1532 1
1530 2
1533 2
1531 - 3
1534 - 3
1532 +4
1535 +4
1533 5
1536 5
1534 d
1537 d
1535 @@ -6,3 +5,2 @@
1538 @@ -6,3 +5,2 @@
1536 5
1539 5
1537 d
1540 d
1538 -lastline
1541 -lastline
1539
1542
1540 '''
1543 '''
1541
1544
1542 newhunks = []
1545 newhunks = []
1543 for c in hunks:
1546 for c in hunks:
1544 if util.safehasattr(c, 'reversehunk'):
1547 if util.safehasattr(c, 'reversehunk'):
1545 c = c.reversehunk()
1548 c = c.reversehunk()
1546 newhunks.append(c)
1549 newhunks.append(c)
1547 return newhunks
1550 return newhunks
1548
1551
1549 def parsepatch(originalchunks, maxcontext=None):
1552 def parsepatch(originalchunks, maxcontext=None):
1550 """patch -> [] of headers -> [] of hunks
1553 """patch -> [] of headers -> [] of hunks
1551
1554
1552 If maxcontext is not None, trim context lines if necessary.
1555 If maxcontext is not None, trim context lines if necessary.
1553
1556
1554 >>> rawpatch = b'''diff --git a/folder1/g b/folder1/g
1557 >>> rawpatch = b'''diff --git a/folder1/g b/folder1/g
1555 ... --- a/folder1/g
1558 ... --- a/folder1/g
1556 ... +++ b/folder1/g
1559 ... +++ b/folder1/g
1557 ... @@ -1,8 +1,10 @@
1560 ... @@ -1,8 +1,10 @@
1558 ... 1
1561 ... 1
1559 ... 2
1562 ... 2
1560 ... -3
1563 ... -3
1561 ... 4
1564 ... 4
1562 ... 5
1565 ... 5
1563 ... 6
1566 ... 6
1564 ... +6.1
1567 ... +6.1
1565 ... +6.2
1568 ... +6.2
1566 ... 7
1569 ... 7
1567 ... 8
1570 ... 8
1568 ... +9'''
1571 ... +9'''
1569 >>> out = util.stringio()
1572 >>> out = util.stringio()
1570 >>> headers = parsepatch([rawpatch], maxcontext=1)
1573 >>> headers = parsepatch([rawpatch], maxcontext=1)
1571 >>> for header in headers:
1574 >>> for header in headers:
1572 ... header.write(out)
1575 ... header.write(out)
1573 ... for hunk in header.hunks:
1576 ... for hunk in header.hunks:
1574 ... hunk.write(out)
1577 ... hunk.write(out)
1575 >>> print(pycompat.sysstr(out.getvalue()))
1578 >>> print(pycompat.sysstr(out.getvalue()))
1576 diff --git a/folder1/g b/folder1/g
1579 diff --git a/folder1/g b/folder1/g
1577 --- a/folder1/g
1580 --- a/folder1/g
1578 +++ b/folder1/g
1581 +++ b/folder1/g
1579 @@ -2,3 +2,2 @@
1582 @@ -2,3 +2,2 @@
1580 2
1583 2
1581 -3
1584 -3
1582 4
1585 4
1583 @@ -6,2 +5,4 @@
1586 @@ -6,2 +5,4 @@
1584 6
1587 6
1585 +6.1
1588 +6.1
1586 +6.2
1589 +6.2
1587 7
1590 7
1588 @@ -8,1 +9,2 @@
1591 @@ -8,1 +9,2 @@
1589 8
1592 8
1590 +9
1593 +9
1591 """
1594 """
1592 class parser(object):
1595 class parser(object):
1593 """patch parsing state machine"""
1596 """patch parsing state machine"""
1594 def __init__(self):
1597 def __init__(self):
1595 self.fromline = 0
1598 self.fromline = 0
1596 self.toline = 0
1599 self.toline = 0
1597 self.proc = ''
1600 self.proc = ''
1598 self.header = None
1601 self.header = None
1599 self.context = []
1602 self.context = []
1600 self.before = []
1603 self.before = []
1601 self.hunk = []
1604 self.hunk = []
1602 self.headers = []
1605 self.headers = []
1603
1606
1604 def addrange(self, limits):
1607 def addrange(self, limits):
1605 fromstart, fromend, tostart, toend, proc = limits
1608 fromstart, fromend, tostart, toend, proc = limits
1606 self.fromline = int(fromstart)
1609 self.fromline = int(fromstart)
1607 self.toline = int(tostart)
1610 self.toline = int(tostart)
1608 self.proc = proc
1611 self.proc = proc
1609
1612
1610 def addcontext(self, context):
1613 def addcontext(self, context):
1611 if self.hunk:
1614 if self.hunk:
1612 h = recordhunk(self.header, self.fromline, self.toline,
1615 h = recordhunk(self.header, self.fromline, self.toline,
1613 self.proc, self.before, self.hunk, context, maxcontext)
1616 self.proc, self.before, self.hunk, context, maxcontext)
1614 self.header.hunks.append(h)
1617 self.header.hunks.append(h)
1615 self.fromline += len(self.before) + h.removed
1618 self.fromline += len(self.before) + h.removed
1616 self.toline += len(self.before) + h.added
1619 self.toline += len(self.before) + h.added
1617 self.before = []
1620 self.before = []
1618 self.hunk = []
1621 self.hunk = []
1619 self.context = context
1622 self.context = context
1620
1623
1621 def addhunk(self, hunk):
1624 def addhunk(self, hunk):
1622 if self.context:
1625 if self.context:
1623 self.before = self.context
1626 self.before = self.context
1624 self.context = []
1627 self.context = []
1625 self.hunk = hunk
1628 self.hunk = hunk
1626
1629
1627 def newfile(self, hdr):
1630 def newfile(self, hdr):
1628 self.addcontext([])
1631 self.addcontext([])
1629 h = header(hdr)
1632 h = header(hdr)
1630 self.headers.append(h)
1633 self.headers.append(h)
1631 self.header = h
1634 self.header = h
1632
1635
1633 def addother(self, line):
1636 def addother(self, line):
1634 pass # 'other' lines are ignored
1637 pass # 'other' lines are ignored
1635
1638
1636 def finished(self):
1639 def finished(self):
1637 self.addcontext([])
1640 self.addcontext([])
1638 return self.headers
1641 return self.headers
1639
1642
1640 transitions = {
1643 transitions = {
1641 'file': {'context': addcontext,
1644 'file': {'context': addcontext,
1642 'file': newfile,
1645 'file': newfile,
1643 'hunk': addhunk,
1646 'hunk': addhunk,
1644 'range': addrange},
1647 'range': addrange},
1645 'context': {'file': newfile,
1648 'context': {'file': newfile,
1646 'hunk': addhunk,
1649 'hunk': addhunk,
1647 'range': addrange,
1650 'range': addrange,
1648 'other': addother},
1651 'other': addother},
1649 'hunk': {'context': addcontext,
1652 'hunk': {'context': addcontext,
1650 'file': newfile,
1653 'file': newfile,
1651 'range': addrange},
1654 'range': addrange},
1652 'range': {'context': addcontext,
1655 'range': {'context': addcontext,
1653 'hunk': addhunk},
1656 'hunk': addhunk},
1654 'other': {'other': addother},
1657 'other': {'other': addother},
1655 }
1658 }
1656
1659
1657 p = parser()
1660 p = parser()
1658 fp = stringio()
1661 fp = stringio()
1659 fp.write(''.join(originalchunks))
1662 fp.write(''.join(originalchunks))
1660 fp.seek(0)
1663 fp.seek(0)
1661
1664
1662 state = 'context'
1665 state = 'context'
1663 for newstate, data in scanpatch(fp):
1666 for newstate, data in scanpatch(fp):
1664 try:
1667 try:
1665 p.transitions[state][newstate](p, data)
1668 p.transitions[state][newstate](p, data)
1666 except KeyError:
1669 except KeyError:
1667 raise PatchError('unhandled transition: %s -> %s' %
1670 raise PatchError('unhandled transition: %s -> %s' %
1668 (state, newstate))
1671 (state, newstate))
1669 state = newstate
1672 state = newstate
1670 del fp
1673 del fp
1671 return p.finished()
1674 return p.finished()
1672
1675
1673 def pathtransform(path, strip, prefix):
1676 def pathtransform(path, strip, prefix):
1674 '''turn a path from a patch into a path suitable for the repository
1677 '''turn a path from a patch into a path suitable for the repository
1675
1678
1676 prefix, if not empty, is expected to be normalized with a / at the end.
1679 prefix, if not empty, is expected to be normalized with a / at the end.
1677
1680
1678 Returns (stripped components, path in repository).
1681 Returns (stripped components, path in repository).
1679
1682
1680 >>> pathtransform(b'a/b/c', 0, b'')
1683 >>> pathtransform(b'a/b/c', 0, b'')
1681 ('', 'a/b/c')
1684 ('', 'a/b/c')
1682 >>> pathtransform(b' a/b/c ', 0, b'')
1685 >>> pathtransform(b' a/b/c ', 0, b'')
1683 ('', ' a/b/c')
1686 ('', ' a/b/c')
1684 >>> pathtransform(b' a/b/c ', 2, b'')
1687 >>> pathtransform(b' a/b/c ', 2, b'')
1685 ('a/b/', 'c')
1688 ('a/b/', 'c')
1686 >>> pathtransform(b'a/b/c', 0, b'd/e/')
1689 >>> pathtransform(b'a/b/c', 0, b'd/e/')
1687 ('', 'd/e/a/b/c')
1690 ('', 'd/e/a/b/c')
1688 >>> pathtransform(b' a//b/c ', 2, b'd/e/')
1691 >>> pathtransform(b' a//b/c ', 2, b'd/e/')
1689 ('a//b/', 'd/e/c')
1692 ('a//b/', 'd/e/c')
1690 >>> pathtransform(b'a/b/c', 3, b'')
1693 >>> pathtransform(b'a/b/c', 3, b'')
1691 Traceback (most recent call last):
1694 Traceback (most recent call last):
1692 PatchError: unable to strip away 1 of 3 dirs from a/b/c
1695 PatchError: unable to strip away 1 of 3 dirs from a/b/c
1693 '''
1696 '''
1694 pathlen = len(path)
1697 pathlen = len(path)
1695 i = 0
1698 i = 0
1696 if strip == 0:
1699 if strip == 0:
1697 return '', prefix + path.rstrip()
1700 return '', prefix + path.rstrip()
1698 count = strip
1701 count = strip
1699 while count > 0:
1702 while count > 0:
1700 i = path.find('/', i)
1703 i = path.find('/', i)
1701 if i == -1:
1704 if i == -1:
1702 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1705 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1703 (count, strip, path))
1706 (count, strip, path))
1704 i += 1
1707 i += 1
1705 # consume '//' in the path
1708 # consume '//' in the path
1706 while i < pathlen - 1 and path[i:i + 1] == '/':
1709 while i < pathlen - 1 and path[i:i + 1] == '/':
1707 i += 1
1710 i += 1
1708 count -= 1
1711 count -= 1
1709 return path[:i].lstrip(), prefix + path[i:].rstrip()
1712 return path[:i].lstrip(), prefix + path[i:].rstrip()
1710
1713
1711 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip, prefix):
1714 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip, prefix):
1712 nulla = afile_orig == "/dev/null"
1715 nulla = afile_orig == "/dev/null"
1713 nullb = bfile_orig == "/dev/null"
1716 nullb = bfile_orig == "/dev/null"
1714 create = nulla and hunk.starta == 0 and hunk.lena == 0
1717 create = nulla and hunk.starta == 0 and hunk.lena == 0
1715 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1718 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1716 abase, afile = pathtransform(afile_orig, strip, prefix)
1719 abase, afile = pathtransform(afile_orig, strip, prefix)
1717 gooda = not nulla and backend.exists(afile)
1720 gooda = not nulla and backend.exists(afile)
1718 bbase, bfile = pathtransform(bfile_orig, strip, prefix)
1721 bbase, bfile = pathtransform(bfile_orig, strip, prefix)
1719 if afile == bfile:
1722 if afile == bfile:
1720 goodb = gooda
1723 goodb = gooda
1721 else:
1724 else:
1722 goodb = not nullb and backend.exists(bfile)
1725 goodb = not nullb and backend.exists(bfile)
1723 missing = not goodb and not gooda and not create
1726 missing = not goodb and not gooda and not create
1724
1727
1725 # some diff programs apparently produce patches where the afile is
1728 # some diff programs apparently produce patches where the afile is
1726 # not /dev/null, but afile starts with bfile
1729 # not /dev/null, but afile starts with bfile
1727 abasedir = afile[:afile.rfind('/') + 1]
1730 abasedir = afile[:afile.rfind('/') + 1]
1728 bbasedir = bfile[:bfile.rfind('/') + 1]
1731 bbasedir = bfile[:bfile.rfind('/') + 1]
1729 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1732 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1730 and hunk.starta == 0 and hunk.lena == 0):
1733 and hunk.starta == 0 and hunk.lena == 0):
1731 create = True
1734 create = True
1732 missing = False
1735 missing = False
1733
1736
1734 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1737 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1735 # diff is between a file and its backup. In this case, the original
1738 # diff is between a file and its backup. In this case, the original
1736 # file should be patched (see original mpatch code).
1739 # file should be patched (see original mpatch code).
1737 isbackup = (abase == bbase and bfile.startswith(afile))
1740 isbackup = (abase == bbase and bfile.startswith(afile))
1738 fname = None
1741 fname = None
1739 if not missing:
1742 if not missing:
1740 if gooda and goodb:
1743 if gooda and goodb:
1741 if isbackup:
1744 if isbackup:
1742 fname = afile
1745 fname = afile
1743 else:
1746 else:
1744 fname = bfile
1747 fname = bfile
1745 elif gooda:
1748 elif gooda:
1746 fname = afile
1749 fname = afile
1747
1750
1748 if not fname:
1751 if not fname:
1749 if not nullb:
1752 if not nullb:
1750 if isbackup:
1753 if isbackup:
1751 fname = afile
1754 fname = afile
1752 else:
1755 else:
1753 fname = bfile
1756 fname = bfile
1754 elif not nulla:
1757 elif not nulla:
1755 fname = afile
1758 fname = afile
1756 else:
1759 else:
1757 raise PatchError(_("undefined source and destination files"))
1760 raise PatchError(_("undefined source and destination files"))
1758
1761
1759 gp = patchmeta(fname)
1762 gp = patchmeta(fname)
1760 if create:
1763 if create:
1761 gp.op = 'ADD'
1764 gp.op = 'ADD'
1762 elif remove:
1765 elif remove:
1763 gp.op = 'DELETE'
1766 gp.op = 'DELETE'
1764 return gp
1767 return gp
1765
1768
1766 def scanpatch(fp):
1769 def scanpatch(fp):
1767 """like patch.iterhunks, but yield different events
1770 """like patch.iterhunks, but yield different events
1768
1771
1769 - ('file', [header_lines + fromfile + tofile])
1772 - ('file', [header_lines + fromfile + tofile])
1770 - ('context', [context_lines])
1773 - ('context', [context_lines])
1771 - ('hunk', [hunk_lines])
1774 - ('hunk', [hunk_lines])
1772 - ('range', (-start,len, +start,len, proc))
1775 - ('range', (-start,len, +start,len, proc))
1773 """
1776 """
1774 lines_re = re.compile(br'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
1777 lines_re = re.compile(br'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
1775 lr = linereader(fp)
1778 lr = linereader(fp)
1776
1779
1777 def scanwhile(first, p):
1780 def scanwhile(first, p):
1778 """scan lr while predicate holds"""
1781 """scan lr while predicate holds"""
1779 lines = [first]
1782 lines = [first]
1780 for line in iter(lr.readline, ''):
1783 for line in iter(lr.readline, ''):
1781 if p(line):
1784 if p(line):
1782 lines.append(line)
1785 lines.append(line)
1783 else:
1786 else:
1784 lr.push(line)
1787 lr.push(line)
1785 break
1788 break
1786 return lines
1789 return lines
1787
1790
1788 for line in iter(lr.readline, ''):
1791 for line in iter(lr.readline, ''):
1789 if line.startswith('diff --git a/') or line.startswith('diff -r '):
1792 if line.startswith('diff --git a/') or line.startswith('diff -r '):
1790 def notheader(line):
1793 def notheader(line):
1791 s = line.split(None, 1)
1794 s = line.split(None, 1)
1792 return not s or s[0] not in ('---', 'diff')
1795 return not s or s[0] not in ('---', 'diff')
1793 header = scanwhile(line, notheader)
1796 header = scanwhile(line, notheader)
1794 fromfile = lr.readline()
1797 fromfile = lr.readline()
1795 if fromfile.startswith('---'):
1798 if fromfile.startswith('---'):
1796 tofile = lr.readline()
1799 tofile = lr.readline()
1797 header += [fromfile, tofile]
1800 header += [fromfile, tofile]
1798 else:
1801 else:
1799 lr.push(fromfile)
1802 lr.push(fromfile)
1800 yield 'file', header
1803 yield 'file', header
1801 elif line.startswith(' '):
1804 elif line.startswith(' '):
1802 cs = (' ', '\\')
1805 cs = (' ', '\\')
1803 yield 'context', scanwhile(line, lambda l: l.startswith(cs))
1806 yield 'context', scanwhile(line, lambda l: l.startswith(cs))
1804 elif line.startswith(('-', '+')):
1807 elif line.startswith(('-', '+')):
1805 cs = ('-', '+', '\\')
1808 cs = ('-', '+', '\\')
1806 yield 'hunk', scanwhile(line, lambda l: l.startswith(cs))
1809 yield 'hunk', scanwhile(line, lambda l: l.startswith(cs))
1807 else:
1810 else:
1808 m = lines_re.match(line)
1811 m = lines_re.match(line)
1809 if m:
1812 if m:
1810 yield 'range', m.groups()
1813 yield 'range', m.groups()
1811 else:
1814 else:
1812 yield 'other', line
1815 yield 'other', line
1813
1816
1814 def scangitpatch(lr, firstline):
1817 def scangitpatch(lr, firstline):
1815 """
1818 """
1816 Git patches can emit:
1819 Git patches can emit:
1817 - rename a to b
1820 - rename a to b
1818 - change b
1821 - change b
1819 - copy a to c
1822 - copy a to c
1820 - change c
1823 - change c
1821
1824
1822 We cannot apply this sequence as-is, the renamed 'a' could not be
1825 We cannot apply this sequence as-is, the renamed 'a' could not be
1823 found for it would have been renamed already. And we cannot copy
1826 found for it would have been renamed already. And we cannot copy
1824 from 'b' instead because 'b' would have been changed already. So
1827 from 'b' instead because 'b' would have been changed already. So
1825 we scan the git patch for copy and rename commands so we can
1828 we scan the git patch for copy and rename commands so we can
1826 perform the copies ahead of time.
1829 perform the copies ahead of time.
1827 """
1830 """
1828 pos = 0
1831 pos = 0
1829 try:
1832 try:
1830 pos = lr.fp.tell()
1833 pos = lr.fp.tell()
1831 fp = lr.fp
1834 fp = lr.fp
1832 except IOError:
1835 except IOError:
1833 fp = stringio(lr.fp.read())
1836 fp = stringio(lr.fp.read())
1834 gitlr = linereader(fp)
1837 gitlr = linereader(fp)
1835 gitlr.push(firstline)
1838 gitlr.push(firstline)
1836 gitpatches = readgitpatch(gitlr)
1839 gitpatches = readgitpatch(gitlr)
1837 fp.seek(pos)
1840 fp.seek(pos)
1838 return gitpatches
1841 return gitpatches
1839
1842
1840 def iterhunks(fp):
1843 def iterhunks(fp):
1841 """Read a patch and yield the following events:
1844 """Read a patch and yield the following events:
1842 - ("file", afile, bfile, firsthunk): select a new target file.
1845 - ("file", afile, bfile, firsthunk): select a new target file.
1843 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1846 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1844 "file" event.
1847 "file" event.
1845 - ("git", gitchanges): current diff is in git format, gitchanges
1848 - ("git", gitchanges): current diff is in git format, gitchanges
1846 maps filenames to gitpatch records. Unique event.
1849 maps filenames to gitpatch records. Unique event.
1847 """
1850 """
1848 afile = ""
1851 afile = ""
1849 bfile = ""
1852 bfile = ""
1850 state = None
1853 state = None
1851 hunknum = 0
1854 hunknum = 0
1852 emitfile = newfile = False
1855 emitfile = newfile = False
1853 gitpatches = None
1856 gitpatches = None
1854
1857
1855 # our states
1858 # our states
1856 BFILE = 1
1859 BFILE = 1
1857 context = None
1860 context = None
1858 lr = linereader(fp)
1861 lr = linereader(fp)
1859
1862
1860 for x in iter(lr.readline, ''):
1863 for x in iter(lr.readline, ''):
1861 if state == BFILE and (
1864 if state == BFILE and (
1862 (not context and x.startswith('@'))
1865 (not context and x.startswith('@'))
1863 or (context is not False and x.startswith('***************'))
1866 or (context is not False and x.startswith('***************'))
1864 or x.startswith('GIT binary patch')):
1867 or x.startswith('GIT binary patch')):
1865 gp = None
1868 gp = None
1866 if (gitpatches and
1869 if (gitpatches and
1867 gitpatches[-1].ispatching(afile, bfile)):
1870 gitpatches[-1].ispatching(afile, bfile)):
1868 gp = gitpatches.pop()
1871 gp = gitpatches.pop()
1869 if x.startswith('GIT binary patch'):
1872 if x.startswith('GIT binary patch'):
1870 h = binhunk(lr, gp.path)
1873 h = binhunk(lr, gp.path)
1871 else:
1874 else:
1872 if context is None and x.startswith('***************'):
1875 if context is None and x.startswith('***************'):
1873 context = True
1876 context = True
1874 h = hunk(x, hunknum + 1, lr, context)
1877 h = hunk(x, hunknum + 1, lr, context)
1875 hunknum += 1
1878 hunknum += 1
1876 if emitfile:
1879 if emitfile:
1877 emitfile = False
1880 emitfile = False
1878 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1881 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1879 yield 'hunk', h
1882 yield 'hunk', h
1880 elif x.startswith('diff --git a/'):
1883 elif x.startswith('diff --git a/'):
1881 m = gitre.match(x.rstrip(' \r\n'))
1884 m = gitre.match(x.rstrip(' \r\n'))
1882 if not m:
1885 if not m:
1883 continue
1886 continue
1884 if gitpatches is None:
1887 if gitpatches is None:
1885 # scan whole input for git metadata
1888 # scan whole input for git metadata
1886 gitpatches = scangitpatch(lr, x)
1889 gitpatches = scangitpatch(lr, x)
1887 yield 'git', [g.copy() for g in gitpatches
1890 yield 'git', [g.copy() for g in gitpatches
1888 if g.op in ('COPY', 'RENAME')]
1891 if g.op in ('COPY', 'RENAME')]
1889 gitpatches.reverse()
1892 gitpatches.reverse()
1890 afile = 'a/' + m.group(1)
1893 afile = 'a/' + m.group(1)
1891 bfile = 'b/' + m.group(2)
1894 bfile = 'b/' + m.group(2)
1892 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1895 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1893 gp = gitpatches.pop()
1896 gp = gitpatches.pop()
1894 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1897 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1895 if not gitpatches:
1898 if not gitpatches:
1896 raise PatchError(_('failed to synchronize metadata for "%s"')
1899 raise PatchError(_('failed to synchronize metadata for "%s"')
1897 % afile[2:])
1900 % afile[2:])
1898 gp = gitpatches[-1]
1901 gp = gitpatches[-1]
1899 newfile = True
1902 newfile = True
1900 elif x.startswith('---'):
1903 elif x.startswith('---'):
1901 # check for a unified diff
1904 # check for a unified diff
1902 l2 = lr.readline()
1905 l2 = lr.readline()
1903 if not l2.startswith('+++'):
1906 if not l2.startswith('+++'):
1904 lr.push(l2)
1907 lr.push(l2)
1905 continue
1908 continue
1906 newfile = True
1909 newfile = True
1907 context = False
1910 context = False
1908 afile = parsefilename(x)
1911 afile = parsefilename(x)
1909 bfile = parsefilename(l2)
1912 bfile = parsefilename(l2)
1910 elif x.startswith('***'):
1913 elif x.startswith('***'):
1911 # check for a context diff
1914 # check for a context diff
1912 l2 = lr.readline()
1915 l2 = lr.readline()
1913 if not l2.startswith('---'):
1916 if not l2.startswith('---'):
1914 lr.push(l2)
1917 lr.push(l2)
1915 continue
1918 continue
1916 l3 = lr.readline()
1919 l3 = lr.readline()
1917 lr.push(l3)
1920 lr.push(l3)
1918 if not l3.startswith("***************"):
1921 if not l3.startswith("***************"):
1919 lr.push(l2)
1922 lr.push(l2)
1920 continue
1923 continue
1921 newfile = True
1924 newfile = True
1922 context = True
1925 context = True
1923 afile = parsefilename(x)
1926 afile = parsefilename(x)
1924 bfile = parsefilename(l2)
1927 bfile = parsefilename(l2)
1925
1928
1926 if newfile:
1929 if newfile:
1927 newfile = False
1930 newfile = False
1928 emitfile = True
1931 emitfile = True
1929 state = BFILE
1932 state = BFILE
1930 hunknum = 0
1933 hunknum = 0
1931
1934
1932 while gitpatches:
1935 while gitpatches:
1933 gp = gitpatches.pop()
1936 gp = gitpatches.pop()
1934 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1937 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1935
1938
1936 def applybindelta(binchunk, data):
1939 def applybindelta(binchunk, data):
1937 """Apply a binary delta hunk
1940 """Apply a binary delta hunk
1938 The algorithm used is the algorithm from git's patch-delta.c
1941 The algorithm used is the algorithm from git's patch-delta.c
1939 """
1942 """
1940 def deltahead(binchunk):
1943 def deltahead(binchunk):
1941 i = 0
1944 i = 0
1942 for c in binchunk:
1945 for c in binchunk:
1943 i += 1
1946 i += 1
1944 if not (ord(c) & 0x80):
1947 if not (ord(c) & 0x80):
1945 return i
1948 return i
1946 return i
1949 return i
1947 out = ""
1950 out = ""
1948 s = deltahead(binchunk)
1951 s = deltahead(binchunk)
1949 binchunk = binchunk[s:]
1952 binchunk = binchunk[s:]
1950 s = deltahead(binchunk)
1953 s = deltahead(binchunk)
1951 binchunk = binchunk[s:]
1954 binchunk = binchunk[s:]
1952 i = 0
1955 i = 0
1953 while i < len(binchunk):
1956 while i < len(binchunk):
1954 cmd = ord(binchunk[i])
1957 cmd = ord(binchunk[i])
1955 i += 1
1958 i += 1
1956 if (cmd & 0x80):
1959 if (cmd & 0x80):
1957 offset = 0
1960 offset = 0
1958 size = 0
1961 size = 0
1959 if (cmd & 0x01):
1962 if (cmd & 0x01):
1960 offset = ord(binchunk[i])
1963 offset = ord(binchunk[i])
1961 i += 1
1964 i += 1
1962 if (cmd & 0x02):
1965 if (cmd & 0x02):
1963 offset |= ord(binchunk[i]) << 8
1966 offset |= ord(binchunk[i]) << 8
1964 i += 1
1967 i += 1
1965 if (cmd & 0x04):
1968 if (cmd & 0x04):
1966 offset |= ord(binchunk[i]) << 16
1969 offset |= ord(binchunk[i]) << 16
1967 i += 1
1970 i += 1
1968 if (cmd & 0x08):
1971 if (cmd & 0x08):
1969 offset |= ord(binchunk[i]) << 24
1972 offset |= ord(binchunk[i]) << 24
1970 i += 1
1973 i += 1
1971 if (cmd & 0x10):
1974 if (cmd & 0x10):
1972 size = ord(binchunk[i])
1975 size = ord(binchunk[i])
1973 i += 1
1976 i += 1
1974 if (cmd & 0x20):
1977 if (cmd & 0x20):
1975 size |= ord(binchunk[i]) << 8
1978 size |= ord(binchunk[i]) << 8
1976 i += 1
1979 i += 1
1977 if (cmd & 0x40):
1980 if (cmd & 0x40):
1978 size |= ord(binchunk[i]) << 16
1981 size |= ord(binchunk[i]) << 16
1979 i += 1
1982 i += 1
1980 if size == 0:
1983 if size == 0:
1981 size = 0x10000
1984 size = 0x10000
1982 offset_end = offset + size
1985 offset_end = offset + size
1983 out += data[offset:offset_end]
1986 out += data[offset:offset_end]
1984 elif cmd != 0:
1987 elif cmd != 0:
1985 offset_end = i + cmd
1988 offset_end = i + cmd
1986 out += binchunk[i:offset_end]
1989 out += binchunk[i:offset_end]
1987 i += cmd
1990 i += cmd
1988 else:
1991 else:
1989 raise PatchError(_('unexpected delta opcode 0'))
1992 raise PatchError(_('unexpected delta opcode 0'))
1990 return out
1993 return out
1991
1994
1992 def applydiff(ui, fp, backend, store, strip=1, prefix='', eolmode='strict'):
1995 def applydiff(ui, fp, backend, store, strip=1, prefix='', eolmode='strict'):
1993 """Reads a patch from fp and tries to apply it.
1996 """Reads a patch from fp and tries to apply it.
1994
1997
1995 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1998 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1996 there was any fuzz.
1999 there was any fuzz.
1997
2000
1998 If 'eolmode' is 'strict', the patch content and patched file are
2001 If 'eolmode' is 'strict', the patch content and patched file are
1999 read in binary mode. Otherwise, line endings are ignored when
2002 read in binary mode. Otherwise, line endings are ignored when
2000 patching then normalized according to 'eolmode'.
2003 patching then normalized according to 'eolmode'.
2001 """
2004 """
2002 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
2005 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
2003 prefix=prefix, eolmode=eolmode)
2006 prefix=prefix, eolmode=eolmode)
2004
2007
2005 def _canonprefix(repo, prefix):
2008 def _canonprefix(repo, prefix):
2006 if prefix:
2009 if prefix:
2007 prefix = pathutil.canonpath(repo.root, repo.getcwd(), prefix)
2010 prefix = pathutil.canonpath(repo.root, repo.getcwd(), prefix)
2008 if prefix != '':
2011 if prefix != '':
2009 prefix += '/'
2012 prefix += '/'
2010 return prefix
2013 return prefix
2011
2014
2012 def _applydiff(ui, fp, patcher, backend, store, strip=1, prefix='',
2015 def _applydiff(ui, fp, patcher, backend, store, strip=1, prefix='',
2013 eolmode='strict'):
2016 eolmode='strict'):
2014 prefix = _canonprefix(backend.repo, prefix)
2017 prefix = _canonprefix(backend.repo, prefix)
2015 def pstrip(p):
2018 def pstrip(p):
2016 return pathtransform(p, strip - 1, prefix)[1]
2019 return pathtransform(p, strip - 1, prefix)[1]
2017
2020
2018 rejects = 0
2021 rejects = 0
2019 err = 0
2022 err = 0
2020 current_file = None
2023 current_file = None
2021
2024
2022 for state, values in iterhunks(fp):
2025 for state, values in iterhunks(fp):
2023 if state == 'hunk':
2026 if state == 'hunk':
2024 if not current_file:
2027 if not current_file:
2025 continue
2028 continue
2026 ret = current_file.apply(values)
2029 ret = current_file.apply(values)
2027 if ret > 0:
2030 if ret > 0:
2028 err = 1
2031 err = 1
2029 elif state == 'file':
2032 elif state == 'file':
2030 if current_file:
2033 if current_file:
2031 rejects += current_file.close()
2034 rejects += current_file.close()
2032 current_file = None
2035 current_file = None
2033 afile, bfile, first_hunk, gp = values
2036 afile, bfile, first_hunk, gp = values
2034 if gp:
2037 if gp:
2035 gp.path = pstrip(gp.path)
2038 gp.path = pstrip(gp.path)
2036 if gp.oldpath:
2039 if gp.oldpath:
2037 gp.oldpath = pstrip(gp.oldpath)
2040 gp.oldpath = pstrip(gp.oldpath)
2038 else:
2041 else:
2039 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
2042 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
2040 prefix)
2043 prefix)
2041 if gp.op == 'RENAME':
2044 if gp.op == 'RENAME':
2042 backend.unlink(gp.oldpath)
2045 backend.unlink(gp.oldpath)
2043 if not first_hunk:
2046 if not first_hunk:
2044 if gp.op == 'DELETE':
2047 if gp.op == 'DELETE':
2045 backend.unlink(gp.path)
2048 backend.unlink(gp.path)
2046 continue
2049 continue
2047 data, mode = None, None
2050 data, mode = None, None
2048 if gp.op in ('RENAME', 'COPY'):
2051 if gp.op in ('RENAME', 'COPY'):
2049 data, mode = store.getfile(gp.oldpath)[:2]
2052 data, mode = store.getfile(gp.oldpath)[:2]
2050 if data is None:
2053 if data is None:
2051 # This means that the old path does not exist
2054 # This means that the old path does not exist
2052 raise PatchError(_("source file '%s' does not exist")
2055 raise PatchError(_("source file '%s' does not exist")
2053 % gp.oldpath)
2056 % gp.oldpath)
2054 if gp.mode:
2057 if gp.mode:
2055 mode = gp.mode
2058 mode = gp.mode
2056 if gp.op == 'ADD':
2059 if gp.op == 'ADD':
2057 # Added files without content have no hunk and
2060 # Added files without content have no hunk and
2058 # must be created
2061 # must be created
2059 data = ''
2062 data = ''
2060 if data or mode:
2063 if data or mode:
2061 if (gp.op in ('ADD', 'RENAME', 'COPY')
2064 if (gp.op in ('ADD', 'RENAME', 'COPY')
2062 and backend.exists(gp.path)):
2065 and backend.exists(gp.path)):
2063 raise PatchError(_("cannot create %s: destination "
2066 raise PatchError(_("cannot create %s: destination "
2064 "already exists") % gp.path)
2067 "already exists") % gp.path)
2065 backend.setfile(gp.path, data, mode, gp.oldpath)
2068 backend.setfile(gp.path, data, mode, gp.oldpath)
2066 continue
2069 continue
2067 try:
2070 try:
2068 current_file = patcher(ui, gp, backend, store,
2071 current_file = patcher(ui, gp, backend, store,
2069 eolmode=eolmode)
2072 eolmode=eolmode)
2070 except PatchError as inst:
2073 except PatchError as inst:
2071 ui.warn(str(inst) + '\n')
2074 ui.warn(str(inst) + '\n')
2072 current_file = None
2075 current_file = None
2073 rejects += 1
2076 rejects += 1
2074 continue
2077 continue
2075 elif state == 'git':
2078 elif state == 'git':
2076 for gp in values:
2079 for gp in values:
2077 path = pstrip(gp.oldpath)
2080 path = pstrip(gp.oldpath)
2078 data, mode = backend.getfile(path)
2081 data, mode = backend.getfile(path)
2079 if data is None:
2082 if data is None:
2080 # The error ignored here will trigger a getfile()
2083 # The error ignored here will trigger a getfile()
2081 # error in a place more appropriate for error
2084 # error in a place more appropriate for error
2082 # handling, and will not interrupt the patching
2085 # handling, and will not interrupt the patching
2083 # process.
2086 # process.
2084 pass
2087 pass
2085 else:
2088 else:
2086 store.setfile(path, data, mode)
2089 store.setfile(path, data, mode)
2087 else:
2090 else:
2088 raise error.Abort(_('unsupported parser state: %s') % state)
2091 raise error.Abort(_('unsupported parser state: %s') % state)
2089
2092
2090 if current_file:
2093 if current_file:
2091 rejects += current_file.close()
2094 rejects += current_file.close()
2092
2095
2093 if rejects:
2096 if rejects:
2094 return -1
2097 return -1
2095 return err
2098 return err
2096
2099
2097 def _externalpatch(ui, repo, patcher, patchname, strip, files,
2100 def _externalpatch(ui, repo, patcher, patchname, strip, files,
2098 similarity):
2101 similarity):
2099 """use <patcher> to apply <patchname> to the working directory.
2102 """use <patcher> to apply <patchname> to the working directory.
2100 returns whether patch was applied with fuzz factor."""
2103 returns whether patch was applied with fuzz factor."""
2101
2104
2102 fuzz = False
2105 fuzz = False
2103 args = []
2106 args = []
2104 cwd = repo.root
2107 cwd = repo.root
2105 if cwd:
2108 if cwd:
2106 args.append('-d %s' % procutil.shellquote(cwd))
2109 args.append('-d %s' % procutil.shellquote(cwd))
2107 cmd = ('%s %s -p%d < %s'
2110 cmd = ('%s %s -p%d < %s'
2108 % (patcher, ' '.join(args), strip, procutil.shellquote(patchname)))
2111 % (patcher, ' '.join(args), strip, procutil.shellquote(patchname)))
2109 fp = procutil.popen(cmd, 'rb')
2112 fp = procutil.popen(cmd, 'rb')
2110 try:
2113 try:
2111 for line in util.iterfile(fp):
2114 for line in util.iterfile(fp):
2112 line = line.rstrip()
2115 line = line.rstrip()
2113 ui.note(line + '\n')
2116 ui.note(line + '\n')
2114 if line.startswith('patching file '):
2117 if line.startswith('patching file '):
2115 pf = util.parsepatchoutput(line)
2118 pf = util.parsepatchoutput(line)
2116 printed_file = False
2119 printed_file = False
2117 files.add(pf)
2120 files.add(pf)
2118 elif line.find('with fuzz') >= 0:
2121 elif line.find('with fuzz') >= 0:
2119 fuzz = True
2122 fuzz = True
2120 if not printed_file:
2123 if not printed_file:
2121 ui.warn(pf + '\n')
2124 ui.warn(pf + '\n')
2122 printed_file = True
2125 printed_file = True
2123 ui.warn(line + '\n')
2126 ui.warn(line + '\n')
2124 elif line.find('saving rejects to file') >= 0:
2127 elif line.find('saving rejects to file') >= 0:
2125 ui.warn(line + '\n')
2128 ui.warn(line + '\n')
2126 elif line.find('FAILED') >= 0:
2129 elif line.find('FAILED') >= 0:
2127 if not printed_file:
2130 if not printed_file:
2128 ui.warn(pf + '\n')
2131 ui.warn(pf + '\n')
2129 printed_file = True
2132 printed_file = True
2130 ui.warn(line + '\n')
2133 ui.warn(line + '\n')
2131 finally:
2134 finally:
2132 if files:
2135 if files:
2133 scmutil.marktouched(repo, files, similarity)
2136 scmutil.marktouched(repo, files, similarity)
2134 code = fp.close()
2137 code = fp.close()
2135 if code:
2138 if code:
2136 raise PatchError(_("patch command failed: %s") %
2139 raise PatchError(_("patch command failed: %s") %
2137 procutil.explainexit(code))
2140 procutil.explainexit(code))
2138 return fuzz
2141 return fuzz
2139
2142
2140 def patchbackend(ui, backend, patchobj, strip, prefix, files=None,
2143 def patchbackend(ui, backend, patchobj, strip, prefix, files=None,
2141 eolmode='strict'):
2144 eolmode='strict'):
2142 if files is None:
2145 if files is None:
2143 files = set()
2146 files = set()
2144 if eolmode is None:
2147 if eolmode is None:
2145 eolmode = ui.config('patch', 'eol')
2148 eolmode = ui.config('patch', 'eol')
2146 if eolmode.lower() not in eolmodes:
2149 if eolmode.lower() not in eolmodes:
2147 raise error.Abort(_('unsupported line endings type: %s') % eolmode)
2150 raise error.Abort(_('unsupported line endings type: %s') % eolmode)
2148 eolmode = eolmode.lower()
2151 eolmode = eolmode.lower()
2149
2152
2150 store = filestore()
2153 store = filestore()
2151 try:
2154 try:
2152 fp = open(patchobj, 'rb')
2155 fp = open(patchobj, 'rb')
2153 except TypeError:
2156 except TypeError:
2154 fp = patchobj
2157 fp = patchobj
2155 try:
2158 try:
2156 ret = applydiff(ui, fp, backend, store, strip=strip, prefix=prefix,
2159 ret = applydiff(ui, fp, backend, store, strip=strip, prefix=prefix,
2157 eolmode=eolmode)
2160 eolmode=eolmode)
2158 finally:
2161 finally:
2159 if fp != patchobj:
2162 if fp != patchobj:
2160 fp.close()
2163 fp.close()
2161 files.update(backend.close())
2164 files.update(backend.close())
2162 store.close()
2165 store.close()
2163 if ret < 0:
2166 if ret < 0:
2164 raise PatchError(_('patch failed to apply'))
2167 raise PatchError(_('patch failed to apply'))
2165 return ret > 0
2168 return ret > 0
2166
2169
2167 def internalpatch(ui, repo, patchobj, strip, prefix='', files=None,
2170 def internalpatch(ui, repo, patchobj, strip, prefix='', files=None,
2168 eolmode='strict', similarity=0):
2171 eolmode='strict', similarity=0):
2169 """use builtin patch to apply <patchobj> to the working directory.
2172 """use builtin patch to apply <patchobj> to the working directory.
2170 returns whether patch was applied with fuzz factor."""
2173 returns whether patch was applied with fuzz factor."""
2171 backend = workingbackend(ui, repo, similarity)
2174 backend = workingbackend(ui, repo, similarity)
2172 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
2175 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
2173
2176
2174 def patchrepo(ui, repo, ctx, store, patchobj, strip, prefix, files=None,
2177 def patchrepo(ui, repo, ctx, store, patchobj, strip, prefix, files=None,
2175 eolmode='strict'):
2178 eolmode='strict'):
2176 backend = repobackend(ui, repo, ctx, store)
2179 backend = repobackend(ui, repo, ctx, store)
2177 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
2180 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
2178
2181
2179 def patch(ui, repo, patchname, strip=1, prefix='', files=None, eolmode='strict',
2182 def patch(ui, repo, patchname, strip=1, prefix='', files=None, eolmode='strict',
2180 similarity=0):
2183 similarity=0):
2181 """Apply <patchname> to the working directory.
2184 """Apply <patchname> to the working directory.
2182
2185
2183 'eolmode' specifies how end of lines should be handled. It can be:
2186 'eolmode' specifies how end of lines should be handled. It can be:
2184 - 'strict': inputs are read in binary mode, EOLs are preserved
2187 - 'strict': inputs are read in binary mode, EOLs are preserved
2185 - 'crlf': EOLs are ignored when patching and reset to CRLF
2188 - 'crlf': EOLs are ignored when patching and reset to CRLF
2186 - 'lf': EOLs are ignored when patching and reset to LF
2189 - 'lf': EOLs are ignored when patching and reset to LF
2187 - None: get it from user settings, default to 'strict'
2190 - None: get it from user settings, default to 'strict'
2188 'eolmode' is ignored when using an external patcher program.
2191 'eolmode' is ignored when using an external patcher program.
2189
2192
2190 Returns whether patch was applied with fuzz factor.
2193 Returns whether patch was applied with fuzz factor.
2191 """
2194 """
2192 patcher = ui.config('ui', 'patch')
2195 patcher = ui.config('ui', 'patch')
2193 if files is None:
2196 if files is None:
2194 files = set()
2197 files = set()
2195 if patcher:
2198 if patcher:
2196 return _externalpatch(ui, repo, patcher, patchname, strip,
2199 return _externalpatch(ui, repo, patcher, patchname, strip,
2197 files, similarity)
2200 files, similarity)
2198 return internalpatch(ui, repo, patchname, strip, prefix, files, eolmode,
2201 return internalpatch(ui, repo, patchname, strip, prefix, files, eolmode,
2199 similarity)
2202 similarity)
2200
2203
2201 def changedfiles(ui, repo, patchpath, strip=1, prefix=''):
2204 def changedfiles(ui, repo, patchpath, strip=1, prefix=''):
2202 backend = fsbackend(ui, repo.root)
2205 backend = fsbackend(ui, repo.root)
2203 prefix = _canonprefix(repo, prefix)
2206 prefix = _canonprefix(repo, prefix)
2204 with open(patchpath, 'rb') as fp:
2207 with open(patchpath, 'rb') as fp:
2205 changed = set()
2208 changed = set()
2206 for state, values in iterhunks(fp):
2209 for state, values in iterhunks(fp):
2207 if state == 'file':
2210 if state == 'file':
2208 afile, bfile, first_hunk, gp = values
2211 afile, bfile, first_hunk, gp = values
2209 if gp:
2212 if gp:
2210 gp.path = pathtransform(gp.path, strip - 1, prefix)[1]
2213 gp.path = pathtransform(gp.path, strip - 1, prefix)[1]
2211 if gp.oldpath:
2214 if gp.oldpath:
2212 gp.oldpath = pathtransform(gp.oldpath, strip - 1,
2215 gp.oldpath = pathtransform(gp.oldpath, strip - 1,
2213 prefix)[1]
2216 prefix)[1]
2214 else:
2217 else:
2215 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
2218 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
2216 prefix)
2219 prefix)
2217 changed.add(gp.path)
2220 changed.add(gp.path)
2218 if gp.op == 'RENAME':
2221 if gp.op == 'RENAME':
2219 changed.add(gp.oldpath)
2222 changed.add(gp.oldpath)
2220 elif state not in ('hunk', 'git'):
2223 elif state not in ('hunk', 'git'):
2221 raise error.Abort(_('unsupported parser state: %s') % state)
2224 raise error.Abort(_('unsupported parser state: %s') % state)
2222 return changed
2225 return changed
2223
2226
2224 class GitDiffRequired(Exception):
2227 class GitDiffRequired(Exception):
2225 pass
2228 pass
2226
2229
2227 def diffallopts(ui, opts=None, untrusted=False, section='diff'):
2230 def diffallopts(ui, opts=None, untrusted=False, section='diff'):
2228 '''return diffopts with all features supported and parsed'''
2231 '''return diffopts with all features supported and parsed'''
2229 return difffeatureopts(ui, opts=opts, untrusted=untrusted, section=section,
2232 return difffeatureopts(ui, opts=opts, untrusted=untrusted, section=section,
2230 git=True, whitespace=True, formatchanging=True)
2233 git=True, whitespace=True, formatchanging=True)
2231
2234
2232 diffopts = diffallopts
2235 diffopts = diffallopts
2233
2236
2234 def difffeatureopts(ui, opts=None, untrusted=False, section='diff', git=False,
2237 def difffeatureopts(ui, opts=None, untrusted=False, section='diff', git=False,
2235 whitespace=False, formatchanging=False):
2238 whitespace=False, formatchanging=False):
2236 '''return diffopts with only opted-in features parsed
2239 '''return diffopts with only opted-in features parsed
2237
2240
2238 Features:
2241 Features:
2239 - git: git-style diffs
2242 - git: git-style diffs
2240 - whitespace: whitespace options like ignoreblanklines and ignorews
2243 - whitespace: whitespace options like ignoreblanklines and ignorews
2241 - formatchanging: options that will likely break or cause correctness issues
2244 - formatchanging: options that will likely break or cause correctness issues
2242 with most diff parsers
2245 with most diff parsers
2243 '''
2246 '''
2244 def get(key, name=None, getter=ui.configbool, forceplain=None):
2247 def get(key, name=None, getter=ui.configbool, forceplain=None):
2245 if opts:
2248 if opts:
2246 v = opts.get(key)
2249 v = opts.get(key)
2247 # diffopts flags are either None-default (which is passed
2250 # diffopts flags are either None-default (which is passed
2248 # through unchanged, so we can identify unset values), or
2251 # through unchanged, so we can identify unset values), or
2249 # some other falsey default (eg --unified, which defaults
2252 # some other falsey default (eg --unified, which defaults
2250 # to an empty string). We only want to override the config
2253 # to an empty string). We only want to override the config
2251 # entries from hgrc with command line values if they
2254 # entries from hgrc with command line values if they
2252 # appear to have been set, which is any truthy value,
2255 # appear to have been set, which is any truthy value,
2253 # True, or False.
2256 # True, or False.
2254 if v or isinstance(v, bool):
2257 if v or isinstance(v, bool):
2255 return v
2258 return v
2256 if forceplain is not None and ui.plain():
2259 if forceplain is not None and ui.plain():
2257 return forceplain
2260 return forceplain
2258 return getter(section, name or key, untrusted=untrusted)
2261 return getter(section, name or key, untrusted=untrusted)
2259
2262
2260 # core options, expected to be understood by every diff parser
2263 # core options, expected to be understood by every diff parser
2261 buildopts = {
2264 buildopts = {
2262 'nodates': get('nodates'),
2265 'nodates': get('nodates'),
2263 'showfunc': get('show_function', 'showfunc'),
2266 'showfunc': get('show_function', 'showfunc'),
2264 'context': get('unified', getter=ui.config),
2267 'context': get('unified', getter=ui.config),
2265 }
2268 }
2266 buildopts['worddiff'] = ui.configbool('experimental', 'worddiff')
2269 buildopts['worddiff'] = ui.configbool('experimental', 'worddiff')
2267 buildopts['xdiff'] = ui.configbool('experimental', 'xdiff')
2270 buildopts['xdiff'] = ui.configbool('experimental', 'xdiff')
2268
2271
2269 if git:
2272 if git:
2270 buildopts['git'] = get('git')
2273 buildopts['git'] = get('git')
2271
2274
2272 # since this is in the experimental section, we need to call
2275 # since this is in the experimental section, we need to call
2273 # ui.configbool directory
2276 # ui.configbool directory
2274 buildopts['showsimilarity'] = ui.configbool('experimental',
2277 buildopts['showsimilarity'] = ui.configbool('experimental',
2275 'extendedheader.similarity')
2278 'extendedheader.similarity')
2276
2279
2277 # need to inspect the ui object instead of using get() since we want to
2280 # need to inspect the ui object instead of using get() since we want to
2278 # test for an int
2281 # test for an int
2279 hconf = ui.config('experimental', 'extendedheader.index')
2282 hconf = ui.config('experimental', 'extendedheader.index')
2280 if hconf is not None:
2283 if hconf is not None:
2281 hlen = None
2284 hlen = None
2282 try:
2285 try:
2283 # the hash config could be an integer (for length of hash) or a
2286 # the hash config could be an integer (for length of hash) or a
2284 # word (e.g. short, full, none)
2287 # word (e.g. short, full, none)
2285 hlen = int(hconf)
2288 hlen = int(hconf)
2286 if hlen < 0 or hlen > 40:
2289 if hlen < 0 or hlen > 40:
2287 msg = _("invalid length for extendedheader.index: '%d'\n")
2290 msg = _("invalid length for extendedheader.index: '%d'\n")
2288 ui.warn(msg % hlen)
2291 ui.warn(msg % hlen)
2289 except ValueError:
2292 except ValueError:
2290 # default value
2293 # default value
2291 if hconf == 'short' or hconf == '':
2294 if hconf == 'short' or hconf == '':
2292 hlen = 12
2295 hlen = 12
2293 elif hconf == 'full':
2296 elif hconf == 'full':
2294 hlen = 40
2297 hlen = 40
2295 elif hconf != 'none':
2298 elif hconf != 'none':
2296 msg = _("invalid value for extendedheader.index: '%s'\n")
2299 msg = _("invalid value for extendedheader.index: '%s'\n")
2297 ui.warn(msg % hconf)
2300 ui.warn(msg % hconf)
2298 finally:
2301 finally:
2299 buildopts['index'] = hlen
2302 buildopts['index'] = hlen
2300
2303
2301 if whitespace:
2304 if whitespace:
2302 buildopts['ignorews'] = get('ignore_all_space', 'ignorews')
2305 buildopts['ignorews'] = get('ignore_all_space', 'ignorews')
2303 buildopts['ignorewsamount'] = get('ignore_space_change',
2306 buildopts['ignorewsamount'] = get('ignore_space_change',
2304 'ignorewsamount')
2307 'ignorewsamount')
2305 buildopts['ignoreblanklines'] = get('ignore_blank_lines',
2308 buildopts['ignoreblanklines'] = get('ignore_blank_lines',
2306 'ignoreblanklines')
2309 'ignoreblanklines')
2307 buildopts['ignorewseol'] = get('ignore_space_at_eol', 'ignorewseol')
2310 buildopts['ignorewseol'] = get('ignore_space_at_eol', 'ignorewseol')
2308 if formatchanging:
2311 if formatchanging:
2309 buildopts['text'] = opts and opts.get('text')
2312 buildopts['text'] = opts and opts.get('text')
2310 binary = None if opts is None else opts.get('binary')
2313 binary = None if opts is None else opts.get('binary')
2311 buildopts['nobinary'] = (not binary if binary is not None
2314 buildopts['nobinary'] = (not binary if binary is not None
2312 else get('nobinary', forceplain=False))
2315 else get('nobinary', forceplain=False))
2313 buildopts['noprefix'] = get('noprefix', forceplain=False)
2316 buildopts['noprefix'] = get('noprefix', forceplain=False)
2314
2317
2315 return mdiff.diffopts(**pycompat.strkwargs(buildopts))
2318 return mdiff.diffopts(**pycompat.strkwargs(buildopts))
2316
2319
2317 def diff(repo, node1=None, node2=None, match=None, changes=None,
2320 def diff(repo, node1=None, node2=None, match=None, changes=None,
2318 opts=None, losedatafn=None, prefix='', relroot='', copy=None,
2321 opts=None, losedatafn=None, prefix='', relroot='', copy=None,
2319 hunksfilterfn=None):
2322 hunksfilterfn=None):
2320 '''yields diff of changes to files between two nodes, or node and
2323 '''yields diff of changes to files between two nodes, or node and
2321 working directory.
2324 working directory.
2322
2325
2323 if node1 is None, use first dirstate parent instead.
2326 if node1 is None, use first dirstate parent instead.
2324 if node2 is None, compare node1 with working directory.
2327 if node2 is None, compare node1 with working directory.
2325
2328
2326 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
2329 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
2327 every time some change cannot be represented with the current
2330 every time some change cannot be represented with the current
2328 patch format. Return False to upgrade to git patch format, True to
2331 patch format. Return False to upgrade to git patch format, True to
2329 accept the loss or raise an exception to abort the diff. It is
2332 accept the loss or raise an exception to abort the diff. It is
2330 called with the name of current file being diffed as 'fn'. If set
2333 called with the name of current file being diffed as 'fn'. If set
2331 to None, patches will always be upgraded to git format when
2334 to None, patches will always be upgraded to git format when
2332 necessary.
2335 necessary.
2333
2336
2334 prefix is a filename prefix that is prepended to all filenames on
2337 prefix is a filename prefix that is prepended to all filenames on
2335 display (used for subrepos).
2338 display (used for subrepos).
2336
2339
2337 relroot, if not empty, must be normalized with a trailing /. Any match
2340 relroot, if not empty, must be normalized with a trailing /. Any match
2338 patterns that fall outside it will be ignored.
2341 patterns that fall outside it will be ignored.
2339
2342
2340 copy, if not empty, should contain mappings {dst@y: src@x} of copy
2343 copy, if not empty, should contain mappings {dst@y: src@x} of copy
2341 information.
2344 information.
2342
2345
2343 hunksfilterfn, if not None, should be a function taking a filectx and
2346 hunksfilterfn, if not None, should be a function taking a filectx and
2344 hunks generator that may yield filtered hunks.
2347 hunks generator that may yield filtered hunks.
2345 '''
2348 '''
2346 for fctx1, fctx2, hdr, hunks in diffhunks(
2349 for fctx1, fctx2, hdr, hunks in diffhunks(
2347 repo, node1=node1, node2=node2,
2350 repo, node1=node1, node2=node2,
2348 match=match, changes=changes, opts=opts,
2351 match=match, changes=changes, opts=opts,
2349 losedatafn=losedatafn, prefix=prefix, relroot=relroot, copy=copy,
2352 losedatafn=losedatafn, prefix=prefix, relroot=relroot, copy=copy,
2350 ):
2353 ):
2351 if hunksfilterfn is not None:
2354 if hunksfilterfn is not None:
2352 # If the file has been removed, fctx2 is None; but this should
2355 # If the file has been removed, fctx2 is None; but this should
2353 # not occur here since we catch removed files early in
2356 # not occur here since we catch removed files early in
2354 # logcmdutil.getlinerangerevs() for 'hg log -L'.
2357 # logcmdutil.getlinerangerevs() for 'hg log -L'.
2355 assert fctx2 is not None, \
2358 assert fctx2 is not None, \
2356 'fctx2 unexpectly None in diff hunks filtering'
2359 'fctx2 unexpectly None in diff hunks filtering'
2357 hunks = hunksfilterfn(fctx2, hunks)
2360 hunks = hunksfilterfn(fctx2, hunks)
2358 text = ''.join(sum((list(hlines) for hrange, hlines in hunks), []))
2361 text = ''.join(sum((list(hlines) for hrange, hlines in hunks), []))
2359 if hdr and (text or len(hdr) > 1):
2362 if hdr and (text or len(hdr) > 1):
2360 yield '\n'.join(hdr) + '\n'
2363 yield '\n'.join(hdr) + '\n'
2361 if text:
2364 if text:
2362 yield text
2365 yield text
2363
2366
2364 def diffhunks(repo, node1=None, node2=None, match=None, changes=None,
2367 def diffhunks(repo, node1=None, node2=None, match=None, changes=None,
2365 opts=None, losedatafn=None, prefix='', relroot='', copy=None):
2368 opts=None, losedatafn=None, prefix='', relroot='', copy=None):
2366 """Yield diff of changes to files in the form of (`header`, `hunks`) tuples
2369 """Yield diff of changes to files in the form of (`header`, `hunks`) tuples
2367 where `header` is a list of diff headers and `hunks` is an iterable of
2370 where `header` is a list of diff headers and `hunks` is an iterable of
2368 (`hunkrange`, `hunklines`) tuples.
2371 (`hunkrange`, `hunklines`) tuples.
2369
2372
2370 See diff() for the meaning of parameters.
2373 See diff() for the meaning of parameters.
2371 """
2374 """
2372
2375
2373 if opts is None:
2376 if opts is None:
2374 opts = mdiff.defaultopts
2377 opts = mdiff.defaultopts
2375
2378
2376 if not node1 and not node2:
2379 if not node1 and not node2:
2377 node1 = repo.dirstate.p1()
2380 node1 = repo.dirstate.p1()
2378
2381
2379 def lrugetfilectx():
2382 def lrugetfilectx():
2380 cache = {}
2383 cache = {}
2381 order = collections.deque()
2384 order = collections.deque()
2382 def getfilectx(f, ctx):
2385 def getfilectx(f, ctx):
2383 fctx = ctx.filectx(f, filelog=cache.get(f))
2386 fctx = ctx.filectx(f, filelog=cache.get(f))
2384 if f not in cache:
2387 if f not in cache:
2385 if len(cache) > 20:
2388 if len(cache) > 20:
2386 del cache[order.popleft()]
2389 del cache[order.popleft()]
2387 cache[f] = fctx.filelog()
2390 cache[f] = fctx.filelog()
2388 else:
2391 else:
2389 order.remove(f)
2392 order.remove(f)
2390 order.append(f)
2393 order.append(f)
2391 return fctx
2394 return fctx
2392 return getfilectx
2395 return getfilectx
2393 getfilectx = lrugetfilectx()
2396 getfilectx = lrugetfilectx()
2394
2397
2395 ctx1 = repo[node1]
2398 ctx1 = repo[node1]
2396 ctx2 = repo[node2]
2399 ctx2 = repo[node2]
2397
2400
2398 relfiltered = False
2401 relfiltered = False
2399 if relroot != '' and match.always():
2402 if relroot != '' and match.always():
2400 # as a special case, create a new matcher with just the relroot
2403 # as a special case, create a new matcher with just the relroot
2401 pats = [relroot]
2404 pats = [relroot]
2402 match = scmutil.match(ctx2, pats, default='path')
2405 match = scmutil.match(ctx2, pats, default='path')
2403 relfiltered = True
2406 relfiltered = True
2404
2407
2405 if not changes:
2408 if not changes:
2406 changes = repo.status(ctx1, ctx2, match=match)
2409 changes = repo.status(ctx1, ctx2, match=match)
2407 modified, added, removed = changes[:3]
2410 modified, added, removed = changes[:3]
2408
2411
2409 if not modified and not added and not removed:
2412 if not modified and not added and not removed:
2410 return []
2413 return []
2411
2414
2412 if repo.ui.debugflag:
2415 if repo.ui.debugflag:
2413 hexfunc = hex
2416 hexfunc = hex
2414 else:
2417 else:
2415 hexfunc = short
2418 hexfunc = short
2416 revs = [hexfunc(node) for node in [ctx1.node(), ctx2.node()] if node]
2419 revs = [hexfunc(node) for node in [ctx1.node(), ctx2.node()] if node]
2417
2420
2418 if copy is None:
2421 if copy is None:
2419 copy = {}
2422 copy = {}
2420 if opts.git or opts.upgrade:
2423 if opts.git or opts.upgrade:
2421 copy = copies.pathcopies(ctx1, ctx2, match=match)
2424 copy = copies.pathcopies(ctx1, ctx2, match=match)
2422
2425
2423 if relroot is not None:
2426 if relroot is not None:
2424 if not relfiltered:
2427 if not relfiltered:
2425 # XXX this would ideally be done in the matcher, but that is
2428 # XXX this would ideally be done in the matcher, but that is
2426 # generally meant to 'or' patterns, not 'and' them. In this case we
2429 # generally meant to 'or' patterns, not 'and' them. In this case we
2427 # need to 'and' all the patterns from the matcher with relroot.
2430 # need to 'and' all the patterns from the matcher with relroot.
2428 def filterrel(l):
2431 def filterrel(l):
2429 return [f for f in l if f.startswith(relroot)]
2432 return [f for f in l if f.startswith(relroot)]
2430 modified = filterrel(modified)
2433 modified = filterrel(modified)
2431 added = filterrel(added)
2434 added = filterrel(added)
2432 removed = filterrel(removed)
2435 removed = filterrel(removed)
2433 relfiltered = True
2436 relfiltered = True
2434 # filter out copies where either side isn't inside the relative root
2437 # filter out copies where either side isn't inside the relative root
2435 copy = dict(((dst, src) for (dst, src) in copy.iteritems()
2438 copy = dict(((dst, src) for (dst, src) in copy.iteritems()
2436 if dst.startswith(relroot)
2439 if dst.startswith(relroot)
2437 and src.startswith(relroot)))
2440 and src.startswith(relroot)))
2438
2441
2439 modifiedset = set(modified)
2442 modifiedset = set(modified)
2440 addedset = set(added)
2443 addedset = set(added)
2441 removedset = set(removed)
2444 removedset = set(removed)
2442 for f in modified:
2445 for f in modified:
2443 if f not in ctx1:
2446 if f not in ctx1:
2444 # Fix up added, since merged-in additions appear as
2447 # Fix up added, since merged-in additions appear as
2445 # modifications during merges
2448 # modifications during merges
2446 modifiedset.remove(f)
2449 modifiedset.remove(f)
2447 addedset.add(f)
2450 addedset.add(f)
2448 for f in removed:
2451 for f in removed:
2449 if f not in ctx1:
2452 if f not in ctx1:
2450 # Merged-in additions that are then removed are reported as removed.
2453 # Merged-in additions that are then removed are reported as removed.
2451 # They are not in ctx1, so We don't want to show them in the diff.
2454 # They are not in ctx1, so We don't want to show them in the diff.
2452 removedset.remove(f)
2455 removedset.remove(f)
2453 modified = sorted(modifiedset)
2456 modified = sorted(modifiedset)
2454 added = sorted(addedset)
2457 added = sorted(addedset)
2455 removed = sorted(removedset)
2458 removed = sorted(removedset)
2456 for dst, src in list(copy.items()):
2459 for dst, src in list(copy.items()):
2457 if src not in ctx1:
2460 if src not in ctx1:
2458 # Files merged in during a merge and then copied/renamed are
2461 # Files merged in during a merge and then copied/renamed are
2459 # reported as copies. We want to show them in the diff as additions.
2462 # reported as copies. We want to show them in the diff as additions.
2460 del copy[dst]
2463 del copy[dst]
2461
2464
2462 def difffn(opts, losedata):
2465 def difffn(opts, losedata):
2463 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2466 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2464 copy, getfilectx, opts, losedata, prefix, relroot)
2467 copy, getfilectx, opts, losedata, prefix, relroot)
2465 if opts.upgrade and not opts.git:
2468 if opts.upgrade and not opts.git:
2466 try:
2469 try:
2467 def losedata(fn):
2470 def losedata(fn):
2468 if not losedatafn or not losedatafn(fn=fn):
2471 if not losedatafn or not losedatafn(fn=fn):
2469 raise GitDiffRequired
2472 raise GitDiffRequired
2470 # Buffer the whole output until we are sure it can be generated
2473 # Buffer the whole output until we are sure it can be generated
2471 return list(difffn(opts.copy(git=False), losedata))
2474 return list(difffn(opts.copy(git=False), losedata))
2472 except GitDiffRequired:
2475 except GitDiffRequired:
2473 return difffn(opts.copy(git=True), None)
2476 return difffn(opts.copy(git=True), None)
2474 else:
2477 else:
2475 return difffn(opts, None)
2478 return difffn(opts, None)
2476
2479
2477 def difflabel(func, *args, **kw):
2480 def difflabel(func, *args, **kw):
2478 '''yields 2-tuples of (output, label) based on the output of func()'''
2481 '''yields 2-tuples of (output, label) based on the output of func()'''
2479 inlinecolor = False
2482 inlinecolor = False
2480 if kw.get(r'opts'):
2483 if kw.get(r'opts'):
2481 inlinecolor = kw[r'opts'].worddiff
2484 inlinecolor = kw[r'opts'].worddiff
2482 headprefixes = [('diff', 'diff.diffline'),
2485 headprefixes = [('diff', 'diff.diffline'),
2483 ('copy', 'diff.extended'),
2486 ('copy', 'diff.extended'),
2484 ('rename', 'diff.extended'),
2487 ('rename', 'diff.extended'),
2485 ('old', 'diff.extended'),
2488 ('old', 'diff.extended'),
2486 ('new', 'diff.extended'),
2489 ('new', 'diff.extended'),
2487 ('deleted', 'diff.extended'),
2490 ('deleted', 'diff.extended'),
2488 ('index', 'diff.extended'),
2491 ('index', 'diff.extended'),
2489 ('similarity', 'diff.extended'),
2492 ('similarity', 'diff.extended'),
2490 ('---', 'diff.file_a'),
2493 ('---', 'diff.file_a'),
2491 ('+++', 'diff.file_b')]
2494 ('+++', 'diff.file_b')]
2492 textprefixes = [('@', 'diff.hunk'),
2495 textprefixes = [('@', 'diff.hunk'),
2493 ('-', 'diff.deleted'),
2496 ('-', 'diff.deleted'),
2494 ('+', 'diff.inserted')]
2497 ('+', 'diff.inserted')]
2495 head = False
2498 head = False
2496 for chunk in func(*args, **kw):
2499 for chunk in func(*args, **kw):
2497 lines = chunk.split('\n')
2500 lines = chunk.split('\n')
2498 matches = {}
2501 matches = {}
2499 if inlinecolor:
2502 if inlinecolor:
2500 matches = _findmatches(lines)
2503 matches = _findmatches(lines)
2501 for i, line in enumerate(lines):
2504 for i, line in enumerate(lines):
2502 if i != 0:
2505 if i != 0:
2503 yield ('\n', '')
2506 yield ('\n', '')
2504 if head:
2507 if head:
2505 if line.startswith('@'):
2508 if line.startswith('@'):
2506 head = False
2509 head = False
2507 else:
2510 else:
2508 if line and not line.startswith((' ', '+', '-', '@', '\\')):
2511 if line and not line.startswith((' ', '+', '-', '@', '\\')):
2509 head = True
2512 head = True
2510 stripline = line
2513 stripline = line
2511 diffline = False
2514 diffline = False
2512 if not head and line and line.startswith(('+', '-')):
2515 if not head and line and line.startswith(('+', '-')):
2513 # highlight tabs and trailing whitespace, but only in
2516 # highlight tabs and trailing whitespace, but only in
2514 # changed lines
2517 # changed lines
2515 stripline = line.rstrip()
2518 stripline = line.rstrip()
2516 diffline = True
2519 diffline = True
2517
2520
2518 prefixes = textprefixes
2521 prefixes = textprefixes
2519 if head:
2522 if head:
2520 prefixes = headprefixes
2523 prefixes = headprefixes
2521 for prefix, label in prefixes:
2524 for prefix, label in prefixes:
2522 if stripline.startswith(prefix):
2525 if stripline.startswith(prefix):
2523 if diffline:
2526 if diffline:
2524 if i in matches:
2527 if i in matches:
2525 for t, l in _inlinediff(lines[i].rstrip(),
2528 for t, l in _inlinediff(lines[i].rstrip(),
2526 lines[matches[i]].rstrip(),
2529 lines[matches[i]].rstrip(),
2527 label):
2530 label):
2528 yield (t, l)
2531 yield (t, l)
2529 else:
2532 else:
2530 for token in tabsplitter.findall(stripline):
2533 for token in tabsplitter.findall(stripline):
2531 if token.startswith('\t'):
2534 if token.startswith('\t'):
2532 yield (token, 'diff.tab')
2535 yield (token, 'diff.tab')
2533 else:
2536 else:
2534 yield (token, label)
2537 yield (token, label)
2535 else:
2538 else:
2536 yield (stripline, label)
2539 yield (stripline, label)
2537 break
2540 break
2538 else:
2541 else:
2539 yield (line, '')
2542 yield (line, '')
2540 if line != stripline:
2543 if line != stripline:
2541 yield (line[len(stripline):], 'diff.trailingwhitespace')
2544 yield (line[len(stripline):], 'diff.trailingwhitespace')
2542
2545
2543 def _findmatches(slist):
2546 def _findmatches(slist):
2544 '''Look for insertion matches to deletion and returns a dict of
2547 '''Look for insertion matches to deletion and returns a dict of
2545 correspondences.
2548 correspondences.
2546 '''
2549 '''
2547 lastmatch = 0
2550 lastmatch = 0
2548 matches = {}
2551 matches = {}
2549 for i, line in enumerate(slist):
2552 for i, line in enumerate(slist):
2550 if line == '':
2553 if line == '':
2551 continue
2554 continue
2552 if line.startswith('-'):
2555 if line.startswith('-'):
2553 lastmatch = max(lastmatch, i)
2556 lastmatch = max(lastmatch, i)
2554 newgroup = False
2557 newgroup = False
2555 for j, newline in enumerate(slist[lastmatch + 1:]):
2558 for j, newline in enumerate(slist[lastmatch + 1:]):
2556 if newline == '':
2559 if newline == '':
2557 continue
2560 continue
2558 if newline.startswith('-') and newgroup: # too far, no match
2561 if newline.startswith('-') and newgroup: # too far, no match
2559 break
2562 break
2560 if newline.startswith('+'): # potential match
2563 if newline.startswith('+'): # potential match
2561 newgroup = True
2564 newgroup = True
2562 sim = difflib.SequenceMatcher(None, line, newline).ratio()
2565 sim = difflib.SequenceMatcher(None, line, newline).ratio()
2563 if sim > 0.7:
2566 if sim > 0.7:
2564 lastmatch = lastmatch + 1 + j
2567 lastmatch = lastmatch + 1 + j
2565 matches[i] = lastmatch
2568 matches[i] = lastmatch
2566 matches[lastmatch] = i
2569 matches[lastmatch] = i
2567 break
2570 break
2568 return matches
2571 return matches
2569
2572
2570 def _inlinediff(s1, s2, operation):
2573 def _inlinediff(s1, s2, operation):
2571 '''Perform string diff to highlight specific changes.'''
2574 '''Perform string diff to highlight specific changes.'''
2572 operation_skip = ('+', '?') if operation == 'diff.deleted' else ('-', '?')
2575 operation_skip = ('+', '?') if operation == 'diff.deleted' else ('-', '?')
2573 if operation == 'diff.deleted':
2576 if operation == 'diff.deleted':
2574 s2, s1 = s1, s2
2577 s2, s1 = s1, s2
2575
2578
2576 buff = []
2579 buff = []
2577 # we never want to higlight the leading +-
2580 # we never want to higlight the leading +-
2578 if operation == 'diff.deleted' and s2.startswith('-'):
2581 if operation == 'diff.deleted' and s2.startswith('-'):
2579 label = operation
2582 label = operation
2580 token = '-'
2583 token = '-'
2581 s2 = s2[1:]
2584 s2 = s2[1:]
2582 s1 = s1[1:]
2585 s1 = s1[1:]
2583 elif operation == 'diff.inserted' and s1.startswith('+'):
2586 elif operation == 'diff.inserted' and s1.startswith('+'):
2584 label = operation
2587 label = operation
2585 token = '+'
2588 token = '+'
2586 s2 = s2[1:]
2589 s2 = s2[1:]
2587 s1 = s1[1:]
2590 s1 = s1[1:]
2588 else:
2591 else:
2589 raise error.ProgrammingError("Case not expected, operation = %s" %
2592 raise error.ProgrammingError("Case not expected, operation = %s" %
2590 operation)
2593 operation)
2591
2594
2592 s = difflib.ndiff(_nonwordre.split(s2), _nonwordre.split(s1))
2595 s = difflib.ndiff(_nonwordre.split(s2), _nonwordre.split(s1))
2593 for part in s:
2596 for part in s:
2594 if part.startswith(operation_skip) or len(part) == 2:
2597 if part.startswith(operation_skip) or len(part) == 2:
2595 continue
2598 continue
2596 l = operation + '.highlight'
2599 l = operation + '.highlight'
2597 if part.startswith(' '):
2600 if part.startswith(' '):
2598 l = operation
2601 l = operation
2599 if part[2:] == '\t':
2602 if part[2:] == '\t':
2600 l = 'diff.tab'
2603 l = 'diff.tab'
2601 if l == label: # contiguous token with same label
2604 if l == label: # contiguous token with same label
2602 token += part[2:]
2605 token += part[2:]
2603 continue
2606 continue
2604 else:
2607 else:
2605 buff.append((token, label))
2608 buff.append((token, label))
2606 label = l
2609 label = l
2607 token = part[2:]
2610 token = part[2:]
2608 buff.append((token, label))
2611 buff.append((token, label))
2609
2612
2610 return buff
2613 return buff
2611
2614
2612 def diffui(*args, **kw):
2615 def diffui(*args, **kw):
2613 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
2616 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
2614 return difflabel(diff, *args, **kw)
2617 return difflabel(diff, *args, **kw)
2615
2618
2616 def _filepairs(modified, added, removed, copy, opts):
2619 def _filepairs(modified, added, removed, copy, opts):
2617 '''generates tuples (f1, f2, copyop), where f1 is the name of the file
2620 '''generates tuples (f1, f2, copyop), where f1 is the name of the file
2618 before and f2 is the the name after. For added files, f1 will be None,
2621 before and f2 is the the name after. For added files, f1 will be None,
2619 and for removed files, f2 will be None. copyop may be set to None, 'copy'
2622 and for removed files, f2 will be None. copyop may be set to None, 'copy'
2620 or 'rename' (the latter two only if opts.git is set).'''
2623 or 'rename' (the latter two only if opts.git is set).'''
2621 gone = set()
2624 gone = set()
2622
2625
2623 copyto = dict([(v, k) for k, v in copy.items()])
2626 copyto = dict([(v, k) for k, v in copy.items()])
2624
2627
2625 addedset, removedset = set(added), set(removed)
2628 addedset, removedset = set(added), set(removed)
2626
2629
2627 for f in sorted(modified + added + removed):
2630 for f in sorted(modified + added + removed):
2628 copyop = None
2631 copyop = None
2629 f1, f2 = f, f
2632 f1, f2 = f, f
2630 if f in addedset:
2633 if f in addedset:
2631 f1 = None
2634 f1 = None
2632 if f in copy:
2635 if f in copy:
2633 if opts.git:
2636 if opts.git:
2634 f1 = copy[f]
2637 f1 = copy[f]
2635 if f1 in removedset and f1 not in gone:
2638 if f1 in removedset and f1 not in gone:
2636 copyop = 'rename'
2639 copyop = 'rename'
2637 gone.add(f1)
2640 gone.add(f1)
2638 else:
2641 else:
2639 copyop = 'copy'
2642 copyop = 'copy'
2640 elif f in removedset:
2643 elif f in removedset:
2641 f2 = None
2644 f2 = None
2642 if opts.git:
2645 if opts.git:
2643 # have we already reported a copy above?
2646 # have we already reported a copy above?
2644 if (f in copyto and copyto[f] in addedset
2647 if (f in copyto and copyto[f] in addedset
2645 and copy[copyto[f]] == f):
2648 and copy[copyto[f]] == f):
2646 continue
2649 continue
2647 yield f1, f2, copyop
2650 yield f1, f2, copyop
2648
2651
2649 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2652 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2650 copy, getfilectx, opts, losedatafn, prefix, relroot):
2653 copy, getfilectx, opts, losedatafn, prefix, relroot):
2651 '''given input data, generate a diff and yield it in blocks
2654 '''given input data, generate a diff and yield it in blocks
2652
2655
2653 If generating a diff would lose data like flags or binary data and
2656 If generating a diff would lose data like flags or binary data and
2654 losedatafn is not None, it will be called.
2657 losedatafn is not None, it will be called.
2655
2658
2656 relroot is removed and prefix is added to every path in the diff output.
2659 relroot is removed and prefix is added to every path in the diff output.
2657
2660
2658 If relroot is not empty, this function expects every path in modified,
2661 If relroot is not empty, this function expects every path in modified,
2659 added, removed and copy to start with it.'''
2662 added, removed and copy to start with it.'''
2660
2663
2661 def gitindex(text):
2664 def gitindex(text):
2662 if not text:
2665 if not text:
2663 text = ""
2666 text = ""
2664 l = len(text)
2667 l = len(text)
2665 s = hashlib.sha1('blob %d\0' % l)
2668 s = hashlib.sha1('blob %d\0' % l)
2666 s.update(text)
2669 s.update(text)
2667 return hex(s.digest())
2670 return hex(s.digest())
2668
2671
2669 if opts.noprefix:
2672 if opts.noprefix:
2670 aprefix = bprefix = ''
2673 aprefix = bprefix = ''
2671 else:
2674 else:
2672 aprefix = 'a/'
2675 aprefix = 'a/'
2673 bprefix = 'b/'
2676 bprefix = 'b/'
2674
2677
2675 def diffline(f, revs):
2678 def diffline(f, revs):
2676 revinfo = ' '.join(["-r %s" % rev for rev in revs])
2679 revinfo = ' '.join(["-r %s" % rev for rev in revs])
2677 return 'diff %s %s' % (revinfo, f)
2680 return 'diff %s %s' % (revinfo, f)
2678
2681
2679 def isempty(fctx):
2682 def isempty(fctx):
2680 return fctx is None or fctx.size() == 0
2683 return fctx is None or fctx.size() == 0
2681
2684
2682 date1 = dateutil.datestr(ctx1.date())
2685 date1 = dateutil.datestr(ctx1.date())
2683 date2 = dateutil.datestr(ctx2.date())
2686 date2 = dateutil.datestr(ctx2.date())
2684
2687
2685 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
2688 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
2686
2689
2687 if relroot != '' and (repo.ui.configbool('devel', 'all-warnings')
2690 if relroot != '' and (repo.ui.configbool('devel', 'all-warnings')
2688 or repo.ui.configbool('devel', 'check-relroot')):
2691 or repo.ui.configbool('devel', 'check-relroot')):
2689 for f in modified + added + removed + list(copy) + list(copy.values()):
2692 for f in modified + added + removed + list(copy) + list(copy.values()):
2690 if f is not None and not f.startswith(relroot):
2693 if f is not None and not f.startswith(relroot):
2691 raise AssertionError(
2694 raise AssertionError(
2692 "file %s doesn't start with relroot %s" % (f, relroot))
2695 "file %s doesn't start with relroot %s" % (f, relroot))
2693
2696
2694 for f1, f2, copyop in _filepairs(modified, added, removed, copy, opts):
2697 for f1, f2, copyop in _filepairs(modified, added, removed, copy, opts):
2695 content1 = None
2698 content1 = None
2696 content2 = None
2699 content2 = None
2697 fctx1 = None
2700 fctx1 = None
2698 fctx2 = None
2701 fctx2 = None
2699 flag1 = None
2702 flag1 = None
2700 flag2 = None
2703 flag2 = None
2701 if f1:
2704 if f1:
2702 fctx1 = getfilectx(f1, ctx1)
2705 fctx1 = getfilectx(f1, ctx1)
2703 if opts.git or losedatafn:
2706 if opts.git or losedatafn:
2704 flag1 = ctx1.flags(f1)
2707 flag1 = ctx1.flags(f1)
2705 if f2:
2708 if f2:
2706 fctx2 = getfilectx(f2, ctx2)
2709 fctx2 = getfilectx(f2, ctx2)
2707 if opts.git or losedatafn:
2710 if opts.git or losedatafn:
2708 flag2 = ctx2.flags(f2)
2711 flag2 = ctx2.flags(f2)
2709 # if binary is True, output "summary" or "base85", but not "text diff"
2712 # if binary is True, output "summary" or "base85", but not "text diff"
2710 if opts.text:
2713 if opts.text:
2711 binary = False
2714 binary = False
2712 else:
2715 else:
2713 binary = any(f.isbinary() for f in [fctx1, fctx2] if f is not None)
2716 binary = any(f.isbinary() for f in [fctx1, fctx2] if f is not None)
2714
2717
2715 if losedatafn and not opts.git:
2718 if losedatafn and not opts.git:
2716 if (binary or
2719 if (binary or
2717 # copy/rename
2720 # copy/rename
2718 f2 in copy or
2721 f2 in copy or
2719 # empty file creation
2722 # empty file creation
2720 (not f1 and isempty(fctx2)) or
2723 (not f1 and isempty(fctx2)) or
2721 # empty file deletion
2724 # empty file deletion
2722 (isempty(fctx1) and not f2) or
2725 (isempty(fctx1) and not f2) or
2723 # create with flags
2726 # create with flags
2724 (not f1 and flag2) or
2727 (not f1 and flag2) or
2725 # change flags
2728 # change flags
2726 (f1 and f2 and flag1 != flag2)):
2729 (f1 and f2 and flag1 != flag2)):
2727 losedatafn(f2 or f1)
2730 losedatafn(f2 or f1)
2728
2731
2729 path1 = f1 or f2
2732 path1 = f1 or f2
2730 path2 = f2 or f1
2733 path2 = f2 or f1
2731 path1 = posixpath.join(prefix, path1[len(relroot):])
2734 path1 = posixpath.join(prefix, path1[len(relroot):])
2732 path2 = posixpath.join(prefix, path2[len(relroot):])
2735 path2 = posixpath.join(prefix, path2[len(relroot):])
2733 header = []
2736 header = []
2734 if opts.git:
2737 if opts.git:
2735 header.append('diff --git %s%s %s%s' %
2738 header.append('diff --git %s%s %s%s' %
2736 (aprefix, path1, bprefix, path2))
2739 (aprefix, path1, bprefix, path2))
2737 if not f1: # added
2740 if not f1: # added
2738 header.append('new file mode %s' % gitmode[flag2])
2741 header.append('new file mode %s' % gitmode[flag2])
2739 elif not f2: # removed
2742 elif not f2: # removed
2740 header.append('deleted file mode %s' % gitmode[flag1])
2743 header.append('deleted file mode %s' % gitmode[flag1])
2741 else: # modified/copied/renamed
2744 else: # modified/copied/renamed
2742 mode1, mode2 = gitmode[flag1], gitmode[flag2]
2745 mode1, mode2 = gitmode[flag1], gitmode[flag2]
2743 if mode1 != mode2:
2746 if mode1 != mode2:
2744 header.append('old mode %s' % mode1)
2747 header.append('old mode %s' % mode1)
2745 header.append('new mode %s' % mode2)
2748 header.append('new mode %s' % mode2)
2746 if copyop is not None:
2749 if copyop is not None:
2747 if opts.showsimilarity:
2750 if opts.showsimilarity:
2748 sim = similar.score(ctx1[path1], ctx2[path2]) * 100
2751 sim = similar.score(ctx1[path1], ctx2[path2]) * 100
2749 header.append('similarity index %d%%' % sim)
2752 header.append('similarity index %d%%' % sim)
2750 header.append('%s from %s' % (copyop, path1))
2753 header.append('%s from %s' % (copyop, path1))
2751 header.append('%s to %s' % (copyop, path2))
2754 header.append('%s to %s' % (copyop, path2))
2752 elif revs and not repo.ui.quiet:
2755 elif revs and not repo.ui.quiet:
2753 header.append(diffline(path1, revs))
2756 header.append(diffline(path1, revs))
2754
2757
2755 # fctx.is | diffopts | what to | is fctx.data()
2758 # fctx.is | diffopts | what to | is fctx.data()
2756 # binary() | text nobinary git index | output? | outputted?
2759 # binary() | text nobinary git index | output? | outputted?
2757 # ------------------------------------|----------------------------
2760 # ------------------------------------|----------------------------
2758 # yes | no no no * | summary | no
2761 # yes | no no no * | summary | no
2759 # yes | no no yes * | base85 | yes
2762 # yes | no no yes * | base85 | yes
2760 # yes | no yes no * | summary | no
2763 # yes | no yes no * | summary | no
2761 # yes | no yes yes 0 | summary | no
2764 # yes | no yes yes 0 | summary | no
2762 # yes | no yes yes >0 | summary | semi [1]
2765 # yes | no yes yes >0 | summary | semi [1]
2763 # yes | yes * * * | text diff | yes
2766 # yes | yes * * * | text diff | yes
2764 # no | * * * * | text diff | yes
2767 # no | * * * * | text diff | yes
2765 # [1]: hash(fctx.data()) is outputted. so fctx.data() cannot be faked
2768 # [1]: hash(fctx.data()) is outputted. so fctx.data() cannot be faked
2766 if binary and (not opts.git or (opts.git and opts.nobinary and not
2769 if binary and (not opts.git or (opts.git and opts.nobinary and not
2767 opts.index)):
2770 opts.index)):
2768 # fast path: no binary content will be displayed, content1 and
2771 # fast path: no binary content will be displayed, content1 and
2769 # content2 are only used for equivalent test. cmp() could have a
2772 # content2 are only used for equivalent test. cmp() could have a
2770 # fast path.
2773 # fast path.
2771 if fctx1 is not None:
2774 if fctx1 is not None:
2772 content1 = b'\0'
2775 content1 = b'\0'
2773 if fctx2 is not None:
2776 if fctx2 is not None:
2774 if fctx1 is not None and not fctx1.cmp(fctx2):
2777 if fctx1 is not None and not fctx1.cmp(fctx2):
2775 content2 = b'\0' # not different
2778 content2 = b'\0' # not different
2776 else:
2779 else:
2777 content2 = b'\0\0'
2780 content2 = b'\0\0'
2778 else:
2781 else:
2779 # normal path: load contents
2782 # normal path: load contents
2780 if fctx1 is not None:
2783 if fctx1 is not None:
2781 content1 = fctx1.data()
2784 content1 = fctx1.data()
2782 if fctx2 is not None:
2785 if fctx2 is not None:
2783 content2 = fctx2.data()
2786 content2 = fctx2.data()
2784
2787
2785 if binary and opts.git and not opts.nobinary:
2788 if binary and opts.git and not opts.nobinary:
2786 text = mdiff.b85diff(content1, content2)
2789 text = mdiff.b85diff(content1, content2)
2787 if text:
2790 if text:
2788 header.append('index %s..%s' %
2791 header.append('index %s..%s' %
2789 (gitindex(content1), gitindex(content2)))
2792 (gitindex(content1), gitindex(content2)))
2790 hunks = (None, [text]),
2793 hunks = (None, [text]),
2791 else:
2794 else:
2792 if opts.git and opts.index > 0:
2795 if opts.git and opts.index > 0:
2793 flag = flag1
2796 flag = flag1
2794 if flag is None:
2797 if flag is None:
2795 flag = flag2
2798 flag = flag2
2796 header.append('index %s..%s %s' %
2799 header.append('index %s..%s %s' %
2797 (gitindex(content1)[0:opts.index],
2800 (gitindex(content1)[0:opts.index],
2798 gitindex(content2)[0:opts.index],
2801 gitindex(content2)[0:opts.index],
2799 gitmode[flag]))
2802 gitmode[flag]))
2800
2803
2801 uheaders, hunks = mdiff.unidiff(content1, date1,
2804 uheaders, hunks = mdiff.unidiff(content1, date1,
2802 content2, date2,
2805 content2, date2,
2803 path1, path2,
2806 path1, path2,
2804 binary=binary, opts=opts)
2807 binary=binary, opts=opts)
2805 header.extend(uheaders)
2808 header.extend(uheaders)
2806 yield fctx1, fctx2, header, hunks
2809 yield fctx1, fctx2, header, hunks
2807
2810
2808 def diffstatsum(stats):
2811 def diffstatsum(stats):
2809 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
2812 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
2810 for f, a, r, b in stats:
2813 for f, a, r, b in stats:
2811 maxfile = max(maxfile, encoding.colwidth(f))
2814 maxfile = max(maxfile, encoding.colwidth(f))
2812 maxtotal = max(maxtotal, a + r)
2815 maxtotal = max(maxtotal, a + r)
2813 addtotal += a
2816 addtotal += a
2814 removetotal += r
2817 removetotal += r
2815 binary = binary or b
2818 binary = binary or b
2816
2819
2817 return maxfile, maxtotal, addtotal, removetotal, binary
2820 return maxfile, maxtotal, addtotal, removetotal, binary
2818
2821
2819 def diffstatdata(lines):
2822 def diffstatdata(lines):
2820 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
2823 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
2821
2824
2822 results = []
2825 results = []
2823 filename, adds, removes, isbinary = None, 0, 0, False
2826 filename, adds, removes, isbinary = None, 0, 0, False
2824
2827
2825 def addresult():
2828 def addresult():
2826 if filename:
2829 if filename:
2827 results.append((filename, adds, removes, isbinary))
2830 results.append((filename, adds, removes, isbinary))
2828
2831
2829 # inheader is used to track if a line is in the
2832 # inheader is used to track if a line is in the
2830 # header portion of the diff. This helps properly account
2833 # header portion of the diff. This helps properly account
2831 # for lines that start with '--' or '++'
2834 # for lines that start with '--' or '++'
2832 inheader = False
2835 inheader = False
2833
2836
2834 for line in lines:
2837 for line in lines:
2835 if line.startswith('diff'):
2838 if line.startswith('diff'):
2836 addresult()
2839 addresult()
2837 # starting a new file diff
2840 # starting a new file diff
2838 # set numbers to 0 and reset inheader
2841 # set numbers to 0 and reset inheader
2839 inheader = True
2842 inheader = True
2840 adds, removes, isbinary = 0, 0, False
2843 adds, removes, isbinary = 0, 0, False
2841 if line.startswith('diff --git a/'):
2844 if line.startswith('diff --git a/'):
2842 filename = gitre.search(line).group(2)
2845 filename = gitre.search(line).group(2)
2843 elif line.startswith('diff -r'):
2846 elif line.startswith('diff -r'):
2844 # format: "diff -r ... -r ... filename"
2847 # format: "diff -r ... -r ... filename"
2845 filename = diffre.search(line).group(1)
2848 filename = diffre.search(line).group(1)
2846 elif line.startswith('@@'):
2849 elif line.startswith('@@'):
2847 inheader = False
2850 inheader = False
2848 elif line.startswith('+') and not inheader:
2851 elif line.startswith('+') and not inheader:
2849 adds += 1
2852 adds += 1
2850 elif line.startswith('-') and not inheader:
2853 elif line.startswith('-') and not inheader:
2851 removes += 1
2854 removes += 1
2852 elif (line.startswith('GIT binary patch') or
2855 elif (line.startswith('GIT binary patch') or
2853 line.startswith('Binary file')):
2856 line.startswith('Binary file')):
2854 isbinary = True
2857 isbinary = True
2855 addresult()
2858 addresult()
2856 return results
2859 return results
2857
2860
2858 def diffstat(lines, width=80):
2861 def diffstat(lines, width=80):
2859 output = []
2862 output = []
2860 stats = diffstatdata(lines)
2863 stats = diffstatdata(lines)
2861 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
2864 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
2862
2865
2863 countwidth = len(str(maxtotal))
2866 countwidth = len(str(maxtotal))
2864 if hasbinary and countwidth < 3:
2867 if hasbinary and countwidth < 3:
2865 countwidth = 3
2868 countwidth = 3
2866 graphwidth = width - countwidth - maxname - 6
2869 graphwidth = width - countwidth - maxname - 6
2867 if graphwidth < 10:
2870 if graphwidth < 10:
2868 graphwidth = 10
2871 graphwidth = 10
2869
2872
2870 def scale(i):
2873 def scale(i):
2871 if maxtotal <= graphwidth:
2874 if maxtotal <= graphwidth:
2872 return i
2875 return i
2873 # If diffstat runs out of room it doesn't print anything,
2876 # If diffstat runs out of room it doesn't print anything,
2874 # which isn't very useful, so always print at least one + or -
2877 # which isn't very useful, so always print at least one + or -
2875 # if there were at least some changes.
2878 # if there were at least some changes.
2876 return max(i * graphwidth // maxtotal, int(bool(i)))
2879 return max(i * graphwidth // maxtotal, int(bool(i)))
2877
2880
2878 for filename, adds, removes, isbinary in stats:
2881 for filename, adds, removes, isbinary in stats:
2879 if isbinary:
2882 if isbinary:
2880 count = 'Bin'
2883 count = 'Bin'
2881 else:
2884 else:
2882 count = '%d' % (adds + removes)
2885 count = '%d' % (adds + removes)
2883 pluses = '+' * scale(adds)
2886 pluses = '+' * scale(adds)
2884 minuses = '-' * scale(removes)
2887 minuses = '-' * scale(removes)
2885 output.append(' %s%s | %*s %s%s\n' %
2888 output.append(' %s%s | %*s %s%s\n' %
2886 (filename, ' ' * (maxname - encoding.colwidth(filename)),
2889 (filename, ' ' * (maxname - encoding.colwidth(filename)),
2887 countwidth, count, pluses, minuses))
2890 countwidth, count, pluses, minuses))
2888
2891
2889 if stats:
2892 if stats:
2890 output.append(_(' %d files changed, %d insertions(+), '
2893 output.append(_(' %d files changed, %d insertions(+), '
2891 '%d deletions(-)\n')
2894 '%d deletions(-)\n')
2892 % (len(stats), totaladds, totalremoves))
2895 % (len(stats), totaladds, totalremoves))
2893
2896
2894 return ''.join(output)
2897 return ''.join(output)
2895
2898
2896 def diffstatui(*args, **kw):
2899 def diffstatui(*args, **kw):
2897 '''like diffstat(), but yields 2-tuples of (output, label) for
2900 '''like diffstat(), but yields 2-tuples of (output, label) for
2898 ui.write()
2901 ui.write()
2899 '''
2902 '''
2900
2903
2901 for line in diffstat(*args, **kw).splitlines():
2904 for line in diffstat(*args, **kw).splitlines():
2902 if line and line[-1] in '+-':
2905 if line and line[-1] in '+-':
2903 name, graph = line.rsplit(' ', 1)
2906 name, graph = line.rsplit(' ', 1)
2904 yield (name + ' ', '')
2907 yield (name + ' ', '')
2905 m = re.search(br'\++', graph)
2908 m = re.search(br'\++', graph)
2906 if m:
2909 if m:
2907 yield (m.group(0), 'diffstat.inserted')
2910 yield (m.group(0), 'diffstat.inserted')
2908 m = re.search(br'-+', graph)
2911 m = re.search(br'-+', graph)
2909 if m:
2912 if m:
2910 yield (m.group(0), 'diffstat.deleted')
2913 yield (m.group(0), 'diffstat.deleted')
2911 else:
2914 else:
2912 yield (line, '')
2915 yield (line, '')
2913 yield ('\n', '')
2916 yield ('\n', '')
@@ -1,1919 +1,1942 b''
1 $ hg init a
1 $ hg init a
2 $ mkdir a/d1
2 $ mkdir a/d1
3 $ mkdir a/d1/d2
3 $ mkdir a/d1/d2
4 $ echo line 1 > a/a
4 $ echo line 1 > a/a
5 $ echo line 1 > a/d1/d2/a
5 $ echo line 1 > a/d1/d2/a
6 $ hg --cwd a ci -Ama
6 $ hg --cwd a ci -Ama
7 adding a
7 adding a
8 adding d1/d2/a
8 adding d1/d2/a
9
9
10 $ echo line 2 >> a/a
10 $ echo line 2 >> a/a
11 $ hg --cwd a ci -u someone -d '1 0' -m'second change'
11 $ hg --cwd a ci -u someone -d '1 0' -m'second change'
12
12
13 import with no args:
13 import with no args:
14
14
15 $ hg --cwd a import
15 $ hg --cwd a import
16 abort: need at least one patch to import
16 abort: need at least one patch to import
17 [255]
17 [255]
18
18
19 generate patches for the test
19 generate patches for the test
20
20
21 $ hg --cwd a export tip > exported-tip.patch
21 $ hg --cwd a export tip > exported-tip.patch
22 $ hg --cwd a diff -r0:1 > diffed-tip.patch
22 $ hg --cwd a diff -r0:1 > diffed-tip.patch
23
23
24
24
25 import exported patch
25 import exported patch
26 (this also tests that editor is not invoked, if the patch contains the
26 (this also tests that editor is not invoked, if the patch contains the
27 commit message and '--edit' is not specified)
27 commit message and '--edit' is not specified)
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 new changesets 80971e65b431
34 new changesets 80971e65b431
35 updating to branch default
35 updating to branch default
36 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
37 $ HGEDITOR=cat hg --cwd b import ../exported-tip.patch
37 $ HGEDITOR=cat hg --cwd b import ../exported-tip.patch
38 applying ../exported-tip.patch
38 applying ../exported-tip.patch
39
39
40 message and committer and date should be same
40 message and committer and date should be same
41
41
42 $ hg --cwd b tip
42 $ hg --cwd b tip
43 changeset: 1:1d4bd90af0e4
43 changeset: 1:1d4bd90af0e4
44 tag: tip
44 tag: tip
45 user: someone
45 user: someone
46 date: Thu Jan 01 00:00:01 1970 +0000
46 date: Thu Jan 01 00:00:01 1970 +0000
47 summary: second change
47 summary: second change
48
48
49 $ rm -r b
49 $ rm -r b
50
50
51
51
52 import exported patch with external patcher
52 import exported patch with external patcher
53 (this also tests that editor is invoked, if the '--edit' is specified,
53 (this also tests that editor is invoked, if the '--edit' is specified,
54 regardless of the commit message in the patch)
54 regardless of the commit message in the patch)
55
55
56 $ cat > dummypatch.py <<EOF
56 $ cat > dummypatch.py <<EOF
57 > from __future__ import print_function
57 > from __future__ import print_function
58 > print('patching file a')
58 > print('patching file a')
59 > open('a', 'wb').write(b'line2\n')
59 > open('a', 'wb').write(b'line2\n')
60 > EOF
60 > EOF
61 $ hg clone -r0 a b
61 $ hg clone -r0 a b
62 adding changesets
62 adding changesets
63 adding manifests
63 adding manifests
64 adding file changes
64 adding file changes
65 added 1 changesets with 2 changes to 2 files
65 added 1 changesets with 2 changes to 2 files
66 new changesets 80971e65b431
66 new changesets 80971e65b431
67 updating to branch default
67 updating to branch default
68 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
68 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
69 $ HGEDITOR=cat hg --config ui.patch="$PYTHON ../dummypatch.py" --cwd b import --edit ../exported-tip.patch
69 $ HGEDITOR=cat hg --config ui.patch="$PYTHON ../dummypatch.py" --cwd b import --edit ../exported-tip.patch
70 applying ../exported-tip.patch
70 applying ../exported-tip.patch
71 second change
71 second change
72
72
73
73
74 HG: Enter commit message. Lines beginning with 'HG:' are removed.
74 HG: Enter commit message. Lines beginning with 'HG:' are removed.
75 HG: Leave message empty to abort commit.
75 HG: Leave message empty to abort commit.
76 HG: --
76 HG: --
77 HG: user: someone
77 HG: user: someone
78 HG: branch 'default'
78 HG: branch 'default'
79 HG: changed a
79 HG: changed a
80 $ cat b/a
80 $ cat b/a
81 line2
81 line2
82 $ rm -r b
82 $ rm -r b
83
83
84
84
85 import of plain diff should fail without message
85 import of plain diff should fail without message
86 (this also tests that editor is invoked, if the patch doesn't contain
86 (this also tests that editor is invoked, if the patch doesn't contain
87 the commit message, regardless of '--edit')
87 the commit message, regardless of '--edit')
88
88
89 $ hg clone -r0 a b
89 $ hg clone -r0 a b
90 adding changesets
90 adding changesets
91 adding manifests
91 adding manifests
92 adding file changes
92 adding file changes
93 added 1 changesets with 2 changes to 2 files
93 added 1 changesets with 2 changes to 2 files
94 new changesets 80971e65b431
94 new changesets 80971e65b431
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 $ cat > $TESTTMP/editor.sh <<EOF
97 $ cat > $TESTTMP/editor.sh <<EOF
98 > env | grep HGEDITFORM
98 > env | grep HGEDITFORM
99 > cat \$1
99 > cat \$1
100 > EOF
100 > EOF
101 $ HGEDITOR="sh $TESTTMP/editor.sh" hg --cwd b import ../diffed-tip.patch
101 $ HGEDITOR="sh $TESTTMP/editor.sh" hg --cwd b import ../diffed-tip.patch
102 applying ../diffed-tip.patch
102 applying ../diffed-tip.patch
103 HGEDITFORM=import.normal.normal
103 HGEDITFORM=import.normal.normal
104
104
105
105
106 HG: Enter commit message. Lines beginning with 'HG:' are removed.
106 HG: Enter commit message. Lines beginning with 'HG:' are removed.
107 HG: Leave message empty to abort commit.
107 HG: Leave message empty to abort commit.
108 HG: --
108 HG: --
109 HG: user: test
109 HG: user: test
110 HG: branch 'default'
110 HG: branch 'default'
111 HG: changed a
111 HG: changed a
112 abort: empty commit message
112 abort: empty commit message
113 [255]
113 [255]
114
114
115 Test avoiding editor invocation at applying the patch with --exact,
115 Test avoiding editor invocation at applying the patch with --exact,
116 even if commit message is empty
116 even if commit message is empty
117
117
118 $ echo a >> b/a
118 $ echo a >> b/a
119 $ hg --cwd b commit -m ' '
119 $ hg --cwd b commit -m ' '
120 $ hg --cwd b tip -T "{node}\n"
120 $ hg --cwd b tip -T "{node}\n"
121 d8804f3f5396d800812f579c8452796a5993bdb2
121 d8804f3f5396d800812f579c8452796a5993bdb2
122 $ hg --cwd b export -o ../empty-log.diff .
122 $ hg --cwd b export -o ../empty-log.diff .
123 $ hg --cwd b update -q -C ".^1"
123 $ hg --cwd b update -q -C ".^1"
124 $ hg --cwd b --config extensions.strip= strip -q tip
124 $ hg --cwd b --config extensions.strip= strip -q tip
125 $ HGEDITOR=cat hg --cwd b import --exact ../empty-log.diff
125 $ HGEDITOR=cat hg --cwd b import --exact ../empty-log.diff
126 applying ../empty-log.diff
126 applying ../empty-log.diff
127 $ hg --cwd b tip -T "{node}\n"
127 $ hg --cwd b tip -T "{node}\n"
128 d8804f3f5396d800812f579c8452796a5993bdb2
128 d8804f3f5396d800812f579c8452796a5993bdb2
129
129
130 $ rm -r b
130 $ rm -r b
131
131
132
132
133 import of plain diff should be ok with message
133 import of plain diff should be ok with message
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 new changesets 80971e65b431
140 new changesets 80971e65b431
141 updating to branch default
141 updating to branch default
142 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
142 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
143 $ hg --cwd b import -mpatch ../diffed-tip.patch
143 $ hg --cwd b import -mpatch ../diffed-tip.patch
144 applying ../diffed-tip.patch
144 applying ../diffed-tip.patch
145 $ rm -r b
145 $ rm -r b
146
146
147
147
148 import of plain diff with specific date and user
148 import of plain diff with specific date and user
149 (this also tests that editor is not invoked, if
149 (this also tests that editor is not invoked, if
150 '--message'/'--logfile' is specified and '--edit' is not)
150 '--message'/'--logfile' is specified and '--edit' is not)
151
151
152 $ hg clone -r0 a b
152 $ hg clone -r0 a b
153 adding changesets
153 adding changesets
154 adding manifests
154 adding manifests
155 adding file changes
155 adding file changes
156 added 1 changesets with 2 changes to 2 files
156 added 1 changesets with 2 changes to 2 files
157 new changesets 80971e65b431
157 new changesets 80971e65b431
158 updating to branch default
158 updating to branch default
159 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
159 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
160 $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch
160 $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch
161 applying ../diffed-tip.patch
161 applying ../diffed-tip.patch
162 $ hg -R b tip -pv
162 $ hg -R b tip -pv
163 changeset: 1:ca68f19f3a40
163 changeset: 1:ca68f19f3a40
164 tag: tip
164 tag: tip
165 user: user@nowhere.net
165 user: user@nowhere.net
166 date: Thu Jan 01 00:00:01 1970 +0000
166 date: Thu Jan 01 00:00:01 1970 +0000
167 files: a
167 files: a
168 description:
168 description:
169 patch
169 patch
170
170
171
171
172 diff -r 80971e65b431 -r ca68f19f3a40 a
172 diff -r 80971e65b431 -r ca68f19f3a40 a
173 --- a/a Thu Jan 01 00:00:00 1970 +0000
173 --- a/a Thu Jan 01 00:00:00 1970 +0000
174 +++ b/a Thu Jan 01 00:00:01 1970 +0000
174 +++ b/a Thu Jan 01 00:00:01 1970 +0000
175 @@ -1,1 +1,2 @@
175 @@ -1,1 +1,2 @@
176 line 1
176 line 1
177 +line 2
177 +line 2
178
178
179 $ rm -r b
179 $ rm -r b
180
180
181
181
182 import of plain diff should be ok with --no-commit
182 import of plain diff should be ok with --no-commit
183 (this also tests that editor is not invoked, if '--no-commit' is
183 (this also tests that editor is not invoked, if '--no-commit' is
184 specified, regardless of '--edit')
184 specified, regardless of '--edit')
185
185
186 $ hg clone -r0 a b
186 $ hg clone -r0 a b
187 adding changesets
187 adding changesets
188 adding manifests
188 adding manifests
189 adding file changes
189 adding file changes
190 added 1 changesets with 2 changes to 2 files
190 added 1 changesets with 2 changes to 2 files
191 new changesets 80971e65b431
191 new changesets 80971e65b431
192 updating to branch default
192 updating to branch default
193 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
193 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
194 $ HGEDITOR=cat hg --cwd b import --no-commit --edit ../diffed-tip.patch
194 $ HGEDITOR=cat hg --cwd b import --no-commit --edit ../diffed-tip.patch
195 applying ../diffed-tip.patch
195 applying ../diffed-tip.patch
196 $ hg --cwd b diff --nodates
196 $ hg --cwd b diff --nodates
197 diff -r 80971e65b431 a
197 diff -r 80971e65b431 a
198 --- a/a
198 --- a/a
199 +++ b/a
199 +++ b/a
200 @@ -1,1 +1,2 @@
200 @@ -1,1 +1,2 @@
201 line 1
201 line 1
202 +line 2
202 +line 2
203 $ rm -r b
203 $ rm -r b
204
204
205
205
206 import of malformed plain diff should fail
206 import of malformed plain diff should fail
207
207
208 $ hg clone -r0 a b
208 $ hg clone -r0 a b
209 adding changesets
209 adding changesets
210 adding manifests
210 adding manifests
211 adding file changes
211 adding file changes
212 added 1 changesets with 2 changes to 2 files
212 added 1 changesets with 2 changes to 2 files
213 new changesets 80971e65b431
213 new changesets 80971e65b431
214 updating to branch default
214 updating to branch default
215 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
215 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
216 $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch
216 $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch
217 $ hg --cwd b import -mpatch ../broken.patch
217 $ hg --cwd b import -mpatch ../broken.patch
218 applying ../broken.patch
218 applying ../broken.patch
219 abort: bad hunk #1
219 abort: bad hunk #1
220 [255]
220 [255]
221 $ rm -r b
221 $ rm -r b
222
222
223
223
224 hg -R repo import
224 hg -R repo import
225 put the clone in a subdir - having a directory named "a"
225 put the clone in a subdir - having a directory named "a"
226 used to hide a bug.
226 used to hide a bug.
227
227
228 $ mkdir dir
228 $ mkdir dir
229 $ hg clone -r0 a dir/b
229 $ hg clone -r0 a dir/b
230 adding changesets
230 adding changesets
231 adding manifests
231 adding manifests
232 adding file changes
232 adding file changes
233 added 1 changesets with 2 changes to 2 files
233 added 1 changesets with 2 changes to 2 files
234 new changesets 80971e65b431
234 new changesets 80971e65b431
235 updating to branch default
235 updating to branch default
236 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
236 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
237 $ cd dir
237 $ cd dir
238 $ hg -R b import ../exported-tip.patch
238 $ hg -R b import ../exported-tip.patch
239 applying ../exported-tip.patch
239 applying ../exported-tip.patch
240 $ cd ..
240 $ cd ..
241 $ rm -r dir
241 $ rm -r dir
242
242
243
243
244 import from stdin
244 import from stdin
245
245
246 $ hg clone -r0 a b
246 $ hg clone -r0 a b
247 adding changesets
247 adding changesets
248 adding manifests
248 adding manifests
249 adding file changes
249 adding file changes
250 added 1 changesets with 2 changes to 2 files
250 added 1 changesets with 2 changes to 2 files
251 new changesets 80971e65b431
251 new changesets 80971e65b431
252 updating to branch default
252 updating to branch default
253 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
253 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
254 $ hg --cwd b import - < exported-tip.patch
254 $ hg --cwd b import - < exported-tip.patch
255 applying patch from stdin
255 applying patch from stdin
256 $ rm -r b
256 $ rm -r b
257
257
258
258
259 import two patches in one stream
259 import two patches in one stream
260
260
261 $ hg init b
261 $ hg init b
262 $ hg --cwd a export 0:tip | hg --cwd b import -
262 $ hg --cwd a export 0:tip | hg --cwd b import -
263 applying patch from stdin
263 applying patch from stdin
264 $ hg --cwd a id
264 $ hg --cwd a id
265 1d4bd90af0e4 tip
265 1d4bd90af0e4 tip
266 $ hg --cwd b id
266 $ hg --cwd b id
267 1d4bd90af0e4 tip
267 1d4bd90af0e4 tip
268 $ rm -r b
268 $ rm -r b
269
269
270
270
271 override commit message
271 override commit message
272
272
273 $ hg clone -r0 a b
273 $ hg clone -r0 a b
274 adding changesets
274 adding changesets
275 adding manifests
275 adding manifests
276 adding file changes
276 adding file changes
277 added 1 changesets with 2 changes to 2 files
277 added 1 changesets with 2 changes to 2 files
278 new changesets 80971e65b431
278 new changesets 80971e65b431
279 updating to branch default
279 updating to branch default
280 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
280 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
281 $ hg --cwd b import -m 'override' - < exported-tip.patch
281 $ hg --cwd b import -m 'override' - < exported-tip.patch
282 applying patch from stdin
282 applying patch from stdin
283 $ hg --cwd b tip | grep override
283 $ hg --cwd b tip | grep override
284 summary: override
284 summary: override
285 $ rm -r b
285 $ rm -r b
286
286
287 $ cat > mkmsg.py <<EOF
287 $ cat > mkmsg.py <<EOF
288 > import email.message, sys
288 > import email.message, sys
289 > msg = email.message.Message()
289 > msg = email.message.Message()
290 > patch = open(sys.argv[1], 'rb').read()
290 > patch = open(sys.argv[1], 'rb').read()
291 > msg.set_payload(b'email commit message\n' + patch)
291 > msg.set_payload(b'email commit message\n' + patch)
292 > msg['Subject'] = 'email patch'
292 > msg['Subject'] = 'email patch'
293 > msg['From'] = 'email patcher'
293 > msg['From'] = 'email patcher'
294 > open(sys.argv[2], 'wb').write(bytes(msg))
294 > open(sys.argv[2], 'wb').write(bytes(msg))
295 > EOF
295 > EOF
296
296
297
297
298 plain diff in email, subject, message body
298 plain diff in email, subject, message body
299
299
300 $ hg clone -r0 a b
300 $ hg clone -r0 a b
301 adding changesets
301 adding changesets
302 adding manifests
302 adding manifests
303 adding file changes
303 adding file changes
304 added 1 changesets with 2 changes to 2 files
304 added 1 changesets with 2 changes to 2 files
305 new changesets 80971e65b431
305 new changesets 80971e65b431
306 updating to branch default
306 updating to branch default
307 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
307 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
308 $ $PYTHON mkmsg.py diffed-tip.patch msg.patch
308 $ $PYTHON mkmsg.py diffed-tip.patch msg.patch
309 $ hg --cwd b import ../msg.patch
309 $ hg --cwd b import ../msg.patch
310 applying ../msg.patch
310 applying ../msg.patch
311 $ hg --cwd b tip | grep email
311 $ hg --cwd b tip | grep email
312 user: email patcher
312 user: email patcher
313 summary: email patch
313 summary: email patch
314 $ rm -r b
314 $ rm -r b
315
315
316
316
317 plain diff in email, no subject, message body
317 plain diff in email, no subject, message body
318
318
319 $ hg clone -r0 a b
319 $ hg clone -r0 a b
320 adding changesets
320 adding changesets
321 adding manifests
321 adding manifests
322 adding file changes
322 adding file changes
323 added 1 changesets with 2 changes to 2 files
323 added 1 changesets with 2 changes to 2 files
324 new changesets 80971e65b431
324 new changesets 80971e65b431
325 updating to branch default
325 updating to branch default
326 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
326 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
327 $ grep -v '^Subject:' msg.patch | hg --cwd b import -
327 $ grep -v '^Subject:' msg.patch | hg --cwd b import -
328 applying patch from stdin
328 applying patch from stdin
329 $ rm -r b
329 $ rm -r b
330
330
331
331
332 plain diff in email, subject, no message body
332 plain diff in email, subject, no message body
333
333
334 $ hg clone -r0 a b
334 $ hg clone -r0 a b
335 adding changesets
335 adding changesets
336 adding manifests
336 adding manifests
337 adding file changes
337 adding file changes
338 added 1 changesets with 2 changes to 2 files
338 added 1 changesets with 2 changes to 2 files
339 new changesets 80971e65b431
339 new changesets 80971e65b431
340 updating to branch default
340 updating to branch default
341 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
341 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
342 $ grep -v '^email ' msg.patch | hg --cwd b import -
342 $ grep -v '^email ' msg.patch | hg --cwd b import -
343 applying patch from stdin
343 applying patch from stdin
344 $ rm -r b
344 $ rm -r b
345
345
346
346
347 plain diff in email, no subject, no message body, should fail
347 plain diff in email, no subject, no message body, should fail
348
348
349 $ hg clone -r0 a b
349 $ hg clone -r0 a b
350 adding changesets
350 adding changesets
351 adding manifests
351 adding manifests
352 adding file changes
352 adding file changes
353 added 1 changesets with 2 changes to 2 files
353 added 1 changesets with 2 changes to 2 files
354 new changesets 80971e65b431
354 new changesets 80971e65b431
355 updating to branch default
355 updating to branch default
356 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
356 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
357 $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
357 $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
358 applying patch from stdin
358 applying patch from stdin
359 abort: empty commit message
359 abort: empty commit message
360 [255]
360 [255]
361 $ rm -r b
361 $ rm -r b
362
362
363
363
364 hg export in email, should use patch header
364 hg export in email, should use patch header
365
365
366 $ hg clone -r0 a b
366 $ hg clone -r0 a b
367 adding changesets
367 adding changesets
368 adding manifests
368 adding manifests
369 adding file changes
369 adding file changes
370 added 1 changesets with 2 changes to 2 files
370 added 1 changesets with 2 changes to 2 files
371 new changesets 80971e65b431
371 new changesets 80971e65b431
372 updating to branch default
372 updating to branch default
373 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
373 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
374 $ $PYTHON mkmsg.py exported-tip.patch msg.patch
374 $ $PYTHON mkmsg.py exported-tip.patch msg.patch
375 $ cat msg.patch | hg --cwd b import -
375 $ cat msg.patch | hg --cwd b import -
376 applying patch from stdin
376 applying patch from stdin
377 $ hg --cwd b tip | grep second
377 $ hg --cwd b tip | grep second
378 summary: second change
378 summary: second change
379 $ rm -r b
379 $ rm -r b
380
380
381
381
382 subject: duplicate detection, removal of [PATCH]
382 subject: duplicate detection, removal of [PATCH]
383 The '---' tests the gitsendmail handling without proper mail headers
383 The '---' tests the gitsendmail handling without proper mail headers
384
384
385 $ cat > mkmsg2.py <<EOF
385 $ cat > mkmsg2.py <<EOF
386 > import email.message, sys
386 > import email.message, sys
387 > msg = email.message.Message()
387 > msg = email.message.Message()
388 > patch = open(sys.argv[1], 'rb').read()
388 > patch = open(sys.argv[1], 'rb').read()
389 > msg.set_payload(b'email patch\n\nnext line\n---\n' + patch)
389 > msg.set_payload(b'email patch\n\nnext line\n---\n' + patch)
390 > msg['Subject'] = '[PATCH] email patch'
390 > msg['Subject'] = '[PATCH] email patch'
391 > msg['From'] = 'email patcher'
391 > msg['From'] = 'email patcher'
392 > open(sys.argv[2], 'wb').write(bytes(msg))
392 > open(sys.argv[2], 'wb').write(bytes(msg))
393 > EOF
393 > EOF
394
394
395
395
396 plain diff in email, [PATCH] subject, message body with subject
396 plain diff in email, [PATCH] subject, message body with subject
397
397
398 $ hg clone -r0 a b
398 $ hg clone -r0 a b
399 adding changesets
399 adding changesets
400 adding manifests
400 adding manifests
401 adding file changes
401 adding file changes
402 added 1 changesets with 2 changes to 2 files
402 added 1 changesets with 2 changes to 2 files
403 new changesets 80971e65b431
403 new changesets 80971e65b431
404 updating to branch default
404 updating to branch default
405 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
405 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
406 $ $PYTHON mkmsg2.py diffed-tip.patch msg.patch
406 $ $PYTHON mkmsg2.py diffed-tip.patch msg.patch
407 $ cat msg.patch | hg --cwd b import -
407 $ cat msg.patch | hg --cwd b import -
408 applying patch from stdin
408 applying patch from stdin
409 $ hg --cwd b tip --template '{desc}\n'
409 $ hg --cwd b tip --template '{desc}\n'
410 email patch
410 email patch
411
411
412 next line
412 next line
413 $ rm -r b
413 $ rm -r b
414
414
415
415
416 Issue963: Parent of working dir incorrect after import of multiple
416 Issue963: Parent of working dir incorrect after import of multiple
417 patches and rollback
417 patches and rollback
418
418
419 We weren't backing up the correct dirstate file when importing many
419 We weren't backing up the correct dirstate file when importing many
420 patches: import patch1 patch2; rollback
420 patches: import patch1 patch2; rollback
421
421
422 $ echo line 3 >> a/a
422 $ echo line 3 >> a/a
423 $ hg --cwd a ci -m'third change'
423 $ hg --cwd a ci -m'third change'
424 $ hg --cwd a export -o '../patch%R' 1 2
424 $ hg --cwd a export -o '../patch%R' 1 2
425 $ hg clone -qr0 a b
425 $ hg clone -qr0 a b
426 $ hg --cwd b parents --template 'parent: {rev}\n'
426 $ hg --cwd b parents --template 'parent: {rev}\n'
427 parent: 0
427 parent: 0
428 $ hg --cwd b import -v ../patch1 ../patch2
428 $ hg --cwd b import -v ../patch1 ../patch2
429 applying ../patch1
429 applying ../patch1
430 patching file a
430 patching file a
431 committing files:
431 committing files:
432 a
432 a
433 committing manifest
433 committing manifest
434 committing changelog
434 committing changelog
435 created 1d4bd90af0e4
435 created 1d4bd90af0e4
436 applying ../patch2
436 applying ../patch2
437 patching file a
437 patching file a
438 committing files:
438 committing files:
439 a
439 a
440 committing manifest
440 committing manifest
441 committing changelog
441 committing changelog
442 created 6d019af21222
442 created 6d019af21222
443 $ hg --cwd b rollback
443 $ hg --cwd b rollback
444 repository tip rolled back to revision 0 (undo import)
444 repository tip rolled back to revision 0 (undo import)
445 working directory now based on revision 0
445 working directory now based on revision 0
446 $ hg --cwd b parents --template 'parent: {rev}\n'
446 $ hg --cwd b parents --template 'parent: {rev}\n'
447 parent: 0
447 parent: 0
448
448
449 Test that "hg rollback" doesn't restore dirstate to one at the
449 Test that "hg rollback" doesn't restore dirstate to one at the
450 beginning of the rolled back transaction in not-"parent-gone" case.
450 beginning of the rolled back transaction in not-"parent-gone" case.
451
451
452 invoking pretxncommit hook will cause marking '.hg/dirstate' as a file
452 invoking pretxncommit hook will cause marking '.hg/dirstate' as a file
453 to be restored when rolling back, after DirstateTransactionPlan (see wiki
453 to be restored when rolling back, after DirstateTransactionPlan (see wiki
454 page for detail).
454 page for detail).
455
455
456 $ hg --cwd b branch -q foobar
456 $ hg --cwd b branch -q foobar
457 $ hg --cwd b commit -m foobar
457 $ hg --cwd b commit -m foobar
458 $ hg --cwd b update 0 -q
458 $ hg --cwd b update 0 -q
459 $ hg --cwd b import ../patch1 ../patch2 --config hooks.pretxncommit=true
459 $ hg --cwd b import ../patch1 ../patch2 --config hooks.pretxncommit=true
460 applying ../patch1
460 applying ../patch1
461 applying ../patch2
461 applying ../patch2
462 $ hg --cwd b update -q 1
462 $ hg --cwd b update -q 1
463 $ hg --cwd b rollback -q
463 $ hg --cwd b rollback -q
464 $ hg --cwd b parents --template 'parent: {rev}\n'
464 $ hg --cwd b parents --template 'parent: {rev}\n'
465 parent: 1
465 parent: 1
466
466
467 $ hg --cwd b update -q -C 0
467 $ hg --cwd b update -q -C 0
468 $ hg --cwd b --config extensions.strip= strip -q 1
468 $ hg --cwd b --config extensions.strip= strip -q 1
469
469
470 Test visibility of in-memory dirstate changes inside transaction to
470 Test visibility of in-memory dirstate changes inside transaction to
471 external process
471 external process
472
472
473 $ echo foo > a/foo
473 $ echo foo > a/foo
474 $ hg --cwd a commit -A -m 'adding foo' foo
474 $ hg --cwd a commit -A -m 'adding foo' foo
475 $ hg --cwd a export -o '../patch%R' 3
475 $ hg --cwd a export -o '../patch%R' 3
476
476
477 $ cat > $TESTTMP/checkvisibility.sh <<EOF
477 $ cat > $TESTTMP/checkvisibility.sh <<EOF
478 > echo "===="
478 > echo "===="
479 > hg parents --template "VISIBLE {rev}:{node|short}\n"
479 > hg parents --template "VISIBLE {rev}:{node|short}\n"
480 > hg status -amr
480 > hg status -amr
481 > # test that pending changes are hidden
481 > # test that pending changes are hidden
482 > unset HG_PENDING
482 > unset HG_PENDING
483 > hg parents --template "ACTUAL {rev}:{node|short}\n"
483 > hg parents --template "ACTUAL {rev}:{node|short}\n"
484 > hg status -amr
484 > hg status -amr
485 > echo "===="
485 > echo "===="
486 > EOF
486 > EOF
487
487
488 == test visibility to external editor
488 == test visibility to external editor
489
489
490 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
490 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
491 ====
491 ====
492 VISIBLE 0:80971e65b431
492 VISIBLE 0:80971e65b431
493 ACTUAL 0:80971e65b431
493 ACTUAL 0:80971e65b431
494 ====
494 ====
495
495
496 $ HGEDITOR="sh $TESTTMP/checkvisibility.sh" hg --cwd b import -v --edit ../patch1 ../patch2 ../patch3
496 $ HGEDITOR="sh $TESTTMP/checkvisibility.sh" hg --cwd b import -v --edit ../patch1 ../patch2 ../patch3
497 applying ../patch1
497 applying ../patch1
498 patching file a
498 patching file a
499 ====
499 ====
500 VISIBLE 0:80971e65b431
500 VISIBLE 0:80971e65b431
501 M a
501 M a
502 ACTUAL 0:80971e65b431
502 ACTUAL 0:80971e65b431
503 M a
503 M a
504 ====
504 ====
505 committing files:
505 committing files:
506 a
506 a
507 committing manifest
507 committing manifest
508 committing changelog
508 committing changelog
509 created 1d4bd90af0e4
509 created 1d4bd90af0e4
510 applying ../patch2
510 applying ../patch2
511 patching file a
511 patching file a
512 ====
512 ====
513 VISIBLE 1:1d4bd90af0e4
513 VISIBLE 1:1d4bd90af0e4
514 M a
514 M a
515 ACTUAL 0:80971e65b431
515 ACTUAL 0:80971e65b431
516 M a
516 M a
517 ====
517 ====
518 committing files:
518 committing files:
519 a
519 a
520 committing manifest
520 committing manifest
521 committing changelog
521 committing changelog
522 created 6d019af21222
522 created 6d019af21222
523 applying ../patch3
523 applying ../patch3
524 patching file foo
524 patching file foo
525 adding foo
525 adding foo
526 ====
526 ====
527 VISIBLE 2:6d019af21222
527 VISIBLE 2:6d019af21222
528 A foo
528 A foo
529 ACTUAL 0:80971e65b431
529 ACTUAL 0:80971e65b431
530 M a
530 M a
531 ====
531 ====
532 committing files:
532 committing files:
533 foo
533 foo
534 committing manifest
534 committing manifest
535 committing changelog
535 committing changelog
536 created 55e3f75b2378
536 created 55e3f75b2378
537
537
538 $ hg --cwd b rollback -q
538 $ hg --cwd b rollback -q
539
539
540 (content of file "a" is already changed and it should be recognized as
540 (content of file "a" is already changed and it should be recognized as
541 "M", even though dirstate is restored to one before "hg import")
541 "M", even though dirstate is restored to one before "hg import")
542
542
543 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
543 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
544 ====
544 ====
545 VISIBLE 0:80971e65b431
545 VISIBLE 0:80971e65b431
546 M a
546 M a
547 ACTUAL 0:80971e65b431
547 ACTUAL 0:80971e65b431
548 M a
548 M a
549 ====
549 ====
550 $ hg --cwd b revert --no-backup a
550 $ hg --cwd b revert --no-backup a
551 $ rm -f b/foo
551 $ rm -f b/foo
552
552
553 == test visibility to precommit external hook
553 == test visibility to precommit external hook
554
554
555 $ cat >> b/.hg/hgrc <<EOF
555 $ cat >> b/.hg/hgrc <<EOF
556 > [hooks]
556 > [hooks]
557 > precommit.visibility = sh $TESTTMP/checkvisibility.sh
557 > precommit.visibility = sh $TESTTMP/checkvisibility.sh
558 > EOF
558 > EOF
559
559
560 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
560 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
561 ====
561 ====
562 VISIBLE 0:80971e65b431
562 VISIBLE 0:80971e65b431
563 ACTUAL 0:80971e65b431
563 ACTUAL 0:80971e65b431
564 ====
564 ====
565
565
566 $ hg --cwd b import ../patch1 ../patch2 ../patch3
566 $ hg --cwd b import ../patch1 ../patch2 ../patch3
567 applying ../patch1
567 applying ../patch1
568 ====
568 ====
569 VISIBLE 0:80971e65b431
569 VISIBLE 0:80971e65b431
570 M a
570 M a
571 ACTUAL 0:80971e65b431
571 ACTUAL 0:80971e65b431
572 M a
572 M a
573 ====
573 ====
574 applying ../patch2
574 applying ../patch2
575 ====
575 ====
576 VISIBLE 1:1d4bd90af0e4
576 VISIBLE 1:1d4bd90af0e4
577 M a
577 M a
578 ACTUAL 0:80971e65b431
578 ACTUAL 0:80971e65b431
579 M a
579 M a
580 ====
580 ====
581 applying ../patch3
581 applying ../patch3
582 ====
582 ====
583 VISIBLE 2:6d019af21222
583 VISIBLE 2:6d019af21222
584 A foo
584 A foo
585 ACTUAL 0:80971e65b431
585 ACTUAL 0:80971e65b431
586 M a
586 M a
587 ====
587 ====
588
588
589 $ hg --cwd b rollback -q
589 $ hg --cwd b rollback -q
590 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
590 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
591 ====
591 ====
592 VISIBLE 0:80971e65b431
592 VISIBLE 0:80971e65b431
593 M a
593 M a
594 ACTUAL 0:80971e65b431
594 ACTUAL 0:80971e65b431
595 M a
595 M a
596 ====
596 ====
597 $ hg --cwd b revert --no-backup a
597 $ hg --cwd b revert --no-backup a
598 $ rm -f b/foo
598 $ rm -f b/foo
599
599
600 $ cat >> b/.hg/hgrc <<EOF
600 $ cat >> b/.hg/hgrc <<EOF
601 > [hooks]
601 > [hooks]
602 > precommit.visibility =
602 > precommit.visibility =
603 > EOF
603 > EOF
604
604
605 == test visibility to pretxncommit external hook
605 == test visibility to pretxncommit external hook
606
606
607 $ cat >> b/.hg/hgrc <<EOF
607 $ cat >> b/.hg/hgrc <<EOF
608 > [hooks]
608 > [hooks]
609 > pretxncommit.visibility = sh $TESTTMP/checkvisibility.sh
609 > pretxncommit.visibility = sh $TESTTMP/checkvisibility.sh
610 > EOF
610 > EOF
611
611
612 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
612 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
613 ====
613 ====
614 VISIBLE 0:80971e65b431
614 VISIBLE 0:80971e65b431
615 ACTUAL 0:80971e65b431
615 ACTUAL 0:80971e65b431
616 ====
616 ====
617
617
618 $ hg --cwd b import ../patch1 ../patch2 ../patch3
618 $ hg --cwd b import ../patch1 ../patch2 ../patch3
619 applying ../patch1
619 applying ../patch1
620 ====
620 ====
621 VISIBLE 0:80971e65b431
621 VISIBLE 0:80971e65b431
622 M a
622 M a
623 ACTUAL 0:80971e65b431
623 ACTUAL 0:80971e65b431
624 M a
624 M a
625 ====
625 ====
626 applying ../patch2
626 applying ../patch2
627 ====
627 ====
628 VISIBLE 1:1d4bd90af0e4
628 VISIBLE 1:1d4bd90af0e4
629 M a
629 M a
630 ACTUAL 0:80971e65b431
630 ACTUAL 0:80971e65b431
631 M a
631 M a
632 ====
632 ====
633 applying ../patch3
633 applying ../patch3
634 ====
634 ====
635 VISIBLE 2:6d019af21222
635 VISIBLE 2:6d019af21222
636 A foo
636 A foo
637 ACTUAL 0:80971e65b431
637 ACTUAL 0:80971e65b431
638 M a
638 M a
639 ====
639 ====
640
640
641 $ hg --cwd b rollback -q
641 $ hg --cwd b rollback -q
642 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
642 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
643 ====
643 ====
644 VISIBLE 0:80971e65b431
644 VISIBLE 0:80971e65b431
645 M a
645 M a
646 ACTUAL 0:80971e65b431
646 ACTUAL 0:80971e65b431
647 M a
647 M a
648 ====
648 ====
649 $ hg --cwd b revert --no-backup a
649 $ hg --cwd b revert --no-backup a
650 $ rm -f b/foo
650 $ rm -f b/foo
651
651
652 $ cat >> b/.hg/hgrc <<EOF
652 $ cat >> b/.hg/hgrc <<EOF
653 > [hooks]
653 > [hooks]
654 > pretxncommit.visibility =
654 > pretxncommit.visibility =
655 > EOF
655 > EOF
656
656
657 $ rm -r b
657 $ rm -r b
658
658
659
659
660 importing a patch in a subdirectory failed at the commit stage
660 importing a patch in a subdirectory failed at the commit stage
661
661
662 $ echo line 2 >> a/d1/d2/a
662 $ echo line 2 >> a/d1/d2/a
663 $ hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change'
663 $ hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change'
664
664
665 hg import in a subdirectory
665 hg import in a subdirectory
666
666
667 $ hg clone -r0 a b
667 $ hg clone -r0 a b
668 adding changesets
668 adding changesets
669 adding manifests
669 adding manifests
670 adding file changes
670 adding file changes
671 added 1 changesets with 2 changes to 2 files
671 added 1 changesets with 2 changes to 2 files
672 new changesets 80971e65b431
672 new changesets 80971e65b431
673 updating to branch default
673 updating to branch default
674 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
674 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
675 $ hg --cwd a export tip > tmp
675 $ hg --cwd a export tip > tmp
676 $ sed -e 's/d1\/d2\///' < tmp > subdir-tip.patch
676 $ sed -e 's/d1\/d2\///' < tmp > subdir-tip.patch
677 $ dir=`pwd`
677 $ dir=`pwd`
678 $ cd b/d1/d2 2>&1 > /dev/null
678 $ cd b/d1/d2 2>&1 > /dev/null
679 $ hg import ../../../subdir-tip.patch
679 $ hg import ../../../subdir-tip.patch
680 applying ../../../subdir-tip.patch
680 applying ../../../subdir-tip.patch
681 $ cd "$dir"
681 $ cd "$dir"
682
682
683 message should be 'subdir change'
683 message should be 'subdir change'
684 committer should be 'someoneelse'
684 committer should be 'someoneelse'
685
685
686 $ hg --cwd b tip
686 $ hg --cwd b tip
687 changeset: 1:3577f5aea227
687 changeset: 1:3577f5aea227
688 tag: tip
688 tag: tip
689 user: someoneelse
689 user: someoneelse
690 date: Thu Jan 01 00:00:01 1970 +0000
690 date: Thu Jan 01 00:00:01 1970 +0000
691 summary: subdir change
691 summary: subdir change
692
692
693
693
694 should be empty
694 should be empty
695
695
696 $ hg --cwd b status
696 $ hg --cwd b status
697
697
698
698
699 Test fuzziness (ambiguous patch location, fuzz=2)
699 Test fuzziness (ambiguous patch location, fuzz=2)
700
700
701 $ hg init fuzzy
701 $ hg init fuzzy
702 $ cd fuzzy
702 $ cd fuzzy
703 $ echo line1 > a
703 $ echo line1 > a
704 $ echo line0 >> a
704 $ echo line0 >> a
705 $ echo line3 >> a
705 $ echo line3 >> a
706 $ hg ci -Am adda
706 $ hg ci -Am adda
707 adding a
707 adding a
708 $ echo line1 > a
708 $ echo line1 > a
709 $ echo line2 >> a
709 $ echo line2 >> a
710 $ echo line0 >> a
710 $ echo line0 >> a
711 $ echo line3 >> a
711 $ echo line3 >> a
712 $ hg ci -m change a
712 $ hg ci -m change a
713 $ hg export tip > fuzzy-tip.patch
713 $ hg export tip > fuzzy-tip.patch
714 $ hg up -C 0
714 $ hg up -C 0
715 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
715 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
716 $ echo line1 > a
716 $ echo line1 > a
717 $ echo line0 >> a
717 $ echo line0 >> a
718 $ echo line1 >> a
718 $ echo line1 >> a
719 $ echo line0 >> a
719 $ echo line0 >> a
720 $ hg ci -m brancha
720 $ hg ci -m brancha
721 created new head
721 created new head
722 $ hg import --config patch.fuzz=0 -v fuzzy-tip.patch
722 $ hg import --config patch.fuzz=0 -v fuzzy-tip.patch
723 applying fuzzy-tip.patch
723 applying fuzzy-tip.patch
724 patching file a
724 patching file a
725 Hunk #1 FAILED at 0
725 Hunk #1 FAILED at 0
726 1 out of 1 hunks FAILED -- saving rejects to file a.rej
726 1 out of 1 hunks FAILED -- saving rejects to file a.rej
727 abort: patch failed to apply
727 abort: patch failed to apply
728 [255]
728 [255]
729 $ hg import --no-commit -v fuzzy-tip.patch
729 $ hg import --no-commit -v fuzzy-tip.patch
730 applying fuzzy-tip.patch
730 applying fuzzy-tip.patch
731 patching file a
731 patching file a
732 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
732 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
733 applied to working directory
733 applied to working directory
734 $ hg revert -a
734 $ hg revert -a
735 reverting a
735 reverting a
736
736
737
737
738 import with --no-commit should have written .hg/last-message.txt
738 import with --no-commit should have written .hg/last-message.txt
739
739
740 $ cat .hg/last-message.txt
740 $ cat .hg/last-message.txt
741 change (no-eol)
741 change (no-eol)
742
742
743
743
744 test fuzziness with eol=auto
744 test fuzziness with eol=auto
745
745
746 $ hg --config patch.eol=auto import --no-commit -v fuzzy-tip.patch
746 $ hg --config patch.eol=auto import --no-commit -v fuzzy-tip.patch
747 applying fuzzy-tip.patch
747 applying fuzzy-tip.patch
748 patching file a
748 patching file a
749 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
749 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
750 applied to working directory
750 applied to working directory
751 $ cd ..
751 $ cd ..
752
752
753
753
754 Test hunk touching empty files (issue906)
754 Test hunk touching empty files (issue906)
755
755
756 $ hg init empty
756 $ hg init empty
757 $ cd empty
757 $ cd empty
758 $ touch a
758 $ touch a
759 $ touch b1
759 $ touch b1
760 $ touch c1
760 $ touch c1
761 $ echo d > d
761 $ echo d > d
762 $ hg ci -Am init
762 $ hg ci -Am init
763 adding a
763 adding a
764 adding b1
764 adding b1
765 adding c1
765 adding c1
766 adding d
766 adding d
767 $ echo a > a
767 $ echo a > a
768 $ echo b > b1
768 $ echo b > b1
769 $ hg mv b1 b2
769 $ hg mv b1 b2
770 $ echo c > c1
770 $ echo c > c1
771 $ hg copy c1 c2
771 $ hg copy c1 c2
772 $ rm d
772 $ rm d
773 $ touch d
773 $ touch d
774 $ hg diff --git
774 $ hg diff --git
775 diff --git a/a b/a
775 diff --git a/a b/a
776 --- a/a
776 --- a/a
777 +++ b/a
777 +++ b/a
778 @@ -0,0 +1,1 @@
778 @@ -0,0 +1,1 @@
779 +a
779 +a
780 diff --git a/b1 b/b2
780 diff --git a/b1 b/b2
781 rename from b1
781 rename from b1
782 rename to b2
782 rename to b2
783 --- a/b1
783 --- a/b1
784 +++ b/b2
784 +++ b/b2
785 @@ -0,0 +1,1 @@
785 @@ -0,0 +1,1 @@
786 +b
786 +b
787 diff --git a/c1 b/c1
787 diff --git a/c1 b/c1
788 --- a/c1
788 --- a/c1
789 +++ b/c1
789 +++ b/c1
790 @@ -0,0 +1,1 @@
790 @@ -0,0 +1,1 @@
791 +c
791 +c
792 diff --git a/c1 b/c2
792 diff --git a/c1 b/c2
793 copy from c1
793 copy from c1
794 copy to c2
794 copy to c2
795 --- a/c1
795 --- a/c1
796 +++ b/c2
796 +++ b/c2
797 @@ -0,0 +1,1 @@
797 @@ -0,0 +1,1 @@
798 +c
798 +c
799 diff --git a/d b/d
799 diff --git a/d b/d
800 --- a/d
800 --- a/d
801 +++ b/d
801 +++ b/d
802 @@ -1,1 +0,0 @@
802 @@ -1,1 +0,0 @@
803 -d
803 -d
804 $ hg ci -m empty
804 $ hg ci -m empty
805 $ hg export --git tip > empty.diff
805 $ hg export --git tip > empty.diff
806 $ hg up -C 0
806 $ hg up -C 0
807 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
807 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
808 $ hg import empty.diff
808 $ hg import empty.diff
809 applying empty.diff
809 applying empty.diff
810 $ for name in a b1 b2 c1 c2 d; do
810 $ for name in a b1 b2 c1 c2 d; do
811 > echo % $name file
811 > echo % $name file
812 > test -f $name && cat $name
812 > test -f $name && cat $name
813 > done
813 > done
814 % a file
814 % a file
815 a
815 a
816 % b1 file
816 % b1 file
817 % b2 file
817 % b2 file
818 b
818 b
819 % c1 file
819 % c1 file
820 c
820 c
821 % c2 file
821 % c2 file
822 c
822 c
823 % d file
823 % d file
824 $ cd ..
824 $ cd ..
825
825
826
826
827 Test importing a patch ending with a binary file removal
827 Test importing a patch ending with a binary file removal
828
828
829 $ hg init binaryremoval
829 $ hg init binaryremoval
830 $ cd binaryremoval
830 $ cd binaryremoval
831 $ echo a > a
831 $ echo a > a
832 $ $PYTHON -c "open('b', 'wb').write(b'a\x00b')"
832 $ $PYTHON -c "open('b', 'wb').write(b'a\x00b')"
833 $ hg ci -Am addall
833 $ hg ci -Am addall
834 adding a
834 adding a
835 adding b
835 adding b
836 $ hg rm a
836 $ hg rm a
837 $ hg rm b
837 $ hg rm b
838 $ hg st
838 $ hg st
839 R a
839 R a
840 R b
840 R b
841 $ hg ci -m remove
841 $ hg ci -m remove
842 $ hg export --git . > remove.diff
842 $ hg export --git . > remove.diff
843 $ cat remove.diff | grep git
843 $ cat remove.diff | grep git
844 diff --git a/a b/a
844 diff --git a/a b/a
845 diff --git a/b b/b
845 diff --git a/b b/b
846 $ hg up -C 0
846 $ hg up -C 0
847 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
847 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
848 $ hg import remove.diff
848 $ hg import remove.diff
849 applying remove.diff
849 applying remove.diff
850 $ hg manifest
850 $ hg manifest
851 $ cd ..
851 $ cd ..
852
852
853
853
854 Issue927: test update+rename with common name
854 Issue927: test update+rename with common name
855
855
856 $ hg init t
856 $ hg init t
857 $ cd t
857 $ cd t
858 $ touch a
858 $ touch a
859 $ hg ci -Am t
859 $ hg ci -Am t
860 adding a
860 adding a
861 $ echo a > a
861 $ echo a > a
862
862
863 Here, bfile.startswith(afile)
863 Here, bfile.startswith(afile)
864
864
865 $ hg copy a a2
865 $ hg copy a a2
866 $ hg ci -m copya
866 $ hg ci -m copya
867 $ hg export --git tip > copy.diff
867 $ hg export --git tip > copy.diff
868 $ hg up -C 0
868 $ hg up -C 0
869 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
869 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
870 $ hg import copy.diff
870 $ hg import copy.diff
871 applying copy.diff
871 applying copy.diff
872
872
873 a should contain an 'a'
873 a should contain an 'a'
874
874
875 $ cat a
875 $ cat a
876 a
876 a
877
877
878 and a2 should have duplicated it
878 and a2 should have duplicated it
879
879
880 $ cat a2
880 $ cat a2
881 a
881 a
882 $ cd ..
882 $ cd ..
883
883
884
884
885 test -p0
885 test -p0
886
886
887 $ hg init p0
887 $ hg init p0
888 $ cd p0
888 $ cd p0
889 $ echo a > a
889 $ echo a > a
890 $ hg ci -Am t
890 $ hg ci -Am t
891 adding a
891 adding a
892 $ hg import -p foo
892 $ hg import -p foo
893 abort: invalid value 'foo' for option -p, expected int
893 abort: invalid value 'foo' for option -p, expected int
894 [255]
894 [255]
895 $ hg import -p0 - << EOF
895 $ hg import -p0 - << EOF
896 > foobar
896 > foobar
897 > --- a Sat Apr 12 22:43:58 2008 -0400
897 > --- a Sat Apr 12 22:43:58 2008 -0400
898 > +++ a Sat Apr 12 22:44:05 2008 -0400
898 > +++ a Sat Apr 12 22:44:05 2008 -0400
899 > @@ -1,1 +1,1 @@
899 > @@ -1,1 +1,1 @@
900 > -a
900 > -a
901 > +bb
901 > +bb
902 > EOF
902 > EOF
903 applying patch from stdin
903 applying patch from stdin
904 $ hg status
904 $ hg status
905 $ cat a
905 $ cat a
906 bb
906 bb
907
907
908 test --prefix
908 test --prefix
909
909
910 $ mkdir -p dir/dir2
910 $ mkdir -p dir/dir2
911 $ echo b > dir/dir2/b
911 $ echo b > dir/dir2/b
912 $ hg ci -Am b
912 $ hg ci -Am b
913 adding dir/dir2/b
913 adding dir/dir2/b
914 $ hg import -p2 --prefix dir - << EOF
914 $ hg import -p2 --prefix dir - << EOF
915 > foobar
915 > foobar
916 > --- drop1/drop2/dir2/b
916 > --- drop1/drop2/dir2/b
917 > +++ drop1/drop2/dir2/b
917 > +++ drop1/drop2/dir2/b
918 > @@ -1,1 +1,1 @@
918 > @@ -1,1 +1,1 @@
919 > -b
919 > -b
920 > +cc
920 > +cc
921 > EOF
921 > EOF
922 applying patch from stdin
922 applying patch from stdin
923 $ hg status
923 $ hg status
924 $ cat dir/dir2/b
924 $ cat dir/dir2/b
925 cc
925 cc
926 $ cd ..
926 $ cd ..
927
927
928
928
929 test paths outside repo root
929 test paths outside repo root
930
930
931 $ mkdir outside
931 $ mkdir outside
932 $ touch outside/foo
932 $ touch outside/foo
933 $ hg init inside
933 $ hg init inside
934 $ cd inside
934 $ cd inside
935 $ hg import - <<EOF
935 $ hg import - <<EOF
936 > diff --git a/a b/b
936 > diff --git a/a b/b
937 > rename from ../outside/foo
937 > rename from ../outside/foo
938 > rename to bar
938 > rename to bar
939 > EOF
939 > EOF
940 applying patch from stdin
940 applying patch from stdin
941 abort: path contains illegal component: ../outside/foo
941 abort: path contains illegal component: ../outside/foo
942 [255]
942 [255]
943 $ cd ..
943 $ cd ..
944
944
945
945
946 test import with similarity and git and strip (issue295 et al.)
946 test import with similarity and git and strip (issue295 et al.)
947
947
948 $ hg init sim
948 $ hg init sim
949 $ cd sim
949 $ cd sim
950 $ echo 'this is a test' > a
950 $ echo 'this is a test' > a
951 $ hg ci -Ama
951 $ hg ci -Ama
952 adding a
952 adding a
953 $ cat > ../rename.diff <<EOF
953 $ cat > ../rename.diff <<EOF
954 > diff --git a/foo/a b/foo/a
954 > diff --git a/foo/a b/foo/a
955 > deleted file mode 100644
955 > deleted file mode 100644
956 > --- a/foo/a
956 > --- a/foo/a
957 > +++ /dev/null
957 > +++ /dev/null
958 > @@ -1,1 +0,0 @@
958 > @@ -1,1 +0,0 @@
959 > -this is a test
959 > -this is a test
960 > diff --git a/foo/b b/foo/b
960 > diff --git a/foo/b b/foo/b
961 > new file mode 100644
961 > new file mode 100644
962 > --- /dev/null
962 > --- /dev/null
963 > +++ b/foo/b
963 > +++ b/foo/b
964 > @@ -0,0 +1,2 @@
964 > @@ -0,0 +1,2 @@
965 > +this is a test
965 > +this is a test
966 > +foo
966 > +foo
967 > EOF
967 > EOF
968 $ hg import --no-commit -v -s 1 ../rename.diff -p2
968 $ hg import --no-commit -v -s 1 ../rename.diff -p2
969 applying ../rename.diff
969 applying ../rename.diff
970 patching file a
970 patching file a
971 patching file b
971 patching file b
972 adding b
972 adding b
973 recording removal of a as rename to b (88% similar)
973 recording removal of a as rename to b (88% similar)
974 applied to working directory
974 applied to working directory
975 $ echo 'mod b' > b
975 $ echo 'mod b' > b
976 $ hg st -C
976 $ hg st -C
977 A b
977 A b
978 a
978 a
979 R a
979 R a
980 $ hg revert -a
980 $ hg revert -a
981 undeleting a
981 undeleting a
982 forgetting b
982 forgetting b
983 $ cat b
983 $ cat b
984 mod b
984 mod b
985 $ rm b
985 $ rm b
986 $ hg import --no-commit -v -s 100 ../rename.diff -p2
986 $ hg import --no-commit -v -s 100 ../rename.diff -p2
987 applying ../rename.diff
987 applying ../rename.diff
988 patching file a
988 patching file a
989 patching file b
989 patching file b
990 adding b
990 adding b
991 applied to working directory
991 applied to working directory
992 $ hg st -C
992 $ hg st -C
993 A b
993 A b
994 R a
994 R a
995 $ cd ..
995 $ cd ..
996
996
997
997
998 Issue1495: add empty file from the end of patch
998 Issue1495: add empty file from the end of patch
999
999
1000 $ hg init addemptyend
1000 $ hg init addemptyend
1001 $ cd addemptyend
1001 $ cd addemptyend
1002 $ touch a
1002 $ touch a
1003 $ hg addremove
1003 $ hg addremove
1004 adding a
1004 adding a
1005 $ hg ci -m "commit"
1005 $ hg ci -m "commit"
1006 $ cat > a.patch <<EOF
1006 $ cat > a.patch <<EOF
1007 > add a, b
1007 > add a, b
1008 > diff --git a/a b/a
1008 > diff --git a/a b/a
1009 > --- a/a
1009 > --- a/a
1010 > +++ b/a
1010 > +++ b/a
1011 > @@ -0,0 +1,1 @@
1011 > @@ -0,0 +1,1 @@
1012 > +a
1012 > +a
1013 > diff --git a/b b/b
1013 > diff --git a/b b/b
1014 > new file mode 100644
1014 > new file mode 100644
1015 > EOF
1015 > EOF
1016 $ hg import --no-commit a.patch
1016 $ hg import --no-commit a.patch
1017 applying a.patch
1017 applying a.patch
1018
1018
1019 apply a good patch followed by an empty patch (mainly to ensure
1019 apply a good patch followed by an empty patch (mainly to ensure
1020 that dirstate is *not* updated when import crashes)
1020 that dirstate is *not* updated when import crashes)
1021 $ hg update -q -C .
1021 $ hg update -q -C .
1022 $ rm b
1022 $ rm b
1023 $ touch empty.patch
1023 $ touch empty.patch
1024 $ hg import a.patch empty.patch
1024 $ hg import a.patch empty.patch
1025 applying a.patch
1025 applying a.patch
1026 applying empty.patch
1026 applying empty.patch
1027 transaction abort!
1027 transaction abort!
1028 rollback completed
1028 rollback completed
1029 abort: empty.patch: no diffs found
1029 abort: empty.patch: no diffs found
1030 [255]
1030 [255]
1031 $ hg tip --template '{rev} {desc|firstline}\n'
1031 $ hg tip --template '{rev} {desc|firstline}\n'
1032 0 commit
1032 0 commit
1033 $ hg -q status
1033 $ hg -q status
1034 M a
1034 M a
1035 $ cd ..
1035 $ cd ..
1036
1036
1037 create file when source is not /dev/null
1037 create file when source is not /dev/null
1038
1038
1039 $ cat > create.patch <<EOF
1039 $ cat > create.patch <<EOF
1040 > diff -Naur proj-orig/foo proj-new/foo
1040 > diff -Naur proj-orig/foo proj-new/foo
1041 > --- proj-orig/foo 1969-12-31 16:00:00.000000000 -0800
1041 > --- proj-orig/foo 1969-12-31 16:00:00.000000000 -0800
1042 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
1042 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
1043 > @@ -0,0 +1,1 @@
1043 > @@ -0,0 +1,1 @@
1044 > +a
1044 > +a
1045 > EOF
1045 > EOF
1046
1046
1047 some people have patches like the following too
1047 some people have patches like the following too
1048
1048
1049 $ cat > create2.patch <<EOF
1049 $ cat > create2.patch <<EOF
1050 > diff -Naur proj-orig/foo proj-new/foo
1050 > diff -Naur proj-orig/foo proj-new/foo
1051 > --- proj-orig/foo.orig 1969-12-31 16:00:00.000000000 -0800
1051 > --- proj-orig/foo.orig 1969-12-31 16:00:00.000000000 -0800
1052 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
1052 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
1053 > @@ -0,0 +1,1 @@
1053 > @@ -0,0 +1,1 @@
1054 > +a
1054 > +a
1055 > EOF
1055 > EOF
1056 $ hg init oddcreate
1056 $ hg init oddcreate
1057 $ cd oddcreate
1057 $ cd oddcreate
1058 $ hg import --no-commit ../create.patch
1058 $ hg import --no-commit ../create.patch
1059 applying ../create.patch
1059 applying ../create.patch
1060 $ cat foo
1060 $ cat foo
1061 a
1061 a
1062 $ rm foo
1062 $ rm foo
1063 $ hg revert foo
1063 $ hg revert foo
1064 $ hg import --no-commit ../create2.patch
1064 $ hg import --no-commit ../create2.patch
1065 applying ../create2.patch
1065 applying ../create2.patch
1066 $ cat foo
1066 $ cat foo
1067 a
1067 a
1068
1068
1069 $ cd ..
1069 $ cd ..
1070
1070
1071 Issue1859: first line mistaken for email headers
1071 Issue1859: first line mistaken for email headers
1072
1072
1073 $ hg init emailconfusion
1073 $ hg init emailconfusion
1074 $ cd emailconfusion
1074 $ cd emailconfusion
1075 $ cat > a.patch <<EOF
1075 $ cat > a.patch <<EOF
1076 > module: summary
1076 > module: summary
1077 >
1077 >
1078 > description
1078 > description
1079 >
1079 >
1080 >
1080 >
1081 > diff -r 000000000000 -r 9b4c1e343b55 test.txt
1081 > diff -r 000000000000 -r 9b4c1e343b55 test.txt
1082 > --- /dev/null
1082 > --- /dev/null
1083 > +++ b/a
1083 > +++ b/a
1084 > @@ -0,0 +1,1 @@
1084 > @@ -0,0 +1,1 @@
1085 > +a
1085 > +a
1086 > EOF
1086 > EOF
1087 $ hg import -d '0 0' a.patch
1087 $ hg import -d '0 0' a.patch
1088 applying a.patch
1088 applying a.patch
1089 $ hg parents -v
1089 $ hg parents -v
1090 changeset: 0:5a681217c0ad
1090 changeset: 0:5a681217c0ad
1091 tag: tip
1091 tag: tip
1092 user: test
1092 user: test
1093 date: Thu Jan 01 00:00:00 1970 +0000
1093 date: Thu Jan 01 00:00:00 1970 +0000
1094 files: a
1094 files: a
1095 description:
1095 description:
1096 module: summary
1096 module: summary
1097
1097
1098 description
1098 description
1099
1099
1100
1100
1101 $ cd ..
1101 $ cd ..
1102
1102
1103
1103
1104 in commit message
1104 in commit message
1105
1105
1106 $ hg init commitconfusion
1106 $ hg init commitconfusion
1107 $ cd commitconfusion
1107 $ cd commitconfusion
1108 $ cat > a.patch <<EOF
1108 $ cat > a.patch <<EOF
1109 > module: summary
1109 > module: summary
1110 >
1110 >
1111 > --- description
1111 > --- description
1112 >
1112 >
1113 > diff --git a/a b/a
1113 > diff --git a/a b/a
1114 > new file mode 100644
1114 > new file mode 100644
1115 > --- /dev/null
1115 > --- /dev/null
1116 > +++ b/a
1116 > +++ b/a
1117 > @@ -0,0 +1,1 @@
1117 > @@ -0,0 +1,1 @@
1118 > +a
1118 > +a
1119 > EOF
1119 > EOF
1120 > hg import -d '0 0' a.patch
1120 > hg import -d '0 0' a.patch
1121 > hg parents -v
1121 > hg parents -v
1122 > cd ..
1122 > cd ..
1123 >
1123 >
1124 > echo '% tricky header splitting'
1124 > echo '% tricky header splitting'
1125 > cat > trickyheaders.patch <<EOF
1125 > cat > trickyheaders.patch <<EOF
1126 > From: User A <user@a>
1126 > From: User A <user@a>
1127 > Subject: [PATCH] from: tricky!
1127 > Subject: [PATCH] from: tricky!
1128 >
1128 >
1129 > # HG changeset patch
1129 > # HG changeset patch
1130 > # User User B
1130 > # User User B
1131 > # Date 1266264441 18000
1131 > # Date 1266264441 18000
1132 > # Branch stable
1132 > # Branch stable
1133 > # Node ID f2be6a1170ac83bf31cb4ae0bad00d7678115bc0
1133 > # Node ID f2be6a1170ac83bf31cb4ae0bad00d7678115bc0
1134 > # Parent 0000000000000000000000000000000000000000
1134 > # Parent 0000000000000000000000000000000000000000
1135 > from: tricky!
1135 > from: tricky!
1136 >
1136 >
1137 > That is not a header.
1137 > That is not a header.
1138 >
1138 >
1139 > diff -r 000000000000 -r f2be6a1170ac foo
1139 > diff -r 000000000000 -r f2be6a1170ac foo
1140 > --- /dev/null
1140 > --- /dev/null
1141 > +++ b/foo
1141 > +++ b/foo
1142 > @@ -0,0 +1,1 @@
1142 > @@ -0,0 +1,1 @@
1143 > +foo
1143 > +foo
1144 > EOF
1144 > EOF
1145 applying a.patch
1145 applying a.patch
1146 changeset: 0:f34d9187897d
1146 changeset: 0:f34d9187897d
1147 tag: tip
1147 tag: tip
1148 user: test
1148 user: test
1149 date: Thu Jan 01 00:00:00 1970 +0000
1149 date: Thu Jan 01 00:00:00 1970 +0000
1150 files: a
1150 files: a
1151 description:
1151 description:
1152 module: summary
1152 module: summary
1153
1153
1154
1154
1155 % tricky header splitting
1155 % tricky header splitting
1156
1156
1157 $ hg init trickyheaders
1157 $ hg init trickyheaders
1158 $ cd trickyheaders
1158 $ cd trickyheaders
1159 $ hg import -d '0 0' ../trickyheaders.patch
1159 $ hg import -d '0 0' ../trickyheaders.patch
1160 applying ../trickyheaders.patch
1160 applying ../trickyheaders.patch
1161 $ hg export --git tip
1161 $ hg export --git tip
1162 # HG changeset patch
1162 # HG changeset patch
1163 # User User B
1163 # User User B
1164 # Date 0 0
1164 # Date 0 0
1165 # Thu Jan 01 00:00:00 1970 +0000
1165 # Thu Jan 01 00:00:00 1970 +0000
1166 # Node ID eb56ab91903632294ac504838508cb370c0901d2
1166 # Node ID eb56ab91903632294ac504838508cb370c0901d2
1167 # Parent 0000000000000000000000000000000000000000
1167 # Parent 0000000000000000000000000000000000000000
1168 from: tricky!
1168 from: tricky!
1169
1169
1170 That is not a header.
1170 That is not a header.
1171
1171
1172 diff --git a/foo b/foo
1172 diff --git a/foo b/foo
1173 new file mode 100644
1173 new file mode 100644
1174 --- /dev/null
1174 --- /dev/null
1175 +++ b/foo
1175 +++ b/foo
1176 @@ -0,0 +1,1 @@
1176 @@ -0,0 +1,1 @@
1177 +foo
1177 +foo
1178 $ cd ..
1178 $ cd ..
1179
1179
1180
1180
1181 Issue2102: hg export and hg import speak different languages
1181 Issue2102: hg export and hg import speak different languages
1182
1182
1183 $ hg init issue2102
1183 $ hg init issue2102
1184 $ cd issue2102
1184 $ cd issue2102
1185 $ mkdir -p src/cmd/gc
1185 $ mkdir -p src/cmd/gc
1186 $ touch src/cmd/gc/mksys.bash
1186 $ touch src/cmd/gc/mksys.bash
1187 $ hg ci -Am init
1187 $ hg ci -Am init
1188 adding src/cmd/gc/mksys.bash
1188 adding src/cmd/gc/mksys.bash
1189 $ hg import - <<EOF
1189 $ hg import - <<EOF
1190 > # HG changeset patch
1190 > # HG changeset patch
1191 > # User Rob Pike
1191 > # User Rob Pike
1192 > # Date 1216685449 25200
1192 > # Date 1216685449 25200
1193 > # Node ID 03aa2b206f499ad6eb50e6e207b9e710d6409c98
1193 > # Node ID 03aa2b206f499ad6eb50e6e207b9e710d6409c98
1194 > # Parent 93d10138ad8df586827ca90b4ddb5033e21a3a84
1194 > # Parent 93d10138ad8df586827ca90b4ddb5033e21a3a84
1195 > help management of empty pkg and lib directories in perforce
1195 > help management of empty pkg and lib directories in perforce
1196 >
1196 >
1197 > R=gri
1197 > R=gri
1198 > DELTA=4 (4 added, 0 deleted, 0 changed)
1198 > DELTA=4 (4 added, 0 deleted, 0 changed)
1199 > OCL=13328
1199 > OCL=13328
1200 > CL=13328
1200 > CL=13328
1201 >
1201 >
1202 > diff --git a/lib/place-holder b/lib/place-holder
1202 > diff --git a/lib/place-holder b/lib/place-holder
1203 > new file mode 100644
1203 > new file mode 100644
1204 > --- /dev/null
1204 > --- /dev/null
1205 > +++ b/lib/place-holder
1205 > +++ b/lib/place-holder
1206 > @@ -0,0 +1,2 @@
1206 > @@ -0,0 +1,2 @@
1207 > +perforce does not maintain empty directories.
1207 > +perforce does not maintain empty directories.
1208 > +this file helps.
1208 > +this file helps.
1209 > diff --git a/pkg/place-holder b/pkg/place-holder
1209 > diff --git a/pkg/place-holder b/pkg/place-holder
1210 > new file mode 100644
1210 > new file mode 100644
1211 > --- /dev/null
1211 > --- /dev/null
1212 > +++ b/pkg/place-holder
1212 > +++ b/pkg/place-holder
1213 > @@ -0,0 +1,2 @@
1213 > @@ -0,0 +1,2 @@
1214 > +perforce does not maintain empty directories.
1214 > +perforce does not maintain empty directories.
1215 > +this file helps.
1215 > +this file helps.
1216 > diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
1216 > diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
1217 > old mode 100644
1217 > old mode 100644
1218 > new mode 100755
1218 > new mode 100755
1219 > EOF
1219 > EOF
1220 applying patch from stdin
1220 applying patch from stdin
1221
1221
1222 #if execbit
1222 #if execbit
1223
1223
1224 $ hg sum
1224 $ hg sum
1225 parent: 1:d59915696727 tip
1225 parent: 1:d59915696727 tip
1226 help management of empty pkg and lib directories in perforce
1226 help management of empty pkg and lib directories in perforce
1227 branch: default
1227 branch: default
1228 commit: (clean)
1228 commit: (clean)
1229 update: (current)
1229 update: (current)
1230 phases: 2 draft
1230 phases: 2 draft
1231
1231
1232 $ hg diff --git -c tip
1232 $ hg diff --git -c tip
1233 diff --git a/lib/place-holder b/lib/place-holder
1233 diff --git a/lib/place-holder b/lib/place-holder
1234 new file mode 100644
1234 new file mode 100644
1235 --- /dev/null
1235 --- /dev/null
1236 +++ b/lib/place-holder
1236 +++ b/lib/place-holder
1237 @@ -0,0 +1,2 @@
1237 @@ -0,0 +1,2 @@
1238 +perforce does not maintain empty directories.
1238 +perforce does not maintain empty directories.
1239 +this file helps.
1239 +this file helps.
1240 diff --git a/pkg/place-holder b/pkg/place-holder
1240 diff --git a/pkg/place-holder b/pkg/place-holder
1241 new file mode 100644
1241 new file mode 100644
1242 --- /dev/null
1242 --- /dev/null
1243 +++ b/pkg/place-holder
1243 +++ b/pkg/place-holder
1244 @@ -0,0 +1,2 @@
1244 @@ -0,0 +1,2 @@
1245 +perforce does not maintain empty directories.
1245 +perforce does not maintain empty directories.
1246 +this file helps.
1246 +this file helps.
1247 diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
1247 diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
1248 old mode 100644
1248 old mode 100644
1249 new mode 100755
1249 new mode 100755
1250
1250
1251 #else
1251 #else
1252
1252
1253 $ hg sum
1253 $ hg sum
1254 parent: 1:28f089cc9ccc tip
1254 parent: 1:28f089cc9ccc tip
1255 help management of empty pkg and lib directories in perforce
1255 help management of empty pkg and lib directories in perforce
1256 branch: default
1256 branch: default
1257 commit: (clean)
1257 commit: (clean)
1258 update: (current)
1258 update: (current)
1259 phases: 2 draft
1259 phases: 2 draft
1260
1260
1261 $ hg diff --git -c tip
1261 $ hg diff --git -c tip
1262 diff --git a/lib/place-holder b/lib/place-holder
1262 diff --git a/lib/place-holder b/lib/place-holder
1263 new file mode 100644
1263 new file mode 100644
1264 --- /dev/null
1264 --- /dev/null
1265 +++ b/lib/place-holder
1265 +++ b/lib/place-holder
1266 @@ -0,0 +1,2 @@
1266 @@ -0,0 +1,2 @@
1267 +perforce does not maintain empty directories.
1267 +perforce does not maintain empty directories.
1268 +this file helps.
1268 +this file helps.
1269 diff --git a/pkg/place-holder b/pkg/place-holder
1269 diff --git a/pkg/place-holder b/pkg/place-holder
1270 new file mode 100644
1270 new file mode 100644
1271 --- /dev/null
1271 --- /dev/null
1272 +++ b/pkg/place-holder
1272 +++ b/pkg/place-holder
1273 @@ -0,0 +1,2 @@
1273 @@ -0,0 +1,2 @@
1274 +perforce does not maintain empty directories.
1274 +perforce does not maintain empty directories.
1275 +this file helps.
1275 +this file helps.
1276
1276
1277 /* The mode change for mksys.bash is missing here, because on platforms */
1277 /* The mode change for mksys.bash is missing here, because on platforms */
1278 /* that don't support execbits, mode changes in patches are ignored when */
1278 /* that don't support execbits, mode changes in patches are ignored when */
1279 /* they are imported. This is obviously also the reason for why the hash */
1279 /* they are imported. This is obviously also the reason for why the hash */
1280 /* in the created changeset is different to the one you see above the */
1280 /* in the created changeset is different to the one you see above the */
1281 /* #else clause */
1281 /* #else clause */
1282
1282
1283 #endif
1283 #endif
1284 $ cd ..
1284 $ cd ..
1285
1285
1286
1286
1287 diff lines looking like headers
1287 diff lines looking like headers
1288
1288
1289 $ hg init difflineslikeheaders
1289 $ hg init difflineslikeheaders
1290 $ cd difflineslikeheaders
1290 $ cd difflineslikeheaders
1291 $ echo a >a
1291 $ echo a >a
1292 $ echo b >b
1292 $ echo b >b
1293 $ echo c >c
1293 $ echo c >c
1294 $ hg ci -Am1
1294 $ hg ci -Am1
1295 adding a
1295 adding a
1296 adding b
1296 adding b
1297 adding c
1297 adding c
1298
1298
1299 $ echo "key: value" >>a
1299 $ echo "key: value" >>a
1300 $ echo "key: value" >>b
1300 $ echo "key: value" >>b
1301 $ echo "foo" >>c
1301 $ echo "foo" >>c
1302 $ hg ci -m2
1302 $ hg ci -m2
1303
1303
1304 $ hg up -C 0
1304 $ hg up -C 0
1305 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
1305 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
1306 $ hg diff --git -c1 >want
1306 $ hg diff --git -c1 >want
1307 $ hg diff -c1 | hg import --no-commit -
1307 $ hg diff -c1 | hg import --no-commit -
1308 applying patch from stdin
1308 applying patch from stdin
1309 $ hg diff --git >have
1309 $ hg diff --git >have
1310 $ diff want have
1310 $ diff want have
1311 $ cd ..
1311 $ cd ..
1312
1312
1313 import a unified diff with no lines of context (diff -U0)
1313 import a unified diff with no lines of context (diff -U0)
1314
1314
1315 $ hg init diffzero
1315 $ hg init diffzero
1316 $ cd diffzero
1316 $ cd diffzero
1317 $ cat > f << EOF
1317 $ cat > f << EOF
1318 > c2
1318 > c2
1319 > c4
1319 > c4
1320 > c5
1320 > c5
1321 > EOF
1321 > EOF
1322 $ hg commit -Am0
1322 $ hg commit -Am0
1323 adding f
1323 adding f
1324
1324
1325 $ hg import --no-commit - << EOF
1325 $ hg import --no-commit - << EOF
1326 > # HG changeset patch
1326 > # HG changeset patch
1327 > # User test
1327 > # User test
1328 > # Date 0 0
1328 > # Date 0 0
1329 > # Node ID f4974ab632f3dee767567b0576c0ec9a4508575c
1329 > # Node ID f4974ab632f3dee767567b0576c0ec9a4508575c
1330 > # Parent 8679a12a975b819fae5f7ad3853a2886d143d794
1330 > # Parent 8679a12a975b819fae5f7ad3853a2886d143d794
1331 > 1
1331 > 1
1332 > diff -r 8679a12a975b -r f4974ab632f3 f
1332 > diff -r 8679a12a975b -r f4974ab632f3 f
1333 > --- a/f Thu Jan 01 00:00:00 1970 +0000
1333 > --- a/f Thu Jan 01 00:00:00 1970 +0000
1334 > +++ b/f Thu Jan 01 00:00:00 1970 +0000
1334 > +++ b/f Thu Jan 01 00:00:00 1970 +0000
1335 > @@ -0,0 +1,1 @@
1335 > @@ -0,0 +1,1 @@
1336 > +c1
1336 > +c1
1337 > @@ -1,0 +3,1 @@
1337 > @@ -1,0 +3,1 @@
1338 > +c3
1338 > +c3
1339 > @@ -3,1 +4,0 @@
1339 > @@ -3,1 +4,0 @@
1340 > -c5
1340 > -c5
1341 > EOF
1341 > EOF
1342 applying patch from stdin
1342 applying patch from stdin
1343
1343
1344 $ cat f
1344 $ cat f
1345 c1
1345 c1
1346 c2
1346 c2
1347 c3
1347 c3
1348 c4
1348 c4
1349
1349
1350 $ cd ..
1350 $ cd ..
1351
1351
1352 commit message that looks like a diff header (issue1879)
1352 commit message that looks like a diff header (issue1879)
1353
1353
1354 $ hg init headerlikemsg
1354 $ hg init headerlikemsg
1355 $ cd headerlikemsg
1355 $ cd headerlikemsg
1356 $ touch empty
1356 $ touch empty
1357 $ echo nonempty >> nonempty
1357 $ echo nonempty >> nonempty
1358 $ hg ci -qAl - <<EOF
1358 $ hg ci -qAl - <<EOF
1359 > blah blah
1359 > blah blah
1360 > diff blah
1360 > diff blah
1361 > blah blah
1361 > blah blah
1362 > EOF
1362 > EOF
1363 $ hg --config diff.git=1 log -pv
1363 $ hg --config diff.git=1 log -pv
1364 changeset: 0:c6ef204ef767
1364 changeset: 0:c6ef204ef767
1365 tag: tip
1365 tag: tip
1366 user: test
1366 user: test
1367 date: Thu Jan 01 00:00:00 1970 +0000
1367 date: Thu Jan 01 00:00:00 1970 +0000
1368 files: empty nonempty
1368 files: empty nonempty
1369 description:
1369 description:
1370 blah blah
1370 blah blah
1371 diff blah
1371 diff blah
1372 blah blah
1372 blah blah
1373
1373
1374
1374
1375 diff --git a/empty b/empty
1375 diff --git a/empty b/empty
1376 new file mode 100644
1376 new file mode 100644
1377 diff --git a/nonempty b/nonempty
1377 diff --git a/nonempty b/nonempty
1378 new file mode 100644
1378 new file mode 100644
1379 --- /dev/null
1379 --- /dev/null
1380 +++ b/nonempty
1380 +++ b/nonempty
1381 @@ -0,0 +1,1 @@
1381 @@ -0,0 +1,1 @@
1382 +nonempty
1382 +nonempty
1383
1383
1384
1384
1385 (without --git, empty file is lost, but commit message should be preserved)
1385 (without --git, empty file is lost, but commit message should be preserved)
1386
1386
1387 $ hg init plain
1387 $ hg init plain
1388 $ hg export 0 | hg -R plain import -
1388 $ hg export 0 | hg -R plain import -
1389 applying patch from stdin
1389 applying patch from stdin
1390 $ hg --config diff.git=1 -R plain log -pv
1390 $ hg --config diff.git=1 -R plain log -pv
1391 changeset: 0:60a2d231e71f
1391 changeset: 0:60a2d231e71f
1392 tag: tip
1392 tag: tip
1393 user: test
1393 user: test
1394 date: Thu Jan 01 00:00:00 1970 +0000
1394 date: Thu Jan 01 00:00:00 1970 +0000
1395 files: nonempty
1395 files: nonempty
1396 description:
1396 description:
1397 blah blah
1397 blah blah
1398 diff blah
1398 diff blah
1399 blah blah
1399 blah blah
1400
1400
1401
1401
1402 diff --git a/nonempty b/nonempty
1402 diff --git a/nonempty b/nonempty
1403 new file mode 100644
1403 new file mode 100644
1404 --- /dev/null
1404 --- /dev/null
1405 +++ b/nonempty
1405 +++ b/nonempty
1406 @@ -0,0 +1,1 @@
1406 @@ -0,0 +1,1 @@
1407 +nonempty
1407 +nonempty
1408
1408
1409
1409
1410 (with --git, patch contents should be fully preserved)
1410 (with --git, patch contents should be fully preserved)
1411
1411
1412 $ hg init git
1412 $ hg init git
1413 $ hg --config diff.git=1 export 0 | hg -R git import -
1413 $ hg --config diff.git=1 export 0 | hg -R git import -
1414 applying patch from stdin
1414 applying patch from stdin
1415 $ hg --config diff.git=1 -R git log -pv
1415 $ hg --config diff.git=1 -R git log -pv
1416 changeset: 0:c6ef204ef767
1416 changeset: 0:c6ef204ef767
1417 tag: tip
1417 tag: tip
1418 user: test
1418 user: test
1419 date: Thu Jan 01 00:00:00 1970 +0000
1419 date: Thu Jan 01 00:00:00 1970 +0000
1420 files: empty nonempty
1420 files: empty nonempty
1421 description:
1421 description:
1422 blah blah
1422 blah blah
1423 diff blah
1423 diff blah
1424 blah blah
1424 blah blah
1425
1425
1426
1426
1427 diff --git a/empty b/empty
1427 diff --git a/empty b/empty
1428 new file mode 100644
1428 new file mode 100644
1429 diff --git a/nonempty b/nonempty
1429 diff --git a/nonempty b/nonempty
1430 new file mode 100644
1430 new file mode 100644
1431 --- /dev/null
1431 --- /dev/null
1432 +++ b/nonempty
1432 +++ b/nonempty
1433 @@ -0,0 +1,1 @@
1433 @@ -0,0 +1,1 @@
1434 +nonempty
1434 +nonempty
1435
1435
1436
1436
1437 $ cd ..
1437 $ cd ..
1438
1438
1439 no segfault while importing a unified diff which start line is zero but chunk
1439 no segfault while importing a unified diff which start line is zero but chunk
1440 size is non-zero
1440 size is non-zero
1441
1441
1442 $ hg init startlinezero
1442 $ hg init startlinezero
1443 $ cd startlinezero
1443 $ cd startlinezero
1444 $ echo foo > foo
1444 $ echo foo > foo
1445 $ hg commit -Amfoo
1445 $ hg commit -Amfoo
1446 adding foo
1446 adding foo
1447
1447
1448 $ hg import --no-commit - << EOF
1448 $ hg import --no-commit - << EOF
1449 > diff a/foo b/foo
1449 > diff a/foo b/foo
1450 > --- a/foo
1450 > --- a/foo
1451 > +++ b/foo
1451 > +++ b/foo
1452 > @@ -0,1 +0,1 @@
1452 > @@ -0,1 +0,1 @@
1453 > foo
1453 > foo
1454 > EOF
1454 > EOF
1455 applying patch from stdin
1455 applying patch from stdin
1456
1456
1457 $ cd ..
1457 $ cd ..
1458
1458
1459 Test corner case involving fuzz and skew
1459 Test corner case involving fuzz and skew
1460
1460
1461 $ hg init morecornercases
1461 $ hg init morecornercases
1462 $ cd morecornercases
1462 $ cd morecornercases
1463
1463
1464 $ cat > 01-no-context-beginning-of-file.diff <<EOF
1464 $ cat > 01-no-context-beginning-of-file.diff <<EOF
1465 > diff --git a/a b/a
1465 > diff --git a/a b/a
1466 > --- a/a
1466 > --- a/a
1467 > +++ b/a
1467 > +++ b/a
1468 > @@ -1,0 +1,1 @@
1468 > @@ -1,0 +1,1 @@
1469 > +line
1469 > +line
1470 > EOF
1470 > EOF
1471
1471
1472 $ cat > 02-no-context-middle-of-file.diff <<EOF
1472 $ cat > 02-no-context-middle-of-file.diff <<EOF
1473 > diff --git a/a b/a
1473 > diff --git a/a b/a
1474 > --- a/a
1474 > --- a/a
1475 > +++ b/a
1475 > +++ b/a
1476 > @@ -1,1 +1,1 @@
1476 > @@ -1,1 +1,1 @@
1477 > -2
1477 > -2
1478 > +add some skew
1478 > +add some skew
1479 > @@ -2,0 +2,1 @@
1479 > @@ -2,0 +2,1 @@
1480 > +line
1480 > +line
1481 > EOF
1481 > EOF
1482
1482
1483 $ cat > 03-no-context-end-of-file.diff <<EOF
1483 $ cat > 03-no-context-end-of-file.diff <<EOF
1484 > diff --git a/a b/a
1484 > diff --git a/a b/a
1485 > --- a/a
1485 > --- a/a
1486 > +++ b/a
1486 > +++ b/a
1487 > @@ -10,0 +10,1 @@
1487 > @@ -10,0 +10,1 @@
1488 > +line
1488 > +line
1489 > EOF
1489 > EOF
1490
1490
1491 $ cat > 04-middle-of-file-completely-fuzzed.diff <<EOF
1491 $ cat > 04-middle-of-file-completely-fuzzed.diff <<EOF
1492 > diff --git a/a b/a
1492 > diff --git a/a b/a
1493 > --- a/a
1493 > --- a/a
1494 > +++ b/a
1494 > +++ b/a
1495 > @@ -1,1 +1,1 @@
1495 > @@ -1,1 +1,1 @@
1496 > -2
1496 > -2
1497 > +add some skew
1497 > +add some skew
1498 > @@ -2,2 +2,3 @@
1498 > @@ -2,2 +2,3 @@
1499 > not matching, should fuzz
1499 > not matching, should fuzz
1500 > ... a bit
1500 > ... a bit
1501 > +line
1501 > +line
1502 > EOF
1502 > EOF
1503
1503
1504 $ cat > a <<EOF
1504 $ cat > a <<EOF
1505 > 1
1505 > 1
1506 > 2
1506 > 2
1507 > 3
1507 > 3
1508 > 4
1508 > 4
1509 > EOF
1509 > EOF
1510 $ hg ci -Am adda a
1510 $ hg ci -Am adda a
1511 $ for p in *.diff; do
1511 $ for p in *.diff; do
1512 > hg import -v --no-commit $p
1512 > hg import -v --no-commit $p
1513 > cat a
1513 > cat a
1514 > hg revert -aqC a
1514 > hg revert -aqC a
1515 > # patch -p1 < $p
1515 > # patch -p1 < $p
1516 > # cat a
1516 > # cat a
1517 > # hg revert -aC a
1517 > # hg revert -aC a
1518 > done
1518 > done
1519 applying 01-no-context-beginning-of-file.diff
1519 applying 01-no-context-beginning-of-file.diff
1520 patching file a
1520 patching file a
1521 applied to working directory
1521 applied to working directory
1522 1
1522 1
1523 line
1523 line
1524 2
1524 2
1525 3
1525 3
1526 4
1526 4
1527 applying 02-no-context-middle-of-file.diff
1527 applying 02-no-context-middle-of-file.diff
1528 patching file a
1528 patching file a
1529 Hunk #1 succeeded at 2 (offset 1 lines).
1529 Hunk #1 succeeded at 2 (offset 1 lines).
1530 Hunk #2 succeeded at 4 (offset 1 lines).
1530 Hunk #2 succeeded at 4 (offset 1 lines).
1531 applied to working directory
1531 applied to working directory
1532 1
1532 1
1533 add some skew
1533 add some skew
1534 3
1534 3
1535 line
1535 line
1536 4
1536 4
1537 applying 03-no-context-end-of-file.diff
1537 applying 03-no-context-end-of-file.diff
1538 patching file a
1538 patching file a
1539 Hunk #1 succeeded at 5 (offset -6 lines).
1539 Hunk #1 succeeded at 5 (offset -6 lines).
1540 applied to working directory
1540 applied to working directory
1541 1
1541 1
1542 2
1542 2
1543 3
1543 3
1544 4
1544 4
1545 line
1545 line
1546 applying 04-middle-of-file-completely-fuzzed.diff
1546 applying 04-middle-of-file-completely-fuzzed.diff
1547 patching file a
1547 patching file a
1548 Hunk #1 succeeded at 2 (offset 1 lines).
1548 Hunk #1 succeeded at 2 (offset 1 lines).
1549 Hunk #2 succeeded at 5 with fuzz 2 (offset 1 lines).
1549 Hunk #2 succeeded at 5 with fuzz 2 (offset 1 lines).
1550 applied to working directory
1550 applied to working directory
1551 1
1551 1
1552 add some skew
1552 add some skew
1553 3
1553 3
1554 4
1554 4
1555 line
1555 line
1556 $ cd ..
1556 $ cd ..
1557
1557
1558 Test partial application
1558 Test partial application
1559 ------------------------
1559 ------------------------
1560
1560
1561 prepare a stack of patches depending on each other
1561 prepare a stack of patches depending on each other
1562
1562
1563 $ hg init partial
1563 $ hg init partial
1564 $ cd partial
1564 $ cd partial
1565 $ cat << EOF > a
1565 $ cat << EOF > a
1566 > one
1566 > one
1567 > two
1567 > two
1568 > three
1568 > three
1569 > four
1569 > four
1570 > five
1570 > five
1571 > six
1571 > six
1572 > seven
1572 > seven
1573 > EOF
1573 > EOF
1574 $ hg add a
1574 $ hg add a
1575 $ echo 'b' > b
1575 $ echo 'b' > b
1576 $ hg add b
1576 $ hg add b
1577 $ hg commit -m 'initial' -u Babar
1577 $ hg commit -m 'initial' -u Babar
1578 $ cat << EOF > a
1578 $ cat << EOF > a
1579 > one
1579 > one
1580 > two
1580 > two
1581 > 3
1581 > 3
1582 > four
1582 > four
1583 > five
1583 > five
1584 > six
1584 > six
1585 > seven
1585 > seven
1586 > EOF
1586 > EOF
1587 $ hg commit -m 'three' -u Celeste
1587 $ hg commit -m 'three' -u Celeste
1588 $ cat << EOF > a
1588 $ cat << EOF > a
1589 > one
1589 > one
1590 > two
1590 > two
1591 > 3
1591 > 3
1592 > 4
1592 > 4
1593 > five
1593 > five
1594 > six
1594 > six
1595 > seven
1595 > seven
1596 > EOF
1596 > EOF
1597 $ hg commit -m 'four' -u Rataxes
1597 $ hg commit -m 'four' -u Rataxes
1598 $ cat << EOF > a
1598 $ cat << EOF > a
1599 > one
1599 > one
1600 > two
1600 > two
1601 > 3
1601 > 3
1602 > 4
1602 > 4
1603 > 5
1603 > 5
1604 > six
1604 > six
1605 > seven
1605 > seven
1606 > EOF
1606 > EOF
1607 $ echo bb >> b
1607 $ echo bb >> b
1608 $ hg commit -m 'five' -u Arthur
1608 $ hg commit -m 'five' -u Arthur
1609 $ echo 'Babar' > jungle
1609 $ echo 'Babar' > jungle
1610 $ hg add jungle
1610 $ hg add jungle
1611 $ hg ci -m 'jungle' -u Zephir
1611 $ hg ci -m 'jungle' -u Zephir
1612 $ echo 'Celeste' >> jungle
1612 $ echo 'Celeste' >> jungle
1613 $ hg ci -m 'extended jungle' -u Cornelius
1613 $ hg ci -m 'extended jungle' -u Cornelius
1614 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1614 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1615 @ extended jungle [Cornelius] 1: +1/-0
1615 @ extended jungle [Cornelius] 1: +1/-0
1616 |
1616 |
1617 o jungle [Zephir] 1: +1/-0
1617 o jungle [Zephir] 1: +1/-0
1618 |
1618 |
1619 o five [Arthur] 2: +2/-1
1619 o five [Arthur] 2: +2/-1
1620 |
1620 |
1621 o four [Rataxes] 1: +1/-1
1621 o four [Rataxes] 1: +1/-1
1622 |
1622 |
1623 o three [Celeste] 1: +1/-1
1623 o three [Celeste] 1: +1/-1
1624 |
1624 |
1625 o initial [Babar] 2: +8/-0
1625 o initial [Babar] 2: +8/-0
1626
1626
1627 Adding those config options should not change the output of diffstat. Bugfix #4755.
1627 Adding those config options should not change the output of diffstat. Bugfix #4755.
1628
1628
1629 $ hg log -r . --template '{diffstat}\n'
1629 $ hg log -r . --template '{diffstat}\n'
1630 1: +1/-0
1630 1: +1/-0
1631 $ hg log -r . --template '{diffstat}\n' --config diff.git=1 \
1631 $ hg log -r . --template '{diffstat}\n' --config diff.git=1 \
1632 > --config diff.noprefix=1
1632 > --config diff.noprefix=1
1633 1: +1/-0
1633 1: +1/-0
1634
1634
1635 Importing with some success and some errors:
1635 Importing with some success and some errors:
1636
1636
1637 $ hg update --rev 'desc(initial)'
1637 $ hg update --rev 'desc(initial)'
1638 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
1638 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
1639 $ hg export --rev 'desc(five)' | hg import --partial -
1639 $ hg export --rev 'desc(five)' | hg import --partial -
1640 applying patch from stdin
1640 applying patch from stdin
1641 patching file a
1641 patching file a
1642 Hunk #1 FAILED at 1
1642 Hunk #1 FAILED at 1
1643 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1643 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1644 patch applied partially
1644 patch applied partially
1645 (fix the .rej files and run `hg commit --amend`)
1645 (fix the .rej files and run `hg commit --amend`)
1646 [1]
1646 [1]
1647
1647
1648 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1648 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1649 @ five [Arthur] 1: +1/-0
1649 @ five [Arthur] 1: +1/-0
1650 |
1650 |
1651 | o extended jungle [Cornelius] 1: +1/-0
1651 | o extended jungle [Cornelius] 1: +1/-0
1652 | |
1652 | |
1653 | o jungle [Zephir] 1: +1/-0
1653 | o jungle [Zephir] 1: +1/-0
1654 | |
1654 | |
1655 | o five [Arthur] 2: +2/-1
1655 | o five [Arthur] 2: +2/-1
1656 | |
1656 | |
1657 | o four [Rataxes] 1: +1/-1
1657 | o four [Rataxes] 1: +1/-1
1658 | |
1658 | |
1659 | o three [Celeste] 1: +1/-1
1659 | o three [Celeste] 1: +1/-1
1660 |/
1660 |/
1661 o initial [Babar] 2: +8/-0
1661 o initial [Babar] 2: +8/-0
1662
1662
1663 $ hg export
1663 $ hg export
1664 # HG changeset patch
1664 # HG changeset patch
1665 # User Arthur
1665 # User Arthur
1666 # Date 0 0
1666 # Date 0 0
1667 # Thu Jan 01 00:00:00 1970 +0000
1667 # Thu Jan 01 00:00:00 1970 +0000
1668 # Node ID 26e6446bb2526e2be1037935f5fca2b2706f1509
1668 # Node ID 26e6446bb2526e2be1037935f5fca2b2706f1509
1669 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1669 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1670 five
1670 five
1671
1671
1672 diff -r 8e4f0351909e -r 26e6446bb252 b
1672 diff -r 8e4f0351909e -r 26e6446bb252 b
1673 --- a/b Thu Jan 01 00:00:00 1970 +0000
1673 --- a/b Thu Jan 01 00:00:00 1970 +0000
1674 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1674 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1675 @@ -1,1 +1,2 @@
1675 @@ -1,1 +1,2 @@
1676 b
1676 b
1677 +bb
1677 +bb
1678 $ hg status -c .
1678 $ hg status -c .
1679 C a
1679 C a
1680 C b
1680 C b
1681 $ ls
1681 $ ls
1682 a
1682 a
1683 a.rej
1683 a.rej
1684 b
1684 b
1685
1685
1686 Importing with zero success:
1686 Importing with zero success:
1687
1687
1688 $ hg update --rev 'desc(initial)'
1688 $ hg update --rev 'desc(initial)'
1689 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1689 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1690 $ hg export --rev 'desc(four)' | hg import --partial -
1690 $ hg export --rev 'desc(four)' | hg import --partial -
1691 applying patch from stdin
1691 applying patch from stdin
1692 patching file a
1692 patching file a
1693 Hunk #1 FAILED at 0
1693 Hunk #1 FAILED at 0
1694 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1694 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1695 patch applied partially
1695 patch applied partially
1696 (fix the .rej files and run `hg commit --amend`)
1696 (fix the .rej files and run `hg commit --amend`)
1697 [1]
1697 [1]
1698
1698
1699 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1699 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1700 @ four [Rataxes] 0: +0/-0
1700 @ four [Rataxes] 0: +0/-0
1701 |
1701 |
1702 | o five [Arthur] 1: +1/-0
1702 | o five [Arthur] 1: +1/-0
1703 |/
1703 |/
1704 | o extended jungle [Cornelius] 1: +1/-0
1704 | o extended jungle [Cornelius] 1: +1/-0
1705 | |
1705 | |
1706 | o jungle [Zephir] 1: +1/-0
1706 | o jungle [Zephir] 1: +1/-0
1707 | |
1707 | |
1708 | o five [Arthur] 2: +2/-1
1708 | o five [Arthur] 2: +2/-1
1709 | |
1709 | |
1710 | o four [Rataxes] 1: +1/-1
1710 | o four [Rataxes] 1: +1/-1
1711 | |
1711 | |
1712 | o three [Celeste] 1: +1/-1
1712 | o three [Celeste] 1: +1/-1
1713 |/
1713 |/
1714 o initial [Babar] 2: +8/-0
1714 o initial [Babar] 2: +8/-0
1715
1715
1716 $ hg export
1716 $ hg export
1717 # HG changeset patch
1717 # HG changeset patch
1718 # User Rataxes
1718 # User Rataxes
1719 # Date 0 0
1719 # Date 0 0
1720 # Thu Jan 01 00:00:00 1970 +0000
1720 # Thu Jan 01 00:00:00 1970 +0000
1721 # Node ID cb9b1847a74d9ad52e93becaf14b98dbcc274e1e
1721 # Node ID cb9b1847a74d9ad52e93becaf14b98dbcc274e1e
1722 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1722 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1723 four
1723 four
1724
1724
1725 $ hg status -c .
1725 $ hg status -c .
1726 C a
1726 C a
1727 C b
1727 C b
1728 $ ls
1728 $ ls
1729 a
1729 a
1730 a.rej
1730 a.rej
1731 b
1731 b
1732
1732
1733 Importing with unknown file:
1733 Importing with unknown file:
1734
1734
1735 $ hg update --rev 'desc(initial)'
1735 $ hg update --rev 'desc(initial)'
1736 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1736 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1737 $ hg export --rev 'desc("extended jungle")' | hg import --partial -
1737 $ hg export --rev 'desc("extended jungle")' | hg import --partial -
1738 applying patch from stdin
1738 applying patch from stdin
1739 unable to find 'jungle' for patching
1739 unable to find 'jungle' for patching
1740 (use '--prefix' to apply patch relative to the current directory)
1740 (use '--prefix' to apply patch relative to the current directory)
1741 1 out of 1 hunks FAILED -- saving rejects to file jungle.rej
1741 1 out of 1 hunks FAILED -- saving rejects to file jungle.rej
1742 patch applied partially
1742 patch applied partially
1743 (fix the .rej files and run `hg commit --amend`)
1743 (fix the .rej files and run `hg commit --amend`)
1744 [1]
1744 [1]
1745
1745
1746 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1746 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1747 @ extended jungle [Cornelius] 0: +0/-0
1747 @ extended jungle [Cornelius] 0: +0/-0
1748 |
1748 |
1749 | o four [Rataxes] 0: +0/-0
1749 | o four [Rataxes] 0: +0/-0
1750 |/
1750 |/
1751 | o five [Arthur] 1: +1/-0
1751 | o five [Arthur] 1: +1/-0
1752 |/
1752 |/
1753 | o extended jungle [Cornelius] 1: +1/-0
1753 | o extended jungle [Cornelius] 1: +1/-0
1754 | |
1754 | |
1755 | o jungle [Zephir] 1: +1/-0
1755 | o jungle [Zephir] 1: +1/-0
1756 | |
1756 | |
1757 | o five [Arthur] 2: +2/-1
1757 | o five [Arthur] 2: +2/-1
1758 | |
1758 | |
1759 | o four [Rataxes] 1: +1/-1
1759 | o four [Rataxes] 1: +1/-1
1760 | |
1760 | |
1761 | o three [Celeste] 1: +1/-1
1761 | o three [Celeste] 1: +1/-1
1762 |/
1762 |/
1763 o initial [Babar] 2: +8/-0
1763 o initial [Babar] 2: +8/-0
1764
1764
1765 $ hg export
1765 $ hg export
1766 # HG changeset patch
1766 # HG changeset patch
1767 # User Cornelius
1767 # User Cornelius
1768 # Date 0 0
1768 # Date 0 0
1769 # Thu Jan 01 00:00:00 1970 +0000
1769 # Thu Jan 01 00:00:00 1970 +0000
1770 # Node ID 1fb1f86bef43c5a75918178f8d23c29fb0a7398d
1770 # Node ID 1fb1f86bef43c5a75918178f8d23c29fb0a7398d
1771 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1771 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1772 extended jungle
1772 extended jungle
1773
1773
1774 $ hg status -c .
1774 $ hg status -c .
1775 C a
1775 C a
1776 C b
1776 C b
1777 $ ls
1777 $ ls
1778 a
1778 a
1779 a.rej
1779 a.rej
1780 b
1780 b
1781 jungle.rej
1781 jungle.rej
1782
1782
1783 Importing multiple failing patches:
1783 Importing multiple failing patches:
1784
1784
1785 $ hg update --rev 'desc(initial)'
1785 $ hg update --rev 'desc(initial)'
1786 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1786 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1787 $ echo 'B' > b # just to make another commit
1787 $ echo 'B' > b # just to make another commit
1788 $ hg commit -m "a new base"
1788 $ hg commit -m "a new base"
1789 created new head
1789 created new head
1790 $ hg export --rev 'desc("four") + desc("extended jungle")' | hg import --partial -
1790 $ hg export --rev 'desc("four") + desc("extended jungle")' | hg import --partial -
1791 applying patch from stdin
1791 applying patch from stdin
1792 patching file a
1792 patching file a
1793 Hunk #1 FAILED at 0
1793 Hunk #1 FAILED at 0
1794 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1794 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1795 patch applied partially
1795 patch applied partially
1796 (fix the .rej files and run `hg commit --amend`)
1796 (fix the .rej files and run `hg commit --amend`)
1797 [1]
1797 [1]
1798 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1798 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1799 @ four [Rataxes] 0: +0/-0
1799 @ four [Rataxes] 0: +0/-0
1800 |
1800 |
1801 o a new base [test] 1: +1/-1
1801 o a new base [test] 1: +1/-1
1802 |
1802 |
1803 | o extended jungle [Cornelius] 0: +0/-0
1803 | o extended jungle [Cornelius] 0: +0/-0
1804 |/
1804 |/
1805 | o four [Rataxes] 0: +0/-0
1805 | o four [Rataxes] 0: +0/-0
1806 |/
1806 |/
1807 | o five [Arthur] 1: +1/-0
1807 | o five [Arthur] 1: +1/-0
1808 |/
1808 |/
1809 | o extended jungle [Cornelius] 1: +1/-0
1809 | o extended jungle [Cornelius] 1: +1/-0
1810 | |
1810 | |
1811 | o jungle [Zephir] 1: +1/-0
1811 | o jungle [Zephir] 1: +1/-0
1812 | |
1812 | |
1813 | o five [Arthur] 2: +2/-1
1813 | o five [Arthur] 2: +2/-1
1814 | |
1814 | |
1815 | o four [Rataxes] 1: +1/-1
1815 | o four [Rataxes] 1: +1/-1
1816 | |
1816 | |
1817 | o three [Celeste] 1: +1/-1
1817 | o three [Celeste] 1: +1/-1
1818 |/
1818 |/
1819 o initial [Babar] 2: +8/-0
1819 o initial [Babar] 2: +8/-0
1820
1820
1821 $ hg export
1821 $ hg export
1822 # HG changeset patch
1822 # HG changeset patch
1823 # User Rataxes
1823 # User Rataxes
1824 # Date 0 0
1824 # Date 0 0
1825 # Thu Jan 01 00:00:00 1970 +0000
1825 # Thu Jan 01 00:00:00 1970 +0000
1826 # Node ID a9d7b6d0ffbb4eb12b7d5939250fcd42e8930a1d
1826 # Node ID a9d7b6d0ffbb4eb12b7d5939250fcd42e8930a1d
1827 # Parent f59f8d2e95a8ca5b1b4ca64320140da85f3b44fd
1827 # Parent f59f8d2e95a8ca5b1b4ca64320140da85f3b44fd
1828 four
1828 four
1829
1829
1830 $ hg status -c .
1830 $ hg status -c .
1831 C a
1831 C a
1832 C b
1832 C b
1833
1833
1834 Importing some extra header
1834 Importing some extra header
1835 ===========================
1835 ===========================
1836
1836
1837 $ cat > $TESTTMP/parseextra.py <<EOF
1837 $ cat > $TESTTMP/parseextra.py <<EOF
1838 > import mercurial.patch
1838 > import mercurial.patch
1839 > import mercurial.cmdutil
1839 > import mercurial.cmdutil
1840 >
1840 >
1841 > def processfoo(repo, data, extra, opts):
1841 > def processfoo(repo, data, extra, opts):
1842 > if b'foo' in data:
1842 > if b'foo' in data:
1843 > extra[b'foo'] = data[b'foo']
1843 > extra[b'foo'] = data[b'foo']
1844 > def postimport(ctx):
1844 > def postimport(ctx):
1845 > if b'foo' in ctx.extra():
1845 > if b'foo' in ctx.extra():
1846 > ctx.repo().ui.write(b'imported-foo: %s\n' % ctx.extra()[b'foo'])
1846 > ctx.repo().ui.write(b'imported-foo: %s\n' % ctx.extra()[b'foo'])
1847 >
1847 >
1848 > mercurial.patch.patchheadermap.append((b'Foo', b'foo'))
1848 > mercurial.patch.patchheadermap.append((b'Foo', b'foo'))
1849 > mercurial.cmdutil.extrapreimport.append(b'foo')
1849 > mercurial.cmdutil.extrapreimport.append(b'foo')
1850 > mercurial.cmdutil.extrapreimportmap[b'foo'] = processfoo
1850 > mercurial.cmdutil.extrapreimportmap[b'foo'] = processfoo
1851 > mercurial.cmdutil.extrapostimport.append(b'foo')
1851 > mercurial.cmdutil.extrapostimport.append(b'foo')
1852 > mercurial.cmdutil.extrapostimportmap[b'foo'] = postimport
1852 > mercurial.cmdutil.extrapostimportmap[b'foo'] = postimport
1853 > EOF
1853 > EOF
1854 $ cat >> $HGRCPATH <<EOF
1854 $ cat >> $HGRCPATH <<EOF
1855 > [extensions]
1855 > [extensions]
1856 > parseextra=$TESTTMP/parseextra.py
1856 > parseextra=$TESTTMP/parseextra.py
1857 > EOF
1857 > EOF
1858 $ hg up -C tip
1858 $ hg up -C tip
1859 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1859 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1860 $ cat > $TESTTMP/foo.patch <<EOF
1860 $ cat > $TESTTMP/foo.patch <<EOF
1861 > # HG changeset patch
1861 > # HG changeset patch
1862 > # User Rataxes
1862 > # User Rataxes
1863 > # Date 0 0
1863 > # Date 0 0
1864 > # Thu Jan 01 00:00:00 1970 +0000
1864 > # Thu Jan 01 00:00:00 1970 +0000
1865 > # Foo bar
1865 > # Foo bar
1866 > height
1866 > height
1867 >
1867 >
1868 > --- a/a Thu Jan 01 00:00:00 1970 +0000
1868 > --- a/a Thu Jan 01 00:00:00 1970 +0000
1869 > +++ b/a Wed Oct 07 09:17:44 2015 +0000
1869 > +++ b/a Wed Oct 07 09:17:44 2015 +0000
1870 > @@ -5,3 +5,4 @@
1870 > @@ -5,3 +5,4 @@
1871 > five
1871 > five
1872 > six
1872 > six
1873 > seven
1873 > seven
1874 > +heigt
1874 > +heigt
1875 > EOF
1875 > EOF
1876 $ hg import $TESTTMP/foo.patch
1876 $ hg import $TESTTMP/foo.patch
1877 applying $TESTTMP/foo.patch
1877 applying $TESTTMP/foo.patch
1878 imported-foo: bar
1878 imported-foo: bar
1879 $ hg log --debug -r . | grep extra
1879 $ hg log --debug -r . | grep extra
1880 extra: branch=default
1880 extra: branch=default
1881 extra: foo=bar
1881 extra: foo=bar
1882
1882
1883 Warn the user that paths are relative to the root of
1883 Warn the user that paths are relative to the root of
1884 repository when file not found for patching
1884 repository when file not found for patching
1885
1885
1886 $ mkdir filedir
1886 $ mkdir filedir
1887 $ echo "file1" >> filedir/file1
1887 $ echo "file1" >> filedir/file1
1888 $ hg add filedir/file1
1888 $ hg add filedir/file1
1889 $ hg commit -m "file1"
1889 $ hg commit -m "file1"
1890 $ cd filedir
1890 $ cd filedir
1891 $ hg import -p 2 - <<EOF
1891 $ hg import -p 2 - <<EOF
1892 > # HG changeset patch
1892 > # HG changeset patch
1893 > # User test
1893 > # User test
1894 > # Date 0 0
1894 > # Date 0 0
1895 > file2
1895 > file2
1896 >
1896 >
1897 > diff --git a/filedir/file1 b/filedir/file1
1897 > diff --git a/filedir/file1 b/filedir/file1
1898 > --- a/filedir/file1
1898 > --- a/filedir/file1
1899 > +++ b/filedir/file1
1899 > +++ b/filedir/file1
1900 > @@ -1,1 +1,2 @@
1900 > @@ -1,1 +1,2 @@
1901 > file1
1901 > file1
1902 > +file2
1902 > +file2
1903 > EOF
1903 > EOF
1904 applying patch from stdin
1904 applying patch from stdin
1905 unable to find 'file1' for patching
1905 unable to find 'file1' for patching
1906 (use '--prefix' to apply patch relative to the current directory)
1906 (use '--prefix' to apply patch relative to the current directory)
1907 1 out of 1 hunks FAILED -- saving rejects to file file1.rej
1907 1 out of 1 hunks FAILED -- saving rejects to file file1.rej
1908 abort: patch failed to apply
1908 abort: patch failed to apply
1909 [255]
1909 [255]
1910
1910
1911 test import crash (issue5375)
1911 test import crash (issue5375)
1912 $ cd ..
1912 $ cd ..
1913 $ hg init repo
1913 $ hg init repo
1914 $ cd repo
1914 $ cd repo
1915 $ printf "diff --git a/a b/b\nrename from a\nrename to b" | hg import -
1915 $ printf "diff --git a/a b/b\nrename from a\nrename to b" | hg import -
1916 applying patch from stdin
1916 applying patch from stdin
1917 a not tracked!
1917 a not tracked!
1918 abort: source file 'a' does not exist
1918 abort: source file 'a' does not exist
1919 [255]
1919 [255]
1920
1921 test immature end of hunk
1922
1923 $ hg import - <<'EOF'
1924 > diff --git a/foo b/foo
1925 > --- a/foo
1926 > --- b/foo
1927 > @@ -0,0 +1,1 @@
1928 > EOF
1929 applying patch from stdin
1930 abort: bad hunk #1: incomplete hunk
1931 [255]
1932
1933 $ hg import - <<'EOF'
1934 > diff --git a/foo b/foo
1935 > --- a/foo
1936 > --- b/foo
1937 > @@ -0,0 +1,1 @@
1938 > \ No newline at end of file
1939 > EOF
1940 applying patch from stdin
1941 abort: bad hunk #1: incomplete hunk
1942 [255]
General Comments 0
You need to be logged in to leave comments. Login now