##// END OF EJS Templates
strip: make --keep option not set all dirstate times to 0...
Durham Goode -
r18760:e74704c3 default
parent child Browse files
Show More
@@ -1,3587 +1,3602
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 of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''manage a stack of patches
8 '''manage a stack of patches
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 create new patch qnew
19 create new patch qnew
20 import existing patch qimport
20 import existing patch qimport
21
21
22 print patch series qseries
22 print patch series qseries
23 print applied patches qapplied
23 print applied patches qapplied
24
24
25 add known patch to applied stack qpush
25 add known patch to applied stack qpush
26 remove patch from applied stack qpop
26 remove patch from applied stack qpop
27 refresh contents of top applied patch qrefresh
27 refresh contents of top applied patch qrefresh
28
28
29 By default, mq will automatically use git patches when required to
29 By default, mq will automatically use git patches when required to
30 avoid losing file mode changes, copy records, binary files or empty
30 avoid losing file mode changes, copy records, binary files or empty
31 files creations or deletions. This behaviour can be configured with::
31 files creations or deletions. This behaviour can be configured with::
32
32
33 [mq]
33 [mq]
34 git = auto/keep/yes/no
34 git = auto/keep/yes/no
35
35
36 If set to 'keep', mq will obey the [diff] section configuration while
36 If set to 'keep', mq will obey the [diff] section configuration while
37 preserving existing git patches upon qrefresh. If set to 'yes' or
37 preserving existing git patches upon qrefresh. If set to 'yes' or
38 'no', mq will override the [diff] section and always generate git or
38 'no', mq will override the [diff] section and always generate git or
39 regular patches, possibly losing data in the second case.
39 regular patches, possibly losing data in the second case.
40
40
41 It may be desirable for mq changesets to be kept in the secret phase (see
41 It may be desirable for mq changesets to be kept in the secret phase (see
42 :hg:`help phases`), which can be enabled with the following setting::
42 :hg:`help phases`), which can be enabled with the following setting::
43
43
44 [mq]
44 [mq]
45 secret = True
45 secret = True
46
46
47 You will by default be managing a patch queue named "patches". You can
47 You will by default be managing a patch queue named "patches". You can
48 create other, independent patch queues with the :hg:`qqueue` command.
48 create other, independent patch queues with the :hg:`qqueue` command.
49
49
50 If the working directory contains uncommitted files, qpush, qpop and
50 If the working directory contains uncommitted files, qpush, qpop and
51 qgoto abort immediately. If -f/--force is used, the changes are
51 qgoto abort immediately. If -f/--force is used, the changes are
52 discarded. Setting::
52 discarded. Setting::
53
53
54 [mq]
54 [mq]
55 keepchanges = True
55 keepchanges = True
56
56
57 make them behave as if --keep-changes were passed, and non-conflicting
57 make them behave as if --keep-changes were passed, and non-conflicting
58 local changes will be tolerated and preserved. If incompatible options
58 local changes will be tolerated and preserved. If incompatible options
59 such as -f/--force or --exact are passed, this setting is ignored.
59 such as -f/--force or --exact are passed, this setting is ignored.
60 '''
60 '''
61
61
62 from mercurial.i18n import _
62 from mercurial.i18n import _
63 from mercurial.node import bin, hex, short, nullid, nullrev
63 from mercurial.node import bin, hex, short, nullid, nullrev
64 from mercurial.lock import release
64 from mercurial.lock import release
65 from mercurial import commands, cmdutil, hg, scmutil, util, revset
65 from mercurial import commands, cmdutil, hg, scmutil, util, revset
66 from mercurial import repair, extensions, error, phases
66 from mercurial import repair, extensions, error, phases
67 from mercurial import patch as patchmod
67 from mercurial import patch as patchmod
68 import os, re, errno, shutil
68 import os, re, errno, shutil
69
69
70 commands.norepo += " qclone"
70 commands.norepo += " qclone"
71
71
72 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
72 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
73
73
74 cmdtable = {}
74 cmdtable = {}
75 command = cmdutil.command(cmdtable)
75 command = cmdutil.command(cmdtable)
76 testedwith = 'internal'
76 testedwith = 'internal'
77
77
78 # Patch names looks like unix-file names.
78 # Patch names looks like unix-file names.
79 # They must be joinable with queue directory and result in the patch path.
79 # They must be joinable with queue directory and result in the patch path.
80 normname = util.normpath
80 normname = util.normpath
81
81
82 class statusentry(object):
82 class statusentry(object):
83 def __init__(self, node, name):
83 def __init__(self, node, name):
84 self.node, self.name = node, name
84 self.node, self.name = node, name
85 def __repr__(self):
85 def __repr__(self):
86 return hex(self.node) + ':' + self.name
86 return hex(self.node) + ':' + self.name
87
87
88 class patchheader(object):
88 class patchheader(object):
89 def __init__(self, pf, plainmode=False):
89 def __init__(self, pf, plainmode=False):
90 def eatdiff(lines):
90 def eatdiff(lines):
91 while lines:
91 while lines:
92 l = lines[-1]
92 l = lines[-1]
93 if (l.startswith("diff -") or
93 if (l.startswith("diff -") or
94 l.startswith("Index:") or
94 l.startswith("Index:") or
95 l.startswith("===========")):
95 l.startswith("===========")):
96 del lines[-1]
96 del lines[-1]
97 else:
97 else:
98 break
98 break
99 def eatempty(lines):
99 def eatempty(lines):
100 while lines:
100 while lines:
101 if not lines[-1].strip():
101 if not lines[-1].strip():
102 del lines[-1]
102 del lines[-1]
103 else:
103 else:
104 break
104 break
105
105
106 message = []
106 message = []
107 comments = []
107 comments = []
108 user = None
108 user = None
109 date = None
109 date = None
110 parent = None
110 parent = None
111 format = None
111 format = None
112 subject = None
112 subject = None
113 branch = None
113 branch = None
114 nodeid = None
114 nodeid = None
115 diffstart = 0
115 diffstart = 0
116
116
117 for line in file(pf):
117 for line in file(pf):
118 line = line.rstrip()
118 line = line.rstrip()
119 if (line.startswith('diff --git')
119 if (line.startswith('diff --git')
120 or (diffstart and line.startswith('+++ '))):
120 or (diffstart and line.startswith('+++ '))):
121 diffstart = 2
121 diffstart = 2
122 break
122 break
123 diffstart = 0 # reset
123 diffstart = 0 # reset
124 if line.startswith("--- "):
124 if line.startswith("--- "):
125 diffstart = 1
125 diffstart = 1
126 continue
126 continue
127 elif format == "hgpatch":
127 elif format == "hgpatch":
128 # parse values when importing the result of an hg export
128 # parse values when importing the result of an hg export
129 if line.startswith("# User "):
129 if line.startswith("# User "):
130 user = line[7:]
130 user = line[7:]
131 elif line.startswith("# Date "):
131 elif line.startswith("# Date "):
132 date = line[7:]
132 date = line[7:]
133 elif line.startswith("# Parent "):
133 elif line.startswith("# Parent "):
134 parent = line[9:].lstrip()
134 parent = line[9:].lstrip()
135 elif line.startswith("# Branch "):
135 elif line.startswith("# Branch "):
136 branch = line[9:]
136 branch = line[9:]
137 elif line.startswith("# Node ID "):
137 elif line.startswith("# Node ID "):
138 nodeid = line[10:]
138 nodeid = line[10:]
139 elif not line.startswith("# ") and line:
139 elif not line.startswith("# ") and line:
140 message.append(line)
140 message.append(line)
141 format = None
141 format = None
142 elif line == '# HG changeset patch':
142 elif line == '# HG changeset patch':
143 message = []
143 message = []
144 format = "hgpatch"
144 format = "hgpatch"
145 elif (format != "tagdone" and (line.startswith("Subject: ") or
145 elif (format != "tagdone" and (line.startswith("Subject: ") or
146 line.startswith("subject: "))):
146 line.startswith("subject: "))):
147 subject = line[9:]
147 subject = line[9:]
148 format = "tag"
148 format = "tag"
149 elif (format != "tagdone" and (line.startswith("From: ") or
149 elif (format != "tagdone" and (line.startswith("From: ") or
150 line.startswith("from: "))):
150 line.startswith("from: "))):
151 user = line[6:]
151 user = line[6:]
152 format = "tag"
152 format = "tag"
153 elif (format != "tagdone" and (line.startswith("Date: ") or
153 elif (format != "tagdone" and (line.startswith("Date: ") or
154 line.startswith("date: "))):
154 line.startswith("date: "))):
155 date = line[6:]
155 date = line[6:]
156 format = "tag"
156 format = "tag"
157 elif format == "tag" and line == "":
157 elif format == "tag" and line == "":
158 # when looking for tags (subject: from: etc) they
158 # when looking for tags (subject: from: etc) they
159 # end once you find a blank line in the source
159 # end once you find a blank line in the source
160 format = "tagdone"
160 format = "tagdone"
161 elif message or line:
161 elif message or line:
162 message.append(line)
162 message.append(line)
163 comments.append(line)
163 comments.append(line)
164
164
165 eatdiff(message)
165 eatdiff(message)
166 eatdiff(comments)
166 eatdiff(comments)
167 # Remember the exact starting line of the patch diffs before consuming
167 # Remember the exact starting line of the patch diffs before consuming
168 # empty lines, for external use by TortoiseHg and others
168 # empty lines, for external use by TortoiseHg and others
169 self.diffstartline = len(comments)
169 self.diffstartline = len(comments)
170 eatempty(message)
170 eatempty(message)
171 eatempty(comments)
171 eatempty(comments)
172
172
173 # make sure message isn't empty
173 # make sure message isn't empty
174 if format and format.startswith("tag") and subject:
174 if format and format.startswith("tag") and subject:
175 message.insert(0, "")
175 message.insert(0, "")
176 message.insert(0, subject)
176 message.insert(0, subject)
177
177
178 self.message = message
178 self.message = message
179 self.comments = comments
179 self.comments = comments
180 self.user = user
180 self.user = user
181 self.date = date
181 self.date = date
182 self.parent = parent
182 self.parent = parent
183 # nodeid and branch are for external use by TortoiseHg and others
183 # nodeid and branch are for external use by TortoiseHg and others
184 self.nodeid = nodeid
184 self.nodeid = nodeid
185 self.branch = branch
185 self.branch = branch
186 self.haspatch = diffstart > 1
186 self.haspatch = diffstart > 1
187 self.plainmode = plainmode
187 self.plainmode = plainmode
188
188
189 def setuser(self, user):
189 def setuser(self, user):
190 if not self.updateheader(['From: ', '# User '], user):
190 if not self.updateheader(['From: ', '# User '], user):
191 try:
191 try:
192 patchheaderat = self.comments.index('# HG changeset patch')
192 patchheaderat = self.comments.index('# HG changeset patch')
193 self.comments.insert(patchheaderat + 1, '# User ' + user)
193 self.comments.insert(patchheaderat + 1, '# User ' + user)
194 except ValueError:
194 except ValueError:
195 if self.plainmode or self._hasheader(['Date: ']):
195 if self.plainmode or self._hasheader(['Date: ']):
196 self.comments = ['From: ' + user] + self.comments
196 self.comments = ['From: ' + user] + self.comments
197 else:
197 else:
198 tmp = ['# HG changeset patch', '# User ' + user, '']
198 tmp = ['# HG changeset patch', '# User ' + user, '']
199 self.comments = tmp + self.comments
199 self.comments = tmp + self.comments
200 self.user = user
200 self.user = user
201
201
202 def setdate(self, date):
202 def setdate(self, date):
203 if not self.updateheader(['Date: ', '# Date '], date):
203 if not self.updateheader(['Date: ', '# Date '], date):
204 try:
204 try:
205 patchheaderat = self.comments.index('# HG changeset patch')
205 patchheaderat = self.comments.index('# HG changeset patch')
206 self.comments.insert(patchheaderat + 1, '# Date ' + date)
206 self.comments.insert(patchheaderat + 1, '# Date ' + date)
207 except ValueError:
207 except ValueError:
208 if self.plainmode or self._hasheader(['From: ']):
208 if self.plainmode or self._hasheader(['From: ']):
209 self.comments = ['Date: ' + date] + self.comments
209 self.comments = ['Date: ' + date] + self.comments
210 else:
210 else:
211 tmp = ['# HG changeset patch', '# Date ' + date, '']
211 tmp = ['# HG changeset patch', '# Date ' + date, '']
212 self.comments = tmp + self.comments
212 self.comments = tmp + self.comments
213 self.date = date
213 self.date = date
214
214
215 def setparent(self, parent):
215 def setparent(self, parent):
216 if not self.updateheader(['# Parent '], parent):
216 if not self.updateheader(['# Parent '], parent):
217 try:
217 try:
218 patchheaderat = self.comments.index('# HG changeset patch')
218 patchheaderat = self.comments.index('# HG changeset patch')
219 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
219 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
220 except ValueError:
220 except ValueError:
221 pass
221 pass
222 self.parent = parent
222 self.parent = parent
223
223
224 def setmessage(self, message):
224 def setmessage(self, message):
225 if self.comments:
225 if self.comments:
226 self._delmsg()
226 self._delmsg()
227 self.message = [message]
227 self.message = [message]
228 self.comments += self.message
228 self.comments += self.message
229
229
230 def updateheader(self, prefixes, new):
230 def updateheader(self, prefixes, new):
231 '''Update all references to a field in the patch header.
231 '''Update all references to a field in the patch header.
232 Return whether the field is present.'''
232 Return whether the field is present.'''
233 res = False
233 res = False
234 for prefix in prefixes:
234 for prefix in prefixes:
235 for i in xrange(len(self.comments)):
235 for i in xrange(len(self.comments)):
236 if self.comments[i].startswith(prefix):
236 if self.comments[i].startswith(prefix):
237 self.comments[i] = prefix + new
237 self.comments[i] = prefix + new
238 res = True
238 res = True
239 break
239 break
240 return res
240 return res
241
241
242 def _hasheader(self, prefixes):
242 def _hasheader(self, prefixes):
243 '''Check if a header starts with any of the given prefixes.'''
243 '''Check if a header starts with any of the given prefixes.'''
244 for prefix in prefixes:
244 for prefix in prefixes:
245 for comment in self.comments:
245 for comment in self.comments:
246 if comment.startswith(prefix):
246 if comment.startswith(prefix):
247 return True
247 return True
248 return False
248 return False
249
249
250 def __str__(self):
250 def __str__(self):
251 if not self.comments:
251 if not self.comments:
252 return ''
252 return ''
253 return '\n'.join(self.comments) + '\n\n'
253 return '\n'.join(self.comments) + '\n\n'
254
254
255 def _delmsg(self):
255 def _delmsg(self):
256 '''Remove existing message, keeping the rest of the comments fields.
256 '''Remove existing message, keeping the rest of the comments fields.
257 If comments contains 'subject: ', message will prepend
257 If comments contains 'subject: ', message will prepend
258 the field and a blank line.'''
258 the field and a blank line.'''
259 if self.message:
259 if self.message:
260 subj = 'subject: ' + self.message[0].lower()
260 subj = 'subject: ' + self.message[0].lower()
261 for i in xrange(len(self.comments)):
261 for i in xrange(len(self.comments)):
262 if subj == self.comments[i].lower():
262 if subj == self.comments[i].lower():
263 del self.comments[i]
263 del self.comments[i]
264 self.message = self.message[2:]
264 self.message = self.message[2:]
265 break
265 break
266 ci = 0
266 ci = 0
267 for mi in self.message:
267 for mi in self.message:
268 while mi != self.comments[ci]:
268 while mi != self.comments[ci]:
269 ci += 1
269 ci += 1
270 del self.comments[ci]
270 del self.comments[ci]
271
271
272 def newcommit(repo, phase, *args, **kwargs):
272 def newcommit(repo, phase, *args, **kwargs):
273 """helper dedicated to ensure a commit respect mq.secret setting
273 """helper dedicated to ensure a commit respect mq.secret setting
274
274
275 It should be used instead of repo.commit inside the mq source for operation
275 It should be used instead of repo.commit inside the mq source for operation
276 creating new changeset.
276 creating new changeset.
277 """
277 """
278 repo = repo.unfiltered()
278 repo = repo.unfiltered()
279 if phase is None:
279 if phase is None:
280 if repo.ui.configbool('mq', 'secret', False):
280 if repo.ui.configbool('mq', 'secret', False):
281 phase = phases.secret
281 phase = phases.secret
282 if phase is not None:
282 if phase is not None:
283 backup = repo.ui.backupconfig('phases', 'new-commit')
283 backup = repo.ui.backupconfig('phases', 'new-commit')
284 # Marking the repository as committing an mq patch can be used
284 # Marking the repository as committing an mq patch can be used
285 # to optimize operations like branchtags().
285 # to optimize operations like branchtags().
286 repo._committingpatch = True
286 repo._committingpatch = True
287 try:
287 try:
288 if phase is not None:
288 if phase is not None:
289 repo.ui.setconfig('phases', 'new-commit', phase)
289 repo.ui.setconfig('phases', 'new-commit', phase)
290 return repo.commit(*args, **kwargs)
290 return repo.commit(*args, **kwargs)
291 finally:
291 finally:
292 repo._committingpatch = False
292 repo._committingpatch = False
293 if phase is not None:
293 if phase is not None:
294 repo.ui.restoreconfig(backup)
294 repo.ui.restoreconfig(backup)
295
295
296 class AbortNoCleanup(error.Abort):
296 class AbortNoCleanup(error.Abort):
297 pass
297 pass
298
298
299 class queue(object):
299 class queue(object):
300 def __init__(self, ui, path, patchdir=None):
300 def __init__(self, ui, path, patchdir=None):
301 self.basepath = path
301 self.basepath = path
302 try:
302 try:
303 fh = open(os.path.join(path, 'patches.queue'))
303 fh = open(os.path.join(path, 'patches.queue'))
304 cur = fh.read().rstrip()
304 cur = fh.read().rstrip()
305 fh.close()
305 fh.close()
306 if not cur:
306 if not cur:
307 curpath = os.path.join(path, 'patches')
307 curpath = os.path.join(path, 'patches')
308 else:
308 else:
309 curpath = os.path.join(path, 'patches-' + cur)
309 curpath = os.path.join(path, 'patches-' + cur)
310 except IOError:
310 except IOError:
311 curpath = os.path.join(path, 'patches')
311 curpath = os.path.join(path, 'patches')
312 self.path = patchdir or curpath
312 self.path = patchdir or curpath
313 self.opener = scmutil.opener(self.path)
313 self.opener = scmutil.opener(self.path)
314 self.ui = ui
314 self.ui = ui
315 self.applieddirty = False
315 self.applieddirty = False
316 self.seriesdirty = False
316 self.seriesdirty = False
317 self.added = []
317 self.added = []
318 self.seriespath = "series"
318 self.seriespath = "series"
319 self.statuspath = "status"
319 self.statuspath = "status"
320 self.guardspath = "guards"
320 self.guardspath = "guards"
321 self.activeguards = None
321 self.activeguards = None
322 self.guardsdirty = False
322 self.guardsdirty = False
323 # Handle mq.git as a bool with extended values
323 # Handle mq.git as a bool with extended values
324 try:
324 try:
325 gitmode = ui.configbool('mq', 'git', None)
325 gitmode = ui.configbool('mq', 'git', None)
326 if gitmode is None:
326 if gitmode is None:
327 raise error.ConfigError
327 raise error.ConfigError
328 self.gitmode = gitmode and 'yes' or 'no'
328 self.gitmode = gitmode and 'yes' or 'no'
329 except error.ConfigError:
329 except error.ConfigError:
330 self.gitmode = ui.config('mq', 'git', 'auto').lower()
330 self.gitmode = ui.config('mq', 'git', 'auto').lower()
331 self.plainmode = ui.configbool('mq', 'plain', False)
331 self.plainmode = ui.configbool('mq', 'plain', False)
332
332
333 @util.propertycache
333 @util.propertycache
334 def applied(self):
334 def applied(self):
335 def parselines(lines):
335 def parselines(lines):
336 for l in lines:
336 for l in lines:
337 entry = l.split(':', 1)
337 entry = l.split(':', 1)
338 if len(entry) > 1:
338 if len(entry) > 1:
339 n, name = entry
339 n, name = entry
340 yield statusentry(bin(n), name)
340 yield statusentry(bin(n), name)
341 elif l.strip():
341 elif l.strip():
342 self.ui.warn(_('malformated mq status line: %s\n') % entry)
342 self.ui.warn(_('malformated mq status line: %s\n') % entry)
343 # else we ignore empty lines
343 # else we ignore empty lines
344 try:
344 try:
345 lines = self.opener.read(self.statuspath).splitlines()
345 lines = self.opener.read(self.statuspath).splitlines()
346 return list(parselines(lines))
346 return list(parselines(lines))
347 except IOError, e:
347 except IOError, e:
348 if e.errno == errno.ENOENT:
348 if e.errno == errno.ENOENT:
349 return []
349 return []
350 raise
350 raise
351
351
352 @util.propertycache
352 @util.propertycache
353 def fullseries(self):
353 def fullseries(self):
354 try:
354 try:
355 return self.opener.read(self.seriespath).splitlines()
355 return self.opener.read(self.seriespath).splitlines()
356 except IOError, e:
356 except IOError, e:
357 if e.errno == errno.ENOENT:
357 if e.errno == errno.ENOENT:
358 return []
358 return []
359 raise
359 raise
360
360
361 @util.propertycache
361 @util.propertycache
362 def series(self):
362 def series(self):
363 self.parseseries()
363 self.parseseries()
364 return self.series
364 return self.series
365
365
366 @util.propertycache
366 @util.propertycache
367 def seriesguards(self):
367 def seriesguards(self):
368 self.parseseries()
368 self.parseseries()
369 return self.seriesguards
369 return self.seriesguards
370
370
371 def invalidate(self):
371 def invalidate(self):
372 for a in 'applied fullseries series seriesguards'.split():
372 for a in 'applied fullseries series seriesguards'.split():
373 if a in self.__dict__:
373 if a in self.__dict__:
374 delattr(self, a)
374 delattr(self, a)
375 self.applieddirty = False
375 self.applieddirty = False
376 self.seriesdirty = False
376 self.seriesdirty = False
377 self.guardsdirty = False
377 self.guardsdirty = False
378 self.activeguards = None
378 self.activeguards = None
379
379
380 def diffopts(self, opts={}, patchfn=None):
380 def diffopts(self, opts={}, patchfn=None):
381 diffopts = patchmod.diffopts(self.ui, opts)
381 diffopts = patchmod.diffopts(self.ui, opts)
382 if self.gitmode == 'auto':
382 if self.gitmode == 'auto':
383 diffopts.upgrade = True
383 diffopts.upgrade = True
384 elif self.gitmode == 'keep':
384 elif self.gitmode == 'keep':
385 pass
385 pass
386 elif self.gitmode in ('yes', 'no'):
386 elif self.gitmode in ('yes', 'no'):
387 diffopts.git = self.gitmode == 'yes'
387 diffopts.git = self.gitmode == 'yes'
388 else:
388 else:
389 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
389 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
390 ' got %s') % self.gitmode)
390 ' got %s') % self.gitmode)
391 if patchfn:
391 if patchfn:
392 diffopts = self.patchopts(diffopts, patchfn)
392 diffopts = self.patchopts(diffopts, patchfn)
393 return diffopts
393 return diffopts
394
394
395 def patchopts(self, diffopts, *patches):
395 def patchopts(self, diffopts, *patches):
396 """Return a copy of input diff options with git set to true if
396 """Return a copy of input diff options with git set to true if
397 referenced patch is a git patch and should be preserved as such.
397 referenced patch is a git patch and should be preserved as such.
398 """
398 """
399 diffopts = diffopts.copy()
399 diffopts = diffopts.copy()
400 if not diffopts.git and self.gitmode == 'keep':
400 if not diffopts.git and self.gitmode == 'keep':
401 for patchfn in patches:
401 for patchfn in patches:
402 patchf = self.opener(patchfn, 'r')
402 patchf = self.opener(patchfn, 'r')
403 # if the patch was a git patch, refresh it as a git patch
403 # if the patch was a git patch, refresh it as a git patch
404 for line in patchf:
404 for line in patchf:
405 if line.startswith('diff --git'):
405 if line.startswith('diff --git'):
406 diffopts.git = True
406 diffopts.git = True
407 break
407 break
408 patchf.close()
408 patchf.close()
409 return diffopts
409 return diffopts
410
410
411 def join(self, *p):
411 def join(self, *p):
412 return os.path.join(self.path, *p)
412 return os.path.join(self.path, *p)
413
413
414 def findseries(self, patch):
414 def findseries(self, patch):
415 def matchpatch(l):
415 def matchpatch(l):
416 l = l.split('#', 1)[0]
416 l = l.split('#', 1)[0]
417 return l.strip() == patch
417 return l.strip() == patch
418 for index, l in enumerate(self.fullseries):
418 for index, l in enumerate(self.fullseries):
419 if matchpatch(l):
419 if matchpatch(l):
420 return index
420 return index
421 return None
421 return None
422
422
423 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
423 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
424
424
425 def parseseries(self):
425 def parseseries(self):
426 self.series = []
426 self.series = []
427 self.seriesguards = []
427 self.seriesguards = []
428 for l in self.fullseries:
428 for l in self.fullseries:
429 h = l.find('#')
429 h = l.find('#')
430 if h == -1:
430 if h == -1:
431 patch = l
431 patch = l
432 comment = ''
432 comment = ''
433 elif h == 0:
433 elif h == 0:
434 continue
434 continue
435 else:
435 else:
436 patch = l[:h]
436 patch = l[:h]
437 comment = l[h:]
437 comment = l[h:]
438 patch = patch.strip()
438 patch = patch.strip()
439 if patch:
439 if patch:
440 if patch in self.series:
440 if patch in self.series:
441 raise util.Abort(_('%s appears more than once in %s') %
441 raise util.Abort(_('%s appears more than once in %s') %
442 (patch, self.join(self.seriespath)))
442 (patch, self.join(self.seriespath)))
443 self.series.append(patch)
443 self.series.append(patch)
444 self.seriesguards.append(self.guard_re.findall(comment))
444 self.seriesguards.append(self.guard_re.findall(comment))
445
445
446 def checkguard(self, guard):
446 def checkguard(self, guard):
447 if not guard:
447 if not guard:
448 return _('guard cannot be an empty string')
448 return _('guard cannot be an empty string')
449 bad_chars = '# \t\r\n\f'
449 bad_chars = '# \t\r\n\f'
450 first = guard[0]
450 first = guard[0]
451 if first in '-+':
451 if first in '-+':
452 return (_('guard %r starts with invalid character: %r') %
452 return (_('guard %r starts with invalid character: %r') %
453 (guard, first))
453 (guard, first))
454 for c in bad_chars:
454 for c in bad_chars:
455 if c in guard:
455 if c in guard:
456 return _('invalid character in guard %r: %r') % (guard, c)
456 return _('invalid character in guard %r: %r') % (guard, c)
457
457
458 def setactive(self, guards):
458 def setactive(self, guards):
459 for guard in guards:
459 for guard in guards:
460 bad = self.checkguard(guard)
460 bad = self.checkguard(guard)
461 if bad:
461 if bad:
462 raise util.Abort(bad)
462 raise util.Abort(bad)
463 guards = sorted(set(guards))
463 guards = sorted(set(guards))
464 self.ui.debug('active guards: %s\n' % ' '.join(guards))
464 self.ui.debug('active guards: %s\n' % ' '.join(guards))
465 self.activeguards = guards
465 self.activeguards = guards
466 self.guardsdirty = True
466 self.guardsdirty = True
467
467
468 def active(self):
468 def active(self):
469 if self.activeguards is None:
469 if self.activeguards is None:
470 self.activeguards = []
470 self.activeguards = []
471 try:
471 try:
472 guards = self.opener.read(self.guardspath).split()
472 guards = self.opener.read(self.guardspath).split()
473 except IOError, err:
473 except IOError, err:
474 if err.errno != errno.ENOENT:
474 if err.errno != errno.ENOENT:
475 raise
475 raise
476 guards = []
476 guards = []
477 for i, guard in enumerate(guards):
477 for i, guard in enumerate(guards):
478 bad = self.checkguard(guard)
478 bad = self.checkguard(guard)
479 if bad:
479 if bad:
480 self.ui.warn('%s:%d: %s\n' %
480 self.ui.warn('%s:%d: %s\n' %
481 (self.join(self.guardspath), i + 1, bad))
481 (self.join(self.guardspath), i + 1, bad))
482 else:
482 else:
483 self.activeguards.append(guard)
483 self.activeguards.append(guard)
484 return self.activeguards
484 return self.activeguards
485
485
486 def setguards(self, idx, guards):
486 def setguards(self, idx, guards):
487 for g in guards:
487 for g in guards:
488 if len(g) < 2:
488 if len(g) < 2:
489 raise util.Abort(_('guard %r too short') % g)
489 raise util.Abort(_('guard %r too short') % g)
490 if g[0] not in '-+':
490 if g[0] not in '-+':
491 raise util.Abort(_('guard %r starts with invalid char') % g)
491 raise util.Abort(_('guard %r starts with invalid char') % g)
492 bad = self.checkguard(g[1:])
492 bad = self.checkguard(g[1:])
493 if bad:
493 if bad:
494 raise util.Abort(bad)
494 raise util.Abort(bad)
495 drop = self.guard_re.sub('', self.fullseries[idx])
495 drop = self.guard_re.sub('', self.fullseries[idx])
496 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
496 self.fullseries[idx] = drop + ''.join([' #' + g for g in guards])
497 self.parseseries()
497 self.parseseries()
498 self.seriesdirty = True
498 self.seriesdirty = True
499
499
500 def pushable(self, idx):
500 def pushable(self, idx):
501 if isinstance(idx, str):
501 if isinstance(idx, str):
502 idx = self.series.index(idx)
502 idx = self.series.index(idx)
503 patchguards = self.seriesguards[idx]
503 patchguards = self.seriesguards[idx]
504 if not patchguards:
504 if not patchguards:
505 return True, None
505 return True, None
506 guards = self.active()
506 guards = self.active()
507 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
507 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
508 if exactneg:
508 if exactneg:
509 return False, repr(exactneg[0])
509 return False, repr(exactneg[0])
510 pos = [g for g in patchguards if g[0] == '+']
510 pos = [g for g in patchguards if g[0] == '+']
511 exactpos = [g for g in pos if g[1:] in guards]
511 exactpos = [g for g in pos if g[1:] in guards]
512 if pos:
512 if pos:
513 if exactpos:
513 if exactpos:
514 return True, repr(exactpos[0])
514 return True, repr(exactpos[0])
515 return False, ' '.join(map(repr, pos))
515 return False, ' '.join(map(repr, pos))
516 return True, ''
516 return True, ''
517
517
518 def explainpushable(self, idx, all_patches=False):
518 def explainpushable(self, idx, all_patches=False):
519 write = all_patches and self.ui.write or self.ui.warn
519 write = all_patches and self.ui.write or self.ui.warn
520 if all_patches or self.ui.verbose:
520 if all_patches or self.ui.verbose:
521 if isinstance(idx, str):
521 if isinstance(idx, str):
522 idx = self.series.index(idx)
522 idx = self.series.index(idx)
523 pushable, why = self.pushable(idx)
523 pushable, why = self.pushable(idx)
524 if all_patches and pushable:
524 if all_patches and pushable:
525 if why is None:
525 if why is None:
526 write(_('allowing %s - no guards in effect\n') %
526 write(_('allowing %s - no guards in effect\n') %
527 self.series[idx])
527 self.series[idx])
528 else:
528 else:
529 if not why:
529 if not why:
530 write(_('allowing %s - no matching negative guards\n') %
530 write(_('allowing %s - no matching negative guards\n') %
531 self.series[idx])
531 self.series[idx])
532 else:
532 else:
533 write(_('allowing %s - guarded by %s\n') %
533 write(_('allowing %s - guarded by %s\n') %
534 (self.series[idx], why))
534 (self.series[idx], why))
535 if not pushable:
535 if not pushable:
536 if why:
536 if why:
537 write(_('skipping %s - guarded by %s\n') %
537 write(_('skipping %s - guarded by %s\n') %
538 (self.series[idx], why))
538 (self.series[idx], why))
539 else:
539 else:
540 write(_('skipping %s - no matching guards\n') %
540 write(_('skipping %s - no matching guards\n') %
541 self.series[idx])
541 self.series[idx])
542
542
543 def savedirty(self):
543 def savedirty(self):
544 def writelist(items, path):
544 def writelist(items, path):
545 fp = self.opener(path, 'w')
545 fp = self.opener(path, 'w')
546 for i in items:
546 for i in items:
547 fp.write("%s\n" % i)
547 fp.write("%s\n" % i)
548 fp.close()
548 fp.close()
549 if self.applieddirty:
549 if self.applieddirty:
550 writelist(map(str, self.applied), self.statuspath)
550 writelist(map(str, self.applied), self.statuspath)
551 self.applieddirty = False
551 self.applieddirty = False
552 if self.seriesdirty:
552 if self.seriesdirty:
553 writelist(self.fullseries, self.seriespath)
553 writelist(self.fullseries, self.seriespath)
554 self.seriesdirty = False
554 self.seriesdirty = False
555 if self.guardsdirty:
555 if self.guardsdirty:
556 writelist(self.activeguards, self.guardspath)
556 writelist(self.activeguards, self.guardspath)
557 self.guardsdirty = False
557 self.guardsdirty = False
558 if self.added:
558 if self.added:
559 qrepo = self.qrepo()
559 qrepo = self.qrepo()
560 if qrepo:
560 if qrepo:
561 qrepo[None].add(f for f in self.added if f not in qrepo[None])
561 qrepo[None].add(f for f in self.added if f not in qrepo[None])
562 self.added = []
562 self.added = []
563
563
564 def removeundo(self, repo):
564 def removeundo(self, repo):
565 undo = repo.sjoin('undo')
565 undo = repo.sjoin('undo')
566 if not os.path.exists(undo):
566 if not os.path.exists(undo):
567 return
567 return
568 try:
568 try:
569 os.unlink(undo)
569 os.unlink(undo)
570 except OSError, inst:
570 except OSError, inst:
571 self.ui.warn(_('error removing undo: %s\n') % str(inst))
571 self.ui.warn(_('error removing undo: %s\n') % str(inst))
572
572
573 def backup(self, repo, files, copy=False):
573 def backup(self, repo, files, copy=False):
574 # backup local changes in --force case
574 # backup local changes in --force case
575 for f in sorted(files):
575 for f in sorted(files):
576 absf = repo.wjoin(f)
576 absf = repo.wjoin(f)
577 if os.path.lexists(absf):
577 if os.path.lexists(absf):
578 self.ui.note(_('saving current version of %s as %s\n') %
578 self.ui.note(_('saving current version of %s as %s\n') %
579 (f, f + '.orig'))
579 (f, f + '.orig'))
580 if copy:
580 if copy:
581 util.copyfile(absf, absf + '.orig')
581 util.copyfile(absf, absf + '.orig')
582 else:
582 else:
583 util.rename(absf, absf + '.orig')
583 util.rename(absf, absf + '.orig')
584
584
585 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
585 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
586 fp=None, changes=None, opts={}):
586 fp=None, changes=None, opts={}):
587 stat = opts.get('stat')
587 stat = opts.get('stat')
588 m = scmutil.match(repo[node1], files, opts)
588 m = scmutil.match(repo[node1], files, opts)
589 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
589 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
590 changes, stat, fp)
590 changes, stat, fp)
591
591
592 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
592 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
593 # first try just applying the patch
593 # first try just applying the patch
594 (err, n) = self.apply(repo, [patch], update_status=False,
594 (err, n) = self.apply(repo, [patch], update_status=False,
595 strict=True, merge=rev)
595 strict=True, merge=rev)
596
596
597 if err == 0:
597 if err == 0:
598 return (err, n)
598 return (err, n)
599
599
600 if n is None:
600 if n is None:
601 raise util.Abort(_("apply failed for patch %s") % patch)
601 raise util.Abort(_("apply failed for patch %s") % patch)
602
602
603 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
603 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
604
604
605 # apply failed, strip away that rev and merge.
605 # apply failed, strip away that rev and merge.
606 hg.clean(repo, head)
606 hg.clean(repo, head)
607 self.strip(repo, [n], update=False, backup='strip')
607 self.strip(repo, [n], update=False, backup='strip')
608
608
609 ctx = repo[rev]
609 ctx = repo[rev]
610 ret = hg.merge(repo, rev)
610 ret = hg.merge(repo, rev)
611 if ret:
611 if ret:
612 raise util.Abort(_("update returned %d") % ret)
612 raise util.Abort(_("update returned %d") % ret)
613 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
613 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
614 if n is None:
614 if n is None:
615 raise util.Abort(_("repo commit failed"))
615 raise util.Abort(_("repo commit failed"))
616 try:
616 try:
617 ph = patchheader(mergeq.join(patch), self.plainmode)
617 ph = patchheader(mergeq.join(patch), self.plainmode)
618 except Exception:
618 except Exception:
619 raise util.Abort(_("unable to read %s") % patch)
619 raise util.Abort(_("unable to read %s") % patch)
620
620
621 diffopts = self.patchopts(diffopts, patch)
621 diffopts = self.patchopts(diffopts, patch)
622 patchf = self.opener(patch, "w")
622 patchf = self.opener(patch, "w")
623 comments = str(ph)
623 comments = str(ph)
624 if comments:
624 if comments:
625 patchf.write(comments)
625 patchf.write(comments)
626 self.printdiff(repo, diffopts, head, n, fp=patchf)
626 self.printdiff(repo, diffopts, head, n, fp=patchf)
627 patchf.close()
627 patchf.close()
628 self.removeundo(repo)
628 self.removeundo(repo)
629 return (0, n)
629 return (0, n)
630
630
631 def qparents(self, repo, rev=None):
631 def qparents(self, repo, rev=None):
632 if rev is None:
632 if rev is None:
633 (p1, p2) = repo.dirstate.parents()
633 (p1, p2) = repo.dirstate.parents()
634 if p2 == nullid:
634 if p2 == nullid:
635 return p1
635 return p1
636 if not self.applied:
636 if not self.applied:
637 return None
637 return None
638 return self.applied[-1].node
638 return self.applied[-1].node
639 p1, p2 = repo.changelog.parents(rev)
639 p1, p2 = repo.changelog.parents(rev)
640 if p2 != nullid and p2 in [x.node for x in self.applied]:
640 if p2 != nullid and p2 in [x.node for x in self.applied]:
641 return p2
641 return p2
642 return p1
642 return p1
643
643
644 def mergepatch(self, repo, mergeq, series, diffopts):
644 def mergepatch(self, repo, mergeq, series, diffopts):
645 if not self.applied:
645 if not self.applied:
646 # each of the patches merged in will have two parents. This
646 # each of the patches merged in will have two parents. This
647 # can confuse the qrefresh, qdiff, and strip code because it
647 # can confuse the qrefresh, qdiff, and strip code because it
648 # needs to know which parent is actually in the patch queue.
648 # needs to know which parent is actually in the patch queue.
649 # so, we insert a merge marker with only one parent. This way
649 # so, we insert a merge marker with only one parent. This way
650 # the first patch in the queue is never a merge patch
650 # the first patch in the queue is never a merge patch
651 #
651 #
652 pname = ".hg.patches.merge.marker"
652 pname = ".hg.patches.merge.marker"
653 n = newcommit(repo, None, '[mq]: merge marker', force=True)
653 n = newcommit(repo, None, '[mq]: merge marker', force=True)
654 self.removeundo(repo)
654 self.removeundo(repo)
655 self.applied.append(statusentry(n, pname))
655 self.applied.append(statusentry(n, pname))
656 self.applieddirty = True
656 self.applieddirty = True
657
657
658 head = self.qparents(repo)
658 head = self.qparents(repo)
659
659
660 for patch in series:
660 for patch in series:
661 patch = mergeq.lookup(patch, strict=True)
661 patch = mergeq.lookup(patch, strict=True)
662 if not patch:
662 if not patch:
663 self.ui.warn(_("patch %s does not exist\n") % patch)
663 self.ui.warn(_("patch %s does not exist\n") % patch)
664 return (1, None)
664 return (1, None)
665 pushable, reason = self.pushable(patch)
665 pushable, reason = self.pushable(patch)
666 if not pushable:
666 if not pushable:
667 self.explainpushable(patch, all_patches=True)
667 self.explainpushable(patch, all_patches=True)
668 continue
668 continue
669 info = mergeq.isapplied(patch)
669 info = mergeq.isapplied(patch)
670 if not info:
670 if not info:
671 self.ui.warn(_("patch %s is not applied\n") % patch)
671 self.ui.warn(_("patch %s is not applied\n") % patch)
672 return (1, None)
672 return (1, None)
673 rev = info[1]
673 rev = info[1]
674 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
674 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
675 if head:
675 if head:
676 self.applied.append(statusentry(head, patch))
676 self.applied.append(statusentry(head, patch))
677 self.applieddirty = True
677 self.applieddirty = True
678 if err:
678 if err:
679 return (err, head)
679 return (err, head)
680 self.savedirty()
680 self.savedirty()
681 return (0, head)
681 return (0, head)
682
682
683 def patch(self, repo, patchfile):
683 def patch(self, repo, patchfile):
684 '''Apply patchfile to the working directory.
684 '''Apply patchfile to the working directory.
685 patchfile: name of patch file'''
685 patchfile: name of patch file'''
686 files = set()
686 files = set()
687 try:
687 try:
688 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
688 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
689 files=files, eolmode=None)
689 files=files, eolmode=None)
690 return (True, list(files), fuzz)
690 return (True, list(files), fuzz)
691 except Exception, inst:
691 except Exception, inst:
692 self.ui.note(str(inst) + '\n')
692 self.ui.note(str(inst) + '\n')
693 if not self.ui.verbose:
693 if not self.ui.verbose:
694 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
694 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
695 self.ui.traceback()
695 self.ui.traceback()
696 return (False, list(files), False)
696 return (False, list(files), False)
697
697
698 def apply(self, repo, series, list=False, update_status=True,
698 def apply(self, repo, series, list=False, update_status=True,
699 strict=False, patchdir=None, merge=None, all_files=None,
699 strict=False, patchdir=None, merge=None, all_files=None,
700 tobackup=None, keepchanges=False):
700 tobackup=None, keepchanges=False):
701 wlock = lock = tr = None
701 wlock = lock = tr = None
702 try:
702 try:
703 wlock = repo.wlock()
703 wlock = repo.wlock()
704 lock = repo.lock()
704 lock = repo.lock()
705 tr = repo.transaction("qpush")
705 tr = repo.transaction("qpush")
706 try:
706 try:
707 ret = self._apply(repo, series, list, update_status,
707 ret = self._apply(repo, series, list, update_status,
708 strict, patchdir, merge, all_files=all_files,
708 strict, patchdir, merge, all_files=all_files,
709 tobackup=tobackup, keepchanges=keepchanges)
709 tobackup=tobackup, keepchanges=keepchanges)
710 tr.close()
710 tr.close()
711 self.savedirty()
711 self.savedirty()
712 return ret
712 return ret
713 except AbortNoCleanup:
713 except AbortNoCleanup:
714 tr.close()
714 tr.close()
715 self.savedirty()
715 self.savedirty()
716 return 2, repo.dirstate.p1()
716 return 2, repo.dirstate.p1()
717 except: # re-raises
717 except: # re-raises
718 try:
718 try:
719 tr.abort()
719 tr.abort()
720 finally:
720 finally:
721 repo.invalidate()
721 repo.invalidate()
722 repo.dirstate.invalidate()
722 repo.dirstate.invalidate()
723 self.invalidate()
723 self.invalidate()
724 raise
724 raise
725 finally:
725 finally:
726 release(tr, lock, wlock)
726 release(tr, lock, wlock)
727 self.removeundo(repo)
727 self.removeundo(repo)
728
728
729 def _apply(self, repo, series, list=False, update_status=True,
729 def _apply(self, repo, series, list=False, update_status=True,
730 strict=False, patchdir=None, merge=None, all_files=None,
730 strict=False, patchdir=None, merge=None, all_files=None,
731 tobackup=None, keepchanges=False):
731 tobackup=None, keepchanges=False):
732 """returns (error, hash)
732 """returns (error, hash)
733
733
734 error = 1 for unable to read, 2 for patch failed, 3 for patch
734 error = 1 for unable to read, 2 for patch failed, 3 for patch
735 fuzz. tobackup is None or a set of files to backup before they
735 fuzz. tobackup is None or a set of files to backup before they
736 are modified by a patch.
736 are modified by a patch.
737 """
737 """
738 # TODO unify with commands.py
738 # TODO unify with commands.py
739 if not patchdir:
739 if not patchdir:
740 patchdir = self.path
740 patchdir = self.path
741 err = 0
741 err = 0
742 n = None
742 n = None
743 for patchname in series:
743 for patchname in series:
744 pushable, reason = self.pushable(patchname)
744 pushable, reason = self.pushable(patchname)
745 if not pushable:
745 if not pushable:
746 self.explainpushable(patchname, all_patches=True)
746 self.explainpushable(patchname, all_patches=True)
747 continue
747 continue
748 self.ui.status(_("applying %s\n") % patchname)
748 self.ui.status(_("applying %s\n") % patchname)
749 pf = os.path.join(patchdir, patchname)
749 pf = os.path.join(patchdir, patchname)
750
750
751 try:
751 try:
752 ph = patchheader(self.join(patchname), self.plainmode)
752 ph = patchheader(self.join(patchname), self.plainmode)
753 except IOError:
753 except IOError:
754 self.ui.warn(_("unable to read %s\n") % patchname)
754 self.ui.warn(_("unable to read %s\n") % patchname)
755 err = 1
755 err = 1
756 break
756 break
757
757
758 message = ph.message
758 message = ph.message
759 if not message:
759 if not message:
760 # The commit message should not be translated
760 # The commit message should not be translated
761 message = "imported patch %s\n" % patchname
761 message = "imported patch %s\n" % patchname
762 else:
762 else:
763 if list:
763 if list:
764 # The commit message should not be translated
764 # The commit message should not be translated
765 message.append("\nimported patch %s" % patchname)
765 message.append("\nimported patch %s" % patchname)
766 message = '\n'.join(message)
766 message = '\n'.join(message)
767
767
768 if ph.haspatch:
768 if ph.haspatch:
769 if tobackup:
769 if tobackup:
770 touched = patchmod.changedfiles(self.ui, repo, pf)
770 touched = patchmod.changedfiles(self.ui, repo, pf)
771 touched = set(touched) & tobackup
771 touched = set(touched) & tobackup
772 if touched and keepchanges:
772 if touched and keepchanges:
773 raise AbortNoCleanup(
773 raise AbortNoCleanup(
774 _("local changes found, refresh first"))
774 _("local changes found, refresh first"))
775 self.backup(repo, touched, copy=True)
775 self.backup(repo, touched, copy=True)
776 tobackup = tobackup - touched
776 tobackup = tobackup - touched
777 (patcherr, files, fuzz) = self.patch(repo, pf)
777 (patcherr, files, fuzz) = self.patch(repo, pf)
778 if all_files is not None:
778 if all_files is not None:
779 all_files.update(files)
779 all_files.update(files)
780 patcherr = not patcherr
780 patcherr = not patcherr
781 else:
781 else:
782 self.ui.warn(_("patch %s is empty\n") % patchname)
782 self.ui.warn(_("patch %s is empty\n") % patchname)
783 patcherr, files, fuzz = 0, [], 0
783 patcherr, files, fuzz = 0, [], 0
784
784
785 if merge and files:
785 if merge and files:
786 # Mark as removed/merged and update dirstate parent info
786 # Mark as removed/merged and update dirstate parent info
787 removed = []
787 removed = []
788 merged = []
788 merged = []
789 for f in files:
789 for f in files:
790 if os.path.lexists(repo.wjoin(f)):
790 if os.path.lexists(repo.wjoin(f)):
791 merged.append(f)
791 merged.append(f)
792 else:
792 else:
793 removed.append(f)
793 removed.append(f)
794 for f in removed:
794 for f in removed:
795 repo.dirstate.remove(f)
795 repo.dirstate.remove(f)
796 for f in merged:
796 for f in merged:
797 repo.dirstate.merge(f)
797 repo.dirstate.merge(f)
798 p1, p2 = repo.dirstate.parents()
798 p1, p2 = repo.dirstate.parents()
799 repo.setparents(p1, merge)
799 repo.setparents(p1, merge)
800
800
801 match = scmutil.matchfiles(repo, files or [])
801 match = scmutil.matchfiles(repo, files or [])
802 oldtip = repo['tip']
802 oldtip = repo['tip']
803 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
803 n = newcommit(repo, None, message, ph.user, ph.date, match=match,
804 force=True)
804 force=True)
805 if repo['tip'] == oldtip:
805 if repo['tip'] == oldtip:
806 raise util.Abort(_("qpush exactly duplicates child changeset"))
806 raise util.Abort(_("qpush exactly duplicates child changeset"))
807 if n is None:
807 if n is None:
808 raise util.Abort(_("repository commit failed"))
808 raise util.Abort(_("repository commit failed"))
809
809
810 if update_status:
810 if update_status:
811 self.applied.append(statusentry(n, patchname))
811 self.applied.append(statusentry(n, patchname))
812
812
813 if patcherr:
813 if patcherr:
814 self.ui.warn(_("patch failed, rejects left in working dir\n"))
814 self.ui.warn(_("patch failed, rejects left in working dir\n"))
815 err = 2
815 err = 2
816 break
816 break
817
817
818 if fuzz and strict:
818 if fuzz and strict:
819 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
819 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
820 err = 3
820 err = 3
821 break
821 break
822 return (err, n)
822 return (err, n)
823
823
824 def _cleanup(self, patches, numrevs, keep=False):
824 def _cleanup(self, patches, numrevs, keep=False):
825 if not keep:
825 if not keep:
826 r = self.qrepo()
826 r = self.qrepo()
827 if r:
827 if r:
828 r[None].forget(patches)
828 r[None].forget(patches)
829 for p in patches:
829 for p in patches:
830 try:
830 try:
831 os.unlink(self.join(p))
831 os.unlink(self.join(p))
832 except OSError, inst:
832 except OSError, inst:
833 if inst.errno != errno.ENOENT:
833 if inst.errno != errno.ENOENT:
834 raise
834 raise
835
835
836 qfinished = []
836 qfinished = []
837 if numrevs:
837 if numrevs:
838 qfinished = self.applied[:numrevs]
838 qfinished = self.applied[:numrevs]
839 del self.applied[:numrevs]
839 del self.applied[:numrevs]
840 self.applieddirty = True
840 self.applieddirty = True
841
841
842 unknown = []
842 unknown = []
843
843
844 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
844 for (i, p) in sorted([(self.findseries(p), p) for p in patches],
845 reverse=True):
845 reverse=True):
846 if i is not None:
846 if i is not None:
847 del self.fullseries[i]
847 del self.fullseries[i]
848 else:
848 else:
849 unknown.append(p)
849 unknown.append(p)
850
850
851 if unknown:
851 if unknown:
852 if numrevs:
852 if numrevs:
853 rev = dict((entry.name, entry.node) for entry in qfinished)
853 rev = dict((entry.name, entry.node) for entry in qfinished)
854 for p in unknown:
854 for p in unknown:
855 msg = _('revision %s refers to unknown patches: %s\n')
855 msg = _('revision %s refers to unknown patches: %s\n')
856 self.ui.warn(msg % (short(rev[p]), p))
856 self.ui.warn(msg % (short(rev[p]), p))
857 else:
857 else:
858 msg = _('unknown patches: %s\n')
858 msg = _('unknown patches: %s\n')
859 raise util.Abort(''.join(msg % p for p in unknown))
859 raise util.Abort(''.join(msg % p for p in unknown))
860
860
861 self.parseseries()
861 self.parseseries()
862 self.seriesdirty = True
862 self.seriesdirty = True
863 return [entry.node for entry in qfinished]
863 return [entry.node for entry in qfinished]
864
864
865 def _revpatches(self, repo, revs):
865 def _revpatches(self, repo, revs):
866 firstrev = repo[self.applied[0].node].rev()
866 firstrev = repo[self.applied[0].node].rev()
867 patches = []
867 patches = []
868 for i, rev in enumerate(revs):
868 for i, rev in enumerate(revs):
869
869
870 if rev < firstrev:
870 if rev < firstrev:
871 raise util.Abort(_('revision %d is not managed') % rev)
871 raise util.Abort(_('revision %d is not managed') % rev)
872
872
873 ctx = repo[rev]
873 ctx = repo[rev]
874 base = self.applied[i].node
874 base = self.applied[i].node
875 if ctx.node() != base:
875 if ctx.node() != base:
876 msg = _('cannot delete revision %d above applied patches')
876 msg = _('cannot delete revision %d above applied patches')
877 raise util.Abort(msg % rev)
877 raise util.Abort(msg % rev)
878
878
879 patch = self.applied[i].name
879 patch = self.applied[i].name
880 for fmt in ('[mq]: %s', 'imported patch %s'):
880 for fmt in ('[mq]: %s', 'imported patch %s'):
881 if ctx.description() == fmt % patch:
881 if ctx.description() == fmt % patch:
882 msg = _('patch %s finalized without changeset message\n')
882 msg = _('patch %s finalized without changeset message\n')
883 repo.ui.status(msg % patch)
883 repo.ui.status(msg % patch)
884 break
884 break
885
885
886 patches.append(patch)
886 patches.append(patch)
887 return patches
887 return patches
888
888
889 def finish(self, repo, revs):
889 def finish(self, repo, revs):
890 # Manually trigger phase computation to ensure phasedefaults is
890 # Manually trigger phase computation to ensure phasedefaults is
891 # executed before we remove the patches.
891 # executed before we remove the patches.
892 repo._phasecache
892 repo._phasecache
893 patches = self._revpatches(repo, sorted(revs))
893 patches = self._revpatches(repo, sorted(revs))
894 qfinished = self._cleanup(patches, len(patches))
894 qfinished = self._cleanup(patches, len(patches))
895 if qfinished and repo.ui.configbool('mq', 'secret', False):
895 if qfinished and repo.ui.configbool('mq', 'secret', False):
896 # only use this logic when the secret option is added
896 # only use this logic when the secret option is added
897 oldqbase = repo[qfinished[0]]
897 oldqbase = repo[qfinished[0]]
898 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
898 tphase = repo.ui.config('phases', 'new-commit', phases.draft)
899 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
899 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
900 phases.advanceboundary(repo, tphase, qfinished)
900 phases.advanceboundary(repo, tphase, qfinished)
901
901
902 def delete(self, repo, patches, opts):
902 def delete(self, repo, patches, opts):
903 if not patches and not opts.get('rev'):
903 if not patches and not opts.get('rev'):
904 raise util.Abort(_('qdelete requires at least one revision or '
904 raise util.Abort(_('qdelete requires at least one revision or '
905 'patch name'))
905 'patch name'))
906
906
907 realpatches = []
907 realpatches = []
908 for patch in patches:
908 for patch in patches:
909 patch = self.lookup(patch, strict=True)
909 patch = self.lookup(patch, strict=True)
910 info = self.isapplied(patch)
910 info = self.isapplied(patch)
911 if info:
911 if info:
912 raise util.Abort(_("cannot delete applied patch %s") % patch)
912 raise util.Abort(_("cannot delete applied patch %s") % patch)
913 if patch not in self.series:
913 if patch not in self.series:
914 raise util.Abort(_("patch %s not in series file") % patch)
914 raise util.Abort(_("patch %s not in series file") % patch)
915 if patch not in realpatches:
915 if patch not in realpatches:
916 realpatches.append(patch)
916 realpatches.append(patch)
917
917
918 numrevs = 0
918 numrevs = 0
919 if opts.get('rev'):
919 if opts.get('rev'):
920 if not self.applied:
920 if not self.applied:
921 raise util.Abort(_('no patches applied'))
921 raise util.Abort(_('no patches applied'))
922 revs = scmutil.revrange(repo, opts.get('rev'))
922 revs = scmutil.revrange(repo, opts.get('rev'))
923 if len(revs) > 1 and revs[0] > revs[1]:
923 if len(revs) > 1 and revs[0] > revs[1]:
924 revs.reverse()
924 revs.reverse()
925 revpatches = self._revpatches(repo, revs)
925 revpatches = self._revpatches(repo, revs)
926 realpatches += revpatches
926 realpatches += revpatches
927 numrevs = len(revpatches)
927 numrevs = len(revpatches)
928
928
929 self._cleanup(realpatches, numrevs, opts.get('keep'))
929 self._cleanup(realpatches, numrevs, opts.get('keep'))
930
930
931 def checktoppatch(self, repo):
931 def checktoppatch(self, repo):
932 '''check that working directory is at qtip'''
932 '''check that working directory is at qtip'''
933 if self.applied:
933 if self.applied:
934 top = self.applied[-1].node
934 top = self.applied[-1].node
935 patch = self.applied[-1].name
935 patch = self.applied[-1].name
936 if repo.dirstate.p1() != top:
936 if repo.dirstate.p1() != top:
937 raise util.Abort(_("working directory revision is not qtip"))
937 raise util.Abort(_("working directory revision is not qtip"))
938 return top, patch
938 return top, patch
939 return None, None
939 return None, None
940
940
941 def checksubstate(self, repo, baserev=None):
941 def checksubstate(self, repo, baserev=None):
942 '''return list of subrepos at a different revision than substate.
942 '''return list of subrepos at a different revision than substate.
943 Abort if any subrepos have uncommitted changes.'''
943 Abort if any subrepos have uncommitted changes.'''
944 inclsubs = []
944 inclsubs = []
945 wctx = repo[None]
945 wctx = repo[None]
946 if baserev:
946 if baserev:
947 bctx = repo[baserev]
947 bctx = repo[baserev]
948 else:
948 else:
949 bctx = wctx.parents()[0]
949 bctx = wctx.parents()[0]
950 for s in sorted(wctx.substate):
950 for s in sorted(wctx.substate):
951 if wctx.sub(s).dirty(True):
951 if wctx.sub(s).dirty(True):
952 raise util.Abort(
952 raise util.Abort(
953 _("uncommitted changes in subrepository %s") % s)
953 _("uncommitted changes in subrepository %s") % s)
954 elif s not in bctx.substate or bctx.sub(s).dirty():
954 elif s not in bctx.substate or bctx.sub(s).dirty():
955 inclsubs.append(s)
955 inclsubs.append(s)
956 return inclsubs
956 return inclsubs
957
957
958 def putsubstate2changes(self, substatestate, changes):
958 def putsubstate2changes(self, substatestate, changes):
959 for files in changes[:3]:
959 for files in changes[:3]:
960 if '.hgsubstate' in files:
960 if '.hgsubstate' in files:
961 return # already listed up
961 return # already listed up
962 # not yet listed up
962 # not yet listed up
963 if substatestate in 'a?':
963 if substatestate in 'a?':
964 changes[1].append('.hgsubstate')
964 changes[1].append('.hgsubstate')
965 elif substatestate in 'r':
965 elif substatestate in 'r':
966 changes[2].append('.hgsubstate')
966 changes[2].append('.hgsubstate')
967 else: # modified
967 else: # modified
968 changes[0].append('.hgsubstate')
968 changes[0].append('.hgsubstate')
969
969
970 def localchangesfound(self, refresh=True):
970 def localchangesfound(self, refresh=True):
971 if refresh:
971 if refresh:
972 raise util.Abort(_("local changes found, refresh first"))
972 raise util.Abort(_("local changes found, refresh first"))
973 else:
973 else:
974 raise util.Abort(_("local changes found"))
974 raise util.Abort(_("local changes found"))
975
975
976 def checklocalchanges(self, repo, force=False, refresh=True):
976 def checklocalchanges(self, repo, force=False, refresh=True):
977 m, a, r, d = repo.status()[:4]
977 m, a, r, d = repo.status()[:4]
978 if (m or a or r or d) and not force:
978 if (m or a or r or d) and not force:
979 self.localchangesfound(refresh)
979 self.localchangesfound(refresh)
980 return m, a, r, d
980 return m, a, r, d
981
981
982 _reserved = ('series', 'status', 'guards', '.', '..')
982 _reserved = ('series', 'status', 'guards', '.', '..')
983 def checkreservedname(self, name):
983 def checkreservedname(self, name):
984 if name in self._reserved:
984 if name in self._reserved:
985 raise util.Abort(_('"%s" cannot be used as the name of a patch')
985 raise util.Abort(_('"%s" cannot be used as the name of a patch')
986 % name)
986 % name)
987 for prefix in ('.hg', '.mq'):
987 for prefix in ('.hg', '.mq'):
988 if name.startswith(prefix):
988 if name.startswith(prefix):
989 raise util.Abort(_('patch name cannot begin with "%s"')
989 raise util.Abort(_('patch name cannot begin with "%s"')
990 % prefix)
990 % prefix)
991 for c in ('#', ':'):
991 for c in ('#', ':'):
992 if c in name:
992 if c in name:
993 raise util.Abort(_('"%s" cannot be used in the name of a patch')
993 raise util.Abort(_('"%s" cannot be used in the name of a patch')
994 % c)
994 % c)
995
995
996 def checkpatchname(self, name, force=False):
996 def checkpatchname(self, name, force=False):
997 self.checkreservedname(name)
997 self.checkreservedname(name)
998 if not force and os.path.exists(self.join(name)):
998 if not force and os.path.exists(self.join(name)):
999 if os.path.isdir(self.join(name)):
999 if os.path.isdir(self.join(name)):
1000 raise util.Abort(_('"%s" already exists as a directory')
1000 raise util.Abort(_('"%s" already exists as a directory')
1001 % name)
1001 % name)
1002 else:
1002 else:
1003 raise util.Abort(_('patch "%s" already exists') % name)
1003 raise util.Abort(_('patch "%s" already exists') % name)
1004
1004
1005 def checkkeepchanges(self, keepchanges, force):
1005 def checkkeepchanges(self, keepchanges, force):
1006 if force and keepchanges:
1006 if force and keepchanges:
1007 raise util.Abort(_('cannot use both --force and --keep-changes'))
1007 raise util.Abort(_('cannot use both --force and --keep-changes'))
1008
1008
1009 def new(self, repo, patchfn, *pats, **opts):
1009 def new(self, repo, patchfn, *pats, **opts):
1010 """options:
1010 """options:
1011 msg: a string or a no-argument function returning a string
1011 msg: a string or a no-argument function returning a string
1012 """
1012 """
1013 msg = opts.get('msg')
1013 msg = opts.get('msg')
1014 user = opts.get('user')
1014 user = opts.get('user')
1015 date = opts.get('date')
1015 date = opts.get('date')
1016 if date:
1016 if date:
1017 date = util.parsedate(date)
1017 date = util.parsedate(date)
1018 diffopts = self.diffopts({'git': opts.get('git')})
1018 diffopts = self.diffopts({'git': opts.get('git')})
1019 if opts.get('checkname', True):
1019 if opts.get('checkname', True):
1020 self.checkpatchname(patchfn)
1020 self.checkpatchname(patchfn)
1021 inclsubs = self.checksubstate(repo)
1021 inclsubs = self.checksubstate(repo)
1022 if inclsubs:
1022 if inclsubs:
1023 inclsubs.append('.hgsubstate')
1023 inclsubs.append('.hgsubstate')
1024 substatestate = repo.dirstate['.hgsubstate']
1024 substatestate = repo.dirstate['.hgsubstate']
1025 if opts.get('include') or opts.get('exclude') or pats:
1025 if opts.get('include') or opts.get('exclude') or pats:
1026 if inclsubs:
1026 if inclsubs:
1027 pats = list(pats or []) + inclsubs
1027 pats = list(pats or []) + inclsubs
1028 match = scmutil.match(repo[None], pats, opts)
1028 match = scmutil.match(repo[None], pats, opts)
1029 # detect missing files in pats
1029 # detect missing files in pats
1030 def badfn(f, msg):
1030 def badfn(f, msg):
1031 if f != '.hgsubstate': # .hgsubstate is auto-created
1031 if f != '.hgsubstate': # .hgsubstate is auto-created
1032 raise util.Abort('%s: %s' % (f, msg))
1032 raise util.Abort('%s: %s' % (f, msg))
1033 match.bad = badfn
1033 match.bad = badfn
1034 changes = repo.status(match=match)
1034 changes = repo.status(match=match)
1035 m, a, r, d = changes[:4]
1035 m, a, r, d = changes[:4]
1036 else:
1036 else:
1037 changes = self.checklocalchanges(repo, force=True)
1037 changes = self.checklocalchanges(repo, force=True)
1038 m, a, r, d = changes
1038 m, a, r, d = changes
1039 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
1039 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
1040 if len(repo[None].parents()) > 1:
1040 if len(repo[None].parents()) > 1:
1041 raise util.Abort(_('cannot manage merge changesets'))
1041 raise util.Abort(_('cannot manage merge changesets'))
1042 commitfiles = m + a + r
1042 commitfiles = m + a + r
1043 self.checktoppatch(repo)
1043 self.checktoppatch(repo)
1044 insert = self.fullseriesend()
1044 insert = self.fullseriesend()
1045 wlock = repo.wlock()
1045 wlock = repo.wlock()
1046 try:
1046 try:
1047 try:
1047 try:
1048 # if patch file write fails, abort early
1048 # if patch file write fails, abort early
1049 p = self.opener(patchfn, "w")
1049 p = self.opener(patchfn, "w")
1050 except IOError, e:
1050 except IOError, e:
1051 raise util.Abort(_('cannot write patch "%s": %s')
1051 raise util.Abort(_('cannot write patch "%s": %s')
1052 % (patchfn, e.strerror))
1052 % (patchfn, e.strerror))
1053 try:
1053 try:
1054 if self.plainmode:
1054 if self.plainmode:
1055 if user:
1055 if user:
1056 p.write("From: " + user + "\n")
1056 p.write("From: " + user + "\n")
1057 if not date:
1057 if not date:
1058 p.write("\n")
1058 p.write("\n")
1059 if date:
1059 if date:
1060 p.write("Date: %d %d\n\n" % date)
1060 p.write("Date: %d %d\n\n" % date)
1061 else:
1061 else:
1062 p.write("# HG changeset patch\n")
1062 p.write("# HG changeset patch\n")
1063 p.write("# Parent "
1063 p.write("# Parent "
1064 + hex(repo[None].p1().node()) + "\n")
1064 + hex(repo[None].p1().node()) + "\n")
1065 if user:
1065 if user:
1066 p.write("# User " + user + "\n")
1066 p.write("# User " + user + "\n")
1067 if date:
1067 if date:
1068 p.write("# Date %s %s\n\n" % date)
1068 p.write("# Date %s %s\n\n" % date)
1069 if util.safehasattr(msg, '__call__'):
1069 if util.safehasattr(msg, '__call__'):
1070 msg = msg()
1070 msg = msg()
1071 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
1071 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
1072 n = newcommit(repo, None, commitmsg, user, date, match=match,
1072 n = newcommit(repo, None, commitmsg, user, date, match=match,
1073 force=True)
1073 force=True)
1074 if n is None:
1074 if n is None:
1075 raise util.Abort(_("repo commit failed"))
1075 raise util.Abort(_("repo commit failed"))
1076 try:
1076 try:
1077 self.fullseries[insert:insert] = [patchfn]
1077 self.fullseries[insert:insert] = [patchfn]
1078 self.applied.append(statusentry(n, patchfn))
1078 self.applied.append(statusentry(n, patchfn))
1079 self.parseseries()
1079 self.parseseries()
1080 self.seriesdirty = True
1080 self.seriesdirty = True
1081 self.applieddirty = True
1081 self.applieddirty = True
1082 if msg:
1082 if msg:
1083 msg = msg + "\n\n"
1083 msg = msg + "\n\n"
1084 p.write(msg)
1084 p.write(msg)
1085 if commitfiles:
1085 if commitfiles:
1086 parent = self.qparents(repo, n)
1086 parent = self.qparents(repo, n)
1087 if inclsubs:
1087 if inclsubs:
1088 self.putsubstate2changes(substatestate, changes)
1088 self.putsubstate2changes(substatestate, changes)
1089 chunks = patchmod.diff(repo, node1=parent, node2=n,
1089 chunks = patchmod.diff(repo, node1=parent, node2=n,
1090 changes=changes, opts=diffopts)
1090 changes=changes, opts=diffopts)
1091 for chunk in chunks:
1091 for chunk in chunks:
1092 p.write(chunk)
1092 p.write(chunk)
1093 p.close()
1093 p.close()
1094 r = self.qrepo()
1094 r = self.qrepo()
1095 if r:
1095 if r:
1096 r[None].add([patchfn])
1096 r[None].add([patchfn])
1097 except: # re-raises
1097 except: # re-raises
1098 repo.rollback()
1098 repo.rollback()
1099 raise
1099 raise
1100 except Exception:
1100 except Exception:
1101 patchpath = self.join(patchfn)
1101 patchpath = self.join(patchfn)
1102 try:
1102 try:
1103 os.unlink(patchpath)
1103 os.unlink(patchpath)
1104 except OSError:
1104 except OSError:
1105 self.ui.warn(_('error unlinking %s\n') % patchpath)
1105 self.ui.warn(_('error unlinking %s\n') % patchpath)
1106 raise
1106 raise
1107 self.removeundo(repo)
1107 self.removeundo(repo)
1108 finally:
1108 finally:
1109 release(wlock)
1109 release(wlock)
1110
1110
1111 def strip(self, repo, revs, update=True, backup="all", force=None):
1111 def strip(self, repo, revs, update=True, backup="all", force=None):
1112 wlock = lock = None
1112 wlock = lock = None
1113 try:
1113 try:
1114 wlock = repo.wlock()
1114 wlock = repo.wlock()
1115 lock = repo.lock()
1115 lock = repo.lock()
1116
1116
1117 if update:
1117 if update:
1118 self.checklocalchanges(repo, force=force, refresh=False)
1118 self.checklocalchanges(repo, force=force, refresh=False)
1119 urev = self.qparents(repo, revs[0])
1119 urev = self.qparents(repo, revs[0])
1120 hg.clean(repo, urev)
1120 hg.clean(repo, urev)
1121 repo.dirstate.write()
1121 repo.dirstate.write()
1122
1122
1123 repair.strip(self.ui, repo, revs, backup)
1123 repair.strip(self.ui, repo, revs, backup)
1124 finally:
1124 finally:
1125 release(lock, wlock)
1125 release(lock, wlock)
1126
1126
1127 def isapplied(self, patch):
1127 def isapplied(self, patch):
1128 """returns (index, rev, patch)"""
1128 """returns (index, rev, patch)"""
1129 for i, a in enumerate(self.applied):
1129 for i, a in enumerate(self.applied):
1130 if a.name == patch:
1130 if a.name == patch:
1131 return (i, a.node, a.name)
1131 return (i, a.node, a.name)
1132 return None
1132 return None
1133
1133
1134 # if the exact patch name does not exist, we try a few
1134 # if the exact patch name does not exist, we try a few
1135 # variations. If strict is passed, we try only #1
1135 # variations. If strict is passed, we try only #1
1136 #
1136 #
1137 # 1) a number (as string) to indicate an offset in the series file
1137 # 1) a number (as string) to indicate an offset in the series file
1138 # 2) a unique substring of the patch name was given
1138 # 2) a unique substring of the patch name was given
1139 # 3) patchname[-+]num to indicate an offset in the series file
1139 # 3) patchname[-+]num to indicate an offset in the series file
1140 def lookup(self, patch, strict=False):
1140 def lookup(self, patch, strict=False):
1141 def partialname(s):
1141 def partialname(s):
1142 if s in self.series:
1142 if s in self.series:
1143 return s
1143 return s
1144 matches = [x for x in self.series if s in x]
1144 matches = [x for x in self.series if s in x]
1145 if len(matches) > 1:
1145 if len(matches) > 1:
1146 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1146 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1147 for m in matches:
1147 for m in matches:
1148 self.ui.warn(' %s\n' % m)
1148 self.ui.warn(' %s\n' % m)
1149 return None
1149 return None
1150 if matches:
1150 if matches:
1151 return matches[0]
1151 return matches[0]
1152 if self.series and self.applied:
1152 if self.series and self.applied:
1153 if s == 'qtip':
1153 if s == 'qtip':
1154 return self.series[self.seriesend(True) - 1]
1154 return self.series[self.seriesend(True) - 1]
1155 if s == 'qbase':
1155 if s == 'qbase':
1156 return self.series[0]
1156 return self.series[0]
1157 return None
1157 return None
1158
1158
1159 if patch in self.series:
1159 if patch in self.series:
1160 return patch
1160 return patch
1161
1161
1162 if not os.path.isfile(self.join(patch)):
1162 if not os.path.isfile(self.join(patch)):
1163 try:
1163 try:
1164 sno = int(patch)
1164 sno = int(patch)
1165 except (ValueError, OverflowError):
1165 except (ValueError, OverflowError):
1166 pass
1166 pass
1167 else:
1167 else:
1168 if -len(self.series) <= sno < len(self.series):
1168 if -len(self.series) <= sno < len(self.series):
1169 return self.series[sno]
1169 return self.series[sno]
1170
1170
1171 if not strict:
1171 if not strict:
1172 res = partialname(patch)
1172 res = partialname(patch)
1173 if res:
1173 if res:
1174 return res
1174 return res
1175 minus = patch.rfind('-')
1175 minus = patch.rfind('-')
1176 if minus >= 0:
1176 if minus >= 0:
1177 res = partialname(patch[:minus])
1177 res = partialname(patch[:minus])
1178 if res:
1178 if res:
1179 i = self.series.index(res)
1179 i = self.series.index(res)
1180 try:
1180 try:
1181 off = int(patch[minus + 1:] or 1)
1181 off = int(patch[minus + 1:] or 1)
1182 except (ValueError, OverflowError):
1182 except (ValueError, OverflowError):
1183 pass
1183 pass
1184 else:
1184 else:
1185 if i - off >= 0:
1185 if i - off >= 0:
1186 return self.series[i - off]
1186 return self.series[i - off]
1187 plus = patch.rfind('+')
1187 plus = patch.rfind('+')
1188 if plus >= 0:
1188 if plus >= 0:
1189 res = partialname(patch[:plus])
1189 res = partialname(patch[:plus])
1190 if res:
1190 if res:
1191 i = self.series.index(res)
1191 i = self.series.index(res)
1192 try:
1192 try:
1193 off = int(patch[plus + 1:] or 1)
1193 off = int(patch[plus + 1:] or 1)
1194 except (ValueError, OverflowError):
1194 except (ValueError, OverflowError):
1195 pass
1195 pass
1196 else:
1196 else:
1197 if i + off < len(self.series):
1197 if i + off < len(self.series):
1198 return self.series[i + off]
1198 return self.series[i + off]
1199 raise util.Abort(_("patch %s not in series") % patch)
1199 raise util.Abort(_("patch %s not in series") % patch)
1200
1200
1201 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1201 def push(self, repo, patch=None, force=False, list=False, mergeq=None,
1202 all=False, move=False, exact=False, nobackup=False,
1202 all=False, move=False, exact=False, nobackup=False,
1203 keepchanges=False):
1203 keepchanges=False):
1204 self.checkkeepchanges(keepchanges, force)
1204 self.checkkeepchanges(keepchanges, force)
1205 diffopts = self.diffopts()
1205 diffopts = self.diffopts()
1206 wlock = repo.wlock()
1206 wlock = repo.wlock()
1207 try:
1207 try:
1208 heads = []
1208 heads = []
1209 for b, ls in repo.branchmap().iteritems():
1209 for b, ls in repo.branchmap().iteritems():
1210 heads += ls
1210 heads += ls
1211 if not heads:
1211 if not heads:
1212 heads = [nullid]
1212 heads = [nullid]
1213 if repo.dirstate.p1() not in heads and not exact:
1213 if repo.dirstate.p1() not in heads and not exact:
1214 self.ui.status(_("(working directory not at a head)\n"))
1214 self.ui.status(_("(working directory not at a head)\n"))
1215
1215
1216 if not self.series:
1216 if not self.series:
1217 self.ui.warn(_('no patches in series\n'))
1217 self.ui.warn(_('no patches in series\n'))
1218 return 0
1218 return 0
1219
1219
1220 # Suppose our series file is: A B C and the current 'top'
1220 # Suppose our series file is: A B C and the current 'top'
1221 # patch is B. qpush C should be performed (moving forward)
1221 # patch is B. qpush C should be performed (moving forward)
1222 # qpush B is a NOP (no change) qpush A is an error (can't
1222 # qpush B is a NOP (no change) qpush A is an error (can't
1223 # go backwards with qpush)
1223 # go backwards with qpush)
1224 if patch:
1224 if patch:
1225 patch = self.lookup(patch)
1225 patch = self.lookup(patch)
1226 info = self.isapplied(patch)
1226 info = self.isapplied(patch)
1227 if info and info[0] >= len(self.applied) - 1:
1227 if info and info[0] >= len(self.applied) - 1:
1228 self.ui.warn(
1228 self.ui.warn(
1229 _('qpush: %s is already at the top\n') % patch)
1229 _('qpush: %s is already at the top\n') % patch)
1230 return 0
1230 return 0
1231
1231
1232 pushable, reason = self.pushable(patch)
1232 pushable, reason = self.pushable(patch)
1233 if pushable:
1233 if pushable:
1234 if self.series.index(patch) < self.seriesend():
1234 if self.series.index(patch) < self.seriesend():
1235 raise util.Abort(
1235 raise util.Abort(
1236 _("cannot push to a previous patch: %s") % patch)
1236 _("cannot push to a previous patch: %s") % patch)
1237 else:
1237 else:
1238 if reason:
1238 if reason:
1239 reason = _('guarded by %s') % reason
1239 reason = _('guarded by %s') % reason
1240 else:
1240 else:
1241 reason = _('no matching guards')
1241 reason = _('no matching guards')
1242 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1242 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1243 return 1
1243 return 1
1244 elif all:
1244 elif all:
1245 patch = self.series[-1]
1245 patch = self.series[-1]
1246 if self.isapplied(patch):
1246 if self.isapplied(patch):
1247 self.ui.warn(_('all patches are currently applied\n'))
1247 self.ui.warn(_('all patches are currently applied\n'))
1248 return 0
1248 return 0
1249
1249
1250 # Following the above example, starting at 'top' of B:
1250 # Following the above example, starting at 'top' of B:
1251 # qpush should be performed (pushes C), but a subsequent
1251 # qpush should be performed (pushes C), but a subsequent
1252 # qpush without an argument is an error (nothing to
1252 # qpush without an argument is an error (nothing to
1253 # apply). This allows a loop of "...while hg qpush..." to
1253 # apply). This allows a loop of "...while hg qpush..." to
1254 # work as it detects an error when done
1254 # work as it detects an error when done
1255 start = self.seriesend()
1255 start = self.seriesend()
1256 if start == len(self.series):
1256 if start == len(self.series):
1257 self.ui.warn(_('patch series already fully applied\n'))
1257 self.ui.warn(_('patch series already fully applied\n'))
1258 return 1
1258 return 1
1259 if not force and not keepchanges:
1259 if not force and not keepchanges:
1260 self.checklocalchanges(repo, refresh=self.applied)
1260 self.checklocalchanges(repo, refresh=self.applied)
1261
1261
1262 if exact:
1262 if exact:
1263 if keepchanges:
1263 if keepchanges:
1264 raise util.Abort(
1264 raise util.Abort(
1265 _("cannot use --exact and --keep-changes together"))
1265 _("cannot use --exact and --keep-changes together"))
1266 if move:
1266 if move:
1267 raise util.Abort(_('cannot use --exact and --move '
1267 raise util.Abort(_('cannot use --exact and --move '
1268 'together'))
1268 'together'))
1269 if self.applied:
1269 if self.applied:
1270 raise util.Abort(_('cannot push --exact with applied '
1270 raise util.Abort(_('cannot push --exact with applied '
1271 'patches'))
1271 'patches'))
1272 root = self.series[start]
1272 root = self.series[start]
1273 target = patchheader(self.join(root), self.plainmode).parent
1273 target = patchheader(self.join(root), self.plainmode).parent
1274 if not target:
1274 if not target:
1275 raise util.Abort(
1275 raise util.Abort(
1276 _("%s does not have a parent recorded") % root)
1276 _("%s does not have a parent recorded") % root)
1277 if not repo[target] == repo['.']:
1277 if not repo[target] == repo['.']:
1278 hg.update(repo, target)
1278 hg.update(repo, target)
1279
1279
1280 if move:
1280 if move:
1281 if not patch:
1281 if not patch:
1282 raise util.Abort(_("please specify the patch to move"))
1282 raise util.Abort(_("please specify the patch to move"))
1283 for fullstart, rpn in enumerate(self.fullseries):
1283 for fullstart, rpn in enumerate(self.fullseries):
1284 # strip markers for patch guards
1284 # strip markers for patch guards
1285 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1285 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1286 break
1286 break
1287 for i, rpn in enumerate(self.fullseries[fullstart:]):
1287 for i, rpn in enumerate(self.fullseries[fullstart:]):
1288 # strip markers for patch guards
1288 # strip markers for patch guards
1289 if self.guard_re.split(rpn, 1)[0] == patch:
1289 if self.guard_re.split(rpn, 1)[0] == patch:
1290 break
1290 break
1291 index = fullstart + i
1291 index = fullstart + i
1292 assert index < len(self.fullseries)
1292 assert index < len(self.fullseries)
1293 fullpatch = self.fullseries[index]
1293 fullpatch = self.fullseries[index]
1294 del self.fullseries[index]
1294 del self.fullseries[index]
1295 self.fullseries.insert(fullstart, fullpatch)
1295 self.fullseries.insert(fullstart, fullpatch)
1296 self.parseseries()
1296 self.parseseries()
1297 self.seriesdirty = True
1297 self.seriesdirty = True
1298
1298
1299 self.applieddirty = True
1299 self.applieddirty = True
1300 if start > 0:
1300 if start > 0:
1301 self.checktoppatch(repo)
1301 self.checktoppatch(repo)
1302 if not patch:
1302 if not patch:
1303 patch = self.series[start]
1303 patch = self.series[start]
1304 end = start + 1
1304 end = start + 1
1305 else:
1305 else:
1306 end = self.series.index(patch, start) + 1
1306 end = self.series.index(patch, start) + 1
1307
1307
1308 tobackup = set()
1308 tobackup = set()
1309 if (not nobackup and force) or keepchanges:
1309 if (not nobackup and force) or keepchanges:
1310 m, a, r, d = self.checklocalchanges(repo, force=True)
1310 m, a, r, d = self.checklocalchanges(repo, force=True)
1311 if keepchanges:
1311 if keepchanges:
1312 tobackup.update(m + a + r + d)
1312 tobackup.update(m + a + r + d)
1313 else:
1313 else:
1314 tobackup.update(m + a)
1314 tobackup.update(m + a)
1315
1315
1316 s = self.series[start:end]
1316 s = self.series[start:end]
1317 all_files = set()
1317 all_files = set()
1318 try:
1318 try:
1319 if mergeq:
1319 if mergeq:
1320 ret = self.mergepatch(repo, mergeq, s, diffopts)
1320 ret = self.mergepatch(repo, mergeq, s, diffopts)
1321 else:
1321 else:
1322 ret = self.apply(repo, s, list, all_files=all_files,
1322 ret = self.apply(repo, s, list, all_files=all_files,
1323 tobackup=tobackup, keepchanges=keepchanges)
1323 tobackup=tobackup, keepchanges=keepchanges)
1324 except: # re-raises
1324 except: # re-raises
1325 self.ui.warn(_('cleaning up working directory...'))
1325 self.ui.warn(_('cleaning up working directory...'))
1326 node = repo.dirstate.p1()
1326 node = repo.dirstate.p1()
1327 hg.revert(repo, node, None)
1327 hg.revert(repo, node, None)
1328 # only remove unknown files that we know we touched or
1328 # only remove unknown files that we know we touched or
1329 # created while patching
1329 # created while patching
1330 for f in all_files:
1330 for f in all_files:
1331 if f not in repo.dirstate:
1331 if f not in repo.dirstate:
1332 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1332 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1333 self.ui.warn(_('done\n'))
1333 self.ui.warn(_('done\n'))
1334 raise
1334 raise
1335
1335
1336 if not self.applied:
1336 if not self.applied:
1337 return ret[0]
1337 return ret[0]
1338 top = self.applied[-1].name
1338 top = self.applied[-1].name
1339 if ret[0] and ret[0] > 1:
1339 if ret[0] and ret[0] > 1:
1340 msg = _("errors during apply, please fix and refresh %s\n")
1340 msg = _("errors during apply, please fix and refresh %s\n")
1341 self.ui.write(msg % top)
1341 self.ui.write(msg % top)
1342 else:
1342 else:
1343 self.ui.write(_("now at: %s\n") % top)
1343 self.ui.write(_("now at: %s\n") % top)
1344 return ret[0]
1344 return ret[0]
1345
1345
1346 finally:
1346 finally:
1347 wlock.release()
1347 wlock.release()
1348
1348
1349 def pop(self, repo, patch=None, force=False, update=True, all=False,
1349 def pop(self, repo, patch=None, force=False, update=True, all=False,
1350 nobackup=False, keepchanges=False):
1350 nobackup=False, keepchanges=False):
1351 self.checkkeepchanges(keepchanges, force)
1351 self.checkkeepchanges(keepchanges, force)
1352 wlock = repo.wlock()
1352 wlock = repo.wlock()
1353 try:
1353 try:
1354 if patch:
1354 if patch:
1355 # index, rev, patch
1355 # index, rev, patch
1356 info = self.isapplied(patch)
1356 info = self.isapplied(patch)
1357 if not info:
1357 if not info:
1358 patch = self.lookup(patch)
1358 patch = self.lookup(patch)
1359 info = self.isapplied(patch)
1359 info = self.isapplied(patch)
1360 if not info:
1360 if not info:
1361 raise util.Abort(_("patch %s is not applied") % patch)
1361 raise util.Abort(_("patch %s is not applied") % patch)
1362
1362
1363 if not self.applied:
1363 if not self.applied:
1364 # Allow qpop -a to work repeatedly,
1364 # Allow qpop -a to work repeatedly,
1365 # but not qpop without an argument
1365 # but not qpop without an argument
1366 self.ui.warn(_("no patches applied\n"))
1366 self.ui.warn(_("no patches applied\n"))
1367 return not all
1367 return not all
1368
1368
1369 if all:
1369 if all:
1370 start = 0
1370 start = 0
1371 elif patch:
1371 elif patch:
1372 start = info[0] + 1
1372 start = info[0] + 1
1373 else:
1373 else:
1374 start = len(self.applied) - 1
1374 start = len(self.applied) - 1
1375
1375
1376 if start >= len(self.applied):
1376 if start >= len(self.applied):
1377 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1377 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1378 return
1378 return
1379
1379
1380 if not update:
1380 if not update:
1381 parents = repo.dirstate.parents()
1381 parents = repo.dirstate.parents()
1382 rr = [x.node for x in self.applied]
1382 rr = [x.node for x in self.applied]
1383 for p in parents:
1383 for p in parents:
1384 if p in rr:
1384 if p in rr:
1385 self.ui.warn(_("qpop: forcing dirstate update\n"))
1385 self.ui.warn(_("qpop: forcing dirstate update\n"))
1386 update = True
1386 update = True
1387 else:
1387 else:
1388 parents = [p.node() for p in repo[None].parents()]
1388 parents = [p.node() for p in repo[None].parents()]
1389 needupdate = False
1389 needupdate = False
1390 for entry in self.applied[start:]:
1390 for entry in self.applied[start:]:
1391 if entry.node in parents:
1391 if entry.node in parents:
1392 needupdate = True
1392 needupdate = True
1393 break
1393 break
1394 update = needupdate
1394 update = needupdate
1395
1395
1396 tobackup = set()
1396 tobackup = set()
1397 if update:
1397 if update:
1398 m, a, r, d = self.checklocalchanges(
1398 m, a, r, d = self.checklocalchanges(
1399 repo, force=force or keepchanges)
1399 repo, force=force or keepchanges)
1400 if force:
1400 if force:
1401 if not nobackup:
1401 if not nobackup:
1402 tobackup.update(m + a)
1402 tobackup.update(m + a)
1403 elif keepchanges:
1403 elif keepchanges:
1404 tobackup.update(m + a + r + d)
1404 tobackup.update(m + a + r + d)
1405
1405
1406 self.applieddirty = True
1406 self.applieddirty = True
1407 end = len(self.applied)
1407 end = len(self.applied)
1408 rev = self.applied[start].node
1408 rev = self.applied[start].node
1409
1409
1410 try:
1410 try:
1411 heads = repo.changelog.heads(rev)
1411 heads = repo.changelog.heads(rev)
1412 except error.LookupError:
1412 except error.LookupError:
1413 node = short(rev)
1413 node = short(rev)
1414 raise util.Abort(_('trying to pop unknown node %s') % node)
1414 raise util.Abort(_('trying to pop unknown node %s') % node)
1415
1415
1416 if heads != [self.applied[-1].node]:
1416 if heads != [self.applied[-1].node]:
1417 raise util.Abort(_("popping would remove a revision not "
1417 raise util.Abort(_("popping would remove a revision not "
1418 "managed by this patch queue"))
1418 "managed by this patch queue"))
1419 if not repo[self.applied[-1].node].mutable():
1419 if not repo[self.applied[-1].node].mutable():
1420 raise util.Abort(
1420 raise util.Abort(
1421 _("popping would remove an immutable revision"),
1421 _("popping would remove an immutable revision"),
1422 hint=_('see "hg help phases" for details'))
1422 hint=_('see "hg help phases" for details'))
1423
1423
1424 # we know there are no local changes, so we can make a simplified
1424 # we know there are no local changes, so we can make a simplified
1425 # form of hg.update.
1425 # form of hg.update.
1426 if update:
1426 if update:
1427 qp = self.qparents(repo, rev)
1427 qp = self.qparents(repo, rev)
1428 ctx = repo[qp]
1428 ctx = repo[qp]
1429 m, a, r, d = repo.status(qp, '.')[:4]
1429 m, a, r, d = repo.status(qp, '.')[:4]
1430 if d:
1430 if d:
1431 raise util.Abort(_("deletions found between repo revs"))
1431 raise util.Abort(_("deletions found between repo revs"))
1432
1432
1433 tobackup = set(a + m + r) & tobackup
1433 tobackup = set(a + m + r) & tobackup
1434 if keepchanges and tobackup:
1434 if keepchanges and tobackup:
1435 self.localchangesfound()
1435 self.localchangesfound()
1436 self.backup(repo, tobackup)
1436 self.backup(repo, tobackup)
1437
1437
1438 for f in a:
1438 for f in a:
1439 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1439 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
1440 repo.dirstate.drop(f)
1440 repo.dirstate.drop(f)
1441 for f in m + r:
1441 for f in m + r:
1442 fctx = ctx[f]
1442 fctx = ctx[f]
1443 repo.wwrite(f, fctx.data(), fctx.flags())
1443 repo.wwrite(f, fctx.data(), fctx.flags())
1444 repo.dirstate.normal(f)
1444 repo.dirstate.normal(f)
1445 repo.setparents(qp, nullid)
1445 repo.setparents(qp, nullid)
1446 for patch in reversed(self.applied[start:end]):
1446 for patch in reversed(self.applied[start:end]):
1447 self.ui.status(_("popping %s\n") % patch.name)
1447 self.ui.status(_("popping %s\n") % patch.name)
1448 del self.applied[start:end]
1448 del self.applied[start:end]
1449 self.strip(repo, [rev], update=False, backup='strip')
1449 self.strip(repo, [rev], update=False, backup='strip')
1450 if self.applied:
1450 if self.applied:
1451 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1451 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1452 else:
1452 else:
1453 self.ui.write(_("patch queue now empty\n"))
1453 self.ui.write(_("patch queue now empty\n"))
1454 finally:
1454 finally:
1455 wlock.release()
1455 wlock.release()
1456
1456
1457 def diff(self, repo, pats, opts):
1457 def diff(self, repo, pats, opts):
1458 top, patch = self.checktoppatch(repo)
1458 top, patch = self.checktoppatch(repo)
1459 if not top:
1459 if not top:
1460 self.ui.write(_("no patches applied\n"))
1460 self.ui.write(_("no patches applied\n"))
1461 return
1461 return
1462 qp = self.qparents(repo, top)
1462 qp = self.qparents(repo, top)
1463 if opts.get('reverse'):
1463 if opts.get('reverse'):
1464 node1, node2 = None, qp
1464 node1, node2 = None, qp
1465 else:
1465 else:
1466 node1, node2 = qp, None
1466 node1, node2 = qp, None
1467 diffopts = self.diffopts(opts, patch)
1467 diffopts = self.diffopts(opts, patch)
1468 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1468 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1469
1469
1470 def refresh(self, repo, pats=None, **opts):
1470 def refresh(self, repo, pats=None, **opts):
1471 if not self.applied:
1471 if not self.applied:
1472 self.ui.write(_("no patches applied\n"))
1472 self.ui.write(_("no patches applied\n"))
1473 return 1
1473 return 1
1474 msg = opts.get('msg', '').rstrip()
1474 msg = opts.get('msg', '').rstrip()
1475 newuser = opts.get('user')
1475 newuser = opts.get('user')
1476 newdate = opts.get('date')
1476 newdate = opts.get('date')
1477 if newdate:
1477 if newdate:
1478 newdate = '%d %d' % util.parsedate(newdate)
1478 newdate = '%d %d' % util.parsedate(newdate)
1479 wlock = repo.wlock()
1479 wlock = repo.wlock()
1480
1480
1481 try:
1481 try:
1482 self.checktoppatch(repo)
1482 self.checktoppatch(repo)
1483 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1483 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1484 if repo.changelog.heads(top) != [top]:
1484 if repo.changelog.heads(top) != [top]:
1485 raise util.Abort(_("cannot refresh a revision with children"))
1485 raise util.Abort(_("cannot refresh a revision with children"))
1486 if not repo[top].mutable():
1486 if not repo[top].mutable():
1487 raise util.Abort(_("cannot refresh immutable revision"),
1487 raise util.Abort(_("cannot refresh immutable revision"),
1488 hint=_('see "hg help phases" for details'))
1488 hint=_('see "hg help phases" for details'))
1489
1489
1490 cparents = repo.changelog.parents(top)
1490 cparents = repo.changelog.parents(top)
1491 patchparent = self.qparents(repo, top)
1491 patchparent = self.qparents(repo, top)
1492
1492
1493 inclsubs = self.checksubstate(repo, hex(patchparent))
1493 inclsubs = self.checksubstate(repo, hex(patchparent))
1494 if inclsubs:
1494 if inclsubs:
1495 inclsubs.append('.hgsubstate')
1495 inclsubs.append('.hgsubstate')
1496 substatestate = repo.dirstate['.hgsubstate']
1496 substatestate = repo.dirstate['.hgsubstate']
1497
1497
1498 ph = patchheader(self.join(patchfn), self.plainmode)
1498 ph = patchheader(self.join(patchfn), self.plainmode)
1499 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1499 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1500 if msg:
1500 if msg:
1501 ph.setmessage(msg)
1501 ph.setmessage(msg)
1502 if newuser:
1502 if newuser:
1503 ph.setuser(newuser)
1503 ph.setuser(newuser)
1504 if newdate:
1504 if newdate:
1505 ph.setdate(newdate)
1505 ph.setdate(newdate)
1506 ph.setparent(hex(patchparent))
1506 ph.setparent(hex(patchparent))
1507
1507
1508 # only commit new patch when write is complete
1508 # only commit new patch when write is complete
1509 patchf = self.opener(patchfn, 'w', atomictemp=True)
1509 patchf = self.opener(patchfn, 'w', atomictemp=True)
1510
1510
1511 comments = str(ph)
1511 comments = str(ph)
1512 if comments:
1512 if comments:
1513 patchf.write(comments)
1513 patchf.write(comments)
1514
1514
1515 # update the dirstate in place, strip off the qtip commit
1515 # update the dirstate in place, strip off the qtip commit
1516 # and then commit.
1516 # and then commit.
1517 #
1517 #
1518 # this should really read:
1518 # this should really read:
1519 # mm, dd, aa = repo.status(top, patchparent)[:3]
1519 # mm, dd, aa = repo.status(top, patchparent)[:3]
1520 # but we do it backwards to take advantage of manifest/changelog
1520 # but we do it backwards to take advantage of manifest/changelog
1521 # caching against the next repo.status call
1521 # caching against the next repo.status call
1522 mm, aa, dd = repo.status(patchparent, top)[:3]
1522 mm, aa, dd = repo.status(patchparent, top)[:3]
1523 changes = repo.changelog.read(top)
1523 changes = repo.changelog.read(top)
1524 man = repo.manifest.read(changes[0])
1524 man = repo.manifest.read(changes[0])
1525 aaa = aa[:]
1525 aaa = aa[:]
1526 matchfn = scmutil.match(repo[None], pats, opts)
1526 matchfn = scmutil.match(repo[None], pats, opts)
1527 # in short mode, we only diff the files included in the
1527 # in short mode, we only diff the files included in the
1528 # patch already plus specified files
1528 # patch already plus specified files
1529 if opts.get('short'):
1529 if opts.get('short'):
1530 # if amending a patch, we start with existing
1530 # if amending a patch, we start with existing
1531 # files plus specified files - unfiltered
1531 # files plus specified files - unfiltered
1532 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1532 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1533 # filter with include/exclude options
1533 # filter with include/exclude options
1534 matchfn = scmutil.match(repo[None], opts=opts)
1534 matchfn = scmutil.match(repo[None], opts=opts)
1535 else:
1535 else:
1536 match = scmutil.matchall(repo)
1536 match = scmutil.matchall(repo)
1537 m, a, r, d = repo.status(match=match)[:4]
1537 m, a, r, d = repo.status(match=match)[:4]
1538 mm = set(mm)
1538 mm = set(mm)
1539 aa = set(aa)
1539 aa = set(aa)
1540 dd = set(dd)
1540 dd = set(dd)
1541
1541
1542 # we might end up with files that were added between
1542 # we might end up with files that were added between
1543 # qtip and the dirstate parent, but then changed in the
1543 # qtip and the dirstate parent, but then changed in the
1544 # local dirstate. in this case, we want them to only
1544 # local dirstate. in this case, we want them to only
1545 # show up in the added section
1545 # show up in the added section
1546 for x in m:
1546 for x in m:
1547 if x not in aa:
1547 if x not in aa:
1548 mm.add(x)
1548 mm.add(x)
1549 # we might end up with files added by the local dirstate that
1549 # we might end up with files added by the local dirstate that
1550 # were deleted by the patch. In this case, they should only
1550 # were deleted by the patch. In this case, they should only
1551 # show up in the changed section.
1551 # show up in the changed section.
1552 for x in a:
1552 for x in a:
1553 if x in dd:
1553 if x in dd:
1554 dd.remove(x)
1554 dd.remove(x)
1555 mm.add(x)
1555 mm.add(x)
1556 else:
1556 else:
1557 aa.add(x)
1557 aa.add(x)
1558 # make sure any files deleted in the local dirstate
1558 # make sure any files deleted in the local dirstate
1559 # are not in the add or change column of the patch
1559 # are not in the add or change column of the patch
1560 forget = []
1560 forget = []
1561 for x in d + r:
1561 for x in d + r:
1562 if x in aa:
1562 if x in aa:
1563 aa.remove(x)
1563 aa.remove(x)
1564 forget.append(x)
1564 forget.append(x)
1565 continue
1565 continue
1566 else:
1566 else:
1567 mm.discard(x)
1567 mm.discard(x)
1568 dd.add(x)
1568 dd.add(x)
1569
1569
1570 m = list(mm)
1570 m = list(mm)
1571 r = list(dd)
1571 r = list(dd)
1572 a = list(aa)
1572 a = list(aa)
1573
1573
1574 # create 'match' that includes the files to be recommitted.
1574 # create 'match' that includes the files to be recommitted.
1575 # apply matchfn via repo.status to ensure correct case handling.
1575 # apply matchfn via repo.status to ensure correct case handling.
1576 cm, ca, cr, cd = repo.status(patchparent, match=matchfn)[:4]
1576 cm, ca, cr, cd = repo.status(patchparent, match=matchfn)[:4]
1577 allmatches = set(cm + ca + cr + cd)
1577 allmatches = set(cm + ca + cr + cd)
1578 refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
1578 refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
1579
1579
1580 files = set(inclsubs)
1580 files = set(inclsubs)
1581 for x in refreshchanges:
1581 for x in refreshchanges:
1582 files.update(x)
1582 files.update(x)
1583 match = scmutil.matchfiles(repo, files)
1583 match = scmutil.matchfiles(repo, files)
1584
1584
1585 bmlist = repo[top].bookmarks()
1585 bmlist = repo[top].bookmarks()
1586
1586
1587 try:
1587 try:
1588 if diffopts.git or diffopts.upgrade:
1588 if diffopts.git or diffopts.upgrade:
1589 copies = {}
1589 copies = {}
1590 for dst in a:
1590 for dst in a:
1591 src = repo.dirstate.copied(dst)
1591 src = repo.dirstate.copied(dst)
1592 # during qfold, the source file for copies may
1592 # during qfold, the source file for copies may
1593 # be removed. Treat this as a simple add.
1593 # be removed. Treat this as a simple add.
1594 if src is not None and src in repo.dirstate:
1594 if src is not None and src in repo.dirstate:
1595 copies.setdefault(src, []).append(dst)
1595 copies.setdefault(src, []).append(dst)
1596 repo.dirstate.add(dst)
1596 repo.dirstate.add(dst)
1597 # remember the copies between patchparent and qtip
1597 # remember the copies between patchparent and qtip
1598 for dst in aaa:
1598 for dst in aaa:
1599 f = repo.file(dst)
1599 f = repo.file(dst)
1600 src = f.renamed(man[dst])
1600 src = f.renamed(man[dst])
1601 if src:
1601 if src:
1602 copies.setdefault(src[0], []).extend(
1602 copies.setdefault(src[0], []).extend(
1603 copies.get(dst, []))
1603 copies.get(dst, []))
1604 if dst in a:
1604 if dst in a:
1605 copies[src[0]].append(dst)
1605 copies[src[0]].append(dst)
1606 # we can't copy a file created by the patch itself
1606 # we can't copy a file created by the patch itself
1607 if dst in copies:
1607 if dst in copies:
1608 del copies[dst]
1608 del copies[dst]
1609 for src, dsts in copies.iteritems():
1609 for src, dsts in copies.iteritems():
1610 for dst in dsts:
1610 for dst in dsts:
1611 repo.dirstate.copy(src, dst)
1611 repo.dirstate.copy(src, dst)
1612 else:
1612 else:
1613 for dst in a:
1613 for dst in a:
1614 repo.dirstate.add(dst)
1614 repo.dirstate.add(dst)
1615 # Drop useless copy information
1615 # Drop useless copy information
1616 for f in list(repo.dirstate.copies()):
1616 for f in list(repo.dirstate.copies()):
1617 repo.dirstate.copy(None, f)
1617 repo.dirstate.copy(None, f)
1618 for f in r:
1618 for f in r:
1619 repo.dirstate.remove(f)
1619 repo.dirstate.remove(f)
1620 # if the patch excludes a modified file, mark that
1620 # if the patch excludes a modified file, mark that
1621 # file with mtime=0 so status can see it.
1621 # file with mtime=0 so status can see it.
1622 mm = []
1622 mm = []
1623 for i in xrange(len(m) - 1, -1, -1):
1623 for i in xrange(len(m) - 1, -1, -1):
1624 if not matchfn(m[i]):
1624 if not matchfn(m[i]):
1625 mm.append(m[i])
1625 mm.append(m[i])
1626 del m[i]
1626 del m[i]
1627 for f in m:
1627 for f in m:
1628 repo.dirstate.normal(f)
1628 repo.dirstate.normal(f)
1629 for f in mm:
1629 for f in mm:
1630 repo.dirstate.normallookup(f)
1630 repo.dirstate.normallookup(f)
1631 for f in forget:
1631 for f in forget:
1632 repo.dirstate.drop(f)
1632 repo.dirstate.drop(f)
1633
1633
1634 if not msg:
1634 if not msg:
1635 if not ph.message:
1635 if not ph.message:
1636 message = "[mq]: %s\n" % patchfn
1636 message = "[mq]: %s\n" % patchfn
1637 else:
1637 else:
1638 message = "\n".join(ph.message)
1638 message = "\n".join(ph.message)
1639 else:
1639 else:
1640 message = msg
1640 message = msg
1641
1641
1642 user = ph.user or changes[1]
1642 user = ph.user or changes[1]
1643
1643
1644 oldphase = repo[top].phase()
1644 oldphase = repo[top].phase()
1645
1645
1646 # assumes strip can roll itself back if interrupted
1646 # assumes strip can roll itself back if interrupted
1647 repo.setparents(*cparents)
1647 repo.setparents(*cparents)
1648 self.applied.pop()
1648 self.applied.pop()
1649 self.applieddirty = True
1649 self.applieddirty = True
1650 self.strip(repo, [top], update=False,
1650 self.strip(repo, [top], update=False,
1651 backup='strip')
1651 backup='strip')
1652 except: # re-raises
1652 except: # re-raises
1653 repo.dirstate.invalidate()
1653 repo.dirstate.invalidate()
1654 raise
1654 raise
1655
1655
1656 try:
1656 try:
1657 # might be nice to attempt to roll back strip after this
1657 # might be nice to attempt to roll back strip after this
1658
1658
1659 # Ensure we create a new changeset in the same phase than
1659 # Ensure we create a new changeset in the same phase than
1660 # the old one.
1660 # the old one.
1661 n = newcommit(repo, oldphase, message, user, ph.date,
1661 n = newcommit(repo, oldphase, message, user, ph.date,
1662 match=match, force=True)
1662 match=match, force=True)
1663 # only write patch after a successful commit
1663 # only write patch after a successful commit
1664 c = [list(x) for x in refreshchanges]
1664 c = [list(x) for x in refreshchanges]
1665 if inclsubs:
1665 if inclsubs:
1666 self.putsubstate2changes(substatestate, c)
1666 self.putsubstate2changes(substatestate, c)
1667 chunks = patchmod.diff(repo, patchparent,
1667 chunks = patchmod.diff(repo, patchparent,
1668 changes=c, opts=diffopts)
1668 changes=c, opts=diffopts)
1669 for chunk in chunks:
1669 for chunk in chunks:
1670 patchf.write(chunk)
1670 patchf.write(chunk)
1671 patchf.close()
1671 patchf.close()
1672
1672
1673 marks = repo._bookmarks
1673 marks = repo._bookmarks
1674 for bm in bmlist:
1674 for bm in bmlist:
1675 marks[bm] = n
1675 marks[bm] = n
1676 marks.write()
1676 marks.write()
1677
1677
1678 self.applied.append(statusentry(n, patchfn))
1678 self.applied.append(statusentry(n, patchfn))
1679 except: # re-raises
1679 except: # re-raises
1680 ctx = repo[cparents[0]]
1680 ctx = repo[cparents[0]]
1681 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1681 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1682 self.savedirty()
1682 self.savedirty()
1683 self.ui.warn(_('refresh interrupted while patch was popped! '
1683 self.ui.warn(_('refresh interrupted while patch was popped! '
1684 '(revert --all, qpush to recover)\n'))
1684 '(revert --all, qpush to recover)\n'))
1685 raise
1685 raise
1686 finally:
1686 finally:
1687 wlock.release()
1687 wlock.release()
1688 self.removeundo(repo)
1688 self.removeundo(repo)
1689
1689
1690 def init(self, repo, create=False):
1690 def init(self, repo, create=False):
1691 if not create and os.path.isdir(self.path):
1691 if not create and os.path.isdir(self.path):
1692 raise util.Abort(_("patch queue directory already exists"))
1692 raise util.Abort(_("patch queue directory already exists"))
1693 try:
1693 try:
1694 os.mkdir(self.path)
1694 os.mkdir(self.path)
1695 except OSError, inst:
1695 except OSError, inst:
1696 if inst.errno != errno.EEXIST or not create:
1696 if inst.errno != errno.EEXIST or not create:
1697 raise
1697 raise
1698 if create:
1698 if create:
1699 return self.qrepo(create=True)
1699 return self.qrepo(create=True)
1700
1700
1701 def unapplied(self, repo, patch=None):
1701 def unapplied(self, repo, patch=None):
1702 if patch and patch not in self.series:
1702 if patch and patch not in self.series:
1703 raise util.Abort(_("patch %s is not in series file") % patch)
1703 raise util.Abort(_("patch %s is not in series file") % patch)
1704 if not patch:
1704 if not patch:
1705 start = self.seriesend()
1705 start = self.seriesend()
1706 else:
1706 else:
1707 start = self.series.index(patch) + 1
1707 start = self.series.index(patch) + 1
1708 unapplied = []
1708 unapplied = []
1709 for i in xrange(start, len(self.series)):
1709 for i in xrange(start, len(self.series)):
1710 pushable, reason = self.pushable(i)
1710 pushable, reason = self.pushable(i)
1711 if pushable:
1711 if pushable:
1712 unapplied.append((i, self.series[i]))
1712 unapplied.append((i, self.series[i]))
1713 self.explainpushable(i)
1713 self.explainpushable(i)
1714 return unapplied
1714 return unapplied
1715
1715
1716 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1716 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1717 summary=False):
1717 summary=False):
1718 def displayname(pfx, patchname, state):
1718 def displayname(pfx, patchname, state):
1719 if pfx:
1719 if pfx:
1720 self.ui.write(pfx)
1720 self.ui.write(pfx)
1721 if summary:
1721 if summary:
1722 ph = patchheader(self.join(patchname), self.plainmode)
1722 ph = patchheader(self.join(patchname), self.plainmode)
1723 msg = ph.message and ph.message[0] or ''
1723 msg = ph.message and ph.message[0] or ''
1724 if self.ui.formatted():
1724 if self.ui.formatted():
1725 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1725 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1726 if width > 0:
1726 if width > 0:
1727 msg = util.ellipsis(msg, width)
1727 msg = util.ellipsis(msg, width)
1728 else:
1728 else:
1729 msg = ''
1729 msg = ''
1730 self.ui.write(patchname, label='qseries.' + state)
1730 self.ui.write(patchname, label='qseries.' + state)
1731 self.ui.write(': ')
1731 self.ui.write(': ')
1732 self.ui.write(msg, label='qseries.message.' + state)
1732 self.ui.write(msg, label='qseries.message.' + state)
1733 else:
1733 else:
1734 self.ui.write(patchname, label='qseries.' + state)
1734 self.ui.write(patchname, label='qseries.' + state)
1735 self.ui.write('\n')
1735 self.ui.write('\n')
1736
1736
1737 applied = set([p.name for p in self.applied])
1737 applied = set([p.name for p in self.applied])
1738 if length is None:
1738 if length is None:
1739 length = len(self.series) - start
1739 length = len(self.series) - start
1740 if not missing:
1740 if not missing:
1741 if self.ui.verbose:
1741 if self.ui.verbose:
1742 idxwidth = len(str(start + length - 1))
1742 idxwidth = len(str(start + length - 1))
1743 for i in xrange(start, start + length):
1743 for i in xrange(start, start + length):
1744 patch = self.series[i]
1744 patch = self.series[i]
1745 if patch in applied:
1745 if patch in applied:
1746 char, state = 'A', 'applied'
1746 char, state = 'A', 'applied'
1747 elif self.pushable(i)[0]:
1747 elif self.pushable(i)[0]:
1748 char, state = 'U', 'unapplied'
1748 char, state = 'U', 'unapplied'
1749 else:
1749 else:
1750 char, state = 'G', 'guarded'
1750 char, state = 'G', 'guarded'
1751 pfx = ''
1751 pfx = ''
1752 if self.ui.verbose:
1752 if self.ui.verbose:
1753 pfx = '%*d %s ' % (idxwidth, i, char)
1753 pfx = '%*d %s ' % (idxwidth, i, char)
1754 elif status and status != char:
1754 elif status and status != char:
1755 continue
1755 continue
1756 displayname(pfx, patch, state)
1756 displayname(pfx, patch, state)
1757 else:
1757 else:
1758 msng_list = []
1758 msng_list = []
1759 for root, dirs, files in os.walk(self.path):
1759 for root, dirs, files in os.walk(self.path):
1760 d = root[len(self.path) + 1:]
1760 d = root[len(self.path) + 1:]
1761 for f in files:
1761 for f in files:
1762 fl = os.path.join(d, f)
1762 fl = os.path.join(d, f)
1763 if (fl not in self.series and
1763 if (fl not in self.series and
1764 fl not in (self.statuspath, self.seriespath,
1764 fl not in (self.statuspath, self.seriespath,
1765 self.guardspath)
1765 self.guardspath)
1766 and not fl.startswith('.')):
1766 and not fl.startswith('.')):
1767 msng_list.append(fl)
1767 msng_list.append(fl)
1768 for x in sorted(msng_list):
1768 for x in sorted(msng_list):
1769 pfx = self.ui.verbose and ('D ') or ''
1769 pfx = self.ui.verbose and ('D ') or ''
1770 displayname(pfx, x, 'missing')
1770 displayname(pfx, x, 'missing')
1771
1771
1772 def issaveline(self, l):
1772 def issaveline(self, l):
1773 if l.name == '.hg.patches.save.line':
1773 if l.name == '.hg.patches.save.line':
1774 return True
1774 return True
1775
1775
1776 def qrepo(self, create=False):
1776 def qrepo(self, create=False):
1777 ui = self.ui.copy()
1777 ui = self.ui.copy()
1778 ui.setconfig('paths', 'default', '', overlay=False)
1778 ui.setconfig('paths', 'default', '', overlay=False)
1779 ui.setconfig('paths', 'default-push', '', overlay=False)
1779 ui.setconfig('paths', 'default-push', '', overlay=False)
1780 if create or os.path.isdir(self.join(".hg")):
1780 if create or os.path.isdir(self.join(".hg")):
1781 return hg.repository(ui, path=self.path, create=create)
1781 return hg.repository(ui, path=self.path, create=create)
1782
1782
1783 def restore(self, repo, rev, delete=None, qupdate=None):
1783 def restore(self, repo, rev, delete=None, qupdate=None):
1784 desc = repo[rev].description().strip()
1784 desc = repo[rev].description().strip()
1785 lines = desc.splitlines()
1785 lines = desc.splitlines()
1786 i = 0
1786 i = 0
1787 datastart = None
1787 datastart = None
1788 series = []
1788 series = []
1789 applied = []
1789 applied = []
1790 qpp = None
1790 qpp = None
1791 for i, line in enumerate(lines):
1791 for i, line in enumerate(lines):
1792 if line == 'Patch Data:':
1792 if line == 'Patch Data:':
1793 datastart = i + 1
1793 datastart = i + 1
1794 elif line.startswith('Dirstate:'):
1794 elif line.startswith('Dirstate:'):
1795 l = line.rstrip()
1795 l = line.rstrip()
1796 l = l[10:].split(' ')
1796 l = l[10:].split(' ')
1797 qpp = [bin(x) for x in l]
1797 qpp = [bin(x) for x in l]
1798 elif datastart is not None:
1798 elif datastart is not None:
1799 l = line.rstrip()
1799 l = line.rstrip()
1800 n, name = l.split(':', 1)
1800 n, name = l.split(':', 1)
1801 if n:
1801 if n:
1802 applied.append(statusentry(bin(n), name))
1802 applied.append(statusentry(bin(n), name))
1803 else:
1803 else:
1804 series.append(l)
1804 series.append(l)
1805 if datastart is None:
1805 if datastart is None:
1806 self.ui.warn(_("no saved patch data found\n"))
1806 self.ui.warn(_("no saved patch data found\n"))
1807 return 1
1807 return 1
1808 self.ui.warn(_("restoring status: %s\n") % lines[0])
1808 self.ui.warn(_("restoring status: %s\n") % lines[0])
1809 self.fullseries = series
1809 self.fullseries = series
1810 self.applied = applied
1810 self.applied = applied
1811 self.parseseries()
1811 self.parseseries()
1812 self.seriesdirty = True
1812 self.seriesdirty = True
1813 self.applieddirty = True
1813 self.applieddirty = True
1814 heads = repo.changelog.heads()
1814 heads = repo.changelog.heads()
1815 if delete:
1815 if delete:
1816 if rev not in heads:
1816 if rev not in heads:
1817 self.ui.warn(_("save entry has children, leaving it alone\n"))
1817 self.ui.warn(_("save entry has children, leaving it alone\n"))
1818 else:
1818 else:
1819 self.ui.warn(_("removing save entry %s\n") % short(rev))
1819 self.ui.warn(_("removing save entry %s\n") % short(rev))
1820 pp = repo.dirstate.parents()
1820 pp = repo.dirstate.parents()
1821 if rev in pp:
1821 if rev in pp:
1822 update = True
1822 update = True
1823 else:
1823 else:
1824 update = False
1824 update = False
1825 self.strip(repo, [rev], update=update, backup='strip')
1825 self.strip(repo, [rev], update=update, backup='strip')
1826 if qpp:
1826 if qpp:
1827 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1827 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1828 (short(qpp[0]), short(qpp[1])))
1828 (short(qpp[0]), short(qpp[1])))
1829 if qupdate:
1829 if qupdate:
1830 self.ui.status(_("updating queue directory\n"))
1830 self.ui.status(_("updating queue directory\n"))
1831 r = self.qrepo()
1831 r = self.qrepo()
1832 if not r:
1832 if not r:
1833 self.ui.warn(_("unable to load queue repository\n"))
1833 self.ui.warn(_("unable to load queue repository\n"))
1834 return 1
1834 return 1
1835 hg.clean(r, qpp[0])
1835 hg.clean(r, qpp[0])
1836
1836
1837 def save(self, repo, msg=None):
1837 def save(self, repo, msg=None):
1838 if not self.applied:
1838 if not self.applied:
1839 self.ui.warn(_("save: no patches applied, exiting\n"))
1839 self.ui.warn(_("save: no patches applied, exiting\n"))
1840 return 1
1840 return 1
1841 if self.issaveline(self.applied[-1]):
1841 if self.issaveline(self.applied[-1]):
1842 self.ui.warn(_("status is already saved\n"))
1842 self.ui.warn(_("status is already saved\n"))
1843 return 1
1843 return 1
1844
1844
1845 if not msg:
1845 if not msg:
1846 msg = _("hg patches saved state")
1846 msg = _("hg patches saved state")
1847 else:
1847 else:
1848 msg = "hg patches: " + msg.rstrip('\r\n')
1848 msg = "hg patches: " + msg.rstrip('\r\n')
1849 r = self.qrepo()
1849 r = self.qrepo()
1850 if r:
1850 if r:
1851 pp = r.dirstate.parents()
1851 pp = r.dirstate.parents()
1852 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1852 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1853 msg += "\n\nPatch Data:\n"
1853 msg += "\n\nPatch Data:\n"
1854 msg += ''.join('%s\n' % x for x in self.applied)
1854 msg += ''.join('%s\n' % x for x in self.applied)
1855 msg += ''.join(':%s\n' % x for x in self.fullseries)
1855 msg += ''.join(':%s\n' % x for x in self.fullseries)
1856 n = repo.commit(msg, force=True)
1856 n = repo.commit(msg, force=True)
1857 if not n:
1857 if not n:
1858 self.ui.warn(_("repo commit failed\n"))
1858 self.ui.warn(_("repo commit failed\n"))
1859 return 1
1859 return 1
1860 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1860 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1861 self.applieddirty = True
1861 self.applieddirty = True
1862 self.removeundo(repo)
1862 self.removeundo(repo)
1863
1863
1864 def fullseriesend(self):
1864 def fullseriesend(self):
1865 if self.applied:
1865 if self.applied:
1866 p = self.applied[-1].name
1866 p = self.applied[-1].name
1867 end = self.findseries(p)
1867 end = self.findseries(p)
1868 if end is None:
1868 if end is None:
1869 return len(self.fullseries)
1869 return len(self.fullseries)
1870 return end + 1
1870 return end + 1
1871 return 0
1871 return 0
1872
1872
1873 def seriesend(self, all_patches=False):
1873 def seriesend(self, all_patches=False):
1874 """If all_patches is False, return the index of the next pushable patch
1874 """If all_patches is False, return the index of the next pushable patch
1875 in the series, or the series length. If all_patches is True, return the
1875 in the series, or the series length. If all_patches is True, return the
1876 index of the first patch past the last applied one.
1876 index of the first patch past the last applied one.
1877 """
1877 """
1878 end = 0
1878 end = 0
1879 def next(start):
1879 def next(start):
1880 if all_patches or start >= len(self.series):
1880 if all_patches or start >= len(self.series):
1881 return start
1881 return start
1882 for i in xrange(start, len(self.series)):
1882 for i in xrange(start, len(self.series)):
1883 p, reason = self.pushable(i)
1883 p, reason = self.pushable(i)
1884 if p:
1884 if p:
1885 return i
1885 return i
1886 self.explainpushable(i)
1886 self.explainpushable(i)
1887 return len(self.series)
1887 return len(self.series)
1888 if self.applied:
1888 if self.applied:
1889 p = self.applied[-1].name
1889 p = self.applied[-1].name
1890 try:
1890 try:
1891 end = self.series.index(p)
1891 end = self.series.index(p)
1892 except ValueError:
1892 except ValueError:
1893 return 0
1893 return 0
1894 return next(end + 1)
1894 return next(end + 1)
1895 return next(end)
1895 return next(end)
1896
1896
1897 def appliedname(self, index):
1897 def appliedname(self, index):
1898 pname = self.applied[index].name
1898 pname = self.applied[index].name
1899 if not self.ui.verbose:
1899 if not self.ui.verbose:
1900 p = pname
1900 p = pname
1901 else:
1901 else:
1902 p = str(self.series.index(pname)) + " " + pname
1902 p = str(self.series.index(pname)) + " " + pname
1903 return p
1903 return p
1904
1904
1905 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1905 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1906 force=None, git=False):
1906 force=None, git=False):
1907 def checkseries(patchname):
1907 def checkseries(patchname):
1908 if patchname in self.series:
1908 if patchname in self.series:
1909 raise util.Abort(_('patch %s is already in the series file')
1909 raise util.Abort(_('patch %s is already in the series file')
1910 % patchname)
1910 % patchname)
1911
1911
1912 if rev:
1912 if rev:
1913 if files:
1913 if files:
1914 raise util.Abort(_('option "-r" not valid when importing '
1914 raise util.Abort(_('option "-r" not valid when importing '
1915 'files'))
1915 'files'))
1916 rev = scmutil.revrange(repo, rev)
1916 rev = scmutil.revrange(repo, rev)
1917 rev.sort(reverse=True)
1917 rev.sort(reverse=True)
1918 elif not files:
1918 elif not files:
1919 raise util.Abort(_('no files or revisions specified'))
1919 raise util.Abort(_('no files or revisions specified'))
1920 if (len(files) > 1 or len(rev) > 1) and patchname:
1920 if (len(files) > 1 or len(rev) > 1) and patchname:
1921 raise util.Abort(_('option "-n" not valid when importing multiple '
1921 raise util.Abort(_('option "-n" not valid when importing multiple '
1922 'patches'))
1922 'patches'))
1923 imported = []
1923 imported = []
1924 if rev:
1924 if rev:
1925 # If mq patches are applied, we can only import revisions
1925 # If mq patches are applied, we can only import revisions
1926 # that form a linear path to qbase.
1926 # that form a linear path to qbase.
1927 # Otherwise, they should form a linear path to a head.
1927 # Otherwise, they should form a linear path to a head.
1928 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1928 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1929 if len(heads) > 1:
1929 if len(heads) > 1:
1930 raise util.Abort(_('revision %d is the root of more than one '
1930 raise util.Abort(_('revision %d is the root of more than one '
1931 'branch') % rev[-1])
1931 'branch') % rev[-1])
1932 if self.applied:
1932 if self.applied:
1933 base = repo.changelog.node(rev[0])
1933 base = repo.changelog.node(rev[0])
1934 if base in [n.node for n in self.applied]:
1934 if base in [n.node for n in self.applied]:
1935 raise util.Abort(_('revision %d is already managed')
1935 raise util.Abort(_('revision %d is already managed')
1936 % rev[0])
1936 % rev[0])
1937 if heads != [self.applied[-1].node]:
1937 if heads != [self.applied[-1].node]:
1938 raise util.Abort(_('revision %d is not the parent of '
1938 raise util.Abort(_('revision %d is not the parent of '
1939 'the queue') % rev[0])
1939 'the queue') % rev[0])
1940 base = repo.changelog.rev(self.applied[0].node)
1940 base = repo.changelog.rev(self.applied[0].node)
1941 lastparent = repo.changelog.parentrevs(base)[0]
1941 lastparent = repo.changelog.parentrevs(base)[0]
1942 else:
1942 else:
1943 if heads != [repo.changelog.node(rev[0])]:
1943 if heads != [repo.changelog.node(rev[0])]:
1944 raise util.Abort(_('revision %d has unmanaged children')
1944 raise util.Abort(_('revision %d has unmanaged children')
1945 % rev[0])
1945 % rev[0])
1946 lastparent = None
1946 lastparent = None
1947
1947
1948 diffopts = self.diffopts({'git': git})
1948 diffopts = self.diffopts({'git': git})
1949 for r in rev:
1949 for r in rev:
1950 if not repo[r].mutable():
1950 if not repo[r].mutable():
1951 raise util.Abort(_('revision %d is not mutable') % r,
1951 raise util.Abort(_('revision %d is not mutable') % r,
1952 hint=_('see "hg help phases" for details'))
1952 hint=_('see "hg help phases" for details'))
1953 p1, p2 = repo.changelog.parentrevs(r)
1953 p1, p2 = repo.changelog.parentrevs(r)
1954 n = repo.changelog.node(r)
1954 n = repo.changelog.node(r)
1955 if p2 != nullrev:
1955 if p2 != nullrev:
1956 raise util.Abort(_('cannot import merge revision %d') % r)
1956 raise util.Abort(_('cannot import merge revision %d') % r)
1957 if lastparent and lastparent != r:
1957 if lastparent and lastparent != r:
1958 raise util.Abort(_('revision %d is not the parent of %d')
1958 raise util.Abort(_('revision %d is not the parent of %d')
1959 % (r, lastparent))
1959 % (r, lastparent))
1960 lastparent = p1
1960 lastparent = p1
1961
1961
1962 if not patchname:
1962 if not patchname:
1963 patchname = normname('%d.diff' % r)
1963 patchname = normname('%d.diff' % r)
1964 checkseries(patchname)
1964 checkseries(patchname)
1965 self.checkpatchname(patchname, force)
1965 self.checkpatchname(patchname, force)
1966 self.fullseries.insert(0, patchname)
1966 self.fullseries.insert(0, patchname)
1967
1967
1968 patchf = self.opener(patchname, "w")
1968 patchf = self.opener(patchname, "w")
1969 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1969 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1970 patchf.close()
1970 patchf.close()
1971
1971
1972 se = statusentry(n, patchname)
1972 se = statusentry(n, patchname)
1973 self.applied.insert(0, se)
1973 self.applied.insert(0, se)
1974
1974
1975 self.added.append(patchname)
1975 self.added.append(patchname)
1976 imported.append(patchname)
1976 imported.append(patchname)
1977 patchname = None
1977 patchname = None
1978 if rev and repo.ui.configbool('mq', 'secret', False):
1978 if rev and repo.ui.configbool('mq', 'secret', False):
1979 # if we added anything with --rev, we must move the secret root
1979 # if we added anything with --rev, we must move the secret root
1980 phases.retractboundary(repo, phases.secret, [n])
1980 phases.retractboundary(repo, phases.secret, [n])
1981 self.parseseries()
1981 self.parseseries()
1982 self.applieddirty = True
1982 self.applieddirty = True
1983 self.seriesdirty = True
1983 self.seriesdirty = True
1984
1984
1985 for i, filename in enumerate(files):
1985 for i, filename in enumerate(files):
1986 if existing:
1986 if existing:
1987 if filename == '-':
1987 if filename == '-':
1988 raise util.Abort(_('-e is incompatible with import from -'))
1988 raise util.Abort(_('-e is incompatible with import from -'))
1989 filename = normname(filename)
1989 filename = normname(filename)
1990 self.checkreservedname(filename)
1990 self.checkreservedname(filename)
1991 originpath = self.join(filename)
1991 originpath = self.join(filename)
1992 if not os.path.isfile(originpath):
1992 if not os.path.isfile(originpath):
1993 raise util.Abort(_("patch %s does not exist") % filename)
1993 raise util.Abort(_("patch %s does not exist") % filename)
1994
1994
1995 if patchname:
1995 if patchname:
1996 self.checkpatchname(patchname, force)
1996 self.checkpatchname(patchname, force)
1997
1997
1998 self.ui.write(_('renaming %s to %s\n')
1998 self.ui.write(_('renaming %s to %s\n')
1999 % (filename, patchname))
1999 % (filename, patchname))
2000 util.rename(originpath, self.join(patchname))
2000 util.rename(originpath, self.join(patchname))
2001 else:
2001 else:
2002 patchname = filename
2002 patchname = filename
2003
2003
2004 else:
2004 else:
2005 if filename == '-' and not patchname:
2005 if filename == '-' and not patchname:
2006 raise util.Abort(_('need --name to import a patch from -'))
2006 raise util.Abort(_('need --name to import a patch from -'))
2007 elif not patchname:
2007 elif not patchname:
2008 patchname = normname(os.path.basename(filename.rstrip('/')))
2008 patchname = normname(os.path.basename(filename.rstrip('/')))
2009 self.checkpatchname(patchname, force)
2009 self.checkpatchname(patchname, force)
2010 try:
2010 try:
2011 if filename == '-':
2011 if filename == '-':
2012 text = self.ui.fin.read()
2012 text = self.ui.fin.read()
2013 else:
2013 else:
2014 fp = hg.openpath(self.ui, filename)
2014 fp = hg.openpath(self.ui, filename)
2015 text = fp.read()
2015 text = fp.read()
2016 fp.close()
2016 fp.close()
2017 except (OSError, IOError):
2017 except (OSError, IOError):
2018 raise util.Abort(_("unable to read file %s") % filename)
2018 raise util.Abort(_("unable to read file %s") % filename)
2019 patchf = self.opener(patchname, "w")
2019 patchf = self.opener(patchname, "w")
2020 patchf.write(text)
2020 patchf.write(text)
2021 patchf.close()
2021 patchf.close()
2022 if not force:
2022 if not force:
2023 checkseries(patchname)
2023 checkseries(patchname)
2024 if patchname not in self.series:
2024 if patchname not in self.series:
2025 index = self.fullseriesend() + i
2025 index = self.fullseriesend() + i
2026 self.fullseries[index:index] = [patchname]
2026 self.fullseries[index:index] = [patchname]
2027 self.parseseries()
2027 self.parseseries()
2028 self.seriesdirty = True
2028 self.seriesdirty = True
2029 self.ui.warn(_("adding %s to series file\n") % patchname)
2029 self.ui.warn(_("adding %s to series file\n") % patchname)
2030 self.added.append(patchname)
2030 self.added.append(patchname)
2031 imported.append(patchname)
2031 imported.append(patchname)
2032 patchname = None
2032 patchname = None
2033
2033
2034 self.removeundo(repo)
2034 self.removeundo(repo)
2035 return imported
2035 return imported
2036
2036
2037 def fixkeepchangesopts(ui, opts):
2037 def fixkeepchangesopts(ui, opts):
2038 if (not ui.configbool('mq', 'keepchanges') or opts.get('force')
2038 if (not ui.configbool('mq', 'keepchanges') or opts.get('force')
2039 or opts.get('exact')):
2039 or opts.get('exact')):
2040 return opts
2040 return opts
2041 opts = dict(opts)
2041 opts = dict(opts)
2042 opts['keep_changes'] = True
2042 opts['keep_changes'] = True
2043 return opts
2043 return opts
2044
2044
2045 @command("qdelete|qremove|qrm",
2045 @command("qdelete|qremove|qrm",
2046 [('k', 'keep', None, _('keep patch file')),
2046 [('k', 'keep', None, _('keep patch file')),
2047 ('r', 'rev', [],
2047 ('r', 'rev', [],
2048 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2048 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2049 _('hg qdelete [-k] [PATCH]...'))
2049 _('hg qdelete [-k] [PATCH]...'))
2050 def delete(ui, repo, *patches, **opts):
2050 def delete(ui, repo, *patches, **opts):
2051 """remove patches from queue
2051 """remove patches from queue
2052
2052
2053 The patches must not be applied, and at least one patch is required. Exact
2053 The patches must not be applied, and at least one patch is required. Exact
2054 patch identifiers must be given. With -k/--keep, the patch files are
2054 patch identifiers must be given. With -k/--keep, the patch files are
2055 preserved in the patch directory.
2055 preserved in the patch directory.
2056
2056
2057 To stop managing a patch and move it into permanent history,
2057 To stop managing a patch and move it into permanent history,
2058 use the :hg:`qfinish` command."""
2058 use the :hg:`qfinish` command."""
2059 q = repo.mq
2059 q = repo.mq
2060 q.delete(repo, patches, opts)
2060 q.delete(repo, patches, opts)
2061 q.savedirty()
2061 q.savedirty()
2062 return 0
2062 return 0
2063
2063
2064 @command("qapplied",
2064 @command("qapplied",
2065 [('1', 'last', None, _('show only the preceding applied patch'))
2065 [('1', 'last', None, _('show only the preceding applied patch'))
2066 ] + seriesopts,
2066 ] + seriesopts,
2067 _('hg qapplied [-1] [-s] [PATCH]'))
2067 _('hg qapplied [-1] [-s] [PATCH]'))
2068 def applied(ui, repo, patch=None, **opts):
2068 def applied(ui, repo, patch=None, **opts):
2069 """print the patches already applied
2069 """print the patches already applied
2070
2070
2071 Returns 0 on success."""
2071 Returns 0 on success."""
2072
2072
2073 q = repo.mq
2073 q = repo.mq
2074
2074
2075 if patch:
2075 if patch:
2076 if patch not in q.series:
2076 if patch not in q.series:
2077 raise util.Abort(_("patch %s is not in series file") % patch)
2077 raise util.Abort(_("patch %s is not in series file") % patch)
2078 end = q.series.index(patch) + 1
2078 end = q.series.index(patch) + 1
2079 else:
2079 else:
2080 end = q.seriesend(True)
2080 end = q.seriesend(True)
2081
2081
2082 if opts.get('last') and not end:
2082 if opts.get('last') and not end:
2083 ui.write(_("no patches applied\n"))
2083 ui.write(_("no patches applied\n"))
2084 return 1
2084 return 1
2085 elif opts.get('last') and end == 1:
2085 elif opts.get('last') and end == 1:
2086 ui.write(_("only one patch applied\n"))
2086 ui.write(_("only one patch applied\n"))
2087 return 1
2087 return 1
2088 elif opts.get('last'):
2088 elif opts.get('last'):
2089 start = end - 2
2089 start = end - 2
2090 end = 1
2090 end = 1
2091 else:
2091 else:
2092 start = 0
2092 start = 0
2093
2093
2094 q.qseries(repo, length=end, start=start, status='A',
2094 q.qseries(repo, length=end, start=start, status='A',
2095 summary=opts.get('summary'))
2095 summary=opts.get('summary'))
2096
2096
2097
2097
2098 @command("qunapplied",
2098 @command("qunapplied",
2099 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2099 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2100 _('hg qunapplied [-1] [-s] [PATCH]'))
2100 _('hg qunapplied [-1] [-s] [PATCH]'))
2101 def unapplied(ui, repo, patch=None, **opts):
2101 def unapplied(ui, repo, patch=None, **opts):
2102 """print the patches not yet applied
2102 """print the patches not yet applied
2103
2103
2104 Returns 0 on success."""
2104 Returns 0 on success."""
2105
2105
2106 q = repo.mq
2106 q = repo.mq
2107 if patch:
2107 if patch:
2108 if patch not in q.series:
2108 if patch not in q.series:
2109 raise util.Abort(_("patch %s is not in series file") % patch)
2109 raise util.Abort(_("patch %s is not in series file") % patch)
2110 start = q.series.index(patch) + 1
2110 start = q.series.index(patch) + 1
2111 else:
2111 else:
2112 start = q.seriesend(True)
2112 start = q.seriesend(True)
2113
2113
2114 if start == len(q.series) and opts.get('first'):
2114 if start == len(q.series) and opts.get('first'):
2115 ui.write(_("all patches applied\n"))
2115 ui.write(_("all patches applied\n"))
2116 return 1
2116 return 1
2117
2117
2118 length = opts.get('first') and 1 or None
2118 length = opts.get('first') and 1 or None
2119 q.qseries(repo, start=start, length=length, status='U',
2119 q.qseries(repo, start=start, length=length, status='U',
2120 summary=opts.get('summary'))
2120 summary=opts.get('summary'))
2121
2121
2122 @command("qimport",
2122 @command("qimport",
2123 [('e', 'existing', None, _('import file in patch directory')),
2123 [('e', 'existing', None, _('import file in patch directory')),
2124 ('n', 'name', '',
2124 ('n', 'name', '',
2125 _('name of patch file'), _('NAME')),
2125 _('name of patch file'), _('NAME')),
2126 ('f', 'force', None, _('overwrite existing files')),
2126 ('f', 'force', None, _('overwrite existing files')),
2127 ('r', 'rev', [],
2127 ('r', 'rev', [],
2128 _('place existing revisions under mq control'), _('REV')),
2128 _('place existing revisions under mq control'), _('REV')),
2129 ('g', 'git', None, _('use git extended diff format')),
2129 ('g', 'git', None, _('use git extended diff format')),
2130 ('P', 'push', None, _('qpush after importing'))],
2130 ('P', 'push', None, _('qpush after importing'))],
2131 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'))
2131 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'))
2132 def qimport(ui, repo, *filename, **opts):
2132 def qimport(ui, repo, *filename, **opts):
2133 """import a patch or existing changeset
2133 """import a patch or existing changeset
2134
2134
2135 The patch is inserted into the series after the last applied
2135 The patch is inserted into the series after the last applied
2136 patch. If no patches have been applied, qimport prepends the patch
2136 patch. If no patches have been applied, qimport prepends the patch
2137 to the series.
2137 to the series.
2138
2138
2139 The patch will have the same name as its source file unless you
2139 The patch will have the same name as its source file unless you
2140 give it a new one with -n/--name.
2140 give it a new one with -n/--name.
2141
2141
2142 You can register an existing patch inside the patch directory with
2142 You can register an existing patch inside the patch directory with
2143 the -e/--existing flag.
2143 the -e/--existing flag.
2144
2144
2145 With -f/--force, an existing patch of the same name will be
2145 With -f/--force, an existing patch of the same name will be
2146 overwritten.
2146 overwritten.
2147
2147
2148 An existing changeset may be placed under mq control with -r/--rev
2148 An existing changeset may be placed under mq control with -r/--rev
2149 (e.g. qimport --rev tip -n patch will place tip under mq control).
2149 (e.g. qimport --rev tip -n patch will place tip under mq control).
2150 With -g/--git, patches imported with --rev will use the git diff
2150 With -g/--git, patches imported with --rev will use the git diff
2151 format. See the diffs help topic for information on why this is
2151 format. See the diffs help topic for information on why this is
2152 important for preserving rename/copy information and permission
2152 important for preserving rename/copy information and permission
2153 changes. Use :hg:`qfinish` to remove changesets from mq control.
2153 changes. Use :hg:`qfinish` to remove changesets from mq control.
2154
2154
2155 To import a patch from standard input, pass - as the patch file.
2155 To import a patch from standard input, pass - as the patch file.
2156 When importing from standard input, a patch name must be specified
2156 When importing from standard input, a patch name must be specified
2157 using the --name flag.
2157 using the --name flag.
2158
2158
2159 To import an existing patch while renaming it::
2159 To import an existing patch while renaming it::
2160
2160
2161 hg qimport -e existing-patch -n new-name
2161 hg qimport -e existing-patch -n new-name
2162
2162
2163 Returns 0 if import succeeded.
2163 Returns 0 if import succeeded.
2164 """
2164 """
2165 lock = repo.lock() # cause this may move phase
2165 lock = repo.lock() # cause this may move phase
2166 try:
2166 try:
2167 q = repo.mq
2167 q = repo.mq
2168 try:
2168 try:
2169 imported = q.qimport(
2169 imported = q.qimport(
2170 repo, filename, patchname=opts.get('name'),
2170 repo, filename, patchname=opts.get('name'),
2171 existing=opts.get('existing'), force=opts.get('force'),
2171 existing=opts.get('existing'), force=opts.get('force'),
2172 rev=opts.get('rev'), git=opts.get('git'))
2172 rev=opts.get('rev'), git=opts.get('git'))
2173 finally:
2173 finally:
2174 q.savedirty()
2174 q.savedirty()
2175 finally:
2175 finally:
2176 lock.release()
2176 lock.release()
2177
2177
2178 if imported and opts.get('push') and not opts.get('rev'):
2178 if imported and opts.get('push') and not opts.get('rev'):
2179 return q.push(repo, imported[-1])
2179 return q.push(repo, imported[-1])
2180 return 0
2180 return 0
2181
2181
2182 def qinit(ui, repo, create):
2182 def qinit(ui, repo, create):
2183 """initialize a new queue repository
2183 """initialize a new queue repository
2184
2184
2185 This command also creates a series file for ordering patches, and
2185 This command also creates a series file for ordering patches, and
2186 an mq-specific .hgignore file in the queue repository, to exclude
2186 an mq-specific .hgignore file in the queue repository, to exclude
2187 the status and guards files (these contain mostly transient state).
2187 the status and guards files (these contain mostly transient state).
2188
2188
2189 Returns 0 if initialization succeeded."""
2189 Returns 0 if initialization succeeded."""
2190 q = repo.mq
2190 q = repo.mq
2191 r = q.init(repo, create)
2191 r = q.init(repo, create)
2192 q.savedirty()
2192 q.savedirty()
2193 if r:
2193 if r:
2194 if not os.path.exists(r.wjoin('.hgignore')):
2194 if not os.path.exists(r.wjoin('.hgignore')):
2195 fp = r.wopener('.hgignore', 'w')
2195 fp = r.wopener('.hgignore', 'w')
2196 fp.write('^\\.hg\n')
2196 fp.write('^\\.hg\n')
2197 fp.write('^\\.mq\n')
2197 fp.write('^\\.mq\n')
2198 fp.write('syntax: glob\n')
2198 fp.write('syntax: glob\n')
2199 fp.write('status\n')
2199 fp.write('status\n')
2200 fp.write('guards\n')
2200 fp.write('guards\n')
2201 fp.close()
2201 fp.close()
2202 if not os.path.exists(r.wjoin('series')):
2202 if not os.path.exists(r.wjoin('series')):
2203 r.wopener('series', 'w').close()
2203 r.wopener('series', 'w').close()
2204 r[None].add(['.hgignore', 'series'])
2204 r[None].add(['.hgignore', 'series'])
2205 commands.add(ui, r)
2205 commands.add(ui, r)
2206 return 0
2206 return 0
2207
2207
2208 @command("^qinit",
2208 @command("^qinit",
2209 [('c', 'create-repo', None, _('create queue repository'))],
2209 [('c', 'create-repo', None, _('create queue repository'))],
2210 _('hg qinit [-c]'))
2210 _('hg qinit [-c]'))
2211 def init(ui, repo, **opts):
2211 def init(ui, repo, **opts):
2212 """init a new queue repository (DEPRECATED)
2212 """init a new queue repository (DEPRECATED)
2213
2213
2214 The queue repository is unversioned by default. If
2214 The queue repository is unversioned by default. If
2215 -c/--create-repo is specified, qinit will create a separate nested
2215 -c/--create-repo is specified, qinit will create a separate nested
2216 repository for patches (qinit -c may also be run later to convert
2216 repository for patches (qinit -c may also be run later to convert
2217 an unversioned patch repository into a versioned one). You can use
2217 an unversioned patch repository into a versioned one). You can use
2218 qcommit to commit changes to this queue repository.
2218 qcommit to commit changes to this queue repository.
2219
2219
2220 This command is deprecated. Without -c, it's implied by other relevant
2220 This command is deprecated. Without -c, it's implied by other relevant
2221 commands. With -c, use :hg:`init --mq` instead."""
2221 commands. With -c, use :hg:`init --mq` instead."""
2222 return qinit(ui, repo, create=opts.get('create_repo'))
2222 return qinit(ui, repo, create=opts.get('create_repo'))
2223
2223
2224 @command("qclone",
2224 @command("qclone",
2225 [('', 'pull', None, _('use pull protocol to copy metadata')),
2225 [('', 'pull', None, _('use pull protocol to copy metadata')),
2226 ('U', 'noupdate', None,
2226 ('U', 'noupdate', None,
2227 _('do not update the new working directories')),
2227 _('do not update the new working directories')),
2228 ('', 'uncompressed', None,
2228 ('', 'uncompressed', None,
2229 _('use uncompressed transfer (fast over LAN)')),
2229 _('use uncompressed transfer (fast over LAN)')),
2230 ('p', 'patches', '',
2230 ('p', 'patches', '',
2231 _('location of source patch repository'), _('REPO')),
2231 _('location of source patch repository'), _('REPO')),
2232 ] + commands.remoteopts,
2232 ] + commands.remoteopts,
2233 _('hg qclone [OPTION]... SOURCE [DEST]'))
2233 _('hg qclone [OPTION]... SOURCE [DEST]'))
2234 def clone(ui, source, dest=None, **opts):
2234 def clone(ui, source, dest=None, **opts):
2235 '''clone main and patch repository at same time
2235 '''clone main and patch repository at same time
2236
2236
2237 If source is local, destination will have no patches applied. If
2237 If source is local, destination will have no patches applied. If
2238 source is remote, this command can not check if patches are
2238 source is remote, this command can not check if patches are
2239 applied in source, so cannot guarantee that patches are not
2239 applied in source, so cannot guarantee that patches are not
2240 applied in destination. If you clone remote repository, be sure
2240 applied in destination. If you clone remote repository, be sure
2241 before that it has no patches applied.
2241 before that it has no patches applied.
2242
2242
2243 Source patch repository is looked for in <src>/.hg/patches by
2243 Source patch repository is looked for in <src>/.hg/patches by
2244 default. Use -p <url> to change.
2244 default. Use -p <url> to change.
2245
2245
2246 The patch directory must be a nested Mercurial repository, as
2246 The patch directory must be a nested Mercurial repository, as
2247 would be created by :hg:`init --mq`.
2247 would be created by :hg:`init --mq`.
2248
2248
2249 Return 0 on success.
2249 Return 0 on success.
2250 '''
2250 '''
2251 def patchdir(repo):
2251 def patchdir(repo):
2252 """compute a patch repo url from a repo object"""
2252 """compute a patch repo url from a repo object"""
2253 url = repo.url()
2253 url = repo.url()
2254 if url.endswith('/'):
2254 if url.endswith('/'):
2255 url = url[:-1]
2255 url = url[:-1]
2256 return url + '/.hg/patches'
2256 return url + '/.hg/patches'
2257
2257
2258 # main repo (destination and sources)
2258 # main repo (destination and sources)
2259 if dest is None:
2259 if dest is None:
2260 dest = hg.defaultdest(source)
2260 dest = hg.defaultdest(source)
2261 sr = hg.peer(ui, opts, ui.expandpath(source))
2261 sr = hg.peer(ui, opts, ui.expandpath(source))
2262
2262
2263 # patches repo (source only)
2263 # patches repo (source only)
2264 if opts.get('patches'):
2264 if opts.get('patches'):
2265 patchespath = ui.expandpath(opts.get('patches'))
2265 patchespath = ui.expandpath(opts.get('patches'))
2266 else:
2266 else:
2267 patchespath = patchdir(sr)
2267 patchespath = patchdir(sr)
2268 try:
2268 try:
2269 hg.peer(ui, opts, patchespath)
2269 hg.peer(ui, opts, patchespath)
2270 except error.RepoError:
2270 except error.RepoError:
2271 raise util.Abort(_('versioned patch repository not found'
2271 raise util.Abort(_('versioned patch repository not found'
2272 ' (see init --mq)'))
2272 ' (see init --mq)'))
2273 qbase, destrev = None, None
2273 qbase, destrev = None, None
2274 if sr.local():
2274 if sr.local():
2275 repo = sr.local()
2275 repo = sr.local()
2276 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2276 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2277 qbase = repo.mq.applied[0].node
2277 qbase = repo.mq.applied[0].node
2278 if not hg.islocal(dest):
2278 if not hg.islocal(dest):
2279 heads = set(repo.heads())
2279 heads = set(repo.heads())
2280 destrev = list(heads.difference(repo.heads(qbase)))
2280 destrev = list(heads.difference(repo.heads(qbase)))
2281 destrev.append(repo.changelog.parents(qbase)[0])
2281 destrev.append(repo.changelog.parents(qbase)[0])
2282 elif sr.capable('lookup'):
2282 elif sr.capable('lookup'):
2283 try:
2283 try:
2284 qbase = sr.lookup('qbase')
2284 qbase = sr.lookup('qbase')
2285 except error.RepoError:
2285 except error.RepoError:
2286 pass
2286 pass
2287
2287
2288 ui.note(_('cloning main repository\n'))
2288 ui.note(_('cloning main repository\n'))
2289 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2289 sr, dr = hg.clone(ui, opts, sr.url(), dest,
2290 pull=opts.get('pull'),
2290 pull=opts.get('pull'),
2291 rev=destrev,
2291 rev=destrev,
2292 update=False,
2292 update=False,
2293 stream=opts.get('uncompressed'))
2293 stream=opts.get('uncompressed'))
2294
2294
2295 ui.note(_('cloning patch repository\n'))
2295 ui.note(_('cloning patch repository\n'))
2296 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2296 hg.clone(ui, opts, opts.get('patches') or patchdir(sr), patchdir(dr),
2297 pull=opts.get('pull'), update=not opts.get('noupdate'),
2297 pull=opts.get('pull'), update=not opts.get('noupdate'),
2298 stream=opts.get('uncompressed'))
2298 stream=opts.get('uncompressed'))
2299
2299
2300 if dr.local():
2300 if dr.local():
2301 repo = dr.local()
2301 repo = dr.local()
2302 if qbase:
2302 if qbase:
2303 ui.note(_('stripping applied patches from destination '
2303 ui.note(_('stripping applied patches from destination '
2304 'repository\n'))
2304 'repository\n'))
2305 repo.mq.strip(repo, [qbase], update=False, backup=None)
2305 repo.mq.strip(repo, [qbase], update=False, backup=None)
2306 if not opts.get('noupdate'):
2306 if not opts.get('noupdate'):
2307 ui.note(_('updating destination repository\n'))
2307 ui.note(_('updating destination repository\n'))
2308 hg.update(repo, repo.changelog.tip())
2308 hg.update(repo, repo.changelog.tip())
2309
2309
2310 @command("qcommit|qci",
2310 @command("qcommit|qci",
2311 commands.table["^commit|ci"][1],
2311 commands.table["^commit|ci"][1],
2312 _('hg qcommit [OPTION]... [FILE]...'))
2312 _('hg qcommit [OPTION]... [FILE]...'))
2313 def commit(ui, repo, *pats, **opts):
2313 def commit(ui, repo, *pats, **opts):
2314 """commit changes in the queue repository (DEPRECATED)
2314 """commit changes in the queue repository (DEPRECATED)
2315
2315
2316 This command is deprecated; use :hg:`commit --mq` instead."""
2316 This command is deprecated; use :hg:`commit --mq` instead."""
2317 q = repo.mq
2317 q = repo.mq
2318 r = q.qrepo()
2318 r = q.qrepo()
2319 if not r:
2319 if not r:
2320 raise util.Abort('no queue repository')
2320 raise util.Abort('no queue repository')
2321 commands.commit(r.ui, r, *pats, **opts)
2321 commands.commit(r.ui, r, *pats, **opts)
2322
2322
2323 @command("qseries",
2323 @command("qseries",
2324 [('m', 'missing', None, _('print patches not in series')),
2324 [('m', 'missing', None, _('print patches not in series')),
2325 ] + seriesopts,
2325 ] + seriesopts,
2326 _('hg qseries [-ms]'))
2326 _('hg qseries [-ms]'))
2327 def series(ui, repo, **opts):
2327 def series(ui, repo, **opts):
2328 """print the entire series file
2328 """print the entire series file
2329
2329
2330 Returns 0 on success."""
2330 Returns 0 on success."""
2331 repo.mq.qseries(repo, missing=opts.get('missing'),
2331 repo.mq.qseries(repo, missing=opts.get('missing'),
2332 summary=opts.get('summary'))
2332 summary=opts.get('summary'))
2333 return 0
2333 return 0
2334
2334
2335 @command("qtop", seriesopts, _('hg qtop [-s]'))
2335 @command("qtop", seriesopts, _('hg qtop [-s]'))
2336 def top(ui, repo, **opts):
2336 def top(ui, repo, **opts):
2337 """print the name of the current patch
2337 """print the name of the current patch
2338
2338
2339 Returns 0 on success."""
2339 Returns 0 on success."""
2340 q = repo.mq
2340 q = repo.mq
2341 t = q.applied and q.seriesend(True) or 0
2341 t = q.applied and q.seriesend(True) or 0
2342 if t:
2342 if t:
2343 q.qseries(repo, start=t - 1, length=1, status='A',
2343 q.qseries(repo, start=t - 1, length=1, status='A',
2344 summary=opts.get('summary'))
2344 summary=opts.get('summary'))
2345 else:
2345 else:
2346 ui.write(_("no patches applied\n"))
2346 ui.write(_("no patches applied\n"))
2347 return 1
2347 return 1
2348
2348
2349 @command("qnext", seriesopts, _('hg qnext [-s]'))
2349 @command("qnext", seriesopts, _('hg qnext [-s]'))
2350 def next(ui, repo, **opts):
2350 def next(ui, repo, **opts):
2351 """print the name of the next pushable patch
2351 """print the name of the next pushable patch
2352
2352
2353 Returns 0 on success."""
2353 Returns 0 on success."""
2354 q = repo.mq
2354 q = repo.mq
2355 end = q.seriesend()
2355 end = q.seriesend()
2356 if end == len(q.series):
2356 if end == len(q.series):
2357 ui.write(_("all patches applied\n"))
2357 ui.write(_("all patches applied\n"))
2358 return 1
2358 return 1
2359 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2359 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2360
2360
2361 @command("qprev", seriesopts, _('hg qprev [-s]'))
2361 @command("qprev", seriesopts, _('hg qprev [-s]'))
2362 def prev(ui, repo, **opts):
2362 def prev(ui, repo, **opts):
2363 """print the name of the preceding applied patch
2363 """print the name of the preceding applied patch
2364
2364
2365 Returns 0 on success."""
2365 Returns 0 on success."""
2366 q = repo.mq
2366 q = repo.mq
2367 l = len(q.applied)
2367 l = len(q.applied)
2368 if l == 1:
2368 if l == 1:
2369 ui.write(_("only one patch applied\n"))
2369 ui.write(_("only one patch applied\n"))
2370 return 1
2370 return 1
2371 if not l:
2371 if not l:
2372 ui.write(_("no patches applied\n"))
2372 ui.write(_("no patches applied\n"))
2373 return 1
2373 return 1
2374 idx = q.series.index(q.applied[-2].name)
2374 idx = q.series.index(q.applied[-2].name)
2375 q.qseries(repo, start=idx, length=1, status='A',
2375 q.qseries(repo, start=idx, length=1, status='A',
2376 summary=opts.get('summary'))
2376 summary=opts.get('summary'))
2377
2377
2378 def setupheaderopts(ui, opts):
2378 def setupheaderopts(ui, opts):
2379 if not opts.get('user') and opts.get('currentuser'):
2379 if not opts.get('user') and opts.get('currentuser'):
2380 opts['user'] = ui.username()
2380 opts['user'] = ui.username()
2381 if not opts.get('date') and opts.get('currentdate'):
2381 if not opts.get('date') and opts.get('currentdate'):
2382 opts['date'] = "%d %d" % util.makedate()
2382 opts['date'] = "%d %d" % util.makedate()
2383
2383
2384 @command("^qnew",
2384 @command("^qnew",
2385 [('e', 'edit', None, _('edit commit message')),
2385 [('e', 'edit', None, _('edit commit message')),
2386 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2386 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2387 ('g', 'git', None, _('use git extended diff format')),
2387 ('g', 'git', None, _('use git extended diff format')),
2388 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2388 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2389 ('u', 'user', '',
2389 ('u', 'user', '',
2390 _('add "From: <USER>" to patch'), _('USER')),
2390 _('add "From: <USER>" to patch'), _('USER')),
2391 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2391 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2392 ('d', 'date', '',
2392 ('d', 'date', '',
2393 _('add "Date: <DATE>" to patch'), _('DATE'))
2393 _('add "Date: <DATE>" to patch'), _('DATE'))
2394 ] + commands.walkopts + commands.commitopts,
2394 ] + commands.walkopts + commands.commitopts,
2395 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2395 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2396 def new(ui, repo, patch, *args, **opts):
2396 def new(ui, repo, patch, *args, **opts):
2397 """create a new patch
2397 """create a new patch
2398
2398
2399 qnew creates a new patch on top of the currently-applied patch (if
2399 qnew creates a new patch on top of the currently-applied patch (if
2400 any). The patch will be initialized with any outstanding changes
2400 any). The patch will be initialized with any outstanding changes
2401 in the working directory. You may also use -I/--include,
2401 in the working directory. You may also use -I/--include,
2402 -X/--exclude, and/or a list of files after the patch name to add
2402 -X/--exclude, and/or a list of files after the patch name to add
2403 only changes to matching files to the new patch, leaving the rest
2403 only changes to matching files to the new patch, leaving the rest
2404 as uncommitted modifications.
2404 as uncommitted modifications.
2405
2405
2406 -u/--user and -d/--date can be used to set the (given) user and
2406 -u/--user and -d/--date can be used to set the (given) user and
2407 date, respectively. -U/--currentuser and -D/--currentdate set user
2407 date, respectively. -U/--currentuser and -D/--currentdate set user
2408 to current user and date to current date.
2408 to current user and date to current date.
2409
2409
2410 -e/--edit, -m/--message or -l/--logfile set the patch header as
2410 -e/--edit, -m/--message or -l/--logfile set the patch header as
2411 well as the commit message. If none is specified, the header is
2411 well as the commit message. If none is specified, the header is
2412 empty and the commit message is '[mq]: PATCH'.
2412 empty and the commit message is '[mq]: PATCH'.
2413
2413
2414 Use the -g/--git option to keep the patch in the git extended diff
2414 Use the -g/--git option to keep the patch in the git extended diff
2415 format. Read the diffs help topic for more information on why this
2415 format. Read the diffs help topic for more information on why this
2416 is important for preserving permission changes and copy/rename
2416 is important for preserving permission changes and copy/rename
2417 information.
2417 information.
2418
2418
2419 Returns 0 on successful creation of a new patch.
2419 Returns 0 on successful creation of a new patch.
2420 """
2420 """
2421 msg = cmdutil.logmessage(ui, opts)
2421 msg = cmdutil.logmessage(ui, opts)
2422 def getmsg():
2422 def getmsg():
2423 return ui.edit(msg, opts.get('user') or ui.username())
2423 return ui.edit(msg, opts.get('user') or ui.username())
2424 q = repo.mq
2424 q = repo.mq
2425 opts['msg'] = msg
2425 opts['msg'] = msg
2426 if opts.get('edit'):
2426 if opts.get('edit'):
2427 opts['msg'] = getmsg
2427 opts['msg'] = getmsg
2428 else:
2428 else:
2429 opts['msg'] = msg
2429 opts['msg'] = msg
2430 setupheaderopts(ui, opts)
2430 setupheaderopts(ui, opts)
2431 q.new(repo, patch, *args, **opts)
2431 q.new(repo, patch, *args, **opts)
2432 q.savedirty()
2432 q.savedirty()
2433 return 0
2433 return 0
2434
2434
2435 @command("^qrefresh",
2435 @command("^qrefresh",
2436 [('e', 'edit', None, _('edit commit message')),
2436 [('e', 'edit', None, _('edit commit message')),
2437 ('g', 'git', None, _('use git extended diff format')),
2437 ('g', 'git', None, _('use git extended diff format')),
2438 ('s', 'short', None,
2438 ('s', 'short', None,
2439 _('refresh only files already in the patch and specified files')),
2439 _('refresh only files already in the patch and specified files')),
2440 ('U', 'currentuser', None,
2440 ('U', 'currentuser', None,
2441 _('add/update author field in patch with current user')),
2441 _('add/update author field in patch with current user')),
2442 ('u', 'user', '',
2442 ('u', 'user', '',
2443 _('add/update author field in patch with given user'), _('USER')),
2443 _('add/update author field in patch with given user'), _('USER')),
2444 ('D', 'currentdate', None,
2444 ('D', 'currentdate', None,
2445 _('add/update date field in patch with current date')),
2445 _('add/update date field in patch with current date')),
2446 ('d', 'date', '',
2446 ('d', 'date', '',
2447 _('add/update date field in patch with given date'), _('DATE'))
2447 _('add/update date field in patch with given date'), _('DATE'))
2448 ] + commands.walkopts + commands.commitopts,
2448 ] + commands.walkopts + commands.commitopts,
2449 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2449 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2450 def refresh(ui, repo, *pats, **opts):
2450 def refresh(ui, repo, *pats, **opts):
2451 """update the current patch
2451 """update the current patch
2452
2452
2453 If any file patterns are provided, the refreshed patch will
2453 If any file patterns are provided, the refreshed patch will
2454 contain only the modifications that match those patterns; the
2454 contain only the modifications that match those patterns; the
2455 remaining modifications will remain in the working directory.
2455 remaining modifications will remain in the working directory.
2456
2456
2457 If -s/--short is specified, files currently included in the patch
2457 If -s/--short is specified, files currently included in the patch
2458 will be refreshed just like matched files and remain in the patch.
2458 will be refreshed just like matched files and remain in the patch.
2459
2459
2460 If -e/--edit is specified, Mercurial will start your configured editor for
2460 If -e/--edit is specified, Mercurial will start your configured editor for
2461 you to enter a message. In case qrefresh fails, you will find a backup of
2461 you to enter a message. In case qrefresh fails, you will find a backup of
2462 your message in ``.hg/last-message.txt``.
2462 your message in ``.hg/last-message.txt``.
2463
2463
2464 hg add/remove/copy/rename work as usual, though you might want to
2464 hg add/remove/copy/rename work as usual, though you might want to
2465 use git-style patches (-g/--git or [diff] git=1) to track copies
2465 use git-style patches (-g/--git or [diff] git=1) to track copies
2466 and renames. See the diffs help topic for more information on the
2466 and renames. See the diffs help topic for more information on the
2467 git diff format.
2467 git diff format.
2468
2468
2469 Returns 0 on success.
2469 Returns 0 on success.
2470 """
2470 """
2471 q = repo.mq
2471 q = repo.mq
2472 message = cmdutil.logmessage(ui, opts)
2472 message = cmdutil.logmessage(ui, opts)
2473 if opts.get('edit'):
2473 if opts.get('edit'):
2474 if not q.applied:
2474 if not q.applied:
2475 ui.write(_("no patches applied\n"))
2475 ui.write(_("no patches applied\n"))
2476 return 1
2476 return 1
2477 if message:
2477 if message:
2478 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2478 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2479 patch = q.applied[-1].name
2479 patch = q.applied[-1].name
2480 ph = patchheader(q.join(patch), q.plainmode)
2480 ph = patchheader(q.join(patch), q.plainmode)
2481 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2481 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2482 # We don't want to lose the patch message if qrefresh fails (issue2062)
2482 # We don't want to lose the patch message if qrefresh fails (issue2062)
2483 repo.savecommitmessage(message)
2483 repo.savecommitmessage(message)
2484 setupheaderopts(ui, opts)
2484 setupheaderopts(ui, opts)
2485 wlock = repo.wlock()
2485 wlock = repo.wlock()
2486 try:
2486 try:
2487 ret = q.refresh(repo, pats, msg=message, **opts)
2487 ret = q.refresh(repo, pats, msg=message, **opts)
2488 q.savedirty()
2488 q.savedirty()
2489 return ret
2489 return ret
2490 finally:
2490 finally:
2491 wlock.release()
2491 wlock.release()
2492
2492
2493 @command("^qdiff",
2493 @command("^qdiff",
2494 commands.diffopts + commands.diffopts2 + commands.walkopts,
2494 commands.diffopts + commands.diffopts2 + commands.walkopts,
2495 _('hg qdiff [OPTION]... [FILE]...'))
2495 _('hg qdiff [OPTION]... [FILE]...'))
2496 def diff(ui, repo, *pats, **opts):
2496 def diff(ui, repo, *pats, **opts):
2497 """diff of the current patch and subsequent modifications
2497 """diff of the current patch and subsequent modifications
2498
2498
2499 Shows a diff which includes the current patch as well as any
2499 Shows a diff which includes the current patch as well as any
2500 changes which have been made in the working directory since the
2500 changes which have been made in the working directory since the
2501 last refresh (thus showing what the current patch would become
2501 last refresh (thus showing what the current patch would become
2502 after a qrefresh).
2502 after a qrefresh).
2503
2503
2504 Use :hg:`diff` if you only want to see the changes made since the
2504 Use :hg:`diff` if you only want to see the changes made since the
2505 last qrefresh, or :hg:`export qtip` if you want to see changes
2505 last qrefresh, or :hg:`export qtip` if you want to see changes
2506 made by the current patch without including changes made since the
2506 made by the current patch without including changes made since the
2507 qrefresh.
2507 qrefresh.
2508
2508
2509 Returns 0 on success.
2509 Returns 0 on success.
2510 """
2510 """
2511 repo.mq.diff(repo, pats, opts)
2511 repo.mq.diff(repo, pats, opts)
2512 return 0
2512 return 0
2513
2513
2514 @command('qfold',
2514 @command('qfold',
2515 [('e', 'edit', None, _('edit patch header')),
2515 [('e', 'edit', None, _('edit patch header')),
2516 ('k', 'keep', None, _('keep folded patch files')),
2516 ('k', 'keep', None, _('keep folded patch files')),
2517 ] + commands.commitopts,
2517 ] + commands.commitopts,
2518 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2518 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2519 def fold(ui, repo, *files, **opts):
2519 def fold(ui, repo, *files, **opts):
2520 """fold the named patches into the current patch
2520 """fold the named patches into the current patch
2521
2521
2522 Patches must not yet be applied. Each patch will be successively
2522 Patches must not yet be applied. Each patch will be successively
2523 applied to the current patch in the order given. If all the
2523 applied to the current patch in the order given. If all the
2524 patches apply successfully, the current patch will be refreshed
2524 patches apply successfully, the current patch will be refreshed
2525 with the new cumulative patch, and the folded patches will be
2525 with the new cumulative patch, and the folded patches will be
2526 deleted. With -k/--keep, the folded patch files will not be
2526 deleted. With -k/--keep, the folded patch files will not be
2527 removed afterwards.
2527 removed afterwards.
2528
2528
2529 The header for each folded patch will be concatenated with the
2529 The header for each folded patch will be concatenated with the
2530 current patch header, separated by a line of ``* * *``.
2530 current patch header, separated by a line of ``* * *``.
2531
2531
2532 Returns 0 on success."""
2532 Returns 0 on success."""
2533 q = repo.mq
2533 q = repo.mq
2534 if not files:
2534 if not files:
2535 raise util.Abort(_('qfold requires at least one patch name'))
2535 raise util.Abort(_('qfold requires at least one patch name'))
2536 if not q.checktoppatch(repo)[0]:
2536 if not q.checktoppatch(repo)[0]:
2537 raise util.Abort(_('no patches applied'))
2537 raise util.Abort(_('no patches applied'))
2538 q.checklocalchanges(repo)
2538 q.checklocalchanges(repo)
2539
2539
2540 message = cmdutil.logmessage(ui, opts)
2540 message = cmdutil.logmessage(ui, opts)
2541 if opts.get('edit'):
2541 if opts.get('edit'):
2542 if message:
2542 if message:
2543 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2543 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2544
2544
2545 parent = q.lookup('qtip')
2545 parent = q.lookup('qtip')
2546 patches = []
2546 patches = []
2547 messages = []
2547 messages = []
2548 for f in files:
2548 for f in files:
2549 p = q.lookup(f)
2549 p = q.lookup(f)
2550 if p in patches or p == parent:
2550 if p in patches or p == parent:
2551 ui.warn(_('skipping already folded patch %s\n') % p)
2551 ui.warn(_('skipping already folded patch %s\n') % p)
2552 if q.isapplied(p):
2552 if q.isapplied(p):
2553 raise util.Abort(_('qfold cannot fold already applied patch %s')
2553 raise util.Abort(_('qfold cannot fold already applied patch %s')
2554 % p)
2554 % p)
2555 patches.append(p)
2555 patches.append(p)
2556
2556
2557 for p in patches:
2557 for p in patches:
2558 if not message:
2558 if not message:
2559 ph = patchheader(q.join(p), q.plainmode)
2559 ph = patchheader(q.join(p), q.plainmode)
2560 if ph.message:
2560 if ph.message:
2561 messages.append(ph.message)
2561 messages.append(ph.message)
2562 pf = q.join(p)
2562 pf = q.join(p)
2563 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2563 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2564 if not patchsuccess:
2564 if not patchsuccess:
2565 raise util.Abort(_('error folding patch %s') % p)
2565 raise util.Abort(_('error folding patch %s') % p)
2566
2566
2567 if not message:
2567 if not message:
2568 ph = patchheader(q.join(parent), q.plainmode)
2568 ph = patchheader(q.join(parent), q.plainmode)
2569 message, user = ph.message, ph.user
2569 message, user = ph.message, ph.user
2570 for msg in messages:
2570 for msg in messages:
2571 message.append('* * *')
2571 message.append('* * *')
2572 message.extend(msg)
2572 message.extend(msg)
2573 message = '\n'.join(message)
2573 message = '\n'.join(message)
2574
2574
2575 if opts.get('edit'):
2575 if opts.get('edit'):
2576 message = ui.edit(message, user or ui.username())
2576 message = ui.edit(message, user or ui.username())
2577
2577
2578 diffopts = q.patchopts(q.diffopts(), *patches)
2578 diffopts = q.patchopts(q.diffopts(), *patches)
2579 wlock = repo.wlock()
2579 wlock = repo.wlock()
2580 try:
2580 try:
2581 q.refresh(repo, msg=message, git=diffopts.git)
2581 q.refresh(repo, msg=message, git=diffopts.git)
2582 q.delete(repo, patches, opts)
2582 q.delete(repo, patches, opts)
2583 q.savedirty()
2583 q.savedirty()
2584 finally:
2584 finally:
2585 wlock.release()
2585 wlock.release()
2586
2586
2587 @command("qgoto",
2587 @command("qgoto",
2588 [('', 'keep-changes', None,
2588 [('', 'keep-changes', None,
2589 _('tolerate non-conflicting local changes')),
2589 _('tolerate non-conflicting local changes')),
2590 ('f', 'force', None, _('overwrite any local changes')),
2590 ('f', 'force', None, _('overwrite any local changes')),
2591 ('', 'no-backup', None, _('do not save backup copies of files'))],
2591 ('', 'no-backup', None, _('do not save backup copies of files'))],
2592 _('hg qgoto [OPTION]... PATCH'))
2592 _('hg qgoto [OPTION]... PATCH'))
2593 def goto(ui, repo, patch, **opts):
2593 def goto(ui, repo, patch, **opts):
2594 '''push or pop patches until named patch is at top of stack
2594 '''push or pop patches until named patch is at top of stack
2595
2595
2596 Returns 0 on success.'''
2596 Returns 0 on success.'''
2597 opts = fixkeepchangesopts(ui, opts)
2597 opts = fixkeepchangesopts(ui, opts)
2598 q = repo.mq
2598 q = repo.mq
2599 patch = q.lookup(patch)
2599 patch = q.lookup(patch)
2600 nobackup = opts.get('no_backup')
2600 nobackup = opts.get('no_backup')
2601 keepchanges = opts.get('keep_changes')
2601 keepchanges = opts.get('keep_changes')
2602 if q.isapplied(patch):
2602 if q.isapplied(patch):
2603 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2603 ret = q.pop(repo, patch, force=opts.get('force'), nobackup=nobackup,
2604 keepchanges=keepchanges)
2604 keepchanges=keepchanges)
2605 else:
2605 else:
2606 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2606 ret = q.push(repo, patch, force=opts.get('force'), nobackup=nobackup,
2607 keepchanges=keepchanges)
2607 keepchanges=keepchanges)
2608 q.savedirty()
2608 q.savedirty()
2609 return ret
2609 return ret
2610
2610
2611 @command("qguard",
2611 @command("qguard",
2612 [('l', 'list', None, _('list all patches and guards')),
2612 [('l', 'list', None, _('list all patches and guards')),
2613 ('n', 'none', None, _('drop all guards'))],
2613 ('n', 'none', None, _('drop all guards'))],
2614 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2614 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2615 def guard(ui, repo, *args, **opts):
2615 def guard(ui, repo, *args, **opts):
2616 '''set or print guards for a patch
2616 '''set or print guards for a patch
2617
2617
2618 Guards control whether a patch can be pushed. A patch with no
2618 Guards control whether a patch can be pushed. A patch with no
2619 guards is always pushed. A patch with a positive guard ("+foo") is
2619 guards is always pushed. A patch with a positive guard ("+foo") is
2620 pushed only if the :hg:`qselect` command has activated it. A patch with
2620 pushed only if the :hg:`qselect` command has activated it. A patch with
2621 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2621 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2622 has activated it.
2622 has activated it.
2623
2623
2624 With no arguments, print the currently active guards.
2624 With no arguments, print the currently active guards.
2625 With arguments, set guards for the named patch.
2625 With arguments, set guards for the named patch.
2626
2626
2627 .. note::
2627 .. note::
2628 Specifying negative guards now requires '--'.
2628 Specifying negative guards now requires '--'.
2629
2629
2630 To set guards on another patch::
2630 To set guards on another patch::
2631
2631
2632 hg qguard other.patch -- +2.6.17 -stable
2632 hg qguard other.patch -- +2.6.17 -stable
2633
2633
2634 Returns 0 on success.
2634 Returns 0 on success.
2635 '''
2635 '''
2636 def status(idx):
2636 def status(idx):
2637 guards = q.seriesguards[idx] or ['unguarded']
2637 guards = q.seriesguards[idx] or ['unguarded']
2638 if q.series[idx] in applied:
2638 if q.series[idx] in applied:
2639 state = 'applied'
2639 state = 'applied'
2640 elif q.pushable(idx)[0]:
2640 elif q.pushable(idx)[0]:
2641 state = 'unapplied'
2641 state = 'unapplied'
2642 else:
2642 else:
2643 state = 'guarded'
2643 state = 'guarded'
2644 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2644 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2645 ui.write('%s: ' % ui.label(q.series[idx], label))
2645 ui.write('%s: ' % ui.label(q.series[idx], label))
2646
2646
2647 for i, guard in enumerate(guards):
2647 for i, guard in enumerate(guards):
2648 if guard.startswith('+'):
2648 if guard.startswith('+'):
2649 ui.write(guard, label='qguard.positive')
2649 ui.write(guard, label='qguard.positive')
2650 elif guard.startswith('-'):
2650 elif guard.startswith('-'):
2651 ui.write(guard, label='qguard.negative')
2651 ui.write(guard, label='qguard.negative')
2652 else:
2652 else:
2653 ui.write(guard, label='qguard.unguarded')
2653 ui.write(guard, label='qguard.unguarded')
2654 if i != len(guards) - 1:
2654 if i != len(guards) - 1:
2655 ui.write(' ')
2655 ui.write(' ')
2656 ui.write('\n')
2656 ui.write('\n')
2657 q = repo.mq
2657 q = repo.mq
2658 applied = set(p.name for p in q.applied)
2658 applied = set(p.name for p in q.applied)
2659 patch = None
2659 patch = None
2660 args = list(args)
2660 args = list(args)
2661 if opts.get('list'):
2661 if opts.get('list'):
2662 if args or opts.get('none'):
2662 if args or opts.get('none'):
2663 raise util.Abort(_('cannot mix -l/--list with options or '
2663 raise util.Abort(_('cannot mix -l/--list with options or '
2664 'arguments'))
2664 'arguments'))
2665 for i in xrange(len(q.series)):
2665 for i in xrange(len(q.series)):
2666 status(i)
2666 status(i)
2667 return
2667 return
2668 if not args or args[0][0:1] in '-+':
2668 if not args or args[0][0:1] in '-+':
2669 if not q.applied:
2669 if not q.applied:
2670 raise util.Abort(_('no patches applied'))
2670 raise util.Abort(_('no patches applied'))
2671 patch = q.applied[-1].name
2671 patch = q.applied[-1].name
2672 if patch is None and args[0][0:1] not in '-+':
2672 if patch is None and args[0][0:1] not in '-+':
2673 patch = args.pop(0)
2673 patch = args.pop(0)
2674 if patch is None:
2674 if patch is None:
2675 raise util.Abort(_('no patch to work with'))
2675 raise util.Abort(_('no patch to work with'))
2676 if args or opts.get('none'):
2676 if args or opts.get('none'):
2677 idx = q.findseries(patch)
2677 idx = q.findseries(patch)
2678 if idx is None:
2678 if idx is None:
2679 raise util.Abort(_('no patch named %s') % patch)
2679 raise util.Abort(_('no patch named %s') % patch)
2680 q.setguards(idx, args)
2680 q.setguards(idx, args)
2681 q.savedirty()
2681 q.savedirty()
2682 else:
2682 else:
2683 status(q.series.index(q.lookup(patch)))
2683 status(q.series.index(q.lookup(patch)))
2684
2684
2685 @command("qheader", [], _('hg qheader [PATCH]'))
2685 @command("qheader", [], _('hg qheader [PATCH]'))
2686 def header(ui, repo, patch=None):
2686 def header(ui, repo, patch=None):
2687 """print the header of the topmost or specified patch
2687 """print the header of the topmost or specified patch
2688
2688
2689 Returns 0 on success."""
2689 Returns 0 on success."""
2690 q = repo.mq
2690 q = repo.mq
2691
2691
2692 if patch:
2692 if patch:
2693 patch = q.lookup(patch)
2693 patch = q.lookup(patch)
2694 else:
2694 else:
2695 if not q.applied:
2695 if not q.applied:
2696 ui.write(_('no patches applied\n'))
2696 ui.write(_('no patches applied\n'))
2697 return 1
2697 return 1
2698 patch = q.lookup('qtip')
2698 patch = q.lookup('qtip')
2699 ph = patchheader(q.join(patch), q.plainmode)
2699 ph = patchheader(q.join(patch), q.plainmode)
2700
2700
2701 ui.write('\n'.join(ph.message) + '\n')
2701 ui.write('\n'.join(ph.message) + '\n')
2702
2702
2703 def lastsavename(path):
2703 def lastsavename(path):
2704 (directory, base) = os.path.split(path)
2704 (directory, base) = os.path.split(path)
2705 names = os.listdir(directory)
2705 names = os.listdir(directory)
2706 namere = re.compile("%s.([0-9]+)" % base)
2706 namere = re.compile("%s.([0-9]+)" % base)
2707 maxindex = None
2707 maxindex = None
2708 maxname = None
2708 maxname = None
2709 for f in names:
2709 for f in names:
2710 m = namere.match(f)
2710 m = namere.match(f)
2711 if m:
2711 if m:
2712 index = int(m.group(1))
2712 index = int(m.group(1))
2713 if maxindex is None or index > maxindex:
2713 if maxindex is None or index > maxindex:
2714 maxindex = index
2714 maxindex = index
2715 maxname = f
2715 maxname = f
2716 if maxname:
2716 if maxname:
2717 return (os.path.join(directory, maxname), maxindex)
2717 return (os.path.join(directory, maxname), maxindex)
2718 return (None, None)
2718 return (None, None)
2719
2719
2720 def savename(path):
2720 def savename(path):
2721 (last, index) = lastsavename(path)
2721 (last, index) = lastsavename(path)
2722 if last is None:
2722 if last is None:
2723 index = 0
2723 index = 0
2724 newpath = path + ".%d" % (index + 1)
2724 newpath = path + ".%d" % (index + 1)
2725 return newpath
2725 return newpath
2726
2726
2727 @command("^qpush",
2727 @command("^qpush",
2728 [('', 'keep-changes', None,
2728 [('', 'keep-changes', None,
2729 _('tolerate non-conflicting local changes')),
2729 _('tolerate non-conflicting local changes')),
2730 ('f', 'force', None, _('apply on top of local changes')),
2730 ('f', 'force', None, _('apply on top of local changes')),
2731 ('e', 'exact', None,
2731 ('e', 'exact', None,
2732 _('apply the target patch to its recorded parent')),
2732 _('apply the target patch to its recorded parent')),
2733 ('l', 'list', None, _('list patch name in commit text')),
2733 ('l', 'list', None, _('list patch name in commit text')),
2734 ('a', 'all', None, _('apply all patches')),
2734 ('a', 'all', None, _('apply all patches')),
2735 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2735 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2736 ('n', 'name', '',
2736 ('n', 'name', '',
2737 _('merge queue name (DEPRECATED)'), _('NAME')),
2737 _('merge queue name (DEPRECATED)'), _('NAME')),
2738 ('', 'move', None,
2738 ('', 'move', None,
2739 _('reorder patch series and apply only the patch')),
2739 _('reorder patch series and apply only the patch')),
2740 ('', 'no-backup', None, _('do not save backup copies of files'))],
2740 ('', 'no-backup', None, _('do not save backup copies of files'))],
2741 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2741 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2742 def push(ui, repo, patch=None, **opts):
2742 def push(ui, repo, patch=None, **opts):
2743 """push the next patch onto the stack
2743 """push the next patch onto the stack
2744
2744
2745 By default, abort if the working directory contains uncommitted
2745 By default, abort if the working directory contains uncommitted
2746 changes. With --keep-changes, abort only if the uncommitted files
2746 changes. With --keep-changes, abort only if the uncommitted files
2747 overlap with patched files. With -f/--force, backup and patch over
2747 overlap with patched files. With -f/--force, backup and patch over
2748 uncommitted changes.
2748 uncommitted changes.
2749
2749
2750 Return 0 on success.
2750 Return 0 on success.
2751 """
2751 """
2752 q = repo.mq
2752 q = repo.mq
2753 mergeq = None
2753 mergeq = None
2754
2754
2755 opts = fixkeepchangesopts(ui, opts)
2755 opts = fixkeepchangesopts(ui, opts)
2756 if opts.get('merge'):
2756 if opts.get('merge'):
2757 if opts.get('name'):
2757 if opts.get('name'):
2758 newpath = repo.join(opts.get('name'))
2758 newpath = repo.join(opts.get('name'))
2759 else:
2759 else:
2760 newpath, i = lastsavename(q.path)
2760 newpath, i = lastsavename(q.path)
2761 if not newpath:
2761 if not newpath:
2762 ui.warn(_("no saved queues found, please use -n\n"))
2762 ui.warn(_("no saved queues found, please use -n\n"))
2763 return 1
2763 return 1
2764 mergeq = queue(ui, repo.path, newpath)
2764 mergeq = queue(ui, repo.path, newpath)
2765 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2765 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2766 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2766 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2767 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2767 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2768 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2768 exact=opts.get('exact'), nobackup=opts.get('no_backup'),
2769 keepchanges=opts.get('keep_changes'))
2769 keepchanges=opts.get('keep_changes'))
2770 return ret
2770 return ret
2771
2771
2772 @command("^qpop",
2772 @command("^qpop",
2773 [('a', 'all', None, _('pop all patches')),
2773 [('a', 'all', None, _('pop all patches')),
2774 ('n', 'name', '',
2774 ('n', 'name', '',
2775 _('queue name to pop (DEPRECATED)'), _('NAME')),
2775 _('queue name to pop (DEPRECATED)'), _('NAME')),
2776 ('', 'keep-changes', None,
2776 ('', 'keep-changes', None,
2777 _('tolerate non-conflicting local changes')),
2777 _('tolerate non-conflicting local changes')),
2778 ('f', 'force', None, _('forget any local changes to patched files')),
2778 ('f', 'force', None, _('forget any local changes to patched files')),
2779 ('', 'no-backup', None, _('do not save backup copies of files'))],
2779 ('', 'no-backup', None, _('do not save backup copies of files'))],
2780 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2780 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2781 def pop(ui, repo, patch=None, **opts):
2781 def pop(ui, repo, patch=None, **opts):
2782 """pop the current patch off the stack
2782 """pop the current patch off the stack
2783
2783
2784 Without argument, pops off the top of the patch stack. If given a
2784 Without argument, pops off the top of the patch stack. If given a
2785 patch name, keeps popping off patches until the named patch is at
2785 patch name, keeps popping off patches until the named patch is at
2786 the top of the stack.
2786 the top of the stack.
2787
2787
2788 By default, abort if the working directory contains uncommitted
2788 By default, abort if the working directory contains uncommitted
2789 changes. With --keep-changes, abort only if the uncommitted files
2789 changes. With --keep-changes, abort only if the uncommitted files
2790 overlap with patched files. With -f/--force, backup and discard
2790 overlap with patched files. With -f/--force, backup and discard
2791 changes made to such files.
2791 changes made to such files.
2792
2792
2793 Return 0 on success.
2793 Return 0 on success.
2794 """
2794 """
2795 opts = fixkeepchangesopts(ui, opts)
2795 opts = fixkeepchangesopts(ui, opts)
2796 localupdate = True
2796 localupdate = True
2797 if opts.get('name'):
2797 if opts.get('name'):
2798 q = queue(ui, repo.path, repo.join(opts.get('name')))
2798 q = queue(ui, repo.path, repo.join(opts.get('name')))
2799 ui.warn(_('using patch queue: %s\n') % q.path)
2799 ui.warn(_('using patch queue: %s\n') % q.path)
2800 localupdate = False
2800 localupdate = False
2801 else:
2801 else:
2802 q = repo.mq
2802 q = repo.mq
2803 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2803 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2804 all=opts.get('all'), nobackup=opts.get('no_backup'),
2804 all=opts.get('all'), nobackup=opts.get('no_backup'),
2805 keepchanges=opts.get('keep_changes'))
2805 keepchanges=opts.get('keep_changes'))
2806 q.savedirty()
2806 q.savedirty()
2807 return ret
2807 return ret
2808
2808
2809 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2809 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2810 def rename(ui, repo, patch, name=None, **opts):
2810 def rename(ui, repo, patch, name=None, **opts):
2811 """rename a patch
2811 """rename a patch
2812
2812
2813 With one argument, renames the current patch to PATCH1.
2813 With one argument, renames the current patch to PATCH1.
2814 With two arguments, renames PATCH1 to PATCH2.
2814 With two arguments, renames PATCH1 to PATCH2.
2815
2815
2816 Returns 0 on success."""
2816 Returns 0 on success."""
2817 q = repo.mq
2817 q = repo.mq
2818 if not name:
2818 if not name:
2819 name = patch
2819 name = patch
2820 patch = None
2820 patch = None
2821
2821
2822 if patch:
2822 if patch:
2823 patch = q.lookup(patch)
2823 patch = q.lookup(patch)
2824 else:
2824 else:
2825 if not q.applied:
2825 if not q.applied:
2826 ui.write(_('no patches applied\n'))
2826 ui.write(_('no patches applied\n'))
2827 return
2827 return
2828 patch = q.lookup('qtip')
2828 patch = q.lookup('qtip')
2829 absdest = q.join(name)
2829 absdest = q.join(name)
2830 if os.path.isdir(absdest):
2830 if os.path.isdir(absdest):
2831 name = normname(os.path.join(name, os.path.basename(patch)))
2831 name = normname(os.path.join(name, os.path.basename(patch)))
2832 absdest = q.join(name)
2832 absdest = q.join(name)
2833 q.checkpatchname(name)
2833 q.checkpatchname(name)
2834
2834
2835 ui.note(_('renaming %s to %s\n') % (patch, name))
2835 ui.note(_('renaming %s to %s\n') % (patch, name))
2836 i = q.findseries(patch)
2836 i = q.findseries(patch)
2837 guards = q.guard_re.findall(q.fullseries[i])
2837 guards = q.guard_re.findall(q.fullseries[i])
2838 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2838 q.fullseries[i] = name + ''.join([' #' + g for g in guards])
2839 q.parseseries()
2839 q.parseseries()
2840 q.seriesdirty = True
2840 q.seriesdirty = True
2841
2841
2842 info = q.isapplied(patch)
2842 info = q.isapplied(patch)
2843 if info:
2843 if info:
2844 q.applied[info[0]] = statusentry(info[1], name)
2844 q.applied[info[0]] = statusentry(info[1], name)
2845 q.applieddirty = True
2845 q.applieddirty = True
2846
2846
2847 destdir = os.path.dirname(absdest)
2847 destdir = os.path.dirname(absdest)
2848 if not os.path.isdir(destdir):
2848 if not os.path.isdir(destdir):
2849 os.makedirs(destdir)
2849 os.makedirs(destdir)
2850 util.rename(q.join(patch), absdest)
2850 util.rename(q.join(patch), absdest)
2851 r = q.qrepo()
2851 r = q.qrepo()
2852 if r and patch in r.dirstate:
2852 if r and patch in r.dirstate:
2853 wctx = r[None]
2853 wctx = r[None]
2854 wlock = r.wlock()
2854 wlock = r.wlock()
2855 try:
2855 try:
2856 if r.dirstate[patch] == 'a':
2856 if r.dirstate[patch] == 'a':
2857 r.dirstate.drop(patch)
2857 r.dirstate.drop(patch)
2858 r.dirstate.add(name)
2858 r.dirstate.add(name)
2859 else:
2859 else:
2860 wctx.copy(patch, name)
2860 wctx.copy(patch, name)
2861 wctx.forget([patch])
2861 wctx.forget([patch])
2862 finally:
2862 finally:
2863 wlock.release()
2863 wlock.release()
2864
2864
2865 q.savedirty()
2865 q.savedirty()
2866
2866
2867 @command("qrestore",
2867 @command("qrestore",
2868 [('d', 'delete', None, _('delete save entry')),
2868 [('d', 'delete', None, _('delete save entry')),
2869 ('u', 'update', None, _('update queue working directory'))],
2869 ('u', 'update', None, _('update queue working directory'))],
2870 _('hg qrestore [-d] [-u] REV'))
2870 _('hg qrestore [-d] [-u] REV'))
2871 def restore(ui, repo, rev, **opts):
2871 def restore(ui, repo, rev, **opts):
2872 """restore the queue state saved by a revision (DEPRECATED)
2872 """restore the queue state saved by a revision (DEPRECATED)
2873
2873
2874 This command is deprecated, use :hg:`rebase` instead."""
2874 This command is deprecated, use :hg:`rebase` instead."""
2875 rev = repo.lookup(rev)
2875 rev = repo.lookup(rev)
2876 q = repo.mq
2876 q = repo.mq
2877 q.restore(repo, rev, delete=opts.get('delete'),
2877 q.restore(repo, rev, delete=opts.get('delete'),
2878 qupdate=opts.get('update'))
2878 qupdate=opts.get('update'))
2879 q.savedirty()
2879 q.savedirty()
2880 return 0
2880 return 0
2881
2881
2882 @command("qsave",
2882 @command("qsave",
2883 [('c', 'copy', None, _('copy patch directory')),
2883 [('c', 'copy', None, _('copy patch directory')),
2884 ('n', 'name', '',
2884 ('n', 'name', '',
2885 _('copy directory name'), _('NAME')),
2885 _('copy directory name'), _('NAME')),
2886 ('e', 'empty', None, _('clear queue status file')),
2886 ('e', 'empty', None, _('clear queue status file')),
2887 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2887 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2888 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2888 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2889 def save(ui, repo, **opts):
2889 def save(ui, repo, **opts):
2890 """save current queue state (DEPRECATED)
2890 """save current queue state (DEPRECATED)
2891
2891
2892 This command is deprecated, use :hg:`rebase` instead."""
2892 This command is deprecated, use :hg:`rebase` instead."""
2893 q = repo.mq
2893 q = repo.mq
2894 message = cmdutil.logmessage(ui, opts)
2894 message = cmdutil.logmessage(ui, opts)
2895 ret = q.save(repo, msg=message)
2895 ret = q.save(repo, msg=message)
2896 if ret:
2896 if ret:
2897 return ret
2897 return ret
2898 q.savedirty() # save to .hg/patches before copying
2898 q.savedirty() # save to .hg/patches before copying
2899 if opts.get('copy'):
2899 if opts.get('copy'):
2900 path = q.path
2900 path = q.path
2901 if opts.get('name'):
2901 if opts.get('name'):
2902 newpath = os.path.join(q.basepath, opts.get('name'))
2902 newpath = os.path.join(q.basepath, opts.get('name'))
2903 if os.path.exists(newpath):
2903 if os.path.exists(newpath):
2904 if not os.path.isdir(newpath):
2904 if not os.path.isdir(newpath):
2905 raise util.Abort(_('destination %s exists and is not '
2905 raise util.Abort(_('destination %s exists and is not '
2906 'a directory') % newpath)
2906 'a directory') % newpath)
2907 if not opts.get('force'):
2907 if not opts.get('force'):
2908 raise util.Abort(_('destination %s exists, '
2908 raise util.Abort(_('destination %s exists, '
2909 'use -f to force') % newpath)
2909 'use -f to force') % newpath)
2910 else:
2910 else:
2911 newpath = savename(path)
2911 newpath = savename(path)
2912 ui.warn(_("copy %s to %s\n") % (path, newpath))
2912 ui.warn(_("copy %s to %s\n") % (path, newpath))
2913 util.copyfiles(path, newpath)
2913 util.copyfiles(path, newpath)
2914 if opts.get('empty'):
2914 if opts.get('empty'):
2915 del q.applied[:]
2915 del q.applied[:]
2916 q.applieddirty = True
2916 q.applieddirty = True
2917 q.savedirty()
2917 q.savedirty()
2918 return 0
2918 return 0
2919
2919
2920 @command("strip",
2920 @command("strip",
2921 [
2921 [
2922 ('r', 'rev', [], _('strip specified revision (optional, '
2922 ('r', 'rev', [], _('strip specified revision (optional, '
2923 'can specify revisions without this '
2923 'can specify revisions without this '
2924 'option)'), _('REV')),
2924 'option)'), _('REV')),
2925 ('f', 'force', None, _('force removal of changesets, discard '
2925 ('f', 'force', None, _('force removal of changesets, discard '
2926 'uncommitted changes (no backup)')),
2926 'uncommitted changes (no backup)')),
2927 ('b', 'backup', None, _('bundle only changesets with local revision'
2927 ('b', 'backup', None, _('bundle only changesets with local revision'
2928 ' number greater than REV which are not'
2928 ' number greater than REV which are not'
2929 ' descendants of REV (DEPRECATED)')),
2929 ' descendants of REV (DEPRECATED)')),
2930 ('', 'no-backup', None, _('no backups')),
2930 ('', 'no-backup', None, _('no backups')),
2931 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
2931 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
2932 ('n', '', None, _('ignored (DEPRECATED)')),
2932 ('n', '', None, _('ignored (DEPRECATED)')),
2933 ('k', 'keep', None, _("do not modify working copy during strip")),
2933 ('k', 'keep', None, _("do not modify working copy during strip")),
2934 ('B', 'bookmark', '', _("remove revs only reachable from given"
2934 ('B', 'bookmark', '', _("remove revs only reachable from given"
2935 " bookmark"))],
2935 " bookmark"))],
2936 _('hg strip [-k] [-f] [-n] [-B bookmark] [-r] REV...'))
2936 _('hg strip [-k] [-f] [-n] [-B bookmark] [-r] REV...'))
2937 def strip(ui, repo, *revs, **opts):
2937 def strip(ui, repo, *revs, **opts):
2938 """strip changesets and all their descendants from the repository
2938 """strip changesets and all their descendants from the repository
2939
2939
2940 The strip command removes the specified changesets and all their
2940 The strip command removes the specified changesets and all their
2941 descendants. If the working directory has uncommitted changes, the
2941 descendants. If the working directory has uncommitted changes, the
2942 operation is aborted unless the --force flag is supplied, in which
2942 operation is aborted unless the --force flag is supplied, in which
2943 case changes will be discarded.
2943 case changes will be discarded.
2944
2944
2945 If a parent of the working directory is stripped, then the working
2945 If a parent of the working directory is stripped, then the working
2946 directory will automatically be updated to the most recent
2946 directory will automatically be updated to the most recent
2947 available ancestor of the stripped parent after the operation
2947 available ancestor of the stripped parent after the operation
2948 completes.
2948 completes.
2949
2949
2950 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2950 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2951 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2951 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2952 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2952 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2953 where BUNDLE is the bundle file created by the strip. Note that
2953 where BUNDLE is the bundle file created by the strip. Note that
2954 the local revision numbers will in general be different after the
2954 the local revision numbers will in general be different after the
2955 restore.
2955 restore.
2956
2956
2957 Use the --no-backup option to discard the backup bundle once the
2957 Use the --no-backup option to discard the backup bundle once the
2958 operation completes.
2958 operation completes.
2959
2959
2960 Strip is not a history-rewriting operation and can be used on
2960 Strip is not a history-rewriting operation and can be used on
2961 changesets in the public phase. But if the stripped changesets have
2961 changesets in the public phase. But if the stripped changesets have
2962 been pushed to a remote repository you will likely pull them again.
2962 been pushed to a remote repository you will likely pull them again.
2963
2963
2964 Return 0 on success.
2964 Return 0 on success.
2965 """
2965 """
2966 backup = 'all'
2966 backup = 'all'
2967 if opts.get('backup'):
2967 if opts.get('backup'):
2968 backup = 'strip'
2968 backup = 'strip'
2969 elif opts.get('no_backup') or opts.get('nobackup'):
2969 elif opts.get('no_backup') or opts.get('nobackup'):
2970 backup = 'none'
2970 backup = 'none'
2971
2971
2972 cl = repo.changelog
2972 cl = repo.changelog
2973 revs = list(revs) + opts.get('rev')
2973 revs = list(revs) + opts.get('rev')
2974 revs = set(scmutil.revrange(repo, revs))
2974 revs = set(scmutil.revrange(repo, revs))
2975
2975
2976 if opts.get('bookmark'):
2976 if opts.get('bookmark'):
2977 mark = opts.get('bookmark')
2977 mark = opts.get('bookmark')
2978 marks = repo._bookmarks
2978 marks = repo._bookmarks
2979 if mark not in marks:
2979 if mark not in marks:
2980 raise util.Abort(_("bookmark '%s' not found") % mark)
2980 raise util.Abort(_("bookmark '%s' not found") % mark)
2981
2981
2982 # If the requested bookmark is not the only one pointing to a
2982 # If the requested bookmark is not the only one pointing to a
2983 # a revision we have to only delete the bookmark and not strip
2983 # a revision we have to only delete the bookmark and not strip
2984 # anything. revsets cannot detect that case.
2984 # anything. revsets cannot detect that case.
2985 uniquebm = True
2985 uniquebm = True
2986 for m, n in marks.iteritems():
2986 for m, n in marks.iteritems():
2987 if m != mark and n == repo[mark].node():
2987 if m != mark and n == repo[mark].node():
2988 uniquebm = False
2988 uniquebm = False
2989 break
2989 break
2990 if uniquebm:
2990 if uniquebm:
2991 rsrevs = repo.revs("ancestors(bookmark(%s)) - "
2991 rsrevs = repo.revs("ancestors(bookmark(%s)) - "
2992 "ancestors(head() and not bookmark(%s)) - "
2992 "ancestors(head() and not bookmark(%s)) - "
2993 "ancestors(bookmark() and not bookmark(%s))",
2993 "ancestors(bookmark() and not bookmark(%s))",
2994 mark, mark, mark)
2994 mark, mark, mark)
2995 revs.update(set(rsrevs))
2995 revs.update(set(rsrevs))
2996 if not revs:
2996 if not revs:
2997 del marks[mark]
2997 del marks[mark]
2998 marks.write()
2998 marks.write()
2999 ui.write(_("bookmark '%s' deleted\n") % mark)
2999 ui.write(_("bookmark '%s' deleted\n") % mark)
3000
3000
3001 if not revs:
3001 if not revs:
3002 raise util.Abort(_('empty revision set'))
3002 raise util.Abort(_('empty revision set'))
3003
3003
3004 descendants = set(cl.descendants(revs))
3004 descendants = set(cl.descendants(revs))
3005 strippedrevs = revs.union(descendants)
3005 strippedrevs = revs.union(descendants)
3006 roots = revs.difference(descendants)
3006 roots = revs.difference(descendants)
3007
3007
3008 update = False
3008 update = False
3009 # if one of the wdir parent is stripped we'll need
3009 # if one of the wdir parent is stripped we'll need
3010 # to update away to an earlier revision
3010 # to update away to an earlier revision
3011 for p in repo.dirstate.parents():
3011 for p in repo.dirstate.parents():
3012 if p != nullid and cl.rev(p) in strippedrevs:
3012 if p != nullid and cl.rev(p) in strippedrevs:
3013 update = True
3013 update = True
3014 break
3014 break
3015
3015
3016 rootnodes = set(cl.node(r) for r in roots)
3016 rootnodes = set(cl.node(r) for r in roots)
3017
3017
3018 q = repo.mq
3018 q = repo.mq
3019 if q.applied:
3019 if q.applied:
3020 # refresh queue state if we're about to strip
3020 # refresh queue state if we're about to strip
3021 # applied patches
3021 # applied patches
3022 if cl.rev(repo.lookup('qtip')) in strippedrevs:
3022 if cl.rev(repo.lookup('qtip')) in strippedrevs:
3023 q.applieddirty = True
3023 q.applieddirty = True
3024 start = 0
3024 start = 0
3025 end = len(q.applied)
3025 end = len(q.applied)
3026 for i, statusentry in enumerate(q.applied):
3026 for i, statusentry in enumerate(q.applied):
3027 if statusentry.node in rootnodes:
3027 if statusentry.node in rootnodes:
3028 # if one of the stripped roots is an applied
3028 # if one of the stripped roots is an applied
3029 # patch, only part of the queue is stripped
3029 # patch, only part of the queue is stripped
3030 start = i
3030 start = i
3031 break
3031 break
3032 del q.applied[start:end]
3032 del q.applied[start:end]
3033 q.savedirty()
3033 q.savedirty()
3034
3034
3035 revs = sorted(rootnodes)
3035 revs = sorted(rootnodes)
3036 if update and opts.get('keep'):
3036 if update and opts.get('keep'):
3037 wlock = repo.wlock()
3037 wlock = repo.wlock()
3038 try:
3038 try:
3039 urev = repo.mq.qparents(repo, revs[0])
3039 urev = repo.mq.qparents(repo, revs[0])
3040 repo.dirstate.rebuild(urev, repo[urev].manifest())
3040 uctx = repo[urev]
3041
3042 # only reset the dirstate for files that would actually change
3043 # between the working context and uctx
3044 descendantrevs = repo.revs("%s::." % uctx.rev())
3045 changedfiles = []
3046 for rev in descendantrevs:
3047 # blindy reset the files, regardless of what actually changed
3048 changedfiles.extend(repo[rev].files())
3049
3050 # reset files that only changed in the dirstate too
3051 dirstate = repo.dirstate
3052 dirchanges = [f for f in dirstate if dirstate[f] != 'n']
3053 changedfiles.extend(dirchanges)
3054
3055 repo.dirstate.rebuild(urev, uctx.manifest(), changedfiles)
3041 repo.dirstate.write()
3056 repo.dirstate.write()
3042 update = False
3057 update = False
3043 finally:
3058 finally:
3044 wlock.release()
3059 wlock.release()
3045
3060
3046 if opts.get('bookmark'):
3061 if opts.get('bookmark'):
3047 del marks[mark]
3062 del marks[mark]
3048 marks.write()
3063 marks.write()
3049 ui.write(_("bookmark '%s' deleted\n") % mark)
3064 ui.write(_("bookmark '%s' deleted\n") % mark)
3050
3065
3051 repo.mq.strip(repo, revs, backup=backup, update=update,
3066 repo.mq.strip(repo, revs, backup=backup, update=update,
3052 force=opts.get('force'))
3067 force=opts.get('force'))
3053
3068
3054 return 0
3069 return 0
3055
3070
3056 @command("qselect",
3071 @command("qselect",
3057 [('n', 'none', None, _('disable all guards')),
3072 [('n', 'none', None, _('disable all guards')),
3058 ('s', 'series', None, _('list all guards in series file')),
3073 ('s', 'series', None, _('list all guards in series file')),
3059 ('', 'pop', None, _('pop to before first guarded applied patch')),
3074 ('', 'pop', None, _('pop to before first guarded applied patch')),
3060 ('', 'reapply', None, _('pop, then reapply patches'))],
3075 ('', 'reapply', None, _('pop, then reapply patches'))],
3061 _('hg qselect [OPTION]... [GUARD]...'))
3076 _('hg qselect [OPTION]... [GUARD]...'))
3062 def select(ui, repo, *args, **opts):
3077 def select(ui, repo, *args, **opts):
3063 '''set or print guarded patches to push
3078 '''set or print guarded patches to push
3064
3079
3065 Use the :hg:`qguard` command to set or print guards on patch, then use
3080 Use the :hg:`qguard` command to set or print guards on patch, then use
3066 qselect to tell mq which guards to use. A patch will be pushed if
3081 qselect to tell mq which guards to use. A patch will be pushed if
3067 it has no guards or any positive guards match the currently
3082 it has no guards or any positive guards match the currently
3068 selected guard, but will not be pushed if any negative guards
3083 selected guard, but will not be pushed if any negative guards
3069 match the current guard. For example::
3084 match the current guard. For example::
3070
3085
3071 qguard foo.patch -- -stable (negative guard)
3086 qguard foo.patch -- -stable (negative guard)
3072 qguard bar.patch +stable (positive guard)
3087 qguard bar.patch +stable (positive guard)
3073 qselect stable
3088 qselect stable
3074
3089
3075 This activates the "stable" guard. mq will skip foo.patch (because
3090 This activates the "stable" guard. mq will skip foo.patch (because
3076 it has a negative match) but push bar.patch (because it has a
3091 it has a negative match) but push bar.patch (because it has a
3077 positive match).
3092 positive match).
3078
3093
3079 With no arguments, prints the currently active guards.
3094 With no arguments, prints the currently active guards.
3080 With one argument, sets the active guard.
3095 With one argument, sets the active guard.
3081
3096
3082 Use -n/--none to deactivate guards (no other arguments needed).
3097 Use -n/--none to deactivate guards (no other arguments needed).
3083 When no guards are active, patches with positive guards are
3098 When no guards are active, patches with positive guards are
3084 skipped and patches with negative guards are pushed.
3099 skipped and patches with negative guards are pushed.
3085
3100
3086 qselect can change the guards on applied patches. It does not pop
3101 qselect can change the guards on applied patches. It does not pop
3087 guarded patches by default. Use --pop to pop back to the last
3102 guarded patches by default. Use --pop to pop back to the last
3088 applied patch that is not guarded. Use --reapply (which implies
3103 applied patch that is not guarded. Use --reapply (which implies
3089 --pop) to push back to the current patch afterwards, but skip
3104 --pop) to push back to the current patch afterwards, but skip
3090 guarded patches.
3105 guarded patches.
3091
3106
3092 Use -s/--series to print a list of all guards in the series file
3107 Use -s/--series to print a list of all guards in the series file
3093 (no other arguments needed). Use -v for more information.
3108 (no other arguments needed). Use -v for more information.
3094
3109
3095 Returns 0 on success.'''
3110 Returns 0 on success.'''
3096
3111
3097 q = repo.mq
3112 q = repo.mq
3098 guards = q.active()
3113 guards = q.active()
3099 if args or opts.get('none'):
3114 if args or opts.get('none'):
3100 old_unapplied = q.unapplied(repo)
3115 old_unapplied = q.unapplied(repo)
3101 old_guarded = [i for i in xrange(len(q.applied)) if
3116 old_guarded = [i for i in xrange(len(q.applied)) if
3102 not q.pushable(i)[0]]
3117 not q.pushable(i)[0]]
3103 q.setactive(args)
3118 q.setactive(args)
3104 q.savedirty()
3119 q.savedirty()
3105 if not args:
3120 if not args:
3106 ui.status(_('guards deactivated\n'))
3121 ui.status(_('guards deactivated\n'))
3107 if not opts.get('pop') and not opts.get('reapply'):
3122 if not opts.get('pop') and not opts.get('reapply'):
3108 unapplied = q.unapplied(repo)
3123 unapplied = q.unapplied(repo)
3109 guarded = [i for i in xrange(len(q.applied))
3124 guarded = [i for i in xrange(len(q.applied))
3110 if not q.pushable(i)[0]]
3125 if not q.pushable(i)[0]]
3111 if len(unapplied) != len(old_unapplied):
3126 if len(unapplied) != len(old_unapplied):
3112 ui.status(_('number of unguarded, unapplied patches has '
3127 ui.status(_('number of unguarded, unapplied patches has '
3113 'changed from %d to %d\n') %
3128 'changed from %d to %d\n') %
3114 (len(old_unapplied), len(unapplied)))
3129 (len(old_unapplied), len(unapplied)))
3115 if len(guarded) != len(old_guarded):
3130 if len(guarded) != len(old_guarded):
3116 ui.status(_('number of guarded, applied patches has changed '
3131 ui.status(_('number of guarded, applied patches has changed '
3117 'from %d to %d\n') %
3132 'from %d to %d\n') %
3118 (len(old_guarded), len(guarded)))
3133 (len(old_guarded), len(guarded)))
3119 elif opts.get('series'):
3134 elif opts.get('series'):
3120 guards = {}
3135 guards = {}
3121 noguards = 0
3136 noguards = 0
3122 for gs in q.seriesguards:
3137 for gs in q.seriesguards:
3123 if not gs:
3138 if not gs:
3124 noguards += 1
3139 noguards += 1
3125 for g in gs:
3140 for g in gs:
3126 guards.setdefault(g, 0)
3141 guards.setdefault(g, 0)
3127 guards[g] += 1
3142 guards[g] += 1
3128 if ui.verbose:
3143 if ui.verbose:
3129 guards['NONE'] = noguards
3144 guards['NONE'] = noguards
3130 guards = guards.items()
3145 guards = guards.items()
3131 guards.sort(key=lambda x: x[0][1:])
3146 guards.sort(key=lambda x: x[0][1:])
3132 if guards:
3147 if guards:
3133 ui.note(_('guards in series file:\n'))
3148 ui.note(_('guards in series file:\n'))
3134 for guard, count in guards:
3149 for guard, count in guards:
3135 ui.note('%2d ' % count)
3150 ui.note('%2d ' % count)
3136 ui.write(guard, '\n')
3151 ui.write(guard, '\n')
3137 else:
3152 else:
3138 ui.note(_('no guards in series file\n'))
3153 ui.note(_('no guards in series file\n'))
3139 else:
3154 else:
3140 if guards:
3155 if guards:
3141 ui.note(_('active guards:\n'))
3156 ui.note(_('active guards:\n'))
3142 for g in guards:
3157 for g in guards:
3143 ui.write(g, '\n')
3158 ui.write(g, '\n')
3144 else:
3159 else:
3145 ui.write(_('no active guards\n'))
3160 ui.write(_('no active guards\n'))
3146 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
3161 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
3147 popped = False
3162 popped = False
3148 if opts.get('pop') or opts.get('reapply'):
3163 if opts.get('pop') or opts.get('reapply'):
3149 for i in xrange(len(q.applied)):
3164 for i in xrange(len(q.applied)):
3150 pushable, reason = q.pushable(i)
3165 pushable, reason = q.pushable(i)
3151 if not pushable:
3166 if not pushable:
3152 ui.status(_('popping guarded patches\n'))
3167 ui.status(_('popping guarded patches\n'))
3153 popped = True
3168 popped = True
3154 if i == 0:
3169 if i == 0:
3155 q.pop(repo, all=True)
3170 q.pop(repo, all=True)
3156 else:
3171 else:
3157 q.pop(repo, str(i - 1))
3172 q.pop(repo, str(i - 1))
3158 break
3173 break
3159 if popped:
3174 if popped:
3160 try:
3175 try:
3161 if reapply:
3176 if reapply:
3162 ui.status(_('reapplying unguarded patches\n'))
3177 ui.status(_('reapplying unguarded patches\n'))
3163 q.push(repo, reapply)
3178 q.push(repo, reapply)
3164 finally:
3179 finally:
3165 q.savedirty()
3180 q.savedirty()
3166
3181
3167 @command("qfinish",
3182 @command("qfinish",
3168 [('a', 'applied', None, _('finish all applied changesets'))],
3183 [('a', 'applied', None, _('finish all applied changesets'))],
3169 _('hg qfinish [-a] [REV]...'))
3184 _('hg qfinish [-a] [REV]...'))
3170 def finish(ui, repo, *revrange, **opts):
3185 def finish(ui, repo, *revrange, **opts):
3171 """move applied patches into repository history
3186 """move applied patches into repository history
3172
3187
3173 Finishes the specified revisions (corresponding to applied
3188 Finishes the specified revisions (corresponding to applied
3174 patches) by moving them out of mq control into regular repository
3189 patches) by moving them out of mq control into regular repository
3175 history.
3190 history.
3176
3191
3177 Accepts a revision range or the -a/--applied option. If --applied
3192 Accepts a revision range or the -a/--applied option. If --applied
3178 is specified, all applied mq revisions are removed from mq
3193 is specified, all applied mq revisions are removed from mq
3179 control. Otherwise, the given revisions must be at the base of the
3194 control. Otherwise, the given revisions must be at the base of the
3180 stack of applied patches.
3195 stack of applied patches.
3181
3196
3182 This can be especially useful if your changes have been applied to
3197 This can be especially useful if your changes have been applied to
3183 an upstream repository, or if you are about to push your changes
3198 an upstream repository, or if you are about to push your changes
3184 to upstream.
3199 to upstream.
3185
3200
3186 Returns 0 on success.
3201 Returns 0 on success.
3187 """
3202 """
3188 if not opts.get('applied') and not revrange:
3203 if not opts.get('applied') and not revrange:
3189 raise util.Abort(_('no revisions specified'))
3204 raise util.Abort(_('no revisions specified'))
3190 elif opts.get('applied'):
3205 elif opts.get('applied'):
3191 revrange = ('qbase::qtip',) + revrange
3206 revrange = ('qbase::qtip',) + revrange
3192
3207
3193 q = repo.mq
3208 q = repo.mq
3194 if not q.applied:
3209 if not q.applied:
3195 ui.status(_('no patches applied\n'))
3210 ui.status(_('no patches applied\n'))
3196 return 0
3211 return 0
3197
3212
3198 revs = scmutil.revrange(repo, revrange)
3213 revs = scmutil.revrange(repo, revrange)
3199 if repo['.'].rev() in revs and repo[None].files():
3214 if repo['.'].rev() in revs and repo[None].files():
3200 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3215 ui.warn(_('warning: uncommitted changes in the working directory\n'))
3201 # queue.finish may changes phases but leave the responsibility to lock the
3216 # queue.finish may changes phases but leave the responsibility to lock the
3202 # repo to the caller to avoid deadlock with wlock. This command code is
3217 # repo to the caller to avoid deadlock with wlock. This command code is
3203 # responsibility for this locking.
3218 # responsibility for this locking.
3204 lock = repo.lock()
3219 lock = repo.lock()
3205 try:
3220 try:
3206 q.finish(repo, revs)
3221 q.finish(repo, revs)
3207 q.savedirty()
3222 q.savedirty()
3208 finally:
3223 finally:
3209 lock.release()
3224 lock.release()
3210 return 0
3225 return 0
3211
3226
3212 @command("qqueue",
3227 @command("qqueue",
3213 [('l', 'list', False, _('list all available queues')),
3228 [('l', 'list', False, _('list all available queues')),
3214 ('', 'active', False, _('print name of active queue')),
3229 ('', 'active', False, _('print name of active queue')),
3215 ('c', 'create', False, _('create new queue')),
3230 ('c', 'create', False, _('create new queue')),
3216 ('', 'rename', False, _('rename active queue')),
3231 ('', 'rename', False, _('rename active queue')),
3217 ('', 'delete', False, _('delete reference to queue')),
3232 ('', 'delete', False, _('delete reference to queue')),
3218 ('', 'purge', False, _('delete queue, and remove patch dir')),
3233 ('', 'purge', False, _('delete queue, and remove patch dir')),
3219 ],
3234 ],
3220 _('[OPTION] [QUEUE]'))
3235 _('[OPTION] [QUEUE]'))
3221 def qqueue(ui, repo, name=None, **opts):
3236 def qqueue(ui, repo, name=None, **opts):
3222 '''manage multiple patch queues
3237 '''manage multiple patch queues
3223
3238
3224 Supports switching between different patch queues, as well as creating
3239 Supports switching between different patch queues, as well as creating
3225 new patch queues and deleting existing ones.
3240 new patch queues and deleting existing ones.
3226
3241
3227 Omitting a queue name or specifying -l/--list will show you the registered
3242 Omitting a queue name or specifying -l/--list will show you the registered
3228 queues - by default the "normal" patches queue is registered. The currently
3243 queues - by default the "normal" patches queue is registered. The currently
3229 active queue will be marked with "(active)". Specifying --active will print
3244 active queue will be marked with "(active)". Specifying --active will print
3230 only the name of the active queue.
3245 only the name of the active queue.
3231
3246
3232 To create a new queue, use -c/--create. The queue is automatically made
3247 To create a new queue, use -c/--create. The queue is automatically made
3233 active, except in the case where there are applied patches from the
3248 active, except in the case where there are applied patches from the
3234 currently active queue in the repository. Then the queue will only be
3249 currently active queue in the repository. Then the queue will only be
3235 created and switching will fail.
3250 created and switching will fail.
3236
3251
3237 To delete an existing queue, use --delete. You cannot delete the currently
3252 To delete an existing queue, use --delete. You cannot delete the currently
3238 active queue.
3253 active queue.
3239
3254
3240 Returns 0 on success.
3255 Returns 0 on success.
3241 '''
3256 '''
3242 q = repo.mq
3257 q = repo.mq
3243 _defaultqueue = 'patches'
3258 _defaultqueue = 'patches'
3244 _allqueues = 'patches.queues'
3259 _allqueues = 'patches.queues'
3245 _activequeue = 'patches.queue'
3260 _activequeue = 'patches.queue'
3246
3261
3247 def _getcurrent():
3262 def _getcurrent():
3248 cur = os.path.basename(q.path)
3263 cur = os.path.basename(q.path)
3249 if cur.startswith('patches-'):
3264 if cur.startswith('patches-'):
3250 cur = cur[8:]
3265 cur = cur[8:]
3251 return cur
3266 return cur
3252
3267
3253 def _noqueues():
3268 def _noqueues():
3254 try:
3269 try:
3255 fh = repo.opener(_allqueues, 'r')
3270 fh = repo.opener(_allqueues, 'r')
3256 fh.close()
3271 fh.close()
3257 except IOError:
3272 except IOError:
3258 return True
3273 return True
3259
3274
3260 return False
3275 return False
3261
3276
3262 def _getqueues():
3277 def _getqueues():
3263 current = _getcurrent()
3278 current = _getcurrent()
3264
3279
3265 try:
3280 try:
3266 fh = repo.opener(_allqueues, 'r')
3281 fh = repo.opener(_allqueues, 'r')
3267 queues = [queue.strip() for queue in fh if queue.strip()]
3282 queues = [queue.strip() for queue in fh if queue.strip()]
3268 fh.close()
3283 fh.close()
3269 if current not in queues:
3284 if current not in queues:
3270 queues.append(current)
3285 queues.append(current)
3271 except IOError:
3286 except IOError:
3272 queues = [_defaultqueue]
3287 queues = [_defaultqueue]
3273
3288
3274 return sorted(queues)
3289 return sorted(queues)
3275
3290
3276 def _setactive(name):
3291 def _setactive(name):
3277 if q.applied:
3292 if q.applied:
3278 raise util.Abort(_('new queue created, but cannot make active '
3293 raise util.Abort(_('new queue created, but cannot make active '
3279 'as patches are applied'))
3294 'as patches are applied'))
3280 _setactivenocheck(name)
3295 _setactivenocheck(name)
3281
3296
3282 def _setactivenocheck(name):
3297 def _setactivenocheck(name):
3283 fh = repo.opener(_activequeue, 'w')
3298 fh = repo.opener(_activequeue, 'w')
3284 if name != 'patches':
3299 if name != 'patches':
3285 fh.write(name)
3300 fh.write(name)
3286 fh.close()
3301 fh.close()
3287
3302
3288 def _addqueue(name):
3303 def _addqueue(name):
3289 fh = repo.opener(_allqueues, 'a')
3304 fh = repo.opener(_allqueues, 'a')
3290 fh.write('%s\n' % (name,))
3305 fh.write('%s\n' % (name,))
3291 fh.close()
3306 fh.close()
3292
3307
3293 def _queuedir(name):
3308 def _queuedir(name):
3294 if name == 'patches':
3309 if name == 'patches':
3295 return repo.join('patches')
3310 return repo.join('patches')
3296 else:
3311 else:
3297 return repo.join('patches-' + name)
3312 return repo.join('patches-' + name)
3298
3313
3299 def _validname(name):
3314 def _validname(name):
3300 for n in name:
3315 for n in name:
3301 if n in ':\\/.':
3316 if n in ':\\/.':
3302 return False
3317 return False
3303 return True
3318 return True
3304
3319
3305 def _delete(name):
3320 def _delete(name):
3306 if name not in existing:
3321 if name not in existing:
3307 raise util.Abort(_('cannot delete queue that does not exist'))
3322 raise util.Abort(_('cannot delete queue that does not exist'))
3308
3323
3309 current = _getcurrent()
3324 current = _getcurrent()
3310
3325
3311 if name == current:
3326 if name == current:
3312 raise util.Abort(_('cannot delete currently active queue'))
3327 raise util.Abort(_('cannot delete currently active queue'))
3313
3328
3314 fh = repo.opener('patches.queues.new', 'w')
3329 fh = repo.opener('patches.queues.new', 'w')
3315 for queue in existing:
3330 for queue in existing:
3316 if queue == name:
3331 if queue == name:
3317 continue
3332 continue
3318 fh.write('%s\n' % (queue,))
3333 fh.write('%s\n' % (queue,))
3319 fh.close()
3334 fh.close()
3320 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3335 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3321
3336
3322 if not name or opts.get('list') or opts.get('active'):
3337 if not name or opts.get('list') or opts.get('active'):
3323 current = _getcurrent()
3338 current = _getcurrent()
3324 if opts.get('active'):
3339 if opts.get('active'):
3325 ui.write('%s\n' % (current,))
3340 ui.write('%s\n' % (current,))
3326 return
3341 return
3327 for queue in _getqueues():
3342 for queue in _getqueues():
3328 ui.write('%s' % (queue,))
3343 ui.write('%s' % (queue,))
3329 if queue == current and not ui.quiet:
3344 if queue == current and not ui.quiet:
3330 ui.write(_(' (active)\n'))
3345 ui.write(_(' (active)\n'))
3331 else:
3346 else:
3332 ui.write('\n')
3347 ui.write('\n')
3333 return
3348 return
3334
3349
3335 if not _validname(name):
3350 if not _validname(name):
3336 raise util.Abort(
3351 raise util.Abort(
3337 _('invalid queue name, may not contain the characters ":\\/."'))
3352 _('invalid queue name, may not contain the characters ":\\/."'))
3338
3353
3339 existing = _getqueues()
3354 existing = _getqueues()
3340
3355
3341 if opts.get('create'):
3356 if opts.get('create'):
3342 if name in existing:
3357 if name in existing:
3343 raise util.Abort(_('queue "%s" already exists') % name)
3358 raise util.Abort(_('queue "%s" already exists') % name)
3344 if _noqueues():
3359 if _noqueues():
3345 _addqueue(_defaultqueue)
3360 _addqueue(_defaultqueue)
3346 _addqueue(name)
3361 _addqueue(name)
3347 _setactive(name)
3362 _setactive(name)
3348 elif opts.get('rename'):
3363 elif opts.get('rename'):
3349 current = _getcurrent()
3364 current = _getcurrent()
3350 if name == current:
3365 if name == current:
3351 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3366 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3352 if name in existing:
3367 if name in existing:
3353 raise util.Abort(_('queue "%s" already exists') % name)
3368 raise util.Abort(_('queue "%s" already exists') % name)
3354
3369
3355 olddir = _queuedir(current)
3370 olddir = _queuedir(current)
3356 newdir = _queuedir(name)
3371 newdir = _queuedir(name)
3357
3372
3358 if os.path.exists(newdir):
3373 if os.path.exists(newdir):
3359 raise util.Abort(_('non-queue directory "%s" already exists') %
3374 raise util.Abort(_('non-queue directory "%s" already exists') %
3360 newdir)
3375 newdir)
3361
3376
3362 fh = repo.opener('patches.queues.new', 'w')
3377 fh = repo.opener('patches.queues.new', 'w')
3363 for queue in existing:
3378 for queue in existing:
3364 if queue == current:
3379 if queue == current:
3365 fh.write('%s\n' % (name,))
3380 fh.write('%s\n' % (name,))
3366 if os.path.exists(olddir):
3381 if os.path.exists(olddir):
3367 util.rename(olddir, newdir)
3382 util.rename(olddir, newdir)
3368 else:
3383 else:
3369 fh.write('%s\n' % (queue,))
3384 fh.write('%s\n' % (queue,))
3370 fh.close()
3385 fh.close()
3371 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3386 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3372 _setactivenocheck(name)
3387 _setactivenocheck(name)
3373 elif opts.get('delete'):
3388 elif opts.get('delete'):
3374 _delete(name)
3389 _delete(name)
3375 elif opts.get('purge'):
3390 elif opts.get('purge'):
3376 if name in existing:
3391 if name in existing:
3377 _delete(name)
3392 _delete(name)
3378 qdir = _queuedir(name)
3393 qdir = _queuedir(name)
3379 if os.path.exists(qdir):
3394 if os.path.exists(qdir):
3380 shutil.rmtree(qdir)
3395 shutil.rmtree(qdir)
3381 else:
3396 else:
3382 if name not in existing:
3397 if name not in existing:
3383 raise util.Abort(_('use --create to create a new queue'))
3398 raise util.Abort(_('use --create to create a new queue'))
3384 _setactive(name)
3399 _setactive(name)
3385
3400
3386 def mqphasedefaults(repo, roots):
3401 def mqphasedefaults(repo, roots):
3387 """callback used to set mq changeset as secret when no phase data exists"""
3402 """callback used to set mq changeset as secret when no phase data exists"""
3388 if repo.mq.applied:
3403 if repo.mq.applied:
3389 if repo.ui.configbool('mq', 'secret', False):
3404 if repo.ui.configbool('mq', 'secret', False):
3390 mqphase = phases.secret
3405 mqphase = phases.secret
3391 else:
3406 else:
3392 mqphase = phases.draft
3407 mqphase = phases.draft
3393 qbase = repo[repo.mq.applied[0].node]
3408 qbase = repo[repo.mq.applied[0].node]
3394 roots[mqphase].add(qbase.node())
3409 roots[mqphase].add(qbase.node())
3395 return roots
3410 return roots
3396
3411
3397 def reposetup(ui, repo):
3412 def reposetup(ui, repo):
3398 class mqrepo(repo.__class__):
3413 class mqrepo(repo.__class__):
3399 @util.propertycache
3414 @util.propertycache
3400 def mq(self):
3415 def mq(self):
3401 return queue(self.ui, self.path)
3416 return queue(self.ui, self.path)
3402
3417
3403 def abortifwdirpatched(self, errmsg, force=False):
3418 def abortifwdirpatched(self, errmsg, force=False):
3404 if self.mq.applied and not force:
3419 if self.mq.applied and not force:
3405 parents = self.dirstate.parents()
3420 parents = self.dirstate.parents()
3406 patches = [s.node for s in self.mq.applied]
3421 patches = [s.node for s in self.mq.applied]
3407 if parents[0] in patches or parents[1] in patches:
3422 if parents[0] in patches or parents[1] in patches:
3408 raise util.Abort(errmsg)
3423 raise util.Abort(errmsg)
3409
3424
3410 def commit(self, text="", user=None, date=None, match=None,
3425 def commit(self, text="", user=None, date=None, match=None,
3411 force=False, editor=False, extra={}):
3426 force=False, editor=False, extra={}):
3412 self.abortifwdirpatched(
3427 self.abortifwdirpatched(
3413 _('cannot commit over an applied mq patch'),
3428 _('cannot commit over an applied mq patch'),
3414 force)
3429 force)
3415
3430
3416 return super(mqrepo, self).commit(text, user, date, match, force,
3431 return super(mqrepo, self).commit(text, user, date, match, force,
3417 editor, extra)
3432 editor, extra)
3418
3433
3419 def checkpush(self, force, revs):
3434 def checkpush(self, force, revs):
3420 if self.mq.applied and not force:
3435 if self.mq.applied and not force:
3421 outapplied = [e.node for e in self.mq.applied]
3436 outapplied = [e.node for e in self.mq.applied]
3422 if revs:
3437 if revs:
3423 # Assume applied patches have no non-patch descendants and
3438 # Assume applied patches have no non-patch descendants and
3424 # are not on remote already. Filtering any changeset not
3439 # are not on remote already. Filtering any changeset not
3425 # pushed.
3440 # pushed.
3426 heads = set(revs)
3441 heads = set(revs)
3427 for node in reversed(outapplied):
3442 for node in reversed(outapplied):
3428 if node in heads:
3443 if node in heads:
3429 break
3444 break
3430 else:
3445 else:
3431 outapplied.pop()
3446 outapplied.pop()
3432 # looking for pushed and shared changeset
3447 # looking for pushed and shared changeset
3433 for node in outapplied:
3448 for node in outapplied:
3434 if self[node].phase() < phases.secret:
3449 if self[node].phase() < phases.secret:
3435 raise util.Abort(_('source has mq patches applied'))
3450 raise util.Abort(_('source has mq patches applied'))
3436 # no non-secret patches pushed
3451 # no non-secret patches pushed
3437 super(mqrepo, self).checkpush(force, revs)
3452 super(mqrepo, self).checkpush(force, revs)
3438
3453
3439 def _findtags(self):
3454 def _findtags(self):
3440 '''augment tags from base class with patch tags'''
3455 '''augment tags from base class with patch tags'''
3441 result = super(mqrepo, self)._findtags()
3456 result = super(mqrepo, self)._findtags()
3442
3457
3443 q = self.mq
3458 q = self.mq
3444 if not q.applied:
3459 if not q.applied:
3445 return result
3460 return result
3446
3461
3447 mqtags = [(patch.node, patch.name) for patch in q.applied]
3462 mqtags = [(patch.node, patch.name) for patch in q.applied]
3448
3463
3449 try:
3464 try:
3450 # for now ignore filtering business
3465 # for now ignore filtering business
3451 self.unfiltered().changelog.rev(mqtags[-1][0])
3466 self.unfiltered().changelog.rev(mqtags[-1][0])
3452 except error.LookupError:
3467 except error.LookupError:
3453 self.ui.warn(_('mq status file refers to unknown node %s\n')
3468 self.ui.warn(_('mq status file refers to unknown node %s\n')
3454 % short(mqtags[-1][0]))
3469 % short(mqtags[-1][0]))
3455 return result
3470 return result
3456
3471
3457 # do not add fake tags for filtered revisions
3472 # do not add fake tags for filtered revisions
3458 included = self.changelog.hasnode
3473 included = self.changelog.hasnode
3459 mqtags = [mqt for mqt in mqtags if included(mqt[0])]
3474 mqtags = [mqt for mqt in mqtags if included(mqt[0])]
3460 if not mqtags:
3475 if not mqtags:
3461 return result
3476 return result
3462
3477
3463 mqtags.append((mqtags[-1][0], 'qtip'))
3478 mqtags.append((mqtags[-1][0], 'qtip'))
3464 mqtags.append((mqtags[0][0], 'qbase'))
3479 mqtags.append((mqtags[0][0], 'qbase'))
3465 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3480 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3466 tags = result[0]
3481 tags = result[0]
3467 for patch in mqtags:
3482 for patch in mqtags:
3468 if patch[1] in tags:
3483 if patch[1] in tags:
3469 self.ui.warn(_('tag %s overrides mq patch of the same '
3484 self.ui.warn(_('tag %s overrides mq patch of the same '
3470 'name\n') % patch[1])
3485 'name\n') % patch[1])
3471 else:
3486 else:
3472 tags[patch[1]] = patch[0]
3487 tags[patch[1]] = patch[0]
3473
3488
3474 return result
3489 return result
3475
3490
3476 if repo.local():
3491 if repo.local():
3477 repo.__class__ = mqrepo
3492 repo.__class__ = mqrepo
3478
3493
3479 repo._phasedefaults.append(mqphasedefaults)
3494 repo._phasedefaults.append(mqphasedefaults)
3480
3495
3481 def mqimport(orig, ui, repo, *args, **kwargs):
3496 def mqimport(orig, ui, repo, *args, **kwargs):
3482 if (util.safehasattr(repo, 'abortifwdirpatched')
3497 if (util.safehasattr(repo, 'abortifwdirpatched')
3483 and not kwargs.get('no_commit', False)):
3498 and not kwargs.get('no_commit', False)):
3484 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3499 repo.abortifwdirpatched(_('cannot import over an applied patch'),
3485 kwargs.get('force'))
3500 kwargs.get('force'))
3486 return orig(ui, repo, *args, **kwargs)
3501 return orig(ui, repo, *args, **kwargs)
3487
3502
3488 def mqinit(orig, ui, *args, **kwargs):
3503 def mqinit(orig, ui, *args, **kwargs):
3489 mq = kwargs.pop('mq', None)
3504 mq = kwargs.pop('mq', None)
3490
3505
3491 if not mq:
3506 if not mq:
3492 return orig(ui, *args, **kwargs)
3507 return orig(ui, *args, **kwargs)
3493
3508
3494 if args:
3509 if args:
3495 repopath = args[0]
3510 repopath = args[0]
3496 if not hg.islocal(repopath):
3511 if not hg.islocal(repopath):
3497 raise util.Abort(_('only a local queue repository '
3512 raise util.Abort(_('only a local queue repository '
3498 'may be initialized'))
3513 'may be initialized'))
3499 else:
3514 else:
3500 repopath = cmdutil.findrepo(os.getcwd())
3515 repopath = cmdutil.findrepo(os.getcwd())
3501 if not repopath:
3516 if not repopath:
3502 raise util.Abort(_('there is no Mercurial repository here '
3517 raise util.Abort(_('there is no Mercurial repository here '
3503 '(.hg not found)'))
3518 '(.hg not found)'))
3504 repo = hg.repository(ui, repopath)
3519 repo = hg.repository(ui, repopath)
3505 return qinit(ui, repo, True)
3520 return qinit(ui, repo, True)
3506
3521
3507 def mqcommand(orig, ui, repo, *args, **kwargs):
3522 def mqcommand(orig, ui, repo, *args, **kwargs):
3508 """Add --mq option to operate on patch repository instead of main"""
3523 """Add --mq option to operate on patch repository instead of main"""
3509
3524
3510 # some commands do not like getting unknown options
3525 # some commands do not like getting unknown options
3511 mq = kwargs.pop('mq', None)
3526 mq = kwargs.pop('mq', None)
3512
3527
3513 if not mq:
3528 if not mq:
3514 return orig(ui, repo, *args, **kwargs)
3529 return orig(ui, repo, *args, **kwargs)
3515
3530
3516 q = repo.mq
3531 q = repo.mq
3517 r = q.qrepo()
3532 r = q.qrepo()
3518 if not r:
3533 if not r:
3519 raise util.Abort(_('no queue repository'))
3534 raise util.Abort(_('no queue repository'))
3520 return orig(r.ui, r, *args, **kwargs)
3535 return orig(r.ui, r, *args, **kwargs)
3521
3536
3522 def summary(orig, ui, repo, *args, **kwargs):
3537 def summary(orig, ui, repo, *args, **kwargs):
3523 r = orig(ui, repo, *args, **kwargs)
3538 r = orig(ui, repo, *args, **kwargs)
3524 q = repo.mq
3539 q = repo.mq
3525 m = []
3540 m = []
3526 a, u = len(q.applied), len(q.unapplied(repo))
3541 a, u = len(q.applied), len(q.unapplied(repo))
3527 if a:
3542 if a:
3528 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3543 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3529 if u:
3544 if u:
3530 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3545 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3531 if m:
3546 if m:
3532 # i18n: column positioning for "hg summary"
3547 # i18n: column positioning for "hg summary"
3533 ui.write(_("mq: %s\n") % ', '.join(m))
3548 ui.write(_("mq: %s\n") % ', '.join(m))
3534 else:
3549 else:
3535 # i18n: column positioning for "hg summary"
3550 # i18n: column positioning for "hg summary"
3536 ui.note(_("mq: (empty queue)\n"))
3551 ui.note(_("mq: (empty queue)\n"))
3537 return r
3552 return r
3538
3553
3539 def revsetmq(repo, subset, x):
3554 def revsetmq(repo, subset, x):
3540 """``mq()``
3555 """``mq()``
3541 Changesets managed by MQ.
3556 Changesets managed by MQ.
3542 """
3557 """
3543 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3558 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3544 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3559 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3545 return [r for r in subset if r in applied]
3560 return [r for r in subset if r in applied]
3546
3561
3547 # tell hggettext to extract docstrings from these functions:
3562 # tell hggettext to extract docstrings from these functions:
3548 i18nfunctions = [revsetmq]
3563 i18nfunctions = [revsetmq]
3549
3564
3550 def extsetup(ui):
3565 def extsetup(ui):
3551 # Ensure mq wrappers are called first, regardless of extension load order by
3566 # Ensure mq wrappers are called first, regardless of extension load order by
3552 # NOT wrapping in uisetup() and instead deferring to init stage two here.
3567 # NOT wrapping in uisetup() and instead deferring to init stage two here.
3553 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3568 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3554
3569
3555 extensions.wrapcommand(commands.table, 'import', mqimport)
3570 extensions.wrapcommand(commands.table, 'import', mqimport)
3556 extensions.wrapcommand(commands.table, 'summary', summary)
3571 extensions.wrapcommand(commands.table, 'summary', summary)
3557
3572
3558 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3573 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3559 entry[1].extend(mqopt)
3574 entry[1].extend(mqopt)
3560
3575
3561 nowrap = set(commands.norepo.split(" "))
3576 nowrap = set(commands.norepo.split(" "))
3562
3577
3563 def dotable(cmdtable):
3578 def dotable(cmdtable):
3564 for cmd in cmdtable.keys():
3579 for cmd in cmdtable.keys():
3565 cmd = cmdutil.parsealiases(cmd)[0]
3580 cmd = cmdutil.parsealiases(cmd)[0]
3566 if cmd in nowrap:
3581 if cmd in nowrap:
3567 continue
3582 continue
3568 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3583 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3569 entry[1].extend(mqopt)
3584 entry[1].extend(mqopt)
3570
3585
3571 dotable(commands.table)
3586 dotable(commands.table)
3572
3587
3573 for extname, extmodule in extensions.extensions():
3588 for extname, extmodule in extensions.extensions():
3574 if extmodule.__file__ != __file__:
3589 if extmodule.__file__ != __file__:
3575 dotable(getattr(extmodule, 'cmdtable', {}))
3590 dotable(getattr(extmodule, 'cmdtable', {}))
3576
3591
3577 revset.symbols['mq'] = revsetmq
3592 revset.symbols['mq'] = revsetmq
3578
3593
3579 colortable = {'qguard.negative': 'red',
3594 colortable = {'qguard.negative': 'red',
3580 'qguard.positive': 'yellow',
3595 'qguard.positive': 'yellow',
3581 'qguard.unguarded': 'green',
3596 'qguard.unguarded': 'green',
3582 'qseries.applied': 'blue bold underline',
3597 'qseries.applied': 'blue bold underline',
3583 'qseries.guarded': 'black bold',
3598 'qseries.guarded': 'black bold',
3584 'qseries.missing': 'red bold',
3599 'qseries.missing': 'red bold',
3585 'qseries.unapplied': 'black bold'}
3600 'qseries.unapplied': 'black bold'}
3586
3601
3587 commands.inferrepo += " qnew qrefresh qdiff qcommit"
3602 commands.inferrepo += " qnew qrefresh qdiff qcommit"
@@ -1,820 +1,825
1 # dirstate.py - working directory tracking for mercurial
1 # dirstate.py - working directory tracking for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 import errno
7 import errno
8
8
9 from node import nullid
9 from node import nullid
10 from i18n import _
10 from i18n import _
11 import scmutil, util, ignore, osutil, parsers, encoding
11 import scmutil, util, ignore, osutil, parsers, encoding
12 import os, stat, errno, gc
12 import os, stat, errno, gc
13
13
14 propertycache = util.propertycache
14 propertycache = util.propertycache
15 filecache = scmutil.filecache
15 filecache = scmutil.filecache
16 _rangemask = 0x7fffffff
16 _rangemask = 0x7fffffff
17
17
18 class repocache(filecache):
18 class repocache(filecache):
19 """filecache for files in .hg/"""
19 """filecache for files in .hg/"""
20 def join(self, obj, fname):
20 def join(self, obj, fname):
21 return obj._opener.join(fname)
21 return obj._opener.join(fname)
22
22
23 class rootcache(filecache):
23 class rootcache(filecache):
24 """filecache for files in the repository root"""
24 """filecache for files in the repository root"""
25 def join(self, obj, fname):
25 def join(self, obj, fname):
26 return obj._join(fname)
26 return obj._join(fname)
27
27
28 def _finddirs(path):
28 def _finddirs(path):
29 pos = path.rfind('/')
29 pos = path.rfind('/')
30 while pos != -1:
30 while pos != -1:
31 yield path[:pos]
31 yield path[:pos]
32 pos = path.rfind('/', 0, pos)
32 pos = path.rfind('/', 0, pos)
33
33
34 def _incdirs(dirs, path):
34 def _incdirs(dirs, path):
35 for base in _finddirs(path):
35 for base in _finddirs(path):
36 if base in dirs:
36 if base in dirs:
37 dirs[base] += 1
37 dirs[base] += 1
38 return
38 return
39 dirs[base] = 1
39 dirs[base] = 1
40
40
41 def _decdirs(dirs, path):
41 def _decdirs(dirs, path):
42 for base in _finddirs(path):
42 for base in _finddirs(path):
43 if dirs[base] > 1:
43 if dirs[base] > 1:
44 dirs[base] -= 1
44 dirs[base] -= 1
45 return
45 return
46 del dirs[base]
46 del dirs[base]
47
47
48 class dirstate(object):
48 class dirstate(object):
49
49
50 def __init__(self, opener, ui, root, validate):
50 def __init__(self, opener, ui, root, validate):
51 '''Create a new dirstate object.
51 '''Create a new dirstate object.
52
52
53 opener is an open()-like callable that can be used to open the
53 opener is an open()-like callable that can be used to open the
54 dirstate file; root is the root of the directory tracked by
54 dirstate file; root is the root of the directory tracked by
55 the dirstate.
55 the dirstate.
56 '''
56 '''
57 self._opener = opener
57 self._opener = opener
58 self._validate = validate
58 self._validate = validate
59 self._root = root
59 self._root = root
60 self._rootdir = os.path.join(root, '')
60 self._rootdir = os.path.join(root, '')
61 self._dirty = False
61 self._dirty = False
62 self._dirtypl = False
62 self._dirtypl = False
63 self._lastnormaltime = 0
63 self._lastnormaltime = 0
64 self._ui = ui
64 self._ui = ui
65 self._filecache = {}
65 self._filecache = {}
66
66
67 @propertycache
67 @propertycache
68 def _map(self):
68 def _map(self):
69 '''Return the dirstate contents as a map from filename to
69 '''Return the dirstate contents as a map from filename to
70 (state, mode, size, time).'''
70 (state, mode, size, time).'''
71 self._read()
71 self._read()
72 return self._map
72 return self._map
73
73
74 @propertycache
74 @propertycache
75 def _copymap(self):
75 def _copymap(self):
76 self._read()
76 self._read()
77 return self._copymap
77 return self._copymap
78
78
79 @propertycache
79 @propertycache
80 def _foldmap(self):
80 def _foldmap(self):
81 f = {}
81 f = {}
82 for name in self._map:
82 for name in self._map:
83 f[util.normcase(name)] = name
83 f[util.normcase(name)] = name
84 for name in self._dirs:
84 for name in self._dirs:
85 f[util.normcase(name)] = name
85 f[util.normcase(name)] = name
86 f['.'] = '.' # prevents useless util.fspath() invocation
86 f['.'] = '.' # prevents useless util.fspath() invocation
87 return f
87 return f
88
88
89 @repocache('branch')
89 @repocache('branch')
90 def _branch(self):
90 def _branch(self):
91 try:
91 try:
92 return self._opener.read("branch").strip() or "default"
92 return self._opener.read("branch").strip() or "default"
93 except IOError, inst:
93 except IOError, inst:
94 if inst.errno != errno.ENOENT:
94 if inst.errno != errno.ENOENT:
95 raise
95 raise
96 return "default"
96 return "default"
97
97
98 @propertycache
98 @propertycache
99 def _pl(self):
99 def _pl(self):
100 try:
100 try:
101 fp = self._opener("dirstate")
101 fp = self._opener("dirstate")
102 st = fp.read(40)
102 st = fp.read(40)
103 fp.close()
103 fp.close()
104 l = len(st)
104 l = len(st)
105 if l == 40:
105 if l == 40:
106 return st[:20], st[20:40]
106 return st[:20], st[20:40]
107 elif l > 0 and l < 40:
107 elif l > 0 and l < 40:
108 raise util.Abort(_('working directory state appears damaged!'))
108 raise util.Abort(_('working directory state appears damaged!'))
109 except IOError, err:
109 except IOError, err:
110 if err.errno != errno.ENOENT:
110 if err.errno != errno.ENOENT:
111 raise
111 raise
112 return [nullid, nullid]
112 return [nullid, nullid]
113
113
114 @propertycache
114 @propertycache
115 def _dirs(self):
115 def _dirs(self):
116 dirs = {}
116 dirs = {}
117 for f, s in self._map.iteritems():
117 for f, s in self._map.iteritems():
118 if s[0] != 'r':
118 if s[0] != 'r':
119 _incdirs(dirs, f)
119 _incdirs(dirs, f)
120 return dirs
120 return dirs
121
121
122 def dirs(self):
122 def dirs(self):
123 return self._dirs
123 return self._dirs
124
124
125 @rootcache('.hgignore')
125 @rootcache('.hgignore')
126 def _ignore(self):
126 def _ignore(self):
127 files = [self._join('.hgignore')]
127 files = [self._join('.hgignore')]
128 for name, path in self._ui.configitems("ui"):
128 for name, path in self._ui.configitems("ui"):
129 if name == 'ignore' or name.startswith('ignore.'):
129 if name == 'ignore' or name.startswith('ignore.'):
130 files.append(util.expandpath(path))
130 files.append(util.expandpath(path))
131 return ignore.ignore(self._root, files, self._ui.warn)
131 return ignore.ignore(self._root, files, self._ui.warn)
132
132
133 @propertycache
133 @propertycache
134 def _slash(self):
134 def _slash(self):
135 return self._ui.configbool('ui', 'slash') and os.sep != '/'
135 return self._ui.configbool('ui', 'slash') and os.sep != '/'
136
136
137 @propertycache
137 @propertycache
138 def _checklink(self):
138 def _checklink(self):
139 return util.checklink(self._root)
139 return util.checklink(self._root)
140
140
141 @propertycache
141 @propertycache
142 def _checkexec(self):
142 def _checkexec(self):
143 return util.checkexec(self._root)
143 return util.checkexec(self._root)
144
144
145 @propertycache
145 @propertycache
146 def _checkcase(self):
146 def _checkcase(self):
147 return not util.checkcase(self._join('.hg'))
147 return not util.checkcase(self._join('.hg'))
148
148
149 def _join(self, f):
149 def _join(self, f):
150 # much faster than os.path.join()
150 # much faster than os.path.join()
151 # it's safe because f is always a relative path
151 # it's safe because f is always a relative path
152 return self._rootdir + f
152 return self._rootdir + f
153
153
154 def flagfunc(self, buildfallback):
154 def flagfunc(self, buildfallback):
155 if self._checklink and self._checkexec:
155 if self._checklink and self._checkexec:
156 def f(x):
156 def f(x):
157 p = self._join(x)
157 p = self._join(x)
158 if os.path.islink(p):
158 if os.path.islink(p):
159 return 'l'
159 return 'l'
160 if util.isexec(p):
160 if util.isexec(p):
161 return 'x'
161 return 'x'
162 return ''
162 return ''
163 return f
163 return f
164
164
165 fallback = buildfallback()
165 fallback = buildfallback()
166 if self._checklink:
166 if self._checklink:
167 def f(x):
167 def f(x):
168 if os.path.islink(self._join(x)):
168 if os.path.islink(self._join(x)):
169 return 'l'
169 return 'l'
170 if 'x' in fallback(x):
170 if 'x' in fallback(x):
171 return 'x'
171 return 'x'
172 return ''
172 return ''
173 return f
173 return f
174 if self._checkexec:
174 if self._checkexec:
175 def f(x):
175 def f(x):
176 if 'l' in fallback(x):
176 if 'l' in fallback(x):
177 return 'l'
177 return 'l'
178 if util.isexec(self._join(x)):
178 if util.isexec(self._join(x)):
179 return 'x'
179 return 'x'
180 return ''
180 return ''
181 return f
181 return f
182 else:
182 else:
183 return fallback
183 return fallback
184
184
185 def getcwd(self):
185 def getcwd(self):
186 cwd = os.getcwd()
186 cwd = os.getcwd()
187 if cwd == self._root:
187 if cwd == self._root:
188 return ''
188 return ''
189 # self._root ends with a path separator if self._root is '/' or 'C:\'
189 # self._root ends with a path separator if self._root is '/' or 'C:\'
190 rootsep = self._root
190 rootsep = self._root
191 if not util.endswithsep(rootsep):
191 if not util.endswithsep(rootsep):
192 rootsep += os.sep
192 rootsep += os.sep
193 if cwd.startswith(rootsep):
193 if cwd.startswith(rootsep):
194 return cwd[len(rootsep):]
194 return cwd[len(rootsep):]
195 else:
195 else:
196 # we're outside the repo. return an absolute path.
196 # we're outside the repo. return an absolute path.
197 return cwd
197 return cwd
198
198
199 def pathto(self, f, cwd=None):
199 def pathto(self, f, cwd=None):
200 if cwd is None:
200 if cwd is None:
201 cwd = self.getcwd()
201 cwd = self.getcwd()
202 path = util.pathto(self._root, cwd, f)
202 path = util.pathto(self._root, cwd, f)
203 if self._slash:
203 if self._slash:
204 return util.normpath(path)
204 return util.normpath(path)
205 return path
205 return path
206
206
207 def __getitem__(self, key):
207 def __getitem__(self, key):
208 '''Return the current state of key (a filename) in the dirstate.
208 '''Return the current state of key (a filename) in the dirstate.
209
209
210 States are:
210 States are:
211 n normal
211 n normal
212 m needs merging
212 m needs merging
213 r marked for removal
213 r marked for removal
214 a marked for addition
214 a marked for addition
215 ? not tracked
215 ? not tracked
216 '''
216 '''
217 return self._map.get(key, ("?",))[0]
217 return self._map.get(key, ("?",))[0]
218
218
219 def __contains__(self, key):
219 def __contains__(self, key):
220 return key in self._map
220 return key in self._map
221
221
222 def __iter__(self):
222 def __iter__(self):
223 for x in sorted(self._map):
223 for x in sorted(self._map):
224 yield x
224 yield x
225
225
226 def parents(self):
226 def parents(self):
227 return [self._validate(p) for p in self._pl]
227 return [self._validate(p) for p in self._pl]
228
228
229 def p1(self):
229 def p1(self):
230 return self._validate(self._pl[0])
230 return self._validate(self._pl[0])
231
231
232 def p2(self):
232 def p2(self):
233 return self._validate(self._pl[1])
233 return self._validate(self._pl[1])
234
234
235 def branch(self):
235 def branch(self):
236 return encoding.tolocal(self._branch)
236 return encoding.tolocal(self._branch)
237
237
238 def setparents(self, p1, p2=nullid):
238 def setparents(self, p1, p2=nullid):
239 """Set dirstate parents to p1 and p2.
239 """Set dirstate parents to p1 and p2.
240
240
241 When moving from two parents to one, 'm' merged entries a
241 When moving from two parents to one, 'm' merged entries a
242 adjusted to normal and previous copy records discarded and
242 adjusted to normal and previous copy records discarded and
243 returned by the call.
243 returned by the call.
244
244
245 See localrepo.setparents()
245 See localrepo.setparents()
246 """
246 """
247 self._dirty = self._dirtypl = True
247 self._dirty = self._dirtypl = True
248 oldp2 = self._pl[1]
248 oldp2 = self._pl[1]
249 self._pl = p1, p2
249 self._pl = p1, p2
250 copies = {}
250 copies = {}
251 if oldp2 != nullid and p2 == nullid:
251 if oldp2 != nullid and p2 == nullid:
252 # Discard 'm' markers when moving away from a merge state
252 # Discard 'm' markers when moving away from a merge state
253 for f, s in self._map.iteritems():
253 for f, s in self._map.iteritems():
254 if s[0] == 'm':
254 if s[0] == 'm':
255 if f in self._copymap:
255 if f in self._copymap:
256 copies[f] = self._copymap[f]
256 copies[f] = self._copymap[f]
257 self.normallookup(f)
257 self.normallookup(f)
258 return copies
258 return copies
259
259
260 def setbranch(self, branch):
260 def setbranch(self, branch):
261 self._branch = encoding.fromlocal(branch)
261 self._branch = encoding.fromlocal(branch)
262 f = self._opener('branch', 'w', atomictemp=True)
262 f = self._opener('branch', 'w', atomictemp=True)
263 try:
263 try:
264 f.write(self._branch + '\n')
264 f.write(self._branch + '\n')
265 f.close()
265 f.close()
266
266
267 # make sure filecache has the correct stat info for _branch after
267 # make sure filecache has the correct stat info for _branch after
268 # replacing the underlying file
268 # replacing the underlying file
269 ce = self._filecache['_branch']
269 ce = self._filecache['_branch']
270 if ce:
270 if ce:
271 ce.refresh()
271 ce.refresh()
272 except: # re-raises
272 except: # re-raises
273 f.discard()
273 f.discard()
274 raise
274 raise
275
275
276 def _read(self):
276 def _read(self):
277 self._map = {}
277 self._map = {}
278 self._copymap = {}
278 self._copymap = {}
279 try:
279 try:
280 st = self._opener.read("dirstate")
280 st = self._opener.read("dirstate")
281 except IOError, err:
281 except IOError, err:
282 if err.errno != errno.ENOENT:
282 if err.errno != errno.ENOENT:
283 raise
283 raise
284 return
284 return
285 if not st:
285 if not st:
286 return
286 return
287
287
288 # Python's garbage collector triggers a GC each time a certain number
288 # Python's garbage collector triggers a GC each time a certain number
289 # of container objects (the number being defined by
289 # of container objects (the number being defined by
290 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
290 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
291 # for each file in the dirstate. The C version then immediately marks
291 # for each file in the dirstate. The C version then immediately marks
292 # them as not to be tracked by the collector. However, this has no
292 # them as not to be tracked by the collector. However, this has no
293 # effect on when GCs are triggered, only on what objects the GC looks
293 # effect on when GCs are triggered, only on what objects the GC looks
294 # into. This means that O(number of files) GCs are unavoidable.
294 # into. This means that O(number of files) GCs are unavoidable.
295 # Depending on when in the process's lifetime the dirstate is parsed,
295 # Depending on when in the process's lifetime the dirstate is parsed,
296 # this can get very expensive. As a workaround, disable GC while
296 # this can get very expensive. As a workaround, disable GC while
297 # parsing the dirstate.
297 # parsing the dirstate.
298 gcenabled = gc.isenabled()
298 gcenabled = gc.isenabled()
299 gc.disable()
299 gc.disable()
300 try:
300 try:
301 p = parsers.parse_dirstate(self._map, self._copymap, st)
301 p = parsers.parse_dirstate(self._map, self._copymap, st)
302 finally:
302 finally:
303 if gcenabled:
303 if gcenabled:
304 gc.enable()
304 gc.enable()
305 if not self._dirtypl:
305 if not self._dirtypl:
306 self._pl = p
306 self._pl = p
307
307
308 def invalidate(self):
308 def invalidate(self):
309 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
309 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
310 "_ignore"):
310 "_ignore"):
311 if a in self.__dict__:
311 if a in self.__dict__:
312 delattr(self, a)
312 delattr(self, a)
313 self._lastnormaltime = 0
313 self._lastnormaltime = 0
314 self._dirty = False
314 self._dirty = False
315
315
316 def copy(self, source, dest):
316 def copy(self, source, dest):
317 """Mark dest as a copy of source. Unmark dest if source is None."""
317 """Mark dest as a copy of source. Unmark dest if source is None."""
318 if source == dest:
318 if source == dest:
319 return
319 return
320 self._dirty = True
320 self._dirty = True
321 if source is not None:
321 if source is not None:
322 self._copymap[dest] = source
322 self._copymap[dest] = source
323 elif dest in self._copymap:
323 elif dest in self._copymap:
324 del self._copymap[dest]
324 del self._copymap[dest]
325
325
326 def copied(self, file):
326 def copied(self, file):
327 return self._copymap.get(file, None)
327 return self._copymap.get(file, None)
328
328
329 def copies(self):
329 def copies(self):
330 return self._copymap
330 return self._copymap
331
331
332 def _droppath(self, f):
332 def _droppath(self, f):
333 if self[f] not in "?r" and "_dirs" in self.__dict__:
333 if self[f] not in "?r" and "_dirs" in self.__dict__:
334 _decdirs(self._dirs, f)
334 _decdirs(self._dirs, f)
335
335
336 def _addpath(self, f, state, mode, size, mtime):
336 def _addpath(self, f, state, mode, size, mtime):
337 oldstate = self[f]
337 oldstate = self[f]
338 if state == 'a' or oldstate == 'r':
338 if state == 'a' or oldstate == 'r':
339 scmutil.checkfilename(f)
339 scmutil.checkfilename(f)
340 if f in self._dirs:
340 if f in self._dirs:
341 raise util.Abort(_('directory %r already in dirstate') % f)
341 raise util.Abort(_('directory %r already in dirstate') % f)
342 # shadows
342 # shadows
343 for d in _finddirs(f):
343 for d in _finddirs(f):
344 if d in self._dirs:
344 if d in self._dirs:
345 break
345 break
346 if d in self._map and self[d] != 'r':
346 if d in self._map and self[d] != 'r':
347 raise util.Abort(
347 raise util.Abort(
348 _('file %r in dirstate clashes with %r') % (d, f))
348 _('file %r in dirstate clashes with %r') % (d, f))
349 if oldstate in "?r" and "_dirs" in self.__dict__:
349 if oldstate in "?r" and "_dirs" in self.__dict__:
350 _incdirs(self._dirs, f)
350 _incdirs(self._dirs, f)
351 self._dirty = True
351 self._dirty = True
352 self._map[f] = (state, mode, size, mtime)
352 self._map[f] = (state, mode, size, mtime)
353
353
354 def normal(self, f):
354 def normal(self, f):
355 '''Mark a file normal and clean.'''
355 '''Mark a file normal and clean.'''
356 s = os.lstat(self._join(f))
356 s = os.lstat(self._join(f))
357 mtime = int(s.st_mtime)
357 mtime = int(s.st_mtime)
358 self._addpath(f, 'n', s.st_mode,
358 self._addpath(f, 'n', s.st_mode,
359 s.st_size & _rangemask, mtime & _rangemask)
359 s.st_size & _rangemask, mtime & _rangemask)
360 if f in self._copymap:
360 if f in self._copymap:
361 del self._copymap[f]
361 del self._copymap[f]
362 if mtime > self._lastnormaltime:
362 if mtime > self._lastnormaltime:
363 # Remember the most recent modification timeslot for status(),
363 # Remember the most recent modification timeslot for status(),
364 # to make sure we won't miss future size-preserving file content
364 # to make sure we won't miss future size-preserving file content
365 # modifications that happen within the same timeslot.
365 # modifications that happen within the same timeslot.
366 self._lastnormaltime = mtime
366 self._lastnormaltime = mtime
367
367
368 def normallookup(self, f):
368 def normallookup(self, f):
369 '''Mark a file normal, but possibly dirty.'''
369 '''Mark a file normal, but possibly dirty.'''
370 if self._pl[1] != nullid and f in self._map:
370 if self._pl[1] != nullid and f in self._map:
371 # if there is a merge going on and the file was either
371 # if there is a merge going on and the file was either
372 # in state 'm' (-1) or coming from other parent (-2) before
372 # in state 'm' (-1) or coming from other parent (-2) before
373 # being removed, restore that state.
373 # being removed, restore that state.
374 entry = self._map[f]
374 entry = self._map[f]
375 if entry[0] == 'r' and entry[2] in (-1, -2):
375 if entry[0] == 'r' and entry[2] in (-1, -2):
376 source = self._copymap.get(f)
376 source = self._copymap.get(f)
377 if entry[2] == -1:
377 if entry[2] == -1:
378 self.merge(f)
378 self.merge(f)
379 elif entry[2] == -2:
379 elif entry[2] == -2:
380 self.otherparent(f)
380 self.otherparent(f)
381 if source:
381 if source:
382 self.copy(source, f)
382 self.copy(source, f)
383 return
383 return
384 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
384 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
385 return
385 return
386 self._addpath(f, 'n', 0, -1, -1)
386 self._addpath(f, 'n', 0, -1, -1)
387 if f in self._copymap:
387 if f in self._copymap:
388 del self._copymap[f]
388 del self._copymap[f]
389
389
390 def otherparent(self, f):
390 def otherparent(self, f):
391 '''Mark as coming from the other parent, always dirty.'''
391 '''Mark as coming from the other parent, always dirty.'''
392 if self._pl[1] == nullid:
392 if self._pl[1] == nullid:
393 raise util.Abort(_("setting %r to other parent "
393 raise util.Abort(_("setting %r to other parent "
394 "only allowed in merges") % f)
394 "only allowed in merges") % f)
395 self._addpath(f, 'n', 0, -2, -1)
395 self._addpath(f, 'n', 0, -2, -1)
396 if f in self._copymap:
396 if f in self._copymap:
397 del self._copymap[f]
397 del self._copymap[f]
398
398
399 def add(self, f):
399 def add(self, f):
400 '''Mark a file added.'''
400 '''Mark a file added.'''
401 self._addpath(f, 'a', 0, -1, -1)
401 self._addpath(f, 'a', 0, -1, -1)
402 if f in self._copymap:
402 if f in self._copymap:
403 del self._copymap[f]
403 del self._copymap[f]
404
404
405 def remove(self, f):
405 def remove(self, f):
406 '''Mark a file removed.'''
406 '''Mark a file removed.'''
407 self._dirty = True
407 self._dirty = True
408 self._droppath(f)
408 self._droppath(f)
409 size = 0
409 size = 0
410 if self._pl[1] != nullid and f in self._map:
410 if self._pl[1] != nullid and f in self._map:
411 # backup the previous state
411 # backup the previous state
412 entry = self._map[f]
412 entry = self._map[f]
413 if entry[0] == 'm': # merge
413 if entry[0] == 'm': # merge
414 size = -1
414 size = -1
415 elif entry[0] == 'n' and entry[2] == -2: # other parent
415 elif entry[0] == 'n' and entry[2] == -2: # other parent
416 size = -2
416 size = -2
417 self._map[f] = ('r', 0, size, 0)
417 self._map[f] = ('r', 0, size, 0)
418 if size == 0 and f in self._copymap:
418 if size == 0 and f in self._copymap:
419 del self._copymap[f]
419 del self._copymap[f]
420
420
421 def merge(self, f):
421 def merge(self, f):
422 '''Mark a file merged.'''
422 '''Mark a file merged.'''
423 if self._pl[1] == nullid:
423 if self._pl[1] == nullid:
424 return self.normallookup(f)
424 return self.normallookup(f)
425 s = os.lstat(self._join(f))
425 s = os.lstat(self._join(f))
426 self._addpath(f, 'm', s.st_mode,
426 self._addpath(f, 'm', s.st_mode,
427 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
427 s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
428 if f in self._copymap:
428 if f in self._copymap:
429 del self._copymap[f]
429 del self._copymap[f]
430
430
431 def drop(self, f):
431 def drop(self, f):
432 '''Drop a file from the dirstate'''
432 '''Drop a file from the dirstate'''
433 if f in self._map:
433 if f in self._map:
434 self._dirty = True
434 self._dirty = True
435 self._droppath(f)
435 self._droppath(f)
436 del self._map[f]
436 del self._map[f]
437
437
438 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
438 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
439 normed = util.normcase(path)
439 normed = util.normcase(path)
440 folded = self._foldmap.get(normed, None)
440 folded = self._foldmap.get(normed, None)
441 if folded is None:
441 if folded is None:
442 if isknown:
442 if isknown:
443 folded = path
443 folded = path
444 else:
444 else:
445 if exists is None:
445 if exists is None:
446 exists = os.path.lexists(os.path.join(self._root, path))
446 exists = os.path.lexists(os.path.join(self._root, path))
447 if not exists:
447 if not exists:
448 # Maybe a path component exists
448 # Maybe a path component exists
449 if not ignoremissing and '/' in path:
449 if not ignoremissing and '/' in path:
450 d, f = path.rsplit('/', 1)
450 d, f = path.rsplit('/', 1)
451 d = self._normalize(d, isknown, ignoremissing, None)
451 d = self._normalize(d, isknown, ignoremissing, None)
452 folded = d + "/" + f
452 folded = d + "/" + f
453 else:
453 else:
454 # No path components, preserve original case
454 # No path components, preserve original case
455 folded = path
455 folded = path
456 else:
456 else:
457 # recursively normalize leading directory components
457 # recursively normalize leading directory components
458 # against dirstate
458 # against dirstate
459 if '/' in normed:
459 if '/' in normed:
460 d, f = normed.rsplit('/', 1)
460 d, f = normed.rsplit('/', 1)
461 d = self._normalize(d, isknown, ignoremissing, True)
461 d = self._normalize(d, isknown, ignoremissing, True)
462 r = self._root + "/" + d
462 r = self._root + "/" + d
463 folded = d + "/" + util.fspath(f, r)
463 folded = d + "/" + util.fspath(f, r)
464 else:
464 else:
465 folded = util.fspath(normed, self._root)
465 folded = util.fspath(normed, self._root)
466 self._foldmap[normed] = folded
466 self._foldmap[normed] = folded
467
467
468 return folded
468 return folded
469
469
470 def normalize(self, path, isknown=False, ignoremissing=False):
470 def normalize(self, path, isknown=False, ignoremissing=False):
471 '''
471 '''
472 normalize the case of a pathname when on a casefolding filesystem
472 normalize the case of a pathname when on a casefolding filesystem
473
473
474 isknown specifies whether the filename came from walking the
474 isknown specifies whether the filename came from walking the
475 disk, to avoid extra filesystem access.
475 disk, to avoid extra filesystem access.
476
476
477 If ignoremissing is True, missing path are returned
477 If ignoremissing is True, missing path are returned
478 unchanged. Otherwise, we try harder to normalize possibly
478 unchanged. Otherwise, we try harder to normalize possibly
479 existing path components.
479 existing path components.
480
480
481 The normalized case is determined based on the following precedence:
481 The normalized case is determined based on the following precedence:
482
482
483 - version of name already stored in the dirstate
483 - version of name already stored in the dirstate
484 - version of name stored on disk
484 - version of name stored on disk
485 - version provided via command arguments
485 - version provided via command arguments
486 '''
486 '''
487
487
488 if self._checkcase:
488 if self._checkcase:
489 return self._normalize(path, isknown, ignoremissing)
489 return self._normalize(path, isknown, ignoremissing)
490 return path
490 return path
491
491
492 def clear(self):
492 def clear(self):
493 self._map = {}
493 self._map = {}
494 if "_dirs" in self.__dict__:
494 if "_dirs" in self.__dict__:
495 delattr(self, "_dirs")
495 delattr(self, "_dirs")
496 self._copymap = {}
496 self._copymap = {}
497 self._pl = [nullid, nullid]
497 self._pl = [nullid, nullid]
498 self._lastnormaltime = 0
498 self._lastnormaltime = 0
499 self._dirty = True
499 self._dirty = True
500
500
501 def rebuild(self, parent, files):
501 def rebuild(self, parent, allfiles, changedfiles=None):
502 changedfiles = changedfiles or allfiles
503 oldmap = self._map
502 self.clear()
504 self.clear()
503 for f in files:
505 for f in allfiles:
504 if 'x' in files.flags(f):
506 if f not in changedfiles:
505 self._map[f] = ('n', 0777, -1, 0)
507 self._map[f] = oldmap[f]
506 else:
508 else:
507 self._map[f] = ('n', 0666, -1, 0)
509 if 'x' in allfiles.flags(f):
510 self._map[f] = ('n', 0777, -1, 0)
511 else:
512 self._map[f] = ('n', 0666, -1, 0)
508 self._pl = (parent, nullid)
513 self._pl = (parent, nullid)
509 self._dirty = True
514 self._dirty = True
510
515
511 def write(self):
516 def write(self):
512 if not self._dirty:
517 if not self._dirty:
513 return
518 return
514 st = self._opener("dirstate", "w", atomictemp=True)
519 st = self._opener("dirstate", "w", atomictemp=True)
515
520
516 def finish(s):
521 def finish(s):
517 st.write(s)
522 st.write(s)
518 st.close()
523 st.close()
519 self._lastnormaltime = 0
524 self._lastnormaltime = 0
520 self._dirty = self._dirtypl = False
525 self._dirty = self._dirtypl = False
521
526
522 # use the modification time of the newly created temporary file as the
527 # use the modification time of the newly created temporary file as the
523 # filesystem's notion of 'now'
528 # filesystem's notion of 'now'
524 now = util.fstat(st).st_mtime
529 now = util.fstat(st).st_mtime
525 finish(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
530 finish(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
526
531
527 def _dirignore(self, f):
532 def _dirignore(self, f):
528 if f == '.':
533 if f == '.':
529 return False
534 return False
530 if self._ignore(f):
535 if self._ignore(f):
531 return True
536 return True
532 for p in _finddirs(f):
537 for p in _finddirs(f):
533 if self._ignore(p):
538 if self._ignore(p):
534 return True
539 return True
535 return False
540 return False
536
541
537 def walk(self, match, subrepos, unknown, ignored):
542 def walk(self, match, subrepos, unknown, ignored):
538 '''
543 '''
539 Walk recursively through the directory tree, finding all files
544 Walk recursively through the directory tree, finding all files
540 matched by match.
545 matched by match.
541
546
542 Return a dict mapping filename to stat-like object (either
547 Return a dict mapping filename to stat-like object (either
543 mercurial.osutil.stat instance or return value of os.stat()).
548 mercurial.osutil.stat instance or return value of os.stat()).
544 '''
549 '''
545
550
546 def fwarn(f, msg):
551 def fwarn(f, msg):
547 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
552 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
548 return False
553 return False
549
554
550 def badtype(mode):
555 def badtype(mode):
551 kind = _('unknown')
556 kind = _('unknown')
552 if stat.S_ISCHR(mode):
557 if stat.S_ISCHR(mode):
553 kind = _('character device')
558 kind = _('character device')
554 elif stat.S_ISBLK(mode):
559 elif stat.S_ISBLK(mode):
555 kind = _('block device')
560 kind = _('block device')
556 elif stat.S_ISFIFO(mode):
561 elif stat.S_ISFIFO(mode):
557 kind = _('fifo')
562 kind = _('fifo')
558 elif stat.S_ISSOCK(mode):
563 elif stat.S_ISSOCK(mode):
559 kind = _('socket')
564 kind = _('socket')
560 elif stat.S_ISDIR(mode):
565 elif stat.S_ISDIR(mode):
561 kind = _('directory')
566 kind = _('directory')
562 return _('unsupported file type (type is %s)') % kind
567 return _('unsupported file type (type is %s)') % kind
563
568
564 ignore = self._ignore
569 ignore = self._ignore
565 dirignore = self._dirignore
570 dirignore = self._dirignore
566 if ignored:
571 if ignored:
567 ignore = util.never
572 ignore = util.never
568 dirignore = util.never
573 dirignore = util.never
569 elif not unknown:
574 elif not unknown:
570 # if unknown and ignored are False, skip step 2
575 # if unknown and ignored are False, skip step 2
571 ignore = util.always
576 ignore = util.always
572 dirignore = util.always
577 dirignore = util.always
573
578
574 matchfn = match.matchfn
579 matchfn = match.matchfn
575 badfn = match.bad
580 badfn = match.bad
576 dmap = self._map
581 dmap = self._map
577 normpath = util.normpath
582 normpath = util.normpath
578 listdir = osutil.listdir
583 listdir = osutil.listdir
579 lstat = os.lstat
584 lstat = os.lstat
580 getkind = stat.S_IFMT
585 getkind = stat.S_IFMT
581 dirkind = stat.S_IFDIR
586 dirkind = stat.S_IFDIR
582 regkind = stat.S_IFREG
587 regkind = stat.S_IFREG
583 lnkkind = stat.S_IFLNK
588 lnkkind = stat.S_IFLNK
584 join = self._join
589 join = self._join
585 work = []
590 work = []
586 wadd = work.append
591 wadd = work.append
587
592
588 exact = skipstep3 = False
593 exact = skipstep3 = False
589 if matchfn == match.exact: # match.exact
594 if matchfn == match.exact: # match.exact
590 exact = True
595 exact = True
591 dirignore = util.always # skip step 2
596 dirignore = util.always # skip step 2
592 elif match.files() and not match.anypats(): # match.match, no patterns
597 elif match.files() and not match.anypats(): # match.match, no patterns
593 skipstep3 = True
598 skipstep3 = True
594
599
595 if not exact and self._checkcase:
600 if not exact and self._checkcase:
596 normalize = self._normalize
601 normalize = self._normalize
597 skipstep3 = False
602 skipstep3 = False
598 else:
603 else:
599 normalize = None
604 normalize = None
600
605
601 files = sorted(match.files())
606 files = sorted(match.files())
602 subrepos.sort()
607 subrepos.sort()
603 i, j = 0, 0
608 i, j = 0, 0
604 while i < len(files) and j < len(subrepos):
609 while i < len(files) and j < len(subrepos):
605 subpath = subrepos[j] + "/"
610 subpath = subrepos[j] + "/"
606 if files[i] < subpath:
611 if files[i] < subpath:
607 i += 1
612 i += 1
608 continue
613 continue
609 while i < len(files) and files[i].startswith(subpath):
614 while i < len(files) and files[i].startswith(subpath):
610 del files[i]
615 del files[i]
611 j += 1
616 j += 1
612
617
613 if not files or '.' in files:
618 if not files or '.' in files:
614 files = ['']
619 files = ['']
615 results = dict.fromkeys(subrepos)
620 results = dict.fromkeys(subrepos)
616 results['.hg'] = None
621 results['.hg'] = None
617
622
618 # step 1: find all explicit files
623 # step 1: find all explicit files
619 for ff in files:
624 for ff in files:
620 if normalize:
625 if normalize:
621 nf = normalize(normpath(ff), False, True)
626 nf = normalize(normpath(ff), False, True)
622 else:
627 else:
623 nf = normpath(ff)
628 nf = normpath(ff)
624 if nf in results:
629 if nf in results:
625 continue
630 continue
626
631
627 try:
632 try:
628 st = lstat(join(nf))
633 st = lstat(join(nf))
629 kind = getkind(st.st_mode)
634 kind = getkind(st.st_mode)
630 if kind == dirkind:
635 if kind == dirkind:
631 skipstep3 = False
636 skipstep3 = False
632 if nf in dmap:
637 if nf in dmap:
633 #file deleted on disk but still in dirstate
638 #file deleted on disk but still in dirstate
634 results[nf] = None
639 results[nf] = None
635 match.dir(nf)
640 match.dir(nf)
636 if not dirignore(nf):
641 if not dirignore(nf):
637 wadd(nf)
642 wadd(nf)
638 elif kind == regkind or kind == lnkkind:
643 elif kind == regkind or kind == lnkkind:
639 results[nf] = st
644 results[nf] = st
640 else:
645 else:
641 badfn(ff, badtype(kind))
646 badfn(ff, badtype(kind))
642 if nf in dmap:
647 if nf in dmap:
643 results[nf] = None
648 results[nf] = None
644 except OSError, inst:
649 except OSError, inst:
645 if nf in dmap: # does it exactly match a file?
650 if nf in dmap: # does it exactly match a file?
646 results[nf] = None
651 results[nf] = None
647 else: # does it match a directory?
652 else: # does it match a directory?
648 prefix = nf + "/"
653 prefix = nf + "/"
649 for fn in dmap:
654 for fn in dmap:
650 if fn.startswith(prefix):
655 if fn.startswith(prefix):
651 match.dir(nf)
656 match.dir(nf)
652 skipstep3 = False
657 skipstep3 = False
653 break
658 break
654 else:
659 else:
655 badfn(ff, inst.strerror)
660 badfn(ff, inst.strerror)
656
661
657 # step 2: visit subdirectories
662 # step 2: visit subdirectories
658 while work:
663 while work:
659 nd = work.pop()
664 nd = work.pop()
660 skip = None
665 skip = None
661 if nd == '.':
666 if nd == '.':
662 nd = ''
667 nd = ''
663 else:
668 else:
664 skip = '.hg'
669 skip = '.hg'
665 try:
670 try:
666 entries = listdir(join(nd), stat=True, skip=skip)
671 entries = listdir(join(nd), stat=True, skip=skip)
667 except OSError, inst:
672 except OSError, inst:
668 if inst.errno in (errno.EACCES, errno.ENOENT):
673 if inst.errno in (errno.EACCES, errno.ENOENT):
669 fwarn(nd, inst.strerror)
674 fwarn(nd, inst.strerror)
670 continue
675 continue
671 raise
676 raise
672 for f, kind, st in entries:
677 for f, kind, st in entries:
673 if normalize:
678 if normalize:
674 nf = normalize(nd and (nd + "/" + f) or f, True, True)
679 nf = normalize(nd and (nd + "/" + f) or f, True, True)
675 else:
680 else:
676 nf = nd and (nd + "/" + f) or f
681 nf = nd and (nd + "/" + f) or f
677 if nf not in results:
682 if nf not in results:
678 if kind == dirkind:
683 if kind == dirkind:
679 if not ignore(nf):
684 if not ignore(nf):
680 match.dir(nf)
685 match.dir(nf)
681 wadd(nf)
686 wadd(nf)
682 if nf in dmap and matchfn(nf):
687 if nf in dmap and matchfn(nf):
683 results[nf] = None
688 results[nf] = None
684 elif kind == regkind or kind == lnkkind:
689 elif kind == regkind or kind == lnkkind:
685 if nf in dmap:
690 if nf in dmap:
686 if matchfn(nf):
691 if matchfn(nf):
687 results[nf] = st
692 results[nf] = st
688 elif matchfn(nf) and not ignore(nf):
693 elif matchfn(nf) and not ignore(nf):
689 results[nf] = st
694 results[nf] = st
690 elif nf in dmap and matchfn(nf):
695 elif nf in dmap and matchfn(nf):
691 results[nf] = None
696 results[nf] = None
692
697
693 # step 3: report unseen items in the dmap hash
698 # step 3: report unseen items in the dmap hash
694 if not skipstep3 and not exact:
699 if not skipstep3 and not exact:
695 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
700 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
696 if unknown:
701 if unknown:
697 # unknown == True means we walked the full directory tree above.
702 # unknown == True means we walked the full directory tree above.
698 # So if a file is not seen it was either a) not matching matchfn
703 # So if a file is not seen it was either a) not matching matchfn
699 # b) ignored, c) missing, or d) under a symlink directory.
704 # b) ignored, c) missing, or d) under a symlink directory.
700 audit_path = scmutil.pathauditor(self._root)
705 audit_path = scmutil.pathauditor(self._root)
701
706
702 for nf in iter(visit):
707 for nf in iter(visit):
703 # Report ignored items in the dmap as long as they are not
708 # Report ignored items in the dmap as long as they are not
704 # under a symlink directory.
709 # under a symlink directory.
705 if ignore(nf) and audit_path.check(nf):
710 if ignore(nf) and audit_path.check(nf):
706 try:
711 try:
707 results[nf] = lstat(join(nf))
712 results[nf] = lstat(join(nf))
708 except OSError:
713 except OSError:
709 # file doesn't exist
714 # file doesn't exist
710 results[nf] = None
715 results[nf] = None
711 else:
716 else:
712 # It's either missing or under a symlink directory
717 # It's either missing or under a symlink directory
713 results[nf] = None
718 results[nf] = None
714 else:
719 else:
715 # We may not have walked the full directory tree above,
720 # We may not have walked the full directory tree above,
716 # so stat everything we missed.
721 # so stat everything we missed.
717 nf = iter(visit).next
722 nf = iter(visit).next
718 for st in util.statfiles([join(i) for i in visit]):
723 for st in util.statfiles([join(i) for i in visit]):
719 results[nf()] = st
724 results[nf()] = st
720 for s in subrepos:
725 for s in subrepos:
721 del results[s]
726 del results[s]
722 del results['.hg']
727 del results['.hg']
723 return results
728 return results
724
729
725 def status(self, match, subrepos, ignored, clean, unknown):
730 def status(self, match, subrepos, ignored, clean, unknown):
726 '''Determine the status of the working copy relative to the
731 '''Determine the status of the working copy relative to the
727 dirstate and return a tuple of lists (unsure, modified, added,
732 dirstate and return a tuple of lists (unsure, modified, added,
728 removed, deleted, unknown, ignored, clean), where:
733 removed, deleted, unknown, ignored, clean), where:
729
734
730 unsure:
735 unsure:
731 files that might have been modified since the dirstate was
736 files that might have been modified since the dirstate was
732 written, but need to be read to be sure (size is the same
737 written, but need to be read to be sure (size is the same
733 but mtime differs)
738 but mtime differs)
734 modified:
739 modified:
735 files that have definitely been modified since the dirstate
740 files that have definitely been modified since the dirstate
736 was written (different size or mode)
741 was written (different size or mode)
737 added:
742 added:
738 files that have been explicitly added with hg add
743 files that have been explicitly added with hg add
739 removed:
744 removed:
740 files that have been explicitly removed with hg remove
745 files that have been explicitly removed with hg remove
741 deleted:
746 deleted:
742 files that have been deleted through other means ("missing")
747 files that have been deleted through other means ("missing")
743 unknown:
748 unknown:
744 files not in the dirstate that are not ignored
749 files not in the dirstate that are not ignored
745 ignored:
750 ignored:
746 files not in the dirstate that are ignored
751 files not in the dirstate that are ignored
747 (by _dirignore())
752 (by _dirignore())
748 clean:
753 clean:
749 files that have definitely not been modified since the
754 files that have definitely not been modified since the
750 dirstate was written
755 dirstate was written
751 '''
756 '''
752 listignored, listclean, listunknown = ignored, clean, unknown
757 listignored, listclean, listunknown = ignored, clean, unknown
753 lookup, modified, added, unknown, ignored = [], [], [], [], []
758 lookup, modified, added, unknown, ignored = [], [], [], [], []
754 removed, deleted, clean = [], [], []
759 removed, deleted, clean = [], [], []
755
760
756 dmap = self._map
761 dmap = self._map
757 ladd = lookup.append # aka "unsure"
762 ladd = lookup.append # aka "unsure"
758 madd = modified.append
763 madd = modified.append
759 aadd = added.append
764 aadd = added.append
760 uadd = unknown.append
765 uadd = unknown.append
761 iadd = ignored.append
766 iadd = ignored.append
762 radd = removed.append
767 radd = removed.append
763 dadd = deleted.append
768 dadd = deleted.append
764 cadd = clean.append
769 cadd = clean.append
765 mexact = match.exact
770 mexact = match.exact
766 dirignore = self._dirignore
771 dirignore = self._dirignore
767 checkexec = self._checkexec
772 checkexec = self._checkexec
768 checklink = self._checklink
773 checklink = self._checklink
769 copymap = self._copymap
774 copymap = self._copymap
770 lastnormaltime = self._lastnormaltime
775 lastnormaltime = self._lastnormaltime
771
776
772 lnkkind = stat.S_IFLNK
777 lnkkind = stat.S_IFLNK
773
778
774 for fn, st in self.walk(match, subrepos, listunknown,
779 for fn, st in self.walk(match, subrepos, listunknown,
775 listignored).iteritems():
780 listignored).iteritems():
776 if fn not in dmap:
781 if fn not in dmap:
777 if (listignored or mexact(fn)) and dirignore(fn):
782 if (listignored or mexact(fn)) and dirignore(fn):
778 if listignored:
783 if listignored:
779 iadd(fn)
784 iadd(fn)
780 elif listunknown:
785 elif listunknown:
781 uadd(fn)
786 uadd(fn)
782 continue
787 continue
783
788
784 state, mode, size, time = dmap[fn]
789 state, mode, size, time = dmap[fn]
785
790
786 if not st and state in "nma":
791 if not st and state in "nma":
787 dadd(fn)
792 dadd(fn)
788 elif state == 'n':
793 elif state == 'n':
789 # The "mode & lnkkind != lnkkind or self._checklink"
794 # The "mode & lnkkind != lnkkind or self._checklink"
790 # lines are an expansion of "islink => checklink"
795 # lines are an expansion of "islink => checklink"
791 # where islink means "is this a link?" and checklink
796 # where islink means "is this a link?" and checklink
792 # means "can we check links?".
797 # means "can we check links?".
793 mtime = int(st.st_mtime)
798 mtime = int(st.st_mtime)
794 if (size >= 0 and
799 if (size >= 0 and
795 ((size != st.st_size and size != st.st_size & _rangemask)
800 ((size != st.st_size and size != st.st_size & _rangemask)
796 or ((mode ^ st.st_mode) & 0100 and checkexec))
801 or ((mode ^ st.st_mode) & 0100 and checkexec))
797 and (mode & lnkkind != lnkkind or checklink)
802 and (mode & lnkkind != lnkkind or checklink)
798 or size == -2 # other parent
803 or size == -2 # other parent
799 or fn in copymap):
804 or fn in copymap):
800 madd(fn)
805 madd(fn)
801 elif ((time != mtime and time != mtime & _rangemask)
806 elif ((time != mtime and time != mtime & _rangemask)
802 and (mode & lnkkind != lnkkind or checklink)):
807 and (mode & lnkkind != lnkkind or checklink)):
803 ladd(fn)
808 ladd(fn)
804 elif mtime == lastnormaltime:
809 elif mtime == lastnormaltime:
805 # fn may have been changed in the same timeslot without
810 # fn may have been changed in the same timeslot without
806 # changing its size. This can happen if we quickly do
811 # changing its size. This can happen if we quickly do
807 # multiple commits in a single transaction.
812 # multiple commits in a single transaction.
808 # Force lookup, so we don't miss such a racy file change.
813 # Force lookup, so we don't miss such a racy file change.
809 ladd(fn)
814 ladd(fn)
810 elif listclean:
815 elif listclean:
811 cadd(fn)
816 cadd(fn)
812 elif state == 'm':
817 elif state == 'm':
813 madd(fn)
818 madd(fn)
814 elif state == 'a':
819 elif state == 'a':
815 aadd(fn)
820 aadd(fn)
816 elif state == 'r':
821 elif state == 'r':
817 radd(fn)
822 radd(fn)
818
823
819 return (lookup, modified, added, removed, deleted, unknown, ignored,
824 return (lookup, modified, added, removed, deleted, unknown, ignored,
820 clean)
825 clean)
@@ -1,468 +1,487
1 $ echo "[extensions]" >> $HGRCPATH
1 $ echo "[extensions]" >> $HGRCPATH
2 $ echo "mq=" >> $HGRCPATH
2 $ echo "mq=" >> $HGRCPATH
3 $ echo "graphlog=" >> $HGRCPATH
3 $ echo "graphlog=" >> $HGRCPATH
4
4
5 $ restore() {
5 $ restore() {
6 > hg unbundle -q .hg/strip-backup/*
6 > hg unbundle -q .hg/strip-backup/*
7 > rm .hg/strip-backup/*
7 > rm .hg/strip-backup/*
8 > }
8 > }
9 $ teststrip() {
9 $ teststrip() {
10 > hg up -C $1
10 > hg up -C $1
11 > echo % before update $1, strip $2
11 > echo % before update $1, strip $2
12 > hg parents
12 > hg parents
13 > hg --traceback strip $2
13 > hg --traceback strip $2
14 > echo % after update $1, strip $2
14 > echo % after update $1, strip $2
15 > hg parents
15 > hg parents
16 > restore
16 > restore
17 > }
17 > }
18
18
19 $ hg init test
19 $ hg init test
20 $ cd test
20 $ cd test
21
21
22 $ echo foo > bar
22 $ echo foo > bar
23 $ hg ci -Ama
23 $ hg ci -Ama
24 adding bar
24 adding bar
25
25
26 $ echo more >> bar
26 $ echo more >> bar
27 $ hg ci -Amb
27 $ hg ci -Amb
28
28
29 $ echo blah >> bar
29 $ echo blah >> bar
30 $ hg ci -Amc
30 $ hg ci -Amc
31
31
32 $ hg up 1
32 $ hg up 1
33 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
33 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
34 $ echo blah >> bar
34 $ echo blah >> bar
35 $ hg ci -Amd
35 $ hg ci -Amd
36 created new head
36 created new head
37
37
38 $ echo final >> bar
38 $ echo final >> bar
39 $ hg ci -Ame
39 $ hg ci -Ame
40
40
41 $ hg log
41 $ hg log
42 changeset: 4:443431ffac4f
42 changeset: 4:443431ffac4f
43 tag: tip
43 tag: tip
44 user: test
44 user: test
45 date: Thu Jan 01 00:00:00 1970 +0000
45 date: Thu Jan 01 00:00:00 1970 +0000
46 summary: e
46 summary: e
47
47
48 changeset: 3:65bd5f99a4a3
48 changeset: 3:65bd5f99a4a3
49 parent: 1:ef3a871183d7
49 parent: 1:ef3a871183d7
50 user: test
50 user: test
51 date: Thu Jan 01 00:00:00 1970 +0000
51 date: Thu Jan 01 00:00:00 1970 +0000
52 summary: d
52 summary: d
53
53
54 changeset: 2:264128213d29
54 changeset: 2:264128213d29
55 user: test
55 user: test
56 date: Thu Jan 01 00:00:00 1970 +0000
56 date: Thu Jan 01 00:00:00 1970 +0000
57 summary: c
57 summary: c
58
58
59 changeset: 1:ef3a871183d7
59 changeset: 1:ef3a871183d7
60 user: test
60 user: test
61 date: Thu Jan 01 00:00:00 1970 +0000
61 date: Thu Jan 01 00:00:00 1970 +0000
62 summary: b
62 summary: b
63
63
64 changeset: 0:9ab35a2d17cb
64 changeset: 0:9ab35a2d17cb
65 user: test
65 user: test
66 date: Thu Jan 01 00:00:00 1970 +0000
66 date: Thu Jan 01 00:00:00 1970 +0000
67 summary: a
67 summary: a
68
68
69
69
70 $ teststrip 4 4
70 $ teststrip 4 4
71 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
71 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
72 % before update 4, strip 4
72 % before update 4, strip 4
73 changeset: 4:443431ffac4f
73 changeset: 4:443431ffac4f
74 tag: tip
74 tag: tip
75 user: test
75 user: test
76 date: Thu Jan 01 00:00:00 1970 +0000
76 date: Thu Jan 01 00:00:00 1970 +0000
77 summary: e
77 summary: e
78
78
79 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
79 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
80 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
80 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
81 % after update 4, strip 4
81 % after update 4, strip 4
82 changeset: 3:65bd5f99a4a3
82 changeset: 3:65bd5f99a4a3
83 tag: tip
83 tag: tip
84 parent: 1:ef3a871183d7
84 parent: 1:ef3a871183d7
85 user: test
85 user: test
86 date: Thu Jan 01 00:00:00 1970 +0000
86 date: Thu Jan 01 00:00:00 1970 +0000
87 summary: d
87 summary: d
88
88
89 $ teststrip 4 3
89 $ teststrip 4 3
90 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
90 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
91 % before update 4, strip 3
91 % before update 4, strip 3
92 changeset: 4:443431ffac4f
92 changeset: 4:443431ffac4f
93 tag: tip
93 tag: tip
94 user: test
94 user: test
95 date: Thu Jan 01 00:00:00 1970 +0000
95 date: Thu Jan 01 00:00:00 1970 +0000
96 summary: e
96 summary: e
97
97
98 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
98 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
99 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
99 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
100 % after update 4, strip 3
100 % after update 4, strip 3
101 changeset: 1:ef3a871183d7
101 changeset: 1:ef3a871183d7
102 user: test
102 user: test
103 date: Thu Jan 01 00:00:00 1970 +0000
103 date: Thu Jan 01 00:00:00 1970 +0000
104 summary: b
104 summary: b
105
105
106 $ teststrip 1 4
106 $ teststrip 1 4
107 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
107 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
108 % before update 1, strip 4
108 % before update 1, strip 4
109 changeset: 1:ef3a871183d7
109 changeset: 1:ef3a871183d7
110 user: test
110 user: test
111 date: Thu Jan 01 00:00:00 1970 +0000
111 date: Thu Jan 01 00:00:00 1970 +0000
112 summary: b
112 summary: b
113
113
114 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
114 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
115 % after update 1, strip 4
115 % after update 1, strip 4
116 changeset: 1:ef3a871183d7
116 changeset: 1:ef3a871183d7
117 user: test
117 user: test
118 date: Thu Jan 01 00:00:00 1970 +0000
118 date: Thu Jan 01 00:00:00 1970 +0000
119 summary: b
119 summary: b
120
120
121 $ teststrip 4 2
121 $ teststrip 4 2
122 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
122 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
123 % before update 4, strip 2
123 % before update 4, strip 2
124 changeset: 4:443431ffac4f
124 changeset: 4:443431ffac4f
125 tag: tip
125 tag: tip
126 user: test
126 user: test
127 date: Thu Jan 01 00:00:00 1970 +0000
127 date: Thu Jan 01 00:00:00 1970 +0000
128 summary: e
128 summary: e
129
129
130 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
130 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
131 % after update 4, strip 2
131 % after update 4, strip 2
132 changeset: 3:443431ffac4f
132 changeset: 3:443431ffac4f
133 tag: tip
133 tag: tip
134 user: test
134 user: test
135 date: Thu Jan 01 00:00:00 1970 +0000
135 date: Thu Jan 01 00:00:00 1970 +0000
136 summary: e
136 summary: e
137
137
138 $ teststrip 4 1
138 $ teststrip 4 1
139 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
139 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
140 % before update 4, strip 1
140 % before update 4, strip 1
141 changeset: 4:264128213d29
141 changeset: 4:264128213d29
142 tag: tip
142 tag: tip
143 parent: 1:ef3a871183d7
143 parent: 1:ef3a871183d7
144 user: test
144 user: test
145 date: Thu Jan 01 00:00:00 1970 +0000
145 date: Thu Jan 01 00:00:00 1970 +0000
146 summary: c
146 summary: c
147
147
148 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
148 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
149 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
149 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
150 % after update 4, strip 1
150 % after update 4, strip 1
151 changeset: 0:9ab35a2d17cb
151 changeset: 0:9ab35a2d17cb
152 tag: tip
152 tag: tip
153 user: test
153 user: test
154 date: Thu Jan 01 00:00:00 1970 +0000
154 date: Thu Jan 01 00:00:00 1970 +0000
155 summary: a
155 summary: a
156
156
157 $ teststrip null 4
157 $ teststrip null 4
158 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
158 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
159 % before update null, strip 4
159 % before update null, strip 4
160 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
160 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
161 % after update null, strip 4
161 % after update null, strip 4
162
162
163 $ hg log
163 $ hg log
164 changeset: 4:264128213d29
164 changeset: 4:264128213d29
165 tag: tip
165 tag: tip
166 parent: 1:ef3a871183d7
166 parent: 1:ef3a871183d7
167 user: test
167 user: test
168 date: Thu Jan 01 00:00:00 1970 +0000
168 date: Thu Jan 01 00:00:00 1970 +0000
169 summary: c
169 summary: c
170
170
171 changeset: 3:443431ffac4f
171 changeset: 3:443431ffac4f
172 user: test
172 user: test
173 date: Thu Jan 01 00:00:00 1970 +0000
173 date: Thu Jan 01 00:00:00 1970 +0000
174 summary: e
174 summary: e
175
175
176 changeset: 2:65bd5f99a4a3
176 changeset: 2:65bd5f99a4a3
177 user: test
177 user: test
178 date: Thu Jan 01 00:00:00 1970 +0000
178 date: Thu Jan 01 00:00:00 1970 +0000
179 summary: d
179 summary: d
180
180
181 changeset: 1:ef3a871183d7
181 changeset: 1:ef3a871183d7
182 user: test
182 user: test
183 date: Thu Jan 01 00:00:00 1970 +0000
183 date: Thu Jan 01 00:00:00 1970 +0000
184 summary: b
184 summary: b
185
185
186 changeset: 0:9ab35a2d17cb
186 changeset: 0:9ab35a2d17cb
187 user: test
187 user: test
188 date: Thu Jan 01 00:00:00 1970 +0000
188 date: Thu Jan 01 00:00:00 1970 +0000
189 summary: a
189 summary: a
190
190
191
191
192 $ hg up -C 2
192 $ hg up -C 2
193 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
193 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
194 $ hg merge 4
194 $ hg merge 4
195 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
195 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
196 (branch merge, don't forget to commit)
196 (branch merge, don't forget to commit)
197
197
198 before strip of merge parent
198 before strip of merge parent
199
199
200 $ hg parents
200 $ hg parents
201 changeset: 2:65bd5f99a4a3
201 changeset: 2:65bd5f99a4a3
202 user: test
202 user: test
203 date: Thu Jan 01 00:00:00 1970 +0000
203 date: Thu Jan 01 00:00:00 1970 +0000
204 summary: d
204 summary: d
205
205
206 changeset: 4:264128213d29
206 changeset: 4:264128213d29
207 tag: tip
207 tag: tip
208 parent: 1:ef3a871183d7
208 parent: 1:ef3a871183d7
209 user: test
209 user: test
210 date: Thu Jan 01 00:00:00 1970 +0000
210 date: Thu Jan 01 00:00:00 1970 +0000
211 summary: c
211 summary: c
212
212
213 $ hg strip 4
213 $ hg strip 4
214 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
214 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
215 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
215 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
216
216
217 after strip of merge parent
217 after strip of merge parent
218
218
219 $ hg parents
219 $ hg parents
220 changeset: 1:ef3a871183d7
220 changeset: 1:ef3a871183d7
221 user: test
221 user: test
222 date: Thu Jan 01 00:00:00 1970 +0000
222 date: Thu Jan 01 00:00:00 1970 +0000
223 summary: b
223 summary: b
224
224
225 $ restore
225 $ restore
226
226
227 $ hg up
227 $ hg up
228 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
228 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
229 $ hg glog
229 $ hg glog
230 @ changeset: 4:264128213d29
230 @ changeset: 4:264128213d29
231 | tag: tip
231 | tag: tip
232 | parent: 1:ef3a871183d7
232 | parent: 1:ef3a871183d7
233 | user: test
233 | user: test
234 | date: Thu Jan 01 00:00:00 1970 +0000
234 | date: Thu Jan 01 00:00:00 1970 +0000
235 | summary: c
235 | summary: c
236 |
236 |
237 | o changeset: 3:443431ffac4f
237 | o changeset: 3:443431ffac4f
238 | | user: test
238 | | user: test
239 | | date: Thu Jan 01 00:00:00 1970 +0000
239 | | date: Thu Jan 01 00:00:00 1970 +0000
240 | | summary: e
240 | | summary: e
241 | |
241 | |
242 | o changeset: 2:65bd5f99a4a3
242 | o changeset: 2:65bd5f99a4a3
243 |/ user: test
243 |/ user: test
244 | date: Thu Jan 01 00:00:00 1970 +0000
244 | date: Thu Jan 01 00:00:00 1970 +0000
245 | summary: d
245 | summary: d
246 |
246 |
247 o changeset: 1:ef3a871183d7
247 o changeset: 1:ef3a871183d7
248 | user: test
248 | user: test
249 | date: Thu Jan 01 00:00:00 1970 +0000
249 | date: Thu Jan 01 00:00:00 1970 +0000
250 | summary: b
250 | summary: b
251 |
251 |
252 o changeset: 0:9ab35a2d17cb
252 o changeset: 0:9ab35a2d17cb
253 user: test
253 user: test
254 date: Thu Jan 01 00:00:00 1970 +0000
254 date: Thu Jan 01 00:00:00 1970 +0000
255 summary: a
255 summary: a
256
256
257
257
258 2 is parent of 3, only one strip should happen
258 2 is parent of 3, only one strip should happen
259
259
260 $ hg strip "roots(2)" 3
260 $ hg strip "roots(2)" 3
261 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
261 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
262 $ hg glog
262 $ hg glog
263 @ changeset: 2:264128213d29
263 @ changeset: 2:264128213d29
264 | tag: tip
264 | tag: tip
265 | user: test
265 | user: test
266 | date: Thu Jan 01 00:00:00 1970 +0000
266 | date: Thu Jan 01 00:00:00 1970 +0000
267 | summary: c
267 | summary: c
268 |
268 |
269 o changeset: 1:ef3a871183d7
269 o changeset: 1:ef3a871183d7
270 | user: test
270 | user: test
271 | date: Thu Jan 01 00:00:00 1970 +0000
271 | date: Thu Jan 01 00:00:00 1970 +0000
272 | summary: b
272 | summary: b
273 |
273 |
274 o changeset: 0:9ab35a2d17cb
274 o changeset: 0:9ab35a2d17cb
275 user: test
275 user: test
276 date: Thu Jan 01 00:00:00 1970 +0000
276 date: Thu Jan 01 00:00:00 1970 +0000
277 summary: a
277 summary: a
278
278
279 $ restore
279 $ restore
280 $ hg glog
280 $ hg glog
281 o changeset: 4:443431ffac4f
281 o changeset: 4:443431ffac4f
282 | tag: tip
282 | tag: tip
283 | user: test
283 | user: test
284 | date: Thu Jan 01 00:00:00 1970 +0000
284 | date: Thu Jan 01 00:00:00 1970 +0000
285 | summary: e
285 | summary: e
286 |
286 |
287 o changeset: 3:65bd5f99a4a3
287 o changeset: 3:65bd5f99a4a3
288 | parent: 1:ef3a871183d7
288 | parent: 1:ef3a871183d7
289 | user: test
289 | user: test
290 | date: Thu Jan 01 00:00:00 1970 +0000
290 | date: Thu Jan 01 00:00:00 1970 +0000
291 | summary: d
291 | summary: d
292 |
292 |
293 | @ changeset: 2:264128213d29
293 | @ changeset: 2:264128213d29
294 |/ user: test
294 |/ user: test
295 | date: Thu Jan 01 00:00:00 1970 +0000
295 | date: Thu Jan 01 00:00:00 1970 +0000
296 | summary: c
296 | summary: c
297 |
297 |
298 o changeset: 1:ef3a871183d7
298 o changeset: 1:ef3a871183d7
299 | user: test
299 | user: test
300 | date: Thu Jan 01 00:00:00 1970 +0000
300 | date: Thu Jan 01 00:00:00 1970 +0000
301 | summary: b
301 | summary: b
302 |
302 |
303 o changeset: 0:9ab35a2d17cb
303 o changeset: 0:9ab35a2d17cb
304 user: test
304 user: test
305 date: Thu Jan 01 00:00:00 1970 +0000
305 date: Thu Jan 01 00:00:00 1970 +0000
306 summary: a
306 summary: a
307
307
308
308
309 2 different branches: 2 strips
309 2 different branches: 2 strips
310
310
311 $ hg strip 2 4
311 $ hg strip 2 4
312 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
312 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
313 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
313 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
314 $ hg glog
314 $ hg glog
315 o changeset: 2:65bd5f99a4a3
315 o changeset: 2:65bd5f99a4a3
316 | tag: tip
316 | tag: tip
317 | user: test
317 | user: test
318 | date: Thu Jan 01 00:00:00 1970 +0000
318 | date: Thu Jan 01 00:00:00 1970 +0000
319 | summary: d
319 | summary: d
320 |
320 |
321 @ changeset: 1:ef3a871183d7
321 @ changeset: 1:ef3a871183d7
322 | user: test
322 | user: test
323 | date: Thu Jan 01 00:00:00 1970 +0000
323 | date: Thu Jan 01 00:00:00 1970 +0000
324 | summary: b
324 | summary: b
325 |
325 |
326 o changeset: 0:9ab35a2d17cb
326 o changeset: 0:9ab35a2d17cb
327 user: test
327 user: test
328 date: Thu Jan 01 00:00:00 1970 +0000
328 date: Thu Jan 01 00:00:00 1970 +0000
329 summary: a
329 summary: a
330
330
331 $ restore
331 $ restore
332
332
333 2 different branches and a common ancestor: 1 strip
333 2 different branches and a common ancestor: 1 strip
334
334
335 $ hg strip 1 "2|4"
335 $ hg strip 1 "2|4"
336 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
336 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
337 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
337 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
338 $ restore
338 $ restore
339
339
340 stripping an empty revset
340 stripping an empty revset
341
341
342 $ hg strip "1 and not 1"
342 $ hg strip "1 and not 1"
343 abort: empty revision set
343 abort: empty revision set
344 [255]
344 [255]
345
345
346 remove branchy history for qimport tests
346 remove branchy history for qimport tests
347
347
348 $ hg strip 3
348 $ hg strip 3
349 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
349 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
350
350
351
351
352 strip of applied mq should cleanup status file
352 strip of applied mq should cleanup status file
353
353
354 $ hg up -C 3
354 $ hg up -C 3
355 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
355 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
356 $ echo fooagain >> bar
356 $ echo fooagain >> bar
357 $ hg ci -mf
357 $ hg ci -mf
358 $ hg qimport -r tip:2
358 $ hg qimport -r tip:2
359
359
360 applied patches before strip
360 applied patches before strip
361
361
362 $ hg qapplied
362 $ hg qapplied
363 2.diff
363 2.diff
364 3.diff
364 3.diff
365 4.diff
365 4.diff
366
366
367 stripping revision in queue
367 stripping revision in queue
368
368
369 $ hg strip 3
369 $ hg strip 3
370 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
370 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
371 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
371 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
372
372
373 applied patches after stripping rev in queue
373 applied patches after stripping rev in queue
374
374
375 $ hg qapplied
375 $ hg qapplied
376 2.diff
376 2.diff
377
377
378 stripping ancestor of queue
378 stripping ancestor of queue
379
379
380 $ hg strip 1
380 $ hg strip 1
381 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
381 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
382 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
382 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
383
383
384 applied patches after stripping ancestor of queue
384 applied patches after stripping ancestor of queue
385
385
386 $ hg qapplied
386 $ hg qapplied
387
387
388 Verify strip protects against stripping wc parent when there are uncommited mods
388 Verify strip protects against stripping wc parent when there are uncommited mods
389
389
390 $ echo b > b
390 $ echo b > b
391 $ hg add b
391 $ hg add b
392 $ hg ci -m 'b'
392 $ hg ci -m 'b'
393 $ hg log --graph
393 $ hg log --graph
394 @ changeset: 1:7519abd79d14
394 @ changeset: 1:7519abd79d14
395 | tag: tip
395 | tag: tip
396 | user: test
396 | user: test
397 | date: Thu Jan 01 00:00:00 1970 +0000
397 | date: Thu Jan 01 00:00:00 1970 +0000
398 | summary: b
398 | summary: b
399 |
399 |
400 o changeset: 0:9ab35a2d17cb
400 o changeset: 0:9ab35a2d17cb
401 user: test
401 user: test
402 date: Thu Jan 01 00:00:00 1970 +0000
402 date: Thu Jan 01 00:00:00 1970 +0000
403 summary: a
403 summary: a
404
404
405
405
406 $ echo c > b
406 $ echo c > b
407 $ echo c > bar
407 $ echo c > bar
408 $ hg strip tip
408 $ hg strip tip
409 abort: local changes found
409 abort: local changes found
410 [255]
410 [255]
411 $ hg strip tip --keep
411 $ hg strip tip --keep
412 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
412 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
413 $ hg log --graph
413 $ hg log --graph
414 @ changeset: 0:9ab35a2d17cb
414 @ changeset: 0:9ab35a2d17cb
415 tag: tip
415 tag: tip
416 user: test
416 user: test
417 date: Thu Jan 01 00:00:00 1970 +0000
417 date: Thu Jan 01 00:00:00 1970 +0000
418 summary: a
418 summary: a
419
419
420 $ hg status
420 $ hg status
421 M bar
421 M bar
422 ? b
422 ? b
423
424 Strip adds, removes, modifies with --keep
425
426 $ touch b
427 $ hg add b
428 $ hg commit -mb
429 $ touch c
430 $ hg add c
431 $ hg rm bar
432 $ hg commit -mc
433 $ echo b > b
434 $ echo d > d
435 $ hg strip --keep tip
436 saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob)
437 $ hg status
438 M b
439 ! bar
440 ? c
441 ? d
423 $ cd ..
442 $ cd ..
424
443
425 stripping many nodes on a complex graph (issue3299)
444 stripping many nodes on a complex graph (issue3299)
426
445
427 $ hg init issue3299
446 $ hg init issue3299
428 $ cd issue3299
447 $ cd issue3299
429 $ hg debugbuilddag '@a.:a@b.:b.:x<a@a.:a<b@b.:b<a@a.:a'
448 $ hg debugbuilddag '@a.:a@b.:b.:x<a@a.:a<b@b.:b<a@a.:a'
430 $ hg strip 'not ancestors(x)'
449 $ hg strip 'not ancestors(x)'
431 saved backup bundle to $TESTTMP/issue3299/.hg/strip-backup/*-backup.hg (glob)
450 saved backup bundle to $TESTTMP/issue3299/.hg/strip-backup/*-backup.hg (glob)
432
451
433 test hg strip -B bookmark
452 test hg strip -B bookmark
434
453
435 $ cd ..
454 $ cd ..
436 $ hg init bookmarks
455 $ hg init bookmarks
437 $ cd bookmarks
456 $ cd bookmarks
438 $ hg debugbuilddag '..<2.*1/2:m<2+3:c<m+3:a<2.:b'
457 $ hg debugbuilddag '..<2.*1/2:m<2+3:c<m+3:a<2.:b'
439 $ hg bookmark -r 'a' 'todelete'
458 $ hg bookmark -r 'a' 'todelete'
440 $ hg bookmark -r 'b' 'B'
459 $ hg bookmark -r 'b' 'B'
441 $ hg bookmark -r 'b' 'nostrip'
460 $ hg bookmark -r 'b' 'nostrip'
442 $ hg bookmark -r 'c' 'delete'
461 $ hg bookmark -r 'c' 'delete'
443 $ hg up -C todelete
462 $ hg up -C todelete
444 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
463 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
445 $ hg strip -B nostrip
464 $ hg strip -B nostrip
446 bookmark 'nostrip' deleted
465 bookmark 'nostrip' deleted
447 abort: empty revision set
466 abort: empty revision set
448 [255]
467 [255]
449 $ hg strip -B todelete
468 $ hg strip -B todelete
450 bookmark 'todelete' deleted
469 bookmark 'todelete' deleted
451 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
470 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
452 saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/*-backup.hg (glob)
471 saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/*-backup.hg (glob)
453 $ hg id -ir dcbb326fdec2
472 $ hg id -ir dcbb326fdec2
454 abort: unknown revision 'dcbb326fdec2'!
473 abort: unknown revision 'dcbb326fdec2'!
455 [255]
474 [255]
456 $ hg id -ir d62d843c9a01
475 $ hg id -ir d62d843c9a01
457 d62d843c9a01
476 d62d843c9a01
458 $ hg bookmarks
477 $ hg bookmarks
459 B 9:ff43616e5d0f
478 B 9:ff43616e5d0f
460 delete 6:2702dd0c91e7
479 delete 6:2702dd0c91e7
461 $ hg strip -B delete
480 $ hg strip -B delete
462 bookmark 'delete' deleted
481 bookmark 'delete' deleted
463 saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/*-backup.hg (glob)
482 saved backup bundle to $TESTTMP/bookmarks/.hg/strip-backup/*-backup.hg (glob)
464 $ hg id -ir 6:2702dd0c91e7
483 $ hg id -ir 6:2702dd0c91e7
465 abort: unknown revision '2702dd0c91e7'!
484 abort: unknown revision '2702dd0c91e7'!
466 [255]
485 [255]
467
486
468 $ cd ..
487 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now