##// END OF EJS Templates
Make hg diff --git -r revA:revB detect (inverted) copies if revA > revB
Alexis S. L. Carvalho -
r5264:0fc16031 default
parent child Browse files
Show More
@@ -1,1330 +1,1347 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
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 from i18n import _
9 from i18n import _
10 from node import *
10 from node import *
11 import base85, cmdutil, mdiff, util, context, revlog, diffhelpers
11 import base85, cmdutil, mdiff, util, context, revlog, diffhelpers
12 import cStringIO, email.Parser, os, popen2, re, sha
12 import cStringIO, email.Parser, os, popen2, re, sha
13 import sys, tempfile, zlib
13 import sys, tempfile, zlib
14
14
15 class PatchError(Exception):
15 class PatchError(Exception):
16 pass
16 pass
17
17
18 class NoHunks(PatchError):
18 class NoHunks(PatchError):
19 pass
19 pass
20
20
21 # helper functions
21 # helper functions
22
22
23 def copyfile(src, dst, basedir=None):
23 def copyfile(src, dst, basedir=None):
24 if not basedir:
24 if not basedir:
25 basedir = os.getcwd()
25 basedir = os.getcwd()
26
26
27 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
27 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
28 if os.path.exists(absdst):
28 if os.path.exists(absdst):
29 raise util.Abort(_("cannot create %s: destination already exists") %
29 raise util.Abort(_("cannot create %s: destination already exists") %
30 dst)
30 dst)
31
31
32 targetdir = os.path.dirname(absdst)
32 targetdir = os.path.dirname(absdst)
33 if not os.path.isdir(targetdir):
33 if not os.path.isdir(targetdir):
34 os.makedirs(targetdir)
34 os.makedirs(targetdir)
35
35
36 util.copyfile(abssrc, absdst)
36 util.copyfile(abssrc, absdst)
37
37
38 # public functions
38 # public functions
39
39
40 def extract(ui, fileobj):
40 def extract(ui, fileobj):
41 '''extract patch from data read from fileobj.
41 '''extract patch from data read from fileobj.
42
42
43 patch can be a normal patch or contained in an email message.
43 patch can be a normal patch or contained in an email message.
44
44
45 return tuple (filename, message, user, date, node, p1, p2).
45 return tuple (filename, message, user, date, node, p1, p2).
46 Any item in the returned tuple can be None. If filename is None,
46 Any item in the returned tuple can be None. If filename is None,
47 fileobj did not contain a patch. Caller must unlink filename when done.'''
47 fileobj did not contain a patch. Caller must unlink filename when done.'''
48
48
49 # attempt to detect the start of a patch
49 # attempt to detect the start of a patch
50 # (this heuristic is borrowed from quilt)
50 # (this heuristic is borrowed from quilt)
51 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
51 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
52 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
52 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
53 '(---|\*\*\*)[ \t])', re.MULTILINE)
53 '(---|\*\*\*)[ \t])', re.MULTILINE)
54
54
55 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
55 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
56 tmpfp = os.fdopen(fd, 'w')
56 tmpfp = os.fdopen(fd, 'w')
57 try:
57 try:
58 msg = email.Parser.Parser().parse(fileobj)
58 msg = email.Parser.Parser().parse(fileobj)
59
59
60 subject = msg['Subject']
60 subject = msg['Subject']
61 user = msg['From']
61 user = msg['From']
62 # should try to parse msg['Date']
62 # should try to parse msg['Date']
63 date = None
63 date = None
64 nodeid = None
64 nodeid = None
65 branch = None
65 branch = None
66 parents = []
66 parents = []
67
67
68 if subject:
68 if subject:
69 if subject.startswith('[PATCH'):
69 if subject.startswith('[PATCH'):
70 pend = subject.find(']')
70 pend = subject.find(']')
71 if pend >= 0:
71 if pend >= 0:
72 subject = subject[pend+1:].lstrip()
72 subject = subject[pend+1:].lstrip()
73 subject = subject.replace('\n\t', ' ')
73 subject = subject.replace('\n\t', ' ')
74 ui.debug('Subject: %s\n' % subject)
74 ui.debug('Subject: %s\n' % subject)
75 if user:
75 if user:
76 ui.debug('From: %s\n' % user)
76 ui.debug('From: %s\n' % user)
77 diffs_seen = 0
77 diffs_seen = 0
78 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
78 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
79 message = ''
79 message = ''
80 for part in msg.walk():
80 for part in msg.walk():
81 content_type = part.get_content_type()
81 content_type = part.get_content_type()
82 ui.debug('Content-Type: %s\n' % content_type)
82 ui.debug('Content-Type: %s\n' % content_type)
83 if content_type not in ok_types:
83 if content_type not in ok_types:
84 continue
84 continue
85 payload = part.get_payload(decode=True)
85 payload = part.get_payload(decode=True)
86 m = diffre.search(payload)
86 m = diffre.search(payload)
87 if m:
87 if m:
88 hgpatch = False
88 hgpatch = False
89 ignoretext = False
89 ignoretext = False
90
90
91 ui.debug(_('found patch at byte %d\n') % m.start(0))
91 ui.debug(_('found patch at byte %d\n') % m.start(0))
92 diffs_seen += 1
92 diffs_seen += 1
93 cfp = cStringIO.StringIO()
93 cfp = cStringIO.StringIO()
94 for line in payload[:m.start(0)].splitlines():
94 for line in payload[:m.start(0)].splitlines():
95 if line.startswith('# HG changeset patch'):
95 if line.startswith('# HG changeset patch'):
96 ui.debug(_('patch generated by hg export\n'))
96 ui.debug(_('patch generated by hg export\n'))
97 hgpatch = True
97 hgpatch = True
98 # drop earlier commit message content
98 # drop earlier commit message content
99 cfp.seek(0)
99 cfp.seek(0)
100 cfp.truncate()
100 cfp.truncate()
101 subject = None
101 subject = None
102 elif hgpatch:
102 elif hgpatch:
103 if line.startswith('# User '):
103 if line.startswith('# User '):
104 user = line[7:]
104 user = line[7:]
105 ui.debug('From: %s\n' % user)
105 ui.debug('From: %s\n' % user)
106 elif line.startswith("# Date "):
106 elif line.startswith("# Date "):
107 date = line[7:]
107 date = line[7:]
108 elif line.startswith("# Branch "):
108 elif line.startswith("# Branch "):
109 branch = line[9:]
109 branch = line[9:]
110 elif line.startswith("# Node ID "):
110 elif line.startswith("# Node ID "):
111 nodeid = line[10:]
111 nodeid = line[10:]
112 elif line.startswith("# Parent "):
112 elif line.startswith("# Parent "):
113 parents.append(line[10:])
113 parents.append(line[10:])
114 elif line == '---' and 'git-send-email' in msg['X-Mailer']:
114 elif line == '---' and 'git-send-email' in msg['X-Mailer']:
115 ignoretext = True
115 ignoretext = True
116 if not line.startswith('# ') and not ignoretext:
116 if not line.startswith('# ') and not ignoretext:
117 cfp.write(line)
117 cfp.write(line)
118 cfp.write('\n')
118 cfp.write('\n')
119 message = cfp.getvalue()
119 message = cfp.getvalue()
120 if tmpfp:
120 if tmpfp:
121 tmpfp.write(payload)
121 tmpfp.write(payload)
122 if not payload.endswith('\n'):
122 if not payload.endswith('\n'):
123 tmpfp.write('\n')
123 tmpfp.write('\n')
124 elif not diffs_seen and message and content_type == 'text/plain':
124 elif not diffs_seen and message and content_type == 'text/plain':
125 message += '\n' + payload
125 message += '\n' + payload
126 except:
126 except:
127 tmpfp.close()
127 tmpfp.close()
128 os.unlink(tmpname)
128 os.unlink(tmpname)
129 raise
129 raise
130
130
131 if subject and not message.startswith(subject):
131 if subject and not message.startswith(subject):
132 message = '%s\n%s' % (subject, message)
132 message = '%s\n%s' % (subject, message)
133 tmpfp.close()
133 tmpfp.close()
134 if not diffs_seen:
134 if not diffs_seen:
135 os.unlink(tmpname)
135 os.unlink(tmpname)
136 return None, message, user, date, branch, None, None, None
136 return None, message, user, date, branch, None, None, None
137 p1 = parents and parents.pop(0) or None
137 p1 = parents and parents.pop(0) or None
138 p2 = parents and parents.pop(0) or None
138 p2 = parents and parents.pop(0) or None
139 return tmpname, message, user, date, branch, nodeid, p1, p2
139 return tmpname, message, user, date, branch, nodeid, p1, p2
140
140
141 GP_PATCH = 1 << 0 # we have to run patch
141 GP_PATCH = 1 << 0 # we have to run patch
142 GP_FILTER = 1 << 1 # there's some copy/rename operation
142 GP_FILTER = 1 << 1 # there's some copy/rename operation
143 GP_BINARY = 1 << 2 # there's a binary patch
143 GP_BINARY = 1 << 2 # there's a binary patch
144
144
145 def readgitpatch(fp, firstline=None):
145 def readgitpatch(fp, firstline=None):
146 """extract git-style metadata about patches from <patchname>"""
146 """extract git-style metadata about patches from <patchname>"""
147 class gitpatch:
147 class gitpatch:
148 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
148 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
149 def __init__(self, path):
149 def __init__(self, path):
150 self.path = path
150 self.path = path
151 self.oldpath = None
151 self.oldpath = None
152 self.mode = None
152 self.mode = None
153 self.op = 'MODIFY'
153 self.op = 'MODIFY'
154 self.copymod = False
154 self.copymod = False
155 self.lineno = 0
155 self.lineno = 0
156 self.binary = False
156 self.binary = False
157
157
158 def reader(fp, firstline):
158 def reader(fp, firstline):
159 if firstline is not None:
159 if firstline is not None:
160 yield firstline
160 yield firstline
161 for line in fp:
161 for line in fp:
162 yield line
162 yield line
163
163
164 # Filter patch for git information
164 # Filter patch for git information
165 gitre = re.compile('diff --git a/(.*) b/(.*)')
165 gitre = re.compile('diff --git a/(.*) b/(.*)')
166 gp = None
166 gp = None
167 gitpatches = []
167 gitpatches = []
168 # Can have a git patch with only metadata, causing patch to complain
168 # Can have a git patch with only metadata, causing patch to complain
169 dopatch = 0
169 dopatch = 0
170
170
171 lineno = 0
171 lineno = 0
172 for line in reader(fp, firstline):
172 for line in reader(fp, firstline):
173 lineno += 1
173 lineno += 1
174 if line.startswith('diff --git'):
174 if line.startswith('diff --git'):
175 m = gitre.match(line)
175 m = gitre.match(line)
176 if m:
176 if m:
177 if gp:
177 if gp:
178 gitpatches.append(gp)
178 gitpatches.append(gp)
179 src, dst = m.group(1, 2)
179 src, dst = m.group(1, 2)
180 gp = gitpatch(dst)
180 gp = gitpatch(dst)
181 gp.lineno = lineno
181 gp.lineno = lineno
182 elif gp:
182 elif gp:
183 if line.startswith('--- '):
183 if line.startswith('--- '):
184 if gp.op in ('COPY', 'RENAME'):
184 if gp.op in ('COPY', 'RENAME'):
185 gp.copymod = True
185 gp.copymod = True
186 dopatch |= GP_FILTER
186 dopatch |= GP_FILTER
187 gitpatches.append(gp)
187 gitpatches.append(gp)
188 gp = None
188 gp = None
189 dopatch |= GP_PATCH
189 dopatch |= GP_PATCH
190 continue
190 continue
191 if line.startswith('rename from '):
191 if line.startswith('rename from '):
192 gp.op = 'RENAME'
192 gp.op = 'RENAME'
193 gp.oldpath = line[12:].rstrip()
193 gp.oldpath = line[12:].rstrip()
194 elif line.startswith('rename to '):
194 elif line.startswith('rename to '):
195 gp.path = line[10:].rstrip()
195 gp.path = line[10:].rstrip()
196 elif line.startswith('copy from '):
196 elif line.startswith('copy from '):
197 gp.op = 'COPY'
197 gp.op = 'COPY'
198 gp.oldpath = line[10:].rstrip()
198 gp.oldpath = line[10:].rstrip()
199 elif line.startswith('copy to '):
199 elif line.startswith('copy to '):
200 gp.path = line[8:].rstrip()
200 gp.path = line[8:].rstrip()
201 elif line.startswith('deleted file'):
201 elif line.startswith('deleted file'):
202 gp.op = 'DELETE'
202 gp.op = 'DELETE'
203 elif line.startswith('new file mode '):
203 elif line.startswith('new file mode '):
204 gp.op = 'ADD'
204 gp.op = 'ADD'
205 gp.mode = int(line.rstrip()[-6:], 8)
205 gp.mode = int(line.rstrip()[-6:], 8)
206 elif line.startswith('new mode '):
206 elif line.startswith('new mode '):
207 gp.mode = int(line.rstrip()[-6:], 8)
207 gp.mode = int(line.rstrip()[-6:], 8)
208 elif line.startswith('GIT binary patch'):
208 elif line.startswith('GIT binary patch'):
209 dopatch |= GP_BINARY
209 dopatch |= GP_BINARY
210 gp.binary = True
210 gp.binary = True
211 if gp:
211 if gp:
212 gitpatches.append(gp)
212 gitpatches.append(gp)
213
213
214 if not gitpatches:
214 if not gitpatches:
215 dopatch = GP_PATCH
215 dopatch = GP_PATCH
216
216
217 return (dopatch, gitpatches)
217 return (dopatch, gitpatches)
218
218
219 def patch(patchname, ui, strip=1, cwd=None, files={}):
219 def patch(patchname, ui, strip=1, cwd=None, files={}):
220 """apply <patchname> to the working directory.
220 """apply <patchname> to the working directory.
221 returns whether patch was applied with fuzz factor."""
221 returns whether patch was applied with fuzz factor."""
222 patcher = ui.config('ui', 'patch')
222 patcher = ui.config('ui', 'patch')
223 args = []
223 args = []
224 try:
224 try:
225 if patcher:
225 if patcher:
226 return externalpatch(patcher, args, patchname, ui, strip, cwd,
226 return externalpatch(patcher, args, patchname, ui, strip, cwd,
227 files)
227 files)
228 else:
228 else:
229 try:
229 try:
230 return internalpatch(patchname, ui, strip, cwd, files)
230 return internalpatch(patchname, ui, strip, cwd, files)
231 except NoHunks:
231 except NoHunks:
232 patcher = util.find_exe('gpatch') or util.find_exe('patch')
232 patcher = util.find_exe('gpatch') or util.find_exe('patch')
233 ui.debug('no valid hunks found; trying with %r instead\n' %
233 ui.debug('no valid hunks found; trying with %r instead\n' %
234 patcher)
234 patcher)
235 if util.needbinarypatch():
235 if util.needbinarypatch():
236 args.append('--binary')
236 args.append('--binary')
237 return externalpatch(patcher, args, patchname, ui, strip, cwd,
237 return externalpatch(patcher, args, patchname, ui, strip, cwd,
238 files)
238 files)
239 except PatchError, err:
239 except PatchError, err:
240 s = str(err)
240 s = str(err)
241 if s:
241 if s:
242 raise util.Abort(s)
242 raise util.Abort(s)
243 else:
243 else:
244 raise util.Abort(_('patch failed to apply'))
244 raise util.Abort(_('patch failed to apply'))
245
245
246 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
246 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
247 """use <patcher> to apply <patchname> to the working directory.
247 """use <patcher> to apply <patchname> to the working directory.
248 returns whether patch was applied with fuzz factor."""
248 returns whether patch was applied with fuzz factor."""
249
249
250 fuzz = False
250 fuzz = False
251 if cwd:
251 if cwd:
252 args.append('-d %s' % util.shellquote(cwd))
252 args.append('-d %s' % util.shellquote(cwd))
253 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
253 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
254 util.shellquote(patchname)))
254 util.shellquote(patchname)))
255
255
256 for line in fp:
256 for line in fp:
257 line = line.rstrip()
257 line = line.rstrip()
258 ui.note(line + '\n')
258 ui.note(line + '\n')
259 if line.startswith('patching file '):
259 if line.startswith('patching file '):
260 pf = util.parse_patch_output(line)
260 pf = util.parse_patch_output(line)
261 printed_file = False
261 printed_file = False
262 files.setdefault(pf, (None, None))
262 files.setdefault(pf, (None, None))
263 elif line.find('with fuzz') >= 0:
263 elif line.find('with fuzz') >= 0:
264 fuzz = True
264 fuzz = True
265 if not printed_file:
265 if not printed_file:
266 ui.warn(pf + '\n')
266 ui.warn(pf + '\n')
267 printed_file = True
267 printed_file = True
268 ui.warn(line + '\n')
268 ui.warn(line + '\n')
269 elif line.find('saving rejects to file') >= 0:
269 elif line.find('saving rejects to file') >= 0:
270 ui.warn(line + '\n')
270 ui.warn(line + '\n')
271 elif line.find('FAILED') >= 0:
271 elif line.find('FAILED') >= 0:
272 if not printed_file:
272 if not printed_file:
273 ui.warn(pf + '\n')
273 ui.warn(pf + '\n')
274 printed_file = True
274 printed_file = True
275 ui.warn(line + '\n')
275 ui.warn(line + '\n')
276 code = fp.close()
276 code = fp.close()
277 if code:
277 if code:
278 raise PatchError(_("patch command failed: %s") %
278 raise PatchError(_("patch command failed: %s") %
279 util.explain_exit(code)[0])
279 util.explain_exit(code)[0])
280 return fuzz
280 return fuzz
281
281
282 def internalpatch(patchobj, ui, strip, cwd, files={}):
282 def internalpatch(patchobj, ui, strip, cwd, files={}):
283 """use builtin patch to apply <patchobj> to the working directory.
283 """use builtin patch to apply <patchobj> to the working directory.
284 returns whether patch was applied with fuzz factor."""
284 returns whether patch was applied with fuzz factor."""
285 try:
285 try:
286 fp = file(patchobj, 'rb')
286 fp = file(patchobj, 'rb')
287 except TypeError:
287 except TypeError:
288 fp = patchobj
288 fp = patchobj
289 if cwd:
289 if cwd:
290 curdir = os.getcwd()
290 curdir = os.getcwd()
291 os.chdir(cwd)
291 os.chdir(cwd)
292 try:
292 try:
293 ret = applydiff(ui, fp, files, strip=strip)
293 ret = applydiff(ui, fp, files, strip=strip)
294 finally:
294 finally:
295 if cwd:
295 if cwd:
296 os.chdir(curdir)
296 os.chdir(curdir)
297 if ret < 0:
297 if ret < 0:
298 raise PatchError
298 raise PatchError
299 return ret > 0
299 return ret > 0
300
300
301 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
301 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
302 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
302 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
303 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
303 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
304
304
305 class patchfile:
305 class patchfile:
306 def __init__(self, ui, fname):
306 def __init__(self, ui, fname):
307 self.fname = fname
307 self.fname = fname
308 self.ui = ui
308 self.ui = ui
309 try:
309 try:
310 fp = file(fname, 'rb')
310 fp = file(fname, 'rb')
311 self.lines = fp.readlines()
311 self.lines = fp.readlines()
312 self.exists = True
312 self.exists = True
313 except IOError:
313 except IOError:
314 dirname = os.path.dirname(fname)
314 dirname = os.path.dirname(fname)
315 if dirname and not os.path.isdir(dirname):
315 if dirname and not os.path.isdir(dirname):
316 dirs = dirname.split(os.path.sep)
316 dirs = dirname.split(os.path.sep)
317 d = ""
317 d = ""
318 for x in dirs:
318 for x in dirs:
319 d = os.path.join(d, x)
319 d = os.path.join(d, x)
320 if not os.path.isdir(d):
320 if not os.path.isdir(d):
321 os.mkdir(d)
321 os.mkdir(d)
322 self.lines = []
322 self.lines = []
323 self.exists = False
323 self.exists = False
324
324
325 self.hash = {}
325 self.hash = {}
326 self.dirty = 0
326 self.dirty = 0
327 self.offset = 0
327 self.offset = 0
328 self.rej = []
328 self.rej = []
329 self.fileprinted = False
329 self.fileprinted = False
330 self.printfile(False)
330 self.printfile(False)
331 self.hunks = 0
331 self.hunks = 0
332
332
333 def printfile(self, warn):
333 def printfile(self, warn):
334 if self.fileprinted:
334 if self.fileprinted:
335 return
335 return
336 if warn or self.ui.verbose:
336 if warn or self.ui.verbose:
337 self.fileprinted = True
337 self.fileprinted = True
338 s = _("patching file %s\n") % self.fname
338 s = _("patching file %s\n") % self.fname
339 if warn:
339 if warn:
340 self.ui.warn(s)
340 self.ui.warn(s)
341 else:
341 else:
342 self.ui.note(s)
342 self.ui.note(s)
343
343
344
344
345 def findlines(self, l, linenum):
345 def findlines(self, l, linenum):
346 # looks through the hash and finds candidate lines. The
346 # looks through the hash and finds candidate lines. The
347 # result is a list of line numbers sorted based on distance
347 # result is a list of line numbers sorted based on distance
348 # from linenum
348 # from linenum
349 def sorter(a, b):
349 def sorter(a, b):
350 vala = abs(a - linenum)
350 vala = abs(a - linenum)
351 valb = abs(b - linenum)
351 valb = abs(b - linenum)
352 return cmp(vala, valb)
352 return cmp(vala, valb)
353
353
354 try:
354 try:
355 cand = self.hash[l]
355 cand = self.hash[l]
356 except:
356 except:
357 return []
357 return []
358
358
359 if len(cand) > 1:
359 if len(cand) > 1:
360 # resort our list of potentials forward then back.
360 # resort our list of potentials forward then back.
361 cand.sort(cmp=sorter)
361 cand.sort(cmp=sorter)
362 return cand
362 return cand
363
363
364 def hashlines(self):
364 def hashlines(self):
365 self.hash = {}
365 self.hash = {}
366 for x in xrange(len(self.lines)):
366 for x in xrange(len(self.lines)):
367 s = self.lines[x]
367 s = self.lines[x]
368 self.hash.setdefault(s, []).append(x)
368 self.hash.setdefault(s, []).append(x)
369
369
370 def write_rej(self):
370 def write_rej(self):
371 # our rejects are a little different from patch(1). This always
371 # our rejects are a little different from patch(1). This always
372 # creates rejects in the same form as the original patch. A file
372 # creates rejects in the same form as the original patch. A file
373 # header is inserted so that you can run the reject through patch again
373 # header is inserted so that you can run the reject through patch again
374 # without having to type the filename.
374 # without having to type the filename.
375
375
376 if not self.rej:
376 if not self.rej:
377 return
377 return
378 if self.hunks != 1:
378 if self.hunks != 1:
379 hunkstr = "s"
379 hunkstr = "s"
380 else:
380 else:
381 hunkstr = ""
381 hunkstr = ""
382
382
383 fname = self.fname + ".rej"
383 fname = self.fname + ".rej"
384 self.ui.warn(
384 self.ui.warn(
385 _("%d out of %d hunk%s FAILED -- saving rejects to file %s\n") %
385 _("%d out of %d hunk%s FAILED -- saving rejects to file %s\n") %
386 (len(self.rej), self.hunks, hunkstr, fname))
386 (len(self.rej), self.hunks, hunkstr, fname))
387 try: os.unlink(fname)
387 try: os.unlink(fname)
388 except:
388 except:
389 pass
389 pass
390 fp = file(fname, 'wb')
390 fp = file(fname, 'wb')
391 base = os.path.basename(self.fname)
391 base = os.path.basename(self.fname)
392 fp.write("--- %s\n+++ %s\n" % (base, base))
392 fp.write("--- %s\n+++ %s\n" % (base, base))
393 for x in self.rej:
393 for x in self.rej:
394 for l in x.hunk:
394 for l in x.hunk:
395 fp.write(l)
395 fp.write(l)
396 if l[-1] != '\n':
396 if l[-1] != '\n':
397 fp.write("\n\ No newline at end of file\n")
397 fp.write("\n\ No newline at end of file\n")
398
398
399 def write(self, dest=None):
399 def write(self, dest=None):
400 if self.dirty:
400 if self.dirty:
401 if not dest:
401 if not dest:
402 dest = self.fname
402 dest = self.fname
403 st = None
403 st = None
404 try:
404 try:
405 st = os.lstat(dest)
405 st = os.lstat(dest)
406 if st.st_nlink > 1:
406 if st.st_nlink > 1:
407 os.unlink(dest)
407 os.unlink(dest)
408 except: pass
408 except: pass
409 fp = file(dest, 'wb')
409 fp = file(dest, 'wb')
410 if st:
410 if st:
411 os.chmod(dest, st.st_mode)
411 os.chmod(dest, st.st_mode)
412 fp.writelines(self.lines)
412 fp.writelines(self.lines)
413 fp.close()
413 fp.close()
414
414
415 def close(self):
415 def close(self):
416 self.write()
416 self.write()
417 self.write_rej()
417 self.write_rej()
418
418
419 def apply(self, h, reverse):
419 def apply(self, h, reverse):
420 if not h.complete():
420 if not h.complete():
421 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
421 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
422 (h.number, h.desc, len(h.a), h.lena, len(h.b),
422 (h.number, h.desc, len(h.a), h.lena, len(h.b),
423 h.lenb))
423 h.lenb))
424
424
425 self.hunks += 1
425 self.hunks += 1
426 if reverse:
426 if reverse:
427 h.reverse()
427 h.reverse()
428
428
429 if self.exists and h.createfile():
429 if self.exists and h.createfile():
430 self.ui.warn(_("file %s already exists\n") % self.fname)
430 self.ui.warn(_("file %s already exists\n") % self.fname)
431 self.rej.append(h)
431 self.rej.append(h)
432 return -1
432 return -1
433
433
434 if isinstance(h, binhunk):
434 if isinstance(h, binhunk):
435 if h.rmfile():
435 if h.rmfile():
436 os.unlink(self.fname)
436 os.unlink(self.fname)
437 else:
437 else:
438 self.lines[:] = h.new()
438 self.lines[:] = h.new()
439 self.offset += len(h.new())
439 self.offset += len(h.new())
440 self.dirty = 1
440 self.dirty = 1
441 return 0
441 return 0
442
442
443 # fast case first, no offsets, no fuzz
443 # fast case first, no offsets, no fuzz
444 old = h.old()
444 old = h.old()
445 # patch starts counting at 1 unless we are adding the file
445 # patch starts counting at 1 unless we are adding the file
446 if h.starta == 0:
446 if h.starta == 0:
447 start = 0
447 start = 0
448 else:
448 else:
449 start = h.starta + self.offset - 1
449 start = h.starta + self.offset - 1
450 orig_start = start
450 orig_start = start
451 if diffhelpers.testhunk(old, self.lines, start) == 0:
451 if diffhelpers.testhunk(old, self.lines, start) == 0:
452 if h.rmfile():
452 if h.rmfile():
453 os.unlink(self.fname)
453 os.unlink(self.fname)
454 else:
454 else:
455 self.lines[start : start + h.lena] = h.new()
455 self.lines[start : start + h.lena] = h.new()
456 self.offset += h.lenb - h.lena
456 self.offset += h.lenb - h.lena
457 self.dirty = 1
457 self.dirty = 1
458 return 0
458 return 0
459
459
460 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
460 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
461 self.hashlines()
461 self.hashlines()
462 if h.hunk[-1][0] != ' ':
462 if h.hunk[-1][0] != ' ':
463 # if the hunk tried to put something at the bottom of the file
463 # if the hunk tried to put something at the bottom of the file
464 # override the start line and use eof here
464 # override the start line and use eof here
465 search_start = len(self.lines)
465 search_start = len(self.lines)
466 else:
466 else:
467 search_start = orig_start
467 search_start = orig_start
468
468
469 for fuzzlen in xrange(3):
469 for fuzzlen in xrange(3):
470 for toponly in [ True, False ]:
470 for toponly in [ True, False ]:
471 old = h.old(fuzzlen, toponly)
471 old = h.old(fuzzlen, toponly)
472
472
473 cand = self.findlines(old[0][1:], search_start)
473 cand = self.findlines(old[0][1:], search_start)
474 for l in cand:
474 for l in cand:
475 if diffhelpers.testhunk(old, self.lines, l) == 0:
475 if diffhelpers.testhunk(old, self.lines, l) == 0:
476 newlines = h.new(fuzzlen, toponly)
476 newlines = h.new(fuzzlen, toponly)
477 self.lines[l : l + len(old)] = newlines
477 self.lines[l : l + len(old)] = newlines
478 self.offset += len(newlines) - len(old)
478 self.offset += len(newlines) - len(old)
479 self.dirty = 1
479 self.dirty = 1
480 if fuzzlen:
480 if fuzzlen:
481 fuzzstr = "with fuzz %d " % fuzzlen
481 fuzzstr = "with fuzz %d " % fuzzlen
482 f = self.ui.warn
482 f = self.ui.warn
483 self.printfile(True)
483 self.printfile(True)
484 else:
484 else:
485 fuzzstr = ""
485 fuzzstr = ""
486 f = self.ui.note
486 f = self.ui.note
487 offset = l - orig_start - fuzzlen
487 offset = l - orig_start - fuzzlen
488 if offset == 1:
488 if offset == 1:
489 linestr = "line"
489 linestr = "line"
490 else:
490 else:
491 linestr = "lines"
491 linestr = "lines"
492 f(_("Hunk #%d succeeded at %d %s(offset %d %s).\n") %
492 f(_("Hunk #%d succeeded at %d %s(offset %d %s).\n") %
493 (h.number, l+1, fuzzstr, offset, linestr))
493 (h.number, l+1, fuzzstr, offset, linestr))
494 return fuzzlen
494 return fuzzlen
495 self.printfile(True)
495 self.printfile(True)
496 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
496 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
497 self.rej.append(h)
497 self.rej.append(h)
498 return -1
498 return -1
499
499
500 class hunk:
500 class hunk:
501 def __init__(self, desc, num, lr, context):
501 def __init__(self, desc, num, lr, context):
502 self.number = num
502 self.number = num
503 self.desc = desc
503 self.desc = desc
504 self.hunk = [ desc ]
504 self.hunk = [ desc ]
505 self.a = []
505 self.a = []
506 self.b = []
506 self.b = []
507 if context:
507 if context:
508 self.read_context_hunk(lr)
508 self.read_context_hunk(lr)
509 else:
509 else:
510 self.read_unified_hunk(lr)
510 self.read_unified_hunk(lr)
511
511
512 def read_unified_hunk(self, lr):
512 def read_unified_hunk(self, lr):
513 m = unidesc.match(self.desc)
513 m = unidesc.match(self.desc)
514 if not m:
514 if not m:
515 raise PatchError(_("bad hunk #%d") % self.number)
515 raise PatchError(_("bad hunk #%d") % self.number)
516 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
516 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
517 if self.lena == None:
517 if self.lena == None:
518 self.lena = 1
518 self.lena = 1
519 else:
519 else:
520 self.lena = int(self.lena)
520 self.lena = int(self.lena)
521 if self.lenb == None:
521 if self.lenb == None:
522 self.lenb = 1
522 self.lenb = 1
523 else:
523 else:
524 self.lenb = int(self.lenb)
524 self.lenb = int(self.lenb)
525 self.starta = int(self.starta)
525 self.starta = int(self.starta)
526 self.startb = int(self.startb)
526 self.startb = int(self.startb)
527 diffhelpers.addlines(lr.fp, self.hunk, self.lena, self.lenb, self.a, self.b)
527 diffhelpers.addlines(lr.fp, self.hunk, self.lena, self.lenb, self.a, self.b)
528 # if we hit eof before finishing out the hunk, the last line will
528 # if we hit eof before finishing out the hunk, the last line will
529 # be zero length. Lets try to fix it up.
529 # be zero length. Lets try to fix it up.
530 while len(self.hunk[-1]) == 0:
530 while len(self.hunk[-1]) == 0:
531 del self.hunk[-1]
531 del self.hunk[-1]
532 del self.a[-1]
532 del self.a[-1]
533 del self.b[-1]
533 del self.b[-1]
534 self.lena -= 1
534 self.lena -= 1
535 self.lenb -= 1
535 self.lenb -= 1
536
536
537 def read_context_hunk(self, lr):
537 def read_context_hunk(self, lr):
538 self.desc = lr.readline()
538 self.desc = lr.readline()
539 m = contextdesc.match(self.desc)
539 m = contextdesc.match(self.desc)
540 if not m:
540 if not m:
541 raise PatchError(_("bad hunk #%d") % self.number)
541 raise PatchError(_("bad hunk #%d") % self.number)
542 foo, self.starta, foo2, aend, foo3 = m.groups()
542 foo, self.starta, foo2, aend, foo3 = m.groups()
543 self.starta = int(self.starta)
543 self.starta = int(self.starta)
544 if aend == None:
544 if aend == None:
545 aend = self.starta
545 aend = self.starta
546 self.lena = int(aend) - self.starta
546 self.lena = int(aend) - self.starta
547 if self.starta:
547 if self.starta:
548 self.lena += 1
548 self.lena += 1
549 for x in xrange(self.lena):
549 for x in xrange(self.lena):
550 l = lr.readline()
550 l = lr.readline()
551 if l.startswith('---'):
551 if l.startswith('---'):
552 lr.push(l)
552 lr.push(l)
553 break
553 break
554 s = l[2:]
554 s = l[2:]
555 if l.startswith('- ') or l.startswith('! '):
555 if l.startswith('- ') or l.startswith('! '):
556 u = '-' + s
556 u = '-' + s
557 elif l.startswith(' '):
557 elif l.startswith(' '):
558 u = ' ' + s
558 u = ' ' + s
559 else:
559 else:
560 raise PatchError(_("bad hunk #%d old text line %d") %
560 raise PatchError(_("bad hunk #%d old text line %d") %
561 (self.number, x))
561 (self.number, x))
562 self.a.append(u)
562 self.a.append(u)
563 self.hunk.append(u)
563 self.hunk.append(u)
564
564
565 l = lr.readline()
565 l = lr.readline()
566 if l.startswith('\ '):
566 if l.startswith('\ '):
567 s = self.a[-1][:-1]
567 s = self.a[-1][:-1]
568 self.a[-1] = s
568 self.a[-1] = s
569 self.hunk[-1] = s
569 self.hunk[-1] = s
570 l = lr.readline()
570 l = lr.readline()
571 m = contextdesc.match(l)
571 m = contextdesc.match(l)
572 if not m:
572 if not m:
573 raise PatchError(_("bad hunk #%d") % self.number)
573 raise PatchError(_("bad hunk #%d") % self.number)
574 foo, self.startb, foo2, bend, foo3 = m.groups()
574 foo, self.startb, foo2, bend, foo3 = m.groups()
575 self.startb = int(self.startb)
575 self.startb = int(self.startb)
576 if bend == None:
576 if bend == None:
577 bend = self.startb
577 bend = self.startb
578 self.lenb = int(bend) - self.startb
578 self.lenb = int(bend) - self.startb
579 if self.startb:
579 if self.startb:
580 self.lenb += 1
580 self.lenb += 1
581 hunki = 1
581 hunki = 1
582 for x in xrange(self.lenb):
582 for x in xrange(self.lenb):
583 l = lr.readline()
583 l = lr.readline()
584 if l.startswith('\ '):
584 if l.startswith('\ '):
585 s = self.b[-1][:-1]
585 s = self.b[-1][:-1]
586 self.b[-1] = s
586 self.b[-1] = s
587 self.hunk[hunki-1] = s
587 self.hunk[hunki-1] = s
588 continue
588 continue
589 if not l:
589 if not l:
590 lr.push(l)
590 lr.push(l)
591 break
591 break
592 s = l[2:]
592 s = l[2:]
593 if l.startswith('+ ') or l.startswith('! '):
593 if l.startswith('+ ') or l.startswith('! '):
594 u = '+' + s
594 u = '+' + s
595 elif l.startswith(' '):
595 elif l.startswith(' '):
596 u = ' ' + s
596 u = ' ' + s
597 elif len(self.b) == 0:
597 elif len(self.b) == 0:
598 # this can happen when the hunk does not add any lines
598 # this can happen when the hunk does not add any lines
599 lr.push(l)
599 lr.push(l)
600 break
600 break
601 else:
601 else:
602 raise PatchError(_("bad hunk #%d old text line %d") %
602 raise PatchError(_("bad hunk #%d old text line %d") %
603 (self.number, x))
603 (self.number, x))
604 self.b.append(s)
604 self.b.append(s)
605 while True:
605 while True:
606 if hunki >= len(self.hunk):
606 if hunki >= len(self.hunk):
607 h = ""
607 h = ""
608 else:
608 else:
609 h = self.hunk[hunki]
609 h = self.hunk[hunki]
610 hunki += 1
610 hunki += 1
611 if h == u:
611 if h == u:
612 break
612 break
613 elif h.startswith('-'):
613 elif h.startswith('-'):
614 continue
614 continue
615 else:
615 else:
616 self.hunk.insert(hunki-1, u)
616 self.hunk.insert(hunki-1, u)
617 break
617 break
618
618
619 if not self.a:
619 if not self.a:
620 # this happens when lines were only added to the hunk
620 # this happens when lines were only added to the hunk
621 for x in self.hunk:
621 for x in self.hunk:
622 if x.startswith('-') or x.startswith(' '):
622 if x.startswith('-') or x.startswith(' '):
623 self.a.append(x)
623 self.a.append(x)
624 if not self.b:
624 if not self.b:
625 # this happens when lines were only deleted from the hunk
625 # this happens when lines were only deleted from the hunk
626 for x in self.hunk:
626 for x in self.hunk:
627 if x.startswith('+') or x.startswith(' '):
627 if x.startswith('+') or x.startswith(' '):
628 self.b.append(x[1:])
628 self.b.append(x[1:])
629 # @@ -start,len +start,len @@
629 # @@ -start,len +start,len @@
630 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
630 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
631 self.startb, self.lenb)
631 self.startb, self.lenb)
632 self.hunk[0] = self.desc
632 self.hunk[0] = self.desc
633
633
634 def reverse(self):
634 def reverse(self):
635 origlena = self.lena
635 origlena = self.lena
636 origstarta = self.starta
636 origstarta = self.starta
637 self.lena = self.lenb
637 self.lena = self.lenb
638 self.starta = self.startb
638 self.starta = self.startb
639 self.lenb = origlena
639 self.lenb = origlena
640 self.startb = origstarta
640 self.startb = origstarta
641 self.a = []
641 self.a = []
642 self.b = []
642 self.b = []
643 # self.hunk[0] is the @@ description
643 # self.hunk[0] is the @@ description
644 for x in xrange(1, len(self.hunk)):
644 for x in xrange(1, len(self.hunk)):
645 o = self.hunk[x]
645 o = self.hunk[x]
646 if o.startswith('-'):
646 if o.startswith('-'):
647 n = '+' + o[1:]
647 n = '+' + o[1:]
648 self.b.append(o[1:])
648 self.b.append(o[1:])
649 elif o.startswith('+'):
649 elif o.startswith('+'):
650 n = '-' + o[1:]
650 n = '-' + o[1:]
651 self.a.append(n)
651 self.a.append(n)
652 else:
652 else:
653 n = o
653 n = o
654 self.b.append(o[1:])
654 self.b.append(o[1:])
655 self.a.append(o)
655 self.a.append(o)
656 self.hunk[x] = o
656 self.hunk[x] = o
657
657
658 def fix_newline(self):
658 def fix_newline(self):
659 diffhelpers.fix_newline(self.hunk, self.a, self.b)
659 diffhelpers.fix_newline(self.hunk, self.a, self.b)
660
660
661 def complete(self):
661 def complete(self):
662 return len(self.a) == self.lena and len(self.b) == self.lenb
662 return len(self.a) == self.lena and len(self.b) == self.lenb
663
663
664 def createfile(self):
664 def createfile(self):
665 return self.starta == 0 and self.lena == 0
665 return self.starta == 0 and self.lena == 0
666
666
667 def rmfile(self):
667 def rmfile(self):
668 return self.startb == 0 and self.lenb == 0
668 return self.startb == 0 and self.lenb == 0
669
669
670 def fuzzit(self, l, fuzz, toponly):
670 def fuzzit(self, l, fuzz, toponly):
671 # this removes context lines from the top and bottom of list 'l'. It
671 # this removes context lines from the top and bottom of list 'l'. It
672 # checks the hunk to make sure only context lines are removed, and then
672 # checks the hunk to make sure only context lines are removed, and then
673 # returns a new shortened list of lines.
673 # returns a new shortened list of lines.
674 fuzz = min(fuzz, len(l)-1)
674 fuzz = min(fuzz, len(l)-1)
675 if fuzz:
675 if fuzz:
676 top = 0
676 top = 0
677 bot = 0
677 bot = 0
678 hlen = len(self.hunk)
678 hlen = len(self.hunk)
679 for x in xrange(hlen-1):
679 for x in xrange(hlen-1):
680 # the hunk starts with the @@ line, so use x+1
680 # the hunk starts with the @@ line, so use x+1
681 if self.hunk[x+1][0] == ' ':
681 if self.hunk[x+1][0] == ' ':
682 top += 1
682 top += 1
683 else:
683 else:
684 break
684 break
685 if not toponly:
685 if not toponly:
686 for x in xrange(hlen-1):
686 for x in xrange(hlen-1):
687 if self.hunk[hlen-bot-1][0] == ' ':
687 if self.hunk[hlen-bot-1][0] == ' ':
688 bot += 1
688 bot += 1
689 else:
689 else:
690 break
690 break
691
691
692 # top and bot now count context in the hunk
692 # top and bot now count context in the hunk
693 # adjust them if either one is short
693 # adjust them if either one is short
694 context = max(top, bot, 3)
694 context = max(top, bot, 3)
695 if bot < context:
695 if bot < context:
696 bot = max(0, fuzz - (context - bot))
696 bot = max(0, fuzz - (context - bot))
697 else:
697 else:
698 bot = min(fuzz, bot)
698 bot = min(fuzz, bot)
699 if top < context:
699 if top < context:
700 top = max(0, fuzz - (context - top))
700 top = max(0, fuzz - (context - top))
701 else:
701 else:
702 top = min(fuzz, top)
702 top = min(fuzz, top)
703
703
704 return l[top:len(l)-bot]
704 return l[top:len(l)-bot]
705 return l
705 return l
706
706
707 def old(self, fuzz=0, toponly=False):
707 def old(self, fuzz=0, toponly=False):
708 return self.fuzzit(self.a, fuzz, toponly)
708 return self.fuzzit(self.a, fuzz, toponly)
709
709
710 def newctrl(self):
710 def newctrl(self):
711 res = []
711 res = []
712 for x in self.hunk:
712 for x in self.hunk:
713 c = x[0]
713 c = x[0]
714 if c == ' ' or c == '+':
714 if c == ' ' or c == '+':
715 res.append(x)
715 res.append(x)
716 return res
716 return res
717
717
718 def new(self, fuzz=0, toponly=False):
718 def new(self, fuzz=0, toponly=False):
719 return self.fuzzit(self.b, fuzz, toponly)
719 return self.fuzzit(self.b, fuzz, toponly)
720
720
721 class binhunk:
721 class binhunk:
722 'A binary patch file. Only understands literals so far.'
722 'A binary patch file. Only understands literals so far.'
723 def __init__(self, gitpatch):
723 def __init__(self, gitpatch):
724 self.gitpatch = gitpatch
724 self.gitpatch = gitpatch
725 self.text = None
725 self.text = None
726 self.hunk = ['GIT binary patch\n']
726 self.hunk = ['GIT binary patch\n']
727
727
728 def createfile(self):
728 def createfile(self):
729 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
729 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
730
730
731 def rmfile(self):
731 def rmfile(self):
732 return self.gitpatch.op == 'DELETE'
732 return self.gitpatch.op == 'DELETE'
733
733
734 def complete(self):
734 def complete(self):
735 return self.text is not None
735 return self.text is not None
736
736
737 def new(self):
737 def new(self):
738 return [self.text]
738 return [self.text]
739
739
740 def extract(self, fp):
740 def extract(self, fp):
741 line = fp.readline()
741 line = fp.readline()
742 self.hunk.append(line)
742 self.hunk.append(line)
743 while line and not line.startswith('literal '):
743 while line and not line.startswith('literal '):
744 line = fp.readline()
744 line = fp.readline()
745 self.hunk.append(line)
745 self.hunk.append(line)
746 if not line:
746 if not line:
747 raise PatchError(_('could not extract binary patch'))
747 raise PatchError(_('could not extract binary patch'))
748 size = int(line[8:].rstrip())
748 size = int(line[8:].rstrip())
749 dec = []
749 dec = []
750 line = fp.readline()
750 line = fp.readline()
751 self.hunk.append(line)
751 self.hunk.append(line)
752 while len(line) > 1:
752 while len(line) > 1:
753 l = line[0]
753 l = line[0]
754 if l <= 'Z' and l >= 'A':
754 if l <= 'Z' and l >= 'A':
755 l = ord(l) - ord('A') + 1
755 l = ord(l) - ord('A') + 1
756 else:
756 else:
757 l = ord(l) - ord('a') + 27
757 l = ord(l) - ord('a') + 27
758 dec.append(base85.b85decode(line[1:-1])[:l])
758 dec.append(base85.b85decode(line[1:-1])[:l])
759 line = fp.readline()
759 line = fp.readline()
760 self.hunk.append(line)
760 self.hunk.append(line)
761 text = zlib.decompress(''.join(dec))
761 text = zlib.decompress(''.join(dec))
762 if len(text) != size:
762 if len(text) != size:
763 raise PatchError(_('binary patch is %d bytes, not %d') %
763 raise PatchError(_('binary patch is %d bytes, not %d') %
764 len(text), size)
764 len(text), size)
765 self.text = text
765 self.text = text
766
766
767 def parsefilename(str):
767 def parsefilename(str):
768 # --- filename \t|space stuff
768 # --- filename \t|space stuff
769 s = str[4:]
769 s = str[4:]
770 i = s.find('\t')
770 i = s.find('\t')
771 if i < 0:
771 if i < 0:
772 i = s.find(' ')
772 i = s.find(' ')
773 if i < 0:
773 if i < 0:
774 return s
774 return s
775 return s[:i]
775 return s[:i]
776
776
777 def selectfile(afile_orig, bfile_orig, hunk, strip, reverse):
777 def selectfile(afile_orig, bfile_orig, hunk, strip, reverse):
778 def pathstrip(path, count=1):
778 def pathstrip(path, count=1):
779 pathlen = len(path)
779 pathlen = len(path)
780 i = 0
780 i = 0
781 if count == 0:
781 if count == 0:
782 return path.rstrip()
782 return path.rstrip()
783 while count > 0:
783 while count > 0:
784 i = path.find('/', i)
784 i = path.find('/', i)
785 if i == -1:
785 if i == -1:
786 raise PatchError(_("unable to strip away %d dirs from %s") %
786 raise PatchError(_("unable to strip away %d dirs from %s") %
787 (count, path))
787 (count, path))
788 i += 1
788 i += 1
789 # consume '//' in the path
789 # consume '//' in the path
790 while i < pathlen - 1 and path[i] == '/':
790 while i < pathlen - 1 and path[i] == '/':
791 i += 1
791 i += 1
792 count -= 1
792 count -= 1
793 return path[i:].rstrip()
793 return path[i:].rstrip()
794
794
795 nulla = afile_orig == "/dev/null"
795 nulla = afile_orig == "/dev/null"
796 nullb = bfile_orig == "/dev/null"
796 nullb = bfile_orig == "/dev/null"
797 afile = pathstrip(afile_orig, strip)
797 afile = pathstrip(afile_orig, strip)
798 gooda = os.path.exists(afile) and not nulla
798 gooda = os.path.exists(afile) and not nulla
799 bfile = pathstrip(bfile_orig, strip)
799 bfile = pathstrip(bfile_orig, strip)
800 if afile == bfile:
800 if afile == bfile:
801 goodb = gooda
801 goodb = gooda
802 else:
802 else:
803 goodb = os.path.exists(bfile) and not nullb
803 goodb = os.path.exists(bfile) and not nullb
804 createfunc = hunk.createfile
804 createfunc = hunk.createfile
805 if reverse:
805 if reverse:
806 createfunc = hunk.rmfile
806 createfunc = hunk.rmfile
807 if not goodb and not gooda and not createfunc():
807 if not goodb and not gooda and not createfunc():
808 raise PatchError(_("unable to find %s or %s for patching") %
808 raise PatchError(_("unable to find %s or %s for patching") %
809 (afile, bfile))
809 (afile, bfile))
810 if gooda and goodb:
810 if gooda and goodb:
811 fname = bfile
811 fname = bfile
812 if afile in bfile:
812 if afile in bfile:
813 fname = afile
813 fname = afile
814 elif gooda:
814 elif gooda:
815 fname = afile
815 fname = afile
816 elif not nullb:
816 elif not nullb:
817 fname = bfile
817 fname = bfile
818 if afile in bfile:
818 if afile in bfile:
819 fname = afile
819 fname = afile
820 elif not nulla:
820 elif not nulla:
821 fname = afile
821 fname = afile
822 return fname
822 return fname
823
823
824 class linereader:
824 class linereader:
825 # simple class to allow pushing lines back into the input stream
825 # simple class to allow pushing lines back into the input stream
826 def __init__(self, fp):
826 def __init__(self, fp):
827 self.fp = fp
827 self.fp = fp
828 self.buf = []
828 self.buf = []
829
829
830 def push(self, line):
830 def push(self, line):
831 self.buf.append(line)
831 self.buf.append(line)
832
832
833 def readline(self):
833 def readline(self):
834 if self.buf:
834 if self.buf:
835 l = self.buf[0]
835 l = self.buf[0]
836 del self.buf[0]
836 del self.buf[0]
837 return l
837 return l
838 return self.fp.readline()
838 return self.fp.readline()
839
839
840 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False,
840 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False,
841 rejmerge=None, updatedir=None):
841 rejmerge=None, updatedir=None):
842 """reads a patch from fp and tries to apply it. The dict 'changed' is
842 """reads a patch from fp and tries to apply it. The dict 'changed' is
843 filled in with all of the filenames changed by the patch. Returns 0
843 filled in with all of the filenames changed by the patch. Returns 0
844 for a clean patch, -1 if any rejects were found and 1 if there was
844 for a clean patch, -1 if any rejects were found and 1 if there was
845 any fuzz."""
845 any fuzz."""
846
846
847 def scangitpatch(fp, firstline, cwd=None):
847 def scangitpatch(fp, firstline, cwd=None):
848 '''git patches can modify a file, then copy that file to
848 '''git patches can modify a file, then copy that file to
849 a new file, but expect the source to be the unmodified form.
849 a new file, but expect the source to be the unmodified form.
850 So we scan the patch looking for that case so we can do
850 So we scan the patch looking for that case so we can do
851 the copies ahead of time.'''
851 the copies ahead of time.'''
852
852
853 pos = 0
853 pos = 0
854 try:
854 try:
855 pos = fp.tell()
855 pos = fp.tell()
856 except IOError:
856 except IOError:
857 fp = cStringIO.StringIO(fp.read())
857 fp = cStringIO.StringIO(fp.read())
858
858
859 (dopatch, gitpatches) = readgitpatch(fp, firstline)
859 (dopatch, gitpatches) = readgitpatch(fp, firstline)
860 for gp in gitpatches:
860 for gp in gitpatches:
861 if gp.copymod:
861 if gp.copymod:
862 copyfile(gp.oldpath, gp.path, basedir=cwd)
862 copyfile(gp.oldpath, gp.path, basedir=cwd)
863
863
864 fp.seek(pos)
864 fp.seek(pos)
865
865
866 return fp, dopatch, gitpatches
866 return fp, dopatch, gitpatches
867
867
868 current_hunk = None
868 current_hunk = None
869 current_file = None
869 current_file = None
870 afile = ""
870 afile = ""
871 bfile = ""
871 bfile = ""
872 state = None
872 state = None
873 hunknum = 0
873 hunknum = 0
874 rejects = 0
874 rejects = 0
875
875
876 git = False
876 git = False
877 gitre = re.compile('diff --git (a/.*) (b/.*)')
877 gitre = re.compile('diff --git (a/.*) (b/.*)')
878
878
879 # our states
879 # our states
880 BFILE = 1
880 BFILE = 1
881 err = 0
881 err = 0
882 context = None
882 context = None
883 lr = linereader(fp)
883 lr = linereader(fp)
884 dopatch = True
884 dopatch = True
885 gitworkdone = False
885 gitworkdone = False
886
886
887 while True:
887 while True:
888 newfile = False
888 newfile = False
889 x = lr.readline()
889 x = lr.readline()
890 if not x:
890 if not x:
891 break
891 break
892 if current_hunk:
892 if current_hunk:
893 if x.startswith('\ '):
893 if x.startswith('\ '):
894 current_hunk.fix_newline()
894 current_hunk.fix_newline()
895 ret = current_file.apply(current_hunk, reverse)
895 ret = current_file.apply(current_hunk, reverse)
896 if ret >= 0:
896 if ret >= 0:
897 changed.setdefault(current_file.fname, (None, None))
897 changed.setdefault(current_file.fname, (None, None))
898 if ret > 0:
898 if ret > 0:
899 err = 1
899 err = 1
900 current_hunk = None
900 current_hunk = None
901 gitworkdone = False
901 gitworkdone = False
902 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
902 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
903 ((context or context == None) and x.startswith('***************')))):
903 ((context or context == None) and x.startswith('***************')))):
904 try:
904 try:
905 if context == None and x.startswith('***************'):
905 if context == None and x.startswith('***************'):
906 context = True
906 context = True
907 current_hunk = hunk(x, hunknum + 1, lr, context)
907 current_hunk = hunk(x, hunknum + 1, lr, context)
908 except PatchError, err:
908 except PatchError, err:
909 ui.debug(err)
909 ui.debug(err)
910 current_hunk = None
910 current_hunk = None
911 continue
911 continue
912 hunknum += 1
912 hunknum += 1
913 if not current_file:
913 if not current_file:
914 if sourcefile:
914 if sourcefile:
915 current_file = patchfile(ui, sourcefile)
915 current_file = patchfile(ui, sourcefile)
916 else:
916 else:
917 current_file = selectfile(afile, bfile, current_hunk,
917 current_file = selectfile(afile, bfile, current_hunk,
918 strip, reverse)
918 strip, reverse)
919 current_file = patchfile(ui, current_file)
919 current_file = patchfile(ui, current_file)
920 elif state == BFILE and x.startswith('GIT binary patch'):
920 elif state == BFILE and x.startswith('GIT binary patch'):
921 current_hunk = binhunk(changed[bfile[2:]][1])
921 current_hunk = binhunk(changed[bfile[2:]][1])
922 if not current_file:
922 if not current_file:
923 if sourcefile:
923 if sourcefile:
924 current_file = patchfile(ui, sourcefile)
924 current_file = patchfile(ui, sourcefile)
925 else:
925 else:
926 current_file = selectfile(afile, bfile, current_hunk,
926 current_file = selectfile(afile, bfile, current_hunk,
927 strip, reverse)
927 strip, reverse)
928 current_file = patchfile(ui, current_file)
928 current_file = patchfile(ui, current_file)
929 hunknum += 1
929 hunknum += 1
930 current_hunk.extract(fp)
930 current_hunk.extract(fp)
931 elif x.startswith('diff --git'):
931 elif x.startswith('diff --git'):
932 # check for git diff, scanning the whole patch file if needed
932 # check for git diff, scanning the whole patch file if needed
933 m = gitre.match(x)
933 m = gitre.match(x)
934 if m:
934 if m:
935 afile, bfile = m.group(1, 2)
935 afile, bfile = m.group(1, 2)
936 if not git:
936 if not git:
937 git = True
937 git = True
938 fp, dopatch, gitpatches = scangitpatch(fp, x)
938 fp, dopatch, gitpatches = scangitpatch(fp, x)
939 for gp in gitpatches:
939 for gp in gitpatches:
940 changed[gp.path] = (gp.op, gp)
940 changed[gp.path] = (gp.op, gp)
941 # else error?
941 # else error?
942 # copy/rename + modify should modify target, not source
942 # copy/rename + modify should modify target, not source
943 if changed.get(bfile[2:], (None, None))[0] in ('COPY',
943 if changed.get(bfile[2:], (None, None))[0] in ('COPY',
944 'RENAME'):
944 'RENAME'):
945 afile = bfile
945 afile = bfile
946 gitworkdone = True
946 gitworkdone = True
947 newfile = True
947 newfile = True
948 elif x.startswith('---'):
948 elif x.startswith('---'):
949 # check for a unified diff
949 # check for a unified diff
950 l2 = lr.readline()
950 l2 = lr.readline()
951 if not l2.startswith('+++'):
951 if not l2.startswith('+++'):
952 lr.push(l2)
952 lr.push(l2)
953 continue
953 continue
954 newfile = True
954 newfile = True
955 context = False
955 context = False
956 afile = parsefilename(x)
956 afile = parsefilename(x)
957 bfile = parsefilename(l2)
957 bfile = parsefilename(l2)
958 elif x.startswith('***'):
958 elif x.startswith('***'):
959 # check for a context diff
959 # check for a context diff
960 l2 = lr.readline()
960 l2 = lr.readline()
961 if not l2.startswith('---'):
961 if not l2.startswith('---'):
962 lr.push(l2)
962 lr.push(l2)
963 continue
963 continue
964 l3 = lr.readline()
964 l3 = lr.readline()
965 lr.push(l3)
965 lr.push(l3)
966 if not l3.startswith("***************"):
966 if not l3.startswith("***************"):
967 lr.push(l2)
967 lr.push(l2)
968 continue
968 continue
969 newfile = True
969 newfile = True
970 context = True
970 context = True
971 afile = parsefilename(x)
971 afile = parsefilename(x)
972 bfile = parsefilename(l2)
972 bfile = parsefilename(l2)
973
973
974 if newfile:
974 if newfile:
975 if current_file:
975 if current_file:
976 current_file.close()
976 current_file.close()
977 if rejmerge:
977 if rejmerge:
978 rejmerge(current_file)
978 rejmerge(current_file)
979 rejects += len(current_file.rej)
979 rejects += len(current_file.rej)
980 state = BFILE
980 state = BFILE
981 current_file = None
981 current_file = None
982 hunknum = 0
982 hunknum = 0
983 if current_hunk:
983 if current_hunk:
984 if current_hunk.complete():
984 if current_hunk.complete():
985 ret = current_file.apply(current_hunk, reverse)
985 ret = current_file.apply(current_hunk, reverse)
986 if ret >= 0:
986 if ret >= 0:
987 changed.setdefault(current_file.fname, (None, None))
987 changed.setdefault(current_file.fname, (None, None))
988 if ret > 0:
988 if ret > 0:
989 err = 1
989 err = 1
990 else:
990 else:
991 fname = current_file and current_file.fname or None
991 fname = current_file and current_file.fname or None
992 raise PatchError(_("malformed patch %s %s") % (fname,
992 raise PatchError(_("malformed patch %s %s") % (fname,
993 current_hunk.desc))
993 current_hunk.desc))
994 if current_file:
994 if current_file:
995 current_file.close()
995 current_file.close()
996 if rejmerge:
996 if rejmerge:
997 rejmerge(current_file)
997 rejmerge(current_file)
998 rejects += len(current_file.rej)
998 rejects += len(current_file.rej)
999 if updatedir and git:
999 if updatedir and git:
1000 updatedir(gitpatches)
1000 updatedir(gitpatches)
1001 if rejects:
1001 if rejects:
1002 return -1
1002 return -1
1003 if hunknum == 0 and dopatch and not gitworkdone:
1003 if hunknum == 0 and dopatch and not gitworkdone:
1004 raise NoHunks
1004 raise NoHunks
1005 return err
1005 return err
1006
1006
1007 def diffopts(ui, opts={}, untrusted=False):
1007 def diffopts(ui, opts={}, untrusted=False):
1008 def get(key, name=None):
1008 def get(key, name=None):
1009 return (opts.get(key) or
1009 return (opts.get(key) or
1010 ui.configbool('diff', name or key, None, untrusted=untrusted))
1010 ui.configbool('diff', name or key, None, untrusted=untrusted))
1011 return mdiff.diffopts(
1011 return mdiff.diffopts(
1012 text=opts.get('text'),
1012 text=opts.get('text'),
1013 git=get('git'),
1013 git=get('git'),
1014 nodates=get('nodates'),
1014 nodates=get('nodates'),
1015 showfunc=get('show_function', 'showfunc'),
1015 showfunc=get('show_function', 'showfunc'),
1016 ignorews=get('ignore_all_space', 'ignorews'),
1016 ignorews=get('ignore_all_space', 'ignorews'),
1017 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1017 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1018 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'))
1018 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'))
1019
1019
1020 def updatedir(ui, repo, patches):
1020 def updatedir(ui, repo, patches):
1021 '''Update dirstate after patch application according to metadata'''
1021 '''Update dirstate after patch application according to metadata'''
1022 if not patches:
1022 if not patches:
1023 return
1023 return
1024 copies = []
1024 copies = []
1025 removes = {}
1025 removes = {}
1026 cfiles = patches.keys()
1026 cfiles = patches.keys()
1027 cwd = repo.getcwd()
1027 cwd = repo.getcwd()
1028 if cwd:
1028 if cwd:
1029 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1029 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1030 for f in patches:
1030 for f in patches:
1031 ctype, gp = patches[f]
1031 ctype, gp = patches[f]
1032 if ctype == 'RENAME':
1032 if ctype == 'RENAME':
1033 copies.append((gp.oldpath, gp.path, gp.copymod))
1033 copies.append((gp.oldpath, gp.path, gp.copymod))
1034 removes[gp.oldpath] = 1
1034 removes[gp.oldpath] = 1
1035 elif ctype == 'COPY':
1035 elif ctype == 'COPY':
1036 copies.append((gp.oldpath, gp.path, gp.copymod))
1036 copies.append((gp.oldpath, gp.path, gp.copymod))
1037 elif ctype == 'DELETE':
1037 elif ctype == 'DELETE':
1038 removes[gp.path] = 1
1038 removes[gp.path] = 1
1039 for src, dst, after in copies:
1039 for src, dst, after in copies:
1040 if not after:
1040 if not after:
1041 copyfile(src, dst, repo.root)
1041 copyfile(src, dst, repo.root)
1042 repo.copy(src, dst)
1042 repo.copy(src, dst)
1043 removes = removes.keys()
1043 removes = removes.keys()
1044 if removes:
1044 if removes:
1045 removes.sort()
1045 removes.sort()
1046 repo.remove(removes, True)
1046 repo.remove(removes, True)
1047 for f in patches:
1047 for f in patches:
1048 ctype, gp = patches[f]
1048 ctype, gp = patches[f]
1049 if gp and gp.mode:
1049 if gp and gp.mode:
1050 x = gp.mode & 0100 != 0
1050 x = gp.mode & 0100 != 0
1051 l = gp.mode & 020000 != 0
1051 l = gp.mode & 020000 != 0
1052 dst = os.path.join(repo.root, gp.path)
1052 dst = os.path.join(repo.root, gp.path)
1053 # patch won't create empty files
1053 # patch won't create empty files
1054 if ctype == 'ADD' and not os.path.exists(dst):
1054 if ctype == 'ADD' and not os.path.exists(dst):
1055 repo.wwrite(gp.path, '', x and 'x' or '')
1055 repo.wwrite(gp.path, '', x and 'x' or '')
1056 else:
1056 else:
1057 util.set_link(dst, l)
1057 util.set_link(dst, l)
1058 if not l:
1058 if not l:
1059 util.set_exec(dst, x)
1059 util.set_exec(dst, x)
1060 cmdutil.addremove(repo, cfiles)
1060 cmdutil.addremove(repo, cfiles)
1061 files = patches.keys()
1061 files = patches.keys()
1062 files.extend([r for r in removes if r not in files])
1062 files.extend([r for r in removes if r not in files])
1063 files.sort()
1063 files.sort()
1064
1064
1065 return files
1065 return files
1066
1066
1067 def b85diff(to, tn):
1067 def b85diff(to, tn):
1068 '''print base85-encoded binary diff'''
1068 '''print base85-encoded binary diff'''
1069 def gitindex(text):
1069 def gitindex(text):
1070 if not text:
1070 if not text:
1071 return '0' * 40
1071 return '0' * 40
1072 l = len(text)
1072 l = len(text)
1073 s = sha.new('blob %d\0' % l)
1073 s = sha.new('blob %d\0' % l)
1074 s.update(text)
1074 s.update(text)
1075 return s.hexdigest()
1075 return s.hexdigest()
1076
1076
1077 def fmtline(line):
1077 def fmtline(line):
1078 l = len(line)
1078 l = len(line)
1079 if l <= 26:
1079 if l <= 26:
1080 l = chr(ord('A') + l - 1)
1080 l = chr(ord('A') + l - 1)
1081 else:
1081 else:
1082 l = chr(l - 26 + ord('a') - 1)
1082 l = chr(l - 26 + ord('a') - 1)
1083 return '%c%s\n' % (l, base85.b85encode(line, True))
1083 return '%c%s\n' % (l, base85.b85encode(line, True))
1084
1084
1085 def chunk(text, csize=52):
1085 def chunk(text, csize=52):
1086 l = len(text)
1086 l = len(text)
1087 i = 0
1087 i = 0
1088 while i < l:
1088 while i < l:
1089 yield text[i:i+csize]
1089 yield text[i:i+csize]
1090 i += csize
1090 i += csize
1091
1091
1092 tohash = gitindex(to)
1092 tohash = gitindex(to)
1093 tnhash = gitindex(tn)
1093 tnhash = gitindex(tn)
1094 if tohash == tnhash:
1094 if tohash == tnhash:
1095 return ""
1095 return ""
1096
1096
1097 # TODO: deltas
1097 # TODO: deltas
1098 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1098 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1099 (tohash, tnhash, len(tn))]
1099 (tohash, tnhash, len(tn))]
1100 for l in chunk(zlib.compress(tn)):
1100 for l in chunk(zlib.compress(tn)):
1101 ret.append(fmtline(l))
1101 ret.append(fmtline(l))
1102 ret.append('\n')
1102 ret.append('\n')
1103 return ''.join(ret)
1103 return ''.join(ret)
1104
1104
1105 def diff(repo, node1=None, node2=None, files=None, match=util.always,
1105 def diff(repo, node1=None, node2=None, files=None, match=util.always,
1106 fp=None, changes=None, opts=None):
1106 fp=None, changes=None, opts=None):
1107 '''print diff of changes to files between two nodes, or node and
1107 '''print diff of changes to files between two nodes, or node and
1108 working directory.
1108 working directory.
1109
1109
1110 if node1 is None, use first dirstate parent instead.
1110 if node1 is None, use first dirstate parent instead.
1111 if node2 is None, compare node1 with working directory.'''
1111 if node2 is None, compare node1 with working directory.'''
1112
1112
1113 if opts is None:
1113 if opts is None:
1114 opts = mdiff.defaultopts
1114 opts = mdiff.defaultopts
1115 if fp is None:
1115 if fp is None:
1116 fp = repo.ui
1116 fp = repo.ui
1117
1117
1118 if not node1:
1118 if not node1:
1119 node1 = repo.dirstate.parents()[0]
1119 node1 = repo.dirstate.parents()[0]
1120
1120
1121 ccache = {}
1121 ccache = {}
1122 def getctx(r):
1122 def getctx(r):
1123 if r not in ccache:
1123 if r not in ccache:
1124 ccache[r] = context.changectx(repo, r)
1124 ccache[r] = context.changectx(repo, r)
1125 return ccache[r]
1125 return ccache[r]
1126
1126
1127 flcache = {}
1127 flcache = {}
1128 def getfilectx(f, ctx):
1128 def getfilectx(f, ctx):
1129 flctx = ctx.filectx(f, filelog=flcache.get(f))
1129 flctx = ctx.filectx(f, filelog=flcache.get(f))
1130 if f not in flcache:
1130 if f not in flcache:
1131 flcache[f] = flctx._filelog
1131 flcache[f] = flctx._filelog
1132 return flctx
1132 return flctx
1133
1133
1134 # reading the data for node1 early allows it to play nicely
1134 # reading the data for node1 early allows it to play nicely
1135 # with repo.status and the revlog cache.
1135 # with repo.status and the revlog cache.
1136 ctx1 = context.changectx(repo, node1)
1136 ctx1 = context.changectx(repo, node1)
1137 # force manifest reading
1137 # force manifest reading
1138 man1 = ctx1.manifest()
1138 man1 = ctx1.manifest()
1139 date1 = util.datestr(ctx1.date())
1139 date1 = util.datestr(ctx1.date())
1140
1140
1141 if not changes:
1141 if not changes:
1142 changes = repo.status(node1, node2, files, match=match)[:5]
1142 changes = repo.status(node1, node2, files, match=match)[:5]
1143 modified, added, removed, deleted, unknown = changes
1143 modified, added, removed, deleted, unknown = changes
1144
1144
1145 if not modified and not added and not removed:
1145 if not modified and not added and not removed:
1146 return
1146 return
1147
1147
1148 if node2:
1148 if node2:
1149 ctx2 = context.changectx(repo, node2)
1149 ctx2 = context.changectx(repo, node2)
1150 execf2 = ctx2.manifest().execf
1150 execf2 = ctx2.manifest().execf
1151 linkf2 = ctx2.manifest().linkf
1151 linkf2 = ctx2.manifest().linkf
1152 else:
1152 else:
1153 ctx2 = context.workingctx(repo)
1153 ctx2 = context.workingctx(repo)
1154 execf2 = util.execfunc(repo.root, None)
1154 execf2 = util.execfunc(repo.root, None)
1155 linkf2 = util.linkfunc(repo.root, None)
1155 linkf2 = util.linkfunc(repo.root, None)
1156 if execf2 is None:
1156 if execf2 is None:
1157 mc = ctx2.parents()[0].manifest().copy()
1157 mc = ctx2.parents()[0].manifest().copy()
1158 execf2 = mc.execf
1158 execf2 = mc.execf
1159 linkf2 = mc.linkf
1159 linkf2 = mc.linkf
1160
1160
1161 # returns False if there was no rename between ctx1 and ctx2
1161 # returns False if there was no rename between ctx1 and ctx2
1162 # returns None if the file was created between ctx1 and ctx2
1162 # returns None if the file was created between ctx1 and ctx2
1163 # returns the (file, node) present in ctx1 that was renamed to f in ctx2
1163 # returns the (file, node) present in ctx1 that was renamed to f in ctx2
1164 def renamed(f):
1164 # This will only really work if c1 is the Nth 1st parent of c2.
1165 startrev = ctx1.rev()
1165 def renamed(c1, c2, man, f):
1166 c = ctx2
1166 startrev = c1.rev()
1167 c = c2
1167 crev = c.rev()
1168 crev = c.rev()
1168 if crev is None:
1169 if crev is None:
1169 crev = repo.changelog.count()
1170 crev = repo.changelog.count()
1170 orig = f
1171 orig = f
1171 while crev > startrev:
1172 while crev > startrev:
1172 if f in c.files():
1173 if f in c.files():
1173 try:
1174 try:
1174 src = getfilectx(f, c).renamed()
1175 src = getfilectx(f, c).renamed()
1175 except revlog.LookupError:
1176 except revlog.LookupError:
1176 return None
1177 return None
1177 if src:
1178 if src:
1178 f = src[0]
1179 f = src[0]
1179 crev = c.parents()[0].rev()
1180 crev = c.parents()[0].rev()
1180 # try to reuse
1181 # try to reuse
1181 c = getctx(crev)
1182 c = getctx(crev)
1182 if f not in man1:
1183 if f not in man:
1183 return None
1184 return None
1184 if f == orig:
1185 if f == orig:
1185 return False
1186 return False
1186 return f
1187 return f
1187
1188
1188 if repo.ui.quiet:
1189 if repo.ui.quiet:
1189 r = None
1190 r = None
1190 else:
1191 else:
1191 hexfunc = repo.ui.debugflag and hex or short
1192 hexfunc = repo.ui.debugflag and hex or short
1192 r = [hexfunc(node) for node in [node1, node2] if node]
1193 r = [hexfunc(node) for node in [node1, node2] if node]
1193
1194
1194 if opts.git:
1195 if opts.git:
1195 copied = {}
1196 copied = {}
1196 for f in added:
1197 c1, c2 = ctx1, ctx2
1197 src = renamed(f)
1198 files = added
1199 man = man1
1200 if node2 and ctx1.rev() >= ctx2.rev():
1201 # renamed() starts at c2 and walks back in history until c1.
1202 # Since ctx1.rev() >= ctx2.rev(), invert ctx2 and ctx1 to
1203 # detect (inverted) copies.
1204 c1, c2 = ctx2, ctx1
1205 files = removed
1206 man = ctx2.manifest()
1207 for f in files:
1208 src = renamed(c1, c2, man, f)
1198 if src:
1209 if src:
1199 copied[f] = src
1210 copied[f] = src
1200 srcs = [x[1] for x in copied.items()]
1211 if ctx1 == c2:
1212 # invert the copied dict
1213 copied = dict([(v, k) for (k, v) in copied.iteritems()])
1214 # If we've renamed file foo to bar (copied['bar'] = 'foo'),
1215 # avoid showing a diff for foo if we're going to show
1216 # the rename to bar.
1217 srcs = [x[1] for x in copied.iteritems() if x[0] in added]
1201
1218
1202 all = modified + added + removed
1219 all = modified + added + removed
1203 all.sort()
1220 all.sort()
1204 gone = {}
1221 gone = {}
1205
1222
1206 for f in all:
1223 for f in all:
1207 to = None
1224 to = None
1208 tn = None
1225 tn = None
1209 dodiff = True
1226 dodiff = True
1210 header = []
1227 header = []
1211 if f in man1:
1228 if f in man1:
1212 to = getfilectx(f, ctx1).data()
1229 to = getfilectx(f, ctx1).data()
1213 if f not in removed:
1230 if f not in removed:
1214 tn = getfilectx(f, ctx2).data()
1231 tn = getfilectx(f, ctx2).data()
1215 if opts.git:
1232 if opts.git:
1216 def gitmode(x, l):
1233 def gitmode(x, l):
1217 return l and '120000' or (x and '100755' or '100644')
1234 return l and '120000' or (x and '100755' or '100644')
1218 def addmodehdr(header, omode, nmode):
1235 def addmodehdr(header, omode, nmode):
1219 if omode != nmode:
1236 if omode != nmode:
1220 header.append('old mode %s\n' % omode)
1237 header.append('old mode %s\n' % omode)
1221 header.append('new mode %s\n' % nmode)
1238 header.append('new mode %s\n' % nmode)
1222
1239
1223 a, b = f, f
1240 a, b = f, f
1224 if f in added:
1241 if f in added:
1225 mode = gitmode(execf2(f), linkf2(f))
1242 mode = gitmode(execf2(f), linkf2(f))
1226 if f in copied:
1243 if f in copied:
1227 a = copied[f]
1244 a = copied[f]
1228 omode = gitmode(man1.execf(a), man1.linkf(a))
1245 omode = gitmode(man1.execf(a), man1.linkf(a))
1229 addmodehdr(header, omode, mode)
1246 addmodehdr(header, omode, mode)
1230 if a in removed and a not in gone:
1247 if a in removed and a not in gone:
1231 op = 'rename'
1248 op = 'rename'
1232 gone[a] = 1
1249 gone[a] = 1
1233 else:
1250 else:
1234 op = 'copy'
1251 op = 'copy'
1235 header.append('%s from %s\n' % (op, a))
1252 header.append('%s from %s\n' % (op, a))
1236 header.append('%s to %s\n' % (op, f))
1253 header.append('%s to %s\n' % (op, f))
1237 to = getfilectx(a, ctx1).data()
1254 to = getfilectx(a, ctx1).data()
1238 else:
1255 else:
1239 header.append('new file mode %s\n' % mode)
1256 header.append('new file mode %s\n' % mode)
1240 if util.binary(tn):
1257 if util.binary(tn):
1241 dodiff = 'binary'
1258 dodiff = 'binary'
1242 elif f in removed:
1259 elif f in removed:
1243 if f in srcs:
1260 if f in srcs:
1244 dodiff = False
1261 dodiff = False
1245 else:
1262 else:
1246 mode = gitmode(man1.execf(f), man1.linkf(f))
1263 mode = gitmode(man1.execf(f), man1.linkf(f))
1247 header.append('deleted file mode %s\n' % mode)
1264 header.append('deleted file mode %s\n' % mode)
1248 else:
1265 else:
1249 omode = gitmode(man1.execf(f), man1.linkf(f))
1266 omode = gitmode(man1.execf(f), man1.linkf(f))
1250 nmode = gitmode(execf2(f), linkf2(f))
1267 nmode = gitmode(execf2(f), linkf2(f))
1251 addmodehdr(header, omode, nmode)
1268 addmodehdr(header, omode, nmode)
1252 if util.binary(to) or util.binary(tn):
1269 if util.binary(to) or util.binary(tn):
1253 dodiff = 'binary'
1270 dodiff = 'binary'
1254 r = None
1271 r = None
1255 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
1272 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
1256 if dodiff:
1273 if dodiff:
1257 if dodiff == 'binary':
1274 if dodiff == 'binary':
1258 text = b85diff(to, tn)
1275 text = b85diff(to, tn)
1259 else:
1276 else:
1260 text = mdiff.unidiff(to, date1,
1277 text = mdiff.unidiff(to, date1,
1261 # ctx2 date may be dynamic
1278 # ctx2 date may be dynamic
1262 tn, util.datestr(ctx2.date()),
1279 tn, util.datestr(ctx2.date()),
1263 f, r, opts=opts)
1280 f, r, opts=opts)
1264 if text or len(header) > 1:
1281 if text or len(header) > 1:
1265 fp.write(''.join(header))
1282 fp.write(''.join(header))
1266 fp.write(text)
1283 fp.write(text)
1267
1284
1268 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1285 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1269 opts=None):
1286 opts=None):
1270 '''export changesets as hg patches.'''
1287 '''export changesets as hg patches.'''
1271
1288
1272 total = len(revs)
1289 total = len(revs)
1273 revwidth = max([len(str(rev)) for rev in revs])
1290 revwidth = max([len(str(rev)) for rev in revs])
1274
1291
1275 def single(rev, seqno, fp):
1292 def single(rev, seqno, fp):
1276 ctx = repo.changectx(rev)
1293 ctx = repo.changectx(rev)
1277 node = ctx.node()
1294 node = ctx.node()
1278 parents = [p.node() for p in ctx.parents() if p]
1295 parents = [p.node() for p in ctx.parents() if p]
1279 branch = ctx.branch()
1296 branch = ctx.branch()
1280 if switch_parent:
1297 if switch_parent:
1281 parents.reverse()
1298 parents.reverse()
1282 prev = (parents and parents[0]) or nullid
1299 prev = (parents and parents[0]) or nullid
1283
1300
1284 if not fp:
1301 if not fp:
1285 fp = cmdutil.make_file(repo, template, node, total=total,
1302 fp = cmdutil.make_file(repo, template, node, total=total,
1286 seqno=seqno, revwidth=revwidth)
1303 seqno=seqno, revwidth=revwidth)
1287 if fp != sys.stdout and hasattr(fp, 'name'):
1304 if fp != sys.stdout and hasattr(fp, 'name'):
1288 repo.ui.note("%s\n" % fp.name)
1305 repo.ui.note("%s\n" % fp.name)
1289
1306
1290 fp.write("# HG changeset patch\n")
1307 fp.write("# HG changeset patch\n")
1291 fp.write("# User %s\n" % ctx.user())
1308 fp.write("# User %s\n" % ctx.user())
1292 fp.write("# Date %d %d\n" % ctx.date())
1309 fp.write("# Date %d %d\n" % ctx.date())
1293 if branch and (branch != 'default'):
1310 if branch and (branch != 'default'):
1294 fp.write("# Branch %s\n" % branch)
1311 fp.write("# Branch %s\n" % branch)
1295 fp.write("# Node ID %s\n" % hex(node))
1312 fp.write("# Node ID %s\n" % hex(node))
1296 fp.write("# Parent %s\n" % hex(prev))
1313 fp.write("# Parent %s\n" % hex(prev))
1297 if len(parents) > 1:
1314 if len(parents) > 1:
1298 fp.write("# Parent %s\n" % hex(parents[1]))
1315 fp.write("# Parent %s\n" % hex(parents[1]))
1299 fp.write(ctx.description().rstrip())
1316 fp.write(ctx.description().rstrip())
1300 fp.write("\n\n")
1317 fp.write("\n\n")
1301
1318
1302 diff(repo, prev, node, fp=fp, opts=opts)
1319 diff(repo, prev, node, fp=fp, opts=opts)
1303 if fp not in (sys.stdout, repo.ui):
1320 if fp not in (sys.stdout, repo.ui):
1304 fp.close()
1321 fp.close()
1305
1322
1306 for seqno, rev in enumerate(revs):
1323 for seqno, rev in enumerate(revs):
1307 single(rev, seqno+1, fp)
1324 single(rev, seqno+1, fp)
1308
1325
1309 def diffstat(patchlines):
1326 def diffstat(patchlines):
1310 if not util.find_exe('diffstat'):
1327 if not util.find_exe('diffstat'):
1311 return
1328 return
1312 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
1329 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
1313 try:
1330 try:
1314 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
1331 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
1315 try:
1332 try:
1316 for line in patchlines: print >> p.tochild, line
1333 for line in patchlines: print >> p.tochild, line
1317 p.tochild.close()
1334 p.tochild.close()
1318 if p.wait(): return
1335 if p.wait(): return
1319 fp = os.fdopen(fd, 'r')
1336 fp = os.fdopen(fd, 'r')
1320 stat = []
1337 stat = []
1321 for line in fp: stat.append(line.lstrip())
1338 for line in fp: stat.append(line.lstrip())
1322 last = stat.pop()
1339 last = stat.pop()
1323 stat.insert(0, last)
1340 stat.insert(0, last)
1324 stat = ''.join(stat)
1341 stat = ''.join(stat)
1325 if stat.startswith('0 files'): raise ValueError
1342 if stat.startswith('0 files'): raise ValueError
1326 return stat
1343 return stat
1327 except: raise
1344 except: raise
1328 finally:
1345 finally:
1329 try: os.unlink(name)
1346 try: os.unlink(name)
1330 except: pass
1347 except: pass
@@ -1,148 +1,158 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 hg init a
3 hg init a
4 cd a
4 cd a
5
5
6 echo start > start
6 echo start > start
7 hg ci -Amstart -d '0 0'
7 hg ci -Amstart -d '0 0'
8 echo new > new
8 echo new > new
9 hg ci -Amnew -d '0 0'
9 hg ci -Amnew -d '0 0'
10 echo '% new file'
10 echo '% new file'
11 hg diff --git -r 0
11 hg diff --git -r 0
12
12
13 hg cp new copy
13 hg cp new copy
14 hg ci -mcopy -d '0 0'
14 hg ci -mcopy -d '0 0'
15 echo '% copy'
15 echo '% copy'
16 hg diff --git -r 1:tip
16 hg diff --git -r 1:tip
17
17
18 hg mv copy rename
18 hg mv copy rename
19 hg ci -mrename -d '0 0'
19 hg ci -mrename -d '0 0'
20 echo '% rename'
20 echo '% rename'
21 hg diff --git -r 2:tip
21 hg diff --git -r 2:tip
22
22
23 hg rm rename
23 hg rm rename
24 hg ci -mdelete -d '0 0'
24 hg ci -mdelete -d '0 0'
25 echo '% delete'
25 echo '% delete'
26 hg diff --git -r 3:tip
26 hg diff --git -r 3:tip
27
27
28 cat > src <<EOF
28 cat > src <<EOF
29 1
29 1
30 2
30 2
31 3
31 3
32 4
32 4
33 5
33 5
34 EOF
34 EOF
35 hg ci -Amsrc -d '0 0'
35 hg ci -Amsrc -d '0 0'
36 chmod +x src
36 chmod +x src
37 hg ci -munexec -d '0 0'
37 hg ci -munexec -d '0 0'
38 echo '% chmod 644'
38 echo '% chmod 644'
39 hg diff --git -r 5:tip
39 hg diff --git -r 5:tip
40
40
41 hg mv src dst
41 hg mv src dst
42 chmod -x dst
42 chmod -x dst
43 echo a >> dst
43 echo a >> dst
44 hg ci -mrenamemod -d '0 0'
44 hg ci -mrenamemod -d '0 0'
45 echo '% rename+mod+chmod'
45 echo '% rename+mod+chmod'
46 hg diff --git -r 6:tip
46 hg diff --git -r 6:tip
47
47
48 echo '% nonexistent in tip+chmod'
48 echo '% nonexistent in tip+chmod'
49 hg diff --git -r 5:6
49 hg diff --git -r 5:6
50
50
51 echo '% binary diff'
51 echo '% binary diff'
52 cp $TESTDIR/binfile.bin .
52 cp $TESTDIR/binfile.bin .
53 hg add binfile.bin
53 hg add binfile.bin
54 hg diff --git > b.diff
54 hg diff --git > b.diff
55 cat b.diff
55 cat b.diff
56
56
57 echo '% import binary diff'
57 echo '% import binary diff'
58 hg revert binfile.bin
58 hg revert binfile.bin
59 rm binfile.bin
59 rm binfile.bin
60 hg import -mfoo b.diff
60 hg import -mfoo b.diff
61 cmp binfile.bin $TESTDIR/binfile.bin
61 cmp binfile.bin $TESTDIR/binfile.bin
62
62
63 echo
63 echo
64 echo '% rename binary file'
64 echo '% rename binary file'
65 hg mv binfile.bin renamed.bin
65 hg mv binfile.bin renamed.bin
66 hg diff --git
66 hg diff --git
67
67
68 echo
68 echo
69 echo '% diff across many revisions'
69 echo '% diff across many revisions'
70 hg mv dst dst2
70 hg mv dst dst2
71 hg ci -m 'mv dst dst2' -d '0 0'
71 hg ci -m 'mv dst dst2' -d '0 0'
72
72
73 echo >> start
73 echo >> start
74 hg ci -m 'change start' -d '0 0'
74 hg ci -m 'change start' -d '0 0'
75
75
76 hg revert -r -2 start
76 hg revert -r -2 start
77 hg mv dst2 dst3
77 hg mv dst2 dst3
78 hg ci -m 'mv dst2 dst3; revert start' -d '0 0'
78 hg ci -m 'mv dst2 dst3; revert start' -d '0 0'
79
79
80 hg diff --git -r 9:11
80 hg diff --git -r 9:11
81 echo '% reversed'
82 hg diff --git -r 11:9
81
83
82 echo a >> foo
84 echo a >> foo
83 hg add foo
85 hg add foo
84 hg ci -m 'add foo'
86 hg ci -m 'add foo'
85 echo b >> foo
87 echo b >> foo
86 hg ci -m 'change foo'
88 hg ci -m 'change foo'
87 hg mv foo bar
89 hg mv foo bar
88 hg ci -m 'mv foo bar'
90 hg ci -m 'mv foo bar'
89 echo c >> bar
91 echo c >> bar
90 hg ci -m 'change bar'
92 hg ci -m 'change bar'
91
93
92 echo
94 echo
93 echo '% file created before r1 and renamed before r2'
95 echo '% file created before r1 and renamed before r2'
94 hg diff --git -r -3:-1
96 hg diff --git -r -3:-1
97 echo '% reversed'
98 hg diff --git -r -1:-3
95 echo
99 echo
96 echo '% file created in r1 and renamed before r2'
100 echo '% file created in r1 and renamed before r2'
97 hg diff --git -r -4:-1
101 hg diff --git -r -4:-1
102 echo '% reversed'
103 hg diff --git -r -1:-4
98 echo
104 echo
99 echo '% file created after r1 and renamed before r2'
105 echo '% file created after r1 and renamed before r2'
100 hg diff --git -r -5:-1
106 hg diff --git -r -5:-1
107 echo '% reversed'
108 hg diff --git -r -1:-5
101
109
102 echo
110 echo
103 echo '% comparing with the working dir'
111 echo '% comparing with the working dir'
104 echo >> start
112 echo >> start
105 hg ci -m 'change start again' -d '0 0'
113 hg ci -m 'change start again' -d '0 0'
106
114
107 echo > created
115 echo > created
108 hg add created
116 hg add created
109 hg ci -m 'add created'
117 hg ci -m 'add created'
110
118
111 hg mv created created2
119 hg mv created created2
112 hg ci -m 'mv created created2'
120 hg ci -m 'mv created created2'
113
121
114 hg mv created2 created3
122 hg mv created2 created3
115 echo "% there's a copy in the working dir..."
123 echo "% there's a copy in the working dir..."
116 hg diff --git
124 hg diff --git
117 echo
125 echo
118 echo "% ...but there's another copy between the original rev and the wd"
126 echo "% ...but there's another copy between the original rev and the wd"
119 hg diff --git -r -2
127 hg diff --git -r -2
120 echo
128 echo
121 echo "% ...but the source of the copy was created after the original rev"
129 echo "% ...but the source of the copy was created after the original rev"
122 hg diff --git -r -3
130 hg diff --git -r -3
123 hg ci -m 'mv created2 created3'
131 hg ci -m 'mv created2 created3'
124
132
125 echo > brand-new
133 echo > brand-new
126 hg add brand-new
134 hg add brand-new
127 hg ci -m 'add brand-new'
135 hg ci -m 'add brand-new'
128 hg mv brand-new brand-new2
136 hg mv brand-new brand-new2
129 echo '% created in parent of wd; renamed in the wd'
137 echo '% created in parent of wd; renamed in the wd'
130 hg diff --git
138 hg diff --git
131
139
132 echo
140 echo
133 echo '% created between r1 and parent of wd; renamed in the wd'
141 echo '% created between r1 and parent of wd; renamed in the wd'
134 hg diff --git -r -2
142 hg diff --git -r -2
135 hg ci -m 'mv brand-new brand-new2'
143 hg ci -m 'mv brand-new brand-new2'
136
144
137 echo '% one file is copied to many destinations and removed'
145 echo '% one file is copied to many destinations and removed'
138 hg cp brand-new2 brand-new3
146 hg cp brand-new2 brand-new3
139 hg mv brand-new2 brand-new3-2
147 hg mv brand-new2 brand-new3-2
140 hg ci -m 'multiple renames/copies'
148 hg ci -m 'multiple renames/copies'
141 hg diff --git -r -2 -r -1
149 hg diff --git -r -2 -r -1
150 echo '% reversed'
151 hg diff --git -r -1 -r -2
142
152
143 echo '% there should be a trailing TAB if there are spaces in the file name'
153 echo '% there should be a trailing TAB if there are spaces in the file name'
144 echo foo > 'with spaces'
154 echo foo > 'with spaces'
145 hg add 'with spaces'
155 hg add 'with spaces'
146 hg diff --git
156 hg diff --git
147 hg ci -m 'add filename with spaces'
157 hg ci -m 'add filename with spaces'
148
158
@@ -1,154 +1,197 b''
1 adding start
1 adding start
2 adding new
2 adding new
3 % new file
3 % new file
4 diff --git a/new b/new
4 diff --git a/new b/new
5 new file mode 100644
5 new file mode 100644
6 --- /dev/null
6 --- /dev/null
7 +++ b/new
7 +++ b/new
8 @@ -0,0 +1,1 @@
8 @@ -0,0 +1,1 @@
9 +new
9 +new
10 % copy
10 % copy
11 diff --git a/new b/copy
11 diff --git a/new b/copy
12 copy from new
12 copy from new
13 copy to copy
13 copy to copy
14 % rename
14 % rename
15 diff --git a/copy b/rename
15 diff --git a/copy b/rename
16 rename from copy
16 rename from copy
17 rename to rename
17 rename to rename
18 % delete
18 % delete
19 diff --git a/rename b/rename
19 diff --git a/rename b/rename
20 deleted file mode 100644
20 deleted file mode 100644
21 --- a/rename
21 --- a/rename
22 +++ /dev/null
22 +++ /dev/null
23 @@ -1,1 +0,0 @@
23 @@ -1,1 +0,0 @@
24 -new
24 -new
25 adding src
25 adding src
26 % chmod 644
26 % chmod 644
27 diff --git a/src b/src
27 diff --git a/src b/src
28 old mode 100644
28 old mode 100644
29 new mode 100755
29 new mode 100755
30 % rename+mod+chmod
30 % rename+mod+chmod
31 diff --git a/src b/dst
31 diff --git a/src b/dst
32 old mode 100755
32 old mode 100755
33 new mode 100644
33 new mode 100644
34 rename from src
34 rename from src
35 rename to dst
35 rename to dst
36 --- a/dst
36 --- a/dst
37 +++ b/dst
37 +++ b/dst
38 @@ -3,3 +3,4 @@ 3
38 @@ -3,3 +3,4 @@ 3
39 3
39 3
40 4
40 4
41 5
41 5
42 +a
42 +a
43 % nonexistent in tip+chmod
43 % nonexistent in tip+chmod
44 diff --git a/src b/src
44 diff --git a/src b/src
45 old mode 100644
45 old mode 100644
46 new mode 100755
46 new mode 100755
47 % binary diff
47 % binary diff
48 diff --git a/binfile.bin b/binfile.bin
48 diff --git a/binfile.bin b/binfile.bin
49 new file mode 100644
49 new file mode 100644
50 index 0000000000000000000000000000000000000000..37ba3d1c6f17137d9c5f5776fa040caf5fe73ff9
50 index 0000000000000000000000000000000000000000..37ba3d1c6f17137d9c5f5776fa040caf5fe73ff9
51 GIT binary patch
51 GIT binary patch
52 literal 593
52 literal 593
53 zc$@)I0<QguP)<h;3K|Lk000e1NJLTq000mG000mO0ssI2kdbIM00009a7bBm000XU
53 zc$@)I0<QguP)<h;3K|Lk000e1NJLTq000mG000mO0ssI2kdbIM00009a7bBm000XU
54 z000XU0RWnu7ytkO2XskIMF-Uh9TW;VpMjwv0005-Nkl<ZD9@FWPs=e;7{<>W$NUkd
54 z000XU0RWnu7ytkO2XskIMF-Uh9TW;VpMjwv0005-Nkl<ZD9@FWPs=e;7{<>W$NUkd
55 zX$nnYLt$-$V!?uy+1V%`z&Eh=ah|duER<4|QWhju3gb^nF*8iYobxWG-qqXl=2~5M
55 zX$nnYLt$-$V!?uy+1V%`z&Eh=ah|duER<4|QWhju3gb^nF*8iYobxWG-qqXl=2~5M
56 z*IoDB)sG^CfNuoBmqLTVU^<;@nwHP!1wrWd`{(mHo6VNXWtyh{alzqmsH*yYzpvLT
56 z*IoDB)sG^CfNuoBmqLTVU^<;@nwHP!1wrWd`{(mHo6VNXWtyh{alzqmsH*yYzpvLT
57 zLdY<T=ks|woh-`&01!ej#(xbV1f|pI*=%;d-%F*E*X#ZH`4I%6SS+$EJDE&ct=8po
57 zLdY<T=ks|woh-`&01!ej#(xbV1f|pI*=%;d-%F*E*X#ZH`4I%6SS+$EJDE&ct=8po
58 ziN#{?_j|kD%Cd|oiqds`xm@;oJ-^?NG3Gdqrs?5u*zI;{nogxsx~^|Fn^Y?Gdc6<;
58 ziN#{?_j|kD%Cd|oiqds`xm@;oJ-^?NG3Gdqrs?5u*zI;{nogxsx~^|Fn^Y?Gdc6<;
59 zfMJ+iF1J`LMx&A2?dEwNW8ClebzPTbIh{@$hS6*`kH@1d%Lo7fA#}N1)oN7`gm$~V
59 zfMJ+iF1J`LMx&A2?dEwNW8ClebzPTbIh{@$hS6*`kH@1d%Lo7fA#}N1)oN7`gm$~V
60 z+wDx#)OFqMcE{s!JN0-xhG8ItAjVkJwEcb`3WWlJfU2r?;Pd%dmR+q@mSri5q9_W-
60 z+wDx#)OFqMcE{s!JN0-xhG8ItAjVkJwEcb`3WWlJfU2r?;Pd%dmR+q@mSri5q9_W-
61 zaR2~ECX?B2w+zELozC0s*6Z~|QG^f{3I#<`?)Q7U-JZ|q5W;9Q8i_=pBuSzunx=U;
61 zaR2~ECX?B2w+zELozC0s*6Z~|QG^f{3I#<`?)Q7U-JZ|q5W;9Q8i_=pBuSzunx=U;
62 z9C)5jBoYw9^?EHyQl(M}1OlQcCX>lXB*ODN003Z&P17_@)3Pi=i0wb04<W?v-u}7K
62 z9C)5jBoYw9^?EHyQl(M}1OlQcCX>lXB*ODN003Z&P17_@)3Pi=i0wb04<W?v-u}7K
63 zXmmQA+wDgE!qR9o8jr`%=ab_&uh(l?R=r;Tjiqon91I2-hIu?57~@*4h7h9uORK#=
63 zXmmQA+wDgE!qR9o8jr`%=ab_&uh(l?R=r;Tjiqon91I2-hIu?57~@*4h7h9uORK#=
64 fQItJW-{SoTm)8|5##k|m00000NkvXXu0mjf{mKw4
64 fQItJW-{SoTm)8|5##k|m00000NkvXXu0mjf{mKw4
65
65
66 % import binary diff
66 % import binary diff
67 applying b.diff
67 applying b.diff
68
68
69 % rename binary file
69 % rename binary file
70 diff --git a/binfile.bin b/renamed.bin
70 diff --git a/binfile.bin b/renamed.bin
71 rename from binfile.bin
71 rename from binfile.bin
72 rename to renamed.bin
72 rename to renamed.bin
73
73
74 % diff across many revisions
74 % diff across many revisions
75 diff --git a/dst2 b/dst3
75 diff --git a/dst2 b/dst3
76 rename from dst2
76 rename from dst2
77 rename to dst3
77 rename to dst3
78 % reversed
79 diff --git a/dst3 b/dst2
80 rename from dst3
81 rename to dst2
78
82
79 % file created before r1 and renamed before r2
83 % file created before r1 and renamed before r2
80 diff --git a/foo b/bar
84 diff --git a/foo b/bar
81 rename from foo
85 rename from foo
82 rename to bar
86 rename to bar
83 --- a/bar
87 --- a/bar
84 +++ b/bar
88 +++ b/bar
85 @@ -1,2 +1,3 @@ a
89 @@ -1,2 +1,3 @@ a
86 a
90 a
87 b
91 b
88 +c
92 +c
93 % reversed
94 diff --git a/bar b/foo
95 rename from bar
96 rename to foo
97 --- a/foo
98 +++ b/foo
99 @@ -1,3 +1,2 @@ a
100 a
101 b
102 -c
89
103
90 % file created in r1 and renamed before r2
104 % file created in r1 and renamed before r2
91 diff --git a/foo b/bar
105 diff --git a/foo b/bar
92 rename from foo
106 rename from foo
93 rename to bar
107 rename to bar
94 --- a/bar
108 --- a/bar
95 +++ b/bar
109 +++ b/bar
96 @@ -1,1 +1,3 @@ a
110 @@ -1,1 +1,3 @@ a
97 a
111 a
98 +b
112 +b
99 +c
113 +c
114 % reversed
115 diff --git a/bar b/foo
116 rename from bar
117 rename to foo
118 --- a/foo
119 +++ b/foo
120 @@ -1,3 +1,1 @@ a
121 a
122 -b
123 -c
100
124
101 % file created after r1 and renamed before r2
125 % file created after r1 and renamed before r2
102 diff --git a/bar b/bar
126 diff --git a/bar b/bar
103 new file mode 100644
127 new file mode 100644
104 --- /dev/null
128 --- /dev/null
105 +++ b/bar
129 +++ b/bar
106 @@ -0,0 +1,3 @@
130 @@ -0,0 +1,3 @@
107 +a
131 +a
108 +b
132 +b
109 +c
133 +c
134 % reversed
135 diff --git a/bar b/bar
136 deleted file mode 100644
137 --- a/bar
138 +++ /dev/null
139 @@ -1,3 +0,0 @@
140 -a
141 -b
142 -c
110
143
111 % comparing with the working dir
144 % comparing with the working dir
112 % there's a copy in the working dir...
145 % there's a copy in the working dir...
113 diff --git a/created2 b/created3
146 diff --git a/created2 b/created3
114 rename from created2
147 rename from created2
115 rename to created3
148 rename to created3
116
149
117 % ...but there's another copy between the original rev and the wd
150 % ...but there's another copy between the original rev and the wd
118 diff --git a/created b/created3
151 diff --git a/created b/created3
119 rename from created
152 rename from created
120 rename to created3
153 rename to created3
121
154
122 % ...but the source of the copy was created after the original rev
155 % ...but the source of the copy was created after the original rev
123 diff --git a/created3 b/created3
156 diff --git a/created3 b/created3
124 new file mode 100644
157 new file mode 100644
125 --- /dev/null
158 --- /dev/null
126 +++ b/created3
159 +++ b/created3
127 @@ -0,0 +1,1 @@
160 @@ -0,0 +1,1 @@
128 +
161 +
129 % created in parent of wd; renamed in the wd
162 % created in parent of wd; renamed in the wd
130 diff --git a/brand-new b/brand-new2
163 diff --git a/brand-new b/brand-new2
131 rename from brand-new
164 rename from brand-new
132 rename to brand-new2
165 rename to brand-new2
133
166
134 % created between r1 and parent of wd; renamed in the wd
167 % created between r1 and parent of wd; renamed in the wd
135 diff --git a/brand-new2 b/brand-new2
168 diff --git a/brand-new2 b/brand-new2
136 new file mode 100644
169 new file mode 100644
137 --- /dev/null
170 --- /dev/null
138 +++ b/brand-new2
171 +++ b/brand-new2
139 @@ -0,0 +1,1 @@
172 @@ -0,0 +1,1 @@
140 +
173 +
141 % one file is copied to many destinations and removed
174 % one file is copied to many destinations and removed
142 diff --git a/brand-new2 b/brand-new3
175 diff --git a/brand-new2 b/brand-new3
143 rename from brand-new2
176 rename from brand-new2
144 rename to brand-new3
177 rename to brand-new3
145 diff --git a/brand-new2 b/brand-new3-2
178 diff --git a/brand-new2 b/brand-new3-2
146 copy from brand-new2
179 copy from brand-new2
147 copy to brand-new3-2
180 copy to brand-new3-2
181 % reversed
182 diff --git a/brand-new3 b/brand-new2
183 rename from brand-new3
184 rename to brand-new2
185 diff --git a/brand-new3-2 b/brand-new3-2
186 deleted file mode 100644
187 --- a/brand-new3-2
188 +++ /dev/null
189 @@ -1,1 +0,0 @@
190 -
148 % there should be a trailing TAB if there are spaces in the file name
191 % there should be a trailing TAB if there are spaces in the file name
149 diff --git a/with spaces b/with spaces
192 diff --git a/with spaces b/with spaces
150 new file mode 100644
193 new file mode 100644
151 --- /dev/null
194 --- /dev/null
152 +++ b/with spaces
195 +++ b/with spaces
153 @@ -0,0 +1,1 @@
196 @@ -0,0 +1,1 @@
154 +foo
197 +foo
General Comments 0
You need to be logged in to leave comments. Login now