##// END OF EJS Templates
Merge with crew-stable
Patrick Mezard -
r9591:012f1244 merge default
parent child Browse files
Show More
@@ -1,1424 +1,1428
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2, incorporated herein by reference.
7 # GNU General Public License version 2, incorporated herein by reference.
8
8
9 from i18n import _
9 from i18n import _
10 from node import hex, nullid, short
10 from node import hex, nullid, short
11 import base85, cmdutil, mdiff, util, diffhelpers, copies
11 import base85, cmdutil, mdiff, util, diffhelpers, copies
12 import cStringIO, email.Parser, os, re
12 import cStringIO, email.Parser, os, re
13 import sys, tempfile, zlib
13 import sys, tempfile, zlib
14
14
15 gitre = re.compile('diff --git a/(.*) b/(.*)')
15 gitre = re.compile('diff --git a/(.*) b/(.*)')
16
16
17 class PatchError(Exception):
17 class PatchError(Exception):
18 pass
18 pass
19
19
20 class NoHunks(PatchError):
20 class NoHunks(PatchError):
21 pass
21 pass
22
22
23 # helper functions
23 # helper functions
24
24
25 def copyfile(src, dst, basedir):
25 def copyfile(src, dst, basedir):
26 abssrc, absdst = [util.canonpath(basedir, basedir, x) for x in [src, dst]]
26 abssrc, absdst = [util.canonpath(basedir, basedir, x) for x in [src, dst]]
27 if os.path.exists(absdst):
27 if os.path.exists(absdst):
28 raise util.Abort(_("cannot create %s: destination already exists") %
28 raise util.Abort(_("cannot create %s: destination already exists") %
29 dst)
29 dst)
30
30
31 dstdir = os.path.dirname(absdst)
31 dstdir = os.path.dirname(absdst)
32 if dstdir and not os.path.isdir(dstdir):
32 if dstdir and not os.path.isdir(dstdir):
33 try:
33 try:
34 os.makedirs(dstdir)
34 os.makedirs(dstdir)
35 except IOError:
35 except IOError:
36 raise util.Abort(
36 raise util.Abort(
37 _("cannot create %s: unable to create destination directory")
37 _("cannot create %s: unable to create destination directory")
38 % dst)
38 % dst)
39
39
40 util.copyfile(abssrc, absdst)
40 util.copyfile(abssrc, absdst)
41
41
42 # public functions
42 # public functions
43
43
44 def extract(ui, fileobj):
44 def extract(ui, fileobj):
45 '''extract patch from data read from fileobj.
45 '''extract patch from data read from fileobj.
46
46
47 patch can be a normal patch or contained in an email message.
47 patch can be a normal patch or contained in an email message.
48
48
49 return tuple (filename, message, user, date, node, p1, p2).
49 return tuple (filename, message, user, date, node, p1, p2).
50 Any item in the returned tuple can be None. If filename is None,
50 Any item in the returned tuple can be None. If filename is None,
51 fileobj did not contain a patch. Caller must unlink filename when done.'''
51 fileobj did not contain a patch. Caller must unlink filename when done.'''
52
52
53 # attempt to detect the start of a patch
53 # attempt to detect the start of a patch
54 # (this heuristic is borrowed from quilt)
54 # (this heuristic is borrowed from quilt)
55 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
55 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
56 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
56 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
57 r'(---|\*\*\*)[ \t])', re.MULTILINE)
57 r'(---|\*\*\*)[ \t])', re.MULTILINE)
58
58
59 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
59 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
60 tmpfp = os.fdopen(fd, 'w')
60 tmpfp = os.fdopen(fd, 'w')
61 try:
61 try:
62 msg = email.Parser.Parser().parse(fileobj)
62 msg = email.Parser.Parser().parse(fileobj)
63
63
64 subject = msg['Subject']
64 subject = msg['Subject']
65 user = msg['From']
65 user = msg['From']
66 if not subject and not user:
67 # Not an email, restore parsed headers if any
68 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
69
66 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
70 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
67 # should try to parse msg['Date']
71 # should try to parse msg['Date']
68 date = None
72 date = None
69 nodeid = None
73 nodeid = None
70 branch = None
74 branch = None
71 parents = []
75 parents = []
72
76
73 if subject:
77 if subject:
74 if subject.startswith('[PATCH'):
78 if subject.startswith('[PATCH'):
75 pend = subject.find(']')
79 pend = subject.find(']')
76 if pend >= 0:
80 if pend >= 0:
77 subject = subject[pend+1:].lstrip()
81 subject = subject[pend+1:].lstrip()
78 subject = subject.replace('\n\t', ' ')
82 subject = subject.replace('\n\t', ' ')
79 ui.debug('Subject: %s\n' % subject)
83 ui.debug('Subject: %s\n' % subject)
80 if user:
84 if user:
81 ui.debug('From: %s\n' % user)
85 ui.debug('From: %s\n' % user)
82 diffs_seen = 0
86 diffs_seen = 0
83 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
87 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
84 message = ''
88 message = ''
85 for part in msg.walk():
89 for part in msg.walk():
86 content_type = part.get_content_type()
90 content_type = part.get_content_type()
87 ui.debug('Content-Type: %s\n' % content_type)
91 ui.debug('Content-Type: %s\n' % content_type)
88 if content_type not in ok_types:
92 if content_type not in ok_types:
89 continue
93 continue
90 payload = part.get_payload(decode=True)
94 payload = part.get_payload(decode=True)
91 m = diffre.search(payload)
95 m = diffre.search(payload)
92 if m:
96 if m:
93 hgpatch = False
97 hgpatch = False
94 ignoretext = False
98 ignoretext = False
95
99
96 ui.debug('found patch at byte %d\n' % m.start(0))
100 ui.debug('found patch at byte %d\n' % m.start(0))
97 diffs_seen += 1
101 diffs_seen += 1
98 cfp = cStringIO.StringIO()
102 cfp = cStringIO.StringIO()
99 for line in payload[:m.start(0)].splitlines():
103 for line in payload[:m.start(0)].splitlines():
100 if line.startswith('# HG changeset patch'):
104 if line.startswith('# HG changeset patch'):
101 ui.debug('patch generated by hg export\n')
105 ui.debug('patch generated by hg export\n')
102 hgpatch = True
106 hgpatch = True
103 # drop earlier commit message content
107 # drop earlier commit message content
104 cfp.seek(0)
108 cfp.seek(0)
105 cfp.truncate()
109 cfp.truncate()
106 subject = None
110 subject = None
107 elif hgpatch:
111 elif hgpatch:
108 if line.startswith('# User '):
112 if line.startswith('# User '):
109 user = line[7:]
113 user = line[7:]
110 ui.debug('From: %s\n' % user)
114 ui.debug('From: %s\n' % user)
111 elif line.startswith("# Date "):
115 elif line.startswith("# Date "):
112 date = line[7:]
116 date = line[7:]
113 elif line.startswith("# Branch "):
117 elif line.startswith("# Branch "):
114 branch = line[9:]
118 branch = line[9:]
115 elif line.startswith("# Node ID "):
119 elif line.startswith("# Node ID "):
116 nodeid = line[10:]
120 nodeid = line[10:]
117 elif line.startswith("# Parent "):
121 elif line.startswith("# Parent "):
118 parents.append(line[10:])
122 parents.append(line[10:])
119 elif line == '---' and gitsendmail:
123 elif line == '---' and gitsendmail:
120 ignoretext = True
124 ignoretext = True
121 if not line.startswith('# ') and not ignoretext:
125 if not line.startswith('# ') and not ignoretext:
122 cfp.write(line)
126 cfp.write(line)
123 cfp.write('\n')
127 cfp.write('\n')
124 message = cfp.getvalue()
128 message = cfp.getvalue()
125 if tmpfp:
129 if tmpfp:
126 tmpfp.write(payload)
130 tmpfp.write(payload)
127 if not payload.endswith('\n'):
131 if not payload.endswith('\n'):
128 tmpfp.write('\n')
132 tmpfp.write('\n')
129 elif not diffs_seen and message and content_type == 'text/plain':
133 elif not diffs_seen and message and content_type == 'text/plain':
130 message += '\n' + payload
134 message += '\n' + payload
131 except:
135 except:
132 tmpfp.close()
136 tmpfp.close()
133 os.unlink(tmpname)
137 os.unlink(tmpname)
134 raise
138 raise
135
139
136 if subject and not message.startswith(subject):
140 if subject and not message.startswith(subject):
137 message = '%s\n%s' % (subject, message)
141 message = '%s\n%s' % (subject, message)
138 tmpfp.close()
142 tmpfp.close()
139 if not diffs_seen:
143 if not diffs_seen:
140 os.unlink(tmpname)
144 os.unlink(tmpname)
141 return None, message, user, date, branch, None, None, None
145 return None, message, user, date, branch, None, None, None
142 p1 = parents and parents.pop(0) or None
146 p1 = parents and parents.pop(0) or None
143 p2 = parents and parents.pop(0) or None
147 p2 = parents and parents.pop(0) or None
144 return tmpname, message, user, date, branch, nodeid, p1, p2
148 return tmpname, message, user, date, branch, nodeid, p1, p2
145
149
146 GP_PATCH = 1 << 0 # we have to run patch
150 GP_PATCH = 1 << 0 # we have to run patch
147 GP_FILTER = 1 << 1 # there's some copy/rename operation
151 GP_FILTER = 1 << 1 # there's some copy/rename operation
148 GP_BINARY = 1 << 2 # there's a binary patch
152 GP_BINARY = 1 << 2 # there's a binary patch
149
153
150 class patchmeta(object):
154 class patchmeta(object):
151 """Patched file metadata
155 """Patched file metadata
152
156
153 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
157 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
154 or COPY. 'path' is patched file path. 'oldpath' is set to the
158 or COPY. 'path' is patched file path. 'oldpath' is set to the
155 origin file when 'op' is either COPY or RENAME, None otherwise. If
159 origin file when 'op' is either COPY or RENAME, None otherwise. If
156 file mode is changed, 'mode' is a tuple (islink, isexec) where
160 file mode is changed, 'mode' is a tuple (islink, isexec) where
157 'islink' is True if the file is a symlink and 'isexec' is True if
161 'islink' is True if the file is a symlink and 'isexec' is True if
158 the file is executable. Otherwise, 'mode' is None.
162 the file is executable. Otherwise, 'mode' is None.
159 """
163 """
160 def __init__(self, path):
164 def __init__(self, path):
161 self.path = path
165 self.path = path
162 self.oldpath = None
166 self.oldpath = None
163 self.mode = None
167 self.mode = None
164 self.op = 'MODIFY'
168 self.op = 'MODIFY'
165 self.lineno = 0
169 self.lineno = 0
166 self.binary = False
170 self.binary = False
167
171
168 def setmode(self, mode):
172 def setmode(self, mode):
169 islink = mode & 020000
173 islink = mode & 020000
170 isexec = mode & 0100
174 isexec = mode & 0100
171 self.mode = (islink, isexec)
175 self.mode = (islink, isexec)
172
176
173 def readgitpatch(lr):
177 def readgitpatch(lr):
174 """extract git-style metadata about patches from <patchname>"""
178 """extract git-style metadata about patches from <patchname>"""
175
179
176 # Filter patch for git information
180 # Filter patch for git information
177 gp = None
181 gp = None
178 gitpatches = []
182 gitpatches = []
179 # Can have a git patch with only metadata, causing patch to complain
183 # Can have a git patch with only metadata, causing patch to complain
180 dopatch = 0
184 dopatch = 0
181
185
182 lineno = 0
186 lineno = 0
183 for line in lr:
187 for line in lr:
184 lineno += 1
188 lineno += 1
185 line = line.rstrip(' \r\n')
189 line = line.rstrip(' \r\n')
186 if line.startswith('diff --git'):
190 if line.startswith('diff --git'):
187 m = gitre.match(line)
191 m = gitre.match(line)
188 if m:
192 if m:
189 if gp:
193 if gp:
190 gitpatches.append(gp)
194 gitpatches.append(gp)
191 dst = m.group(2)
195 dst = m.group(2)
192 gp = patchmeta(dst)
196 gp = patchmeta(dst)
193 gp.lineno = lineno
197 gp.lineno = lineno
194 elif gp:
198 elif gp:
195 if line.startswith('--- '):
199 if line.startswith('--- '):
196 if gp.op in ('COPY', 'RENAME'):
200 if gp.op in ('COPY', 'RENAME'):
197 dopatch |= GP_FILTER
201 dopatch |= GP_FILTER
198 gitpatches.append(gp)
202 gitpatches.append(gp)
199 gp = None
203 gp = None
200 dopatch |= GP_PATCH
204 dopatch |= GP_PATCH
201 continue
205 continue
202 if line.startswith('rename from '):
206 if line.startswith('rename from '):
203 gp.op = 'RENAME'
207 gp.op = 'RENAME'
204 gp.oldpath = line[12:]
208 gp.oldpath = line[12:]
205 elif line.startswith('rename to '):
209 elif line.startswith('rename to '):
206 gp.path = line[10:]
210 gp.path = line[10:]
207 elif line.startswith('copy from '):
211 elif line.startswith('copy from '):
208 gp.op = 'COPY'
212 gp.op = 'COPY'
209 gp.oldpath = line[10:]
213 gp.oldpath = line[10:]
210 elif line.startswith('copy to '):
214 elif line.startswith('copy to '):
211 gp.path = line[8:]
215 gp.path = line[8:]
212 elif line.startswith('deleted file'):
216 elif line.startswith('deleted file'):
213 gp.op = 'DELETE'
217 gp.op = 'DELETE'
214 # is the deleted file a symlink?
218 # is the deleted file a symlink?
215 gp.setmode(int(line[-6:], 8))
219 gp.setmode(int(line[-6:], 8))
216 elif line.startswith('new file mode '):
220 elif line.startswith('new file mode '):
217 gp.op = 'ADD'
221 gp.op = 'ADD'
218 gp.setmode(int(line[-6:], 8))
222 gp.setmode(int(line[-6:], 8))
219 elif line.startswith('new mode '):
223 elif line.startswith('new mode '):
220 gp.setmode(int(line[-6:], 8))
224 gp.setmode(int(line[-6:], 8))
221 elif line.startswith('GIT binary patch'):
225 elif line.startswith('GIT binary patch'):
222 dopatch |= GP_BINARY
226 dopatch |= GP_BINARY
223 gp.binary = True
227 gp.binary = True
224 if gp:
228 if gp:
225 gitpatches.append(gp)
229 gitpatches.append(gp)
226
230
227 if not gitpatches:
231 if not gitpatches:
228 dopatch = GP_PATCH
232 dopatch = GP_PATCH
229
233
230 return (dopatch, gitpatches)
234 return (dopatch, gitpatches)
231
235
232 class linereader(object):
236 class linereader(object):
233 # simple class to allow pushing lines back into the input stream
237 # simple class to allow pushing lines back into the input stream
234 def __init__(self, fp, textmode=False):
238 def __init__(self, fp, textmode=False):
235 self.fp = fp
239 self.fp = fp
236 self.buf = []
240 self.buf = []
237 self.textmode = textmode
241 self.textmode = textmode
238
242
239 def push(self, line):
243 def push(self, line):
240 if line is not None:
244 if line is not None:
241 self.buf.append(line)
245 self.buf.append(line)
242
246
243 def readline(self):
247 def readline(self):
244 if self.buf:
248 if self.buf:
245 l = self.buf[0]
249 l = self.buf[0]
246 del self.buf[0]
250 del self.buf[0]
247 return l
251 return l
248 l = self.fp.readline()
252 l = self.fp.readline()
249 if self.textmode and l.endswith('\r\n'):
253 if self.textmode and l.endswith('\r\n'):
250 l = l[:-2] + '\n'
254 l = l[:-2] + '\n'
251 return l
255 return l
252
256
253 def __iter__(self):
257 def __iter__(self):
254 while 1:
258 while 1:
255 l = self.readline()
259 l = self.readline()
256 if not l:
260 if not l:
257 break
261 break
258 yield l
262 yield l
259
263
260 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
264 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
261 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
265 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
262 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
266 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
263
267
264 class patchfile(object):
268 class patchfile(object):
265 def __init__(self, ui, fname, opener, missing=False, eol=None):
269 def __init__(self, ui, fname, opener, missing=False, eol=None):
266 self.fname = fname
270 self.fname = fname
267 self.eol = eol
271 self.eol = eol
268 self.opener = opener
272 self.opener = opener
269 self.ui = ui
273 self.ui = ui
270 self.lines = []
274 self.lines = []
271 self.exists = False
275 self.exists = False
272 self.missing = missing
276 self.missing = missing
273 if not missing:
277 if not missing:
274 try:
278 try:
275 self.lines = self.readlines(fname)
279 self.lines = self.readlines(fname)
276 self.exists = True
280 self.exists = True
277 except IOError:
281 except IOError:
278 pass
282 pass
279 else:
283 else:
280 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
284 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
281
285
282 self.hash = {}
286 self.hash = {}
283 self.dirty = 0
287 self.dirty = 0
284 self.offset = 0
288 self.offset = 0
285 self.rej = []
289 self.rej = []
286 self.fileprinted = False
290 self.fileprinted = False
287 self.printfile(False)
291 self.printfile(False)
288 self.hunks = 0
292 self.hunks = 0
289
293
290 def readlines(self, fname):
294 def readlines(self, fname):
291 fp = self.opener(fname, 'r')
295 fp = self.opener(fname, 'r')
292 try:
296 try:
293 return list(linereader(fp, self.eol is not None))
297 return list(linereader(fp, self.eol is not None))
294 finally:
298 finally:
295 fp.close()
299 fp.close()
296
300
297 def writelines(self, fname, lines):
301 def writelines(self, fname, lines):
298 fp = self.opener(fname, 'w')
302 fp = self.opener(fname, 'w')
299 try:
303 try:
300 if self.eol and self.eol != '\n':
304 if self.eol and self.eol != '\n':
301 for l in lines:
305 for l in lines:
302 if l and l[-1] == '\n':
306 if l and l[-1] == '\n':
303 l = l[:-1] + self.eol
307 l = l[:-1] + self.eol
304 fp.write(l)
308 fp.write(l)
305 else:
309 else:
306 fp.writelines(lines)
310 fp.writelines(lines)
307 finally:
311 finally:
308 fp.close()
312 fp.close()
309
313
310 def unlink(self, fname):
314 def unlink(self, fname):
311 os.unlink(fname)
315 os.unlink(fname)
312
316
313 def printfile(self, warn):
317 def printfile(self, warn):
314 if self.fileprinted:
318 if self.fileprinted:
315 return
319 return
316 if warn or self.ui.verbose:
320 if warn or self.ui.verbose:
317 self.fileprinted = True
321 self.fileprinted = True
318 s = _("patching file %s\n") % self.fname
322 s = _("patching file %s\n") % self.fname
319 if warn:
323 if warn:
320 self.ui.warn(s)
324 self.ui.warn(s)
321 else:
325 else:
322 self.ui.note(s)
326 self.ui.note(s)
323
327
324
328
325 def findlines(self, l, linenum):
329 def findlines(self, l, linenum):
326 # looks through the hash and finds candidate lines. The
330 # looks through the hash and finds candidate lines. The
327 # result is a list of line numbers sorted based on distance
331 # result is a list of line numbers sorted based on distance
328 # from linenum
332 # from linenum
329
333
330 try:
334 try:
331 cand = self.hash[l]
335 cand = self.hash[l]
332 except:
336 except:
333 return []
337 return []
334
338
335 if len(cand) > 1:
339 if len(cand) > 1:
336 # resort our list of potentials forward then back.
340 # resort our list of potentials forward then back.
337 cand.sort(key=lambda x: abs(x - linenum))
341 cand.sort(key=lambda x: abs(x - linenum))
338 return cand
342 return cand
339
343
340 def hashlines(self):
344 def hashlines(self):
341 self.hash = {}
345 self.hash = {}
342 for x, s in enumerate(self.lines):
346 for x, s in enumerate(self.lines):
343 self.hash.setdefault(s, []).append(x)
347 self.hash.setdefault(s, []).append(x)
344
348
345 def write_rej(self):
349 def write_rej(self):
346 # our rejects are a little different from patch(1). This always
350 # our rejects are a little different from patch(1). This always
347 # creates rejects in the same form as the original patch. A file
351 # creates rejects in the same form as the original patch. A file
348 # header is inserted so that you can run the reject through patch again
352 # header is inserted so that you can run the reject through patch again
349 # without having to type the filename.
353 # without having to type the filename.
350
354
351 if not self.rej:
355 if not self.rej:
352 return
356 return
353
357
354 fname = self.fname + ".rej"
358 fname = self.fname + ".rej"
355 self.ui.warn(
359 self.ui.warn(
356 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
360 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
357 (len(self.rej), self.hunks, fname))
361 (len(self.rej), self.hunks, fname))
358
362
359 def rejlines():
363 def rejlines():
360 base = os.path.basename(self.fname)
364 base = os.path.basename(self.fname)
361 yield "--- %s\n+++ %s\n" % (base, base)
365 yield "--- %s\n+++ %s\n" % (base, base)
362 for x in self.rej:
366 for x in self.rej:
363 for l in x.hunk:
367 for l in x.hunk:
364 yield l
368 yield l
365 if l[-1] != '\n':
369 if l[-1] != '\n':
366 yield "\n\ No newline at end of file\n"
370 yield "\n\ No newline at end of file\n"
367
371
368 self.writelines(fname, rejlines())
372 self.writelines(fname, rejlines())
369
373
370 def write(self, dest=None):
374 def write(self, dest=None):
371 if not self.dirty:
375 if not self.dirty:
372 return
376 return
373 if not dest:
377 if not dest:
374 dest = self.fname
378 dest = self.fname
375 self.writelines(dest, self.lines)
379 self.writelines(dest, self.lines)
376
380
377 def close(self):
381 def close(self):
378 self.write()
382 self.write()
379 self.write_rej()
383 self.write_rej()
380
384
381 def apply(self, h):
385 def apply(self, h):
382 if not h.complete():
386 if not h.complete():
383 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
387 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
384 (h.number, h.desc, len(h.a), h.lena, len(h.b),
388 (h.number, h.desc, len(h.a), h.lena, len(h.b),
385 h.lenb))
389 h.lenb))
386
390
387 self.hunks += 1
391 self.hunks += 1
388
392
389 if self.missing:
393 if self.missing:
390 self.rej.append(h)
394 self.rej.append(h)
391 return -1
395 return -1
392
396
393 if self.exists and h.createfile():
397 if self.exists and h.createfile():
394 self.ui.warn(_("file %s already exists\n") % self.fname)
398 self.ui.warn(_("file %s already exists\n") % self.fname)
395 self.rej.append(h)
399 self.rej.append(h)
396 return -1
400 return -1
397
401
398 if isinstance(h, githunk):
402 if isinstance(h, githunk):
399 if h.rmfile():
403 if h.rmfile():
400 self.unlink(self.fname)
404 self.unlink(self.fname)
401 else:
405 else:
402 self.lines[:] = h.new()
406 self.lines[:] = h.new()
403 self.offset += len(h.new())
407 self.offset += len(h.new())
404 self.dirty = 1
408 self.dirty = 1
405 return 0
409 return 0
406
410
407 # fast case first, no offsets, no fuzz
411 # fast case first, no offsets, no fuzz
408 old = h.old()
412 old = h.old()
409 # patch starts counting at 1 unless we are adding the file
413 # patch starts counting at 1 unless we are adding the file
410 if h.starta == 0:
414 if h.starta == 0:
411 start = 0
415 start = 0
412 else:
416 else:
413 start = h.starta + self.offset - 1
417 start = h.starta + self.offset - 1
414 orig_start = start
418 orig_start = start
415 if diffhelpers.testhunk(old, self.lines, start) == 0:
419 if diffhelpers.testhunk(old, self.lines, start) == 0:
416 if h.rmfile():
420 if h.rmfile():
417 self.unlink(self.fname)
421 self.unlink(self.fname)
418 else:
422 else:
419 self.lines[start : start + h.lena] = h.new()
423 self.lines[start : start + h.lena] = h.new()
420 self.offset += h.lenb - h.lena
424 self.offset += h.lenb - h.lena
421 self.dirty = 1
425 self.dirty = 1
422 return 0
426 return 0
423
427
424 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
428 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
425 self.hashlines()
429 self.hashlines()
426 if h.hunk[-1][0] != ' ':
430 if h.hunk[-1][0] != ' ':
427 # if the hunk tried to put something at the bottom of the file
431 # if the hunk tried to put something at the bottom of the file
428 # override the start line and use eof here
432 # override the start line and use eof here
429 search_start = len(self.lines)
433 search_start = len(self.lines)
430 else:
434 else:
431 search_start = orig_start
435 search_start = orig_start
432
436
433 for fuzzlen in xrange(3):
437 for fuzzlen in xrange(3):
434 for toponly in [ True, False ]:
438 for toponly in [ True, False ]:
435 old = h.old(fuzzlen, toponly)
439 old = h.old(fuzzlen, toponly)
436
440
437 cand = self.findlines(old[0][1:], search_start)
441 cand = self.findlines(old[0][1:], search_start)
438 for l in cand:
442 for l in cand:
439 if diffhelpers.testhunk(old, self.lines, l) == 0:
443 if diffhelpers.testhunk(old, self.lines, l) == 0:
440 newlines = h.new(fuzzlen, toponly)
444 newlines = h.new(fuzzlen, toponly)
441 self.lines[l : l + len(old)] = newlines
445 self.lines[l : l + len(old)] = newlines
442 self.offset += len(newlines) - len(old)
446 self.offset += len(newlines) - len(old)
443 self.dirty = 1
447 self.dirty = 1
444 if fuzzlen:
448 if fuzzlen:
445 fuzzstr = "with fuzz %d " % fuzzlen
449 fuzzstr = "with fuzz %d " % fuzzlen
446 f = self.ui.warn
450 f = self.ui.warn
447 self.printfile(True)
451 self.printfile(True)
448 else:
452 else:
449 fuzzstr = ""
453 fuzzstr = ""
450 f = self.ui.note
454 f = self.ui.note
451 offset = l - orig_start - fuzzlen
455 offset = l - orig_start - fuzzlen
452 if offset == 1:
456 if offset == 1:
453 msg = _("Hunk #%d succeeded at %d %s"
457 msg = _("Hunk #%d succeeded at %d %s"
454 "(offset %d line).\n")
458 "(offset %d line).\n")
455 else:
459 else:
456 msg = _("Hunk #%d succeeded at %d %s"
460 msg = _("Hunk #%d succeeded at %d %s"
457 "(offset %d lines).\n")
461 "(offset %d lines).\n")
458 f(msg % (h.number, l+1, fuzzstr, offset))
462 f(msg % (h.number, l+1, fuzzstr, offset))
459 return fuzzlen
463 return fuzzlen
460 self.printfile(True)
464 self.printfile(True)
461 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
465 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
462 self.rej.append(h)
466 self.rej.append(h)
463 return -1
467 return -1
464
468
465 class hunk(object):
469 class hunk(object):
466 def __init__(self, desc, num, lr, context, create=False, remove=False):
470 def __init__(self, desc, num, lr, context, create=False, remove=False):
467 self.number = num
471 self.number = num
468 self.desc = desc
472 self.desc = desc
469 self.hunk = [ desc ]
473 self.hunk = [ desc ]
470 self.a = []
474 self.a = []
471 self.b = []
475 self.b = []
472 if context:
476 if context:
473 self.read_context_hunk(lr)
477 self.read_context_hunk(lr)
474 else:
478 else:
475 self.read_unified_hunk(lr)
479 self.read_unified_hunk(lr)
476 self.create = create
480 self.create = create
477 self.remove = remove and not create
481 self.remove = remove and not create
478
482
479 def read_unified_hunk(self, lr):
483 def read_unified_hunk(self, lr):
480 m = unidesc.match(self.desc)
484 m = unidesc.match(self.desc)
481 if not m:
485 if not m:
482 raise PatchError(_("bad hunk #%d") % self.number)
486 raise PatchError(_("bad hunk #%d") % self.number)
483 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
487 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
484 if self.lena is None:
488 if self.lena is None:
485 self.lena = 1
489 self.lena = 1
486 else:
490 else:
487 self.lena = int(self.lena)
491 self.lena = int(self.lena)
488 if self.lenb is None:
492 if self.lenb is None:
489 self.lenb = 1
493 self.lenb = 1
490 else:
494 else:
491 self.lenb = int(self.lenb)
495 self.lenb = int(self.lenb)
492 self.starta = int(self.starta)
496 self.starta = int(self.starta)
493 self.startb = int(self.startb)
497 self.startb = int(self.startb)
494 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
498 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
495 # if we hit eof before finishing out the hunk, the last line will
499 # if we hit eof before finishing out the hunk, the last line will
496 # be zero length. Lets try to fix it up.
500 # be zero length. Lets try to fix it up.
497 while len(self.hunk[-1]) == 0:
501 while len(self.hunk[-1]) == 0:
498 del self.hunk[-1]
502 del self.hunk[-1]
499 del self.a[-1]
503 del self.a[-1]
500 del self.b[-1]
504 del self.b[-1]
501 self.lena -= 1
505 self.lena -= 1
502 self.lenb -= 1
506 self.lenb -= 1
503
507
504 def read_context_hunk(self, lr):
508 def read_context_hunk(self, lr):
505 self.desc = lr.readline()
509 self.desc = lr.readline()
506 m = contextdesc.match(self.desc)
510 m = contextdesc.match(self.desc)
507 if not m:
511 if not m:
508 raise PatchError(_("bad hunk #%d") % self.number)
512 raise PatchError(_("bad hunk #%d") % self.number)
509 foo, self.starta, foo2, aend, foo3 = m.groups()
513 foo, self.starta, foo2, aend, foo3 = m.groups()
510 self.starta = int(self.starta)
514 self.starta = int(self.starta)
511 if aend is None:
515 if aend is None:
512 aend = self.starta
516 aend = self.starta
513 self.lena = int(aend) - self.starta
517 self.lena = int(aend) - self.starta
514 if self.starta:
518 if self.starta:
515 self.lena += 1
519 self.lena += 1
516 for x in xrange(self.lena):
520 for x in xrange(self.lena):
517 l = lr.readline()
521 l = lr.readline()
518 if l.startswith('---'):
522 if l.startswith('---'):
519 lr.push(l)
523 lr.push(l)
520 break
524 break
521 s = l[2:]
525 s = l[2:]
522 if l.startswith('- ') or l.startswith('! '):
526 if l.startswith('- ') or l.startswith('! '):
523 u = '-' + s
527 u = '-' + s
524 elif l.startswith(' '):
528 elif l.startswith(' '):
525 u = ' ' + s
529 u = ' ' + s
526 else:
530 else:
527 raise PatchError(_("bad hunk #%d old text line %d") %
531 raise PatchError(_("bad hunk #%d old text line %d") %
528 (self.number, x))
532 (self.number, x))
529 self.a.append(u)
533 self.a.append(u)
530 self.hunk.append(u)
534 self.hunk.append(u)
531
535
532 l = lr.readline()
536 l = lr.readline()
533 if l.startswith('\ '):
537 if l.startswith('\ '):
534 s = self.a[-1][:-1]
538 s = self.a[-1][:-1]
535 self.a[-1] = s
539 self.a[-1] = s
536 self.hunk[-1] = s
540 self.hunk[-1] = s
537 l = lr.readline()
541 l = lr.readline()
538 m = contextdesc.match(l)
542 m = contextdesc.match(l)
539 if not m:
543 if not m:
540 raise PatchError(_("bad hunk #%d") % self.number)
544 raise PatchError(_("bad hunk #%d") % self.number)
541 foo, self.startb, foo2, bend, foo3 = m.groups()
545 foo, self.startb, foo2, bend, foo3 = m.groups()
542 self.startb = int(self.startb)
546 self.startb = int(self.startb)
543 if bend is None:
547 if bend is None:
544 bend = self.startb
548 bend = self.startb
545 self.lenb = int(bend) - self.startb
549 self.lenb = int(bend) - self.startb
546 if self.startb:
550 if self.startb:
547 self.lenb += 1
551 self.lenb += 1
548 hunki = 1
552 hunki = 1
549 for x in xrange(self.lenb):
553 for x in xrange(self.lenb):
550 l = lr.readline()
554 l = lr.readline()
551 if l.startswith('\ '):
555 if l.startswith('\ '):
552 s = self.b[-1][:-1]
556 s = self.b[-1][:-1]
553 self.b[-1] = s
557 self.b[-1] = s
554 self.hunk[hunki-1] = s
558 self.hunk[hunki-1] = s
555 continue
559 continue
556 if not l:
560 if not l:
557 lr.push(l)
561 lr.push(l)
558 break
562 break
559 s = l[2:]
563 s = l[2:]
560 if l.startswith('+ ') or l.startswith('! '):
564 if l.startswith('+ ') or l.startswith('! '):
561 u = '+' + s
565 u = '+' + s
562 elif l.startswith(' '):
566 elif l.startswith(' '):
563 u = ' ' + s
567 u = ' ' + s
564 elif len(self.b) == 0:
568 elif len(self.b) == 0:
565 # this can happen when the hunk does not add any lines
569 # this can happen when the hunk does not add any lines
566 lr.push(l)
570 lr.push(l)
567 break
571 break
568 else:
572 else:
569 raise PatchError(_("bad hunk #%d old text line %d") %
573 raise PatchError(_("bad hunk #%d old text line %d") %
570 (self.number, x))
574 (self.number, x))
571 self.b.append(s)
575 self.b.append(s)
572 while True:
576 while True:
573 if hunki >= len(self.hunk):
577 if hunki >= len(self.hunk):
574 h = ""
578 h = ""
575 else:
579 else:
576 h = self.hunk[hunki]
580 h = self.hunk[hunki]
577 hunki += 1
581 hunki += 1
578 if h == u:
582 if h == u:
579 break
583 break
580 elif h.startswith('-'):
584 elif h.startswith('-'):
581 continue
585 continue
582 else:
586 else:
583 self.hunk.insert(hunki-1, u)
587 self.hunk.insert(hunki-1, u)
584 break
588 break
585
589
586 if not self.a:
590 if not self.a:
587 # this happens when lines were only added to the hunk
591 # this happens when lines were only added to the hunk
588 for x in self.hunk:
592 for x in self.hunk:
589 if x.startswith('-') or x.startswith(' '):
593 if x.startswith('-') or x.startswith(' '):
590 self.a.append(x)
594 self.a.append(x)
591 if not self.b:
595 if not self.b:
592 # this happens when lines were only deleted from the hunk
596 # this happens when lines were only deleted from the hunk
593 for x in self.hunk:
597 for x in self.hunk:
594 if x.startswith('+') or x.startswith(' '):
598 if x.startswith('+') or x.startswith(' '):
595 self.b.append(x[1:])
599 self.b.append(x[1:])
596 # @@ -start,len +start,len @@
600 # @@ -start,len +start,len @@
597 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
601 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
598 self.startb, self.lenb)
602 self.startb, self.lenb)
599 self.hunk[0] = self.desc
603 self.hunk[0] = self.desc
600
604
601 def fix_newline(self):
605 def fix_newline(self):
602 diffhelpers.fix_newline(self.hunk, self.a, self.b)
606 diffhelpers.fix_newline(self.hunk, self.a, self.b)
603
607
604 def complete(self):
608 def complete(self):
605 return len(self.a) == self.lena and len(self.b) == self.lenb
609 return len(self.a) == self.lena and len(self.b) == self.lenb
606
610
607 def createfile(self):
611 def createfile(self):
608 return self.starta == 0 and self.lena == 0 and self.create
612 return self.starta == 0 and self.lena == 0 and self.create
609
613
610 def rmfile(self):
614 def rmfile(self):
611 return self.startb == 0 and self.lenb == 0 and self.remove
615 return self.startb == 0 and self.lenb == 0 and self.remove
612
616
613 def fuzzit(self, l, fuzz, toponly):
617 def fuzzit(self, l, fuzz, toponly):
614 # this removes context lines from the top and bottom of list 'l'. It
618 # this removes context lines from the top and bottom of list 'l'. It
615 # checks the hunk to make sure only context lines are removed, and then
619 # checks the hunk to make sure only context lines are removed, and then
616 # returns a new shortened list of lines.
620 # returns a new shortened list of lines.
617 fuzz = min(fuzz, len(l)-1)
621 fuzz = min(fuzz, len(l)-1)
618 if fuzz:
622 if fuzz:
619 top = 0
623 top = 0
620 bot = 0
624 bot = 0
621 hlen = len(self.hunk)
625 hlen = len(self.hunk)
622 for x in xrange(hlen-1):
626 for x in xrange(hlen-1):
623 # the hunk starts with the @@ line, so use x+1
627 # the hunk starts with the @@ line, so use x+1
624 if self.hunk[x+1][0] == ' ':
628 if self.hunk[x+1][0] == ' ':
625 top += 1
629 top += 1
626 else:
630 else:
627 break
631 break
628 if not toponly:
632 if not toponly:
629 for x in xrange(hlen-1):
633 for x in xrange(hlen-1):
630 if self.hunk[hlen-bot-1][0] == ' ':
634 if self.hunk[hlen-bot-1][0] == ' ':
631 bot += 1
635 bot += 1
632 else:
636 else:
633 break
637 break
634
638
635 # top and bot now count context in the hunk
639 # top and bot now count context in the hunk
636 # adjust them if either one is short
640 # adjust them if either one is short
637 context = max(top, bot, 3)
641 context = max(top, bot, 3)
638 if bot < context:
642 if bot < context:
639 bot = max(0, fuzz - (context - bot))
643 bot = max(0, fuzz - (context - bot))
640 else:
644 else:
641 bot = min(fuzz, bot)
645 bot = min(fuzz, bot)
642 if top < context:
646 if top < context:
643 top = max(0, fuzz - (context - top))
647 top = max(0, fuzz - (context - top))
644 else:
648 else:
645 top = min(fuzz, top)
649 top = min(fuzz, top)
646
650
647 return l[top:len(l)-bot]
651 return l[top:len(l)-bot]
648 return l
652 return l
649
653
650 def old(self, fuzz=0, toponly=False):
654 def old(self, fuzz=0, toponly=False):
651 return self.fuzzit(self.a, fuzz, toponly)
655 return self.fuzzit(self.a, fuzz, toponly)
652
656
653 def newctrl(self):
657 def newctrl(self):
654 res = []
658 res = []
655 for x in self.hunk:
659 for x in self.hunk:
656 c = x[0]
660 c = x[0]
657 if c == ' ' or c == '+':
661 if c == ' ' or c == '+':
658 res.append(x)
662 res.append(x)
659 return res
663 return res
660
664
661 def new(self, fuzz=0, toponly=False):
665 def new(self, fuzz=0, toponly=False):
662 return self.fuzzit(self.b, fuzz, toponly)
666 return self.fuzzit(self.b, fuzz, toponly)
663
667
664 class githunk(object):
668 class githunk(object):
665 """A git hunk"""
669 """A git hunk"""
666 def __init__(self, gitpatch):
670 def __init__(self, gitpatch):
667 self.gitpatch = gitpatch
671 self.gitpatch = gitpatch
668 self.text = None
672 self.text = None
669 self.hunk = []
673 self.hunk = []
670
674
671 def createfile(self):
675 def createfile(self):
672 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
676 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
673
677
674 def rmfile(self):
678 def rmfile(self):
675 return self.gitpatch.op == 'DELETE'
679 return self.gitpatch.op == 'DELETE'
676
680
677 def complete(self):
681 def complete(self):
678 return self.text is not None
682 return self.text is not None
679
683
680 def new(self):
684 def new(self):
681 return [self.text]
685 return [self.text]
682
686
683 class binhunk(githunk):
687 class binhunk(githunk):
684 'A binary patch file. Only understands literals so far.'
688 'A binary patch file. Only understands literals so far.'
685 def __init__(self, gitpatch):
689 def __init__(self, gitpatch):
686 super(binhunk, self).__init__(gitpatch)
690 super(binhunk, self).__init__(gitpatch)
687 self.hunk = ['GIT binary patch\n']
691 self.hunk = ['GIT binary patch\n']
688
692
689 def extract(self, lr):
693 def extract(self, lr):
690 line = lr.readline()
694 line = lr.readline()
691 self.hunk.append(line)
695 self.hunk.append(line)
692 while line and not line.startswith('literal '):
696 while line and not line.startswith('literal '):
693 line = lr.readline()
697 line = lr.readline()
694 self.hunk.append(line)
698 self.hunk.append(line)
695 if not line:
699 if not line:
696 raise PatchError(_('could not extract binary patch'))
700 raise PatchError(_('could not extract binary patch'))
697 size = int(line[8:].rstrip())
701 size = int(line[8:].rstrip())
698 dec = []
702 dec = []
699 line = lr.readline()
703 line = lr.readline()
700 self.hunk.append(line)
704 self.hunk.append(line)
701 while len(line) > 1:
705 while len(line) > 1:
702 l = line[0]
706 l = line[0]
703 if l <= 'Z' and l >= 'A':
707 if l <= 'Z' and l >= 'A':
704 l = ord(l) - ord('A') + 1
708 l = ord(l) - ord('A') + 1
705 else:
709 else:
706 l = ord(l) - ord('a') + 27
710 l = ord(l) - ord('a') + 27
707 dec.append(base85.b85decode(line[1:-1])[:l])
711 dec.append(base85.b85decode(line[1:-1])[:l])
708 line = lr.readline()
712 line = lr.readline()
709 self.hunk.append(line)
713 self.hunk.append(line)
710 text = zlib.decompress(''.join(dec))
714 text = zlib.decompress(''.join(dec))
711 if len(text) != size:
715 if len(text) != size:
712 raise PatchError(_('binary patch is %d bytes, not %d') %
716 raise PatchError(_('binary patch is %d bytes, not %d') %
713 len(text), size)
717 len(text), size)
714 self.text = text
718 self.text = text
715
719
716 class symlinkhunk(githunk):
720 class symlinkhunk(githunk):
717 """A git symlink hunk"""
721 """A git symlink hunk"""
718 def __init__(self, gitpatch, hunk):
722 def __init__(self, gitpatch, hunk):
719 super(symlinkhunk, self).__init__(gitpatch)
723 super(symlinkhunk, self).__init__(gitpatch)
720 self.hunk = hunk
724 self.hunk = hunk
721
725
722 def complete(self):
726 def complete(self):
723 return True
727 return True
724
728
725 def fix_newline(self):
729 def fix_newline(self):
726 return
730 return
727
731
728 def parsefilename(str):
732 def parsefilename(str):
729 # --- filename \t|space stuff
733 # --- filename \t|space stuff
730 s = str[4:].rstrip('\r\n')
734 s = str[4:].rstrip('\r\n')
731 i = s.find('\t')
735 i = s.find('\t')
732 if i < 0:
736 if i < 0:
733 i = s.find(' ')
737 i = s.find(' ')
734 if i < 0:
738 if i < 0:
735 return s
739 return s
736 return s[:i]
740 return s[:i]
737
741
738 def selectfile(afile_orig, bfile_orig, hunk, strip):
742 def selectfile(afile_orig, bfile_orig, hunk, strip):
739 def pathstrip(path, count=1):
743 def pathstrip(path, count=1):
740 pathlen = len(path)
744 pathlen = len(path)
741 i = 0
745 i = 0
742 if count == 0:
746 if count == 0:
743 return '', path.rstrip()
747 return '', path.rstrip()
744 while count > 0:
748 while count > 0:
745 i = path.find('/', i)
749 i = path.find('/', i)
746 if i == -1:
750 if i == -1:
747 raise PatchError(_("unable to strip away %d dirs from %s") %
751 raise PatchError(_("unable to strip away %d dirs from %s") %
748 (count, path))
752 (count, path))
749 i += 1
753 i += 1
750 # consume '//' in the path
754 # consume '//' in the path
751 while i < pathlen - 1 and path[i] == '/':
755 while i < pathlen - 1 and path[i] == '/':
752 i += 1
756 i += 1
753 count -= 1
757 count -= 1
754 return path[:i].lstrip(), path[i:].rstrip()
758 return path[:i].lstrip(), path[i:].rstrip()
755
759
756 nulla = afile_orig == "/dev/null"
760 nulla = afile_orig == "/dev/null"
757 nullb = bfile_orig == "/dev/null"
761 nullb = bfile_orig == "/dev/null"
758 abase, afile = pathstrip(afile_orig, strip)
762 abase, afile = pathstrip(afile_orig, strip)
759 gooda = not nulla and util.lexists(afile)
763 gooda = not nulla and util.lexists(afile)
760 bbase, bfile = pathstrip(bfile_orig, strip)
764 bbase, bfile = pathstrip(bfile_orig, strip)
761 if afile == bfile:
765 if afile == bfile:
762 goodb = gooda
766 goodb = gooda
763 else:
767 else:
764 goodb = not nullb and os.path.exists(bfile)
768 goodb = not nullb and os.path.exists(bfile)
765 createfunc = hunk.createfile
769 createfunc = hunk.createfile
766 missing = not goodb and not gooda and not createfunc()
770 missing = not goodb and not gooda and not createfunc()
767
771
768 # some diff programs apparently produce create patches where the
772 # some diff programs apparently produce create patches where the
769 # afile is not /dev/null, but rather the same name as the bfile
773 # afile is not /dev/null, but rather the same name as the bfile
770 if missing and afile == bfile:
774 if missing and afile == bfile:
771 # this isn't very pretty
775 # this isn't very pretty
772 hunk.create = True
776 hunk.create = True
773 if createfunc():
777 if createfunc():
774 missing = False
778 missing = False
775 else:
779 else:
776 hunk.create = False
780 hunk.create = False
777
781
778 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
782 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
779 # diff is between a file and its backup. In this case, the original
783 # diff is between a file and its backup. In this case, the original
780 # file should be patched (see original mpatch code).
784 # file should be patched (see original mpatch code).
781 isbackup = (abase == bbase and bfile.startswith(afile))
785 isbackup = (abase == bbase and bfile.startswith(afile))
782 fname = None
786 fname = None
783 if not missing:
787 if not missing:
784 if gooda and goodb:
788 if gooda and goodb:
785 fname = isbackup and afile or bfile
789 fname = isbackup and afile or bfile
786 elif gooda:
790 elif gooda:
787 fname = afile
791 fname = afile
788
792
789 if not fname:
793 if not fname:
790 if not nullb:
794 if not nullb:
791 fname = isbackup and afile or bfile
795 fname = isbackup and afile or bfile
792 elif not nulla:
796 elif not nulla:
793 fname = afile
797 fname = afile
794 else:
798 else:
795 raise PatchError(_("undefined source and destination files"))
799 raise PatchError(_("undefined source and destination files"))
796
800
797 return fname, missing
801 return fname, missing
798
802
799 def scangitpatch(lr, firstline):
803 def scangitpatch(lr, firstline):
800 """
804 """
801 Git patches can emit:
805 Git patches can emit:
802 - rename a to b
806 - rename a to b
803 - change b
807 - change b
804 - copy a to c
808 - copy a to c
805 - change c
809 - change c
806
810
807 We cannot apply this sequence as-is, the renamed 'a' could not be
811 We cannot apply this sequence as-is, the renamed 'a' could not be
808 found for it would have been renamed already. And we cannot copy
812 found for it would have been renamed already. And we cannot copy
809 from 'b' instead because 'b' would have been changed already. So
813 from 'b' instead because 'b' would have been changed already. So
810 we scan the git patch for copy and rename commands so we can
814 we scan the git patch for copy and rename commands so we can
811 perform the copies ahead of time.
815 perform the copies ahead of time.
812 """
816 """
813 pos = 0
817 pos = 0
814 try:
818 try:
815 pos = lr.fp.tell()
819 pos = lr.fp.tell()
816 fp = lr.fp
820 fp = lr.fp
817 except IOError:
821 except IOError:
818 fp = cStringIO.StringIO(lr.fp.read())
822 fp = cStringIO.StringIO(lr.fp.read())
819 gitlr = linereader(fp, lr.textmode)
823 gitlr = linereader(fp, lr.textmode)
820 gitlr.push(firstline)
824 gitlr.push(firstline)
821 (dopatch, gitpatches) = readgitpatch(gitlr)
825 (dopatch, gitpatches) = readgitpatch(gitlr)
822 fp.seek(pos)
826 fp.seek(pos)
823 return dopatch, gitpatches
827 return dopatch, gitpatches
824
828
825 def iterhunks(ui, fp, sourcefile=None, textmode=False):
829 def iterhunks(ui, fp, sourcefile=None, textmode=False):
826 """Read a patch and yield the following events:
830 """Read a patch and yield the following events:
827 - ("file", afile, bfile, firsthunk): select a new target file.
831 - ("file", afile, bfile, firsthunk): select a new target file.
828 - ("hunk", hunk): a new hunk is ready to be applied, follows a
832 - ("hunk", hunk): a new hunk is ready to be applied, follows a
829 "file" event.
833 "file" event.
830 - ("git", gitchanges): current diff is in git format, gitchanges
834 - ("git", gitchanges): current diff is in git format, gitchanges
831 maps filenames to gitpatch records. Unique event.
835 maps filenames to gitpatch records. Unique event.
832
836
833 If textmode is True, input line-endings are normalized to LF.
837 If textmode is True, input line-endings are normalized to LF.
834 """
838 """
835 changed = {}
839 changed = {}
836 current_hunk = None
840 current_hunk = None
837 afile = ""
841 afile = ""
838 bfile = ""
842 bfile = ""
839 state = None
843 state = None
840 hunknum = 0
844 hunknum = 0
841 emitfile = False
845 emitfile = False
842 git = False
846 git = False
843
847
844 # our states
848 # our states
845 BFILE = 1
849 BFILE = 1
846 context = None
850 context = None
847 lr = linereader(fp, textmode)
851 lr = linereader(fp, textmode)
848 dopatch = True
852 dopatch = True
849 # gitworkdone is True if a git operation (copy, rename, ...) was
853 # gitworkdone is True if a git operation (copy, rename, ...) was
850 # performed already for the current file. Useful when the file
854 # performed already for the current file. Useful when the file
851 # section may have no hunk.
855 # section may have no hunk.
852 gitworkdone = False
856 gitworkdone = False
853
857
854 while True:
858 while True:
855 newfile = False
859 newfile = False
856 x = lr.readline()
860 x = lr.readline()
857 if not x:
861 if not x:
858 break
862 break
859 if current_hunk:
863 if current_hunk:
860 if x.startswith('\ '):
864 if x.startswith('\ '):
861 current_hunk.fix_newline()
865 current_hunk.fix_newline()
862 yield 'hunk', current_hunk
866 yield 'hunk', current_hunk
863 current_hunk = None
867 current_hunk = None
864 gitworkdone = False
868 gitworkdone = False
865 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
869 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
866 ((context is not False) and x.startswith('***************')))):
870 ((context is not False) and x.startswith('***************')))):
867 try:
871 try:
868 if context is None and x.startswith('***************'):
872 if context is None and x.startswith('***************'):
869 context = True
873 context = True
870 gpatch = changed.get(bfile)
874 gpatch = changed.get(bfile)
871 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
875 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
872 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
876 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
873 current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
877 current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
874 if remove:
878 if remove:
875 gpatch = changed.get(afile[2:])
879 gpatch = changed.get(afile[2:])
876 if gpatch and gpatch.mode[0]:
880 if gpatch and gpatch.mode[0]:
877 current_hunk = symlinkhunk(gpatch, current_hunk)
881 current_hunk = symlinkhunk(gpatch, current_hunk)
878 except PatchError, err:
882 except PatchError, err:
879 ui.debug(err)
883 ui.debug(err)
880 current_hunk = None
884 current_hunk = None
881 continue
885 continue
882 hunknum += 1
886 hunknum += 1
883 if emitfile:
887 if emitfile:
884 emitfile = False
888 emitfile = False
885 yield 'file', (afile, bfile, current_hunk)
889 yield 'file', (afile, bfile, current_hunk)
886 elif state == BFILE and x.startswith('GIT binary patch'):
890 elif state == BFILE and x.startswith('GIT binary patch'):
887 current_hunk = binhunk(changed[bfile])
891 current_hunk = binhunk(changed[bfile])
888 hunknum += 1
892 hunknum += 1
889 if emitfile:
893 if emitfile:
890 emitfile = False
894 emitfile = False
891 yield 'file', ('a/' + afile, 'b/' + bfile, current_hunk)
895 yield 'file', ('a/' + afile, 'b/' + bfile, current_hunk)
892 current_hunk.extract(lr)
896 current_hunk.extract(lr)
893 elif x.startswith('diff --git'):
897 elif x.startswith('diff --git'):
894 # check for git diff, scanning the whole patch file if needed
898 # check for git diff, scanning the whole patch file if needed
895 m = gitre.match(x)
899 m = gitre.match(x)
896 if m:
900 if m:
897 afile, bfile = m.group(1, 2)
901 afile, bfile = m.group(1, 2)
898 if not git:
902 if not git:
899 git = True
903 git = True
900 dopatch, gitpatches = scangitpatch(lr, x)
904 dopatch, gitpatches = scangitpatch(lr, x)
901 yield 'git', gitpatches
905 yield 'git', gitpatches
902 for gp in gitpatches:
906 for gp in gitpatches:
903 changed[gp.path] = gp
907 changed[gp.path] = gp
904 # else error?
908 # else error?
905 # copy/rename + modify should modify target, not source
909 # copy/rename + modify should modify target, not source
906 gp = changed.get(bfile)
910 gp = changed.get(bfile)
907 if gp and gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD'):
911 if gp and gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD'):
908 afile = bfile
912 afile = bfile
909 gitworkdone = True
913 gitworkdone = True
910 newfile = True
914 newfile = True
911 elif x.startswith('---'):
915 elif x.startswith('---'):
912 # check for a unified diff
916 # check for a unified diff
913 l2 = lr.readline()
917 l2 = lr.readline()
914 if not l2.startswith('+++'):
918 if not l2.startswith('+++'):
915 lr.push(l2)
919 lr.push(l2)
916 continue
920 continue
917 newfile = True
921 newfile = True
918 context = False
922 context = False
919 afile = parsefilename(x)
923 afile = parsefilename(x)
920 bfile = parsefilename(l2)
924 bfile = parsefilename(l2)
921 elif x.startswith('***'):
925 elif x.startswith('***'):
922 # check for a context diff
926 # check for a context diff
923 l2 = lr.readline()
927 l2 = lr.readline()
924 if not l2.startswith('---'):
928 if not l2.startswith('---'):
925 lr.push(l2)
929 lr.push(l2)
926 continue
930 continue
927 l3 = lr.readline()
931 l3 = lr.readline()
928 lr.push(l3)
932 lr.push(l3)
929 if not l3.startswith("***************"):
933 if not l3.startswith("***************"):
930 lr.push(l2)
934 lr.push(l2)
931 continue
935 continue
932 newfile = True
936 newfile = True
933 context = True
937 context = True
934 afile = parsefilename(x)
938 afile = parsefilename(x)
935 bfile = parsefilename(l2)
939 bfile = parsefilename(l2)
936
940
937 if newfile:
941 if newfile:
938 emitfile = True
942 emitfile = True
939 state = BFILE
943 state = BFILE
940 hunknum = 0
944 hunknum = 0
941 if current_hunk:
945 if current_hunk:
942 if current_hunk.complete():
946 if current_hunk.complete():
943 yield 'hunk', current_hunk
947 yield 'hunk', current_hunk
944 else:
948 else:
945 raise PatchError(_("malformed patch %s %s") % (afile,
949 raise PatchError(_("malformed patch %s %s") % (afile,
946 current_hunk.desc))
950 current_hunk.desc))
947
951
948 if hunknum == 0 and dopatch and not gitworkdone:
952 if hunknum == 0 and dopatch and not gitworkdone:
949 raise NoHunks
953 raise NoHunks
950
954
951 def applydiff(ui, fp, changed, strip=1, sourcefile=None, eol=None):
955 def applydiff(ui, fp, changed, strip=1, sourcefile=None, eol=None):
952 """
956 """
953 Reads a patch from fp and tries to apply it.
957 Reads a patch from fp and tries to apply it.
954
958
955 The dict 'changed' is filled in with all of the filenames changed
959 The dict 'changed' is filled in with all of the filenames changed
956 by the patch. Returns 0 for a clean patch, -1 if any rejects were
960 by the patch. Returns 0 for a clean patch, -1 if any rejects were
957 found and 1 if there was any fuzz.
961 found and 1 if there was any fuzz.
958
962
959 If 'eol' is None, the patch content and patched file are read in
963 If 'eol' is None, the patch content and patched file are read in
960 binary mode. Otherwise, line endings are ignored when patching then
964 binary mode. Otherwise, line endings are ignored when patching then
961 normalized to 'eol' (usually '\n' or \r\n').
965 normalized to 'eol' (usually '\n' or \r\n').
962 """
966 """
963 rejects = 0
967 rejects = 0
964 err = 0
968 err = 0
965 current_file = None
969 current_file = None
966 gitpatches = None
970 gitpatches = None
967 opener = util.opener(os.getcwd())
971 opener = util.opener(os.getcwd())
968 textmode = eol is not None
972 textmode = eol is not None
969
973
970 def closefile():
974 def closefile():
971 if not current_file:
975 if not current_file:
972 return 0
976 return 0
973 current_file.close()
977 current_file.close()
974 return len(current_file.rej)
978 return len(current_file.rej)
975
979
976 for state, values in iterhunks(ui, fp, sourcefile, textmode):
980 for state, values in iterhunks(ui, fp, sourcefile, textmode):
977 if state == 'hunk':
981 if state == 'hunk':
978 if not current_file:
982 if not current_file:
979 continue
983 continue
980 current_hunk = values
984 current_hunk = values
981 ret = current_file.apply(current_hunk)
985 ret = current_file.apply(current_hunk)
982 if ret >= 0:
986 if ret >= 0:
983 changed.setdefault(current_file.fname, None)
987 changed.setdefault(current_file.fname, None)
984 if ret > 0:
988 if ret > 0:
985 err = 1
989 err = 1
986 elif state == 'file':
990 elif state == 'file':
987 rejects += closefile()
991 rejects += closefile()
988 afile, bfile, first_hunk = values
992 afile, bfile, first_hunk = values
989 try:
993 try:
990 if sourcefile:
994 if sourcefile:
991 current_file = patchfile(ui, sourcefile, opener, eol=eol)
995 current_file = patchfile(ui, sourcefile, opener, eol=eol)
992 else:
996 else:
993 current_file, missing = selectfile(afile, bfile, first_hunk,
997 current_file, missing = selectfile(afile, bfile, first_hunk,
994 strip)
998 strip)
995 current_file = patchfile(ui, current_file, opener, missing, eol)
999 current_file = patchfile(ui, current_file, opener, missing, eol)
996 except PatchError, err:
1000 except PatchError, err:
997 ui.warn(str(err) + '\n')
1001 ui.warn(str(err) + '\n')
998 current_file, current_hunk = None, None
1002 current_file, current_hunk = None, None
999 rejects += 1
1003 rejects += 1
1000 continue
1004 continue
1001 elif state == 'git':
1005 elif state == 'git':
1002 gitpatches = values
1006 gitpatches = values
1003 cwd = os.getcwd()
1007 cwd = os.getcwd()
1004 for gp in gitpatches:
1008 for gp in gitpatches:
1005 if gp.op in ('COPY', 'RENAME'):
1009 if gp.op in ('COPY', 'RENAME'):
1006 copyfile(gp.oldpath, gp.path, cwd)
1010 copyfile(gp.oldpath, gp.path, cwd)
1007 changed[gp.path] = gp
1011 changed[gp.path] = gp
1008 else:
1012 else:
1009 raise util.Abort(_('unsupported parser state: %s') % state)
1013 raise util.Abort(_('unsupported parser state: %s') % state)
1010
1014
1011 rejects += closefile()
1015 rejects += closefile()
1012
1016
1013 if rejects:
1017 if rejects:
1014 return -1
1018 return -1
1015 return err
1019 return err
1016
1020
1017 def diffopts(ui, opts={}, untrusted=False):
1021 def diffopts(ui, opts={}, untrusted=False):
1018 def get(key, name=None, getter=ui.configbool):
1022 def get(key, name=None, getter=ui.configbool):
1019 return (opts.get(key) or
1023 return (opts.get(key) or
1020 getter('diff', name or key, None, untrusted=untrusted))
1024 getter('diff', name or key, None, untrusted=untrusted))
1021 return mdiff.diffopts(
1025 return mdiff.diffopts(
1022 text=opts.get('text'),
1026 text=opts.get('text'),
1023 git=get('git'),
1027 git=get('git'),
1024 nodates=get('nodates'),
1028 nodates=get('nodates'),
1025 showfunc=get('show_function', 'showfunc'),
1029 showfunc=get('show_function', 'showfunc'),
1026 ignorews=get('ignore_all_space', 'ignorews'),
1030 ignorews=get('ignore_all_space', 'ignorews'),
1027 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1031 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1028 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1032 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1029 context=get('unified', getter=ui.config))
1033 context=get('unified', getter=ui.config))
1030
1034
1031 def updatedir(ui, repo, patches, similarity=0):
1035 def updatedir(ui, repo, patches, similarity=0):
1032 '''Update dirstate after patch application according to metadata'''
1036 '''Update dirstate after patch application according to metadata'''
1033 if not patches:
1037 if not patches:
1034 return
1038 return
1035 copies = []
1039 copies = []
1036 removes = set()
1040 removes = set()
1037 cfiles = patches.keys()
1041 cfiles = patches.keys()
1038 cwd = repo.getcwd()
1042 cwd = repo.getcwd()
1039 if cwd:
1043 if cwd:
1040 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1044 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1041 for f in patches:
1045 for f in patches:
1042 gp = patches[f]
1046 gp = patches[f]
1043 if not gp:
1047 if not gp:
1044 continue
1048 continue
1045 if gp.op == 'RENAME':
1049 if gp.op == 'RENAME':
1046 copies.append((gp.oldpath, gp.path))
1050 copies.append((gp.oldpath, gp.path))
1047 removes.add(gp.oldpath)
1051 removes.add(gp.oldpath)
1048 elif gp.op == 'COPY':
1052 elif gp.op == 'COPY':
1049 copies.append((gp.oldpath, gp.path))
1053 copies.append((gp.oldpath, gp.path))
1050 elif gp.op == 'DELETE':
1054 elif gp.op == 'DELETE':
1051 removes.add(gp.path)
1055 removes.add(gp.path)
1052 for src, dst in copies:
1056 for src, dst in copies:
1053 repo.copy(src, dst)
1057 repo.copy(src, dst)
1054 if (not similarity) and removes:
1058 if (not similarity) and removes:
1055 repo.remove(sorted(removes), True)
1059 repo.remove(sorted(removes), True)
1056 for f in patches:
1060 for f in patches:
1057 gp = patches[f]
1061 gp = patches[f]
1058 if gp and gp.mode:
1062 if gp and gp.mode:
1059 islink, isexec = gp.mode
1063 islink, isexec = gp.mode
1060 dst = repo.wjoin(gp.path)
1064 dst = repo.wjoin(gp.path)
1061 # patch won't create empty files
1065 # patch won't create empty files
1062 if gp.op == 'ADD' and not os.path.exists(dst):
1066 if gp.op == 'ADD' and not os.path.exists(dst):
1063 flags = (isexec and 'x' or '') + (islink and 'l' or '')
1067 flags = (isexec and 'x' or '') + (islink and 'l' or '')
1064 repo.wwrite(gp.path, '', flags)
1068 repo.wwrite(gp.path, '', flags)
1065 elif gp.op != 'DELETE':
1069 elif gp.op != 'DELETE':
1066 util.set_flags(dst, islink, isexec)
1070 util.set_flags(dst, islink, isexec)
1067 cmdutil.addremove(repo, cfiles, similarity=similarity)
1071 cmdutil.addremove(repo, cfiles, similarity=similarity)
1068 files = patches.keys()
1072 files = patches.keys()
1069 files.extend([r for r in removes if r not in files])
1073 files.extend([r for r in removes if r not in files])
1070 return sorted(files)
1074 return sorted(files)
1071
1075
1072 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
1076 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
1073 """use <patcher> to apply <patchname> to the working directory.
1077 """use <patcher> to apply <patchname> to the working directory.
1074 returns whether patch was applied with fuzz factor."""
1078 returns whether patch was applied with fuzz factor."""
1075
1079
1076 fuzz = False
1080 fuzz = False
1077 if cwd:
1081 if cwd:
1078 args.append('-d %s' % util.shellquote(cwd))
1082 args.append('-d %s' % util.shellquote(cwd))
1079 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1083 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1080 util.shellquote(patchname)))
1084 util.shellquote(patchname)))
1081
1085
1082 for line in fp:
1086 for line in fp:
1083 line = line.rstrip()
1087 line = line.rstrip()
1084 ui.note(line + '\n')
1088 ui.note(line + '\n')
1085 if line.startswith('patching file '):
1089 if line.startswith('patching file '):
1086 pf = util.parse_patch_output(line)
1090 pf = util.parse_patch_output(line)
1087 printed_file = False
1091 printed_file = False
1088 files.setdefault(pf, None)
1092 files.setdefault(pf, None)
1089 elif line.find('with fuzz') >= 0:
1093 elif line.find('with fuzz') >= 0:
1090 fuzz = True
1094 fuzz = True
1091 if not printed_file:
1095 if not printed_file:
1092 ui.warn(pf + '\n')
1096 ui.warn(pf + '\n')
1093 printed_file = True
1097 printed_file = True
1094 ui.warn(line + '\n')
1098 ui.warn(line + '\n')
1095 elif line.find('saving rejects to file') >= 0:
1099 elif line.find('saving rejects to file') >= 0:
1096 ui.warn(line + '\n')
1100 ui.warn(line + '\n')
1097 elif line.find('FAILED') >= 0:
1101 elif line.find('FAILED') >= 0:
1098 if not printed_file:
1102 if not printed_file:
1099 ui.warn(pf + '\n')
1103 ui.warn(pf + '\n')
1100 printed_file = True
1104 printed_file = True
1101 ui.warn(line + '\n')
1105 ui.warn(line + '\n')
1102 code = fp.close()
1106 code = fp.close()
1103 if code:
1107 if code:
1104 raise PatchError(_("patch command failed: %s") %
1108 raise PatchError(_("patch command failed: %s") %
1105 util.explain_exit(code)[0])
1109 util.explain_exit(code)[0])
1106 return fuzz
1110 return fuzz
1107
1111
1108 def internalpatch(patchobj, ui, strip, cwd, files={}, eolmode='strict'):
1112 def internalpatch(patchobj, ui, strip, cwd, files={}, eolmode='strict'):
1109 """use builtin patch to apply <patchobj> to the working directory.
1113 """use builtin patch to apply <patchobj> to the working directory.
1110 returns whether patch was applied with fuzz factor."""
1114 returns whether patch was applied with fuzz factor."""
1111
1115
1112 if eolmode is None:
1116 if eolmode is None:
1113 eolmode = ui.config('patch', 'eol', 'strict')
1117 eolmode = ui.config('patch', 'eol', 'strict')
1114 try:
1118 try:
1115 eol = {'strict': None, 'crlf': '\r\n', 'lf': '\n'}[eolmode.lower()]
1119 eol = {'strict': None, 'crlf': '\r\n', 'lf': '\n'}[eolmode.lower()]
1116 except KeyError:
1120 except KeyError:
1117 raise util.Abort(_('Unsupported line endings type: %s') % eolmode)
1121 raise util.Abort(_('Unsupported line endings type: %s') % eolmode)
1118
1122
1119 try:
1123 try:
1120 fp = open(patchobj, 'rb')
1124 fp = open(patchobj, 'rb')
1121 except TypeError:
1125 except TypeError:
1122 fp = patchobj
1126 fp = patchobj
1123 if cwd:
1127 if cwd:
1124 curdir = os.getcwd()
1128 curdir = os.getcwd()
1125 os.chdir(cwd)
1129 os.chdir(cwd)
1126 try:
1130 try:
1127 ret = applydiff(ui, fp, files, strip=strip, eol=eol)
1131 ret = applydiff(ui, fp, files, strip=strip, eol=eol)
1128 finally:
1132 finally:
1129 if cwd:
1133 if cwd:
1130 os.chdir(curdir)
1134 os.chdir(curdir)
1131 if ret < 0:
1135 if ret < 0:
1132 raise PatchError
1136 raise PatchError
1133 return ret > 0
1137 return ret > 0
1134
1138
1135 def patch(patchname, ui, strip=1, cwd=None, files={}, eolmode='strict'):
1139 def patch(patchname, ui, strip=1, cwd=None, files={}, eolmode='strict'):
1136 """Apply <patchname> to the working directory.
1140 """Apply <patchname> to the working directory.
1137
1141
1138 'eolmode' specifies how end of lines should be handled. It can be:
1142 'eolmode' specifies how end of lines should be handled. It can be:
1139 - 'strict': inputs are read in binary mode, EOLs are preserved
1143 - 'strict': inputs are read in binary mode, EOLs are preserved
1140 - 'crlf': EOLs are ignored when patching and reset to CRLF
1144 - 'crlf': EOLs are ignored when patching and reset to CRLF
1141 - 'lf': EOLs are ignored when patching and reset to LF
1145 - 'lf': EOLs are ignored when patching and reset to LF
1142 - None: get it from user settings, default to 'strict'
1146 - None: get it from user settings, default to 'strict'
1143 'eolmode' is ignored when using an external patcher program.
1147 'eolmode' is ignored when using an external patcher program.
1144
1148
1145 Returns whether patch was applied with fuzz factor.
1149 Returns whether patch was applied with fuzz factor.
1146 """
1150 """
1147 patcher = ui.config('ui', 'patch')
1151 patcher = ui.config('ui', 'patch')
1148 args = []
1152 args = []
1149 try:
1153 try:
1150 if patcher:
1154 if patcher:
1151 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1155 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1152 files)
1156 files)
1153 else:
1157 else:
1154 try:
1158 try:
1155 return internalpatch(patchname, ui, strip, cwd, files, eolmode)
1159 return internalpatch(patchname, ui, strip, cwd, files, eolmode)
1156 except NoHunks:
1160 except NoHunks:
1157 patcher = util.find_exe('gpatch') or util.find_exe('patch') or 'patch'
1161 patcher = util.find_exe('gpatch') or util.find_exe('patch') or 'patch'
1158 ui.debug('no valid hunks found; trying with %r instead\n' %
1162 ui.debug('no valid hunks found; trying with %r instead\n' %
1159 patcher)
1163 patcher)
1160 if util.needbinarypatch():
1164 if util.needbinarypatch():
1161 args.append('--binary')
1165 args.append('--binary')
1162 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1166 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1163 files)
1167 files)
1164 except PatchError, err:
1168 except PatchError, err:
1165 s = str(err)
1169 s = str(err)
1166 if s:
1170 if s:
1167 raise util.Abort(s)
1171 raise util.Abort(s)
1168 else:
1172 else:
1169 raise util.Abort(_('patch failed to apply'))
1173 raise util.Abort(_('patch failed to apply'))
1170
1174
1171 def b85diff(to, tn):
1175 def b85diff(to, tn):
1172 '''print base85-encoded binary diff'''
1176 '''print base85-encoded binary diff'''
1173 def gitindex(text):
1177 def gitindex(text):
1174 if not text:
1178 if not text:
1175 return '0' * 40
1179 return '0' * 40
1176 l = len(text)
1180 l = len(text)
1177 s = util.sha1('blob %d\0' % l)
1181 s = util.sha1('blob %d\0' % l)
1178 s.update(text)
1182 s.update(text)
1179 return s.hexdigest()
1183 return s.hexdigest()
1180
1184
1181 def fmtline(line):
1185 def fmtline(line):
1182 l = len(line)
1186 l = len(line)
1183 if l <= 26:
1187 if l <= 26:
1184 l = chr(ord('A') + l - 1)
1188 l = chr(ord('A') + l - 1)
1185 else:
1189 else:
1186 l = chr(l - 26 + ord('a') - 1)
1190 l = chr(l - 26 + ord('a') - 1)
1187 return '%c%s\n' % (l, base85.b85encode(line, True))
1191 return '%c%s\n' % (l, base85.b85encode(line, True))
1188
1192
1189 def chunk(text, csize=52):
1193 def chunk(text, csize=52):
1190 l = len(text)
1194 l = len(text)
1191 i = 0
1195 i = 0
1192 while i < l:
1196 while i < l:
1193 yield text[i:i+csize]
1197 yield text[i:i+csize]
1194 i += csize
1198 i += csize
1195
1199
1196 tohash = gitindex(to)
1200 tohash = gitindex(to)
1197 tnhash = gitindex(tn)
1201 tnhash = gitindex(tn)
1198 if tohash == tnhash:
1202 if tohash == tnhash:
1199 return ""
1203 return ""
1200
1204
1201 # TODO: deltas
1205 # TODO: deltas
1202 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1206 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1203 (tohash, tnhash, len(tn))]
1207 (tohash, tnhash, len(tn))]
1204 for l in chunk(zlib.compress(tn)):
1208 for l in chunk(zlib.compress(tn)):
1205 ret.append(fmtline(l))
1209 ret.append(fmtline(l))
1206 ret.append('\n')
1210 ret.append('\n')
1207 return ''.join(ret)
1211 return ''.join(ret)
1208
1212
1209 def _addmodehdr(header, omode, nmode):
1213 def _addmodehdr(header, omode, nmode):
1210 if omode != nmode:
1214 if omode != nmode:
1211 header.append('old mode %s\n' % omode)
1215 header.append('old mode %s\n' % omode)
1212 header.append('new mode %s\n' % nmode)
1216 header.append('new mode %s\n' % nmode)
1213
1217
1214 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None):
1218 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None):
1215 '''yields diff of changes to files between two nodes, or node and
1219 '''yields diff of changes to files between two nodes, or node and
1216 working directory.
1220 working directory.
1217
1221
1218 if node1 is None, use first dirstate parent instead.
1222 if node1 is None, use first dirstate parent instead.
1219 if node2 is None, compare node1 with working directory.'''
1223 if node2 is None, compare node1 with working directory.'''
1220
1224
1221 if opts is None:
1225 if opts is None:
1222 opts = mdiff.defaultopts
1226 opts = mdiff.defaultopts
1223
1227
1224 if not node1:
1228 if not node1:
1225 node1 = repo.dirstate.parents()[0]
1229 node1 = repo.dirstate.parents()[0]
1226
1230
1227 def lrugetfilectx():
1231 def lrugetfilectx():
1228 cache = {}
1232 cache = {}
1229 order = []
1233 order = []
1230 def getfilectx(f, ctx):
1234 def getfilectx(f, ctx):
1231 fctx = ctx.filectx(f, filelog=cache.get(f))
1235 fctx = ctx.filectx(f, filelog=cache.get(f))
1232 if f not in cache:
1236 if f not in cache:
1233 if len(cache) > 20:
1237 if len(cache) > 20:
1234 del cache[order.pop(0)]
1238 del cache[order.pop(0)]
1235 cache[f] = fctx._filelog
1239 cache[f] = fctx._filelog
1236 else:
1240 else:
1237 order.remove(f)
1241 order.remove(f)
1238 order.append(f)
1242 order.append(f)
1239 return fctx
1243 return fctx
1240 return getfilectx
1244 return getfilectx
1241 getfilectx = lrugetfilectx()
1245 getfilectx = lrugetfilectx()
1242
1246
1243 ctx1 = repo[node1]
1247 ctx1 = repo[node1]
1244 ctx2 = repo[node2]
1248 ctx2 = repo[node2]
1245
1249
1246 if not changes:
1250 if not changes:
1247 changes = repo.status(ctx1, ctx2, match=match)
1251 changes = repo.status(ctx1, ctx2, match=match)
1248 modified, added, removed = changes[:3]
1252 modified, added, removed = changes[:3]
1249
1253
1250 if not modified and not added and not removed:
1254 if not modified and not added and not removed:
1251 return
1255 return
1252
1256
1253 date1 = util.datestr(ctx1.date())
1257 date1 = util.datestr(ctx1.date())
1254 man1 = ctx1.manifest()
1258 man1 = ctx1.manifest()
1255
1259
1256 if repo.ui.quiet:
1260 if repo.ui.quiet:
1257 r = None
1261 r = None
1258 else:
1262 else:
1259 hexfunc = repo.ui.debugflag and hex or short
1263 hexfunc = repo.ui.debugflag and hex or short
1260 r = [hexfunc(node) for node in [node1, node2] if node]
1264 r = [hexfunc(node) for node in [node1, node2] if node]
1261
1265
1262 if opts.git:
1266 if opts.git:
1263 copy, diverge = copies.copies(repo, ctx1, ctx2, repo[nullid])
1267 copy, diverge = copies.copies(repo, ctx1, ctx2, repo[nullid])
1264 copy = copy.copy()
1268 copy = copy.copy()
1265 for k, v in copy.items():
1269 for k, v in copy.items():
1266 copy[v] = k
1270 copy[v] = k
1267
1271
1268 gone = set()
1272 gone = set()
1269 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1273 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1270
1274
1271 for f in sorted(modified + added + removed):
1275 for f in sorted(modified + added + removed):
1272 to = None
1276 to = None
1273 tn = None
1277 tn = None
1274 dodiff = True
1278 dodiff = True
1275 header = []
1279 header = []
1276 if f in man1:
1280 if f in man1:
1277 to = getfilectx(f, ctx1).data()
1281 to = getfilectx(f, ctx1).data()
1278 if f not in removed:
1282 if f not in removed:
1279 tn = getfilectx(f, ctx2).data()
1283 tn = getfilectx(f, ctx2).data()
1280 a, b = f, f
1284 a, b = f, f
1281 if opts.git:
1285 if opts.git:
1282 if f in added:
1286 if f in added:
1283 mode = gitmode[ctx2.flags(f)]
1287 mode = gitmode[ctx2.flags(f)]
1284 if f in copy:
1288 if f in copy:
1285 a = copy[f]
1289 a = copy[f]
1286 omode = gitmode[man1.flags(a)]
1290 omode = gitmode[man1.flags(a)]
1287 _addmodehdr(header, omode, mode)
1291 _addmodehdr(header, omode, mode)
1288 if a in removed and a not in gone:
1292 if a in removed and a not in gone:
1289 op = 'rename'
1293 op = 'rename'
1290 gone.add(a)
1294 gone.add(a)
1291 else:
1295 else:
1292 op = 'copy'
1296 op = 'copy'
1293 header.append('%s from %s\n' % (op, a))
1297 header.append('%s from %s\n' % (op, a))
1294 header.append('%s to %s\n' % (op, f))
1298 header.append('%s to %s\n' % (op, f))
1295 to = getfilectx(a, ctx1).data()
1299 to = getfilectx(a, ctx1).data()
1296 else:
1300 else:
1297 header.append('new file mode %s\n' % mode)
1301 header.append('new file mode %s\n' % mode)
1298 if util.binary(tn):
1302 if util.binary(tn):
1299 dodiff = 'binary'
1303 dodiff = 'binary'
1300 elif f in removed:
1304 elif f in removed:
1301 # have we already reported a copy above?
1305 # have we already reported a copy above?
1302 if f in copy and copy[f] in added and copy[copy[f]] == f:
1306 if f in copy and copy[f] in added and copy[copy[f]] == f:
1303 dodiff = False
1307 dodiff = False
1304 else:
1308 else:
1305 header.append('deleted file mode %s\n' %
1309 header.append('deleted file mode %s\n' %
1306 gitmode[man1.flags(f)])
1310 gitmode[man1.flags(f)])
1307 else:
1311 else:
1308 omode = gitmode[man1.flags(f)]
1312 omode = gitmode[man1.flags(f)]
1309 nmode = gitmode[ctx2.flags(f)]
1313 nmode = gitmode[ctx2.flags(f)]
1310 _addmodehdr(header, omode, nmode)
1314 _addmodehdr(header, omode, nmode)
1311 if util.binary(to) or util.binary(tn):
1315 if util.binary(to) or util.binary(tn):
1312 dodiff = 'binary'
1316 dodiff = 'binary'
1313 r = None
1317 r = None
1314 header.insert(0, mdiff.diffline(r, a, b, opts))
1318 header.insert(0, mdiff.diffline(r, a, b, opts))
1315 if dodiff:
1319 if dodiff:
1316 if dodiff == 'binary':
1320 if dodiff == 'binary':
1317 text = b85diff(to, tn)
1321 text = b85diff(to, tn)
1318 else:
1322 else:
1319 text = mdiff.unidiff(to, date1,
1323 text = mdiff.unidiff(to, date1,
1320 # ctx2 date may be dynamic
1324 # ctx2 date may be dynamic
1321 tn, util.datestr(ctx2.date()),
1325 tn, util.datestr(ctx2.date()),
1322 a, b, r, opts=opts)
1326 a, b, r, opts=opts)
1323 if header and (text or len(header) > 1):
1327 if header and (text or len(header) > 1):
1324 yield ''.join(header)
1328 yield ''.join(header)
1325 if text:
1329 if text:
1326 yield text
1330 yield text
1327
1331
1328 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1332 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1329 opts=None):
1333 opts=None):
1330 '''export changesets as hg patches.'''
1334 '''export changesets as hg patches.'''
1331
1335
1332 total = len(revs)
1336 total = len(revs)
1333 revwidth = max([len(str(rev)) for rev in revs])
1337 revwidth = max([len(str(rev)) for rev in revs])
1334
1338
1335 def single(rev, seqno, fp):
1339 def single(rev, seqno, fp):
1336 ctx = repo[rev]
1340 ctx = repo[rev]
1337 node = ctx.node()
1341 node = ctx.node()
1338 parents = [p.node() for p in ctx.parents() if p]
1342 parents = [p.node() for p in ctx.parents() if p]
1339 branch = ctx.branch()
1343 branch = ctx.branch()
1340 if switch_parent:
1344 if switch_parent:
1341 parents.reverse()
1345 parents.reverse()
1342 prev = (parents and parents[0]) or nullid
1346 prev = (parents and parents[0]) or nullid
1343
1347
1344 if not fp:
1348 if not fp:
1345 fp = cmdutil.make_file(repo, template, node, total=total,
1349 fp = cmdutil.make_file(repo, template, node, total=total,
1346 seqno=seqno, revwidth=revwidth,
1350 seqno=seqno, revwidth=revwidth,
1347 mode='ab')
1351 mode='ab')
1348 if fp != sys.stdout and hasattr(fp, 'name'):
1352 if fp != sys.stdout and hasattr(fp, 'name'):
1349 repo.ui.note("%s\n" % fp.name)
1353 repo.ui.note("%s\n" % fp.name)
1350
1354
1351 fp.write("# HG changeset patch\n")
1355 fp.write("# HG changeset patch\n")
1352 fp.write("# User %s\n" % ctx.user())
1356 fp.write("# User %s\n" % ctx.user())
1353 fp.write("# Date %d %d\n" % ctx.date())
1357 fp.write("# Date %d %d\n" % ctx.date())
1354 if branch and (branch != 'default'):
1358 if branch and (branch != 'default'):
1355 fp.write("# Branch %s\n" % branch)
1359 fp.write("# Branch %s\n" % branch)
1356 fp.write("# Node ID %s\n" % hex(node))
1360 fp.write("# Node ID %s\n" % hex(node))
1357 fp.write("# Parent %s\n" % hex(prev))
1361 fp.write("# Parent %s\n" % hex(prev))
1358 if len(parents) > 1:
1362 if len(parents) > 1:
1359 fp.write("# Parent %s\n" % hex(parents[1]))
1363 fp.write("# Parent %s\n" % hex(parents[1]))
1360 fp.write(ctx.description().rstrip())
1364 fp.write(ctx.description().rstrip())
1361 fp.write("\n\n")
1365 fp.write("\n\n")
1362
1366
1363 for chunk in diff(repo, prev, node, opts=opts):
1367 for chunk in diff(repo, prev, node, opts=opts):
1364 fp.write(chunk)
1368 fp.write(chunk)
1365
1369
1366 for seqno, rev in enumerate(revs):
1370 for seqno, rev in enumerate(revs):
1367 single(rev, seqno+1, fp)
1371 single(rev, seqno+1, fp)
1368
1372
1369 def diffstatdata(lines):
1373 def diffstatdata(lines):
1370 filename, adds, removes = None, 0, 0
1374 filename, adds, removes = None, 0, 0
1371 for line in lines:
1375 for line in lines:
1372 if line.startswith('diff'):
1376 if line.startswith('diff'):
1373 if filename:
1377 if filename:
1374 yield (filename, adds, removes)
1378 yield (filename, adds, removes)
1375 # set numbers to 0 anyway when starting new file
1379 # set numbers to 0 anyway when starting new file
1376 adds, removes = 0, 0
1380 adds, removes = 0, 0
1377 if line.startswith('diff --git'):
1381 if line.startswith('diff --git'):
1378 filename = gitre.search(line).group(1)
1382 filename = gitre.search(line).group(1)
1379 else:
1383 else:
1380 # format: "diff -r ... -r ... filename"
1384 # format: "diff -r ... -r ... filename"
1381 filename = line.split(None, 5)[-1]
1385 filename = line.split(None, 5)[-1]
1382 elif line.startswith('+') and not line.startswith('+++'):
1386 elif line.startswith('+') and not line.startswith('+++'):
1383 adds += 1
1387 adds += 1
1384 elif line.startswith('-') and not line.startswith('---'):
1388 elif line.startswith('-') and not line.startswith('---'):
1385 removes += 1
1389 removes += 1
1386 if filename:
1390 if filename:
1387 yield (filename, adds, removes)
1391 yield (filename, adds, removes)
1388
1392
1389 def diffstat(lines, width=80):
1393 def diffstat(lines, width=80):
1390 output = []
1394 output = []
1391 stats = list(diffstatdata(lines))
1395 stats = list(diffstatdata(lines))
1392
1396
1393 maxtotal, maxname = 0, 0
1397 maxtotal, maxname = 0, 0
1394 totaladds, totalremoves = 0, 0
1398 totaladds, totalremoves = 0, 0
1395 for filename, adds, removes in stats:
1399 for filename, adds, removes in stats:
1396 totaladds += adds
1400 totaladds += adds
1397 totalremoves += removes
1401 totalremoves += removes
1398 maxname = max(maxname, len(filename))
1402 maxname = max(maxname, len(filename))
1399 maxtotal = max(maxtotal, adds+removes)
1403 maxtotal = max(maxtotal, adds+removes)
1400
1404
1401 countwidth = len(str(maxtotal))
1405 countwidth = len(str(maxtotal))
1402 graphwidth = width - countwidth - maxname - 6
1406 graphwidth = width - countwidth - maxname - 6
1403 if graphwidth < 10:
1407 if graphwidth < 10:
1404 graphwidth = 10
1408 graphwidth = 10
1405
1409
1406 def scale(i):
1410 def scale(i):
1407 if maxtotal <= graphwidth:
1411 if maxtotal <= graphwidth:
1408 return i
1412 return i
1409 # If diffstat runs out of room it doesn't print anything,
1413 # If diffstat runs out of room it doesn't print anything,
1410 # which isn't very useful, so always print at least one + or -
1414 # which isn't very useful, so always print at least one + or -
1411 # if there were at least some changes.
1415 # if there were at least some changes.
1412 return max(i * graphwidth // maxtotal, int(bool(i)))
1416 return max(i * graphwidth // maxtotal, int(bool(i)))
1413
1417
1414 for filename, adds, removes in stats:
1418 for filename, adds, removes in stats:
1415 pluses = '+' * scale(adds)
1419 pluses = '+' * scale(adds)
1416 minuses = '-' * scale(removes)
1420 minuses = '-' * scale(removes)
1417 output.append(' %-*s | %*.d %s%s\n' % (maxname, filename, countwidth,
1421 output.append(' %-*s | %*.d %s%s\n' % (maxname, filename, countwidth,
1418 adds+removes, pluses, minuses))
1422 adds+removes, pluses, minuses))
1419
1423
1420 if stats:
1424 if stats:
1421 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1425 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1422 % (len(stats), totaladds, totalremoves))
1426 % (len(stats), totaladds, totalremoves))
1423
1427
1424 return ''.join(output)
1428 return ''.join(output)
@@ -1,292 +1,292
1 # windows.py - Windows utility function implementations for Mercurial
1 # windows.py - Windows utility function implementations for Mercurial
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 from i18n import _
8 from i18n import _
9 import osutil, error
9 import osutil, error
10 import errno, msvcrt, os, re, sys
10 import errno, msvcrt, os, re, sys
11
11
12 nulldev = 'NUL:'
12 nulldev = 'NUL:'
13 umask = 002
13 umask = 002
14
14
15 # wrap osutil.posixfile to provide friendlier exceptions
15 # wrap osutil.posixfile to provide friendlier exceptions
16 def posixfile(name, mode='r', buffering=-1):
16 def posixfile(name, mode='r', buffering=-1):
17 try:
17 try:
18 return osutil.posixfile(name, mode, buffering)
18 return osutil.posixfile(name, mode, buffering)
19 except WindowsError, err:
19 except WindowsError, err:
20 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
20 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
21 posixfile.__doc__ = osutil.posixfile.__doc__
21 posixfile.__doc__ = osutil.posixfile.__doc__
22
22
23 class winstdout(object):
23 class winstdout(object):
24 '''stdout on windows misbehaves if sent through a pipe'''
24 '''stdout on windows misbehaves if sent through a pipe'''
25
25
26 def __init__(self, fp):
26 def __init__(self, fp):
27 self.fp = fp
27 self.fp = fp
28
28
29 def __getattr__(self, key):
29 def __getattr__(self, key):
30 return getattr(self.fp, key)
30 return getattr(self.fp, key)
31
31
32 def close(self):
32 def close(self):
33 try:
33 try:
34 self.fp.close()
34 self.fp.close()
35 except: pass
35 except: pass
36
36
37 def write(self, s):
37 def write(self, s):
38 try:
38 try:
39 # This is workaround for "Not enough space" error on
39 # This is workaround for "Not enough space" error on
40 # writing large size of data to console.
40 # writing large size of data to console.
41 limit = 16000
41 limit = 16000
42 l = len(s)
42 l = len(s)
43 start = 0
43 start = 0
44 self.softspace = 0;
44 self.softspace = 0;
45 while start < l:
45 while start < l:
46 end = start + limit
46 end = start + limit
47 self.fp.write(s[start:end])
47 self.fp.write(s[start:end])
48 start = end
48 start = end
49 except IOError, inst:
49 except IOError, inst:
50 if inst.errno != 0: raise
50 if inst.errno != 0: raise
51 self.close()
51 self.close()
52 raise IOError(errno.EPIPE, 'Broken pipe')
52 raise IOError(errno.EPIPE, 'Broken pipe')
53
53
54 def flush(self):
54 def flush(self):
55 try:
55 try:
56 return self.fp.flush()
56 return self.fp.flush()
57 except IOError, inst:
57 except IOError, inst:
58 if inst.errno != errno.EINVAL: raise
58 if inst.errno != errno.EINVAL: raise
59 self.close()
59 self.close()
60 raise IOError(errno.EPIPE, 'Broken pipe')
60 raise IOError(errno.EPIPE, 'Broken pipe')
61
61
62 sys.stdout = winstdout(sys.stdout)
62 sys.stdout = winstdout(sys.stdout)
63
63
64 def _is_win_9x():
64 def _is_win_9x():
65 '''return true if run on windows 95, 98 or me.'''
65 '''return true if run on windows 95, 98 or me.'''
66 try:
66 try:
67 return sys.getwindowsversion()[3] == 1
67 return sys.getwindowsversion()[3] == 1
68 except AttributeError:
68 except AttributeError:
69 return 'command' in os.environ.get('comspec', '')
69 return 'command' in os.environ.get('comspec', '')
70
70
71 def openhardlinks():
71 def openhardlinks():
72 return not _is_win_9x() and "win32api" in globals()
72 return not _is_win_9x() and "win32api" in globals()
73
73
74 def system_rcpath():
74 def system_rcpath():
75 try:
75 try:
76 return system_rcpath_win32()
76 return system_rcpath_win32()
77 except:
77 except:
78 return [r'c:\mercurial\mercurial.ini']
78 return [r'c:\mercurial\mercurial.ini']
79
79
80 def user_rcpath():
80 def user_rcpath():
81 '''return os-specific hgrc search path to the user dir'''
81 '''return os-specific hgrc search path to the user dir'''
82 try:
82 try:
83 path = user_rcpath_win32()
83 path = user_rcpath_win32()
84 except:
84 except:
85 home = os.path.expanduser('~')
85 home = os.path.expanduser('~')
86 path = [os.path.join(home, 'mercurial.ini'),
86 path = [os.path.join(home, 'mercurial.ini'),
87 os.path.join(home, '.hgrc')]
87 os.path.join(home, '.hgrc')]
88 userprofile = os.environ.get('USERPROFILE')
88 userprofile = os.environ.get('USERPROFILE')
89 if userprofile:
89 if userprofile:
90 path.append(os.path.join(userprofile, 'mercurial.ini'))
90 path.append(os.path.join(userprofile, 'mercurial.ini'))
91 path.append(os.path.join(userprofile, '.hgrc'))
91 path.append(os.path.join(userprofile, '.hgrc'))
92 return path
92 return path
93
93
94 def parse_patch_output(output_line):
94 def parse_patch_output(output_line):
95 """parses the output produced by patch and returns the filename"""
95 """parses the output produced by patch and returns the filename"""
96 pf = output_line[14:]
96 pf = output_line[14:]
97 if pf[0] == '`':
97 if pf[0] == '`':
98 pf = pf[1:-1] # Remove the quotes
98 pf = pf[1:-1] # Remove the quotes
99 return pf
99 return pf
100
100
101 def sshargs(sshcmd, host, user, port):
101 def sshargs(sshcmd, host, user, port):
102 '''Build argument list for ssh or Plink'''
102 '''Build argument list for ssh or Plink'''
103 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
103 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
104 args = user and ("%s@%s" % (user, host)) or host
104 args = user and ("%s@%s" % (user, host)) or host
105 return port and ("%s %s %s" % (args, pflag, port)) or args
105 return port and ("%s %s %s" % (args, pflag, port)) or args
106
106
107 def testpid(pid):
107 def testpid(pid):
108 '''return False if pid dead, True if running or not known'''
108 '''return False if pid dead, True if running or not known'''
109 return True
109 return True
110
110
111 def set_flags(f, l, x):
111 def set_flags(f, l, x):
112 pass
112 pass
113
113
114 def set_binary(fd):
114 def set_binary(fd):
115 # When run without console, pipes may expose invalid
115 # When run without console, pipes may expose invalid
116 # fileno(), usually set to -1.
116 # fileno(), usually set to -1.
117 if hasattr(fd, 'fileno') and fd.fileno() >= 0:
117 if hasattr(fd, 'fileno') and fd.fileno() >= 0:
118 msvcrt.setmode(fd.fileno(), os.O_BINARY)
118 msvcrt.setmode(fd.fileno(), os.O_BINARY)
119
119
120 def pconvert(path):
120 def pconvert(path):
121 return '/'.join(path.split(os.sep))
121 return '/'.join(path.split(os.sep))
122
122
123 def localpath(path):
123 def localpath(path):
124 return path.replace('/', '\\')
124 return path.replace('/', '\\')
125
125
126 def normpath(path):
126 def normpath(path):
127 return pconvert(os.path.normpath(path))
127 return pconvert(os.path.normpath(path))
128
128
129 def realpath(path):
129 def realpath(path):
130 '''
130 '''
131 Returns the true, canonical file system path equivalent to the given
131 Returns the true, canonical file system path equivalent to the given
132 path.
132 path.
133 '''
133 '''
134 # TODO: There may be a more clever way to do this that also handles other,
134 # TODO: There may be a more clever way to do this that also handles other,
135 # less common file systems.
135 # less common file systems.
136 return os.path.normpath(os.path.normcase(os.path.realpath(path)))
136 return os.path.normpath(os.path.normcase(os.path.realpath(path)))
137
137
138 def samestat(s1, s2):
138 def samestat(s1, s2):
139 return False
139 return False
140
140
141 # A sequence of backslashes is special iff it precedes a double quote:
141 # A sequence of backslashes is special iff it precedes a double quote:
142 # - if there's an even number of backslashes, the double quote is not
142 # - if there's an even number of backslashes, the double quote is not
143 # quoted (i.e. it ends the quoted region)
143 # quoted (i.e. it ends the quoted region)
144 # - if there's an odd number of backslashes, the double quote is quoted
144 # - if there's an odd number of backslashes, the double quote is quoted
145 # - in both cases, every pair of backslashes is unquoted into a single
145 # - in both cases, every pair of backslashes is unquoted into a single
146 # backslash
146 # backslash
147 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
147 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
148 # So, to quote a string, we must surround it in double quotes, double
148 # So, to quote a string, we must surround it in double quotes, double
149 # the number of backslashes that preceed double quotes and add another
149 # the number of backslashes that preceed double quotes and add another
150 # backslash before every double quote (being careful with the double
150 # backslash before every double quote (being careful with the double
151 # quote we've appended to the end)
151 # quote we've appended to the end)
152 _quotere = None
152 _quotere = None
153 def shellquote(s):
153 def shellquote(s):
154 global _quotere
154 global _quotere
155 if _quotere is None:
155 if _quotere is None:
156 _quotere = re.compile(r'(\\*)("|\\$)')
156 _quotere = re.compile(r'(\\*)("|\\$)')
157 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
157 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
158
158
159 def quotecommand(cmd):
159 def quotecommand(cmd):
160 """Build a command string suitable for os.popen* calls."""
160 """Build a command string suitable for os.popen* calls."""
161 # The extra quotes are needed because popen* runs the command
161 # The extra quotes are needed because popen* runs the command
162 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
162 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
163 return '"' + cmd + '"'
163 return '"' + cmd + '"'
164
164
165 def popen(command, mode='r'):
165 def popen(command, mode='r'):
166 # Work around "popen spawned process may not write to stdout
166 # Work around "popen spawned process may not write to stdout
167 # under windows"
167 # under windows"
168 # http://bugs.python.org/issue1366
168 # http://bugs.python.org/issue1366
169 command += " 2> %s" % nulldev
169 command += " 2> %s" % nulldev
170 return os.popen(quotecommand(command), mode)
170 return os.popen(quotecommand(command), mode)
171
171
172 def explain_exit(code):
172 def explain_exit(code):
173 return _("exited with status %d") % code, code
173 return _("exited with status %d") % code, code
174
174
175 # if you change this stub into a real check, please try to implement the
175 # if you change this stub into a real check, please try to implement the
176 # username and groupname functions above, too.
176 # username and groupname functions above, too.
177 def isowner(st):
177 def isowner(st):
178 return True
178 return True
179
179
180 def find_exe(command):
180 def find_exe(command):
181 '''Find executable for command searching like cmd.exe does.
181 '''Find executable for command searching like cmd.exe does.
182 If command is a basename then PATH is searched for command.
182 If command is a basename then PATH is searched for command.
183 PATH isn't searched if command is an absolute or relative path.
183 PATH isn't searched if command is an absolute or relative path.
184 An extension from PATHEXT is found and added if not present.
184 An extension from PATHEXT is found and added if not present.
185 If command isn't found None is returned.'''
185 If command isn't found None is returned.'''
186 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
186 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
187 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
187 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
188 if os.path.splitext(command)[1].lower() in pathexts:
188 if os.path.splitext(command)[1].lower() in pathexts:
189 pathexts = ['']
189 pathexts = ['']
190
190
191 def findexisting(pathcommand):
191 def findexisting(pathcommand):
192 'Will append extension (if needed) and return existing file'
192 'Will append extension (if needed) and return existing file'
193 for ext in pathexts:
193 for ext in pathexts:
194 executable = pathcommand + ext
194 executable = pathcommand + ext
195 if os.path.exists(executable):
195 if os.path.exists(executable):
196 return executable
196 return executable
197 return None
197 return None
198
198
199 if os.sep in command:
199 if os.sep in command:
200 return findexisting(command)
200 return findexisting(command)
201
201
202 for path in os.environ.get('PATH', '').split(os.pathsep):
202 for path in os.environ.get('PATH', '').split(os.pathsep):
203 executable = findexisting(os.path.join(path, command))
203 executable = findexisting(os.path.join(path, command))
204 if executable is not None:
204 if executable is not None:
205 return executable
205 return executable
206 return None
206 return None
207
207
208 def set_signal_handler():
208 def set_signal_handler():
209 try:
209 try:
210 set_signal_handler_win32()
210 set_signal_handler_win32()
211 except NameError:
211 except NameError:
212 pass
212 pass
213
213
214 def statfiles(files):
214 def statfiles(files):
215 '''Stat each file in files and yield stat or None if file does not exist.
215 '''Stat each file in files and yield stat or None if file does not exist.
216 Cluster and cache stat per directory to minimize number of OS stat calls.'''
216 Cluster and cache stat per directory to minimize number of OS stat calls.'''
217 ncase = os.path.normcase
217 ncase = os.path.normcase
218 sep = os.sep
218 sep = os.sep
219 dircache = {} # dirname -> filename -> status | None if file does not exist
219 dircache = {} # dirname -> filename -> status | None if file does not exist
220 for nf in files:
220 for nf in files:
221 nf = ncase(nf)
221 nf = ncase(nf)
222 dir, base = os.path.split(nf)
222 dir, base = os.path.split(nf)
223 if not dir:
223 if not dir:
224 dir = '.'
224 dir = '.'
225 cache = dircache.get(dir, None)
225 cache = dircache.get(dir, None)
226 if cache is None:
226 if cache is None:
227 try:
227 try:
228 dmap = dict([(ncase(n), s)
228 dmap = dict([(ncase(n), s)
229 for n, k, s in osutil.listdir(dir, True)])
229 for n, k, s in osutil.listdir(dir, True)])
230 except OSError, err:
230 except OSError, err:
231 # handle directory not found in Python version prior to 2.5
231 # handle directory not found in Python version prior to 2.5
232 # Python <= 2.4 returns native Windows code 3 in errno
232 # Python <= 2.4 returns native Windows code 3 in errno
233 # Python >= 2.5 returns ENOENT and adds winerror field
233 # Python >= 2.5 returns ENOENT and adds winerror field
234 # EINVAL is raised if dir is not a directory.
234 # EINVAL is raised if dir is not a directory.
235 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
235 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
236 errno.ENOTDIR):
236 errno.ENOTDIR):
237 raise
237 raise
238 dmap = {}
238 dmap = {}
239 cache = dircache.setdefault(dir, dmap)
239 cache = dircache.setdefault(dir, dmap)
240 yield cache.get(base, None)
240 yield cache.get(base, None)
241
241
242 def getuser():
242 def getuser():
243 '''return name of current user'''
243 '''return name of current user'''
244 raise error.Abort(_('user name not available - set USERNAME '
244 raise error.Abort(_('user name not available - set USERNAME '
245 'environment variable'))
245 'environment variable'))
246
246
247 def username(uid=None):
247 def username(uid=None):
248 """Return the name of the user with the given uid.
248 """Return the name of the user with the given uid.
249
249
250 If uid is None, return the name of the current user."""
250 If uid is None, return the name of the current user."""
251 return None
251 return None
252
252
253 def groupname(gid=None):
253 def groupname(gid=None):
254 """Return the name of the group with the given gid.
254 """Return the name of the group with the given gid.
255
255
256 If gid is None, return the name of the current group."""
256 If gid is None, return the name of the current group."""
257 return None
257 return None
258
258
259 def _removedirs(name):
259 def _removedirs(name):
260 """special version of os.removedirs that does not remove symlinked
260 """special version of os.removedirs that does not remove symlinked
261 directories or junction points if they actually contain files"""
261 directories or junction points if they actually contain files"""
262 if osutil.listdir(name):
262 if osutil.listdir(name):
263 return
263 return
264 os.rmdir(name)
264 os.rmdir(name)
265 head, tail = os.path.split(name)
265 head, tail = os.path.split(name)
266 if not tail:
266 if not tail:
267 head, tail = os.path.split(head)
267 head, tail = os.path.split(head)
268 while head and tail:
268 while head and tail:
269 try:
269 try:
270 if osutil.listdir(name):
270 if osutil.listdir(head):
271 return
271 return
272 os.rmdir(head)
272 os.rmdir(head)
273 except:
273 except:
274 break
274 break
275 head, tail = os.path.split(head)
275 head, tail = os.path.split(head)
276
276
277 def unlink(f):
277 def unlink(f):
278 """unlink and remove the directory if it is empty"""
278 """unlink and remove the directory if it is empty"""
279 os.unlink(f)
279 os.unlink(f)
280 # try removing directories that might now be empty
280 # try removing directories that might now be empty
281 try:
281 try:
282 _removedirs(os.path.dirname(f))
282 _removedirs(os.path.dirname(f))
283 except OSError:
283 except OSError:
284 pass
284 pass
285
285
286 try:
286 try:
287 # override functions with win32 versions if possible
287 # override functions with win32 versions if possible
288 from win32 import *
288 from win32 import *
289 except ImportError:
289 except ImportError:
290 pass
290 pass
291
291
292 expandglobs = True
292 expandglobs = True
@@ -1,347 +1,368
1 #!/bin/sh
1 #!/bin/sh
2
2
3 hg init a
3 hg init a
4 mkdir a/d1
4 mkdir a/d1
5 mkdir a/d1/d2
5 mkdir a/d1/d2
6 echo line 1 > a/a
6 echo line 1 > a/a
7 echo line 1 > a/d1/d2/a
7 echo line 1 > a/d1/d2/a
8 hg --cwd a ci -Ama
8 hg --cwd a ci -Ama
9
9
10 echo line 2 >> a/a
10 echo line 2 >> a/a
11 hg --cwd a ci -u someone -d '1 0' -m'second change'
11 hg --cwd a ci -u someone -d '1 0' -m'second change'
12
12
13 echo % import exported patch
13 echo % import exported patch
14 hg clone -r0 a b
14 hg clone -r0 a b
15 hg --cwd a export tip > tip.patch
15 hg --cwd a export tip > tip.patch
16 hg --cwd b import ../tip.patch
16 hg --cwd b import ../tip.patch
17 echo % message should be same
17 echo % message should be same
18 hg --cwd b tip | grep 'second change'
18 hg --cwd b tip | grep 'second change'
19 echo % committer should be same
19 echo % committer should be same
20 hg --cwd b tip | grep someone
20 hg --cwd b tip | grep someone
21 rm -r b
21 rm -r b
22
22
23 echo % import exported patch with external patcher
23 echo % import exported patch with external patcher
24 cat > dummypatch.py <<EOF
24 cat > dummypatch.py <<EOF
25 print 'patching file a'
25 print 'patching file a'
26 file('a', 'wb').write('line2\n')
26 file('a', 'wb').write('line2\n')
27 EOF
27 EOF
28 chmod +x dummypatch.py
28 chmod +x dummypatch.py
29 hg clone -r0 a b
29 hg clone -r0 a b
30 hg --cwd a export tip > tip.patch
30 hg --cwd a export tip > tip.patch
31 hg --config ui.patch='python ../dummypatch.py' --cwd b import ../tip.patch
31 hg --config ui.patch='python ../dummypatch.py' --cwd b import ../tip.patch
32 cat b/a
32 cat b/a
33 rm -r b
33 rm -r b
34
34
35 echo % import of plain diff should fail without message
35 echo % import of plain diff should fail without message
36 hg clone -r0 a b
36 hg clone -r0 a b
37 hg --cwd a diff -r0:1 > tip.patch
37 hg --cwd a diff -r0:1 > tip.patch
38 hg --cwd b import ../tip.patch
38 hg --cwd b import ../tip.patch
39 rm -r b
39 rm -r b
40
40
41 echo % import of plain diff should be ok with message
41 echo % import of plain diff should be ok with message
42 hg clone -r0 a b
42 hg clone -r0 a b
43 hg --cwd a diff -r0:1 > tip.patch
43 hg --cwd a diff -r0:1 > tip.patch
44 hg --cwd b import -mpatch ../tip.patch
44 hg --cwd b import -mpatch ../tip.patch
45 rm -r b
45 rm -r b
46
46
47 echo % import of plain diff with specific date and user
47 echo % import of plain diff with specific date and user
48 hg clone -r0 a b
48 hg clone -r0 a b
49 hg --cwd a diff -r0:1 > tip.patch
49 hg --cwd a diff -r0:1 > tip.patch
50 hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../tip.patch
50 hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../tip.patch
51 hg -R b tip -pv
51 hg -R b tip -pv
52 rm -r b
52 rm -r b
53
53
54 echo % import of plain diff should be ok with --no-commit
54 echo % import of plain diff should be ok with --no-commit
55 hg clone -r0 a b
55 hg clone -r0 a b
56 hg --cwd a diff -r0:1 > tip.patch
56 hg --cwd a diff -r0:1 > tip.patch
57 hg --cwd b import --no-commit ../tip.patch
57 hg --cwd b import --no-commit ../tip.patch
58 hg --cwd b diff --nodates
58 hg --cwd b diff --nodates
59 rm -r b
59 rm -r b
60
60
61 echo % hg -R repo import
61 echo % hg -R repo import
62 # put the clone in a subdir - having a directory named "a"
62 # put the clone in a subdir - having a directory named "a"
63 # used to hide a bug.
63 # used to hide a bug.
64 mkdir dir
64 mkdir dir
65 hg clone -r0 a dir/b
65 hg clone -r0 a dir/b
66 hg --cwd a export tip > dir/tip.patch
66 hg --cwd a export tip > dir/tip.patch
67 cd dir
67 cd dir
68 hg -R b import tip.patch
68 hg -R b import tip.patch
69 cd ..
69 cd ..
70 rm -r dir
70 rm -r dir
71
71
72 echo % import from stdin
72 echo % import from stdin
73 hg clone -r0 a b
73 hg clone -r0 a b
74 hg --cwd a export tip | hg --cwd b import -
74 hg --cwd a export tip | hg --cwd b import -
75 rm -r b
75 rm -r b
76
76
77 echo % override commit message
77 echo % override commit message
78 hg clone -r0 a b
78 hg clone -r0 a b
79 hg --cwd a export tip | hg --cwd b import -m 'override' -
79 hg --cwd a export tip | hg --cwd b import -m 'override' -
80 hg --cwd b tip | grep override
80 hg --cwd b tip | grep override
81 rm -r b
81 rm -r b
82
82
83 cat > mkmsg.py <<EOF
83 cat > mkmsg.py <<EOF
84 import email.Message, sys
84 import email.Message, sys
85 msg = email.Message.Message()
85 msg = email.Message.Message()
86 msg.set_payload('email commit message\n' + open('tip.patch', 'rb').read())
86 msg.set_payload('email commit message\n' + open('tip.patch', 'rb').read())
87 msg['Subject'] = 'email patch'
87 msg['Subject'] = 'email patch'
88 msg['From'] = 'email patcher'
88 msg['From'] = 'email patcher'
89 sys.stdout.write(msg.as_string())
89 sys.stdout.write(msg.as_string())
90 EOF
90 EOF
91
91
92 echo % plain diff in email, subject, message body
92 echo % plain diff in email, subject, message body
93 hg clone -r0 a b
93 hg clone -r0 a b
94 hg --cwd a diff -r0:1 > tip.patch
94 hg --cwd a diff -r0:1 > tip.patch
95 python mkmsg.py > msg.patch
95 python mkmsg.py > msg.patch
96 hg --cwd b import ../msg.patch
96 hg --cwd b import ../msg.patch
97 hg --cwd b tip | grep email
97 hg --cwd b tip | grep email
98 rm -r b
98 rm -r b
99
99
100 echo % plain diff in email, no subject, message body
100 echo % plain diff in email, no subject, message body
101 hg clone -r0 a b
101 hg clone -r0 a b
102 grep -v '^Subject:' msg.patch | hg --cwd b import -
102 grep -v '^Subject:' msg.patch | hg --cwd b import -
103 rm -r b
103 rm -r b
104
104
105 echo % plain diff in email, subject, no message body
105 echo % plain diff in email, subject, no message body
106 hg clone -r0 a b
106 hg clone -r0 a b
107 grep -v '^email ' msg.patch | hg --cwd b import -
107 grep -v '^email ' msg.patch | hg --cwd b import -
108 rm -r b
108 rm -r b
109
109
110 echo % plain diff in email, no subject, no message body, should fail
110 echo % plain diff in email, no subject, no message body, should fail
111 hg clone -r0 a b
111 hg clone -r0 a b
112 egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
112 egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
113 rm -r b
113 rm -r b
114
114
115 echo % hg export in email, should use patch header
115 echo % hg export in email, should use patch header
116 hg clone -r0 a b
116 hg clone -r0 a b
117 hg --cwd a export tip > tip.patch
117 hg --cwd a export tip > tip.patch
118 python mkmsg.py | hg --cwd b import -
118 python mkmsg.py | hg --cwd b import -
119 hg --cwd b tip | grep second
119 hg --cwd b tip | grep second
120 rm -r b
120 rm -r b
121
121
122 # subject: duplicate detection, removal of [PATCH]
122 # subject: duplicate detection, removal of [PATCH]
123 # The '---' tests the gitsendmail handling without proper mail headers
123 # The '---' tests the gitsendmail handling without proper mail headers
124 cat > mkmsg2.py <<EOF
124 cat > mkmsg2.py <<EOF
125 import email.Message, sys
125 import email.Message, sys
126 msg = email.Message.Message()
126 msg = email.Message.Message()
127 msg.set_payload('email patch\n\nnext line\n---\n' + open('tip.patch').read())
127 msg.set_payload('email patch\n\nnext line\n---\n' + open('tip.patch').read())
128 msg['Subject'] = '[PATCH] email patch'
128 msg['Subject'] = '[PATCH] email patch'
129 msg['From'] = 'email patcher'
129 msg['From'] = 'email patcher'
130 sys.stdout.write(msg.as_string())
130 sys.stdout.write(msg.as_string())
131 EOF
131 EOF
132
132
133 echo '% plain diff in email, [PATCH] subject, message body with subject'
133 echo '% plain diff in email, [PATCH] subject, message body with subject'
134 hg clone -r0 a b
134 hg clone -r0 a b
135 hg --cwd a diff -r0:1 > tip.patch
135 hg --cwd a diff -r0:1 > tip.patch
136 python mkmsg2.py | hg --cwd b import -
136 python mkmsg2.py | hg --cwd b import -
137 hg --cwd b tip --template '{desc}\n'
137 hg --cwd b tip --template '{desc}\n'
138 rm -r b
138 rm -r b
139
139
140 # We weren't backing up the correct dirstate file when importing many patches
140 # We weren't backing up the correct dirstate file when importing many patches
141 # (issue963)
141 # (issue963)
142 echo '% import patch1 patch2; rollback'
142 echo '% import patch1 patch2; rollback'
143 echo line 3 >> a/a
143 echo line 3 >> a/a
144 hg --cwd a ci -m'third change'
144 hg --cwd a ci -m'third change'
145 hg --cwd a export -o '../patch%R' 1 2
145 hg --cwd a export -o '../patch%R' 1 2
146 hg clone -qr0 a b
146 hg clone -qr0 a b
147 hg --cwd b parents --template 'parent: {rev}\n'
147 hg --cwd b parents --template 'parent: {rev}\n'
148 hg --cwd b import ../patch1 ../patch2
148 hg --cwd b import ../patch1 ../patch2
149 hg --cwd b rollback
149 hg --cwd b rollback
150 hg --cwd b parents --template 'parent: {rev}\n'
150 hg --cwd b parents --template 'parent: {rev}\n'
151 rm -r b
151 rm -r b
152
152
153 # bug non regression test
153 # bug non regression test
154 # importing a patch in a subdirectory failed at the commit stage
154 # importing a patch in a subdirectory failed at the commit stage
155 echo line 2 >> a/d1/d2/a
155 echo line 2 >> a/d1/d2/a
156 hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change'
156 hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change'
157 echo % hg import in a subdirectory
157 echo % hg import in a subdirectory
158 hg clone -r0 a b
158 hg clone -r0 a b
159 hg --cwd a export tip | sed -e 's/d1\/d2\///' > tip.patch
159 hg --cwd a export tip | sed -e 's/d1\/d2\///' > tip.patch
160 dir=`pwd`
160 dir=`pwd`
161 cd b/d1/d2 2>&1 > /dev/null
161 cd b/d1/d2 2>&1 > /dev/null
162 hg import ../../../tip.patch
162 hg import ../../../tip.patch
163 cd $dir
163 cd $dir
164 echo "% message should be 'subdir change'"
164 echo "% message should be 'subdir change'"
165 hg --cwd b tip | grep 'subdir change'
165 hg --cwd b tip | grep 'subdir change'
166 echo "% committer should be 'someoneelse'"
166 echo "% committer should be 'someoneelse'"
167 hg --cwd b tip | grep someoneelse
167 hg --cwd b tip | grep someoneelse
168 echo "% should be empty"
168 echo "% should be empty"
169 hg --cwd b status
169 hg --cwd b status
170
170
171
171
172 # Test fuzziness (ambiguous patch location, fuzz=2)
172 # Test fuzziness (ambiguous patch location, fuzz=2)
173 echo % test fuzziness
173 echo % test fuzziness
174 hg init fuzzy
174 hg init fuzzy
175 cd fuzzy
175 cd fuzzy
176 echo line1 > a
176 echo line1 > a
177 echo line0 >> a
177 echo line0 >> a
178 echo line3 >> a
178 echo line3 >> a
179 hg ci -Am adda
179 hg ci -Am adda
180 echo line1 > a
180 echo line1 > a
181 echo line2 >> a
181 echo line2 >> a
182 echo line0 >> a
182 echo line0 >> a
183 echo line3 >> a
183 echo line3 >> a
184 hg ci -m change a
184 hg ci -m change a
185 hg export tip > tip.patch
185 hg export tip > tip.patch
186 hg up -C 0
186 hg up -C 0
187 echo line1 > a
187 echo line1 > a
188 echo line0 >> a
188 echo line0 >> a
189 echo line1 >> a
189 echo line1 >> a
190 echo line0 >> a
190 echo line0 >> a
191 hg ci -m brancha
191 hg ci -m brancha
192 hg import -v tip.patch
192 hg import -v tip.patch
193 cd ..
193 cd ..
194
194
195 # Test hunk touching empty files (issue906)
195 # Test hunk touching empty files (issue906)
196 hg init empty
196 hg init empty
197 cd empty
197 cd empty
198 touch a
198 touch a
199 touch b1
199 touch b1
200 touch c1
200 touch c1
201 echo d > d
201 echo d > d
202 hg ci -Am init
202 hg ci -Am init
203 echo a > a
203 echo a > a
204 echo b > b1
204 echo b > b1
205 hg mv b1 b2
205 hg mv b1 b2
206 echo c > c1
206 echo c > c1
207 hg copy c1 c2
207 hg copy c1 c2
208 rm d
208 rm d
209 touch d
209 touch d
210 hg diff --git
210 hg diff --git
211 hg ci -m empty
211 hg ci -m empty
212 hg export --git tip > empty.diff
212 hg export --git tip > empty.diff
213 hg up -C 0
213 hg up -C 0
214 hg import empty.diff
214 hg import empty.diff
215 for name in a b1 b2 c1 c2 d;
215 for name in a b1 b2 c1 c2 d;
216 do
216 do
217 echo % $name file
217 echo % $name file
218 test -f $name && cat $name
218 test -f $name && cat $name
219 done
219 done
220 cd ..
220 cd ..
221
221
222 # Test importing a patch ending with a binary file removal
222 # Test importing a patch ending with a binary file removal
223 echo % test trailing binary removal
223 echo % test trailing binary removal
224 hg init binaryremoval
224 hg init binaryremoval
225 cd binaryremoval
225 cd binaryremoval
226 echo a > a
226 echo a > a
227 python -c "file('b', 'wb').write('a\x00b')"
227 python -c "file('b', 'wb').write('a\x00b')"
228 hg ci -Am addall
228 hg ci -Am addall
229 hg rm a
229 hg rm a
230 hg rm b
230 hg rm b
231 hg st
231 hg st
232 hg ci -m remove
232 hg ci -m remove
233 hg export --git . > remove.diff
233 hg export --git . > remove.diff
234 cat remove.diff | grep git
234 cat remove.diff | grep git
235 hg up -C 0
235 hg up -C 0
236 hg import remove.diff
236 hg import remove.diff
237 hg manifest
237 hg manifest
238 cd ..
238 cd ..
239
239
240 echo % 'test update+rename with common name (issue 927)'
240 echo % 'test update+rename with common name (issue 927)'
241 hg init t
241 hg init t
242 cd t
242 cd t
243 touch a
243 touch a
244 hg ci -Am t
244 hg ci -Am t
245 echo a > a
245 echo a > a
246 # Here, bfile.startswith(afile)
246 # Here, bfile.startswith(afile)
247 hg copy a a2
247 hg copy a a2
248 hg ci -m copya
248 hg ci -m copya
249 hg export --git tip > copy.diff
249 hg export --git tip > copy.diff
250 hg up -C 0
250 hg up -C 0
251 hg import copy.diff
251 hg import copy.diff
252 echo % view a
252 echo % view a
253 # a should contain an 'a'
253 # a should contain an 'a'
254 cat a
254 cat a
255 echo % view a2
255 echo % view a2
256 # and a2 should have duplicated it
256 # and a2 should have duplicated it
257 cat a2
257 cat a2
258 cd ..
258 cd ..
259
259
260 echo % 'test -p0'
260 echo % 'test -p0'
261 hg init p0
261 hg init p0
262 cd p0
262 cd p0
263 echo a > a
263 echo a > a
264 hg ci -Am t
264 hg ci -Am t
265 hg import -p0 - << EOF
265 hg import -p0 - << EOF
266 foobar
266 foobar
267 --- a Sat Apr 12 22:43:58 2008 -0400
267 --- a Sat Apr 12 22:43:58 2008 -0400
268 +++ a Sat Apr 12 22:44:05 2008 -0400
268 +++ a Sat Apr 12 22:44:05 2008 -0400
269 @@ -1,1 +1,1 @@
269 @@ -1,1 +1,1 @@
270 -a
270 -a
271 +bb
271 +bb
272 EOF
272 EOF
273 hg status
273 hg status
274 cat a
274 cat a
275 cd ..
275 cd ..
276
276
277 echo % 'test paths outside repo root'
277 echo % 'test paths outside repo root'
278 mkdir outside
278 mkdir outside
279 touch outside/foo
279 touch outside/foo
280 hg init inside
280 hg init inside
281 cd inside
281 cd inside
282 hg import - <<EOF
282 hg import - <<EOF
283 diff --git a/a b/b
283 diff --git a/a b/b
284 rename from ../outside/foo
284 rename from ../outside/foo
285 rename to bar
285 rename to bar
286 EOF
286 EOF
287 cd ..
287 cd ..
288
288
289 echo '% test import with similarity (issue295)'
289 echo '% test import with similarity (issue295)'
290 hg init sim
290 hg init sim
291 cd sim
291 cd sim
292 echo 'this is a test' > a
292 echo 'this is a test' > a
293 hg ci -Ama
293 hg ci -Ama
294 cat > ../rename.diff <<EOF
294 cat > ../rename.diff <<EOF
295 diff --git a/a b/a
295 diff --git a/a b/a
296 deleted file mode 100644
296 deleted file mode 100644
297 --- a/a
297 --- a/a
298 +++ /dev/null
298 +++ /dev/null
299 @@ -1,1 +0,0 @@
299 @@ -1,1 +0,0 @@
300 -this is a test
300 -this is a test
301 diff --git a/b b/b
301 diff --git a/b b/b
302 new file mode 100644
302 new file mode 100644
303 --- /dev/null
303 --- /dev/null
304 +++ b/b
304 +++ b/b
305 @@ -0,0 +1,2 @@
305 @@ -0,0 +1,2 @@
306 +this is a test
306 +this is a test
307 +foo
307 +foo
308 EOF
308 EOF
309 hg import --no-commit -v -s 1 ../rename.diff
309 hg import --no-commit -v -s 1 ../rename.diff
310 hg st -C
310 hg st -C
311 hg revert -a
311 hg revert -a
312 rm b
312 rm b
313 hg import --no-commit -v -s 100 ../rename.diff
313 hg import --no-commit -v -s 100 ../rename.diff
314 hg st -C
314 hg st -C
315 cd ..
315 cd ..
316
316
317
317
318 echo '% add empty file from the end of patch (issue 1495)'
318 echo '% add empty file from the end of patch (issue 1495)'
319 hg init addemptyend
319 hg init addemptyend
320 cd addemptyend
320 cd addemptyend
321 touch a
321 touch a
322 hg addremove
322 hg addremove
323 hg ci -m "commit"
323 hg ci -m "commit"
324 cat > a.patch <<EOF
324 cat > a.patch <<EOF
325 diff --git a/a b/a
325 diff --git a/a b/a
326 --- a/a
326 --- a/a
327 +++ b/a
327 +++ b/a
328 @@ -0,0 +1,1 @@
328 @@ -0,0 +1,1 @@
329 +a
329 +a
330 diff --git a/b b/b
330 diff --git a/b b/b
331 new file mode 100644
331 new file mode 100644
332 EOF
332 EOF
333 hg import --no-commit a.patch
333 hg import --no-commit a.patch
334 cd ..
334 cd ..
335
335
336 echo '% create file when source is not /dev/null'
336 echo '% create file when source is not /dev/null'
337 cat > create.patch <<EOF
337 cat > create.patch <<EOF
338 diff -Naur proj-orig/foo proj-new/foo
338 diff -Naur proj-orig/foo proj-new/foo
339 --- proj-orig/foo 1969-12-31 16:00:00.000000000 -0800
339 --- proj-orig/foo 1969-12-31 16:00:00.000000000 -0800
340 +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
340 +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
341 @@ -0,0 +1,1 @@
341 @@ -0,0 +1,1 @@
342 +a
342 +a
343 EOF
343 EOF
344 hg init oddcreate
344 hg init oddcreate
345 cd oddcreate
345 cd oddcreate
346 hg import --no-commit ../create.patch
346 hg import --no-commit ../create.patch
347 cat foo
347 cat foo
348 cd ..
349
350 echo % 'first line mistaken for email headers (issue 1859)'
351 hg init emailconfusion
352 cd emailconfusion
353 cat > a.patch <<EOF
354 module: summary
355
356 description
357
358
359 diff -r 000000000000 -r 9b4c1e343b55 test.txt
360 --- /dev/null
361 +++ b/a
362 @@ -0,0 +1,1 @@
363 +a
364 EOF
365 hg import -d '0 0' a.patch
366 hg parents -v
367 cd ..
368
@@ -1,297 +1,310
1 adding a
1 adding a
2 adding d1/d2/a
2 adding d1/d2/a
3 % import exported patch
3 % import exported patch
4 requesting all changes
4 requesting all changes
5 adding changesets
5 adding changesets
6 adding manifests
6 adding manifests
7 adding file changes
7 adding file changes
8 added 1 changesets with 2 changes to 2 files
8 added 1 changesets with 2 changes to 2 files
9 updating working directory
9 updating working directory
10 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
10 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
11 applying ../tip.patch
11 applying ../tip.patch
12 % message should be same
12 % message should be same
13 summary: second change
13 summary: second change
14 % committer should be same
14 % committer should be same
15 user: someone
15 user: someone
16 % import exported patch with external patcher
16 % import exported patch with external patcher
17 requesting all changes
17 requesting all changes
18 adding changesets
18 adding changesets
19 adding manifests
19 adding manifests
20 adding file changes
20 adding file changes
21 added 1 changesets with 2 changes to 2 files
21 added 1 changesets with 2 changes to 2 files
22 updating working directory
22 updating working directory
23 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
23 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
24 applying ../tip.patch
24 applying ../tip.patch
25 line2
25 line2
26 % import of plain diff should fail without message
26 % import of plain diff should fail without message
27 requesting all changes
27 requesting all changes
28 adding changesets
28 adding changesets
29 adding manifests
29 adding manifests
30 adding file changes
30 adding file changes
31 added 1 changesets with 2 changes to 2 files
31 added 1 changesets with 2 changes to 2 files
32 updating working directory
32 updating working directory
33 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
33 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
34 applying ../tip.patch
34 applying ../tip.patch
35 abort: empty commit message
35 abort: empty commit message
36 % import of plain diff should be ok with message
36 % import of plain diff should be ok with message
37 requesting all changes
37 requesting all changes
38 adding changesets
38 adding changesets
39 adding manifests
39 adding manifests
40 adding file changes
40 adding file changes
41 added 1 changesets with 2 changes to 2 files
41 added 1 changesets with 2 changes to 2 files
42 updating working directory
42 updating working directory
43 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
43 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
44 applying ../tip.patch
44 applying ../tip.patch
45 % import of plain diff with specific date and user
45 % import of plain diff with specific date and user
46 requesting all changes
46 requesting all changes
47 adding changesets
47 adding changesets
48 adding manifests
48 adding manifests
49 adding file changes
49 adding file changes
50 added 1 changesets with 2 changes to 2 files
50 added 1 changesets with 2 changes to 2 files
51 updating working directory
51 updating working directory
52 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
52 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
53 applying ../tip.patch
53 applying ../tip.patch
54 changeset: 1:ca68f19f3a40
54 changeset: 1:ca68f19f3a40
55 tag: tip
55 tag: tip
56 user: user@nowhere.net
56 user: user@nowhere.net
57 date: Thu Jan 01 00:00:01 1970 +0000
57 date: Thu Jan 01 00:00:01 1970 +0000
58 files: a
58 files: a
59 description:
59 description:
60 patch
60 patch
61
61
62
62
63 diff -r 80971e65b431 -r ca68f19f3a40 a
63 diff -r 80971e65b431 -r ca68f19f3a40 a
64 --- a/a Thu Jan 01 00:00:00 1970 +0000
64 --- a/a Thu Jan 01 00:00:00 1970 +0000
65 +++ b/a Thu Jan 01 00:00:01 1970 +0000
65 +++ b/a Thu Jan 01 00:00:01 1970 +0000
66 @@ -1,1 +1,2 @@
66 @@ -1,1 +1,2 @@
67 line 1
67 line 1
68 +line 2
68 +line 2
69
69
70 % import of plain diff should be ok with --no-commit
70 % import of plain diff should be ok with --no-commit
71 requesting all changes
71 requesting all changes
72 adding changesets
72 adding changesets
73 adding manifests
73 adding manifests
74 adding file changes
74 adding file changes
75 added 1 changesets with 2 changes to 2 files
75 added 1 changesets with 2 changes to 2 files
76 updating working directory
76 updating working directory
77 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
77 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
78 applying ../tip.patch
78 applying ../tip.patch
79 diff -r 80971e65b431 a
79 diff -r 80971e65b431 a
80 --- a/a
80 --- a/a
81 +++ b/a
81 +++ b/a
82 @@ -1,1 +1,2 @@
82 @@ -1,1 +1,2 @@
83 line 1
83 line 1
84 +line 2
84 +line 2
85 % hg -R repo import
85 % hg -R repo import
86 requesting all changes
86 requesting all changes
87 adding changesets
87 adding changesets
88 adding manifests
88 adding manifests
89 adding file changes
89 adding file changes
90 added 1 changesets with 2 changes to 2 files
90 added 1 changesets with 2 changes to 2 files
91 updating working directory
91 updating working directory
92 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
92 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
93 applying tip.patch
93 applying tip.patch
94 % import from stdin
94 % import from stdin
95 requesting all changes
95 requesting all changes
96 adding changesets
96 adding changesets
97 adding manifests
97 adding manifests
98 adding file changes
98 adding file changes
99 added 1 changesets with 2 changes to 2 files
99 added 1 changesets with 2 changes to 2 files
100 updating working directory
100 updating working directory
101 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
101 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
102 applying patch from stdin
102 applying patch from stdin
103 % override commit message
103 % override commit message
104 requesting all changes
104 requesting all changes
105 adding changesets
105 adding changesets
106 adding manifests
106 adding manifests
107 adding file changes
107 adding file changes
108 added 1 changesets with 2 changes to 2 files
108 added 1 changesets with 2 changes to 2 files
109 updating working directory
109 updating working directory
110 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
110 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
111 applying patch from stdin
111 applying patch from stdin
112 summary: override
112 summary: override
113 % plain diff in email, subject, message body
113 % plain diff in email, subject, message body
114 requesting all changes
114 requesting all changes
115 adding changesets
115 adding changesets
116 adding manifests
116 adding manifests
117 adding file changes
117 adding file changes
118 added 1 changesets with 2 changes to 2 files
118 added 1 changesets with 2 changes to 2 files
119 updating working directory
119 updating working directory
120 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
120 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
121 applying ../msg.patch
121 applying ../msg.patch
122 user: email patcher
122 user: email patcher
123 summary: email patch
123 summary: email patch
124 % plain diff in email, no subject, message body
124 % plain diff in email, no subject, message body
125 requesting all changes
125 requesting all changes
126 adding changesets
126 adding changesets
127 adding manifests
127 adding manifests
128 adding file changes
128 adding file changes
129 added 1 changesets with 2 changes to 2 files
129 added 1 changesets with 2 changes to 2 files
130 updating working directory
130 updating working directory
131 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
131 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
132 applying patch from stdin
132 applying patch from stdin
133 % plain diff in email, subject, no message body
133 % plain diff in email, subject, no message body
134 requesting all changes
134 requesting all changes
135 adding changesets
135 adding changesets
136 adding manifests
136 adding manifests
137 adding file changes
137 adding file changes
138 added 1 changesets with 2 changes to 2 files
138 added 1 changesets with 2 changes to 2 files
139 updating working directory
139 updating working directory
140 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
140 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
141 applying patch from stdin
141 applying patch from stdin
142 % plain diff in email, no subject, no message body, should fail
142 % plain diff in email, no subject, no message body, should fail
143 requesting all changes
143 requesting all changes
144 adding changesets
144 adding changesets
145 adding manifests
145 adding manifests
146 adding file changes
146 adding file changes
147 added 1 changesets with 2 changes to 2 files
147 added 1 changesets with 2 changes to 2 files
148 updating working directory
148 updating working directory
149 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
149 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
150 applying patch from stdin
150 applying patch from stdin
151 abort: empty commit message
151 abort: empty commit message
152 % hg export in email, should use patch header
152 % hg export in email, should use patch header
153 requesting all changes
153 requesting all changes
154 adding changesets
154 adding changesets
155 adding manifests
155 adding manifests
156 adding file changes
156 adding file changes
157 added 1 changesets with 2 changes to 2 files
157 added 1 changesets with 2 changes to 2 files
158 updating working directory
158 updating working directory
159 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
159 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
160 applying patch from stdin
160 applying patch from stdin
161 summary: second change
161 summary: second change
162 % plain diff in email, [PATCH] subject, message body with subject
162 % plain diff in email, [PATCH] subject, message body with subject
163 requesting all changes
163 requesting all changes
164 adding changesets
164 adding changesets
165 adding manifests
165 adding manifests
166 adding file changes
166 adding file changes
167 added 1 changesets with 2 changes to 2 files
167 added 1 changesets with 2 changes to 2 files
168 updating working directory
168 updating working directory
169 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
169 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
170 applying patch from stdin
170 applying patch from stdin
171 email patch
171 email patch
172
172
173 next line
173 next line
174 ---
174 ---
175 % import patch1 patch2; rollback
175 % import patch1 patch2; rollback
176 parent: 0
176 parent: 0
177 applying ../patch1
177 applying ../patch1
178 applying ../patch2
178 applying ../patch2
179 rolling back last transaction
179 rolling back last transaction
180 parent: 1
180 parent: 1
181 % hg import in a subdirectory
181 % hg import in a subdirectory
182 requesting all changes
182 requesting all changes
183 adding changesets
183 adding changesets
184 adding manifests
184 adding manifests
185 adding file changes
185 adding file changes
186 added 1 changesets with 2 changes to 2 files
186 added 1 changesets with 2 changes to 2 files
187 updating working directory
187 updating working directory
188 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
188 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
189 applying ../../../tip.patch
189 applying ../../../tip.patch
190 % message should be 'subdir change'
190 % message should be 'subdir change'
191 summary: subdir change
191 summary: subdir change
192 % committer should be 'someoneelse'
192 % committer should be 'someoneelse'
193 user: someoneelse
193 user: someoneelse
194 % should be empty
194 % should be empty
195 % test fuzziness
195 % test fuzziness
196 adding a
196 adding a
197 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
197 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
198 created new head
198 created new head
199 applying tip.patch
199 applying tip.patch
200 patching file a
200 patching file a
201 Hunk #1 succeeded at 1 with fuzz 2 (offset -2 lines).
201 Hunk #1 succeeded at 1 with fuzz 2 (offset -2 lines).
202 a
202 a
203 adding a
203 adding a
204 adding b1
204 adding b1
205 adding c1
205 adding c1
206 adding d
206 adding d
207 diff --git a/a b/a
207 diff --git a/a b/a
208 --- a/a
208 --- a/a
209 +++ b/a
209 +++ b/a
210 @@ -0,0 +1,1 @@
210 @@ -0,0 +1,1 @@
211 +a
211 +a
212 diff --git a/b1 b/b2
212 diff --git a/b1 b/b2
213 rename from b1
213 rename from b1
214 rename to b2
214 rename to b2
215 --- a/b1
215 --- a/b1
216 +++ b/b2
216 +++ b/b2
217 @@ -0,0 +1,1 @@
217 @@ -0,0 +1,1 @@
218 +b
218 +b
219 diff --git a/c1 b/c1
219 diff --git a/c1 b/c1
220 --- a/c1
220 --- a/c1
221 +++ b/c1
221 +++ b/c1
222 @@ -0,0 +1,1 @@
222 @@ -0,0 +1,1 @@
223 +c
223 +c
224 diff --git a/c1 b/c2
224 diff --git a/c1 b/c2
225 copy from c1
225 copy from c1
226 copy to c2
226 copy to c2
227 --- a/c1
227 --- a/c1
228 +++ b/c2
228 +++ b/c2
229 @@ -0,0 +1,1 @@
229 @@ -0,0 +1,1 @@
230 +c
230 +c
231 diff --git a/d b/d
231 diff --git a/d b/d
232 --- a/d
232 --- a/d
233 +++ b/d
233 +++ b/d
234 @@ -1,1 +0,0 @@
234 @@ -1,1 +0,0 @@
235 -d
235 -d
236 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
236 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
237 applying empty.diff
237 applying empty.diff
238 % a file
238 % a file
239 a
239 a
240 % b1 file
240 % b1 file
241 % b2 file
241 % b2 file
242 b
242 b
243 % c1 file
243 % c1 file
244 c
244 c
245 % c2 file
245 % c2 file
246 c
246 c
247 % d file
247 % d file
248 % test trailing binary removal
248 % test trailing binary removal
249 adding a
249 adding a
250 adding b
250 adding b
251 R a
251 R a
252 R b
252 R b
253 diff --git a/a b/a
253 diff --git a/a b/a
254 diff --git a/b b/b
254 diff --git a/b b/b
255 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
255 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
256 applying remove.diff
256 applying remove.diff
257 % test update+rename with common name (issue 927)
257 % test update+rename with common name (issue 927)
258 adding a
258 adding a
259 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
259 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
260 applying copy.diff
260 applying copy.diff
261 % view a
261 % view a
262 a
262 a
263 % view a2
263 % view a2
264 a
264 a
265 % test -p0
265 % test -p0
266 adding a
266 adding a
267 applying patch from stdin
267 applying patch from stdin
268 bb
268 bb
269 % test paths outside repo root
269 % test paths outside repo root
270 applying patch from stdin
270 applying patch from stdin
271 abort: ../outside/foo not under root
271 abort: ../outside/foo not under root
272 % test import with similarity (issue295)
272 % test import with similarity (issue295)
273 adding a
273 adding a
274 applying ../rename.diff
274 applying ../rename.diff
275 patching file a
275 patching file a
276 patching file b
276 patching file b
277 removing a
277 removing a
278 adding b
278 adding b
279 recording removal of a as rename to b (88% similar)
279 recording removal of a as rename to b (88% similar)
280 A b
280 A b
281 a
281 a
282 R a
282 R a
283 undeleting a
283 undeleting a
284 forgetting b
284 forgetting b
285 applying ../rename.diff
285 applying ../rename.diff
286 patching file a
286 patching file a
287 patching file b
287 patching file b
288 removing a
288 removing a
289 adding b
289 adding b
290 A b
290 A b
291 R a
291 R a
292 % add empty file from the end of patch (issue 1495)
292 % add empty file from the end of patch (issue 1495)
293 adding a
293 adding a
294 applying a.patch
294 applying a.patch
295 % create file when source is not /dev/null
295 % create file when source is not /dev/null
296 applying ../create.patch
296 applying ../create.patch
297 a
297 a
298 % first line mistaken for email headers (issue 1859)
299 applying a.patch
300 changeset: 0:5a681217c0ad
301 tag: tip
302 user: test
303 date: Thu Jan 01 00:00:00 1970 +0000
304 files: a
305 description:
306 module: summary
307
308 description
309
310
@@ -1,110 +1,119
1 #!/bin/sh
1 #!/bin/sh
2
2
3 remove() {
3 remove() {
4 hg rm $@
4 hg rm $@
5 hg st
5 hg st
6 # do not use ls -R, which recurses in .hg subdirs on Mac OS X 10.5
6 # do not use ls -R, which recurses in .hg subdirs on Mac OS X 10.5
7 find . -name .hg -prune -o -type f -print | sort
7 find . -name .hg -prune -o -type f -print | sort
8 hg up -C
8 hg up -C
9 }
9 }
10
10
11 hg init a
11 hg init a
12 cd a
12 cd a
13 echo a > foo
13 echo a > foo
14
14
15 echo % file not managed
15 echo % file not managed
16 remove foo
16 remove foo
17
17
18 hg add foo
18 hg add foo
19 hg commit -m1
19 hg commit -m1
20
20
21 # the table cases
21 # the table cases
22
22
23 echo % 00 state added, options none
23 echo % 00 state added, options none
24 echo b > bar
24 echo b > bar
25 hg add bar
25 hg add bar
26 remove bar
26 remove bar
27
27
28 echo % 01 state clean, options none
28 echo % 01 state clean, options none
29 remove foo
29 remove foo
30
30
31 echo % 02 state modified, options none
31 echo % 02 state modified, options none
32 echo b >> foo
32 echo b >> foo
33 remove foo
33 remove foo
34
34
35 echo % 03 state missing, options none
35 echo % 03 state missing, options none
36 rm foo
36 rm foo
37 remove foo
37 remove foo
38
38
39 echo % 10 state added, options -f
39 echo % 10 state added, options -f
40 echo b > bar
40 echo b > bar
41 hg add bar
41 hg add bar
42 remove -f bar
42 remove -f bar
43 rm bar
43 rm bar
44
44
45 echo % 11 state clean, options -f
45 echo % 11 state clean, options -f
46 remove -f foo
46 remove -f foo
47
47
48 echo % 12 state modified, options -f
48 echo % 12 state modified, options -f
49 echo b >> foo
49 echo b >> foo
50 remove -f foo
50 remove -f foo
51
51
52 echo % 13 state missing, options -f
52 echo % 13 state missing, options -f
53 rm foo
53 rm foo
54 remove -f foo
54 remove -f foo
55
55
56 echo % 20 state added, options -A
56 echo % 20 state added, options -A
57 echo b > bar
57 echo b > bar
58 hg add bar
58 hg add bar
59 remove -A bar
59 remove -A bar
60
60
61 echo % 21 state clean, options -A
61 echo % 21 state clean, options -A
62 remove -A foo
62 remove -A foo
63
63
64 echo % 22 state modified, options -A
64 echo % 22 state modified, options -A
65 echo b >> foo
65 echo b >> foo
66 remove -A foo
66 remove -A foo
67
67
68 echo % 23 state missing, options -A
68 echo % 23 state missing, options -A
69 rm foo
69 rm foo
70 remove -A foo
70 remove -A foo
71
71
72 echo % 30 state added, options -Af
72 echo % 30 state added, options -Af
73 echo b > bar
73 echo b > bar
74 hg add bar
74 hg add bar
75 remove -Af bar
75 remove -Af bar
76 rm bar
76 rm bar
77
77
78 echo % 31 state clean, options -Af
78 echo % 31 state clean, options -Af
79 remove -Af foo
79 remove -Af foo
80
80
81 echo % 32 state modified, options -Af
81 echo % 32 state modified, options -Af
82 echo b >> foo
82 echo b >> foo
83 remove -Af foo
83 remove -Af foo
84
84
85 echo % 33 state missing, options -Af
85 echo % 33 state missing, options -Af
86 rm foo
86 rm foo
87 remove -Af foo
87 remove -Af foo
88
88
89 # test some directory stuff
89 # test some directory stuff
90
90
91 mkdir test
91 mkdir test
92 echo a > test/foo
92 echo a > test/foo
93 echo b > test/bar
93 echo b > test/bar
94 hg ci -Am2
94 hg ci -Am2
95
95
96 echo % dir, options none
96 echo % dir, options none
97 rm test/bar
97 rm test/bar
98 remove test
98 remove test
99
99
100 echo % dir, options -f
100 echo % dir, options -f
101 rm test/bar
101 rm test/bar
102 remove -f test
102 remove -f test
103
103
104 echo % dir, options -A
104 echo % dir, options -A
105 rm test/bar
105 rm test/bar
106 remove -A test
106 remove -A test
107
107
108 echo % dir, options -Af
108 echo % dir, options -Af
109 rm test/bar
109 rm test/bar
110 remove -Af test
110 remove -Af test
111
112 echo 'test remove dropping empty trees (issue1861)'
113 mkdir -p issue1861/b/c
114 echo x > issue1861/x
115 echo y > issue1861/b/c/y
116 hg ci -Am add
117 hg rm issue1861/b
118 hg ci -m remove
119 ls issue1861
@@ -1,113 +1,118
1 % file not managed
1 % file not managed
2 not removing foo: file is untracked
2 not removing foo: file is untracked
3 ? foo
3 ? foo
4 ./foo
4 ./foo
5 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
5 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
6 % 00 state added, options none
6 % 00 state added, options none
7 not removing bar: file has been marked for add (use -f to force removal)
7 not removing bar: file has been marked for add (use -f to force removal)
8 A bar
8 A bar
9 ./bar
9 ./bar
10 ./foo
10 ./foo
11 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
11 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
12 % 01 state clean, options none
12 % 01 state clean, options none
13 R foo
13 R foo
14 ? bar
14 ? bar
15 ./bar
15 ./bar
16 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
16 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
17 % 02 state modified, options none
17 % 02 state modified, options none
18 not removing foo: file is modified (use -f to force removal)
18 not removing foo: file is modified (use -f to force removal)
19 M foo
19 M foo
20 ? bar
20 ? bar
21 ./bar
21 ./bar
22 ./foo
22 ./foo
23 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
23 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
24 % 03 state missing, options none
24 % 03 state missing, options none
25 R foo
25 R foo
26 ? bar
26 ? bar
27 ./bar
27 ./bar
28 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
28 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
29 % 10 state added, options -f
29 % 10 state added, options -f
30 ? bar
30 ? bar
31 ./bar
31 ./bar
32 ./foo
32 ./foo
33 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
33 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
34 % 11 state clean, options -f
34 % 11 state clean, options -f
35 R foo
35 R foo
36 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
37 % 12 state modified, options -f
37 % 12 state modified, options -f
38 R foo
38 R foo
39 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
39 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
40 % 13 state missing, options -f
40 % 13 state missing, options -f
41 R foo
41 R foo
42 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
42 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
43 % 20 state added, options -A
43 % 20 state added, options -A
44 not removing bar: file still exists (use -f to force removal)
44 not removing bar: file still exists (use -f to force removal)
45 A bar
45 A bar
46 ./bar
46 ./bar
47 ./foo
47 ./foo
48 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
48 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
49 % 21 state clean, options -A
49 % 21 state clean, options -A
50 not removing foo: file still exists (use -f to force removal)
50 not removing foo: file still exists (use -f to force removal)
51 ? bar
51 ? bar
52 ./bar
52 ./bar
53 ./foo
53 ./foo
54 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
54 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
55 % 22 state modified, options -A
55 % 22 state modified, options -A
56 not removing foo: file still exists (use -f to force removal)
56 not removing foo: file still exists (use -f to force removal)
57 M foo
57 M foo
58 ? bar
58 ? bar
59 ./bar
59 ./bar
60 ./foo
60 ./foo
61 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
61 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
62 % 23 state missing, options -A
62 % 23 state missing, options -A
63 R foo
63 R foo
64 ? bar
64 ? bar
65 ./bar
65 ./bar
66 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
66 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
67 % 30 state added, options -Af
67 % 30 state added, options -Af
68 ? bar
68 ? bar
69 ./bar
69 ./bar
70 ./foo
70 ./foo
71 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
71 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
72 % 31 state clean, options -Af
72 % 31 state clean, options -Af
73 R foo
73 R foo
74 ./foo
74 ./foo
75 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
75 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
76 % 32 state modified, options -Af
76 % 32 state modified, options -Af
77 R foo
77 R foo
78 ./foo
78 ./foo
79 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
79 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
80 % 33 state missing, options -Af
80 % 33 state missing, options -Af
81 R foo
81 R foo
82 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
82 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
83 adding test/bar
83 adding test/bar
84 adding test/foo
84 adding test/foo
85 % dir, options none
85 % dir, options none
86 removing test/bar
86 removing test/bar
87 removing test/foo
87 removing test/foo
88 R test/bar
88 R test/bar
89 R test/foo
89 R test/foo
90 ./foo
90 ./foo
91 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
91 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
92 % dir, options -f
92 % dir, options -f
93 removing test/bar
93 removing test/bar
94 removing test/foo
94 removing test/foo
95 R test/bar
95 R test/bar
96 R test/foo
96 R test/foo
97 ./foo
97 ./foo
98 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
98 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
99 % dir, options -A
99 % dir, options -A
100 not removing test/foo: file still exists (use -f to force removal)
100 not removing test/foo: file still exists (use -f to force removal)
101 removing test/bar
101 removing test/bar
102 R test/bar
102 R test/bar
103 ./foo
103 ./foo
104 ./test/foo
104 ./test/foo
105 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
105 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
106 % dir, options -Af
106 % dir, options -Af
107 removing test/bar
107 removing test/bar
108 removing test/foo
108 removing test/foo
109 R test/bar
109 R test/bar
110 R test/foo
110 R test/foo
111 ./foo
111 ./foo
112 ./test/foo
112 ./test/foo
113 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
113 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
114 test remove dropping empty trees (issue1861)
115 adding issue1861/b/c/y
116 adding issue1861/x
117 removing issue1861/b/c/y
118 x
General Comments 0
You need to be logged in to leave comments. Login now