##// END OF EJS Templates
patch.pathtransform: prepend prefix even if strip is 0...
Siddharth Agarwal -
r24385:885a573f default
parent child Browse files
Show More
@@ -1,2422 +1,2424 b''
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import cStringIO, email, os, errno, re, posixpath, copy
9 import cStringIO, email, os, errno, re, posixpath, copy
10 import tempfile, zlib, shutil
10 import tempfile, zlib, shutil
11 # On python2.4 you have to import these by name or they fail to
11 # On python2.4 you have to import these by name or they fail to
12 # load. This was not a problem on Python 2.7.
12 # load. This was not a problem on Python 2.7.
13 import email.Generator
13 import email.Generator
14 import email.Parser
14 import email.Parser
15
15
16 from i18n import _
16 from i18n import _
17 from node import hex, short
17 from node import hex, short
18 import cStringIO
18 import cStringIO
19 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
19 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
20
20
21 gitre = re.compile('diff --git a/(.*) b/(.*)')
21 gitre = re.compile('diff --git a/(.*) b/(.*)')
22 tabsplitter = re.compile(r'(\t+|[^\t]+)')
22 tabsplitter = re.compile(r'(\t+|[^\t]+)')
23
23
24 class PatchError(Exception):
24 class PatchError(Exception):
25 pass
25 pass
26
26
27
27
28 # public functions
28 # public functions
29
29
30 def split(stream):
30 def split(stream):
31 '''return an iterator of individual patches from a stream'''
31 '''return an iterator of individual patches from a stream'''
32 def isheader(line, inheader):
32 def isheader(line, inheader):
33 if inheader and line[0] in (' ', '\t'):
33 if inheader and line[0] in (' ', '\t'):
34 # continuation
34 # continuation
35 return True
35 return True
36 if line[0] in (' ', '-', '+'):
36 if line[0] in (' ', '-', '+'):
37 # diff line - don't check for header pattern in there
37 # diff line - don't check for header pattern in there
38 return False
38 return False
39 l = line.split(': ', 1)
39 l = line.split(': ', 1)
40 return len(l) == 2 and ' ' not in l[0]
40 return len(l) == 2 and ' ' not in l[0]
41
41
42 def chunk(lines):
42 def chunk(lines):
43 return cStringIO.StringIO(''.join(lines))
43 return cStringIO.StringIO(''.join(lines))
44
44
45 def hgsplit(stream, cur):
45 def hgsplit(stream, cur):
46 inheader = True
46 inheader = True
47
47
48 for line in stream:
48 for line in stream:
49 if not line.strip():
49 if not line.strip():
50 inheader = False
50 inheader = False
51 if not inheader and line.startswith('# HG changeset patch'):
51 if not inheader and line.startswith('# HG changeset patch'):
52 yield chunk(cur)
52 yield chunk(cur)
53 cur = []
53 cur = []
54 inheader = True
54 inheader = True
55
55
56 cur.append(line)
56 cur.append(line)
57
57
58 if cur:
58 if cur:
59 yield chunk(cur)
59 yield chunk(cur)
60
60
61 def mboxsplit(stream, cur):
61 def mboxsplit(stream, cur):
62 for line in stream:
62 for line in stream:
63 if line.startswith('From '):
63 if line.startswith('From '):
64 for c in split(chunk(cur[1:])):
64 for c in split(chunk(cur[1:])):
65 yield c
65 yield c
66 cur = []
66 cur = []
67
67
68 cur.append(line)
68 cur.append(line)
69
69
70 if cur:
70 if cur:
71 for c in split(chunk(cur[1:])):
71 for c in split(chunk(cur[1:])):
72 yield c
72 yield c
73
73
74 def mimesplit(stream, cur):
74 def mimesplit(stream, cur):
75 def msgfp(m):
75 def msgfp(m):
76 fp = cStringIO.StringIO()
76 fp = cStringIO.StringIO()
77 g = email.Generator.Generator(fp, mangle_from_=False)
77 g = email.Generator.Generator(fp, mangle_from_=False)
78 g.flatten(m)
78 g.flatten(m)
79 fp.seek(0)
79 fp.seek(0)
80 return fp
80 return fp
81
81
82 for line in stream:
82 for line in stream:
83 cur.append(line)
83 cur.append(line)
84 c = chunk(cur)
84 c = chunk(cur)
85
85
86 m = email.Parser.Parser().parse(c)
86 m = email.Parser.Parser().parse(c)
87 if not m.is_multipart():
87 if not m.is_multipart():
88 yield msgfp(m)
88 yield msgfp(m)
89 else:
89 else:
90 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
90 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
91 for part in m.walk():
91 for part in m.walk():
92 ct = part.get_content_type()
92 ct = part.get_content_type()
93 if ct not in ok_types:
93 if ct not in ok_types:
94 continue
94 continue
95 yield msgfp(part)
95 yield msgfp(part)
96
96
97 def headersplit(stream, cur):
97 def headersplit(stream, cur):
98 inheader = False
98 inheader = False
99
99
100 for line in stream:
100 for line in stream:
101 if not inheader and isheader(line, inheader):
101 if not inheader and isheader(line, inheader):
102 yield chunk(cur)
102 yield chunk(cur)
103 cur = []
103 cur = []
104 inheader = True
104 inheader = True
105 if inheader and not isheader(line, inheader):
105 if inheader and not isheader(line, inheader):
106 inheader = False
106 inheader = False
107
107
108 cur.append(line)
108 cur.append(line)
109
109
110 if cur:
110 if cur:
111 yield chunk(cur)
111 yield chunk(cur)
112
112
113 def remainder(cur):
113 def remainder(cur):
114 yield chunk(cur)
114 yield chunk(cur)
115
115
116 class fiter(object):
116 class fiter(object):
117 def __init__(self, fp):
117 def __init__(self, fp):
118 self.fp = fp
118 self.fp = fp
119
119
120 def __iter__(self):
120 def __iter__(self):
121 return self
121 return self
122
122
123 def next(self):
123 def next(self):
124 l = self.fp.readline()
124 l = self.fp.readline()
125 if not l:
125 if not l:
126 raise StopIteration
126 raise StopIteration
127 return l
127 return l
128
128
129 inheader = False
129 inheader = False
130 cur = []
130 cur = []
131
131
132 mimeheaders = ['content-type']
132 mimeheaders = ['content-type']
133
133
134 if not util.safehasattr(stream, 'next'):
134 if not util.safehasattr(stream, 'next'):
135 # http responses, for example, have readline but not next
135 # http responses, for example, have readline but not next
136 stream = fiter(stream)
136 stream = fiter(stream)
137
137
138 for line in stream:
138 for line in stream:
139 cur.append(line)
139 cur.append(line)
140 if line.startswith('# HG changeset patch'):
140 if line.startswith('# HG changeset patch'):
141 return hgsplit(stream, cur)
141 return hgsplit(stream, cur)
142 elif line.startswith('From '):
142 elif line.startswith('From '):
143 return mboxsplit(stream, cur)
143 return mboxsplit(stream, cur)
144 elif isheader(line, inheader):
144 elif isheader(line, inheader):
145 inheader = True
145 inheader = True
146 if line.split(':', 1)[0].lower() in mimeheaders:
146 if line.split(':', 1)[0].lower() in mimeheaders:
147 # let email parser handle this
147 # let email parser handle this
148 return mimesplit(stream, cur)
148 return mimesplit(stream, cur)
149 elif line.startswith('--- ') and inheader:
149 elif line.startswith('--- ') and inheader:
150 # No evil headers seen by diff start, split by hand
150 # No evil headers seen by diff start, split by hand
151 return headersplit(stream, cur)
151 return headersplit(stream, cur)
152 # Not enough info, keep reading
152 # Not enough info, keep reading
153
153
154 # if we are here, we have a very plain patch
154 # if we are here, we have a very plain patch
155 return remainder(cur)
155 return remainder(cur)
156
156
157 def extract(ui, fileobj):
157 def extract(ui, fileobj):
158 '''extract patch from data read from fileobj.
158 '''extract patch from data read from fileobj.
159
159
160 patch can be a normal patch or contained in an email message.
160 patch can be a normal patch or contained in an email message.
161
161
162 return tuple (filename, message, user, date, branch, node, p1, p2).
162 return tuple (filename, message, user, date, branch, node, p1, p2).
163 Any item in the returned tuple can be None. If filename is None,
163 Any item in the returned tuple can be None. If filename is None,
164 fileobj did not contain a patch. Caller must unlink filename when done.'''
164 fileobj did not contain a patch. Caller must unlink filename when done.'''
165
165
166 # attempt to detect the start of a patch
166 # attempt to detect the start of a patch
167 # (this heuristic is borrowed from quilt)
167 # (this heuristic is borrowed from quilt)
168 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
168 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
169 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
169 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
170 r'---[ \t].*?^\+\+\+[ \t]|'
170 r'---[ \t].*?^\+\+\+[ \t]|'
171 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
171 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
172
172
173 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
173 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
174 tmpfp = os.fdopen(fd, 'w')
174 tmpfp = os.fdopen(fd, 'w')
175 try:
175 try:
176 msg = email.Parser.Parser().parse(fileobj)
176 msg = email.Parser.Parser().parse(fileobj)
177
177
178 subject = msg['Subject']
178 subject = msg['Subject']
179 user = msg['From']
179 user = msg['From']
180 if not subject and not user:
180 if not subject and not user:
181 # Not an email, restore parsed headers if any
181 # Not an email, restore parsed headers if any
182 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
182 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
183
183
184 # should try to parse msg['Date']
184 # should try to parse msg['Date']
185 date = None
185 date = None
186 nodeid = None
186 nodeid = None
187 branch = None
187 branch = None
188 parents = []
188 parents = []
189
189
190 if subject:
190 if subject:
191 if subject.startswith('[PATCH'):
191 if subject.startswith('[PATCH'):
192 pend = subject.find(']')
192 pend = subject.find(']')
193 if pend >= 0:
193 if pend >= 0:
194 subject = subject[pend + 1:].lstrip()
194 subject = subject[pend + 1:].lstrip()
195 subject = re.sub(r'\n[ \t]+', ' ', subject)
195 subject = re.sub(r'\n[ \t]+', ' ', subject)
196 ui.debug('Subject: %s\n' % subject)
196 ui.debug('Subject: %s\n' % subject)
197 if user:
197 if user:
198 ui.debug('From: %s\n' % user)
198 ui.debug('From: %s\n' % user)
199 diffs_seen = 0
199 diffs_seen = 0
200 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
200 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
201 message = ''
201 message = ''
202 for part in msg.walk():
202 for part in msg.walk():
203 content_type = part.get_content_type()
203 content_type = part.get_content_type()
204 ui.debug('Content-Type: %s\n' % content_type)
204 ui.debug('Content-Type: %s\n' % content_type)
205 if content_type not in ok_types:
205 if content_type not in ok_types:
206 continue
206 continue
207 payload = part.get_payload(decode=True)
207 payload = part.get_payload(decode=True)
208 m = diffre.search(payload)
208 m = diffre.search(payload)
209 if m:
209 if m:
210 hgpatch = False
210 hgpatch = False
211 hgpatchheader = False
211 hgpatchheader = False
212 ignoretext = False
212 ignoretext = False
213
213
214 ui.debug('found patch at byte %d\n' % m.start(0))
214 ui.debug('found patch at byte %d\n' % m.start(0))
215 diffs_seen += 1
215 diffs_seen += 1
216 cfp = cStringIO.StringIO()
216 cfp = cStringIO.StringIO()
217 for line in payload[:m.start(0)].splitlines():
217 for line in payload[:m.start(0)].splitlines():
218 if line.startswith('# HG changeset patch') and not hgpatch:
218 if line.startswith('# HG changeset patch') and not hgpatch:
219 ui.debug('patch generated by hg export\n')
219 ui.debug('patch generated by hg export\n')
220 hgpatch = True
220 hgpatch = True
221 hgpatchheader = True
221 hgpatchheader = True
222 # drop earlier commit message content
222 # drop earlier commit message content
223 cfp.seek(0)
223 cfp.seek(0)
224 cfp.truncate()
224 cfp.truncate()
225 subject = None
225 subject = None
226 elif hgpatchheader:
226 elif hgpatchheader:
227 if line.startswith('# User '):
227 if line.startswith('# User '):
228 user = line[7:]
228 user = line[7:]
229 ui.debug('From: %s\n' % user)
229 ui.debug('From: %s\n' % user)
230 elif line.startswith("# Date "):
230 elif line.startswith("# Date "):
231 date = line[7:]
231 date = line[7:]
232 elif line.startswith("# Branch "):
232 elif line.startswith("# Branch "):
233 branch = line[9:]
233 branch = line[9:]
234 elif line.startswith("# Node ID "):
234 elif line.startswith("# Node ID "):
235 nodeid = line[10:]
235 nodeid = line[10:]
236 elif line.startswith("# Parent "):
236 elif line.startswith("# Parent "):
237 parents.append(line[9:].lstrip())
237 parents.append(line[9:].lstrip())
238 elif not line.startswith("# "):
238 elif not line.startswith("# "):
239 hgpatchheader = False
239 hgpatchheader = False
240 elif line == '---':
240 elif line == '---':
241 ignoretext = True
241 ignoretext = True
242 if not hgpatchheader and not ignoretext:
242 if not hgpatchheader and not ignoretext:
243 cfp.write(line)
243 cfp.write(line)
244 cfp.write('\n')
244 cfp.write('\n')
245 message = cfp.getvalue()
245 message = cfp.getvalue()
246 if tmpfp:
246 if tmpfp:
247 tmpfp.write(payload)
247 tmpfp.write(payload)
248 if not payload.endswith('\n'):
248 if not payload.endswith('\n'):
249 tmpfp.write('\n')
249 tmpfp.write('\n')
250 elif not diffs_seen and message and content_type == 'text/plain':
250 elif not diffs_seen and message and content_type == 'text/plain':
251 message += '\n' + payload
251 message += '\n' + payload
252 except: # re-raises
252 except: # re-raises
253 tmpfp.close()
253 tmpfp.close()
254 os.unlink(tmpname)
254 os.unlink(tmpname)
255 raise
255 raise
256
256
257 if subject and not message.startswith(subject):
257 if subject and not message.startswith(subject):
258 message = '%s\n%s' % (subject, message)
258 message = '%s\n%s' % (subject, message)
259 tmpfp.close()
259 tmpfp.close()
260 if not diffs_seen:
260 if not diffs_seen:
261 os.unlink(tmpname)
261 os.unlink(tmpname)
262 return None, message, user, date, branch, None, None, None
262 return None, message, user, date, branch, None, None, None
263
263
264 if parents:
264 if parents:
265 p1 = parents.pop(0)
265 p1 = parents.pop(0)
266 else:
266 else:
267 p1 = None
267 p1 = None
268
268
269 if parents:
269 if parents:
270 p2 = parents.pop(0)
270 p2 = parents.pop(0)
271 else:
271 else:
272 p2 = None
272 p2 = None
273
273
274 return tmpname, message, user, date, branch, nodeid, p1, p2
274 return tmpname, message, user, date, branch, nodeid, p1, p2
275
275
276 class patchmeta(object):
276 class patchmeta(object):
277 """Patched file metadata
277 """Patched file metadata
278
278
279 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
279 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
280 or COPY. 'path' is patched file path. 'oldpath' is set to the
280 or COPY. 'path' is patched file path. 'oldpath' is set to the
281 origin file when 'op' is either COPY or RENAME, None otherwise. If
281 origin file when 'op' is either COPY or RENAME, None otherwise. If
282 file mode is changed, 'mode' is a tuple (islink, isexec) where
282 file mode is changed, 'mode' is a tuple (islink, isexec) where
283 'islink' is True if the file is a symlink and 'isexec' is True if
283 'islink' is True if the file is a symlink and 'isexec' is True if
284 the file is executable. Otherwise, 'mode' is None.
284 the file is executable. Otherwise, 'mode' is None.
285 """
285 """
286 def __init__(self, path):
286 def __init__(self, path):
287 self.path = path
287 self.path = path
288 self.oldpath = None
288 self.oldpath = None
289 self.mode = None
289 self.mode = None
290 self.op = 'MODIFY'
290 self.op = 'MODIFY'
291 self.binary = False
291 self.binary = False
292
292
293 def setmode(self, mode):
293 def setmode(self, mode):
294 islink = mode & 020000
294 islink = mode & 020000
295 isexec = mode & 0100
295 isexec = mode & 0100
296 self.mode = (islink, isexec)
296 self.mode = (islink, isexec)
297
297
298 def copy(self):
298 def copy(self):
299 other = patchmeta(self.path)
299 other = patchmeta(self.path)
300 other.oldpath = self.oldpath
300 other.oldpath = self.oldpath
301 other.mode = self.mode
301 other.mode = self.mode
302 other.op = self.op
302 other.op = self.op
303 other.binary = self.binary
303 other.binary = self.binary
304 return other
304 return other
305
305
306 def _ispatchinga(self, afile):
306 def _ispatchinga(self, afile):
307 if afile == '/dev/null':
307 if afile == '/dev/null':
308 return self.op == 'ADD'
308 return self.op == 'ADD'
309 return afile == 'a/' + (self.oldpath or self.path)
309 return afile == 'a/' + (self.oldpath or self.path)
310
310
311 def _ispatchingb(self, bfile):
311 def _ispatchingb(self, bfile):
312 if bfile == '/dev/null':
312 if bfile == '/dev/null':
313 return self.op == 'DELETE'
313 return self.op == 'DELETE'
314 return bfile == 'b/' + self.path
314 return bfile == 'b/' + self.path
315
315
316 def ispatching(self, afile, bfile):
316 def ispatching(self, afile, bfile):
317 return self._ispatchinga(afile) and self._ispatchingb(bfile)
317 return self._ispatchinga(afile) and self._ispatchingb(bfile)
318
318
319 def __repr__(self):
319 def __repr__(self):
320 return "<patchmeta %s %r>" % (self.op, self.path)
320 return "<patchmeta %s %r>" % (self.op, self.path)
321
321
322 def readgitpatch(lr):
322 def readgitpatch(lr):
323 """extract git-style metadata about patches from <patchname>"""
323 """extract git-style metadata about patches from <patchname>"""
324
324
325 # Filter patch for git information
325 # Filter patch for git information
326 gp = None
326 gp = None
327 gitpatches = []
327 gitpatches = []
328 for line in lr:
328 for line in lr:
329 line = line.rstrip(' \r\n')
329 line = line.rstrip(' \r\n')
330 if line.startswith('diff --git a/'):
330 if line.startswith('diff --git a/'):
331 m = gitre.match(line)
331 m = gitre.match(line)
332 if m:
332 if m:
333 if gp:
333 if gp:
334 gitpatches.append(gp)
334 gitpatches.append(gp)
335 dst = m.group(2)
335 dst = m.group(2)
336 gp = patchmeta(dst)
336 gp = patchmeta(dst)
337 elif gp:
337 elif gp:
338 if line.startswith('--- '):
338 if line.startswith('--- '):
339 gitpatches.append(gp)
339 gitpatches.append(gp)
340 gp = None
340 gp = None
341 continue
341 continue
342 if line.startswith('rename from '):
342 if line.startswith('rename from '):
343 gp.op = 'RENAME'
343 gp.op = 'RENAME'
344 gp.oldpath = line[12:]
344 gp.oldpath = line[12:]
345 elif line.startswith('rename to '):
345 elif line.startswith('rename to '):
346 gp.path = line[10:]
346 gp.path = line[10:]
347 elif line.startswith('copy from '):
347 elif line.startswith('copy from '):
348 gp.op = 'COPY'
348 gp.op = 'COPY'
349 gp.oldpath = line[10:]
349 gp.oldpath = line[10:]
350 elif line.startswith('copy to '):
350 elif line.startswith('copy to '):
351 gp.path = line[8:]
351 gp.path = line[8:]
352 elif line.startswith('deleted file'):
352 elif line.startswith('deleted file'):
353 gp.op = 'DELETE'
353 gp.op = 'DELETE'
354 elif line.startswith('new file mode '):
354 elif line.startswith('new file mode '):
355 gp.op = 'ADD'
355 gp.op = 'ADD'
356 gp.setmode(int(line[-6:], 8))
356 gp.setmode(int(line[-6:], 8))
357 elif line.startswith('new mode '):
357 elif line.startswith('new mode '):
358 gp.setmode(int(line[-6:], 8))
358 gp.setmode(int(line[-6:], 8))
359 elif line.startswith('GIT binary patch'):
359 elif line.startswith('GIT binary patch'):
360 gp.binary = True
360 gp.binary = True
361 if gp:
361 if gp:
362 gitpatches.append(gp)
362 gitpatches.append(gp)
363
363
364 return gitpatches
364 return gitpatches
365
365
366 class linereader(object):
366 class linereader(object):
367 # simple class to allow pushing lines back into the input stream
367 # simple class to allow pushing lines back into the input stream
368 def __init__(self, fp):
368 def __init__(self, fp):
369 self.fp = fp
369 self.fp = fp
370 self.buf = []
370 self.buf = []
371
371
372 def push(self, line):
372 def push(self, line):
373 if line is not None:
373 if line is not None:
374 self.buf.append(line)
374 self.buf.append(line)
375
375
376 def readline(self):
376 def readline(self):
377 if self.buf:
377 if self.buf:
378 l = self.buf[0]
378 l = self.buf[0]
379 del self.buf[0]
379 del self.buf[0]
380 return l
380 return l
381 return self.fp.readline()
381 return self.fp.readline()
382
382
383 def __iter__(self):
383 def __iter__(self):
384 while True:
384 while True:
385 l = self.readline()
385 l = self.readline()
386 if not l:
386 if not l:
387 break
387 break
388 yield l
388 yield l
389
389
390 class abstractbackend(object):
390 class abstractbackend(object):
391 def __init__(self, ui):
391 def __init__(self, ui):
392 self.ui = ui
392 self.ui = ui
393
393
394 def getfile(self, fname):
394 def getfile(self, fname):
395 """Return target file data and flags as a (data, (islink,
395 """Return target file data and flags as a (data, (islink,
396 isexec)) tuple. Data is None if file is missing/deleted.
396 isexec)) tuple. Data is None if file is missing/deleted.
397 """
397 """
398 raise NotImplementedError
398 raise NotImplementedError
399
399
400 def setfile(self, fname, data, mode, copysource):
400 def setfile(self, fname, data, mode, copysource):
401 """Write data to target file fname and set its mode. mode is a
401 """Write data to target file fname and set its mode. mode is a
402 (islink, isexec) tuple. If data is None, the file content should
402 (islink, isexec) tuple. If data is None, the file content should
403 be left unchanged. If the file is modified after being copied,
403 be left unchanged. If the file is modified after being copied,
404 copysource is set to the original file name.
404 copysource is set to the original file name.
405 """
405 """
406 raise NotImplementedError
406 raise NotImplementedError
407
407
408 def unlink(self, fname):
408 def unlink(self, fname):
409 """Unlink target file."""
409 """Unlink target file."""
410 raise NotImplementedError
410 raise NotImplementedError
411
411
412 def writerej(self, fname, failed, total, lines):
412 def writerej(self, fname, failed, total, lines):
413 """Write rejected lines for fname. total is the number of hunks
413 """Write rejected lines for fname. total is the number of hunks
414 which failed to apply and total the total number of hunks for this
414 which failed to apply and total the total number of hunks for this
415 files.
415 files.
416 """
416 """
417 pass
417 pass
418
418
419 def exists(self, fname):
419 def exists(self, fname):
420 raise NotImplementedError
420 raise NotImplementedError
421
421
422 class fsbackend(abstractbackend):
422 class fsbackend(abstractbackend):
423 def __init__(self, ui, basedir):
423 def __init__(self, ui, basedir):
424 super(fsbackend, self).__init__(ui)
424 super(fsbackend, self).__init__(ui)
425 self.opener = scmutil.opener(basedir)
425 self.opener = scmutil.opener(basedir)
426
426
427 def _join(self, f):
427 def _join(self, f):
428 return os.path.join(self.opener.base, f)
428 return os.path.join(self.opener.base, f)
429
429
430 def getfile(self, fname):
430 def getfile(self, fname):
431 if self.opener.islink(fname):
431 if self.opener.islink(fname):
432 return (self.opener.readlink(fname), (True, False))
432 return (self.opener.readlink(fname), (True, False))
433
433
434 isexec = False
434 isexec = False
435 try:
435 try:
436 isexec = self.opener.lstat(fname).st_mode & 0100 != 0
436 isexec = self.opener.lstat(fname).st_mode & 0100 != 0
437 except OSError, e:
437 except OSError, e:
438 if e.errno != errno.ENOENT:
438 if e.errno != errno.ENOENT:
439 raise
439 raise
440 try:
440 try:
441 return (self.opener.read(fname), (False, isexec))
441 return (self.opener.read(fname), (False, isexec))
442 except IOError, e:
442 except IOError, e:
443 if e.errno != errno.ENOENT:
443 if e.errno != errno.ENOENT:
444 raise
444 raise
445 return None, None
445 return None, None
446
446
447 def setfile(self, fname, data, mode, copysource):
447 def setfile(self, fname, data, mode, copysource):
448 islink, isexec = mode
448 islink, isexec = mode
449 if data is None:
449 if data is None:
450 self.opener.setflags(fname, islink, isexec)
450 self.opener.setflags(fname, islink, isexec)
451 return
451 return
452 if islink:
452 if islink:
453 self.opener.symlink(data, fname)
453 self.opener.symlink(data, fname)
454 else:
454 else:
455 self.opener.write(fname, data)
455 self.opener.write(fname, data)
456 if isexec:
456 if isexec:
457 self.opener.setflags(fname, False, True)
457 self.opener.setflags(fname, False, True)
458
458
459 def unlink(self, fname):
459 def unlink(self, fname):
460 self.opener.unlinkpath(fname, ignoremissing=True)
460 self.opener.unlinkpath(fname, ignoremissing=True)
461
461
462 def writerej(self, fname, failed, total, lines):
462 def writerej(self, fname, failed, total, lines):
463 fname = fname + ".rej"
463 fname = fname + ".rej"
464 self.ui.warn(
464 self.ui.warn(
465 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
465 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
466 (failed, total, fname))
466 (failed, total, fname))
467 fp = self.opener(fname, 'w')
467 fp = self.opener(fname, 'w')
468 fp.writelines(lines)
468 fp.writelines(lines)
469 fp.close()
469 fp.close()
470
470
471 def exists(self, fname):
471 def exists(self, fname):
472 return self.opener.lexists(fname)
472 return self.opener.lexists(fname)
473
473
474 class workingbackend(fsbackend):
474 class workingbackend(fsbackend):
475 def __init__(self, ui, repo, similarity):
475 def __init__(self, ui, repo, similarity):
476 super(workingbackend, self).__init__(ui, repo.root)
476 super(workingbackend, self).__init__(ui, repo.root)
477 self.repo = repo
477 self.repo = repo
478 self.similarity = similarity
478 self.similarity = similarity
479 self.removed = set()
479 self.removed = set()
480 self.changed = set()
480 self.changed = set()
481 self.copied = []
481 self.copied = []
482
482
483 def _checkknown(self, fname):
483 def _checkknown(self, fname):
484 if self.repo.dirstate[fname] == '?' and self.exists(fname):
484 if self.repo.dirstate[fname] == '?' and self.exists(fname):
485 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
485 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
486
486
487 def setfile(self, fname, data, mode, copysource):
487 def setfile(self, fname, data, mode, copysource):
488 self._checkknown(fname)
488 self._checkknown(fname)
489 super(workingbackend, self).setfile(fname, data, mode, copysource)
489 super(workingbackend, self).setfile(fname, data, mode, copysource)
490 if copysource is not None:
490 if copysource is not None:
491 self.copied.append((copysource, fname))
491 self.copied.append((copysource, fname))
492 self.changed.add(fname)
492 self.changed.add(fname)
493
493
494 def unlink(self, fname):
494 def unlink(self, fname):
495 self._checkknown(fname)
495 self._checkknown(fname)
496 super(workingbackend, self).unlink(fname)
496 super(workingbackend, self).unlink(fname)
497 self.removed.add(fname)
497 self.removed.add(fname)
498 self.changed.add(fname)
498 self.changed.add(fname)
499
499
500 def close(self):
500 def close(self):
501 wctx = self.repo[None]
501 wctx = self.repo[None]
502 changed = set(self.changed)
502 changed = set(self.changed)
503 for src, dst in self.copied:
503 for src, dst in self.copied:
504 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
504 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
505 if self.removed:
505 if self.removed:
506 wctx.forget(sorted(self.removed))
506 wctx.forget(sorted(self.removed))
507 for f in self.removed:
507 for f in self.removed:
508 if f not in self.repo.dirstate:
508 if f not in self.repo.dirstate:
509 # File was deleted and no longer belongs to the
509 # File was deleted and no longer belongs to the
510 # dirstate, it was probably marked added then
510 # dirstate, it was probably marked added then
511 # deleted, and should not be considered by
511 # deleted, and should not be considered by
512 # marktouched().
512 # marktouched().
513 changed.discard(f)
513 changed.discard(f)
514 if changed:
514 if changed:
515 scmutil.marktouched(self.repo, changed, self.similarity)
515 scmutil.marktouched(self.repo, changed, self.similarity)
516 return sorted(self.changed)
516 return sorted(self.changed)
517
517
518 class filestore(object):
518 class filestore(object):
519 def __init__(self, maxsize=None):
519 def __init__(self, maxsize=None):
520 self.opener = None
520 self.opener = None
521 self.files = {}
521 self.files = {}
522 self.created = 0
522 self.created = 0
523 self.maxsize = maxsize
523 self.maxsize = maxsize
524 if self.maxsize is None:
524 if self.maxsize is None:
525 self.maxsize = 4*(2**20)
525 self.maxsize = 4*(2**20)
526 self.size = 0
526 self.size = 0
527 self.data = {}
527 self.data = {}
528
528
529 def setfile(self, fname, data, mode, copied=None):
529 def setfile(self, fname, data, mode, copied=None):
530 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
530 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
531 self.data[fname] = (data, mode, copied)
531 self.data[fname] = (data, mode, copied)
532 self.size += len(data)
532 self.size += len(data)
533 else:
533 else:
534 if self.opener is None:
534 if self.opener is None:
535 root = tempfile.mkdtemp(prefix='hg-patch-')
535 root = tempfile.mkdtemp(prefix='hg-patch-')
536 self.opener = scmutil.opener(root)
536 self.opener = scmutil.opener(root)
537 # Avoid filename issues with these simple names
537 # Avoid filename issues with these simple names
538 fn = str(self.created)
538 fn = str(self.created)
539 self.opener.write(fn, data)
539 self.opener.write(fn, data)
540 self.created += 1
540 self.created += 1
541 self.files[fname] = (fn, mode, copied)
541 self.files[fname] = (fn, mode, copied)
542
542
543 def getfile(self, fname):
543 def getfile(self, fname):
544 if fname in self.data:
544 if fname in self.data:
545 return self.data[fname]
545 return self.data[fname]
546 if not self.opener or fname not in self.files:
546 if not self.opener or fname not in self.files:
547 return None, None, None
547 return None, None, None
548 fn, mode, copied = self.files[fname]
548 fn, mode, copied = self.files[fname]
549 return self.opener.read(fn), mode, copied
549 return self.opener.read(fn), mode, copied
550
550
551 def close(self):
551 def close(self):
552 if self.opener:
552 if self.opener:
553 shutil.rmtree(self.opener.base)
553 shutil.rmtree(self.opener.base)
554
554
555 class repobackend(abstractbackend):
555 class repobackend(abstractbackend):
556 def __init__(self, ui, repo, ctx, store):
556 def __init__(self, ui, repo, ctx, store):
557 super(repobackend, self).__init__(ui)
557 super(repobackend, self).__init__(ui)
558 self.repo = repo
558 self.repo = repo
559 self.ctx = ctx
559 self.ctx = ctx
560 self.store = store
560 self.store = store
561 self.changed = set()
561 self.changed = set()
562 self.removed = set()
562 self.removed = set()
563 self.copied = {}
563 self.copied = {}
564
564
565 def _checkknown(self, fname):
565 def _checkknown(self, fname):
566 if fname not in self.ctx:
566 if fname not in self.ctx:
567 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
567 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
568
568
569 def getfile(self, fname):
569 def getfile(self, fname):
570 try:
570 try:
571 fctx = self.ctx[fname]
571 fctx = self.ctx[fname]
572 except error.LookupError:
572 except error.LookupError:
573 return None, None
573 return None, None
574 flags = fctx.flags()
574 flags = fctx.flags()
575 return fctx.data(), ('l' in flags, 'x' in flags)
575 return fctx.data(), ('l' in flags, 'x' in flags)
576
576
577 def setfile(self, fname, data, mode, copysource):
577 def setfile(self, fname, data, mode, copysource):
578 if copysource:
578 if copysource:
579 self._checkknown(copysource)
579 self._checkknown(copysource)
580 if data is None:
580 if data is None:
581 data = self.ctx[fname].data()
581 data = self.ctx[fname].data()
582 self.store.setfile(fname, data, mode, copysource)
582 self.store.setfile(fname, data, mode, copysource)
583 self.changed.add(fname)
583 self.changed.add(fname)
584 if copysource:
584 if copysource:
585 self.copied[fname] = copysource
585 self.copied[fname] = copysource
586
586
587 def unlink(self, fname):
587 def unlink(self, fname):
588 self._checkknown(fname)
588 self._checkknown(fname)
589 self.removed.add(fname)
589 self.removed.add(fname)
590
590
591 def exists(self, fname):
591 def exists(self, fname):
592 return fname in self.ctx
592 return fname in self.ctx
593
593
594 def close(self):
594 def close(self):
595 return self.changed | self.removed
595 return self.changed | self.removed
596
596
597 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
597 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
598 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
598 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
599 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
599 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
600 eolmodes = ['strict', 'crlf', 'lf', 'auto']
600 eolmodes = ['strict', 'crlf', 'lf', 'auto']
601
601
602 class patchfile(object):
602 class patchfile(object):
603 def __init__(self, ui, gp, backend, store, eolmode='strict'):
603 def __init__(self, ui, gp, backend, store, eolmode='strict'):
604 self.fname = gp.path
604 self.fname = gp.path
605 self.eolmode = eolmode
605 self.eolmode = eolmode
606 self.eol = None
606 self.eol = None
607 self.backend = backend
607 self.backend = backend
608 self.ui = ui
608 self.ui = ui
609 self.lines = []
609 self.lines = []
610 self.exists = False
610 self.exists = False
611 self.missing = True
611 self.missing = True
612 self.mode = gp.mode
612 self.mode = gp.mode
613 self.copysource = gp.oldpath
613 self.copysource = gp.oldpath
614 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
614 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
615 self.remove = gp.op == 'DELETE'
615 self.remove = gp.op == 'DELETE'
616 if self.copysource is None:
616 if self.copysource is None:
617 data, mode = backend.getfile(self.fname)
617 data, mode = backend.getfile(self.fname)
618 else:
618 else:
619 data, mode = store.getfile(self.copysource)[:2]
619 data, mode = store.getfile(self.copysource)[:2]
620 if data is not None:
620 if data is not None:
621 self.exists = self.copysource is None or backend.exists(self.fname)
621 self.exists = self.copysource is None or backend.exists(self.fname)
622 self.missing = False
622 self.missing = False
623 if data:
623 if data:
624 self.lines = mdiff.splitnewlines(data)
624 self.lines = mdiff.splitnewlines(data)
625 if self.mode is None:
625 if self.mode is None:
626 self.mode = mode
626 self.mode = mode
627 if self.lines:
627 if self.lines:
628 # Normalize line endings
628 # Normalize line endings
629 if self.lines[0].endswith('\r\n'):
629 if self.lines[0].endswith('\r\n'):
630 self.eol = '\r\n'
630 self.eol = '\r\n'
631 elif self.lines[0].endswith('\n'):
631 elif self.lines[0].endswith('\n'):
632 self.eol = '\n'
632 self.eol = '\n'
633 if eolmode != 'strict':
633 if eolmode != 'strict':
634 nlines = []
634 nlines = []
635 for l in self.lines:
635 for l in self.lines:
636 if l.endswith('\r\n'):
636 if l.endswith('\r\n'):
637 l = l[:-2] + '\n'
637 l = l[:-2] + '\n'
638 nlines.append(l)
638 nlines.append(l)
639 self.lines = nlines
639 self.lines = nlines
640 else:
640 else:
641 if self.create:
641 if self.create:
642 self.missing = False
642 self.missing = False
643 if self.mode is None:
643 if self.mode is None:
644 self.mode = (False, False)
644 self.mode = (False, False)
645 if self.missing:
645 if self.missing:
646 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
646 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
647
647
648 self.hash = {}
648 self.hash = {}
649 self.dirty = 0
649 self.dirty = 0
650 self.offset = 0
650 self.offset = 0
651 self.skew = 0
651 self.skew = 0
652 self.rej = []
652 self.rej = []
653 self.fileprinted = False
653 self.fileprinted = False
654 self.printfile(False)
654 self.printfile(False)
655 self.hunks = 0
655 self.hunks = 0
656
656
657 def writelines(self, fname, lines, mode):
657 def writelines(self, fname, lines, mode):
658 if self.eolmode == 'auto':
658 if self.eolmode == 'auto':
659 eol = self.eol
659 eol = self.eol
660 elif self.eolmode == 'crlf':
660 elif self.eolmode == 'crlf':
661 eol = '\r\n'
661 eol = '\r\n'
662 else:
662 else:
663 eol = '\n'
663 eol = '\n'
664
664
665 if self.eolmode != 'strict' and eol and eol != '\n':
665 if self.eolmode != 'strict' and eol and eol != '\n':
666 rawlines = []
666 rawlines = []
667 for l in lines:
667 for l in lines:
668 if l and l[-1] == '\n':
668 if l and l[-1] == '\n':
669 l = l[:-1] + eol
669 l = l[:-1] + eol
670 rawlines.append(l)
670 rawlines.append(l)
671 lines = rawlines
671 lines = rawlines
672
672
673 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
673 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
674
674
675 def printfile(self, warn):
675 def printfile(self, warn):
676 if self.fileprinted:
676 if self.fileprinted:
677 return
677 return
678 if warn or self.ui.verbose:
678 if warn or self.ui.verbose:
679 self.fileprinted = True
679 self.fileprinted = True
680 s = _("patching file %s\n") % self.fname
680 s = _("patching file %s\n") % self.fname
681 if warn:
681 if warn:
682 self.ui.warn(s)
682 self.ui.warn(s)
683 else:
683 else:
684 self.ui.note(s)
684 self.ui.note(s)
685
685
686
686
687 def findlines(self, l, linenum):
687 def findlines(self, l, linenum):
688 # looks through the hash and finds candidate lines. The
688 # looks through the hash and finds candidate lines. The
689 # result is a list of line numbers sorted based on distance
689 # result is a list of line numbers sorted based on distance
690 # from linenum
690 # from linenum
691
691
692 cand = self.hash.get(l, [])
692 cand = self.hash.get(l, [])
693 if len(cand) > 1:
693 if len(cand) > 1:
694 # resort our list of potentials forward then back.
694 # resort our list of potentials forward then back.
695 cand.sort(key=lambda x: abs(x - linenum))
695 cand.sort(key=lambda x: abs(x - linenum))
696 return cand
696 return cand
697
697
698 def write_rej(self):
698 def write_rej(self):
699 # our rejects are a little different from patch(1). This always
699 # our rejects are a little different from patch(1). This always
700 # creates rejects in the same form as the original patch. A file
700 # creates rejects in the same form as the original patch. A file
701 # header is inserted so that you can run the reject through patch again
701 # header is inserted so that you can run the reject through patch again
702 # without having to type the filename.
702 # without having to type the filename.
703 if not self.rej:
703 if not self.rej:
704 return
704 return
705 base = os.path.basename(self.fname)
705 base = os.path.basename(self.fname)
706 lines = ["--- %s\n+++ %s\n" % (base, base)]
706 lines = ["--- %s\n+++ %s\n" % (base, base)]
707 for x in self.rej:
707 for x in self.rej:
708 for l in x.hunk:
708 for l in x.hunk:
709 lines.append(l)
709 lines.append(l)
710 if l[-1] != '\n':
710 if l[-1] != '\n':
711 lines.append("\n\ No newline at end of file\n")
711 lines.append("\n\ No newline at end of file\n")
712 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
712 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
713
713
714 def apply(self, h):
714 def apply(self, h):
715 if not h.complete():
715 if not h.complete():
716 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
716 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
717 (h.number, h.desc, len(h.a), h.lena, len(h.b),
717 (h.number, h.desc, len(h.a), h.lena, len(h.b),
718 h.lenb))
718 h.lenb))
719
719
720 self.hunks += 1
720 self.hunks += 1
721
721
722 if self.missing:
722 if self.missing:
723 self.rej.append(h)
723 self.rej.append(h)
724 return -1
724 return -1
725
725
726 if self.exists and self.create:
726 if self.exists and self.create:
727 if self.copysource:
727 if self.copysource:
728 self.ui.warn(_("cannot create %s: destination already "
728 self.ui.warn(_("cannot create %s: destination already "
729 "exists\n") % self.fname)
729 "exists\n") % self.fname)
730 else:
730 else:
731 self.ui.warn(_("file %s already exists\n") % self.fname)
731 self.ui.warn(_("file %s already exists\n") % self.fname)
732 self.rej.append(h)
732 self.rej.append(h)
733 return -1
733 return -1
734
734
735 if isinstance(h, binhunk):
735 if isinstance(h, binhunk):
736 if self.remove:
736 if self.remove:
737 self.backend.unlink(self.fname)
737 self.backend.unlink(self.fname)
738 else:
738 else:
739 l = h.new(self.lines)
739 l = h.new(self.lines)
740 self.lines[:] = l
740 self.lines[:] = l
741 self.offset += len(l)
741 self.offset += len(l)
742 self.dirty = True
742 self.dirty = True
743 return 0
743 return 0
744
744
745 horig = h
745 horig = h
746 if (self.eolmode in ('crlf', 'lf')
746 if (self.eolmode in ('crlf', 'lf')
747 or self.eolmode == 'auto' and self.eol):
747 or self.eolmode == 'auto' and self.eol):
748 # If new eols are going to be normalized, then normalize
748 # If new eols are going to be normalized, then normalize
749 # hunk data before patching. Otherwise, preserve input
749 # hunk data before patching. Otherwise, preserve input
750 # line-endings.
750 # line-endings.
751 h = h.getnormalized()
751 h = h.getnormalized()
752
752
753 # fast case first, no offsets, no fuzz
753 # fast case first, no offsets, no fuzz
754 old, oldstart, new, newstart = h.fuzzit(0, False)
754 old, oldstart, new, newstart = h.fuzzit(0, False)
755 oldstart += self.offset
755 oldstart += self.offset
756 orig_start = oldstart
756 orig_start = oldstart
757 # if there's skew we want to emit the "(offset %d lines)" even
757 # if there's skew we want to emit the "(offset %d lines)" even
758 # when the hunk cleanly applies at start + skew, so skip the
758 # when the hunk cleanly applies at start + skew, so skip the
759 # fast case code
759 # fast case code
760 if (self.skew == 0 and
760 if (self.skew == 0 and
761 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
761 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
762 if self.remove:
762 if self.remove:
763 self.backend.unlink(self.fname)
763 self.backend.unlink(self.fname)
764 else:
764 else:
765 self.lines[oldstart:oldstart + len(old)] = new
765 self.lines[oldstart:oldstart + len(old)] = new
766 self.offset += len(new) - len(old)
766 self.offset += len(new) - len(old)
767 self.dirty = True
767 self.dirty = True
768 return 0
768 return 0
769
769
770 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
770 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
771 self.hash = {}
771 self.hash = {}
772 for x, s in enumerate(self.lines):
772 for x, s in enumerate(self.lines):
773 self.hash.setdefault(s, []).append(x)
773 self.hash.setdefault(s, []).append(x)
774
774
775 for fuzzlen in xrange(3):
775 for fuzzlen in xrange(3):
776 for toponly in [True, False]:
776 for toponly in [True, False]:
777 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
777 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
778 oldstart = oldstart + self.offset + self.skew
778 oldstart = oldstart + self.offset + self.skew
779 oldstart = min(oldstart, len(self.lines))
779 oldstart = min(oldstart, len(self.lines))
780 if old:
780 if old:
781 cand = self.findlines(old[0][1:], oldstart)
781 cand = self.findlines(old[0][1:], oldstart)
782 else:
782 else:
783 # Only adding lines with no or fuzzed context, just
783 # Only adding lines with no or fuzzed context, just
784 # take the skew in account
784 # take the skew in account
785 cand = [oldstart]
785 cand = [oldstart]
786
786
787 for l in cand:
787 for l in cand:
788 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
788 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
789 self.lines[l : l + len(old)] = new
789 self.lines[l : l + len(old)] = new
790 self.offset += len(new) - len(old)
790 self.offset += len(new) - len(old)
791 self.skew = l - orig_start
791 self.skew = l - orig_start
792 self.dirty = True
792 self.dirty = True
793 offset = l - orig_start - fuzzlen
793 offset = l - orig_start - fuzzlen
794 if fuzzlen:
794 if fuzzlen:
795 msg = _("Hunk #%d succeeded at %d "
795 msg = _("Hunk #%d succeeded at %d "
796 "with fuzz %d "
796 "with fuzz %d "
797 "(offset %d lines).\n")
797 "(offset %d lines).\n")
798 self.printfile(True)
798 self.printfile(True)
799 self.ui.warn(msg %
799 self.ui.warn(msg %
800 (h.number, l + 1, fuzzlen, offset))
800 (h.number, l + 1, fuzzlen, offset))
801 else:
801 else:
802 msg = _("Hunk #%d succeeded at %d "
802 msg = _("Hunk #%d succeeded at %d "
803 "(offset %d lines).\n")
803 "(offset %d lines).\n")
804 self.ui.note(msg % (h.number, l + 1, offset))
804 self.ui.note(msg % (h.number, l + 1, offset))
805 return fuzzlen
805 return fuzzlen
806 self.printfile(True)
806 self.printfile(True)
807 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
807 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
808 self.rej.append(horig)
808 self.rej.append(horig)
809 return -1
809 return -1
810
810
811 def close(self):
811 def close(self):
812 if self.dirty:
812 if self.dirty:
813 self.writelines(self.fname, self.lines, self.mode)
813 self.writelines(self.fname, self.lines, self.mode)
814 self.write_rej()
814 self.write_rej()
815 return len(self.rej)
815 return len(self.rej)
816
816
817 class header(object):
817 class header(object):
818 """patch header
818 """patch header
819 """
819 """
820 diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
820 diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
821 diff_re = re.compile('diff -r .* (.*)$')
821 diff_re = re.compile('diff -r .* (.*)$')
822 allhunks_re = re.compile('(?:index|deleted file) ')
822 allhunks_re = re.compile('(?:index|deleted file) ')
823 pretty_re = re.compile('(?:new file|deleted file) ')
823 pretty_re = re.compile('(?:new file|deleted file) ')
824 special_re = re.compile('(?:index|new|deleted|copy|rename) ')
824 special_re = re.compile('(?:index|new|deleted|copy|rename) ')
825
825
826 def __init__(self, header):
826 def __init__(self, header):
827 self.header = header
827 self.header = header
828 self.hunks = []
828 self.hunks = []
829
829
830 def binary(self):
830 def binary(self):
831 return util.any(h.startswith('index ') for h in self.header)
831 return util.any(h.startswith('index ') for h in self.header)
832
832
833 def pretty(self, fp):
833 def pretty(self, fp):
834 for h in self.header:
834 for h in self.header:
835 if h.startswith('index '):
835 if h.startswith('index '):
836 fp.write(_('this modifies a binary file (all or nothing)\n'))
836 fp.write(_('this modifies a binary file (all or nothing)\n'))
837 break
837 break
838 if self.pretty_re.match(h):
838 if self.pretty_re.match(h):
839 fp.write(h)
839 fp.write(h)
840 if self.binary():
840 if self.binary():
841 fp.write(_('this is a binary file\n'))
841 fp.write(_('this is a binary file\n'))
842 break
842 break
843 if h.startswith('---'):
843 if h.startswith('---'):
844 fp.write(_('%d hunks, %d lines changed\n') %
844 fp.write(_('%d hunks, %d lines changed\n') %
845 (len(self.hunks),
845 (len(self.hunks),
846 sum([max(h.added, h.removed) for h in self.hunks])))
846 sum([max(h.added, h.removed) for h in self.hunks])))
847 break
847 break
848 fp.write(h)
848 fp.write(h)
849
849
850 def write(self, fp):
850 def write(self, fp):
851 fp.write(''.join(self.header))
851 fp.write(''.join(self.header))
852
852
853 def allhunks(self):
853 def allhunks(self):
854 return util.any(self.allhunks_re.match(h) for h in self.header)
854 return util.any(self.allhunks_re.match(h) for h in self.header)
855
855
856 def files(self):
856 def files(self):
857 match = self.diffgit_re.match(self.header[0])
857 match = self.diffgit_re.match(self.header[0])
858 if match:
858 if match:
859 fromfile, tofile = match.groups()
859 fromfile, tofile = match.groups()
860 if fromfile == tofile:
860 if fromfile == tofile:
861 return [fromfile]
861 return [fromfile]
862 return [fromfile, tofile]
862 return [fromfile, tofile]
863 else:
863 else:
864 return self.diff_re.match(self.header[0]).groups()
864 return self.diff_re.match(self.header[0]).groups()
865
865
866 def filename(self):
866 def filename(self):
867 return self.files()[-1]
867 return self.files()[-1]
868
868
869 def __repr__(self):
869 def __repr__(self):
870 return '<header %s>' % (' '.join(map(repr, self.files())))
870 return '<header %s>' % (' '.join(map(repr, self.files())))
871
871
872 def special(self):
872 def special(self):
873 return util.any(self.special_re.match(h) for h in self.header)
873 return util.any(self.special_re.match(h) for h in self.header)
874
874
875 class recordhunk(object):
875 class recordhunk(object):
876 """patch hunk
876 """patch hunk
877
877
878 XXX shouldn't we merge this with the other hunk class?
878 XXX shouldn't we merge this with the other hunk class?
879 """
879 """
880 maxcontext = 3
880 maxcontext = 3
881
881
882 def __init__(self, header, fromline, toline, proc, before, hunk, after):
882 def __init__(self, header, fromline, toline, proc, before, hunk, after):
883 def trimcontext(number, lines):
883 def trimcontext(number, lines):
884 delta = len(lines) - self.maxcontext
884 delta = len(lines) - self.maxcontext
885 if False and delta > 0:
885 if False and delta > 0:
886 return number + delta, lines[:self.maxcontext]
886 return number + delta, lines[:self.maxcontext]
887 return number, lines
887 return number, lines
888
888
889 self.header = header
889 self.header = header
890 self.fromline, self.before = trimcontext(fromline, before)
890 self.fromline, self.before = trimcontext(fromline, before)
891 self.toline, self.after = trimcontext(toline, after)
891 self.toline, self.after = trimcontext(toline, after)
892 self.proc = proc
892 self.proc = proc
893 self.hunk = hunk
893 self.hunk = hunk
894 self.added, self.removed = self.countchanges(self.hunk)
894 self.added, self.removed = self.countchanges(self.hunk)
895
895
896 def __eq__(self, v):
896 def __eq__(self, v):
897 if not isinstance(v, recordhunk):
897 if not isinstance(v, recordhunk):
898 return False
898 return False
899
899
900 return ((v.hunk == self.hunk) and
900 return ((v.hunk == self.hunk) and
901 (v.proc == self.proc) and
901 (v.proc == self.proc) and
902 (self.fromline == v.fromline) and
902 (self.fromline == v.fromline) and
903 (self.header.files() == v.header.files()))
903 (self.header.files() == v.header.files()))
904
904
905 def __hash__(self):
905 def __hash__(self):
906 return hash((tuple(self.hunk),
906 return hash((tuple(self.hunk),
907 tuple(self.header.files()),
907 tuple(self.header.files()),
908 self.fromline,
908 self.fromline,
909 self.proc))
909 self.proc))
910
910
911 def countchanges(self, hunk):
911 def countchanges(self, hunk):
912 """hunk -> (n+,n-)"""
912 """hunk -> (n+,n-)"""
913 add = len([h for h in hunk if h[0] == '+'])
913 add = len([h for h in hunk if h[0] == '+'])
914 rem = len([h for h in hunk if h[0] == '-'])
914 rem = len([h for h in hunk if h[0] == '-'])
915 return add, rem
915 return add, rem
916
916
917 def write(self, fp):
917 def write(self, fp):
918 delta = len(self.before) + len(self.after)
918 delta = len(self.before) + len(self.after)
919 if self.after and self.after[-1] == '\\ No newline at end of file\n':
919 if self.after and self.after[-1] == '\\ No newline at end of file\n':
920 delta -= 1
920 delta -= 1
921 fromlen = delta + self.removed
921 fromlen = delta + self.removed
922 tolen = delta + self.added
922 tolen = delta + self.added
923 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
923 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
924 (self.fromline, fromlen, self.toline, tolen,
924 (self.fromline, fromlen, self.toline, tolen,
925 self.proc and (' ' + self.proc)))
925 self.proc and (' ' + self.proc)))
926 fp.write(''.join(self.before + self.hunk + self.after))
926 fp.write(''.join(self.before + self.hunk + self.after))
927
927
928 pretty = write
928 pretty = write
929
929
930 def filename(self):
930 def filename(self):
931 return self.header.filename()
931 return self.header.filename()
932
932
933 def __repr__(self):
933 def __repr__(self):
934 return '<hunk %r@%d>' % (self.filename(), self.fromline)
934 return '<hunk %r@%d>' % (self.filename(), self.fromline)
935
935
936 def filterpatch(ui, headers):
936 def filterpatch(ui, headers):
937 """Interactively filter patch chunks into applied-only chunks"""
937 """Interactively filter patch chunks into applied-only chunks"""
938
938
939 def prompt(skipfile, skipall, query, chunk):
939 def prompt(skipfile, skipall, query, chunk):
940 """prompt query, and process base inputs
940 """prompt query, and process base inputs
941
941
942 - y/n for the rest of file
942 - y/n for the rest of file
943 - y/n for the rest
943 - y/n for the rest
944 - ? (help)
944 - ? (help)
945 - q (quit)
945 - q (quit)
946
946
947 Return True/False and possibly updated skipfile and skipall.
947 Return True/False and possibly updated skipfile and skipall.
948 """
948 """
949 newpatches = None
949 newpatches = None
950 if skipall is not None:
950 if skipall is not None:
951 return skipall, skipfile, skipall, newpatches
951 return skipall, skipfile, skipall, newpatches
952 if skipfile is not None:
952 if skipfile is not None:
953 return skipfile, skipfile, skipall, newpatches
953 return skipfile, skipfile, skipall, newpatches
954 while True:
954 while True:
955 resps = _('[Ynesfdaq?]'
955 resps = _('[Ynesfdaq?]'
956 '$$ &Yes, record this change'
956 '$$ &Yes, record this change'
957 '$$ &No, skip this change'
957 '$$ &No, skip this change'
958 '$$ &Edit this change manually'
958 '$$ &Edit this change manually'
959 '$$ &Skip remaining changes to this file'
959 '$$ &Skip remaining changes to this file'
960 '$$ Record remaining changes to this &file'
960 '$$ Record remaining changes to this &file'
961 '$$ &Done, skip remaining changes and files'
961 '$$ &Done, skip remaining changes and files'
962 '$$ Record &all changes to all remaining files'
962 '$$ Record &all changes to all remaining files'
963 '$$ &Quit, recording no changes'
963 '$$ &Quit, recording no changes'
964 '$$ &? (display help)')
964 '$$ &? (display help)')
965 r = ui.promptchoice("%s %s" % (query, resps))
965 r = ui.promptchoice("%s %s" % (query, resps))
966 ui.write("\n")
966 ui.write("\n")
967 if r == 8: # ?
967 if r == 8: # ?
968 for c, t in ui.extractchoices(resps)[1]:
968 for c, t in ui.extractchoices(resps)[1]:
969 ui.write('%s - %s\n' % (c, t.lower()))
969 ui.write('%s - %s\n' % (c, t.lower()))
970 continue
970 continue
971 elif r == 0: # yes
971 elif r == 0: # yes
972 ret = True
972 ret = True
973 elif r == 1: # no
973 elif r == 1: # no
974 ret = False
974 ret = False
975 elif r == 2: # Edit patch
975 elif r == 2: # Edit patch
976 if chunk is None:
976 if chunk is None:
977 ui.write(_('cannot edit patch for whole file'))
977 ui.write(_('cannot edit patch for whole file'))
978 ui.write("\n")
978 ui.write("\n")
979 continue
979 continue
980 if chunk.header.binary():
980 if chunk.header.binary():
981 ui.write(_('cannot edit patch for binary file'))
981 ui.write(_('cannot edit patch for binary file'))
982 ui.write("\n")
982 ui.write("\n")
983 continue
983 continue
984 # Patch comment based on the Git one (based on comment at end of
984 # Patch comment based on the Git one (based on comment at end of
985 # http://mercurial.selenic.com/wiki/RecordExtension)
985 # http://mercurial.selenic.com/wiki/RecordExtension)
986 phelp = '---' + _("""
986 phelp = '---' + _("""
987 To remove '-' lines, make them ' ' lines (context).
987 To remove '-' lines, make them ' ' lines (context).
988 To remove '+' lines, delete them.
988 To remove '+' lines, delete them.
989 Lines starting with # will be removed from the patch.
989 Lines starting with # will be removed from the patch.
990
990
991 If the patch applies cleanly, the edited hunk will immediately be
991 If the patch applies cleanly, the edited hunk will immediately be
992 added to the record list. If it does not apply cleanly, a rejects
992 added to the record list. If it does not apply cleanly, a rejects
993 file will be generated: you can use that when you try again. If
993 file will be generated: you can use that when you try again. If
994 all lines of the hunk are removed, then the edit is aborted and
994 all lines of the hunk are removed, then the edit is aborted and
995 the hunk is left unchanged.
995 the hunk is left unchanged.
996 """)
996 """)
997 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
997 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
998 suffix=".diff", text=True)
998 suffix=".diff", text=True)
999 ncpatchfp = None
999 ncpatchfp = None
1000 try:
1000 try:
1001 # Write the initial patch
1001 # Write the initial patch
1002 f = os.fdopen(patchfd, "w")
1002 f = os.fdopen(patchfd, "w")
1003 chunk.header.write(f)
1003 chunk.header.write(f)
1004 chunk.write(f)
1004 chunk.write(f)
1005 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
1005 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
1006 f.close()
1006 f.close()
1007 # Start the editor and wait for it to complete
1007 # Start the editor and wait for it to complete
1008 editor = ui.geteditor()
1008 editor = ui.geteditor()
1009 ui.system("%s \"%s\"" % (editor, patchfn),
1009 ui.system("%s \"%s\"" % (editor, patchfn),
1010 environ={'HGUSER': ui.username()},
1010 environ={'HGUSER': ui.username()},
1011 onerr=util.Abort, errprefix=_("edit failed"))
1011 onerr=util.Abort, errprefix=_("edit failed"))
1012 # Remove comment lines
1012 # Remove comment lines
1013 patchfp = open(patchfn)
1013 patchfp = open(patchfn)
1014 ncpatchfp = cStringIO.StringIO()
1014 ncpatchfp = cStringIO.StringIO()
1015 for line in patchfp:
1015 for line in patchfp:
1016 if not line.startswith('#'):
1016 if not line.startswith('#'):
1017 ncpatchfp.write(line)
1017 ncpatchfp.write(line)
1018 patchfp.close()
1018 patchfp.close()
1019 ncpatchfp.seek(0)
1019 ncpatchfp.seek(0)
1020 newpatches = parsepatch(ncpatchfp)
1020 newpatches = parsepatch(ncpatchfp)
1021 finally:
1021 finally:
1022 os.unlink(patchfn)
1022 os.unlink(patchfn)
1023 del ncpatchfp
1023 del ncpatchfp
1024 # Signal that the chunk shouldn't be applied as-is, but
1024 # Signal that the chunk shouldn't be applied as-is, but
1025 # provide the new patch to be used instead.
1025 # provide the new patch to be used instead.
1026 ret = False
1026 ret = False
1027 elif r == 3: # Skip
1027 elif r == 3: # Skip
1028 ret = skipfile = False
1028 ret = skipfile = False
1029 elif r == 4: # file (Record remaining)
1029 elif r == 4: # file (Record remaining)
1030 ret = skipfile = True
1030 ret = skipfile = True
1031 elif r == 5: # done, skip remaining
1031 elif r == 5: # done, skip remaining
1032 ret = skipall = False
1032 ret = skipall = False
1033 elif r == 6: # all
1033 elif r == 6: # all
1034 ret = skipall = True
1034 ret = skipall = True
1035 elif r == 7: # quit
1035 elif r == 7: # quit
1036 raise util.Abort(_('user quit'))
1036 raise util.Abort(_('user quit'))
1037 return ret, skipfile, skipall, newpatches
1037 return ret, skipfile, skipall, newpatches
1038
1038
1039 seen = set()
1039 seen = set()
1040 applied = {} # 'filename' -> [] of chunks
1040 applied = {} # 'filename' -> [] of chunks
1041 skipfile, skipall = None, None
1041 skipfile, skipall = None, None
1042 pos, total = 1, sum(len(h.hunks) for h in headers)
1042 pos, total = 1, sum(len(h.hunks) for h in headers)
1043 for h in headers:
1043 for h in headers:
1044 pos += len(h.hunks)
1044 pos += len(h.hunks)
1045 skipfile = None
1045 skipfile = None
1046 fixoffset = 0
1046 fixoffset = 0
1047 hdr = ''.join(h.header)
1047 hdr = ''.join(h.header)
1048 if hdr in seen:
1048 if hdr in seen:
1049 continue
1049 continue
1050 seen.add(hdr)
1050 seen.add(hdr)
1051 if skipall is None:
1051 if skipall is None:
1052 h.pretty(ui)
1052 h.pretty(ui)
1053 msg = (_('examine changes to %s?') %
1053 msg = (_('examine changes to %s?') %
1054 _(' and ').join("'%s'" % f for f in h.files()))
1054 _(' and ').join("'%s'" % f for f in h.files()))
1055 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
1055 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
1056 if not r:
1056 if not r:
1057 continue
1057 continue
1058 applied[h.filename()] = [h]
1058 applied[h.filename()] = [h]
1059 if h.allhunks():
1059 if h.allhunks():
1060 applied[h.filename()] += h.hunks
1060 applied[h.filename()] += h.hunks
1061 continue
1061 continue
1062 for i, chunk in enumerate(h.hunks):
1062 for i, chunk in enumerate(h.hunks):
1063 if skipfile is None and skipall is None:
1063 if skipfile is None and skipall is None:
1064 chunk.pretty(ui)
1064 chunk.pretty(ui)
1065 if total == 1:
1065 if total == 1:
1066 msg = _("record this change to '%s'?") % chunk.filename()
1066 msg = _("record this change to '%s'?") % chunk.filename()
1067 else:
1067 else:
1068 idx = pos - len(h.hunks) + i
1068 idx = pos - len(h.hunks) + i
1069 msg = _("record change %d/%d to '%s'?") % (idx, total,
1069 msg = _("record change %d/%d to '%s'?") % (idx, total,
1070 chunk.filename())
1070 chunk.filename())
1071 r, skipfile, skipall, newpatches = prompt(skipfile,
1071 r, skipfile, skipall, newpatches = prompt(skipfile,
1072 skipall, msg, chunk)
1072 skipall, msg, chunk)
1073 if r:
1073 if r:
1074 if fixoffset:
1074 if fixoffset:
1075 chunk = copy.copy(chunk)
1075 chunk = copy.copy(chunk)
1076 chunk.toline += fixoffset
1076 chunk.toline += fixoffset
1077 applied[chunk.filename()].append(chunk)
1077 applied[chunk.filename()].append(chunk)
1078 elif newpatches is not None:
1078 elif newpatches is not None:
1079 for newpatch in newpatches:
1079 for newpatch in newpatches:
1080 for newhunk in newpatch.hunks:
1080 for newhunk in newpatch.hunks:
1081 if fixoffset:
1081 if fixoffset:
1082 newhunk.toline += fixoffset
1082 newhunk.toline += fixoffset
1083 applied[newhunk.filename()].append(newhunk)
1083 applied[newhunk.filename()].append(newhunk)
1084 else:
1084 else:
1085 fixoffset += chunk.removed - chunk.added
1085 fixoffset += chunk.removed - chunk.added
1086 return sum([h for h in applied.itervalues()
1086 return sum([h for h in applied.itervalues()
1087 if h[0].special() or len(h) > 1], [])
1087 if h[0].special() or len(h) > 1], [])
1088 class hunk(object):
1088 class hunk(object):
1089 def __init__(self, desc, num, lr, context):
1089 def __init__(self, desc, num, lr, context):
1090 self.number = num
1090 self.number = num
1091 self.desc = desc
1091 self.desc = desc
1092 self.hunk = [desc]
1092 self.hunk = [desc]
1093 self.a = []
1093 self.a = []
1094 self.b = []
1094 self.b = []
1095 self.starta = self.lena = None
1095 self.starta = self.lena = None
1096 self.startb = self.lenb = None
1096 self.startb = self.lenb = None
1097 if lr is not None:
1097 if lr is not None:
1098 if context:
1098 if context:
1099 self.read_context_hunk(lr)
1099 self.read_context_hunk(lr)
1100 else:
1100 else:
1101 self.read_unified_hunk(lr)
1101 self.read_unified_hunk(lr)
1102
1102
1103 def getnormalized(self):
1103 def getnormalized(self):
1104 """Return a copy with line endings normalized to LF."""
1104 """Return a copy with line endings normalized to LF."""
1105
1105
1106 def normalize(lines):
1106 def normalize(lines):
1107 nlines = []
1107 nlines = []
1108 for line in lines:
1108 for line in lines:
1109 if line.endswith('\r\n'):
1109 if line.endswith('\r\n'):
1110 line = line[:-2] + '\n'
1110 line = line[:-2] + '\n'
1111 nlines.append(line)
1111 nlines.append(line)
1112 return nlines
1112 return nlines
1113
1113
1114 # Dummy object, it is rebuilt manually
1114 # Dummy object, it is rebuilt manually
1115 nh = hunk(self.desc, self.number, None, None)
1115 nh = hunk(self.desc, self.number, None, None)
1116 nh.number = self.number
1116 nh.number = self.number
1117 nh.desc = self.desc
1117 nh.desc = self.desc
1118 nh.hunk = self.hunk
1118 nh.hunk = self.hunk
1119 nh.a = normalize(self.a)
1119 nh.a = normalize(self.a)
1120 nh.b = normalize(self.b)
1120 nh.b = normalize(self.b)
1121 nh.starta = self.starta
1121 nh.starta = self.starta
1122 nh.startb = self.startb
1122 nh.startb = self.startb
1123 nh.lena = self.lena
1123 nh.lena = self.lena
1124 nh.lenb = self.lenb
1124 nh.lenb = self.lenb
1125 return nh
1125 return nh
1126
1126
1127 def read_unified_hunk(self, lr):
1127 def read_unified_hunk(self, lr):
1128 m = unidesc.match(self.desc)
1128 m = unidesc.match(self.desc)
1129 if not m:
1129 if not m:
1130 raise PatchError(_("bad hunk #%d") % self.number)
1130 raise PatchError(_("bad hunk #%d") % self.number)
1131 self.starta, self.lena, self.startb, self.lenb = m.groups()
1131 self.starta, self.lena, self.startb, self.lenb = m.groups()
1132 if self.lena is None:
1132 if self.lena is None:
1133 self.lena = 1
1133 self.lena = 1
1134 else:
1134 else:
1135 self.lena = int(self.lena)
1135 self.lena = int(self.lena)
1136 if self.lenb is None:
1136 if self.lenb is None:
1137 self.lenb = 1
1137 self.lenb = 1
1138 else:
1138 else:
1139 self.lenb = int(self.lenb)
1139 self.lenb = int(self.lenb)
1140 self.starta = int(self.starta)
1140 self.starta = int(self.starta)
1141 self.startb = int(self.startb)
1141 self.startb = int(self.startb)
1142 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a,
1142 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a,
1143 self.b)
1143 self.b)
1144 # if we hit eof before finishing out the hunk, the last line will
1144 # if we hit eof before finishing out the hunk, the last line will
1145 # be zero length. Lets try to fix it up.
1145 # be zero length. Lets try to fix it up.
1146 while len(self.hunk[-1]) == 0:
1146 while len(self.hunk[-1]) == 0:
1147 del self.hunk[-1]
1147 del self.hunk[-1]
1148 del self.a[-1]
1148 del self.a[-1]
1149 del self.b[-1]
1149 del self.b[-1]
1150 self.lena -= 1
1150 self.lena -= 1
1151 self.lenb -= 1
1151 self.lenb -= 1
1152 self._fixnewline(lr)
1152 self._fixnewline(lr)
1153
1153
1154 def read_context_hunk(self, lr):
1154 def read_context_hunk(self, lr):
1155 self.desc = lr.readline()
1155 self.desc = lr.readline()
1156 m = contextdesc.match(self.desc)
1156 m = contextdesc.match(self.desc)
1157 if not m:
1157 if not m:
1158 raise PatchError(_("bad hunk #%d") % self.number)
1158 raise PatchError(_("bad hunk #%d") % self.number)
1159 self.starta, aend = m.groups()
1159 self.starta, aend = m.groups()
1160 self.starta = int(self.starta)
1160 self.starta = int(self.starta)
1161 if aend is None:
1161 if aend is None:
1162 aend = self.starta
1162 aend = self.starta
1163 self.lena = int(aend) - self.starta
1163 self.lena = int(aend) - self.starta
1164 if self.starta:
1164 if self.starta:
1165 self.lena += 1
1165 self.lena += 1
1166 for x in xrange(self.lena):
1166 for x in xrange(self.lena):
1167 l = lr.readline()
1167 l = lr.readline()
1168 if l.startswith('---'):
1168 if l.startswith('---'):
1169 # lines addition, old block is empty
1169 # lines addition, old block is empty
1170 lr.push(l)
1170 lr.push(l)
1171 break
1171 break
1172 s = l[2:]
1172 s = l[2:]
1173 if l.startswith('- ') or l.startswith('! '):
1173 if l.startswith('- ') or l.startswith('! '):
1174 u = '-' + s
1174 u = '-' + s
1175 elif l.startswith(' '):
1175 elif l.startswith(' '):
1176 u = ' ' + s
1176 u = ' ' + s
1177 else:
1177 else:
1178 raise PatchError(_("bad hunk #%d old text line %d") %
1178 raise PatchError(_("bad hunk #%d old text line %d") %
1179 (self.number, x))
1179 (self.number, x))
1180 self.a.append(u)
1180 self.a.append(u)
1181 self.hunk.append(u)
1181 self.hunk.append(u)
1182
1182
1183 l = lr.readline()
1183 l = lr.readline()
1184 if l.startswith('\ '):
1184 if l.startswith('\ '):
1185 s = self.a[-1][:-1]
1185 s = self.a[-1][:-1]
1186 self.a[-1] = s
1186 self.a[-1] = s
1187 self.hunk[-1] = s
1187 self.hunk[-1] = s
1188 l = lr.readline()
1188 l = lr.readline()
1189 m = contextdesc.match(l)
1189 m = contextdesc.match(l)
1190 if not m:
1190 if not m:
1191 raise PatchError(_("bad hunk #%d") % self.number)
1191 raise PatchError(_("bad hunk #%d") % self.number)
1192 self.startb, bend = m.groups()
1192 self.startb, bend = m.groups()
1193 self.startb = int(self.startb)
1193 self.startb = int(self.startb)
1194 if bend is None:
1194 if bend is None:
1195 bend = self.startb
1195 bend = self.startb
1196 self.lenb = int(bend) - self.startb
1196 self.lenb = int(bend) - self.startb
1197 if self.startb:
1197 if self.startb:
1198 self.lenb += 1
1198 self.lenb += 1
1199 hunki = 1
1199 hunki = 1
1200 for x in xrange(self.lenb):
1200 for x in xrange(self.lenb):
1201 l = lr.readline()
1201 l = lr.readline()
1202 if l.startswith('\ '):
1202 if l.startswith('\ '):
1203 # XXX: the only way to hit this is with an invalid line range.
1203 # XXX: the only way to hit this is with an invalid line range.
1204 # The no-eol marker is not counted in the line range, but I
1204 # The no-eol marker is not counted in the line range, but I
1205 # guess there are diff(1) out there which behave differently.
1205 # guess there are diff(1) out there which behave differently.
1206 s = self.b[-1][:-1]
1206 s = self.b[-1][:-1]
1207 self.b[-1] = s
1207 self.b[-1] = s
1208 self.hunk[hunki - 1] = s
1208 self.hunk[hunki - 1] = s
1209 continue
1209 continue
1210 if not l:
1210 if not l:
1211 # line deletions, new block is empty and we hit EOF
1211 # line deletions, new block is empty and we hit EOF
1212 lr.push(l)
1212 lr.push(l)
1213 break
1213 break
1214 s = l[2:]
1214 s = l[2:]
1215 if l.startswith('+ ') or l.startswith('! '):
1215 if l.startswith('+ ') or l.startswith('! '):
1216 u = '+' + s
1216 u = '+' + s
1217 elif l.startswith(' '):
1217 elif l.startswith(' '):
1218 u = ' ' + s
1218 u = ' ' + s
1219 elif len(self.b) == 0:
1219 elif len(self.b) == 0:
1220 # line deletions, new block is empty
1220 # line deletions, new block is empty
1221 lr.push(l)
1221 lr.push(l)
1222 break
1222 break
1223 else:
1223 else:
1224 raise PatchError(_("bad hunk #%d old text line %d") %
1224 raise PatchError(_("bad hunk #%d old text line %d") %
1225 (self.number, x))
1225 (self.number, x))
1226 self.b.append(s)
1226 self.b.append(s)
1227 while True:
1227 while True:
1228 if hunki >= len(self.hunk):
1228 if hunki >= len(self.hunk):
1229 h = ""
1229 h = ""
1230 else:
1230 else:
1231 h = self.hunk[hunki]
1231 h = self.hunk[hunki]
1232 hunki += 1
1232 hunki += 1
1233 if h == u:
1233 if h == u:
1234 break
1234 break
1235 elif h.startswith('-'):
1235 elif h.startswith('-'):
1236 continue
1236 continue
1237 else:
1237 else:
1238 self.hunk.insert(hunki - 1, u)
1238 self.hunk.insert(hunki - 1, u)
1239 break
1239 break
1240
1240
1241 if not self.a:
1241 if not self.a:
1242 # this happens when lines were only added to the hunk
1242 # this happens when lines were only added to the hunk
1243 for x in self.hunk:
1243 for x in self.hunk:
1244 if x.startswith('-') or x.startswith(' '):
1244 if x.startswith('-') or x.startswith(' '):
1245 self.a.append(x)
1245 self.a.append(x)
1246 if not self.b:
1246 if not self.b:
1247 # this happens when lines were only deleted from the hunk
1247 # this happens when lines were only deleted from the hunk
1248 for x in self.hunk:
1248 for x in self.hunk:
1249 if x.startswith('+') or x.startswith(' '):
1249 if x.startswith('+') or x.startswith(' '):
1250 self.b.append(x[1:])
1250 self.b.append(x[1:])
1251 # @@ -start,len +start,len @@
1251 # @@ -start,len +start,len @@
1252 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
1252 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
1253 self.startb, self.lenb)
1253 self.startb, self.lenb)
1254 self.hunk[0] = self.desc
1254 self.hunk[0] = self.desc
1255 self._fixnewline(lr)
1255 self._fixnewline(lr)
1256
1256
1257 def _fixnewline(self, lr):
1257 def _fixnewline(self, lr):
1258 l = lr.readline()
1258 l = lr.readline()
1259 if l.startswith('\ '):
1259 if l.startswith('\ '):
1260 diffhelpers.fix_newline(self.hunk, self.a, self.b)
1260 diffhelpers.fix_newline(self.hunk, self.a, self.b)
1261 else:
1261 else:
1262 lr.push(l)
1262 lr.push(l)
1263
1263
1264 def complete(self):
1264 def complete(self):
1265 return len(self.a) == self.lena and len(self.b) == self.lenb
1265 return len(self.a) == self.lena and len(self.b) == self.lenb
1266
1266
1267 def _fuzzit(self, old, new, fuzz, toponly):
1267 def _fuzzit(self, old, new, fuzz, toponly):
1268 # this removes context lines from the top and bottom of list 'l'. It
1268 # this removes context lines from the top and bottom of list 'l'. It
1269 # checks the hunk to make sure only context lines are removed, and then
1269 # checks the hunk to make sure only context lines are removed, and then
1270 # returns a new shortened list of lines.
1270 # returns a new shortened list of lines.
1271 fuzz = min(fuzz, len(old))
1271 fuzz = min(fuzz, len(old))
1272 if fuzz:
1272 if fuzz:
1273 top = 0
1273 top = 0
1274 bot = 0
1274 bot = 0
1275 hlen = len(self.hunk)
1275 hlen = len(self.hunk)
1276 for x in xrange(hlen - 1):
1276 for x in xrange(hlen - 1):
1277 # the hunk starts with the @@ line, so use x+1
1277 # the hunk starts with the @@ line, so use x+1
1278 if self.hunk[x + 1][0] == ' ':
1278 if self.hunk[x + 1][0] == ' ':
1279 top += 1
1279 top += 1
1280 else:
1280 else:
1281 break
1281 break
1282 if not toponly:
1282 if not toponly:
1283 for x in xrange(hlen - 1):
1283 for x in xrange(hlen - 1):
1284 if self.hunk[hlen - bot - 1][0] == ' ':
1284 if self.hunk[hlen - bot - 1][0] == ' ':
1285 bot += 1
1285 bot += 1
1286 else:
1286 else:
1287 break
1287 break
1288
1288
1289 bot = min(fuzz, bot)
1289 bot = min(fuzz, bot)
1290 top = min(fuzz, top)
1290 top = min(fuzz, top)
1291 return old[top:len(old) - bot], new[top:len(new) - bot], top
1291 return old[top:len(old) - bot], new[top:len(new) - bot], top
1292 return old, new, 0
1292 return old, new, 0
1293
1293
1294 def fuzzit(self, fuzz, toponly):
1294 def fuzzit(self, fuzz, toponly):
1295 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1295 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1296 oldstart = self.starta + top
1296 oldstart = self.starta + top
1297 newstart = self.startb + top
1297 newstart = self.startb + top
1298 # zero length hunk ranges already have their start decremented
1298 # zero length hunk ranges already have their start decremented
1299 if self.lena and oldstart > 0:
1299 if self.lena and oldstart > 0:
1300 oldstart -= 1
1300 oldstart -= 1
1301 if self.lenb and newstart > 0:
1301 if self.lenb and newstart > 0:
1302 newstart -= 1
1302 newstart -= 1
1303 return old, oldstart, new, newstart
1303 return old, oldstart, new, newstart
1304
1304
1305 class binhunk(object):
1305 class binhunk(object):
1306 'A binary patch file.'
1306 'A binary patch file.'
1307 def __init__(self, lr, fname):
1307 def __init__(self, lr, fname):
1308 self.text = None
1308 self.text = None
1309 self.delta = False
1309 self.delta = False
1310 self.hunk = ['GIT binary patch\n']
1310 self.hunk = ['GIT binary patch\n']
1311 self._fname = fname
1311 self._fname = fname
1312 self._read(lr)
1312 self._read(lr)
1313
1313
1314 def complete(self):
1314 def complete(self):
1315 return self.text is not None
1315 return self.text is not None
1316
1316
1317 def new(self, lines):
1317 def new(self, lines):
1318 if self.delta:
1318 if self.delta:
1319 return [applybindelta(self.text, ''.join(lines))]
1319 return [applybindelta(self.text, ''.join(lines))]
1320 return [self.text]
1320 return [self.text]
1321
1321
1322 def _read(self, lr):
1322 def _read(self, lr):
1323 def getline(lr, hunk):
1323 def getline(lr, hunk):
1324 l = lr.readline()
1324 l = lr.readline()
1325 hunk.append(l)
1325 hunk.append(l)
1326 return l.rstrip('\r\n')
1326 return l.rstrip('\r\n')
1327
1327
1328 size = 0
1328 size = 0
1329 while True:
1329 while True:
1330 line = getline(lr, self.hunk)
1330 line = getline(lr, self.hunk)
1331 if not line:
1331 if not line:
1332 raise PatchError(_('could not extract "%s" binary data')
1332 raise PatchError(_('could not extract "%s" binary data')
1333 % self._fname)
1333 % self._fname)
1334 if line.startswith('literal '):
1334 if line.startswith('literal '):
1335 size = int(line[8:].rstrip())
1335 size = int(line[8:].rstrip())
1336 break
1336 break
1337 if line.startswith('delta '):
1337 if line.startswith('delta '):
1338 size = int(line[6:].rstrip())
1338 size = int(line[6:].rstrip())
1339 self.delta = True
1339 self.delta = True
1340 break
1340 break
1341 dec = []
1341 dec = []
1342 line = getline(lr, self.hunk)
1342 line = getline(lr, self.hunk)
1343 while len(line) > 1:
1343 while len(line) > 1:
1344 l = line[0]
1344 l = line[0]
1345 if l <= 'Z' and l >= 'A':
1345 if l <= 'Z' and l >= 'A':
1346 l = ord(l) - ord('A') + 1
1346 l = ord(l) - ord('A') + 1
1347 else:
1347 else:
1348 l = ord(l) - ord('a') + 27
1348 l = ord(l) - ord('a') + 27
1349 try:
1349 try:
1350 dec.append(base85.b85decode(line[1:])[:l])
1350 dec.append(base85.b85decode(line[1:])[:l])
1351 except ValueError, e:
1351 except ValueError, e:
1352 raise PatchError(_('could not decode "%s" binary patch: %s')
1352 raise PatchError(_('could not decode "%s" binary patch: %s')
1353 % (self._fname, str(e)))
1353 % (self._fname, str(e)))
1354 line = getline(lr, self.hunk)
1354 line = getline(lr, self.hunk)
1355 text = zlib.decompress(''.join(dec))
1355 text = zlib.decompress(''.join(dec))
1356 if len(text) != size:
1356 if len(text) != size:
1357 raise PatchError(_('"%s" length is %d bytes, should be %d')
1357 raise PatchError(_('"%s" length is %d bytes, should be %d')
1358 % (self._fname, len(text), size))
1358 % (self._fname, len(text), size))
1359 self.text = text
1359 self.text = text
1360
1360
1361 def parsefilename(str):
1361 def parsefilename(str):
1362 # --- filename \t|space stuff
1362 # --- filename \t|space stuff
1363 s = str[4:].rstrip('\r\n')
1363 s = str[4:].rstrip('\r\n')
1364 i = s.find('\t')
1364 i = s.find('\t')
1365 if i < 0:
1365 if i < 0:
1366 i = s.find(' ')
1366 i = s.find(' ')
1367 if i < 0:
1367 if i < 0:
1368 return s
1368 return s
1369 return s[:i]
1369 return s[:i]
1370
1370
1371 def parsepatch(originalchunks):
1371 def parsepatch(originalchunks):
1372 """patch -> [] of headers -> [] of hunks """
1372 """patch -> [] of headers -> [] of hunks """
1373 class parser(object):
1373 class parser(object):
1374 """patch parsing state machine"""
1374 """patch parsing state machine"""
1375 def __init__(self):
1375 def __init__(self):
1376 self.fromline = 0
1376 self.fromline = 0
1377 self.toline = 0
1377 self.toline = 0
1378 self.proc = ''
1378 self.proc = ''
1379 self.header = None
1379 self.header = None
1380 self.context = []
1380 self.context = []
1381 self.before = []
1381 self.before = []
1382 self.hunk = []
1382 self.hunk = []
1383 self.headers = []
1383 self.headers = []
1384
1384
1385 def addrange(self, limits):
1385 def addrange(self, limits):
1386 fromstart, fromend, tostart, toend, proc = limits
1386 fromstart, fromend, tostart, toend, proc = limits
1387 self.fromline = int(fromstart)
1387 self.fromline = int(fromstart)
1388 self.toline = int(tostart)
1388 self.toline = int(tostart)
1389 self.proc = proc
1389 self.proc = proc
1390
1390
1391 def addcontext(self, context):
1391 def addcontext(self, context):
1392 if self.hunk:
1392 if self.hunk:
1393 h = recordhunk(self.header, self.fromline, self.toline,
1393 h = recordhunk(self.header, self.fromline, self.toline,
1394 self.proc, self.before, self.hunk, context)
1394 self.proc, self.before, self.hunk, context)
1395 self.header.hunks.append(h)
1395 self.header.hunks.append(h)
1396 self.fromline += len(self.before) + h.removed
1396 self.fromline += len(self.before) + h.removed
1397 self.toline += len(self.before) + h.added
1397 self.toline += len(self.before) + h.added
1398 self.before = []
1398 self.before = []
1399 self.hunk = []
1399 self.hunk = []
1400 self.proc = ''
1400 self.proc = ''
1401 self.context = context
1401 self.context = context
1402
1402
1403 def addhunk(self, hunk):
1403 def addhunk(self, hunk):
1404 if self.context:
1404 if self.context:
1405 self.before = self.context
1405 self.before = self.context
1406 self.context = []
1406 self.context = []
1407 self.hunk = hunk
1407 self.hunk = hunk
1408
1408
1409 def newfile(self, hdr):
1409 def newfile(self, hdr):
1410 self.addcontext([])
1410 self.addcontext([])
1411 h = header(hdr)
1411 h = header(hdr)
1412 self.headers.append(h)
1412 self.headers.append(h)
1413 self.header = h
1413 self.header = h
1414
1414
1415 def addother(self, line):
1415 def addother(self, line):
1416 pass # 'other' lines are ignored
1416 pass # 'other' lines are ignored
1417
1417
1418 def finished(self):
1418 def finished(self):
1419 self.addcontext([])
1419 self.addcontext([])
1420 return self.headers
1420 return self.headers
1421
1421
1422 transitions = {
1422 transitions = {
1423 'file': {'context': addcontext,
1423 'file': {'context': addcontext,
1424 'file': newfile,
1424 'file': newfile,
1425 'hunk': addhunk,
1425 'hunk': addhunk,
1426 'range': addrange},
1426 'range': addrange},
1427 'context': {'file': newfile,
1427 'context': {'file': newfile,
1428 'hunk': addhunk,
1428 'hunk': addhunk,
1429 'range': addrange,
1429 'range': addrange,
1430 'other': addother},
1430 'other': addother},
1431 'hunk': {'context': addcontext,
1431 'hunk': {'context': addcontext,
1432 'file': newfile,
1432 'file': newfile,
1433 'range': addrange},
1433 'range': addrange},
1434 'range': {'context': addcontext,
1434 'range': {'context': addcontext,
1435 'hunk': addhunk},
1435 'hunk': addhunk},
1436 'other': {'other': addother},
1436 'other': {'other': addother},
1437 }
1437 }
1438
1438
1439 p = parser()
1439 p = parser()
1440 fp = cStringIO.StringIO()
1440 fp = cStringIO.StringIO()
1441 fp.write(''.join(originalchunks))
1441 fp.write(''.join(originalchunks))
1442 fp.seek(0)
1442 fp.seek(0)
1443
1443
1444 state = 'context'
1444 state = 'context'
1445 for newstate, data in scanpatch(fp):
1445 for newstate, data in scanpatch(fp):
1446 try:
1446 try:
1447 p.transitions[state][newstate](p, data)
1447 p.transitions[state][newstate](p, data)
1448 except KeyError:
1448 except KeyError:
1449 raise PatchError('unhandled transition: %s -> %s' %
1449 raise PatchError('unhandled transition: %s -> %s' %
1450 (state, newstate))
1450 (state, newstate))
1451 state = newstate
1451 state = newstate
1452 del fp
1452 del fp
1453 return p.finished()
1453 return p.finished()
1454
1454
1455 def pathtransform(path, strip, prefix):
1455 def pathtransform(path, strip, prefix):
1456 '''turn a path from a patch into a path suitable for the repository
1456 '''turn a path from a patch into a path suitable for the repository
1457
1457
1458 prefix, if not empty, is expected to be normalized with a / at the end.
1458 prefix, if not empty, is expected to be normalized with a / at the end.
1459
1459
1460 Returns (stripped components, path in repository).
1460 Returns (stripped components, path in repository).
1461
1461
1462 >>> pathtransform('a/b/c', 0, '')
1462 >>> pathtransform('a/b/c', 0, '')
1463 ('', 'a/b/c')
1463 ('', 'a/b/c')
1464 >>> pathtransform(' a/b/c ', 0, '')
1464 >>> pathtransform(' a/b/c ', 0, '')
1465 ('', ' a/b/c')
1465 ('', ' a/b/c')
1466 >>> pathtransform(' a/b/c ', 2, '')
1466 >>> pathtransform(' a/b/c ', 2, '')
1467 ('a/b/', 'c')
1467 ('a/b/', 'c')
1468 >>> pathtransform('a/b/c', 0, 'd/e/')
1469 ('', 'd/e/a/b/c')
1468 >>> pathtransform(' a//b/c ', 2, 'd/e/')
1470 >>> pathtransform(' a//b/c ', 2, 'd/e/')
1469 ('a//b/', 'd/e/c')
1471 ('a//b/', 'd/e/c')
1470 >>> pathtransform('a/b/c', 3, '')
1472 >>> pathtransform('a/b/c', 3, '')
1471 Traceback (most recent call last):
1473 Traceback (most recent call last):
1472 PatchError: unable to strip away 1 of 3 dirs from a/b/c
1474 PatchError: unable to strip away 1 of 3 dirs from a/b/c
1473 '''
1475 '''
1474 pathlen = len(path)
1476 pathlen = len(path)
1475 i = 0
1477 i = 0
1476 if strip == 0:
1478 if strip == 0:
1477 return '', path.rstrip()
1479 return '', prefix + path.rstrip()
1478 count = strip
1480 count = strip
1479 while count > 0:
1481 while count > 0:
1480 i = path.find('/', i)
1482 i = path.find('/', i)
1481 if i == -1:
1483 if i == -1:
1482 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1484 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1483 (count, strip, path))
1485 (count, strip, path))
1484 i += 1
1486 i += 1
1485 # consume '//' in the path
1487 # consume '//' in the path
1486 while i < pathlen - 1 and path[i] == '/':
1488 while i < pathlen - 1 and path[i] == '/':
1487 i += 1
1489 i += 1
1488 count -= 1
1490 count -= 1
1489 return path[:i].lstrip(), prefix + path[i:].rstrip()
1491 return path[:i].lstrip(), prefix + path[i:].rstrip()
1490
1492
1491 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip, prefix):
1493 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip, prefix):
1492 nulla = afile_orig == "/dev/null"
1494 nulla = afile_orig == "/dev/null"
1493 nullb = bfile_orig == "/dev/null"
1495 nullb = bfile_orig == "/dev/null"
1494 create = nulla and hunk.starta == 0 and hunk.lena == 0
1496 create = nulla and hunk.starta == 0 and hunk.lena == 0
1495 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1497 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1496 abase, afile = pathtransform(afile_orig, strip, prefix)
1498 abase, afile = pathtransform(afile_orig, strip, prefix)
1497 gooda = not nulla and backend.exists(afile)
1499 gooda = not nulla and backend.exists(afile)
1498 bbase, bfile = pathtransform(bfile_orig, strip, prefix)
1500 bbase, bfile = pathtransform(bfile_orig, strip, prefix)
1499 if afile == bfile:
1501 if afile == bfile:
1500 goodb = gooda
1502 goodb = gooda
1501 else:
1503 else:
1502 goodb = not nullb and backend.exists(bfile)
1504 goodb = not nullb and backend.exists(bfile)
1503 missing = not goodb and not gooda and not create
1505 missing = not goodb and not gooda and not create
1504
1506
1505 # some diff programs apparently produce patches where the afile is
1507 # some diff programs apparently produce patches where the afile is
1506 # not /dev/null, but afile starts with bfile
1508 # not /dev/null, but afile starts with bfile
1507 abasedir = afile[:afile.rfind('/') + 1]
1509 abasedir = afile[:afile.rfind('/') + 1]
1508 bbasedir = bfile[:bfile.rfind('/') + 1]
1510 bbasedir = bfile[:bfile.rfind('/') + 1]
1509 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1511 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1510 and hunk.starta == 0 and hunk.lena == 0):
1512 and hunk.starta == 0 and hunk.lena == 0):
1511 create = True
1513 create = True
1512 missing = False
1514 missing = False
1513
1515
1514 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1516 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1515 # diff is between a file and its backup. In this case, the original
1517 # diff is between a file and its backup. In this case, the original
1516 # file should be patched (see original mpatch code).
1518 # file should be patched (see original mpatch code).
1517 isbackup = (abase == bbase and bfile.startswith(afile))
1519 isbackup = (abase == bbase and bfile.startswith(afile))
1518 fname = None
1520 fname = None
1519 if not missing:
1521 if not missing:
1520 if gooda and goodb:
1522 if gooda and goodb:
1521 if isbackup:
1523 if isbackup:
1522 fname = afile
1524 fname = afile
1523 else:
1525 else:
1524 fname = bfile
1526 fname = bfile
1525 elif gooda:
1527 elif gooda:
1526 fname = afile
1528 fname = afile
1527
1529
1528 if not fname:
1530 if not fname:
1529 if not nullb:
1531 if not nullb:
1530 if isbackup:
1532 if isbackup:
1531 fname = afile
1533 fname = afile
1532 else:
1534 else:
1533 fname = bfile
1535 fname = bfile
1534 elif not nulla:
1536 elif not nulla:
1535 fname = afile
1537 fname = afile
1536 else:
1538 else:
1537 raise PatchError(_("undefined source and destination files"))
1539 raise PatchError(_("undefined source and destination files"))
1538
1540
1539 gp = patchmeta(fname)
1541 gp = patchmeta(fname)
1540 if create:
1542 if create:
1541 gp.op = 'ADD'
1543 gp.op = 'ADD'
1542 elif remove:
1544 elif remove:
1543 gp.op = 'DELETE'
1545 gp.op = 'DELETE'
1544 return gp
1546 return gp
1545
1547
1546 def scanpatch(fp):
1548 def scanpatch(fp):
1547 """like patch.iterhunks, but yield different events
1549 """like patch.iterhunks, but yield different events
1548
1550
1549 - ('file', [header_lines + fromfile + tofile])
1551 - ('file', [header_lines + fromfile + tofile])
1550 - ('context', [context_lines])
1552 - ('context', [context_lines])
1551 - ('hunk', [hunk_lines])
1553 - ('hunk', [hunk_lines])
1552 - ('range', (-start,len, +start,len, proc))
1554 - ('range', (-start,len, +start,len, proc))
1553 """
1555 """
1554 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
1556 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
1555 lr = linereader(fp)
1557 lr = linereader(fp)
1556
1558
1557 def scanwhile(first, p):
1559 def scanwhile(first, p):
1558 """scan lr while predicate holds"""
1560 """scan lr while predicate holds"""
1559 lines = [first]
1561 lines = [first]
1560 while True:
1562 while True:
1561 line = lr.readline()
1563 line = lr.readline()
1562 if not line:
1564 if not line:
1563 break
1565 break
1564 if p(line):
1566 if p(line):
1565 lines.append(line)
1567 lines.append(line)
1566 else:
1568 else:
1567 lr.push(line)
1569 lr.push(line)
1568 break
1570 break
1569 return lines
1571 return lines
1570
1572
1571 while True:
1573 while True:
1572 line = lr.readline()
1574 line = lr.readline()
1573 if not line:
1575 if not line:
1574 break
1576 break
1575 if line.startswith('diff --git a/') or line.startswith('diff -r '):
1577 if line.startswith('diff --git a/') or line.startswith('diff -r '):
1576 def notheader(line):
1578 def notheader(line):
1577 s = line.split(None, 1)
1579 s = line.split(None, 1)
1578 return not s or s[0] not in ('---', 'diff')
1580 return not s or s[0] not in ('---', 'diff')
1579 header = scanwhile(line, notheader)
1581 header = scanwhile(line, notheader)
1580 fromfile = lr.readline()
1582 fromfile = lr.readline()
1581 if fromfile.startswith('---'):
1583 if fromfile.startswith('---'):
1582 tofile = lr.readline()
1584 tofile = lr.readline()
1583 header += [fromfile, tofile]
1585 header += [fromfile, tofile]
1584 else:
1586 else:
1585 lr.push(fromfile)
1587 lr.push(fromfile)
1586 yield 'file', header
1588 yield 'file', header
1587 elif line[0] == ' ':
1589 elif line[0] == ' ':
1588 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
1590 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
1589 elif line[0] in '-+':
1591 elif line[0] in '-+':
1590 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
1592 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
1591 else:
1593 else:
1592 m = lines_re.match(line)
1594 m = lines_re.match(line)
1593 if m:
1595 if m:
1594 yield 'range', m.groups()
1596 yield 'range', m.groups()
1595 else:
1597 else:
1596 yield 'other', line
1598 yield 'other', line
1597
1599
1598 def scangitpatch(lr, firstline):
1600 def scangitpatch(lr, firstline):
1599 """
1601 """
1600 Git patches can emit:
1602 Git patches can emit:
1601 - rename a to b
1603 - rename a to b
1602 - change b
1604 - change b
1603 - copy a to c
1605 - copy a to c
1604 - change c
1606 - change c
1605
1607
1606 We cannot apply this sequence as-is, the renamed 'a' could not be
1608 We cannot apply this sequence as-is, the renamed 'a' could not be
1607 found for it would have been renamed already. And we cannot copy
1609 found for it would have been renamed already. And we cannot copy
1608 from 'b' instead because 'b' would have been changed already. So
1610 from 'b' instead because 'b' would have been changed already. So
1609 we scan the git patch for copy and rename commands so we can
1611 we scan the git patch for copy and rename commands so we can
1610 perform the copies ahead of time.
1612 perform the copies ahead of time.
1611 """
1613 """
1612 pos = 0
1614 pos = 0
1613 try:
1615 try:
1614 pos = lr.fp.tell()
1616 pos = lr.fp.tell()
1615 fp = lr.fp
1617 fp = lr.fp
1616 except IOError:
1618 except IOError:
1617 fp = cStringIO.StringIO(lr.fp.read())
1619 fp = cStringIO.StringIO(lr.fp.read())
1618 gitlr = linereader(fp)
1620 gitlr = linereader(fp)
1619 gitlr.push(firstline)
1621 gitlr.push(firstline)
1620 gitpatches = readgitpatch(gitlr)
1622 gitpatches = readgitpatch(gitlr)
1621 fp.seek(pos)
1623 fp.seek(pos)
1622 return gitpatches
1624 return gitpatches
1623
1625
1624 def iterhunks(fp):
1626 def iterhunks(fp):
1625 """Read a patch and yield the following events:
1627 """Read a patch and yield the following events:
1626 - ("file", afile, bfile, firsthunk): select a new target file.
1628 - ("file", afile, bfile, firsthunk): select a new target file.
1627 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1629 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1628 "file" event.
1630 "file" event.
1629 - ("git", gitchanges): current diff is in git format, gitchanges
1631 - ("git", gitchanges): current diff is in git format, gitchanges
1630 maps filenames to gitpatch records. Unique event.
1632 maps filenames to gitpatch records. Unique event.
1631 """
1633 """
1632 afile = ""
1634 afile = ""
1633 bfile = ""
1635 bfile = ""
1634 state = None
1636 state = None
1635 hunknum = 0
1637 hunknum = 0
1636 emitfile = newfile = False
1638 emitfile = newfile = False
1637 gitpatches = None
1639 gitpatches = None
1638
1640
1639 # our states
1641 # our states
1640 BFILE = 1
1642 BFILE = 1
1641 context = None
1643 context = None
1642 lr = linereader(fp)
1644 lr = linereader(fp)
1643
1645
1644 while True:
1646 while True:
1645 x = lr.readline()
1647 x = lr.readline()
1646 if not x:
1648 if not x:
1647 break
1649 break
1648 if state == BFILE and (
1650 if state == BFILE and (
1649 (not context and x[0] == '@')
1651 (not context and x[0] == '@')
1650 or (context is not False and x.startswith('***************'))
1652 or (context is not False and x.startswith('***************'))
1651 or x.startswith('GIT binary patch')):
1653 or x.startswith('GIT binary patch')):
1652 gp = None
1654 gp = None
1653 if (gitpatches and
1655 if (gitpatches and
1654 gitpatches[-1].ispatching(afile, bfile)):
1656 gitpatches[-1].ispatching(afile, bfile)):
1655 gp = gitpatches.pop()
1657 gp = gitpatches.pop()
1656 if x.startswith('GIT binary patch'):
1658 if x.startswith('GIT binary patch'):
1657 h = binhunk(lr, gp.path)
1659 h = binhunk(lr, gp.path)
1658 else:
1660 else:
1659 if context is None and x.startswith('***************'):
1661 if context is None and x.startswith('***************'):
1660 context = True
1662 context = True
1661 h = hunk(x, hunknum + 1, lr, context)
1663 h = hunk(x, hunknum + 1, lr, context)
1662 hunknum += 1
1664 hunknum += 1
1663 if emitfile:
1665 if emitfile:
1664 emitfile = False
1666 emitfile = False
1665 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1667 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1666 yield 'hunk', h
1668 yield 'hunk', h
1667 elif x.startswith('diff --git a/'):
1669 elif x.startswith('diff --git a/'):
1668 m = gitre.match(x.rstrip(' \r\n'))
1670 m = gitre.match(x.rstrip(' \r\n'))
1669 if not m:
1671 if not m:
1670 continue
1672 continue
1671 if gitpatches is None:
1673 if gitpatches is None:
1672 # scan whole input for git metadata
1674 # scan whole input for git metadata
1673 gitpatches = scangitpatch(lr, x)
1675 gitpatches = scangitpatch(lr, x)
1674 yield 'git', [g.copy() for g in gitpatches
1676 yield 'git', [g.copy() for g in gitpatches
1675 if g.op in ('COPY', 'RENAME')]
1677 if g.op in ('COPY', 'RENAME')]
1676 gitpatches.reverse()
1678 gitpatches.reverse()
1677 afile = 'a/' + m.group(1)
1679 afile = 'a/' + m.group(1)
1678 bfile = 'b/' + m.group(2)
1680 bfile = 'b/' + m.group(2)
1679 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1681 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1680 gp = gitpatches.pop()
1682 gp = gitpatches.pop()
1681 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1683 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1682 if not gitpatches:
1684 if not gitpatches:
1683 raise PatchError(_('failed to synchronize metadata for "%s"')
1685 raise PatchError(_('failed to synchronize metadata for "%s"')
1684 % afile[2:])
1686 % afile[2:])
1685 gp = gitpatches[-1]
1687 gp = gitpatches[-1]
1686 newfile = True
1688 newfile = True
1687 elif x.startswith('---'):
1689 elif x.startswith('---'):
1688 # check for a unified diff
1690 # check for a unified diff
1689 l2 = lr.readline()
1691 l2 = lr.readline()
1690 if not l2.startswith('+++'):
1692 if not l2.startswith('+++'):
1691 lr.push(l2)
1693 lr.push(l2)
1692 continue
1694 continue
1693 newfile = True
1695 newfile = True
1694 context = False
1696 context = False
1695 afile = parsefilename(x)
1697 afile = parsefilename(x)
1696 bfile = parsefilename(l2)
1698 bfile = parsefilename(l2)
1697 elif x.startswith('***'):
1699 elif x.startswith('***'):
1698 # check for a context diff
1700 # check for a context diff
1699 l2 = lr.readline()
1701 l2 = lr.readline()
1700 if not l2.startswith('---'):
1702 if not l2.startswith('---'):
1701 lr.push(l2)
1703 lr.push(l2)
1702 continue
1704 continue
1703 l3 = lr.readline()
1705 l3 = lr.readline()
1704 lr.push(l3)
1706 lr.push(l3)
1705 if not l3.startswith("***************"):
1707 if not l3.startswith("***************"):
1706 lr.push(l2)
1708 lr.push(l2)
1707 continue
1709 continue
1708 newfile = True
1710 newfile = True
1709 context = True
1711 context = True
1710 afile = parsefilename(x)
1712 afile = parsefilename(x)
1711 bfile = parsefilename(l2)
1713 bfile = parsefilename(l2)
1712
1714
1713 if newfile:
1715 if newfile:
1714 newfile = False
1716 newfile = False
1715 emitfile = True
1717 emitfile = True
1716 state = BFILE
1718 state = BFILE
1717 hunknum = 0
1719 hunknum = 0
1718
1720
1719 while gitpatches:
1721 while gitpatches:
1720 gp = gitpatches.pop()
1722 gp = gitpatches.pop()
1721 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1723 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1722
1724
1723 def applybindelta(binchunk, data):
1725 def applybindelta(binchunk, data):
1724 """Apply a binary delta hunk
1726 """Apply a binary delta hunk
1725 The algorithm used is the algorithm from git's patch-delta.c
1727 The algorithm used is the algorithm from git's patch-delta.c
1726 """
1728 """
1727 def deltahead(binchunk):
1729 def deltahead(binchunk):
1728 i = 0
1730 i = 0
1729 for c in binchunk:
1731 for c in binchunk:
1730 i += 1
1732 i += 1
1731 if not (ord(c) & 0x80):
1733 if not (ord(c) & 0x80):
1732 return i
1734 return i
1733 return i
1735 return i
1734 out = ""
1736 out = ""
1735 s = deltahead(binchunk)
1737 s = deltahead(binchunk)
1736 binchunk = binchunk[s:]
1738 binchunk = binchunk[s:]
1737 s = deltahead(binchunk)
1739 s = deltahead(binchunk)
1738 binchunk = binchunk[s:]
1740 binchunk = binchunk[s:]
1739 i = 0
1741 i = 0
1740 while i < len(binchunk):
1742 while i < len(binchunk):
1741 cmd = ord(binchunk[i])
1743 cmd = ord(binchunk[i])
1742 i += 1
1744 i += 1
1743 if (cmd & 0x80):
1745 if (cmd & 0x80):
1744 offset = 0
1746 offset = 0
1745 size = 0
1747 size = 0
1746 if (cmd & 0x01):
1748 if (cmd & 0x01):
1747 offset = ord(binchunk[i])
1749 offset = ord(binchunk[i])
1748 i += 1
1750 i += 1
1749 if (cmd & 0x02):
1751 if (cmd & 0x02):
1750 offset |= ord(binchunk[i]) << 8
1752 offset |= ord(binchunk[i]) << 8
1751 i += 1
1753 i += 1
1752 if (cmd & 0x04):
1754 if (cmd & 0x04):
1753 offset |= ord(binchunk[i]) << 16
1755 offset |= ord(binchunk[i]) << 16
1754 i += 1
1756 i += 1
1755 if (cmd & 0x08):
1757 if (cmd & 0x08):
1756 offset |= ord(binchunk[i]) << 24
1758 offset |= ord(binchunk[i]) << 24
1757 i += 1
1759 i += 1
1758 if (cmd & 0x10):
1760 if (cmd & 0x10):
1759 size = ord(binchunk[i])
1761 size = ord(binchunk[i])
1760 i += 1
1762 i += 1
1761 if (cmd & 0x20):
1763 if (cmd & 0x20):
1762 size |= ord(binchunk[i]) << 8
1764 size |= ord(binchunk[i]) << 8
1763 i += 1
1765 i += 1
1764 if (cmd & 0x40):
1766 if (cmd & 0x40):
1765 size |= ord(binchunk[i]) << 16
1767 size |= ord(binchunk[i]) << 16
1766 i += 1
1768 i += 1
1767 if size == 0:
1769 if size == 0:
1768 size = 0x10000
1770 size = 0x10000
1769 offset_end = offset + size
1771 offset_end = offset + size
1770 out += data[offset:offset_end]
1772 out += data[offset:offset_end]
1771 elif cmd != 0:
1773 elif cmd != 0:
1772 offset_end = i + cmd
1774 offset_end = i + cmd
1773 out += binchunk[i:offset_end]
1775 out += binchunk[i:offset_end]
1774 i += cmd
1776 i += cmd
1775 else:
1777 else:
1776 raise PatchError(_('unexpected delta opcode 0'))
1778 raise PatchError(_('unexpected delta opcode 0'))
1777 return out
1779 return out
1778
1780
1779 def applydiff(ui, fp, backend, store, strip=1, prefix='', eolmode='strict'):
1781 def applydiff(ui, fp, backend, store, strip=1, prefix='', eolmode='strict'):
1780 """Reads a patch from fp and tries to apply it.
1782 """Reads a patch from fp and tries to apply it.
1781
1783
1782 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1784 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1783 there was any fuzz.
1785 there was any fuzz.
1784
1786
1785 If 'eolmode' is 'strict', the patch content and patched file are
1787 If 'eolmode' is 'strict', the patch content and patched file are
1786 read in binary mode. Otherwise, line endings are ignored when
1788 read in binary mode. Otherwise, line endings are ignored when
1787 patching then normalized according to 'eolmode'.
1789 patching then normalized according to 'eolmode'.
1788 """
1790 """
1789 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1791 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1790 prefix=prefix, eolmode=eolmode)
1792 prefix=prefix, eolmode=eolmode)
1791
1793
1792 def _applydiff(ui, fp, patcher, backend, store, strip=1, prefix='',
1794 def _applydiff(ui, fp, patcher, backend, store, strip=1, prefix='',
1793 eolmode='strict'):
1795 eolmode='strict'):
1794
1796
1795 if prefix:
1797 if prefix:
1796 # clean up double slashes, lack of trailing slashes, etc
1798 # clean up double slashes, lack of trailing slashes, etc
1797 prefix = util.normpath(prefix) + '/'
1799 prefix = util.normpath(prefix) + '/'
1798 def pstrip(p):
1800 def pstrip(p):
1799 return pathtransform(p, strip - 1, prefix)[1]
1801 return pathtransform(p, strip - 1, prefix)[1]
1800
1802
1801 rejects = 0
1803 rejects = 0
1802 err = 0
1804 err = 0
1803 current_file = None
1805 current_file = None
1804
1806
1805 for state, values in iterhunks(fp):
1807 for state, values in iterhunks(fp):
1806 if state == 'hunk':
1808 if state == 'hunk':
1807 if not current_file:
1809 if not current_file:
1808 continue
1810 continue
1809 ret = current_file.apply(values)
1811 ret = current_file.apply(values)
1810 if ret > 0:
1812 if ret > 0:
1811 err = 1
1813 err = 1
1812 elif state == 'file':
1814 elif state == 'file':
1813 if current_file:
1815 if current_file:
1814 rejects += current_file.close()
1816 rejects += current_file.close()
1815 current_file = None
1817 current_file = None
1816 afile, bfile, first_hunk, gp = values
1818 afile, bfile, first_hunk, gp = values
1817 if gp:
1819 if gp:
1818 gp.path = pstrip(gp.path)
1820 gp.path = pstrip(gp.path)
1819 if gp.oldpath:
1821 if gp.oldpath:
1820 gp.oldpath = pstrip(gp.oldpath)
1822 gp.oldpath = pstrip(gp.oldpath)
1821 else:
1823 else:
1822 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
1824 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
1823 prefix)
1825 prefix)
1824 if gp.op == 'RENAME':
1826 if gp.op == 'RENAME':
1825 backend.unlink(gp.oldpath)
1827 backend.unlink(gp.oldpath)
1826 if not first_hunk:
1828 if not first_hunk:
1827 if gp.op == 'DELETE':
1829 if gp.op == 'DELETE':
1828 backend.unlink(gp.path)
1830 backend.unlink(gp.path)
1829 continue
1831 continue
1830 data, mode = None, None
1832 data, mode = None, None
1831 if gp.op in ('RENAME', 'COPY'):
1833 if gp.op in ('RENAME', 'COPY'):
1832 data, mode = store.getfile(gp.oldpath)[:2]
1834 data, mode = store.getfile(gp.oldpath)[:2]
1833 # FIXME: failing getfile has never been handled here
1835 # FIXME: failing getfile has never been handled here
1834 assert data is not None
1836 assert data is not None
1835 if gp.mode:
1837 if gp.mode:
1836 mode = gp.mode
1838 mode = gp.mode
1837 if gp.op == 'ADD':
1839 if gp.op == 'ADD':
1838 # Added files without content have no hunk and
1840 # Added files without content have no hunk and
1839 # must be created
1841 # must be created
1840 data = ''
1842 data = ''
1841 if data or mode:
1843 if data or mode:
1842 if (gp.op in ('ADD', 'RENAME', 'COPY')
1844 if (gp.op in ('ADD', 'RENAME', 'COPY')
1843 and backend.exists(gp.path)):
1845 and backend.exists(gp.path)):
1844 raise PatchError(_("cannot create %s: destination "
1846 raise PatchError(_("cannot create %s: destination "
1845 "already exists") % gp.path)
1847 "already exists") % gp.path)
1846 backend.setfile(gp.path, data, mode, gp.oldpath)
1848 backend.setfile(gp.path, data, mode, gp.oldpath)
1847 continue
1849 continue
1848 try:
1850 try:
1849 current_file = patcher(ui, gp, backend, store,
1851 current_file = patcher(ui, gp, backend, store,
1850 eolmode=eolmode)
1852 eolmode=eolmode)
1851 except PatchError, inst:
1853 except PatchError, inst:
1852 ui.warn(str(inst) + '\n')
1854 ui.warn(str(inst) + '\n')
1853 current_file = None
1855 current_file = None
1854 rejects += 1
1856 rejects += 1
1855 continue
1857 continue
1856 elif state == 'git':
1858 elif state == 'git':
1857 for gp in values:
1859 for gp in values:
1858 path = pstrip(gp.oldpath)
1860 path = pstrip(gp.oldpath)
1859 data, mode = backend.getfile(path)
1861 data, mode = backend.getfile(path)
1860 if data is None:
1862 if data is None:
1861 # The error ignored here will trigger a getfile()
1863 # The error ignored here will trigger a getfile()
1862 # error in a place more appropriate for error
1864 # error in a place more appropriate for error
1863 # handling, and will not interrupt the patching
1865 # handling, and will not interrupt the patching
1864 # process.
1866 # process.
1865 pass
1867 pass
1866 else:
1868 else:
1867 store.setfile(path, data, mode)
1869 store.setfile(path, data, mode)
1868 else:
1870 else:
1869 raise util.Abort(_('unsupported parser state: %s') % state)
1871 raise util.Abort(_('unsupported parser state: %s') % state)
1870
1872
1871 if current_file:
1873 if current_file:
1872 rejects += current_file.close()
1874 rejects += current_file.close()
1873
1875
1874 if rejects:
1876 if rejects:
1875 return -1
1877 return -1
1876 return err
1878 return err
1877
1879
1878 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1880 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1879 similarity):
1881 similarity):
1880 """use <patcher> to apply <patchname> to the working directory.
1882 """use <patcher> to apply <patchname> to the working directory.
1881 returns whether patch was applied with fuzz factor."""
1883 returns whether patch was applied with fuzz factor."""
1882
1884
1883 fuzz = False
1885 fuzz = False
1884 args = []
1886 args = []
1885 cwd = repo.root
1887 cwd = repo.root
1886 if cwd:
1888 if cwd:
1887 args.append('-d %s' % util.shellquote(cwd))
1889 args.append('-d %s' % util.shellquote(cwd))
1888 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1890 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1889 util.shellquote(patchname)))
1891 util.shellquote(patchname)))
1890 try:
1892 try:
1891 for line in fp:
1893 for line in fp:
1892 line = line.rstrip()
1894 line = line.rstrip()
1893 ui.note(line + '\n')
1895 ui.note(line + '\n')
1894 if line.startswith('patching file '):
1896 if line.startswith('patching file '):
1895 pf = util.parsepatchoutput(line)
1897 pf = util.parsepatchoutput(line)
1896 printed_file = False
1898 printed_file = False
1897 files.add(pf)
1899 files.add(pf)
1898 elif line.find('with fuzz') >= 0:
1900 elif line.find('with fuzz') >= 0:
1899 fuzz = True
1901 fuzz = True
1900 if not printed_file:
1902 if not printed_file:
1901 ui.warn(pf + '\n')
1903 ui.warn(pf + '\n')
1902 printed_file = True
1904 printed_file = True
1903 ui.warn(line + '\n')
1905 ui.warn(line + '\n')
1904 elif line.find('saving rejects to file') >= 0:
1906 elif line.find('saving rejects to file') >= 0:
1905 ui.warn(line + '\n')
1907 ui.warn(line + '\n')
1906 elif line.find('FAILED') >= 0:
1908 elif line.find('FAILED') >= 0:
1907 if not printed_file:
1909 if not printed_file:
1908 ui.warn(pf + '\n')
1910 ui.warn(pf + '\n')
1909 printed_file = True
1911 printed_file = True
1910 ui.warn(line + '\n')
1912 ui.warn(line + '\n')
1911 finally:
1913 finally:
1912 if files:
1914 if files:
1913 scmutil.marktouched(repo, files, similarity)
1915 scmutil.marktouched(repo, files, similarity)
1914 code = fp.close()
1916 code = fp.close()
1915 if code:
1917 if code:
1916 raise PatchError(_("patch command failed: %s") %
1918 raise PatchError(_("patch command failed: %s") %
1917 util.explainexit(code)[0])
1919 util.explainexit(code)[0])
1918 return fuzz
1920 return fuzz
1919
1921
1920 def patchbackend(ui, backend, patchobj, strip, prefix, files=None,
1922 def patchbackend(ui, backend, patchobj, strip, prefix, files=None,
1921 eolmode='strict'):
1923 eolmode='strict'):
1922 if files is None:
1924 if files is None:
1923 files = set()
1925 files = set()
1924 if eolmode is None:
1926 if eolmode is None:
1925 eolmode = ui.config('patch', 'eol', 'strict')
1927 eolmode = ui.config('patch', 'eol', 'strict')
1926 if eolmode.lower() not in eolmodes:
1928 if eolmode.lower() not in eolmodes:
1927 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1929 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1928 eolmode = eolmode.lower()
1930 eolmode = eolmode.lower()
1929
1931
1930 store = filestore()
1932 store = filestore()
1931 try:
1933 try:
1932 fp = open(patchobj, 'rb')
1934 fp = open(patchobj, 'rb')
1933 except TypeError:
1935 except TypeError:
1934 fp = patchobj
1936 fp = patchobj
1935 try:
1937 try:
1936 ret = applydiff(ui, fp, backend, store, strip=strip, prefix=prefix,
1938 ret = applydiff(ui, fp, backend, store, strip=strip, prefix=prefix,
1937 eolmode=eolmode)
1939 eolmode=eolmode)
1938 finally:
1940 finally:
1939 if fp != patchobj:
1941 if fp != patchobj:
1940 fp.close()
1942 fp.close()
1941 files.update(backend.close())
1943 files.update(backend.close())
1942 store.close()
1944 store.close()
1943 if ret < 0:
1945 if ret < 0:
1944 raise PatchError(_('patch failed to apply'))
1946 raise PatchError(_('patch failed to apply'))
1945 return ret > 0
1947 return ret > 0
1946
1948
1947 def internalpatch(ui, repo, patchobj, strip, prefix='', files=None,
1949 def internalpatch(ui, repo, patchobj, strip, prefix='', files=None,
1948 eolmode='strict', similarity=0):
1950 eolmode='strict', similarity=0):
1949 """use builtin patch to apply <patchobj> to the working directory.
1951 """use builtin patch to apply <patchobj> to the working directory.
1950 returns whether patch was applied with fuzz factor."""
1952 returns whether patch was applied with fuzz factor."""
1951 backend = workingbackend(ui, repo, similarity)
1953 backend = workingbackend(ui, repo, similarity)
1952 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
1954 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
1953
1955
1954 def patchrepo(ui, repo, ctx, store, patchobj, strip, prefix, files=None,
1956 def patchrepo(ui, repo, ctx, store, patchobj, strip, prefix, files=None,
1955 eolmode='strict'):
1957 eolmode='strict'):
1956 backend = repobackend(ui, repo, ctx, store)
1958 backend = repobackend(ui, repo, ctx, store)
1957 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
1959 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
1958
1960
1959 def patch(ui, repo, patchname, strip=1, prefix='', files=None, eolmode='strict',
1961 def patch(ui, repo, patchname, strip=1, prefix='', files=None, eolmode='strict',
1960 similarity=0):
1962 similarity=0):
1961 """Apply <patchname> to the working directory.
1963 """Apply <patchname> to the working directory.
1962
1964
1963 'eolmode' specifies how end of lines should be handled. It can be:
1965 'eolmode' specifies how end of lines should be handled. It can be:
1964 - 'strict': inputs are read in binary mode, EOLs are preserved
1966 - 'strict': inputs are read in binary mode, EOLs are preserved
1965 - 'crlf': EOLs are ignored when patching and reset to CRLF
1967 - 'crlf': EOLs are ignored when patching and reset to CRLF
1966 - 'lf': EOLs are ignored when patching and reset to LF
1968 - 'lf': EOLs are ignored when patching and reset to LF
1967 - None: get it from user settings, default to 'strict'
1969 - None: get it from user settings, default to 'strict'
1968 'eolmode' is ignored when using an external patcher program.
1970 'eolmode' is ignored when using an external patcher program.
1969
1971
1970 Returns whether patch was applied with fuzz factor.
1972 Returns whether patch was applied with fuzz factor.
1971 """
1973 """
1972 patcher = ui.config('ui', 'patch')
1974 patcher = ui.config('ui', 'patch')
1973 if files is None:
1975 if files is None:
1974 files = set()
1976 files = set()
1975 if patcher:
1977 if patcher:
1976 return _externalpatch(ui, repo, patcher, patchname, strip,
1978 return _externalpatch(ui, repo, patcher, patchname, strip,
1977 files, similarity)
1979 files, similarity)
1978 return internalpatch(ui, repo, patchname, strip, prefix, files, eolmode,
1980 return internalpatch(ui, repo, patchname, strip, prefix, files, eolmode,
1979 similarity)
1981 similarity)
1980
1982
1981 def changedfiles(ui, repo, patchpath, strip=1):
1983 def changedfiles(ui, repo, patchpath, strip=1):
1982 backend = fsbackend(ui, repo.root)
1984 backend = fsbackend(ui, repo.root)
1983 fp = open(patchpath, 'rb')
1985 fp = open(patchpath, 'rb')
1984 try:
1986 try:
1985 changed = set()
1987 changed = set()
1986 for state, values in iterhunks(fp):
1988 for state, values in iterhunks(fp):
1987 if state == 'file':
1989 if state == 'file':
1988 afile, bfile, first_hunk, gp = values
1990 afile, bfile, first_hunk, gp = values
1989 if gp:
1991 if gp:
1990 gp.path = pathtransform(gp.path, strip - 1, '')[1]
1992 gp.path = pathtransform(gp.path, strip - 1, '')[1]
1991 if gp.oldpath:
1993 if gp.oldpath:
1992 gp.oldpath = pathtransform(gp.oldpath, strip - 1, '')[1]
1994 gp.oldpath = pathtransform(gp.oldpath, strip - 1, '')[1]
1993 else:
1995 else:
1994 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
1996 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
1995 '')
1997 '')
1996 changed.add(gp.path)
1998 changed.add(gp.path)
1997 if gp.op == 'RENAME':
1999 if gp.op == 'RENAME':
1998 changed.add(gp.oldpath)
2000 changed.add(gp.oldpath)
1999 elif state not in ('hunk', 'git'):
2001 elif state not in ('hunk', 'git'):
2000 raise util.Abort(_('unsupported parser state: %s') % state)
2002 raise util.Abort(_('unsupported parser state: %s') % state)
2001 return changed
2003 return changed
2002 finally:
2004 finally:
2003 fp.close()
2005 fp.close()
2004
2006
2005 class GitDiffRequired(Exception):
2007 class GitDiffRequired(Exception):
2006 pass
2008 pass
2007
2009
2008 def diffallopts(ui, opts=None, untrusted=False, section='diff'):
2010 def diffallopts(ui, opts=None, untrusted=False, section='diff'):
2009 '''return diffopts with all features supported and parsed'''
2011 '''return diffopts with all features supported and parsed'''
2010 return difffeatureopts(ui, opts=opts, untrusted=untrusted, section=section,
2012 return difffeatureopts(ui, opts=opts, untrusted=untrusted, section=section,
2011 git=True, whitespace=True, formatchanging=True)
2013 git=True, whitespace=True, formatchanging=True)
2012
2014
2013 diffopts = diffallopts
2015 diffopts = diffallopts
2014
2016
2015 def difffeatureopts(ui, opts=None, untrusted=False, section='diff', git=False,
2017 def difffeatureopts(ui, opts=None, untrusted=False, section='diff', git=False,
2016 whitespace=False, formatchanging=False):
2018 whitespace=False, formatchanging=False):
2017 '''return diffopts with only opted-in features parsed
2019 '''return diffopts with only opted-in features parsed
2018
2020
2019 Features:
2021 Features:
2020 - git: git-style diffs
2022 - git: git-style diffs
2021 - whitespace: whitespace options like ignoreblanklines and ignorews
2023 - whitespace: whitespace options like ignoreblanklines and ignorews
2022 - formatchanging: options that will likely break or cause correctness issues
2024 - formatchanging: options that will likely break or cause correctness issues
2023 with most diff parsers
2025 with most diff parsers
2024 '''
2026 '''
2025 def get(key, name=None, getter=ui.configbool, forceplain=None):
2027 def get(key, name=None, getter=ui.configbool, forceplain=None):
2026 if opts:
2028 if opts:
2027 v = opts.get(key)
2029 v = opts.get(key)
2028 if v:
2030 if v:
2029 return v
2031 return v
2030 if forceplain is not None and ui.plain():
2032 if forceplain is not None and ui.plain():
2031 return forceplain
2033 return forceplain
2032 return getter(section, name or key, None, untrusted=untrusted)
2034 return getter(section, name or key, None, untrusted=untrusted)
2033
2035
2034 # core options, expected to be understood by every diff parser
2036 # core options, expected to be understood by every diff parser
2035 buildopts = {
2037 buildopts = {
2036 'nodates': get('nodates'),
2038 'nodates': get('nodates'),
2037 'showfunc': get('show_function', 'showfunc'),
2039 'showfunc': get('show_function', 'showfunc'),
2038 'context': get('unified', getter=ui.config),
2040 'context': get('unified', getter=ui.config),
2039 }
2041 }
2040
2042
2041 if git:
2043 if git:
2042 buildopts['git'] = get('git')
2044 buildopts['git'] = get('git')
2043 if whitespace:
2045 if whitespace:
2044 buildopts['ignorews'] = get('ignore_all_space', 'ignorews')
2046 buildopts['ignorews'] = get('ignore_all_space', 'ignorews')
2045 buildopts['ignorewsamount'] = get('ignore_space_change',
2047 buildopts['ignorewsamount'] = get('ignore_space_change',
2046 'ignorewsamount')
2048 'ignorewsamount')
2047 buildopts['ignoreblanklines'] = get('ignore_blank_lines',
2049 buildopts['ignoreblanklines'] = get('ignore_blank_lines',
2048 'ignoreblanklines')
2050 'ignoreblanklines')
2049 if formatchanging:
2051 if formatchanging:
2050 buildopts['text'] = opts and opts.get('text')
2052 buildopts['text'] = opts and opts.get('text')
2051 buildopts['nobinary'] = get('nobinary')
2053 buildopts['nobinary'] = get('nobinary')
2052 buildopts['noprefix'] = get('noprefix', forceplain=False)
2054 buildopts['noprefix'] = get('noprefix', forceplain=False)
2053
2055
2054 return mdiff.diffopts(**buildopts)
2056 return mdiff.diffopts(**buildopts)
2055
2057
2056 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
2058 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
2057 losedatafn=None, prefix=''):
2059 losedatafn=None, prefix=''):
2058 '''yields diff of changes to files between two nodes, or node and
2060 '''yields diff of changes to files between two nodes, or node and
2059 working directory.
2061 working directory.
2060
2062
2061 if node1 is None, use first dirstate parent instead.
2063 if node1 is None, use first dirstate parent instead.
2062 if node2 is None, compare node1 with working directory.
2064 if node2 is None, compare node1 with working directory.
2063
2065
2064 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
2066 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
2065 every time some change cannot be represented with the current
2067 every time some change cannot be represented with the current
2066 patch format. Return False to upgrade to git patch format, True to
2068 patch format. Return False to upgrade to git patch format, True to
2067 accept the loss or raise an exception to abort the diff. It is
2069 accept the loss or raise an exception to abort the diff. It is
2068 called with the name of current file being diffed as 'fn'. If set
2070 called with the name of current file being diffed as 'fn'. If set
2069 to None, patches will always be upgraded to git format when
2071 to None, patches will always be upgraded to git format when
2070 necessary.
2072 necessary.
2071
2073
2072 prefix is a filename prefix that is prepended to all filenames on
2074 prefix is a filename prefix that is prepended to all filenames on
2073 display (used for subrepos).
2075 display (used for subrepos).
2074 '''
2076 '''
2075
2077
2076 if opts is None:
2078 if opts is None:
2077 opts = mdiff.defaultopts
2079 opts = mdiff.defaultopts
2078
2080
2079 if not node1 and not node2:
2081 if not node1 and not node2:
2080 node1 = repo.dirstate.p1()
2082 node1 = repo.dirstate.p1()
2081
2083
2082 def lrugetfilectx():
2084 def lrugetfilectx():
2083 cache = {}
2085 cache = {}
2084 order = util.deque()
2086 order = util.deque()
2085 def getfilectx(f, ctx):
2087 def getfilectx(f, ctx):
2086 fctx = ctx.filectx(f, filelog=cache.get(f))
2088 fctx = ctx.filectx(f, filelog=cache.get(f))
2087 if f not in cache:
2089 if f not in cache:
2088 if len(cache) > 20:
2090 if len(cache) > 20:
2089 del cache[order.popleft()]
2091 del cache[order.popleft()]
2090 cache[f] = fctx.filelog()
2092 cache[f] = fctx.filelog()
2091 else:
2093 else:
2092 order.remove(f)
2094 order.remove(f)
2093 order.append(f)
2095 order.append(f)
2094 return fctx
2096 return fctx
2095 return getfilectx
2097 return getfilectx
2096 getfilectx = lrugetfilectx()
2098 getfilectx = lrugetfilectx()
2097
2099
2098 ctx1 = repo[node1]
2100 ctx1 = repo[node1]
2099 ctx2 = repo[node2]
2101 ctx2 = repo[node2]
2100
2102
2101 if not changes:
2103 if not changes:
2102 changes = repo.status(ctx1, ctx2, match=match)
2104 changes = repo.status(ctx1, ctx2, match=match)
2103 modified, added, removed = changes[:3]
2105 modified, added, removed = changes[:3]
2104
2106
2105 if not modified and not added and not removed:
2107 if not modified and not added and not removed:
2106 return []
2108 return []
2107
2109
2108 if repo.ui.debugflag:
2110 if repo.ui.debugflag:
2109 hexfunc = hex
2111 hexfunc = hex
2110 else:
2112 else:
2111 hexfunc = short
2113 hexfunc = short
2112 revs = [hexfunc(node) for node in [ctx1.node(), ctx2.node()] if node]
2114 revs = [hexfunc(node) for node in [ctx1.node(), ctx2.node()] if node]
2113
2115
2114 copy = {}
2116 copy = {}
2115 if opts.git or opts.upgrade:
2117 if opts.git or opts.upgrade:
2116 copy = copies.pathcopies(ctx1, ctx2)
2118 copy = copies.pathcopies(ctx1, ctx2)
2117
2119
2118 def difffn(opts, losedata):
2120 def difffn(opts, losedata):
2119 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2121 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2120 copy, getfilectx, opts, losedata, prefix)
2122 copy, getfilectx, opts, losedata, prefix)
2121 if opts.upgrade and not opts.git:
2123 if opts.upgrade and not opts.git:
2122 try:
2124 try:
2123 def losedata(fn):
2125 def losedata(fn):
2124 if not losedatafn or not losedatafn(fn=fn):
2126 if not losedatafn or not losedatafn(fn=fn):
2125 raise GitDiffRequired
2127 raise GitDiffRequired
2126 # Buffer the whole output until we are sure it can be generated
2128 # Buffer the whole output until we are sure it can be generated
2127 return list(difffn(opts.copy(git=False), losedata))
2129 return list(difffn(opts.copy(git=False), losedata))
2128 except GitDiffRequired:
2130 except GitDiffRequired:
2129 return difffn(opts.copy(git=True), None)
2131 return difffn(opts.copy(git=True), None)
2130 else:
2132 else:
2131 return difffn(opts, None)
2133 return difffn(opts, None)
2132
2134
2133 def difflabel(func, *args, **kw):
2135 def difflabel(func, *args, **kw):
2134 '''yields 2-tuples of (output, label) based on the output of func()'''
2136 '''yields 2-tuples of (output, label) based on the output of func()'''
2135 headprefixes = [('diff', 'diff.diffline'),
2137 headprefixes = [('diff', 'diff.diffline'),
2136 ('copy', 'diff.extended'),
2138 ('copy', 'diff.extended'),
2137 ('rename', 'diff.extended'),
2139 ('rename', 'diff.extended'),
2138 ('old', 'diff.extended'),
2140 ('old', 'diff.extended'),
2139 ('new', 'diff.extended'),
2141 ('new', 'diff.extended'),
2140 ('deleted', 'diff.extended'),
2142 ('deleted', 'diff.extended'),
2141 ('---', 'diff.file_a'),
2143 ('---', 'diff.file_a'),
2142 ('+++', 'diff.file_b')]
2144 ('+++', 'diff.file_b')]
2143 textprefixes = [('@', 'diff.hunk'),
2145 textprefixes = [('@', 'diff.hunk'),
2144 ('-', 'diff.deleted'),
2146 ('-', 'diff.deleted'),
2145 ('+', 'diff.inserted')]
2147 ('+', 'diff.inserted')]
2146 head = False
2148 head = False
2147 for chunk in func(*args, **kw):
2149 for chunk in func(*args, **kw):
2148 lines = chunk.split('\n')
2150 lines = chunk.split('\n')
2149 for i, line in enumerate(lines):
2151 for i, line in enumerate(lines):
2150 if i != 0:
2152 if i != 0:
2151 yield ('\n', '')
2153 yield ('\n', '')
2152 if head:
2154 if head:
2153 if line.startswith('@'):
2155 if line.startswith('@'):
2154 head = False
2156 head = False
2155 else:
2157 else:
2156 if line and line[0] not in ' +-@\\':
2158 if line and line[0] not in ' +-@\\':
2157 head = True
2159 head = True
2158 stripline = line
2160 stripline = line
2159 diffline = False
2161 diffline = False
2160 if not head and line and line[0] in '+-':
2162 if not head and line and line[0] in '+-':
2161 # highlight tabs and trailing whitespace, but only in
2163 # highlight tabs and trailing whitespace, but only in
2162 # changed lines
2164 # changed lines
2163 stripline = line.rstrip()
2165 stripline = line.rstrip()
2164 diffline = True
2166 diffline = True
2165
2167
2166 prefixes = textprefixes
2168 prefixes = textprefixes
2167 if head:
2169 if head:
2168 prefixes = headprefixes
2170 prefixes = headprefixes
2169 for prefix, label in prefixes:
2171 for prefix, label in prefixes:
2170 if stripline.startswith(prefix):
2172 if stripline.startswith(prefix):
2171 if diffline:
2173 if diffline:
2172 for token in tabsplitter.findall(stripline):
2174 for token in tabsplitter.findall(stripline):
2173 if '\t' == token[0]:
2175 if '\t' == token[0]:
2174 yield (token, 'diff.tab')
2176 yield (token, 'diff.tab')
2175 else:
2177 else:
2176 yield (token, label)
2178 yield (token, label)
2177 else:
2179 else:
2178 yield (stripline, label)
2180 yield (stripline, label)
2179 break
2181 break
2180 else:
2182 else:
2181 yield (line, '')
2183 yield (line, '')
2182 if line != stripline:
2184 if line != stripline:
2183 yield (line[len(stripline):], 'diff.trailingwhitespace')
2185 yield (line[len(stripline):], 'diff.trailingwhitespace')
2184
2186
2185 def diffui(*args, **kw):
2187 def diffui(*args, **kw):
2186 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
2188 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
2187 return difflabel(diff, *args, **kw)
2189 return difflabel(diff, *args, **kw)
2188
2190
2189 def _filepairs(ctx1, modified, added, removed, copy, opts):
2191 def _filepairs(ctx1, modified, added, removed, copy, opts):
2190 '''generates tuples (f1, f2, copyop), where f1 is the name of the file
2192 '''generates tuples (f1, f2, copyop), where f1 is the name of the file
2191 before and f2 is the the name after. For added files, f1 will be None,
2193 before and f2 is the the name after. For added files, f1 will be None,
2192 and for removed files, f2 will be None. copyop may be set to None, 'copy'
2194 and for removed files, f2 will be None. copyop may be set to None, 'copy'
2193 or 'rename' (the latter two only if opts.git is set).'''
2195 or 'rename' (the latter two only if opts.git is set).'''
2194 gone = set()
2196 gone = set()
2195
2197
2196 copyto = dict([(v, k) for k, v in copy.items()])
2198 copyto = dict([(v, k) for k, v in copy.items()])
2197
2199
2198 addedset, removedset = set(added), set(removed)
2200 addedset, removedset = set(added), set(removed)
2199 # Fix up added, since merged-in additions appear as
2201 # Fix up added, since merged-in additions appear as
2200 # modifications during merges
2202 # modifications during merges
2201 for f in modified:
2203 for f in modified:
2202 if f not in ctx1:
2204 if f not in ctx1:
2203 addedset.add(f)
2205 addedset.add(f)
2204
2206
2205 for f in sorted(modified + added + removed):
2207 for f in sorted(modified + added + removed):
2206 copyop = None
2208 copyop = None
2207 f1, f2 = f, f
2209 f1, f2 = f, f
2208 if f in addedset:
2210 if f in addedset:
2209 f1 = None
2211 f1 = None
2210 if f in copy:
2212 if f in copy:
2211 if opts.git:
2213 if opts.git:
2212 f1 = copy[f]
2214 f1 = copy[f]
2213 if f1 in removedset and f1 not in gone:
2215 if f1 in removedset and f1 not in gone:
2214 copyop = 'rename'
2216 copyop = 'rename'
2215 gone.add(f1)
2217 gone.add(f1)
2216 else:
2218 else:
2217 copyop = 'copy'
2219 copyop = 'copy'
2218 elif f in removedset:
2220 elif f in removedset:
2219 f2 = None
2221 f2 = None
2220 if opts.git:
2222 if opts.git:
2221 # have we already reported a copy above?
2223 # have we already reported a copy above?
2222 if (f in copyto and copyto[f] in addedset
2224 if (f in copyto and copyto[f] in addedset
2223 and copy[copyto[f]] == f):
2225 and copy[copyto[f]] == f):
2224 continue
2226 continue
2225 yield f1, f2, copyop
2227 yield f1, f2, copyop
2226
2228
2227 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2229 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2228 copy, getfilectx, opts, losedatafn, prefix):
2230 copy, getfilectx, opts, losedatafn, prefix):
2229 '''given input data, generate a diff and yield it in blocks
2231 '''given input data, generate a diff and yield it in blocks
2230
2232
2231 If generating a diff would lose data like flags or binary data and
2233 If generating a diff would lose data like flags or binary data and
2232 losedatafn is not None, it will be called.
2234 losedatafn is not None, it will be called.
2233
2235
2234 prefix is added to every path in the diff output.'''
2236 prefix is added to every path in the diff output.'''
2235
2237
2236 def gitindex(text):
2238 def gitindex(text):
2237 if not text:
2239 if not text:
2238 text = ""
2240 text = ""
2239 l = len(text)
2241 l = len(text)
2240 s = util.sha1('blob %d\0' % l)
2242 s = util.sha1('blob %d\0' % l)
2241 s.update(text)
2243 s.update(text)
2242 return s.hexdigest()
2244 return s.hexdigest()
2243
2245
2244 if opts.noprefix:
2246 if opts.noprefix:
2245 aprefix = bprefix = ''
2247 aprefix = bprefix = ''
2246 else:
2248 else:
2247 aprefix = 'a/'
2249 aprefix = 'a/'
2248 bprefix = 'b/'
2250 bprefix = 'b/'
2249
2251
2250 def diffline(f, revs):
2252 def diffline(f, revs):
2251 revinfo = ' '.join(["-r %s" % rev for rev in revs])
2253 revinfo = ' '.join(["-r %s" % rev for rev in revs])
2252 return 'diff %s %s' % (revinfo, f)
2254 return 'diff %s %s' % (revinfo, f)
2253
2255
2254 date1 = util.datestr(ctx1.date())
2256 date1 = util.datestr(ctx1.date())
2255 date2 = util.datestr(ctx2.date())
2257 date2 = util.datestr(ctx2.date())
2256
2258
2257 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
2259 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
2258
2260
2259 for f1, f2, copyop in _filepairs(
2261 for f1, f2, copyop in _filepairs(
2260 ctx1, modified, added, removed, copy, opts):
2262 ctx1, modified, added, removed, copy, opts):
2261 content1 = None
2263 content1 = None
2262 content2 = None
2264 content2 = None
2263 flag1 = None
2265 flag1 = None
2264 flag2 = None
2266 flag2 = None
2265 if f1:
2267 if f1:
2266 content1 = getfilectx(f1, ctx1).data()
2268 content1 = getfilectx(f1, ctx1).data()
2267 if opts.git or losedatafn:
2269 if opts.git or losedatafn:
2268 flag1 = ctx1.flags(f1)
2270 flag1 = ctx1.flags(f1)
2269 if f2:
2271 if f2:
2270 content2 = getfilectx(f2, ctx2).data()
2272 content2 = getfilectx(f2, ctx2).data()
2271 if opts.git or losedatafn:
2273 if opts.git or losedatafn:
2272 flag2 = ctx2.flags(f2)
2274 flag2 = ctx2.flags(f2)
2273 binary = False
2275 binary = False
2274 if opts.git or losedatafn:
2276 if opts.git or losedatafn:
2275 binary = util.binary(content1) or util.binary(content2)
2277 binary = util.binary(content1) or util.binary(content2)
2276
2278
2277 if losedatafn and not opts.git:
2279 if losedatafn and not opts.git:
2278 if (binary or
2280 if (binary or
2279 # copy/rename
2281 # copy/rename
2280 f2 in copy or
2282 f2 in copy or
2281 # empty file creation
2283 # empty file creation
2282 (not f1 and not content2) or
2284 (not f1 and not content2) or
2283 # empty file deletion
2285 # empty file deletion
2284 (not content1 and not f2) or
2286 (not content1 and not f2) or
2285 # create with flags
2287 # create with flags
2286 (not f1 and flag2) or
2288 (not f1 and flag2) or
2287 # change flags
2289 # change flags
2288 (f1 and f2 and flag1 != flag2)):
2290 (f1 and f2 and flag1 != flag2)):
2289 losedatafn(f2 or f1)
2291 losedatafn(f2 or f1)
2290
2292
2291 path1 = posixpath.join(prefix, f1 or f2)
2293 path1 = posixpath.join(prefix, f1 or f2)
2292 path2 = posixpath.join(prefix, f2 or f1)
2294 path2 = posixpath.join(prefix, f2 or f1)
2293 header = []
2295 header = []
2294 if opts.git:
2296 if opts.git:
2295 header.append('diff --git %s%s %s%s' %
2297 header.append('diff --git %s%s %s%s' %
2296 (aprefix, path1, bprefix, path2))
2298 (aprefix, path1, bprefix, path2))
2297 if not f1: # added
2299 if not f1: # added
2298 header.append('new file mode %s' % gitmode[flag2])
2300 header.append('new file mode %s' % gitmode[flag2])
2299 elif not f2: # removed
2301 elif not f2: # removed
2300 header.append('deleted file mode %s' % gitmode[flag1])
2302 header.append('deleted file mode %s' % gitmode[flag1])
2301 else: # modified/copied/renamed
2303 else: # modified/copied/renamed
2302 mode1, mode2 = gitmode[flag1], gitmode[flag2]
2304 mode1, mode2 = gitmode[flag1], gitmode[flag2]
2303 if mode1 != mode2:
2305 if mode1 != mode2:
2304 header.append('old mode %s' % mode1)
2306 header.append('old mode %s' % mode1)
2305 header.append('new mode %s' % mode2)
2307 header.append('new mode %s' % mode2)
2306 if copyop is not None:
2308 if copyop is not None:
2307 header.append('%s from %s' % (copyop, path1))
2309 header.append('%s from %s' % (copyop, path1))
2308 header.append('%s to %s' % (copyop, path2))
2310 header.append('%s to %s' % (copyop, path2))
2309 elif revs and not repo.ui.quiet:
2311 elif revs and not repo.ui.quiet:
2310 header.append(diffline(path1, revs))
2312 header.append(diffline(path1, revs))
2311
2313
2312 if binary and opts.git and not opts.nobinary:
2314 if binary and opts.git and not opts.nobinary:
2313 text = mdiff.b85diff(content1, content2)
2315 text = mdiff.b85diff(content1, content2)
2314 if text:
2316 if text:
2315 header.append('index %s..%s' %
2317 header.append('index %s..%s' %
2316 (gitindex(content1), gitindex(content2)))
2318 (gitindex(content1), gitindex(content2)))
2317 else:
2319 else:
2318 text = mdiff.unidiff(content1, date1,
2320 text = mdiff.unidiff(content1, date1,
2319 content2, date2,
2321 content2, date2,
2320 path1, path2, opts=opts)
2322 path1, path2, opts=opts)
2321 if header and (text or len(header) > 1):
2323 if header and (text or len(header) > 1):
2322 yield '\n'.join(header) + '\n'
2324 yield '\n'.join(header) + '\n'
2323 if text:
2325 if text:
2324 yield text
2326 yield text
2325
2327
2326 def diffstatsum(stats):
2328 def diffstatsum(stats):
2327 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
2329 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
2328 for f, a, r, b in stats:
2330 for f, a, r, b in stats:
2329 maxfile = max(maxfile, encoding.colwidth(f))
2331 maxfile = max(maxfile, encoding.colwidth(f))
2330 maxtotal = max(maxtotal, a + r)
2332 maxtotal = max(maxtotal, a + r)
2331 addtotal += a
2333 addtotal += a
2332 removetotal += r
2334 removetotal += r
2333 binary = binary or b
2335 binary = binary or b
2334
2336
2335 return maxfile, maxtotal, addtotal, removetotal, binary
2337 return maxfile, maxtotal, addtotal, removetotal, binary
2336
2338
2337 def diffstatdata(lines):
2339 def diffstatdata(lines):
2338 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
2340 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
2339
2341
2340 results = []
2342 results = []
2341 filename, adds, removes, isbinary = None, 0, 0, False
2343 filename, adds, removes, isbinary = None, 0, 0, False
2342
2344
2343 def addresult():
2345 def addresult():
2344 if filename:
2346 if filename:
2345 results.append((filename, adds, removes, isbinary))
2347 results.append((filename, adds, removes, isbinary))
2346
2348
2347 for line in lines:
2349 for line in lines:
2348 if line.startswith('diff'):
2350 if line.startswith('diff'):
2349 addresult()
2351 addresult()
2350 # set numbers to 0 anyway when starting new file
2352 # set numbers to 0 anyway when starting new file
2351 adds, removes, isbinary = 0, 0, False
2353 adds, removes, isbinary = 0, 0, False
2352 if line.startswith('diff --git a/'):
2354 if line.startswith('diff --git a/'):
2353 filename = gitre.search(line).group(2)
2355 filename = gitre.search(line).group(2)
2354 elif line.startswith('diff -r'):
2356 elif line.startswith('diff -r'):
2355 # format: "diff -r ... -r ... filename"
2357 # format: "diff -r ... -r ... filename"
2356 filename = diffre.search(line).group(1)
2358 filename = diffre.search(line).group(1)
2357 elif line.startswith('+') and not line.startswith('+++ '):
2359 elif line.startswith('+') and not line.startswith('+++ '):
2358 adds += 1
2360 adds += 1
2359 elif line.startswith('-') and not line.startswith('--- '):
2361 elif line.startswith('-') and not line.startswith('--- '):
2360 removes += 1
2362 removes += 1
2361 elif (line.startswith('GIT binary patch') or
2363 elif (line.startswith('GIT binary patch') or
2362 line.startswith('Binary file')):
2364 line.startswith('Binary file')):
2363 isbinary = True
2365 isbinary = True
2364 addresult()
2366 addresult()
2365 return results
2367 return results
2366
2368
2367 def diffstat(lines, width=80, git=False):
2369 def diffstat(lines, width=80, git=False):
2368 output = []
2370 output = []
2369 stats = diffstatdata(lines)
2371 stats = diffstatdata(lines)
2370 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
2372 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
2371
2373
2372 countwidth = len(str(maxtotal))
2374 countwidth = len(str(maxtotal))
2373 if hasbinary and countwidth < 3:
2375 if hasbinary and countwidth < 3:
2374 countwidth = 3
2376 countwidth = 3
2375 graphwidth = width - countwidth - maxname - 6
2377 graphwidth = width - countwidth - maxname - 6
2376 if graphwidth < 10:
2378 if graphwidth < 10:
2377 graphwidth = 10
2379 graphwidth = 10
2378
2380
2379 def scale(i):
2381 def scale(i):
2380 if maxtotal <= graphwidth:
2382 if maxtotal <= graphwidth:
2381 return i
2383 return i
2382 # If diffstat runs out of room it doesn't print anything,
2384 # If diffstat runs out of room it doesn't print anything,
2383 # which isn't very useful, so always print at least one + or -
2385 # which isn't very useful, so always print at least one + or -
2384 # if there were at least some changes.
2386 # if there were at least some changes.
2385 return max(i * graphwidth // maxtotal, int(bool(i)))
2387 return max(i * graphwidth // maxtotal, int(bool(i)))
2386
2388
2387 for filename, adds, removes, isbinary in stats:
2389 for filename, adds, removes, isbinary in stats:
2388 if isbinary:
2390 if isbinary:
2389 count = 'Bin'
2391 count = 'Bin'
2390 else:
2392 else:
2391 count = adds + removes
2393 count = adds + removes
2392 pluses = '+' * scale(adds)
2394 pluses = '+' * scale(adds)
2393 minuses = '-' * scale(removes)
2395 minuses = '-' * scale(removes)
2394 output.append(' %s%s | %*s %s%s\n' %
2396 output.append(' %s%s | %*s %s%s\n' %
2395 (filename, ' ' * (maxname - encoding.colwidth(filename)),
2397 (filename, ' ' * (maxname - encoding.colwidth(filename)),
2396 countwidth, count, pluses, minuses))
2398 countwidth, count, pluses, minuses))
2397
2399
2398 if stats:
2400 if stats:
2399 output.append(_(' %d files changed, %d insertions(+), '
2401 output.append(_(' %d files changed, %d insertions(+), '
2400 '%d deletions(-)\n')
2402 '%d deletions(-)\n')
2401 % (len(stats), totaladds, totalremoves))
2403 % (len(stats), totaladds, totalremoves))
2402
2404
2403 return ''.join(output)
2405 return ''.join(output)
2404
2406
2405 def diffstatui(*args, **kw):
2407 def diffstatui(*args, **kw):
2406 '''like diffstat(), but yields 2-tuples of (output, label) for
2408 '''like diffstat(), but yields 2-tuples of (output, label) for
2407 ui.write()
2409 ui.write()
2408 '''
2410 '''
2409
2411
2410 for line in diffstat(*args, **kw).splitlines():
2412 for line in diffstat(*args, **kw).splitlines():
2411 if line and line[-1] in '+-':
2413 if line and line[-1] in '+-':
2412 name, graph = line.rsplit(' ', 1)
2414 name, graph = line.rsplit(' ', 1)
2413 yield (name + ' ', '')
2415 yield (name + ' ', '')
2414 m = re.search(r'\++', graph)
2416 m = re.search(r'\++', graph)
2415 if m:
2417 if m:
2416 yield (m.group(0), 'diffstat.inserted')
2418 yield (m.group(0), 'diffstat.inserted')
2417 m = re.search(r'-+', graph)
2419 m = re.search(r'-+', graph)
2418 if m:
2420 if m:
2419 yield (m.group(0), 'diffstat.deleted')
2421 yield (m.group(0), 'diffstat.deleted')
2420 else:
2422 else:
2421 yield (line, '')
2423 yield (line, '')
2422 yield ('\n', '')
2424 yield ('\n', '')
@@ -1,775 +1,798 b''
1 $ hg init repo
1 $ hg init repo
2 $ cd repo
2 $ cd repo
3
3
4 New file:
4 New file:
5
5
6 $ hg import -d "1000000 0" -mnew - <<EOF
6 $ hg import -d "1000000 0" -mnew - <<EOF
7 > diff --git a/new b/new
7 > diff --git a/new b/new
8 > new file mode 100644
8 > new file mode 100644
9 > index 0000000..7898192
9 > index 0000000..7898192
10 > --- /dev/null
10 > --- /dev/null
11 > +++ b/new
11 > +++ b/new
12 > @@ -0,0 +1 @@
12 > @@ -0,0 +1 @@
13 > +a
13 > +a
14 > EOF
14 > EOF
15 applying patch from stdin
15 applying patch from stdin
16
16
17 $ hg tip -q
17 $ hg tip -q
18 0:ae3ee40d2079
18 0:ae3ee40d2079
19
19
20 New empty file:
20 New empty file:
21
21
22 $ hg import -d "1000000 0" -mempty - <<EOF
22 $ hg import -d "1000000 0" -mempty - <<EOF
23 > diff --git a/empty b/empty
23 > diff --git a/empty b/empty
24 > new file mode 100644
24 > new file mode 100644
25 > EOF
25 > EOF
26 applying patch from stdin
26 applying patch from stdin
27
27
28 $ hg tip -q
28 $ hg tip -q
29 1:ab199dc869b5
29 1:ab199dc869b5
30
30
31 $ hg locate empty
31 $ hg locate empty
32 empty
32 empty
33
33
34 chmod +x:
34 chmod +x:
35
35
36 $ hg import -d "1000000 0" -msetx - <<EOF
36 $ hg import -d "1000000 0" -msetx - <<EOF
37 > diff --git a/new b/new
37 > diff --git a/new b/new
38 > old mode 100644
38 > old mode 100644
39 > new mode 100755
39 > new mode 100755
40 > EOF
40 > EOF
41 applying patch from stdin
41 applying patch from stdin
42
42
43 #if execbit
43 #if execbit
44 $ hg tip -q
44 $ hg tip -q
45 2:3a34410f282e
45 2:3a34410f282e
46 $ test -x new
46 $ test -x new
47 $ hg rollback -q
47 $ hg rollback -q
48 #else
48 #else
49 $ hg tip -q
49 $ hg tip -q
50 1:ab199dc869b5
50 1:ab199dc869b5
51 #endif
51 #endif
52
52
53 Copy and removing x bit:
53 Copy and removing x bit:
54
54
55 $ hg import -f -d "1000000 0" -mcopy - <<EOF
55 $ hg import -f -d "1000000 0" -mcopy - <<EOF
56 > diff --git a/new b/copy
56 > diff --git a/new b/copy
57 > old mode 100755
57 > old mode 100755
58 > new mode 100644
58 > new mode 100644
59 > similarity index 100%
59 > similarity index 100%
60 > copy from new
60 > copy from new
61 > copy to copy
61 > copy to copy
62 > diff --git a/new b/copyx
62 > diff --git a/new b/copyx
63 > similarity index 100%
63 > similarity index 100%
64 > copy from new
64 > copy from new
65 > copy to copyx
65 > copy to copyx
66 > EOF
66 > EOF
67 applying patch from stdin
67 applying patch from stdin
68
68
69 $ test -f copy
69 $ test -f copy
70 #if execbit
70 #if execbit
71 $ test ! -x copy
71 $ test ! -x copy
72 $ test -x copyx
72 $ test -x copyx
73 $ hg tip -q
73 $ hg tip -q
74 2:21dfaae65c71
74 2:21dfaae65c71
75 #else
75 #else
76 $ hg tip -q
76 $ hg tip -q
77 2:0efdaa8e3bf3
77 2:0efdaa8e3bf3
78 #endif
78 #endif
79
79
80 $ hg up -qCr1
80 $ hg up -qCr1
81 $ hg rollback -q
81 $ hg rollback -q
82
82
83 Copy (like above but independent of execbit):
83 Copy (like above but independent of execbit):
84
84
85 $ hg import -d "1000000 0" -mcopy - <<EOF
85 $ hg import -d "1000000 0" -mcopy - <<EOF
86 > diff --git a/new b/copy
86 > diff --git a/new b/copy
87 > similarity index 100%
87 > similarity index 100%
88 > copy from new
88 > copy from new
89 > copy to copy
89 > copy to copy
90 > diff --git a/new b/copyx
90 > diff --git a/new b/copyx
91 > similarity index 100%
91 > similarity index 100%
92 > copy from new
92 > copy from new
93 > copy to copyx
93 > copy to copyx
94 > EOF
94 > EOF
95 applying patch from stdin
95 applying patch from stdin
96
96
97 $ hg tip -q
97 $ hg tip -q
98 2:0efdaa8e3bf3
98 2:0efdaa8e3bf3
99 $ test -f copy
99 $ test -f copy
100
100
101 $ cat copy
101 $ cat copy
102 a
102 a
103
103
104 $ hg cat copy
104 $ hg cat copy
105 a
105 a
106
106
107 Rename:
107 Rename:
108
108
109 $ hg import -d "1000000 0" -mrename - <<EOF
109 $ hg import -d "1000000 0" -mrename - <<EOF
110 > diff --git a/copy b/rename
110 > diff --git a/copy b/rename
111 > similarity index 100%
111 > similarity index 100%
112 > rename from copy
112 > rename from copy
113 > rename to rename
113 > rename to rename
114 > EOF
114 > EOF
115 applying patch from stdin
115 applying patch from stdin
116
116
117 $ hg tip -q
117 $ hg tip -q
118 3:b1f57753fad2
118 3:b1f57753fad2
119
119
120 $ hg locate
120 $ hg locate
121 copyx
121 copyx
122 empty
122 empty
123 new
123 new
124 rename
124 rename
125
125
126 Delete:
126 Delete:
127
127
128 $ hg import -d "1000000 0" -mdelete - <<EOF
128 $ hg import -d "1000000 0" -mdelete - <<EOF
129 > diff --git a/copyx b/copyx
129 > diff --git a/copyx b/copyx
130 > deleted file mode 100755
130 > deleted file mode 100755
131 > index 7898192..0000000
131 > index 7898192..0000000
132 > --- a/copyx
132 > --- a/copyx
133 > +++ /dev/null
133 > +++ /dev/null
134 > @@ -1 +0,0 @@
134 > @@ -1 +0,0 @@
135 > -a
135 > -a
136 > EOF
136 > EOF
137 applying patch from stdin
137 applying patch from stdin
138
138
139 $ hg tip -q
139 $ hg tip -q
140 4:1bd1da94b9b2
140 4:1bd1da94b9b2
141
141
142 $ hg locate
142 $ hg locate
143 empty
143 empty
144 new
144 new
145 rename
145 rename
146
146
147 $ test -f copyx
147 $ test -f copyx
148 [1]
148 [1]
149
149
150 Regular diff:
150 Regular diff:
151
151
152 $ hg import -d "1000000 0" -mregular - <<EOF
152 $ hg import -d "1000000 0" -mregular - <<EOF
153 > diff --git a/rename b/rename
153 > diff --git a/rename b/rename
154 > index 7898192..72e1fe3 100644
154 > index 7898192..72e1fe3 100644
155 > --- a/rename
155 > --- a/rename
156 > +++ b/rename
156 > +++ b/rename
157 > @@ -1 +1,5 @@
157 > @@ -1 +1,5 @@
158 > a
158 > a
159 > +a
159 > +a
160 > +a
160 > +a
161 > +a
161 > +a
162 > +a
162 > +a
163 > EOF
163 > EOF
164 applying patch from stdin
164 applying patch from stdin
165
165
166 $ hg tip -q
166 $ hg tip -q
167 5:46fe99cb3035
167 5:46fe99cb3035
168
168
169 Copy and modify:
169 Copy and modify:
170
170
171 $ hg import -d "1000000 0" -mcopymod - <<EOF
171 $ hg import -d "1000000 0" -mcopymod - <<EOF
172 > diff --git a/rename b/copy2
172 > diff --git a/rename b/copy2
173 > similarity index 80%
173 > similarity index 80%
174 > copy from rename
174 > copy from rename
175 > copy to copy2
175 > copy to copy2
176 > index 72e1fe3..b53c148 100644
176 > index 72e1fe3..b53c148 100644
177 > --- a/rename
177 > --- a/rename
178 > +++ b/copy2
178 > +++ b/copy2
179 > @@ -1,5 +1,5 @@
179 > @@ -1,5 +1,5 @@
180 > a
180 > a
181 > a
181 > a
182 > -a
182 > -a
183 > +b
183 > +b
184 > a
184 > a
185 > a
185 > a
186 > EOF
186 > EOF
187 applying patch from stdin
187 applying patch from stdin
188
188
189 $ hg tip -q
189 $ hg tip -q
190 6:ffeb3197c12d
190 6:ffeb3197c12d
191
191
192 $ hg cat copy2
192 $ hg cat copy2
193 a
193 a
194 a
194 a
195 b
195 b
196 a
196 a
197 a
197 a
198
198
199 Rename and modify:
199 Rename and modify:
200
200
201 $ hg import -d "1000000 0" -mrenamemod - <<EOF
201 $ hg import -d "1000000 0" -mrenamemod - <<EOF
202 > diff --git a/copy2 b/rename2
202 > diff --git a/copy2 b/rename2
203 > similarity index 80%
203 > similarity index 80%
204 > rename from copy2
204 > rename from copy2
205 > rename to rename2
205 > rename to rename2
206 > index b53c148..8f81e29 100644
206 > index b53c148..8f81e29 100644
207 > --- a/copy2
207 > --- a/copy2
208 > +++ b/rename2
208 > +++ b/rename2
209 > @@ -1,5 +1,5 @@
209 > @@ -1,5 +1,5 @@
210 > a
210 > a
211 > a
211 > a
212 > b
212 > b
213 > -a
213 > -a
214 > +c
214 > +c
215 > a
215 > a
216 > EOF
216 > EOF
217 applying patch from stdin
217 applying patch from stdin
218
218
219 $ hg tip -q
219 $ hg tip -q
220 7:401aede9e6bb
220 7:401aede9e6bb
221
221
222 $ hg locate copy2
222 $ hg locate copy2
223 [1]
223 [1]
224 $ hg cat rename2
224 $ hg cat rename2
225 a
225 a
226 a
226 a
227 b
227 b
228 c
228 c
229 a
229 a
230
230
231 One file renamed multiple times:
231 One file renamed multiple times:
232
232
233 $ hg import -d "1000000 0" -mmultirenames - <<EOF
233 $ hg import -d "1000000 0" -mmultirenames - <<EOF
234 > diff --git a/rename2 b/rename3
234 > diff --git a/rename2 b/rename3
235 > rename from rename2
235 > rename from rename2
236 > rename to rename3
236 > rename to rename3
237 > diff --git a/rename2 b/rename3-2
237 > diff --git a/rename2 b/rename3-2
238 > rename from rename2
238 > rename from rename2
239 > rename to rename3-2
239 > rename to rename3-2
240 > EOF
240 > EOF
241 applying patch from stdin
241 applying patch from stdin
242
242
243 $ hg tip -q
243 $ hg tip -q
244 8:2ef727e684e8
244 8:2ef727e684e8
245
245
246 $ hg log -vr. --template '{rev} {files} / {file_copies}\n'
246 $ hg log -vr. --template '{rev} {files} / {file_copies}\n'
247 8 rename2 rename3 rename3-2 / rename3 (rename2)rename3-2 (rename2)
247 8 rename2 rename3 rename3-2 / rename3 (rename2)rename3-2 (rename2)
248
248
249 $ hg locate rename2 rename3 rename3-2
249 $ hg locate rename2 rename3 rename3-2
250 rename3
250 rename3
251 rename3-2
251 rename3-2
252
252
253 $ hg cat rename3
253 $ hg cat rename3
254 a
254 a
255 a
255 a
256 b
256 b
257 c
257 c
258 a
258 a
259
259
260 $ hg cat rename3-2
260 $ hg cat rename3-2
261 a
261 a
262 a
262 a
263 b
263 b
264 c
264 c
265 a
265 a
266
266
267 $ echo foo > foo
267 $ echo foo > foo
268 $ hg add foo
268 $ hg add foo
269 $ hg ci -m 'add foo'
269 $ hg ci -m 'add foo'
270
270
271 Binary files and regular patch hunks:
271 Binary files and regular patch hunks:
272
272
273 $ hg import -d "1000000 0" -m binaryregular - <<EOF
273 $ hg import -d "1000000 0" -m binaryregular - <<EOF
274 > diff --git a/binary b/binary
274 > diff --git a/binary b/binary
275 > new file mode 100644
275 > new file mode 100644
276 > index 0000000000000000000000000000000000000000..593f4708db84ac8fd0f5cc47c634f38c013fe9e4
276 > index 0000000000000000000000000000000000000000..593f4708db84ac8fd0f5cc47c634f38c013fe9e4
277 > GIT binary patch
277 > GIT binary patch
278 > literal 4
278 > literal 4
279 > Lc\${NkU|;|M00aO5
279 > Lc\${NkU|;|M00aO5
280 >
280 >
281 > diff --git a/foo b/foo2
281 > diff --git a/foo b/foo2
282 > rename from foo
282 > rename from foo
283 > rename to foo2
283 > rename to foo2
284 > EOF
284 > EOF
285 applying patch from stdin
285 applying patch from stdin
286
286
287 $ hg tip -q
287 $ hg tip -q
288 10:27377172366e
288 10:27377172366e
289
289
290 $ cat foo2
290 $ cat foo2
291 foo
291 foo
292
292
293 $ hg manifest --debug | grep binary
293 $ hg manifest --debug | grep binary
294 045c85ba38952325e126c70962cc0f9d9077bc67 644 binary
294 045c85ba38952325e126c70962cc0f9d9077bc67 644 binary
295
295
296 Multiple binary files:
296 Multiple binary files:
297
297
298 $ hg import -d "1000000 0" -m multibinary - <<EOF
298 $ hg import -d "1000000 0" -m multibinary - <<EOF
299 > diff --git a/mbinary1 b/mbinary1
299 > diff --git a/mbinary1 b/mbinary1
300 > new file mode 100644
300 > new file mode 100644
301 > index 0000000000000000000000000000000000000000..593f4708db84ac8fd0f5cc47c634f38c013fe9e4
301 > index 0000000000000000000000000000000000000000..593f4708db84ac8fd0f5cc47c634f38c013fe9e4
302 > GIT binary patch
302 > GIT binary patch
303 > literal 4
303 > literal 4
304 > Lc\${NkU|;|M00aO5
304 > Lc\${NkU|;|M00aO5
305 >
305 >
306 > diff --git a/mbinary2 b/mbinary2
306 > diff --git a/mbinary2 b/mbinary2
307 > new file mode 100644
307 > new file mode 100644
308 > index 0000000000000000000000000000000000000000..112363ac1917b417ffbd7f376ca786a1e5fa7490
308 > index 0000000000000000000000000000000000000000..112363ac1917b417ffbd7f376ca786a1e5fa7490
309 > GIT binary patch
309 > GIT binary patch
310 > literal 5
310 > literal 5
311 > Mc\${NkU|\`?^000jF3jhEB
311 > Mc\${NkU|\`?^000jF3jhEB
312 >
312 >
313 > EOF
313 > EOF
314 applying patch from stdin
314 applying patch from stdin
315
315
316 $ hg tip -q
316 $ hg tip -q
317 11:18b73a84b4ab
317 11:18b73a84b4ab
318
318
319 $ hg manifest --debug | grep mbinary
319 $ hg manifest --debug | grep mbinary
320 045c85ba38952325e126c70962cc0f9d9077bc67 644 mbinary1
320 045c85ba38952325e126c70962cc0f9d9077bc67 644 mbinary1
321 a874b471193996e7cb034bb301cac7bdaf3e3f46 644 mbinary2
321 a874b471193996e7cb034bb301cac7bdaf3e3f46 644 mbinary2
322
322
323 Binary file and delta hunk (we build the patch using this sed hack to
323 Binary file and delta hunk (we build the patch using this sed hack to
324 avoid an unquoted ^, which check-code says breaks sh on Solaris):
324 avoid an unquoted ^, which check-code says breaks sh on Solaris):
325
325
326 $ sed 's/ caret /^/g;s/ dollarparen /$(/g' > quote-hack.patch <<'EOF'
326 $ sed 's/ caret /^/g;s/ dollarparen /$(/g' > quote-hack.patch <<'EOF'
327 > diff --git a/delta b/delta
327 > diff --git a/delta b/delta
328 > new file mode 100644
328 > new file mode 100644
329 > index 0000000000000000000000000000000000000000..8c9b7831b231c2600843e303e66b521353a200b3
329 > index 0000000000000000000000000000000000000000..8c9b7831b231c2600843e303e66b521353a200b3
330 > GIT binary patch
330 > GIT binary patch
331 > literal 3749
331 > literal 3749
332 > zcmV;W4qEYvP)<h;3K|Lk000e1NJLTq006iE002D*0ssI2kt{U(0000PbVXQnQ*UN;
332 > zcmV;W4qEYvP)<h;3K|Lk000e1NJLTq006iE002D*0ssI2kt{U(0000PbVXQnQ*UN;
333 > zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU=M@d9MRCwC#oC!>o#}>x{(W-y~UN*tK
333 > zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU=M@d9MRCwC#oC!>o#}>x{(W-y~UN*tK
334 > z%A%sxiUy2Ys)0Vm#ueArYKoYqX;GuiqZpgirM6nCVoYk?YNAz3G~z;BZ~@~&OQEe4
334 > z%A%sxiUy2Ys)0Vm#ueArYKoYqX;GuiqZpgirM6nCVoYk?YNAz3G~z;BZ~@~&OQEe4
335 > zmGvS5isFJI;Pd_7J+EKxyHZeu` caret t4r2>F;h-+VK3{_{WoGv8dSpFDYDrA%3UX03pt
335 > zmGvS5isFJI;Pd_7J+EKxyHZeu` caret t4r2>F;h-+VK3{_{WoGv8dSpFDYDrA%3UX03pt
336 > zOaVoi0*W#P6lDr1$`nwPDWE7*rhuYM0Y#YtiZTThWeO<D6i}2YpqR<%$s>bRRaI42
336 > zOaVoi0*W#P6lDr1$`nwPDWE7*rhuYM0Y#YtiZTThWeO<D6i}2YpqR<%$s>bRRaI42
337 > zS3iFIxJ8Q=EnBv1Z7?pBw_bLjJb3V+tgP(Tty_2R-mR#p04x78n2n7MSOFyt4i1iv
337 > zS3iFIxJ8Q=EnBv1Z7?pBw_bLjJb3V+tgP(Tty_2R-mR#p04x78n2n7MSOFyt4i1iv
338 > zjxH`PPEJmgD7U?IK&h;(EGQ@_DJc<@01=4fiNXHcKZ8LhZQ8T}E3U4tUS3}OrcgQW
338 > zjxH`PPEJmgD7U?IK&h;(EGQ@_DJc<@01=4fiNXHcKZ8LhZQ8T}E3U4tUS3}OrcgQW
339 > zWdX{K8#l7Ev&#$ysR)G#0*rC+<WGZ3?CtG4bm-ve>Dj$|_qJ`@D*stNP_AFUe&x!Q
339 > zWdX{K8#l7Ev&#$ysR)G#0*rC+<WGZ3?CtG4bm-ve>Dj$|_qJ`@D*stNP_AFUe&x!Q
340 > zJ9q9B7Z=ym)MyZ?Tg1ROunUYr81nV?B@!tYS~5_|%gfW#(_s<4UN1!Q?Dv8d>g#m6
340 > zJ9q9B7Z=ym)MyZ?Tg1ROunUYr81nV?B@!tYS~5_|%gfW#(_s<4UN1!Q?Dv8d>g#m6
341 > z%*@R2@bI2JdnzxQ!EDU`$eQY!tgI~Zn$prz;gaXNod5*5p(1Bz=P$qfvZ$y?dC@X~
341 > z%*@R2@bI2JdnzxQ!EDU`$eQY!tgI~Zn$prz;gaXNod5*5p(1Bz=P$qfvZ$y?dC@X~
342 > zlAD+NAKhB{=;6bMwzjqn>9mavvKOGd`s%A+fBiL>Q;xJWpa72C+}u{JTHUX>{~}Qj
342 > zlAD+NAKhB{=;6bMwzjqn>9mavvKOGd`s%A+fBiL>Q;xJWpa72C+}u{JTHUX>{~}Qj
343 > zUb%hyHgN~c?cBLjInvUALMD9g-aXt54ZL8AOCvXL-V6!~ijR*kEG$&Mv?!pE61OlI
343 > zUb%hyHgN~c?cBLjInvUALMD9g-aXt54ZL8AOCvXL-V6!~ijR*kEG$&Mv?!pE61OlI
344 > z8nzMSPE8F7bH|Py*RNl1VUCggq<V)>@_6gkEeiz7{rmTeuNTW6+KVS#0FG%IHf-3L
344 > z8nzMSPE8F7bH|Py*RNl1VUCggq<V)>@_6gkEeiz7{rmTeuNTW6+KVS#0FG%IHf-3L
345 > zGiS21vn>WCCr+GLx caret !uNetzB6u3o(w6&1C2?_LW8ij$+$sZ*zZ`|US3H@8N~%&V%Z
345 > zGiS21vn>WCCr+GLx caret !uNetzB6u3o(w6&1C2?_LW8ij$+$sZ*zZ`|US3H@8N~%&V%Z
346 > zAeA0HdhFS=$6|nzn3%YH`SN<>DQRO;Qc caret )dfdvA caret 5u`Xf;Zzu<ZQHgG?28V-#s<;T
346 > zAeA0HdhFS=$6|nzn3%YH`SN<>DQRO;Qc caret )dfdvA caret 5u`Xf;Zzu<ZQHgG?28V-#s<;T
347 > zzkh#LA)v7gpoE5ou3o*GoUUF%b#iht&kl9d0)><$FE1}ACr68;uCA`6DrGmz_U+rp
347 > zzkh#LA)v7gpoE5ou3o*GoUUF%b#iht&kl9d0)><$FE1}ACr68;uCA`6DrGmz_U+rp
348 > zL>Rx;X_yhk$fP_yJrTCQ|NgsW0A<985g&c@k-NKly<>mgU8n||ZPPV<`SN8#%$+-T
348 > zL>Rx;X_yhk$fP_yJrTCQ|NgsW0A<985g&c@k-NKly<>mgU8n||ZPPV<`SN8#%$+-T
349 > zfP$T!ou8jypFVwnzqhxyUvIxXd-wF~*U!ht=hCH1wzjqn9x#)IrhDa;S0JbK caret z_$W
349 > zfP$T!ou8jypFVwnzqhxyUvIxXd-wF~*U!ht=hCH1wzjqn9x#)IrhDa;S0JbK caret z_$W
350 > zd(8rX@;7|t*;GJ5h$SZ{v(}+UBEs$4w~?{@9%`_Z<P<kox5bMWuUWH(sF9hONgd$Q
350 > zd(8rX@;7|t*;GJ5h$SZ{v(}+UBEs$4w~?{@9%`_Z<P<kox5bMWuUWH(sF9hONgd$Q
351 > zunCgwT@1|CU9+;X caret 4z&|M~@yw23Ay50NFWn=FqF%yLZEUty;AT2??1oV@B)Nt))J7
351 > zunCgwT@1|CU9+;X caret 4z&|M~@yw23Ay50NFWn=FqF%yLZEUty;AT2??1oV@B)Nt))J7
352 > zh>{5j2@f7T=-an%L_`E)h;mZ4D_5>?7tjQtVPRo2XU-&;mX(!l-MSTJP4XWY82JAC
352 > zh>{5j2@f7T=-an%L_`E)h;mZ4D_5>?7tjQtVPRo2XU-&;mX(!l-MSTJP4XWY82JAC
353 > z@57+y&!1=P{Mn{W8)-HzEsgAtd63}Cazc>O6vGb>51%@9DzbyI3?4j~$ijmT95_IS
353 > z@57+y&!1=P{Mn{W8)-HzEsgAtd63}Cazc>O6vGb>51%@9DzbyI3?4j~$ijmT95_IS
354 > zS#r!LCDW%*4-O7CGnkr$xXR1RQ&UrA<CQt} caret 73NL%zk`)Jk!yxUAt-1r}ggLn-Zq}
354 > zS#r!LCDW%*4-O7CGnkr$xXR1RQ&UrA<CQt} caret 73NL%zk`)Jk!yxUAt-1r}ggLn-Zq}
355 > z*s){8pw68;i+kiG%CpBKYSJLLFyq&*U8}qDp+kpe&6<Vp(Z58%l#~>ZK?&s7y?b}i
355 > z*s){8pw68;i+kiG%CpBKYSJLLFyq&*U8}qDp+kpe&6<Vp(Z58%l#~>ZK?&s7y?b}i
356 > zuwcOgO%x-27A;y785zknl_{sU;E6v$8{pWmVS{KaJPpu`i;HP$#flY@u~Ua~K3%tN
356 > zuwcOgO%x-27A;y785zknl_{sU;E6v$8{pWmVS{KaJPpu`i;HP$#flY@u~Ua~K3%tN
357 > z-LhrNh{9SoHgDd%WXTc$$~Dq{?AWou3!H&?V8K{ caret {P9Ot5vecD?%1&-E-ntBFj87(
357 > z-LhrNh{9SoHgDd%WXTc$$~Dq{?AWou3!H&?V8K{ caret {P9Ot5vecD?%1&-E-ntBFj87(
358 > zy5`QE%QRX7qcHC%1{Ua}M~}L6=`wQUNEQ=I;qc+ZMMXtK2T+0os;jEco;}OV9z1w3
358 > zy5`QE%QRX7qcHC%1{Ua}M~}L6=`wQUNEQ=I;qc+ZMMXtK2T+0os;jEco;}OV9z1w3
359 > zARqv caret bm-85xnRCng3OT|MyVSmR3ND7 caret ?KaQGG! caret (aTbo1N;Nz;X3Q9FJbwK6`0?Yp
359 > zARqv caret bm-85xnRCng3OT|MyVSmR3ND7 caret ?KaQGG! caret (aTbo1N;Nz;X3Q9FJbwK6`0?Yp
360 > zj*X2ac;Pw3!I2|JShDaF>-gJmzm1NLj){rk&o|$E caret WAsfrK=x&@B!`w7Hik81sPz4
360 > zj*X2ac;Pw3!I2|JShDaF>-gJmzm1NLj){rk&o|$E caret WAsfrK=x&@B!`w7Hik81sPz4
361 > zuJTaiCppM>-+c!wPzcUw)5@?J4U-u|pJ~xbWUe-C+60k caret 7>9!)56DbjmA~`OJJ40v
361 > zuJTaiCppM>-+c!wPzcUw)5@?J4U-u|pJ~xbWUe-C+60k caret 7>9!)56DbjmA~`OJJ40v
362 > zu3hCA7eJXZWeN|1iJLu87$;+fS8+Kq6O`aT)*_x@sY#t7LxwoEcVw*)cWhhQW@l%!
362 > zu3hCA7eJXZWeN|1iJLu87$;+fS8+Kq6O`aT)*_x@sY#t7LxwoEcVw*)cWhhQW@l%!
363 > z{#Z=y+qcK@%z{p*D=8_Fcg278AnH3fI5;~yGu?9TscxXaaP*4$f<LIv! caret 5Lfr%vKg
363 > z{#Z=y+qcK@%z{p*D=8_Fcg278AnH3fI5;~yGu?9TscxXaaP*4$f<LIv! caret 5Lfr%vKg
364 > zpxmunH#%=+ICMvZA~wyNH%~eMl!-g caret R!cYJ#WmLq5N8viz#J%%LPtkO?V)tZ81cp>
364 > zpxmunH#%=+ICMvZA~wyNH%~eMl!-g caret R!cYJ#WmLq5N8viz#J%%LPtkO?V)tZ81cp>
365 > z{ALK?fNPePmd;289&M8Q3>YwgZX5GcGY&n>K1<x)!`;Qjg&}bb!Lrnl@xH#kS~VYE
365 > z{ALK?fNPePmd;289&M8Q3>YwgZX5GcGY&n>K1<x)!`;Qjg&}bb!Lrnl@xH#kS~VYE
366 > zpJmIJO`A3iy+Y3X`k>cY-@}Iw2Onq`=!ba3eATgs3yg3Wej=+P-Z8WF#w=RXvS@J3
366 > zpJmIJO`A3iy+Y3X`k>cY-@}Iw2Onq`=!ba3eATgs3yg3Wej=+P-Z8WF#w=RXvS@J3
367 > zEyhVTj-gO?kfDu1g9afo<RkPrYzG#_yF41IFxF%Ylg>9lx6<clPweR-b7Hn+r)e1l
367 > zEyhVTj-gO?kfDu1g9afo<RkPrYzG#_yF41IFxF%Ylg>9lx6<clPweR-b7Hn+r)e1l
368 > zO6c6FbNt@;;*w$z;N|H>h{czme)_4V6UC4hv**kX2@L caret Bgds dollarparen &P7M4dhfmWe)!=B
368 > zO6c6FbNt@;;*w$z;N|H>h{czme)_4V6UC4hv**kX2@L caret Bgds dollarparen &P7M4dhfmWe)!=B
369 > zR3X=Y{P9N}p@-##@1ZNW1YbVaiP~D@8m&<dzEP&cO|87Ju#j*=;wH~Exr>i*Hpp&@
369 > zR3X=Y{P9N}p@-##@1ZNW1YbVaiP~D@8m&<dzEP&cO|87Ju#j*=;wH~Exr>i*Hpp&@
370 > z`9!Sj+O;byD~s8qZ>6QB8uv7Bpn&&?xe;;e<M4F8KEID&pT7QmqoSgq&06adp5T=U
370 > z`9!Sj+O;byD~s8qZ>6QB8uv7Bpn&&?xe;;e<M4F8KEID&pT7QmqoSgq&06adp5T=U
371 > z6DH*4=AB7C1D9Amu?ia-wtxSAlmTEO96XHx)-+rKP;ip$pukuSJGW3P1aUmc2yo%)
371 > z6DH*4=AB7C1D9Amu?ia-wtxSAlmTEO96XHx)-+rKP;ip$pukuSJGW3P1aUmc2yo%)
372 > z&<t3F>d1X+1qzaag-%x+eKHx{?Afz3GBQSw9u0lw<mB+I#v11TKRpKWQS+lvVL7=u
372 > z&<t3F>d1X+1qzaag-%x+eKHx{?Afz3GBQSw9u0lw<mB+I#v11TKRpKWQS+lvVL7=u
373 > zHr6)1ynEF<i3kO6A8&ppPMo-F=PnWfXkSj@i*7J6C<F}wR?s(O0niC?t+6;+k}pPq
373 > zHr6)1ynEF<i3kO6A8&ppPMo-F=PnWfXkSj@i*7J6C<F}wR?s(O0niC?t+6;+k}pPq
374 > zrok&TPU40rL0ZYDwenNrrmPZ`gjo@DEF`7 caret cKP||pUr;+r)hyn9O37=xA`3%Bj-ih
374 > zrok&TPU40rL0ZYDwenNrrmPZ`gjo@DEF`7 caret cKP||pUr;+r)hyn9O37=xA`3%Bj-ih
375 > z+1usk<%5G-y+R?tA`qY=)6&vNjL{P?QzHg%P%>`ZxP=QB%DHY6L26?36V_p caret {}n$q
375 > z+1usk<%5G-y+R?tA`qY=)6&vNjL{P?QzHg%P%>`ZxP=QB%DHY6L26?36V_p caret {}n$q
376 > z3@9W=KmGI*Ng_Q#AzA%-z|Z caret |#oW(hkfgpuS$RKRhlrarX%efMMCs}GLChec5+y{6
376 > z3@9W=KmGI*Ng_Q#AzA%-z|Z caret |#oW(hkfgpuS$RKRhlrarX%efMMCs}GLChec5+y{6
377 > z1Qnxim_C-fmQuaAK_NUHUBV&;1c0V)wji<RcdZ*aAWTwyt>hVnlt caret asFCe0&a@tqp
377 > z1Qnxim_C-fmQuaAK_NUHUBV&;1c0V)wji<RcdZ*aAWTwyt>hVnlt caret asFCe0&a@tqp
378 > zEEy;$L}D$X6)wfQNl8gu6Z>oB3_RrP=gTyK2@@w#LbQfLNHj>Q&z(C5wUFhK+}0aV
378 > zEEy;$L}D$X6)wfQNl8gu6Z>oB3_RrP=gTyK2@@w#LbQfLNHj>Q&z(C5wUFhK+}0aV
379 > zSohlc=7K+spN<ctf}5KgKqNyJDNP9;LZd)nTE=9|6Xdr9%Hzk63-tL2c9FD*rsyYY
379 > zSohlc=7K+spN<ctf}5KgKqNyJDNP9;LZd)nTE=9|6Xdr9%Hzk63-tL2c9FD*rsyYY
380 > z!}t+Yljq7-p$X;4_YL?6d;mdY3R##o1e%rlPxrsMh8|;sKTr~ caret QD#sw3&vS$FwlTk
380 > z!}t+Yljq7-p$X;4_YL?6d;mdY3R##o1e%rlPxrsMh8|;sKTr~ caret QD#sw3&vS$FwlTk
381 > zp1#Gw!Qo-$LtvpXt#ApV0g) caret F=qFB`VB!W297x=$mr<$>rco3v$QKih_xN!k6;M=@
381 > zp1#Gw!Qo-$LtvpXt#ApV0g) caret F=qFB`VB!W297x=$mr<$>rco3v$QKih_xN!k6;M=@
382 > zCr?gDNQj7tm@;JwD;Ty&NlBSCYZk(b3dZeN8D4h2{r20dSFc7;(>E&r`s=TVtzpB4
382 > zCr?gDNQj7tm@;JwD;Ty&NlBSCYZk(b3dZeN8D4h2{r20dSFc7;(>E&r`s=TVtzpB4
383 > zk+ caret N&zCAiRns(?p6iBlk9v&h{1ve(FNtc)td51M>)TkXhc6{>5C)`fS$&)A1*CP1%
383 > zk+ caret N&zCAiRns(?p6iBlk9v&h{1ve(FNtc)td51M>)TkXhc6{>5C)`fS$&)A1*CP1%
384 > zld+peue4aYbg3C0!+4mu+}vE caret j_feX+ZijvffBI7Ofh#RZ*U3<3J5(+nfRCzexqQ5
384 > zld+peue4aYbg3C0!+4mu+}vE caret j_feX+ZijvffBI7Ofh#RZ*U3<3J5(+nfRCzexqQ5
385 > zgM&##Y4Dd{e%ZKjqrbm@|Ni}l4jo!AqtFynj3Xsd$o caret ?yV4$|UQ(j&UWCH>M=o_&N
385 > zgM&##Y4Dd{e%ZKjqrbm@|Ni}l4jo!AqtFynj3Xsd$o caret ?yV4$|UQ(j&UWCH>M=o_&N
386 > zmclXc3i|Q#<;#EoG>~V}4unTHbUK}u=y4;rA3S&vzC3 caret aJP!&D4RvvGfoyo(>C>la
386 > zmclXc3i|Q#<;#EoG>~V}4unTHbUK}u=y4;rA3S&vzC3 caret aJP!&D4RvvGfoyo(>C>la
387 > zijP<=v>X{3Ne&2BXo}DV8l0V-jdv`$am0ubG{Wuh%CTd|l9Q7m;G&|U@#Dvbhlj(d
387 > zijP<=v>X{3Ne&2BXo}DV8l0V-jdv`$am0ubG{Wuh%CTd|l9Q7m;G&|U@#Dvbhlj(d
388 > zg6W{3ATxYt#T?)3;SmIgOP4M|Dki~I_TX7SxP0x}wI~DQI7Lhm2BI7gph(aPIFAd;
388 > zg6W{3ATxYt#T?)3;SmIgOP4M|Dki~I_TX7SxP0x}wI~DQI7Lhm2BI7gph(aPIFAd;
389 > zQ&UsF`Q{rOz+z=87c5v%@5u~d6dWV5OlX`oH3cAH&UlvsZUEo(Q(P|lKs17rXvaiU
389 > zQ&UsF`Q{rOz+z=87c5v%@5u~d6dWV5OlX`oH3cAH&UlvsZUEo(Q(P|lKs17rXvaiU
390 > zQcj}IEufi1+Bnh6&(EhF{7O3vLHp`jjlp0J<M1kh$+$2xGm~Zk7OY7(q=&Rdhq*RG
390 > zQcj}IEufi1+Bnh6&(EhF{7O3vLHp`jjlp0J<M1kh$+$2xGm~Zk7OY7(q=&Rdhq*RG
391 > zwrmcd5MnP}xByB_)P@{J>DR9x6;`cUwPM8z){yooNiXPOc9_{W-gtwxE5TUg0vJk6
391 > zwrmcd5MnP}xByB_)P@{J>DR9x6;`cUwPM8z){yooNiXPOc9_{W-gtwxE5TUg0vJk6
392 > zO#JGruV&1cL6VGK2?+_YQr4`+EY8;Sm$9U$uuGRN=uj3k7?O9b+R~J7t_y*K64ZnI
392 > zO#JGruV&1cL6VGK2?+_YQr4`+EY8;Sm$9U$uuGRN=uj3k7?O9b+R~J7t_y*K64ZnI
393 > zM+{aE<b(v?vSmw;9zFP!aE266zHIhlmdI@ caret xa6o2jwdRk54a$>pcRbC29ZyG!Cfdp
393 > zM+{aE<b(v?vSmw;9zFP!aE266zHIhlmdI@ caret xa6o2jwdRk54a$>pcRbC29ZyG!Cfdp
394 > zutFf`Q`vljgo!(wHf=)F#m2_MIuj;L(2ja2YsQRX+rswV{d<H`Ar;(@%aNa9VPU8Z
394 > zutFf`Q`vljgo!(wHf=)F#m2_MIuj;L(2ja2YsQRX+rswV{d<H`Ar;(@%aNa9VPU8Z
395 > z;tq*`y}dm#NDJHKlV}uTIm!_vAq5E7!X-p{P=Z=Sh668>PuVS1*6e}OwOiMc;u3OQ
395 > z;tq*`y}dm#NDJHKlV}uTIm!_vAq5E7!X-p{P=Z=Sh668>PuVS1*6e}OwOiMc;u3OQ
396 > z@Bs)w3=lzfKoufH$SFuPG@uZ4NOnM#+=8LnQ2Q4zUd+nM+OT26;lqbN{P07dhH{jH
396 > z@Bs)w3=lzfKoufH$SFuPG@uZ4NOnM#+=8LnQ2Q4zUd+nM+OT26;lqbN{P07dhH{jH
397 > zManE8 caret dLms-Q2;1kB<*Q1a3f8kZr;xX=!Qro@`~@xN*Qj>gx;i;0Z24!~i2uLb`}v
397 > zManE8 caret dLms-Q2;1kB<*Q1a3f8kZr;xX=!Qro@`~@xN*Qj>gx;i;0Z24!~i2uLb`}v
398 > zA?R$|wvC+m caret Ups=*(4lDh*=UN8{5h(A?p#D caret 2N$8u4Z55!q?ZAh(iEEng9_Zi>IgO
398 > zA?R$|wvC+m caret Ups=*(4lDh*=UN8{5h(A?p#D caret 2N$8u4Z55!q?ZAh(iEEng9_Zi>IgO
399 > z#~**JC8hE4@n{hO&8btT5F*?nC_%LhA3i)PDhh-pB_&1wGrDIl caret *=8x3n&;akBf caret -
399 > z#~**JC8hE4@n{hO&8btT5F*?nC_%LhA3i)PDhh-pB_&1wGrDIl caret *=8x3n&;akBf caret -
400 > zJd&86kq$%%907v caret tgWoQdwI`|oNK%VvU~S#C<o caret F?6c48?Cjj#-4P<>HFD%&|Ni~t
400 > zJd&86kq$%%907v caret tgWoQdwI`|oNK%VvU~S#C<o caret F?6c48?Cjj#-4P<>HFD%&|Ni~t
401 > zKJ(|#H`$<5W+6ZkBb213rXonKZLB+X> caret L}J@W6osP3piLD_5?R!`S}*{xLBzFiL4@
401 > zKJ(|#H`$<5W+6ZkBb213rXonKZLB+X> caret L}J@W6osP3piLD_5?R!`S}*{xLBzFiL4@
402 > zX+}l{`A%?f@T5tT%ztu60p;)be`fWC`tP@WpO=?cpf8Xuf1OSj6d3f@Ki(ovDYq%0
402 > zX+}l{`A%?f@T5tT%ztu60p;)be`fWC`tP@WpO=?cpf8Xuf1OSj6d3f@Ki(ovDYq%0
403 > z{4ZSe`kOay5@=lAT!}vFzxyemC{sXDrhuYM0Y#ZI1r%ipD9W11{w=@&xgJ}t2x;ep
403 > z{4ZSe`kOay5@=lAT!}vFzxyemC{sXDrhuYM0Y#ZI1r%ipD9W11{w=@&xgJ}t2x;ep
404 > P00000NkvXXu0mjfZ5|Er
404 > P00000NkvXXu0mjfZ5|Er
405 >
405 >
406 > literal 0
406 > literal 0
407 > HcmV?d00001
407 > HcmV?d00001
408 >
408 >
409 > EOF
409 > EOF
410 $ hg import -d "1000000 0" -m delta quote-hack.patch
410 $ hg import -d "1000000 0" -m delta quote-hack.patch
411 applying quote-hack.patch
411 applying quote-hack.patch
412 $ rm quote-hack.patch
412 $ rm quote-hack.patch
413
413
414 $ hg manifest --debug | grep delta
414 $ hg manifest --debug | grep delta
415 9600f98bb60ce732634d126aaa4ac1ec959c573e 644 delta
415 9600f98bb60ce732634d126aaa4ac1ec959c573e 644 delta
416
416
417 $ hg import -d "1000000 0" -m delta - <<'EOF'
417 $ hg import -d "1000000 0" -m delta - <<'EOF'
418 > diff --git a/delta b/delta
418 > diff --git a/delta b/delta
419 > index 8c9b7831b231c2600843e303e66b521353a200b3..0021dd95bc0dba53c39ce81377126d43731d68df 100644
419 > index 8c9b7831b231c2600843e303e66b521353a200b3..0021dd95bc0dba53c39ce81377126d43731d68df 100644
420 > GIT binary patch
420 > GIT binary patch
421 > delta 49
421 > delta 49
422 > zcmZ1~yHs|=21Z8J$r~9bFdA-lVv=EEw4WT$qRf2QSa5SIOAHI6(&k4T8H|kLo4vWB
422 > zcmZ1~yHs|=21Z8J$r~9bFdA-lVv=EEw4WT$qRf2QSa5SIOAHI6(&k4T8H|kLo4vWB
423 > FSO9ZT4bA`n
423 > FSO9ZT4bA`n
424 >
424 >
425 > delta 49
425 > delta 49
426 > zcmV-10M7rV9i<(xumJ(}ld%Di0Xefm0vrMXpOaq%BLm9I%d>?9Tm%6Vv*HM70RcC&
426 > zcmV-10M7rV9i<(xumJ(}ld%Di0Xefm0vrMXpOaq%BLm9I%d>?9Tm%6Vv*HM70RcC&
427 > HOA1;9yU-AD
427 > HOA1;9yU-AD
428 >
428 >
429 > EOF
429 > EOF
430 applying patch from stdin
430 applying patch from stdin
431
431
432 $ hg manifest --debug | grep delta
432 $ hg manifest --debug | grep delta
433 56094bbea136dcf8dbd4088f6af469bde1a98b75 644 delta
433 56094bbea136dcf8dbd4088f6af469bde1a98b75 644 delta
434
434
435 Filenames with spaces:
435 Filenames with spaces:
436
436
437 $ sed 's,EOL$,,g' <<EOF | hg import -d "1000000 0" -m spaces -
437 $ sed 's,EOL$,,g' <<EOF | hg import -d "1000000 0" -m spaces -
438 > diff --git a/foo bar b/foo bar
438 > diff --git a/foo bar b/foo bar
439 > new file mode 100644
439 > new file mode 100644
440 > index 0000000..257cc56
440 > index 0000000..257cc56
441 > --- /dev/null
441 > --- /dev/null
442 > +++ b/foo bar EOL
442 > +++ b/foo bar EOL
443 > @@ -0,0 +1 @@
443 > @@ -0,0 +1 @@
444 > +foo
444 > +foo
445 > EOF
445 > EOF
446 applying patch from stdin
446 applying patch from stdin
447
447
448 $ hg tip -q
448 $ hg tip -q
449 14:4b79479c9a6d
449 14:4b79479c9a6d
450
450
451 $ cat "foo bar"
451 $ cat "foo bar"
452 foo
452 foo
453
453
454 Copy then modify the original file:
454 Copy then modify the original file:
455
455
456 $ hg import -d "1000000 0" -m copy-mod-orig - <<EOF
456 $ hg import -d "1000000 0" -m copy-mod-orig - <<EOF
457 > diff --git a/foo2 b/foo2
457 > diff --git a/foo2 b/foo2
458 > index 257cc56..fe08ec6 100644
458 > index 257cc56..fe08ec6 100644
459 > --- a/foo2
459 > --- a/foo2
460 > +++ b/foo2
460 > +++ b/foo2
461 > @@ -1 +1,2 @@
461 > @@ -1 +1,2 @@
462 > foo
462 > foo
463 > +new line
463 > +new line
464 > diff --git a/foo2 b/foo3
464 > diff --git a/foo2 b/foo3
465 > similarity index 100%
465 > similarity index 100%
466 > copy from foo2
466 > copy from foo2
467 > copy to foo3
467 > copy to foo3
468 > EOF
468 > EOF
469 applying patch from stdin
469 applying patch from stdin
470
470
471 $ hg tip -q
471 $ hg tip -q
472 15:9cbe44af4ae9
472 15:9cbe44af4ae9
473
473
474 $ cat foo3
474 $ cat foo3
475 foo
475 foo
476
476
477 Move text file and patch as binary
477 Move text file and patch as binary
478
478
479 $ echo a > text2
479 $ echo a > text2
480 $ hg ci -Am0
480 $ hg ci -Am0
481 adding text2
481 adding text2
482 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
482 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
483 > diff --git a/text2 b/binary2
483 > diff --git a/text2 b/binary2
484 > rename from text2
484 > rename from text2
485 > rename to binary2
485 > rename to binary2
486 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
486 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
487 > GIT binary patch
487 > GIT binary patch
488 > literal 5
488 > literal 5
489 > Mc$`b*O5$Pw00T?_*Z=?k
489 > Mc$`b*O5$Pw00T?_*Z=?k
490 >
490 >
491 > EOF
491 > EOF
492 applying patch from stdin
492 applying patch from stdin
493
493
494 $ cat binary2
494 $ cat binary2
495 a
495 a
496 b
496 b
497 \x00 (no-eol) (esc)
497 \x00 (no-eol) (esc)
498
498
499 $ hg st --copies --change .
499 $ hg st --copies --change .
500 A binary2
500 A binary2
501 text2
501 text2
502 R text2
502 R text2
503
503
504 Invalid base85 content
504 Invalid base85 content
505
505
506 $ hg rollback
506 $ hg rollback
507 repository tip rolled back to revision 16 (undo import)
507 repository tip rolled back to revision 16 (undo import)
508 working directory now based on revision 16
508 working directory now based on revision 16
509 $ hg revert -aq
509 $ hg revert -aq
510 $ hg import -d "1000000 0" -m invalid-binary - <<"EOF"
510 $ hg import -d "1000000 0" -m invalid-binary - <<"EOF"
511 > diff --git a/text2 b/binary2
511 > diff --git a/text2 b/binary2
512 > rename from text2
512 > rename from text2
513 > rename to binary2
513 > rename to binary2
514 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
514 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
515 > GIT binary patch
515 > GIT binary patch
516 > literal 5
516 > literal 5
517 > Mc$`b*O.$Pw00T?_*Z=?k
517 > Mc$`b*O.$Pw00T?_*Z=?k
518 >
518 >
519 > EOF
519 > EOF
520 applying patch from stdin
520 applying patch from stdin
521 abort: could not decode "binary2" binary patch: bad base85 character at position 6
521 abort: could not decode "binary2" binary patch: bad base85 character at position 6
522 [255]
522 [255]
523
523
524 $ hg revert -aq
524 $ hg revert -aq
525 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
525 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
526 > diff --git a/text2 b/binary2
526 > diff --git a/text2 b/binary2
527 > rename from text2
527 > rename from text2
528 > rename to binary2
528 > rename to binary2
529 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
529 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
530 > GIT binary patch
530 > GIT binary patch
531 > literal 6
531 > literal 6
532 > Mc$`b*O5$Pw00T?_*Z=?k
532 > Mc$`b*O5$Pw00T?_*Z=?k
533 >
533 >
534 > EOF
534 > EOF
535 applying patch from stdin
535 applying patch from stdin
536 abort: "binary2" length is 5 bytes, should be 6
536 abort: "binary2" length is 5 bytes, should be 6
537 [255]
537 [255]
538
538
539 $ hg revert -aq
539 $ hg revert -aq
540 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
540 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
541 > diff --git a/text2 b/binary2
541 > diff --git a/text2 b/binary2
542 > rename from text2
542 > rename from text2
543 > rename to binary2
543 > rename to binary2
544 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
544 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
545 > GIT binary patch
545 > GIT binary patch
546 > Mc$`b*O5$Pw00T?_*Z=?k
546 > Mc$`b*O5$Pw00T?_*Z=?k
547 >
547 >
548 > EOF
548 > EOF
549 applying patch from stdin
549 applying patch from stdin
550 abort: could not extract "binary2" binary data
550 abort: could not extract "binary2" binary data
551 [255]
551 [255]
552
552
553 Simulate a copy/paste turning LF into CRLF (issue2870)
553 Simulate a copy/paste turning LF into CRLF (issue2870)
554
554
555 $ hg revert -aq
555 $ hg revert -aq
556 $ cat > binary.diff <<"EOF"
556 $ cat > binary.diff <<"EOF"
557 > diff --git a/text2 b/binary2
557 > diff --git a/text2 b/binary2
558 > rename from text2
558 > rename from text2
559 > rename to binary2
559 > rename to binary2
560 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
560 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
561 > GIT binary patch
561 > GIT binary patch
562 > literal 5
562 > literal 5
563 > Mc$`b*O5$Pw00T?_*Z=?k
563 > Mc$`b*O5$Pw00T?_*Z=?k
564 >
564 >
565 > EOF
565 > EOF
566 >>> fp = file('binary.diff', 'rb')
566 >>> fp = file('binary.diff', 'rb')
567 >>> data = fp.read()
567 >>> data = fp.read()
568 >>> fp.close()
568 >>> fp.close()
569 >>> file('binary.diff', 'wb').write(data.replace('\n', '\r\n'))
569 >>> file('binary.diff', 'wb').write(data.replace('\n', '\r\n'))
570 $ rm binary2
570 $ rm binary2
571 $ hg import --no-commit binary.diff
571 $ hg import --no-commit binary.diff
572 applying binary.diff
572 applying binary.diff
573
573
574 $ cd ..
574 $ cd ..
575
575
576 Consecutive import with renames (issue2459)
576 Consecutive import with renames (issue2459)
577
577
578 $ hg init issue2459
578 $ hg init issue2459
579 $ cd issue2459
579 $ cd issue2459
580 $ hg import --no-commit --force - <<EOF
580 $ hg import --no-commit --force - <<EOF
581 > diff --git a/a b/a
581 > diff --git a/a b/a
582 > new file mode 100644
582 > new file mode 100644
583 > EOF
583 > EOF
584 applying patch from stdin
584 applying patch from stdin
585 $ hg import --no-commit --force - <<EOF
585 $ hg import --no-commit --force - <<EOF
586 > diff --git a/a b/b
586 > diff --git a/a b/b
587 > rename from a
587 > rename from a
588 > rename to b
588 > rename to b
589 > EOF
589 > EOF
590 applying patch from stdin
590 applying patch from stdin
591 a has not been committed yet, so no copy data will be stored for b.
591 a has not been committed yet, so no copy data will be stored for b.
592 $ hg debugstate
592 $ hg debugstate
593 a 0 -1 unset b
593 a 0 -1 unset b
594 $ hg ci -m done
594 $ hg ci -m done
595 $ cd ..
595 $ cd ..
596
596
597 Renames and strip
597 Renames and strip
598
598
599 $ hg init renameandstrip
599 $ hg init renameandstrip
600 $ cd renameandstrip
600 $ cd renameandstrip
601 $ echo a > a
601 $ echo a > a
602 $ hg ci -Am adda
602 $ hg ci -Am adda
603 adding a
603 adding a
604 $ hg import --no-commit -p2 - <<EOF
604 $ hg import --no-commit -p2 - <<EOF
605 > diff --git a/foo/a b/foo/b
605 > diff --git a/foo/a b/foo/b
606 > rename from foo/a
606 > rename from foo/a
607 > rename to foo/b
607 > rename to foo/b
608 > EOF
608 > EOF
609 applying patch from stdin
609 applying patch from stdin
610 $ hg st --copies
610 $ hg st --copies
611 A b
611 A b
612 a
612 a
613 R a
613 R a
614
614
615 Prefix with strip, renames, creates etc
615 Prefix with strip, renames, creates etc
616
616
617 $ hg revert -aC
617 $ hg revert -aC
618 undeleting a
618 undeleting a
619 forgetting b
619 forgetting b
620 $ rm b
620 $ rm b
621 $ mkdir -p dir/dir2
621 $ mkdir -p dir/dir2
622 $ echo b > dir/dir2/b
622 $ echo b > dir/dir2/b
623 $ echo c > dir/dir2/c
623 $ echo c > dir/dir2/c
624 $ echo d > dir/d
624 $ echo d > dir/d
625 $ hg ci -Am addbcd
625 $ hg ci -Am addbcd
626 adding dir/d
626 adding dir/d
627 adding dir/dir2/b
627 adding dir/dir2/b
628 adding dir/dir2/c
628 adding dir/dir2/c
629 $ hg import --no-commit --prefix dir/ - <<EOF
630 > diff --git a/a b/a
631 > --- /dev/null
632 > +++ b/a
633 > @@ -0,0 +1 @@
634 > +aaa
635 > diff --git a/d b/d
636 > --- a/d
637 > +++ b/d
638 > @@ -1,1 +1,2 @@
639 > d
640 > +dd
641 > EOF
642 applying patch from stdin
643 $ cat dir/a
644 aaa
645 $ cat dir/d
646 d
647 dd
648 $ hg revert -aC
649 forgetting dir/a (glob)
650 reverting dir/d (glob)
651 $ rm dir/a
629 (test that prefixes are relative to the root)
652 (test that prefixes are relative to the root)
630 $ mkdir tmpdir
653 $ mkdir tmpdir
631 $ cd tmpdir
654 $ cd tmpdir
632 $ hg import --no-commit -p2 --prefix dir/ - <<EOF
655 $ hg import --no-commit -p2 --prefix dir/ - <<EOF
633 > diff --git a/foo/a b/foo/a
656 > diff --git a/foo/a b/foo/a
634 > new file mode 100644
657 > new file mode 100644
635 > --- /dev/null
658 > --- /dev/null
636 > +++ b/foo/a
659 > +++ b/foo/a
637 > @@ -0,0 +1 @@
660 > @@ -0,0 +1 @@
638 > +a
661 > +a
639 > diff --git a/foo/dir2/b b/foo/dir2/b2
662 > diff --git a/foo/dir2/b b/foo/dir2/b2
640 > rename from foo/dir2/b
663 > rename from foo/dir2/b
641 > rename to foo/dir2/b2
664 > rename to foo/dir2/b2
642 > diff --git a/foo/dir2/c b/foo/dir2/c
665 > diff --git a/foo/dir2/c b/foo/dir2/c
643 > --- a/foo/dir2/c
666 > --- a/foo/dir2/c
644 > +++ b/foo/dir2/c
667 > +++ b/foo/dir2/c
645 > @@ -0,0 +1 @@
668 > @@ -0,0 +1 @@
646 > +cc
669 > +cc
647 > diff --git a/foo/d b/foo/d
670 > diff --git a/foo/d b/foo/d
648 > deleted file mode 100644
671 > deleted file mode 100644
649 > --- a/foo/d
672 > --- a/foo/d
650 > +++ /dev/null
673 > +++ /dev/null
651 > @@ -1,1 +0,0 @@
674 > @@ -1,1 +0,0 @@
652 > -d
675 > -d
653 > EOF
676 > EOF
654 applying patch from stdin
677 applying patch from stdin
655 $ hg st --copies
678 $ hg st --copies
656 M dir/dir2/c
679 M dir/dir2/c
657 A dir/a
680 A dir/a
658 A dir/dir2/b2
681 A dir/dir2/b2
659 dir/dir2/b
682 dir/dir2/b
660 R dir/d
683 R dir/d
661 R dir/dir2/b
684 R dir/dir2/b
662 $ cd ..
685 $ cd ..
663
686
664 Renames, similarity and git diff
687 Renames, similarity and git diff
665
688
666 $ hg revert -aC
689 $ hg revert -aC
667 forgetting dir/a (glob)
690 forgetting dir/a (glob)
668 undeleting dir/d (glob)
691 undeleting dir/d (glob)
669 undeleting dir/dir2/b (glob)
692 undeleting dir/dir2/b (glob)
670 forgetting dir/dir2/b2 (glob)
693 forgetting dir/dir2/b2 (glob)
671 reverting dir/dir2/c (glob)
694 reverting dir/dir2/c (glob)
672 $ rm dir/a dir/dir2/b2
695 $ rm dir/a dir/dir2/b2
673 $ hg import --similarity 90 --no-commit - <<EOF
696 $ hg import --similarity 90 --no-commit - <<EOF
674 > diff --git a/a b/b
697 > diff --git a/a b/b
675 > rename from a
698 > rename from a
676 > rename to b
699 > rename to b
677 > EOF
700 > EOF
678 applying patch from stdin
701 applying patch from stdin
679 $ hg st --copies
702 $ hg st --copies
680 A b
703 A b
681 a
704 a
682 R a
705 R a
683 $ cd ..
706 $ cd ..
684
707
685 Pure copy with existing destination
708 Pure copy with existing destination
686
709
687 $ hg init copytoexisting
710 $ hg init copytoexisting
688 $ cd copytoexisting
711 $ cd copytoexisting
689 $ echo a > a
712 $ echo a > a
690 $ echo b > b
713 $ echo b > b
691 $ hg ci -Am add
714 $ hg ci -Am add
692 adding a
715 adding a
693 adding b
716 adding b
694 $ hg import --no-commit - <<EOF
717 $ hg import --no-commit - <<EOF
695 > diff --git a/a b/b
718 > diff --git a/a b/b
696 > copy from a
719 > copy from a
697 > copy to b
720 > copy to b
698 > EOF
721 > EOF
699 applying patch from stdin
722 applying patch from stdin
700 abort: cannot create b: destination already exists
723 abort: cannot create b: destination already exists
701 [255]
724 [255]
702 $ cat b
725 $ cat b
703 b
726 b
704
727
705 Copy and changes with existing destination
728 Copy and changes with existing destination
706
729
707 $ hg import --no-commit - <<EOF
730 $ hg import --no-commit - <<EOF
708 > diff --git a/a b/b
731 > diff --git a/a b/b
709 > copy from a
732 > copy from a
710 > copy to b
733 > copy to b
711 > --- a/a
734 > --- a/a
712 > +++ b/b
735 > +++ b/b
713 > @@ -1,1 +1,2 @@
736 > @@ -1,1 +1,2 @@
714 > a
737 > a
715 > +b
738 > +b
716 > EOF
739 > EOF
717 applying patch from stdin
740 applying patch from stdin
718 cannot create b: destination already exists
741 cannot create b: destination already exists
719 1 out of 1 hunks FAILED -- saving rejects to file b.rej
742 1 out of 1 hunks FAILED -- saving rejects to file b.rej
720 abort: patch failed to apply
743 abort: patch failed to apply
721 [255]
744 [255]
722 $ cat b
745 $ cat b
723 b
746 b
724
747
725 #if symlink
748 #if symlink
726
749
727 $ ln -s b linkb
750 $ ln -s b linkb
728 $ hg add linkb
751 $ hg add linkb
729 $ hg ci -m addlinkb
752 $ hg ci -m addlinkb
730 $ hg import --no-commit - <<EOF
753 $ hg import --no-commit - <<EOF
731 > diff --git a/linkb b/linkb
754 > diff --git a/linkb b/linkb
732 > deleted file mode 120000
755 > deleted file mode 120000
733 > --- a/linkb
756 > --- a/linkb
734 > +++ /dev/null
757 > +++ /dev/null
735 > @@ -1,1 +0,0 @@
758 > @@ -1,1 +0,0 @@
736 > -badhunk
759 > -badhunk
737 > \ No newline at end of file
760 > \ No newline at end of file
738 > EOF
761 > EOF
739 applying patch from stdin
762 applying patch from stdin
740 patching file linkb
763 patching file linkb
741 Hunk #1 FAILED at 0
764 Hunk #1 FAILED at 0
742 1 out of 1 hunks FAILED -- saving rejects to file linkb.rej
765 1 out of 1 hunks FAILED -- saving rejects to file linkb.rej
743 abort: patch failed to apply
766 abort: patch failed to apply
744 [255]
767 [255]
745 $ hg st
768 $ hg st
746 ? b.rej
769 ? b.rej
747 ? linkb.rej
770 ? linkb.rej
748
771
749 #endif
772 #endif
750
773
751 Test corner case involving copies and multiple hunks (issue3384)
774 Test corner case involving copies and multiple hunks (issue3384)
752
775
753 $ hg revert -qa
776 $ hg revert -qa
754 $ hg import --no-commit - <<EOF
777 $ hg import --no-commit - <<EOF
755 > diff --git a/a b/c
778 > diff --git a/a b/c
756 > copy from a
779 > copy from a
757 > copy to c
780 > copy to c
758 > --- a/a
781 > --- a/a
759 > +++ b/c
782 > +++ b/c
760 > @@ -1,1 +1,2 @@
783 > @@ -1,1 +1,2 @@
761 > a
784 > a
762 > +a
785 > +a
763 > @@ -2,1 +2,2 @@
786 > @@ -2,1 +2,2 @@
764 > a
787 > a
765 > +a
788 > +a
766 > diff --git a/a b/a
789 > diff --git a/a b/a
767 > --- a/a
790 > --- a/a
768 > +++ b/a
791 > +++ b/a
769 > @@ -1,1 +1,2 @@
792 > @@ -1,1 +1,2 @@
770 > a
793 > a
771 > +b
794 > +b
772 > EOF
795 > EOF
773 applying patch from stdin
796 applying patch from stdin
774
797
775 $ cd ..
798 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now