##// END OF EJS Templates
Don't generate git patches that rename a file to multiple destinations...
Alexis S. L. Carvalho -
r3702:70c3ee22 default
parent child Browse files
Show More
@@ -1,667 +1,672 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 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from demandload import demandload
8 from demandload import demandload
9 from i18n import gettext as _
9 from i18n import gettext as _
10 from node import *
10 from node import *
11 demandload(globals(), "base85 cmdutil mdiff util")
11 demandload(globals(), "base85 cmdutil mdiff util")
12 demandload(globals(), "cStringIO email.Parser errno os popen2 re shutil sha")
12 demandload(globals(), "cStringIO email.Parser errno os popen2 re shutil sha")
13 demandload(globals(), "sys tempfile zlib")
13 demandload(globals(), "sys tempfile zlib")
14
14
15 # helper functions
15 # helper functions
16
16
17 def copyfile(src, dst, basedir=None):
17 def copyfile(src, dst, basedir=None):
18 if not basedir:
18 if not basedir:
19 basedir = os.getcwd()
19 basedir = os.getcwd()
20
20
21 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
21 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
22 if os.path.exists(absdst):
22 if os.path.exists(absdst):
23 raise util.Abort(_("cannot create %s: destination already exists") %
23 raise util.Abort(_("cannot create %s: destination already exists") %
24 dst)
24 dst)
25
25
26 targetdir = os.path.dirname(absdst)
26 targetdir = os.path.dirname(absdst)
27 if not os.path.isdir(targetdir):
27 if not os.path.isdir(targetdir):
28 os.makedirs(targetdir)
28 os.makedirs(targetdir)
29
29
30 util.copyfile(abssrc, absdst)
30 util.copyfile(abssrc, absdst)
31
31
32 # public functions
32 # public functions
33
33
34 def extract(ui, fileobj):
34 def extract(ui, fileobj):
35 '''extract patch from data read from fileobj.
35 '''extract patch from data read from fileobj.
36
36
37 patch can be normal patch or contained in email message.
37 patch can be normal patch or contained in email message.
38
38
39 return tuple (filename, message, user, date). any item in returned
39 return tuple (filename, message, user, date). any item in returned
40 tuple can be None. if filename is None, fileobj did not contain
40 tuple can be None. if filename is None, fileobj did not contain
41 patch. caller must unlink filename when done.'''
41 patch. caller must unlink filename when done.'''
42
42
43 # attempt to detect the start of a patch
43 # attempt to detect the start of a patch
44 # (this heuristic is borrowed from quilt)
44 # (this heuristic is borrowed from quilt)
45 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
45 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
46 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
46 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
47 '(---|\*\*\*)[ \t])', re.MULTILINE)
47 '(---|\*\*\*)[ \t])', re.MULTILINE)
48
48
49 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
49 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
50 tmpfp = os.fdopen(fd, 'w')
50 tmpfp = os.fdopen(fd, 'w')
51 try:
51 try:
52 hgpatch = False
52 hgpatch = False
53
53
54 msg = email.Parser.Parser().parse(fileobj)
54 msg = email.Parser.Parser().parse(fileobj)
55
55
56 message = msg['Subject']
56 message = msg['Subject']
57 user = msg['From']
57 user = msg['From']
58 # should try to parse msg['Date']
58 # should try to parse msg['Date']
59 date = None
59 date = None
60
60
61 if message:
61 if message:
62 message = message.replace('\n\t', ' ')
62 message = message.replace('\n\t', ' ')
63 ui.debug('Subject: %s\n' % message)
63 ui.debug('Subject: %s\n' % message)
64 if user:
64 if user:
65 ui.debug('From: %s\n' % user)
65 ui.debug('From: %s\n' % user)
66 diffs_seen = 0
66 diffs_seen = 0
67 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
67 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
68
68
69 for part in msg.walk():
69 for part in msg.walk():
70 content_type = part.get_content_type()
70 content_type = part.get_content_type()
71 ui.debug('Content-Type: %s\n' % content_type)
71 ui.debug('Content-Type: %s\n' % content_type)
72 if content_type not in ok_types:
72 if content_type not in ok_types:
73 continue
73 continue
74 payload = part.get_payload(decode=True)
74 payload = part.get_payload(decode=True)
75 m = diffre.search(payload)
75 m = diffre.search(payload)
76 if m:
76 if m:
77 ui.debug(_('found patch at byte %d\n') % m.start(0))
77 ui.debug(_('found patch at byte %d\n') % m.start(0))
78 diffs_seen += 1
78 diffs_seen += 1
79 cfp = cStringIO.StringIO()
79 cfp = cStringIO.StringIO()
80 if message:
80 if message:
81 cfp.write(message)
81 cfp.write(message)
82 cfp.write('\n')
82 cfp.write('\n')
83 for line in payload[:m.start(0)].splitlines():
83 for line in payload[:m.start(0)].splitlines():
84 if line.startswith('# HG changeset patch'):
84 if line.startswith('# HG changeset patch'):
85 ui.debug(_('patch generated by hg export\n'))
85 ui.debug(_('patch generated by hg export\n'))
86 hgpatch = True
86 hgpatch = True
87 # drop earlier commit message content
87 # drop earlier commit message content
88 cfp.seek(0)
88 cfp.seek(0)
89 cfp.truncate()
89 cfp.truncate()
90 elif hgpatch:
90 elif hgpatch:
91 if line.startswith('# User '):
91 if line.startswith('# User '):
92 user = line[7:]
92 user = line[7:]
93 ui.debug('From: %s\n' % user)
93 ui.debug('From: %s\n' % user)
94 elif line.startswith("# Date "):
94 elif line.startswith("# Date "):
95 date = line[7:]
95 date = line[7:]
96 if not line.startswith('# '):
96 if not line.startswith('# '):
97 cfp.write(line)
97 cfp.write(line)
98 cfp.write('\n')
98 cfp.write('\n')
99 message = cfp.getvalue()
99 message = cfp.getvalue()
100 if tmpfp:
100 if tmpfp:
101 tmpfp.write(payload)
101 tmpfp.write(payload)
102 if not payload.endswith('\n'):
102 if not payload.endswith('\n'):
103 tmpfp.write('\n')
103 tmpfp.write('\n')
104 elif not diffs_seen and message and content_type == 'text/plain':
104 elif not diffs_seen and message and content_type == 'text/plain':
105 message += '\n' + payload
105 message += '\n' + payload
106 except:
106 except:
107 tmpfp.close()
107 tmpfp.close()
108 os.unlink(tmpname)
108 os.unlink(tmpname)
109 raise
109 raise
110
110
111 tmpfp.close()
111 tmpfp.close()
112 if not diffs_seen:
112 if not diffs_seen:
113 os.unlink(tmpname)
113 os.unlink(tmpname)
114 return None, message, user, date
114 return None, message, user, date
115 return tmpname, message, user, date
115 return tmpname, message, user, date
116
116
117 def readgitpatch(patchname):
117 def readgitpatch(patchname):
118 """extract git-style metadata about patches from <patchname>"""
118 """extract git-style metadata about patches from <patchname>"""
119 class gitpatch:
119 class gitpatch:
120 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
120 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
121 def __init__(self, path):
121 def __init__(self, path):
122 self.path = path
122 self.path = path
123 self.oldpath = None
123 self.oldpath = None
124 self.mode = None
124 self.mode = None
125 self.op = 'MODIFY'
125 self.op = 'MODIFY'
126 self.copymod = False
126 self.copymod = False
127 self.lineno = 0
127 self.lineno = 0
128 self.binary = False
128 self.binary = False
129
129
130 # Filter patch for git information
130 # Filter patch for git information
131 gitre = re.compile('diff --git a/(.*) b/(.*)')
131 gitre = re.compile('diff --git a/(.*) b/(.*)')
132 pf = file(patchname)
132 pf = file(patchname)
133 gp = None
133 gp = None
134 gitpatches = []
134 gitpatches = []
135 # Can have a git patch with only metadata, causing patch to complain
135 # Can have a git patch with only metadata, causing patch to complain
136 dopatch = False
136 dopatch = False
137
137
138 lineno = 0
138 lineno = 0
139 for line in pf:
139 for line in pf:
140 lineno += 1
140 lineno += 1
141 if line.startswith('diff --git'):
141 if line.startswith('diff --git'):
142 m = gitre.match(line)
142 m = gitre.match(line)
143 if m:
143 if m:
144 if gp:
144 if gp:
145 gitpatches.append(gp)
145 gitpatches.append(gp)
146 src, dst = m.group(1, 2)
146 src, dst = m.group(1, 2)
147 gp = gitpatch(dst)
147 gp = gitpatch(dst)
148 gp.lineno = lineno
148 gp.lineno = lineno
149 elif gp:
149 elif gp:
150 if line.startswith('--- '):
150 if line.startswith('--- '):
151 if gp.op in ('COPY', 'RENAME'):
151 if gp.op in ('COPY', 'RENAME'):
152 gp.copymod = True
152 gp.copymod = True
153 dopatch = 'filter'
153 dopatch = 'filter'
154 gitpatches.append(gp)
154 gitpatches.append(gp)
155 gp = None
155 gp = None
156 if not dopatch:
156 if not dopatch:
157 dopatch = True
157 dopatch = True
158 continue
158 continue
159 if line.startswith('rename from '):
159 if line.startswith('rename from '):
160 gp.op = 'RENAME'
160 gp.op = 'RENAME'
161 gp.oldpath = line[12:].rstrip()
161 gp.oldpath = line[12:].rstrip()
162 elif line.startswith('rename to '):
162 elif line.startswith('rename to '):
163 gp.path = line[10:].rstrip()
163 gp.path = line[10:].rstrip()
164 elif line.startswith('copy from '):
164 elif line.startswith('copy from '):
165 gp.op = 'COPY'
165 gp.op = 'COPY'
166 gp.oldpath = line[10:].rstrip()
166 gp.oldpath = line[10:].rstrip()
167 elif line.startswith('copy to '):
167 elif line.startswith('copy to '):
168 gp.path = line[8:].rstrip()
168 gp.path = line[8:].rstrip()
169 elif line.startswith('deleted file'):
169 elif line.startswith('deleted file'):
170 gp.op = 'DELETE'
170 gp.op = 'DELETE'
171 elif line.startswith('new file mode '):
171 elif line.startswith('new file mode '):
172 gp.op = 'ADD'
172 gp.op = 'ADD'
173 gp.mode = int(line.rstrip()[-3:], 8)
173 gp.mode = int(line.rstrip()[-3:], 8)
174 elif line.startswith('new mode '):
174 elif line.startswith('new mode '):
175 gp.mode = int(line.rstrip()[-3:], 8)
175 gp.mode = int(line.rstrip()[-3:], 8)
176 elif line.startswith('GIT binary patch'):
176 elif line.startswith('GIT binary patch'):
177 if not dopatch:
177 if not dopatch:
178 dopatch = 'binary'
178 dopatch = 'binary'
179 gp.binary = True
179 gp.binary = True
180 if gp:
180 if gp:
181 gitpatches.append(gp)
181 gitpatches.append(gp)
182
182
183 if not gitpatches:
183 if not gitpatches:
184 dopatch = True
184 dopatch = True
185
185
186 return (dopatch, gitpatches)
186 return (dopatch, gitpatches)
187
187
188 def dogitpatch(patchname, gitpatches, cwd=None):
188 def dogitpatch(patchname, gitpatches, cwd=None):
189 """Preprocess git patch so that vanilla patch can handle it"""
189 """Preprocess git patch so that vanilla patch can handle it"""
190 def extractbin(fp):
190 def extractbin(fp):
191 line = fp.readline().rstrip()
191 line = fp.readline().rstrip()
192 while line and not line.startswith('literal '):
192 while line and not line.startswith('literal '):
193 line = fp.readline().rstrip()
193 line = fp.readline().rstrip()
194 if not line:
194 if not line:
195 return
195 return
196 size = int(line[8:])
196 size = int(line[8:])
197 dec = []
197 dec = []
198 line = fp.readline().rstrip()
198 line = fp.readline().rstrip()
199 while line:
199 while line:
200 l = line[0]
200 l = line[0]
201 if l <= 'Z' and l >= 'A':
201 if l <= 'Z' and l >= 'A':
202 l = ord(l) - ord('A') + 1
202 l = ord(l) - ord('A') + 1
203 else:
203 else:
204 l = ord(l) - ord('a') + 27
204 l = ord(l) - ord('a') + 27
205 dec.append(base85.b85decode(line[1:])[:l])
205 dec.append(base85.b85decode(line[1:])[:l])
206 line = fp.readline().rstrip()
206 line = fp.readline().rstrip()
207 text = zlib.decompress(''.join(dec))
207 text = zlib.decompress(''.join(dec))
208 if len(text) != size:
208 if len(text) != size:
209 raise util.Abort(_('binary patch is %d bytes, not %d') %
209 raise util.Abort(_('binary patch is %d bytes, not %d') %
210 (len(text), size))
210 (len(text), size))
211 return text
211 return text
212
212
213 pf = file(patchname)
213 pf = file(patchname)
214 pfline = 1
214 pfline = 1
215
215
216 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
216 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
217 tmpfp = os.fdopen(fd, 'w')
217 tmpfp = os.fdopen(fd, 'w')
218
218
219 try:
219 try:
220 for i in xrange(len(gitpatches)):
220 for i in xrange(len(gitpatches)):
221 p = gitpatches[i]
221 p = gitpatches[i]
222 if not p.copymod and not p.binary:
222 if not p.copymod and not p.binary:
223 continue
223 continue
224
224
225 # rewrite patch hunk
225 # rewrite patch hunk
226 while pfline < p.lineno:
226 while pfline < p.lineno:
227 tmpfp.write(pf.readline())
227 tmpfp.write(pf.readline())
228 pfline += 1
228 pfline += 1
229
229
230 if p.binary:
230 if p.binary:
231 text = extractbin(pf)
231 text = extractbin(pf)
232 if not text:
232 if not text:
233 raise util.Abort(_('binary patch extraction failed'))
233 raise util.Abort(_('binary patch extraction failed'))
234 if not cwd:
234 if not cwd:
235 cwd = os.getcwd()
235 cwd = os.getcwd()
236 absdst = os.path.join(cwd, p.path)
236 absdst = os.path.join(cwd, p.path)
237 basedir = os.path.dirname(absdst)
237 basedir = os.path.dirname(absdst)
238 if not os.path.isdir(basedir):
238 if not os.path.isdir(basedir):
239 os.makedirs(basedir)
239 os.makedirs(basedir)
240 out = file(absdst, 'wb')
240 out = file(absdst, 'wb')
241 out.write(text)
241 out.write(text)
242 out.close()
242 out.close()
243 elif p.copymod:
243 elif p.copymod:
244 copyfile(p.oldpath, p.path, basedir=cwd)
244 copyfile(p.oldpath, p.path, basedir=cwd)
245 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
245 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
246 line = pf.readline()
246 line = pf.readline()
247 pfline += 1
247 pfline += 1
248 while not line.startswith('--- a/'):
248 while not line.startswith('--- a/'):
249 tmpfp.write(line)
249 tmpfp.write(line)
250 line = pf.readline()
250 line = pf.readline()
251 pfline += 1
251 pfline += 1
252 tmpfp.write('--- a/%s\n' % p.path)
252 tmpfp.write('--- a/%s\n' % p.path)
253
253
254 line = pf.readline()
254 line = pf.readline()
255 while line:
255 while line:
256 tmpfp.write(line)
256 tmpfp.write(line)
257 line = pf.readline()
257 line = pf.readline()
258 except:
258 except:
259 tmpfp.close()
259 tmpfp.close()
260 os.unlink(patchname)
260 os.unlink(patchname)
261 raise
261 raise
262
262
263 tmpfp.close()
263 tmpfp.close()
264 return patchname
264 return patchname
265
265
266 def patch(patchname, ui, strip=1, cwd=None, files={}):
266 def patch(patchname, ui, strip=1, cwd=None, files={}):
267 """apply the patch <patchname> to the working directory.
267 """apply the patch <patchname> to the working directory.
268 a list of patched files is returned"""
268 a list of patched files is returned"""
269
269
270 # helper function
270 # helper function
271 def __patch(patchname):
271 def __patch(patchname):
272 """patch and updates the files and fuzz variables"""
272 """patch and updates the files and fuzz variables"""
273 fuzz = False
273 fuzz = False
274
274
275 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''),
275 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''),
276 'patch')
276 'patch')
277 args = []
277 args = []
278 if cwd:
278 if cwd:
279 args.append('-d %s' % util.shellquote(cwd))
279 args.append('-d %s' % util.shellquote(cwd))
280 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
280 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
281 util.shellquote(patchname)))
281 util.shellquote(patchname)))
282
282
283 for line in fp:
283 for line in fp:
284 line = line.rstrip()
284 line = line.rstrip()
285 ui.note(line + '\n')
285 ui.note(line + '\n')
286 if line.startswith('patching file '):
286 if line.startswith('patching file '):
287 pf = util.parse_patch_output(line)
287 pf = util.parse_patch_output(line)
288 printed_file = False
288 printed_file = False
289 files.setdefault(pf, (None, None))
289 files.setdefault(pf, (None, None))
290 elif line.find('with fuzz') >= 0:
290 elif line.find('with fuzz') >= 0:
291 fuzz = True
291 fuzz = True
292 if not printed_file:
292 if not printed_file:
293 ui.warn(pf + '\n')
293 ui.warn(pf + '\n')
294 printed_file = True
294 printed_file = True
295 ui.warn(line + '\n')
295 ui.warn(line + '\n')
296 elif line.find('saving rejects to file') >= 0:
296 elif line.find('saving rejects to file') >= 0:
297 ui.warn(line + '\n')
297 ui.warn(line + '\n')
298 elif line.find('FAILED') >= 0:
298 elif line.find('FAILED') >= 0:
299 if not printed_file:
299 if not printed_file:
300 ui.warn(pf + '\n')
300 ui.warn(pf + '\n')
301 printed_file = True
301 printed_file = True
302 ui.warn(line + '\n')
302 ui.warn(line + '\n')
303 code = fp.close()
303 code = fp.close()
304 if code:
304 if code:
305 raise util.Abort(_("patch command failed: %s") %
305 raise util.Abort(_("patch command failed: %s") %
306 util.explain_exit(code)[0])
306 util.explain_exit(code)[0])
307 return fuzz
307 return fuzz
308
308
309 (dopatch, gitpatches) = readgitpatch(patchname)
309 (dopatch, gitpatches) = readgitpatch(patchname)
310 for gp in gitpatches:
310 for gp in gitpatches:
311 files[gp.path] = (gp.op, gp)
311 files[gp.path] = (gp.op, gp)
312
312
313 fuzz = False
313 fuzz = False
314 if dopatch:
314 if dopatch:
315 if dopatch in ('filter', 'binary'):
315 if dopatch in ('filter', 'binary'):
316 patchname = dogitpatch(patchname, gitpatches, cwd=cwd)
316 patchname = dogitpatch(patchname, gitpatches, cwd=cwd)
317 try:
317 try:
318 if dopatch != 'binary':
318 if dopatch != 'binary':
319 fuzz = __patch(patchname)
319 fuzz = __patch(patchname)
320 finally:
320 finally:
321 if dopatch == 'filter':
321 if dopatch == 'filter':
322 os.unlink(patchname)
322 os.unlink(patchname)
323
323
324 return fuzz
324 return fuzz
325
325
326 def diffopts(ui, opts={}, untrusted=False):
326 def diffopts(ui, opts={}, untrusted=False):
327 def get(key, name=None):
327 def get(key, name=None):
328 return (opts.get(key) or
328 return (opts.get(key) or
329 ui.configbool('diff', name or key, None, untrusted=untrusted))
329 ui.configbool('diff', name or key, None, untrusted=untrusted))
330 return mdiff.diffopts(
330 return mdiff.diffopts(
331 text=opts.get('text'),
331 text=opts.get('text'),
332 git=get('git'),
332 git=get('git'),
333 nodates=get('nodates'),
333 nodates=get('nodates'),
334 showfunc=get('show_function', 'showfunc'),
334 showfunc=get('show_function', 'showfunc'),
335 ignorews=get('ignore_all_space', 'ignorews'),
335 ignorews=get('ignore_all_space', 'ignorews'),
336 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
336 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
337 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'))
337 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'))
338
338
339 def updatedir(ui, repo, patches, wlock=None):
339 def updatedir(ui, repo, patches, wlock=None):
340 '''Update dirstate after patch application according to metadata'''
340 '''Update dirstate after patch application according to metadata'''
341 if not patches:
341 if not patches:
342 return
342 return
343 copies = []
343 copies = []
344 removes = {}
344 removes = {}
345 cfiles = patches.keys()
345 cfiles = patches.keys()
346 cwd = repo.getcwd()
346 cwd = repo.getcwd()
347 if cwd:
347 if cwd:
348 cfiles = [util.pathto(cwd, f) for f in patches.keys()]
348 cfiles = [util.pathto(cwd, f) for f in patches.keys()]
349 for f in patches:
349 for f in patches:
350 ctype, gp = patches[f]
350 ctype, gp = patches[f]
351 if ctype == 'RENAME':
351 if ctype == 'RENAME':
352 copies.append((gp.oldpath, gp.path, gp.copymod))
352 copies.append((gp.oldpath, gp.path, gp.copymod))
353 removes[gp.oldpath] = 1
353 removes[gp.oldpath] = 1
354 elif ctype == 'COPY':
354 elif ctype == 'COPY':
355 copies.append((gp.oldpath, gp.path, gp.copymod))
355 copies.append((gp.oldpath, gp.path, gp.copymod))
356 elif ctype == 'DELETE':
356 elif ctype == 'DELETE':
357 removes[gp.path] = 1
357 removes[gp.path] = 1
358 for src, dst, after in copies:
358 for src, dst, after in copies:
359 if not after:
359 if not after:
360 copyfile(src, dst, repo.root)
360 copyfile(src, dst, repo.root)
361 repo.copy(src, dst, wlock=wlock)
361 repo.copy(src, dst, wlock=wlock)
362 removes = removes.keys()
362 removes = removes.keys()
363 if removes:
363 if removes:
364 removes.sort()
364 removes.sort()
365 repo.remove(removes, True, wlock=wlock)
365 repo.remove(removes, True, wlock=wlock)
366 for f in patches:
366 for f in patches:
367 ctype, gp = patches[f]
367 ctype, gp = patches[f]
368 if gp and gp.mode:
368 if gp and gp.mode:
369 x = gp.mode & 0100 != 0
369 x = gp.mode & 0100 != 0
370 dst = os.path.join(repo.root, gp.path)
370 dst = os.path.join(repo.root, gp.path)
371 # patch won't create empty files
371 # patch won't create empty files
372 if ctype == 'ADD' and not os.path.exists(dst):
372 if ctype == 'ADD' and not os.path.exists(dst):
373 repo.wwrite(gp.path, '')
373 repo.wwrite(gp.path, '')
374 util.set_exec(dst, x)
374 util.set_exec(dst, x)
375 cmdutil.addremove(repo, cfiles, wlock=wlock)
375 cmdutil.addremove(repo, cfiles, wlock=wlock)
376 files = patches.keys()
376 files = patches.keys()
377 files.extend([r for r in removes if r not in files])
377 files.extend([r for r in removes if r not in files])
378 files.sort()
378 files.sort()
379
379
380 return files
380 return files
381
381
382 def b85diff(fp, to, tn):
382 def b85diff(fp, to, tn):
383 '''print base85-encoded binary diff'''
383 '''print base85-encoded binary diff'''
384 def gitindex(text):
384 def gitindex(text):
385 if not text:
385 if not text:
386 return '0' * 40
386 return '0' * 40
387 l = len(text)
387 l = len(text)
388 s = sha.new('blob %d\0' % l)
388 s = sha.new('blob %d\0' % l)
389 s.update(text)
389 s.update(text)
390 return s.hexdigest()
390 return s.hexdigest()
391
391
392 def fmtline(line):
392 def fmtline(line):
393 l = len(line)
393 l = len(line)
394 if l <= 26:
394 if l <= 26:
395 l = chr(ord('A') + l - 1)
395 l = chr(ord('A') + l - 1)
396 else:
396 else:
397 l = chr(l - 26 + ord('a') - 1)
397 l = chr(l - 26 + ord('a') - 1)
398 return '%c%s\n' % (l, base85.b85encode(line, True))
398 return '%c%s\n' % (l, base85.b85encode(line, True))
399
399
400 def chunk(text, csize=52):
400 def chunk(text, csize=52):
401 l = len(text)
401 l = len(text)
402 i = 0
402 i = 0
403 while i < l:
403 while i < l:
404 yield text[i:i+csize]
404 yield text[i:i+csize]
405 i += csize
405 i += csize
406
406
407 # TODO: deltas
407 # TODO: deltas
408 l = len(tn)
408 l = len(tn)
409 fp.write('index %s..%s\nGIT binary patch\nliteral %s\n' %
409 fp.write('index %s..%s\nGIT binary patch\nliteral %s\n' %
410 (gitindex(to), gitindex(tn), len(tn)))
410 (gitindex(to), gitindex(tn), len(tn)))
411
411
412 tn = ''.join([fmtline(l) for l in chunk(zlib.compress(tn))])
412 tn = ''.join([fmtline(l) for l in chunk(zlib.compress(tn))])
413 fp.write(tn)
413 fp.write(tn)
414 fp.write('\n')
414 fp.write('\n')
415
415
416 def diff(repo, node1=None, node2=None, files=None, match=util.always,
416 def diff(repo, node1=None, node2=None, files=None, match=util.always,
417 fp=None, changes=None, opts=None):
417 fp=None, changes=None, opts=None):
418 '''print diff of changes to files between two nodes, or node and
418 '''print diff of changes to files between two nodes, or node and
419 working directory.
419 working directory.
420
420
421 if node1 is None, use first dirstate parent instead.
421 if node1 is None, use first dirstate parent instead.
422 if node2 is None, compare node1 with working directory.'''
422 if node2 is None, compare node1 with working directory.'''
423
423
424 if opts is None:
424 if opts is None:
425 opts = mdiff.defaultopts
425 opts = mdiff.defaultopts
426 if fp is None:
426 if fp is None:
427 fp = repo.ui
427 fp = repo.ui
428
428
429 if not node1:
429 if not node1:
430 node1 = repo.dirstate.parents()[0]
430 node1 = repo.dirstate.parents()[0]
431
431
432 clcache = {}
432 clcache = {}
433 def getchangelog(n):
433 def getchangelog(n):
434 if n not in clcache:
434 if n not in clcache:
435 clcache[n] = repo.changelog.read(n)
435 clcache[n] = repo.changelog.read(n)
436 return clcache[n]
436 return clcache[n]
437 mcache = {}
437 mcache = {}
438 def getmanifest(n):
438 def getmanifest(n):
439 if n not in mcache:
439 if n not in mcache:
440 mcache[n] = repo.manifest.read(n)
440 mcache[n] = repo.manifest.read(n)
441 return mcache[n]
441 return mcache[n]
442 fcache = {}
442 fcache = {}
443 def getfile(f):
443 def getfile(f):
444 if f not in fcache:
444 if f not in fcache:
445 fcache[f] = repo.file(f)
445 fcache[f] = repo.file(f)
446 return fcache[f]
446 return fcache[f]
447
447
448 # reading the data for node1 early allows it to play nicely
448 # reading the data for node1 early allows it to play nicely
449 # with repo.status and the revlog cache.
449 # with repo.status and the revlog cache.
450 change = getchangelog(node1)
450 change = getchangelog(node1)
451 mmap = getmanifest(change[0])
451 mmap = getmanifest(change[0])
452 date1 = util.datestr(change[2])
452 date1 = util.datestr(change[2])
453
453
454 if not changes:
454 if not changes:
455 changes = repo.status(node1, node2, files, match=match)[:5]
455 changes = repo.status(node1, node2, files, match=match)[:5]
456 modified, added, removed, deleted, unknown = changes
456 modified, added, removed, deleted, unknown = changes
457 if files:
457 if files:
458 def filterfiles(filters):
458 def filterfiles(filters):
459 l = [x for x in filters if x in files]
459 l = [x for x in filters if x in files]
460
460
461 for t in files:
461 for t in files:
462 if not t.endswith("/"):
462 if not t.endswith("/"):
463 t += "/"
463 t += "/"
464 l += [x for x in filters if x.startswith(t)]
464 l += [x for x in filters if x.startswith(t)]
465 return l
465 return l
466
466
467 modified, added, removed = map(filterfiles, (modified, added, removed))
467 modified, added, removed = map(filterfiles, (modified, added, removed))
468
468
469 if not modified and not added and not removed:
469 if not modified and not added and not removed:
470 return
470 return
471
471
472 # returns False if there was no rename between n1 and n2
472 # returns False if there was no rename between n1 and n2
473 # returns None if the file was created between n1 and n2
473 # returns None if the file was created between n1 and n2
474 # returns the (file, node) present in n1 that was renamed to f in n2
474 # returns the (file, node) present in n1 that was renamed to f in n2
475 def renamedbetween(f, n1, n2):
475 def renamedbetween(f, n1, n2):
476 r1, r2 = map(repo.changelog.rev, (n1, n2))
476 r1, r2 = map(repo.changelog.rev, (n1, n2))
477 orig = f
477 orig = f
478 src = None
478 src = None
479 while r2 > r1:
479 while r2 > r1:
480 cl = getchangelog(n2)
480 cl = getchangelog(n2)
481 if f in cl[3]:
481 if f in cl[3]:
482 m = getmanifest(cl[0])
482 m = getmanifest(cl[0])
483 try:
483 try:
484 src = getfile(f).renamed(m[f])
484 src = getfile(f).renamed(m[f])
485 except KeyError:
485 except KeyError:
486 return None
486 return None
487 if src:
487 if src:
488 f = src[0]
488 f = src[0]
489 n2 = repo.changelog.parents(n2)[0]
489 n2 = repo.changelog.parents(n2)[0]
490 r2 = repo.changelog.rev(n2)
490 r2 = repo.changelog.rev(n2)
491 cl = getchangelog(n1)
491 cl = getchangelog(n1)
492 m = getmanifest(cl[0])
492 m = getmanifest(cl[0])
493 if f not in m:
493 if f not in m:
494 return None
494 return None
495 if f == orig:
495 if f == orig:
496 return False
496 return False
497 return f, m[f]
497 return f, m[f]
498
498
499 if node2:
499 if node2:
500 change = getchangelog(node2)
500 change = getchangelog(node2)
501 mmap2 = getmanifest(change[0])
501 mmap2 = getmanifest(change[0])
502 _date2 = util.datestr(change[2])
502 _date2 = util.datestr(change[2])
503 def date2(f):
503 def date2(f):
504 return _date2
504 return _date2
505 def read(f):
505 def read(f):
506 return getfile(f).read(mmap2[f])
506 return getfile(f).read(mmap2[f])
507 def renamed(f):
507 def renamed(f):
508 return renamedbetween(f, node1, node2)
508 return renamedbetween(f, node1, node2)
509 else:
509 else:
510 tz = util.makedate()[1]
510 tz = util.makedate()[1]
511 _date2 = util.datestr()
511 _date2 = util.datestr()
512 def date2(f):
512 def date2(f):
513 try:
513 try:
514 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
514 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
515 except OSError, err:
515 except OSError, err:
516 if err.errno != errno.ENOENT: raise
516 if err.errno != errno.ENOENT: raise
517 return _date2
517 return _date2
518 def read(f):
518 def read(f):
519 return repo.wread(f)
519 return repo.wread(f)
520 def renamed(f):
520 def renamed(f):
521 src = repo.dirstate.copied(f)
521 src = repo.dirstate.copied(f)
522 parent = repo.dirstate.parents()[0]
522 parent = repo.dirstate.parents()[0]
523 if src:
523 if src:
524 f = src
524 f = src
525 of = renamedbetween(f, node1, parent)
525 of = renamedbetween(f, node1, parent)
526 if of or of is None:
526 if of or of is None:
527 return of
527 return of
528 elif src:
528 elif src:
529 cl = getchangelog(parent)[0]
529 cl = getchangelog(parent)[0]
530 return (src, getmanifest(cl)[src])
530 return (src, getmanifest(cl)[src])
531 else:
531 else:
532 return None
532 return None
533
533
534 if repo.ui.quiet:
534 if repo.ui.quiet:
535 r = None
535 r = None
536 else:
536 else:
537 hexfunc = repo.ui.debugflag and hex or short
537 hexfunc = repo.ui.debugflag and hex or short
538 r = [hexfunc(node) for node in [node1, node2] if node]
538 r = [hexfunc(node) for node in [node1, node2] if node]
539
539
540 if opts.git:
540 if opts.git:
541 copied = {}
541 copied = {}
542 for f in added:
542 for f in added:
543 src = renamed(f)
543 src = renamed(f)
544 if src:
544 if src:
545 copied[f] = src
545 copied[f] = src
546 srcs = [x[1][0] for x in copied.items()]
546 srcs = [x[1][0] for x in copied.items()]
547
547
548 all = modified + added + removed
548 all = modified + added + removed
549 all.sort()
549 all.sort()
550 gone = {}
550 for f in all:
551 for f in all:
551 to = None
552 to = None
552 tn = None
553 tn = None
553 dodiff = True
554 dodiff = True
554 header = []
555 header = []
555 if f in mmap:
556 if f in mmap:
556 to = getfile(f).read(mmap[f])
557 to = getfile(f).read(mmap[f])
557 if f not in removed:
558 if f not in removed:
558 tn = read(f)
559 tn = read(f)
559 if opts.git:
560 if opts.git:
560 def gitmode(x):
561 def gitmode(x):
561 return x and '100755' or '100644'
562 return x and '100755' or '100644'
562 def addmodehdr(header, omode, nmode):
563 def addmodehdr(header, omode, nmode):
563 if omode != nmode:
564 if omode != nmode:
564 header.append('old mode %s\n' % omode)
565 header.append('old mode %s\n' % omode)
565 header.append('new mode %s\n' % nmode)
566 header.append('new mode %s\n' % nmode)
566
567
567 a, b = f, f
568 a, b = f, f
568 if f in added:
569 if f in added:
569 if node2:
570 if node2:
570 mode = gitmode(mmap2.execf(f))
571 mode = gitmode(mmap2.execf(f))
571 else:
572 else:
572 mode = gitmode(util.is_exec(repo.wjoin(f), None))
573 mode = gitmode(util.is_exec(repo.wjoin(f), None))
573 if f in copied:
574 if f in copied:
574 a, arev = copied[f]
575 a, arev = copied[f]
575 omode = gitmode(mmap.execf(a))
576 omode = gitmode(mmap.execf(a))
576 addmodehdr(header, omode, mode)
577 addmodehdr(header, omode, mode)
577 op = a in removed and 'rename' or 'copy'
578 if a in removed and a not in gone:
579 op = 'rename'
580 gone[a] = 1
581 else:
582 op = 'copy'
578 header.append('%s from %s\n' % (op, a))
583 header.append('%s from %s\n' % (op, a))
579 header.append('%s to %s\n' % (op, f))
584 header.append('%s to %s\n' % (op, f))
580 to = getfile(a).read(arev)
585 to = getfile(a).read(arev)
581 else:
586 else:
582 header.append('new file mode %s\n' % mode)
587 header.append('new file mode %s\n' % mode)
583 if util.binary(tn):
588 if util.binary(tn):
584 dodiff = 'binary'
589 dodiff = 'binary'
585 elif f in removed:
590 elif f in removed:
586 if f in srcs:
591 if f in srcs:
587 dodiff = False
592 dodiff = False
588 else:
593 else:
589 mode = gitmode(mmap.execf(f))
594 mode = gitmode(mmap.execf(f))
590 header.append('deleted file mode %s\n' % mode)
595 header.append('deleted file mode %s\n' % mode)
591 else:
596 else:
592 omode = gitmode(mmap.execf(f))
597 omode = gitmode(mmap.execf(f))
593 if node2:
598 if node2:
594 nmode = gitmode(mmap2.execf(f))
599 nmode = gitmode(mmap2.execf(f))
595 else:
600 else:
596 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
601 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
597 addmodehdr(header, omode, nmode)
602 addmodehdr(header, omode, nmode)
598 if util.binary(to) or util.binary(tn):
603 if util.binary(to) or util.binary(tn):
599 dodiff = 'binary'
604 dodiff = 'binary'
600 r = None
605 r = None
601 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
606 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
602 if dodiff == 'binary':
607 if dodiff == 'binary':
603 fp.write(''.join(header))
608 fp.write(''.join(header))
604 b85diff(fp, to, tn)
609 b85diff(fp, to, tn)
605 elif dodiff:
610 elif dodiff:
606 text = mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts)
611 text = mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts)
607 if text or len(header) > 1:
612 if text or len(header) > 1:
608 fp.write(''.join(header))
613 fp.write(''.join(header))
609 fp.write(text)
614 fp.write(text)
610
615
611 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
616 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
612 opts=None):
617 opts=None):
613 '''export changesets as hg patches.'''
618 '''export changesets as hg patches.'''
614
619
615 total = len(revs)
620 total = len(revs)
616 revwidth = max(map(len, revs))
621 revwidth = max(map(len, revs))
617
622
618 def single(node, seqno, fp):
623 def single(node, seqno, fp):
619 parents = [p for p in repo.changelog.parents(node) if p != nullid]
624 parents = [p for p in repo.changelog.parents(node) if p != nullid]
620 if switch_parent:
625 if switch_parent:
621 parents.reverse()
626 parents.reverse()
622 prev = (parents and parents[0]) or nullid
627 prev = (parents and parents[0]) or nullid
623 change = repo.changelog.read(node)
628 change = repo.changelog.read(node)
624
629
625 if not fp:
630 if not fp:
626 fp = cmdutil.make_file(repo, template, node, total=total,
631 fp = cmdutil.make_file(repo, template, node, total=total,
627 seqno=seqno, revwidth=revwidth)
632 seqno=seqno, revwidth=revwidth)
628 if fp not in (sys.stdout, repo.ui):
633 if fp not in (sys.stdout, repo.ui):
629 repo.ui.note("%s\n" % fp.name)
634 repo.ui.note("%s\n" % fp.name)
630
635
631 fp.write("# HG changeset patch\n")
636 fp.write("# HG changeset patch\n")
632 fp.write("# User %s\n" % change[1])
637 fp.write("# User %s\n" % change[1])
633 fp.write("# Date %d %d\n" % change[2])
638 fp.write("# Date %d %d\n" % change[2])
634 fp.write("# Node ID %s\n" % hex(node))
639 fp.write("# Node ID %s\n" % hex(node))
635 fp.write("# Parent %s\n" % hex(prev))
640 fp.write("# Parent %s\n" % hex(prev))
636 if len(parents) > 1:
641 if len(parents) > 1:
637 fp.write("# Parent %s\n" % hex(parents[1]))
642 fp.write("# Parent %s\n" % hex(parents[1]))
638 fp.write(change[4].rstrip())
643 fp.write(change[4].rstrip())
639 fp.write("\n\n")
644 fp.write("\n\n")
640
645
641 diff(repo, prev, node, fp=fp, opts=opts)
646 diff(repo, prev, node, fp=fp, opts=opts)
642 if fp not in (sys.stdout, repo.ui):
647 if fp not in (sys.stdout, repo.ui):
643 fp.close()
648 fp.close()
644
649
645 for seqno, cset in enumerate(revs):
650 for seqno, cset in enumerate(revs):
646 single(cset, seqno, fp)
651 single(cset, seqno, fp)
647
652
648 def diffstat(patchlines):
653 def diffstat(patchlines):
649 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
654 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
650 try:
655 try:
651 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
656 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
652 try:
657 try:
653 for line in patchlines: print >> p.tochild, line
658 for line in patchlines: print >> p.tochild, line
654 p.tochild.close()
659 p.tochild.close()
655 if p.wait(): return
660 if p.wait(): return
656 fp = os.fdopen(fd, 'r')
661 fp = os.fdopen(fd, 'r')
657 stat = []
662 stat = []
658 for line in fp: stat.append(line.lstrip())
663 for line in fp: stat.append(line.lstrip())
659 last = stat.pop()
664 last = stat.pop()
660 stat.insert(0, last)
665 stat.insert(0, last)
661 stat = ''.join(stat)
666 stat = ''.join(stat)
662 if stat.startswith('0 files'): raise ValueError
667 if stat.startswith('0 files'): raise ValueError
663 return stat
668 return stat
664 except: raise
669 except: raise
665 finally:
670 finally:
666 try: os.unlink(name)
671 try: os.unlink(name)
667 except: pass
672 except: pass
@@ -1,129 +1,137 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 '% diff across many revisions'
64 echo '% diff across many revisions'
65 hg mv dst dst2
65 hg mv dst dst2
66 hg ci -m 'mv dst dst2' -d '0 0'
66 hg ci -m 'mv dst dst2' -d '0 0'
67
67
68 echo >> start
68 echo >> start
69 hg ci -m 'change start' -d '0 0'
69 hg ci -m 'change start' -d '0 0'
70
70
71 hg revert -r -2 start
71 hg revert -r -2 start
72 hg mv dst2 dst3
72 hg mv dst2 dst3
73 hg ci -m 'mv dst2 dst3; revert start' -d '0 0'
73 hg ci -m 'mv dst2 dst3; revert start' -d '0 0'
74
74
75 hg diff --git -r 9:11
75 hg diff --git -r 9:11
76
76
77 echo a >> foo
77 echo a >> foo
78 hg add foo
78 hg add foo
79 hg ci -m 'add foo'
79 hg ci -m 'add foo'
80 echo b >> foo
80 echo b >> foo
81 hg ci -m 'change foo'
81 hg ci -m 'change foo'
82 hg mv foo bar
82 hg mv foo bar
83 hg ci -m 'mv foo bar'
83 hg ci -m 'mv foo bar'
84 echo c >> bar
84 echo c >> bar
85 hg ci -m 'change bar'
85 hg ci -m 'change bar'
86
86
87 echo
87 echo
88 echo '% file created before r1 and renamed before r2'
88 echo '% file created before r1 and renamed before r2'
89 hg diff --git -r -3:-1
89 hg diff --git -r -3:-1
90 echo
90 echo
91 echo '% file created in r1 and renamed before r2'
91 echo '% file created in r1 and renamed before r2'
92 hg diff --git -r -4:-1
92 hg diff --git -r -4:-1
93 echo
93 echo
94 echo '% file created after r1 and renamed before r2'
94 echo '% file created after r1 and renamed before r2'
95 hg diff --git -r -5:-1
95 hg diff --git -r -5:-1
96
96
97 echo
97 echo
98 echo '% comparing with the working dir'
98 echo '% comparing with the working dir'
99 echo >> start
99 echo >> start
100 hg ci -m 'change start again' -d '0 0'
100 hg ci -m 'change start again' -d '0 0'
101
101
102 echo > created
102 echo > created
103 hg add created
103 hg add created
104 hg ci -m 'add created'
104 hg ci -m 'add created'
105
105
106 hg mv created created2
106 hg mv created created2
107 hg ci -m 'mv created created2'
107 hg ci -m 'mv created created2'
108
108
109 hg mv created2 created3
109 hg mv created2 created3
110 echo "% there's a copy in the working dir..."
110 echo "% there's a copy in the working dir..."
111 hg diff --git
111 hg diff --git
112 echo
112 echo
113 echo "% ...but there's another copy between the original rev and the wd"
113 echo "% ...but there's another copy between the original rev and the wd"
114 hg diff --git -r -2
114 hg diff --git -r -2
115 echo
115 echo
116 echo "% ...but the source of the copy was created after the original rev"
116 echo "% ...but the source of the copy was created after the original rev"
117 hg diff --git -r -3
117 hg diff --git -r -3
118 hg ci -m 'mv created2 created3'
118 hg ci -m 'mv created2 created3'
119
119
120 echo > brand-new
120 echo > brand-new
121 hg add brand-new
121 hg add brand-new
122 hg ci -m 'add brand-new'
122 hg ci -m 'add brand-new'
123 hg mv brand-new brand-new2
123 hg mv brand-new brand-new2
124 echo '% created in parent of wd; renamed in the wd'
124 echo '% created in parent of wd; renamed in the wd'
125 hg diff --git
125 hg diff --git
126
126
127 echo
127 echo
128 echo '% created between r1 and parent of wd; renamed in the wd'
128 echo '% created between r1 and parent of wd; renamed in the wd'
129 hg diff --git -r -2
129 hg diff --git -r -2
130 hg ci -m 'mv brand-new brand-new2'
131
132 echo '% one file is copied to many destinations and removed'
133 hg cp brand-new2 brand-new3
134 hg mv brand-new2 brand-new3-2
135 hg ci -m 'multiple renames/copies'
136 hg diff --git -r -2 -r -1
137
@@ -1,135 +1,142 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 % diff across many revisions
69 % diff across many revisions
70 diff --git a/dst2 b/dst3
70 diff --git a/dst2 b/dst3
71 rename from dst2
71 rename from dst2
72 rename to dst3
72 rename to dst3
73
73
74 % file created before r1 and renamed before r2
74 % file created before r1 and renamed before r2
75 diff --git a/foo b/bar
75 diff --git a/foo b/bar
76 rename from foo
76 rename from foo
77 rename to bar
77 rename to bar
78 --- a/bar
78 --- a/bar
79 +++ b/bar
79 +++ b/bar
80 @@ -1,2 +1,3 @@ a
80 @@ -1,2 +1,3 @@ a
81 a
81 a
82 b
82 b
83 +c
83 +c
84
84
85 % file created in r1 and renamed before r2
85 % file created in r1 and renamed before r2
86 diff --git a/foo b/bar
86 diff --git a/foo b/bar
87 rename from foo
87 rename from foo
88 rename to bar
88 rename to bar
89 --- a/bar
89 --- a/bar
90 +++ b/bar
90 +++ b/bar
91 @@ -1,1 +1,3 @@ a
91 @@ -1,1 +1,3 @@ a
92 a
92 a
93 +b
93 +b
94 +c
94 +c
95
95
96 % file created after r1 and renamed before r2
96 % file created after r1 and renamed before r2
97 diff --git a/bar b/bar
97 diff --git a/bar b/bar
98 new file mode 100644
98 new file mode 100644
99 --- /dev/null
99 --- /dev/null
100 +++ b/bar
100 +++ b/bar
101 @@ -0,0 +1,3 @@
101 @@ -0,0 +1,3 @@
102 +a
102 +a
103 +b
103 +b
104 +c
104 +c
105
105
106 % comparing with the working dir
106 % comparing with the working dir
107 % there's a copy in the working dir...
107 % there's a copy in the working dir...
108 diff --git a/created2 b/created3
108 diff --git a/created2 b/created3
109 rename from created2
109 rename from created2
110 rename to created3
110 rename to created3
111
111
112 % ...but there's another copy between the original rev and the wd
112 % ...but there's another copy between the original rev and the wd
113 diff --git a/created b/created3
113 diff --git a/created b/created3
114 rename from created
114 rename from created
115 rename to created3
115 rename to created3
116
116
117 % ...but the source of the copy was created after the original rev
117 % ...but the source of the copy was created after the original rev
118 diff --git a/created3 b/created3
118 diff --git a/created3 b/created3
119 new file mode 100644
119 new file mode 100644
120 --- /dev/null
120 --- /dev/null
121 +++ b/created3
121 +++ b/created3
122 @@ -0,0 +1,1 @@
122 @@ -0,0 +1,1 @@
123 +
123 +
124 % created in parent of wd; renamed in the wd
124 % created in parent of wd; renamed in the wd
125 diff --git a/brand-new b/brand-new2
125 diff --git a/brand-new b/brand-new2
126 rename from brand-new
126 rename from brand-new
127 rename to brand-new2
127 rename to brand-new2
128
128
129 % created between r1 and parent of wd; renamed in the wd
129 % created between r1 and parent of wd; renamed in the wd
130 diff --git a/brand-new2 b/brand-new2
130 diff --git a/brand-new2 b/brand-new2
131 new file mode 100644
131 new file mode 100644
132 --- /dev/null
132 --- /dev/null
133 +++ b/brand-new2
133 +++ b/brand-new2
134 @@ -0,0 +1,1 @@
134 @@ -0,0 +1,1 @@
135 +
135 +
136 % one file is copied to many destinations and removed
137 diff --git a/brand-new2 b/brand-new3
138 rename from brand-new2
139 rename to brand-new3
140 diff --git a/brand-new2 b/brand-new3-2
141 copy from brand-new2
142 copy to brand-new3-2
General Comments 0
You need to be logged in to leave comments. Login now