##// END OF EJS Templates
record: exiting editor with non-zero status should not stop recording session...
Laurent Charignon -
r25483:fb04372d default
parent child Browse files
Show More
@@ -1,2551 +1,2553 b''
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import collections
9 import collections
10 import cStringIO, email, os, errno, re, posixpath, copy
10 import cStringIO, email, os, errno, re, posixpath, copy
11 import tempfile, zlib, shutil
11 import tempfile, zlib, shutil
12 # On python2.4 you have to import these by name or they fail to
12 # On python2.4 you have to import these by name or they fail to
13 # load. This was not a problem on Python 2.7.
13 # load. This was not a problem on Python 2.7.
14 import email.Generator
14 import email.Generator
15 import email.Parser
15 import email.Parser
16
16
17 from i18n import _
17 from i18n import _
18 from node import hex, short
18 from node import hex, short
19 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
19 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
20 import pathutil
20 import pathutil
21
21
22 gitre = re.compile('diff --git a/(.*) b/(.*)')
22 gitre = re.compile('diff --git a/(.*) b/(.*)')
23 tabsplitter = re.compile(r'(\t+|[^\t]+)')
23 tabsplitter = re.compile(r'(\t+|[^\t]+)')
24
24
25 class PatchError(Exception):
25 class PatchError(Exception):
26 pass
26 pass
27
27
28
28
29 # public functions
29 # public functions
30
30
31 def split(stream):
31 def split(stream):
32 '''return an iterator of individual patches from a stream'''
32 '''return an iterator of individual patches from a stream'''
33 def isheader(line, inheader):
33 def isheader(line, inheader):
34 if inheader and line[0] in (' ', '\t'):
34 if inheader and line[0] in (' ', '\t'):
35 # continuation
35 # continuation
36 return True
36 return True
37 if line[0] in (' ', '-', '+'):
37 if line[0] in (' ', '-', '+'):
38 # diff line - don't check for header pattern in there
38 # diff line - don't check for header pattern in there
39 return False
39 return False
40 l = line.split(': ', 1)
40 l = line.split(': ', 1)
41 return len(l) == 2 and ' ' not in l[0]
41 return len(l) == 2 and ' ' not in l[0]
42
42
43 def chunk(lines):
43 def chunk(lines):
44 return cStringIO.StringIO(''.join(lines))
44 return cStringIO.StringIO(''.join(lines))
45
45
46 def hgsplit(stream, cur):
46 def hgsplit(stream, cur):
47 inheader = True
47 inheader = True
48
48
49 for line in stream:
49 for line in stream:
50 if not line.strip():
50 if not line.strip():
51 inheader = False
51 inheader = False
52 if not inheader and line.startswith('# HG changeset patch'):
52 if not inheader and line.startswith('# HG changeset patch'):
53 yield chunk(cur)
53 yield chunk(cur)
54 cur = []
54 cur = []
55 inheader = True
55 inheader = True
56
56
57 cur.append(line)
57 cur.append(line)
58
58
59 if cur:
59 if cur:
60 yield chunk(cur)
60 yield chunk(cur)
61
61
62 def mboxsplit(stream, cur):
62 def mboxsplit(stream, cur):
63 for line in stream:
63 for line in stream:
64 if line.startswith('From '):
64 if line.startswith('From '):
65 for c in split(chunk(cur[1:])):
65 for c in split(chunk(cur[1:])):
66 yield c
66 yield c
67 cur = []
67 cur = []
68
68
69 cur.append(line)
69 cur.append(line)
70
70
71 if cur:
71 if cur:
72 for c in split(chunk(cur[1:])):
72 for c in split(chunk(cur[1:])):
73 yield c
73 yield c
74
74
75 def mimesplit(stream, cur):
75 def mimesplit(stream, cur):
76 def msgfp(m):
76 def msgfp(m):
77 fp = cStringIO.StringIO()
77 fp = cStringIO.StringIO()
78 g = email.Generator.Generator(fp, mangle_from_=False)
78 g = email.Generator.Generator(fp, mangle_from_=False)
79 g.flatten(m)
79 g.flatten(m)
80 fp.seek(0)
80 fp.seek(0)
81 return fp
81 return fp
82
82
83 for line in stream:
83 for line in stream:
84 cur.append(line)
84 cur.append(line)
85 c = chunk(cur)
85 c = chunk(cur)
86
86
87 m = email.Parser.Parser().parse(c)
87 m = email.Parser.Parser().parse(c)
88 if not m.is_multipart():
88 if not m.is_multipart():
89 yield msgfp(m)
89 yield msgfp(m)
90 else:
90 else:
91 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
91 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
92 for part in m.walk():
92 for part in m.walk():
93 ct = part.get_content_type()
93 ct = part.get_content_type()
94 if ct not in ok_types:
94 if ct not in ok_types:
95 continue
95 continue
96 yield msgfp(part)
96 yield msgfp(part)
97
97
98 def headersplit(stream, cur):
98 def headersplit(stream, cur):
99 inheader = False
99 inheader = False
100
100
101 for line in stream:
101 for line in stream:
102 if not inheader and isheader(line, inheader):
102 if not inheader and isheader(line, inheader):
103 yield chunk(cur)
103 yield chunk(cur)
104 cur = []
104 cur = []
105 inheader = True
105 inheader = True
106 if inheader and not isheader(line, inheader):
106 if inheader and not isheader(line, inheader):
107 inheader = False
107 inheader = False
108
108
109 cur.append(line)
109 cur.append(line)
110
110
111 if cur:
111 if cur:
112 yield chunk(cur)
112 yield chunk(cur)
113
113
114 def remainder(cur):
114 def remainder(cur):
115 yield chunk(cur)
115 yield chunk(cur)
116
116
117 class fiter(object):
117 class fiter(object):
118 def __init__(self, fp):
118 def __init__(self, fp):
119 self.fp = fp
119 self.fp = fp
120
120
121 def __iter__(self):
121 def __iter__(self):
122 return self
122 return self
123
123
124 def next(self):
124 def next(self):
125 l = self.fp.readline()
125 l = self.fp.readline()
126 if not l:
126 if not l:
127 raise StopIteration
127 raise StopIteration
128 return l
128 return l
129
129
130 inheader = False
130 inheader = False
131 cur = []
131 cur = []
132
132
133 mimeheaders = ['content-type']
133 mimeheaders = ['content-type']
134
134
135 if not util.safehasattr(stream, 'next'):
135 if not util.safehasattr(stream, 'next'):
136 # http responses, for example, have readline but not next
136 # http responses, for example, have readline but not next
137 stream = fiter(stream)
137 stream = fiter(stream)
138
138
139 for line in stream:
139 for line in stream:
140 cur.append(line)
140 cur.append(line)
141 if line.startswith('# HG changeset patch'):
141 if line.startswith('# HG changeset patch'):
142 return hgsplit(stream, cur)
142 return hgsplit(stream, cur)
143 elif line.startswith('From '):
143 elif line.startswith('From '):
144 return mboxsplit(stream, cur)
144 return mboxsplit(stream, cur)
145 elif isheader(line, inheader):
145 elif isheader(line, inheader):
146 inheader = True
146 inheader = True
147 if line.split(':', 1)[0].lower() in mimeheaders:
147 if line.split(':', 1)[0].lower() in mimeheaders:
148 # let email parser handle this
148 # let email parser handle this
149 return mimesplit(stream, cur)
149 return mimesplit(stream, cur)
150 elif line.startswith('--- ') and inheader:
150 elif line.startswith('--- ') and inheader:
151 # No evil headers seen by diff start, split by hand
151 # No evil headers seen by diff start, split by hand
152 return headersplit(stream, cur)
152 return headersplit(stream, cur)
153 # Not enough info, keep reading
153 # Not enough info, keep reading
154
154
155 # if we are here, we have a very plain patch
155 # if we are here, we have a very plain patch
156 return remainder(cur)
156 return remainder(cur)
157
157
158 def extract(ui, fileobj):
158 def extract(ui, fileobj):
159 '''extract patch from data read from fileobj.
159 '''extract patch from data read from fileobj.
160
160
161 patch can be a normal patch or contained in an email message.
161 patch can be a normal patch or contained in an email message.
162
162
163 return tuple (filename, message, user, date, branch, node, p1, p2).
163 return tuple (filename, message, user, date, branch, node, p1, p2).
164 Any item in the returned tuple can be None. If filename is None,
164 Any item in the returned tuple can be None. If filename is None,
165 fileobj did not contain a patch. Caller must unlink filename when done.'''
165 fileobj did not contain a patch. Caller must unlink filename when done.'''
166
166
167 # attempt to detect the start of a patch
167 # attempt to detect the start of a patch
168 # (this heuristic is borrowed from quilt)
168 # (this heuristic is borrowed from quilt)
169 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
169 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
170 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
170 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
171 r'---[ \t].*?^\+\+\+[ \t]|'
171 r'---[ \t].*?^\+\+\+[ \t]|'
172 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
172 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
173
173
174 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
174 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
175 tmpfp = os.fdopen(fd, 'w')
175 tmpfp = os.fdopen(fd, 'w')
176 try:
176 try:
177 msg = email.Parser.Parser().parse(fileobj)
177 msg = email.Parser.Parser().parse(fileobj)
178
178
179 subject = msg['Subject']
179 subject = msg['Subject']
180 user = msg['From']
180 user = msg['From']
181 if not subject and not user:
181 if not subject and not user:
182 # Not an email, restore parsed headers if any
182 # Not an email, restore parsed headers if any
183 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
183 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
184
184
185 # should try to parse msg['Date']
185 # should try to parse msg['Date']
186 date = None
186 date = None
187 nodeid = None
187 nodeid = None
188 branch = None
188 branch = None
189 parents = []
189 parents = []
190
190
191 if subject:
191 if subject:
192 if subject.startswith('[PATCH'):
192 if subject.startswith('[PATCH'):
193 pend = subject.find(']')
193 pend = subject.find(']')
194 if pend >= 0:
194 if pend >= 0:
195 subject = subject[pend + 1:].lstrip()
195 subject = subject[pend + 1:].lstrip()
196 subject = re.sub(r'\n[ \t]+', ' ', subject)
196 subject = re.sub(r'\n[ \t]+', ' ', subject)
197 ui.debug('Subject: %s\n' % subject)
197 ui.debug('Subject: %s\n' % subject)
198 if user:
198 if user:
199 ui.debug('From: %s\n' % user)
199 ui.debug('From: %s\n' % user)
200 diffs_seen = 0
200 diffs_seen = 0
201 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
201 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
202 message = ''
202 message = ''
203 for part in msg.walk():
203 for part in msg.walk():
204 content_type = part.get_content_type()
204 content_type = part.get_content_type()
205 ui.debug('Content-Type: %s\n' % content_type)
205 ui.debug('Content-Type: %s\n' % content_type)
206 if content_type not in ok_types:
206 if content_type not in ok_types:
207 continue
207 continue
208 payload = part.get_payload(decode=True)
208 payload = part.get_payload(decode=True)
209 m = diffre.search(payload)
209 m = diffre.search(payload)
210 if m:
210 if m:
211 hgpatch = False
211 hgpatch = False
212 hgpatchheader = False
212 hgpatchheader = False
213 ignoretext = False
213 ignoretext = False
214
214
215 ui.debug('found patch at byte %d\n' % m.start(0))
215 ui.debug('found patch at byte %d\n' % m.start(0))
216 diffs_seen += 1
216 diffs_seen += 1
217 cfp = cStringIO.StringIO()
217 cfp = cStringIO.StringIO()
218 for line in payload[:m.start(0)].splitlines():
218 for line in payload[:m.start(0)].splitlines():
219 if line.startswith('# HG changeset patch') and not hgpatch:
219 if line.startswith('# HG changeset patch') and not hgpatch:
220 ui.debug('patch generated by hg export\n')
220 ui.debug('patch generated by hg export\n')
221 hgpatch = True
221 hgpatch = True
222 hgpatchheader = True
222 hgpatchheader = True
223 # drop earlier commit message content
223 # drop earlier commit message content
224 cfp.seek(0)
224 cfp.seek(0)
225 cfp.truncate()
225 cfp.truncate()
226 subject = None
226 subject = None
227 elif hgpatchheader:
227 elif hgpatchheader:
228 if line.startswith('# User '):
228 if line.startswith('# User '):
229 user = line[7:]
229 user = line[7:]
230 ui.debug('From: %s\n' % user)
230 ui.debug('From: %s\n' % user)
231 elif line.startswith("# Date "):
231 elif line.startswith("# Date "):
232 date = line[7:]
232 date = line[7:]
233 elif line.startswith("# Branch "):
233 elif line.startswith("# Branch "):
234 branch = line[9:]
234 branch = line[9:]
235 elif line.startswith("# Node ID "):
235 elif line.startswith("# Node ID "):
236 nodeid = line[10:]
236 nodeid = line[10:]
237 elif line.startswith("# Parent "):
237 elif line.startswith("# Parent "):
238 parents.append(line[9:].lstrip())
238 parents.append(line[9:].lstrip())
239 elif not line.startswith("# "):
239 elif not line.startswith("# "):
240 hgpatchheader = False
240 hgpatchheader = False
241 elif line == '---':
241 elif line == '---':
242 ignoretext = True
242 ignoretext = True
243 if not hgpatchheader and not ignoretext:
243 if not hgpatchheader and not ignoretext:
244 cfp.write(line)
244 cfp.write(line)
245 cfp.write('\n')
245 cfp.write('\n')
246 message = cfp.getvalue()
246 message = cfp.getvalue()
247 if tmpfp:
247 if tmpfp:
248 tmpfp.write(payload)
248 tmpfp.write(payload)
249 if not payload.endswith('\n'):
249 if not payload.endswith('\n'):
250 tmpfp.write('\n')
250 tmpfp.write('\n')
251 elif not diffs_seen and message and content_type == 'text/plain':
251 elif not diffs_seen and message and content_type == 'text/plain':
252 message += '\n' + payload
252 message += '\n' + payload
253 except: # re-raises
253 except: # re-raises
254 tmpfp.close()
254 tmpfp.close()
255 os.unlink(tmpname)
255 os.unlink(tmpname)
256 raise
256 raise
257
257
258 if subject and not message.startswith(subject):
258 if subject and not message.startswith(subject):
259 message = '%s\n%s' % (subject, message)
259 message = '%s\n%s' % (subject, message)
260 tmpfp.close()
260 tmpfp.close()
261 if not diffs_seen:
261 if not diffs_seen:
262 os.unlink(tmpname)
262 os.unlink(tmpname)
263 return None, message, user, date, branch, None, None, None
263 return None, message, user, date, branch, None, None, None
264
264
265 if parents:
265 if parents:
266 p1 = parents.pop(0)
266 p1 = parents.pop(0)
267 else:
267 else:
268 p1 = None
268 p1 = None
269
269
270 if parents:
270 if parents:
271 p2 = parents.pop(0)
271 p2 = parents.pop(0)
272 else:
272 else:
273 p2 = None
273 p2 = None
274
274
275 return tmpname, message, user, date, branch, nodeid, p1, p2
275 return tmpname, message, user, date, branch, nodeid, p1, p2
276
276
277 class patchmeta(object):
277 class patchmeta(object):
278 """Patched file metadata
278 """Patched file metadata
279
279
280 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
280 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
281 or COPY. 'path' is patched file path. 'oldpath' is set to the
281 or COPY. 'path' is patched file path. 'oldpath' is set to the
282 origin file when 'op' is either COPY or RENAME, None otherwise. If
282 origin file when 'op' is either COPY or RENAME, None otherwise. If
283 file mode is changed, 'mode' is a tuple (islink, isexec) where
283 file mode is changed, 'mode' is a tuple (islink, isexec) where
284 'islink' is True if the file is a symlink and 'isexec' is True if
284 'islink' is True if the file is a symlink and 'isexec' is True if
285 the file is executable. Otherwise, 'mode' is None.
285 the file is executable. Otherwise, 'mode' is None.
286 """
286 """
287 def __init__(self, path):
287 def __init__(self, path):
288 self.path = path
288 self.path = path
289 self.oldpath = None
289 self.oldpath = None
290 self.mode = None
290 self.mode = None
291 self.op = 'MODIFY'
291 self.op = 'MODIFY'
292 self.binary = False
292 self.binary = False
293
293
294 def setmode(self, mode):
294 def setmode(self, mode):
295 islink = mode & 020000
295 islink = mode & 020000
296 isexec = mode & 0100
296 isexec = mode & 0100
297 self.mode = (islink, isexec)
297 self.mode = (islink, isexec)
298
298
299 def copy(self):
299 def copy(self):
300 other = patchmeta(self.path)
300 other = patchmeta(self.path)
301 other.oldpath = self.oldpath
301 other.oldpath = self.oldpath
302 other.mode = self.mode
302 other.mode = self.mode
303 other.op = self.op
303 other.op = self.op
304 other.binary = self.binary
304 other.binary = self.binary
305 return other
305 return other
306
306
307 def _ispatchinga(self, afile):
307 def _ispatchinga(self, afile):
308 if afile == '/dev/null':
308 if afile == '/dev/null':
309 return self.op == 'ADD'
309 return self.op == 'ADD'
310 return afile == 'a/' + (self.oldpath or self.path)
310 return afile == 'a/' + (self.oldpath or self.path)
311
311
312 def _ispatchingb(self, bfile):
312 def _ispatchingb(self, bfile):
313 if bfile == '/dev/null':
313 if bfile == '/dev/null':
314 return self.op == 'DELETE'
314 return self.op == 'DELETE'
315 return bfile == 'b/' + self.path
315 return bfile == 'b/' + self.path
316
316
317 def ispatching(self, afile, bfile):
317 def ispatching(self, afile, bfile):
318 return self._ispatchinga(afile) and self._ispatchingb(bfile)
318 return self._ispatchinga(afile) and self._ispatchingb(bfile)
319
319
320 def __repr__(self):
320 def __repr__(self):
321 return "<patchmeta %s %r>" % (self.op, self.path)
321 return "<patchmeta %s %r>" % (self.op, self.path)
322
322
323 def readgitpatch(lr):
323 def readgitpatch(lr):
324 """extract git-style metadata about patches from <patchname>"""
324 """extract git-style metadata about patches from <patchname>"""
325
325
326 # Filter patch for git information
326 # Filter patch for git information
327 gp = None
327 gp = None
328 gitpatches = []
328 gitpatches = []
329 for line in lr:
329 for line in lr:
330 line = line.rstrip(' \r\n')
330 line = line.rstrip(' \r\n')
331 if line.startswith('diff --git a/'):
331 if line.startswith('diff --git a/'):
332 m = gitre.match(line)
332 m = gitre.match(line)
333 if m:
333 if m:
334 if gp:
334 if gp:
335 gitpatches.append(gp)
335 gitpatches.append(gp)
336 dst = m.group(2)
336 dst = m.group(2)
337 gp = patchmeta(dst)
337 gp = patchmeta(dst)
338 elif gp:
338 elif gp:
339 if line.startswith('--- '):
339 if line.startswith('--- '):
340 gitpatches.append(gp)
340 gitpatches.append(gp)
341 gp = None
341 gp = None
342 continue
342 continue
343 if line.startswith('rename from '):
343 if line.startswith('rename from '):
344 gp.op = 'RENAME'
344 gp.op = 'RENAME'
345 gp.oldpath = line[12:]
345 gp.oldpath = line[12:]
346 elif line.startswith('rename to '):
346 elif line.startswith('rename to '):
347 gp.path = line[10:]
347 gp.path = line[10:]
348 elif line.startswith('copy from '):
348 elif line.startswith('copy from '):
349 gp.op = 'COPY'
349 gp.op = 'COPY'
350 gp.oldpath = line[10:]
350 gp.oldpath = line[10:]
351 elif line.startswith('copy to '):
351 elif line.startswith('copy to '):
352 gp.path = line[8:]
352 gp.path = line[8:]
353 elif line.startswith('deleted file'):
353 elif line.startswith('deleted file'):
354 gp.op = 'DELETE'
354 gp.op = 'DELETE'
355 elif line.startswith('new file mode '):
355 elif line.startswith('new file mode '):
356 gp.op = 'ADD'
356 gp.op = 'ADD'
357 gp.setmode(int(line[-6:], 8))
357 gp.setmode(int(line[-6:], 8))
358 elif line.startswith('new mode '):
358 elif line.startswith('new mode '):
359 gp.setmode(int(line[-6:], 8))
359 gp.setmode(int(line[-6:], 8))
360 elif line.startswith('GIT binary patch'):
360 elif line.startswith('GIT binary patch'):
361 gp.binary = True
361 gp.binary = True
362 if gp:
362 if gp:
363 gitpatches.append(gp)
363 gitpatches.append(gp)
364
364
365 return gitpatches
365 return gitpatches
366
366
367 class linereader(object):
367 class linereader(object):
368 # simple class to allow pushing lines back into the input stream
368 # simple class to allow pushing lines back into the input stream
369 def __init__(self, fp):
369 def __init__(self, fp):
370 self.fp = fp
370 self.fp = fp
371 self.buf = []
371 self.buf = []
372
372
373 def push(self, line):
373 def push(self, line):
374 if line is not None:
374 if line is not None:
375 self.buf.append(line)
375 self.buf.append(line)
376
376
377 def readline(self):
377 def readline(self):
378 if self.buf:
378 if self.buf:
379 l = self.buf[0]
379 l = self.buf[0]
380 del self.buf[0]
380 del self.buf[0]
381 return l
381 return l
382 return self.fp.readline()
382 return self.fp.readline()
383
383
384 def __iter__(self):
384 def __iter__(self):
385 while True:
385 while True:
386 l = self.readline()
386 l = self.readline()
387 if not l:
387 if not l:
388 break
388 break
389 yield l
389 yield l
390
390
391 class abstractbackend(object):
391 class abstractbackend(object):
392 def __init__(self, ui):
392 def __init__(self, ui):
393 self.ui = ui
393 self.ui = ui
394
394
395 def getfile(self, fname):
395 def getfile(self, fname):
396 """Return target file data and flags as a (data, (islink,
396 """Return target file data and flags as a (data, (islink,
397 isexec)) tuple. Data is None if file is missing/deleted.
397 isexec)) tuple. Data is None if file is missing/deleted.
398 """
398 """
399 raise NotImplementedError
399 raise NotImplementedError
400
400
401 def setfile(self, fname, data, mode, copysource):
401 def setfile(self, fname, data, mode, copysource):
402 """Write data to target file fname and set its mode. mode is a
402 """Write data to target file fname and set its mode. mode is a
403 (islink, isexec) tuple. If data is None, the file content should
403 (islink, isexec) tuple. If data is None, the file content should
404 be left unchanged. If the file is modified after being copied,
404 be left unchanged. If the file is modified after being copied,
405 copysource is set to the original file name.
405 copysource is set to the original file name.
406 """
406 """
407 raise NotImplementedError
407 raise NotImplementedError
408
408
409 def unlink(self, fname):
409 def unlink(self, fname):
410 """Unlink target file."""
410 """Unlink target file."""
411 raise NotImplementedError
411 raise NotImplementedError
412
412
413 def writerej(self, fname, failed, total, lines):
413 def writerej(self, fname, failed, total, lines):
414 """Write rejected lines for fname. total is the number of hunks
414 """Write rejected lines for fname. total is the number of hunks
415 which failed to apply and total the total number of hunks for this
415 which failed to apply and total the total number of hunks for this
416 files.
416 files.
417 """
417 """
418 pass
418 pass
419
419
420 def exists(self, fname):
420 def exists(self, fname):
421 raise NotImplementedError
421 raise NotImplementedError
422
422
423 class fsbackend(abstractbackend):
423 class fsbackend(abstractbackend):
424 def __init__(self, ui, basedir):
424 def __init__(self, ui, basedir):
425 super(fsbackend, self).__init__(ui)
425 super(fsbackend, self).__init__(ui)
426 self.opener = scmutil.opener(basedir)
426 self.opener = scmutil.opener(basedir)
427
427
428 def _join(self, f):
428 def _join(self, f):
429 return os.path.join(self.opener.base, f)
429 return os.path.join(self.opener.base, f)
430
430
431 def getfile(self, fname):
431 def getfile(self, fname):
432 if self.opener.islink(fname):
432 if self.opener.islink(fname):
433 return (self.opener.readlink(fname), (True, False))
433 return (self.opener.readlink(fname), (True, False))
434
434
435 isexec = False
435 isexec = False
436 try:
436 try:
437 isexec = self.opener.lstat(fname).st_mode & 0100 != 0
437 isexec = self.opener.lstat(fname).st_mode & 0100 != 0
438 except OSError, e:
438 except OSError, e:
439 if e.errno != errno.ENOENT:
439 if e.errno != errno.ENOENT:
440 raise
440 raise
441 try:
441 try:
442 return (self.opener.read(fname), (False, isexec))
442 return (self.opener.read(fname), (False, isexec))
443 except IOError, e:
443 except IOError, e:
444 if e.errno != errno.ENOENT:
444 if e.errno != errno.ENOENT:
445 raise
445 raise
446 return None, None
446 return None, None
447
447
448 def setfile(self, fname, data, mode, copysource):
448 def setfile(self, fname, data, mode, copysource):
449 islink, isexec = mode
449 islink, isexec = mode
450 if data is None:
450 if data is None:
451 self.opener.setflags(fname, islink, isexec)
451 self.opener.setflags(fname, islink, isexec)
452 return
452 return
453 if islink:
453 if islink:
454 self.opener.symlink(data, fname)
454 self.opener.symlink(data, fname)
455 else:
455 else:
456 self.opener.write(fname, data)
456 self.opener.write(fname, data)
457 if isexec:
457 if isexec:
458 self.opener.setflags(fname, False, True)
458 self.opener.setflags(fname, False, True)
459
459
460 def unlink(self, fname):
460 def unlink(self, fname):
461 self.opener.unlinkpath(fname, ignoremissing=True)
461 self.opener.unlinkpath(fname, ignoremissing=True)
462
462
463 def writerej(self, fname, failed, total, lines):
463 def writerej(self, fname, failed, total, lines):
464 fname = fname + ".rej"
464 fname = fname + ".rej"
465 self.ui.warn(
465 self.ui.warn(
466 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
466 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
467 (failed, total, fname))
467 (failed, total, fname))
468 fp = self.opener(fname, 'w')
468 fp = self.opener(fname, 'w')
469 fp.writelines(lines)
469 fp.writelines(lines)
470 fp.close()
470 fp.close()
471
471
472 def exists(self, fname):
472 def exists(self, fname):
473 return self.opener.lexists(fname)
473 return self.opener.lexists(fname)
474
474
475 class workingbackend(fsbackend):
475 class workingbackend(fsbackend):
476 def __init__(self, ui, repo, similarity):
476 def __init__(self, ui, repo, similarity):
477 super(workingbackend, self).__init__(ui, repo.root)
477 super(workingbackend, self).__init__(ui, repo.root)
478 self.repo = repo
478 self.repo = repo
479 self.similarity = similarity
479 self.similarity = similarity
480 self.removed = set()
480 self.removed = set()
481 self.changed = set()
481 self.changed = set()
482 self.copied = []
482 self.copied = []
483
483
484 def _checkknown(self, fname):
484 def _checkknown(self, fname):
485 if self.repo.dirstate[fname] == '?' and self.exists(fname):
485 if self.repo.dirstate[fname] == '?' and self.exists(fname):
486 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
486 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
487
487
488 def setfile(self, fname, data, mode, copysource):
488 def setfile(self, fname, data, mode, copysource):
489 self._checkknown(fname)
489 self._checkknown(fname)
490 super(workingbackend, self).setfile(fname, data, mode, copysource)
490 super(workingbackend, self).setfile(fname, data, mode, copysource)
491 if copysource is not None:
491 if copysource is not None:
492 self.copied.append((copysource, fname))
492 self.copied.append((copysource, fname))
493 self.changed.add(fname)
493 self.changed.add(fname)
494
494
495 def unlink(self, fname):
495 def unlink(self, fname):
496 self._checkknown(fname)
496 self._checkknown(fname)
497 super(workingbackend, self).unlink(fname)
497 super(workingbackend, self).unlink(fname)
498 self.removed.add(fname)
498 self.removed.add(fname)
499 self.changed.add(fname)
499 self.changed.add(fname)
500
500
501 def close(self):
501 def close(self):
502 wctx = self.repo[None]
502 wctx = self.repo[None]
503 changed = set(self.changed)
503 changed = set(self.changed)
504 for src, dst in self.copied:
504 for src, dst in self.copied:
505 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
505 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
506 if self.removed:
506 if self.removed:
507 wctx.forget(sorted(self.removed))
507 wctx.forget(sorted(self.removed))
508 for f in self.removed:
508 for f in self.removed:
509 if f not in self.repo.dirstate:
509 if f not in self.repo.dirstate:
510 # File was deleted and no longer belongs to the
510 # File was deleted and no longer belongs to the
511 # dirstate, it was probably marked added then
511 # dirstate, it was probably marked added then
512 # deleted, and should not be considered by
512 # deleted, and should not be considered by
513 # marktouched().
513 # marktouched().
514 changed.discard(f)
514 changed.discard(f)
515 if changed:
515 if changed:
516 scmutil.marktouched(self.repo, changed, self.similarity)
516 scmutil.marktouched(self.repo, changed, self.similarity)
517 return sorted(self.changed)
517 return sorted(self.changed)
518
518
519 class filestore(object):
519 class filestore(object):
520 def __init__(self, maxsize=None):
520 def __init__(self, maxsize=None):
521 self.opener = None
521 self.opener = None
522 self.files = {}
522 self.files = {}
523 self.created = 0
523 self.created = 0
524 self.maxsize = maxsize
524 self.maxsize = maxsize
525 if self.maxsize is None:
525 if self.maxsize is None:
526 self.maxsize = 4*(2**20)
526 self.maxsize = 4*(2**20)
527 self.size = 0
527 self.size = 0
528 self.data = {}
528 self.data = {}
529
529
530 def setfile(self, fname, data, mode, copied=None):
530 def setfile(self, fname, data, mode, copied=None):
531 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
531 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
532 self.data[fname] = (data, mode, copied)
532 self.data[fname] = (data, mode, copied)
533 self.size += len(data)
533 self.size += len(data)
534 else:
534 else:
535 if self.opener is None:
535 if self.opener is None:
536 root = tempfile.mkdtemp(prefix='hg-patch-')
536 root = tempfile.mkdtemp(prefix='hg-patch-')
537 self.opener = scmutil.opener(root)
537 self.opener = scmutil.opener(root)
538 # Avoid filename issues with these simple names
538 # Avoid filename issues with these simple names
539 fn = str(self.created)
539 fn = str(self.created)
540 self.opener.write(fn, data)
540 self.opener.write(fn, data)
541 self.created += 1
541 self.created += 1
542 self.files[fname] = (fn, mode, copied)
542 self.files[fname] = (fn, mode, copied)
543
543
544 def getfile(self, fname):
544 def getfile(self, fname):
545 if fname in self.data:
545 if fname in self.data:
546 return self.data[fname]
546 return self.data[fname]
547 if not self.opener or fname not in self.files:
547 if not self.opener or fname not in self.files:
548 return None, None, None
548 return None, None, None
549 fn, mode, copied = self.files[fname]
549 fn, mode, copied = self.files[fname]
550 return self.opener.read(fn), mode, copied
550 return self.opener.read(fn), mode, copied
551
551
552 def close(self):
552 def close(self):
553 if self.opener:
553 if self.opener:
554 shutil.rmtree(self.opener.base)
554 shutil.rmtree(self.opener.base)
555
555
556 class repobackend(abstractbackend):
556 class repobackend(abstractbackend):
557 def __init__(self, ui, repo, ctx, store):
557 def __init__(self, ui, repo, ctx, store):
558 super(repobackend, self).__init__(ui)
558 super(repobackend, self).__init__(ui)
559 self.repo = repo
559 self.repo = repo
560 self.ctx = ctx
560 self.ctx = ctx
561 self.store = store
561 self.store = store
562 self.changed = set()
562 self.changed = set()
563 self.removed = set()
563 self.removed = set()
564 self.copied = {}
564 self.copied = {}
565
565
566 def _checkknown(self, fname):
566 def _checkknown(self, fname):
567 if fname not in self.ctx:
567 if fname not in self.ctx:
568 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
568 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
569
569
570 def getfile(self, fname):
570 def getfile(self, fname):
571 try:
571 try:
572 fctx = self.ctx[fname]
572 fctx = self.ctx[fname]
573 except error.LookupError:
573 except error.LookupError:
574 return None, None
574 return None, None
575 flags = fctx.flags()
575 flags = fctx.flags()
576 return fctx.data(), ('l' in flags, 'x' in flags)
576 return fctx.data(), ('l' in flags, 'x' in flags)
577
577
578 def setfile(self, fname, data, mode, copysource):
578 def setfile(self, fname, data, mode, copysource):
579 if copysource:
579 if copysource:
580 self._checkknown(copysource)
580 self._checkknown(copysource)
581 if data is None:
581 if data is None:
582 data = self.ctx[fname].data()
582 data = self.ctx[fname].data()
583 self.store.setfile(fname, data, mode, copysource)
583 self.store.setfile(fname, data, mode, copysource)
584 self.changed.add(fname)
584 self.changed.add(fname)
585 if copysource:
585 if copysource:
586 self.copied[fname] = copysource
586 self.copied[fname] = copysource
587
587
588 def unlink(self, fname):
588 def unlink(self, fname):
589 self._checkknown(fname)
589 self._checkknown(fname)
590 self.removed.add(fname)
590 self.removed.add(fname)
591
591
592 def exists(self, fname):
592 def exists(self, fname):
593 return fname in self.ctx
593 return fname in self.ctx
594
594
595 def close(self):
595 def close(self):
596 return self.changed | self.removed
596 return self.changed | self.removed
597
597
598 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
598 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
599 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
599 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
600 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
600 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
601 eolmodes = ['strict', 'crlf', 'lf', 'auto']
601 eolmodes = ['strict', 'crlf', 'lf', 'auto']
602
602
603 class patchfile(object):
603 class patchfile(object):
604 def __init__(self, ui, gp, backend, store, eolmode='strict'):
604 def __init__(self, ui, gp, backend, store, eolmode='strict'):
605 self.fname = gp.path
605 self.fname = gp.path
606 self.eolmode = eolmode
606 self.eolmode = eolmode
607 self.eol = None
607 self.eol = None
608 self.backend = backend
608 self.backend = backend
609 self.ui = ui
609 self.ui = ui
610 self.lines = []
610 self.lines = []
611 self.exists = False
611 self.exists = False
612 self.missing = True
612 self.missing = True
613 self.mode = gp.mode
613 self.mode = gp.mode
614 self.copysource = gp.oldpath
614 self.copysource = gp.oldpath
615 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
615 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
616 self.remove = gp.op == 'DELETE'
616 self.remove = gp.op == 'DELETE'
617 if self.copysource is None:
617 if self.copysource is None:
618 data, mode = backend.getfile(self.fname)
618 data, mode = backend.getfile(self.fname)
619 else:
619 else:
620 data, mode = store.getfile(self.copysource)[:2]
620 data, mode = store.getfile(self.copysource)[:2]
621 if data is not None:
621 if data is not None:
622 self.exists = self.copysource is None or backend.exists(self.fname)
622 self.exists = self.copysource is None or backend.exists(self.fname)
623 self.missing = False
623 self.missing = False
624 if data:
624 if data:
625 self.lines = mdiff.splitnewlines(data)
625 self.lines = mdiff.splitnewlines(data)
626 if self.mode is None:
626 if self.mode is None:
627 self.mode = mode
627 self.mode = mode
628 if self.lines:
628 if self.lines:
629 # Normalize line endings
629 # Normalize line endings
630 if self.lines[0].endswith('\r\n'):
630 if self.lines[0].endswith('\r\n'):
631 self.eol = '\r\n'
631 self.eol = '\r\n'
632 elif self.lines[0].endswith('\n'):
632 elif self.lines[0].endswith('\n'):
633 self.eol = '\n'
633 self.eol = '\n'
634 if eolmode != 'strict':
634 if eolmode != 'strict':
635 nlines = []
635 nlines = []
636 for l in self.lines:
636 for l in self.lines:
637 if l.endswith('\r\n'):
637 if l.endswith('\r\n'):
638 l = l[:-2] + '\n'
638 l = l[:-2] + '\n'
639 nlines.append(l)
639 nlines.append(l)
640 self.lines = nlines
640 self.lines = nlines
641 else:
641 else:
642 if self.create:
642 if self.create:
643 self.missing = False
643 self.missing = False
644 if self.mode is None:
644 if self.mode is None:
645 self.mode = (False, False)
645 self.mode = (False, False)
646 if self.missing:
646 if self.missing:
647 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
647 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
648
648
649 self.hash = {}
649 self.hash = {}
650 self.dirty = 0
650 self.dirty = 0
651 self.offset = 0
651 self.offset = 0
652 self.skew = 0
652 self.skew = 0
653 self.rej = []
653 self.rej = []
654 self.fileprinted = False
654 self.fileprinted = False
655 self.printfile(False)
655 self.printfile(False)
656 self.hunks = 0
656 self.hunks = 0
657
657
658 def writelines(self, fname, lines, mode):
658 def writelines(self, fname, lines, mode):
659 if self.eolmode == 'auto':
659 if self.eolmode == 'auto':
660 eol = self.eol
660 eol = self.eol
661 elif self.eolmode == 'crlf':
661 elif self.eolmode == 'crlf':
662 eol = '\r\n'
662 eol = '\r\n'
663 else:
663 else:
664 eol = '\n'
664 eol = '\n'
665
665
666 if self.eolmode != 'strict' and eol and eol != '\n':
666 if self.eolmode != 'strict' and eol and eol != '\n':
667 rawlines = []
667 rawlines = []
668 for l in lines:
668 for l in lines:
669 if l and l[-1] == '\n':
669 if l and l[-1] == '\n':
670 l = l[:-1] + eol
670 l = l[:-1] + eol
671 rawlines.append(l)
671 rawlines.append(l)
672 lines = rawlines
672 lines = rawlines
673
673
674 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
674 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
675
675
676 def printfile(self, warn):
676 def printfile(self, warn):
677 if self.fileprinted:
677 if self.fileprinted:
678 return
678 return
679 if warn or self.ui.verbose:
679 if warn or self.ui.verbose:
680 self.fileprinted = True
680 self.fileprinted = True
681 s = _("patching file %s\n") % self.fname
681 s = _("patching file %s\n") % self.fname
682 if warn:
682 if warn:
683 self.ui.warn(s)
683 self.ui.warn(s)
684 else:
684 else:
685 self.ui.note(s)
685 self.ui.note(s)
686
686
687
687
688 def findlines(self, l, linenum):
688 def findlines(self, l, linenum):
689 # looks through the hash and finds candidate lines. The
689 # looks through the hash and finds candidate lines. The
690 # result is a list of line numbers sorted based on distance
690 # result is a list of line numbers sorted based on distance
691 # from linenum
691 # from linenum
692
692
693 cand = self.hash.get(l, [])
693 cand = self.hash.get(l, [])
694 if len(cand) > 1:
694 if len(cand) > 1:
695 # resort our list of potentials forward then back.
695 # resort our list of potentials forward then back.
696 cand.sort(key=lambda x: abs(x - linenum))
696 cand.sort(key=lambda x: abs(x - linenum))
697 return cand
697 return cand
698
698
699 def write_rej(self):
699 def write_rej(self):
700 # our rejects are a little different from patch(1). This always
700 # our rejects are a little different from patch(1). This always
701 # creates rejects in the same form as the original patch. A file
701 # creates rejects in the same form as the original patch. A file
702 # header is inserted so that you can run the reject through patch again
702 # header is inserted so that you can run the reject through patch again
703 # without having to type the filename.
703 # without having to type the filename.
704 if not self.rej:
704 if not self.rej:
705 return
705 return
706 base = os.path.basename(self.fname)
706 base = os.path.basename(self.fname)
707 lines = ["--- %s\n+++ %s\n" % (base, base)]
707 lines = ["--- %s\n+++ %s\n" % (base, base)]
708 for x in self.rej:
708 for x in self.rej:
709 for l in x.hunk:
709 for l in x.hunk:
710 lines.append(l)
710 lines.append(l)
711 if l[-1] != '\n':
711 if l[-1] != '\n':
712 lines.append("\n\ No newline at end of file\n")
712 lines.append("\n\ No newline at end of file\n")
713 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
713 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
714
714
715 def apply(self, h):
715 def apply(self, h):
716 if not h.complete():
716 if not h.complete():
717 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
717 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
718 (h.number, h.desc, len(h.a), h.lena, len(h.b),
718 (h.number, h.desc, len(h.a), h.lena, len(h.b),
719 h.lenb))
719 h.lenb))
720
720
721 self.hunks += 1
721 self.hunks += 1
722
722
723 if self.missing:
723 if self.missing:
724 self.rej.append(h)
724 self.rej.append(h)
725 return -1
725 return -1
726
726
727 if self.exists and self.create:
727 if self.exists and self.create:
728 if self.copysource:
728 if self.copysource:
729 self.ui.warn(_("cannot create %s: destination already "
729 self.ui.warn(_("cannot create %s: destination already "
730 "exists\n") % self.fname)
730 "exists\n") % self.fname)
731 else:
731 else:
732 self.ui.warn(_("file %s already exists\n") % self.fname)
732 self.ui.warn(_("file %s already exists\n") % self.fname)
733 self.rej.append(h)
733 self.rej.append(h)
734 return -1
734 return -1
735
735
736 if isinstance(h, binhunk):
736 if isinstance(h, binhunk):
737 if self.remove:
737 if self.remove:
738 self.backend.unlink(self.fname)
738 self.backend.unlink(self.fname)
739 else:
739 else:
740 l = h.new(self.lines)
740 l = h.new(self.lines)
741 self.lines[:] = l
741 self.lines[:] = l
742 self.offset += len(l)
742 self.offset += len(l)
743 self.dirty = True
743 self.dirty = True
744 return 0
744 return 0
745
745
746 horig = h
746 horig = h
747 if (self.eolmode in ('crlf', 'lf')
747 if (self.eolmode in ('crlf', 'lf')
748 or self.eolmode == 'auto' and self.eol):
748 or self.eolmode == 'auto' and self.eol):
749 # If new eols are going to be normalized, then normalize
749 # If new eols are going to be normalized, then normalize
750 # hunk data before patching. Otherwise, preserve input
750 # hunk data before patching. Otherwise, preserve input
751 # line-endings.
751 # line-endings.
752 h = h.getnormalized()
752 h = h.getnormalized()
753
753
754 # fast case first, no offsets, no fuzz
754 # fast case first, no offsets, no fuzz
755 old, oldstart, new, newstart = h.fuzzit(0, False)
755 old, oldstart, new, newstart = h.fuzzit(0, False)
756 oldstart += self.offset
756 oldstart += self.offset
757 orig_start = oldstart
757 orig_start = oldstart
758 # if there's skew we want to emit the "(offset %d lines)" even
758 # if there's skew we want to emit the "(offset %d lines)" even
759 # when the hunk cleanly applies at start + skew, so skip the
759 # when the hunk cleanly applies at start + skew, so skip the
760 # fast case code
760 # fast case code
761 if (self.skew == 0 and
761 if (self.skew == 0 and
762 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
762 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
763 if self.remove:
763 if self.remove:
764 self.backend.unlink(self.fname)
764 self.backend.unlink(self.fname)
765 else:
765 else:
766 self.lines[oldstart:oldstart + len(old)] = new
766 self.lines[oldstart:oldstart + len(old)] = new
767 self.offset += len(new) - len(old)
767 self.offset += len(new) - len(old)
768 self.dirty = True
768 self.dirty = True
769 return 0
769 return 0
770
770
771 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
771 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
772 self.hash = {}
772 self.hash = {}
773 for x, s in enumerate(self.lines):
773 for x, s in enumerate(self.lines):
774 self.hash.setdefault(s, []).append(x)
774 self.hash.setdefault(s, []).append(x)
775
775
776 for fuzzlen in xrange(3):
776 for fuzzlen in xrange(3):
777 for toponly in [True, False]:
777 for toponly in [True, False]:
778 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
778 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
779 oldstart = oldstart + self.offset + self.skew
779 oldstart = oldstart + self.offset + self.skew
780 oldstart = min(oldstart, len(self.lines))
780 oldstart = min(oldstart, len(self.lines))
781 if old:
781 if old:
782 cand = self.findlines(old[0][1:], oldstart)
782 cand = self.findlines(old[0][1:], oldstart)
783 else:
783 else:
784 # Only adding lines with no or fuzzed context, just
784 # Only adding lines with no or fuzzed context, just
785 # take the skew in account
785 # take the skew in account
786 cand = [oldstart]
786 cand = [oldstart]
787
787
788 for l in cand:
788 for l in cand:
789 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
789 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
790 self.lines[l : l + len(old)] = new
790 self.lines[l : l + len(old)] = new
791 self.offset += len(new) - len(old)
791 self.offset += len(new) - len(old)
792 self.skew = l - orig_start
792 self.skew = l - orig_start
793 self.dirty = True
793 self.dirty = True
794 offset = l - orig_start - fuzzlen
794 offset = l - orig_start - fuzzlen
795 if fuzzlen:
795 if fuzzlen:
796 msg = _("Hunk #%d succeeded at %d "
796 msg = _("Hunk #%d succeeded at %d "
797 "with fuzz %d "
797 "with fuzz %d "
798 "(offset %d lines).\n")
798 "(offset %d lines).\n")
799 self.printfile(True)
799 self.printfile(True)
800 self.ui.warn(msg %
800 self.ui.warn(msg %
801 (h.number, l + 1, fuzzlen, offset))
801 (h.number, l + 1, fuzzlen, offset))
802 else:
802 else:
803 msg = _("Hunk #%d succeeded at %d "
803 msg = _("Hunk #%d succeeded at %d "
804 "(offset %d lines).\n")
804 "(offset %d lines).\n")
805 self.ui.note(msg % (h.number, l + 1, offset))
805 self.ui.note(msg % (h.number, l + 1, offset))
806 return fuzzlen
806 return fuzzlen
807 self.printfile(True)
807 self.printfile(True)
808 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
808 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
809 self.rej.append(horig)
809 self.rej.append(horig)
810 return -1
810 return -1
811
811
812 def close(self):
812 def close(self):
813 if self.dirty:
813 if self.dirty:
814 self.writelines(self.fname, self.lines, self.mode)
814 self.writelines(self.fname, self.lines, self.mode)
815 self.write_rej()
815 self.write_rej()
816 return len(self.rej)
816 return len(self.rej)
817
817
818 class header(object):
818 class header(object):
819 """patch header
819 """patch header
820 """
820 """
821 diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
821 diffgit_re = re.compile('diff --git a/(.*) b/(.*)$')
822 diff_re = re.compile('diff -r .* (.*)$')
822 diff_re = re.compile('diff -r .* (.*)$')
823 allhunks_re = re.compile('(?:index|deleted file) ')
823 allhunks_re = re.compile('(?:index|deleted file) ')
824 pretty_re = re.compile('(?:new file|deleted file) ')
824 pretty_re = re.compile('(?:new file|deleted file) ')
825 special_re = re.compile('(?:index|deleted|copy|rename) ')
825 special_re = re.compile('(?:index|deleted|copy|rename) ')
826 newfile_re = re.compile('(?:new file)')
826 newfile_re = re.compile('(?:new file)')
827
827
828 def __init__(self, header):
828 def __init__(self, header):
829 self.header = header
829 self.header = header
830 self.hunks = []
830 self.hunks = []
831
831
832 def binary(self):
832 def binary(self):
833 return any(h.startswith('index ') for h in self.header)
833 return any(h.startswith('index ') for h in self.header)
834
834
835 def pretty(self, fp):
835 def pretty(self, fp):
836 for h in self.header:
836 for h in self.header:
837 if h.startswith('index '):
837 if h.startswith('index '):
838 fp.write(_('this modifies a binary file (all or nothing)\n'))
838 fp.write(_('this modifies a binary file (all or nothing)\n'))
839 break
839 break
840 if self.pretty_re.match(h):
840 if self.pretty_re.match(h):
841 fp.write(h)
841 fp.write(h)
842 if self.binary():
842 if self.binary():
843 fp.write(_('this is a binary file\n'))
843 fp.write(_('this is a binary file\n'))
844 break
844 break
845 if h.startswith('---'):
845 if h.startswith('---'):
846 fp.write(_('%d hunks, %d lines changed\n') %
846 fp.write(_('%d hunks, %d lines changed\n') %
847 (len(self.hunks),
847 (len(self.hunks),
848 sum([max(h.added, h.removed) for h in self.hunks])))
848 sum([max(h.added, h.removed) for h in self.hunks])))
849 break
849 break
850 fp.write(h)
850 fp.write(h)
851
851
852 def write(self, fp):
852 def write(self, fp):
853 fp.write(''.join(self.header))
853 fp.write(''.join(self.header))
854
854
855 def allhunks(self):
855 def allhunks(self):
856 return any(self.allhunks_re.match(h) for h in self.header)
856 return any(self.allhunks_re.match(h) for h in self.header)
857
857
858 def files(self):
858 def files(self):
859 match = self.diffgit_re.match(self.header[0])
859 match = self.diffgit_re.match(self.header[0])
860 if match:
860 if match:
861 fromfile, tofile = match.groups()
861 fromfile, tofile = match.groups()
862 if fromfile == tofile:
862 if fromfile == tofile:
863 return [fromfile]
863 return [fromfile]
864 return [fromfile, tofile]
864 return [fromfile, tofile]
865 else:
865 else:
866 return self.diff_re.match(self.header[0]).groups()
866 return self.diff_re.match(self.header[0]).groups()
867
867
868 def filename(self):
868 def filename(self):
869 return self.files()[-1]
869 return self.files()[-1]
870
870
871 def __repr__(self):
871 def __repr__(self):
872 return '<header %s>' % (' '.join(map(repr, self.files())))
872 return '<header %s>' % (' '.join(map(repr, self.files())))
873
873
874 def isnewfile(self):
874 def isnewfile(self):
875 return any(self.newfile_re.match(h) for h in self.header)
875 return any(self.newfile_re.match(h) for h in self.header)
876
876
877 def special(self):
877 def special(self):
878 # Special files are shown only at the header level and not at the hunk
878 # Special files are shown only at the header level and not at the hunk
879 # level for example a file that has been deleted is a special file.
879 # level for example a file that has been deleted is a special file.
880 # The user cannot change the content of the operation, in the case of
880 # The user cannot change the content of the operation, in the case of
881 # the deleted file he has to take the deletion or not take it, he
881 # the deleted file he has to take the deletion or not take it, he
882 # cannot take some of it.
882 # cannot take some of it.
883 # Newly added files are special if they are empty, they are not special
883 # Newly added files are special if they are empty, they are not special
884 # if they have some content as we want to be able to change it
884 # if they have some content as we want to be able to change it
885 nocontent = len(self.header) == 2
885 nocontent = len(self.header) == 2
886 emptynewfile = self.isnewfile() and nocontent
886 emptynewfile = self.isnewfile() and nocontent
887 return emptynewfile or \
887 return emptynewfile or \
888 any(self.special_re.match(h) for h in self.header)
888 any(self.special_re.match(h) for h in self.header)
889
889
890 class recordhunk(object):
890 class recordhunk(object):
891 """patch hunk
891 """patch hunk
892
892
893 XXX shouldn't we merge this with the other hunk class?
893 XXX shouldn't we merge this with the other hunk class?
894 """
894 """
895 maxcontext = 3
895 maxcontext = 3
896
896
897 def __init__(self, header, fromline, toline, proc, before, hunk, after):
897 def __init__(self, header, fromline, toline, proc, before, hunk, after):
898 def trimcontext(number, lines):
898 def trimcontext(number, lines):
899 delta = len(lines) - self.maxcontext
899 delta = len(lines) - self.maxcontext
900 if False and delta > 0:
900 if False and delta > 0:
901 return number + delta, lines[:self.maxcontext]
901 return number + delta, lines[:self.maxcontext]
902 return number, lines
902 return number, lines
903
903
904 self.header = header
904 self.header = header
905 self.fromline, self.before = trimcontext(fromline, before)
905 self.fromline, self.before = trimcontext(fromline, before)
906 self.toline, self.after = trimcontext(toline, after)
906 self.toline, self.after = trimcontext(toline, after)
907 self.proc = proc
907 self.proc = proc
908 self.hunk = hunk
908 self.hunk = hunk
909 self.added, self.removed = self.countchanges(self.hunk)
909 self.added, self.removed = self.countchanges(self.hunk)
910
910
911 def __eq__(self, v):
911 def __eq__(self, v):
912 if not isinstance(v, recordhunk):
912 if not isinstance(v, recordhunk):
913 return False
913 return False
914
914
915 return ((v.hunk == self.hunk) and
915 return ((v.hunk == self.hunk) and
916 (v.proc == self.proc) and
916 (v.proc == self.proc) and
917 (self.fromline == v.fromline) and
917 (self.fromline == v.fromline) and
918 (self.header.files() == v.header.files()))
918 (self.header.files() == v.header.files()))
919
919
920 def __hash__(self):
920 def __hash__(self):
921 return hash((tuple(self.hunk),
921 return hash((tuple(self.hunk),
922 tuple(self.header.files()),
922 tuple(self.header.files()),
923 self.fromline,
923 self.fromline,
924 self.proc))
924 self.proc))
925
925
926 def countchanges(self, hunk):
926 def countchanges(self, hunk):
927 """hunk -> (n+,n-)"""
927 """hunk -> (n+,n-)"""
928 add = len([h for h in hunk if h[0] == '+'])
928 add = len([h for h in hunk if h[0] == '+'])
929 rem = len([h for h in hunk if h[0] == '-'])
929 rem = len([h for h in hunk if h[0] == '-'])
930 return add, rem
930 return add, rem
931
931
932 def write(self, fp):
932 def write(self, fp):
933 delta = len(self.before) + len(self.after)
933 delta = len(self.before) + len(self.after)
934 if self.after and self.after[-1] == '\\ No newline at end of file\n':
934 if self.after and self.after[-1] == '\\ No newline at end of file\n':
935 delta -= 1
935 delta -= 1
936 fromlen = delta + self.removed
936 fromlen = delta + self.removed
937 tolen = delta + self.added
937 tolen = delta + self.added
938 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
938 fp.write('@@ -%d,%d +%d,%d @@%s\n' %
939 (self.fromline, fromlen, self.toline, tolen,
939 (self.fromline, fromlen, self.toline, tolen,
940 self.proc and (' ' + self.proc)))
940 self.proc and (' ' + self.proc)))
941 fp.write(''.join(self.before + self.hunk + self.after))
941 fp.write(''.join(self.before + self.hunk + self.after))
942
942
943 pretty = write
943 pretty = write
944
944
945 def filename(self):
945 def filename(self):
946 return self.header.filename()
946 return self.header.filename()
947
947
948 def __repr__(self):
948 def __repr__(self):
949 return '<hunk %r@%d>' % (self.filename(), self.fromline)
949 return '<hunk %r@%d>' % (self.filename(), self.fromline)
950
950
951 def filterpatch(ui, headers, operation=None):
951 def filterpatch(ui, headers, operation=None):
952 """Interactively filter patch chunks into applied-only chunks"""
952 """Interactively filter patch chunks into applied-only chunks"""
953 if operation is None:
953 if operation is None:
954 operation = _('record')
954 operation = _('record')
955
955
956 def prompt(skipfile, skipall, query, chunk):
956 def prompt(skipfile, skipall, query, chunk):
957 """prompt query, and process base inputs
957 """prompt query, and process base inputs
958
958
959 - y/n for the rest of file
959 - y/n for the rest of file
960 - y/n for the rest
960 - y/n for the rest
961 - ? (help)
961 - ? (help)
962 - q (quit)
962 - q (quit)
963
963
964 Return True/False and possibly updated skipfile and skipall.
964 Return True/False and possibly updated skipfile and skipall.
965 """
965 """
966 newpatches = None
966 newpatches = None
967 if skipall is not None:
967 if skipall is not None:
968 return skipall, skipfile, skipall, newpatches
968 return skipall, skipfile, skipall, newpatches
969 if skipfile is not None:
969 if skipfile is not None:
970 return skipfile, skipfile, skipall, newpatches
970 return skipfile, skipfile, skipall, newpatches
971 while True:
971 while True:
972 resps = _('[Ynesfdaq?]'
972 resps = _('[Ynesfdaq?]'
973 '$$ &Yes, record this change'
973 '$$ &Yes, record this change'
974 '$$ &No, skip this change'
974 '$$ &No, skip this change'
975 '$$ &Edit this change manually'
975 '$$ &Edit this change manually'
976 '$$ &Skip remaining changes to this file'
976 '$$ &Skip remaining changes to this file'
977 '$$ Record remaining changes to this &file'
977 '$$ Record remaining changes to this &file'
978 '$$ &Done, skip remaining changes and files'
978 '$$ &Done, skip remaining changes and files'
979 '$$ Record &all changes to all remaining files'
979 '$$ Record &all changes to all remaining files'
980 '$$ &Quit, recording no changes'
980 '$$ &Quit, recording no changes'
981 '$$ &? (display help)')
981 '$$ &? (display help)')
982 r = ui.promptchoice("%s %s" % (query, resps))
982 r = ui.promptchoice("%s %s" % (query, resps))
983 ui.write("\n")
983 ui.write("\n")
984 if r == 8: # ?
984 if r == 8: # ?
985 for c, t in ui.extractchoices(resps)[1]:
985 for c, t in ui.extractchoices(resps)[1]:
986 ui.write('%s - %s\n' % (c, t.lower()))
986 ui.write('%s - %s\n' % (c, t.lower()))
987 continue
987 continue
988 elif r == 0: # yes
988 elif r == 0: # yes
989 ret = True
989 ret = True
990 elif r == 1: # no
990 elif r == 1: # no
991 ret = False
991 ret = False
992 elif r == 2: # Edit patch
992 elif r == 2: # Edit patch
993 if chunk is None:
993 if chunk is None:
994 ui.write(_('cannot edit patch for whole file'))
994 ui.write(_('cannot edit patch for whole file'))
995 ui.write("\n")
995 ui.write("\n")
996 continue
996 continue
997 if chunk.header.binary():
997 if chunk.header.binary():
998 ui.write(_('cannot edit patch for binary file'))
998 ui.write(_('cannot edit patch for binary file'))
999 ui.write("\n")
999 ui.write("\n")
1000 continue
1000 continue
1001 # Patch comment based on the Git one (based on comment at end of
1001 # Patch comment based on the Git one (based on comment at end of
1002 # http://mercurial.selenic.com/wiki/RecordExtension)
1002 # http://mercurial.selenic.com/wiki/RecordExtension)
1003 phelp = '---' + _("""
1003 phelp = '---' + _("""
1004 To remove '-' lines, make them ' ' lines (context).
1004 To remove '-' lines, make them ' ' lines (context).
1005 To remove '+' lines, delete them.
1005 To remove '+' lines, delete them.
1006 Lines starting with # will be removed from the patch.
1006 Lines starting with # will be removed from the patch.
1007
1007
1008 If the patch applies cleanly, the edited hunk will immediately be
1008 If the patch applies cleanly, the edited hunk will immediately be
1009 added to the record list. If it does not apply cleanly, a rejects
1009 added to the record list. If it does not apply cleanly, a rejects
1010 file will be generated: you can use that when you try again. If
1010 file will be generated: you can use that when you try again. If
1011 all lines of the hunk are removed, then the edit is aborted and
1011 all lines of the hunk are removed, then the edit is aborted and
1012 the hunk is left unchanged.
1012 the hunk is left unchanged.
1013 """)
1013 """)
1014 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
1014 (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
1015 suffix=".diff", text=True)
1015 suffix=".diff", text=True)
1016 ncpatchfp = None
1016 ncpatchfp = None
1017 try:
1017 try:
1018 # Write the initial patch
1018 # Write the initial patch
1019 f = os.fdopen(patchfd, "w")
1019 f = os.fdopen(patchfd, "w")
1020 chunk.header.write(f)
1020 chunk.header.write(f)
1021 chunk.write(f)
1021 chunk.write(f)
1022 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
1022 f.write('\n'.join(['# ' + i for i in phelp.splitlines()]))
1023 f.close()
1023 f.close()
1024 # Start the editor and wait for it to complete
1024 # Start the editor and wait for it to complete
1025 editor = ui.geteditor()
1025 editor = ui.geteditor()
1026 ui.system("%s \"%s\"" % (editor, patchfn),
1026 ret = ui.system("%s \"%s\"" % (editor, patchfn),
1027 environ={'HGUSER': ui.username()},
1027 environ={'HGUSER': ui.username()})
1028 onerr=util.Abort, errprefix=_("edit failed"))
1028 if ret != 0:
1029 ui.warn(_("editor exited with exit code %d\n") % ret)
1030 continue
1029 # Remove comment lines
1031 # Remove comment lines
1030 patchfp = open(patchfn)
1032 patchfp = open(patchfn)
1031 ncpatchfp = cStringIO.StringIO()
1033 ncpatchfp = cStringIO.StringIO()
1032 for line in patchfp:
1034 for line in patchfp:
1033 if not line.startswith('#'):
1035 if not line.startswith('#'):
1034 ncpatchfp.write(line)
1036 ncpatchfp.write(line)
1035 patchfp.close()
1037 patchfp.close()
1036 ncpatchfp.seek(0)
1038 ncpatchfp.seek(0)
1037 newpatches = parsepatch(ncpatchfp)
1039 newpatches = parsepatch(ncpatchfp)
1038 finally:
1040 finally:
1039 os.unlink(patchfn)
1041 os.unlink(patchfn)
1040 del ncpatchfp
1042 del ncpatchfp
1041 # Signal that the chunk shouldn't be applied as-is, but
1043 # Signal that the chunk shouldn't be applied as-is, but
1042 # provide the new patch to be used instead.
1044 # provide the new patch to be used instead.
1043 ret = False
1045 ret = False
1044 elif r == 3: # Skip
1046 elif r == 3: # Skip
1045 ret = skipfile = False
1047 ret = skipfile = False
1046 elif r == 4: # file (Record remaining)
1048 elif r == 4: # file (Record remaining)
1047 ret = skipfile = True
1049 ret = skipfile = True
1048 elif r == 5: # done, skip remaining
1050 elif r == 5: # done, skip remaining
1049 ret = skipall = False
1051 ret = skipall = False
1050 elif r == 6: # all
1052 elif r == 6: # all
1051 ret = skipall = True
1053 ret = skipall = True
1052 elif r == 7: # quit
1054 elif r == 7: # quit
1053 raise util.Abort(_('user quit'))
1055 raise util.Abort(_('user quit'))
1054 return ret, skipfile, skipall, newpatches
1056 return ret, skipfile, skipall, newpatches
1055
1057
1056 seen = set()
1058 seen = set()
1057 applied = {} # 'filename' -> [] of chunks
1059 applied = {} # 'filename' -> [] of chunks
1058 skipfile, skipall = None, None
1060 skipfile, skipall = None, None
1059 pos, total = 1, sum(len(h.hunks) for h in headers)
1061 pos, total = 1, sum(len(h.hunks) for h in headers)
1060 for h in headers:
1062 for h in headers:
1061 pos += len(h.hunks)
1063 pos += len(h.hunks)
1062 skipfile = None
1064 skipfile = None
1063 fixoffset = 0
1065 fixoffset = 0
1064 hdr = ''.join(h.header)
1066 hdr = ''.join(h.header)
1065 if hdr in seen:
1067 if hdr in seen:
1066 continue
1068 continue
1067 seen.add(hdr)
1069 seen.add(hdr)
1068 if skipall is None:
1070 if skipall is None:
1069 h.pretty(ui)
1071 h.pretty(ui)
1070 msg = (_('examine changes to %s?') %
1072 msg = (_('examine changes to %s?') %
1071 _(' and ').join("'%s'" % f for f in h.files()))
1073 _(' and ').join("'%s'" % f for f in h.files()))
1072 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
1074 r, skipfile, skipall, np = prompt(skipfile, skipall, msg, None)
1073 if not r:
1075 if not r:
1074 continue
1076 continue
1075 applied[h.filename()] = [h]
1077 applied[h.filename()] = [h]
1076 if h.allhunks():
1078 if h.allhunks():
1077 applied[h.filename()] += h.hunks
1079 applied[h.filename()] += h.hunks
1078 continue
1080 continue
1079 for i, chunk in enumerate(h.hunks):
1081 for i, chunk in enumerate(h.hunks):
1080 if skipfile is None and skipall is None:
1082 if skipfile is None and skipall is None:
1081 chunk.pretty(ui)
1083 chunk.pretty(ui)
1082 if total == 1:
1084 if total == 1:
1083 msg = _("record this change to '%s'?") % chunk.filename()
1085 msg = _("record this change to '%s'?") % chunk.filename()
1084 else:
1086 else:
1085 idx = pos - len(h.hunks) + i
1087 idx = pos - len(h.hunks) + i
1086 msg = _("record change %d/%d to '%s'?") % (idx, total,
1088 msg = _("record change %d/%d to '%s'?") % (idx, total,
1087 chunk.filename())
1089 chunk.filename())
1088 r, skipfile, skipall, newpatches = prompt(skipfile,
1090 r, skipfile, skipall, newpatches = prompt(skipfile,
1089 skipall, msg, chunk)
1091 skipall, msg, chunk)
1090 if r:
1092 if r:
1091 if fixoffset:
1093 if fixoffset:
1092 chunk = copy.copy(chunk)
1094 chunk = copy.copy(chunk)
1093 chunk.toline += fixoffset
1095 chunk.toline += fixoffset
1094 applied[chunk.filename()].append(chunk)
1096 applied[chunk.filename()].append(chunk)
1095 elif newpatches is not None:
1097 elif newpatches is not None:
1096 for newpatch in newpatches:
1098 for newpatch in newpatches:
1097 for newhunk in newpatch.hunks:
1099 for newhunk in newpatch.hunks:
1098 if fixoffset:
1100 if fixoffset:
1099 newhunk.toline += fixoffset
1101 newhunk.toline += fixoffset
1100 applied[newhunk.filename()].append(newhunk)
1102 applied[newhunk.filename()].append(newhunk)
1101 else:
1103 else:
1102 fixoffset += chunk.removed - chunk.added
1104 fixoffset += chunk.removed - chunk.added
1103 return sum([h for h in applied.itervalues()
1105 return sum([h for h in applied.itervalues()
1104 if h[0].special() or len(h) > 1], [])
1106 if h[0].special() or len(h) > 1], [])
1105 class hunk(object):
1107 class hunk(object):
1106 def __init__(self, desc, num, lr, context):
1108 def __init__(self, desc, num, lr, context):
1107 self.number = num
1109 self.number = num
1108 self.desc = desc
1110 self.desc = desc
1109 self.hunk = [desc]
1111 self.hunk = [desc]
1110 self.a = []
1112 self.a = []
1111 self.b = []
1113 self.b = []
1112 self.starta = self.lena = None
1114 self.starta = self.lena = None
1113 self.startb = self.lenb = None
1115 self.startb = self.lenb = None
1114 if lr is not None:
1116 if lr is not None:
1115 if context:
1117 if context:
1116 self.read_context_hunk(lr)
1118 self.read_context_hunk(lr)
1117 else:
1119 else:
1118 self.read_unified_hunk(lr)
1120 self.read_unified_hunk(lr)
1119
1121
1120 def getnormalized(self):
1122 def getnormalized(self):
1121 """Return a copy with line endings normalized to LF."""
1123 """Return a copy with line endings normalized to LF."""
1122
1124
1123 def normalize(lines):
1125 def normalize(lines):
1124 nlines = []
1126 nlines = []
1125 for line in lines:
1127 for line in lines:
1126 if line.endswith('\r\n'):
1128 if line.endswith('\r\n'):
1127 line = line[:-2] + '\n'
1129 line = line[:-2] + '\n'
1128 nlines.append(line)
1130 nlines.append(line)
1129 return nlines
1131 return nlines
1130
1132
1131 # Dummy object, it is rebuilt manually
1133 # Dummy object, it is rebuilt manually
1132 nh = hunk(self.desc, self.number, None, None)
1134 nh = hunk(self.desc, self.number, None, None)
1133 nh.number = self.number
1135 nh.number = self.number
1134 nh.desc = self.desc
1136 nh.desc = self.desc
1135 nh.hunk = self.hunk
1137 nh.hunk = self.hunk
1136 nh.a = normalize(self.a)
1138 nh.a = normalize(self.a)
1137 nh.b = normalize(self.b)
1139 nh.b = normalize(self.b)
1138 nh.starta = self.starta
1140 nh.starta = self.starta
1139 nh.startb = self.startb
1141 nh.startb = self.startb
1140 nh.lena = self.lena
1142 nh.lena = self.lena
1141 nh.lenb = self.lenb
1143 nh.lenb = self.lenb
1142 return nh
1144 return nh
1143
1145
1144 def read_unified_hunk(self, lr):
1146 def read_unified_hunk(self, lr):
1145 m = unidesc.match(self.desc)
1147 m = unidesc.match(self.desc)
1146 if not m:
1148 if not m:
1147 raise PatchError(_("bad hunk #%d") % self.number)
1149 raise PatchError(_("bad hunk #%d") % self.number)
1148 self.starta, self.lena, self.startb, self.lenb = m.groups()
1150 self.starta, self.lena, self.startb, self.lenb = m.groups()
1149 if self.lena is None:
1151 if self.lena is None:
1150 self.lena = 1
1152 self.lena = 1
1151 else:
1153 else:
1152 self.lena = int(self.lena)
1154 self.lena = int(self.lena)
1153 if self.lenb is None:
1155 if self.lenb is None:
1154 self.lenb = 1
1156 self.lenb = 1
1155 else:
1157 else:
1156 self.lenb = int(self.lenb)
1158 self.lenb = int(self.lenb)
1157 self.starta = int(self.starta)
1159 self.starta = int(self.starta)
1158 self.startb = int(self.startb)
1160 self.startb = int(self.startb)
1159 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a,
1161 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a,
1160 self.b)
1162 self.b)
1161 # if we hit eof before finishing out the hunk, the last line will
1163 # if we hit eof before finishing out the hunk, the last line will
1162 # be zero length. Lets try to fix it up.
1164 # be zero length. Lets try to fix it up.
1163 while len(self.hunk[-1]) == 0:
1165 while len(self.hunk[-1]) == 0:
1164 del self.hunk[-1]
1166 del self.hunk[-1]
1165 del self.a[-1]
1167 del self.a[-1]
1166 del self.b[-1]
1168 del self.b[-1]
1167 self.lena -= 1
1169 self.lena -= 1
1168 self.lenb -= 1
1170 self.lenb -= 1
1169 self._fixnewline(lr)
1171 self._fixnewline(lr)
1170
1172
1171 def read_context_hunk(self, lr):
1173 def read_context_hunk(self, lr):
1172 self.desc = lr.readline()
1174 self.desc = lr.readline()
1173 m = contextdesc.match(self.desc)
1175 m = contextdesc.match(self.desc)
1174 if not m:
1176 if not m:
1175 raise PatchError(_("bad hunk #%d") % self.number)
1177 raise PatchError(_("bad hunk #%d") % self.number)
1176 self.starta, aend = m.groups()
1178 self.starta, aend = m.groups()
1177 self.starta = int(self.starta)
1179 self.starta = int(self.starta)
1178 if aend is None:
1180 if aend is None:
1179 aend = self.starta
1181 aend = self.starta
1180 self.lena = int(aend) - self.starta
1182 self.lena = int(aend) - self.starta
1181 if self.starta:
1183 if self.starta:
1182 self.lena += 1
1184 self.lena += 1
1183 for x in xrange(self.lena):
1185 for x in xrange(self.lena):
1184 l = lr.readline()
1186 l = lr.readline()
1185 if l.startswith('---'):
1187 if l.startswith('---'):
1186 # lines addition, old block is empty
1188 # lines addition, old block is empty
1187 lr.push(l)
1189 lr.push(l)
1188 break
1190 break
1189 s = l[2:]
1191 s = l[2:]
1190 if l.startswith('- ') or l.startswith('! '):
1192 if l.startswith('- ') or l.startswith('! '):
1191 u = '-' + s
1193 u = '-' + s
1192 elif l.startswith(' '):
1194 elif l.startswith(' '):
1193 u = ' ' + s
1195 u = ' ' + s
1194 else:
1196 else:
1195 raise PatchError(_("bad hunk #%d old text line %d") %
1197 raise PatchError(_("bad hunk #%d old text line %d") %
1196 (self.number, x))
1198 (self.number, x))
1197 self.a.append(u)
1199 self.a.append(u)
1198 self.hunk.append(u)
1200 self.hunk.append(u)
1199
1201
1200 l = lr.readline()
1202 l = lr.readline()
1201 if l.startswith('\ '):
1203 if l.startswith('\ '):
1202 s = self.a[-1][:-1]
1204 s = self.a[-1][:-1]
1203 self.a[-1] = s
1205 self.a[-1] = s
1204 self.hunk[-1] = s
1206 self.hunk[-1] = s
1205 l = lr.readline()
1207 l = lr.readline()
1206 m = contextdesc.match(l)
1208 m = contextdesc.match(l)
1207 if not m:
1209 if not m:
1208 raise PatchError(_("bad hunk #%d") % self.number)
1210 raise PatchError(_("bad hunk #%d") % self.number)
1209 self.startb, bend = m.groups()
1211 self.startb, bend = m.groups()
1210 self.startb = int(self.startb)
1212 self.startb = int(self.startb)
1211 if bend is None:
1213 if bend is None:
1212 bend = self.startb
1214 bend = self.startb
1213 self.lenb = int(bend) - self.startb
1215 self.lenb = int(bend) - self.startb
1214 if self.startb:
1216 if self.startb:
1215 self.lenb += 1
1217 self.lenb += 1
1216 hunki = 1
1218 hunki = 1
1217 for x in xrange(self.lenb):
1219 for x in xrange(self.lenb):
1218 l = lr.readline()
1220 l = lr.readline()
1219 if l.startswith('\ '):
1221 if l.startswith('\ '):
1220 # XXX: the only way to hit this is with an invalid line range.
1222 # XXX: the only way to hit this is with an invalid line range.
1221 # The no-eol marker is not counted in the line range, but I
1223 # The no-eol marker is not counted in the line range, but I
1222 # guess there are diff(1) out there which behave differently.
1224 # guess there are diff(1) out there which behave differently.
1223 s = self.b[-1][:-1]
1225 s = self.b[-1][:-1]
1224 self.b[-1] = s
1226 self.b[-1] = s
1225 self.hunk[hunki - 1] = s
1227 self.hunk[hunki - 1] = s
1226 continue
1228 continue
1227 if not l:
1229 if not l:
1228 # line deletions, new block is empty and we hit EOF
1230 # line deletions, new block is empty and we hit EOF
1229 lr.push(l)
1231 lr.push(l)
1230 break
1232 break
1231 s = l[2:]
1233 s = l[2:]
1232 if l.startswith('+ ') or l.startswith('! '):
1234 if l.startswith('+ ') or l.startswith('! '):
1233 u = '+' + s
1235 u = '+' + s
1234 elif l.startswith(' '):
1236 elif l.startswith(' '):
1235 u = ' ' + s
1237 u = ' ' + s
1236 elif len(self.b) == 0:
1238 elif len(self.b) == 0:
1237 # line deletions, new block is empty
1239 # line deletions, new block is empty
1238 lr.push(l)
1240 lr.push(l)
1239 break
1241 break
1240 else:
1242 else:
1241 raise PatchError(_("bad hunk #%d old text line %d") %
1243 raise PatchError(_("bad hunk #%d old text line %d") %
1242 (self.number, x))
1244 (self.number, x))
1243 self.b.append(s)
1245 self.b.append(s)
1244 while True:
1246 while True:
1245 if hunki >= len(self.hunk):
1247 if hunki >= len(self.hunk):
1246 h = ""
1248 h = ""
1247 else:
1249 else:
1248 h = self.hunk[hunki]
1250 h = self.hunk[hunki]
1249 hunki += 1
1251 hunki += 1
1250 if h == u:
1252 if h == u:
1251 break
1253 break
1252 elif h.startswith('-'):
1254 elif h.startswith('-'):
1253 continue
1255 continue
1254 else:
1256 else:
1255 self.hunk.insert(hunki - 1, u)
1257 self.hunk.insert(hunki - 1, u)
1256 break
1258 break
1257
1259
1258 if not self.a:
1260 if not self.a:
1259 # this happens when lines were only added to the hunk
1261 # this happens when lines were only added to the hunk
1260 for x in self.hunk:
1262 for x in self.hunk:
1261 if x.startswith('-') or x.startswith(' '):
1263 if x.startswith('-') or x.startswith(' '):
1262 self.a.append(x)
1264 self.a.append(x)
1263 if not self.b:
1265 if not self.b:
1264 # this happens when lines were only deleted from the hunk
1266 # this happens when lines were only deleted from the hunk
1265 for x in self.hunk:
1267 for x in self.hunk:
1266 if x.startswith('+') or x.startswith(' '):
1268 if x.startswith('+') or x.startswith(' '):
1267 self.b.append(x[1:])
1269 self.b.append(x[1:])
1268 # @@ -start,len +start,len @@
1270 # @@ -start,len +start,len @@
1269 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
1271 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
1270 self.startb, self.lenb)
1272 self.startb, self.lenb)
1271 self.hunk[0] = self.desc
1273 self.hunk[0] = self.desc
1272 self._fixnewline(lr)
1274 self._fixnewline(lr)
1273
1275
1274 def _fixnewline(self, lr):
1276 def _fixnewline(self, lr):
1275 l = lr.readline()
1277 l = lr.readline()
1276 if l.startswith('\ '):
1278 if l.startswith('\ '):
1277 diffhelpers.fix_newline(self.hunk, self.a, self.b)
1279 diffhelpers.fix_newline(self.hunk, self.a, self.b)
1278 else:
1280 else:
1279 lr.push(l)
1281 lr.push(l)
1280
1282
1281 def complete(self):
1283 def complete(self):
1282 return len(self.a) == self.lena and len(self.b) == self.lenb
1284 return len(self.a) == self.lena and len(self.b) == self.lenb
1283
1285
1284 def _fuzzit(self, old, new, fuzz, toponly):
1286 def _fuzzit(self, old, new, fuzz, toponly):
1285 # this removes context lines from the top and bottom of list 'l'. It
1287 # this removes context lines from the top and bottom of list 'l'. It
1286 # checks the hunk to make sure only context lines are removed, and then
1288 # checks the hunk to make sure only context lines are removed, and then
1287 # returns a new shortened list of lines.
1289 # returns a new shortened list of lines.
1288 fuzz = min(fuzz, len(old))
1290 fuzz = min(fuzz, len(old))
1289 if fuzz:
1291 if fuzz:
1290 top = 0
1292 top = 0
1291 bot = 0
1293 bot = 0
1292 hlen = len(self.hunk)
1294 hlen = len(self.hunk)
1293 for x in xrange(hlen - 1):
1295 for x in xrange(hlen - 1):
1294 # the hunk starts with the @@ line, so use x+1
1296 # the hunk starts with the @@ line, so use x+1
1295 if self.hunk[x + 1][0] == ' ':
1297 if self.hunk[x + 1][0] == ' ':
1296 top += 1
1298 top += 1
1297 else:
1299 else:
1298 break
1300 break
1299 if not toponly:
1301 if not toponly:
1300 for x in xrange(hlen - 1):
1302 for x in xrange(hlen - 1):
1301 if self.hunk[hlen - bot - 1][0] == ' ':
1303 if self.hunk[hlen - bot - 1][0] == ' ':
1302 bot += 1
1304 bot += 1
1303 else:
1305 else:
1304 break
1306 break
1305
1307
1306 bot = min(fuzz, bot)
1308 bot = min(fuzz, bot)
1307 top = min(fuzz, top)
1309 top = min(fuzz, top)
1308 return old[top:len(old) - bot], new[top:len(new) - bot], top
1310 return old[top:len(old) - bot], new[top:len(new) - bot], top
1309 return old, new, 0
1311 return old, new, 0
1310
1312
1311 def fuzzit(self, fuzz, toponly):
1313 def fuzzit(self, fuzz, toponly):
1312 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1314 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1313 oldstart = self.starta + top
1315 oldstart = self.starta + top
1314 newstart = self.startb + top
1316 newstart = self.startb + top
1315 # zero length hunk ranges already have their start decremented
1317 # zero length hunk ranges already have their start decremented
1316 if self.lena and oldstart > 0:
1318 if self.lena and oldstart > 0:
1317 oldstart -= 1
1319 oldstart -= 1
1318 if self.lenb and newstart > 0:
1320 if self.lenb and newstart > 0:
1319 newstart -= 1
1321 newstart -= 1
1320 return old, oldstart, new, newstart
1322 return old, oldstart, new, newstart
1321
1323
1322 class binhunk(object):
1324 class binhunk(object):
1323 'A binary patch file.'
1325 'A binary patch file.'
1324 def __init__(self, lr, fname):
1326 def __init__(self, lr, fname):
1325 self.text = None
1327 self.text = None
1326 self.delta = False
1328 self.delta = False
1327 self.hunk = ['GIT binary patch\n']
1329 self.hunk = ['GIT binary patch\n']
1328 self._fname = fname
1330 self._fname = fname
1329 self._read(lr)
1331 self._read(lr)
1330
1332
1331 def complete(self):
1333 def complete(self):
1332 return self.text is not None
1334 return self.text is not None
1333
1335
1334 def new(self, lines):
1336 def new(self, lines):
1335 if self.delta:
1337 if self.delta:
1336 return [applybindelta(self.text, ''.join(lines))]
1338 return [applybindelta(self.text, ''.join(lines))]
1337 return [self.text]
1339 return [self.text]
1338
1340
1339 def _read(self, lr):
1341 def _read(self, lr):
1340 def getline(lr, hunk):
1342 def getline(lr, hunk):
1341 l = lr.readline()
1343 l = lr.readline()
1342 hunk.append(l)
1344 hunk.append(l)
1343 return l.rstrip('\r\n')
1345 return l.rstrip('\r\n')
1344
1346
1345 size = 0
1347 size = 0
1346 while True:
1348 while True:
1347 line = getline(lr, self.hunk)
1349 line = getline(lr, self.hunk)
1348 if not line:
1350 if not line:
1349 raise PatchError(_('could not extract "%s" binary data')
1351 raise PatchError(_('could not extract "%s" binary data')
1350 % self._fname)
1352 % self._fname)
1351 if line.startswith('literal '):
1353 if line.startswith('literal '):
1352 size = int(line[8:].rstrip())
1354 size = int(line[8:].rstrip())
1353 break
1355 break
1354 if line.startswith('delta '):
1356 if line.startswith('delta '):
1355 size = int(line[6:].rstrip())
1357 size = int(line[6:].rstrip())
1356 self.delta = True
1358 self.delta = True
1357 break
1359 break
1358 dec = []
1360 dec = []
1359 line = getline(lr, self.hunk)
1361 line = getline(lr, self.hunk)
1360 while len(line) > 1:
1362 while len(line) > 1:
1361 l = line[0]
1363 l = line[0]
1362 if l <= 'Z' and l >= 'A':
1364 if l <= 'Z' and l >= 'A':
1363 l = ord(l) - ord('A') + 1
1365 l = ord(l) - ord('A') + 1
1364 else:
1366 else:
1365 l = ord(l) - ord('a') + 27
1367 l = ord(l) - ord('a') + 27
1366 try:
1368 try:
1367 dec.append(base85.b85decode(line[1:])[:l])
1369 dec.append(base85.b85decode(line[1:])[:l])
1368 except ValueError, e:
1370 except ValueError, e:
1369 raise PatchError(_('could not decode "%s" binary patch: %s')
1371 raise PatchError(_('could not decode "%s" binary patch: %s')
1370 % (self._fname, str(e)))
1372 % (self._fname, str(e)))
1371 line = getline(lr, self.hunk)
1373 line = getline(lr, self.hunk)
1372 text = zlib.decompress(''.join(dec))
1374 text = zlib.decompress(''.join(dec))
1373 if len(text) != size:
1375 if len(text) != size:
1374 raise PatchError(_('"%s" length is %d bytes, should be %d')
1376 raise PatchError(_('"%s" length is %d bytes, should be %d')
1375 % (self._fname, len(text), size))
1377 % (self._fname, len(text), size))
1376 self.text = text
1378 self.text = text
1377
1379
1378 def parsefilename(str):
1380 def parsefilename(str):
1379 # --- filename \t|space stuff
1381 # --- filename \t|space stuff
1380 s = str[4:].rstrip('\r\n')
1382 s = str[4:].rstrip('\r\n')
1381 i = s.find('\t')
1383 i = s.find('\t')
1382 if i < 0:
1384 if i < 0:
1383 i = s.find(' ')
1385 i = s.find(' ')
1384 if i < 0:
1386 if i < 0:
1385 return s
1387 return s
1386 return s[:i]
1388 return s[:i]
1387
1389
1388 def reversehunks(hunks):
1390 def reversehunks(hunks):
1389 '''reverse the signs in the hunks given as argument
1391 '''reverse the signs in the hunks given as argument
1390
1392
1391 This function operates on hunks coming out of patch.filterpatch, that is
1393 This function operates on hunks coming out of patch.filterpatch, that is
1392 a list of the form: [header1, hunk1, hunk2, header2...]. Example usage:
1394 a list of the form: [header1, hunk1, hunk2, header2...]. Example usage:
1393
1395
1394 >>> rawpatch = """diff --git a/folder1/g b/folder1/g
1396 >>> rawpatch = """diff --git a/folder1/g b/folder1/g
1395 ... --- a/folder1/g
1397 ... --- a/folder1/g
1396 ... +++ b/folder1/g
1398 ... +++ b/folder1/g
1397 ... @@ -1,7 +1,7 @@
1399 ... @@ -1,7 +1,7 @@
1398 ... +firstline
1400 ... +firstline
1399 ... c
1401 ... c
1400 ... 1
1402 ... 1
1401 ... 2
1403 ... 2
1402 ... + 3
1404 ... + 3
1403 ... -4
1405 ... -4
1404 ... 5
1406 ... 5
1405 ... d
1407 ... d
1406 ... +lastline"""
1408 ... +lastline"""
1407 >>> hunks = parsepatch(rawpatch)
1409 >>> hunks = parsepatch(rawpatch)
1408 >>> hunkscomingfromfilterpatch = []
1410 >>> hunkscomingfromfilterpatch = []
1409 >>> for h in hunks:
1411 >>> for h in hunks:
1410 ... hunkscomingfromfilterpatch.append(h)
1412 ... hunkscomingfromfilterpatch.append(h)
1411 ... hunkscomingfromfilterpatch.extend(h.hunks)
1413 ... hunkscomingfromfilterpatch.extend(h.hunks)
1412
1414
1413 >>> reversedhunks = reversehunks(hunkscomingfromfilterpatch)
1415 >>> reversedhunks = reversehunks(hunkscomingfromfilterpatch)
1414 >>> fp = cStringIO.StringIO()
1416 >>> fp = cStringIO.StringIO()
1415 >>> for c in reversedhunks:
1417 >>> for c in reversedhunks:
1416 ... c.write(fp)
1418 ... c.write(fp)
1417 >>> fp.seek(0)
1419 >>> fp.seek(0)
1418 >>> reversedpatch = fp.read()
1420 >>> reversedpatch = fp.read()
1419 >>> print reversedpatch
1421 >>> print reversedpatch
1420 diff --git a/folder1/g b/folder1/g
1422 diff --git a/folder1/g b/folder1/g
1421 --- a/folder1/g
1423 --- a/folder1/g
1422 +++ b/folder1/g
1424 +++ b/folder1/g
1423 @@ -1,4 +1,3 @@
1425 @@ -1,4 +1,3 @@
1424 -firstline
1426 -firstline
1425 c
1427 c
1426 1
1428 1
1427 2
1429 2
1428 @@ -1,6 +2,6 @@
1430 @@ -1,6 +2,6 @@
1429 c
1431 c
1430 1
1432 1
1431 2
1433 2
1432 - 3
1434 - 3
1433 +4
1435 +4
1434 5
1436 5
1435 d
1437 d
1436 @@ -5,3 +6,2 @@
1438 @@ -5,3 +6,2 @@
1437 5
1439 5
1438 d
1440 d
1439 -lastline
1441 -lastline
1440
1442
1441 '''
1443 '''
1442
1444
1443 import crecord as crecordmod
1445 import crecord as crecordmod
1444 newhunks = []
1446 newhunks = []
1445 for c in hunks:
1447 for c in hunks:
1446 if isinstance(c, crecordmod.uihunk):
1448 if isinstance(c, crecordmod.uihunk):
1447 # curses hunks encapsulate the record hunk in _hunk
1449 # curses hunks encapsulate the record hunk in _hunk
1448 c = c._hunk
1450 c = c._hunk
1449 if isinstance(c, recordhunk):
1451 if isinstance(c, recordhunk):
1450 for j, line in enumerate(c.hunk):
1452 for j, line in enumerate(c.hunk):
1451 if line.startswith("-"):
1453 if line.startswith("-"):
1452 c.hunk[j] = "+" + c.hunk[j][1:]
1454 c.hunk[j] = "+" + c.hunk[j][1:]
1453 elif line.startswith("+"):
1455 elif line.startswith("+"):
1454 c.hunk[j] = "-" + c.hunk[j][1:]
1456 c.hunk[j] = "-" + c.hunk[j][1:]
1455 c.added, c.removed = c.removed, c.added
1457 c.added, c.removed = c.removed, c.added
1456 newhunks.append(c)
1458 newhunks.append(c)
1457 return newhunks
1459 return newhunks
1458
1460
1459 def parsepatch(originalchunks):
1461 def parsepatch(originalchunks):
1460 """patch -> [] of headers -> [] of hunks """
1462 """patch -> [] of headers -> [] of hunks """
1461 class parser(object):
1463 class parser(object):
1462 """patch parsing state machine"""
1464 """patch parsing state machine"""
1463 def __init__(self):
1465 def __init__(self):
1464 self.fromline = 0
1466 self.fromline = 0
1465 self.toline = 0
1467 self.toline = 0
1466 self.proc = ''
1468 self.proc = ''
1467 self.header = None
1469 self.header = None
1468 self.context = []
1470 self.context = []
1469 self.before = []
1471 self.before = []
1470 self.hunk = []
1472 self.hunk = []
1471 self.headers = []
1473 self.headers = []
1472
1474
1473 def addrange(self, limits):
1475 def addrange(self, limits):
1474 fromstart, fromend, tostart, toend, proc = limits
1476 fromstart, fromend, tostart, toend, proc = limits
1475 self.fromline = int(fromstart)
1477 self.fromline = int(fromstart)
1476 self.toline = int(tostart)
1478 self.toline = int(tostart)
1477 self.proc = proc
1479 self.proc = proc
1478
1480
1479 def addcontext(self, context):
1481 def addcontext(self, context):
1480 if self.hunk:
1482 if self.hunk:
1481 h = recordhunk(self.header, self.fromline, self.toline,
1483 h = recordhunk(self.header, self.fromline, self.toline,
1482 self.proc, self.before, self.hunk, context)
1484 self.proc, self.before, self.hunk, context)
1483 self.header.hunks.append(h)
1485 self.header.hunks.append(h)
1484 self.fromline += len(self.before) + h.removed
1486 self.fromline += len(self.before) + h.removed
1485 self.toline += len(self.before) + h.added
1487 self.toline += len(self.before) + h.added
1486 self.before = []
1488 self.before = []
1487 self.hunk = []
1489 self.hunk = []
1488 self.proc = ''
1490 self.proc = ''
1489 self.context = context
1491 self.context = context
1490
1492
1491 def addhunk(self, hunk):
1493 def addhunk(self, hunk):
1492 if self.context:
1494 if self.context:
1493 self.before = self.context
1495 self.before = self.context
1494 self.context = []
1496 self.context = []
1495 self.hunk = hunk
1497 self.hunk = hunk
1496
1498
1497 def newfile(self, hdr):
1499 def newfile(self, hdr):
1498 self.addcontext([])
1500 self.addcontext([])
1499 h = header(hdr)
1501 h = header(hdr)
1500 self.headers.append(h)
1502 self.headers.append(h)
1501 self.header = h
1503 self.header = h
1502
1504
1503 def addother(self, line):
1505 def addother(self, line):
1504 pass # 'other' lines are ignored
1506 pass # 'other' lines are ignored
1505
1507
1506 def finished(self):
1508 def finished(self):
1507 self.addcontext([])
1509 self.addcontext([])
1508 return self.headers
1510 return self.headers
1509
1511
1510 transitions = {
1512 transitions = {
1511 'file': {'context': addcontext,
1513 'file': {'context': addcontext,
1512 'file': newfile,
1514 'file': newfile,
1513 'hunk': addhunk,
1515 'hunk': addhunk,
1514 'range': addrange},
1516 'range': addrange},
1515 'context': {'file': newfile,
1517 'context': {'file': newfile,
1516 'hunk': addhunk,
1518 'hunk': addhunk,
1517 'range': addrange,
1519 'range': addrange,
1518 'other': addother},
1520 'other': addother},
1519 'hunk': {'context': addcontext,
1521 'hunk': {'context': addcontext,
1520 'file': newfile,
1522 'file': newfile,
1521 'range': addrange},
1523 'range': addrange},
1522 'range': {'context': addcontext,
1524 'range': {'context': addcontext,
1523 'hunk': addhunk},
1525 'hunk': addhunk},
1524 'other': {'other': addother},
1526 'other': {'other': addother},
1525 }
1527 }
1526
1528
1527 p = parser()
1529 p = parser()
1528 fp = cStringIO.StringIO()
1530 fp = cStringIO.StringIO()
1529 fp.write(''.join(originalchunks))
1531 fp.write(''.join(originalchunks))
1530 fp.seek(0)
1532 fp.seek(0)
1531
1533
1532 state = 'context'
1534 state = 'context'
1533 for newstate, data in scanpatch(fp):
1535 for newstate, data in scanpatch(fp):
1534 try:
1536 try:
1535 p.transitions[state][newstate](p, data)
1537 p.transitions[state][newstate](p, data)
1536 except KeyError:
1538 except KeyError:
1537 raise PatchError('unhandled transition: %s -> %s' %
1539 raise PatchError('unhandled transition: %s -> %s' %
1538 (state, newstate))
1540 (state, newstate))
1539 state = newstate
1541 state = newstate
1540 del fp
1542 del fp
1541 return p.finished()
1543 return p.finished()
1542
1544
1543 def pathtransform(path, strip, prefix):
1545 def pathtransform(path, strip, prefix):
1544 '''turn a path from a patch into a path suitable for the repository
1546 '''turn a path from a patch into a path suitable for the repository
1545
1547
1546 prefix, if not empty, is expected to be normalized with a / at the end.
1548 prefix, if not empty, is expected to be normalized with a / at the end.
1547
1549
1548 Returns (stripped components, path in repository).
1550 Returns (stripped components, path in repository).
1549
1551
1550 >>> pathtransform('a/b/c', 0, '')
1552 >>> pathtransform('a/b/c', 0, '')
1551 ('', 'a/b/c')
1553 ('', 'a/b/c')
1552 >>> pathtransform(' a/b/c ', 0, '')
1554 >>> pathtransform(' a/b/c ', 0, '')
1553 ('', ' a/b/c')
1555 ('', ' a/b/c')
1554 >>> pathtransform(' a/b/c ', 2, '')
1556 >>> pathtransform(' a/b/c ', 2, '')
1555 ('a/b/', 'c')
1557 ('a/b/', 'c')
1556 >>> pathtransform('a/b/c', 0, 'd/e/')
1558 >>> pathtransform('a/b/c', 0, 'd/e/')
1557 ('', 'd/e/a/b/c')
1559 ('', 'd/e/a/b/c')
1558 >>> pathtransform(' a//b/c ', 2, 'd/e/')
1560 >>> pathtransform(' a//b/c ', 2, 'd/e/')
1559 ('a//b/', 'd/e/c')
1561 ('a//b/', 'd/e/c')
1560 >>> pathtransform('a/b/c', 3, '')
1562 >>> pathtransform('a/b/c', 3, '')
1561 Traceback (most recent call last):
1563 Traceback (most recent call last):
1562 PatchError: unable to strip away 1 of 3 dirs from a/b/c
1564 PatchError: unable to strip away 1 of 3 dirs from a/b/c
1563 '''
1565 '''
1564 pathlen = len(path)
1566 pathlen = len(path)
1565 i = 0
1567 i = 0
1566 if strip == 0:
1568 if strip == 0:
1567 return '', prefix + path.rstrip()
1569 return '', prefix + path.rstrip()
1568 count = strip
1570 count = strip
1569 while count > 0:
1571 while count > 0:
1570 i = path.find('/', i)
1572 i = path.find('/', i)
1571 if i == -1:
1573 if i == -1:
1572 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1574 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1573 (count, strip, path))
1575 (count, strip, path))
1574 i += 1
1576 i += 1
1575 # consume '//' in the path
1577 # consume '//' in the path
1576 while i < pathlen - 1 and path[i] == '/':
1578 while i < pathlen - 1 and path[i] == '/':
1577 i += 1
1579 i += 1
1578 count -= 1
1580 count -= 1
1579 return path[:i].lstrip(), prefix + path[i:].rstrip()
1581 return path[:i].lstrip(), prefix + path[i:].rstrip()
1580
1582
1581 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip, prefix):
1583 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip, prefix):
1582 nulla = afile_orig == "/dev/null"
1584 nulla = afile_orig == "/dev/null"
1583 nullb = bfile_orig == "/dev/null"
1585 nullb = bfile_orig == "/dev/null"
1584 create = nulla and hunk.starta == 0 and hunk.lena == 0
1586 create = nulla and hunk.starta == 0 and hunk.lena == 0
1585 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1587 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1586 abase, afile = pathtransform(afile_orig, strip, prefix)
1588 abase, afile = pathtransform(afile_orig, strip, prefix)
1587 gooda = not nulla and backend.exists(afile)
1589 gooda = not nulla and backend.exists(afile)
1588 bbase, bfile = pathtransform(bfile_orig, strip, prefix)
1590 bbase, bfile = pathtransform(bfile_orig, strip, prefix)
1589 if afile == bfile:
1591 if afile == bfile:
1590 goodb = gooda
1592 goodb = gooda
1591 else:
1593 else:
1592 goodb = not nullb and backend.exists(bfile)
1594 goodb = not nullb and backend.exists(bfile)
1593 missing = not goodb and not gooda and not create
1595 missing = not goodb and not gooda and not create
1594
1596
1595 # some diff programs apparently produce patches where the afile is
1597 # some diff programs apparently produce patches where the afile is
1596 # not /dev/null, but afile starts with bfile
1598 # not /dev/null, but afile starts with bfile
1597 abasedir = afile[:afile.rfind('/') + 1]
1599 abasedir = afile[:afile.rfind('/') + 1]
1598 bbasedir = bfile[:bfile.rfind('/') + 1]
1600 bbasedir = bfile[:bfile.rfind('/') + 1]
1599 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1601 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1600 and hunk.starta == 0 and hunk.lena == 0):
1602 and hunk.starta == 0 and hunk.lena == 0):
1601 create = True
1603 create = True
1602 missing = False
1604 missing = False
1603
1605
1604 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1606 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1605 # diff is between a file and its backup. In this case, the original
1607 # diff is between a file and its backup. In this case, the original
1606 # file should be patched (see original mpatch code).
1608 # file should be patched (see original mpatch code).
1607 isbackup = (abase == bbase and bfile.startswith(afile))
1609 isbackup = (abase == bbase and bfile.startswith(afile))
1608 fname = None
1610 fname = None
1609 if not missing:
1611 if not missing:
1610 if gooda and goodb:
1612 if gooda and goodb:
1611 if isbackup:
1613 if isbackup:
1612 fname = afile
1614 fname = afile
1613 else:
1615 else:
1614 fname = bfile
1616 fname = bfile
1615 elif gooda:
1617 elif gooda:
1616 fname = afile
1618 fname = afile
1617
1619
1618 if not fname:
1620 if not fname:
1619 if not nullb:
1621 if not nullb:
1620 if isbackup:
1622 if isbackup:
1621 fname = afile
1623 fname = afile
1622 else:
1624 else:
1623 fname = bfile
1625 fname = bfile
1624 elif not nulla:
1626 elif not nulla:
1625 fname = afile
1627 fname = afile
1626 else:
1628 else:
1627 raise PatchError(_("undefined source and destination files"))
1629 raise PatchError(_("undefined source and destination files"))
1628
1630
1629 gp = patchmeta(fname)
1631 gp = patchmeta(fname)
1630 if create:
1632 if create:
1631 gp.op = 'ADD'
1633 gp.op = 'ADD'
1632 elif remove:
1634 elif remove:
1633 gp.op = 'DELETE'
1635 gp.op = 'DELETE'
1634 return gp
1636 return gp
1635
1637
1636 def scanpatch(fp):
1638 def scanpatch(fp):
1637 """like patch.iterhunks, but yield different events
1639 """like patch.iterhunks, but yield different events
1638
1640
1639 - ('file', [header_lines + fromfile + tofile])
1641 - ('file', [header_lines + fromfile + tofile])
1640 - ('context', [context_lines])
1642 - ('context', [context_lines])
1641 - ('hunk', [hunk_lines])
1643 - ('hunk', [hunk_lines])
1642 - ('range', (-start,len, +start,len, proc))
1644 - ('range', (-start,len, +start,len, proc))
1643 """
1645 """
1644 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
1646 lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
1645 lr = linereader(fp)
1647 lr = linereader(fp)
1646
1648
1647 def scanwhile(first, p):
1649 def scanwhile(first, p):
1648 """scan lr while predicate holds"""
1650 """scan lr while predicate holds"""
1649 lines = [first]
1651 lines = [first]
1650 while True:
1652 while True:
1651 line = lr.readline()
1653 line = lr.readline()
1652 if not line:
1654 if not line:
1653 break
1655 break
1654 if p(line):
1656 if p(line):
1655 lines.append(line)
1657 lines.append(line)
1656 else:
1658 else:
1657 lr.push(line)
1659 lr.push(line)
1658 break
1660 break
1659 return lines
1661 return lines
1660
1662
1661 while True:
1663 while True:
1662 line = lr.readline()
1664 line = lr.readline()
1663 if not line:
1665 if not line:
1664 break
1666 break
1665 if line.startswith('diff --git a/') or line.startswith('diff -r '):
1667 if line.startswith('diff --git a/') or line.startswith('diff -r '):
1666 def notheader(line):
1668 def notheader(line):
1667 s = line.split(None, 1)
1669 s = line.split(None, 1)
1668 return not s or s[0] not in ('---', 'diff')
1670 return not s or s[0] not in ('---', 'diff')
1669 header = scanwhile(line, notheader)
1671 header = scanwhile(line, notheader)
1670 fromfile = lr.readline()
1672 fromfile = lr.readline()
1671 if fromfile.startswith('---'):
1673 if fromfile.startswith('---'):
1672 tofile = lr.readline()
1674 tofile = lr.readline()
1673 header += [fromfile, tofile]
1675 header += [fromfile, tofile]
1674 else:
1676 else:
1675 lr.push(fromfile)
1677 lr.push(fromfile)
1676 yield 'file', header
1678 yield 'file', header
1677 elif line[0] == ' ':
1679 elif line[0] == ' ':
1678 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
1680 yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
1679 elif line[0] in '-+':
1681 elif line[0] in '-+':
1680 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
1682 yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
1681 else:
1683 else:
1682 m = lines_re.match(line)
1684 m = lines_re.match(line)
1683 if m:
1685 if m:
1684 yield 'range', m.groups()
1686 yield 'range', m.groups()
1685 else:
1687 else:
1686 yield 'other', line
1688 yield 'other', line
1687
1689
1688 def scangitpatch(lr, firstline):
1690 def scangitpatch(lr, firstline):
1689 """
1691 """
1690 Git patches can emit:
1692 Git patches can emit:
1691 - rename a to b
1693 - rename a to b
1692 - change b
1694 - change b
1693 - copy a to c
1695 - copy a to c
1694 - change c
1696 - change c
1695
1697
1696 We cannot apply this sequence as-is, the renamed 'a' could not be
1698 We cannot apply this sequence as-is, the renamed 'a' could not be
1697 found for it would have been renamed already. And we cannot copy
1699 found for it would have been renamed already. And we cannot copy
1698 from 'b' instead because 'b' would have been changed already. So
1700 from 'b' instead because 'b' would have been changed already. So
1699 we scan the git patch for copy and rename commands so we can
1701 we scan the git patch for copy and rename commands so we can
1700 perform the copies ahead of time.
1702 perform the copies ahead of time.
1701 """
1703 """
1702 pos = 0
1704 pos = 0
1703 try:
1705 try:
1704 pos = lr.fp.tell()
1706 pos = lr.fp.tell()
1705 fp = lr.fp
1707 fp = lr.fp
1706 except IOError:
1708 except IOError:
1707 fp = cStringIO.StringIO(lr.fp.read())
1709 fp = cStringIO.StringIO(lr.fp.read())
1708 gitlr = linereader(fp)
1710 gitlr = linereader(fp)
1709 gitlr.push(firstline)
1711 gitlr.push(firstline)
1710 gitpatches = readgitpatch(gitlr)
1712 gitpatches = readgitpatch(gitlr)
1711 fp.seek(pos)
1713 fp.seek(pos)
1712 return gitpatches
1714 return gitpatches
1713
1715
1714 def iterhunks(fp):
1716 def iterhunks(fp):
1715 """Read a patch and yield the following events:
1717 """Read a patch and yield the following events:
1716 - ("file", afile, bfile, firsthunk): select a new target file.
1718 - ("file", afile, bfile, firsthunk): select a new target file.
1717 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1719 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1718 "file" event.
1720 "file" event.
1719 - ("git", gitchanges): current diff is in git format, gitchanges
1721 - ("git", gitchanges): current diff is in git format, gitchanges
1720 maps filenames to gitpatch records. Unique event.
1722 maps filenames to gitpatch records. Unique event.
1721 """
1723 """
1722 afile = ""
1724 afile = ""
1723 bfile = ""
1725 bfile = ""
1724 state = None
1726 state = None
1725 hunknum = 0
1727 hunknum = 0
1726 emitfile = newfile = False
1728 emitfile = newfile = False
1727 gitpatches = None
1729 gitpatches = None
1728
1730
1729 # our states
1731 # our states
1730 BFILE = 1
1732 BFILE = 1
1731 context = None
1733 context = None
1732 lr = linereader(fp)
1734 lr = linereader(fp)
1733
1735
1734 while True:
1736 while True:
1735 x = lr.readline()
1737 x = lr.readline()
1736 if not x:
1738 if not x:
1737 break
1739 break
1738 if state == BFILE and (
1740 if state == BFILE and (
1739 (not context and x[0] == '@')
1741 (not context and x[0] == '@')
1740 or (context is not False and x.startswith('***************'))
1742 or (context is not False and x.startswith('***************'))
1741 or x.startswith('GIT binary patch')):
1743 or x.startswith('GIT binary patch')):
1742 gp = None
1744 gp = None
1743 if (gitpatches and
1745 if (gitpatches and
1744 gitpatches[-1].ispatching(afile, bfile)):
1746 gitpatches[-1].ispatching(afile, bfile)):
1745 gp = gitpatches.pop()
1747 gp = gitpatches.pop()
1746 if x.startswith('GIT binary patch'):
1748 if x.startswith('GIT binary patch'):
1747 h = binhunk(lr, gp.path)
1749 h = binhunk(lr, gp.path)
1748 else:
1750 else:
1749 if context is None and x.startswith('***************'):
1751 if context is None and x.startswith('***************'):
1750 context = True
1752 context = True
1751 h = hunk(x, hunknum + 1, lr, context)
1753 h = hunk(x, hunknum + 1, lr, context)
1752 hunknum += 1
1754 hunknum += 1
1753 if emitfile:
1755 if emitfile:
1754 emitfile = False
1756 emitfile = False
1755 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1757 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1756 yield 'hunk', h
1758 yield 'hunk', h
1757 elif x.startswith('diff --git a/'):
1759 elif x.startswith('diff --git a/'):
1758 m = gitre.match(x.rstrip(' \r\n'))
1760 m = gitre.match(x.rstrip(' \r\n'))
1759 if not m:
1761 if not m:
1760 continue
1762 continue
1761 if gitpatches is None:
1763 if gitpatches is None:
1762 # scan whole input for git metadata
1764 # scan whole input for git metadata
1763 gitpatches = scangitpatch(lr, x)
1765 gitpatches = scangitpatch(lr, x)
1764 yield 'git', [g.copy() for g in gitpatches
1766 yield 'git', [g.copy() for g in gitpatches
1765 if g.op in ('COPY', 'RENAME')]
1767 if g.op in ('COPY', 'RENAME')]
1766 gitpatches.reverse()
1768 gitpatches.reverse()
1767 afile = 'a/' + m.group(1)
1769 afile = 'a/' + m.group(1)
1768 bfile = 'b/' + m.group(2)
1770 bfile = 'b/' + m.group(2)
1769 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1771 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1770 gp = gitpatches.pop()
1772 gp = gitpatches.pop()
1771 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1773 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1772 if not gitpatches:
1774 if not gitpatches:
1773 raise PatchError(_('failed to synchronize metadata for "%s"')
1775 raise PatchError(_('failed to synchronize metadata for "%s"')
1774 % afile[2:])
1776 % afile[2:])
1775 gp = gitpatches[-1]
1777 gp = gitpatches[-1]
1776 newfile = True
1778 newfile = True
1777 elif x.startswith('---'):
1779 elif x.startswith('---'):
1778 # check for a unified diff
1780 # check for a unified diff
1779 l2 = lr.readline()
1781 l2 = lr.readline()
1780 if not l2.startswith('+++'):
1782 if not l2.startswith('+++'):
1781 lr.push(l2)
1783 lr.push(l2)
1782 continue
1784 continue
1783 newfile = True
1785 newfile = True
1784 context = False
1786 context = False
1785 afile = parsefilename(x)
1787 afile = parsefilename(x)
1786 bfile = parsefilename(l2)
1788 bfile = parsefilename(l2)
1787 elif x.startswith('***'):
1789 elif x.startswith('***'):
1788 # check for a context diff
1790 # check for a context diff
1789 l2 = lr.readline()
1791 l2 = lr.readline()
1790 if not l2.startswith('---'):
1792 if not l2.startswith('---'):
1791 lr.push(l2)
1793 lr.push(l2)
1792 continue
1794 continue
1793 l3 = lr.readline()
1795 l3 = lr.readline()
1794 lr.push(l3)
1796 lr.push(l3)
1795 if not l3.startswith("***************"):
1797 if not l3.startswith("***************"):
1796 lr.push(l2)
1798 lr.push(l2)
1797 continue
1799 continue
1798 newfile = True
1800 newfile = True
1799 context = True
1801 context = True
1800 afile = parsefilename(x)
1802 afile = parsefilename(x)
1801 bfile = parsefilename(l2)
1803 bfile = parsefilename(l2)
1802
1804
1803 if newfile:
1805 if newfile:
1804 newfile = False
1806 newfile = False
1805 emitfile = True
1807 emitfile = True
1806 state = BFILE
1808 state = BFILE
1807 hunknum = 0
1809 hunknum = 0
1808
1810
1809 while gitpatches:
1811 while gitpatches:
1810 gp = gitpatches.pop()
1812 gp = gitpatches.pop()
1811 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1813 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1812
1814
1813 def applybindelta(binchunk, data):
1815 def applybindelta(binchunk, data):
1814 """Apply a binary delta hunk
1816 """Apply a binary delta hunk
1815 The algorithm used is the algorithm from git's patch-delta.c
1817 The algorithm used is the algorithm from git's patch-delta.c
1816 """
1818 """
1817 def deltahead(binchunk):
1819 def deltahead(binchunk):
1818 i = 0
1820 i = 0
1819 for c in binchunk:
1821 for c in binchunk:
1820 i += 1
1822 i += 1
1821 if not (ord(c) & 0x80):
1823 if not (ord(c) & 0x80):
1822 return i
1824 return i
1823 return i
1825 return i
1824 out = ""
1826 out = ""
1825 s = deltahead(binchunk)
1827 s = deltahead(binchunk)
1826 binchunk = binchunk[s:]
1828 binchunk = binchunk[s:]
1827 s = deltahead(binchunk)
1829 s = deltahead(binchunk)
1828 binchunk = binchunk[s:]
1830 binchunk = binchunk[s:]
1829 i = 0
1831 i = 0
1830 while i < len(binchunk):
1832 while i < len(binchunk):
1831 cmd = ord(binchunk[i])
1833 cmd = ord(binchunk[i])
1832 i += 1
1834 i += 1
1833 if (cmd & 0x80):
1835 if (cmd & 0x80):
1834 offset = 0
1836 offset = 0
1835 size = 0
1837 size = 0
1836 if (cmd & 0x01):
1838 if (cmd & 0x01):
1837 offset = ord(binchunk[i])
1839 offset = ord(binchunk[i])
1838 i += 1
1840 i += 1
1839 if (cmd & 0x02):
1841 if (cmd & 0x02):
1840 offset |= ord(binchunk[i]) << 8
1842 offset |= ord(binchunk[i]) << 8
1841 i += 1
1843 i += 1
1842 if (cmd & 0x04):
1844 if (cmd & 0x04):
1843 offset |= ord(binchunk[i]) << 16
1845 offset |= ord(binchunk[i]) << 16
1844 i += 1
1846 i += 1
1845 if (cmd & 0x08):
1847 if (cmd & 0x08):
1846 offset |= ord(binchunk[i]) << 24
1848 offset |= ord(binchunk[i]) << 24
1847 i += 1
1849 i += 1
1848 if (cmd & 0x10):
1850 if (cmd & 0x10):
1849 size = ord(binchunk[i])
1851 size = ord(binchunk[i])
1850 i += 1
1852 i += 1
1851 if (cmd & 0x20):
1853 if (cmd & 0x20):
1852 size |= ord(binchunk[i]) << 8
1854 size |= ord(binchunk[i]) << 8
1853 i += 1
1855 i += 1
1854 if (cmd & 0x40):
1856 if (cmd & 0x40):
1855 size |= ord(binchunk[i]) << 16
1857 size |= ord(binchunk[i]) << 16
1856 i += 1
1858 i += 1
1857 if size == 0:
1859 if size == 0:
1858 size = 0x10000
1860 size = 0x10000
1859 offset_end = offset + size
1861 offset_end = offset + size
1860 out += data[offset:offset_end]
1862 out += data[offset:offset_end]
1861 elif cmd != 0:
1863 elif cmd != 0:
1862 offset_end = i + cmd
1864 offset_end = i + cmd
1863 out += binchunk[i:offset_end]
1865 out += binchunk[i:offset_end]
1864 i += cmd
1866 i += cmd
1865 else:
1867 else:
1866 raise PatchError(_('unexpected delta opcode 0'))
1868 raise PatchError(_('unexpected delta opcode 0'))
1867 return out
1869 return out
1868
1870
1869 def applydiff(ui, fp, backend, store, strip=1, prefix='', eolmode='strict'):
1871 def applydiff(ui, fp, backend, store, strip=1, prefix='', eolmode='strict'):
1870 """Reads a patch from fp and tries to apply it.
1872 """Reads a patch from fp and tries to apply it.
1871
1873
1872 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1874 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1873 there was any fuzz.
1875 there was any fuzz.
1874
1876
1875 If 'eolmode' is 'strict', the patch content and patched file are
1877 If 'eolmode' is 'strict', the patch content and patched file are
1876 read in binary mode. Otherwise, line endings are ignored when
1878 read in binary mode. Otherwise, line endings are ignored when
1877 patching then normalized according to 'eolmode'.
1879 patching then normalized according to 'eolmode'.
1878 """
1880 """
1879 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1881 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1880 prefix=prefix, eolmode=eolmode)
1882 prefix=prefix, eolmode=eolmode)
1881
1883
1882 def _applydiff(ui, fp, patcher, backend, store, strip=1, prefix='',
1884 def _applydiff(ui, fp, patcher, backend, store, strip=1, prefix='',
1883 eolmode='strict'):
1885 eolmode='strict'):
1884
1886
1885 if prefix:
1887 if prefix:
1886 prefix = pathutil.canonpath(backend.repo.root, backend.repo.getcwd(),
1888 prefix = pathutil.canonpath(backend.repo.root, backend.repo.getcwd(),
1887 prefix)
1889 prefix)
1888 if prefix != '':
1890 if prefix != '':
1889 prefix += '/'
1891 prefix += '/'
1890 def pstrip(p):
1892 def pstrip(p):
1891 return pathtransform(p, strip - 1, prefix)[1]
1893 return pathtransform(p, strip - 1, prefix)[1]
1892
1894
1893 rejects = 0
1895 rejects = 0
1894 err = 0
1896 err = 0
1895 current_file = None
1897 current_file = None
1896
1898
1897 for state, values in iterhunks(fp):
1899 for state, values in iterhunks(fp):
1898 if state == 'hunk':
1900 if state == 'hunk':
1899 if not current_file:
1901 if not current_file:
1900 continue
1902 continue
1901 ret = current_file.apply(values)
1903 ret = current_file.apply(values)
1902 if ret > 0:
1904 if ret > 0:
1903 err = 1
1905 err = 1
1904 elif state == 'file':
1906 elif state == 'file':
1905 if current_file:
1907 if current_file:
1906 rejects += current_file.close()
1908 rejects += current_file.close()
1907 current_file = None
1909 current_file = None
1908 afile, bfile, first_hunk, gp = values
1910 afile, bfile, first_hunk, gp = values
1909 if gp:
1911 if gp:
1910 gp.path = pstrip(gp.path)
1912 gp.path = pstrip(gp.path)
1911 if gp.oldpath:
1913 if gp.oldpath:
1912 gp.oldpath = pstrip(gp.oldpath)
1914 gp.oldpath = pstrip(gp.oldpath)
1913 else:
1915 else:
1914 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
1916 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
1915 prefix)
1917 prefix)
1916 if gp.op == 'RENAME':
1918 if gp.op == 'RENAME':
1917 backend.unlink(gp.oldpath)
1919 backend.unlink(gp.oldpath)
1918 if not first_hunk:
1920 if not first_hunk:
1919 if gp.op == 'DELETE':
1921 if gp.op == 'DELETE':
1920 backend.unlink(gp.path)
1922 backend.unlink(gp.path)
1921 continue
1923 continue
1922 data, mode = None, None
1924 data, mode = None, None
1923 if gp.op in ('RENAME', 'COPY'):
1925 if gp.op in ('RENAME', 'COPY'):
1924 data, mode = store.getfile(gp.oldpath)[:2]
1926 data, mode = store.getfile(gp.oldpath)[:2]
1925 # FIXME: failing getfile has never been handled here
1927 # FIXME: failing getfile has never been handled here
1926 assert data is not None
1928 assert data is not None
1927 if gp.mode:
1929 if gp.mode:
1928 mode = gp.mode
1930 mode = gp.mode
1929 if gp.op == 'ADD':
1931 if gp.op == 'ADD':
1930 # Added files without content have no hunk and
1932 # Added files without content have no hunk and
1931 # must be created
1933 # must be created
1932 data = ''
1934 data = ''
1933 if data or mode:
1935 if data or mode:
1934 if (gp.op in ('ADD', 'RENAME', 'COPY')
1936 if (gp.op in ('ADD', 'RENAME', 'COPY')
1935 and backend.exists(gp.path)):
1937 and backend.exists(gp.path)):
1936 raise PatchError(_("cannot create %s: destination "
1938 raise PatchError(_("cannot create %s: destination "
1937 "already exists") % gp.path)
1939 "already exists") % gp.path)
1938 backend.setfile(gp.path, data, mode, gp.oldpath)
1940 backend.setfile(gp.path, data, mode, gp.oldpath)
1939 continue
1941 continue
1940 try:
1942 try:
1941 current_file = patcher(ui, gp, backend, store,
1943 current_file = patcher(ui, gp, backend, store,
1942 eolmode=eolmode)
1944 eolmode=eolmode)
1943 except PatchError, inst:
1945 except PatchError, inst:
1944 ui.warn(str(inst) + '\n')
1946 ui.warn(str(inst) + '\n')
1945 current_file = None
1947 current_file = None
1946 rejects += 1
1948 rejects += 1
1947 continue
1949 continue
1948 elif state == 'git':
1950 elif state == 'git':
1949 for gp in values:
1951 for gp in values:
1950 path = pstrip(gp.oldpath)
1952 path = pstrip(gp.oldpath)
1951 data, mode = backend.getfile(path)
1953 data, mode = backend.getfile(path)
1952 if data is None:
1954 if data is None:
1953 # The error ignored here will trigger a getfile()
1955 # The error ignored here will trigger a getfile()
1954 # error in a place more appropriate for error
1956 # error in a place more appropriate for error
1955 # handling, and will not interrupt the patching
1957 # handling, and will not interrupt the patching
1956 # process.
1958 # process.
1957 pass
1959 pass
1958 else:
1960 else:
1959 store.setfile(path, data, mode)
1961 store.setfile(path, data, mode)
1960 else:
1962 else:
1961 raise util.Abort(_('unsupported parser state: %s') % state)
1963 raise util.Abort(_('unsupported parser state: %s') % state)
1962
1964
1963 if current_file:
1965 if current_file:
1964 rejects += current_file.close()
1966 rejects += current_file.close()
1965
1967
1966 if rejects:
1968 if rejects:
1967 return -1
1969 return -1
1968 return err
1970 return err
1969
1971
1970 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1972 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1971 similarity):
1973 similarity):
1972 """use <patcher> to apply <patchname> to the working directory.
1974 """use <patcher> to apply <patchname> to the working directory.
1973 returns whether patch was applied with fuzz factor."""
1975 returns whether patch was applied with fuzz factor."""
1974
1976
1975 fuzz = False
1977 fuzz = False
1976 args = []
1978 args = []
1977 cwd = repo.root
1979 cwd = repo.root
1978 if cwd:
1980 if cwd:
1979 args.append('-d %s' % util.shellquote(cwd))
1981 args.append('-d %s' % util.shellquote(cwd))
1980 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1982 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1981 util.shellquote(patchname)))
1983 util.shellquote(patchname)))
1982 try:
1984 try:
1983 for line in fp:
1985 for line in fp:
1984 line = line.rstrip()
1986 line = line.rstrip()
1985 ui.note(line + '\n')
1987 ui.note(line + '\n')
1986 if line.startswith('patching file '):
1988 if line.startswith('patching file '):
1987 pf = util.parsepatchoutput(line)
1989 pf = util.parsepatchoutput(line)
1988 printed_file = False
1990 printed_file = False
1989 files.add(pf)
1991 files.add(pf)
1990 elif line.find('with fuzz') >= 0:
1992 elif line.find('with fuzz') >= 0:
1991 fuzz = True
1993 fuzz = True
1992 if not printed_file:
1994 if not printed_file:
1993 ui.warn(pf + '\n')
1995 ui.warn(pf + '\n')
1994 printed_file = True
1996 printed_file = True
1995 ui.warn(line + '\n')
1997 ui.warn(line + '\n')
1996 elif line.find('saving rejects to file') >= 0:
1998 elif line.find('saving rejects to file') >= 0:
1997 ui.warn(line + '\n')
1999 ui.warn(line + '\n')
1998 elif line.find('FAILED') >= 0:
2000 elif line.find('FAILED') >= 0:
1999 if not printed_file:
2001 if not printed_file:
2000 ui.warn(pf + '\n')
2002 ui.warn(pf + '\n')
2001 printed_file = True
2003 printed_file = True
2002 ui.warn(line + '\n')
2004 ui.warn(line + '\n')
2003 finally:
2005 finally:
2004 if files:
2006 if files:
2005 scmutil.marktouched(repo, files, similarity)
2007 scmutil.marktouched(repo, files, similarity)
2006 code = fp.close()
2008 code = fp.close()
2007 if code:
2009 if code:
2008 raise PatchError(_("patch command failed: %s") %
2010 raise PatchError(_("patch command failed: %s") %
2009 util.explainexit(code)[0])
2011 util.explainexit(code)[0])
2010 return fuzz
2012 return fuzz
2011
2013
2012 def patchbackend(ui, backend, patchobj, strip, prefix, files=None,
2014 def patchbackend(ui, backend, patchobj, strip, prefix, files=None,
2013 eolmode='strict'):
2015 eolmode='strict'):
2014 if files is None:
2016 if files is None:
2015 files = set()
2017 files = set()
2016 if eolmode is None:
2018 if eolmode is None:
2017 eolmode = ui.config('patch', 'eol', 'strict')
2019 eolmode = ui.config('patch', 'eol', 'strict')
2018 if eolmode.lower() not in eolmodes:
2020 if eolmode.lower() not in eolmodes:
2019 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
2021 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
2020 eolmode = eolmode.lower()
2022 eolmode = eolmode.lower()
2021
2023
2022 store = filestore()
2024 store = filestore()
2023 try:
2025 try:
2024 fp = open(patchobj, 'rb')
2026 fp = open(patchobj, 'rb')
2025 except TypeError:
2027 except TypeError:
2026 fp = patchobj
2028 fp = patchobj
2027 try:
2029 try:
2028 ret = applydiff(ui, fp, backend, store, strip=strip, prefix=prefix,
2030 ret = applydiff(ui, fp, backend, store, strip=strip, prefix=prefix,
2029 eolmode=eolmode)
2031 eolmode=eolmode)
2030 finally:
2032 finally:
2031 if fp != patchobj:
2033 if fp != patchobj:
2032 fp.close()
2034 fp.close()
2033 files.update(backend.close())
2035 files.update(backend.close())
2034 store.close()
2036 store.close()
2035 if ret < 0:
2037 if ret < 0:
2036 raise PatchError(_('patch failed to apply'))
2038 raise PatchError(_('patch failed to apply'))
2037 return ret > 0
2039 return ret > 0
2038
2040
2039 def internalpatch(ui, repo, patchobj, strip, prefix='', files=None,
2041 def internalpatch(ui, repo, patchobj, strip, prefix='', files=None,
2040 eolmode='strict', similarity=0):
2042 eolmode='strict', similarity=0):
2041 """use builtin patch to apply <patchobj> to the working directory.
2043 """use builtin patch to apply <patchobj> to the working directory.
2042 returns whether patch was applied with fuzz factor."""
2044 returns whether patch was applied with fuzz factor."""
2043 backend = workingbackend(ui, repo, similarity)
2045 backend = workingbackend(ui, repo, similarity)
2044 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
2046 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
2045
2047
2046 def patchrepo(ui, repo, ctx, store, patchobj, strip, prefix, files=None,
2048 def patchrepo(ui, repo, ctx, store, patchobj, strip, prefix, files=None,
2047 eolmode='strict'):
2049 eolmode='strict'):
2048 backend = repobackend(ui, repo, ctx, store)
2050 backend = repobackend(ui, repo, ctx, store)
2049 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
2051 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
2050
2052
2051 def patch(ui, repo, patchname, strip=1, prefix='', files=None, eolmode='strict',
2053 def patch(ui, repo, patchname, strip=1, prefix='', files=None, eolmode='strict',
2052 similarity=0):
2054 similarity=0):
2053 """Apply <patchname> to the working directory.
2055 """Apply <patchname> to the working directory.
2054
2056
2055 'eolmode' specifies how end of lines should be handled. It can be:
2057 'eolmode' specifies how end of lines should be handled. It can be:
2056 - 'strict': inputs are read in binary mode, EOLs are preserved
2058 - 'strict': inputs are read in binary mode, EOLs are preserved
2057 - 'crlf': EOLs are ignored when patching and reset to CRLF
2059 - 'crlf': EOLs are ignored when patching and reset to CRLF
2058 - 'lf': EOLs are ignored when patching and reset to LF
2060 - 'lf': EOLs are ignored when patching and reset to LF
2059 - None: get it from user settings, default to 'strict'
2061 - None: get it from user settings, default to 'strict'
2060 'eolmode' is ignored when using an external patcher program.
2062 'eolmode' is ignored when using an external patcher program.
2061
2063
2062 Returns whether patch was applied with fuzz factor.
2064 Returns whether patch was applied with fuzz factor.
2063 """
2065 """
2064 patcher = ui.config('ui', 'patch')
2066 patcher = ui.config('ui', 'patch')
2065 if files is None:
2067 if files is None:
2066 files = set()
2068 files = set()
2067 if patcher:
2069 if patcher:
2068 return _externalpatch(ui, repo, patcher, patchname, strip,
2070 return _externalpatch(ui, repo, patcher, patchname, strip,
2069 files, similarity)
2071 files, similarity)
2070 return internalpatch(ui, repo, patchname, strip, prefix, files, eolmode,
2072 return internalpatch(ui, repo, patchname, strip, prefix, files, eolmode,
2071 similarity)
2073 similarity)
2072
2074
2073 def changedfiles(ui, repo, patchpath, strip=1):
2075 def changedfiles(ui, repo, patchpath, strip=1):
2074 backend = fsbackend(ui, repo.root)
2076 backend = fsbackend(ui, repo.root)
2075 fp = open(patchpath, 'rb')
2077 fp = open(patchpath, 'rb')
2076 try:
2078 try:
2077 changed = set()
2079 changed = set()
2078 for state, values in iterhunks(fp):
2080 for state, values in iterhunks(fp):
2079 if state == 'file':
2081 if state == 'file':
2080 afile, bfile, first_hunk, gp = values
2082 afile, bfile, first_hunk, gp = values
2081 if gp:
2083 if gp:
2082 gp.path = pathtransform(gp.path, strip - 1, '')[1]
2084 gp.path = pathtransform(gp.path, strip - 1, '')[1]
2083 if gp.oldpath:
2085 if gp.oldpath:
2084 gp.oldpath = pathtransform(gp.oldpath, strip - 1, '')[1]
2086 gp.oldpath = pathtransform(gp.oldpath, strip - 1, '')[1]
2085 else:
2087 else:
2086 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
2088 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
2087 '')
2089 '')
2088 changed.add(gp.path)
2090 changed.add(gp.path)
2089 if gp.op == 'RENAME':
2091 if gp.op == 'RENAME':
2090 changed.add(gp.oldpath)
2092 changed.add(gp.oldpath)
2091 elif state not in ('hunk', 'git'):
2093 elif state not in ('hunk', 'git'):
2092 raise util.Abort(_('unsupported parser state: %s') % state)
2094 raise util.Abort(_('unsupported parser state: %s') % state)
2093 return changed
2095 return changed
2094 finally:
2096 finally:
2095 fp.close()
2097 fp.close()
2096
2098
2097 class GitDiffRequired(Exception):
2099 class GitDiffRequired(Exception):
2098 pass
2100 pass
2099
2101
2100 def diffallopts(ui, opts=None, untrusted=False, section='diff'):
2102 def diffallopts(ui, opts=None, untrusted=False, section='diff'):
2101 '''return diffopts with all features supported and parsed'''
2103 '''return diffopts with all features supported and parsed'''
2102 return difffeatureopts(ui, opts=opts, untrusted=untrusted, section=section,
2104 return difffeatureopts(ui, opts=opts, untrusted=untrusted, section=section,
2103 git=True, whitespace=True, formatchanging=True)
2105 git=True, whitespace=True, formatchanging=True)
2104
2106
2105 diffopts = diffallopts
2107 diffopts = diffallopts
2106
2108
2107 def difffeatureopts(ui, opts=None, untrusted=False, section='diff', git=False,
2109 def difffeatureopts(ui, opts=None, untrusted=False, section='diff', git=False,
2108 whitespace=False, formatchanging=False):
2110 whitespace=False, formatchanging=False):
2109 '''return diffopts with only opted-in features parsed
2111 '''return diffopts with only opted-in features parsed
2110
2112
2111 Features:
2113 Features:
2112 - git: git-style diffs
2114 - git: git-style diffs
2113 - whitespace: whitespace options like ignoreblanklines and ignorews
2115 - whitespace: whitespace options like ignoreblanklines and ignorews
2114 - formatchanging: options that will likely break or cause correctness issues
2116 - formatchanging: options that will likely break or cause correctness issues
2115 with most diff parsers
2117 with most diff parsers
2116 '''
2118 '''
2117 def get(key, name=None, getter=ui.configbool, forceplain=None):
2119 def get(key, name=None, getter=ui.configbool, forceplain=None):
2118 if opts:
2120 if opts:
2119 v = opts.get(key)
2121 v = opts.get(key)
2120 if v:
2122 if v:
2121 return v
2123 return v
2122 if forceplain is not None and ui.plain():
2124 if forceplain is not None and ui.plain():
2123 return forceplain
2125 return forceplain
2124 return getter(section, name or key, None, untrusted=untrusted)
2126 return getter(section, name or key, None, untrusted=untrusted)
2125
2127
2126 # core options, expected to be understood by every diff parser
2128 # core options, expected to be understood by every diff parser
2127 buildopts = {
2129 buildopts = {
2128 'nodates': get('nodates'),
2130 'nodates': get('nodates'),
2129 'showfunc': get('show_function', 'showfunc'),
2131 'showfunc': get('show_function', 'showfunc'),
2130 'context': get('unified', getter=ui.config),
2132 'context': get('unified', getter=ui.config),
2131 }
2133 }
2132
2134
2133 if git:
2135 if git:
2134 buildopts['git'] = get('git')
2136 buildopts['git'] = get('git')
2135 if whitespace:
2137 if whitespace:
2136 buildopts['ignorews'] = get('ignore_all_space', 'ignorews')
2138 buildopts['ignorews'] = get('ignore_all_space', 'ignorews')
2137 buildopts['ignorewsamount'] = get('ignore_space_change',
2139 buildopts['ignorewsamount'] = get('ignore_space_change',
2138 'ignorewsamount')
2140 'ignorewsamount')
2139 buildopts['ignoreblanklines'] = get('ignore_blank_lines',
2141 buildopts['ignoreblanklines'] = get('ignore_blank_lines',
2140 'ignoreblanklines')
2142 'ignoreblanklines')
2141 if formatchanging:
2143 if formatchanging:
2142 buildopts['text'] = opts and opts.get('text')
2144 buildopts['text'] = opts and opts.get('text')
2143 buildopts['nobinary'] = get('nobinary')
2145 buildopts['nobinary'] = get('nobinary')
2144 buildopts['noprefix'] = get('noprefix', forceplain=False)
2146 buildopts['noprefix'] = get('noprefix', forceplain=False)
2145
2147
2146 return mdiff.diffopts(**buildopts)
2148 return mdiff.diffopts(**buildopts)
2147
2149
2148 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
2150 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
2149 losedatafn=None, prefix='', relroot=''):
2151 losedatafn=None, prefix='', relroot=''):
2150 '''yields diff of changes to files between two nodes, or node and
2152 '''yields diff of changes to files between two nodes, or node and
2151 working directory.
2153 working directory.
2152
2154
2153 if node1 is None, use first dirstate parent instead.
2155 if node1 is None, use first dirstate parent instead.
2154 if node2 is None, compare node1 with working directory.
2156 if node2 is None, compare node1 with working directory.
2155
2157
2156 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
2158 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
2157 every time some change cannot be represented with the current
2159 every time some change cannot be represented with the current
2158 patch format. Return False to upgrade to git patch format, True to
2160 patch format. Return False to upgrade to git patch format, True to
2159 accept the loss or raise an exception to abort the diff. It is
2161 accept the loss or raise an exception to abort the diff. It is
2160 called with the name of current file being diffed as 'fn'. If set
2162 called with the name of current file being diffed as 'fn'. If set
2161 to None, patches will always be upgraded to git format when
2163 to None, patches will always be upgraded to git format when
2162 necessary.
2164 necessary.
2163
2165
2164 prefix is a filename prefix that is prepended to all filenames on
2166 prefix is a filename prefix that is prepended to all filenames on
2165 display (used for subrepos).
2167 display (used for subrepos).
2166
2168
2167 relroot, if not empty, must be normalized with a trailing /. Any match
2169 relroot, if not empty, must be normalized with a trailing /. Any match
2168 patterns that fall outside it will be ignored.'''
2170 patterns that fall outside it will be ignored.'''
2169
2171
2170 if opts is None:
2172 if opts is None:
2171 opts = mdiff.defaultopts
2173 opts = mdiff.defaultopts
2172
2174
2173 if not node1 and not node2:
2175 if not node1 and not node2:
2174 node1 = repo.dirstate.p1()
2176 node1 = repo.dirstate.p1()
2175
2177
2176 def lrugetfilectx():
2178 def lrugetfilectx():
2177 cache = {}
2179 cache = {}
2178 order = collections.deque()
2180 order = collections.deque()
2179 def getfilectx(f, ctx):
2181 def getfilectx(f, ctx):
2180 fctx = ctx.filectx(f, filelog=cache.get(f))
2182 fctx = ctx.filectx(f, filelog=cache.get(f))
2181 if f not in cache:
2183 if f not in cache:
2182 if len(cache) > 20:
2184 if len(cache) > 20:
2183 del cache[order.popleft()]
2185 del cache[order.popleft()]
2184 cache[f] = fctx.filelog()
2186 cache[f] = fctx.filelog()
2185 else:
2187 else:
2186 order.remove(f)
2188 order.remove(f)
2187 order.append(f)
2189 order.append(f)
2188 return fctx
2190 return fctx
2189 return getfilectx
2191 return getfilectx
2190 getfilectx = lrugetfilectx()
2192 getfilectx = lrugetfilectx()
2191
2193
2192 ctx1 = repo[node1]
2194 ctx1 = repo[node1]
2193 ctx2 = repo[node2]
2195 ctx2 = repo[node2]
2194
2196
2195 relfiltered = False
2197 relfiltered = False
2196 if relroot != '' and match.always():
2198 if relroot != '' and match.always():
2197 # as a special case, create a new matcher with just the relroot
2199 # as a special case, create a new matcher with just the relroot
2198 pats = [relroot]
2200 pats = [relroot]
2199 match = scmutil.match(ctx2, pats, default='path')
2201 match = scmutil.match(ctx2, pats, default='path')
2200 relfiltered = True
2202 relfiltered = True
2201
2203
2202 if not changes:
2204 if not changes:
2203 changes = repo.status(ctx1, ctx2, match=match)
2205 changes = repo.status(ctx1, ctx2, match=match)
2204 modified, added, removed = changes[:3]
2206 modified, added, removed = changes[:3]
2205
2207
2206 if not modified and not added and not removed:
2208 if not modified and not added and not removed:
2207 return []
2209 return []
2208
2210
2209 if repo.ui.debugflag:
2211 if repo.ui.debugflag:
2210 hexfunc = hex
2212 hexfunc = hex
2211 else:
2213 else:
2212 hexfunc = short
2214 hexfunc = short
2213 revs = [hexfunc(node) for node in [ctx1.node(), ctx2.node()] if node]
2215 revs = [hexfunc(node) for node in [ctx1.node(), ctx2.node()] if node]
2214
2216
2215 copy = {}
2217 copy = {}
2216 if opts.git or opts.upgrade:
2218 if opts.git or opts.upgrade:
2217 copy = copies.pathcopies(ctx1, ctx2, match=match)
2219 copy = copies.pathcopies(ctx1, ctx2, match=match)
2218
2220
2219 if relroot is not None:
2221 if relroot is not None:
2220 if not relfiltered:
2222 if not relfiltered:
2221 # XXX this would ideally be done in the matcher, but that is
2223 # XXX this would ideally be done in the matcher, but that is
2222 # generally meant to 'or' patterns, not 'and' them. In this case we
2224 # generally meant to 'or' patterns, not 'and' them. In this case we
2223 # need to 'and' all the patterns from the matcher with relroot.
2225 # need to 'and' all the patterns from the matcher with relroot.
2224 def filterrel(l):
2226 def filterrel(l):
2225 return [f for f in l if f.startswith(relroot)]
2227 return [f for f in l if f.startswith(relroot)]
2226 modified = filterrel(modified)
2228 modified = filterrel(modified)
2227 added = filterrel(added)
2229 added = filterrel(added)
2228 removed = filterrel(removed)
2230 removed = filterrel(removed)
2229 relfiltered = True
2231 relfiltered = True
2230 # filter out copies where either side isn't inside the relative root
2232 # filter out copies where either side isn't inside the relative root
2231 copy = dict(((dst, src) for (dst, src) in copy.iteritems()
2233 copy = dict(((dst, src) for (dst, src) in copy.iteritems()
2232 if dst.startswith(relroot)
2234 if dst.startswith(relroot)
2233 and src.startswith(relroot)))
2235 and src.startswith(relroot)))
2234
2236
2235 def difffn(opts, losedata):
2237 def difffn(opts, losedata):
2236 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2238 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2237 copy, getfilectx, opts, losedata, prefix, relroot)
2239 copy, getfilectx, opts, losedata, prefix, relroot)
2238 if opts.upgrade and not opts.git:
2240 if opts.upgrade and not opts.git:
2239 try:
2241 try:
2240 def losedata(fn):
2242 def losedata(fn):
2241 if not losedatafn or not losedatafn(fn=fn):
2243 if not losedatafn or not losedatafn(fn=fn):
2242 raise GitDiffRequired
2244 raise GitDiffRequired
2243 # Buffer the whole output until we are sure it can be generated
2245 # Buffer the whole output until we are sure it can be generated
2244 return list(difffn(opts.copy(git=False), losedata))
2246 return list(difffn(opts.copy(git=False), losedata))
2245 except GitDiffRequired:
2247 except GitDiffRequired:
2246 return difffn(opts.copy(git=True), None)
2248 return difffn(opts.copy(git=True), None)
2247 else:
2249 else:
2248 return difffn(opts, None)
2250 return difffn(opts, None)
2249
2251
2250 def difflabel(func, *args, **kw):
2252 def difflabel(func, *args, **kw):
2251 '''yields 2-tuples of (output, label) based on the output of func()'''
2253 '''yields 2-tuples of (output, label) based on the output of func()'''
2252 headprefixes = [('diff', 'diff.diffline'),
2254 headprefixes = [('diff', 'diff.diffline'),
2253 ('copy', 'diff.extended'),
2255 ('copy', 'diff.extended'),
2254 ('rename', 'diff.extended'),
2256 ('rename', 'diff.extended'),
2255 ('old', 'diff.extended'),
2257 ('old', 'diff.extended'),
2256 ('new', 'diff.extended'),
2258 ('new', 'diff.extended'),
2257 ('deleted', 'diff.extended'),
2259 ('deleted', 'diff.extended'),
2258 ('---', 'diff.file_a'),
2260 ('---', 'diff.file_a'),
2259 ('+++', 'diff.file_b')]
2261 ('+++', 'diff.file_b')]
2260 textprefixes = [('@', 'diff.hunk'),
2262 textprefixes = [('@', 'diff.hunk'),
2261 ('-', 'diff.deleted'),
2263 ('-', 'diff.deleted'),
2262 ('+', 'diff.inserted')]
2264 ('+', 'diff.inserted')]
2263 head = False
2265 head = False
2264 for chunk in func(*args, **kw):
2266 for chunk in func(*args, **kw):
2265 lines = chunk.split('\n')
2267 lines = chunk.split('\n')
2266 for i, line in enumerate(lines):
2268 for i, line in enumerate(lines):
2267 if i != 0:
2269 if i != 0:
2268 yield ('\n', '')
2270 yield ('\n', '')
2269 if head:
2271 if head:
2270 if line.startswith('@'):
2272 if line.startswith('@'):
2271 head = False
2273 head = False
2272 else:
2274 else:
2273 if line and line[0] not in ' +-@\\':
2275 if line and line[0] not in ' +-@\\':
2274 head = True
2276 head = True
2275 stripline = line
2277 stripline = line
2276 diffline = False
2278 diffline = False
2277 if not head and line and line[0] in '+-':
2279 if not head and line and line[0] in '+-':
2278 # highlight tabs and trailing whitespace, but only in
2280 # highlight tabs and trailing whitespace, but only in
2279 # changed lines
2281 # changed lines
2280 stripline = line.rstrip()
2282 stripline = line.rstrip()
2281 diffline = True
2283 diffline = True
2282
2284
2283 prefixes = textprefixes
2285 prefixes = textprefixes
2284 if head:
2286 if head:
2285 prefixes = headprefixes
2287 prefixes = headprefixes
2286 for prefix, label in prefixes:
2288 for prefix, label in prefixes:
2287 if stripline.startswith(prefix):
2289 if stripline.startswith(prefix):
2288 if diffline:
2290 if diffline:
2289 for token in tabsplitter.findall(stripline):
2291 for token in tabsplitter.findall(stripline):
2290 if '\t' == token[0]:
2292 if '\t' == token[0]:
2291 yield (token, 'diff.tab')
2293 yield (token, 'diff.tab')
2292 else:
2294 else:
2293 yield (token, label)
2295 yield (token, label)
2294 else:
2296 else:
2295 yield (stripline, label)
2297 yield (stripline, label)
2296 break
2298 break
2297 else:
2299 else:
2298 yield (line, '')
2300 yield (line, '')
2299 if line != stripline:
2301 if line != stripline:
2300 yield (line[len(stripline):], 'diff.trailingwhitespace')
2302 yield (line[len(stripline):], 'diff.trailingwhitespace')
2301
2303
2302 def diffui(*args, **kw):
2304 def diffui(*args, **kw):
2303 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
2305 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
2304 return difflabel(diff, *args, **kw)
2306 return difflabel(diff, *args, **kw)
2305
2307
2306 def _filepairs(ctx1, modified, added, removed, copy, opts):
2308 def _filepairs(ctx1, modified, added, removed, copy, opts):
2307 '''generates tuples (f1, f2, copyop), where f1 is the name of the file
2309 '''generates tuples (f1, f2, copyop), where f1 is the name of the file
2308 before and f2 is the the name after. For added files, f1 will be None,
2310 before and f2 is the the name after. For added files, f1 will be None,
2309 and for removed files, f2 will be None. copyop may be set to None, 'copy'
2311 and for removed files, f2 will be None. copyop may be set to None, 'copy'
2310 or 'rename' (the latter two only if opts.git is set).'''
2312 or 'rename' (the latter two only if opts.git is set).'''
2311 gone = set()
2313 gone = set()
2312
2314
2313 copyto = dict([(v, k) for k, v in copy.items()])
2315 copyto = dict([(v, k) for k, v in copy.items()])
2314
2316
2315 addedset, removedset = set(added), set(removed)
2317 addedset, removedset = set(added), set(removed)
2316 # Fix up added, since merged-in additions appear as
2318 # Fix up added, since merged-in additions appear as
2317 # modifications during merges
2319 # modifications during merges
2318 for f in modified:
2320 for f in modified:
2319 if f not in ctx1:
2321 if f not in ctx1:
2320 addedset.add(f)
2322 addedset.add(f)
2321
2323
2322 for f in sorted(modified + added + removed):
2324 for f in sorted(modified + added + removed):
2323 copyop = None
2325 copyop = None
2324 f1, f2 = f, f
2326 f1, f2 = f, f
2325 if f in addedset:
2327 if f in addedset:
2326 f1 = None
2328 f1 = None
2327 if f in copy:
2329 if f in copy:
2328 if opts.git:
2330 if opts.git:
2329 f1 = copy[f]
2331 f1 = copy[f]
2330 if f1 in removedset and f1 not in gone:
2332 if f1 in removedset and f1 not in gone:
2331 copyop = 'rename'
2333 copyop = 'rename'
2332 gone.add(f1)
2334 gone.add(f1)
2333 else:
2335 else:
2334 copyop = 'copy'
2336 copyop = 'copy'
2335 elif f in removedset:
2337 elif f in removedset:
2336 f2 = None
2338 f2 = None
2337 if opts.git:
2339 if opts.git:
2338 # have we already reported a copy above?
2340 # have we already reported a copy above?
2339 if (f in copyto and copyto[f] in addedset
2341 if (f in copyto and copyto[f] in addedset
2340 and copy[copyto[f]] == f):
2342 and copy[copyto[f]] == f):
2341 continue
2343 continue
2342 yield f1, f2, copyop
2344 yield f1, f2, copyop
2343
2345
2344 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2346 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
2345 copy, getfilectx, opts, losedatafn, prefix, relroot):
2347 copy, getfilectx, opts, losedatafn, prefix, relroot):
2346 '''given input data, generate a diff and yield it in blocks
2348 '''given input data, generate a diff and yield it in blocks
2347
2349
2348 If generating a diff would lose data like flags or binary data and
2350 If generating a diff would lose data like flags or binary data and
2349 losedatafn is not None, it will be called.
2351 losedatafn is not None, it will be called.
2350
2352
2351 relroot is removed and prefix is added to every path in the diff output.
2353 relroot is removed and prefix is added to every path in the diff output.
2352
2354
2353 If relroot is not empty, this function expects every path in modified,
2355 If relroot is not empty, this function expects every path in modified,
2354 added, removed and copy to start with it.'''
2356 added, removed and copy to start with it.'''
2355
2357
2356 def gitindex(text):
2358 def gitindex(text):
2357 if not text:
2359 if not text:
2358 text = ""
2360 text = ""
2359 l = len(text)
2361 l = len(text)
2360 s = util.sha1('blob %d\0' % l)
2362 s = util.sha1('blob %d\0' % l)
2361 s.update(text)
2363 s.update(text)
2362 return s.hexdigest()
2364 return s.hexdigest()
2363
2365
2364 if opts.noprefix:
2366 if opts.noprefix:
2365 aprefix = bprefix = ''
2367 aprefix = bprefix = ''
2366 else:
2368 else:
2367 aprefix = 'a/'
2369 aprefix = 'a/'
2368 bprefix = 'b/'
2370 bprefix = 'b/'
2369
2371
2370 def diffline(f, revs):
2372 def diffline(f, revs):
2371 revinfo = ' '.join(["-r %s" % rev for rev in revs])
2373 revinfo = ' '.join(["-r %s" % rev for rev in revs])
2372 return 'diff %s %s' % (revinfo, f)
2374 return 'diff %s %s' % (revinfo, f)
2373
2375
2374 date1 = util.datestr(ctx1.date())
2376 date1 = util.datestr(ctx1.date())
2375 date2 = util.datestr(ctx2.date())
2377 date2 = util.datestr(ctx2.date())
2376
2378
2377 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
2379 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
2378
2380
2379 if relroot != '' and (repo.ui.configbool('devel', 'all')
2381 if relroot != '' and (repo.ui.configbool('devel', 'all')
2380 or repo.ui.configbool('devel', 'check-relroot')):
2382 or repo.ui.configbool('devel', 'check-relroot')):
2381 for f in modified + added + removed + copy.keys() + copy.values():
2383 for f in modified + added + removed + copy.keys() + copy.values():
2382 if f is not None and not f.startswith(relroot):
2384 if f is not None and not f.startswith(relroot):
2383 raise AssertionError(
2385 raise AssertionError(
2384 "file %s doesn't start with relroot %s" % (f, relroot))
2386 "file %s doesn't start with relroot %s" % (f, relroot))
2385
2387
2386 for f1, f2, copyop in _filepairs(
2388 for f1, f2, copyop in _filepairs(
2387 ctx1, modified, added, removed, copy, opts):
2389 ctx1, modified, added, removed, copy, opts):
2388 content1 = None
2390 content1 = None
2389 content2 = None
2391 content2 = None
2390 flag1 = None
2392 flag1 = None
2391 flag2 = None
2393 flag2 = None
2392 if f1:
2394 if f1:
2393 content1 = getfilectx(f1, ctx1).data()
2395 content1 = getfilectx(f1, ctx1).data()
2394 if opts.git or losedatafn:
2396 if opts.git or losedatafn:
2395 flag1 = ctx1.flags(f1)
2397 flag1 = ctx1.flags(f1)
2396 if f2:
2398 if f2:
2397 content2 = getfilectx(f2, ctx2).data()
2399 content2 = getfilectx(f2, ctx2).data()
2398 if opts.git or losedatafn:
2400 if opts.git or losedatafn:
2399 flag2 = ctx2.flags(f2)
2401 flag2 = ctx2.flags(f2)
2400 binary = False
2402 binary = False
2401 if opts.git or losedatafn:
2403 if opts.git or losedatafn:
2402 binary = util.binary(content1) or util.binary(content2)
2404 binary = util.binary(content1) or util.binary(content2)
2403
2405
2404 if losedatafn and not opts.git:
2406 if losedatafn and not opts.git:
2405 if (binary or
2407 if (binary or
2406 # copy/rename
2408 # copy/rename
2407 f2 in copy or
2409 f2 in copy or
2408 # empty file creation
2410 # empty file creation
2409 (not f1 and not content2) or
2411 (not f1 and not content2) or
2410 # empty file deletion
2412 # empty file deletion
2411 (not content1 and not f2) or
2413 (not content1 and not f2) or
2412 # create with flags
2414 # create with flags
2413 (not f1 and flag2) or
2415 (not f1 and flag2) or
2414 # change flags
2416 # change flags
2415 (f1 and f2 and flag1 != flag2)):
2417 (f1 and f2 and flag1 != flag2)):
2416 losedatafn(f2 or f1)
2418 losedatafn(f2 or f1)
2417
2419
2418 path1 = f1 or f2
2420 path1 = f1 or f2
2419 path2 = f2 or f1
2421 path2 = f2 or f1
2420 path1 = posixpath.join(prefix, path1[len(relroot):])
2422 path1 = posixpath.join(prefix, path1[len(relroot):])
2421 path2 = posixpath.join(prefix, path2[len(relroot):])
2423 path2 = posixpath.join(prefix, path2[len(relroot):])
2422 header = []
2424 header = []
2423 if opts.git:
2425 if opts.git:
2424 header.append('diff --git %s%s %s%s' %
2426 header.append('diff --git %s%s %s%s' %
2425 (aprefix, path1, bprefix, path2))
2427 (aprefix, path1, bprefix, path2))
2426 if not f1: # added
2428 if not f1: # added
2427 header.append('new file mode %s' % gitmode[flag2])
2429 header.append('new file mode %s' % gitmode[flag2])
2428 elif not f2: # removed
2430 elif not f2: # removed
2429 header.append('deleted file mode %s' % gitmode[flag1])
2431 header.append('deleted file mode %s' % gitmode[flag1])
2430 else: # modified/copied/renamed
2432 else: # modified/copied/renamed
2431 mode1, mode2 = gitmode[flag1], gitmode[flag2]
2433 mode1, mode2 = gitmode[flag1], gitmode[flag2]
2432 if mode1 != mode2:
2434 if mode1 != mode2:
2433 header.append('old mode %s' % mode1)
2435 header.append('old mode %s' % mode1)
2434 header.append('new mode %s' % mode2)
2436 header.append('new mode %s' % mode2)
2435 if copyop is not None:
2437 if copyop is not None:
2436 header.append('%s from %s' % (copyop, path1))
2438 header.append('%s from %s' % (copyop, path1))
2437 header.append('%s to %s' % (copyop, path2))
2439 header.append('%s to %s' % (copyop, path2))
2438 elif revs and not repo.ui.quiet:
2440 elif revs and not repo.ui.quiet:
2439 header.append(diffline(path1, revs))
2441 header.append(diffline(path1, revs))
2440
2442
2441 if binary and opts.git and not opts.nobinary:
2443 if binary and opts.git and not opts.nobinary:
2442 text = mdiff.b85diff(content1, content2)
2444 text = mdiff.b85diff(content1, content2)
2443 if text:
2445 if text:
2444 header.append('index %s..%s' %
2446 header.append('index %s..%s' %
2445 (gitindex(content1), gitindex(content2)))
2447 (gitindex(content1), gitindex(content2)))
2446 else:
2448 else:
2447 text = mdiff.unidiff(content1, date1,
2449 text = mdiff.unidiff(content1, date1,
2448 content2, date2,
2450 content2, date2,
2449 path1, path2, opts=opts)
2451 path1, path2, opts=opts)
2450 if header and (text or len(header) > 1):
2452 if header and (text or len(header) > 1):
2451 yield '\n'.join(header) + '\n'
2453 yield '\n'.join(header) + '\n'
2452 if text:
2454 if text:
2453 yield text
2455 yield text
2454
2456
2455 def diffstatsum(stats):
2457 def diffstatsum(stats):
2456 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
2458 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
2457 for f, a, r, b in stats:
2459 for f, a, r, b in stats:
2458 maxfile = max(maxfile, encoding.colwidth(f))
2460 maxfile = max(maxfile, encoding.colwidth(f))
2459 maxtotal = max(maxtotal, a + r)
2461 maxtotal = max(maxtotal, a + r)
2460 addtotal += a
2462 addtotal += a
2461 removetotal += r
2463 removetotal += r
2462 binary = binary or b
2464 binary = binary or b
2463
2465
2464 return maxfile, maxtotal, addtotal, removetotal, binary
2466 return maxfile, maxtotal, addtotal, removetotal, binary
2465
2467
2466 def diffstatdata(lines):
2468 def diffstatdata(lines):
2467 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
2469 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
2468
2470
2469 results = []
2471 results = []
2470 filename, adds, removes, isbinary = None, 0, 0, False
2472 filename, adds, removes, isbinary = None, 0, 0, False
2471
2473
2472 def addresult():
2474 def addresult():
2473 if filename:
2475 if filename:
2474 results.append((filename, adds, removes, isbinary))
2476 results.append((filename, adds, removes, isbinary))
2475
2477
2476 for line in lines:
2478 for line in lines:
2477 if line.startswith('diff'):
2479 if line.startswith('diff'):
2478 addresult()
2480 addresult()
2479 # set numbers to 0 anyway when starting new file
2481 # set numbers to 0 anyway when starting new file
2480 adds, removes, isbinary = 0, 0, False
2482 adds, removes, isbinary = 0, 0, False
2481 if line.startswith('diff --git a/'):
2483 if line.startswith('diff --git a/'):
2482 filename = gitre.search(line).group(2)
2484 filename = gitre.search(line).group(2)
2483 elif line.startswith('diff -r'):
2485 elif line.startswith('diff -r'):
2484 # format: "diff -r ... -r ... filename"
2486 # format: "diff -r ... -r ... filename"
2485 filename = diffre.search(line).group(1)
2487 filename = diffre.search(line).group(1)
2486 elif line.startswith('+') and not line.startswith('+++ '):
2488 elif line.startswith('+') and not line.startswith('+++ '):
2487 adds += 1
2489 adds += 1
2488 elif line.startswith('-') and not line.startswith('--- '):
2490 elif line.startswith('-') and not line.startswith('--- '):
2489 removes += 1
2491 removes += 1
2490 elif (line.startswith('GIT binary patch') or
2492 elif (line.startswith('GIT binary patch') or
2491 line.startswith('Binary file')):
2493 line.startswith('Binary file')):
2492 isbinary = True
2494 isbinary = True
2493 addresult()
2495 addresult()
2494 return results
2496 return results
2495
2497
2496 def diffstat(lines, width=80, git=False):
2498 def diffstat(lines, width=80, git=False):
2497 output = []
2499 output = []
2498 stats = diffstatdata(lines)
2500 stats = diffstatdata(lines)
2499 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
2501 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
2500
2502
2501 countwidth = len(str(maxtotal))
2503 countwidth = len(str(maxtotal))
2502 if hasbinary and countwidth < 3:
2504 if hasbinary and countwidth < 3:
2503 countwidth = 3
2505 countwidth = 3
2504 graphwidth = width - countwidth - maxname - 6
2506 graphwidth = width - countwidth - maxname - 6
2505 if graphwidth < 10:
2507 if graphwidth < 10:
2506 graphwidth = 10
2508 graphwidth = 10
2507
2509
2508 def scale(i):
2510 def scale(i):
2509 if maxtotal <= graphwidth:
2511 if maxtotal <= graphwidth:
2510 return i
2512 return i
2511 # If diffstat runs out of room it doesn't print anything,
2513 # If diffstat runs out of room it doesn't print anything,
2512 # which isn't very useful, so always print at least one + or -
2514 # which isn't very useful, so always print at least one + or -
2513 # if there were at least some changes.
2515 # if there were at least some changes.
2514 return max(i * graphwidth // maxtotal, int(bool(i)))
2516 return max(i * graphwidth // maxtotal, int(bool(i)))
2515
2517
2516 for filename, adds, removes, isbinary in stats:
2518 for filename, adds, removes, isbinary in stats:
2517 if isbinary:
2519 if isbinary:
2518 count = 'Bin'
2520 count = 'Bin'
2519 else:
2521 else:
2520 count = adds + removes
2522 count = adds + removes
2521 pluses = '+' * scale(adds)
2523 pluses = '+' * scale(adds)
2522 minuses = '-' * scale(removes)
2524 minuses = '-' * scale(removes)
2523 output.append(' %s%s | %*s %s%s\n' %
2525 output.append(' %s%s | %*s %s%s\n' %
2524 (filename, ' ' * (maxname - encoding.colwidth(filename)),
2526 (filename, ' ' * (maxname - encoding.colwidth(filename)),
2525 countwidth, count, pluses, minuses))
2527 countwidth, count, pluses, minuses))
2526
2528
2527 if stats:
2529 if stats:
2528 output.append(_(' %d files changed, %d insertions(+), '
2530 output.append(_(' %d files changed, %d insertions(+), '
2529 '%d deletions(-)\n')
2531 '%d deletions(-)\n')
2530 % (len(stats), totaladds, totalremoves))
2532 % (len(stats), totaladds, totalremoves))
2531
2533
2532 return ''.join(output)
2534 return ''.join(output)
2533
2535
2534 def diffstatui(*args, **kw):
2536 def diffstatui(*args, **kw):
2535 '''like diffstat(), but yields 2-tuples of (output, label) for
2537 '''like diffstat(), but yields 2-tuples of (output, label) for
2536 ui.write()
2538 ui.write()
2537 '''
2539 '''
2538
2540
2539 for line in diffstat(*args, **kw).splitlines():
2541 for line in diffstat(*args, **kw).splitlines():
2540 if line and line[-1] in '+-':
2542 if line and line[-1] in '+-':
2541 name, graph = line.rsplit(' ', 1)
2543 name, graph = line.rsplit(' ', 1)
2542 yield (name + ' ', '')
2544 yield (name + ' ', '')
2543 m = re.search(r'\++', graph)
2545 m = re.search(r'\++', graph)
2544 if m:
2546 if m:
2545 yield (m.group(0), 'diffstat.inserted')
2547 yield (m.group(0), 'diffstat.inserted')
2546 m = re.search(r'-+', graph)
2548 m = re.search(r'-+', graph)
2547 if m:
2549 if m:
2548 yield (m.group(0), 'diffstat.deleted')
2550 yield (m.group(0), 'diffstat.deleted')
2549 else:
2551 else:
2550 yield (line, '')
2552 yield (line, '')
2551 yield ('\n', '')
2553 yield ('\n', '')
@@ -1,1501 +1,1528 b''
1 Set up a repo
1 Set up a repo
2
2
3 $ cat <<EOF >> $HGRCPATH
3 $ cat <<EOF >> $HGRCPATH
4 > [ui]
4 > [ui]
5 > interactive = true
5 > interactive = true
6 > [extensions]
6 > [extensions]
7 > record =
7 > record =
8 > EOF
8 > EOF
9
9
10 $ hg init a
10 $ hg init a
11 $ cd a
11 $ cd a
12
12
13 Select no files
13 Select no files
14
14
15 $ touch empty-rw
15 $ touch empty-rw
16 $ hg add empty-rw
16 $ hg add empty-rw
17
17
18 $ hg commit -i empty-rw<<EOF
18 $ hg commit -i empty-rw<<EOF
19 > n
19 > n
20 > EOF
20 > EOF
21 diff --git a/empty-rw b/empty-rw
21 diff --git a/empty-rw b/empty-rw
22 new file mode 100644
22 new file mode 100644
23 examine changes to 'empty-rw'? [Ynesfdaq?] n
23 examine changes to 'empty-rw'? [Ynesfdaq?] n
24
24
25 no changes to record
25 no changes to record
26
26
27 $ hg tip -p
27 $ hg tip -p
28 changeset: -1:000000000000
28 changeset: -1:000000000000
29 tag: tip
29 tag: tip
30 user:
30 user:
31 date: Thu Jan 01 00:00:00 1970 +0000
31 date: Thu Jan 01 00:00:00 1970 +0000
32
32
33
33
34
34
35 Select files but no hunks
35 Select files but no hunks
36
36
37 $ hg commit -i empty-rw<<EOF
37 $ hg commit -i empty-rw<<EOF
38 > y
38 > y
39 > n
39 > n
40 > EOF
40 > EOF
41 diff --git a/empty-rw b/empty-rw
41 diff --git a/empty-rw b/empty-rw
42 new file mode 100644
42 new file mode 100644
43 examine changes to 'empty-rw'? [Ynesfdaq?] y
43 examine changes to 'empty-rw'? [Ynesfdaq?] y
44
44
45 abort: empty commit message
45 abort: empty commit message
46 [255]
46 [255]
47
47
48 $ hg tip -p
48 $ hg tip -p
49 changeset: -1:000000000000
49 changeset: -1:000000000000
50 tag: tip
50 tag: tip
51 user:
51 user:
52 date: Thu Jan 01 00:00:00 1970 +0000
52 date: Thu Jan 01 00:00:00 1970 +0000
53
53
54
54
55
55
56 Record empty file
56 Record empty file
57
57
58 $ hg commit -i -d '0 0' -m empty empty-rw<<EOF
58 $ hg commit -i -d '0 0' -m empty empty-rw<<EOF
59 > y
59 > y
60 > y
60 > y
61 > EOF
61 > EOF
62 diff --git a/empty-rw b/empty-rw
62 diff --git a/empty-rw b/empty-rw
63 new file mode 100644
63 new file mode 100644
64 examine changes to 'empty-rw'? [Ynesfdaq?] y
64 examine changes to 'empty-rw'? [Ynesfdaq?] y
65
65
66
66
67 $ hg tip -p
67 $ hg tip -p
68 changeset: 0:c0708cf4e46e
68 changeset: 0:c0708cf4e46e
69 tag: tip
69 tag: tip
70 user: test
70 user: test
71 date: Thu Jan 01 00:00:00 1970 +0000
71 date: Thu Jan 01 00:00:00 1970 +0000
72 summary: empty
72 summary: empty
73
73
74
74
75
75
76 Summary shows we updated to the new cset
76 Summary shows we updated to the new cset
77
77
78 $ hg summary
78 $ hg summary
79 parent: 0:c0708cf4e46e tip
79 parent: 0:c0708cf4e46e tip
80 empty
80 empty
81 branch: default
81 branch: default
82 commit: (clean)
82 commit: (clean)
83 update: (current)
83 update: (current)
84 phases: 1 draft
84 phases: 1 draft
85
85
86 Rename empty file
86 Rename empty file
87
87
88 $ hg mv empty-rw empty-rename
88 $ hg mv empty-rw empty-rename
89 $ hg commit -i -d '1 0' -m rename<<EOF
89 $ hg commit -i -d '1 0' -m rename<<EOF
90 > y
90 > y
91 > EOF
91 > EOF
92 diff --git a/empty-rw b/empty-rename
92 diff --git a/empty-rw b/empty-rename
93 rename from empty-rw
93 rename from empty-rw
94 rename to empty-rename
94 rename to empty-rename
95 examine changes to 'empty-rw' and 'empty-rename'? [Ynesfdaq?] y
95 examine changes to 'empty-rw' and 'empty-rename'? [Ynesfdaq?] y
96
96
97
97
98 $ hg tip -p
98 $ hg tip -p
99 changeset: 1:d695e8dcb197
99 changeset: 1:d695e8dcb197
100 tag: tip
100 tag: tip
101 user: test
101 user: test
102 date: Thu Jan 01 00:00:01 1970 +0000
102 date: Thu Jan 01 00:00:01 1970 +0000
103 summary: rename
103 summary: rename
104
104
105
105
106
106
107 Copy empty file
107 Copy empty file
108
108
109 $ hg cp empty-rename empty-copy
109 $ hg cp empty-rename empty-copy
110 $ hg commit -i -d '2 0' -m copy<<EOF
110 $ hg commit -i -d '2 0' -m copy<<EOF
111 > y
111 > y
112 > EOF
112 > EOF
113 diff --git a/empty-rename b/empty-copy
113 diff --git a/empty-rename b/empty-copy
114 copy from empty-rename
114 copy from empty-rename
115 copy to empty-copy
115 copy to empty-copy
116 examine changes to 'empty-rename' and 'empty-copy'? [Ynesfdaq?] y
116 examine changes to 'empty-rename' and 'empty-copy'? [Ynesfdaq?] y
117
117
118
118
119 $ hg tip -p
119 $ hg tip -p
120 changeset: 2:1d4b90bea524
120 changeset: 2:1d4b90bea524
121 tag: tip
121 tag: tip
122 user: test
122 user: test
123 date: Thu Jan 01 00:00:02 1970 +0000
123 date: Thu Jan 01 00:00:02 1970 +0000
124 summary: copy
124 summary: copy
125
125
126
126
127
127
128 Delete empty file
128 Delete empty file
129
129
130 $ hg rm empty-copy
130 $ hg rm empty-copy
131 $ hg commit -i -d '3 0' -m delete<<EOF
131 $ hg commit -i -d '3 0' -m delete<<EOF
132 > y
132 > y
133 > EOF
133 > EOF
134 diff --git a/empty-copy b/empty-copy
134 diff --git a/empty-copy b/empty-copy
135 deleted file mode 100644
135 deleted file mode 100644
136 examine changes to 'empty-copy'? [Ynesfdaq?] y
136 examine changes to 'empty-copy'? [Ynesfdaq?] y
137
137
138
138
139 $ hg tip -p
139 $ hg tip -p
140 changeset: 3:b39a238f01a1
140 changeset: 3:b39a238f01a1
141 tag: tip
141 tag: tip
142 user: test
142 user: test
143 date: Thu Jan 01 00:00:03 1970 +0000
143 date: Thu Jan 01 00:00:03 1970 +0000
144 summary: delete
144 summary: delete
145
145
146
146
147
147
148 Add binary file
148 Add binary file
149
149
150 $ hg bundle --base -2 tip.bundle
150 $ hg bundle --base -2 tip.bundle
151 1 changesets found
151 1 changesets found
152 $ hg add tip.bundle
152 $ hg add tip.bundle
153 $ hg commit -i -d '4 0' -m binary<<EOF
153 $ hg commit -i -d '4 0' -m binary<<EOF
154 > y
154 > y
155 > EOF
155 > EOF
156 diff --git a/tip.bundle b/tip.bundle
156 diff --git a/tip.bundle b/tip.bundle
157 new file mode 100644
157 new file mode 100644
158 this is a binary file
158 this is a binary file
159 examine changes to 'tip.bundle'? [Ynesfdaq?] y
159 examine changes to 'tip.bundle'? [Ynesfdaq?] y
160
160
161
161
162 $ hg tip -p
162 $ hg tip -p
163 changeset: 4:ad816da3711e
163 changeset: 4:ad816da3711e
164 tag: tip
164 tag: tip
165 user: test
165 user: test
166 date: Thu Jan 01 00:00:04 1970 +0000
166 date: Thu Jan 01 00:00:04 1970 +0000
167 summary: binary
167 summary: binary
168
168
169 diff -r b39a238f01a1 -r ad816da3711e tip.bundle
169 diff -r b39a238f01a1 -r ad816da3711e tip.bundle
170 Binary file tip.bundle has changed
170 Binary file tip.bundle has changed
171
171
172
172
173 Change binary file
173 Change binary file
174
174
175 $ hg bundle --base -2 tip.bundle
175 $ hg bundle --base -2 tip.bundle
176 1 changesets found
176 1 changesets found
177 $ hg commit -i -d '5 0' -m binary-change<<EOF
177 $ hg commit -i -d '5 0' -m binary-change<<EOF
178 > y
178 > y
179 > EOF
179 > EOF
180 diff --git a/tip.bundle b/tip.bundle
180 diff --git a/tip.bundle b/tip.bundle
181 this modifies a binary file (all or nothing)
181 this modifies a binary file (all or nothing)
182 examine changes to 'tip.bundle'? [Ynesfdaq?] y
182 examine changes to 'tip.bundle'? [Ynesfdaq?] y
183
183
184
184
185 $ hg tip -p
185 $ hg tip -p
186 changeset: 5:dccd6f3eb485
186 changeset: 5:dccd6f3eb485
187 tag: tip
187 tag: tip
188 user: test
188 user: test
189 date: Thu Jan 01 00:00:05 1970 +0000
189 date: Thu Jan 01 00:00:05 1970 +0000
190 summary: binary-change
190 summary: binary-change
191
191
192 diff -r ad816da3711e -r dccd6f3eb485 tip.bundle
192 diff -r ad816da3711e -r dccd6f3eb485 tip.bundle
193 Binary file tip.bundle has changed
193 Binary file tip.bundle has changed
194
194
195
195
196 Rename and change binary file
196 Rename and change binary file
197
197
198 $ hg mv tip.bundle top.bundle
198 $ hg mv tip.bundle top.bundle
199 $ hg bundle --base -2 top.bundle
199 $ hg bundle --base -2 top.bundle
200 1 changesets found
200 1 changesets found
201 $ hg commit -i -d '6 0' -m binary-change-rename<<EOF
201 $ hg commit -i -d '6 0' -m binary-change-rename<<EOF
202 > y
202 > y
203 > EOF
203 > EOF
204 diff --git a/tip.bundle b/top.bundle
204 diff --git a/tip.bundle b/top.bundle
205 rename from tip.bundle
205 rename from tip.bundle
206 rename to top.bundle
206 rename to top.bundle
207 this modifies a binary file (all or nothing)
207 this modifies a binary file (all or nothing)
208 examine changes to 'tip.bundle' and 'top.bundle'? [Ynesfdaq?] y
208 examine changes to 'tip.bundle' and 'top.bundle'? [Ynesfdaq?] y
209
209
210
210
211 $ hg tip -p
211 $ hg tip -p
212 changeset: 6:7fa44105f5b3
212 changeset: 6:7fa44105f5b3
213 tag: tip
213 tag: tip
214 user: test
214 user: test
215 date: Thu Jan 01 00:00:06 1970 +0000
215 date: Thu Jan 01 00:00:06 1970 +0000
216 summary: binary-change-rename
216 summary: binary-change-rename
217
217
218 diff -r dccd6f3eb485 -r 7fa44105f5b3 tip.bundle
218 diff -r dccd6f3eb485 -r 7fa44105f5b3 tip.bundle
219 Binary file tip.bundle has changed
219 Binary file tip.bundle has changed
220 diff -r dccd6f3eb485 -r 7fa44105f5b3 top.bundle
220 diff -r dccd6f3eb485 -r 7fa44105f5b3 top.bundle
221 Binary file top.bundle has changed
221 Binary file top.bundle has changed
222
222
223
223
224 Add plain file
224 Add plain file
225
225
226 $ for i in 1 2 3 4 5 6 7 8 9 10; do
226 $ for i in 1 2 3 4 5 6 7 8 9 10; do
227 > echo $i >> plain
227 > echo $i >> plain
228 > done
228 > done
229
229
230 $ hg add plain
230 $ hg add plain
231 $ hg commit -i -d '7 0' -m plain plain<<EOF
231 $ hg commit -i -d '7 0' -m plain plain<<EOF
232 > y
232 > y
233 > y
233 > y
234 > EOF
234 > EOF
235 diff --git a/plain b/plain
235 diff --git a/plain b/plain
236 new file mode 100644
236 new file mode 100644
237 examine changes to 'plain'? [Ynesfdaq?] y
237 examine changes to 'plain'? [Ynesfdaq?] y
238
238
239 @@ -0,0 +1,10 @@
239 @@ -0,0 +1,10 @@
240 +1
240 +1
241 +2
241 +2
242 +3
242 +3
243 +4
243 +4
244 +5
244 +5
245 +6
245 +6
246 +7
246 +7
247 +8
247 +8
248 +9
248 +9
249 +10
249 +10
250 record this change to 'plain'? [Ynesfdaq?] y
250 record this change to 'plain'? [Ynesfdaq?] y
251
251
252 $ hg tip -p
252 $ hg tip -p
253 changeset: 7:11fb457c1be4
253 changeset: 7:11fb457c1be4
254 tag: tip
254 tag: tip
255 user: test
255 user: test
256 date: Thu Jan 01 00:00:07 1970 +0000
256 date: Thu Jan 01 00:00:07 1970 +0000
257 summary: plain
257 summary: plain
258
258
259 diff -r 7fa44105f5b3 -r 11fb457c1be4 plain
259 diff -r 7fa44105f5b3 -r 11fb457c1be4 plain
260 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
260 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
261 +++ b/plain Thu Jan 01 00:00:07 1970 +0000
261 +++ b/plain Thu Jan 01 00:00:07 1970 +0000
262 @@ -0,0 +1,10 @@
262 @@ -0,0 +1,10 @@
263 +1
263 +1
264 +2
264 +2
265 +3
265 +3
266 +4
266 +4
267 +5
267 +5
268 +6
268 +6
269 +7
269 +7
270 +8
270 +8
271 +9
271 +9
272 +10
272 +10
273
273
274 Modify end of plain file with username unset
274 Modify end of plain file with username unset
275
275
276 $ echo 11 >> plain
276 $ echo 11 >> plain
277 $ unset HGUSER
277 $ unset HGUSER
278 $ hg commit -i --config ui.username= -d '8 0' -m end plain
278 $ hg commit -i --config ui.username= -d '8 0' -m end plain
279 abort: no username supplied
279 abort: no username supplied
280 (use "hg config --edit" to set your username)
280 (use "hg config --edit" to set your username)
281 [255]
281 [255]
282
282
283
283
284 Modify end of plain file, also test that diffopts are accounted for
284 Modify end of plain file, also test that diffopts are accounted for
285
285
286 $ HGUSER="test"
286 $ HGUSER="test"
287 $ export HGUSER
287 $ export HGUSER
288 $ hg commit -i --config diff.showfunc=true -d '8 0' -m end plain <<EOF
288 $ hg commit -i --config diff.showfunc=true -d '8 0' -m end plain <<EOF
289 > y
289 > y
290 > y
290 > y
291 > EOF
291 > EOF
292 diff --git a/plain b/plain
292 diff --git a/plain b/plain
293 1 hunks, 1 lines changed
293 1 hunks, 1 lines changed
294 examine changes to 'plain'? [Ynesfdaq?] y
294 examine changes to 'plain'? [Ynesfdaq?] y
295
295
296 @@ -8,3 +8,4 @@ 7
296 @@ -8,3 +8,4 @@ 7
297 8
297 8
298 9
298 9
299 10
299 10
300 +11
300 +11
301 record this change to 'plain'? [Ynesfdaq?] y
301 record this change to 'plain'? [Ynesfdaq?] y
302
302
303
303
304 Modify end of plain file, no EOL
304 Modify end of plain file, no EOL
305
305
306 $ hg tip --template '{node}' >> plain
306 $ hg tip --template '{node}' >> plain
307 $ hg commit -i -d '9 0' -m noeol plain <<EOF
307 $ hg commit -i -d '9 0' -m noeol plain <<EOF
308 > y
308 > y
309 > y
309 > y
310 > EOF
310 > EOF
311 diff --git a/plain b/plain
311 diff --git a/plain b/plain
312 1 hunks, 1 lines changed
312 1 hunks, 1 lines changed
313 examine changes to 'plain'? [Ynesfdaq?] y
313 examine changes to 'plain'? [Ynesfdaq?] y
314
314
315 @@ -9,3 +9,4 @@
315 @@ -9,3 +9,4 @@
316 9
316 9
317 10
317 10
318 11
318 11
319 +7264f99c5f5ff3261504828afa4fb4d406c3af54
319 +7264f99c5f5ff3261504828afa4fb4d406c3af54
320 \ No newline at end of file
320 \ No newline at end of file
321 record this change to 'plain'? [Ynesfdaq?] y
321 record this change to 'plain'? [Ynesfdaq?] y
322
322
323
323
324 Modify end of plain file, add EOL
324 Modify end of plain file, add EOL
325
325
326 $ echo >> plain
326 $ echo >> plain
327 $ echo 1 > plain2
327 $ echo 1 > plain2
328 $ hg add plain2
328 $ hg add plain2
329 $ hg commit -i -d '10 0' -m eol plain plain2 <<EOF
329 $ hg commit -i -d '10 0' -m eol plain plain2 <<EOF
330 > y
330 > y
331 > y
331 > y
332 > y
332 > y
333 > y
333 > y
334 > EOF
334 > EOF
335 diff --git a/plain b/plain
335 diff --git a/plain b/plain
336 1 hunks, 1 lines changed
336 1 hunks, 1 lines changed
337 examine changes to 'plain'? [Ynesfdaq?] y
337 examine changes to 'plain'? [Ynesfdaq?] y
338
338
339 @@ -9,4 +9,4 @@
339 @@ -9,4 +9,4 @@
340 9
340 9
341 10
341 10
342 11
342 11
343 -7264f99c5f5ff3261504828afa4fb4d406c3af54
343 -7264f99c5f5ff3261504828afa4fb4d406c3af54
344 \ No newline at end of file
344 \ No newline at end of file
345 +7264f99c5f5ff3261504828afa4fb4d406c3af54
345 +7264f99c5f5ff3261504828afa4fb4d406c3af54
346 record change 1/2 to 'plain'? [Ynesfdaq?] y
346 record change 1/2 to 'plain'? [Ynesfdaq?] y
347
347
348 diff --git a/plain2 b/plain2
348 diff --git a/plain2 b/plain2
349 new file mode 100644
349 new file mode 100644
350 examine changes to 'plain2'? [Ynesfdaq?] y
350 examine changes to 'plain2'? [Ynesfdaq?] y
351
351
352 @@ -0,0 +1,1 @@
352 @@ -0,0 +1,1 @@
353 +1
353 +1
354 record change 2/2 to 'plain2'? [Ynesfdaq?] y
354 record change 2/2 to 'plain2'? [Ynesfdaq?] y
355
355
356 Modify beginning, trim end, record both, add another file to test
356 Modify beginning, trim end, record both, add another file to test
357 changes numbering
357 changes numbering
358
358
359 $ rm plain
359 $ rm plain
360 $ for i in 2 2 3 4 5 6 7 8 9 10; do
360 $ for i in 2 2 3 4 5 6 7 8 9 10; do
361 > echo $i >> plain
361 > echo $i >> plain
362 > done
362 > done
363 $ echo 2 >> plain2
363 $ echo 2 >> plain2
364
364
365 $ hg commit -i -d '10 0' -m begin-and-end plain plain2 <<EOF
365 $ hg commit -i -d '10 0' -m begin-and-end plain plain2 <<EOF
366 > y
366 > y
367 > y
367 > y
368 > y
368 > y
369 > y
369 > y
370 > y
370 > y
371 > EOF
371 > EOF
372 diff --git a/plain b/plain
372 diff --git a/plain b/plain
373 2 hunks, 3 lines changed
373 2 hunks, 3 lines changed
374 examine changes to 'plain'? [Ynesfdaq?] y
374 examine changes to 'plain'? [Ynesfdaq?] y
375
375
376 @@ -1,4 +1,4 @@
376 @@ -1,4 +1,4 @@
377 -1
377 -1
378 +2
378 +2
379 2
379 2
380 3
380 3
381 4
381 4
382 record change 1/3 to 'plain'? [Ynesfdaq?] y
382 record change 1/3 to 'plain'? [Ynesfdaq?] y
383
383
384 @@ -8,5 +8,3 @@
384 @@ -8,5 +8,3 @@
385 8
385 8
386 9
386 9
387 10
387 10
388 -11
388 -11
389 -7264f99c5f5ff3261504828afa4fb4d406c3af54
389 -7264f99c5f5ff3261504828afa4fb4d406c3af54
390 record change 2/3 to 'plain'? [Ynesfdaq?] y
390 record change 2/3 to 'plain'? [Ynesfdaq?] y
391
391
392 diff --git a/plain2 b/plain2
392 diff --git a/plain2 b/plain2
393 1 hunks, 1 lines changed
393 1 hunks, 1 lines changed
394 examine changes to 'plain2'? [Ynesfdaq?] y
394 examine changes to 'plain2'? [Ynesfdaq?] y
395
395
396 @@ -1,1 +1,2 @@
396 @@ -1,1 +1,2 @@
397 1
397 1
398 +2
398 +2
399 record change 3/3 to 'plain2'? [Ynesfdaq?] y
399 record change 3/3 to 'plain2'? [Ynesfdaq?] y
400
400
401
401
402 $ hg tip -p
402 $ hg tip -p
403 changeset: 11:21df83db12b8
403 changeset: 11:21df83db12b8
404 tag: tip
404 tag: tip
405 user: test
405 user: test
406 date: Thu Jan 01 00:00:10 1970 +0000
406 date: Thu Jan 01 00:00:10 1970 +0000
407 summary: begin-and-end
407 summary: begin-and-end
408
408
409 diff -r ddb8b281c3ff -r 21df83db12b8 plain
409 diff -r ddb8b281c3ff -r 21df83db12b8 plain
410 --- a/plain Thu Jan 01 00:00:10 1970 +0000
410 --- a/plain Thu Jan 01 00:00:10 1970 +0000
411 +++ b/plain Thu Jan 01 00:00:10 1970 +0000
411 +++ b/plain Thu Jan 01 00:00:10 1970 +0000
412 @@ -1,4 +1,4 @@
412 @@ -1,4 +1,4 @@
413 -1
413 -1
414 +2
414 +2
415 2
415 2
416 3
416 3
417 4
417 4
418 @@ -8,5 +8,3 @@
418 @@ -8,5 +8,3 @@
419 8
419 8
420 9
420 9
421 10
421 10
422 -11
422 -11
423 -7264f99c5f5ff3261504828afa4fb4d406c3af54
423 -7264f99c5f5ff3261504828afa4fb4d406c3af54
424 diff -r ddb8b281c3ff -r 21df83db12b8 plain2
424 diff -r ddb8b281c3ff -r 21df83db12b8 plain2
425 --- a/plain2 Thu Jan 01 00:00:10 1970 +0000
425 --- a/plain2 Thu Jan 01 00:00:10 1970 +0000
426 +++ b/plain2 Thu Jan 01 00:00:10 1970 +0000
426 +++ b/plain2 Thu Jan 01 00:00:10 1970 +0000
427 @@ -1,1 +1,2 @@
427 @@ -1,1 +1,2 @@
428 1
428 1
429 +2
429 +2
430
430
431
431
432 Trim beginning, modify end
432 Trim beginning, modify end
433
433
434 $ rm plain
434 $ rm plain
435 > for i in 4 5 6 7 8 9 10.new; do
435 > for i in 4 5 6 7 8 9 10.new; do
436 > echo $i >> plain
436 > echo $i >> plain
437 > done
437 > done
438
438
439 Record end
439 Record end
440
440
441 $ hg commit -i -d '11 0' -m end-only plain <<EOF
441 $ hg commit -i -d '11 0' -m end-only plain <<EOF
442 > y
442 > y
443 > n
443 > n
444 > y
444 > y
445 > EOF
445 > EOF
446 diff --git a/plain b/plain
446 diff --git a/plain b/plain
447 2 hunks, 4 lines changed
447 2 hunks, 4 lines changed
448 examine changes to 'plain'? [Ynesfdaq?] y
448 examine changes to 'plain'? [Ynesfdaq?] y
449
449
450 @@ -1,9 +1,6 @@
450 @@ -1,9 +1,6 @@
451 -2
451 -2
452 -2
452 -2
453 -3
453 -3
454 4
454 4
455 5
455 5
456 6
456 6
457 7
457 7
458 8
458 8
459 9
459 9
460 record change 1/2 to 'plain'? [Ynesfdaq?] n
460 record change 1/2 to 'plain'? [Ynesfdaq?] n
461
461
462 @@ -4,7 +1,7 @@
462 @@ -4,7 +1,7 @@
463 4
463 4
464 5
464 5
465 6
465 6
466 7
466 7
467 8
467 8
468 9
468 9
469 -10
469 -10
470 +10.new
470 +10.new
471 record change 2/2 to 'plain'? [Ynesfdaq?] y
471 record change 2/2 to 'plain'? [Ynesfdaq?] y
472
472
473
473
474 $ hg tip -p
474 $ hg tip -p
475 changeset: 12:99337501826f
475 changeset: 12:99337501826f
476 tag: tip
476 tag: tip
477 user: test
477 user: test
478 date: Thu Jan 01 00:00:11 1970 +0000
478 date: Thu Jan 01 00:00:11 1970 +0000
479 summary: end-only
479 summary: end-only
480
480
481 diff -r 21df83db12b8 -r 99337501826f plain
481 diff -r 21df83db12b8 -r 99337501826f plain
482 --- a/plain Thu Jan 01 00:00:10 1970 +0000
482 --- a/plain Thu Jan 01 00:00:10 1970 +0000
483 +++ b/plain Thu Jan 01 00:00:11 1970 +0000
483 +++ b/plain Thu Jan 01 00:00:11 1970 +0000
484 @@ -7,4 +7,4 @@
484 @@ -7,4 +7,4 @@
485 7
485 7
486 8
486 8
487 9
487 9
488 -10
488 -10
489 +10.new
489 +10.new
490
490
491
491
492 Record beginning
492 Record beginning
493
493
494 $ hg commit -i -d '12 0' -m begin-only plain <<EOF
494 $ hg commit -i -d '12 0' -m begin-only plain <<EOF
495 > y
495 > y
496 > y
496 > y
497 > EOF
497 > EOF
498 diff --git a/plain b/plain
498 diff --git a/plain b/plain
499 1 hunks, 3 lines changed
499 1 hunks, 3 lines changed
500 examine changes to 'plain'? [Ynesfdaq?] y
500 examine changes to 'plain'? [Ynesfdaq?] y
501
501
502 @@ -1,6 +1,3 @@
502 @@ -1,6 +1,3 @@
503 -2
503 -2
504 -2
504 -2
505 -3
505 -3
506 4
506 4
507 5
507 5
508 6
508 6
509 record this change to 'plain'? [Ynesfdaq?] y
509 record this change to 'plain'? [Ynesfdaq?] y
510
510
511
511
512 $ hg tip -p
512 $ hg tip -p
513 changeset: 13:bbd45465d540
513 changeset: 13:bbd45465d540
514 tag: tip
514 tag: tip
515 user: test
515 user: test
516 date: Thu Jan 01 00:00:12 1970 +0000
516 date: Thu Jan 01 00:00:12 1970 +0000
517 summary: begin-only
517 summary: begin-only
518
518
519 diff -r 99337501826f -r bbd45465d540 plain
519 diff -r 99337501826f -r bbd45465d540 plain
520 --- a/plain Thu Jan 01 00:00:11 1970 +0000
520 --- a/plain Thu Jan 01 00:00:11 1970 +0000
521 +++ b/plain Thu Jan 01 00:00:12 1970 +0000
521 +++ b/plain Thu Jan 01 00:00:12 1970 +0000
522 @@ -1,6 +1,3 @@
522 @@ -1,6 +1,3 @@
523 -2
523 -2
524 -2
524 -2
525 -3
525 -3
526 4
526 4
527 5
527 5
528 6
528 6
529
529
530
530
531 Add to beginning, trim from end
531 Add to beginning, trim from end
532
532
533 $ rm plain
533 $ rm plain
534 $ for i in 1 2 3 4 5 6 7 8 9; do
534 $ for i in 1 2 3 4 5 6 7 8 9; do
535 > echo $i >> plain
535 > echo $i >> plain
536 > done
536 > done
537
537
538 Record end
538 Record end
539
539
540 $ hg commit -i --traceback -d '13 0' -m end-again plain<<EOF
540 $ hg commit -i --traceback -d '13 0' -m end-again plain<<EOF
541 > y
541 > y
542 > n
542 > n
543 > y
543 > y
544 > EOF
544 > EOF
545 diff --git a/plain b/plain
545 diff --git a/plain b/plain
546 2 hunks, 4 lines changed
546 2 hunks, 4 lines changed
547 examine changes to 'plain'? [Ynesfdaq?] y
547 examine changes to 'plain'? [Ynesfdaq?] y
548
548
549 @@ -1,6 +1,9 @@
549 @@ -1,6 +1,9 @@
550 +1
550 +1
551 +2
551 +2
552 +3
552 +3
553 4
553 4
554 5
554 5
555 6
555 6
556 7
556 7
557 8
557 8
558 9
558 9
559 record change 1/2 to 'plain'? [Ynesfdaq?] n
559 record change 1/2 to 'plain'? [Ynesfdaq?] n
560
560
561 @@ -1,7 +4,6 @@
561 @@ -1,7 +4,6 @@
562 4
562 4
563 5
563 5
564 6
564 6
565 7
565 7
566 8
566 8
567 9
567 9
568 -10.new
568 -10.new
569 record change 2/2 to 'plain'? [Ynesfdaq?] y
569 record change 2/2 to 'plain'? [Ynesfdaq?] y
570
570
571
571
572 Add to beginning, middle, end
572 Add to beginning, middle, end
573
573
574 $ rm plain
574 $ rm plain
575 $ for i in 1 2 3 4 5 5.new 5.reallynew 6 7 8 9 10 11; do
575 $ for i in 1 2 3 4 5 5.new 5.reallynew 6 7 8 9 10 11; do
576 > echo $i >> plain
576 > echo $i >> plain
577 > done
577 > done
578
578
579 Record beginning, middle, and test that format-breaking diffopts are ignored
579 Record beginning, middle, and test that format-breaking diffopts are ignored
580
580
581 $ hg commit -i --config diff.noprefix=True -d '14 0' -m middle-only plain <<EOF
581 $ hg commit -i --config diff.noprefix=True -d '14 0' -m middle-only plain <<EOF
582 > y
582 > y
583 > y
583 > y
584 > y
584 > y
585 > n
585 > n
586 > EOF
586 > EOF
587 diff --git a/plain b/plain
587 diff --git a/plain b/plain
588 3 hunks, 7 lines changed
588 3 hunks, 7 lines changed
589 examine changes to 'plain'? [Ynesfdaq?] y
589 examine changes to 'plain'? [Ynesfdaq?] y
590
590
591 @@ -1,2 +1,5 @@
591 @@ -1,2 +1,5 @@
592 +1
592 +1
593 +2
593 +2
594 +3
594 +3
595 4
595 4
596 5
596 5
597 record change 1/3 to 'plain'? [Ynesfdaq?] y
597 record change 1/3 to 'plain'? [Ynesfdaq?] y
598
598
599 @@ -1,6 +4,8 @@
599 @@ -1,6 +4,8 @@
600 4
600 4
601 5
601 5
602 +5.new
602 +5.new
603 +5.reallynew
603 +5.reallynew
604 6
604 6
605 7
605 7
606 8
606 8
607 9
607 9
608 record change 2/3 to 'plain'? [Ynesfdaq?] y
608 record change 2/3 to 'plain'? [Ynesfdaq?] y
609
609
610 @@ -3,4 +8,6 @@
610 @@ -3,4 +8,6 @@
611 6
611 6
612 7
612 7
613 8
613 8
614 9
614 9
615 +10
615 +10
616 +11
616 +11
617 record change 3/3 to 'plain'? [Ynesfdaq?] n
617 record change 3/3 to 'plain'? [Ynesfdaq?] n
618
618
619
619
620 $ hg tip -p
620 $ hg tip -p
621 changeset: 15:f34a7937ec33
621 changeset: 15:f34a7937ec33
622 tag: tip
622 tag: tip
623 user: test
623 user: test
624 date: Thu Jan 01 00:00:14 1970 +0000
624 date: Thu Jan 01 00:00:14 1970 +0000
625 summary: middle-only
625 summary: middle-only
626
626
627 diff -r 82c065d0b850 -r f34a7937ec33 plain
627 diff -r 82c065d0b850 -r f34a7937ec33 plain
628 --- a/plain Thu Jan 01 00:00:13 1970 +0000
628 --- a/plain Thu Jan 01 00:00:13 1970 +0000
629 +++ b/plain Thu Jan 01 00:00:14 1970 +0000
629 +++ b/plain Thu Jan 01 00:00:14 1970 +0000
630 @@ -1,5 +1,10 @@
630 @@ -1,5 +1,10 @@
631 +1
631 +1
632 +2
632 +2
633 +3
633 +3
634 4
634 4
635 5
635 5
636 +5.new
636 +5.new
637 +5.reallynew
637 +5.reallynew
638 6
638 6
639 7
639 7
640 8
640 8
641
641
642
642
643 Record end
643 Record end
644
644
645 $ hg commit -i -d '15 0' -m end-only plain <<EOF
645 $ hg commit -i -d '15 0' -m end-only plain <<EOF
646 > y
646 > y
647 > y
647 > y
648 > EOF
648 > EOF
649 diff --git a/plain b/plain
649 diff --git a/plain b/plain
650 1 hunks, 2 lines changed
650 1 hunks, 2 lines changed
651 examine changes to 'plain'? [Ynesfdaq?] y
651 examine changes to 'plain'? [Ynesfdaq?] y
652
652
653 @@ -9,3 +9,5 @@
653 @@ -9,3 +9,5 @@
654 7
654 7
655 8
655 8
656 9
656 9
657 +10
657 +10
658 +11
658 +11
659 record this change to 'plain'? [Ynesfdaq?] y
659 record this change to 'plain'? [Ynesfdaq?] y
660
660
661
661
662 $ hg tip -p
662 $ hg tip -p
663 changeset: 16:f9900b71a04c
663 changeset: 16:f9900b71a04c
664 tag: tip
664 tag: tip
665 user: test
665 user: test
666 date: Thu Jan 01 00:00:15 1970 +0000
666 date: Thu Jan 01 00:00:15 1970 +0000
667 summary: end-only
667 summary: end-only
668
668
669 diff -r f34a7937ec33 -r f9900b71a04c plain
669 diff -r f34a7937ec33 -r f9900b71a04c plain
670 --- a/plain Thu Jan 01 00:00:14 1970 +0000
670 --- a/plain Thu Jan 01 00:00:14 1970 +0000
671 +++ b/plain Thu Jan 01 00:00:15 1970 +0000
671 +++ b/plain Thu Jan 01 00:00:15 1970 +0000
672 @@ -9,3 +9,5 @@
672 @@ -9,3 +9,5 @@
673 7
673 7
674 8
674 8
675 9
675 9
676 +10
676 +10
677 +11
677 +11
678
678
679
679
680 $ mkdir subdir
680 $ mkdir subdir
681 $ cd subdir
681 $ cd subdir
682 $ echo a > a
682 $ echo a > a
683 $ hg ci -d '16 0' -Amsubdir
683 $ hg ci -d '16 0' -Amsubdir
684 adding subdir/a
684 adding subdir/a
685
685
686 $ echo a >> a
686 $ echo a >> a
687 $ hg commit -i -d '16 0' -m subdir-change a <<EOF
687 $ hg commit -i -d '16 0' -m subdir-change a <<EOF
688 > y
688 > y
689 > y
689 > y
690 > EOF
690 > EOF
691 diff --git a/subdir/a b/subdir/a
691 diff --git a/subdir/a b/subdir/a
692 1 hunks, 1 lines changed
692 1 hunks, 1 lines changed
693 examine changes to 'subdir/a'? [Ynesfdaq?] y
693 examine changes to 'subdir/a'? [Ynesfdaq?] y
694
694
695 @@ -1,1 +1,2 @@
695 @@ -1,1 +1,2 @@
696 a
696 a
697 +a
697 +a
698 record this change to 'subdir/a'? [Ynesfdaq?] y
698 record this change to 'subdir/a'? [Ynesfdaq?] y
699
699
700
700
701 $ hg tip -p
701 $ hg tip -p
702 changeset: 18:61be427a9deb
702 changeset: 18:61be427a9deb
703 tag: tip
703 tag: tip
704 user: test
704 user: test
705 date: Thu Jan 01 00:00:16 1970 +0000
705 date: Thu Jan 01 00:00:16 1970 +0000
706 summary: subdir-change
706 summary: subdir-change
707
707
708 diff -r a7ffae4d61cb -r 61be427a9deb subdir/a
708 diff -r a7ffae4d61cb -r 61be427a9deb subdir/a
709 --- a/subdir/a Thu Jan 01 00:00:16 1970 +0000
709 --- a/subdir/a Thu Jan 01 00:00:16 1970 +0000
710 +++ b/subdir/a Thu Jan 01 00:00:16 1970 +0000
710 +++ b/subdir/a Thu Jan 01 00:00:16 1970 +0000
711 @@ -1,1 +1,2 @@
711 @@ -1,1 +1,2 @@
712 a
712 a
713 +a
713 +a
714
714
715
715
716 $ echo a > f1
716 $ echo a > f1
717 $ echo b > f2
717 $ echo b > f2
718 $ hg add f1 f2
718 $ hg add f1 f2
719
719
720 $ hg ci -mz -d '17 0'
720 $ hg ci -mz -d '17 0'
721
721
722 $ echo a >> f1
722 $ echo a >> f1
723 $ echo b >> f2
723 $ echo b >> f2
724
724
725 Help, quit
725 Help, quit
726
726
727 $ hg commit -i <<EOF
727 $ hg commit -i <<EOF
728 > ?
728 > ?
729 > q
729 > q
730 > EOF
730 > EOF
731 diff --git a/subdir/f1 b/subdir/f1
731 diff --git a/subdir/f1 b/subdir/f1
732 1 hunks, 1 lines changed
732 1 hunks, 1 lines changed
733 examine changes to 'subdir/f1'? [Ynesfdaq?] ?
733 examine changes to 'subdir/f1'? [Ynesfdaq?] ?
734
734
735 y - yes, record this change
735 y - yes, record this change
736 n - no, skip this change
736 n - no, skip this change
737 e - edit this change manually
737 e - edit this change manually
738 s - skip remaining changes to this file
738 s - skip remaining changes to this file
739 f - record remaining changes to this file
739 f - record remaining changes to this file
740 d - done, skip remaining changes and files
740 d - done, skip remaining changes and files
741 a - record all changes to all remaining files
741 a - record all changes to all remaining files
742 q - quit, recording no changes
742 q - quit, recording no changes
743 ? - ? (display help)
743 ? - ? (display help)
744 examine changes to 'subdir/f1'? [Ynesfdaq?] q
744 examine changes to 'subdir/f1'? [Ynesfdaq?] q
745
745
746 abort: user quit
746 abort: user quit
747 [255]
747 [255]
748
748
749 Skip
749 Skip
750
750
751 $ hg commit -i <<EOF
751 $ hg commit -i <<EOF
752 > s
752 > s
753 > EOF
753 > EOF
754 diff --git a/subdir/f1 b/subdir/f1
754 diff --git a/subdir/f1 b/subdir/f1
755 1 hunks, 1 lines changed
755 1 hunks, 1 lines changed
756 examine changes to 'subdir/f1'? [Ynesfdaq?] s
756 examine changes to 'subdir/f1'? [Ynesfdaq?] s
757
757
758 diff --git a/subdir/f2 b/subdir/f2
758 diff --git a/subdir/f2 b/subdir/f2
759 1 hunks, 1 lines changed
759 1 hunks, 1 lines changed
760 examine changes to 'subdir/f2'? [Ynesfdaq?] abort: response expected
760 examine changes to 'subdir/f2'? [Ynesfdaq?] abort: response expected
761 [255]
761 [255]
762
762
763 No
763 No
764
764
765 $ hg commit -i <<EOF
765 $ hg commit -i <<EOF
766 > n
766 > n
767 > EOF
767 > EOF
768 diff --git a/subdir/f1 b/subdir/f1
768 diff --git a/subdir/f1 b/subdir/f1
769 1 hunks, 1 lines changed
769 1 hunks, 1 lines changed
770 examine changes to 'subdir/f1'? [Ynesfdaq?] n
770 examine changes to 'subdir/f1'? [Ynesfdaq?] n
771
771
772 diff --git a/subdir/f2 b/subdir/f2
772 diff --git a/subdir/f2 b/subdir/f2
773 1 hunks, 1 lines changed
773 1 hunks, 1 lines changed
774 examine changes to 'subdir/f2'? [Ynesfdaq?] abort: response expected
774 examine changes to 'subdir/f2'? [Ynesfdaq?] abort: response expected
775 [255]
775 [255]
776
776
777 f, quit
777 f, quit
778
778
779 $ hg commit -i <<EOF
779 $ hg commit -i <<EOF
780 > f
780 > f
781 > q
781 > q
782 > EOF
782 > EOF
783 diff --git a/subdir/f1 b/subdir/f1
783 diff --git a/subdir/f1 b/subdir/f1
784 1 hunks, 1 lines changed
784 1 hunks, 1 lines changed
785 examine changes to 'subdir/f1'? [Ynesfdaq?] f
785 examine changes to 'subdir/f1'? [Ynesfdaq?] f
786
786
787 diff --git a/subdir/f2 b/subdir/f2
787 diff --git a/subdir/f2 b/subdir/f2
788 1 hunks, 1 lines changed
788 1 hunks, 1 lines changed
789 examine changes to 'subdir/f2'? [Ynesfdaq?] q
789 examine changes to 'subdir/f2'? [Ynesfdaq?] q
790
790
791 abort: user quit
791 abort: user quit
792 [255]
792 [255]
793
793
794 s, all
794 s, all
795
795
796 $ hg commit -i -d '18 0' -mx <<EOF
796 $ hg commit -i -d '18 0' -mx <<EOF
797 > s
797 > s
798 > a
798 > a
799 > EOF
799 > EOF
800 diff --git a/subdir/f1 b/subdir/f1
800 diff --git a/subdir/f1 b/subdir/f1
801 1 hunks, 1 lines changed
801 1 hunks, 1 lines changed
802 examine changes to 'subdir/f1'? [Ynesfdaq?] s
802 examine changes to 'subdir/f1'? [Ynesfdaq?] s
803
803
804 diff --git a/subdir/f2 b/subdir/f2
804 diff --git a/subdir/f2 b/subdir/f2
805 1 hunks, 1 lines changed
805 1 hunks, 1 lines changed
806 examine changes to 'subdir/f2'? [Ynesfdaq?] a
806 examine changes to 'subdir/f2'? [Ynesfdaq?] a
807
807
808
808
809 $ hg tip -p
809 $ hg tip -p
810 changeset: 20:b3df3dda369a
810 changeset: 20:b3df3dda369a
811 tag: tip
811 tag: tip
812 user: test
812 user: test
813 date: Thu Jan 01 00:00:18 1970 +0000
813 date: Thu Jan 01 00:00:18 1970 +0000
814 summary: x
814 summary: x
815
815
816 diff -r 6e02d6c9906d -r b3df3dda369a subdir/f2
816 diff -r 6e02d6c9906d -r b3df3dda369a subdir/f2
817 --- a/subdir/f2 Thu Jan 01 00:00:17 1970 +0000
817 --- a/subdir/f2 Thu Jan 01 00:00:17 1970 +0000
818 +++ b/subdir/f2 Thu Jan 01 00:00:18 1970 +0000
818 +++ b/subdir/f2 Thu Jan 01 00:00:18 1970 +0000
819 @@ -1,1 +1,2 @@
819 @@ -1,1 +1,2 @@
820 b
820 b
821 +b
821 +b
822
822
823
823
824 f
824 f
825
825
826 $ hg commit -i -d '19 0' -my <<EOF
826 $ hg commit -i -d '19 0' -my <<EOF
827 > f
827 > f
828 > EOF
828 > EOF
829 diff --git a/subdir/f1 b/subdir/f1
829 diff --git a/subdir/f1 b/subdir/f1
830 1 hunks, 1 lines changed
830 1 hunks, 1 lines changed
831 examine changes to 'subdir/f1'? [Ynesfdaq?] f
831 examine changes to 'subdir/f1'? [Ynesfdaq?] f
832
832
833
833
834 $ hg tip -p
834 $ hg tip -p
835 changeset: 21:38ec577f126b
835 changeset: 21:38ec577f126b
836 tag: tip
836 tag: tip
837 user: test
837 user: test
838 date: Thu Jan 01 00:00:19 1970 +0000
838 date: Thu Jan 01 00:00:19 1970 +0000
839 summary: y
839 summary: y
840
840
841 diff -r b3df3dda369a -r 38ec577f126b subdir/f1
841 diff -r b3df3dda369a -r 38ec577f126b subdir/f1
842 --- a/subdir/f1 Thu Jan 01 00:00:18 1970 +0000
842 --- a/subdir/f1 Thu Jan 01 00:00:18 1970 +0000
843 +++ b/subdir/f1 Thu Jan 01 00:00:19 1970 +0000
843 +++ b/subdir/f1 Thu Jan 01 00:00:19 1970 +0000
844 @@ -1,1 +1,2 @@
844 @@ -1,1 +1,2 @@
845 a
845 a
846 +a
846 +a
847
847
848
848
849 #if execbit
849 #if execbit
850
850
851 Preserve chmod +x
851 Preserve chmod +x
852
852
853 $ chmod +x f1
853 $ chmod +x f1
854 $ echo a >> f1
854 $ echo a >> f1
855 $ hg commit -i -d '20 0' -mz <<EOF
855 $ hg commit -i -d '20 0' -mz <<EOF
856 > y
856 > y
857 > y
857 > y
858 > y
858 > y
859 > EOF
859 > EOF
860 diff --git a/subdir/f1 b/subdir/f1
860 diff --git a/subdir/f1 b/subdir/f1
861 old mode 100644
861 old mode 100644
862 new mode 100755
862 new mode 100755
863 1 hunks, 1 lines changed
863 1 hunks, 1 lines changed
864 examine changes to 'subdir/f1'? [Ynesfdaq?] y
864 examine changes to 'subdir/f1'? [Ynesfdaq?] y
865
865
866 @@ -1,2 +1,3 @@
866 @@ -1,2 +1,3 @@
867 a
867 a
868 a
868 a
869 +a
869 +a
870 record this change to 'subdir/f1'? [Ynesfdaq?] y
870 record this change to 'subdir/f1'? [Ynesfdaq?] y
871
871
872
872
873 $ hg tip --config diff.git=True -p
873 $ hg tip --config diff.git=True -p
874 changeset: 22:3261adceb075
874 changeset: 22:3261adceb075
875 tag: tip
875 tag: tip
876 user: test
876 user: test
877 date: Thu Jan 01 00:00:20 1970 +0000
877 date: Thu Jan 01 00:00:20 1970 +0000
878 summary: z
878 summary: z
879
879
880 diff --git a/subdir/f1 b/subdir/f1
880 diff --git a/subdir/f1 b/subdir/f1
881 old mode 100644
881 old mode 100644
882 new mode 100755
882 new mode 100755
883 --- a/subdir/f1
883 --- a/subdir/f1
884 +++ b/subdir/f1
884 +++ b/subdir/f1
885 @@ -1,2 +1,3 @@
885 @@ -1,2 +1,3 @@
886 a
886 a
887 a
887 a
888 +a
888 +a
889
889
890
890
891 Preserve execute permission on original
891 Preserve execute permission on original
892
892
893 $ echo b >> f1
893 $ echo b >> f1
894 $ hg commit -i -d '21 0' -maa <<EOF
894 $ hg commit -i -d '21 0' -maa <<EOF
895 > y
895 > y
896 > y
896 > y
897 > y
897 > y
898 > EOF
898 > EOF
899 diff --git a/subdir/f1 b/subdir/f1
899 diff --git a/subdir/f1 b/subdir/f1
900 1 hunks, 1 lines changed
900 1 hunks, 1 lines changed
901 examine changes to 'subdir/f1'? [Ynesfdaq?] y
901 examine changes to 'subdir/f1'? [Ynesfdaq?] y
902
902
903 @@ -1,3 +1,4 @@
903 @@ -1,3 +1,4 @@
904 a
904 a
905 a
905 a
906 a
906 a
907 +b
907 +b
908 record this change to 'subdir/f1'? [Ynesfdaq?] y
908 record this change to 'subdir/f1'? [Ynesfdaq?] y
909
909
910
910
911 $ hg tip --config diff.git=True -p
911 $ hg tip --config diff.git=True -p
912 changeset: 23:b429867550db
912 changeset: 23:b429867550db
913 tag: tip
913 tag: tip
914 user: test
914 user: test
915 date: Thu Jan 01 00:00:21 1970 +0000
915 date: Thu Jan 01 00:00:21 1970 +0000
916 summary: aa
916 summary: aa
917
917
918 diff --git a/subdir/f1 b/subdir/f1
918 diff --git a/subdir/f1 b/subdir/f1
919 --- a/subdir/f1
919 --- a/subdir/f1
920 +++ b/subdir/f1
920 +++ b/subdir/f1
921 @@ -1,3 +1,4 @@
921 @@ -1,3 +1,4 @@
922 a
922 a
923 a
923 a
924 a
924 a
925 +b
925 +b
926
926
927
927
928 Preserve chmod -x
928 Preserve chmod -x
929
929
930 $ chmod -x f1
930 $ chmod -x f1
931 $ echo c >> f1
931 $ echo c >> f1
932 $ hg commit -i -d '22 0' -mab <<EOF
932 $ hg commit -i -d '22 0' -mab <<EOF
933 > y
933 > y
934 > y
934 > y
935 > y
935 > y
936 > EOF
936 > EOF
937 diff --git a/subdir/f1 b/subdir/f1
937 diff --git a/subdir/f1 b/subdir/f1
938 old mode 100755
938 old mode 100755
939 new mode 100644
939 new mode 100644
940 1 hunks, 1 lines changed
940 1 hunks, 1 lines changed
941 examine changes to 'subdir/f1'? [Ynesfdaq?] y
941 examine changes to 'subdir/f1'? [Ynesfdaq?] y
942
942
943 @@ -2,3 +2,4 @@
943 @@ -2,3 +2,4 @@
944 a
944 a
945 a
945 a
946 b
946 b
947 +c
947 +c
948 record this change to 'subdir/f1'? [Ynesfdaq?] y
948 record this change to 'subdir/f1'? [Ynesfdaq?] y
949
949
950
950
951 $ hg tip --config diff.git=True -p
951 $ hg tip --config diff.git=True -p
952 changeset: 24:0b082130c20a
952 changeset: 24:0b082130c20a
953 tag: tip
953 tag: tip
954 user: test
954 user: test
955 date: Thu Jan 01 00:00:22 1970 +0000
955 date: Thu Jan 01 00:00:22 1970 +0000
956 summary: ab
956 summary: ab
957
957
958 diff --git a/subdir/f1 b/subdir/f1
958 diff --git a/subdir/f1 b/subdir/f1
959 old mode 100755
959 old mode 100755
960 new mode 100644
960 new mode 100644
961 --- a/subdir/f1
961 --- a/subdir/f1
962 +++ b/subdir/f1
962 +++ b/subdir/f1
963 @@ -2,3 +2,4 @@
963 @@ -2,3 +2,4 @@
964 a
964 a
965 a
965 a
966 b
966 b
967 +c
967 +c
968
968
969
969
970 #else
970 #else
971
971
972 Slightly bogus tests to get almost same repo structure as when x bit is used
972 Slightly bogus tests to get almost same repo structure as when x bit is used
973 - but with different hashes.
973 - but with different hashes.
974
974
975 Mock "Preserve chmod +x"
975 Mock "Preserve chmod +x"
976
976
977 $ echo a >> f1
977 $ echo a >> f1
978 $ hg commit -i -d '20 0' -mz <<EOF
978 $ hg commit -i -d '20 0' -mz <<EOF
979 > y
979 > y
980 > y
980 > y
981 > y
981 > y
982 > EOF
982 > EOF
983 diff --git a/subdir/f1 b/subdir/f1
983 diff --git a/subdir/f1 b/subdir/f1
984 1 hunks, 1 lines changed
984 1 hunks, 1 lines changed
985 examine changes to 'subdir/f1'? [Ynesfdaq?] y
985 examine changes to 'subdir/f1'? [Ynesfdaq?] y
986
986
987 @@ -1,2 +1,3 @@
987 @@ -1,2 +1,3 @@
988 a
988 a
989 a
989 a
990 +a
990 +a
991 record this change to 'subdir/f1'? [Ynesfdaq?] y
991 record this change to 'subdir/f1'? [Ynesfdaq?] y
992
992
993
993
994 $ hg tip --config diff.git=True -p
994 $ hg tip --config diff.git=True -p
995 changeset: 22:0d463bd428f5
995 changeset: 22:0d463bd428f5
996 tag: tip
996 tag: tip
997 user: test
997 user: test
998 date: Thu Jan 01 00:00:20 1970 +0000
998 date: Thu Jan 01 00:00:20 1970 +0000
999 summary: z
999 summary: z
1000
1000
1001 diff --git a/subdir/f1 b/subdir/f1
1001 diff --git a/subdir/f1 b/subdir/f1
1002 --- a/subdir/f1
1002 --- a/subdir/f1
1003 +++ b/subdir/f1
1003 +++ b/subdir/f1
1004 @@ -1,2 +1,3 @@
1004 @@ -1,2 +1,3 @@
1005 a
1005 a
1006 a
1006 a
1007 +a
1007 +a
1008
1008
1009
1009
1010 Mock "Preserve execute permission on original"
1010 Mock "Preserve execute permission on original"
1011
1011
1012 $ echo b >> f1
1012 $ echo b >> f1
1013 $ hg commit -i -d '21 0' -maa <<EOF
1013 $ hg commit -i -d '21 0' -maa <<EOF
1014 > y
1014 > y
1015 > y
1015 > y
1016 > y
1016 > y
1017 > EOF
1017 > EOF
1018 diff --git a/subdir/f1 b/subdir/f1
1018 diff --git a/subdir/f1 b/subdir/f1
1019 1 hunks, 1 lines changed
1019 1 hunks, 1 lines changed
1020 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1020 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1021
1021
1022 @@ -1,3 +1,4 @@
1022 @@ -1,3 +1,4 @@
1023 a
1023 a
1024 a
1024 a
1025 a
1025 a
1026 +b
1026 +b
1027 record this change to 'subdir/f1'? [Ynesfdaq?] y
1027 record this change to 'subdir/f1'? [Ynesfdaq?] y
1028
1028
1029
1029
1030 $ hg tip --config diff.git=True -p
1030 $ hg tip --config diff.git=True -p
1031 changeset: 23:0eab41a3e524
1031 changeset: 23:0eab41a3e524
1032 tag: tip
1032 tag: tip
1033 user: test
1033 user: test
1034 date: Thu Jan 01 00:00:21 1970 +0000
1034 date: Thu Jan 01 00:00:21 1970 +0000
1035 summary: aa
1035 summary: aa
1036
1036
1037 diff --git a/subdir/f1 b/subdir/f1
1037 diff --git a/subdir/f1 b/subdir/f1
1038 --- a/subdir/f1
1038 --- a/subdir/f1
1039 +++ b/subdir/f1
1039 +++ b/subdir/f1
1040 @@ -1,3 +1,4 @@
1040 @@ -1,3 +1,4 @@
1041 a
1041 a
1042 a
1042 a
1043 a
1043 a
1044 +b
1044 +b
1045
1045
1046
1046
1047 Mock "Preserve chmod -x"
1047 Mock "Preserve chmod -x"
1048
1048
1049 $ chmod -x f1
1049 $ chmod -x f1
1050 $ echo c >> f1
1050 $ echo c >> f1
1051 $ hg commit -i -d '22 0' -mab <<EOF
1051 $ hg commit -i -d '22 0' -mab <<EOF
1052 > y
1052 > y
1053 > y
1053 > y
1054 > y
1054 > y
1055 > EOF
1055 > EOF
1056 diff --git a/subdir/f1 b/subdir/f1
1056 diff --git a/subdir/f1 b/subdir/f1
1057 1 hunks, 1 lines changed
1057 1 hunks, 1 lines changed
1058 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1058 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1059
1059
1060 @@ -2,3 +2,4 @@
1060 @@ -2,3 +2,4 @@
1061 a
1061 a
1062 a
1062 a
1063 b
1063 b
1064 +c
1064 +c
1065 record this change to 'subdir/f1'? [Ynesfdaq?] y
1065 record this change to 'subdir/f1'? [Ynesfdaq?] y
1066
1066
1067
1067
1068 $ hg tip --config diff.git=True -p
1068 $ hg tip --config diff.git=True -p
1069 changeset: 24:f4f718f27b7c
1069 changeset: 24:f4f718f27b7c
1070 tag: tip
1070 tag: tip
1071 user: test
1071 user: test
1072 date: Thu Jan 01 00:00:22 1970 +0000
1072 date: Thu Jan 01 00:00:22 1970 +0000
1073 summary: ab
1073 summary: ab
1074
1074
1075 diff --git a/subdir/f1 b/subdir/f1
1075 diff --git a/subdir/f1 b/subdir/f1
1076 --- a/subdir/f1
1076 --- a/subdir/f1
1077 +++ b/subdir/f1
1077 +++ b/subdir/f1
1078 @@ -2,3 +2,4 @@
1078 @@ -2,3 +2,4 @@
1079 a
1079 a
1080 a
1080 a
1081 b
1081 b
1082 +c
1082 +c
1083
1083
1084
1084
1085 #endif
1085 #endif
1086
1086
1087 $ cd ..
1087 $ cd ..
1088
1088
1089
1089
1090 Abort early when a merge is in progress
1090 Abort early when a merge is in progress
1091
1091
1092 $ hg up 4
1092 $ hg up 4
1093 1 files updated, 0 files merged, 6 files removed, 0 files unresolved
1093 1 files updated, 0 files merged, 6 files removed, 0 files unresolved
1094
1094
1095 $ touch iwillmergethat
1095 $ touch iwillmergethat
1096 $ hg add iwillmergethat
1096 $ hg add iwillmergethat
1097
1097
1098 $ hg branch thatbranch
1098 $ hg branch thatbranch
1099 marked working directory as branch thatbranch
1099 marked working directory as branch thatbranch
1100 (branches are permanent and global, did you want a bookmark?)
1100 (branches are permanent and global, did you want a bookmark?)
1101
1101
1102 $ hg ci -m'new head'
1102 $ hg ci -m'new head'
1103
1103
1104 $ hg up default
1104 $ hg up default
1105 6 files updated, 0 files merged, 2 files removed, 0 files unresolved
1105 6 files updated, 0 files merged, 2 files removed, 0 files unresolved
1106
1106
1107 $ hg merge thatbranch
1107 $ hg merge thatbranch
1108 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1108 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1109 (branch merge, don't forget to commit)
1109 (branch merge, don't forget to commit)
1110
1110
1111 $ hg commit -i -m'will abort'
1111 $ hg commit -i -m'will abort'
1112 abort: cannot partially commit a merge (use "hg commit" instead)
1112 abort: cannot partially commit a merge (use "hg commit" instead)
1113 [255]
1113 [255]
1114
1114
1115 $ hg up -C
1115 $ hg up -C
1116 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1116 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1117
1117
1118 Editing patch (and ignoring trailing text)
1118 Editing patch (and ignoring trailing text)
1119
1119
1120 $ cat > editor.sh << '__EOF__'
1120 $ cat > editor.sh << '__EOF__'
1121 > sed -e 7d -e '5s/^-/ /' -e '/^# ---/i\
1121 > sed -e 7d -e '5s/^-/ /' -e '/^# ---/i\
1122 > trailing\nditto' "$1" > tmp
1122 > trailing\nditto' "$1" > tmp
1123 > mv tmp "$1"
1123 > mv tmp "$1"
1124 > __EOF__
1124 > __EOF__
1125 $ cat > editedfile << '__EOF__'
1125 $ cat > editedfile << '__EOF__'
1126 > This is the first line
1126 > This is the first line
1127 > This is the second line
1127 > This is the second line
1128 > This is the third line
1128 > This is the third line
1129 > __EOF__
1129 > __EOF__
1130 $ hg add editedfile
1130 $ hg add editedfile
1131 $ hg commit -medit-patch-1
1131 $ hg commit -medit-patch-1
1132 $ cat > editedfile << '__EOF__'
1132 $ cat > editedfile << '__EOF__'
1133 > This line has changed
1133 > This line has changed
1134 > This change will be committed
1134 > This change will be committed
1135 > This is the third line
1135 > This is the third line
1136 > __EOF__
1136 > __EOF__
1137 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-2 <<EOF
1137 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-2 <<EOF
1138 > y
1138 > y
1139 > e
1139 > e
1140 > EOF
1140 > EOF
1141 diff --git a/editedfile b/editedfile
1141 diff --git a/editedfile b/editedfile
1142 1 hunks, 2 lines changed
1142 1 hunks, 2 lines changed
1143 examine changes to 'editedfile'? [Ynesfdaq?] y
1143 examine changes to 'editedfile'? [Ynesfdaq?] y
1144
1144
1145 @@ -1,3 +1,3 @@
1145 @@ -1,3 +1,3 @@
1146 -This is the first line
1146 -This is the first line
1147 -This is the second line
1147 -This is the second line
1148 +This line has changed
1148 +This line has changed
1149 +This change will be committed
1149 +This change will be committed
1150 This is the third line
1150 This is the third line
1151 record this change to 'editedfile'? [Ynesfdaq?] e
1151 record this change to 'editedfile'? [Ynesfdaq?] e
1152
1152
1153 $ cat editedfile
1153 $ cat editedfile
1154 This line has changed
1154 This line has changed
1155 This change will be committed
1155 This change will be committed
1156 This is the third line
1156 This is the third line
1157 $ hg cat -r tip editedfile
1157 $ hg cat -r tip editedfile
1158 This is the first line
1158 This is the first line
1159 This change will be committed
1159 This change will be committed
1160 This is the third line
1160 This is the third line
1161 $ hg revert editedfile
1161 $ hg revert editedfile
1162
1162
1163 Trying to edit patch for whole file
1163 Trying to edit patch for whole file
1164
1164
1165 $ echo "This is the fourth line" >> editedfile
1165 $ echo "This is the fourth line" >> editedfile
1166 $ hg commit -i <<EOF
1166 $ hg commit -i <<EOF
1167 > e
1167 > e
1168 > q
1168 > q
1169 > EOF
1169 > EOF
1170 diff --git a/editedfile b/editedfile
1170 diff --git a/editedfile b/editedfile
1171 1 hunks, 1 lines changed
1171 1 hunks, 1 lines changed
1172 examine changes to 'editedfile'? [Ynesfdaq?] e
1172 examine changes to 'editedfile'? [Ynesfdaq?] e
1173
1173
1174 cannot edit patch for whole file
1174 cannot edit patch for whole file
1175 examine changes to 'editedfile'? [Ynesfdaq?] q
1175 examine changes to 'editedfile'? [Ynesfdaq?] q
1176
1176
1177 abort: user quit
1177 abort: user quit
1178 [255]
1178 [255]
1179 $ hg revert editedfile
1179 $ hg revert editedfile
1180
1180
1181 Removing changes from patch
1181 Removing changes from patch
1182
1182
1183 $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
1183 $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
1184 $ mv tmp editedfile
1184 $ mv tmp editedfile
1185 $ echo "This line has been added" >> editedfile
1185 $ echo "This line has been added" >> editedfile
1186 $ cat > editor.sh << '__EOF__'
1186 $ cat > editor.sh << '__EOF__'
1187 > sed -e 's/^[-+]/ /' "$1" > tmp
1187 > sed -e 's/^[-+]/ /' "$1" > tmp
1188 > mv tmp "$1"
1188 > mv tmp "$1"
1189 > __EOF__
1189 > __EOF__
1190 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1190 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1191 > y
1191 > y
1192 > e
1192 > e
1193 > EOF
1193 > EOF
1194 diff --git a/editedfile b/editedfile
1194 diff --git a/editedfile b/editedfile
1195 1 hunks, 3 lines changed
1195 1 hunks, 3 lines changed
1196 examine changes to 'editedfile'? [Ynesfdaq?] y
1196 examine changes to 'editedfile'? [Ynesfdaq?] y
1197
1197
1198 @@ -1,3 +1,3 @@
1198 @@ -1,3 +1,3 @@
1199 -This is the first line
1199 -This is the first line
1200 -This change will be committed
1200 -This change will be committed
1201 -This is the third line
1201 -This is the third line
1202 +This change will not be committed
1202 +This change will not be committed
1203 +This is the second line
1203 +This is the second line
1204 +This line has been added
1204 +This line has been added
1205 record this change to 'editedfile'? [Ynesfdaq?] e
1205 record this change to 'editedfile'? [Ynesfdaq?] e
1206
1206
1207 no changes to record
1207 no changes to record
1208 $ cat editedfile
1208 $ cat editedfile
1209 This change will not be committed
1209 This change will not be committed
1210 This is the second line
1210 This is the second line
1211 This line has been added
1211 This line has been added
1212 $ hg cat -r tip editedfile
1212 $ hg cat -r tip editedfile
1213 This is the first line
1213 This is the first line
1214 This change will be committed
1214 This change will be committed
1215 This is the third line
1215 This is the third line
1216 $ hg revert editedfile
1216 $ hg revert editedfile
1217
1217
1218 Invalid patch
1218 Invalid patch
1219
1219
1220 $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
1220 $ sed -e '3s/third/second/' -e '2s/will/will not/' -e 1d editedfile > tmp
1221 $ mv tmp editedfile
1221 $ mv tmp editedfile
1222 $ echo "This line has been added" >> editedfile
1222 $ echo "This line has been added" >> editedfile
1223 $ cat > editor.sh << '__EOF__'
1223 $ cat > editor.sh << '__EOF__'
1224 > sed s/This/That/ "$1" > tmp
1224 > sed s/This/That/ "$1" > tmp
1225 > mv tmp "$1"
1225 > mv tmp "$1"
1226 > __EOF__
1226 > __EOF__
1227 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1227 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1228 > y
1228 > y
1229 > e
1229 > e
1230 > EOF
1230 > EOF
1231 diff --git a/editedfile b/editedfile
1231 diff --git a/editedfile b/editedfile
1232 1 hunks, 3 lines changed
1232 1 hunks, 3 lines changed
1233 examine changes to 'editedfile'? [Ynesfdaq?] y
1233 examine changes to 'editedfile'? [Ynesfdaq?] y
1234
1234
1235 @@ -1,3 +1,3 @@
1235 @@ -1,3 +1,3 @@
1236 -This is the first line
1236 -This is the first line
1237 -This change will be committed
1237 -This change will be committed
1238 -This is the third line
1238 -This is the third line
1239 +This change will not be committed
1239 +This change will not be committed
1240 +This is the second line
1240 +This is the second line
1241 +This line has been added
1241 +This line has been added
1242 record this change to 'editedfile'? [Ynesfdaq?] e
1242 record this change to 'editedfile'? [Ynesfdaq?] e
1243
1243
1244 patching file editedfile
1244 patching file editedfile
1245 Hunk #1 FAILED at 0
1245 Hunk #1 FAILED at 0
1246 1 out of 1 hunks FAILED -- saving rejects to file editedfile.rej
1246 1 out of 1 hunks FAILED -- saving rejects to file editedfile.rej
1247 abort: patch failed to apply
1247 abort: patch failed to apply
1248 [255]
1248 [255]
1249 $ cat editedfile
1249 $ cat editedfile
1250 This change will not be committed
1250 This change will not be committed
1251 This is the second line
1251 This is the second line
1252 This line has been added
1252 This line has been added
1253 $ hg cat -r tip editedfile
1253 $ hg cat -r tip editedfile
1254 This is the first line
1254 This is the first line
1255 This change will be committed
1255 This change will be committed
1256 This is the third line
1256 This is the third line
1257 $ cat editedfile.rej
1257 $ cat editedfile.rej
1258 --- editedfile
1258 --- editedfile
1259 +++ editedfile
1259 +++ editedfile
1260 @@ -1,3 +1,3 @@
1260 @@ -1,3 +1,3 @@
1261 -That is the first line
1261 -That is the first line
1262 -That change will be committed
1262 -That change will be committed
1263 -That is the third line
1263 -That is the third line
1264 +That change will not be committed
1264 +That change will not be committed
1265 +That is the second line
1265 +That is the second line
1266 +That line has been added
1266 +That line has been added
1267
1267
1268 Malformed patch - error handling
1268 Malformed patch - error handling
1269
1269
1270 $ cat > editor.sh << '__EOF__'
1270 $ cat > editor.sh << '__EOF__'
1271 > sed -e '/^@/p' "$1" > tmp
1271 > sed -e '/^@/p' "$1" > tmp
1272 > mv tmp "$1"
1272 > mv tmp "$1"
1273 > __EOF__
1273 > __EOF__
1274 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1274 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1275 > y
1275 > y
1276 > e
1276 > e
1277 > EOF
1277 > EOF
1278 diff --git a/editedfile b/editedfile
1278 diff --git a/editedfile b/editedfile
1279 1 hunks, 3 lines changed
1279 1 hunks, 3 lines changed
1280 examine changes to 'editedfile'? [Ynesfdaq?] y
1280 examine changes to 'editedfile'? [Ynesfdaq?] y
1281
1281
1282 @@ -1,3 +1,3 @@
1282 @@ -1,3 +1,3 @@
1283 -This is the first line
1283 -This is the first line
1284 -This change will be committed
1284 -This change will be committed
1285 -This is the third line
1285 -This is the third line
1286 +This change will not be committed
1286 +This change will not be committed
1287 +This is the second line
1287 +This is the second line
1288 +This line has been added
1288 +This line has been added
1289 record this change to 'editedfile'? [Ynesfdaq?] e
1289 record this change to 'editedfile'? [Ynesfdaq?] e
1290
1290
1291 abort: error parsing patch: unhandled transition: range -> range
1291 abort: error parsing patch: unhandled transition: range -> range
1292 [255]
1292 [255]
1293
1293
1294 Exiting editor with status 1, ignores the edit but does not stop the recording
1295 session
1296
1297 $ HGEDITOR=false hg commit -i <<EOF
1298 > y
1299 > e
1300 > n
1301 > EOF
1302 diff --git a/editedfile b/editedfile
1303 1 hunks, 3 lines changed
1304 examine changes to 'editedfile'? [Ynesfdaq?] y
1305
1306 @@ -1,3 +1,3 @@
1307 -This is the first line
1308 -This change will be committed
1309 -This is the third line
1310 +This change will not be committed
1311 +This is the second line
1312 +This line has been added
1313 record this change to 'editedfile'? [Ynesfdaq?] e
1314
1315 editor exited with exit code 1
1316 record this change to 'editedfile'? [Ynesfdaq?] n
1317
1318 no changes to record
1319
1320
1294 random text in random positions is still an error
1321 random text in random positions is still an error
1295
1322
1296 $ cat > editor.sh << '__EOF__'
1323 $ cat > editor.sh << '__EOF__'
1297 > sed -e '/^@/i\
1324 > sed -e '/^@/i\
1298 > other' "$1" > tmp
1325 > other' "$1" > tmp
1299 > mv tmp "$1"
1326 > mv tmp "$1"
1300 > __EOF__
1327 > __EOF__
1301 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1328 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i <<EOF
1302 > y
1329 > y
1303 > e
1330 > e
1304 > EOF
1331 > EOF
1305 diff --git a/editedfile b/editedfile
1332 diff --git a/editedfile b/editedfile
1306 1 hunks, 3 lines changed
1333 1 hunks, 3 lines changed
1307 examine changes to 'editedfile'? [Ynesfdaq?] y
1334 examine changes to 'editedfile'? [Ynesfdaq?] y
1308
1335
1309 @@ -1,3 +1,3 @@
1336 @@ -1,3 +1,3 @@
1310 -This is the first line
1337 -This is the first line
1311 -This change will be committed
1338 -This change will be committed
1312 -This is the third line
1339 -This is the third line
1313 +This change will not be committed
1340 +This change will not be committed
1314 +This is the second line
1341 +This is the second line
1315 +This line has been added
1342 +This line has been added
1316 record this change to 'editedfile'? [Ynesfdaq?] e
1343 record this change to 'editedfile'? [Ynesfdaq?] e
1317
1344
1318 abort: error parsing patch: unhandled transition: file -> other
1345 abort: error parsing patch: unhandled transition: file -> other
1319 [255]
1346 [255]
1320
1347
1321 $ hg up -C
1348 $ hg up -C
1322 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1349 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1323
1350
1324 With win32text
1351 With win32text
1325
1352
1326 $ echo '[extensions]' >> .hg/hgrc
1353 $ echo '[extensions]' >> .hg/hgrc
1327 $ echo 'win32text = ' >> .hg/hgrc
1354 $ echo 'win32text = ' >> .hg/hgrc
1328 $ echo '[decode]' >> .hg/hgrc
1355 $ echo '[decode]' >> .hg/hgrc
1329 $ echo '** = cleverdecode:' >> .hg/hgrc
1356 $ echo '** = cleverdecode:' >> .hg/hgrc
1330 $ echo '[encode]' >> .hg/hgrc
1357 $ echo '[encode]' >> .hg/hgrc
1331 $ echo '** = cleverencode:' >> .hg/hgrc
1358 $ echo '** = cleverencode:' >> .hg/hgrc
1332 $ echo '[patch]' >> .hg/hgrc
1359 $ echo '[patch]' >> .hg/hgrc
1333 $ echo 'eol = crlf' >> .hg/hgrc
1360 $ echo 'eol = crlf' >> .hg/hgrc
1334
1361
1335 Ignore win32text deprecation warning for now:
1362 Ignore win32text deprecation warning for now:
1336
1363
1337 $ echo '[win32text]' >> .hg/hgrc
1364 $ echo '[win32text]' >> .hg/hgrc
1338 $ echo 'warn = no' >> .hg/hgrc
1365 $ echo 'warn = no' >> .hg/hgrc
1339
1366
1340 $ echo d >> subdir/f1
1367 $ echo d >> subdir/f1
1341 $ hg commit -i -d '24 0' -mw1 <<EOF
1368 $ hg commit -i -d '24 0' -mw1 <<EOF
1342 > y
1369 > y
1343 > y
1370 > y
1344 > EOF
1371 > EOF
1345 diff --git a/subdir/f1 b/subdir/f1
1372 diff --git a/subdir/f1 b/subdir/f1
1346 1 hunks, 1 lines changed
1373 1 hunks, 1 lines changed
1347 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1374 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1348
1375
1349 @@ -3,3 +3,4 @@
1376 @@ -3,3 +3,4 @@
1350 a
1377 a
1351 b
1378 b
1352 c
1379 c
1353 +d
1380 +d
1354 record this change to 'subdir/f1'? [Ynesfdaq?] y
1381 record this change to 'subdir/f1'? [Ynesfdaq?] y
1355
1382
1356
1383
1357 $ hg tip -p
1384 $ hg tip -p
1358 changeset: 28:* (glob)
1385 changeset: 28:* (glob)
1359 tag: tip
1386 tag: tip
1360 user: test
1387 user: test
1361 date: Thu Jan 01 00:00:24 1970 +0000
1388 date: Thu Jan 01 00:00:24 1970 +0000
1362 summary: w1
1389 summary: w1
1363
1390
1364 diff -r ???????????? -r ???????????? subdir/f1 (glob)
1391 diff -r ???????????? -r ???????????? subdir/f1 (glob)
1365 --- a/subdir/f1 Thu Jan 01 00:00:23 1970 +0000
1392 --- a/subdir/f1 Thu Jan 01 00:00:23 1970 +0000
1366 +++ b/subdir/f1 Thu Jan 01 00:00:24 1970 +0000
1393 +++ b/subdir/f1 Thu Jan 01 00:00:24 1970 +0000
1367 @@ -3,3 +3,4 @@
1394 @@ -3,3 +3,4 @@
1368 a
1395 a
1369 b
1396 b
1370 c
1397 c
1371 +d
1398 +d
1372
1399
1373
1400
1374
1401
1375 Test --user when ui.username not set
1402 Test --user when ui.username not set
1376 $ unset HGUSER
1403 $ unset HGUSER
1377 $ echo e >> subdir/f1
1404 $ echo e >> subdir/f1
1378 $ hg commit -i --config ui.username= -d '8 0' --user xyz -m "user flag" <<EOF
1405 $ hg commit -i --config ui.username= -d '8 0' --user xyz -m "user flag" <<EOF
1379 > y
1406 > y
1380 > y
1407 > y
1381 > EOF
1408 > EOF
1382 diff --git a/subdir/f1 b/subdir/f1
1409 diff --git a/subdir/f1 b/subdir/f1
1383 1 hunks, 1 lines changed
1410 1 hunks, 1 lines changed
1384 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1411 examine changes to 'subdir/f1'? [Ynesfdaq?] y
1385
1412
1386 @@ -4,3 +4,4 @@
1413 @@ -4,3 +4,4 @@
1387 b
1414 b
1388 c
1415 c
1389 d
1416 d
1390 +e
1417 +e
1391 record this change to 'subdir/f1'? [Ynesfdaq?] y
1418 record this change to 'subdir/f1'? [Ynesfdaq?] y
1392
1419
1393 $ hg log --template '{author}\n' -l 1
1420 $ hg log --template '{author}\n' -l 1
1394 xyz
1421 xyz
1395 $ HGUSER="test"
1422 $ HGUSER="test"
1396 $ export HGUSER
1423 $ export HGUSER
1397
1424
1398
1425
1399 Moving files
1426 Moving files
1400
1427
1401 $ hg update -C .
1428 $ hg update -C .
1402 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1429 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1403 $ hg mv plain plain3
1430 $ hg mv plain plain3
1404 $ echo somechange >> plain3
1431 $ echo somechange >> plain3
1405 $ hg commit -i -d '23 0' -mmoving_files << EOF
1432 $ hg commit -i -d '23 0' -mmoving_files << EOF
1406 > y
1433 > y
1407 > y
1434 > y
1408 > EOF
1435 > EOF
1409 diff --git a/plain b/plain3
1436 diff --git a/plain b/plain3
1410 rename from plain
1437 rename from plain
1411 rename to plain3
1438 rename to plain3
1412 1 hunks, 1 lines changed
1439 1 hunks, 1 lines changed
1413 examine changes to 'plain' and 'plain3'? [Ynesfdaq?] y
1440 examine changes to 'plain' and 'plain3'? [Ynesfdaq?] y
1414
1441
1415 @@ -11,3 +11,4 @@
1442 @@ -11,3 +11,4 @@
1416 9
1443 9
1417 10
1444 10
1418 11
1445 11
1419 +somechange
1446 +somechange
1420 record this change to 'plain3'? [Ynesfdaq?] y
1447 record this change to 'plain3'? [Ynesfdaq?] y
1421
1448
1422 The #if execbit block above changes the hash here on some systems
1449 The #if execbit block above changes the hash here on some systems
1423 $ hg tip
1450 $ hg tip
1424 changeset: 30:* (glob)
1451 changeset: 30:* (glob)
1425 tag: tip
1452 tag: tip
1426 user: test
1453 user: test
1427 date: Thu Jan 01 00:00:23 1970 +0000
1454 date: Thu Jan 01 00:00:23 1970 +0000
1428 summary: moving_files
1455 summary: moving_files
1429
1456
1430 Editing patch of newly added file
1457 Editing patch of newly added file
1431
1458
1432 $ hg update -C .
1459 $ hg update -C .
1433 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1460 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1434 $ cat > editor.sh << '__EOF__'
1461 $ cat > editor.sh << '__EOF__'
1435 > cat "$1" | sed "s/first/very/g" > tt
1462 > cat "$1" | sed "s/first/very/g" > tt
1436 > mv tt "$1"
1463 > mv tt "$1"
1437 > __EOF__
1464 > __EOF__
1438 $ cat > newfile << '__EOF__'
1465 $ cat > newfile << '__EOF__'
1439 > This is the first line
1466 > This is the first line
1440 > This is the second line
1467 > This is the second line
1441 > This is the third line
1468 > This is the third line
1442 > __EOF__
1469 > __EOF__
1443 $ hg add newfile
1470 $ hg add newfile
1444 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-new <<EOF
1471 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -d '23 0' -medit-patch-new <<EOF
1445 > y
1472 > y
1446 > e
1473 > e
1447 > EOF
1474 > EOF
1448 diff --git a/newfile b/newfile
1475 diff --git a/newfile b/newfile
1449 new file mode 100644
1476 new file mode 100644
1450 examine changes to 'newfile'? [Ynesfdaq?] y
1477 examine changes to 'newfile'? [Ynesfdaq?] y
1451
1478
1452 @@ -0,0 +1,3 @@
1479 @@ -0,0 +1,3 @@
1453 +This is the first line
1480 +This is the first line
1454 +This is the second line
1481 +This is the second line
1455 +This is the third line
1482 +This is the third line
1456 record this change to 'newfile'? [Ynesfdaq?] e
1483 record this change to 'newfile'? [Ynesfdaq?] e
1457
1484
1458 $ hg cat -r tip newfile
1485 $ hg cat -r tip newfile
1459 This is the very line
1486 This is the very line
1460 This is the second line
1487 This is the second line
1461 This is the third line
1488 This is the third line
1462
1489
1463 $ cat newfile
1490 $ cat newfile
1464 This is the first line
1491 This is the first line
1465 This is the second line
1492 This is the second line
1466 This is the third line
1493 This is the third line
1467
1494
1468 Add new file from within a subdirectory
1495 Add new file from within a subdirectory
1469 $ hg update -C .
1496 $ hg update -C .
1470 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1497 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1471 $ mkdir folder
1498 $ mkdir folder
1472 $ cd folder
1499 $ cd folder
1473 $ echo "foo" > bar
1500 $ echo "foo" > bar
1474 $ hg add bar
1501 $ hg add bar
1475 $ hg commit -i -d '23 0' -mnewfilesubdir <<EOF
1502 $ hg commit -i -d '23 0' -mnewfilesubdir <<EOF
1476 > y
1503 > y
1477 > y
1504 > y
1478 > EOF
1505 > EOF
1479 diff --git a/folder/bar b/folder/bar
1506 diff --git a/folder/bar b/folder/bar
1480 new file mode 100644
1507 new file mode 100644
1481 examine changes to 'folder/bar'? [Ynesfdaq?] y
1508 examine changes to 'folder/bar'? [Ynesfdaq?] y
1482
1509
1483 @@ -0,0 +1,1 @@
1510 @@ -0,0 +1,1 @@
1484 +foo
1511 +foo
1485 record this change to 'folder/bar'? [Ynesfdaq?] y
1512 record this change to 'folder/bar'? [Ynesfdaq?] y
1486
1513
1487 The #if execbit block above changes the hashes here on some systems
1514 The #if execbit block above changes the hashes here on some systems
1488 $ hg tip -p
1515 $ hg tip -p
1489 changeset: 32:* (glob)
1516 changeset: 32:* (glob)
1490 tag: tip
1517 tag: tip
1491 user: test
1518 user: test
1492 date: Thu Jan 01 00:00:23 1970 +0000
1519 date: Thu Jan 01 00:00:23 1970 +0000
1493 summary: newfilesubdir
1520 summary: newfilesubdir
1494
1521
1495 diff -r * -r * folder/bar (glob)
1522 diff -r * -r * folder/bar (glob)
1496 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1523 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1497 +++ b/folder/bar Thu Jan 01 00:00:23 1970 +0000
1524 +++ b/folder/bar Thu Jan 01 00:00:23 1970 +0000
1498 @@ -0,0 +1,1 @@
1525 @@ -0,0 +1,1 @@
1499 +foo
1526 +foo
1500
1527
1501 $ cd ..
1528 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now