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