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