##// END OF EJS Templates
clean up trailing spaces, leading spaces in C
Dirkjan Ochtman -
r7186:f77c8d83 default
parent child Browse files
Show More
@@ -1,355 +1,355 b''
1 # common code for the convert extension
1 # common code for the convert extension
2 import base64, errno
2 import base64, errno
3 import os
3 import os
4 import cPickle as pickle
4 import cPickle as pickle
5 from mercurial import util, strutil
5 from mercurial import util, strutil
6 from mercurial.i18n import _
6 from mercurial.i18n import _
7
7
8 def encodeargs(args):
8 def encodeargs(args):
9 def encodearg(s):
9 def encodearg(s):
10 lines = base64.encodestring(s)
10 lines = base64.encodestring(s)
11 lines = [l.splitlines()[0] for l in lines]
11 lines = [l.splitlines()[0] for l in lines]
12 return ''.join(lines)
12 return ''.join(lines)
13
13
14 s = pickle.dumps(args)
14 s = pickle.dumps(args)
15 return encodearg(s)
15 return encodearg(s)
16
16
17 def decodeargs(s):
17 def decodeargs(s):
18 s = base64.decodestring(s)
18 s = base64.decodestring(s)
19 return pickle.loads(s)
19 return pickle.loads(s)
20
20
21 class MissingTool(Exception): pass
21 class MissingTool(Exception): pass
22
22
23 def checktool(exe, name=None, abort=True):
23 def checktool(exe, name=None, abort=True):
24 name = name or exe
24 name = name or exe
25 if not util.find_exe(exe):
25 if not util.find_exe(exe):
26 exc = abort and util.Abort or MissingTool
26 exc = abort and util.Abort or MissingTool
27 raise exc(_('cannot find required "%s" tool') % name)
27 raise exc(_('cannot find required "%s" tool') % name)
28
28
29 class NoRepo(Exception): pass
29 class NoRepo(Exception): pass
30
30
31 SKIPREV = 'SKIP'
31 SKIPREV = 'SKIP'
32
32
33 class commit(object):
33 class commit(object):
34 def __init__(self, author, date, desc, parents, branch=None, rev=None,
34 def __init__(self, author, date, desc, parents, branch=None, rev=None,
35 extra={}):
35 extra={}):
36 self.author = author or 'unknown'
36 self.author = author or 'unknown'
37 self.date = date or '0 0'
37 self.date = date or '0 0'
38 self.desc = desc
38 self.desc = desc
39 self.parents = parents
39 self.parents = parents
40 self.branch = branch
40 self.branch = branch
41 self.rev = rev
41 self.rev = rev
42 self.extra = extra
42 self.extra = extra
43
43
44 class converter_source(object):
44 class converter_source(object):
45 """Conversion source interface"""
45 """Conversion source interface"""
46
46
47 def __init__(self, ui, path=None, rev=None):
47 def __init__(self, ui, path=None, rev=None):
48 """Initialize conversion source (or raise NoRepo("message")
48 """Initialize conversion source (or raise NoRepo("message")
49 exception if path is not a valid repository)"""
49 exception if path is not a valid repository)"""
50 self.ui = ui
50 self.ui = ui
51 self.path = path
51 self.path = path
52 self.rev = rev
52 self.rev = rev
53
53
54 self.encoding = 'utf-8'
54 self.encoding = 'utf-8'
55
55
56 def before(self):
56 def before(self):
57 pass
57 pass
58
58
59 def after(self):
59 def after(self):
60 pass
60 pass
61
61
62 def setrevmap(self, revmap):
62 def setrevmap(self, revmap):
63 """set the map of already-converted revisions"""
63 """set the map of already-converted revisions"""
64 pass
64 pass
65
65
66 def getheads(self):
66 def getheads(self):
67 """Return a list of this repository's heads"""
67 """Return a list of this repository's heads"""
68 raise NotImplementedError()
68 raise NotImplementedError()
69
69
70 def getfile(self, name, rev):
70 def getfile(self, name, rev):
71 """Return file contents as a string. rev is the identifier returned
71 """Return file contents as a string. rev is the identifier returned
72 by a previous call to getchanges().
72 by a previous call to getchanges().
73 """
73 """
74 raise NotImplementedError()
74 raise NotImplementedError()
75
75
76 def getmode(self, name, rev):
76 def getmode(self, name, rev):
77 """Return file mode, eg. '', 'x', or 'l'. rev is the identifier
77 """Return file mode, eg. '', 'x', or 'l'. rev is the identifier
78 returned by a previous call to getchanges().
78 returned by a previous call to getchanges().
79 """
79 """
80 raise NotImplementedError()
80 raise NotImplementedError()
81
81
82 def getchanges(self, version):
82 def getchanges(self, version):
83 """Returns a tuple of (files, copies).
83 """Returns a tuple of (files, copies).
84
84
85 files is a sorted list of (filename, id) tuples for all files
85 files is a sorted list of (filename, id) tuples for all files
86 changed between version and it's first parent returned by
86 changed between version and it's first parent returned by
87 getcommit(). id is the source revision id of the file.
87 getcommit(). id is the source revision id of the file.
88
88
89 copies is a dictionary of dest: source
89 copies is a dictionary of dest: source
90 """
90 """
91 raise NotImplementedError()
91 raise NotImplementedError()
92
92
93 def getcommit(self, version):
93 def getcommit(self, version):
94 """Return the commit object for version"""
94 """Return the commit object for version"""
95 raise NotImplementedError()
95 raise NotImplementedError()
96
96
97 def gettags(self):
97 def gettags(self):
98 """Return the tags as a dictionary of name: revision"""
98 """Return the tags as a dictionary of name: revision"""
99 raise NotImplementedError()
99 raise NotImplementedError()
100
100
101 def recode(self, s, encoding=None):
101 def recode(self, s, encoding=None):
102 if not encoding:
102 if not encoding:
103 encoding = self.encoding or 'utf-8'
103 encoding = self.encoding or 'utf-8'
104
104
105 if isinstance(s, unicode):
105 if isinstance(s, unicode):
106 return s.encode("utf-8")
106 return s.encode("utf-8")
107 try:
107 try:
108 return s.decode(encoding).encode("utf-8")
108 return s.decode(encoding).encode("utf-8")
109 except:
109 except:
110 try:
110 try:
111 return s.decode("latin-1").encode("utf-8")
111 return s.decode("latin-1").encode("utf-8")
112 except:
112 except:
113 return s.decode(encoding, "replace").encode("utf-8")
113 return s.decode(encoding, "replace").encode("utf-8")
114
114
115 def getchangedfiles(self, rev, i):
115 def getchangedfiles(self, rev, i):
116 """Return the files changed by rev compared to parent[i].
116 """Return the files changed by rev compared to parent[i].
117
117
118 i is an index selecting one of the parents of rev. The return
118 i is an index selecting one of the parents of rev. The return
119 value should be the list of files that are different in rev and
119 value should be the list of files that are different in rev and
120 this parent.
120 this parent.
121
121
122 If rev has no parents, i is None.
122 If rev has no parents, i is None.
123
123
124 This function is only needed to support --filemap
124 This function is only needed to support --filemap
125 """
125 """
126 raise NotImplementedError()
126 raise NotImplementedError()
127
127
128 def converted(self, rev, sinkrev):
128 def converted(self, rev, sinkrev):
129 '''Notify the source that a revision has been converted.'''
129 '''Notify the source that a revision has been converted.'''
130 pass
130 pass
131
131
132
132
133 class converter_sink(object):
133 class converter_sink(object):
134 """Conversion sink (target) interface"""
134 """Conversion sink (target) interface"""
135
135
136 def __init__(self, ui, path):
136 def __init__(self, ui, path):
137 """Initialize conversion sink (or raise NoRepo("message")
137 """Initialize conversion sink (or raise NoRepo("message")
138 exception if path is not a valid repository)
138 exception if path is not a valid repository)
139
139
140 created is a list of paths to remove if a fatal error occurs
140 created is a list of paths to remove if a fatal error occurs
141 later"""
141 later"""
142 self.ui = ui
142 self.ui = ui
143 self.path = path
143 self.path = path
144 self.created = []
144 self.created = []
145
145
146 def getheads(self):
146 def getheads(self):
147 """Return a list of this repository's heads"""
147 """Return a list of this repository's heads"""
148 raise NotImplementedError()
148 raise NotImplementedError()
149
149
150 def revmapfile(self):
150 def revmapfile(self):
151 """Path to a file that will contain lines
151 """Path to a file that will contain lines
152 source_rev_id sink_rev_id
152 source_rev_id sink_rev_id
153 mapping equivalent revision identifiers for each system."""
153 mapping equivalent revision identifiers for each system."""
154 raise NotImplementedError()
154 raise NotImplementedError()
155
155
156 def authorfile(self):
156 def authorfile(self):
157 """Path to a file that will contain lines
157 """Path to a file that will contain lines
158 srcauthor=dstauthor
158 srcauthor=dstauthor
159 mapping equivalent authors identifiers for each system."""
159 mapping equivalent authors identifiers for each system."""
160 return None
160 return None
161
161
162 def putcommit(self, files, copies, parents, commit, source):
162 def putcommit(self, files, copies, parents, commit, source):
163 """Create a revision with all changed files listed in 'files'
163 """Create a revision with all changed files listed in 'files'
164 and having listed parents. 'commit' is a commit object containing
164 and having listed parents. 'commit' is a commit object containing
165 at a minimum the author, date, and message for this changeset.
165 at a minimum the author, date, and message for this changeset.
166 'files' is a list of (path, version) tuples, 'copies'is a dictionary
166 'files' is a list of (path, version) tuples, 'copies'is a dictionary
167 mapping destinations to sources, and 'source' is the source repository.
167 mapping destinations to sources, and 'source' is the source repository.
168 Only getfile() and getmode() should be called on 'source'.
168 Only getfile() and getmode() should be called on 'source'.
169
169
170 Note that the sink repository is not told to update itself to
170 Note that the sink repository is not told to update itself to
171 a particular revision (or even what that revision would be)
171 a particular revision (or even what that revision would be)
172 before it receives the file data.
172 before it receives the file data.
173 """
173 """
174 raise NotImplementedError()
174 raise NotImplementedError()
175
175
176 def puttags(self, tags):
176 def puttags(self, tags):
177 """Put tags into sink.
177 """Put tags into sink.
178 tags: {tagname: sink_rev_id, ...}"""
178 tags: {tagname: sink_rev_id, ...}"""
179 raise NotImplementedError()
179 raise NotImplementedError()
180
180
181 def setbranch(self, branch, pbranches):
181 def setbranch(self, branch, pbranches):
182 """Set the current branch name. Called before the first putcommit
182 """Set the current branch name. Called before the first putcommit
183 on the branch.
183 on the branch.
184 branch: branch name for subsequent commits
184 branch: branch name for subsequent commits
185 pbranches: (converted parent revision, parent branch) tuples"""
185 pbranches: (converted parent revision, parent branch) tuples"""
186 pass
186 pass
187
187
188 def setfilemapmode(self, active):
188 def setfilemapmode(self, active):
189 """Tell the destination that we're using a filemap
189 """Tell the destination that we're using a filemap
190
190
191 Some converter_sources (svn in particular) can claim that a file
191 Some converter_sources (svn in particular) can claim that a file
192 was changed in a revision, even if there was no change. This method
192 was changed in a revision, even if there was no change. This method
193 tells the destination that we're using a filemap and that it should
193 tells the destination that we're using a filemap and that it should
194 filter empty revisions.
194 filter empty revisions.
195 """
195 """
196 pass
196 pass
197
197
198 def before(self):
198 def before(self):
199 pass
199 pass
200
200
201 def after(self):
201 def after(self):
202 pass
202 pass
203
203
204
204
205 class commandline(object):
205 class commandline(object):
206 def __init__(self, ui, command):
206 def __init__(self, ui, command):
207 self.ui = ui
207 self.ui = ui
208 self.command = command
208 self.command = command
209
209
210 def prerun(self):
210 def prerun(self):
211 pass
211 pass
212
212
213 def postrun(self):
213 def postrun(self):
214 pass
214 pass
215
215
216 def _cmdline(self, cmd, *args, **kwargs):
216 def _cmdline(self, cmd, *args, **kwargs):
217 cmdline = [self.command, cmd] + list(args)
217 cmdline = [self.command, cmd] + list(args)
218 for k, v in kwargs.iteritems():
218 for k, v in kwargs.iteritems():
219 if len(k) == 1:
219 if len(k) == 1:
220 cmdline.append('-' + k)
220 cmdline.append('-' + k)
221 else:
221 else:
222 cmdline.append('--' + k.replace('_', '-'))
222 cmdline.append('--' + k.replace('_', '-'))
223 try:
223 try:
224 if len(k) == 1:
224 if len(k) == 1:
225 cmdline.append('' + v)
225 cmdline.append('' + v)
226 else:
226 else:
227 cmdline[-1] += '=' + v
227 cmdline[-1] += '=' + v
228 except TypeError:
228 except TypeError:
229 pass
229 pass
230 cmdline = [util.shellquote(arg) for arg in cmdline]
230 cmdline = [util.shellquote(arg) for arg in cmdline]
231 cmdline += ['2>', util.nulldev, '<', util.nulldev]
231 cmdline += ['2>', util.nulldev, '<', util.nulldev]
232 cmdline = ' '.join(cmdline)
232 cmdline = ' '.join(cmdline)
233 return cmdline
233 return cmdline
234
234
235 def _run(self, cmd, *args, **kwargs):
235 def _run(self, cmd, *args, **kwargs):
236 cmdline = self._cmdline(cmd, *args, **kwargs)
236 cmdline = self._cmdline(cmd, *args, **kwargs)
237 self.ui.debug(_('running: %s\n') % (cmdline,))
237 self.ui.debug(_('running: %s\n') % (cmdline,))
238 self.prerun()
238 self.prerun()
239 try:
239 try:
240 return util.popen(cmdline)
240 return util.popen(cmdline)
241 finally:
241 finally:
242 self.postrun()
242 self.postrun()
243
243
244 def run(self, cmd, *args, **kwargs):
244 def run(self, cmd, *args, **kwargs):
245 fp = self._run(cmd, *args, **kwargs)
245 fp = self._run(cmd, *args, **kwargs)
246 output = fp.read()
246 output = fp.read()
247 self.ui.debug(output)
247 self.ui.debug(output)
248 return output, fp.close()
248 return output, fp.close()
249
249
250 def runlines(self, cmd, *args, **kwargs):
250 def runlines(self, cmd, *args, **kwargs):
251 fp = self._run(cmd, *args, **kwargs)
251 fp = self._run(cmd, *args, **kwargs)
252 output = fp.readlines()
252 output = fp.readlines()
253 self.ui.debug(''.join(output))
253 self.ui.debug(''.join(output))
254 return output, fp.close()
254 return output, fp.close()
255
255
256 def checkexit(self, status, output=''):
256 def checkexit(self, status, output=''):
257 if status:
257 if status:
258 if output:
258 if output:
259 self.ui.warn(_('%s error:\n') % self.command)
259 self.ui.warn(_('%s error:\n') % self.command)
260 self.ui.warn(output)
260 self.ui.warn(output)
261 msg = util.explain_exit(status)[0]
261 msg = util.explain_exit(status)[0]
262 raise util.Abort(_('%s %s') % (self.command, msg))
262 raise util.Abort(_('%s %s') % (self.command, msg))
263
263
264 def run0(self, cmd, *args, **kwargs):
264 def run0(self, cmd, *args, **kwargs):
265 output, status = self.run(cmd, *args, **kwargs)
265 output, status = self.run(cmd, *args, **kwargs)
266 self.checkexit(status, output)
266 self.checkexit(status, output)
267 return output
267 return output
268
268
269 def runlines0(self, cmd, *args, **kwargs):
269 def runlines0(self, cmd, *args, **kwargs):
270 output, status = self.runlines(cmd, *args, **kwargs)
270 output, status = self.runlines(cmd, *args, **kwargs)
271 self.checkexit(status, ''.join(output))
271 self.checkexit(status, ''.join(output))
272 return output
272 return output
273
273
274 def getargmax(self):
274 def getargmax(self):
275 if '_argmax' in self.__dict__:
275 if '_argmax' in self.__dict__:
276 return self._argmax
276 return self._argmax
277
277
278 # POSIX requires at least 4096 bytes for ARG_MAX
278 # POSIX requires at least 4096 bytes for ARG_MAX
279 self._argmax = 4096
279 self._argmax = 4096
280 try:
280 try:
281 self._argmax = os.sysconf("SC_ARG_MAX")
281 self._argmax = os.sysconf("SC_ARG_MAX")
282 except:
282 except:
283 pass
283 pass
284
284
285 # Windows shells impose their own limits on command line length,
285 # Windows shells impose their own limits on command line length,
286 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
286 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
287 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
287 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
288 # details about cmd.exe limitations.
288 # details about cmd.exe limitations.
289
289
290 # Since ARG_MAX is for command line _and_ environment, lower our limit
290 # Since ARG_MAX is for command line _and_ environment, lower our limit
291 # (and make happy Windows shells while doing this).
291 # (and make happy Windows shells while doing this).
292
292
293 self._argmax = self._argmax/2 - 1
293 self._argmax = self._argmax/2 - 1
294 return self._argmax
294 return self._argmax
295
295
296 def limit_arglist(self, arglist, cmd, *args, **kwargs):
296 def limit_arglist(self, arglist, cmd, *args, **kwargs):
297 limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs))
297 limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs))
298 bytes = 0
298 bytes = 0
299 fl = []
299 fl = []
300 for fn in arglist:
300 for fn in arglist:
301 b = len(fn) + 3
301 b = len(fn) + 3
302 if bytes + b < limit or len(fl) == 0:
302 if bytes + b < limit or len(fl) == 0:
303 fl.append(fn)
303 fl.append(fn)
304 bytes += b
304 bytes += b
305 else:
305 else:
306 yield fl
306 yield fl
307 fl = [fn]
307 fl = [fn]
308 bytes = b
308 bytes = b
309 if fl:
309 if fl:
310 yield fl
310 yield fl
311
311
312 def xargs(self, arglist, cmd, *args, **kwargs):
312 def xargs(self, arglist, cmd, *args, **kwargs):
313 for l in self.limit_arglist(arglist, cmd, *args, **kwargs):
313 for l in self.limit_arglist(arglist, cmd, *args, **kwargs):
314 self.run0(cmd, *(list(args) + l), **kwargs)
314 self.run0(cmd, *(list(args) + l), **kwargs)
315
315
316 class mapfile(dict):
316 class mapfile(dict):
317 def __init__(self, ui, path):
317 def __init__(self, ui, path):
318 super(mapfile, self).__init__()
318 super(mapfile, self).__init__()
319 self.ui = ui
319 self.ui = ui
320 self.path = path
320 self.path = path
321 self.fp = None
321 self.fp = None
322 self.order = []
322 self.order = []
323 self._read()
323 self._read()
324
324
325 def _read(self):
325 def _read(self):
326 if self.path is None:
326 if self.path is None:
327 return
327 return
328 try:
328 try:
329 fp = open(self.path, 'r')
329 fp = open(self.path, 'r')
330 except IOError, err:
330 except IOError, err:
331 if err.errno != errno.ENOENT:
331 if err.errno != errno.ENOENT:
332 raise
332 raise
333 return
333 return
334 for line in fp:
334 for line in fp:
335 key, value = strutil.rsplit(line[:-1], ' ', 1)
335 key, value = strutil.rsplit(line[:-1], ' ', 1)
336 if key not in self:
336 if key not in self:
337 self.order.append(key)
337 self.order.append(key)
338 super(mapfile, self).__setitem__(key, value)
338 super(mapfile, self).__setitem__(key, value)
339 fp.close()
339 fp.close()
340
340
341 def __setitem__(self, key, value):
341 def __setitem__(self, key, value):
342 if self.fp is None:
342 if self.fp is None:
343 try:
343 try:
344 self.fp = open(self.path, 'a')
344 self.fp = open(self.path, 'a')
345 except IOError, err:
345 except IOError, err:
346 raise util.Abort(_('could not open map file %r: %s') %
346 raise util.Abort(_('could not open map file %r: %s') %
347 (self.path, err.strerror))
347 (self.path, err.strerror))
348 self.fp.write('%s %s\n' % (key, value))
348 self.fp.write('%s %s\n' % (key, value))
349 self.fp.flush()
349 self.fp.flush()
350 super(mapfile, self).__setitem__(key, value)
350 super(mapfile, self).__setitem__(key, value)
351
351
352 def close(self):
352 def close(self):
353 if self.fp:
353 if self.fp:
354 self.fp.close()
354 self.fp.close()
355 self.fp = None
355 self.fp = None
@@ -1,2516 +1,2516 b''
1 # mq.py - patch queues for mercurial
1 # mq.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 '''patch management and development
8 '''patch management and development
9
9
10 This extension lets you work with a stack of patches in a Mercurial
10 This extension lets you work with a stack of patches in a Mercurial
11 repository. It manages two stacks of patches - all known patches, and
11 repository. It manages two stacks of patches - all known patches, and
12 applied patches (subset of known patches).
12 applied patches (subset of known patches).
13
13
14 Known patches are represented as patch files in the .hg/patches
14 Known patches are represented as patch files in the .hg/patches
15 directory. Applied patches are both patch files and changesets.
15 directory. Applied patches are both patch files and changesets.
16
16
17 Common tasks (use "hg help command" for more details):
17 Common tasks (use "hg help command" for more details):
18
18
19 prepare repository to work with patches qinit
19 prepare repository to work with patches qinit
20 create new patch qnew
20 create new patch qnew
21 import existing patch qimport
21 import existing patch qimport
22
22
23 print patch series qseries
23 print patch series qseries
24 print applied patches qapplied
24 print applied patches qapplied
25 print name of top applied patch qtop
25 print name of top applied patch qtop
26
26
27 add known patch to applied stack qpush
27 add known patch to applied stack qpush
28 remove patch from applied stack qpop
28 remove patch from applied stack qpop
29 refresh contents of top applied patch qrefresh
29 refresh contents of top applied patch qrefresh
30 '''
30 '''
31
31
32 from mercurial.i18n import _
32 from mercurial.i18n import _
33 from mercurial.node import bin, hex, short
33 from mercurial.node import bin, hex, short
34 from mercurial.repo import RepoError
34 from mercurial.repo import RepoError
35 from mercurial import commands, cmdutil, hg, patch, revlog, util
35 from mercurial import commands, cmdutil, hg, patch, revlog, util
36 from mercurial import repair
36 from mercurial import repair
37 import os, sys, re, errno, urllib
37 import os, sys, re, errno, urllib
38
38
39 commands.norepo += " qclone"
39 commands.norepo += " qclone"
40
40
41 # Patch names looks like unix-file names.
41 # Patch names looks like unix-file names.
42 # They must be joinable with queue directory and result in the patch path.
42 # They must be joinable with queue directory and result in the patch path.
43 normname = util.normpath
43 normname = util.normpath
44
44
45 class statusentry:
45 class statusentry:
46 def __init__(self, rev, name=None):
46 def __init__(self, rev, name=None):
47 if not name:
47 if not name:
48 fields = rev.split(':', 1)
48 fields = rev.split(':', 1)
49 if len(fields) == 2:
49 if len(fields) == 2:
50 self.rev, self.name = fields
50 self.rev, self.name = fields
51 else:
51 else:
52 self.rev, self.name = None, None
52 self.rev, self.name = None, None
53 else:
53 else:
54 self.rev, self.name = rev, name
54 self.rev, self.name = rev, name
55
55
56 def __str__(self):
56 def __str__(self):
57 return self.rev + ':' + self.name
57 return self.rev + ':' + self.name
58
58
59 class queue:
59 class queue:
60 def __init__(self, ui, path, patchdir=None):
60 def __init__(self, ui, path, patchdir=None):
61 self.basepath = path
61 self.basepath = path
62 self.path = patchdir or os.path.join(path, "patches")
62 self.path = patchdir or os.path.join(path, "patches")
63 self.opener = util.opener(self.path)
63 self.opener = util.opener(self.path)
64 self.ui = ui
64 self.ui = ui
65 self.applied = []
65 self.applied = []
66 self.full_series = []
66 self.full_series = []
67 self.applied_dirty = 0
67 self.applied_dirty = 0
68 self.series_dirty = 0
68 self.series_dirty = 0
69 self.series_path = "series"
69 self.series_path = "series"
70 self.status_path = "status"
70 self.status_path = "status"
71 self.guards_path = "guards"
71 self.guards_path = "guards"
72 self.active_guards = None
72 self.active_guards = None
73 self.guards_dirty = False
73 self.guards_dirty = False
74 self._diffopts = None
74 self._diffopts = None
75
75
76 if os.path.exists(self.join(self.series_path)):
76 if os.path.exists(self.join(self.series_path)):
77 self.full_series = self.opener(self.series_path).read().splitlines()
77 self.full_series = self.opener(self.series_path).read().splitlines()
78 self.parse_series()
78 self.parse_series()
79
79
80 if os.path.exists(self.join(self.status_path)):
80 if os.path.exists(self.join(self.status_path)):
81 lines = self.opener(self.status_path).read().splitlines()
81 lines = self.opener(self.status_path).read().splitlines()
82 self.applied = [statusentry(l) for l in lines]
82 self.applied = [statusentry(l) for l in lines]
83
83
84 def diffopts(self):
84 def diffopts(self):
85 if self._diffopts is None:
85 if self._diffopts is None:
86 self._diffopts = patch.diffopts(self.ui)
86 self._diffopts = patch.diffopts(self.ui)
87 return self._diffopts
87 return self._diffopts
88
88
89 def join(self, *p):
89 def join(self, *p):
90 return os.path.join(self.path, *p)
90 return os.path.join(self.path, *p)
91
91
92 def find_series(self, patch):
92 def find_series(self, patch):
93 pre = re.compile("(\s*)([^#]+)")
93 pre = re.compile("(\s*)([^#]+)")
94 index = 0
94 index = 0
95 for l in self.full_series:
95 for l in self.full_series:
96 m = pre.match(l)
96 m = pre.match(l)
97 if m:
97 if m:
98 s = m.group(2)
98 s = m.group(2)
99 s = s.rstrip()
99 s = s.rstrip()
100 if s == patch:
100 if s == patch:
101 return index
101 return index
102 index += 1
102 index += 1
103 return None
103 return None
104
104
105 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
105 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
106
106
107 def parse_series(self):
107 def parse_series(self):
108 self.series = []
108 self.series = []
109 self.series_guards = []
109 self.series_guards = []
110 for l in self.full_series:
110 for l in self.full_series:
111 h = l.find('#')
111 h = l.find('#')
112 if h == -1:
112 if h == -1:
113 patch = l
113 patch = l
114 comment = ''
114 comment = ''
115 elif h == 0:
115 elif h == 0:
116 continue
116 continue
117 else:
117 else:
118 patch = l[:h]
118 patch = l[:h]
119 comment = l[h:]
119 comment = l[h:]
120 patch = patch.strip()
120 patch = patch.strip()
121 if patch:
121 if patch:
122 if patch in self.series:
122 if patch in self.series:
123 raise util.Abort(_('%s appears more than once in %s') %
123 raise util.Abort(_('%s appears more than once in %s') %
124 (patch, self.join(self.series_path)))
124 (patch, self.join(self.series_path)))
125 self.series.append(patch)
125 self.series.append(patch)
126 self.series_guards.append(self.guard_re.findall(comment))
126 self.series_guards.append(self.guard_re.findall(comment))
127
127
128 def check_guard(self, guard):
128 def check_guard(self, guard):
129 if not guard:
129 if not guard:
130 return _('guard cannot be an empty string')
130 return _('guard cannot be an empty string')
131 bad_chars = '# \t\r\n\f'
131 bad_chars = '# \t\r\n\f'
132 first = guard[0]
132 first = guard[0]
133 for c in '-+':
133 for c in '-+':
134 if first == c:
134 if first == c:
135 return (_('guard %r starts with invalid character: %r') %
135 return (_('guard %r starts with invalid character: %r') %
136 (guard, c))
136 (guard, c))
137 for c in bad_chars:
137 for c in bad_chars:
138 if c in guard:
138 if c in guard:
139 return _('invalid character in guard %r: %r') % (guard, c)
139 return _('invalid character in guard %r: %r') % (guard, c)
140
140
141 def set_active(self, guards):
141 def set_active(self, guards):
142 for guard in guards:
142 for guard in guards:
143 bad = self.check_guard(guard)
143 bad = self.check_guard(guard)
144 if bad:
144 if bad:
145 raise util.Abort(bad)
145 raise util.Abort(bad)
146 guards = util.sort(util.unique(guards))
146 guards = util.sort(util.unique(guards))
147 self.ui.debug(_('active guards: %s\n') % ' '.join(guards))
147 self.ui.debug(_('active guards: %s\n') % ' '.join(guards))
148 self.active_guards = guards
148 self.active_guards = guards
149 self.guards_dirty = True
149 self.guards_dirty = True
150
150
151 def active(self):
151 def active(self):
152 if self.active_guards is None:
152 if self.active_guards is None:
153 self.active_guards = []
153 self.active_guards = []
154 try:
154 try:
155 guards = self.opener(self.guards_path).read().split()
155 guards = self.opener(self.guards_path).read().split()
156 except IOError, err:
156 except IOError, err:
157 if err.errno != errno.ENOENT: raise
157 if err.errno != errno.ENOENT: raise
158 guards = []
158 guards = []
159 for i, guard in enumerate(guards):
159 for i, guard in enumerate(guards):
160 bad = self.check_guard(guard)
160 bad = self.check_guard(guard)
161 if bad:
161 if bad:
162 self.ui.warn('%s:%d: %s\n' %
162 self.ui.warn('%s:%d: %s\n' %
163 (self.join(self.guards_path), i + 1, bad))
163 (self.join(self.guards_path), i + 1, bad))
164 else:
164 else:
165 self.active_guards.append(guard)
165 self.active_guards.append(guard)
166 return self.active_guards
166 return self.active_guards
167
167
168 def set_guards(self, idx, guards):
168 def set_guards(self, idx, guards):
169 for g in guards:
169 for g in guards:
170 if len(g) < 2:
170 if len(g) < 2:
171 raise util.Abort(_('guard %r too short') % g)
171 raise util.Abort(_('guard %r too short') % g)
172 if g[0] not in '-+':
172 if g[0] not in '-+':
173 raise util.Abort(_('guard %r starts with invalid char') % g)
173 raise util.Abort(_('guard %r starts with invalid char') % g)
174 bad = self.check_guard(g[1:])
174 bad = self.check_guard(g[1:])
175 if bad:
175 if bad:
176 raise util.Abort(bad)
176 raise util.Abort(bad)
177 drop = self.guard_re.sub('', self.full_series[idx])
177 drop = self.guard_re.sub('', self.full_series[idx])
178 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
178 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
179 self.parse_series()
179 self.parse_series()
180 self.series_dirty = True
180 self.series_dirty = True
181
181
182 def pushable(self, idx):
182 def pushable(self, idx):
183 if isinstance(idx, str):
183 if isinstance(idx, str):
184 idx = self.series.index(idx)
184 idx = self.series.index(idx)
185 patchguards = self.series_guards[idx]
185 patchguards = self.series_guards[idx]
186 if not patchguards:
186 if not patchguards:
187 return True, None
187 return True, None
188 default = False
188 default = False
189 guards = self.active()
189 guards = self.active()
190 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
190 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
191 if exactneg:
191 if exactneg:
192 return False, exactneg[0]
192 return False, exactneg[0]
193 pos = [g for g in patchguards if g[0] == '+']
193 pos = [g for g in patchguards if g[0] == '+']
194 exactpos = [g for g in pos if g[1:] in guards]
194 exactpos = [g for g in pos if g[1:] in guards]
195 if pos:
195 if pos:
196 if exactpos:
196 if exactpos:
197 return True, exactpos[0]
197 return True, exactpos[0]
198 return False, pos
198 return False, pos
199 return True, ''
199 return True, ''
200
200
201 def explain_pushable(self, idx, all_patches=False):
201 def explain_pushable(self, idx, all_patches=False):
202 write = all_patches and self.ui.write or self.ui.warn
202 write = all_patches and self.ui.write or self.ui.warn
203 if all_patches or self.ui.verbose:
203 if all_patches or self.ui.verbose:
204 if isinstance(idx, str):
204 if isinstance(idx, str):
205 idx = self.series.index(idx)
205 idx = self.series.index(idx)
206 pushable, why = self.pushable(idx)
206 pushable, why = self.pushable(idx)
207 if all_patches and pushable:
207 if all_patches and pushable:
208 if why is None:
208 if why is None:
209 write(_('allowing %s - no guards in effect\n') %
209 write(_('allowing %s - no guards in effect\n') %
210 self.series[idx])
210 self.series[idx])
211 else:
211 else:
212 if not why:
212 if not why:
213 write(_('allowing %s - no matching negative guards\n') %
213 write(_('allowing %s - no matching negative guards\n') %
214 self.series[idx])
214 self.series[idx])
215 else:
215 else:
216 write(_('allowing %s - guarded by %r\n') %
216 write(_('allowing %s - guarded by %r\n') %
217 (self.series[idx], why))
217 (self.series[idx], why))
218 if not pushable:
218 if not pushable:
219 if why:
219 if why:
220 write(_('skipping %s - guarded by %r\n') %
220 write(_('skipping %s - guarded by %r\n') %
221 (self.series[idx], why))
221 (self.series[idx], why))
222 else:
222 else:
223 write(_('skipping %s - no matching guards\n') %
223 write(_('skipping %s - no matching guards\n') %
224 self.series[idx])
224 self.series[idx])
225
225
226 def save_dirty(self):
226 def save_dirty(self):
227 def write_list(items, path):
227 def write_list(items, path):
228 fp = self.opener(path, 'w')
228 fp = self.opener(path, 'w')
229 for i in items:
229 for i in items:
230 fp.write("%s\n" % i)
230 fp.write("%s\n" % i)
231 fp.close()
231 fp.close()
232 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
232 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
233 if self.series_dirty: write_list(self.full_series, self.series_path)
233 if self.series_dirty: write_list(self.full_series, self.series_path)
234 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
234 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
235
235
236 def readheaders(self, patch):
236 def readheaders(self, patch):
237 def eatdiff(lines):
237 def eatdiff(lines):
238 while lines:
238 while lines:
239 l = lines[-1]
239 l = lines[-1]
240 if (l.startswith("diff -") or
240 if (l.startswith("diff -") or
241 l.startswith("Index:") or
241 l.startswith("Index:") or
242 l.startswith("===========")):
242 l.startswith("===========")):
243 del lines[-1]
243 del lines[-1]
244 else:
244 else:
245 break
245 break
246 def eatempty(lines):
246 def eatempty(lines):
247 while lines:
247 while lines:
248 l = lines[-1]
248 l = lines[-1]
249 if re.match('\s*$', l):
249 if re.match('\s*$', l):
250 del lines[-1]
250 del lines[-1]
251 else:
251 else:
252 break
252 break
253
253
254 pf = self.join(patch)
254 pf = self.join(patch)
255 message = []
255 message = []
256 comments = []
256 comments = []
257 user = None
257 user = None
258 date = None
258 date = None
259 format = None
259 format = None
260 subject = None
260 subject = None
261 diffstart = 0
261 diffstart = 0
262
262
263 for line in file(pf):
263 for line in file(pf):
264 line = line.rstrip()
264 line = line.rstrip()
265 if line.startswith('diff --git'):
265 if line.startswith('diff --git'):
266 diffstart = 2
266 diffstart = 2
267 break
267 break
268 if diffstart:
268 if diffstart:
269 if line.startswith('+++ '):
269 if line.startswith('+++ '):
270 diffstart = 2
270 diffstart = 2
271 break
271 break
272 if line.startswith("--- "):
272 if line.startswith("--- "):
273 diffstart = 1
273 diffstart = 1
274 continue
274 continue
275 elif format == "hgpatch":
275 elif format == "hgpatch":
276 # parse values when importing the result of an hg export
276 # parse values when importing the result of an hg export
277 if line.startswith("# User "):
277 if line.startswith("# User "):
278 user = line[7:]
278 user = line[7:]
279 elif line.startswith("# Date "):
279 elif line.startswith("# Date "):
280 date = line[7:]
280 date = line[7:]
281 elif not line.startswith("# ") and line:
281 elif not line.startswith("# ") and line:
282 message.append(line)
282 message.append(line)
283 format = None
283 format = None
284 elif line == '# HG changeset patch':
284 elif line == '# HG changeset patch':
285 format = "hgpatch"
285 format = "hgpatch"
286 elif (format != "tagdone" and (line.startswith("Subject: ") or
286 elif (format != "tagdone" and (line.startswith("Subject: ") or
287 line.startswith("subject: "))):
287 line.startswith("subject: "))):
288 subject = line[9:]
288 subject = line[9:]
289 format = "tag"
289 format = "tag"
290 elif (format != "tagdone" and (line.startswith("From: ") or
290 elif (format != "tagdone" and (line.startswith("From: ") or
291 line.startswith("from: "))):
291 line.startswith("from: "))):
292 user = line[6:]
292 user = line[6:]
293 format = "tag"
293 format = "tag"
294 elif format == "tag" and line == "":
294 elif format == "tag" and line == "":
295 # when looking for tags (subject: from: etc) they
295 # when looking for tags (subject: from: etc) they
296 # end once you find a blank line in the source
296 # end once you find a blank line in the source
297 format = "tagdone"
297 format = "tagdone"
298 elif message or line:
298 elif message or line:
299 message.append(line)
299 message.append(line)
300 comments.append(line)
300 comments.append(line)
301
301
302 eatdiff(message)
302 eatdiff(message)
303 eatdiff(comments)
303 eatdiff(comments)
304 eatempty(message)
304 eatempty(message)
305 eatempty(comments)
305 eatempty(comments)
306
306
307 # make sure message isn't empty
307 # make sure message isn't empty
308 if format and format.startswith("tag") and subject:
308 if format and format.startswith("tag") and subject:
309 message.insert(0, "")
309 message.insert(0, "")
310 message.insert(0, subject)
310 message.insert(0, subject)
311 return (message, comments, user, date, diffstart > 1)
311 return (message, comments, user, date, diffstart > 1)
312
312
313 def removeundo(self, repo):
313 def removeundo(self, repo):
314 undo = repo.sjoin('undo')
314 undo = repo.sjoin('undo')
315 if not os.path.exists(undo):
315 if not os.path.exists(undo):
316 return
316 return
317 try:
317 try:
318 os.unlink(undo)
318 os.unlink(undo)
319 except OSError, inst:
319 except OSError, inst:
320 self.ui.warn(_('error removing undo: %s\n') % str(inst))
320 self.ui.warn(_('error removing undo: %s\n') % str(inst))
321
321
322 def printdiff(self, repo, node1, node2=None, files=None,
322 def printdiff(self, repo, node1, node2=None, files=None,
323 fp=None, changes=None, opts={}):
323 fp=None, changes=None, opts={}):
324 m = cmdutil.match(repo, files, opts)
324 m = cmdutil.match(repo, files, opts)
325 patch.diff(repo, node1, node2, m, fp, changes, self.diffopts())
325 patch.diff(repo, node1, node2, m, fp, changes, self.diffopts())
326
326
327 def mergeone(self, repo, mergeq, head, patch, rev):
327 def mergeone(self, repo, mergeq, head, patch, rev):
328 # first try just applying the patch
328 # first try just applying the patch
329 (err, n) = self.apply(repo, [ patch ], update_status=False,
329 (err, n) = self.apply(repo, [ patch ], update_status=False,
330 strict=True, merge=rev)
330 strict=True, merge=rev)
331
331
332 if err == 0:
332 if err == 0:
333 return (err, n)
333 return (err, n)
334
334
335 if n is None:
335 if n is None:
336 raise util.Abort(_("apply failed for patch %s") % patch)
336 raise util.Abort(_("apply failed for patch %s") % patch)
337
337
338 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
338 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
339
339
340 # apply failed, strip away that rev and merge.
340 # apply failed, strip away that rev and merge.
341 hg.clean(repo, head)
341 hg.clean(repo, head)
342 self.strip(repo, n, update=False, backup='strip')
342 self.strip(repo, n, update=False, backup='strip')
343
343
344 ctx = repo[rev]
344 ctx = repo[rev]
345 ret = hg.merge(repo, rev)
345 ret = hg.merge(repo, rev)
346 if ret:
346 if ret:
347 raise util.Abort(_("update returned %d") % ret)
347 raise util.Abort(_("update returned %d") % ret)
348 n = repo.commit(None, ctx.description(), ctx.user(), force=1)
348 n = repo.commit(None, ctx.description(), ctx.user(), force=1)
349 if n == None:
349 if n == None:
350 raise util.Abort(_("repo commit failed"))
350 raise util.Abort(_("repo commit failed"))
351 try:
351 try:
352 message, comments, user, date, patchfound = mergeq.readheaders(patch)
352 message, comments, user, date, patchfound = mergeq.readheaders(patch)
353 except:
353 except:
354 raise util.Abort(_("unable to read %s") % patch)
354 raise util.Abort(_("unable to read %s") % patch)
355
355
356 patchf = self.opener(patch, "w")
356 patchf = self.opener(patch, "w")
357 if comments:
357 if comments:
358 comments = "\n".join(comments) + '\n\n'
358 comments = "\n".join(comments) + '\n\n'
359 patchf.write(comments)
359 patchf.write(comments)
360 self.printdiff(repo, head, n, fp=patchf)
360 self.printdiff(repo, head, n, fp=patchf)
361 patchf.close()
361 patchf.close()
362 self.removeundo(repo)
362 self.removeundo(repo)
363 return (0, n)
363 return (0, n)
364
364
365 def qparents(self, repo, rev=None):
365 def qparents(self, repo, rev=None):
366 if rev is None:
366 if rev is None:
367 (p1, p2) = repo.dirstate.parents()
367 (p1, p2) = repo.dirstate.parents()
368 if p2 == revlog.nullid:
368 if p2 == revlog.nullid:
369 return p1
369 return p1
370 if len(self.applied) == 0:
370 if len(self.applied) == 0:
371 return None
371 return None
372 return revlog.bin(self.applied[-1].rev)
372 return revlog.bin(self.applied[-1].rev)
373 pp = repo.changelog.parents(rev)
373 pp = repo.changelog.parents(rev)
374 if pp[1] != revlog.nullid:
374 if pp[1] != revlog.nullid:
375 arevs = [ x.rev for x in self.applied ]
375 arevs = [ x.rev for x in self.applied ]
376 p0 = revlog.hex(pp[0])
376 p0 = revlog.hex(pp[0])
377 p1 = revlog.hex(pp[1])
377 p1 = revlog.hex(pp[1])
378 if p0 in arevs:
378 if p0 in arevs:
379 return pp[0]
379 return pp[0]
380 if p1 in arevs:
380 if p1 in arevs:
381 return pp[1]
381 return pp[1]
382 return pp[0]
382 return pp[0]
383
383
384 def mergepatch(self, repo, mergeq, series):
384 def mergepatch(self, repo, mergeq, series):
385 if len(self.applied) == 0:
385 if len(self.applied) == 0:
386 # each of the patches merged in will have two parents. This
386 # each of the patches merged in will have two parents. This
387 # can confuse the qrefresh, qdiff, and strip code because it
387 # can confuse the qrefresh, qdiff, and strip code because it
388 # needs to know which parent is actually in the patch queue.
388 # needs to know which parent is actually in the patch queue.
389 # so, we insert a merge marker with only one parent. This way
389 # so, we insert a merge marker with only one parent. This way
390 # the first patch in the queue is never a merge patch
390 # the first patch in the queue is never a merge patch
391 #
391 #
392 pname = ".hg.patches.merge.marker"
392 pname = ".hg.patches.merge.marker"
393 n = repo.commit(None, '[mq]: merge marker', user=None, force=1)
393 n = repo.commit(None, '[mq]: merge marker', user=None, force=1)
394 self.removeundo(repo)
394 self.removeundo(repo)
395 self.applied.append(statusentry(revlog.hex(n), pname))
395 self.applied.append(statusentry(revlog.hex(n), pname))
396 self.applied_dirty = 1
396 self.applied_dirty = 1
397
397
398 head = self.qparents(repo)
398 head = self.qparents(repo)
399
399
400 for patch in series:
400 for patch in series:
401 patch = mergeq.lookup(patch, strict=True)
401 patch = mergeq.lookup(patch, strict=True)
402 if not patch:
402 if not patch:
403 self.ui.warn(_("patch %s does not exist\n") % patch)
403 self.ui.warn(_("patch %s does not exist\n") % patch)
404 return (1, None)
404 return (1, None)
405 pushable, reason = self.pushable(patch)
405 pushable, reason = self.pushable(patch)
406 if not pushable:
406 if not pushable:
407 self.explain_pushable(patch, all_patches=True)
407 self.explain_pushable(patch, all_patches=True)
408 continue
408 continue
409 info = mergeq.isapplied(patch)
409 info = mergeq.isapplied(patch)
410 if not info:
410 if not info:
411 self.ui.warn(_("patch %s is not applied\n") % patch)
411 self.ui.warn(_("patch %s is not applied\n") % patch)
412 return (1, None)
412 return (1, None)
413 rev = revlog.bin(info[1])
413 rev = revlog.bin(info[1])
414 (err, head) = self.mergeone(repo, mergeq, head, patch, rev)
414 (err, head) = self.mergeone(repo, mergeq, head, patch, rev)
415 if head:
415 if head:
416 self.applied.append(statusentry(revlog.hex(head), patch))
416 self.applied.append(statusentry(revlog.hex(head), patch))
417 self.applied_dirty = 1
417 self.applied_dirty = 1
418 if err:
418 if err:
419 return (err, head)
419 return (err, head)
420 self.save_dirty()
420 self.save_dirty()
421 return (0, head)
421 return (0, head)
422
422
423 def patch(self, repo, patchfile):
423 def patch(self, repo, patchfile):
424 '''Apply patchfile to the working directory.
424 '''Apply patchfile to the working directory.
425 patchfile: file name of patch'''
425 patchfile: file name of patch'''
426 files = {}
426 files = {}
427 try:
427 try:
428 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
428 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
429 files=files)
429 files=files)
430 except Exception, inst:
430 except Exception, inst:
431 self.ui.note(str(inst) + '\n')
431 self.ui.note(str(inst) + '\n')
432 if not self.ui.verbose:
432 if not self.ui.verbose:
433 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
433 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
434 return (False, files, False)
434 return (False, files, False)
435
435
436 return (True, files, fuzz)
436 return (True, files, fuzz)
437
437
438 def apply(self, repo, series, list=False, update_status=True,
438 def apply(self, repo, series, list=False, update_status=True,
439 strict=False, patchdir=None, merge=None, all_files={}):
439 strict=False, patchdir=None, merge=None, all_files={}):
440 wlock = lock = tr = None
440 wlock = lock = tr = None
441 try:
441 try:
442 wlock = repo.wlock()
442 wlock = repo.wlock()
443 lock = repo.lock()
443 lock = repo.lock()
444 tr = repo.transaction()
444 tr = repo.transaction()
445 try:
445 try:
446 ret = self._apply(repo, series, list, update_status,
446 ret = self._apply(repo, series, list, update_status,
447 strict, patchdir, merge, all_files=all_files)
447 strict, patchdir, merge, all_files=all_files)
448 tr.close()
448 tr.close()
449 self.save_dirty()
449 self.save_dirty()
450 return ret
450 return ret
451 except:
451 except:
452 try:
452 try:
453 tr.abort()
453 tr.abort()
454 finally:
454 finally:
455 repo.invalidate()
455 repo.invalidate()
456 repo.dirstate.invalidate()
456 repo.dirstate.invalidate()
457 raise
457 raise
458 finally:
458 finally:
459 del tr, lock, wlock
459 del tr, lock, wlock
460 self.removeundo(repo)
460 self.removeundo(repo)
461
461
462 def _apply(self, repo, series, list=False, update_status=True,
462 def _apply(self, repo, series, list=False, update_status=True,
463 strict=False, patchdir=None, merge=None, all_files={}):
463 strict=False, patchdir=None, merge=None, all_files={}):
464 # TODO unify with commands.py
464 # TODO unify with commands.py
465 if not patchdir:
465 if not patchdir:
466 patchdir = self.path
466 patchdir = self.path
467 err = 0
467 err = 0
468 n = None
468 n = None
469 for patchname in series:
469 for patchname in series:
470 pushable, reason = self.pushable(patchname)
470 pushable, reason = self.pushable(patchname)
471 if not pushable:
471 if not pushable:
472 self.explain_pushable(patchname, all_patches=True)
472 self.explain_pushable(patchname, all_patches=True)
473 continue
473 continue
474 self.ui.warn(_("applying %s\n") % patchname)
474 self.ui.warn(_("applying %s\n") % patchname)
475 pf = os.path.join(patchdir, patchname)
475 pf = os.path.join(patchdir, patchname)
476
476
477 try:
477 try:
478 message, comments, user, date, patchfound = self.readheaders(patchname)
478 message, comments, user, date, patchfound = self.readheaders(patchname)
479 except:
479 except:
480 self.ui.warn(_("Unable to read %s\n") % patchname)
480 self.ui.warn(_("Unable to read %s\n") % patchname)
481 err = 1
481 err = 1
482 break
482 break
483
483
484 if not message:
484 if not message:
485 message = _("imported patch %s\n") % patchname
485 message = _("imported patch %s\n") % patchname
486 else:
486 else:
487 if list:
487 if list:
488 message.append(_("\nimported patch %s") % patchname)
488 message.append(_("\nimported patch %s") % patchname)
489 message = '\n'.join(message)
489 message = '\n'.join(message)
490
490
491 (patcherr, files, fuzz) = self.patch(repo, pf)
491 (patcherr, files, fuzz) = self.patch(repo, pf)
492 all_files.update(files)
492 all_files.update(files)
493 patcherr = not patcherr
493 patcherr = not patcherr
494
494
495 if merge and files:
495 if merge and files:
496 # Mark as removed/merged and update dirstate parent info
496 # Mark as removed/merged and update dirstate parent info
497 removed = []
497 removed = []
498 merged = []
498 merged = []
499 for f in files:
499 for f in files:
500 if os.path.exists(repo.wjoin(f)):
500 if os.path.exists(repo.wjoin(f)):
501 merged.append(f)
501 merged.append(f)
502 else:
502 else:
503 removed.append(f)
503 removed.append(f)
504 for f in removed:
504 for f in removed:
505 repo.dirstate.remove(f)
505 repo.dirstate.remove(f)
506 for f in merged:
506 for f in merged:
507 repo.dirstate.merge(f)
507 repo.dirstate.merge(f)
508 p1, p2 = repo.dirstate.parents()
508 p1, p2 = repo.dirstate.parents()
509 repo.dirstate.setparents(p1, merge)
509 repo.dirstate.setparents(p1, merge)
510
510
511 files = patch.updatedir(self.ui, repo, files)
511 files = patch.updatedir(self.ui, repo, files)
512 match = cmdutil.matchfiles(repo, files or [])
512 match = cmdutil.matchfiles(repo, files or [])
513 n = repo.commit(files, message, user, date, match=match,
513 n = repo.commit(files, message, user, date, match=match,
514 force=True)
514 force=True)
515
515
516 if n == None:
516 if n == None:
517 raise util.Abort(_("repo commit failed"))
517 raise util.Abort(_("repo commit failed"))
518
518
519 if update_status:
519 if update_status:
520 self.applied.append(statusentry(revlog.hex(n), patchname))
520 self.applied.append(statusentry(revlog.hex(n), patchname))
521
521
522 if patcherr:
522 if patcherr:
523 if not patchfound:
523 if not patchfound:
524 self.ui.warn(_("patch %s is empty\n") % patchname)
524 self.ui.warn(_("patch %s is empty\n") % patchname)
525 err = 0
525 err = 0
526 else:
526 else:
527 self.ui.warn(_("patch failed, rejects left in working dir\n"))
527 self.ui.warn(_("patch failed, rejects left in working dir\n"))
528 err = 1
528 err = 1
529 break
529 break
530
530
531 if fuzz and strict:
531 if fuzz and strict:
532 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
532 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
533 err = 1
533 err = 1
534 break
534 break
535 return (err, n)
535 return (err, n)
536
536
537 def _clean_series(self, patches):
537 def _clean_series(self, patches):
538 indices = util.sort([self.find_series(p) for p in patches])
538 indices = util.sort([self.find_series(p) for p in patches])
539 for i in indices[-1::-1]:
539 for i in indices[-1::-1]:
540 del self.full_series[i]
540 del self.full_series[i]
541 self.parse_series()
541 self.parse_series()
542 self.series_dirty = 1
542 self.series_dirty = 1
543
543
544 def finish(self, repo, revs):
544 def finish(self, repo, revs):
545 revs.sort()
545 revs.sort()
546 firstrev = repo[self.applied[0].rev].rev()
546 firstrev = repo[self.applied[0].rev].rev()
547 appliedbase = 0
547 appliedbase = 0
548 patches = []
548 patches = []
549 for rev in util.sort(revs):
549 for rev in util.sort(revs):
550 if rev < firstrev:
550 if rev < firstrev:
551 raise util.Abort(_('revision %d is not managed') % rev)
551 raise util.Abort(_('revision %d is not managed') % rev)
552 base = revlog.bin(self.applied[appliedbase].rev)
552 base = revlog.bin(self.applied[appliedbase].rev)
553 node = repo.changelog.node(rev)
553 node = repo.changelog.node(rev)
554 if node != base:
554 if node != base:
555 raise util.Abort(_('cannot delete revision %d above '
555 raise util.Abort(_('cannot delete revision %d above '
556 'applied patches') % rev)
556 'applied patches') % rev)
557 patches.append(self.applied[appliedbase].name)
557 patches.append(self.applied[appliedbase].name)
558 appliedbase += 1
558 appliedbase += 1
559
559
560 r = self.qrepo()
560 r = self.qrepo()
561 if r:
561 if r:
562 r.remove(patches, True)
562 r.remove(patches, True)
563 else:
563 else:
564 for p in patches:
564 for p in patches:
565 os.unlink(self.join(p))
565 os.unlink(self.join(p))
566
566
567 del self.applied[:appliedbase]
567 del self.applied[:appliedbase]
568 self.applied_dirty = 1
568 self.applied_dirty = 1
569 self._clean_series(patches)
569 self._clean_series(patches)
570
570
571 def delete(self, repo, patches, opts):
571 def delete(self, repo, patches, opts):
572 if not patches and not opts.get('rev'):
572 if not patches and not opts.get('rev'):
573 raise util.Abort(_('qdelete requires at least one revision or '
573 raise util.Abort(_('qdelete requires at least one revision or '
574 'patch name'))
574 'patch name'))
575
575
576 realpatches = []
576 realpatches = []
577 for patch in patches:
577 for patch in patches:
578 patch = self.lookup(patch, strict=True)
578 patch = self.lookup(patch, strict=True)
579 info = self.isapplied(patch)
579 info = self.isapplied(patch)
580 if info:
580 if info:
581 raise util.Abort(_("cannot delete applied patch %s") % patch)
581 raise util.Abort(_("cannot delete applied patch %s") % patch)
582 if patch not in self.series:
582 if patch not in self.series:
583 raise util.Abort(_("patch %s not in series file") % patch)
583 raise util.Abort(_("patch %s not in series file") % patch)
584 realpatches.append(patch)
584 realpatches.append(patch)
585
585
586 appliedbase = 0
586 appliedbase = 0
587 if opts.get('rev'):
587 if opts.get('rev'):
588 if not self.applied:
588 if not self.applied:
589 raise util.Abort(_('no patches applied'))
589 raise util.Abort(_('no patches applied'))
590 revs = cmdutil.revrange(repo, opts['rev'])
590 revs = cmdutil.revrange(repo, opts['rev'])
591 if len(revs) > 1 and revs[0] > revs[1]:
591 if len(revs) > 1 and revs[0] > revs[1]:
592 revs.reverse()
592 revs.reverse()
593 for rev in revs:
593 for rev in revs:
594 if appliedbase >= len(self.applied):
594 if appliedbase >= len(self.applied):
595 raise util.Abort(_("revision %d is not managed") % rev)
595 raise util.Abort(_("revision %d is not managed") % rev)
596
596
597 base = revlog.bin(self.applied[appliedbase].rev)
597 base = revlog.bin(self.applied[appliedbase].rev)
598 node = repo.changelog.node(rev)
598 node = repo.changelog.node(rev)
599 if node != base:
599 if node != base:
600 raise util.Abort(_("cannot delete revision %d above "
600 raise util.Abort(_("cannot delete revision %d above "
601 "applied patches") % rev)
601 "applied patches") % rev)
602 realpatches.append(self.applied[appliedbase].name)
602 realpatches.append(self.applied[appliedbase].name)
603 appliedbase += 1
603 appliedbase += 1
604
604
605 if not opts.get('keep'):
605 if not opts.get('keep'):
606 r = self.qrepo()
606 r = self.qrepo()
607 if r:
607 if r:
608 r.remove(realpatches, True)
608 r.remove(realpatches, True)
609 else:
609 else:
610 for p in realpatches:
610 for p in realpatches:
611 os.unlink(self.join(p))
611 os.unlink(self.join(p))
612
612
613 if appliedbase:
613 if appliedbase:
614 del self.applied[:appliedbase]
614 del self.applied[:appliedbase]
615 self.applied_dirty = 1
615 self.applied_dirty = 1
616 self._clean_series(realpatches)
616 self._clean_series(realpatches)
617
617
618 def check_toppatch(self, repo):
618 def check_toppatch(self, repo):
619 if len(self.applied) > 0:
619 if len(self.applied) > 0:
620 top = revlog.bin(self.applied[-1].rev)
620 top = revlog.bin(self.applied[-1].rev)
621 pp = repo.dirstate.parents()
621 pp = repo.dirstate.parents()
622 if top not in pp:
622 if top not in pp:
623 raise util.Abort(_("working directory revision is not qtip"))
623 raise util.Abort(_("working directory revision is not qtip"))
624 return top
624 return top
625 return None
625 return None
626 def check_localchanges(self, repo, force=False, refresh=True):
626 def check_localchanges(self, repo, force=False, refresh=True):
627 m, a, r, d = repo.status()[:4]
627 m, a, r, d = repo.status()[:4]
628 if m or a or r or d:
628 if m or a or r or d:
629 if not force:
629 if not force:
630 if refresh:
630 if refresh:
631 raise util.Abort(_("local changes found, refresh first"))
631 raise util.Abort(_("local changes found, refresh first"))
632 else:
632 else:
633 raise util.Abort(_("local changes found"))
633 raise util.Abort(_("local changes found"))
634 return m, a, r, d
634 return m, a, r, d
635
635
636 _reserved = ('series', 'status', 'guards')
636 _reserved = ('series', 'status', 'guards')
637 def check_reserved_name(self, name):
637 def check_reserved_name(self, name):
638 if (name in self._reserved or name.startswith('.hg')
638 if (name in self._reserved or name.startswith('.hg')
639 or name.startswith('.mq')):
639 or name.startswith('.mq')):
640 raise util.Abort(_('"%s" cannot be used as the name of a patch')
640 raise util.Abort(_('"%s" cannot be used as the name of a patch')
641 % name)
641 % name)
642
642
643 def new(self, repo, patchfn, *pats, **opts):
643 def new(self, repo, patchfn, *pats, **opts):
644 """options:
644 """options:
645 msg: a string or a no-argument function returning a string
645 msg: a string or a no-argument function returning a string
646 """
646 """
647 msg = opts.get('msg')
647 msg = opts.get('msg')
648 force = opts.get('force')
648 force = opts.get('force')
649 user = opts.get('user')
649 user = opts.get('user')
650 date = opts.get('date')
650 date = opts.get('date')
651 if date:
651 if date:
652 date = util.parsedate(date)
652 date = util.parsedate(date)
653 self.check_reserved_name(patchfn)
653 self.check_reserved_name(patchfn)
654 if os.path.exists(self.join(patchfn)):
654 if os.path.exists(self.join(patchfn)):
655 raise util.Abort(_('patch "%s" already exists') % patchfn)
655 raise util.Abort(_('patch "%s" already exists') % patchfn)
656 if opts.get('include') or opts.get('exclude') or pats:
656 if opts.get('include') or opts.get('exclude') or pats:
657 match = cmdutil.match(repo, pats, opts)
657 match = cmdutil.match(repo, pats, opts)
658 # detect missing files in pats
658 # detect missing files in pats
659 def badfn(f, msg):
659 def badfn(f, msg):
660 raise util.Abort('%s: %s' % (f, msg))
660 raise util.Abort('%s: %s' % (f, msg))
661 match.bad = badfn
661 match.bad = badfn
662 m, a, r, d = repo.status(match=match)[:4]
662 m, a, r, d = repo.status(match=match)[:4]
663 else:
663 else:
664 m, a, r, d = self.check_localchanges(repo, force)
664 m, a, r, d = self.check_localchanges(repo, force)
665 match = cmdutil.match(repo, m + a + r)
665 match = cmdutil.match(repo, m + a + r)
666 commitfiles = m + a + r
666 commitfiles = m + a + r
667 self.check_toppatch(repo)
667 self.check_toppatch(repo)
668 insert = self.full_series_end()
668 insert = self.full_series_end()
669 wlock = repo.wlock()
669 wlock = repo.wlock()
670 try:
670 try:
671 # if patch file write fails, abort early
671 # if patch file write fails, abort early
672 p = self.opener(patchfn, "w")
672 p = self.opener(patchfn, "w")
673 try:
673 try:
674 if date:
674 if date:
675 p.write("# HG changeset patch\n")
675 p.write("# HG changeset patch\n")
676 if user:
676 if user:
677 p.write("# User " + user + "\n")
677 p.write("# User " + user + "\n")
678 p.write("# Date %d %d\n\n" % date)
678 p.write("# Date %d %d\n\n" % date)
679 elif user:
679 elif user:
680 p.write("From: " + user + "\n\n")
680 p.write("From: " + user + "\n\n")
681
681
682 if callable(msg):
682 if callable(msg):
683 msg = msg()
683 msg = msg()
684 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
684 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
685 n = repo.commit(commitfiles, commitmsg, user, date, match=match, force=True)
685 n = repo.commit(commitfiles, commitmsg, user, date, match=match, force=True)
686 if n == None:
686 if n == None:
687 raise util.Abort(_("repo commit failed"))
687 raise util.Abort(_("repo commit failed"))
688 try:
688 try:
689 self.full_series[insert:insert] = [patchfn]
689 self.full_series[insert:insert] = [patchfn]
690 self.applied.append(statusentry(revlog.hex(n), patchfn))
690 self.applied.append(statusentry(revlog.hex(n), patchfn))
691 self.parse_series()
691 self.parse_series()
692 self.series_dirty = 1
692 self.series_dirty = 1
693 self.applied_dirty = 1
693 self.applied_dirty = 1
694 if msg:
694 if msg:
695 msg = msg + "\n"
695 msg = msg + "\n"
696 p.write(msg)
696 p.write(msg)
697 if commitfiles:
697 if commitfiles:
698 diffopts = self.diffopts()
698 diffopts = self.diffopts()
699 if opts.get('git'): diffopts.git = True
699 if opts.get('git'): diffopts.git = True
700 parent = self.qparents(repo, n)
700 parent = self.qparents(repo, n)
701 patch.diff(repo, node1=parent, node2=n, fp=p,
701 patch.diff(repo, node1=parent, node2=n, fp=p,
702 match=match, opts=diffopts)
702 match=match, opts=diffopts)
703 p.close()
703 p.close()
704 wlock = None
704 wlock = None
705 r = self.qrepo()
705 r = self.qrepo()
706 if r: r.add([patchfn])
706 if r: r.add([patchfn])
707 except:
707 except:
708 repo.rollback()
708 repo.rollback()
709 raise
709 raise
710 except Exception, inst:
710 except Exception, inst:
711 patchpath = self.join(patchfn)
711 patchpath = self.join(patchfn)
712 try:
712 try:
713 os.unlink(patchpath)
713 os.unlink(patchpath)
714 except:
714 except:
715 self.ui.warn(_('error unlinking %s\n') % patchpath)
715 self.ui.warn(_('error unlinking %s\n') % patchpath)
716 raise
716 raise
717 self.removeundo(repo)
717 self.removeundo(repo)
718 finally:
718 finally:
719 del wlock
719 del wlock
720
720
721 def strip(self, repo, rev, update=True, backup="all", force=None):
721 def strip(self, repo, rev, update=True, backup="all", force=None):
722 wlock = lock = None
722 wlock = lock = None
723 try:
723 try:
724 wlock = repo.wlock()
724 wlock = repo.wlock()
725 lock = repo.lock()
725 lock = repo.lock()
726
726
727 if update:
727 if update:
728 self.check_localchanges(repo, force=force, refresh=False)
728 self.check_localchanges(repo, force=force, refresh=False)
729 urev = self.qparents(repo, rev)
729 urev = self.qparents(repo, rev)
730 hg.clean(repo, urev)
730 hg.clean(repo, urev)
731 repo.dirstate.write()
731 repo.dirstate.write()
732
732
733 self.removeundo(repo)
733 self.removeundo(repo)
734 repair.strip(self.ui, repo, rev, backup)
734 repair.strip(self.ui, repo, rev, backup)
735 # strip may have unbundled a set of backed up revisions after
735 # strip may have unbundled a set of backed up revisions after
736 # the actual strip
736 # the actual strip
737 self.removeundo(repo)
737 self.removeundo(repo)
738 finally:
738 finally:
739 del lock, wlock
739 del lock, wlock
740
740
741 def isapplied(self, patch):
741 def isapplied(self, patch):
742 """returns (index, rev, patch)"""
742 """returns (index, rev, patch)"""
743 for i in xrange(len(self.applied)):
743 for i in xrange(len(self.applied)):
744 a = self.applied[i]
744 a = self.applied[i]
745 if a.name == patch:
745 if a.name == patch:
746 return (i, a.rev, a.name)
746 return (i, a.rev, a.name)
747 return None
747 return None
748
748
749 # if the exact patch name does not exist, we try a few
749 # if the exact patch name does not exist, we try a few
750 # variations. If strict is passed, we try only #1
750 # variations. If strict is passed, we try only #1
751 #
751 #
752 # 1) a number to indicate an offset in the series file
752 # 1) a number to indicate an offset in the series file
753 # 2) a unique substring of the patch name was given
753 # 2) a unique substring of the patch name was given
754 # 3) patchname[-+]num to indicate an offset in the series file
754 # 3) patchname[-+]num to indicate an offset in the series file
755 def lookup(self, patch, strict=False):
755 def lookup(self, patch, strict=False):
756 patch = patch and str(patch)
756 patch = patch and str(patch)
757
757
758 def partial_name(s):
758 def partial_name(s):
759 if s in self.series:
759 if s in self.series:
760 return s
760 return s
761 matches = [x for x in self.series if s in x]
761 matches = [x for x in self.series if s in x]
762 if len(matches) > 1:
762 if len(matches) > 1:
763 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
763 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
764 for m in matches:
764 for m in matches:
765 self.ui.warn(' %s\n' % m)
765 self.ui.warn(' %s\n' % m)
766 return None
766 return None
767 if matches:
767 if matches:
768 return matches[0]
768 return matches[0]
769 if len(self.series) > 0 and len(self.applied) > 0:
769 if len(self.series) > 0 and len(self.applied) > 0:
770 if s == 'qtip':
770 if s == 'qtip':
771 return self.series[self.series_end(True)-1]
771 return self.series[self.series_end(True)-1]
772 if s == 'qbase':
772 if s == 'qbase':
773 return self.series[0]
773 return self.series[0]
774 return None
774 return None
775 if patch == None:
775 if patch == None:
776 return None
776 return None
777
777
778 # we don't want to return a partial match until we make
778 # we don't want to return a partial match until we make
779 # sure the file name passed in does not exist (checked below)
779 # sure the file name passed in does not exist (checked below)
780 res = partial_name(patch)
780 res = partial_name(patch)
781 if res and res == patch:
781 if res and res == patch:
782 return res
782 return res
783
783
784 if not os.path.isfile(self.join(patch)):
784 if not os.path.isfile(self.join(patch)):
785 try:
785 try:
786 sno = int(patch)
786 sno = int(patch)
787 except(ValueError, OverflowError):
787 except(ValueError, OverflowError):
788 pass
788 pass
789 else:
789 else:
790 if sno < len(self.series):
790 if sno < len(self.series):
791 return self.series[sno]
791 return self.series[sno]
792 if not strict:
792 if not strict:
793 # return any partial match made above
793 # return any partial match made above
794 if res:
794 if res:
795 return res
795 return res
796 minus = patch.rfind('-')
796 minus = patch.rfind('-')
797 if minus >= 0:
797 if minus >= 0:
798 res = partial_name(patch[:minus])
798 res = partial_name(patch[:minus])
799 if res:
799 if res:
800 i = self.series.index(res)
800 i = self.series.index(res)
801 try:
801 try:
802 off = int(patch[minus+1:] or 1)
802 off = int(patch[minus+1:] or 1)
803 except(ValueError, OverflowError):
803 except(ValueError, OverflowError):
804 pass
804 pass
805 else:
805 else:
806 if i - off >= 0:
806 if i - off >= 0:
807 return self.series[i - off]
807 return self.series[i - off]
808 plus = patch.rfind('+')
808 plus = patch.rfind('+')
809 if plus >= 0:
809 if plus >= 0:
810 res = partial_name(patch[:plus])
810 res = partial_name(patch[:plus])
811 if res:
811 if res:
812 i = self.series.index(res)
812 i = self.series.index(res)
813 try:
813 try:
814 off = int(patch[plus+1:] or 1)
814 off = int(patch[plus+1:] or 1)
815 except(ValueError, OverflowError):
815 except(ValueError, OverflowError):
816 pass
816 pass
817 else:
817 else:
818 if i + off < len(self.series):
818 if i + off < len(self.series):
819 return self.series[i + off]
819 return self.series[i + off]
820 raise util.Abort(_("patch %s not in series") % patch)
820 raise util.Abort(_("patch %s not in series") % patch)
821
821
822 def push(self, repo, patch=None, force=False, list=False,
822 def push(self, repo, patch=None, force=False, list=False,
823 mergeq=None):
823 mergeq=None):
824 wlock = repo.wlock()
824 wlock = repo.wlock()
825 if repo.dirstate.parents()[0] != repo.changelog.tip():
825 if repo.dirstate.parents()[0] != repo.changelog.tip():
826 self.ui.status(_("(working directory not at tip)\n"))
826 self.ui.status(_("(working directory not at tip)\n"))
827
827
828 try:
828 try:
829 patch = self.lookup(patch)
829 patch = self.lookup(patch)
830 # Suppose our series file is: A B C and the current 'top'
830 # Suppose our series file is: A B C and the current 'top'
831 # patch is B. qpush C should be performed (moving forward)
831 # patch is B. qpush C should be performed (moving forward)
832 # qpush B is a NOP (no change) qpush A is an error (can't
832 # qpush B is a NOP (no change) qpush A is an error (can't
833 # go backwards with qpush)
833 # go backwards with qpush)
834 if patch:
834 if patch:
835 info = self.isapplied(patch)
835 info = self.isapplied(patch)
836 if info:
836 if info:
837 if info[0] < len(self.applied) - 1:
837 if info[0] < len(self.applied) - 1:
838 raise util.Abort(
838 raise util.Abort(
839 _("cannot push to a previous patch: %s") % patch)
839 _("cannot push to a previous patch: %s") % patch)
840 if info[0] < len(self.series) - 1:
840 if info[0] < len(self.series) - 1:
841 self.ui.warn(
841 self.ui.warn(
842 _('qpush: %s is already at the top\n') % patch)
842 _('qpush: %s is already at the top\n') % patch)
843 else:
843 else:
844 self.ui.warn(_('all patches are currently applied\n'))
844 self.ui.warn(_('all patches are currently applied\n'))
845 return
845 return
846
846
847 # Following the above example, starting at 'top' of B:
847 # Following the above example, starting at 'top' of B:
848 # qpush should be performed (pushes C), but a subsequent
848 # qpush should be performed (pushes C), but a subsequent
849 # qpush without an argument is an error (nothing to
849 # qpush without an argument is an error (nothing to
850 # apply). This allows a loop of "...while hg qpush..." to
850 # apply). This allows a loop of "...while hg qpush..." to
851 # work as it detects an error when done
851 # work as it detects an error when done
852 if self.series_end() == len(self.series):
852 if self.series_end() == len(self.series):
853 self.ui.warn(_('patch series already fully applied\n'))
853 self.ui.warn(_('patch series already fully applied\n'))
854 return 1
854 return 1
855 if not force:
855 if not force:
856 self.check_localchanges(repo)
856 self.check_localchanges(repo)
857
857
858 self.applied_dirty = 1;
858 self.applied_dirty = 1;
859 start = self.series_end()
859 start = self.series_end()
860 if start > 0:
860 if start > 0:
861 self.check_toppatch(repo)
861 self.check_toppatch(repo)
862 if not patch:
862 if not patch:
863 patch = self.series[start]
863 patch = self.series[start]
864 end = start + 1
864 end = start + 1
865 else:
865 else:
866 end = self.series.index(patch, start) + 1
866 end = self.series.index(patch, start) + 1
867 s = self.series[start:end]
867 s = self.series[start:end]
868 all_files = {}
868 all_files = {}
869 try:
869 try:
870 if mergeq:
870 if mergeq:
871 ret = self.mergepatch(repo, mergeq, s)
871 ret = self.mergepatch(repo, mergeq, s)
872 else:
872 else:
873 ret = self.apply(repo, s, list, all_files=all_files)
873 ret = self.apply(repo, s, list, all_files=all_files)
874 except:
874 except:
875 self.ui.warn(_('cleaning up working directory...'))
875 self.ui.warn(_('cleaning up working directory...'))
876 node = repo.dirstate.parents()[0]
876 node = repo.dirstate.parents()[0]
877 hg.revert(repo, node, None)
877 hg.revert(repo, node, None)
878 unknown = repo.status(unknown=True)[4]
878 unknown = repo.status(unknown=True)[4]
879 # only remove unknown files that we know we touched or
879 # only remove unknown files that we know we touched or
880 # created while patching
880 # created while patching
881 for f in unknown:
881 for f in unknown:
882 if f in all_files:
882 if f in all_files:
883 util.unlink(repo.wjoin(f))
883 util.unlink(repo.wjoin(f))
884 self.ui.warn(_('done\n'))
884 self.ui.warn(_('done\n'))
885 raise
885 raise
886 top = self.applied[-1].name
886 top = self.applied[-1].name
887 if ret[0]:
887 if ret[0]:
888 self.ui.write(
888 self.ui.write(
889 "Errors during apply, please fix and refresh %s\n" % top)
889 "Errors during apply, please fix and refresh %s\n" % top)
890 else:
890 else:
891 self.ui.write("Now at: %s\n" % top)
891 self.ui.write("Now at: %s\n" % top)
892 return ret[0]
892 return ret[0]
893 finally:
893 finally:
894 del wlock
894 del wlock
895
895
896 def pop(self, repo, patch=None, force=False, update=True, all=False):
896 def pop(self, repo, patch=None, force=False, update=True, all=False):
897 def getfile(f, rev, flags):
897 def getfile(f, rev, flags):
898 t = repo.file(f).read(rev)
898 t = repo.file(f).read(rev)
899 repo.wwrite(f, t, flags)
899 repo.wwrite(f, t, flags)
900
900
901 wlock = repo.wlock()
901 wlock = repo.wlock()
902 try:
902 try:
903 if patch:
903 if patch:
904 # index, rev, patch
904 # index, rev, patch
905 info = self.isapplied(patch)
905 info = self.isapplied(patch)
906 if not info:
906 if not info:
907 patch = self.lookup(patch)
907 patch = self.lookup(patch)
908 info = self.isapplied(patch)
908 info = self.isapplied(patch)
909 if not info:
909 if not info:
910 raise util.Abort(_("patch %s is not applied") % patch)
910 raise util.Abort(_("patch %s is not applied") % patch)
911
911
912 if len(self.applied) == 0:
912 if len(self.applied) == 0:
913 # Allow qpop -a to work repeatedly,
913 # Allow qpop -a to work repeatedly,
914 # but not qpop without an argument
914 # but not qpop without an argument
915 self.ui.warn(_("no patches applied\n"))
915 self.ui.warn(_("no patches applied\n"))
916 return not all
916 return not all
917
917
918 if not update:
918 if not update:
919 parents = repo.dirstate.parents()
919 parents = repo.dirstate.parents()
920 rr = [ revlog.bin(x.rev) for x in self.applied ]
920 rr = [ revlog.bin(x.rev) for x in self.applied ]
921 for p in parents:
921 for p in parents:
922 if p in rr:
922 if p in rr:
923 self.ui.warn(_("qpop: forcing dirstate update\n"))
923 self.ui.warn(_("qpop: forcing dirstate update\n"))
924 update = True
924 update = True
925
925
926 if not force and update:
926 if not force and update:
927 self.check_localchanges(repo)
927 self.check_localchanges(repo)
928
928
929 self.applied_dirty = 1;
929 self.applied_dirty = 1;
930 end = len(self.applied)
930 end = len(self.applied)
931 if not patch:
931 if not patch:
932 if all:
932 if all:
933 popi = 0
933 popi = 0
934 else:
934 else:
935 popi = len(self.applied) - 1
935 popi = len(self.applied) - 1
936 else:
936 else:
937 popi = info[0] + 1
937 popi = info[0] + 1
938 if popi >= end:
938 if popi >= end:
939 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
939 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
940 return
940 return
941 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
941 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
942
942
943 start = info[0]
943 start = info[0]
944 rev = revlog.bin(info[1])
944 rev = revlog.bin(info[1])
945
945
946 if update:
946 if update:
947 top = self.check_toppatch(repo)
947 top = self.check_toppatch(repo)
948
948
949 if repo.changelog.heads(rev) != [revlog.bin(self.applied[-1].rev)]:
949 if repo.changelog.heads(rev) != [revlog.bin(self.applied[-1].rev)]:
950 raise util.Abort(_("popping would remove a revision not "
950 raise util.Abort(_("popping would remove a revision not "
951 "managed by this patch queue"))
951 "managed by this patch queue"))
952
952
953 # we know there are no local changes, so we can make a simplified
953 # we know there are no local changes, so we can make a simplified
954 # form of hg.update.
954 # form of hg.update.
955 if update:
955 if update:
956 qp = self.qparents(repo, rev)
956 qp = self.qparents(repo, rev)
957 changes = repo.changelog.read(qp)
957 changes = repo.changelog.read(qp)
958 mmap = repo.manifest.read(changes[0])
958 mmap = repo.manifest.read(changes[0])
959 m, a, r, d = repo.status(qp, top)[:4]
959 m, a, r, d = repo.status(qp, top)[:4]
960 if d:
960 if d:
961 raise util.Abort(_("deletions found between repo revs"))
961 raise util.Abort(_("deletions found between repo revs"))
962 for f in m:
962 for f in m:
963 getfile(f, mmap[f], mmap.flags(f))
963 getfile(f, mmap[f], mmap.flags(f))
964 for f in r:
964 for f in r:
965 getfile(f, mmap[f], mmap.flags(f))
965 getfile(f, mmap[f], mmap.flags(f))
966 for f in m + r:
966 for f in m + r:
967 repo.dirstate.normal(f)
967 repo.dirstate.normal(f)
968 for f in a:
968 for f in a:
969 try:
969 try:
970 os.unlink(repo.wjoin(f))
970 os.unlink(repo.wjoin(f))
971 except OSError, e:
971 except OSError, e:
972 if e.errno != errno.ENOENT:
972 if e.errno != errno.ENOENT:
973 raise
973 raise
974 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
974 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
975 except: pass
975 except: pass
976 repo.dirstate.forget(f)
976 repo.dirstate.forget(f)
977 repo.dirstate.setparents(qp, revlog.nullid)
977 repo.dirstate.setparents(qp, revlog.nullid)
978 del self.applied[start:end]
978 del self.applied[start:end]
979 self.strip(repo, rev, update=False, backup='strip')
979 self.strip(repo, rev, update=False, backup='strip')
980 if len(self.applied):
980 if len(self.applied):
981 self.ui.write(_("Now at: %s\n") % self.applied[-1].name)
981 self.ui.write(_("Now at: %s\n") % self.applied[-1].name)
982 else:
982 else:
983 self.ui.write(_("Patch queue now empty\n"))
983 self.ui.write(_("Patch queue now empty\n"))
984 finally:
984 finally:
985 del wlock
985 del wlock
986
986
987 def diff(self, repo, pats, opts):
987 def diff(self, repo, pats, opts):
988 top = self.check_toppatch(repo)
988 top = self.check_toppatch(repo)
989 if not top:
989 if not top:
990 self.ui.write(_("No patches applied\n"))
990 self.ui.write(_("No patches applied\n"))
991 return
991 return
992 qp = self.qparents(repo, top)
992 qp = self.qparents(repo, top)
993 self._diffopts = patch.diffopts(self.ui, opts)
993 self._diffopts = patch.diffopts(self.ui, opts)
994 self.printdiff(repo, qp, files=pats, opts=opts)
994 self.printdiff(repo, qp, files=pats, opts=opts)
995
995
996 def refresh(self, repo, pats=None, **opts):
996 def refresh(self, repo, pats=None, **opts):
997 if len(self.applied) == 0:
997 if len(self.applied) == 0:
998 self.ui.write(_("No patches applied\n"))
998 self.ui.write(_("No patches applied\n"))
999 return 1
999 return 1
1000 newdate = opts.get('date')
1000 newdate = opts.get('date')
1001 if newdate:
1001 if newdate:
1002 newdate = '%d %d' % util.parsedate(newdate)
1002 newdate = '%d %d' % util.parsedate(newdate)
1003 wlock = repo.wlock()
1003 wlock = repo.wlock()
1004 try:
1004 try:
1005 self.check_toppatch(repo)
1005 self.check_toppatch(repo)
1006 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
1006 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
1007 top = revlog.bin(top)
1007 top = revlog.bin(top)
1008 if repo.changelog.heads(top) != [top]:
1008 if repo.changelog.heads(top) != [top]:
1009 raise util.Abort(_("cannot refresh a revision with children"))
1009 raise util.Abort(_("cannot refresh a revision with children"))
1010 cparents = repo.changelog.parents(top)
1010 cparents = repo.changelog.parents(top)
1011 patchparent = self.qparents(repo, top)
1011 patchparent = self.qparents(repo, top)
1012 message, comments, user, date, patchfound = self.readheaders(patchfn)
1012 message, comments, user, date, patchfound = self.readheaders(patchfn)
1013
1013
1014 patchf = self.opener(patchfn, 'r+')
1014 patchf = self.opener(patchfn, 'r+')
1015
1015
1016 # if the patch was a git patch, refresh it as a git patch
1016 # if the patch was a git patch, refresh it as a git patch
1017 for line in patchf:
1017 for line in patchf:
1018 if line.startswith('diff --git'):
1018 if line.startswith('diff --git'):
1019 self.diffopts().git = True
1019 self.diffopts().git = True
1020 break
1020 break
1021
1021
1022 msg = opts.get('msg', '').rstrip()
1022 msg = opts.get('msg', '').rstrip()
1023 if msg and comments:
1023 if msg and comments:
1024 # Remove existing message, keeping the rest of the comments
1024 # Remove existing message, keeping the rest of the comments
1025 # fields.
1025 # fields.
1026 # If comments contains 'subject: ', message will prepend
1026 # If comments contains 'subject: ', message will prepend
1027 # the field and a blank line.
1027 # the field and a blank line.
1028 if message:
1028 if message:
1029 subj = 'subject: ' + message[0].lower()
1029 subj = 'subject: ' + message[0].lower()
1030 for i in xrange(len(comments)):
1030 for i in xrange(len(comments)):
1031 if subj == comments[i].lower():
1031 if subj == comments[i].lower():
1032 del comments[i]
1032 del comments[i]
1033 message = message[2:]
1033 message = message[2:]
1034 break
1034 break
1035 ci = 0
1035 ci = 0
1036 for mi in xrange(len(message)):
1036 for mi in xrange(len(message)):
1037 while message[mi] != comments[ci]:
1037 while message[mi] != comments[ci]:
1038 ci += 1
1038 ci += 1
1039 del comments[ci]
1039 del comments[ci]
1040
1040
1041 def setheaderfield(comments, prefixes, new):
1041 def setheaderfield(comments, prefixes, new):
1042 # Update all references to a field in the patch header.
1042 # Update all references to a field in the patch header.
1043 # If none found, add it email style.
1043 # If none found, add it email style.
1044 res = False
1044 res = False
1045 for prefix in prefixes:
1045 for prefix in prefixes:
1046 for i in xrange(len(comments)):
1046 for i in xrange(len(comments)):
1047 if comments[i].startswith(prefix):
1047 if comments[i].startswith(prefix):
1048 comments[i] = prefix + new
1048 comments[i] = prefix + new
1049 res = True
1049 res = True
1050 break
1050 break
1051 return res
1051 return res
1052
1052
1053 newuser = opts.get('user')
1053 newuser = opts.get('user')
1054 if newuser:
1054 if newuser:
1055 if not setheaderfield(comments, ['From: ', '# User '], newuser):
1055 if not setheaderfield(comments, ['From: ', '# User '], newuser):
1056 try:
1056 try:
1057 patchheaderat = comments.index('# HG changeset patch')
1057 patchheaderat = comments.index('# HG changeset patch')
1058 comments.insert(patchheaderat + 1,'# User ' + newuser)
1058 comments.insert(patchheaderat + 1,'# User ' + newuser)
1059 except ValueError:
1059 except ValueError:
1060 comments = ['From: ' + newuser, ''] + comments
1060 comments = ['From: ' + newuser, ''] + comments
1061 user = newuser
1061 user = newuser
1062
1062
1063 if newdate:
1063 if newdate:
1064 if setheaderfield(comments, ['# Date '], newdate):
1064 if setheaderfield(comments, ['# Date '], newdate):
1065 date = newdate
1065 date = newdate
1066
1066
1067 if msg:
1067 if msg:
1068 comments.append(msg)
1068 comments.append(msg)
1069
1069
1070 patchf.seek(0)
1070 patchf.seek(0)
1071 patchf.truncate()
1071 patchf.truncate()
1072
1072
1073 if comments:
1073 if comments:
1074 comments = "\n".join(comments) + '\n\n'
1074 comments = "\n".join(comments) + '\n\n'
1075 patchf.write(comments)
1075 patchf.write(comments)
1076
1076
1077 if opts.get('git'):
1077 if opts.get('git'):
1078 self.diffopts().git = True
1078 self.diffopts().git = True
1079 tip = repo.changelog.tip()
1079 tip = repo.changelog.tip()
1080 if top == tip:
1080 if top == tip:
1081 # if the top of our patch queue is also the tip, there is an
1081 # if the top of our patch queue is also the tip, there is an
1082 # optimization here. We update the dirstate in place and strip
1082 # optimization here. We update the dirstate in place and strip
1083 # off the tip commit. Then just commit the current directory
1083 # off the tip commit. Then just commit the current directory
1084 # tree. We can also send repo.commit the list of files
1084 # tree. We can also send repo.commit the list of files
1085 # changed to speed up the diff
1085 # changed to speed up the diff
1086 #
1086 #
1087 # in short mode, we only diff the files included in the
1087 # in short mode, we only diff the files included in the
1088 # patch already plus specified files
1088 # patch already plus specified files
1089 #
1089 #
1090 # this should really read:
1090 # this should really read:
1091 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1091 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1092 # but we do it backwards to take advantage of manifest/chlog
1092 # but we do it backwards to take advantage of manifest/chlog
1093 # caching against the next repo.status call
1093 # caching against the next repo.status call
1094 #
1094 #
1095 mm, aa, dd, aa2 = repo.status(patchparent, tip)[:4]
1095 mm, aa, dd, aa2 = repo.status(patchparent, tip)[:4]
1096 changes = repo.changelog.read(tip)
1096 changes = repo.changelog.read(tip)
1097 man = repo.manifest.read(changes[0])
1097 man = repo.manifest.read(changes[0])
1098 aaa = aa[:]
1098 aaa = aa[:]
1099 matchfn = cmdutil.match(repo, pats, opts)
1099 matchfn = cmdutil.match(repo, pats, opts)
1100 if opts.get('short'):
1100 if opts.get('short'):
1101 # if amending a patch, we start with existing
1101 # if amending a patch, we start with existing
1102 # files plus specified files - unfiltered
1102 # files plus specified files - unfiltered
1103 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1103 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1104 # filter with inc/exl options
1104 # filter with inc/exl options
1105 matchfn = cmdutil.match(repo, opts=opts)
1105 matchfn = cmdutil.match(repo, opts=opts)
1106 else:
1106 else:
1107 match = cmdutil.matchall(repo)
1107 match = cmdutil.matchall(repo)
1108 m, a, r, d = repo.status(match=match)[:4]
1108 m, a, r, d = repo.status(match=match)[:4]
1109
1109
1110 # we might end up with files that were added between
1110 # we might end up with files that were added between
1111 # tip and the dirstate parent, but then changed in the
1111 # tip and the dirstate parent, but then changed in the
1112 # local dirstate. in this case, we want them to only
1112 # local dirstate. in this case, we want them to only
1113 # show up in the added section
1113 # show up in the added section
1114 for x in m:
1114 for x in m:
1115 if x not in aa:
1115 if x not in aa:
1116 mm.append(x)
1116 mm.append(x)
1117 # we might end up with files added by the local dirstate that
1117 # we might end up with files added by the local dirstate that
1118 # were deleted by the patch. In this case, they should only
1118 # were deleted by the patch. In this case, they should only
1119 # show up in the changed section.
1119 # show up in the changed section.
1120 for x in a:
1120 for x in a:
1121 if x in dd:
1121 if x in dd:
1122 del dd[dd.index(x)]
1122 del dd[dd.index(x)]
1123 mm.append(x)
1123 mm.append(x)
1124 else:
1124 else:
1125 aa.append(x)
1125 aa.append(x)
1126 # make sure any files deleted in the local dirstate
1126 # make sure any files deleted in the local dirstate
1127 # are not in the add or change column of the patch
1127 # are not in the add or change column of the patch
1128 forget = []
1128 forget = []
1129 for x in d + r:
1129 for x in d + r:
1130 if x in aa:
1130 if x in aa:
1131 del aa[aa.index(x)]
1131 del aa[aa.index(x)]
1132 forget.append(x)
1132 forget.append(x)
1133 continue
1133 continue
1134 elif x in mm:
1134 elif x in mm:
1135 del mm[mm.index(x)]
1135 del mm[mm.index(x)]
1136 dd.append(x)
1136 dd.append(x)
1137
1137
1138 m = util.unique(mm)
1138 m = util.unique(mm)
1139 r = util.unique(dd)
1139 r = util.unique(dd)
1140 a = util.unique(aa)
1140 a = util.unique(aa)
1141 c = [filter(matchfn, l) for l in (m, a, r)]
1141 c = [filter(matchfn, l) for l in (m, a, r)]
1142 match = cmdutil.matchfiles(repo, util.unique(c[0] + c[1] + c[2]))
1142 match = cmdutil.matchfiles(repo, util.unique(c[0] + c[1] + c[2]))
1143 patch.diff(repo, patchparent, match=match,
1143 patch.diff(repo, patchparent, match=match,
1144 fp=patchf, changes=c, opts=self.diffopts())
1144 fp=patchf, changes=c, opts=self.diffopts())
1145 patchf.close()
1145 patchf.close()
1146
1146
1147 repo.dirstate.setparents(*cparents)
1147 repo.dirstate.setparents(*cparents)
1148 copies = {}
1148 copies = {}
1149 for dst in a:
1149 for dst in a:
1150 src = repo.dirstate.copied(dst)
1150 src = repo.dirstate.copied(dst)
1151 if src is not None:
1151 if src is not None:
1152 copies.setdefault(src, []).append(dst)
1152 copies.setdefault(src, []).append(dst)
1153 repo.dirstate.add(dst)
1153 repo.dirstate.add(dst)
1154 # remember the copies between patchparent and tip
1154 # remember the copies between patchparent and tip
1155 # this may be slow, so don't do it if we're not tracking copies
1155 # this may be slow, so don't do it if we're not tracking copies
1156 if self.diffopts().git:
1156 if self.diffopts().git:
1157 for dst in aaa:
1157 for dst in aaa:
1158 f = repo.file(dst)
1158 f = repo.file(dst)
1159 src = f.renamed(man[dst])
1159 src = f.renamed(man[dst])
1160 if src:
1160 if src:
1161 copies.setdefault(src[0], []).extend(copies.get(dst, []))
1161 copies.setdefault(src[0], []).extend(copies.get(dst, []))
1162 if dst in a:
1162 if dst in a:
1163 copies[src[0]].append(dst)
1163 copies[src[0]].append(dst)
1164 # we can't copy a file created by the patch itself
1164 # we can't copy a file created by the patch itself
1165 if dst in copies:
1165 if dst in copies:
1166 del copies[dst]
1166 del copies[dst]
1167 for src, dsts in copies.iteritems():
1167 for src, dsts in copies.iteritems():
1168 for dst in dsts:
1168 for dst in dsts:
1169 repo.dirstate.copy(src, dst)
1169 repo.dirstate.copy(src, dst)
1170 for f in r:
1170 for f in r:
1171 repo.dirstate.remove(f)
1171 repo.dirstate.remove(f)
1172 # if the patch excludes a modified file, mark that
1172 # if the patch excludes a modified file, mark that
1173 # file with mtime=0 so status can see it.
1173 # file with mtime=0 so status can see it.
1174 mm = []
1174 mm = []
1175 for i in xrange(len(m)-1, -1, -1):
1175 for i in xrange(len(m)-1, -1, -1):
1176 if not matchfn(m[i]):
1176 if not matchfn(m[i]):
1177 mm.append(m[i])
1177 mm.append(m[i])
1178 del m[i]
1178 del m[i]
1179 for f in m:
1179 for f in m:
1180 repo.dirstate.normal(f)
1180 repo.dirstate.normal(f)
1181 for f in mm:
1181 for f in mm:
1182 repo.dirstate.normallookup(f)
1182 repo.dirstate.normallookup(f)
1183 for f in forget:
1183 for f in forget:
1184 repo.dirstate.forget(f)
1184 repo.dirstate.forget(f)
1185
1185
1186 if not msg:
1186 if not msg:
1187 if not message:
1187 if not message:
1188 message = "[mq]: %s\n" % patchfn
1188 message = "[mq]: %s\n" % patchfn
1189 else:
1189 else:
1190 message = "\n".join(message)
1190 message = "\n".join(message)
1191 else:
1191 else:
1192 message = msg
1192 message = msg
1193
1193
1194 if not user:
1194 if not user:
1195 user = changes[1]
1195 user = changes[1]
1196
1196
1197 self.applied.pop()
1197 self.applied.pop()
1198 self.applied_dirty = 1
1198 self.applied_dirty = 1
1199 self.strip(repo, top, update=False,
1199 self.strip(repo, top, update=False,
1200 backup='strip')
1200 backup='strip')
1201 n = repo.commit(match.files(), message, user, date, match=match,
1201 n = repo.commit(match.files(), message, user, date, match=match,
1202 force=1)
1202 force=1)
1203 self.applied.append(statusentry(revlog.hex(n), patchfn))
1203 self.applied.append(statusentry(revlog.hex(n), patchfn))
1204 self.removeundo(repo)
1204 self.removeundo(repo)
1205 else:
1205 else:
1206 self.printdiff(repo, patchparent, fp=patchf)
1206 self.printdiff(repo, patchparent, fp=patchf)
1207 patchf.close()
1207 patchf.close()
1208 added = repo.status()[1]
1208 added = repo.status()[1]
1209 for a in added:
1209 for a in added:
1210 f = repo.wjoin(a)
1210 f = repo.wjoin(a)
1211 try:
1211 try:
1212 os.unlink(f)
1212 os.unlink(f)
1213 except OSError, e:
1213 except OSError, e:
1214 if e.errno != errno.ENOENT:
1214 if e.errno != errno.ENOENT:
1215 raise
1215 raise
1216 try: os.removedirs(os.path.dirname(f))
1216 try: os.removedirs(os.path.dirname(f))
1217 except: pass
1217 except: pass
1218 # forget the file copies in the dirstate
1218 # forget the file copies in the dirstate
1219 # push should readd the files later on
1219 # push should readd the files later on
1220 repo.dirstate.forget(a)
1220 repo.dirstate.forget(a)
1221 self.pop(repo, force=True)
1221 self.pop(repo, force=True)
1222 self.push(repo, force=True)
1222 self.push(repo, force=True)
1223 finally:
1223 finally:
1224 del wlock
1224 del wlock
1225
1225
1226 def init(self, repo, create=False):
1226 def init(self, repo, create=False):
1227 if not create and os.path.isdir(self.path):
1227 if not create and os.path.isdir(self.path):
1228 raise util.Abort(_("patch queue directory already exists"))
1228 raise util.Abort(_("patch queue directory already exists"))
1229 try:
1229 try:
1230 os.mkdir(self.path)
1230 os.mkdir(self.path)
1231 except OSError, inst:
1231 except OSError, inst:
1232 if inst.errno != errno.EEXIST or not create:
1232 if inst.errno != errno.EEXIST or not create:
1233 raise
1233 raise
1234 if create:
1234 if create:
1235 return self.qrepo(create=True)
1235 return self.qrepo(create=True)
1236
1236
1237 def unapplied(self, repo, patch=None):
1237 def unapplied(self, repo, patch=None):
1238 if patch and patch not in self.series:
1238 if patch and patch not in self.series:
1239 raise util.Abort(_("patch %s is not in series file") % patch)
1239 raise util.Abort(_("patch %s is not in series file") % patch)
1240 if not patch:
1240 if not patch:
1241 start = self.series_end()
1241 start = self.series_end()
1242 else:
1242 else:
1243 start = self.series.index(patch) + 1
1243 start = self.series.index(patch) + 1
1244 unapplied = []
1244 unapplied = []
1245 for i in xrange(start, len(self.series)):
1245 for i in xrange(start, len(self.series)):
1246 pushable, reason = self.pushable(i)
1246 pushable, reason = self.pushable(i)
1247 if pushable:
1247 if pushable:
1248 unapplied.append((i, self.series[i]))
1248 unapplied.append((i, self.series[i]))
1249 self.explain_pushable(i)
1249 self.explain_pushable(i)
1250 return unapplied
1250 return unapplied
1251
1251
1252 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1252 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1253 summary=False):
1253 summary=False):
1254 def displayname(patchname):
1254 def displayname(patchname):
1255 if summary:
1255 if summary:
1256 msg = self.readheaders(patchname)[0]
1256 msg = self.readheaders(patchname)[0]
1257 msg = msg and ': ' + msg[0] or ': '
1257 msg = msg and ': ' + msg[0] or ': '
1258 else:
1258 else:
1259 msg = ''
1259 msg = ''
1260 return '%s%s' % (patchname, msg)
1260 return '%s%s' % (patchname, msg)
1261
1261
1262 applied = dict.fromkeys([p.name for p in self.applied])
1262 applied = dict.fromkeys([p.name for p in self.applied])
1263 if length is None:
1263 if length is None:
1264 length = len(self.series) - start
1264 length = len(self.series) - start
1265 if not missing:
1265 if not missing:
1266 for i in xrange(start, start+length):
1266 for i in xrange(start, start+length):
1267 patch = self.series[i]
1267 patch = self.series[i]
1268 if patch in applied:
1268 if patch in applied:
1269 stat = 'A'
1269 stat = 'A'
1270 elif self.pushable(i)[0]:
1270 elif self.pushable(i)[0]:
1271 stat = 'U'
1271 stat = 'U'
1272 else:
1272 else:
1273 stat = 'G'
1273 stat = 'G'
1274 pfx = ''
1274 pfx = ''
1275 if self.ui.verbose:
1275 if self.ui.verbose:
1276 pfx = '%d %s ' % (i, stat)
1276 pfx = '%d %s ' % (i, stat)
1277 elif status and status != stat:
1277 elif status and status != stat:
1278 continue
1278 continue
1279 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1279 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1280 else:
1280 else:
1281 msng_list = []
1281 msng_list = []
1282 for root, dirs, files in os.walk(self.path):
1282 for root, dirs, files in os.walk(self.path):
1283 d = root[len(self.path) + 1:]
1283 d = root[len(self.path) + 1:]
1284 for f in files:
1284 for f in files:
1285 fl = os.path.join(d, f)
1285 fl = os.path.join(d, f)
1286 if (fl not in self.series and
1286 if (fl not in self.series and
1287 fl not in (self.status_path, self.series_path,
1287 fl not in (self.status_path, self.series_path,
1288 self.guards_path)
1288 self.guards_path)
1289 and not fl.startswith('.')):
1289 and not fl.startswith('.')):
1290 msng_list.append(fl)
1290 msng_list.append(fl)
1291 for x in util.sort(msng_list):
1291 for x in util.sort(msng_list):
1292 pfx = self.ui.verbose and ('D ') or ''
1292 pfx = self.ui.verbose and ('D ') or ''
1293 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1293 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1294
1294
1295 def issaveline(self, l):
1295 def issaveline(self, l):
1296 if l.name == '.hg.patches.save.line':
1296 if l.name == '.hg.patches.save.line':
1297 return True
1297 return True
1298
1298
1299 def qrepo(self, create=False):
1299 def qrepo(self, create=False):
1300 if create or os.path.isdir(self.join(".hg")):
1300 if create or os.path.isdir(self.join(".hg")):
1301 return hg.repository(self.ui, path=self.path, create=create)
1301 return hg.repository(self.ui, path=self.path, create=create)
1302
1302
1303 def restore(self, repo, rev, delete=None, qupdate=None):
1303 def restore(self, repo, rev, delete=None, qupdate=None):
1304 c = repo.changelog.read(rev)
1304 c = repo.changelog.read(rev)
1305 desc = c[4].strip()
1305 desc = c[4].strip()
1306 lines = desc.splitlines()
1306 lines = desc.splitlines()
1307 i = 0
1307 i = 0
1308 datastart = None
1308 datastart = None
1309 series = []
1309 series = []
1310 applied = []
1310 applied = []
1311 qpp = None
1311 qpp = None
1312 for i in xrange(0, len(lines)):
1312 for i in xrange(0, len(lines)):
1313 if lines[i] == 'Patch Data:':
1313 if lines[i] == 'Patch Data:':
1314 datastart = i + 1
1314 datastart = i + 1
1315 elif lines[i].startswith('Dirstate:'):
1315 elif lines[i].startswith('Dirstate:'):
1316 l = lines[i].rstrip()
1316 l = lines[i].rstrip()
1317 l = l[10:].split(' ')
1317 l = l[10:].split(' ')
1318 qpp = [ bin(x) for x in l ]
1318 qpp = [ bin(x) for x in l ]
1319 elif datastart != None:
1319 elif datastart != None:
1320 l = lines[i].rstrip()
1320 l = lines[i].rstrip()
1321 se = statusentry(l)
1321 se = statusentry(l)
1322 file_ = se.name
1322 file_ = se.name
1323 if se.rev:
1323 if se.rev:
1324 applied.append(se)
1324 applied.append(se)
1325 else:
1325 else:
1326 series.append(file_)
1326 series.append(file_)
1327 if datastart == None:
1327 if datastart == None:
1328 self.ui.warn(_("No saved patch data found\n"))
1328 self.ui.warn(_("No saved patch data found\n"))
1329 return 1
1329 return 1
1330 self.ui.warn(_("restoring status: %s\n") % lines[0])
1330 self.ui.warn(_("restoring status: %s\n") % lines[0])
1331 self.full_series = series
1331 self.full_series = series
1332 self.applied = applied
1332 self.applied = applied
1333 self.parse_series()
1333 self.parse_series()
1334 self.series_dirty = 1
1334 self.series_dirty = 1
1335 self.applied_dirty = 1
1335 self.applied_dirty = 1
1336 heads = repo.changelog.heads()
1336 heads = repo.changelog.heads()
1337 if delete:
1337 if delete:
1338 if rev not in heads:
1338 if rev not in heads:
1339 self.ui.warn(_("save entry has children, leaving it alone\n"))
1339 self.ui.warn(_("save entry has children, leaving it alone\n"))
1340 else:
1340 else:
1341 self.ui.warn(_("removing save entry %s\n") % short(rev))
1341 self.ui.warn(_("removing save entry %s\n") % short(rev))
1342 pp = repo.dirstate.parents()
1342 pp = repo.dirstate.parents()
1343 if rev in pp:
1343 if rev in pp:
1344 update = True
1344 update = True
1345 else:
1345 else:
1346 update = False
1346 update = False
1347 self.strip(repo, rev, update=update, backup='strip')
1347 self.strip(repo, rev, update=update, backup='strip')
1348 if qpp:
1348 if qpp:
1349 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1349 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1350 (short(qpp[0]), short(qpp[1])))
1350 (short(qpp[0]), short(qpp[1])))
1351 if qupdate:
1351 if qupdate:
1352 self.ui.status(_("queue directory updating\n"))
1352 self.ui.status(_("queue directory updating\n"))
1353 r = self.qrepo()
1353 r = self.qrepo()
1354 if not r:
1354 if not r:
1355 self.ui.warn(_("Unable to load queue repository\n"))
1355 self.ui.warn(_("Unable to load queue repository\n"))
1356 return 1
1356 return 1
1357 hg.clean(r, qpp[0])
1357 hg.clean(r, qpp[0])
1358
1358
1359 def save(self, repo, msg=None):
1359 def save(self, repo, msg=None):
1360 if len(self.applied) == 0:
1360 if len(self.applied) == 0:
1361 self.ui.warn(_("save: no patches applied, exiting\n"))
1361 self.ui.warn(_("save: no patches applied, exiting\n"))
1362 return 1
1362 return 1
1363 if self.issaveline(self.applied[-1]):
1363 if self.issaveline(self.applied[-1]):
1364 self.ui.warn(_("status is already saved\n"))
1364 self.ui.warn(_("status is already saved\n"))
1365 return 1
1365 return 1
1366
1366
1367 ar = [ ':' + x for x in self.full_series ]
1367 ar = [ ':' + x for x in self.full_series ]
1368 if not msg:
1368 if not msg:
1369 msg = _("hg patches saved state")
1369 msg = _("hg patches saved state")
1370 else:
1370 else:
1371 msg = "hg patches: " + msg.rstrip('\r\n')
1371 msg = "hg patches: " + msg.rstrip('\r\n')
1372 r = self.qrepo()
1372 r = self.qrepo()
1373 if r:
1373 if r:
1374 pp = r.dirstate.parents()
1374 pp = r.dirstate.parents()
1375 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1375 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1376 msg += "\n\nPatch Data:\n"
1376 msg += "\n\nPatch Data:\n"
1377 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1377 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1378 "\n".join(ar) + '\n' or "")
1378 "\n".join(ar) + '\n' or "")
1379 n = repo.commit(None, text, user=None, force=1)
1379 n = repo.commit(None, text, user=None, force=1)
1380 if not n:
1380 if not n:
1381 self.ui.warn(_("repo commit failed\n"))
1381 self.ui.warn(_("repo commit failed\n"))
1382 return 1
1382 return 1
1383 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1383 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1384 self.applied_dirty = 1
1384 self.applied_dirty = 1
1385 self.removeundo(repo)
1385 self.removeundo(repo)
1386
1386
1387 def full_series_end(self):
1387 def full_series_end(self):
1388 if len(self.applied) > 0:
1388 if len(self.applied) > 0:
1389 p = self.applied[-1].name
1389 p = self.applied[-1].name
1390 end = self.find_series(p)
1390 end = self.find_series(p)
1391 if end == None:
1391 if end == None:
1392 return len(self.full_series)
1392 return len(self.full_series)
1393 return end + 1
1393 return end + 1
1394 return 0
1394 return 0
1395
1395
1396 def series_end(self, all_patches=False):
1396 def series_end(self, all_patches=False):
1397 """If all_patches is False, return the index of the next pushable patch
1397 """If all_patches is False, return the index of the next pushable patch
1398 in the series, or the series length. If all_patches is True, return the
1398 in the series, or the series length. If all_patches is True, return the
1399 index of the first patch past the last applied one.
1399 index of the first patch past the last applied one.
1400 """
1400 """
1401 end = 0
1401 end = 0
1402 def next(start):
1402 def next(start):
1403 if all_patches:
1403 if all_patches:
1404 return start
1404 return start
1405 i = start
1405 i = start
1406 while i < len(self.series):
1406 while i < len(self.series):
1407 p, reason = self.pushable(i)
1407 p, reason = self.pushable(i)
1408 if p:
1408 if p:
1409 break
1409 break
1410 self.explain_pushable(i)
1410 self.explain_pushable(i)
1411 i += 1
1411 i += 1
1412 return i
1412 return i
1413 if len(self.applied) > 0:
1413 if len(self.applied) > 0:
1414 p = self.applied[-1].name
1414 p = self.applied[-1].name
1415 try:
1415 try:
1416 end = self.series.index(p)
1416 end = self.series.index(p)
1417 except ValueError:
1417 except ValueError:
1418 return 0
1418 return 0
1419 return next(end + 1)
1419 return next(end + 1)
1420 return next(end)
1420 return next(end)
1421
1421
1422 def appliedname(self, index):
1422 def appliedname(self, index):
1423 pname = self.applied[index].name
1423 pname = self.applied[index].name
1424 if not self.ui.verbose:
1424 if not self.ui.verbose:
1425 p = pname
1425 p = pname
1426 else:
1426 else:
1427 p = str(self.series.index(pname)) + " " + pname
1427 p = str(self.series.index(pname)) + " " + pname
1428 return p
1428 return p
1429
1429
1430 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1430 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1431 force=None, git=False):
1431 force=None, git=False):
1432 def checkseries(patchname):
1432 def checkseries(patchname):
1433 if patchname in self.series:
1433 if patchname in self.series:
1434 raise util.Abort(_('patch %s is already in the series file')
1434 raise util.Abort(_('patch %s is already in the series file')
1435 % patchname)
1435 % patchname)
1436 def checkfile(patchname):
1436 def checkfile(patchname):
1437 if not force and os.path.exists(self.join(patchname)):
1437 if not force and os.path.exists(self.join(patchname)):
1438 raise util.Abort(_('patch "%s" already exists')
1438 raise util.Abort(_('patch "%s" already exists')
1439 % patchname)
1439 % patchname)
1440
1440
1441 if rev:
1441 if rev:
1442 if files:
1442 if files:
1443 raise util.Abort(_('option "-r" not valid when importing '
1443 raise util.Abort(_('option "-r" not valid when importing '
1444 'files'))
1444 'files'))
1445 rev = cmdutil.revrange(repo, rev)
1445 rev = cmdutil.revrange(repo, rev)
1446 rev.sort(lambda x, y: cmp(y, x))
1446 rev.sort(lambda x, y: cmp(y, x))
1447 if (len(files) > 1 or len(rev) > 1) and patchname:
1447 if (len(files) > 1 or len(rev) > 1) and patchname:
1448 raise util.Abort(_('option "-n" not valid when importing multiple '
1448 raise util.Abort(_('option "-n" not valid when importing multiple '
1449 'patches'))
1449 'patches'))
1450 i = 0
1450 i = 0
1451 added = []
1451 added = []
1452 if rev:
1452 if rev:
1453 # If mq patches are applied, we can only import revisions
1453 # If mq patches are applied, we can only import revisions
1454 # that form a linear path to qbase.
1454 # that form a linear path to qbase.
1455 # Otherwise, they should form a linear path to a head.
1455 # Otherwise, they should form a linear path to a head.
1456 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1456 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1457 if len(heads) > 1:
1457 if len(heads) > 1:
1458 raise util.Abort(_('revision %d is the root of more than one '
1458 raise util.Abort(_('revision %d is the root of more than one '
1459 'branch') % rev[-1])
1459 'branch') % rev[-1])
1460 if self.applied:
1460 if self.applied:
1461 base = revlog.hex(repo.changelog.node(rev[0]))
1461 base = revlog.hex(repo.changelog.node(rev[0]))
1462 if base in [n.rev for n in self.applied]:
1462 if base in [n.rev for n in self.applied]:
1463 raise util.Abort(_('revision %d is already managed')
1463 raise util.Abort(_('revision %d is already managed')
1464 % rev[0])
1464 % rev[0])
1465 if heads != [revlog.bin(self.applied[-1].rev)]:
1465 if heads != [revlog.bin(self.applied[-1].rev)]:
1466 raise util.Abort(_('revision %d is not the parent of '
1466 raise util.Abort(_('revision %d is not the parent of '
1467 'the queue') % rev[0])
1467 'the queue') % rev[0])
1468 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1468 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1469 lastparent = repo.changelog.parentrevs(base)[0]
1469 lastparent = repo.changelog.parentrevs(base)[0]
1470 else:
1470 else:
1471 if heads != [repo.changelog.node(rev[0])]:
1471 if heads != [repo.changelog.node(rev[0])]:
1472 raise util.Abort(_('revision %d has unmanaged children')
1472 raise util.Abort(_('revision %d has unmanaged children')
1473 % rev[0])
1473 % rev[0])
1474 lastparent = None
1474 lastparent = None
1475
1475
1476 if git:
1476 if git:
1477 self.diffopts().git = True
1477 self.diffopts().git = True
1478
1478
1479 for r in rev:
1479 for r in rev:
1480 p1, p2 = repo.changelog.parentrevs(r)
1480 p1, p2 = repo.changelog.parentrevs(r)
1481 n = repo.changelog.node(r)
1481 n = repo.changelog.node(r)
1482 if p2 != revlog.nullrev:
1482 if p2 != revlog.nullrev:
1483 raise util.Abort(_('cannot import merge revision %d') % r)
1483 raise util.Abort(_('cannot import merge revision %d') % r)
1484 if lastparent and lastparent != r:
1484 if lastparent and lastparent != r:
1485 raise util.Abort(_('revision %d is not the parent of %d')
1485 raise util.Abort(_('revision %d is not the parent of %d')
1486 % (r, lastparent))
1486 % (r, lastparent))
1487 lastparent = p1
1487 lastparent = p1
1488
1488
1489 if not patchname:
1489 if not patchname:
1490 patchname = normname('%d.diff' % r)
1490 patchname = normname('%d.diff' % r)
1491 self.check_reserved_name(patchname)
1491 self.check_reserved_name(patchname)
1492 checkseries(patchname)
1492 checkseries(patchname)
1493 checkfile(patchname)
1493 checkfile(patchname)
1494 self.full_series.insert(0, patchname)
1494 self.full_series.insert(0, patchname)
1495
1495
1496 patchf = self.opener(patchname, "w")
1496 patchf = self.opener(patchname, "w")
1497 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1497 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1498 patchf.close()
1498 patchf.close()
1499
1499
1500 se = statusentry(revlog.hex(n), patchname)
1500 se = statusentry(revlog.hex(n), patchname)
1501 self.applied.insert(0, se)
1501 self.applied.insert(0, se)
1502
1502
1503 added.append(patchname)
1503 added.append(patchname)
1504 patchname = None
1504 patchname = None
1505 self.parse_series()
1505 self.parse_series()
1506 self.applied_dirty = 1
1506 self.applied_dirty = 1
1507
1507
1508 for filename in files:
1508 for filename in files:
1509 if existing:
1509 if existing:
1510 if filename == '-':
1510 if filename == '-':
1511 raise util.Abort(_('-e is incompatible with import from -'))
1511 raise util.Abort(_('-e is incompatible with import from -'))
1512 if not patchname:
1512 if not patchname:
1513 patchname = normname(filename)
1513 patchname = normname(filename)
1514 self.check_reserved_name(patchname)
1514 self.check_reserved_name(patchname)
1515 if not os.path.isfile(self.join(patchname)):
1515 if not os.path.isfile(self.join(patchname)):
1516 raise util.Abort(_("patch %s does not exist") % patchname)
1516 raise util.Abort(_("patch %s does not exist") % patchname)
1517 else:
1517 else:
1518 try:
1518 try:
1519 if filename == '-':
1519 if filename == '-':
1520 if not patchname:
1520 if not patchname:
1521 raise util.Abort(_('need --name to import a patch from -'))
1521 raise util.Abort(_('need --name to import a patch from -'))
1522 text = sys.stdin.read()
1522 text = sys.stdin.read()
1523 else:
1523 else:
1524 if os.path.exists(filename):
1524 if os.path.exists(filename):
1525 text = file(filename, 'rb').read()
1525 text = file(filename, 'rb').read()
1526 else:
1526 else:
1527 text = urllib.urlopen(filename).read()
1527 text = urllib.urlopen(filename).read()
1528 except IOError:
1528 except IOError:
1529 raise util.Abort(_("unable to read %s") % filename)
1529 raise util.Abort(_("unable to read %s") % filename)
1530 if not patchname:
1530 if not patchname:
1531 patchname = normname(os.path.basename(filename))
1531 patchname = normname(os.path.basename(filename))
1532 self.check_reserved_name(patchname)
1532 self.check_reserved_name(patchname)
1533 checkfile(patchname)
1533 checkfile(patchname)
1534 patchf = self.opener(patchname, "w")
1534 patchf = self.opener(patchname, "w")
1535 patchf.write(text)
1535 patchf.write(text)
1536 if not force:
1536 if not force:
1537 checkseries(patchname)
1537 checkseries(patchname)
1538 if patchname not in self.series:
1538 if patchname not in self.series:
1539 index = self.full_series_end() + i
1539 index = self.full_series_end() + i
1540 self.full_series[index:index] = [patchname]
1540 self.full_series[index:index] = [patchname]
1541 self.parse_series()
1541 self.parse_series()
1542 self.ui.warn("adding %s to series file\n" % patchname)
1542 self.ui.warn("adding %s to series file\n" % patchname)
1543 i += 1
1543 i += 1
1544 added.append(patchname)
1544 added.append(patchname)
1545 patchname = None
1545 patchname = None
1546 self.series_dirty = 1
1546 self.series_dirty = 1
1547 qrepo = self.qrepo()
1547 qrepo = self.qrepo()
1548 if qrepo:
1548 if qrepo:
1549 qrepo.add(added)
1549 qrepo.add(added)
1550
1550
1551 def delete(ui, repo, *patches, **opts):
1551 def delete(ui, repo, *patches, **opts):
1552 """remove patches from queue
1552 """remove patches from queue
1553
1553
1554 The patches must not be applied, unless they are arguments to
1554 The patches must not be applied, unless they are arguments to
1555 the --rev parameter. At least one patch or revision is required.
1555 the --rev parameter. At least one patch or revision is required.
1556
1556
1557 With --rev, mq will stop managing the named revisions (converting
1557 With --rev, mq will stop managing the named revisions (converting
1558 them to regular mercurial changesets). The qfinish command should be
1558 them to regular mercurial changesets). The qfinish command should be
1559 used as an alternative for qdel -r, as the latter option is deprecated.
1559 used as an alternative for qdel -r, as the latter option is deprecated.
1560
1560
1561 With --keep, the patch files are preserved in the patch directory."""
1561 With --keep, the patch files are preserved in the patch directory."""
1562 q = repo.mq
1562 q = repo.mq
1563 q.delete(repo, patches, opts)
1563 q.delete(repo, patches, opts)
1564 q.save_dirty()
1564 q.save_dirty()
1565 return 0
1565 return 0
1566
1566
1567 def applied(ui, repo, patch=None, **opts):
1567 def applied(ui, repo, patch=None, **opts):
1568 """print the patches already applied"""
1568 """print the patches already applied"""
1569 q = repo.mq
1569 q = repo.mq
1570 if patch:
1570 if patch:
1571 if patch not in q.series:
1571 if patch not in q.series:
1572 raise util.Abort(_("patch %s is not in series file") % patch)
1572 raise util.Abort(_("patch %s is not in series file") % patch)
1573 end = q.series.index(patch) + 1
1573 end = q.series.index(patch) + 1
1574 else:
1574 else:
1575 end = q.series_end(True)
1575 end = q.series_end(True)
1576 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1576 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1577
1577
1578 def unapplied(ui, repo, patch=None, **opts):
1578 def unapplied(ui, repo, patch=None, **opts):
1579 """print the patches not yet applied"""
1579 """print the patches not yet applied"""
1580 q = repo.mq
1580 q = repo.mq
1581 if patch:
1581 if patch:
1582 if patch not in q.series:
1582 if patch not in q.series:
1583 raise util.Abort(_("patch %s is not in series file") % patch)
1583 raise util.Abort(_("patch %s is not in series file") % patch)
1584 start = q.series.index(patch) + 1
1584 start = q.series.index(patch) + 1
1585 else:
1585 else:
1586 start = q.series_end(True)
1586 start = q.series_end(True)
1587 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1587 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1588
1588
1589 def qimport(ui, repo, *filename, **opts):
1589 def qimport(ui, repo, *filename, **opts):
1590 """import a patch
1590 """import a patch
1591
1591
1592 The patch is inserted into the series after the last applied patch.
1592 The patch is inserted into the series after the last applied patch.
1593 If no patches have been applied, qimport prepends the patch
1593 If no patches have been applied, qimport prepends the patch
1594 to the series.
1594 to the series.
1595
1595
1596 The patch will have the same name as its source file unless you
1596 The patch will have the same name as its source file unless you
1597 give it a new one with --name.
1597 give it a new one with --name.
1598
1598
1599 You can register an existing patch inside the patch directory
1599 You can register an existing patch inside the patch directory
1600 with the --existing flag.
1600 with the --existing flag.
1601
1601
1602 With --force, an existing patch of the same name will be overwritten.
1602 With --force, an existing patch of the same name will be overwritten.
1603
1603
1604 An existing changeset may be placed under mq control with --rev
1604 An existing changeset may be placed under mq control with --rev
1605 (e.g. qimport --rev tip -n patch will place tip under mq control).
1605 (e.g. qimport --rev tip -n patch will place tip under mq control).
1606 With --git, patches imported with --rev will use the git diff
1606 With --git, patches imported with --rev will use the git diff
1607 format.
1607 format.
1608 """
1608 """
1609 q = repo.mq
1609 q = repo.mq
1610 q.qimport(repo, filename, patchname=opts['name'],
1610 q.qimport(repo, filename, patchname=opts['name'],
1611 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1611 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1612 git=opts['git'])
1612 git=opts['git'])
1613 q.save_dirty()
1613 q.save_dirty()
1614 return 0
1614 return 0
1615
1615
1616 def init(ui, repo, **opts):
1616 def init(ui, repo, **opts):
1617 """init a new queue repository
1617 """init a new queue repository
1618
1618
1619 The queue repository is unversioned by default. If -c is
1619 The queue repository is unversioned by default. If -c is
1620 specified, qinit will create a separate nested repository
1620 specified, qinit will create a separate nested repository
1621 for patches (qinit -c may also be run later to convert
1621 for patches (qinit -c may also be run later to convert
1622 an unversioned patch repository into a versioned one).
1622 an unversioned patch repository into a versioned one).
1623 You can use qcommit to commit changes to this queue repository."""
1623 You can use qcommit to commit changes to this queue repository."""
1624 q = repo.mq
1624 q = repo.mq
1625 r = q.init(repo, create=opts['create_repo'])
1625 r = q.init(repo, create=opts['create_repo'])
1626 q.save_dirty()
1626 q.save_dirty()
1627 if r:
1627 if r:
1628 if not os.path.exists(r.wjoin('.hgignore')):
1628 if not os.path.exists(r.wjoin('.hgignore')):
1629 fp = r.wopener('.hgignore', 'w')
1629 fp = r.wopener('.hgignore', 'w')
1630 fp.write('^\\.hg\n')
1630 fp.write('^\\.hg\n')
1631 fp.write('^\\.mq\n')
1631 fp.write('^\\.mq\n')
1632 fp.write('syntax: glob\n')
1632 fp.write('syntax: glob\n')
1633 fp.write('status\n')
1633 fp.write('status\n')
1634 fp.write('guards\n')
1634 fp.write('guards\n')
1635 fp.close()
1635 fp.close()
1636 if not os.path.exists(r.wjoin('series')):
1636 if not os.path.exists(r.wjoin('series')):
1637 r.wopener('series', 'w').close()
1637 r.wopener('series', 'w').close()
1638 r.add(['.hgignore', 'series'])
1638 r.add(['.hgignore', 'series'])
1639 commands.add(ui, r)
1639 commands.add(ui, r)
1640 return 0
1640 return 0
1641
1641
1642 def clone(ui, source, dest=None, **opts):
1642 def clone(ui, source, dest=None, **opts):
1643 '''clone main and patch repository at same time
1643 '''clone main and patch repository at same time
1644
1644
1645 If source is local, destination will have no patches applied. If
1645 If source is local, destination will have no patches applied. If
1646 source is remote, this command can not check if patches are
1646 source is remote, this command can not check if patches are
1647 applied in source, so cannot guarantee that patches are not
1647 applied in source, so cannot guarantee that patches are not
1648 applied in destination. If you clone remote repository, be sure
1648 applied in destination. If you clone remote repository, be sure
1649 before that it has no patches applied.
1649 before that it has no patches applied.
1650
1650
1651 Source patch repository is looked for in <src>/.hg/patches by
1651 Source patch repository is looked for in <src>/.hg/patches by
1652 default. Use -p <url> to change.
1652 default. Use -p <url> to change.
1653
1653
1654 The patch directory must be a nested mercurial repository, as
1654 The patch directory must be a nested mercurial repository, as
1655 would be created by qinit -c.
1655 would be created by qinit -c.
1656 '''
1656 '''
1657 def patchdir(repo):
1657 def patchdir(repo):
1658 url = repo.url()
1658 url = repo.url()
1659 if url.endswith('/'):
1659 if url.endswith('/'):
1660 url = url[:-1]
1660 url = url[:-1]
1661 return url + '/.hg/patches'
1661 return url + '/.hg/patches'
1662 cmdutil.setremoteconfig(ui, opts)
1662 cmdutil.setremoteconfig(ui, opts)
1663 if dest is None:
1663 if dest is None:
1664 dest = hg.defaultdest(source)
1664 dest = hg.defaultdest(source)
1665 sr = hg.repository(ui, ui.expandpath(source))
1665 sr = hg.repository(ui, ui.expandpath(source))
1666 patchespath = opts['patches'] or patchdir(sr)
1666 patchespath = opts['patches'] or patchdir(sr)
1667 try:
1667 try:
1668 pr = hg.repository(ui, patchespath)
1668 pr = hg.repository(ui, patchespath)
1669 except RepoError:
1669 except RepoError:
1670 raise util.Abort(_('versioned patch repository not found'
1670 raise util.Abort(_('versioned patch repository not found'
1671 ' (see qinit -c)'))
1671 ' (see qinit -c)'))
1672 qbase, destrev = None, None
1672 qbase, destrev = None, None
1673 if sr.local():
1673 if sr.local():
1674 if sr.mq.applied:
1674 if sr.mq.applied:
1675 qbase = revlog.bin(sr.mq.applied[0].rev)
1675 qbase = revlog.bin(sr.mq.applied[0].rev)
1676 if not hg.islocal(dest):
1676 if not hg.islocal(dest):
1677 heads = dict.fromkeys(sr.heads())
1677 heads = dict.fromkeys(sr.heads())
1678 for h in sr.heads(qbase):
1678 for h in sr.heads(qbase):
1679 del heads[h]
1679 del heads[h]
1680 destrev = heads.keys()
1680 destrev = heads.keys()
1681 destrev.append(sr.changelog.parents(qbase)[0])
1681 destrev.append(sr.changelog.parents(qbase)[0])
1682 elif sr.capable('lookup'):
1682 elif sr.capable('lookup'):
1683 try:
1683 try:
1684 qbase = sr.lookup('qbase')
1684 qbase = sr.lookup('qbase')
1685 except RepoError:
1685 except RepoError:
1686 pass
1686 pass
1687 ui.note(_('cloning main repo\n'))
1687 ui.note(_('cloning main repo\n'))
1688 sr, dr = hg.clone(ui, sr.url(), dest,
1688 sr, dr = hg.clone(ui, sr.url(), dest,
1689 pull=opts['pull'],
1689 pull=opts['pull'],
1690 rev=destrev,
1690 rev=destrev,
1691 update=False,
1691 update=False,
1692 stream=opts['uncompressed'])
1692 stream=opts['uncompressed'])
1693 ui.note(_('cloning patch repo\n'))
1693 ui.note(_('cloning patch repo\n'))
1694 spr, dpr = hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1694 spr, dpr = hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1695 pull=opts['pull'], update=not opts['noupdate'],
1695 pull=opts['pull'], update=not opts['noupdate'],
1696 stream=opts['uncompressed'])
1696 stream=opts['uncompressed'])
1697 if dr.local():
1697 if dr.local():
1698 if qbase:
1698 if qbase:
1699 ui.note(_('stripping applied patches from destination repo\n'))
1699 ui.note(_('stripping applied patches from destination repo\n'))
1700 dr.mq.strip(dr, qbase, update=False, backup=None)
1700 dr.mq.strip(dr, qbase, update=False, backup=None)
1701 if not opts['noupdate']:
1701 if not opts['noupdate']:
1702 ui.note(_('updating destination repo\n'))
1702 ui.note(_('updating destination repo\n'))
1703 hg.update(dr, dr.changelog.tip())
1703 hg.update(dr, dr.changelog.tip())
1704
1704
1705 def commit(ui, repo, *pats, **opts):
1705 def commit(ui, repo, *pats, **opts):
1706 """commit changes in the queue repository"""
1706 """commit changes in the queue repository"""
1707 q = repo.mq
1707 q = repo.mq
1708 r = q.qrepo()
1708 r = q.qrepo()
1709 if not r: raise util.Abort('no queue repository')
1709 if not r: raise util.Abort('no queue repository')
1710 commands.commit(r.ui, r, *pats, **opts)
1710 commands.commit(r.ui, r, *pats, **opts)
1711
1711
1712 def series(ui, repo, **opts):
1712 def series(ui, repo, **opts):
1713 """print the entire series file"""
1713 """print the entire series file"""
1714 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1714 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1715 return 0
1715 return 0
1716
1716
1717 def top(ui, repo, **opts):
1717 def top(ui, repo, **opts):
1718 """print the name of the current patch"""
1718 """print the name of the current patch"""
1719 q = repo.mq
1719 q = repo.mq
1720 t = q.applied and q.series_end(True) or 0
1720 t = q.applied and q.series_end(True) or 0
1721 if t:
1721 if t:
1722 return q.qseries(repo, start=t-1, length=1, status='A',
1722 return q.qseries(repo, start=t-1, length=1, status='A',
1723 summary=opts.get('summary'))
1723 summary=opts.get('summary'))
1724 else:
1724 else:
1725 ui.write("No patches applied\n")
1725 ui.write("No patches applied\n")
1726 return 1
1726 return 1
1727
1727
1728 def next(ui, repo, **opts):
1728 def next(ui, repo, **opts):
1729 """print the name of the next patch"""
1729 """print the name of the next patch"""
1730 q = repo.mq
1730 q = repo.mq
1731 end = q.series_end()
1731 end = q.series_end()
1732 if end == len(q.series):
1732 if end == len(q.series):
1733 ui.write("All patches applied\n")
1733 ui.write("All patches applied\n")
1734 return 1
1734 return 1
1735 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1735 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1736
1736
1737 def prev(ui, repo, **opts):
1737 def prev(ui, repo, **opts):
1738 """print the name of the previous patch"""
1738 """print the name of the previous patch"""
1739 q = repo.mq
1739 q = repo.mq
1740 l = len(q.applied)
1740 l = len(q.applied)
1741 if l == 1:
1741 if l == 1:
1742 ui.write("Only one patch applied\n")
1742 ui.write("Only one patch applied\n")
1743 return 1
1743 return 1
1744 if not l:
1744 if not l:
1745 ui.write("No patches applied\n")
1745 ui.write("No patches applied\n")
1746 return 1
1746 return 1
1747 return q.qseries(repo, start=l-2, length=1, status='A',
1747 return q.qseries(repo, start=l-2, length=1, status='A',
1748 summary=opts.get('summary'))
1748 summary=opts.get('summary'))
1749
1749
1750 def setupheaderopts(ui, opts):
1750 def setupheaderopts(ui, opts):
1751 def do(opt,val):
1751 def do(opt,val):
1752 if not opts[opt] and opts['current' + opt]:
1752 if not opts[opt] and opts['current' + opt]:
1753 opts[opt] = val
1753 opts[opt] = val
1754 do('user', ui.username())
1754 do('user', ui.username())
1755 do('date', "%d %d" % util.makedate())
1755 do('date', "%d %d" % util.makedate())
1756
1756
1757 def new(ui, repo, patch, *args, **opts):
1757 def new(ui, repo, patch, *args, **opts):
1758 """create a new patch
1758 """create a new patch
1759
1759
1760 qnew creates a new patch on top of the currently-applied patch
1760 qnew creates a new patch on top of the currently-applied patch
1761 (if any). It will refuse to run if there are any outstanding
1761 (if any). It will refuse to run if there are any outstanding
1762 changes unless -f is specified, in which case the patch will
1762 changes unless -f is specified, in which case the patch will
1763 be initialised with them. You may also use -I, -X, and/or a list of
1763 be initialised with them. You may also use -I, -X, and/or a list of
1764 files after the patch name to add only changes to matching files
1764 files after the patch name to add only changes to matching files
1765 to the new patch, leaving the rest as uncommitted modifications.
1765 to the new patch, leaving the rest as uncommitted modifications.
1766
1766
1767 -e, -m or -l set the patch header as well as the commit message.
1767 -e, -m or -l set the patch header as well as the commit message.
1768 If none is specified, the patch header is empty and the
1768 If none is specified, the patch header is empty and the
1769 commit message is '[mq]: PATCH'"""
1769 commit message is '[mq]: PATCH'"""
1770 msg = cmdutil.logmessage(opts)
1770 msg = cmdutil.logmessage(opts)
1771 def getmsg(): return ui.edit(msg, ui.username())
1771 def getmsg(): return ui.edit(msg, ui.username())
1772 q = repo.mq
1772 q = repo.mq
1773 opts['msg'] = msg
1773 opts['msg'] = msg
1774 if opts.get('edit'):
1774 if opts.get('edit'):
1775 opts['msg'] = getmsg
1775 opts['msg'] = getmsg
1776 else:
1776 else:
1777 opts['msg'] = msg
1777 opts['msg'] = msg
1778 setupheaderopts(ui, opts)
1778 setupheaderopts(ui, opts)
1779 q.new(repo, patch, *args, **opts)
1779 q.new(repo, patch, *args, **opts)
1780 q.save_dirty()
1780 q.save_dirty()
1781 return 0
1781 return 0
1782
1782
1783 def refresh(ui, repo, *pats, **opts):
1783 def refresh(ui, repo, *pats, **opts):
1784 """update the current patch
1784 """update the current patch
1785
1785
1786 If any file patterns are provided, the refreshed patch will contain only
1786 If any file patterns are provided, the refreshed patch will contain only
1787 the modifications that match those patterns; the remaining modifications
1787 the modifications that match those patterns; the remaining modifications
1788 will remain in the working directory.
1788 will remain in the working directory.
1789
1789
1790 If --short is specified, files currently included in the patch will
1790 If --short is specified, files currently included in the patch will
1791 be refreshed just like matched files and remain in the patch.
1791 be refreshed just like matched files and remain in the patch.
1792
1792
1793 hg add/remove/copy/rename work as usual, though you might want to use
1793 hg add/remove/copy/rename work as usual, though you might want to use
1794 git-style patches (--git or [diff] git=1) to track copies and renames.
1794 git-style patches (--git or [diff] git=1) to track copies and renames.
1795 """
1795 """
1796 q = repo.mq
1796 q = repo.mq
1797 message = cmdutil.logmessage(opts)
1797 message = cmdutil.logmessage(opts)
1798 if opts['edit']:
1798 if opts['edit']:
1799 if not q.applied:
1799 if not q.applied:
1800 ui.write(_("No patches applied\n"))
1800 ui.write(_("No patches applied\n"))
1801 return 1
1801 return 1
1802 if message:
1802 if message:
1803 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1803 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1804 patch = q.applied[-1].name
1804 patch = q.applied[-1].name
1805 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1805 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1806 message = ui.edit('\n'.join(message), user or ui.username())
1806 message = ui.edit('\n'.join(message), user or ui.username())
1807 setupheaderopts(ui, opts)
1807 setupheaderopts(ui, opts)
1808 ret = q.refresh(repo, pats, msg=message, **opts)
1808 ret = q.refresh(repo, pats, msg=message, **opts)
1809 q.save_dirty()
1809 q.save_dirty()
1810 return ret
1810 return ret
1811
1811
1812 def diff(ui, repo, *pats, **opts):
1812 def diff(ui, repo, *pats, **opts):
1813 """diff of the current patch and subsequent modifications
1813 """diff of the current patch and subsequent modifications
1814
1814
1815 Shows a diff which includes the current patch as well as any changes which
1815 Shows a diff which includes the current patch as well as any changes which
1816 have been made in the working directory since the last refresh (thus
1816 have been made in the working directory since the last refresh (thus
1817 showing what the current patch would become after a qrefresh).
1817 showing what the current patch would become after a qrefresh).
1818
1818
1819 Use 'hg diff' if you only want to see the changes made since the last
1819 Use 'hg diff' if you only want to see the changes made since the last
1820 qrefresh, or 'hg export qtip' if you want to see changes made by the
1820 qrefresh, or 'hg export qtip' if you want to see changes made by the
1821 current patch without including changes made since the qrefresh.
1821 current patch without including changes made since the qrefresh.
1822 """
1822 """
1823 repo.mq.diff(repo, pats, opts)
1823 repo.mq.diff(repo, pats, opts)
1824 return 0
1824 return 0
1825
1825
1826 def fold(ui, repo, *files, **opts):
1826 def fold(ui, repo, *files, **opts):
1827 """fold the named patches into the current patch
1827 """fold the named patches into the current patch
1828
1828
1829 Patches must not yet be applied. Each patch will be successively
1829 Patches must not yet be applied. Each patch will be successively
1830 applied to the current patch in the order given. If all the
1830 applied to the current patch in the order given. If all the
1831 patches apply successfully, the current patch will be refreshed
1831 patches apply successfully, the current patch will be refreshed
1832 with the new cumulative patch, and the folded patches will
1832 with the new cumulative patch, and the folded patches will
1833 be deleted. With -k/--keep, the folded patch files will not
1833 be deleted. With -k/--keep, the folded patch files will not
1834 be removed afterwards.
1834 be removed afterwards.
1835
1835
1836 The header for each folded patch will be concatenated with
1836 The header for each folded patch will be concatenated with
1837 the current patch header, separated by a line of '* * *'."""
1837 the current patch header, separated by a line of '* * *'."""
1838
1838
1839 q = repo.mq
1839 q = repo.mq
1840
1840
1841 if not files:
1841 if not files:
1842 raise util.Abort(_('qfold requires at least one patch name'))
1842 raise util.Abort(_('qfold requires at least one patch name'))
1843 if not q.check_toppatch(repo):
1843 if not q.check_toppatch(repo):
1844 raise util.Abort(_('No patches applied'))
1844 raise util.Abort(_('No patches applied'))
1845
1845
1846 message = cmdutil.logmessage(opts)
1846 message = cmdutil.logmessage(opts)
1847 if opts['edit']:
1847 if opts['edit']:
1848 if message:
1848 if message:
1849 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1849 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1850
1850
1851 parent = q.lookup('qtip')
1851 parent = q.lookup('qtip')
1852 patches = []
1852 patches = []
1853 messages = []
1853 messages = []
1854 for f in files:
1854 for f in files:
1855 p = q.lookup(f)
1855 p = q.lookup(f)
1856 if p in patches or p == parent:
1856 if p in patches or p == parent:
1857 ui.warn(_('Skipping already folded patch %s') % p)
1857 ui.warn(_('Skipping already folded patch %s') % p)
1858 if q.isapplied(p):
1858 if q.isapplied(p):
1859 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1859 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1860 patches.append(p)
1860 patches.append(p)
1861
1861
1862 for p in patches:
1862 for p in patches:
1863 if not message:
1863 if not message:
1864 messages.append(q.readheaders(p)[0])
1864 messages.append(q.readheaders(p)[0])
1865 pf = q.join(p)
1865 pf = q.join(p)
1866 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1866 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1867 if not patchsuccess:
1867 if not patchsuccess:
1868 raise util.Abort(_('Error folding patch %s') % p)
1868 raise util.Abort(_('Error folding patch %s') % p)
1869 patch.updatedir(ui, repo, files)
1869 patch.updatedir(ui, repo, files)
1870
1870
1871 if not message:
1871 if not message:
1872 message, comments, user = q.readheaders(parent)[0:3]
1872 message, comments, user = q.readheaders(parent)[0:3]
1873 for msg in messages:
1873 for msg in messages:
1874 message.append('* * *')
1874 message.append('* * *')
1875 message.extend(msg)
1875 message.extend(msg)
1876 message = '\n'.join(message)
1876 message = '\n'.join(message)
1877
1877
1878 if opts['edit']:
1878 if opts['edit']:
1879 message = ui.edit(message, user or ui.username())
1879 message = ui.edit(message, user or ui.username())
1880
1880
1881 q.refresh(repo, msg=message)
1881 q.refresh(repo, msg=message)
1882 q.delete(repo, patches, opts)
1882 q.delete(repo, patches, opts)
1883 q.save_dirty()
1883 q.save_dirty()
1884
1884
1885 def goto(ui, repo, patch, **opts):
1885 def goto(ui, repo, patch, **opts):
1886 '''push or pop patches until named patch is at top of stack'''
1886 '''push or pop patches until named patch is at top of stack'''
1887 q = repo.mq
1887 q = repo.mq
1888 patch = q.lookup(patch)
1888 patch = q.lookup(patch)
1889 if q.isapplied(patch):
1889 if q.isapplied(patch):
1890 ret = q.pop(repo, patch, force=opts['force'])
1890 ret = q.pop(repo, patch, force=opts['force'])
1891 else:
1891 else:
1892 ret = q.push(repo, patch, force=opts['force'])
1892 ret = q.push(repo, patch, force=opts['force'])
1893 q.save_dirty()
1893 q.save_dirty()
1894 return ret
1894 return ret
1895
1895
1896 def guard(ui, repo, *args, **opts):
1896 def guard(ui, repo, *args, **opts):
1897 '''set or print guards for a patch
1897 '''set or print guards for a patch
1898
1898
1899 Guards control whether a patch can be pushed. A patch with no
1899 Guards control whether a patch can be pushed. A patch with no
1900 guards is always pushed. A patch with a positive guard ("+foo") is
1900 guards is always pushed. A patch with a positive guard ("+foo") is
1901 pushed only if the qselect command has activated it. A patch with
1901 pushed only if the qselect command has activated it. A patch with
1902 a negative guard ("-foo") is never pushed if the qselect command
1902 a negative guard ("-foo") is never pushed if the qselect command
1903 has activated it.
1903 has activated it.
1904
1904
1905 With no arguments, print the currently active guards.
1905 With no arguments, print the currently active guards.
1906 With arguments, set guards for the named patch.
1906 With arguments, set guards for the named patch.
1907
1907
1908 To set a negative guard "-foo" on topmost patch ("--" is needed so
1908 To set a negative guard "-foo" on topmost patch ("--" is needed so
1909 hg will not interpret "-foo" as an option):
1909 hg will not interpret "-foo" as an option):
1910 hg qguard -- -foo
1910 hg qguard -- -foo
1911
1911
1912 To set guards on another patch:
1912 To set guards on another patch:
1913 hg qguard other.patch +2.6.17 -stable
1913 hg qguard other.patch +2.6.17 -stable
1914 '''
1914 '''
1915 def status(idx):
1915 def status(idx):
1916 guards = q.series_guards[idx] or ['unguarded']
1916 guards = q.series_guards[idx] or ['unguarded']
1917 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1917 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1918 q = repo.mq
1918 q = repo.mq
1919 patch = None
1919 patch = None
1920 args = list(args)
1920 args = list(args)
1921 if opts['list']:
1921 if opts['list']:
1922 if args or opts['none']:
1922 if args or opts['none']:
1923 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1923 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1924 for i in xrange(len(q.series)):
1924 for i in xrange(len(q.series)):
1925 status(i)
1925 status(i)
1926 return
1926 return
1927 if not args or args[0][0:1] in '-+':
1927 if not args or args[0][0:1] in '-+':
1928 if not q.applied:
1928 if not q.applied:
1929 raise util.Abort(_('no patches applied'))
1929 raise util.Abort(_('no patches applied'))
1930 patch = q.applied[-1].name
1930 patch = q.applied[-1].name
1931 if patch is None and args[0][0:1] not in '-+':
1931 if patch is None and args[0][0:1] not in '-+':
1932 patch = args.pop(0)
1932 patch = args.pop(0)
1933 if patch is None:
1933 if patch is None:
1934 raise util.Abort(_('no patch to work with'))
1934 raise util.Abort(_('no patch to work with'))
1935 if args or opts['none']:
1935 if args or opts['none']:
1936 idx = q.find_series(patch)
1936 idx = q.find_series(patch)
1937 if idx is None:
1937 if idx is None:
1938 raise util.Abort(_('no patch named %s') % patch)
1938 raise util.Abort(_('no patch named %s') % patch)
1939 q.set_guards(idx, args)
1939 q.set_guards(idx, args)
1940 q.save_dirty()
1940 q.save_dirty()
1941 else:
1941 else:
1942 status(q.series.index(q.lookup(patch)))
1942 status(q.series.index(q.lookup(patch)))
1943
1943
1944 def header(ui, repo, patch=None):
1944 def header(ui, repo, patch=None):
1945 """Print the header of the topmost or specified patch"""
1945 """Print the header of the topmost or specified patch"""
1946 q = repo.mq
1946 q = repo.mq
1947
1947
1948 if patch:
1948 if patch:
1949 patch = q.lookup(patch)
1949 patch = q.lookup(patch)
1950 else:
1950 else:
1951 if not q.applied:
1951 if not q.applied:
1952 ui.write('No patches applied\n')
1952 ui.write('No patches applied\n')
1953 return 1
1953 return 1
1954 patch = q.lookup('qtip')
1954 patch = q.lookup('qtip')
1955 message = repo.mq.readheaders(patch)[0]
1955 message = repo.mq.readheaders(patch)[0]
1956
1956
1957 ui.write('\n'.join(message) + '\n')
1957 ui.write('\n'.join(message) + '\n')
1958
1958
1959 def lastsavename(path):
1959 def lastsavename(path):
1960 (directory, base) = os.path.split(path)
1960 (directory, base) = os.path.split(path)
1961 names = os.listdir(directory)
1961 names = os.listdir(directory)
1962 namere = re.compile("%s.([0-9]+)" % base)
1962 namere = re.compile("%s.([0-9]+)" % base)
1963 maxindex = None
1963 maxindex = None
1964 maxname = None
1964 maxname = None
1965 for f in names:
1965 for f in names:
1966 m = namere.match(f)
1966 m = namere.match(f)
1967 if m:
1967 if m:
1968 index = int(m.group(1))
1968 index = int(m.group(1))
1969 if maxindex == None or index > maxindex:
1969 if maxindex == None or index > maxindex:
1970 maxindex = index
1970 maxindex = index
1971 maxname = f
1971 maxname = f
1972 if maxname:
1972 if maxname:
1973 return (os.path.join(directory, maxname), maxindex)
1973 return (os.path.join(directory, maxname), maxindex)
1974 return (None, None)
1974 return (None, None)
1975
1975
1976 def savename(path):
1976 def savename(path):
1977 (last, index) = lastsavename(path)
1977 (last, index) = lastsavename(path)
1978 if last is None:
1978 if last is None:
1979 index = 0
1979 index = 0
1980 newpath = path + ".%d" % (index + 1)
1980 newpath = path + ".%d" % (index + 1)
1981 return newpath
1981 return newpath
1982
1982
1983 def push(ui, repo, patch=None, **opts):
1983 def push(ui, repo, patch=None, **opts):
1984 """push the next patch onto the stack
1984 """push the next patch onto the stack
1985
1985
1986 When --force is applied, all local changes in patched files will be lost.
1986 When --force is applied, all local changes in patched files will be lost.
1987 """
1987 """
1988 q = repo.mq
1988 q = repo.mq
1989 mergeq = None
1989 mergeq = None
1990
1990
1991 if opts['all']:
1991 if opts['all']:
1992 if not q.series:
1992 if not q.series:
1993 ui.warn(_('no patches in series\n'))
1993 ui.warn(_('no patches in series\n'))
1994 return 0
1994 return 0
1995 patch = q.series[-1]
1995 patch = q.series[-1]
1996 if opts['merge']:
1996 if opts['merge']:
1997 if opts['name']:
1997 if opts['name']:
1998 newpath = repo.join(opts['name'])
1998 newpath = repo.join(opts['name'])
1999 else:
1999 else:
2000 newpath, i = lastsavename(q.path)
2000 newpath, i = lastsavename(q.path)
2001 if not newpath:
2001 if not newpath:
2002 ui.warn(_("no saved queues found, please use -n\n"))
2002 ui.warn(_("no saved queues found, please use -n\n"))
2003 return 1
2003 return 1
2004 mergeq = queue(ui, repo.join(""), newpath)
2004 mergeq = queue(ui, repo.join(""), newpath)
2005 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2005 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2006 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2006 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2007 mergeq=mergeq)
2007 mergeq=mergeq)
2008 return ret
2008 return ret
2009
2009
2010 def pop(ui, repo, patch=None, **opts):
2010 def pop(ui, repo, patch=None, **opts):
2011 """pop the current patch off the stack
2011 """pop the current patch off the stack
2012
2012
2013 By default, pops off the top of the patch stack. If given a patch name,
2013 By default, pops off the top of the patch stack. If given a patch name,
2014 keeps popping off patches until the named patch is at the top of the stack.
2014 keeps popping off patches until the named patch is at the top of the stack.
2015 """
2015 """
2016 localupdate = True
2016 localupdate = True
2017 if opts['name']:
2017 if opts['name']:
2018 q = queue(ui, repo.join(""), repo.join(opts['name']))
2018 q = queue(ui, repo.join(""), repo.join(opts['name']))
2019 ui.warn(_('using patch queue: %s\n') % q.path)
2019 ui.warn(_('using patch queue: %s\n') % q.path)
2020 localupdate = False
2020 localupdate = False
2021 else:
2021 else:
2022 q = repo.mq
2022 q = repo.mq
2023 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2023 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2024 all=opts['all'])
2024 all=opts['all'])
2025 q.save_dirty()
2025 q.save_dirty()
2026 return ret
2026 return ret
2027
2027
2028 def rename(ui, repo, patch, name=None, **opts):
2028 def rename(ui, repo, patch, name=None, **opts):
2029 """rename a patch
2029 """rename a patch
2030
2030
2031 With one argument, renames the current patch to PATCH1.
2031 With one argument, renames the current patch to PATCH1.
2032 With two arguments, renames PATCH1 to PATCH2."""
2032 With two arguments, renames PATCH1 to PATCH2."""
2033
2033
2034 q = repo.mq
2034 q = repo.mq
2035
2035
2036 if not name:
2036 if not name:
2037 name = patch
2037 name = patch
2038 patch = None
2038 patch = None
2039
2039
2040 if patch:
2040 if patch:
2041 patch = q.lookup(patch)
2041 patch = q.lookup(patch)
2042 else:
2042 else:
2043 if not q.applied:
2043 if not q.applied:
2044 ui.write(_('No patches applied\n'))
2044 ui.write(_('No patches applied\n'))
2045 return
2045 return
2046 patch = q.lookup('qtip')
2046 patch = q.lookup('qtip')
2047 absdest = q.join(name)
2047 absdest = q.join(name)
2048 if os.path.isdir(absdest):
2048 if os.path.isdir(absdest):
2049 name = normname(os.path.join(name, os.path.basename(patch)))
2049 name = normname(os.path.join(name, os.path.basename(patch)))
2050 absdest = q.join(name)
2050 absdest = q.join(name)
2051 if os.path.exists(absdest):
2051 if os.path.exists(absdest):
2052 raise util.Abort(_('%s already exists') % absdest)
2052 raise util.Abort(_('%s already exists') % absdest)
2053
2053
2054 if name in q.series:
2054 if name in q.series:
2055 raise util.Abort(_('A patch named %s already exists in the series file') % name)
2055 raise util.Abort(_('A patch named %s already exists in the series file') % name)
2056
2056
2057 if ui.verbose:
2057 if ui.verbose:
2058 ui.write('Renaming %s to %s\n' % (patch, name))
2058 ui.write('Renaming %s to %s\n' % (patch, name))
2059 i = q.find_series(patch)
2059 i = q.find_series(patch)
2060 guards = q.guard_re.findall(q.full_series[i])
2060 guards = q.guard_re.findall(q.full_series[i])
2061 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2061 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2062 q.parse_series()
2062 q.parse_series()
2063 q.series_dirty = 1
2063 q.series_dirty = 1
2064
2064
2065 info = q.isapplied(patch)
2065 info = q.isapplied(patch)
2066 if info:
2066 if info:
2067 q.applied[info[0]] = statusentry(info[1], name)
2067 q.applied[info[0]] = statusentry(info[1], name)
2068 q.applied_dirty = 1
2068 q.applied_dirty = 1
2069
2069
2070 util.rename(q.join(patch), absdest)
2070 util.rename(q.join(patch), absdest)
2071 r = q.qrepo()
2071 r = q.qrepo()
2072 if r:
2072 if r:
2073 wlock = r.wlock()
2073 wlock = r.wlock()
2074 try:
2074 try:
2075 if r.dirstate[patch] == 'a':
2075 if r.dirstate[patch] == 'a':
2076 r.dirstate.forget(patch)
2076 r.dirstate.forget(patch)
2077 r.dirstate.add(name)
2077 r.dirstate.add(name)
2078 else:
2078 else:
2079 if r.dirstate[name] == 'r':
2079 if r.dirstate[name] == 'r':
2080 r.undelete([name])
2080 r.undelete([name])
2081 r.copy(patch, name)
2081 r.copy(patch, name)
2082 r.remove([patch], False)
2082 r.remove([patch], False)
2083 finally:
2083 finally:
2084 del wlock
2084 del wlock
2085
2085
2086 q.save_dirty()
2086 q.save_dirty()
2087
2087
2088 def restore(ui, repo, rev, **opts):
2088 def restore(ui, repo, rev, **opts):
2089 """restore the queue state saved by a rev"""
2089 """restore the queue state saved by a rev"""
2090 rev = repo.lookup(rev)
2090 rev = repo.lookup(rev)
2091 q = repo.mq
2091 q = repo.mq
2092 q.restore(repo, rev, delete=opts['delete'],
2092 q.restore(repo, rev, delete=opts['delete'],
2093 qupdate=opts['update'])
2093 qupdate=opts['update'])
2094 q.save_dirty()
2094 q.save_dirty()
2095 return 0
2095 return 0
2096
2096
2097 def save(ui, repo, **opts):
2097 def save(ui, repo, **opts):
2098 """save current queue state"""
2098 """save current queue state"""
2099 q = repo.mq
2099 q = repo.mq
2100 message = cmdutil.logmessage(opts)
2100 message = cmdutil.logmessage(opts)
2101 ret = q.save(repo, msg=message)
2101 ret = q.save(repo, msg=message)
2102 if ret:
2102 if ret:
2103 return ret
2103 return ret
2104 q.save_dirty()
2104 q.save_dirty()
2105 if opts['copy']:
2105 if opts['copy']:
2106 path = q.path
2106 path = q.path
2107 if opts['name']:
2107 if opts['name']:
2108 newpath = os.path.join(q.basepath, opts['name'])
2108 newpath = os.path.join(q.basepath, opts['name'])
2109 if os.path.exists(newpath):
2109 if os.path.exists(newpath):
2110 if not os.path.isdir(newpath):
2110 if not os.path.isdir(newpath):
2111 raise util.Abort(_('destination %s exists and is not '
2111 raise util.Abort(_('destination %s exists and is not '
2112 'a directory') % newpath)
2112 'a directory') % newpath)
2113 if not opts['force']:
2113 if not opts['force']:
2114 raise util.Abort(_('destination %s exists, '
2114 raise util.Abort(_('destination %s exists, '
2115 'use -f to force') % newpath)
2115 'use -f to force') % newpath)
2116 else:
2116 else:
2117 newpath = savename(path)
2117 newpath = savename(path)
2118 ui.warn(_("copy %s to %s\n") % (path, newpath))
2118 ui.warn(_("copy %s to %s\n") % (path, newpath))
2119 util.copyfiles(path, newpath)
2119 util.copyfiles(path, newpath)
2120 if opts['empty']:
2120 if opts['empty']:
2121 try:
2121 try:
2122 os.unlink(q.join(q.status_path))
2122 os.unlink(q.join(q.status_path))
2123 except:
2123 except:
2124 pass
2124 pass
2125 return 0
2125 return 0
2126
2126
2127 def strip(ui, repo, rev, **opts):
2127 def strip(ui, repo, rev, **opts):
2128 """strip a revision and all its descendants from the repository
2128 """strip a revision and all its descendants from the repository
2129
2129
2130 If one of the working dir's parent revisions is stripped, the working
2130 If one of the working dir's parent revisions is stripped, the working
2131 directory will be updated to the parent of the stripped revision.
2131 directory will be updated to the parent of the stripped revision.
2132 """
2132 """
2133 backup = 'all'
2133 backup = 'all'
2134 if opts['backup']:
2134 if opts['backup']:
2135 backup = 'strip'
2135 backup = 'strip'
2136 elif opts['nobackup']:
2136 elif opts['nobackup']:
2137 backup = 'none'
2137 backup = 'none'
2138
2138
2139 rev = repo.lookup(rev)
2139 rev = repo.lookup(rev)
2140 p = repo.dirstate.parents()
2140 p = repo.dirstate.parents()
2141 cl = repo.changelog
2141 cl = repo.changelog
2142 update = True
2142 update = True
2143 if p[0] == revlog.nullid:
2143 if p[0] == revlog.nullid:
2144 update = False
2144 update = False
2145 elif p[1] == revlog.nullid and rev != cl.ancestor(p[0], rev):
2145 elif p[1] == revlog.nullid and rev != cl.ancestor(p[0], rev):
2146 update = False
2146 update = False
2147 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2147 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2148 update = False
2148 update = False
2149
2149
2150 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2150 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2151 return 0
2151 return 0
2152
2152
2153 def select(ui, repo, *args, **opts):
2153 def select(ui, repo, *args, **opts):
2154 '''set or print guarded patches to push
2154 '''set or print guarded patches to push
2155
2155
2156 Use the qguard command to set or print guards on patch, then use
2156 Use the qguard command to set or print guards on patch, then use
2157 qselect to tell mq which guards to use. A patch will be pushed if it
2157 qselect to tell mq which guards to use. A patch will be pushed if it
2158 has no guards or any positive guards match the currently selected guard,
2158 has no guards or any positive guards match the currently selected guard,
2159 but will not be pushed if any negative guards match the current guard.
2159 but will not be pushed if any negative guards match the current guard.
2160 For example:
2160 For example:
2161
2161
2162 qguard foo.patch -stable (negative guard)
2162 qguard foo.patch -stable (negative guard)
2163 qguard bar.patch +stable (positive guard)
2163 qguard bar.patch +stable (positive guard)
2164 qselect stable
2164 qselect stable
2165
2165
2166 This activates the "stable" guard. mq will skip foo.patch (because
2166 This activates the "stable" guard. mq will skip foo.patch (because
2167 it has a negative match) but push bar.patch (because it
2167 it has a negative match) but push bar.patch (because it
2168 has a positive match).
2168 has a positive match).
2169
2169
2170 With no arguments, prints the currently active guards.
2170 With no arguments, prints the currently active guards.
2171 With one argument, sets the active guard.
2171 With one argument, sets the active guard.
2172
2172
2173 Use -n/--none to deactivate guards (no other arguments needed).
2173 Use -n/--none to deactivate guards (no other arguments needed).
2174 When no guards are active, patches with positive guards are skipped
2174 When no guards are active, patches with positive guards are skipped
2175 and patches with negative guards are pushed.
2175 and patches with negative guards are pushed.
2176
2176
2177 qselect can change the guards on applied patches. It does not pop
2177 qselect can change the guards on applied patches. It does not pop
2178 guarded patches by default. Use --pop to pop back to the last applied
2178 guarded patches by default. Use --pop to pop back to the last applied
2179 patch that is not guarded. Use --reapply (which implies --pop) to push
2179 patch that is not guarded. Use --reapply (which implies --pop) to push
2180 back to the current patch afterwards, but skip guarded patches.
2180 back to the current patch afterwards, but skip guarded patches.
2181
2181
2182 Use -s/--series to print a list of all guards in the series file (no
2182 Use -s/--series to print a list of all guards in the series file (no
2183 other arguments needed). Use -v for more information.'''
2183 other arguments needed). Use -v for more information.'''
2184
2184
2185 q = repo.mq
2185 q = repo.mq
2186 guards = q.active()
2186 guards = q.active()
2187 if args or opts['none']:
2187 if args or opts['none']:
2188 old_unapplied = q.unapplied(repo)
2188 old_unapplied = q.unapplied(repo)
2189 old_guarded = [i for i in xrange(len(q.applied)) if
2189 old_guarded = [i for i in xrange(len(q.applied)) if
2190 not q.pushable(i)[0]]
2190 not q.pushable(i)[0]]
2191 q.set_active(args)
2191 q.set_active(args)
2192 q.save_dirty()
2192 q.save_dirty()
2193 if not args:
2193 if not args:
2194 ui.status(_('guards deactivated\n'))
2194 ui.status(_('guards deactivated\n'))
2195 if not opts['pop'] and not opts['reapply']:
2195 if not opts['pop'] and not opts['reapply']:
2196 unapplied = q.unapplied(repo)
2196 unapplied = q.unapplied(repo)
2197 guarded = [i for i in xrange(len(q.applied))
2197 guarded = [i for i in xrange(len(q.applied))
2198 if not q.pushable(i)[0]]
2198 if not q.pushable(i)[0]]
2199 if len(unapplied) != len(old_unapplied):
2199 if len(unapplied) != len(old_unapplied):
2200 ui.status(_('number of unguarded, unapplied patches has '
2200 ui.status(_('number of unguarded, unapplied patches has '
2201 'changed from %d to %d\n') %
2201 'changed from %d to %d\n') %
2202 (len(old_unapplied), len(unapplied)))
2202 (len(old_unapplied), len(unapplied)))
2203 if len(guarded) != len(old_guarded):
2203 if len(guarded) != len(old_guarded):
2204 ui.status(_('number of guarded, applied patches has changed '
2204 ui.status(_('number of guarded, applied patches has changed '
2205 'from %d to %d\n') %
2205 'from %d to %d\n') %
2206 (len(old_guarded), len(guarded)))
2206 (len(old_guarded), len(guarded)))
2207 elif opts['series']:
2207 elif opts['series']:
2208 guards = {}
2208 guards = {}
2209 noguards = 0
2209 noguards = 0
2210 for gs in q.series_guards:
2210 for gs in q.series_guards:
2211 if not gs:
2211 if not gs:
2212 noguards += 1
2212 noguards += 1
2213 for g in gs:
2213 for g in gs:
2214 guards.setdefault(g, 0)
2214 guards.setdefault(g, 0)
2215 guards[g] += 1
2215 guards[g] += 1
2216 if ui.verbose:
2216 if ui.verbose:
2217 guards['NONE'] = noguards
2217 guards['NONE'] = noguards
2218 guards = guards.items()
2218 guards = guards.items()
2219 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
2219 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
2220 if guards:
2220 if guards:
2221 ui.note(_('guards in series file:\n'))
2221 ui.note(_('guards in series file:\n'))
2222 for guard, count in guards:
2222 for guard, count in guards:
2223 ui.note('%2d ' % count)
2223 ui.note('%2d ' % count)
2224 ui.write(guard, '\n')
2224 ui.write(guard, '\n')
2225 else:
2225 else:
2226 ui.note(_('no guards in series file\n'))
2226 ui.note(_('no guards in series file\n'))
2227 else:
2227 else:
2228 if guards:
2228 if guards:
2229 ui.note(_('active guards:\n'))
2229 ui.note(_('active guards:\n'))
2230 for g in guards:
2230 for g in guards:
2231 ui.write(g, '\n')
2231 ui.write(g, '\n')
2232 else:
2232 else:
2233 ui.write(_('no active guards\n'))
2233 ui.write(_('no active guards\n'))
2234 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2234 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2235 popped = False
2235 popped = False
2236 if opts['pop'] or opts['reapply']:
2236 if opts['pop'] or opts['reapply']:
2237 for i in xrange(len(q.applied)):
2237 for i in xrange(len(q.applied)):
2238 pushable, reason = q.pushable(i)
2238 pushable, reason = q.pushable(i)
2239 if not pushable:
2239 if not pushable:
2240 ui.status(_('popping guarded patches\n'))
2240 ui.status(_('popping guarded patches\n'))
2241 popped = True
2241 popped = True
2242 if i == 0:
2242 if i == 0:
2243 q.pop(repo, all=True)
2243 q.pop(repo, all=True)
2244 else:
2244 else:
2245 q.pop(repo, i-1)
2245 q.pop(repo, i-1)
2246 break
2246 break
2247 if popped:
2247 if popped:
2248 try:
2248 try:
2249 if reapply:
2249 if reapply:
2250 ui.status(_('reapplying unguarded patches\n'))
2250 ui.status(_('reapplying unguarded patches\n'))
2251 q.push(repo, reapply)
2251 q.push(repo, reapply)
2252 finally:
2252 finally:
2253 q.save_dirty()
2253 q.save_dirty()
2254
2254
2255 def finish(ui, repo, *revrange, **opts):
2255 def finish(ui, repo, *revrange, **opts):
2256 """move applied patches into repository history
2256 """move applied patches into repository history
2257
2257
2258 Finishes the specified revisions (corresponding to applied patches) by
2258 Finishes the specified revisions (corresponding to applied patches) by
2259 moving them out of mq control into regular repository history.
2259 moving them out of mq control into regular repository history.
2260
2260
2261 Accepts a revision range or the --applied option. If --applied is
2261 Accepts a revision range or the --applied option. If --applied is
2262 specified, all applied mq revisions are removed from mq control.
2262 specified, all applied mq revisions are removed from mq control.
2263 Otherwise, the given revisions must be at the base of the stack of
2263 Otherwise, the given revisions must be at the base of the stack of
2264 applied patches.
2264 applied patches.
2265
2265
2266 This can be especially useful if your changes have been applied to an
2266 This can be especially useful if your changes have been applied to an
2267 upstream repository, or if you are about to push your changes to upstream.
2267 upstream repository, or if you are about to push your changes to upstream.
2268 """
2268 """
2269 if not opts['applied'] and not revrange:
2269 if not opts['applied'] and not revrange:
2270 raise util.Abort(_('no revisions specified'))
2270 raise util.Abort(_('no revisions specified'))
2271 elif opts['applied']:
2271 elif opts['applied']:
2272 revrange = ('qbase:qtip',) + revrange
2272 revrange = ('qbase:qtip',) + revrange
2273
2273
2274 q = repo.mq
2274 q = repo.mq
2275 if not q.applied:
2275 if not q.applied:
2276 ui.status(_('no patches applied\n'))
2276 ui.status(_('no patches applied\n'))
2277 return 0
2277 return 0
2278
2278
2279 revs = cmdutil.revrange(repo, revrange)
2279 revs = cmdutil.revrange(repo, revrange)
2280 q.finish(repo, revs)
2280 q.finish(repo, revs)
2281 q.save_dirty()
2281 q.save_dirty()
2282 return 0
2282 return 0
2283
2283
2284 def reposetup(ui, repo):
2284 def reposetup(ui, repo):
2285 class mqrepo(repo.__class__):
2285 class mqrepo(repo.__class__):
2286 def abort_if_wdir_patched(self, errmsg, force=False):
2286 def abort_if_wdir_patched(self, errmsg, force=False):
2287 if self.mq.applied and not force:
2287 if self.mq.applied and not force:
2288 parent = revlog.hex(self.dirstate.parents()[0])
2288 parent = revlog.hex(self.dirstate.parents()[0])
2289 if parent in [s.rev for s in self.mq.applied]:
2289 if parent in [s.rev for s in self.mq.applied]:
2290 raise util.Abort(errmsg)
2290 raise util.Abort(errmsg)
2291
2291
2292 def commit(self, *args, **opts):
2292 def commit(self, *args, **opts):
2293 if len(args) >= 6:
2293 if len(args) >= 6:
2294 force = args[5]
2294 force = args[5]
2295 else:
2295 else:
2296 force = opts.get('force')
2296 force = opts.get('force')
2297 self.abort_if_wdir_patched(
2297 self.abort_if_wdir_patched(
2298 _('cannot commit over an applied mq patch'),
2298 _('cannot commit over an applied mq patch'),
2299 force)
2299 force)
2300
2300
2301 return super(mqrepo, self).commit(*args, **opts)
2301 return super(mqrepo, self).commit(*args, **opts)
2302
2302
2303 def push(self, remote, force=False, revs=None):
2303 def push(self, remote, force=False, revs=None):
2304 if self.mq.applied and not force and not revs:
2304 if self.mq.applied and not force and not revs:
2305 raise util.Abort(_('source has mq patches applied'))
2305 raise util.Abort(_('source has mq patches applied'))
2306 return super(mqrepo, self).push(remote, force, revs)
2306 return super(mqrepo, self).push(remote, force, revs)
2307
2307
2308 def tags(self):
2308 def tags(self):
2309 if self.tagscache:
2309 if self.tagscache:
2310 return self.tagscache
2310 return self.tagscache
2311
2311
2312 tagscache = super(mqrepo, self).tags()
2312 tagscache = super(mqrepo, self).tags()
2313
2313
2314 q = self.mq
2314 q = self.mq
2315 if not q.applied:
2315 if not q.applied:
2316 return tagscache
2316 return tagscache
2317
2317
2318 mqtags = [(revlog.bin(patch.rev), patch.name) for patch in q.applied]
2318 mqtags = [(revlog.bin(patch.rev), patch.name) for patch in q.applied]
2319
2319
2320 if mqtags[-1][0] not in self.changelog.nodemap:
2320 if mqtags[-1][0] not in self.changelog.nodemap:
2321 self.ui.warn(_('mq status file refers to unknown node %s\n')
2321 self.ui.warn(_('mq status file refers to unknown node %s\n')
2322 % revlog.short(mqtags[-1][0]))
2322 % revlog.short(mqtags[-1][0]))
2323 return tagscache
2323 return tagscache
2324
2324
2325 mqtags.append((mqtags[-1][0], 'qtip'))
2325 mqtags.append((mqtags[-1][0], 'qtip'))
2326 mqtags.append((mqtags[0][0], 'qbase'))
2326 mqtags.append((mqtags[0][0], 'qbase'))
2327 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2327 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2328 for patch in mqtags:
2328 for patch in mqtags:
2329 if patch[1] in tagscache:
2329 if patch[1] in tagscache:
2330 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2330 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2331 % patch[1])
2331 % patch[1])
2332 else:
2332 else:
2333 tagscache[patch[1]] = patch[0]
2333 tagscache[patch[1]] = patch[0]
2334
2334
2335 return tagscache
2335 return tagscache
2336
2336
2337 def _branchtags(self, partial, lrev):
2337 def _branchtags(self, partial, lrev):
2338 q = self.mq
2338 q = self.mq
2339 if not q.applied:
2339 if not q.applied:
2340 return super(mqrepo, self)._branchtags(partial, lrev)
2340 return super(mqrepo, self)._branchtags(partial, lrev)
2341
2341
2342 cl = self.changelog
2342 cl = self.changelog
2343 qbasenode = revlog.bin(q.applied[0].rev)
2343 qbasenode = revlog.bin(q.applied[0].rev)
2344 if qbasenode not in cl.nodemap:
2344 if qbasenode not in cl.nodemap:
2345 self.ui.warn(_('mq status file refers to unknown node %s\n')
2345 self.ui.warn(_('mq status file refers to unknown node %s\n')
2346 % revlog.short(qbasenode))
2346 % revlog.short(qbasenode))
2347 return super(mqrepo, self)._branchtags(partial, lrev)
2347 return super(mqrepo, self)._branchtags(partial, lrev)
2348
2348
2349 qbase = cl.rev(qbasenode)
2349 qbase = cl.rev(qbasenode)
2350 start = lrev + 1
2350 start = lrev + 1
2351 if start < qbase:
2351 if start < qbase:
2352 # update the cache (excluding the patches) and save it
2352 # update the cache (excluding the patches) and save it
2353 self._updatebranchcache(partial, lrev+1, qbase)
2353 self._updatebranchcache(partial, lrev+1, qbase)
2354 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2354 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2355 start = qbase
2355 start = qbase
2356 # if start = qbase, the cache is as updated as it should be.
2356 # if start = qbase, the cache is as updated as it should be.
2357 # if start > qbase, the cache includes (part of) the patches.
2357 # if start > qbase, the cache includes (part of) the patches.
2358 # we might as well use it, but we won't save it.
2358 # we might as well use it, but we won't save it.
2359
2359
2360 # update the cache up to the tip
2360 # update the cache up to the tip
2361 self._updatebranchcache(partial, start, len(cl))
2361 self._updatebranchcache(partial, start, len(cl))
2362
2362
2363 return partial
2363 return partial
2364
2364
2365 if repo.local():
2365 if repo.local():
2366 repo.__class__ = mqrepo
2366 repo.__class__ = mqrepo
2367 repo.mq = queue(ui, repo.join(""))
2367 repo.mq = queue(ui, repo.join(""))
2368
2368
2369 def uisetup(ui):
2369 def uisetup(ui):
2370 # override import to disallow importing over patch
2370 # override import to disallow importing over patch
2371 importalias, importcmd = cmdutil.findcmd(ui, 'import', commands.table)
2371 importalias, importcmd = cmdutil.findcmd(ui, 'import', commands.table)
2372 for alias, cmd in commands.table.iteritems():
2372 for alias, cmd in commands.table.iteritems():
2373 if cmd is importcmd:
2373 if cmd is importcmd:
2374 importkey = alias
2374 importkey = alias
2375 break
2375 break
2376 orig_import = importcmd[0]
2376 orig_import = importcmd[0]
2377 def mqimport(ui, repo, patch1, *patches, **opts):
2377 def mqimport(ui, repo, patch1, *patches, **opts):
2378 if hasattr(repo, 'abort_if_wdir_patched'):
2378 if hasattr(repo, 'abort_if_wdir_patched'):
2379 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2379 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2380 opts.get('force'))
2380 opts.get('force'))
2381 orig_import(ui, repo, patch1, *patches, **opts)
2381 orig_import(ui, repo, patch1, *patches, **opts)
2382 importcmd = list(importcmd)
2382 importcmd = list(importcmd)
2383 importcmd[0] = mqimport
2383 importcmd[0] = mqimport
2384 commands.table[importkey] = tuple(importcmd)
2384 commands.table[importkey] = tuple(importcmd)
2385
2385
2386 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2386 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2387
2387
2388 cmdtable = {
2388 cmdtable = {
2389 "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')),
2389 "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')),
2390 "qclone":
2390 "qclone":
2391 (clone,
2391 (clone,
2392 [('', 'pull', None, _('use pull protocol to copy metadata')),
2392 [('', 'pull', None, _('use pull protocol to copy metadata')),
2393 ('U', 'noupdate', None, _('do not update the new working directories')),
2393 ('U', 'noupdate', None, _('do not update the new working directories')),
2394 ('', 'uncompressed', None,
2394 ('', 'uncompressed', None,
2395 _('use uncompressed transfer (fast over LAN)')),
2395 _('use uncompressed transfer (fast over LAN)')),
2396 ('p', 'patches', '', _('location of source patch repo')),
2396 ('p', 'patches', '', _('location of source patch repo')),
2397 ] + commands.remoteopts,
2397 ] + commands.remoteopts,
2398 _('hg qclone [OPTION]... SOURCE [DEST]')),
2398 _('hg qclone [OPTION]... SOURCE [DEST]')),
2399 "qcommit|qci":
2399 "qcommit|qci":
2400 (commit,
2400 (commit,
2401 commands.table["^commit|ci"][1],
2401 commands.table["^commit|ci"][1],
2402 _('hg qcommit [OPTION]... [FILE]...')),
2402 _('hg qcommit [OPTION]... [FILE]...')),
2403 "^qdiff":
2403 "^qdiff":
2404 (diff,
2404 (diff,
2405 commands.diffopts + commands.diffopts2 + commands.walkopts,
2405 commands.diffopts + commands.diffopts2 + commands.walkopts,
2406 _('hg qdiff [OPTION]... [FILE]...')),
2406 _('hg qdiff [OPTION]... [FILE]...')),
2407 "qdelete|qremove|qrm":
2407 "qdelete|qremove|qrm":
2408 (delete,
2408 (delete,
2409 [('k', 'keep', None, _('keep patch file')),
2409 [('k', 'keep', None, _('keep patch file')),
2410 ('r', 'rev', [], _('stop managing a revision'))],
2410 ('r', 'rev', [], _('stop managing a revision'))],
2411 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2411 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2412 'qfold':
2412 'qfold':
2413 (fold,
2413 (fold,
2414 [('e', 'edit', None, _('edit patch header')),
2414 [('e', 'edit', None, _('edit patch header')),
2415 ('k', 'keep', None, _('keep folded patch files')),
2415 ('k', 'keep', None, _('keep folded patch files')),
2416 ] + commands.commitopts,
2416 ] + commands.commitopts,
2417 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2417 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2418 'qgoto':
2418 'qgoto':
2419 (goto,
2419 (goto,
2420 [('f', 'force', None, _('overwrite any local changes'))],
2420 [('f', 'force', None, _('overwrite any local changes'))],
2421 _('hg qgoto [OPTION]... PATCH')),
2421 _('hg qgoto [OPTION]... PATCH')),
2422 'qguard':
2422 'qguard':
2423 (guard,
2423 (guard,
2424 [('l', 'list', None, _('list all patches and guards')),
2424 [('l', 'list', None, _('list all patches and guards')),
2425 ('n', 'none', None, _('drop all guards'))],
2425 ('n', 'none', None, _('drop all guards'))],
2426 _('hg qguard [-l] [-n] [PATCH] [+GUARD]... [-GUARD]...')),
2426 _('hg qguard [-l] [-n] [PATCH] [+GUARD]... [-GUARD]...')),
2427 'qheader': (header, [], _('hg qheader [PATCH]')),
2427 'qheader': (header, [], _('hg qheader [PATCH]')),
2428 "^qimport":
2428 "^qimport":
2429 (qimport,
2429 (qimport,
2430 [('e', 'existing', None, _('import file in patch dir')),
2430 [('e', 'existing', None, _('import file in patch dir')),
2431 ('n', 'name', '', _('patch file name')),
2431 ('n', 'name', '', _('patch file name')),
2432 ('f', 'force', None, _('overwrite existing files')),
2432 ('f', 'force', None, _('overwrite existing files')),
2433 ('r', 'rev', [], _('place existing revisions under mq control')),
2433 ('r', 'rev', [], _('place existing revisions under mq control')),
2434 ('g', 'git', None, _('use git extended diff format'))],
2434 ('g', 'git', None, _('use git extended diff format'))],
2435 _('hg qimport [-e] [-n NAME] [-f] [-g] [-r REV]... FILE...')),
2435 _('hg qimport [-e] [-n NAME] [-f] [-g] [-r REV]... FILE...')),
2436 "^qinit":
2436 "^qinit":
2437 (init,
2437 (init,
2438 [('c', 'create-repo', None, _('create queue repository'))],
2438 [('c', 'create-repo', None, _('create queue repository'))],
2439 _('hg qinit [-c]')),
2439 _('hg qinit [-c]')),
2440 "qnew":
2440 "qnew":
2441 (new,
2441 (new,
2442 [('e', 'edit', None, _('edit commit message')),
2442 [('e', 'edit', None, _('edit commit message')),
2443 ('f', 'force', None, _('import uncommitted changes into patch')),
2443 ('f', 'force', None, _('import uncommitted changes into patch')),
2444 ('g', 'git', None, _('use git extended diff format')),
2444 ('g', 'git', None, _('use git extended diff format')),
2445 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2445 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2446 ('u', 'user', '', _('add "From: <given user>" to patch')),
2446 ('u', 'user', '', _('add "From: <given user>" to patch')),
2447 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2447 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2448 ('d', 'date', '', _('add "Date: <given date>" to patch'))
2448 ('d', 'date', '', _('add "Date: <given date>" to patch'))
2449 ] + commands.walkopts + commands.commitopts,
2449 ] + commands.walkopts + commands.commitopts,
2450 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2450 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2451 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2451 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2452 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2452 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2453 "^qpop":
2453 "^qpop":
2454 (pop,
2454 (pop,
2455 [('a', 'all', None, _('pop all patches')),
2455 [('a', 'all', None, _('pop all patches')),
2456 ('n', 'name', '', _('queue name to pop')),
2456 ('n', 'name', '', _('queue name to pop')),
2457 ('f', 'force', None, _('forget any local changes'))],
2457 ('f', 'force', None, _('forget any local changes'))],
2458 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2458 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2459 "^qpush":
2459 "^qpush":
2460 (push,
2460 (push,
2461 [('f', 'force', None, _('apply if the patch has rejects')),
2461 [('f', 'force', None, _('apply if the patch has rejects')),
2462 ('l', 'list', None, _('list patch name in commit text')),
2462 ('l', 'list', None, _('list patch name in commit text')),
2463 ('a', 'all', None, _('apply all patches')),
2463 ('a', 'all', None, _('apply all patches')),
2464 ('m', 'merge', None, _('merge from another queue')),
2464 ('m', 'merge', None, _('merge from another queue')),
2465 ('n', 'name', '', _('merge queue name'))],
2465 ('n', 'name', '', _('merge queue name'))],
2466 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2466 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2467 "^qrefresh":
2467 "^qrefresh":
2468 (refresh,
2468 (refresh,
2469 [('e', 'edit', None, _('edit commit message')),
2469 [('e', 'edit', None, _('edit commit message')),
2470 ('g', 'git', None, _('use git extended diff format')),
2470 ('g', 'git', None, _('use git extended diff format')),
2471 ('s', 'short', None, _('refresh only files already in the patch and specified files')),
2471 ('s', 'short', None, _('refresh only files already in the patch and specified files')),
2472 ('U', 'currentuser', None, _('add/update "From: <current user>" in patch')),
2472 ('U', 'currentuser', None, _('add/update "From: <current user>" in patch')),
2473 ('u', 'user', '', _('add/update "From: <given user>" in patch')),
2473 ('u', 'user', '', _('add/update "From: <given user>" in patch')),
2474 ('D', 'currentdate', None, _('update "Date: <current date>" in patch (if present)')),
2474 ('D', 'currentdate', None, _('update "Date: <current date>" in patch (if present)')),
2475 ('d', 'date', '', _('update "Date: <given date>" in patch (if present)'))
2475 ('d', 'date', '', _('update "Date: <given date>" in patch (if present)'))
2476 ] + commands.walkopts + commands.commitopts,
2476 ] + commands.walkopts + commands.commitopts,
2477 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2477 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2478 'qrename|qmv':
2478 'qrename|qmv':
2479 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2479 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2480 "qrestore":
2480 "qrestore":
2481 (restore,
2481 (restore,
2482 [('d', 'delete', None, _('delete save entry')),
2482 [('d', 'delete', None, _('delete save entry')),
2483 ('u', 'update', None, _('update queue working dir'))],
2483 ('u', 'update', None, _('update queue working dir'))],
2484 _('hg qrestore [-d] [-u] REV')),
2484 _('hg qrestore [-d] [-u] REV')),
2485 "qsave":
2485 "qsave":
2486 (save,
2486 (save,
2487 [('c', 'copy', None, _('copy patch directory')),
2487 [('c', 'copy', None, _('copy patch directory')),
2488 ('n', 'name', '', _('copy directory name')),
2488 ('n', 'name', '', _('copy directory name')),
2489 ('e', 'empty', None, _('clear queue status file')),
2489 ('e', 'empty', None, _('clear queue status file')),
2490 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2490 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2491 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2491 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2492 "qselect":
2492 "qselect":
2493 (select,
2493 (select,
2494 [('n', 'none', None, _('disable all guards')),
2494 [('n', 'none', None, _('disable all guards')),
2495 ('s', 'series', None, _('list all guards in series file')),
2495 ('s', 'series', None, _('list all guards in series file')),
2496 ('', 'pop', None, _('pop to before first guarded applied patch')),
2496 ('', 'pop', None, _('pop to before first guarded applied patch')),
2497 ('', 'reapply', None, _('pop, then reapply patches'))],
2497 ('', 'reapply', None, _('pop, then reapply patches'))],
2498 _('hg qselect [OPTION]... [GUARD]...')),
2498 _('hg qselect [OPTION]... [GUARD]...')),
2499 "qseries":
2499 "qseries":
2500 (series,
2500 (series,
2501 [('m', 'missing', None, _('print patches not in series')),
2501 [('m', 'missing', None, _('print patches not in series')),
2502 ] + seriesopts,
2502 ] + seriesopts,
2503 _('hg qseries [-ms]')),
2503 _('hg qseries [-ms]')),
2504 "^strip":
2504 "^strip":
2505 (strip,
2505 (strip,
2506 [('f', 'force', None, _('force removal with local changes')),
2506 [('f', 'force', None, _('force removal with local changes')),
2507 ('b', 'backup', None, _('bundle unrelated changesets')),
2507 ('b', 'backup', None, _('bundle unrelated changesets')),
2508 ('n', 'nobackup', None, _('no backups'))],
2508 ('n', 'nobackup', None, _('no backups'))],
2509 _('hg strip [-f] [-b] [-n] REV')),
2509 _('hg strip [-f] [-b] [-n] REV')),
2510 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2510 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2511 "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')),
2511 "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')),
2512 "qfinish":
2512 "qfinish":
2513 (finish,
2513 (finish,
2514 [('a', 'applied', None, _('finish all applied changesets'))],
2514 [('a', 'applied', None, _('finish all applied changesets'))],
2515 _('hg qfinish [-a] [REV...]')),
2515 _('hg qfinish [-a] [REV...]')),
2516 }
2516 }
@@ -1,435 +1,435 b''
1 /*
1 /*
2 parsers.c - efficient content parsing
2 parsers.c - efficient content parsing
3
3
4 Copyright 2008 Matt Mackall <mpm@selenic.com> and others
4 Copyright 2008 Matt Mackall <mpm@selenic.com> and others
5
5
6 This software may be used and distributed according to the terms of
6 This software may be used and distributed according to the terms of
7 the GNU General Public License, incorporated herein by reference.
7 the GNU General Public License, incorporated herein by reference.
8 */
8 */
9
9
10 #include <Python.h>
10 #include <Python.h>
11 #include <ctype.h>
11 #include <ctype.h>
12 #include <string.h>
12 #include <string.h>
13
13
14 static int hexdigit(char c)
14 static int hexdigit(char c)
15 {
15 {
16 if (c >= '0' && c <= '9')
16 if (c >= '0' && c <= '9')
17 return c - '0';
17 return c - '0';
18 if (c >= 'a' && c <= 'f')
18 if (c >= 'a' && c <= 'f')
19 return c - 'a' + 10;
19 return c - 'a' + 10;
20 if (c >= 'A' && c <= 'F')
20 if (c >= 'A' && c <= 'F')
21 return c - 'A' + 10;
21 return c - 'A' + 10;
22
22
23 PyErr_SetString(PyExc_ValueError, "input contains non-hex character");
23 PyErr_SetString(PyExc_ValueError, "input contains non-hex character");
24 return 0;
24 return 0;
25 }
25 }
26
26
27 /*
27 /*
28 * Turn a hex-encoded string into binary.
28 * Turn a hex-encoded string into binary.
29 */
29 */
30 static PyObject *unhexlify(const char *str, int len)
30 static PyObject *unhexlify(const char *str, int len)
31 {
31 {
32 PyObject *ret;
32 PyObject *ret;
33 const char *c;
33 const char *c;
34 char *d;
34 char *d;
35
35
36 ret = PyString_FromStringAndSize(NULL, len / 2);
36 ret = PyString_FromStringAndSize(NULL, len / 2);
37 if (!ret)
37 if (!ret)
38 return NULL;
38 return NULL;
39
39
40 d = PyString_AS_STRING(ret);
40 d = PyString_AS_STRING(ret);
41 for (c = str; c < str + len;) {
41 for (c = str; c < str + len;) {
42 int hi = hexdigit(*c++);
42 int hi = hexdigit(*c++);
43 int lo = hexdigit(*c++);
43 int lo = hexdigit(*c++);
44 *d++ = (hi << 4) | lo;
44 *d++ = (hi << 4) | lo;
45 }
45 }
46
46
47 return ret;
47 return ret;
48 }
48 }
49
49
50 /*
50 /*
51 * This code assumes that a manifest is stitched together with newline
51 * This code assumes that a manifest is stitched together with newline
52 * ('\n') characters.
52 * ('\n') characters.
53 */
53 */
54 static PyObject *parse_manifest(PyObject *self, PyObject *args)
54 static PyObject *parse_manifest(PyObject *self, PyObject *args)
55 {
55 {
56 PyObject *mfdict, *fdict;
56 PyObject *mfdict, *fdict;
57 char *str, *cur, *start, *zero;
57 char *str, *cur, *start, *zero;
58 int len;
58 int len;
59
59
60 if (!PyArg_ParseTuple(args, "O!O!s#:parse_manifest",
60 if (!PyArg_ParseTuple(args, "O!O!s#:parse_manifest",
61 &PyDict_Type, &mfdict,
61 &PyDict_Type, &mfdict,
62 &PyDict_Type, &fdict,
62 &PyDict_Type, &fdict,
63 &str, &len))
63 &str, &len))
64 goto quit;
64 goto quit;
65
65
66 for (start = cur = str, zero = NULL; cur < str + len; cur++) {
66 for (start = cur = str, zero = NULL; cur < str + len; cur++) {
67 PyObject *file = NULL, *node = NULL;
67 PyObject *file = NULL, *node = NULL;
68 PyObject *flags = NULL;
68 PyObject *flags = NULL;
69 int nlen;
69 int nlen;
70
70
71 if (!*cur) {
71 if (!*cur) {
72 zero = cur;
72 zero = cur;
73 continue;
73 continue;
74 }
74 }
75 else if (*cur != '\n')
75 else if (*cur != '\n')
76 continue;
76 continue;
77
77
78 if (!zero) {
78 if (!zero) {
79 PyErr_SetString(PyExc_ValueError,
79 PyErr_SetString(PyExc_ValueError,
80 "manifest entry has no separator");
80 "manifest entry has no separator");
81 goto quit;
81 goto quit;
82 }
82 }
83
83
84 file = PyString_FromStringAndSize(start, zero - start);
84 file = PyString_FromStringAndSize(start, zero - start);
85 if (!file)
85 if (!file)
86 goto bail;
86 goto bail;
87
87
88 nlen = cur - zero - 1;
88 nlen = cur - zero - 1;
89
89
90 node = unhexlify(zero + 1, nlen > 40 ? 40 : nlen);
90 node = unhexlify(zero + 1, nlen > 40 ? 40 : nlen);
91 if (!node)
91 if (!node)
92 goto bail;
92 goto bail;
93
93
94 if (nlen > 40) {
94 if (nlen > 40) {
95 PyObject *flags;
95 PyObject *flags;
96
96
97 flags = PyString_FromStringAndSize(zero + 41,
97 flags = PyString_FromStringAndSize(zero + 41,
98 nlen - 40);
98 nlen - 40);
99 if (!flags)
99 if (!flags)
100 goto bail;
100 goto bail;
101
101
102 if (PyDict_SetItem(fdict, file, flags) == -1)
102 if (PyDict_SetItem(fdict, file, flags) == -1)
103 goto bail;
103 goto bail;
104 }
104 }
105
105
106 if (PyDict_SetItem(mfdict, file, node) == -1)
106 if (PyDict_SetItem(mfdict, file, node) == -1)
107 goto bail;
107 goto bail;
108
108
109 start = cur + 1;
109 start = cur + 1;
110 zero = NULL;
110 zero = NULL;
111
111
112 Py_XDECREF(flags);
112 Py_XDECREF(flags);
113 Py_XDECREF(node);
113 Py_XDECREF(node);
114 Py_XDECREF(file);
114 Py_XDECREF(file);
115 continue;
115 continue;
116 bail:
116 bail:
117 Py_XDECREF(flags);
117 Py_XDECREF(flags);
118 Py_XDECREF(node);
118 Py_XDECREF(node);
119 Py_XDECREF(file);
119 Py_XDECREF(file);
120 goto quit;
120 goto quit;
121 }
121 }
122
122
123 if (len > 0 && *(cur - 1) != '\n') {
123 if (len > 0 && *(cur - 1) != '\n') {
124 PyErr_SetString(PyExc_ValueError,
124 PyErr_SetString(PyExc_ValueError,
125 "manifest contains trailing garbage");
125 "manifest contains trailing garbage");
126 goto quit;
126 goto quit;
127 }
127 }
128
128
129 Py_INCREF(Py_None);
129 Py_INCREF(Py_None);
130 return Py_None;
130 return Py_None;
131 quit:
131 quit:
132 return NULL;
132 return NULL;
133 }
133 }
134
134
135 #ifdef _WIN32
135 #ifdef _WIN32
136 # ifdef _MSC_VER
136 # ifdef _MSC_VER
137 /* msvc 6.0 has problems */
137 /* msvc 6.0 has problems */
138 # define inline __inline
138 # define inline __inline
139 typedef unsigned long uint32_t;
139 typedef unsigned long uint32_t;
140 typedef unsigned __int64 uint64_t;
140 typedef unsigned __int64 uint64_t;
141 # else
141 # else
142 # include <stdint.h>
142 # include <stdint.h>
143 # endif
143 # endif
144 static uint32_t ntohl(uint32_t x)
144 static uint32_t ntohl(uint32_t x)
145 {
145 {
146 return ((x & 0x000000ffUL) << 24) |
146 return ((x & 0x000000ffUL) << 24) |
147 ((x & 0x0000ff00UL) << 8) |
147 ((x & 0x0000ff00UL) << 8) |
148 ((x & 0x00ff0000UL) >> 8) |
148 ((x & 0x00ff0000UL) >> 8) |
149 ((x & 0xff000000UL) >> 24);
149 ((x & 0xff000000UL) >> 24);
150 }
150 }
151 #else
151 #else
152 /* not windows */
152 /* not windows */
153 # include <sys/types.h>
153 # include <sys/types.h>
154 # if defined __BEOS__ && !defined __HAIKU__
154 # if defined __BEOS__ && !defined __HAIKU__
155 # include <ByteOrder.h>
155 # include <ByteOrder.h>
156 # else
156 # else
157 # include <arpa/inet.h>
157 # include <arpa/inet.h>
158 # endif
158 # endif
159 # include <inttypes.h>
159 # include <inttypes.h>
160 #endif
160 #endif
161
161
162 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
162 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
163 {
163 {
164 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
164 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
165 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
165 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
166 char *str, *cur, *end, *cpos;
166 char *str, *cur, *end, *cpos;
167 int state, mode, size, mtime;
167 int state, mode, size, mtime;
168 unsigned int flen;
168 unsigned int flen;
169 int len;
169 int len;
170 char decode[16]; /* for alignment */
170 char decode[16]; /* for alignment */
171
171
172 if (!PyArg_ParseTuple(args, "O!O!s#:parse_dirstate",
172 if (!PyArg_ParseTuple(args, "O!O!s#:parse_dirstate",
173 &PyDict_Type, &dmap,
173 &PyDict_Type, &dmap,
174 &PyDict_Type, &cmap,
174 &PyDict_Type, &cmap,
175 &str, &len))
175 &str, &len))
176 goto quit;
176 goto quit;
177
177
178 /* read parents */
178 /* read parents */
179 if (len < 40)
179 if (len < 40)
180 goto quit;
180 goto quit;
181
181
182 parents = Py_BuildValue("s#s#", str, 20, str + 20, 20);
182 parents = Py_BuildValue("s#s#", str, 20, str + 20, 20);
183 if (!parents)
183 if (!parents)
184 goto quit;
184 goto quit;
185
185
186 /* read filenames */
186 /* read filenames */
187 cur = str + 40;
187 cur = str + 40;
188 end = str + len;
188 end = str + len;
189
189
190 while (cur < end - 17) {
190 while (cur < end - 17) {
191 /* unpack header */
191 /* unpack header */
192 state = *cur;
192 state = *cur;
193 memcpy(decode, cur + 1, 16);
193 memcpy(decode, cur + 1, 16);
194 mode = ntohl(*(uint32_t *)(decode));
194 mode = ntohl(*(uint32_t *)(decode));
195 size = ntohl(*(uint32_t *)(decode + 4));
195 size = ntohl(*(uint32_t *)(decode + 4));
196 mtime = ntohl(*(uint32_t *)(decode + 8));
196 mtime = ntohl(*(uint32_t *)(decode + 8));
197 flen = ntohl(*(uint32_t *)(decode + 12));
197 flen = ntohl(*(uint32_t *)(decode + 12));
198 cur += 17;
198 cur += 17;
199 if (flen > end - cur) {
199 if (flen > end - cur) {
200 PyErr_SetString(PyExc_ValueError, "overflow in dirstate");
200 PyErr_SetString(PyExc_ValueError, "overflow in dirstate");
201 goto quit;
201 goto quit;
202 }
202 }
203
203
204 entry = Py_BuildValue("ciii", state, mode, size, mtime);
204 entry = Py_BuildValue("ciii", state, mode, size, mtime);
205 if (!entry)
205 if (!entry)
206 goto quit;
206 goto quit;
207 PyObject_GC_UnTrack(entry); /* don't waste time with this */
207 PyObject_GC_UnTrack(entry); /* don't waste time with this */
208
208
209 cpos = memchr(cur, 0, flen);
209 cpos = memchr(cur, 0, flen);
210 if (cpos) {
210 if (cpos) {
211 fname = PyString_FromStringAndSize(cur, cpos - cur);
211 fname = PyString_FromStringAndSize(cur, cpos - cur);
212 cname = PyString_FromStringAndSize(cpos + 1,
212 cname = PyString_FromStringAndSize(cpos + 1,
213 flen - (cpos - cur) - 1);
213 flen - (cpos - cur) - 1);
214 if (!fname || !cname ||
214 if (!fname || !cname ||
215 PyDict_SetItem(cmap, fname, cname) == -1 ||
215 PyDict_SetItem(cmap, fname, cname) == -1 ||
216 PyDict_SetItem(dmap, fname, entry) == -1)
216 PyDict_SetItem(dmap, fname, entry) == -1)
217 goto quit;
217 goto quit;
218 Py_DECREF(cname);
218 Py_DECREF(cname);
219 } else {
219 } else {
220 fname = PyString_FromStringAndSize(cur, flen);
220 fname = PyString_FromStringAndSize(cur, flen);
221 if (!fname ||
221 if (!fname ||
222 PyDict_SetItem(dmap, fname, entry) == -1)
222 PyDict_SetItem(dmap, fname, entry) == -1)
223 goto quit;
223 goto quit;
224 }
224 }
225 cur += flen;
225 cur += flen;
226 Py_DECREF(fname);
226 Py_DECREF(fname);
227 Py_DECREF(entry);
227 Py_DECREF(entry);
228 fname = cname = entry = NULL;
228 fname = cname = entry = NULL;
229 }
229 }
230
230
231 ret = parents;
231 ret = parents;
232 Py_INCREF(ret);
232 Py_INCREF(ret);
233 quit:
233 quit:
234 Py_XDECREF(fname);
234 Py_XDECREF(fname);
235 Py_XDECREF(cname);
235 Py_XDECREF(cname);
236 Py_XDECREF(entry);
236 Py_XDECREF(entry);
237 Py_XDECREF(parents);
237 Py_XDECREF(parents);
238 return ret;
238 return ret;
239 }
239 }
240
240
241 const char nullid[20];
241 const char nullid[20];
242 const int nullrev = -1;
242 const int nullrev = -1;
243
243
244 /* create an index tuple, insert into the nodemap */
244 /* create an index tuple, insert into the nodemap */
245 static PyObject * _build_idx_entry(PyObject *nodemap, int n, uint64_t offset_flags,
245 static PyObject * _build_idx_entry(PyObject *nodemap, int n, uint64_t offset_flags,
246 int comp_len, int uncomp_len, int base_rev,
246 int comp_len, int uncomp_len, int base_rev,
247 int link_rev, int parent_1, int parent_2,
247 int link_rev, int parent_1, int parent_2,
248 const char *c_node_id)
248 const char *c_node_id)
249 {
249 {
250 int err;
250 int err;
251 PyObject *entry, *node_id, *n_obj;
251 PyObject *entry, *node_id, *n_obj;
252
252
253 node_id = PyString_FromStringAndSize(c_node_id, 20);
253 node_id = PyString_FromStringAndSize(c_node_id, 20);
254 n_obj = PyInt_FromLong(n);
254 n_obj = PyInt_FromLong(n);
255 if (!node_id || !n_obj)
255 if (!node_id || !n_obj)
256 err = -1;
256 err = -1;
257 else
257 else
258 err = PyDict_SetItem(nodemap, node_id, n_obj);
258 err = PyDict_SetItem(nodemap, node_id, n_obj);
259
259
260 Py_XDECREF(n_obj);
260 Py_XDECREF(n_obj);
261 if (err)
261 if (err)
262 goto error_dealloc;
262 goto error_dealloc;
263
263
264 entry = Py_BuildValue("LiiiiiiN", offset_flags, comp_len,
264 entry = Py_BuildValue("LiiiiiiN", offset_flags, comp_len,
265 uncomp_len, base_rev, link_rev,
265 uncomp_len, base_rev, link_rev,
266 parent_1, parent_2, node_id);
266 parent_1, parent_2, node_id);
267 if (!entry)
267 if (!entry)
268 goto error_dealloc;
268 goto error_dealloc;
269 PyObject_GC_UnTrack(entry); /* don't waste time with this */
269 PyObject_GC_UnTrack(entry); /* don't waste time with this */
270
270
271 return entry;
271 return entry;
272
272
273 error_dealloc:
273 error_dealloc:
274 Py_XDECREF(node_id);
274 Py_XDECREF(node_id);
275 return NULL;
275 return NULL;
276 }
276 }
277
277
278 /* RevlogNG format (all in big endian, data may be inlined):
278 /* RevlogNG format (all in big endian, data may be inlined):
279 * 6 bytes: offset
279 * 6 bytes: offset
280 * 2 bytes: flags
280 * 2 bytes: flags
281 * 4 bytes: compressed length
281 * 4 bytes: compressed length
282 * 4 bytes: uncompressed length
282 * 4 bytes: uncompressed length
283 * 4 bytes: base revision
283 * 4 bytes: base revision
284 * 4 bytes: link revision
284 * 4 bytes: link revision
285 * 4 bytes: parent 1 revision
285 * 4 bytes: parent 1 revision
286 * 4 bytes: parent 2 revision
286 * 4 bytes: parent 2 revision
287 * 32 bytes: nodeid (only 20 bytes used)
287 * 32 bytes: nodeid (only 20 bytes used)
288 */
288 */
289 static int _parse_index_ng (const char *data, int size, int inlined,
289 static int _parse_index_ng (const char *data, int size, int inlined,
290 PyObject *index, PyObject *nodemap)
290 PyObject *index, PyObject *nodemap)
291 {
291 {
292 PyObject *entry;
292 PyObject *entry;
293 int n = 0, err;
293 int n = 0, err;
294 uint64_t offset_flags;
294 uint64_t offset_flags;
295 int comp_len, uncomp_len, base_rev, link_rev, parent_1, parent_2;
295 int comp_len, uncomp_len, base_rev, link_rev, parent_1, parent_2;
296 const char *c_node_id;
296 const char *c_node_id;
297 const char *end = data + size;
297 const char *end = data + size;
298 char decode[64]; /* to enforce alignment with inline data */
298 char decode[64]; /* to enforce alignment with inline data */
299
299
300 while (data < end) {
300 while (data < end) {
301 unsigned int step;
301 unsigned int step;
302
302
303 memcpy(decode, data, 64);
303 memcpy(decode, data, 64);
304 offset_flags = ntohl(*((uint32_t *) (decode + 4)));
304 offset_flags = ntohl(*((uint32_t *) (decode + 4)));
305 if (n == 0) /* mask out version number for the first entry */
305 if (n == 0) /* mask out version number for the first entry */
306 offset_flags &= 0xFFFF;
306 offset_flags &= 0xFFFF;
307 else {
307 else {
308 uint32_t offset_high = ntohl(*((uint32_t *) decode));
308 uint32_t offset_high = ntohl(*((uint32_t *) decode));
309 offset_flags |= ((uint64_t) offset_high) << 32;
309 offset_flags |= ((uint64_t) offset_high) << 32;
310 }
310 }
311
311
312 comp_len = ntohl(*((uint32_t *) (decode + 8)));
312 comp_len = ntohl(*((uint32_t *) (decode + 8)));
313 uncomp_len = ntohl(*((uint32_t *) (decode + 12)));
313 uncomp_len = ntohl(*((uint32_t *) (decode + 12)));
314 base_rev = ntohl(*((uint32_t *) (decode + 16)));
314 base_rev = ntohl(*((uint32_t *) (decode + 16)));
315 link_rev = ntohl(*((uint32_t *) (decode + 20)));
315 link_rev = ntohl(*((uint32_t *) (decode + 20)));
316 parent_1 = ntohl(*((uint32_t *) (decode + 24)));
316 parent_1 = ntohl(*((uint32_t *) (decode + 24)));
317 parent_2 = ntohl(*((uint32_t *) (decode + 28)));
317 parent_2 = ntohl(*((uint32_t *) (decode + 28)));
318 c_node_id = decode + 32;
318 c_node_id = decode + 32;
319
319
320 entry = _build_idx_entry(nodemap, n, offset_flags,
320 entry = _build_idx_entry(nodemap, n, offset_flags,
321 comp_len, uncomp_len, base_rev,
321 comp_len, uncomp_len, base_rev,
322 link_rev, parent_1, parent_2,
322 link_rev, parent_1, parent_2,
323 c_node_id);
323 c_node_id);
324 if (!entry)
324 if (!entry)
325 return 0;
325 return 0;
326
326
327 if (inlined) {
327 if (inlined) {
328 err = PyList_Append(index, entry);
328 err = PyList_Append(index, entry);
329 Py_DECREF(entry);
329 Py_DECREF(entry);
330 if (err)
330 if (err)
331 return 0;
331 return 0;
332 } else
332 } else
333 PyList_SET_ITEM(index, n, entry); /* steals reference */
333 PyList_SET_ITEM(index, n, entry); /* steals reference */
334
334
335 n++;
335 n++;
336 step = 64 + (inlined ? comp_len : 0);
336 step = 64 + (inlined ? comp_len : 0);
337 if (end - data < step)
337 if (end - data < step)
338 break;
338 break;
339 data += step;
339 data += step;
340 }
340 }
341 if (data != end) {
341 if (data != end) {
342 if (!PyErr_Occurred())
342 if (!PyErr_Occurred())
343 PyErr_SetString(PyExc_ValueError, "corrupt index file");
343 PyErr_SetString(PyExc_ValueError, "corrupt index file");
344 return 0;
344 return 0;
345 }
345 }
346
346
347 /* create the nullid/nullrev entry in the nodemap and the
347 /* create the nullid/nullrev entry in the nodemap and the
348 * magic nullid entry in the index at [-1] */
348 * magic nullid entry in the index at [-1] */
349 entry = _build_idx_entry(nodemap,
349 entry = _build_idx_entry(nodemap,
350 nullrev, 0, 0, 0, -1, -1, -1, -1, nullid);
350 nullrev, 0, 0, 0, -1, -1, -1, -1, nullid);
351 if (!entry)
351 if (!entry)
352 return 0;
352 return 0;
353 if (inlined) {
353 if (inlined) {
354 err = PyList_Append(index, entry);
354 err = PyList_Append(index, entry);
355 Py_DECREF(entry);
355 Py_DECREF(entry);
356 if (err)
356 if (err)
357 return 0;
357 return 0;
358 } else
358 } else
359 PyList_SET_ITEM(index, n, entry); /* steals reference */
359 PyList_SET_ITEM(index, n, entry); /* steals reference */
360
360
361 return 1;
361 return 1;
362 }
362 }
363
363
364 /* This function parses a index file and returns a Python tuple of the
364 /* This function parses a index file and returns a Python tuple of the
365 * following format: (index, nodemap, cache)
365 * following format: (index, nodemap, cache)
366 *
366 *
367 * index: a list of tuples containing the RevlogNG records
367 * index: a list of tuples containing the RevlogNG records
368 * nodemap: a dict mapping node ids to indices in the index list
368 * nodemap: a dict mapping node ids to indices in the index list
369 * cache: if data is inlined, a tuple (index_file_content, 0) else None
369 * cache: if data is inlined, a tuple (index_file_content, 0) else None
370 */
370 */
371 static PyObject *parse_index(PyObject *self, PyObject *args)
371 static PyObject *parse_index(PyObject *self, PyObject *args)
372 {
372 {
373 const char *data;
373 const char *data;
374 int size, inlined;
374 int size, inlined;
375 PyObject *rval = NULL, *index = NULL, *nodemap = NULL, *cache = NULL;
375 PyObject *rval = NULL, *index = NULL, *nodemap = NULL, *cache = NULL;
376 PyObject *data_obj = NULL, *inlined_obj;
376 PyObject *data_obj = NULL, *inlined_obj;
377
377
378 if (!PyArg_ParseTuple(args, "s#O", &data, &size, &inlined_obj))
378 if (!PyArg_ParseTuple(args, "s#O", &data, &size, &inlined_obj))
379 return NULL;
379 return NULL;
380 inlined = inlined_obj && PyObject_IsTrue(inlined_obj);
380 inlined = inlined_obj && PyObject_IsTrue(inlined_obj);
381
381
382 /* If no data is inlined, we know the size of the index list in
382 /* If no data is inlined, we know the size of the index list in
383 * advance: size divided by size of one one revlog record (64 bytes)
383 * advance: size divided by size of one one revlog record (64 bytes)
384 * plus one for the nullid */
384 * plus one for the nullid */
385 index = inlined ? PyList_New(0) : PyList_New(size / 64 + 1);
385 index = inlined ? PyList_New(0) : PyList_New(size / 64 + 1);
386 if (!index)
386 if (!index)
387 goto quit;
387 goto quit;
388
388
389 nodemap = PyDict_New();
389 nodemap = PyDict_New();
390 if (!nodemap)
390 if (!nodemap)
391 goto quit;
391 goto quit;
392
392
393 /* set up the cache return value */
393 /* set up the cache return value */
394 if (inlined) {
394 if (inlined) {
395 /* Note that the reference to data_obj is only borrowed */
395 /* Note that the reference to data_obj is only borrowed */
396 data_obj = PyTuple_GET_ITEM(args, 0);
396 data_obj = PyTuple_GET_ITEM(args, 0);
397 cache = Py_BuildValue("iO", 0, data_obj);
397 cache = Py_BuildValue("iO", 0, data_obj);
398 if (!cache)
398 if (!cache)
399 goto quit;
399 goto quit;
400 } else {
400 } else {
401 cache = Py_None;
401 cache = Py_None;
402 Py_INCREF(Py_None);
402 Py_INCREF(Py_None);
403 }
403 }
404
404
405 /* actually populate the index and the nodemap with data */
405 /* actually populate the index and the nodemap with data */
406 if (!_parse_index_ng (data, size, inlined, index, nodemap))
406 if (!_parse_index_ng (data, size, inlined, index, nodemap))
407 goto quit;
407 goto quit;
408
408
409 rval = Py_BuildValue("NNN", index, nodemap, cache);
409 rval = Py_BuildValue("NNN", index, nodemap, cache);
410 if (!rval)
410 if (!rval)
411 goto quit;
411 goto quit;
412 return rval;
412 return rval;
413
413
414 quit:
414 quit:
415 Py_XDECREF(index);
415 Py_XDECREF(index);
416 Py_XDECREF(nodemap);
416 Py_XDECREF(nodemap);
417 Py_XDECREF(cache);
417 Py_XDECREF(cache);
418 Py_XDECREF(rval);
418 Py_XDECREF(rval);
419 return NULL;
419 return NULL;
420 }
420 }
421
421
422
422
423 static char parsers_doc[] = "Efficient content parsing.";
423 static char parsers_doc[] = "Efficient content parsing.";
424
424
425 static PyMethodDef methods[] = {
425 static PyMethodDef methods[] = {
426 {"parse_manifest", parse_manifest, METH_VARARGS, "parse a manifest\n"},
426 {"parse_manifest", parse_manifest, METH_VARARGS, "parse a manifest\n"},
427 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
427 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
428 {"parse_index", parse_index, METH_VARARGS, "parse a revlog index\n"},
428 {"parse_index", parse_index, METH_VARARGS, "parse a revlog index\n"},
429 {NULL, NULL}
429 {NULL, NULL}
430 };
430 };
431
431
432 PyMODINIT_FUNC initparsers(void)
432 PyMODINIT_FUNC initparsers(void)
433 {
433 {
434 Py_InitModule3("parsers", methods, parsers_doc);
434 Py_InitModule3("parsers", methods, parsers_doc);
435 }
435 }
@@ -1,1345 +1,1345 b''
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 # 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, revlog, diffhelpers, copies
11 import base85, cmdutil, mdiff, util, revlog, diffhelpers, copies
12 import cStringIO, email.Parser, os, re, errno
12 import cStringIO, email.Parser, os, re, 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 class patchmeta:
146 class patchmeta:
147 """Patched file metadata
147 """Patched file metadata
148
148
149 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
149 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
150 or COPY. 'path' is patched file path. 'oldpath' is set to the
150 or COPY. 'path' is patched file path. 'oldpath' is set to the
151 origin file when 'op' is either COPY or RENAME, None otherwise. If
151 origin file when 'op' is either COPY or RENAME, None otherwise. If
152 file mode is changed, 'mode' is a tuple (islink, isexec) where
152 file mode is changed, 'mode' is a tuple (islink, isexec) where
153 'islink' is True if the file is a symlink and 'isexec' is True if
153 'islink' is True if the file is a symlink and 'isexec' is True if
154 the file is executable. Otherwise, 'mode' is None.
154 the file is executable. Otherwise, 'mode' is None.
155 """
155 """
156 def __init__(self, path):
156 def __init__(self, path):
157 self.path = path
157 self.path = path
158 self.oldpath = None
158 self.oldpath = None
159 self.mode = None
159 self.mode = None
160 self.op = 'MODIFY'
160 self.op = 'MODIFY'
161 self.lineno = 0
161 self.lineno = 0
162 self.binary = False
162 self.binary = False
163
163
164 def setmode(self, mode):
164 def setmode(self, mode):
165 islink = mode & 020000
165 islink = mode & 020000
166 isexec = mode & 0100
166 isexec = mode & 0100
167 self.mode = (islink, isexec)
167 self.mode = (islink, isexec)
168
168
169 def readgitpatch(lr):
169 def readgitpatch(lr):
170 """extract git-style metadata about patches from <patchname>"""
170 """extract git-style metadata about patches from <patchname>"""
171
171
172 # Filter patch for git information
172 # Filter patch for git information
173 gitre = re.compile('diff --git a/(.*) b/(.*)')
173 gitre = re.compile('diff --git a/(.*) b/(.*)')
174 gp = None
174 gp = None
175 gitpatches = []
175 gitpatches = []
176 # Can have a git patch with only metadata, causing patch to complain
176 # Can have a git patch with only metadata, causing patch to complain
177 dopatch = 0
177 dopatch = 0
178
178
179 lineno = 0
179 lineno = 0
180 for line in lr:
180 for line in lr:
181 lineno += 1
181 lineno += 1
182 if line.startswith('diff --git'):
182 if line.startswith('diff --git'):
183 m = gitre.match(line)
183 m = gitre.match(line)
184 if m:
184 if m:
185 if gp:
185 if gp:
186 gitpatches.append(gp)
186 gitpatches.append(gp)
187 src, dst = m.group(1, 2)
187 src, dst = m.group(1, 2)
188 gp = patchmeta(dst)
188 gp = patchmeta(dst)
189 gp.lineno = lineno
189 gp.lineno = lineno
190 elif gp:
190 elif gp:
191 if line.startswith('--- '):
191 if line.startswith('--- '):
192 if gp.op in ('COPY', 'RENAME'):
192 if gp.op in ('COPY', 'RENAME'):
193 dopatch |= GP_FILTER
193 dopatch |= GP_FILTER
194 gitpatches.append(gp)
194 gitpatches.append(gp)
195 gp = None
195 gp = None
196 dopatch |= GP_PATCH
196 dopatch |= GP_PATCH
197 continue
197 continue
198 if line.startswith('rename from '):
198 if line.startswith('rename from '):
199 gp.op = 'RENAME'
199 gp.op = 'RENAME'
200 gp.oldpath = line[12:].rstrip()
200 gp.oldpath = line[12:].rstrip()
201 elif line.startswith('rename to '):
201 elif line.startswith('rename to '):
202 gp.path = line[10:].rstrip()
202 gp.path = line[10:].rstrip()
203 elif line.startswith('copy from '):
203 elif line.startswith('copy from '):
204 gp.op = 'COPY'
204 gp.op = 'COPY'
205 gp.oldpath = line[10:].rstrip()
205 gp.oldpath = line[10:].rstrip()
206 elif line.startswith('copy to '):
206 elif line.startswith('copy to '):
207 gp.path = line[8:].rstrip()
207 gp.path = line[8:].rstrip()
208 elif line.startswith('deleted file'):
208 elif line.startswith('deleted file'):
209 gp.op = 'DELETE'
209 gp.op = 'DELETE'
210 elif line.startswith('new file mode '):
210 elif line.startswith('new file mode '):
211 gp.op = 'ADD'
211 gp.op = 'ADD'
212 gp.setmode(int(line.rstrip()[-6:], 8))
212 gp.setmode(int(line.rstrip()[-6:], 8))
213 elif line.startswith('new mode '):
213 elif line.startswith('new mode '):
214 gp.setmode(int(line.rstrip()[-6:], 8))
214 gp.setmode(int(line.rstrip()[-6:], 8))
215 elif line.startswith('GIT binary patch'):
215 elif line.startswith('GIT binary patch'):
216 dopatch |= GP_BINARY
216 dopatch |= GP_BINARY
217 gp.binary = True
217 gp.binary = True
218 if gp:
218 if gp:
219 gitpatches.append(gp)
219 gitpatches.append(gp)
220
220
221 if not gitpatches:
221 if not gitpatches:
222 dopatch = GP_PATCH
222 dopatch = GP_PATCH
223
223
224 return (dopatch, gitpatches)
224 return (dopatch, gitpatches)
225
225
226 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
226 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
227 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
227 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
228 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
228 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
229
229
230 class patchfile:
230 class patchfile:
231 def __init__(self, ui, fname, missing=False):
231 def __init__(self, ui, fname, missing=False):
232 self.fname = fname
232 self.fname = fname
233 self.ui = ui
233 self.ui = ui
234 self.lines = []
234 self.lines = []
235 self.exists = False
235 self.exists = False
236 self.missing = missing
236 self.missing = missing
237 if not missing:
237 if not missing:
238 try:
238 try:
239 fp = file(fname, 'rb')
239 fp = file(fname, 'rb')
240 self.lines = fp.readlines()
240 self.lines = fp.readlines()
241 self.exists = True
241 self.exists = True
242 except IOError:
242 except IOError:
243 pass
243 pass
244 else:
244 else:
245 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
245 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
246
246
247 if not self.exists:
247 if not self.exists:
248 dirname = os.path.dirname(fname)
248 dirname = os.path.dirname(fname)
249 if dirname and not os.path.isdir(dirname):
249 if dirname and not os.path.isdir(dirname):
250 os.makedirs(dirname)
250 os.makedirs(dirname)
251
251
252 self.hash = {}
252 self.hash = {}
253 self.dirty = 0
253 self.dirty = 0
254 self.offset = 0
254 self.offset = 0
255 self.rej = []
255 self.rej = []
256 self.fileprinted = False
256 self.fileprinted = False
257 self.printfile(False)
257 self.printfile(False)
258 self.hunks = 0
258 self.hunks = 0
259
259
260 def printfile(self, warn):
260 def printfile(self, warn):
261 if self.fileprinted:
261 if self.fileprinted:
262 return
262 return
263 if warn or self.ui.verbose:
263 if warn or self.ui.verbose:
264 self.fileprinted = True
264 self.fileprinted = True
265 s = _("patching file %s\n") % self.fname
265 s = _("patching file %s\n") % self.fname
266 if warn:
266 if warn:
267 self.ui.warn(s)
267 self.ui.warn(s)
268 else:
268 else:
269 self.ui.note(s)
269 self.ui.note(s)
270
270
271
271
272 def findlines(self, l, linenum):
272 def findlines(self, l, linenum):
273 # looks through the hash and finds candidate lines. The
273 # looks through the hash and finds candidate lines. The
274 # result is a list of line numbers sorted based on distance
274 # result is a list of line numbers sorted based on distance
275 # from linenum
275 # from linenum
276 def sorter(a, b):
276 def sorter(a, b):
277 vala = abs(a - linenum)
277 vala = abs(a - linenum)
278 valb = abs(b - linenum)
278 valb = abs(b - linenum)
279 return cmp(vala, valb)
279 return cmp(vala, valb)
280
280
281 try:
281 try:
282 cand = self.hash[l]
282 cand = self.hash[l]
283 except:
283 except:
284 return []
284 return []
285
285
286 if len(cand) > 1:
286 if len(cand) > 1:
287 # resort our list of potentials forward then back.
287 # resort our list of potentials forward then back.
288 cand.sort(sorter)
288 cand.sort(sorter)
289 return cand
289 return cand
290
290
291 def hashlines(self):
291 def hashlines(self):
292 self.hash = {}
292 self.hash = {}
293 for x in xrange(len(self.lines)):
293 for x in xrange(len(self.lines)):
294 s = self.lines[x]
294 s = self.lines[x]
295 self.hash.setdefault(s, []).append(x)
295 self.hash.setdefault(s, []).append(x)
296
296
297 def write_rej(self):
297 def write_rej(self):
298 # our rejects are a little different from patch(1). This always
298 # our rejects are a little different from patch(1). This always
299 # creates rejects in the same form as the original patch. A file
299 # creates rejects in the same form as the original patch. A file
300 # header is inserted so that you can run the reject through patch again
300 # header is inserted so that you can run the reject through patch again
301 # without having to type the filename.
301 # without having to type the filename.
302
302
303 if not self.rej:
303 if not self.rej:
304 return
304 return
305
305
306 fname = self.fname + ".rej"
306 fname = self.fname + ".rej"
307 self.ui.warn(
307 self.ui.warn(
308 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
308 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
309 (len(self.rej), self.hunks, fname))
309 (len(self.rej), self.hunks, fname))
310 try: os.unlink(fname)
310 try: os.unlink(fname)
311 except:
311 except:
312 pass
312 pass
313 fp = file(fname, 'wb')
313 fp = file(fname, 'wb')
314 base = os.path.basename(self.fname)
314 base = os.path.basename(self.fname)
315 fp.write("--- %s\n+++ %s\n" % (base, base))
315 fp.write("--- %s\n+++ %s\n" % (base, base))
316 for x in self.rej:
316 for x in self.rej:
317 for l in x.hunk:
317 for l in x.hunk:
318 fp.write(l)
318 fp.write(l)
319 if l[-1] != '\n':
319 if l[-1] != '\n':
320 fp.write("\n\ No newline at end of file\n")
320 fp.write("\n\ No newline at end of file\n")
321
321
322 def write(self, dest=None):
322 def write(self, dest=None):
323 if self.dirty:
323 if self.dirty:
324 if not dest:
324 if not dest:
325 dest = self.fname
325 dest = self.fname
326 st = None
326 st = None
327 try:
327 try:
328 st = os.lstat(dest)
328 st = os.lstat(dest)
329 except OSError, inst:
329 except OSError, inst:
330 if inst.errno != errno.ENOENT:
330 if inst.errno != errno.ENOENT:
331 raise
331 raise
332 if st and st.st_nlink > 1:
332 if st and st.st_nlink > 1:
333 os.unlink(dest)
333 os.unlink(dest)
334 fp = file(dest, 'wb')
334 fp = file(dest, 'wb')
335 if st and st.st_nlink > 1:
335 if st and st.st_nlink > 1:
336 os.chmod(dest, st.st_mode)
336 os.chmod(dest, st.st_mode)
337 fp.writelines(self.lines)
337 fp.writelines(self.lines)
338 fp.close()
338 fp.close()
339
339
340 def close(self):
340 def close(self):
341 self.write()
341 self.write()
342 self.write_rej()
342 self.write_rej()
343
343
344 def apply(self, h, reverse):
344 def apply(self, h, reverse):
345 if not h.complete():
345 if not h.complete():
346 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
346 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
347 (h.number, h.desc, len(h.a), h.lena, len(h.b),
347 (h.number, h.desc, len(h.a), h.lena, len(h.b),
348 h.lenb))
348 h.lenb))
349
349
350 self.hunks += 1
350 self.hunks += 1
351 if reverse:
351 if reverse:
352 h.reverse()
352 h.reverse()
353
353
354 if self.missing:
354 if self.missing:
355 self.rej.append(h)
355 self.rej.append(h)
356 return -1
356 return -1
357
357
358 if self.exists and h.createfile():
358 if self.exists and h.createfile():
359 self.ui.warn(_("file %s already exists\n") % self.fname)
359 self.ui.warn(_("file %s already exists\n") % self.fname)
360 self.rej.append(h)
360 self.rej.append(h)
361 return -1
361 return -1
362
362
363 if isinstance(h, binhunk):
363 if isinstance(h, binhunk):
364 if h.rmfile():
364 if h.rmfile():
365 os.unlink(self.fname)
365 os.unlink(self.fname)
366 else:
366 else:
367 self.lines[:] = h.new()
367 self.lines[:] = h.new()
368 self.offset += len(h.new())
368 self.offset += len(h.new())
369 self.dirty = 1
369 self.dirty = 1
370 return 0
370 return 0
371
371
372 # fast case first, no offsets, no fuzz
372 # fast case first, no offsets, no fuzz
373 old = h.old()
373 old = h.old()
374 # patch starts counting at 1 unless we are adding the file
374 # patch starts counting at 1 unless we are adding the file
375 if h.starta == 0:
375 if h.starta == 0:
376 start = 0
376 start = 0
377 else:
377 else:
378 start = h.starta + self.offset - 1
378 start = h.starta + self.offset - 1
379 orig_start = start
379 orig_start = start
380 if diffhelpers.testhunk(old, self.lines, start) == 0:
380 if diffhelpers.testhunk(old, self.lines, start) == 0:
381 if h.rmfile():
381 if h.rmfile():
382 os.unlink(self.fname)
382 os.unlink(self.fname)
383 else:
383 else:
384 self.lines[start : start + h.lena] = h.new()
384 self.lines[start : start + h.lena] = h.new()
385 self.offset += h.lenb - h.lena
385 self.offset += h.lenb - h.lena
386 self.dirty = 1
386 self.dirty = 1
387 return 0
387 return 0
388
388
389 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
389 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
390 self.hashlines()
390 self.hashlines()
391 if h.hunk[-1][0] != ' ':
391 if h.hunk[-1][0] != ' ':
392 # if the hunk tried to put something at the bottom of the file
392 # if the hunk tried to put something at the bottom of the file
393 # override the start line and use eof here
393 # override the start line and use eof here
394 search_start = len(self.lines)
394 search_start = len(self.lines)
395 else:
395 else:
396 search_start = orig_start
396 search_start = orig_start
397
397
398 for fuzzlen in xrange(3):
398 for fuzzlen in xrange(3):
399 for toponly in [ True, False ]:
399 for toponly in [ True, False ]:
400 old = h.old(fuzzlen, toponly)
400 old = h.old(fuzzlen, toponly)
401
401
402 cand = self.findlines(old[0][1:], search_start)
402 cand = self.findlines(old[0][1:], search_start)
403 for l in cand:
403 for l in cand:
404 if diffhelpers.testhunk(old, self.lines, l) == 0:
404 if diffhelpers.testhunk(old, self.lines, l) == 0:
405 newlines = h.new(fuzzlen, toponly)
405 newlines = h.new(fuzzlen, toponly)
406 self.lines[l : l + len(old)] = newlines
406 self.lines[l : l + len(old)] = newlines
407 self.offset += len(newlines) - len(old)
407 self.offset += len(newlines) - len(old)
408 self.dirty = 1
408 self.dirty = 1
409 if fuzzlen:
409 if fuzzlen:
410 fuzzstr = "with fuzz %d " % fuzzlen
410 fuzzstr = "with fuzz %d " % fuzzlen
411 f = self.ui.warn
411 f = self.ui.warn
412 self.printfile(True)
412 self.printfile(True)
413 else:
413 else:
414 fuzzstr = ""
414 fuzzstr = ""
415 f = self.ui.note
415 f = self.ui.note
416 offset = l - orig_start - fuzzlen
416 offset = l - orig_start - fuzzlen
417 if offset == 1:
417 if offset == 1:
418 linestr = "line"
418 linestr = "line"
419 else:
419 else:
420 linestr = "lines"
420 linestr = "lines"
421 f(_("Hunk #%d succeeded at %d %s(offset %d %s).\n") %
421 f(_("Hunk #%d succeeded at %d %s(offset %d %s).\n") %
422 (h.number, l+1, fuzzstr, offset, linestr))
422 (h.number, l+1, fuzzstr, offset, linestr))
423 return fuzzlen
423 return fuzzlen
424 self.printfile(True)
424 self.printfile(True)
425 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
425 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
426 self.rej.append(h)
426 self.rej.append(h)
427 return -1
427 return -1
428
428
429 class hunk:
429 class hunk:
430 def __init__(self, desc, num, lr, context, create=False, remove=False):
430 def __init__(self, desc, num, lr, context, create=False, remove=False):
431 self.number = num
431 self.number = num
432 self.desc = desc
432 self.desc = desc
433 self.hunk = [ desc ]
433 self.hunk = [ desc ]
434 self.a = []
434 self.a = []
435 self.b = []
435 self.b = []
436 if context:
436 if context:
437 self.read_context_hunk(lr)
437 self.read_context_hunk(lr)
438 else:
438 else:
439 self.read_unified_hunk(lr)
439 self.read_unified_hunk(lr)
440 self.create = create
440 self.create = create
441 self.remove = remove and not create
441 self.remove = remove and not create
442
442
443 def read_unified_hunk(self, lr):
443 def read_unified_hunk(self, lr):
444 m = unidesc.match(self.desc)
444 m = unidesc.match(self.desc)
445 if not m:
445 if not m:
446 raise PatchError(_("bad hunk #%d") % self.number)
446 raise PatchError(_("bad hunk #%d") % self.number)
447 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
447 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
448 if self.lena == None:
448 if self.lena == None:
449 self.lena = 1
449 self.lena = 1
450 else:
450 else:
451 self.lena = int(self.lena)
451 self.lena = int(self.lena)
452 if self.lenb == None:
452 if self.lenb == None:
453 self.lenb = 1
453 self.lenb = 1
454 else:
454 else:
455 self.lenb = int(self.lenb)
455 self.lenb = int(self.lenb)
456 self.starta = int(self.starta)
456 self.starta = int(self.starta)
457 self.startb = int(self.startb)
457 self.startb = int(self.startb)
458 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
458 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
459 # if we hit eof before finishing out the hunk, the last line will
459 # if we hit eof before finishing out the hunk, the last line will
460 # be zero length. Lets try to fix it up.
460 # be zero length. Lets try to fix it up.
461 while len(self.hunk[-1]) == 0:
461 while len(self.hunk[-1]) == 0:
462 del self.hunk[-1]
462 del self.hunk[-1]
463 del self.a[-1]
463 del self.a[-1]
464 del self.b[-1]
464 del self.b[-1]
465 self.lena -= 1
465 self.lena -= 1
466 self.lenb -= 1
466 self.lenb -= 1
467
467
468 def read_context_hunk(self, lr):
468 def read_context_hunk(self, lr):
469 self.desc = lr.readline()
469 self.desc = lr.readline()
470 m = contextdesc.match(self.desc)
470 m = contextdesc.match(self.desc)
471 if not m:
471 if not m:
472 raise PatchError(_("bad hunk #%d") % self.number)
472 raise PatchError(_("bad hunk #%d") % self.number)
473 foo, self.starta, foo2, aend, foo3 = m.groups()
473 foo, self.starta, foo2, aend, foo3 = m.groups()
474 self.starta = int(self.starta)
474 self.starta = int(self.starta)
475 if aend == None:
475 if aend == None:
476 aend = self.starta
476 aend = self.starta
477 self.lena = int(aend) - self.starta
477 self.lena = int(aend) - self.starta
478 if self.starta:
478 if self.starta:
479 self.lena += 1
479 self.lena += 1
480 for x in xrange(self.lena):
480 for x in xrange(self.lena):
481 l = lr.readline()
481 l = lr.readline()
482 if l.startswith('---'):
482 if l.startswith('---'):
483 lr.push(l)
483 lr.push(l)
484 break
484 break
485 s = l[2:]
485 s = l[2:]
486 if l.startswith('- ') or l.startswith('! '):
486 if l.startswith('- ') or l.startswith('! '):
487 u = '-' + s
487 u = '-' + s
488 elif l.startswith(' '):
488 elif l.startswith(' '):
489 u = ' ' + s
489 u = ' ' + s
490 else:
490 else:
491 raise PatchError(_("bad hunk #%d old text line %d") %
491 raise PatchError(_("bad hunk #%d old text line %d") %
492 (self.number, x))
492 (self.number, x))
493 self.a.append(u)
493 self.a.append(u)
494 self.hunk.append(u)
494 self.hunk.append(u)
495
495
496 l = lr.readline()
496 l = lr.readline()
497 if l.startswith('\ '):
497 if l.startswith('\ '):
498 s = self.a[-1][:-1]
498 s = self.a[-1][:-1]
499 self.a[-1] = s
499 self.a[-1] = s
500 self.hunk[-1] = s
500 self.hunk[-1] = s
501 l = lr.readline()
501 l = lr.readline()
502 m = contextdesc.match(l)
502 m = contextdesc.match(l)
503 if not m:
503 if not m:
504 raise PatchError(_("bad hunk #%d") % self.number)
504 raise PatchError(_("bad hunk #%d") % self.number)
505 foo, self.startb, foo2, bend, foo3 = m.groups()
505 foo, self.startb, foo2, bend, foo3 = m.groups()
506 self.startb = int(self.startb)
506 self.startb = int(self.startb)
507 if bend == None:
507 if bend == None:
508 bend = self.startb
508 bend = self.startb
509 self.lenb = int(bend) - self.startb
509 self.lenb = int(bend) - self.startb
510 if self.startb:
510 if self.startb:
511 self.lenb += 1
511 self.lenb += 1
512 hunki = 1
512 hunki = 1
513 for x in xrange(self.lenb):
513 for x in xrange(self.lenb):
514 l = lr.readline()
514 l = lr.readline()
515 if l.startswith('\ '):
515 if l.startswith('\ '):
516 s = self.b[-1][:-1]
516 s = self.b[-1][:-1]
517 self.b[-1] = s
517 self.b[-1] = s
518 self.hunk[hunki-1] = s
518 self.hunk[hunki-1] = s
519 continue
519 continue
520 if not l:
520 if not l:
521 lr.push(l)
521 lr.push(l)
522 break
522 break
523 s = l[2:]
523 s = l[2:]
524 if l.startswith('+ ') or l.startswith('! '):
524 if l.startswith('+ ') or l.startswith('! '):
525 u = '+' + s
525 u = '+' + s
526 elif l.startswith(' '):
526 elif l.startswith(' '):
527 u = ' ' + s
527 u = ' ' + s
528 elif len(self.b) == 0:
528 elif len(self.b) == 0:
529 # this can happen when the hunk does not add any lines
529 # this can happen when the hunk does not add any lines
530 lr.push(l)
530 lr.push(l)
531 break
531 break
532 else:
532 else:
533 raise PatchError(_("bad hunk #%d old text line %d") %
533 raise PatchError(_("bad hunk #%d old text line %d") %
534 (self.number, x))
534 (self.number, x))
535 self.b.append(s)
535 self.b.append(s)
536 while True:
536 while True:
537 if hunki >= len(self.hunk):
537 if hunki >= len(self.hunk):
538 h = ""
538 h = ""
539 else:
539 else:
540 h = self.hunk[hunki]
540 h = self.hunk[hunki]
541 hunki += 1
541 hunki += 1
542 if h == u:
542 if h == u:
543 break
543 break
544 elif h.startswith('-'):
544 elif h.startswith('-'):
545 continue
545 continue
546 else:
546 else:
547 self.hunk.insert(hunki-1, u)
547 self.hunk.insert(hunki-1, u)
548 break
548 break
549
549
550 if not self.a:
550 if not self.a:
551 # this happens when lines were only added to the hunk
551 # this happens when lines were only added to the hunk
552 for x in self.hunk:
552 for x in self.hunk:
553 if x.startswith('-') or x.startswith(' '):
553 if x.startswith('-') or x.startswith(' '):
554 self.a.append(x)
554 self.a.append(x)
555 if not self.b:
555 if not self.b:
556 # this happens when lines were only deleted from the hunk
556 # this happens when lines were only deleted from the hunk
557 for x in self.hunk:
557 for x in self.hunk:
558 if x.startswith('+') or x.startswith(' '):
558 if x.startswith('+') or x.startswith(' '):
559 self.b.append(x[1:])
559 self.b.append(x[1:])
560 # @@ -start,len +start,len @@
560 # @@ -start,len +start,len @@
561 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
561 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
562 self.startb, self.lenb)
562 self.startb, self.lenb)
563 self.hunk[0] = self.desc
563 self.hunk[0] = self.desc
564
564
565 def reverse(self):
565 def reverse(self):
566 self.create, self.remove = self.remove, self.create
566 self.create, self.remove = self.remove, self.create
567 origlena = self.lena
567 origlena = self.lena
568 origstarta = self.starta
568 origstarta = self.starta
569 self.lena = self.lenb
569 self.lena = self.lenb
570 self.starta = self.startb
570 self.starta = self.startb
571 self.lenb = origlena
571 self.lenb = origlena
572 self.startb = origstarta
572 self.startb = origstarta
573 self.a = []
573 self.a = []
574 self.b = []
574 self.b = []
575 # self.hunk[0] is the @@ description
575 # self.hunk[0] is the @@ description
576 for x in xrange(1, len(self.hunk)):
576 for x in xrange(1, len(self.hunk)):
577 o = self.hunk[x]
577 o = self.hunk[x]
578 if o.startswith('-'):
578 if o.startswith('-'):
579 n = '+' + o[1:]
579 n = '+' + o[1:]
580 self.b.append(o[1:])
580 self.b.append(o[1:])
581 elif o.startswith('+'):
581 elif o.startswith('+'):
582 n = '-' + o[1:]
582 n = '-' + o[1:]
583 self.a.append(n)
583 self.a.append(n)
584 else:
584 else:
585 n = o
585 n = o
586 self.b.append(o[1:])
586 self.b.append(o[1:])
587 self.a.append(o)
587 self.a.append(o)
588 self.hunk[x] = o
588 self.hunk[x] = o
589
589
590 def fix_newline(self):
590 def fix_newline(self):
591 diffhelpers.fix_newline(self.hunk, self.a, self.b)
591 diffhelpers.fix_newline(self.hunk, self.a, self.b)
592
592
593 def complete(self):
593 def complete(self):
594 return len(self.a) == self.lena and len(self.b) == self.lenb
594 return len(self.a) == self.lena and len(self.b) == self.lenb
595
595
596 def createfile(self):
596 def createfile(self):
597 return self.starta == 0 and self.lena == 0 and self.create
597 return self.starta == 0 and self.lena == 0 and self.create
598
598
599 def rmfile(self):
599 def rmfile(self):
600 return self.startb == 0 and self.lenb == 0 and self.remove
600 return self.startb == 0 and self.lenb == 0 and self.remove
601
601
602 def fuzzit(self, l, fuzz, toponly):
602 def fuzzit(self, l, fuzz, toponly):
603 # this removes context lines from the top and bottom of list 'l'. It
603 # this removes context lines from the top and bottom of list 'l'. It
604 # checks the hunk to make sure only context lines are removed, and then
604 # checks the hunk to make sure only context lines are removed, and then
605 # returns a new shortened list of lines.
605 # returns a new shortened list of lines.
606 fuzz = min(fuzz, len(l)-1)
606 fuzz = min(fuzz, len(l)-1)
607 if fuzz:
607 if fuzz:
608 top = 0
608 top = 0
609 bot = 0
609 bot = 0
610 hlen = len(self.hunk)
610 hlen = len(self.hunk)
611 for x in xrange(hlen-1):
611 for x in xrange(hlen-1):
612 # the hunk starts with the @@ line, so use x+1
612 # the hunk starts with the @@ line, so use x+1
613 if self.hunk[x+1][0] == ' ':
613 if self.hunk[x+1][0] == ' ':
614 top += 1
614 top += 1
615 else:
615 else:
616 break
616 break
617 if not toponly:
617 if not toponly:
618 for x in xrange(hlen-1):
618 for x in xrange(hlen-1):
619 if self.hunk[hlen-bot-1][0] == ' ':
619 if self.hunk[hlen-bot-1][0] == ' ':
620 bot += 1
620 bot += 1
621 else:
621 else:
622 break
622 break
623
623
624 # top and bot now count context in the hunk
624 # top and bot now count context in the hunk
625 # adjust them if either one is short
625 # adjust them if either one is short
626 context = max(top, bot, 3)
626 context = max(top, bot, 3)
627 if bot < context:
627 if bot < context:
628 bot = max(0, fuzz - (context - bot))
628 bot = max(0, fuzz - (context - bot))
629 else:
629 else:
630 bot = min(fuzz, bot)
630 bot = min(fuzz, bot)
631 if top < context:
631 if top < context:
632 top = max(0, fuzz - (context - top))
632 top = max(0, fuzz - (context - top))
633 else:
633 else:
634 top = min(fuzz, top)
634 top = min(fuzz, top)
635
635
636 return l[top:len(l)-bot]
636 return l[top:len(l)-bot]
637 return l
637 return l
638
638
639 def old(self, fuzz=0, toponly=False):
639 def old(self, fuzz=0, toponly=False):
640 return self.fuzzit(self.a, fuzz, toponly)
640 return self.fuzzit(self.a, fuzz, toponly)
641
641
642 def newctrl(self):
642 def newctrl(self):
643 res = []
643 res = []
644 for x in self.hunk:
644 for x in self.hunk:
645 c = x[0]
645 c = x[0]
646 if c == ' ' or c == '+':
646 if c == ' ' or c == '+':
647 res.append(x)
647 res.append(x)
648 return res
648 return res
649
649
650 def new(self, fuzz=0, toponly=False):
650 def new(self, fuzz=0, toponly=False):
651 return self.fuzzit(self.b, fuzz, toponly)
651 return self.fuzzit(self.b, fuzz, toponly)
652
652
653 class binhunk:
653 class binhunk:
654 'A binary patch file. Only understands literals so far.'
654 'A binary patch file. Only understands literals so far.'
655 def __init__(self, gitpatch):
655 def __init__(self, gitpatch):
656 self.gitpatch = gitpatch
656 self.gitpatch = gitpatch
657 self.text = None
657 self.text = None
658 self.hunk = ['GIT binary patch\n']
658 self.hunk = ['GIT binary patch\n']
659
659
660 def createfile(self):
660 def createfile(self):
661 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
661 return self.gitpatch.op in ('ADD', 'RENAME', 'COPY')
662
662
663 def rmfile(self):
663 def rmfile(self):
664 return self.gitpatch.op == 'DELETE'
664 return self.gitpatch.op == 'DELETE'
665
665
666 def complete(self):
666 def complete(self):
667 return self.text is not None
667 return self.text is not None
668
668
669 def new(self):
669 def new(self):
670 return [self.text]
670 return [self.text]
671
671
672 def extract(self, lr):
672 def extract(self, lr):
673 line = lr.readline()
673 line = lr.readline()
674 self.hunk.append(line)
674 self.hunk.append(line)
675 while line and not line.startswith('literal '):
675 while line and not line.startswith('literal '):
676 line = lr.readline()
676 line = lr.readline()
677 self.hunk.append(line)
677 self.hunk.append(line)
678 if not line:
678 if not line:
679 raise PatchError(_('could not extract binary patch'))
679 raise PatchError(_('could not extract binary patch'))
680 size = int(line[8:].rstrip())
680 size = int(line[8:].rstrip())
681 dec = []
681 dec = []
682 line = lr.readline()
682 line = lr.readline()
683 self.hunk.append(line)
683 self.hunk.append(line)
684 while len(line) > 1:
684 while len(line) > 1:
685 l = line[0]
685 l = line[0]
686 if l <= 'Z' and l >= 'A':
686 if l <= 'Z' and l >= 'A':
687 l = ord(l) - ord('A') + 1
687 l = ord(l) - ord('A') + 1
688 else:
688 else:
689 l = ord(l) - ord('a') + 27
689 l = ord(l) - ord('a') + 27
690 dec.append(base85.b85decode(line[1:-1])[:l])
690 dec.append(base85.b85decode(line[1:-1])[:l])
691 line = lr.readline()
691 line = lr.readline()
692 self.hunk.append(line)
692 self.hunk.append(line)
693 text = zlib.decompress(''.join(dec))
693 text = zlib.decompress(''.join(dec))
694 if len(text) != size:
694 if len(text) != size:
695 raise PatchError(_('binary patch is %d bytes, not %d') %
695 raise PatchError(_('binary patch is %d bytes, not %d') %
696 len(text), size)
696 len(text), size)
697 self.text = text
697 self.text = text
698
698
699 def parsefilename(str):
699 def parsefilename(str):
700 # --- filename \t|space stuff
700 # --- filename \t|space stuff
701 s = str[4:].rstrip('\r\n')
701 s = str[4:].rstrip('\r\n')
702 i = s.find('\t')
702 i = s.find('\t')
703 if i < 0:
703 if i < 0:
704 i = s.find(' ')
704 i = s.find(' ')
705 if i < 0:
705 if i < 0:
706 return s
706 return s
707 return s[:i]
707 return s[:i]
708
708
709 def selectfile(afile_orig, bfile_orig, hunk, strip, reverse):
709 def selectfile(afile_orig, bfile_orig, hunk, strip, reverse):
710 def pathstrip(path, count=1):
710 def pathstrip(path, count=1):
711 pathlen = len(path)
711 pathlen = len(path)
712 i = 0
712 i = 0
713 if count == 0:
713 if count == 0:
714 return '', path.rstrip()
714 return '', path.rstrip()
715 while count > 0:
715 while count > 0:
716 i = path.find('/', i)
716 i = path.find('/', i)
717 if i == -1:
717 if i == -1:
718 raise PatchError(_("unable to strip away %d dirs from %s") %
718 raise PatchError(_("unable to strip away %d dirs from %s") %
719 (count, path))
719 (count, path))
720 i += 1
720 i += 1
721 # consume '//' in the path
721 # consume '//' in the path
722 while i < pathlen - 1 and path[i] == '/':
722 while i < pathlen - 1 and path[i] == '/':
723 i += 1
723 i += 1
724 count -= 1
724 count -= 1
725 return path[:i].lstrip(), path[i:].rstrip()
725 return path[:i].lstrip(), path[i:].rstrip()
726
726
727 nulla = afile_orig == "/dev/null"
727 nulla = afile_orig == "/dev/null"
728 nullb = bfile_orig == "/dev/null"
728 nullb = bfile_orig == "/dev/null"
729 abase, afile = pathstrip(afile_orig, strip)
729 abase, afile = pathstrip(afile_orig, strip)
730 gooda = not nulla and os.path.exists(afile)
730 gooda = not nulla and os.path.exists(afile)
731 bbase, bfile = pathstrip(bfile_orig, strip)
731 bbase, bfile = pathstrip(bfile_orig, strip)
732 if afile == bfile:
732 if afile == bfile:
733 goodb = gooda
733 goodb = gooda
734 else:
734 else:
735 goodb = not nullb and os.path.exists(bfile)
735 goodb = not nullb and os.path.exists(bfile)
736 createfunc = hunk.createfile
736 createfunc = hunk.createfile
737 if reverse:
737 if reverse:
738 createfunc = hunk.rmfile
738 createfunc = hunk.rmfile
739 missing = not goodb and not gooda and not createfunc()
739 missing = not goodb and not gooda and not createfunc()
740 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
740 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
741 # diff is between a file and its backup. In this case, the original
741 # diff is between a file and its backup. In this case, the original
742 # file should be patched (see original mpatch code).
742 # file should be patched (see original mpatch code).
743 isbackup = (abase == bbase and bfile.startswith(afile))
743 isbackup = (abase == bbase and bfile.startswith(afile))
744 fname = None
744 fname = None
745 if not missing:
745 if not missing:
746 if gooda and goodb:
746 if gooda and goodb:
747 fname = isbackup and afile or bfile
747 fname = isbackup and afile or bfile
748 elif gooda:
748 elif gooda:
749 fname = afile
749 fname = afile
750
750
751 if not fname:
751 if not fname:
752 if not nullb:
752 if not nullb:
753 fname = isbackup and afile or bfile
753 fname = isbackup and afile or bfile
754 elif not nulla:
754 elif not nulla:
755 fname = afile
755 fname = afile
756 else:
756 else:
757 raise PatchError(_("undefined source and destination files"))
757 raise PatchError(_("undefined source and destination files"))
758
758
759 return fname, missing
759 return fname, missing
760
760
761 class linereader:
761 class linereader:
762 # simple class to allow pushing lines back into the input stream
762 # simple class to allow pushing lines back into the input stream
763 def __init__(self, fp):
763 def __init__(self, fp):
764 self.fp = fp
764 self.fp = fp
765 self.buf = []
765 self.buf = []
766
766
767 def push(self, line):
767 def push(self, line):
768 if line is not None:
768 if line is not None:
769 self.buf.append(line)
769 self.buf.append(line)
770
770
771 def readline(self):
771 def readline(self):
772 if self.buf:
772 if self.buf:
773 l = self.buf[0]
773 l = self.buf[0]
774 del self.buf[0]
774 del self.buf[0]
775 return l
775 return l
776 return self.fp.readline()
776 return self.fp.readline()
777
777
778 def __iter__(self):
778 def __iter__(self):
779 while 1:
779 while 1:
780 l = self.readline()
780 l = self.readline()
781 if not l:
781 if not l:
782 break
782 break
783 yield l
783 yield l
784
784
785 def scangitpatch(lr, firstline):
785 def scangitpatch(lr, firstline):
786 """
786 """
787 Git patches can emit:
787 Git patches can emit:
788 - rename a to b
788 - rename a to b
789 - change b
789 - change b
790 - copy a to c
790 - copy a to c
791 - change c
791 - change c
792
792
793 We cannot apply this sequence as-is, the renamed 'a' could not be
793 We cannot apply this sequence as-is, the renamed 'a' could not be
794 found for it would have been renamed already. And we cannot copy
794 found for it would have been renamed already. And we cannot copy
795 from 'b' instead because 'b' would have been changed already. So
795 from 'b' instead because 'b' would have been changed already. So
796 we scan the git patch for copy and rename commands so we can
796 we scan the git patch for copy and rename commands so we can
797 perform the copies ahead of time.
797 perform the copies ahead of time.
798 """
798 """
799 pos = 0
799 pos = 0
800 try:
800 try:
801 pos = lr.fp.tell()
801 pos = lr.fp.tell()
802 fp = lr.fp
802 fp = lr.fp
803 except IOError:
803 except IOError:
804 fp = cStringIO.StringIO(lr.fp.read())
804 fp = cStringIO.StringIO(lr.fp.read())
805 gitlr = linereader(fp)
805 gitlr = linereader(fp)
806 gitlr.push(firstline)
806 gitlr.push(firstline)
807 (dopatch, gitpatches) = readgitpatch(gitlr)
807 (dopatch, gitpatches) = readgitpatch(gitlr)
808 fp.seek(pos)
808 fp.seek(pos)
809 return dopatch, gitpatches
809 return dopatch, gitpatches
810
810
811 def iterhunks(ui, fp, sourcefile=None):
811 def iterhunks(ui, fp, sourcefile=None):
812 """Read a patch and yield the following events:
812 """Read a patch and yield the following events:
813 - ("file", afile, bfile, firsthunk): select a new target file.
813 - ("file", afile, bfile, firsthunk): select a new target file.
814 - ("hunk", hunk): a new hunk is ready to be applied, follows a
814 - ("hunk", hunk): a new hunk is ready to be applied, follows a
815 "file" event.
815 "file" event.
816 - ("git", gitchanges): current diff is in git format, gitchanges
816 - ("git", gitchanges): current diff is in git format, gitchanges
817 maps filenames to gitpatch records. Unique event.
817 maps filenames to gitpatch records. Unique event.
818 """
818 """
819 changed = {}
819 changed = {}
820 current_hunk = None
820 current_hunk = None
821 afile = ""
821 afile = ""
822 bfile = ""
822 bfile = ""
823 state = None
823 state = None
824 hunknum = 0
824 hunknum = 0
825 emitfile = False
825 emitfile = False
826
826
827 git = False
827 git = False
828 gitre = re.compile('diff --git (a/.*) (b/.*)')
828 gitre = re.compile('diff --git (a/.*) (b/.*)')
829
829
830 # our states
830 # our states
831 BFILE = 1
831 BFILE = 1
832 context = None
832 context = None
833 lr = linereader(fp)
833 lr = linereader(fp)
834 dopatch = True
834 dopatch = True
835 # gitworkdone is True if a git operation (copy, rename, ...) was
835 # gitworkdone is True if a git operation (copy, rename, ...) was
836 # performed already for the current file. Useful when the file
836 # performed already for the current file. Useful when the file
837 # section may have no hunk.
837 # section may have no hunk.
838 gitworkdone = False
838 gitworkdone = False
839
839
840 while True:
840 while True:
841 newfile = False
841 newfile = False
842 x = lr.readline()
842 x = lr.readline()
843 if not x:
843 if not x:
844 break
844 break
845 if current_hunk:
845 if current_hunk:
846 if x.startswith('\ '):
846 if x.startswith('\ '):
847 current_hunk.fix_newline()
847 current_hunk.fix_newline()
848 yield 'hunk', current_hunk
848 yield 'hunk', current_hunk
849 current_hunk = None
849 current_hunk = None
850 gitworkdone = False
850 gitworkdone = False
851 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
851 if ((sourcefile or state == BFILE) and ((not context and x[0] == '@') or
852 ((context or context == None) and x.startswith('***************')))):
852 ((context or context == None) and x.startswith('***************')))):
853 try:
853 try:
854 if context == None and x.startswith('***************'):
854 if context == None and x.startswith('***************'):
855 context = True
855 context = True
856 gpatch = changed.get(bfile[2:])
856 gpatch = changed.get(bfile[2:])
857 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
857 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
858 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
858 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
859 current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
859 current_hunk = hunk(x, hunknum + 1, lr, context, create, remove)
860 except PatchError, err:
860 except PatchError, err:
861 ui.debug(err)
861 ui.debug(err)
862 current_hunk = None
862 current_hunk = None
863 continue
863 continue
864 hunknum += 1
864 hunknum += 1
865 if emitfile:
865 if emitfile:
866 emitfile = False
866 emitfile = False
867 yield 'file', (afile, bfile, current_hunk)
867 yield 'file', (afile, bfile, current_hunk)
868 elif state == BFILE and x.startswith('GIT binary patch'):
868 elif state == BFILE and x.startswith('GIT binary patch'):
869 current_hunk = binhunk(changed[bfile[2:]])
869 current_hunk = binhunk(changed[bfile[2:]])
870 hunknum += 1
870 hunknum += 1
871 if emitfile:
871 if emitfile:
872 emitfile = False
872 emitfile = False
873 yield 'file', (afile, bfile, current_hunk)
873 yield 'file', (afile, bfile, current_hunk)
874 current_hunk.extract(lr)
874 current_hunk.extract(lr)
875 elif x.startswith('diff --git'):
875 elif x.startswith('diff --git'):
876 # check for git diff, scanning the whole patch file if needed
876 # check for git diff, scanning the whole patch file if needed
877 m = gitre.match(x)
877 m = gitre.match(x)
878 if m:
878 if m:
879 afile, bfile = m.group(1, 2)
879 afile, bfile = m.group(1, 2)
880 if not git:
880 if not git:
881 git = True
881 git = True
882 dopatch, gitpatches = scangitpatch(lr, x)
882 dopatch, gitpatches = scangitpatch(lr, x)
883 yield 'git', gitpatches
883 yield 'git', gitpatches
884 for gp in gitpatches:
884 for gp in gitpatches:
885 changed[gp.path] = gp
885 changed[gp.path] = gp
886 # else error?
886 # else error?
887 # copy/rename + modify should modify target, not source
887 # copy/rename + modify should modify target, not source
888 gp = changed.get(bfile[2:])
888 gp = changed.get(bfile[2:])
889 if gp and gp.op in ('COPY', 'DELETE', 'RENAME'):
889 if gp and gp.op in ('COPY', 'DELETE', 'RENAME'):
890 afile = bfile
890 afile = bfile
891 gitworkdone = True
891 gitworkdone = True
892 newfile = True
892 newfile = True
893 elif x.startswith('---'):
893 elif x.startswith('---'):
894 # check for a unified diff
894 # check for a unified diff
895 l2 = lr.readline()
895 l2 = lr.readline()
896 if not l2.startswith('+++'):
896 if not l2.startswith('+++'):
897 lr.push(l2)
897 lr.push(l2)
898 continue
898 continue
899 newfile = True
899 newfile = True
900 context = False
900 context = False
901 afile = parsefilename(x)
901 afile = parsefilename(x)
902 bfile = parsefilename(l2)
902 bfile = parsefilename(l2)
903 elif x.startswith('***'):
903 elif x.startswith('***'):
904 # check for a context diff
904 # check for a context diff
905 l2 = lr.readline()
905 l2 = lr.readline()
906 if not l2.startswith('---'):
906 if not l2.startswith('---'):
907 lr.push(l2)
907 lr.push(l2)
908 continue
908 continue
909 l3 = lr.readline()
909 l3 = lr.readline()
910 lr.push(l3)
910 lr.push(l3)
911 if not l3.startswith("***************"):
911 if not l3.startswith("***************"):
912 lr.push(l2)
912 lr.push(l2)
913 continue
913 continue
914 newfile = True
914 newfile = True
915 context = True
915 context = True
916 afile = parsefilename(x)
916 afile = parsefilename(x)
917 bfile = parsefilename(l2)
917 bfile = parsefilename(l2)
918
918
919 if newfile:
919 if newfile:
920 emitfile = True
920 emitfile = True
921 state = BFILE
921 state = BFILE
922 hunknum = 0
922 hunknum = 0
923 if current_hunk:
923 if current_hunk:
924 if current_hunk.complete():
924 if current_hunk.complete():
925 yield 'hunk', current_hunk
925 yield 'hunk', current_hunk
926 else:
926 else:
927 raise PatchError(_("malformed patch %s %s") % (afile,
927 raise PatchError(_("malformed patch %s %s") % (afile,
928 current_hunk.desc))
928 current_hunk.desc))
929
929
930 if hunknum == 0 and dopatch and not gitworkdone:
930 if hunknum == 0 and dopatch and not gitworkdone:
931 raise NoHunks
931 raise NoHunks
932
932
933 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False):
933 def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False):
934 """reads a patch from fp and tries to apply it. The dict 'changed' is
934 """reads a patch from fp and tries to apply it. The dict 'changed' is
935 filled in with all of the filenames changed by the patch. Returns 0
935 filled in with all of the filenames changed by the patch. Returns 0
936 for a clean patch, -1 if any rejects were found and 1 if there was
936 for a clean patch, -1 if any rejects were found and 1 if there was
937 any fuzz."""
937 any fuzz."""
938
938
939 rejects = 0
939 rejects = 0
940 err = 0
940 err = 0
941 current_file = None
941 current_file = None
942 gitpatches = None
942 gitpatches = None
943
943
944 def closefile():
944 def closefile():
945 if not current_file:
945 if not current_file:
946 return 0
946 return 0
947 current_file.close()
947 current_file.close()
948 return len(current_file.rej)
948 return len(current_file.rej)
949
949
950 for state, values in iterhunks(ui, fp, sourcefile):
950 for state, values in iterhunks(ui, fp, sourcefile):
951 if state == 'hunk':
951 if state == 'hunk':
952 if not current_file:
952 if not current_file:
953 continue
953 continue
954 current_hunk = values
954 current_hunk = values
955 ret = current_file.apply(current_hunk, reverse)
955 ret = current_file.apply(current_hunk, reverse)
956 if ret >= 0:
956 if ret >= 0:
957 changed.setdefault(current_file.fname, None)
957 changed.setdefault(current_file.fname, None)
958 if ret > 0:
958 if ret > 0:
959 err = 1
959 err = 1
960 elif state == 'file':
960 elif state == 'file':
961 rejects += closefile()
961 rejects += closefile()
962 afile, bfile, first_hunk = values
962 afile, bfile, first_hunk = values
963 try:
963 try:
964 if sourcefile:
964 if sourcefile:
965 current_file = patchfile(ui, sourcefile)
965 current_file = patchfile(ui, sourcefile)
966 else:
966 else:
967 current_file, missing = selectfile(afile, bfile, first_hunk,
967 current_file, missing = selectfile(afile, bfile, first_hunk,
968 strip, reverse)
968 strip, reverse)
969 current_file = patchfile(ui, current_file, missing)
969 current_file = patchfile(ui, current_file, missing)
970 except PatchError, err:
970 except PatchError, err:
971 ui.warn(str(err) + '\n')
971 ui.warn(str(err) + '\n')
972 current_file, current_hunk = None, None
972 current_file, current_hunk = None, None
973 rejects += 1
973 rejects += 1
974 continue
974 continue
975 elif state == 'git':
975 elif state == 'git':
976 gitpatches = values
976 gitpatches = values
977 cwd = os.getcwd()
977 cwd = os.getcwd()
978 for gp in gitpatches:
978 for gp in gitpatches:
979 if gp.op in ('COPY', 'RENAME'):
979 if gp.op in ('COPY', 'RENAME'):
980 src, dst = [util.canonpath(cwd, cwd, x)
980 src, dst = [util.canonpath(cwd, cwd, x)
981 for x in [gp.oldpath, gp.path]]
981 for x in [gp.oldpath, gp.path]]
982 copyfile(src, dst)
982 copyfile(src, dst)
983 changed[gp.path] = gp
983 changed[gp.path] = gp
984 else:
984 else:
985 raise util.Abort(_('unsupported parser state: %s') % state)
985 raise util.Abort(_('unsupported parser state: %s') % state)
986
986
987 rejects += closefile()
987 rejects += closefile()
988
988
989 if rejects:
989 if rejects:
990 return -1
990 return -1
991 return err
991 return err
992
992
993 def diffopts(ui, opts={}, untrusted=False):
993 def diffopts(ui, opts={}, untrusted=False):
994 def get(key, name=None, getter=ui.configbool):
994 def get(key, name=None, getter=ui.configbool):
995 return (opts.get(key) or
995 return (opts.get(key) or
996 getter('diff', name or key, None, untrusted=untrusted))
996 getter('diff', name or key, None, untrusted=untrusted))
997 return mdiff.diffopts(
997 return mdiff.diffopts(
998 text=opts.get('text'),
998 text=opts.get('text'),
999 git=get('git'),
999 git=get('git'),
1000 nodates=get('nodates'),
1000 nodates=get('nodates'),
1001 showfunc=get('show_function', 'showfunc'),
1001 showfunc=get('show_function', 'showfunc'),
1002 ignorews=get('ignore_all_space', 'ignorews'),
1002 ignorews=get('ignore_all_space', 'ignorews'),
1003 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1003 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1004 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1004 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1005 context=get('unified', getter=ui.config))
1005 context=get('unified', getter=ui.config))
1006
1006
1007 def updatedir(ui, repo, patches):
1007 def updatedir(ui, repo, patches):
1008 '''Update dirstate after patch application according to metadata'''
1008 '''Update dirstate after patch application according to metadata'''
1009 if not patches:
1009 if not patches:
1010 return
1010 return
1011 copies = []
1011 copies = []
1012 removes = {}
1012 removes = {}
1013 cfiles = patches.keys()
1013 cfiles = patches.keys()
1014 cwd = repo.getcwd()
1014 cwd = repo.getcwd()
1015 if cwd:
1015 if cwd:
1016 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1016 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
1017 for f in patches:
1017 for f in patches:
1018 gp = patches[f]
1018 gp = patches[f]
1019 if not gp:
1019 if not gp:
1020 continue
1020 continue
1021 if gp.op == 'RENAME':
1021 if gp.op == 'RENAME':
1022 copies.append((gp.oldpath, gp.path))
1022 copies.append((gp.oldpath, gp.path))
1023 removes[gp.oldpath] = 1
1023 removes[gp.oldpath] = 1
1024 elif gp.op == 'COPY':
1024 elif gp.op == 'COPY':
1025 copies.append((gp.oldpath, gp.path))
1025 copies.append((gp.oldpath, gp.path))
1026 elif gp.op == 'DELETE':
1026 elif gp.op == 'DELETE':
1027 removes[gp.path] = 1
1027 removes[gp.path] = 1
1028 for src, dst in copies:
1028 for src, dst in copies:
1029 repo.copy(src, dst)
1029 repo.copy(src, dst)
1030 removes = removes.keys()
1030 removes = removes.keys()
1031 if removes:
1031 if removes:
1032 repo.remove(util.sort(removes), True)
1032 repo.remove(util.sort(removes), True)
1033 for f in patches:
1033 for f in patches:
1034 gp = patches[f]
1034 gp = patches[f]
1035 if gp and gp.mode:
1035 if gp and gp.mode:
1036 islink, isexec = gp.mode
1036 islink, isexec = gp.mode
1037 dst = os.path.join(repo.root, gp.path)
1037 dst = os.path.join(repo.root, gp.path)
1038 # patch won't create empty files
1038 # patch won't create empty files
1039 if gp.op == 'ADD' and not os.path.exists(dst):
1039 if gp.op == 'ADD' and not os.path.exists(dst):
1040 flags = (isexec and 'x' or '') + (islink and 'l' or '')
1040 flags = (isexec and 'x' or '') + (islink and 'l' or '')
1041 repo.wwrite(gp.path, '', flags)
1041 repo.wwrite(gp.path, '', flags)
1042 else:
1042 else:
1043 util.set_flags(dst, islink, isexec)
1043 util.set_flags(dst, islink, isexec)
1044 cmdutil.addremove(repo, cfiles)
1044 cmdutil.addremove(repo, cfiles)
1045 files = patches.keys()
1045 files = patches.keys()
1046 files.extend([r for r in removes if r not in files])
1046 files.extend([r for r in removes if r not in files])
1047 return util.sort(files)
1047 return util.sort(files)
1048
1048
1049 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
1049 def externalpatch(patcher, args, patchname, ui, strip, cwd, files):
1050 """use <patcher> to apply <patchname> to the working directory.
1050 """use <patcher> to apply <patchname> to the working directory.
1051 returns whether patch was applied with fuzz factor."""
1051 returns whether patch was applied with fuzz factor."""
1052
1052
1053 fuzz = False
1053 fuzz = False
1054 if cwd:
1054 if cwd:
1055 args.append('-d %s' % util.shellquote(cwd))
1055 args.append('-d %s' % util.shellquote(cwd))
1056 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1056 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1057 util.shellquote(patchname)))
1057 util.shellquote(patchname)))
1058
1058
1059 for line in fp:
1059 for line in fp:
1060 line = line.rstrip()
1060 line = line.rstrip()
1061 ui.note(line + '\n')
1061 ui.note(line + '\n')
1062 if line.startswith('patching file '):
1062 if line.startswith('patching file '):
1063 pf = util.parse_patch_output(line)
1063 pf = util.parse_patch_output(line)
1064 printed_file = False
1064 printed_file = False
1065 files.setdefault(pf, (None, None))
1065 files.setdefault(pf, (None, None))
1066 elif line.find('with fuzz') >= 0:
1066 elif line.find('with fuzz') >= 0:
1067 fuzz = True
1067 fuzz = True
1068 if not printed_file:
1068 if not printed_file:
1069 ui.warn(pf + '\n')
1069 ui.warn(pf + '\n')
1070 printed_file = True
1070 printed_file = True
1071 ui.warn(line + '\n')
1071 ui.warn(line + '\n')
1072 elif line.find('saving rejects to file') >= 0:
1072 elif line.find('saving rejects to file') >= 0:
1073 ui.warn(line + '\n')
1073 ui.warn(line + '\n')
1074 elif line.find('FAILED') >= 0:
1074 elif line.find('FAILED') >= 0:
1075 if not printed_file:
1075 if not printed_file:
1076 ui.warn(pf + '\n')
1076 ui.warn(pf + '\n')
1077 printed_file = True
1077 printed_file = True
1078 ui.warn(line + '\n')
1078 ui.warn(line + '\n')
1079 code = fp.close()
1079 code = fp.close()
1080 if code:
1080 if code:
1081 raise PatchError(_("patch command failed: %s") %
1081 raise PatchError(_("patch command failed: %s") %
1082 util.explain_exit(code)[0])
1082 util.explain_exit(code)[0])
1083 return fuzz
1083 return fuzz
1084
1084
1085 def internalpatch(patchobj, ui, strip, cwd, files={}):
1085 def internalpatch(patchobj, ui, strip, cwd, files={}):
1086 """use builtin patch to apply <patchobj> to the working directory.
1086 """use builtin patch to apply <patchobj> to the working directory.
1087 returns whether patch was applied with fuzz factor."""
1087 returns whether patch was applied with fuzz factor."""
1088 try:
1088 try:
1089 fp = file(patchobj, 'rb')
1089 fp = file(patchobj, 'rb')
1090 except TypeError:
1090 except TypeError:
1091 fp = patchobj
1091 fp = patchobj
1092 if cwd:
1092 if cwd:
1093 curdir = os.getcwd()
1093 curdir = os.getcwd()
1094 os.chdir(cwd)
1094 os.chdir(cwd)
1095 try:
1095 try:
1096 ret = applydiff(ui, fp, files, strip=strip)
1096 ret = applydiff(ui, fp, files, strip=strip)
1097 finally:
1097 finally:
1098 if cwd:
1098 if cwd:
1099 os.chdir(curdir)
1099 os.chdir(curdir)
1100 if ret < 0:
1100 if ret < 0:
1101 raise PatchError
1101 raise PatchError
1102 return ret > 0
1102 return ret > 0
1103
1103
1104 def patch(patchname, ui, strip=1, cwd=None, files={}):
1104 def patch(patchname, ui, strip=1, cwd=None, files={}):
1105 """apply <patchname> to the working directory.
1105 """apply <patchname> to the working directory.
1106 returns whether patch was applied with fuzz factor."""
1106 returns whether patch was applied with fuzz factor."""
1107 patcher = ui.config('ui', 'patch')
1107 patcher = ui.config('ui', 'patch')
1108 args = []
1108 args = []
1109 try:
1109 try:
1110 if patcher:
1110 if patcher:
1111 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1111 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1112 files)
1112 files)
1113 else:
1113 else:
1114 try:
1114 try:
1115 return internalpatch(patchname, ui, strip, cwd, files)
1115 return internalpatch(patchname, ui, strip, cwd, files)
1116 except NoHunks:
1116 except NoHunks:
1117 patcher = util.find_exe('gpatch') or util.find_exe('patch')
1117 patcher = util.find_exe('gpatch') or util.find_exe('patch')
1118 ui.debug(_('no valid hunks found; trying with %r instead\n') %
1118 ui.debug(_('no valid hunks found; trying with %r instead\n') %
1119 patcher)
1119 patcher)
1120 if util.needbinarypatch():
1120 if util.needbinarypatch():
1121 args.append('--binary')
1121 args.append('--binary')
1122 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1122 return externalpatch(patcher, args, patchname, ui, strip, cwd,
1123 files)
1123 files)
1124 except PatchError, err:
1124 except PatchError, err:
1125 s = str(err)
1125 s = str(err)
1126 if s:
1126 if s:
1127 raise util.Abort(s)
1127 raise util.Abort(s)
1128 else:
1128 else:
1129 raise util.Abort(_('patch failed to apply'))
1129 raise util.Abort(_('patch failed to apply'))
1130
1130
1131 def b85diff(to, tn):
1131 def b85diff(to, tn):
1132 '''print base85-encoded binary diff'''
1132 '''print base85-encoded binary diff'''
1133 def gitindex(text):
1133 def gitindex(text):
1134 if not text:
1134 if not text:
1135 return '0' * 40
1135 return '0' * 40
1136 l = len(text)
1136 l = len(text)
1137 s = util.sha1('blob %d\0' % l)
1137 s = util.sha1('blob %d\0' % l)
1138 s.update(text)
1138 s.update(text)
1139 return s.hexdigest()
1139 return s.hexdigest()
1140
1140
1141 def fmtline(line):
1141 def fmtline(line):
1142 l = len(line)
1142 l = len(line)
1143 if l <= 26:
1143 if l <= 26:
1144 l = chr(ord('A') + l - 1)
1144 l = chr(ord('A') + l - 1)
1145 else:
1145 else:
1146 l = chr(l - 26 + ord('a') - 1)
1146 l = chr(l - 26 + ord('a') - 1)
1147 return '%c%s\n' % (l, base85.b85encode(line, True))
1147 return '%c%s\n' % (l, base85.b85encode(line, True))
1148
1148
1149 def chunk(text, csize=52):
1149 def chunk(text, csize=52):
1150 l = len(text)
1150 l = len(text)
1151 i = 0
1151 i = 0
1152 while i < l:
1152 while i < l:
1153 yield text[i:i+csize]
1153 yield text[i:i+csize]
1154 i += csize
1154 i += csize
1155
1155
1156 tohash = gitindex(to)
1156 tohash = gitindex(to)
1157 tnhash = gitindex(tn)
1157 tnhash = gitindex(tn)
1158 if tohash == tnhash:
1158 if tohash == tnhash:
1159 return ""
1159 return ""
1160
1160
1161 # TODO: deltas
1161 # TODO: deltas
1162 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1162 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1163 (tohash, tnhash, len(tn))]
1163 (tohash, tnhash, len(tn))]
1164 for l in chunk(zlib.compress(tn)):
1164 for l in chunk(zlib.compress(tn)):
1165 ret.append(fmtline(l))
1165 ret.append(fmtline(l))
1166 ret.append('\n')
1166 ret.append('\n')
1167 return ''.join(ret)
1167 return ''.join(ret)
1168
1168
1169 def diff(repo, node1=None, node2=None, match=None,
1169 def diff(repo, node1=None, node2=None, match=None,
1170 fp=None, changes=None, opts=None):
1170 fp=None, changes=None, opts=None):
1171 '''print diff of changes to files between two nodes, or node and
1171 '''print diff of changes to files between two nodes, or node and
1172 working directory.
1172 working directory.
1173
1173
1174 if node1 is None, use first dirstate parent instead.
1174 if node1 is None, use first dirstate parent instead.
1175 if node2 is None, compare node1 with working directory.'''
1175 if node2 is None, compare node1 with working directory.'''
1176
1176
1177 if not match:
1177 if not match:
1178 match = cmdutil.matchall(repo)
1178 match = cmdutil.matchall(repo)
1179
1179
1180 if opts is None:
1180 if opts is None:
1181 opts = mdiff.defaultopts
1181 opts = mdiff.defaultopts
1182 if fp is None:
1182 if fp is None:
1183 fp = repo.ui
1183 fp = repo.ui
1184
1184
1185 if not node1:
1185 if not node1:
1186 node1 = repo.dirstate.parents()[0]
1186 node1 = repo.dirstate.parents()[0]
1187
1187
1188 flcache = {}
1188 flcache = {}
1189 def getfilectx(f, ctx):
1189 def getfilectx(f, ctx):
1190 flctx = ctx.filectx(f, filelog=flcache.get(f))
1190 flctx = ctx.filectx(f, filelog=flcache.get(f))
1191 if f not in flcache:
1191 if f not in flcache:
1192 flcache[f] = flctx._filelog
1192 flcache[f] = flctx._filelog
1193 return flctx
1193 return flctx
1194
1194
1195 ctx1 = repo[node1]
1195 ctx1 = repo[node1]
1196 ctx2 = repo[node2]
1196 ctx2 = repo[node2]
1197
1197
1198 if not changes:
1198 if not changes:
1199 changes = repo.status(ctx1, ctx2, match=match)
1199 changes = repo.status(ctx1, ctx2, match=match)
1200 modified, added, removed = changes[:3]
1200 modified, added, removed = changes[:3]
1201
1201
1202 if not modified and not added and not removed:
1202 if not modified and not added and not removed:
1203 return
1203 return
1204
1204
1205 date1 = util.datestr(ctx1.date())
1205 date1 = util.datestr(ctx1.date())
1206 man1 = ctx1.manifest()
1206 man1 = ctx1.manifest()
1207
1207
1208 if repo.ui.quiet:
1208 if repo.ui.quiet:
1209 r = None
1209 r = None
1210 else:
1210 else:
1211 hexfunc = repo.ui.debugflag and hex or short
1211 hexfunc = repo.ui.debugflag and hex or short
1212 r = [hexfunc(node) for node in [node1, node2] if node]
1212 r = [hexfunc(node) for node in [node1, node2] if node]
1213
1213
1214 if opts.git:
1214 if opts.git:
1215 copy, diverge = copies.copies(repo, ctx1, ctx2, repo[nullid])
1215 copy, diverge = copies.copies(repo, ctx1, ctx2, repo[nullid])
1216 for k, v in copy.items():
1216 for k, v in copy.items():
1217 copy[v] = k
1217 copy[v] = k
1218
1218
1219 gone = {}
1219 gone = {}
1220 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1220 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1221
1221
1222 for f in util.sort(modified + added + removed):
1222 for f in util.sort(modified + added + removed):
1223 to = None
1223 to = None
1224 tn = None
1224 tn = None
1225 dodiff = True
1225 dodiff = True
1226 header = []
1226 header = []
1227 if f in man1:
1227 if f in man1:
1228 to = getfilectx(f, ctx1).data()
1228 to = getfilectx(f, ctx1).data()
1229 if f not in removed:
1229 if f not in removed:
1230 tn = getfilectx(f, ctx2).data()
1230 tn = getfilectx(f, ctx2).data()
1231 a, b = f, f
1231 a, b = f, f
1232 if opts.git:
1232 if opts.git:
1233 def addmodehdr(header, omode, nmode):
1233 def addmodehdr(header, omode, nmode):
1234 if omode != nmode:
1234 if omode != nmode:
1235 header.append('old mode %s\n' % omode)
1235 header.append('old mode %s\n' % omode)
1236 header.append('new mode %s\n' % nmode)
1236 header.append('new mode %s\n' % nmode)
1237
1237
1238 if f in added:
1238 if f in added:
1239 mode = gitmode[ctx2.flags(f)]
1239 mode = gitmode[ctx2.flags(f)]
1240 if f in copy:
1240 if f in copy:
1241 a = copy[f]
1241 a = copy[f]
1242 omode = gitmode[man1.flags(a)]
1242 omode = gitmode[man1.flags(a)]
1243 addmodehdr(header, omode, mode)
1243 addmodehdr(header, omode, mode)
1244 if a in removed and a not in gone:
1244 if a in removed and a not in gone:
1245 op = 'rename'
1245 op = 'rename'
1246 gone[a] = 1
1246 gone[a] = 1
1247 else:
1247 else:
1248 op = 'copy'
1248 op = 'copy'
1249 header.append('%s from %s\n' % (op, a))
1249 header.append('%s from %s\n' % (op, a))
1250 header.append('%s to %s\n' % (op, f))
1250 header.append('%s to %s\n' % (op, f))
1251 to = getfilectx(a, ctx1).data()
1251 to = getfilectx(a, ctx1).data()
1252 else:
1252 else:
1253 header.append('new file mode %s\n' % mode)
1253 header.append('new file mode %s\n' % mode)
1254 if util.binary(tn):
1254 if util.binary(tn):
1255 dodiff = 'binary'
1255 dodiff = 'binary'
1256 elif f in removed:
1256 elif f in removed:
1257 # have we already reported a copy above?
1257 # have we already reported a copy above?
1258 if f in copy and copy[f] in added and copy[copy[f]] == f:
1258 if f in copy and copy[f] in added and copy[copy[f]] == f:
1259 dodiff = False
1259 dodiff = False
1260 else:
1260 else:
1261 header.append('deleted file mode %s\n' %
1261 header.append('deleted file mode %s\n' %
1262 gitmode[man1.flags(f)])
1262 gitmode[man1.flags(f)])
1263 else:
1263 else:
1264 omode = gitmode[man1.flags(f)]
1264 omode = gitmode[man1.flags(f)]
1265 nmode = gitmode[ctx2.flags(f)]
1265 nmode = gitmode[ctx2.flags(f)]
1266 addmodehdr(header, omode, nmode)
1266 addmodehdr(header, omode, nmode)
1267 if util.binary(to) or util.binary(tn):
1267 if util.binary(to) or util.binary(tn):
1268 dodiff = 'binary'
1268 dodiff = 'binary'
1269 r = None
1269 r = None
1270 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
1270 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
1271 if dodiff:
1271 if dodiff:
1272 if dodiff == 'binary':
1272 if dodiff == 'binary':
1273 text = b85diff(to, tn)
1273 text = b85diff(to, tn)
1274 else:
1274 else:
1275 text = mdiff.unidiff(to, date1,
1275 text = mdiff.unidiff(to, date1,
1276 # ctx2 date may be dynamic
1276 # ctx2 date may be dynamic
1277 tn, util.datestr(ctx2.date()),
1277 tn, util.datestr(ctx2.date()),
1278 a, b, r, opts=opts)
1278 a, b, r, opts=opts)
1279 if text or len(header) > 1:
1279 if text or len(header) > 1:
1280 fp.write(''.join(header))
1280 fp.write(''.join(header))
1281 fp.write(text)
1281 fp.write(text)
1282
1282
1283 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1283 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1284 opts=None):
1284 opts=None):
1285 '''export changesets as hg patches.'''
1285 '''export changesets as hg patches.'''
1286
1286
1287 total = len(revs)
1287 total = len(revs)
1288 revwidth = max([len(str(rev)) for rev in revs])
1288 revwidth = max([len(str(rev)) for rev in revs])
1289
1289
1290 def single(rev, seqno, fp):
1290 def single(rev, seqno, fp):
1291 ctx = repo[rev]
1291 ctx = repo[rev]
1292 node = ctx.node()
1292 node = ctx.node()
1293 parents = [p.node() for p in ctx.parents() if p]
1293 parents = [p.node() for p in ctx.parents() if p]
1294 branch = ctx.branch()
1294 branch = ctx.branch()
1295 if switch_parent:
1295 if switch_parent:
1296 parents.reverse()
1296 parents.reverse()
1297 prev = (parents and parents[0]) or nullid
1297 prev = (parents and parents[0]) or nullid
1298
1298
1299 if not fp:
1299 if not fp:
1300 fp = cmdutil.make_file(repo, template, node, total=total,
1300 fp = cmdutil.make_file(repo, template, node, total=total,
1301 seqno=seqno, revwidth=revwidth)
1301 seqno=seqno, revwidth=revwidth)
1302 if fp != sys.stdout and hasattr(fp, 'name'):
1302 if fp != sys.stdout and hasattr(fp, 'name'):
1303 repo.ui.note("%s\n" % fp.name)
1303 repo.ui.note("%s\n" % fp.name)
1304
1304
1305 fp.write("# HG changeset patch\n")
1305 fp.write("# HG changeset patch\n")
1306 fp.write("# User %s\n" % ctx.user())
1306 fp.write("# User %s\n" % ctx.user())
1307 fp.write("# Date %d %d\n" % ctx.date())
1307 fp.write("# Date %d %d\n" % ctx.date())
1308 if branch and (branch != 'default'):
1308 if branch and (branch != 'default'):
1309 fp.write("# Branch %s\n" % branch)
1309 fp.write("# Branch %s\n" % branch)
1310 fp.write("# Node ID %s\n" % hex(node))
1310 fp.write("# Node ID %s\n" % hex(node))
1311 fp.write("# Parent %s\n" % hex(prev))
1311 fp.write("# Parent %s\n" % hex(prev))
1312 if len(parents) > 1:
1312 if len(parents) > 1:
1313 fp.write("# Parent %s\n" % hex(parents[1]))
1313 fp.write("# Parent %s\n" % hex(parents[1]))
1314 fp.write(ctx.description().rstrip())
1314 fp.write(ctx.description().rstrip())
1315 fp.write("\n\n")
1315 fp.write("\n\n")
1316
1316
1317 diff(repo, prev, node, fp=fp, opts=opts)
1317 diff(repo, prev, node, fp=fp, opts=opts)
1318 if fp not in (sys.stdout, repo.ui):
1318 if fp not in (sys.stdout, repo.ui):
1319 fp.close()
1319 fp.close()
1320
1320
1321 for seqno, rev in enumerate(revs):
1321 for seqno, rev in enumerate(revs):
1322 single(rev, seqno+1, fp)
1322 single(rev, seqno+1, fp)
1323
1323
1324 def diffstat(patchlines):
1324 def diffstat(patchlines):
1325 if not util.find_exe('diffstat'):
1325 if not util.find_exe('diffstat'):
1326 return
1326 return
1327 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
1327 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
1328 try:
1328 try:
1329 p = util.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
1329 p = util.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
1330 try:
1330 try:
1331 for line in patchlines:
1331 for line in patchlines:
1332 p.tochild.write(line + "\n")
1332 p.tochild.write(line + "\n")
1333 p.tochild.close()
1333 p.tochild.close()
1334 if p.wait(): return
1334 if p.wait(): return
1335 fp = os.fdopen(fd, 'r')
1335 fp = os.fdopen(fd, 'r')
1336 stat = []
1336 stat = []
1337 for line in fp: stat.append(line.lstrip())
1337 for line in fp: stat.append(line.lstrip())
1338 last = stat.pop()
1338 last = stat.pop()
1339 stat.insert(0, last)
1339 stat.insert(0, last)
1340 stat = ''.join(stat)
1340 stat = ''.join(stat)
1341 return stat
1341 return stat
1342 except: raise
1342 except: raise
1343 finally:
1343 finally:
1344 try: os.unlink(name)
1344 try: os.unlink(name)
1345 except: pass
1345 except: pass
@@ -1,55 +1,55 b''
1 # simple script to be used in hooks
1 # simple script to be used in hooks
2 # copy it to the current directory when the test starts:
2 # copy it to the current directory when the test starts:
3 #
3 #
4 # cp "$TESTDIR"/printenv.py .
4 # cp "$TESTDIR"/printenv.py .
5 #
5 #
6 # put something like this in the repo .hg/hgrc:
6 # put something like this in the repo .hg/hgrc:
7 #
7 #
8 # [hooks]
8 # [hooks]
9 # changegroup = python ../printenv.py <hookname> [exit] [output]
9 # changegroup = python ../printenv.py <hookname> [exit] [output]
10 #
10 #
11 # - <hookname> is a mandatory argument (e.g. "changegroup")
11 # - <hookname> is a mandatory argument (e.g. "changegroup")
12 # - [exit] is the exit code of the hook (default: 0)
12 # - [exit] is the exit code of the hook (default: 0)
13 # - [output] is the name of the output file (default: use sys.stdout)
13 # - [output] is the name of the output file (default: use sys.stdout)
14 # the file will be opened in append mode.
14 # the file will be opened in append mode.
15 #
15 #
16 import os
16 import os
17 import sys
17 import sys
18
18
19 try:
19 try:
20 import msvcrt
20 import msvcrt
21 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
21 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
22 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
22 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
23 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
23 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
24 except ImportError:
24 except ImportError:
25 pass
25 pass
26
26
27 exitcode = 0
27 exitcode = 0
28 out = sys.stdout
28 out = sys.stdout
29
29
30 name = sys.argv[1]
30 name = sys.argv[1]
31 if len(sys.argv) > 2:
31 if len(sys.argv) > 2:
32 exitcode = int(sys.argv[2])
32 exitcode = int(sys.argv[2])
33 if len(sys.argv) > 3:
33 if len(sys.argv) > 3:
34 out = open(sys.argv[3], "ab")
34 out = open(sys.argv[3], "ab")
35
35
36 # variables with empty values may not exist on all platforms, filter
36 # variables with empty values may not exist on all platforms, filter
37 # them now for portability sake.
37 # them now for portability sake.
38 env = [k for k, v in os.environ.iteritems()
38 env = [k for k, v in os.environ.iteritems()
39 if k.startswith("HG_") and v]
39 if k.startswith("HG_") and v]
40 env.sort()
40 env.sort()
41
41
42 # edit the variable part of the variable
42 # edit the variable part of the variable
43 url = os.environ.get("HG_URL", "")
43 url = os.environ.get("HG_URL", "")
44 if url.startswith("file:"):
44 if url.startswith("file:"):
45 os.environ["HG_URL"] = "file:"
45 os.environ["HG_URL"] = "file:"
46 elif url.startswith("remote:http"):
46 elif url.startswith("remote:http"):
47 os.environ["HG_URL"] = "remote:http"
47 os.environ["HG_URL"] = "remote:http"
48
48
49 out.write("%s hook: " % name)
49 out.write("%s hook: " % name)
50 for v in env:
50 for v in env:
51 out.write("%s=%s " % (v, os.environ[v]))
51 out.write("%s=%s " % (v, os.environ[v]))
52 out.write("\n")
52 out.write("\n")
53 out.close()
53 out.close()
54
54
55 sys.exit(exitcode)
55 sys.exit(exitcode)
General Comments 0
You need to be logged in to leave comments. Login now