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