##// END OF EJS Templates
handle git patches that rename a file to more than one destination
Alexis S. L. Carvalho -
r3701:05c8704a default
parent child Browse files
Show More
@@ -1,665 +1,667 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.append(gp.oldpath)
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.append(gp.path)
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 if removes:
363 if removes:
364 removes.sort()
363 repo.remove(removes, True, wlock=wlock)
365 repo.remove(removes, True, wlock=wlock)
364 for f in patches:
366 for f in patches:
365 ctype, gp = patches[f]
367 ctype, gp = patches[f]
366 if gp and gp.mode:
368 if gp and gp.mode:
367 x = gp.mode & 0100 != 0
369 x = gp.mode & 0100 != 0
368 dst = os.path.join(repo.root, gp.path)
370 dst = os.path.join(repo.root, gp.path)
369 # patch won't create empty files
371 # patch won't create empty files
370 if ctype == 'ADD' and not os.path.exists(dst):
372 if ctype == 'ADD' and not os.path.exists(dst):
371 repo.wwrite(gp.path, '')
373 repo.wwrite(gp.path, '')
372 util.set_exec(dst, x)
374 util.set_exec(dst, x)
373 cmdutil.addremove(repo, cfiles, wlock=wlock)
375 cmdutil.addremove(repo, cfiles, wlock=wlock)
374 files = patches.keys()
376 files = patches.keys()
375 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])
376 files.sort()
378 files.sort()
377
379
378 return files
380 return files
379
381
380 def b85diff(fp, to, tn):
382 def b85diff(fp, to, tn):
381 '''print base85-encoded binary diff'''
383 '''print base85-encoded binary diff'''
382 def gitindex(text):
384 def gitindex(text):
383 if not text:
385 if not text:
384 return '0' * 40
386 return '0' * 40
385 l = len(text)
387 l = len(text)
386 s = sha.new('blob %d\0' % l)
388 s = sha.new('blob %d\0' % l)
387 s.update(text)
389 s.update(text)
388 return s.hexdigest()
390 return s.hexdigest()
389
391
390 def fmtline(line):
392 def fmtline(line):
391 l = len(line)
393 l = len(line)
392 if l <= 26:
394 if l <= 26:
393 l = chr(ord('A') + l - 1)
395 l = chr(ord('A') + l - 1)
394 else:
396 else:
395 l = chr(l - 26 + ord('a') - 1)
397 l = chr(l - 26 + ord('a') - 1)
396 return '%c%s\n' % (l, base85.b85encode(line, True))
398 return '%c%s\n' % (l, base85.b85encode(line, True))
397
399
398 def chunk(text, csize=52):
400 def chunk(text, csize=52):
399 l = len(text)
401 l = len(text)
400 i = 0
402 i = 0
401 while i < l:
403 while i < l:
402 yield text[i:i+csize]
404 yield text[i:i+csize]
403 i += csize
405 i += csize
404
406
405 # TODO: deltas
407 # TODO: deltas
406 l = len(tn)
408 l = len(tn)
407 fp.write('index %s..%s\nGIT binary patch\nliteral %s\n' %
409 fp.write('index %s..%s\nGIT binary patch\nliteral %s\n' %
408 (gitindex(to), gitindex(tn), len(tn)))
410 (gitindex(to), gitindex(tn), len(tn)))
409
411
410 tn = ''.join([fmtline(l) for l in chunk(zlib.compress(tn))])
412 tn = ''.join([fmtline(l) for l in chunk(zlib.compress(tn))])
411 fp.write(tn)
413 fp.write(tn)
412 fp.write('\n')
414 fp.write('\n')
413
415
414 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,
415 fp=None, changes=None, opts=None):
417 fp=None, changes=None, opts=None):
416 '''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
417 working directory.
419 working directory.
418
420
419 if node1 is None, use first dirstate parent instead.
421 if node1 is None, use first dirstate parent instead.
420 if node2 is None, compare node1 with working directory.'''
422 if node2 is None, compare node1 with working directory.'''
421
423
422 if opts is None:
424 if opts is None:
423 opts = mdiff.defaultopts
425 opts = mdiff.defaultopts
424 if fp is None:
426 if fp is None:
425 fp = repo.ui
427 fp = repo.ui
426
428
427 if not node1:
429 if not node1:
428 node1 = repo.dirstate.parents()[0]
430 node1 = repo.dirstate.parents()[0]
429
431
430 clcache = {}
432 clcache = {}
431 def getchangelog(n):
433 def getchangelog(n):
432 if n not in clcache:
434 if n not in clcache:
433 clcache[n] = repo.changelog.read(n)
435 clcache[n] = repo.changelog.read(n)
434 return clcache[n]
436 return clcache[n]
435 mcache = {}
437 mcache = {}
436 def getmanifest(n):
438 def getmanifest(n):
437 if n not in mcache:
439 if n not in mcache:
438 mcache[n] = repo.manifest.read(n)
440 mcache[n] = repo.manifest.read(n)
439 return mcache[n]
441 return mcache[n]
440 fcache = {}
442 fcache = {}
441 def getfile(f):
443 def getfile(f):
442 if f not in fcache:
444 if f not in fcache:
443 fcache[f] = repo.file(f)
445 fcache[f] = repo.file(f)
444 return fcache[f]
446 return fcache[f]
445
447
446 # reading the data for node1 early allows it to play nicely
448 # reading the data for node1 early allows it to play nicely
447 # with repo.status and the revlog cache.
449 # with repo.status and the revlog cache.
448 change = getchangelog(node1)
450 change = getchangelog(node1)
449 mmap = getmanifest(change[0])
451 mmap = getmanifest(change[0])
450 date1 = util.datestr(change[2])
452 date1 = util.datestr(change[2])
451
453
452 if not changes:
454 if not changes:
453 changes = repo.status(node1, node2, files, match=match)[:5]
455 changes = repo.status(node1, node2, files, match=match)[:5]
454 modified, added, removed, deleted, unknown = changes
456 modified, added, removed, deleted, unknown = changes
455 if files:
457 if files:
456 def filterfiles(filters):
458 def filterfiles(filters):
457 l = [x for x in filters if x in files]
459 l = [x for x in filters if x in files]
458
460
459 for t in files:
461 for t in files:
460 if not t.endswith("/"):
462 if not t.endswith("/"):
461 t += "/"
463 t += "/"
462 l += [x for x in filters if x.startswith(t)]
464 l += [x for x in filters if x.startswith(t)]
463 return l
465 return l
464
466
465 modified, added, removed = map(filterfiles, (modified, added, removed))
467 modified, added, removed = map(filterfiles, (modified, added, removed))
466
468
467 if not modified and not added and not removed:
469 if not modified and not added and not removed:
468 return
470 return
469
471
470 # returns False if there was no rename between n1 and n2
472 # returns False if there was no rename between n1 and n2
471 # returns None if the file was created between n1 and n2
473 # returns None if the file was created between n1 and n2
472 # 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
473 def renamedbetween(f, n1, n2):
475 def renamedbetween(f, n1, n2):
474 r1, r2 = map(repo.changelog.rev, (n1, n2))
476 r1, r2 = map(repo.changelog.rev, (n1, n2))
475 orig = f
477 orig = f
476 src = None
478 src = None
477 while r2 > r1:
479 while r2 > r1:
478 cl = getchangelog(n2)
480 cl = getchangelog(n2)
479 if f in cl[3]:
481 if f in cl[3]:
480 m = getmanifest(cl[0])
482 m = getmanifest(cl[0])
481 try:
483 try:
482 src = getfile(f).renamed(m[f])
484 src = getfile(f).renamed(m[f])
483 except KeyError:
485 except KeyError:
484 return None
486 return None
485 if src:
487 if src:
486 f = src[0]
488 f = src[0]
487 n2 = repo.changelog.parents(n2)[0]
489 n2 = repo.changelog.parents(n2)[0]
488 r2 = repo.changelog.rev(n2)
490 r2 = repo.changelog.rev(n2)
489 cl = getchangelog(n1)
491 cl = getchangelog(n1)
490 m = getmanifest(cl[0])
492 m = getmanifest(cl[0])
491 if f not in m:
493 if f not in m:
492 return None
494 return None
493 if f == orig:
495 if f == orig:
494 return False
496 return False
495 return f, m[f]
497 return f, m[f]
496
498
497 if node2:
499 if node2:
498 change = getchangelog(node2)
500 change = getchangelog(node2)
499 mmap2 = getmanifest(change[0])
501 mmap2 = getmanifest(change[0])
500 _date2 = util.datestr(change[2])
502 _date2 = util.datestr(change[2])
501 def date2(f):
503 def date2(f):
502 return _date2
504 return _date2
503 def read(f):
505 def read(f):
504 return getfile(f).read(mmap2[f])
506 return getfile(f).read(mmap2[f])
505 def renamed(f):
507 def renamed(f):
506 return renamedbetween(f, node1, node2)
508 return renamedbetween(f, node1, node2)
507 else:
509 else:
508 tz = util.makedate()[1]
510 tz = util.makedate()[1]
509 _date2 = util.datestr()
511 _date2 = util.datestr()
510 def date2(f):
512 def date2(f):
511 try:
513 try:
512 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
514 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
513 except OSError, err:
515 except OSError, err:
514 if err.errno != errno.ENOENT: raise
516 if err.errno != errno.ENOENT: raise
515 return _date2
517 return _date2
516 def read(f):
518 def read(f):
517 return repo.wread(f)
519 return repo.wread(f)
518 def renamed(f):
520 def renamed(f):
519 src = repo.dirstate.copied(f)
521 src = repo.dirstate.copied(f)
520 parent = repo.dirstate.parents()[0]
522 parent = repo.dirstate.parents()[0]
521 if src:
523 if src:
522 f = src
524 f = src
523 of = renamedbetween(f, node1, parent)
525 of = renamedbetween(f, node1, parent)
524 if of or of is None:
526 if of or of is None:
525 return of
527 return of
526 elif src:
528 elif src:
527 cl = getchangelog(parent)[0]
529 cl = getchangelog(parent)[0]
528 return (src, getmanifest(cl)[src])
530 return (src, getmanifest(cl)[src])
529 else:
531 else:
530 return None
532 return None
531
533
532 if repo.ui.quiet:
534 if repo.ui.quiet:
533 r = None
535 r = None
534 else:
536 else:
535 hexfunc = repo.ui.debugflag and hex or short
537 hexfunc = repo.ui.debugflag and hex or short
536 r = [hexfunc(node) for node in [node1, node2] if node]
538 r = [hexfunc(node) for node in [node1, node2] if node]
537
539
538 if opts.git:
540 if opts.git:
539 copied = {}
541 copied = {}
540 for f in added:
542 for f in added:
541 src = renamed(f)
543 src = renamed(f)
542 if src:
544 if src:
543 copied[f] = src
545 copied[f] = src
544 srcs = [x[1][0] for x in copied.items()]
546 srcs = [x[1][0] for x in copied.items()]
545
547
546 all = modified + added + removed
548 all = modified + added + removed
547 all.sort()
549 all.sort()
548 for f in all:
550 for f in all:
549 to = None
551 to = None
550 tn = None
552 tn = None
551 dodiff = True
553 dodiff = True
552 header = []
554 header = []
553 if f in mmap:
555 if f in mmap:
554 to = getfile(f).read(mmap[f])
556 to = getfile(f).read(mmap[f])
555 if f not in removed:
557 if f not in removed:
556 tn = read(f)
558 tn = read(f)
557 if opts.git:
559 if opts.git:
558 def gitmode(x):
560 def gitmode(x):
559 return x and '100755' or '100644'
561 return x and '100755' or '100644'
560 def addmodehdr(header, omode, nmode):
562 def addmodehdr(header, omode, nmode):
561 if omode != nmode:
563 if omode != nmode:
562 header.append('old mode %s\n' % omode)
564 header.append('old mode %s\n' % omode)
563 header.append('new mode %s\n' % nmode)
565 header.append('new mode %s\n' % nmode)
564
566
565 a, b = f, f
567 a, b = f, f
566 if f in added:
568 if f in added:
567 if node2:
569 if node2:
568 mode = gitmode(mmap2.execf(f))
570 mode = gitmode(mmap2.execf(f))
569 else:
571 else:
570 mode = gitmode(util.is_exec(repo.wjoin(f), None))
572 mode = gitmode(util.is_exec(repo.wjoin(f), None))
571 if f in copied:
573 if f in copied:
572 a, arev = copied[f]
574 a, arev = copied[f]
573 omode = gitmode(mmap.execf(a))
575 omode = gitmode(mmap.execf(a))
574 addmodehdr(header, omode, mode)
576 addmodehdr(header, omode, mode)
575 op = a in removed and 'rename' or 'copy'
577 op = a in removed and 'rename' or 'copy'
576 header.append('%s from %s\n' % (op, a))
578 header.append('%s from %s\n' % (op, a))
577 header.append('%s to %s\n' % (op, f))
579 header.append('%s to %s\n' % (op, f))
578 to = getfile(a).read(arev)
580 to = getfile(a).read(arev)
579 else:
581 else:
580 header.append('new file mode %s\n' % mode)
582 header.append('new file mode %s\n' % mode)
581 if util.binary(tn):
583 if util.binary(tn):
582 dodiff = 'binary'
584 dodiff = 'binary'
583 elif f in removed:
585 elif f in removed:
584 if f in srcs:
586 if f in srcs:
585 dodiff = False
587 dodiff = False
586 else:
588 else:
587 mode = gitmode(mmap.execf(f))
589 mode = gitmode(mmap.execf(f))
588 header.append('deleted file mode %s\n' % mode)
590 header.append('deleted file mode %s\n' % mode)
589 else:
591 else:
590 omode = gitmode(mmap.execf(f))
592 omode = gitmode(mmap.execf(f))
591 if node2:
593 if node2:
592 nmode = gitmode(mmap2.execf(f))
594 nmode = gitmode(mmap2.execf(f))
593 else:
595 else:
594 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
596 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
595 addmodehdr(header, omode, nmode)
597 addmodehdr(header, omode, nmode)
596 if util.binary(to) or util.binary(tn):
598 if util.binary(to) or util.binary(tn):
597 dodiff = 'binary'
599 dodiff = 'binary'
598 r = None
600 r = None
599 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
601 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
600 if dodiff == 'binary':
602 if dodiff == 'binary':
601 fp.write(''.join(header))
603 fp.write(''.join(header))
602 b85diff(fp, to, tn)
604 b85diff(fp, to, tn)
603 elif dodiff:
605 elif dodiff:
604 text = mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts)
606 text = mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts)
605 if text or len(header) > 1:
607 if text or len(header) > 1:
606 fp.write(''.join(header))
608 fp.write(''.join(header))
607 fp.write(text)
609 fp.write(text)
608
610
609 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
611 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
610 opts=None):
612 opts=None):
611 '''export changesets as hg patches.'''
613 '''export changesets as hg patches.'''
612
614
613 total = len(revs)
615 total = len(revs)
614 revwidth = max(map(len, revs))
616 revwidth = max(map(len, revs))
615
617
616 def single(node, seqno, fp):
618 def single(node, seqno, fp):
617 parents = [p for p in repo.changelog.parents(node) if p != nullid]
619 parents = [p for p in repo.changelog.parents(node) if p != nullid]
618 if switch_parent:
620 if switch_parent:
619 parents.reverse()
621 parents.reverse()
620 prev = (parents and parents[0]) or nullid
622 prev = (parents and parents[0]) or nullid
621 change = repo.changelog.read(node)
623 change = repo.changelog.read(node)
622
624
623 if not fp:
625 if not fp:
624 fp = cmdutil.make_file(repo, template, node, total=total,
626 fp = cmdutil.make_file(repo, template, node, total=total,
625 seqno=seqno, revwidth=revwidth)
627 seqno=seqno, revwidth=revwidth)
626 if fp not in (sys.stdout, repo.ui):
628 if fp not in (sys.stdout, repo.ui):
627 repo.ui.note("%s\n" % fp.name)
629 repo.ui.note("%s\n" % fp.name)
628
630
629 fp.write("# HG changeset patch\n")
631 fp.write("# HG changeset patch\n")
630 fp.write("# User %s\n" % change[1])
632 fp.write("# User %s\n" % change[1])
631 fp.write("# Date %d %d\n" % change[2])
633 fp.write("# Date %d %d\n" % change[2])
632 fp.write("# Node ID %s\n" % hex(node))
634 fp.write("# Node ID %s\n" % hex(node))
633 fp.write("# Parent %s\n" % hex(prev))
635 fp.write("# Parent %s\n" % hex(prev))
634 if len(parents) > 1:
636 if len(parents) > 1:
635 fp.write("# Parent %s\n" % hex(parents[1]))
637 fp.write("# Parent %s\n" % hex(parents[1]))
636 fp.write(change[4].rstrip())
638 fp.write(change[4].rstrip())
637 fp.write("\n\n")
639 fp.write("\n\n")
638
640
639 diff(repo, prev, node, fp=fp, opts=opts)
641 diff(repo, prev, node, fp=fp, opts=opts)
640 if fp not in (sys.stdout, repo.ui):
642 if fp not in (sys.stdout, repo.ui):
641 fp.close()
643 fp.close()
642
644
643 for seqno, cset in enumerate(revs):
645 for seqno, cset in enumerate(revs):
644 single(cset, seqno, fp)
646 single(cset, seqno, fp)
645
647
646 def diffstat(patchlines):
648 def diffstat(patchlines):
647 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
649 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
648 try:
650 try:
649 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
651 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
650 try:
652 try:
651 for line in patchlines: print >> p.tochild, line
653 for line in patchlines: print >> p.tochild, line
652 p.tochild.close()
654 p.tochild.close()
653 if p.wait(): return
655 if p.wait(): return
654 fp = os.fdopen(fd, 'r')
656 fp = os.fdopen(fd, 'r')
655 stat = []
657 stat = []
656 for line in fp: stat.append(line.lstrip())
658 for line in fp: stat.append(line.lstrip())
657 last = stat.pop()
659 last = stat.pop()
658 stat.insert(0, last)
660 stat.insert(0, last)
659 stat = ''.join(stat)
661 stat = ''.join(stat)
660 if stat.startswith('0 files'): raise ValueError
662 if stat.startswith('0 files'): raise ValueError
661 return stat
663 return stat
662 except: raise
664 except: raise
663 finally:
665 finally:
664 try: os.unlink(name)
666 try: os.unlink(name)
665 except: pass
667 except: pass
@@ -1,129 +1,145 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 % new file
6 echo % new file
7 hg import -mnew - <<EOF
7 hg import -mnew - <<EOF
8 diff --git a/new b/new
8 diff --git a/new b/new
9 new file mode 100644
9 new file mode 100644
10 index 0000000..7898192
10 index 0000000..7898192
11 --- /dev/null
11 --- /dev/null
12 +++ b/new
12 +++ b/new
13 @@ -0,0 +1 @@
13 @@ -0,0 +1 @@
14 +a
14 +a
15 EOF
15 EOF
16
16
17 echo % new empty file
17 echo % new empty file
18 hg import -mempty - <<EOF
18 hg import -mempty - <<EOF
19 diff --git a/empty b/empty
19 diff --git a/empty b/empty
20 new file mode 100644
20 new file mode 100644
21 EOF
21 EOF
22 hg locate empty
22 hg locate empty
23
23
24 echo % chmod +x
24 echo % chmod +x
25 hg import -msetx - <<EOF
25 hg import -msetx - <<EOF
26 diff --git a/new b/new
26 diff --git a/new b/new
27 old mode 100644
27 old mode 100644
28 new mode 100755
28 new mode 100755
29 EOF
29 EOF
30
30
31 test -x new || echo failed
31 test -x new || echo failed
32
32
33 echo % copy
33 echo % copy
34 hg import -mcopy - <<EOF
34 hg import -mcopy - <<EOF
35 diff --git a/new b/copy
35 diff --git a/new b/copy
36 old mode 100755
36 old mode 100755
37 new mode 100644
37 new mode 100644
38 similarity index 100%
38 similarity index 100%
39 copy from new
39 copy from new
40 copy to copy
40 copy to copy
41 diff --git a/new b/copyx
41 diff --git a/new b/copyx
42 similarity index 100%
42 similarity index 100%
43 copy from new
43 copy from new
44 copy to copyx
44 copy to copyx
45 EOF
45 EOF
46
46
47 test -f copy -a ! -x copy || echo failed
47 test -f copy -a ! -x copy || echo failed
48 test -x copyx || echo failed
48 test -x copyx || echo failed
49 cat copy
49 cat copy
50 hg cat copy
50 hg cat copy
51
51
52 echo % rename
52 echo % rename
53 hg import -mrename - <<EOF
53 hg import -mrename - <<EOF
54 diff --git a/copy b/rename
54 diff --git a/copy b/rename
55 similarity index 100%
55 similarity index 100%
56 rename from copy
56 rename from copy
57 rename to rename
57 rename to rename
58 EOF
58 EOF
59
59
60 hg locate
60 hg locate
61
61
62 echo % delete
62 echo % delete
63 hg import -mdelete - <<EOF
63 hg import -mdelete - <<EOF
64 diff --git a/copyx b/copyx
64 diff --git a/copyx b/copyx
65 deleted file mode 100755
65 deleted file mode 100755
66 index 7898192..0000000
66 index 7898192..0000000
67 --- a/copyx
67 --- a/copyx
68 +++ /dev/null
68 +++ /dev/null
69 @@ -1 +0,0 @@
69 @@ -1 +0,0 @@
70 -a
70 -a
71 EOF
71 EOF
72
72
73 hg locate
73 hg locate
74 test -f copyx && echo failed || true
74 test -f copyx && echo failed || true
75
75
76 echo % regular diff
76 echo % regular diff
77 hg import -mregular - <<EOF
77 hg import -mregular - <<EOF
78 diff --git a/rename b/rename
78 diff --git a/rename b/rename
79 index 7898192..72e1fe3 100644
79 index 7898192..72e1fe3 100644
80 --- a/rename
80 --- a/rename
81 +++ b/rename
81 +++ b/rename
82 @@ -1 +1,5 @@
82 @@ -1 +1,5 @@
83 a
83 a
84 +a
84 +a
85 +a
85 +a
86 +a
86 +a
87 +a
87 +a
88 EOF
88 EOF
89
89
90 echo % copy and modify
90 echo % copy and modify
91 hg import -mcopymod - <<EOF
91 hg import -mcopymod - <<EOF
92 diff --git a/rename b/copy2
92 diff --git a/rename b/copy2
93 similarity index 80%
93 similarity index 80%
94 copy from rename
94 copy from rename
95 copy to copy2
95 copy to copy2
96 index 72e1fe3..b53c148 100644
96 index 72e1fe3..b53c148 100644
97 --- a/rename
97 --- a/rename
98 +++ b/copy2
98 +++ b/copy2
99 @@ -1,5 +1,5 @@
99 @@ -1,5 +1,5 @@
100 a
100 a
101 a
101 a
102 -a
102 -a
103 +b
103 +b
104 a
104 a
105 a
105 a
106 EOF
106 EOF
107
107
108 hg cat copy2
108 hg cat copy2
109
109
110 echo % rename and modify
110 echo % rename and modify
111 hg import -mrenamemod - <<EOF
111 hg import -mrenamemod - <<EOF
112 diff --git a/copy2 b/rename2
112 diff --git a/copy2 b/rename2
113 similarity index 80%
113 similarity index 80%
114 rename from copy2
114 rename from copy2
115 rename to rename2
115 rename to rename2
116 index b53c148..8f81e29 100644
116 index b53c148..8f81e29 100644
117 --- a/copy2
117 --- a/copy2
118 +++ b/rename2
118 +++ b/rename2
119 @@ -1,5 +1,5 @@
119 @@ -1,5 +1,5 @@
120 a
120 a
121 a
121 a
122 b
122 b
123 -a
123 -a
124 +c
124 +c
125 a
125 a
126 EOF
126 EOF
127
127
128 hg locate copy2
128 hg locate copy2
129 hg cat rename2
129 hg cat rename2
130
131 echo % one file renamed multiple times
132 hg import -mmultirenames - <<EOF
133 diff --git a/rename2 b/rename3
134 rename from rename2
135 rename to rename3
136 diff --git a/rename2 b/rename3-2
137 rename from rename2
138 rename to rename3-2
139 EOF
140 hg log -vCr. --template '{rev} {files} / {file_copies%filecopy}\n'
141
142 hg locate rename2 rename3 rename3-2
143 hg cat rename3
144 echo
145 hg cat rename3-2
@@ -1,39 +1,56 b''
1 % new file
1 % new file
2 applying patch from stdin
2 applying patch from stdin
3 % new empty file
3 % new empty file
4 applying patch from stdin
4 applying patch from stdin
5 empty
5 empty
6 % chmod +x
6 % chmod +x
7 applying patch from stdin
7 applying patch from stdin
8 % copy
8 % copy
9 applying patch from stdin
9 applying patch from stdin
10 a
10 a
11 a
11 a
12 % rename
12 % rename
13 applying patch from stdin
13 applying patch from stdin
14 copyx
14 copyx
15 empty
15 empty
16 new
16 new
17 rename
17 rename
18 % delete
18 % delete
19 applying patch from stdin
19 applying patch from stdin
20 empty
20 empty
21 new
21 new
22 rename
22 rename
23 % regular diff
23 % regular diff
24 applying patch from stdin
24 applying patch from stdin
25 % copy and modify
25 % copy and modify
26 applying patch from stdin
26 applying patch from stdin
27 a
27 a
28 a
28 a
29 b
29 b
30 a
30 a
31 a
31 a
32 % rename and modify
32 % rename and modify
33 applying patch from stdin
33 applying patch from stdin
34 copy2: No such file or directory
34 copy2: No such file or directory
35 a
35 a
36 a
36 a
37 b
37 b
38 c
38 c
39 a
39 a
40 % one file renamed multiple times
41 applying patch from stdin
42 9 rename2 rename3 rename3-2 / rename3 (rename2)rename3-2 (rename2)
43 rename2: No such file or directory
44 rename3
45 rename3-2
46 a
47 a
48 b
49 c
50 a
51
52 a
53 a
54 b
55 c
56 a
General Comments 0
You need to be logged in to leave comments. Login now