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