##// END OF EJS Templates
patch: remove patch.patch() cwd argument
Patrick Mezard -
r14382:2d16f15d default
parent child Browse files
Show More
@@ -1,3307 +1,3307 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, scmutil, util, revset
48 from mercurial import commands, cmdutil, hg, scmutil, util, revset
49 from mercurial import repair, extensions, url, error
49 from mercurial import repair, extensions, url, error
50 from mercurial import patch as patchmod
50 from mercurial import patch as patchmod
51 import os, sys, re, errno, shutil
51 import os, sys, re, errno, shutil
52
52
53 commands.norepo += " qclone"
53 commands.norepo += " qclone"
54
54
55 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
55 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
56
56
57 cmdtable = {}
57 cmdtable = {}
58 command = cmdutil.command(cmdtable)
58 command = cmdutil.command(cmdtable)
59
59
60 # Patch names looks like unix-file names.
60 # Patch names looks like unix-file names.
61 # They must be joinable with queue directory and result in the patch path.
61 # They must be joinable with queue directory and result in the patch path.
62 normname = util.normpath
62 normname = util.normpath
63
63
64 class statusentry(object):
64 class statusentry(object):
65 def __init__(self, node, name):
65 def __init__(self, node, name):
66 self.node, self.name = node, name
66 self.node, self.name = node, name
67 def __repr__(self):
67 def __repr__(self):
68 return hex(self.node) + ':' + self.name
68 return hex(self.node) + ':' + self.name
69
69
70 class patchheader(object):
70 class patchheader(object):
71 def __init__(self, pf, plainmode=False):
71 def __init__(self, pf, plainmode=False):
72 def eatdiff(lines):
72 def eatdiff(lines):
73 while lines:
73 while lines:
74 l = lines[-1]
74 l = lines[-1]
75 if (l.startswith("diff -") or
75 if (l.startswith("diff -") or
76 l.startswith("Index:") or
76 l.startswith("Index:") or
77 l.startswith("===========")):
77 l.startswith("===========")):
78 del lines[-1]
78 del lines[-1]
79 else:
79 else:
80 break
80 break
81 def eatempty(lines):
81 def eatempty(lines):
82 while lines:
82 while lines:
83 if not lines[-1].strip():
83 if not lines[-1].strip():
84 del lines[-1]
84 del lines[-1]
85 else:
85 else:
86 break
86 break
87
87
88 message = []
88 message = []
89 comments = []
89 comments = []
90 user = None
90 user = None
91 date = None
91 date = None
92 parent = None
92 parent = None
93 format = None
93 format = None
94 subject = None
94 subject = None
95 branch = None
95 branch = None
96 nodeid = None
96 nodeid = None
97 diffstart = 0
97 diffstart = 0
98
98
99 for line in file(pf):
99 for line in file(pf):
100 line = line.rstrip()
100 line = line.rstrip()
101 if (line.startswith('diff --git')
101 if (line.startswith('diff --git')
102 or (diffstart and line.startswith('+++ '))):
102 or (diffstart and line.startswith('+++ '))):
103 diffstart = 2
103 diffstart = 2
104 break
104 break
105 diffstart = 0 # reset
105 diffstart = 0 # reset
106 if line.startswith("--- "):
106 if line.startswith("--- "):
107 diffstart = 1
107 diffstart = 1
108 continue
108 continue
109 elif format == "hgpatch":
109 elif format == "hgpatch":
110 # parse values when importing the result of an hg export
110 # parse values when importing the result of an hg export
111 if line.startswith("# User "):
111 if line.startswith("# User "):
112 user = line[7:]
112 user = line[7:]
113 elif line.startswith("# Date "):
113 elif line.startswith("# Date "):
114 date = line[7:]
114 date = line[7:]
115 elif line.startswith("# Parent "):
115 elif line.startswith("# Parent "):
116 parent = line[9:].lstrip()
116 parent = line[9:].lstrip()
117 elif line.startswith("# Branch "):
117 elif line.startswith("# Branch "):
118 branch = line[9:]
118 branch = line[9:]
119 elif line.startswith("# Node ID "):
119 elif line.startswith("# Node ID "):
120 nodeid = line[10:]
120 nodeid = line[10:]
121 elif not line.startswith("# ") and line:
121 elif not line.startswith("# ") and line:
122 message.append(line)
122 message.append(line)
123 format = None
123 format = None
124 elif line == '# HG changeset patch':
124 elif line == '# HG changeset patch':
125 message = []
125 message = []
126 format = "hgpatch"
126 format = "hgpatch"
127 elif (format != "tagdone" and (line.startswith("Subject: ") or
127 elif (format != "tagdone" and (line.startswith("Subject: ") or
128 line.startswith("subject: "))):
128 line.startswith("subject: "))):
129 subject = line[9:]
129 subject = line[9:]
130 format = "tag"
130 format = "tag"
131 elif (format != "tagdone" and (line.startswith("From: ") or
131 elif (format != "tagdone" and (line.startswith("From: ") or
132 line.startswith("from: "))):
132 line.startswith("from: "))):
133 user = line[6:]
133 user = line[6:]
134 format = "tag"
134 format = "tag"
135 elif (format != "tagdone" and (line.startswith("Date: ") or
135 elif (format != "tagdone" and (line.startswith("Date: ") or
136 line.startswith("date: "))):
136 line.startswith("date: "))):
137 date = line[6:]
137 date = line[6:]
138 format = "tag"
138 format = "tag"
139 elif format == "tag" and line == "":
139 elif format == "tag" and line == "":
140 # when looking for tags (subject: from: etc) they
140 # when looking for tags (subject: from: etc) they
141 # end once you find a blank line in the source
141 # end once you find a blank line in the source
142 format = "tagdone"
142 format = "tagdone"
143 elif message or line:
143 elif message or line:
144 message.append(line)
144 message.append(line)
145 comments.append(line)
145 comments.append(line)
146
146
147 eatdiff(message)
147 eatdiff(message)
148 eatdiff(comments)
148 eatdiff(comments)
149 # Remember the exact starting line of the patch diffs before consuming
149 # Remember the exact starting line of the patch diffs before consuming
150 # empty lines, for external use by TortoiseHg and others
150 # empty lines, for external use by TortoiseHg and others
151 self.diffstartline = len(comments)
151 self.diffstartline = len(comments)
152 eatempty(message)
152 eatempty(message)
153 eatempty(comments)
153 eatempty(comments)
154
154
155 # make sure message isn't empty
155 # make sure message isn't empty
156 if format and format.startswith("tag") and subject:
156 if format and format.startswith("tag") and subject:
157 message.insert(0, "")
157 message.insert(0, "")
158 message.insert(0, subject)
158 message.insert(0, subject)
159
159
160 self.message = message
160 self.message = message
161 self.comments = comments
161 self.comments = comments
162 self.user = user
162 self.user = user
163 self.date = date
163 self.date = date
164 self.parent = parent
164 self.parent = parent
165 # nodeid and branch are for external use by TortoiseHg and others
165 # nodeid and branch are for external use by TortoiseHg and others
166 self.nodeid = nodeid
166 self.nodeid = nodeid
167 self.branch = branch
167 self.branch = branch
168 self.haspatch = diffstart > 1
168 self.haspatch = diffstart > 1
169 self.plainmode = plainmode
169 self.plainmode = plainmode
170
170
171 def setuser(self, user):
171 def setuser(self, user):
172 if not self.updateheader(['From: ', '# User '], user):
172 if not self.updateheader(['From: ', '# User '], user):
173 try:
173 try:
174 patchheaderat = self.comments.index('# HG changeset patch')
174 patchheaderat = self.comments.index('# HG changeset patch')
175 self.comments.insert(patchheaderat + 1, '# User ' + user)
175 self.comments.insert(patchheaderat + 1, '# User ' + user)
176 except ValueError:
176 except ValueError:
177 if self.plainmode or self._hasheader(['Date: ']):
177 if self.plainmode or self._hasheader(['Date: ']):
178 self.comments = ['From: ' + user] + self.comments
178 self.comments = ['From: ' + user] + self.comments
179 else:
179 else:
180 tmp = ['# HG changeset patch', '# User ' + user, '']
180 tmp = ['# HG changeset patch', '# User ' + user, '']
181 self.comments = tmp + self.comments
181 self.comments = tmp + self.comments
182 self.user = user
182 self.user = user
183
183
184 def setdate(self, date):
184 def setdate(self, date):
185 if not self.updateheader(['Date: ', '# Date '], date):
185 if not self.updateheader(['Date: ', '# Date '], date):
186 try:
186 try:
187 patchheaderat = self.comments.index('# HG changeset patch')
187 patchheaderat = self.comments.index('# HG changeset patch')
188 self.comments.insert(patchheaderat + 1, '# Date ' + date)
188 self.comments.insert(patchheaderat + 1, '# Date ' + date)
189 except ValueError:
189 except ValueError:
190 if self.plainmode or self._hasheader(['From: ']):
190 if self.plainmode or self._hasheader(['From: ']):
191 self.comments = ['Date: ' + date] + self.comments
191 self.comments = ['Date: ' + date] + self.comments
192 else:
192 else:
193 tmp = ['# HG changeset patch', '# Date ' + date, '']
193 tmp = ['# HG changeset patch', '# Date ' + date, '']
194 self.comments = tmp + self.comments
194 self.comments = tmp + self.comments
195 self.date = date
195 self.date = date
196
196
197 def setparent(self, parent):
197 def setparent(self, parent):
198 if not self.updateheader(['# Parent '], parent):
198 if not self.updateheader(['# Parent '], parent):
199 try:
199 try:
200 patchheaderat = self.comments.index('# HG changeset patch')
200 patchheaderat = self.comments.index('# HG changeset patch')
201 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
201 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
202 except ValueError:
202 except ValueError:
203 pass
203 pass
204 self.parent = parent
204 self.parent = parent
205
205
206 def setmessage(self, message):
206 def setmessage(self, message):
207 if self.comments:
207 if self.comments:
208 self._delmsg()
208 self._delmsg()
209 self.message = [message]
209 self.message = [message]
210 self.comments += self.message
210 self.comments += self.message
211
211
212 def updateheader(self, prefixes, new):
212 def updateheader(self, prefixes, new):
213 '''Update all references to a field in the patch header.
213 '''Update all references to a field in the patch header.
214 Return whether the field is present.'''
214 Return whether the field is present.'''
215 res = False
215 res = False
216 for prefix in prefixes:
216 for prefix in prefixes:
217 for i in xrange(len(self.comments)):
217 for i in xrange(len(self.comments)):
218 if self.comments[i].startswith(prefix):
218 if self.comments[i].startswith(prefix):
219 self.comments[i] = prefix + new
219 self.comments[i] = prefix + new
220 res = True
220 res = True
221 break
221 break
222 return res
222 return res
223
223
224 def _hasheader(self, prefixes):
224 def _hasheader(self, prefixes):
225 '''Check if a header starts with any of the given prefixes.'''
225 '''Check if a header starts with any of the given prefixes.'''
226 for prefix in prefixes:
226 for prefix in prefixes:
227 for comment in self.comments:
227 for comment in self.comments:
228 if comment.startswith(prefix):
228 if comment.startswith(prefix):
229 return True
229 return True
230 return False
230 return False
231
231
232 def __str__(self):
232 def __str__(self):
233 if not self.comments:
233 if not self.comments:
234 return ''
234 return ''
235 return '\n'.join(self.comments) + '\n\n'
235 return '\n'.join(self.comments) + '\n\n'
236
236
237 def _delmsg(self):
237 def _delmsg(self):
238 '''Remove existing message, keeping the rest of the comments fields.
238 '''Remove existing message, keeping the rest of the comments fields.
239 If comments contains 'subject: ', message will prepend
239 If comments contains 'subject: ', message will prepend
240 the field and a blank line.'''
240 the field and a blank line.'''
241 if self.message:
241 if self.message:
242 subj = 'subject: ' + self.message[0].lower()
242 subj = 'subject: ' + self.message[0].lower()
243 for i in xrange(len(self.comments)):
243 for i in xrange(len(self.comments)):
244 if subj == self.comments[i].lower():
244 if subj == self.comments[i].lower():
245 del self.comments[i]
245 del self.comments[i]
246 self.message = self.message[2:]
246 self.message = self.message[2:]
247 break
247 break
248 ci = 0
248 ci = 0
249 for mi in self.message:
249 for mi in self.message:
250 while mi != self.comments[ci]:
250 while mi != self.comments[ci]:
251 ci += 1
251 ci += 1
252 del self.comments[ci]
252 del self.comments[ci]
253
253
254 class queue(object):
254 class queue(object):
255 def __init__(self, ui, path, patchdir=None):
255 def __init__(self, ui, path, patchdir=None):
256 self.basepath = path
256 self.basepath = path
257 try:
257 try:
258 fh = open(os.path.join(path, 'patches.queue'))
258 fh = open(os.path.join(path, 'patches.queue'))
259 cur = fh.read().rstrip()
259 cur = fh.read().rstrip()
260 fh.close()
260 fh.close()
261 if not cur:
261 if not cur:
262 curpath = os.path.join(path, 'patches')
262 curpath = os.path.join(path, 'patches')
263 else:
263 else:
264 curpath = os.path.join(path, 'patches-' + cur)
264 curpath = os.path.join(path, 'patches-' + cur)
265 except IOError:
265 except IOError:
266 curpath = os.path.join(path, 'patches')
266 curpath = os.path.join(path, 'patches')
267 self.path = patchdir or curpath
267 self.path = patchdir or curpath
268 self.opener = scmutil.opener(self.path)
268 self.opener = scmutil.opener(self.path)
269 self.ui = ui
269 self.ui = ui
270 self.applied_dirty = 0
270 self.applied_dirty = 0
271 self.series_dirty = 0
271 self.series_dirty = 0
272 self.added = []
272 self.added = []
273 self.series_path = "series"
273 self.series_path = "series"
274 self.status_path = "status"
274 self.status_path = "status"
275 self.guards_path = "guards"
275 self.guards_path = "guards"
276 self.active_guards = None
276 self.active_guards = None
277 self.guards_dirty = False
277 self.guards_dirty = False
278 # Handle mq.git as a bool with extended values
278 # Handle mq.git as a bool with extended values
279 try:
279 try:
280 gitmode = ui.configbool('mq', 'git', None)
280 gitmode = ui.configbool('mq', 'git', None)
281 if gitmode is None:
281 if gitmode is None:
282 raise error.ConfigError()
282 raise error.ConfigError()
283 self.gitmode = gitmode and 'yes' or 'no'
283 self.gitmode = gitmode and 'yes' or 'no'
284 except error.ConfigError:
284 except error.ConfigError:
285 self.gitmode = ui.config('mq', 'git', 'auto').lower()
285 self.gitmode = ui.config('mq', 'git', 'auto').lower()
286 self.plainmode = ui.configbool('mq', 'plain', False)
286 self.plainmode = ui.configbool('mq', 'plain', False)
287
287
288 @util.propertycache
288 @util.propertycache
289 def applied(self):
289 def applied(self):
290 if os.path.exists(self.join(self.status_path)):
290 if os.path.exists(self.join(self.status_path)):
291 def parselines(lines):
291 def parselines(lines):
292 for l in lines:
292 for l in lines:
293 entry = l.split(':', 1)
293 entry = l.split(':', 1)
294 if len(entry) > 1:
294 if len(entry) > 1:
295 n, name = entry
295 n, name = entry
296 yield statusentry(bin(n), name)
296 yield statusentry(bin(n), name)
297 elif l.strip():
297 elif l.strip():
298 self.ui.warn(_('malformated mq status line: %s\n') % entry)
298 self.ui.warn(_('malformated mq status line: %s\n') % entry)
299 # else we ignore empty lines
299 # else we ignore empty lines
300 lines = self.opener.read(self.status_path).splitlines()
300 lines = self.opener.read(self.status_path).splitlines()
301 return list(parselines(lines))
301 return list(parselines(lines))
302 return []
302 return []
303
303
304 @util.propertycache
304 @util.propertycache
305 def full_series(self):
305 def full_series(self):
306 if os.path.exists(self.join(self.series_path)):
306 if os.path.exists(self.join(self.series_path)):
307 return self.opener.read(self.series_path).splitlines()
307 return self.opener.read(self.series_path).splitlines()
308 return []
308 return []
309
309
310 @util.propertycache
310 @util.propertycache
311 def series(self):
311 def series(self):
312 self.parse_series()
312 self.parse_series()
313 return self.series
313 return self.series
314
314
315 @util.propertycache
315 @util.propertycache
316 def series_guards(self):
316 def series_guards(self):
317 self.parse_series()
317 self.parse_series()
318 return self.series_guards
318 return self.series_guards
319
319
320 def invalidate(self):
320 def invalidate(self):
321 for a in 'applied full_series series series_guards'.split():
321 for a in 'applied full_series series series_guards'.split():
322 if a in self.__dict__:
322 if a in self.__dict__:
323 delattr(self, a)
323 delattr(self, a)
324 self.applied_dirty = 0
324 self.applied_dirty = 0
325 self.series_dirty = 0
325 self.series_dirty = 0
326 self.guards_dirty = False
326 self.guards_dirty = False
327 self.active_guards = None
327 self.active_guards = None
328
328
329 def diffopts(self, opts={}, patchfn=None):
329 def diffopts(self, opts={}, patchfn=None):
330 diffopts = patchmod.diffopts(self.ui, opts)
330 diffopts = patchmod.diffopts(self.ui, opts)
331 if self.gitmode == 'auto':
331 if self.gitmode == 'auto':
332 diffopts.upgrade = True
332 diffopts.upgrade = True
333 elif self.gitmode == 'keep':
333 elif self.gitmode == 'keep':
334 pass
334 pass
335 elif self.gitmode in ('yes', 'no'):
335 elif self.gitmode in ('yes', 'no'):
336 diffopts.git = self.gitmode == 'yes'
336 diffopts.git = self.gitmode == 'yes'
337 else:
337 else:
338 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
338 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
339 ' got %s') % self.gitmode)
339 ' got %s') % self.gitmode)
340 if patchfn:
340 if patchfn:
341 diffopts = self.patchopts(diffopts, patchfn)
341 diffopts = self.patchopts(diffopts, patchfn)
342 return diffopts
342 return diffopts
343
343
344 def patchopts(self, diffopts, *patches):
344 def patchopts(self, diffopts, *patches):
345 """Return a copy of input diff options with git set to true if
345 """Return a copy of input diff options with git set to true if
346 referenced patch is a git patch and should be preserved as such.
346 referenced patch is a git patch and should be preserved as such.
347 """
347 """
348 diffopts = diffopts.copy()
348 diffopts = diffopts.copy()
349 if not diffopts.git and self.gitmode == 'keep':
349 if not diffopts.git and self.gitmode == 'keep':
350 for patchfn in patches:
350 for patchfn in patches:
351 patchf = self.opener(patchfn, 'r')
351 patchf = self.opener(patchfn, 'r')
352 # if the patch was a git patch, refresh it as a git patch
352 # if the patch was a git patch, refresh it as a git patch
353 for line in patchf:
353 for line in patchf:
354 if line.startswith('diff --git'):
354 if line.startswith('diff --git'):
355 diffopts.git = True
355 diffopts.git = True
356 break
356 break
357 patchf.close()
357 patchf.close()
358 return diffopts
358 return diffopts
359
359
360 def join(self, *p):
360 def join(self, *p):
361 return os.path.join(self.path, *p)
361 return os.path.join(self.path, *p)
362
362
363 def find_series(self, patch):
363 def find_series(self, patch):
364 def matchpatch(l):
364 def matchpatch(l):
365 l = l.split('#', 1)[0]
365 l = l.split('#', 1)[0]
366 return l.strip() == patch
366 return l.strip() == patch
367 for index, l in enumerate(self.full_series):
367 for index, l in enumerate(self.full_series):
368 if matchpatch(l):
368 if matchpatch(l):
369 return index
369 return index
370 return None
370 return None
371
371
372 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
372 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
373
373
374 def parse_series(self):
374 def parse_series(self):
375 self.series = []
375 self.series = []
376 self.series_guards = []
376 self.series_guards = []
377 for l in self.full_series:
377 for l in self.full_series:
378 h = l.find('#')
378 h = l.find('#')
379 if h == -1:
379 if h == -1:
380 patch = l
380 patch = l
381 comment = ''
381 comment = ''
382 elif h == 0:
382 elif h == 0:
383 continue
383 continue
384 else:
384 else:
385 patch = l[:h]
385 patch = l[:h]
386 comment = l[h:]
386 comment = l[h:]
387 patch = patch.strip()
387 patch = patch.strip()
388 if patch:
388 if patch:
389 if patch in self.series:
389 if patch in self.series:
390 raise util.Abort(_('%s appears more than once in %s') %
390 raise util.Abort(_('%s appears more than once in %s') %
391 (patch, self.join(self.series_path)))
391 (patch, self.join(self.series_path)))
392 self.series.append(patch)
392 self.series.append(patch)
393 self.series_guards.append(self.guard_re.findall(comment))
393 self.series_guards.append(self.guard_re.findall(comment))
394
394
395 def check_guard(self, guard):
395 def check_guard(self, guard):
396 if not guard:
396 if not guard:
397 return _('guard cannot be an empty string')
397 return _('guard cannot be an empty string')
398 bad_chars = '# \t\r\n\f'
398 bad_chars = '# \t\r\n\f'
399 first = guard[0]
399 first = guard[0]
400 if first in '-+':
400 if first in '-+':
401 return (_('guard %r starts with invalid character: %r') %
401 return (_('guard %r starts with invalid character: %r') %
402 (guard, first))
402 (guard, first))
403 for c in bad_chars:
403 for c in bad_chars:
404 if c in guard:
404 if c in guard:
405 return _('invalid character in guard %r: %r') % (guard, c)
405 return _('invalid character in guard %r: %r') % (guard, c)
406
406
407 def set_active(self, guards):
407 def set_active(self, guards):
408 for guard in guards:
408 for guard in guards:
409 bad = self.check_guard(guard)
409 bad = self.check_guard(guard)
410 if bad:
410 if bad:
411 raise util.Abort(bad)
411 raise util.Abort(bad)
412 guards = sorted(set(guards))
412 guards = sorted(set(guards))
413 self.ui.debug('active guards: %s\n' % ' '.join(guards))
413 self.ui.debug('active guards: %s\n' % ' '.join(guards))
414 self.active_guards = guards
414 self.active_guards = guards
415 self.guards_dirty = True
415 self.guards_dirty = True
416
416
417 def active(self):
417 def active(self):
418 if self.active_guards is None:
418 if self.active_guards is None:
419 self.active_guards = []
419 self.active_guards = []
420 try:
420 try:
421 guards = self.opener.read(self.guards_path).split()
421 guards = self.opener.read(self.guards_path).split()
422 except IOError, err:
422 except IOError, err:
423 if err.errno != errno.ENOENT:
423 if err.errno != errno.ENOENT:
424 raise
424 raise
425 guards = []
425 guards = []
426 for i, guard in enumerate(guards):
426 for i, guard in enumerate(guards):
427 bad = self.check_guard(guard)
427 bad = self.check_guard(guard)
428 if bad:
428 if bad:
429 self.ui.warn('%s:%d: %s\n' %
429 self.ui.warn('%s:%d: %s\n' %
430 (self.join(self.guards_path), i + 1, bad))
430 (self.join(self.guards_path), i + 1, bad))
431 else:
431 else:
432 self.active_guards.append(guard)
432 self.active_guards.append(guard)
433 return self.active_guards
433 return self.active_guards
434
434
435 def set_guards(self, idx, guards):
435 def set_guards(self, idx, guards):
436 for g in guards:
436 for g in guards:
437 if len(g) < 2:
437 if len(g) < 2:
438 raise util.Abort(_('guard %r too short') % g)
438 raise util.Abort(_('guard %r too short') % g)
439 if g[0] not in '-+':
439 if g[0] not in '-+':
440 raise util.Abort(_('guard %r starts with invalid char') % g)
440 raise util.Abort(_('guard %r starts with invalid char') % g)
441 bad = self.check_guard(g[1:])
441 bad = self.check_guard(g[1:])
442 if bad:
442 if bad:
443 raise util.Abort(bad)
443 raise util.Abort(bad)
444 drop = self.guard_re.sub('', self.full_series[idx])
444 drop = self.guard_re.sub('', self.full_series[idx])
445 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
445 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
446 self.parse_series()
446 self.parse_series()
447 self.series_dirty = True
447 self.series_dirty = True
448
448
449 def pushable(self, idx):
449 def pushable(self, idx):
450 if isinstance(idx, str):
450 if isinstance(idx, str):
451 idx = self.series.index(idx)
451 idx = self.series.index(idx)
452 patchguards = self.series_guards[idx]
452 patchguards = self.series_guards[idx]
453 if not patchguards:
453 if not patchguards:
454 return True, None
454 return True, None
455 guards = self.active()
455 guards = self.active()
456 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
456 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
457 if exactneg:
457 if exactneg:
458 return False, exactneg[0]
458 return False, exactneg[0]
459 pos = [g for g in patchguards if g[0] == '+']
459 pos = [g for g in patchguards if g[0] == '+']
460 exactpos = [g for g in pos if g[1:] in guards]
460 exactpos = [g for g in pos if g[1:] in guards]
461 if pos:
461 if pos:
462 if exactpos:
462 if exactpos:
463 return True, exactpos[0]
463 return True, exactpos[0]
464 return False, pos
464 return False, pos
465 return True, ''
465 return True, ''
466
466
467 def explain_pushable(self, idx, all_patches=False):
467 def explain_pushable(self, idx, all_patches=False):
468 write = all_patches and self.ui.write or self.ui.warn
468 write = all_patches and self.ui.write or self.ui.warn
469 if all_patches or self.ui.verbose:
469 if all_patches or self.ui.verbose:
470 if isinstance(idx, str):
470 if isinstance(idx, str):
471 idx = self.series.index(idx)
471 idx = self.series.index(idx)
472 pushable, why = self.pushable(idx)
472 pushable, why = self.pushable(idx)
473 if all_patches and pushable:
473 if all_patches and pushable:
474 if why is None:
474 if why is None:
475 write(_('allowing %s - no guards in effect\n') %
475 write(_('allowing %s - no guards in effect\n') %
476 self.series[idx])
476 self.series[idx])
477 else:
477 else:
478 if not why:
478 if not why:
479 write(_('allowing %s - no matching negative guards\n') %
479 write(_('allowing %s - no matching negative guards\n') %
480 self.series[idx])
480 self.series[idx])
481 else:
481 else:
482 write(_('allowing %s - guarded by %r\n') %
482 write(_('allowing %s - guarded by %r\n') %
483 (self.series[idx], why))
483 (self.series[idx], why))
484 if not pushable:
484 if not pushable:
485 if why:
485 if why:
486 write(_('skipping %s - guarded by %r\n') %
486 write(_('skipping %s - guarded by %r\n') %
487 (self.series[idx], why))
487 (self.series[idx], why))
488 else:
488 else:
489 write(_('skipping %s - no matching guards\n') %
489 write(_('skipping %s - no matching guards\n') %
490 self.series[idx])
490 self.series[idx])
491
491
492 def save_dirty(self):
492 def save_dirty(self):
493 def write_list(items, path):
493 def write_list(items, path):
494 fp = self.opener(path, 'w')
494 fp = self.opener(path, 'w')
495 for i in items:
495 for i in items:
496 fp.write("%s\n" % i)
496 fp.write("%s\n" % i)
497 fp.close()
497 fp.close()
498 if self.applied_dirty:
498 if self.applied_dirty:
499 write_list(map(str, self.applied), self.status_path)
499 write_list(map(str, self.applied), self.status_path)
500 if self.series_dirty:
500 if self.series_dirty:
501 write_list(self.full_series, self.series_path)
501 write_list(self.full_series, self.series_path)
502 if self.guards_dirty:
502 if self.guards_dirty:
503 write_list(self.active_guards, self.guards_path)
503 write_list(self.active_guards, self.guards_path)
504 if self.added:
504 if self.added:
505 qrepo = self.qrepo()
505 qrepo = self.qrepo()
506 if qrepo:
506 if qrepo:
507 qrepo[None].add(f for f in self.added if f not in qrepo[None])
507 qrepo[None].add(f for f in self.added if f not in qrepo[None])
508 self.added = []
508 self.added = []
509
509
510 def removeundo(self, repo):
510 def removeundo(self, repo):
511 undo = repo.sjoin('undo')
511 undo = repo.sjoin('undo')
512 if not os.path.exists(undo):
512 if not os.path.exists(undo):
513 return
513 return
514 try:
514 try:
515 os.unlink(undo)
515 os.unlink(undo)
516 except OSError, inst:
516 except OSError, inst:
517 self.ui.warn(_('error removing undo: %s\n') % str(inst))
517 self.ui.warn(_('error removing undo: %s\n') % str(inst))
518
518
519 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
519 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
520 fp=None, changes=None, opts={}):
520 fp=None, changes=None, opts={}):
521 stat = opts.get('stat')
521 stat = opts.get('stat')
522 m = scmutil.match(repo, files, opts)
522 m = scmutil.match(repo, files, opts)
523 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
523 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
524 changes, stat, fp)
524 changes, stat, fp)
525
525
526 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
526 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
527 # first try just applying the patch
527 # first try just applying the patch
528 (err, n) = self.apply(repo, [patch], update_status=False,
528 (err, n) = self.apply(repo, [patch], update_status=False,
529 strict=True, merge=rev)
529 strict=True, merge=rev)
530
530
531 if err == 0:
531 if err == 0:
532 return (err, n)
532 return (err, n)
533
533
534 if n is None:
534 if n is None:
535 raise util.Abort(_("apply failed for patch %s") % patch)
535 raise util.Abort(_("apply failed for patch %s") % patch)
536
536
537 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
537 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
538
538
539 # apply failed, strip away that rev and merge.
539 # apply failed, strip away that rev and merge.
540 hg.clean(repo, head)
540 hg.clean(repo, head)
541 self.strip(repo, [n], update=False, backup='strip')
541 self.strip(repo, [n], update=False, backup='strip')
542
542
543 ctx = repo[rev]
543 ctx = repo[rev]
544 ret = hg.merge(repo, rev)
544 ret = hg.merge(repo, rev)
545 if ret:
545 if ret:
546 raise util.Abort(_("update returned %d") % ret)
546 raise util.Abort(_("update returned %d") % ret)
547 n = repo.commit(ctx.description(), ctx.user(), force=True)
547 n = repo.commit(ctx.description(), ctx.user(), force=True)
548 if n is None:
548 if n is None:
549 raise util.Abort(_("repo commit failed"))
549 raise util.Abort(_("repo commit failed"))
550 try:
550 try:
551 ph = patchheader(mergeq.join(patch), self.plainmode)
551 ph = patchheader(mergeq.join(patch), self.plainmode)
552 except:
552 except:
553 raise util.Abort(_("unable to read %s") % patch)
553 raise util.Abort(_("unable to read %s") % patch)
554
554
555 diffopts = self.patchopts(diffopts, patch)
555 diffopts = self.patchopts(diffopts, patch)
556 patchf = self.opener(patch, "w")
556 patchf = self.opener(patch, "w")
557 comments = str(ph)
557 comments = str(ph)
558 if comments:
558 if comments:
559 patchf.write(comments)
559 patchf.write(comments)
560 self.printdiff(repo, diffopts, head, n, fp=patchf)
560 self.printdiff(repo, diffopts, head, n, fp=patchf)
561 patchf.close()
561 patchf.close()
562 self.removeundo(repo)
562 self.removeundo(repo)
563 return (0, n)
563 return (0, n)
564
564
565 def qparents(self, repo, rev=None):
565 def qparents(self, repo, rev=None):
566 if rev is None:
566 if rev is None:
567 (p1, p2) = repo.dirstate.parents()
567 (p1, p2) = repo.dirstate.parents()
568 if p2 == nullid:
568 if p2 == nullid:
569 return p1
569 return p1
570 if not self.applied:
570 if not self.applied:
571 return None
571 return None
572 return self.applied[-1].node
572 return self.applied[-1].node
573 p1, p2 = repo.changelog.parents(rev)
573 p1, p2 = repo.changelog.parents(rev)
574 if p2 != nullid and p2 in [x.node for x in self.applied]:
574 if p2 != nullid and p2 in [x.node for x in self.applied]:
575 return p2
575 return p2
576 return p1
576 return p1
577
577
578 def mergepatch(self, repo, mergeq, series, diffopts):
578 def mergepatch(self, repo, mergeq, series, diffopts):
579 if not self.applied:
579 if not self.applied:
580 # each of the patches merged in will have two parents. This
580 # each of the patches merged in will have two parents. This
581 # can confuse the qrefresh, qdiff, and strip code because it
581 # can confuse the qrefresh, qdiff, and strip code because it
582 # needs to know which parent is actually in the patch queue.
582 # needs to know which parent is actually in the patch queue.
583 # so, we insert a merge marker with only one parent. This way
583 # so, we insert a merge marker with only one parent. This way
584 # the first patch in the queue is never a merge patch
584 # the first patch in the queue is never a merge patch
585 #
585 #
586 pname = ".hg.patches.merge.marker"
586 pname = ".hg.patches.merge.marker"
587 n = repo.commit('[mq]: merge marker', force=True)
587 n = repo.commit('[mq]: merge marker', force=True)
588 self.removeundo(repo)
588 self.removeundo(repo)
589 self.applied.append(statusentry(n, pname))
589 self.applied.append(statusentry(n, pname))
590 self.applied_dirty = 1
590 self.applied_dirty = 1
591
591
592 head = self.qparents(repo)
592 head = self.qparents(repo)
593
593
594 for patch in series:
594 for patch in series:
595 patch = mergeq.lookup(patch, strict=True)
595 patch = mergeq.lookup(patch, strict=True)
596 if not patch:
596 if not patch:
597 self.ui.warn(_("patch %s does not exist\n") % patch)
597 self.ui.warn(_("patch %s does not exist\n") % patch)
598 return (1, None)
598 return (1, None)
599 pushable, reason = self.pushable(patch)
599 pushable, reason = self.pushable(patch)
600 if not pushable:
600 if not pushable:
601 self.explain_pushable(patch, all_patches=True)
601 self.explain_pushable(patch, all_patches=True)
602 continue
602 continue
603 info = mergeq.isapplied(patch)
603 info = mergeq.isapplied(patch)
604 if not info:
604 if not info:
605 self.ui.warn(_("patch %s is not applied\n") % patch)
605 self.ui.warn(_("patch %s is not applied\n") % patch)
606 return (1, None)
606 return (1, None)
607 rev = info[1]
607 rev = info[1]
608 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
608 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
609 if head:
609 if head:
610 self.applied.append(statusentry(head, patch))
610 self.applied.append(statusentry(head, patch))
611 self.applied_dirty = 1
611 self.applied_dirty = 1
612 if err:
612 if err:
613 return (err, head)
613 return (err, head)
614 self.save_dirty()
614 self.save_dirty()
615 return (0, head)
615 return (0, head)
616
616
617 def patch(self, repo, patchfile):
617 def patch(self, repo, patchfile):
618 '''Apply patchfile to the working directory.
618 '''Apply patchfile to the working directory.
619 patchfile: name of patch file'''
619 patchfile: name of patch file'''
620 files = {}
620 files = {}
621 try:
621 try:
622 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
622 fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
623 cwd=repo.root, files=files, eolmode=None)
623 files=files, eolmode=None)
624 return (True, list(files), fuzz)
624 return (True, list(files), fuzz)
625 except Exception, inst:
625 except Exception, inst:
626 self.ui.note(str(inst) + '\n')
626 self.ui.note(str(inst) + '\n')
627 if not self.ui.verbose:
627 if not self.ui.verbose:
628 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
628 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
629 return (False, list(files), False)
629 return (False, list(files), False)
630
630
631 def apply(self, repo, series, list=False, update_status=True,
631 def apply(self, repo, series, list=False, update_status=True,
632 strict=False, patchdir=None, merge=None, all_files=None):
632 strict=False, patchdir=None, merge=None, all_files=None):
633 wlock = lock = tr = None
633 wlock = lock = tr = None
634 try:
634 try:
635 wlock = repo.wlock()
635 wlock = repo.wlock()
636 lock = repo.lock()
636 lock = repo.lock()
637 tr = repo.transaction("qpush")
637 tr = repo.transaction("qpush")
638 try:
638 try:
639 ret = self._apply(repo, series, list, update_status,
639 ret = self._apply(repo, series, list, update_status,
640 strict, patchdir, merge, all_files=all_files)
640 strict, patchdir, merge, all_files=all_files)
641 tr.close()
641 tr.close()
642 self.save_dirty()
642 self.save_dirty()
643 return ret
643 return ret
644 except:
644 except:
645 try:
645 try:
646 tr.abort()
646 tr.abort()
647 finally:
647 finally:
648 repo.invalidate()
648 repo.invalidate()
649 repo.dirstate.invalidate()
649 repo.dirstate.invalidate()
650 raise
650 raise
651 finally:
651 finally:
652 release(tr, lock, wlock)
652 release(tr, lock, wlock)
653 self.removeundo(repo)
653 self.removeundo(repo)
654
654
655 def _apply(self, repo, series, list=False, update_status=True,
655 def _apply(self, repo, series, list=False, update_status=True,
656 strict=False, patchdir=None, merge=None, all_files=None):
656 strict=False, patchdir=None, merge=None, all_files=None):
657 '''returns (error, hash)
657 '''returns (error, hash)
658 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
658 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
659 # TODO unify with commands.py
659 # TODO unify with commands.py
660 if not patchdir:
660 if not patchdir:
661 patchdir = self.path
661 patchdir = self.path
662 err = 0
662 err = 0
663 n = None
663 n = None
664 for patchname in series:
664 for patchname in series:
665 pushable, reason = self.pushable(patchname)
665 pushable, reason = self.pushable(patchname)
666 if not pushable:
666 if not pushable:
667 self.explain_pushable(patchname, all_patches=True)
667 self.explain_pushable(patchname, all_patches=True)
668 continue
668 continue
669 self.ui.status(_("applying %s\n") % patchname)
669 self.ui.status(_("applying %s\n") % patchname)
670 pf = os.path.join(patchdir, patchname)
670 pf = os.path.join(patchdir, patchname)
671
671
672 try:
672 try:
673 ph = patchheader(self.join(patchname), self.plainmode)
673 ph = patchheader(self.join(patchname), self.plainmode)
674 except IOError:
674 except IOError:
675 self.ui.warn(_("unable to read %s\n") % patchname)
675 self.ui.warn(_("unable to read %s\n") % patchname)
676 err = 1
676 err = 1
677 break
677 break
678
678
679 message = ph.message
679 message = ph.message
680 if not message:
680 if not message:
681 # The commit message should not be translated
681 # The commit message should not be translated
682 message = "imported patch %s\n" % patchname
682 message = "imported patch %s\n" % patchname
683 else:
683 else:
684 if list:
684 if list:
685 # The commit message should not be translated
685 # The commit message should not be translated
686 message.append("\nimported patch %s" % patchname)
686 message.append("\nimported patch %s" % patchname)
687 message = '\n'.join(message)
687 message = '\n'.join(message)
688
688
689 if ph.haspatch:
689 if ph.haspatch:
690 (patcherr, files, fuzz) = self.patch(repo, pf)
690 (patcherr, files, fuzz) = self.patch(repo, pf)
691 if all_files is not None:
691 if all_files is not None:
692 all_files.update(files)
692 all_files.update(files)
693 patcherr = not patcherr
693 patcherr = not patcherr
694 else:
694 else:
695 self.ui.warn(_("patch %s is empty\n") % patchname)
695 self.ui.warn(_("patch %s is empty\n") % patchname)
696 patcherr, files, fuzz = 0, [], 0
696 patcherr, files, fuzz = 0, [], 0
697
697
698 if merge and files:
698 if merge and files:
699 # Mark as removed/merged and update dirstate parent info
699 # Mark as removed/merged and update dirstate parent info
700 removed = []
700 removed = []
701 merged = []
701 merged = []
702 for f in files:
702 for f in files:
703 if os.path.lexists(repo.wjoin(f)):
703 if os.path.lexists(repo.wjoin(f)):
704 merged.append(f)
704 merged.append(f)
705 else:
705 else:
706 removed.append(f)
706 removed.append(f)
707 for f in removed:
707 for f in removed:
708 repo.dirstate.remove(f)
708 repo.dirstate.remove(f)
709 for f in merged:
709 for f in merged:
710 repo.dirstate.merge(f)
710 repo.dirstate.merge(f)
711 p1, p2 = repo.dirstate.parents()
711 p1, p2 = repo.dirstate.parents()
712 repo.dirstate.setparents(p1, merge)
712 repo.dirstate.setparents(p1, merge)
713
713
714 match = scmutil.matchfiles(repo, files or [])
714 match = scmutil.matchfiles(repo, files or [])
715 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
715 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
716
716
717 if n is None:
717 if n is None:
718 raise util.Abort(_("repository commit failed"))
718 raise util.Abort(_("repository commit failed"))
719
719
720 if update_status:
720 if update_status:
721 self.applied.append(statusentry(n, patchname))
721 self.applied.append(statusentry(n, patchname))
722
722
723 if patcherr:
723 if patcherr:
724 self.ui.warn(_("patch failed, rejects left in working dir\n"))
724 self.ui.warn(_("patch failed, rejects left in working dir\n"))
725 err = 2
725 err = 2
726 break
726 break
727
727
728 if fuzz and strict:
728 if fuzz and strict:
729 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
729 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
730 err = 3
730 err = 3
731 break
731 break
732 return (err, n)
732 return (err, n)
733
733
734 def _cleanup(self, patches, numrevs, keep=False):
734 def _cleanup(self, patches, numrevs, keep=False):
735 if not keep:
735 if not keep:
736 r = self.qrepo()
736 r = self.qrepo()
737 if r:
737 if r:
738 r[None].remove(patches, True)
738 r[None].remove(patches, True)
739 else:
739 else:
740 for p in patches:
740 for p in patches:
741 os.unlink(self.join(p))
741 os.unlink(self.join(p))
742
742
743 if numrevs:
743 if numrevs:
744 qfinished = self.applied[:numrevs]
744 qfinished = self.applied[:numrevs]
745 del self.applied[:numrevs]
745 del self.applied[:numrevs]
746 self.applied_dirty = 1
746 self.applied_dirty = 1
747
747
748 unknown = []
748 unknown = []
749
749
750 for (i, p) in sorted([(self.find_series(p), p) for p in patches],
750 for (i, p) in sorted([(self.find_series(p), p) for p in patches],
751 reverse=True):
751 reverse=True):
752 if i is not None:
752 if i is not None:
753 del self.full_series[i]
753 del self.full_series[i]
754 else:
754 else:
755 unknown.append(p)
755 unknown.append(p)
756
756
757 if unknown:
757 if unknown:
758 if numrevs:
758 if numrevs:
759 rev = dict((entry.name, entry.node) for entry in qfinished)
759 rev = dict((entry.name, entry.node) for entry in qfinished)
760 for p in unknown:
760 for p in unknown:
761 msg = _('revision %s refers to unknown patches: %s\n')
761 msg = _('revision %s refers to unknown patches: %s\n')
762 self.ui.warn(msg % (short(rev[p]), p))
762 self.ui.warn(msg % (short(rev[p]), p))
763 else:
763 else:
764 msg = _('unknown patches: %s\n')
764 msg = _('unknown patches: %s\n')
765 raise util.Abort(''.join(msg % p for p in unknown))
765 raise util.Abort(''.join(msg % p for p in unknown))
766
766
767 self.parse_series()
767 self.parse_series()
768 self.series_dirty = 1
768 self.series_dirty = 1
769
769
770 def _revpatches(self, repo, revs):
770 def _revpatches(self, repo, revs):
771 firstrev = repo[self.applied[0].node].rev()
771 firstrev = repo[self.applied[0].node].rev()
772 patches = []
772 patches = []
773 for i, rev in enumerate(revs):
773 for i, rev in enumerate(revs):
774
774
775 if rev < firstrev:
775 if rev < firstrev:
776 raise util.Abort(_('revision %d is not managed') % rev)
776 raise util.Abort(_('revision %d is not managed') % rev)
777
777
778 ctx = repo[rev]
778 ctx = repo[rev]
779 base = self.applied[i].node
779 base = self.applied[i].node
780 if ctx.node() != base:
780 if ctx.node() != base:
781 msg = _('cannot delete revision %d above applied patches')
781 msg = _('cannot delete revision %d above applied patches')
782 raise util.Abort(msg % rev)
782 raise util.Abort(msg % rev)
783
783
784 patch = self.applied[i].name
784 patch = self.applied[i].name
785 for fmt in ('[mq]: %s', 'imported patch %s'):
785 for fmt in ('[mq]: %s', 'imported patch %s'):
786 if ctx.description() == fmt % patch:
786 if ctx.description() == fmt % patch:
787 msg = _('patch %s finalized without changeset message\n')
787 msg = _('patch %s finalized without changeset message\n')
788 repo.ui.status(msg % patch)
788 repo.ui.status(msg % patch)
789 break
789 break
790
790
791 patches.append(patch)
791 patches.append(patch)
792 return patches
792 return patches
793
793
794 def finish(self, repo, revs):
794 def finish(self, repo, revs):
795 patches = self._revpatches(repo, sorted(revs))
795 patches = self._revpatches(repo, sorted(revs))
796 self._cleanup(patches, len(patches))
796 self._cleanup(patches, len(patches))
797
797
798 def delete(self, repo, patches, opts):
798 def delete(self, repo, patches, opts):
799 if not patches and not opts.get('rev'):
799 if not patches and not opts.get('rev'):
800 raise util.Abort(_('qdelete requires at least one revision or '
800 raise util.Abort(_('qdelete requires at least one revision or '
801 'patch name'))
801 'patch name'))
802
802
803 realpatches = []
803 realpatches = []
804 for patch in patches:
804 for patch in patches:
805 patch = self.lookup(patch, strict=True)
805 patch = self.lookup(patch, strict=True)
806 info = self.isapplied(patch)
806 info = self.isapplied(patch)
807 if info:
807 if info:
808 raise util.Abort(_("cannot delete applied patch %s") % patch)
808 raise util.Abort(_("cannot delete applied patch %s") % patch)
809 if patch not in self.series:
809 if patch not in self.series:
810 raise util.Abort(_("patch %s not in series file") % patch)
810 raise util.Abort(_("patch %s not in series file") % patch)
811 if patch not in realpatches:
811 if patch not in realpatches:
812 realpatches.append(patch)
812 realpatches.append(patch)
813
813
814 numrevs = 0
814 numrevs = 0
815 if opts.get('rev'):
815 if opts.get('rev'):
816 if not self.applied:
816 if not self.applied:
817 raise util.Abort(_('no patches applied'))
817 raise util.Abort(_('no patches applied'))
818 revs = scmutil.revrange(repo, opts.get('rev'))
818 revs = scmutil.revrange(repo, opts.get('rev'))
819 if len(revs) > 1 and revs[0] > revs[1]:
819 if len(revs) > 1 and revs[0] > revs[1]:
820 revs.reverse()
820 revs.reverse()
821 revpatches = self._revpatches(repo, revs)
821 revpatches = self._revpatches(repo, revs)
822 realpatches += revpatches
822 realpatches += revpatches
823 numrevs = len(revpatches)
823 numrevs = len(revpatches)
824
824
825 self._cleanup(realpatches, numrevs, opts.get('keep'))
825 self._cleanup(realpatches, numrevs, opts.get('keep'))
826
826
827 def check_toppatch(self, repo):
827 def check_toppatch(self, repo):
828 if self.applied:
828 if self.applied:
829 top = self.applied[-1].node
829 top = self.applied[-1].node
830 patch = self.applied[-1].name
830 patch = self.applied[-1].name
831 pp = repo.dirstate.parents()
831 pp = repo.dirstate.parents()
832 if top not in pp:
832 if top not in pp:
833 raise util.Abort(_("working directory revision is not qtip"))
833 raise util.Abort(_("working directory revision is not qtip"))
834 return top, patch
834 return top, patch
835 return None, None
835 return None, None
836
836
837 def check_substate(self, repo):
837 def check_substate(self, repo):
838 '''return list of subrepos at a different revision than substate.
838 '''return list of subrepos at a different revision than substate.
839 Abort if any subrepos have uncommitted changes.'''
839 Abort if any subrepos have uncommitted changes.'''
840 inclsubs = []
840 inclsubs = []
841 wctx = repo[None]
841 wctx = repo[None]
842 for s in wctx.substate:
842 for s in wctx.substate:
843 if wctx.sub(s).dirty(True):
843 if wctx.sub(s).dirty(True):
844 raise util.Abort(
844 raise util.Abort(
845 _("uncommitted changes in subrepository %s") % s)
845 _("uncommitted changes in subrepository %s") % s)
846 elif wctx.sub(s).dirty():
846 elif wctx.sub(s).dirty():
847 inclsubs.append(s)
847 inclsubs.append(s)
848 return inclsubs
848 return inclsubs
849
849
850 def localchangesfound(self, refresh=True):
850 def localchangesfound(self, refresh=True):
851 if refresh:
851 if refresh:
852 raise util.Abort(_("local changes found, refresh first"))
852 raise util.Abort(_("local changes found, refresh first"))
853 else:
853 else:
854 raise util.Abort(_("local changes found"))
854 raise util.Abort(_("local changes found"))
855
855
856 def check_localchanges(self, repo, force=False, refresh=True):
856 def check_localchanges(self, repo, force=False, refresh=True):
857 m, a, r, d = repo.status()[:4]
857 m, a, r, d = repo.status()[:4]
858 if (m or a or r or d) and not force:
858 if (m or a or r or d) and not force:
859 self.localchangesfound(refresh)
859 self.localchangesfound(refresh)
860 return m, a, r, d
860 return m, a, r, d
861
861
862 _reserved = ('series', 'status', 'guards', '.', '..')
862 _reserved = ('series', 'status', 'guards', '.', '..')
863 def check_reserved_name(self, name):
863 def check_reserved_name(self, name):
864 if name in self._reserved:
864 if name in self._reserved:
865 raise util.Abort(_('"%s" cannot be used as the name of a patch')
865 raise util.Abort(_('"%s" cannot be used as the name of a patch')
866 % name)
866 % name)
867 for prefix in ('.hg', '.mq'):
867 for prefix in ('.hg', '.mq'):
868 if name.startswith(prefix):
868 if name.startswith(prefix):
869 raise util.Abort(_('patch name cannot begin with "%s"')
869 raise util.Abort(_('patch name cannot begin with "%s"')
870 % prefix)
870 % prefix)
871 for c in ('#', ':'):
871 for c in ('#', ':'):
872 if c in name:
872 if c in name:
873 raise util.Abort(_('"%s" cannot be used in the name of a patch')
873 raise util.Abort(_('"%s" cannot be used in the name of a patch')
874 % c)
874 % c)
875
875
876
876
877 def new(self, repo, patchfn, *pats, **opts):
877 def new(self, repo, patchfn, *pats, **opts):
878 """options:
878 """options:
879 msg: a string or a no-argument function returning a string
879 msg: a string or a no-argument function returning a string
880 """
880 """
881 msg = opts.get('msg')
881 msg = opts.get('msg')
882 user = opts.get('user')
882 user = opts.get('user')
883 date = opts.get('date')
883 date = opts.get('date')
884 if date:
884 if date:
885 date = util.parsedate(date)
885 date = util.parsedate(date)
886 diffopts = self.diffopts({'git': opts.get('git')})
886 diffopts = self.diffopts({'git': opts.get('git')})
887 self.check_reserved_name(patchfn)
887 self.check_reserved_name(patchfn)
888 if os.path.exists(self.join(patchfn)):
888 if os.path.exists(self.join(patchfn)):
889 if os.path.isdir(self.join(patchfn)):
889 if os.path.isdir(self.join(patchfn)):
890 raise util.Abort(_('"%s" already exists as a directory')
890 raise util.Abort(_('"%s" already exists as a directory')
891 % patchfn)
891 % patchfn)
892 else:
892 else:
893 raise util.Abort(_('patch "%s" already exists') % patchfn)
893 raise util.Abort(_('patch "%s" already exists') % patchfn)
894
894
895 inclsubs = self.check_substate(repo)
895 inclsubs = self.check_substate(repo)
896 if inclsubs:
896 if inclsubs:
897 inclsubs.append('.hgsubstate')
897 inclsubs.append('.hgsubstate')
898 if opts.get('include') or opts.get('exclude') or pats:
898 if opts.get('include') or opts.get('exclude') or pats:
899 if inclsubs:
899 if inclsubs:
900 pats = list(pats or []) + inclsubs
900 pats = list(pats or []) + inclsubs
901 match = scmutil.match(repo, pats, opts)
901 match = scmutil.match(repo, pats, opts)
902 # detect missing files in pats
902 # detect missing files in pats
903 def badfn(f, msg):
903 def badfn(f, msg):
904 if f != '.hgsubstate': # .hgsubstate is auto-created
904 if f != '.hgsubstate': # .hgsubstate is auto-created
905 raise util.Abort('%s: %s' % (f, msg))
905 raise util.Abort('%s: %s' % (f, msg))
906 match.bad = badfn
906 match.bad = badfn
907 m, a, r, d = repo.status(match=match)[:4]
907 m, a, r, d = repo.status(match=match)[:4]
908 else:
908 else:
909 m, a, r, d = self.check_localchanges(repo, force=True)
909 m, a, r, d = self.check_localchanges(repo, force=True)
910 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
910 match = scmutil.matchfiles(repo, m + a + r + inclsubs)
911 if len(repo[None].parents()) > 1:
911 if len(repo[None].parents()) > 1:
912 raise util.Abort(_('cannot manage merge changesets'))
912 raise util.Abort(_('cannot manage merge changesets'))
913 commitfiles = m + a + r
913 commitfiles = m + a + r
914 self.check_toppatch(repo)
914 self.check_toppatch(repo)
915 insert = self.full_series_end()
915 insert = self.full_series_end()
916 wlock = repo.wlock()
916 wlock = repo.wlock()
917 try:
917 try:
918 try:
918 try:
919 # if patch file write fails, abort early
919 # if patch file write fails, abort early
920 p = self.opener(patchfn, "w")
920 p = self.opener(patchfn, "w")
921 except IOError, e:
921 except IOError, e:
922 raise util.Abort(_('cannot write patch "%s": %s')
922 raise util.Abort(_('cannot write patch "%s": %s')
923 % (patchfn, e.strerror))
923 % (patchfn, e.strerror))
924 try:
924 try:
925 if self.plainmode:
925 if self.plainmode:
926 if user:
926 if user:
927 p.write("From: " + user + "\n")
927 p.write("From: " + user + "\n")
928 if not date:
928 if not date:
929 p.write("\n")
929 p.write("\n")
930 if date:
930 if date:
931 p.write("Date: %d %d\n\n" % date)
931 p.write("Date: %d %d\n\n" % date)
932 else:
932 else:
933 p.write("# HG changeset patch\n")
933 p.write("# HG changeset patch\n")
934 p.write("# Parent "
934 p.write("# Parent "
935 + hex(repo[None].p1().node()) + "\n")
935 + hex(repo[None].p1().node()) + "\n")
936 if user:
936 if user:
937 p.write("# User " + user + "\n")
937 p.write("# User " + user + "\n")
938 if date:
938 if date:
939 p.write("# Date %s %s\n\n" % date)
939 p.write("# Date %s %s\n\n" % date)
940 if hasattr(msg, '__call__'):
940 if hasattr(msg, '__call__'):
941 msg = msg()
941 msg = msg()
942 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
942 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
943 n = repo.commit(commitmsg, user, date, match=match, force=True)
943 n = repo.commit(commitmsg, user, date, match=match, force=True)
944 if n is None:
944 if n is None:
945 raise util.Abort(_("repo commit failed"))
945 raise util.Abort(_("repo commit failed"))
946 try:
946 try:
947 self.full_series[insert:insert] = [patchfn]
947 self.full_series[insert:insert] = [patchfn]
948 self.applied.append(statusentry(n, patchfn))
948 self.applied.append(statusentry(n, patchfn))
949 self.parse_series()
949 self.parse_series()
950 self.series_dirty = 1
950 self.series_dirty = 1
951 self.applied_dirty = 1
951 self.applied_dirty = 1
952 if msg:
952 if msg:
953 msg = msg + "\n\n"
953 msg = msg + "\n\n"
954 p.write(msg)
954 p.write(msg)
955 if commitfiles:
955 if commitfiles:
956 parent = self.qparents(repo, n)
956 parent = self.qparents(repo, n)
957 chunks = patchmod.diff(repo, node1=parent, node2=n,
957 chunks = patchmod.diff(repo, node1=parent, node2=n,
958 match=match, opts=diffopts)
958 match=match, opts=diffopts)
959 for chunk in chunks:
959 for chunk in chunks:
960 p.write(chunk)
960 p.write(chunk)
961 p.close()
961 p.close()
962 wlock.release()
962 wlock.release()
963 wlock = None
963 wlock = None
964 r = self.qrepo()
964 r = self.qrepo()
965 if r:
965 if r:
966 r[None].add([patchfn])
966 r[None].add([patchfn])
967 except:
967 except:
968 repo.rollback()
968 repo.rollback()
969 raise
969 raise
970 except Exception:
970 except Exception:
971 patchpath = self.join(patchfn)
971 patchpath = self.join(patchfn)
972 try:
972 try:
973 os.unlink(patchpath)
973 os.unlink(patchpath)
974 except:
974 except:
975 self.ui.warn(_('error unlinking %s\n') % patchpath)
975 self.ui.warn(_('error unlinking %s\n') % patchpath)
976 raise
976 raise
977 self.removeundo(repo)
977 self.removeundo(repo)
978 finally:
978 finally:
979 release(wlock)
979 release(wlock)
980
980
981 def strip(self, repo, revs, update=True, backup="all", force=None):
981 def strip(self, repo, revs, update=True, backup="all", force=None):
982 wlock = lock = None
982 wlock = lock = None
983 try:
983 try:
984 wlock = repo.wlock()
984 wlock = repo.wlock()
985 lock = repo.lock()
985 lock = repo.lock()
986
986
987 if update:
987 if update:
988 self.check_localchanges(repo, force=force, refresh=False)
988 self.check_localchanges(repo, force=force, refresh=False)
989 urev = self.qparents(repo, revs[0])
989 urev = self.qparents(repo, revs[0])
990 hg.clean(repo, urev)
990 hg.clean(repo, urev)
991 repo.dirstate.write()
991 repo.dirstate.write()
992
992
993 self.removeundo(repo)
993 self.removeundo(repo)
994 for rev in revs:
994 for rev in revs:
995 repair.strip(self.ui, repo, rev, backup)
995 repair.strip(self.ui, repo, rev, backup)
996 # strip may have unbundled a set of backed up revisions after
996 # strip may have unbundled a set of backed up revisions after
997 # the actual strip
997 # the actual strip
998 self.removeundo(repo)
998 self.removeundo(repo)
999 finally:
999 finally:
1000 release(lock, wlock)
1000 release(lock, wlock)
1001
1001
1002 def isapplied(self, patch):
1002 def isapplied(self, patch):
1003 """returns (index, rev, patch)"""
1003 """returns (index, rev, patch)"""
1004 for i, a in enumerate(self.applied):
1004 for i, a in enumerate(self.applied):
1005 if a.name == patch:
1005 if a.name == patch:
1006 return (i, a.node, a.name)
1006 return (i, a.node, a.name)
1007 return None
1007 return None
1008
1008
1009 # if the exact patch name does not exist, we try a few
1009 # if the exact patch name does not exist, we try a few
1010 # variations. If strict is passed, we try only #1
1010 # variations. If strict is passed, we try only #1
1011 #
1011 #
1012 # 1) a number to indicate an offset in the series file
1012 # 1) a number to indicate an offset in the series file
1013 # 2) a unique substring of the patch name was given
1013 # 2) a unique substring of the patch name was given
1014 # 3) patchname[-+]num to indicate an offset in the series file
1014 # 3) patchname[-+]num to indicate an offset in the series file
1015 def lookup(self, patch, strict=False):
1015 def lookup(self, patch, strict=False):
1016 patch = patch and str(patch)
1016 patch = patch and str(patch)
1017
1017
1018 def partial_name(s):
1018 def partial_name(s):
1019 if s in self.series:
1019 if s in self.series:
1020 return s
1020 return s
1021 matches = [x for x in self.series if s in x]
1021 matches = [x for x in self.series if s in x]
1022 if len(matches) > 1:
1022 if len(matches) > 1:
1023 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1023 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
1024 for m in matches:
1024 for m in matches:
1025 self.ui.warn(' %s\n' % m)
1025 self.ui.warn(' %s\n' % m)
1026 return None
1026 return None
1027 if matches:
1027 if matches:
1028 return matches[0]
1028 return matches[0]
1029 if self.series and self.applied:
1029 if self.series and self.applied:
1030 if s == 'qtip':
1030 if s == 'qtip':
1031 return self.series[self.series_end(True)-1]
1031 return self.series[self.series_end(True)-1]
1032 if s == 'qbase':
1032 if s == 'qbase':
1033 return self.series[0]
1033 return self.series[0]
1034 return None
1034 return None
1035
1035
1036 if patch is None:
1036 if patch is None:
1037 return None
1037 return None
1038 if patch in self.series:
1038 if patch in self.series:
1039 return patch
1039 return patch
1040
1040
1041 if not os.path.isfile(self.join(patch)):
1041 if not os.path.isfile(self.join(patch)):
1042 try:
1042 try:
1043 sno = int(patch)
1043 sno = int(patch)
1044 except (ValueError, OverflowError):
1044 except (ValueError, OverflowError):
1045 pass
1045 pass
1046 else:
1046 else:
1047 if -len(self.series) <= sno < len(self.series):
1047 if -len(self.series) <= sno < len(self.series):
1048 return self.series[sno]
1048 return self.series[sno]
1049
1049
1050 if not strict:
1050 if not strict:
1051 res = partial_name(patch)
1051 res = partial_name(patch)
1052 if res:
1052 if res:
1053 return res
1053 return res
1054 minus = patch.rfind('-')
1054 minus = patch.rfind('-')
1055 if minus >= 0:
1055 if minus >= 0:
1056 res = partial_name(patch[:minus])
1056 res = partial_name(patch[:minus])
1057 if res:
1057 if res:
1058 i = self.series.index(res)
1058 i = self.series.index(res)
1059 try:
1059 try:
1060 off = int(patch[minus + 1:] or 1)
1060 off = int(patch[minus + 1:] or 1)
1061 except (ValueError, OverflowError):
1061 except (ValueError, OverflowError):
1062 pass
1062 pass
1063 else:
1063 else:
1064 if i - off >= 0:
1064 if i - off >= 0:
1065 return self.series[i - off]
1065 return self.series[i - off]
1066 plus = patch.rfind('+')
1066 plus = patch.rfind('+')
1067 if plus >= 0:
1067 if plus >= 0:
1068 res = partial_name(patch[:plus])
1068 res = partial_name(patch[:plus])
1069 if res:
1069 if res:
1070 i = self.series.index(res)
1070 i = self.series.index(res)
1071 try:
1071 try:
1072 off = int(patch[plus + 1:] or 1)
1072 off = int(patch[plus + 1:] or 1)
1073 except (ValueError, OverflowError):
1073 except (ValueError, OverflowError):
1074 pass
1074 pass
1075 else:
1075 else:
1076 if i + off < len(self.series):
1076 if i + off < len(self.series):
1077 return self.series[i + off]
1077 return self.series[i + off]
1078 raise util.Abort(_("patch %s not in series") % patch)
1078 raise util.Abort(_("patch %s not in series") % patch)
1079
1079
1080 def push(self, repo, patch=None, force=False, list=False,
1080 def push(self, repo, patch=None, force=False, list=False,
1081 mergeq=None, all=False, move=False, exact=False):
1081 mergeq=None, all=False, move=False, exact=False):
1082 diffopts = self.diffopts()
1082 diffopts = self.diffopts()
1083 wlock = repo.wlock()
1083 wlock = repo.wlock()
1084 try:
1084 try:
1085 heads = []
1085 heads = []
1086 for b, ls in repo.branchmap().iteritems():
1086 for b, ls in repo.branchmap().iteritems():
1087 heads += ls
1087 heads += ls
1088 if not heads:
1088 if not heads:
1089 heads = [nullid]
1089 heads = [nullid]
1090 if repo.dirstate.p1() not in heads and not exact:
1090 if repo.dirstate.p1() not in heads and not exact:
1091 self.ui.status(_("(working directory not at a head)\n"))
1091 self.ui.status(_("(working directory not at a head)\n"))
1092
1092
1093 if not self.series:
1093 if not self.series:
1094 self.ui.warn(_('no patches in series\n'))
1094 self.ui.warn(_('no patches in series\n'))
1095 return 0
1095 return 0
1096
1096
1097 patch = self.lookup(patch)
1097 patch = self.lookup(patch)
1098 # Suppose our series file is: A B C and the current 'top'
1098 # Suppose our series file is: A B C and the current 'top'
1099 # patch is B. qpush C should be performed (moving forward)
1099 # patch is B. qpush C should be performed (moving forward)
1100 # qpush B is a NOP (no change) qpush A is an error (can't
1100 # qpush B is a NOP (no change) qpush A is an error (can't
1101 # go backwards with qpush)
1101 # go backwards with qpush)
1102 if patch:
1102 if patch:
1103 info = self.isapplied(patch)
1103 info = self.isapplied(patch)
1104 if info and info[0] >= len(self.applied) - 1:
1104 if info and info[0] >= len(self.applied) - 1:
1105 self.ui.warn(
1105 self.ui.warn(
1106 _('qpush: %s is already at the top\n') % patch)
1106 _('qpush: %s is already at the top\n') % patch)
1107 return 0
1107 return 0
1108
1108
1109 pushable, reason = self.pushable(patch)
1109 pushable, reason = self.pushable(patch)
1110 if pushable:
1110 if pushable:
1111 if self.series.index(patch) < self.series_end():
1111 if self.series.index(patch) < self.series_end():
1112 raise util.Abort(
1112 raise util.Abort(
1113 _("cannot push to a previous patch: %s") % patch)
1113 _("cannot push to a previous patch: %s") % patch)
1114 else:
1114 else:
1115 if reason:
1115 if reason:
1116 reason = _('guarded by %r') % reason
1116 reason = _('guarded by %r') % reason
1117 else:
1117 else:
1118 reason = _('no matching guards')
1118 reason = _('no matching guards')
1119 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1119 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1120 return 1
1120 return 1
1121 elif all:
1121 elif all:
1122 patch = self.series[-1]
1122 patch = self.series[-1]
1123 if self.isapplied(patch):
1123 if self.isapplied(patch):
1124 self.ui.warn(_('all patches are currently applied\n'))
1124 self.ui.warn(_('all patches are currently applied\n'))
1125 return 0
1125 return 0
1126
1126
1127 # Following the above example, starting at 'top' of B:
1127 # Following the above example, starting at 'top' of B:
1128 # qpush should be performed (pushes C), but a subsequent
1128 # qpush should be performed (pushes C), but a subsequent
1129 # qpush without an argument is an error (nothing to
1129 # qpush without an argument is an error (nothing to
1130 # apply). This allows a loop of "...while hg qpush..." to
1130 # apply). This allows a loop of "...while hg qpush..." to
1131 # work as it detects an error when done
1131 # work as it detects an error when done
1132 start = self.series_end()
1132 start = self.series_end()
1133 if start == len(self.series):
1133 if start == len(self.series):
1134 self.ui.warn(_('patch series already fully applied\n'))
1134 self.ui.warn(_('patch series already fully applied\n'))
1135 return 1
1135 return 1
1136
1136
1137 if exact:
1137 if exact:
1138 if move:
1138 if move:
1139 raise util.Abort(_("cannot use --exact and --move together"))
1139 raise util.Abort(_("cannot use --exact and --move together"))
1140 if self.applied:
1140 if self.applied:
1141 raise util.Abort(_("cannot push --exact with applied patches"))
1141 raise util.Abort(_("cannot push --exact with applied patches"))
1142 root = self.series[start]
1142 root = self.series[start]
1143 target = patchheader(self.join(root), self.plainmode).parent
1143 target = patchheader(self.join(root), self.plainmode).parent
1144 if not target:
1144 if not target:
1145 raise util.Abort(_("%s does not have a parent recorded" % root))
1145 raise util.Abort(_("%s does not have a parent recorded" % root))
1146 if not repo[target] == repo['.']:
1146 if not repo[target] == repo['.']:
1147 hg.update(repo, target)
1147 hg.update(repo, target)
1148
1148
1149 if move:
1149 if move:
1150 if not patch:
1150 if not patch:
1151 raise util.Abort(_("please specify the patch to move"))
1151 raise util.Abort(_("please specify the patch to move"))
1152 for i, rpn in enumerate(self.full_series[start:]):
1152 for i, rpn in enumerate(self.full_series[start:]):
1153 # strip markers for patch guards
1153 # strip markers for patch guards
1154 if self.guard_re.split(rpn, 1)[0] == patch:
1154 if self.guard_re.split(rpn, 1)[0] == patch:
1155 break
1155 break
1156 index = start + i
1156 index = start + i
1157 assert index < len(self.full_series)
1157 assert index < len(self.full_series)
1158 fullpatch = self.full_series[index]
1158 fullpatch = self.full_series[index]
1159 del self.full_series[index]
1159 del self.full_series[index]
1160 self.full_series.insert(start, fullpatch)
1160 self.full_series.insert(start, fullpatch)
1161 self.parse_series()
1161 self.parse_series()
1162 self.series_dirty = 1
1162 self.series_dirty = 1
1163
1163
1164 self.applied_dirty = 1
1164 self.applied_dirty = 1
1165 if start > 0:
1165 if start > 0:
1166 self.check_toppatch(repo)
1166 self.check_toppatch(repo)
1167 if not patch:
1167 if not patch:
1168 patch = self.series[start]
1168 patch = self.series[start]
1169 end = start + 1
1169 end = start + 1
1170 else:
1170 else:
1171 end = self.series.index(patch, start) + 1
1171 end = self.series.index(patch, start) + 1
1172
1172
1173 s = self.series[start:end]
1173 s = self.series[start:end]
1174
1174
1175 if not force:
1175 if not force:
1176 mm, aa, rr, dd = repo.status()[:4]
1176 mm, aa, rr, dd = repo.status()[:4]
1177 wcfiles = set(mm + aa + rr + dd)
1177 wcfiles = set(mm + aa + rr + dd)
1178 if wcfiles:
1178 if wcfiles:
1179 for patchname in s:
1179 for patchname in s:
1180 pf = os.path.join(self.path, patchname)
1180 pf = os.path.join(self.path, patchname)
1181 patchfiles = patchmod.changedfiles(self.ui, repo, pf)
1181 patchfiles = patchmod.changedfiles(self.ui, repo, pf)
1182 if wcfiles.intersection(patchfiles):
1182 if wcfiles.intersection(patchfiles):
1183 self.localchangesfound(self.applied)
1183 self.localchangesfound(self.applied)
1184 elif mergeq:
1184 elif mergeq:
1185 self.check_localchanges(refresh=self.applied)
1185 self.check_localchanges(refresh=self.applied)
1186
1186
1187 all_files = set()
1187 all_files = set()
1188 try:
1188 try:
1189 if mergeq:
1189 if mergeq:
1190 ret = self.mergepatch(repo, mergeq, s, diffopts)
1190 ret = self.mergepatch(repo, mergeq, s, diffopts)
1191 else:
1191 else:
1192 ret = self.apply(repo, s, list, all_files=all_files)
1192 ret = self.apply(repo, s, list, all_files=all_files)
1193 except:
1193 except:
1194 self.ui.warn(_('cleaning up working directory...'))
1194 self.ui.warn(_('cleaning up working directory...'))
1195 node = repo.dirstate.p1()
1195 node = repo.dirstate.p1()
1196 hg.revert(repo, node, None)
1196 hg.revert(repo, node, None)
1197 # only remove unknown files that we know we touched or
1197 # only remove unknown files that we know we touched or
1198 # created while patching
1198 # created while patching
1199 for f in all_files:
1199 for f in all_files:
1200 if f not in repo.dirstate:
1200 if f not in repo.dirstate:
1201 try:
1201 try:
1202 util.unlinkpath(repo.wjoin(f))
1202 util.unlinkpath(repo.wjoin(f))
1203 except OSError, inst:
1203 except OSError, inst:
1204 if inst.errno != errno.ENOENT:
1204 if inst.errno != errno.ENOENT:
1205 raise
1205 raise
1206 self.ui.warn(_('done\n'))
1206 self.ui.warn(_('done\n'))
1207 raise
1207 raise
1208
1208
1209 if not self.applied:
1209 if not self.applied:
1210 return ret[0]
1210 return ret[0]
1211 top = self.applied[-1].name
1211 top = self.applied[-1].name
1212 if ret[0] and ret[0] > 1:
1212 if ret[0] and ret[0] > 1:
1213 msg = _("errors during apply, please fix and refresh %s\n")
1213 msg = _("errors during apply, please fix and refresh %s\n")
1214 self.ui.write(msg % top)
1214 self.ui.write(msg % top)
1215 else:
1215 else:
1216 self.ui.write(_("now at: %s\n") % top)
1216 self.ui.write(_("now at: %s\n") % top)
1217 return ret[0]
1217 return ret[0]
1218
1218
1219 finally:
1219 finally:
1220 wlock.release()
1220 wlock.release()
1221
1221
1222 def pop(self, repo, patch=None, force=False, update=True, all=False):
1222 def pop(self, repo, patch=None, force=False, update=True, all=False):
1223 wlock = repo.wlock()
1223 wlock = repo.wlock()
1224 try:
1224 try:
1225 if patch:
1225 if patch:
1226 # index, rev, patch
1226 # index, rev, patch
1227 info = self.isapplied(patch)
1227 info = self.isapplied(patch)
1228 if not info:
1228 if not info:
1229 patch = self.lookup(patch)
1229 patch = self.lookup(patch)
1230 info = self.isapplied(patch)
1230 info = self.isapplied(patch)
1231 if not info:
1231 if not info:
1232 raise util.Abort(_("patch %s is not applied") % patch)
1232 raise util.Abort(_("patch %s is not applied") % patch)
1233
1233
1234 if not self.applied:
1234 if not self.applied:
1235 # Allow qpop -a to work repeatedly,
1235 # Allow qpop -a to work repeatedly,
1236 # but not qpop without an argument
1236 # but not qpop without an argument
1237 self.ui.warn(_("no patches applied\n"))
1237 self.ui.warn(_("no patches applied\n"))
1238 return not all
1238 return not all
1239
1239
1240 if all:
1240 if all:
1241 start = 0
1241 start = 0
1242 elif patch:
1242 elif patch:
1243 start = info[0] + 1
1243 start = info[0] + 1
1244 else:
1244 else:
1245 start = len(self.applied) - 1
1245 start = len(self.applied) - 1
1246
1246
1247 if start >= len(self.applied):
1247 if start >= len(self.applied):
1248 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1248 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1249 return
1249 return
1250
1250
1251 if not update:
1251 if not update:
1252 parents = repo.dirstate.parents()
1252 parents = repo.dirstate.parents()
1253 rr = [x.node for x in self.applied]
1253 rr = [x.node for x in self.applied]
1254 for p in parents:
1254 for p in parents:
1255 if p in rr:
1255 if p in rr:
1256 self.ui.warn(_("qpop: forcing dirstate update\n"))
1256 self.ui.warn(_("qpop: forcing dirstate update\n"))
1257 update = True
1257 update = True
1258 else:
1258 else:
1259 parents = [p.node() for p in repo[None].parents()]
1259 parents = [p.node() for p in repo[None].parents()]
1260 needupdate = False
1260 needupdate = False
1261 for entry in self.applied[start:]:
1261 for entry in self.applied[start:]:
1262 if entry.node in parents:
1262 if entry.node in parents:
1263 needupdate = True
1263 needupdate = True
1264 break
1264 break
1265 update = needupdate
1265 update = needupdate
1266
1266
1267 self.applied_dirty = 1
1267 self.applied_dirty = 1
1268 end = len(self.applied)
1268 end = len(self.applied)
1269 rev = self.applied[start].node
1269 rev = self.applied[start].node
1270 if update:
1270 if update:
1271 top = self.check_toppatch(repo)[0]
1271 top = self.check_toppatch(repo)[0]
1272
1272
1273 try:
1273 try:
1274 heads = repo.changelog.heads(rev)
1274 heads = repo.changelog.heads(rev)
1275 except error.LookupError:
1275 except error.LookupError:
1276 node = short(rev)
1276 node = short(rev)
1277 raise util.Abort(_('trying to pop unknown node %s') % node)
1277 raise util.Abort(_('trying to pop unknown node %s') % node)
1278
1278
1279 if heads != [self.applied[-1].node]:
1279 if heads != [self.applied[-1].node]:
1280 raise util.Abort(_("popping would remove a revision not "
1280 raise util.Abort(_("popping would remove a revision not "
1281 "managed by this patch queue"))
1281 "managed by this patch queue"))
1282
1282
1283 # we know there are no local changes, so we can make a simplified
1283 # we know there are no local changes, so we can make a simplified
1284 # form of hg.update.
1284 # form of hg.update.
1285 if update:
1285 if update:
1286 qp = self.qparents(repo, rev)
1286 qp = self.qparents(repo, rev)
1287 ctx = repo[qp]
1287 ctx = repo[qp]
1288 m, a, r, d = repo.status(qp, top)[:4]
1288 m, a, r, d = repo.status(qp, top)[:4]
1289 parentfiles = set(m + a + r + d)
1289 parentfiles = set(m + a + r + d)
1290 if not force and parentfiles:
1290 if not force and parentfiles:
1291 mm, aa, rr, dd = repo.status()[:4]
1291 mm, aa, rr, dd = repo.status()[:4]
1292 wcfiles = set(mm + aa + rr + dd)
1292 wcfiles = set(mm + aa + rr + dd)
1293 if wcfiles.intersection(parentfiles):
1293 if wcfiles.intersection(parentfiles):
1294 self.localchangesfound()
1294 self.localchangesfound()
1295 if d:
1295 if d:
1296 raise util.Abort(_("deletions found between repo revs"))
1296 raise util.Abort(_("deletions found between repo revs"))
1297 for f in a:
1297 for f in a:
1298 try:
1298 try:
1299 util.unlinkpath(repo.wjoin(f))
1299 util.unlinkpath(repo.wjoin(f))
1300 except OSError, e:
1300 except OSError, e:
1301 if e.errno != errno.ENOENT:
1301 if e.errno != errno.ENOENT:
1302 raise
1302 raise
1303 repo.dirstate.forget(f)
1303 repo.dirstate.forget(f)
1304 for f in m + r:
1304 for f in m + r:
1305 fctx = ctx[f]
1305 fctx = ctx[f]
1306 repo.wwrite(f, fctx.data(), fctx.flags())
1306 repo.wwrite(f, fctx.data(), fctx.flags())
1307 repo.dirstate.normal(f)
1307 repo.dirstate.normal(f)
1308 repo.dirstate.setparents(qp, nullid)
1308 repo.dirstate.setparents(qp, nullid)
1309 for patch in reversed(self.applied[start:end]):
1309 for patch in reversed(self.applied[start:end]):
1310 self.ui.status(_("popping %s\n") % patch.name)
1310 self.ui.status(_("popping %s\n") % patch.name)
1311 del self.applied[start:end]
1311 del self.applied[start:end]
1312 self.strip(repo, [rev], update=False, backup='strip')
1312 self.strip(repo, [rev], update=False, backup='strip')
1313 if self.applied:
1313 if self.applied:
1314 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1314 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1315 else:
1315 else:
1316 self.ui.write(_("patch queue now empty\n"))
1316 self.ui.write(_("patch queue now empty\n"))
1317 finally:
1317 finally:
1318 wlock.release()
1318 wlock.release()
1319
1319
1320 def diff(self, repo, pats, opts):
1320 def diff(self, repo, pats, opts):
1321 top, patch = self.check_toppatch(repo)
1321 top, patch = self.check_toppatch(repo)
1322 if not top:
1322 if not top:
1323 self.ui.write(_("no patches applied\n"))
1323 self.ui.write(_("no patches applied\n"))
1324 return
1324 return
1325 qp = self.qparents(repo, top)
1325 qp = self.qparents(repo, top)
1326 if opts.get('reverse'):
1326 if opts.get('reverse'):
1327 node1, node2 = None, qp
1327 node1, node2 = None, qp
1328 else:
1328 else:
1329 node1, node2 = qp, None
1329 node1, node2 = qp, None
1330 diffopts = self.diffopts(opts, patch)
1330 diffopts = self.diffopts(opts, patch)
1331 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1331 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1332
1332
1333 def refresh(self, repo, pats=None, **opts):
1333 def refresh(self, repo, pats=None, **opts):
1334 if not self.applied:
1334 if not self.applied:
1335 self.ui.write(_("no patches applied\n"))
1335 self.ui.write(_("no patches applied\n"))
1336 return 1
1336 return 1
1337 msg = opts.get('msg', '').rstrip()
1337 msg = opts.get('msg', '').rstrip()
1338 newuser = opts.get('user')
1338 newuser = opts.get('user')
1339 newdate = opts.get('date')
1339 newdate = opts.get('date')
1340 if newdate:
1340 if newdate:
1341 newdate = '%d %d' % util.parsedate(newdate)
1341 newdate = '%d %d' % util.parsedate(newdate)
1342 wlock = repo.wlock()
1342 wlock = repo.wlock()
1343
1343
1344 try:
1344 try:
1345 self.check_toppatch(repo)
1345 self.check_toppatch(repo)
1346 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1346 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1347 if repo.changelog.heads(top) != [top]:
1347 if repo.changelog.heads(top) != [top]:
1348 raise util.Abort(_("cannot refresh a revision with children"))
1348 raise util.Abort(_("cannot refresh a revision with children"))
1349
1349
1350 inclsubs = self.check_substate(repo)
1350 inclsubs = self.check_substate(repo)
1351
1351
1352 cparents = repo.changelog.parents(top)
1352 cparents = repo.changelog.parents(top)
1353 patchparent = self.qparents(repo, top)
1353 patchparent = self.qparents(repo, top)
1354 ph = patchheader(self.join(patchfn), self.plainmode)
1354 ph = patchheader(self.join(patchfn), self.plainmode)
1355 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1355 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1356 if msg:
1356 if msg:
1357 ph.setmessage(msg)
1357 ph.setmessage(msg)
1358 if newuser:
1358 if newuser:
1359 ph.setuser(newuser)
1359 ph.setuser(newuser)
1360 if newdate:
1360 if newdate:
1361 ph.setdate(newdate)
1361 ph.setdate(newdate)
1362 ph.setparent(hex(patchparent))
1362 ph.setparent(hex(patchparent))
1363
1363
1364 # only commit new patch when write is complete
1364 # only commit new patch when write is complete
1365 patchf = self.opener(patchfn, 'w', atomictemp=True)
1365 patchf = self.opener(patchfn, 'w', atomictemp=True)
1366
1366
1367 comments = str(ph)
1367 comments = str(ph)
1368 if comments:
1368 if comments:
1369 patchf.write(comments)
1369 patchf.write(comments)
1370
1370
1371 # update the dirstate in place, strip off the qtip commit
1371 # update the dirstate in place, strip off the qtip commit
1372 # and then commit.
1372 # and then commit.
1373 #
1373 #
1374 # this should really read:
1374 # this should really read:
1375 # mm, dd, aa = repo.status(top, patchparent)[:3]
1375 # mm, dd, aa = repo.status(top, patchparent)[:3]
1376 # but we do it backwards to take advantage of manifest/chlog
1376 # but we do it backwards to take advantage of manifest/chlog
1377 # caching against the next repo.status call
1377 # caching against the next repo.status call
1378 mm, aa, dd = repo.status(patchparent, top)[:3]
1378 mm, aa, dd = repo.status(patchparent, top)[:3]
1379 changes = repo.changelog.read(top)
1379 changes = repo.changelog.read(top)
1380 man = repo.manifest.read(changes[0])
1380 man = repo.manifest.read(changes[0])
1381 aaa = aa[:]
1381 aaa = aa[:]
1382 matchfn = scmutil.match(repo, pats, opts)
1382 matchfn = scmutil.match(repo, pats, opts)
1383 # in short mode, we only diff the files included in the
1383 # in short mode, we only diff the files included in the
1384 # patch already plus specified files
1384 # patch already plus specified files
1385 if opts.get('short'):
1385 if opts.get('short'):
1386 # if amending a patch, we start with existing
1386 # if amending a patch, we start with existing
1387 # files plus specified files - unfiltered
1387 # files plus specified files - unfiltered
1388 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1388 match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1389 # filter with inc/exl options
1389 # filter with inc/exl options
1390 matchfn = scmutil.match(repo, opts=opts)
1390 matchfn = scmutil.match(repo, opts=opts)
1391 else:
1391 else:
1392 match = scmutil.matchall(repo)
1392 match = scmutil.matchall(repo)
1393 m, a, r, d = repo.status(match=match)[:4]
1393 m, a, r, d = repo.status(match=match)[:4]
1394 mm = set(mm)
1394 mm = set(mm)
1395 aa = set(aa)
1395 aa = set(aa)
1396 dd = set(dd)
1396 dd = set(dd)
1397
1397
1398 # we might end up with files that were added between
1398 # we might end up with files that were added between
1399 # qtip and the dirstate parent, but then changed in the
1399 # qtip and the dirstate parent, but then changed in the
1400 # local dirstate. in this case, we want them to only
1400 # local dirstate. in this case, we want them to only
1401 # show up in the added section
1401 # show up in the added section
1402 for x in m:
1402 for x in m:
1403 if x not in aa:
1403 if x not in aa:
1404 mm.add(x)
1404 mm.add(x)
1405 # we might end up with files added by the local dirstate that
1405 # we might end up with files added by the local dirstate that
1406 # were deleted by the patch. In this case, they should only
1406 # were deleted by the patch. In this case, they should only
1407 # show up in the changed section.
1407 # show up in the changed section.
1408 for x in a:
1408 for x in a:
1409 if x in dd:
1409 if x in dd:
1410 dd.remove(x)
1410 dd.remove(x)
1411 mm.add(x)
1411 mm.add(x)
1412 else:
1412 else:
1413 aa.add(x)
1413 aa.add(x)
1414 # make sure any files deleted in the local dirstate
1414 # make sure any files deleted in the local dirstate
1415 # are not in the add or change column of the patch
1415 # are not in the add or change column of the patch
1416 forget = []
1416 forget = []
1417 for x in d + r:
1417 for x in d + r:
1418 if x in aa:
1418 if x in aa:
1419 aa.remove(x)
1419 aa.remove(x)
1420 forget.append(x)
1420 forget.append(x)
1421 continue
1421 continue
1422 else:
1422 else:
1423 mm.discard(x)
1423 mm.discard(x)
1424 dd.add(x)
1424 dd.add(x)
1425
1425
1426 m = list(mm)
1426 m = list(mm)
1427 r = list(dd)
1427 r = list(dd)
1428 a = list(aa)
1428 a = list(aa)
1429 c = [filter(matchfn, l) for l in (m, a, r)]
1429 c = [filter(matchfn, l) for l in (m, a, r)]
1430 match = scmutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1430 match = scmutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs))
1431 chunks = patchmod.diff(repo, patchparent, match=match,
1431 chunks = patchmod.diff(repo, patchparent, match=match,
1432 changes=c, opts=diffopts)
1432 changes=c, opts=diffopts)
1433 for chunk in chunks:
1433 for chunk in chunks:
1434 patchf.write(chunk)
1434 patchf.write(chunk)
1435
1435
1436 try:
1436 try:
1437 if diffopts.git or diffopts.upgrade:
1437 if diffopts.git or diffopts.upgrade:
1438 copies = {}
1438 copies = {}
1439 for dst in a:
1439 for dst in a:
1440 src = repo.dirstate.copied(dst)
1440 src = repo.dirstate.copied(dst)
1441 # during qfold, the source file for copies may
1441 # during qfold, the source file for copies may
1442 # be removed. Treat this as a simple add.
1442 # be removed. Treat this as a simple add.
1443 if src is not None and src in repo.dirstate:
1443 if src is not None and src in repo.dirstate:
1444 copies.setdefault(src, []).append(dst)
1444 copies.setdefault(src, []).append(dst)
1445 repo.dirstate.add(dst)
1445 repo.dirstate.add(dst)
1446 # remember the copies between patchparent and qtip
1446 # remember the copies between patchparent and qtip
1447 for dst in aaa:
1447 for dst in aaa:
1448 f = repo.file(dst)
1448 f = repo.file(dst)
1449 src = f.renamed(man[dst])
1449 src = f.renamed(man[dst])
1450 if src:
1450 if src:
1451 copies.setdefault(src[0], []).extend(
1451 copies.setdefault(src[0], []).extend(
1452 copies.get(dst, []))
1452 copies.get(dst, []))
1453 if dst in a:
1453 if dst in a:
1454 copies[src[0]].append(dst)
1454 copies[src[0]].append(dst)
1455 # we can't copy a file created by the patch itself
1455 # we can't copy a file created by the patch itself
1456 if dst in copies:
1456 if dst in copies:
1457 del copies[dst]
1457 del copies[dst]
1458 for src, dsts in copies.iteritems():
1458 for src, dsts in copies.iteritems():
1459 for dst in dsts:
1459 for dst in dsts:
1460 repo.dirstate.copy(src, dst)
1460 repo.dirstate.copy(src, dst)
1461 else:
1461 else:
1462 for dst in a:
1462 for dst in a:
1463 repo.dirstate.add(dst)
1463 repo.dirstate.add(dst)
1464 # Drop useless copy information
1464 # Drop useless copy information
1465 for f in list(repo.dirstate.copies()):
1465 for f in list(repo.dirstate.copies()):
1466 repo.dirstate.copy(None, f)
1466 repo.dirstate.copy(None, f)
1467 for f in r:
1467 for f in r:
1468 repo.dirstate.remove(f)
1468 repo.dirstate.remove(f)
1469 # if the patch excludes a modified file, mark that
1469 # if the patch excludes a modified file, mark that
1470 # file with mtime=0 so status can see it.
1470 # file with mtime=0 so status can see it.
1471 mm = []
1471 mm = []
1472 for i in xrange(len(m)-1, -1, -1):
1472 for i in xrange(len(m)-1, -1, -1):
1473 if not matchfn(m[i]):
1473 if not matchfn(m[i]):
1474 mm.append(m[i])
1474 mm.append(m[i])
1475 del m[i]
1475 del m[i]
1476 for f in m:
1476 for f in m:
1477 repo.dirstate.normal(f)
1477 repo.dirstate.normal(f)
1478 for f in mm:
1478 for f in mm:
1479 repo.dirstate.normallookup(f)
1479 repo.dirstate.normallookup(f)
1480 for f in forget:
1480 for f in forget:
1481 repo.dirstate.forget(f)
1481 repo.dirstate.forget(f)
1482
1482
1483 if not msg:
1483 if not msg:
1484 if not ph.message:
1484 if not ph.message:
1485 message = "[mq]: %s\n" % patchfn
1485 message = "[mq]: %s\n" % patchfn
1486 else:
1486 else:
1487 message = "\n".join(ph.message)
1487 message = "\n".join(ph.message)
1488 else:
1488 else:
1489 message = msg
1489 message = msg
1490
1490
1491 user = ph.user or changes[1]
1491 user = ph.user or changes[1]
1492
1492
1493 # assumes strip can roll itself back if interrupted
1493 # assumes strip can roll itself back if interrupted
1494 repo.dirstate.setparents(*cparents)
1494 repo.dirstate.setparents(*cparents)
1495 self.applied.pop()
1495 self.applied.pop()
1496 self.applied_dirty = 1
1496 self.applied_dirty = 1
1497 self.strip(repo, [top], update=False,
1497 self.strip(repo, [top], update=False,
1498 backup='strip')
1498 backup='strip')
1499 except:
1499 except:
1500 repo.dirstate.invalidate()
1500 repo.dirstate.invalidate()
1501 raise
1501 raise
1502
1502
1503 try:
1503 try:
1504 # might be nice to attempt to roll back strip after this
1504 # might be nice to attempt to roll back strip after this
1505 n = repo.commit(message, user, ph.date, match=match,
1505 n = repo.commit(message, user, ph.date, match=match,
1506 force=True)
1506 force=True)
1507 # only write patch after a successful commit
1507 # only write patch after a successful commit
1508 patchf.rename()
1508 patchf.rename()
1509 self.applied.append(statusentry(n, patchfn))
1509 self.applied.append(statusentry(n, patchfn))
1510 except:
1510 except:
1511 ctx = repo[cparents[0]]
1511 ctx = repo[cparents[0]]
1512 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1512 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1513 self.save_dirty()
1513 self.save_dirty()
1514 self.ui.warn(_('refresh interrupted while patch was popped! '
1514 self.ui.warn(_('refresh interrupted while patch was popped! '
1515 '(revert --all, qpush to recover)\n'))
1515 '(revert --all, qpush to recover)\n'))
1516 raise
1516 raise
1517 finally:
1517 finally:
1518 wlock.release()
1518 wlock.release()
1519 self.removeundo(repo)
1519 self.removeundo(repo)
1520
1520
1521 def init(self, repo, create=False):
1521 def init(self, repo, create=False):
1522 if not create and os.path.isdir(self.path):
1522 if not create and os.path.isdir(self.path):
1523 raise util.Abort(_("patch queue directory already exists"))
1523 raise util.Abort(_("patch queue directory already exists"))
1524 try:
1524 try:
1525 os.mkdir(self.path)
1525 os.mkdir(self.path)
1526 except OSError, inst:
1526 except OSError, inst:
1527 if inst.errno != errno.EEXIST or not create:
1527 if inst.errno != errno.EEXIST or not create:
1528 raise
1528 raise
1529 if create:
1529 if create:
1530 return self.qrepo(create=True)
1530 return self.qrepo(create=True)
1531
1531
1532 def unapplied(self, repo, patch=None):
1532 def unapplied(self, repo, patch=None):
1533 if patch and patch not in self.series:
1533 if patch and patch not in self.series:
1534 raise util.Abort(_("patch %s is not in series file") % patch)
1534 raise util.Abort(_("patch %s is not in series file") % patch)
1535 if not patch:
1535 if not patch:
1536 start = self.series_end()
1536 start = self.series_end()
1537 else:
1537 else:
1538 start = self.series.index(patch) + 1
1538 start = self.series.index(patch) + 1
1539 unapplied = []
1539 unapplied = []
1540 for i in xrange(start, len(self.series)):
1540 for i in xrange(start, len(self.series)):
1541 pushable, reason = self.pushable(i)
1541 pushable, reason = self.pushable(i)
1542 if pushable:
1542 if pushable:
1543 unapplied.append((i, self.series[i]))
1543 unapplied.append((i, self.series[i]))
1544 self.explain_pushable(i)
1544 self.explain_pushable(i)
1545 return unapplied
1545 return unapplied
1546
1546
1547 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1547 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1548 summary=False):
1548 summary=False):
1549 def displayname(pfx, patchname, state):
1549 def displayname(pfx, patchname, state):
1550 if pfx:
1550 if pfx:
1551 self.ui.write(pfx)
1551 self.ui.write(pfx)
1552 if summary:
1552 if summary:
1553 ph = patchheader(self.join(patchname), self.plainmode)
1553 ph = patchheader(self.join(patchname), self.plainmode)
1554 msg = ph.message and ph.message[0] or ''
1554 msg = ph.message and ph.message[0] or ''
1555 if self.ui.formatted():
1555 if self.ui.formatted():
1556 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1556 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
1557 if width > 0:
1557 if width > 0:
1558 msg = util.ellipsis(msg, width)
1558 msg = util.ellipsis(msg, width)
1559 else:
1559 else:
1560 msg = ''
1560 msg = ''
1561 self.ui.write(patchname, label='qseries.' + state)
1561 self.ui.write(patchname, label='qseries.' + state)
1562 self.ui.write(': ')
1562 self.ui.write(': ')
1563 self.ui.write(msg, label='qseries.message.' + state)
1563 self.ui.write(msg, label='qseries.message.' + state)
1564 else:
1564 else:
1565 self.ui.write(patchname, label='qseries.' + state)
1565 self.ui.write(patchname, label='qseries.' + state)
1566 self.ui.write('\n')
1566 self.ui.write('\n')
1567
1567
1568 applied = set([p.name for p in self.applied])
1568 applied = set([p.name for p in self.applied])
1569 if length is None:
1569 if length is None:
1570 length = len(self.series) - start
1570 length = len(self.series) - start
1571 if not missing:
1571 if not missing:
1572 if self.ui.verbose:
1572 if self.ui.verbose:
1573 idxwidth = len(str(start + length - 1))
1573 idxwidth = len(str(start + length - 1))
1574 for i in xrange(start, start + length):
1574 for i in xrange(start, start + length):
1575 patch = self.series[i]
1575 patch = self.series[i]
1576 if patch in applied:
1576 if patch in applied:
1577 char, state = 'A', 'applied'
1577 char, state = 'A', 'applied'
1578 elif self.pushable(i)[0]:
1578 elif self.pushable(i)[0]:
1579 char, state = 'U', 'unapplied'
1579 char, state = 'U', 'unapplied'
1580 else:
1580 else:
1581 char, state = 'G', 'guarded'
1581 char, state = 'G', 'guarded'
1582 pfx = ''
1582 pfx = ''
1583 if self.ui.verbose:
1583 if self.ui.verbose:
1584 pfx = '%*d %s ' % (idxwidth, i, char)
1584 pfx = '%*d %s ' % (idxwidth, i, char)
1585 elif status and status != char:
1585 elif status and status != char:
1586 continue
1586 continue
1587 displayname(pfx, patch, state)
1587 displayname(pfx, patch, state)
1588 else:
1588 else:
1589 msng_list = []
1589 msng_list = []
1590 for root, dirs, files in os.walk(self.path):
1590 for root, dirs, files in os.walk(self.path):
1591 d = root[len(self.path) + 1:]
1591 d = root[len(self.path) + 1:]
1592 for f in files:
1592 for f in files:
1593 fl = os.path.join(d, f)
1593 fl = os.path.join(d, f)
1594 if (fl not in self.series and
1594 if (fl not in self.series and
1595 fl not in (self.status_path, self.series_path,
1595 fl not in (self.status_path, self.series_path,
1596 self.guards_path)
1596 self.guards_path)
1597 and not fl.startswith('.')):
1597 and not fl.startswith('.')):
1598 msng_list.append(fl)
1598 msng_list.append(fl)
1599 for x in sorted(msng_list):
1599 for x in sorted(msng_list):
1600 pfx = self.ui.verbose and ('D ') or ''
1600 pfx = self.ui.verbose and ('D ') or ''
1601 displayname(pfx, x, 'missing')
1601 displayname(pfx, x, 'missing')
1602
1602
1603 def issaveline(self, l):
1603 def issaveline(self, l):
1604 if l.name == '.hg.patches.save.line':
1604 if l.name == '.hg.patches.save.line':
1605 return True
1605 return True
1606
1606
1607 def qrepo(self, create=False):
1607 def qrepo(self, create=False):
1608 ui = self.ui.copy()
1608 ui = self.ui.copy()
1609 ui.setconfig('paths', 'default', '', overlay=False)
1609 ui.setconfig('paths', 'default', '', overlay=False)
1610 ui.setconfig('paths', 'default-push', '', overlay=False)
1610 ui.setconfig('paths', 'default-push', '', overlay=False)
1611 if create or os.path.isdir(self.join(".hg")):
1611 if create or os.path.isdir(self.join(".hg")):
1612 return hg.repository(ui, path=self.path, create=create)
1612 return hg.repository(ui, path=self.path, create=create)
1613
1613
1614 def restore(self, repo, rev, delete=None, qupdate=None):
1614 def restore(self, repo, rev, delete=None, qupdate=None):
1615 desc = repo[rev].description().strip()
1615 desc = repo[rev].description().strip()
1616 lines = desc.splitlines()
1616 lines = desc.splitlines()
1617 i = 0
1617 i = 0
1618 datastart = None
1618 datastart = None
1619 series = []
1619 series = []
1620 applied = []
1620 applied = []
1621 qpp = None
1621 qpp = None
1622 for i, line in enumerate(lines):
1622 for i, line in enumerate(lines):
1623 if line == 'Patch Data:':
1623 if line == 'Patch Data:':
1624 datastart = i + 1
1624 datastart = i + 1
1625 elif line.startswith('Dirstate:'):
1625 elif line.startswith('Dirstate:'):
1626 l = line.rstrip()
1626 l = line.rstrip()
1627 l = l[10:].split(' ')
1627 l = l[10:].split(' ')
1628 qpp = [bin(x) for x in l]
1628 qpp = [bin(x) for x in l]
1629 elif datastart is not None:
1629 elif datastart is not None:
1630 l = line.rstrip()
1630 l = line.rstrip()
1631 n, name = l.split(':', 1)
1631 n, name = l.split(':', 1)
1632 if n:
1632 if n:
1633 applied.append(statusentry(bin(n), name))
1633 applied.append(statusentry(bin(n), name))
1634 else:
1634 else:
1635 series.append(l)
1635 series.append(l)
1636 if datastart is None:
1636 if datastart is None:
1637 self.ui.warn(_("No saved patch data found\n"))
1637 self.ui.warn(_("No saved patch data found\n"))
1638 return 1
1638 return 1
1639 self.ui.warn(_("restoring status: %s\n") % lines[0])
1639 self.ui.warn(_("restoring status: %s\n") % lines[0])
1640 self.full_series = series
1640 self.full_series = series
1641 self.applied = applied
1641 self.applied = applied
1642 self.parse_series()
1642 self.parse_series()
1643 self.series_dirty = 1
1643 self.series_dirty = 1
1644 self.applied_dirty = 1
1644 self.applied_dirty = 1
1645 heads = repo.changelog.heads()
1645 heads = repo.changelog.heads()
1646 if delete:
1646 if delete:
1647 if rev not in heads:
1647 if rev not in heads:
1648 self.ui.warn(_("save entry has children, leaving it alone\n"))
1648 self.ui.warn(_("save entry has children, leaving it alone\n"))
1649 else:
1649 else:
1650 self.ui.warn(_("removing save entry %s\n") % short(rev))
1650 self.ui.warn(_("removing save entry %s\n") % short(rev))
1651 pp = repo.dirstate.parents()
1651 pp = repo.dirstate.parents()
1652 if rev in pp:
1652 if rev in pp:
1653 update = True
1653 update = True
1654 else:
1654 else:
1655 update = False
1655 update = False
1656 self.strip(repo, [rev], update=update, backup='strip')
1656 self.strip(repo, [rev], update=update, backup='strip')
1657 if qpp:
1657 if qpp:
1658 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1658 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1659 (short(qpp[0]), short(qpp[1])))
1659 (short(qpp[0]), short(qpp[1])))
1660 if qupdate:
1660 if qupdate:
1661 self.ui.status(_("updating queue directory\n"))
1661 self.ui.status(_("updating queue directory\n"))
1662 r = self.qrepo()
1662 r = self.qrepo()
1663 if not r:
1663 if not r:
1664 self.ui.warn(_("Unable to load queue repository\n"))
1664 self.ui.warn(_("Unable to load queue repository\n"))
1665 return 1
1665 return 1
1666 hg.clean(r, qpp[0])
1666 hg.clean(r, qpp[0])
1667
1667
1668 def save(self, repo, msg=None):
1668 def save(self, repo, msg=None):
1669 if not self.applied:
1669 if not self.applied:
1670 self.ui.warn(_("save: no patches applied, exiting\n"))
1670 self.ui.warn(_("save: no patches applied, exiting\n"))
1671 return 1
1671 return 1
1672 if self.issaveline(self.applied[-1]):
1672 if self.issaveline(self.applied[-1]):
1673 self.ui.warn(_("status is already saved\n"))
1673 self.ui.warn(_("status is already saved\n"))
1674 return 1
1674 return 1
1675
1675
1676 if not msg:
1676 if not msg:
1677 msg = _("hg patches saved state")
1677 msg = _("hg patches saved state")
1678 else:
1678 else:
1679 msg = "hg patches: " + msg.rstrip('\r\n')
1679 msg = "hg patches: " + msg.rstrip('\r\n')
1680 r = self.qrepo()
1680 r = self.qrepo()
1681 if r:
1681 if r:
1682 pp = r.dirstate.parents()
1682 pp = r.dirstate.parents()
1683 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1683 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1684 msg += "\n\nPatch Data:\n"
1684 msg += "\n\nPatch Data:\n"
1685 msg += ''.join('%s\n' % x for x in self.applied)
1685 msg += ''.join('%s\n' % x for x in self.applied)
1686 msg += ''.join(':%s\n' % x for x in self.full_series)
1686 msg += ''.join(':%s\n' % x for x in self.full_series)
1687 n = repo.commit(msg, force=True)
1687 n = repo.commit(msg, force=True)
1688 if not n:
1688 if not n:
1689 self.ui.warn(_("repo commit failed\n"))
1689 self.ui.warn(_("repo commit failed\n"))
1690 return 1
1690 return 1
1691 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1691 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1692 self.applied_dirty = 1
1692 self.applied_dirty = 1
1693 self.removeundo(repo)
1693 self.removeundo(repo)
1694
1694
1695 def full_series_end(self):
1695 def full_series_end(self):
1696 if self.applied:
1696 if self.applied:
1697 p = self.applied[-1].name
1697 p = self.applied[-1].name
1698 end = self.find_series(p)
1698 end = self.find_series(p)
1699 if end is None:
1699 if end is None:
1700 return len(self.full_series)
1700 return len(self.full_series)
1701 return end + 1
1701 return end + 1
1702 return 0
1702 return 0
1703
1703
1704 def series_end(self, all_patches=False):
1704 def series_end(self, all_patches=False):
1705 """If all_patches is False, return the index of the next pushable patch
1705 """If all_patches is False, return the index of the next pushable patch
1706 in the series, or the series length. If all_patches is True, return the
1706 in the series, or the series length. If all_patches is True, return the
1707 index of the first patch past the last applied one.
1707 index of the first patch past the last applied one.
1708 """
1708 """
1709 end = 0
1709 end = 0
1710 def next(start):
1710 def next(start):
1711 if all_patches or start >= len(self.series):
1711 if all_patches or start >= len(self.series):
1712 return start
1712 return start
1713 for i in xrange(start, len(self.series)):
1713 for i in xrange(start, len(self.series)):
1714 p, reason = self.pushable(i)
1714 p, reason = self.pushable(i)
1715 if p:
1715 if p:
1716 break
1716 break
1717 self.explain_pushable(i)
1717 self.explain_pushable(i)
1718 return i
1718 return i
1719 if self.applied:
1719 if self.applied:
1720 p = self.applied[-1].name
1720 p = self.applied[-1].name
1721 try:
1721 try:
1722 end = self.series.index(p)
1722 end = self.series.index(p)
1723 except ValueError:
1723 except ValueError:
1724 return 0
1724 return 0
1725 return next(end + 1)
1725 return next(end + 1)
1726 return next(end)
1726 return next(end)
1727
1727
1728 def appliedname(self, index):
1728 def appliedname(self, index):
1729 pname = self.applied[index].name
1729 pname = self.applied[index].name
1730 if not self.ui.verbose:
1730 if not self.ui.verbose:
1731 p = pname
1731 p = pname
1732 else:
1732 else:
1733 p = str(self.series.index(pname)) + " " + pname
1733 p = str(self.series.index(pname)) + " " + pname
1734 return p
1734 return p
1735
1735
1736 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1736 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1737 force=None, git=False):
1737 force=None, git=False):
1738 def checkseries(patchname):
1738 def checkseries(patchname):
1739 if patchname in self.series:
1739 if patchname in self.series:
1740 raise util.Abort(_('patch %s is already in the series file')
1740 raise util.Abort(_('patch %s is already in the series file')
1741 % patchname)
1741 % patchname)
1742 def checkfile(patchname):
1742 def checkfile(patchname):
1743 if not force and os.path.exists(self.join(patchname)):
1743 if not force and os.path.exists(self.join(patchname)):
1744 raise util.Abort(_('patch "%s" already exists')
1744 raise util.Abort(_('patch "%s" already exists')
1745 % patchname)
1745 % patchname)
1746
1746
1747 if rev:
1747 if rev:
1748 if files:
1748 if files:
1749 raise util.Abort(_('option "-r" not valid when importing '
1749 raise util.Abort(_('option "-r" not valid when importing '
1750 'files'))
1750 'files'))
1751 rev = scmutil.revrange(repo, rev)
1751 rev = scmutil.revrange(repo, rev)
1752 rev.sort(reverse=True)
1752 rev.sort(reverse=True)
1753 if (len(files) > 1 or len(rev) > 1) and patchname:
1753 if (len(files) > 1 or len(rev) > 1) and patchname:
1754 raise util.Abort(_('option "-n" not valid when importing multiple '
1754 raise util.Abort(_('option "-n" not valid when importing multiple '
1755 'patches'))
1755 'patches'))
1756 if rev:
1756 if rev:
1757 # If mq patches are applied, we can only import revisions
1757 # If mq patches are applied, we can only import revisions
1758 # that form a linear path to qbase.
1758 # that form a linear path to qbase.
1759 # Otherwise, they should form a linear path to a head.
1759 # Otherwise, they should form a linear path to a head.
1760 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1760 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1761 if len(heads) > 1:
1761 if len(heads) > 1:
1762 raise util.Abort(_('revision %d is the root of more than one '
1762 raise util.Abort(_('revision %d is the root of more than one '
1763 'branch') % rev[-1])
1763 'branch') % rev[-1])
1764 if self.applied:
1764 if self.applied:
1765 base = repo.changelog.node(rev[0])
1765 base = repo.changelog.node(rev[0])
1766 if base in [n.node for n in self.applied]:
1766 if base in [n.node for n in self.applied]:
1767 raise util.Abort(_('revision %d is already managed')
1767 raise util.Abort(_('revision %d is already managed')
1768 % rev[0])
1768 % rev[0])
1769 if heads != [self.applied[-1].node]:
1769 if heads != [self.applied[-1].node]:
1770 raise util.Abort(_('revision %d is not the parent of '
1770 raise util.Abort(_('revision %d is not the parent of '
1771 'the queue') % rev[0])
1771 'the queue') % rev[0])
1772 base = repo.changelog.rev(self.applied[0].node)
1772 base = repo.changelog.rev(self.applied[0].node)
1773 lastparent = repo.changelog.parentrevs(base)[0]
1773 lastparent = repo.changelog.parentrevs(base)[0]
1774 else:
1774 else:
1775 if heads != [repo.changelog.node(rev[0])]:
1775 if heads != [repo.changelog.node(rev[0])]:
1776 raise util.Abort(_('revision %d has unmanaged children')
1776 raise util.Abort(_('revision %d has unmanaged children')
1777 % rev[0])
1777 % rev[0])
1778 lastparent = None
1778 lastparent = None
1779
1779
1780 diffopts = self.diffopts({'git': git})
1780 diffopts = self.diffopts({'git': git})
1781 for r in rev:
1781 for r in rev:
1782 p1, p2 = repo.changelog.parentrevs(r)
1782 p1, p2 = repo.changelog.parentrevs(r)
1783 n = repo.changelog.node(r)
1783 n = repo.changelog.node(r)
1784 if p2 != nullrev:
1784 if p2 != nullrev:
1785 raise util.Abort(_('cannot import merge revision %d') % r)
1785 raise util.Abort(_('cannot import merge revision %d') % r)
1786 if lastparent and lastparent != r:
1786 if lastparent and lastparent != r:
1787 raise util.Abort(_('revision %d is not the parent of %d')
1787 raise util.Abort(_('revision %d is not the parent of %d')
1788 % (r, lastparent))
1788 % (r, lastparent))
1789 lastparent = p1
1789 lastparent = p1
1790
1790
1791 if not patchname:
1791 if not patchname:
1792 patchname = normname('%d.diff' % r)
1792 patchname = normname('%d.diff' % r)
1793 self.check_reserved_name(patchname)
1793 self.check_reserved_name(patchname)
1794 checkseries(patchname)
1794 checkseries(patchname)
1795 checkfile(patchname)
1795 checkfile(patchname)
1796 self.full_series.insert(0, patchname)
1796 self.full_series.insert(0, patchname)
1797
1797
1798 patchf = self.opener(patchname, "w")
1798 patchf = self.opener(patchname, "w")
1799 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1799 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1800 patchf.close()
1800 patchf.close()
1801
1801
1802 se = statusentry(n, patchname)
1802 se = statusentry(n, patchname)
1803 self.applied.insert(0, se)
1803 self.applied.insert(0, se)
1804
1804
1805 self.added.append(patchname)
1805 self.added.append(patchname)
1806 patchname = None
1806 patchname = None
1807 self.parse_series()
1807 self.parse_series()
1808 self.applied_dirty = 1
1808 self.applied_dirty = 1
1809 self.series_dirty = True
1809 self.series_dirty = True
1810
1810
1811 for i, filename in enumerate(files):
1811 for i, filename in enumerate(files):
1812 if existing:
1812 if existing:
1813 if filename == '-':
1813 if filename == '-':
1814 raise util.Abort(_('-e is incompatible with import from -'))
1814 raise util.Abort(_('-e is incompatible with import from -'))
1815 filename = normname(filename)
1815 filename = normname(filename)
1816 self.check_reserved_name(filename)
1816 self.check_reserved_name(filename)
1817 originpath = self.join(filename)
1817 originpath = self.join(filename)
1818 if not os.path.isfile(originpath):
1818 if not os.path.isfile(originpath):
1819 raise util.Abort(_("patch %s does not exist") % filename)
1819 raise util.Abort(_("patch %s does not exist") % filename)
1820
1820
1821 if patchname:
1821 if patchname:
1822 self.check_reserved_name(patchname)
1822 self.check_reserved_name(patchname)
1823 checkfile(patchname)
1823 checkfile(patchname)
1824
1824
1825 self.ui.write(_('renaming %s to %s\n')
1825 self.ui.write(_('renaming %s to %s\n')
1826 % (filename, patchname))
1826 % (filename, patchname))
1827 util.rename(originpath, self.join(patchname))
1827 util.rename(originpath, self.join(patchname))
1828 else:
1828 else:
1829 patchname = filename
1829 patchname = filename
1830
1830
1831 else:
1831 else:
1832 try:
1832 try:
1833 if filename == '-':
1833 if filename == '-':
1834 if not patchname:
1834 if not patchname:
1835 raise util.Abort(
1835 raise util.Abort(
1836 _('need --name to import a patch from -'))
1836 _('need --name to import a patch from -'))
1837 text = sys.stdin.read()
1837 text = sys.stdin.read()
1838 else:
1838 else:
1839 fp = url.open(self.ui, filename)
1839 fp = url.open(self.ui, filename)
1840 text = fp.read()
1840 text = fp.read()
1841 fp.close()
1841 fp.close()
1842 except (OSError, IOError):
1842 except (OSError, IOError):
1843 raise util.Abort(_("unable to read file %s") % filename)
1843 raise util.Abort(_("unable to read file %s") % filename)
1844 if not patchname:
1844 if not patchname:
1845 patchname = normname(os.path.basename(filename))
1845 patchname = normname(os.path.basename(filename))
1846 self.check_reserved_name(patchname)
1846 self.check_reserved_name(patchname)
1847 checkfile(patchname)
1847 checkfile(patchname)
1848 patchf = self.opener(patchname, "w")
1848 patchf = self.opener(patchname, "w")
1849 patchf.write(text)
1849 patchf.write(text)
1850 patchf.close()
1850 patchf.close()
1851 if not force:
1851 if not force:
1852 checkseries(patchname)
1852 checkseries(patchname)
1853 if patchname not in self.series:
1853 if patchname not in self.series:
1854 index = self.full_series_end() + i
1854 index = self.full_series_end() + i
1855 self.full_series[index:index] = [patchname]
1855 self.full_series[index:index] = [patchname]
1856 self.parse_series()
1856 self.parse_series()
1857 self.series_dirty = True
1857 self.series_dirty = True
1858 self.ui.warn(_("adding %s to series file\n") % patchname)
1858 self.ui.warn(_("adding %s to series file\n") % patchname)
1859 self.added.append(patchname)
1859 self.added.append(patchname)
1860 patchname = None
1860 patchname = None
1861
1861
1862 self.removeundo(repo)
1862 self.removeundo(repo)
1863
1863
1864 @command("qdelete|qremove|qrm",
1864 @command("qdelete|qremove|qrm",
1865 [('k', 'keep', None, _('keep patch file')),
1865 [('k', 'keep', None, _('keep patch file')),
1866 ('r', 'rev', [],
1866 ('r', 'rev', [],
1867 _('stop managing a revision (DEPRECATED)'), _('REV'))],
1867 _('stop managing a revision (DEPRECATED)'), _('REV'))],
1868 _('hg qdelete [-k] [PATCH]...'))
1868 _('hg qdelete [-k] [PATCH]...'))
1869 def delete(ui, repo, *patches, **opts):
1869 def delete(ui, repo, *patches, **opts):
1870 """remove patches from queue
1870 """remove patches from queue
1871
1871
1872 The patches must not be applied, and at least one patch is required. With
1872 The patches must not be applied, and at least one patch is required. With
1873 -k/--keep, the patch files are preserved in the patch directory.
1873 -k/--keep, the patch files are preserved in the patch directory.
1874
1874
1875 To stop managing a patch and move it into permanent history,
1875 To stop managing a patch and move it into permanent history,
1876 use the :hg:`qfinish` command."""
1876 use the :hg:`qfinish` command."""
1877 q = repo.mq
1877 q = repo.mq
1878 q.delete(repo, patches, opts)
1878 q.delete(repo, patches, opts)
1879 q.save_dirty()
1879 q.save_dirty()
1880 return 0
1880 return 0
1881
1881
1882 @command("qapplied",
1882 @command("qapplied",
1883 [('1', 'last', None, _('show only the last patch'))
1883 [('1', 'last', None, _('show only the last patch'))
1884 ] + seriesopts,
1884 ] + seriesopts,
1885 _('hg qapplied [-1] [-s] [PATCH]'))
1885 _('hg qapplied [-1] [-s] [PATCH]'))
1886 def applied(ui, repo, patch=None, **opts):
1886 def applied(ui, repo, patch=None, **opts):
1887 """print the patches already applied
1887 """print the patches already applied
1888
1888
1889 Returns 0 on success."""
1889 Returns 0 on success."""
1890
1890
1891 q = repo.mq
1891 q = repo.mq
1892
1892
1893 if patch:
1893 if patch:
1894 if patch not in q.series:
1894 if patch not in q.series:
1895 raise util.Abort(_("patch %s is not in series file") % patch)
1895 raise util.Abort(_("patch %s is not in series file") % patch)
1896 end = q.series.index(patch) + 1
1896 end = q.series.index(patch) + 1
1897 else:
1897 else:
1898 end = q.series_end(True)
1898 end = q.series_end(True)
1899
1899
1900 if opts.get('last') and not end:
1900 if opts.get('last') and not end:
1901 ui.write(_("no patches applied\n"))
1901 ui.write(_("no patches applied\n"))
1902 return 1
1902 return 1
1903 elif opts.get('last') and end == 1:
1903 elif opts.get('last') and end == 1:
1904 ui.write(_("only one patch applied\n"))
1904 ui.write(_("only one patch applied\n"))
1905 return 1
1905 return 1
1906 elif opts.get('last'):
1906 elif opts.get('last'):
1907 start = end - 2
1907 start = end - 2
1908 end = 1
1908 end = 1
1909 else:
1909 else:
1910 start = 0
1910 start = 0
1911
1911
1912 q.qseries(repo, length=end, start=start, status='A',
1912 q.qseries(repo, length=end, start=start, status='A',
1913 summary=opts.get('summary'))
1913 summary=opts.get('summary'))
1914
1914
1915
1915
1916 @command("qunapplied",
1916 @command("qunapplied",
1917 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
1917 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
1918 _('hg qunapplied [-1] [-s] [PATCH]'))
1918 _('hg qunapplied [-1] [-s] [PATCH]'))
1919 def unapplied(ui, repo, patch=None, **opts):
1919 def unapplied(ui, repo, patch=None, **opts):
1920 """print the patches not yet applied
1920 """print the patches not yet applied
1921
1921
1922 Returns 0 on success."""
1922 Returns 0 on success."""
1923
1923
1924 q = repo.mq
1924 q = repo.mq
1925 if patch:
1925 if patch:
1926 if patch not in q.series:
1926 if patch not in q.series:
1927 raise util.Abort(_("patch %s is not in series file") % patch)
1927 raise util.Abort(_("patch %s is not in series file") % patch)
1928 start = q.series.index(patch) + 1
1928 start = q.series.index(patch) + 1
1929 else:
1929 else:
1930 start = q.series_end(True)
1930 start = q.series_end(True)
1931
1931
1932 if start == len(q.series) and opts.get('first'):
1932 if start == len(q.series) and opts.get('first'):
1933 ui.write(_("all patches applied\n"))
1933 ui.write(_("all patches applied\n"))
1934 return 1
1934 return 1
1935
1935
1936 length = opts.get('first') and 1 or None
1936 length = opts.get('first') and 1 or None
1937 q.qseries(repo, start=start, length=length, status='U',
1937 q.qseries(repo, start=start, length=length, status='U',
1938 summary=opts.get('summary'))
1938 summary=opts.get('summary'))
1939
1939
1940 @command("qimport",
1940 @command("qimport",
1941 [('e', 'existing', None, _('import file in patch directory')),
1941 [('e', 'existing', None, _('import file in patch directory')),
1942 ('n', 'name', '',
1942 ('n', 'name', '',
1943 _('name of patch file'), _('NAME')),
1943 _('name of patch file'), _('NAME')),
1944 ('f', 'force', None, _('overwrite existing files')),
1944 ('f', 'force', None, _('overwrite existing files')),
1945 ('r', 'rev', [],
1945 ('r', 'rev', [],
1946 _('place existing revisions under mq control'), _('REV')),
1946 _('place existing revisions under mq control'), _('REV')),
1947 ('g', 'git', None, _('use git extended diff format')),
1947 ('g', 'git', None, _('use git extended diff format')),
1948 ('P', 'push', None, _('qpush after importing'))],
1948 ('P', 'push', None, _('qpush after importing'))],
1949 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...'))
1949 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...'))
1950 def qimport(ui, repo, *filename, **opts):
1950 def qimport(ui, repo, *filename, **opts):
1951 """import a patch
1951 """import a patch
1952
1952
1953 The patch is inserted into the series after the last applied
1953 The patch is inserted into the series after the last applied
1954 patch. If no patches have been applied, qimport prepends the patch
1954 patch. If no patches have been applied, qimport prepends the patch
1955 to the series.
1955 to the series.
1956
1956
1957 The patch will have the same name as its source file unless you
1957 The patch will have the same name as its source file unless you
1958 give it a new one with -n/--name.
1958 give it a new one with -n/--name.
1959
1959
1960 You can register an existing patch inside the patch directory with
1960 You can register an existing patch inside the patch directory with
1961 the -e/--existing flag.
1961 the -e/--existing flag.
1962
1962
1963 With -f/--force, an existing patch of the same name will be
1963 With -f/--force, an existing patch of the same name will be
1964 overwritten.
1964 overwritten.
1965
1965
1966 An existing changeset may be placed under mq control with -r/--rev
1966 An existing changeset may be placed under mq control with -r/--rev
1967 (e.g. qimport --rev tip -n patch will place tip under mq control).
1967 (e.g. qimport --rev tip -n patch will place tip under mq control).
1968 With -g/--git, patches imported with --rev will use the git diff
1968 With -g/--git, patches imported with --rev will use the git diff
1969 format. See the diffs help topic for information on why this is
1969 format. See the diffs help topic for information on why this is
1970 important for preserving rename/copy information and permission
1970 important for preserving rename/copy information and permission
1971 changes. Use :hg:`qfinish` to remove changesets from mq control.
1971 changes. Use :hg:`qfinish` to remove changesets from mq control.
1972
1972
1973 To import a patch from standard input, pass - as the patch file.
1973 To import a patch from standard input, pass - as the patch file.
1974 When importing from standard input, a patch name must be specified
1974 When importing from standard input, a patch name must be specified
1975 using the --name flag.
1975 using the --name flag.
1976
1976
1977 To import an existing patch while renaming it::
1977 To import an existing patch while renaming it::
1978
1978
1979 hg qimport -e existing-patch -n new-name
1979 hg qimport -e existing-patch -n new-name
1980
1980
1981 Returns 0 if import succeeded.
1981 Returns 0 if import succeeded.
1982 """
1982 """
1983 q = repo.mq
1983 q = repo.mq
1984 try:
1984 try:
1985 q.qimport(repo, filename, patchname=opts.get('name'),
1985 q.qimport(repo, filename, patchname=opts.get('name'),
1986 existing=opts.get('existing'), force=opts.get('force'),
1986 existing=opts.get('existing'), force=opts.get('force'),
1987 rev=opts.get('rev'), git=opts.get('git'))
1987 rev=opts.get('rev'), git=opts.get('git'))
1988 finally:
1988 finally:
1989 q.save_dirty()
1989 q.save_dirty()
1990
1990
1991 if opts.get('push') and not opts.get('rev'):
1991 if opts.get('push') and not opts.get('rev'):
1992 return q.push(repo, None)
1992 return q.push(repo, None)
1993 return 0
1993 return 0
1994
1994
1995 def qinit(ui, repo, create):
1995 def qinit(ui, repo, create):
1996 """initialize a new queue repository
1996 """initialize a new queue repository
1997
1997
1998 This command also creates a series file for ordering patches, and
1998 This command also creates a series file for ordering patches, and
1999 an mq-specific .hgignore file in the queue repository, to exclude
1999 an mq-specific .hgignore file in the queue repository, to exclude
2000 the status and guards files (these contain mostly transient state).
2000 the status and guards files (these contain mostly transient state).
2001
2001
2002 Returns 0 if initialization succeeded."""
2002 Returns 0 if initialization succeeded."""
2003 q = repo.mq
2003 q = repo.mq
2004 r = q.init(repo, create)
2004 r = q.init(repo, create)
2005 q.save_dirty()
2005 q.save_dirty()
2006 if r:
2006 if r:
2007 if not os.path.exists(r.wjoin('.hgignore')):
2007 if not os.path.exists(r.wjoin('.hgignore')):
2008 fp = r.wopener('.hgignore', 'w')
2008 fp = r.wopener('.hgignore', 'w')
2009 fp.write('^\\.hg\n')
2009 fp.write('^\\.hg\n')
2010 fp.write('^\\.mq\n')
2010 fp.write('^\\.mq\n')
2011 fp.write('syntax: glob\n')
2011 fp.write('syntax: glob\n')
2012 fp.write('status\n')
2012 fp.write('status\n')
2013 fp.write('guards\n')
2013 fp.write('guards\n')
2014 fp.close()
2014 fp.close()
2015 if not os.path.exists(r.wjoin('series')):
2015 if not os.path.exists(r.wjoin('series')):
2016 r.wopener('series', 'w').close()
2016 r.wopener('series', 'w').close()
2017 r[None].add(['.hgignore', 'series'])
2017 r[None].add(['.hgignore', 'series'])
2018 commands.add(ui, r)
2018 commands.add(ui, r)
2019 return 0
2019 return 0
2020
2020
2021 @command("^qinit",
2021 @command("^qinit",
2022 [('c', 'create-repo', None, _('create queue repository'))],
2022 [('c', 'create-repo', None, _('create queue repository'))],
2023 _('hg qinit [-c]'))
2023 _('hg qinit [-c]'))
2024 def init(ui, repo, **opts):
2024 def init(ui, repo, **opts):
2025 """init a new queue repository (DEPRECATED)
2025 """init a new queue repository (DEPRECATED)
2026
2026
2027 The queue repository is unversioned by default. If
2027 The queue repository is unversioned by default. If
2028 -c/--create-repo is specified, qinit will create a separate nested
2028 -c/--create-repo is specified, qinit will create a separate nested
2029 repository for patches (qinit -c may also be run later to convert
2029 repository for patches (qinit -c may also be run later to convert
2030 an unversioned patch repository into a versioned one). You can use
2030 an unversioned patch repository into a versioned one). You can use
2031 qcommit to commit changes to this queue repository.
2031 qcommit to commit changes to this queue repository.
2032
2032
2033 This command is deprecated. Without -c, it's implied by other relevant
2033 This command is deprecated. Without -c, it's implied by other relevant
2034 commands. With -c, use :hg:`init --mq` instead."""
2034 commands. With -c, use :hg:`init --mq` instead."""
2035 return qinit(ui, repo, create=opts.get('create_repo'))
2035 return qinit(ui, repo, create=opts.get('create_repo'))
2036
2036
2037 @command("qclone",
2037 @command("qclone",
2038 [('', 'pull', None, _('use pull protocol to copy metadata')),
2038 [('', 'pull', None, _('use pull protocol to copy metadata')),
2039 ('U', 'noupdate', None, _('do not update the new working directories')),
2039 ('U', 'noupdate', None, _('do not update the new working directories')),
2040 ('', 'uncompressed', None,
2040 ('', 'uncompressed', None,
2041 _('use uncompressed transfer (fast over LAN)')),
2041 _('use uncompressed transfer (fast over LAN)')),
2042 ('p', 'patches', '',
2042 ('p', 'patches', '',
2043 _('location of source patch repository'), _('REPO')),
2043 _('location of source patch repository'), _('REPO')),
2044 ] + commands.remoteopts,
2044 ] + commands.remoteopts,
2045 _('hg qclone [OPTION]... SOURCE [DEST]'))
2045 _('hg qclone [OPTION]... SOURCE [DEST]'))
2046 def clone(ui, source, dest=None, **opts):
2046 def clone(ui, source, dest=None, **opts):
2047 '''clone main and patch repository at same time
2047 '''clone main and patch repository at same time
2048
2048
2049 If source is local, destination will have no patches applied. If
2049 If source is local, destination will have no patches applied. If
2050 source is remote, this command can not check if patches are
2050 source is remote, this command can not check if patches are
2051 applied in source, so cannot guarantee that patches are not
2051 applied in source, so cannot guarantee that patches are not
2052 applied in destination. If you clone remote repository, be sure
2052 applied in destination. If you clone remote repository, be sure
2053 before that it has no patches applied.
2053 before that it has no patches applied.
2054
2054
2055 Source patch repository is looked for in <src>/.hg/patches by
2055 Source patch repository is looked for in <src>/.hg/patches by
2056 default. Use -p <url> to change.
2056 default. Use -p <url> to change.
2057
2057
2058 The patch directory must be a nested Mercurial repository, as
2058 The patch directory must be a nested Mercurial repository, as
2059 would be created by :hg:`init --mq`.
2059 would be created by :hg:`init --mq`.
2060
2060
2061 Return 0 on success.
2061 Return 0 on success.
2062 '''
2062 '''
2063 def patchdir(repo):
2063 def patchdir(repo):
2064 url = repo.url()
2064 url = repo.url()
2065 if url.endswith('/'):
2065 if url.endswith('/'):
2066 url = url[:-1]
2066 url = url[:-1]
2067 return url + '/.hg/patches'
2067 return url + '/.hg/patches'
2068 if dest is None:
2068 if dest is None:
2069 dest = hg.defaultdest(source)
2069 dest = hg.defaultdest(source)
2070 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
2070 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
2071 if opts.get('patches'):
2071 if opts.get('patches'):
2072 patchespath = ui.expandpath(opts.get('patches'))
2072 patchespath = ui.expandpath(opts.get('patches'))
2073 else:
2073 else:
2074 patchespath = patchdir(sr)
2074 patchespath = patchdir(sr)
2075 try:
2075 try:
2076 hg.repository(ui, patchespath)
2076 hg.repository(ui, patchespath)
2077 except error.RepoError:
2077 except error.RepoError:
2078 raise util.Abort(_('versioned patch repository not found'
2078 raise util.Abort(_('versioned patch repository not found'
2079 ' (see init --mq)'))
2079 ' (see init --mq)'))
2080 qbase, destrev = None, None
2080 qbase, destrev = None, None
2081 if sr.local():
2081 if sr.local():
2082 if sr.mq.applied:
2082 if sr.mq.applied:
2083 qbase = sr.mq.applied[0].node
2083 qbase = sr.mq.applied[0].node
2084 if not hg.islocal(dest):
2084 if not hg.islocal(dest):
2085 heads = set(sr.heads())
2085 heads = set(sr.heads())
2086 destrev = list(heads.difference(sr.heads(qbase)))
2086 destrev = list(heads.difference(sr.heads(qbase)))
2087 destrev.append(sr.changelog.parents(qbase)[0])
2087 destrev.append(sr.changelog.parents(qbase)[0])
2088 elif sr.capable('lookup'):
2088 elif sr.capable('lookup'):
2089 try:
2089 try:
2090 qbase = sr.lookup('qbase')
2090 qbase = sr.lookup('qbase')
2091 except error.RepoError:
2091 except error.RepoError:
2092 pass
2092 pass
2093 ui.note(_('cloning main repository\n'))
2093 ui.note(_('cloning main repository\n'))
2094 sr, dr = hg.clone(ui, sr.url(), dest,
2094 sr, dr = hg.clone(ui, sr.url(), dest,
2095 pull=opts.get('pull'),
2095 pull=opts.get('pull'),
2096 rev=destrev,
2096 rev=destrev,
2097 update=False,
2097 update=False,
2098 stream=opts.get('uncompressed'))
2098 stream=opts.get('uncompressed'))
2099 ui.note(_('cloning patch repository\n'))
2099 ui.note(_('cloning patch repository\n'))
2100 hg.clone(ui, opts.get('patches') or patchdir(sr), patchdir(dr),
2100 hg.clone(ui, opts.get('patches') or patchdir(sr), patchdir(dr),
2101 pull=opts.get('pull'), update=not opts.get('noupdate'),
2101 pull=opts.get('pull'), update=not opts.get('noupdate'),
2102 stream=opts.get('uncompressed'))
2102 stream=opts.get('uncompressed'))
2103 if dr.local():
2103 if dr.local():
2104 if qbase:
2104 if qbase:
2105 ui.note(_('stripping applied patches from destination '
2105 ui.note(_('stripping applied patches from destination '
2106 'repository\n'))
2106 'repository\n'))
2107 dr.mq.strip(dr, [qbase], update=False, backup=None)
2107 dr.mq.strip(dr, [qbase], update=False, backup=None)
2108 if not opts.get('noupdate'):
2108 if not opts.get('noupdate'):
2109 ui.note(_('updating destination repository\n'))
2109 ui.note(_('updating destination repository\n'))
2110 hg.update(dr, dr.changelog.tip())
2110 hg.update(dr, dr.changelog.tip())
2111
2111
2112 @command("qcommit|qci",
2112 @command("qcommit|qci",
2113 commands.table["^commit|ci"][1],
2113 commands.table["^commit|ci"][1],
2114 _('hg qcommit [OPTION]... [FILE]...'))
2114 _('hg qcommit [OPTION]... [FILE]...'))
2115 def commit(ui, repo, *pats, **opts):
2115 def commit(ui, repo, *pats, **opts):
2116 """commit changes in the queue repository (DEPRECATED)
2116 """commit changes in the queue repository (DEPRECATED)
2117
2117
2118 This command is deprecated; use :hg:`commit --mq` instead."""
2118 This command is deprecated; use :hg:`commit --mq` instead."""
2119 q = repo.mq
2119 q = repo.mq
2120 r = q.qrepo()
2120 r = q.qrepo()
2121 if not r:
2121 if not r:
2122 raise util.Abort('no queue repository')
2122 raise util.Abort('no queue repository')
2123 commands.commit(r.ui, r, *pats, **opts)
2123 commands.commit(r.ui, r, *pats, **opts)
2124
2124
2125 @command("qseries",
2125 @command("qseries",
2126 [('m', 'missing', None, _('print patches not in series')),
2126 [('m', 'missing', None, _('print patches not in series')),
2127 ] + seriesopts,
2127 ] + seriesopts,
2128 _('hg qseries [-ms]'))
2128 _('hg qseries [-ms]'))
2129 def series(ui, repo, **opts):
2129 def series(ui, repo, **opts):
2130 """print the entire series file
2130 """print the entire series file
2131
2131
2132 Returns 0 on success."""
2132 Returns 0 on success."""
2133 repo.mq.qseries(repo, missing=opts.get('missing'), summary=opts.get('summary'))
2133 repo.mq.qseries(repo, missing=opts.get('missing'), summary=opts.get('summary'))
2134 return 0
2134 return 0
2135
2135
2136 @command("qtop", [] + seriesopts, _('hg qtop [-s]'))
2136 @command("qtop", [] + seriesopts, _('hg qtop [-s]'))
2137 def top(ui, repo, **opts):
2137 def top(ui, repo, **opts):
2138 """print the name of the current patch
2138 """print the name of the current patch
2139
2139
2140 Returns 0 on success."""
2140 Returns 0 on success."""
2141 q = repo.mq
2141 q = repo.mq
2142 t = q.applied and q.series_end(True) or 0
2142 t = q.applied and q.series_end(True) or 0
2143 if t:
2143 if t:
2144 q.qseries(repo, start=t - 1, length=1, status='A',
2144 q.qseries(repo, start=t - 1, length=1, status='A',
2145 summary=opts.get('summary'))
2145 summary=opts.get('summary'))
2146 else:
2146 else:
2147 ui.write(_("no patches applied\n"))
2147 ui.write(_("no patches applied\n"))
2148 return 1
2148 return 1
2149
2149
2150 @command("qnext", [] + seriesopts, _('hg qnext [-s]'))
2150 @command("qnext", [] + seriesopts, _('hg qnext [-s]'))
2151 def next(ui, repo, **opts):
2151 def next(ui, repo, **opts):
2152 """print the name of the next patch
2152 """print the name of the next patch
2153
2153
2154 Returns 0 on success."""
2154 Returns 0 on success."""
2155 q = repo.mq
2155 q = repo.mq
2156 end = q.series_end()
2156 end = q.series_end()
2157 if end == len(q.series):
2157 if end == len(q.series):
2158 ui.write(_("all patches applied\n"))
2158 ui.write(_("all patches applied\n"))
2159 return 1
2159 return 1
2160 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2160 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2161
2161
2162 @command("qprev", [] + seriesopts, _('hg qprev [-s]'))
2162 @command("qprev", [] + seriesopts, _('hg qprev [-s]'))
2163 def prev(ui, repo, **opts):
2163 def prev(ui, repo, **opts):
2164 """print the name of the previous patch
2164 """print the name of the previous patch
2165
2165
2166 Returns 0 on success."""
2166 Returns 0 on success."""
2167 q = repo.mq
2167 q = repo.mq
2168 l = len(q.applied)
2168 l = len(q.applied)
2169 if l == 1:
2169 if l == 1:
2170 ui.write(_("only one patch applied\n"))
2170 ui.write(_("only one patch applied\n"))
2171 return 1
2171 return 1
2172 if not l:
2172 if not l:
2173 ui.write(_("no patches applied\n"))
2173 ui.write(_("no patches applied\n"))
2174 return 1
2174 return 1
2175 q.qseries(repo, start=l - 2, length=1, status='A',
2175 q.qseries(repo, start=l - 2, length=1, status='A',
2176 summary=opts.get('summary'))
2176 summary=opts.get('summary'))
2177
2177
2178 def setupheaderopts(ui, opts):
2178 def setupheaderopts(ui, opts):
2179 if not opts.get('user') and opts.get('currentuser'):
2179 if not opts.get('user') and opts.get('currentuser'):
2180 opts['user'] = ui.username()
2180 opts['user'] = ui.username()
2181 if not opts.get('date') and opts.get('currentdate'):
2181 if not opts.get('date') and opts.get('currentdate'):
2182 opts['date'] = "%d %d" % util.makedate()
2182 opts['date'] = "%d %d" % util.makedate()
2183
2183
2184 @command("^qnew",
2184 @command("^qnew",
2185 [('e', 'edit', None, _('edit commit message')),
2185 [('e', 'edit', None, _('edit commit message')),
2186 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2186 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2187 ('g', 'git', None, _('use git extended diff format')),
2187 ('g', 'git', None, _('use git extended diff format')),
2188 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2188 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2189 ('u', 'user', '',
2189 ('u', 'user', '',
2190 _('add "From: <USER>" to patch'), _('USER')),
2190 _('add "From: <USER>" to patch'), _('USER')),
2191 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2191 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2192 ('d', 'date', '',
2192 ('d', 'date', '',
2193 _('add "Date: <DATE>" to patch'), _('DATE'))
2193 _('add "Date: <DATE>" to patch'), _('DATE'))
2194 ] + commands.walkopts + commands.commitopts,
2194 ] + commands.walkopts + commands.commitopts,
2195 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2195 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'))
2196 def new(ui, repo, patch, *args, **opts):
2196 def new(ui, repo, patch, *args, **opts):
2197 """create a new patch
2197 """create a new patch
2198
2198
2199 qnew creates a new patch on top of the currently-applied patch (if
2199 qnew creates a new patch on top of the currently-applied patch (if
2200 any). The patch will be initialized with any outstanding changes
2200 any). The patch will be initialized with any outstanding changes
2201 in the working directory. You may also use -I/--include,
2201 in the working directory. You may also use -I/--include,
2202 -X/--exclude, and/or a list of files after the patch name to add
2202 -X/--exclude, and/or a list of files after the patch name to add
2203 only changes to matching files to the new patch, leaving the rest
2203 only changes to matching files to the new patch, leaving the rest
2204 as uncommitted modifications.
2204 as uncommitted modifications.
2205
2205
2206 -u/--user and -d/--date can be used to set the (given) user and
2206 -u/--user and -d/--date can be used to set the (given) user and
2207 date, respectively. -U/--currentuser and -D/--currentdate set user
2207 date, respectively. -U/--currentuser and -D/--currentdate set user
2208 to current user and date to current date.
2208 to current user and date to current date.
2209
2209
2210 -e/--edit, -m/--message or -l/--logfile set the patch header as
2210 -e/--edit, -m/--message or -l/--logfile set the patch header as
2211 well as the commit message. If none is specified, the header is
2211 well as the commit message. If none is specified, the header is
2212 empty and the commit message is '[mq]: PATCH'.
2212 empty and the commit message is '[mq]: PATCH'.
2213
2213
2214 Use the -g/--git option to keep the patch in the git extended diff
2214 Use the -g/--git option to keep the patch in the git extended diff
2215 format. Read the diffs help topic for more information on why this
2215 format. Read the diffs help topic for more information on why this
2216 is important for preserving permission changes and copy/rename
2216 is important for preserving permission changes and copy/rename
2217 information.
2217 information.
2218
2218
2219 Returns 0 on successful creation of a new patch.
2219 Returns 0 on successful creation of a new patch.
2220 """
2220 """
2221 msg = cmdutil.logmessage(opts)
2221 msg = cmdutil.logmessage(opts)
2222 def getmsg():
2222 def getmsg():
2223 return ui.edit(msg, opts.get('user') or ui.username())
2223 return ui.edit(msg, opts.get('user') or ui.username())
2224 q = repo.mq
2224 q = repo.mq
2225 opts['msg'] = msg
2225 opts['msg'] = msg
2226 if opts.get('edit'):
2226 if opts.get('edit'):
2227 opts['msg'] = getmsg
2227 opts['msg'] = getmsg
2228 else:
2228 else:
2229 opts['msg'] = msg
2229 opts['msg'] = msg
2230 setupheaderopts(ui, opts)
2230 setupheaderopts(ui, opts)
2231 q.new(repo, patch, *args, **opts)
2231 q.new(repo, patch, *args, **opts)
2232 q.save_dirty()
2232 q.save_dirty()
2233 return 0
2233 return 0
2234
2234
2235 @command("^qrefresh",
2235 @command("^qrefresh",
2236 [('e', 'edit', None, _('edit commit message')),
2236 [('e', 'edit', None, _('edit commit message')),
2237 ('g', 'git', None, _('use git extended diff format')),
2237 ('g', 'git', None, _('use git extended diff format')),
2238 ('s', 'short', None,
2238 ('s', 'short', None,
2239 _('refresh only files already in the patch and specified files')),
2239 _('refresh only files already in the patch and specified files')),
2240 ('U', 'currentuser', None,
2240 ('U', 'currentuser', None,
2241 _('add/update author field in patch with current user')),
2241 _('add/update author field in patch with current user')),
2242 ('u', 'user', '',
2242 ('u', 'user', '',
2243 _('add/update author field in patch with given user'), _('USER')),
2243 _('add/update author field in patch with given user'), _('USER')),
2244 ('D', 'currentdate', None,
2244 ('D', 'currentdate', None,
2245 _('add/update date field in patch with current date')),
2245 _('add/update date field in patch with current date')),
2246 ('d', 'date', '',
2246 ('d', 'date', '',
2247 _('add/update date field in patch with given date'), _('DATE'))
2247 _('add/update date field in patch with given date'), _('DATE'))
2248 ] + commands.walkopts + commands.commitopts,
2248 ] + commands.walkopts + commands.commitopts,
2249 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2249 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'))
2250 def refresh(ui, repo, *pats, **opts):
2250 def refresh(ui, repo, *pats, **opts):
2251 """update the current patch
2251 """update the current patch
2252
2252
2253 If any file patterns are provided, the refreshed patch will
2253 If any file patterns are provided, the refreshed patch will
2254 contain only the modifications that match those patterns; the
2254 contain only the modifications that match those patterns; the
2255 remaining modifications will remain in the working directory.
2255 remaining modifications will remain in the working directory.
2256
2256
2257 If -s/--short is specified, files currently included in the patch
2257 If -s/--short is specified, files currently included in the patch
2258 will be refreshed just like matched files and remain in the patch.
2258 will be refreshed just like matched files and remain in the patch.
2259
2259
2260 If -e/--edit is specified, Mercurial will start your configured editor for
2260 If -e/--edit is specified, Mercurial will start your configured editor for
2261 you to enter a message. In case qrefresh fails, you will find a backup of
2261 you to enter a message. In case qrefresh fails, you will find a backup of
2262 your message in ``.hg/last-message.txt``.
2262 your message in ``.hg/last-message.txt``.
2263
2263
2264 hg add/remove/copy/rename work as usual, though you might want to
2264 hg add/remove/copy/rename work as usual, though you might want to
2265 use git-style patches (-g/--git or [diff] git=1) to track copies
2265 use git-style patches (-g/--git or [diff] git=1) to track copies
2266 and renames. See the diffs help topic for more information on the
2266 and renames. See the diffs help topic for more information on the
2267 git diff format.
2267 git diff format.
2268
2268
2269 Returns 0 on success.
2269 Returns 0 on success.
2270 """
2270 """
2271 q = repo.mq
2271 q = repo.mq
2272 message = cmdutil.logmessage(opts)
2272 message = cmdutil.logmessage(opts)
2273 if opts.get('edit'):
2273 if opts.get('edit'):
2274 if not q.applied:
2274 if not q.applied:
2275 ui.write(_("no patches applied\n"))
2275 ui.write(_("no patches applied\n"))
2276 return 1
2276 return 1
2277 if message:
2277 if message:
2278 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2278 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2279 patch = q.applied[-1].name
2279 patch = q.applied[-1].name
2280 ph = patchheader(q.join(patch), q.plainmode)
2280 ph = patchheader(q.join(patch), q.plainmode)
2281 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2281 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2282 # We don't want to lose the patch message if qrefresh fails (issue2062)
2282 # We don't want to lose the patch message if qrefresh fails (issue2062)
2283 msgfile = repo.opener('last-message.txt', 'wb')
2283 msgfile = repo.opener('last-message.txt', 'wb')
2284 msgfile.write(message)
2284 msgfile.write(message)
2285 msgfile.close()
2285 msgfile.close()
2286 setupheaderopts(ui, opts)
2286 setupheaderopts(ui, opts)
2287 ret = q.refresh(repo, pats, msg=message, **opts)
2287 ret = q.refresh(repo, pats, msg=message, **opts)
2288 q.save_dirty()
2288 q.save_dirty()
2289 return ret
2289 return ret
2290
2290
2291 @command("^qdiff",
2291 @command("^qdiff",
2292 commands.diffopts + commands.diffopts2 + commands.walkopts,
2292 commands.diffopts + commands.diffopts2 + commands.walkopts,
2293 _('hg qdiff [OPTION]... [FILE]...'))
2293 _('hg qdiff [OPTION]... [FILE]...'))
2294 def diff(ui, repo, *pats, **opts):
2294 def diff(ui, repo, *pats, **opts):
2295 """diff of the current patch and subsequent modifications
2295 """diff of the current patch and subsequent modifications
2296
2296
2297 Shows a diff which includes the current patch as well as any
2297 Shows a diff which includes the current patch as well as any
2298 changes which have been made in the working directory since the
2298 changes which have been made in the working directory since the
2299 last refresh (thus showing what the current patch would become
2299 last refresh (thus showing what the current patch would become
2300 after a qrefresh).
2300 after a qrefresh).
2301
2301
2302 Use :hg:`diff` if you only want to see the changes made since the
2302 Use :hg:`diff` if you only want to see the changes made since the
2303 last qrefresh, or :hg:`export qtip` if you want to see changes
2303 last qrefresh, or :hg:`export qtip` if you want to see changes
2304 made by the current patch without including changes made since the
2304 made by the current patch without including changes made since the
2305 qrefresh.
2305 qrefresh.
2306
2306
2307 Returns 0 on success.
2307 Returns 0 on success.
2308 """
2308 """
2309 repo.mq.diff(repo, pats, opts)
2309 repo.mq.diff(repo, pats, opts)
2310 return 0
2310 return 0
2311
2311
2312 @command('qfold',
2312 @command('qfold',
2313 [('e', 'edit', None, _('edit patch header')),
2313 [('e', 'edit', None, _('edit patch header')),
2314 ('k', 'keep', None, _('keep folded patch files')),
2314 ('k', 'keep', None, _('keep folded patch files')),
2315 ] + commands.commitopts,
2315 ] + commands.commitopts,
2316 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2316 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'))
2317 def fold(ui, repo, *files, **opts):
2317 def fold(ui, repo, *files, **opts):
2318 """fold the named patches into the current patch
2318 """fold the named patches into the current patch
2319
2319
2320 Patches must not yet be applied. Each patch will be successively
2320 Patches must not yet be applied. Each patch will be successively
2321 applied to the current patch in the order given. If all the
2321 applied to the current patch in the order given. If all the
2322 patches apply successfully, the current patch will be refreshed
2322 patches apply successfully, the current patch will be refreshed
2323 with the new cumulative patch, and the folded patches will be
2323 with the new cumulative patch, and the folded patches will be
2324 deleted. With -k/--keep, the folded patch files will not be
2324 deleted. With -k/--keep, the folded patch files will not be
2325 removed afterwards.
2325 removed afterwards.
2326
2326
2327 The header for each folded patch will be concatenated with the
2327 The header for each folded patch will be concatenated with the
2328 current patch header, separated by a line of ``* * *``.
2328 current patch header, separated by a line of ``* * *``.
2329
2329
2330 Returns 0 on success."""
2330 Returns 0 on success."""
2331
2331
2332 q = repo.mq
2332 q = repo.mq
2333
2333
2334 if not files:
2334 if not files:
2335 raise util.Abort(_('qfold requires at least one patch name'))
2335 raise util.Abort(_('qfold requires at least one patch name'))
2336 if not q.check_toppatch(repo)[0]:
2336 if not q.check_toppatch(repo)[0]:
2337 raise util.Abort(_('no patches applied'))
2337 raise util.Abort(_('no patches applied'))
2338 q.check_localchanges(repo)
2338 q.check_localchanges(repo)
2339
2339
2340 message = cmdutil.logmessage(opts)
2340 message = cmdutil.logmessage(opts)
2341 if opts.get('edit'):
2341 if opts.get('edit'):
2342 if message:
2342 if message:
2343 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2343 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2344
2344
2345 parent = q.lookup('qtip')
2345 parent = q.lookup('qtip')
2346 patches = []
2346 patches = []
2347 messages = []
2347 messages = []
2348 for f in files:
2348 for f in files:
2349 p = q.lookup(f)
2349 p = q.lookup(f)
2350 if p in patches or p == parent:
2350 if p in patches or p == parent:
2351 ui.warn(_('Skipping already folded patch %s\n') % p)
2351 ui.warn(_('Skipping already folded patch %s\n') % p)
2352 if q.isapplied(p):
2352 if q.isapplied(p):
2353 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2353 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2354 patches.append(p)
2354 patches.append(p)
2355
2355
2356 for p in patches:
2356 for p in patches:
2357 if not message:
2357 if not message:
2358 ph = patchheader(q.join(p), q.plainmode)
2358 ph = patchheader(q.join(p), q.plainmode)
2359 if ph.message:
2359 if ph.message:
2360 messages.append(ph.message)
2360 messages.append(ph.message)
2361 pf = q.join(p)
2361 pf = q.join(p)
2362 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2362 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2363 if not patchsuccess:
2363 if not patchsuccess:
2364 raise util.Abort(_('error folding patch %s') % p)
2364 raise util.Abort(_('error folding patch %s') % p)
2365
2365
2366 if not message:
2366 if not message:
2367 ph = patchheader(q.join(parent), q.plainmode)
2367 ph = patchheader(q.join(parent), q.plainmode)
2368 message, user = ph.message, ph.user
2368 message, user = ph.message, ph.user
2369 for msg in messages:
2369 for msg in messages:
2370 message.append('* * *')
2370 message.append('* * *')
2371 message.extend(msg)
2371 message.extend(msg)
2372 message = '\n'.join(message)
2372 message = '\n'.join(message)
2373
2373
2374 if opts.get('edit'):
2374 if opts.get('edit'):
2375 message = ui.edit(message, user or ui.username())
2375 message = ui.edit(message, user or ui.username())
2376
2376
2377 diffopts = q.patchopts(q.diffopts(), *patches)
2377 diffopts = q.patchopts(q.diffopts(), *patches)
2378 q.refresh(repo, msg=message, git=diffopts.git)
2378 q.refresh(repo, msg=message, git=diffopts.git)
2379 q.delete(repo, patches, opts)
2379 q.delete(repo, patches, opts)
2380 q.save_dirty()
2380 q.save_dirty()
2381
2381
2382 @command("qgoto",
2382 @command("qgoto",
2383 [('f', 'force', None, _('overwrite any local changes'))],
2383 [('f', 'force', None, _('overwrite any local changes'))],
2384 _('hg qgoto [OPTION]... PATCH'))
2384 _('hg qgoto [OPTION]... PATCH'))
2385 def goto(ui, repo, patch, **opts):
2385 def goto(ui, repo, patch, **opts):
2386 '''push or pop patches until named patch is at top of stack
2386 '''push or pop patches until named patch is at top of stack
2387
2387
2388 Returns 0 on success.'''
2388 Returns 0 on success.'''
2389 q = repo.mq
2389 q = repo.mq
2390 patch = q.lookup(patch)
2390 patch = q.lookup(patch)
2391 if q.isapplied(patch):
2391 if q.isapplied(patch):
2392 ret = q.pop(repo, patch, force=opts.get('force'))
2392 ret = q.pop(repo, patch, force=opts.get('force'))
2393 else:
2393 else:
2394 ret = q.push(repo, patch, force=opts.get('force'))
2394 ret = q.push(repo, patch, force=opts.get('force'))
2395 q.save_dirty()
2395 q.save_dirty()
2396 return ret
2396 return ret
2397
2397
2398 @command("qguard",
2398 @command("qguard",
2399 [('l', 'list', None, _('list all patches and guards')),
2399 [('l', 'list', None, _('list all patches and guards')),
2400 ('n', 'none', None, _('drop all guards'))],
2400 ('n', 'none', None, _('drop all guards'))],
2401 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2401 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'))
2402 def guard(ui, repo, *args, **opts):
2402 def guard(ui, repo, *args, **opts):
2403 '''set or print guards for a patch
2403 '''set or print guards for a patch
2404
2404
2405 Guards control whether a patch can be pushed. A patch with no
2405 Guards control whether a patch can be pushed. A patch with no
2406 guards is always pushed. A patch with a positive guard ("+foo") is
2406 guards is always pushed. A patch with a positive guard ("+foo") is
2407 pushed only if the :hg:`qselect` command has activated it. A patch with
2407 pushed only if the :hg:`qselect` command has activated it. A patch with
2408 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2408 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2409 has activated it.
2409 has activated it.
2410
2410
2411 With no arguments, print the currently active guards.
2411 With no arguments, print the currently active guards.
2412 With arguments, set guards for the named patch.
2412 With arguments, set guards for the named patch.
2413
2413
2414 .. note::
2414 .. note::
2415 Specifying negative guards now requires '--'.
2415 Specifying negative guards now requires '--'.
2416
2416
2417 To set guards on another patch::
2417 To set guards on another patch::
2418
2418
2419 hg qguard other.patch -- +2.6.17 -stable
2419 hg qguard other.patch -- +2.6.17 -stable
2420
2420
2421 Returns 0 on success.
2421 Returns 0 on success.
2422 '''
2422 '''
2423 def status(idx):
2423 def status(idx):
2424 guards = q.series_guards[idx] or ['unguarded']
2424 guards = q.series_guards[idx] or ['unguarded']
2425 if q.series[idx] in applied:
2425 if q.series[idx] in applied:
2426 state = 'applied'
2426 state = 'applied'
2427 elif q.pushable(idx)[0]:
2427 elif q.pushable(idx)[0]:
2428 state = 'unapplied'
2428 state = 'unapplied'
2429 else:
2429 else:
2430 state = 'guarded'
2430 state = 'guarded'
2431 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2431 label = 'qguard.patch qguard.%s qseries.%s' % (state, state)
2432 ui.write('%s: ' % ui.label(q.series[idx], label))
2432 ui.write('%s: ' % ui.label(q.series[idx], label))
2433
2433
2434 for i, guard in enumerate(guards):
2434 for i, guard in enumerate(guards):
2435 if guard.startswith('+'):
2435 if guard.startswith('+'):
2436 ui.write(guard, label='qguard.positive')
2436 ui.write(guard, label='qguard.positive')
2437 elif guard.startswith('-'):
2437 elif guard.startswith('-'):
2438 ui.write(guard, label='qguard.negative')
2438 ui.write(guard, label='qguard.negative')
2439 else:
2439 else:
2440 ui.write(guard, label='qguard.unguarded')
2440 ui.write(guard, label='qguard.unguarded')
2441 if i != len(guards) - 1:
2441 if i != len(guards) - 1:
2442 ui.write(' ')
2442 ui.write(' ')
2443 ui.write('\n')
2443 ui.write('\n')
2444 q = repo.mq
2444 q = repo.mq
2445 applied = set(p.name for p in q.applied)
2445 applied = set(p.name for p in q.applied)
2446 patch = None
2446 patch = None
2447 args = list(args)
2447 args = list(args)
2448 if opts.get('list'):
2448 if opts.get('list'):
2449 if args or opts.get('none'):
2449 if args or opts.get('none'):
2450 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2450 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2451 for i in xrange(len(q.series)):
2451 for i in xrange(len(q.series)):
2452 status(i)
2452 status(i)
2453 return
2453 return
2454 if not args or args[0][0:1] in '-+':
2454 if not args or args[0][0:1] in '-+':
2455 if not q.applied:
2455 if not q.applied:
2456 raise util.Abort(_('no patches applied'))
2456 raise util.Abort(_('no patches applied'))
2457 patch = q.applied[-1].name
2457 patch = q.applied[-1].name
2458 if patch is None and args[0][0:1] not in '-+':
2458 if patch is None and args[0][0:1] not in '-+':
2459 patch = args.pop(0)
2459 patch = args.pop(0)
2460 if patch is None:
2460 if patch is None:
2461 raise util.Abort(_('no patch to work with'))
2461 raise util.Abort(_('no patch to work with'))
2462 if args or opts.get('none'):
2462 if args or opts.get('none'):
2463 idx = q.find_series(patch)
2463 idx = q.find_series(patch)
2464 if idx is None:
2464 if idx is None:
2465 raise util.Abort(_('no patch named %s') % patch)
2465 raise util.Abort(_('no patch named %s') % patch)
2466 q.set_guards(idx, args)
2466 q.set_guards(idx, args)
2467 q.save_dirty()
2467 q.save_dirty()
2468 else:
2468 else:
2469 status(q.series.index(q.lookup(patch)))
2469 status(q.series.index(q.lookup(patch)))
2470
2470
2471 @command("qheader", [], _('hg qheader [PATCH]'))
2471 @command("qheader", [], _('hg qheader [PATCH]'))
2472 def header(ui, repo, patch=None):
2472 def header(ui, repo, patch=None):
2473 """print the header of the topmost or specified patch
2473 """print the header of the topmost or specified patch
2474
2474
2475 Returns 0 on success."""
2475 Returns 0 on success."""
2476 q = repo.mq
2476 q = repo.mq
2477
2477
2478 if patch:
2478 if patch:
2479 patch = q.lookup(patch)
2479 patch = q.lookup(patch)
2480 else:
2480 else:
2481 if not q.applied:
2481 if not q.applied:
2482 ui.write(_('no patches applied\n'))
2482 ui.write(_('no patches applied\n'))
2483 return 1
2483 return 1
2484 patch = q.lookup('qtip')
2484 patch = q.lookup('qtip')
2485 ph = patchheader(q.join(patch), q.plainmode)
2485 ph = patchheader(q.join(patch), q.plainmode)
2486
2486
2487 ui.write('\n'.join(ph.message) + '\n')
2487 ui.write('\n'.join(ph.message) + '\n')
2488
2488
2489 def lastsavename(path):
2489 def lastsavename(path):
2490 (directory, base) = os.path.split(path)
2490 (directory, base) = os.path.split(path)
2491 names = os.listdir(directory)
2491 names = os.listdir(directory)
2492 namere = re.compile("%s.([0-9]+)" % base)
2492 namere = re.compile("%s.([0-9]+)" % base)
2493 maxindex = None
2493 maxindex = None
2494 maxname = None
2494 maxname = None
2495 for f in names:
2495 for f in names:
2496 m = namere.match(f)
2496 m = namere.match(f)
2497 if m:
2497 if m:
2498 index = int(m.group(1))
2498 index = int(m.group(1))
2499 if maxindex is None or index > maxindex:
2499 if maxindex is None or index > maxindex:
2500 maxindex = index
2500 maxindex = index
2501 maxname = f
2501 maxname = f
2502 if maxname:
2502 if maxname:
2503 return (os.path.join(directory, maxname), maxindex)
2503 return (os.path.join(directory, maxname), maxindex)
2504 return (None, None)
2504 return (None, None)
2505
2505
2506 def savename(path):
2506 def savename(path):
2507 (last, index) = lastsavename(path)
2507 (last, index) = lastsavename(path)
2508 if last is None:
2508 if last is None:
2509 index = 0
2509 index = 0
2510 newpath = path + ".%d" % (index + 1)
2510 newpath = path + ".%d" % (index + 1)
2511 return newpath
2511 return newpath
2512
2512
2513 @command("^qpush",
2513 @command("^qpush",
2514 [('f', 'force', None, _('apply on top of local changes')),
2514 [('f', 'force', None, _('apply on top of local changes')),
2515 ('e', 'exact', None, _('apply the target patch to its recorded parent')),
2515 ('e', 'exact', None, _('apply the target patch to its recorded parent')),
2516 ('l', 'list', None, _('list patch name in commit text')),
2516 ('l', 'list', None, _('list patch name in commit text')),
2517 ('a', 'all', None, _('apply all patches')),
2517 ('a', 'all', None, _('apply all patches')),
2518 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2518 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2519 ('n', 'name', '',
2519 ('n', 'name', '',
2520 _('merge queue name (DEPRECATED)'), _('NAME')),
2520 _('merge queue name (DEPRECATED)'), _('NAME')),
2521 ('', 'move', None, _('reorder patch series and apply only the patch'))],
2521 ('', 'move', None, _('reorder patch series and apply only the patch'))],
2522 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2522 _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'))
2523 def push(ui, repo, patch=None, **opts):
2523 def push(ui, repo, patch=None, **opts):
2524 """push the next patch onto the stack
2524 """push the next patch onto the stack
2525
2525
2526 When -f/--force is applied, all local changes in patched files
2526 When -f/--force is applied, all local changes in patched files
2527 will be lost.
2527 will be lost.
2528
2528
2529 Return 0 on success.
2529 Return 0 on success.
2530 """
2530 """
2531 q = repo.mq
2531 q = repo.mq
2532 mergeq = None
2532 mergeq = None
2533
2533
2534 if opts.get('merge'):
2534 if opts.get('merge'):
2535 if opts.get('name'):
2535 if opts.get('name'):
2536 newpath = repo.join(opts.get('name'))
2536 newpath = repo.join(opts.get('name'))
2537 else:
2537 else:
2538 newpath, i = lastsavename(q.path)
2538 newpath, i = lastsavename(q.path)
2539 if not newpath:
2539 if not newpath:
2540 ui.warn(_("no saved queues found, please use -n\n"))
2540 ui.warn(_("no saved queues found, please use -n\n"))
2541 return 1
2541 return 1
2542 mergeq = queue(ui, repo.join(""), newpath)
2542 mergeq = queue(ui, repo.join(""), newpath)
2543 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2543 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2544 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2544 ret = q.push(repo, patch, force=opts.get('force'), list=opts.get('list'),
2545 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2545 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'),
2546 exact=opts.get('exact'))
2546 exact=opts.get('exact'))
2547 return ret
2547 return ret
2548
2548
2549 @command("^qpop",
2549 @command("^qpop",
2550 [('a', 'all', None, _('pop all patches')),
2550 [('a', 'all', None, _('pop all patches')),
2551 ('n', 'name', '',
2551 ('n', 'name', '',
2552 _('queue name to pop (DEPRECATED)'), _('NAME')),
2552 _('queue name to pop (DEPRECATED)'), _('NAME')),
2553 ('f', 'force', None, _('forget any local changes to patched files'))],
2553 ('f', 'force', None, _('forget any local changes to patched files'))],
2554 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2554 _('hg qpop [-a] [-f] [PATCH | INDEX]'))
2555 def pop(ui, repo, patch=None, **opts):
2555 def pop(ui, repo, patch=None, **opts):
2556 """pop the current patch off the stack
2556 """pop the current patch off the stack
2557
2557
2558 By default, pops off the top of the patch stack. If given a patch
2558 By default, pops off the top of the patch stack. If given a patch
2559 name, keeps popping off patches until the named patch is at the
2559 name, keeps popping off patches until the named patch is at the
2560 top of the stack.
2560 top of the stack.
2561
2561
2562 Return 0 on success.
2562 Return 0 on success.
2563 """
2563 """
2564 localupdate = True
2564 localupdate = True
2565 if opts.get('name'):
2565 if opts.get('name'):
2566 q = queue(ui, repo.join(""), repo.join(opts.get('name')))
2566 q = queue(ui, repo.join(""), repo.join(opts.get('name')))
2567 ui.warn(_('using patch queue: %s\n') % q.path)
2567 ui.warn(_('using patch queue: %s\n') % q.path)
2568 localupdate = False
2568 localupdate = False
2569 else:
2569 else:
2570 q = repo.mq
2570 q = repo.mq
2571 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2571 ret = q.pop(repo, patch, force=opts.get('force'), update=localupdate,
2572 all=opts.get('all'))
2572 all=opts.get('all'))
2573 q.save_dirty()
2573 q.save_dirty()
2574 return ret
2574 return ret
2575
2575
2576 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2576 @command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]'))
2577 def rename(ui, repo, patch, name=None, **opts):
2577 def rename(ui, repo, patch, name=None, **opts):
2578 """rename a patch
2578 """rename a patch
2579
2579
2580 With one argument, renames the current patch to PATCH1.
2580 With one argument, renames the current patch to PATCH1.
2581 With two arguments, renames PATCH1 to PATCH2.
2581 With two arguments, renames PATCH1 to PATCH2.
2582
2582
2583 Returns 0 on success."""
2583 Returns 0 on success."""
2584
2584
2585 q = repo.mq
2585 q = repo.mq
2586
2586
2587 if not name:
2587 if not name:
2588 name = patch
2588 name = patch
2589 patch = None
2589 patch = None
2590
2590
2591 if patch:
2591 if patch:
2592 patch = q.lookup(patch)
2592 patch = q.lookup(patch)
2593 else:
2593 else:
2594 if not q.applied:
2594 if not q.applied:
2595 ui.write(_('no patches applied\n'))
2595 ui.write(_('no patches applied\n'))
2596 return
2596 return
2597 patch = q.lookup('qtip')
2597 patch = q.lookup('qtip')
2598 absdest = q.join(name)
2598 absdest = q.join(name)
2599 if os.path.isdir(absdest):
2599 if os.path.isdir(absdest):
2600 name = normname(os.path.join(name, os.path.basename(patch)))
2600 name = normname(os.path.join(name, os.path.basename(patch)))
2601 absdest = q.join(name)
2601 absdest = q.join(name)
2602 if os.path.exists(absdest):
2602 if os.path.exists(absdest):
2603 raise util.Abort(_('%s already exists') % absdest)
2603 raise util.Abort(_('%s already exists') % absdest)
2604
2604
2605 if name in q.series:
2605 if name in q.series:
2606 raise util.Abort(
2606 raise util.Abort(
2607 _('A patch named %s already exists in the series file') % name)
2607 _('A patch named %s already exists in the series file') % name)
2608
2608
2609 ui.note(_('renaming %s to %s\n') % (patch, name))
2609 ui.note(_('renaming %s to %s\n') % (patch, name))
2610 i = q.find_series(patch)
2610 i = q.find_series(patch)
2611 guards = q.guard_re.findall(q.full_series[i])
2611 guards = q.guard_re.findall(q.full_series[i])
2612 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2612 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2613 q.parse_series()
2613 q.parse_series()
2614 q.series_dirty = 1
2614 q.series_dirty = 1
2615
2615
2616 info = q.isapplied(patch)
2616 info = q.isapplied(patch)
2617 if info:
2617 if info:
2618 q.applied[info[0]] = statusentry(info[1], name)
2618 q.applied[info[0]] = statusentry(info[1], name)
2619 q.applied_dirty = 1
2619 q.applied_dirty = 1
2620
2620
2621 destdir = os.path.dirname(absdest)
2621 destdir = os.path.dirname(absdest)
2622 if not os.path.isdir(destdir):
2622 if not os.path.isdir(destdir):
2623 os.makedirs(destdir)
2623 os.makedirs(destdir)
2624 util.rename(q.join(patch), absdest)
2624 util.rename(q.join(patch), absdest)
2625 r = q.qrepo()
2625 r = q.qrepo()
2626 if r and patch in r.dirstate:
2626 if r and patch in r.dirstate:
2627 wctx = r[None]
2627 wctx = r[None]
2628 wlock = r.wlock()
2628 wlock = r.wlock()
2629 try:
2629 try:
2630 if r.dirstate[patch] == 'a':
2630 if r.dirstate[patch] == 'a':
2631 r.dirstate.forget(patch)
2631 r.dirstate.forget(patch)
2632 r.dirstate.add(name)
2632 r.dirstate.add(name)
2633 else:
2633 else:
2634 if r.dirstate[name] == 'r':
2634 if r.dirstate[name] == 'r':
2635 wctx.undelete([name])
2635 wctx.undelete([name])
2636 wctx.copy(patch, name)
2636 wctx.copy(patch, name)
2637 wctx.remove([patch], False)
2637 wctx.remove([patch], False)
2638 finally:
2638 finally:
2639 wlock.release()
2639 wlock.release()
2640
2640
2641 q.save_dirty()
2641 q.save_dirty()
2642
2642
2643 @command("qrestore",
2643 @command("qrestore",
2644 [('d', 'delete', None, _('delete save entry')),
2644 [('d', 'delete', None, _('delete save entry')),
2645 ('u', 'update', None, _('update queue working directory'))],
2645 ('u', 'update', None, _('update queue working directory'))],
2646 _('hg qrestore [-d] [-u] REV'))
2646 _('hg qrestore [-d] [-u] REV'))
2647 def restore(ui, repo, rev, **opts):
2647 def restore(ui, repo, rev, **opts):
2648 """restore the queue state saved by a revision (DEPRECATED)
2648 """restore the queue state saved by a revision (DEPRECATED)
2649
2649
2650 This command is deprecated, use :hg:`rebase` instead."""
2650 This command is deprecated, use :hg:`rebase` instead."""
2651 rev = repo.lookup(rev)
2651 rev = repo.lookup(rev)
2652 q = repo.mq
2652 q = repo.mq
2653 q.restore(repo, rev, delete=opts.get('delete'),
2653 q.restore(repo, rev, delete=opts.get('delete'),
2654 qupdate=opts.get('update'))
2654 qupdate=opts.get('update'))
2655 q.save_dirty()
2655 q.save_dirty()
2656 return 0
2656 return 0
2657
2657
2658 @command("qsave",
2658 @command("qsave",
2659 [('c', 'copy', None, _('copy patch directory')),
2659 [('c', 'copy', None, _('copy patch directory')),
2660 ('n', 'name', '',
2660 ('n', 'name', '',
2661 _('copy directory name'), _('NAME')),
2661 _('copy directory name'), _('NAME')),
2662 ('e', 'empty', None, _('clear queue status file')),
2662 ('e', 'empty', None, _('clear queue status file')),
2663 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2663 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2664 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2664 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'))
2665 def save(ui, repo, **opts):
2665 def save(ui, repo, **opts):
2666 """save current queue state (DEPRECATED)
2666 """save current queue state (DEPRECATED)
2667
2667
2668 This command is deprecated, use :hg:`rebase` instead."""
2668 This command is deprecated, use :hg:`rebase` instead."""
2669 q = repo.mq
2669 q = repo.mq
2670 message = cmdutil.logmessage(opts)
2670 message = cmdutil.logmessage(opts)
2671 ret = q.save(repo, msg=message)
2671 ret = q.save(repo, msg=message)
2672 if ret:
2672 if ret:
2673 return ret
2673 return ret
2674 q.save_dirty()
2674 q.save_dirty()
2675 if opts.get('copy'):
2675 if opts.get('copy'):
2676 path = q.path
2676 path = q.path
2677 if opts.get('name'):
2677 if opts.get('name'):
2678 newpath = os.path.join(q.basepath, opts.get('name'))
2678 newpath = os.path.join(q.basepath, opts.get('name'))
2679 if os.path.exists(newpath):
2679 if os.path.exists(newpath):
2680 if not os.path.isdir(newpath):
2680 if not os.path.isdir(newpath):
2681 raise util.Abort(_('destination %s exists and is not '
2681 raise util.Abort(_('destination %s exists and is not '
2682 'a directory') % newpath)
2682 'a directory') % newpath)
2683 if not opts.get('force'):
2683 if not opts.get('force'):
2684 raise util.Abort(_('destination %s exists, '
2684 raise util.Abort(_('destination %s exists, '
2685 'use -f to force') % newpath)
2685 'use -f to force') % newpath)
2686 else:
2686 else:
2687 newpath = savename(path)
2687 newpath = savename(path)
2688 ui.warn(_("copy %s to %s\n") % (path, newpath))
2688 ui.warn(_("copy %s to %s\n") % (path, newpath))
2689 util.copyfiles(path, newpath)
2689 util.copyfiles(path, newpath)
2690 if opts.get('empty'):
2690 if opts.get('empty'):
2691 try:
2691 try:
2692 os.unlink(q.join(q.status_path))
2692 os.unlink(q.join(q.status_path))
2693 except:
2693 except:
2694 pass
2694 pass
2695 return 0
2695 return 0
2696
2696
2697 @command("strip",
2697 @command("strip",
2698 [('f', 'force', None, _('force removal of changesets, discard '
2698 [('f', 'force', None, _('force removal of changesets, discard '
2699 'uncommitted changes (no backup)')),
2699 'uncommitted changes (no backup)')),
2700 ('b', 'backup', None, _('bundle only changesets with local revision'
2700 ('b', 'backup', None, _('bundle only changesets with local revision'
2701 ' number greater than REV which are not'
2701 ' number greater than REV which are not'
2702 ' descendants of REV (DEPRECATED)')),
2702 ' descendants of REV (DEPRECATED)')),
2703 ('n', 'no-backup', None, _('no backups')),
2703 ('n', 'no-backup', None, _('no backups')),
2704 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
2704 ('', 'nobackup', None, _('no backups (DEPRECATED)')),
2705 ('k', 'keep', None, _("do not modify working copy during strip"))],
2705 ('k', 'keep', None, _("do not modify working copy during strip"))],
2706 _('hg strip [-k] [-f] [-n] REV...'))
2706 _('hg strip [-k] [-f] [-n] REV...'))
2707 def strip(ui, repo, *revs, **opts):
2707 def strip(ui, repo, *revs, **opts):
2708 """strip changesets and all their descendants from the repository
2708 """strip changesets and all their descendants from the repository
2709
2709
2710 The strip command removes the specified changesets and all their
2710 The strip command removes the specified changesets and all their
2711 descendants. If the working directory has uncommitted changes, the
2711 descendants. If the working directory has uncommitted changes, the
2712 operation is aborted unless the --force flag is supplied, in which
2712 operation is aborted unless the --force flag is supplied, in which
2713 case changes will be discarded.
2713 case changes will be discarded.
2714
2714
2715 If a parent of the working directory is stripped, then the working
2715 If a parent of the working directory is stripped, then the working
2716 directory will automatically be updated to the most recent
2716 directory will automatically be updated to the most recent
2717 available ancestor of the stripped parent after the operation
2717 available ancestor of the stripped parent after the operation
2718 completes.
2718 completes.
2719
2719
2720 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2720 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2721 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2721 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2722 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2722 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2723 where BUNDLE is the bundle file created by the strip. Note that
2723 where BUNDLE is the bundle file created by the strip. Note that
2724 the local revision numbers will in general be different after the
2724 the local revision numbers will in general be different after the
2725 restore.
2725 restore.
2726
2726
2727 Use the --no-backup option to discard the backup bundle once the
2727 Use the --no-backup option to discard the backup bundle once the
2728 operation completes.
2728 operation completes.
2729
2729
2730 Return 0 on success.
2730 Return 0 on success.
2731 """
2731 """
2732 backup = 'all'
2732 backup = 'all'
2733 if opts.get('backup'):
2733 if opts.get('backup'):
2734 backup = 'strip'
2734 backup = 'strip'
2735 elif opts.get('no_backup') or opts.get('nobackup'):
2735 elif opts.get('no_backup') or opts.get('nobackup'):
2736 backup = 'none'
2736 backup = 'none'
2737
2737
2738 cl = repo.changelog
2738 cl = repo.changelog
2739 revs = set(scmutil.revrange(repo, revs))
2739 revs = set(scmutil.revrange(repo, revs))
2740 if not revs:
2740 if not revs:
2741 raise util.Abort(_('empty revision set'))
2741 raise util.Abort(_('empty revision set'))
2742
2742
2743 descendants = set(cl.descendants(*revs))
2743 descendants = set(cl.descendants(*revs))
2744 strippedrevs = revs.union(descendants)
2744 strippedrevs = revs.union(descendants)
2745 roots = revs.difference(descendants)
2745 roots = revs.difference(descendants)
2746
2746
2747 update = False
2747 update = False
2748 # if one of the wdir parent is stripped we'll need
2748 # if one of the wdir parent is stripped we'll need
2749 # to update away to an earlier revision
2749 # to update away to an earlier revision
2750 for p in repo.dirstate.parents():
2750 for p in repo.dirstate.parents():
2751 if p != nullid and cl.rev(p) in strippedrevs:
2751 if p != nullid and cl.rev(p) in strippedrevs:
2752 update = True
2752 update = True
2753 break
2753 break
2754
2754
2755 rootnodes = set(cl.node(r) for r in roots)
2755 rootnodes = set(cl.node(r) for r in roots)
2756
2756
2757 q = repo.mq
2757 q = repo.mq
2758 if q.applied:
2758 if q.applied:
2759 # refresh queue state if we're about to strip
2759 # refresh queue state if we're about to strip
2760 # applied patches
2760 # applied patches
2761 if cl.rev(repo.lookup('qtip')) in strippedrevs:
2761 if cl.rev(repo.lookup('qtip')) in strippedrevs:
2762 q.applied_dirty = True
2762 q.applied_dirty = True
2763 start = 0
2763 start = 0
2764 end = len(q.applied)
2764 end = len(q.applied)
2765 for i, statusentry in enumerate(q.applied):
2765 for i, statusentry in enumerate(q.applied):
2766 if statusentry.node in rootnodes:
2766 if statusentry.node in rootnodes:
2767 # if one of the stripped roots is an applied
2767 # if one of the stripped roots is an applied
2768 # patch, only part of the queue is stripped
2768 # patch, only part of the queue is stripped
2769 start = i
2769 start = i
2770 break
2770 break
2771 del q.applied[start:end]
2771 del q.applied[start:end]
2772 q.save_dirty()
2772 q.save_dirty()
2773
2773
2774 revs = list(rootnodes)
2774 revs = list(rootnodes)
2775 if update and opts.get('keep'):
2775 if update and opts.get('keep'):
2776 wlock = repo.wlock()
2776 wlock = repo.wlock()
2777 try:
2777 try:
2778 urev = repo.mq.qparents(repo, revs[0])
2778 urev = repo.mq.qparents(repo, revs[0])
2779 repo.dirstate.rebuild(urev, repo[urev].manifest())
2779 repo.dirstate.rebuild(urev, repo[urev].manifest())
2780 repo.dirstate.write()
2780 repo.dirstate.write()
2781 update = False
2781 update = False
2782 finally:
2782 finally:
2783 wlock.release()
2783 wlock.release()
2784
2784
2785 repo.mq.strip(repo, revs, backup=backup, update=update,
2785 repo.mq.strip(repo, revs, backup=backup, update=update,
2786 force=opts.get('force'))
2786 force=opts.get('force'))
2787 return 0
2787 return 0
2788
2788
2789 @command("qselect",
2789 @command("qselect",
2790 [('n', 'none', None, _('disable all guards')),
2790 [('n', 'none', None, _('disable all guards')),
2791 ('s', 'series', None, _('list all guards in series file')),
2791 ('s', 'series', None, _('list all guards in series file')),
2792 ('', 'pop', None, _('pop to before first guarded applied patch')),
2792 ('', 'pop', None, _('pop to before first guarded applied patch')),
2793 ('', 'reapply', None, _('pop, then reapply patches'))],
2793 ('', 'reapply', None, _('pop, then reapply patches'))],
2794 _('hg qselect [OPTION]... [GUARD]...'))
2794 _('hg qselect [OPTION]... [GUARD]...'))
2795 def select(ui, repo, *args, **opts):
2795 def select(ui, repo, *args, **opts):
2796 '''set or print guarded patches to push
2796 '''set or print guarded patches to push
2797
2797
2798 Use the :hg:`qguard` command to set or print guards on patch, then use
2798 Use the :hg:`qguard` command to set or print guards on patch, then use
2799 qselect to tell mq which guards to use. A patch will be pushed if
2799 qselect to tell mq which guards to use. A patch will be pushed if
2800 it has no guards or any positive guards match the currently
2800 it has no guards or any positive guards match the currently
2801 selected guard, but will not be pushed if any negative guards
2801 selected guard, but will not be pushed if any negative guards
2802 match the current guard. For example::
2802 match the current guard. For example::
2803
2803
2804 qguard foo.patch -- -stable (negative guard)
2804 qguard foo.patch -- -stable (negative guard)
2805 qguard bar.patch +stable (positive guard)
2805 qguard bar.patch +stable (positive guard)
2806 qselect stable
2806 qselect stable
2807
2807
2808 This activates the "stable" guard. mq will skip foo.patch (because
2808 This activates the "stable" guard. mq will skip foo.patch (because
2809 it has a negative match) but push bar.patch (because it has a
2809 it has a negative match) but push bar.patch (because it has a
2810 positive match).
2810 positive match).
2811
2811
2812 With no arguments, prints the currently active guards.
2812 With no arguments, prints the currently active guards.
2813 With one argument, sets the active guard.
2813 With one argument, sets the active guard.
2814
2814
2815 Use -n/--none to deactivate guards (no other arguments needed).
2815 Use -n/--none to deactivate guards (no other arguments needed).
2816 When no guards are active, patches with positive guards are
2816 When no guards are active, patches with positive guards are
2817 skipped and patches with negative guards are pushed.
2817 skipped and patches with negative guards are pushed.
2818
2818
2819 qselect can change the guards on applied patches. It does not pop
2819 qselect can change the guards on applied patches. It does not pop
2820 guarded patches by default. Use --pop to pop back to the last
2820 guarded patches by default. Use --pop to pop back to the last
2821 applied patch that is not guarded. Use --reapply (which implies
2821 applied patch that is not guarded. Use --reapply (which implies
2822 --pop) to push back to the current patch afterwards, but skip
2822 --pop) to push back to the current patch afterwards, but skip
2823 guarded patches.
2823 guarded patches.
2824
2824
2825 Use -s/--series to print a list of all guards in the series file
2825 Use -s/--series to print a list of all guards in the series file
2826 (no other arguments needed). Use -v for more information.
2826 (no other arguments needed). Use -v for more information.
2827
2827
2828 Returns 0 on success.'''
2828 Returns 0 on success.'''
2829
2829
2830 q = repo.mq
2830 q = repo.mq
2831 guards = q.active()
2831 guards = q.active()
2832 if args or opts.get('none'):
2832 if args or opts.get('none'):
2833 old_unapplied = q.unapplied(repo)
2833 old_unapplied = q.unapplied(repo)
2834 old_guarded = [i for i in xrange(len(q.applied)) if
2834 old_guarded = [i for i in xrange(len(q.applied)) if
2835 not q.pushable(i)[0]]
2835 not q.pushable(i)[0]]
2836 q.set_active(args)
2836 q.set_active(args)
2837 q.save_dirty()
2837 q.save_dirty()
2838 if not args:
2838 if not args:
2839 ui.status(_('guards deactivated\n'))
2839 ui.status(_('guards deactivated\n'))
2840 if not opts.get('pop') and not opts.get('reapply'):
2840 if not opts.get('pop') and not opts.get('reapply'):
2841 unapplied = q.unapplied(repo)
2841 unapplied = q.unapplied(repo)
2842 guarded = [i for i in xrange(len(q.applied))
2842 guarded = [i for i in xrange(len(q.applied))
2843 if not q.pushable(i)[0]]
2843 if not q.pushable(i)[0]]
2844 if len(unapplied) != len(old_unapplied):
2844 if len(unapplied) != len(old_unapplied):
2845 ui.status(_('number of unguarded, unapplied patches has '
2845 ui.status(_('number of unguarded, unapplied patches has '
2846 'changed from %d to %d\n') %
2846 'changed from %d to %d\n') %
2847 (len(old_unapplied), len(unapplied)))
2847 (len(old_unapplied), len(unapplied)))
2848 if len(guarded) != len(old_guarded):
2848 if len(guarded) != len(old_guarded):
2849 ui.status(_('number of guarded, applied patches has changed '
2849 ui.status(_('number of guarded, applied patches has changed '
2850 'from %d to %d\n') %
2850 'from %d to %d\n') %
2851 (len(old_guarded), len(guarded)))
2851 (len(old_guarded), len(guarded)))
2852 elif opts.get('series'):
2852 elif opts.get('series'):
2853 guards = {}
2853 guards = {}
2854 noguards = 0
2854 noguards = 0
2855 for gs in q.series_guards:
2855 for gs in q.series_guards:
2856 if not gs:
2856 if not gs:
2857 noguards += 1
2857 noguards += 1
2858 for g in gs:
2858 for g in gs:
2859 guards.setdefault(g, 0)
2859 guards.setdefault(g, 0)
2860 guards[g] += 1
2860 guards[g] += 1
2861 if ui.verbose:
2861 if ui.verbose:
2862 guards['NONE'] = noguards
2862 guards['NONE'] = noguards
2863 guards = guards.items()
2863 guards = guards.items()
2864 guards.sort(key=lambda x: x[0][1:])
2864 guards.sort(key=lambda x: x[0][1:])
2865 if guards:
2865 if guards:
2866 ui.note(_('guards in series file:\n'))
2866 ui.note(_('guards in series file:\n'))
2867 for guard, count in guards:
2867 for guard, count in guards:
2868 ui.note('%2d ' % count)
2868 ui.note('%2d ' % count)
2869 ui.write(guard, '\n')
2869 ui.write(guard, '\n')
2870 else:
2870 else:
2871 ui.note(_('no guards in series file\n'))
2871 ui.note(_('no guards in series file\n'))
2872 else:
2872 else:
2873 if guards:
2873 if guards:
2874 ui.note(_('active guards:\n'))
2874 ui.note(_('active guards:\n'))
2875 for g in guards:
2875 for g in guards:
2876 ui.write(g, '\n')
2876 ui.write(g, '\n')
2877 else:
2877 else:
2878 ui.write(_('no active guards\n'))
2878 ui.write(_('no active guards\n'))
2879 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
2879 reapply = opts.get('reapply') and q.applied and q.appliedname(-1)
2880 popped = False
2880 popped = False
2881 if opts.get('pop') or opts.get('reapply'):
2881 if opts.get('pop') or opts.get('reapply'):
2882 for i in xrange(len(q.applied)):
2882 for i in xrange(len(q.applied)):
2883 pushable, reason = q.pushable(i)
2883 pushable, reason = q.pushable(i)
2884 if not pushable:
2884 if not pushable:
2885 ui.status(_('popping guarded patches\n'))
2885 ui.status(_('popping guarded patches\n'))
2886 popped = True
2886 popped = True
2887 if i == 0:
2887 if i == 0:
2888 q.pop(repo, all=True)
2888 q.pop(repo, all=True)
2889 else:
2889 else:
2890 q.pop(repo, i - 1)
2890 q.pop(repo, i - 1)
2891 break
2891 break
2892 if popped:
2892 if popped:
2893 try:
2893 try:
2894 if reapply:
2894 if reapply:
2895 ui.status(_('reapplying unguarded patches\n'))
2895 ui.status(_('reapplying unguarded patches\n'))
2896 q.push(repo, reapply)
2896 q.push(repo, reapply)
2897 finally:
2897 finally:
2898 q.save_dirty()
2898 q.save_dirty()
2899
2899
2900 @command("qfinish",
2900 @command("qfinish",
2901 [('a', 'applied', None, _('finish all applied changesets'))],
2901 [('a', 'applied', None, _('finish all applied changesets'))],
2902 _('hg qfinish [-a] [REV]...'))
2902 _('hg qfinish [-a] [REV]...'))
2903 def finish(ui, repo, *revrange, **opts):
2903 def finish(ui, repo, *revrange, **opts):
2904 """move applied patches into repository history
2904 """move applied patches into repository history
2905
2905
2906 Finishes the specified revisions (corresponding to applied
2906 Finishes the specified revisions (corresponding to applied
2907 patches) by moving them out of mq control into regular repository
2907 patches) by moving them out of mq control into regular repository
2908 history.
2908 history.
2909
2909
2910 Accepts a revision range or the -a/--applied option. If --applied
2910 Accepts a revision range or the -a/--applied option. If --applied
2911 is specified, all applied mq revisions are removed from mq
2911 is specified, all applied mq revisions are removed from mq
2912 control. Otherwise, the given revisions must be at the base of the
2912 control. Otherwise, the given revisions must be at the base of the
2913 stack of applied patches.
2913 stack of applied patches.
2914
2914
2915 This can be especially useful if your changes have been applied to
2915 This can be especially useful if your changes have been applied to
2916 an upstream repository, or if you are about to push your changes
2916 an upstream repository, or if you are about to push your changes
2917 to upstream.
2917 to upstream.
2918
2918
2919 Returns 0 on success.
2919 Returns 0 on success.
2920 """
2920 """
2921 if not opts.get('applied') and not revrange:
2921 if not opts.get('applied') and not revrange:
2922 raise util.Abort(_('no revisions specified'))
2922 raise util.Abort(_('no revisions specified'))
2923 elif opts.get('applied'):
2923 elif opts.get('applied'):
2924 revrange = ('qbase::qtip',) + revrange
2924 revrange = ('qbase::qtip',) + revrange
2925
2925
2926 q = repo.mq
2926 q = repo.mq
2927 if not q.applied:
2927 if not q.applied:
2928 ui.status(_('no patches applied\n'))
2928 ui.status(_('no patches applied\n'))
2929 return 0
2929 return 0
2930
2930
2931 revs = scmutil.revrange(repo, revrange)
2931 revs = scmutil.revrange(repo, revrange)
2932 q.finish(repo, revs)
2932 q.finish(repo, revs)
2933 q.save_dirty()
2933 q.save_dirty()
2934 return 0
2934 return 0
2935
2935
2936 @command("qqueue",
2936 @command("qqueue",
2937 [('l', 'list', False, _('list all available queues')),
2937 [('l', 'list', False, _('list all available queues')),
2938 ('c', 'create', False, _('create new queue')),
2938 ('c', 'create', False, _('create new queue')),
2939 ('', 'rename', False, _('rename active queue')),
2939 ('', 'rename', False, _('rename active queue')),
2940 ('', 'delete', False, _('delete reference to queue')),
2940 ('', 'delete', False, _('delete reference to queue')),
2941 ('', 'purge', False, _('delete queue, and remove patch dir')),
2941 ('', 'purge', False, _('delete queue, and remove patch dir')),
2942 ],
2942 ],
2943 _('[OPTION] [QUEUE]'))
2943 _('[OPTION] [QUEUE]'))
2944 def qqueue(ui, repo, name=None, **opts):
2944 def qqueue(ui, repo, name=None, **opts):
2945 '''manage multiple patch queues
2945 '''manage multiple patch queues
2946
2946
2947 Supports switching between different patch queues, as well as creating
2947 Supports switching between different patch queues, as well as creating
2948 new patch queues and deleting existing ones.
2948 new patch queues and deleting existing ones.
2949
2949
2950 Omitting a queue name or specifying -l/--list will show you the registered
2950 Omitting a queue name or specifying -l/--list will show you the registered
2951 queues - by default the "normal" patches queue is registered. The currently
2951 queues - by default the "normal" patches queue is registered. The currently
2952 active queue will be marked with "(active)".
2952 active queue will be marked with "(active)".
2953
2953
2954 To create a new queue, use -c/--create. The queue is automatically made
2954 To create a new queue, use -c/--create. The queue is automatically made
2955 active, except in the case where there are applied patches from the
2955 active, except in the case where there are applied patches from the
2956 currently active queue in the repository. Then the queue will only be
2956 currently active queue in the repository. Then the queue will only be
2957 created and switching will fail.
2957 created and switching will fail.
2958
2958
2959 To delete an existing queue, use --delete. You cannot delete the currently
2959 To delete an existing queue, use --delete. You cannot delete the currently
2960 active queue.
2960 active queue.
2961
2961
2962 Returns 0 on success.
2962 Returns 0 on success.
2963 '''
2963 '''
2964
2964
2965 q = repo.mq
2965 q = repo.mq
2966
2966
2967 _defaultqueue = 'patches'
2967 _defaultqueue = 'patches'
2968 _allqueues = 'patches.queues'
2968 _allqueues = 'patches.queues'
2969 _activequeue = 'patches.queue'
2969 _activequeue = 'patches.queue'
2970
2970
2971 def _getcurrent():
2971 def _getcurrent():
2972 cur = os.path.basename(q.path)
2972 cur = os.path.basename(q.path)
2973 if cur.startswith('patches-'):
2973 if cur.startswith('patches-'):
2974 cur = cur[8:]
2974 cur = cur[8:]
2975 return cur
2975 return cur
2976
2976
2977 def _noqueues():
2977 def _noqueues():
2978 try:
2978 try:
2979 fh = repo.opener(_allqueues, 'r')
2979 fh = repo.opener(_allqueues, 'r')
2980 fh.close()
2980 fh.close()
2981 except IOError:
2981 except IOError:
2982 return True
2982 return True
2983
2983
2984 return False
2984 return False
2985
2985
2986 def _getqueues():
2986 def _getqueues():
2987 current = _getcurrent()
2987 current = _getcurrent()
2988
2988
2989 try:
2989 try:
2990 fh = repo.opener(_allqueues, 'r')
2990 fh = repo.opener(_allqueues, 'r')
2991 queues = [queue.strip() for queue in fh if queue.strip()]
2991 queues = [queue.strip() for queue in fh if queue.strip()]
2992 fh.close()
2992 fh.close()
2993 if current not in queues:
2993 if current not in queues:
2994 queues.append(current)
2994 queues.append(current)
2995 except IOError:
2995 except IOError:
2996 queues = [_defaultqueue]
2996 queues = [_defaultqueue]
2997
2997
2998 return sorted(queues)
2998 return sorted(queues)
2999
2999
3000 def _setactive(name):
3000 def _setactive(name):
3001 if q.applied:
3001 if q.applied:
3002 raise util.Abort(_('patches applied - cannot set new queue active'))
3002 raise util.Abort(_('patches applied - cannot set new queue active'))
3003 _setactivenocheck(name)
3003 _setactivenocheck(name)
3004
3004
3005 def _setactivenocheck(name):
3005 def _setactivenocheck(name):
3006 fh = repo.opener(_activequeue, 'w')
3006 fh = repo.opener(_activequeue, 'w')
3007 if name != 'patches':
3007 if name != 'patches':
3008 fh.write(name)
3008 fh.write(name)
3009 fh.close()
3009 fh.close()
3010
3010
3011 def _addqueue(name):
3011 def _addqueue(name):
3012 fh = repo.opener(_allqueues, 'a')
3012 fh = repo.opener(_allqueues, 'a')
3013 fh.write('%s\n' % (name,))
3013 fh.write('%s\n' % (name,))
3014 fh.close()
3014 fh.close()
3015
3015
3016 def _queuedir(name):
3016 def _queuedir(name):
3017 if name == 'patches':
3017 if name == 'patches':
3018 return repo.join('patches')
3018 return repo.join('patches')
3019 else:
3019 else:
3020 return repo.join('patches-' + name)
3020 return repo.join('patches-' + name)
3021
3021
3022 def _validname(name):
3022 def _validname(name):
3023 for n in name:
3023 for n in name:
3024 if n in ':\\/.':
3024 if n in ':\\/.':
3025 return False
3025 return False
3026 return True
3026 return True
3027
3027
3028 def _delete(name):
3028 def _delete(name):
3029 if name not in existing:
3029 if name not in existing:
3030 raise util.Abort(_('cannot delete queue that does not exist'))
3030 raise util.Abort(_('cannot delete queue that does not exist'))
3031
3031
3032 current = _getcurrent()
3032 current = _getcurrent()
3033
3033
3034 if name == current:
3034 if name == current:
3035 raise util.Abort(_('cannot delete currently active queue'))
3035 raise util.Abort(_('cannot delete currently active queue'))
3036
3036
3037 fh = repo.opener('patches.queues.new', 'w')
3037 fh = repo.opener('patches.queues.new', 'w')
3038 for queue in existing:
3038 for queue in existing:
3039 if queue == name:
3039 if queue == name:
3040 continue
3040 continue
3041 fh.write('%s\n' % (queue,))
3041 fh.write('%s\n' % (queue,))
3042 fh.close()
3042 fh.close()
3043 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3043 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3044
3044
3045 if not name or opts.get('list'):
3045 if not name or opts.get('list'):
3046 current = _getcurrent()
3046 current = _getcurrent()
3047 for queue in _getqueues():
3047 for queue in _getqueues():
3048 ui.write('%s' % (queue,))
3048 ui.write('%s' % (queue,))
3049 if queue == current and not ui.quiet:
3049 if queue == current and not ui.quiet:
3050 ui.write(_(' (active)\n'))
3050 ui.write(_(' (active)\n'))
3051 else:
3051 else:
3052 ui.write('\n')
3052 ui.write('\n')
3053 return
3053 return
3054
3054
3055 if not _validname(name):
3055 if not _validname(name):
3056 raise util.Abort(
3056 raise util.Abort(
3057 _('invalid queue name, may not contain the characters ":\\/."'))
3057 _('invalid queue name, may not contain the characters ":\\/."'))
3058
3058
3059 existing = _getqueues()
3059 existing = _getqueues()
3060
3060
3061 if opts.get('create'):
3061 if opts.get('create'):
3062 if name in existing:
3062 if name in existing:
3063 raise util.Abort(_('queue "%s" already exists') % name)
3063 raise util.Abort(_('queue "%s" already exists') % name)
3064 if _noqueues():
3064 if _noqueues():
3065 _addqueue(_defaultqueue)
3065 _addqueue(_defaultqueue)
3066 _addqueue(name)
3066 _addqueue(name)
3067 _setactive(name)
3067 _setactive(name)
3068 elif opts.get('rename'):
3068 elif opts.get('rename'):
3069 current = _getcurrent()
3069 current = _getcurrent()
3070 if name == current:
3070 if name == current:
3071 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3071 raise util.Abort(_('can\'t rename "%s" to its current name') % name)
3072 if name in existing:
3072 if name in existing:
3073 raise util.Abort(_('queue "%s" already exists') % name)
3073 raise util.Abort(_('queue "%s" already exists') % name)
3074
3074
3075 olddir = _queuedir(current)
3075 olddir = _queuedir(current)
3076 newdir = _queuedir(name)
3076 newdir = _queuedir(name)
3077
3077
3078 if os.path.exists(newdir):
3078 if os.path.exists(newdir):
3079 raise util.Abort(_('non-queue directory "%s" already exists') %
3079 raise util.Abort(_('non-queue directory "%s" already exists') %
3080 newdir)
3080 newdir)
3081
3081
3082 fh = repo.opener('patches.queues.new', 'w')
3082 fh = repo.opener('patches.queues.new', 'w')
3083 for queue in existing:
3083 for queue in existing:
3084 if queue == current:
3084 if queue == current:
3085 fh.write('%s\n' % (name,))
3085 fh.write('%s\n' % (name,))
3086 if os.path.exists(olddir):
3086 if os.path.exists(olddir):
3087 util.rename(olddir, newdir)
3087 util.rename(olddir, newdir)
3088 else:
3088 else:
3089 fh.write('%s\n' % (queue,))
3089 fh.write('%s\n' % (queue,))
3090 fh.close()
3090 fh.close()
3091 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3091 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
3092 _setactivenocheck(name)
3092 _setactivenocheck(name)
3093 elif opts.get('delete'):
3093 elif opts.get('delete'):
3094 _delete(name)
3094 _delete(name)
3095 elif opts.get('purge'):
3095 elif opts.get('purge'):
3096 if name in existing:
3096 if name in existing:
3097 _delete(name)
3097 _delete(name)
3098 qdir = _queuedir(name)
3098 qdir = _queuedir(name)
3099 if os.path.exists(qdir):
3099 if os.path.exists(qdir):
3100 shutil.rmtree(qdir)
3100 shutil.rmtree(qdir)
3101 else:
3101 else:
3102 if name not in existing:
3102 if name not in existing:
3103 raise util.Abort(_('use --create to create a new queue'))
3103 raise util.Abort(_('use --create to create a new queue'))
3104 _setactive(name)
3104 _setactive(name)
3105
3105
3106 def reposetup(ui, repo):
3106 def reposetup(ui, repo):
3107 class mqrepo(repo.__class__):
3107 class mqrepo(repo.__class__):
3108 @util.propertycache
3108 @util.propertycache
3109 def mq(self):
3109 def mq(self):
3110 return queue(self.ui, self.join(""))
3110 return queue(self.ui, self.join(""))
3111
3111
3112 def abort_if_wdir_patched(self, errmsg, force=False):
3112 def abort_if_wdir_patched(self, errmsg, force=False):
3113 if self.mq.applied and not force:
3113 if self.mq.applied and not force:
3114 parents = self.dirstate.parents()
3114 parents = self.dirstate.parents()
3115 patches = [s.node for s in self.mq.applied]
3115 patches = [s.node for s in self.mq.applied]
3116 if parents[0] in patches or parents[1] in patches:
3116 if parents[0] in patches or parents[1] in patches:
3117 raise util.Abort(errmsg)
3117 raise util.Abort(errmsg)
3118
3118
3119 def commit(self, text="", user=None, date=None, match=None,
3119 def commit(self, text="", user=None, date=None, match=None,
3120 force=False, editor=False, extra={}):
3120 force=False, editor=False, extra={}):
3121 self.abort_if_wdir_patched(
3121 self.abort_if_wdir_patched(
3122 _('cannot commit over an applied mq patch'),
3122 _('cannot commit over an applied mq patch'),
3123 force)
3123 force)
3124
3124
3125 return super(mqrepo, self).commit(text, user, date, match, force,
3125 return super(mqrepo, self).commit(text, user, date, match, force,
3126 editor, extra)
3126 editor, extra)
3127
3127
3128 def checkpush(self, force, revs):
3128 def checkpush(self, force, revs):
3129 if self.mq.applied and not force:
3129 if self.mq.applied and not force:
3130 haspatches = True
3130 haspatches = True
3131 if revs:
3131 if revs:
3132 # Assume applied patches have no non-patch descendants
3132 # Assume applied patches have no non-patch descendants
3133 # and are not on remote already. If they appear in the
3133 # and are not on remote already. If they appear in the
3134 # set of resolved 'revs', bail out.
3134 # set of resolved 'revs', bail out.
3135 applied = set(e.node for e in self.mq.applied)
3135 applied = set(e.node for e in self.mq.applied)
3136 haspatches = bool([n for n in revs if n in applied])
3136 haspatches = bool([n for n in revs if n in applied])
3137 if haspatches:
3137 if haspatches:
3138 raise util.Abort(_('source has mq patches applied'))
3138 raise util.Abort(_('source has mq patches applied'))
3139 super(mqrepo, self).checkpush(force, revs)
3139 super(mqrepo, self).checkpush(force, revs)
3140
3140
3141 def _findtags(self):
3141 def _findtags(self):
3142 '''augment tags from base class with patch tags'''
3142 '''augment tags from base class with patch tags'''
3143 result = super(mqrepo, self)._findtags()
3143 result = super(mqrepo, self)._findtags()
3144
3144
3145 q = self.mq
3145 q = self.mq
3146 if not q.applied:
3146 if not q.applied:
3147 return result
3147 return result
3148
3148
3149 mqtags = [(patch.node, patch.name) for patch in q.applied]
3149 mqtags = [(patch.node, patch.name) for patch in q.applied]
3150
3150
3151 try:
3151 try:
3152 self.changelog.rev(mqtags[-1][0])
3152 self.changelog.rev(mqtags[-1][0])
3153 except error.RepoLookupError:
3153 except error.RepoLookupError:
3154 self.ui.warn(_('mq status file refers to unknown node %s\n')
3154 self.ui.warn(_('mq status file refers to unknown node %s\n')
3155 % short(mqtags[-1][0]))
3155 % short(mqtags[-1][0]))
3156 return result
3156 return result
3157
3157
3158 mqtags.append((mqtags[-1][0], 'qtip'))
3158 mqtags.append((mqtags[-1][0], 'qtip'))
3159 mqtags.append((mqtags[0][0], 'qbase'))
3159 mqtags.append((mqtags[0][0], 'qbase'))
3160 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3160 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
3161 tags = result[0]
3161 tags = result[0]
3162 for patch in mqtags:
3162 for patch in mqtags:
3163 if patch[1] in tags:
3163 if patch[1] in tags:
3164 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
3164 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
3165 % patch[1])
3165 % patch[1])
3166 else:
3166 else:
3167 tags[patch[1]] = patch[0]
3167 tags[patch[1]] = patch[0]
3168
3168
3169 return result
3169 return result
3170
3170
3171 def _branchtags(self, partial, lrev):
3171 def _branchtags(self, partial, lrev):
3172 q = self.mq
3172 q = self.mq
3173 if not q.applied:
3173 if not q.applied:
3174 return super(mqrepo, self)._branchtags(partial, lrev)
3174 return super(mqrepo, self)._branchtags(partial, lrev)
3175
3175
3176 cl = self.changelog
3176 cl = self.changelog
3177 qbasenode = q.applied[0].node
3177 qbasenode = q.applied[0].node
3178 try:
3178 try:
3179 qbase = cl.rev(qbasenode)
3179 qbase = cl.rev(qbasenode)
3180 except error.LookupError:
3180 except error.LookupError:
3181 self.ui.warn(_('mq status file refers to unknown node %s\n')
3181 self.ui.warn(_('mq status file refers to unknown node %s\n')
3182 % short(qbasenode))
3182 % short(qbasenode))
3183 return super(mqrepo, self)._branchtags(partial, lrev)
3183 return super(mqrepo, self)._branchtags(partial, lrev)
3184
3184
3185 start = lrev + 1
3185 start = lrev + 1
3186 if start < qbase:
3186 if start < qbase:
3187 # update the cache (excluding the patches) and save it
3187 # update the cache (excluding the patches) and save it
3188 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
3188 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
3189 self._updatebranchcache(partial, ctxgen)
3189 self._updatebranchcache(partial, ctxgen)
3190 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
3190 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
3191 start = qbase
3191 start = qbase
3192 # if start = qbase, the cache is as updated as it should be.
3192 # if start = qbase, the cache is as updated as it should be.
3193 # if start > qbase, the cache includes (part of) the patches.
3193 # if start > qbase, the cache includes (part of) the patches.
3194 # we might as well use it, but we won't save it.
3194 # we might as well use it, but we won't save it.
3195
3195
3196 # update the cache up to the tip
3196 # update the cache up to the tip
3197 ctxgen = (self[r] for r in xrange(start, len(cl)))
3197 ctxgen = (self[r] for r in xrange(start, len(cl)))
3198 self._updatebranchcache(partial, ctxgen)
3198 self._updatebranchcache(partial, ctxgen)
3199
3199
3200 return partial
3200 return partial
3201
3201
3202 if repo.local():
3202 if repo.local():
3203 repo.__class__ = mqrepo
3203 repo.__class__ = mqrepo
3204
3204
3205 def mqimport(orig, ui, repo, *args, **kwargs):
3205 def mqimport(orig, ui, repo, *args, **kwargs):
3206 if (hasattr(repo, 'abort_if_wdir_patched')
3206 if (hasattr(repo, 'abort_if_wdir_patched')
3207 and not kwargs.get('no_commit', False)):
3207 and not kwargs.get('no_commit', False)):
3208 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
3208 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
3209 kwargs.get('force'))
3209 kwargs.get('force'))
3210 return orig(ui, repo, *args, **kwargs)
3210 return orig(ui, repo, *args, **kwargs)
3211
3211
3212 def mqinit(orig, ui, *args, **kwargs):
3212 def mqinit(orig, ui, *args, **kwargs):
3213 mq = kwargs.pop('mq', None)
3213 mq = kwargs.pop('mq', None)
3214
3214
3215 if not mq:
3215 if not mq:
3216 return orig(ui, *args, **kwargs)
3216 return orig(ui, *args, **kwargs)
3217
3217
3218 if args:
3218 if args:
3219 repopath = args[0]
3219 repopath = args[0]
3220 if not hg.islocal(repopath):
3220 if not hg.islocal(repopath):
3221 raise util.Abort(_('only a local queue repository '
3221 raise util.Abort(_('only a local queue repository '
3222 'may be initialized'))
3222 'may be initialized'))
3223 else:
3223 else:
3224 repopath = cmdutil.findrepo(os.getcwd())
3224 repopath = cmdutil.findrepo(os.getcwd())
3225 if not repopath:
3225 if not repopath:
3226 raise util.Abort(_('there is no Mercurial repository here '
3226 raise util.Abort(_('there is no Mercurial repository here '
3227 '(.hg not found)'))
3227 '(.hg not found)'))
3228 repo = hg.repository(ui, repopath)
3228 repo = hg.repository(ui, repopath)
3229 return qinit(ui, repo, True)
3229 return qinit(ui, repo, True)
3230
3230
3231 def mqcommand(orig, ui, repo, *args, **kwargs):
3231 def mqcommand(orig, ui, repo, *args, **kwargs):
3232 """Add --mq option to operate on patch repository instead of main"""
3232 """Add --mq option to operate on patch repository instead of main"""
3233
3233
3234 # some commands do not like getting unknown options
3234 # some commands do not like getting unknown options
3235 mq = kwargs.pop('mq', None)
3235 mq = kwargs.pop('mq', None)
3236
3236
3237 if not mq:
3237 if not mq:
3238 return orig(ui, repo, *args, **kwargs)
3238 return orig(ui, repo, *args, **kwargs)
3239
3239
3240 q = repo.mq
3240 q = repo.mq
3241 r = q.qrepo()
3241 r = q.qrepo()
3242 if not r:
3242 if not r:
3243 raise util.Abort(_('no queue repository'))
3243 raise util.Abort(_('no queue repository'))
3244 return orig(r.ui, r, *args, **kwargs)
3244 return orig(r.ui, r, *args, **kwargs)
3245
3245
3246 def summary(orig, ui, repo, *args, **kwargs):
3246 def summary(orig, ui, repo, *args, **kwargs):
3247 r = orig(ui, repo, *args, **kwargs)
3247 r = orig(ui, repo, *args, **kwargs)
3248 q = repo.mq
3248 q = repo.mq
3249 m = []
3249 m = []
3250 a, u = len(q.applied), len(q.unapplied(repo))
3250 a, u = len(q.applied), len(q.unapplied(repo))
3251 if a:
3251 if a:
3252 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3252 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
3253 if u:
3253 if u:
3254 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3254 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
3255 if m:
3255 if m:
3256 ui.write("mq: %s\n" % ', '.join(m))
3256 ui.write("mq: %s\n" % ', '.join(m))
3257 else:
3257 else:
3258 ui.note(_("mq: (empty queue)\n"))
3258 ui.note(_("mq: (empty queue)\n"))
3259 return r
3259 return r
3260
3260
3261 def revsetmq(repo, subset, x):
3261 def revsetmq(repo, subset, x):
3262 """``mq()``
3262 """``mq()``
3263 Changesets managed by MQ.
3263 Changesets managed by MQ.
3264 """
3264 """
3265 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3265 revset.getargs(x, 0, 0, _("mq takes no arguments"))
3266 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3266 applied = set([repo[r.node].rev() for r in repo.mq.applied])
3267 return [r for r in subset if r in applied]
3267 return [r for r in subset if r in applied]
3268
3268
3269 def extsetup(ui):
3269 def extsetup(ui):
3270 revset.symbols['mq'] = revsetmq
3270 revset.symbols['mq'] = revsetmq
3271
3271
3272 # tell hggettext to extract docstrings from these functions:
3272 # tell hggettext to extract docstrings from these functions:
3273 i18nfunctions = [revsetmq]
3273 i18nfunctions = [revsetmq]
3274
3274
3275 def uisetup(ui):
3275 def uisetup(ui):
3276 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3276 mqopt = [('', 'mq', None, _("operate on patch repository"))]
3277
3277
3278 extensions.wrapcommand(commands.table, 'import', mqimport)
3278 extensions.wrapcommand(commands.table, 'import', mqimport)
3279 extensions.wrapcommand(commands.table, 'summary', summary)
3279 extensions.wrapcommand(commands.table, 'summary', summary)
3280
3280
3281 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3281 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
3282 entry[1].extend(mqopt)
3282 entry[1].extend(mqopt)
3283
3283
3284 nowrap = set(commands.norepo.split(" ") + ['qrecord'])
3284 nowrap = set(commands.norepo.split(" ") + ['qrecord'])
3285
3285
3286 def dotable(cmdtable):
3286 def dotable(cmdtable):
3287 for cmd in cmdtable.keys():
3287 for cmd in cmdtable.keys():
3288 cmd = cmdutil.parsealiases(cmd)[0]
3288 cmd = cmdutil.parsealiases(cmd)[0]
3289 if cmd in nowrap:
3289 if cmd in nowrap:
3290 continue
3290 continue
3291 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3291 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
3292 entry[1].extend(mqopt)
3292 entry[1].extend(mqopt)
3293
3293
3294 dotable(commands.table)
3294 dotable(commands.table)
3295
3295
3296 for extname, extmodule in extensions.extensions():
3296 for extname, extmodule in extensions.extensions():
3297 if extmodule.__file__ != __file__:
3297 if extmodule.__file__ != __file__:
3298 dotable(getattr(extmodule, 'cmdtable', {}))
3298 dotable(getattr(extmodule, 'cmdtable', {}))
3299
3299
3300
3300
3301 colortable = {'qguard.negative': 'red',
3301 colortable = {'qguard.negative': 'red',
3302 'qguard.positive': 'yellow',
3302 'qguard.positive': 'yellow',
3303 'qguard.unguarded': 'green',
3303 'qguard.unguarded': 'green',
3304 'qseries.applied': 'blue bold underline',
3304 'qseries.applied': 'blue bold underline',
3305 'qseries.guarded': 'black bold',
3305 'qseries.guarded': 'black bold',
3306 'qseries.missing': 'red bold',
3306 'qseries.missing': 'red bold',
3307 'qseries.unapplied': 'black bold'}
3307 'qseries.unapplied': 'black bold'}
@@ -1,632 +1,631 b''
1 # Patch transplanting extension for Mercurial
1 # Patch transplanting extension for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.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 '''command to transplant changesets from another branch
8 '''command to transplant changesets from another branch
9
9
10 This extension allows you to transplant patches from another branch.
10 This extension allows you to transplant patches from another branch.
11
11
12 Transplanted patches are recorded in .hg/transplant/transplants, as a
12 Transplanted patches are recorded in .hg/transplant/transplants, as a
13 map from a changeset hash to its hash in the source repository.
13 map from a changeset hash to its hash in the source repository.
14 '''
14 '''
15
15
16 from mercurial.i18n import _
16 from mercurial.i18n import _
17 import os, tempfile
17 import os, tempfile
18 from mercurial import bundlerepo, hg, merge, match
18 from mercurial import bundlerepo, hg, merge, match
19 from mercurial import patch, revlog, scmutil, util, error, cmdutil
19 from mercurial import patch, revlog, scmutil, util, error, cmdutil
20 from mercurial import revset, templatekw
20 from mercurial import revset, templatekw
21
21
22 cmdtable = {}
22 cmdtable = {}
23 command = cmdutil.command(cmdtable)
23 command = cmdutil.command(cmdtable)
24
24
25 class transplantentry(object):
25 class transplantentry(object):
26 def __init__(self, lnode, rnode):
26 def __init__(self, lnode, rnode):
27 self.lnode = lnode
27 self.lnode = lnode
28 self.rnode = rnode
28 self.rnode = rnode
29
29
30 class transplants(object):
30 class transplants(object):
31 def __init__(self, path=None, transplantfile=None, opener=None):
31 def __init__(self, path=None, transplantfile=None, opener=None):
32 self.path = path
32 self.path = path
33 self.transplantfile = transplantfile
33 self.transplantfile = transplantfile
34 self.opener = opener
34 self.opener = opener
35
35
36 if not opener:
36 if not opener:
37 self.opener = scmutil.opener(self.path)
37 self.opener = scmutil.opener(self.path)
38 self.transplants = {}
38 self.transplants = {}
39 self.dirty = False
39 self.dirty = False
40 self.read()
40 self.read()
41
41
42 def read(self):
42 def read(self):
43 abspath = os.path.join(self.path, self.transplantfile)
43 abspath = os.path.join(self.path, self.transplantfile)
44 if self.transplantfile and os.path.exists(abspath):
44 if self.transplantfile and os.path.exists(abspath):
45 for line in self.opener.read(self.transplantfile).splitlines():
45 for line in self.opener.read(self.transplantfile).splitlines():
46 lnode, rnode = map(revlog.bin, line.split(':'))
46 lnode, rnode = map(revlog.bin, line.split(':'))
47 list = self.transplants.setdefault(rnode, [])
47 list = self.transplants.setdefault(rnode, [])
48 list.append(transplantentry(lnode, rnode))
48 list.append(transplantentry(lnode, rnode))
49
49
50 def write(self):
50 def write(self):
51 if self.dirty and self.transplantfile:
51 if self.dirty and self.transplantfile:
52 if not os.path.isdir(self.path):
52 if not os.path.isdir(self.path):
53 os.mkdir(self.path)
53 os.mkdir(self.path)
54 fp = self.opener(self.transplantfile, 'w')
54 fp = self.opener(self.transplantfile, 'w')
55 for list in self.transplants.itervalues():
55 for list in self.transplants.itervalues():
56 for t in list:
56 for t in list:
57 l, r = map(revlog.hex, (t.lnode, t.rnode))
57 l, r = map(revlog.hex, (t.lnode, t.rnode))
58 fp.write(l + ':' + r + '\n')
58 fp.write(l + ':' + r + '\n')
59 fp.close()
59 fp.close()
60 self.dirty = False
60 self.dirty = False
61
61
62 def get(self, rnode):
62 def get(self, rnode):
63 return self.transplants.get(rnode) or []
63 return self.transplants.get(rnode) or []
64
64
65 def set(self, lnode, rnode):
65 def set(self, lnode, rnode):
66 list = self.transplants.setdefault(rnode, [])
66 list = self.transplants.setdefault(rnode, [])
67 list.append(transplantentry(lnode, rnode))
67 list.append(transplantentry(lnode, rnode))
68 self.dirty = True
68 self.dirty = True
69
69
70 def remove(self, transplant):
70 def remove(self, transplant):
71 list = self.transplants.get(transplant.rnode)
71 list = self.transplants.get(transplant.rnode)
72 if list:
72 if list:
73 del list[list.index(transplant)]
73 del list[list.index(transplant)]
74 self.dirty = True
74 self.dirty = True
75
75
76 class transplanter(object):
76 class transplanter(object):
77 def __init__(self, ui, repo):
77 def __init__(self, ui, repo):
78 self.ui = ui
78 self.ui = ui
79 self.path = repo.join('transplant')
79 self.path = repo.join('transplant')
80 self.opener = scmutil.opener(self.path)
80 self.opener = scmutil.opener(self.path)
81 self.transplants = transplants(self.path, 'transplants',
81 self.transplants = transplants(self.path, 'transplants',
82 opener=self.opener)
82 opener=self.opener)
83
83
84 def applied(self, repo, node, parent):
84 def applied(self, repo, node, parent):
85 '''returns True if a node is already an ancestor of parent
85 '''returns True if a node is already an ancestor of parent
86 or has already been transplanted'''
86 or has already been transplanted'''
87 if hasnode(repo, node):
87 if hasnode(repo, node):
88 if node in repo.changelog.reachable(parent, stop=node):
88 if node in repo.changelog.reachable(parent, stop=node):
89 return True
89 return True
90 for t in self.transplants.get(node):
90 for t in self.transplants.get(node):
91 # it might have been stripped
91 # it might have been stripped
92 if not hasnode(repo, t.lnode):
92 if not hasnode(repo, t.lnode):
93 self.transplants.remove(t)
93 self.transplants.remove(t)
94 return False
94 return False
95 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
95 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
96 return True
96 return True
97 return False
97 return False
98
98
99 def apply(self, repo, source, revmap, merges, opts={}):
99 def apply(self, repo, source, revmap, merges, opts={}):
100 '''apply the revisions in revmap one by one in revision order'''
100 '''apply the revisions in revmap one by one in revision order'''
101 revs = sorted(revmap)
101 revs = sorted(revmap)
102 p1, p2 = repo.dirstate.parents()
102 p1, p2 = repo.dirstate.parents()
103 pulls = []
103 pulls = []
104 diffopts = patch.diffopts(self.ui, opts)
104 diffopts = patch.diffopts(self.ui, opts)
105 diffopts.git = True
105 diffopts.git = True
106
106
107 lock = wlock = None
107 lock = wlock = None
108 try:
108 try:
109 wlock = repo.wlock()
109 wlock = repo.wlock()
110 lock = repo.lock()
110 lock = repo.lock()
111 for rev in revs:
111 for rev in revs:
112 node = revmap[rev]
112 node = revmap[rev]
113 revstr = '%s:%s' % (rev, revlog.short(node))
113 revstr = '%s:%s' % (rev, revlog.short(node))
114
114
115 if self.applied(repo, node, p1):
115 if self.applied(repo, node, p1):
116 self.ui.warn(_('skipping already applied revision %s\n') %
116 self.ui.warn(_('skipping already applied revision %s\n') %
117 revstr)
117 revstr)
118 continue
118 continue
119
119
120 parents = source.changelog.parents(node)
120 parents = source.changelog.parents(node)
121 if not opts.get('filter'):
121 if not opts.get('filter'):
122 # If the changeset parent is the same as the
122 # If the changeset parent is the same as the
123 # wdir's parent, just pull it.
123 # wdir's parent, just pull it.
124 if parents[0] == p1:
124 if parents[0] == p1:
125 pulls.append(node)
125 pulls.append(node)
126 p1 = node
126 p1 = node
127 continue
127 continue
128 if pulls:
128 if pulls:
129 if source != repo:
129 if source != repo:
130 repo.pull(source, heads=pulls)
130 repo.pull(source, heads=pulls)
131 merge.update(repo, pulls[-1], False, False, None)
131 merge.update(repo, pulls[-1], False, False, None)
132 p1, p2 = repo.dirstate.parents()
132 p1, p2 = repo.dirstate.parents()
133 pulls = []
133 pulls = []
134
134
135 domerge = False
135 domerge = False
136 if node in merges:
136 if node in merges:
137 # pulling all the merge revs at once would mean we
137 # pulling all the merge revs at once would mean we
138 # couldn't transplant after the latest even if
138 # couldn't transplant after the latest even if
139 # transplants before them fail.
139 # transplants before them fail.
140 domerge = True
140 domerge = True
141 if not hasnode(repo, node):
141 if not hasnode(repo, node):
142 repo.pull(source, heads=[node])
142 repo.pull(source, heads=[node])
143
143
144 if parents[1] != revlog.nullid:
144 if parents[1] != revlog.nullid:
145 self.ui.note(_('skipping merge changeset %s:%s\n')
145 self.ui.note(_('skipping merge changeset %s:%s\n')
146 % (rev, revlog.short(node)))
146 % (rev, revlog.short(node)))
147 patchfile = None
147 patchfile = None
148 else:
148 else:
149 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
149 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
150 fp = os.fdopen(fd, 'w')
150 fp = os.fdopen(fd, 'w')
151 gen = patch.diff(source, parents[0], node, opts=diffopts)
151 gen = patch.diff(source, parents[0], node, opts=diffopts)
152 for chunk in gen:
152 for chunk in gen:
153 fp.write(chunk)
153 fp.write(chunk)
154 fp.close()
154 fp.close()
155
155
156 del revmap[rev]
156 del revmap[rev]
157 if patchfile or domerge:
157 if patchfile or domerge:
158 try:
158 try:
159 n = self.applyone(repo, node,
159 n = self.applyone(repo, node,
160 source.changelog.read(node),
160 source.changelog.read(node),
161 patchfile, merge=domerge,
161 patchfile, merge=domerge,
162 log=opts.get('log'),
162 log=opts.get('log'),
163 filter=opts.get('filter'))
163 filter=opts.get('filter'))
164 if n and domerge:
164 if n and domerge:
165 self.ui.status(_('%s merged at %s\n') % (revstr,
165 self.ui.status(_('%s merged at %s\n') % (revstr,
166 revlog.short(n)))
166 revlog.short(n)))
167 elif n:
167 elif n:
168 self.ui.status(_('%s transplanted to %s\n')
168 self.ui.status(_('%s transplanted to %s\n')
169 % (revlog.short(node),
169 % (revlog.short(node),
170 revlog.short(n)))
170 revlog.short(n)))
171 finally:
171 finally:
172 if patchfile:
172 if patchfile:
173 os.unlink(patchfile)
173 os.unlink(patchfile)
174 if pulls:
174 if pulls:
175 repo.pull(source, heads=pulls)
175 repo.pull(source, heads=pulls)
176 merge.update(repo, pulls[-1], False, False, None)
176 merge.update(repo, pulls[-1], False, False, None)
177 finally:
177 finally:
178 self.saveseries(revmap, merges)
178 self.saveseries(revmap, merges)
179 self.transplants.write()
179 self.transplants.write()
180 lock.release()
180 lock.release()
181 wlock.release()
181 wlock.release()
182
182
183 def filter(self, filter, node, changelog, patchfile):
183 def filter(self, filter, node, changelog, patchfile):
184 '''arbitrarily rewrite changeset before applying it'''
184 '''arbitrarily rewrite changeset before applying it'''
185
185
186 self.ui.status(_('filtering %s\n') % patchfile)
186 self.ui.status(_('filtering %s\n') % patchfile)
187 user, date, msg = (changelog[1], changelog[2], changelog[4])
187 user, date, msg = (changelog[1], changelog[2], changelog[4])
188 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
188 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
189 fp = os.fdopen(fd, 'w')
189 fp = os.fdopen(fd, 'w')
190 fp.write("# HG changeset patch\n")
190 fp.write("# HG changeset patch\n")
191 fp.write("# User %s\n" % user)
191 fp.write("# User %s\n" % user)
192 fp.write("# Date %d %d\n" % date)
192 fp.write("# Date %d %d\n" % date)
193 fp.write(msg + '\n')
193 fp.write(msg + '\n')
194 fp.close()
194 fp.close()
195
195
196 try:
196 try:
197 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
197 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
198 util.shellquote(patchfile)),
198 util.shellquote(patchfile)),
199 environ={'HGUSER': changelog[1],
199 environ={'HGUSER': changelog[1],
200 'HGREVISION': revlog.hex(node),
200 'HGREVISION': revlog.hex(node),
201 },
201 },
202 onerr=util.Abort, errprefix=_('filter failed'))
202 onerr=util.Abort, errprefix=_('filter failed'))
203 user, date, msg = self.parselog(file(headerfile))[1:4]
203 user, date, msg = self.parselog(file(headerfile))[1:4]
204 finally:
204 finally:
205 os.unlink(headerfile)
205 os.unlink(headerfile)
206
206
207 return (user, date, msg)
207 return (user, date, msg)
208
208
209 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
209 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
210 filter=None):
210 filter=None):
211 '''apply the patch in patchfile to the repository as a transplant'''
211 '''apply the patch in patchfile to the repository as a transplant'''
212 (manifest, user, (time, timezone), files, message) = cl[:5]
212 (manifest, user, (time, timezone), files, message) = cl[:5]
213 date = "%d %d" % (time, timezone)
213 date = "%d %d" % (time, timezone)
214 extra = {'transplant_source': node}
214 extra = {'transplant_source': node}
215 if filter:
215 if filter:
216 (user, date, message) = self.filter(filter, node, cl, patchfile)
216 (user, date, message) = self.filter(filter, node, cl, patchfile)
217
217
218 if log:
218 if log:
219 # we don't translate messages inserted into commits
219 # we don't translate messages inserted into commits
220 message += '\n(transplanted from %s)' % revlog.hex(node)
220 message += '\n(transplanted from %s)' % revlog.hex(node)
221
221
222 self.ui.status(_('applying %s\n') % revlog.short(node))
222 self.ui.status(_('applying %s\n') % revlog.short(node))
223 self.ui.note('%s %s\n%s\n' % (user, date, message))
223 self.ui.note('%s %s\n%s\n' % (user, date, message))
224
224
225 if not patchfile and not merge:
225 if not patchfile and not merge:
226 raise util.Abort(_('can only omit patchfile if merging'))
226 raise util.Abort(_('can only omit patchfile if merging'))
227 if patchfile:
227 if patchfile:
228 try:
228 try:
229 files = {}
229 files = {}
230 patch.patch(self.ui, repo, patchfile, cwd=repo.root,
230 patch.patch(self.ui, repo, patchfile, files=files, eolmode=None)
231 files=files, eolmode=None)
232 files = list(files)
231 files = list(files)
233 if not files:
232 if not files:
234 self.ui.warn(_('%s: empty changeset') % revlog.hex(node))
233 self.ui.warn(_('%s: empty changeset') % revlog.hex(node))
235 return None
234 return None
236 except Exception, inst:
235 except Exception, inst:
237 seriespath = os.path.join(self.path, 'series')
236 seriespath = os.path.join(self.path, 'series')
238 if os.path.exists(seriespath):
237 if os.path.exists(seriespath):
239 os.unlink(seriespath)
238 os.unlink(seriespath)
240 p1 = repo.dirstate.p1()
239 p1 = repo.dirstate.p1()
241 p2 = node
240 p2 = node
242 self.log(user, date, message, p1, p2, merge=merge)
241 self.log(user, date, message, p1, p2, merge=merge)
243 self.ui.write(str(inst) + '\n')
242 self.ui.write(str(inst) + '\n')
244 raise util.Abort(_('fix up the merge and run '
243 raise util.Abort(_('fix up the merge and run '
245 'hg transplant --continue'))
244 'hg transplant --continue'))
246 else:
245 else:
247 files = None
246 files = None
248 if merge:
247 if merge:
249 p1, p2 = repo.dirstate.parents()
248 p1, p2 = repo.dirstate.parents()
250 repo.dirstate.setparents(p1, node)
249 repo.dirstate.setparents(p1, node)
251 m = match.always(repo.root, '')
250 m = match.always(repo.root, '')
252 else:
251 else:
253 m = match.exact(repo.root, '', files)
252 m = match.exact(repo.root, '', files)
254
253
255 n = repo.commit(message, user, date, extra=extra, match=m)
254 n = repo.commit(message, user, date, extra=extra, match=m)
256 if not n:
255 if not n:
257 # Crash here to prevent an unclear crash later, in
256 # Crash here to prevent an unclear crash later, in
258 # transplants.write(). This can happen if patch.patch()
257 # transplants.write(). This can happen if patch.patch()
259 # does nothing but claims success or if repo.status() fails
258 # does nothing but claims success or if repo.status() fails
260 # to report changes done by patch.patch(). These both
259 # to report changes done by patch.patch(). These both
261 # appear to be bugs in other parts of Mercurial, but dying
260 # appear to be bugs in other parts of Mercurial, but dying
262 # here, as soon as we can detect the problem, is preferable
261 # here, as soon as we can detect the problem, is preferable
263 # to silently dropping changesets on the floor.
262 # to silently dropping changesets on the floor.
264 raise RuntimeError('nothing committed after transplant')
263 raise RuntimeError('nothing committed after transplant')
265 if not merge:
264 if not merge:
266 self.transplants.set(n, node)
265 self.transplants.set(n, node)
267
266
268 return n
267 return n
269
268
270 def resume(self, repo, source, opts=None):
269 def resume(self, repo, source, opts=None):
271 '''recover last transaction and apply remaining changesets'''
270 '''recover last transaction and apply remaining changesets'''
272 if os.path.exists(os.path.join(self.path, 'journal')):
271 if os.path.exists(os.path.join(self.path, 'journal')):
273 n, node = self.recover(repo)
272 n, node = self.recover(repo)
274 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
273 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
275 revlog.short(n)))
274 revlog.short(n)))
276 seriespath = os.path.join(self.path, 'series')
275 seriespath = os.path.join(self.path, 'series')
277 if not os.path.exists(seriespath):
276 if not os.path.exists(seriespath):
278 self.transplants.write()
277 self.transplants.write()
279 return
278 return
280 nodes, merges = self.readseries()
279 nodes, merges = self.readseries()
281 revmap = {}
280 revmap = {}
282 for n in nodes:
281 for n in nodes:
283 revmap[source.changelog.rev(n)] = n
282 revmap[source.changelog.rev(n)] = n
284 os.unlink(seriespath)
283 os.unlink(seriespath)
285
284
286 self.apply(repo, source, revmap, merges, opts)
285 self.apply(repo, source, revmap, merges, opts)
287
286
288 def recover(self, repo):
287 def recover(self, repo):
289 '''commit working directory using journal metadata'''
288 '''commit working directory using journal metadata'''
290 node, user, date, message, parents = self.readlog()
289 node, user, date, message, parents = self.readlog()
291 merge = len(parents) == 2
290 merge = len(parents) == 2
292
291
293 if not user or not date or not message or not parents[0]:
292 if not user or not date or not message or not parents[0]:
294 raise util.Abort(_('transplant log file is corrupt'))
293 raise util.Abort(_('transplant log file is corrupt'))
295
294
296 extra = {'transplant_source': node}
295 extra = {'transplant_source': node}
297 wlock = repo.wlock()
296 wlock = repo.wlock()
298 try:
297 try:
299 p1, p2 = repo.dirstate.parents()
298 p1, p2 = repo.dirstate.parents()
300 if p1 != parents[0]:
299 if p1 != parents[0]:
301 raise util.Abort(
300 raise util.Abort(
302 _('working dir not at transplant parent %s') %
301 _('working dir not at transplant parent %s') %
303 revlog.hex(parents[0]))
302 revlog.hex(parents[0]))
304 if merge:
303 if merge:
305 repo.dirstate.setparents(p1, parents[1])
304 repo.dirstate.setparents(p1, parents[1])
306 n = repo.commit(message, user, date, extra=extra)
305 n = repo.commit(message, user, date, extra=extra)
307 if not n:
306 if not n:
308 raise util.Abort(_('commit failed'))
307 raise util.Abort(_('commit failed'))
309 if not merge:
308 if not merge:
310 self.transplants.set(n, node)
309 self.transplants.set(n, node)
311 self.unlog()
310 self.unlog()
312
311
313 return n, node
312 return n, node
314 finally:
313 finally:
315 wlock.release()
314 wlock.release()
316
315
317 def readseries(self):
316 def readseries(self):
318 nodes = []
317 nodes = []
319 merges = []
318 merges = []
320 cur = nodes
319 cur = nodes
321 for line in self.opener.read('series').splitlines():
320 for line in self.opener.read('series').splitlines():
322 if line.startswith('# Merges'):
321 if line.startswith('# Merges'):
323 cur = merges
322 cur = merges
324 continue
323 continue
325 cur.append(revlog.bin(line))
324 cur.append(revlog.bin(line))
326
325
327 return (nodes, merges)
326 return (nodes, merges)
328
327
329 def saveseries(self, revmap, merges):
328 def saveseries(self, revmap, merges):
330 if not revmap:
329 if not revmap:
331 return
330 return
332
331
333 if not os.path.isdir(self.path):
332 if not os.path.isdir(self.path):
334 os.mkdir(self.path)
333 os.mkdir(self.path)
335 series = self.opener('series', 'w')
334 series = self.opener('series', 'w')
336 for rev in sorted(revmap):
335 for rev in sorted(revmap):
337 series.write(revlog.hex(revmap[rev]) + '\n')
336 series.write(revlog.hex(revmap[rev]) + '\n')
338 if merges:
337 if merges:
339 series.write('# Merges\n')
338 series.write('# Merges\n')
340 for m in merges:
339 for m in merges:
341 series.write(revlog.hex(m) + '\n')
340 series.write(revlog.hex(m) + '\n')
342 series.close()
341 series.close()
343
342
344 def parselog(self, fp):
343 def parselog(self, fp):
345 parents = []
344 parents = []
346 message = []
345 message = []
347 node = revlog.nullid
346 node = revlog.nullid
348 inmsg = False
347 inmsg = False
349 user = None
348 user = None
350 date = None
349 date = None
351 for line in fp.read().splitlines():
350 for line in fp.read().splitlines():
352 if inmsg:
351 if inmsg:
353 message.append(line)
352 message.append(line)
354 elif line.startswith('# User '):
353 elif line.startswith('# User '):
355 user = line[7:]
354 user = line[7:]
356 elif line.startswith('# Date '):
355 elif line.startswith('# Date '):
357 date = line[7:]
356 date = line[7:]
358 elif line.startswith('# Node ID '):
357 elif line.startswith('# Node ID '):
359 node = revlog.bin(line[10:])
358 node = revlog.bin(line[10:])
360 elif line.startswith('# Parent '):
359 elif line.startswith('# Parent '):
361 parents.append(revlog.bin(line[9:]))
360 parents.append(revlog.bin(line[9:]))
362 elif not line.startswith('# '):
361 elif not line.startswith('# '):
363 inmsg = True
362 inmsg = True
364 message.append(line)
363 message.append(line)
365 if None in (user, date):
364 if None in (user, date):
366 raise util.Abort(_("filter corrupted changeset (no user or date)"))
365 raise util.Abort(_("filter corrupted changeset (no user or date)"))
367 return (node, user, date, '\n'.join(message), parents)
366 return (node, user, date, '\n'.join(message), parents)
368
367
369 def log(self, user, date, message, p1, p2, merge=False):
368 def log(self, user, date, message, p1, p2, merge=False):
370 '''journal changelog metadata for later recover'''
369 '''journal changelog metadata for later recover'''
371
370
372 if not os.path.isdir(self.path):
371 if not os.path.isdir(self.path):
373 os.mkdir(self.path)
372 os.mkdir(self.path)
374 fp = self.opener('journal', 'w')
373 fp = self.opener('journal', 'w')
375 fp.write('# User %s\n' % user)
374 fp.write('# User %s\n' % user)
376 fp.write('# Date %s\n' % date)
375 fp.write('# Date %s\n' % date)
377 fp.write('# Node ID %s\n' % revlog.hex(p2))
376 fp.write('# Node ID %s\n' % revlog.hex(p2))
378 fp.write('# Parent ' + revlog.hex(p1) + '\n')
377 fp.write('# Parent ' + revlog.hex(p1) + '\n')
379 if merge:
378 if merge:
380 fp.write('# Parent ' + revlog.hex(p2) + '\n')
379 fp.write('# Parent ' + revlog.hex(p2) + '\n')
381 fp.write(message.rstrip() + '\n')
380 fp.write(message.rstrip() + '\n')
382 fp.close()
381 fp.close()
383
382
384 def readlog(self):
383 def readlog(self):
385 return self.parselog(self.opener('journal'))
384 return self.parselog(self.opener('journal'))
386
385
387 def unlog(self):
386 def unlog(self):
388 '''remove changelog journal'''
387 '''remove changelog journal'''
389 absdst = os.path.join(self.path, 'journal')
388 absdst = os.path.join(self.path, 'journal')
390 if os.path.exists(absdst):
389 if os.path.exists(absdst):
391 os.unlink(absdst)
390 os.unlink(absdst)
392
391
393 def transplantfilter(self, repo, source, root):
392 def transplantfilter(self, repo, source, root):
394 def matchfn(node):
393 def matchfn(node):
395 if self.applied(repo, node, root):
394 if self.applied(repo, node, root):
396 return False
395 return False
397 if source.changelog.parents(node)[1] != revlog.nullid:
396 if source.changelog.parents(node)[1] != revlog.nullid:
398 return False
397 return False
399 extra = source.changelog.read(node)[5]
398 extra = source.changelog.read(node)[5]
400 cnode = extra.get('transplant_source')
399 cnode = extra.get('transplant_source')
401 if cnode and self.applied(repo, cnode, root):
400 if cnode and self.applied(repo, cnode, root):
402 return False
401 return False
403 return True
402 return True
404
403
405 return matchfn
404 return matchfn
406
405
407 def hasnode(repo, node):
406 def hasnode(repo, node):
408 try:
407 try:
409 return repo.changelog.rev(node) is not None
408 return repo.changelog.rev(node) is not None
410 except error.RevlogError:
409 except error.RevlogError:
411 return False
410 return False
412
411
413 def browserevs(ui, repo, nodes, opts):
412 def browserevs(ui, repo, nodes, opts):
414 '''interactively transplant changesets'''
413 '''interactively transplant changesets'''
415 def browsehelp(ui):
414 def browsehelp(ui):
416 ui.write(_('y: transplant this changeset\n'
415 ui.write(_('y: transplant this changeset\n'
417 'n: skip this changeset\n'
416 'n: skip this changeset\n'
418 'm: merge at this changeset\n'
417 'm: merge at this changeset\n'
419 'p: show patch\n'
418 'p: show patch\n'
420 'c: commit selected changesets\n'
419 'c: commit selected changesets\n'
421 'q: cancel transplant\n'
420 'q: cancel transplant\n'
422 '?: show this help\n'))
421 '?: show this help\n'))
423
422
424 displayer = cmdutil.show_changeset(ui, repo, opts)
423 displayer = cmdutil.show_changeset(ui, repo, opts)
425 transplants = []
424 transplants = []
426 merges = []
425 merges = []
427 for node in nodes:
426 for node in nodes:
428 displayer.show(repo[node])
427 displayer.show(repo[node])
429 action = None
428 action = None
430 while not action:
429 while not action:
431 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
430 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
432 if action == '?':
431 if action == '?':
433 browsehelp(ui)
432 browsehelp(ui)
434 action = None
433 action = None
435 elif action == 'p':
434 elif action == 'p':
436 parent = repo.changelog.parents(node)[0]
435 parent = repo.changelog.parents(node)[0]
437 for chunk in patch.diff(repo, parent, node):
436 for chunk in patch.diff(repo, parent, node):
438 ui.write(chunk)
437 ui.write(chunk)
439 action = None
438 action = None
440 elif action not in ('y', 'n', 'm', 'c', 'q'):
439 elif action not in ('y', 'n', 'm', 'c', 'q'):
441 ui.write(_('no such option\n'))
440 ui.write(_('no such option\n'))
442 action = None
441 action = None
443 if action == 'y':
442 if action == 'y':
444 transplants.append(node)
443 transplants.append(node)
445 elif action == 'm':
444 elif action == 'm':
446 merges.append(node)
445 merges.append(node)
447 elif action == 'c':
446 elif action == 'c':
448 break
447 break
449 elif action == 'q':
448 elif action == 'q':
450 transplants = ()
449 transplants = ()
451 merges = ()
450 merges = ()
452 break
451 break
453 displayer.close()
452 displayer.close()
454 return (transplants, merges)
453 return (transplants, merges)
455
454
456 @command('transplant',
455 @command('transplant',
457 [('s', 'source', '', _('pull patches from REPO'), _('REPO')),
456 [('s', 'source', '', _('pull patches from REPO'), _('REPO')),
458 ('b', 'branch', [],
457 ('b', 'branch', [],
459 _('pull patches from branch BRANCH'), _('BRANCH')),
458 _('pull patches from branch BRANCH'), _('BRANCH')),
460 ('a', 'all', None, _('pull all changesets up to BRANCH')),
459 ('a', 'all', None, _('pull all changesets up to BRANCH')),
461 ('p', 'prune', [], _('skip over REV'), _('REV')),
460 ('p', 'prune', [], _('skip over REV'), _('REV')),
462 ('m', 'merge', [], _('merge at REV'), _('REV')),
461 ('m', 'merge', [], _('merge at REV'), _('REV')),
463 ('', 'log', None, _('append transplant info to log message')),
462 ('', 'log', None, _('append transplant info to log message')),
464 ('c', 'continue', None, _('continue last transplant session '
463 ('c', 'continue', None, _('continue last transplant session '
465 'after repair')),
464 'after repair')),
466 ('', 'filter', '',
465 ('', 'filter', '',
467 _('filter changesets through command'), _('CMD'))],
466 _('filter changesets through command'), _('CMD'))],
468 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
467 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
469 '[-m REV] [REV]...'))
468 '[-m REV] [REV]...'))
470 def transplant(ui, repo, *revs, **opts):
469 def transplant(ui, repo, *revs, **opts):
471 '''transplant changesets from another branch
470 '''transplant changesets from another branch
472
471
473 Selected changesets will be applied on top of the current working
472 Selected changesets will be applied on top of the current working
474 directory with the log of the original changeset. The changesets
473 directory with the log of the original changeset. The changesets
475 are copied and will thus appear twice in the history. Use the
474 are copied and will thus appear twice in the history. Use the
476 rebase extension instead if you want to move a whole branch of
475 rebase extension instead if you want to move a whole branch of
477 unpublished changesets.
476 unpublished changesets.
478
477
479 If --log is specified, log messages will have a comment appended
478 If --log is specified, log messages will have a comment appended
480 of the form::
479 of the form::
481
480
482 (transplanted from CHANGESETHASH)
481 (transplanted from CHANGESETHASH)
483
482
484 You can rewrite the changelog message with the --filter option.
483 You can rewrite the changelog message with the --filter option.
485 Its argument will be invoked with the current changelog message as
484 Its argument will be invoked with the current changelog message as
486 $1 and the patch as $2.
485 $1 and the patch as $2.
487
486
488 If --source/-s is specified, selects changesets from the named
487 If --source/-s is specified, selects changesets from the named
489 repository. If --branch/-b is specified, selects changesets from
488 repository. If --branch/-b is specified, selects changesets from
490 the branch holding the named revision, up to that revision. If
489 the branch holding the named revision, up to that revision. If
491 --all/-a is specified, all changesets on the branch will be
490 --all/-a is specified, all changesets on the branch will be
492 transplanted, otherwise you will be prompted to select the
491 transplanted, otherwise you will be prompted to select the
493 changesets you want.
492 changesets you want.
494
493
495 :hg:`transplant --branch REVISION --all` will transplant the
494 :hg:`transplant --branch REVISION --all` will transplant the
496 selected branch (up to the named revision) onto your current
495 selected branch (up to the named revision) onto your current
497 working directory.
496 working directory.
498
497
499 You can optionally mark selected transplanted changesets as merge
498 You can optionally mark selected transplanted changesets as merge
500 changesets. You will not be prompted to transplant any ancestors
499 changesets. You will not be prompted to transplant any ancestors
501 of a merged transplant, and you can merge descendants of them
500 of a merged transplant, and you can merge descendants of them
502 normally instead of transplanting them.
501 normally instead of transplanting them.
503
502
504 If no merges or revisions are provided, :hg:`transplant` will
503 If no merges or revisions are provided, :hg:`transplant` will
505 start an interactive changeset browser.
504 start an interactive changeset browser.
506
505
507 If a changeset application fails, you can fix the merge by hand
506 If a changeset application fails, you can fix the merge by hand
508 and then resume where you left off by calling :hg:`transplant
507 and then resume where you left off by calling :hg:`transplant
509 --continue/-c`.
508 --continue/-c`.
510 '''
509 '''
511 def incwalk(repo, csets, match=util.always):
510 def incwalk(repo, csets, match=util.always):
512 for node in csets:
511 for node in csets:
513 if match(node):
512 if match(node):
514 yield node
513 yield node
515
514
516 def transplantwalk(repo, root, branches, match=util.always):
515 def transplantwalk(repo, root, branches, match=util.always):
517 if not branches:
516 if not branches:
518 branches = repo.heads()
517 branches = repo.heads()
519 ancestors = []
518 ancestors = []
520 for branch in branches:
519 for branch in branches:
521 ancestors.append(repo.changelog.ancestor(root, branch))
520 ancestors.append(repo.changelog.ancestor(root, branch))
522 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
521 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
523 if match(node):
522 if match(node):
524 yield node
523 yield node
525
524
526 def checkopts(opts, revs):
525 def checkopts(opts, revs):
527 if opts.get('continue'):
526 if opts.get('continue'):
528 if opts.get('branch') or opts.get('all') or opts.get('merge'):
527 if opts.get('branch') or opts.get('all') or opts.get('merge'):
529 raise util.Abort(_('--continue is incompatible with '
528 raise util.Abort(_('--continue is incompatible with '
530 'branch, all or merge'))
529 'branch, all or merge'))
531 return
530 return
532 if not (opts.get('source') or revs or
531 if not (opts.get('source') or revs or
533 opts.get('merge') or opts.get('branch')):
532 opts.get('merge') or opts.get('branch')):
534 raise util.Abort(_('no source URL, branch tag or revision '
533 raise util.Abort(_('no source URL, branch tag or revision '
535 'list provided'))
534 'list provided'))
536 if opts.get('all'):
535 if opts.get('all'):
537 if not opts.get('branch'):
536 if not opts.get('branch'):
538 raise util.Abort(_('--all requires a branch revision'))
537 raise util.Abort(_('--all requires a branch revision'))
539 if revs:
538 if revs:
540 raise util.Abort(_('--all is incompatible with a '
539 raise util.Abort(_('--all is incompatible with a '
541 'revision list'))
540 'revision list'))
542
541
543 checkopts(opts, revs)
542 checkopts(opts, revs)
544
543
545 if not opts.get('log'):
544 if not opts.get('log'):
546 opts['log'] = ui.config('transplant', 'log')
545 opts['log'] = ui.config('transplant', 'log')
547 if not opts.get('filter'):
546 if not opts.get('filter'):
548 opts['filter'] = ui.config('transplant', 'filter')
547 opts['filter'] = ui.config('transplant', 'filter')
549
548
550 tp = transplanter(ui, repo)
549 tp = transplanter(ui, repo)
551
550
552 p1, p2 = repo.dirstate.parents()
551 p1, p2 = repo.dirstate.parents()
553 if len(repo) > 0 and p1 == revlog.nullid:
552 if len(repo) > 0 and p1 == revlog.nullid:
554 raise util.Abort(_('no revision checked out'))
553 raise util.Abort(_('no revision checked out'))
555 if not opts.get('continue'):
554 if not opts.get('continue'):
556 if p2 != revlog.nullid:
555 if p2 != revlog.nullid:
557 raise util.Abort(_('outstanding uncommitted merges'))
556 raise util.Abort(_('outstanding uncommitted merges'))
558 m, a, r, d = repo.status()[:4]
557 m, a, r, d = repo.status()[:4]
559 if m or a or r or d:
558 if m or a or r or d:
560 raise util.Abort(_('outstanding local changes'))
559 raise util.Abort(_('outstanding local changes'))
561
560
562 sourcerepo = opts.get('source')
561 sourcerepo = opts.get('source')
563 if sourcerepo:
562 if sourcerepo:
564 source = hg.repository(ui, ui.expandpath(sourcerepo))
563 source = hg.repository(ui, ui.expandpath(sourcerepo))
565 branches = map(source.lookup, opts.get('branch', ()))
564 branches = map(source.lookup, opts.get('branch', ()))
566 source, csets, cleanupfn = bundlerepo.getremotechanges(ui, repo, source,
565 source, csets, cleanupfn = bundlerepo.getremotechanges(ui, repo, source,
567 onlyheads=branches, force=True)
566 onlyheads=branches, force=True)
568 else:
567 else:
569 source = repo
568 source = repo
570 branches = map(source.lookup, opts.get('branch', ()))
569 branches = map(source.lookup, opts.get('branch', ()))
571 cleanupfn = None
570 cleanupfn = None
572
571
573 try:
572 try:
574 if opts.get('continue'):
573 if opts.get('continue'):
575 tp.resume(repo, source, opts)
574 tp.resume(repo, source, opts)
576 return
575 return
577
576
578 tf = tp.transplantfilter(repo, source, p1)
577 tf = tp.transplantfilter(repo, source, p1)
579 if opts.get('prune'):
578 if opts.get('prune'):
580 prune = [source.lookup(r)
579 prune = [source.lookup(r)
581 for r in scmutil.revrange(source, opts.get('prune'))]
580 for r in scmutil.revrange(source, opts.get('prune'))]
582 matchfn = lambda x: tf(x) and x not in prune
581 matchfn = lambda x: tf(x) and x not in prune
583 else:
582 else:
584 matchfn = tf
583 matchfn = tf
585 merges = map(source.lookup, opts.get('merge', ()))
584 merges = map(source.lookup, opts.get('merge', ()))
586 revmap = {}
585 revmap = {}
587 if revs:
586 if revs:
588 for r in scmutil.revrange(source, revs):
587 for r in scmutil.revrange(source, revs):
589 revmap[int(r)] = source.lookup(r)
588 revmap[int(r)] = source.lookup(r)
590 elif opts.get('all') or not merges:
589 elif opts.get('all') or not merges:
591 if source != repo:
590 if source != repo:
592 alltransplants = incwalk(source, csets, match=matchfn)
591 alltransplants = incwalk(source, csets, match=matchfn)
593 else:
592 else:
594 alltransplants = transplantwalk(source, p1, branches,
593 alltransplants = transplantwalk(source, p1, branches,
595 match=matchfn)
594 match=matchfn)
596 if opts.get('all'):
595 if opts.get('all'):
597 revs = alltransplants
596 revs = alltransplants
598 else:
597 else:
599 revs, newmerges = browserevs(ui, source, alltransplants, opts)
598 revs, newmerges = browserevs(ui, source, alltransplants, opts)
600 merges.extend(newmerges)
599 merges.extend(newmerges)
601 for r in revs:
600 for r in revs:
602 revmap[source.changelog.rev(r)] = r
601 revmap[source.changelog.rev(r)] = r
603 for r in merges:
602 for r in merges:
604 revmap[source.changelog.rev(r)] = r
603 revmap[source.changelog.rev(r)] = r
605
604
606 tp.apply(repo, source, revmap, merges, opts)
605 tp.apply(repo, source, revmap, merges, opts)
607 finally:
606 finally:
608 if cleanupfn:
607 if cleanupfn:
609 cleanupfn()
608 cleanupfn()
610
609
611 def revsettransplanted(repo, subset, x):
610 def revsettransplanted(repo, subset, x):
612 """``transplanted([set])``
611 """``transplanted([set])``
613 Transplanted changesets in set, or all transplanted changesets.
612 Transplanted changesets in set, or all transplanted changesets.
614 """
613 """
615 if x:
614 if x:
616 s = revset.getset(repo, subset, x)
615 s = revset.getset(repo, subset, x)
617 else:
616 else:
618 s = subset
617 s = subset
619 return [r for r in s if repo[r].extra().get('transplant_source')]
618 return [r for r in s if repo[r].extra().get('transplant_source')]
620
619
621 def kwtransplanted(repo, ctx, **args):
620 def kwtransplanted(repo, ctx, **args):
622 """:transplanted: String. The node identifier of the transplanted
621 """:transplanted: String. The node identifier of the transplanted
623 changeset if any."""
622 changeset if any."""
624 n = ctx.extra().get('transplant_source')
623 n = ctx.extra().get('transplant_source')
625 return n and revlog.hex(n) or ''
624 return n and revlog.hex(n) or ''
626
625
627 def extsetup(ui):
626 def extsetup(ui):
628 revset.symbols['transplanted'] = revsettransplanted
627 revset.symbols['transplanted'] = revsettransplanted
629 templatekw.keywords['transplanted'] = kwtransplanted
628 templatekw.keywords['transplanted'] = kwtransplanted
630
629
631 # tell hggettext to extract docstrings from these functions:
630 # tell hggettext to extract docstrings from these functions:
632 i18nfunctions = [revsettransplanted, kwtransplanted]
631 i18nfunctions = [revsettransplanted, kwtransplanted]
@@ -1,5051 +1,5051 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, bin, nullid, nullrev, short
8 from node import hex, bin, nullid, nullrev, short
9 from lock import release
9 from lock import release
10 from i18n import _, gettext
10 from i18n import _, gettext
11 import os, re, sys, difflib, time, tempfile
11 import os, re, sys, difflib, time, tempfile
12 import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
12 import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
13 import patch, help, url, encoding, templatekw, discovery
13 import patch, help, url, encoding, templatekw, discovery
14 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
14 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
15 import merge as mergemod
15 import merge as mergemod
16 import minirst, revset
16 import minirst, revset
17 import dagparser, context, simplemerge
17 import dagparser, context, simplemerge
18 import random, setdiscovery, treediscovery, dagutil
18 import random, setdiscovery, treediscovery, dagutil
19
19
20 table = {}
20 table = {}
21
21
22 command = cmdutil.command(table)
22 command = cmdutil.command(table)
23
23
24 # common command options
24 # common command options
25
25
26 globalopts = [
26 globalopts = [
27 ('R', 'repository', '',
27 ('R', 'repository', '',
28 _('repository root directory or name of overlay bundle file'),
28 _('repository root directory or name of overlay bundle file'),
29 _('REPO')),
29 _('REPO')),
30 ('', 'cwd', '',
30 ('', 'cwd', '',
31 _('change working directory'), _('DIR')),
31 _('change working directory'), _('DIR')),
32 ('y', 'noninteractive', None,
32 ('y', 'noninteractive', None,
33 _('do not prompt, assume \'yes\' for any required answers')),
33 _('do not prompt, assume \'yes\' for any required answers')),
34 ('q', 'quiet', None, _('suppress output')),
34 ('q', 'quiet', None, _('suppress output')),
35 ('v', 'verbose', None, _('enable additional output')),
35 ('v', 'verbose', None, _('enable additional output')),
36 ('', 'config', [],
36 ('', 'config', [],
37 _('set/override config option (use \'section.name=value\')'),
37 _('set/override config option (use \'section.name=value\')'),
38 _('CONFIG')),
38 _('CONFIG')),
39 ('', 'debug', None, _('enable debugging output')),
39 ('', 'debug', None, _('enable debugging output')),
40 ('', 'debugger', None, _('start debugger')),
40 ('', 'debugger', None, _('start debugger')),
41 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
41 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
42 _('ENCODE')),
42 _('ENCODE')),
43 ('', 'encodingmode', encoding.encodingmode,
43 ('', 'encodingmode', encoding.encodingmode,
44 _('set the charset encoding mode'), _('MODE')),
44 _('set the charset encoding mode'), _('MODE')),
45 ('', 'traceback', None, _('always print a traceback on exception')),
45 ('', 'traceback', None, _('always print a traceback on exception')),
46 ('', 'time', None, _('time how long the command takes')),
46 ('', 'time', None, _('time how long the command takes')),
47 ('', 'profile', None, _('print command execution profile')),
47 ('', 'profile', None, _('print command execution profile')),
48 ('', 'version', None, _('output version information and exit')),
48 ('', 'version', None, _('output version information and exit')),
49 ('h', 'help', None, _('display help and exit')),
49 ('h', 'help', None, _('display help and exit')),
50 ]
50 ]
51
51
52 dryrunopts = [('n', 'dry-run', None,
52 dryrunopts = [('n', 'dry-run', None,
53 _('do not perform actions, just print output'))]
53 _('do not perform actions, just print output'))]
54
54
55 remoteopts = [
55 remoteopts = [
56 ('e', 'ssh', '',
56 ('e', 'ssh', '',
57 _('specify ssh command to use'), _('CMD')),
57 _('specify ssh command to use'), _('CMD')),
58 ('', 'remotecmd', '',
58 ('', 'remotecmd', '',
59 _('specify hg command to run on the remote side'), _('CMD')),
59 _('specify hg command to run on the remote side'), _('CMD')),
60 ('', 'insecure', None,
60 ('', 'insecure', None,
61 _('do not verify server certificate (ignoring web.cacerts config)')),
61 _('do not verify server certificate (ignoring web.cacerts config)')),
62 ]
62 ]
63
63
64 walkopts = [
64 walkopts = [
65 ('I', 'include', [],
65 ('I', 'include', [],
66 _('include names matching the given patterns'), _('PATTERN')),
66 _('include names matching the given patterns'), _('PATTERN')),
67 ('X', 'exclude', [],
67 ('X', 'exclude', [],
68 _('exclude names matching the given patterns'), _('PATTERN')),
68 _('exclude names matching the given patterns'), _('PATTERN')),
69 ]
69 ]
70
70
71 commitopts = [
71 commitopts = [
72 ('m', 'message', '',
72 ('m', 'message', '',
73 _('use text as commit message'), _('TEXT')),
73 _('use text as commit message'), _('TEXT')),
74 ('l', 'logfile', '',
74 ('l', 'logfile', '',
75 _('read commit message from file'), _('FILE')),
75 _('read commit message from file'), _('FILE')),
76 ]
76 ]
77
77
78 commitopts2 = [
78 commitopts2 = [
79 ('d', 'date', '',
79 ('d', 'date', '',
80 _('record the specified date as commit date'), _('DATE')),
80 _('record the specified date as commit date'), _('DATE')),
81 ('u', 'user', '',
81 ('u', 'user', '',
82 _('record the specified user as committer'), _('USER')),
82 _('record the specified user as committer'), _('USER')),
83 ]
83 ]
84
84
85 templateopts = [
85 templateopts = [
86 ('', 'style', '',
86 ('', 'style', '',
87 _('display using template map file'), _('STYLE')),
87 _('display using template map file'), _('STYLE')),
88 ('', 'template', '',
88 ('', 'template', '',
89 _('display with template'), _('TEMPLATE')),
89 _('display with template'), _('TEMPLATE')),
90 ]
90 ]
91
91
92 logopts = [
92 logopts = [
93 ('p', 'patch', None, _('show patch')),
93 ('p', 'patch', None, _('show patch')),
94 ('g', 'git', None, _('use git extended diff format')),
94 ('g', 'git', None, _('use git extended diff format')),
95 ('l', 'limit', '',
95 ('l', 'limit', '',
96 _('limit number of changes displayed'), _('NUM')),
96 _('limit number of changes displayed'), _('NUM')),
97 ('M', 'no-merges', None, _('do not show merges')),
97 ('M', 'no-merges', None, _('do not show merges')),
98 ('', 'stat', None, _('output diffstat-style summary of changes')),
98 ('', 'stat', None, _('output diffstat-style summary of changes')),
99 ] + templateopts
99 ] + templateopts
100
100
101 diffopts = [
101 diffopts = [
102 ('a', 'text', None, _('treat all files as text')),
102 ('a', 'text', None, _('treat all files as text')),
103 ('g', 'git', None, _('use git extended diff format')),
103 ('g', 'git', None, _('use git extended diff format')),
104 ('', 'nodates', None, _('omit dates from diff headers'))
104 ('', 'nodates', None, _('omit dates from diff headers'))
105 ]
105 ]
106
106
107 diffopts2 = [
107 diffopts2 = [
108 ('p', 'show-function', None, _('show which function each change is in')),
108 ('p', 'show-function', None, _('show which function each change is in')),
109 ('', 'reverse', None, _('produce a diff that undoes the changes')),
109 ('', 'reverse', None, _('produce a diff that undoes the changes')),
110 ('w', 'ignore-all-space', None,
110 ('w', 'ignore-all-space', None,
111 _('ignore white space when comparing lines')),
111 _('ignore white space when comparing lines')),
112 ('b', 'ignore-space-change', None,
112 ('b', 'ignore-space-change', None,
113 _('ignore changes in the amount of white space')),
113 _('ignore changes in the amount of white space')),
114 ('B', 'ignore-blank-lines', None,
114 ('B', 'ignore-blank-lines', None,
115 _('ignore changes whose lines are all blank')),
115 _('ignore changes whose lines are all blank')),
116 ('U', 'unified', '',
116 ('U', 'unified', '',
117 _('number of lines of context to show'), _('NUM')),
117 _('number of lines of context to show'), _('NUM')),
118 ('', 'stat', None, _('output diffstat-style summary of changes')),
118 ('', 'stat', None, _('output diffstat-style summary of changes')),
119 ]
119 ]
120
120
121 similarityopts = [
121 similarityopts = [
122 ('s', 'similarity', '',
122 ('s', 'similarity', '',
123 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
123 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
124 ]
124 ]
125
125
126 subrepoopts = [
126 subrepoopts = [
127 ('S', 'subrepos', None,
127 ('S', 'subrepos', None,
128 _('recurse into subrepositories'))
128 _('recurse into subrepositories'))
129 ]
129 ]
130
130
131 # Commands start here, listed alphabetically
131 # Commands start here, listed alphabetically
132
132
133 @command('^add',
133 @command('^add',
134 walkopts + subrepoopts + dryrunopts,
134 walkopts + subrepoopts + dryrunopts,
135 _('[OPTION]... [FILE]...'))
135 _('[OPTION]... [FILE]...'))
136 def add(ui, repo, *pats, **opts):
136 def add(ui, repo, *pats, **opts):
137 """add the specified files on the next commit
137 """add the specified files on the next commit
138
138
139 Schedule files to be version controlled and added to the
139 Schedule files to be version controlled and added to the
140 repository.
140 repository.
141
141
142 The files will be added to the repository at the next commit. To
142 The files will be added to the repository at the next commit. To
143 undo an add before that, see :hg:`forget`.
143 undo an add before that, see :hg:`forget`.
144
144
145 If no names are given, add all files to the repository.
145 If no names are given, add all files to the repository.
146
146
147 .. container:: verbose
147 .. container:: verbose
148
148
149 An example showing how new (unknown) files are added
149 An example showing how new (unknown) files are added
150 automatically by :hg:`add`::
150 automatically by :hg:`add`::
151
151
152 $ ls
152 $ ls
153 foo.c
153 foo.c
154 $ hg status
154 $ hg status
155 ? foo.c
155 ? foo.c
156 $ hg add
156 $ hg add
157 adding foo.c
157 adding foo.c
158 $ hg status
158 $ hg status
159 A foo.c
159 A foo.c
160
160
161 Returns 0 if all files are successfully added.
161 Returns 0 if all files are successfully added.
162 """
162 """
163
163
164 m = scmutil.match(repo, pats, opts)
164 m = scmutil.match(repo, pats, opts)
165 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
165 rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'),
166 opts.get('subrepos'), prefix="")
166 opts.get('subrepos'), prefix="")
167 return rejected and 1 or 0
167 return rejected and 1 or 0
168
168
169 @command('addremove',
169 @command('addremove',
170 similarityopts + walkopts + dryrunopts,
170 similarityopts + walkopts + dryrunopts,
171 _('[OPTION]... [FILE]...'))
171 _('[OPTION]... [FILE]...'))
172 def addremove(ui, repo, *pats, **opts):
172 def addremove(ui, repo, *pats, **opts):
173 """add all new files, delete all missing files
173 """add all new files, delete all missing files
174
174
175 Add all new files and remove all missing files from the
175 Add all new files and remove all missing files from the
176 repository.
176 repository.
177
177
178 New files are ignored if they match any of the patterns in
178 New files are ignored if they match any of the patterns in
179 ``.hgignore``. As with add, these changes take effect at the next
179 ``.hgignore``. As with add, these changes take effect at the next
180 commit.
180 commit.
181
181
182 Use the -s/--similarity option to detect renamed files. With a
182 Use the -s/--similarity option to detect renamed files. With a
183 parameter greater than 0, this compares every removed file with
183 parameter greater than 0, this compares every removed file with
184 every added file and records those similar enough as renames. This
184 every added file and records those similar enough as renames. This
185 option takes a percentage between 0 (disabled) and 100 (files must
185 option takes a percentage between 0 (disabled) and 100 (files must
186 be identical) as its parameter. Detecting renamed files this way
186 be identical) as its parameter. Detecting renamed files this way
187 can be expensive. After using this option, :hg:`status -C` can be
187 can be expensive. After using this option, :hg:`status -C` can be
188 used to check which files were identified as moved or renamed.
188 used to check which files were identified as moved or renamed.
189
189
190 Returns 0 if all files are successfully added.
190 Returns 0 if all files are successfully added.
191 """
191 """
192 try:
192 try:
193 sim = float(opts.get('similarity') or 100)
193 sim = float(opts.get('similarity') or 100)
194 except ValueError:
194 except ValueError:
195 raise util.Abort(_('similarity must be a number'))
195 raise util.Abort(_('similarity must be a number'))
196 if sim < 0 or sim > 100:
196 if sim < 0 or sim > 100:
197 raise util.Abort(_('similarity must be between 0 and 100'))
197 raise util.Abort(_('similarity must be between 0 and 100'))
198 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
198 return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0)
199
199
200 @command('^annotate|blame',
200 @command('^annotate|blame',
201 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
201 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
202 ('', 'follow', None,
202 ('', 'follow', None,
203 _('follow copies/renames and list the filename (DEPRECATED)')),
203 _('follow copies/renames and list the filename (DEPRECATED)')),
204 ('', 'no-follow', None, _("don't follow copies and renames")),
204 ('', 'no-follow', None, _("don't follow copies and renames")),
205 ('a', 'text', None, _('treat all files as text')),
205 ('a', 'text', None, _('treat all files as text')),
206 ('u', 'user', None, _('list the author (long with -v)')),
206 ('u', 'user', None, _('list the author (long with -v)')),
207 ('f', 'file', None, _('list the filename')),
207 ('f', 'file', None, _('list the filename')),
208 ('d', 'date', None, _('list the date (short with -q)')),
208 ('d', 'date', None, _('list the date (short with -q)')),
209 ('n', 'number', None, _('list the revision number (default)')),
209 ('n', 'number', None, _('list the revision number (default)')),
210 ('c', 'changeset', None, _('list the changeset')),
210 ('c', 'changeset', None, _('list the changeset')),
211 ('l', 'line-number', None, _('show line number at the first appearance'))
211 ('l', 'line-number', None, _('show line number at the first appearance'))
212 ] + walkopts,
212 ] + walkopts,
213 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
213 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'))
214 def annotate(ui, repo, *pats, **opts):
214 def annotate(ui, repo, *pats, **opts):
215 """show changeset information by line for each file
215 """show changeset information by line for each file
216
216
217 List changes in files, showing the revision id responsible for
217 List changes in files, showing the revision id responsible for
218 each line
218 each line
219
219
220 This command is useful for discovering when a change was made and
220 This command is useful for discovering when a change was made and
221 by whom.
221 by whom.
222
222
223 Without the -a/--text option, annotate will avoid processing files
223 Without the -a/--text option, annotate will avoid processing files
224 it detects as binary. With -a, annotate will annotate the file
224 it detects as binary. With -a, annotate will annotate the file
225 anyway, although the results will probably be neither useful
225 anyway, although the results will probably be neither useful
226 nor desirable.
226 nor desirable.
227
227
228 Returns 0 on success.
228 Returns 0 on success.
229 """
229 """
230 if opts.get('follow'):
230 if opts.get('follow'):
231 # --follow is deprecated and now just an alias for -f/--file
231 # --follow is deprecated and now just an alias for -f/--file
232 # to mimic the behavior of Mercurial before version 1.5
232 # to mimic the behavior of Mercurial before version 1.5
233 opts['file'] = True
233 opts['file'] = True
234
234
235 datefunc = ui.quiet and util.shortdate or util.datestr
235 datefunc = ui.quiet and util.shortdate or util.datestr
236 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
236 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
237
237
238 if not pats:
238 if not pats:
239 raise util.Abort(_('at least one filename or pattern is required'))
239 raise util.Abort(_('at least one filename or pattern is required'))
240
240
241 opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
241 opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
242 ('number', ' ', lambda x: str(x[0].rev())),
242 ('number', ' ', lambda x: str(x[0].rev())),
243 ('changeset', ' ', lambda x: short(x[0].node())),
243 ('changeset', ' ', lambda x: short(x[0].node())),
244 ('date', ' ', getdate),
244 ('date', ' ', getdate),
245 ('file', ' ', lambda x: x[0].path()),
245 ('file', ' ', lambda x: x[0].path()),
246 ('line_number', ':', lambda x: str(x[1])),
246 ('line_number', ':', lambda x: str(x[1])),
247 ]
247 ]
248
248
249 if (not opts.get('user') and not opts.get('changeset')
249 if (not opts.get('user') and not opts.get('changeset')
250 and not opts.get('date') and not opts.get('file')):
250 and not opts.get('date') and not opts.get('file')):
251 opts['number'] = True
251 opts['number'] = True
252
252
253 linenumber = opts.get('line_number') is not None
253 linenumber = opts.get('line_number') is not None
254 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
254 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
255 raise util.Abort(_('at least one of -n/-c is required for -l'))
255 raise util.Abort(_('at least one of -n/-c is required for -l'))
256
256
257 funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
257 funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
258 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
258 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
259
259
260 def bad(x, y):
260 def bad(x, y):
261 raise util.Abort("%s: %s" % (x, y))
261 raise util.Abort("%s: %s" % (x, y))
262
262
263 ctx = scmutil.revsingle(repo, opts.get('rev'))
263 ctx = scmutil.revsingle(repo, opts.get('rev'))
264 m = scmutil.match(repo, pats, opts)
264 m = scmutil.match(repo, pats, opts)
265 m.bad = bad
265 m.bad = bad
266 follow = not opts.get('no_follow')
266 follow = not opts.get('no_follow')
267 for abs in ctx.walk(m):
267 for abs in ctx.walk(m):
268 fctx = ctx[abs]
268 fctx = ctx[abs]
269 if not opts.get('text') and util.binary(fctx.data()):
269 if not opts.get('text') and util.binary(fctx.data()):
270 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
270 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
271 continue
271 continue
272
272
273 lines = fctx.annotate(follow=follow, linenumber=linenumber)
273 lines = fctx.annotate(follow=follow, linenumber=linenumber)
274 pieces = []
274 pieces = []
275
275
276 for f, sep in funcmap:
276 for f, sep in funcmap:
277 l = [f(n) for n, dummy in lines]
277 l = [f(n) for n, dummy in lines]
278 if l:
278 if l:
279 sized = [(x, encoding.colwidth(x)) for x in l]
279 sized = [(x, encoding.colwidth(x)) for x in l]
280 ml = max([w for x, w in sized])
280 ml = max([w for x, w in sized])
281 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
281 pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
282 for x, w in sized])
282 for x, w in sized])
283
283
284 if pieces:
284 if pieces:
285 for p, l in zip(zip(*pieces), lines):
285 for p, l in zip(zip(*pieces), lines):
286 ui.write("%s: %s" % ("".join(p), l[1]))
286 ui.write("%s: %s" % ("".join(p), l[1]))
287
287
288 @command('archive',
288 @command('archive',
289 [('', 'no-decode', None, _('do not pass files through decoders')),
289 [('', 'no-decode', None, _('do not pass files through decoders')),
290 ('p', 'prefix', '', _('directory prefix for files in archive'),
290 ('p', 'prefix', '', _('directory prefix for files in archive'),
291 _('PREFIX')),
291 _('PREFIX')),
292 ('r', 'rev', '', _('revision to distribute'), _('REV')),
292 ('r', 'rev', '', _('revision to distribute'), _('REV')),
293 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
293 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
294 ] + subrepoopts + walkopts,
294 ] + subrepoopts + walkopts,
295 _('[OPTION]... DEST'))
295 _('[OPTION]... DEST'))
296 def archive(ui, repo, dest, **opts):
296 def archive(ui, repo, dest, **opts):
297 '''create an unversioned archive of a repository revision
297 '''create an unversioned archive of a repository revision
298
298
299 By default, the revision used is the parent of the working
299 By default, the revision used is the parent of the working
300 directory; use -r/--rev to specify a different revision.
300 directory; use -r/--rev to specify a different revision.
301
301
302 The archive type is automatically detected based on file
302 The archive type is automatically detected based on file
303 extension (or override using -t/--type).
303 extension (or override using -t/--type).
304
304
305 Valid types are:
305 Valid types are:
306
306
307 :``files``: a directory full of files (default)
307 :``files``: a directory full of files (default)
308 :``tar``: tar archive, uncompressed
308 :``tar``: tar archive, uncompressed
309 :``tbz2``: tar archive, compressed using bzip2
309 :``tbz2``: tar archive, compressed using bzip2
310 :``tgz``: tar archive, compressed using gzip
310 :``tgz``: tar archive, compressed using gzip
311 :``uzip``: zip archive, uncompressed
311 :``uzip``: zip archive, uncompressed
312 :``zip``: zip archive, compressed using deflate
312 :``zip``: zip archive, compressed using deflate
313
313
314 The exact name of the destination archive or directory is given
314 The exact name of the destination archive or directory is given
315 using a format string; see :hg:`help export` for details.
315 using a format string; see :hg:`help export` for details.
316
316
317 Each member added to an archive file has a directory prefix
317 Each member added to an archive file has a directory prefix
318 prepended. Use -p/--prefix to specify a format string for the
318 prepended. Use -p/--prefix to specify a format string for the
319 prefix. The default is the basename of the archive, with suffixes
319 prefix. The default is the basename of the archive, with suffixes
320 removed.
320 removed.
321
321
322 Returns 0 on success.
322 Returns 0 on success.
323 '''
323 '''
324
324
325 ctx = scmutil.revsingle(repo, opts.get('rev'))
325 ctx = scmutil.revsingle(repo, opts.get('rev'))
326 if not ctx:
326 if not ctx:
327 raise util.Abort(_('no working directory: please specify a revision'))
327 raise util.Abort(_('no working directory: please specify a revision'))
328 node = ctx.node()
328 node = ctx.node()
329 dest = cmdutil.makefilename(repo, dest, node)
329 dest = cmdutil.makefilename(repo, dest, node)
330 if os.path.realpath(dest) == repo.root:
330 if os.path.realpath(dest) == repo.root:
331 raise util.Abort(_('repository root cannot be destination'))
331 raise util.Abort(_('repository root cannot be destination'))
332
332
333 kind = opts.get('type') or archival.guesskind(dest) or 'files'
333 kind = opts.get('type') or archival.guesskind(dest) or 'files'
334 prefix = opts.get('prefix')
334 prefix = opts.get('prefix')
335
335
336 if dest == '-':
336 if dest == '-':
337 if kind == 'files':
337 if kind == 'files':
338 raise util.Abort(_('cannot archive plain files to stdout'))
338 raise util.Abort(_('cannot archive plain files to stdout'))
339 dest = sys.stdout
339 dest = sys.stdout
340 if not prefix:
340 if not prefix:
341 prefix = os.path.basename(repo.root) + '-%h'
341 prefix = os.path.basename(repo.root) + '-%h'
342
342
343 prefix = cmdutil.makefilename(repo, prefix, node)
343 prefix = cmdutil.makefilename(repo, prefix, node)
344 matchfn = scmutil.match(repo, [], opts)
344 matchfn = scmutil.match(repo, [], opts)
345 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
345 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
346 matchfn, prefix, subrepos=opts.get('subrepos'))
346 matchfn, prefix, subrepos=opts.get('subrepos'))
347
347
348 @command('backout',
348 @command('backout',
349 [('', 'merge', None, _('merge with old dirstate parent after backout')),
349 [('', 'merge', None, _('merge with old dirstate parent after backout')),
350 ('', 'parent', '', _('parent to choose when backing out merge'), _('REV')),
350 ('', 'parent', '', _('parent to choose when backing out merge'), _('REV')),
351 ('t', 'tool', '', _('specify merge tool')),
351 ('t', 'tool', '', _('specify merge tool')),
352 ('r', 'rev', '', _('revision to backout'), _('REV')),
352 ('r', 'rev', '', _('revision to backout'), _('REV')),
353 ] + walkopts + commitopts + commitopts2,
353 ] + walkopts + commitopts + commitopts2,
354 _('[OPTION]... [-r] REV'))
354 _('[OPTION]... [-r] REV'))
355 def backout(ui, repo, node=None, rev=None, **opts):
355 def backout(ui, repo, node=None, rev=None, **opts):
356 '''reverse effect of earlier changeset
356 '''reverse effect of earlier changeset
357
357
358 Prepare a new changeset with the effect of REV undone in the
358 Prepare a new changeset with the effect of REV undone in the
359 current working directory.
359 current working directory.
360
360
361 If REV is the parent of the working directory, then this new changeset
361 If REV is the parent of the working directory, then this new changeset
362 is committed automatically. Otherwise, hg needs to merge the
362 is committed automatically. Otherwise, hg needs to merge the
363 changes and the merged result is left uncommitted.
363 changes and the merged result is left uncommitted.
364
364
365 By default, the pending changeset will have one parent,
365 By default, the pending changeset will have one parent,
366 maintaining a linear history. With --merge, the pending changeset
366 maintaining a linear history. With --merge, the pending changeset
367 will instead have two parents: the old parent of the working
367 will instead have two parents: the old parent of the working
368 directory and a new child of REV that simply undoes REV.
368 directory and a new child of REV that simply undoes REV.
369
369
370 Before version 1.7, the behavior without --merge was equivalent to
370 Before version 1.7, the behavior without --merge was equivalent to
371 specifying --merge followed by :hg:`update --clean .` to cancel
371 specifying --merge followed by :hg:`update --clean .` to cancel
372 the merge and leave the child of REV as a head to be merged
372 the merge and leave the child of REV as a head to be merged
373 separately.
373 separately.
374
374
375 See :hg:`help dates` for a list of formats valid for -d/--date.
375 See :hg:`help dates` for a list of formats valid for -d/--date.
376
376
377 Returns 0 on success.
377 Returns 0 on success.
378 '''
378 '''
379 if rev and node:
379 if rev and node:
380 raise util.Abort(_("please specify just one revision"))
380 raise util.Abort(_("please specify just one revision"))
381
381
382 if not rev:
382 if not rev:
383 rev = node
383 rev = node
384
384
385 if not rev:
385 if not rev:
386 raise util.Abort(_("please specify a revision to backout"))
386 raise util.Abort(_("please specify a revision to backout"))
387
387
388 date = opts.get('date')
388 date = opts.get('date')
389 if date:
389 if date:
390 opts['date'] = util.parsedate(date)
390 opts['date'] = util.parsedate(date)
391
391
392 cmdutil.bailifchanged(repo)
392 cmdutil.bailifchanged(repo)
393 node = scmutil.revsingle(repo, rev).node()
393 node = scmutil.revsingle(repo, rev).node()
394
394
395 op1, op2 = repo.dirstate.parents()
395 op1, op2 = repo.dirstate.parents()
396 a = repo.changelog.ancestor(op1, node)
396 a = repo.changelog.ancestor(op1, node)
397 if a != node:
397 if a != node:
398 raise util.Abort(_('cannot backout change on a different branch'))
398 raise util.Abort(_('cannot backout change on a different branch'))
399
399
400 p1, p2 = repo.changelog.parents(node)
400 p1, p2 = repo.changelog.parents(node)
401 if p1 == nullid:
401 if p1 == nullid:
402 raise util.Abort(_('cannot backout a change with no parents'))
402 raise util.Abort(_('cannot backout a change with no parents'))
403 if p2 != nullid:
403 if p2 != nullid:
404 if not opts.get('parent'):
404 if not opts.get('parent'):
405 raise util.Abort(_('cannot backout a merge changeset without '
405 raise util.Abort(_('cannot backout a merge changeset without '
406 '--parent'))
406 '--parent'))
407 p = repo.lookup(opts['parent'])
407 p = repo.lookup(opts['parent'])
408 if p not in (p1, p2):
408 if p not in (p1, p2):
409 raise util.Abort(_('%s is not a parent of %s') %
409 raise util.Abort(_('%s is not a parent of %s') %
410 (short(p), short(node)))
410 (short(p), short(node)))
411 parent = p
411 parent = p
412 else:
412 else:
413 if opts.get('parent'):
413 if opts.get('parent'):
414 raise util.Abort(_('cannot use --parent on non-merge changeset'))
414 raise util.Abort(_('cannot use --parent on non-merge changeset'))
415 parent = p1
415 parent = p1
416
416
417 # the backout should appear on the same branch
417 # the backout should appear on the same branch
418 branch = repo.dirstate.branch()
418 branch = repo.dirstate.branch()
419 hg.clean(repo, node, show_stats=False)
419 hg.clean(repo, node, show_stats=False)
420 repo.dirstate.setbranch(branch)
420 repo.dirstate.setbranch(branch)
421 revert_opts = opts.copy()
421 revert_opts = opts.copy()
422 revert_opts['date'] = None
422 revert_opts['date'] = None
423 revert_opts['all'] = True
423 revert_opts['all'] = True
424 revert_opts['rev'] = hex(parent)
424 revert_opts['rev'] = hex(parent)
425 revert_opts['no_backup'] = None
425 revert_opts['no_backup'] = None
426 revert(ui, repo, **revert_opts)
426 revert(ui, repo, **revert_opts)
427 if not opts.get('merge') and op1 != node:
427 if not opts.get('merge') and op1 != node:
428 try:
428 try:
429 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
429 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
430 return hg.update(repo, op1)
430 return hg.update(repo, op1)
431 finally:
431 finally:
432 ui.setconfig('ui', 'forcemerge', '')
432 ui.setconfig('ui', 'forcemerge', '')
433
433
434 commit_opts = opts.copy()
434 commit_opts = opts.copy()
435 commit_opts['addremove'] = False
435 commit_opts['addremove'] = False
436 if not commit_opts['message'] and not commit_opts['logfile']:
436 if not commit_opts['message'] and not commit_opts['logfile']:
437 # we don't translate commit messages
437 # we don't translate commit messages
438 commit_opts['message'] = "Backed out changeset %s" % short(node)
438 commit_opts['message'] = "Backed out changeset %s" % short(node)
439 commit_opts['force_editor'] = True
439 commit_opts['force_editor'] = True
440 commit(ui, repo, **commit_opts)
440 commit(ui, repo, **commit_opts)
441 def nice(node):
441 def nice(node):
442 return '%d:%s' % (repo.changelog.rev(node), short(node))
442 return '%d:%s' % (repo.changelog.rev(node), short(node))
443 ui.status(_('changeset %s backs out changeset %s\n') %
443 ui.status(_('changeset %s backs out changeset %s\n') %
444 (nice(repo.changelog.tip()), nice(node)))
444 (nice(repo.changelog.tip()), nice(node)))
445 if opts.get('merge') and op1 != node:
445 if opts.get('merge') and op1 != node:
446 hg.clean(repo, op1, show_stats=False)
446 hg.clean(repo, op1, show_stats=False)
447 ui.status(_('merging with changeset %s\n')
447 ui.status(_('merging with changeset %s\n')
448 % nice(repo.changelog.tip()))
448 % nice(repo.changelog.tip()))
449 try:
449 try:
450 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
450 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
451 return hg.merge(repo, hex(repo.changelog.tip()))
451 return hg.merge(repo, hex(repo.changelog.tip()))
452 finally:
452 finally:
453 ui.setconfig('ui', 'forcemerge', '')
453 ui.setconfig('ui', 'forcemerge', '')
454 return 0
454 return 0
455
455
456 @command('bisect',
456 @command('bisect',
457 [('r', 'reset', False, _('reset bisect state')),
457 [('r', 'reset', False, _('reset bisect state')),
458 ('g', 'good', False, _('mark changeset good')),
458 ('g', 'good', False, _('mark changeset good')),
459 ('b', 'bad', False, _('mark changeset bad')),
459 ('b', 'bad', False, _('mark changeset bad')),
460 ('s', 'skip', False, _('skip testing changeset')),
460 ('s', 'skip', False, _('skip testing changeset')),
461 ('e', 'extend', False, _('extend the bisect range')),
461 ('e', 'extend', False, _('extend the bisect range')),
462 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
462 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
463 ('U', 'noupdate', False, _('do not update to target'))],
463 ('U', 'noupdate', False, _('do not update to target'))],
464 _("[-gbsr] [-U] [-c CMD] [REV]"))
464 _("[-gbsr] [-U] [-c CMD] [REV]"))
465 def bisect(ui, repo, rev=None, extra=None, command=None,
465 def bisect(ui, repo, rev=None, extra=None, command=None,
466 reset=None, good=None, bad=None, skip=None, extend=None,
466 reset=None, good=None, bad=None, skip=None, extend=None,
467 noupdate=None):
467 noupdate=None):
468 """subdivision search of changesets
468 """subdivision search of changesets
469
469
470 This command helps to find changesets which introduce problems. To
470 This command helps to find changesets which introduce problems. To
471 use, mark the earliest changeset you know exhibits the problem as
471 use, mark the earliest changeset you know exhibits the problem as
472 bad, then mark the latest changeset which is free from the problem
472 bad, then mark the latest changeset which is free from the problem
473 as good. Bisect will update your working directory to a revision
473 as good. Bisect will update your working directory to a revision
474 for testing (unless the -U/--noupdate option is specified). Once
474 for testing (unless the -U/--noupdate option is specified). Once
475 you have performed tests, mark the working directory as good or
475 you have performed tests, mark the working directory as good or
476 bad, and bisect will either update to another candidate changeset
476 bad, and bisect will either update to another candidate changeset
477 or announce that it has found the bad revision.
477 or announce that it has found the bad revision.
478
478
479 As a shortcut, you can also use the revision argument to mark a
479 As a shortcut, you can also use the revision argument to mark a
480 revision as good or bad without checking it out first.
480 revision as good or bad without checking it out first.
481
481
482 If you supply a command, it will be used for automatic bisection.
482 If you supply a command, it will be used for automatic bisection.
483 Its exit status will be used to mark revisions as good or bad:
483 Its exit status will be used to mark revisions as good or bad:
484 status 0 means good, 125 means to skip the revision, 127
484 status 0 means good, 125 means to skip the revision, 127
485 (command not found) will abort the bisection, and any other
485 (command not found) will abort the bisection, and any other
486 non-zero exit status means the revision is bad.
486 non-zero exit status means the revision is bad.
487
487
488 Returns 0 on success.
488 Returns 0 on success.
489 """
489 """
490 def extendbisectrange(nodes, good):
490 def extendbisectrange(nodes, good):
491 # bisect is incomplete when it ends on a merge node and
491 # bisect is incomplete when it ends on a merge node and
492 # one of the parent was not checked.
492 # one of the parent was not checked.
493 parents = repo[nodes[0]].parents()
493 parents = repo[nodes[0]].parents()
494 if len(parents) > 1:
494 if len(parents) > 1:
495 side = good and state['bad'] or state['good']
495 side = good and state['bad'] or state['good']
496 num = len(set(i.node() for i in parents) & set(side))
496 num = len(set(i.node() for i in parents) & set(side))
497 if num == 1:
497 if num == 1:
498 return parents[0].ancestor(parents[1])
498 return parents[0].ancestor(parents[1])
499 return None
499 return None
500
500
501 def print_result(nodes, good):
501 def print_result(nodes, good):
502 displayer = cmdutil.show_changeset(ui, repo, {})
502 displayer = cmdutil.show_changeset(ui, repo, {})
503 if len(nodes) == 1:
503 if len(nodes) == 1:
504 # narrowed it down to a single revision
504 # narrowed it down to a single revision
505 if good:
505 if good:
506 ui.write(_("The first good revision is:\n"))
506 ui.write(_("The first good revision is:\n"))
507 else:
507 else:
508 ui.write(_("The first bad revision is:\n"))
508 ui.write(_("The first bad revision is:\n"))
509 displayer.show(repo[nodes[0]])
509 displayer.show(repo[nodes[0]])
510 extendnode = extendbisectrange(nodes, good)
510 extendnode = extendbisectrange(nodes, good)
511 if extendnode is not None:
511 if extendnode is not None:
512 ui.write(_('Not all ancestors of this changeset have been'
512 ui.write(_('Not all ancestors of this changeset have been'
513 ' checked.\nUse bisect --extend to continue the '
513 ' checked.\nUse bisect --extend to continue the '
514 'bisection from\nthe common ancestor, %s.\n')
514 'bisection from\nthe common ancestor, %s.\n')
515 % extendnode)
515 % extendnode)
516 else:
516 else:
517 # multiple possible revisions
517 # multiple possible revisions
518 if good:
518 if good:
519 ui.write(_("Due to skipped revisions, the first "
519 ui.write(_("Due to skipped revisions, the first "
520 "good revision could be any of:\n"))
520 "good revision could be any of:\n"))
521 else:
521 else:
522 ui.write(_("Due to skipped revisions, the first "
522 ui.write(_("Due to skipped revisions, the first "
523 "bad revision could be any of:\n"))
523 "bad revision could be any of:\n"))
524 for n in nodes:
524 for n in nodes:
525 displayer.show(repo[n])
525 displayer.show(repo[n])
526 displayer.close()
526 displayer.close()
527
527
528 def check_state(state, interactive=True):
528 def check_state(state, interactive=True):
529 if not state['good'] or not state['bad']:
529 if not state['good'] or not state['bad']:
530 if (good or bad or skip or reset) and interactive:
530 if (good or bad or skip or reset) and interactive:
531 return
531 return
532 if not state['good']:
532 if not state['good']:
533 raise util.Abort(_('cannot bisect (no known good revisions)'))
533 raise util.Abort(_('cannot bisect (no known good revisions)'))
534 else:
534 else:
535 raise util.Abort(_('cannot bisect (no known bad revisions)'))
535 raise util.Abort(_('cannot bisect (no known bad revisions)'))
536 return True
536 return True
537
537
538 # backward compatibility
538 # backward compatibility
539 if rev in "good bad reset init".split():
539 if rev in "good bad reset init".split():
540 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
540 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
541 cmd, rev, extra = rev, extra, None
541 cmd, rev, extra = rev, extra, None
542 if cmd == "good":
542 if cmd == "good":
543 good = True
543 good = True
544 elif cmd == "bad":
544 elif cmd == "bad":
545 bad = True
545 bad = True
546 else:
546 else:
547 reset = True
547 reset = True
548 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
548 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
549 raise util.Abort(_('incompatible arguments'))
549 raise util.Abort(_('incompatible arguments'))
550
550
551 if reset:
551 if reset:
552 p = repo.join("bisect.state")
552 p = repo.join("bisect.state")
553 if os.path.exists(p):
553 if os.path.exists(p):
554 os.unlink(p)
554 os.unlink(p)
555 return
555 return
556
556
557 state = hbisect.load_state(repo)
557 state = hbisect.load_state(repo)
558
558
559 if command:
559 if command:
560 changesets = 1
560 changesets = 1
561 try:
561 try:
562 while changesets:
562 while changesets:
563 # update state
563 # update state
564 status = util.system(command)
564 status = util.system(command)
565 if status == 125:
565 if status == 125:
566 transition = "skip"
566 transition = "skip"
567 elif status == 0:
567 elif status == 0:
568 transition = "good"
568 transition = "good"
569 # status < 0 means process was killed
569 # status < 0 means process was killed
570 elif status == 127:
570 elif status == 127:
571 raise util.Abort(_("failed to execute %s") % command)
571 raise util.Abort(_("failed to execute %s") % command)
572 elif status < 0:
572 elif status < 0:
573 raise util.Abort(_("%s killed") % command)
573 raise util.Abort(_("%s killed") % command)
574 else:
574 else:
575 transition = "bad"
575 transition = "bad"
576 ctx = scmutil.revsingle(repo, rev)
576 ctx = scmutil.revsingle(repo, rev)
577 rev = None # clear for future iterations
577 rev = None # clear for future iterations
578 state[transition].append(ctx.node())
578 state[transition].append(ctx.node())
579 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
579 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
580 check_state(state, interactive=False)
580 check_state(state, interactive=False)
581 # bisect
581 # bisect
582 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
582 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
583 # update to next check
583 # update to next check
584 cmdutil.bailifchanged(repo)
584 cmdutil.bailifchanged(repo)
585 hg.clean(repo, nodes[0], show_stats=False)
585 hg.clean(repo, nodes[0], show_stats=False)
586 finally:
586 finally:
587 hbisect.save_state(repo, state)
587 hbisect.save_state(repo, state)
588 print_result(nodes, good)
588 print_result(nodes, good)
589 return
589 return
590
590
591 # update state
591 # update state
592
592
593 if rev:
593 if rev:
594 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
594 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
595 else:
595 else:
596 nodes = [repo.lookup('.')]
596 nodes = [repo.lookup('.')]
597
597
598 if good or bad or skip:
598 if good or bad or skip:
599 if good:
599 if good:
600 state['good'] += nodes
600 state['good'] += nodes
601 elif bad:
601 elif bad:
602 state['bad'] += nodes
602 state['bad'] += nodes
603 elif skip:
603 elif skip:
604 state['skip'] += nodes
604 state['skip'] += nodes
605 hbisect.save_state(repo, state)
605 hbisect.save_state(repo, state)
606
606
607 if not check_state(state):
607 if not check_state(state):
608 return
608 return
609
609
610 # actually bisect
610 # actually bisect
611 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
611 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
612 if extend:
612 if extend:
613 if not changesets:
613 if not changesets:
614 extendnode = extendbisectrange(nodes, good)
614 extendnode = extendbisectrange(nodes, good)
615 if extendnode is not None:
615 if extendnode is not None:
616 ui.write(_("Extending search to changeset %d:%s\n"
616 ui.write(_("Extending search to changeset %d:%s\n"
617 % (extendnode.rev(), extendnode)))
617 % (extendnode.rev(), extendnode)))
618 if noupdate:
618 if noupdate:
619 return
619 return
620 cmdutil.bailifchanged(repo)
620 cmdutil.bailifchanged(repo)
621 return hg.clean(repo, extendnode.node())
621 return hg.clean(repo, extendnode.node())
622 raise util.Abort(_("nothing to extend"))
622 raise util.Abort(_("nothing to extend"))
623
623
624 if changesets == 0:
624 if changesets == 0:
625 print_result(nodes, good)
625 print_result(nodes, good)
626 else:
626 else:
627 assert len(nodes) == 1 # only a single node can be tested next
627 assert len(nodes) == 1 # only a single node can be tested next
628 node = nodes[0]
628 node = nodes[0]
629 # compute the approximate number of remaining tests
629 # compute the approximate number of remaining tests
630 tests, size = 0, 2
630 tests, size = 0, 2
631 while size <= changesets:
631 while size <= changesets:
632 tests, size = tests + 1, size * 2
632 tests, size = tests + 1, size * 2
633 rev = repo.changelog.rev(node)
633 rev = repo.changelog.rev(node)
634 ui.write(_("Testing changeset %d:%s "
634 ui.write(_("Testing changeset %d:%s "
635 "(%d changesets remaining, ~%d tests)\n")
635 "(%d changesets remaining, ~%d tests)\n")
636 % (rev, short(node), changesets, tests))
636 % (rev, short(node), changesets, tests))
637 if not noupdate:
637 if not noupdate:
638 cmdutil.bailifchanged(repo)
638 cmdutil.bailifchanged(repo)
639 return hg.clean(repo, node)
639 return hg.clean(repo, node)
640
640
641 @command('bookmarks',
641 @command('bookmarks',
642 [('f', 'force', False, _('force')),
642 [('f', 'force', False, _('force')),
643 ('r', 'rev', '', _('revision'), _('REV')),
643 ('r', 'rev', '', _('revision'), _('REV')),
644 ('d', 'delete', False, _('delete a given bookmark')),
644 ('d', 'delete', False, _('delete a given bookmark')),
645 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
645 ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
646 ('i', 'inactive', False, _('do not mark a new bookmark active'))],
646 ('i', 'inactive', False, _('do not mark a new bookmark active'))],
647 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
647 _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]'))
648 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
648 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False,
649 rename=None, inactive=False):
649 rename=None, inactive=False):
650 '''track a line of development with movable markers
650 '''track a line of development with movable markers
651
651
652 Bookmarks are pointers to certain commits that move when
652 Bookmarks are pointers to certain commits that move when
653 committing. Bookmarks are local. They can be renamed, copied and
653 committing. Bookmarks are local. They can be renamed, copied and
654 deleted. It is possible to use bookmark names in :hg:`merge` and
654 deleted. It is possible to use bookmark names in :hg:`merge` and
655 :hg:`update` to merge and update respectively to a given bookmark.
655 :hg:`update` to merge and update respectively to a given bookmark.
656
656
657 You can use :hg:`bookmark NAME` to set a bookmark on the working
657 You can use :hg:`bookmark NAME` to set a bookmark on the working
658 directory's parent revision with the given name. If you specify
658 directory's parent revision with the given name. If you specify
659 a revision using -r REV (where REV may be an existing bookmark),
659 a revision using -r REV (where REV may be an existing bookmark),
660 the bookmark is assigned to that revision.
660 the bookmark is assigned to that revision.
661
661
662 Bookmarks can be pushed and pulled between repositories (see :hg:`help
662 Bookmarks can be pushed and pulled between repositories (see :hg:`help
663 push` and :hg:`help pull`). This requires both the local and remote
663 push` and :hg:`help pull`). This requires both the local and remote
664 repositories to support bookmarks. For versions prior to 1.8, this means
664 repositories to support bookmarks. For versions prior to 1.8, this means
665 the bookmarks extension must be enabled.
665 the bookmarks extension must be enabled.
666 '''
666 '''
667 hexfn = ui.debugflag and hex or short
667 hexfn = ui.debugflag and hex or short
668 marks = repo._bookmarks
668 marks = repo._bookmarks
669 cur = repo.changectx('.').node()
669 cur = repo.changectx('.').node()
670
670
671 if rename:
671 if rename:
672 if rename not in marks:
672 if rename not in marks:
673 raise util.Abort(_("bookmark '%s' does not exist") % rename)
673 raise util.Abort(_("bookmark '%s' does not exist") % rename)
674 if mark in marks and not force:
674 if mark in marks and not force:
675 raise util.Abort(_("bookmark '%s' already exists "
675 raise util.Abort(_("bookmark '%s' already exists "
676 "(use -f to force)") % mark)
676 "(use -f to force)") % mark)
677 if mark is None:
677 if mark is None:
678 raise util.Abort(_("new bookmark name required"))
678 raise util.Abort(_("new bookmark name required"))
679 marks[mark] = marks[rename]
679 marks[mark] = marks[rename]
680 if repo._bookmarkcurrent == rename and not inactive:
680 if repo._bookmarkcurrent == rename and not inactive:
681 bookmarks.setcurrent(repo, mark)
681 bookmarks.setcurrent(repo, mark)
682 del marks[rename]
682 del marks[rename]
683 bookmarks.write(repo)
683 bookmarks.write(repo)
684 return
684 return
685
685
686 if delete:
686 if delete:
687 if mark is None:
687 if mark is None:
688 raise util.Abort(_("bookmark name required"))
688 raise util.Abort(_("bookmark name required"))
689 if mark not in marks:
689 if mark not in marks:
690 raise util.Abort(_("bookmark '%s' does not exist") % mark)
690 raise util.Abort(_("bookmark '%s' does not exist") % mark)
691 if mark == repo._bookmarkcurrent:
691 if mark == repo._bookmarkcurrent:
692 bookmarks.setcurrent(repo, None)
692 bookmarks.setcurrent(repo, None)
693 del marks[mark]
693 del marks[mark]
694 bookmarks.write(repo)
694 bookmarks.write(repo)
695 return
695 return
696
696
697 if mark is not None:
697 if mark is not None:
698 if "\n" in mark:
698 if "\n" in mark:
699 raise util.Abort(_("bookmark name cannot contain newlines"))
699 raise util.Abort(_("bookmark name cannot contain newlines"))
700 mark = mark.strip()
700 mark = mark.strip()
701 if not mark:
701 if not mark:
702 raise util.Abort(_("bookmark names cannot consist entirely of "
702 raise util.Abort(_("bookmark names cannot consist entirely of "
703 "whitespace"))
703 "whitespace"))
704 if inactive and mark == repo._bookmarkcurrent:
704 if inactive and mark == repo._bookmarkcurrent:
705 bookmarks.setcurrent(repo, None)
705 bookmarks.setcurrent(repo, None)
706 return
706 return
707 if mark in marks and not force:
707 if mark in marks and not force:
708 raise util.Abort(_("bookmark '%s' already exists "
708 raise util.Abort(_("bookmark '%s' already exists "
709 "(use -f to force)") % mark)
709 "(use -f to force)") % mark)
710 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
710 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
711 and not force):
711 and not force):
712 raise util.Abort(
712 raise util.Abort(
713 _("a bookmark cannot have the name of an existing branch"))
713 _("a bookmark cannot have the name of an existing branch"))
714 if rev:
714 if rev:
715 marks[mark] = repo.lookup(rev)
715 marks[mark] = repo.lookup(rev)
716 else:
716 else:
717 marks[mark] = repo.changectx('.').node()
717 marks[mark] = repo.changectx('.').node()
718 if not inactive and repo.changectx('.').node() == marks[mark]:
718 if not inactive and repo.changectx('.').node() == marks[mark]:
719 bookmarks.setcurrent(repo, mark)
719 bookmarks.setcurrent(repo, mark)
720 bookmarks.write(repo)
720 bookmarks.write(repo)
721 return
721 return
722
722
723 if mark is None:
723 if mark is None:
724 if rev:
724 if rev:
725 raise util.Abort(_("bookmark name required"))
725 raise util.Abort(_("bookmark name required"))
726 if len(marks) == 0:
726 if len(marks) == 0:
727 ui.status(_("no bookmarks set\n"))
727 ui.status(_("no bookmarks set\n"))
728 else:
728 else:
729 for bmark, n in sorted(marks.iteritems()):
729 for bmark, n in sorted(marks.iteritems()):
730 current = repo._bookmarkcurrent
730 current = repo._bookmarkcurrent
731 if bmark == current and n == cur:
731 if bmark == current and n == cur:
732 prefix, label = '*', 'bookmarks.current'
732 prefix, label = '*', 'bookmarks.current'
733 else:
733 else:
734 prefix, label = ' ', ''
734 prefix, label = ' ', ''
735
735
736 if ui.quiet:
736 if ui.quiet:
737 ui.write("%s\n" % bmark, label=label)
737 ui.write("%s\n" % bmark, label=label)
738 else:
738 else:
739 ui.write(" %s %-25s %d:%s\n" % (
739 ui.write(" %s %-25s %d:%s\n" % (
740 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
740 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
741 label=label)
741 label=label)
742 return
742 return
743
743
744 @command('branch',
744 @command('branch',
745 [('f', 'force', None,
745 [('f', 'force', None,
746 _('set branch name even if it shadows an existing branch')),
746 _('set branch name even if it shadows an existing branch')),
747 ('C', 'clean', None, _('reset branch name to parent branch name'))],
747 ('C', 'clean', None, _('reset branch name to parent branch name'))],
748 _('[-fC] [NAME]'))
748 _('[-fC] [NAME]'))
749 def branch(ui, repo, label=None, **opts):
749 def branch(ui, repo, label=None, **opts):
750 """set or show the current branch name
750 """set or show the current branch name
751
751
752 With no argument, show the current branch name. With one argument,
752 With no argument, show the current branch name. With one argument,
753 set the working directory branch name (the branch will not exist
753 set the working directory branch name (the branch will not exist
754 in the repository until the next commit). Standard practice
754 in the repository until the next commit). Standard practice
755 recommends that primary development take place on the 'default'
755 recommends that primary development take place on the 'default'
756 branch.
756 branch.
757
757
758 Unless -f/--force is specified, branch will not let you set a
758 Unless -f/--force is specified, branch will not let you set a
759 branch name that already exists, even if it's inactive.
759 branch name that already exists, even if it's inactive.
760
760
761 Use -C/--clean to reset the working directory branch to that of
761 Use -C/--clean to reset the working directory branch to that of
762 the parent of the working directory, negating a previous branch
762 the parent of the working directory, negating a previous branch
763 change.
763 change.
764
764
765 Use the command :hg:`update` to switch to an existing branch. Use
765 Use the command :hg:`update` to switch to an existing branch. Use
766 :hg:`commit --close-branch` to mark this branch as closed.
766 :hg:`commit --close-branch` to mark this branch as closed.
767
767
768 Returns 0 on success.
768 Returns 0 on success.
769 """
769 """
770
770
771 if opts.get('clean'):
771 if opts.get('clean'):
772 label = repo[None].p1().branch()
772 label = repo[None].p1().branch()
773 repo.dirstate.setbranch(label)
773 repo.dirstate.setbranch(label)
774 ui.status(_('reset working directory to branch %s\n') % label)
774 ui.status(_('reset working directory to branch %s\n') % label)
775 elif label:
775 elif label:
776 if not opts.get('force') and label in repo.branchtags():
776 if not opts.get('force') and label in repo.branchtags():
777 if label not in [p.branch() for p in repo.parents()]:
777 if label not in [p.branch() for p in repo.parents()]:
778 raise util.Abort(_('a branch of the same name already exists'),
778 raise util.Abort(_('a branch of the same name already exists'),
779 # i18n: "it" refers to an existing branch
779 # i18n: "it" refers to an existing branch
780 hint=_("use 'hg update' to switch to it"))
780 hint=_("use 'hg update' to switch to it"))
781 repo.dirstate.setbranch(label)
781 repo.dirstate.setbranch(label)
782 ui.status(_('marked working directory as branch %s\n') % label)
782 ui.status(_('marked working directory as branch %s\n') % label)
783 else:
783 else:
784 ui.write("%s\n" % repo.dirstate.branch())
784 ui.write("%s\n" % repo.dirstate.branch())
785
785
786 @command('branches',
786 @command('branches',
787 [('a', 'active', False, _('show only branches that have unmerged heads')),
787 [('a', 'active', False, _('show only branches that have unmerged heads')),
788 ('c', 'closed', False, _('show normal and closed branches'))],
788 ('c', 'closed', False, _('show normal and closed branches'))],
789 _('[-ac]'))
789 _('[-ac]'))
790 def branches(ui, repo, active=False, closed=False):
790 def branches(ui, repo, active=False, closed=False):
791 """list repository named branches
791 """list repository named branches
792
792
793 List the repository's named branches, indicating which ones are
793 List the repository's named branches, indicating which ones are
794 inactive. If -c/--closed is specified, also list branches which have
794 inactive. If -c/--closed is specified, also list branches which have
795 been marked closed (see :hg:`commit --close-branch`).
795 been marked closed (see :hg:`commit --close-branch`).
796
796
797 If -a/--active is specified, only show active branches. A branch
797 If -a/--active is specified, only show active branches. A branch
798 is considered active if it contains repository heads.
798 is considered active if it contains repository heads.
799
799
800 Use the command :hg:`update` to switch to an existing branch.
800 Use the command :hg:`update` to switch to an existing branch.
801
801
802 Returns 0.
802 Returns 0.
803 """
803 """
804
804
805 hexfunc = ui.debugflag and hex or short
805 hexfunc = ui.debugflag and hex or short
806 activebranches = [repo[n].branch() for n in repo.heads()]
806 activebranches = [repo[n].branch() for n in repo.heads()]
807 def testactive(tag, node):
807 def testactive(tag, node):
808 realhead = tag in activebranches
808 realhead = tag in activebranches
809 open = node in repo.branchheads(tag, closed=False)
809 open = node in repo.branchheads(tag, closed=False)
810 return realhead and open
810 return realhead and open
811 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
811 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
812 for tag, node in repo.branchtags().items()],
812 for tag, node in repo.branchtags().items()],
813 reverse=True)
813 reverse=True)
814
814
815 for isactive, node, tag in branches:
815 for isactive, node, tag in branches:
816 if (not active) or isactive:
816 if (not active) or isactive:
817 if ui.quiet:
817 if ui.quiet:
818 ui.write("%s\n" % tag)
818 ui.write("%s\n" % tag)
819 else:
819 else:
820 hn = repo.lookup(node)
820 hn = repo.lookup(node)
821 if isactive:
821 if isactive:
822 label = 'branches.active'
822 label = 'branches.active'
823 notice = ''
823 notice = ''
824 elif hn not in repo.branchheads(tag, closed=False):
824 elif hn not in repo.branchheads(tag, closed=False):
825 if not closed:
825 if not closed:
826 continue
826 continue
827 label = 'branches.closed'
827 label = 'branches.closed'
828 notice = _(' (closed)')
828 notice = _(' (closed)')
829 else:
829 else:
830 label = 'branches.inactive'
830 label = 'branches.inactive'
831 notice = _(' (inactive)')
831 notice = _(' (inactive)')
832 if tag == repo.dirstate.branch():
832 if tag == repo.dirstate.branch():
833 label = 'branches.current'
833 label = 'branches.current'
834 rev = str(node).rjust(31 - encoding.colwidth(tag))
834 rev = str(node).rjust(31 - encoding.colwidth(tag))
835 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
835 rev = ui.label('%s:%s' % (rev, hexfunc(hn)), 'log.changeset')
836 tag = ui.label(tag, label)
836 tag = ui.label(tag, label)
837 ui.write("%s %s%s\n" % (tag, rev, notice))
837 ui.write("%s %s%s\n" % (tag, rev, notice))
838
838
839 @command('bundle',
839 @command('bundle',
840 [('f', 'force', None, _('run even when the destination is unrelated')),
840 [('f', 'force', None, _('run even when the destination is unrelated')),
841 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
841 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
842 _('REV')),
842 _('REV')),
843 ('b', 'branch', [], _('a specific branch you would like to bundle'),
843 ('b', 'branch', [], _('a specific branch you would like to bundle'),
844 _('BRANCH')),
844 _('BRANCH')),
845 ('', 'base', [],
845 ('', 'base', [],
846 _('a base changeset assumed to be available at the destination'),
846 _('a base changeset assumed to be available at the destination'),
847 _('REV')),
847 _('REV')),
848 ('a', 'all', None, _('bundle all changesets in the repository')),
848 ('a', 'all', None, _('bundle all changesets in the repository')),
849 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
849 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
850 ] + remoteopts,
850 ] + remoteopts,
851 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
851 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
852 def bundle(ui, repo, fname, dest=None, **opts):
852 def bundle(ui, repo, fname, dest=None, **opts):
853 """create a changegroup file
853 """create a changegroup file
854
854
855 Generate a compressed changegroup file collecting changesets not
855 Generate a compressed changegroup file collecting changesets not
856 known to be in another repository.
856 known to be in another repository.
857
857
858 If you omit the destination repository, then hg assumes the
858 If you omit the destination repository, then hg assumes the
859 destination will have all the nodes you specify with --base
859 destination will have all the nodes you specify with --base
860 parameters. To create a bundle containing all changesets, use
860 parameters. To create a bundle containing all changesets, use
861 -a/--all (or --base null).
861 -a/--all (or --base null).
862
862
863 You can change compression method with the -t/--type option.
863 You can change compression method with the -t/--type option.
864 The available compression methods are: none, bzip2, and
864 The available compression methods are: none, bzip2, and
865 gzip (by default, bundles are compressed using bzip2).
865 gzip (by default, bundles are compressed using bzip2).
866
866
867 The bundle file can then be transferred using conventional means
867 The bundle file can then be transferred using conventional means
868 and applied to another repository with the unbundle or pull
868 and applied to another repository with the unbundle or pull
869 command. This is useful when direct push and pull are not
869 command. This is useful when direct push and pull are not
870 available or when exporting an entire repository is undesirable.
870 available or when exporting an entire repository is undesirable.
871
871
872 Applying bundles preserves all changeset contents including
872 Applying bundles preserves all changeset contents including
873 permissions, copy/rename information, and revision history.
873 permissions, copy/rename information, and revision history.
874
874
875 Returns 0 on success, 1 if no changes found.
875 Returns 0 on success, 1 if no changes found.
876 """
876 """
877 revs = None
877 revs = None
878 if 'rev' in opts:
878 if 'rev' in opts:
879 revs = scmutil.revrange(repo, opts['rev'])
879 revs = scmutil.revrange(repo, opts['rev'])
880
880
881 if opts.get('all'):
881 if opts.get('all'):
882 base = ['null']
882 base = ['null']
883 else:
883 else:
884 base = scmutil.revrange(repo, opts.get('base'))
884 base = scmutil.revrange(repo, opts.get('base'))
885 if base:
885 if base:
886 if dest:
886 if dest:
887 raise util.Abort(_("--base is incompatible with specifying "
887 raise util.Abort(_("--base is incompatible with specifying "
888 "a destination"))
888 "a destination"))
889 common = [repo.lookup(rev) for rev in base]
889 common = [repo.lookup(rev) for rev in base]
890 heads = revs and map(repo.lookup, revs) or revs
890 heads = revs and map(repo.lookup, revs) or revs
891 else:
891 else:
892 dest = ui.expandpath(dest or 'default-push', dest or 'default')
892 dest = ui.expandpath(dest or 'default-push', dest or 'default')
893 dest, branches = hg.parseurl(dest, opts.get('branch'))
893 dest, branches = hg.parseurl(dest, opts.get('branch'))
894 other = hg.repository(hg.remoteui(repo, opts), dest)
894 other = hg.repository(hg.remoteui(repo, opts), dest)
895 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
895 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
896 heads = revs and map(repo.lookup, revs) or revs
896 heads = revs and map(repo.lookup, revs) or revs
897 common, outheads = discovery.findcommonoutgoing(repo, other,
897 common, outheads = discovery.findcommonoutgoing(repo, other,
898 onlyheads=heads,
898 onlyheads=heads,
899 force=opts.get('force'))
899 force=opts.get('force'))
900
900
901 cg = repo.getbundle('bundle', common=common, heads=heads)
901 cg = repo.getbundle('bundle', common=common, heads=heads)
902 if not cg:
902 if not cg:
903 ui.status(_("no changes found\n"))
903 ui.status(_("no changes found\n"))
904 return 1
904 return 1
905
905
906 bundletype = opts.get('type', 'bzip2').lower()
906 bundletype = opts.get('type', 'bzip2').lower()
907 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
907 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
908 bundletype = btypes.get(bundletype)
908 bundletype = btypes.get(bundletype)
909 if bundletype not in changegroup.bundletypes:
909 if bundletype not in changegroup.bundletypes:
910 raise util.Abort(_('unknown bundle type specified with --type'))
910 raise util.Abort(_('unknown bundle type specified with --type'))
911
911
912 changegroup.writebundle(cg, fname, bundletype)
912 changegroup.writebundle(cg, fname, bundletype)
913
913
914 @command('cat',
914 @command('cat',
915 [('o', 'output', '',
915 [('o', 'output', '',
916 _('print output to file with formatted name'), _('FORMAT')),
916 _('print output to file with formatted name'), _('FORMAT')),
917 ('r', 'rev', '', _('print the given revision'), _('REV')),
917 ('r', 'rev', '', _('print the given revision'), _('REV')),
918 ('', 'decode', None, _('apply any matching decode filter')),
918 ('', 'decode', None, _('apply any matching decode filter')),
919 ] + walkopts,
919 ] + walkopts,
920 _('[OPTION]... FILE...'))
920 _('[OPTION]... FILE...'))
921 def cat(ui, repo, file1, *pats, **opts):
921 def cat(ui, repo, file1, *pats, **opts):
922 """output the current or given revision of files
922 """output the current or given revision of files
923
923
924 Print the specified files as they were at the given revision. If
924 Print the specified files as they were at the given revision. If
925 no revision is given, the parent of the working directory is used,
925 no revision is given, the parent of the working directory is used,
926 or tip if no revision is checked out.
926 or tip if no revision is checked out.
927
927
928 Output may be to a file, in which case the name of the file is
928 Output may be to a file, in which case the name of the file is
929 given using a format string. The formatting rules are the same as
929 given using a format string. The formatting rules are the same as
930 for the export command, with the following additions:
930 for the export command, with the following additions:
931
931
932 :``%s``: basename of file being printed
932 :``%s``: basename of file being printed
933 :``%d``: dirname of file being printed, or '.' if in repository root
933 :``%d``: dirname of file being printed, or '.' if in repository root
934 :``%p``: root-relative path name of file being printed
934 :``%p``: root-relative path name of file being printed
935
935
936 Returns 0 on success.
936 Returns 0 on success.
937 """
937 """
938 ctx = scmutil.revsingle(repo, opts.get('rev'))
938 ctx = scmutil.revsingle(repo, opts.get('rev'))
939 err = 1
939 err = 1
940 m = scmutil.match(repo, (file1,) + pats, opts)
940 m = scmutil.match(repo, (file1,) + pats, opts)
941 for abs in ctx.walk(m):
941 for abs in ctx.walk(m):
942 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
942 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
943 pathname=abs)
943 pathname=abs)
944 data = ctx[abs].data()
944 data = ctx[abs].data()
945 if opts.get('decode'):
945 if opts.get('decode'):
946 data = repo.wwritedata(abs, data)
946 data = repo.wwritedata(abs, data)
947 fp.write(data)
947 fp.write(data)
948 fp.close()
948 fp.close()
949 err = 0
949 err = 0
950 return err
950 return err
951
951
952 @command('^clone',
952 @command('^clone',
953 [('U', 'noupdate', None,
953 [('U', 'noupdate', None,
954 _('the clone will include an empty working copy (only a repository)')),
954 _('the clone will include an empty working copy (only a repository)')),
955 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
955 ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')),
956 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
956 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
957 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
957 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
958 ('', 'pull', None, _('use pull protocol to copy metadata')),
958 ('', 'pull', None, _('use pull protocol to copy metadata')),
959 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
959 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
960 ] + remoteopts,
960 ] + remoteopts,
961 _('[OPTION]... SOURCE [DEST]'))
961 _('[OPTION]... SOURCE [DEST]'))
962 def clone(ui, source, dest=None, **opts):
962 def clone(ui, source, dest=None, **opts):
963 """make a copy of an existing repository
963 """make a copy of an existing repository
964
964
965 Create a copy of an existing repository in a new directory.
965 Create a copy of an existing repository in a new directory.
966
966
967 If no destination directory name is specified, it defaults to the
967 If no destination directory name is specified, it defaults to the
968 basename of the source.
968 basename of the source.
969
969
970 The location of the source is added to the new repository's
970 The location of the source is added to the new repository's
971 ``.hg/hgrc`` file, as the default to be used for future pulls.
971 ``.hg/hgrc`` file, as the default to be used for future pulls.
972
972
973 See :hg:`help urls` for valid source format details.
973 See :hg:`help urls` for valid source format details.
974
974
975 It is possible to specify an ``ssh://`` URL as the destination, but no
975 It is possible to specify an ``ssh://`` URL as the destination, but no
976 ``.hg/hgrc`` and working directory will be created on the remote side.
976 ``.hg/hgrc`` and working directory will be created on the remote side.
977 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
977 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
978
978
979 A set of changesets (tags, or branch names) to pull may be specified
979 A set of changesets (tags, or branch names) to pull may be specified
980 by listing each changeset (tag, or branch name) with -r/--rev.
980 by listing each changeset (tag, or branch name) with -r/--rev.
981 If -r/--rev is used, the cloned repository will contain only a subset
981 If -r/--rev is used, the cloned repository will contain only a subset
982 of the changesets of the source repository. Only the set of changesets
982 of the changesets of the source repository. Only the set of changesets
983 defined by all -r/--rev options (including all their ancestors)
983 defined by all -r/--rev options (including all their ancestors)
984 will be pulled into the destination repository.
984 will be pulled into the destination repository.
985 No subsequent changesets (including subsequent tags) will be present
985 No subsequent changesets (including subsequent tags) will be present
986 in the destination.
986 in the destination.
987
987
988 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
988 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
989 local source repositories.
989 local source repositories.
990
990
991 For efficiency, hardlinks are used for cloning whenever the source
991 For efficiency, hardlinks are used for cloning whenever the source
992 and destination are on the same filesystem (note this applies only
992 and destination are on the same filesystem (note this applies only
993 to the repository data, not to the working directory). Some
993 to the repository data, not to the working directory). Some
994 filesystems, such as AFS, implement hardlinking incorrectly, but
994 filesystems, such as AFS, implement hardlinking incorrectly, but
995 do not report errors. In these cases, use the --pull option to
995 do not report errors. In these cases, use the --pull option to
996 avoid hardlinking.
996 avoid hardlinking.
997
997
998 In some cases, you can clone repositories and the working directory
998 In some cases, you can clone repositories and the working directory
999 using full hardlinks with ::
999 using full hardlinks with ::
1000
1000
1001 $ cp -al REPO REPOCLONE
1001 $ cp -al REPO REPOCLONE
1002
1002
1003 This is the fastest way to clone, but it is not always safe. The
1003 This is the fastest way to clone, but it is not always safe. The
1004 operation is not atomic (making sure REPO is not modified during
1004 operation is not atomic (making sure REPO is not modified during
1005 the operation is up to you) and you have to make sure your editor
1005 the operation is up to you) and you have to make sure your editor
1006 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
1006 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
1007 this is not compatible with certain extensions that place their
1007 this is not compatible with certain extensions that place their
1008 metadata under the .hg directory, such as mq.
1008 metadata under the .hg directory, such as mq.
1009
1009
1010 Mercurial will update the working directory to the first applicable
1010 Mercurial will update the working directory to the first applicable
1011 revision from this list:
1011 revision from this list:
1012
1012
1013 a) null if -U or the source repository has no changesets
1013 a) null if -U or the source repository has no changesets
1014 b) if -u . and the source repository is local, the first parent of
1014 b) if -u . and the source repository is local, the first parent of
1015 the source repository's working directory
1015 the source repository's working directory
1016 c) the changeset specified with -u (if a branch name, this means the
1016 c) the changeset specified with -u (if a branch name, this means the
1017 latest head of that branch)
1017 latest head of that branch)
1018 d) the changeset specified with -r
1018 d) the changeset specified with -r
1019 e) the tipmost head specified with -b
1019 e) the tipmost head specified with -b
1020 f) the tipmost head specified with the url#branch source syntax
1020 f) the tipmost head specified with the url#branch source syntax
1021 g) the tipmost head of the default branch
1021 g) the tipmost head of the default branch
1022 h) tip
1022 h) tip
1023
1023
1024 Returns 0 on success.
1024 Returns 0 on success.
1025 """
1025 """
1026 if opts.get('noupdate') and opts.get('updaterev'):
1026 if opts.get('noupdate') and opts.get('updaterev'):
1027 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1027 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
1028
1028
1029 r = hg.clone(hg.remoteui(ui, opts), source, dest,
1029 r = hg.clone(hg.remoteui(ui, opts), source, dest,
1030 pull=opts.get('pull'),
1030 pull=opts.get('pull'),
1031 stream=opts.get('uncompressed'),
1031 stream=opts.get('uncompressed'),
1032 rev=opts.get('rev'),
1032 rev=opts.get('rev'),
1033 update=opts.get('updaterev') or not opts.get('noupdate'),
1033 update=opts.get('updaterev') or not opts.get('noupdate'),
1034 branch=opts.get('branch'))
1034 branch=opts.get('branch'))
1035
1035
1036 return r is None
1036 return r is None
1037
1037
1038 @command('^commit|ci',
1038 @command('^commit|ci',
1039 [('A', 'addremove', None,
1039 [('A', 'addremove', None,
1040 _('mark new/missing files as added/removed before committing')),
1040 _('mark new/missing files as added/removed before committing')),
1041 ('', 'close-branch', None,
1041 ('', 'close-branch', None,
1042 _('mark a branch as closed, hiding it from the branch list')),
1042 _('mark a branch as closed, hiding it from the branch list')),
1043 ] + walkopts + commitopts + commitopts2,
1043 ] + walkopts + commitopts + commitopts2,
1044 _('[OPTION]... [FILE]...'))
1044 _('[OPTION]... [FILE]...'))
1045 def commit(ui, repo, *pats, **opts):
1045 def commit(ui, repo, *pats, **opts):
1046 """commit the specified files or all outstanding changes
1046 """commit the specified files or all outstanding changes
1047
1047
1048 Commit changes to the given files into the repository. Unlike a
1048 Commit changes to the given files into the repository. Unlike a
1049 centralized SCM, this operation is a local operation. See
1049 centralized SCM, this operation is a local operation. See
1050 :hg:`push` for a way to actively distribute your changes.
1050 :hg:`push` for a way to actively distribute your changes.
1051
1051
1052 If a list of files is omitted, all changes reported by :hg:`status`
1052 If a list of files is omitted, all changes reported by :hg:`status`
1053 will be committed.
1053 will be committed.
1054
1054
1055 If you are committing the result of a merge, do not provide any
1055 If you are committing the result of a merge, do not provide any
1056 filenames or -I/-X filters.
1056 filenames or -I/-X filters.
1057
1057
1058 If no commit message is specified, Mercurial starts your
1058 If no commit message is specified, Mercurial starts your
1059 configured editor where you can enter a message. In case your
1059 configured editor where you can enter a message. In case your
1060 commit fails, you will find a backup of your message in
1060 commit fails, you will find a backup of your message in
1061 ``.hg/last-message.txt``.
1061 ``.hg/last-message.txt``.
1062
1062
1063 See :hg:`help dates` for a list of formats valid for -d/--date.
1063 See :hg:`help dates` for a list of formats valid for -d/--date.
1064
1064
1065 Returns 0 on success, 1 if nothing changed.
1065 Returns 0 on success, 1 if nothing changed.
1066 """
1066 """
1067 extra = {}
1067 extra = {}
1068 if opts.get('close_branch'):
1068 if opts.get('close_branch'):
1069 if repo['.'].node() not in repo.branchheads():
1069 if repo['.'].node() not in repo.branchheads():
1070 # The topo heads set is included in the branch heads set of the
1070 # The topo heads set is included in the branch heads set of the
1071 # current branch, so it's sufficient to test branchheads
1071 # current branch, so it's sufficient to test branchheads
1072 raise util.Abort(_('can only close branch heads'))
1072 raise util.Abort(_('can only close branch heads'))
1073 extra['close'] = 1
1073 extra['close'] = 1
1074 e = cmdutil.commiteditor
1074 e = cmdutil.commiteditor
1075 if opts.get('force_editor'):
1075 if opts.get('force_editor'):
1076 e = cmdutil.commitforceeditor
1076 e = cmdutil.commitforceeditor
1077
1077
1078 def commitfunc(ui, repo, message, match, opts):
1078 def commitfunc(ui, repo, message, match, opts):
1079 return repo.commit(message, opts.get('user'), opts.get('date'), match,
1079 return repo.commit(message, opts.get('user'), opts.get('date'), match,
1080 editor=e, extra=extra)
1080 editor=e, extra=extra)
1081
1081
1082 branch = repo[None].branch()
1082 branch = repo[None].branch()
1083 bheads = repo.branchheads(branch)
1083 bheads = repo.branchheads(branch)
1084
1084
1085 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1085 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1086 if not node:
1086 if not node:
1087 stat = repo.status(match=scmutil.match(repo, pats, opts))
1087 stat = repo.status(match=scmutil.match(repo, pats, opts))
1088 if stat[3]:
1088 if stat[3]:
1089 ui.status(_("nothing changed (%d missing files, see 'hg status')\n")
1089 ui.status(_("nothing changed (%d missing files, see 'hg status')\n")
1090 % len(stat[3]))
1090 % len(stat[3]))
1091 else:
1091 else:
1092 ui.status(_("nothing changed\n"))
1092 ui.status(_("nothing changed\n"))
1093 return 1
1093 return 1
1094
1094
1095 ctx = repo[node]
1095 ctx = repo[node]
1096 parents = ctx.parents()
1096 parents = ctx.parents()
1097
1097
1098 if bheads and not [x for x in parents
1098 if bheads and not [x for x in parents
1099 if x.node() in bheads and x.branch() == branch]:
1099 if x.node() in bheads and x.branch() == branch]:
1100 ui.status(_('created new head\n'))
1100 ui.status(_('created new head\n'))
1101 # The message is not printed for initial roots. For the other
1101 # The message is not printed for initial roots. For the other
1102 # changesets, it is printed in the following situations:
1102 # changesets, it is printed in the following situations:
1103 #
1103 #
1104 # Par column: for the 2 parents with ...
1104 # Par column: for the 2 parents with ...
1105 # N: null or no parent
1105 # N: null or no parent
1106 # B: parent is on another named branch
1106 # B: parent is on another named branch
1107 # C: parent is a regular non head changeset
1107 # C: parent is a regular non head changeset
1108 # H: parent was a branch head of the current branch
1108 # H: parent was a branch head of the current branch
1109 # Msg column: whether we print "created new head" message
1109 # Msg column: whether we print "created new head" message
1110 # In the following, it is assumed that there already exists some
1110 # In the following, it is assumed that there already exists some
1111 # initial branch heads of the current branch, otherwise nothing is
1111 # initial branch heads of the current branch, otherwise nothing is
1112 # printed anyway.
1112 # printed anyway.
1113 #
1113 #
1114 # Par Msg Comment
1114 # Par Msg Comment
1115 # NN y additional topo root
1115 # NN y additional topo root
1116 #
1116 #
1117 # BN y additional branch root
1117 # BN y additional branch root
1118 # CN y additional topo head
1118 # CN y additional topo head
1119 # HN n usual case
1119 # HN n usual case
1120 #
1120 #
1121 # BB y weird additional branch root
1121 # BB y weird additional branch root
1122 # CB y branch merge
1122 # CB y branch merge
1123 # HB n merge with named branch
1123 # HB n merge with named branch
1124 #
1124 #
1125 # CC y additional head from merge
1125 # CC y additional head from merge
1126 # CH n merge with a head
1126 # CH n merge with a head
1127 #
1127 #
1128 # HH n head merge: head count decreases
1128 # HH n head merge: head count decreases
1129
1129
1130 if not opts.get('close_branch'):
1130 if not opts.get('close_branch'):
1131 for r in parents:
1131 for r in parents:
1132 if r.extra().get('close') and r.branch() == branch:
1132 if r.extra().get('close') and r.branch() == branch:
1133 ui.status(_('reopening closed branch head %d\n') % r)
1133 ui.status(_('reopening closed branch head %d\n') % r)
1134
1134
1135 if ui.debugflag:
1135 if ui.debugflag:
1136 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1136 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
1137 elif ui.verbose:
1137 elif ui.verbose:
1138 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1138 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
1139
1139
1140 @command('copy|cp',
1140 @command('copy|cp',
1141 [('A', 'after', None, _('record a copy that has already occurred')),
1141 [('A', 'after', None, _('record a copy that has already occurred')),
1142 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1142 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1143 ] + walkopts + dryrunopts,
1143 ] + walkopts + dryrunopts,
1144 _('[OPTION]... [SOURCE]... DEST'))
1144 _('[OPTION]... [SOURCE]... DEST'))
1145 def copy(ui, repo, *pats, **opts):
1145 def copy(ui, repo, *pats, **opts):
1146 """mark files as copied for the next commit
1146 """mark files as copied for the next commit
1147
1147
1148 Mark dest as having copies of source files. If dest is a
1148 Mark dest as having copies of source files. If dest is a
1149 directory, copies are put in that directory. If dest is a file,
1149 directory, copies are put in that directory. If dest is a file,
1150 the source must be a single file.
1150 the source must be a single file.
1151
1151
1152 By default, this command copies the contents of files as they
1152 By default, this command copies the contents of files as they
1153 exist in the working directory. If invoked with -A/--after, the
1153 exist in the working directory. If invoked with -A/--after, the
1154 operation is recorded, but no copying is performed.
1154 operation is recorded, but no copying is performed.
1155
1155
1156 This command takes effect with the next commit. To undo a copy
1156 This command takes effect with the next commit. To undo a copy
1157 before that, see :hg:`revert`.
1157 before that, see :hg:`revert`.
1158
1158
1159 Returns 0 on success, 1 if errors are encountered.
1159 Returns 0 on success, 1 if errors are encountered.
1160 """
1160 """
1161 wlock = repo.wlock(False)
1161 wlock = repo.wlock(False)
1162 try:
1162 try:
1163 return cmdutil.copy(ui, repo, pats, opts)
1163 return cmdutil.copy(ui, repo, pats, opts)
1164 finally:
1164 finally:
1165 wlock.release()
1165 wlock.release()
1166
1166
1167 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1167 @command('debugancestor', [], _('[INDEX] REV1 REV2'))
1168 def debugancestor(ui, repo, *args):
1168 def debugancestor(ui, repo, *args):
1169 """find the ancestor revision of two revisions in a given index"""
1169 """find the ancestor revision of two revisions in a given index"""
1170 if len(args) == 3:
1170 if len(args) == 3:
1171 index, rev1, rev2 = args
1171 index, rev1, rev2 = args
1172 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1172 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index)
1173 lookup = r.lookup
1173 lookup = r.lookup
1174 elif len(args) == 2:
1174 elif len(args) == 2:
1175 if not repo:
1175 if not repo:
1176 raise util.Abort(_("there is no Mercurial repository here "
1176 raise util.Abort(_("there is no Mercurial repository here "
1177 "(.hg not found)"))
1177 "(.hg not found)"))
1178 rev1, rev2 = args
1178 rev1, rev2 = args
1179 r = repo.changelog
1179 r = repo.changelog
1180 lookup = repo.lookup
1180 lookup = repo.lookup
1181 else:
1181 else:
1182 raise util.Abort(_('either two or three arguments required'))
1182 raise util.Abort(_('either two or three arguments required'))
1183 a = r.ancestor(lookup(rev1), lookup(rev2))
1183 a = r.ancestor(lookup(rev1), lookup(rev2))
1184 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1184 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1185
1185
1186 @command('debugbuilddag',
1186 @command('debugbuilddag',
1187 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1187 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
1188 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1188 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
1189 ('n', 'new-file', None, _('add new file at each rev'))],
1189 ('n', 'new-file', None, _('add new file at each rev'))],
1190 _('[OPTION]... [TEXT]'))
1190 _('[OPTION]... [TEXT]'))
1191 def debugbuilddag(ui, repo, text=None,
1191 def debugbuilddag(ui, repo, text=None,
1192 mergeable_file=False,
1192 mergeable_file=False,
1193 overwritten_file=False,
1193 overwritten_file=False,
1194 new_file=False):
1194 new_file=False):
1195 """builds a repo with a given DAG from scratch in the current empty repo
1195 """builds a repo with a given DAG from scratch in the current empty repo
1196
1196
1197 The description of the DAG is read from stdin if not given on the
1197 The description of the DAG is read from stdin if not given on the
1198 command line.
1198 command line.
1199
1199
1200 Elements:
1200 Elements:
1201
1201
1202 - "+n" is a linear run of n nodes based on the current default parent
1202 - "+n" is a linear run of n nodes based on the current default parent
1203 - "." is a single node based on the current default parent
1203 - "." is a single node based on the current default parent
1204 - "$" resets the default parent to null (implied at the start);
1204 - "$" resets the default parent to null (implied at the start);
1205 otherwise the default parent is always the last node created
1205 otherwise the default parent is always the last node created
1206 - "<p" sets the default parent to the backref p
1206 - "<p" sets the default parent to the backref p
1207 - "*p" is a fork at parent p, which is a backref
1207 - "*p" is a fork at parent p, which is a backref
1208 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1208 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
1209 - "/p2" is a merge of the preceding node and p2
1209 - "/p2" is a merge of the preceding node and p2
1210 - ":tag" defines a local tag for the preceding node
1210 - ":tag" defines a local tag for the preceding node
1211 - "@branch" sets the named branch for subsequent nodes
1211 - "@branch" sets the named branch for subsequent nodes
1212 - "#...\\n" is a comment up to the end of the line
1212 - "#...\\n" is a comment up to the end of the line
1213
1213
1214 Whitespace between the above elements is ignored.
1214 Whitespace between the above elements is ignored.
1215
1215
1216 A backref is either
1216 A backref is either
1217
1217
1218 - a number n, which references the node curr-n, where curr is the current
1218 - a number n, which references the node curr-n, where curr is the current
1219 node, or
1219 node, or
1220 - the name of a local tag you placed earlier using ":tag", or
1220 - the name of a local tag you placed earlier using ":tag", or
1221 - empty to denote the default parent.
1221 - empty to denote the default parent.
1222
1222
1223 All string valued-elements are either strictly alphanumeric, or must
1223 All string valued-elements are either strictly alphanumeric, or must
1224 be enclosed in double quotes ("..."), with "\\" as escape character.
1224 be enclosed in double quotes ("..."), with "\\" as escape character.
1225 """
1225 """
1226
1226
1227 if text is None:
1227 if text is None:
1228 ui.status(_("reading DAG from stdin\n"))
1228 ui.status(_("reading DAG from stdin\n"))
1229 text = sys.stdin.read()
1229 text = sys.stdin.read()
1230
1230
1231 cl = repo.changelog
1231 cl = repo.changelog
1232 if len(cl) > 0:
1232 if len(cl) > 0:
1233 raise util.Abort(_('repository is not empty'))
1233 raise util.Abort(_('repository is not empty'))
1234
1234
1235 # determine number of revs in DAG
1235 # determine number of revs in DAG
1236 total = 0
1236 total = 0
1237 for type, data in dagparser.parsedag(text):
1237 for type, data in dagparser.parsedag(text):
1238 if type == 'n':
1238 if type == 'n':
1239 total += 1
1239 total += 1
1240
1240
1241 if mergeable_file:
1241 if mergeable_file:
1242 linesperrev = 2
1242 linesperrev = 2
1243 # make a file with k lines per rev
1243 # make a file with k lines per rev
1244 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1244 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
1245 initialmergedlines.append("")
1245 initialmergedlines.append("")
1246
1246
1247 tags = []
1247 tags = []
1248
1248
1249 tr = repo.transaction("builddag")
1249 tr = repo.transaction("builddag")
1250 try:
1250 try:
1251
1251
1252 at = -1
1252 at = -1
1253 atbranch = 'default'
1253 atbranch = 'default'
1254 nodeids = []
1254 nodeids = []
1255 ui.progress(_('building'), 0, unit=_('revisions'), total=total)
1255 ui.progress(_('building'), 0, unit=_('revisions'), total=total)
1256 for type, data in dagparser.parsedag(text):
1256 for type, data in dagparser.parsedag(text):
1257 if type == 'n':
1257 if type == 'n':
1258 ui.note('node %s\n' % str(data))
1258 ui.note('node %s\n' % str(data))
1259 id, ps = data
1259 id, ps = data
1260
1260
1261 files = []
1261 files = []
1262 fctxs = {}
1262 fctxs = {}
1263
1263
1264 p2 = None
1264 p2 = None
1265 if mergeable_file:
1265 if mergeable_file:
1266 fn = "mf"
1266 fn = "mf"
1267 p1 = repo[ps[0]]
1267 p1 = repo[ps[0]]
1268 if len(ps) > 1:
1268 if len(ps) > 1:
1269 p2 = repo[ps[1]]
1269 p2 = repo[ps[1]]
1270 pa = p1.ancestor(p2)
1270 pa = p1.ancestor(p2)
1271 base, local, other = [x[fn].data() for x in pa, p1, p2]
1271 base, local, other = [x[fn].data() for x in pa, p1, p2]
1272 m3 = simplemerge.Merge3Text(base, local, other)
1272 m3 = simplemerge.Merge3Text(base, local, other)
1273 ml = [l.strip() for l in m3.merge_lines()]
1273 ml = [l.strip() for l in m3.merge_lines()]
1274 ml.append("")
1274 ml.append("")
1275 elif at > 0:
1275 elif at > 0:
1276 ml = p1[fn].data().split("\n")
1276 ml = p1[fn].data().split("\n")
1277 else:
1277 else:
1278 ml = initialmergedlines
1278 ml = initialmergedlines
1279 ml[id * linesperrev] += " r%i" % id
1279 ml[id * linesperrev] += " r%i" % id
1280 mergedtext = "\n".join(ml)
1280 mergedtext = "\n".join(ml)
1281 files.append(fn)
1281 files.append(fn)
1282 fctxs[fn] = context.memfilectx(fn, mergedtext)
1282 fctxs[fn] = context.memfilectx(fn, mergedtext)
1283
1283
1284 if overwritten_file:
1284 if overwritten_file:
1285 fn = "of"
1285 fn = "of"
1286 files.append(fn)
1286 files.append(fn)
1287 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1287 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1288
1288
1289 if new_file:
1289 if new_file:
1290 fn = "nf%i" % id
1290 fn = "nf%i" % id
1291 files.append(fn)
1291 files.append(fn)
1292 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1292 fctxs[fn] = context.memfilectx(fn, "r%i\n" % id)
1293 if len(ps) > 1:
1293 if len(ps) > 1:
1294 if not p2:
1294 if not p2:
1295 p2 = repo[ps[1]]
1295 p2 = repo[ps[1]]
1296 for fn in p2:
1296 for fn in p2:
1297 if fn.startswith("nf"):
1297 if fn.startswith("nf"):
1298 files.append(fn)
1298 files.append(fn)
1299 fctxs[fn] = p2[fn]
1299 fctxs[fn] = p2[fn]
1300
1300
1301 def fctxfn(repo, cx, path):
1301 def fctxfn(repo, cx, path):
1302 return fctxs.get(path)
1302 return fctxs.get(path)
1303
1303
1304 if len(ps) == 0 or ps[0] < 0:
1304 if len(ps) == 0 or ps[0] < 0:
1305 pars = [None, None]
1305 pars = [None, None]
1306 elif len(ps) == 1:
1306 elif len(ps) == 1:
1307 pars = [nodeids[ps[0]], None]
1307 pars = [nodeids[ps[0]], None]
1308 else:
1308 else:
1309 pars = [nodeids[p] for p in ps]
1309 pars = [nodeids[p] for p in ps]
1310 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1310 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
1311 date=(id, 0),
1311 date=(id, 0),
1312 user="debugbuilddag",
1312 user="debugbuilddag",
1313 extra={'branch': atbranch})
1313 extra={'branch': atbranch})
1314 nodeid = repo.commitctx(cx)
1314 nodeid = repo.commitctx(cx)
1315 nodeids.append(nodeid)
1315 nodeids.append(nodeid)
1316 at = id
1316 at = id
1317 elif type == 'l':
1317 elif type == 'l':
1318 id, name = data
1318 id, name = data
1319 ui.note('tag %s\n' % name)
1319 ui.note('tag %s\n' % name)
1320 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1320 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
1321 elif type == 'a':
1321 elif type == 'a':
1322 ui.note('branch %s\n' % data)
1322 ui.note('branch %s\n' % data)
1323 atbranch = data
1323 atbranch = data
1324 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1324 ui.progress(_('building'), id, unit=_('revisions'), total=total)
1325 tr.close()
1325 tr.close()
1326 finally:
1326 finally:
1327 ui.progress(_('building'), None)
1327 ui.progress(_('building'), None)
1328 tr.release()
1328 tr.release()
1329
1329
1330 if tags:
1330 if tags:
1331 repo.opener.write("localtags", "".join(tags))
1331 repo.opener.write("localtags", "".join(tags))
1332
1332
1333 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1333 @command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE'))
1334 def debugbundle(ui, bundlepath, all=None, **opts):
1334 def debugbundle(ui, bundlepath, all=None, **opts):
1335 """lists the contents of a bundle"""
1335 """lists the contents of a bundle"""
1336 f = url.open(ui, bundlepath)
1336 f = url.open(ui, bundlepath)
1337 try:
1337 try:
1338 gen = changegroup.readbundle(f, bundlepath)
1338 gen = changegroup.readbundle(f, bundlepath)
1339 if all:
1339 if all:
1340 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1340 ui.write("format: id, p1, p2, cset, delta base, len(delta)\n")
1341
1341
1342 def showchunks(named):
1342 def showchunks(named):
1343 ui.write("\n%s\n" % named)
1343 ui.write("\n%s\n" % named)
1344 chain = None
1344 chain = None
1345 while 1:
1345 while 1:
1346 chunkdata = gen.deltachunk(chain)
1346 chunkdata = gen.deltachunk(chain)
1347 if not chunkdata:
1347 if not chunkdata:
1348 break
1348 break
1349 node = chunkdata['node']
1349 node = chunkdata['node']
1350 p1 = chunkdata['p1']
1350 p1 = chunkdata['p1']
1351 p2 = chunkdata['p2']
1351 p2 = chunkdata['p2']
1352 cs = chunkdata['cs']
1352 cs = chunkdata['cs']
1353 deltabase = chunkdata['deltabase']
1353 deltabase = chunkdata['deltabase']
1354 delta = chunkdata['delta']
1354 delta = chunkdata['delta']
1355 ui.write("%s %s %s %s %s %s\n" %
1355 ui.write("%s %s %s %s %s %s\n" %
1356 (hex(node), hex(p1), hex(p2),
1356 (hex(node), hex(p1), hex(p2),
1357 hex(cs), hex(deltabase), len(delta)))
1357 hex(cs), hex(deltabase), len(delta)))
1358 chain = node
1358 chain = node
1359
1359
1360 chunkdata = gen.changelogheader()
1360 chunkdata = gen.changelogheader()
1361 showchunks("changelog")
1361 showchunks("changelog")
1362 chunkdata = gen.manifestheader()
1362 chunkdata = gen.manifestheader()
1363 showchunks("manifest")
1363 showchunks("manifest")
1364 while 1:
1364 while 1:
1365 chunkdata = gen.filelogheader()
1365 chunkdata = gen.filelogheader()
1366 if not chunkdata:
1366 if not chunkdata:
1367 break
1367 break
1368 fname = chunkdata['filename']
1368 fname = chunkdata['filename']
1369 showchunks(fname)
1369 showchunks(fname)
1370 else:
1370 else:
1371 chunkdata = gen.changelogheader()
1371 chunkdata = gen.changelogheader()
1372 chain = None
1372 chain = None
1373 while 1:
1373 while 1:
1374 chunkdata = gen.deltachunk(chain)
1374 chunkdata = gen.deltachunk(chain)
1375 if not chunkdata:
1375 if not chunkdata:
1376 break
1376 break
1377 node = chunkdata['node']
1377 node = chunkdata['node']
1378 ui.write("%s\n" % hex(node))
1378 ui.write("%s\n" % hex(node))
1379 chain = node
1379 chain = node
1380 finally:
1380 finally:
1381 f.close()
1381 f.close()
1382
1382
1383 @command('debugcheckstate', [], '')
1383 @command('debugcheckstate', [], '')
1384 def debugcheckstate(ui, repo):
1384 def debugcheckstate(ui, repo):
1385 """validate the correctness of the current dirstate"""
1385 """validate the correctness of the current dirstate"""
1386 parent1, parent2 = repo.dirstate.parents()
1386 parent1, parent2 = repo.dirstate.parents()
1387 m1 = repo[parent1].manifest()
1387 m1 = repo[parent1].manifest()
1388 m2 = repo[parent2].manifest()
1388 m2 = repo[parent2].manifest()
1389 errors = 0
1389 errors = 0
1390 for f in repo.dirstate:
1390 for f in repo.dirstate:
1391 state = repo.dirstate[f]
1391 state = repo.dirstate[f]
1392 if state in "nr" and f not in m1:
1392 if state in "nr" and f not in m1:
1393 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1393 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1394 errors += 1
1394 errors += 1
1395 if state in "a" and f in m1:
1395 if state in "a" and f in m1:
1396 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1396 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1397 errors += 1
1397 errors += 1
1398 if state in "m" and f not in m1 and f not in m2:
1398 if state in "m" and f not in m1 and f not in m2:
1399 ui.warn(_("%s in state %s, but not in either manifest\n") %
1399 ui.warn(_("%s in state %s, but not in either manifest\n") %
1400 (f, state))
1400 (f, state))
1401 errors += 1
1401 errors += 1
1402 for f in m1:
1402 for f in m1:
1403 state = repo.dirstate[f]
1403 state = repo.dirstate[f]
1404 if state not in "nrm":
1404 if state not in "nrm":
1405 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1405 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1406 errors += 1
1406 errors += 1
1407 if errors:
1407 if errors:
1408 error = _(".hg/dirstate inconsistent with current parent's manifest")
1408 error = _(".hg/dirstate inconsistent with current parent's manifest")
1409 raise util.Abort(error)
1409 raise util.Abort(error)
1410
1410
1411 @command('debugcommands', [], _('[COMMAND]'))
1411 @command('debugcommands', [], _('[COMMAND]'))
1412 def debugcommands(ui, cmd='', *args):
1412 def debugcommands(ui, cmd='', *args):
1413 """list all available commands and options"""
1413 """list all available commands and options"""
1414 for cmd, vals in sorted(table.iteritems()):
1414 for cmd, vals in sorted(table.iteritems()):
1415 cmd = cmd.split('|')[0].strip('^')
1415 cmd = cmd.split('|')[0].strip('^')
1416 opts = ', '.join([i[1] for i in vals[1]])
1416 opts = ', '.join([i[1] for i in vals[1]])
1417 ui.write('%s: %s\n' % (cmd, opts))
1417 ui.write('%s: %s\n' % (cmd, opts))
1418
1418
1419 @command('debugcomplete',
1419 @command('debugcomplete',
1420 [('o', 'options', None, _('show the command options'))],
1420 [('o', 'options', None, _('show the command options'))],
1421 _('[-o] CMD'))
1421 _('[-o] CMD'))
1422 def debugcomplete(ui, cmd='', **opts):
1422 def debugcomplete(ui, cmd='', **opts):
1423 """returns the completion list associated with the given command"""
1423 """returns the completion list associated with the given command"""
1424
1424
1425 if opts.get('options'):
1425 if opts.get('options'):
1426 options = []
1426 options = []
1427 otables = [globalopts]
1427 otables = [globalopts]
1428 if cmd:
1428 if cmd:
1429 aliases, entry = cmdutil.findcmd(cmd, table, False)
1429 aliases, entry = cmdutil.findcmd(cmd, table, False)
1430 otables.append(entry[1])
1430 otables.append(entry[1])
1431 for t in otables:
1431 for t in otables:
1432 for o in t:
1432 for o in t:
1433 if "(DEPRECATED)" in o[3]:
1433 if "(DEPRECATED)" in o[3]:
1434 continue
1434 continue
1435 if o[0]:
1435 if o[0]:
1436 options.append('-%s' % o[0])
1436 options.append('-%s' % o[0])
1437 options.append('--%s' % o[1])
1437 options.append('--%s' % o[1])
1438 ui.write("%s\n" % "\n".join(options))
1438 ui.write("%s\n" % "\n".join(options))
1439 return
1439 return
1440
1440
1441 cmdlist = cmdutil.findpossible(cmd, table)
1441 cmdlist = cmdutil.findpossible(cmd, table)
1442 if ui.verbose:
1442 if ui.verbose:
1443 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1443 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1444 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1444 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1445
1445
1446 @command('debugdag',
1446 @command('debugdag',
1447 [('t', 'tags', None, _('use tags as labels')),
1447 [('t', 'tags', None, _('use tags as labels')),
1448 ('b', 'branches', None, _('annotate with branch names')),
1448 ('b', 'branches', None, _('annotate with branch names')),
1449 ('', 'dots', None, _('use dots for runs')),
1449 ('', 'dots', None, _('use dots for runs')),
1450 ('s', 'spaces', None, _('separate elements by spaces'))],
1450 ('s', 'spaces', None, _('separate elements by spaces'))],
1451 _('[OPTION]... [FILE [REV]...]'))
1451 _('[OPTION]... [FILE [REV]...]'))
1452 def debugdag(ui, repo, file_=None, *revs, **opts):
1452 def debugdag(ui, repo, file_=None, *revs, **opts):
1453 """format the changelog or an index DAG as a concise textual description
1453 """format the changelog or an index DAG as a concise textual description
1454
1454
1455 If you pass a revlog index, the revlog's DAG is emitted. If you list
1455 If you pass a revlog index, the revlog's DAG is emitted. If you list
1456 revision numbers, they get labelled in the output as rN.
1456 revision numbers, they get labelled in the output as rN.
1457
1457
1458 Otherwise, the changelog DAG of the current repo is emitted.
1458 Otherwise, the changelog DAG of the current repo is emitted.
1459 """
1459 """
1460 spaces = opts.get('spaces')
1460 spaces = opts.get('spaces')
1461 dots = opts.get('dots')
1461 dots = opts.get('dots')
1462 if file_:
1462 if file_:
1463 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1463 rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1464 revs = set((int(r) for r in revs))
1464 revs = set((int(r) for r in revs))
1465 def events():
1465 def events():
1466 for r in rlog:
1466 for r in rlog:
1467 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1467 yield 'n', (r, list(set(p for p in rlog.parentrevs(r) if p != -1)))
1468 if r in revs:
1468 if r in revs:
1469 yield 'l', (r, "r%i" % r)
1469 yield 'l', (r, "r%i" % r)
1470 elif repo:
1470 elif repo:
1471 cl = repo.changelog
1471 cl = repo.changelog
1472 tags = opts.get('tags')
1472 tags = opts.get('tags')
1473 branches = opts.get('branches')
1473 branches = opts.get('branches')
1474 if tags:
1474 if tags:
1475 labels = {}
1475 labels = {}
1476 for l, n in repo.tags().items():
1476 for l, n in repo.tags().items():
1477 labels.setdefault(cl.rev(n), []).append(l)
1477 labels.setdefault(cl.rev(n), []).append(l)
1478 def events():
1478 def events():
1479 b = "default"
1479 b = "default"
1480 for r in cl:
1480 for r in cl:
1481 if branches:
1481 if branches:
1482 newb = cl.read(cl.node(r))[5]['branch']
1482 newb = cl.read(cl.node(r))[5]['branch']
1483 if newb != b:
1483 if newb != b:
1484 yield 'a', newb
1484 yield 'a', newb
1485 b = newb
1485 b = newb
1486 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1486 yield 'n', (r, list(set(p for p in cl.parentrevs(r) if p != -1)))
1487 if tags:
1487 if tags:
1488 ls = labels.get(r)
1488 ls = labels.get(r)
1489 if ls:
1489 if ls:
1490 for l in ls:
1490 for l in ls:
1491 yield 'l', (r, l)
1491 yield 'l', (r, l)
1492 else:
1492 else:
1493 raise util.Abort(_('need repo for changelog dag'))
1493 raise util.Abort(_('need repo for changelog dag'))
1494
1494
1495 for line in dagparser.dagtextlines(events(),
1495 for line in dagparser.dagtextlines(events(),
1496 addspaces=spaces,
1496 addspaces=spaces,
1497 wraplabels=True,
1497 wraplabels=True,
1498 wrapannotations=True,
1498 wrapannotations=True,
1499 wrapnonlinear=dots,
1499 wrapnonlinear=dots,
1500 usedots=dots,
1500 usedots=dots,
1501 maxlinewidth=70):
1501 maxlinewidth=70):
1502 ui.write(line)
1502 ui.write(line)
1503 ui.write("\n")
1503 ui.write("\n")
1504
1504
1505 @command('debugdata',
1505 @command('debugdata',
1506 [('c', 'changelog', False, _('open changelog')),
1506 [('c', 'changelog', False, _('open changelog')),
1507 ('m', 'manifest', False, _('open manifest'))],
1507 ('m', 'manifest', False, _('open manifest'))],
1508 _('-c|-m|FILE REV'))
1508 _('-c|-m|FILE REV'))
1509 def debugdata(ui, repo, file_, rev = None, **opts):
1509 def debugdata(ui, repo, file_, rev = None, **opts):
1510 """dump the contents of a data file revision"""
1510 """dump the contents of a data file revision"""
1511 if opts.get('changelog') or opts.get('manifest'):
1511 if opts.get('changelog') or opts.get('manifest'):
1512 file_, rev = None, file_
1512 file_, rev = None, file_
1513 elif rev is None:
1513 elif rev is None:
1514 raise error.CommandError('debugdata', _('invalid arguments'))
1514 raise error.CommandError('debugdata', _('invalid arguments'))
1515 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1515 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
1516 try:
1516 try:
1517 ui.write(r.revision(r.lookup(rev)))
1517 ui.write(r.revision(r.lookup(rev)))
1518 except KeyError:
1518 except KeyError:
1519 raise util.Abort(_('invalid revision identifier %s') % rev)
1519 raise util.Abort(_('invalid revision identifier %s') % rev)
1520
1520
1521 @command('debugdate',
1521 @command('debugdate',
1522 [('e', 'extended', None, _('try extended date formats'))],
1522 [('e', 'extended', None, _('try extended date formats'))],
1523 _('[-e] DATE [RANGE]'))
1523 _('[-e] DATE [RANGE]'))
1524 def debugdate(ui, date, range=None, **opts):
1524 def debugdate(ui, date, range=None, **opts):
1525 """parse and display a date"""
1525 """parse and display a date"""
1526 if opts["extended"]:
1526 if opts["extended"]:
1527 d = util.parsedate(date, util.extendeddateformats)
1527 d = util.parsedate(date, util.extendeddateformats)
1528 else:
1528 else:
1529 d = util.parsedate(date)
1529 d = util.parsedate(date)
1530 ui.write("internal: %s %s\n" % d)
1530 ui.write("internal: %s %s\n" % d)
1531 ui.write("standard: %s\n" % util.datestr(d))
1531 ui.write("standard: %s\n" % util.datestr(d))
1532 if range:
1532 if range:
1533 m = util.matchdate(range)
1533 m = util.matchdate(range)
1534 ui.write("match: %s\n" % m(d[0]))
1534 ui.write("match: %s\n" % m(d[0]))
1535
1535
1536 @command('debugdiscovery',
1536 @command('debugdiscovery',
1537 [('', 'old', None, _('use old-style discovery')),
1537 [('', 'old', None, _('use old-style discovery')),
1538 ('', 'nonheads', None,
1538 ('', 'nonheads', None,
1539 _('use old-style discovery with non-heads included')),
1539 _('use old-style discovery with non-heads included')),
1540 ] + remoteopts,
1540 ] + remoteopts,
1541 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1541 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
1542 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1542 def debugdiscovery(ui, repo, remoteurl="default", **opts):
1543 """runs the changeset discovery protocol in isolation"""
1543 """runs the changeset discovery protocol in isolation"""
1544 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl), opts.get('branch'))
1544 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl), opts.get('branch'))
1545 remote = hg.repository(hg.remoteui(repo, opts), remoteurl)
1545 remote = hg.repository(hg.remoteui(repo, opts), remoteurl)
1546 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1546 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
1547
1547
1548 # make sure tests are repeatable
1548 # make sure tests are repeatable
1549 random.seed(12323)
1549 random.seed(12323)
1550
1550
1551 def doit(localheads, remoteheads):
1551 def doit(localheads, remoteheads):
1552 if opts.get('old'):
1552 if opts.get('old'):
1553 if localheads:
1553 if localheads:
1554 raise util.Abort('cannot use localheads with old style discovery')
1554 raise util.Abort('cannot use localheads with old style discovery')
1555 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1555 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
1556 force=True)
1556 force=True)
1557 common = set(common)
1557 common = set(common)
1558 if not opts.get('nonheads'):
1558 if not opts.get('nonheads'):
1559 ui.write("unpruned common: %s\n" % " ".join([short(n)
1559 ui.write("unpruned common: %s\n" % " ".join([short(n)
1560 for n in common]))
1560 for n in common]))
1561 dag = dagutil.revlogdag(repo.changelog)
1561 dag = dagutil.revlogdag(repo.changelog)
1562 all = dag.ancestorset(dag.internalizeall(common))
1562 all = dag.ancestorset(dag.internalizeall(common))
1563 common = dag.externalizeall(dag.headsetofconnecteds(all))
1563 common = dag.externalizeall(dag.headsetofconnecteds(all))
1564 else:
1564 else:
1565 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1565 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
1566 common = set(common)
1566 common = set(common)
1567 rheads = set(hds)
1567 rheads = set(hds)
1568 lheads = set(repo.heads())
1568 lheads = set(repo.heads())
1569 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1569 ui.write("common heads: %s\n" % " ".join([short(n) for n in common]))
1570 if lheads <= common:
1570 if lheads <= common:
1571 ui.write("local is subset\n")
1571 ui.write("local is subset\n")
1572 elif rheads <= common:
1572 elif rheads <= common:
1573 ui.write("remote is subset\n")
1573 ui.write("remote is subset\n")
1574
1574
1575 serverlogs = opts.get('serverlog')
1575 serverlogs = opts.get('serverlog')
1576 if serverlogs:
1576 if serverlogs:
1577 for filename in serverlogs:
1577 for filename in serverlogs:
1578 logfile = open(filename, 'r')
1578 logfile = open(filename, 'r')
1579 try:
1579 try:
1580 line = logfile.readline()
1580 line = logfile.readline()
1581 while line:
1581 while line:
1582 parts = line.strip().split(';')
1582 parts = line.strip().split(';')
1583 op = parts[1]
1583 op = parts[1]
1584 if op == 'cg':
1584 if op == 'cg':
1585 pass
1585 pass
1586 elif op == 'cgss':
1586 elif op == 'cgss':
1587 doit(parts[2].split(' '), parts[3].split(' '))
1587 doit(parts[2].split(' '), parts[3].split(' '))
1588 elif op == 'unb':
1588 elif op == 'unb':
1589 doit(parts[3].split(' '), parts[2].split(' '))
1589 doit(parts[3].split(' '), parts[2].split(' '))
1590 line = logfile.readline()
1590 line = logfile.readline()
1591 finally:
1591 finally:
1592 logfile.close()
1592 logfile.close()
1593
1593
1594 else:
1594 else:
1595 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1595 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
1596 opts.get('remote_head'))
1596 opts.get('remote_head'))
1597 localrevs = opts.get('local_head')
1597 localrevs = opts.get('local_head')
1598 doit(localrevs, remoterevs)
1598 doit(localrevs, remoterevs)
1599
1599
1600 @command('debugfsinfo', [], _('[PATH]'))
1600 @command('debugfsinfo', [], _('[PATH]'))
1601 def debugfsinfo(ui, path = "."):
1601 def debugfsinfo(ui, path = "."):
1602 """show information detected about current filesystem"""
1602 """show information detected about current filesystem"""
1603 util.writefile('.debugfsinfo', '')
1603 util.writefile('.debugfsinfo', '')
1604 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1604 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
1605 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1605 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
1606 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1606 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
1607 and 'yes' or 'no'))
1607 and 'yes' or 'no'))
1608 os.unlink('.debugfsinfo')
1608 os.unlink('.debugfsinfo')
1609
1609
1610 @command('debuggetbundle',
1610 @command('debuggetbundle',
1611 [('H', 'head', [], _('id of head node'), _('ID')),
1611 [('H', 'head', [], _('id of head node'), _('ID')),
1612 ('C', 'common', [], _('id of common node'), _('ID')),
1612 ('C', 'common', [], _('id of common node'), _('ID')),
1613 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1613 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1614 _('REPO FILE [-H|-C ID]...'))
1614 _('REPO FILE [-H|-C ID]...'))
1615 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1615 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1616 """retrieves a bundle from a repo
1616 """retrieves a bundle from a repo
1617
1617
1618 Every ID must be a full-length hex node id string. Saves the bundle to the
1618 Every ID must be a full-length hex node id string. Saves the bundle to the
1619 given file.
1619 given file.
1620 """
1620 """
1621 repo = hg.repository(ui, repopath)
1621 repo = hg.repository(ui, repopath)
1622 if not repo.capable('getbundle'):
1622 if not repo.capable('getbundle'):
1623 raise util.Abort("getbundle() not supported by target repository")
1623 raise util.Abort("getbundle() not supported by target repository")
1624 args = {}
1624 args = {}
1625 if common:
1625 if common:
1626 args['common'] = [bin(s) for s in common]
1626 args['common'] = [bin(s) for s in common]
1627 if head:
1627 if head:
1628 args['heads'] = [bin(s) for s in head]
1628 args['heads'] = [bin(s) for s in head]
1629 bundle = repo.getbundle('debug', **args)
1629 bundle = repo.getbundle('debug', **args)
1630
1630
1631 bundletype = opts.get('type', 'bzip2').lower()
1631 bundletype = opts.get('type', 'bzip2').lower()
1632 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1632 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
1633 bundletype = btypes.get(bundletype)
1633 bundletype = btypes.get(bundletype)
1634 if bundletype not in changegroup.bundletypes:
1634 if bundletype not in changegroup.bundletypes:
1635 raise util.Abort(_('unknown bundle type specified with --type'))
1635 raise util.Abort(_('unknown bundle type specified with --type'))
1636 changegroup.writebundle(bundle, bundlepath, bundletype)
1636 changegroup.writebundle(bundle, bundlepath, bundletype)
1637
1637
1638 @command('debugignore', [], '')
1638 @command('debugignore', [], '')
1639 def debugignore(ui, repo, *values, **opts):
1639 def debugignore(ui, repo, *values, **opts):
1640 """display the combined ignore pattern"""
1640 """display the combined ignore pattern"""
1641 ignore = repo.dirstate._ignore
1641 ignore = repo.dirstate._ignore
1642 if hasattr(ignore, 'includepat'):
1642 if hasattr(ignore, 'includepat'):
1643 ui.write("%s\n" % ignore.includepat)
1643 ui.write("%s\n" % ignore.includepat)
1644 else:
1644 else:
1645 raise util.Abort(_("no ignore patterns found"))
1645 raise util.Abort(_("no ignore patterns found"))
1646
1646
1647 @command('debugindex',
1647 @command('debugindex',
1648 [('c', 'changelog', False, _('open changelog')),
1648 [('c', 'changelog', False, _('open changelog')),
1649 ('m', 'manifest', False, _('open manifest')),
1649 ('m', 'manifest', False, _('open manifest')),
1650 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1650 ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1651 _('[-f FORMAT] -c|-m|FILE'))
1651 _('[-f FORMAT] -c|-m|FILE'))
1652 def debugindex(ui, repo, file_ = None, **opts):
1652 def debugindex(ui, repo, file_ = None, **opts):
1653 """dump the contents of an index file"""
1653 """dump the contents of an index file"""
1654 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1654 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1655 format = opts.get('format', 0)
1655 format = opts.get('format', 0)
1656 if format not in (0, 1):
1656 if format not in (0, 1):
1657 raise util.Abort(_("unknown format %d") % format)
1657 raise util.Abort(_("unknown format %d") % format)
1658
1658
1659 generaldelta = r.version & revlog.REVLOGGENERALDELTA
1659 generaldelta = r.version & revlog.REVLOGGENERALDELTA
1660 if generaldelta:
1660 if generaldelta:
1661 basehdr = ' delta'
1661 basehdr = ' delta'
1662 else:
1662 else:
1663 basehdr = ' base'
1663 basehdr = ' base'
1664
1664
1665 if format == 0:
1665 if format == 0:
1666 ui.write(" rev offset length " + basehdr + " linkrev"
1666 ui.write(" rev offset length " + basehdr + " linkrev"
1667 " nodeid p1 p2\n")
1667 " nodeid p1 p2\n")
1668 elif format == 1:
1668 elif format == 1:
1669 ui.write(" rev flag offset length"
1669 ui.write(" rev flag offset length"
1670 " size " + basehdr + " link p1 p2 nodeid\n")
1670 " size " + basehdr + " link p1 p2 nodeid\n")
1671
1671
1672 for i in r:
1672 for i in r:
1673 node = r.node(i)
1673 node = r.node(i)
1674 if generaldelta:
1674 if generaldelta:
1675 base = r.deltaparent(i)
1675 base = r.deltaparent(i)
1676 else:
1676 else:
1677 base = r.chainbase(i)
1677 base = r.chainbase(i)
1678 if format == 0:
1678 if format == 0:
1679 try:
1679 try:
1680 pp = r.parents(node)
1680 pp = r.parents(node)
1681 except:
1681 except:
1682 pp = [nullid, nullid]
1682 pp = [nullid, nullid]
1683 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1683 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1684 i, r.start(i), r.length(i), base, r.linkrev(i),
1684 i, r.start(i), r.length(i), base, r.linkrev(i),
1685 short(node), short(pp[0]), short(pp[1])))
1685 short(node), short(pp[0]), short(pp[1])))
1686 elif format == 1:
1686 elif format == 1:
1687 pr = r.parentrevs(i)
1687 pr = r.parentrevs(i)
1688 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1688 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
1689 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1689 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1690 base, r.linkrev(i), pr[0], pr[1], short(node)))
1690 base, r.linkrev(i), pr[0], pr[1], short(node)))
1691
1691
1692 @command('debugindexdot', [], _('FILE'))
1692 @command('debugindexdot', [], _('FILE'))
1693 def debugindexdot(ui, repo, file_):
1693 def debugindexdot(ui, repo, file_):
1694 """dump an index DAG as a graphviz dot file"""
1694 """dump an index DAG as a graphviz dot file"""
1695 r = None
1695 r = None
1696 if repo:
1696 if repo:
1697 filelog = repo.file(file_)
1697 filelog = repo.file(file_)
1698 if len(filelog):
1698 if len(filelog):
1699 r = filelog
1699 r = filelog
1700 if not r:
1700 if not r:
1701 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1701 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_)
1702 ui.write("digraph G {\n")
1702 ui.write("digraph G {\n")
1703 for i in r:
1703 for i in r:
1704 node = r.node(i)
1704 node = r.node(i)
1705 pp = r.parents(node)
1705 pp = r.parents(node)
1706 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1706 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1707 if pp[1] != nullid:
1707 if pp[1] != nullid:
1708 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1708 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1709 ui.write("}\n")
1709 ui.write("}\n")
1710
1710
1711 @command('debuginstall', [], '')
1711 @command('debuginstall', [], '')
1712 def debuginstall(ui):
1712 def debuginstall(ui):
1713 '''test Mercurial installation
1713 '''test Mercurial installation
1714
1714
1715 Returns 0 on success.
1715 Returns 0 on success.
1716 '''
1716 '''
1717
1717
1718 def writetemp(contents):
1718 def writetemp(contents):
1719 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1719 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1720 f = os.fdopen(fd, "wb")
1720 f = os.fdopen(fd, "wb")
1721 f.write(contents)
1721 f.write(contents)
1722 f.close()
1722 f.close()
1723 return name
1723 return name
1724
1724
1725 problems = 0
1725 problems = 0
1726
1726
1727 # encoding
1727 # encoding
1728 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1728 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1729 try:
1729 try:
1730 encoding.fromlocal("test")
1730 encoding.fromlocal("test")
1731 except util.Abort, inst:
1731 except util.Abort, inst:
1732 ui.write(" %s\n" % inst)
1732 ui.write(" %s\n" % inst)
1733 ui.write(_(" (check that your locale is properly set)\n"))
1733 ui.write(_(" (check that your locale is properly set)\n"))
1734 problems += 1
1734 problems += 1
1735
1735
1736 # compiled modules
1736 # compiled modules
1737 ui.status(_("Checking installed modules (%s)...\n")
1737 ui.status(_("Checking installed modules (%s)...\n")
1738 % os.path.dirname(__file__))
1738 % os.path.dirname(__file__))
1739 try:
1739 try:
1740 import bdiff, mpatch, base85, osutil
1740 import bdiff, mpatch, base85, osutil
1741 except Exception, inst:
1741 except Exception, inst:
1742 ui.write(" %s\n" % inst)
1742 ui.write(" %s\n" % inst)
1743 ui.write(_(" One or more extensions could not be found"))
1743 ui.write(_(" One or more extensions could not be found"))
1744 ui.write(_(" (check that you compiled the extensions)\n"))
1744 ui.write(_(" (check that you compiled the extensions)\n"))
1745 problems += 1
1745 problems += 1
1746
1746
1747 # templates
1747 # templates
1748 ui.status(_("Checking templates...\n"))
1748 ui.status(_("Checking templates...\n"))
1749 try:
1749 try:
1750 import templater
1750 import templater
1751 templater.templater(templater.templatepath("map-cmdline.default"))
1751 templater.templater(templater.templatepath("map-cmdline.default"))
1752 except Exception, inst:
1752 except Exception, inst:
1753 ui.write(" %s\n" % inst)
1753 ui.write(" %s\n" % inst)
1754 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1754 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1755 problems += 1
1755 problems += 1
1756
1756
1757 # editor
1757 # editor
1758 ui.status(_("Checking commit editor...\n"))
1758 ui.status(_("Checking commit editor...\n"))
1759 editor = ui.geteditor()
1759 editor = ui.geteditor()
1760 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
1760 cmdpath = util.findexe(editor) or util.findexe(editor.split()[0])
1761 if not cmdpath:
1761 if not cmdpath:
1762 if editor == 'vi':
1762 if editor == 'vi':
1763 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1763 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1764 ui.write(_(" (specify a commit editor in your configuration"
1764 ui.write(_(" (specify a commit editor in your configuration"
1765 " file)\n"))
1765 " file)\n"))
1766 else:
1766 else:
1767 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1767 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1768 ui.write(_(" (specify a commit editor in your configuration"
1768 ui.write(_(" (specify a commit editor in your configuration"
1769 " file)\n"))
1769 " file)\n"))
1770 problems += 1
1770 problems += 1
1771
1771
1772 # check username
1772 # check username
1773 ui.status(_("Checking username...\n"))
1773 ui.status(_("Checking username...\n"))
1774 try:
1774 try:
1775 ui.username()
1775 ui.username()
1776 except util.Abort, e:
1776 except util.Abort, e:
1777 ui.write(" %s\n" % e)
1777 ui.write(" %s\n" % e)
1778 ui.write(_(" (specify a username in your configuration file)\n"))
1778 ui.write(_(" (specify a username in your configuration file)\n"))
1779 problems += 1
1779 problems += 1
1780
1780
1781 if not problems:
1781 if not problems:
1782 ui.status(_("No problems detected\n"))
1782 ui.status(_("No problems detected\n"))
1783 else:
1783 else:
1784 ui.write(_("%s problems detected,"
1784 ui.write(_("%s problems detected,"
1785 " please check your install!\n") % problems)
1785 " please check your install!\n") % problems)
1786
1786
1787 return problems
1787 return problems
1788
1788
1789 @command('debugknown', [], _('REPO ID...'))
1789 @command('debugknown', [], _('REPO ID...'))
1790 def debugknown(ui, repopath, *ids, **opts):
1790 def debugknown(ui, repopath, *ids, **opts):
1791 """test whether node ids are known to a repo
1791 """test whether node ids are known to a repo
1792
1792
1793 Every ID must be a full-length hex node id string. Returns a list of 0s and 1s
1793 Every ID must be a full-length hex node id string. Returns a list of 0s and 1s
1794 indicating unknown/known.
1794 indicating unknown/known.
1795 """
1795 """
1796 repo = hg.repository(ui, repopath)
1796 repo = hg.repository(ui, repopath)
1797 if not repo.capable('known'):
1797 if not repo.capable('known'):
1798 raise util.Abort("known() not supported by target repository")
1798 raise util.Abort("known() not supported by target repository")
1799 flags = repo.known([bin(s) for s in ids])
1799 flags = repo.known([bin(s) for s in ids])
1800 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1800 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1801
1801
1802 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
1802 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'))
1803 def debugpushkey(ui, repopath, namespace, *keyinfo):
1803 def debugpushkey(ui, repopath, namespace, *keyinfo):
1804 '''access the pushkey key/value protocol
1804 '''access the pushkey key/value protocol
1805
1805
1806 With two args, list the keys in the given namespace.
1806 With two args, list the keys in the given namespace.
1807
1807
1808 With five args, set a key to new if it currently is set to old.
1808 With five args, set a key to new if it currently is set to old.
1809 Reports success or failure.
1809 Reports success or failure.
1810 '''
1810 '''
1811
1811
1812 target = hg.repository(ui, repopath)
1812 target = hg.repository(ui, repopath)
1813 if keyinfo:
1813 if keyinfo:
1814 key, old, new = keyinfo
1814 key, old, new = keyinfo
1815 r = target.pushkey(namespace, key, old, new)
1815 r = target.pushkey(namespace, key, old, new)
1816 ui.status(str(r) + '\n')
1816 ui.status(str(r) + '\n')
1817 return not r
1817 return not r
1818 else:
1818 else:
1819 for k, v in target.listkeys(namespace).iteritems():
1819 for k, v in target.listkeys(namespace).iteritems():
1820 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1820 ui.write("%s\t%s\n" % (k.encode('string-escape'),
1821 v.encode('string-escape')))
1821 v.encode('string-escape')))
1822
1822
1823 @command('debugrebuildstate',
1823 @command('debugrebuildstate',
1824 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
1824 [('r', 'rev', '', _('revision to rebuild to'), _('REV'))],
1825 _('[-r REV] [REV]'))
1825 _('[-r REV] [REV]'))
1826 def debugrebuildstate(ui, repo, rev="tip"):
1826 def debugrebuildstate(ui, repo, rev="tip"):
1827 """rebuild the dirstate as it would look like for the given revision"""
1827 """rebuild the dirstate as it would look like for the given revision"""
1828 ctx = scmutil.revsingle(repo, rev)
1828 ctx = scmutil.revsingle(repo, rev)
1829 wlock = repo.wlock()
1829 wlock = repo.wlock()
1830 try:
1830 try:
1831 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1831 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1832 finally:
1832 finally:
1833 wlock.release()
1833 wlock.release()
1834
1834
1835 @command('debugrename',
1835 @command('debugrename',
1836 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1836 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1837 _('[-r REV] FILE'))
1837 _('[-r REV] FILE'))
1838 def debugrename(ui, repo, file1, *pats, **opts):
1838 def debugrename(ui, repo, file1, *pats, **opts):
1839 """dump rename information"""
1839 """dump rename information"""
1840
1840
1841 ctx = scmutil.revsingle(repo, opts.get('rev'))
1841 ctx = scmutil.revsingle(repo, opts.get('rev'))
1842 m = scmutil.match(repo, (file1,) + pats, opts)
1842 m = scmutil.match(repo, (file1,) + pats, opts)
1843 for abs in ctx.walk(m):
1843 for abs in ctx.walk(m):
1844 fctx = ctx[abs]
1844 fctx = ctx[abs]
1845 o = fctx.filelog().renamed(fctx.filenode())
1845 o = fctx.filelog().renamed(fctx.filenode())
1846 rel = m.rel(abs)
1846 rel = m.rel(abs)
1847 if o:
1847 if o:
1848 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1848 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1849 else:
1849 else:
1850 ui.write(_("%s not renamed\n") % rel)
1850 ui.write(_("%s not renamed\n") % rel)
1851
1851
1852 @command('debugrevlog',
1852 @command('debugrevlog',
1853 [('c', 'changelog', False, _('open changelog')),
1853 [('c', 'changelog', False, _('open changelog')),
1854 ('m', 'manifest', False, _('open manifest')),
1854 ('m', 'manifest', False, _('open manifest')),
1855 ('d', 'dump', False, _('dump index data'))],
1855 ('d', 'dump', False, _('dump index data'))],
1856 _('-c|-m|FILE'))
1856 _('-c|-m|FILE'))
1857 def debugrevlog(ui, repo, file_ = None, **opts):
1857 def debugrevlog(ui, repo, file_ = None, **opts):
1858 """show data and statistics about a revlog"""
1858 """show data and statistics about a revlog"""
1859 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
1859 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
1860
1860
1861 if opts.get("dump"):
1861 if opts.get("dump"):
1862 numrevs = len(r)
1862 numrevs = len(r)
1863 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
1863 ui.write("# rev p1rev p2rev start end deltastart base p1 p2"
1864 " rawsize totalsize compression heads\n")
1864 " rawsize totalsize compression heads\n")
1865 ts = 0
1865 ts = 0
1866 heads = set()
1866 heads = set()
1867 for rev in xrange(numrevs):
1867 for rev in xrange(numrevs):
1868 dbase = r.deltaparent(rev)
1868 dbase = r.deltaparent(rev)
1869 if dbase == -1:
1869 if dbase == -1:
1870 dbase = rev
1870 dbase = rev
1871 cbase = r.chainbase(rev)
1871 cbase = r.chainbase(rev)
1872 p1, p2 = r.parentrevs(rev)
1872 p1, p2 = r.parentrevs(rev)
1873 rs = r.rawsize(rev)
1873 rs = r.rawsize(rev)
1874 ts = ts + rs
1874 ts = ts + rs
1875 heads -= set(r.parentrevs(rev))
1875 heads -= set(r.parentrevs(rev))
1876 heads.add(rev)
1876 heads.add(rev)
1877 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
1877 ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" %
1878 (rev, p1, p2, r.start(rev), r.end(rev),
1878 (rev, p1, p2, r.start(rev), r.end(rev),
1879 r.start(dbase), r.start(cbase),
1879 r.start(dbase), r.start(cbase),
1880 r.start(p1), r.start(p2),
1880 r.start(p1), r.start(p2),
1881 rs, ts, ts / r.end(rev), len(heads)))
1881 rs, ts, ts / r.end(rev), len(heads)))
1882 return 0
1882 return 0
1883
1883
1884 v = r.version
1884 v = r.version
1885 format = v & 0xFFFF
1885 format = v & 0xFFFF
1886 flags = []
1886 flags = []
1887 gdelta = False
1887 gdelta = False
1888 if v & revlog.REVLOGNGINLINEDATA:
1888 if v & revlog.REVLOGNGINLINEDATA:
1889 flags.append('inline')
1889 flags.append('inline')
1890 if v & revlog.REVLOGGENERALDELTA:
1890 if v & revlog.REVLOGGENERALDELTA:
1891 gdelta = True
1891 gdelta = True
1892 flags.append('generaldelta')
1892 flags.append('generaldelta')
1893 if not flags:
1893 if not flags:
1894 flags = ['(none)']
1894 flags = ['(none)']
1895
1895
1896 nummerges = 0
1896 nummerges = 0
1897 numfull = 0
1897 numfull = 0
1898 numprev = 0
1898 numprev = 0
1899 nump1 = 0
1899 nump1 = 0
1900 nump2 = 0
1900 nump2 = 0
1901 numother = 0
1901 numother = 0
1902 nump1prev = 0
1902 nump1prev = 0
1903 nump2prev = 0
1903 nump2prev = 0
1904 chainlengths = []
1904 chainlengths = []
1905
1905
1906 datasize = [None, 0, 0L]
1906 datasize = [None, 0, 0L]
1907 fullsize = [None, 0, 0L]
1907 fullsize = [None, 0, 0L]
1908 deltasize = [None, 0, 0L]
1908 deltasize = [None, 0, 0L]
1909
1909
1910 def addsize(size, l):
1910 def addsize(size, l):
1911 if l[0] is None or size < l[0]:
1911 if l[0] is None or size < l[0]:
1912 l[0] = size
1912 l[0] = size
1913 if size > l[1]:
1913 if size > l[1]:
1914 l[1] = size
1914 l[1] = size
1915 l[2] += size
1915 l[2] += size
1916
1916
1917 numrevs = len(r)
1917 numrevs = len(r)
1918 for rev in xrange(numrevs):
1918 for rev in xrange(numrevs):
1919 p1, p2 = r.parentrevs(rev)
1919 p1, p2 = r.parentrevs(rev)
1920 delta = r.deltaparent(rev)
1920 delta = r.deltaparent(rev)
1921 if format > 0:
1921 if format > 0:
1922 addsize(r.rawsize(rev), datasize)
1922 addsize(r.rawsize(rev), datasize)
1923 if p2 != nullrev:
1923 if p2 != nullrev:
1924 nummerges += 1
1924 nummerges += 1
1925 size = r.length(rev)
1925 size = r.length(rev)
1926 if delta == nullrev:
1926 if delta == nullrev:
1927 chainlengths.append(0)
1927 chainlengths.append(0)
1928 numfull += 1
1928 numfull += 1
1929 addsize(size, fullsize)
1929 addsize(size, fullsize)
1930 else:
1930 else:
1931 chainlengths.append(chainlengths[delta] + 1)
1931 chainlengths.append(chainlengths[delta] + 1)
1932 addsize(size, deltasize)
1932 addsize(size, deltasize)
1933 if delta == rev - 1:
1933 if delta == rev - 1:
1934 numprev += 1
1934 numprev += 1
1935 if delta == p1:
1935 if delta == p1:
1936 nump1prev += 1
1936 nump1prev += 1
1937 elif delta == p2:
1937 elif delta == p2:
1938 nump2prev += 1
1938 nump2prev += 1
1939 elif delta == p1:
1939 elif delta == p1:
1940 nump1 += 1
1940 nump1 += 1
1941 elif delta == p2:
1941 elif delta == p2:
1942 nump2 += 1
1942 nump2 += 1
1943 elif delta != nullrev:
1943 elif delta != nullrev:
1944 numother += 1
1944 numother += 1
1945
1945
1946 numdeltas = numrevs - numfull
1946 numdeltas = numrevs - numfull
1947 numoprev = numprev - nump1prev - nump2prev
1947 numoprev = numprev - nump1prev - nump2prev
1948 totalrawsize = datasize[2]
1948 totalrawsize = datasize[2]
1949 datasize[2] /= numrevs
1949 datasize[2] /= numrevs
1950 fulltotal = fullsize[2]
1950 fulltotal = fullsize[2]
1951 fullsize[2] /= numfull
1951 fullsize[2] /= numfull
1952 deltatotal = deltasize[2]
1952 deltatotal = deltasize[2]
1953 deltasize[2] /= numrevs - numfull
1953 deltasize[2] /= numrevs - numfull
1954 totalsize = fulltotal + deltatotal
1954 totalsize = fulltotal + deltatotal
1955 avgchainlen = sum(chainlengths) / numrevs
1955 avgchainlen = sum(chainlengths) / numrevs
1956 compratio = totalrawsize / totalsize
1956 compratio = totalrawsize / totalsize
1957
1957
1958 basedfmtstr = '%%%dd\n'
1958 basedfmtstr = '%%%dd\n'
1959 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
1959 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
1960
1960
1961 def dfmtstr(max):
1961 def dfmtstr(max):
1962 return basedfmtstr % len(str(max))
1962 return basedfmtstr % len(str(max))
1963 def pcfmtstr(max, padding=0):
1963 def pcfmtstr(max, padding=0):
1964 return basepcfmtstr % (len(str(max)), ' ' * padding)
1964 return basepcfmtstr % (len(str(max)), ' ' * padding)
1965
1965
1966 def pcfmt(value, total):
1966 def pcfmt(value, total):
1967 return (value, 100 * float(value) / total)
1967 return (value, 100 * float(value) / total)
1968
1968
1969 ui.write('format : %d\n' % format)
1969 ui.write('format : %d\n' % format)
1970 ui.write('flags : %s\n' % ', '.join(flags))
1970 ui.write('flags : %s\n' % ', '.join(flags))
1971
1971
1972 ui.write('\n')
1972 ui.write('\n')
1973 fmt = pcfmtstr(totalsize)
1973 fmt = pcfmtstr(totalsize)
1974 fmt2 = dfmtstr(totalsize)
1974 fmt2 = dfmtstr(totalsize)
1975 ui.write('revisions : ' + fmt2 % numrevs)
1975 ui.write('revisions : ' + fmt2 % numrevs)
1976 ui.write(' merges : ' + fmt % pcfmt(nummerges, numrevs))
1976 ui.write(' merges : ' + fmt % pcfmt(nummerges, numrevs))
1977 ui.write(' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs))
1977 ui.write(' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs))
1978 ui.write('revisions : ' + fmt2 % numrevs)
1978 ui.write('revisions : ' + fmt2 % numrevs)
1979 ui.write(' full : ' + fmt % pcfmt(numfull, numrevs))
1979 ui.write(' full : ' + fmt % pcfmt(numfull, numrevs))
1980 ui.write(' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
1980 ui.write(' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
1981 ui.write('revision size : ' + fmt2 % totalsize)
1981 ui.write('revision size : ' + fmt2 % totalsize)
1982 ui.write(' full : ' + fmt % pcfmt(fulltotal, totalsize))
1982 ui.write(' full : ' + fmt % pcfmt(fulltotal, totalsize))
1983 ui.write(' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
1983 ui.write(' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
1984
1984
1985 ui.write('\n')
1985 ui.write('\n')
1986 fmt = dfmtstr(max(avgchainlen, compratio))
1986 fmt = dfmtstr(max(avgchainlen, compratio))
1987 ui.write('avg chain length : ' + fmt % avgchainlen)
1987 ui.write('avg chain length : ' + fmt % avgchainlen)
1988 ui.write('compression ratio : ' + fmt % compratio)
1988 ui.write('compression ratio : ' + fmt % compratio)
1989
1989
1990 if format > 0:
1990 if format > 0:
1991 ui.write('\n')
1991 ui.write('\n')
1992 ui.write('uncompressed data size (min/max/avg) : %d / %d / %d\n'
1992 ui.write('uncompressed data size (min/max/avg) : %d / %d / %d\n'
1993 % tuple(datasize))
1993 % tuple(datasize))
1994 ui.write('full revision size (min/max/avg) : %d / %d / %d\n'
1994 ui.write('full revision size (min/max/avg) : %d / %d / %d\n'
1995 % tuple(fullsize))
1995 % tuple(fullsize))
1996 ui.write('delta size (min/max/avg) : %d / %d / %d\n'
1996 ui.write('delta size (min/max/avg) : %d / %d / %d\n'
1997 % tuple(deltasize))
1997 % tuple(deltasize))
1998
1998
1999 if numdeltas > 0:
1999 if numdeltas > 0:
2000 ui.write('\n')
2000 ui.write('\n')
2001 fmt = pcfmtstr(numdeltas)
2001 fmt = pcfmtstr(numdeltas)
2002 fmt2 = pcfmtstr(numdeltas, 4)
2002 fmt2 = pcfmtstr(numdeltas, 4)
2003 ui.write('deltas against prev : ' + fmt % pcfmt(numprev, numdeltas))
2003 ui.write('deltas against prev : ' + fmt % pcfmt(numprev, numdeltas))
2004 if numprev > 0:
2004 if numprev > 0:
2005 ui.write(' where prev = p1 : ' + fmt2 % pcfmt(nump1prev, numprev))
2005 ui.write(' where prev = p1 : ' + fmt2 % pcfmt(nump1prev, numprev))
2006 ui.write(' where prev = p2 : ' + fmt2 % pcfmt(nump2prev, numprev))
2006 ui.write(' where prev = p2 : ' + fmt2 % pcfmt(nump2prev, numprev))
2007 ui.write(' other : ' + fmt2 % pcfmt(numoprev, numprev))
2007 ui.write(' other : ' + fmt2 % pcfmt(numoprev, numprev))
2008 if gdelta:
2008 if gdelta:
2009 ui.write('deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas))
2009 ui.write('deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas))
2010 ui.write('deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas))
2010 ui.write('deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas))
2011 ui.write('deltas against other : ' + fmt % pcfmt(numother, numdeltas))
2011 ui.write('deltas against other : ' + fmt % pcfmt(numother, numdeltas))
2012
2012
2013 @command('debugrevspec', [], ('REVSPEC'))
2013 @command('debugrevspec', [], ('REVSPEC'))
2014 def debugrevspec(ui, repo, expr):
2014 def debugrevspec(ui, repo, expr):
2015 '''parse and apply a revision specification'''
2015 '''parse and apply a revision specification'''
2016 if ui.verbose:
2016 if ui.verbose:
2017 tree = revset.parse(expr)[0]
2017 tree = revset.parse(expr)[0]
2018 ui.note(tree, "\n")
2018 ui.note(tree, "\n")
2019 newtree = revset.findaliases(ui, tree)
2019 newtree = revset.findaliases(ui, tree)
2020 if newtree != tree:
2020 if newtree != tree:
2021 ui.note(newtree, "\n")
2021 ui.note(newtree, "\n")
2022 func = revset.match(ui, expr)
2022 func = revset.match(ui, expr)
2023 for c in func(repo, range(len(repo))):
2023 for c in func(repo, range(len(repo))):
2024 ui.write("%s\n" % c)
2024 ui.write("%s\n" % c)
2025
2025
2026 @command('debugsetparents', [], _('REV1 [REV2]'))
2026 @command('debugsetparents', [], _('REV1 [REV2]'))
2027 def debugsetparents(ui, repo, rev1, rev2=None):
2027 def debugsetparents(ui, repo, rev1, rev2=None):
2028 """manually set the parents of the current working directory
2028 """manually set the parents of the current working directory
2029
2029
2030 This is useful for writing repository conversion tools, but should
2030 This is useful for writing repository conversion tools, but should
2031 be used with care.
2031 be used with care.
2032
2032
2033 Returns 0 on success.
2033 Returns 0 on success.
2034 """
2034 """
2035
2035
2036 r1 = scmutil.revsingle(repo, rev1).node()
2036 r1 = scmutil.revsingle(repo, rev1).node()
2037 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2037 r2 = scmutil.revsingle(repo, rev2, 'null').node()
2038
2038
2039 wlock = repo.wlock()
2039 wlock = repo.wlock()
2040 try:
2040 try:
2041 repo.dirstate.setparents(r1, r2)
2041 repo.dirstate.setparents(r1, r2)
2042 finally:
2042 finally:
2043 wlock.release()
2043 wlock.release()
2044
2044
2045 @command('debugstate',
2045 @command('debugstate',
2046 [('', 'nodates', None, _('do not display the saved mtime')),
2046 [('', 'nodates', None, _('do not display the saved mtime')),
2047 ('', 'datesort', None, _('sort by saved mtime'))],
2047 ('', 'datesort', None, _('sort by saved mtime'))],
2048 _('[OPTION]...'))
2048 _('[OPTION]...'))
2049 def debugstate(ui, repo, nodates=None, datesort=None):
2049 def debugstate(ui, repo, nodates=None, datesort=None):
2050 """show the contents of the current dirstate"""
2050 """show the contents of the current dirstate"""
2051 timestr = ""
2051 timestr = ""
2052 showdate = not nodates
2052 showdate = not nodates
2053 if datesort:
2053 if datesort:
2054 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2054 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
2055 else:
2055 else:
2056 keyfunc = None # sort by filename
2056 keyfunc = None # sort by filename
2057 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2057 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
2058 if showdate:
2058 if showdate:
2059 if ent[3] == -1:
2059 if ent[3] == -1:
2060 # Pad or slice to locale representation
2060 # Pad or slice to locale representation
2061 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2061 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
2062 time.localtime(0)))
2062 time.localtime(0)))
2063 timestr = 'unset'
2063 timestr = 'unset'
2064 timestr = (timestr[:locale_len] +
2064 timestr = (timestr[:locale_len] +
2065 ' ' * (locale_len - len(timestr)))
2065 ' ' * (locale_len - len(timestr)))
2066 else:
2066 else:
2067 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2067 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
2068 time.localtime(ent[3]))
2068 time.localtime(ent[3]))
2069 if ent[1] & 020000:
2069 if ent[1] & 020000:
2070 mode = 'lnk'
2070 mode = 'lnk'
2071 else:
2071 else:
2072 mode = '%3o' % (ent[1] & 0777)
2072 mode = '%3o' % (ent[1] & 0777)
2073 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2073 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
2074 for f in repo.dirstate.copies():
2074 for f in repo.dirstate.copies():
2075 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2075 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
2076
2076
2077 @command('debugsub',
2077 @command('debugsub',
2078 [('r', 'rev', '',
2078 [('r', 'rev', '',
2079 _('revision to check'), _('REV'))],
2079 _('revision to check'), _('REV'))],
2080 _('[-r REV] [REV]'))
2080 _('[-r REV] [REV]'))
2081 def debugsub(ui, repo, rev=None):
2081 def debugsub(ui, repo, rev=None):
2082 ctx = scmutil.revsingle(repo, rev, None)
2082 ctx = scmutil.revsingle(repo, rev, None)
2083 for k, v in sorted(ctx.substate.items()):
2083 for k, v in sorted(ctx.substate.items()):
2084 ui.write('path %s\n' % k)
2084 ui.write('path %s\n' % k)
2085 ui.write(' source %s\n' % v[0])
2085 ui.write(' source %s\n' % v[0])
2086 ui.write(' revision %s\n' % v[1])
2086 ui.write(' revision %s\n' % v[1])
2087
2087
2088 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2088 @command('debugwalk', walkopts, _('[OPTION]... [FILE]...'))
2089 def debugwalk(ui, repo, *pats, **opts):
2089 def debugwalk(ui, repo, *pats, **opts):
2090 """show how files match on given patterns"""
2090 """show how files match on given patterns"""
2091 m = scmutil.match(repo, pats, opts)
2091 m = scmutil.match(repo, pats, opts)
2092 items = list(repo.walk(m))
2092 items = list(repo.walk(m))
2093 if not items:
2093 if not items:
2094 return
2094 return
2095 fmt = 'f %%-%ds %%-%ds %%s' % (
2095 fmt = 'f %%-%ds %%-%ds %%s' % (
2096 max([len(abs) for abs in items]),
2096 max([len(abs) for abs in items]),
2097 max([len(m.rel(abs)) for abs in items]))
2097 max([len(m.rel(abs)) for abs in items]))
2098 for abs in items:
2098 for abs in items:
2099 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
2099 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
2100 ui.write("%s\n" % line.rstrip())
2100 ui.write("%s\n" % line.rstrip())
2101
2101
2102 @command('debugwireargs',
2102 @command('debugwireargs',
2103 [('', 'three', '', 'three'),
2103 [('', 'three', '', 'three'),
2104 ('', 'four', '', 'four'),
2104 ('', 'four', '', 'four'),
2105 ('', 'five', '', 'five'),
2105 ('', 'five', '', 'five'),
2106 ] + remoteopts,
2106 ] + remoteopts,
2107 _('REPO [OPTIONS]... [ONE [TWO]]'))
2107 _('REPO [OPTIONS]... [ONE [TWO]]'))
2108 def debugwireargs(ui, repopath, *vals, **opts):
2108 def debugwireargs(ui, repopath, *vals, **opts):
2109 repo = hg.repository(hg.remoteui(ui, opts), repopath)
2109 repo = hg.repository(hg.remoteui(ui, opts), repopath)
2110 for opt in remoteopts:
2110 for opt in remoteopts:
2111 del opts[opt[1]]
2111 del opts[opt[1]]
2112 args = {}
2112 args = {}
2113 for k, v in opts.iteritems():
2113 for k, v in opts.iteritems():
2114 if v:
2114 if v:
2115 args[k] = v
2115 args[k] = v
2116 # run twice to check that we don't mess up the stream for the next command
2116 # run twice to check that we don't mess up the stream for the next command
2117 res1 = repo.debugwireargs(*vals, **args)
2117 res1 = repo.debugwireargs(*vals, **args)
2118 res2 = repo.debugwireargs(*vals, **args)
2118 res2 = repo.debugwireargs(*vals, **args)
2119 ui.write("%s\n" % res1)
2119 ui.write("%s\n" % res1)
2120 if res1 != res2:
2120 if res1 != res2:
2121 ui.warn("%s\n" % res2)
2121 ui.warn("%s\n" % res2)
2122
2122
2123 @command('^diff',
2123 @command('^diff',
2124 [('r', 'rev', [], _('revision'), _('REV')),
2124 [('r', 'rev', [], _('revision'), _('REV')),
2125 ('c', 'change', '', _('change made by revision'), _('REV'))
2125 ('c', 'change', '', _('change made by revision'), _('REV'))
2126 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2126 ] + diffopts + diffopts2 + walkopts + subrepoopts,
2127 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2127 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'))
2128 def diff(ui, repo, *pats, **opts):
2128 def diff(ui, repo, *pats, **opts):
2129 """diff repository (or selected files)
2129 """diff repository (or selected files)
2130
2130
2131 Show differences between revisions for the specified files.
2131 Show differences between revisions for the specified files.
2132
2132
2133 Differences between files are shown using the unified diff format.
2133 Differences between files are shown using the unified diff format.
2134
2134
2135 .. note::
2135 .. note::
2136 diff may generate unexpected results for merges, as it will
2136 diff may generate unexpected results for merges, as it will
2137 default to comparing against the working directory's first
2137 default to comparing against the working directory's first
2138 parent changeset if no revisions are specified.
2138 parent changeset if no revisions are specified.
2139
2139
2140 When two revision arguments are given, then changes are shown
2140 When two revision arguments are given, then changes are shown
2141 between those revisions. If only one revision is specified then
2141 between those revisions. If only one revision is specified then
2142 that revision is compared to the working directory, and, when no
2142 that revision is compared to the working directory, and, when no
2143 revisions are specified, the working directory files are compared
2143 revisions are specified, the working directory files are compared
2144 to its parent.
2144 to its parent.
2145
2145
2146 Alternatively you can specify -c/--change with a revision to see
2146 Alternatively you can specify -c/--change with a revision to see
2147 the changes in that changeset relative to its first parent.
2147 the changes in that changeset relative to its first parent.
2148
2148
2149 Without the -a/--text option, diff will avoid generating diffs of
2149 Without the -a/--text option, diff will avoid generating diffs of
2150 files it detects as binary. With -a, diff will generate a diff
2150 files it detects as binary. With -a, diff will generate a diff
2151 anyway, probably with undesirable results.
2151 anyway, probably with undesirable results.
2152
2152
2153 Use the -g/--git option to generate diffs in the git extended diff
2153 Use the -g/--git option to generate diffs in the git extended diff
2154 format. For more information, read :hg:`help diffs`.
2154 format. For more information, read :hg:`help diffs`.
2155
2155
2156 Returns 0 on success.
2156 Returns 0 on success.
2157 """
2157 """
2158
2158
2159 revs = opts.get('rev')
2159 revs = opts.get('rev')
2160 change = opts.get('change')
2160 change = opts.get('change')
2161 stat = opts.get('stat')
2161 stat = opts.get('stat')
2162 reverse = opts.get('reverse')
2162 reverse = opts.get('reverse')
2163
2163
2164 if revs and change:
2164 if revs and change:
2165 msg = _('cannot specify --rev and --change at the same time')
2165 msg = _('cannot specify --rev and --change at the same time')
2166 raise util.Abort(msg)
2166 raise util.Abort(msg)
2167 elif change:
2167 elif change:
2168 node2 = scmutil.revsingle(repo, change, None).node()
2168 node2 = scmutil.revsingle(repo, change, None).node()
2169 node1 = repo[node2].p1().node()
2169 node1 = repo[node2].p1().node()
2170 else:
2170 else:
2171 node1, node2 = scmutil.revpair(repo, revs)
2171 node1, node2 = scmutil.revpair(repo, revs)
2172
2172
2173 if reverse:
2173 if reverse:
2174 node1, node2 = node2, node1
2174 node1, node2 = node2, node1
2175
2175
2176 diffopts = patch.diffopts(ui, opts)
2176 diffopts = patch.diffopts(ui, opts)
2177 m = scmutil.match(repo, pats, opts)
2177 m = scmutil.match(repo, pats, opts)
2178 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2178 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2179 listsubrepos=opts.get('subrepos'))
2179 listsubrepos=opts.get('subrepos'))
2180
2180
2181 @command('^export',
2181 @command('^export',
2182 [('o', 'output', '',
2182 [('o', 'output', '',
2183 _('print output to file with formatted name'), _('FORMAT')),
2183 _('print output to file with formatted name'), _('FORMAT')),
2184 ('', 'switch-parent', None, _('diff against the second parent')),
2184 ('', 'switch-parent', None, _('diff against the second parent')),
2185 ('r', 'rev', [], _('revisions to export'), _('REV')),
2185 ('r', 'rev', [], _('revisions to export'), _('REV')),
2186 ] + diffopts,
2186 ] + diffopts,
2187 _('[OPTION]... [-o OUTFILESPEC] REV...'))
2187 _('[OPTION]... [-o OUTFILESPEC] REV...'))
2188 def export(ui, repo, *changesets, **opts):
2188 def export(ui, repo, *changesets, **opts):
2189 """dump the header and diffs for one or more changesets
2189 """dump the header and diffs for one or more changesets
2190
2190
2191 Print the changeset header and diffs for one or more revisions.
2191 Print the changeset header and diffs for one or more revisions.
2192
2192
2193 The information shown in the changeset header is: author, date,
2193 The information shown in the changeset header is: author, date,
2194 branch name (if non-default), changeset hash, parent(s) and commit
2194 branch name (if non-default), changeset hash, parent(s) and commit
2195 comment.
2195 comment.
2196
2196
2197 .. note::
2197 .. note::
2198 export may generate unexpected diff output for merge
2198 export may generate unexpected diff output for merge
2199 changesets, as it will compare the merge changeset against its
2199 changesets, as it will compare the merge changeset against its
2200 first parent only.
2200 first parent only.
2201
2201
2202 Output may be to a file, in which case the name of the file is
2202 Output may be to a file, in which case the name of the file is
2203 given using a format string. The formatting rules are as follows:
2203 given using a format string. The formatting rules are as follows:
2204
2204
2205 :``%%``: literal "%" character
2205 :``%%``: literal "%" character
2206 :``%H``: changeset hash (40 hexadecimal digits)
2206 :``%H``: changeset hash (40 hexadecimal digits)
2207 :``%N``: number of patches being generated
2207 :``%N``: number of patches being generated
2208 :``%R``: changeset revision number
2208 :``%R``: changeset revision number
2209 :``%b``: basename of the exporting repository
2209 :``%b``: basename of the exporting repository
2210 :``%h``: short-form changeset hash (12 hexadecimal digits)
2210 :``%h``: short-form changeset hash (12 hexadecimal digits)
2211 :``%n``: zero-padded sequence number, starting at 1
2211 :``%n``: zero-padded sequence number, starting at 1
2212 :``%r``: zero-padded changeset revision number
2212 :``%r``: zero-padded changeset revision number
2213
2213
2214 Without the -a/--text option, export will avoid generating diffs
2214 Without the -a/--text option, export will avoid generating diffs
2215 of files it detects as binary. With -a, export will generate a
2215 of files it detects as binary. With -a, export will generate a
2216 diff anyway, probably with undesirable results.
2216 diff anyway, probably with undesirable results.
2217
2217
2218 Use the -g/--git option to generate diffs in the git extended diff
2218 Use the -g/--git option to generate diffs in the git extended diff
2219 format. See :hg:`help diffs` for more information.
2219 format. See :hg:`help diffs` for more information.
2220
2220
2221 With the --switch-parent option, the diff will be against the
2221 With the --switch-parent option, the diff will be against the
2222 second parent. It can be useful to review a merge.
2222 second parent. It can be useful to review a merge.
2223
2223
2224 Returns 0 on success.
2224 Returns 0 on success.
2225 """
2225 """
2226 changesets += tuple(opts.get('rev', []))
2226 changesets += tuple(opts.get('rev', []))
2227 if not changesets:
2227 if not changesets:
2228 raise util.Abort(_("export requires at least one changeset"))
2228 raise util.Abort(_("export requires at least one changeset"))
2229 revs = scmutil.revrange(repo, changesets)
2229 revs = scmutil.revrange(repo, changesets)
2230 if len(revs) > 1:
2230 if len(revs) > 1:
2231 ui.note(_('exporting patches:\n'))
2231 ui.note(_('exporting patches:\n'))
2232 else:
2232 else:
2233 ui.note(_('exporting patch:\n'))
2233 ui.note(_('exporting patch:\n'))
2234 cmdutil.export(repo, revs, template=opts.get('output'),
2234 cmdutil.export(repo, revs, template=opts.get('output'),
2235 switch_parent=opts.get('switch_parent'),
2235 switch_parent=opts.get('switch_parent'),
2236 opts=patch.diffopts(ui, opts))
2236 opts=patch.diffopts(ui, opts))
2237
2237
2238 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2238 @command('^forget', walkopts, _('[OPTION]... FILE...'))
2239 def forget(ui, repo, *pats, **opts):
2239 def forget(ui, repo, *pats, **opts):
2240 """forget the specified files on the next commit
2240 """forget the specified files on the next commit
2241
2241
2242 Mark the specified files so they will no longer be tracked
2242 Mark the specified files so they will no longer be tracked
2243 after the next commit.
2243 after the next commit.
2244
2244
2245 This only removes files from the current branch, not from the
2245 This only removes files from the current branch, not from the
2246 entire project history, and it does not delete them from the
2246 entire project history, and it does not delete them from the
2247 working directory.
2247 working directory.
2248
2248
2249 To undo a forget before the next commit, see :hg:`add`.
2249 To undo a forget before the next commit, see :hg:`add`.
2250
2250
2251 Returns 0 on success.
2251 Returns 0 on success.
2252 """
2252 """
2253
2253
2254 if not pats:
2254 if not pats:
2255 raise util.Abort(_('no files specified'))
2255 raise util.Abort(_('no files specified'))
2256
2256
2257 m = scmutil.match(repo, pats, opts)
2257 m = scmutil.match(repo, pats, opts)
2258 s = repo.status(match=m, clean=True)
2258 s = repo.status(match=m, clean=True)
2259 forget = sorted(s[0] + s[1] + s[3] + s[6])
2259 forget = sorted(s[0] + s[1] + s[3] + s[6])
2260 errs = 0
2260 errs = 0
2261
2261
2262 for f in m.files():
2262 for f in m.files():
2263 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2263 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2264 ui.warn(_('not removing %s: file is already untracked\n')
2264 ui.warn(_('not removing %s: file is already untracked\n')
2265 % m.rel(f))
2265 % m.rel(f))
2266 errs = 1
2266 errs = 1
2267
2267
2268 for f in forget:
2268 for f in forget:
2269 if ui.verbose or not m.exact(f):
2269 if ui.verbose or not m.exact(f):
2270 ui.status(_('removing %s\n') % m.rel(f))
2270 ui.status(_('removing %s\n') % m.rel(f))
2271
2271
2272 repo[None].remove(forget, unlink=False)
2272 repo[None].remove(forget, unlink=False)
2273 return errs
2273 return errs
2274
2274
2275 @command('grep',
2275 @command('grep',
2276 [('0', 'print0', None, _('end fields with NUL')),
2276 [('0', 'print0', None, _('end fields with NUL')),
2277 ('', 'all', None, _('print all revisions that match')),
2277 ('', 'all', None, _('print all revisions that match')),
2278 ('a', 'text', None, _('treat all files as text')),
2278 ('a', 'text', None, _('treat all files as text')),
2279 ('f', 'follow', None,
2279 ('f', 'follow', None,
2280 _('follow changeset history,'
2280 _('follow changeset history,'
2281 ' or file history across copies and renames')),
2281 ' or file history across copies and renames')),
2282 ('i', 'ignore-case', None, _('ignore case when matching')),
2282 ('i', 'ignore-case', None, _('ignore case when matching')),
2283 ('l', 'files-with-matches', None,
2283 ('l', 'files-with-matches', None,
2284 _('print only filenames and revisions that match')),
2284 _('print only filenames and revisions that match')),
2285 ('n', 'line-number', None, _('print matching line numbers')),
2285 ('n', 'line-number', None, _('print matching line numbers')),
2286 ('r', 'rev', [],
2286 ('r', 'rev', [],
2287 _('only search files changed within revision range'), _('REV')),
2287 _('only search files changed within revision range'), _('REV')),
2288 ('u', 'user', None, _('list the author (long with -v)')),
2288 ('u', 'user', None, _('list the author (long with -v)')),
2289 ('d', 'date', None, _('list the date (short with -q)')),
2289 ('d', 'date', None, _('list the date (short with -q)')),
2290 ] + walkopts,
2290 ] + walkopts,
2291 _('[OPTION]... PATTERN [FILE]...'))
2291 _('[OPTION]... PATTERN [FILE]...'))
2292 def grep(ui, repo, pattern, *pats, **opts):
2292 def grep(ui, repo, pattern, *pats, **opts):
2293 """search for a pattern in specified files and revisions
2293 """search for a pattern in specified files and revisions
2294
2294
2295 Search revisions of files for a regular expression.
2295 Search revisions of files for a regular expression.
2296
2296
2297 This command behaves differently than Unix grep. It only accepts
2297 This command behaves differently than Unix grep. It only accepts
2298 Python/Perl regexps. It searches repository history, not the
2298 Python/Perl regexps. It searches repository history, not the
2299 working directory. It always prints the revision number in which a
2299 working directory. It always prints the revision number in which a
2300 match appears.
2300 match appears.
2301
2301
2302 By default, grep only prints output for the first revision of a
2302 By default, grep only prints output for the first revision of a
2303 file in which it finds a match. To get it to print every revision
2303 file in which it finds a match. To get it to print every revision
2304 that contains a change in match status ("-" for a match that
2304 that contains a change in match status ("-" for a match that
2305 becomes a non-match, or "+" for a non-match that becomes a match),
2305 becomes a non-match, or "+" for a non-match that becomes a match),
2306 use the --all flag.
2306 use the --all flag.
2307
2307
2308 Returns 0 if a match is found, 1 otherwise.
2308 Returns 0 if a match is found, 1 otherwise.
2309 """
2309 """
2310 reflags = 0
2310 reflags = 0
2311 if opts.get('ignore_case'):
2311 if opts.get('ignore_case'):
2312 reflags |= re.I
2312 reflags |= re.I
2313 try:
2313 try:
2314 regexp = re.compile(pattern, reflags)
2314 regexp = re.compile(pattern, reflags)
2315 except re.error, inst:
2315 except re.error, inst:
2316 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2316 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2317 return 1
2317 return 1
2318 sep, eol = ':', '\n'
2318 sep, eol = ':', '\n'
2319 if opts.get('print0'):
2319 if opts.get('print0'):
2320 sep = eol = '\0'
2320 sep = eol = '\0'
2321
2321
2322 getfile = util.lrucachefunc(repo.file)
2322 getfile = util.lrucachefunc(repo.file)
2323
2323
2324 def matchlines(body):
2324 def matchlines(body):
2325 begin = 0
2325 begin = 0
2326 linenum = 0
2326 linenum = 0
2327 while True:
2327 while True:
2328 match = regexp.search(body, begin)
2328 match = regexp.search(body, begin)
2329 if not match:
2329 if not match:
2330 break
2330 break
2331 mstart, mend = match.span()
2331 mstart, mend = match.span()
2332 linenum += body.count('\n', begin, mstart) + 1
2332 linenum += body.count('\n', begin, mstart) + 1
2333 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2333 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2334 begin = body.find('\n', mend) + 1 or len(body)
2334 begin = body.find('\n', mend) + 1 or len(body)
2335 lend = begin - 1
2335 lend = begin - 1
2336 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2336 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2337
2337
2338 class linestate(object):
2338 class linestate(object):
2339 def __init__(self, line, linenum, colstart, colend):
2339 def __init__(self, line, linenum, colstart, colend):
2340 self.line = line
2340 self.line = line
2341 self.linenum = linenum
2341 self.linenum = linenum
2342 self.colstart = colstart
2342 self.colstart = colstart
2343 self.colend = colend
2343 self.colend = colend
2344
2344
2345 def __hash__(self):
2345 def __hash__(self):
2346 return hash((self.linenum, self.line))
2346 return hash((self.linenum, self.line))
2347
2347
2348 def __eq__(self, other):
2348 def __eq__(self, other):
2349 return self.line == other.line
2349 return self.line == other.line
2350
2350
2351 matches = {}
2351 matches = {}
2352 copies = {}
2352 copies = {}
2353 def grepbody(fn, rev, body):
2353 def grepbody(fn, rev, body):
2354 matches[rev].setdefault(fn, [])
2354 matches[rev].setdefault(fn, [])
2355 m = matches[rev][fn]
2355 m = matches[rev][fn]
2356 for lnum, cstart, cend, line in matchlines(body):
2356 for lnum, cstart, cend, line in matchlines(body):
2357 s = linestate(line, lnum, cstart, cend)
2357 s = linestate(line, lnum, cstart, cend)
2358 m.append(s)
2358 m.append(s)
2359
2359
2360 def difflinestates(a, b):
2360 def difflinestates(a, b):
2361 sm = difflib.SequenceMatcher(None, a, b)
2361 sm = difflib.SequenceMatcher(None, a, b)
2362 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2362 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2363 if tag == 'insert':
2363 if tag == 'insert':
2364 for i in xrange(blo, bhi):
2364 for i in xrange(blo, bhi):
2365 yield ('+', b[i])
2365 yield ('+', b[i])
2366 elif tag == 'delete':
2366 elif tag == 'delete':
2367 for i in xrange(alo, ahi):
2367 for i in xrange(alo, ahi):
2368 yield ('-', a[i])
2368 yield ('-', a[i])
2369 elif tag == 'replace':
2369 elif tag == 'replace':
2370 for i in xrange(alo, ahi):
2370 for i in xrange(alo, ahi):
2371 yield ('-', a[i])
2371 yield ('-', a[i])
2372 for i in xrange(blo, bhi):
2372 for i in xrange(blo, bhi):
2373 yield ('+', b[i])
2373 yield ('+', b[i])
2374
2374
2375 def display(fn, ctx, pstates, states):
2375 def display(fn, ctx, pstates, states):
2376 rev = ctx.rev()
2376 rev = ctx.rev()
2377 datefunc = ui.quiet and util.shortdate or util.datestr
2377 datefunc = ui.quiet and util.shortdate or util.datestr
2378 found = False
2378 found = False
2379 filerevmatches = {}
2379 filerevmatches = {}
2380 def binary():
2380 def binary():
2381 flog = getfile(fn)
2381 flog = getfile(fn)
2382 return util.binary(flog.read(ctx.filenode(fn)))
2382 return util.binary(flog.read(ctx.filenode(fn)))
2383
2383
2384 if opts.get('all'):
2384 if opts.get('all'):
2385 iter = difflinestates(pstates, states)
2385 iter = difflinestates(pstates, states)
2386 else:
2386 else:
2387 iter = [('', l) for l in states]
2387 iter = [('', l) for l in states]
2388 for change, l in iter:
2388 for change, l in iter:
2389 cols = [fn, str(rev)]
2389 cols = [fn, str(rev)]
2390 before, match, after = None, None, None
2390 before, match, after = None, None, None
2391 if opts.get('line_number'):
2391 if opts.get('line_number'):
2392 cols.append(str(l.linenum))
2392 cols.append(str(l.linenum))
2393 if opts.get('all'):
2393 if opts.get('all'):
2394 cols.append(change)
2394 cols.append(change)
2395 if opts.get('user'):
2395 if opts.get('user'):
2396 cols.append(ui.shortuser(ctx.user()))
2396 cols.append(ui.shortuser(ctx.user()))
2397 if opts.get('date'):
2397 if opts.get('date'):
2398 cols.append(datefunc(ctx.date()))
2398 cols.append(datefunc(ctx.date()))
2399 if opts.get('files_with_matches'):
2399 if opts.get('files_with_matches'):
2400 c = (fn, rev)
2400 c = (fn, rev)
2401 if c in filerevmatches:
2401 if c in filerevmatches:
2402 continue
2402 continue
2403 filerevmatches[c] = 1
2403 filerevmatches[c] = 1
2404 else:
2404 else:
2405 before = l.line[:l.colstart]
2405 before = l.line[:l.colstart]
2406 match = l.line[l.colstart:l.colend]
2406 match = l.line[l.colstart:l.colend]
2407 after = l.line[l.colend:]
2407 after = l.line[l.colend:]
2408 ui.write(sep.join(cols))
2408 ui.write(sep.join(cols))
2409 if before is not None:
2409 if before is not None:
2410 if not opts.get('text') and binary():
2410 if not opts.get('text') and binary():
2411 ui.write(sep + " Binary file matches")
2411 ui.write(sep + " Binary file matches")
2412 else:
2412 else:
2413 ui.write(sep + before)
2413 ui.write(sep + before)
2414 ui.write(match, label='grep.match')
2414 ui.write(match, label='grep.match')
2415 ui.write(after)
2415 ui.write(after)
2416 ui.write(eol)
2416 ui.write(eol)
2417 found = True
2417 found = True
2418 return found
2418 return found
2419
2419
2420 skip = {}
2420 skip = {}
2421 revfiles = {}
2421 revfiles = {}
2422 matchfn = scmutil.match(repo, pats, opts)
2422 matchfn = scmutil.match(repo, pats, opts)
2423 found = False
2423 found = False
2424 follow = opts.get('follow')
2424 follow = opts.get('follow')
2425
2425
2426 def prep(ctx, fns):
2426 def prep(ctx, fns):
2427 rev = ctx.rev()
2427 rev = ctx.rev()
2428 pctx = ctx.p1()
2428 pctx = ctx.p1()
2429 parent = pctx.rev()
2429 parent = pctx.rev()
2430 matches.setdefault(rev, {})
2430 matches.setdefault(rev, {})
2431 matches.setdefault(parent, {})
2431 matches.setdefault(parent, {})
2432 files = revfiles.setdefault(rev, [])
2432 files = revfiles.setdefault(rev, [])
2433 for fn in fns:
2433 for fn in fns:
2434 flog = getfile(fn)
2434 flog = getfile(fn)
2435 try:
2435 try:
2436 fnode = ctx.filenode(fn)
2436 fnode = ctx.filenode(fn)
2437 except error.LookupError:
2437 except error.LookupError:
2438 continue
2438 continue
2439
2439
2440 copied = flog.renamed(fnode)
2440 copied = flog.renamed(fnode)
2441 copy = follow and copied and copied[0]
2441 copy = follow and copied and copied[0]
2442 if copy:
2442 if copy:
2443 copies.setdefault(rev, {})[fn] = copy
2443 copies.setdefault(rev, {})[fn] = copy
2444 if fn in skip:
2444 if fn in skip:
2445 if copy:
2445 if copy:
2446 skip[copy] = True
2446 skip[copy] = True
2447 continue
2447 continue
2448 files.append(fn)
2448 files.append(fn)
2449
2449
2450 if fn not in matches[rev]:
2450 if fn not in matches[rev]:
2451 grepbody(fn, rev, flog.read(fnode))
2451 grepbody(fn, rev, flog.read(fnode))
2452
2452
2453 pfn = copy or fn
2453 pfn = copy or fn
2454 if pfn not in matches[parent]:
2454 if pfn not in matches[parent]:
2455 try:
2455 try:
2456 fnode = pctx.filenode(pfn)
2456 fnode = pctx.filenode(pfn)
2457 grepbody(pfn, parent, flog.read(fnode))
2457 grepbody(pfn, parent, flog.read(fnode))
2458 except error.LookupError:
2458 except error.LookupError:
2459 pass
2459 pass
2460
2460
2461 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2461 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2462 rev = ctx.rev()
2462 rev = ctx.rev()
2463 parent = ctx.p1().rev()
2463 parent = ctx.p1().rev()
2464 for fn in sorted(revfiles.get(rev, [])):
2464 for fn in sorted(revfiles.get(rev, [])):
2465 states = matches[rev][fn]
2465 states = matches[rev][fn]
2466 copy = copies.get(rev, {}).get(fn)
2466 copy = copies.get(rev, {}).get(fn)
2467 if fn in skip:
2467 if fn in skip:
2468 if copy:
2468 if copy:
2469 skip[copy] = True
2469 skip[copy] = True
2470 continue
2470 continue
2471 pstates = matches.get(parent, {}).get(copy or fn, [])
2471 pstates = matches.get(parent, {}).get(copy or fn, [])
2472 if pstates or states:
2472 if pstates or states:
2473 r = display(fn, ctx, pstates, states)
2473 r = display(fn, ctx, pstates, states)
2474 found = found or r
2474 found = found or r
2475 if r and not opts.get('all'):
2475 if r and not opts.get('all'):
2476 skip[fn] = True
2476 skip[fn] = True
2477 if copy:
2477 if copy:
2478 skip[copy] = True
2478 skip[copy] = True
2479 del matches[rev]
2479 del matches[rev]
2480 del revfiles[rev]
2480 del revfiles[rev]
2481
2481
2482 return not found
2482 return not found
2483
2483
2484 @command('heads',
2484 @command('heads',
2485 [('r', 'rev', '',
2485 [('r', 'rev', '',
2486 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2486 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2487 ('t', 'topo', False, _('show topological heads only')),
2487 ('t', 'topo', False, _('show topological heads only')),
2488 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2488 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2489 ('c', 'closed', False, _('show normal and closed branch heads')),
2489 ('c', 'closed', False, _('show normal and closed branch heads')),
2490 ] + templateopts,
2490 ] + templateopts,
2491 _('[-ac] [-r STARTREV] [REV]...'))
2491 _('[-ac] [-r STARTREV] [REV]...'))
2492 def heads(ui, repo, *branchrevs, **opts):
2492 def heads(ui, repo, *branchrevs, **opts):
2493 """show current repository heads or show branch heads
2493 """show current repository heads or show branch heads
2494
2494
2495 With no arguments, show all repository branch heads.
2495 With no arguments, show all repository branch heads.
2496
2496
2497 Repository "heads" are changesets with no child changesets. They are
2497 Repository "heads" are changesets with no child changesets. They are
2498 where development generally takes place and are the usual targets
2498 where development generally takes place and are the usual targets
2499 for update and merge operations. Branch heads are changesets that have
2499 for update and merge operations. Branch heads are changesets that have
2500 no child changeset on the same branch.
2500 no child changeset on the same branch.
2501
2501
2502 If one or more REVs are given, only branch heads on the branches
2502 If one or more REVs are given, only branch heads on the branches
2503 associated with the specified changesets are shown.
2503 associated with the specified changesets are shown.
2504
2504
2505 If -c/--closed is specified, also show branch heads marked closed
2505 If -c/--closed is specified, also show branch heads marked closed
2506 (see :hg:`commit --close-branch`).
2506 (see :hg:`commit --close-branch`).
2507
2507
2508 If STARTREV is specified, only those heads that are descendants of
2508 If STARTREV is specified, only those heads that are descendants of
2509 STARTREV will be displayed.
2509 STARTREV will be displayed.
2510
2510
2511 If -t/--topo is specified, named branch mechanics will be ignored and only
2511 If -t/--topo is specified, named branch mechanics will be ignored and only
2512 changesets without children will be shown.
2512 changesets without children will be shown.
2513
2513
2514 Returns 0 if matching heads are found, 1 if not.
2514 Returns 0 if matching heads are found, 1 if not.
2515 """
2515 """
2516
2516
2517 start = None
2517 start = None
2518 if 'rev' in opts:
2518 if 'rev' in opts:
2519 start = scmutil.revsingle(repo, opts['rev'], None).node()
2519 start = scmutil.revsingle(repo, opts['rev'], None).node()
2520
2520
2521 if opts.get('topo'):
2521 if opts.get('topo'):
2522 heads = [repo[h] for h in repo.heads(start)]
2522 heads = [repo[h] for h in repo.heads(start)]
2523 else:
2523 else:
2524 heads = []
2524 heads = []
2525 for b, ls in repo.branchmap().iteritems():
2525 for b, ls in repo.branchmap().iteritems():
2526 if start is None:
2526 if start is None:
2527 heads += [repo[h] for h in ls]
2527 heads += [repo[h] for h in ls]
2528 continue
2528 continue
2529 startrev = repo.changelog.rev(start)
2529 startrev = repo.changelog.rev(start)
2530 descendants = set(repo.changelog.descendants(startrev))
2530 descendants = set(repo.changelog.descendants(startrev))
2531 descendants.add(startrev)
2531 descendants.add(startrev)
2532 rev = repo.changelog.rev
2532 rev = repo.changelog.rev
2533 heads += [repo[h] for h in ls if rev(h) in descendants]
2533 heads += [repo[h] for h in ls if rev(h) in descendants]
2534
2534
2535 if branchrevs:
2535 if branchrevs:
2536 branches = set(repo[br].branch() for br in branchrevs)
2536 branches = set(repo[br].branch() for br in branchrevs)
2537 heads = [h for h in heads if h.branch() in branches]
2537 heads = [h for h in heads if h.branch() in branches]
2538
2538
2539 if not opts.get('closed'):
2539 if not opts.get('closed'):
2540 heads = [h for h in heads if not h.extra().get('close')]
2540 heads = [h for h in heads if not h.extra().get('close')]
2541
2541
2542 if opts.get('active') and branchrevs:
2542 if opts.get('active') and branchrevs:
2543 dagheads = repo.heads(start)
2543 dagheads = repo.heads(start)
2544 heads = [h for h in heads if h.node() in dagheads]
2544 heads = [h for h in heads if h.node() in dagheads]
2545
2545
2546 if branchrevs:
2546 if branchrevs:
2547 haveheads = set(h.branch() for h in heads)
2547 haveheads = set(h.branch() for h in heads)
2548 if branches - haveheads:
2548 if branches - haveheads:
2549 headless = ', '.join(b for b in branches - haveheads)
2549 headless = ', '.join(b for b in branches - haveheads)
2550 msg = _('no open branch heads found on branches %s')
2550 msg = _('no open branch heads found on branches %s')
2551 if opts.get('rev'):
2551 if opts.get('rev'):
2552 msg += _(' (started at %s)' % opts['rev'])
2552 msg += _(' (started at %s)' % opts['rev'])
2553 ui.warn((msg + '\n') % headless)
2553 ui.warn((msg + '\n') % headless)
2554
2554
2555 if not heads:
2555 if not heads:
2556 return 1
2556 return 1
2557
2557
2558 heads = sorted(heads, key=lambda x: -x.rev())
2558 heads = sorted(heads, key=lambda x: -x.rev())
2559 displayer = cmdutil.show_changeset(ui, repo, opts)
2559 displayer = cmdutil.show_changeset(ui, repo, opts)
2560 for ctx in heads:
2560 for ctx in heads:
2561 displayer.show(ctx)
2561 displayer.show(ctx)
2562 displayer.close()
2562 displayer.close()
2563
2563
2564 @command('help',
2564 @command('help',
2565 [('e', 'extension', None, _('show only help for extensions')),
2565 [('e', 'extension', None, _('show only help for extensions')),
2566 ('c', 'command', None, _('show only help for commands'))],
2566 ('c', 'command', None, _('show only help for commands'))],
2567 _('[-ec] [TOPIC]'))
2567 _('[-ec] [TOPIC]'))
2568 def help_(ui, name=None, with_version=False, unknowncmd=False, full=True, **opts):
2568 def help_(ui, name=None, with_version=False, unknowncmd=False, full=True, **opts):
2569 """show help for a given topic or a help overview
2569 """show help for a given topic or a help overview
2570
2570
2571 With no arguments, print a list of commands with short help messages.
2571 With no arguments, print a list of commands with short help messages.
2572
2572
2573 Given a topic, extension, or command name, print help for that
2573 Given a topic, extension, or command name, print help for that
2574 topic.
2574 topic.
2575
2575
2576 Returns 0 if successful.
2576 Returns 0 if successful.
2577 """
2577 """
2578 option_lists = []
2578 option_lists = []
2579 textwidth = min(ui.termwidth(), 80) - 2
2579 textwidth = min(ui.termwidth(), 80) - 2
2580
2580
2581 def addglobalopts(aliases):
2581 def addglobalopts(aliases):
2582 if ui.verbose:
2582 if ui.verbose:
2583 option_lists.append((_("global options:"), globalopts))
2583 option_lists.append((_("global options:"), globalopts))
2584 if name == 'shortlist':
2584 if name == 'shortlist':
2585 option_lists.append((_('use "hg help" for the full list '
2585 option_lists.append((_('use "hg help" for the full list '
2586 'of commands'), ()))
2586 'of commands'), ()))
2587 else:
2587 else:
2588 if name == 'shortlist':
2588 if name == 'shortlist':
2589 msg = _('use "hg help" for the full list of commands '
2589 msg = _('use "hg help" for the full list of commands '
2590 'or "hg -v" for details')
2590 'or "hg -v" for details')
2591 elif name and not full:
2591 elif name and not full:
2592 msg = _('use "hg help %s" to show the full help text' % name)
2592 msg = _('use "hg help %s" to show the full help text' % name)
2593 elif aliases:
2593 elif aliases:
2594 msg = _('use "hg -v help%s" to show builtin aliases and '
2594 msg = _('use "hg -v help%s" to show builtin aliases and '
2595 'global options') % (name and " " + name or "")
2595 'global options') % (name and " " + name or "")
2596 else:
2596 else:
2597 msg = _('use "hg -v help %s" to show global options') % name
2597 msg = _('use "hg -v help %s" to show global options') % name
2598 option_lists.append((msg, ()))
2598 option_lists.append((msg, ()))
2599
2599
2600 def helpcmd(name):
2600 def helpcmd(name):
2601 if with_version:
2601 if with_version:
2602 version_(ui)
2602 version_(ui)
2603 ui.write('\n')
2603 ui.write('\n')
2604
2604
2605 try:
2605 try:
2606 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
2606 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
2607 except error.AmbiguousCommand, inst:
2607 except error.AmbiguousCommand, inst:
2608 # py3k fix: except vars can't be used outside the scope of the
2608 # py3k fix: except vars can't be used outside the scope of the
2609 # except block, nor can be used inside a lambda. python issue4617
2609 # except block, nor can be used inside a lambda. python issue4617
2610 prefix = inst.args[0]
2610 prefix = inst.args[0]
2611 select = lambda c: c.lstrip('^').startswith(prefix)
2611 select = lambda c: c.lstrip('^').startswith(prefix)
2612 helplist(_('list of commands:\n\n'), select)
2612 helplist(_('list of commands:\n\n'), select)
2613 return
2613 return
2614
2614
2615 # check if it's an invalid alias and display its error if it is
2615 # check if it's an invalid alias and display its error if it is
2616 if getattr(entry[0], 'badalias', False):
2616 if getattr(entry[0], 'badalias', False):
2617 if not unknowncmd:
2617 if not unknowncmd:
2618 entry[0](ui)
2618 entry[0](ui)
2619 return
2619 return
2620
2620
2621 # synopsis
2621 # synopsis
2622 if len(entry) > 2:
2622 if len(entry) > 2:
2623 if entry[2].startswith('hg'):
2623 if entry[2].startswith('hg'):
2624 ui.write("%s\n" % entry[2])
2624 ui.write("%s\n" % entry[2])
2625 else:
2625 else:
2626 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
2626 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
2627 else:
2627 else:
2628 ui.write('hg %s\n' % aliases[0])
2628 ui.write('hg %s\n' % aliases[0])
2629
2629
2630 # aliases
2630 # aliases
2631 if full and not ui.quiet and len(aliases) > 1:
2631 if full and not ui.quiet and len(aliases) > 1:
2632 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
2632 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
2633
2633
2634 # description
2634 # description
2635 doc = gettext(entry[0].__doc__)
2635 doc = gettext(entry[0].__doc__)
2636 if not doc:
2636 if not doc:
2637 doc = _("(no help text available)")
2637 doc = _("(no help text available)")
2638 if hasattr(entry[0], 'definition'): # aliased command
2638 if hasattr(entry[0], 'definition'): # aliased command
2639 if entry[0].definition.startswith('!'): # shell alias
2639 if entry[0].definition.startswith('!'): # shell alias
2640 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
2640 doc = _('shell alias for::\n\n %s') % entry[0].definition[1:]
2641 else:
2641 else:
2642 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
2642 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
2643 if ui.quiet or not full:
2643 if ui.quiet or not full:
2644 doc = doc.splitlines()[0]
2644 doc = doc.splitlines()[0]
2645 keep = ui.verbose and ['verbose'] or []
2645 keep = ui.verbose and ['verbose'] or []
2646 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
2646 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
2647 ui.write("\n%s\n" % formatted)
2647 ui.write("\n%s\n" % formatted)
2648 if pruned:
2648 if pruned:
2649 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
2649 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
2650
2650
2651 if not ui.quiet:
2651 if not ui.quiet:
2652 # options
2652 # options
2653 if entry[1]:
2653 if entry[1]:
2654 option_lists.append((_("options:\n"), entry[1]))
2654 option_lists.append((_("options:\n"), entry[1]))
2655
2655
2656 addglobalopts(False)
2656 addglobalopts(False)
2657
2657
2658 # check if this command shadows a non-trivial (multi-line)
2658 # check if this command shadows a non-trivial (multi-line)
2659 # extension help text
2659 # extension help text
2660 try:
2660 try:
2661 mod = extensions.find(name)
2661 mod = extensions.find(name)
2662 doc = gettext(mod.__doc__) or ''
2662 doc = gettext(mod.__doc__) or ''
2663 if '\n' in doc.strip():
2663 if '\n' in doc.strip():
2664 msg = _('use "hg help -e %s" to show help for '
2664 msg = _('use "hg help -e %s" to show help for '
2665 'the %s extension') % (name, name)
2665 'the %s extension') % (name, name)
2666 ui.write('\n%s\n' % msg)
2666 ui.write('\n%s\n' % msg)
2667 except KeyError:
2667 except KeyError:
2668 pass
2668 pass
2669
2669
2670 def helplist(header, select=None):
2670 def helplist(header, select=None):
2671 h = {}
2671 h = {}
2672 cmds = {}
2672 cmds = {}
2673 for c, e in table.iteritems():
2673 for c, e in table.iteritems():
2674 f = c.split("|", 1)[0]
2674 f = c.split("|", 1)[0]
2675 if select and not select(f):
2675 if select and not select(f):
2676 continue
2676 continue
2677 if (not select and name != 'shortlist' and
2677 if (not select and name != 'shortlist' and
2678 e[0].__module__ != __name__):
2678 e[0].__module__ != __name__):
2679 continue
2679 continue
2680 if name == "shortlist" and not f.startswith("^"):
2680 if name == "shortlist" and not f.startswith("^"):
2681 continue
2681 continue
2682 f = f.lstrip("^")
2682 f = f.lstrip("^")
2683 if not ui.debugflag and f.startswith("debug"):
2683 if not ui.debugflag and f.startswith("debug"):
2684 continue
2684 continue
2685 doc = e[0].__doc__
2685 doc = e[0].__doc__
2686 if doc and 'DEPRECATED' in doc and not ui.verbose:
2686 if doc and 'DEPRECATED' in doc and not ui.verbose:
2687 continue
2687 continue
2688 doc = gettext(doc)
2688 doc = gettext(doc)
2689 if not doc:
2689 if not doc:
2690 doc = _("(no help text available)")
2690 doc = _("(no help text available)")
2691 h[f] = doc.splitlines()[0].rstrip()
2691 h[f] = doc.splitlines()[0].rstrip()
2692 cmds[f] = c.lstrip("^")
2692 cmds[f] = c.lstrip("^")
2693
2693
2694 if not h:
2694 if not h:
2695 ui.status(_('no commands defined\n'))
2695 ui.status(_('no commands defined\n'))
2696 return
2696 return
2697
2697
2698 ui.status(header)
2698 ui.status(header)
2699 fns = sorted(h)
2699 fns = sorted(h)
2700 m = max(map(len, fns))
2700 m = max(map(len, fns))
2701 for f in fns:
2701 for f in fns:
2702 if ui.verbose:
2702 if ui.verbose:
2703 commands = cmds[f].replace("|",", ")
2703 commands = cmds[f].replace("|",", ")
2704 ui.write(" %s:\n %s\n"%(commands, h[f]))
2704 ui.write(" %s:\n %s\n"%(commands, h[f]))
2705 else:
2705 else:
2706 ui.write('%s\n' % (util.wrap(h[f], textwidth,
2706 ui.write('%s\n' % (util.wrap(h[f], textwidth,
2707 initindent=' %-*s ' % (m, f),
2707 initindent=' %-*s ' % (m, f),
2708 hangindent=' ' * (m + 4))))
2708 hangindent=' ' * (m + 4))))
2709
2709
2710 if not ui.quiet:
2710 if not ui.quiet:
2711 addglobalopts(True)
2711 addglobalopts(True)
2712
2712
2713 def helptopic(name):
2713 def helptopic(name):
2714 for names, header, doc in help.helptable:
2714 for names, header, doc in help.helptable:
2715 if name in names:
2715 if name in names:
2716 break
2716 break
2717 else:
2717 else:
2718 raise error.UnknownCommand(name)
2718 raise error.UnknownCommand(name)
2719
2719
2720 # description
2720 # description
2721 if not doc:
2721 if not doc:
2722 doc = _("(no help text available)")
2722 doc = _("(no help text available)")
2723 if hasattr(doc, '__call__'):
2723 if hasattr(doc, '__call__'):
2724 doc = doc()
2724 doc = doc()
2725
2725
2726 ui.write("%s\n\n" % header)
2726 ui.write("%s\n\n" % header)
2727 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
2727 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
2728 try:
2728 try:
2729 cmdutil.findcmd(name, table)
2729 cmdutil.findcmd(name, table)
2730 ui.write(_('\nuse "hg help -c %s" to see help for '
2730 ui.write(_('\nuse "hg help -c %s" to see help for '
2731 'the %s command\n') % (name, name))
2731 'the %s command\n') % (name, name))
2732 except error.UnknownCommand:
2732 except error.UnknownCommand:
2733 pass
2733 pass
2734
2734
2735 def helpext(name):
2735 def helpext(name):
2736 try:
2736 try:
2737 mod = extensions.find(name)
2737 mod = extensions.find(name)
2738 doc = gettext(mod.__doc__) or _('no help text available')
2738 doc = gettext(mod.__doc__) or _('no help text available')
2739 except KeyError:
2739 except KeyError:
2740 mod = None
2740 mod = None
2741 doc = extensions.disabledext(name)
2741 doc = extensions.disabledext(name)
2742 if not doc:
2742 if not doc:
2743 raise error.UnknownCommand(name)
2743 raise error.UnknownCommand(name)
2744
2744
2745 if '\n' not in doc:
2745 if '\n' not in doc:
2746 head, tail = doc, ""
2746 head, tail = doc, ""
2747 else:
2747 else:
2748 head, tail = doc.split('\n', 1)
2748 head, tail = doc.split('\n', 1)
2749 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
2749 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
2750 if tail:
2750 if tail:
2751 ui.write(minirst.format(tail, textwidth))
2751 ui.write(minirst.format(tail, textwidth))
2752 ui.status('\n\n')
2752 ui.status('\n\n')
2753
2753
2754 if mod:
2754 if mod:
2755 try:
2755 try:
2756 ct = mod.cmdtable
2756 ct = mod.cmdtable
2757 except AttributeError:
2757 except AttributeError:
2758 ct = {}
2758 ct = {}
2759 modcmds = set([c.split('|', 1)[0] for c in ct])
2759 modcmds = set([c.split('|', 1)[0] for c in ct])
2760 helplist(_('list of commands:\n\n'), modcmds.__contains__)
2760 helplist(_('list of commands:\n\n'), modcmds.__contains__)
2761 else:
2761 else:
2762 ui.write(_('use "hg help extensions" for information on enabling '
2762 ui.write(_('use "hg help extensions" for information on enabling '
2763 'extensions\n'))
2763 'extensions\n'))
2764
2764
2765 def helpextcmd(name):
2765 def helpextcmd(name):
2766 cmd, ext, mod = extensions.disabledcmd(ui, name, ui.config('ui', 'strict'))
2766 cmd, ext, mod = extensions.disabledcmd(ui, name, ui.config('ui', 'strict'))
2767 doc = gettext(mod.__doc__).splitlines()[0]
2767 doc = gettext(mod.__doc__).splitlines()[0]
2768
2768
2769 msg = help.listexts(_("'%s' is provided by the following "
2769 msg = help.listexts(_("'%s' is provided by the following "
2770 "extension:") % cmd, {ext: doc}, indent=4)
2770 "extension:") % cmd, {ext: doc}, indent=4)
2771 ui.write(minirst.format(msg, textwidth))
2771 ui.write(minirst.format(msg, textwidth))
2772 ui.write('\n\n')
2772 ui.write('\n\n')
2773 ui.write(_('use "hg help extensions" for information on enabling '
2773 ui.write(_('use "hg help extensions" for information on enabling '
2774 'extensions\n'))
2774 'extensions\n'))
2775
2775
2776 if name and name != 'shortlist':
2776 if name and name != 'shortlist':
2777 i = None
2777 i = None
2778 if unknowncmd:
2778 if unknowncmd:
2779 queries = (helpextcmd,)
2779 queries = (helpextcmd,)
2780 elif opts.get('extension'):
2780 elif opts.get('extension'):
2781 queries = (helpext,)
2781 queries = (helpext,)
2782 elif opts.get('command'):
2782 elif opts.get('command'):
2783 queries = (helpcmd,)
2783 queries = (helpcmd,)
2784 else:
2784 else:
2785 queries = (helptopic, helpcmd, helpext, helpextcmd)
2785 queries = (helptopic, helpcmd, helpext, helpextcmd)
2786 for f in queries:
2786 for f in queries:
2787 try:
2787 try:
2788 f(name)
2788 f(name)
2789 i = None
2789 i = None
2790 break
2790 break
2791 except error.UnknownCommand, inst:
2791 except error.UnknownCommand, inst:
2792 i = inst
2792 i = inst
2793 if i:
2793 if i:
2794 raise i
2794 raise i
2795
2795
2796 else:
2796 else:
2797 # program name
2797 # program name
2798 if ui.verbose or with_version:
2798 if ui.verbose or with_version:
2799 version_(ui)
2799 version_(ui)
2800 else:
2800 else:
2801 ui.status(_("Mercurial Distributed SCM\n"))
2801 ui.status(_("Mercurial Distributed SCM\n"))
2802 ui.status('\n')
2802 ui.status('\n')
2803
2803
2804 # list of commands
2804 # list of commands
2805 if name == "shortlist":
2805 if name == "shortlist":
2806 header = _('basic commands:\n\n')
2806 header = _('basic commands:\n\n')
2807 else:
2807 else:
2808 header = _('list of commands:\n\n')
2808 header = _('list of commands:\n\n')
2809
2809
2810 helplist(header)
2810 helplist(header)
2811 if name != 'shortlist':
2811 if name != 'shortlist':
2812 text = help.listexts(_('enabled extensions:'), extensions.enabled())
2812 text = help.listexts(_('enabled extensions:'), extensions.enabled())
2813 if text:
2813 if text:
2814 ui.write("\n%s\n" % minirst.format(text, textwidth))
2814 ui.write("\n%s\n" % minirst.format(text, textwidth))
2815
2815
2816 # list all option lists
2816 # list all option lists
2817 opt_output = []
2817 opt_output = []
2818 multioccur = False
2818 multioccur = False
2819 for title, options in option_lists:
2819 for title, options in option_lists:
2820 opt_output.append(("\n%s" % title, None))
2820 opt_output.append(("\n%s" % title, None))
2821 for option in options:
2821 for option in options:
2822 if len(option) == 5:
2822 if len(option) == 5:
2823 shortopt, longopt, default, desc, optlabel = option
2823 shortopt, longopt, default, desc, optlabel = option
2824 else:
2824 else:
2825 shortopt, longopt, default, desc = option
2825 shortopt, longopt, default, desc = option
2826 optlabel = _("VALUE") # default label
2826 optlabel = _("VALUE") # default label
2827
2827
2828 if _("DEPRECATED") in desc and not ui.verbose:
2828 if _("DEPRECATED") in desc and not ui.verbose:
2829 continue
2829 continue
2830 if isinstance(default, list):
2830 if isinstance(default, list):
2831 numqualifier = " %s [+]" % optlabel
2831 numqualifier = " %s [+]" % optlabel
2832 multioccur = True
2832 multioccur = True
2833 elif (default is not None) and not isinstance(default, bool):
2833 elif (default is not None) and not isinstance(default, bool):
2834 numqualifier = " %s" % optlabel
2834 numqualifier = " %s" % optlabel
2835 else:
2835 else:
2836 numqualifier = ""
2836 numqualifier = ""
2837 opt_output.append(("%2s%s" %
2837 opt_output.append(("%2s%s" %
2838 (shortopt and "-%s" % shortopt,
2838 (shortopt and "-%s" % shortopt,
2839 longopt and " --%s%s" %
2839 longopt and " --%s%s" %
2840 (longopt, numqualifier)),
2840 (longopt, numqualifier)),
2841 "%s%s" % (desc,
2841 "%s%s" % (desc,
2842 default
2842 default
2843 and _(" (default: %s)") % default
2843 and _(" (default: %s)") % default
2844 or "")))
2844 or "")))
2845 if multioccur:
2845 if multioccur:
2846 msg = _("\n[+] marked option can be specified multiple times")
2846 msg = _("\n[+] marked option can be specified multiple times")
2847 if ui.verbose and name != 'shortlist':
2847 if ui.verbose and name != 'shortlist':
2848 opt_output.append((msg, None))
2848 opt_output.append((msg, None))
2849 else:
2849 else:
2850 opt_output.insert(-1, (msg, None))
2850 opt_output.insert(-1, (msg, None))
2851
2851
2852 if not name:
2852 if not name:
2853 ui.write(_("\nadditional help topics:\n\n"))
2853 ui.write(_("\nadditional help topics:\n\n"))
2854 topics = []
2854 topics = []
2855 for names, header, doc in help.helptable:
2855 for names, header, doc in help.helptable:
2856 topics.append((sorted(names, key=len, reverse=True)[0], header))
2856 topics.append((sorted(names, key=len, reverse=True)[0], header))
2857 topics_len = max([len(s[0]) for s in topics])
2857 topics_len = max([len(s[0]) for s in topics])
2858 for t, desc in topics:
2858 for t, desc in topics:
2859 ui.write(" %-*s %s\n" % (topics_len, t, desc))
2859 ui.write(" %-*s %s\n" % (topics_len, t, desc))
2860
2860
2861 if opt_output:
2861 if opt_output:
2862 colwidth = encoding.colwidth
2862 colwidth = encoding.colwidth
2863 # normalize: (opt or message, desc or None, width of opt)
2863 # normalize: (opt or message, desc or None, width of opt)
2864 entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0)
2864 entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0)
2865 for opt, desc in opt_output]
2865 for opt, desc in opt_output]
2866 hanging = max([e[2] for e in entries])
2866 hanging = max([e[2] for e in entries])
2867 for opt, desc, width in entries:
2867 for opt, desc, width in entries:
2868 if desc:
2868 if desc:
2869 initindent = ' %s%s ' % (opt, ' ' * (hanging - width))
2869 initindent = ' %s%s ' % (opt, ' ' * (hanging - width))
2870 hangindent = ' ' * (hanging + 3)
2870 hangindent = ' ' * (hanging + 3)
2871 ui.write('%s\n' % (util.wrap(desc, textwidth,
2871 ui.write('%s\n' % (util.wrap(desc, textwidth,
2872 initindent=initindent,
2872 initindent=initindent,
2873 hangindent=hangindent)))
2873 hangindent=hangindent)))
2874 else:
2874 else:
2875 ui.write("%s\n" % opt)
2875 ui.write("%s\n" % opt)
2876
2876
2877 @command('identify|id',
2877 @command('identify|id',
2878 [('r', 'rev', '',
2878 [('r', 'rev', '',
2879 _('identify the specified revision'), _('REV')),
2879 _('identify the specified revision'), _('REV')),
2880 ('n', 'num', None, _('show local revision number')),
2880 ('n', 'num', None, _('show local revision number')),
2881 ('i', 'id', None, _('show global revision id')),
2881 ('i', 'id', None, _('show global revision id')),
2882 ('b', 'branch', None, _('show branch')),
2882 ('b', 'branch', None, _('show branch')),
2883 ('t', 'tags', None, _('show tags')),
2883 ('t', 'tags', None, _('show tags')),
2884 ('B', 'bookmarks', None, _('show bookmarks'))],
2884 ('B', 'bookmarks', None, _('show bookmarks'))],
2885 _('[-nibtB] [-r REV] [SOURCE]'))
2885 _('[-nibtB] [-r REV] [SOURCE]'))
2886 def identify(ui, repo, source=None, rev=None,
2886 def identify(ui, repo, source=None, rev=None,
2887 num=None, id=None, branch=None, tags=None, bookmarks=None):
2887 num=None, id=None, branch=None, tags=None, bookmarks=None):
2888 """identify the working copy or specified revision
2888 """identify the working copy or specified revision
2889
2889
2890 Print a summary identifying the repository state at REV using one or
2890 Print a summary identifying the repository state at REV using one or
2891 two parent hash identifiers, followed by a "+" if the working
2891 two parent hash identifiers, followed by a "+" if the working
2892 directory has uncommitted changes, the branch name (if not default),
2892 directory has uncommitted changes, the branch name (if not default),
2893 a list of tags, and a list of bookmarks.
2893 a list of tags, and a list of bookmarks.
2894
2894
2895 When REV is not given, print a summary of the current state of the
2895 When REV is not given, print a summary of the current state of the
2896 repository.
2896 repository.
2897
2897
2898 Specifying a path to a repository root or Mercurial bundle will
2898 Specifying a path to a repository root or Mercurial bundle will
2899 cause lookup to operate on that repository/bundle.
2899 cause lookup to operate on that repository/bundle.
2900
2900
2901 Returns 0 if successful.
2901 Returns 0 if successful.
2902 """
2902 """
2903
2903
2904 if not repo and not source:
2904 if not repo and not source:
2905 raise util.Abort(_("there is no Mercurial repository here "
2905 raise util.Abort(_("there is no Mercurial repository here "
2906 "(.hg not found)"))
2906 "(.hg not found)"))
2907
2907
2908 hexfunc = ui.debugflag and hex or short
2908 hexfunc = ui.debugflag and hex or short
2909 default = not (num or id or branch or tags or bookmarks)
2909 default = not (num or id or branch or tags or bookmarks)
2910 output = []
2910 output = []
2911 revs = []
2911 revs = []
2912
2912
2913 if source:
2913 if source:
2914 source, branches = hg.parseurl(ui.expandpath(source))
2914 source, branches = hg.parseurl(ui.expandpath(source))
2915 repo = hg.repository(ui, source)
2915 repo = hg.repository(ui, source)
2916 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
2916 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
2917
2917
2918 if not repo.local():
2918 if not repo.local():
2919 if num or branch or tags:
2919 if num or branch or tags:
2920 raise util.Abort(
2920 raise util.Abort(
2921 _("can't query remote revision number, branch, or tags"))
2921 _("can't query remote revision number, branch, or tags"))
2922 if not rev and revs:
2922 if not rev and revs:
2923 rev = revs[0]
2923 rev = revs[0]
2924 if not rev:
2924 if not rev:
2925 rev = "tip"
2925 rev = "tip"
2926
2926
2927 remoterev = repo.lookup(rev)
2927 remoterev = repo.lookup(rev)
2928 if default or id:
2928 if default or id:
2929 output = [hexfunc(remoterev)]
2929 output = [hexfunc(remoterev)]
2930
2930
2931 def getbms():
2931 def getbms():
2932 bms = []
2932 bms = []
2933
2933
2934 if 'bookmarks' in repo.listkeys('namespaces'):
2934 if 'bookmarks' in repo.listkeys('namespaces'):
2935 hexremoterev = hex(remoterev)
2935 hexremoterev = hex(remoterev)
2936 bms = [bm for bm, bmr in repo.listkeys('bookmarks').iteritems()
2936 bms = [bm for bm, bmr in repo.listkeys('bookmarks').iteritems()
2937 if bmr == hexremoterev]
2937 if bmr == hexremoterev]
2938
2938
2939 return bms
2939 return bms
2940
2940
2941 if bookmarks:
2941 if bookmarks:
2942 output.extend(getbms())
2942 output.extend(getbms())
2943 elif default and not ui.quiet:
2943 elif default and not ui.quiet:
2944 # multiple bookmarks for a single parent separated by '/'
2944 # multiple bookmarks for a single parent separated by '/'
2945 bm = '/'.join(getbms())
2945 bm = '/'.join(getbms())
2946 if bm:
2946 if bm:
2947 output.append(bm)
2947 output.append(bm)
2948 else:
2948 else:
2949 if not rev:
2949 if not rev:
2950 ctx = repo[None]
2950 ctx = repo[None]
2951 parents = ctx.parents()
2951 parents = ctx.parents()
2952 changed = ""
2952 changed = ""
2953 if default or id or num:
2953 if default or id or num:
2954 changed = util.any(repo.status()) and "+" or ""
2954 changed = util.any(repo.status()) and "+" or ""
2955 if default or id:
2955 if default or id:
2956 output = ["%s%s" %
2956 output = ["%s%s" %
2957 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
2957 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
2958 if num:
2958 if num:
2959 output.append("%s%s" %
2959 output.append("%s%s" %
2960 ('+'.join([str(p.rev()) for p in parents]), changed))
2960 ('+'.join([str(p.rev()) for p in parents]), changed))
2961 else:
2961 else:
2962 ctx = scmutil.revsingle(repo, rev)
2962 ctx = scmutil.revsingle(repo, rev)
2963 if default or id:
2963 if default or id:
2964 output = [hexfunc(ctx.node())]
2964 output = [hexfunc(ctx.node())]
2965 if num:
2965 if num:
2966 output.append(str(ctx.rev()))
2966 output.append(str(ctx.rev()))
2967
2967
2968 if default and not ui.quiet:
2968 if default and not ui.quiet:
2969 b = ctx.branch()
2969 b = ctx.branch()
2970 if b != 'default':
2970 if b != 'default':
2971 output.append("(%s)" % b)
2971 output.append("(%s)" % b)
2972
2972
2973 # multiple tags for a single parent separated by '/'
2973 # multiple tags for a single parent separated by '/'
2974 t = '/'.join(ctx.tags())
2974 t = '/'.join(ctx.tags())
2975 if t:
2975 if t:
2976 output.append(t)
2976 output.append(t)
2977
2977
2978 # multiple bookmarks for a single parent separated by '/'
2978 # multiple bookmarks for a single parent separated by '/'
2979 bm = '/'.join(ctx.bookmarks())
2979 bm = '/'.join(ctx.bookmarks())
2980 if bm:
2980 if bm:
2981 output.append(bm)
2981 output.append(bm)
2982 else:
2982 else:
2983 if branch:
2983 if branch:
2984 output.append(ctx.branch())
2984 output.append(ctx.branch())
2985
2985
2986 if tags:
2986 if tags:
2987 output.extend(ctx.tags())
2987 output.extend(ctx.tags())
2988
2988
2989 if bookmarks:
2989 if bookmarks:
2990 output.extend(ctx.bookmarks())
2990 output.extend(ctx.bookmarks())
2991
2991
2992 ui.write("%s\n" % ' '.join(output))
2992 ui.write("%s\n" % ' '.join(output))
2993
2993
2994 @command('import|patch',
2994 @command('import|patch',
2995 [('p', 'strip', 1,
2995 [('p', 'strip', 1,
2996 _('directory strip option for patch. This has the same '
2996 _('directory strip option for patch. This has the same '
2997 'meaning as the corresponding patch option'), _('NUM')),
2997 'meaning as the corresponding patch option'), _('NUM')),
2998 ('b', 'base', '', _('base path'), _('PATH')),
2998 ('b', 'base', '', _('base path'), _('PATH')),
2999 ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
2999 ('f', 'force', None, _('skip check for outstanding uncommitted changes')),
3000 ('', 'no-commit', None,
3000 ('', 'no-commit', None,
3001 _("don't commit, just update the working directory")),
3001 _("don't commit, just update the working directory")),
3002 ('', 'exact', None,
3002 ('', 'exact', None,
3003 _('apply patch to the nodes from which it was generated')),
3003 _('apply patch to the nodes from which it was generated')),
3004 ('', 'import-branch', None,
3004 ('', 'import-branch', None,
3005 _('use any branch information in patch (implied by --exact)'))] +
3005 _('use any branch information in patch (implied by --exact)'))] +
3006 commitopts + commitopts2 + similarityopts,
3006 commitopts + commitopts2 + similarityopts,
3007 _('[OPTION]... PATCH...'))
3007 _('[OPTION]... PATCH...'))
3008 def import_(ui, repo, patch1, *patches, **opts):
3008 def import_(ui, repo, patch1, *patches, **opts):
3009 """import an ordered set of patches
3009 """import an ordered set of patches
3010
3010
3011 Import a list of patches and commit them individually (unless
3011 Import a list of patches and commit them individually (unless
3012 --no-commit is specified).
3012 --no-commit is specified).
3013
3013
3014 If there are outstanding changes in the working directory, import
3014 If there are outstanding changes in the working directory, import
3015 will abort unless given the -f/--force flag.
3015 will abort unless given the -f/--force flag.
3016
3016
3017 You can import a patch straight from a mail message. Even patches
3017 You can import a patch straight from a mail message. Even patches
3018 as attachments work (to use the body part, it must have type
3018 as attachments work (to use the body part, it must have type
3019 text/plain or text/x-patch). From and Subject headers of email
3019 text/plain or text/x-patch). From and Subject headers of email
3020 message are used as default committer and commit message. All
3020 message are used as default committer and commit message. All
3021 text/plain body parts before first diff are added to commit
3021 text/plain body parts before first diff are added to commit
3022 message.
3022 message.
3023
3023
3024 If the imported patch was generated by :hg:`export`, user and
3024 If the imported patch was generated by :hg:`export`, user and
3025 description from patch override values from message headers and
3025 description from patch override values from message headers and
3026 body. Values given on command line with -m/--message and -u/--user
3026 body. Values given on command line with -m/--message and -u/--user
3027 override these.
3027 override these.
3028
3028
3029 If --exact is specified, import will set the working directory to
3029 If --exact is specified, import will set the working directory to
3030 the parent of each patch before applying it, and will abort if the
3030 the parent of each patch before applying it, and will abort if the
3031 resulting changeset has a different ID than the one recorded in
3031 resulting changeset has a different ID than the one recorded in
3032 the patch. This may happen due to character set problems or other
3032 the patch. This may happen due to character set problems or other
3033 deficiencies in the text patch format.
3033 deficiencies in the text patch format.
3034
3034
3035 With -s/--similarity, hg will attempt to discover renames and
3035 With -s/--similarity, hg will attempt to discover renames and
3036 copies in the patch in the same way as 'addremove'.
3036 copies in the patch in the same way as 'addremove'.
3037
3037
3038 To read a patch from standard input, use "-" as the patch name. If
3038 To read a patch from standard input, use "-" as the patch name. If
3039 a URL is specified, the patch will be downloaded from it.
3039 a URL is specified, the patch will be downloaded from it.
3040 See :hg:`help dates` for a list of formats valid for -d/--date.
3040 See :hg:`help dates` for a list of formats valid for -d/--date.
3041
3041
3042 Returns 0 on success.
3042 Returns 0 on success.
3043 """
3043 """
3044 patches = (patch1,) + patches
3044 patches = (patch1,) + patches
3045
3045
3046 date = opts.get('date')
3046 date = opts.get('date')
3047 if date:
3047 if date:
3048 opts['date'] = util.parsedate(date)
3048 opts['date'] = util.parsedate(date)
3049
3049
3050 try:
3050 try:
3051 sim = float(opts.get('similarity') or 0)
3051 sim = float(opts.get('similarity') or 0)
3052 except ValueError:
3052 except ValueError:
3053 raise util.Abort(_('similarity must be a number'))
3053 raise util.Abort(_('similarity must be a number'))
3054 if sim < 0 or sim > 100:
3054 if sim < 0 or sim > 100:
3055 raise util.Abort(_('similarity must be between 0 and 100'))
3055 raise util.Abort(_('similarity must be between 0 and 100'))
3056
3056
3057 if opts.get('exact') or not opts.get('force'):
3057 if opts.get('exact') or not opts.get('force'):
3058 cmdutil.bailifchanged(repo)
3058 cmdutil.bailifchanged(repo)
3059
3059
3060 d = opts["base"]
3060 d = opts["base"]
3061 strip = opts["strip"]
3061 strip = opts["strip"]
3062 wlock = lock = None
3062 wlock = lock = None
3063 msgs = []
3063 msgs = []
3064
3064
3065 def tryone(ui, hunk):
3065 def tryone(ui, hunk):
3066 tmpname, message, user, date, branch, nodeid, p1, p2 = \
3066 tmpname, message, user, date, branch, nodeid, p1, p2 = \
3067 patch.extract(ui, hunk)
3067 patch.extract(ui, hunk)
3068
3068
3069 if not tmpname:
3069 if not tmpname:
3070 return None
3070 return None
3071 commitid = _('to working directory')
3071 commitid = _('to working directory')
3072
3072
3073 try:
3073 try:
3074 cmdline_message = cmdutil.logmessage(opts)
3074 cmdline_message = cmdutil.logmessage(opts)
3075 if cmdline_message:
3075 if cmdline_message:
3076 # pickup the cmdline msg
3076 # pickup the cmdline msg
3077 message = cmdline_message
3077 message = cmdline_message
3078 elif message:
3078 elif message:
3079 # pickup the patch msg
3079 # pickup the patch msg
3080 message = message.strip()
3080 message = message.strip()
3081 else:
3081 else:
3082 # launch the editor
3082 # launch the editor
3083 message = None
3083 message = None
3084 ui.debug('message:\n%s\n' % message)
3084 ui.debug('message:\n%s\n' % message)
3085
3085
3086 wp = repo.parents()
3086 wp = repo.parents()
3087 if opts.get('exact'):
3087 if opts.get('exact'):
3088 if not nodeid or not p1:
3088 if not nodeid or not p1:
3089 raise util.Abort(_('not a Mercurial patch'))
3089 raise util.Abort(_('not a Mercurial patch'))
3090 p1 = repo.lookup(p1)
3090 p1 = repo.lookup(p1)
3091 p2 = repo.lookup(p2 or hex(nullid))
3091 p2 = repo.lookup(p2 or hex(nullid))
3092
3092
3093 if p1 != wp[0].node():
3093 if p1 != wp[0].node():
3094 hg.clean(repo, p1)
3094 hg.clean(repo, p1)
3095 repo.dirstate.setparents(p1, p2)
3095 repo.dirstate.setparents(p1, p2)
3096 elif p2:
3096 elif p2:
3097 try:
3097 try:
3098 p1 = repo.lookup(p1)
3098 p1 = repo.lookup(p1)
3099 p2 = repo.lookup(p2)
3099 p2 = repo.lookup(p2)
3100 if p1 == wp[0].node():
3100 if p1 == wp[0].node():
3101 repo.dirstate.setparents(p1, p2)
3101 repo.dirstate.setparents(p1, p2)
3102 except error.RepoError:
3102 except error.RepoError:
3103 pass
3103 pass
3104 if opts.get('exact') or opts.get('import_branch'):
3104 if opts.get('exact') or opts.get('import_branch'):
3105 repo.dirstate.setbranch(branch or 'default')
3105 repo.dirstate.setbranch(branch or 'default')
3106
3106
3107 files = {}
3107 files = {}
3108 patch.patch(ui, repo, tmpname, strip=strip, cwd=repo.root,
3108 patch.patch(ui, repo, tmpname, strip=strip, files=files,
3109 files=files, eolmode=None, similarity=sim / 100.0)
3109 eolmode=None, similarity=sim / 100.0)
3110 files = list(files)
3110 files = list(files)
3111 if opts.get('no_commit'):
3111 if opts.get('no_commit'):
3112 if message:
3112 if message:
3113 msgs.append(message)
3113 msgs.append(message)
3114 else:
3114 else:
3115 if opts.get('exact'):
3115 if opts.get('exact'):
3116 m = None
3116 m = None
3117 else:
3117 else:
3118 m = scmutil.matchfiles(repo, files or [])
3118 m = scmutil.matchfiles(repo, files or [])
3119 n = repo.commit(message, opts.get('user') or user,
3119 n = repo.commit(message, opts.get('user') or user,
3120 opts.get('date') or date, match=m,
3120 opts.get('date') or date, match=m,
3121 editor=cmdutil.commiteditor)
3121 editor=cmdutil.commiteditor)
3122 if opts.get('exact'):
3122 if opts.get('exact'):
3123 if hex(n) != nodeid:
3123 if hex(n) != nodeid:
3124 repo.rollback()
3124 repo.rollback()
3125 raise util.Abort(_('patch is damaged'
3125 raise util.Abort(_('patch is damaged'
3126 ' or loses information'))
3126 ' or loses information'))
3127 # Force a dirstate write so that the next transaction
3127 # Force a dirstate write so that the next transaction
3128 # backups an up-do-date file.
3128 # backups an up-do-date file.
3129 repo.dirstate.write()
3129 repo.dirstate.write()
3130 if n:
3130 if n:
3131 commitid = short(n)
3131 commitid = short(n)
3132
3132
3133 return commitid
3133 return commitid
3134 finally:
3134 finally:
3135 os.unlink(tmpname)
3135 os.unlink(tmpname)
3136
3136
3137 try:
3137 try:
3138 wlock = repo.wlock()
3138 wlock = repo.wlock()
3139 lock = repo.lock()
3139 lock = repo.lock()
3140 lastcommit = None
3140 lastcommit = None
3141 for p in patches:
3141 for p in patches:
3142 pf = os.path.join(d, p)
3142 pf = os.path.join(d, p)
3143
3143
3144 if pf == '-':
3144 if pf == '-':
3145 ui.status(_("applying patch from stdin\n"))
3145 ui.status(_("applying patch from stdin\n"))
3146 pf = sys.stdin
3146 pf = sys.stdin
3147 else:
3147 else:
3148 ui.status(_("applying %s\n") % p)
3148 ui.status(_("applying %s\n") % p)
3149 pf = url.open(ui, pf)
3149 pf = url.open(ui, pf)
3150
3150
3151 haspatch = False
3151 haspatch = False
3152 for hunk in patch.split(pf):
3152 for hunk in patch.split(pf):
3153 commitid = tryone(ui, hunk)
3153 commitid = tryone(ui, hunk)
3154 if commitid:
3154 if commitid:
3155 haspatch = True
3155 haspatch = True
3156 if lastcommit:
3156 if lastcommit:
3157 ui.status(_('applied %s\n') % lastcommit)
3157 ui.status(_('applied %s\n') % lastcommit)
3158 lastcommit = commitid
3158 lastcommit = commitid
3159
3159
3160 if not haspatch:
3160 if not haspatch:
3161 raise util.Abort(_('no diffs found'))
3161 raise util.Abort(_('no diffs found'))
3162
3162
3163 if msgs:
3163 if msgs:
3164 repo.opener.write('last-message.txt', '\n* * *\n'.join(msgs))
3164 repo.opener.write('last-message.txt', '\n* * *\n'.join(msgs))
3165 finally:
3165 finally:
3166 release(lock, wlock)
3166 release(lock, wlock)
3167
3167
3168 @command('incoming|in',
3168 @command('incoming|in',
3169 [('f', 'force', None,
3169 [('f', 'force', None,
3170 _('run even if remote repository is unrelated')),
3170 _('run even if remote repository is unrelated')),
3171 ('n', 'newest-first', None, _('show newest record first')),
3171 ('n', 'newest-first', None, _('show newest record first')),
3172 ('', 'bundle', '',
3172 ('', 'bundle', '',
3173 _('file to store the bundles into'), _('FILE')),
3173 _('file to store the bundles into'), _('FILE')),
3174 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3174 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3175 ('B', 'bookmarks', False, _("compare bookmarks")),
3175 ('B', 'bookmarks', False, _("compare bookmarks")),
3176 ('b', 'branch', [],
3176 ('b', 'branch', [],
3177 _('a specific branch you would like to pull'), _('BRANCH')),
3177 _('a specific branch you would like to pull'), _('BRANCH')),
3178 ] + logopts + remoteopts + subrepoopts,
3178 ] + logopts + remoteopts + subrepoopts,
3179 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3179 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3180 def incoming(ui, repo, source="default", **opts):
3180 def incoming(ui, repo, source="default", **opts):
3181 """show new changesets found in source
3181 """show new changesets found in source
3182
3182
3183 Show new changesets found in the specified path/URL or the default
3183 Show new changesets found in the specified path/URL or the default
3184 pull location. These are the changesets that would have been pulled
3184 pull location. These are the changesets that would have been pulled
3185 if a pull at the time you issued this command.
3185 if a pull at the time you issued this command.
3186
3186
3187 For remote repository, using --bundle avoids downloading the
3187 For remote repository, using --bundle avoids downloading the
3188 changesets twice if the incoming is followed by a pull.
3188 changesets twice if the incoming is followed by a pull.
3189
3189
3190 See pull for valid source format details.
3190 See pull for valid source format details.
3191
3191
3192 Returns 0 if there are incoming changes, 1 otherwise.
3192 Returns 0 if there are incoming changes, 1 otherwise.
3193 """
3193 """
3194 if opts.get('bundle') and opts.get('subrepos'):
3194 if opts.get('bundle') and opts.get('subrepos'):
3195 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3195 raise util.Abort(_('cannot combine --bundle and --subrepos'))
3196
3196
3197 if opts.get('bookmarks'):
3197 if opts.get('bookmarks'):
3198 source, branches = hg.parseurl(ui.expandpath(source),
3198 source, branches = hg.parseurl(ui.expandpath(source),
3199 opts.get('branch'))
3199 opts.get('branch'))
3200 other = hg.repository(hg.remoteui(repo, opts), source)
3200 other = hg.repository(hg.remoteui(repo, opts), source)
3201 if 'bookmarks' not in other.listkeys('namespaces'):
3201 if 'bookmarks' not in other.listkeys('namespaces'):
3202 ui.warn(_("remote doesn't support bookmarks\n"))
3202 ui.warn(_("remote doesn't support bookmarks\n"))
3203 return 0
3203 return 0
3204 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3204 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3205 return bookmarks.diff(ui, repo, other)
3205 return bookmarks.diff(ui, repo, other)
3206
3206
3207 repo._subtoppath = ui.expandpath(source)
3207 repo._subtoppath = ui.expandpath(source)
3208 try:
3208 try:
3209 return hg.incoming(ui, repo, source, opts)
3209 return hg.incoming(ui, repo, source, opts)
3210 finally:
3210 finally:
3211 del repo._subtoppath
3211 del repo._subtoppath
3212
3212
3213
3213
3214 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
3214 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'))
3215 def init(ui, dest=".", **opts):
3215 def init(ui, dest=".", **opts):
3216 """create a new repository in the given directory
3216 """create a new repository in the given directory
3217
3217
3218 Initialize a new repository in the given directory. If the given
3218 Initialize a new repository in the given directory. If the given
3219 directory does not exist, it will be created.
3219 directory does not exist, it will be created.
3220
3220
3221 If no directory is given, the current directory is used.
3221 If no directory is given, the current directory is used.
3222
3222
3223 It is possible to specify an ``ssh://`` URL as the destination.
3223 It is possible to specify an ``ssh://`` URL as the destination.
3224 See :hg:`help urls` for more information.
3224 See :hg:`help urls` for more information.
3225
3225
3226 Returns 0 on success.
3226 Returns 0 on success.
3227 """
3227 """
3228 hg.repository(hg.remoteui(ui, opts), ui.expandpath(dest), create=True)
3228 hg.repository(hg.remoteui(ui, opts), ui.expandpath(dest), create=True)
3229
3229
3230 @command('locate',
3230 @command('locate',
3231 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3231 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3232 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3232 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3233 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3233 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3234 ] + walkopts,
3234 ] + walkopts,
3235 _('[OPTION]... [PATTERN]...'))
3235 _('[OPTION]... [PATTERN]...'))
3236 def locate(ui, repo, *pats, **opts):
3236 def locate(ui, repo, *pats, **opts):
3237 """locate files matching specific patterns
3237 """locate files matching specific patterns
3238
3238
3239 Print files under Mercurial control in the working directory whose
3239 Print files under Mercurial control in the working directory whose
3240 names match the given patterns.
3240 names match the given patterns.
3241
3241
3242 By default, this command searches all directories in the working
3242 By default, this command searches all directories in the working
3243 directory. To search just the current directory and its
3243 directory. To search just the current directory and its
3244 subdirectories, use "--include .".
3244 subdirectories, use "--include .".
3245
3245
3246 If no patterns are given to match, this command prints the names
3246 If no patterns are given to match, this command prints the names
3247 of all files under Mercurial control in the working directory.
3247 of all files under Mercurial control in the working directory.
3248
3248
3249 If you want to feed the output of this command into the "xargs"
3249 If you want to feed the output of this command into the "xargs"
3250 command, use the -0 option to both this command and "xargs". This
3250 command, use the -0 option to both this command and "xargs". This
3251 will avoid the problem of "xargs" treating single filenames that
3251 will avoid the problem of "xargs" treating single filenames that
3252 contain whitespace as multiple filenames.
3252 contain whitespace as multiple filenames.
3253
3253
3254 Returns 0 if a match is found, 1 otherwise.
3254 Returns 0 if a match is found, 1 otherwise.
3255 """
3255 """
3256 end = opts.get('print0') and '\0' or '\n'
3256 end = opts.get('print0') and '\0' or '\n'
3257 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3257 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3258
3258
3259 ret = 1
3259 ret = 1
3260 m = scmutil.match(repo, pats, opts, default='relglob')
3260 m = scmutil.match(repo, pats, opts, default='relglob')
3261 m.bad = lambda x, y: False
3261 m.bad = lambda x, y: False
3262 for abs in repo[rev].walk(m):
3262 for abs in repo[rev].walk(m):
3263 if not rev and abs not in repo.dirstate:
3263 if not rev and abs not in repo.dirstate:
3264 continue
3264 continue
3265 if opts.get('fullpath'):
3265 if opts.get('fullpath'):
3266 ui.write(repo.wjoin(abs), end)
3266 ui.write(repo.wjoin(abs), end)
3267 else:
3267 else:
3268 ui.write(((pats and m.rel(abs)) or abs), end)
3268 ui.write(((pats and m.rel(abs)) or abs), end)
3269 ret = 0
3269 ret = 0
3270
3270
3271 return ret
3271 return ret
3272
3272
3273 @command('^log|history',
3273 @command('^log|history',
3274 [('f', 'follow', None,
3274 [('f', 'follow', None,
3275 _('follow changeset history, or file history across copies and renames')),
3275 _('follow changeset history, or file history across copies and renames')),
3276 ('', 'follow-first', None,
3276 ('', 'follow-first', None,
3277 _('only follow the first parent of merge changesets')),
3277 _('only follow the first parent of merge changesets')),
3278 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3278 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3279 ('C', 'copies', None, _('show copied files')),
3279 ('C', 'copies', None, _('show copied files')),
3280 ('k', 'keyword', [],
3280 ('k', 'keyword', [],
3281 _('do case-insensitive search for a given text'), _('TEXT')),
3281 _('do case-insensitive search for a given text'), _('TEXT')),
3282 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
3282 ('r', 'rev', [], _('show the specified revision or range'), _('REV')),
3283 ('', 'removed', None, _('include revisions where files were removed')),
3283 ('', 'removed', None, _('include revisions where files were removed')),
3284 ('m', 'only-merges', None, _('show only merges')),
3284 ('m', 'only-merges', None, _('show only merges')),
3285 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3285 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3286 ('', 'only-branch', [],
3286 ('', 'only-branch', [],
3287 _('show only changesets within the given named branch (DEPRECATED)'),
3287 _('show only changesets within the given named branch (DEPRECATED)'),
3288 _('BRANCH')),
3288 _('BRANCH')),
3289 ('b', 'branch', [],
3289 ('b', 'branch', [],
3290 _('show changesets within the given named branch'), _('BRANCH')),
3290 _('show changesets within the given named branch'), _('BRANCH')),
3291 ('P', 'prune', [],
3291 ('P', 'prune', [],
3292 _('do not display revision or any of its ancestors'), _('REV')),
3292 _('do not display revision or any of its ancestors'), _('REV')),
3293 ] + logopts + walkopts,
3293 ] + logopts + walkopts,
3294 _('[OPTION]... [FILE]'))
3294 _('[OPTION]... [FILE]'))
3295 def log(ui, repo, *pats, **opts):
3295 def log(ui, repo, *pats, **opts):
3296 """show revision history of entire repository or files
3296 """show revision history of entire repository or files
3297
3297
3298 Print the revision history of the specified files or the entire
3298 Print the revision history of the specified files or the entire
3299 project.
3299 project.
3300
3300
3301 File history is shown without following rename or copy history of
3301 File history is shown without following rename or copy history of
3302 files. Use -f/--follow with a filename to follow history across
3302 files. Use -f/--follow with a filename to follow history across
3303 renames and copies. --follow without a filename will only show
3303 renames and copies. --follow without a filename will only show
3304 ancestors or descendants of the starting revision. --follow-first
3304 ancestors or descendants of the starting revision. --follow-first
3305 only follows the first parent of merge revisions.
3305 only follows the first parent of merge revisions.
3306
3306
3307 If no revision range is specified, the default is ``tip:0`` unless
3307 If no revision range is specified, the default is ``tip:0`` unless
3308 --follow is set, in which case the working directory parent is
3308 --follow is set, in which case the working directory parent is
3309 used as the starting revision. You can specify a revision set for
3309 used as the starting revision. You can specify a revision set for
3310 log, see :hg:`help revsets` for more information.
3310 log, see :hg:`help revsets` for more information.
3311
3311
3312 See :hg:`help dates` for a list of formats valid for -d/--date.
3312 See :hg:`help dates` for a list of formats valid for -d/--date.
3313
3313
3314 By default this command prints revision number and changeset id,
3314 By default this command prints revision number and changeset id,
3315 tags, non-trivial parents, user, date and time, and a summary for
3315 tags, non-trivial parents, user, date and time, and a summary for
3316 each commit. When the -v/--verbose switch is used, the list of
3316 each commit. When the -v/--verbose switch is used, the list of
3317 changed files and full commit message are shown.
3317 changed files and full commit message are shown.
3318
3318
3319 .. note::
3319 .. note::
3320 log -p/--patch may generate unexpected diff output for merge
3320 log -p/--patch may generate unexpected diff output for merge
3321 changesets, as it will only compare the merge changeset against
3321 changesets, as it will only compare the merge changeset against
3322 its first parent. Also, only files different from BOTH parents
3322 its first parent. Also, only files different from BOTH parents
3323 will appear in files:.
3323 will appear in files:.
3324
3324
3325 Returns 0 on success.
3325 Returns 0 on success.
3326 """
3326 """
3327
3327
3328 matchfn = scmutil.match(repo, pats, opts)
3328 matchfn = scmutil.match(repo, pats, opts)
3329 limit = cmdutil.loglimit(opts)
3329 limit = cmdutil.loglimit(opts)
3330 count = 0
3330 count = 0
3331
3331
3332 endrev = None
3332 endrev = None
3333 if opts.get('copies') and opts.get('rev'):
3333 if opts.get('copies') and opts.get('rev'):
3334 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
3334 endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1
3335
3335
3336 df = False
3336 df = False
3337 if opts["date"]:
3337 if opts["date"]:
3338 df = util.matchdate(opts["date"])
3338 df = util.matchdate(opts["date"])
3339
3339
3340 branches = opts.get('branch', []) + opts.get('only_branch', [])
3340 branches = opts.get('branch', []) + opts.get('only_branch', [])
3341 opts['branch'] = [repo.lookupbranch(b) for b in branches]
3341 opts['branch'] = [repo.lookupbranch(b) for b in branches]
3342
3342
3343 displayer = cmdutil.show_changeset(ui, repo, opts, True)
3343 displayer = cmdutil.show_changeset(ui, repo, opts, True)
3344 def prep(ctx, fns):
3344 def prep(ctx, fns):
3345 rev = ctx.rev()
3345 rev = ctx.rev()
3346 parents = [p for p in repo.changelog.parentrevs(rev)
3346 parents = [p for p in repo.changelog.parentrevs(rev)
3347 if p != nullrev]
3347 if p != nullrev]
3348 if opts.get('no_merges') and len(parents) == 2:
3348 if opts.get('no_merges') and len(parents) == 2:
3349 return
3349 return
3350 if opts.get('only_merges') and len(parents) != 2:
3350 if opts.get('only_merges') and len(parents) != 2:
3351 return
3351 return
3352 if opts.get('branch') and ctx.branch() not in opts['branch']:
3352 if opts.get('branch') and ctx.branch() not in opts['branch']:
3353 return
3353 return
3354 if df and not df(ctx.date()[0]):
3354 if df and not df(ctx.date()[0]):
3355 return
3355 return
3356 if opts['user'] and not [k for k in opts['user']
3356 if opts['user'] and not [k for k in opts['user']
3357 if k.lower() in ctx.user().lower()]:
3357 if k.lower() in ctx.user().lower()]:
3358 return
3358 return
3359 if opts.get('keyword'):
3359 if opts.get('keyword'):
3360 for k in [kw.lower() for kw in opts['keyword']]:
3360 for k in [kw.lower() for kw in opts['keyword']]:
3361 if (k in ctx.user().lower() or
3361 if (k in ctx.user().lower() or
3362 k in ctx.description().lower() or
3362 k in ctx.description().lower() or
3363 k in " ".join(ctx.files()).lower()):
3363 k in " ".join(ctx.files()).lower()):
3364 break
3364 break
3365 else:
3365 else:
3366 return
3366 return
3367
3367
3368 copies = None
3368 copies = None
3369 if opts.get('copies') and rev:
3369 if opts.get('copies') and rev:
3370 copies = []
3370 copies = []
3371 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3371 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3372 for fn in ctx.files():
3372 for fn in ctx.files():
3373 rename = getrenamed(fn, rev)
3373 rename = getrenamed(fn, rev)
3374 if rename:
3374 if rename:
3375 copies.append((fn, rename[0]))
3375 copies.append((fn, rename[0]))
3376
3376
3377 revmatchfn = None
3377 revmatchfn = None
3378 if opts.get('patch') or opts.get('stat'):
3378 if opts.get('patch') or opts.get('stat'):
3379 if opts.get('follow') or opts.get('follow_first'):
3379 if opts.get('follow') or opts.get('follow_first'):
3380 # note: this might be wrong when following through merges
3380 # note: this might be wrong when following through merges
3381 revmatchfn = scmutil.match(repo, fns, default='path')
3381 revmatchfn = scmutil.match(repo, fns, default='path')
3382 else:
3382 else:
3383 revmatchfn = matchfn
3383 revmatchfn = matchfn
3384
3384
3385 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
3385 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
3386
3386
3387 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3387 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
3388 if count == limit:
3388 if count == limit:
3389 break
3389 break
3390 if displayer.flush(ctx.rev()):
3390 if displayer.flush(ctx.rev()):
3391 count += 1
3391 count += 1
3392 displayer.close()
3392 displayer.close()
3393
3393
3394 @command('manifest',
3394 @command('manifest',
3395 [('r', 'rev', '', _('revision to display'), _('REV'))],
3395 [('r', 'rev', '', _('revision to display'), _('REV'))],
3396 _('[-r REV]'))
3396 _('[-r REV]'))
3397 def manifest(ui, repo, node=None, rev=None):
3397 def manifest(ui, repo, node=None, rev=None):
3398 """output the current or given revision of the project manifest
3398 """output the current or given revision of the project manifest
3399
3399
3400 Print a list of version controlled files for the given revision.
3400 Print a list of version controlled files for the given revision.
3401 If no revision is given, the first parent of the working directory
3401 If no revision is given, the first parent of the working directory
3402 is used, or the null revision if no revision is checked out.
3402 is used, or the null revision if no revision is checked out.
3403
3403
3404 With -v, print file permissions, symlink and executable bits.
3404 With -v, print file permissions, symlink and executable bits.
3405 With --debug, print file revision hashes.
3405 With --debug, print file revision hashes.
3406
3406
3407 Returns 0 on success.
3407 Returns 0 on success.
3408 """
3408 """
3409
3409
3410 if rev and node:
3410 if rev and node:
3411 raise util.Abort(_("please specify just one revision"))
3411 raise util.Abort(_("please specify just one revision"))
3412
3412
3413 if not node:
3413 if not node:
3414 node = rev
3414 node = rev
3415
3415
3416 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
3416 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
3417 ctx = scmutil.revsingle(repo, node)
3417 ctx = scmutil.revsingle(repo, node)
3418 for f in ctx:
3418 for f in ctx:
3419 if ui.debugflag:
3419 if ui.debugflag:
3420 ui.write("%40s " % hex(ctx.manifest()[f]))
3420 ui.write("%40s " % hex(ctx.manifest()[f]))
3421 if ui.verbose:
3421 if ui.verbose:
3422 ui.write(decor[ctx.flags(f)])
3422 ui.write(decor[ctx.flags(f)])
3423 ui.write("%s\n" % f)
3423 ui.write("%s\n" % f)
3424
3424
3425 @command('^merge',
3425 @command('^merge',
3426 [('f', 'force', None, _('force a merge with outstanding changes')),
3426 [('f', 'force', None, _('force a merge with outstanding changes')),
3427 ('t', 'tool', '', _('specify merge tool')),
3427 ('t', 'tool', '', _('specify merge tool')),
3428 ('r', 'rev', '', _('revision to merge'), _('REV')),
3428 ('r', 'rev', '', _('revision to merge'), _('REV')),
3429 ('P', 'preview', None,
3429 ('P', 'preview', None,
3430 _('review revisions to merge (no merge is performed)'))],
3430 _('review revisions to merge (no merge is performed)'))],
3431 _('[-P] [-f] [[-r] REV]'))
3431 _('[-P] [-f] [[-r] REV]'))
3432 def merge(ui, repo, node=None, **opts):
3432 def merge(ui, repo, node=None, **opts):
3433 """merge working directory with another revision
3433 """merge working directory with another revision
3434
3434
3435 The current working directory is updated with all changes made in
3435 The current working directory is updated with all changes made in
3436 the requested revision since the last common predecessor revision.
3436 the requested revision since the last common predecessor revision.
3437
3437
3438 Files that changed between either parent are marked as changed for
3438 Files that changed between either parent are marked as changed for
3439 the next commit and a commit must be performed before any further
3439 the next commit and a commit must be performed before any further
3440 updates to the repository are allowed. The next commit will have
3440 updates to the repository are allowed. The next commit will have
3441 two parents.
3441 two parents.
3442
3442
3443 ``--tool`` can be used to specify the merge tool used for file
3443 ``--tool`` can be used to specify the merge tool used for file
3444 merges. It overrides the HGMERGE environment variable and your
3444 merges. It overrides the HGMERGE environment variable and your
3445 configuration files. See :hg:`help merge-tools` for options.
3445 configuration files. See :hg:`help merge-tools` for options.
3446
3446
3447 If no revision is specified, the working directory's parent is a
3447 If no revision is specified, the working directory's parent is a
3448 head revision, and the current branch contains exactly one other
3448 head revision, and the current branch contains exactly one other
3449 head, the other head is merged with by default. Otherwise, an
3449 head, the other head is merged with by default. Otherwise, an
3450 explicit revision with which to merge with must be provided.
3450 explicit revision with which to merge with must be provided.
3451
3451
3452 :hg:`resolve` must be used to resolve unresolved files.
3452 :hg:`resolve` must be used to resolve unresolved files.
3453
3453
3454 To undo an uncommitted merge, use :hg:`update --clean .` which
3454 To undo an uncommitted merge, use :hg:`update --clean .` which
3455 will check out a clean copy of the original merge parent, losing
3455 will check out a clean copy of the original merge parent, losing
3456 all changes.
3456 all changes.
3457
3457
3458 Returns 0 on success, 1 if there are unresolved files.
3458 Returns 0 on success, 1 if there are unresolved files.
3459 """
3459 """
3460
3460
3461 if opts.get('rev') and node:
3461 if opts.get('rev') and node:
3462 raise util.Abort(_("please specify just one revision"))
3462 raise util.Abort(_("please specify just one revision"))
3463 if not node:
3463 if not node:
3464 node = opts.get('rev')
3464 node = opts.get('rev')
3465
3465
3466 if not node:
3466 if not node:
3467 branch = repo[None].branch()
3467 branch = repo[None].branch()
3468 bheads = repo.branchheads(branch)
3468 bheads = repo.branchheads(branch)
3469 if len(bheads) > 2:
3469 if len(bheads) > 2:
3470 raise util.Abort(_("branch '%s' has %d heads - "
3470 raise util.Abort(_("branch '%s' has %d heads - "
3471 "please merge with an explicit rev")
3471 "please merge with an explicit rev")
3472 % (branch, len(bheads)),
3472 % (branch, len(bheads)),
3473 hint=_("run 'hg heads .' to see heads"))
3473 hint=_("run 'hg heads .' to see heads"))
3474
3474
3475 parent = repo.dirstate.p1()
3475 parent = repo.dirstate.p1()
3476 if len(bheads) == 1:
3476 if len(bheads) == 1:
3477 if len(repo.heads()) > 1:
3477 if len(repo.heads()) > 1:
3478 raise util.Abort(_("branch '%s' has one head - "
3478 raise util.Abort(_("branch '%s' has one head - "
3479 "please merge with an explicit rev")
3479 "please merge with an explicit rev")
3480 % branch,
3480 % branch,
3481 hint=_("run 'hg heads' to see all heads"))
3481 hint=_("run 'hg heads' to see all heads"))
3482 msg = _('there is nothing to merge')
3482 msg = _('there is nothing to merge')
3483 if parent != repo.lookup(repo[None].branch()):
3483 if parent != repo.lookup(repo[None].branch()):
3484 msg = _('%s - use "hg update" instead') % msg
3484 msg = _('%s - use "hg update" instead') % msg
3485 raise util.Abort(msg)
3485 raise util.Abort(msg)
3486
3486
3487 if parent not in bheads:
3487 if parent not in bheads:
3488 raise util.Abort(_('working directory not at a head revision'),
3488 raise util.Abort(_('working directory not at a head revision'),
3489 hint=_("use 'hg update' or merge with an "
3489 hint=_("use 'hg update' or merge with an "
3490 "explicit revision"))
3490 "explicit revision"))
3491 node = parent == bheads[0] and bheads[-1] or bheads[0]
3491 node = parent == bheads[0] and bheads[-1] or bheads[0]
3492 else:
3492 else:
3493 node = scmutil.revsingle(repo, node).node()
3493 node = scmutil.revsingle(repo, node).node()
3494
3494
3495 if opts.get('preview'):
3495 if opts.get('preview'):
3496 # find nodes that are ancestors of p2 but not of p1
3496 # find nodes that are ancestors of p2 but not of p1
3497 p1 = repo.lookup('.')
3497 p1 = repo.lookup('.')
3498 p2 = repo.lookup(node)
3498 p2 = repo.lookup(node)
3499 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3499 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3500
3500
3501 displayer = cmdutil.show_changeset(ui, repo, opts)
3501 displayer = cmdutil.show_changeset(ui, repo, opts)
3502 for node in nodes:
3502 for node in nodes:
3503 displayer.show(repo[node])
3503 displayer.show(repo[node])
3504 displayer.close()
3504 displayer.close()
3505 return 0
3505 return 0
3506
3506
3507 try:
3507 try:
3508 # ui.forcemerge is an internal variable, do not document
3508 # ui.forcemerge is an internal variable, do not document
3509 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
3509 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
3510 return hg.merge(repo, node, force=opts.get('force'))
3510 return hg.merge(repo, node, force=opts.get('force'))
3511 finally:
3511 finally:
3512 ui.setconfig('ui', 'forcemerge', '')
3512 ui.setconfig('ui', 'forcemerge', '')
3513
3513
3514 @command('outgoing|out',
3514 @command('outgoing|out',
3515 [('f', 'force', None, _('run even when the destination is unrelated')),
3515 [('f', 'force', None, _('run even when the destination is unrelated')),
3516 ('r', 'rev', [],
3516 ('r', 'rev', [],
3517 _('a changeset intended to be included in the destination'), _('REV')),
3517 _('a changeset intended to be included in the destination'), _('REV')),
3518 ('n', 'newest-first', None, _('show newest record first')),
3518 ('n', 'newest-first', None, _('show newest record first')),
3519 ('B', 'bookmarks', False, _('compare bookmarks')),
3519 ('B', 'bookmarks', False, _('compare bookmarks')),
3520 ('b', 'branch', [], _('a specific branch you would like to push'),
3520 ('b', 'branch', [], _('a specific branch you would like to push'),
3521 _('BRANCH')),
3521 _('BRANCH')),
3522 ] + logopts + remoteopts + subrepoopts,
3522 ] + logopts + remoteopts + subrepoopts,
3523 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3523 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3524 def outgoing(ui, repo, dest=None, **opts):
3524 def outgoing(ui, repo, dest=None, **opts):
3525 """show changesets not found in the destination
3525 """show changesets not found in the destination
3526
3526
3527 Show changesets not found in the specified destination repository
3527 Show changesets not found in the specified destination repository
3528 or the default push location. These are the changesets that would
3528 or the default push location. These are the changesets that would
3529 be pushed if a push was requested.
3529 be pushed if a push was requested.
3530
3530
3531 See pull for details of valid destination formats.
3531 See pull for details of valid destination formats.
3532
3532
3533 Returns 0 if there are outgoing changes, 1 otherwise.
3533 Returns 0 if there are outgoing changes, 1 otherwise.
3534 """
3534 """
3535
3535
3536 if opts.get('bookmarks'):
3536 if opts.get('bookmarks'):
3537 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3537 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3538 dest, branches = hg.parseurl(dest, opts.get('branch'))
3538 dest, branches = hg.parseurl(dest, opts.get('branch'))
3539 other = hg.repository(hg.remoteui(repo, opts), dest)
3539 other = hg.repository(hg.remoteui(repo, opts), dest)
3540 if 'bookmarks' not in other.listkeys('namespaces'):
3540 if 'bookmarks' not in other.listkeys('namespaces'):
3541 ui.warn(_("remote doesn't support bookmarks\n"))
3541 ui.warn(_("remote doesn't support bookmarks\n"))
3542 return 0
3542 return 0
3543 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3543 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3544 return bookmarks.diff(ui, other, repo)
3544 return bookmarks.diff(ui, other, repo)
3545
3545
3546 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3546 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3547 try:
3547 try:
3548 return hg.outgoing(ui, repo, dest, opts)
3548 return hg.outgoing(ui, repo, dest, opts)
3549 finally:
3549 finally:
3550 del repo._subtoppath
3550 del repo._subtoppath
3551
3551
3552 @command('parents',
3552 @command('parents',
3553 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3553 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3554 ] + templateopts,
3554 ] + templateopts,
3555 _('[-r REV] [FILE]'))
3555 _('[-r REV] [FILE]'))
3556 def parents(ui, repo, file_=None, **opts):
3556 def parents(ui, repo, file_=None, **opts):
3557 """show the parents of the working directory or revision
3557 """show the parents of the working directory or revision
3558
3558
3559 Print the working directory's parent revisions. If a revision is
3559 Print the working directory's parent revisions. If a revision is
3560 given via -r/--rev, the parent of that revision will be printed.
3560 given via -r/--rev, the parent of that revision will be printed.
3561 If a file argument is given, the revision in which the file was
3561 If a file argument is given, the revision in which the file was
3562 last changed (before the working directory revision or the
3562 last changed (before the working directory revision or the
3563 argument to --rev if given) is printed.
3563 argument to --rev if given) is printed.
3564
3564
3565 Returns 0 on success.
3565 Returns 0 on success.
3566 """
3566 """
3567
3567
3568 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3568 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3569
3569
3570 if file_:
3570 if file_:
3571 m = scmutil.match(repo, (file_,), opts)
3571 m = scmutil.match(repo, (file_,), opts)
3572 if m.anypats() or len(m.files()) != 1:
3572 if m.anypats() or len(m.files()) != 1:
3573 raise util.Abort(_('can only specify an explicit filename'))
3573 raise util.Abort(_('can only specify an explicit filename'))
3574 file_ = m.files()[0]
3574 file_ = m.files()[0]
3575 filenodes = []
3575 filenodes = []
3576 for cp in ctx.parents():
3576 for cp in ctx.parents():
3577 if not cp:
3577 if not cp:
3578 continue
3578 continue
3579 try:
3579 try:
3580 filenodes.append(cp.filenode(file_))
3580 filenodes.append(cp.filenode(file_))
3581 except error.LookupError:
3581 except error.LookupError:
3582 pass
3582 pass
3583 if not filenodes:
3583 if not filenodes:
3584 raise util.Abort(_("'%s' not found in manifest!") % file_)
3584 raise util.Abort(_("'%s' not found in manifest!") % file_)
3585 fl = repo.file(file_)
3585 fl = repo.file(file_)
3586 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
3586 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
3587 else:
3587 else:
3588 p = [cp.node() for cp in ctx.parents()]
3588 p = [cp.node() for cp in ctx.parents()]
3589
3589
3590 displayer = cmdutil.show_changeset(ui, repo, opts)
3590 displayer = cmdutil.show_changeset(ui, repo, opts)
3591 for n in p:
3591 for n in p:
3592 if n != nullid:
3592 if n != nullid:
3593 displayer.show(repo[n])
3593 displayer.show(repo[n])
3594 displayer.close()
3594 displayer.close()
3595
3595
3596 @command('paths', [], _('[NAME]'))
3596 @command('paths', [], _('[NAME]'))
3597 def paths(ui, repo, search=None):
3597 def paths(ui, repo, search=None):
3598 """show aliases for remote repositories
3598 """show aliases for remote repositories
3599
3599
3600 Show definition of symbolic path name NAME. If no name is given,
3600 Show definition of symbolic path name NAME. If no name is given,
3601 show definition of all available names.
3601 show definition of all available names.
3602
3602
3603 Option -q/--quiet suppresses all output when searching for NAME
3603 Option -q/--quiet suppresses all output when searching for NAME
3604 and shows only the path names when listing all definitions.
3604 and shows only the path names when listing all definitions.
3605
3605
3606 Path names are defined in the [paths] section of your
3606 Path names are defined in the [paths] section of your
3607 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3607 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3608 repository, ``.hg/hgrc`` is used, too.
3608 repository, ``.hg/hgrc`` is used, too.
3609
3609
3610 The path names ``default`` and ``default-push`` have a special
3610 The path names ``default`` and ``default-push`` have a special
3611 meaning. When performing a push or pull operation, they are used
3611 meaning. When performing a push or pull operation, they are used
3612 as fallbacks if no location is specified on the command-line.
3612 as fallbacks if no location is specified on the command-line.
3613 When ``default-push`` is set, it will be used for push and
3613 When ``default-push`` is set, it will be used for push and
3614 ``default`` will be used for pull; otherwise ``default`` is used
3614 ``default`` will be used for pull; otherwise ``default`` is used
3615 as the fallback for both. When cloning a repository, the clone
3615 as the fallback for both. When cloning a repository, the clone
3616 source is written as ``default`` in ``.hg/hgrc``. Note that
3616 source is written as ``default`` in ``.hg/hgrc``. Note that
3617 ``default`` and ``default-push`` apply to all inbound (e.g.
3617 ``default`` and ``default-push`` apply to all inbound (e.g.
3618 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
3618 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
3619 :hg:`bundle`) operations.
3619 :hg:`bundle`) operations.
3620
3620
3621 See :hg:`help urls` for more information.
3621 See :hg:`help urls` for more information.
3622
3622
3623 Returns 0 on success.
3623 Returns 0 on success.
3624 """
3624 """
3625 if search:
3625 if search:
3626 for name, path in ui.configitems("paths"):
3626 for name, path in ui.configitems("paths"):
3627 if name == search:
3627 if name == search:
3628 ui.status("%s\n" % util.hidepassword(path))
3628 ui.status("%s\n" % util.hidepassword(path))
3629 return
3629 return
3630 if not ui.quiet:
3630 if not ui.quiet:
3631 ui.warn(_("not found!\n"))
3631 ui.warn(_("not found!\n"))
3632 return 1
3632 return 1
3633 else:
3633 else:
3634 for name, path in ui.configitems("paths"):
3634 for name, path in ui.configitems("paths"):
3635 if ui.quiet:
3635 if ui.quiet:
3636 ui.write("%s\n" % name)
3636 ui.write("%s\n" % name)
3637 else:
3637 else:
3638 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
3638 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
3639
3639
3640 def postincoming(ui, repo, modheads, optupdate, checkout):
3640 def postincoming(ui, repo, modheads, optupdate, checkout):
3641 if modheads == 0:
3641 if modheads == 0:
3642 return
3642 return
3643 if optupdate:
3643 if optupdate:
3644 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
3644 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
3645 return hg.update(repo, checkout)
3645 return hg.update(repo, checkout)
3646 else:
3646 else:
3647 ui.status(_("not updating, since new heads added\n"))
3647 ui.status(_("not updating, since new heads added\n"))
3648 if modheads > 1:
3648 if modheads > 1:
3649 currentbranchheads = len(repo.branchheads())
3649 currentbranchheads = len(repo.branchheads())
3650 if currentbranchheads == modheads:
3650 if currentbranchheads == modheads:
3651 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3651 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3652 elif currentbranchheads > 1:
3652 elif currentbranchheads > 1:
3653 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to merge)\n"))
3653 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to merge)\n"))
3654 else:
3654 else:
3655 ui.status(_("(run 'hg heads' to see heads)\n"))
3655 ui.status(_("(run 'hg heads' to see heads)\n"))
3656 else:
3656 else:
3657 ui.status(_("(run 'hg update' to get a working copy)\n"))
3657 ui.status(_("(run 'hg update' to get a working copy)\n"))
3658
3658
3659 @command('^pull',
3659 @command('^pull',
3660 [('u', 'update', None,
3660 [('u', 'update', None,
3661 _('update to new branch head if changesets were pulled')),
3661 _('update to new branch head if changesets were pulled')),
3662 ('f', 'force', None, _('run even when remote repository is unrelated')),
3662 ('f', 'force', None, _('run even when remote repository is unrelated')),
3663 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3663 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3664 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3664 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3665 ('b', 'branch', [], _('a specific branch you would like to pull'),
3665 ('b', 'branch', [], _('a specific branch you would like to pull'),
3666 _('BRANCH')),
3666 _('BRANCH')),
3667 ] + remoteopts,
3667 ] + remoteopts,
3668 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3668 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3669 def pull(ui, repo, source="default", **opts):
3669 def pull(ui, repo, source="default", **opts):
3670 """pull changes from the specified source
3670 """pull changes from the specified source
3671
3671
3672 Pull changes from a remote repository to a local one.
3672 Pull changes from a remote repository to a local one.
3673
3673
3674 This finds all changes from the repository at the specified path
3674 This finds all changes from the repository at the specified path
3675 or URL and adds them to a local repository (the current one unless
3675 or URL and adds them to a local repository (the current one unless
3676 -R is specified). By default, this does not update the copy of the
3676 -R is specified). By default, this does not update the copy of the
3677 project in the working directory.
3677 project in the working directory.
3678
3678
3679 Use :hg:`incoming` if you want to see what would have been added
3679 Use :hg:`incoming` if you want to see what would have been added
3680 by a pull at the time you issued this command. If you then decide
3680 by a pull at the time you issued this command. If you then decide
3681 to add those changes to the repository, you should use :hg:`pull
3681 to add those changes to the repository, you should use :hg:`pull
3682 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3682 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3683
3683
3684 If SOURCE is omitted, the 'default' path will be used.
3684 If SOURCE is omitted, the 'default' path will be used.
3685 See :hg:`help urls` for more information.
3685 See :hg:`help urls` for more information.
3686
3686
3687 Returns 0 on success, 1 if an update had unresolved files.
3687 Returns 0 on success, 1 if an update had unresolved files.
3688 """
3688 """
3689 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3689 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3690 other = hg.repository(hg.remoteui(repo, opts), source)
3690 other = hg.repository(hg.remoteui(repo, opts), source)
3691 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3691 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3692 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3692 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3693
3693
3694 if opts.get('bookmark'):
3694 if opts.get('bookmark'):
3695 if not revs:
3695 if not revs:
3696 revs = []
3696 revs = []
3697 rb = other.listkeys('bookmarks')
3697 rb = other.listkeys('bookmarks')
3698 for b in opts['bookmark']:
3698 for b in opts['bookmark']:
3699 if b not in rb:
3699 if b not in rb:
3700 raise util.Abort(_('remote bookmark %s not found!') % b)
3700 raise util.Abort(_('remote bookmark %s not found!') % b)
3701 revs.append(rb[b])
3701 revs.append(rb[b])
3702
3702
3703 if revs:
3703 if revs:
3704 try:
3704 try:
3705 revs = [other.lookup(rev) for rev in revs]
3705 revs = [other.lookup(rev) for rev in revs]
3706 except error.CapabilityError:
3706 except error.CapabilityError:
3707 err = _("other repository doesn't support revision lookup, "
3707 err = _("other repository doesn't support revision lookup, "
3708 "so a rev cannot be specified.")
3708 "so a rev cannot be specified.")
3709 raise util.Abort(err)
3709 raise util.Abort(err)
3710
3710
3711 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
3711 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
3712 bookmarks.updatefromremote(ui, repo, other)
3712 bookmarks.updatefromremote(ui, repo, other)
3713 if checkout:
3713 if checkout:
3714 checkout = str(repo.changelog.rev(other.lookup(checkout)))
3714 checkout = str(repo.changelog.rev(other.lookup(checkout)))
3715 repo._subtoppath = source
3715 repo._subtoppath = source
3716 try:
3716 try:
3717 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
3717 ret = postincoming(ui, repo, modheads, opts.get('update'), checkout)
3718
3718
3719 finally:
3719 finally:
3720 del repo._subtoppath
3720 del repo._subtoppath
3721
3721
3722 # update specified bookmarks
3722 # update specified bookmarks
3723 if opts.get('bookmark'):
3723 if opts.get('bookmark'):
3724 for b in opts['bookmark']:
3724 for b in opts['bookmark']:
3725 # explicit pull overrides local bookmark if any
3725 # explicit pull overrides local bookmark if any
3726 ui.status(_("importing bookmark %s\n") % b)
3726 ui.status(_("importing bookmark %s\n") % b)
3727 repo._bookmarks[b] = repo[rb[b]].node()
3727 repo._bookmarks[b] = repo[rb[b]].node()
3728 bookmarks.write(repo)
3728 bookmarks.write(repo)
3729
3729
3730 return ret
3730 return ret
3731
3731
3732 @command('^push',
3732 @command('^push',
3733 [('f', 'force', None, _('force push')),
3733 [('f', 'force', None, _('force push')),
3734 ('r', 'rev', [],
3734 ('r', 'rev', [],
3735 _('a changeset intended to be included in the destination'),
3735 _('a changeset intended to be included in the destination'),
3736 _('REV')),
3736 _('REV')),
3737 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
3737 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
3738 ('b', 'branch', [],
3738 ('b', 'branch', [],
3739 _('a specific branch you would like to push'), _('BRANCH')),
3739 _('a specific branch you would like to push'), _('BRANCH')),
3740 ('', 'new-branch', False, _('allow pushing a new branch')),
3740 ('', 'new-branch', False, _('allow pushing a new branch')),
3741 ] + remoteopts,
3741 ] + remoteopts,
3742 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
3742 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
3743 def push(ui, repo, dest=None, **opts):
3743 def push(ui, repo, dest=None, **opts):
3744 """push changes to the specified destination
3744 """push changes to the specified destination
3745
3745
3746 Push changesets from the local repository to the specified
3746 Push changesets from the local repository to the specified
3747 destination.
3747 destination.
3748
3748
3749 This operation is symmetrical to pull: it is identical to a pull
3749 This operation is symmetrical to pull: it is identical to a pull
3750 in the destination repository from the current one.
3750 in the destination repository from the current one.
3751
3751
3752 By default, push will not allow creation of new heads at the
3752 By default, push will not allow creation of new heads at the
3753 destination, since multiple heads would make it unclear which head
3753 destination, since multiple heads would make it unclear which head
3754 to use. In this situation, it is recommended to pull and merge
3754 to use. In this situation, it is recommended to pull and merge
3755 before pushing.
3755 before pushing.
3756
3756
3757 Use --new-branch if you want to allow push to create a new named
3757 Use --new-branch if you want to allow push to create a new named
3758 branch that is not present at the destination. This allows you to
3758 branch that is not present at the destination. This allows you to
3759 only create a new branch without forcing other changes.
3759 only create a new branch without forcing other changes.
3760
3760
3761 Use -f/--force to override the default behavior and push all
3761 Use -f/--force to override the default behavior and push all
3762 changesets on all branches.
3762 changesets on all branches.
3763
3763
3764 If -r/--rev is used, the specified revision and all its ancestors
3764 If -r/--rev is used, the specified revision and all its ancestors
3765 will be pushed to the remote repository.
3765 will be pushed to the remote repository.
3766
3766
3767 Please see :hg:`help urls` for important details about ``ssh://``
3767 Please see :hg:`help urls` for important details about ``ssh://``
3768 URLs. If DESTINATION is omitted, a default path will be used.
3768 URLs. If DESTINATION is omitted, a default path will be used.
3769
3769
3770 Returns 0 if push was successful, 1 if nothing to push.
3770 Returns 0 if push was successful, 1 if nothing to push.
3771 """
3771 """
3772
3772
3773 if opts.get('bookmark'):
3773 if opts.get('bookmark'):
3774 for b in opts['bookmark']:
3774 for b in opts['bookmark']:
3775 # translate -B options to -r so changesets get pushed
3775 # translate -B options to -r so changesets get pushed
3776 if b in repo._bookmarks:
3776 if b in repo._bookmarks:
3777 opts.setdefault('rev', []).append(b)
3777 opts.setdefault('rev', []).append(b)
3778 else:
3778 else:
3779 # if we try to push a deleted bookmark, translate it to null
3779 # if we try to push a deleted bookmark, translate it to null
3780 # this lets simultaneous -r, -b options continue working
3780 # this lets simultaneous -r, -b options continue working
3781 opts.setdefault('rev', []).append("null")
3781 opts.setdefault('rev', []).append("null")
3782
3782
3783 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3783 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3784 dest, branches = hg.parseurl(dest, opts.get('branch'))
3784 dest, branches = hg.parseurl(dest, opts.get('branch'))
3785 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
3785 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
3786 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
3786 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
3787 other = hg.repository(hg.remoteui(repo, opts), dest)
3787 other = hg.repository(hg.remoteui(repo, opts), dest)
3788 if revs:
3788 if revs:
3789 revs = [repo.lookup(rev) for rev in revs]
3789 revs = [repo.lookup(rev) for rev in revs]
3790
3790
3791 repo._subtoppath = dest
3791 repo._subtoppath = dest
3792 try:
3792 try:
3793 # push subrepos depth-first for coherent ordering
3793 # push subrepos depth-first for coherent ordering
3794 c = repo['']
3794 c = repo['']
3795 subs = c.substate # only repos that are committed
3795 subs = c.substate # only repos that are committed
3796 for s in sorted(subs):
3796 for s in sorted(subs):
3797 if not c.sub(s).push(opts.get('force')):
3797 if not c.sub(s).push(opts.get('force')):
3798 return False
3798 return False
3799 finally:
3799 finally:
3800 del repo._subtoppath
3800 del repo._subtoppath
3801 result = repo.push(other, opts.get('force'), revs=revs,
3801 result = repo.push(other, opts.get('force'), revs=revs,
3802 newbranch=opts.get('new_branch'))
3802 newbranch=opts.get('new_branch'))
3803
3803
3804 result = (result == 0)
3804 result = (result == 0)
3805
3805
3806 if opts.get('bookmark'):
3806 if opts.get('bookmark'):
3807 rb = other.listkeys('bookmarks')
3807 rb = other.listkeys('bookmarks')
3808 for b in opts['bookmark']:
3808 for b in opts['bookmark']:
3809 # explicit push overrides remote bookmark if any
3809 # explicit push overrides remote bookmark if any
3810 if b in repo._bookmarks:
3810 if b in repo._bookmarks:
3811 ui.status(_("exporting bookmark %s\n") % b)
3811 ui.status(_("exporting bookmark %s\n") % b)
3812 new = repo[b].hex()
3812 new = repo[b].hex()
3813 elif b in rb:
3813 elif b in rb:
3814 ui.status(_("deleting remote bookmark %s\n") % b)
3814 ui.status(_("deleting remote bookmark %s\n") % b)
3815 new = '' # delete
3815 new = '' # delete
3816 else:
3816 else:
3817 ui.warn(_('bookmark %s does not exist on the local '
3817 ui.warn(_('bookmark %s does not exist on the local '
3818 'or remote repository!\n') % b)
3818 'or remote repository!\n') % b)
3819 return 2
3819 return 2
3820 old = rb.get(b, '')
3820 old = rb.get(b, '')
3821 r = other.pushkey('bookmarks', b, old, new)
3821 r = other.pushkey('bookmarks', b, old, new)
3822 if not r:
3822 if not r:
3823 ui.warn(_('updating bookmark %s failed!\n') % b)
3823 ui.warn(_('updating bookmark %s failed!\n') % b)
3824 if not result:
3824 if not result:
3825 result = 2
3825 result = 2
3826
3826
3827 return result
3827 return result
3828
3828
3829 @command('recover', [])
3829 @command('recover', [])
3830 def recover(ui, repo):
3830 def recover(ui, repo):
3831 """roll back an interrupted transaction
3831 """roll back an interrupted transaction
3832
3832
3833 Recover from an interrupted commit or pull.
3833 Recover from an interrupted commit or pull.
3834
3834
3835 This command tries to fix the repository status after an
3835 This command tries to fix the repository status after an
3836 interrupted operation. It should only be necessary when Mercurial
3836 interrupted operation. It should only be necessary when Mercurial
3837 suggests it.
3837 suggests it.
3838
3838
3839 Returns 0 if successful, 1 if nothing to recover or verify fails.
3839 Returns 0 if successful, 1 if nothing to recover or verify fails.
3840 """
3840 """
3841 if repo.recover():
3841 if repo.recover():
3842 return hg.verify(repo)
3842 return hg.verify(repo)
3843 return 1
3843 return 1
3844
3844
3845 @command('^remove|rm',
3845 @command('^remove|rm',
3846 [('A', 'after', None, _('record delete for missing files')),
3846 [('A', 'after', None, _('record delete for missing files')),
3847 ('f', 'force', None,
3847 ('f', 'force', None,
3848 _('remove (and delete) file even if added or modified')),
3848 _('remove (and delete) file even if added or modified')),
3849 ] + walkopts,
3849 ] + walkopts,
3850 _('[OPTION]... FILE...'))
3850 _('[OPTION]... FILE...'))
3851 def remove(ui, repo, *pats, **opts):
3851 def remove(ui, repo, *pats, **opts):
3852 """remove the specified files on the next commit
3852 """remove the specified files on the next commit
3853
3853
3854 Schedule the indicated files for removal from the repository.
3854 Schedule the indicated files for removal from the repository.
3855
3855
3856 This only removes files from the current branch, not from the
3856 This only removes files from the current branch, not from the
3857 entire project history. -A/--after can be used to remove only
3857 entire project history. -A/--after can be used to remove only
3858 files that have already been deleted, -f/--force can be used to
3858 files that have already been deleted, -f/--force can be used to
3859 force deletion, and -Af can be used to remove files from the next
3859 force deletion, and -Af can be used to remove files from the next
3860 revision without deleting them from the working directory.
3860 revision without deleting them from the working directory.
3861
3861
3862 The following table details the behavior of remove for different
3862 The following table details the behavior of remove for different
3863 file states (columns) and option combinations (rows). The file
3863 file states (columns) and option combinations (rows). The file
3864 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
3864 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
3865 reported by :hg:`status`). The actions are Warn, Remove (from
3865 reported by :hg:`status`). The actions are Warn, Remove (from
3866 branch) and Delete (from disk)::
3866 branch) and Delete (from disk)::
3867
3867
3868 A C M !
3868 A C M !
3869 none W RD W R
3869 none W RD W R
3870 -f R RD RD R
3870 -f R RD RD R
3871 -A W W W R
3871 -A W W W R
3872 -Af R R R R
3872 -Af R R R R
3873
3873
3874 This command schedules the files to be removed at the next commit.
3874 This command schedules the files to be removed at the next commit.
3875 To undo a remove before that, see :hg:`revert`.
3875 To undo a remove before that, see :hg:`revert`.
3876
3876
3877 Returns 0 on success, 1 if any warnings encountered.
3877 Returns 0 on success, 1 if any warnings encountered.
3878 """
3878 """
3879
3879
3880 ret = 0
3880 ret = 0
3881 after, force = opts.get('after'), opts.get('force')
3881 after, force = opts.get('after'), opts.get('force')
3882 if not pats and not after:
3882 if not pats and not after:
3883 raise util.Abort(_('no files specified'))
3883 raise util.Abort(_('no files specified'))
3884
3884
3885 m = scmutil.match(repo, pats, opts)
3885 m = scmutil.match(repo, pats, opts)
3886 s = repo.status(match=m, clean=True)
3886 s = repo.status(match=m, clean=True)
3887 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
3887 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
3888
3888
3889 for f in m.files():
3889 for f in m.files():
3890 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
3890 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
3891 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
3891 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
3892 ret = 1
3892 ret = 1
3893
3893
3894 if force:
3894 if force:
3895 remove, forget = modified + deleted + clean, added
3895 remove, forget = modified + deleted + clean, added
3896 elif after:
3896 elif after:
3897 remove, forget = deleted, []
3897 remove, forget = deleted, []
3898 for f in modified + added + clean:
3898 for f in modified + added + clean:
3899 ui.warn(_('not removing %s: file still exists (use -f'
3899 ui.warn(_('not removing %s: file still exists (use -f'
3900 ' to force removal)\n') % m.rel(f))
3900 ' to force removal)\n') % m.rel(f))
3901 ret = 1
3901 ret = 1
3902 else:
3902 else:
3903 remove, forget = deleted + clean, []
3903 remove, forget = deleted + clean, []
3904 for f in modified:
3904 for f in modified:
3905 ui.warn(_('not removing %s: file is modified (use -f'
3905 ui.warn(_('not removing %s: file is modified (use -f'
3906 ' to force removal)\n') % m.rel(f))
3906 ' to force removal)\n') % m.rel(f))
3907 ret = 1
3907 ret = 1
3908 for f in added:
3908 for f in added:
3909 ui.warn(_('not removing %s: file has been marked for add (use -f'
3909 ui.warn(_('not removing %s: file has been marked for add (use -f'
3910 ' to force removal)\n') % m.rel(f))
3910 ' to force removal)\n') % m.rel(f))
3911 ret = 1
3911 ret = 1
3912
3912
3913 for f in sorted(remove + forget):
3913 for f in sorted(remove + forget):
3914 if ui.verbose or not m.exact(f):
3914 if ui.verbose or not m.exact(f):
3915 ui.status(_('removing %s\n') % m.rel(f))
3915 ui.status(_('removing %s\n') % m.rel(f))
3916
3916
3917 repo[None].forget(forget)
3917 repo[None].forget(forget)
3918 repo[None].remove(remove, unlink=not after)
3918 repo[None].remove(remove, unlink=not after)
3919 return ret
3919 return ret
3920
3920
3921 @command('rename|move|mv',
3921 @command('rename|move|mv',
3922 [('A', 'after', None, _('record a rename that has already occurred')),
3922 [('A', 'after', None, _('record a rename that has already occurred')),
3923 ('f', 'force', None, _('forcibly copy over an existing managed file')),
3923 ('f', 'force', None, _('forcibly copy over an existing managed file')),
3924 ] + walkopts + dryrunopts,
3924 ] + walkopts + dryrunopts,
3925 _('[OPTION]... SOURCE... DEST'))
3925 _('[OPTION]... SOURCE... DEST'))
3926 def rename(ui, repo, *pats, **opts):
3926 def rename(ui, repo, *pats, **opts):
3927 """rename files; equivalent of copy + remove
3927 """rename files; equivalent of copy + remove
3928
3928
3929 Mark dest as copies of sources; mark sources for deletion. If dest
3929 Mark dest as copies of sources; mark sources for deletion. If dest
3930 is a directory, copies are put in that directory. If dest is a
3930 is a directory, copies are put in that directory. If dest is a
3931 file, there can only be one source.
3931 file, there can only be one source.
3932
3932
3933 By default, this command copies the contents of files as they
3933 By default, this command copies the contents of files as they
3934 exist in the working directory. If invoked with -A/--after, the
3934 exist in the working directory. If invoked with -A/--after, the
3935 operation is recorded, but no copying is performed.
3935 operation is recorded, but no copying is performed.
3936
3936
3937 This command takes effect at the next commit. To undo a rename
3937 This command takes effect at the next commit. To undo a rename
3938 before that, see :hg:`revert`.
3938 before that, see :hg:`revert`.
3939
3939
3940 Returns 0 on success, 1 if errors are encountered.
3940 Returns 0 on success, 1 if errors are encountered.
3941 """
3941 """
3942 wlock = repo.wlock(False)
3942 wlock = repo.wlock(False)
3943 try:
3943 try:
3944 return cmdutil.copy(ui, repo, pats, opts, rename=True)
3944 return cmdutil.copy(ui, repo, pats, opts, rename=True)
3945 finally:
3945 finally:
3946 wlock.release()
3946 wlock.release()
3947
3947
3948 @command('resolve',
3948 @command('resolve',
3949 [('a', 'all', None, _('select all unresolved files')),
3949 [('a', 'all', None, _('select all unresolved files')),
3950 ('l', 'list', None, _('list state of files needing merge')),
3950 ('l', 'list', None, _('list state of files needing merge')),
3951 ('m', 'mark', None, _('mark files as resolved')),
3951 ('m', 'mark', None, _('mark files as resolved')),
3952 ('u', 'unmark', None, _('mark files as unresolved')),
3952 ('u', 'unmark', None, _('mark files as unresolved')),
3953 ('t', 'tool', '', _('specify merge tool')),
3953 ('t', 'tool', '', _('specify merge tool')),
3954 ('n', 'no-status', None, _('hide status prefix'))]
3954 ('n', 'no-status', None, _('hide status prefix'))]
3955 + walkopts,
3955 + walkopts,
3956 _('[OPTION]... [FILE]...'))
3956 _('[OPTION]... [FILE]...'))
3957 def resolve(ui, repo, *pats, **opts):
3957 def resolve(ui, repo, *pats, **opts):
3958 """redo merges or set/view the merge status of files
3958 """redo merges or set/view the merge status of files
3959
3959
3960 Merges with unresolved conflicts are often the result of
3960 Merges with unresolved conflicts are often the result of
3961 non-interactive merging using the ``internal:merge`` configuration
3961 non-interactive merging using the ``internal:merge`` configuration
3962 setting, or a command-line merge tool like ``diff3``. The resolve
3962 setting, or a command-line merge tool like ``diff3``. The resolve
3963 command is used to manage the files involved in a merge, after
3963 command is used to manage the files involved in a merge, after
3964 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
3964 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
3965 working directory must have two parents).
3965 working directory must have two parents).
3966
3966
3967 The resolve command can be used in the following ways:
3967 The resolve command can be used in the following ways:
3968
3968
3969 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
3969 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
3970 files, discarding any previous merge attempts. Re-merging is not
3970 files, discarding any previous merge attempts. Re-merging is not
3971 performed for files already marked as resolved. Use ``--all/-a``
3971 performed for files already marked as resolved. Use ``--all/-a``
3972 to selects all unresolved files. ``--tool`` can be used to specify
3972 to selects all unresolved files. ``--tool`` can be used to specify
3973 the merge tool used for the given files. It overrides the HGMERGE
3973 the merge tool used for the given files. It overrides the HGMERGE
3974 environment variable and your configuration files.
3974 environment variable and your configuration files.
3975
3975
3976 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
3976 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
3977 (e.g. after having manually fixed-up the files). The default is
3977 (e.g. after having manually fixed-up the files). The default is
3978 to mark all unresolved files.
3978 to mark all unresolved files.
3979
3979
3980 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
3980 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
3981 default is to mark all resolved files.
3981 default is to mark all resolved files.
3982
3982
3983 - :hg:`resolve -l`: list files which had or still have conflicts.
3983 - :hg:`resolve -l`: list files which had or still have conflicts.
3984 In the printed list, ``U`` = unresolved and ``R`` = resolved.
3984 In the printed list, ``U`` = unresolved and ``R`` = resolved.
3985
3985
3986 Note that Mercurial will not let you commit files with unresolved
3986 Note that Mercurial will not let you commit files with unresolved
3987 merge conflicts. You must use :hg:`resolve -m ...` before you can
3987 merge conflicts. You must use :hg:`resolve -m ...` before you can
3988 commit after a conflicting merge.
3988 commit after a conflicting merge.
3989
3989
3990 Returns 0 on success, 1 if any files fail a resolve attempt.
3990 Returns 0 on success, 1 if any files fail a resolve attempt.
3991 """
3991 """
3992
3992
3993 all, mark, unmark, show, nostatus = \
3993 all, mark, unmark, show, nostatus = \
3994 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
3994 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
3995
3995
3996 if (show and (mark or unmark)) or (mark and unmark):
3996 if (show and (mark or unmark)) or (mark and unmark):
3997 raise util.Abort(_("too many options specified"))
3997 raise util.Abort(_("too many options specified"))
3998 if pats and all:
3998 if pats and all:
3999 raise util.Abort(_("can't specify --all and patterns"))
3999 raise util.Abort(_("can't specify --all and patterns"))
4000 if not (all or pats or show or mark or unmark):
4000 if not (all or pats or show or mark or unmark):
4001 raise util.Abort(_('no files or directories specified; '
4001 raise util.Abort(_('no files or directories specified; '
4002 'use --all to remerge all files'))
4002 'use --all to remerge all files'))
4003
4003
4004 ms = mergemod.mergestate(repo)
4004 ms = mergemod.mergestate(repo)
4005 m = scmutil.match(repo, pats, opts)
4005 m = scmutil.match(repo, pats, opts)
4006 ret = 0
4006 ret = 0
4007
4007
4008 for f in ms:
4008 for f in ms:
4009 if m(f):
4009 if m(f):
4010 if show:
4010 if show:
4011 if nostatus:
4011 if nostatus:
4012 ui.write("%s\n" % f)
4012 ui.write("%s\n" % f)
4013 else:
4013 else:
4014 ui.write("%s %s\n" % (ms[f].upper(), f),
4014 ui.write("%s %s\n" % (ms[f].upper(), f),
4015 label='resolve.' +
4015 label='resolve.' +
4016 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
4016 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
4017 elif mark:
4017 elif mark:
4018 ms.mark(f, "r")
4018 ms.mark(f, "r")
4019 elif unmark:
4019 elif unmark:
4020 ms.mark(f, "u")
4020 ms.mark(f, "u")
4021 else:
4021 else:
4022 wctx = repo[None]
4022 wctx = repo[None]
4023 mctx = wctx.parents()[-1]
4023 mctx = wctx.parents()[-1]
4024
4024
4025 # backup pre-resolve (merge uses .orig for its own purposes)
4025 # backup pre-resolve (merge uses .orig for its own purposes)
4026 a = repo.wjoin(f)
4026 a = repo.wjoin(f)
4027 util.copyfile(a, a + ".resolve")
4027 util.copyfile(a, a + ".resolve")
4028
4028
4029 try:
4029 try:
4030 # resolve file
4030 # resolve file
4031 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4031 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
4032 if ms.resolve(f, wctx, mctx):
4032 if ms.resolve(f, wctx, mctx):
4033 ret = 1
4033 ret = 1
4034 finally:
4034 finally:
4035 ui.setconfig('ui', 'forcemerge', '')
4035 ui.setconfig('ui', 'forcemerge', '')
4036
4036
4037 # replace filemerge's .orig file with our resolve file
4037 # replace filemerge's .orig file with our resolve file
4038 util.rename(a + ".resolve", a + ".orig")
4038 util.rename(a + ".resolve", a + ".orig")
4039
4039
4040 ms.commit()
4040 ms.commit()
4041 return ret
4041 return ret
4042
4042
4043 @command('revert',
4043 @command('revert',
4044 [('a', 'all', None, _('revert all changes when no arguments given')),
4044 [('a', 'all', None, _('revert all changes when no arguments given')),
4045 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4045 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4046 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4046 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4047 ('', 'no-backup', None, _('do not save backup copies of files')),
4047 ('', 'no-backup', None, _('do not save backup copies of files')),
4048 ] + walkopts + dryrunopts,
4048 ] + walkopts + dryrunopts,
4049 _('[OPTION]... [-r REV] [NAME]...'))
4049 _('[OPTION]... [-r REV] [NAME]...'))
4050 def revert(ui, repo, *pats, **opts):
4050 def revert(ui, repo, *pats, **opts):
4051 """restore individual files or directories to an earlier state
4051 """restore individual files or directories to an earlier state
4052
4052
4053 .. note::
4053 .. note::
4054 This command is most likely not what you are looking for.
4054 This command is most likely not what you are looking for.
4055 Revert will partially overwrite content in the working
4055 Revert will partially overwrite content in the working
4056 directory without changing the working directory parents. Use
4056 directory without changing the working directory parents. Use
4057 :hg:`update -r rev` to check out earlier revisions, or
4057 :hg:`update -r rev` to check out earlier revisions, or
4058 :hg:`update --clean .` to undo a merge which has added another
4058 :hg:`update --clean .` to undo a merge which has added another
4059 parent.
4059 parent.
4060
4060
4061 With no revision specified, revert the named files or directories
4061 With no revision specified, revert the named files or directories
4062 to the contents they had in the parent of the working directory.
4062 to the contents they had in the parent of the working directory.
4063 This restores the contents of the affected files to an unmodified
4063 This restores the contents of the affected files to an unmodified
4064 state and unschedules adds, removes, copies, and renames. If the
4064 state and unschedules adds, removes, copies, and renames. If the
4065 working directory has two parents, you must explicitly specify a
4065 working directory has two parents, you must explicitly specify a
4066 revision.
4066 revision.
4067
4067
4068 Using the -r/--rev option, revert the given files or directories
4068 Using the -r/--rev option, revert the given files or directories
4069 to their contents as of a specific revision. This can be helpful
4069 to their contents as of a specific revision. This can be helpful
4070 to "roll back" some or all of an earlier change. See :hg:`help
4070 to "roll back" some or all of an earlier change. See :hg:`help
4071 dates` for a list of formats valid for -d/--date.
4071 dates` for a list of formats valid for -d/--date.
4072
4072
4073 Revert modifies the working directory. It does not commit any
4073 Revert modifies the working directory. It does not commit any
4074 changes, or change the parent of the working directory. If you
4074 changes, or change the parent of the working directory. If you
4075 revert to a revision other than the parent of the working
4075 revert to a revision other than the parent of the working
4076 directory, the reverted files will thus appear modified
4076 directory, the reverted files will thus appear modified
4077 afterwards.
4077 afterwards.
4078
4078
4079 If a file has been deleted, it is restored. Files scheduled for
4079 If a file has been deleted, it is restored. Files scheduled for
4080 addition are just unscheduled and left as they are. If the
4080 addition are just unscheduled and left as they are. If the
4081 executable mode of a file was changed, it is reset.
4081 executable mode of a file was changed, it is reset.
4082
4082
4083 If names are given, all files matching the names are reverted.
4083 If names are given, all files matching the names are reverted.
4084 If no arguments are given, no files are reverted.
4084 If no arguments are given, no files are reverted.
4085
4085
4086 Modified files are saved with a .orig suffix before reverting.
4086 Modified files are saved with a .orig suffix before reverting.
4087 To disable these backups, use --no-backup.
4087 To disable these backups, use --no-backup.
4088
4088
4089 Returns 0 on success.
4089 Returns 0 on success.
4090 """
4090 """
4091
4091
4092 if opts.get("date"):
4092 if opts.get("date"):
4093 if opts.get("rev"):
4093 if opts.get("rev"):
4094 raise util.Abort(_("you can't specify a revision and a date"))
4094 raise util.Abort(_("you can't specify a revision and a date"))
4095 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4095 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4096
4096
4097 parent, p2 = repo.dirstate.parents()
4097 parent, p2 = repo.dirstate.parents()
4098 if not opts.get('rev') and p2 != nullid:
4098 if not opts.get('rev') and p2 != nullid:
4099 raise util.Abort(_('uncommitted merge - '
4099 raise util.Abort(_('uncommitted merge - '
4100 'use "hg update", see "hg help revert"'))
4100 'use "hg update", see "hg help revert"'))
4101
4101
4102 if not pats and not opts.get('all'):
4102 if not pats and not opts.get('all'):
4103 raise util.Abort(_('no files or directories specified; '
4103 raise util.Abort(_('no files or directories specified; '
4104 'use --all to revert the whole repo'))
4104 'use --all to revert the whole repo'))
4105
4105
4106 ctx = scmutil.revsingle(repo, opts.get('rev'))
4106 ctx = scmutil.revsingle(repo, opts.get('rev'))
4107 node = ctx.node()
4107 node = ctx.node()
4108 mf = ctx.manifest()
4108 mf = ctx.manifest()
4109 if node == parent:
4109 if node == parent:
4110 pmf = mf
4110 pmf = mf
4111 else:
4111 else:
4112 pmf = None
4112 pmf = None
4113
4113
4114 # need all matching names in dirstate and manifest of target rev,
4114 # need all matching names in dirstate and manifest of target rev,
4115 # so have to walk both. do not print errors if files exist in one
4115 # so have to walk both. do not print errors if files exist in one
4116 # but not other.
4116 # but not other.
4117
4117
4118 names = {}
4118 names = {}
4119
4119
4120 wlock = repo.wlock()
4120 wlock = repo.wlock()
4121 try:
4121 try:
4122 # walk dirstate.
4122 # walk dirstate.
4123
4123
4124 m = scmutil.match(repo, pats, opts)
4124 m = scmutil.match(repo, pats, opts)
4125 m.bad = lambda x, y: False
4125 m.bad = lambda x, y: False
4126 for abs in repo.walk(m):
4126 for abs in repo.walk(m):
4127 names[abs] = m.rel(abs), m.exact(abs)
4127 names[abs] = m.rel(abs), m.exact(abs)
4128
4128
4129 # walk target manifest.
4129 # walk target manifest.
4130
4130
4131 def badfn(path, msg):
4131 def badfn(path, msg):
4132 if path in names:
4132 if path in names:
4133 return
4133 return
4134 path_ = path + '/'
4134 path_ = path + '/'
4135 for f in names:
4135 for f in names:
4136 if f.startswith(path_):
4136 if f.startswith(path_):
4137 return
4137 return
4138 ui.warn("%s: %s\n" % (m.rel(path), msg))
4138 ui.warn("%s: %s\n" % (m.rel(path), msg))
4139
4139
4140 m = scmutil.match(repo, pats, opts)
4140 m = scmutil.match(repo, pats, opts)
4141 m.bad = badfn
4141 m.bad = badfn
4142 for abs in repo[node].walk(m):
4142 for abs in repo[node].walk(m):
4143 if abs not in names:
4143 if abs not in names:
4144 names[abs] = m.rel(abs), m.exact(abs)
4144 names[abs] = m.rel(abs), m.exact(abs)
4145
4145
4146 m = scmutil.matchfiles(repo, names)
4146 m = scmutil.matchfiles(repo, names)
4147 changes = repo.status(match=m)[:4]
4147 changes = repo.status(match=m)[:4]
4148 modified, added, removed, deleted = map(set, changes)
4148 modified, added, removed, deleted = map(set, changes)
4149
4149
4150 # if f is a rename, also revert the source
4150 # if f is a rename, also revert the source
4151 cwd = repo.getcwd()
4151 cwd = repo.getcwd()
4152 for f in added:
4152 for f in added:
4153 src = repo.dirstate.copied(f)
4153 src = repo.dirstate.copied(f)
4154 if src and src not in names and repo.dirstate[src] == 'r':
4154 if src and src not in names and repo.dirstate[src] == 'r':
4155 removed.add(src)
4155 removed.add(src)
4156 names[src] = (repo.pathto(src, cwd), True)
4156 names[src] = (repo.pathto(src, cwd), True)
4157
4157
4158 def removeforget(abs):
4158 def removeforget(abs):
4159 if repo.dirstate[abs] == 'a':
4159 if repo.dirstate[abs] == 'a':
4160 return _('forgetting %s\n')
4160 return _('forgetting %s\n')
4161 return _('removing %s\n')
4161 return _('removing %s\n')
4162
4162
4163 revert = ([], _('reverting %s\n'))
4163 revert = ([], _('reverting %s\n'))
4164 add = ([], _('adding %s\n'))
4164 add = ([], _('adding %s\n'))
4165 remove = ([], removeforget)
4165 remove = ([], removeforget)
4166 undelete = ([], _('undeleting %s\n'))
4166 undelete = ([], _('undeleting %s\n'))
4167
4167
4168 disptable = (
4168 disptable = (
4169 # dispatch table:
4169 # dispatch table:
4170 # file state
4170 # file state
4171 # action if in target manifest
4171 # action if in target manifest
4172 # action if not in target manifest
4172 # action if not in target manifest
4173 # make backup if in target manifest
4173 # make backup if in target manifest
4174 # make backup if not in target manifest
4174 # make backup if not in target manifest
4175 (modified, revert, remove, True, True),
4175 (modified, revert, remove, True, True),
4176 (added, revert, remove, True, False),
4176 (added, revert, remove, True, False),
4177 (removed, undelete, None, False, False),
4177 (removed, undelete, None, False, False),
4178 (deleted, revert, remove, False, False),
4178 (deleted, revert, remove, False, False),
4179 )
4179 )
4180
4180
4181 for abs, (rel, exact) in sorted(names.items()):
4181 for abs, (rel, exact) in sorted(names.items()):
4182 mfentry = mf.get(abs)
4182 mfentry = mf.get(abs)
4183 target = repo.wjoin(abs)
4183 target = repo.wjoin(abs)
4184 def handle(xlist, dobackup):
4184 def handle(xlist, dobackup):
4185 xlist[0].append(abs)
4185 xlist[0].append(abs)
4186 if (dobackup and not opts.get('no_backup') and
4186 if (dobackup and not opts.get('no_backup') and
4187 os.path.lexists(target)):
4187 os.path.lexists(target)):
4188 bakname = "%s.orig" % rel
4188 bakname = "%s.orig" % rel
4189 ui.note(_('saving current version of %s as %s\n') %
4189 ui.note(_('saving current version of %s as %s\n') %
4190 (rel, bakname))
4190 (rel, bakname))
4191 if not opts.get('dry_run'):
4191 if not opts.get('dry_run'):
4192 util.rename(target, bakname)
4192 util.rename(target, bakname)
4193 if ui.verbose or not exact:
4193 if ui.verbose or not exact:
4194 msg = xlist[1]
4194 msg = xlist[1]
4195 if not isinstance(msg, basestring):
4195 if not isinstance(msg, basestring):
4196 msg = msg(abs)
4196 msg = msg(abs)
4197 ui.status(msg % rel)
4197 ui.status(msg % rel)
4198 for table, hitlist, misslist, backuphit, backupmiss in disptable:
4198 for table, hitlist, misslist, backuphit, backupmiss in disptable:
4199 if abs not in table:
4199 if abs not in table:
4200 continue
4200 continue
4201 # file has changed in dirstate
4201 # file has changed in dirstate
4202 if mfentry:
4202 if mfentry:
4203 handle(hitlist, backuphit)
4203 handle(hitlist, backuphit)
4204 elif misslist is not None:
4204 elif misslist is not None:
4205 handle(misslist, backupmiss)
4205 handle(misslist, backupmiss)
4206 break
4206 break
4207 else:
4207 else:
4208 if abs not in repo.dirstate:
4208 if abs not in repo.dirstate:
4209 if mfentry:
4209 if mfentry:
4210 handle(add, True)
4210 handle(add, True)
4211 elif exact:
4211 elif exact:
4212 ui.warn(_('file not managed: %s\n') % rel)
4212 ui.warn(_('file not managed: %s\n') % rel)
4213 continue
4213 continue
4214 # file has not changed in dirstate
4214 # file has not changed in dirstate
4215 if node == parent:
4215 if node == parent:
4216 if exact:
4216 if exact:
4217 ui.warn(_('no changes needed to %s\n') % rel)
4217 ui.warn(_('no changes needed to %s\n') % rel)
4218 continue
4218 continue
4219 if pmf is None:
4219 if pmf is None:
4220 # only need parent manifest in this unlikely case,
4220 # only need parent manifest in this unlikely case,
4221 # so do not read by default
4221 # so do not read by default
4222 pmf = repo[parent].manifest()
4222 pmf = repo[parent].manifest()
4223 if abs in pmf:
4223 if abs in pmf:
4224 if mfentry:
4224 if mfentry:
4225 # if version of file is same in parent and target
4225 # if version of file is same in parent and target
4226 # manifests, do nothing
4226 # manifests, do nothing
4227 if (pmf[abs] != mfentry or
4227 if (pmf[abs] != mfentry or
4228 pmf.flags(abs) != mf.flags(abs)):
4228 pmf.flags(abs) != mf.flags(abs)):
4229 handle(revert, False)
4229 handle(revert, False)
4230 else:
4230 else:
4231 handle(remove, False)
4231 handle(remove, False)
4232
4232
4233 if not opts.get('dry_run'):
4233 if not opts.get('dry_run'):
4234 def checkout(f):
4234 def checkout(f):
4235 fc = ctx[f]
4235 fc = ctx[f]
4236 repo.wwrite(f, fc.data(), fc.flags())
4236 repo.wwrite(f, fc.data(), fc.flags())
4237
4237
4238 audit_path = scmutil.pathauditor(repo.root)
4238 audit_path = scmutil.pathauditor(repo.root)
4239 for f in remove[0]:
4239 for f in remove[0]:
4240 if repo.dirstate[f] == 'a':
4240 if repo.dirstate[f] == 'a':
4241 repo.dirstate.forget(f)
4241 repo.dirstate.forget(f)
4242 continue
4242 continue
4243 audit_path(f)
4243 audit_path(f)
4244 try:
4244 try:
4245 util.unlinkpath(repo.wjoin(f))
4245 util.unlinkpath(repo.wjoin(f))
4246 except OSError:
4246 except OSError:
4247 pass
4247 pass
4248 repo.dirstate.remove(f)
4248 repo.dirstate.remove(f)
4249
4249
4250 normal = None
4250 normal = None
4251 if node == parent:
4251 if node == parent:
4252 # We're reverting to our parent. If possible, we'd like status
4252 # We're reverting to our parent. If possible, we'd like status
4253 # to report the file as clean. We have to use normallookup for
4253 # to report the file as clean. We have to use normallookup for
4254 # merges to avoid losing information about merged/dirty files.
4254 # merges to avoid losing information about merged/dirty files.
4255 if p2 != nullid:
4255 if p2 != nullid:
4256 normal = repo.dirstate.normallookup
4256 normal = repo.dirstate.normallookup
4257 else:
4257 else:
4258 normal = repo.dirstate.normal
4258 normal = repo.dirstate.normal
4259 for f in revert[0]:
4259 for f in revert[0]:
4260 checkout(f)
4260 checkout(f)
4261 if normal:
4261 if normal:
4262 normal(f)
4262 normal(f)
4263
4263
4264 for f in add[0]:
4264 for f in add[0]:
4265 checkout(f)
4265 checkout(f)
4266 repo.dirstate.add(f)
4266 repo.dirstate.add(f)
4267
4267
4268 normal = repo.dirstate.normallookup
4268 normal = repo.dirstate.normallookup
4269 if node == parent and p2 == nullid:
4269 if node == parent and p2 == nullid:
4270 normal = repo.dirstate.normal
4270 normal = repo.dirstate.normal
4271 for f in undelete[0]:
4271 for f in undelete[0]:
4272 checkout(f)
4272 checkout(f)
4273 normal(f)
4273 normal(f)
4274
4274
4275 finally:
4275 finally:
4276 wlock.release()
4276 wlock.release()
4277
4277
4278 @command('rollback', dryrunopts)
4278 @command('rollback', dryrunopts)
4279 def rollback(ui, repo, **opts):
4279 def rollback(ui, repo, **opts):
4280 """roll back the last transaction (dangerous)
4280 """roll back the last transaction (dangerous)
4281
4281
4282 This command should be used with care. There is only one level of
4282 This command should be used with care. There is only one level of
4283 rollback, and there is no way to undo a rollback. It will also
4283 rollback, and there is no way to undo a rollback. It will also
4284 restore the dirstate at the time of the last transaction, losing
4284 restore the dirstate at the time of the last transaction, losing
4285 any dirstate changes since that time. This command does not alter
4285 any dirstate changes since that time. This command does not alter
4286 the working directory.
4286 the working directory.
4287
4287
4288 Transactions are used to encapsulate the effects of all commands
4288 Transactions are used to encapsulate the effects of all commands
4289 that create new changesets or propagate existing changesets into a
4289 that create new changesets or propagate existing changesets into a
4290 repository. For example, the following commands are transactional,
4290 repository. For example, the following commands are transactional,
4291 and their effects can be rolled back:
4291 and their effects can be rolled back:
4292
4292
4293 - commit
4293 - commit
4294 - import
4294 - import
4295 - pull
4295 - pull
4296 - push (with this repository as the destination)
4296 - push (with this repository as the destination)
4297 - unbundle
4297 - unbundle
4298
4298
4299 This command is not intended for use on public repositories. Once
4299 This command is not intended for use on public repositories. Once
4300 changes are visible for pull by other users, rolling a transaction
4300 changes are visible for pull by other users, rolling a transaction
4301 back locally is ineffective (someone else may already have pulled
4301 back locally is ineffective (someone else may already have pulled
4302 the changes). Furthermore, a race is possible with readers of the
4302 the changes). Furthermore, a race is possible with readers of the
4303 repository; for example an in-progress pull from the repository
4303 repository; for example an in-progress pull from the repository
4304 may fail if a rollback is performed.
4304 may fail if a rollback is performed.
4305
4305
4306 Returns 0 on success, 1 if no rollback data is available.
4306 Returns 0 on success, 1 if no rollback data is available.
4307 """
4307 """
4308 return repo.rollback(opts.get('dry_run'))
4308 return repo.rollback(opts.get('dry_run'))
4309
4309
4310 @command('root', [])
4310 @command('root', [])
4311 def root(ui, repo):
4311 def root(ui, repo):
4312 """print the root (top) of the current working directory
4312 """print the root (top) of the current working directory
4313
4313
4314 Print the root directory of the current repository.
4314 Print the root directory of the current repository.
4315
4315
4316 Returns 0 on success.
4316 Returns 0 on success.
4317 """
4317 """
4318 ui.write(repo.root + "\n")
4318 ui.write(repo.root + "\n")
4319
4319
4320 @command('^serve',
4320 @command('^serve',
4321 [('A', 'accesslog', '', _('name of access log file to write to'),
4321 [('A', 'accesslog', '', _('name of access log file to write to'),
4322 _('FILE')),
4322 _('FILE')),
4323 ('d', 'daemon', None, _('run server in background')),
4323 ('d', 'daemon', None, _('run server in background')),
4324 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
4324 ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')),
4325 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4325 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4326 # use string type, then we can check if something was passed
4326 # use string type, then we can check if something was passed
4327 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4327 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4328 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4328 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4329 _('ADDR')),
4329 _('ADDR')),
4330 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4330 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4331 _('PREFIX')),
4331 _('PREFIX')),
4332 ('n', 'name', '',
4332 ('n', 'name', '',
4333 _('name to show in web pages (default: working directory)'), _('NAME')),
4333 _('name to show in web pages (default: working directory)'), _('NAME')),
4334 ('', 'web-conf', '',
4334 ('', 'web-conf', '',
4335 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
4335 _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')),
4336 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4336 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4337 _('FILE')),
4337 _('FILE')),
4338 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4338 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4339 ('', 'stdio', None, _('for remote clients')),
4339 ('', 'stdio', None, _('for remote clients')),
4340 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4340 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4341 ('', 'style', '', _('template style to use'), _('STYLE')),
4341 ('', 'style', '', _('template style to use'), _('STYLE')),
4342 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4342 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4343 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
4343 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))],
4344 _('[OPTION]...'))
4344 _('[OPTION]...'))
4345 def serve(ui, repo, **opts):
4345 def serve(ui, repo, **opts):
4346 """start stand-alone webserver
4346 """start stand-alone webserver
4347
4347
4348 Start a local HTTP repository browser and pull server. You can use
4348 Start a local HTTP repository browser and pull server. You can use
4349 this for ad-hoc sharing and browsing of repositories. It is
4349 this for ad-hoc sharing and browsing of repositories. It is
4350 recommended to use a real web server to serve a repository for
4350 recommended to use a real web server to serve a repository for
4351 longer periods of time.
4351 longer periods of time.
4352
4352
4353 Please note that the server does not implement access control.
4353 Please note that the server does not implement access control.
4354 This means that, by default, anybody can read from the server and
4354 This means that, by default, anybody can read from the server and
4355 nobody can write to it by default. Set the ``web.allow_push``
4355 nobody can write to it by default. Set the ``web.allow_push``
4356 option to ``*`` to allow everybody to push to the server. You
4356 option to ``*`` to allow everybody to push to the server. You
4357 should use a real web server if you need to authenticate users.
4357 should use a real web server if you need to authenticate users.
4358
4358
4359 By default, the server logs accesses to stdout and errors to
4359 By default, the server logs accesses to stdout and errors to
4360 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4360 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4361 files.
4361 files.
4362
4362
4363 To have the server choose a free port number to listen on, specify
4363 To have the server choose a free port number to listen on, specify
4364 a port number of 0; in this case, the server will print the port
4364 a port number of 0; in this case, the server will print the port
4365 number it uses.
4365 number it uses.
4366
4366
4367 Returns 0 on success.
4367 Returns 0 on success.
4368 """
4368 """
4369
4369
4370 if opts["stdio"]:
4370 if opts["stdio"]:
4371 if repo is None:
4371 if repo is None:
4372 raise error.RepoError(_("There is no Mercurial repository here"
4372 raise error.RepoError(_("There is no Mercurial repository here"
4373 " (.hg not found)"))
4373 " (.hg not found)"))
4374 s = sshserver.sshserver(ui, repo)
4374 s = sshserver.sshserver(ui, repo)
4375 s.serve_forever()
4375 s.serve_forever()
4376
4376
4377 # this way we can check if something was given in the command-line
4377 # this way we can check if something was given in the command-line
4378 if opts.get('port'):
4378 if opts.get('port'):
4379 opts['port'] = util.getport(opts.get('port'))
4379 opts['port'] = util.getport(opts.get('port'))
4380
4380
4381 baseui = repo and repo.baseui or ui
4381 baseui = repo and repo.baseui or ui
4382 optlist = ("name templates style address port prefix ipv6"
4382 optlist = ("name templates style address port prefix ipv6"
4383 " accesslog errorlog certificate encoding")
4383 " accesslog errorlog certificate encoding")
4384 for o in optlist.split():
4384 for o in optlist.split():
4385 val = opts.get(o, '')
4385 val = opts.get(o, '')
4386 if val in (None, ''): # should check against default options instead
4386 if val in (None, ''): # should check against default options instead
4387 continue
4387 continue
4388 baseui.setconfig("web", o, val)
4388 baseui.setconfig("web", o, val)
4389 if repo and repo.ui != baseui:
4389 if repo and repo.ui != baseui:
4390 repo.ui.setconfig("web", o, val)
4390 repo.ui.setconfig("web", o, val)
4391
4391
4392 o = opts.get('web_conf') or opts.get('webdir_conf')
4392 o = opts.get('web_conf') or opts.get('webdir_conf')
4393 if not o:
4393 if not o:
4394 if not repo:
4394 if not repo:
4395 raise error.RepoError(_("There is no Mercurial repository"
4395 raise error.RepoError(_("There is no Mercurial repository"
4396 " here (.hg not found)"))
4396 " here (.hg not found)"))
4397 o = repo.root
4397 o = repo.root
4398
4398
4399 app = hgweb.hgweb(o, baseui=ui)
4399 app = hgweb.hgweb(o, baseui=ui)
4400
4400
4401 class service(object):
4401 class service(object):
4402 def init(self):
4402 def init(self):
4403 util.setsignalhandler()
4403 util.setsignalhandler()
4404 self.httpd = hgweb.server.create_server(ui, app)
4404 self.httpd = hgweb.server.create_server(ui, app)
4405
4405
4406 if opts['port'] and not ui.verbose:
4406 if opts['port'] and not ui.verbose:
4407 return
4407 return
4408
4408
4409 if self.httpd.prefix:
4409 if self.httpd.prefix:
4410 prefix = self.httpd.prefix.strip('/') + '/'
4410 prefix = self.httpd.prefix.strip('/') + '/'
4411 else:
4411 else:
4412 prefix = ''
4412 prefix = ''
4413
4413
4414 port = ':%d' % self.httpd.port
4414 port = ':%d' % self.httpd.port
4415 if port == ':80':
4415 if port == ':80':
4416 port = ''
4416 port = ''
4417
4417
4418 bindaddr = self.httpd.addr
4418 bindaddr = self.httpd.addr
4419 if bindaddr == '0.0.0.0':
4419 if bindaddr == '0.0.0.0':
4420 bindaddr = '*'
4420 bindaddr = '*'
4421 elif ':' in bindaddr: # IPv6
4421 elif ':' in bindaddr: # IPv6
4422 bindaddr = '[%s]' % bindaddr
4422 bindaddr = '[%s]' % bindaddr
4423
4423
4424 fqaddr = self.httpd.fqaddr
4424 fqaddr = self.httpd.fqaddr
4425 if ':' in fqaddr:
4425 if ':' in fqaddr:
4426 fqaddr = '[%s]' % fqaddr
4426 fqaddr = '[%s]' % fqaddr
4427 if opts['port']:
4427 if opts['port']:
4428 write = ui.status
4428 write = ui.status
4429 else:
4429 else:
4430 write = ui.write
4430 write = ui.write
4431 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
4431 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
4432 (fqaddr, port, prefix, bindaddr, self.httpd.port))
4432 (fqaddr, port, prefix, bindaddr, self.httpd.port))
4433
4433
4434 def run(self):
4434 def run(self):
4435 self.httpd.serve_forever()
4435 self.httpd.serve_forever()
4436
4436
4437 service = service()
4437 service = service()
4438
4438
4439 cmdutil.service(opts, initfn=service.init, runfn=service.run)
4439 cmdutil.service(opts, initfn=service.init, runfn=service.run)
4440
4440
4441 @command('showconfig|debugconfig',
4441 @command('showconfig|debugconfig',
4442 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4442 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4443 _('[-u] [NAME]...'))
4443 _('[-u] [NAME]...'))
4444 def showconfig(ui, repo, *values, **opts):
4444 def showconfig(ui, repo, *values, **opts):
4445 """show combined config settings from all hgrc files
4445 """show combined config settings from all hgrc files
4446
4446
4447 With no arguments, print names and values of all config items.
4447 With no arguments, print names and values of all config items.
4448
4448
4449 With one argument of the form section.name, print just the value
4449 With one argument of the form section.name, print just the value
4450 of that config item.
4450 of that config item.
4451
4451
4452 With multiple arguments, print names and values of all config
4452 With multiple arguments, print names and values of all config
4453 items with matching section names.
4453 items with matching section names.
4454
4454
4455 With --debug, the source (filename and line number) is printed
4455 With --debug, the source (filename and line number) is printed
4456 for each config item.
4456 for each config item.
4457
4457
4458 Returns 0 on success.
4458 Returns 0 on success.
4459 """
4459 """
4460
4460
4461 for f in scmutil.rcpath():
4461 for f in scmutil.rcpath():
4462 ui.debug(_('read config from: %s\n') % f)
4462 ui.debug(_('read config from: %s\n') % f)
4463 untrusted = bool(opts.get('untrusted'))
4463 untrusted = bool(opts.get('untrusted'))
4464 if values:
4464 if values:
4465 sections = [v for v in values if '.' not in v]
4465 sections = [v for v in values if '.' not in v]
4466 items = [v for v in values if '.' in v]
4466 items = [v for v in values if '.' in v]
4467 if len(items) > 1 or items and sections:
4467 if len(items) > 1 or items and sections:
4468 raise util.Abort(_('only one config item permitted'))
4468 raise util.Abort(_('only one config item permitted'))
4469 for section, name, value in ui.walkconfig(untrusted=untrusted):
4469 for section, name, value in ui.walkconfig(untrusted=untrusted):
4470 value = str(value).replace('\n', '\\n')
4470 value = str(value).replace('\n', '\\n')
4471 sectname = section + '.' + name
4471 sectname = section + '.' + name
4472 if values:
4472 if values:
4473 for v in values:
4473 for v in values:
4474 if v == section:
4474 if v == section:
4475 ui.debug('%s: ' %
4475 ui.debug('%s: ' %
4476 ui.configsource(section, name, untrusted))
4476 ui.configsource(section, name, untrusted))
4477 ui.write('%s=%s\n' % (sectname, value))
4477 ui.write('%s=%s\n' % (sectname, value))
4478 elif v == sectname:
4478 elif v == sectname:
4479 ui.debug('%s: ' %
4479 ui.debug('%s: ' %
4480 ui.configsource(section, name, untrusted))
4480 ui.configsource(section, name, untrusted))
4481 ui.write(value, '\n')
4481 ui.write(value, '\n')
4482 else:
4482 else:
4483 ui.debug('%s: ' %
4483 ui.debug('%s: ' %
4484 ui.configsource(section, name, untrusted))
4484 ui.configsource(section, name, untrusted))
4485 ui.write('%s=%s\n' % (sectname, value))
4485 ui.write('%s=%s\n' % (sectname, value))
4486
4486
4487 @command('^status|st',
4487 @command('^status|st',
4488 [('A', 'all', None, _('show status of all files')),
4488 [('A', 'all', None, _('show status of all files')),
4489 ('m', 'modified', None, _('show only modified files')),
4489 ('m', 'modified', None, _('show only modified files')),
4490 ('a', 'added', None, _('show only added files')),
4490 ('a', 'added', None, _('show only added files')),
4491 ('r', 'removed', None, _('show only removed files')),
4491 ('r', 'removed', None, _('show only removed files')),
4492 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4492 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4493 ('c', 'clean', None, _('show only files without changes')),
4493 ('c', 'clean', None, _('show only files without changes')),
4494 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4494 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4495 ('i', 'ignored', None, _('show only ignored files')),
4495 ('i', 'ignored', None, _('show only ignored files')),
4496 ('n', 'no-status', None, _('hide status prefix')),
4496 ('n', 'no-status', None, _('hide status prefix')),
4497 ('C', 'copies', None, _('show source of copied files')),
4497 ('C', 'copies', None, _('show source of copied files')),
4498 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4498 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4499 ('', 'rev', [], _('show difference from revision'), _('REV')),
4499 ('', 'rev', [], _('show difference from revision'), _('REV')),
4500 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4500 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4501 ] + walkopts + subrepoopts,
4501 ] + walkopts + subrepoopts,
4502 _('[OPTION]... [FILE]...'))
4502 _('[OPTION]... [FILE]...'))
4503 def status(ui, repo, *pats, **opts):
4503 def status(ui, repo, *pats, **opts):
4504 """show changed files in the working directory
4504 """show changed files in the working directory
4505
4505
4506 Show status of files in the repository. If names are given, only
4506 Show status of files in the repository. If names are given, only
4507 files that match are shown. Files that are clean or ignored or
4507 files that match are shown. Files that are clean or ignored or
4508 the source of a copy/move operation, are not listed unless
4508 the source of a copy/move operation, are not listed unless
4509 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4509 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4510 Unless options described with "show only ..." are given, the
4510 Unless options described with "show only ..." are given, the
4511 options -mardu are used.
4511 options -mardu are used.
4512
4512
4513 Option -q/--quiet hides untracked (unknown and ignored) files
4513 Option -q/--quiet hides untracked (unknown and ignored) files
4514 unless explicitly requested with -u/--unknown or -i/--ignored.
4514 unless explicitly requested with -u/--unknown or -i/--ignored.
4515
4515
4516 .. note::
4516 .. note::
4517 status may appear to disagree with diff if permissions have
4517 status may appear to disagree with diff if permissions have
4518 changed or a merge has occurred. The standard diff format does
4518 changed or a merge has occurred. The standard diff format does
4519 not report permission changes and diff only reports changes
4519 not report permission changes and diff only reports changes
4520 relative to one merge parent.
4520 relative to one merge parent.
4521
4521
4522 If one revision is given, it is used as the base revision.
4522 If one revision is given, it is used as the base revision.
4523 If two revisions are given, the differences between them are
4523 If two revisions are given, the differences between them are
4524 shown. The --change option can also be used as a shortcut to list
4524 shown. The --change option can also be used as a shortcut to list
4525 the changed files of a revision from its first parent.
4525 the changed files of a revision from its first parent.
4526
4526
4527 The codes used to show the status of files are::
4527 The codes used to show the status of files are::
4528
4528
4529 M = modified
4529 M = modified
4530 A = added
4530 A = added
4531 R = removed
4531 R = removed
4532 C = clean
4532 C = clean
4533 ! = missing (deleted by non-hg command, but still tracked)
4533 ! = missing (deleted by non-hg command, but still tracked)
4534 ? = not tracked
4534 ? = not tracked
4535 I = ignored
4535 I = ignored
4536 = origin of the previous file listed as A (added)
4536 = origin of the previous file listed as A (added)
4537
4537
4538 Returns 0 on success.
4538 Returns 0 on success.
4539 """
4539 """
4540
4540
4541 revs = opts.get('rev')
4541 revs = opts.get('rev')
4542 change = opts.get('change')
4542 change = opts.get('change')
4543
4543
4544 if revs and change:
4544 if revs and change:
4545 msg = _('cannot specify --rev and --change at the same time')
4545 msg = _('cannot specify --rev and --change at the same time')
4546 raise util.Abort(msg)
4546 raise util.Abort(msg)
4547 elif change:
4547 elif change:
4548 node2 = repo.lookup(change)
4548 node2 = repo.lookup(change)
4549 node1 = repo[node2].p1().node()
4549 node1 = repo[node2].p1().node()
4550 else:
4550 else:
4551 node1, node2 = scmutil.revpair(repo, revs)
4551 node1, node2 = scmutil.revpair(repo, revs)
4552
4552
4553 cwd = (pats and repo.getcwd()) or ''
4553 cwd = (pats and repo.getcwd()) or ''
4554 end = opts.get('print0') and '\0' or '\n'
4554 end = opts.get('print0') and '\0' or '\n'
4555 copy = {}
4555 copy = {}
4556 states = 'modified added removed deleted unknown ignored clean'.split()
4556 states = 'modified added removed deleted unknown ignored clean'.split()
4557 show = [k for k in states if opts.get(k)]
4557 show = [k for k in states if opts.get(k)]
4558 if opts.get('all'):
4558 if opts.get('all'):
4559 show += ui.quiet and (states[:4] + ['clean']) or states
4559 show += ui.quiet and (states[:4] + ['clean']) or states
4560 if not show:
4560 if not show:
4561 show = ui.quiet and states[:4] or states[:5]
4561 show = ui.quiet and states[:4] or states[:5]
4562
4562
4563 stat = repo.status(node1, node2, scmutil.match(repo, pats, opts),
4563 stat = repo.status(node1, node2, scmutil.match(repo, pats, opts),
4564 'ignored' in show, 'clean' in show, 'unknown' in show,
4564 'ignored' in show, 'clean' in show, 'unknown' in show,
4565 opts.get('subrepos'))
4565 opts.get('subrepos'))
4566 changestates = zip(states, 'MAR!?IC', stat)
4566 changestates = zip(states, 'MAR!?IC', stat)
4567
4567
4568 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
4568 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
4569 ctxn = repo[nullid]
4569 ctxn = repo[nullid]
4570 ctx1 = repo[node1]
4570 ctx1 = repo[node1]
4571 ctx2 = repo[node2]
4571 ctx2 = repo[node2]
4572 added = stat[1]
4572 added = stat[1]
4573 if node2 is None:
4573 if node2 is None:
4574 added = stat[0] + stat[1] # merged?
4574 added = stat[0] + stat[1] # merged?
4575
4575
4576 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
4576 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
4577 if k in added:
4577 if k in added:
4578 copy[k] = v
4578 copy[k] = v
4579 elif v in added:
4579 elif v in added:
4580 copy[v] = k
4580 copy[v] = k
4581
4581
4582 for state, char, files in changestates:
4582 for state, char, files in changestates:
4583 if state in show:
4583 if state in show:
4584 format = "%s %%s%s" % (char, end)
4584 format = "%s %%s%s" % (char, end)
4585 if opts.get('no_status'):
4585 if opts.get('no_status'):
4586 format = "%%s%s" % end
4586 format = "%%s%s" % end
4587
4587
4588 for f in files:
4588 for f in files:
4589 ui.write(format % repo.pathto(f, cwd),
4589 ui.write(format % repo.pathto(f, cwd),
4590 label='status.' + state)
4590 label='status.' + state)
4591 if f in copy:
4591 if f in copy:
4592 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
4592 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
4593 label='status.copied')
4593 label='status.copied')
4594
4594
4595 @command('^summary|sum',
4595 @command('^summary|sum',
4596 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
4596 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
4597 def summary(ui, repo, **opts):
4597 def summary(ui, repo, **opts):
4598 """summarize working directory state
4598 """summarize working directory state
4599
4599
4600 This generates a brief summary of the working directory state,
4600 This generates a brief summary of the working directory state,
4601 including parents, branch, commit status, and available updates.
4601 including parents, branch, commit status, and available updates.
4602
4602
4603 With the --remote option, this will check the default paths for
4603 With the --remote option, this will check the default paths for
4604 incoming and outgoing changes. This can be time-consuming.
4604 incoming and outgoing changes. This can be time-consuming.
4605
4605
4606 Returns 0 on success.
4606 Returns 0 on success.
4607 """
4607 """
4608
4608
4609 ctx = repo[None]
4609 ctx = repo[None]
4610 parents = ctx.parents()
4610 parents = ctx.parents()
4611 pnode = parents[0].node()
4611 pnode = parents[0].node()
4612
4612
4613 for p in parents:
4613 for p in parents:
4614 # label with log.changeset (instead of log.parent) since this
4614 # label with log.changeset (instead of log.parent) since this
4615 # shows a working directory parent *changeset*:
4615 # shows a working directory parent *changeset*:
4616 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
4616 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
4617 label='log.changeset')
4617 label='log.changeset')
4618 ui.write(' '.join(p.tags()), label='log.tag')
4618 ui.write(' '.join(p.tags()), label='log.tag')
4619 if p.bookmarks():
4619 if p.bookmarks():
4620 ui.write(' ' + ' '.join(p.bookmarks()), label='log.bookmark')
4620 ui.write(' ' + ' '.join(p.bookmarks()), label='log.bookmark')
4621 if p.rev() == -1:
4621 if p.rev() == -1:
4622 if not len(repo):
4622 if not len(repo):
4623 ui.write(_(' (empty repository)'))
4623 ui.write(_(' (empty repository)'))
4624 else:
4624 else:
4625 ui.write(_(' (no revision checked out)'))
4625 ui.write(_(' (no revision checked out)'))
4626 ui.write('\n')
4626 ui.write('\n')
4627 if p.description():
4627 if p.description():
4628 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
4628 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
4629 label='log.summary')
4629 label='log.summary')
4630
4630
4631 branch = ctx.branch()
4631 branch = ctx.branch()
4632 bheads = repo.branchheads(branch)
4632 bheads = repo.branchheads(branch)
4633 m = _('branch: %s\n') % branch
4633 m = _('branch: %s\n') % branch
4634 if branch != 'default':
4634 if branch != 'default':
4635 ui.write(m, label='log.branch')
4635 ui.write(m, label='log.branch')
4636 else:
4636 else:
4637 ui.status(m, label='log.branch')
4637 ui.status(m, label='log.branch')
4638
4638
4639 st = list(repo.status(unknown=True))[:6]
4639 st = list(repo.status(unknown=True))[:6]
4640
4640
4641 c = repo.dirstate.copies()
4641 c = repo.dirstate.copies()
4642 copied, renamed = [], []
4642 copied, renamed = [], []
4643 for d, s in c.iteritems():
4643 for d, s in c.iteritems():
4644 if s in st[2]:
4644 if s in st[2]:
4645 st[2].remove(s)
4645 st[2].remove(s)
4646 renamed.append(d)
4646 renamed.append(d)
4647 else:
4647 else:
4648 copied.append(d)
4648 copied.append(d)
4649 if d in st[1]:
4649 if d in st[1]:
4650 st[1].remove(d)
4650 st[1].remove(d)
4651 st.insert(3, renamed)
4651 st.insert(3, renamed)
4652 st.insert(4, copied)
4652 st.insert(4, copied)
4653
4653
4654 ms = mergemod.mergestate(repo)
4654 ms = mergemod.mergestate(repo)
4655 st.append([f for f in ms if ms[f] == 'u'])
4655 st.append([f for f in ms if ms[f] == 'u'])
4656
4656
4657 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
4657 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
4658 st.append(subs)
4658 st.append(subs)
4659
4659
4660 labels = [ui.label(_('%d modified'), 'status.modified'),
4660 labels = [ui.label(_('%d modified'), 'status.modified'),
4661 ui.label(_('%d added'), 'status.added'),
4661 ui.label(_('%d added'), 'status.added'),
4662 ui.label(_('%d removed'), 'status.removed'),
4662 ui.label(_('%d removed'), 'status.removed'),
4663 ui.label(_('%d renamed'), 'status.copied'),
4663 ui.label(_('%d renamed'), 'status.copied'),
4664 ui.label(_('%d copied'), 'status.copied'),
4664 ui.label(_('%d copied'), 'status.copied'),
4665 ui.label(_('%d deleted'), 'status.deleted'),
4665 ui.label(_('%d deleted'), 'status.deleted'),
4666 ui.label(_('%d unknown'), 'status.unknown'),
4666 ui.label(_('%d unknown'), 'status.unknown'),
4667 ui.label(_('%d ignored'), 'status.ignored'),
4667 ui.label(_('%d ignored'), 'status.ignored'),
4668 ui.label(_('%d unresolved'), 'resolve.unresolved'),
4668 ui.label(_('%d unresolved'), 'resolve.unresolved'),
4669 ui.label(_('%d subrepos'), 'status.modified')]
4669 ui.label(_('%d subrepos'), 'status.modified')]
4670 t = []
4670 t = []
4671 for s, l in zip(st, labels):
4671 for s, l in zip(st, labels):
4672 if s:
4672 if s:
4673 t.append(l % len(s))
4673 t.append(l % len(s))
4674
4674
4675 t = ', '.join(t)
4675 t = ', '.join(t)
4676 cleanworkdir = False
4676 cleanworkdir = False
4677
4677
4678 if len(parents) > 1:
4678 if len(parents) > 1:
4679 t += _(' (merge)')
4679 t += _(' (merge)')
4680 elif branch != parents[0].branch():
4680 elif branch != parents[0].branch():
4681 t += _(' (new branch)')
4681 t += _(' (new branch)')
4682 elif (parents[0].extra().get('close') and
4682 elif (parents[0].extra().get('close') and
4683 pnode in repo.branchheads(branch, closed=True)):
4683 pnode in repo.branchheads(branch, closed=True)):
4684 t += _(' (head closed)')
4684 t += _(' (head closed)')
4685 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
4685 elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
4686 t += _(' (clean)')
4686 t += _(' (clean)')
4687 cleanworkdir = True
4687 cleanworkdir = True
4688 elif pnode not in bheads:
4688 elif pnode not in bheads:
4689 t += _(' (new branch head)')
4689 t += _(' (new branch head)')
4690
4690
4691 if cleanworkdir:
4691 if cleanworkdir:
4692 ui.status(_('commit: %s\n') % t.strip())
4692 ui.status(_('commit: %s\n') % t.strip())
4693 else:
4693 else:
4694 ui.write(_('commit: %s\n') % t.strip())
4694 ui.write(_('commit: %s\n') % t.strip())
4695
4695
4696 # all ancestors of branch heads - all ancestors of parent = new csets
4696 # all ancestors of branch heads - all ancestors of parent = new csets
4697 new = [0] * len(repo)
4697 new = [0] * len(repo)
4698 cl = repo.changelog
4698 cl = repo.changelog
4699 for a in [cl.rev(n) for n in bheads]:
4699 for a in [cl.rev(n) for n in bheads]:
4700 new[a] = 1
4700 new[a] = 1
4701 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
4701 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
4702 new[a] = 1
4702 new[a] = 1
4703 for a in [p.rev() for p in parents]:
4703 for a in [p.rev() for p in parents]:
4704 if a >= 0:
4704 if a >= 0:
4705 new[a] = 0
4705 new[a] = 0
4706 for a in cl.ancestors(*[p.rev() for p in parents]):
4706 for a in cl.ancestors(*[p.rev() for p in parents]):
4707 new[a] = 0
4707 new[a] = 0
4708 new = sum(new)
4708 new = sum(new)
4709
4709
4710 if new == 0:
4710 if new == 0:
4711 ui.status(_('update: (current)\n'))
4711 ui.status(_('update: (current)\n'))
4712 elif pnode not in bheads:
4712 elif pnode not in bheads:
4713 ui.write(_('update: %d new changesets (update)\n') % new)
4713 ui.write(_('update: %d new changesets (update)\n') % new)
4714 else:
4714 else:
4715 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
4715 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
4716 (new, len(bheads)))
4716 (new, len(bheads)))
4717
4717
4718 if opts.get('remote'):
4718 if opts.get('remote'):
4719 t = []
4719 t = []
4720 source, branches = hg.parseurl(ui.expandpath('default'))
4720 source, branches = hg.parseurl(ui.expandpath('default'))
4721 other = hg.repository(hg.remoteui(repo, {}), source)
4721 other = hg.repository(hg.remoteui(repo, {}), source)
4722 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4722 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
4723 ui.debug('comparing with %s\n' % util.hidepassword(source))
4723 ui.debug('comparing with %s\n' % util.hidepassword(source))
4724 repo.ui.pushbuffer()
4724 repo.ui.pushbuffer()
4725 commoninc = discovery.findcommonincoming(repo, other)
4725 commoninc = discovery.findcommonincoming(repo, other)
4726 _common, incoming, _rheads = commoninc
4726 _common, incoming, _rheads = commoninc
4727 repo.ui.popbuffer()
4727 repo.ui.popbuffer()
4728 if incoming:
4728 if incoming:
4729 t.append(_('1 or more incoming'))
4729 t.append(_('1 or more incoming'))
4730
4730
4731 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
4731 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
4732 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
4732 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
4733 if source != dest:
4733 if source != dest:
4734 other = hg.repository(hg.remoteui(repo, {}), dest)
4734 other = hg.repository(hg.remoteui(repo, {}), dest)
4735 commoninc = None
4735 commoninc = None
4736 ui.debug('comparing with %s\n' % util.hidepassword(dest))
4736 ui.debug('comparing with %s\n' % util.hidepassword(dest))
4737 repo.ui.pushbuffer()
4737 repo.ui.pushbuffer()
4738 common, outheads = discovery.findcommonoutgoing(repo, other,
4738 common, outheads = discovery.findcommonoutgoing(repo, other,
4739 commoninc=commoninc)
4739 commoninc=commoninc)
4740 repo.ui.popbuffer()
4740 repo.ui.popbuffer()
4741 o = repo.changelog.findmissing(common=common, heads=outheads)
4741 o = repo.changelog.findmissing(common=common, heads=outheads)
4742 if o:
4742 if o:
4743 t.append(_('%d outgoing') % len(o))
4743 t.append(_('%d outgoing') % len(o))
4744 if 'bookmarks' in other.listkeys('namespaces'):
4744 if 'bookmarks' in other.listkeys('namespaces'):
4745 lmarks = repo.listkeys('bookmarks')
4745 lmarks = repo.listkeys('bookmarks')
4746 rmarks = other.listkeys('bookmarks')
4746 rmarks = other.listkeys('bookmarks')
4747 diff = set(rmarks) - set(lmarks)
4747 diff = set(rmarks) - set(lmarks)
4748 if len(diff) > 0:
4748 if len(diff) > 0:
4749 t.append(_('%d incoming bookmarks') % len(diff))
4749 t.append(_('%d incoming bookmarks') % len(diff))
4750 diff = set(lmarks) - set(rmarks)
4750 diff = set(lmarks) - set(rmarks)
4751 if len(diff) > 0:
4751 if len(diff) > 0:
4752 t.append(_('%d outgoing bookmarks') % len(diff))
4752 t.append(_('%d outgoing bookmarks') % len(diff))
4753
4753
4754 if t:
4754 if t:
4755 ui.write(_('remote: %s\n') % (', '.join(t)))
4755 ui.write(_('remote: %s\n') % (', '.join(t)))
4756 else:
4756 else:
4757 ui.status(_('remote: (synced)\n'))
4757 ui.status(_('remote: (synced)\n'))
4758
4758
4759 @command('tag',
4759 @command('tag',
4760 [('f', 'force', None, _('force tag')),
4760 [('f', 'force', None, _('force tag')),
4761 ('l', 'local', None, _('make the tag local')),
4761 ('l', 'local', None, _('make the tag local')),
4762 ('r', 'rev', '', _('revision to tag'), _('REV')),
4762 ('r', 'rev', '', _('revision to tag'), _('REV')),
4763 ('', 'remove', None, _('remove a tag')),
4763 ('', 'remove', None, _('remove a tag')),
4764 # -l/--local is already there, commitopts cannot be used
4764 # -l/--local is already there, commitopts cannot be used
4765 ('e', 'edit', None, _('edit commit message')),
4765 ('e', 'edit', None, _('edit commit message')),
4766 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
4766 ('m', 'message', '', _('use <text> as commit message'), _('TEXT')),
4767 ] + commitopts2,
4767 ] + commitopts2,
4768 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
4768 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
4769 def tag(ui, repo, name1, *names, **opts):
4769 def tag(ui, repo, name1, *names, **opts):
4770 """add one or more tags for the current or given revision
4770 """add one or more tags for the current or given revision
4771
4771
4772 Name a particular revision using <name>.
4772 Name a particular revision using <name>.
4773
4773
4774 Tags are used to name particular revisions of the repository and are
4774 Tags are used to name particular revisions of the repository and are
4775 very useful to compare different revisions, to go back to significant
4775 very useful to compare different revisions, to go back to significant
4776 earlier versions or to mark branch points as releases, etc. Changing
4776 earlier versions or to mark branch points as releases, etc. Changing
4777 an existing tag is normally disallowed; use -f/--force to override.
4777 an existing tag is normally disallowed; use -f/--force to override.
4778
4778
4779 If no revision is given, the parent of the working directory is
4779 If no revision is given, the parent of the working directory is
4780 used, or tip if no revision is checked out.
4780 used, or tip if no revision is checked out.
4781
4781
4782 To facilitate version control, distribution, and merging of tags,
4782 To facilitate version control, distribution, and merging of tags,
4783 they are stored as a file named ".hgtags" which is managed similarly
4783 they are stored as a file named ".hgtags" which is managed similarly
4784 to other project files and can be hand-edited if necessary. This
4784 to other project files and can be hand-edited if necessary. This
4785 also means that tagging creates a new commit. The file
4785 also means that tagging creates a new commit. The file
4786 ".hg/localtags" is used for local tags (not shared among
4786 ".hg/localtags" is used for local tags (not shared among
4787 repositories).
4787 repositories).
4788
4788
4789 Tag commits are usually made at the head of a branch. If the parent
4789 Tag commits are usually made at the head of a branch. If the parent
4790 of the working directory is not a branch head, :hg:`tag` aborts; use
4790 of the working directory is not a branch head, :hg:`tag` aborts; use
4791 -f/--force to force the tag commit to be based on a non-head
4791 -f/--force to force the tag commit to be based on a non-head
4792 changeset.
4792 changeset.
4793
4793
4794 See :hg:`help dates` for a list of formats valid for -d/--date.
4794 See :hg:`help dates` for a list of formats valid for -d/--date.
4795
4795
4796 Since tag names have priority over branch names during revision
4796 Since tag names have priority over branch names during revision
4797 lookup, using an existing branch name as a tag name is discouraged.
4797 lookup, using an existing branch name as a tag name is discouraged.
4798
4798
4799 Returns 0 on success.
4799 Returns 0 on success.
4800 """
4800 """
4801
4801
4802 rev_ = "."
4802 rev_ = "."
4803 names = [t.strip() for t in (name1,) + names]
4803 names = [t.strip() for t in (name1,) + names]
4804 if len(names) != len(set(names)):
4804 if len(names) != len(set(names)):
4805 raise util.Abort(_('tag names must be unique'))
4805 raise util.Abort(_('tag names must be unique'))
4806 for n in names:
4806 for n in names:
4807 if n in ['tip', '.', 'null']:
4807 if n in ['tip', '.', 'null']:
4808 raise util.Abort(_("the name '%s' is reserved") % n)
4808 raise util.Abort(_("the name '%s' is reserved") % n)
4809 if not n:
4809 if not n:
4810 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
4810 raise util.Abort(_('tag names cannot consist entirely of whitespace'))
4811 if opts.get('rev') and opts.get('remove'):
4811 if opts.get('rev') and opts.get('remove'):
4812 raise util.Abort(_("--rev and --remove are incompatible"))
4812 raise util.Abort(_("--rev and --remove are incompatible"))
4813 if opts.get('rev'):
4813 if opts.get('rev'):
4814 rev_ = opts['rev']
4814 rev_ = opts['rev']
4815 message = opts.get('message')
4815 message = opts.get('message')
4816 if opts.get('remove'):
4816 if opts.get('remove'):
4817 expectedtype = opts.get('local') and 'local' or 'global'
4817 expectedtype = opts.get('local') and 'local' or 'global'
4818 for n in names:
4818 for n in names:
4819 if not repo.tagtype(n):
4819 if not repo.tagtype(n):
4820 raise util.Abort(_("tag '%s' does not exist") % n)
4820 raise util.Abort(_("tag '%s' does not exist") % n)
4821 if repo.tagtype(n) != expectedtype:
4821 if repo.tagtype(n) != expectedtype:
4822 if expectedtype == 'global':
4822 if expectedtype == 'global':
4823 raise util.Abort(_("tag '%s' is not a global tag") % n)
4823 raise util.Abort(_("tag '%s' is not a global tag") % n)
4824 else:
4824 else:
4825 raise util.Abort(_("tag '%s' is not a local tag") % n)
4825 raise util.Abort(_("tag '%s' is not a local tag") % n)
4826 rev_ = nullid
4826 rev_ = nullid
4827 if not message:
4827 if not message:
4828 # we don't translate commit messages
4828 # we don't translate commit messages
4829 message = 'Removed tag %s' % ', '.join(names)
4829 message = 'Removed tag %s' % ', '.join(names)
4830 elif not opts.get('force'):
4830 elif not opts.get('force'):
4831 for n in names:
4831 for n in names:
4832 if n in repo.tags():
4832 if n in repo.tags():
4833 raise util.Abort(_("tag '%s' already exists "
4833 raise util.Abort(_("tag '%s' already exists "
4834 "(use -f to force)") % n)
4834 "(use -f to force)") % n)
4835 if not opts.get('local'):
4835 if not opts.get('local'):
4836 p1, p2 = repo.dirstate.parents()
4836 p1, p2 = repo.dirstate.parents()
4837 if p2 != nullid:
4837 if p2 != nullid:
4838 raise util.Abort(_('uncommitted merge'))
4838 raise util.Abort(_('uncommitted merge'))
4839 bheads = repo.branchheads()
4839 bheads = repo.branchheads()
4840 if not opts.get('force') and bheads and p1 not in bheads:
4840 if not opts.get('force') and bheads and p1 not in bheads:
4841 raise util.Abort(_('not at a branch head (use -f to force)'))
4841 raise util.Abort(_('not at a branch head (use -f to force)'))
4842 r = scmutil.revsingle(repo, rev_).node()
4842 r = scmutil.revsingle(repo, rev_).node()
4843
4843
4844 if not message:
4844 if not message:
4845 # we don't translate commit messages
4845 # we don't translate commit messages
4846 message = ('Added tag %s for changeset %s' %
4846 message = ('Added tag %s for changeset %s' %
4847 (', '.join(names), short(r)))
4847 (', '.join(names), short(r)))
4848
4848
4849 date = opts.get('date')
4849 date = opts.get('date')
4850 if date:
4850 if date:
4851 date = util.parsedate(date)
4851 date = util.parsedate(date)
4852
4852
4853 if opts.get('edit'):
4853 if opts.get('edit'):
4854 message = ui.edit(message, ui.username())
4854 message = ui.edit(message, ui.username())
4855
4855
4856 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
4856 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
4857
4857
4858 @command('tags', [], '')
4858 @command('tags', [], '')
4859 def tags(ui, repo):
4859 def tags(ui, repo):
4860 """list repository tags
4860 """list repository tags
4861
4861
4862 This lists both regular and local tags. When the -v/--verbose
4862 This lists both regular and local tags. When the -v/--verbose
4863 switch is used, a third column "local" is printed for local tags.
4863 switch is used, a third column "local" is printed for local tags.
4864
4864
4865 Returns 0 on success.
4865 Returns 0 on success.
4866 """
4866 """
4867
4867
4868 hexfunc = ui.debugflag and hex or short
4868 hexfunc = ui.debugflag and hex or short
4869 tagtype = ""
4869 tagtype = ""
4870
4870
4871 for t, n in reversed(repo.tagslist()):
4871 for t, n in reversed(repo.tagslist()):
4872 if ui.quiet:
4872 if ui.quiet:
4873 ui.write("%s\n" % t)
4873 ui.write("%s\n" % t)
4874 continue
4874 continue
4875
4875
4876 hn = hexfunc(n)
4876 hn = hexfunc(n)
4877 r = "%5d:%s" % (repo.changelog.rev(n), hn)
4877 r = "%5d:%s" % (repo.changelog.rev(n), hn)
4878 spaces = " " * (30 - encoding.colwidth(t))
4878 spaces = " " * (30 - encoding.colwidth(t))
4879
4879
4880 if ui.verbose:
4880 if ui.verbose:
4881 if repo.tagtype(t) == 'local':
4881 if repo.tagtype(t) == 'local':
4882 tagtype = " local"
4882 tagtype = " local"
4883 else:
4883 else:
4884 tagtype = ""
4884 tagtype = ""
4885 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
4885 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
4886
4886
4887 @command('tip',
4887 @command('tip',
4888 [('p', 'patch', None, _('show patch')),
4888 [('p', 'patch', None, _('show patch')),
4889 ('g', 'git', None, _('use git extended diff format')),
4889 ('g', 'git', None, _('use git extended diff format')),
4890 ] + templateopts,
4890 ] + templateopts,
4891 _('[-p] [-g]'))
4891 _('[-p] [-g]'))
4892 def tip(ui, repo, **opts):
4892 def tip(ui, repo, **opts):
4893 """show the tip revision
4893 """show the tip revision
4894
4894
4895 The tip revision (usually just called the tip) is the changeset
4895 The tip revision (usually just called the tip) is the changeset
4896 most recently added to the repository (and therefore the most
4896 most recently added to the repository (and therefore the most
4897 recently changed head).
4897 recently changed head).
4898
4898
4899 If you have just made a commit, that commit will be the tip. If
4899 If you have just made a commit, that commit will be the tip. If
4900 you have just pulled changes from another repository, the tip of
4900 you have just pulled changes from another repository, the tip of
4901 that repository becomes the current tip. The "tip" tag is special
4901 that repository becomes the current tip. The "tip" tag is special
4902 and cannot be renamed or assigned to a different changeset.
4902 and cannot be renamed or assigned to a different changeset.
4903
4903
4904 Returns 0 on success.
4904 Returns 0 on success.
4905 """
4905 """
4906 displayer = cmdutil.show_changeset(ui, repo, opts)
4906 displayer = cmdutil.show_changeset(ui, repo, opts)
4907 displayer.show(repo[len(repo) - 1])
4907 displayer.show(repo[len(repo) - 1])
4908 displayer.close()
4908 displayer.close()
4909
4909
4910 @command('unbundle',
4910 @command('unbundle',
4911 [('u', 'update', None,
4911 [('u', 'update', None,
4912 _('update to new branch head if changesets were unbundled'))],
4912 _('update to new branch head if changesets were unbundled'))],
4913 _('[-u] FILE...'))
4913 _('[-u] FILE...'))
4914 def unbundle(ui, repo, fname1, *fnames, **opts):
4914 def unbundle(ui, repo, fname1, *fnames, **opts):
4915 """apply one or more changegroup files
4915 """apply one or more changegroup files
4916
4916
4917 Apply one or more compressed changegroup files generated by the
4917 Apply one or more compressed changegroup files generated by the
4918 bundle command.
4918 bundle command.
4919
4919
4920 Returns 0 on success, 1 if an update has unresolved files.
4920 Returns 0 on success, 1 if an update has unresolved files.
4921 """
4921 """
4922 fnames = (fname1,) + fnames
4922 fnames = (fname1,) + fnames
4923
4923
4924 lock = repo.lock()
4924 lock = repo.lock()
4925 wc = repo['.']
4925 wc = repo['.']
4926 try:
4926 try:
4927 for fname in fnames:
4927 for fname in fnames:
4928 f = url.open(ui, fname)
4928 f = url.open(ui, fname)
4929 gen = changegroup.readbundle(f, fname)
4929 gen = changegroup.readbundle(f, fname)
4930 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
4930 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname,
4931 lock=lock)
4931 lock=lock)
4932 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
4932 bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch())
4933 finally:
4933 finally:
4934 lock.release()
4934 lock.release()
4935 return postincoming(ui, repo, modheads, opts.get('update'), None)
4935 return postincoming(ui, repo, modheads, opts.get('update'), None)
4936
4936
4937 @command('^update|up|checkout|co',
4937 @command('^update|up|checkout|co',
4938 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
4938 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
4939 ('c', 'check', None,
4939 ('c', 'check', None,
4940 _('update across branches if no uncommitted changes')),
4940 _('update across branches if no uncommitted changes')),
4941 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4941 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4942 ('r', 'rev', '', _('revision'), _('REV'))],
4942 ('r', 'rev', '', _('revision'), _('REV'))],
4943 _('[-c] [-C] [-d DATE] [[-r] REV]'))
4943 _('[-c] [-C] [-d DATE] [[-r] REV]'))
4944 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
4944 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
4945 """update working directory (or switch revisions)
4945 """update working directory (or switch revisions)
4946
4946
4947 Update the repository's working directory to the specified
4947 Update the repository's working directory to the specified
4948 changeset. If no changeset is specified, update to the tip of the
4948 changeset. If no changeset is specified, update to the tip of the
4949 current named branch.
4949 current named branch.
4950
4950
4951 If the changeset is not a descendant of the working directory's
4951 If the changeset is not a descendant of the working directory's
4952 parent, the update is aborted. With the -c/--check option, the
4952 parent, the update is aborted. With the -c/--check option, the
4953 working directory is checked for uncommitted changes; if none are
4953 working directory is checked for uncommitted changes; if none are
4954 found, the working directory is updated to the specified
4954 found, the working directory is updated to the specified
4955 changeset.
4955 changeset.
4956
4956
4957 The following rules apply when the working directory contains
4957 The following rules apply when the working directory contains
4958 uncommitted changes:
4958 uncommitted changes:
4959
4959
4960 1. If neither -c/--check nor -C/--clean is specified, and if
4960 1. If neither -c/--check nor -C/--clean is specified, and if
4961 the requested changeset is an ancestor or descendant of
4961 the requested changeset is an ancestor or descendant of
4962 the working directory's parent, the uncommitted changes
4962 the working directory's parent, the uncommitted changes
4963 are merged into the requested changeset and the merged
4963 are merged into the requested changeset and the merged
4964 result is left uncommitted. If the requested changeset is
4964 result is left uncommitted. If the requested changeset is
4965 not an ancestor or descendant (that is, it is on another
4965 not an ancestor or descendant (that is, it is on another
4966 branch), the update is aborted and the uncommitted changes
4966 branch), the update is aborted and the uncommitted changes
4967 are preserved.
4967 are preserved.
4968
4968
4969 2. With the -c/--check option, the update is aborted and the
4969 2. With the -c/--check option, the update is aborted and the
4970 uncommitted changes are preserved.
4970 uncommitted changes are preserved.
4971
4971
4972 3. With the -C/--clean option, uncommitted changes are discarded and
4972 3. With the -C/--clean option, uncommitted changes are discarded and
4973 the working directory is updated to the requested changeset.
4973 the working directory is updated to the requested changeset.
4974
4974
4975 Use null as the changeset to remove the working directory (like
4975 Use null as the changeset to remove the working directory (like
4976 :hg:`clone -U`).
4976 :hg:`clone -U`).
4977
4977
4978 If you want to update just one file to an older changeset, use
4978 If you want to update just one file to an older changeset, use
4979 :hg:`revert`.
4979 :hg:`revert`.
4980
4980
4981 See :hg:`help dates` for a list of formats valid for -d/--date.
4981 See :hg:`help dates` for a list of formats valid for -d/--date.
4982
4982
4983 Returns 0 on success, 1 if there are unresolved files.
4983 Returns 0 on success, 1 if there are unresolved files.
4984 """
4984 """
4985 if rev and node:
4985 if rev and node:
4986 raise util.Abort(_("please specify just one revision"))
4986 raise util.Abort(_("please specify just one revision"))
4987
4987
4988 if rev is None or rev == '':
4988 if rev is None or rev == '':
4989 rev = node
4989 rev = node
4990
4990
4991 # if we defined a bookmark, we have to remember the original bookmark name
4991 # if we defined a bookmark, we have to remember the original bookmark name
4992 brev = rev
4992 brev = rev
4993 rev = scmutil.revsingle(repo, rev, rev).rev()
4993 rev = scmutil.revsingle(repo, rev, rev).rev()
4994
4994
4995 if check and clean:
4995 if check and clean:
4996 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
4996 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
4997
4997
4998 if check:
4998 if check:
4999 # we could use dirty() but we can ignore merge and branch trivia
4999 # we could use dirty() but we can ignore merge and branch trivia
5000 c = repo[None]
5000 c = repo[None]
5001 if c.modified() or c.added() or c.removed():
5001 if c.modified() or c.added() or c.removed():
5002 raise util.Abort(_("uncommitted local changes"))
5002 raise util.Abort(_("uncommitted local changes"))
5003
5003
5004 if date:
5004 if date:
5005 if rev is not None:
5005 if rev is not None:
5006 raise util.Abort(_("you can't specify a revision and a date"))
5006 raise util.Abort(_("you can't specify a revision and a date"))
5007 rev = cmdutil.finddate(ui, repo, date)
5007 rev = cmdutil.finddate(ui, repo, date)
5008
5008
5009 if clean or check:
5009 if clean or check:
5010 ret = hg.clean(repo, rev)
5010 ret = hg.clean(repo, rev)
5011 else:
5011 else:
5012 ret = hg.update(repo, rev)
5012 ret = hg.update(repo, rev)
5013
5013
5014 if brev in repo._bookmarks:
5014 if brev in repo._bookmarks:
5015 bookmarks.setcurrent(repo, brev)
5015 bookmarks.setcurrent(repo, brev)
5016
5016
5017 return ret
5017 return ret
5018
5018
5019 @command('verify', [])
5019 @command('verify', [])
5020 def verify(ui, repo):
5020 def verify(ui, repo):
5021 """verify the integrity of the repository
5021 """verify the integrity of the repository
5022
5022
5023 Verify the integrity of the current repository.
5023 Verify the integrity of the current repository.
5024
5024
5025 This will perform an extensive check of the repository's
5025 This will perform an extensive check of the repository's
5026 integrity, validating the hashes and checksums of each entry in
5026 integrity, validating the hashes and checksums of each entry in
5027 the changelog, manifest, and tracked files, as well as the
5027 the changelog, manifest, and tracked files, as well as the
5028 integrity of their crosslinks and indices.
5028 integrity of their crosslinks and indices.
5029
5029
5030 Returns 0 on success, 1 if errors are encountered.
5030 Returns 0 on success, 1 if errors are encountered.
5031 """
5031 """
5032 return hg.verify(repo)
5032 return hg.verify(repo)
5033
5033
5034 @command('version', [])
5034 @command('version', [])
5035 def version_(ui):
5035 def version_(ui):
5036 """output version and copyright information"""
5036 """output version and copyright information"""
5037 ui.write(_("Mercurial Distributed SCM (version %s)\n")
5037 ui.write(_("Mercurial Distributed SCM (version %s)\n")
5038 % util.version())
5038 % util.version())
5039 ui.status(_(
5039 ui.status(_(
5040 "(see http://mercurial.selenic.com for more information)\n"
5040 "(see http://mercurial.selenic.com for more information)\n"
5041 "\nCopyright (C) 2005-2011 Matt Mackall and others\n"
5041 "\nCopyright (C) 2005-2011 Matt Mackall and others\n"
5042 "This is free software; see the source for copying conditions. "
5042 "This is free software; see the source for copying conditions. "
5043 "There is NO\nwarranty; "
5043 "There is NO\nwarranty; "
5044 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5044 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5045 ))
5045 ))
5046
5046
5047 norepo = ("clone init version help debugcommands debugcomplete"
5047 norepo = ("clone init version help debugcommands debugcomplete"
5048 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
5048 " debugdate debuginstall debugfsinfo debugpushkey debugwireargs"
5049 " debugknown debuggetbundle debugbundle")
5049 " debugknown debuggetbundle debugbundle")
5050 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
5050 optionalrepo = ("identify paths serve showconfig debugancestor debugdag"
5051 " debugdata debugindex debugindexdot debugrevlog")
5051 " debugdata debugindex debugindexdot debugrevlog")
@@ -1,1779 +1,1780 b''
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import cStringIO, email.Parser, os, errno, re
9 import cStringIO, email.Parser, os, errno, re
10 import tempfile, zlib
10 import tempfile, zlib
11
11
12 from i18n import _
12 from i18n import _
13 from node import hex, nullid, short
13 from node import hex, nullid, short
14 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding
14 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding
15
15
16 gitre = re.compile('diff --git a/(.*) b/(.*)')
16 gitre = re.compile('diff --git a/(.*) b/(.*)')
17
17
18 class PatchError(Exception):
18 class PatchError(Exception):
19 pass
19 pass
20
20
21
21
22 # public functions
22 # public functions
23
23
24 def split(stream):
24 def split(stream):
25 '''return an iterator of individual patches from a stream'''
25 '''return an iterator of individual patches from a stream'''
26 def isheader(line, inheader):
26 def isheader(line, inheader):
27 if inheader and line[0] in (' ', '\t'):
27 if inheader and line[0] in (' ', '\t'):
28 # continuation
28 # continuation
29 return True
29 return True
30 if line[0] in (' ', '-', '+'):
30 if line[0] in (' ', '-', '+'):
31 # diff line - don't check for header pattern in there
31 # diff line - don't check for header pattern in there
32 return False
32 return False
33 l = line.split(': ', 1)
33 l = line.split(': ', 1)
34 return len(l) == 2 and ' ' not in l[0]
34 return len(l) == 2 and ' ' not in l[0]
35
35
36 def chunk(lines):
36 def chunk(lines):
37 return cStringIO.StringIO(''.join(lines))
37 return cStringIO.StringIO(''.join(lines))
38
38
39 def hgsplit(stream, cur):
39 def hgsplit(stream, cur):
40 inheader = True
40 inheader = True
41
41
42 for line in stream:
42 for line in stream:
43 if not line.strip():
43 if not line.strip():
44 inheader = False
44 inheader = False
45 if not inheader and line.startswith('# HG changeset patch'):
45 if not inheader and line.startswith('# HG changeset patch'):
46 yield chunk(cur)
46 yield chunk(cur)
47 cur = []
47 cur = []
48 inheader = True
48 inheader = True
49
49
50 cur.append(line)
50 cur.append(line)
51
51
52 if cur:
52 if cur:
53 yield chunk(cur)
53 yield chunk(cur)
54
54
55 def mboxsplit(stream, cur):
55 def mboxsplit(stream, cur):
56 for line in stream:
56 for line in stream:
57 if line.startswith('From '):
57 if line.startswith('From '):
58 for c in split(chunk(cur[1:])):
58 for c in split(chunk(cur[1:])):
59 yield c
59 yield c
60 cur = []
60 cur = []
61
61
62 cur.append(line)
62 cur.append(line)
63
63
64 if cur:
64 if cur:
65 for c in split(chunk(cur[1:])):
65 for c in split(chunk(cur[1:])):
66 yield c
66 yield c
67
67
68 def mimesplit(stream, cur):
68 def mimesplit(stream, cur):
69 def msgfp(m):
69 def msgfp(m):
70 fp = cStringIO.StringIO()
70 fp = cStringIO.StringIO()
71 g = email.Generator.Generator(fp, mangle_from_=False)
71 g = email.Generator.Generator(fp, mangle_from_=False)
72 g.flatten(m)
72 g.flatten(m)
73 fp.seek(0)
73 fp.seek(0)
74 return fp
74 return fp
75
75
76 for line in stream:
76 for line in stream:
77 cur.append(line)
77 cur.append(line)
78 c = chunk(cur)
78 c = chunk(cur)
79
79
80 m = email.Parser.Parser().parse(c)
80 m = email.Parser.Parser().parse(c)
81 if not m.is_multipart():
81 if not m.is_multipart():
82 yield msgfp(m)
82 yield msgfp(m)
83 else:
83 else:
84 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
84 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
85 for part in m.walk():
85 for part in m.walk():
86 ct = part.get_content_type()
86 ct = part.get_content_type()
87 if ct not in ok_types:
87 if ct not in ok_types:
88 continue
88 continue
89 yield msgfp(part)
89 yield msgfp(part)
90
90
91 def headersplit(stream, cur):
91 def headersplit(stream, cur):
92 inheader = False
92 inheader = False
93
93
94 for line in stream:
94 for line in stream:
95 if not inheader and isheader(line, inheader):
95 if not inheader and isheader(line, inheader):
96 yield chunk(cur)
96 yield chunk(cur)
97 cur = []
97 cur = []
98 inheader = True
98 inheader = True
99 if inheader and not isheader(line, inheader):
99 if inheader and not isheader(line, inheader):
100 inheader = False
100 inheader = False
101
101
102 cur.append(line)
102 cur.append(line)
103
103
104 if cur:
104 if cur:
105 yield chunk(cur)
105 yield chunk(cur)
106
106
107 def remainder(cur):
107 def remainder(cur):
108 yield chunk(cur)
108 yield chunk(cur)
109
109
110 class fiter(object):
110 class fiter(object):
111 def __init__(self, fp):
111 def __init__(self, fp):
112 self.fp = fp
112 self.fp = fp
113
113
114 def __iter__(self):
114 def __iter__(self):
115 return self
115 return self
116
116
117 def next(self):
117 def next(self):
118 l = self.fp.readline()
118 l = self.fp.readline()
119 if not l:
119 if not l:
120 raise StopIteration
120 raise StopIteration
121 return l
121 return l
122
122
123 inheader = False
123 inheader = False
124 cur = []
124 cur = []
125
125
126 mimeheaders = ['content-type']
126 mimeheaders = ['content-type']
127
127
128 if not hasattr(stream, 'next'):
128 if not hasattr(stream, 'next'):
129 # http responses, for example, have readline but not next
129 # http responses, for example, have readline but not next
130 stream = fiter(stream)
130 stream = fiter(stream)
131
131
132 for line in stream:
132 for line in stream:
133 cur.append(line)
133 cur.append(line)
134 if line.startswith('# HG changeset patch'):
134 if line.startswith('# HG changeset patch'):
135 return hgsplit(stream, cur)
135 return hgsplit(stream, cur)
136 elif line.startswith('From '):
136 elif line.startswith('From '):
137 return mboxsplit(stream, cur)
137 return mboxsplit(stream, cur)
138 elif isheader(line, inheader):
138 elif isheader(line, inheader):
139 inheader = True
139 inheader = True
140 if line.split(':', 1)[0].lower() in mimeheaders:
140 if line.split(':', 1)[0].lower() in mimeheaders:
141 # let email parser handle this
141 # let email parser handle this
142 return mimesplit(stream, cur)
142 return mimesplit(stream, cur)
143 elif line.startswith('--- ') and inheader:
143 elif line.startswith('--- ') and inheader:
144 # No evil headers seen by diff start, split by hand
144 # No evil headers seen by diff start, split by hand
145 return headersplit(stream, cur)
145 return headersplit(stream, cur)
146 # Not enough info, keep reading
146 # Not enough info, keep reading
147
147
148 # if we are here, we have a very plain patch
148 # if we are here, we have a very plain patch
149 return remainder(cur)
149 return remainder(cur)
150
150
151 def extract(ui, fileobj):
151 def extract(ui, fileobj):
152 '''extract patch from data read from fileobj.
152 '''extract patch from data read from fileobj.
153
153
154 patch can be a normal patch or contained in an email message.
154 patch can be a normal patch or contained in an email message.
155
155
156 return tuple (filename, message, user, date, branch, node, p1, p2).
156 return tuple (filename, message, user, date, branch, node, p1, p2).
157 Any item in the returned tuple can be None. If filename is None,
157 Any item in the returned tuple can be None. If filename is None,
158 fileobj did not contain a patch. Caller must unlink filename when done.'''
158 fileobj did not contain a patch. Caller must unlink filename when done.'''
159
159
160 # attempt to detect the start of a patch
160 # attempt to detect the start of a patch
161 # (this heuristic is borrowed from quilt)
161 # (this heuristic is borrowed from quilt)
162 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
162 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
163 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
163 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
164 r'---[ \t].*?^\+\+\+[ \t]|'
164 r'---[ \t].*?^\+\+\+[ \t]|'
165 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
165 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
166
166
167 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
167 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
168 tmpfp = os.fdopen(fd, 'w')
168 tmpfp = os.fdopen(fd, 'w')
169 try:
169 try:
170 msg = email.Parser.Parser().parse(fileobj)
170 msg = email.Parser.Parser().parse(fileobj)
171
171
172 subject = msg['Subject']
172 subject = msg['Subject']
173 user = msg['From']
173 user = msg['From']
174 if not subject and not user:
174 if not subject and not user:
175 # Not an email, restore parsed headers if any
175 # Not an email, restore parsed headers if any
176 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
176 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
177
177
178 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
178 gitsendmail = 'git-send-email' in msg.get('X-Mailer', '')
179 # should try to parse msg['Date']
179 # should try to parse msg['Date']
180 date = None
180 date = None
181 nodeid = None
181 nodeid = None
182 branch = None
182 branch = None
183 parents = []
183 parents = []
184
184
185 if subject:
185 if subject:
186 if subject.startswith('[PATCH'):
186 if subject.startswith('[PATCH'):
187 pend = subject.find(']')
187 pend = subject.find(']')
188 if pend >= 0:
188 if pend >= 0:
189 subject = subject[pend + 1:].lstrip()
189 subject = subject[pend + 1:].lstrip()
190 subject = subject.replace('\n\t', ' ')
190 subject = subject.replace('\n\t', ' ')
191 ui.debug('Subject: %s\n' % subject)
191 ui.debug('Subject: %s\n' % subject)
192 if user:
192 if user:
193 ui.debug('From: %s\n' % user)
193 ui.debug('From: %s\n' % user)
194 diffs_seen = 0
194 diffs_seen = 0
195 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
195 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
196 message = ''
196 message = ''
197 for part in msg.walk():
197 for part in msg.walk():
198 content_type = part.get_content_type()
198 content_type = part.get_content_type()
199 ui.debug('Content-Type: %s\n' % content_type)
199 ui.debug('Content-Type: %s\n' % content_type)
200 if content_type not in ok_types:
200 if content_type not in ok_types:
201 continue
201 continue
202 payload = part.get_payload(decode=True)
202 payload = part.get_payload(decode=True)
203 m = diffre.search(payload)
203 m = diffre.search(payload)
204 if m:
204 if m:
205 hgpatch = False
205 hgpatch = False
206 hgpatchheader = False
206 hgpatchheader = False
207 ignoretext = False
207 ignoretext = False
208
208
209 ui.debug('found patch at byte %d\n' % m.start(0))
209 ui.debug('found patch at byte %d\n' % m.start(0))
210 diffs_seen += 1
210 diffs_seen += 1
211 cfp = cStringIO.StringIO()
211 cfp = cStringIO.StringIO()
212 for line in payload[:m.start(0)].splitlines():
212 for line in payload[:m.start(0)].splitlines():
213 if line.startswith('# HG changeset patch') and not hgpatch:
213 if line.startswith('# HG changeset patch') and not hgpatch:
214 ui.debug('patch generated by hg export\n')
214 ui.debug('patch generated by hg export\n')
215 hgpatch = True
215 hgpatch = True
216 hgpatchheader = True
216 hgpatchheader = True
217 # drop earlier commit message content
217 # drop earlier commit message content
218 cfp.seek(0)
218 cfp.seek(0)
219 cfp.truncate()
219 cfp.truncate()
220 subject = None
220 subject = None
221 elif hgpatchheader:
221 elif hgpatchheader:
222 if line.startswith('# User '):
222 if line.startswith('# User '):
223 user = line[7:]
223 user = line[7:]
224 ui.debug('From: %s\n' % user)
224 ui.debug('From: %s\n' % user)
225 elif line.startswith("# Date "):
225 elif line.startswith("# Date "):
226 date = line[7:]
226 date = line[7:]
227 elif line.startswith("# Branch "):
227 elif line.startswith("# Branch "):
228 branch = line[9:]
228 branch = line[9:]
229 elif line.startswith("# Node ID "):
229 elif line.startswith("# Node ID "):
230 nodeid = line[10:]
230 nodeid = line[10:]
231 elif line.startswith("# Parent "):
231 elif line.startswith("# Parent "):
232 parents.append(line[10:])
232 parents.append(line[10:])
233 elif not line.startswith("# "):
233 elif not line.startswith("# "):
234 hgpatchheader = False
234 hgpatchheader = False
235 elif line == '---' and gitsendmail:
235 elif line == '---' and gitsendmail:
236 ignoretext = True
236 ignoretext = True
237 if not hgpatchheader and not ignoretext:
237 if not hgpatchheader and not ignoretext:
238 cfp.write(line)
238 cfp.write(line)
239 cfp.write('\n')
239 cfp.write('\n')
240 message = cfp.getvalue()
240 message = cfp.getvalue()
241 if tmpfp:
241 if tmpfp:
242 tmpfp.write(payload)
242 tmpfp.write(payload)
243 if not payload.endswith('\n'):
243 if not payload.endswith('\n'):
244 tmpfp.write('\n')
244 tmpfp.write('\n')
245 elif not diffs_seen and message and content_type == 'text/plain':
245 elif not diffs_seen and message and content_type == 'text/plain':
246 message += '\n' + payload
246 message += '\n' + payload
247 except:
247 except:
248 tmpfp.close()
248 tmpfp.close()
249 os.unlink(tmpname)
249 os.unlink(tmpname)
250 raise
250 raise
251
251
252 if subject and not message.startswith(subject):
252 if subject and not message.startswith(subject):
253 message = '%s\n%s' % (subject, message)
253 message = '%s\n%s' % (subject, message)
254 tmpfp.close()
254 tmpfp.close()
255 if not diffs_seen:
255 if not diffs_seen:
256 os.unlink(tmpname)
256 os.unlink(tmpname)
257 return None, message, user, date, branch, None, None, None
257 return None, message, user, date, branch, None, None, None
258 p1 = parents and parents.pop(0) or None
258 p1 = parents and parents.pop(0) or None
259 p2 = parents and parents.pop(0) or None
259 p2 = parents and parents.pop(0) or None
260 return tmpname, message, user, date, branch, nodeid, p1, p2
260 return tmpname, message, user, date, branch, nodeid, p1, p2
261
261
262 class patchmeta(object):
262 class patchmeta(object):
263 """Patched file metadata
263 """Patched file metadata
264
264
265 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
265 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
266 or COPY. 'path' is patched file path. 'oldpath' is set to the
266 or COPY. 'path' is patched file path. 'oldpath' is set to the
267 origin file when 'op' is either COPY or RENAME, None otherwise. If
267 origin file when 'op' is either COPY or RENAME, None otherwise. If
268 file mode is changed, 'mode' is a tuple (islink, isexec) where
268 file mode is changed, 'mode' is a tuple (islink, isexec) where
269 'islink' is True if the file is a symlink and 'isexec' is True if
269 'islink' is True if the file is a symlink and 'isexec' is True if
270 the file is executable. Otherwise, 'mode' is None.
270 the file is executable. Otherwise, 'mode' is None.
271 """
271 """
272 def __init__(self, path):
272 def __init__(self, path):
273 self.path = path
273 self.path = path
274 self.oldpath = None
274 self.oldpath = None
275 self.mode = None
275 self.mode = None
276 self.op = 'MODIFY'
276 self.op = 'MODIFY'
277 self.binary = False
277 self.binary = False
278
278
279 def setmode(self, mode):
279 def setmode(self, mode):
280 islink = mode & 020000
280 islink = mode & 020000
281 isexec = mode & 0100
281 isexec = mode & 0100
282 self.mode = (islink, isexec)
282 self.mode = (islink, isexec)
283
283
284 def __repr__(self):
284 def __repr__(self):
285 return "<patchmeta %s %r>" % (self.op, self.path)
285 return "<patchmeta %s %r>" % (self.op, self.path)
286
286
287 def readgitpatch(lr):
287 def readgitpatch(lr):
288 """extract git-style metadata about patches from <patchname>"""
288 """extract git-style metadata about patches from <patchname>"""
289
289
290 # Filter patch for git information
290 # Filter patch for git information
291 gp = None
291 gp = None
292 gitpatches = []
292 gitpatches = []
293 for line in lr:
293 for line in lr:
294 line = line.rstrip(' \r\n')
294 line = line.rstrip(' \r\n')
295 if line.startswith('diff --git'):
295 if line.startswith('diff --git'):
296 m = gitre.match(line)
296 m = gitre.match(line)
297 if m:
297 if m:
298 if gp:
298 if gp:
299 gitpatches.append(gp)
299 gitpatches.append(gp)
300 dst = m.group(2)
300 dst = m.group(2)
301 gp = patchmeta(dst)
301 gp = patchmeta(dst)
302 elif gp:
302 elif gp:
303 if line.startswith('--- '):
303 if line.startswith('--- '):
304 gitpatches.append(gp)
304 gitpatches.append(gp)
305 gp = None
305 gp = None
306 continue
306 continue
307 if line.startswith('rename from '):
307 if line.startswith('rename from '):
308 gp.op = 'RENAME'
308 gp.op = 'RENAME'
309 gp.oldpath = line[12:]
309 gp.oldpath = line[12:]
310 elif line.startswith('rename to '):
310 elif line.startswith('rename to '):
311 gp.path = line[10:]
311 gp.path = line[10:]
312 elif line.startswith('copy from '):
312 elif line.startswith('copy from '):
313 gp.op = 'COPY'
313 gp.op = 'COPY'
314 gp.oldpath = line[10:]
314 gp.oldpath = line[10:]
315 elif line.startswith('copy to '):
315 elif line.startswith('copy to '):
316 gp.path = line[8:]
316 gp.path = line[8:]
317 elif line.startswith('deleted file'):
317 elif line.startswith('deleted file'):
318 gp.op = 'DELETE'
318 gp.op = 'DELETE'
319 elif line.startswith('new file mode '):
319 elif line.startswith('new file mode '):
320 gp.op = 'ADD'
320 gp.op = 'ADD'
321 gp.setmode(int(line[-6:], 8))
321 gp.setmode(int(line[-6:], 8))
322 elif line.startswith('new mode '):
322 elif line.startswith('new mode '):
323 gp.setmode(int(line[-6:], 8))
323 gp.setmode(int(line[-6:], 8))
324 elif line.startswith('GIT binary patch'):
324 elif line.startswith('GIT binary patch'):
325 gp.binary = True
325 gp.binary = True
326 if gp:
326 if gp:
327 gitpatches.append(gp)
327 gitpatches.append(gp)
328
328
329 return gitpatches
329 return gitpatches
330
330
331 class linereader(object):
331 class linereader(object):
332 # simple class to allow pushing lines back into the input stream
332 # simple class to allow pushing lines back into the input stream
333 def __init__(self, fp, textmode=False):
333 def __init__(self, fp, textmode=False):
334 self.fp = fp
334 self.fp = fp
335 self.buf = []
335 self.buf = []
336 self.textmode = textmode
336 self.textmode = textmode
337 self.eol = None
337 self.eol = None
338
338
339 def push(self, line):
339 def push(self, line):
340 if line is not None:
340 if line is not None:
341 self.buf.append(line)
341 self.buf.append(line)
342
342
343 def readline(self):
343 def readline(self):
344 if self.buf:
344 if self.buf:
345 l = self.buf[0]
345 l = self.buf[0]
346 del self.buf[0]
346 del self.buf[0]
347 return l
347 return l
348 l = self.fp.readline()
348 l = self.fp.readline()
349 if not self.eol:
349 if not self.eol:
350 if l.endswith('\r\n'):
350 if l.endswith('\r\n'):
351 self.eol = '\r\n'
351 self.eol = '\r\n'
352 elif l.endswith('\n'):
352 elif l.endswith('\n'):
353 self.eol = '\n'
353 self.eol = '\n'
354 if self.textmode and l.endswith('\r\n'):
354 if self.textmode and l.endswith('\r\n'):
355 l = l[:-2] + '\n'
355 l = l[:-2] + '\n'
356 return l
356 return l
357
357
358 def __iter__(self):
358 def __iter__(self):
359 while 1:
359 while 1:
360 l = self.readline()
360 l = self.readline()
361 if not l:
361 if not l:
362 break
362 break
363 yield l
363 yield l
364
364
365 class abstractbackend(object):
365 class abstractbackend(object):
366 def __init__(self, ui):
366 def __init__(self, ui):
367 self.ui = ui
367 self.ui = ui
368
368
369 def readlines(self, fname):
369 def readlines(self, fname):
370 """Return target file lines, or its content as a single line
370 """Return target file lines, or its content as a single line
371 for symlinks.
371 for symlinks.
372 """
372 """
373 raise NotImplementedError
373 raise NotImplementedError
374
374
375 def writelines(self, fname, lines, mode):
375 def writelines(self, fname, lines, mode):
376 """Write lines to target file. mode is a (islink, isexec)
376 """Write lines to target file. mode is a (islink, isexec)
377 tuple, or None if there is no mode information.
377 tuple, or None if there is no mode information.
378 """
378 """
379 raise NotImplementedError
379 raise NotImplementedError
380
380
381 def unlink(self, fname):
381 def unlink(self, fname):
382 """Unlink target file."""
382 """Unlink target file."""
383 raise NotImplementedError
383 raise NotImplementedError
384
384
385 def writerej(self, fname, failed, total, lines):
385 def writerej(self, fname, failed, total, lines):
386 """Write rejected lines for fname. total is the number of hunks
386 """Write rejected lines for fname. total is the number of hunks
387 which failed to apply and total the total number of hunks for this
387 which failed to apply and total the total number of hunks for this
388 files.
388 files.
389 """
389 """
390 pass
390 pass
391
391
392 def copy(self, src, dst):
392 def copy(self, src, dst):
393 """Copy src file into dst file. Create intermediate directories if
393 """Copy src file into dst file. Create intermediate directories if
394 necessary. Files are specified relatively to the patching base
394 necessary. Files are specified relatively to the patching base
395 directory.
395 directory.
396 """
396 """
397 raise NotImplementedError
397 raise NotImplementedError
398
398
399 def exists(self, fname):
399 def exists(self, fname):
400 raise NotImplementedError
400 raise NotImplementedError
401
401
402 def setmode(self, fname, islink, isexec):
402 def setmode(self, fname, islink, isexec):
403 """Change target file mode."""
403 """Change target file mode."""
404 raise NotImplementedError
404 raise NotImplementedError
405
405
406 class fsbackend(abstractbackend):
406 class fsbackend(abstractbackend):
407 def __init__(self, ui, basedir):
407 def __init__(self, ui, basedir):
408 super(fsbackend, self).__init__(ui)
408 super(fsbackend, self).__init__(ui)
409 self.opener = scmutil.opener(basedir)
409 self.opener = scmutil.opener(basedir)
410
410
411 def _join(self, f):
411 def _join(self, f):
412 return os.path.join(self.opener.base, f)
412 return os.path.join(self.opener.base, f)
413
413
414 def readlines(self, fname):
414 def readlines(self, fname):
415 if os.path.islink(self._join(fname)):
415 if os.path.islink(self._join(fname)):
416 return [os.readlink(self._join(fname))]
416 return [os.readlink(self._join(fname))]
417 fp = self.opener(fname, 'r')
417 fp = self.opener(fname, 'r')
418 try:
418 try:
419 return list(fp)
419 return list(fp)
420 finally:
420 finally:
421 fp.close()
421 fp.close()
422
422
423 def writelines(self, fname, lines, mode):
423 def writelines(self, fname, lines, mode):
424 if not mode:
424 if not mode:
425 # Preserve mode information
425 # Preserve mode information
426 isexec, islink = False, False
426 isexec, islink = False, False
427 try:
427 try:
428 isexec = os.lstat(self._join(fname)).st_mode & 0100 != 0
428 isexec = os.lstat(self._join(fname)).st_mode & 0100 != 0
429 islink = os.path.islink(self._join(fname))
429 islink = os.path.islink(self._join(fname))
430 except OSError, e:
430 except OSError, e:
431 if e.errno != errno.ENOENT:
431 if e.errno != errno.ENOENT:
432 raise
432 raise
433 else:
433 else:
434 islink, isexec = mode
434 islink, isexec = mode
435 if islink:
435 if islink:
436 self.opener.symlink(''.join(lines), fname)
436 self.opener.symlink(''.join(lines), fname)
437 else:
437 else:
438 self.opener(fname, 'w').writelines(lines)
438 self.opener(fname, 'w').writelines(lines)
439 if isexec:
439 if isexec:
440 util.setflags(self._join(fname), False, True)
440 util.setflags(self._join(fname), False, True)
441
441
442 def unlink(self, fname):
442 def unlink(self, fname):
443 try:
443 try:
444 util.unlinkpath(self._join(fname))
444 util.unlinkpath(self._join(fname))
445 except OSError, inst:
445 except OSError, inst:
446 if inst.errno != errno.ENOENT:
446 if inst.errno != errno.ENOENT:
447 raise
447 raise
448
448
449 def writerej(self, fname, failed, total, lines):
449 def writerej(self, fname, failed, total, lines):
450 fname = fname + ".rej"
450 fname = fname + ".rej"
451 self.ui.warn(
451 self.ui.warn(
452 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
452 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
453 (failed, total, fname))
453 (failed, total, fname))
454 fp = self.opener(fname, 'w')
454 fp = self.opener(fname, 'w')
455 fp.writelines(lines)
455 fp.writelines(lines)
456 fp.close()
456 fp.close()
457
457
458 def copy(self, src, dst):
458 def copy(self, src, dst):
459 basedir = self.opener.base
459 basedir = self.opener.base
460 abssrc, absdst = [scmutil.canonpath(basedir, basedir, x)
460 abssrc, absdst = [scmutil.canonpath(basedir, basedir, x)
461 for x in [src, dst]]
461 for x in [src, dst]]
462 if os.path.lexists(absdst):
462 if os.path.lexists(absdst):
463 raise util.Abort(_("cannot create %s: destination already exists")
463 raise util.Abort(_("cannot create %s: destination already exists")
464 % dst)
464 % dst)
465 dstdir = os.path.dirname(absdst)
465 dstdir = os.path.dirname(absdst)
466 if dstdir and not os.path.isdir(dstdir):
466 if dstdir and not os.path.isdir(dstdir):
467 try:
467 try:
468 os.makedirs(dstdir)
468 os.makedirs(dstdir)
469 except IOError:
469 except IOError:
470 raise util.Abort(
470 raise util.Abort(
471 _("cannot create %s: unable to create destination directory")
471 _("cannot create %s: unable to create destination directory")
472 % dst)
472 % dst)
473 util.copyfile(abssrc, absdst)
473 util.copyfile(abssrc, absdst)
474
474
475 def exists(self, fname):
475 def exists(self, fname):
476 return os.path.lexists(self._join(fname))
476 return os.path.lexists(self._join(fname))
477
477
478 def setmode(self, fname, islink, isexec):
478 def setmode(self, fname, islink, isexec):
479 util.setflags(self._join(fname), islink, isexec)
479 util.setflags(self._join(fname), islink, isexec)
480
480
481 class workingbackend(fsbackend):
481 class workingbackend(fsbackend):
482 def __init__(self, ui, repo, similarity):
482 def __init__(self, ui, repo, similarity):
483 super(workingbackend, self).__init__(ui, repo.root)
483 super(workingbackend, self).__init__(ui, repo.root)
484 self.repo = repo
484 self.repo = repo
485 self.similarity = similarity
485 self.similarity = similarity
486 self.removed = set()
486 self.removed = set()
487 self.changed = set()
487 self.changed = set()
488 self.copied = []
488 self.copied = []
489
489
490 def writelines(self, fname, lines, mode):
490 def writelines(self, fname, lines, mode):
491 super(workingbackend, self).writelines(fname, lines, mode)
491 super(workingbackend, self).writelines(fname, lines, mode)
492 self.changed.add(fname)
492 self.changed.add(fname)
493
493
494 def unlink(self, fname):
494 def unlink(self, fname):
495 super(workingbackend, self).unlink(fname)
495 super(workingbackend, self).unlink(fname)
496 self.removed.add(fname)
496 self.removed.add(fname)
497 self.changed.add(fname)
497 self.changed.add(fname)
498
498
499 def copy(self, src, dst):
499 def copy(self, src, dst):
500 super(workingbackend, self).copy(src, dst)
500 super(workingbackend, self).copy(src, dst)
501 self.copied.append((src, dst))
501 self.copied.append((src, dst))
502 self.changed.add(dst)
502 self.changed.add(dst)
503
503
504 def setmode(self, fname, islink, isexec):
504 def setmode(self, fname, islink, isexec):
505 super(workingbackend, self).setmode(fname, islink, isexec)
505 super(workingbackend, self).setmode(fname, islink, isexec)
506 self.changed.add(fname)
506 self.changed.add(fname)
507
507
508 def close(self):
508 def close(self):
509 wctx = self.repo[None]
509 wctx = self.repo[None]
510 addremoved = set(self.changed)
510 addremoved = set(self.changed)
511 for src, dst in self.copied:
511 for src, dst in self.copied:
512 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
512 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
513 addremoved.discard(src)
513 addremoved.discard(src)
514 if (not self.similarity) and self.removed:
514 if (not self.similarity) and self.removed:
515 wctx.remove(sorted(self.removed))
515 wctx.remove(sorted(self.removed))
516 if addremoved:
516 if addremoved:
517 cwd = self.repo.getcwd()
517 cwd = self.repo.getcwd()
518 if cwd:
518 if cwd:
519 addremoved = [util.pathto(self.repo.root, cwd, f)
519 addremoved = [util.pathto(self.repo.root, cwd, f)
520 for f in addremoved]
520 for f in addremoved]
521 scmutil.addremove(self.repo, addremoved, similarity=self.similarity)
521 scmutil.addremove(self.repo, addremoved, similarity=self.similarity)
522 return sorted(self.changed)
522 return sorted(self.changed)
523
523
524 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
524 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
525 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
525 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
526 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
526 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
527 eolmodes = ['strict', 'crlf', 'lf', 'auto']
527 eolmodes = ['strict', 'crlf', 'lf', 'auto']
528
528
529 class patchfile(object):
529 class patchfile(object):
530 def __init__(self, ui, fname, backend, mode, missing=False,
530 def __init__(self, ui, fname, backend, mode, missing=False,
531 eolmode='strict'):
531 eolmode='strict'):
532 self.fname = fname
532 self.fname = fname
533 self.eolmode = eolmode
533 self.eolmode = eolmode
534 self.eol = None
534 self.eol = None
535 self.backend = backend
535 self.backend = backend
536 self.ui = ui
536 self.ui = ui
537 self.lines = []
537 self.lines = []
538 self.exists = False
538 self.exists = False
539 self.missing = missing
539 self.missing = missing
540 self.mode = mode
540 self.mode = mode
541 if not missing:
541 if not missing:
542 try:
542 try:
543 self.lines = self.backend.readlines(fname)
543 self.lines = self.backend.readlines(fname)
544 if self.lines:
544 if self.lines:
545 # Normalize line endings
545 # Normalize line endings
546 if self.lines[0].endswith('\r\n'):
546 if self.lines[0].endswith('\r\n'):
547 self.eol = '\r\n'
547 self.eol = '\r\n'
548 elif self.lines[0].endswith('\n'):
548 elif self.lines[0].endswith('\n'):
549 self.eol = '\n'
549 self.eol = '\n'
550 if eolmode != 'strict':
550 if eolmode != 'strict':
551 nlines = []
551 nlines = []
552 for l in self.lines:
552 for l in self.lines:
553 if l.endswith('\r\n'):
553 if l.endswith('\r\n'):
554 l = l[:-2] + '\n'
554 l = l[:-2] + '\n'
555 nlines.append(l)
555 nlines.append(l)
556 self.lines = nlines
556 self.lines = nlines
557 self.exists = True
557 self.exists = True
558 except IOError:
558 except IOError:
559 pass
559 pass
560 else:
560 else:
561 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
561 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
562
562
563 self.hash = {}
563 self.hash = {}
564 self.dirty = 0
564 self.dirty = 0
565 self.offset = 0
565 self.offset = 0
566 self.skew = 0
566 self.skew = 0
567 self.rej = []
567 self.rej = []
568 self.fileprinted = False
568 self.fileprinted = False
569 self.printfile(False)
569 self.printfile(False)
570 self.hunks = 0
570 self.hunks = 0
571
571
572 def writelines(self, fname, lines, mode):
572 def writelines(self, fname, lines, mode):
573 if self.eolmode == 'auto':
573 if self.eolmode == 'auto':
574 eol = self.eol
574 eol = self.eol
575 elif self.eolmode == 'crlf':
575 elif self.eolmode == 'crlf':
576 eol = '\r\n'
576 eol = '\r\n'
577 else:
577 else:
578 eol = '\n'
578 eol = '\n'
579
579
580 if self.eolmode != 'strict' and eol and eol != '\n':
580 if self.eolmode != 'strict' and eol and eol != '\n':
581 rawlines = []
581 rawlines = []
582 for l in lines:
582 for l in lines:
583 if l and l[-1] == '\n':
583 if l and l[-1] == '\n':
584 l = l[:-1] + eol
584 l = l[:-1] + eol
585 rawlines.append(l)
585 rawlines.append(l)
586 lines = rawlines
586 lines = rawlines
587
587
588 self.backend.writelines(fname, lines, mode)
588 self.backend.writelines(fname, lines, mode)
589
589
590 def printfile(self, warn):
590 def printfile(self, warn):
591 if self.fileprinted:
591 if self.fileprinted:
592 return
592 return
593 if warn or self.ui.verbose:
593 if warn or self.ui.verbose:
594 self.fileprinted = True
594 self.fileprinted = True
595 s = _("patching file %s\n") % self.fname
595 s = _("patching file %s\n") % self.fname
596 if warn:
596 if warn:
597 self.ui.warn(s)
597 self.ui.warn(s)
598 else:
598 else:
599 self.ui.note(s)
599 self.ui.note(s)
600
600
601
601
602 def findlines(self, l, linenum):
602 def findlines(self, l, linenum):
603 # looks through the hash and finds candidate lines. The
603 # looks through the hash and finds candidate lines. The
604 # result is a list of line numbers sorted based on distance
604 # result is a list of line numbers sorted based on distance
605 # from linenum
605 # from linenum
606
606
607 cand = self.hash.get(l, [])
607 cand = self.hash.get(l, [])
608 if len(cand) > 1:
608 if len(cand) > 1:
609 # resort our list of potentials forward then back.
609 # resort our list of potentials forward then back.
610 cand.sort(key=lambda x: abs(x - linenum))
610 cand.sort(key=lambda x: abs(x - linenum))
611 return cand
611 return cand
612
612
613 def write_rej(self):
613 def write_rej(self):
614 # our rejects are a little different from patch(1). This always
614 # our rejects are a little different from patch(1). This always
615 # creates rejects in the same form as the original patch. A file
615 # creates rejects in the same form as the original patch. A file
616 # header is inserted so that you can run the reject through patch again
616 # header is inserted so that you can run the reject through patch again
617 # without having to type the filename.
617 # without having to type the filename.
618 if not self.rej:
618 if not self.rej:
619 return
619 return
620 base = os.path.basename(self.fname)
620 base = os.path.basename(self.fname)
621 lines = ["--- %s\n+++ %s\n" % (base, base)]
621 lines = ["--- %s\n+++ %s\n" % (base, base)]
622 for x in self.rej:
622 for x in self.rej:
623 for l in x.hunk:
623 for l in x.hunk:
624 lines.append(l)
624 lines.append(l)
625 if l[-1] != '\n':
625 if l[-1] != '\n':
626 lines.append("\n\ No newline at end of file\n")
626 lines.append("\n\ No newline at end of file\n")
627 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
627 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
628
628
629 def apply(self, h):
629 def apply(self, h):
630 if not h.complete():
630 if not h.complete():
631 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
631 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
632 (h.number, h.desc, len(h.a), h.lena, len(h.b),
632 (h.number, h.desc, len(h.a), h.lena, len(h.b),
633 h.lenb))
633 h.lenb))
634
634
635 self.hunks += 1
635 self.hunks += 1
636
636
637 if self.missing:
637 if self.missing:
638 self.rej.append(h)
638 self.rej.append(h)
639 return -1
639 return -1
640
640
641 if self.exists and h.createfile():
641 if self.exists and h.createfile():
642 self.ui.warn(_("file %s already exists\n") % self.fname)
642 self.ui.warn(_("file %s already exists\n") % self.fname)
643 self.rej.append(h)
643 self.rej.append(h)
644 return -1
644 return -1
645
645
646 if isinstance(h, binhunk):
646 if isinstance(h, binhunk):
647 if h.rmfile():
647 if h.rmfile():
648 self.backend.unlink(self.fname)
648 self.backend.unlink(self.fname)
649 else:
649 else:
650 self.lines[:] = h.new()
650 self.lines[:] = h.new()
651 self.offset += len(h.new())
651 self.offset += len(h.new())
652 self.dirty = True
652 self.dirty = True
653 return 0
653 return 0
654
654
655 horig = h
655 horig = h
656 if (self.eolmode in ('crlf', 'lf')
656 if (self.eolmode in ('crlf', 'lf')
657 or self.eolmode == 'auto' and self.eol):
657 or self.eolmode == 'auto' and self.eol):
658 # If new eols are going to be normalized, then normalize
658 # If new eols are going to be normalized, then normalize
659 # hunk data before patching. Otherwise, preserve input
659 # hunk data before patching. Otherwise, preserve input
660 # line-endings.
660 # line-endings.
661 h = h.getnormalized()
661 h = h.getnormalized()
662
662
663 # fast case first, no offsets, no fuzz
663 # fast case first, no offsets, no fuzz
664 old = h.old()
664 old = h.old()
665 # patch starts counting at 1 unless we are adding the file
665 # patch starts counting at 1 unless we are adding the file
666 if h.starta == 0:
666 if h.starta == 0:
667 start = 0
667 start = 0
668 else:
668 else:
669 start = h.starta + self.offset - 1
669 start = h.starta + self.offset - 1
670 orig_start = start
670 orig_start = start
671 # if there's skew we want to emit the "(offset %d lines)" even
671 # if there's skew we want to emit the "(offset %d lines)" even
672 # when the hunk cleanly applies at start + skew, so skip the
672 # when the hunk cleanly applies at start + skew, so skip the
673 # fast case code
673 # fast case code
674 if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0:
674 if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0:
675 if h.rmfile():
675 if h.rmfile():
676 self.backend.unlink(self.fname)
676 self.backend.unlink(self.fname)
677 else:
677 else:
678 self.lines[start : start + h.lena] = h.new()
678 self.lines[start : start + h.lena] = h.new()
679 self.offset += h.lenb - h.lena
679 self.offset += h.lenb - h.lena
680 self.dirty = True
680 self.dirty = True
681 return 0
681 return 0
682
682
683 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
683 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
684 self.hash = {}
684 self.hash = {}
685 for x, s in enumerate(self.lines):
685 for x, s in enumerate(self.lines):
686 self.hash.setdefault(s, []).append(x)
686 self.hash.setdefault(s, []).append(x)
687 if h.hunk[-1][0] != ' ':
687 if h.hunk[-1][0] != ' ':
688 # if the hunk tried to put something at the bottom of the file
688 # if the hunk tried to put something at the bottom of the file
689 # override the start line and use eof here
689 # override the start line and use eof here
690 search_start = len(self.lines)
690 search_start = len(self.lines)
691 else:
691 else:
692 search_start = orig_start + self.skew
692 search_start = orig_start + self.skew
693
693
694 for fuzzlen in xrange(3):
694 for fuzzlen in xrange(3):
695 for toponly in [True, False]:
695 for toponly in [True, False]:
696 old = h.old(fuzzlen, toponly)
696 old = h.old(fuzzlen, toponly)
697
697
698 cand = self.findlines(old[0][1:], search_start)
698 cand = self.findlines(old[0][1:], search_start)
699 for l in cand:
699 for l in cand:
700 if diffhelpers.testhunk(old, self.lines, l) == 0:
700 if diffhelpers.testhunk(old, self.lines, l) == 0:
701 newlines = h.new(fuzzlen, toponly)
701 newlines = h.new(fuzzlen, toponly)
702 self.lines[l : l + len(old)] = newlines
702 self.lines[l : l + len(old)] = newlines
703 self.offset += len(newlines) - len(old)
703 self.offset += len(newlines) - len(old)
704 self.skew = l - orig_start
704 self.skew = l - orig_start
705 self.dirty = True
705 self.dirty = True
706 offset = l - orig_start - fuzzlen
706 offset = l - orig_start - fuzzlen
707 if fuzzlen:
707 if fuzzlen:
708 msg = _("Hunk #%d succeeded at %d "
708 msg = _("Hunk #%d succeeded at %d "
709 "with fuzz %d "
709 "with fuzz %d "
710 "(offset %d lines).\n")
710 "(offset %d lines).\n")
711 self.printfile(True)
711 self.printfile(True)
712 self.ui.warn(msg %
712 self.ui.warn(msg %
713 (h.number, l + 1, fuzzlen, offset))
713 (h.number, l + 1, fuzzlen, offset))
714 else:
714 else:
715 msg = _("Hunk #%d succeeded at %d "
715 msg = _("Hunk #%d succeeded at %d "
716 "(offset %d lines).\n")
716 "(offset %d lines).\n")
717 self.ui.note(msg % (h.number, l + 1, offset))
717 self.ui.note(msg % (h.number, l + 1, offset))
718 return fuzzlen
718 return fuzzlen
719 self.printfile(True)
719 self.printfile(True)
720 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
720 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
721 self.rej.append(horig)
721 self.rej.append(horig)
722 return -1
722 return -1
723
723
724 def close(self):
724 def close(self):
725 if self.dirty:
725 if self.dirty:
726 self.writelines(self.fname, self.lines, self.mode)
726 self.writelines(self.fname, self.lines, self.mode)
727 self.write_rej()
727 self.write_rej()
728 return len(self.rej)
728 return len(self.rej)
729
729
730 class hunk(object):
730 class hunk(object):
731 def __init__(self, desc, num, lr, context, create=False, remove=False):
731 def __init__(self, desc, num, lr, context, create=False, remove=False):
732 self.number = num
732 self.number = num
733 self.desc = desc
733 self.desc = desc
734 self.hunk = [desc]
734 self.hunk = [desc]
735 self.a = []
735 self.a = []
736 self.b = []
736 self.b = []
737 self.starta = self.lena = None
737 self.starta = self.lena = None
738 self.startb = self.lenb = None
738 self.startb = self.lenb = None
739 if lr is not None:
739 if lr is not None:
740 if context:
740 if context:
741 self.read_context_hunk(lr)
741 self.read_context_hunk(lr)
742 else:
742 else:
743 self.read_unified_hunk(lr)
743 self.read_unified_hunk(lr)
744 self.create = create
744 self.create = create
745 self.remove = remove and not create
745 self.remove = remove and not create
746
746
747 def getnormalized(self):
747 def getnormalized(self):
748 """Return a copy with line endings normalized to LF."""
748 """Return a copy with line endings normalized to LF."""
749
749
750 def normalize(lines):
750 def normalize(lines):
751 nlines = []
751 nlines = []
752 for line in lines:
752 for line in lines:
753 if line.endswith('\r\n'):
753 if line.endswith('\r\n'):
754 line = line[:-2] + '\n'
754 line = line[:-2] + '\n'
755 nlines.append(line)
755 nlines.append(line)
756 return nlines
756 return nlines
757
757
758 # Dummy object, it is rebuilt manually
758 # Dummy object, it is rebuilt manually
759 nh = hunk(self.desc, self.number, None, None, False, False)
759 nh = hunk(self.desc, self.number, None, None, False, False)
760 nh.number = self.number
760 nh.number = self.number
761 nh.desc = self.desc
761 nh.desc = self.desc
762 nh.hunk = self.hunk
762 nh.hunk = self.hunk
763 nh.a = normalize(self.a)
763 nh.a = normalize(self.a)
764 nh.b = normalize(self.b)
764 nh.b = normalize(self.b)
765 nh.starta = self.starta
765 nh.starta = self.starta
766 nh.startb = self.startb
766 nh.startb = self.startb
767 nh.lena = self.lena
767 nh.lena = self.lena
768 nh.lenb = self.lenb
768 nh.lenb = self.lenb
769 nh.create = self.create
769 nh.create = self.create
770 nh.remove = self.remove
770 nh.remove = self.remove
771 return nh
771 return nh
772
772
773 def read_unified_hunk(self, lr):
773 def read_unified_hunk(self, lr):
774 m = unidesc.match(self.desc)
774 m = unidesc.match(self.desc)
775 if not m:
775 if not m:
776 raise PatchError(_("bad hunk #%d") % self.number)
776 raise PatchError(_("bad hunk #%d") % self.number)
777 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
777 self.starta, foo, self.lena, self.startb, foo2, self.lenb = m.groups()
778 if self.lena is None:
778 if self.lena is None:
779 self.lena = 1
779 self.lena = 1
780 else:
780 else:
781 self.lena = int(self.lena)
781 self.lena = int(self.lena)
782 if self.lenb is None:
782 if self.lenb is None:
783 self.lenb = 1
783 self.lenb = 1
784 else:
784 else:
785 self.lenb = int(self.lenb)
785 self.lenb = int(self.lenb)
786 self.starta = int(self.starta)
786 self.starta = int(self.starta)
787 self.startb = int(self.startb)
787 self.startb = int(self.startb)
788 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
788 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a, self.b)
789 # if we hit eof before finishing out the hunk, the last line will
789 # if we hit eof before finishing out the hunk, the last line will
790 # be zero length. Lets try to fix it up.
790 # be zero length. Lets try to fix it up.
791 while len(self.hunk[-1]) == 0:
791 while len(self.hunk[-1]) == 0:
792 del self.hunk[-1]
792 del self.hunk[-1]
793 del self.a[-1]
793 del self.a[-1]
794 del self.b[-1]
794 del self.b[-1]
795 self.lena -= 1
795 self.lena -= 1
796 self.lenb -= 1
796 self.lenb -= 1
797 self._fixnewline(lr)
797 self._fixnewline(lr)
798
798
799 def read_context_hunk(self, lr):
799 def read_context_hunk(self, lr):
800 self.desc = lr.readline()
800 self.desc = lr.readline()
801 m = contextdesc.match(self.desc)
801 m = contextdesc.match(self.desc)
802 if not m:
802 if not m:
803 raise PatchError(_("bad hunk #%d") % self.number)
803 raise PatchError(_("bad hunk #%d") % self.number)
804 foo, self.starta, foo2, aend, foo3 = m.groups()
804 foo, self.starta, foo2, aend, foo3 = m.groups()
805 self.starta = int(self.starta)
805 self.starta = int(self.starta)
806 if aend is None:
806 if aend is None:
807 aend = self.starta
807 aend = self.starta
808 self.lena = int(aend) - self.starta
808 self.lena = int(aend) - self.starta
809 if self.starta:
809 if self.starta:
810 self.lena += 1
810 self.lena += 1
811 for x in xrange(self.lena):
811 for x in xrange(self.lena):
812 l = lr.readline()
812 l = lr.readline()
813 if l.startswith('---'):
813 if l.startswith('---'):
814 # lines addition, old block is empty
814 # lines addition, old block is empty
815 lr.push(l)
815 lr.push(l)
816 break
816 break
817 s = l[2:]
817 s = l[2:]
818 if l.startswith('- ') or l.startswith('! '):
818 if l.startswith('- ') or l.startswith('! '):
819 u = '-' + s
819 u = '-' + s
820 elif l.startswith(' '):
820 elif l.startswith(' '):
821 u = ' ' + s
821 u = ' ' + s
822 else:
822 else:
823 raise PatchError(_("bad hunk #%d old text line %d") %
823 raise PatchError(_("bad hunk #%d old text line %d") %
824 (self.number, x))
824 (self.number, x))
825 self.a.append(u)
825 self.a.append(u)
826 self.hunk.append(u)
826 self.hunk.append(u)
827
827
828 l = lr.readline()
828 l = lr.readline()
829 if l.startswith('\ '):
829 if l.startswith('\ '):
830 s = self.a[-1][:-1]
830 s = self.a[-1][:-1]
831 self.a[-1] = s
831 self.a[-1] = s
832 self.hunk[-1] = s
832 self.hunk[-1] = s
833 l = lr.readline()
833 l = lr.readline()
834 m = contextdesc.match(l)
834 m = contextdesc.match(l)
835 if not m:
835 if not m:
836 raise PatchError(_("bad hunk #%d") % self.number)
836 raise PatchError(_("bad hunk #%d") % self.number)
837 foo, self.startb, foo2, bend, foo3 = m.groups()
837 foo, self.startb, foo2, bend, foo3 = m.groups()
838 self.startb = int(self.startb)
838 self.startb = int(self.startb)
839 if bend is None:
839 if bend is None:
840 bend = self.startb
840 bend = self.startb
841 self.lenb = int(bend) - self.startb
841 self.lenb = int(bend) - self.startb
842 if self.startb:
842 if self.startb:
843 self.lenb += 1
843 self.lenb += 1
844 hunki = 1
844 hunki = 1
845 for x in xrange(self.lenb):
845 for x in xrange(self.lenb):
846 l = lr.readline()
846 l = lr.readline()
847 if l.startswith('\ '):
847 if l.startswith('\ '):
848 # XXX: the only way to hit this is with an invalid line range.
848 # XXX: the only way to hit this is with an invalid line range.
849 # The no-eol marker is not counted in the line range, but I
849 # The no-eol marker is not counted in the line range, but I
850 # guess there are diff(1) out there which behave differently.
850 # guess there are diff(1) out there which behave differently.
851 s = self.b[-1][:-1]
851 s = self.b[-1][:-1]
852 self.b[-1] = s
852 self.b[-1] = s
853 self.hunk[hunki - 1] = s
853 self.hunk[hunki - 1] = s
854 continue
854 continue
855 if not l:
855 if not l:
856 # line deletions, new block is empty and we hit EOF
856 # line deletions, new block is empty and we hit EOF
857 lr.push(l)
857 lr.push(l)
858 break
858 break
859 s = l[2:]
859 s = l[2:]
860 if l.startswith('+ ') or l.startswith('! '):
860 if l.startswith('+ ') or l.startswith('! '):
861 u = '+' + s
861 u = '+' + s
862 elif l.startswith(' '):
862 elif l.startswith(' '):
863 u = ' ' + s
863 u = ' ' + s
864 elif len(self.b) == 0:
864 elif len(self.b) == 0:
865 # line deletions, new block is empty
865 # line deletions, new block is empty
866 lr.push(l)
866 lr.push(l)
867 break
867 break
868 else:
868 else:
869 raise PatchError(_("bad hunk #%d old text line %d") %
869 raise PatchError(_("bad hunk #%d old text line %d") %
870 (self.number, x))
870 (self.number, x))
871 self.b.append(s)
871 self.b.append(s)
872 while True:
872 while True:
873 if hunki >= len(self.hunk):
873 if hunki >= len(self.hunk):
874 h = ""
874 h = ""
875 else:
875 else:
876 h = self.hunk[hunki]
876 h = self.hunk[hunki]
877 hunki += 1
877 hunki += 1
878 if h == u:
878 if h == u:
879 break
879 break
880 elif h.startswith('-'):
880 elif h.startswith('-'):
881 continue
881 continue
882 else:
882 else:
883 self.hunk.insert(hunki - 1, u)
883 self.hunk.insert(hunki - 1, u)
884 break
884 break
885
885
886 if not self.a:
886 if not self.a:
887 # this happens when lines were only added to the hunk
887 # this happens when lines were only added to the hunk
888 for x in self.hunk:
888 for x in self.hunk:
889 if x.startswith('-') or x.startswith(' '):
889 if x.startswith('-') or x.startswith(' '):
890 self.a.append(x)
890 self.a.append(x)
891 if not self.b:
891 if not self.b:
892 # this happens when lines were only deleted from the hunk
892 # this happens when lines were only deleted from the hunk
893 for x in self.hunk:
893 for x in self.hunk:
894 if x.startswith('+') or x.startswith(' '):
894 if x.startswith('+') or x.startswith(' '):
895 self.b.append(x[1:])
895 self.b.append(x[1:])
896 # @@ -start,len +start,len @@
896 # @@ -start,len +start,len @@
897 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
897 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
898 self.startb, self.lenb)
898 self.startb, self.lenb)
899 self.hunk[0] = self.desc
899 self.hunk[0] = self.desc
900 self._fixnewline(lr)
900 self._fixnewline(lr)
901
901
902 def _fixnewline(self, lr):
902 def _fixnewline(self, lr):
903 l = lr.readline()
903 l = lr.readline()
904 if l.startswith('\ '):
904 if l.startswith('\ '):
905 diffhelpers.fix_newline(self.hunk, self.a, self.b)
905 diffhelpers.fix_newline(self.hunk, self.a, self.b)
906 else:
906 else:
907 lr.push(l)
907 lr.push(l)
908
908
909 def complete(self):
909 def complete(self):
910 return len(self.a) == self.lena and len(self.b) == self.lenb
910 return len(self.a) == self.lena and len(self.b) == self.lenb
911
911
912 def createfile(self):
912 def createfile(self):
913 return self.starta == 0 and self.lena == 0 and self.create
913 return self.starta == 0 and self.lena == 0 and self.create
914
914
915 def rmfile(self):
915 def rmfile(self):
916 return self.startb == 0 and self.lenb == 0 and self.remove
916 return self.startb == 0 and self.lenb == 0 and self.remove
917
917
918 def fuzzit(self, l, fuzz, toponly):
918 def fuzzit(self, l, fuzz, toponly):
919 # this removes context lines from the top and bottom of list 'l'. It
919 # this removes context lines from the top and bottom of list 'l'. It
920 # checks the hunk to make sure only context lines are removed, and then
920 # checks the hunk to make sure only context lines are removed, and then
921 # returns a new shortened list of lines.
921 # returns a new shortened list of lines.
922 fuzz = min(fuzz, len(l)-1)
922 fuzz = min(fuzz, len(l)-1)
923 if fuzz:
923 if fuzz:
924 top = 0
924 top = 0
925 bot = 0
925 bot = 0
926 hlen = len(self.hunk)
926 hlen = len(self.hunk)
927 for x in xrange(hlen - 1):
927 for x in xrange(hlen - 1):
928 # the hunk starts with the @@ line, so use x+1
928 # the hunk starts with the @@ line, so use x+1
929 if self.hunk[x + 1][0] == ' ':
929 if self.hunk[x + 1][0] == ' ':
930 top += 1
930 top += 1
931 else:
931 else:
932 break
932 break
933 if not toponly:
933 if not toponly:
934 for x in xrange(hlen - 1):
934 for x in xrange(hlen - 1):
935 if self.hunk[hlen - bot - 1][0] == ' ':
935 if self.hunk[hlen - bot - 1][0] == ' ':
936 bot += 1
936 bot += 1
937 else:
937 else:
938 break
938 break
939
939
940 # top and bot now count context in the hunk
940 # top and bot now count context in the hunk
941 # adjust them if either one is short
941 # adjust them if either one is short
942 context = max(top, bot, 3)
942 context = max(top, bot, 3)
943 if bot < context:
943 if bot < context:
944 bot = max(0, fuzz - (context - bot))
944 bot = max(0, fuzz - (context - bot))
945 else:
945 else:
946 bot = min(fuzz, bot)
946 bot = min(fuzz, bot)
947 if top < context:
947 if top < context:
948 top = max(0, fuzz - (context - top))
948 top = max(0, fuzz - (context - top))
949 else:
949 else:
950 top = min(fuzz, top)
950 top = min(fuzz, top)
951
951
952 return l[top:len(l)-bot]
952 return l[top:len(l)-bot]
953 return l
953 return l
954
954
955 def old(self, fuzz=0, toponly=False):
955 def old(self, fuzz=0, toponly=False):
956 return self.fuzzit(self.a, fuzz, toponly)
956 return self.fuzzit(self.a, fuzz, toponly)
957
957
958 def new(self, fuzz=0, toponly=False):
958 def new(self, fuzz=0, toponly=False):
959 return self.fuzzit(self.b, fuzz, toponly)
959 return self.fuzzit(self.b, fuzz, toponly)
960
960
961 class binhunk:
961 class binhunk:
962 'A binary patch file. Only understands literals so far.'
962 'A binary patch file. Only understands literals so far.'
963 def __init__(self, gitpatch):
963 def __init__(self, gitpatch):
964 self.gitpatch = gitpatch
964 self.gitpatch = gitpatch
965 self.text = None
965 self.text = None
966 self.hunk = ['GIT binary patch\n']
966 self.hunk = ['GIT binary patch\n']
967
967
968 def createfile(self):
968 def createfile(self):
969 return self.gitpatch.op == 'ADD'
969 return self.gitpatch.op == 'ADD'
970
970
971 def rmfile(self):
971 def rmfile(self):
972 return self.gitpatch.op == 'DELETE'
972 return self.gitpatch.op == 'DELETE'
973
973
974 def complete(self):
974 def complete(self):
975 return self.text is not None
975 return self.text is not None
976
976
977 def new(self):
977 def new(self):
978 return [self.text]
978 return [self.text]
979
979
980 def extract(self, lr):
980 def extract(self, lr):
981 line = lr.readline()
981 line = lr.readline()
982 self.hunk.append(line)
982 self.hunk.append(line)
983 while line and not line.startswith('literal '):
983 while line and not line.startswith('literal '):
984 line = lr.readline()
984 line = lr.readline()
985 self.hunk.append(line)
985 self.hunk.append(line)
986 if not line:
986 if not line:
987 raise PatchError(_('could not extract binary patch'))
987 raise PatchError(_('could not extract binary patch'))
988 size = int(line[8:].rstrip())
988 size = int(line[8:].rstrip())
989 dec = []
989 dec = []
990 line = lr.readline()
990 line = lr.readline()
991 self.hunk.append(line)
991 self.hunk.append(line)
992 while len(line) > 1:
992 while len(line) > 1:
993 l = line[0]
993 l = line[0]
994 if l <= 'Z' and l >= 'A':
994 if l <= 'Z' and l >= 'A':
995 l = ord(l) - ord('A') + 1
995 l = ord(l) - ord('A') + 1
996 else:
996 else:
997 l = ord(l) - ord('a') + 27
997 l = ord(l) - ord('a') + 27
998 dec.append(base85.b85decode(line[1:-1])[:l])
998 dec.append(base85.b85decode(line[1:-1])[:l])
999 line = lr.readline()
999 line = lr.readline()
1000 self.hunk.append(line)
1000 self.hunk.append(line)
1001 text = zlib.decompress(''.join(dec))
1001 text = zlib.decompress(''.join(dec))
1002 if len(text) != size:
1002 if len(text) != size:
1003 raise PatchError(_('binary patch is %d bytes, not %d') %
1003 raise PatchError(_('binary patch is %d bytes, not %d') %
1004 len(text), size)
1004 len(text), size)
1005 self.text = text
1005 self.text = text
1006
1006
1007 def parsefilename(str):
1007 def parsefilename(str):
1008 # --- filename \t|space stuff
1008 # --- filename \t|space stuff
1009 s = str[4:].rstrip('\r\n')
1009 s = str[4:].rstrip('\r\n')
1010 i = s.find('\t')
1010 i = s.find('\t')
1011 if i < 0:
1011 if i < 0:
1012 i = s.find(' ')
1012 i = s.find(' ')
1013 if i < 0:
1013 if i < 0:
1014 return s
1014 return s
1015 return s[:i]
1015 return s[:i]
1016
1016
1017 def pathstrip(path, strip):
1017 def pathstrip(path, strip):
1018 pathlen = len(path)
1018 pathlen = len(path)
1019 i = 0
1019 i = 0
1020 if strip == 0:
1020 if strip == 0:
1021 return '', path.rstrip()
1021 return '', path.rstrip()
1022 count = strip
1022 count = strip
1023 while count > 0:
1023 while count > 0:
1024 i = path.find('/', i)
1024 i = path.find('/', i)
1025 if i == -1:
1025 if i == -1:
1026 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1026 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1027 (count, strip, path))
1027 (count, strip, path))
1028 i += 1
1028 i += 1
1029 # consume '//' in the path
1029 # consume '//' in the path
1030 while i < pathlen - 1 and path[i] == '/':
1030 while i < pathlen - 1 and path[i] == '/':
1031 i += 1
1031 i += 1
1032 count -= 1
1032 count -= 1
1033 return path[:i].lstrip(), path[i:].rstrip()
1033 return path[:i].lstrip(), path[i:].rstrip()
1034
1034
1035 def selectfile(backend, afile_orig, bfile_orig, hunk, strip):
1035 def selectfile(backend, afile_orig, bfile_orig, hunk, strip):
1036 nulla = afile_orig == "/dev/null"
1036 nulla = afile_orig == "/dev/null"
1037 nullb = bfile_orig == "/dev/null"
1037 nullb = bfile_orig == "/dev/null"
1038 abase, afile = pathstrip(afile_orig, strip)
1038 abase, afile = pathstrip(afile_orig, strip)
1039 gooda = not nulla and backend.exists(afile)
1039 gooda = not nulla and backend.exists(afile)
1040 bbase, bfile = pathstrip(bfile_orig, strip)
1040 bbase, bfile = pathstrip(bfile_orig, strip)
1041 if afile == bfile:
1041 if afile == bfile:
1042 goodb = gooda
1042 goodb = gooda
1043 else:
1043 else:
1044 goodb = not nullb and backend.exists(bfile)
1044 goodb = not nullb and backend.exists(bfile)
1045 createfunc = hunk.createfile
1045 createfunc = hunk.createfile
1046 missing = not goodb and not gooda and not createfunc()
1046 missing = not goodb and not gooda and not createfunc()
1047
1047
1048 # some diff programs apparently produce patches where the afile is
1048 # some diff programs apparently produce patches where the afile is
1049 # not /dev/null, but afile starts with bfile
1049 # not /dev/null, but afile starts with bfile
1050 abasedir = afile[:afile.rfind('/') + 1]
1050 abasedir = afile[:afile.rfind('/') + 1]
1051 bbasedir = bfile[:bfile.rfind('/') + 1]
1051 bbasedir = bfile[:bfile.rfind('/') + 1]
1052 if missing and abasedir == bbasedir and afile.startswith(bfile):
1052 if missing and abasedir == bbasedir and afile.startswith(bfile):
1053 # this isn't very pretty
1053 # this isn't very pretty
1054 hunk.create = True
1054 hunk.create = True
1055 if createfunc():
1055 if createfunc():
1056 missing = False
1056 missing = False
1057 else:
1057 else:
1058 hunk.create = False
1058 hunk.create = False
1059
1059
1060 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1060 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1061 # diff is between a file and its backup. In this case, the original
1061 # diff is between a file and its backup. In this case, the original
1062 # file should be patched (see original mpatch code).
1062 # file should be patched (see original mpatch code).
1063 isbackup = (abase == bbase and bfile.startswith(afile))
1063 isbackup = (abase == bbase and bfile.startswith(afile))
1064 fname = None
1064 fname = None
1065 if not missing:
1065 if not missing:
1066 if gooda and goodb:
1066 if gooda and goodb:
1067 fname = isbackup and afile or bfile
1067 fname = isbackup and afile or bfile
1068 elif gooda:
1068 elif gooda:
1069 fname = afile
1069 fname = afile
1070
1070
1071 if not fname:
1071 if not fname:
1072 if not nullb:
1072 if not nullb:
1073 fname = isbackup and afile or bfile
1073 fname = isbackup and afile or bfile
1074 elif not nulla:
1074 elif not nulla:
1075 fname = afile
1075 fname = afile
1076 else:
1076 else:
1077 raise PatchError(_("undefined source and destination files"))
1077 raise PatchError(_("undefined source and destination files"))
1078
1078
1079 return fname, missing
1079 return fname, missing
1080
1080
1081 def scangitpatch(lr, firstline):
1081 def scangitpatch(lr, firstline):
1082 """
1082 """
1083 Git patches can emit:
1083 Git patches can emit:
1084 - rename a to b
1084 - rename a to b
1085 - change b
1085 - change b
1086 - copy a to c
1086 - copy a to c
1087 - change c
1087 - change c
1088
1088
1089 We cannot apply this sequence as-is, the renamed 'a' could not be
1089 We cannot apply this sequence as-is, the renamed 'a' could not be
1090 found for it would have been renamed already. And we cannot copy
1090 found for it would have been renamed already. And we cannot copy
1091 from 'b' instead because 'b' would have been changed already. So
1091 from 'b' instead because 'b' would have been changed already. So
1092 we scan the git patch for copy and rename commands so we can
1092 we scan the git patch for copy and rename commands so we can
1093 perform the copies ahead of time.
1093 perform the copies ahead of time.
1094 """
1094 """
1095 pos = 0
1095 pos = 0
1096 try:
1096 try:
1097 pos = lr.fp.tell()
1097 pos = lr.fp.tell()
1098 fp = lr.fp
1098 fp = lr.fp
1099 except IOError:
1099 except IOError:
1100 fp = cStringIO.StringIO(lr.fp.read())
1100 fp = cStringIO.StringIO(lr.fp.read())
1101 gitlr = linereader(fp, lr.textmode)
1101 gitlr = linereader(fp, lr.textmode)
1102 gitlr.push(firstline)
1102 gitlr.push(firstline)
1103 gitpatches = readgitpatch(gitlr)
1103 gitpatches = readgitpatch(gitlr)
1104 fp.seek(pos)
1104 fp.seek(pos)
1105 return gitpatches
1105 return gitpatches
1106
1106
1107 def iterhunks(fp):
1107 def iterhunks(fp):
1108 """Read a patch and yield the following events:
1108 """Read a patch and yield the following events:
1109 - ("file", afile, bfile, firsthunk): select a new target file.
1109 - ("file", afile, bfile, firsthunk): select a new target file.
1110 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1110 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1111 "file" event.
1111 "file" event.
1112 - ("git", gitchanges): current diff is in git format, gitchanges
1112 - ("git", gitchanges): current diff is in git format, gitchanges
1113 maps filenames to gitpatch records. Unique event.
1113 maps filenames to gitpatch records. Unique event.
1114 """
1114 """
1115 changed = {}
1115 changed = {}
1116 afile = ""
1116 afile = ""
1117 bfile = ""
1117 bfile = ""
1118 state = None
1118 state = None
1119 hunknum = 0
1119 hunknum = 0
1120 emitfile = newfile = False
1120 emitfile = newfile = False
1121 git = False
1121 git = False
1122
1122
1123 # our states
1123 # our states
1124 BFILE = 1
1124 BFILE = 1
1125 context = None
1125 context = None
1126 lr = linereader(fp)
1126 lr = linereader(fp)
1127
1127
1128 while True:
1128 while True:
1129 x = lr.readline()
1129 x = lr.readline()
1130 if not x:
1130 if not x:
1131 break
1131 break
1132 if (state == BFILE and ((not context and x[0] == '@') or
1132 if (state == BFILE and ((not context and x[0] == '@') or
1133 ((context is not False) and x.startswith('***************')))):
1133 ((context is not False) and x.startswith('***************')))):
1134 if context is None and x.startswith('***************'):
1134 if context is None and x.startswith('***************'):
1135 context = True
1135 context = True
1136 gpatch = changed.get(bfile)
1136 gpatch = changed.get(bfile)
1137 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
1137 create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD'
1138 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
1138 remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE'
1139 h = hunk(x, hunknum + 1, lr, context, create, remove)
1139 h = hunk(x, hunknum + 1, lr, context, create, remove)
1140 hunknum += 1
1140 hunknum += 1
1141 if emitfile:
1141 if emitfile:
1142 emitfile = False
1142 emitfile = False
1143 yield 'file', (afile, bfile, h, gpatch and gpatch.mode or None)
1143 yield 'file', (afile, bfile, h, gpatch and gpatch.mode or None)
1144 yield 'hunk', h
1144 yield 'hunk', h
1145 elif state == BFILE and x.startswith('GIT binary patch'):
1145 elif state == BFILE and x.startswith('GIT binary patch'):
1146 gpatch = changed[bfile]
1146 gpatch = changed[bfile]
1147 h = binhunk(gpatch)
1147 h = binhunk(gpatch)
1148 hunknum += 1
1148 hunknum += 1
1149 if emitfile:
1149 if emitfile:
1150 emitfile = False
1150 emitfile = False
1151 yield 'file', ('a/' + afile, 'b/' + bfile, h,
1151 yield 'file', ('a/' + afile, 'b/' + bfile, h,
1152 gpatch and gpatch.mode or None)
1152 gpatch and gpatch.mode or None)
1153 h.extract(lr)
1153 h.extract(lr)
1154 yield 'hunk', h
1154 yield 'hunk', h
1155 elif x.startswith('diff --git'):
1155 elif x.startswith('diff --git'):
1156 # check for git diff, scanning the whole patch file if needed
1156 # check for git diff, scanning the whole patch file if needed
1157 m = gitre.match(x)
1157 m = gitre.match(x)
1158 if m:
1158 if m:
1159 afile, bfile = m.group(1, 2)
1159 afile, bfile = m.group(1, 2)
1160 if not git:
1160 if not git:
1161 git = True
1161 git = True
1162 gitpatches = scangitpatch(lr, x)
1162 gitpatches = scangitpatch(lr, x)
1163 yield 'git', gitpatches
1163 yield 'git', gitpatches
1164 for gp in gitpatches:
1164 for gp in gitpatches:
1165 changed[gp.path] = gp
1165 changed[gp.path] = gp
1166 # else error?
1166 # else error?
1167 # copy/rename + modify should modify target, not source
1167 # copy/rename + modify should modify target, not source
1168 gp = changed.get(bfile)
1168 gp = changed.get(bfile)
1169 if gp and (gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD')
1169 if gp and (gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD')
1170 or gp.mode):
1170 or gp.mode):
1171 afile = bfile
1171 afile = bfile
1172 newfile = True
1172 newfile = True
1173 elif x.startswith('---'):
1173 elif x.startswith('---'):
1174 # check for a unified diff
1174 # check for a unified diff
1175 l2 = lr.readline()
1175 l2 = lr.readline()
1176 if not l2.startswith('+++'):
1176 if not l2.startswith('+++'):
1177 lr.push(l2)
1177 lr.push(l2)
1178 continue
1178 continue
1179 newfile = True
1179 newfile = True
1180 context = False
1180 context = False
1181 afile = parsefilename(x)
1181 afile = parsefilename(x)
1182 bfile = parsefilename(l2)
1182 bfile = parsefilename(l2)
1183 elif x.startswith('***'):
1183 elif x.startswith('***'):
1184 # check for a context diff
1184 # check for a context diff
1185 l2 = lr.readline()
1185 l2 = lr.readline()
1186 if not l2.startswith('---'):
1186 if not l2.startswith('---'):
1187 lr.push(l2)
1187 lr.push(l2)
1188 continue
1188 continue
1189 l3 = lr.readline()
1189 l3 = lr.readline()
1190 lr.push(l3)
1190 lr.push(l3)
1191 if not l3.startswith("***************"):
1191 if not l3.startswith("***************"):
1192 lr.push(l2)
1192 lr.push(l2)
1193 continue
1193 continue
1194 newfile = True
1194 newfile = True
1195 context = True
1195 context = True
1196 afile = parsefilename(x)
1196 afile = parsefilename(x)
1197 bfile = parsefilename(l2)
1197 bfile = parsefilename(l2)
1198
1198
1199 if newfile:
1199 if newfile:
1200 newfile = False
1200 newfile = False
1201 emitfile = True
1201 emitfile = True
1202 state = BFILE
1202 state = BFILE
1203 hunknum = 0
1203 hunknum = 0
1204
1204
1205 def applydiff(ui, fp, changed, backend, strip=1, eolmode='strict'):
1205 def applydiff(ui, fp, changed, backend, strip=1, eolmode='strict'):
1206 """Reads a patch from fp and tries to apply it.
1206 """Reads a patch from fp and tries to apply it.
1207
1207
1208 The dict 'changed' is filled in with all of the filenames changed
1208 The dict 'changed' is filled in with all of the filenames changed
1209 by the patch. Returns 0 for a clean patch, -1 if any rejects were
1209 by the patch. Returns 0 for a clean patch, -1 if any rejects were
1210 found and 1 if there was any fuzz.
1210 found and 1 if there was any fuzz.
1211
1211
1212 If 'eolmode' is 'strict', the patch content and patched file are
1212 If 'eolmode' is 'strict', the patch content and patched file are
1213 read in binary mode. Otherwise, line endings are ignored when
1213 read in binary mode. Otherwise, line endings are ignored when
1214 patching then normalized according to 'eolmode'.
1214 patching then normalized according to 'eolmode'.
1215 """
1215 """
1216 return _applydiff(ui, fp, patchfile, backend, changed, strip=strip,
1216 return _applydiff(ui, fp, patchfile, backend, changed, strip=strip,
1217 eolmode=eolmode)
1217 eolmode=eolmode)
1218
1218
1219 def _applydiff(ui, fp, patcher, backend, changed, strip=1, eolmode='strict'):
1219 def _applydiff(ui, fp, patcher, backend, changed, strip=1, eolmode='strict'):
1220 rejects = 0
1220 rejects = 0
1221 err = 0
1221 err = 0
1222 current_file = None
1222 current_file = None
1223
1223
1224 for state, values in iterhunks(fp):
1224 for state, values in iterhunks(fp):
1225 if state == 'hunk':
1225 if state == 'hunk':
1226 if not current_file:
1226 if not current_file:
1227 continue
1227 continue
1228 ret = current_file.apply(values)
1228 ret = current_file.apply(values)
1229 if ret >= 0:
1229 if ret >= 0:
1230 changed.setdefault(current_file.fname, None)
1230 changed.setdefault(current_file.fname, None)
1231 if ret > 0:
1231 if ret > 0:
1232 err = 1
1232 err = 1
1233 elif state == 'file':
1233 elif state == 'file':
1234 if current_file:
1234 if current_file:
1235 rejects += current_file.close()
1235 rejects += current_file.close()
1236 afile, bfile, first_hunk, mode = values
1236 afile, bfile, first_hunk, mode = values
1237 try:
1237 try:
1238 current_file, missing = selectfile(backend, afile, bfile,
1238 current_file, missing = selectfile(backend, afile, bfile,
1239 first_hunk, strip)
1239 first_hunk, strip)
1240 current_file = patcher(ui, current_file, backend, mode,
1240 current_file = patcher(ui, current_file, backend, mode,
1241 missing=missing, eolmode=eolmode)
1241 missing=missing, eolmode=eolmode)
1242 except PatchError, inst:
1242 except PatchError, inst:
1243 ui.warn(str(inst) + '\n')
1243 ui.warn(str(inst) + '\n')
1244 current_file = None
1244 current_file = None
1245 rejects += 1
1245 rejects += 1
1246 continue
1246 continue
1247 elif state == 'git':
1247 elif state == 'git':
1248 for gp in values:
1248 for gp in values:
1249 gp.path = pathstrip(gp.path, strip - 1)[1]
1249 gp.path = pathstrip(gp.path, strip - 1)[1]
1250 if gp.oldpath:
1250 if gp.oldpath:
1251 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1251 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1252 if gp.op in ('COPY', 'RENAME'):
1252 if gp.op in ('COPY', 'RENAME'):
1253 backend.copy(gp.oldpath, gp.path)
1253 backend.copy(gp.oldpath, gp.path)
1254 changed[gp.path] = gp
1254 changed[gp.path] = gp
1255 else:
1255 else:
1256 raise util.Abort(_('unsupported parser state: %s') % state)
1256 raise util.Abort(_('unsupported parser state: %s') % state)
1257
1257
1258 if current_file:
1258 if current_file:
1259 rejects += current_file.close()
1259 rejects += current_file.close()
1260
1260
1261 # Handle mode changes without hunk
1261 # Handle mode changes without hunk
1262 removed = set()
1262 removed = set()
1263 for gp in changed.itervalues():
1263 for gp in changed.itervalues():
1264 if not gp:
1264 if not gp:
1265 continue
1265 continue
1266 if gp.op == 'DELETE':
1266 if gp.op == 'DELETE':
1267 removed.add(gp.path)
1267 removed.add(gp.path)
1268 continue
1268 continue
1269 if gp.op == 'RENAME':
1269 if gp.op == 'RENAME':
1270 removed.add(gp.oldpath)
1270 removed.add(gp.oldpath)
1271 if gp.mode:
1271 if gp.mode:
1272 if gp.op == 'ADD' and not backend.exists(gp.path):
1272 if gp.op == 'ADD' and not backend.exists(gp.path):
1273 # Added files without content have no hunk and must be created
1273 # Added files without content have no hunk and must be created
1274 backend.writelines(gp.path, [], gp.mode)
1274 backend.writelines(gp.path, [], gp.mode)
1275 else:
1275 else:
1276 backend.setmode(gp.path, gp.mode[0], gp.mode[1])
1276 backend.setmode(gp.path, gp.mode[0], gp.mode[1])
1277 for path in sorted(removed):
1277 for path in sorted(removed):
1278 backend.unlink(path)
1278 backend.unlink(path)
1279
1279
1280 if rejects:
1280 if rejects:
1281 return -1
1281 return -1
1282 return err
1282 return err
1283
1283
1284 def _externalpatch(ui, repo, patcher, patchname, strip, cwd, files,
1284 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1285 similarity):
1285 similarity):
1286 """use <patcher> to apply <patchname> to the working directory.
1286 """use <patcher> to apply <patchname> to the working directory.
1287 returns whether patch was applied with fuzz factor."""
1287 returns whether patch was applied with fuzz factor."""
1288
1288
1289 fuzz = False
1289 fuzz = False
1290 args = []
1290 args = []
1291 cwd = repo.root
1291 if cwd:
1292 if cwd:
1292 args.append('-d %s' % util.shellquote(cwd))
1293 args.append('-d %s' % util.shellquote(cwd))
1293 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1294 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1294 util.shellquote(patchname)))
1295 util.shellquote(patchname)))
1295 try:
1296 try:
1296 for line in fp:
1297 for line in fp:
1297 line = line.rstrip()
1298 line = line.rstrip()
1298 ui.note(line + '\n')
1299 ui.note(line + '\n')
1299 if line.startswith('patching file '):
1300 if line.startswith('patching file '):
1300 pf = util.parsepatchoutput(line)
1301 pf = util.parsepatchoutput(line)
1301 printed_file = False
1302 printed_file = False
1302 files.setdefault(pf, None)
1303 files.setdefault(pf, None)
1303 elif line.find('with fuzz') >= 0:
1304 elif line.find('with fuzz') >= 0:
1304 fuzz = True
1305 fuzz = True
1305 if not printed_file:
1306 if not printed_file:
1306 ui.warn(pf + '\n')
1307 ui.warn(pf + '\n')
1307 printed_file = True
1308 printed_file = True
1308 ui.warn(line + '\n')
1309 ui.warn(line + '\n')
1309 elif line.find('saving rejects to file') >= 0:
1310 elif line.find('saving rejects to file') >= 0:
1310 ui.warn(line + '\n')
1311 ui.warn(line + '\n')
1311 elif line.find('FAILED') >= 0:
1312 elif line.find('FAILED') >= 0:
1312 if not printed_file:
1313 if not printed_file:
1313 ui.warn(pf + '\n')
1314 ui.warn(pf + '\n')
1314 printed_file = True
1315 printed_file = True
1315 ui.warn(line + '\n')
1316 ui.warn(line + '\n')
1316 finally:
1317 finally:
1317 if files:
1318 if files:
1318 cfiles = list(files)
1319 cfiles = list(files)
1319 cwd = repo.getcwd()
1320 cwd = repo.getcwd()
1320 if cwd:
1321 if cwd:
1321 cfiles = [util.pathto(repo.root, cwd, f)
1322 cfiles = [util.pathto(repo.root, cwd, f)
1322 for f in cfile]
1323 for f in cfile]
1323 scmutil.addremove(repo, cfiles, similarity=similarity)
1324 scmutil.addremove(repo, cfiles, similarity=similarity)
1324 code = fp.close()
1325 code = fp.close()
1325 if code:
1326 if code:
1326 raise PatchError(_("patch command failed: %s") %
1327 raise PatchError(_("patch command failed: %s") %
1327 util.explainexit(code)[0])
1328 util.explainexit(code)[0])
1328 return fuzz
1329 return fuzz
1329
1330
1330 def internalpatch(ui, repo, patchobj, strip, files=None, eolmode='strict',
1331 def internalpatch(ui, repo, patchobj, strip, files=None, eolmode='strict',
1331 similarity=0):
1332 similarity=0):
1332 """use builtin patch to apply <patchobj> to the working directory.
1333 """use builtin patch to apply <patchobj> to the working directory.
1333 returns whether patch was applied with fuzz factor."""
1334 returns whether patch was applied with fuzz factor."""
1334
1335
1335 if files is None:
1336 if files is None:
1336 files = {}
1337 files = {}
1337 if eolmode is None:
1338 if eolmode is None:
1338 eolmode = ui.config('patch', 'eol', 'strict')
1339 eolmode = ui.config('patch', 'eol', 'strict')
1339 if eolmode.lower() not in eolmodes:
1340 if eolmode.lower() not in eolmodes:
1340 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1341 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1341 eolmode = eolmode.lower()
1342 eolmode = eolmode.lower()
1342
1343
1343 backend = workingbackend(ui, repo, similarity)
1344 backend = workingbackend(ui, repo, similarity)
1344 try:
1345 try:
1345 fp = open(patchobj, 'rb')
1346 fp = open(patchobj, 'rb')
1346 except TypeError:
1347 except TypeError:
1347 fp = patchobj
1348 fp = patchobj
1348 try:
1349 try:
1349 ret = applydiff(ui, fp, files, backend, strip=strip, eolmode=eolmode)
1350 ret = applydiff(ui, fp, files, backend, strip=strip, eolmode=eolmode)
1350 finally:
1351 finally:
1351 if fp != patchobj:
1352 if fp != patchobj:
1352 fp.close()
1353 fp.close()
1353 files.update(dict.fromkeys(backend.close()))
1354 files.update(dict.fromkeys(backend.close()))
1354 if ret < 0:
1355 if ret < 0:
1355 raise PatchError(_('patch failed to apply'))
1356 raise PatchError(_('patch failed to apply'))
1356 return ret > 0
1357 return ret > 0
1357
1358
1358 def patch(ui, repo, patchname, strip=1, cwd=None, files=None, eolmode='strict',
1359 def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
1359 similarity=0):
1360 similarity=0):
1360 """Apply <patchname> to the working directory.
1361 """Apply <patchname> to the working directory.
1361
1362
1362 'eolmode' specifies how end of lines should be handled. It can be:
1363 'eolmode' specifies how end of lines should be handled. It can be:
1363 - 'strict': inputs are read in binary mode, EOLs are preserved
1364 - 'strict': inputs are read in binary mode, EOLs are preserved
1364 - 'crlf': EOLs are ignored when patching and reset to CRLF
1365 - 'crlf': EOLs are ignored when patching and reset to CRLF
1365 - 'lf': EOLs are ignored when patching and reset to LF
1366 - 'lf': EOLs are ignored when patching and reset to LF
1366 - None: get it from user settings, default to 'strict'
1367 - None: get it from user settings, default to 'strict'
1367 'eolmode' is ignored when using an external patcher program.
1368 'eolmode' is ignored when using an external patcher program.
1368
1369
1369 Returns whether patch was applied with fuzz factor.
1370 Returns whether patch was applied with fuzz factor.
1370 """
1371 """
1371 patcher = ui.config('ui', 'patch')
1372 patcher = ui.config('ui', 'patch')
1372 if files is None:
1373 if files is None:
1373 files = {}
1374 files = {}
1374 try:
1375 try:
1375 if patcher:
1376 if patcher:
1376 return _externalpatch(ui, repo, patcher, patchname, strip,
1377 return _externalpatch(ui, repo, patcher, patchname, strip,
1377 cwd, files, similarity)
1378 files, similarity)
1378 return internalpatch(ui, repo, patchname, strip, files, eolmode,
1379 return internalpatch(ui, repo, patchname, strip, files, eolmode,
1379 similarity)
1380 similarity)
1380 except PatchError, err:
1381 except PatchError, err:
1381 raise util.Abort(str(err))
1382 raise util.Abort(str(err))
1382
1383
1383 def changedfiles(ui, repo, patchpath, strip=1):
1384 def changedfiles(ui, repo, patchpath, strip=1):
1384 backend = fsbackend(ui, repo.root)
1385 backend = fsbackend(ui, repo.root)
1385 fp = open(patchpath, 'rb')
1386 fp = open(patchpath, 'rb')
1386 try:
1387 try:
1387 changed = set()
1388 changed = set()
1388 for state, values in iterhunks(fp):
1389 for state, values in iterhunks(fp):
1389 if state == 'hunk':
1390 if state == 'hunk':
1390 continue
1391 continue
1391 elif state == 'file':
1392 elif state == 'file':
1392 afile, bfile, first_hunk, mode = values
1393 afile, bfile, first_hunk, mode = values
1393 current_file, missing = selectfile(backend, afile, bfile,
1394 current_file, missing = selectfile(backend, afile, bfile,
1394 first_hunk, strip)
1395 first_hunk, strip)
1395 changed.add(current_file)
1396 changed.add(current_file)
1396 elif state == 'git':
1397 elif state == 'git':
1397 for gp in values:
1398 for gp in values:
1398 gp.path = pathstrip(gp.path, strip - 1)[1]
1399 gp.path = pathstrip(gp.path, strip - 1)[1]
1399 changed.add(gp.path)
1400 changed.add(gp.path)
1400 if gp.oldpath:
1401 if gp.oldpath:
1401 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1402 gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1]
1402 if gp.op == 'RENAME':
1403 if gp.op == 'RENAME':
1403 changed.add(gp.oldpath)
1404 changed.add(gp.oldpath)
1404 else:
1405 else:
1405 raise util.Abort(_('unsupported parser state: %s') % state)
1406 raise util.Abort(_('unsupported parser state: %s') % state)
1406 return changed
1407 return changed
1407 finally:
1408 finally:
1408 fp.close()
1409 fp.close()
1409
1410
1410 def b85diff(to, tn):
1411 def b85diff(to, tn):
1411 '''print base85-encoded binary diff'''
1412 '''print base85-encoded binary diff'''
1412 def gitindex(text):
1413 def gitindex(text):
1413 if not text:
1414 if not text:
1414 return hex(nullid)
1415 return hex(nullid)
1415 l = len(text)
1416 l = len(text)
1416 s = util.sha1('blob %d\0' % l)
1417 s = util.sha1('blob %d\0' % l)
1417 s.update(text)
1418 s.update(text)
1418 return s.hexdigest()
1419 return s.hexdigest()
1419
1420
1420 def fmtline(line):
1421 def fmtline(line):
1421 l = len(line)
1422 l = len(line)
1422 if l <= 26:
1423 if l <= 26:
1423 l = chr(ord('A') + l - 1)
1424 l = chr(ord('A') + l - 1)
1424 else:
1425 else:
1425 l = chr(l - 26 + ord('a') - 1)
1426 l = chr(l - 26 + ord('a') - 1)
1426 return '%c%s\n' % (l, base85.b85encode(line, True))
1427 return '%c%s\n' % (l, base85.b85encode(line, True))
1427
1428
1428 def chunk(text, csize=52):
1429 def chunk(text, csize=52):
1429 l = len(text)
1430 l = len(text)
1430 i = 0
1431 i = 0
1431 while i < l:
1432 while i < l:
1432 yield text[i:i + csize]
1433 yield text[i:i + csize]
1433 i += csize
1434 i += csize
1434
1435
1435 tohash = gitindex(to)
1436 tohash = gitindex(to)
1436 tnhash = gitindex(tn)
1437 tnhash = gitindex(tn)
1437 if tohash == tnhash:
1438 if tohash == tnhash:
1438 return ""
1439 return ""
1439
1440
1440 # TODO: deltas
1441 # TODO: deltas
1441 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1442 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
1442 (tohash, tnhash, len(tn))]
1443 (tohash, tnhash, len(tn))]
1443 for l in chunk(zlib.compress(tn)):
1444 for l in chunk(zlib.compress(tn)):
1444 ret.append(fmtline(l))
1445 ret.append(fmtline(l))
1445 ret.append('\n')
1446 ret.append('\n')
1446 return ''.join(ret)
1447 return ''.join(ret)
1447
1448
1448 class GitDiffRequired(Exception):
1449 class GitDiffRequired(Exception):
1449 pass
1450 pass
1450
1451
1451 def diffopts(ui, opts=None, untrusted=False):
1452 def diffopts(ui, opts=None, untrusted=False):
1452 def get(key, name=None, getter=ui.configbool):
1453 def get(key, name=None, getter=ui.configbool):
1453 return ((opts and opts.get(key)) or
1454 return ((opts and opts.get(key)) or
1454 getter('diff', name or key, None, untrusted=untrusted))
1455 getter('diff', name or key, None, untrusted=untrusted))
1455 return mdiff.diffopts(
1456 return mdiff.diffopts(
1456 text=opts and opts.get('text'),
1457 text=opts and opts.get('text'),
1457 git=get('git'),
1458 git=get('git'),
1458 nodates=get('nodates'),
1459 nodates=get('nodates'),
1459 showfunc=get('show_function', 'showfunc'),
1460 showfunc=get('show_function', 'showfunc'),
1460 ignorews=get('ignore_all_space', 'ignorews'),
1461 ignorews=get('ignore_all_space', 'ignorews'),
1461 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1462 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
1462 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1463 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'),
1463 context=get('unified', getter=ui.config))
1464 context=get('unified', getter=ui.config))
1464
1465
1465 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1466 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1466 losedatafn=None, prefix=''):
1467 losedatafn=None, prefix=''):
1467 '''yields diff of changes to files between two nodes, or node and
1468 '''yields diff of changes to files between two nodes, or node and
1468 working directory.
1469 working directory.
1469
1470
1470 if node1 is None, use first dirstate parent instead.
1471 if node1 is None, use first dirstate parent instead.
1471 if node2 is None, compare node1 with working directory.
1472 if node2 is None, compare node1 with working directory.
1472
1473
1473 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1474 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1474 every time some change cannot be represented with the current
1475 every time some change cannot be represented with the current
1475 patch format. Return False to upgrade to git patch format, True to
1476 patch format. Return False to upgrade to git patch format, True to
1476 accept the loss or raise an exception to abort the diff. It is
1477 accept the loss or raise an exception to abort the diff. It is
1477 called with the name of current file being diffed as 'fn'. If set
1478 called with the name of current file being diffed as 'fn'. If set
1478 to None, patches will always be upgraded to git format when
1479 to None, patches will always be upgraded to git format when
1479 necessary.
1480 necessary.
1480
1481
1481 prefix is a filename prefix that is prepended to all filenames on
1482 prefix is a filename prefix that is prepended to all filenames on
1482 display (used for subrepos).
1483 display (used for subrepos).
1483 '''
1484 '''
1484
1485
1485 if opts is None:
1486 if opts is None:
1486 opts = mdiff.defaultopts
1487 opts = mdiff.defaultopts
1487
1488
1488 if not node1 and not node2:
1489 if not node1 and not node2:
1489 node1 = repo.dirstate.p1()
1490 node1 = repo.dirstate.p1()
1490
1491
1491 def lrugetfilectx():
1492 def lrugetfilectx():
1492 cache = {}
1493 cache = {}
1493 order = []
1494 order = []
1494 def getfilectx(f, ctx):
1495 def getfilectx(f, ctx):
1495 fctx = ctx.filectx(f, filelog=cache.get(f))
1496 fctx = ctx.filectx(f, filelog=cache.get(f))
1496 if f not in cache:
1497 if f not in cache:
1497 if len(cache) > 20:
1498 if len(cache) > 20:
1498 del cache[order.pop(0)]
1499 del cache[order.pop(0)]
1499 cache[f] = fctx.filelog()
1500 cache[f] = fctx.filelog()
1500 else:
1501 else:
1501 order.remove(f)
1502 order.remove(f)
1502 order.append(f)
1503 order.append(f)
1503 return fctx
1504 return fctx
1504 return getfilectx
1505 return getfilectx
1505 getfilectx = lrugetfilectx()
1506 getfilectx = lrugetfilectx()
1506
1507
1507 ctx1 = repo[node1]
1508 ctx1 = repo[node1]
1508 ctx2 = repo[node2]
1509 ctx2 = repo[node2]
1509
1510
1510 if not changes:
1511 if not changes:
1511 changes = repo.status(ctx1, ctx2, match=match)
1512 changes = repo.status(ctx1, ctx2, match=match)
1512 modified, added, removed = changes[:3]
1513 modified, added, removed = changes[:3]
1513
1514
1514 if not modified and not added and not removed:
1515 if not modified and not added and not removed:
1515 return []
1516 return []
1516
1517
1517 revs = None
1518 revs = None
1518 if not repo.ui.quiet:
1519 if not repo.ui.quiet:
1519 hexfunc = repo.ui.debugflag and hex or short
1520 hexfunc = repo.ui.debugflag and hex or short
1520 revs = [hexfunc(node) for node in [node1, node2] if node]
1521 revs = [hexfunc(node) for node in [node1, node2] if node]
1521
1522
1522 copy = {}
1523 copy = {}
1523 if opts.git or opts.upgrade:
1524 if opts.git or opts.upgrade:
1524 copy = copies.copies(repo, ctx1, ctx2, repo[nullid])[0]
1525 copy = copies.copies(repo, ctx1, ctx2, repo[nullid])[0]
1525
1526
1526 difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2,
1527 difffn = lambda opts, losedata: trydiff(repo, revs, ctx1, ctx2,
1527 modified, added, removed, copy, getfilectx, opts, losedata, prefix)
1528 modified, added, removed, copy, getfilectx, opts, losedata, prefix)
1528 if opts.upgrade and not opts.git:
1529 if opts.upgrade and not opts.git:
1529 try:
1530 try:
1530 def losedata(fn):
1531 def losedata(fn):
1531 if not losedatafn or not losedatafn(fn=fn):
1532 if not losedatafn or not losedatafn(fn=fn):
1532 raise GitDiffRequired()
1533 raise GitDiffRequired()
1533 # Buffer the whole output until we are sure it can be generated
1534 # Buffer the whole output until we are sure it can be generated
1534 return list(difffn(opts.copy(git=False), losedata))
1535 return list(difffn(opts.copy(git=False), losedata))
1535 except GitDiffRequired:
1536 except GitDiffRequired:
1536 return difffn(opts.copy(git=True), None)
1537 return difffn(opts.copy(git=True), None)
1537 else:
1538 else:
1538 return difffn(opts, None)
1539 return difffn(opts, None)
1539
1540
1540 def difflabel(func, *args, **kw):
1541 def difflabel(func, *args, **kw):
1541 '''yields 2-tuples of (output, label) based on the output of func()'''
1542 '''yields 2-tuples of (output, label) based on the output of func()'''
1542 prefixes = [('diff', 'diff.diffline'),
1543 prefixes = [('diff', 'diff.diffline'),
1543 ('copy', 'diff.extended'),
1544 ('copy', 'diff.extended'),
1544 ('rename', 'diff.extended'),
1545 ('rename', 'diff.extended'),
1545 ('old', 'diff.extended'),
1546 ('old', 'diff.extended'),
1546 ('new', 'diff.extended'),
1547 ('new', 'diff.extended'),
1547 ('deleted', 'diff.extended'),
1548 ('deleted', 'diff.extended'),
1548 ('---', 'diff.file_a'),
1549 ('---', 'diff.file_a'),
1549 ('+++', 'diff.file_b'),
1550 ('+++', 'diff.file_b'),
1550 ('@@', 'diff.hunk'),
1551 ('@@', 'diff.hunk'),
1551 ('-', 'diff.deleted'),
1552 ('-', 'diff.deleted'),
1552 ('+', 'diff.inserted')]
1553 ('+', 'diff.inserted')]
1553
1554
1554 for chunk in func(*args, **kw):
1555 for chunk in func(*args, **kw):
1555 lines = chunk.split('\n')
1556 lines = chunk.split('\n')
1556 for i, line in enumerate(lines):
1557 for i, line in enumerate(lines):
1557 if i != 0:
1558 if i != 0:
1558 yield ('\n', '')
1559 yield ('\n', '')
1559 stripline = line
1560 stripline = line
1560 if line and line[0] in '+-':
1561 if line and line[0] in '+-':
1561 # highlight trailing whitespace, but only in changed lines
1562 # highlight trailing whitespace, but only in changed lines
1562 stripline = line.rstrip()
1563 stripline = line.rstrip()
1563 for prefix, label in prefixes:
1564 for prefix, label in prefixes:
1564 if stripline.startswith(prefix):
1565 if stripline.startswith(prefix):
1565 yield (stripline, label)
1566 yield (stripline, label)
1566 break
1567 break
1567 else:
1568 else:
1568 yield (line, '')
1569 yield (line, '')
1569 if line != stripline:
1570 if line != stripline:
1570 yield (line[len(stripline):], 'diff.trailingwhitespace')
1571 yield (line[len(stripline):], 'diff.trailingwhitespace')
1571
1572
1572 def diffui(*args, **kw):
1573 def diffui(*args, **kw):
1573 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1574 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1574 return difflabel(diff, *args, **kw)
1575 return difflabel(diff, *args, **kw)
1575
1576
1576
1577
1577 def _addmodehdr(header, omode, nmode):
1578 def _addmodehdr(header, omode, nmode):
1578 if omode != nmode:
1579 if omode != nmode:
1579 header.append('old mode %s\n' % omode)
1580 header.append('old mode %s\n' % omode)
1580 header.append('new mode %s\n' % nmode)
1581 header.append('new mode %s\n' % nmode)
1581
1582
1582 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1583 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1583 copy, getfilectx, opts, losedatafn, prefix):
1584 copy, getfilectx, opts, losedatafn, prefix):
1584
1585
1585 def join(f):
1586 def join(f):
1586 return os.path.join(prefix, f)
1587 return os.path.join(prefix, f)
1587
1588
1588 date1 = util.datestr(ctx1.date())
1589 date1 = util.datestr(ctx1.date())
1589 man1 = ctx1.manifest()
1590 man1 = ctx1.manifest()
1590
1591
1591 gone = set()
1592 gone = set()
1592 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1593 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1593
1594
1594 copyto = dict([(v, k) for k, v in copy.items()])
1595 copyto = dict([(v, k) for k, v in copy.items()])
1595
1596
1596 if opts.git:
1597 if opts.git:
1597 revs = None
1598 revs = None
1598
1599
1599 for f in sorted(modified + added + removed):
1600 for f in sorted(modified + added + removed):
1600 to = None
1601 to = None
1601 tn = None
1602 tn = None
1602 dodiff = True
1603 dodiff = True
1603 header = []
1604 header = []
1604 if f in man1:
1605 if f in man1:
1605 to = getfilectx(f, ctx1).data()
1606 to = getfilectx(f, ctx1).data()
1606 if f not in removed:
1607 if f not in removed:
1607 tn = getfilectx(f, ctx2).data()
1608 tn = getfilectx(f, ctx2).data()
1608 a, b = f, f
1609 a, b = f, f
1609 if opts.git or losedatafn:
1610 if opts.git or losedatafn:
1610 if f in added:
1611 if f in added:
1611 mode = gitmode[ctx2.flags(f)]
1612 mode = gitmode[ctx2.flags(f)]
1612 if f in copy or f in copyto:
1613 if f in copy or f in copyto:
1613 if opts.git:
1614 if opts.git:
1614 if f in copy:
1615 if f in copy:
1615 a = copy[f]
1616 a = copy[f]
1616 else:
1617 else:
1617 a = copyto[f]
1618 a = copyto[f]
1618 omode = gitmode[man1.flags(a)]
1619 omode = gitmode[man1.flags(a)]
1619 _addmodehdr(header, omode, mode)
1620 _addmodehdr(header, omode, mode)
1620 if a in removed and a not in gone:
1621 if a in removed and a not in gone:
1621 op = 'rename'
1622 op = 'rename'
1622 gone.add(a)
1623 gone.add(a)
1623 else:
1624 else:
1624 op = 'copy'
1625 op = 'copy'
1625 header.append('%s from %s\n' % (op, join(a)))
1626 header.append('%s from %s\n' % (op, join(a)))
1626 header.append('%s to %s\n' % (op, join(f)))
1627 header.append('%s to %s\n' % (op, join(f)))
1627 to = getfilectx(a, ctx1).data()
1628 to = getfilectx(a, ctx1).data()
1628 else:
1629 else:
1629 losedatafn(f)
1630 losedatafn(f)
1630 else:
1631 else:
1631 if opts.git:
1632 if opts.git:
1632 header.append('new file mode %s\n' % mode)
1633 header.append('new file mode %s\n' % mode)
1633 elif ctx2.flags(f):
1634 elif ctx2.flags(f):
1634 losedatafn(f)
1635 losedatafn(f)
1635 # In theory, if tn was copied or renamed we should check
1636 # In theory, if tn was copied or renamed we should check
1636 # if the source is binary too but the copy record already
1637 # if the source is binary too but the copy record already
1637 # forces git mode.
1638 # forces git mode.
1638 if util.binary(tn):
1639 if util.binary(tn):
1639 if opts.git:
1640 if opts.git:
1640 dodiff = 'binary'
1641 dodiff = 'binary'
1641 else:
1642 else:
1642 losedatafn(f)
1643 losedatafn(f)
1643 if not opts.git and not tn:
1644 if not opts.git and not tn:
1644 # regular diffs cannot represent new empty file
1645 # regular diffs cannot represent new empty file
1645 losedatafn(f)
1646 losedatafn(f)
1646 elif f in removed:
1647 elif f in removed:
1647 if opts.git:
1648 if opts.git:
1648 # have we already reported a copy above?
1649 # have we already reported a copy above?
1649 if ((f in copy and copy[f] in added
1650 if ((f in copy and copy[f] in added
1650 and copyto[copy[f]] == f) or
1651 and copyto[copy[f]] == f) or
1651 (f in copyto and copyto[f] in added
1652 (f in copyto and copyto[f] in added
1652 and copy[copyto[f]] == f)):
1653 and copy[copyto[f]] == f)):
1653 dodiff = False
1654 dodiff = False
1654 else:
1655 else:
1655 header.append('deleted file mode %s\n' %
1656 header.append('deleted file mode %s\n' %
1656 gitmode[man1.flags(f)])
1657 gitmode[man1.flags(f)])
1657 elif not to or util.binary(to):
1658 elif not to or util.binary(to):
1658 # regular diffs cannot represent empty file deletion
1659 # regular diffs cannot represent empty file deletion
1659 losedatafn(f)
1660 losedatafn(f)
1660 else:
1661 else:
1661 oflag = man1.flags(f)
1662 oflag = man1.flags(f)
1662 nflag = ctx2.flags(f)
1663 nflag = ctx2.flags(f)
1663 binary = util.binary(to) or util.binary(tn)
1664 binary = util.binary(to) or util.binary(tn)
1664 if opts.git:
1665 if opts.git:
1665 _addmodehdr(header, gitmode[oflag], gitmode[nflag])
1666 _addmodehdr(header, gitmode[oflag], gitmode[nflag])
1666 if binary:
1667 if binary:
1667 dodiff = 'binary'
1668 dodiff = 'binary'
1668 elif binary or nflag != oflag:
1669 elif binary or nflag != oflag:
1669 losedatafn(f)
1670 losedatafn(f)
1670 if opts.git:
1671 if opts.git:
1671 header.insert(0, mdiff.diffline(revs, join(a), join(b), opts))
1672 header.insert(0, mdiff.diffline(revs, join(a), join(b), opts))
1672
1673
1673 if dodiff:
1674 if dodiff:
1674 if dodiff == 'binary':
1675 if dodiff == 'binary':
1675 text = b85diff(to, tn)
1676 text = b85diff(to, tn)
1676 else:
1677 else:
1677 text = mdiff.unidiff(to, date1,
1678 text = mdiff.unidiff(to, date1,
1678 # ctx2 date may be dynamic
1679 # ctx2 date may be dynamic
1679 tn, util.datestr(ctx2.date()),
1680 tn, util.datestr(ctx2.date()),
1680 join(a), join(b), revs, opts=opts)
1681 join(a), join(b), revs, opts=opts)
1681 if header and (text or len(header) > 1):
1682 if header and (text or len(header) > 1):
1682 yield ''.join(header)
1683 yield ''.join(header)
1683 if text:
1684 if text:
1684 yield text
1685 yield text
1685
1686
1686 def diffstatdata(lines):
1687 def diffstatdata(lines):
1687 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1688 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1688
1689
1689 filename, adds, removes = None, 0, 0
1690 filename, adds, removes = None, 0, 0
1690 for line in lines:
1691 for line in lines:
1691 if line.startswith('diff'):
1692 if line.startswith('diff'):
1692 if filename:
1693 if filename:
1693 isbinary = adds == 0 and removes == 0
1694 isbinary = adds == 0 and removes == 0
1694 yield (filename, adds, removes, isbinary)
1695 yield (filename, adds, removes, isbinary)
1695 # set numbers to 0 anyway when starting new file
1696 # set numbers to 0 anyway when starting new file
1696 adds, removes = 0, 0
1697 adds, removes = 0, 0
1697 if line.startswith('diff --git'):
1698 if line.startswith('diff --git'):
1698 filename = gitre.search(line).group(1)
1699 filename = gitre.search(line).group(1)
1699 elif line.startswith('diff -r'):
1700 elif line.startswith('diff -r'):
1700 # format: "diff -r ... -r ... filename"
1701 # format: "diff -r ... -r ... filename"
1701 filename = diffre.search(line).group(1)
1702 filename = diffre.search(line).group(1)
1702 elif line.startswith('+') and not line.startswith('+++'):
1703 elif line.startswith('+') and not line.startswith('+++'):
1703 adds += 1
1704 adds += 1
1704 elif line.startswith('-') and not line.startswith('---'):
1705 elif line.startswith('-') and not line.startswith('---'):
1705 removes += 1
1706 removes += 1
1706 if filename:
1707 if filename:
1707 isbinary = adds == 0 and removes == 0
1708 isbinary = adds == 0 and removes == 0
1708 yield (filename, adds, removes, isbinary)
1709 yield (filename, adds, removes, isbinary)
1709
1710
1710 def diffstat(lines, width=80, git=False):
1711 def diffstat(lines, width=80, git=False):
1711 output = []
1712 output = []
1712 stats = list(diffstatdata(lines))
1713 stats = list(diffstatdata(lines))
1713
1714
1714 maxtotal, maxname = 0, 0
1715 maxtotal, maxname = 0, 0
1715 totaladds, totalremoves = 0, 0
1716 totaladds, totalremoves = 0, 0
1716 hasbinary = False
1717 hasbinary = False
1717
1718
1718 sized = [(filename, adds, removes, isbinary, encoding.colwidth(filename))
1719 sized = [(filename, adds, removes, isbinary, encoding.colwidth(filename))
1719 for filename, adds, removes, isbinary in stats]
1720 for filename, adds, removes, isbinary in stats]
1720
1721
1721 for filename, adds, removes, isbinary, namewidth in sized:
1722 for filename, adds, removes, isbinary, namewidth in sized:
1722 totaladds += adds
1723 totaladds += adds
1723 totalremoves += removes
1724 totalremoves += removes
1724 maxname = max(maxname, namewidth)
1725 maxname = max(maxname, namewidth)
1725 maxtotal = max(maxtotal, adds + removes)
1726 maxtotal = max(maxtotal, adds + removes)
1726 if isbinary:
1727 if isbinary:
1727 hasbinary = True
1728 hasbinary = True
1728
1729
1729 countwidth = len(str(maxtotal))
1730 countwidth = len(str(maxtotal))
1730 if hasbinary and countwidth < 3:
1731 if hasbinary and countwidth < 3:
1731 countwidth = 3
1732 countwidth = 3
1732 graphwidth = width - countwidth - maxname - 6
1733 graphwidth = width - countwidth - maxname - 6
1733 if graphwidth < 10:
1734 if graphwidth < 10:
1734 graphwidth = 10
1735 graphwidth = 10
1735
1736
1736 def scale(i):
1737 def scale(i):
1737 if maxtotal <= graphwidth:
1738 if maxtotal <= graphwidth:
1738 return i
1739 return i
1739 # If diffstat runs out of room it doesn't print anything,
1740 # If diffstat runs out of room it doesn't print anything,
1740 # which isn't very useful, so always print at least one + or -
1741 # which isn't very useful, so always print at least one + or -
1741 # if there were at least some changes.
1742 # if there were at least some changes.
1742 return max(i * graphwidth // maxtotal, int(bool(i)))
1743 return max(i * graphwidth // maxtotal, int(bool(i)))
1743
1744
1744 for filename, adds, removes, isbinary, namewidth in sized:
1745 for filename, adds, removes, isbinary, namewidth in sized:
1745 if git and isbinary:
1746 if git and isbinary:
1746 count = 'Bin'
1747 count = 'Bin'
1747 else:
1748 else:
1748 count = adds + removes
1749 count = adds + removes
1749 pluses = '+' * scale(adds)
1750 pluses = '+' * scale(adds)
1750 minuses = '-' * scale(removes)
1751 minuses = '-' * scale(removes)
1751 output.append(' %s%s | %*s %s%s\n' %
1752 output.append(' %s%s | %*s %s%s\n' %
1752 (filename, ' ' * (maxname - namewidth),
1753 (filename, ' ' * (maxname - namewidth),
1753 countwidth, count,
1754 countwidth, count,
1754 pluses, minuses))
1755 pluses, minuses))
1755
1756
1756 if stats:
1757 if stats:
1757 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1758 output.append(_(' %d files changed, %d insertions(+), %d deletions(-)\n')
1758 % (len(stats), totaladds, totalremoves))
1759 % (len(stats), totaladds, totalremoves))
1759
1760
1760 return ''.join(output)
1761 return ''.join(output)
1761
1762
1762 def diffstatui(*args, **kw):
1763 def diffstatui(*args, **kw):
1763 '''like diffstat(), but yields 2-tuples of (output, label) for
1764 '''like diffstat(), but yields 2-tuples of (output, label) for
1764 ui.write()
1765 ui.write()
1765 '''
1766 '''
1766
1767
1767 for line in diffstat(*args, **kw).splitlines():
1768 for line in diffstat(*args, **kw).splitlines():
1768 if line and line[-1] in '+-':
1769 if line and line[-1] in '+-':
1769 name, graph = line.rsplit(' ', 1)
1770 name, graph = line.rsplit(' ', 1)
1770 yield (name + ' ', '')
1771 yield (name + ' ', '')
1771 m = re.search(r'\++', graph)
1772 m = re.search(r'\++', graph)
1772 if m:
1773 if m:
1773 yield (m.group(0), 'diffstat.inserted')
1774 yield (m.group(0), 'diffstat.inserted')
1774 m = re.search(r'-+', graph)
1775 m = re.search(r'-+', graph)
1775 if m:
1776 if m:
1776 yield (m.group(0), 'diffstat.deleted')
1777 yield (m.group(0), 'diffstat.deleted')
1777 else:
1778 else:
1778 yield (line, '')
1779 yield (line, '')
1779 yield ('\n', '')
1780 yield ('\n', '')
General Comments 0
You need to be logged in to leave comments. Login now