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