##// END OF EJS Templates
merge with stable
Matt Mackall -
r14052:ecaa7859 merge default
parent child Browse files
Show More
@@ -1,3292 +1,3292 b''
1 # mq.py - patch queues for mercurial
1 # mq.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms 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 You will by default be managing a patch queue named "patches". You can
41 You will by default be managing a patch queue named "patches". You can
42 create other, independent patch queues with the :hg:`qqueue` command.
42 create other, independent patch queues with the :hg:`qqueue` command.
43 '''
43 '''
44
44
45 from mercurial.i18n import _
45 from mercurial.i18n import _
46 from mercurial.node import bin, hex, short, nullid, nullrev
46 from mercurial.node import bin, hex, short, nullid, nullrev
47 from mercurial.lock import release
47 from mercurial.lock import release
48 from mercurial import commands, cmdutil, hg, patch, scmutil, util
48 from mercurial import commands, cmdutil, hg, patch, scmutil, util
49 from mercurial import repair, extensions, url, error
49 from mercurial import repair, extensions, url, error
50 import os, sys, re, errno, shutil
50 import os, sys, re, errno, shutil
51
51
52 commands.norepo += " qclone"
52 commands.norepo += " qclone"
53
53
54 # Patch names looks like unix-file names.
54 # Patch names looks like unix-file names.
55 # They must be joinable with queue directory and result in the patch path.
55 # They must be joinable with queue directory and result in the patch path.
56 normname = util.normpath
56 normname = util.normpath
57
57
58 class statusentry(object):
58 class statusentry(object):
59 def __init__(self, node, name):
59 def __init__(self, node, name):
60 self.node, self.name = node, name
60 self.node, self.name = node, name
61 def __repr__(self):
61 def __repr__(self):
62 return hex(self.node) + ':' + self.name
62 return hex(self.node) + ':' + self.name
63
63
64 class patchheader(object):
64 class patchheader(object):
65 def __init__(self, pf, plainmode=False):
65 def __init__(self, pf, plainmode=False):
66 def eatdiff(lines):
66 def eatdiff(lines):
67 while lines:
67 while lines:
68 l = lines[-1]
68 l = lines[-1]
69 if (l.startswith("diff -") or
69 if (l.startswith("diff -") or
70 l.startswith("Index:") or
70 l.startswith("Index:") or
71 l.startswith("===========")):
71 l.startswith("===========")):
72 del lines[-1]
72 del lines[-1]
73 else:
73 else:
74 break
74 break
75 def eatempty(lines):
75 def eatempty(lines):
76 while lines:
76 while lines:
77 if not lines[-1].strip():
77 if not lines[-1].strip():
78 del lines[-1]
78 del lines[-1]
79 else:
79 else:
80 break
80 break
81
81
82 message = []
82 message = []
83 comments = []
83 comments = []
84 user = None
84 user = None
85 date = None
85 date = None
86 parent = None
86 parent = None
87 format = None
87 format = None
88 subject = None
88 subject = None
89 branch = None
89 branch = None
90 nodeid = None
90 nodeid = None
91 diffstart = 0
91 diffstart = 0
92
92
93 for line in file(pf):
93 for line in file(pf):
94 line = line.rstrip()
94 line = line.rstrip()
95 if (line.startswith('diff --git')
95 if (line.startswith('diff --git')
96 or (diffstart and line.startswith('+++ '))):
96 or (diffstart and line.startswith('+++ '))):
97 diffstart = 2
97 diffstart = 2
98 break
98 break
99 diffstart = 0 # reset
99 diffstart = 0 # reset
100 if line.startswith("--- "):
100 if line.startswith("--- "):
101 diffstart = 1
101 diffstart = 1
102 continue
102 continue
103 elif format == "hgpatch":
103 elif format == "hgpatch":
104 # parse values when importing the result of an hg export
104 # parse values when importing the result of an hg export
105 if line.startswith("# User "):
105 if line.startswith("# User "):
106 user = line[7:]
106 user = line[7:]
107 elif line.startswith("# Date "):
107 elif line.startswith("# Date "):
108 date = line[7:]
108 date = line[7:]
109 elif line.startswith("# Parent "):
109 elif line.startswith("# Parent "):
110 parent = line[9:]
110 parent = line[9:]
111 elif line.startswith("# Branch "):
111 elif line.startswith("# Branch "):
112 branch = line[9:]
112 branch = line[9:]
113 elif line.startswith("# Node ID "):
113 elif line.startswith("# Node ID "):
114 nodeid = line[10:]
114 nodeid = line[10:]
115 elif not line.startswith("# ") and line:
115 elif not line.startswith("# ") and line:
116 message.append(line)
116 message.append(line)
117 format = None
117 format = None
118 elif line == '# HG changeset patch':
118 elif line == '# HG changeset patch':
119 message = []
119 message = []
120 format = "hgpatch"
120 format = "hgpatch"
121 elif (format != "tagdone" and (line.startswith("Subject: ") or
121 elif (format != "tagdone" and (line.startswith("Subject: ") or
122 line.startswith("subject: "))):
122 line.startswith("subject: "))):
123 subject = line[9:]
123 subject = line[9:]
124 format = "tag"
124 format = "tag"
125 elif (format != "tagdone" and (line.startswith("From: ") or
125 elif (format != "tagdone" and (line.startswith("From: ") or
126 line.startswith("from: "))):
126 line.startswith("from: "))):
127 user = line[6:]
127 user = line[6:]
128 format = "tag"
128 format = "tag"
129 elif (format != "tagdone" and (line.startswith("Date: ") or
129 elif (format != "tagdone" and (line.startswith("Date: ") or
130 line.startswith("date: "))):
130 line.startswith("date: "))):
131 date = line[6:]
131 date = line[6:]
132 format = "tag"
132 format = "tag"
133 elif format == "tag" and line == "":
133 elif format == "tag" and line == "":
134 # when looking for tags (subject: from: etc) they
134 # when looking for tags (subject: from: etc) they
135 # end once you find a blank line in the source
135 # end once you find a blank line in the source
136 format = "tagdone"
136 format = "tagdone"
137 elif message or line:
137 elif message or line:
138 message.append(line)
138 message.append(line)
139 comments.append(line)
139 comments.append(line)
140
140
141 eatdiff(message)
141 eatdiff(message)
142 eatdiff(comments)
142 eatdiff(comments)
143 # Remember the exact starting line of the patch diffs before consuming
143 # Remember the exact starting line of the patch diffs before consuming
144 # empty lines, for external use by TortoiseHg and others
144 # empty lines, for external use by TortoiseHg and others
145 self.diffstartline = len(comments)
145 self.diffstartline = len(comments)
146 eatempty(message)
146 eatempty(message)
147 eatempty(comments)
147 eatempty(comments)
148
148
149 # make sure message isn't empty
149 # make sure message isn't empty
150 if format and format.startswith("tag") and subject:
150 if format and format.startswith("tag") and subject:
151 message.insert(0, "")
151 message.insert(0, "")
152 message.insert(0, subject)
152 message.insert(0, subject)
153
153
154 self.message = message
154 self.message = message
155 self.comments = comments
155 self.comments = comments
156 self.user = user
156 self.user = user
157 self.date = date
157 self.date = date
158 self.parent = parent
158 self.parent = parent
159 # nodeid and branch are for external use by TortoiseHg and others
159 # nodeid and branch are for external use by TortoiseHg and others
160 self.nodeid = nodeid
160 self.nodeid = nodeid
161 self.branch = branch
161 self.branch = branch
162 self.haspatch = diffstart > 1
162 self.haspatch = diffstart > 1
163 self.plainmode = plainmode
163 self.plainmode = plainmode
164
164
165 def setuser(self, user):
165 def setuser(self, user):
166 if not self.updateheader(['From: ', '# User '], user):
166 if not self.updateheader(['From: ', '# User '], user):
167 try:
167 try:
168 patchheaderat = self.comments.index('# HG changeset patch')
168 patchheaderat = self.comments.index('# HG changeset patch')
169 self.comments.insert(patchheaderat + 1, '# User ' + user)
169 self.comments.insert(patchheaderat + 1, '# User ' + user)
170 except ValueError:
170 except ValueError:
171 if self.plainmode or self._hasheader(['Date: ']):
171 if self.plainmode or self._hasheader(['Date: ']):
172 self.comments = ['From: ' + user] + self.comments
172 self.comments = ['From: ' + user] + self.comments
173 else:
173 else:
174 tmp = ['# HG changeset patch', '# User ' + user, '']
174 tmp = ['# HG changeset patch', '# User ' + user, '']
175 self.comments = tmp + self.comments
175 self.comments = tmp + self.comments
176 self.user = user
176 self.user = user
177
177
178 def setdate(self, date):
178 def setdate(self, date):
179 if not self.updateheader(['Date: ', '# Date '], date):
179 if not self.updateheader(['Date: ', '# Date '], date):
180 try:
180 try:
181 patchheaderat = self.comments.index('# HG changeset patch')
181 patchheaderat = self.comments.index('# HG changeset patch')
182 self.comments.insert(patchheaderat + 1, '# Date ' + date)
182 self.comments.insert(patchheaderat + 1, '# Date ' + date)
183 except ValueError:
183 except ValueError:
184 if self.plainmode or self._hasheader(['From: ']):
184 if self.plainmode or self._hasheader(['From: ']):
185 self.comments = ['Date: ' + date] + self.comments
185 self.comments = ['Date: ' + date] + self.comments
186 else:
186 else:
187 tmp = ['# HG changeset patch', '# Date ' + date, '']
187 tmp = ['# HG changeset patch', '# Date ' + date, '']
188 self.comments = tmp + self.comments
188 self.comments = tmp + self.comments
189 self.date = date
189 self.date = date
190
190
191 def setparent(self, parent):
191 def setparent(self, parent):
192 if not self.updateheader(['# Parent '], parent):
192 if not self.updateheader(['# Parent '], parent):
193 try:
193 try:
194 patchheaderat = self.comments.index('# HG changeset patch')
194 patchheaderat = self.comments.index('# HG changeset patch')
195 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
195 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
196 except ValueError:
196 except ValueError:
197 pass
197 pass
198 self.parent = parent
198 self.parent = parent
199
199
200 def setmessage(self, message):
200 def setmessage(self, message):
201 if self.comments:
201 if self.comments:
202 self._delmsg()
202 self._delmsg()
203 self.message = [message]
203 self.message = [message]
204 self.comments += self.message
204 self.comments += self.message
205
205
206 def updateheader(self, prefixes, new):
206 def updateheader(self, prefixes, new):
207 '''Update all references to a field in the patch header.
207 '''Update all references to a field in the patch header.
208 Return whether the field is present.'''
208 Return whether the field is present.'''
209 res = False
209 res = False
210 for prefix in prefixes:
210 for prefix in prefixes:
211 for i in xrange(len(self.comments)):
211 for i in xrange(len(self.comments)):
212 if self.comments[i].startswith(prefix):
212 if self.comments[i].startswith(prefix):
213 self.comments[i] = prefix + new
213 self.comments[i] = prefix + new
214 res = True
214 res = True
215 break
215 break
216 return res
216 return res
217
217
218 def _hasheader(self, prefixes):
218 def _hasheader(self, prefixes):
219 '''Check if a header starts with any of the given prefixes.'''
219 '''Check if a header starts with any of the given prefixes.'''
220 for prefix in prefixes:
220 for prefix in prefixes:
221 for comment in self.comments:
221 for comment in self.comments:
222 if comment.startswith(prefix):
222 if comment.startswith(prefix):
223 return True
223 return True
224 return False
224 return False
225
225
226 def __str__(self):
226 def __str__(self):
227 if not self.comments:
227 if not self.comments:
228 return ''
228 return ''
229 return '\n'.join(self.comments) + '\n\n'
229 return '\n'.join(self.comments) + '\n\n'
230
230
231 def _delmsg(self):
231 def _delmsg(self):
232 '''Remove existing message, keeping the rest of the comments fields.
232 '''Remove existing message, keeping the rest of the comments fields.
233 If comments contains 'subject: ', message will prepend
233 If comments contains 'subject: ', message will prepend
234 the field and a blank line.'''
234 the field and a blank line.'''
235 if self.message:
235 if self.message:
236 subj = 'subject: ' + self.message[0].lower()
236 subj = 'subject: ' + self.message[0].lower()
237 for i in xrange(len(self.comments)):
237 for i in xrange(len(self.comments)):
238 if subj == self.comments[i].lower():
238 if subj == self.comments[i].lower():
239 del self.comments[i]
239 del self.comments[i]
240 self.message = self.message[2:]
240 self.message = self.message[2:]
241 break
241 break
242 ci = 0
242 ci = 0
243 for mi in self.message:
243 for mi in self.message:
244 while mi != self.comments[ci]:
244 while mi != self.comments[ci]:
245 ci += 1
245 ci += 1
246 del self.comments[ci]
246 del self.comments[ci]
247
247
248 class queue(object):
248 class queue(object):
249 def __init__(self, ui, path, patchdir=None):
249 def __init__(self, ui, path, patchdir=None):
250 self.basepath = path
250 self.basepath = path
251 try:
251 try:
252 fh = open(os.path.join(path, 'patches.queue'))
252 fh = open(os.path.join(path, 'patches.queue'))
253 cur = fh.read().rstrip()
253 cur = fh.read().rstrip()
254 fh.close()
254 fh.close()
255 if not cur:
255 if not cur:
256 curpath = os.path.join(path, 'patches')
256 curpath = os.path.join(path, 'patches')
257 else:
257 else:
258 curpath = os.path.join(path, 'patches-' + cur)
258 curpath = os.path.join(path, 'patches-' + cur)
259 except IOError:
259 except IOError:
260 curpath = os.path.join(path, 'patches')
260 curpath = os.path.join(path, 'patches')
261 self.path = patchdir or curpath
261 self.path = patchdir or curpath
262 self.opener = scmutil.opener(self.path)
262 self.opener = scmutil.opener(self.path)
263 self.ui = ui
263 self.ui = ui
264 self.applied_dirty = 0
264 self.applied_dirty = 0
265 self.series_dirty = 0
265 self.series_dirty = 0
266 self.added = []
266 self.added = []
267 self.series_path = "series"
267 self.series_path = "series"
268 self.status_path = "status"
268 self.status_path = "status"
269 self.guards_path = "guards"
269 self.guards_path = "guards"
270 self.active_guards = None
270 self.active_guards = None
271 self.guards_dirty = False
271 self.guards_dirty = False
272 # Handle mq.git as a bool with extended values
272 # Handle mq.git as a bool with extended values
273 try:
273 try:
274 gitmode = ui.configbool('mq', 'git', None)
274 gitmode = ui.configbool('mq', 'git', None)
275 if gitmode is None:
275 if gitmode is None:
276 raise error.ConfigError()
276 raise error.ConfigError()
277 self.gitmode = gitmode and 'yes' or 'no'
277 self.gitmode = gitmode and 'yes' or 'no'
278 except error.ConfigError:
278 except error.ConfigError:
279 self.gitmode = ui.config('mq', 'git', 'auto').lower()
279 self.gitmode = ui.config('mq', 'git', 'auto').lower()
280 self.plainmode = ui.configbool('mq', 'plain', False)
280 self.plainmode = ui.configbool('mq', 'plain', False)
281
281
282 @util.propertycache
282 @util.propertycache
283 def applied(self):
283 def applied(self):
284 if os.path.exists(self.join(self.status_path)):
284 if os.path.exists(self.join(self.status_path)):
285 def parselines(lines):
285 def parselines(lines):
286 for l in lines:
286 for l in lines:
287 entry = l.split(':', 1)
287 entry = l.split(':', 1)
288 if len(entry) > 1:
288 if len(entry) > 1:
289 n, name = entry
289 n, name = entry
290 yield statusentry(bin(n), name)
290 yield statusentry(bin(n), name)
291 elif l.strip():
291 elif l.strip():
292 self.ui.warn(_('malformated mq status line: %s\n') % entry)
292 self.ui.warn(_('malformated mq status line: %s\n') % entry)
293 # else we ignore empty lines
293 # else we ignore empty lines
294 lines = self.opener(self.status_path).read().splitlines()
294 lines = self.opener(self.status_path).read().splitlines()
295 return list(parselines(lines))
295 return list(parselines(lines))
296 return []
296 return []
297
297
298 @util.propertycache
298 @util.propertycache
299 def full_series(self):
299 def full_series(self):
300 if os.path.exists(self.join(self.series_path)):
300 if os.path.exists(self.join(self.series_path)):
301 return self.opener(self.series_path).read().splitlines()
301 return self.opener(self.series_path).read().splitlines()
302 return []
302 return []
303
303
304 @util.propertycache
304 @util.propertycache
305 def series(self):
305 def series(self):
306 self.parse_series()
306 self.parse_series()
307 return self.series
307 return self.series
308
308
309 @util.propertycache
309 @util.propertycache
310 def series_guards(self):
310 def series_guards(self):
311 self.parse_series()
311 self.parse_series()
312 return self.series_guards
312 return self.series_guards
313
313
314 def invalidate(self):
314 def invalidate(self):
315 for a in 'applied full_series series series_guards'.split():
315 for a in 'applied full_series series series_guards'.split():
316 if a in self.__dict__:
316 if a in self.__dict__:
317 delattr(self, a)
317 delattr(self, a)
318 self.applied_dirty = 0
318 self.applied_dirty = 0
319 self.series_dirty = 0
319 self.series_dirty = 0
320 self.guards_dirty = False
320 self.guards_dirty = False
321 self.active_guards = None
321 self.active_guards = None
322
322
323 def diffopts(self, opts={}, patchfn=None):
323 def diffopts(self, opts={}, patchfn=None):
324 diffopts = patch.diffopts(self.ui, opts)
324 diffopts = patch.diffopts(self.ui, opts)
325 if self.gitmode == 'auto':
325 if self.gitmode == 'auto':
326 diffopts.upgrade = True
326 diffopts.upgrade = True
327 elif self.gitmode == 'keep':
327 elif self.gitmode == 'keep':
328 pass
328 pass
329 elif self.gitmode in ('yes', 'no'):
329 elif self.gitmode in ('yes', 'no'):
330 diffopts.git = self.gitmode == 'yes'
330 diffopts.git = self.gitmode == 'yes'
331 else:
331 else:
332 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
332 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
333 ' got %s') % self.gitmode)
333 ' got %s') % self.gitmode)
334 if patchfn:
334 if patchfn:
335 diffopts = self.patchopts(diffopts, patchfn)
335 diffopts = self.patchopts(diffopts, patchfn)
336 return diffopts
336 return diffopts
337
337
338 def patchopts(self, diffopts, *patches):
338 def patchopts(self, diffopts, *patches):
339 """Return a copy of input diff options with git set to true if
339 """Return a copy of input diff options with git set to true if
340 referenced patch is a git patch and should be preserved as such.
340 referenced patch is a git patch and should be preserved as such.
341 """
341 """
342 diffopts = diffopts.copy()
342 diffopts = diffopts.copy()
343 if not diffopts.git and self.gitmode == 'keep':
343 if not diffopts.git and self.gitmode == 'keep':
344 for patchfn in patches:
344 for patchfn in patches:
345 patchf = self.opener(patchfn, 'r')
345 patchf = self.opener(patchfn, 'r')
346 # if the patch was a git patch, refresh it as a git patch
346 # if the patch was a git patch, refresh it as a git patch
347 for line in patchf:
347 for line in patchf:
348 if line.startswith('diff --git'):
348 if line.startswith('diff --git'):
349 diffopts.git = True
349 diffopts.git = True
350 break
350 break
351 patchf.close()
351 patchf.close()
352 return diffopts
352 return diffopts
353
353
354 def join(self, *p):
354 def join(self, *p):
355 return os.path.join(self.path, *p)
355 return os.path.join(self.path, *p)
356
356
357 def find_series(self, patch):
357 def find_series(self, patch):
358 def matchpatch(l):
358 def matchpatch(l):
359 l = l.split('#', 1)[0]
359 l = l.split('#', 1)[0]
360 return l.strip() == patch
360 return l.strip() == patch
361 for index, l in enumerate(self.full_series):
361 for index, l in enumerate(self.full_series):
362 if matchpatch(l):
362 if matchpatch(l):
363 return index
363 return index
364 return None
364 return None
365
365
366 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
366 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
367
367
368 def parse_series(self):
368 def parse_series(self):
369 self.series = []
369 self.series = []
370 self.series_guards = []
370 self.series_guards = []
371 for l in self.full_series:
371 for l in self.full_series:
372 h = l.find('#')
372 h = l.find('#')
373 if h == -1:
373 if h == -1:
374 patch = l
374 patch = l
375 comment = ''
375 comment = ''
376 elif h == 0:
376 elif h == 0:
377 continue
377 continue
378 else:
378 else:
379 patch = l[:h]
379 patch = l[:h]
380 comment = l[h:]
380 comment = l[h:]
381 patch = patch.strip()
381 patch = patch.strip()
382 if patch:
382 if patch:
383 if patch in self.series:
383 if patch in self.series:
384 raise util.Abort(_('%s appears more than once in %s') %
384 raise util.Abort(_('%s appears more than once in %s') %
385 (patch, self.join(self.series_path)))
385 (patch, self.join(self.series_path)))
386 self.series.append(patch)
386 self.series.append(patch)
387 self.series_guards.append(self.guard_re.findall(comment))
387 self.series_guards.append(self.guard_re.findall(comment))
388
388
389 def check_guard(self, guard):
389 def check_guard(self, guard):
390 if not guard:
390 if not guard:
391 return _('guard cannot be an empty string')
391 return _('guard cannot be an empty string')
392 bad_chars = '# \t\r\n\f'
392 bad_chars = '# \t\r\n\f'
393 first = guard[0]
393 first = guard[0]
394 if first in '-+':
394 if first in '-+':
395 return (_('guard %r starts with invalid character: %r') %
395 return (_('guard %r starts with invalid character: %r') %
396 (guard, first))
396 (guard, first))
397 for c in bad_chars:
397 for c in bad_chars:
398 if c in guard:
398 if c in guard:
399 return _('invalid character in guard %r: %r') % (guard, c)
399 return _('invalid character in guard %r: %r') % (guard, c)
400
400
401 def set_active(self, guards):
401 def set_active(self, guards):
402 for guard in guards:
402 for guard in guards:
403 bad = self.check_guard(guard)
403 bad = self.check_guard(guard)
404 if bad:
404 if bad:
405 raise util.Abort(bad)
405 raise util.Abort(bad)
406 guards = sorted(set(guards))
406 guards = sorted(set(guards))
407 self.ui.debug('active guards: %s\n' % ' '.join(guards))
407 self.ui.debug('active guards: %s\n' % ' '.join(guards))
408 self.active_guards = guards
408 self.active_guards = guards
409 self.guards_dirty = True
409 self.guards_dirty = True
410
410
411 def active(self):
411 def active(self):
412 if self.active_guards is None:
412 if self.active_guards is None:
413 self.active_guards = []
413 self.active_guards = []
414 try:
414 try:
415 guards = self.opener(self.guards_path).read().split()
415 guards = self.opener(self.guards_path).read().split()
416 except IOError, err:
416 except IOError, err:
417 if err.errno != errno.ENOENT:
417 if err.errno != errno.ENOENT:
418 raise
418 raise
419 guards = []
419 guards = []
420 for i, guard in enumerate(guards):
420 for i, guard in enumerate(guards):
421 bad = self.check_guard(guard)
421 bad = self.check_guard(guard)
422 if bad:
422 if bad:
423 self.ui.warn('%s:%d: %s\n' %
423 self.ui.warn('%s:%d: %s\n' %
424 (self.join(self.guards_path), i + 1, bad))
424 (self.join(self.guards_path), i + 1, bad))
425 else:
425 else:
426 self.active_guards.append(guard)
426 self.active_guards.append(guard)
427 return self.active_guards
427 return self.active_guards
428
428
429 def set_guards(self, idx, guards):
429 def set_guards(self, idx, guards):
430 for g in guards:
430 for g in guards:
431 if len(g) < 2:
431 if len(g) < 2:
432 raise util.Abort(_('guard %r too short') % g)
432 raise util.Abort(_('guard %r too short') % g)
433 if g[0] not in '-+':
433 if g[0] not in '-+':
434 raise util.Abort(_('guard %r starts with invalid char') % g)
434 raise util.Abort(_('guard %r starts with invalid char') % g)
435 bad = self.check_guard(g[1:])
435 bad = self.check_guard(g[1:])
436 if bad:
436 if bad:
437 raise util.Abort(bad)
437 raise util.Abort(bad)
438 drop = self.guard_re.sub('', self.full_series[idx])
438 drop = self.guard_re.sub('', self.full_series[idx])
439 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
439 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
440 self.parse_series()
440 self.parse_series()
441 self.series_dirty = True
441 self.series_dirty = True
442
442
443 def pushable(self, idx):
443 def pushable(self, idx):
444 if isinstance(idx, str):
444 if isinstance(idx, str):
445 idx = self.series.index(idx)
445 idx = self.series.index(idx)
446 patchguards = self.series_guards[idx]
446 patchguards = self.series_guards[idx]
447 if not patchguards:
447 if not patchguards:
448 return True, None
448 return True, None
449 guards = self.active()
449 guards = self.active()
450 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
450 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
451 if exactneg:
451 if exactneg:
452 return False, exactneg[0]
452 return False, exactneg[0]
453 pos = [g for g in patchguards if g[0] == '+']
453 pos = [g for g in patchguards if g[0] == '+']
454 exactpos = [g for g in pos if g[1:] in guards]
454 exactpos = [g for g in pos if g[1:] in guards]
455 if pos:
455 if pos:
456 if exactpos:
456 if exactpos:
457 return True, exactpos[0]
457 return True, exactpos[0]
458 return False, pos
458 return False, pos
459 return True, ''
459 return True, ''
460
460
461 def explain_pushable(self, idx, all_patches=False):
461 def explain_pushable(self, idx, all_patches=False):
462 write = all_patches and self.ui.write or self.ui.warn
462 write = all_patches and self.ui.write or self.ui.warn
463 if all_patches or self.ui.verbose:
463 if all_patches or self.ui.verbose:
464 if isinstance(idx, str):
464 if isinstance(idx, str):
465 idx = self.series.index(idx)
465 idx = self.series.index(idx)
466 pushable, why = self.pushable(idx)
466 pushable, why = self.pushable(idx)
467 if all_patches and pushable:
467 if all_patches and pushable:
468 if why is None:
468 if why is None:
469 write(_('allowing %s - no guards in effect\n') %
469 write(_('allowing %s - no guards in effect\n') %
470 self.series[idx])
470 self.series[idx])
471 else:
471 else:
472 if not why:
472 if not why:
473 write(_('allowing %s - no matching negative guards\n') %
473 write(_('allowing %s - no matching negative guards\n') %
474 self.series[idx])
474 self.series[idx])
475 else:
475 else:
476 write(_('allowing %s - guarded by %r\n') %
476 write(_('allowing %s - guarded by %r\n') %
477 (self.series[idx], why))
477 (self.series[idx], why))
478 if not pushable:
478 if not pushable:
479 if why:
479 if why:
480 write(_('skipping %s - guarded by %r\n') %
480 write(_('skipping %s - guarded by %r\n') %
481 (self.series[idx], why))
481 (self.series[idx], why))
482 else:
482 else:
483 write(_('skipping %s - no matching guards\n') %
483 write(_('skipping %s - no matching guards\n') %
484 self.series[idx])
484 self.series[idx])
485
485
486 def save_dirty(self):
486 def save_dirty(self):
487 def write_list(items, path):
487 def write_list(items, path):
488 fp = self.opener(path, 'w')
488 fp = self.opener(path, 'w')
489 for i in items:
489 for i in items:
490 fp.write("%s\n" % i)
490 fp.write("%s\n" % i)
491 fp.close()
491 fp.close()
492 if self.applied_dirty:
492 if self.applied_dirty:
493 write_list(map(str, self.applied), self.status_path)
493 write_list(map(str, self.applied), self.status_path)
494 if self.series_dirty:
494 if self.series_dirty:
495 write_list(self.full_series, self.series_path)
495 write_list(self.full_series, self.series_path)
496 if self.guards_dirty:
496 if self.guards_dirty:
497 write_list(self.active_guards, self.guards_path)
497 write_list(self.active_guards, self.guards_path)
498 if self.added:
498 if self.added:
499 qrepo = self.qrepo()
499 qrepo = self.qrepo()
500 if qrepo:
500 if qrepo:
501 qrepo[None].add(f for f in self.added if f not in qrepo[None])
501 qrepo[None].add(f for f in self.added if f not in qrepo[None])
502 self.added = []
502 self.added = []
503
503
504 def removeundo(self, repo):
504 def removeundo(self, repo):
505 undo = repo.sjoin('undo')
505 undo = repo.sjoin('undo')
506 if not os.path.exists(undo):
506 if not os.path.exists(undo):
507 return
507 return
508 try:
508 try:
509 os.unlink(undo)
509 os.unlink(undo)
510 except OSError, inst:
510 except OSError, inst:
511 self.ui.warn(_('error removing undo: %s\n') % str(inst))
511 self.ui.warn(_('error removing undo: %s\n') % str(inst))
512
512
513 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
513 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
514 fp=None, changes=None, opts={}):
514 fp=None, changes=None, opts={}):
515 stat = opts.get('stat')
515 stat = opts.get('stat')
516 m = cmdutil.match(repo, files, opts)
516 m = cmdutil.match(repo, files, opts)
517 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
517 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
518 changes, stat, fp)
518 changes, stat, fp)
519
519
520 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
520 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
521 # first try just applying the patch
521 # first try just applying the patch
522 (err, n) = self.apply(repo, [patch], update_status=False,
522 (err, n) = self.apply(repo, [patch], update_status=False,
523 strict=True, merge=rev)
523 strict=True, merge=rev)
524
524
525 if err == 0:
525 if err == 0:
526 return (err, n)
526 return (err, n)
527
527
528 if n is None:
528 if n is None:
529 raise util.Abort(_("apply failed for patch %s") % patch)
529 raise util.Abort(_("apply failed for patch %s") % patch)
530
530
531 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
531 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
532
532
533 # apply failed, strip away that rev and merge.
533 # apply failed, strip away that rev and merge.
534 hg.clean(repo, head)
534 hg.clean(repo, head)
535 self.strip(repo, [n], update=False, backup='strip')
535 self.strip(repo, [n], update=False, backup='strip')
536
536
537 ctx = repo[rev]
537 ctx = repo[rev]
538 ret = hg.merge(repo, rev)
538 ret = hg.merge(repo, rev)
539 if ret:
539 if ret:
540 raise util.Abort(_("update returned %d") % ret)
540 raise util.Abort(_("update returned %d") % ret)
541 n = repo.commit(ctx.description(), ctx.user(), force=True)
541 n = repo.commit(ctx.description(), ctx.user(), force=True)
542 if n is None:
542 if n is None:
543 raise util.Abort(_("repo commit failed"))
543 raise util.Abort(_("repo commit failed"))
544 try:
544 try:
545 ph = patchheader(mergeq.join(patch), self.plainmode)
545 ph = patchheader(mergeq.join(patch), self.plainmode)
546 except:
546 except:
547 raise util.Abort(_("unable to read %s") % patch)
547 raise util.Abort(_("unable to read %s") % patch)
548
548
549 diffopts = self.patchopts(diffopts, patch)
549 diffopts = self.patchopts(diffopts, patch)
550 patchf = self.opener(patch, "w")
550 patchf = self.opener(patch, "w")
551 comments = str(ph)
551 comments = str(ph)
552 if comments:
552 if comments:
553 patchf.write(comments)
553 patchf.write(comments)
554 self.printdiff(repo, diffopts, head, n, fp=patchf)
554 self.printdiff(repo, diffopts, head, n, fp=patchf)
555 patchf.close()
555 patchf.close()
556 self.removeundo(repo)
556 self.removeundo(repo)
557 return (0, n)
557 return (0, n)
558
558
559 def qparents(self, repo, rev=None):
559 def qparents(self, repo, rev=None):
560 if rev is None:
560 if rev is None:
561 (p1, p2) = repo.dirstate.parents()
561 (p1, p2) = repo.dirstate.parents()
562 if p2 == nullid:
562 if p2 == nullid:
563 return p1
563 return p1
564 if not self.applied:
564 if not self.applied:
565 return None
565 return None
566 return self.applied[-1].node
566 return self.applied[-1].node
567 p1, p2 = repo.changelog.parents(rev)
567 p1, p2 = repo.changelog.parents(rev)
568 if p2 != nullid and p2 in [x.node for x in self.applied]:
568 if p2 != nullid and p2 in [x.node for x in self.applied]:
569 return p2
569 return p2
570 return p1
570 return p1
571
571
572 def mergepatch(self, repo, mergeq, series, diffopts):
572 def mergepatch(self, repo, mergeq, series, diffopts):
573 if not self.applied:
573 if not self.applied:
574 # each of the patches merged in will have two parents. This
574 # each of the patches merged in will have two parents. This
575 # can confuse the qrefresh, qdiff, and strip code because it
575 # can confuse the qrefresh, qdiff, and strip code because it
576 # needs to know which parent is actually in the patch queue.
576 # needs to know which parent is actually in the patch queue.
577 # so, we insert a merge marker with only one parent. This way
577 # so, we insert a merge marker with only one parent. This way
578 # the first patch in the queue is never a merge patch
578 # the first patch in the queue is never a merge patch
579 #
579 #
580 pname = ".hg.patches.merge.marker"
580 pname = ".hg.patches.merge.marker"
581 n = repo.commit('[mq]: merge marker', force=True)
581 n = repo.commit('[mq]: merge marker', force=True)
582 self.removeundo(repo)
582 self.removeundo(repo)
583 self.applied.append(statusentry(n, pname))
583 self.applied.append(statusentry(n, pname))
584 self.applied_dirty = 1
584 self.applied_dirty = 1
585
585
586 head = self.qparents(repo)
586 head = self.qparents(repo)
587
587
588 for patch in series:
588 for patch in series:
589 patch = mergeq.lookup(patch, strict=True)
589 patch = mergeq.lookup(patch, strict=True)
590 if not patch:
590 if not patch:
591 self.ui.warn(_("patch %s does not exist\n") % patch)
591 self.ui.warn(_("patch %s does not exist\n") % patch)
592 return (1, None)
592 return (1, None)
593 pushable, reason = self.pushable(patch)
593 pushable, reason = self.pushable(patch)
594 if not pushable:
594 if not pushable:
595 self.explain_pushable(patch, all_patches=True)
595 self.explain_pushable(patch, all_patches=True)
596 continue
596 continue
597 info = mergeq.isapplied(patch)
597 info = mergeq.isapplied(patch)
598 if not info:
598 if not info:
599 self.ui.warn(_("patch %s is not applied\n") % patch)
599 self.ui.warn(_("patch %s is not applied\n") % patch)
600 return (1, None)
600 return (1, None)
601 rev = info[1]
601 rev = info[1]
602 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
602 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
603 if head:
603 if head:
604 self.applied.append(statusentry(head, patch))
604 self.applied.append(statusentry(head, patch))
605 self.applied_dirty = 1
605 self.applied_dirty = 1
606 if err:
606 if err:
607 return (err, head)
607 return (err, head)
608 self.save_dirty()
608 self.save_dirty()
609 return (0, head)
609 return (0, head)
610
610
611 def patch(self, repo, patchfile):
611 def patch(self, repo, patchfile):
612 '''Apply patchfile to the working directory.
612 '''Apply patchfile to the working directory.
613 patchfile: name of patch file'''
613 patchfile: name of patch file'''
614 files = {}
614 files = {}
615 try:
615 try:
616 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
616 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
617 files=files, eolmode=None)
617 files=files, eolmode=None)
618 except Exception, inst:
618 except Exception, inst:
619 self.ui.note(str(inst) + '\n')
619 self.ui.note(str(inst) + '\n')
620 if not self.ui.verbose:
620 if not self.ui.verbose:
621 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
621 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
622 return (False, files, False)
622 return (False, files, False)
623
623
624 return (True, files, fuzz)
624 return (True, files, fuzz)
625
625
626 def apply(self, repo, series, list=False, update_status=True,
626 def apply(self, repo, series, list=False, update_status=True,
627 strict=False, patchdir=None, merge=None, all_files=None):
627 strict=False, patchdir=None, merge=None, all_files=None):
628 wlock = lock = tr = None
628 wlock = lock = tr = None
629 try:
629 try:
630 wlock = repo.wlock()
630 wlock = repo.wlock()
631 lock = repo.lock()
631 lock = repo.lock()
632 tr = repo.transaction("qpush")
632 tr = repo.transaction("qpush")
633 try:
633 try:
634 ret = self._apply(repo, series, list, update_status,
634 ret = self._apply(repo, series, list, update_status,
635 strict, patchdir, merge, all_files=all_files)
635 strict, patchdir, merge, all_files=all_files)
636 tr.close()
636 tr.close()
637 self.save_dirty()
637 self.save_dirty()
638 return ret
638 return ret
639 except:
639 except:
640 try:
640 try:
641 tr.abort()
641 tr.abort()
642 finally:
642 finally:
643 repo.invalidate()
643 repo.invalidate()
644 repo.dirstate.invalidate()
644 repo.dirstate.invalidate()
645 raise
645 raise
646 finally:
646 finally:
647 release(tr, lock, wlock)
647 release(tr, lock, wlock)
648 self.removeundo(repo)
648 self.removeundo(repo)
649
649
650 def _apply(self, repo, series, list=False, update_status=True,
650 def _apply(self, repo, series, list=False, update_status=True,
651 strict=False, patchdir=None, merge=None, all_files=None):
651 strict=False, patchdir=None, merge=None, all_files=None):
652 '''returns (error, hash)
652 '''returns (error, hash)
653 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
653 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
654 # TODO unify with commands.py
654 # TODO unify with commands.py
655 if not patchdir:
655 if not patchdir:
656 patchdir = self.path
656 patchdir = self.path
657 err = 0
657 err = 0
658 n = None
658 n = None
659 for patchname in series:
659 for patchname in series:
660 pushable, reason = self.pushable(patchname)
660 pushable, reason = self.pushable(patchname)
661 if not pushable:
661 if not pushable:
662 self.explain_pushable(patchname, all_patches=True)
662 self.explain_pushable(patchname, all_patches=True)
663 continue
663 continue
664 self.ui.status(_("applying %s\n") % patchname)
664 self.ui.status(_("applying %s\n") % patchname)
665 pf = os.path.join(patchdir, patchname)
665 pf = os.path.join(patchdir, patchname)
666
666
667 try:
667 try:
668 ph = patchheader(self.join(patchname), self.plainmode)
668 ph = patchheader(self.join(patchname), self.plainmode)
669 except:
669 except:
670 self.ui.warn(_("unable to read %s\n") % patchname)
670 self.ui.warn(_("unable to read %s\n") % patchname)
671 err = 1
671 err = 1
672 break
672 break
673
673
674 message = ph.message
674 message = ph.message
675 if not message:
675 if not message:
676 # The commit message should not be translated
676 # The commit message should not be translated
677 message = "imported patch %s\n" % patchname
677 message = "imported patch %s\n" % patchname
678 else:
678 else:
679 if list:
679 if list:
680 # The commit message should not be translated
680 # The commit message should not be translated
681 message.append("\nimported patch %s" % patchname)
681 message.append("\nimported patch %s" % patchname)
682 message = '\n'.join(message)
682 message = '\n'.join(message)
683
683
684 if ph.haspatch:
684 if ph.haspatch:
685 (patcherr, files, fuzz) = self.patch(repo, pf)
685 (patcherr, files, fuzz) = self.patch(repo, pf)
686 if all_files is not None:
686 if all_files is not None:
687 all_files.update(files)
687 all_files.update(files)
688 patcherr = not patcherr
688 patcherr = not patcherr
689 else:
689 else:
690 self.ui.warn(_("patch %s is empty\n") % patchname)
690 self.ui.warn(_("patch %s is empty\n") % patchname)
691 patcherr, files, fuzz = 0, [], 0
691 patcherr, files, fuzz = 0, [], 0
692
692
693 if merge and files:
693 if merge and files:
694 # Mark as removed/merged and update dirstate parent info
694 # Mark as removed/merged and update dirstate parent info
695 removed = []
695 removed = []
696 merged = []
696 merged = []
697 for f in files:
697 for f in files:
698 if os.path.lexists(repo.wjoin(f)):
698 if os.path.lexists(repo.wjoin(f)):
699 merged.append(f)
699 merged.append(f)
700 else:
700 else:
701 removed.append(f)
701 removed.append(f)
702 for f in removed:
702 for f in removed:
703 repo.dirstate.remove(f)
703 repo.dirstate.remove(f)
704 for f in merged:
704 for f in merged:
705 repo.dirstate.merge(f)
705 repo.dirstate.merge(f)
706 p1, p2 = repo.dirstate.parents()
706 p1, p2 = repo.dirstate.parents()
707 repo.dirstate.setparents(p1, merge)
707 repo.dirstate.setparents(p1, merge)
708
708
709 files = cmdutil.updatedir(self.ui, repo, files)
709 files = cmdutil.updatedir(self.ui, repo, files)
710 match = cmdutil.matchfiles(repo, files or [])
710 match = cmdutil.matchfiles(repo, files or [])
711 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
711 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
712
712
713 if n is None:
713 if n is None:
714 raise util.Abort(_("repository commit failed"))
714 raise util.Abort(_("repository commit failed"))
715
715
716 if update_status:
716 if update_status:
717 self.applied.append(statusentry(n, patchname))
717 self.applied.append(statusentry(n, patchname))
718
718
719 if patcherr:
719 if patcherr:
720 self.ui.warn(_("patch failed, rejects left in working dir\n"))
720 self.ui.warn(_("patch failed, rejects left in working dir\n"))
721 err = 2
721 err = 2
722 break
722 break
723
723
724 if fuzz and strict:
724 if fuzz and strict:
725 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
725 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
726 err = 3
726 err = 3
727 break
727 break
728 return (err, n)
728 return (err, n)
729
729
730 def _cleanup(self, patches, numrevs, keep=False):
730 def _cleanup(self, patches, numrevs, keep=False):
731 if not keep:
731 if not keep:
732 r = self.qrepo()
732 r = self.qrepo()
733 if r:
733 if r:
734 r[None].remove(patches, True)
734 r[None].remove(patches, True)
735 else:
735 else:
736 for p in patches:
736 for p in patches:
737 os.unlink(self.join(p))
737 os.unlink(self.join(p))
738
738
739 if numrevs:
739 if numrevs:
740 qfinished = self.applied[:numrevs]
740 qfinished = self.applied[:numrevs]
741 del self.applied[:numrevs]
741 del self.applied[:numrevs]
742 self.applied_dirty = 1
742 self.applied_dirty = 1
743
743
744 unknown = []
744 unknown = []
745
745
746 for (i, p) in sorted([(self.find_series(p), p) for p in patches],
746 for (i, p) in sorted([(self.find_series(p), p) for p in patches],
747 reverse=True):
747 reverse=True):
748 if i is not None:
748 if i is not None:
749 del self.full_series[i]
749 del self.full_series[i]
750 else:
750 else:
751 unknown.append(p)
751 unknown.append(p)
752
752
753 if unknown:
753 if unknown:
754 if numrevs:
754 if numrevs:
755 rev = dict((entry.name, entry.node) for entry in qfinished)
755 rev = dict((entry.name, entry.node) for entry in qfinished)
756 for p in unknown:
756 for p in unknown:
757 msg = _('revision %s refers to unknown patches: %s\n')
757 msg = _('revision %s refers to unknown patches: %s\n')
758 self.ui.warn(msg % (short(rev[p]), p))
758 self.ui.warn(msg % (short(rev[p]), p))
759 else:
759 else:
760 msg = _('unknown patches: %s\n')
760 msg = _('unknown patches: %s\n')
761 raise util.Abort(''.join(msg % p for p in unknown))
761 raise util.Abort(''.join(msg % p for p in unknown))
762
762
763 self.parse_series()
763 self.parse_series()
764 self.series_dirty = 1
764 self.series_dirty = 1
765
765
766 def _revpatches(self, repo, revs):
766 def _revpatches(self, repo, revs):
767 firstrev = repo[self.applied[0].node].rev()
767 firstrev = repo[self.applied[0].node].rev()
768 patches = []
768 patches = []
769 for i, rev in enumerate(revs):
769 for i, rev in enumerate(revs):
770
770
771 if rev < firstrev:
771 if rev < firstrev:
772 raise util.Abort(_('revision %d is not managed') % rev)
772 raise util.Abort(_('revision %d is not managed') % rev)
773
773
774 ctx = repo[rev]
774 ctx = repo[rev]
775 base = self.applied[i].node
775 base = self.applied[i].node
776 if ctx.node() != base:
776 if ctx.node() != base:
777 msg = _('cannot delete revision %d above applied patches')
777 msg = _('cannot delete revision %d above applied patches')
778 raise util.Abort(msg % rev)
778 raise util.Abort(msg % rev)
779
779
780 patch = self.applied[i].name
780 patch = self.applied[i].name
781 for fmt in ('[mq]: %s', 'imported patch %s'):
781 for fmt in ('[mq]: %s', 'imported patch %s'):
782 if ctx.description() == fmt % patch:
782 if ctx.description() == fmt % patch:
783 msg = _('patch %s finalized without changeset message\n')
783 msg = _('patch %s finalized without changeset message\n')
784 repo.ui.status(msg % patch)
784 repo.ui.status(msg % patch)
785 break
785 break
786
786
787 patches.append(patch)
787 patches.append(patch)
788 return patches
788 return patches
789
789
790 def finish(self, repo, revs):
790 def finish(self, repo, revs):
791 patches = self._revpatches(repo, sorted(revs))
791 patches = self._revpatches(repo, sorted(revs))
792 self._cleanup(patches, len(patches))
792 self._cleanup(patches, len(patches))
793
793
794 def delete(self, repo, patches, opts):
794 def delete(self, repo, patches, opts):
795 if not patches and not opts.get('rev'):
795 if not patches and not opts.get('rev'):
796 raise util.Abort(_('qdelete requires at least one revision or '
796 raise util.Abort(_('qdelete requires at least one revision or '
797 'patch name'))
797 'patch name'))
798
798
799 realpatches = []
799 realpatches = []
800 for patch in patches:
800 for patch in patches:
801 patch = self.lookup(patch, strict=True)
801 patch = self.lookup(patch, strict=True)
802 info = self.isapplied(patch)
802 info = self.isapplied(patch)
803 if info:
803 if info:
804 raise util.Abort(_("cannot delete applied patch %s") % patch)
804 raise util.Abort(_("cannot delete applied patch %s") % patch)
805 if patch not in self.series:
805 if patch not in self.series:
806 raise util.Abort(_("patch %s not in series file") % patch)
806 raise util.Abort(_("patch %s not in series file") % patch)
807 if patch not in realpatches:
807 if patch not in realpatches:
808 realpatches.append(patch)
808 realpatches.append(patch)
809
809
810 numrevs = 0
810 numrevs = 0
811 if opts.get('rev'):
811 if opts.get('rev'):
812 if not self.applied:
812 if not self.applied:
813 raise util.Abort(_('no patches applied'))
813 raise util.Abort(_('no patches applied'))
814 revs = cmdutil.revrange(repo, opts.get('rev'))
814 revs = cmdutil.revrange(repo, opts.get('rev'))
815 if len(revs) > 1 and revs[0] > revs[1]:
815 if len(revs) > 1 and revs[0] > revs[1]:
816 revs.reverse()
816 revs.reverse()
817 revpatches = self._revpatches(repo, revs)
817 revpatches = self._revpatches(repo, revs)
818 realpatches += revpatches
818 realpatches += revpatches
819 numrevs = len(revpatches)
819 numrevs = len(revpatches)
820
820
821 self._cleanup(realpatches, numrevs, opts.get('keep'))
821 self._cleanup(realpatches, numrevs, opts.get('keep'))
822
822
823 def check_toppatch(self, repo):
823 def check_toppatch(self, repo):
824 if self.applied:
824 if self.applied:
825 top = self.applied[-1].node
825 top = self.applied[-1].node
826 patch = self.applied[-1].name
826 patch = self.applied[-1].name
827 pp = repo.dirstate.parents()
827 pp = repo.dirstate.parents()
828 if top not in pp:
828 if top not in pp:
829 raise util.Abort(_("working directory revision is not qtip"))
829 raise util.Abort(_("working directory revision is not qtip"))
830 return top, patch
830 return top, patch
831 return None, None
831 return None, None
832
832
833 def check_substate(self, repo):
833 def check_substate(self, repo):
834 '''return list of subrepos at a different revision than substate.
834 '''return list of subrepos at a different revision than substate.
835 Abort if any subrepos have uncommitted changes.'''
835 Abort if any subrepos have uncommitted changes.'''
836 inclsubs = []
836 inclsubs = []
837 wctx = repo[None]
837 wctx = repo[None]
838 for s in wctx.substate:
838 for s in wctx.substate:
839 if wctx.sub(s).dirty(True):
839 if wctx.sub(s).dirty(True):
840 raise util.Abort(
840 raise util.Abort(
841 _("uncommitted changes in subrepository %s") % s)
841 _("uncommitted changes in subrepository %s") % s)
842 elif wctx.sub(s).dirty():
842 elif wctx.sub(s).dirty():
843 inclsubs.append(s)
843 inclsubs.append(s)
844 return inclsubs
844 return inclsubs
845
845
846 def check_localchanges(self, repo, force=False, refresh=True):
846 def check_localchanges(self, repo, force=False, refresh=True):
847 m, a, r, d = repo.status()[:4]
847 m, a, r, d = repo.status()[:4]
848 if (m or a or r or d) and not force:
848 if (m or a or r or d) and not force:
849 if refresh:
849 if refresh:
850 raise util.Abort(_("local changes found, refresh first"))
850 raise util.Abort(_("local changes found, refresh first"))
851 else:
851 else:
852 raise util.Abort(_("local changes found"))
852 raise util.Abort(_("local changes found"))
853 return m, a, r, d
853 return m, a, r, d
854
854
855 _reserved = ('series', 'status', 'guards')
855 _reserved = ('series', 'status', 'guards', '.', '..')
856 def check_reserved_name(self, name):
856 def check_reserved_name(self, name):
857 if (name in self._reserved or name.startswith('.hg')
857 if (name in self._reserved or name.startswith('.hg')
858 or name.startswith('.mq') or '#' in name or ':' in name):
858 or name.startswith('.mq') or '#' in name or ':' in name):
859 raise util.Abort(_('"%s" cannot be used as the name of a patch')
859 raise util.Abort(_('"%s" cannot be used as the name of a patch')
860 % name)
860 % name)
861
861
862 def new(self, repo, patchfn, *pats, **opts):
862 def new(self, repo, patchfn, *pats, **opts):
863 """options:
863 """options:
864 msg: a string or a no-argument function returning a string
864 msg: a string or a no-argument function returning a string
865 """
865 """
866 msg = opts.get('msg')
866 msg = opts.get('msg')
867 user = opts.get('user')
867 user = opts.get('user')
868 date = opts.get('date')
868 date = opts.get('date')
869 if date:
869 if date:
870 date = util.parsedate(date)
870 date = util.parsedate(date)
871 diffopts = self.diffopts({'git': opts.get('git')})
871 diffopts = self.diffopts({'git': opts.get('git')})
872 self.check_reserved_name(patchfn)
872 self.check_reserved_name(patchfn)
873 if os.path.exists(self.join(patchfn)):
873 if os.path.exists(self.join(patchfn)):
874 if os.path.isdir(self.join(patchfn)):
874 if os.path.isdir(self.join(patchfn)):
875 raise util.Abort(_('"%s" already exists as a directory')
875 raise util.Abort(_('"%s" already exists as a directory')
876 % patchfn)
876 % patchfn)
877 else:
877 else:
878 raise util.Abort(_('patch "%s" already exists') % patchfn)
878 raise util.Abort(_('patch "%s" already exists') % patchfn)
879
879
880 inclsubs = self.check_substate(repo)
880 inclsubs = self.check_substate(repo)
881 if inclsubs:
881 if inclsubs:
882 inclsubs.append('.hgsubstate')
882 inclsubs.append('.hgsubstate')
883 if opts.get('include') or opts.get('exclude') or pats:
883 if opts.get('include') or opts.get('exclude') or pats:
884 if inclsubs:
884 if inclsubs:
885 pats = list(pats or []) + inclsubs
885 pats = list(pats or []) + inclsubs
886 match = cmdutil.match(repo, pats, opts)
886 match = cmdutil.match(repo, pats, opts)
887 # detect missing files in pats
887 # detect missing files in pats
888 def badfn(f, msg):
888 def badfn(f, msg):
889 if f != '.hgsubstate': # .hgsubstate is auto-created
889 if f != '.hgsubstate': # .hgsubstate is auto-created
890 raise util.Abort('%s: %s' % (f, msg))
890 raise util.Abort('%s: %s' % (f, msg))
891 match.bad = badfn
891 match.bad = badfn
892 m, a, r, d = repo.status(match=match)[:4]
892 m, a, r, d = repo.status(match=match)[:4]
893 else:
893 else:
894 m, a, r, d = self.check_localchanges(repo, force=True)
894 m, a, r, d = self.check_localchanges(repo, force=True)
895 match = cmdutil.matchfiles(repo, m + a + r + inclsubs)
895 match = cmdutil.matchfiles(repo, m + a + r + inclsubs)
896 if len(repo[None].parents()) > 1:
896 if len(repo[None].parents()) > 1:
897 raise util.Abort(_('cannot manage merge changesets'))
897 raise util.Abort(_('cannot manage merge changesets'))
898 commitfiles = m + a + r
898 commitfiles = m + a + r
899 self.check_toppatch(repo)
899 self.check_toppatch(repo)
900 insert = self.full_series_end()
900 insert = self.full_series_end()
901 wlock = repo.wlock()
901 wlock = repo.wlock()
902 try:
902 try:
903 try:
903 try:
904 # if patch file write fails, abort early
904 # if patch file write fails, abort early
905 p = self.opener(patchfn, "w")
905 p = self.opener(patchfn, "w")
906 except IOError, e:
906 except IOError, e:
907 raise util.Abort(_('cannot write patch "%s": %s')
907 raise util.Abort(_('cannot write patch "%s": %s')
908 % (patchfn, e.strerror))
908 % (patchfn, e.strerror))
909 try:
909 try:
910 if self.plainmode:
910 if self.plainmode:
911 if user:
911 if user:
912 p.write("From: " + user + "\n")
912 p.write("From: " + user + "\n")
913 if not date:
913 if not date:
914 p.write("\n")
914 p.write("\n")
915 if date:
915 if date:
916 p.write("Date: %d %d\n\n" % date)
916 p.write("Date: %d %d\n\n" % date)
917 else:
917 else:
918 p.write("# HG changeset patch\n")
918 p.write("# HG changeset patch\n")
919 p.write("# Parent "
919 p.write("# Parent "
920 + hex(repo[None].p1().node()) + "\n")
920 + hex(repo[None].p1().node()) + "\n")
921 if user:
921 if user:
922 p.write("# User " + user + "\n")
922 p.write("# User " + user + "\n")
923 if date:
923 if date:
924 p.write("# Date %s %s\n\n" % date)
924 p.write("# Date %s %s\n\n" % date)
925 if hasattr(msg, '__call__'):
925 if hasattr(msg, '__call__'):
926 msg = msg()
926 msg = msg()
927 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
927 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
928 n = repo.commit(commitmsg, user, date, match=match, force=True)
928 n = repo.commit(commitmsg, user, date, match=match, force=True)
929 if n is None:
929 if n is None:
930 raise util.Abort(_("repo commit failed"))
930 raise util.Abort(_("repo commit failed"))
931 try:
931 try:
932 self.full_series[insert:insert] = [patchfn]
932 self.full_series[insert:insert] = [patchfn]
933 self.applied.append(statusentry(n, patchfn))
933 self.applied.append(statusentry(n, patchfn))
934 self.parse_series()
934 self.parse_series()
935 self.series_dirty = 1
935 self.series_dirty = 1
936 self.applied_dirty = 1
936 self.applied_dirty = 1
937 if msg:
937 if msg:
938 msg = msg + "\n\n"
938 msg = msg + "\n\n"
939 p.write(msg)
939 p.write(msg)
940 if commitfiles:
940 if commitfiles:
941 parent = self.qparents(repo, n)
941 parent = self.qparents(repo, n)
942 chunks = patch.diff(repo, node1=parent, node2=n,
942 chunks = patch.diff(repo, node1=parent, node2=n,
943 match=match, opts=diffopts)
943 match=match, opts=diffopts)
944 for chunk in chunks:
944 for chunk in chunks:
945 p.write(chunk)
945 p.write(chunk)
946 p.close()
946 p.close()
947 wlock.release()
947 wlock.release()
948 wlock = None
948 wlock = None
949 r = self.qrepo()
949 r = self.qrepo()
950 if r:
950 if r:
951 r[None].add([patchfn])
951 r[None].add([patchfn])
952 except:
952 except:
953 repo.rollback()
953 repo.rollback()
954 raise
954 raise
955 except Exception:
955 except Exception:
956 patchpath = self.join(patchfn)
956 patchpath = self.join(patchfn)
957 try:
957 try:
958 os.unlink(patchpath)
958 os.unlink(patchpath)
959 except:
959 except:
960 self.ui.warn(_('error unlinking %s\n') % patchpath)
960 self.ui.warn(_('error unlinking %s\n') % patchpath)
961 raise
961 raise
962 self.removeundo(repo)
962 self.removeundo(repo)
963 finally:
963 finally:
964 release(wlock)
964 release(wlock)
965
965
966 def strip(self, repo, revs, update=True, backup="all", force=None):
966 def strip(self, repo, revs, update=True, backup="all", force=None):
967 wlock = lock = None
967 wlock = lock = None
968 try:
968 try:
969 wlock = repo.wlock()
969 wlock = repo.wlock()
970 lock = repo.lock()
970 lock = repo.lock()
971
971
972 if update:
972 if update:
973 self.check_localchanges(repo, force=force, refresh=False)
973 self.check_localchanges(repo, force=force, refresh=False)
974 urev = self.qparents(repo, revs[0])
974 urev = self.qparents(repo, revs[0])
975 hg.clean(repo, urev)
975 hg.clean(repo, urev)
976 repo.dirstate.write()
976 repo.dirstate.write()
977
977
978 self.removeundo(repo)
978 self.removeundo(repo)
979 for rev in revs:
979 for rev in revs:
980 repair.strip(self.ui, repo, rev, backup)
980 repair.strip(self.ui, repo, rev, backup)
981 # strip may have unbundled a set of backed up revisions after
981 # strip may have unbundled a set of backed up revisions after
982 # the actual strip
982 # the actual strip
983 self.removeundo(repo)
983 self.removeundo(repo)
984 finally:
984 finally:
985 release(lock, wlock)
985 release(lock, wlock)
986
986
987 def isapplied(self, patch):
987 def isapplied(self, patch):
988 """returns (index, rev, patch)"""
988 """returns (index, rev, patch)"""
989 for i, a in enumerate(self.applied):
989 for i, a in enumerate(self.applied):
990 if a.name == patch:
990 if a.name == patch:
991 return (i, a.node, a.name)
991 return (i, a.node, a.name)
992 return None
992 return None
993
993
994 # if the exact patch name does not exist, we try a few
994 # if the exact patch name does not exist, we try a few
995 # variations. If strict is passed, we try only #1
995 # variations. If strict is passed, we try only #1
996 #
996 #
997 # 1) a number to indicate an offset in the series file
997 # 1) a number to indicate an offset in the series file
998 # 2) a unique substring of the patch name was given
998 # 2) a unique substring of the patch name was given
999 # 3) patchname[-+]num to indicate an offset in the series file
999 # 3) patchname[-+]num to indicate an offset in the series file
1000 def lookup(self, patch, strict=False):
1000 def lookup(self, patch, strict=False):
1001 patch = patch and str(patch)
1001 patch = patch and str(patch)
1002
1002
1003 def partial_name(s):
1003 def partial_name(s):
1004 if s in self.series:
1004 if s in self.series:
1005 return s
1005 return s
1006 matches = [x for x in self.series if s in x]
1006 matches = [x for x in self.series if s in x]
1007 if len(matches) > 1:
1007 if len(matches) > 1:
1008 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1008 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1009 for m in matches:
1009 for m in matches:
1010 self.ui.warn(' %s\n' % m)
1010 self.ui.warn(' %s\n' % m)
1011 return None
1011 return None
1012 if matches:
1012 if matches:
1013 return matches[0]
1013 return matches[0]
1014 if self.series and self.applied:
1014 if self.series and self.applied:
1015 if s == 'qtip':
1015 if s == 'qtip':
1016 return self.series[self.series_end(True)-1]
1016 return self.series[self.series_end(True)-1]
1017 if s == 'qbase':
1017 if s == 'qbase':
1018 return self.series[0]
1018 return self.series[0]
1019 return None
1019 return None
1020
1020
1021 if patch is None:
1021 if patch is None:
1022 return None
1022 return None
1023 if patch in self.series:
1023 if patch in self.series:
1024 return patch
1024 return patch
1025
1025
1026 if not os.path.isfile(self.join(patch)):
1026 if not os.path.isfile(self.join(patch)):
1027 try:
1027 try:
1028 sno = int(patch)
1028 sno = int(patch)
1029 except (ValueError, OverflowError):
1029 except (ValueError, OverflowError):
1030 pass
1030 pass
1031 else:
1031 else:
1032 if -len(self.series) <= sno < len(self.series):
1032 if -len(self.series) <= sno < len(self.series):
1033 return self.series[sno]
1033 return self.series[sno]
1034
1034
1035 if not strict:
1035 if not strict:
1036 res = partial_name(patch)
1036 res = partial_name(patch)
1037 if res:
1037 if res:
1038 return res
1038 return res
1039 minus = patch.rfind('-')
1039 minus = patch.rfind('-')
1040 if minus >= 0:
1040 if minus >= 0:
1041 res = partial_name(patch[:minus])
1041 res = partial_name(patch[:minus])
1042 if res:
1042 if res:
1043 i = self.series.index(res)
1043 i = self.series.index(res)
1044 try:
1044 try:
1045 off = int(patch[minus + 1:] or 1)
1045 off = int(patch[minus + 1:] or 1)
1046 except (ValueError, OverflowError):
1046 except (ValueError, OverflowError):
1047 pass
1047 pass
1048 else:
1048 else:
1049 if i - off >= 0:
1049 if i - off >= 0:
1050 return self.series[i - off]
1050 return self.series[i - off]
1051 plus = patch.rfind('+')
1051 plus = patch.rfind('+')
1052 if plus >= 0:
1052 if plus >= 0:
1053 res = partial_name(patch[:plus])
1053 res = partial_name(patch[:plus])
1054 if res:
1054 if res:
1055 i = self.series.index(res)
1055 i = self.series.index(res)
1056 try:
1056 try:
1057 off = int(patch[plus + 1:] or 1)
1057 off = int(patch[plus + 1:] or 1)
1058 except (ValueError, OverflowError):
1058 except (ValueError, OverflowError):
1059 pass
1059 pass
1060 else:
1060 else:
1061 if i + off < len(self.series):
1061 if i + off < len(self.series):
1062 return self.series[i + off]
1062 return self.series[i + off]
1063 raise util.Abort(_("patch %s not in series") % patch)
1063 raise util.Abort(_("patch %s not in series") % patch)
1064
1064
1065 def push(self, repo, patch=None, force=False, list=False,
1065 def push(self, repo, patch=None, force=False, list=False,
1066 mergeq=None, all=False, move=False, exact=False):
1066 mergeq=None, all=False, move=False, exact=False):
1067 diffopts = self.diffopts()
1067 diffopts = self.diffopts()
1068 wlock = repo.wlock()
1068 wlock = repo.wlock()
1069 try:
1069 try:
1070 heads = []
1070 heads = []
1071 for b, ls in repo.branchmap().iteritems():
1071 for b, ls in repo.branchmap().iteritems():
1072 heads += ls
1072 heads += ls
1073 if not heads:
1073 if not heads:
1074 heads = [nullid]
1074 heads = [nullid]
1075 if repo.dirstate.p1() not in heads and not exact:
1075 if repo.dirstate.p1() not in heads and not exact:
1076 self.ui.status(_("(working directory not at a head)\n"))
1076 self.ui.status(_("(working directory not at a head)\n"))
1077
1077
1078 if not self.series:
1078 if not self.series:
1079 self.ui.warn(_('no patches in series\n'))
1079 self.ui.warn(_('no patches in series\n'))
1080 return 0
1080 return 0
1081
1081
1082 patch = self.lookup(patch)
1082 patch = self.lookup(patch)
1083 # Suppose our series file is: A B C and the current 'top'
1083 # Suppose our series file is: A B C and the current 'top'
1084 # patch is B. qpush C should be performed (moving forward)
1084 # patch is B. qpush C should be performed (moving forward)
1085 # qpush B is a NOP (no change) qpush A is an error (can't
1085 # qpush B is a NOP (no change) qpush A is an error (can't
1086 # go backwards with qpush)
1086 # go backwards with qpush)
1087 if patch:
1087 if patch:
1088 info = self.isapplied(patch)
1088 info = self.isapplied(patch)
1089 if info and info[0] >= len(self.applied) - 1:
1089 if info and info[0] >= len(self.applied) - 1:
1090 self.ui.warn(
1090 self.ui.warn(
1091 _('qpush: %s is already at the top\n') % patch)
1091 _('qpush: %s is already at the top\n') % patch)
1092 return 0
1092 return 0
1093
1093
1094 pushable, reason = self.pushable(patch)
1094 pushable, reason = self.pushable(patch)
1095 if pushable:
1095 if pushable:
1096 if self.series.index(patch) < self.series_end():
1096 if self.series.index(patch) < self.series_end():
1097 raise util.Abort(
1097 raise util.Abort(
1098 _("cannot push to a previous patch: %s") % patch)
1098 _("cannot push to a previous patch: %s") % patch)
1099 else:
1099 else:
1100 if reason:
1100 if reason:
1101 reason = _('guarded by %r') % reason
1101 reason = _('guarded by %r') % reason
1102 else:
1102 else:
1103 reason = _('no matching guards')
1103 reason = _('no matching guards')
1104 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1104 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1105 return 1
1105 return 1
1106 elif all:
1106 elif all:
1107 patch = self.series[-1]
1107 patch = self.series[-1]
1108 if self.isapplied(patch):
1108 if self.isapplied(patch):
1109 self.ui.warn(_('all patches are currently applied\n'))
1109 self.ui.warn(_('all patches are currently applied\n'))
1110 return 0
1110 return 0
1111
1111
1112 # Following the above example, starting at 'top' of B:
1112 # Following the above example, starting at 'top' of B:
1113 # qpush should be performed (pushes C), but a subsequent
1113 # qpush should be performed (pushes C), but a subsequent
1114 # qpush without an argument is an error (nothing to
1114 # qpush without an argument is an error (nothing to
1115 # apply). This allows a loop of "...while hg qpush..." to
1115 # apply). This allows a loop of "...while hg qpush..." to
1116 # work as it detects an error when done
1116 # work as it detects an error when done
1117 start = self.series_end()
1117 start = self.series_end()
1118 if start == len(self.series):
1118 if start == len(self.series):
1119 self.ui.warn(_('patch series already fully applied\n'))
1119 self.ui.warn(_('patch series already fully applied\n'))
1120 return 1
1120 return 1
1121 if not force:
1121 if not force:
1122 self.check_localchanges(repo)
1122 self.check_localchanges(repo)
1123
1123
1124 if exact:
1124 if exact:
1125 if move:
1125 if move:
1126 raise util.Abort(_("cannot use --exact and --move together"))
1126 raise util.Abort(_("cannot use --exact and --move together"))
1127 if self.applied:
1127 if self.applied:
1128 raise util.Abort(_("cannot push --exact with applied patches"))
1128 raise util.Abort(_("cannot push --exact with applied patches"))
1129 root = self.series[start]
1129 root = self.series[start]
1130 target = patchheader(self.join(root), self.plainmode).parent
1130 target = patchheader(self.join(root), self.plainmode).parent
1131 if not target:
1131 if not target:
1132 raise util.Abort(_("%s does not have a parent recorded" % root))
1132 raise util.Abort(_("%s does not have a parent recorded" % root))
1133 if not repo[target] == repo['.']:
1133 if not repo[target] == repo['.']:
1134 hg.update(repo, target)
1134 hg.update(repo, target)
1135
1135
1136 if move:
1136 if move:
1137 if not patch:
1137 if not patch:
1138 raise util.Abort(_("please specify the patch to move"))
1138 raise util.Abort(_("please specify the patch to move"))
1139 for i, rpn in enumerate(self.full_series[start:]):
1139 for i, rpn in enumerate(self.full_series[start:]):
1140 # strip markers for patch guards
1140 # strip markers for patch guards
1141 if self.guard_re.split(rpn, 1)[0] == patch:
1141 if self.guard_re.split(rpn, 1)[0] == patch:
1142 break
1142 break
1143 index = start + i
1143 index = start + i
1144 assert index < len(self.full_series)
1144 assert index < len(self.full_series)
1145 fullpatch = self.full_series[index]
1145 fullpatch = self.full_series[index]
1146 del self.full_series[index]
1146 del self.full_series[index]
1147 self.full_series.insert(start, fullpatch)
1147 self.full_series.insert(start, fullpatch)
1148 self.parse_series()
1148 self.parse_series()
1149 self.series_dirty = 1
1149 self.series_dirty = 1
1150
1150
1151 self.applied_dirty = 1
1151 self.applied_dirty = 1
1152 if start > 0:
1152 if start > 0:
1153 self.check_toppatch(repo)
1153 self.check_toppatch(repo)
1154 if not patch:
1154 if not patch:
1155 patch = self.series[start]
1155 patch = self.series[start]
1156 end = start + 1
1156 end = start + 1
1157 else:
1157 else:
1158 end = self.series.index(patch, start) + 1
1158 end = self.series.index(patch, start) + 1
1159
1159
1160 s = self.series[start:end]
1160 s = self.series[start:end]
1161 all_files = set()
1161 all_files = set()
1162 try:
1162 try:
1163 if mergeq:
1163 if mergeq:
1164 ret = self.mergepatch(repo, mergeq, s, diffopts)
1164 ret = self.mergepatch(repo, mergeq, s, diffopts)
1165 else:
1165 else:
1166 ret = self.apply(repo, s, list, all_files=all_files)
1166 ret = self.apply(repo, s, list, all_files=all_files)
1167 except:
1167 except:
1168 self.ui.warn(_('cleaning up working directory...'))
1168 self.ui.warn(_('cleaning up working directory...'))
1169 node = repo.dirstate.p1()
1169 node = repo.dirstate.p1()
1170 hg.revert(repo, node, None)
1170 hg.revert(repo, node, None)
1171 # only remove unknown files that we know we touched or
1171 # only remove unknown files that we know we touched or
1172 # created while patching
1172 # created while patching
1173 for f in all_files:
1173 for f in all_files:
1174 if f not in repo.dirstate:
1174 if f not in repo.dirstate:
1175 try:
1175 try:
1176 util.unlinkpath(repo.wjoin(f))
1176 util.unlinkpath(repo.wjoin(f))
1177 except OSError, inst:
1177 except OSError, inst:
1178 if inst.errno != errno.ENOENT:
1178 if inst.errno != errno.ENOENT:
1179 raise
1179 raise
1180 self.ui.warn(_('done\n'))
1180 self.ui.warn(_('done\n'))
1181 raise
1181 raise
1182
1182
1183 if not self.applied:
1183 if not self.applied:
1184 return ret[0]
1184 return ret[0]
1185 top = self.applied[-1].name
1185 top = self.applied[-1].name
1186 if ret[0] and ret[0] > 1:
1186 if ret[0] and ret[0] > 1:
1187 msg = _("errors during apply, please fix and refresh %s\n")
1187 msg = _("errors during apply, please fix and refresh %s\n")
1188 self.ui.write(msg % top)
1188 self.ui.write(msg % top)
1189 else:
1189 else:
1190 self.ui.write(_("now at: %s\n") % top)
1190 self.ui.write(_("now at: %s\n") % top)
1191 return ret[0]
1191 return ret[0]
1192
1192
1193 finally:
1193 finally:
1194 wlock.release()
1194 wlock.release()
1195
1195
1196 def pop(self, repo, patch=None, force=False, update=True, all=False):
1196 def pop(self, repo, patch=None, force=False, update=True, all=False):
1197 wlock = repo.wlock()
1197 wlock = repo.wlock()
1198 try:
1198 try:
1199 if patch:
1199 if patch:
1200 # index, rev, patch
1200 # index, rev, patch
1201 info = self.isapplied(patch)
1201 info = self.isapplied(patch)
1202 if not info:
1202 if not info:
1203 patch = self.lookup(patch)
1203 patch = self.lookup(patch)
1204 info = self.isapplied(patch)
1204 info = self.isapplied(patch)
1205 if not info:
1205 if not info:
1206 raise util.Abort(_("patch %s is not applied") % patch)
1206 raise util.Abort(_("patch %s is not applied") % patch)
1207
1207
1208 if not self.applied:
1208 if not self.applied:
1209 # Allow qpop -a to work repeatedly,
1209 # Allow qpop -a to work repeatedly,
1210 # but not qpop without an argument
1210 # but not qpop without an argument
1211 self.ui.warn(_("no patches applied\n"))
1211 self.ui.warn(_("no patches applied\n"))
1212 return not all
1212 return not all
1213
1213
1214 if all:
1214 if all:
1215 start = 0
1215 start = 0
1216 elif patch:
1216 elif patch:
1217 start = info[0] + 1
1217 start = info[0] + 1
1218 else:
1218 else:
1219 start = len(self.applied) - 1
1219 start = len(self.applied) - 1
1220
1220
1221 if start >= len(self.applied):
1221 if start >= len(self.applied):
1222 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1222 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1223 return
1223 return
1224
1224
1225 if not update:
1225 if not update:
1226 parents = repo.dirstate.parents()
1226 parents = repo.dirstate.parents()
1227 rr = [x.node for x in self.applied]
1227 rr = [x.node for x in self.applied]
1228 for p in parents:
1228 for p in parents:
1229 if p in rr:
1229 if p in rr:
1230 self.ui.warn(_("qpop: forcing dirstate update\n"))
1230 self.ui.warn(_("qpop: forcing dirstate update\n"))
1231 update = True
1231 update = True
1232 else:
1232 else:
1233 parents = [p.node() for p in repo[None].parents()]
1233 parents = [p.node() for p in repo[None].parents()]
1234 needupdate = False
1234 needupdate = False
1235 for entry in self.applied[start:]:
1235 for entry in self.applied[start:]:
1236 if entry.node in parents:
1236 if entry.node in parents:
1237 needupdate = True
1237 needupdate = True
1238 break
1238 break
1239 update = needupdate
1239 update = needupdate
1240
1240
1241 if not force and update:
1241 if not force and update:
1242 self.check_localchanges(repo)
1242 self.check_localchanges(repo)
1243
1243
1244 self.applied_dirty = 1
1244 self.applied_dirty = 1
1245 end = len(self.applied)
1245 end = len(self.applied)
1246 rev = self.applied[start].node
1246 rev = self.applied[start].node
1247 if update:
1247 if update:
1248 top = self.check_toppatch(repo)[0]
1248 top = self.check_toppatch(repo)[0]
1249
1249
1250 try:
1250 try:
1251 heads = repo.changelog.heads(rev)
1251 heads = repo.changelog.heads(rev)
1252 except error.LookupError:
1252 except error.LookupError:
1253 node = short(rev)
1253 node = short(rev)
1254 raise util.Abort(_('trying to pop unknown node %s') % node)
1254 raise util.Abort(_('trying to pop unknown node %s') % node)
1255
1255
1256 if heads != [self.applied[-1].node]:
1256 if heads != [self.applied[-1].node]:
1257 raise util.Abort(_("popping would remove a revision not "
1257 raise util.Abort(_("popping would remove a revision not "
1258 "managed by this patch queue"))
1258 "managed by this patch queue"))
1259
1259
1260 # we know there are no local changes, so we can make a simplified
1260 # we know there are no local changes, so we can make a simplified
1261 # form of hg.update.
1261 # form of hg.update.
1262 if update:
1262 if update:
1263 qp = self.qparents(repo, rev)
1263 qp = self.qparents(repo, rev)
1264 ctx = repo[qp]
1264 ctx = repo[qp]
1265 m, a, r, d = repo.status(qp, top)[:4]
1265 m, a, r, d = repo.status(qp, top)[:4]
1266 if d:
1266 if d:
1267 raise util.Abort(_("deletions found between repo revs"))
1267 raise util.Abort(_("deletions found between repo revs"))
1268 for f in a:
1268 for f in a:
1269 try:
1269 try:
1270 util.unlinkpath(repo.wjoin(f))
1270 util.unlinkpath(repo.wjoin(f))
1271 except OSError, e:
1271 except OSError, e:
1272 if e.errno != errno.ENOENT:
1272 if e.errno != errno.ENOENT:
1273 raise
1273 raise
1274 repo.dirstate.forget(f)
1274 repo.dirstate.forget(f)
1275 for f in m + r:
1275 for f in m + r:
1276 fctx = ctx[f]
1276 fctx = ctx[f]
1277 repo.wwrite(f, fctx.data(), fctx.flags())
1277 repo.wwrite(f, fctx.data(), fctx.flags())
1278 repo.dirstate.normal(f)
1278 repo.dirstate.normal(f)
1279 repo.dirstate.setparents(qp, nullid)
1279 repo.dirstate.setparents(qp, nullid)
1280 for patch in reversed(self.applied[start:end]):
1280 for patch in reversed(self.applied[start:end]):
1281 self.ui.status(_("popping %s\n") % patch.name)
1281 self.ui.status(_("popping %s\n") % patch.name)
1282 del self.applied[start:end]
1282 del self.applied[start:end]
1283 self.strip(repo, [rev], update=False, backup='strip')
1283 self.strip(repo, [rev], update=False, backup='strip')
1284 if self.applied:
1284 if self.applied:
1285 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1285 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1286 else:
1286 else:
1287 self.ui.write(_("patch queue now empty\n"))
1287 self.ui.write(_("patch queue now empty\n"))
1288 finally:
1288 finally:
1289 wlock.release()
1289 wlock.release()
1290
1290
1291 def diff(self, repo, pats, opts):
1291 def diff(self, repo, pats, opts):
1292 top, patch = self.check_toppatch(repo)
1292 top, patch = self.check_toppatch(repo)
1293 if not top:
1293 if not top:
1294 self.ui.write(_("no patches applied\n"))
1294 self.ui.write(_("no patches applied\n"))
1295 return
1295 return
1296 qp = self.qparents(repo, top)
1296 qp = self.qparents(repo, top)
1297 if opts.get('reverse'):
1297 if opts.get('reverse'):
1298 node1, node2 = None, qp
1298 node1, node2 = None, qp
1299 else:
1299 else:
1300 node1, node2 = qp, None
1300 node1, node2 = qp, None
1301 diffopts = self.diffopts(opts, patch)
1301 diffopts = self.diffopts(opts, patch)
1302 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1302 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1303
1303
1304 def refresh(self, repo, pats=None, **opts):
1304 def refresh(self, repo, pats=None, **opts):
1305 if not self.applied:
1305 if not self.applied:
1306 self.ui.write(_("no patches applied\n"))
1306 self.ui.write(_("no patches applied\n"))
1307 return 1
1307 return 1
1308 msg = opts.get('msg', '').rstrip()
1308 msg = opts.get('msg', '').rstrip()
1309 newuser = opts.get('user')
1309 newuser = opts.get('user')
1310 newdate = opts.get('date')
1310 newdate = opts.get('date')
1311 if newdate:
1311 if newdate:
1312 newdate = '%d %d' % util.parsedate(newdate)
1312 newdate = '%d %d' % util.parsedate(newdate)
1313 wlock = repo.wlock()
1313 wlock = repo.wlock()
1314
1314
1315 try:
1315 try:
1316 self.check_toppatch(repo)
1316 self.check_toppatch(repo)
1317 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1317 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1318 if repo.changelog.heads(top) != [top]:
1318 if repo.changelog.heads(top) != [top]:
1319 raise util.Abort(_("cannot refresh a revision with children"))
1319 raise util.Abort(_("cannot refresh a revision with children"))
1320
1320
1321 inclsubs = self.check_substate(repo)
1321 inclsubs = self.check_substate(repo)
1322
1322
1323 cparents = repo.changelog.parents(top)
1323 cparents = repo.changelog.parents(top)
1324 patchparent = self.qparents(repo, top)
1324 patchparent = self.qparents(repo, top)
1325 ph = patchheader(self.join(patchfn), self.plainmode)
1325 ph = patchheader(self.join(patchfn), self.plainmode)
1326 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1326 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1327 if msg:
1327 if msg:
1328 ph.setmessage(msg)
1328 ph.setmessage(msg)
1329 if newuser:
1329 if newuser:
1330 ph.setuser(newuser)
1330 ph.setuser(newuser)
1331 if newdate:
1331 if newdate:
1332 ph.setdate(newdate)
1332 ph.setdate(newdate)
1333 ph.setparent(hex(patchparent))
1333 ph.setparent(hex(patchparent))
1334
1334
1335 # only commit new patch when write is complete
1335 # only commit new patch when write is complete
1336 patchf = self.opener(patchfn, 'w', atomictemp=True)
1336 patchf = self.opener(patchfn, 'w', atomictemp=True)
1337
1337
1338 comments = str(ph)
1338 comments = str(ph)
1339 if comments:
1339 if comments:
1340 patchf.write(comments)
1340 patchf.write(comments)
1341
1341
1342 # update the dirstate in place, strip off the qtip commit
1342 # update the dirstate in place, strip off the qtip commit
1343 # and then commit.
1343 # and then commit.
1344 #
1344 #
1345 # this should really read:
1345 # this should really read:
1346 # mm, dd, aa = repo.status(top, patchparent)[:3]
1346 # mm, dd, aa = repo.status(top, patchparent)[:3]
1347 # but we do it backwards to take advantage of manifest/chlog
1347 # but we do it backwards to take advantage of manifest/chlog
1348 # caching against the next repo.status call
1348 # caching against the next repo.status call
1349 mm, aa, dd = repo.status(patchparent, top)[:3]
1349 mm, aa, dd = repo.status(patchparent, top)[:3]
1350 changes = repo.changelog.read(top)
1350 changes = repo.changelog.read(top)
1351 man = repo.manifest.read(changes[0])
1351 man = repo.manifest.read(changes[0])
1352 aaa = aa[:]
1352 aaa = aa[:]
1353 matchfn = cmdutil.match(repo, pats, opts)
1353 matchfn = cmdutil.match(repo, pats, opts)
1354 # in short mode, we only diff the files included in the
1354 # in short mode, we only diff the files included in the
1355 # patch already plus specified files
1355 # patch already plus specified files
1356 if opts.get('short'):
1356 if opts.get('short'):
1357 # if amending a patch, we start with existing
1357 # if amending a patch, we start with existing
1358 # files plus specified files - unfiltered
1358 # files plus specified files - unfiltered
1359 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1359 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1360 # filter with inc/exl options
1360 # filter with inc/exl options
1361 matchfn = cmdutil.match(repo, opts=opts)
1361 matchfn = cmdutil.match(repo, opts=opts)
1362 else:
1362 else:
1363 match = cmdutil.matchall(repo)
1363 match = cmdutil.matchall(repo)
1364 m, a, r, d = repo.status(match=match)[:4]
1364 m, a, r, d = repo.status(match=match)[:4]
1365 mm = set(mm)
1365 mm = set(mm)
1366 aa = set(aa)
1366 aa = set(aa)
1367 dd = set(dd)
1367 dd = set(dd)
1368
1368
1369 # we might end up with files that were added between
1369 # we might end up with files that were added between
1370 # qtip and the dirstate parent, but then changed in the
1370 # qtip and the dirstate parent, but then changed in the
1371 # local dirstate. in this case, we want them to only
1371 # local dirstate. in this case, we want them to only
1372 # show up in the added section
1372 # show up in the added section
1373 for x in m:
1373 for x in m:
1374 if x not in aa:
1374 if x not in aa:
1375 mm.add(x)
1375 mm.add(x)
1376 # we might end up with files added by the local dirstate that
1376 # we might end up with files added by the local dirstate that
1377 # were deleted by the patch. In this case, they should only
1377 # were deleted by the patch. In this case, they should only
1378 # show up in the changed section.
1378 # show up in the changed section.
1379 for x in a:
1379 for x in a:
1380 if x in dd:
1380 if x in dd:
1381 dd.remove(x)
1381 dd.remove(x)
1382 mm.add(x)
1382 mm.add(x)
1383 else:
1383 else:
1384 aa.add(x)
1384 aa.add(x)
1385 # make sure any files deleted in the local dirstate
1385 # make sure any files deleted in the local dirstate
1386 # are not in the add or change column of the patch
1386 # are not in the add or change column of the patch
1387 forget = []
1387 forget = []
1388 for x in d + r:
1388 for x in d + r:
1389 if x in aa:
1389 if x in aa:
1390 aa.remove(x)
1390 aa.remove(x)
1391 forget.append(x)
1391 forget.append(x)
1392 continue
1392 continue
1393 else:
1393 else:
1394 mm.discard(x)
1394 mm.discard(x)
1395 dd.add(x)
1395 dd.add(x)
1396
1396
1397 m = list(mm)
1397 m = list(mm)
1398 r = list(dd)
1398 r = list(dd)
1399 a = list(aa)
1399 a = list(aa)
1400 c = [filter(matchfn, l) for l in (m, a, r)]
1400 c = [filter(matchfn, l) for l in (m, a, r)]
1401 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1401 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1402 chunks = patch.diff(repo, patchparent, match=match,
1402 chunks = patch.diff(repo, patchparent, match=match,
1403 changes=c, opts=diffopts)
1403 changes=c, opts=diffopts)
1404 for chunk in chunks:
1404 for chunk in chunks:
1405 patchf.write(chunk)
1405 patchf.write(chunk)
1406
1406
1407 try:
1407 try:
1408 if diffopts.git or diffopts.upgrade:
1408 if diffopts.git or diffopts.upgrade:
1409 copies = {}
1409 copies = {}
1410 for dst in a:
1410 for dst in a:
1411 src = repo.dirstate.copied(dst)
1411 src = repo.dirstate.copied(dst)
1412 # during qfold, the source file for copies may
1412 # during qfold, the source file for copies may
1413 # be removed. Treat this as a simple add.
1413 # be removed. Treat this as a simple add.
1414 if src is not None and src in repo.dirstate:
1414 if src is not None and src in repo.dirstate:
1415 copies.setdefault(src, []).append(dst)
1415 copies.setdefault(src, []).append(dst)
1416 repo.dirstate.add(dst)
1416 repo.dirstate.add(dst)
1417 # remember the copies between patchparent and qtip
1417 # remember the copies between patchparent and qtip
1418 for dst in aaa:
1418 for dst in aaa:
1419 f = repo.file(dst)
1419 f = repo.file(dst)
1420 src = f.renamed(man[dst])
1420 src = f.renamed(man[dst])
1421 if src:
1421 if src:
1422 copies.setdefault(src[0], []).extend(
1422 copies.setdefault(src[0], []).extend(
1423 copies.get(dst, []))
1423 copies.get(dst, []))
1424 if dst in a:
1424 if dst in a:
1425 copies[src[0]].append(dst)
1425 copies[src[0]].append(dst)
1426 # we can't copy a file created by the patch itself
1426 # we can't copy a file created by the patch itself
1427 if dst in copies:
1427 if dst in copies:
1428 del copies[dst]
1428 del copies[dst]
1429 for src, dsts in copies.iteritems():
1429 for src, dsts in copies.iteritems():
1430 for dst in dsts:
1430 for dst in dsts:
1431 repo.dirstate.copy(src, dst)
1431 repo.dirstate.copy(src, dst)
1432 else:
1432 else:
1433 for dst in a:
1433 for dst in a:
1434 repo.dirstate.add(dst)
1434 repo.dirstate.add(dst)
1435 # Drop useless copy information
1435 # Drop useless copy information
1436 for f in list(repo.dirstate.copies()):
1436 for f in list(repo.dirstate.copies()):
1437 repo.dirstate.copy(None, f)
1437 repo.dirstate.copy(None, f)
1438 for f in r:
1438 for f in r:
1439 repo.dirstate.remove(f)
1439 repo.dirstate.remove(f)
1440 # if the patch excludes a modified file, mark that
1440 # if the patch excludes a modified file, mark that
1441 # file with mtime=0 so status can see it.
1441 # file with mtime=0 so status can see it.
1442 mm = []
1442 mm = []
1443 for i in xrange(len(m)-1, -1, -1):
1443 for i in xrange(len(m)-1, -1, -1):
1444 if not matchfn(m[i]):
1444 if not matchfn(m[i]):
1445 mm.append(m[i])
1445 mm.append(m[i])
1446 del m[i]
1446 del m[i]
1447 for f in m:
1447 for f in m:
1448 repo.dirstate.normal(f)
1448 repo.dirstate.normal(f)
1449 for f in mm:
1449 for f in mm:
1450 repo.dirstate.normallookup(f)
1450 repo.dirstate.normallookup(f)
1451 for f in forget:
1451 for f in forget:
1452 repo.dirstate.forget(f)
1452 repo.dirstate.forget(f)
1453
1453
1454 if not msg:
1454 if not msg:
1455 if not ph.message:
1455 if not ph.message:
1456 message = "[mq]: %s\n" % patchfn
1456 message = "[mq]: %s\n" % patchfn
1457 else:
1457 else:
1458 message = "\n".join(ph.message)
1458 message = "\n".join(ph.message)
1459 else:
1459 else:
1460 message = msg
1460 message = msg
1461
1461
1462 user = ph.user or changes[1]
1462 user = ph.user or changes[1]
1463
1463
1464 # assumes strip can roll itself back if interrupted
1464 # assumes strip can roll itself back if interrupted
1465 repo.dirstate.setparents(*cparents)
1465 repo.dirstate.setparents(*cparents)
1466 self.applied.pop()
1466 self.applied.pop()
1467 self.applied_dirty = 1
1467 self.applied_dirty = 1
1468 self.strip(repo, [top], update=False,
1468 self.strip(repo, [top], update=False,
1469 backup='strip')
1469 backup='strip')
1470 except:
1470 except:
1471 repo.dirstate.invalidate()
1471 repo.dirstate.invalidate()
1472 raise
1472 raise
1473
1473
1474 try:
1474 try:
1475 # might be nice to attempt to roll back strip after this
1475 # might be nice to attempt to roll back strip after this
1476 n = repo.commit(message, user, ph.date, match=match,
1476 n = repo.commit(message, user, ph.date, match=match,
1477 force=True)
1477 force=True)
1478 # only write patch after a successful commit
1478 # only write patch after a successful commit
1479 patchf.rename()
1479 patchf.rename()
1480 self.applied.append(statusentry(n, patchfn))
1480 self.applied.append(statusentry(n, patchfn))
1481 except:
1481 except:
1482 ctx = repo[cparents[0]]
1482 ctx = repo[cparents[0]]
1483 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1483 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1484 self.save_dirty()
1484 self.save_dirty()
1485 self.ui.warn(_('refresh interrupted while patch was popped! '
1485 self.ui.warn(_('refresh interrupted while patch was popped! '
1486 '(revert --all, qpush to recover)\n'))
1486 '(revert --all, qpush to recover)\n'))
1487 raise
1487 raise
1488 finally:
1488 finally:
1489 wlock.release()
1489 wlock.release()
1490 self.removeundo(repo)
1490 self.removeundo(repo)
1491
1491
1492 def init(self, repo, create=False):
1492 def init(self, repo, create=False):
1493 if not create and os.path.isdir(self.path):
1493 if not create and os.path.isdir(self.path):
1494 raise util.Abort(_("patch queue directory already exists"))
1494 raise util.Abort(_("patch queue directory already exists"))
1495 try:
1495 try:
1496 os.mkdir(self.path)
1496 os.mkdir(self.path)
1497 except OSError, inst:
1497 except OSError, inst:
1498 if inst.errno != errno.EEXIST or not create:
1498 if inst.errno != errno.EEXIST or not create:
1499 raise
1499 raise
1500 if create:
1500 if create:
1501 return self.qrepo(create=True)
1501 return self.qrepo(create=True)
1502
1502
1503 def unapplied(self, repo, patch=None):
1503 def unapplied(self, repo, patch=None):
1504 if patch and patch not in self.series:
1504 if patch and patch not in self.series:
1505 raise util.Abort(_("patch %s is not in series file") % patch)
1505 raise util.Abort(_("patch %s is not in series file") % patch)
1506 if not patch:
1506 if not patch:
1507 start = self.series_end()
1507 start = self.series_end()
1508 else:
1508 else:
1509 start = self.series.index(patch) + 1
1509 start = self.series.index(patch) + 1
1510 unapplied = []
1510 unapplied = []
1511 for i in xrange(start, len(self.series)):
1511 for i in xrange(start, len(self.series)):
1512 pushable, reason = self.pushable(i)
1512 pushable, reason = self.pushable(i)
1513 if pushable:
1513 if pushable:
1514 unapplied.append((i, self.series[i]))
1514 unapplied.append((i, self.series[i]))
1515 self.explain_pushable(i)
1515 self.explain_pushable(i)
1516 return unapplied
1516 return unapplied
1517
1517
1518 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1518 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1519 summary=False):
1519 summary=False):
1520 def displayname(pfx, patchname, state):
1520 def displayname(pfx, patchname, state):
1521 if pfx:
1521 if pfx:
1522 self.ui.write(pfx)
1522 self.ui.write(pfx)
1523 if summary:
1523 if summary:
1524 ph = patchheader(self.join(patchname), self.plainmode)
1524 ph = patchheader(self.join(patchname), self.plainmode)
1525 msg = ph.message and ph.message[0] or ''
1525 msg = ph.message and ph.message[0] or ''
1526 if self.ui.formatted():
1526 if self.ui.formatted():
1527 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1527 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1528 if width > 0:
1528 if width > 0:
1529 msg = util.ellipsis(msg, width)
1529 msg = util.ellipsis(msg, width)
1530 else:
1530 else:
1531 msg = ''
1531 msg = ''
1532 self.ui.write(patchname, label='qseries.' + state)
1532 self.ui.write(patchname, label='qseries.' + state)
1533 self.ui.write(': ')
1533 self.ui.write(': ')
1534 self.ui.write(msg, label='qseries.message.' + state)
1534 self.ui.write(msg, label='qseries.message.' + state)
1535 else:
1535 else:
1536 self.ui.write(patchname, label='qseries.' + state)
1536 self.ui.write(patchname, label='qseries.' + state)
1537 self.ui.write('\n')
1537 self.ui.write('\n')
1538
1538
1539 applied = set([p.name for p in self.applied])
1539 applied = set([p.name for p in self.applied])
1540 if length is None:
1540 if length is None:
1541 length = len(self.series) - start
1541 length = len(self.series) - start
1542 if not missing:
1542 if not missing:
1543 if self.ui.verbose:
1543 if self.ui.verbose:
1544 idxwidth = len(str(start + length - 1))
1544 idxwidth = len(str(start + length - 1))
1545 for i in xrange(start, start + length):
1545 for i in xrange(start, start + length):
1546 patch = self.series[i]
1546 patch = self.series[i]
1547 if patch in applied:
1547 if patch in applied:
1548 char, state = 'A', 'applied'
1548 char, state = 'A', 'applied'
1549 elif self.pushable(i)[0]:
1549 elif self.pushable(i)[0]:
1550 char, state = 'U', 'unapplied'
1550 char, state = 'U', 'unapplied'
1551 else:
1551 else:
1552 char, state = 'G', 'guarded'
1552 char, state = 'G', 'guarded'
1553 pfx = ''
1553 pfx = ''
1554 if self.ui.verbose:
1554 if self.ui.verbose:
1555 pfx = '%*d %s ' % (idxwidth, i, char)
1555 pfx = '%*d %s ' % (idxwidth, i, char)
1556 elif status and status != char:
1556 elif status and status != char:
1557 continue
1557 continue
1558 displayname(pfx, patch, state)
1558 displayname(pfx, patch, state)
1559 else:
1559 else:
1560 msng_list = []
1560 msng_list = []
1561 for root, dirs, files in os.walk(self.path):
1561 for root, dirs, files in os.walk(self.path):
1562 d = root[len(self.path) + 1:]
1562 d = root[len(self.path) + 1:]
1563 for f in files:
1563 for f in files:
1564 fl = os.path.join(d, f)
1564 fl = os.path.join(d, f)
1565 if (fl not in self.series and
1565 if (fl not in self.series and
1566 fl not in (self.status_path, self.series_path,
1566 fl not in (self.status_path, self.series_path,
1567 self.guards_path)
1567 self.guards_path)
1568 and not fl.startswith('.')):
1568 and not fl.startswith('.')):
1569 msng_list.append(fl)
1569 msng_list.append(fl)
1570 for x in sorted(msng_list):
1570 for x in sorted(msng_list):
1571 pfx = self.ui.verbose and ('D ') or ''
1571 pfx = self.ui.verbose and ('D ') or ''
1572 displayname(pfx, x, 'missing')
1572 displayname(pfx, x, 'missing')
1573
1573
1574 def issaveline(self, l):
1574 def issaveline(self, l):
1575 if l.name == '.hg.patches.save.line':
1575 if l.name == '.hg.patches.save.line':
1576 return True
1576 return True
1577
1577
1578 def qrepo(self, create=False):
1578 def qrepo(self, create=False):
1579 ui = self.ui.copy()
1579 ui = self.ui.copy()
1580 ui.setconfig('paths', 'default', '', overlay=False)
1580 ui.setconfig('paths', 'default', '', overlay=False)
1581 ui.setconfig('paths', 'default-push', '', overlay=False)
1581 ui.setconfig('paths', 'default-push', '', overlay=False)
1582 if create or os.path.isdir(self.join(".hg")):
1582 if create or os.path.isdir(self.join(".hg")):
1583 return hg.repository(ui, path=self.path, create=create)
1583 return hg.repository(ui, path=self.path, create=create)
1584
1584
1585 def restore(self, repo, rev, delete=None, qupdate=None):
1585 def restore(self, repo, rev, delete=None, qupdate=None):
1586 desc = repo[rev].description().strip()
1586 desc = repo[rev].description().strip()
1587 lines = desc.splitlines()
1587 lines = desc.splitlines()
1588 i = 0
1588 i = 0
1589 datastart = None
1589 datastart = None
1590 series = []
1590 series = []
1591 applied = []
1591 applied = []
1592 qpp = None
1592 qpp = None
1593 for i, line in enumerate(lines):
1593 for i, line in enumerate(lines):
1594 if line == 'Patch Data:':
1594 if line == 'Patch Data:':
1595 datastart = i + 1
1595 datastart = i + 1
1596 elif line.startswith('Dirstate:'):
1596 elif line.startswith('Dirstate:'):
1597 l = line.rstrip()
1597 l = line.rstrip()
1598 l = l[10:].split(' ')
1598 l = l[10:].split(' ')
1599 qpp = [bin(x) for x in l]
1599 qpp = [bin(x) for x in l]
1600 elif datastart is not None:
1600 elif datastart is not None:
1601 l = line.rstrip()
1601 l = line.rstrip()
1602 n, name = l.split(':', 1)
1602 n, name = l.split(':', 1)
1603 if n:
1603 if n:
1604 applied.append(statusentry(bin(n), name))
1604 applied.append(statusentry(bin(n), name))
1605 else:
1605 else:
1606 series.append(l)
1606 series.append(l)
1607 if datastart is None:
1607 if datastart is None:
1608 self.ui.warn(_("No saved patch data found\n"))
1608 self.ui.warn(_("No saved patch data found\n"))
1609 return 1
1609 return 1
1610 self.ui.warn(_("restoring status: %s\n") % lines[0])
1610 self.ui.warn(_("restoring status: %s\n") % lines[0])
1611 self.full_series = series
1611 self.full_series = series
1612 self.applied = applied
1612 self.applied = applied
1613 self.parse_series()
1613 self.parse_series()
1614 self.series_dirty = 1
1614 self.series_dirty = 1
1615 self.applied_dirty = 1
1615 self.applied_dirty = 1
1616 heads = repo.changelog.heads()
1616 heads = repo.changelog.heads()
1617 if delete:
1617 if delete:
1618 if rev not in heads:
1618 if rev not in heads:
1619 self.ui.warn(_("save entry has children, leaving it alone\n"))
1619 self.ui.warn(_("save entry has children, leaving it alone\n"))
1620 else:
1620 else:
1621 self.ui.warn(_("removing save entry %s\n") % short(rev))
1621 self.ui.warn(_("removing save entry %s\n") % short(rev))
1622 pp = repo.dirstate.parents()
1622 pp = repo.dirstate.parents()
1623 if rev in pp:
1623 if rev in pp:
1624 update = True
1624 update = True
1625 else:
1625 else:
1626 update = False
1626 update = False
1627 self.strip(repo, [rev], update=update, backup='strip')
1627 self.strip(repo, [rev], update=update, backup='strip')
1628 if qpp:
1628 if qpp:
1629 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1629 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1630 (short(qpp[0]), short(qpp[1])))
1630 (short(qpp[0]), short(qpp[1])))
1631 if qupdate:
1631 if qupdate:
1632 self.ui.status(_("updating queue directory\n"))
1632 self.ui.status(_("updating queue directory\n"))
1633 r = self.qrepo()
1633 r = self.qrepo()
1634 if not r:
1634 if not r:
1635 self.ui.warn(_("Unable to load queue repository\n"))
1635 self.ui.warn(_("Unable to load queue repository\n"))
1636 return 1
1636 return 1
1637 hg.clean(r, qpp[0])
1637 hg.clean(r, qpp[0])
1638
1638
1639 def save(self, repo, msg=None):
1639 def save(self, repo, msg=None):
1640 if not self.applied:
1640 if not self.applied:
1641 self.ui.warn(_("save: no patches applied, exiting\n"))
1641 self.ui.warn(_("save: no patches applied, exiting\n"))
1642 return 1
1642 return 1
1643 if self.issaveline(self.applied[-1]):
1643 if self.issaveline(self.applied[-1]):
1644 self.ui.warn(_("status is already saved\n"))
1644 self.ui.warn(_("status is already saved\n"))
1645 return 1
1645 return 1
1646
1646
1647 if not msg:
1647 if not msg:
1648 msg = _("hg patches saved state")
1648 msg = _("hg patches saved state")
1649 else:
1649 else:
1650 msg = "hg patches: " + msg.rstrip('\r\n')
1650 msg = "hg patches: " + msg.rstrip('\r\n')
1651 r = self.qrepo()
1651 r = self.qrepo()
1652 if r:
1652 if r:
1653 pp = r.dirstate.parents()
1653 pp = r.dirstate.parents()
1654 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1654 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1655 msg += "\n\nPatch Data:\n"
1655 msg += "\n\nPatch Data:\n"
1656 msg += ''.join('%s\n' % x for x in self.applied)
1656 msg += ''.join('%s\n' % x for x in self.applied)
1657 msg += ''.join(':%s\n' % x for x in self.full_series)
1657 msg += ''.join(':%s\n' % x for x in self.full_series)
1658 n = repo.commit(msg, force=True)
1658 n = repo.commit(msg, force=True)
1659 if not n:
1659 if not n:
1660 self.ui.warn(_("repo commit failed\n"))
1660 self.ui.warn(_("repo commit failed\n"))
1661 return 1
1661 return 1
1662 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1662 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1663 self.applied_dirty = 1
1663 self.applied_dirty = 1
1664 self.removeundo(repo)
1664 self.removeundo(repo)
1665
1665
1666 def full_series_end(self):
1666 def full_series_end(self):
1667 if self.applied:
1667 if self.applied:
1668 p = self.applied[-1].name
1668 p = self.applied[-1].name
1669 end = self.find_series(p)
1669 end = self.find_series(p)
1670 if end is None:
1670 if end is None:
1671 return len(self.full_series)
1671 return len(self.full_series)
1672 return end + 1
1672 return end + 1
1673 return 0
1673 return 0
1674
1674
1675 def series_end(self, all_patches=False):
1675 def series_end(self, all_patches=False):
1676 """If all_patches is False, return the index of the next pushable patch
1676 """If all_patches is False, return the index of the next pushable patch
1677 in the series, or the series length. If all_patches is True, return the
1677 in the series, or the series length. If all_patches is True, return the
1678 index of the first patch past the last applied one.
1678 index of the first patch past the last applied one.
1679 """
1679 """
1680 end = 0
1680 end = 0
1681 def next(start):
1681 def next(start):
1682 if all_patches or start >= len(self.series):
1682 if all_patches or start >= len(self.series):
1683 return start
1683 return start
1684 for i in xrange(start, len(self.series)):
1684 for i in xrange(start, len(self.series)):
1685 p, reason = self.pushable(i)
1685 p, reason = self.pushable(i)
1686 if p:
1686 if p:
1687 break
1687 break
1688 self.explain_pushable(i)
1688 self.explain_pushable(i)
1689 return i
1689 return i
1690 if self.applied:
1690 if self.applied:
1691 p = self.applied[-1].name
1691 p = self.applied[-1].name
1692 try:
1692 try:
1693 end = self.series.index(p)
1693 end = self.series.index(p)
1694 except ValueError:
1694 except ValueError:
1695 return 0
1695 return 0
1696 return next(end + 1)
1696 return next(end + 1)
1697 return next(end)
1697 return next(end)
1698
1698
1699 def appliedname(self, index):
1699 def appliedname(self, index):
1700 pname = self.applied[index].name
1700 pname = self.applied[index].name
1701 if not self.ui.verbose:
1701 if not self.ui.verbose:
1702 p = pname
1702 p = pname
1703 else:
1703 else:
1704 p = str(self.series.index(pname)) + " " + pname
1704 p = str(self.series.index(pname)) + " " + pname
1705 return p
1705 return p
1706
1706
1707 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1707 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1708 force=None, git=False):
1708 force=None, git=False):
1709 def checkseries(patchname):
1709 def checkseries(patchname):
1710 if patchname in self.series:
1710 if patchname in self.series:
1711 raise util.Abort(_('patch %s is already in the series file')
1711 raise util.Abort(_('patch %s is already in the series file')
1712 % patchname)
1712 % patchname)
1713 def checkfile(patchname):
1713 def checkfile(patchname):
1714 if not force and os.path.exists(self.join(patchname)):
1714 if not force and os.path.exists(self.join(patchname)):
1715 raise util.Abort(_('patch "%s" already exists')
1715 raise util.Abort(_('patch "%s" already exists')
1716 % patchname)
1716 % patchname)
1717
1717
1718 if rev:
1718 if rev:
1719 if files:
1719 if files:
1720 raise util.Abort(_('option "-r" not valid when importing '
1720 raise util.Abort(_('option "-r" not valid when importing '
1721 'files'))
1721 'files'))
1722 rev = cmdutil.revrange(repo, rev)
1722 rev = cmdutil.revrange(repo, rev)
1723 rev.sort(reverse=True)
1723 rev.sort(reverse=True)
1724 if (len(files) > 1 or len(rev) > 1) and patchname:
1724 if (len(files) > 1 or len(rev) > 1) and patchname:
1725 raise util.Abort(_('option "-n" not valid when importing multiple '
1725 raise util.Abort(_('option "-n" not valid when importing multiple '
1726 'patches'))
1726 'patches'))
1727 if rev:
1727 if rev:
1728 # If mq patches are applied, we can only import revisions
1728 # If mq patches are applied, we can only import revisions
1729 # that form a linear path to qbase.
1729 # that form a linear path to qbase.
1730 # Otherwise, they should form a linear path to a head.
1730 # Otherwise, they should form a linear path to a head.
1731 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1731 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1732 if len(heads) > 1:
1732 if len(heads) > 1:
1733 raise util.Abort(_('revision %d is the root of more than one '
1733 raise util.Abort(_('revision %d is the root of more than one '
1734 'branch') % rev[-1])
1734 'branch') % rev[-1])
1735 if self.applied:
1735 if self.applied:
1736 base = repo.changelog.node(rev[0])
1736 base = repo.changelog.node(rev[0])
1737 if base in [n.node for n in self.applied]:
1737 if base in [n.node for n in self.applied]:
1738 raise util.Abort(_('revision %d is already managed')
1738 raise util.Abort(_('revision %d is already managed')
1739 % rev[0])
1739 % rev[0])
1740 if heads != [self.applied[-1].node]:
1740 if heads != [self.applied[-1].node]:
1741 raise util.Abort(_('revision %d is not the parent of '
1741 raise util.Abort(_('revision %d is not the parent of '
1742 'the queue') % rev[0])
1742 'the queue') % rev[0])
1743 base = repo.changelog.rev(self.applied[0].node)
1743 base = repo.changelog.rev(self.applied[0].node)
1744 lastparent = repo.changelog.parentrevs(base)[0]
1744 lastparent = repo.changelog.parentrevs(base)[0]
1745 else:
1745 else:
1746 if heads != [repo.changelog.node(rev[0])]:
1746 if heads != [repo.changelog.node(rev[0])]:
1747 raise util.Abort(_('revision %d has unmanaged children')
1747 raise util.Abort(_('revision %d has unmanaged children')
1748 % rev[0])
1748 % rev[0])
1749 lastparent = None
1749 lastparent = None
1750
1750
1751 diffopts = self.diffopts({'git': git})
1751 diffopts = self.diffopts({'git': git})
1752 for r in rev:
1752 for r in rev:
1753 p1, p2 = repo.changelog.parentrevs(r)
1753 p1, p2 = repo.changelog.parentrevs(r)
1754 n = repo.changelog.node(r)
1754 n = repo.changelog.node(r)
1755 if p2 != nullrev:
1755 if p2 != nullrev:
1756 raise util.Abort(_('cannot import merge revision %d') % r)
1756 raise util.Abort(_('cannot import merge revision %d') % r)
1757 if lastparent and lastparent != r:
1757 if lastparent and lastparent != r:
1758 raise util.Abort(_('revision %d is not the parent of %d')
1758 raise util.Abort(_('revision %d is not the parent of %d')
1759 % (r, lastparent))
1759 % (r, lastparent))
1760 lastparent = p1
1760 lastparent = p1
1761
1761
1762 if not patchname:
1762 if not patchname:
1763 patchname = normname('%d.diff' % r)
1763 patchname = normname('%d.diff' % r)
1764 self.check_reserved_name(patchname)
1764 self.check_reserved_name(patchname)
1765 checkseries(patchname)
1765 checkseries(patchname)
1766 checkfile(patchname)
1766 checkfile(patchname)
1767 self.full_series.insert(0, patchname)
1767 self.full_series.insert(0, patchname)
1768
1768
1769 patchf = self.opener(patchname, "w")
1769 patchf = self.opener(patchname, "w")
1770 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1770 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1771 patchf.close()
1771 patchf.close()
1772
1772
1773 se = statusentry(n, patchname)
1773 se = statusentry(n, patchname)
1774 self.applied.insert(0, se)
1774 self.applied.insert(0, se)
1775
1775
1776 self.added.append(patchname)
1776 self.added.append(patchname)
1777 patchname = None
1777 patchname = None
1778 self.parse_series()
1778 self.parse_series()
1779 self.applied_dirty = 1
1779 self.applied_dirty = 1
1780 self.series_dirty = True
1780 self.series_dirty = True
1781
1781
1782 for i, filename in enumerate(files):
1782 for i, filename in enumerate(files):
1783 if existing:
1783 if existing:
1784 if filename == '-':
1784 if filename == '-':
1785 raise util.Abort(_('-e is incompatible with import from -'))
1785 raise util.Abort(_('-e is incompatible with import from -'))
1786 filename = normname(filename)
1786 filename = normname(filename)
1787 self.check_reserved_name(filename)
1787 self.check_reserved_name(filename)
1788 originpath = self.join(filename)
1788 originpath = self.join(filename)
1789 if not os.path.isfile(originpath):
1789 if not os.path.isfile(originpath):
1790 raise util.Abort(_("patch %s does not exist") % filename)
1790 raise util.Abort(_("patch %s does not exist") % filename)
1791
1791
1792 if patchname:
1792 if patchname:
1793 self.check_reserved_name(patchname)
1793 self.check_reserved_name(patchname)
1794 checkfile(patchname)
1794 checkfile(patchname)
1795
1795
1796 self.ui.write(_('renaming %s to %s\n')
1796 self.ui.write(_('renaming %s to %s\n')
1797 % (filename, patchname))
1797 % (filename, patchname))
1798 util.rename(originpath, self.join(patchname))
1798 util.rename(originpath, self.join(patchname))
1799 else:
1799 else:
1800 patchname = filename
1800 patchname = filename
1801
1801
1802 else:
1802 else:
1803 try:
1803 try:
1804 if filename == '-':
1804 if filename == '-':
1805 if not patchname:
1805 if not patchname:
1806 raise util.Abort(
1806 raise util.Abort(
1807 _('need --name to import a patch from -'))
1807 _('need --name to import a patch from -'))
1808 text = sys.stdin.read()
1808 text = sys.stdin.read()
1809 else:
1809 else:
1810 fp = url.open(self.ui, filename)
1810 fp = url.open(self.ui, filename)
1811 text = fp.read()
1811 text = fp.read()
1812 fp.close()
1812 fp.close()
1813 except (OSError, IOError):
1813 except (OSError, IOError):
1814 raise util.Abort(_("unable to read file %s") % filename)
1814 raise util.Abort(_("unable to read file %s") % filename)
1815 if not patchname:
1815 if not patchname:
1816 patchname = normname(os.path.basename(filename))
1816 patchname = normname(os.path.basename(filename))
1817 self.check_reserved_name(patchname)
1817 self.check_reserved_name(patchname)
1818 checkfile(patchname)
1818 checkfile(patchname)
1819 patchf = self.opener(patchname, "w")
1819 patchf = self.opener(patchname, "w")
1820 patchf.write(text)
1820 patchf.write(text)
1821 patchf.close()
1821 patchf.close()
1822 if not force:
1822 if not force:
1823 checkseries(patchname)
1823 checkseries(patchname)
1824 if patchname not in self.series:
1824 if patchname not in self.series:
1825 index = self.full_series_end() + i
1825 index = self.full_series_end() + i
1826 self.full_series[index:index] = [patchname]
1826 self.full_series[index:index] = [patchname]
1827 self.parse_series()
1827 self.parse_series()
1828 self.series_dirty = True
1828 self.series_dirty = True
1829 self.ui.warn(_("adding %s to series file\n") % patchname)
1829 self.ui.warn(_("adding %s to series file\n") % patchname)
1830 self.added.append(patchname)
1830 self.added.append(patchname)
1831 patchname = None
1831 patchname = None
1832
1832
1833 self.removeundo(repo)
1833 self.removeundo(repo)
1834
1834
1835 def delete(ui, repo, *patches, **opts):
1835 def delete(ui, repo, *patches, **opts):
1836 """remove patches from queue
1836 """remove patches from queue
1837
1837
1838 The patches must not be applied, and at least one patch is required. With
1838 The patches must not be applied, and at least one patch is required. With
1839 -k/--keep, the patch files are preserved in the patch directory.
1839 -k/--keep, the patch files are preserved in the patch directory.
1840
1840
1841 To stop managing a patch and move it into permanent history,
1841 To stop managing a patch and move it into permanent history,
1842 use the :hg:`qfinish` command."""
1842 use the :hg:`qfinish` command."""
1843 q = repo.mq
1843 q = repo.mq
1844 q.delete(repo, patches, opts)
1844 q.delete(repo, patches, opts)
1845 q.save_dirty()
1845 q.save_dirty()
1846 return 0
1846 return 0
1847
1847
1848 def applied(ui, repo, patch=None, **opts):
1848 def applied(ui, repo, patch=None, **opts):
1849 """print the patches already applied
1849 """print the patches already applied
1850
1850
1851 Returns 0 on success."""
1851 Returns 0 on success."""
1852
1852
1853 q = repo.mq
1853 q = repo.mq
1854
1854
1855 if patch:
1855 if patch:
1856 if patch not in q.series:
1856 if patch not in q.series:
1857 raise util.Abort(_("patch %s is not in series file") % patch)
1857 raise util.Abort(_("patch %s is not in series file") % patch)
1858 end = q.series.index(patch) + 1
1858 end = q.series.index(patch) + 1
1859 else:
1859 else:
1860 end = q.series_end(True)
1860 end = q.series_end(True)
1861
1861
1862 if opts.get('last') and not end:
1862 if opts.get('last') and not end:
1863 ui.write(_("no patches applied\n"))
1863 ui.write(_("no patches applied\n"))
1864 return 1
1864 return 1
1865 elif opts.get('last') and end == 1:
1865 elif opts.get('last') and end == 1:
1866 ui.write(_("only one patch applied\n"))
1866 ui.write(_("only one patch applied\n"))
1867 return 1
1867 return 1
1868 elif opts.get('last'):
1868 elif opts.get('last'):
1869 start = end - 2
1869 start = end - 2
1870 end = 1
1870 end = 1
1871 else:
1871 else:
1872 start = 0
1872 start = 0
1873
1873
1874 q.qseries(repo, length=end, start=start, status='A',
1874 q.qseries(repo, length=end, start=start, status='A',
1875 summary=opts.get('summary'))
1875 summary=opts.get('summary'))
1876
1876
1877
1877
1878 def unapplied(ui, repo, patch=None, **opts):
1878 def unapplied(ui, repo, patch=None, **opts):
1879 """print the patches not yet applied
1879 """print the patches not yet applied
1880
1880
1881 Returns 0 on success."""
1881 Returns 0 on success."""
1882
1882
1883 q = repo.mq
1883 q = repo.mq
1884 if patch:
1884 if patch:
1885 if patch not in q.series:
1885 if patch not in q.series:
1886 raise util.Abort(_("patch %s is not in series file") % patch)
1886 raise util.Abort(_("patch %s is not in series file") % patch)
1887 start = q.series.index(patch) + 1
1887 start = q.series.index(patch) + 1
1888 else:
1888 else:
1889 start = q.series_end(True)
1889 start = q.series_end(True)
1890
1890
1891 if start == len(q.series) and opts.get('first'):
1891 if start == len(q.series) and opts.get('first'):
1892 ui.write(_("all patches applied\n"))
1892 ui.write(_("all patches applied\n"))
1893 return 1
1893 return 1
1894
1894
1895 length = opts.get('first') and 1 or None
1895 length = opts.get('first') and 1 or None
1896 q.qseries(repo, start=start, length=length, status='U',
1896 q.qseries(repo, start=start, length=length, status='U',
1897 summary=opts.get('summary'))
1897 summary=opts.get('summary'))
1898
1898
1899 def qimport(ui, repo, *filename, **opts):
1899 def qimport(ui, repo, *filename, **opts):
1900 """import a patch
1900 """import a patch
1901
1901
1902 The patch is inserted into the series after the last applied
1902 The patch is inserted into the series after the last applied
1903 patch. If no patches have been applied, qimport prepends the patch
1903 patch. If no patches have been applied, qimport prepends the patch
1904 to the series.
1904 to the series.
1905
1905
1906 The patch will have the same name as its source file unless you
1906 The patch will have the same name as its source file unless you
1907 give it a new one with -n/--name.
1907 give it a new one with -n/--name.
1908
1908
1909 You can register an existing patch inside the patch directory with
1909 You can register an existing patch inside the patch directory with
1910 the -e/--existing flag.
1910 the -e/--existing flag.
1911
1911
1912 With -f/--force, an existing patch of the same name will be
1912 With -f/--force, an existing patch of the same name will be
1913 overwritten.
1913 overwritten.
1914
1914
1915 An existing changeset may be placed under mq control with -r/--rev
1915 An existing changeset may be placed under mq control with -r/--rev
1916 (e.g. qimport --rev tip -n patch will place tip under mq control).
1916 (e.g. qimport --rev tip -n patch will place tip under mq control).
1917 With -g/--git, patches imported with --rev will use the git diff
1917 With -g/--git, patches imported with --rev will use the git diff
1918 format. See the diffs help topic for information on why this is
1918 format. See the diffs help topic for information on why this is
1919 important for preserving rename/copy information and permission
1919 important for preserving rename/copy information and permission
1920 changes. Use :hg:`qfinish` to remove changesets from mq control.
1920 changes. Use :hg:`qfinish` to remove changesets from mq control.
1921
1921
1922 To import a patch from standard input, pass - as the patch file.
1922 To import a patch from standard input, pass - as the patch file.
1923 When importing from standard input, a patch name must be specified
1923 When importing from standard input, a patch name must be specified
1924 using the --name flag.
1924 using the --name flag.
1925
1925
1926 To import an existing patch while renaming it::
1926 To import an existing patch while renaming it::
1927
1927
1928 hg qimport -e existing-patch -n new-name
1928 hg qimport -e existing-patch -n new-name
1929
1929
1930 Returns 0 if import succeeded.
1930 Returns 0 if import succeeded.
1931 """
1931 """
1932 q = repo.mq
1932 q = repo.mq
1933 try:
1933 try:
1934 q.qimport(repo, filename, patchname=opts.get('name'),
1934 q.qimport(repo, filename, patchname=opts.get('name'),
1935 existing=opts.get('existing'), force=opts.get('force'),
1935 existing=opts.get('existing'), force=opts.get('force'),
1936 rev=opts.get('rev'), git=opts.get('git'))
1936 rev=opts.get('rev'), git=opts.get('git'))
1937 finally:
1937 finally:
1938 q.save_dirty()
1938 q.save_dirty()
1939
1939
1940 if opts.get('push') and not opts.get('rev'):
1940 if opts.get('push') and not opts.get('rev'):
1941 return q.push(repo, None)
1941 return q.push(repo, None)
1942 return 0
1942 return 0
1943
1943
1944 def qinit(ui, repo, create):
1944 def qinit(ui, repo, create):
1945 """initialize a new queue repository
1945 """initialize a new queue repository
1946
1946
1947 This command also creates a series file for ordering patches, and
1947 This command also creates a series file for ordering patches, and
1948 an mq-specific .hgignore file in the queue repository, to exclude
1948 an mq-specific .hgignore file in the queue repository, to exclude
1949 the status and guards files (these contain mostly transient state).
1949 the status and guards files (these contain mostly transient state).
1950
1950
1951 Returns 0 if initialization succeeded."""
1951 Returns 0 if initialization succeeded."""
1952 q = repo.mq
1952 q = repo.mq
1953 r = q.init(repo, create)
1953 r = q.init(repo, create)
1954 q.save_dirty()
1954 q.save_dirty()
1955 if r:
1955 if r:
1956 if not os.path.exists(r.wjoin('.hgignore')):
1956 if not os.path.exists(r.wjoin('.hgignore')):
1957 fp = r.wopener('.hgignore', 'w')
1957 fp = r.wopener('.hgignore', 'w')
1958 fp.write('^\\.hg\n')
1958 fp.write('^\\.hg\n')
1959 fp.write('^\\.mq\n')
1959 fp.write('^\\.mq\n')
1960 fp.write('syntax: glob\n')
1960 fp.write('syntax: glob\n')
1961 fp.write('status\n')
1961 fp.write('status\n')
1962 fp.write('guards\n')
1962 fp.write('guards\n')
1963 fp.close()
1963 fp.close()
1964 if not os.path.exists(r.wjoin('series')):
1964 if not os.path.exists(r.wjoin('series')):
1965 r.wopener('series', 'w').close()
1965 r.wopener('series', 'w').close()
1966 r[None].add(['.hgignore', 'series'])
1966 r[None].add(['.hgignore', 'series'])
1967 commands.add(ui, r)
1967 commands.add(ui, r)
1968 return 0
1968 return 0
1969
1969
1970 def init(ui, repo, **opts):
1970 def init(ui, repo, **opts):
1971 """init a new queue repository (DEPRECATED)
1971 """init a new queue repository (DEPRECATED)
1972
1972
1973 The queue repository is unversioned by default. If
1973 The queue repository is unversioned by default. If
1974 -c/--create-repo is specified, qinit will create a separate nested
1974 -c/--create-repo is specified, qinit will create a separate nested
1975 repository for patches (qinit -c may also be run later to convert
1975 repository for patches (qinit -c may also be run later to convert
1976 an unversioned patch repository into a versioned one). You can use
1976 an unversioned patch repository into a versioned one). You can use
1977 qcommit to commit changes to this queue repository.
1977 qcommit to commit changes to this queue repository.
1978
1978
1979 This command is deprecated. Without -c, it's implied by other relevant
1979 This command is deprecated. Without -c, it's implied by other relevant
1980 commands. With -c, use :hg:`init --mq` instead."""
1980 commands. With -c, use :hg:`init --mq` instead."""
1981 return qinit(ui, repo, create=opts.get('create_repo'))
1981 return qinit(ui, repo, create=opts.get('create_repo'))
1982
1982
1983 def clone(ui, source, dest=None, **opts):
1983 def clone(ui, source, dest=None, **opts):
1984 '''clone main and patch repository at same time
1984 '''clone main and patch repository at same time
1985
1985
1986 If source is local, destination will have no patches applied. If
1986 If source is local, destination will have no patches applied. If
1987 source is remote, this command can not check if patches are
1987 source is remote, this command can not check if patches are
1988 applied in source, so cannot guarantee that patches are not
1988 applied in source, so cannot guarantee that patches are not
1989 applied in destination. If you clone remote repository, be sure
1989 applied in destination. If you clone remote repository, be sure
1990 before that it has no patches applied.
1990 before that it has no patches applied.
1991
1991
1992 Source patch repository is looked for in <src>/.hg/patches by
1992 Source patch repository is looked for in <src>/.hg/patches by
1993 default. Use -p <url> to change.
1993 default. Use -p <url> to change.
1994
1994
1995 The patch directory must be a nested Mercurial repository, as
1995 The patch directory must be a nested Mercurial repository, as
1996 would be created by :hg:`init --mq`.
1996 would be created by :hg:`init --mq`.
1997
1997
1998 Return 0 on success.
1998 Return 0 on success.
1999 '''
1999 '''
2000 def patchdir(repo):
2000 def patchdir(repo):
2001 url = repo.url()
2001 url = repo.url()
2002 if url.endswith('/'):
2002 if url.endswith('/'):
2003 url = url[:-1]
2003 url = url[:-1]
2004 return url + '/.hg/patches'
2004 return url + '/.hg/patches'
2005 if dest is None:
2005 if dest is None:
2006 dest = hg.defaultdest(source)
2006 dest = hg.defaultdest(source)
2007 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
2007 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
2008 if opts.get('patches'):
2008 if opts.get('patches'):
2009 patchespath = ui.expandpath(opts.get('patches'))
2009 patchespath = ui.expandpath(opts.get('patches'))
2010 else:
2010 else:
2011 patchespath = patchdir(sr)
2011 patchespath = patchdir(sr)
2012 try:
2012 try:
2013 hg.repository(ui, patchespath)
2013 hg.repository(ui, patchespath)
2014 except error.RepoError:
2014 except error.RepoError:
2015 raise util.Abort(_('versioned patch repository not found'
2015 raise util.Abort(_('versioned patch repository not found'
2016 ' (see init --mq)'))
2016 ' (see init --mq)'))
2017 qbase, destrev = None, None
2017 qbase, destrev = None, None
2018 if sr.local():
2018 if sr.local():
2019 if sr.mq.applied:
2019 if sr.mq.applied:
2020 qbase = sr.mq.applied[0].node
2020 qbase = sr.mq.applied[0].node
2021 if not hg.islocal(dest):
2021 if not hg.islocal(dest):
2022 heads = set(sr.heads())
2022 heads = set(sr.heads())
2023 destrev = list(heads.difference(sr.heads(qbase)))
2023 destrev = list(heads.difference(sr.heads(qbase)))
2024 destrev.append(sr.changelog.parents(qbase)[0])
2024 destrev.append(sr.changelog.parents(qbase)[0])
2025 elif sr.capable('lookup'):
2025 elif sr.capable('lookup'):
2026 try:
2026 try:
2027 qbase = sr.lookup('qbase')
2027 qbase = sr.lookup('qbase')
2028 except error.RepoError:
2028 except error.RepoError:
2029 pass
2029 pass
2030 ui.note(_('cloning main repository\n'))
2030 ui.note(_('cloning main repository\n'))
2031 sr, dr = hg.clone(ui, sr.url(), dest,
2031 sr, dr = hg.clone(ui, sr.url(), dest,
2032 pull=opts.get('pull'),
2032 pull=opts.get('pull'),
2033 rev=destrev,
2033 rev=destrev,
2034 update=False,
2034 update=False,
2035 stream=opts.get('uncompressed'))
2035 stream=opts.get('uncompressed'))
2036 ui.note(_('cloning patch repository\n'))
2036 ui.note(_('cloning patch repository\n'))
2037 hg.clone(ui, opts.get('patches') or patchdir(sr), patchdir(dr),
2037 hg.clone(ui, opts.get('patches') or patchdir(sr), patchdir(dr),
2038 pull=opts.get('pull'), update=not opts.get('noupdate'),
2038 pull=opts.get('pull'), update=not opts.get('noupdate'),
2039 stream=opts.get('uncompressed'))
2039 stream=opts.get('uncompressed'))
2040 if dr.local():
2040 if dr.local():
2041 if qbase:
2041 if qbase:
2042 ui.note(_('stripping applied patches from destination '
2042 ui.note(_('stripping applied patches from destination '
2043 'repository\n'))
2043 'repository\n'))
2044 dr.mq.strip(dr, [qbase], update=False, backup=None)
2044 dr.mq.strip(dr, [qbase], update=False, backup=None)
2045 if not opts.get('noupdate'):
2045 if not opts.get('noupdate'):
2046 ui.note(_('updating destination repository\n'))
2046 ui.note(_('updating destination repository\n'))
2047 hg.update(dr, dr.changelog.tip())
2047 hg.update(dr, dr.changelog.tip())
2048
2048
2049 def commit(ui, repo, *pats, **opts):
2049 def commit(ui, repo, *pats, **opts):
2050 """commit changes in the queue repository (DEPRECATED)
2050 """commit changes in the queue repository (DEPRECATED)
2051
2051
2052 This command is deprecated; use :hg:`commit --mq` instead."""
2052 This command is deprecated; use :hg:`commit --mq` instead."""
2053 q = repo.mq
2053 q = repo.mq
2054 r = q.qrepo()
2054 r = q.qrepo()
2055 if not r:
2055 if not r:
2056 raise util.Abort('no queue repository')
2056 raise util.Abort('no queue repository')
2057 commands.commit(r.ui, r, *pats, **opts)
2057 commands.commit(r.ui, r, *pats, **opts)
2058
2058
2059 def series(ui, repo, **opts):
2059 def series(ui, repo, **opts):
2060 """print the entire series file
2060 """print the entire series file
2061
2061
2062 Returns 0 on success."""
2062 Returns 0 on success."""
2063 repo.mq.qseries(repo, missing=opts.get('missing'), summary=opts.get('summary'))
2063 repo.mq.qseries(repo, missing=opts.get('missing'), summary=opts.get('summary'))
2064 return 0
2064 return 0
2065
2065
2066 def top(ui, repo, **opts):
2066 def top(ui, repo, **opts):
2067 """print the name of the current patch
2067 """print the name of the current patch
2068
2068
2069 Returns 0 on success."""
2069 Returns 0 on success."""
2070 q = repo.mq
2070 q = repo.mq
2071 t = q.applied and q.series_end(True) or 0
2071 t = q.applied and q.series_end(True) or 0
2072 if t:
2072 if t:
2073 q.qseries(repo, start=t - 1, length=1, status='A',
2073 q.qseries(repo, start=t - 1, length=1, status='A',
2074 summary=opts.get('summary'))
2074 summary=opts.get('summary'))
2075 else:
2075 else:
2076 ui.write(_("no patches applied\n"))
2076 ui.write(_("no patches applied\n"))
2077 return 1
2077 return 1
2078
2078
2079 def next(ui, repo, **opts):
2079 def next(ui, repo, **opts):
2080 """print the name of the next patch
2080 """print the name of the next patch
2081
2081
2082 Returns 0 on success."""
2082 Returns 0 on success."""
2083 q = repo.mq
2083 q = repo.mq
2084 end = q.series_end()
2084 end = q.series_end()
2085 if end == len(q.series):
2085 if end == len(q.series):
2086 ui.write(_("all patches applied\n"))
2086 ui.write(_("all patches applied\n"))
2087 return 1
2087 return 1
2088 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2088 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2089
2089
2090 def prev(ui, repo, **opts):
2090 def prev(ui, repo, **opts):
2091 """print the name of the previous patch
2091 """print the name of the previous patch
2092
2092
2093 Returns 0 on success."""
2093 Returns 0 on success."""
2094 q = repo.mq
2094 q = repo.mq
2095 l = len(q.applied)
2095 l = len(q.applied)
2096 if l == 1:
2096 if l == 1:
2097 ui.write(_("only one patch applied\n"))
2097 ui.write(_("only one patch applied\n"))
2098 return 1
2098 return 1
2099 if not l:
2099 if not l:
2100 ui.write(_("no patches applied\n"))
2100 ui.write(_("no patches applied\n"))
2101 return 1
2101 return 1
2102 q.qseries(repo, start=l - 2, length=1, status='A',
2102 q.qseries(repo, start=l - 2, length=1, status='A',
2103 summary=opts.get('summary'))
2103 summary=opts.get('summary'))
2104
2104
2105 def setupheaderopts(ui, opts):
2105 def setupheaderopts(ui, opts):
2106 if not opts.get('user') and opts.get('currentuser'):
2106 if not opts.get('user') and opts.get('currentuser'):
2107 opts['user'] = ui.username()
2107 opts['user'] = ui.username()
2108 if not opts.get('date') and opts.get('currentdate'):
2108 if not opts.get('date') and opts.get('currentdate'):
2109 opts['date'] = "%d %d" % util.makedate()
2109 opts['date'] = "%d %d" % util.makedate()
2110
2110
2111 def new(ui, repo, patch, *args, **opts):
2111 def new(ui, repo, patch, *args, **opts):
2112 """create a new patch
2112 """create a new patch
2113
2113
2114 qnew creates a new patch on top of the currently-applied patch (if
2114 qnew creates a new patch on top of the currently-applied patch (if
2115 any). The patch will be initialized with any outstanding changes
2115 any). The patch will be initialized with any outstanding changes
2116 in the working directory. You may also use -I/--include,
2116 in the working directory. You may also use -I/--include,
2117 -X/--exclude, and/or a list of files after the patch name to add
2117 -X/--exclude, and/or a list of files after the patch name to add
2118 only changes to matching files to the new patch, leaving the rest
2118 only changes to matching files to the new patch, leaving the rest
2119 as uncommitted modifications.
2119 as uncommitted modifications.
2120
2120
2121 -u/--user and -d/--date can be used to set the (given) user and
2121 -u/--user and -d/--date can be used to set the (given) user and
2122 date, respectively. -U/--currentuser and -D/--currentdate set user
2122 date, respectively. -U/--currentuser and -D/--currentdate set user
2123 to current user and date to current date.
2123 to current user and date to current date.
2124
2124
2125 -e/--edit, -m/--message or -l/--logfile set the patch header as
2125 -e/--edit, -m/--message or -l/--logfile set the patch header as
2126 well as the commit message. If none is specified, the header is
2126 well as the commit message. If none is specified, the header is
2127 empty and the commit message is '[mq]: PATCH'.
2127 empty and the commit message is '[mq]: PATCH'.
2128
2128
2129 Use the -g/--git option to keep the patch in the git extended diff
2129 Use the -g/--git option to keep the patch in the git extended diff
2130 format. Read the diffs help topic for more information on why this
2130 format. Read the diffs help topic for more information on why this
2131 is important for preserving permission changes and copy/rename
2131 is important for preserving permission changes and copy/rename
2132 information.
2132 information.
2133
2133
2134 Returns 0 on successful creation of a new patch.
2134 Returns 0 on successful creation of a new patch.
2135 """
2135 """
2136 msg = cmdutil.logmessage(opts)
2136 msg = cmdutil.logmessage(opts)
2137 def getmsg():
2137 def getmsg():
2138 return ui.edit(msg, opts.get('user') or ui.username())
2138 return ui.edit(msg, opts.get('user') or ui.username())
2139 q = repo.mq
2139 q = repo.mq
2140 opts['msg'] = msg
2140 opts['msg'] = msg
2141 if opts.get('edit'):
2141 if opts.get('edit'):
2142 opts['msg'] = getmsg
2142 opts['msg'] = getmsg
2143 else:
2143 else:
2144 opts['msg'] = msg
2144 opts['msg'] = msg
2145 setupheaderopts(ui, opts)
2145 setupheaderopts(ui, opts)
2146 q.new(repo, patch, *args, **opts)
2146 q.new(repo, patch, *args, **opts)
2147 q.save_dirty()
2147 q.save_dirty()
2148 return 0
2148 return 0
2149
2149
2150 def refresh(ui, repo, *pats, **opts):
2150 def refresh(ui, repo, *pats, **opts):
2151 """update the current patch
2151 """update the current patch
2152
2152
2153 If any file patterns are provided, the refreshed patch will
2153 If any file patterns are provided, the refreshed patch will
2154 contain only the modifications that match those patterns; the
2154 contain only the modifications that match those patterns; the
2155 remaining modifications will remain in the working directory.
2155 remaining modifications will remain in the working directory.
2156
2156
2157 If -s/--short is specified, files currently included in the patch
2157 If -s/--short is specified, files currently included in the patch
2158 will be refreshed just like matched files and remain in the patch.
2158 will be refreshed just like matched files and remain in the patch.
2159
2159
2160 If -e/--edit is specified, Mercurial will start your configured editor for
2160 If -e/--edit is specified, Mercurial will start your configured editor for
2161 you to enter a message. In case qrefresh fails, you will find a backup of
2161 you to enter a message. In case qrefresh fails, you will find a backup of
2162 your message in ``.hg/last-message.txt``.
2162 your message in ``.hg/last-message.txt``.
2163
2163
2164 hg add/remove/copy/rename work as usual, though you might want to
2164 hg add/remove/copy/rename work as usual, though you might want to
2165 use git-style patches (-g/--git or [diff] git=1) to track copies
2165 use git-style patches (-g/--git or [diff] git=1) to track copies
2166 and renames. See the diffs help topic for more information on the
2166 and renames. See the diffs help topic for more information on the
2167 git diff format.
2167 git diff format.
2168
2168
2169 Returns 0 on success.
2169 Returns 0 on success.
2170 """
2170 """
2171 q = repo.mq
2171 q = repo.mq
2172 message = cmdutil.logmessage(opts)
2172 message = cmdutil.logmessage(opts)
2173 if opts.get('edit'):
2173 if opts.get('edit'):
2174 if not q.applied:
2174 if not q.applied:
2175 ui.write(_("no patches applied\n"))
2175 ui.write(_("no patches applied\n"))
2176 return 1
2176 return 1
2177 if message:
2177 if message:
2178 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2178 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2179 patch = q.applied[-1].name
2179 patch = q.applied[-1].name
2180 ph = patchheader(q.join(patch), q.plainmode)
2180 ph = patchheader(q.join(patch), q.plainmode)
2181 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2181 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2182 # We don't want to lose the patch message if qrefresh fails (issue2062)
2182 # We don't want to lose the patch message if qrefresh fails (issue2062)
2183 msgfile = repo.opener('last-message.txt', 'wb')
2183 msgfile = repo.opener('last-message.txt', 'wb')
2184 msgfile.write(message)
2184 msgfile.write(message)
2185 msgfile.close()
2185 msgfile.close()
2186 setupheaderopts(ui, opts)
2186 setupheaderopts(ui, opts)
2187 ret = q.refresh(repo, pats, msg=message, **opts)
2187 ret = q.refresh(repo, pats, msg=message, **opts)
2188 q.save_dirty()
2188 q.save_dirty()
2189 return ret
2189 return ret
2190
2190
2191 def diff(ui, repo, *pats, **opts):
2191 def diff(ui, repo, *pats, **opts):
2192 """diff of the current patch and subsequent modifications
2192 """diff of the current patch and subsequent modifications
2193
2193
2194 Shows a diff which includes the current patch as well as any
2194 Shows a diff which includes the current patch as well as any
2195 changes which have been made in the working directory since the
2195 changes which have been made in the working directory since the
2196 last refresh (thus showing what the current patch would become
2196 last refresh (thus showing what the current patch would become
2197 after a qrefresh).
2197 after a qrefresh).
2198
2198
2199 Use :hg:`diff` if you only want to see the changes made since the
2199 Use :hg:`diff` if you only want to see the changes made since the
2200 last qrefresh, or :hg:`export qtip` if you want to see changes
2200 last qrefresh, or :hg:`export qtip` if you want to see changes
2201 made by the current patch without including changes made since the
2201 made by the current patch without including changes made since the
2202 qrefresh.
2202 qrefresh.
2203
2203
2204 Returns 0 on success.
2204 Returns 0 on success.
2205 """
2205 """
2206 repo.mq.diff(repo, pats, opts)
2206 repo.mq.diff(repo, pats, opts)
2207 return 0
2207 return 0
2208
2208
2209 def fold(ui, repo, *files, **opts):
2209 def fold(ui, repo, *files, **opts):
2210 """fold the named patches into the current patch
2210 """fold the named patches into the current patch
2211
2211
2212 Patches must not yet be applied. Each patch will be successively
2212 Patches must not yet be applied. Each patch will be successively
2213 applied to the current patch in the order given. If all the
2213 applied to the current patch in the order given. If all the
2214 patches apply successfully, the current patch will be refreshed
2214 patches apply successfully, the current patch will be refreshed
2215 with the new cumulative patch, and the folded patches will be
2215 with the new cumulative patch, and the folded patches will be
2216 deleted. With -k/--keep, the folded patch files will not be
2216 deleted. With -k/--keep, the folded patch files will not be
2217 removed afterwards.
2217 removed afterwards.
2218
2218
2219 The header for each folded patch will be concatenated with the
2219 The header for each folded patch will be concatenated with the
2220 current patch header, separated by a line of ``* * *``.
2220 current patch header, separated by a line of ``* * *``.
2221
2221
2222 Returns 0 on success."""
2222 Returns 0 on success."""
2223
2223
2224 q = repo.mq
2224 q = repo.mq
2225
2225
2226 if not files:
2226 if not files:
2227 raise util.Abort(_('qfold requires at least one patch name'))
2227 raise util.Abort(_('qfold requires at least one patch name'))
2228 if not q.check_toppatch(repo)[0]:
2228 if not q.check_toppatch(repo)[0]:
2229 raise util.Abort(_('no patches applied'))
2229 raise util.Abort(_('no patches applied'))
2230 q.check_localchanges(repo)
2230 q.check_localchanges(repo)
2231
2231
2232 message = cmdutil.logmessage(opts)
2232 message = cmdutil.logmessage(opts)
2233 if opts.get('edit'):
2233 if opts.get('edit'):
2234 if message:
2234 if message:
2235 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2235 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2236
2236
2237 parent = q.lookup('qtip')
2237 parent = q.lookup('qtip')
2238 patches = []
2238 patches = []
2239 messages = []
2239 messages = []
2240 for f in files:
2240 for f in files:
2241 p = q.lookup(f)
2241 p = q.lookup(f)
2242 if p in patches or p == parent:
2242 if p in patches or p == parent:
2243 ui.warn(_('Skipping already folded patch %s\n') % p)
2243 ui.warn(_('Skipping already folded patch %s\n') % p)
2244 if q.isapplied(p):
2244 if q.isapplied(p):
2245 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2245 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2246 patches.append(p)
2246 patches.append(p)
2247
2247
2248 for p in patches:
2248 for p in patches:
2249 if not message:
2249 if not message:
2250 ph = patchheader(q.join(p), q.plainmode)
2250 ph = patchheader(q.join(p), q.plainmode)
2251 if ph.message:
2251 if ph.message:
2252 messages.append(ph.message)
2252 messages.append(ph.message)
2253 pf = q.join(p)
2253 pf = q.join(p)
2254 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2254 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2255 if not patchsuccess:
2255 if not patchsuccess:
2256 raise util.Abort(_('error folding patch %s') % p)
2256 raise util.Abort(_('error folding patch %s') % p)
2257 cmdutil.updatedir(ui, repo, files)
2257 cmdutil.updatedir(ui, repo, files)
2258
2258
2259 if not message:
2259 if not message:
2260 ph = patchheader(q.join(parent), q.plainmode)
2260 ph = patchheader(q.join(parent), q.plainmode)
2261 message, user = ph.message, ph.user
2261 message, user = ph.message, ph.user
2262 for msg in messages:
2262 for msg in messages:
2263 message.append('* * *')
2263 message.append('* * *')
2264 message.extend(msg)
2264 message.extend(msg)
2265 message = '\n'.join(message)
2265 message = '\n'.join(message)
2266
2266
2267 if opts.get('edit'):
2267 if opts.get('edit'):
2268 message = ui.edit(message, user or ui.username())
2268 message = ui.edit(message, user or ui.username())
2269
2269
2270 diffopts = q.patchopts(q.diffopts(), *patches)
2270 diffopts = q.patchopts(q.diffopts(), *patches)
2271 q.refresh(repo, msg=message, git=diffopts.git)
2271 q.refresh(repo, msg=message, git=diffopts.git)
2272 q.delete(repo, patches, opts)
2272 q.delete(repo, patches, opts)
2273 q.save_dirty()
2273 q.save_dirty()
2274
2274
2275 def goto(ui, repo, patch, **opts):
2275 def goto(ui, repo, patch, **opts):
2276 '''push or pop patches until named patch is at top of stack
2276 '''push or pop patches until named patch is at top of stack
2277
2277
2278 Returns 0 on success.'''
2278 Returns 0 on success.'''
2279 q = repo.mq
2279 q = repo.mq
2280 patch = q.lookup(patch)
2280 patch = q.lookup(patch)
2281 if q.isapplied(patch):
2281 if q.isapplied(patch):
2282 ret = q.pop(repo, patch, force=opts.get('force'))
2282 ret = q.pop(repo, patch, force=opts.get('force'))
2283 else:
2283 else:
2284 ret = q.push(repo, patch, force=opts.get('force'))
2284 ret = q.push(repo, patch, force=opts.get('force'))
2285 q.save_dirty()
2285 q.save_dirty()
2286 return ret
2286 return ret
2287
2287
2288 def guard(ui, repo, *args, **opts):
2288 def guard(ui, repo, *args, **opts):
2289 '''set or print guards for a patch
2289 '''set or print guards for a patch
2290
2290
2291 Guards control whether a patch can be pushed. A patch with no
2291 Guards control whether a patch can be pushed. A patch with no
2292 guards is always pushed. A patch with a positive guard ("+foo") is
2292 guards is always pushed. A patch with a positive guard ("+foo") is
2293 pushed only if the :hg:`qselect` command has activated it. A patch with
2293 pushed only if the :hg:`qselect` command has activated it. A patch with
2294 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2294 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2295 has activated it.
2295 has activated it.
2296
2296
2297 With no arguments, print the currently active guards.
2297 With no arguments, print the currently active guards.
2298 With arguments, set guards for the named patch.
2298 With arguments, set guards for the named patch.
2299
2299
2300 .. note::
2300 .. note::
2301 Specifying negative guards now requires '--'.
2301 Specifying negative guards now requires '--'.
2302
2302
2303 To set guards on another patch::
2303 To set guards on another patch::
2304
2304
2305 hg qguard other.patch -- +2.6.17 -stable
2305 hg qguard other.patch -- +2.6.17 -stable
2306
2306
2307 Returns 0 on success.
2307 Returns 0 on success.
2308 '''
2308 '''
2309 def status(idx):
2309 def status(idx):
2310 guards = q.series_guards[idx] or ['unguarded']
2310 guards = q.series_guards[idx] or ['unguarded']
2311 if q.series[idx] in applied:
2311 if q.series[idx] in applied:
2312 state = 'applied'
2312 state = 'applied'
2313 elif q.pushable(idx)[0]:
2313 elif q.pushable(idx)[0]:
2314 state = 'unapplied'
2314 state = 'unapplied'
2315 else:
2315 else:
2316 state = 'guarded'
2316 state = 'guarded'
2317 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2317 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2318 ui.write('%s: ' % ui.label(q.series[idx], label))
2318 ui.write('%s: ' % ui.label(q.series[idx], label))
2319
2319
2320 for i, guard in enumerate(guards):
2320 for i, guard in enumerate(guards):
2321 if guard.startswith('+'):
2321 if guard.startswith('+'):
2322 ui.write(guard, label='qguard.positive')
2322 ui.write(guard, label='qguard.positive')
2323 elif guard.startswith('-'):
2323 elif guard.startswith('-'):
2324 ui.write(guard, label='qguard.negative')
2324 ui.write(guard, label='qguard.negative')
2325 else:
2325 else:
2326 ui.write(guard, label='qguard.unguarded')
2326 ui.write(guard, label='qguard.unguarded')
2327 if i != len(guards) - 1:
2327 if i != len(guards) - 1:
2328 ui.write(' ')
2328 ui.write(' ')
2329 ui.write('\n')
2329 ui.write('\n')
2330 q = repo.mq
2330 q = repo.mq
2331 applied = set(p.name for p in q.applied)
2331 applied = set(p.name for p in q.applied)
2332 patch = None
2332 patch = None
2333 args = list(args)
2333 args = list(args)
2334 if opts.get('list'):
2334 if opts.get('list'):
2335 if args or opts.get('none'):
2335 if args or opts.get('none'):
2336 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2336 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2337 for i in xrange(len(q.series)):
2337 for i in xrange(len(q.series)):
2338 status(i)
2338 status(i)
2339 return
2339 return
2340 if not args or args[0][0:1] in '-+':
2340 if not args or args[0][0:1] in '-+':
2341 if not q.applied:
2341 if not q.applied:
2342 raise util.Abort(_('no patches applied'))
2342 raise util.Abort(_('no patches applied'))
2343 patch = q.applied[-1].name
2343 patch = q.applied[-1].name
2344 if patch is None and args[0][0:1] not in '-+':
2344 if patch is None and args[0][0:1] not in '-+':
2345 patch = args.pop(0)
2345 patch = args.pop(0)
2346 if patch is None:
2346 if patch is None:
2347 raise util.Abort(_('no patch to work with'))
2347 raise util.Abort(_('no patch to work with'))
2348 if args or opts.get('none'):
2348 if args or opts.get('none'):
2349 idx = q.find_series(patch)
2349 idx = q.find_series(patch)
2350 if idx is None:
2350 if idx is None:
2351 raise util.Abort(_('no patch named %s') % patch)
2351 raise util.Abort(_('no patch named %s') % patch)
2352 q.set_guards(idx, args)
2352 q.set_guards(idx, args)
2353 q.save_dirty()
2353 q.save_dirty()
2354 else:
2354 else:
2355 status(q.series.index(q.lookup(patch)))
2355 status(q.series.index(q.lookup(patch)))
2356
2356
2357 def header(ui, repo, patch=None):
2357 def header(ui, repo, patch=None):
2358 """print the header of the topmost or specified patch
2358 """print the header of the topmost or specified patch
2359
2359
2360 Returns 0 on success."""
2360 Returns 0 on success."""
2361 q = repo.mq
2361 q = repo.mq
2362
2362
2363 if patch:
2363 if patch:
2364 patch = q.lookup(patch)
2364 patch = q.lookup(patch)
2365 else:
2365 else:
2366 if not q.applied:
2366 if not q.applied:
2367 ui.write(_('no patches applied\n'))
2367 ui.write(_('no patches applied\n'))
2368 return 1
2368 return 1
2369 patch = q.lookup('qtip')
2369 patch = q.lookup('qtip')
2370 ph = patchheader(q.join(patch), q.plainmode)
2370 ph = patchheader(q.join(patch), q.plainmode)
2371
2371
2372 ui.write('\n'.join(ph.message) + '\n')
2372 ui.write('\n'.join(ph.message) + '\n')
2373
2373
2374 def lastsavename(path):
2374 def lastsavename(path):
2375 (directory, base) = os.path.split(path)
2375 (directory, base) = os.path.split(path)
2376 names = os.listdir(directory)
2376 names = os.listdir(directory)
2377 namere = re.compile("%s.([0-9]+)" % base)
2377 namere = re.compile("%s.([0-9]+)" % base)
2378 maxindex = None
2378 maxindex = None
2379 maxname = None
2379 maxname = None
2380 for f in names:
2380 for f in names:
2381 m = namere.match(f)
2381 m = namere.match(f)
2382 if m:
2382 if m:
2383 index = int(m.group(1))
2383 index = int(m.group(1))
2384 if maxindex is None or index > maxindex:
2384 if maxindex is None or index > maxindex:
2385 maxindex = index
2385 maxindex = index
2386 maxname = f
2386 maxname = f
2387 if maxname:
2387 if maxname:
2388 return (os.path.join(directory, maxname), maxindex)
2388 return (os.path.join(directory, maxname), maxindex)
2389 return (None, None)
2389 return (None, None)
2390
2390
2391 def savename(path):
2391 def savename(path):
2392 (last, index) = lastsavename(path)
2392 (last, index) = lastsavename(path)
2393 if last is None:
2393 if last is None:
2394 index = 0
2394 index = 0
2395 newpath = path + ".%d" % (index + 1)
2395 newpath = path + ".%d" % (index + 1)
2396 return newpath
2396 return newpath
2397
2397
2398 def push(ui, repo, patch=None, **opts):
2398 def push(ui, repo, patch=None, **opts):
2399 """push the next patch onto the stack
2399 """push the next patch onto the stack
2400
2400
2401 When -f/--force is applied, all local changes in patched files
2401 When -f/--force is applied, all local changes in patched files
2402 will be lost.
2402 will be lost.
2403
2403
2404 Return 0 on success.
2404 Return 0 on success.
2405 """
2405 """
2406 q = repo.mq
2406 q = repo.mq
2407 mergeq = None
2407 mergeq = None
2408
2408
2409 if opts.get('merge'):
2409 if opts.get('merge'):
2410 if opts.get('name'):
2410 if opts.get('name'):
2411 newpath = repo.join(opts.get('name'))
2411 newpath = repo.join(opts.get('name'))
2412 else:
2412 else:
2413 newpath, i = lastsavename(q.path)
2413 newpath, i = lastsavename(q.path)
2414 if not newpath:
2414 if not newpath:
2415 ui.warn(_("no saved queues found, please use -n\n"))
2415 ui.warn(_("no saved queues found, please use -n\n"))
2416 return 1
2416 return 1
2417 mergeq = queue(ui, repo.join(""), newpath)
2417 mergeq = queue(ui, repo.join(""), newpath)
2418 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2418 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2419 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2419 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2420 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2420 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2421 exact=opts.get('exact'))
2421 exact=opts.get('exact'))
2422 return ret
2422 return ret
2423
2423
2424 def pop(ui, repo, patch=None, **opts):
2424 def pop(ui, repo, patch=None, **opts):
2425 """pop the current patch off the stack
2425 """pop the current patch off the stack
2426
2426
2427 By default, pops off the top of the patch stack. If given a patch
2427 By default, pops off the top of the patch stack. If given a patch
2428 name, keeps popping off patches until the named patch is at the
2428 name, keeps popping off patches until the named patch is at the
2429 top of the stack.
2429 top of the stack.
2430
2430
2431 Return 0 on success.
2431 Return 0 on success.
2432 """
2432 """
2433 localupdate = True
2433 localupdate = True
2434 if opts.get('name'):
2434 if opts.get('name'):
2435 q = queue(ui, repo.join(""), repo.join(opts.get('name')))
2435 q = queue(ui, repo.join(""), repo.join(opts.get('name')))
2436 ui.warn(_('using patch queue: %s\n') % q.path)
2436 ui.warn(_('using patch queue: %s\n') % q.path)
2437 localupdate = False
2437 localupdate = False
2438 else:
2438 else:
2439 q = repo.mq
2439 q = repo.mq
2440 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2440 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2441 all=opts.get('all'))
2441 all=opts.get('all'))
2442 q.save_dirty()
2442 q.save_dirty()
2443 return ret
2443 return ret
2444
2444
2445 def rename(ui, repo, patch, name=None, **opts):
2445 def rename(ui, repo, patch, name=None, **opts):
2446 """rename a patch
2446 """rename a patch
2447
2447
2448 With one argument, renames the current patch to PATCH1.
2448 With one argument, renames the current patch to PATCH1.
2449 With two arguments, renames PATCH1 to PATCH2.
2449 With two arguments, renames PATCH1 to PATCH2.
2450
2450
2451 Returns 0 on success."""
2451 Returns 0 on success."""
2452
2452
2453 q = repo.mq
2453 q = repo.mq
2454
2454
2455 if not name:
2455 if not name:
2456 name = patch
2456 name = patch
2457 patch = None
2457 patch = None
2458
2458
2459 if patch:
2459 if patch:
2460 patch = q.lookup(patch)
2460 patch = q.lookup(patch)
2461 else:
2461 else:
2462 if not q.applied:
2462 if not q.applied:
2463 ui.write(_('no patches applied\n'))
2463 ui.write(_('no patches applied\n'))
2464 return
2464 return
2465 patch = q.lookup('qtip')
2465 patch = q.lookup('qtip')
2466 absdest = q.join(name)
2466 absdest = q.join(name)
2467 if os.path.isdir(absdest):
2467 if os.path.isdir(absdest):
2468 name = normname(os.path.join(name, os.path.basename(patch)))
2468 name = normname(os.path.join(name, os.path.basename(patch)))
2469 absdest = q.join(name)
2469 absdest = q.join(name)
2470 if os.path.exists(absdest):
2470 if os.path.exists(absdest):
2471 raise util.Abort(_('%s already exists') % absdest)
2471 raise util.Abort(_('%s already exists') % absdest)
2472
2472
2473 if name in q.series:
2473 if name in q.series:
2474 raise util.Abort(
2474 raise util.Abort(
2475 _('A patch named %s already exists in the series file') % name)
2475 _('A patch named %s already exists in the series file') % name)
2476
2476
2477 ui.note(_('renaming %s to %s\n') % (patch, name))
2477 ui.note(_('renaming %s to %s\n') % (patch, name))
2478 i = q.find_series(patch)
2478 i = q.find_series(patch)
2479 guards = q.guard_re.findall(q.full_series[i])
2479 guards = q.guard_re.findall(q.full_series[i])
2480 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2480 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2481 q.parse_series()
2481 q.parse_series()
2482 q.series_dirty = 1
2482 q.series_dirty = 1
2483
2483
2484 info = q.isapplied(patch)
2484 info = q.isapplied(patch)
2485 if info:
2485 if info:
2486 q.applied[info[0]] = statusentry(info[1], name)
2486 q.applied[info[0]] = statusentry(info[1], name)
2487 q.applied_dirty = 1
2487 q.applied_dirty = 1
2488
2488
2489 destdir = os.path.dirname(absdest)
2489 destdir = os.path.dirname(absdest)
2490 if not os.path.isdir(destdir):
2490 if not os.path.isdir(destdir):
2491 os.makedirs(destdir)
2491 os.makedirs(destdir)
2492 util.rename(q.join(patch), absdest)
2492 util.rename(q.join(patch), absdest)
2493 r = q.qrepo()
2493 r = q.qrepo()
2494 if r and patch in r.dirstate:
2494 if r and patch in r.dirstate:
2495 wctx = r[None]
2495 wctx = r[None]
2496 wlock = r.wlock()
2496 wlock = r.wlock()
2497 try:
2497 try:
2498 if r.dirstate[patch] == 'a':
2498 if r.dirstate[patch] == 'a':
2499 r.dirstate.forget(patch)
2499 r.dirstate.forget(patch)
2500 r.dirstate.add(name)
2500 r.dirstate.add(name)
2501 else:
2501 else:
2502 if r.dirstate[name] == 'r':
2502 if r.dirstate[name] == 'r':
2503 wctx.undelete([name])
2503 wctx.undelete([name])
2504 wctx.copy(patch, name)
2504 wctx.copy(patch, name)
2505 wctx.remove([patch], False)
2505 wctx.remove([patch], False)
2506 finally:
2506 finally:
2507 wlock.release()
2507 wlock.release()
2508
2508
2509 q.save_dirty()
2509 q.save_dirty()
2510
2510
2511 def restore(ui, repo, rev, **opts):
2511 def restore(ui, repo, rev, **opts):
2512 """restore the queue state saved by a revision (DEPRECATED)
2512 """restore the queue state saved by a revision (DEPRECATED)
2513
2513
2514 This command is deprecated, use :hg:`rebase` instead."""
2514 This command is deprecated, use :hg:`rebase` instead."""
2515 rev = repo.lookup(rev)
2515 rev = repo.lookup(rev)
2516 q = repo.mq
2516 q = repo.mq
2517 q.restore(repo, rev, delete=opts.get('delete'),
2517 q.restore(repo, rev, delete=opts.get('delete'),
2518 qupdate=opts.get('update'))
2518 qupdate=opts.get('update'))
2519 q.save_dirty()
2519 q.save_dirty()
2520 return 0
2520 return 0
2521
2521
2522 def save(ui, repo, **opts):
2522 def save(ui, repo, **opts):
2523 """save current queue state (DEPRECATED)
2523 """save current queue state (DEPRECATED)
2524
2524
2525 This command is deprecated, use :hg:`rebase` instead."""
2525 This command is deprecated, use :hg:`rebase` instead."""
2526 q = repo.mq
2526 q = repo.mq
2527 message = cmdutil.logmessage(opts)
2527 message = cmdutil.logmessage(opts)
2528 ret = q.save(repo, msg=message)
2528 ret = q.save(repo, msg=message)
2529 if ret:
2529 if ret:
2530 return ret
2530 return ret
2531 q.save_dirty()
2531 q.save_dirty()
2532 if opts.get('copy'):
2532 if opts.get('copy'):
2533 path = q.path
2533 path = q.path
2534 if opts.get('name'):
2534 if opts.get('name'):
2535 newpath = os.path.join(q.basepath, opts.get('name'))
2535 newpath = os.path.join(q.basepath, opts.get('name'))
2536 if os.path.exists(newpath):
2536 if os.path.exists(newpath):
2537 if not os.path.isdir(newpath):
2537 if not os.path.isdir(newpath):
2538 raise util.Abort(_('destination %s exists and is not '
2538 raise util.Abort(_('destination %s exists and is not '
2539 'a directory') % newpath)
2539 'a directory') % newpath)
2540 if not opts.get('force'):
2540 if not opts.get('force'):
2541 raise util.Abort(_('destination %s exists, '
2541 raise util.Abort(_('destination %s exists, '
2542 'use -f to force') % newpath)
2542 'use -f to force') % newpath)
2543 else:
2543 else:
2544 newpath = savename(path)
2544 newpath = savename(path)
2545 ui.warn(_("copy %s to %s\n") % (path, newpath))
2545 ui.warn(_("copy %s to %s\n") % (path, newpath))
2546 util.copyfiles(path, newpath)
2546 util.copyfiles(path, newpath)
2547 if opts.get('empty'):
2547 if opts.get('empty'):
2548 try:
2548 try:
2549 os.unlink(q.join(q.status_path))
2549 os.unlink(q.join(q.status_path))
2550 except:
2550 except:
2551 pass
2551 pass
2552 return 0
2552 return 0
2553
2553
2554 def strip(ui, repo, *revs, **opts):
2554 def strip(ui, repo, *revs, **opts):
2555 """strip changesets and all their descendants from the repository
2555 """strip changesets and all their descendants from the repository
2556
2556
2557 The strip command removes the specified changesets and all their
2557 The strip command removes the specified changesets and all their
2558 descendants. If the working directory has uncommitted changes, the
2558 descendants. If the working directory has uncommitted changes, the
2559 operation is aborted unless the --force flag is supplied, in which
2559 operation is aborted unless the --force flag is supplied, in which
2560 case changes will be discarded.
2560 case changes will be discarded.
2561
2561
2562 If a parent of the working directory is stripped, then the working
2562 If a parent of the working directory is stripped, then the working
2563 directory will automatically be updated to the most recent
2563 directory will automatically be updated to the most recent
2564 available ancestor of the stripped parent after the operation
2564 available ancestor of the stripped parent after the operation
2565 completes.
2565 completes.
2566
2566
2567 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2567 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2568 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2568 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2569 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2569 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2570 where BUNDLE is the bundle file created by the strip. Note that
2570 where BUNDLE is the bundle file created by the strip. Note that
2571 the local revision numbers will in general be different after the
2571 the local revision numbers will in general be different after the
2572 restore.
2572 restore.
2573
2573
2574 Use the --no-backup option to discard the backup bundle once the
2574 Use the --no-backup option to discard the backup bundle once the
2575 operation completes.
2575 operation completes.
2576
2576
2577 Return 0 on success.
2577 Return 0 on success.
2578 """
2578 """
2579 backup = 'all'
2579 backup = 'all'
2580 if opts.get('backup'):
2580 if opts.get('backup'):
2581 backup = 'strip'
2581 backup = 'strip'
2582 elif opts.get('no_backup') or opts.get('nobackup'):
2582 elif opts.get('no_backup') or opts.get('nobackup'):
2583 backup = 'none'
2583 backup = 'none'
2584
2584
2585 cl = repo.changelog
2585 cl = repo.changelog
2586 revs = set(cmdutil.revrange(repo, revs))
2586 revs = set(cmdutil.revrange(repo, revs))
2587 if not revs:
2587 if not revs:
2588 raise util.Abort(_('empty revision set'))
2588 raise util.Abort(_('empty revision set'))
2589
2589
2590 descendants = set(cl.descendants(*revs))
2590 descendants = set(cl.descendants(*revs))
2591 strippedrevs = revs.union(descendants)
2591 strippedrevs = revs.union(descendants)
2592 roots = revs.difference(descendants)
2592 roots = revs.difference(descendants)
2593
2593
2594 update = False
2594 update = False
2595 # if one of the wdir parent is stripped we'll need
2595 # if one of the wdir parent is stripped we'll need
2596 # to update away to an earlier revision
2596 # to update away to an earlier revision
2597 for p in repo.dirstate.parents():
2597 for p in repo.dirstate.parents():
2598 if p != nullid and cl.rev(p) in strippedrevs:
2598 if p != nullid and cl.rev(p) in strippedrevs:
2599 update = True
2599 update = True
2600 break
2600 break
2601
2601
2602 rootnodes = set(cl.node(r) for r in roots)
2602 rootnodes = set(cl.node(r) for r in roots)
2603
2603
2604 q = repo.mq
2604 q = repo.mq
2605 if q.applied:
2605 if q.applied:
2606 # refresh queue state if we're about to strip
2606 # refresh queue state if we're about to strip
2607 # applied patches
2607 # applied patches
2608 if cl.rev(repo.lookup('qtip')) in strippedrevs:
2608 if cl.rev(repo.lookup('qtip')) in strippedrevs:
2609 q.applied_dirty = True
2609 q.applied_dirty = True
2610 start = 0
2610 start = 0
2611 end = len(q.applied)
2611 end = len(q.applied)
2612 for i, statusentry in enumerate(q.applied):
2612 for i, statusentry in enumerate(q.applied):
2613 if statusentry.node in rootnodes:
2613 if statusentry.node in rootnodes:
2614 # if one of the stripped roots is an applied
2614 # if one of the stripped roots is an applied
2615 # patch, only part of the queue is stripped
2615 # patch, only part of the queue is stripped
2616 start = i
2616 start = i
2617 break
2617 break
2618 del q.applied[start:end]
2618 del q.applied[start:end]
2619 q.save_dirty()
2619 q.save_dirty()
2620
2620
2621 revs = list(rootnodes)
2621 revs = list(rootnodes)
2622 if update and opts.get('keep'):
2622 if update and opts.get('keep'):
2623 wlock = repo.wlock()
2623 wlock = repo.wlock()
2624 try:
2624 try:
2625 urev = repo.mq.qparents(repo, revs[0])
2625 urev = repo.mq.qparents(repo, revs[0])
2626 repo.dirstate.rebuild(urev, repo[urev].manifest())
2626 repo.dirstate.rebuild(urev, repo[urev].manifest())
2627 repo.dirstate.write()
2627 repo.dirstate.write()
2628 update = False
2628 update = False
2629 finally:
2629 finally:
2630 wlock.release()
2630 wlock.release()
2631
2631
2632 repo.mq.strip(repo, revs, backup=backup, update=update,
2632 repo.mq.strip(repo, revs, backup=backup, update=update,
2633 force=opts.get('force'))
2633 force=opts.get('force'))
2634 return 0
2634 return 0
2635
2635
2636 def select(ui, repo, *args, **opts):
2636 def select(ui, repo, *args, **opts):
2637 '''set or print guarded patches to push
2637 '''set or print guarded patches to push
2638
2638
2639 Use the :hg:`qguard` command to set or print guards on patch, then use
2639 Use the :hg:`qguard` command to set or print guards on patch, then use
2640 qselect to tell mq which guards to use. A patch will be pushed if
2640 qselect to tell mq which guards to use. A patch will be pushed if
2641 it has no guards or any positive guards match the currently
2641 it has no guards or any positive guards match the currently
2642 selected guard, but will not be pushed if any negative guards
2642 selected guard, but will not be pushed if any negative guards
2643 match the current guard. For example::
2643 match the current guard. For example::
2644
2644
2645 qguard foo.patch -- -stable (negative guard)
2645 qguard foo.patch -- -stable (negative guard)
2646 qguard bar.patch +stable (positive guard)
2646 qguard bar.patch +stable (positive guard)
2647 qselect stable
2647 qselect stable
2648
2648
2649 This activates the "stable" guard. mq will skip foo.patch (because
2649 This activates the "stable" guard. mq will skip foo.patch (because
2650 it has a negative match) but push bar.patch (because it has a
2650 it has a negative match) but push bar.patch (because it has a
2651 positive match).
2651 positive match).
2652
2652
2653 With no arguments, prints the currently active guards.
2653 With no arguments, prints the currently active guards.
2654 With one argument, sets the active guard.
2654 With one argument, sets the active guard.
2655
2655
2656 Use -n/--none to deactivate guards (no other arguments needed).
2656 Use -n/--none to deactivate guards (no other arguments needed).
2657 When no guards are active, patches with positive guards are
2657 When no guards are active, patches with positive guards are
2658 skipped and patches with negative guards are pushed.
2658 skipped and patches with negative guards are pushed.
2659
2659
2660 qselect can change the guards on applied patches. It does not pop
2660 qselect can change the guards on applied patches. It does not pop
2661 guarded patches by default. Use --pop to pop back to the last
2661 guarded patches by default. Use --pop to pop back to the last
2662 applied patch that is not guarded. Use --reapply (which implies
2662 applied patch that is not guarded. Use --reapply (which implies
2663 --pop) to push back to the current patch afterwards, but skip
2663 --pop) to push back to the current patch afterwards, but skip
2664 guarded patches.
2664 guarded patches.
2665
2665
2666 Use -s/--series to print a list of all guards in the series file
2666 Use -s/--series to print a list of all guards in the series file
2667 (no other arguments needed). Use -v for more information.
2667 (no other arguments needed). Use -v for more information.
2668
2668
2669 Returns 0 on success.'''
2669 Returns 0 on success.'''
2670
2670
2671 q = repo.mq
2671 q = repo.mq
2672 guards = q.active()
2672 guards = q.active()
2673 if args or opts.get('none'):
2673 if args or opts.get('none'):
2674 old_unapplied = q.unapplied(repo)
2674 old_unapplied = q.unapplied(repo)
2675 old_guarded = [i for i in xrange(len(q.applied)) if
2675 old_guarded = [i for i in xrange(len(q.applied)) if
2676 not q.pushable(i)[0]]
2676 not q.pushable(i)[0]]
2677 q.set_active(args)
2677 q.set_active(args)
2678 q.save_dirty()
2678 q.save_dirty()
2679 if not args:
2679 if not args:
2680 ui.status(_('guards deactivated\n'))
2680 ui.status(_('guards deactivated\n'))
2681 if not opts.get('pop') and not opts.get('reapply'):
2681 if not opts.get('pop') and not opts.get('reapply'):
2682 unapplied = q.unapplied(repo)
2682 unapplied = q.unapplied(repo)
2683 guarded = [i for i in xrange(len(q.applied))
2683 guarded = [i for i in xrange(len(q.applied))
2684 if not q.pushable(i)[0]]
2684 if not q.pushable(i)[0]]
2685 if len(unapplied) != len(old_unapplied):
2685 if len(unapplied) != len(old_unapplied):
2686 ui.status(_('number of unguarded, unapplied patches has '
2686 ui.status(_('number of unguarded, unapplied patches has '
2687 'changed from %d to %d\n') %
2687 'changed from %d to %d\n') %
2688 (len(old_unapplied), len(unapplied)))
2688 (len(old_unapplied), len(unapplied)))
2689 if len(guarded) != len(old_guarded):
2689 if len(guarded) != len(old_guarded):
2690 ui.status(_('number of guarded, applied patches has changed '
2690 ui.status(_('number of guarded, applied patches has changed '
2691 'from %d to %d\n') %
2691 'from %d to %d\n') %
2692 (len(old_guarded), len(guarded)))
2692 (len(old_guarded), len(guarded)))
2693 elif opts.get('series'):
2693 elif opts.get('series'):
2694 guards = {}
2694 guards = {}
2695 noguards = 0
2695 noguards = 0
2696 for gs in q.series_guards:
2696 for gs in q.series_guards:
2697 if not gs:
2697 if not gs:
2698 noguards += 1
2698 noguards += 1
2699 for g in gs:
2699 for g in gs:
2700 guards.setdefault(g, 0)
2700 guards.setdefault(g, 0)
2701 guards[g] += 1
2701 guards[g] += 1
2702 if ui.verbose:
2702 if ui.verbose:
2703 guards['NONE'] = noguards
2703 guards['NONE'] = noguards
2704 guards = guards.items()
2704 guards = guards.items()
2705 guards.sort(key=lambda x: x[0][1:])
2705 guards.sort(key=lambda x: x[0][1:])
2706 if guards:
2706 if guards:
2707 ui.note(_('guards in series file:\n'))
2707 ui.note(_('guards in series file:\n'))
2708 for guard, count in guards:
2708 for guard, count in guards:
2709 ui.note('%2d ' % count)
2709 ui.note('%2d ' % count)
2710 ui.write(guard, '\n')
2710 ui.write(guard, '\n')
2711 else:
2711 else:
2712 ui.note(_('no guards in series file\n'))
2712 ui.note(_('no guards in series file\n'))
2713 else:
2713 else:
2714 if guards:
2714 if guards:
2715 ui.note(_('active guards:\n'))
2715 ui.note(_('active guards:\n'))
2716 for g in guards:
2716 for g in guards:
2717 ui.write(g, '\n')
2717 ui.write(g, '\n')
2718 else:
2718 else:
2719 ui.write(_('no active guards\n'))
2719 ui.write(_('no active guards\n'))
2720 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
2720 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
2721 popped = False
2721 popped = False
2722 if opts.get('pop') or opts.get('reapply'):
2722 if opts.get('pop') or opts.get('reapply'):
2723 for i in xrange(len(q.applied)):
2723 for i in xrange(len(q.applied)):
2724 pushable, reason = q.pushable(i)
2724 pushable, reason = q.pushable(i)
2725 if not pushable:
2725 if not pushable:
2726 ui.status(_('popping guarded patches\n'))
2726 ui.status(_('popping guarded patches\n'))
2727 popped = True
2727 popped = True
2728 if i == 0:
2728 if i == 0:
2729 q.pop(repo, all=True)
2729 q.pop(repo, all=True)
2730 else:
2730 else:
2731 q.pop(repo, i - 1)
2731 q.pop(repo, i - 1)
2732 break
2732 break
2733 if popped:
2733 if popped:
2734 try:
2734 try:
2735 if reapply:
2735 if reapply:
2736 ui.status(_('reapplying unguarded patches\n'))
2736 ui.status(_('reapplying unguarded patches\n'))
2737 q.push(repo, reapply)
2737 q.push(repo, reapply)
2738 finally:
2738 finally:
2739 q.save_dirty()
2739 q.save_dirty()
2740
2740
2741 def finish(ui, repo, *revrange, **opts):
2741 def finish(ui, repo, *revrange, **opts):
2742 """move applied patches into repository history
2742 """move applied patches into repository history
2743
2743
2744 Finishes the specified revisions (corresponding to applied
2744 Finishes the specified revisions (corresponding to applied
2745 patches) by moving them out of mq control into regular repository
2745 patches) by moving them out of mq control into regular repository
2746 history.
2746 history.
2747
2747
2748 Accepts a revision range or the -a/--applied option. If --applied
2748 Accepts a revision range or the -a/--applied option. If --applied
2749 is specified, all applied mq revisions are removed from mq
2749 is specified, all applied mq revisions are removed from mq
2750 control. Otherwise, the given revisions must be at the base of the
2750 control. Otherwise, the given revisions must be at the base of the
2751 stack of applied patches.
2751 stack of applied patches.
2752
2752
2753 This can be especially useful if your changes have been applied to
2753 This can be especially useful if your changes have been applied to
2754 an upstream repository, or if you are about to push your changes
2754 an upstream repository, or if you are about to push your changes
2755 to upstream.
2755 to upstream.
2756
2756
2757 Returns 0 on success.
2757 Returns 0 on success.
2758 """
2758 """
2759 if not opts.get('applied') and not revrange:
2759 if not opts.get('applied') and not revrange:
2760 raise util.Abort(_('no revisions specified'))
2760 raise util.Abort(_('no revisions specified'))
2761 elif opts.get('applied'):
2761 elif opts.get('applied'):
2762 revrange = ('qbase::qtip',) + revrange
2762 revrange = ('qbase::qtip',) + revrange
2763
2763
2764 q = repo.mq
2764 q = repo.mq
2765 if not q.applied:
2765 if not q.applied:
2766 ui.status(_('no patches applied\n'))
2766 ui.status(_('no patches applied\n'))
2767 return 0
2767 return 0
2768
2768
2769 revs = cmdutil.revrange(repo, revrange)
2769 revs = cmdutil.revrange(repo, revrange)
2770 q.finish(repo, revs)
2770 q.finish(repo, revs)
2771 q.save_dirty()
2771 q.save_dirty()
2772 return 0
2772 return 0
2773
2773
2774 def qqueue(ui, repo, name=None, **opts):
2774 def qqueue(ui, repo, name=None, **opts):
2775 '''manage multiple patch queues
2775 '''manage multiple patch queues
2776
2776
2777 Supports switching between different patch queues, as well as creating
2777 Supports switching between different patch queues, as well as creating
2778 new patch queues and deleting existing ones.
2778 new patch queues and deleting existing ones.
2779
2779
2780 Omitting a queue name or specifying -l/--list will show you the registered
2780 Omitting a queue name or specifying -l/--list will show you the registered
2781 queues - by default the "normal" patches queue is registered. The currently
2781 queues - by default the "normal" patches queue is registered. The currently
2782 active queue will be marked with "(active)".
2782 active queue will be marked with "(active)".
2783
2783
2784 To create a new queue, use -c/--create. The queue is automatically made
2784 To create a new queue, use -c/--create. The queue is automatically made
2785 active, except in the case where there are applied patches from the
2785 active, except in the case where there are applied patches from the
2786 currently active queue in the repository. Then the queue will only be
2786 currently active queue in the repository. Then the queue will only be
2787 created and switching will fail.
2787 created and switching will fail.
2788
2788
2789 To delete an existing queue, use --delete. You cannot delete the currently
2789 To delete an existing queue, use --delete. You cannot delete the currently
2790 active queue.
2790 active queue.
2791
2791
2792 Returns 0 on success.
2792 Returns 0 on success.
2793 '''
2793 '''
2794
2794
2795 q = repo.mq
2795 q = repo.mq
2796
2796
2797 _defaultqueue = 'patches'
2797 _defaultqueue = 'patches'
2798 _allqueues = 'patches.queues'
2798 _allqueues = 'patches.queues'
2799 _activequeue = 'patches.queue'
2799 _activequeue = 'patches.queue'
2800
2800
2801 def _getcurrent():
2801 def _getcurrent():
2802 cur = os.path.basename(q.path)
2802 cur = os.path.basename(q.path)
2803 if cur.startswith('patches-'):
2803 if cur.startswith('patches-'):
2804 cur = cur[8:]
2804 cur = cur[8:]
2805 return cur
2805 return cur
2806
2806
2807 def _noqueues():
2807 def _noqueues():
2808 try:
2808 try:
2809 fh = repo.opener(_allqueues, 'r')
2809 fh = repo.opener(_allqueues, 'r')
2810 fh.close()
2810 fh.close()
2811 except IOError:
2811 except IOError:
2812 return True
2812 return True
2813
2813
2814 return False
2814 return False
2815
2815
2816 def _getqueues():
2816 def _getqueues():
2817 current = _getcurrent()
2817 current = _getcurrent()
2818
2818
2819 try:
2819 try:
2820 fh = repo.opener(_allqueues, 'r')
2820 fh = repo.opener(_allqueues, 'r')
2821 queues = [queue.strip() for queue in fh if queue.strip()]
2821 queues = [queue.strip() for queue in fh if queue.strip()]
2822 fh.close()
2822 fh.close()
2823 if current not in queues:
2823 if current not in queues:
2824 queues.append(current)
2824 queues.append(current)
2825 except IOError:
2825 except IOError:
2826 queues = [_defaultqueue]
2826 queues = [_defaultqueue]
2827
2827
2828 return sorted(queues)
2828 return sorted(queues)
2829
2829
2830 def _setactive(name):
2830 def _setactive(name):
2831 if q.applied:
2831 if q.applied:
2832 raise util.Abort(_('patches applied - cannot set new queue active'))
2832 raise util.Abort(_('patches applied - cannot set new queue active'))
2833 _setactivenocheck(name)
2833 _setactivenocheck(name)
2834
2834
2835 def _setactivenocheck(name):
2835 def _setactivenocheck(name):
2836 fh = repo.opener(_activequeue, 'w')
2836 fh = repo.opener(_activequeue, 'w')
2837 if name != 'patches':
2837 if name != 'patches':
2838 fh.write(name)
2838 fh.write(name)
2839 fh.close()
2839 fh.close()
2840
2840
2841 def _addqueue(name):
2841 def _addqueue(name):
2842 fh = repo.opener(_allqueues, 'a')
2842 fh = repo.opener(_allqueues, 'a')
2843 fh.write('%s\n' % (name,))
2843 fh.write('%s\n' % (name,))
2844 fh.close()
2844 fh.close()
2845
2845
2846 def _queuedir(name):
2846 def _queuedir(name):
2847 if name == 'patches':
2847 if name == 'patches':
2848 return repo.join('patches')
2848 return repo.join('patches')
2849 else:
2849 else:
2850 return repo.join('patches-' + name)
2850 return repo.join('patches-' + name)
2851
2851
2852 def _validname(name):
2852 def _validname(name):
2853 for n in name:
2853 for n in name:
2854 if n in ':\\/.':
2854 if n in ':\\/.':
2855 return False
2855 return False
2856 return True
2856 return True
2857
2857
2858 def _delete(name):
2858 def _delete(name):
2859 if name not in existing:
2859 if name not in existing:
2860 raise util.Abort(_('cannot delete queue that does not exist'))
2860 raise util.Abort(_('cannot delete queue that does not exist'))
2861
2861
2862 current = _getcurrent()
2862 current = _getcurrent()
2863
2863
2864 if name == current:
2864 if name == current:
2865 raise util.Abort(_('cannot delete currently active queue'))
2865 raise util.Abort(_('cannot delete currently active queue'))
2866
2866
2867 fh = repo.opener('patches.queues.new', 'w')
2867 fh = repo.opener('patches.queues.new', 'w')
2868 for queue in existing:
2868 for queue in existing:
2869 if queue == name:
2869 if queue == name:
2870 continue
2870 continue
2871 fh.write('%s\n' % (queue,))
2871 fh.write('%s\n' % (queue,))
2872 fh.close()
2872 fh.close()
2873 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2873 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2874
2874
2875 if not name or opts.get('list'):
2875 if not name or opts.get('list'):
2876 current = _getcurrent()
2876 current = _getcurrent()
2877 for queue in _getqueues():
2877 for queue in _getqueues():
2878 ui.write('%s' % (queue,))
2878 ui.write('%s' % (queue,))
2879 if queue == current and not ui.quiet:
2879 if queue == current and not ui.quiet:
2880 ui.write(_(' (active)\n'))
2880 ui.write(_(' (active)\n'))
2881 else:
2881 else:
2882 ui.write('\n')
2882 ui.write('\n')
2883 return
2883 return
2884
2884
2885 if not _validname(name):
2885 if not _validname(name):
2886 raise util.Abort(
2886 raise util.Abort(
2887 _('invalid queue name, may not contain the characters ":\\/."'))
2887 _('invalid queue name, may not contain the characters ":\\/."'))
2888
2888
2889 existing = _getqueues()
2889 existing = _getqueues()
2890
2890
2891 if opts.get('create'):
2891 if opts.get('create'):
2892 if name in existing:
2892 if name in existing:
2893 raise util.Abort(_('queue "%s" already exists') % name)
2893 raise util.Abort(_('queue "%s" already exists') % name)
2894 if _noqueues():
2894 if _noqueues():
2895 _addqueue(_defaultqueue)
2895 _addqueue(_defaultqueue)
2896 _addqueue(name)
2896 _addqueue(name)
2897 _setactive(name)
2897 _setactive(name)
2898 elif opts.get('rename'):
2898 elif opts.get('rename'):
2899 current = _getcurrent()
2899 current = _getcurrent()
2900 if name == current:
2900 if name == current:
2901 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
2901 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
2902 if name in existing:
2902 if name in existing:
2903 raise util.Abort(_('queue "%s" already exists') % name)
2903 raise util.Abort(_('queue "%s" already exists') % name)
2904
2904
2905 olddir = _queuedir(current)
2905 olddir = _queuedir(current)
2906 newdir = _queuedir(name)
2906 newdir = _queuedir(name)
2907
2907
2908 if os.path.exists(newdir):
2908 if os.path.exists(newdir):
2909 raise util.Abort(_('non-queue directory "%s" already exists') %
2909 raise util.Abort(_('non-queue directory "%s" already exists') %
2910 newdir)
2910 newdir)
2911
2911
2912 fh = repo.opener('patches.queues.new', 'w')
2912 fh = repo.opener('patches.queues.new', 'w')
2913 for queue in existing:
2913 for queue in existing:
2914 if queue == current:
2914 if queue == current:
2915 fh.write('%s\n' % (name,))
2915 fh.write('%s\n' % (name,))
2916 if os.path.exists(olddir):
2916 if os.path.exists(olddir):
2917 util.rename(olddir, newdir)
2917 util.rename(olddir, newdir)
2918 else:
2918 else:
2919 fh.write('%s\n' % (queue,))
2919 fh.write('%s\n' % (queue,))
2920 fh.close()
2920 fh.close()
2921 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2921 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2922 _setactivenocheck(name)
2922 _setactivenocheck(name)
2923 elif opts.get('delete'):
2923 elif opts.get('delete'):
2924 _delete(name)
2924 _delete(name)
2925 elif opts.get('purge'):
2925 elif opts.get('purge'):
2926 if name in existing:
2926 if name in existing:
2927 _delete(name)
2927 _delete(name)
2928 qdir = _queuedir(name)
2928 qdir = _queuedir(name)
2929 if os.path.exists(qdir):
2929 if os.path.exists(qdir):
2930 shutil.rmtree(qdir)
2930 shutil.rmtree(qdir)
2931 else:
2931 else:
2932 if name not in existing:
2932 if name not in existing:
2933 raise util.Abort(_('use --create to create a new queue'))
2933 raise util.Abort(_('use --create to create a new queue'))
2934 _setactive(name)
2934 _setactive(name)
2935
2935
2936 def reposetup(ui, repo):
2936 def reposetup(ui, repo):
2937 class mqrepo(repo.__class__):
2937 class mqrepo(repo.__class__):
2938 @util.propertycache
2938 @util.propertycache
2939 def mq(self):
2939 def mq(self):
2940 return queue(self.ui, self.join(""))
2940 return queue(self.ui, self.join(""))
2941
2941
2942 def abort_if_wdir_patched(self, errmsg, force=False):
2942 def abort_if_wdir_patched(self, errmsg, force=False):
2943 if self.mq.applied and not force:
2943 if self.mq.applied and not force:
2944 parents = self.dirstate.parents()
2944 parents = self.dirstate.parents()
2945 patches = [s.node for s in self.mq.applied]
2945 patches = [s.node for s in self.mq.applied]
2946 if parents[0] in patches or parents[1] in patches:
2946 if parents[0] in patches or parents[1] in patches:
2947 raise util.Abort(errmsg)
2947 raise util.Abort(errmsg)
2948
2948
2949 def commit(self, text="", user=None, date=None, match=None,
2949 def commit(self, text="", user=None, date=None, match=None,
2950 force=False, editor=False, extra={}):
2950 force=False, editor=False, extra={}):
2951 self.abort_if_wdir_patched(
2951 self.abort_if_wdir_patched(
2952 _('cannot commit over an applied mq patch'),
2952 _('cannot commit over an applied mq patch'),
2953 force)
2953 force)
2954
2954
2955 return super(mqrepo, self).commit(text, user, date, match, force,
2955 return super(mqrepo, self).commit(text, user, date, match, force,
2956 editor, extra)
2956 editor, extra)
2957
2957
2958 def checkpush(self, force, revs):
2958 def checkpush(self, force, revs):
2959 if self.mq.applied and not force:
2959 if self.mq.applied and not force:
2960 haspatches = True
2960 haspatches = True
2961 if revs:
2961 if revs:
2962 # Assume applied patches have no non-patch descendants
2962 # Assume applied patches have no non-patch descendants
2963 # and are not on remote already. If they appear in the
2963 # and are not on remote already. If they appear in the
2964 # set of resolved 'revs', bail out.
2964 # set of resolved 'revs', bail out.
2965 applied = set(e.node for e in self.mq.applied)
2965 applied = set(e.node for e in self.mq.applied)
2966 haspatches = bool([n for n in revs if n in applied])
2966 haspatches = bool([n for n in revs if n in applied])
2967 if haspatches:
2967 if haspatches:
2968 raise util.Abort(_('source has mq patches applied'))
2968 raise util.Abort(_('source has mq patches applied'))
2969 super(mqrepo, self).checkpush(force, revs)
2969 super(mqrepo, self).checkpush(force, revs)
2970
2970
2971 def _findtags(self):
2971 def _findtags(self):
2972 '''augment tags from base class with patch tags'''
2972 '''augment tags from base class with patch tags'''
2973 result = super(mqrepo, self)._findtags()
2973 result = super(mqrepo, self)._findtags()
2974
2974
2975 q = self.mq
2975 q = self.mq
2976 if not q.applied:
2976 if not q.applied:
2977 return result
2977 return result
2978
2978
2979 mqtags = [(patch.node, patch.name) for patch in q.applied]
2979 mqtags = [(patch.node, patch.name) for patch in q.applied]
2980
2980
2981 try:
2981 try:
2982 r = self.changelog.rev(mqtags[-1][0])
2982 r = self.changelog.rev(mqtags[-1][0])
2983 except error.RepoLookupError:
2983 except error.RepoLookupError:
2984 self.ui.warn(_('mq status file refers to unknown node %s\n')
2984 self.ui.warn(_('mq status file refers to unknown node %s\n')
2985 % short(mqtags[-1][0]))
2985 % short(mqtags[-1][0]))
2986 return result
2986 return result
2987
2987
2988 mqtags.append((mqtags[-1][0], 'qtip'))
2988 mqtags.append((mqtags[-1][0], 'qtip'))
2989 mqtags.append((mqtags[0][0], 'qbase'))
2989 mqtags.append((mqtags[0][0], 'qbase'))
2990 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2990 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2991 tags = result[0]
2991 tags = result[0]
2992 for patch in mqtags:
2992 for patch in mqtags:
2993 if patch[1] in tags:
2993 if patch[1] in tags:
2994 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2994 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2995 % patch[1])
2995 % patch[1])
2996 else:
2996 else:
2997 tags[patch[1]] = patch[0]
2997 tags[patch[1]] = patch[0]
2998
2998
2999 return result
2999 return result
3000
3000
3001 def _branchtags(self, partial, lrev):
3001 def _branchtags(self, partial, lrev):
3002 q = self.mq
3002 q = self.mq
3003 if not q.applied:
3003 if not q.applied:
3004 return super(mqrepo, self)._branchtags(partial, lrev)
3004 return super(mqrepo, self)._branchtags(partial, lrev)
3005
3005
3006 cl = self.changelog
3006 cl = self.changelog
3007 qbasenode = q.applied[0].node
3007 qbasenode = q.applied[0].node
3008 try:
3008 try:
3009 qbase = cl.rev(qbasenode)
3009 qbase = cl.rev(qbasenode)
3010 except error.LookupError:
3010 except error.LookupError:
3011 self.ui.warn(_('mq status file refers to unknown node %s\n')
3011 self.ui.warn(_('mq status file refers to unknown node %s\n')
3012 % short(qbasenode))
3012 % short(qbasenode))
3013 return super(mqrepo, self)._branchtags(partial, lrev)
3013 return super(mqrepo, self)._branchtags(partial, lrev)
3014
3014
3015 start = lrev + 1
3015 start = lrev + 1
3016 if start < qbase:
3016 if start < qbase:
3017 # update the cache (excluding the patches) and save it
3017 # update the cache (excluding the patches) and save it
3018 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
3018 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
3019 self._updatebranchcache(partial, ctxgen)
3019 self._updatebranchcache(partial, ctxgen)
3020 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
3020 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
3021 start = qbase
3021 start = qbase
3022 # if start = qbase, the cache is as updated as it should be.
3022 # if start = qbase, the cache is as updated as it should be.
3023 # if start > qbase, the cache includes (part of) the patches.
3023 # if start > qbase, the cache includes (part of) the patches.
3024 # we might as well use it, but we won't save it.
3024 # we might as well use it, but we won't save it.
3025
3025
3026 # update the cache up to the tip
3026 # update the cache up to the tip
3027 ctxgen = (self[r] for r in xrange(start, len(cl)))
3027 ctxgen = (self[r] for r in xrange(start, len(cl)))
3028 self._updatebranchcache(partial, ctxgen)
3028 self._updatebranchcache(partial, ctxgen)
3029
3029
3030 return partial
3030 return partial
3031
3031
3032 if repo.local():
3032 if repo.local():
3033 repo.__class__ = mqrepo
3033 repo.__class__ = mqrepo
3034
3034
3035 def mqimport(orig, ui, repo, *args, **kwargs):
3035 def mqimport(orig, ui, repo, *args, **kwargs):
3036 if (hasattr(repo, 'abort_if_wdir_patched')
3036 if (hasattr(repo, 'abort_if_wdir_patched')
3037 and not kwargs.get('no_commit', False)):
3037 and not kwargs.get('no_commit', False)):
3038 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
3038 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
3039 kwargs.get('force'))
3039 kwargs.get('force'))
3040 return orig(ui, repo, *args, **kwargs)
3040 return orig(ui, repo, *args, **kwargs)
3041
3041
3042 def mqinit(orig, ui, *args, **kwargs):
3042 def mqinit(orig, ui, *args, **kwargs):
3043 mq = kwargs.pop('mq', None)
3043 mq = kwargs.pop('mq', None)
3044
3044
3045 if not mq:
3045 if not mq:
3046 return orig(ui, *args, **kwargs)
3046 return orig(ui, *args, **kwargs)
3047
3047
3048 if args:
3048 if args:
3049 repopath = args[0]
3049 repopath = args[0]
3050 if not hg.islocal(repopath):
3050 if not hg.islocal(repopath):
3051 raise util.Abort(_('only a local queue repository '
3051 raise util.Abort(_('only a local queue repository '
3052 'may be initialized'))
3052 'may be initialized'))
3053 else:
3053 else:
3054 repopath = cmdutil.findrepo(os.getcwd())
3054 repopath = cmdutil.findrepo(os.getcwd())
3055 if not repopath:
3055 if not repopath:
3056 raise util.Abort(_('there is no Mercurial repository here '
3056 raise util.Abort(_('there is no Mercurial repository here '
3057 '(.hg not found)'))
3057 '(.hg not found)'))
3058 repo = hg.repository(ui, repopath)
3058 repo = hg.repository(ui, repopath)
3059 return qinit(ui, repo, True)
3059 return qinit(ui, repo, True)
3060
3060
3061 def mqcommand(orig, ui, repo, *args, **kwargs):
3061 def mqcommand(orig, ui, repo, *args, **kwargs):
3062 """Add --mq option to operate on patch repository instead of main"""
3062 """Add --mq option to operate on patch repository instead of main"""
3063
3063
3064 # some commands do not like getting unknown options
3064 # some commands do not like getting unknown options
3065 mq = kwargs.pop('mq', None)
3065 mq = kwargs.pop('mq', None)
3066
3066
3067 if not mq:
3067 if not mq:
3068 return orig(ui, repo, *args, **kwargs)
3068 return orig(ui, repo, *args, **kwargs)
3069
3069
3070 q = repo.mq
3070 q = repo.mq
3071 r = q.qrepo()
3071 r = q.qrepo()
3072 if not r:
3072 if not r:
3073 raise util.Abort(_('no queue repository'))
3073 raise util.Abort(_('no queue repository'))
3074 return orig(r.ui, r, *args, **kwargs)
3074 return orig(r.ui, r, *args, **kwargs)
3075
3075
3076 def summary(orig, ui, repo, *args, **kwargs):
3076 def summary(orig, ui, repo, *args, **kwargs):
3077 r = orig(ui, repo, *args, **kwargs)
3077 r = orig(ui, repo, *args, **kwargs)
3078 q = repo.mq
3078 q = repo.mq
3079 m = []
3079 m = []
3080 a, u = len(q.applied), len(q.unapplied(repo))
3080 a, u = len(q.applied), len(q.unapplied(repo))
3081 if a:
3081 if a:
3082 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3082 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3083 if u:
3083 if u:
3084 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3084 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3085 if m:
3085 if m:
3086 ui.write("mq: %s\n" % ', '.join(m))
3086 ui.write("mq: %s\n" % ', '.join(m))
3087 else:
3087 else:
3088 ui.note(_("mq: (empty queue)\n"))
3088 ui.note(_("mq: (empty queue)\n"))
3089 return r
3089 return r
3090
3090
3091 def uisetup(ui):
3091 def uisetup(ui):
3092 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3092 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3093
3093
3094 extensions.wrapcommand(commands.table, 'import', mqimport)
3094 extensions.wrapcommand(commands.table, 'import', mqimport)
3095 extensions.wrapcommand(commands.table, 'summary', summary)
3095 extensions.wrapcommand(commands.table, 'summary', summary)
3096
3096
3097 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3097 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3098 entry[1].extend(mqopt)
3098 entry[1].extend(mqopt)
3099
3099
3100 nowrap = set(commands.norepo.split(" ") + ['qrecord'])
3100 nowrap = set(commands.norepo.split(" ") + ['qrecord'])
3101
3101
3102 def dotable(cmdtable):
3102 def dotable(cmdtable):
3103 for cmd in cmdtable.keys():
3103 for cmd in cmdtable.keys():
3104 cmd = cmdutil.parsealiases(cmd)[0]
3104 cmd = cmdutil.parsealiases(cmd)[0]
3105 if cmd in nowrap:
3105 if cmd in nowrap:
3106 continue
3106 continue
3107 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3107 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3108 entry[1].extend(mqopt)
3108 entry[1].extend(mqopt)
3109
3109
3110 dotable(commands.table)
3110 dotable(commands.table)
3111
3111
3112 for extname, extmodule in extensions.extensions():
3112 for extname, extmodule in extensions.extensions():
3113 if extmodule.__file__ != __file__:
3113 if extmodule.__file__ != __file__:
3114 dotable(getattr(extmodule, 'cmdtable', {}))
3114 dotable(getattr(extmodule, 'cmdtable', {}))
3115
3115
3116 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
3116 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
3117
3117
3118 cmdtable = {
3118 cmdtable = {
3119 "qapplied":
3119 "qapplied":
3120 (applied,
3120 (applied,
3121 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
3121 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
3122 _('hg qapplied [-1] [-s] [PATCH]')),
3122 _('hg qapplied [-1] [-s] [PATCH]')),
3123 "qclone":
3123 "qclone":
3124 (clone,
3124 (clone,
3125 [('', 'pull', None, _('use pull protocol to copy metadata')),
3125 [('', 'pull', None, _('use pull protocol to copy metadata')),
3126 ('U', 'noupdate', None, _('do not update the new working directories')),
3126 ('U', 'noupdate', None, _('do not update the new working directories')),
3127 ('', 'uncompressed', None,
3127 ('', 'uncompressed', None,
3128 _('use uncompressed transfer (fast over LAN)')),
3128 _('use uncompressed transfer (fast over LAN)')),
3129 ('p', 'patches', '',
3129 ('p', 'patches', '',
3130 _('location of source patch repository'), _('REPO')),
3130 _('location of source patch repository'), _('REPO')),
3131 ] + commands.remoteopts,
3131 ] + commands.remoteopts,
3132 _('hg qclone [OPTION]... SOURCE [DEST]')),
3132 _('hg qclone [OPTION]... SOURCE [DEST]')),
3133 "qcommit|qci":
3133 "qcommit|qci":
3134 (commit,
3134 (commit,
3135 commands.table["^commit|ci"][1],
3135 commands.table["^commit|ci"][1],
3136 _('hg qcommit [OPTION]... [FILE]...')),
3136 _('hg qcommit [OPTION]... [FILE]...')),
3137 "^qdiff":
3137 "^qdiff":
3138 (diff,
3138 (diff,
3139 commands.diffopts + commands.diffopts2 + commands.walkopts,
3139 commands.diffopts + commands.diffopts2 + commands.walkopts,
3140 _('hg qdiff [OPTION]... [FILE]...')),
3140 _('hg qdiff [OPTION]... [FILE]...')),
3141 "qdelete|qremove|qrm":
3141 "qdelete|qremove|qrm":
3142 (delete,
3142 (delete,
3143 [('k', 'keep', None, _('keep patch file')),
3143 [('k', 'keep', None, _('keep patch file')),
3144 ('r', 'rev', [],
3144 ('r', 'rev', [],
3145 _('stop managing a revision (DEPRECATED)'), _('REV'))],
3145 _('stop managing a revision (DEPRECATED)'), _('REV'))],
3146 _('hg qdelete [-k] [PATCH]...')),
3146 _('hg qdelete [-k] [PATCH]...')),
3147 'qfold':
3147 'qfold':
3148 (fold,
3148 (fold,
3149 [('e', 'edit', None, _('edit patch header')),
3149 [('e', 'edit', None, _('edit patch header')),
3150 ('k', 'keep', None, _('keep folded patch files')),
3150 ('k', 'keep', None, _('keep folded patch files')),
3151 ] + commands.commitopts,
3151 ] + commands.commitopts,
3152 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
3152 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
3153 'qgoto':
3153 'qgoto':
3154 (goto,
3154 (goto,
3155 [('f', 'force', None, _('overwrite any local changes'))],
3155 [('f', 'force', None, _('overwrite any local changes'))],
3156 _('hg qgoto [OPTION]... PATCH')),
3156 _('hg qgoto [OPTION]... PATCH')),
3157 'qguard':
3157 'qguard':
3158 (guard,
3158 (guard,
3159 [('l', 'list', None, _('list all patches and guards')),
3159 [('l', 'list', None, _('list all patches and guards')),
3160 ('n', 'none', None, _('drop all guards'))],
3160 ('n', 'none', None, _('drop all guards'))],
3161 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')),
3161 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')),
3162 'qheader': (header, [], _('hg qheader [PATCH]')),
3162 'qheader': (header, [], _('hg qheader [PATCH]')),
3163 "qimport":
3163 "qimport":
3164 (qimport,
3164 (qimport,
3165 [('e', 'existing', None, _('import file in patch directory')),
3165 [('e', 'existing', None, _('import file in patch directory')),
3166 ('n', 'name', '',
3166 ('n', 'name', '',
3167 _('name of patch file'), _('NAME')),
3167 _('name of patch file'), _('NAME')),
3168 ('f', 'force', None, _('overwrite existing files')),
3168 ('f', 'force', None, _('overwrite existing files')),
3169 ('r', 'rev', [],
3169 ('r', 'rev', [],
3170 _('place existing revisions under mq control'), _('REV')),
3170 _('place existing revisions under mq control'), _('REV')),
3171 ('g', 'git', None, _('use git extended diff format')),
3171 ('g', 'git', None, _('use git extended diff format')),
3172 ('P', 'push', None, _('qpush after importing'))],
3172 ('P', 'push', None, _('qpush after importing'))],
3173 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
3173 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
3174 "^qinit":
3174 "^qinit":
3175 (init,
3175 (init,
3176 [('c', 'create-repo', None, _('create queue repository'))],
3176 [('c', 'create-repo', None, _('create queue repository'))],
3177 _('hg qinit [-c]')),
3177 _('hg qinit [-c]')),
3178 "^qnew":
3178 "^qnew":
3179 (new,
3179 (new,
3180 [('e', 'edit', None, _('edit commit message')),
3180 [('e', 'edit', None, _('edit commit message')),
3181 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
3181 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
3182 ('g', 'git', None, _('use git extended diff format')),
3182 ('g', 'git', None, _('use git extended diff format')),
3183 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
3183 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
3184 ('u', 'user', '',
3184 ('u', 'user', '',
3185 _('add "From: <USER>" to patch'), _('USER')),
3185 _('add "From: <USER>" to patch'), _('USER')),
3186 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
3186 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
3187 ('d', 'date', '',
3187 ('d', 'date', '',
3188 _('add "Date: <DATE>" to patch'), _('DATE'))
3188 _('add "Date: <DATE>" to patch'), _('DATE'))
3189 ] + commands.walkopts + commands.commitopts,
3189 ] + commands.walkopts + commands.commitopts,
3190 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...')),
3190 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...')),
3191 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
3191 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
3192 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
3192 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
3193 "^qpop":
3193 "^qpop":
3194 (pop,
3194 (pop,
3195 [('a', 'all', None, _('pop all patches')),
3195 [('a', 'all', None, _('pop all patches')),
3196 ('n', 'name', '',
3196 ('n', 'name', '',
3197 _('queue name to pop (DEPRECATED)'), _('NAME')),
3197 _('queue name to pop (DEPRECATED)'), _('NAME')),
3198 ('f', 'force', None, _('forget any local changes to patched files'))],
3198 ('f', 'force', None, _('forget any local changes to patched files'))],
3199 _('hg qpop [-a] [-f] [PATCH | INDEX]')),
3199 _('hg qpop [-a] [-f] [PATCH | INDEX]')),
3200 "^qpush":
3200 "^qpush":
3201 (push,
3201 (push,
3202 [('f', 'force', None, _('apply on top of local changes')),
3202 [('f', 'force', None, _('apply on top of local changes')),
3203 ('e', 'exact', None, _('apply the target patch to its recorded parent')),
3203 ('e', 'exact', None, _('apply the target patch to its recorded parent')),
3204 ('l', 'list', None, _('list patch name in commit text')),
3204 ('l', 'list', None, _('list patch name in commit text')),
3205 ('a', 'all', None, _('apply all patches')),
3205 ('a', 'all', None, _('apply all patches')),
3206 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
3206 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
3207 ('n', 'name', '',
3207 ('n', 'name', '',
3208 _('merge queue name (DEPRECATED)'), _('NAME')),
3208 _('merge queue name (DEPRECATED)'), _('NAME')),
3209 ('', 'move', None, _('reorder patch series and apply only the patch'))],
3209 ('', 'move', None, _('reorder patch series and apply only the patch'))],
3210 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]')),
3210 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]')),
3211 "^qrefresh":
3211 "^qrefresh":
3212 (refresh,
3212 (refresh,
3213 [('e', 'edit', None, _('edit commit message')),
3213 [('e', 'edit', None, _('edit commit message')),
3214 ('g', 'git', None, _('use git extended diff format')),
3214 ('g', 'git', None, _('use git extended diff format')),
3215 ('s', 'short', None,
3215 ('s', 'short', None,
3216 _('refresh only files already in the patch and specified files')),
3216 _('refresh only files already in the patch and specified files')),
3217 ('U', 'currentuser', None,
3217 ('U', 'currentuser', None,
3218 _('add/update author field in patch with current user')),
3218 _('add/update author field in patch with current user')),
3219 ('u', 'user', '',
3219 ('u', 'user', '',
3220 _('add/update author field in patch with given user'), _('USER')),
3220 _('add/update author field in patch with given user'), _('USER')),
3221 ('D', 'currentdate', None,
3221 ('D', 'currentdate', None,
3222 _('add/update date field in patch with current date')),
3222 _('add/update date field in patch with current date')),
3223 ('d', 'date', '',
3223 ('d', 'date', '',
3224 _('add/update date field in patch with given date'), _('DATE'))
3224 _('add/update date field in patch with given date'), _('DATE'))
3225 ] + commands.walkopts + commands.commitopts,
3225 ] + commands.walkopts + commands.commitopts,
3226 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
3226 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
3227 'qrename|qmv':
3227 'qrename|qmv':
3228 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
3228 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
3229 "qrestore":
3229 "qrestore":
3230 (restore,
3230 (restore,
3231 [('d', 'delete', None, _('delete save entry')),
3231 [('d', 'delete', None, _('delete save entry')),
3232 ('u', 'update', None, _('update queue working directory'))],
3232 ('u', 'update', None, _('update queue working directory'))],
3233 _('hg qrestore [-d] [-u] REV')),
3233 _('hg qrestore [-d] [-u] REV')),
3234 "qsave":
3234 "qsave":
3235 (save,
3235 (save,
3236 [('c', 'copy', None, _('copy patch directory')),
3236 [('c', 'copy', None, _('copy patch directory')),
3237 ('n', 'name', '',
3237 ('n', 'name', '',
3238 _('copy directory name'), _('NAME')),
3238 _('copy directory name'), _('NAME')),
3239 ('e', 'empty', None, _('clear queue status file')),
3239 ('e', 'empty', None, _('clear queue status file')),
3240 ('f', 'force', None, _('force copy'))] + commands.commitopts,
3240 ('f', 'force', None, _('force copy'))] + commands.commitopts,
3241 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
3241 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
3242 "qselect":
3242 "qselect":
3243 (select,
3243 (select,
3244 [('n', 'none', None, _('disable all guards')),
3244 [('n', 'none', None, _('disable all guards')),
3245 ('s', 'series', None, _('list all guards in series file')),
3245 ('s', 'series', None, _('list all guards in series file')),
3246 ('', 'pop', None, _('pop to before first guarded applied patch')),
3246 ('', 'pop', None, _('pop to before first guarded applied patch')),
3247 ('', 'reapply', None, _('pop, then reapply patches'))],
3247 ('', 'reapply', None, _('pop, then reapply patches'))],
3248 _('hg qselect [OPTION]... [GUARD]...')),
3248 _('hg qselect [OPTION]... [GUARD]...')),
3249 "qseries":
3249 "qseries":
3250 (series,
3250 (series,
3251 [('m', 'missing', None, _('print patches not in series')),
3251 [('m', 'missing', None, _('print patches not in series')),
3252 ] + seriesopts,
3252 ] + seriesopts,
3253 _('hg qseries [-ms]')),
3253 _('hg qseries [-ms]')),
3254 "strip":
3254 "strip":
3255 (strip,
3255 (strip,
3256 [('f', 'force', None, _('force removal of changesets, discard '
3256 [('f', 'force', None, _('force removal of changesets, discard '
3257 'uncommitted changes (no backup)')),
3257 'uncommitted changes (no backup)')),
3258 ('b', 'backup', None, _('bundle only changesets with local revision'
3258 ('b', 'backup', None, _('bundle only changesets with local revision'
3259 ' number greater than REV which are not'
3259 ' number greater than REV which are not'
3260 ' descendants of REV (DEPRECATED)')),
3260 ' descendants of REV (DEPRECATED)')),
3261 ('n', 'no-backup', None, _('no backups')),
3261 ('n', 'no-backup', None, _('no backups')),
3262 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
3262 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
3263 ('k', 'keep', None, _("do not modify working copy during strip"))],
3263 ('k', 'keep', None, _("do not modify working copy during strip"))],
3264 _('hg strip [-k] [-f] [-n] REV...')),
3264 _('hg strip [-k] [-f] [-n] REV...')),
3265 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
3265 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
3266 "qunapplied":
3266 "qunapplied":
3267 (unapplied,
3267 (unapplied,
3268 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
3268 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
3269 _('hg qunapplied [-1] [-s] [PATCH]')),
3269 _('hg qunapplied [-1] [-s] [PATCH]')),
3270 "qfinish":
3270 "qfinish":
3271 (finish,
3271 (finish,
3272 [('a', 'applied', None, _('finish all applied changesets'))],
3272 [('a', 'applied', None, _('finish all applied changesets'))],
3273 _('hg qfinish [-a] [REV]...')),
3273 _('hg qfinish [-a] [REV]...')),
3274 'qqueue':
3274 'qqueue':
3275 (qqueue,
3275 (qqueue,
3276 [
3276 [
3277 ('l', 'list', False, _('list all available queues')),
3277 ('l', 'list', False, _('list all available queues')),
3278 ('c', 'create', False, _('create new queue')),
3278 ('c', 'create', False, _('create new queue')),
3279 ('', 'rename', False, _('rename active queue')),
3279 ('', 'rename', False, _('rename active queue')),
3280 ('', 'delete', False, _('delete reference to queue')),
3280 ('', 'delete', False, _('delete reference to queue')),
3281 ('', 'purge', False, _('delete queue, and remove patch dir')),
3281 ('', 'purge', False, _('delete queue, and remove patch dir')),
3282 ],
3282 ],
3283 _('[OPTION] [QUEUE]')),
3283 _('[OPTION] [QUEUE]')),
3284 }
3284 }
3285
3285
3286 colortable = {'qguard.negative': 'red',
3286 colortable = {'qguard.negative': 'red',
3287 'qguard.positive': 'yellow',
3287 'qguard.positive': 'yellow',
3288 'qguard.unguarded': 'green',
3288 'qguard.unguarded': 'green',
3289 'qseries.applied': 'blue bold underline',
3289 'qseries.applied': 'blue bold underline',
3290 'qseries.guarded': 'black bold',
3290 'qseries.guarded': 'black bold',
3291 'qseries.missing': 'red bold',
3291 'qseries.missing': 'red bold',
3292 'qseries.unapplied': 'black bold'}
3292 'qseries.unapplied': 'black bold'}
@@ -1,1031 +1,1045 b''
1 # subrepo.py - sub-repository handling for Mercurial
1 # subrepo.py - sub-repository handling for Mercurial
2 #
2 #
3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2009-2010 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
7
8 import errno, os, re, xml.dom.minidom, shutil, posixpath
8 import errno, os, re, xml.dom.minidom, shutil, posixpath
9 import stat, subprocess, tarfile
9 import stat, subprocess, tarfile
10 from i18n import _
10 from i18n import _
11 import config, scmutil, util, node, error, cmdutil, url, bookmarks
11 import config, scmutil, util, node, error, cmdutil, url, bookmarks
12 hg = None
12 hg = None
13 propertycache = util.propertycache
13
14
14 nullstate = ('', '', 'empty')
15 nullstate = ('', '', 'empty')
15
16
16 def state(ctx, ui):
17 def state(ctx, ui):
17 """return a state dict, mapping subrepo paths configured in .hgsub
18 """return a state dict, mapping subrepo paths configured in .hgsub
18 to tuple: (source from .hgsub, revision from .hgsubstate, kind
19 to tuple: (source from .hgsub, revision from .hgsubstate, kind
19 (key in types dict))
20 (key in types dict))
20 """
21 """
21 p = config.config()
22 p = config.config()
22 def read(f, sections=None, remap=None):
23 def read(f, sections=None, remap=None):
23 if f in ctx:
24 if f in ctx:
24 try:
25 try:
25 data = ctx[f].data()
26 data = ctx[f].data()
26 except IOError, err:
27 except IOError, err:
27 if err.errno != errno.ENOENT:
28 if err.errno != errno.ENOENT:
28 raise
29 raise
29 # handle missing subrepo spec files as removed
30 # handle missing subrepo spec files as removed
30 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
31 ui.warn(_("warning: subrepo spec file %s not found\n") % f)
31 return
32 return
32 p.parse(f, data, sections, remap, read)
33 p.parse(f, data, sections, remap, read)
33 else:
34 else:
34 raise util.Abort(_("subrepo spec file %s not found") % f)
35 raise util.Abort(_("subrepo spec file %s not found") % f)
35
36
36 if '.hgsub' in ctx:
37 if '.hgsub' in ctx:
37 read('.hgsub')
38 read('.hgsub')
38
39
39 for path, src in ui.configitems('subpaths'):
40 for path, src in ui.configitems('subpaths'):
40 p.set('subpaths', path, src, ui.configsource('subpaths', path))
41 p.set('subpaths', path, src, ui.configsource('subpaths', path))
41
42
42 rev = {}
43 rev = {}
43 if '.hgsubstate' in ctx:
44 if '.hgsubstate' in ctx:
44 try:
45 try:
45 for l in ctx['.hgsubstate'].data().splitlines():
46 for l in ctx['.hgsubstate'].data().splitlines():
46 revision, path = l.split(" ", 1)
47 revision, path = l.split(" ", 1)
47 rev[path] = revision
48 rev[path] = revision
48 except IOError, err:
49 except IOError, err:
49 if err.errno != errno.ENOENT:
50 if err.errno != errno.ENOENT:
50 raise
51 raise
51
52
52 state = {}
53 state = {}
53 for path, src in p[''].items():
54 for path, src in p[''].items():
54 kind = 'hg'
55 kind = 'hg'
55 if src.startswith('['):
56 if src.startswith('['):
56 if ']' not in src:
57 if ']' not in src:
57 raise util.Abort(_('missing ] in subrepo source'))
58 raise util.Abort(_('missing ] in subrepo source'))
58 kind, src = src.split(']', 1)
59 kind, src = src.split(']', 1)
59 kind = kind[1:]
60 kind = kind[1:]
60
61
61 for pattern, repl in p.items('subpaths'):
62 for pattern, repl in p.items('subpaths'):
62 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
63 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
63 # does a string decode.
64 # does a string decode.
64 repl = repl.encode('string-escape')
65 repl = repl.encode('string-escape')
65 # However, we still want to allow back references to go
66 # However, we still want to allow back references to go
66 # through unharmed, so we turn r'\\1' into r'\1'. Again,
67 # through unharmed, so we turn r'\\1' into r'\1'. Again,
67 # extra escapes are needed because re.sub string decodes.
68 # extra escapes are needed because re.sub string decodes.
68 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
69 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
69 try:
70 try:
70 src = re.sub(pattern, repl, src, 1)
71 src = re.sub(pattern, repl, src, 1)
71 except re.error, e:
72 except re.error, e:
72 raise util.Abort(_("bad subrepository pattern in %s: %s")
73 raise util.Abort(_("bad subrepository pattern in %s: %s")
73 % (p.source('subpaths', pattern), e))
74 % (p.source('subpaths', pattern), e))
74
75
75 state[path] = (src.strip(), rev.get(path, ''), kind)
76 state[path] = (src.strip(), rev.get(path, ''), kind)
76
77
77 return state
78 return state
78
79
79 def writestate(repo, state):
80 def writestate(repo, state):
80 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
81 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
81 repo.wwrite('.hgsubstate',
82 repo.wwrite('.hgsubstate',
82 ''.join(['%s %s\n' % (state[s][1], s)
83 ''.join(['%s %s\n' % (state[s][1], s)
83 for s in sorted(state)]), '')
84 for s in sorted(state)]), '')
84
85
85 def submerge(repo, wctx, mctx, actx, overwrite):
86 def submerge(repo, wctx, mctx, actx, overwrite):
86 """delegated from merge.applyupdates: merging of .hgsubstate file
87 """delegated from merge.applyupdates: merging of .hgsubstate file
87 in working context, merging context and ancestor context"""
88 in working context, merging context and ancestor context"""
88 if mctx == actx: # backwards?
89 if mctx == actx: # backwards?
89 actx = wctx.p1()
90 actx = wctx.p1()
90 s1 = wctx.substate
91 s1 = wctx.substate
91 s2 = mctx.substate
92 s2 = mctx.substate
92 sa = actx.substate
93 sa = actx.substate
93 sm = {}
94 sm = {}
94
95
95 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
96 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
96
97
97 def debug(s, msg, r=""):
98 def debug(s, msg, r=""):
98 if r:
99 if r:
99 r = "%s:%s:%s" % r
100 r = "%s:%s:%s" % r
100 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
101 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
101
102
102 for s, l in s1.items():
103 for s, l in s1.items():
103 a = sa.get(s, nullstate)
104 a = sa.get(s, nullstate)
104 ld = l # local state with possible dirty flag for compares
105 ld = l # local state with possible dirty flag for compares
105 if wctx.sub(s).dirty():
106 if wctx.sub(s).dirty():
106 ld = (l[0], l[1] + "+")
107 ld = (l[0], l[1] + "+")
107 if wctx == actx: # overwrite
108 if wctx == actx: # overwrite
108 a = ld
109 a = ld
109
110
110 if s in s2:
111 if s in s2:
111 r = s2[s]
112 r = s2[s]
112 if ld == r or r == a: # no change or local is newer
113 if ld == r or r == a: # no change or local is newer
113 sm[s] = l
114 sm[s] = l
114 continue
115 continue
115 elif ld == a: # other side changed
116 elif ld == a: # other side changed
116 debug(s, "other changed, get", r)
117 debug(s, "other changed, get", r)
117 wctx.sub(s).get(r, overwrite)
118 wctx.sub(s).get(r, overwrite)
118 sm[s] = r
119 sm[s] = r
119 elif ld[0] != r[0]: # sources differ
120 elif ld[0] != r[0]: # sources differ
120 if repo.ui.promptchoice(
121 if repo.ui.promptchoice(
121 _(' subrepository sources for %s differ\n'
122 _(' subrepository sources for %s differ\n'
122 'use (l)ocal source (%s) or (r)emote source (%s)?')
123 'use (l)ocal source (%s) or (r)emote source (%s)?')
123 % (s, l[0], r[0]),
124 % (s, l[0], r[0]),
124 (_('&Local'), _('&Remote')), 0):
125 (_('&Local'), _('&Remote')), 0):
125 debug(s, "prompt changed, get", r)
126 debug(s, "prompt changed, get", r)
126 wctx.sub(s).get(r, overwrite)
127 wctx.sub(s).get(r, overwrite)
127 sm[s] = r
128 sm[s] = r
128 elif ld[1] == a[1]: # local side is unchanged
129 elif ld[1] == a[1]: # local side is unchanged
129 debug(s, "other side changed, get", r)
130 debug(s, "other side changed, get", r)
130 wctx.sub(s).get(r, overwrite)
131 wctx.sub(s).get(r, overwrite)
131 sm[s] = r
132 sm[s] = r
132 else:
133 else:
133 debug(s, "both sides changed, merge with", r)
134 debug(s, "both sides changed, merge with", r)
134 wctx.sub(s).merge(r)
135 wctx.sub(s).merge(r)
135 sm[s] = l
136 sm[s] = l
136 elif ld == a: # remote removed, local unchanged
137 elif ld == a: # remote removed, local unchanged
137 debug(s, "remote removed, remove")
138 debug(s, "remote removed, remove")
138 wctx.sub(s).remove()
139 wctx.sub(s).remove()
139 else:
140 else:
140 if repo.ui.promptchoice(
141 if repo.ui.promptchoice(
141 _(' local changed subrepository %s which remote removed\n'
142 _(' local changed subrepository %s which remote removed\n'
142 'use (c)hanged version or (d)elete?') % s,
143 'use (c)hanged version or (d)elete?') % s,
143 (_('&Changed'), _('&Delete')), 0):
144 (_('&Changed'), _('&Delete')), 0):
144 debug(s, "prompt remove")
145 debug(s, "prompt remove")
145 wctx.sub(s).remove()
146 wctx.sub(s).remove()
146
147
147 for s, r in sorted(s2.items()):
148 for s, r in sorted(s2.items()):
148 if s in s1:
149 if s in s1:
149 continue
150 continue
150 elif s not in sa:
151 elif s not in sa:
151 debug(s, "remote added, get", r)
152 debug(s, "remote added, get", r)
152 mctx.sub(s).get(r)
153 mctx.sub(s).get(r)
153 sm[s] = r
154 sm[s] = r
154 elif r != sa[s]:
155 elif r != sa[s]:
155 if repo.ui.promptchoice(
156 if repo.ui.promptchoice(
156 _(' remote changed subrepository %s which local removed\n'
157 _(' remote changed subrepository %s which local removed\n'
157 'use (c)hanged version or (d)elete?') % s,
158 'use (c)hanged version or (d)elete?') % s,
158 (_('&Changed'), _('&Delete')), 0) == 0:
159 (_('&Changed'), _('&Delete')), 0) == 0:
159 debug(s, "prompt recreate", r)
160 debug(s, "prompt recreate", r)
160 wctx.sub(s).get(r)
161 wctx.sub(s).get(r)
161 sm[s] = r
162 sm[s] = r
162
163
163 # record merged .hgsubstate
164 # record merged .hgsubstate
164 writestate(repo, sm)
165 writestate(repo, sm)
165
166
166 def _updateprompt(ui, sub, dirty, local, remote):
167 def _updateprompt(ui, sub, dirty, local, remote):
167 if dirty:
168 if dirty:
168 msg = (_(' subrepository sources for %s differ\n'
169 msg = (_(' subrepository sources for %s differ\n'
169 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
170 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
170 % (subrelpath(sub), local, remote))
171 % (subrelpath(sub), local, remote))
171 else:
172 else:
172 msg = (_(' subrepository sources for %s differ (in checked out version)\n'
173 msg = (_(' subrepository sources for %s differ (in checked out version)\n'
173 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
174 'use (l)ocal source (%s) or (r)emote source (%s)?\n')
174 % (subrelpath(sub), local, remote))
175 % (subrelpath(sub), local, remote))
175 return ui.promptchoice(msg, (_('&Local'), _('&Remote')), 0)
176 return ui.promptchoice(msg, (_('&Local'), _('&Remote')), 0)
176
177
177 def reporelpath(repo):
178 def reporelpath(repo):
178 """return path to this (sub)repo as seen from outermost repo"""
179 """return path to this (sub)repo as seen from outermost repo"""
179 parent = repo
180 parent = repo
180 while hasattr(parent, '_subparent'):
181 while hasattr(parent, '_subparent'):
181 parent = parent._subparent
182 parent = parent._subparent
182 return repo.root[len(parent.root)+1:]
183 return repo.root[len(parent.root)+1:]
183
184
184 def subrelpath(sub):
185 def subrelpath(sub):
185 """return path to this subrepo as seen from outermost repo"""
186 """return path to this subrepo as seen from outermost repo"""
186 if hasattr(sub, '_relpath'):
187 if hasattr(sub, '_relpath'):
187 return sub._relpath
188 return sub._relpath
188 if not hasattr(sub, '_repo'):
189 if not hasattr(sub, '_repo'):
189 return sub._path
190 return sub._path
190 return reporelpath(sub._repo)
191 return reporelpath(sub._repo)
191
192
192 def _abssource(repo, push=False, abort=True):
193 def _abssource(repo, push=False, abort=True):
193 """return pull/push path of repo - either based on parent repo .hgsub info
194 """return pull/push path of repo - either based on parent repo .hgsub info
194 or on the top repo config. Abort or return None if no source found."""
195 or on the top repo config. Abort or return None if no source found."""
195 if hasattr(repo, '_subparent'):
196 if hasattr(repo, '_subparent'):
196 source = url.url(repo._subsource)
197 source = url.url(repo._subsource)
197 source.path = posixpath.normpath(source.path)
198 source.path = posixpath.normpath(source.path)
198 if posixpath.isabs(source.path) or source.scheme:
199 if posixpath.isabs(source.path) or source.scheme:
199 return str(source)
200 return str(source)
200 parent = _abssource(repo._subparent, push, abort=False)
201 parent = _abssource(repo._subparent, push, abort=False)
201 if parent:
202 if parent:
202 parent = url.url(parent)
203 parent = url.url(parent)
203 parent.path = posixpath.join(parent.path, source.path)
204 parent.path = posixpath.join(parent.path, source.path)
204 parent.path = posixpath.normpath(parent.path)
205 parent.path = posixpath.normpath(parent.path)
205 return str(parent)
206 return str(parent)
206 else: # recursion reached top repo
207 else: # recursion reached top repo
207 if hasattr(repo, '_subtoppath'):
208 if hasattr(repo, '_subtoppath'):
208 return repo._subtoppath
209 return repo._subtoppath
209 if push and repo.ui.config('paths', 'default-push'):
210 if push and repo.ui.config('paths', 'default-push'):
210 return repo.ui.config('paths', 'default-push')
211 return repo.ui.config('paths', 'default-push')
211 if repo.ui.config('paths', 'default'):
212 if repo.ui.config('paths', 'default'):
212 return repo.ui.config('paths', 'default')
213 return repo.ui.config('paths', 'default')
213 if abort:
214 if abort:
214 raise util.Abort(_("default path for subrepository %s not found") %
215 raise util.Abort(_("default path for subrepository %s not found") %
215 reporelpath(repo))
216 reporelpath(repo))
216
217
217 def itersubrepos(ctx1, ctx2):
218 def itersubrepos(ctx1, ctx2):
218 """find subrepos in ctx1 or ctx2"""
219 """find subrepos in ctx1 or ctx2"""
219 # Create a (subpath, ctx) mapping where we prefer subpaths from
220 # Create a (subpath, ctx) mapping where we prefer subpaths from
220 # ctx1. The subpaths from ctx2 are important when the .hgsub file
221 # ctx1. The subpaths from ctx2 are important when the .hgsub file
221 # has been modified (in ctx2) but not yet committed (in ctx1).
222 # has been modified (in ctx2) but not yet committed (in ctx1).
222 subpaths = dict.fromkeys(ctx2.substate, ctx2)
223 subpaths = dict.fromkeys(ctx2.substate, ctx2)
223 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
224 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
224 for subpath, ctx in sorted(subpaths.iteritems()):
225 for subpath, ctx in sorted(subpaths.iteritems()):
225 yield subpath, ctx.sub(subpath)
226 yield subpath, ctx.sub(subpath)
226
227
227 def subrepo(ctx, path):
228 def subrepo(ctx, path):
228 """return instance of the right subrepo class for subrepo in path"""
229 """return instance of the right subrepo class for subrepo in path"""
229 # subrepo inherently violates our import layering rules
230 # subrepo inherently violates our import layering rules
230 # because it wants to make repo objects from deep inside the stack
231 # because it wants to make repo objects from deep inside the stack
231 # so we manually delay the circular imports to not break
232 # so we manually delay the circular imports to not break
232 # scripts that don't use our demand-loading
233 # scripts that don't use our demand-loading
233 global hg
234 global hg
234 import hg as h
235 import hg as h
235 hg = h
236 hg = h
236
237
237 scmutil.path_auditor(ctx._repo.root)(path)
238 scmutil.path_auditor(ctx._repo.root)(path)
238 state = ctx.substate.get(path, nullstate)
239 state = ctx.substate.get(path, nullstate)
239 if state[2] not in types:
240 if state[2] not in types:
240 raise util.Abort(_('unknown subrepo type %s') % state[2])
241 raise util.Abort(_('unknown subrepo type %s') % state[2])
241 return types[state[2]](ctx, path, state[:2])
242 return types[state[2]](ctx, path, state[:2])
242
243
243 # subrepo classes need to implement the following abstract class:
244 # subrepo classes need to implement the following abstract class:
244
245
245 class abstractsubrepo(object):
246 class abstractsubrepo(object):
246
247
247 def dirty(self, ignoreupdate=False):
248 def dirty(self, ignoreupdate=False):
248 """returns true if the dirstate of the subrepo is dirty or does not
249 """returns true if the dirstate of the subrepo is dirty or does not
249 match current stored state. If ignoreupdate is true, only check
250 match current stored state. If ignoreupdate is true, only check
250 whether the subrepo has uncommitted changes in its dirstate.
251 whether the subrepo has uncommitted changes in its dirstate.
251 """
252 """
252 raise NotImplementedError
253 raise NotImplementedError
253
254
254 def checknested(self, path):
255 def checknested(self, path):
255 """check if path is a subrepository within this repository"""
256 """check if path is a subrepository within this repository"""
256 return False
257 return False
257
258
258 def commit(self, text, user, date):
259 def commit(self, text, user, date):
259 """commit the current changes to the subrepo with the given
260 """commit the current changes to the subrepo with the given
260 log message. Use given user and date if possible. Return the
261 log message. Use given user and date if possible. Return the
261 new state of the subrepo.
262 new state of the subrepo.
262 """
263 """
263 raise NotImplementedError
264 raise NotImplementedError
264
265
265 def remove(self):
266 def remove(self):
266 """remove the subrepo
267 """remove the subrepo
267
268
268 (should verify the dirstate is not dirty first)
269 (should verify the dirstate is not dirty first)
269 """
270 """
270 raise NotImplementedError
271 raise NotImplementedError
271
272
272 def get(self, state, overwrite=False):
273 def get(self, state, overwrite=False):
273 """run whatever commands are needed to put the subrepo into
274 """run whatever commands are needed to put the subrepo into
274 this state
275 this state
275 """
276 """
276 raise NotImplementedError
277 raise NotImplementedError
277
278
278 def merge(self, state):
279 def merge(self, state):
279 """merge currently-saved state with the new state."""
280 """merge currently-saved state with the new state."""
280 raise NotImplementedError
281 raise NotImplementedError
281
282
282 def push(self, force):
283 def push(self, force):
283 """perform whatever action is analogous to 'hg push'
284 """perform whatever action is analogous to 'hg push'
284
285
285 This may be a no-op on some systems.
286 This may be a no-op on some systems.
286 """
287 """
287 raise NotImplementedError
288 raise NotImplementedError
288
289
289 def add(self, ui, match, dryrun, prefix):
290 def add(self, ui, match, dryrun, prefix):
290 return []
291 return []
291
292
292 def status(self, rev2, **opts):
293 def status(self, rev2, **opts):
293 return [], [], [], [], [], [], []
294 return [], [], [], [], [], [], []
294
295
295 def diff(self, diffopts, node2, match, prefix, **opts):
296 def diff(self, diffopts, node2, match, prefix, **opts):
296 pass
297 pass
297
298
298 def outgoing(self, ui, dest, opts):
299 def outgoing(self, ui, dest, opts):
299 return 1
300 return 1
300
301
301 def incoming(self, ui, source, opts):
302 def incoming(self, ui, source, opts):
302 return 1
303 return 1
303
304
304 def files(self):
305 def files(self):
305 """return filename iterator"""
306 """return filename iterator"""
306 raise NotImplementedError
307 raise NotImplementedError
307
308
308 def filedata(self, name):
309 def filedata(self, name):
309 """return file data"""
310 """return file data"""
310 raise NotImplementedError
311 raise NotImplementedError
311
312
312 def fileflags(self, name):
313 def fileflags(self, name):
313 """return file flags"""
314 """return file flags"""
314 return ''
315 return ''
315
316
316 def archive(self, ui, archiver, prefix):
317 def archive(self, ui, archiver, prefix):
317 files = self.files()
318 files = self.files()
318 total = len(files)
319 total = len(files)
319 relpath = subrelpath(self)
320 relpath = subrelpath(self)
320 ui.progress(_('archiving (%s)') % relpath, 0,
321 ui.progress(_('archiving (%s)') % relpath, 0,
321 unit=_('files'), total=total)
322 unit=_('files'), total=total)
322 for i, name in enumerate(files):
323 for i, name in enumerate(files):
323 flags = self.fileflags(name)
324 flags = self.fileflags(name)
324 mode = 'x' in flags and 0755 or 0644
325 mode = 'x' in flags and 0755 or 0644
325 symlink = 'l' in flags
326 symlink = 'l' in flags
326 archiver.addfile(os.path.join(prefix, self._path, name),
327 archiver.addfile(os.path.join(prefix, self._path, name),
327 mode, symlink, self.filedata(name))
328 mode, symlink, self.filedata(name))
328 ui.progress(_('archiving (%s)') % relpath, i + 1,
329 ui.progress(_('archiving (%s)') % relpath, i + 1,
329 unit=_('files'), total=total)
330 unit=_('files'), total=total)
330 ui.progress(_('archiving (%s)') % relpath, None)
331 ui.progress(_('archiving (%s)') % relpath, None)
331
332
332
333
333 class hgsubrepo(abstractsubrepo):
334 class hgsubrepo(abstractsubrepo):
334 def __init__(self, ctx, path, state):
335 def __init__(self, ctx, path, state):
335 self._path = path
336 self._path = path
336 self._state = state
337 self._state = state
337 r = ctx._repo
338 r = ctx._repo
338 root = r.wjoin(path)
339 root = r.wjoin(path)
339 create = False
340 create = False
340 if not os.path.exists(os.path.join(root, '.hg')):
341 if not os.path.exists(os.path.join(root, '.hg')):
341 create = True
342 create = True
342 util.makedirs(root)
343 util.makedirs(root)
343 self._repo = hg.repository(r.ui, root, create=create)
344 self._repo = hg.repository(r.ui, root, create=create)
344 self._repo._subparent = r
345 self._repo._subparent = r
345 self._repo._subsource = state[0]
346 self._repo._subsource = state[0]
346
347
347 if create:
348 if create:
348 fp = self._repo.opener("hgrc", "w", text=True)
349 fp = self._repo.opener("hgrc", "w", text=True)
349 fp.write('[paths]\n')
350 fp.write('[paths]\n')
350
351
351 def addpathconfig(key, value):
352 def addpathconfig(key, value):
352 if value:
353 if value:
353 fp.write('%s = %s\n' % (key, value))
354 fp.write('%s = %s\n' % (key, value))
354 self._repo.ui.setconfig('paths', key, value)
355 self._repo.ui.setconfig('paths', key, value)
355
356
356 defpath = _abssource(self._repo, abort=False)
357 defpath = _abssource(self._repo, abort=False)
357 defpushpath = _abssource(self._repo, True, abort=False)
358 defpushpath = _abssource(self._repo, True, abort=False)
358 addpathconfig('default', defpath)
359 addpathconfig('default', defpath)
359 if defpath != defpushpath:
360 if defpath != defpushpath:
360 addpathconfig('default-push', defpushpath)
361 addpathconfig('default-push', defpushpath)
361 fp.close()
362 fp.close()
362
363
363 def add(self, ui, match, dryrun, prefix):
364 def add(self, ui, match, dryrun, prefix):
364 return cmdutil.add(ui, self._repo, match, dryrun, True,
365 return cmdutil.add(ui, self._repo, match, dryrun, True,
365 os.path.join(prefix, self._path))
366 os.path.join(prefix, self._path))
366
367
367 def status(self, rev2, **opts):
368 def status(self, rev2, **opts):
368 try:
369 try:
369 rev1 = self._state[1]
370 rev1 = self._state[1]
370 ctx1 = self._repo[rev1]
371 ctx1 = self._repo[rev1]
371 ctx2 = self._repo[rev2]
372 ctx2 = self._repo[rev2]
372 return self._repo.status(ctx1, ctx2, **opts)
373 return self._repo.status(ctx1, ctx2, **opts)
373 except error.RepoLookupError, inst:
374 except error.RepoLookupError, inst:
374 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
375 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
375 % (inst, subrelpath(self)))
376 % (inst, subrelpath(self)))
376 return [], [], [], [], [], [], []
377 return [], [], [], [], [], [], []
377
378
378 def diff(self, diffopts, node2, match, prefix, **opts):
379 def diff(self, diffopts, node2, match, prefix, **opts):
379 try:
380 try:
380 node1 = node.bin(self._state[1])
381 node1 = node.bin(self._state[1])
381 # We currently expect node2 to come from substate and be
382 # We currently expect node2 to come from substate and be
382 # in hex format
383 # in hex format
383 if node2 is not None:
384 if node2 is not None:
384 node2 = node.bin(node2)
385 node2 = node.bin(node2)
385 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
386 cmdutil.diffordiffstat(self._repo.ui, self._repo, diffopts,
386 node1, node2, match,
387 node1, node2, match,
387 prefix=os.path.join(prefix, self._path),
388 prefix=os.path.join(prefix, self._path),
388 listsubrepos=True, **opts)
389 listsubrepos=True, **opts)
389 except error.RepoLookupError, inst:
390 except error.RepoLookupError, inst:
390 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
391 self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
391 % (inst, subrelpath(self)))
392 % (inst, subrelpath(self)))
392
393
393 def archive(self, ui, archiver, prefix):
394 def archive(self, ui, archiver, prefix):
394 abstractsubrepo.archive(self, ui, archiver, prefix)
395 abstractsubrepo.archive(self, ui, archiver, prefix)
395
396
396 rev = self._state[1]
397 rev = self._state[1]
397 ctx = self._repo[rev]
398 ctx = self._repo[rev]
398 for subpath in ctx.substate:
399 for subpath in ctx.substate:
399 s = subrepo(ctx, subpath)
400 s = subrepo(ctx, subpath)
400 s.archive(ui, archiver, os.path.join(prefix, self._path))
401 s.archive(ui, archiver, os.path.join(prefix, self._path))
401
402
402 def dirty(self, ignoreupdate=False):
403 def dirty(self, ignoreupdate=False):
403 r = self._state[1]
404 r = self._state[1]
404 if r == '' and not ignoreupdate: # no state recorded
405 if r == '' and not ignoreupdate: # no state recorded
405 return True
406 return True
406 w = self._repo[None]
407 w = self._repo[None]
407 if w.p1() != self._repo[r] and not ignoreupdate:
408 if w.p1() != self._repo[r] and not ignoreupdate:
408 # different version checked out
409 # different version checked out
409 return True
410 return True
410 return w.dirty() # working directory changed
411 return w.dirty() # working directory changed
411
412
412 def checknested(self, path):
413 def checknested(self, path):
413 return self._repo._checknested(self._repo.wjoin(path))
414 return self._repo._checknested(self._repo.wjoin(path))
414
415
415 def commit(self, text, user, date):
416 def commit(self, text, user, date):
416 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
417 self._repo.ui.debug("committing subrepo %s\n" % subrelpath(self))
417 n = self._repo.commit(text, user, date)
418 n = self._repo.commit(text, user, date)
418 if not n:
419 if not n:
419 return self._repo['.'].hex() # different version checked out
420 return self._repo['.'].hex() # different version checked out
420 return node.hex(n)
421 return node.hex(n)
421
422
422 def remove(self):
423 def remove(self):
423 # we can't fully delete the repository as it may contain
424 # we can't fully delete the repository as it may contain
424 # local-only history
425 # local-only history
425 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
426 self._repo.ui.note(_('removing subrepo %s\n') % subrelpath(self))
426 hg.clean(self._repo, node.nullid, False)
427 hg.clean(self._repo, node.nullid, False)
427
428
428 def _get(self, state):
429 def _get(self, state):
429 source, revision, kind = state
430 source, revision, kind = state
430 if revision not in self._repo:
431 if revision not in self._repo:
431 self._repo._subsource = source
432 self._repo._subsource = source
432 srcurl = _abssource(self._repo)
433 srcurl = _abssource(self._repo)
433 self._repo.ui.status(_('pulling subrepo %s from %s\n')
434 self._repo.ui.status(_('pulling subrepo %s from %s\n')
434 % (subrelpath(self), srcurl))
435 % (subrelpath(self), srcurl))
435 other = hg.repository(self._repo.ui, srcurl)
436 other = hg.repository(self._repo.ui, srcurl)
436 self._repo.pull(other)
437 self._repo.pull(other)
437 bookmarks.updatefromremote(self._repo.ui, self._repo, other)
438 bookmarks.updatefromremote(self._repo.ui, self._repo, other)
438
439
439 def get(self, state, overwrite=False):
440 def get(self, state, overwrite=False):
440 self._get(state)
441 self._get(state)
441 source, revision, kind = state
442 source, revision, kind = state
442 self._repo.ui.debug("getting subrepo %s\n" % self._path)
443 self._repo.ui.debug("getting subrepo %s\n" % self._path)
443 hg.clean(self._repo, revision, False)
444 hg.clean(self._repo, revision, False)
444
445
445 def merge(self, state):
446 def merge(self, state):
446 self._get(state)
447 self._get(state)
447 cur = self._repo['.']
448 cur = self._repo['.']
448 dst = self._repo[state[1]]
449 dst = self._repo[state[1]]
449 anc = dst.ancestor(cur)
450 anc = dst.ancestor(cur)
450
451
451 def mergefunc():
452 def mergefunc():
452 if anc == cur:
453 if anc == cur:
453 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
454 self._repo.ui.debug("updating subrepo %s\n" % subrelpath(self))
454 hg.update(self._repo, state[1])
455 hg.update(self._repo, state[1])
455 elif anc == dst:
456 elif anc == dst:
456 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
457 self._repo.ui.debug("skipping subrepo %s\n" % subrelpath(self))
457 else:
458 else:
458 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
459 self._repo.ui.debug("merging subrepo %s\n" % subrelpath(self))
459 hg.merge(self._repo, state[1], remind=False)
460 hg.merge(self._repo, state[1], remind=False)
460
461
461 wctx = self._repo[None]
462 wctx = self._repo[None]
462 if self.dirty():
463 if self.dirty():
463 if anc != dst:
464 if anc != dst:
464 if _updateprompt(self._repo.ui, self, wctx.dirty(), cur, dst):
465 if _updateprompt(self._repo.ui, self, wctx.dirty(), cur, dst):
465 mergefunc()
466 mergefunc()
466 else:
467 else:
467 mergefunc()
468 mergefunc()
468 else:
469 else:
469 mergefunc()
470 mergefunc()
470
471
471 def push(self, force):
472 def push(self, force):
472 # push subrepos depth-first for coherent ordering
473 # push subrepos depth-first for coherent ordering
473 c = self._repo['']
474 c = self._repo['']
474 subs = c.substate # only repos that are committed
475 subs = c.substate # only repos that are committed
475 for s in sorted(subs):
476 for s in sorted(subs):
476 if not c.sub(s).push(force):
477 if not c.sub(s).push(force):
477 return False
478 return False
478
479
479 dsturl = _abssource(self._repo, True)
480 dsturl = _abssource(self._repo, True)
480 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
481 self._repo.ui.status(_('pushing subrepo %s to %s\n') %
481 (subrelpath(self), dsturl))
482 (subrelpath(self), dsturl))
482 other = hg.repository(self._repo.ui, dsturl)
483 other = hg.repository(self._repo.ui, dsturl)
483 return self._repo.push(other, force)
484 return self._repo.push(other, force)
484
485
485 def outgoing(self, ui, dest, opts):
486 def outgoing(self, ui, dest, opts):
486 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
487 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
487
488
488 def incoming(self, ui, source, opts):
489 def incoming(self, ui, source, opts):
489 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
490 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
490
491
491 def files(self):
492 def files(self):
492 rev = self._state[1]
493 rev = self._state[1]
493 ctx = self._repo[rev]
494 ctx = self._repo[rev]
494 return ctx.manifest()
495 return ctx.manifest()
495
496
496 def filedata(self, name):
497 def filedata(self, name):
497 rev = self._state[1]
498 rev = self._state[1]
498 return self._repo[rev][name].data()
499 return self._repo[rev][name].data()
499
500
500 def fileflags(self, name):
501 def fileflags(self, name):
501 rev = self._state[1]
502 rev = self._state[1]
502 ctx = self._repo[rev]
503 ctx = self._repo[rev]
503 return ctx.flags(name)
504 return ctx.flags(name)
504
505
505
506
506 class svnsubrepo(abstractsubrepo):
507 class svnsubrepo(abstractsubrepo):
507 def __init__(self, ctx, path, state):
508 def __init__(self, ctx, path, state):
508 self._path = path
509 self._path = path
509 self._state = state
510 self._state = state
510 self._ctx = ctx
511 self._ctx = ctx
511 self._ui = ctx._repo.ui
512 self._ui = ctx._repo.ui
512
513
513 def _svncommand(self, commands, filename=''):
514 def _svncommand(self, commands, filename=''):
514 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
515 cmd = ['svn']
515 cmd = ['svn']
516 # Starting in svn 1.5 --non-interactive is a global flag
516 # Starting in svn 1.5 --non-interactive is a global flag
517 # instead of being per-command, but we need to support 1.4 so
517 # instead of being per-command, but we need to support 1.4 so
518 # we have to be intelligent about what commands take
518 # we have to be intelligent about what commands take
519 # --non-interactive.
519 # --non-interactive.
520 if (not self._ui.interactive() and
520 if (not self._ui.interactive() and
521 commands[0] in ('update', 'checkout', 'commit')):
521 commands[0] in ('update', 'checkout', 'commit')):
522 cmd.append('--non-interactive')
522 cmd.append('--non-interactive')
523 cmd.extend(commands)
523 cmd.extend(commands)
524 cmd.append(path)
524 if filename is not None:
525 path = os.path.join(self._ctx._repo.origroot, self._path, filename)
526 cmd.append(path)
525 env = dict(os.environ)
527 env = dict(os.environ)
526 # Avoid localized output, preserve current locale for everything else.
528 # Avoid localized output, preserve current locale for everything else.
527 env['LC_MESSAGES'] = 'C'
529 env['LC_MESSAGES'] = 'C'
528 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
530 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
529 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
531 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
530 universal_newlines=True, env=env)
532 universal_newlines=True, env=env)
531 stdout, stderr = p.communicate()
533 stdout, stderr = p.communicate()
532 stderr = stderr.strip()
534 stderr = stderr.strip()
533 if stderr:
535 if stderr:
534 raise util.Abort(stderr)
536 raise util.Abort(stderr)
535 return stdout
537 return stdout
536
538
539 @propertycache
540 def _svnversion(self):
541 output = self._svncommand(['--version'], filename=None)
542 m = re.search(r'^svn,\s+version\s+(\d+)\.(\d+)', output)
543 if not m:
544 raise util.Abort(_('cannot retrieve svn tool version'))
545 return (int(m.group(1)), int(m.group(2)))
546
537 def _wcrevs(self):
547 def _wcrevs(self):
538 # Get the working directory revision as well as the last
548 # Get the working directory revision as well as the last
539 # commit revision so we can compare the subrepo state with
549 # commit revision so we can compare the subrepo state with
540 # both. We used to store the working directory one.
550 # both. We used to store the working directory one.
541 output = self._svncommand(['info', '--xml'])
551 output = self._svncommand(['info', '--xml'])
542 doc = xml.dom.minidom.parseString(output)
552 doc = xml.dom.minidom.parseString(output)
543 entries = doc.getElementsByTagName('entry')
553 entries = doc.getElementsByTagName('entry')
544 lastrev, rev = '0', '0'
554 lastrev, rev = '0', '0'
545 if entries:
555 if entries:
546 rev = str(entries[0].getAttribute('revision')) or '0'
556 rev = str(entries[0].getAttribute('revision')) or '0'
547 commits = entries[0].getElementsByTagName('commit')
557 commits = entries[0].getElementsByTagName('commit')
548 if commits:
558 if commits:
549 lastrev = str(commits[0].getAttribute('revision')) or '0'
559 lastrev = str(commits[0].getAttribute('revision')) or '0'
550 return (lastrev, rev)
560 return (lastrev, rev)
551
561
552 def _wcrev(self):
562 def _wcrev(self):
553 return self._wcrevs()[0]
563 return self._wcrevs()[0]
554
564
555 def _wcchanged(self):
565 def _wcchanged(self):
556 """Return (changes, extchanges) where changes is True
566 """Return (changes, extchanges) where changes is True
557 if the working directory was changed, and extchanges is
567 if the working directory was changed, and extchanges is
558 True if any of these changes concern an external entry.
568 True if any of these changes concern an external entry.
559 """
569 """
560 output = self._svncommand(['status', '--xml'])
570 output = self._svncommand(['status', '--xml'])
561 externals, changes = [], []
571 externals, changes = [], []
562 doc = xml.dom.minidom.parseString(output)
572 doc = xml.dom.minidom.parseString(output)
563 for e in doc.getElementsByTagName('entry'):
573 for e in doc.getElementsByTagName('entry'):
564 s = e.getElementsByTagName('wc-status')
574 s = e.getElementsByTagName('wc-status')
565 if not s:
575 if not s:
566 continue
576 continue
567 item = s[0].getAttribute('item')
577 item = s[0].getAttribute('item')
568 props = s[0].getAttribute('props')
578 props = s[0].getAttribute('props')
569 path = e.getAttribute('path')
579 path = e.getAttribute('path')
570 if item == 'external':
580 if item == 'external':
571 externals.append(path)
581 externals.append(path)
572 if (item not in ('', 'normal', 'unversioned', 'external')
582 if (item not in ('', 'normal', 'unversioned', 'external')
573 or props not in ('', 'none')):
583 or props not in ('', 'none')):
574 changes.append(path)
584 changes.append(path)
575 for path in changes:
585 for path in changes:
576 for ext in externals:
586 for ext in externals:
577 if path == ext or path.startswith(ext + os.sep):
587 if path == ext or path.startswith(ext + os.sep):
578 return True, True
588 return True, True
579 return bool(changes), False
589 return bool(changes), False
580
590
581 def dirty(self, ignoreupdate=False):
591 def dirty(self, ignoreupdate=False):
582 if not self._wcchanged()[0]:
592 if not self._wcchanged()[0]:
583 if self._state[1] in self._wcrevs() or ignoreupdate:
593 if self._state[1] in self._wcrevs() or ignoreupdate:
584 return False
594 return False
585 return True
595 return True
586
596
587 def commit(self, text, user, date):
597 def commit(self, text, user, date):
588 # user and date are out of our hands since svn is centralized
598 # user and date are out of our hands since svn is centralized
589 changed, extchanged = self._wcchanged()
599 changed, extchanged = self._wcchanged()
590 if not changed:
600 if not changed:
591 return self._wcrev()
601 return self._wcrev()
592 if extchanged:
602 if extchanged:
593 # Do not try to commit externals
603 # Do not try to commit externals
594 raise util.Abort(_('cannot commit svn externals'))
604 raise util.Abort(_('cannot commit svn externals'))
595 commitinfo = self._svncommand(['commit', '-m', text])
605 commitinfo = self._svncommand(['commit', '-m', text])
596 self._ui.status(commitinfo)
606 self._ui.status(commitinfo)
597 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
607 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
598 if not newrev:
608 if not newrev:
599 raise util.Abort(commitinfo.splitlines()[-1])
609 raise util.Abort(commitinfo.splitlines()[-1])
600 newrev = newrev.groups()[0]
610 newrev = newrev.groups()[0]
601 self._ui.status(self._svncommand(['update', '-r', newrev]))
611 self._ui.status(self._svncommand(['update', '-r', newrev]))
602 return newrev
612 return newrev
603
613
604 def remove(self):
614 def remove(self):
605 if self.dirty():
615 if self.dirty():
606 self._ui.warn(_('not removing repo %s because '
616 self._ui.warn(_('not removing repo %s because '
607 'it has changes.\n' % self._path))
617 'it has changes.\n' % self._path))
608 return
618 return
609 self._ui.note(_('removing subrepo %s\n') % self._path)
619 self._ui.note(_('removing subrepo %s\n') % self._path)
610
620
611 def onerror(function, path, excinfo):
621 def onerror(function, path, excinfo):
612 if function is not os.remove:
622 if function is not os.remove:
613 raise
623 raise
614 # read-only files cannot be unlinked under Windows
624 # read-only files cannot be unlinked under Windows
615 s = os.stat(path)
625 s = os.stat(path)
616 if (s.st_mode & stat.S_IWRITE) != 0:
626 if (s.st_mode & stat.S_IWRITE) != 0:
617 raise
627 raise
618 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
628 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
619 os.remove(path)
629 os.remove(path)
620
630
621 path = self._ctx._repo.wjoin(self._path)
631 path = self._ctx._repo.wjoin(self._path)
622 shutil.rmtree(path, onerror=onerror)
632 shutil.rmtree(path, onerror=onerror)
623 try:
633 try:
624 os.removedirs(os.path.dirname(path))
634 os.removedirs(os.path.dirname(path))
625 except OSError:
635 except OSError:
626 pass
636 pass
627
637
628 def get(self, state, overwrite=False):
638 def get(self, state, overwrite=False):
629 if overwrite:
639 if overwrite:
630 self._svncommand(['revert', '--recursive'])
640 self._svncommand(['revert', '--recursive'])
631 status = self._svncommand(['checkout', state[0], '--revision', state[1]])
641 args = ['checkout']
642 if self._svnversion >= (1, 5):
643 args.append('--force')
644 args.extend([state[0], '--revision', state[1]])
645 status = self._svncommand(args)
632 if not re.search('Checked out revision [0-9]+.', status):
646 if not re.search('Checked out revision [0-9]+.', status):
633 raise util.Abort(status.splitlines()[-1])
647 raise util.Abort(status.splitlines()[-1])
634 self._ui.status(status)
648 self._ui.status(status)
635
649
636 def merge(self, state):
650 def merge(self, state):
637 old = self._state[1]
651 old = self._state[1]
638 new = state[1]
652 new = state[1]
639 if new != self._wcrev():
653 if new != self._wcrev():
640 dirty = old == self._wcrev() or self._wcchanged()[0]
654 dirty = old == self._wcrev() or self._wcchanged()[0]
641 if _updateprompt(self._ui, self, dirty, self._wcrev(), new):
655 if _updateprompt(self._ui, self, dirty, self._wcrev(), new):
642 self.get(state, False)
656 self.get(state, False)
643
657
644 def push(self, force):
658 def push(self, force):
645 # push is a no-op for SVN
659 # push is a no-op for SVN
646 return True
660 return True
647
661
648 def files(self):
662 def files(self):
649 output = self._svncommand(['list'])
663 output = self._svncommand(['list'])
650 # This works because svn forbids \n in filenames.
664 # This works because svn forbids \n in filenames.
651 return output.splitlines()
665 return output.splitlines()
652
666
653 def filedata(self, name):
667 def filedata(self, name):
654 return self._svncommand(['cat'], name)
668 return self._svncommand(['cat'], name)
655
669
656
670
657 class gitsubrepo(abstractsubrepo):
671 class gitsubrepo(abstractsubrepo):
658 def __init__(self, ctx, path, state):
672 def __init__(self, ctx, path, state):
659 # TODO add git version check.
673 # TODO add git version check.
660 self._state = state
674 self._state = state
661 self._ctx = ctx
675 self._ctx = ctx
662 self._path = path
676 self._path = path
663 self._relpath = os.path.join(reporelpath(ctx._repo), path)
677 self._relpath = os.path.join(reporelpath(ctx._repo), path)
664 self._abspath = ctx._repo.wjoin(path)
678 self._abspath = ctx._repo.wjoin(path)
665 self._subparent = ctx._repo
679 self._subparent = ctx._repo
666 self._ui = ctx._repo.ui
680 self._ui = ctx._repo.ui
667
681
668 def _gitcommand(self, commands, env=None, stream=False):
682 def _gitcommand(self, commands, env=None, stream=False):
669 return self._gitdir(commands, env=env, stream=stream)[0]
683 return self._gitdir(commands, env=env, stream=stream)[0]
670
684
671 def _gitdir(self, commands, env=None, stream=False):
685 def _gitdir(self, commands, env=None, stream=False):
672 return self._gitnodir(commands, env=env, stream=stream,
686 return self._gitnodir(commands, env=env, stream=stream,
673 cwd=self._abspath)
687 cwd=self._abspath)
674
688
675 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
689 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
676 """Calls the git command
690 """Calls the git command
677
691
678 The methods tries to call the git command. versions previor to 1.6.0
692 The methods tries to call the git command. versions previor to 1.6.0
679 are not supported and very probably fail.
693 are not supported and very probably fail.
680 """
694 """
681 self._ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
695 self._ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
682 # unless ui.quiet is set, print git's stderr,
696 # unless ui.quiet is set, print git's stderr,
683 # which is mostly progress and useful info
697 # which is mostly progress and useful info
684 errpipe = None
698 errpipe = None
685 if self._ui.quiet:
699 if self._ui.quiet:
686 errpipe = open(os.devnull, 'w')
700 errpipe = open(os.devnull, 'w')
687 p = subprocess.Popen(['git'] + commands, bufsize=-1, cwd=cwd, env=env,
701 p = subprocess.Popen(['git'] + commands, bufsize=-1, cwd=cwd, env=env,
688 close_fds=util.closefds,
702 close_fds=util.closefds,
689 stdout=subprocess.PIPE, stderr=errpipe)
703 stdout=subprocess.PIPE, stderr=errpipe)
690 if stream:
704 if stream:
691 return p.stdout, None
705 return p.stdout, None
692
706
693 retdata = p.stdout.read().strip()
707 retdata = p.stdout.read().strip()
694 # wait for the child to exit to avoid race condition.
708 # wait for the child to exit to avoid race condition.
695 p.wait()
709 p.wait()
696
710
697 if p.returncode != 0 and p.returncode != 1:
711 if p.returncode != 0 and p.returncode != 1:
698 # there are certain error codes that are ok
712 # there are certain error codes that are ok
699 command = commands[0]
713 command = commands[0]
700 if command in ('cat-file', 'symbolic-ref'):
714 if command in ('cat-file', 'symbolic-ref'):
701 return retdata, p.returncode
715 return retdata, p.returncode
702 # for all others, abort
716 # for all others, abort
703 raise util.Abort('git %s error %d in %s' %
717 raise util.Abort('git %s error %d in %s' %
704 (command, p.returncode, self._relpath))
718 (command, p.returncode, self._relpath))
705
719
706 return retdata, p.returncode
720 return retdata, p.returncode
707
721
708 def _gitmissing(self):
722 def _gitmissing(self):
709 return not os.path.exists(os.path.join(self._abspath, '.git'))
723 return not os.path.exists(os.path.join(self._abspath, '.git'))
710
724
711 def _gitstate(self):
725 def _gitstate(self):
712 return self._gitcommand(['rev-parse', 'HEAD'])
726 return self._gitcommand(['rev-parse', 'HEAD'])
713
727
714 def _gitcurrentbranch(self):
728 def _gitcurrentbranch(self):
715 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
729 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
716 if err:
730 if err:
717 current = None
731 current = None
718 return current
732 return current
719
733
720 def _gitremote(self, remote):
734 def _gitremote(self, remote):
721 out = self._gitcommand(['remote', 'show', '-n', remote])
735 out = self._gitcommand(['remote', 'show', '-n', remote])
722 line = out.split('\n')[1]
736 line = out.split('\n')[1]
723 i = line.index('URL: ') + len('URL: ')
737 i = line.index('URL: ') + len('URL: ')
724 return line[i:]
738 return line[i:]
725
739
726 def _githavelocally(self, revision):
740 def _githavelocally(self, revision):
727 out, code = self._gitdir(['cat-file', '-e', revision])
741 out, code = self._gitdir(['cat-file', '-e', revision])
728 return code == 0
742 return code == 0
729
743
730 def _gitisancestor(self, r1, r2):
744 def _gitisancestor(self, r1, r2):
731 base = self._gitcommand(['merge-base', r1, r2])
745 base = self._gitcommand(['merge-base', r1, r2])
732 return base == r1
746 return base == r1
733
747
734 def _gitbranchmap(self):
748 def _gitbranchmap(self):
735 '''returns 2 things:
749 '''returns 2 things:
736 a map from git branch to revision
750 a map from git branch to revision
737 a map from revision to branches'''
751 a map from revision to branches'''
738 branch2rev = {}
752 branch2rev = {}
739 rev2branch = {}
753 rev2branch = {}
740
754
741 out = self._gitcommand(['for-each-ref', '--format',
755 out = self._gitcommand(['for-each-ref', '--format',
742 '%(objectname) %(refname)'])
756 '%(objectname) %(refname)'])
743 for line in out.split('\n'):
757 for line in out.split('\n'):
744 revision, ref = line.split(' ')
758 revision, ref = line.split(' ')
745 if (not ref.startswith('refs/heads/') and
759 if (not ref.startswith('refs/heads/') and
746 not ref.startswith('refs/remotes/')):
760 not ref.startswith('refs/remotes/')):
747 continue
761 continue
748 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
762 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
749 continue # ignore remote/HEAD redirects
763 continue # ignore remote/HEAD redirects
750 branch2rev[ref] = revision
764 branch2rev[ref] = revision
751 rev2branch.setdefault(revision, []).append(ref)
765 rev2branch.setdefault(revision, []).append(ref)
752 return branch2rev, rev2branch
766 return branch2rev, rev2branch
753
767
754 def _gittracking(self, branches):
768 def _gittracking(self, branches):
755 'return map of remote branch to local tracking branch'
769 'return map of remote branch to local tracking branch'
756 # assumes no more than one local tracking branch for each remote
770 # assumes no more than one local tracking branch for each remote
757 tracking = {}
771 tracking = {}
758 for b in branches:
772 for b in branches:
759 if b.startswith('refs/remotes/'):
773 if b.startswith('refs/remotes/'):
760 continue
774 continue
761 remote = self._gitcommand(['config', 'branch.%s.remote' % b])
775 remote = self._gitcommand(['config', 'branch.%s.remote' % b])
762 if remote:
776 if remote:
763 ref = self._gitcommand(['config', 'branch.%s.merge' % b])
777 ref = self._gitcommand(['config', 'branch.%s.merge' % b])
764 tracking['refs/remotes/%s/%s' %
778 tracking['refs/remotes/%s/%s' %
765 (remote, ref.split('/', 2)[2])] = b
779 (remote, ref.split('/', 2)[2])] = b
766 return tracking
780 return tracking
767
781
768 def _abssource(self, source):
782 def _abssource(self, source):
769 if '://' not in source:
783 if '://' not in source:
770 # recognize the scp syntax as an absolute source
784 # recognize the scp syntax as an absolute source
771 colon = source.find(':')
785 colon = source.find(':')
772 if colon != -1 and '/' not in source[:colon]:
786 if colon != -1 and '/' not in source[:colon]:
773 return source
787 return source
774 self._subsource = source
788 self._subsource = source
775 return _abssource(self)
789 return _abssource(self)
776
790
777 def _fetch(self, source, revision):
791 def _fetch(self, source, revision):
778 if self._gitmissing():
792 if self._gitmissing():
779 source = self._abssource(source)
793 source = self._abssource(source)
780 self._ui.status(_('cloning subrepo %s from %s\n') %
794 self._ui.status(_('cloning subrepo %s from %s\n') %
781 (self._relpath, source))
795 (self._relpath, source))
782 self._gitnodir(['clone', source, self._abspath])
796 self._gitnodir(['clone', source, self._abspath])
783 if self._githavelocally(revision):
797 if self._githavelocally(revision):
784 return
798 return
785 self._ui.status(_('pulling subrepo %s from %s\n') %
799 self._ui.status(_('pulling subrepo %s from %s\n') %
786 (self._relpath, self._gitremote('origin')))
800 (self._relpath, self._gitremote('origin')))
787 # try only origin: the originally cloned repo
801 # try only origin: the originally cloned repo
788 self._gitcommand(['fetch'])
802 self._gitcommand(['fetch'])
789 if not self._githavelocally(revision):
803 if not self._githavelocally(revision):
790 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
804 raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
791 (revision, self._relpath))
805 (revision, self._relpath))
792
806
793 def dirty(self, ignoreupdate=False):
807 def dirty(self, ignoreupdate=False):
794 if self._gitmissing():
808 if self._gitmissing():
795 return True
809 return True
796 if not ignoreupdate and self._state[1] != self._gitstate():
810 if not ignoreupdate and self._state[1] != self._gitstate():
797 # different version checked out
811 # different version checked out
798 return True
812 return True
799 # check for staged changes or modified files; ignore untracked files
813 # check for staged changes or modified files; ignore untracked files
800 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
814 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
801 return code == 1
815 return code == 1
802
816
803 def get(self, state, overwrite=False):
817 def get(self, state, overwrite=False):
804 source, revision, kind = state
818 source, revision, kind = state
805 self._fetch(source, revision)
819 self._fetch(source, revision)
806 # if the repo was set to be bare, unbare it
820 # if the repo was set to be bare, unbare it
807 if self._gitcommand(['config', '--bool', 'core.bare']) == 'true':
821 if self._gitcommand(['config', '--bool', 'core.bare']) == 'true':
808 self._gitcommand(['config', 'core.bare', 'false'])
822 self._gitcommand(['config', 'core.bare', 'false'])
809 if self._gitstate() == revision:
823 if self._gitstate() == revision:
810 self._gitcommand(['reset', '--hard', 'HEAD'])
824 self._gitcommand(['reset', '--hard', 'HEAD'])
811 return
825 return
812 elif self._gitstate() == revision:
826 elif self._gitstate() == revision:
813 if overwrite:
827 if overwrite:
814 # first reset the index to unmark new files for commit, because
828 # first reset the index to unmark new files for commit, because
815 # reset --hard will otherwise throw away files added for commit,
829 # reset --hard will otherwise throw away files added for commit,
816 # not just unmark them.
830 # not just unmark them.
817 self._gitcommand(['reset', 'HEAD'])
831 self._gitcommand(['reset', 'HEAD'])
818 self._gitcommand(['reset', '--hard', 'HEAD'])
832 self._gitcommand(['reset', '--hard', 'HEAD'])
819 return
833 return
820 branch2rev, rev2branch = self._gitbranchmap()
834 branch2rev, rev2branch = self._gitbranchmap()
821
835
822 def checkout(args):
836 def checkout(args):
823 cmd = ['checkout']
837 cmd = ['checkout']
824 if overwrite:
838 if overwrite:
825 # first reset the index to unmark new files for commit, because
839 # first reset the index to unmark new files for commit, because
826 # the -f option will otherwise throw away files added for
840 # the -f option will otherwise throw away files added for
827 # commit, not just unmark them.
841 # commit, not just unmark them.
828 self._gitcommand(['reset', 'HEAD'])
842 self._gitcommand(['reset', 'HEAD'])
829 cmd.append('-f')
843 cmd.append('-f')
830 self._gitcommand(cmd + args)
844 self._gitcommand(cmd + args)
831
845
832 def rawcheckout():
846 def rawcheckout():
833 # no branch to checkout, check it out with no branch
847 # no branch to checkout, check it out with no branch
834 self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
848 self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
835 self._relpath)
849 self._relpath)
836 self._ui.warn(_('check out a git branch if you intend '
850 self._ui.warn(_('check out a git branch if you intend '
837 'to make changes\n'))
851 'to make changes\n'))
838 checkout(['-q', revision])
852 checkout(['-q', revision])
839
853
840 if revision not in rev2branch:
854 if revision not in rev2branch:
841 rawcheckout()
855 rawcheckout()
842 return
856 return
843 branches = rev2branch[revision]
857 branches = rev2branch[revision]
844 firstlocalbranch = None
858 firstlocalbranch = None
845 for b in branches:
859 for b in branches:
846 if b == 'refs/heads/master':
860 if b == 'refs/heads/master':
847 # master trumps all other branches
861 # master trumps all other branches
848 checkout(['refs/heads/master'])
862 checkout(['refs/heads/master'])
849 return
863 return
850 if not firstlocalbranch and not b.startswith('refs/remotes/'):
864 if not firstlocalbranch and not b.startswith('refs/remotes/'):
851 firstlocalbranch = b
865 firstlocalbranch = b
852 if firstlocalbranch:
866 if firstlocalbranch:
853 checkout([firstlocalbranch])
867 checkout([firstlocalbranch])
854 return
868 return
855
869
856 tracking = self._gittracking(branch2rev.keys())
870 tracking = self._gittracking(branch2rev.keys())
857 # choose a remote branch already tracked if possible
871 # choose a remote branch already tracked if possible
858 remote = branches[0]
872 remote = branches[0]
859 if remote not in tracking:
873 if remote not in tracking:
860 for b in branches:
874 for b in branches:
861 if b in tracking:
875 if b in tracking:
862 remote = b
876 remote = b
863 break
877 break
864
878
865 if remote not in tracking:
879 if remote not in tracking:
866 # create a new local tracking branch
880 # create a new local tracking branch
867 local = remote.split('/', 2)[2]
881 local = remote.split('/', 2)[2]
868 checkout(['-b', local, remote])
882 checkout(['-b', local, remote])
869 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
883 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
870 # When updating to a tracked remote branch,
884 # When updating to a tracked remote branch,
871 # if the local tracking branch is downstream of it,
885 # if the local tracking branch is downstream of it,
872 # a normal `git pull` would have performed a "fast-forward merge"
886 # a normal `git pull` would have performed a "fast-forward merge"
873 # which is equivalent to updating the local branch to the remote.
887 # which is equivalent to updating the local branch to the remote.
874 # Since we are only looking at branching at update, we need to
888 # Since we are only looking at branching at update, we need to
875 # detect this situation and perform this action lazily.
889 # detect this situation and perform this action lazily.
876 if tracking[remote] != self._gitcurrentbranch():
890 if tracking[remote] != self._gitcurrentbranch():
877 checkout([tracking[remote]])
891 checkout([tracking[remote]])
878 self._gitcommand(['merge', '--ff', remote])
892 self._gitcommand(['merge', '--ff', remote])
879 else:
893 else:
880 # a real merge would be required, just checkout the revision
894 # a real merge would be required, just checkout the revision
881 rawcheckout()
895 rawcheckout()
882
896
883 def commit(self, text, user, date):
897 def commit(self, text, user, date):
884 if self._gitmissing():
898 if self._gitmissing():
885 raise util.Abort(_("subrepo %s is missing") % self._relpath)
899 raise util.Abort(_("subrepo %s is missing") % self._relpath)
886 cmd = ['commit', '-a', '-m', text]
900 cmd = ['commit', '-a', '-m', text]
887 env = os.environ.copy()
901 env = os.environ.copy()
888 if user:
902 if user:
889 cmd += ['--author', user]
903 cmd += ['--author', user]
890 if date:
904 if date:
891 # git's date parser silently ignores when seconds < 1e9
905 # git's date parser silently ignores when seconds < 1e9
892 # convert to ISO8601
906 # convert to ISO8601
893 env['GIT_AUTHOR_DATE'] = util.datestr(date,
907 env['GIT_AUTHOR_DATE'] = util.datestr(date,
894 '%Y-%m-%dT%H:%M:%S %1%2')
908 '%Y-%m-%dT%H:%M:%S %1%2')
895 self._gitcommand(cmd, env=env)
909 self._gitcommand(cmd, env=env)
896 # make sure commit works otherwise HEAD might not exist under certain
910 # make sure commit works otherwise HEAD might not exist under certain
897 # circumstances
911 # circumstances
898 return self._gitstate()
912 return self._gitstate()
899
913
900 def merge(self, state):
914 def merge(self, state):
901 source, revision, kind = state
915 source, revision, kind = state
902 self._fetch(source, revision)
916 self._fetch(source, revision)
903 base = self._gitcommand(['merge-base', revision, self._state[1]])
917 base = self._gitcommand(['merge-base', revision, self._state[1]])
904 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
918 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
905
919
906 def mergefunc():
920 def mergefunc():
907 if base == revision:
921 if base == revision:
908 self.get(state) # fast forward merge
922 self.get(state) # fast forward merge
909 elif base != self._state[1]:
923 elif base != self._state[1]:
910 self._gitcommand(['merge', '--no-commit', revision])
924 self._gitcommand(['merge', '--no-commit', revision])
911
925
912 if self.dirty():
926 if self.dirty():
913 if self._gitstate() != revision:
927 if self._gitstate() != revision:
914 dirty = self._gitstate() == self._state[1] or code != 0
928 dirty = self._gitstate() == self._state[1] or code != 0
915 if _updateprompt(self._ui, self, dirty,
929 if _updateprompt(self._ui, self, dirty,
916 self._state[1][:7], revision[:7]):
930 self._state[1][:7], revision[:7]):
917 mergefunc()
931 mergefunc()
918 else:
932 else:
919 mergefunc()
933 mergefunc()
920
934
921 def push(self, force):
935 def push(self, force):
922 if self._gitmissing():
936 if self._gitmissing():
923 raise util.Abort(_("subrepo %s is missing") % self._relpath)
937 raise util.Abort(_("subrepo %s is missing") % self._relpath)
924 # if a branch in origin contains the revision, nothing to do
938 # if a branch in origin contains the revision, nothing to do
925 branch2rev, rev2branch = self._gitbranchmap()
939 branch2rev, rev2branch = self._gitbranchmap()
926 if self._state[1] in rev2branch:
940 if self._state[1] in rev2branch:
927 for b in rev2branch[self._state[1]]:
941 for b in rev2branch[self._state[1]]:
928 if b.startswith('refs/remotes/origin/'):
942 if b.startswith('refs/remotes/origin/'):
929 return True
943 return True
930 for b, revision in branch2rev.iteritems():
944 for b, revision in branch2rev.iteritems():
931 if b.startswith('refs/remotes/origin/'):
945 if b.startswith('refs/remotes/origin/'):
932 if self._gitisancestor(self._state[1], revision):
946 if self._gitisancestor(self._state[1], revision):
933 return True
947 return True
934 # otherwise, try to push the currently checked out branch
948 # otherwise, try to push the currently checked out branch
935 cmd = ['push']
949 cmd = ['push']
936 if force:
950 if force:
937 cmd.append('--force')
951 cmd.append('--force')
938
952
939 current = self._gitcurrentbranch()
953 current = self._gitcurrentbranch()
940 if current:
954 if current:
941 # determine if the current branch is even useful
955 # determine if the current branch is even useful
942 if not self._gitisancestor(self._state[1], current):
956 if not self._gitisancestor(self._state[1], current):
943 self._ui.warn(_('unrelated git branch checked out '
957 self._ui.warn(_('unrelated git branch checked out '
944 'in subrepo %s\n') % self._relpath)
958 'in subrepo %s\n') % self._relpath)
945 return False
959 return False
946 self._ui.status(_('pushing branch %s of subrepo %s\n') %
960 self._ui.status(_('pushing branch %s of subrepo %s\n') %
947 (current.split('/', 2)[2], self._relpath))
961 (current.split('/', 2)[2], self._relpath))
948 self._gitcommand(cmd + ['origin', current])
962 self._gitcommand(cmd + ['origin', current])
949 return True
963 return True
950 else:
964 else:
951 self._ui.warn(_('no branch checked out in subrepo %s\n'
965 self._ui.warn(_('no branch checked out in subrepo %s\n'
952 'cannot push revision %s') %
966 'cannot push revision %s') %
953 (self._relpath, self._state[1]))
967 (self._relpath, self._state[1]))
954 return False
968 return False
955
969
956 def remove(self):
970 def remove(self):
957 if self._gitmissing():
971 if self._gitmissing():
958 return
972 return
959 if self.dirty():
973 if self.dirty():
960 self._ui.warn(_('not removing repo %s because '
974 self._ui.warn(_('not removing repo %s because '
961 'it has changes.\n') % self._relpath)
975 'it has changes.\n') % self._relpath)
962 return
976 return
963 # we can't fully delete the repository as it may contain
977 # we can't fully delete the repository as it may contain
964 # local-only history
978 # local-only history
965 self._ui.note(_('removing subrepo %s\n') % self._relpath)
979 self._ui.note(_('removing subrepo %s\n') % self._relpath)
966 self._gitcommand(['config', 'core.bare', 'true'])
980 self._gitcommand(['config', 'core.bare', 'true'])
967 for f in os.listdir(self._abspath):
981 for f in os.listdir(self._abspath):
968 if f == '.git':
982 if f == '.git':
969 continue
983 continue
970 path = os.path.join(self._abspath, f)
984 path = os.path.join(self._abspath, f)
971 if os.path.isdir(path) and not os.path.islink(path):
985 if os.path.isdir(path) and not os.path.islink(path):
972 shutil.rmtree(path)
986 shutil.rmtree(path)
973 else:
987 else:
974 os.remove(path)
988 os.remove(path)
975
989
976 def archive(self, ui, archiver, prefix):
990 def archive(self, ui, archiver, prefix):
977 source, revision = self._state
991 source, revision = self._state
978 self._fetch(source, revision)
992 self._fetch(source, revision)
979
993
980 # Parse git's native archive command.
994 # Parse git's native archive command.
981 # This should be much faster than manually traversing the trees
995 # This should be much faster than manually traversing the trees
982 # and objects with many subprocess calls.
996 # and objects with many subprocess calls.
983 tarstream = self._gitcommand(['archive', revision], stream=True)
997 tarstream = self._gitcommand(['archive', revision], stream=True)
984 tar = tarfile.open(fileobj=tarstream, mode='r|')
998 tar = tarfile.open(fileobj=tarstream, mode='r|')
985 relpath = subrelpath(self)
999 relpath = subrelpath(self)
986 ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1000 ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
987 for i, info in enumerate(tar):
1001 for i, info in enumerate(tar):
988 if info.isdir():
1002 if info.isdir():
989 continue
1003 continue
990 if info.issym():
1004 if info.issym():
991 data = info.linkname
1005 data = info.linkname
992 else:
1006 else:
993 data = tar.extractfile(info).read()
1007 data = tar.extractfile(info).read()
994 archiver.addfile(os.path.join(prefix, self._path, info.name),
1008 archiver.addfile(os.path.join(prefix, self._path, info.name),
995 info.mode, info.issym(), data)
1009 info.mode, info.issym(), data)
996 ui.progress(_('archiving (%s)') % relpath, i + 1,
1010 ui.progress(_('archiving (%s)') % relpath, i + 1,
997 unit=_('files'))
1011 unit=_('files'))
998 ui.progress(_('archiving (%s)') % relpath, None)
1012 ui.progress(_('archiving (%s)') % relpath, None)
999
1013
1000
1014
1001 def status(self, rev2, **opts):
1015 def status(self, rev2, **opts):
1002 if self._gitmissing():
1016 if self._gitmissing():
1003 # if the repo is missing, return no results
1017 # if the repo is missing, return no results
1004 return [], [], [], [], [], [], []
1018 return [], [], [], [], [], [], []
1005 rev1 = self._state[1]
1019 rev1 = self._state[1]
1006 modified, added, removed = [], [], []
1020 modified, added, removed = [], [], []
1007 if rev2:
1021 if rev2:
1008 command = ['diff-tree', rev1, rev2]
1022 command = ['diff-tree', rev1, rev2]
1009 else:
1023 else:
1010 command = ['diff-index', rev1]
1024 command = ['diff-index', rev1]
1011 out = self._gitcommand(command)
1025 out = self._gitcommand(command)
1012 for line in out.split('\n'):
1026 for line in out.split('\n'):
1013 tab = line.find('\t')
1027 tab = line.find('\t')
1014 if tab == -1:
1028 if tab == -1:
1015 continue
1029 continue
1016 status, f = line[tab - 1], line[tab + 1:]
1030 status, f = line[tab - 1], line[tab + 1:]
1017 if status == 'M':
1031 if status == 'M':
1018 modified.append(f)
1032 modified.append(f)
1019 elif status == 'A':
1033 elif status == 'A':
1020 added.append(f)
1034 added.append(f)
1021 elif status == 'D':
1035 elif status == 'D':
1022 removed.append(f)
1036 removed.append(f)
1023
1037
1024 deleted = unknown = ignored = clean = []
1038 deleted = unknown = ignored = clean = []
1025 return modified, added, removed, deleted, unknown, ignored, clean
1039 return modified, added, removed, deleted, unknown, ignored, clean
1026
1040
1027 types = {
1041 types = {
1028 'hg': hgsubrepo,
1042 'hg': hgsubrepo,
1029 'svn': svnsubrepo,
1043 'svn': svnsubrepo,
1030 'git': gitsubrepo,
1044 'git': gitsubrepo,
1031 }
1045 }
@@ -1,278 +1,288 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """Test the running system for features availability. Exit with zero
2 """Test the running system for features availability. Exit with zero
3 if all features are there, non-zero otherwise. If a feature name is
3 if all features are there, non-zero otherwise. If a feature name is
4 prefixed with "no-", the absence of feature is tested.
4 prefixed with "no-", the absence of feature is tested.
5 """
5 """
6 import optparse
6 import optparse
7 import os
7 import os
8 import re
8 import re
9 import sys
9 import sys
10 import tempfile
10 import tempfile
11
11
12 tempprefix = 'hg-hghave-'
12 tempprefix = 'hg-hghave-'
13
13
14 def matchoutput(cmd, regexp, ignorestatus=False):
14 def matchoutput(cmd, regexp, ignorestatus=False):
15 """Return True if cmd executes successfully and its output
15 """Return True if cmd executes successfully and its output
16 is matched by the supplied regular expression.
16 is matched by the supplied regular expression.
17 """
17 """
18 r = re.compile(regexp)
18 r = re.compile(regexp)
19 fh = os.popen(cmd)
19 fh = os.popen(cmd)
20 s = fh.read()
20 s = fh.read()
21 try:
21 try:
22 ret = fh.close()
22 ret = fh.close()
23 except IOError:
23 except IOError:
24 # Happen in Windows test environment
24 # Happen in Windows test environment
25 ret = 1
25 ret = 1
26 return (ignorestatus or ret is None) and r.search(s)
26 return (ignorestatus or ret is None) and r.search(s)
27
27
28 def has_baz():
28 def has_baz():
29 return matchoutput('baz --version 2>&1', r'baz Bazaar version')
29 return matchoutput('baz --version 2>&1', r'baz Bazaar version')
30
30
31 def has_bzr():
31 def has_bzr():
32 try:
32 try:
33 import bzrlib
33 import bzrlib
34 return bzrlib.__doc__ != None
34 return bzrlib.__doc__ != None
35 except ImportError:
35 except ImportError:
36 return False
36 return False
37
37
38 def has_bzr114():
38 def has_bzr114():
39 try:
39 try:
40 import bzrlib
40 import bzrlib
41 return (bzrlib.__doc__ != None
41 return (bzrlib.__doc__ != None
42 and bzrlib.version_info[:2] >= (1, 14))
42 and bzrlib.version_info[:2] >= (1, 14))
43 except ImportError:
43 except ImportError:
44 return False
44 return False
45
45
46 def has_cvs():
46 def has_cvs():
47 re = r'Concurrent Versions System.*?server'
47 re = r'Concurrent Versions System.*?server'
48 return matchoutput('cvs --version 2>&1', re)
48 return matchoutput('cvs --version 2>&1', re)
49
49
50 def has_darcs():
50 def has_darcs():
51 return matchoutput('darcs --version', r'2\.[2-9]', True)
51 return matchoutput('darcs --version', r'2\.[2-9]', True)
52
52
53 def has_mtn():
53 def has_mtn():
54 return matchoutput('mtn --version', r'monotone', True) and not matchoutput(
54 return matchoutput('mtn --version', r'monotone', True) and not matchoutput(
55 'mtn --version', r'monotone 0\.(\d|[12]\d|3[01])[^\d]', True)
55 'mtn --version', r'monotone 0\.(\d|[12]\d|3[01])[^\d]', True)
56
56
57 def has_eol_in_paths():
57 def has_eol_in_paths():
58 try:
58 try:
59 fd, path = tempfile.mkstemp(prefix=tempprefix, suffix='\n\r')
59 fd, path = tempfile.mkstemp(prefix=tempprefix, suffix='\n\r')
60 os.close(fd)
60 os.close(fd)
61 os.remove(path)
61 os.remove(path)
62 return True
62 return True
63 except:
63 except:
64 return False
64 return False
65
65
66 def has_executablebit():
66 def has_executablebit():
67 fd, path = tempfile.mkstemp(prefix=tempprefix)
67 fd, path = tempfile.mkstemp(prefix=tempprefix)
68 os.close(fd)
68 os.close(fd)
69 try:
69 try:
70 s = os.lstat(path).st_mode
70 s = os.lstat(path).st_mode
71 os.chmod(path, s | 0100)
71 os.chmod(path, s | 0100)
72 return (os.lstat(path).st_mode & 0100 != 0)
72 return (os.lstat(path).st_mode & 0100 != 0)
73 finally:
73 finally:
74 os.remove(path)
74 os.remove(path)
75
75
76 def has_icasefs():
76 def has_icasefs():
77 # Stolen from mercurial.util
77 # Stolen from mercurial.util
78 fd, path = tempfile.mkstemp(prefix=tempprefix, dir='.')
78 fd, path = tempfile.mkstemp(prefix=tempprefix, dir='.')
79 os.close(fd)
79 os.close(fd)
80 try:
80 try:
81 s1 = os.stat(path)
81 s1 = os.stat(path)
82 d, b = os.path.split(path)
82 d, b = os.path.split(path)
83 p2 = os.path.join(d, b.upper())
83 p2 = os.path.join(d, b.upper())
84 if path == p2:
84 if path == p2:
85 p2 = os.path.join(d, b.lower())
85 p2 = os.path.join(d, b.lower())
86 try:
86 try:
87 s2 = os.stat(p2)
87 s2 = os.stat(p2)
88 return s2 == s1
88 return s2 == s1
89 except:
89 except:
90 return False
90 return False
91 finally:
91 finally:
92 os.remove(path)
92 os.remove(path)
93
93
94 def has_inotify():
94 def has_inotify():
95 try:
95 try:
96 import hgext.inotify.linux.watcher
96 import hgext.inotify.linux.watcher
97 return True
97 return True
98 except ImportError:
98 except ImportError:
99 return False
99 return False
100
100
101 def has_fifo():
101 def has_fifo():
102 return hasattr(os, "mkfifo")
102 return hasattr(os, "mkfifo")
103
103
104 def has_lsprof():
104 def has_lsprof():
105 try:
105 try:
106 import _lsprof
106 import _lsprof
107 return True
107 return True
108 except ImportError:
108 except ImportError:
109 return False
109 return False
110
110
111 def has_gettext():
111 def has_gettext():
112 return matchoutput('msgfmt --version', 'GNU gettext-tools')
112 return matchoutput('msgfmt --version', 'GNU gettext-tools')
113
113
114 def has_git():
114 def has_git():
115 return matchoutput('git --version 2>&1', r'^git version')
115 return matchoutput('git --version 2>&1', r'^git version')
116
116
117 def has_docutils():
117 def has_docutils():
118 try:
118 try:
119 from docutils.core import publish_cmdline
119 from docutils.core import publish_cmdline
120 return True
120 return True
121 except ImportError:
121 except ImportError:
122 return False
122 return False
123
123
124 def getsvnversion():
125 m = matchoutput('svn --version 2>&1', r'^svn,\s+version\s+(\d+)\.(\d+)')
126 if not m:
127 return (0, 0)
128 return (int(m.group(1)), int(m.group(2)))
129
130 def has_svn15():
131 return getsvnversion() >= (1, 5)
132
124 def has_svn():
133 def has_svn():
125 return matchoutput('svn --version 2>&1', r'^svn, version') and \
134 return matchoutput('svn --version 2>&1', r'^svn, version') and \
126 matchoutput('svnadmin --version 2>&1', r'^svnadmin, version')
135 matchoutput('svnadmin --version 2>&1', r'^svnadmin, version')
127
136
128 def has_svn_bindings():
137 def has_svn_bindings():
129 try:
138 try:
130 import svn.core
139 import svn.core
131 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
140 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
132 if version < (1, 4):
141 if version < (1, 4):
133 return False
142 return False
134 return True
143 return True
135 except ImportError:
144 except ImportError:
136 return False
145 return False
137
146
138 def has_p4():
147 def has_p4():
139 return matchoutput('p4 -V', r'Rev\. P4/') and matchoutput('p4d -V', r'Rev\. P4D/')
148 return matchoutput('p4 -V', r'Rev\. P4/') and matchoutput('p4d -V', r'Rev\. P4D/')
140
149
141 def has_symlink():
150 def has_symlink():
142 return hasattr(os, "symlink")
151 return hasattr(os, "symlink")
143
152
144 def has_tla():
153 def has_tla():
145 return matchoutput('tla --version 2>&1', r'The GNU Arch Revision')
154 return matchoutput('tla --version 2>&1', r'The GNU Arch Revision')
146
155
147 def has_gpg():
156 def has_gpg():
148 return matchoutput('gpg --version 2>&1', r'GnuPG')
157 return matchoutput('gpg --version 2>&1', r'GnuPG')
149
158
150 def has_unix_permissions():
159 def has_unix_permissions():
151 d = tempfile.mkdtemp(prefix=tempprefix, dir=".")
160 d = tempfile.mkdtemp(prefix=tempprefix, dir=".")
152 try:
161 try:
153 fname = os.path.join(d, 'foo')
162 fname = os.path.join(d, 'foo')
154 for umask in (077, 007, 022):
163 for umask in (077, 007, 022):
155 os.umask(umask)
164 os.umask(umask)
156 f = open(fname, 'w')
165 f = open(fname, 'w')
157 f.close()
166 f.close()
158 mode = os.stat(fname).st_mode
167 mode = os.stat(fname).st_mode
159 os.unlink(fname)
168 os.unlink(fname)
160 if mode & 0777 != ~umask & 0666:
169 if mode & 0777 != ~umask & 0666:
161 return False
170 return False
162 return True
171 return True
163 finally:
172 finally:
164 os.rmdir(d)
173 os.rmdir(d)
165
174
166 def has_pygments():
175 def has_pygments():
167 try:
176 try:
168 import pygments
177 import pygments
169 return True
178 return True
170 except ImportError:
179 except ImportError:
171 return False
180 return False
172
181
173 def has_outer_repo():
182 def has_outer_repo():
174 return matchoutput('hg root 2>&1', r'')
183 return matchoutput('hg root 2>&1', r'')
175
184
176 def has_ssl():
185 def has_ssl():
177 try:
186 try:
178 import ssl
187 import ssl
179 import OpenSSL
188 import OpenSSL
180 OpenSSL.SSL.Context
189 OpenSSL.SSL.Context
181 return True
190 return True
182 except ImportError:
191 except ImportError:
183 return False
192 return False
184
193
185 checks = {
194 checks = {
186 "baz": (has_baz, "GNU Arch baz client"),
195 "baz": (has_baz, "GNU Arch baz client"),
187 "bzr": (has_bzr, "Canonical's Bazaar client"),
196 "bzr": (has_bzr, "Canonical's Bazaar client"),
188 "bzr114": (has_bzr114, "Canonical's Bazaar client >= 1.14"),
197 "bzr114": (has_bzr114, "Canonical's Bazaar client >= 1.14"),
189 "cvs": (has_cvs, "cvs client/server"),
198 "cvs": (has_cvs, "cvs client/server"),
190 "darcs": (has_darcs, "darcs client"),
199 "darcs": (has_darcs, "darcs client"),
191 "docutils": (has_docutils, "Docutils text processing library"),
200 "docutils": (has_docutils, "Docutils text processing library"),
192 "eol-in-paths": (has_eol_in_paths, "end-of-lines in paths"),
201 "eol-in-paths": (has_eol_in_paths, "end-of-lines in paths"),
193 "execbit": (has_executablebit, "executable bit"),
202 "execbit": (has_executablebit, "executable bit"),
194 "fifo": (has_fifo, "named pipes"),
203 "fifo": (has_fifo, "named pipes"),
195 "gettext": (has_gettext, "GNU Gettext (msgfmt)"),
204 "gettext": (has_gettext, "GNU Gettext (msgfmt)"),
196 "git": (has_git, "git command line client"),
205 "git": (has_git, "git command line client"),
197 "gpg": (has_gpg, "gpg client"),
206 "gpg": (has_gpg, "gpg client"),
198 "icasefs": (has_icasefs, "case insensitive file system"),
207 "icasefs": (has_icasefs, "case insensitive file system"),
199 "inotify": (has_inotify, "inotify extension support"),
208 "inotify": (has_inotify, "inotify extension support"),
200 "lsprof": (has_lsprof, "python lsprof module"),
209 "lsprof": (has_lsprof, "python lsprof module"),
201 "mtn": (has_mtn, "monotone client (> 0.31)"),
210 "mtn": (has_mtn, "monotone client (> 0.31)"),
202 "outer-repo": (has_outer_repo, "outer repo"),
211 "outer-repo": (has_outer_repo, "outer repo"),
203 "p4": (has_p4, "Perforce server and client"),
212 "p4": (has_p4, "Perforce server and client"),
204 "pygments": (has_pygments, "Pygments source highlighting library"),
213 "pygments": (has_pygments, "Pygments source highlighting library"),
205 "ssl": (has_ssl, "python >= 2.6 ssl module and python OpenSSL"),
214 "ssl": (has_ssl, "python >= 2.6 ssl module and python OpenSSL"),
206 "svn": (has_svn, "subversion client and admin tools"),
215 "svn": (has_svn, "subversion client and admin tools"),
216 "svn15": (has_svn15, "subversion client and admin tools >= 1.5"),
207 "svn-bindings": (has_svn_bindings, "subversion python bindings"),
217 "svn-bindings": (has_svn_bindings, "subversion python bindings"),
208 "symlink": (has_symlink, "symbolic links"),
218 "symlink": (has_symlink, "symbolic links"),
209 "tla": (has_tla, "GNU Arch tla client"),
219 "tla": (has_tla, "GNU Arch tla client"),
210 "unix-permissions": (has_unix_permissions, "unix-style permissions"),
220 "unix-permissions": (has_unix_permissions, "unix-style permissions"),
211 }
221 }
212
222
213 def list_features():
223 def list_features():
214 for name, feature in checks.iteritems():
224 for name, feature in checks.iteritems():
215 desc = feature[1]
225 desc = feature[1]
216 print name + ':', desc
226 print name + ':', desc
217
227
218 def test_features():
228 def test_features():
219 failed = 0
229 failed = 0
220 for name, feature in checks.iteritems():
230 for name, feature in checks.iteritems():
221 check, _ = feature
231 check, _ = feature
222 try:
232 try:
223 check()
233 check()
224 except Exception, e:
234 except Exception, e:
225 print "feature %s failed: %s" % (name, e)
235 print "feature %s failed: %s" % (name, e)
226 failed += 1
236 failed += 1
227 return failed
237 return failed
228
238
229 parser = optparse.OptionParser("%prog [options] [features]")
239 parser = optparse.OptionParser("%prog [options] [features]")
230 parser.add_option("--test-features", action="store_true",
240 parser.add_option("--test-features", action="store_true",
231 help="test available features")
241 help="test available features")
232 parser.add_option("--list-features", action="store_true",
242 parser.add_option("--list-features", action="store_true",
233 help="list available features")
243 help="list available features")
234 parser.add_option("-q", "--quiet", action="store_true",
244 parser.add_option("-q", "--quiet", action="store_true",
235 help="check features silently")
245 help="check features silently")
236
246
237 if __name__ == '__main__':
247 if __name__ == '__main__':
238 options, args = parser.parse_args()
248 options, args = parser.parse_args()
239 if options.list_features:
249 if options.list_features:
240 list_features()
250 list_features()
241 sys.exit(0)
251 sys.exit(0)
242
252
243 if options.test_features:
253 if options.test_features:
244 sys.exit(test_features())
254 sys.exit(test_features())
245
255
246 quiet = options.quiet
256 quiet = options.quiet
247
257
248 failures = 0
258 failures = 0
249
259
250 def error(msg):
260 def error(msg):
251 global failures
261 global failures
252 if not quiet:
262 if not quiet:
253 sys.stderr.write(msg + '\n')
263 sys.stderr.write(msg + '\n')
254 failures += 1
264 failures += 1
255
265
256 for feature in args:
266 for feature in args:
257 negate = feature.startswith('no-')
267 negate = feature.startswith('no-')
258 if negate:
268 if negate:
259 feature = feature[3:]
269 feature = feature[3:]
260
270
261 if feature not in checks:
271 if feature not in checks:
262 error('skipped: unknown feature: ' + feature)
272 error('skipped: unknown feature: ' + feature)
263 continue
273 continue
264
274
265 check, desc = checks[feature]
275 check, desc = checks[feature]
266 try:
276 try:
267 available = check()
277 available = check()
268 except Exception, e:
278 except Exception, e:
269 error('hghave check failed: ' + feature)
279 error('hghave check failed: ' + feature)
270 continue
280 continue
271
281
272 if not negate and not available:
282 if not negate and not available:
273 error('skipped: missing feature: ' + desc)
283 error('skipped: missing feature: ' + desc)
274 elif negate and available:
284 elif negate and available:
275 error('skipped: system supports %s' % desc)
285 error('skipped: system supports %s' % desc)
276
286
277 if failures != 0:
287 if failures != 0:
278 sys.exit(1)
288 sys.exit(1)
@@ -1,229 +1,235 b''
1
1
2 $ catpatch() {
2 $ catpatch() {
3 > cat $1 | sed -e "s/^\(# Parent \).*/\1/"
3 > cat $1 | sed -e "s/^\(# Parent \).*/\1/"
4 > }
4 > }
5 $ echo "[extensions]" >> $HGRCPATH
5 $ echo "[extensions]" >> $HGRCPATH
6 $ echo "mq=" >> $HGRCPATH
6 $ echo "mq=" >> $HGRCPATH
7 $ runtest() {
7 $ runtest() {
8 > hg init mq
8 > hg init mq
9 > cd mq
9 > cd mq
10 >
10 >
11 > echo a > a
11 > echo a > a
12 > hg ci -Ama
12 > hg ci -Ama
13 >
13 >
14 > echo '% qnew should refuse bad patch names'
14 > echo '% qnew should refuse bad patch names'
15 > hg qnew series
15 > hg qnew series
16 > hg qnew status
16 > hg qnew status
17 > hg qnew guards
17 > hg qnew guards
18 > hg qnew .
19 > hg qnew ..
18 > hg qnew .hgignore
20 > hg qnew .hgignore
19 > hg qnew .mqfoo
21 > hg qnew .mqfoo
20 > hg qnew 'foo#bar'
22 > hg qnew 'foo#bar'
21 > hg qnew 'foo:bar'
23 > hg qnew 'foo:bar'
22 >
24 >
23 > hg qinit -c
25 > hg qinit -c
24 >
26 >
25 > echo '% qnew with name containing slash'
27 > echo '% qnew with name containing slash'
26 > hg qnew foo/
28 > hg qnew foo/
27 > hg qnew foo/bar.patch
29 > hg qnew foo/bar.patch
28 > hg qnew foo
30 > hg qnew foo
29 > hg qseries
31 > hg qseries
30 > hg qpop
32 > hg qpop
31 > hg qdelete foo/bar.patch
33 > hg qdelete foo/bar.patch
32 >
34 >
33 > echo '% qnew with uncommitted changes'
35 > echo '% qnew with uncommitted changes'
34 > echo a > somefile
36 > echo a > somefile
35 > hg add somefile
37 > hg add somefile
36 > hg qnew uncommitted.patch
38 > hg qnew uncommitted.patch
37 > hg st
39 > hg st
38 > hg qseries
40 > hg qseries
39 >
41 >
40 > echo '% qnew implies add'
42 > echo '% qnew implies add'
41 > hg -R .hg/patches st
43 > hg -R .hg/patches st
42 >
44 >
43 > echo '% qnew missing'
45 > echo '% qnew missing'
44 > hg qnew missing.patch missing
46 > hg qnew missing.patch missing
45 >
47 >
46 > echo '% qnew -m'
48 > echo '% qnew -m'
47 > hg qnew -m 'foo bar' mtest.patch
49 > hg qnew -m 'foo bar' mtest.patch
48 > catpatch .hg/patches/mtest.patch
50 > catpatch .hg/patches/mtest.patch
49 >
51 >
50 > echo '% qnew twice'
52 > echo '% qnew twice'
51 > hg qnew first.patch
53 > hg qnew first.patch
52 > hg qnew first.patch
54 > hg qnew first.patch
53 >
55 >
54 > touch ../first.patch
56 > touch ../first.patch
55 > hg qimport ../first.patch
57 > hg qimport ../first.patch
56 >
58 >
57 > echo '% qnew -f from a subdirectory'
59 > echo '% qnew -f from a subdirectory'
58 > hg qpop -a
60 > hg qpop -a
59 > mkdir d
61 > mkdir d
60 > cd d
62 > cd d
61 > echo b > b
63 > echo b > b
62 > hg ci -Am t
64 > hg ci -Am t
63 > echo b >> b
65 > echo b >> b
64 > hg st
66 > hg st
65 > hg qnew -g -f p
67 > hg qnew -g -f p
66 > catpatch ../.hg/patches/p
68 > catpatch ../.hg/patches/p
67 >
69 >
68 > echo '% qnew -u with no username configured'
70 > echo '% qnew -u with no username configured'
69 > HGUSER= hg qnew -u blue red
71 > HGUSER= hg qnew -u blue red
70 > catpatch ../.hg/patches/red
72 > catpatch ../.hg/patches/red
71 >
73 >
72 > echo '% qnew -e -u with no username configured'
74 > echo '% qnew -e -u with no username configured'
73 > HGUSER= hg qnew -e -u chartreuse fucsia
75 > HGUSER= hg qnew -e -u chartreuse fucsia
74 > catpatch ../.hg/patches/fucsia
76 > catpatch ../.hg/patches/fucsia
75 >
77 >
76 > echo '% fail when trying to import a merge'
78 > echo '% fail when trying to import a merge'
77 > hg init merge
79 > hg init merge
78 > cd merge
80 > cd merge
79 > touch a
81 > touch a
80 > hg ci -Am null
82 > hg ci -Am null
81 > echo a >> a
83 > echo a >> a
82 > hg ci -m a
84 > hg ci -m a
83 > hg up -r 0
85 > hg up -r 0
84 > echo b >> a
86 > echo b >> a
85 > hg ci -m b
87 > hg ci -m b
86 > hg merge -f 1
88 > hg merge -f 1
87 > hg resolve --mark a
89 > hg resolve --mark a
88 > hg qnew -f merge
90 > hg qnew -f merge
89 >
91 >
90 > cd ../../..
92 > cd ../../..
91 > rm -r mq
93 > rm -r mq
92 > }
94 > }
93
95
94 plain headers
96 plain headers
95
97
96 $ echo "[mq]" >> $HGRCPATH
98 $ echo "[mq]" >> $HGRCPATH
97 $ echo "plain=true" >> $HGRCPATH
99 $ echo "plain=true" >> $HGRCPATH
98 $ mkdir sandbox
100 $ mkdir sandbox
99 $ (cd sandbox ; runtest)
101 $ (cd sandbox ; runtest)
100 adding a
102 adding a
101 % qnew should refuse bad patch names
103 % qnew should refuse bad patch names
102 abort: "series" cannot be used as the name of a patch
104 abort: "series" cannot be used as the name of a patch
103 abort: "status" cannot be used as the name of a patch
105 abort: "status" cannot be used as the name of a patch
104 abort: "guards" cannot be used as the name of a patch
106 abort: "guards" cannot be used as the name of a patch
107 abort: "." cannot be used as the name of a patch
108 abort: ".." cannot be used as the name of a patch
105 abort: ".hgignore" cannot be used as the name of a patch
109 abort: ".hgignore" cannot be used as the name of a patch
106 abort: ".mqfoo" cannot be used as the name of a patch
110 abort: ".mqfoo" cannot be used as the name of a patch
107 abort: "foo#bar" cannot be used as the name of a patch
111 abort: "foo#bar" cannot be used as the name of a patch
108 abort: "foo:bar" cannot be used as the name of a patch
112 abort: "foo:bar" cannot be used as the name of a patch
109 % qnew with name containing slash
113 % qnew with name containing slash
110 abort: path ends in directory separator: foo/
114 abort: path ends in directory separator: foo/
111 abort: "foo" already exists as a directory
115 abort: "foo" already exists as a directory
112 foo/bar.patch
116 foo/bar.patch
113 popping foo/bar.patch
117 popping foo/bar.patch
114 patch queue now empty
118 patch queue now empty
115 % qnew with uncommitted changes
119 % qnew with uncommitted changes
116 uncommitted.patch
120 uncommitted.patch
117 % qnew implies add
121 % qnew implies add
118 A .hgignore
122 A .hgignore
119 A series
123 A series
120 A uncommitted.patch
124 A uncommitted.patch
121 % qnew missing
125 % qnew missing
122 abort: missing: No such file or directory
126 abort: missing: No such file or directory
123 % qnew -m
127 % qnew -m
124 foo bar
128 foo bar
125
129
126 % qnew twice
130 % qnew twice
127 abort: patch "first.patch" already exists
131 abort: patch "first.patch" already exists
128 abort: patch "first.patch" already exists
132 abort: patch "first.patch" already exists
129 % qnew -f from a subdirectory
133 % qnew -f from a subdirectory
130 popping first.patch
134 popping first.patch
131 popping mtest.patch
135 popping mtest.patch
132 popping uncommitted.patch
136 popping uncommitted.patch
133 patch queue now empty
137 patch queue now empty
134 adding d/b
138 adding d/b
135 M d/b
139 M d/b
136 diff --git a/d/b b/d/b
140 diff --git a/d/b b/d/b
137 --- a/d/b
141 --- a/d/b
138 +++ b/d/b
142 +++ b/d/b
139 @@ -1,1 +1,2 @@
143 @@ -1,1 +1,2 @@
140 b
144 b
141 +b
145 +b
142 % qnew -u with no username configured
146 % qnew -u with no username configured
143 From: blue
147 From: blue
144
148
145 % qnew -e -u with no username configured
149 % qnew -e -u with no username configured
146 From: chartreuse
150 From: chartreuse
147
151
148 % fail when trying to import a merge
152 % fail when trying to import a merge
149 adding a
153 adding a
150 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
154 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
151 created new head
155 created new head
152 merging a
156 merging a
153 warning: conflicts during merge.
157 warning: conflicts during merge.
154 merging a failed!
158 merging a failed!
155 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
159 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
156 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
160 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
157 abort: cannot manage merge changesets
161 abort: cannot manage merge changesets
158 $ rm -r sandbox
162 $ rm -r sandbox
159
163
160 hg headers
164 hg headers
161
165
162 $ echo "plain=false" >> $HGRCPATH
166 $ echo "plain=false" >> $HGRCPATH
163 $ mkdir sandbox
167 $ mkdir sandbox
164 $ (cd sandbox ; runtest)
168 $ (cd sandbox ; runtest)
165 adding a
169 adding a
166 % qnew should refuse bad patch names
170 % qnew should refuse bad patch names
167 abort: "series" cannot be used as the name of a patch
171 abort: "series" cannot be used as the name of a patch
168 abort: "status" cannot be used as the name of a patch
172 abort: "status" cannot be used as the name of a patch
169 abort: "guards" cannot be used as the name of a patch
173 abort: "guards" cannot be used as the name of a patch
174 abort: "." cannot be used as the name of a patch
175 abort: ".." cannot be used as the name of a patch
170 abort: ".hgignore" cannot be used as the name of a patch
176 abort: ".hgignore" cannot be used as the name of a patch
171 abort: ".mqfoo" cannot be used as the name of a patch
177 abort: ".mqfoo" cannot be used as the name of a patch
172 abort: "foo#bar" cannot be used as the name of a patch
178 abort: "foo#bar" cannot be used as the name of a patch
173 abort: "foo:bar" cannot be used as the name of a patch
179 abort: "foo:bar" cannot be used as the name of a patch
174 % qnew with name containing slash
180 % qnew with name containing slash
175 abort: path ends in directory separator: foo/
181 abort: path ends in directory separator: foo/
176 abort: "foo" already exists as a directory
182 abort: "foo" already exists as a directory
177 foo/bar.patch
183 foo/bar.patch
178 popping foo/bar.patch
184 popping foo/bar.patch
179 patch queue now empty
185 patch queue now empty
180 % qnew with uncommitted changes
186 % qnew with uncommitted changes
181 uncommitted.patch
187 uncommitted.patch
182 % qnew implies add
188 % qnew implies add
183 A .hgignore
189 A .hgignore
184 A series
190 A series
185 A uncommitted.patch
191 A uncommitted.patch
186 % qnew missing
192 % qnew missing
187 abort: missing: No such file or directory
193 abort: missing: No such file or directory
188 % qnew -m
194 % qnew -m
189 # HG changeset patch
195 # HG changeset patch
190 # Parent
196 # Parent
191 foo bar
197 foo bar
192
198
193 % qnew twice
199 % qnew twice
194 abort: patch "first.patch" already exists
200 abort: patch "first.patch" already exists
195 abort: patch "first.patch" already exists
201 abort: patch "first.patch" already exists
196 % qnew -f from a subdirectory
202 % qnew -f from a subdirectory
197 popping first.patch
203 popping first.patch
198 popping mtest.patch
204 popping mtest.patch
199 popping uncommitted.patch
205 popping uncommitted.patch
200 patch queue now empty
206 patch queue now empty
201 adding d/b
207 adding d/b
202 M d/b
208 M d/b
203 # HG changeset patch
209 # HG changeset patch
204 # Parent
210 # Parent
205 diff --git a/d/b b/d/b
211 diff --git a/d/b b/d/b
206 --- a/d/b
212 --- a/d/b
207 +++ b/d/b
213 +++ b/d/b
208 @@ -1,1 +1,2 @@
214 @@ -1,1 +1,2 @@
209 b
215 b
210 +b
216 +b
211 % qnew -u with no username configured
217 % qnew -u with no username configured
212 # HG changeset patch
218 # HG changeset patch
213 # Parent
219 # Parent
214 # User blue
220 # User blue
215 % qnew -e -u with no username configured
221 % qnew -e -u with no username configured
216 # HG changeset patch
222 # HG changeset patch
217 # Parent
223 # Parent
218 # User chartreuse
224 # User chartreuse
219 % fail when trying to import a merge
225 % fail when trying to import a merge
220 adding a
226 adding a
221 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
227 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
222 created new head
228 created new head
223 merging a
229 merging a
224 warning: conflicts during merge.
230 warning: conflicts during merge.
225 merging a failed!
231 merging a failed!
226 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
232 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
227 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
233 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
228 abort: cannot manage merge changesets
234 abort: cannot manage merge changesets
229 $ rm -r sandbox
235 $ rm -r sandbox
@@ -1,440 +1,491 b''
1 $ "$TESTDIR/hghave" svn || exit 80
1 $ "$TESTDIR/hghave" svn || exit 80
2
2
3 $ fix_path()
3 $ fix_path()
4 > {
4 > {
5 > tr '\\' /
5 > tr '\\' /
6 > }
6 > }
7
7
8 SVN wants all paths to start with a slash. Unfortunately, Windows ones
8 SVN wants all paths to start with a slash. Unfortunately, Windows ones
9 don't. Handle that.
9 don't. Handle that.
10
10
11 $ escapedwd=`pwd | fix_path`
11 $ escapedwd=`pwd | fix_path`
12 $ expr "$escapedwd" : '\/' > /dev/null || escapedwd="/$escapedwd"
12 $ expr "$escapedwd" : '\/' > /dev/null || escapedwd="/$escapedwd"
13 $ escapedwd=`python -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$escapedwd"`
13 $ escapedwd=`python -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$escapedwd"`
14
14
15 create subversion repo
15 create subversion repo
16
16
17 $ SVNREPO="file://$escapedwd/svn-repo"
17 $ SVNREPO="file://$escapedwd/svn-repo"
18 $ WCROOT="`pwd`/svn-wc"
18 $ WCROOT="`pwd`/svn-wc"
19 $ svnadmin create svn-repo
19 $ svnadmin create svn-repo
20 $ svn co "$SVNREPO" svn-wc
20 $ svn co "$SVNREPO" svn-wc
21 Checked out revision 0.
21 Checked out revision 0.
22 $ cd svn-wc
22 $ cd svn-wc
23 $ mkdir src
23 $ mkdir src
24 $ echo alpha > src/alpha
24 $ echo alpha > src/alpha
25 $ svn add src
25 $ svn add src
26 A src
26 A src
27 A src/alpha
27 A src/alpha
28 $ mkdir externals
28 $ mkdir externals
29 $ echo other > externals/other
29 $ echo other > externals/other
30 $ svn add externals
30 $ svn add externals
31 A externals
31 A externals
32 A externals/other
32 A externals/other
33 $ svn ci -m 'Add alpha'
33 $ svn ci -m 'Add alpha'
34 Adding externals
34 Adding externals
35 Adding externals/other
35 Adding externals/other
36 Adding src
36 Adding src
37 Adding src/alpha
37 Adding src/alpha
38 Transmitting file data ..
38 Transmitting file data ..
39 Committed revision 1.
39 Committed revision 1.
40 $ svn up
40 $ svn up
41 At revision 1.
41 At revision 1.
42 $ echo "externals -r1 $SVNREPO/externals" > extdef
42 $ echo "externals -r1 $SVNREPO/externals" > extdef
43 $ svn propset -F extdef svn:externals src
43 $ svn propset -F extdef svn:externals src
44 property 'svn:externals' set on 'src'
44 property 'svn:externals' set on 'src'
45 $ svn ci -m 'Setting externals'
45 $ svn ci -m 'Setting externals'
46 Sending src
46 Sending src
47
47
48 Committed revision 2.
48 Committed revision 2.
49 $ cd ..
49 $ cd ..
50
50
51 create hg repo
51 create hg repo
52
52
53 $ mkdir sub
53 $ mkdir sub
54 $ cd sub
54 $ cd sub
55 $ hg init t
55 $ hg init t
56 $ cd t
56 $ cd t
57
57
58 first revision, no sub
58 first revision, no sub
59
59
60 $ echo a > a
60 $ echo a > a
61 $ hg ci -Am0
61 $ hg ci -Am0
62 adding a
62 adding a
63
63
64 add first svn sub with leading whitespaces
64 add first svn sub with leading whitespaces
65
65
66 $ echo "s = [svn] $SVNREPO/src" >> .hgsub
66 $ echo "s = [svn] $SVNREPO/src" >> .hgsub
67 $ echo "subdir/s = [svn] $SVNREPO/src" >> .hgsub
67 $ echo "subdir/s = [svn] $SVNREPO/src" >> .hgsub
68 $ svn co --quiet "$SVNREPO"/src s
68 $ svn co --quiet "$SVNREPO"/src s
69 $ mkdir subdir
69 $ mkdir subdir
70 $ svn co --quiet "$SVNREPO"/src subdir/s
70 $ svn co --quiet "$SVNREPO"/src subdir/s
71 $ hg add .hgsub
71 $ hg add .hgsub
72 $ hg ci -m1
72 $ hg ci -m1
73 committing subrepository s
73 committing subrepository s
74 committing subrepository subdir/s
74 committing subrepository subdir/s
75
75
76 make sure we avoid empty commits (issue2445)
76 make sure we avoid empty commits (issue2445)
77
77
78 $ hg sum
78 $ hg sum
79 parent: 1:* tip (glob)
79 parent: 1:* tip (glob)
80 1
80 1
81 branch: default
81 branch: default
82 commit: (clean)
82 commit: (clean)
83 update: (current)
83 update: (current)
84 $ hg ci -moops
84 $ hg ci -moops
85 nothing changed
85 nothing changed
86 [1]
86 [1]
87
87
88 debugsub
88 debugsub
89
89
90 $ hg debugsub
90 $ hg debugsub
91 path s
91 path s
92 source file://*/svn-repo/src (glob)
92 source file://*/svn-repo/src (glob)
93 revision 2
93 revision 2
94 path subdir/s
94 path subdir/s
95 source file://*/svn-repo/src (glob)
95 source file://*/svn-repo/src (glob)
96 revision 2
96 revision 2
97
97
98 change file in svn and hg, commit
98 change file in svn and hg, commit
99
99
100 $ echo a >> a
100 $ echo a >> a
101 $ echo alpha >> s/alpha
101 $ echo alpha >> s/alpha
102 $ hg sum
102 $ hg sum
103 parent: 1:* tip (glob)
103 parent: 1:* tip (glob)
104 1
104 1
105 branch: default
105 branch: default
106 commit: 1 modified, 1 subrepos
106 commit: 1 modified, 1 subrepos
107 update: (current)
107 update: (current)
108 $ hg commit -m 'Message!'
108 $ hg commit -m 'Message!'
109 committing subrepository s
109 committing subrepository s
110 Sending*s/alpha (glob)
110 Sending*s/alpha (glob)
111 Transmitting file data .
111 Transmitting file data .
112 Committed revision 3.
112 Committed revision 3.
113
113
114 Fetching external item into '$TESTTMP/sub/t/s/externals'
114 Fetching external item into '$TESTTMP/sub/t/s/externals'
115 External at revision 1.
115 External at revision 1.
116
116
117 At revision 3.
117 At revision 3.
118 $ hg debugsub
118 $ hg debugsub
119 path s
119 path s
120 source file://*/svn-repo/src (glob)
120 source file://*/svn-repo/src (glob)
121 revision 3
121 revision 3
122 path subdir/s
122 path subdir/s
123 source file://*/svn-repo/src (glob)
123 source file://*/svn-repo/src (glob)
124 revision 2
124 revision 2
125
125
126 add an unrelated revision in svn and update the subrepo to without
126 add an unrelated revision in svn and update the subrepo to without
127 bringing any changes.
127 bringing any changes.
128
128
129 $ svn mkdir "$SVNREPO/unrelated" -m 'create unrelated'
129 $ svn mkdir "$SVNREPO/unrelated" -m 'create unrelated'
130
130
131 Committed revision 4.
131 Committed revision 4.
132 $ svn up s
132 $ svn up s
133
133
134 Fetching external item into 's/externals'
134 Fetching external item into 's/externals'
135 External at revision 1.
135 External at revision 1.
136
136
137 At revision 4.
137 At revision 4.
138 $ hg sum
138 $ hg sum
139 parent: 2:* tip (glob)
139 parent: 2:* tip (glob)
140 Message!
140 Message!
141 branch: default
141 branch: default
142 commit: (clean)
142 commit: (clean)
143 update: (current)
143 update: (current)
144
144
145 $ echo a > s/a
145 $ echo a > s/a
146
146
147 should be empty despite change to s/a
147 should be empty despite change to s/a
148
148
149 $ hg st
149 $ hg st
150
150
151 add a commit from svn
151 add a commit from svn
152
152
153 $ cd "$WCROOT"/src
153 $ cd "$WCROOT"/src
154 $ svn up
154 $ svn up
155 U alpha
155 U alpha
156
156
157 Fetching external item into 'externals'
157 Fetching external item into 'externals'
158 A externals/other
158 A externals/other
159 Updated external to revision 1.
159 Updated external to revision 1.
160
160
161 Updated to revision 4.
161 Updated to revision 4.
162 $ echo xyz >> alpha
162 $ echo xyz >> alpha
163 $ svn propset svn:mime-type 'text/xml' alpha
163 $ svn propset svn:mime-type 'text/xml' alpha
164 property 'svn:mime-type' set on 'alpha'
164 property 'svn:mime-type' set on 'alpha'
165 $ svn ci -m 'amend a from svn'
165 $ svn ci -m 'amend a from svn'
166 Sending src/alpha
166 Sending src/alpha
167 Transmitting file data .
167 Transmitting file data .
168 Committed revision 5.
168 Committed revision 5.
169 $ cd ../../sub/t
169 $ cd ../../sub/t
170
170
171 this commit from hg will fail
171 this commit from hg will fail
172
172
173 $ echo zzz >> s/alpha
173 $ echo zzz >> s/alpha
174 $ hg ci -m 'amend alpha from hg'
174 $ hg ci -m 'amend alpha from hg'
175 committing subrepository s
175 committing subrepository s
176 abort: svn: Commit failed (details follow):
176 abort: svn: Commit failed (details follow):
177 svn: (Out of date)?.*/src/alpha.*(is out of date)? (re)
177 svn: (Out of date)?.*/src/alpha.*(is out of date)? (re)
178 [255]
178 [255]
179 $ svn revert -q s/alpha
179 $ svn revert -q s/alpha
180
180
181 this commit fails because of meta changes
181 this commit fails because of meta changes
182
182
183 $ svn propset svn:mime-type 'text/html' s/alpha
183 $ svn propset svn:mime-type 'text/html' s/alpha
184 property 'svn:mime-type' set on 's/alpha'
184 property 'svn:mime-type' set on 's/alpha'
185 $ hg ci -m 'amend alpha from hg'
185 $ hg ci -m 'amend alpha from hg'
186 committing subrepository s
186 committing subrepository s
187 abort: svn: Commit failed (details follow):
187 abort: svn: Commit failed (details follow):
188 svn: (Out of date)?.*/src/alpha.*(is out of date)? (re)
188 svn: (Out of date)?.*/src/alpha.*(is out of date)? (re)
189 [255]
189 [255]
190 $ svn revert -q s/alpha
190 $ svn revert -q s/alpha
191
191
192 this commit fails because of externals changes
192 this commit fails because of externals changes
193
193
194 $ echo zzz > s/externals/other
194 $ echo zzz > s/externals/other
195 $ hg ci -m 'amend externals from hg'
195 $ hg ci -m 'amend externals from hg'
196 committing subrepository s
196 committing subrepository s
197 abort: cannot commit svn externals
197 abort: cannot commit svn externals
198 [255]
198 [255]
199 $ hg diff --subrepos -r 1:2 | grep -v diff
199 $ hg diff --subrepos -r 1:2 | grep -v diff
200 --- a/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
200 --- a/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
201 +++ b/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
201 +++ b/.hgsubstate Thu Jan 01 00:00:00 1970 +0000
202 @@ -1,2 +1,2 @@
202 @@ -1,2 +1,2 @@
203 -2 s
203 -2 s
204 +3 s
204 +3 s
205 2 subdir/s
205 2 subdir/s
206 --- a/a Thu Jan 01 00:00:00 1970 +0000
206 --- a/a Thu Jan 01 00:00:00 1970 +0000
207 +++ b/a Thu Jan 01 00:00:00 1970 +0000
207 +++ b/a Thu Jan 01 00:00:00 1970 +0000
208 @@ -1,1 +1,2 @@
208 @@ -1,1 +1,2 @@
209 a
209 a
210 +a
210 +a
211 $ svn revert -q s/externals/other
211 $ svn revert -q s/externals/other
212
212
213 this commit fails because of externals meta changes
213 this commit fails because of externals meta changes
214
214
215 $ svn propset svn:mime-type 'text/html' s/externals/other
215 $ svn propset svn:mime-type 'text/html' s/externals/other
216 property 'svn:mime-type' set on 's/externals/other'
216 property 'svn:mime-type' set on 's/externals/other'
217 $ hg ci -m 'amend externals from hg'
217 $ hg ci -m 'amend externals from hg'
218 committing subrepository s
218 committing subrepository s
219 abort: cannot commit svn externals
219 abort: cannot commit svn externals
220 [255]
220 [255]
221 $ svn revert -q s/externals/other
221 $ svn revert -q s/externals/other
222
222
223 clone
223 clone
224
224
225 $ cd ..
225 $ cd ..
226 $ hg clone t tc | fix_path
226 $ hg clone t tc | fix_path
227 updating to branch default
227 updating to branch default
228 A tc/s/alpha
228 A tc/s/alpha
229 U tc/s
229 U tc/s
230
230
231 Fetching external item into 'tc/s/externals'
231 Fetching external item into 'tc/s/externals'
232 A tc/s/externals/other
232 A tc/s/externals/other
233 Checked out external at revision 1.
233 Checked out external at revision 1.
234
234
235 Checked out revision 3.
235 Checked out revision 3.
236 A tc/subdir/s/alpha
236 A tc/subdir/s/alpha
237 U tc/subdir/s
237 U tc/subdir/s
238
238
239 Fetching external item into 'tc/subdir/s/externals'
239 Fetching external item into 'tc/subdir/s/externals'
240 A tc/subdir/s/externals/other
240 A tc/subdir/s/externals/other
241 Checked out external at revision 1.
241 Checked out external at revision 1.
242
242
243 Checked out revision 2.
243 Checked out revision 2.
244 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
244 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
245 $ cd tc
245 $ cd tc
246
246
247 debugsub in clone
247 debugsub in clone
248
248
249 $ hg debugsub
249 $ hg debugsub
250 path s
250 path s
251 source file://*/svn-repo/src (glob)
251 source file://*/svn-repo/src (glob)
252 revision 3
252 revision 3
253 path subdir/s
253 path subdir/s
254 source file://*/svn-repo/src (glob)
254 source file://*/svn-repo/src (glob)
255 revision 2
255 revision 2
256
256
257 verify subrepo is contained within the repo directory
257 verify subrepo is contained within the repo directory
258
258
259 $ python -c "import os.path; print os.path.exists('s')"
259 $ python -c "import os.path; print os.path.exists('s')"
260 True
260 True
261
261
262 update to nullrev (must delete the subrepo)
262 update to nullrev (must delete the subrepo)
263
263
264 $ hg up null
264 $ hg up null
265 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
265 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
266 $ ls
266 $ ls
267
267
268 Check hg update --clean
268 Check hg update --clean
269 $ cd $TESTTMP/sub/t
269 $ cd $TESTTMP/sub/t
270 $ cd s
270 $ cd s
271 $ echo c0 > alpha
271 $ echo c0 > alpha
272 $ echo c1 > f1
272 $ echo c1 > f1
273 $ echo c1 > f2
273 $ echo c1 > f2
274 $ svn add f1 -q
274 $ svn add f1 -q
275 $ svn status
275 $ svn status
276 ? * a (glob)
276 ? * a (glob)
277 X * externals (glob)
277 X * externals (glob)
278 ? * f2 (glob)
278 ? * f2 (glob)
279 M * alpha (glob)
279 M * alpha (glob)
280 A * f1 (glob)
280 A * f1 (glob)
281
281
282 Performing status on external item at 'externals'
282 Performing status on external item at 'externals'
283 $ cd ../..
283 $ cd ../..
284 $ hg -R t update -C
284 $ hg -R t update -C
285
285
286 Fetching external item into 't/s/externals'
286 Fetching external item into 't/s/externals'
287 Checked out external at revision 1.
287 Checked out external at revision 1.
288
288
289 Checked out revision 3.
289 Checked out revision 3.
290 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
290 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
291 $ cd t/s
291 $ cd t/s
292 $ svn status
292 $ svn status
293 ? * a (glob)
293 ? * a (glob)
294 X * externals (glob)
294 X * externals (glob)
295 ? * f1 (glob)
295 ? * f1 (glob)
296 ? * f2 (glob)
296 ? * f2 (glob)
297
297
298 Performing status on external item at 'externals'
298 Performing status on external item at 'externals'
299
299
300 Sticky subrepositories, no changes
300 Sticky subrepositories, no changes
301 $ cd $TESTTMP/sub/t
301 $ cd $TESTTMP/sub/t
302 $ hg id -n
302 $ hg id -n
303 2
303 2
304 $ cd s
304 $ cd s
305 $ svnversion
305 $ svnversion
306 3
306 3
307 $ cd ..
307 $ cd ..
308 $ hg update 1
308 $ hg update 1
309 U $TESTTMP/sub/t/s/alpha
309 U $TESTTMP/sub/t/s/alpha
310
310
311 Fetching external item into '$TESTTMP/sub/t/s/externals'
311 Fetching external item into '$TESTTMP/sub/t/s/externals'
312 Checked out external at revision 1.
312 Checked out external at revision 1.
313
313
314 Checked out revision 2.
314 Checked out revision 2.
315 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
315 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
316 $ hg id -n
316 $ hg id -n
317 1
317 1
318 $ cd s
318 $ cd s
319 $ svnversion
319 $ svnversion
320 2
320 2
321 $ cd ..
321 $ cd ..
322
322
323 Sticky subrepositorys, file changes
323 Sticky subrepositorys, file changes
324 $ touch s/f1
324 $ touch s/f1
325 $ cd s
325 $ cd s
326 $ svn add f1
326 $ svn add f1
327 A f1
327 A f1
328 $ cd ..
328 $ cd ..
329 $ hg id -n
329 $ hg id -n
330 1
330 1
331 $ cd s
331 $ cd s
332 $ svnversion
332 $ svnversion
333 2M
333 2M
334 $ cd ..
334 $ cd ..
335 $ hg update tip
335 $ hg update tip
336 subrepository sources for s differ
336 subrepository sources for s differ
337 use (l)ocal source (2) or (r)emote source (3)?
337 use (l)ocal source (2) or (r)emote source (3)?
338 l
338 l
339 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
339 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
340 $ hg id -n
340 $ hg id -n
341 2+
341 2+
342 $ cd s
342 $ cd s
343 $ svnversion
343 $ svnversion
344 2M
344 2M
345 $ cd ..
345 $ cd ..
346 $ hg update --clean tip
346 $ hg update --clean tip
347 U $TESTTMP/sub/t/s/alpha
347 U $TESTTMP/sub/t/s/alpha
348
348
349 Fetching external item into '$TESTTMP/sub/t/s/externals'
349 Fetching external item into '$TESTTMP/sub/t/s/externals'
350 Checked out external at revision 1.
350 Checked out external at revision 1.
351
351
352 Checked out revision 3.
352 Checked out revision 3.
353 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
353 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
354
354
355 Sticky subrepository, revision updates
355 Sticky subrepository, revision updates
356 $ hg id -n
356 $ hg id -n
357 2
357 2
358 $ cd s
358 $ cd s
359 $ svnversion
359 $ svnversion
360 3
360 3
361 $ cd ..
361 $ cd ..
362 $ cd s
362 $ cd s
363 $ svn update -r 1
363 $ svn update -r 1
364 U alpha
364 U alpha
365 U .
365 U .
366
366
367 Fetching external item into 'externals'
367 Fetching external item into 'externals'
368 Updated external to revision 1.
368 Updated external to revision 1.
369
369
370 Updated to revision 1.
370 Updated to revision 1.
371 $ cd ..
371 $ cd ..
372 $ hg update 1
372 $ hg update 1
373 subrepository sources for s differ (in checked out version)
373 subrepository sources for s differ (in checked out version)
374 use (l)ocal source (1) or (r)emote source (2)?
374 use (l)ocal source (1) or (r)emote source (2)?
375 l
375 l
376 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
376 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
377 $ hg id -n
377 $ hg id -n
378 1+
378 1+
379 $ cd s
379 $ cd s
380 $ svnversion
380 $ svnversion
381 1
381 1
382 $ cd ..
382 $ cd ..
383
383
384 Sticky subrepository, file changes and revision updates
384 Sticky subrepository, file changes and revision updates
385 $ touch s/f1
385 $ touch s/f1
386 $ cd s
386 $ cd s
387 $ svn add f1
387 $ svn add f1
388 A f1
388 A f1
389 $ svnversion
389 $ svnversion
390 1M
390 1M
391 $ cd ..
391 $ cd ..
392 $ hg id -n
392 $ hg id -n
393 1+
393 1+
394 $ hg update tip
394 $ hg update tip
395 subrepository sources for s differ
395 subrepository sources for s differ
396 use (l)ocal source (1) or (r)emote source (3)?
396 use (l)ocal source (1) or (r)emote source (3)?
397 l
397 l
398 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
398 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
399 $ hg id -n
399 $ hg id -n
400 2
400 2
401 $ cd s
401 $ cd s
402 $ svnversion
402 $ svnversion
403 1M
403 1M
404 $ cd ..
404 $ cd ..
405
405
406 Sticky repository, update --clean
406 Sticky repository, update --clean
407 $ hg update --clean tip
407 $ hg update --clean tip
408 U $TESTTMP/sub/t/s/alpha
408 U $TESTTMP/sub/t/s/alpha
409 U $TESTTMP/sub/t/s
409 U $TESTTMP/sub/t/s
410
410
411 Fetching external item into '$TESTTMP/sub/t/s/externals'
411 Fetching external item into '$TESTTMP/sub/t/s/externals'
412 Checked out external at revision 1.
412 Checked out external at revision 1.
413
413
414 Checked out revision 3.
414 Checked out revision 3.
415 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
415 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
416 $ hg id -n
416 $ hg id -n
417 2
417 2
418 $ cd s
418 $ cd s
419 $ svnversion
419 $ svnversion
420 3
420 3
421 $ cd ..
421 $ cd ..
422
422
423 Test subrepo already at intended revision:
423 Test subrepo already at intended revision:
424 $ cd s
424 $ cd s
425 $ svn update -r 2
425 $ svn update -r 2
426 U alpha
426 U alpha
427
427
428 Fetching external item into 'externals'
428 Fetching external item into 'externals'
429 Updated external to revision 1.
429 Updated external to revision 1.
430
430
431 Updated to revision 2.
431 Updated to revision 2.
432 $ cd ..
432 $ cd ..
433 $ hg update 1
433 $ hg update 1
434 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
434 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
435 $ hg id -n
435 $ hg id -n
436 1+
436 1+
437 $ cd s
437 $ cd s
438 $ svnversion
438 $ svnversion
439 2
439 2
440 $ cd ..
440 $ cd ..
441
442 Test case where subversion would fail to update the subrepo because there
443 are unknown directories being replaced by tracked ones (happens with rebase).
444
445 $ cd $WCROOT/src
446 $ mkdir dir
447 $ echo epsilon.py > dir/epsilon.py
448 $ svn add dir
449 A dir
450 A dir/epsilon.py
451 $ svn ci -m 'Add dir/epsilon.py'
452 Adding src/dir
453 Adding src/dir/epsilon.py
454 Transmitting file data .
455 Committed revision 6.
456 $ cd ../..
457 $ hg init rebaserepo
458 $ cd rebaserepo
459 $ svn co -r5 --quiet "$SVNREPO"/src s
460 $ echo "s = [svn] $SVNREPO/src" >> .hgsub
461 $ hg add .hgsub
462 $ hg ci -m addsub
463 committing subrepository s
464 $ echo a > a
465 $ hg ci -Am adda
466 adding a
467 $ hg up 0
468 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
469 $ svn up -r6 s
470 A s/dir
471 A s/dir/epsilon.py
472
473 Fetching external item into 's/externals'
474 Updated external to revision 1.
475
476 Updated to revision 6.
477 $ hg ci -m updatesub
478 committing subrepository s
479 created new head
480 $ echo pyc > s/dir/epsilon.pyc
481 $ hg up 1
482 D $TESTTMP/rebaserepo/s/dir
483
484 Fetching external item into '$TESTTMP/rebaserepo/s/externals'
485 Checked out external at revision 1.
486
487 Checked out revision 5.
488 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
489 $ if "$TESTDIR/hghave" -q svn15; then
490 > hg up 2 >/dev/null 2>&1 || echo update failed
491 > fi
General Comments 0
You need to be logged in to leave comments. Login now