##// END OF EJS Templates
formatter: replace contexthint() with demand loading of ctx object...
Yuya Nishihara -
r39660:713085b4 default
parent child Browse files
Show More
@@ -1,968 +1,966
1 # Mercurial bookmark support code
1 # Mercurial bookmark support code
2 #
2 #
3 # Copyright 2008 David Soria Parra <dsp@php.net>
3 # Copyright 2008 David Soria Parra <dsp@php.net>
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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import struct
11 import struct
12
12
13 from .i18n import _
13 from .i18n import _
14 from .node import (
14 from .node import (
15 bin,
15 bin,
16 hex,
16 hex,
17 short,
17 short,
18 wdirid,
18 wdirid,
19 )
19 )
20 from . import (
20 from . import (
21 encoding,
21 encoding,
22 error,
22 error,
23 obsutil,
23 obsutil,
24 pycompat,
24 pycompat,
25 scmutil,
25 scmutil,
26 txnutil,
26 txnutil,
27 util,
27 util,
28 )
28 )
29
29
30 # label constants
30 # label constants
31 # until 3.5, bookmarks.current was the advertised name, not
31 # until 3.5, bookmarks.current was the advertised name, not
32 # bookmarks.active, so we must use both to avoid breaking old
32 # bookmarks.active, so we must use both to avoid breaking old
33 # custom styles
33 # custom styles
34 activebookmarklabel = 'bookmarks.active bookmarks.current'
34 activebookmarklabel = 'bookmarks.active bookmarks.current'
35
35
36 def _getbkfile(repo):
36 def _getbkfile(repo):
37 """Hook so that extensions that mess with the store can hook bm storage.
37 """Hook so that extensions that mess with the store can hook bm storage.
38
38
39 For core, this just handles wether we should see pending
39 For core, this just handles wether we should see pending
40 bookmarks or the committed ones. Other extensions (like share)
40 bookmarks or the committed ones. Other extensions (like share)
41 may need to tweak this behavior further.
41 may need to tweak this behavior further.
42 """
42 """
43 fp, pending = txnutil.trypending(repo.root, repo.vfs, 'bookmarks')
43 fp, pending = txnutil.trypending(repo.root, repo.vfs, 'bookmarks')
44 return fp
44 return fp
45
45
46 class bmstore(object):
46 class bmstore(object):
47 """Storage for bookmarks.
47 """Storage for bookmarks.
48
48
49 This object should do all bookmark-related reads and writes, so
49 This object should do all bookmark-related reads and writes, so
50 that it's fairly simple to replace the storage underlying
50 that it's fairly simple to replace the storage underlying
51 bookmarks without having to clone the logic surrounding
51 bookmarks without having to clone the logic surrounding
52 bookmarks. This type also should manage the active bookmark, if
52 bookmarks. This type also should manage the active bookmark, if
53 any.
53 any.
54
54
55 This particular bmstore implementation stores bookmarks as
55 This particular bmstore implementation stores bookmarks as
56 {hash}\s{name}\n (the same format as localtags) in
56 {hash}\s{name}\n (the same format as localtags) in
57 .hg/bookmarks. The mapping is stored as {name: nodeid}.
57 .hg/bookmarks. The mapping is stored as {name: nodeid}.
58 """
58 """
59
59
60 def __init__(self, repo):
60 def __init__(self, repo):
61 self._repo = repo
61 self._repo = repo
62 self._refmap = refmap = {} # refspec: node
62 self._refmap = refmap = {} # refspec: node
63 self._nodemap = nodemap = {} # node: sorted([refspec, ...])
63 self._nodemap = nodemap = {} # node: sorted([refspec, ...])
64 self._clean = True
64 self._clean = True
65 self._aclean = True
65 self._aclean = True
66 nm = repo.changelog.nodemap
66 nm = repo.changelog.nodemap
67 tonode = bin # force local lookup
67 tonode = bin # force local lookup
68 try:
68 try:
69 with _getbkfile(repo) as bkfile:
69 with _getbkfile(repo) as bkfile:
70 for line in bkfile:
70 for line in bkfile:
71 line = line.strip()
71 line = line.strip()
72 if not line:
72 if not line:
73 continue
73 continue
74 try:
74 try:
75 sha, refspec = line.split(' ', 1)
75 sha, refspec = line.split(' ', 1)
76 node = tonode(sha)
76 node = tonode(sha)
77 if node in nm:
77 if node in nm:
78 refspec = encoding.tolocal(refspec)
78 refspec = encoding.tolocal(refspec)
79 refmap[refspec] = node
79 refmap[refspec] = node
80 nrefs = nodemap.get(node)
80 nrefs = nodemap.get(node)
81 if nrefs is None:
81 if nrefs is None:
82 nodemap[node] = [refspec]
82 nodemap[node] = [refspec]
83 else:
83 else:
84 nrefs.append(refspec)
84 nrefs.append(refspec)
85 if nrefs[-2] > refspec:
85 if nrefs[-2] > refspec:
86 # bookmarks weren't sorted before 4.5
86 # bookmarks weren't sorted before 4.5
87 nrefs.sort()
87 nrefs.sort()
88 except (TypeError, ValueError):
88 except (TypeError, ValueError):
89 # TypeError:
89 # TypeError:
90 # - bin(...)
90 # - bin(...)
91 # ValueError:
91 # ValueError:
92 # - node in nm, for non-20-bytes entry
92 # - node in nm, for non-20-bytes entry
93 # - split(...), for string without ' '
93 # - split(...), for string without ' '
94 repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n')
94 repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n')
95 % pycompat.bytestr(line))
95 % pycompat.bytestr(line))
96 except IOError as inst:
96 except IOError as inst:
97 if inst.errno != errno.ENOENT:
97 if inst.errno != errno.ENOENT:
98 raise
98 raise
99 self._active = _readactive(repo, self)
99 self._active = _readactive(repo, self)
100
100
101 @property
101 @property
102 def active(self):
102 def active(self):
103 return self._active
103 return self._active
104
104
105 @active.setter
105 @active.setter
106 def active(self, mark):
106 def active(self, mark):
107 if mark is not None and mark not in self._refmap:
107 if mark is not None and mark not in self._refmap:
108 raise AssertionError('bookmark %s does not exist!' % mark)
108 raise AssertionError('bookmark %s does not exist!' % mark)
109
109
110 self._active = mark
110 self._active = mark
111 self._aclean = False
111 self._aclean = False
112
112
113 def __len__(self):
113 def __len__(self):
114 return len(self._refmap)
114 return len(self._refmap)
115
115
116 def __iter__(self):
116 def __iter__(self):
117 return iter(self._refmap)
117 return iter(self._refmap)
118
118
119 def iteritems(self):
119 def iteritems(self):
120 return self._refmap.iteritems()
120 return self._refmap.iteritems()
121
121
122 def items(self):
122 def items(self):
123 return self._refmap.items()
123 return self._refmap.items()
124
124
125 # TODO: maybe rename to allnames()?
125 # TODO: maybe rename to allnames()?
126 def keys(self):
126 def keys(self):
127 return self._refmap.keys()
127 return self._refmap.keys()
128
128
129 # TODO: maybe rename to allnodes()? but nodes would have to be deduplicated
129 # TODO: maybe rename to allnodes()? but nodes would have to be deduplicated
130 # could be self._nodemap.keys()
130 # could be self._nodemap.keys()
131 def values(self):
131 def values(self):
132 return self._refmap.values()
132 return self._refmap.values()
133
133
134 def __contains__(self, mark):
134 def __contains__(self, mark):
135 return mark in self._refmap
135 return mark in self._refmap
136
136
137 def __getitem__(self, mark):
137 def __getitem__(self, mark):
138 return self._refmap[mark]
138 return self._refmap[mark]
139
139
140 def get(self, mark, default=None):
140 def get(self, mark, default=None):
141 return self._refmap.get(mark, default)
141 return self._refmap.get(mark, default)
142
142
143 def _set(self, mark, node):
143 def _set(self, mark, node):
144 self._clean = False
144 self._clean = False
145 if mark in self._refmap:
145 if mark in self._refmap:
146 self._del(mark)
146 self._del(mark)
147 self._refmap[mark] = node
147 self._refmap[mark] = node
148 nrefs = self._nodemap.get(node)
148 nrefs = self._nodemap.get(node)
149 if nrefs is None:
149 if nrefs is None:
150 self._nodemap[node] = [mark]
150 self._nodemap[node] = [mark]
151 else:
151 else:
152 nrefs.append(mark)
152 nrefs.append(mark)
153 nrefs.sort()
153 nrefs.sort()
154
154
155 def _del(self, mark):
155 def _del(self, mark):
156 self._clean = False
156 self._clean = False
157 node = self._refmap.pop(mark)
157 node = self._refmap.pop(mark)
158 nrefs = self._nodemap[node]
158 nrefs = self._nodemap[node]
159 if len(nrefs) == 1:
159 if len(nrefs) == 1:
160 assert nrefs[0] == mark
160 assert nrefs[0] == mark
161 del self._nodemap[node]
161 del self._nodemap[node]
162 else:
162 else:
163 nrefs.remove(mark)
163 nrefs.remove(mark)
164
164
165 def names(self, node):
165 def names(self, node):
166 """Return a sorted list of bookmarks pointing to the specified node"""
166 """Return a sorted list of bookmarks pointing to the specified node"""
167 return self._nodemap.get(node, [])
167 return self._nodemap.get(node, [])
168
168
169 def changectx(self, mark):
169 def changectx(self, mark):
170 node = self._refmap[mark]
170 node = self._refmap[mark]
171 return self._repo[node]
171 return self._repo[node]
172
172
173 def applychanges(self, repo, tr, changes):
173 def applychanges(self, repo, tr, changes):
174 """Apply a list of changes to bookmarks
174 """Apply a list of changes to bookmarks
175 """
175 """
176 bmchanges = tr.changes.get('bookmarks')
176 bmchanges = tr.changes.get('bookmarks')
177 for name, node in changes:
177 for name, node in changes:
178 old = self._refmap.get(name)
178 old = self._refmap.get(name)
179 if node is None:
179 if node is None:
180 self._del(name)
180 self._del(name)
181 else:
181 else:
182 self._set(name, node)
182 self._set(name, node)
183 if bmchanges is not None:
183 if bmchanges is not None:
184 # if a previous value exist preserve the "initial" value
184 # if a previous value exist preserve the "initial" value
185 previous = bmchanges.get(name)
185 previous = bmchanges.get(name)
186 if previous is not None:
186 if previous is not None:
187 old = previous[0]
187 old = previous[0]
188 bmchanges[name] = (old, node)
188 bmchanges[name] = (old, node)
189 self._recordchange(tr)
189 self._recordchange(tr)
190
190
191 def _recordchange(self, tr):
191 def _recordchange(self, tr):
192 """record that bookmarks have been changed in a transaction
192 """record that bookmarks have been changed in a transaction
193
193
194 The transaction is then responsible for updating the file content."""
194 The transaction is then responsible for updating the file content."""
195 tr.addfilegenerator('bookmarks', ('bookmarks',), self._write,
195 tr.addfilegenerator('bookmarks', ('bookmarks',), self._write,
196 location='plain')
196 location='plain')
197 tr.hookargs['bookmark_moved'] = '1'
197 tr.hookargs['bookmark_moved'] = '1'
198
198
199 def _writerepo(self, repo):
199 def _writerepo(self, repo):
200 """Factored out for extensibility"""
200 """Factored out for extensibility"""
201 rbm = repo._bookmarks
201 rbm = repo._bookmarks
202 if rbm.active not in self._refmap:
202 if rbm.active not in self._refmap:
203 rbm.active = None
203 rbm.active = None
204 rbm._writeactive()
204 rbm._writeactive()
205
205
206 with repo.wlock():
206 with repo.wlock():
207 file_ = repo.vfs('bookmarks', 'w', atomictemp=True,
207 file_ = repo.vfs('bookmarks', 'w', atomictemp=True,
208 checkambig=True)
208 checkambig=True)
209 try:
209 try:
210 self._write(file_)
210 self._write(file_)
211 except: # re-raises
211 except: # re-raises
212 file_.discard()
212 file_.discard()
213 raise
213 raise
214 finally:
214 finally:
215 file_.close()
215 file_.close()
216
216
217 def _writeactive(self):
217 def _writeactive(self):
218 if self._aclean:
218 if self._aclean:
219 return
219 return
220 with self._repo.wlock():
220 with self._repo.wlock():
221 if self._active is not None:
221 if self._active is not None:
222 f = self._repo.vfs('bookmarks.current', 'w', atomictemp=True,
222 f = self._repo.vfs('bookmarks.current', 'w', atomictemp=True,
223 checkambig=True)
223 checkambig=True)
224 try:
224 try:
225 f.write(encoding.fromlocal(self._active))
225 f.write(encoding.fromlocal(self._active))
226 finally:
226 finally:
227 f.close()
227 f.close()
228 else:
228 else:
229 self._repo.vfs.tryunlink('bookmarks.current')
229 self._repo.vfs.tryunlink('bookmarks.current')
230 self._aclean = True
230 self._aclean = True
231
231
232 def _write(self, fp):
232 def _write(self, fp):
233 for name, node in sorted(self._refmap.iteritems()):
233 for name, node in sorted(self._refmap.iteritems()):
234 fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name)))
234 fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name)))
235 self._clean = True
235 self._clean = True
236 self._repo.invalidatevolatilesets()
236 self._repo.invalidatevolatilesets()
237
237
238 def expandname(self, bname):
238 def expandname(self, bname):
239 if bname == '.':
239 if bname == '.':
240 if self.active:
240 if self.active:
241 return self.active
241 return self.active
242 else:
242 else:
243 raise error.RepoLookupError(_("no active bookmark"))
243 raise error.RepoLookupError(_("no active bookmark"))
244 return bname
244 return bname
245
245
246 def checkconflict(self, mark, force=False, target=None):
246 def checkconflict(self, mark, force=False, target=None):
247 """check repo for a potential clash of mark with an existing bookmark,
247 """check repo for a potential clash of mark with an existing bookmark,
248 branch, or hash
248 branch, or hash
249
249
250 If target is supplied, then check that we are moving the bookmark
250 If target is supplied, then check that we are moving the bookmark
251 forward.
251 forward.
252
252
253 If force is supplied, then forcibly move the bookmark to a new commit
253 If force is supplied, then forcibly move the bookmark to a new commit
254 regardless if it is a move forward.
254 regardless if it is a move forward.
255
255
256 If divergent bookmark are to be deleted, they will be returned as list.
256 If divergent bookmark are to be deleted, they will be returned as list.
257 """
257 """
258 cur = self._repo['.'].node()
258 cur = self._repo['.'].node()
259 if mark in self._refmap and not force:
259 if mark in self._refmap and not force:
260 if target:
260 if target:
261 if self._refmap[mark] == target and target == cur:
261 if self._refmap[mark] == target and target == cur:
262 # re-activating a bookmark
262 # re-activating a bookmark
263 return []
263 return []
264 rev = self._repo[target].rev()
264 rev = self._repo[target].rev()
265 anc = self._repo.changelog.ancestors([rev])
265 anc = self._repo.changelog.ancestors([rev])
266 bmctx = self.changectx(mark)
266 bmctx = self.changectx(mark)
267 divs = [self._refmap[b] for b in self._refmap
267 divs = [self._refmap[b] for b in self._refmap
268 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
268 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
269
269
270 # allow resolving a single divergent bookmark even if moving
270 # allow resolving a single divergent bookmark even if moving
271 # the bookmark across branches when a revision is specified
271 # the bookmark across branches when a revision is specified
272 # that contains a divergent bookmark
272 # that contains a divergent bookmark
273 if bmctx.rev() not in anc and target in divs:
273 if bmctx.rev() not in anc and target in divs:
274 return divergent2delete(self._repo, [target], mark)
274 return divergent2delete(self._repo, [target], mark)
275
275
276 deletefrom = [b for b in divs
276 deletefrom = [b for b in divs
277 if self._repo[b].rev() in anc or b == target]
277 if self._repo[b].rev() in anc or b == target]
278 delbms = divergent2delete(self._repo, deletefrom, mark)
278 delbms = divergent2delete(self._repo, deletefrom, mark)
279 if validdest(self._repo, bmctx, self._repo[target]):
279 if validdest(self._repo, bmctx, self._repo[target]):
280 self._repo.ui.status(
280 self._repo.ui.status(
281 _("moving bookmark '%s' forward from %s\n") %
281 _("moving bookmark '%s' forward from %s\n") %
282 (mark, short(bmctx.node())))
282 (mark, short(bmctx.node())))
283 return delbms
283 return delbms
284 raise error.Abort(_("bookmark '%s' already exists "
284 raise error.Abort(_("bookmark '%s' already exists "
285 "(use -f to force)") % mark)
285 "(use -f to force)") % mark)
286 if ((mark in self._repo.branchmap() or
286 if ((mark in self._repo.branchmap() or
287 mark == self._repo.dirstate.branch()) and not force):
287 mark == self._repo.dirstate.branch()) and not force):
288 raise error.Abort(
288 raise error.Abort(
289 _("a bookmark cannot have the name of an existing branch"))
289 _("a bookmark cannot have the name of an existing branch"))
290 if len(mark) > 3 and not force:
290 if len(mark) > 3 and not force:
291 try:
291 try:
292 shadowhash = scmutil.isrevsymbol(self._repo, mark)
292 shadowhash = scmutil.isrevsymbol(self._repo, mark)
293 except error.LookupError: # ambiguous identifier
293 except error.LookupError: # ambiguous identifier
294 shadowhash = False
294 shadowhash = False
295 if shadowhash:
295 if shadowhash:
296 self._repo.ui.warn(
296 self._repo.ui.warn(
297 _("bookmark %s matches a changeset hash\n"
297 _("bookmark %s matches a changeset hash\n"
298 "(did you leave a -r out of an 'hg bookmark' "
298 "(did you leave a -r out of an 'hg bookmark' "
299 "command?)\n")
299 "command?)\n")
300 % mark)
300 % mark)
301 return []
301 return []
302
302
303 def _readactive(repo, marks):
303 def _readactive(repo, marks):
304 """
304 """
305 Get the active bookmark. We can have an active bookmark that updates
305 Get the active bookmark. We can have an active bookmark that updates
306 itself as we commit. This function returns the name of that bookmark.
306 itself as we commit. This function returns the name of that bookmark.
307 It is stored in .hg/bookmarks.current
307 It is stored in .hg/bookmarks.current
308 """
308 """
309 mark = None
309 mark = None
310 try:
310 try:
311 file = repo.vfs('bookmarks.current')
311 file = repo.vfs('bookmarks.current')
312 except IOError as inst:
312 except IOError as inst:
313 if inst.errno != errno.ENOENT:
313 if inst.errno != errno.ENOENT:
314 raise
314 raise
315 return None
315 return None
316 try:
316 try:
317 # No readline() in osutil.posixfile, reading everything is
317 # No readline() in osutil.posixfile, reading everything is
318 # cheap.
318 # cheap.
319 # Note that it's possible for readlines() here to raise
319 # Note that it's possible for readlines() here to raise
320 # IOError, since we might be reading the active mark over
320 # IOError, since we might be reading the active mark over
321 # static-http which only tries to load the file when we try
321 # static-http which only tries to load the file when we try
322 # to read from it.
322 # to read from it.
323 mark = encoding.tolocal((file.readlines() or [''])[0])
323 mark = encoding.tolocal((file.readlines() or [''])[0])
324 if mark == '' or mark not in marks:
324 if mark == '' or mark not in marks:
325 mark = None
325 mark = None
326 except IOError as inst:
326 except IOError as inst:
327 if inst.errno != errno.ENOENT:
327 if inst.errno != errno.ENOENT:
328 raise
328 raise
329 return None
329 return None
330 finally:
330 finally:
331 file.close()
331 file.close()
332 return mark
332 return mark
333
333
334 def activate(repo, mark):
334 def activate(repo, mark):
335 """
335 """
336 Set the given bookmark to be 'active', meaning that this bookmark will
336 Set the given bookmark to be 'active', meaning that this bookmark will
337 follow new commits that are made.
337 follow new commits that are made.
338 The name is recorded in .hg/bookmarks.current
338 The name is recorded in .hg/bookmarks.current
339 """
339 """
340 repo._bookmarks.active = mark
340 repo._bookmarks.active = mark
341 repo._bookmarks._writeactive()
341 repo._bookmarks._writeactive()
342
342
343 def deactivate(repo):
343 def deactivate(repo):
344 """
344 """
345 Unset the active bookmark in this repository.
345 Unset the active bookmark in this repository.
346 """
346 """
347 repo._bookmarks.active = None
347 repo._bookmarks.active = None
348 repo._bookmarks._writeactive()
348 repo._bookmarks._writeactive()
349
349
350 def isactivewdirparent(repo):
350 def isactivewdirparent(repo):
351 """
351 """
352 Tell whether the 'active' bookmark (the one that follows new commits)
352 Tell whether the 'active' bookmark (the one that follows new commits)
353 points to one of the parents of the current working directory (wdir).
353 points to one of the parents of the current working directory (wdir).
354
354
355 While this is normally the case, it can on occasion be false; for example,
355 While this is normally the case, it can on occasion be false; for example,
356 immediately after a pull, the active bookmark can be moved to point
356 immediately after a pull, the active bookmark can be moved to point
357 to a place different than the wdir. This is solved by running `hg update`.
357 to a place different than the wdir. This is solved by running `hg update`.
358 """
358 """
359 mark = repo._activebookmark
359 mark = repo._activebookmark
360 marks = repo._bookmarks
360 marks = repo._bookmarks
361 parents = [p.node() for p in repo[None].parents()]
361 parents = [p.node() for p in repo[None].parents()]
362 return (mark in marks and marks[mark] in parents)
362 return (mark in marks and marks[mark] in parents)
363
363
364 def divergent2delete(repo, deletefrom, bm):
364 def divergent2delete(repo, deletefrom, bm):
365 """find divergent versions of bm on nodes in deletefrom.
365 """find divergent versions of bm on nodes in deletefrom.
366
366
367 the list of bookmark to delete."""
367 the list of bookmark to delete."""
368 todelete = []
368 todelete = []
369 marks = repo._bookmarks
369 marks = repo._bookmarks
370 divergent = [b for b in marks if b.split('@', 1)[0] == bm.split('@', 1)[0]]
370 divergent = [b for b in marks if b.split('@', 1)[0] == bm.split('@', 1)[0]]
371 for mark in divergent:
371 for mark in divergent:
372 if mark == '@' or '@' not in mark:
372 if mark == '@' or '@' not in mark:
373 # can't be divergent by definition
373 # can't be divergent by definition
374 continue
374 continue
375 if mark and marks[mark] in deletefrom:
375 if mark and marks[mark] in deletefrom:
376 if mark != bm:
376 if mark != bm:
377 todelete.append(mark)
377 todelete.append(mark)
378 return todelete
378 return todelete
379
379
380 def headsforactive(repo):
380 def headsforactive(repo):
381 """Given a repo with an active bookmark, return divergent bookmark nodes.
381 """Given a repo with an active bookmark, return divergent bookmark nodes.
382
382
383 Args:
383 Args:
384 repo: A repository with an active bookmark.
384 repo: A repository with an active bookmark.
385
385
386 Returns:
386 Returns:
387 A list of binary node ids that is the full list of other
387 A list of binary node ids that is the full list of other
388 revisions with bookmarks divergent from the active bookmark. If
388 revisions with bookmarks divergent from the active bookmark. If
389 there were no divergent bookmarks, then this list will contain
389 there were no divergent bookmarks, then this list will contain
390 only one entry.
390 only one entry.
391 """
391 """
392 if not repo._activebookmark:
392 if not repo._activebookmark:
393 raise ValueError(
393 raise ValueError(
394 'headsforactive() only makes sense with an active bookmark')
394 'headsforactive() only makes sense with an active bookmark')
395 name = repo._activebookmark.split('@', 1)[0]
395 name = repo._activebookmark.split('@', 1)[0]
396 heads = []
396 heads = []
397 for mark, n in repo._bookmarks.iteritems():
397 for mark, n in repo._bookmarks.iteritems():
398 if mark.split('@', 1)[0] == name:
398 if mark.split('@', 1)[0] == name:
399 heads.append(n)
399 heads.append(n)
400 return heads
400 return heads
401
401
402 def calculateupdate(ui, repo):
402 def calculateupdate(ui, repo):
403 '''Return a tuple (activemark, movemarkfrom) indicating the active bookmark
403 '''Return a tuple (activemark, movemarkfrom) indicating the active bookmark
404 and where to move the active bookmark from, if needed.'''
404 and where to move the active bookmark from, if needed.'''
405 checkout, movemarkfrom = None, None
405 checkout, movemarkfrom = None, None
406 activemark = repo._activebookmark
406 activemark = repo._activebookmark
407 if isactivewdirparent(repo):
407 if isactivewdirparent(repo):
408 movemarkfrom = repo['.'].node()
408 movemarkfrom = repo['.'].node()
409 elif activemark:
409 elif activemark:
410 ui.status(_("updating to active bookmark %s\n") % activemark)
410 ui.status(_("updating to active bookmark %s\n") % activemark)
411 checkout = activemark
411 checkout = activemark
412 return (checkout, movemarkfrom)
412 return (checkout, movemarkfrom)
413
413
414 def update(repo, parents, node):
414 def update(repo, parents, node):
415 deletefrom = parents
415 deletefrom = parents
416 marks = repo._bookmarks
416 marks = repo._bookmarks
417 active = marks.active
417 active = marks.active
418 if not active:
418 if not active:
419 return False
419 return False
420
420
421 bmchanges = []
421 bmchanges = []
422 if marks[active] in parents:
422 if marks[active] in parents:
423 new = repo[node]
423 new = repo[node]
424 divs = [marks.changectx(b) for b in marks
424 divs = [marks.changectx(b) for b in marks
425 if b.split('@', 1)[0] == active.split('@', 1)[0]]
425 if b.split('@', 1)[0] == active.split('@', 1)[0]]
426 anc = repo.changelog.ancestors([new.rev()])
426 anc = repo.changelog.ancestors([new.rev()])
427 deletefrom = [b.node() for b in divs if b.rev() in anc or b == new]
427 deletefrom = [b.node() for b in divs if b.rev() in anc or b == new]
428 if validdest(repo, marks.changectx(active), new):
428 if validdest(repo, marks.changectx(active), new):
429 bmchanges.append((active, new.node()))
429 bmchanges.append((active, new.node()))
430
430
431 for bm in divergent2delete(repo, deletefrom, active):
431 for bm in divergent2delete(repo, deletefrom, active):
432 bmchanges.append((bm, None))
432 bmchanges.append((bm, None))
433
433
434 if bmchanges:
434 if bmchanges:
435 with repo.lock(), repo.transaction('bookmark') as tr:
435 with repo.lock(), repo.transaction('bookmark') as tr:
436 marks.applychanges(repo, tr, bmchanges)
436 marks.applychanges(repo, tr, bmchanges)
437 return bool(bmchanges)
437 return bool(bmchanges)
438
438
439 def listbinbookmarks(repo):
439 def listbinbookmarks(repo):
440 # We may try to list bookmarks on a repo type that does not
440 # We may try to list bookmarks on a repo type that does not
441 # support it (e.g., statichttprepository).
441 # support it (e.g., statichttprepository).
442 marks = getattr(repo, '_bookmarks', {})
442 marks = getattr(repo, '_bookmarks', {})
443
443
444 hasnode = repo.changelog.hasnode
444 hasnode = repo.changelog.hasnode
445 for k, v in marks.iteritems():
445 for k, v in marks.iteritems():
446 # don't expose local divergent bookmarks
446 # don't expose local divergent bookmarks
447 if hasnode(v) and ('@' not in k or k.endswith('@')):
447 if hasnode(v) and ('@' not in k or k.endswith('@')):
448 yield k, v
448 yield k, v
449
449
450 def listbookmarks(repo):
450 def listbookmarks(repo):
451 d = {}
451 d = {}
452 for book, node in listbinbookmarks(repo):
452 for book, node in listbinbookmarks(repo):
453 d[book] = hex(node)
453 d[book] = hex(node)
454 return d
454 return d
455
455
456 def pushbookmark(repo, key, old, new):
456 def pushbookmark(repo, key, old, new):
457 with repo.wlock(), repo.lock(), repo.transaction('bookmarks') as tr:
457 with repo.wlock(), repo.lock(), repo.transaction('bookmarks') as tr:
458 marks = repo._bookmarks
458 marks = repo._bookmarks
459 existing = hex(marks.get(key, ''))
459 existing = hex(marks.get(key, ''))
460 if existing != old and existing != new:
460 if existing != old and existing != new:
461 return False
461 return False
462 if new == '':
462 if new == '':
463 changes = [(key, None)]
463 changes = [(key, None)]
464 else:
464 else:
465 if new not in repo:
465 if new not in repo:
466 return False
466 return False
467 changes = [(key, repo[new].node())]
467 changes = [(key, repo[new].node())]
468 marks.applychanges(repo, tr, changes)
468 marks.applychanges(repo, tr, changes)
469 return True
469 return True
470
470
471 def comparebookmarks(repo, srcmarks, dstmarks, targets=None):
471 def comparebookmarks(repo, srcmarks, dstmarks, targets=None):
472 '''Compare bookmarks between srcmarks and dstmarks
472 '''Compare bookmarks between srcmarks and dstmarks
473
473
474 This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
474 This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
475 differ, invalid)", each are list of bookmarks below:
475 differ, invalid)", each are list of bookmarks below:
476
476
477 :addsrc: added on src side (removed on dst side, perhaps)
477 :addsrc: added on src side (removed on dst side, perhaps)
478 :adddst: added on dst side (removed on src side, perhaps)
478 :adddst: added on dst side (removed on src side, perhaps)
479 :advsrc: advanced on src side
479 :advsrc: advanced on src side
480 :advdst: advanced on dst side
480 :advdst: advanced on dst side
481 :diverge: diverge
481 :diverge: diverge
482 :differ: changed, but changeset referred on src is unknown on dst
482 :differ: changed, but changeset referred on src is unknown on dst
483 :invalid: unknown on both side
483 :invalid: unknown on both side
484 :same: same on both side
484 :same: same on both side
485
485
486 Each elements of lists in result tuple is tuple "(bookmark name,
486 Each elements of lists in result tuple is tuple "(bookmark name,
487 changeset ID on source side, changeset ID on destination
487 changeset ID on source side, changeset ID on destination
488 side)". Each changeset IDs are 40 hexadecimal digit string or
488 side)". Each changeset IDs are 40 hexadecimal digit string or
489 None.
489 None.
490
490
491 Changeset IDs of tuples in "addsrc", "adddst", "differ" or
491 Changeset IDs of tuples in "addsrc", "adddst", "differ" or
492 "invalid" list may be unknown for repo.
492 "invalid" list may be unknown for repo.
493
493
494 If "targets" is specified, only bookmarks listed in it are
494 If "targets" is specified, only bookmarks listed in it are
495 examined.
495 examined.
496 '''
496 '''
497
497
498 if targets:
498 if targets:
499 bset = set(targets)
499 bset = set(targets)
500 else:
500 else:
501 srcmarkset = set(srcmarks)
501 srcmarkset = set(srcmarks)
502 dstmarkset = set(dstmarks)
502 dstmarkset = set(dstmarks)
503 bset = srcmarkset | dstmarkset
503 bset = srcmarkset | dstmarkset
504
504
505 results = ([], [], [], [], [], [], [], [])
505 results = ([], [], [], [], [], [], [], [])
506 addsrc = results[0].append
506 addsrc = results[0].append
507 adddst = results[1].append
507 adddst = results[1].append
508 advsrc = results[2].append
508 advsrc = results[2].append
509 advdst = results[3].append
509 advdst = results[3].append
510 diverge = results[4].append
510 diverge = results[4].append
511 differ = results[5].append
511 differ = results[5].append
512 invalid = results[6].append
512 invalid = results[6].append
513 same = results[7].append
513 same = results[7].append
514
514
515 for b in sorted(bset):
515 for b in sorted(bset):
516 if b not in srcmarks:
516 if b not in srcmarks:
517 if b in dstmarks:
517 if b in dstmarks:
518 adddst((b, None, dstmarks[b]))
518 adddst((b, None, dstmarks[b]))
519 else:
519 else:
520 invalid((b, None, None))
520 invalid((b, None, None))
521 elif b not in dstmarks:
521 elif b not in dstmarks:
522 addsrc((b, srcmarks[b], None))
522 addsrc((b, srcmarks[b], None))
523 else:
523 else:
524 scid = srcmarks[b]
524 scid = srcmarks[b]
525 dcid = dstmarks[b]
525 dcid = dstmarks[b]
526 if scid == dcid:
526 if scid == dcid:
527 same((b, scid, dcid))
527 same((b, scid, dcid))
528 elif scid in repo and dcid in repo:
528 elif scid in repo and dcid in repo:
529 sctx = repo[scid]
529 sctx = repo[scid]
530 dctx = repo[dcid]
530 dctx = repo[dcid]
531 if sctx.rev() < dctx.rev():
531 if sctx.rev() < dctx.rev():
532 if validdest(repo, sctx, dctx):
532 if validdest(repo, sctx, dctx):
533 advdst((b, scid, dcid))
533 advdst((b, scid, dcid))
534 else:
534 else:
535 diverge((b, scid, dcid))
535 diverge((b, scid, dcid))
536 else:
536 else:
537 if validdest(repo, dctx, sctx):
537 if validdest(repo, dctx, sctx):
538 advsrc((b, scid, dcid))
538 advsrc((b, scid, dcid))
539 else:
539 else:
540 diverge((b, scid, dcid))
540 diverge((b, scid, dcid))
541 else:
541 else:
542 # it is too expensive to examine in detail, in this case
542 # it is too expensive to examine in detail, in this case
543 differ((b, scid, dcid))
543 differ((b, scid, dcid))
544
544
545 return results
545 return results
546
546
547 def _diverge(ui, b, path, localmarks, remotenode):
547 def _diverge(ui, b, path, localmarks, remotenode):
548 '''Return appropriate diverged bookmark for specified ``path``
548 '''Return appropriate diverged bookmark for specified ``path``
549
549
550 This returns None, if it is failed to assign any divergent
550 This returns None, if it is failed to assign any divergent
551 bookmark name.
551 bookmark name.
552
552
553 This reuses already existing one with "@number" suffix, if it
553 This reuses already existing one with "@number" suffix, if it
554 refers ``remotenode``.
554 refers ``remotenode``.
555 '''
555 '''
556 if b == '@':
556 if b == '@':
557 b = ''
557 b = ''
558 # try to use an @pathalias suffix
558 # try to use an @pathalias suffix
559 # if an @pathalias already exists, we overwrite (update) it
559 # if an @pathalias already exists, we overwrite (update) it
560 if path.startswith("file:"):
560 if path.startswith("file:"):
561 path = util.url(path).path
561 path = util.url(path).path
562 for p, u in ui.configitems("paths"):
562 for p, u in ui.configitems("paths"):
563 if u.startswith("file:"):
563 if u.startswith("file:"):
564 u = util.url(u).path
564 u = util.url(u).path
565 if path == u:
565 if path == u:
566 return '%s@%s' % (b, p)
566 return '%s@%s' % (b, p)
567
567
568 # assign a unique "@number" suffix newly
568 # assign a unique "@number" suffix newly
569 for x in range(1, 100):
569 for x in range(1, 100):
570 n = '%s@%d' % (b, x)
570 n = '%s@%d' % (b, x)
571 if n not in localmarks or localmarks[n] == remotenode:
571 if n not in localmarks or localmarks[n] == remotenode:
572 return n
572 return n
573
573
574 return None
574 return None
575
575
576 def unhexlifybookmarks(marks):
576 def unhexlifybookmarks(marks):
577 binremotemarks = {}
577 binremotemarks = {}
578 for name, node in marks.items():
578 for name, node in marks.items():
579 binremotemarks[name] = bin(node)
579 binremotemarks[name] = bin(node)
580 return binremotemarks
580 return binremotemarks
581
581
582 _binaryentry = struct.Struct('>20sH')
582 _binaryentry = struct.Struct('>20sH')
583
583
584 def binaryencode(bookmarks):
584 def binaryencode(bookmarks):
585 """encode a '(bookmark, node)' iterable into a binary stream
585 """encode a '(bookmark, node)' iterable into a binary stream
586
586
587 the binary format is:
587 the binary format is:
588
588
589 <node><bookmark-length><bookmark-name>
589 <node><bookmark-length><bookmark-name>
590
590
591 :node: is a 20 bytes binary node,
591 :node: is a 20 bytes binary node,
592 :bookmark-length: an unsigned short,
592 :bookmark-length: an unsigned short,
593 :bookmark-name: the name of the bookmark (of length <bookmark-length>)
593 :bookmark-name: the name of the bookmark (of length <bookmark-length>)
594
594
595 wdirid (all bits set) will be used as a special value for "missing"
595 wdirid (all bits set) will be used as a special value for "missing"
596 """
596 """
597 binarydata = []
597 binarydata = []
598 for book, node in bookmarks:
598 for book, node in bookmarks:
599 if not node: # None or ''
599 if not node: # None or ''
600 node = wdirid
600 node = wdirid
601 binarydata.append(_binaryentry.pack(node, len(book)))
601 binarydata.append(_binaryentry.pack(node, len(book)))
602 binarydata.append(book)
602 binarydata.append(book)
603 return ''.join(binarydata)
603 return ''.join(binarydata)
604
604
605 def binarydecode(stream):
605 def binarydecode(stream):
606 """decode a binary stream into an '(bookmark, node)' iterable
606 """decode a binary stream into an '(bookmark, node)' iterable
607
607
608 the binary format is:
608 the binary format is:
609
609
610 <node><bookmark-length><bookmark-name>
610 <node><bookmark-length><bookmark-name>
611
611
612 :node: is a 20 bytes binary node,
612 :node: is a 20 bytes binary node,
613 :bookmark-length: an unsigned short,
613 :bookmark-length: an unsigned short,
614 :bookmark-name: the name of the bookmark (of length <bookmark-length>))
614 :bookmark-name: the name of the bookmark (of length <bookmark-length>))
615
615
616 wdirid (all bits set) will be used as a special value for "missing"
616 wdirid (all bits set) will be used as a special value for "missing"
617 """
617 """
618 entrysize = _binaryentry.size
618 entrysize = _binaryentry.size
619 books = []
619 books = []
620 while True:
620 while True:
621 entry = stream.read(entrysize)
621 entry = stream.read(entrysize)
622 if len(entry) < entrysize:
622 if len(entry) < entrysize:
623 if entry:
623 if entry:
624 raise error.Abort(_('bad bookmark stream'))
624 raise error.Abort(_('bad bookmark stream'))
625 break
625 break
626 node, length = _binaryentry.unpack(entry)
626 node, length = _binaryentry.unpack(entry)
627 bookmark = stream.read(length)
627 bookmark = stream.read(length)
628 if len(bookmark) < length:
628 if len(bookmark) < length:
629 if entry:
629 if entry:
630 raise error.Abort(_('bad bookmark stream'))
630 raise error.Abort(_('bad bookmark stream'))
631 if node == wdirid:
631 if node == wdirid:
632 node = None
632 node = None
633 books.append((bookmark, node))
633 books.append((bookmark, node))
634 return books
634 return books
635
635
636 def updatefromremote(ui, repo, remotemarks, path, trfunc, explicit=()):
636 def updatefromremote(ui, repo, remotemarks, path, trfunc, explicit=()):
637 ui.debug("checking for updated bookmarks\n")
637 ui.debug("checking for updated bookmarks\n")
638 localmarks = repo._bookmarks
638 localmarks = repo._bookmarks
639 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same
639 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same
640 ) = comparebookmarks(repo, remotemarks, localmarks)
640 ) = comparebookmarks(repo, remotemarks, localmarks)
641
641
642 status = ui.status
642 status = ui.status
643 warn = ui.warn
643 warn = ui.warn
644 if ui.configbool('ui', 'quietbookmarkmove'):
644 if ui.configbool('ui', 'quietbookmarkmove'):
645 status = warn = ui.debug
645 status = warn = ui.debug
646
646
647 explicit = set(explicit)
647 explicit = set(explicit)
648 changed = []
648 changed = []
649 for b, scid, dcid in addsrc:
649 for b, scid, dcid in addsrc:
650 if scid in repo: # add remote bookmarks for changes we already have
650 if scid in repo: # add remote bookmarks for changes we already have
651 changed.append((b, scid, status,
651 changed.append((b, scid, status,
652 _("adding remote bookmark %s\n") % (b)))
652 _("adding remote bookmark %s\n") % (b)))
653 elif b in explicit:
653 elif b in explicit:
654 explicit.remove(b)
654 explicit.remove(b)
655 ui.warn(_("remote bookmark %s points to locally missing %s\n")
655 ui.warn(_("remote bookmark %s points to locally missing %s\n")
656 % (b, hex(scid)[:12]))
656 % (b, hex(scid)[:12]))
657
657
658 for b, scid, dcid in advsrc:
658 for b, scid, dcid in advsrc:
659 changed.append((b, scid, status,
659 changed.append((b, scid, status,
660 _("updating bookmark %s\n") % (b)))
660 _("updating bookmark %s\n") % (b)))
661 # remove normal movement from explicit set
661 # remove normal movement from explicit set
662 explicit.difference_update(d[0] for d in changed)
662 explicit.difference_update(d[0] for d in changed)
663
663
664 for b, scid, dcid in diverge:
664 for b, scid, dcid in diverge:
665 if b in explicit:
665 if b in explicit:
666 explicit.discard(b)
666 explicit.discard(b)
667 changed.append((b, scid, status,
667 changed.append((b, scid, status,
668 _("importing bookmark %s\n") % (b)))
668 _("importing bookmark %s\n") % (b)))
669 else:
669 else:
670 db = _diverge(ui, b, path, localmarks, scid)
670 db = _diverge(ui, b, path, localmarks, scid)
671 if db:
671 if db:
672 changed.append((db, scid, warn,
672 changed.append((db, scid, warn,
673 _("divergent bookmark %s stored as %s\n") %
673 _("divergent bookmark %s stored as %s\n") %
674 (b, db)))
674 (b, db)))
675 else:
675 else:
676 warn(_("warning: failed to assign numbered name "
676 warn(_("warning: failed to assign numbered name "
677 "to divergent bookmark %s\n") % (b))
677 "to divergent bookmark %s\n") % (b))
678 for b, scid, dcid in adddst + advdst:
678 for b, scid, dcid in adddst + advdst:
679 if b in explicit:
679 if b in explicit:
680 explicit.discard(b)
680 explicit.discard(b)
681 changed.append((b, scid, status,
681 changed.append((b, scid, status,
682 _("importing bookmark %s\n") % (b)))
682 _("importing bookmark %s\n") % (b)))
683 for b, scid, dcid in differ:
683 for b, scid, dcid in differ:
684 if b in explicit:
684 if b in explicit:
685 explicit.remove(b)
685 explicit.remove(b)
686 ui.warn(_("remote bookmark %s points to locally missing %s\n")
686 ui.warn(_("remote bookmark %s points to locally missing %s\n")
687 % (b, hex(scid)[:12]))
687 % (b, hex(scid)[:12]))
688
688
689 if changed:
689 if changed:
690 tr = trfunc()
690 tr = trfunc()
691 changes = []
691 changes = []
692 for b, node, writer, msg in sorted(changed):
692 for b, node, writer, msg in sorted(changed):
693 changes.append((b, node))
693 changes.append((b, node))
694 writer(msg)
694 writer(msg)
695 localmarks.applychanges(repo, tr, changes)
695 localmarks.applychanges(repo, tr, changes)
696
696
697 def incoming(ui, repo, peer):
697 def incoming(ui, repo, peer):
698 '''Show bookmarks incoming from other to repo
698 '''Show bookmarks incoming from other to repo
699 '''
699 '''
700 ui.status(_("searching for changed bookmarks\n"))
700 ui.status(_("searching for changed bookmarks\n"))
701
701
702 with peer.commandexecutor() as e:
702 with peer.commandexecutor() as e:
703 remotemarks = unhexlifybookmarks(e.callcommand('listkeys', {
703 remotemarks = unhexlifybookmarks(e.callcommand('listkeys', {
704 'namespace': 'bookmarks',
704 'namespace': 'bookmarks',
705 }).result())
705 }).result())
706
706
707 r = comparebookmarks(repo, remotemarks, repo._bookmarks)
707 r = comparebookmarks(repo, remotemarks, repo._bookmarks)
708 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
708 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
709
709
710 incomings = []
710 incomings = []
711 if ui.debugflag:
711 if ui.debugflag:
712 getid = lambda id: id
712 getid = lambda id: id
713 else:
713 else:
714 getid = lambda id: id[:12]
714 getid = lambda id: id[:12]
715 if ui.verbose:
715 if ui.verbose:
716 def add(b, id, st):
716 def add(b, id, st):
717 incomings.append(" %-25s %s %s\n" % (b, getid(id), st))
717 incomings.append(" %-25s %s %s\n" % (b, getid(id), st))
718 else:
718 else:
719 def add(b, id, st):
719 def add(b, id, st):
720 incomings.append(" %-25s %s\n" % (b, getid(id)))
720 incomings.append(" %-25s %s\n" % (b, getid(id)))
721 for b, scid, dcid in addsrc:
721 for b, scid, dcid in addsrc:
722 # i18n: "added" refers to a bookmark
722 # i18n: "added" refers to a bookmark
723 add(b, hex(scid), _('added'))
723 add(b, hex(scid), _('added'))
724 for b, scid, dcid in advsrc:
724 for b, scid, dcid in advsrc:
725 # i18n: "advanced" refers to a bookmark
725 # i18n: "advanced" refers to a bookmark
726 add(b, hex(scid), _('advanced'))
726 add(b, hex(scid), _('advanced'))
727 for b, scid, dcid in diverge:
727 for b, scid, dcid in diverge:
728 # i18n: "diverged" refers to a bookmark
728 # i18n: "diverged" refers to a bookmark
729 add(b, hex(scid), _('diverged'))
729 add(b, hex(scid), _('diverged'))
730 for b, scid, dcid in differ:
730 for b, scid, dcid in differ:
731 # i18n: "changed" refers to a bookmark
731 # i18n: "changed" refers to a bookmark
732 add(b, hex(scid), _('changed'))
732 add(b, hex(scid), _('changed'))
733
733
734 if not incomings:
734 if not incomings:
735 ui.status(_("no changed bookmarks found\n"))
735 ui.status(_("no changed bookmarks found\n"))
736 return 1
736 return 1
737
737
738 for s in sorted(incomings):
738 for s in sorted(incomings):
739 ui.write(s)
739 ui.write(s)
740
740
741 return 0
741 return 0
742
742
743 def outgoing(ui, repo, other):
743 def outgoing(ui, repo, other):
744 '''Show bookmarks outgoing from repo to other
744 '''Show bookmarks outgoing from repo to other
745 '''
745 '''
746 ui.status(_("searching for changed bookmarks\n"))
746 ui.status(_("searching for changed bookmarks\n"))
747
747
748 remotemarks = unhexlifybookmarks(other.listkeys('bookmarks'))
748 remotemarks = unhexlifybookmarks(other.listkeys('bookmarks'))
749 r = comparebookmarks(repo, repo._bookmarks, remotemarks)
749 r = comparebookmarks(repo, repo._bookmarks, remotemarks)
750 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
750 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
751
751
752 outgoings = []
752 outgoings = []
753 if ui.debugflag:
753 if ui.debugflag:
754 getid = lambda id: id
754 getid = lambda id: id
755 else:
755 else:
756 getid = lambda id: id[:12]
756 getid = lambda id: id[:12]
757 if ui.verbose:
757 if ui.verbose:
758 def add(b, id, st):
758 def add(b, id, st):
759 outgoings.append(" %-25s %s %s\n" % (b, getid(id), st))
759 outgoings.append(" %-25s %s %s\n" % (b, getid(id), st))
760 else:
760 else:
761 def add(b, id, st):
761 def add(b, id, st):
762 outgoings.append(" %-25s %s\n" % (b, getid(id)))
762 outgoings.append(" %-25s %s\n" % (b, getid(id)))
763 for b, scid, dcid in addsrc:
763 for b, scid, dcid in addsrc:
764 # i18n: "added refers to a bookmark
764 # i18n: "added refers to a bookmark
765 add(b, hex(scid), _('added'))
765 add(b, hex(scid), _('added'))
766 for b, scid, dcid in adddst:
766 for b, scid, dcid in adddst:
767 # i18n: "deleted" refers to a bookmark
767 # i18n: "deleted" refers to a bookmark
768 add(b, ' ' * 40, _('deleted'))
768 add(b, ' ' * 40, _('deleted'))
769 for b, scid, dcid in advsrc:
769 for b, scid, dcid in advsrc:
770 # i18n: "advanced" refers to a bookmark
770 # i18n: "advanced" refers to a bookmark
771 add(b, hex(scid), _('advanced'))
771 add(b, hex(scid), _('advanced'))
772 for b, scid, dcid in diverge:
772 for b, scid, dcid in diverge:
773 # i18n: "diverged" refers to a bookmark
773 # i18n: "diverged" refers to a bookmark
774 add(b, hex(scid), _('diverged'))
774 add(b, hex(scid), _('diverged'))
775 for b, scid, dcid in differ:
775 for b, scid, dcid in differ:
776 # i18n: "changed" refers to a bookmark
776 # i18n: "changed" refers to a bookmark
777 add(b, hex(scid), _('changed'))
777 add(b, hex(scid), _('changed'))
778
778
779 if not outgoings:
779 if not outgoings:
780 ui.status(_("no changed bookmarks found\n"))
780 ui.status(_("no changed bookmarks found\n"))
781 return 1
781 return 1
782
782
783 for s in sorted(outgoings):
783 for s in sorted(outgoings):
784 ui.write(s)
784 ui.write(s)
785
785
786 return 0
786 return 0
787
787
788 def summary(repo, peer):
788 def summary(repo, peer):
789 '''Compare bookmarks between repo and other for "hg summary" output
789 '''Compare bookmarks between repo and other for "hg summary" output
790
790
791 This returns "(# of incoming, # of outgoing)" tuple.
791 This returns "(# of incoming, # of outgoing)" tuple.
792 '''
792 '''
793 with peer.commandexecutor() as e:
793 with peer.commandexecutor() as e:
794 remotemarks = unhexlifybookmarks(e.callcommand('listkeys', {
794 remotemarks = unhexlifybookmarks(e.callcommand('listkeys', {
795 'namespace': 'bookmarks',
795 'namespace': 'bookmarks',
796 }).result())
796 }).result())
797
797
798 r = comparebookmarks(repo, remotemarks, repo._bookmarks)
798 r = comparebookmarks(repo, remotemarks, repo._bookmarks)
799 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
799 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
800 return (len(addsrc), len(adddst))
800 return (len(addsrc), len(adddst))
801
801
802 def validdest(repo, old, new):
802 def validdest(repo, old, new):
803 """Is the new bookmark destination a valid update from the old one"""
803 """Is the new bookmark destination a valid update from the old one"""
804 repo = repo.unfiltered()
804 repo = repo.unfiltered()
805 if old == new:
805 if old == new:
806 # Old == new -> nothing to update.
806 # Old == new -> nothing to update.
807 return False
807 return False
808 elif not old:
808 elif not old:
809 # old is nullrev, anything is valid.
809 # old is nullrev, anything is valid.
810 # (new != nullrev has been excluded by the previous check)
810 # (new != nullrev has been excluded by the previous check)
811 return True
811 return True
812 elif repo.obsstore:
812 elif repo.obsstore:
813 return new.node() in obsutil.foreground(repo, [old.node()])
813 return new.node() in obsutil.foreground(repo, [old.node()])
814 else:
814 else:
815 # still an independent clause as it is lazier (and therefore faster)
815 # still an independent clause as it is lazier (and therefore faster)
816 return old.isancestorof(new)
816 return old.isancestorof(new)
817
817
818 def checkformat(repo, mark):
818 def checkformat(repo, mark):
819 """return a valid version of a potential bookmark name
819 """return a valid version of a potential bookmark name
820
820
821 Raises an abort error if the bookmark name is not valid.
821 Raises an abort error if the bookmark name is not valid.
822 """
822 """
823 mark = mark.strip()
823 mark = mark.strip()
824 if not mark:
824 if not mark:
825 raise error.Abort(_("bookmark names cannot consist entirely of "
825 raise error.Abort(_("bookmark names cannot consist entirely of "
826 "whitespace"))
826 "whitespace"))
827 scmutil.checknewlabel(repo, mark, 'bookmark')
827 scmutil.checknewlabel(repo, mark, 'bookmark')
828 return mark
828 return mark
829
829
830 def delete(repo, tr, names):
830 def delete(repo, tr, names):
831 """remove a mark from the bookmark store
831 """remove a mark from the bookmark store
832
832
833 Raises an abort error if mark does not exist.
833 Raises an abort error if mark does not exist.
834 """
834 """
835 marks = repo._bookmarks
835 marks = repo._bookmarks
836 changes = []
836 changes = []
837 for mark in names:
837 for mark in names:
838 if mark not in marks:
838 if mark not in marks:
839 raise error.Abort(_("bookmark '%s' does not exist") % mark)
839 raise error.Abort(_("bookmark '%s' does not exist") % mark)
840 if mark == repo._activebookmark:
840 if mark == repo._activebookmark:
841 deactivate(repo)
841 deactivate(repo)
842 changes.append((mark, None))
842 changes.append((mark, None))
843 marks.applychanges(repo, tr, changes)
843 marks.applychanges(repo, tr, changes)
844
844
845 def rename(repo, tr, old, new, force=False, inactive=False):
845 def rename(repo, tr, old, new, force=False, inactive=False):
846 """rename a bookmark from old to new
846 """rename a bookmark from old to new
847
847
848 If force is specified, then the new name can overwrite an existing
848 If force is specified, then the new name can overwrite an existing
849 bookmark.
849 bookmark.
850
850
851 If inactive is specified, then do not activate the new bookmark.
851 If inactive is specified, then do not activate the new bookmark.
852
852
853 Raises an abort error if old is not in the bookmark store.
853 Raises an abort error if old is not in the bookmark store.
854 """
854 """
855 marks = repo._bookmarks
855 marks = repo._bookmarks
856 mark = checkformat(repo, new)
856 mark = checkformat(repo, new)
857 if old not in marks:
857 if old not in marks:
858 raise error.Abort(_("bookmark '%s' does not exist") % old)
858 raise error.Abort(_("bookmark '%s' does not exist") % old)
859 changes = []
859 changes = []
860 for bm in marks.checkconflict(mark, force):
860 for bm in marks.checkconflict(mark, force):
861 changes.append((bm, None))
861 changes.append((bm, None))
862 changes.extend([(mark, marks[old]), (old, None)])
862 changes.extend([(mark, marks[old]), (old, None)])
863 marks.applychanges(repo, tr, changes)
863 marks.applychanges(repo, tr, changes)
864 if repo._activebookmark == old and not inactive:
864 if repo._activebookmark == old and not inactive:
865 activate(repo, mark)
865 activate(repo, mark)
866
866
867 def addbookmarks(repo, tr, names, rev=None, force=False, inactive=False):
867 def addbookmarks(repo, tr, names, rev=None, force=False, inactive=False):
868 """add a list of bookmarks
868 """add a list of bookmarks
869
869
870 If force is specified, then the new name can overwrite an existing
870 If force is specified, then the new name can overwrite an existing
871 bookmark.
871 bookmark.
872
872
873 If inactive is specified, then do not activate any bookmark. Otherwise, the
873 If inactive is specified, then do not activate any bookmark. Otherwise, the
874 first bookmark is activated.
874 first bookmark is activated.
875
875
876 Raises an abort error if old is not in the bookmark store.
876 Raises an abort error if old is not in the bookmark store.
877 """
877 """
878 marks = repo._bookmarks
878 marks = repo._bookmarks
879 cur = repo['.'].node()
879 cur = repo['.'].node()
880 newact = None
880 newact = None
881 changes = []
881 changes = []
882 hiddenrev = None
882 hiddenrev = None
883
883
884 # unhide revs if any
884 # unhide revs if any
885 if rev:
885 if rev:
886 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
886 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
887
887
888 for mark in names:
888 for mark in names:
889 mark = checkformat(repo, mark)
889 mark = checkformat(repo, mark)
890 if newact is None:
890 if newact is None:
891 newact = mark
891 newact = mark
892 if inactive and mark == repo._activebookmark:
892 if inactive and mark == repo._activebookmark:
893 deactivate(repo)
893 deactivate(repo)
894 return
894 return
895 tgt = cur
895 tgt = cur
896 if rev:
896 if rev:
897 ctx = scmutil.revsingle(repo, rev)
897 ctx = scmutil.revsingle(repo, rev)
898 if ctx.hidden():
898 if ctx.hidden():
899 hiddenrev = ctx.hex()[:12]
899 hiddenrev = ctx.hex()[:12]
900 tgt = ctx.node()
900 tgt = ctx.node()
901 for bm in marks.checkconflict(mark, force, tgt):
901 for bm in marks.checkconflict(mark, force, tgt):
902 changes.append((bm, None))
902 changes.append((bm, None))
903 changes.append((mark, tgt))
903 changes.append((mark, tgt))
904
904
905 if hiddenrev:
905 if hiddenrev:
906 repo.ui.warn(_("bookmarking hidden changeset %s\n") % hiddenrev)
906 repo.ui.warn(_("bookmarking hidden changeset %s\n") % hiddenrev)
907
907
908 if ctx.obsolete():
908 if ctx.obsolete():
909 msg = obsutil._getfilteredreason(repo, "%s" % hiddenrev, ctx)
909 msg = obsutil._getfilteredreason(repo, "%s" % hiddenrev, ctx)
910 repo.ui.warn("(%s)\n" % msg)
910 repo.ui.warn("(%s)\n" % msg)
911
911
912 marks.applychanges(repo, tr, changes)
912 marks.applychanges(repo, tr, changes)
913 if not inactive and cur == marks[newact] and not rev:
913 if not inactive and cur == marks[newact] and not rev:
914 activate(repo, newact)
914 activate(repo, newact)
915 elif cur != tgt and newact == repo._activebookmark:
915 elif cur != tgt and newact == repo._activebookmark:
916 deactivate(repo)
916 deactivate(repo)
917
917
918 def _printbookmarks(ui, repo, bmarks, **opts):
918 def _printbookmarks(ui, repo, bmarks, **opts):
919 """private method to print bookmarks
919 """private method to print bookmarks
920
920
921 Provides a way for extensions to control how bookmarks are printed (e.g.
921 Provides a way for extensions to control how bookmarks are printed (e.g.
922 prepend or postpend names)
922 prepend or postpend names)
923 """
923 """
924 opts = pycompat.byteskwargs(opts)
924 opts = pycompat.byteskwargs(opts)
925 fm = ui.formatter('bookmarks', opts)
925 fm = ui.formatter('bookmarks', opts)
926 contexthint = fm.contexthint('bookmark rev node active')
927 hexfn = fm.hexfunc
926 hexfn = fm.hexfunc
928 if len(bmarks) == 0 and fm.isplain():
927 if len(bmarks) == 0 and fm.isplain():
929 ui.status(_("no bookmarks set\n"))
928 ui.status(_("no bookmarks set\n"))
930 for bmark, (n, prefix, label) in sorted(bmarks.iteritems()):
929 for bmark, (n, prefix, label) in sorted(bmarks.iteritems()):
931 fm.startitem()
930 fm.startitem()
932 if 'ctx' in contexthint:
931 fm.context(repo=repo)
933 fm.context(ctx=repo[n])
934 if not ui.quiet:
932 if not ui.quiet:
935 fm.plain(' %s ' % prefix, label=label)
933 fm.plain(' %s ' % prefix, label=label)
936 fm.write('bookmark', '%s', bmark, label=label)
934 fm.write('bookmark', '%s', bmark, label=label)
937 pad = " " * (25 - encoding.colwidth(bmark))
935 pad = " " * (25 - encoding.colwidth(bmark))
938 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
936 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
939 repo.changelog.rev(n), hexfn(n), label=label)
937 repo.changelog.rev(n), hexfn(n), label=label)
940 fm.data(active=(activebookmarklabel in label))
938 fm.data(active=(activebookmarklabel in label))
941 fm.plain('\n')
939 fm.plain('\n')
942 fm.end()
940 fm.end()
943
941
944 def printbookmarks(ui, repo, **opts):
942 def printbookmarks(ui, repo, **opts):
945 """print bookmarks to a formatter
943 """print bookmarks to a formatter
946
944
947 Provides a way for extensions to control how bookmarks are printed.
945 Provides a way for extensions to control how bookmarks are printed.
948 """
946 """
949 marks = repo._bookmarks
947 marks = repo._bookmarks
950 bmarks = {}
948 bmarks = {}
951 for bmark, n in sorted(marks.iteritems()):
949 for bmark, n in sorted(marks.iteritems()):
952 active = repo._activebookmark
950 active = repo._activebookmark
953 if bmark == active:
951 if bmark == active:
954 prefix, label = '*', activebookmarklabel
952 prefix, label = '*', activebookmarklabel
955 else:
953 else:
956 prefix, label = ' ', ''
954 prefix, label = ' ', ''
957
955
958 bmarks[bmark] = (n, prefix, label)
956 bmarks[bmark] = (n, prefix, label)
959 _printbookmarks(ui, repo, bmarks, **opts)
957 _printbookmarks(ui, repo, bmarks, **opts)
960
958
961 def preparehookargs(name, old, new):
959 def preparehookargs(name, old, new):
962 if new is None:
960 if new is None:
963 new = ''
961 new = ''
964 if old is None:
962 if old is None:
965 old = ''
963 old = ''
966 return {'bookmark': name,
964 return {'bookmark': name,
967 'node': hex(new),
965 'node': hex(new),
968 'oldnode': hex(old)}
966 'oldnode': hex(old)}
@@ -1,5923 +1,5921
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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14 import sys
14 import sys
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 hex,
18 hex,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 short,
21 short,
22 )
22 )
23 from . import (
23 from . import (
24 archival,
24 archival,
25 bookmarks,
25 bookmarks,
26 bundle2,
26 bundle2,
27 changegroup,
27 changegroup,
28 cmdutil,
28 cmdutil,
29 copies,
29 copies,
30 debugcommands as debugcommandsmod,
30 debugcommands as debugcommandsmod,
31 destutil,
31 destutil,
32 dirstateguard,
32 dirstateguard,
33 discovery,
33 discovery,
34 encoding,
34 encoding,
35 error,
35 error,
36 exchange,
36 exchange,
37 extensions,
37 extensions,
38 filemerge,
38 filemerge,
39 formatter,
39 formatter,
40 graphmod,
40 graphmod,
41 hbisect,
41 hbisect,
42 help,
42 help,
43 hg,
43 hg,
44 logcmdutil,
44 logcmdutil,
45 merge as mergemod,
45 merge as mergemod,
46 narrowspec,
46 narrowspec,
47 obsolete,
47 obsolete,
48 obsutil,
48 obsutil,
49 patch,
49 patch,
50 phases,
50 phases,
51 pycompat,
51 pycompat,
52 rcutil,
52 rcutil,
53 registrar,
53 registrar,
54 repair,
54 repair,
55 revsetlang,
55 revsetlang,
56 rewriteutil,
56 rewriteutil,
57 scmutil,
57 scmutil,
58 server,
58 server,
59 state as statemod,
59 state as statemod,
60 streamclone,
60 streamclone,
61 tags as tagsmod,
61 tags as tagsmod,
62 templatekw,
62 templatekw,
63 ui as uimod,
63 ui as uimod,
64 util,
64 util,
65 wireprotoserver,
65 wireprotoserver,
66 )
66 )
67 from .utils import (
67 from .utils import (
68 dateutil,
68 dateutil,
69 stringutil,
69 stringutil,
70 )
70 )
71
71
72 table = {}
72 table = {}
73 table.update(debugcommandsmod.command._table)
73 table.update(debugcommandsmod.command._table)
74
74
75 command = registrar.command(table)
75 command = registrar.command(table)
76 INTENT_READONLY = registrar.INTENT_READONLY
76 INTENT_READONLY = registrar.INTENT_READONLY
77
77
78 # common command options
78 # common command options
79
79
80 globalopts = [
80 globalopts = [
81 ('R', 'repository', '',
81 ('R', 'repository', '',
82 _('repository root directory or name of overlay bundle file'),
82 _('repository root directory or name of overlay bundle file'),
83 _('REPO')),
83 _('REPO')),
84 ('', 'cwd', '',
84 ('', 'cwd', '',
85 _('change working directory'), _('DIR')),
85 _('change working directory'), _('DIR')),
86 ('y', 'noninteractive', None,
86 ('y', 'noninteractive', None,
87 _('do not prompt, automatically pick the first choice for all prompts')),
87 _('do not prompt, automatically pick the first choice for all prompts')),
88 ('q', 'quiet', None, _('suppress output')),
88 ('q', 'quiet', None, _('suppress output')),
89 ('v', 'verbose', None, _('enable additional output')),
89 ('v', 'verbose', None, _('enable additional output')),
90 ('', 'color', '',
90 ('', 'color', '',
91 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
91 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
92 # and should not be translated
92 # and should not be translated
93 _("when to colorize (boolean, always, auto, never, or debug)"),
93 _("when to colorize (boolean, always, auto, never, or debug)"),
94 _('TYPE')),
94 _('TYPE')),
95 ('', 'config', [],
95 ('', 'config', [],
96 _('set/override config option (use \'section.name=value\')'),
96 _('set/override config option (use \'section.name=value\')'),
97 _('CONFIG')),
97 _('CONFIG')),
98 ('', 'debug', None, _('enable debugging output')),
98 ('', 'debug', None, _('enable debugging output')),
99 ('', 'debugger', None, _('start debugger')),
99 ('', 'debugger', None, _('start debugger')),
100 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
100 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
101 _('ENCODE')),
101 _('ENCODE')),
102 ('', 'encodingmode', encoding.encodingmode,
102 ('', 'encodingmode', encoding.encodingmode,
103 _('set the charset encoding mode'), _('MODE')),
103 _('set the charset encoding mode'), _('MODE')),
104 ('', 'traceback', None, _('always print a traceback on exception')),
104 ('', 'traceback', None, _('always print a traceback on exception')),
105 ('', 'time', None, _('time how long the command takes')),
105 ('', 'time', None, _('time how long the command takes')),
106 ('', 'profile', None, _('print command execution profile')),
106 ('', 'profile', None, _('print command execution profile')),
107 ('', 'version', None, _('output version information and exit')),
107 ('', 'version', None, _('output version information and exit')),
108 ('h', 'help', None, _('display help and exit')),
108 ('h', 'help', None, _('display help and exit')),
109 ('', 'hidden', False, _('consider hidden changesets')),
109 ('', 'hidden', False, _('consider hidden changesets')),
110 ('', 'pager', 'auto',
110 ('', 'pager', 'auto',
111 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
111 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
112 ]
112 ]
113
113
114 dryrunopts = cmdutil.dryrunopts
114 dryrunopts = cmdutil.dryrunopts
115 remoteopts = cmdutil.remoteopts
115 remoteopts = cmdutil.remoteopts
116 walkopts = cmdutil.walkopts
116 walkopts = cmdutil.walkopts
117 commitopts = cmdutil.commitopts
117 commitopts = cmdutil.commitopts
118 commitopts2 = cmdutil.commitopts2
118 commitopts2 = cmdutil.commitopts2
119 formatteropts = cmdutil.formatteropts
119 formatteropts = cmdutil.formatteropts
120 templateopts = cmdutil.templateopts
120 templateopts = cmdutil.templateopts
121 logopts = cmdutil.logopts
121 logopts = cmdutil.logopts
122 diffopts = cmdutil.diffopts
122 diffopts = cmdutil.diffopts
123 diffwsopts = cmdutil.diffwsopts
123 diffwsopts = cmdutil.diffwsopts
124 diffopts2 = cmdutil.diffopts2
124 diffopts2 = cmdutil.diffopts2
125 mergetoolopts = cmdutil.mergetoolopts
125 mergetoolopts = cmdutil.mergetoolopts
126 similarityopts = cmdutil.similarityopts
126 similarityopts = cmdutil.similarityopts
127 subrepoopts = cmdutil.subrepoopts
127 subrepoopts = cmdutil.subrepoopts
128 debugrevlogopts = cmdutil.debugrevlogopts
128 debugrevlogopts = cmdutil.debugrevlogopts
129
129
130 # Commands start here, listed alphabetically
130 # Commands start here, listed alphabetically
131
131
132 @command('^add',
132 @command('^add',
133 walkopts + subrepoopts + dryrunopts,
133 walkopts + subrepoopts + dryrunopts,
134 _('[OPTION]... [FILE]...'),
134 _('[OPTION]... [FILE]...'),
135 inferrepo=True)
135 inferrepo=True)
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 (except
145 If no names are given, add all files to the repository (except
146 files matching ``.hgignore``).
146 files matching ``.hgignore``).
147
147
148 .. container:: verbose
148 .. container:: verbose
149
149
150 Examples:
150 Examples:
151
151
152 - New (unknown) files are added
152 - New (unknown) files are added
153 automatically by :hg:`add`::
153 automatically by :hg:`add`::
154
154
155 $ ls
155 $ ls
156 foo.c
156 foo.c
157 $ hg status
157 $ hg status
158 ? foo.c
158 ? foo.c
159 $ hg add
159 $ hg add
160 adding foo.c
160 adding foo.c
161 $ hg status
161 $ hg status
162 A foo.c
162 A foo.c
163
163
164 - Specific files to be added can be specified::
164 - Specific files to be added can be specified::
165
165
166 $ ls
166 $ ls
167 bar.c foo.c
167 bar.c foo.c
168 $ hg status
168 $ hg status
169 ? bar.c
169 ? bar.c
170 ? foo.c
170 ? foo.c
171 $ hg add bar.c
171 $ hg add bar.c
172 $ hg status
172 $ hg status
173 A bar.c
173 A bar.c
174 ? foo.c
174 ? foo.c
175
175
176 Returns 0 if all files are successfully added.
176 Returns 0 if all files are successfully added.
177 """
177 """
178
178
179 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
179 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
180 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
180 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
181 return rejected and 1 or 0
181 return rejected and 1 or 0
182
182
183 @command('addremove',
183 @command('addremove',
184 similarityopts + subrepoopts + walkopts + dryrunopts,
184 similarityopts + subrepoopts + walkopts + dryrunopts,
185 _('[OPTION]... [FILE]...'),
185 _('[OPTION]... [FILE]...'),
186 inferrepo=True)
186 inferrepo=True)
187 def addremove(ui, repo, *pats, **opts):
187 def addremove(ui, repo, *pats, **opts):
188 """add all new files, delete all missing files
188 """add all new files, delete all missing files
189
189
190 Add all new files and remove all missing files from the
190 Add all new files and remove all missing files from the
191 repository.
191 repository.
192
192
193 Unless names are given, new files are ignored if they match any of
193 Unless names are given, new files are ignored if they match any of
194 the patterns in ``.hgignore``. As with add, these changes take
194 the patterns in ``.hgignore``. As with add, these changes take
195 effect at the next commit.
195 effect at the next commit.
196
196
197 Use the -s/--similarity option to detect renamed files. This
197 Use the -s/--similarity option to detect renamed files. This
198 option takes a percentage between 0 (disabled) and 100 (files must
198 option takes a percentage between 0 (disabled) and 100 (files must
199 be identical) as its parameter. With a parameter greater than 0,
199 be identical) as its parameter. With a parameter greater than 0,
200 this compares every removed file with every added file and records
200 this compares every removed file with every added file and records
201 those similar enough as renames. Detecting renamed files this way
201 those similar enough as renames. Detecting renamed files this way
202 can be expensive. After using this option, :hg:`status -C` can be
202 can be expensive. After using this option, :hg:`status -C` can be
203 used to check which files were identified as moved or renamed. If
203 used to check which files were identified as moved or renamed. If
204 not specified, -s/--similarity defaults to 100 and only renames of
204 not specified, -s/--similarity defaults to 100 and only renames of
205 identical files are detected.
205 identical files are detected.
206
206
207 .. container:: verbose
207 .. container:: verbose
208
208
209 Examples:
209 Examples:
210
210
211 - A number of files (bar.c and foo.c) are new,
211 - A number of files (bar.c and foo.c) are new,
212 while foobar.c has been removed (without using :hg:`remove`)
212 while foobar.c has been removed (without using :hg:`remove`)
213 from the repository::
213 from the repository::
214
214
215 $ ls
215 $ ls
216 bar.c foo.c
216 bar.c foo.c
217 $ hg status
217 $ hg status
218 ! foobar.c
218 ! foobar.c
219 ? bar.c
219 ? bar.c
220 ? foo.c
220 ? foo.c
221 $ hg addremove
221 $ hg addremove
222 adding bar.c
222 adding bar.c
223 adding foo.c
223 adding foo.c
224 removing foobar.c
224 removing foobar.c
225 $ hg status
225 $ hg status
226 A bar.c
226 A bar.c
227 A foo.c
227 A foo.c
228 R foobar.c
228 R foobar.c
229
229
230 - A file foobar.c was moved to foo.c without using :hg:`rename`.
230 - A file foobar.c was moved to foo.c without using :hg:`rename`.
231 Afterwards, it was edited slightly::
231 Afterwards, it was edited slightly::
232
232
233 $ ls
233 $ ls
234 foo.c
234 foo.c
235 $ hg status
235 $ hg status
236 ! foobar.c
236 ! foobar.c
237 ? foo.c
237 ? foo.c
238 $ hg addremove --similarity 90
238 $ hg addremove --similarity 90
239 removing foobar.c
239 removing foobar.c
240 adding foo.c
240 adding foo.c
241 recording removal of foobar.c as rename to foo.c (94% similar)
241 recording removal of foobar.c as rename to foo.c (94% similar)
242 $ hg status -C
242 $ hg status -C
243 A foo.c
243 A foo.c
244 foobar.c
244 foobar.c
245 R foobar.c
245 R foobar.c
246
246
247 Returns 0 if all files are successfully added.
247 Returns 0 if all files are successfully added.
248 """
248 """
249 opts = pycompat.byteskwargs(opts)
249 opts = pycompat.byteskwargs(opts)
250 if not opts.get('similarity'):
250 if not opts.get('similarity'):
251 opts['similarity'] = '100'
251 opts['similarity'] = '100'
252 matcher = scmutil.match(repo[None], pats, opts)
252 matcher = scmutil.match(repo[None], pats, opts)
253 return scmutil.addremove(repo, matcher, "", opts)
253 return scmutil.addremove(repo, matcher, "", opts)
254
254
255 @command('^annotate|blame',
255 @command('^annotate|blame',
256 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
256 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
257 ('', 'follow', None,
257 ('', 'follow', None,
258 _('follow copies/renames and list the filename (DEPRECATED)')),
258 _('follow copies/renames and list the filename (DEPRECATED)')),
259 ('', 'no-follow', None, _("don't follow copies and renames")),
259 ('', 'no-follow', None, _("don't follow copies and renames")),
260 ('a', 'text', None, _('treat all files as text')),
260 ('a', 'text', None, _('treat all files as text')),
261 ('u', 'user', None, _('list the author (long with -v)')),
261 ('u', 'user', None, _('list the author (long with -v)')),
262 ('f', 'file', None, _('list the filename')),
262 ('f', 'file', None, _('list the filename')),
263 ('d', 'date', None, _('list the date (short with -q)')),
263 ('d', 'date', None, _('list the date (short with -q)')),
264 ('n', 'number', None, _('list the revision number (default)')),
264 ('n', 'number', None, _('list the revision number (default)')),
265 ('c', 'changeset', None, _('list the changeset')),
265 ('c', 'changeset', None, _('list the changeset')),
266 ('l', 'line-number', None, _('show line number at the first appearance')),
266 ('l', 'line-number', None, _('show line number at the first appearance')),
267 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
267 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
268 ] + diffwsopts + walkopts + formatteropts,
268 ] + diffwsopts + walkopts + formatteropts,
269 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
269 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
270 inferrepo=True)
270 inferrepo=True)
271 def annotate(ui, repo, *pats, **opts):
271 def annotate(ui, repo, *pats, **opts):
272 """show changeset information by line for each file
272 """show changeset information by line for each file
273
273
274 List changes in files, showing the revision id responsible for
274 List changes in files, showing the revision id responsible for
275 each line.
275 each line.
276
276
277 This command is useful for discovering when a change was made and
277 This command is useful for discovering when a change was made and
278 by whom.
278 by whom.
279
279
280 If you include --file, --user, or --date, the revision number is
280 If you include --file, --user, or --date, the revision number is
281 suppressed unless you also include --number.
281 suppressed unless you also include --number.
282
282
283 Without the -a/--text option, annotate will avoid processing files
283 Without the -a/--text option, annotate will avoid processing files
284 it detects as binary. With -a, annotate will annotate the file
284 it detects as binary. With -a, annotate will annotate the file
285 anyway, although the results will probably be neither useful
285 anyway, although the results will probably be neither useful
286 nor desirable.
286 nor desirable.
287
287
288 Returns 0 on success.
288 Returns 0 on success.
289 """
289 """
290 opts = pycompat.byteskwargs(opts)
290 opts = pycompat.byteskwargs(opts)
291 if not pats:
291 if not pats:
292 raise error.Abort(_('at least one filename or pattern is required'))
292 raise error.Abort(_('at least one filename or pattern is required'))
293
293
294 if opts.get('follow'):
294 if opts.get('follow'):
295 # --follow is deprecated and now just an alias for -f/--file
295 # --follow is deprecated and now just an alias for -f/--file
296 # to mimic the behavior of Mercurial before version 1.5
296 # to mimic the behavior of Mercurial before version 1.5
297 opts['file'] = True
297 opts['file'] = True
298
298
299 rev = opts.get('rev')
299 rev = opts.get('rev')
300 if rev:
300 if rev:
301 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
301 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
302 ctx = scmutil.revsingle(repo, rev)
302 ctx = scmutil.revsingle(repo, rev)
303
303
304 rootfm = ui.formatter('annotate', opts)
304 rootfm = ui.formatter('annotate', opts)
305 if ui.quiet:
305 if ui.quiet:
306 datefunc = dateutil.shortdate
306 datefunc = dateutil.shortdate
307 else:
307 else:
308 datefunc = dateutil.datestr
308 datefunc = dateutil.datestr
309 if ctx.rev() is None:
309 if ctx.rev() is None:
310 def hexfn(node):
310 def hexfn(node):
311 if node is None:
311 if node is None:
312 return None
312 return None
313 else:
313 else:
314 return rootfm.hexfunc(node)
314 return rootfm.hexfunc(node)
315 if opts.get('changeset'):
315 if opts.get('changeset'):
316 # omit "+" suffix which is appended to node hex
316 # omit "+" suffix which is appended to node hex
317 def formatrev(rev):
317 def formatrev(rev):
318 if rev is None:
318 if rev is None:
319 return '%d' % ctx.p1().rev()
319 return '%d' % ctx.p1().rev()
320 else:
320 else:
321 return '%d' % rev
321 return '%d' % rev
322 else:
322 else:
323 def formatrev(rev):
323 def formatrev(rev):
324 if rev is None:
324 if rev is None:
325 return '%d+' % ctx.p1().rev()
325 return '%d+' % ctx.p1().rev()
326 else:
326 else:
327 return '%d ' % rev
327 return '%d ' % rev
328 def formathex(hex):
328 def formathex(hex):
329 if hex is None:
329 if hex is None:
330 return '%s+' % rootfm.hexfunc(ctx.p1().node())
330 return '%s+' % rootfm.hexfunc(ctx.p1().node())
331 else:
331 else:
332 return '%s ' % hex
332 return '%s ' % hex
333 else:
333 else:
334 hexfn = rootfm.hexfunc
334 hexfn = rootfm.hexfunc
335 formatrev = formathex = pycompat.bytestr
335 formatrev = formathex = pycompat.bytestr
336
336
337 opmap = [('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
337 opmap = [('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
338 ('rev', ' ', lambda x: x.fctx.rev(), formatrev),
338 ('rev', ' ', lambda x: x.fctx.rev(), formatrev),
339 ('node', ' ', lambda x: hexfn(x.fctx.node()), formathex),
339 ('node', ' ', lambda x: hexfn(x.fctx.node()), formathex),
340 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
340 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
341 ('path', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
341 ('path', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
342 ('line_number', ':', lambda x: x.lineno, pycompat.bytestr),
342 ('line_number', ':', lambda x: x.lineno, pycompat.bytestr),
343 ]
343 ]
344 opnamemap = {'rev': 'number', 'node': 'changeset', 'path': 'file'}
344 opnamemap = {'rev': 'number', 'node': 'changeset', 'path': 'file'}
345
345
346 if (not opts.get('user') and not opts.get('changeset')
346 if (not opts.get('user') and not opts.get('changeset')
347 and not opts.get('date') and not opts.get('file')):
347 and not opts.get('date') and not opts.get('file')):
348 opts['number'] = True
348 opts['number'] = True
349
349
350 linenumber = opts.get('line_number') is not None
350 linenumber = opts.get('line_number') is not None
351 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
351 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
352 raise error.Abort(_('at least one of -n/-c is required for -l'))
352 raise error.Abort(_('at least one of -n/-c is required for -l'))
353
353
354 ui.pager('annotate')
354 ui.pager('annotate')
355
355
356 if rootfm.isplain():
356 if rootfm.isplain():
357 def makefunc(get, fmt):
357 def makefunc(get, fmt):
358 return lambda x: fmt(get(x))
358 return lambda x: fmt(get(x))
359 else:
359 else:
360 def makefunc(get, fmt):
360 def makefunc(get, fmt):
361 return get
361 return get
362 datahint = rootfm.datahint()
362 datahint = rootfm.datahint()
363 funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap
363 funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap
364 if opts.get(opnamemap.get(fn, fn)) or fn in datahint]
364 if opts.get(opnamemap.get(fn, fn)) or fn in datahint]
365 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
365 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
366 fields = ' '.join(fn for fn, sep, get, fmt in opmap
366 fields = ' '.join(fn for fn, sep, get, fmt in opmap
367 if opts.get(opnamemap.get(fn, fn)) or fn in datahint)
367 if opts.get(opnamemap.get(fn, fn)) or fn in datahint)
368
368
369 def bad(x, y):
369 def bad(x, y):
370 raise error.Abort("%s: %s" % (x, y))
370 raise error.Abort("%s: %s" % (x, y))
371
371
372 m = scmutil.match(ctx, pats, opts, badfn=bad)
372 m = scmutil.match(ctx, pats, opts, badfn=bad)
373
373
374 follow = not opts.get('no_follow')
374 follow = not opts.get('no_follow')
375 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
375 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
376 whitespace=True)
376 whitespace=True)
377 skiprevs = opts.get('skip')
377 skiprevs = opts.get('skip')
378 if skiprevs:
378 if skiprevs:
379 skiprevs = scmutil.revrange(repo, skiprevs)
379 skiprevs = scmutil.revrange(repo, skiprevs)
380
380
381 for abs in ctx.walk(m):
381 for abs in ctx.walk(m):
382 fctx = ctx[abs]
382 fctx = ctx[abs]
383 rootfm.startitem()
383 rootfm.startitem()
384 rootfm.data(path=abs)
384 rootfm.data(path=abs)
385 if not opts.get('text') and fctx.isbinary():
385 if not opts.get('text') and fctx.isbinary():
386 rootfm.plain(_("%s: binary file\n")
386 rootfm.plain(_("%s: binary file\n")
387 % ((pats and m.rel(abs)) or abs))
387 % ((pats and m.rel(abs)) or abs))
388 continue
388 continue
389
389
390 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
390 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
391 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
391 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
392 diffopts=diffopts)
392 diffopts=diffopts)
393 if not lines:
393 if not lines:
394 fm.end()
394 fm.end()
395 continue
395 continue
396 formats = []
396 formats = []
397 pieces = []
397 pieces = []
398
398
399 for f, sep in funcmap:
399 for f, sep in funcmap:
400 l = [f(n) for n in lines]
400 l = [f(n) for n in lines]
401 if fm.isplain():
401 if fm.isplain():
402 sizes = [encoding.colwidth(x) for x in l]
402 sizes = [encoding.colwidth(x) for x in l]
403 ml = max(sizes)
403 ml = max(sizes)
404 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
404 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
405 else:
405 else:
406 formats.append(['%s' for x in l])
406 formats.append(['%s' for x in l])
407 pieces.append(l)
407 pieces.append(l)
408
408
409 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
409 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
410 fm.startitem()
410 fm.startitem()
411 fm.context(fctx=n.fctx)
411 fm.context(fctx=n.fctx)
412 fm.write(fields, "".join(f), *p)
412 fm.write(fields, "".join(f), *p)
413 if n.skip:
413 if n.skip:
414 fmt = "* %s"
414 fmt = "* %s"
415 else:
415 else:
416 fmt = ": %s"
416 fmt = ": %s"
417 fm.write('line', fmt, n.text)
417 fm.write('line', fmt, n.text)
418
418
419 if not lines[-1].text.endswith('\n'):
419 if not lines[-1].text.endswith('\n'):
420 fm.plain('\n')
420 fm.plain('\n')
421 fm.end()
421 fm.end()
422
422
423 rootfm.end()
423 rootfm.end()
424
424
425 @command('archive',
425 @command('archive',
426 [('', 'no-decode', None, _('do not pass files through decoders')),
426 [('', 'no-decode', None, _('do not pass files through decoders')),
427 ('p', 'prefix', '', _('directory prefix for files in archive'),
427 ('p', 'prefix', '', _('directory prefix for files in archive'),
428 _('PREFIX')),
428 _('PREFIX')),
429 ('r', 'rev', '', _('revision to distribute'), _('REV')),
429 ('r', 'rev', '', _('revision to distribute'), _('REV')),
430 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
430 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
431 ] + subrepoopts + walkopts,
431 ] + subrepoopts + walkopts,
432 _('[OPTION]... DEST'))
432 _('[OPTION]... DEST'))
433 def archive(ui, repo, dest, **opts):
433 def archive(ui, repo, dest, **opts):
434 '''create an unversioned archive of a repository revision
434 '''create an unversioned archive of a repository revision
435
435
436 By default, the revision used is the parent of the working
436 By default, the revision used is the parent of the working
437 directory; use -r/--rev to specify a different revision.
437 directory; use -r/--rev to specify a different revision.
438
438
439 The archive type is automatically detected based on file
439 The archive type is automatically detected based on file
440 extension (to override, use -t/--type).
440 extension (to override, use -t/--type).
441
441
442 .. container:: verbose
442 .. container:: verbose
443
443
444 Examples:
444 Examples:
445
445
446 - create a zip file containing the 1.0 release::
446 - create a zip file containing the 1.0 release::
447
447
448 hg archive -r 1.0 project-1.0.zip
448 hg archive -r 1.0 project-1.0.zip
449
449
450 - create a tarball excluding .hg files::
450 - create a tarball excluding .hg files::
451
451
452 hg archive project.tar.gz -X ".hg*"
452 hg archive project.tar.gz -X ".hg*"
453
453
454 Valid types are:
454 Valid types are:
455
455
456 :``files``: a directory full of files (default)
456 :``files``: a directory full of files (default)
457 :``tar``: tar archive, uncompressed
457 :``tar``: tar archive, uncompressed
458 :``tbz2``: tar archive, compressed using bzip2
458 :``tbz2``: tar archive, compressed using bzip2
459 :``tgz``: tar archive, compressed using gzip
459 :``tgz``: tar archive, compressed using gzip
460 :``uzip``: zip archive, uncompressed
460 :``uzip``: zip archive, uncompressed
461 :``zip``: zip archive, compressed using deflate
461 :``zip``: zip archive, compressed using deflate
462
462
463 The exact name of the destination archive or directory is given
463 The exact name of the destination archive or directory is given
464 using a format string; see :hg:`help export` for details.
464 using a format string; see :hg:`help export` for details.
465
465
466 Each member added to an archive file has a directory prefix
466 Each member added to an archive file has a directory prefix
467 prepended. Use -p/--prefix to specify a format string for the
467 prepended. Use -p/--prefix to specify a format string for the
468 prefix. The default is the basename of the archive, with suffixes
468 prefix. The default is the basename of the archive, with suffixes
469 removed.
469 removed.
470
470
471 Returns 0 on success.
471 Returns 0 on success.
472 '''
472 '''
473
473
474 opts = pycompat.byteskwargs(opts)
474 opts = pycompat.byteskwargs(opts)
475 rev = opts.get('rev')
475 rev = opts.get('rev')
476 if rev:
476 if rev:
477 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
477 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
478 ctx = scmutil.revsingle(repo, rev)
478 ctx = scmutil.revsingle(repo, rev)
479 if not ctx:
479 if not ctx:
480 raise error.Abort(_('no working directory: please specify a revision'))
480 raise error.Abort(_('no working directory: please specify a revision'))
481 node = ctx.node()
481 node = ctx.node()
482 dest = cmdutil.makefilename(ctx, dest)
482 dest = cmdutil.makefilename(ctx, dest)
483 if os.path.realpath(dest) == repo.root:
483 if os.path.realpath(dest) == repo.root:
484 raise error.Abort(_('repository root cannot be destination'))
484 raise error.Abort(_('repository root cannot be destination'))
485
485
486 kind = opts.get('type') or archival.guesskind(dest) or 'files'
486 kind = opts.get('type') or archival.guesskind(dest) or 'files'
487 prefix = opts.get('prefix')
487 prefix = opts.get('prefix')
488
488
489 if dest == '-':
489 if dest == '-':
490 if kind == 'files':
490 if kind == 'files':
491 raise error.Abort(_('cannot archive plain files to stdout'))
491 raise error.Abort(_('cannot archive plain files to stdout'))
492 dest = cmdutil.makefileobj(ctx, dest)
492 dest = cmdutil.makefileobj(ctx, dest)
493 if not prefix:
493 if not prefix:
494 prefix = os.path.basename(repo.root) + '-%h'
494 prefix = os.path.basename(repo.root) + '-%h'
495
495
496 prefix = cmdutil.makefilename(ctx, prefix)
496 prefix = cmdutil.makefilename(ctx, prefix)
497 match = scmutil.match(ctx, [], opts)
497 match = scmutil.match(ctx, [], opts)
498 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
498 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
499 match, prefix, subrepos=opts.get('subrepos'))
499 match, prefix, subrepos=opts.get('subrepos'))
500
500
501 @command('backout',
501 @command('backout',
502 [('', 'merge', None, _('merge with old dirstate parent after backout')),
502 [('', 'merge', None, _('merge with old dirstate parent after backout')),
503 ('', 'commit', None,
503 ('', 'commit', None,
504 _('commit if no conflicts were encountered (DEPRECATED)')),
504 _('commit if no conflicts were encountered (DEPRECATED)')),
505 ('', 'no-commit', None, _('do not commit')),
505 ('', 'no-commit', None, _('do not commit')),
506 ('', 'parent', '',
506 ('', 'parent', '',
507 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
507 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
508 ('r', 'rev', '', _('revision to backout'), _('REV')),
508 ('r', 'rev', '', _('revision to backout'), _('REV')),
509 ('e', 'edit', False, _('invoke editor on commit messages')),
509 ('e', 'edit', False, _('invoke editor on commit messages')),
510 ] + mergetoolopts + walkopts + commitopts + commitopts2,
510 ] + mergetoolopts + walkopts + commitopts + commitopts2,
511 _('[OPTION]... [-r] REV'))
511 _('[OPTION]... [-r] REV'))
512 def backout(ui, repo, node=None, rev=None, **opts):
512 def backout(ui, repo, node=None, rev=None, **opts):
513 '''reverse effect of earlier changeset
513 '''reverse effect of earlier changeset
514
514
515 Prepare a new changeset with the effect of REV undone in the
515 Prepare a new changeset with the effect of REV undone in the
516 current working directory. If no conflicts were encountered,
516 current working directory. If no conflicts were encountered,
517 it will be committed immediately.
517 it will be committed immediately.
518
518
519 If REV is the parent of the working directory, then this new changeset
519 If REV is the parent of the working directory, then this new changeset
520 is committed automatically (unless --no-commit is specified).
520 is committed automatically (unless --no-commit is specified).
521
521
522 .. note::
522 .. note::
523
523
524 :hg:`backout` cannot be used to fix either an unwanted or
524 :hg:`backout` cannot be used to fix either an unwanted or
525 incorrect merge.
525 incorrect merge.
526
526
527 .. container:: verbose
527 .. container:: verbose
528
528
529 Examples:
529 Examples:
530
530
531 - Reverse the effect of the parent of the working directory.
531 - Reverse the effect of the parent of the working directory.
532 This backout will be committed immediately::
532 This backout will be committed immediately::
533
533
534 hg backout -r .
534 hg backout -r .
535
535
536 - Reverse the effect of previous bad revision 23::
536 - Reverse the effect of previous bad revision 23::
537
537
538 hg backout -r 23
538 hg backout -r 23
539
539
540 - Reverse the effect of previous bad revision 23 and
540 - Reverse the effect of previous bad revision 23 and
541 leave changes uncommitted::
541 leave changes uncommitted::
542
542
543 hg backout -r 23 --no-commit
543 hg backout -r 23 --no-commit
544 hg commit -m "Backout revision 23"
544 hg commit -m "Backout revision 23"
545
545
546 By default, the pending changeset will have one parent,
546 By default, the pending changeset will have one parent,
547 maintaining a linear history. With --merge, the pending
547 maintaining a linear history. With --merge, the pending
548 changeset will instead have two parents: the old parent of the
548 changeset will instead have two parents: the old parent of the
549 working directory and a new child of REV that simply undoes REV.
549 working directory and a new child of REV that simply undoes REV.
550
550
551 Before version 1.7, the behavior without --merge was equivalent
551 Before version 1.7, the behavior without --merge was equivalent
552 to specifying --merge followed by :hg:`update --clean .` to
552 to specifying --merge followed by :hg:`update --clean .` to
553 cancel the merge and leave the child of REV as a head to be
553 cancel the merge and leave the child of REV as a head to be
554 merged separately.
554 merged separately.
555
555
556 See :hg:`help dates` for a list of formats valid for -d/--date.
556 See :hg:`help dates` for a list of formats valid for -d/--date.
557
557
558 See :hg:`help revert` for a way to restore files to the state
558 See :hg:`help revert` for a way to restore files to the state
559 of another revision.
559 of another revision.
560
560
561 Returns 0 on success, 1 if nothing to backout or there are unresolved
561 Returns 0 on success, 1 if nothing to backout or there are unresolved
562 files.
562 files.
563 '''
563 '''
564 with repo.wlock(), repo.lock():
564 with repo.wlock(), repo.lock():
565 return _dobackout(ui, repo, node, rev, **opts)
565 return _dobackout(ui, repo, node, rev, **opts)
566
566
567 def _dobackout(ui, repo, node=None, rev=None, **opts):
567 def _dobackout(ui, repo, node=None, rev=None, **opts):
568 opts = pycompat.byteskwargs(opts)
568 opts = pycompat.byteskwargs(opts)
569 if opts.get('commit') and opts.get('no_commit'):
569 if opts.get('commit') and opts.get('no_commit'):
570 raise error.Abort(_("cannot use --commit with --no-commit"))
570 raise error.Abort(_("cannot use --commit with --no-commit"))
571 if opts.get('merge') and opts.get('no_commit'):
571 if opts.get('merge') and opts.get('no_commit'):
572 raise error.Abort(_("cannot use --merge with --no-commit"))
572 raise error.Abort(_("cannot use --merge with --no-commit"))
573
573
574 if rev and node:
574 if rev and node:
575 raise error.Abort(_("please specify just one revision"))
575 raise error.Abort(_("please specify just one revision"))
576
576
577 if not rev:
577 if not rev:
578 rev = node
578 rev = node
579
579
580 if not rev:
580 if not rev:
581 raise error.Abort(_("please specify a revision to backout"))
581 raise error.Abort(_("please specify a revision to backout"))
582
582
583 date = opts.get('date')
583 date = opts.get('date')
584 if date:
584 if date:
585 opts['date'] = dateutil.parsedate(date)
585 opts['date'] = dateutil.parsedate(date)
586
586
587 cmdutil.checkunfinished(repo)
587 cmdutil.checkunfinished(repo)
588 cmdutil.bailifchanged(repo)
588 cmdutil.bailifchanged(repo)
589 node = scmutil.revsingle(repo, rev).node()
589 node = scmutil.revsingle(repo, rev).node()
590
590
591 op1, op2 = repo.dirstate.parents()
591 op1, op2 = repo.dirstate.parents()
592 if not repo.changelog.isancestor(node, op1):
592 if not repo.changelog.isancestor(node, op1):
593 raise error.Abort(_('cannot backout change that is not an ancestor'))
593 raise error.Abort(_('cannot backout change that is not an ancestor'))
594
594
595 p1, p2 = repo.changelog.parents(node)
595 p1, p2 = repo.changelog.parents(node)
596 if p1 == nullid:
596 if p1 == nullid:
597 raise error.Abort(_('cannot backout a change with no parents'))
597 raise error.Abort(_('cannot backout a change with no parents'))
598 if p2 != nullid:
598 if p2 != nullid:
599 if not opts.get('parent'):
599 if not opts.get('parent'):
600 raise error.Abort(_('cannot backout a merge changeset'))
600 raise error.Abort(_('cannot backout a merge changeset'))
601 p = repo.lookup(opts['parent'])
601 p = repo.lookup(opts['parent'])
602 if p not in (p1, p2):
602 if p not in (p1, p2):
603 raise error.Abort(_('%s is not a parent of %s') %
603 raise error.Abort(_('%s is not a parent of %s') %
604 (short(p), short(node)))
604 (short(p), short(node)))
605 parent = p
605 parent = p
606 else:
606 else:
607 if opts.get('parent'):
607 if opts.get('parent'):
608 raise error.Abort(_('cannot use --parent on non-merge changeset'))
608 raise error.Abort(_('cannot use --parent on non-merge changeset'))
609 parent = p1
609 parent = p1
610
610
611 # the backout should appear on the same branch
611 # the backout should appear on the same branch
612 branch = repo.dirstate.branch()
612 branch = repo.dirstate.branch()
613 bheads = repo.branchheads(branch)
613 bheads = repo.branchheads(branch)
614 rctx = scmutil.revsingle(repo, hex(parent))
614 rctx = scmutil.revsingle(repo, hex(parent))
615 if not opts.get('merge') and op1 != node:
615 if not opts.get('merge') and op1 != node:
616 with dirstateguard.dirstateguard(repo, 'backout'):
616 with dirstateguard.dirstateguard(repo, 'backout'):
617 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
617 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
618 with ui.configoverride(overrides, 'backout'):
618 with ui.configoverride(overrides, 'backout'):
619 stats = mergemod.update(repo, parent, True, True, node, False)
619 stats = mergemod.update(repo, parent, True, True, node, False)
620 repo.setparents(op1, op2)
620 repo.setparents(op1, op2)
621 hg._showstats(repo, stats)
621 hg._showstats(repo, stats)
622 if stats.unresolvedcount:
622 if stats.unresolvedcount:
623 repo.ui.status(_("use 'hg resolve' to retry unresolved "
623 repo.ui.status(_("use 'hg resolve' to retry unresolved "
624 "file merges\n"))
624 "file merges\n"))
625 return 1
625 return 1
626 else:
626 else:
627 hg.clean(repo, node, show_stats=False)
627 hg.clean(repo, node, show_stats=False)
628 repo.dirstate.setbranch(branch)
628 repo.dirstate.setbranch(branch)
629 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
629 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
630
630
631 if opts.get('no_commit'):
631 if opts.get('no_commit'):
632 msg = _("changeset %s backed out, "
632 msg = _("changeset %s backed out, "
633 "don't forget to commit.\n")
633 "don't forget to commit.\n")
634 ui.status(msg % short(node))
634 ui.status(msg % short(node))
635 return 0
635 return 0
636
636
637 def commitfunc(ui, repo, message, match, opts):
637 def commitfunc(ui, repo, message, match, opts):
638 editform = 'backout'
638 editform = 'backout'
639 e = cmdutil.getcommiteditor(editform=editform,
639 e = cmdutil.getcommiteditor(editform=editform,
640 **pycompat.strkwargs(opts))
640 **pycompat.strkwargs(opts))
641 if not message:
641 if not message:
642 # we don't translate commit messages
642 # we don't translate commit messages
643 message = "Backed out changeset %s" % short(node)
643 message = "Backed out changeset %s" % short(node)
644 e = cmdutil.getcommiteditor(edit=True, editform=editform)
644 e = cmdutil.getcommiteditor(edit=True, editform=editform)
645 return repo.commit(message, opts.get('user'), opts.get('date'),
645 return repo.commit(message, opts.get('user'), opts.get('date'),
646 match, editor=e)
646 match, editor=e)
647 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
647 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
648 if not newnode:
648 if not newnode:
649 ui.status(_("nothing changed\n"))
649 ui.status(_("nothing changed\n"))
650 return 1
650 return 1
651 cmdutil.commitstatus(repo, newnode, branch, bheads)
651 cmdutil.commitstatus(repo, newnode, branch, bheads)
652
652
653 def nice(node):
653 def nice(node):
654 return '%d:%s' % (repo.changelog.rev(node), short(node))
654 return '%d:%s' % (repo.changelog.rev(node), short(node))
655 ui.status(_('changeset %s backs out changeset %s\n') %
655 ui.status(_('changeset %s backs out changeset %s\n') %
656 (nice(repo.changelog.tip()), nice(node)))
656 (nice(repo.changelog.tip()), nice(node)))
657 if opts.get('merge') and op1 != node:
657 if opts.get('merge') and op1 != node:
658 hg.clean(repo, op1, show_stats=False)
658 hg.clean(repo, op1, show_stats=False)
659 ui.status(_('merging with changeset %s\n')
659 ui.status(_('merging with changeset %s\n')
660 % nice(repo.changelog.tip()))
660 % nice(repo.changelog.tip()))
661 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
661 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
662 with ui.configoverride(overrides, 'backout'):
662 with ui.configoverride(overrides, 'backout'):
663 return hg.merge(repo, hex(repo.changelog.tip()))
663 return hg.merge(repo, hex(repo.changelog.tip()))
664 return 0
664 return 0
665
665
666 @command('bisect',
666 @command('bisect',
667 [('r', 'reset', False, _('reset bisect state')),
667 [('r', 'reset', False, _('reset bisect state')),
668 ('g', 'good', False, _('mark changeset good')),
668 ('g', 'good', False, _('mark changeset good')),
669 ('b', 'bad', False, _('mark changeset bad')),
669 ('b', 'bad', False, _('mark changeset bad')),
670 ('s', 'skip', False, _('skip testing changeset')),
670 ('s', 'skip', False, _('skip testing changeset')),
671 ('e', 'extend', False, _('extend the bisect range')),
671 ('e', 'extend', False, _('extend the bisect range')),
672 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
672 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
673 ('U', 'noupdate', False, _('do not update to target'))],
673 ('U', 'noupdate', False, _('do not update to target'))],
674 _("[-gbsr] [-U] [-c CMD] [REV]"))
674 _("[-gbsr] [-U] [-c CMD] [REV]"))
675 def bisect(ui, repo, rev=None, extra=None, command=None,
675 def bisect(ui, repo, rev=None, extra=None, command=None,
676 reset=None, good=None, bad=None, skip=None, extend=None,
676 reset=None, good=None, bad=None, skip=None, extend=None,
677 noupdate=None):
677 noupdate=None):
678 """subdivision search of changesets
678 """subdivision search of changesets
679
679
680 This command helps to find changesets which introduce problems. To
680 This command helps to find changesets which introduce problems. To
681 use, mark the earliest changeset you know exhibits the problem as
681 use, mark the earliest changeset you know exhibits the problem as
682 bad, then mark the latest changeset which is free from the problem
682 bad, then mark the latest changeset which is free from the problem
683 as good. Bisect will update your working directory to a revision
683 as good. Bisect will update your working directory to a revision
684 for testing (unless the -U/--noupdate option is specified). Once
684 for testing (unless the -U/--noupdate option is specified). Once
685 you have performed tests, mark the working directory as good or
685 you have performed tests, mark the working directory as good or
686 bad, and bisect will either update to another candidate changeset
686 bad, and bisect will either update to another candidate changeset
687 or announce that it has found the bad revision.
687 or announce that it has found the bad revision.
688
688
689 As a shortcut, you can also use the revision argument to mark a
689 As a shortcut, you can also use the revision argument to mark a
690 revision as good or bad without checking it out first.
690 revision as good or bad without checking it out first.
691
691
692 If you supply a command, it will be used for automatic bisection.
692 If you supply a command, it will be used for automatic bisection.
693 The environment variable HG_NODE will contain the ID of the
693 The environment variable HG_NODE will contain the ID of the
694 changeset being tested. The exit status of the command will be
694 changeset being tested. The exit status of the command will be
695 used to mark revisions as good or bad: status 0 means good, 125
695 used to mark revisions as good or bad: status 0 means good, 125
696 means to skip the revision, 127 (command not found) will abort the
696 means to skip the revision, 127 (command not found) will abort the
697 bisection, and any other non-zero exit status means the revision
697 bisection, and any other non-zero exit status means the revision
698 is bad.
698 is bad.
699
699
700 .. container:: verbose
700 .. container:: verbose
701
701
702 Some examples:
702 Some examples:
703
703
704 - start a bisection with known bad revision 34, and good revision 12::
704 - start a bisection with known bad revision 34, and good revision 12::
705
705
706 hg bisect --bad 34
706 hg bisect --bad 34
707 hg bisect --good 12
707 hg bisect --good 12
708
708
709 - advance the current bisection by marking current revision as good or
709 - advance the current bisection by marking current revision as good or
710 bad::
710 bad::
711
711
712 hg bisect --good
712 hg bisect --good
713 hg bisect --bad
713 hg bisect --bad
714
714
715 - mark the current revision, or a known revision, to be skipped (e.g. if
715 - mark the current revision, or a known revision, to be skipped (e.g. if
716 that revision is not usable because of another issue)::
716 that revision is not usable because of another issue)::
717
717
718 hg bisect --skip
718 hg bisect --skip
719 hg bisect --skip 23
719 hg bisect --skip 23
720
720
721 - skip all revisions that do not touch directories ``foo`` or ``bar``::
721 - skip all revisions that do not touch directories ``foo`` or ``bar``::
722
722
723 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
723 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
724
724
725 - forget the current bisection::
725 - forget the current bisection::
726
726
727 hg bisect --reset
727 hg bisect --reset
728
728
729 - use 'make && make tests' to automatically find the first broken
729 - use 'make && make tests' to automatically find the first broken
730 revision::
730 revision::
731
731
732 hg bisect --reset
732 hg bisect --reset
733 hg bisect --bad 34
733 hg bisect --bad 34
734 hg bisect --good 12
734 hg bisect --good 12
735 hg bisect --command "make && make tests"
735 hg bisect --command "make && make tests"
736
736
737 - see all changesets whose states are already known in the current
737 - see all changesets whose states are already known in the current
738 bisection::
738 bisection::
739
739
740 hg log -r "bisect(pruned)"
740 hg log -r "bisect(pruned)"
741
741
742 - see the changeset currently being bisected (especially useful
742 - see the changeset currently being bisected (especially useful
743 if running with -U/--noupdate)::
743 if running with -U/--noupdate)::
744
744
745 hg log -r "bisect(current)"
745 hg log -r "bisect(current)"
746
746
747 - see all changesets that took part in the current bisection::
747 - see all changesets that took part in the current bisection::
748
748
749 hg log -r "bisect(range)"
749 hg log -r "bisect(range)"
750
750
751 - you can even get a nice graph::
751 - you can even get a nice graph::
752
752
753 hg log --graph -r "bisect(range)"
753 hg log --graph -r "bisect(range)"
754
754
755 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
755 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
756
756
757 Returns 0 on success.
757 Returns 0 on success.
758 """
758 """
759 # backward compatibility
759 # backward compatibility
760 if rev in "good bad reset init".split():
760 if rev in "good bad reset init".split():
761 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
761 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
762 cmd, rev, extra = rev, extra, None
762 cmd, rev, extra = rev, extra, None
763 if cmd == "good":
763 if cmd == "good":
764 good = True
764 good = True
765 elif cmd == "bad":
765 elif cmd == "bad":
766 bad = True
766 bad = True
767 else:
767 else:
768 reset = True
768 reset = True
769 elif extra:
769 elif extra:
770 raise error.Abort(_('incompatible arguments'))
770 raise error.Abort(_('incompatible arguments'))
771
771
772 incompatibles = {
772 incompatibles = {
773 '--bad': bad,
773 '--bad': bad,
774 '--command': bool(command),
774 '--command': bool(command),
775 '--extend': extend,
775 '--extend': extend,
776 '--good': good,
776 '--good': good,
777 '--reset': reset,
777 '--reset': reset,
778 '--skip': skip,
778 '--skip': skip,
779 }
779 }
780
780
781 enabled = [x for x in incompatibles if incompatibles[x]]
781 enabled = [x for x in incompatibles if incompatibles[x]]
782
782
783 if len(enabled) > 1:
783 if len(enabled) > 1:
784 raise error.Abort(_('%s and %s are incompatible') %
784 raise error.Abort(_('%s and %s are incompatible') %
785 tuple(sorted(enabled)[0:2]))
785 tuple(sorted(enabled)[0:2]))
786
786
787 if reset:
787 if reset:
788 hbisect.resetstate(repo)
788 hbisect.resetstate(repo)
789 return
789 return
790
790
791 state = hbisect.load_state(repo)
791 state = hbisect.load_state(repo)
792
792
793 # update state
793 # update state
794 if good or bad or skip:
794 if good or bad or skip:
795 if rev:
795 if rev:
796 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
796 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
797 else:
797 else:
798 nodes = [repo.lookup('.')]
798 nodes = [repo.lookup('.')]
799 if good:
799 if good:
800 state['good'] += nodes
800 state['good'] += nodes
801 elif bad:
801 elif bad:
802 state['bad'] += nodes
802 state['bad'] += nodes
803 elif skip:
803 elif skip:
804 state['skip'] += nodes
804 state['skip'] += nodes
805 hbisect.save_state(repo, state)
805 hbisect.save_state(repo, state)
806 if not (state['good'] and state['bad']):
806 if not (state['good'] and state['bad']):
807 return
807 return
808
808
809 def mayupdate(repo, node, show_stats=True):
809 def mayupdate(repo, node, show_stats=True):
810 """common used update sequence"""
810 """common used update sequence"""
811 if noupdate:
811 if noupdate:
812 return
812 return
813 cmdutil.checkunfinished(repo)
813 cmdutil.checkunfinished(repo)
814 cmdutil.bailifchanged(repo)
814 cmdutil.bailifchanged(repo)
815 return hg.clean(repo, node, show_stats=show_stats)
815 return hg.clean(repo, node, show_stats=show_stats)
816
816
817 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
817 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
818
818
819 if command:
819 if command:
820 changesets = 1
820 changesets = 1
821 if noupdate:
821 if noupdate:
822 try:
822 try:
823 node = state['current'][0]
823 node = state['current'][0]
824 except LookupError:
824 except LookupError:
825 raise error.Abort(_('current bisect revision is unknown - '
825 raise error.Abort(_('current bisect revision is unknown - '
826 'start a new bisect to fix'))
826 'start a new bisect to fix'))
827 else:
827 else:
828 node, p2 = repo.dirstate.parents()
828 node, p2 = repo.dirstate.parents()
829 if p2 != nullid:
829 if p2 != nullid:
830 raise error.Abort(_('current bisect revision is a merge'))
830 raise error.Abort(_('current bisect revision is a merge'))
831 if rev:
831 if rev:
832 node = repo[scmutil.revsingle(repo, rev, node)].node()
832 node = repo[scmutil.revsingle(repo, rev, node)].node()
833 try:
833 try:
834 while changesets:
834 while changesets:
835 # update state
835 # update state
836 state['current'] = [node]
836 state['current'] = [node]
837 hbisect.save_state(repo, state)
837 hbisect.save_state(repo, state)
838 status = ui.system(command, environ={'HG_NODE': hex(node)},
838 status = ui.system(command, environ={'HG_NODE': hex(node)},
839 blockedtag='bisect_check')
839 blockedtag='bisect_check')
840 if status == 125:
840 if status == 125:
841 transition = "skip"
841 transition = "skip"
842 elif status == 0:
842 elif status == 0:
843 transition = "good"
843 transition = "good"
844 # status < 0 means process was killed
844 # status < 0 means process was killed
845 elif status == 127:
845 elif status == 127:
846 raise error.Abort(_("failed to execute %s") % command)
846 raise error.Abort(_("failed to execute %s") % command)
847 elif status < 0:
847 elif status < 0:
848 raise error.Abort(_("%s killed") % command)
848 raise error.Abort(_("%s killed") % command)
849 else:
849 else:
850 transition = "bad"
850 transition = "bad"
851 state[transition].append(node)
851 state[transition].append(node)
852 ctx = repo[node]
852 ctx = repo[node]
853 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
853 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
854 transition))
854 transition))
855 hbisect.checkstate(state)
855 hbisect.checkstate(state)
856 # bisect
856 # bisect
857 nodes, changesets, bgood = hbisect.bisect(repo, state)
857 nodes, changesets, bgood = hbisect.bisect(repo, state)
858 # update to next check
858 # update to next check
859 node = nodes[0]
859 node = nodes[0]
860 mayupdate(repo, node, show_stats=False)
860 mayupdate(repo, node, show_stats=False)
861 finally:
861 finally:
862 state['current'] = [node]
862 state['current'] = [node]
863 hbisect.save_state(repo, state)
863 hbisect.save_state(repo, state)
864 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
864 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
865 return
865 return
866
866
867 hbisect.checkstate(state)
867 hbisect.checkstate(state)
868
868
869 # actually bisect
869 # actually bisect
870 nodes, changesets, good = hbisect.bisect(repo, state)
870 nodes, changesets, good = hbisect.bisect(repo, state)
871 if extend:
871 if extend:
872 if not changesets:
872 if not changesets:
873 extendnode = hbisect.extendrange(repo, state, nodes, good)
873 extendnode = hbisect.extendrange(repo, state, nodes, good)
874 if extendnode is not None:
874 if extendnode is not None:
875 ui.write(_("Extending search to changeset %d:%s\n")
875 ui.write(_("Extending search to changeset %d:%s\n")
876 % (extendnode.rev(), extendnode))
876 % (extendnode.rev(), extendnode))
877 state['current'] = [extendnode.node()]
877 state['current'] = [extendnode.node()]
878 hbisect.save_state(repo, state)
878 hbisect.save_state(repo, state)
879 return mayupdate(repo, extendnode.node())
879 return mayupdate(repo, extendnode.node())
880 raise error.Abort(_("nothing to extend"))
880 raise error.Abort(_("nothing to extend"))
881
881
882 if changesets == 0:
882 if changesets == 0:
883 hbisect.printresult(ui, repo, state, displayer, nodes, good)
883 hbisect.printresult(ui, repo, state, displayer, nodes, good)
884 else:
884 else:
885 assert len(nodes) == 1 # only a single node can be tested next
885 assert len(nodes) == 1 # only a single node can be tested next
886 node = nodes[0]
886 node = nodes[0]
887 # compute the approximate number of remaining tests
887 # compute the approximate number of remaining tests
888 tests, size = 0, 2
888 tests, size = 0, 2
889 while size <= changesets:
889 while size <= changesets:
890 tests, size = tests + 1, size * 2
890 tests, size = tests + 1, size * 2
891 rev = repo.changelog.rev(node)
891 rev = repo.changelog.rev(node)
892 ui.write(_("Testing changeset %d:%s "
892 ui.write(_("Testing changeset %d:%s "
893 "(%d changesets remaining, ~%d tests)\n")
893 "(%d changesets remaining, ~%d tests)\n")
894 % (rev, short(node), changesets, tests))
894 % (rev, short(node), changesets, tests))
895 state['current'] = [node]
895 state['current'] = [node]
896 hbisect.save_state(repo, state)
896 hbisect.save_state(repo, state)
897 return mayupdate(repo, node)
897 return mayupdate(repo, node)
898
898
899 @command('bookmarks|bookmark',
899 @command('bookmarks|bookmark',
900 [('f', 'force', False, _('force')),
900 [('f', 'force', False, _('force')),
901 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
901 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
902 ('d', 'delete', False, _('delete a given bookmark')),
902 ('d', 'delete', False, _('delete a given bookmark')),
903 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
903 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
904 ('i', 'inactive', False, _('mark a bookmark inactive')),
904 ('i', 'inactive', False, _('mark a bookmark inactive')),
905 ('', 'active', False, _('display the active bookmark')),
905 ('', 'active', False, _('display the active bookmark')),
906 ] + formatteropts,
906 ] + formatteropts,
907 _('hg bookmarks [OPTIONS]... [NAME]...'))
907 _('hg bookmarks [OPTIONS]... [NAME]...'))
908 def bookmark(ui, repo, *names, **opts):
908 def bookmark(ui, repo, *names, **opts):
909 '''create a new bookmark or list existing bookmarks
909 '''create a new bookmark or list existing bookmarks
910
910
911 Bookmarks are labels on changesets to help track lines of development.
911 Bookmarks are labels on changesets to help track lines of development.
912 Bookmarks are unversioned and can be moved, renamed and deleted.
912 Bookmarks are unversioned and can be moved, renamed and deleted.
913 Deleting or moving a bookmark has no effect on the associated changesets.
913 Deleting or moving a bookmark has no effect on the associated changesets.
914
914
915 Creating or updating to a bookmark causes it to be marked as 'active'.
915 Creating or updating to a bookmark causes it to be marked as 'active'.
916 The active bookmark is indicated with a '*'.
916 The active bookmark is indicated with a '*'.
917 When a commit is made, the active bookmark will advance to the new commit.
917 When a commit is made, the active bookmark will advance to the new commit.
918 A plain :hg:`update` will also advance an active bookmark, if possible.
918 A plain :hg:`update` will also advance an active bookmark, if possible.
919 Updating away from a bookmark will cause it to be deactivated.
919 Updating away from a bookmark will cause it to be deactivated.
920
920
921 Bookmarks can be pushed and pulled between repositories (see
921 Bookmarks can be pushed and pulled between repositories (see
922 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
922 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
923 diverged, a new 'divergent bookmark' of the form 'name@path' will
923 diverged, a new 'divergent bookmark' of the form 'name@path' will
924 be created. Using :hg:`merge` will resolve the divergence.
924 be created. Using :hg:`merge` will resolve the divergence.
925
925
926 Specifying bookmark as '.' to -m or -d options is equivalent to specifying
926 Specifying bookmark as '.' to -m or -d options is equivalent to specifying
927 the active bookmark's name.
927 the active bookmark's name.
928
928
929 A bookmark named '@' has the special property that :hg:`clone` will
929 A bookmark named '@' has the special property that :hg:`clone` will
930 check it out by default if it exists.
930 check it out by default if it exists.
931
931
932 The '--active' flag will display the current bookmark or return non-zero,
932 The '--active' flag will display the current bookmark or return non-zero,
933 if combined with other action, they will be performed on the active
933 if combined with other action, they will be performed on the active
934 bookmark.
934 bookmark.
935
935
936 .. container:: verbose
936 .. container:: verbose
937
937
938 Examples:
938 Examples:
939
939
940 - create an active bookmark for a new line of development::
940 - create an active bookmark for a new line of development::
941
941
942 hg book new-feature
942 hg book new-feature
943
943
944 - create an inactive bookmark as a place marker::
944 - create an inactive bookmark as a place marker::
945
945
946 hg book -i reviewed
946 hg book -i reviewed
947
947
948 - create an inactive bookmark on another changeset::
948 - create an inactive bookmark on another changeset::
949
949
950 hg book -r .^ tested
950 hg book -r .^ tested
951
951
952 - rename bookmark turkey to dinner::
952 - rename bookmark turkey to dinner::
953
953
954 hg book -m turkey dinner
954 hg book -m turkey dinner
955
955
956 - move the '@' bookmark from another branch::
956 - move the '@' bookmark from another branch::
957
957
958 hg book -f @
958 hg book -f @
959 '''
959 '''
960 force = opts.get(r'force')
960 force = opts.get(r'force')
961 rev = opts.get(r'rev')
961 rev = opts.get(r'rev')
962 delete = opts.get(r'delete')
962 delete = opts.get(r'delete')
963 rename = opts.get(r'rename')
963 rename = opts.get(r'rename')
964 inactive = opts.get(r'inactive')
964 inactive = opts.get(r'inactive')
965 active = opts.get(r'active')
965 active = opts.get(r'active')
966
966
967 if delete and rename:
967 if delete and rename:
968 raise error.Abort(_("--delete and --rename are incompatible"))
968 raise error.Abort(_("--delete and --rename are incompatible"))
969 if delete and rev:
969 if delete and rev:
970 raise error.Abort(_("--rev is incompatible with --delete"))
970 raise error.Abort(_("--rev is incompatible with --delete"))
971 if rename and rev:
971 if rename and rev:
972 raise error.Abort(_("--rev is incompatible with --rename"))
972 raise error.Abort(_("--rev is incompatible with --rename"))
973 if delete and active:
973 if delete and active:
974 raise error.Abort(_("--delete is incompatible with --active"))
974 raise error.Abort(_("--delete is incompatible with --active"))
975 if rev and active:
975 if rev and active:
976 raise error.Abort(_("--rev is incompatible with --active"))
976 raise error.Abort(_("--rev is incompatible with --active"))
977 if rename and active:
977 if rename and active:
978 raise error.Abort(_("--rename is incompatible with --active"))
978 raise error.Abort(_("--rename is incompatible with --active"))
979 if names and active:
979 if names and active:
980 raise error.Abort(_("NAMES is incompatible with --active"))
980 raise error.Abort(_("NAMES is incompatible with --active"))
981 if inactive and active:
981 if inactive and active:
982 raise error.Abort(_("--inactive is incompatible with --active"))
982 raise error.Abort(_("--inactive is incompatible with --active"))
983 if not names and (delete or rev):
983 if not names and (delete or rev):
984 raise error.Abort(_("bookmark name required"))
984 raise error.Abort(_("bookmark name required"))
985
985
986 if delete or rename or names or inactive:
986 if delete or rename or names or inactive:
987 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
987 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
988 if delete:
988 if delete:
989 names = pycompat.maplist(repo._bookmarks.expandname, names)
989 names = pycompat.maplist(repo._bookmarks.expandname, names)
990 bookmarks.delete(repo, tr, names)
990 bookmarks.delete(repo, tr, names)
991 elif rename:
991 elif rename:
992 if not names:
992 if not names:
993 raise error.Abort(_("new bookmark name required"))
993 raise error.Abort(_("new bookmark name required"))
994 elif len(names) > 1:
994 elif len(names) > 1:
995 raise error.Abort(_("only one new bookmark name allowed"))
995 raise error.Abort(_("only one new bookmark name allowed"))
996 rename = repo._bookmarks.expandname(rename)
996 rename = repo._bookmarks.expandname(rename)
997 bookmarks.rename(repo, tr, rename, names[0], force, inactive)
997 bookmarks.rename(repo, tr, rename, names[0], force, inactive)
998 elif names:
998 elif names:
999 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
999 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1000 elif inactive:
1000 elif inactive:
1001 if len(repo._bookmarks) == 0:
1001 if len(repo._bookmarks) == 0:
1002 ui.status(_("no bookmarks set\n"))
1002 ui.status(_("no bookmarks set\n"))
1003 elif not repo._activebookmark:
1003 elif not repo._activebookmark:
1004 ui.status(_("no active bookmark\n"))
1004 ui.status(_("no active bookmark\n"))
1005 else:
1005 else:
1006 bookmarks.deactivate(repo)
1006 bookmarks.deactivate(repo)
1007 elif active:
1007 elif active:
1008 book = repo._activebookmark
1008 book = repo._activebookmark
1009 if book is None:
1009 if book is None:
1010 return 1
1010 return 1
1011 ui.write("%s\n" % book, label=bookmarks.activebookmarklabel)
1011 ui.write("%s\n" % book, label=bookmarks.activebookmarklabel)
1012 else: # show bookmarks
1012 else: # show bookmarks
1013 bookmarks.printbookmarks(ui, repo, **opts)
1013 bookmarks.printbookmarks(ui, repo, **opts)
1014
1014
1015 @command('branch',
1015 @command('branch',
1016 [('f', 'force', None,
1016 [('f', 'force', None,
1017 _('set branch name even if it shadows an existing branch')),
1017 _('set branch name even if it shadows an existing branch')),
1018 ('C', 'clean', None, _('reset branch name to parent branch name')),
1018 ('C', 'clean', None, _('reset branch name to parent branch name')),
1019 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1019 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1020 ],
1020 ],
1021 _('[-fC] [NAME]'))
1021 _('[-fC] [NAME]'))
1022 def branch(ui, repo, label=None, **opts):
1022 def branch(ui, repo, label=None, **opts):
1023 """set or show the current branch name
1023 """set or show the current branch name
1024
1024
1025 .. note::
1025 .. note::
1026
1026
1027 Branch names are permanent and global. Use :hg:`bookmark` to create a
1027 Branch names are permanent and global. Use :hg:`bookmark` to create a
1028 light-weight bookmark instead. See :hg:`help glossary` for more
1028 light-weight bookmark instead. See :hg:`help glossary` for more
1029 information about named branches and bookmarks.
1029 information about named branches and bookmarks.
1030
1030
1031 With no argument, show the current branch name. With one argument,
1031 With no argument, show the current branch name. With one argument,
1032 set the working directory branch name (the branch will not exist
1032 set the working directory branch name (the branch will not exist
1033 in the repository until the next commit). Standard practice
1033 in the repository until the next commit). Standard practice
1034 recommends that primary development take place on the 'default'
1034 recommends that primary development take place on the 'default'
1035 branch.
1035 branch.
1036
1036
1037 Unless -f/--force is specified, branch will not let you set a
1037 Unless -f/--force is specified, branch will not let you set a
1038 branch name that already exists.
1038 branch name that already exists.
1039
1039
1040 Use -C/--clean to reset the working directory branch to that of
1040 Use -C/--clean to reset the working directory branch to that of
1041 the parent of the working directory, negating a previous branch
1041 the parent of the working directory, negating a previous branch
1042 change.
1042 change.
1043
1043
1044 Use the command :hg:`update` to switch to an existing branch. Use
1044 Use the command :hg:`update` to switch to an existing branch. Use
1045 :hg:`commit --close-branch` to mark this branch head as closed.
1045 :hg:`commit --close-branch` to mark this branch head as closed.
1046 When all heads of a branch are closed, the branch will be
1046 When all heads of a branch are closed, the branch will be
1047 considered closed.
1047 considered closed.
1048
1048
1049 Returns 0 on success.
1049 Returns 0 on success.
1050 """
1050 """
1051 opts = pycompat.byteskwargs(opts)
1051 opts = pycompat.byteskwargs(opts)
1052 revs = opts.get('rev')
1052 revs = opts.get('rev')
1053 if label:
1053 if label:
1054 label = label.strip()
1054 label = label.strip()
1055
1055
1056 if not opts.get('clean') and not label:
1056 if not opts.get('clean') and not label:
1057 if revs:
1057 if revs:
1058 raise error.Abort(_("no branch name specified for the revisions"))
1058 raise error.Abort(_("no branch name specified for the revisions"))
1059 ui.write("%s\n" % repo.dirstate.branch())
1059 ui.write("%s\n" % repo.dirstate.branch())
1060 return
1060 return
1061
1061
1062 with repo.wlock():
1062 with repo.wlock():
1063 if opts.get('clean'):
1063 if opts.get('clean'):
1064 label = repo[None].p1().branch()
1064 label = repo[None].p1().branch()
1065 repo.dirstate.setbranch(label)
1065 repo.dirstate.setbranch(label)
1066 ui.status(_('reset working directory to branch %s\n') % label)
1066 ui.status(_('reset working directory to branch %s\n') % label)
1067 elif label:
1067 elif label:
1068
1068
1069 scmutil.checknewlabel(repo, label, 'branch')
1069 scmutil.checknewlabel(repo, label, 'branch')
1070 if revs:
1070 if revs:
1071 return cmdutil.changebranch(ui, repo, revs, label)
1071 return cmdutil.changebranch(ui, repo, revs, label)
1072
1072
1073 if not opts.get('force') and label in repo.branchmap():
1073 if not opts.get('force') and label in repo.branchmap():
1074 if label not in [p.branch() for p in repo[None].parents()]:
1074 if label not in [p.branch() for p in repo[None].parents()]:
1075 raise error.Abort(_('a branch of the same name already'
1075 raise error.Abort(_('a branch of the same name already'
1076 ' exists'),
1076 ' exists'),
1077 # i18n: "it" refers to an existing branch
1077 # i18n: "it" refers to an existing branch
1078 hint=_("use 'hg update' to switch to it"))
1078 hint=_("use 'hg update' to switch to it"))
1079
1079
1080 repo.dirstate.setbranch(label)
1080 repo.dirstate.setbranch(label)
1081 ui.status(_('marked working directory as branch %s\n') % label)
1081 ui.status(_('marked working directory as branch %s\n') % label)
1082
1082
1083 # find any open named branches aside from default
1083 # find any open named branches aside from default
1084 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1084 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1085 if n != "default" and not c]
1085 if n != "default" and not c]
1086 if not others:
1086 if not others:
1087 ui.status(_('(branches are permanent and global, '
1087 ui.status(_('(branches are permanent and global, '
1088 'did you want a bookmark?)\n'))
1088 'did you want a bookmark?)\n'))
1089
1089
1090 @command('branches',
1090 @command('branches',
1091 [('a', 'active', False,
1091 [('a', 'active', False,
1092 _('show only branches that have unmerged heads (DEPRECATED)')),
1092 _('show only branches that have unmerged heads (DEPRECATED)')),
1093 ('c', 'closed', False, _('show normal and closed branches')),
1093 ('c', 'closed', False, _('show normal and closed branches')),
1094 ] + formatteropts,
1094 ] + formatteropts,
1095 _('[-c]'),
1095 _('[-c]'),
1096 intents={INTENT_READONLY})
1096 intents={INTENT_READONLY})
1097 def branches(ui, repo, active=False, closed=False, **opts):
1097 def branches(ui, repo, active=False, closed=False, **opts):
1098 """list repository named branches
1098 """list repository named branches
1099
1099
1100 List the repository's named branches, indicating which ones are
1100 List the repository's named branches, indicating which ones are
1101 inactive. If -c/--closed is specified, also list branches which have
1101 inactive. If -c/--closed is specified, also list branches which have
1102 been marked closed (see :hg:`commit --close-branch`).
1102 been marked closed (see :hg:`commit --close-branch`).
1103
1103
1104 Use the command :hg:`update` to switch to an existing branch.
1104 Use the command :hg:`update` to switch to an existing branch.
1105
1105
1106 Returns 0.
1106 Returns 0.
1107 """
1107 """
1108
1108
1109 opts = pycompat.byteskwargs(opts)
1109 opts = pycompat.byteskwargs(opts)
1110 ui.pager('branches')
1110 ui.pager('branches')
1111 fm = ui.formatter('branches', opts)
1111 fm = ui.formatter('branches', opts)
1112 hexfunc = fm.hexfunc
1112 hexfunc = fm.hexfunc
1113
1113
1114 allheads = set(repo.heads())
1114 allheads = set(repo.heads())
1115 branches = []
1115 branches = []
1116 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1116 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1117 isactive = False
1117 isactive = False
1118 if not isclosed:
1118 if not isclosed:
1119 openheads = set(repo.branchmap().iteropen(heads))
1119 openheads = set(repo.branchmap().iteropen(heads))
1120 isactive = bool(openheads & allheads)
1120 isactive = bool(openheads & allheads)
1121 branches.append((tag, repo[tip], isactive, not isclosed))
1121 branches.append((tag, repo[tip], isactive, not isclosed))
1122 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1122 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1123 reverse=True)
1123 reverse=True)
1124
1124
1125 for tag, ctx, isactive, isopen in branches:
1125 for tag, ctx, isactive, isopen in branches:
1126 if active and not isactive:
1126 if active and not isactive:
1127 continue
1127 continue
1128 if isactive:
1128 if isactive:
1129 label = 'branches.active'
1129 label = 'branches.active'
1130 notice = ''
1130 notice = ''
1131 elif not isopen:
1131 elif not isopen:
1132 if not closed:
1132 if not closed:
1133 continue
1133 continue
1134 label = 'branches.closed'
1134 label = 'branches.closed'
1135 notice = _(' (closed)')
1135 notice = _(' (closed)')
1136 else:
1136 else:
1137 label = 'branches.inactive'
1137 label = 'branches.inactive'
1138 notice = _(' (inactive)')
1138 notice = _(' (inactive)')
1139 current = (tag == repo.dirstate.branch())
1139 current = (tag == repo.dirstate.branch())
1140 if current:
1140 if current:
1141 label = 'branches.current'
1141 label = 'branches.current'
1142
1142
1143 fm.startitem()
1143 fm.startitem()
1144 fm.write('branch', '%s', tag, label=label)
1144 fm.write('branch', '%s', tag, label=label)
1145 rev = ctx.rev()
1145 rev = ctx.rev()
1146 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1146 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1147 fmt = ' ' * padsize + ' %d:%s'
1147 fmt = ' ' * padsize + ' %d:%s'
1148 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1148 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1149 label='log.changeset changeset.%s' % ctx.phasestr())
1149 label='log.changeset changeset.%s' % ctx.phasestr())
1150 fm.context(ctx=ctx)
1150 fm.context(ctx=ctx)
1151 fm.data(active=isactive, closed=not isopen, current=current)
1151 fm.data(active=isactive, closed=not isopen, current=current)
1152 if not ui.quiet:
1152 if not ui.quiet:
1153 fm.plain(notice)
1153 fm.plain(notice)
1154 fm.plain('\n')
1154 fm.plain('\n')
1155 fm.end()
1155 fm.end()
1156
1156
1157 @command('bundle',
1157 @command('bundle',
1158 [('f', 'force', None, _('run even when the destination is unrelated')),
1158 [('f', 'force', None, _('run even when the destination is unrelated')),
1159 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1159 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1160 _('REV')),
1160 _('REV')),
1161 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1161 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1162 _('BRANCH')),
1162 _('BRANCH')),
1163 ('', 'base', [],
1163 ('', 'base', [],
1164 _('a base changeset assumed to be available at the destination'),
1164 _('a base changeset assumed to be available at the destination'),
1165 _('REV')),
1165 _('REV')),
1166 ('a', 'all', None, _('bundle all changesets in the repository')),
1166 ('a', 'all', None, _('bundle all changesets in the repository')),
1167 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1167 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1168 ] + remoteopts,
1168 ] + remoteopts,
1169 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1169 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1170 def bundle(ui, repo, fname, dest=None, **opts):
1170 def bundle(ui, repo, fname, dest=None, **opts):
1171 """create a bundle file
1171 """create a bundle file
1172
1172
1173 Generate a bundle file containing data to be transferred to another
1173 Generate a bundle file containing data to be transferred to another
1174 repository.
1174 repository.
1175
1175
1176 To create a bundle containing all changesets, use -a/--all
1176 To create a bundle containing all changesets, use -a/--all
1177 (or --base null). Otherwise, hg assumes the destination will have
1177 (or --base null). Otherwise, hg assumes the destination will have
1178 all the nodes you specify with --base parameters. Otherwise, hg
1178 all the nodes you specify with --base parameters. Otherwise, hg
1179 will assume the repository has all the nodes in destination, or
1179 will assume the repository has all the nodes in destination, or
1180 default-push/default if no destination is specified, where destination
1180 default-push/default if no destination is specified, where destination
1181 is the repository you provide through DEST option.
1181 is the repository you provide through DEST option.
1182
1182
1183 You can change bundle format with the -t/--type option. See
1183 You can change bundle format with the -t/--type option. See
1184 :hg:`help bundlespec` for documentation on this format. By default,
1184 :hg:`help bundlespec` for documentation on this format. By default,
1185 the most appropriate format is used and compression defaults to
1185 the most appropriate format is used and compression defaults to
1186 bzip2.
1186 bzip2.
1187
1187
1188 The bundle file can then be transferred using conventional means
1188 The bundle file can then be transferred using conventional means
1189 and applied to another repository with the unbundle or pull
1189 and applied to another repository with the unbundle or pull
1190 command. This is useful when direct push and pull are not
1190 command. This is useful when direct push and pull are not
1191 available or when exporting an entire repository is undesirable.
1191 available or when exporting an entire repository is undesirable.
1192
1192
1193 Applying bundles preserves all changeset contents including
1193 Applying bundles preserves all changeset contents including
1194 permissions, copy/rename information, and revision history.
1194 permissions, copy/rename information, and revision history.
1195
1195
1196 Returns 0 on success, 1 if no changes found.
1196 Returns 0 on success, 1 if no changes found.
1197 """
1197 """
1198 opts = pycompat.byteskwargs(opts)
1198 opts = pycompat.byteskwargs(opts)
1199 revs = None
1199 revs = None
1200 if 'rev' in opts:
1200 if 'rev' in opts:
1201 revstrings = opts['rev']
1201 revstrings = opts['rev']
1202 revs = scmutil.revrange(repo, revstrings)
1202 revs = scmutil.revrange(repo, revstrings)
1203 if revstrings and not revs:
1203 if revstrings and not revs:
1204 raise error.Abort(_('no commits to bundle'))
1204 raise error.Abort(_('no commits to bundle'))
1205
1205
1206 bundletype = opts.get('type', 'bzip2').lower()
1206 bundletype = opts.get('type', 'bzip2').lower()
1207 try:
1207 try:
1208 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1208 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1209 except error.UnsupportedBundleSpecification as e:
1209 except error.UnsupportedBundleSpecification as e:
1210 raise error.Abort(pycompat.bytestr(e),
1210 raise error.Abort(pycompat.bytestr(e),
1211 hint=_("see 'hg help bundlespec' for supported "
1211 hint=_("see 'hg help bundlespec' for supported "
1212 "values for --type"))
1212 "values for --type"))
1213 cgversion = bundlespec.contentopts["cg.version"]
1213 cgversion = bundlespec.contentopts["cg.version"]
1214
1214
1215 # Packed bundles are a pseudo bundle format for now.
1215 # Packed bundles are a pseudo bundle format for now.
1216 if cgversion == 's1':
1216 if cgversion == 's1':
1217 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1217 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1218 hint=_("use 'hg debugcreatestreamclonebundle'"))
1218 hint=_("use 'hg debugcreatestreamclonebundle'"))
1219
1219
1220 if opts.get('all'):
1220 if opts.get('all'):
1221 if dest:
1221 if dest:
1222 raise error.Abort(_("--all is incompatible with specifying "
1222 raise error.Abort(_("--all is incompatible with specifying "
1223 "a destination"))
1223 "a destination"))
1224 if opts.get('base'):
1224 if opts.get('base'):
1225 ui.warn(_("ignoring --base because --all was specified\n"))
1225 ui.warn(_("ignoring --base because --all was specified\n"))
1226 base = ['null']
1226 base = ['null']
1227 else:
1227 else:
1228 base = scmutil.revrange(repo, opts.get('base'))
1228 base = scmutil.revrange(repo, opts.get('base'))
1229 if cgversion not in changegroup.supportedoutgoingversions(repo):
1229 if cgversion not in changegroup.supportedoutgoingversions(repo):
1230 raise error.Abort(_("repository does not support bundle version %s") %
1230 raise error.Abort(_("repository does not support bundle version %s") %
1231 cgversion)
1231 cgversion)
1232
1232
1233 if base:
1233 if base:
1234 if dest:
1234 if dest:
1235 raise error.Abort(_("--base is incompatible with specifying "
1235 raise error.Abort(_("--base is incompatible with specifying "
1236 "a destination"))
1236 "a destination"))
1237 common = [repo[rev].node() for rev in base]
1237 common = [repo[rev].node() for rev in base]
1238 heads = [repo[r].node() for r in revs] if revs else None
1238 heads = [repo[r].node() for r in revs] if revs else None
1239 outgoing = discovery.outgoing(repo, common, heads)
1239 outgoing = discovery.outgoing(repo, common, heads)
1240 else:
1240 else:
1241 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1241 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1242 dest, branches = hg.parseurl(dest, opts.get('branch'))
1242 dest, branches = hg.parseurl(dest, opts.get('branch'))
1243 other = hg.peer(repo, opts, dest)
1243 other = hg.peer(repo, opts, dest)
1244 revs = [repo[r].hex() for r in revs]
1244 revs = [repo[r].hex() for r in revs]
1245 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1245 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1246 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1246 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1247 outgoing = discovery.findcommonoutgoing(repo, other,
1247 outgoing = discovery.findcommonoutgoing(repo, other,
1248 onlyheads=heads,
1248 onlyheads=heads,
1249 force=opts.get('force'),
1249 force=opts.get('force'),
1250 portable=True)
1250 portable=True)
1251
1251
1252 if not outgoing.missing:
1252 if not outgoing.missing:
1253 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1253 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1254 return 1
1254 return 1
1255
1255
1256 if cgversion == '01': #bundle1
1256 if cgversion == '01': #bundle1
1257 bversion = 'HG10' + bundlespec.wirecompression
1257 bversion = 'HG10' + bundlespec.wirecompression
1258 bcompression = None
1258 bcompression = None
1259 elif cgversion in ('02', '03'):
1259 elif cgversion in ('02', '03'):
1260 bversion = 'HG20'
1260 bversion = 'HG20'
1261 bcompression = bundlespec.wirecompression
1261 bcompression = bundlespec.wirecompression
1262 else:
1262 else:
1263 raise error.ProgrammingError(
1263 raise error.ProgrammingError(
1264 'bundle: unexpected changegroup version %s' % cgversion)
1264 'bundle: unexpected changegroup version %s' % cgversion)
1265
1265
1266 # TODO compression options should be derived from bundlespec parsing.
1266 # TODO compression options should be derived from bundlespec parsing.
1267 # This is a temporary hack to allow adjusting bundle compression
1267 # This is a temporary hack to allow adjusting bundle compression
1268 # level without a) formalizing the bundlespec changes to declare it
1268 # level without a) formalizing the bundlespec changes to declare it
1269 # b) introducing a command flag.
1269 # b) introducing a command flag.
1270 compopts = {}
1270 compopts = {}
1271 complevel = ui.configint('experimental',
1271 complevel = ui.configint('experimental',
1272 'bundlecomplevel.' + bundlespec.compression)
1272 'bundlecomplevel.' + bundlespec.compression)
1273 if complevel is None:
1273 if complevel is None:
1274 complevel = ui.configint('experimental', 'bundlecomplevel')
1274 complevel = ui.configint('experimental', 'bundlecomplevel')
1275 if complevel is not None:
1275 if complevel is not None:
1276 compopts['level'] = complevel
1276 compopts['level'] = complevel
1277
1277
1278 # Allow overriding the bundling of obsmarker in phases through
1278 # Allow overriding the bundling of obsmarker in phases through
1279 # configuration while we don't have a bundle version that include them
1279 # configuration while we don't have a bundle version that include them
1280 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1280 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1281 bundlespec.contentopts['obsolescence'] = True
1281 bundlespec.contentopts['obsolescence'] = True
1282 if repo.ui.configbool('experimental', 'bundle-phases'):
1282 if repo.ui.configbool('experimental', 'bundle-phases'):
1283 bundlespec.contentopts['phases'] = True
1283 bundlespec.contentopts['phases'] = True
1284
1284
1285 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1285 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1286 bundlespec.contentopts, compression=bcompression,
1286 bundlespec.contentopts, compression=bcompression,
1287 compopts=compopts)
1287 compopts=compopts)
1288
1288
1289 @command('cat',
1289 @command('cat',
1290 [('o', 'output', '',
1290 [('o', 'output', '',
1291 _('print output to file with formatted name'), _('FORMAT')),
1291 _('print output to file with formatted name'), _('FORMAT')),
1292 ('r', 'rev', '', _('print the given revision'), _('REV')),
1292 ('r', 'rev', '', _('print the given revision'), _('REV')),
1293 ('', 'decode', None, _('apply any matching decode filter')),
1293 ('', 'decode', None, _('apply any matching decode filter')),
1294 ] + walkopts + formatteropts,
1294 ] + walkopts + formatteropts,
1295 _('[OPTION]... FILE...'),
1295 _('[OPTION]... FILE...'),
1296 inferrepo=True,
1296 inferrepo=True,
1297 intents={INTENT_READONLY})
1297 intents={INTENT_READONLY})
1298 def cat(ui, repo, file1, *pats, **opts):
1298 def cat(ui, repo, file1, *pats, **opts):
1299 """output the current or given revision of files
1299 """output the current or given revision of files
1300
1300
1301 Print the specified files as they were at the given revision. If
1301 Print the specified files as they were at the given revision. If
1302 no revision is given, the parent of the working directory is used.
1302 no revision is given, the parent of the working directory is used.
1303
1303
1304 Output may be to a file, in which case the name of the file is
1304 Output may be to a file, in which case the name of the file is
1305 given using a template string. See :hg:`help templates`. In addition
1305 given using a template string. See :hg:`help templates`. In addition
1306 to the common template keywords, the following formatting rules are
1306 to the common template keywords, the following formatting rules are
1307 supported:
1307 supported:
1308
1308
1309 :``%%``: literal "%" character
1309 :``%%``: literal "%" character
1310 :``%s``: basename of file being printed
1310 :``%s``: basename of file being printed
1311 :``%d``: dirname of file being printed, or '.' if in repository root
1311 :``%d``: dirname of file being printed, or '.' if in repository root
1312 :``%p``: root-relative path name of file being printed
1312 :``%p``: root-relative path name of file being printed
1313 :``%H``: changeset hash (40 hexadecimal digits)
1313 :``%H``: changeset hash (40 hexadecimal digits)
1314 :``%R``: changeset revision number
1314 :``%R``: changeset revision number
1315 :``%h``: short-form changeset hash (12 hexadecimal digits)
1315 :``%h``: short-form changeset hash (12 hexadecimal digits)
1316 :``%r``: zero-padded changeset revision number
1316 :``%r``: zero-padded changeset revision number
1317 :``%b``: basename of the exporting repository
1317 :``%b``: basename of the exporting repository
1318 :``\\``: literal "\\" character
1318 :``\\``: literal "\\" character
1319
1319
1320 Returns 0 on success.
1320 Returns 0 on success.
1321 """
1321 """
1322 opts = pycompat.byteskwargs(opts)
1322 opts = pycompat.byteskwargs(opts)
1323 rev = opts.get('rev')
1323 rev = opts.get('rev')
1324 if rev:
1324 if rev:
1325 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1325 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1326 ctx = scmutil.revsingle(repo, rev)
1326 ctx = scmutil.revsingle(repo, rev)
1327 m = scmutil.match(ctx, (file1,) + pats, opts)
1327 m = scmutil.match(ctx, (file1,) + pats, opts)
1328 fntemplate = opts.pop('output', '')
1328 fntemplate = opts.pop('output', '')
1329 if cmdutil.isstdiofilename(fntemplate):
1329 if cmdutil.isstdiofilename(fntemplate):
1330 fntemplate = ''
1330 fntemplate = ''
1331
1331
1332 if fntemplate:
1332 if fntemplate:
1333 fm = formatter.nullformatter(ui, 'cat', opts)
1333 fm = formatter.nullformatter(ui, 'cat', opts)
1334 else:
1334 else:
1335 ui.pager('cat')
1335 ui.pager('cat')
1336 fm = ui.formatter('cat', opts)
1336 fm = ui.formatter('cat', opts)
1337 with fm:
1337 with fm:
1338 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1338 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1339 **pycompat.strkwargs(opts))
1339 **pycompat.strkwargs(opts))
1340
1340
1341 @command('^clone',
1341 @command('^clone',
1342 [('U', 'noupdate', None, _('the clone will include an empty working '
1342 [('U', 'noupdate', None, _('the clone will include an empty working '
1343 'directory (only a repository)')),
1343 'directory (only a repository)')),
1344 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1344 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1345 _('REV')),
1345 _('REV')),
1346 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1346 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1347 ' and its ancestors'), _('REV')),
1347 ' and its ancestors'), _('REV')),
1348 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1348 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1349 ' changesets and their ancestors'), _('BRANCH')),
1349 ' changesets and their ancestors'), _('BRANCH')),
1350 ('', 'pull', None, _('use pull protocol to copy metadata')),
1350 ('', 'pull', None, _('use pull protocol to copy metadata')),
1351 ('', 'uncompressed', None,
1351 ('', 'uncompressed', None,
1352 _('an alias to --stream (DEPRECATED)')),
1352 _('an alias to --stream (DEPRECATED)')),
1353 ('', 'stream', None,
1353 ('', 'stream', None,
1354 _('clone with minimal data processing')),
1354 _('clone with minimal data processing')),
1355 ] + remoteopts,
1355 ] + remoteopts,
1356 _('[OPTION]... SOURCE [DEST]'),
1356 _('[OPTION]... SOURCE [DEST]'),
1357 norepo=True)
1357 norepo=True)
1358 def clone(ui, source, dest=None, **opts):
1358 def clone(ui, source, dest=None, **opts):
1359 """make a copy of an existing repository
1359 """make a copy of an existing repository
1360
1360
1361 Create a copy of an existing repository in a new directory.
1361 Create a copy of an existing repository in a new directory.
1362
1362
1363 If no destination directory name is specified, it defaults to the
1363 If no destination directory name is specified, it defaults to the
1364 basename of the source.
1364 basename of the source.
1365
1365
1366 The location of the source is added to the new repository's
1366 The location of the source is added to the new repository's
1367 ``.hg/hgrc`` file, as the default to be used for future pulls.
1367 ``.hg/hgrc`` file, as the default to be used for future pulls.
1368
1368
1369 Only local paths and ``ssh://`` URLs are supported as
1369 Only local paths and ``ssh://`` URLs are supported as
1370 destinations. For ``ssh://`` destinations, no working directory or
1370 destinations. For ``ssh://`` destinations, no working directory or
1371 ``.hg/hgrc`` will be created on the remote side.
1371 ``.hg/hgrc`` will be created on the remote side.
1372
1372
1373 If the source repository has a bookmark called '@' set, that
1373 If the source repository has a bookmark called '@' set, that
1374 revision will be checked out in the new repository by default.
1374 revision will be checked out in the new repository by default.
1375
1375
1376 To check out a particular version, use -u/--update, or
1376 To check out a particular version, use -u/--update, or
1377 -U/--noupdate to create a clone with no working directory.
1377 -U/--noupdate to create a clone with no working directory.
1378
1378
1379 To pull only a subset of changesets, specify one or more revisions
1379 To pull only a subset of changesets, specify one or more revisions
1380 identifiers with -r/--rev or branches with -b/--branch. The
1380 identifiers with -r/--rev or branches with -b/--branch. The
1381 resulting clone will contain only the specified changesets and
1381 resulting clone will contain only the specified changesets and
1382 their ancestors. These options (or 'clone src#rev dest') imply
1382 their ancestors. These options (or 'clone src#rev dest') imply
1383 --pull, even for local source repositories.
1383 --pull, even for local source repositories.
1384
1384
1385 In normal clone mode, the remote normalizes repository data into a common
1385 In normal clone mode, the remote normalizes repository data into a common
1386 exchange format and the receiving end translates this data into its local
1386 exchange format and the receiving end translates this data into its local
1387 storage format. --stream activates a different clone mode that essentially
1387 storage format. --stream activates a different clone mode that essentially
1388 copies repository files from the remote with minimal data processing. This
1388 copies repository files from the remote with minimal data processing. This
1389 significantly reduces the CPU cost of a clone both remotely and locally.
1389 significantly reduces the CPU cost of a clone both remotely and locally.
1390 However, it often increases the transferred data size by 30-40%. This can
1390 However, it often increases the transferred data size by 30-40%. This can
1391 result in substantially faster clones where I/O throughput is plentiful,
1391 result in substantially faster clones where I/O throughput is plentiful,
1392 especially for larger repositories. A side-effect of --stream clones is
1392 especially for larger repositories. A side-effect of --stream clones is
1393 that storage settings and requirements on the remote are applied locally:
1393 that storage settings and requirements on the remote are applied locally:
1394 a modern client may inherit legacy or inefficient storage used by the
1394 a modern client may inherit legacy or inefficient storage used by the
1395 remote or a legacy Mercurial client may not be able to clone from a
1395 remote or a legacy Mercurial client may not be able to clone from a
1396 modern Mercurial remote.
1396 modern Mercurial remote.
1397
1397
1398 .. note::
1398 .. note::
1399
1399
1400 Specifying a tag will include the tagged changeset but not the
1400 Specifying a tag will include the tagged changeset but not the
1401 changeset containing the tag.
1401 changeset containing the tag.
1402
1402
1403 .. container:: verbose
1403 .. container:: verbose
1404
1404
1405 For efficiency, hardlinks are used for cloning whenever the
1405 For efficiency, hardlinks are used for cloning whenever the
1406 source and destination are on the same filesystem (note this
1406 source and destination are on the same filesystem (note this
1407 applies only to the repository data, not to the working
1407 applies only to the repository data, not to the working
1408 directory). Some filesystems, such as AFS, implement hardlinking
1408 directory). Some filesystems, such as AFS, implement hardlinking
1409 incorrectly, but do not report errors. In these cases, use the
1409 incorrectly, but do not report errors. In these cases, use the
1410 --pull option to avoid hardlinking.
1410 --pull option to avoid hardlinking.
1411
1411
1412 Mercurial will update the working directory to the first applicable
1412 Mercurial will update the working directory to the first applicable
1413 revision from this list:
1413 revision from this list:
1414
1414
1415 a) null if -U or the source repository has no changesets
1415 a) null if -U or the source repository has no changesets
1416 b) if -u . and the source repository is local, the first parent of
1416 b) if -u . and the source repository is local, the first parent of
1417 the source repository's working directory
1417 the source repository's working directory
1418 c) the changeset specified with -u (if a branch name, this means the
1418 c) the changeset specified with -u (if a branch name, this means the
1419 latest head of that branch)
1419 latest head of that branch)
1420 d) the changeset specified with -r
1420 d) the changeset specified with -r
1421 e) the tipmost head specified with -b
1421 e) the tipmost head specified with -b
1422 f) the tipmost head specified with the url#branch source syntax
1422 f) the tipmost head specified with the url#branch source syntax
1423 g) the revision marked with the '@' bookmark, if present
1423 g) the revision marked with the '@' bookmark, if present
1424 h) the tipmost head of the default branch
1424 h) the tipmost head of the default branch
1425 i) tip
1425 i) tip
1426
1426
1427 When cloning from servers that support it, Mercurial may fetch
1427 When cloning from servers that support it, Mercurial may fetch
1428 pre-generated data from a server-advertised URL or inline from the
1428 pre-generated data from a server-advertised URL or inline from the
1429 same stream. When this is done, hooks operating on incoming changesets
1429 same stream. When this is done, hooks operating on incoming changesets
1430 and changegroups may fire more than once, once for each pre-generated
1430 and changegroups may fire more than once, once for each pre-generated
1431 bundle and as well as for any additional remaining data. In addition,
1431 bundle and as well as for any additional remaining data. In addition,
1432 if an error occurs, the repository may be rolled back to a partial
1432 if an error occurs, the repository may be rolled back to a partial
1433 clone. This behavior may change in future releases.
1433 clone. This behavior may change in future releases.
1434 See :hg:`help -e clonebundles` for more.
1434 See :hg:`help -e clonebundles` for more.
1435
1435
1436 Examples:
1436 Examples:
1437
1437
1438 - clone a remote repository to a new directory named hg/::
1438 - clone a remote repository to a new directory named hg/::
1439
1439
1440 hg clone https://www.mercurial-scm.org/repo/hg/
1440 hg clone https://www.mercurial-scm.org/repo/hg/
1441
1441
1442 - create a lightweight local clone::
1442 - create a lightweight local clone::
1443
1443
1444 hg clone project/ project-feature/
1444 hg clone project/ project-feature/
1445
1445
1446 - clone from an absolute path on an ssh server (note double-slash)::
1446 - clone from an absolute path on an ssh server (note double-slash)::
1447
1447
1448 hg clone ssh://user@server//home/projects/alpha/
1448 hg clone ssh://user@server//home/projects/alpha/
1449
1449
1450 - do a streaming clone while checking out a specified version::
1450 - do a streaming clone while checking out a specified version::
1451
1451
1452 hg clone --stream http://server/repo -u 1.5
1452 hg clone --stream http://server/repo -u 1.5
1453
1453
1454 - create a repository without changesets after a particular revision::
1454 - create a repository without changesets after a particular revision::
1455
1455
1456 hg clone -r 04e544 experimental/ good/
1456 hg clone -r 04e544 experimental/ good/
1457
1457
1458 - clone (and track) a particular named branch::
1458 - clone (and track) a particular named branch::
1459
1459
1460 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1460 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1461
1461
1462 See :hg:`help urls` for details on specifying URLs.
1462 See :hg:`help urls` for details on specifying URLs.
1463
1463
1464 Returns 0 on success.
1464 Returns 0 on success.
1465 """
1465 """
1466 opts = pycompat.byteskwargs(opts)
1466 opts = pycompat.byteskwargs(opts)
1467 if opts.get('noupdate') and opts.get('updaterev'):
1467 if opts.get('noupdate') and opts.get('updaterev'):
1468 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1468 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1469
1469
1470 # --include/--exclude can come from narrow or sparse.
1470 # --include/--exclude can come from narrow or sparse.
1471 includepats, excludepats = None, None
1471 includepats, excludepats = None, None
1472
1472
1473 # hg.clone() differentiates between None and an empty set. So make sure
1473 # hg.clone() differentiates between None and an empty set. So make sure
1474 # patterns are sets if narrow is requested without patterns.
1474 # patterns are sets if narrow is requested without patterns.
1475 if opts.get('narrow'):
1475 if opts.get('narrow'):
1476 includepats = set()
1476 includepats = set()
1477 excludepats = set()
1477 excludepats = set()
1478
1478
1479 if opts.get('include'):
1479 if opts.get('include'):
1480 includepats = narrowspec.parsepatterns(opts.get('include'))
1480 includepats = narrowspec.parsepatterns(opts.get('include'))
1481 if opts.get('exclude'):
1481 if opts.get('exclude'):
1482 excludepats = narrowspec.parsepatterns(opts.get('exclude'))
1482 excludepats = narrowspec.parsepatterns(opts.get('exclude'))
1483
1483
1484 r = hg.clone(ui, opts, source, dest,
1484 r = hg.clone(ui, opts, source, dest,
1485 pull=opts.get('pull'),
1485 pull=opts.get('pull'),
1486 stream=opts.get('stream') or opts.get('uncompressed'),
1486 stream=opts.get('stream') or opts.get('uncompressed'),
1487 revs=opts.get('rev'),
1487 revs=opts.get('rev'),
1488 update=opts.get('updaterev') or not opts.get('noupdate'),
1488 update=opts.get('updaterev') or not opts.get('noupdate'),
1489 branch=opts.get('branch'),
1489 branch=opts.get('branch'),
1490 shareopts=opts.get('shareopts'),
1490 shareopts=opts.get('shareopts'),
1491 storeincludepats=includepats,
1491 storeincludepats=includepats,
1492 storeexcludepats=excludepats)
1492 storeexcludepats=excludepats)
1493
1493
1494 return r is None
1494 return r is None
1495
1495
1496 @command('^commit|ci',
1496 @command('^commit|ci',
1497 [('A', 'addremove', None,
1497 [('A', 'addremove', None,
1498 _('mark new/missing files as added/removed before committing')),
1498 _('mark new/missing files as added/removed before committing')),
1499 ('', 'close-branch', None,
1499 ('', 'close-branch', None,
1500 _('mark a branch head as closed')),
1500 _('mark a branch head as closed')),
1501 ('', 'amend', None, _('amend the parent of the working directory')),
1501 ('', 'amend', None, _('amend the parent of the working directory')),
1502 ('s', 'secret', None, _('use the secret phase for committing')),
1502 ('s', 'secret', None, _('use the secret phase for committing')),
1503 ('e', 'edit', None, _('invoke editor on commit messages')),
1503 ('e', 'edit', None, _('invoke editor on commit messages')),
1504 ('i', 'interactive', None, _('use interactive mode')),
1504 ('i', 'interactive', None, _('use interactive mode')),
1505 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1505 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1506 _('[OPTION]... [FILE]...'),
1506 _('[OPTION]... [FILE]...'),
1507 inferrepo=True)
1507 inferrepo=True)
1508 def commit(ui, repo, *pats, **opts):
1508 def commit(ui, repo, *pats, **opts):
1509 """commit the specified files or all outstanding changes
1509 """commit the specified files or all outstanding changes
1510
1510
1511 Commit changes to the given files into the repository. Unlike a
1511 Commit changes to the given files into the repository. Unlike a
1512 centralized SCM, this operation is a local operation. See
1512 centralized SCM, this operation is a local operation. See
1513 :hg:`push` for a way to actively distribute your changes.
1513 :hg:`push` for a way to actively distribute your changes.
1514
1514
1515 If a list of files is omitted, all changes reported by :hg:`status`
1515 If a list of files is omitted, all changes reported by :hg:`status`
1516 will be committed.
1516 will be committed.
1517
1517
1518 If you are committing the result of a merge, do not provide any
1518 If you are committing the result of a merge, do not provide any
1519 filenames or -I/-X filters.
1519 filenames or -I/-X filters.
1520
1520
1521 If no commit message is specified, Mercurial starts your
1521 If no commit message is specified, Mercurial starts your
1522 configured editor where you can enter a message. In case your
1522 configured editor where you can enter a message. In case your
1523 commit fails, you will find a backup of your message in
1523 commit fails, you will find a backup of your message in
1524 ``.hg/last-message.txt``.
1524 ``.hg/last-message.txt``.
1525
1525
1526 The --close-branch flag can be used to mark the current branch
1526 The --close-branch flag can be used to mark the current branch
1527 head closed. When all heads of a branch are closed, the branch
1527 head closed. When all heads of a branch are closed, the branch
1528 will be considered closed and no longer listed.
1528 will be considered closed and no longer listed.
1529
1529
1530 The --amend flag can be used to amend the parent of the
1530 The --amend flag can be used to amend the parent of the
1531 working directory with a new commit that contains the changes
1531 working directory with a new commit that contains the changes
1532 in the parent in addition to those currently reported by :hg:`status`,
1532 in the parent in addition to those currently reported by :hg:`status`,
1533 if there are any. The old commit is stored in a backup bundle in
1533 if there are any. The old commit is stored in a backup bundle in
1534 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1534 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1535 on how to restore it).
1535 on how to restore it).
1536
1536
1537 Message, user and date are taken from the amended commit unless
1537 Message, user and date are taken from the amended commit unless
1538 specified. When a message isn't specified on the command line,
1538 specified. When a message isn't specified on the command line,
1539 the editor will open with the message of the amended commit.
1539 the editor will open with the message of the amended commit.
1540
1540
1541 It is not possible to amend public changesets (see :hg:`help phases`)
1541 It is not possible to amend public changesets (see :hg:`help phases`)
1542 or changesets that have children.
1542 or changesets that have children.
1543
1543
1544 See :hg:`help dates` for a list of formats valid for -d/--date.
1544 See :hg:`help dates` for a list of formats valid for -d/--date.
1545
1545
1546 Returns 0 on success, 1 if nothing changed.
1546 Returns 0 on success, 1 if nothing changed.
1547
1547
1548 .. container:: verbose
1548 .. container:: verbose
1549
1549
1550 Examples:
1550 Examples:
1551
1551
1552 - commit all files ending in .py::
1552 - commit all files ending in .py::
1553
1553
1554 hg commit --include "set:**.py"
1554 hg commit --include "set:**.py"
1555
1555
1556 - commit all non-binary files::
1556 - commit all non-binary files::
1557
1557
1558 hg commit --exclude "set:binary()"
1558 hg commit --exclude "set:binary()"
1559
1559
1560 - amend the current commit and set the date to now::
1560 - amend the current commit and set the date to now::
1561
1561
1562 hg commit --amend --date now
1562 hg commit --amend --date now
1563 """
1563 """
1564 with repo.wlock(), repo.lock():
1564 with repo.wlock(), repo.lock():
1565 return _docommit(ui, repo, *pats, **opts)
1565 return _docommit(ui, repo, *pats, **opts)
1566
1566
1567 def _docommit(ui, repo, *pats, **opts):
1567 def _docommit(ui, repo, *pats, **opts):
1568 if opts.get(r'interactive'):
1568 if opts.get(r'interactive'):
1569 opts.pop(r'interactive')
1569 opts.pop(r'interactive')
1570 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1570 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1571 cmdutil.recordfilter, *pats,
1571 cmdutil.recordfilter, *pats,
1572 **opts)
1572 **opts)
1573 # ret can be 0 (no changes to record) or the value returned by
1573 # ret can be 0 (no changes to record) or the value returned by
1574 # commit(), 1 if nothing changed or None on success.
1574 # commit(), 1 if nothing changed or None on success.
1575 return 1 if ret == 0 else ret
1575 return 1 if ret == 0 else ret
1576
1576
1577 opts = pycompat.byteskwargs(opts)
1577 opts = pycompat.byteskwargs(opts)
1578 if opts.get('subrepos'):
1578 if opts.get('subrepos'):
1579 if opts.get('amend'):
1579 if opts.get('amend'):
1580 raise error.Abort(_('cannot amend with --subrepos'))
1580 raise error.Abort(_('cannot amend with --subrepos'))
1581 # Let --subrepos on the command line override config setting.
1581 # Let --subrepos on the command line override config setting.
1582 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1582 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1583
1583
1584 cmdutil.checkunfinished(repo, commit=True)
1584 cmdutil.checkunfinished(repo, commit=True)
1585
1585
1586 branch = repo[None].branch()
1586 branch = repo[None].branch()
1587 bheads = repo.branchheads(branch)
1587 bheads = repo.branchheads(branch)
1588
1588
1589 extra = {}
1589 extra = {}
1590 if opts.get('close_branch'):
1590 if opts.get('close_branch'):
1591 extra['close'] = '1'
1591 extra['close'] = '1'
1592
1592
1593 if not bheads:
1593 if not bheads:
1594 raise error.Abort(_('can only close branch heads'))
1594 raise error.Abort(_('can only close branch heads'))
1595 elif opts.get('amend'):
1595 elif opts.get('amend'):
1596 if repo[None].parents()[0].p1().branch() != branch and \
1596 if repo[None].parents()[0].p1().branch() != branch and \
1597 repo[None].parents()[0].p2().branch() != branch:
1597 repo[None].parents()[0].p2().branch() != branch:
1598 raise error.Abort(_('can only close branch heads'))
1598 raise error.Abort(_('can only close branch heads'))
1599
1599
1600 if opts.get('amend'):
1600 if opts.get('amend'):
1601 if ui.configbool('ui', 'commitsubrepos'):
1601 if ui.configbool('ui', 'commitsubrepos'):
1602 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1602 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1603
1603
1604 old = repo['.']
1604 old = repo['.']
1605 rewriteutil.precheck(repo, [old.rev()], 'amend')
1605 rewriteutil.precheck(repo, [old.rev()], 'amend')
1606
1606
1607 # Currently histedit gets confused if an amend happens while histedit
1607 # Currently histedit gets confused if an amend happens while histedit
1608 # is in progress. Since we have a checkunfinished command, we are
1608 # is in progress. Since we have a checkunfinished command, we are
1609 # temporarily honoring it.
1609 # temporarily honoring it.
1610 #
1610 #
1611 # Note: eventually this guard will be removed. Please do not expect
1611 # Note: eventually this guard will be removed. Please do not expect
1612 # this behavior to remain.
1612 # this behavior to remain.
1613 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1613 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1614 cmdutil.checkunfinished(repo)
1614 cmdutil.checkunfinished(repo)
1615
1615
1616 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1616 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1617 if node == old.node():
1617 if node == old.node():
1618 ui.status(_("nothing changed\n"))
1618 ui.status(_("nothing changed\n"))
1619 return 1
1619 return 1
1620 else:
1620 else:
1621 def commitfunc(ui, repo, message, match, opts):
1621 def commitfunc(ui, repo, message, match, opts):
1622 overrides = {}
1622 overrides = {}
1623 if opts.get('secret'):
1623 if opts.get('secret'):
1624 overrides[('phases', 'new-commit')] = 'secret'
1624 overrides[('phases', 'new-commit')] = 'secret'
1625
1625
1626 baseui = repo.baseui
1626 baseui = repo.baseui
1627 with baseui.configoverride(overrides, 'commit'):
1627 with baseui.configoverride(overrides, 'commit'):
1628 with ui.configoverride(overrides, 'commit'):
1628 with ui.configoverride(overrides, 'commit'):
1629 editform = cmdutil.mergeeditform(repo[None],
1629 editform = cmdutil.mergeeditform(repo[None],
1630 'commit.normal')
1630 'commit.normal')
1631 editor = cmdutil.getcommiteditor(
1631 editor = cmdutil.getcommiteditor(
1632 editform=editform, **pycompat.strkwargs(opts))
1632 editform=editform, **pycompat.strkwargs(opts))
1633 return repo.commit(message,
1633 return repo.commit(message,
1634 opts.get('user'),
1634 opts.get('user'),
1635 opts.get('date'),
1635 opts.get('date'),
1636 match,
1636 match,
1637 editor=editor,
1637 editor=editor,
1638 extra=extra)
1638 extra=extra)
1639
1639
1640 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1640 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1641
1641
1642 if not node:
1642 if not node:
1643 stat = cmdutil.postcommitstatus(repo, pats, opts)
1643 stat = cmdutil.postcommitstatus(repo, pats, opts)
1644 if stat[3]:
1644 if stat[3]:
1645 ui.status(_("nothing changed (%d missing files, see "
1645 ui.status(_("nothing changed (%d missing files, see "
1646 "'hg status')\n") % len(stat[3]))
1646 "'hg status')\n") % len(stat[3]))
1647 else:
1647 else:
1648 ui.status(_("nothing changed\n"))
1648 ui.status(_("nothing changed\n"))
1649 return 1
1649 return 1
1650
1650
1651 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1651 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1652
1652
1653 @command('config|showconfig|debugconfig',
1653 @command('config|showconfig|debugconfig',
1654 [('u', 'untrusted', None, _('show untrusted configuration options')),
1654 [('u', 'untrusted', None, _('show untrusted configuration options')),
1655 ('e', 'edit', None, _('edit user config')),
1655 ('e', 'edit', None, _('edit user config')),
1656 ('l', 'local', None, _('edit repository config')),
1656 ('l', 'local', None, _('edit repository config')),
1657 ('g', 'global', None, _('edit global config'))] + formatteropts,
1657 ('g', 'global', None, _('edit global config'))] + formatteropts,
1658 _('[-u] [NAME]...'),
1658 _('[-u] [NAME]...'),
1659 optionalrepo=True,
1659 optionalrepo=True,
1660 intents={INTENT_READONLY})
1660 intents={INTENT_READONLY})
1661 def config(ui, repo, *values, **opts):
1661 def config(ui, repo, *values, **opts):
1662 """show combined config settings from all hgrc files
1662 """show combined config settings from all hgrc files
1663
1663
1664 With no arguments, print names and values of all config items.
1664 With no arguments, print names and values of all config items.
1665
1665
1666 With one argument of the form section.name, print just the value
1666 With one argument of the form section.name, print just the value
1667 of that config item.
1667 of that config item.
1668
1668
1669 With multiple arguments, print names and values of all config
1669 With multiple arguments, print names and values of all config
1670 items with matching section names or section.names.
1670 items with matching section names or section.names.
1671
1671
1672 With --edit, start an editor on the user-level config file. With
1672 With --edit, start an editor on the user-level config file. With
1673 --global, edit the system-wide config file. With --local, edit the
1673 --global, edit the system-wide config file. With --local, edit the
1674 repository-level config file.
1674 repository-level config file.
1675
1675
1676 With --debug, the source (filename and line number) is printed
1676 With --debug, the source (filename and line number) is printed
1677 for each config item.
1677 for each config item.
1678
1678
1679 See :hg:`help config` for more information about config files.
1679 See :hg:`help config` for more information about config files.
1680
1680
1681 Returns 0 on success, 1 if NAME does not exist.
1681 Returns 0 on success, 1 if NAME does not exist.
1682
1682
1683 """
1683 """
1684
1684
1685 opts = pycompat.byteskwargs(opts)
1685 opts = pycompat.byteskwargs(opts)
1686 if opts.get('edit') or opts.get('local') or opts.get('global'):
1686 if opts.get('edit') or opts.get('local') or opts.get('global'):
1687 if opts.get('local') and opts.get('global'):
1687 if opts.get('local') and opts.get('global'):
1688 raise error.Abort(_("can't use --local and --global together"))
1688 raise error.Abort(_("can't use --local and --global together"))
1689
1689
1690 if opts.get('local'):
1690 if opts.get('local'):
1691 if not repo:
1691 if not repo:
1692 raise error.Abort(_("can't use --local outside a repository"))
1692 raise error.Abort(_("can't use --local outside a repository"))
1693 paths = [repo.vfs.join('hgrc')]
1693 paths = [repo.vfs.join('hgrc')]
1694 elif opts.get('global'):
1694 elif opts.get('global'):
1695 paths = rcutil.systemrcpath()
1695 paths = rcutil.systemrcpath()
1696 else:
1696 else:
1697 paths = rcutil.userrcpath()
1697 paths = rcutil.userrcpath()
1698
1698
1699 for f in paths:
1699 for f in paths:
1700 if os.path.exists(f):
1700 if os.path.exists(f):
1701 break
1701 break
1702 else:
1702 else:
1703 if opts.get('global'):
1703 if opts.get('global'):
1704 samplehgrc = uimod.samplehgrcs['global']
1704 samplehgrc = uimod.samplehgrcs['global']
1705 elif opts.get('local'):
1705 elif opts.get('local'):
1706 samplehgrc = uimod.samplehgrcs['local']
1706 samplehgrc = uimod.samplehgrcs['local']
1707 else:
1707 else:
1708 samplehgrc = uimod.samplehgrcs['user']
1708 samplehgrc = uimod.samplehgrcs['user']
1709
1709
1710 f = paths[0]
1710 f = paths[0]
1711 fp = open(f, "wb")
1711 fp = open(f, "wb")
1712 fp.write(util.tonativeeol(samplehgrc))
1712 fp.write(util.tonativeeol(samplehgrc))
1713 fp.close()
1713 fp.close()
1714
1714
1715 editor = ui.geteditor()
1715 editor = ui.geteditor()
1716 ui.system("%s \"%s\"" % (editor, f),
1716 ui.system("%s \"%s\"" % (editor, f),
1717 onerr=error.Abort, errprefix=_("edit failed"),
1717 onerr=error.Abort, errprefix=_("edit failed"),
1718 blockedtag='config_edit')
1718 blockedtag='config_edit')
1719 return
1719 return
1720 ui.pager('config')
1720 ui.pager('config')
1721 fm = ui.formatter('config', opts)
1721 fm = ui.formatter('config', opts)
1722 for t, f in rcutil.rccomponents():
1722 for t, f in rcutil.rccomponents():
1723 if t == 'path':
1723 if t == 'path':
1724 ui.debug('read config from: %s\n' % f)
1724 ui.debug('read config from: %s\n' % f)
1725 elif t == 'items':
1725 elif t == 'items':
1726 for section, name, value, source in f:
1726 for section, name, value, source in f:
1727 ui.debug('set config by: %s\n' % source)
1727 ui.debug('set config by: %s\n' % source)
1728 else:
1728 else:
1729 raise error.ProgrammingError('unknown rctype: %s' % t)
1729 raise error.ProgrammingError('unknown rctype: %s' % t)
1730 untrusted = bool(opts.get('untrusted'))
1730 untrusted = bool(opts.get('untrusted'))
1731
1731
1732 selsections = selentries = []
1732 selsections = selentries = []
1733 if values:
1733 if values:
1734 selsections = [v for v in values if '.' not in v]
1734 selsections = [v for v in values if '.' not in v]
1735 selentries = [v for v in values if '.' in v]
1735 selentries = [v for v in values if '.' in v]
1736 uniquesel = (len(selentries) == 1 and not selsections)
1736 uniquesel = (len(selentries) == 1 and not selsections)
1737 selsections = set(selsections)
1737 selsections = set(selsections)
1738 selentries = set(selentries)
1738 selentries = set(selentries)
1739
1739
1740 matched = False
1740 matched = False
1741 for section, name, value in ui.walkconfig(untrusted=untrusted):
1741 for section, name, value in ui.walkconfig(untrusted=untrusted):
1742 source = ui.configsource(section, name, untrusted)
1742 source = ui.configsource(section, name, untrusted)
1743 value = pycompat.bytestr(value)
1743 value = pycompat.bytestr(value)
1744 if fm.isplain():
1744 if fm.isplain():
1745 source = source or 'none'
1745 source = source or 'none'
1746 value = value.replace('\n', '\\n')
1746 value = value.replace('\n', '\\n')
1747 entryname = section + '.' + name
1747 entryname = section + '.' + name
1748 if values and not (section in selsections or entryname in selentries):
1748 if values and not (section in selsections or entryname in selentries):
1749 continue
1749 continue
1750 fm.startitem()
1750 fm.startitem()
1751 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1751 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1752 if uniquesel:
1752 if uniquesel:
1753 fm.data(name=entryname)
1753 fm.data(name=entryname)
1754 fm.write('value', '%s\n', value)
1754 fm.write('value', '%s\n', value)
1755 else:
1755 else:
1756 fm.write('name value', '%s=%s\n', entryname, value)
1756 fm.write('name value', '%s=%s\n', entryname, value)
1757 matched = True
1757 matched = True
1758 fm.end()
1758 fm.end()
1759 if matched:
1759 if matched:
1760 return 0
1760 return 0
1761 return 1
1761 return 1
1762
1762
1763 @command('copy|cp',
1763 @command('copy|cp',
1764 [('A', 'after', None, _('record a copy that has already occurred')),
1764 [('A', 'after', None, _('record a copy that has already occurred')),
1765 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1765 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1766 ] + walkopts + dryrunopts,
1766 ] + walkopts + dryrunopts,
1767 _('[OPTION]... [SOURCE]... DEST'))
1767 _('[OPTION]... [SOURCE]... DEST'))
1768 def copy(ui, repo, *pats, **opts):
1768 def copy(ui, repo, *pats, **opts):
1769 """mark files as copied for the next commit
1769 """mark files as copied for the next commit
1770
1770
1771 Mark dest as having copies of source files. If dest is a
1771 Mark dest as having copies of source files. If dest is a
1772 directory, copies are put in that directory. If dest is a file,
1772 directory, copies are put in that directory. If dest is a file,
1773 the source must be a single file.
1773 the source must be a single file.
1774
1774
1775 By default, this command copies the contents of files as they
1775 By default, this command copies the contents of files as they
1776 exist in the working directory. If invoked with -A/--after, the
1776 exist in the working directory. If invoked with -A/--after, the
1777 operation is recorded, but no copying is performed.
1777 operation is recorded, but no copying is performed.
1778
1778
1779 This command takes effect with the next commit. To undo a copy
1779 This command takes effect with the next commit. To undo a copy
1780 before that, see :hg:`revert`.
1780 before that, see :hg:`revert`.
1781
1781
1782 Returns 0 on success, 1 if errors are encountered.
1782 Returns 0 on success, 1 if errors are encountered.
1783 """
1783 """
1784 opts = pycompat.byteskwargs(opts)
1784 opts = pycompat.byteskwargs(opts)
1785 with repo.wlock(False):
1785 with repo.wlock(False):
1786 return cmdutil.copy(ui, repo, pats, opts)
1786 return cmdutil.copy(ui, repo, pats, opts)
1787
1787
1788 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1788 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1789 def debugcommands(ui, cmd='', *args):
1789 def debugcommands(ui, cmd='', *args):
1790 """list all available commands and options"""
1790 """list all available commands and options"""
1791 for cmd, vals in sorted(table.iteritems()):
1791 for cmd, vals in sorted(table.iteritems()):
1792 cmd = cmd.split('|')[0].strip('^')
1792 cmd = cmd.split('|')[0].strip('^')
1793 opts = ', '.join([i[1] for i in vals[1]])
1793 opts = ', '.join([i[1] for i in vals[1]])
1794 ui.write('%s: %s\n' % (cmd, opts))
1794 ui.write('%s: %s\n' % (cmd, opts))
1795
1795
1796 @command('debugcomplete',
1796 @command('debugcomplete',
1797 [('o', 'options', None, _('show the command options'))],
1797 [('o', 'options', None, _('show the command options'))],
1798 _('[-o] CMD'),
1798 _('[-o] CMD'),
1799 norepo=True)
1799 norepo=True)
1800 def debugcomplete(ui, cmd='', **opts):
1800 def debugcomplete(ui, cmd='', **opts):
1801 """returns the completion list associated with the given command"""
1801 """returns the completion list associated with the given command"""
1802
1802
1803 if opts.get(r'options'):
1803 if opts.get(r'options'):
1804 options = []
1804 options = []
1805 otables = [globalopts]
1805 otables = [globalopts]
1806 if cmd:
1806 if cmd:
1807 aliases, entry = cmdutil.findcmd(cmd, table, False)
1807 aliases, entry = cmdutil.findcmd(cmd, table, False)
1808 otables.append(entry[1])
1808 otables.append(entry[1])
1809 for t in otables:
1809 for t in otables:
1810 for o in t:
1810 for o in t:
1811 if "(DEPRECATED)" in o[3]:
1811 if "(DEPRECATED)" in o[3]:
1812 continue
1812 continue
1813 if o[0]:
1813 if o[0]:
1814 options.append('-%s' % o[0])
1814 options.append('-%s' % o[0])
1815 options.append('--%s' % o[1])
1815 options.append('--%s' % o[1])
1816 ui.write("%s\n" % "\n".join(options))
1816 ui.write("%s\n" % "\n".join(options))
1817 return
1817 return
1818
1818
1819 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1819 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1820 if ui.verbose:
1820 if ui.verbose:
1821 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1821 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1822 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1822 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1823
1823
1824 @command('^diff',
1824 @command('^diff',
1825 [('r', 'rev', [], _('revision'), _('REV')),
1825 [('r', 'rev', [], _('revision'), _('REV')),
1826 ('c', 'change', '', _('change made by revision'), _('REV'))
1826 ('c', 'change', '', _('change made by revision'), _('REV'))
1827 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1827 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1828 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1828 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1829 inferrepo=True,
1829 inferrepo=True,
1830 intents={INTENT_READONLY})
1830 intents={INTENT_READONLY})
1831 def diff(ui, repo, *pats, **opts):
1831 def diff(ui, repo, *pats, **opts):
1832 """diff repository (or selected files)
1832 """diff repository (or selected files)
1833
1833
1834 Show differences between revisions for the specified files.
1834 Show differences between revisions for the specified files.
1835
1835
1836 Differences between files are shown using the unified diff format.
1836 Differences between files are shown using the unified diff format.
1837
1837
1838 .. note::
1838 .. note::
1839
1839
1840 :hg:`diff` may generate unexpected results for merges, as it will
1840 :hg:`diff` may generate unexpected results for merges, as it will
1841 default to comparing against the working directory's first
1841 default to comparing against the working directory's first
1842 parent changeset if no revisions are specified.
1842 parent changeset if no revisions are specified.
1843
1843
1844 When two revision arguments are given, then changes are shown
1844 When two revision arguments are given, then changes are shown
1845 between those revisions. If only one revision is specified then
1845 between those revisions. If only one revision is specified then
1846 that revision is compared to the working directory, and, when no
1846 that revision is compared to the working directory, and, when no
1847 revisions are specified, the working directory files are compared
1847 revisions are specified, the working directory files are compared
1848 to its first parent.
1848 to its first parent.
1849
1849
1850 Alternatively you can specify -c/--change with a revision to see
1850 Alternatively you can specify -c/--change with a revision to see
1851 the changes in that changeset relative to its first parent.
1851 the changes in that changeset relative to its first parent.
1852
1852
1853 Without the -a/--text option, diff will avoid generating diffs of
1853 Without the -a/--text option, diff will avoid generating diffs of
1854 files it detects as binary. With -a, diff will generate a diff
1854 files it detects as binary. With -a, diff will generate a diff
1855 anyway, probably with undesirable results.
1855 anyway, probably with undesirable results.
1856
1856
1857 Use the -g/--git option to generate diffs in the git extended diff
1857 Use the -g/--git option to generate diffs in the git extended diff
1858 format. For more information, read :hg:`help diffs`.
1858 format. For more information, read :hg:`help diffs`.
1859
1859
1860 .. container:: verbose
1860 .. container:: verbose
1861
1861
1862 Examples:
1862 Examples:
1863
1863
1864 - compare a file in the current working directory to its parent::
1864 - compare a file in the current working directory to its parent::
1865
1865
1866 hg diff foo.c
1866 hg diff foo.c
1867
1867
1868 - compare two historical versions of a directory, with rename info::
1868 - compare two historical versions of a directory, with rename info::
1869
1869
1870 hg diff --git -r 1.0:1.2 lib/
1870 hg diff --git -r 1.0:1.2 lib/
1871
1871
1872 - get change stats relative to the last change on some date::
1872 - get change stats relative to the last change on some date::
1873
1873
1874 hg diff --stat -r "date('may 2')"
1874 hg diff --stat -r "date('may 2')"
1875
1875
1876 - diff all newly-added files that contain a keyword::
1876 - diff all newly-added files that contain a keyword::
1877
1877
1878 hg diff "set:added() and grep(GNU)"
1878 hg diff "set:added() and grep(GNU)"
1879
1879
1880 - compare a revision and its parents::
1880 - compare a revision and its parents::
1881
1881
1882 hg diff -c 9353 # compare against first parent
1882 hg diff -c 9353 # compare against first parent
1883 hg diff -r 9353^:9353 # same using revset syntax
1883 hg diff -r 9353^:9353 # same using revset syntax
1884 hg diff -r 9353^2:9353 # compare against the second parent
1884 hg diff -r 9353^2:9353 # compare against the second parent
1885
1885
1886 Returns 0 on success.
1886 Returns 0 on success.
1887 """
1887 """
1888
1888
1889 opts = pycompat.byteskwargs(opts)
1889 opts = pycompat.byteskwargs(opts)
1890 revs = opts.get('rev')
1890 revs = opts.get('rev')
1891 change = opts.get('change')
1891 change = opts.get('change')
1892 stat = opts.get('stat')
1892 stat = opts.get('stat')
1893 reverse = opts.get('reverse')
1893 reverse = opts.get('reverse')
1894
1894
1895 if revs and change:
1895 if revs and change:
1896 msg = _('cannot specify --rev and --change at the same time')
1896 msg = _('cannot specify --rev and --change at the same time')
1897 raise error.Abort(msg)
1897 raise error.Abort(msg)
1898 elif change:
1898 elif change:
1899 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
1899 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
1900 ctx2 = scmutil.revsingle(repo, change, None)
1900 ctx2 = scmutil.revsingle(repo, change, None)
1901 ctx1 = ctx2.p1()
1901 ctx1 = ctx2.p1()
1902 else:
1902 else:
1903 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
1903 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
1904 ctx1, ctx2 = scmutil.revpair(repo, revs)
1904 ctx1, ctx2 = scmutil.revpair(repo, revs)
1905 node1, node2 = ctx1.node(), ctx2.node()
1905 node1, node2 = ctx1.node(), ctx2.node()
1906
1906
1907 if reverse:
1907 if reverse:
1908 node1, node2 = node2, node1
1908 node1, node2 = node2, node1
1909
1909
1910 diffopts = patch.diffallopts(ui, opts)
1910 diffopts = patch.diffallopts(ui, opts)
1911 m = scmutil.match(ctx2, pats, opts)
1911 m = scmutil.match(ctx2, pats, opts)
1912 ui.pager('diff')
1912 ui.pager('diff')
1913 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1913 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1914 listsubrepos=opts.get('subrepos'),
1914 listsubrepos=opts.get('subrepos'),
1915 root=opts.get('root'))
1915 root=opts.get('root'))
1916
1916
1917 @command('^export',
1917 @command('^export',
1918 [('B', 'bookmark', '',
1918 [('B', 'bookmark', '',
1919 _('export changes only reachable by given bookmark')),
1919 _('export changes only reachable by given bookmark')),
1920 ('o', 'output', '',
1920 ('o', 'output', '',
1921 _('print output to file with formatted name'), _('FORMAT')),
1921 _('print output to file with formatted name'), _('FORMAT')),
1922 ('', 'switch-parent', None, _('diff against the second parent')),
1922 ('', 'switch-parent', None, _('diff against the second parent')),
1923 ('r', 'rev', [], _('revisions to export'), _('REV')),
1923 ('r', 'rev', [], _('revisions to export'), _('REV')),
1924 ] + diffopts + formatteropts,
1924 ] + diffopts + formatteropts,
1925 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
1925 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
1926 intents={INTENT_READONLY})
1926 intents={INTENT_READONLY})
1927 def export(ui, repo, *changesets, **opts):
1927 def export(ui, repo, *changesets, **opts):
1928 """dump the header and diffs for one or more changesets
1928 """dump the header and diffs for one or more changesets
1929
1929
1930 Print the changeset header and diffs for one or more revisions.
1930 Print the changeset header and diffs for one or more revisions.
1931 If no revision is given, the parent of the working directory is used.
1931 If no revision is given, the parent of the working directory is used.
1932
1932
1933 The information shown in the changeset header is: author, date,
1933 The information shown in the changeset header is: author, date,
1934 branch name (if non-default), changeset hash, parent(s) and commit
1934 branch name (if non-default), changeset hash, parent(s) and commit
1935 comment.
1935 comment.
1936
1936
1937 .. note::
1937 .. note::
1938
1938
1939 :hg:`export` may generate unexpected diff output for merge
1939 :hg:`export` may generate unexpected diff output for merge
1940 changesets, as it will compare the merge changeset against its
1940 changesets, as it will compare the merge changeset against its
1941 first parent only.
1941 first parent only.
1942
1942
1943 Output may be to a file, in which case the name of the file is
1943 Output may be to a file, in which case the name of the file is
1944 given using a template string. See :hg:`help templates`. In addition
1944 given using a template string. See :hg:`help templates`. In addition
1945 to the common template keywords, the following formatting rules are
1945 to the common template keywords, the following formatting rules are
1946 supported:
1946 supported:
1947
1947
1948 :``%%``: literal "%" character
1948 :``%%``: literal "%" character
1949 :``%H``: changeset hash (40 hexadecimal digits)
1949 :``%H``: changeset hash (40 hexadecimal digits)
1950 :``%N``: number of patches being generated
1950 :``%N``: number of patches being generated
1951 :``%R``: changeset revision number
1951 :``%R``: changeset revision number
1952 :``%b``: basename of the exporting repository
1952 :``%b``: basename of the exporting repository
1953 :``%h``: short-form changeset hash (12 hexadecimal digits)
1953 :``%h``: short-form changeset hash (12 hexadecimal digits)
1954 :``%m``: first line of the commit message (only alphanumeric characters)
1954 :``%m``: first line of the commit message (only alphanumeric characters)
1955 :``%n``: zero-padded sequence number, starting at 1
1955 :``%n``: zero-padded sequence number, starting at 1
1956 :``%r``: zero-padded changeset revision number
1956 :``%r``: zero-padded changeset revision number
1957 :``\\``: literal "\\" character
1957 :``\\``: literal "\\" character
1958
1958
1959 Without the -a/--text option, export will avoid generating diffs
1959 Without the -a/--text option, export will avoid generating diffs
1960 of files it detects as binary. With -a, export will generate a
1960 of files it detects as binary. With -a, export will generate a
1961 diff anyway, probably with undesirable results.
1961 diff anyway, probably with undesirable results.
1962
1962
1963 With -B/--bookmark changesets reachable by the given bookmark are
1963 With -B/--bookmark changesets reachable by the given bookmark are
1964 selected.
1964 selected.
1965
1965
1966 Use the -g/--git option to generate diffs in the git extended diff
1966 Use the -g/--git option to generate diffs in the git extended diff
1967 format. See :hg:`help diffs` for more information.
1967 format. See :hg:`help diffs` for more information.
1968
1968
1969 With the --switch-parent option, the diff will be against the
1969 With the --switch-parent option, the diff will be against the
1970 second parent. It can be useful to review a merge.
1970 second parent. It can be useful to review a merge.
1971
1971
1972 .. container:: verbose
1972 .. container:: verbose
1973
1973
1974 Examples:
1974 Examples:
1975
1975
1976 - use export and import to transplant a bugfix to the current
1976 - use export and import to transplant a bugfix to the current
1977 branch::
1977 branch::
1978
1978
1979 hg export -r 9353 | hg import -
1979 hg export -r 9353 | hg import -
1980
1980
1981 - export all the changesets between two revisions to a file with
1981 - export all the changesets between two revisions to a file with
1982 rename information::
1982 rename information::
1983
1983
1984 hg export --git -r 123:150 > changes.txt
1984 hg export --git -r 123:150 > changes.txt
1985
1985
1986 - split outgoing changes into a series of patches with
1986 - split outgoing changes into a series of patches with
1987 descriptive names::
1987 descriptive names::
1988
1988
1989 hg export -r "outgoing()" -o "%n-%m.patch"
1989 hg export -r "outgoing()" -o "%n-%m.patch"
1990
1990
1991 Returns 0 on success.
1991 Returns 0 on success.
1992 """
1992 """
1993 opts = pycompat.byteskwargs(opts)
1993 opts = pycompat.byteskwargs(opts)
1994 bookmark = opts.get('bookmark')
1994 bookmark = opts.get('bookmark')
1995 changesets += tuple(opts.get('rev', []))
1995 changesets += tuple(opts.get('rev', []))
1996
1996
1997 if bookmark and changesets:
1997 if bookmark and changesets:
1998 raise error.Abort(_("-r and -B are mutually exclusive"))
1998 raise error.Abort(_("-r and -B are mutually exclusive"))
1999
1999
2000 if bookmark:
2000 if bookmark:
2001 if bookmark not in repo._bookmarks:
2001 if bookmark not in repo._bookmarks:
2002 raise error.Abort(_("bookmark '%s' not found") % bookmark)
2002 raise error.Abort(_("bookmark '%s' not found") % bookmark)
2003
2003
2004 revs = scmutil.bookmarkrevs(repo, bookmark)
2004 revs = scmutil.bookmarkrevs(repo, bookmark)
2005 else:
2005 else:
2006 if not changesets:
2006 if not changesets:
2007 changesets = ['.']
2007 changesets = ['.']
2008
2008
2009 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
2009 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
2010 revs = scmutil.revrange(repo, changesets)
2010 revs = scmutil.revrange(repo, changesets)
2011
2011
2012 if not revs:
2012 if not revs:
2013 raise error.Abort(_("export requires at least one changeset"))
2013 raise error.Abort(_("export requires at least one changeset"))
2014 if len(revs) > 1:
2014 if len(revs) > 1:
2015 ui.note(_('exporting patches:\n'))
2015 ui.note(_('exporting patches:\n'))
2016 else:
2016 else:
2017 ui.note(_('exporting patch:\n'))
2017 ui.note(_('exporting patch:\n'))
2018
2018
2019 fntemplate = opts.get('output')
2019 fntemplate = opts.get('output')
2020 if cmdutil.isstdiofilename(fntemplate):
2020 if cmdutil.isstdiofilename(fntemplate):
2021 fntemplate = ''
2021 fntemplate = ''
2022
2022
2023 if fntemplate:
2023 if fntemplate:
2024 fm = formatter.nullformatter(ui, 'export', opts)
2024 fm = formatter.nullformatter(ui, 'export', opts)
2025 else:
2025 else:
2026 ui.pager('export')
2026 ui.pager('export')
2027 fm = ui.formatter('export', opts)
2027 fm = ui.formatter('export', opts)
2028 with fm:
2028 with fm:
2029 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
2029 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
2030 switch_parent=opts.get('switch_parent'),
2030 switch_parent=opts.get('switch_parent'),
2031 opts=patch.diffallopts(ui, opts))
2031 opts=patch.diffallopts(ui, opts))
2032
2032
2033 @command('files',
2033 @command('files',
2034 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2034 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2035 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2035 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2036 ] + walkopts + formatteropts + subrepoopts,
2036 ] + walkopts + formatteropts + subrepoopts,
2037 _('[OPTION]... [FILE]...'),
2037 _('[OPTION]... [FILE]...'),
2038 intents={INTENT_READONLY})
2038 intents={INTENT_READONLY})
2039 def files(ui, repo, *pats, **opts):
2039 def files(ui, repo, *pats, **opts):
2040 """list tracked files
2040 """list tracked files
2041
2041
2042 Print files under Mercurial control in the working directory or
2042 Print files under Mercurial control in the working directory or
2043 specified revision for given files (excluding removed files).
2043 specified revision for given files (excluding removed files).
2044 Files can be specified as filenames or filesets.
2044 Files can be specified as filenames or filesets.
2045
2045
2046 If no files are given to match, this command prints the names
2046 If no files are given to match, this command prints the names
2047 of all files under Mercurial control.
2047 of all files under Mercurial control.
2048
2048
2049 .. container:: verbose
2049 .. container:: verbose
2050
2050
2051 Examples:
2051 Examples:
2052
2052
2053 - list all files under the current directory::
2053 - list all files under the current directory::
2054
2054
2055 hg files .
2055 hg files .
2056
2056
2057 - shows sizes and flags for current revision::
2057 - shows sizes and flags for current revision::
2058
2058
2059 hg files -vr .
2059 hg files -vr .
2060
2060
2061 - list all files named README::
2061 - list all files named README::
2062
2062
2063 hg files -I "**/README"
2063 hg files -I "**/README"
2064
2064
2065 - list all binary files::
2065 - list all binary files::
2066
2066
2067 hg files "set:binary()"
2067 hg files "set:binary()"
2068
2068
2069 - find files containing a regular expression::
2069 - find files containing a regular expression::
2070
2070
2071 hg files "set:grep('bob')"
2071 hg files "set:grep('bob')"
2072
2072
2073 - search tracked file contents with xargs and grep::
2073 - search tracked file contents with xargs and grep::
2074
2074
2075 hg files -0 | xargs -0 grep foo
2075 hg files -0 | xargs -0 grep foo
2076
2076
2077 See :hg:`help patterns` and :hg:`help filesets` for more information
2077 See :hg:`help patterns` and :hg:`help filesets` for more information
2078 on specifying file patterns.
2078 on specifying file patterns.
2079
2079
2080 Returns 0 if a match is found, 1 otherwise.
2080 Returns 0 if a match is found, 1 otherwise.
2081
2081
2082 """
2082 """
2083
2083
2084 opts = pycompat.byteskwargs(opts)
2084 opts = pycompat.byteskwargs(opts)
2085 rev = opts.get('rev')
2085 rev = opts.get('rev')
2086 if rev:
2086 if rev:
2087 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2087 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2088 ctx = scmutil.revsingle(repo, rev, None)
2088 ctx = scmutil.revsingle(repo, rev, None)
2089
2089
2090 end = '\n'
2090 end = '\n'
2091 if opts.get('print0'):
2091 if opts.get('print0'):
2092 end = '\0'
2092 end = '\0'
2093 fmt = '%s' + end
2093 fmt = '%s' + end
2094
2094
2095 m = scmutil.match(ctx, pats, opts)
2095 m = scmutil.match(ctx, pats, opts)
2096 ui.pager('files')
2096 ui.pager('files')
2097 with ui.formatter('files', opts) as fm:
2097 with ui.formatter('files', opts) as fm:
2098 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2098 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2099
2099
2100 @command(
2100 @command(
2101 '^forget',
2101 '^forget',
2102 [('i', 'interactive', None, _('use interactive mode')),
2102 [('i', 'interactive', None, _('use interactive mode')),
2103 ] + walkopts + dryrunopts,
2103 ] + walkopts + dryrunopts,
2104 _('[OPTION]... FILE...'), inferrepo=True)
2104 _('[OPTION]... FILE...'), inferrepo=True)
2105 def forget(ui, repo, *pats, **opts):
2105 def forget(ui, repo, *pats, **opts):
2106 """forget the specified files on the next commit
2106 """forget the specified files on the next commit
2107
2107
2108 Mark the specified files so they will no longer be tracked
2108 Mark the specified files so they will no longer be tracked
2109 after the next commit.
2109 after the next commit.
2110
2110
2111 This only removes files from the current branch, not from the
2111 This only removes files from the current branch, not from the
2112 entire project history, and it does not delete them from the
2112 entire project history, and it does not delete them from the
2113 working directory.
2113 working directory.
2114
2114
2115 To delete the file from the working directory, see :hg:`remove`.
2115 To delete the file from the working directory, see :hg:`remove`.
2116
2116
2117 To undo a forget before the next commit, see :hg:`add`.
2117 To undo a forget before the next commit, see :hg:`add`.
2118
2118
2119 .. container:: verbose
2119 .. container:: verbose
2120
2120
2121 Examples:
2121 Examples:
2122
2122
2123 - forget newly-added binary files::
2123 - forget newly-added binary files::
2124
2124
2125 hg forget "set:added() and binary()"
2125 hg forget "set:added() and binary()"
2126
2126
2127 - forget files that would be excluded by .hgignore::
2127 - forget files that would be excluded by .hgignore::
2128
2128
2129 hg forget "set:hgignore()"
2129 hg forget "set:hgignore()"
2130
2130
2131 Returns 0 on success.
2131 Returns 0 on success.
2132 """
2132 """
2133
2133
2134 opts = pycompat.byteskwargs(opts)
2134 opts = pycompat.byteskwargs(opts)
2135 if not pats:
2135 if not pats:
2136 raise error.Abort(_('no files specified'))
2136 raise error.Abort(_('no files specified'))
2137
2137
2138 m = scmutil.match(repo[None], pats, opts)
2138 m = scmutil.match(repo[None], pats, opts)
2139 dryrun, interactive = opts.get('dry_run'), opts.get('interactive')
2139 dryrun, interactive = opts.get('dry_run'), opts.get('interactive')
2140 rejected = cmdutil.forget(ui, repo, m, prefix="",
2140 rejected = cmdutil.forget(ui, repo, m, prefix="",
2141 explicitonly=False, dryrun=dryrun,
2141 explicitonly=False, dryrun=dryrun,
2142 interactive=interactive)[0]
2142 interactive=interactive)[0]
2143 return rejected and 1 or 0
2143 return rejected and 1 or 0
2144
2144
2145 @command(
2145 @command(
2146 'graft',
2146 'graft',
2147 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2147 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2148 ('c', 'continue', False, _('resume interrupted graft')),
2148 ('c', 'continue', False, _('resume interrupted graft')),
2149 ('', 'stop', False, _('stop interrupted graft')),
2149 ('', 'stop', False, _('stop interrupted graft')),
2150 ('', 'abort', False, _('abort interrupted graft')),
2150 ('', 'abort', False, _('abort interrupted graft')),
2151 ('e', 'edit', False, _('invoke editor on commit messages')),
2151 ('e', 'edit', False, _('invoke editor on commit messages')),
2152 ('', 'log', None, _('append graft info to log message')),
2152 ('', 'log', None, _('append graft info to log message')),
2153 ('', 'no-commit', None,
2153 ('', 'no-commit', None,
2154 _("don't commit, just apply the changes in working directory")),
2154 _("don't commit, just apply the changes in working directory")),
2155 ('f', 'force', False, _('force graft')),
2155 ('f', 'force', False, _('force graft')),
2156 ('D', 'currentdate', False,
2156 ('D', 'currentdate', False,
2157 _('record the current date as commit date')),
2157 _('record the current date as commit date')),
2158 ('U', 'currentuser', False,
2158 ('U', 'currentuser', False,
2159 _('record the current user as committer'), _('DATE'))]
2159 _('record the current user as committer'), _('DATE'))]
2160 + commitopts2 + mergetoolopts + dryrunopts,
2160 + commitopts2 + mergetoolopts + dryrunopts,
2161 _('[OPTION]... [-r REV]... REV...'))
2161 _('[OPTION]... [-r REV]... REV...'))
2162 def graft(ui, repo, *revs, **opts):
2162 def graft(ui, repo, *revs, **opts):
2163 '''copy changes from other branches onto the current branch
2163 '''copy changes from other branches onto the current branch
2164
2164
2165 This command uses Mercurial's merge logic to copy individual
2165 This command uses Mercurial's merge logic to copy individual
2166 changes from other branches without merging branches in the
2166 changes from other branches without merging branches in the
2167 history graph. This is sometimes known as 'backporting' or
2167 history graph. This is sometimes known as 'backporting' or
2168 'cherry-picking'. By default, graft will copy user, date, and
2168 'cherry-picking'. By default, graft will copy user, date, and
2169 description from the source changesets.
2169 description from the source changesets.
2170
2170
2171 Changesets that are ancestors of the current revision, that have
2171 Changesets that are ancestors of the current revision, that have
2172 already been grafted, or that are merges will be skipped.
2172 already been grafted, or that are merges will be skipped.
2173
2173
2174 If --log is specified, log messages will have a comment appended
2174 If --log is specified, log messages will have a comment appended
2175 of the form::
2175 of the form::
2176
2176
2177 (grafted from CHANGESETHASH)
2177 (grafted from CHANGESETHASH)
2178
2178
2179 If --force is specified, revisions will be grafted even if they
2179 If --force is specified, revisions will be grafted even if they
2180 are already ancestors of, or have been grafted to, the destination.
2180 are already ancestors of, or have been grafted to, the destination.
2181 This is useful when the revisions have since been backed out.
2181 This is useful when the revisions have since been backed out.
2182
2182
2183 If a graft merge results in conflicts, the graft process is
2183 If a graft merge results in conflicts, the graft process is
2184 interrupted so that the current merge can be manually resolved.
2184 interrupted so that the current merge can be manually resolved.
2185 Once all conflicts are addressed, the graft process can be
2185 Once all conflicts are addressed, the graft process can be
2186 continued with the -c/--continue option.
2186 continued with the -c/--continue option.
2187
2187
2188 The -c/--continue option reapplies all the earlier options.
2188 The -c/--continue option reapplies all the earlier options.
2189
2189
2190 .. container:: verbose
2190 .. container:: verbose
2191
2191
2192 Examples:
2192 Examples:
2193
2193
2194 - copy a single change to the stable branch and edit its description::
2194 - copy a single change to the stable branch and edit its description::
2195
2195
2196 hg update stable
2196 hg update stable
2197 hg graft --edit 9393
2197 hg graft --edit 9393
2198
2198
2199 - graft a range of changesets with one exception, updating dates::
2199 - graft a range of changesets with one exception, updating dates::
2200
2200
2201 hg graft -D "2085::2093 and not 2091"
2201 hg graft -D "2085::2093 and not 2091"
2202
2202
2203 - continue a graft after resolving conflicts::
2203 - continue a graft after resolving conflicts::
2204
2204
2205 hg graft -c
2205 hg graft -c
2206
2206
2207 - show the source of a grafted changeset::
2207 - show the source of a grafted changeset::
2208
2208
2209 hg log --debug -r .
2209 hg log --debug -r .
2210
2210
2211 - show revisions sorted by date::
2211 - show revisions sorted by date::
2212
2212
2213 hg log -r "sort(all(), date)"
2213 hg log -r "sort(all(), date)"
2214
2214
2215 See :hg:`help revisions` for more about specifying revisions.
2215 See :hg:`help revisions` for more about specifying revisions.
2216
2216
2217 Returns 0 on successful completion.
2217 Returns 0 on successful completion.
2218 '''
2218 '''
2219 with repo.wlock():
2219 with repo.wlock():
2220 return _dograft(ui, repo, *revs, **opts)
2220 return _dograft(ui, repo, *revs, **opts)
2221
2221
2222 def _dograft(ui, repo, *revs, **opts):
2222 def _dograft(ui, repo, *revs, **opts):
2223 opts = pycompat.byteskwargs(opts)
2223 opts = pycompat.byteskwargs(opts)
2224 if revs and opts.get('rev'):
2224 if revs and opts.get('rev'):
2225 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2225 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2226 'revision ordering!\n'))
2226 'revision ordering!\n'))
2227
2227
2228 revs = list(revs)
2228 revs = list(revs)
2229 revs.extend(opts.get('rev'))
2229 revs.extend(opts.get('rev'))
2230 # a dict of data to be stored in state file
2230 # a dict of data to be stored in state file
2231 statedata = {}
2231 statedata = {}
2232 # list of new nodes created by ongoing graft
2232 # list of new nodes created by ongoing graft
2233 statedata['newnodes'] = []
2233 statedata['newnodes'] = []
2234
2234
2235 if not opts.get('user') and opts.get('currentuser'):
2235 if not opts.get('user') and opts.get('currentuser'):
2236 opts['user'] = ui.username()
2236 opts['user'] = ui.username()
2237 if not opts.get('date') and opts.get('currentdate'):
2237 if not opts.get('date') and opts.get('currentdate'):
2238 opts['date'] = "%d %d" % dateutil.makedate()
2238 opts['date'] = "%d %d" % dateutil.makedate()
2239
2239
2240 editor = cmdutil.getcommiteditor(editform='graft',
2240 editor = cmdutil.getcommiteditor(editform='graft',
2241 **pycompat.strkwargs(opts))
2241 **pycompat.strkwargs(opts))
2242
2242
2243 cont = False
2243 cont = False
2244 if opts.get('no_commit'):
2244 if opts.get('no_commit'):
2245 if opts.get('edit'):
2245 if opts.get('edit'):
2246 raise error.Abort(_("cannot specify --no-commit and "
2246 raise error.Abort(_("cannot specify --no-commit and "
2247 "--edit together"))
2247 "--edit together"))
2248 if opts.get('currentuser'):
2248 if opts.get('currentuser'):
2249 raise error.Abort(_("cannot specify --no-commit and "
2249 raise error.Abort(_("cannot specify --no-commit and "
2250 "--currentuser together"))
2250 "--currentuser together"))
2251 if opts.get('currentdate'):
2251 if opts.get('currentdate'):
2252 raise error.Abort(_("cannot specify --no-commit and "
2252 raise error.Abort(_("cannot specify --no-commit and "
2253 "--currentdate together"))
2253 "--currentdate together"))
2254 if opts.get('log'):
2254 if opts.get('log'):
2255 raise error.Abort(_("cannot specify --no-commit and "
2255 raise error.Abort(_("cannot specify --no-commit and "
2256 "--log together"))
2256 "--log together"))
2257
2257
2258 graftstate = statemod.cmdstate(repo, 'graftstate')
2258 graftstate = statemod.cmdstate(repo, 'graftstate')
2259
2259
2260 if opts.get('stop'):
2260 if opts.get('stop'):
2261 if opts.get('continue'):
2261 if opts.get('continue'):
2262 raise error.Abort(_("cannot use '--continue' and "
2262 raise error.Abort(_("cannot use '--continue' and "
2263 "'--stop' together"))
2263 "'--stop' together"))
2264 if opts.get('abort'):
2264 if opts.get('abort'):
2265 raise error.Abort(_("cannot use '--abort' and '--stop' together"))
2265 raise error.Abort(_("cannot use '--abort' and '--stop' together"))
2266
2266
2267 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2267 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2268 opts.get('date'), opts.get('currentdate'),
2268 opts.get('date'), opts.get('currentdate'),
2269 opts.get('currentuser'), opts.get('rev'))):
2269 opts.get('currentuser'), opts.get('rev'))):
2270 raise error.Abort(_("cannot specify any other flag with '--stop'"))
2270 raise error.Abort(_("cannot specify any other flag with '--stop'"))
2271 return _stopgraft(ui, repo, graftstate)
2271 return _stopgraft(ui, repo, graftstate)
2272 elif opts.get('abort'):
2272 elif opts.get('abort'):
2273 if opts.get('continue'):
2273 if opts.get('continue'):
2274 raise error.Abort(_("cannot use '--continue' and "
2274 raise error.Abort(_("cannot use '--continue' and "
2275 "'--abort' together"))
2275 "'--abort' together"))
2276 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2276 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2277 opts.get('date'), opts.get('currentdate'),
2277 opts.get('date'), opts.get('currentdate'),
2278 opts.get('currentuser'), opts.get('rev'))):
2278 opts.get('currentuser'), opts.get('rev'))):
2279 raise error.Abort(_("cannot specify any other flag with '--abort'"))
2279 raise error.Abort(_("cannot specify any other flag with '--abort'"))
2280
2280
2281 return _abortgraft(ui, repo, graftstate)
2281 return _abortgraft(ui, repo, graftstate)
2282 elif opts.get('continue'):
2282 elif opts.get('continue'):
2283 cont = True
2283 cont = True
2284 if revs:
2284 if revs:
2285 raise error.Abort(_("can't specify --continue and revisions"))
2285 raise error.Abort(_("can't specify --continue and revisions"))
2286 # read in unfinished revisions
2286 # read in unfinished revisions
2287 if graftstate.exists():
2287 if graftstate.exists():
2288 statedata = _readgraftstate(repo, graftstate)
2288 statedata = _readgraftstate(repo, graftstate)
2289 if statedata.get('date'):
2289 if statedata.get('date'):
2290 opts['date'] = statedata['date']
2290 opts['date'] = statedata['date']
2291 if statedata.get('user'):
2291 if statedata.get('user'):
2292 opts['user'] = statedata['user']
2292 opts['user'] = statedata['user']
2293 if statedata.get('log'):
2293 if statedata.get('log'):
2294 opts['log'] = True
2294 opts['log'] = True
2295 if statedata.get('no_commit'):
2295 if statedata.get('no_commit'):
2296 opts['no_commit'] = statedata.get('no_commit')
2296 opts['no_commit'] = statedata.get('no_commit')
2297 nodes = statedata['nodes']
2297 nodes = statedata['nodes']
2298 revs = [repo[node].rev() for node in nodes]
2298 revs = [repo[node].rev() for node in nodes]
2299 else:
2299 else:
2300 cmdutil.wrongtooltocontinue(repo, _('graft'))
2300 cmdutil.wrongtooltocontinue(repo, _('graft'))
2301 else:
2301 else:
2302 if not revs:
2302 if not revs:
2303 raise error.Abort(_('no revisions specified'))
2303 raise error.Abort(_('no revisions specified'))
2304 cmdutil.checkunfinished(repo)
2304 cmdutil.checkunfinished(repo)
2305 cmdutil.bailifchanged(repo)
2305 cmdutil.bailifchanged(repo)
2306 revs = scmutil.revrange(repo, revs)
2306 revs = scmutil.revrange(repo, revs)
2307
2307
2308 skipped = set()
2308 skipped = set()
2309 # check for merges
2309 # check for merges
2310 for rev in repo.revs('%ld and merge()', revs):
2310 for rev in repo.revs('%ld and merge()', revs):
2311 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2311 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2312 skipped.add(rev)
2312 skipped.add(rev)
2313 revs = [r for r in revs if r not in skipped]
2313 revs = [r for r in revs if r not in skipped]
2314 if not revs:
2314 if not revs:
2315 return -1
2315 return -1
2316
2316
2317 # Don't check in the --continue case, in effect retaining --force across
2317 # Don't check in the --continue case, in effect retaining --force across
2318 # --continues. That's because without --force, any revisions we decided to
2318 # --continues. That's because without --force, any revisions we decided to
2319 # skip would have been filtered out here, so they wouldn't have made their
2319 # skip would have been filtered out here, so they wouldn't have made their
2320 # way to the graftstate. With --force, any revisions we would have otherwise
2320 # way to the graftstate. With --force, any revisions we would have otherwise
2321 # skipped would not have been filtered out, and if they hadn't been applied
2321 # skipped would not have been filtered out, and if they hadn't been applied
2322 # already, they'd have been in the graftstate.
2322 # already, they'd have been in the graftstate.
2323 if not (cont or opts.get('force')):
2323 if not (cont or opts.get('force')):
2324 # check for ancestors of dest branch
2324 # check for ancestors of dest branch
2325 crev = repo['.'].rev()
2325 crev = repo['.'].rev()
2326 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2326 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2327 # XXX make this lazy in the future
2327 # XXX make this lazy in the future
2328 # don't mutate while iterating, create a copy
2328 # don't mutate while iterating, create a copy
2329 for rev in list(revs):
2329 for rev in list(revs):
2330 if rev in ancestors:
2330 if rev in ancestors:
2331 ui.warn(_('skipping ancestor revision %d:%s\n') %
2331 ui.warn(_('skipping ancestor revision %d:%s\n') %
2332 (rev, repo[rev]))
2332 (rev, repo[rev]))
2333 # XXX remove on list is slow
2333 # XXX remove on list is slow
2334 revs.remove(rev)
2334 revs.remove(rev)
2335 if not revs:
2335 if not revs:
2336 return -1
2336 return -1
2337
2337
2338 # analyze revs for earlier grafts
2338 # analyze revs for earlier grafts
2339 ids = {}
2339 ids = {}
2340 for ctx in repo.set("%ld", revs):
2340 for ctx in repo.set("%ld", revs):
2341 ids[ctx.hex()] = ctx.rev()
2341 ids[ctx.hex()] = ctx.rev()
2342 n = ctx.extra().get('source')
2342 n = ctx.extra().get('source')
2343 if n:
2343 if n:
2344 ids[n] = ctx.rev()
2344 ids[n] = ctx.rev()
2345
2345
2346 # check ancestors for earlier grafts
2346 # check ancestors for earlier grafts
2347 ui.debug('scanning for duplicate grafts\n')
2347 ui.debug('scanning for duplicate grafts\n')
2348
2348
2349 # The only changesets we can be sure doesn't contain grafts of any
2349 # The only changesets we can be sure doesn't contain grafts of any
2350 # revs, are the ones that are common ancestors of *all* revs:
2350 # revs, are the ones that are common ancestors of *all* revs:
2351 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2351 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2352 ctx = repo[rev]
2352 ctx = repo[rev]
2353 n = ctx.extra().get('source')
2353 n = ctx.extra().get('source')
2354 if n in ids:
2354 if n in ids:
2355 try:
2355 try:
2356 r = repo[n].rev()
2356 r = repo[n].rev()
2357 except error.RepoLookupError:
2357 except error.RepoLookupError:
2358 r = None
2358 r = None
2359 if r in revs:
2359 if r in revs:
2360 ui.warn(_('skipping revision %d:%s '
2360 ui.warn(_('skipping revision %d:%s '
2361 '(already grafted to %d:%s)\n')
2361 '(already grafted to %d:%s)\n')
2362 % (r, repo[r], rev, ctx))
2362 % (r, repo[r], rev, ctx))
2363 revs.remove(r)
2363 revs.remove(r)
2364 elif ids[n] in revs:
2364 elif ids[n] in revs:
2365 if r is None:
2365 if r is None:
2366 ui.warn(_('skipping already grafted revision %d:%s '
2366 ui.warn(_('skipping already grafted revision %d:%s '
2367 '(%d:%s also has unknown origin %s)\n')
2367 '(%d:%s also has unknown origin %s)\n')
2368 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2368 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2369 else:
2369 else:
2370 ui.warn(_('skipping already grafted revision %d:%s '
2370 ui.warn(_('skipping already grafted revision %d:%s '
2371 '(%d:%s also has origin %d:%s)\n')
2371 '(%d:%s also has origin %d:%s)\n')
2372 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2372 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2373 revs.remove(ids[n])
2373 revs.remove(ids[n])
2374 elif ctx.hex() in ids:
2374 elif ctx.hex() in ids:
2375 r = ids[ctx.hex()]
2375 r = ids[ctx.hex()]
2376 ui.warn(_('skipping already grafted revision %d:%s '
2376 ui.warn(_('skipping already grafted revision %d:%s '
2377 '(was grafted from %d:%s)\n') %
2377 '(was grafted from %d:%s)\n') %
2378 (r, repo[r], rev, ctx))
2378 (r, repo[r], rev, ctx))
2379 revs.remove(r)
2379 revs.remove(r)
2380 if not revs:
2380 if not revs:
2381 return -1
2381 return -1
2382
2382
2383 if opts.get('no_commit'):
2383 if opts.get('no_commit'):
2384 statedata['no_commit'] = True
2384 statedata['no_commit'] = True
2385 for pos, ctx in enumerate(repo.set("%ld", revs)):
2385 for pos, ctx in enumerate(repo.set("%ld", revs)):
2386 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2386 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2387 ctx.description().split('\n', 1)[0])
2387 ctx.description().split('\n', 1)[0])
2388 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2388 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2389 if names:
2389 if names:
2390 desc += ' (%s)' % ' '.join(names)
2390 desc += ' (%s)' % ' '.join(names)
2391 ui.status(_('grafting %s\n') % desc)
2391 ui.status(_('grafting %s\n') % desc)
2392 if opts.get('dry_run'):
2392 if opts.get('dry_run'):
2393 continue
2393 continue
2394
2394
2395 source = ctx.extra().get('source')
2395 source = ctx.extra().get('source')
2396 extra = {}
2396 extra = {}
2397 if source:
2397 if source:
2398 extra['source'] = source
2398 extra['source'] = source
2399 extra['intermediate-source'] = ctx.hex()
2399 extra['intermediate-source'] = ctx.hex()
2400 else:
2400 else:
2401 extra['source'] = ctx.hex()
2401 extra['source'] = ctx.hex()
2402 user = ctx.user()
2402 user = ctx.user()
2403 if opts.get('user'):
2403 if opts.get('user'):
2404 user = opts['user']
2404 user = opts['user']
2405 statedata['user'] = user
2405 statedata['user'] = user
2406 date = ctx.date()
2406 date = ctx.date()
2407 if opts.get('date'):
2407 if opts.get('date'):
2408 date = opts['date']
2408 date = opts['date']
2409 statedata['date'] = date
2409 statedata['date'] = date
2410 message = ctx.description()
2410 message = ctx.description()
2411 if opts.get('log'):
2411 if opts.get('log'):
2412 message += '\n(grafted from %s)' % ctx.hex()
2412 message += '\n(grafted from %s)' % ctx.hex()
2413 statedata['log'] = True
2413 statedata['log'] = True
2414
2414
2415 # we don't merge the first commit when continuing
2415 # we don't merge the first commit when continuing
2416 if not cont:
2416 if not cont:
2417 # perform the graft merge with p1(rev) as 'ancestor'
2417 # perform the graft merge with p1(rev) as 'ancestor'
2418 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
2418 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
2419 with ui.configoverride(overrides, 'graft'):
2419 with ui.configoverride(overrides, 'graft'):
2420 stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'graft'])
2420 stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'graft'])
2421 # report any conflicts
2421 # report any conflicts
2422 if stats.unresolvedcount > 0:
2422 if stats.unresolvedcount > 0:
2423 # write out state for --continue
2423 # write out state for --continue
2424 nodes = [repo[rev].hex() for rev in revs[pos:]]
2424 nodes = [repo[rev].hex() for rev in revs[pos:]]
2425 statedata['nodes'] = nodes
2425 statedata['nodes'] = nodes
2426 stateversion = 1
2426 stateversion = 1
2427 graftstate.save(stateversion, statedata)
2427 graftstate.save(stateversion, statedata)
2428 hint = _("use 'hg resolve' and 'hg graft --continue'")
2428 hint = _("use 'hg resolve' and 'hg graft --continue'")
2429 raise error.Abort(
2429 raise error.Abort(
2430 _("unresolved conflicts, can't continue"),
2430 _("unresolved conflicts, can't continue"),
2431 hint=hint)
2431 hint=hint)
2432 else:
2432 else:
2433 cont = False
2433 cont = False
2434
2434
2435 # commit if --no-commit is false
2435 # commit if --no-commit is false
2436 if not opts.get('no_commit'):
2436 if not opts.get('no_commit'):
2437 node = repo.commit(text=message, user=user, date=date, extra=extra,
2437 node = repo.commit(text=message, user=user, date=date, extra=extra,
2438 editor=editor)
2438 editor=editor)
2439 if node is None:
2439 if node is None:
2440 ui.warn(
2440 ui.warn(
2441 _('note: graft of %d:%s created no changes to commit\n') %
2441 _('note: graft of %d:%s created no changes to commit\n') %
2442 (ctx.rev(), ctx))
2442 (ctx.rev(), ctx))
2443 # checking that newnodes exist because old state files won't have it
2443 # checking that newnodes exist because old state files won't have it
2444 elif statedata.get('newnodes') is not None:
2444 elif statedata.get('newnodes') is not None:
2445 statedata['newnodes'].append(node)
2445 statedata['newnodes'].append(node)
2446
2446
2447 # remove state when we complete successfully
2447 # remove state when we complete successfully
2448 if not opts.get('dry_run'):
2448 if not opts.get('dry_run'):
2449 graftstate.delete()
2449 graftstate.delete()
2450
2450
2451 return 0
2451 return 0
2452
2452
2453 def _abortgraft(ui, repo, graftstate):
2453 def _abortgraft(ui, repo, graftstate):
2454 """abort the interrupted graft and rollbacks to the state before interrupted
2454 """abort the interrupted graft and rollbacks to the state before interrupted
2455 graft"""
2455 graft"""
2456 if not graftstate.exists():
2456 if not graftstate.exists():
2457 raise error.Abort(_("no interrupted graft to abort"))
2457 raise error.Abort(_("no interrupted graft to abort"))
2458 statedata = _readgraftstate(repo, graftstate)
2458 statedata = _readgraftstate(repo, graftstate)
2459 newnodes = statedata.get('newnodes')
2459 newnodes = statedata.get('newnodes')
2460 if newnodes is None:
2460 if newnodes is None:
2461 # and old graft state which does not have all the data required to abort
2461 # and old graft state which does not have all the data required to abort
2462 # the graft
2462 # the graft
2463 raise error.Abort(_("cannot abort using an old graftstate"))
2463 raise error.Abort(_("cannot abort using an old graftstate"))
2464
2464
2465 # changeset from which graft operation was started
2465 # changeset from which graft operation was started
2466 startctx = None
2466 startctx = None
2467 if len(newnodes) > 0:
2467 if len(newnodes) > 0:
2468 startctx = repo[newnodes[0]].p1()
2468 startctx = repo[newnodes[0]].p1()
2469 else:
2469 else:
2470 startctx = repo['.']
2470 startctx = repo['.']
2471 # whether to strip or not
2471 # whether to strip or not
2472 cleanup = False
2472 cleanup = False
2473 if newnodes:
2473 if newnodes:
2474 newnodes = [repo[r].rev() for r in newnodes]
2474 newnodes = [repo[r].rev() for r in newnodes]
2475 cleanup = True
2475 cleanup = True
2476 # checking that none of the newnodes turned public or is public
2476 # checking that none of the newnodes turned public or is public
2477 immutable = [c for c in newnodes if not repo[c].mutable()]
2477 immutable = [c for c in newnodes if not repo[c].mutable()]
2478 if immutable:
2478 if immutable:
2479 repo.ui.warn(_("cannot clean up public changesets %s\n")
2479 repo.ui.warn(_("cannot clean up public changesets %s\n")
2480 % ', '.join(bytes(repo[r]) for r in immutable),
2480 % ', '.join(bytes(repo[r]) for r in immutable),
2481 hint=_("see 'hg help phases' for details"))
2481 hint=_("see 'hg help phases' for details"))
2482 cleanup = False
2482 cleanup = False
2483
2483
2484 # checking that no new nodes are created on top of grafted revs
2484 # checking that no new nodes are created on top of grafted revs
2485 desc = set(repo.changelog.descendants(newnodes))
2485 desc = set(repo.changelog.descendants(newnodes))
2486 if desc - set(newnodes):
2486 if desc - set(newnodes):
2487 repo.ui.warn(_("new changesets detected on destination "
2487 repo.ui.warn(_("new changesets detected on destination "
2488 "branch, can't strip\n"))
2488 "branch, can't strip\n"))
2489 cleanup = False
2489 cleanup = False
2490
2490
2491 if cleanup:
2491 if cleanup:
2492 with repo.wlock(), repo.lock():
2492 with repo.wlock(), repo.lock():
2493 hg.updaterepo(repo, startctx.node(), overwrite=True)
2493 hg.updaterepo(repo, startctx.node(), overwrite=True)
2494 # stripping the new nodes created
2494 # stripping the new nodes created
2495 strippoints = [c.node() for c in repo.set("roots(%ld)",
2495 strippoints = [c.node() for c in repo.set("roots(%ld)",
2496 newnodes)]
2496 newnodes)]
2497 repair.strip(repo.ui, repo, strippoints, backup=False)
2497 repair.strip(repo.ui, repo, strippoints, backup=False)
2498
2498
2499 if not cleanup:
2499 if not cleanup:
2500 # we don't update to the startnode if we can't strip
2500 # we don't update to the startnode if we can't strip
2501 startctx = repo['.']
2501 startctx = repo['.']
2502 hg.updaterepo(repo, startctx.node(), overwrite=True)
2502 hg.updaterepo(repo, startctx.node(), overwrite=True)
2503
2503
2504 ui.status(_("graft aborted\n"))
2504 ui.status(_("graft aborted\n"))
2505 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12])
2505 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12])
2506 graftstate.delete()
2506 graftstate.delete()
2507 return 0
2507 return 0
2508
2508
2509 def _readgraftstate(repo, graftstate):
2509 def _readgraftstate(repo, graftstate):
2510 """read the graft state file and return a dict of the data stored in it"""
2510 """read the graft state file and return a dict of the data stored in it"""
2511 try:
2511 try:
2512 return graftstate.read()
2512 return graftstate.read()
2513 except error.CorruptedState:
2513 except error.CorruptedState:
2514 nodes = repo.vfs.read('graftstate').splitlines()
2514 nodes = repo.vfs.read('graftstate').splitlines()
2515 return {'nodes': nodes}
2515 return {'nodes': nodes}
2516
2516
2517 def _stopgraft(ui, repo, graftstate):
2517 def _stopgraft(ui, repo, graftstate):
2518 """stop the interrupted graft"""
2518 """stop the interrupted graft"""
2519 if not graftstate.exists():
2519 if not graftstate.exists():
2520 raise error.Abort(_("no interrupted graft found"))
2520 raise error.Abort(_("no interrupted graft found"))
2521 pctx = repo['.']
2521 pctx = repo['.']
2522 hg.updaterepo(repo, pctx.node(), overwrite=True)
2522 hg.updaterepo(repo, pctx.node(), overwrite=True)
2523 graftstate.delete()
2523 graftstate.delete()
2524 ui.status(_("stopped the interrupted graft\n"))
2524 ui.status(_("stopped the interrupted graft\n"))
2525 ui.status(_("working directory is now at %s\n") % pctx.hex()[:12])
2525 ui.status(_("working directory is now at %s\n") % pctx.hex()[:12])
2526 return 0
2526 return 0
2527
2527
2528 @command('grep',
2528 @command('grep',
2529 [('0', 'print0', None, _('end fields with NUL')),
2529 [('0', 'print0', None, _('end fields with NUL')),
2530 ('', 'all', None, _('print all revisions that match (DEPRECATED) ')),
2530 ('', 'all', None, _('print all revisions that match (DEPRECATED) ')),
2531 ('', 'diff', None, _('print all revisions when the term was introduced '
2531 ('', 'diff', None, _('print all revisions when the term was introduced '
2532 'or removed')),
2532 'or removed')),
2533 ('a', 'text', None, _('treat all files as text')),
2533 ('a', 'text', None, _('treat all files as text')),
2534 ('f', 'follow', None,
2534 ('f', 'follow', None,
2535 _('follow changeset history,'
2535 _('follow changeset history,'
2536 ' or file history across copies and renames')),
2536 ' or file history across copies and renames')),
2537 ('i', 'ignore-case', None, _('ignore case when matching')),
2537 ('i', 'ignore-case', None, _('ignore case when matching')),
2538 ('l', 'files-with-matches', None,
2538 ('l', 'files-with-matches', None,
2539 _('print only filenames and revisions that match')),
2539 _('print only filenames and revisions that match')),
2540 ('n', 'line-number', None, _('print matching line numbers')),
2540 ('n', 'line-number', None, _('print matching line numbers')),
2541 ('r', 'rev', [],
2541 ('r', 'rev', [],
2542 _('only search files changed within revision range'), _('REV')),
2542 _('only search files changed within revision range'), _('REV')),
2543 ('', 'all-files', None,
2543 ('', 'all-files', None,
2544 _('include all files in the changeset while grepping (EXPERIMENTAL)')),
2544 _('include all files in the changeset while grepping (EXPERIMENTAL)')),
2545 ('u', 'user', None, _('list the author (long with -v)')),
2545 ('u', 'user', None, _('list the author (long with -v)')),
2546 ('d', 'date', None, _('list the date (short with -q)')),
2546 ('d', 'date', None, _('list the date (short with -q)')),
2547 ] + formatteropts + walkopts,
2547 ] + formatteropts + walkopts,
2548 _('[OPTION]... PATTERN [FILE]...'),
2548 _('[OPTION]... PATTERN [FILE]...'),
2549 inferrepo=True,
2549 inferrepo=True,
2550 intents={INTENT_READONLY})
2550 intents={INTENT_READONLY})
2551 def grep(ui, repo, pattern, *pats, **opts):
2551 def grep(ui, repo, pattern, *pats, **opts):
2552 """search revision history for a pattern in specified files
2552 """search revision history for a pattern in specified files
2553
2553
2554 Search revision history for a regular expression in the specified
2554 Search revision history for a regular expression in the specified
2555 files or the entire project.
2555 files or the entire project.
2556
2556
2557 By default, grep prints the most recent revision number for each
2557 By default, grep prints the most recent revision number for each
2558 file in which it finds a match. To get it to print every revision
2558 file in which it finds a match. To get it to print every revision
2559 that contains a change in match status ("-" for a match that becomes
2559 that contains a change in match status ("-" for a match that becomes
2560 a non-match, or "+" for a non-match that becomes a match), use the
2560 a non-match, or "+" for a non-match that becomes a match), use the
2561 --diff flag.
2561 --diff flag.
2562
2562
2563 PATTERN can be any Python (roughly Perl-compatible) regular
2563 PATTERN can be any Python (roughly Perl-compatible) regular
2564 expression.
2564 expression.
2565
2565
2566 If no FILEs are specified (and -f/--follow isn't set), all files in
2566 If no FILEs are specified (and -f/--follow isn't set), all files in
2567 the repository are searched, including those that don't exist in the
2567 the repository are searched, including those that don't exist in the
2568 current branch or have been deleted in a prior changeset.
2568 current branch or have been deleted in a prior changeset.
2569
2569
2570 Returns 0 if a match is found, 1 otherwise.
2570 Returns 0 if a match is found, 1 otherwise.
2571 """
2571 """
2572 opts = pycompat.byteskwargs(opts)
2572 opts = pycompat.byteskwargs(opts)
2573 diff = opts.get('all') or opts.get('diff')
2573 diff = opts.get('all') or opts.get('diff')
2574 all_files = opts.get('all_files')
2574 all_files = opts.get('all_files')
2575 if diff and opts.get('all_files'):
2575 if diff and opts.get('all_files'):
2576 raise error.Abort(_('--diff and --all-files are mutually exclusive'))
2576 raise error.Abort(_('--diff and --all-files are mutually exclusive'))
2577 # TODO: remove "not opts.get('rev')" if --all-files -rMULTIREV gets working
2577 # TODO: remove "not opts.get('rev')" if --all-files -rMULTIREV gets working
2578 if opts.get('all_files') is None and not opts.get('rev') and not diff:
2578 if opts.get('all_files') is None and not opts.get('rev') and not diff:
2579 # experimental config: commands.grep.all-files
2579 # experimental config: commands.grep.all-files
2580 opts['all_files'] = ui.configbool('commands', 'grep.all-files')
2580 opts['all_files'] = ui.configbool('commands', 'grep.all-files')
2581 plaingrep = opts.get('all_files') and not opts.get('rev')
2581 plaingrep = opts.get('all_files') and not opts.get('rev')
2582 if plaingrep:
2582 if plaingrep:
2583 opts['rev'] = ['wdir()']
2583 opts['rev'] = ['wdir()']
2584
2584
2585 reflags = re.M
2585 reflags = re.M
2586 if opts.get('ignore_case'):
2586 if opts.get('ignore_case'):
2587 reflags |= re.I
2587 reflags |= re.I
2588 try:
2588 try:
2589 regexp = util.re.compile(pattern, reflags)
2589 regexp = util.re.compile(pattern, reflags)
2590 except re.error as inst:
2590 except re.error as inst:
2591 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2591 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2592 return 1
2592 return 1
2593 sep, eol = ':', '\n'
2593 sep, eol = ':', '\n'
2594 if opts.get('print0'):
2594 if opts.get('print0'):
2595 sep = eol = '\0'
2595 sep = eol = '\0'
2596
2596
2597 getfile = util.lrucachefunc(repo.file)
2597 getfile = util.lrucachefunc(repo.file)
2598
2598
2599 def matchlines(body):
2599 def matchlines(body):
2600 begin = 0
2600 begin = 0
2601 linenum = 0
2601 linenum = 0
2602 while begin < len(body):
2602 while begin < len(body):
2603 match = regexp.search(body, begin)
2603 match = regexp.search(body, begin)
2604 if not match:
2604 if not match:
2605 break
2605 break
2606 mstart, mend = match.span()
2606 mstart, mend = match.span()
2607 linenum += body.count('\n', begin, mstart) + 1
2607 linenum += body.count('\n', begin, mstart) + 1
2608 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2608 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2609 begin = body.find('\n', mend) + 1 or len(body) + 1
2609 begin = body.find('\n', mend) + 1 or len(body) + 1
2610 lend = begin - 1
2610 lend = begin - 1
2611 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2611 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2612
2612
2613 class linestate(object):
2613 class linestate(object):
2614 def __init__(self, line, linenum, colstart, colend):
2614 def __init__(self, line, linenum, colstart, colend):
2615 self.line = line
2615 self.line = line
2616 self.linenum = linenum
2616 self.linenum = linenum
2617 self.colstart = colstart
2617 self.colstart = colstart
2618 self.colend = colend
2618 self.colend = colend
2619
2619
2620 def __hash__(self):
2620 def __hash__(self):
2621 return hash((self.linenum, self.line))
2621 return hash((self.linenum, self.line))
2622
2622
2623 def __eq__(self, other):
2623 def __eq__(self, other):
2624 return self.line == other.line
2624 return self.line == other.line
2625
2625
2626 def findpos(self):
2626 def findpos(self):
2627 """Iterate all (start, end) indices of matches"""
2627 """Iterate all (start, end) indices of matches"""
2628 yield self.colstart, self.colend
2628 yield self.colstart, self.colend
2629 p = self.colend
2629 p = self.colend
2630 while p < len(self.line):
2630 while p < len(self.line):
2631 m = regexp.search(self.line, p)
2631 m = regexp.search(self.line, p)
2632 if not m:
2632 if not m:
2633 break
2633 break
2634 yield m.span()
2634 yield m.span()
2635 p = m.end()
2635 p = m.end()
2636
2636
2637 matches = {}
2637 matches = {}
2638 copies = {}
2638 copies = {}
2639 def grepbody(fn, rev, body):
2639 def grepbody(fn, rev, body):
2640 matches[rev].setdefault(fn, [])
2640 matches[rev].setdefault(fn, [])
2641 m = matches[rev][fn]
2641 m = matches[rev][fn]
2642 for lnum, cstart, cend, line in matchlines(body):
2642 for lnum, cstart, cend, line in matchlines(body):
2643 s = linestate(line, lnum, cstart, cend)
2643 s = linestate(line, lnum, cstart, cend)
2644 m.append(s)
2644 m.append(s)
2645
2645
2646 def difflinestates(a, b):
2646 def difflinestates(a, b):
2647 sm = difflib.SequenceMatcher(None, a, b)
2647 sm = difflib.SequenceMatcher(None, a, b)
2648 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2648 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2649 if tag == r'insert':
2649 if tag == r'insert':
2650 for i in pycompat.xrange(blo, bhi):
2650 for i in pycompat.xrange(blo, bhi):
2651 yield ('+', b[i])
2651 yield ('+', b[i])
2652 elif tag == r'delete':
2652 elif tag == r'delete':
2653 for i in pycompat.xrange(alo, ahi):
2653 for i in pycompat.xrange(alo, ahi):
2654 yield ('-', a[i])
2654 yield ('-', a[i])
2655 elif tag == r'replace':
2655 elif tag == r'replace':
2656 for i in pycompat.xrange(alo, ahi):
2656 for i in pycompat.xrange(alo, ahi):
2657 yield ('-', a[i])
2657 yield ('-', a[i])
2658 for i in pycompat.xrange(blo, bhi):
2658 for i in pycompat.xrange(blo, bhi):
2659 yield ('+', b[i])
2659 yield ('+', b[i])
2660
2660
2661 def display(fm, fn, ctx, pstates, states):
2661 def display(fm, fn, ctx, pstates, states):
2662 rev = scmutil.intrev(ctx)
2662 rev = scmutil.intrev(ctx)
2663 if fm.isplain():
2663 if fm.isplain():
2664 formatuser = ui.shortuser
2664 formatuser = ui.shortuser
2665 else:
2665 else:
2666 formatuser = pycompat.bytestr
2666 formatuser = pycompat.bytestr
2667 if ui.quiet:
2667 if ui.quiet:
2668 datefmt = '%Y-%m-%d'
2668 datefmt = '%Y-%m-%d'
2669 else:
2669 else:
2670 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2670 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2671 found = False
2671 found = False
2672 @util.cachefunc
2672 @util.cachefunc
2673 def binary():
2673 def binary():
2674 flog = getfile(fn)
2674 flog = getfile(fn)
2675 try:
2675 try:
2676 return stringutil.binary(flog.read(ctx.filenode(fn)))
2676 return stringutil.binary(flog.read(ctx.filenode(fn)))
2677 except error.WdirUnsupported:
2677 except error.WdirUnsupported:
2678 return ctx[fn].isbinary()
2678 return ctx[fn].isbinary()
2679
2679
2680 fieldnamemap = {'filename': 'path', 'linenumber': 'line_number'}
2680 fieldnamemap = {'filename': 'path', 'linenumber': 'line_number'}
2681 if diff:
2681 if diff:
2682 iter = difflinestates(pstates, states)
2682 iter = difflinestates(pstates, states)
2683 else:
2683 else:
2684 iter = [('', l) for l in states]
2684 iter = [('', l) for l in states]
2685 for change, l in iter:
2685 for change, l in iter:
2686 fm.startitem()
2686 fm.startitem()
2687 fm.context(ctx=ctx)
2687 fm.context(ctx=ctx)
2688 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)))
2688 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)))
2689
2689
2690 cols = [
2690 cols = [
2691 ('filename', '%s', fn, True),
2691 ('filename', '%s', fn, True),
2692 ('rev', '%d', rev, not plaingrep),
2692 ('rev', '%d', rev, not plaingrep),
2693 ('linenumber', '%d', l.linenum, opts.get('line_number')),
2693 ('linenumber', '%d', l.linenum, opts.get('line_number')),
2694 ]
2694 ]
2695 if diff:
2695 if diff:
2696 cols.append(('change', '%s', change, True))
2696 cols.append(('change', '%s', change, True))
2697 cols.extend([
2697 cols.extend([
2698 ('user', '%s', formatuser(ctx.user()), opts.get('user')),
2698 ('user', '%s', formatuser(ctx.user()), opts.get('user')),
2699 ('date', '%s', fm.formatdate(ctx.date(), datefmt),
2699 ('date', '%s', fm.formatdate(ctx.date(), datefmt),
2700 opts.get('date')),
2700 opts.get('date')),
2701 ])
2701 ])
2702 lastcol = next(
2702 lastcol = next(
2703 name for name, fmt, data, cond in reversed(cols) if cond)
2703 name for name, fmt, data, cond in reversed(cols) if cond)
2704 for name, fmt, data, cond in cols:
2704 for name, fmt, data, cond in cols:
2705 field = fieldnamemap.get(name, name)
2705 field = fieldnamemap.get(name, name)
2706 fm.condwrite(cond, field, fmt, data, label='grep.%s' % name)
2706 fm.condwrite(cond, field, fmt, data, label='grep.%s' % name)
2707 if cond and name != lastcol:
2707 if cond and name != lastcol:
2708 fm.plain(sep, label='grep.sep')
2708 fm.plain(sep, label='grep.sep')
2709 if not opts.get('files_with_matches'):
2709 if not opts.get('files_with_matches'):
2710 fm.plain(sep, label='grep.sep')
2710 fm.plain(sep, label='grep.sep')
2711 if not opts.get('text') and binary():
2711 if not opts.get('text') and binary():
2712 fm.plain(_(" Binary file matches"))
2712 fm.plain(_(" Binary file matches"))
2713 else:
2713 else:
2714 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2714 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2715 fm.plain(eol)
2715 fm.plain(eol)
2716 found = True
2716 found = True
2717 if opts.get('files_with_matches'):
2717 if opts.get('files_with_matches'):
2718 break
2718 break
2719 return found
2719 return found
2720
2720
2721 def displaymatches(fm, l):
2721 def displaymatches(fm, l):
2722 p = 0
2722 p = 0
2723 for s, e in l.findpos():
2723 for s, e in l.findpos():
2724 if p < s:
2724 if p < s:
2725 fm.startitem()
2725 fm.startitem()
2726 fm.write('text', '%s', l.line[p:s])
2726 fm.write('text', '%s', l.line[p:s])
2727 fm.data(matched=False)
2727 fm.data(matched=False)
2728 fm.startitem()
2728 fm.startitem()
2729 fm.write('text', '%s', l.line[s:e], label='grep.match')
2729 fm.write('text', '%s', l.line[s:e], label='grep.match')
2730 fm.data(matched=True)
2730 fm.data(matched=True)
2731 p = e
2731 p = e
2732 if p < len(l.line):
2732 if p < len(l.line):
2733 fm.startitem()
2733 fm.startitem()
2734 fm.write('text', '%s', l.line[p:])
2734 fm.write('text', '%s', l.line[p:])
2735 fm.data(matched=False)
2735 fm.data(matched=False)
2736 fm.end()
2736 fm.end()
2737
2737
2738 skip = {}
2738 skip = {}
2739 revfiles = {}
2739 revfiles = {}
2740 match = scmutil.match(repo[None], pats, opts)
2740 match = scmutil.match(repo[None], pats, opts)
2741 found = False
2741 found = False
2742 follow = opts.get('follow')
2742 follow = opts.get('follow')
2743
2743
2744 def prep(ctx, fns):
2744 def prep(ctx, fns):
2745 rev = ctx.rev()
2745 rev = ctx.rev()
2746 pctx = ctx.p1()
2746 pctx = ctx.p1()
2747 parent = pctx.rev()
2747 parent = pctx.rev()
2748 matches.setdefault(rev, {})
2748 matches.setdefault(rev, {})
2749 matches.setdefault(parent, {})
2749 matches.setdefault(parent, {})
2750 files = revfiles.setdefault(rev, [])
2750 files = revfiles.setdefault(rev, [])
2751 for fn in fns:
2751 for fn in fns:
2752 flog = getfile(fn)
2752 flog = getfile(fn)
2753 try:
2753 try:
2754 fnode = ctx.filenode(fn)
2754 fnode = ctx.filenode(fn)
2755 except error.LookupError:
2755 except error.LookupError:
2756 continue
2756 continue
2757 try:
2757 try:
2758 copied = flog.renamed(fnode)
2758 copied = flog.renamed(fnode)
2759 except error.WdirUnsupported:
2759 except error.WdirUnsupported:
2760 copied = ctx[fn].renamed()
2760 copied = ctx[fn].renamed()
2761 copy = follow and copied and copied[0]
2761 copy = follow and copied and copied[0]
2762 if copy:
2762 if copy:
2763 copies.setdefault(rev, {})[fn] = copy
2763 copies.setdefault(rev, {})[fn] = copy
2764 if fn in skip:
2764 if fn in skip:
2765 if copy:
2765 if copy:
2766 skip[copy] = True
2766 skip[copy] = True
2767 continue
2767 continue
2768 files.append(fn)
2768 files.append(fn)
2769
2769
2770 if fn not in matches[rev]:
2770 if fn not in matches[rev]:
2771 try:
2771 try:
2772 content = flog.read(fnode)
2772 content = flog.read(fnode)
2773 except error.WdirUnsupported:
2773 except error.WdirUnsupported:
2774 content = ctx[fn].data()
2774 content = ctx[fn].data()
2775 grepbody(fn, rev, content)
2775 grepbody(fn, rev, content)
2776
2776
2777 pfn = copy or fn
2777 pfn = copy or fn
2778 if pfn not in matches[parent]:
2778 if pfn not in matches[parent]:
2779 try:
2779 try:
2780 fnode = pctx.filenode(pfn)
2780 fnode = pctx.filenode(pfn)
2781 grepbody(pfn, parent, flog.read(fnode))
2781 grepbody(pfn, parent, flog.read(fnode))
2782 except error.LookupError:
2782 except error.LookupError:
2783 pass
2783 pass
2784
2784
2785 ui.pager('grep')
2785 ui.pager('grep')
2786 fm = ui.formatter('grep', opts)
2786 fm = ui.formatter('grep', opts)
2787 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2787 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2788 rev = ctx.rev()
2788 rev = ctx.rev()
2789 parent = ctx.p1().rev()
2789 parent = ctx.p1().rev()
2790 for fn in sorted(revfiles.get(rev, [])):
2790 for fn in sorted(revfiles.get(rev, [])):
2791 states = matches[rev][fn]
2791 states = matches[rev][fn]
2792 copy = copies.get(rev, {}).get(fn)
2792 copy = copies.get(rev, {}).get(fn)
2793 if fn in skip:
2793 if fn in skip:
2794 if copy:
2794 if copy:
2795 skip[copy] = True
2795 skip[copy] = True
2796 continue
2796 continue
2797 pstates = matches.get(parent, {}).get(copy or fn, [])
2797 pstates = matches.get(parent, {}).get(copy or fn, [])
2798 if pstates or states:
2798 if pstates or states:
2799 r = display(fm, fn, ctx, pstates, states)
2799 r = display(fm, fn, ctx, pstates, states)
2800 found = found or r
2800 found = found or r
2801 if r and not diff and not all_files:
2801 if r and not diff and not all_files:
2802 skip[fn] = True
2802 skip[fn] = True
2803 if copy:
2803 if copy:
2804 skip[copy] = True
2804 skip[copy] = True
2805 del revfiles[rev]
2805 del revfiles[rev]
2806 # We will keep the matches dict for the duration of the window
2806 # We will keep the matches dict for the duration of the window
2807 # clear the matches dict once the window is over
2807 # clear the matches dict once the window is over
2808 if not revfiles:
2808 if not revfiles:
2809 matches.clear()
2809 matches.clear()
2810 fm.end()
2810 fm.end()
2811
2811
2812 return not found
2812 return not found
2813
2813
2814 @command('heads',
2814 @command('heads',
2815 [('r', 'rev', '',
2815 [('r', 'rev', '',
2816 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2816 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2817 ('t', 'topo', False, _('show topological heads only')),
2817 ('t', 'topo', False, _('show topological heads only')),
2818 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2818 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2819 ('c', 'closed', False, _('show normal and closed branch heads')),
2819 ('c', 'closed', False, _('show normal and closed branch heads')),
2820 ] + templateopts,
2820 ] + templateopts,
2821 _('[-ct] [-r STARTREV] [REV]...'),
2821 _('[-ct] [-r STARTREV] [REV]...'),
2822 intents={INTENT_READONLY})
2822 intents={INTENT_READONLY})
2823 def heads(ui, repo, *branchrevs, **opts):
2823 def heads(ui, repo, *branchrevs, **opts):
2824 """show branch heads
2824 """show branch heads
2825
2825
2826 With no arguments, show all open branch heads in the repository.
2826 With no arguments, show all open branch heads in the repository.
2827 Branch heads are changesets that have no descendants on the
2827 Branch heads are changesets that have no descendants on the
2828 same branch. They are where development generally takes place and
2828 same branch. They are where development generally takes place and
2829 are the usual targets for update and merge operations.
2829 are the usual targets for update and merge operations.
2830
2830
2831 If one or more REVs are given, only open branch heads on the
2831 If one or more REVs are given, only open branch heads on the
2832 branches associated with the specified changesets are shown. This
2832 branches associated with the specified changesets are shown. This
2833 means that you can use :hg:`heads .` to see the heads on the
2833 means that you can use :hg:`heads .` to see the heads on the
2834 currently checked-out branch.
2834 currently checked-out branch.
2835
2835
2836 If -c/--closed is specified, also show branch heads marked closed
2836 If -c/--closed is specified, also show branch heads marked closed
2837 (see :hg:`commit --close-branch`).
2837 (see :hg:`commit --close-branch`).
2838
2838
2839 If STARTREV is specified, only those heads that are descendants of
2839 If STARTREV is specified, only those heads that are descendants of
2840 STARTREV will be displayed.
2840 STARTREV will be displayed.
2841
2841
2842 If -t/--topo is specified, named branch mechanics will be ignored and only
2842 If -t/--topo is specified, named branch mechanics will be ignored and only
2843 topological heads (changesets with no children) will be shown.
2843 topological heads (changesets with no children) will be shown.
2844
2844
2845 Returns 0 if matching heads are found, 1 if not.
2845 Returns 0 if matching heads are found, 1 if not.
2846 """
2846 """
2847
2847
2848 opts = pycompat.byteskwargs(opts)
2848 opts = pycompat.byteskwargs(opts)
2849 start = None
2849 start = None
2850 rev = opts.get('rev')
2850 rev = opts.get('rev')
2851 if rev:
2851 if rev:
2852 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2852 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2853 start = scmutil.revsingle(repo, rev, None).node()
2853 start = scmutil.revsingle(repo, rev, None).node()
2854
2854
2855 if opts.get('topo'):
2855 if opts.get('topo'):
2856 heads = [repo[h] for h in repo.heads(start)]
2856 heads = [repo[h] for h in repo.heads(start)]
2857 else:
2857 else:
2858 heads = []
2858 heads = []
2859 for branch in repo.branchmap():
2859 for branch in repo.branchmap():
2860 heads += repo.branchheads(branch, start, opts.get('closed'))
2860 heads += repo.branchheads(branch, start, opts.get('closed'))
2861 heads = [repo[h] for h in heads]
2861 heads = [repo[h] for h in heads]
2862
2862
2863 if branchrevs:
2863 if branchrevs:
2864 branches = set(repo[r].branch()
2864 branches = set(repo[r].branch()
2865 for r in scmutil.revrange(repo, branchrevs))
2865 for r in scmutil.revrange(repo, branchrevs))
2866 heads = [h for h in heads if h.branch() in branches]
2866 heads = [h for h in heads if h.branch() in branches]
2867
2867
2868 if opts.get('active') and branchrevs:
2868 if opts.get('active') and branchrevs:
2869 dagheads = repo.heads(start)
2869 dagheads = repo.heads(start)
2870 heads = [h for h in heads if h.node() in dagheads]
2870 heads = [h for h in heads if h.node() in dagheads]
2871
2871
2872 if branchrevs:
2872 if branchrevs:
2873 haveheads = set(h.branch() for h in heads)
2873 haveheads = set(h.branch() for h in heads)
2874 if branches - haveheads:
2874 if branches - haveheads:
2875 headless = ', '.join(b for b in branches - haveheads)
2875 headless = ', '.join(b for b in branches - haveheads)
2876 msg = _('no open branch heads found on branches %s')
2876 msg = _('no open branch heads found on branches %s')
2877 if opts.get('rev'):
2877 if opts.get('rev'):
2878 msg += _(' (started at %s)') % opts['rev']
2878 msg += _(' (started at %s)') % opts['rev']
2879 ui.warn((msg + '\n') % headless)
2879 ui.warn((msg + '\n') % headless)
2880
2880
2881 if not heads:
2881 if not heads:
2882 return 1
2882 return 1
2883
2883
2884 ui.pager('heads')
2884 ui.pager('heads')
2885 heads = sorted(heads, key=lambda x: -x.rev())
2885 heads = sorted(heads, key=lambda x: -x.rev())
2886 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
2886 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
2887 for ctx in heads:
2887 for ctx in heads:
2888 displayer.show(ctx)
2888 displayer.show(ctx)
2889 displayer.close()
2889 displayer.close()
2890
2890
2891 @command('help',
2891 @command('help',
2892 [('e', 'extension', None, _('show only help for extensions')),
2892 [('e', 'extension', None, _('show only help for extensions')),
2893 ('c', 'command', None, _('show only help for commands')),
2893 ('c', 'command', None, _('show only help for commands')),
2894 ('k', 'keyword', None, _('show topics matching keyword')),
2894 ('k', 'keyword', None, _('show topics matching keyword')),
2895 ('s', 'system', [], _('show help for specific platform(s)')),
2895 ('s', 'system', [], _('show help for specific platform(s)')),
2896 ],
2896 ],
2897 _('[-ecks] [TOPIC]'),
2897 _('[-ecks] [TOPIC]'),
2898 norepo=True,
2898 norepo=True,
2899 intents={INTENT_READONLY})
2899 intents={INTENT_READONLY})
2900 def help_(ui, name=None, **opts):
2900 def help_(ui, name=None, **opts):
2901 """show help for a given topic or a help overview
2901 """show help for a given topic or a help overview
2902
2902
2903 With no arguments, print a list of commands with short help messages.
2903 With no arguments, print a list of commands with short help messages.
2904
2904
2905 Given a topic, extension, or command name, print help for that
2905 Given a topic, extension, or command name, print help for that
2906 topic.
2906 topic.
2907
2907
2908 Returns 0 if successful.
2908 Returns 0 if successful.
2909 """
2909 """
2910
2910
2911 keep = opts.get(r'system') or []
2911 keep = opts.get(r'system') or []
2912 if len(keep) == 0:
2912 if len(keep) == 0:
2913 if pycompat.sysplatform.startswith('win'):
2913 if pycompat.sysplatform.startswith('win'):
2914 keep.append('windows')
2914 keep.append('windows')
2915 elif pycompat.sysplatform == 'OpenVMS':
2915 elif pycompat.sysplatform == 'OpenVMS':
2916 keep.append('vms')
2916 keep.append('vms')
2917 elif pycompat.sysplatform == 'plan9':
2917 elif pycompat.sysplatform == 'plan9':
2918 keep.append('plan9')
2918 keep.append('plan9')
2919 else:
2919 else:
2920 keep.append('unix')
2920 keep.append('unix')
2921 keep.append(pycompat.sysplatform.lower())
2921 keep.append(pycompat.sysplatform.lower())
2922 if ui.verbose:
2922 if ui.verbose:
2923 keep.append('verbose')
2923 keep.append('verbose')
2924
2924
2925 commands = sys.modules[__name__]
2925 commands = sys.modules[__name__]
2926 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
2926 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
2927 ui.pager('help')
2927 ui.pager('help')
2928 ui.write(formatted)
2928 ui.write(formatted)
2929
2929
2930
2930
2931 @command('identify|id',
2931 @command('identify|id',
2932 [('r', 'rev', '',
2932 [('r', 'rev', '',
2933 _('identify the specified revision'), _('REV')),
2933 _('identify the specified revision'), _('REV')),
2934 ('n', 'num', None, _('show local revision number')),
2934 ('n', 'num', None, _('show local revision number')),
2935 ('i', 'id', None, _('show global revision id')),
2935 ('i', 'id', None, _('show global revision id')),
2936 ('b', 'branch', None, _('show branch')),
2936 ('b', 'branch', None, _('show branch')),
2937 ('t', 'tags', None, _('show tags')),
2937 ('t', 'tags', None, _('show tags')),
2938 ('B', 'bookmarks', None, _('show bookmarks')),
2938 ('B', 'bookmarks', None, _('show bookmarks')),
2939 ] + remoteopts + formatteropts,
2939 ] + remoteopts + formatteropts,
2940 _('[-nibtB] [-r REV] [SOURCE]'),
2940 _('[-nibtB] [-r REV] [SOURCE]'),
2941 optionalrepo=True,
2941 optionalrepo=True,
2942 intents={INTENT_READONLY})
2942 intents={INTENT_READONLY})
2943 def identify(ui, repo, source=None, rev=None,
2943 def identify(ui, repo, source=None, rev=None,
2944 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2944 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2945 """identify the working directory or specified revision
2945 """identify the working directory or specified revision
2946
2946
2947 Print a summary identifying the repository state at REV using one or
2947 Print a summary identifying the repository state at REV using one or
2948 two parent hash identifiers, followed by a "+" if the working
2948 two parent hash identifiers, followed by a "+" if the working
2949 directory has uncommitted changes, the branch name (if not default),
2949 directory has uncommitted changes, the branch name (if not default),
2950 a list of tags, and a list of bookmarks.
2950 a list of tags, and a list of bookmarks.
2951
2951
2952 When REV is not given, print a summary of the current state of the
2952 When REV is not given, print a summary of the current state of the
2953 repository including the working directory. Specify -r. to get information
2953 repository including the working directory. Specify -r. to get information
2954 of the working directory parent without scanning uncommitted changes.
2954 of the working directory parent without scanning uncommitted changes.
2955
2955
2956 Specifying a path to a repository root or Mercurial bundle will
2956 Specifying a path to a repository root or Mercurial bundle will
2957 cause lookup to operate on that repository/bundle.
2957 cause lookup to operate on that repository/bundle.
2958
2958
2959 .. container:: verbose
2959 .. container:: verbose
2960
2960
2961 Examples:
2961 Examples:
2962
2962
2963 - generate a build identifier for the working directory::
2963 - generate a build identifier for the working directory::
2964
2964
2965 hg id --id > build-id.dat
2965 hg id --id > build-id.dat
2966
2966
2967 - find the revision corresponding to a tag::
2967 - find the revision corresponding to a tag::
2968
2968
2969 hg id -n -r 1.3
2969 hg id -n -r 1.3
2970
2970
2971 - check the most recent revision of a remote repository::
2971 - check the most recent revision of a remote repository::
2972
2972
2973 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2973 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2974
2974
2975 See :hg:`log` for generating more information about specific revisions,
2975 See :hg:`log` for generating more information about specific revisions,
2976 including full hash identifiers.
2976 including full hash identifiers.
2977
2977
2978 Returns 0 if successful.
2978 Returns 0 if successful.
2979 """
2979 """
2980
2980
2981 opts = pycompat.byteskwargs(opts)
2981 opts = pycompat.byteskwargs(opts)
2982 if not repo and not source:
2982 if not repo and not source:
2983 raise error.Abort(_("there is no Mercurial repository here "
2983 raise error.Abort(_("there is no Mercurial repository here "
2984 "(.hg not found)"))
2984 "(.hg not found)"))
2985
2985
2986 if ui.debugflag:
2986 if ui.debugflag:
2987 hexfunc = hex
2987 hexfunc = hex
2988 else:
2988 else:
2989 hexfunc = short
2989 hexfunc = short
2990 default = not (num or id or branch or tags or bookmarks)
2990 default = not (num or id or branch or tags or bookmarks)
2991 output = []
2991 output = []
2992 revs = []
2992 revs = []
2993
2993
2994 if source:
2994 if source:
2995 source, branches = hg.parseurl(ui.expandpath(source))
2995 source, branches = hg.parseurl(ui.expandpath(source))
2996 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2996 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2997 repo = peer.local()
2997 repo = peer.local()
2998 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2998 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2999
2999
3000 fm = ui.formatter('identify', opts)
3000 fm = ui.formatter('identify', opts)
3001 fm.startitem()
3001 fm.startitem()
3002
3002
3003 if not repo:
3003 if not repo:
3004 if num or branch or tags:
3004 if num or branch or tags:
3005 raise error.Abort(
3005 raise error.Abort(
3006 _("can't query remote revision number, branch, or tags"))
3006 _("can't query remote revision number, branch, or tags"))
3007 if not rev and revs:
3007 if not rev and revs:
3008 rev = revs[0]
3008 rev = revs[0]
3009 if not rev:
3009 if not rev:
3010 rev = "tip"
3010 rev = "tip"
3011
3011
3012 remoterev = peer.lookup(rev)
3012 remoterev = peer.lookup(rev)
3013 hexrev = hexfunc(remoterev)
3013 hexrev = hexfunc(remoterev)
3014 if default or id:
3014 if default or id:
3015 output = [hexrev]
3015 output = [hexrev]
3016 fm.data(id=hexrev)
3016 fm.data(id=hexrev)
3017
3017
3018 def getbms():
3018 def getbms():
3019 bms = []
3019 bms = []
3020
3020
3021 if 'bookmarks' in peer.listkeys('namespaces'):
3021 if 'bookmarks' in peer.listkeys('namespaces'):
3022 hexremoterev = hex(remoterev)
3022 hexremoterev = hex(remoterev)
3023 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3023 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3024 if bmr == hexremoterev]
3024 if bmr == hexremoterev]
3025
3025
3026 return sorted(bms)
3026 return sorted(bms)
3027
3027
3028 bms = getbms()
3028 bms = getbms()
3029 if bookmarks:
3029 if bookmarks:
3030 output.extend(bms)
3030 output.extend(bms)
3031 elif default and not ui.quiet:
3031 elif default and not ui.quiet:
3032 # multiple bookmarks for a single parent separated by '/'
3032 # multiple bookmarks for a single parent separated by '/'
3033 bm = '/'.join(bms)
3033 bm = '/'.join(bms)
3034 if bm:
3034 if bm:
3035 output.append(bm)
3035 output.append(bm)
3036
3036
3037 fm.data(node=hex(remoterev))
3037 fm.data(node=hex(remoterev))
3038 fm.data(bookmarks=fm.formatlist(bms, name='bookmark'))
3038 fm.data(bookmarks=fm.formatlist(bms, name='bookmark'))
3039 else:
3039 else:
3040 if rev:
3040 if rev:
3041 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3041 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3042 ctx = scmutil.revsingle(repo, rev, None)
3042 ctx = scmutil.revsingle(repo, rev, None)
3043
3043
3044 if ctx.rev() is None:
3044 if ctx.rev() is None:
3045 ctx = repo[None]
3045 ctx = repo[None]
3046 parents = ctx.parents()
3046 parents = ctx.parents()
3047 taglist = []
3047 taglist = []
3048 for p in parents:
3048 for p in parents:
3049 taglist.extend(p.tags())
3049 taglist.extend(p.tags())
3050
3050
3051 dirty = ""
3051 dirty = ""
3052 if ctx.dirty(missing=True, merge=False, branch=False):
3052 if ctx.dirty(missing=True, merge=False, branch=False):
3053 dirty = '+'
3053 dirty = '+'
3054 fm.data(dirty=dirty)
3054 fm.data(dirty=dirty)
3055
3055
3056 hexoutput = [hexfunc(p.node()) for p in parents]
3056 hexoutput = [hexfunc(p.node()) for p in parents]
3057 if default or id:
3057 if default or id:
3058 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
3058 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
3059 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
3059 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
3060
3060
3061 if num:
3061 if num:
3062 numoutput = ["%d" % p.rev() for p in parents]
3062 numoutput = ["%d" % p.rev() for p in parents]
3063 output.append("%s%s" % ('+'.join(numoutput), dirty))
3063 output.append("%s%s" % ('+'.join(numoutput), dirty))
3064
3064
3065 fn = fm.nested('parents', tmpl='{rev}:{node|formatnode}', sep=' ')
3065 fn = fm.nested('parents', tmpl='{rev}:{node|formatnode}', sep=' ')
3066 for p in parents:
3066 for p in parents:
3067 fn.startitem()
3067 fn.startitem()
3068 fn.data(rev=p.rev())
3068 fn.data(rev=p.rev())
3069 fn.data(node=p.hex())
3069 fn.data(node=p.hex())
3070 fn.context(ctx=p)
3070 fn.context(ctx=p)
3071 fn.end()
3071 fn.end()
3072 else:
3072 else:
3073 hexoutput = hexfunc(ctx.node())
3073 hexoutput = hexfunc(ctx.node())
3074 if default or id:
3074 if default or id:
3075 output = [hexoutput]
3075 output = [hexoutput]
3076 fm.data(id=hexoutput)
3076 fm.data(id=hexoutput)
3077
3077
3078 if num:
3078 if num:
3079 output.append(pycompat.bytestr(ctx.rev()))
3079 output.append(pycompat.bytestr(ctx.rev()))
3080 taglist = ctx.tags()
3080 taglist = ctx.tags()
3081
3081
3082 if default and not ui.quiet:
3082 if default and not ui.quiet:
3083 b = ctx.branch()
3083 b = ctx.branch()
3084 if b != 'default':
3084 if b != 'default':
3085 output.append("(%s)" % b)
3085 output.append("(%s)" % b)
3086
3086
3087 # multiple tags for a single parent separated by '/'
3087 # multiple tags for a single parent separated by '/'
3088 t = '/'.join(taglist)
3088 t = '/'.join(taglist)
3089 if t:
3089 if t:
3090 output.append(t)
3090 output.append(t)
3091
3091
3092 # multiple bookmarks for a single parent separated by '/'
3092 # multiple bookmarks for a single parent separated by '/'
3093 bm = '/'.join(ctx.bookmarks())
3093 bm = '/'.join(ctx.bookmarks())
3094 if bm:
3094 if bm:
3095 output.append(bm)
3095 output.append(bm)
3096 else:
3096 else:
3097 if branch:
3097 if branch:
3098 output.append(ctx.branch())
3098 output.append(ctx.branch())
3099
3099
3100 if tags:
3100 if tags:
3101 output.extend(taglist)
3101 output.extend(taglist)
3102
3102
3103 if bookmarks:
3103 if bookmarks:
3104 output.extend(ctx.bookmarks())
3104 output.extend(ctx.bookmarks())
3105
3105
3106 fm.data(node=ctx.hex())
3106 fm.data(node=ctx.hex())
3107 fm.data(branch=ctx.branch())
3107 fm.data(branch=ctx.branch())
3108 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
3108 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
3109 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
3109 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
3110 fm.context(ctx=ctx)
3110 fm.context(ctx=ctx)
3111
3111
3112 fm.plain("%s\n" % ' '.join(output))
3112 fm.plain("%s\n" % ' '.join(output))
3113 fm.end()
3113 fm.end()
3114
3114
3115 @command('import|patch',
3115 @command('import|patch',
3116 [('p', 'strip', 1,
3116 [('p', 'strip', 1,
3117 _('directory strip option for patch. This has the same '
3117 _('directory strip option for patch. This has the same '
3118 'meaning as the corresponding patch option'), _('NUM')),
3118 'meaning as the corresponding patch option'), _('NUM')),
3119 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3119 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3120 ('e', 'edit', False, _('invoke editor on commit messages')),
3120 ('e', 'edit', False, _('invoke editor on commit messages')),
3121 ('f', 'force', None,
3121 ('f', 'force', None,
3122 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3122 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3123 ('', 'no-commit', None,
3123 ('', 'no-commit', None,
3124 _("don't commit, just update the working directory")),
3124 _("don't commit, just update the working directory")),
3125 ('', 'bypass', None,
3125 ('', 'bypass', None,
3126 _("apply patch without touching the working directory")),
3126 _("apply patch without touching the working directory")),
3127 ('', 'partial', None,
3127 ('', 'partial', None,
3128 _('commit even if some hunks fail')),
3128 _('commit even if some hunks fail')),
3129 ('', 'exact', None,
3129 ('', 'exact', None,
3130 _('abort if patch would apply lossily')),
3130 _('abort if patch would apply lossily')),
3131 ('', 'prefix', '',
3131 ('', 'prefix', '',
3132 _('apply patch to subdirectory'), _('DIR')),
3132 _('apply patch to subdirectory'), _('DIR')),
3133 ('', 'import-branch', None,
3133 ('', 'import-branch', None,
3134 _('use any branch information in patch (implied by --exact)'))] +
3134 _('use any branch information in patch (implied by --exact)'))] +
3135 commitopts + commitopts2 + similarityopts,
3135 commitopts + commitopts2 + similarityopts,
3136 _('[OPTION]... PATCH...'))
3136 _('[OPTION]... PATCH...'))
3137 def import_(ui, repo, patch1=None, *patches, **opts):
3137 def import_(ui, repo, patch1=None, *patches, **opts):
3138 """import an ordered set of patches
3138 """import an ordered set of patches
3139
3139
3140 Import a list of patches and commit them individually (unless
3140 Import a list of patches and commit them individually (unless
3141 --no-commit is specified).
3141 --no-commit is specified).
3142
3142
3143 To read a patch from standard input (stdin), use "-" as the patch
3143 To read a patch from standard input (stdin), use "-" as the patch
3144 name. If a URL is specified, the patch will be downloaded from
3144 name. If a URL is specified, the patch will be downloaded from
3145 there.
3145 there.
3146
3146
3147 Import first applies changes to the working directory (unless
3147 Import first applies changes to the working directory (unless
3148 --bypass is specified), import will abort if there are outstanding
3148 --bypass is specified), import will abort if there are outstanding
3149 changes.
3149 changes.
3150
3150
3151 Use --bypass to apply and commit patches directly to the
3151 Use --bypass to apply and commit patches directly to the
3152 repository, without affecting the working directory. Without
3152 repository, without affecting the working directory. Without
3153 --exact, patches will be applied on top of the working directory
3153 --exact, patches will be applied on top of the working directory
3154 parent revision.
3154 parent revision.
3155
3155
3156 You can import a patch straight from a mail message. Even patches
3156 You can import a patch straight from a mail message. Even patches
3157 as attachments work (to use the body part, it must have type
3157 as attachments work (to use the body part, it must have type
3158 text/plain or text/x-patch). From and Subject headers of email
3158 text/plain or text/x-patch). From and Subject headers of email
3159 message are used as default committer and commit message. All
3159 message are used as default committer and commit message. All
3160 text/plain body parts before first diff are added to the commit
3160 text/plain body parts before first diff are added to the commit
3161 message.
3161 message.
3162
3162
3163 If the imported patch was generated by :hg:`export`, user and
3163 If the imported patch was generated by :hg:`export`, user and
3164 description from patch override values from message headers and
3164 description from patch override values from message headers and
3165 body. Values given on command line with -m/--message and -u/--user
3165 body. Values given on command line with -m/--message and -u/--user
3166 override these.
3166 override these.
3167
3167
3168 If --exact is specified, import will set the working directory to
3168 If --exact is specified, import will set the working directory to
3169 the parent of each patch before applying it, and will abort if the
3169 the parent of each patch before applying it, and will abort if the
3170 resulting changeset has a different ID than the one recorded in
3170 resulting changeset has a different ID than the one recorded in
3171 the patch. This will guard against various ways that portable
3171 the patch. This will guard against various ways that portable
3172 patch formats and mail systems might fail to transfer Mercurial
3172 patch formats and mail systems might fail to transfer Mercurial
3173 data or metadata. See :hg:`bundle` for lossless transmission.
3173 data or metadata. See :hg:`bundle` for lossless transmission.
3174
3174
3175 Use --partial to ensure a changeset will be created from the patch
3175 Use --partial to ensure a changeset will be created from the patch
3176 even if some hunks fail to apply. Hunks that fail to apply will be
3176 even if some hunks fail to apply. Hunks that fail to apply will be
3177 written to a <target-file>.rej file. Conflicts can then be resolved
3177 written to a <target-file>.rej file. Conflicts can then be resolved
3178 by hand before :hg:`commit --amend` is run to update the created
3178 by hand before :hg:`commit --amend` is run to update the created
3179 changeset. This flag exists to let people import patches that
3179 changeset. This flag exists to let people import patches that
3180 partially apply without losing the associated metadata (author,
3180 partially apply without losing the associated metadata (author,
3181 date, description, ...).
3181 date, description, ...).
3182
3182
3183 .. note::
3183 .. note::
3184
3184
3185 When no hunks apply cleanly, :hg:`import --partial` will create
3185 When no hunks apply cleanly, :hg:`import --partial` will create
3186 an empty changeset, importing only the patch metadata.
3186 an empty changeset, importing only the patch metadata.
3187
3187
3188 With -s/--similarity, hg will attempt to discover renames and
3188 With -s/--similarity, hg will attempt to discover renames and
3189 copies in the patch in the same way as :hg:`addremove`.
3189 copies in the patch in the same way as :hg:`addremove`.
3190
3190
3191 It is possible to use external patch programs to perform the patch
3191 It is possible to use external patch programs to perform the patch
3192 by setting the ``ui.patch`` configuration option. For the default
3192 by setting the ``ui.patch`` configuration option. For the default
3193 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3193 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3194 See :hg:`help config` for more information about configuration
3194 See :hg:`help config` for more information about configuration
3195 files and how to use these options.
3195 files and how to use these options.
3196
3196
3197 See :hg:`help dates` for a list of formats valid for -d/--date.
3197 See :hg:`help dates` for a list of formats valid for -d/--date.
3198
3198
3199 .. container:: verbose
3199 .. container:: verbose
3200
3200
3201 Examples:
3201 Examples:
3202
3202
3203 - import a traditional patch from a website and detect renames::
3203 - import a traditional patch from a website and detect renames::
3204
3204
3205 hg import -s 80 http://example.com/bugfix.patch
3205 hg import -s 80 http://example.com/bugfix.patch
3206
3206
3207 - import a changeset from an hgweb server::
3207 - import a changeset from an hgweb server::
3208
3208
3209 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3209 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3210
3210
3211 - import all the patches in an Unix-style mbox::
3211 - import all the patches in an Unix-style mbox::
3212
3212
3213 hg import incoming-patches.mbox
3213 hg import incoming-patches.mbox
3214
3214
3215 - import patches from stdin::
3215 - import patches from stdin::
3216
3216
3217 hg import -
3217 hg import -
3218
3218
3219 - attempt to exactly restore an exported changeset (not always
3219 - attempt to exactly restore an exported changeset (not always
3220 possible)::
3220 possible)::
3221
3221
3222 hg import --exact proposed-fix.patch
3222 hg import --exact proposed-fix.patch
3223
3223
3224 - use an external tool to apply a patch which is too fuzzy for
3224 - use an external tool to apply a patch which is too fuzzy for
3225 the default internal tool.
3225 the default internal tool.
3226
3226
3227 hg import --config ui.patch="patch --merge" fuzzy.patch
3227 hg import --config ui.patch="patch --merge" fuzzy.patch
3228
3228
3229 - change the default fuzzing from 2 to a less strict 7
3229 - change the default fuzzing from 2 to a less strict 7
3230
3230
3231 hg import --config ui.fuzz=7 fuzz.patch
3231 hg import --config ui.fuzz=7 fuzz.patch
3232
3232
3233 Returns 0 on success, 1 on partial success (see --partial).
3233 Returns 0 on success, 1 on partial success (see --partial).
3234 """
3234 """
3235
3235
3236 opts = pycompat.byteskwargs(opts)
3236 opts = pycompat.byteskwargs(opts)
3237 if not patch1:
3237 if not patch1:
3238 raise error.Abort(_('need at least one patch to import'))
3238 raise error.Abort(_('need at least one patch to import'))
3239
3239
3240 patches = (patch1,) + patches
3240 patches = (patch1,) + patches
3241
3241
3242 date = opts.get('date')
3242 date = opts.get('date')
3243 if date:
3243 if date:
3244 opts['date'] = dateutil.parsedate(date)
3244 opts['date'] = dateutil.parsedate(date)
3245
3245
3246 exact = opts.get('exact')
3246 exact = opts.get('exact')
3247 update = not opts.get('bypass')
3247 update = not opts.get('bypass')
3248 if not update and opts.get('no_commit'):
3248 if not update and opts.get('no_commit'):
3249 raise error.Abort(_('cannot use --no-commit with --bypass'))
3249 raise error.Abort(_('cannot use --no-commit with --bypass'))
3250 try:
3250 try:
3251 sim = float(opts.get('similarity') or 0)
3251 sim = float(opts.get('similarity') or 0)
3252 except ValueError:
3252 except ValueError:
3253 raise error.Abort(_('similarity must be a number'))
3253 raise error.Abort(_('similarity must be a number'))
3254 if sim < 0 or sim > 100:
3254 if sim < 0 or sim > 100:
3255 raise error.Abort(_('similarity must be between 0 and 100'))
3255 raise error.Abort(_('similarity must be between 0 and 100'))
3256 if sim and not update:
3256 if sim and not update:
3257 raise error.Abort(_('cannot use --similarity with --bypass'))
3257 raise error.Abort(_('cannot use --similarity with --bypass'))
3258 if exact:
3258 if exact:
3259 if opts.get('edit'):
3259 if opts.get('edit'):
3260 raise error.Abort(_('cannot use --exact with --edit'))
3260 raise error.Abort(_('cannot use --exact with --edit'))
3261 if opts.get('prefix'):
3261 if opts.get('prefix'):
3262 raise error.Abort(_('cannot use --exact with --prefix'))
3262 raise error.Abort(_('cannot use --exact with --prefix'))
3263
3263
3264 base = opts["base"]
3264 base = opts["base"]
3265 msgs = []
3265 msgs = []
3266 ret = 0
3266 ret = 0
3267
3267
3268 with repo.wlock():
3268 with repo.wlock():
3269 if update:
3269 if update:
3270 cmdutil.checkunfinished(repo)
3270 cmdutil.checkunfinished(repo)
3271 if (exact or not opts.get('force')):
3271 if (exact or not opts.get('force')):
3272 cmdutil.bailifchanged(repo)
3272 cmdutil.bailifchanged(repo)
3273
3273
3274 if not opts.get('no_commit'):
3274 if not opts.get('no_commit'):
3275 lock = repo.lock
3275 lock = repo.lock
3276 tr = lambda: repo.transaction('import')
3276 tr = lambda: repo.transaction('import')
3277 dsguard = util.nullcontextmanager
3277 dsguard = util.nullcontextmanager
3278 else:
3278 else:
3279 lock = util.nullcontextmanager
3279 lock = util.nullcontextmanager
3280 tr = util.nullcontextmanager
3280 tr = util.nullcontextmanager
3281 dsguard = lambda: dirstateguard.dirstateguard(repo, 'import')
3281 dsguard = lambda: dirstateguard.dirstateguard(repo, 'import')
3282 with lock(), tr(), dsguard():
3282 with lock(), tr(), dsguard():
3283 parents = repo[None].parents()
3283 parents = repo[None].parents()
3284 for patchurl in patches:
3284 for patchurl in patches:
3285 if patchurl == '-':
3285 if patchurl == '-':
3286 ui.status(_('applying patch from stdin\n'))
3286 ui.status(_('applying patch from stdin\n'))
3287 patchfile = ui.fin
3287 patchfile = ui.fin
3288 patchurl = 'stdin' # for error message
3288 patchurl = 'stdin' # for error message
3289 else:
3289 else:
3290 patchurl = os.path.join(base, patchurl)
3290 patchurl = os.path.join(base, patchurl)
3291 ui.status(_('applying %s\n') % patchurl)
3291 ui.status(_('applying %s\n') % patchurl)
3292 patchfile = hg.openpath(ui, patchurl)
3292 patchfile = hg.openpath(ui, patchurl)
3293
3293
3294 haspatch = False
3294 haspatch = False
3295 for hunk in patch.split(patchfile):
3295 for hunk in patch.split(patchfile):
3296 with patch.extract(ui, hunk) as patchdata:
3296 with patch.extract(ui, hunk) as patchdata:
3297 msg, node, rej = cmdutil.tryimportone(ui, repo,
3297 msg, node, rej = cmdutil.tryimportone(ui, repo,
3298 patchdata,
3298 patchdata,
3299 parents, opts,
3299 parents, opts,
3300 msgs, hg.clean)
3300 msgs, hg.clean)
3301 if msg:
3301 if msg:
3302 haspatch = True
3302 haspatch = True
3303 ui.note(msg + '\n')
3303 ui.note(msg + '\n')
3304 if update or exact:
3304 if update or exact:
3305 parents = repo[None].parents()
3305 parents = repo[None].parents()
3306 else:
3306 else:
3307 parents = [repo[node]]
3307 parents = [repo[node]]
3308 if rej:
3308 if rej:
3309 ui.write_err(_("patch applied partially\n"))
3309 ui.write_err(_("patch applied partially\n"))
3310 ui.write_err(_("(fix the .rej files and run "
3310 ui.write_err(_("(fix the .rej files and run "
3311 "`hg commit --amend`)\n"))
3311 "`hg commit --amend`)\n"))
3312 ret = 1
3312 ret = 1
3313 break
3313 break
3314
3314
3315 if not haspatch:
3315 if not haspatch:
3316 raise error.Abort(_('%s: no diffs found') % patchurl)
3316 raise error.Abort(_('%s: no diffs found') % patchurl)
3317
3317
3318 if msgs:
3318 if msgs:
3319 repo.savecommitmessage('\n* * *\n'.join(msgs))
3319 repo.savecommitmessage('\n* * *\n'.join(msgs))
3320 return ret
3320 return ret
3321
3321
3322 @command('incoming|in',
3322 @command('incoming|in',
3323 [('f', 'force', None,
3323 [('f', 'force', None,
3324 _('run even if remote repository is unrelated')),
3324 _('run even if remote repository is unrelated')),
3325 ('n', 'newest-first', None, _('show newest record first')),
3325 ('n', 'newest-first', None, _('show newest record first')),
3326 ('', 'bundle', '',
3326 ('', 'bundle', '',
3327 _('file to store the bundles into'), _('FILE')),
3327 _('file to store the bundles into'), _('FILE')),
3328 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3328 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3329 ('B', 'bookmarks', False, _("compare bookmarks")),
3329 ('B', 'bookmarks', False, _("compare bookmarks")),
3330 ('b', 'branch', [],
3330 ('b', 'branch', [],
3331 _('a specific branch you would like to pull'), _('BRANCH')),
3331 _('a specific branch you would like to pull'), _('BRANCH')),
3332 ] + logopts + remoteopts + subrepoopts,
3332 ] + logopts + remoteopts + subrepoopts,
3333 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3333 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3334 def incoming(ui, repo, source="default", **opts):
3334 def incoming(ui, repo, source="default", **opts):
3335 """show new changesets found in source
3335 """show new changesets found in source
3336
3336
3337 Show new changesets found in the specified path/URL or the default
3337 Show new changesets found in the specified path/URL or the default
3338 pull location. These are the changesets that would have been pulled
3338 pull location. These are the changesets that would have been pulled
3339 by :hg:`pull` at the time you issued this command.
3339 by :hg:`pull` at the time you issued this command.
3340
3340
3341 See pull for valid source format details.
3341 See pull for valid source format details.
3342
3342
3343 .. container:: verbose
3343 .. container:: verbose
3344
3344
3345 With -B/--bookmarks, the result of bookmark comparison between
3345 With -B/--bookmarks, the result of bookmark comparison between
3346 local and remote repositories is displayed. With -v/--verbose,
3346 local and remote repositories is displayed. With -v/--verbose,
3347 status is also displayed for each bookmark like below::
3347 status is also displayed for each bookmark like below::
3348
3348
3349 BM1 01234567890a added
3349 BM1 01234567890a added
3350 BM2 1234567890ab advanced
3350 BM2 1234567890ab advanced
3351 BM3 234567890abc diverged
3351 BM3 234567890abc diverged
3352 BM4 34567890abcd changed
3352 BM4 34567890abcd changed
3353
3353
3354 The action taken locally when pulling depends on the
3354 The action taken locally when pulling depends on the
3355 status of each bookmark:
3355 status of each bookmark:
3356
3356
3357 :``added``: pull will create it
3357 :``added``: pull will create it
3358 :``advanced``: pull will update it
3358 :``advanced``: pull will update it
3359 :``diverged``: pull will create a divergent bookmark
3359 :``diverged``: pull will create a divergent bookmark
3360 :``changed``: result depends on remote changesets
3360 :``changed``: result depends on remote changesets
3361
3361
3362 From the point of view of pulling behavior, bookmark
3362 From the point of view of pulling behavior, bookmark
3363 existing only in the remote repository are treated as ``added``,
3363 existing only in the remote repository are treated as ``added``,
3364 even if it is in fact locally deleted.
3364 even if it is in fact locally deleted.
3365
3365
3366 .. container:: verbose
3366 .. container:: verbose
3367
3367
3368 For remote repository, using --bundle avoids downloading the
3368 For remote repository, using --bundle avoids downloading the
3369 changesets twice if the incoming is followed by a pull.
3369 changesets twice if the incoming is followed by a pull.
3370
3370
3371 Examples:
3371 Examples:
3372
3372
3373 - show incoming changes with patches and full description::
3373 - show incoming changes with patches and full description::
3374
3374
3375 hg incoming -vp
3375 hg incoming -vp
3376
3376
3377 - show incoming changes excluding merges, store a bundle::
3377 - show incoming changes excluding merges, store a bundle::
3378
3378
3379 hg in -vpM --bundle incoming.hg
3379 hg in -vpM --bundle incoming.hg
3380 hg pull incoming.hg
3380 hg pull incoming.hg
3381
3381
3382 - briefly list changes inside a bundle::
3382 - briefly list changes inside a bundle::
3383
3383
3384 hg in changes.hg -T "{desc|firstline}\\n"
3384 hg in changes.hg -T "{desc|firstline}\\n"
3385
3385
3386 Returns 0 if there are incoming changes, 1 otherwise.
3386 Returns 0 if there are incoming changes, 1 otherwise.
3387 """
3387 """
3388 opts = pycompat.byteskwargs(opts)
3388 opts = pycompat.byteskwargs(opts)
3389 if opts.get('graph'):
3389 if opts.get('graph'):
3390 logcmdutil.checkunsupportedgraphflags([], opts)
3390 logcmdutil.checkunsupportedgraphflags([], opts)
3391 def display(other, chlist, displayer):
3391 def display(other, chlist, displayer):
3392 revdag = logcmdutil.graphrevs(other, chlist, opts)
3392 revdag = logcmdutil.graphrevs(other, chlist, opts)
3393 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3393 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3394 graphmod.asciiedges)
3394 graphmod.asciiedges)
3395
3395
3396 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3396 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3397 return 0
3397 return 0
3398
3398
3399 if opts.get('bundle') and opts.get('subrepos'):
3399 if opts.get('bundle') and opts.get('subrepos'):
3400 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3400 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3401
3401
3402 if opts.get('bookmarks'):
3402 if opts.get('bookmarks'):
3403 source, branches = hg.parseurl(ui.expandpath(source),
3403 source, branches = hg.parseurl(ui.expandpath(source),
3404 opts.get('branch'))
3404 opts.get('branch'))
3405 other = hg.peer(repo, opts, source)
3405 other = hg.peer(repo, opts, source)
3406 if 'bookmarks' not in other.listkeys('namespaces'):
3406 if 'bookmarks' not in other.listkeys('namespaces'):
3407 ui.warn(_("remote doesn't support bookmarks\n"))
3407 ui.warn(_("remote doesn't support bookmarks\n"))
3408 return 0
3408 return 0
3409 ui.pager('incoming')
3409 ui.pager('incoming')
3410 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3410 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3411 return bookmarks.incoming(ui, repo, other)
3411 return bookmarks.incoming(ui, repo, other)
3412
3412
3413 repo._subtoppath = ui.expandpath(source)
3413 repo._subtoppath = ui.expandpath(source)
3414 try:
3414 try:
3415 return hg.incoming(ui, repo, source, opts)
3415 return hg.incoming(ui, repo, source, opts)
3416 finally:
3416 finally:
3417 del repo._subtoppath
3417 del repo._subtoppath
3418
3418
3419
3419
3420 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3420 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3421 norepo=True)
3421 norepo=True)
3422 def init(ui, dest=".", **opts):
3422 def init(ui, dest=".", **opts):
3423 """create a new repository in the given directory
3423 """create a new repository in the given directory
3424
3424
3425 Initialize a new repository in the given directory. If the given
3425 Initialize a new repository in the given directory. If the given
3426 directory does not exist, it will be created.
3426 directory does not exist, it will be created.
3427
3427
3428 If no directory is given, the current directory is used.
3428 If no directory is given, the current directory is used.
3429
3429
3430 It is possible to specify an ``ssh://`` URL as the destination.
3430 It is possible to specify an ``ssh://`` URL as the destination.
3431 See :hg:`help urls` for more information.
3431 See :hg:`help urls` for more information.
3432
3432
3433 Returns 0 on success.
3433 Returns 0 on success.
3434 """
3434 """
3435 opts = pycompat.byteskwargs(opts)
3435 opts = pycompat.byteskwargs(opts)
3436 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3436 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3437
3437
3438 @command('locate',
3438 @command('locate',
3439 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3439 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3440 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3440 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3441 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3441 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3442 ] + walkopts,
3442 ] + walkopts,
3443 _('[OPTION]... [PATTERN]...'))
3443 _('[OPTION]... [PATTERN]...'))
3444 def locate(ui, repo, *pats, **opts):
3444 def locate(ui, repo, *pats, **opts):
3445 """locate files matching specific patterns (DEPRECATED)
3445 """locate files matching specific patterns (DEPRECATED)
3446
3446
3447 Print files under Mercurial control in the working directory whose
3447 Print files under Mercurial control in the working directory whose
3448 names match the given patterns.
3448 names match the given patterns.
3449
3449
3450 By default, this command searches all directories in the working
3450 By default, this command searches all directories in the working
3451 directory. To search just the current directory and its
3451 directory. To search just the current directory and its
3452 subdirectories, use "--include .".
3452 subdirectories, use "--include .".
3453
3453
3454 If no patterns are given to match, this command prints the names
3454 If no patterns are given to match, this command prints the names
3455 of all files under Mercurial control in the working directory.
3455 of all files under Mercurial control in the working directory.
3456
3456
3457 If you want to feed the output of this command into the "xargs"
3457 If you want to feed the output of this command into the "xargs"
3458 command, use the -0 option to both this command and "xargs". This
3458 command, use the -0 option to both this command and "xargs". This
3459 will avoid the problem of "xargs" treating single filenames that
3459 will avoid the problem of "xargs" treating single filenames that
3460 contain whitespace as multiple filenames.
3460 contain whitespace as multiple filenames.
3461
3461
3462 See :hg:`help files` for a more versatile command.
3462 See :hg:`help files` for a more versatile command.
3463
3463
3464 Returns 0 if a match is found, 1 otherwise.
3464 Returns 0 if a match is found, 1 otherwise.
3465 """
3465 """
3466 opts = pycompat.byteskwargs(opts)
3466 opts = pycompat.byteskwargs(opts)
3467 if opts.get('print0'):
3467 if opts.get('print0'):
3468 end = '\0'
3468 end = '\0'
3469 else:
3469 else:
3470 end = '\n'
3470 end = '\n'
3471 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3471 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3472
3472
3473 ret = 1
3473 ret = 1
3474 m = scmutil.match(ctx, pats, opts, default='relglob',
3474 m = scmutil.match(ctx, pats, opts, default='relglob',
3475 badfn=lambda x, y: False)
3475 badfn=lambda x, y: False)
3476
3476
3477 ui.pager('locate')
3477 ui.pager('locate')
3478 if ctx.rev() is None:
3478 if ctx.rev() is None:
3479 # When run on the working copy, "locate" includes removed files, so
3479 # When run on the working copy, "locate" includes removed files, so
3480 # we get the list of files from the dirstate.
3480 # we get the list of files from the dirstate.
3481 filesgen = sorted(repo.dirstate.matches(m))
3481 filesgen = sorted(repo.dirstate.matches(m))
3482 else:
3482 else:
3483 filesgen = ctx.matches(m)
3483 filesgen = ctx.matches(m)
3484 for abs in filesgen:
3484 for abs in filesgen:
3485 if opts.get('fullpath'):
3485 if opts.get('fullpath'):
3486 ui.write(repo.wjoin(abs), end)
3486 ui.write(repo.wjoin(abs), end)
3487 else:
3487 else:
3488 ui.write(((pats and m.rel(abs)) or abs), end)
3488 ui.write(((pats and m.rel(abs)) or abs), end)
3489 ret = 0
3489 ret = 0
3490
3490
3491 return ret
3491 return ret
3492
3492
3493 @command('^log|history',
3493 @command('^log|history',
3494 [('f', 'follow', None,
3494 [('f', 'follow', None,
3495 _('follow changeset history, or file history across copies and renames')),
3495 _('follow changeset history, or file history across copies and renames')),
3496 ('', 'follow-first', None,
3496 ('', 'follow-first', None,
3497 _('only follow the first parent of merge changesets (DEPRECATED)')),
3497 _('only follow the first parent of merge changesets (DEPRECATED)')),
3498 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3498 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3499 ('C', 'copies', None, _('show copied files')),
3499 ('C', 'copies', None, _('show copied files')),
3500 ('k', 'keyword', [],
3500 ('k', 'keyword', [],
3501 _('do case-insensitive search for a given text'), _('TEXT')),
3501 _('do case-insensitive search for a given text'), _('TEXT')),
3502 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3502 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3503 ('L', 'line-range', [],
3503 ('L', 'line-range', [],
3504 _('follow line range of specified file (EXPERIMENTAL)'),
3504 _('follow line range of specified file (EXPERIMENTAL)'),
3505 _('FILE,RANGE')),
3505 _('FILE,RANGE')),
3506 ('', 'removed', None, _('include revisions where files were removed')),
3506 ('', 'removed', None, _('include revisions where files were removed')),
3507 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3507 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3508 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3508 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3509 ('', 'only-branch', [],
3509 ('', 'only-branch', [],
3510 _('show only changesets within the given named branch (DEPRECATED)'),
3510 _('show only changesets within the given named branch (DEPRECATED)'),
3511 _('BRANCH')),
3511 _('BRANCH')),
3512 ('b', 'branch', [],
3512 ('b', 'branch', [],
3513 _('show changesets within the given named branch'), _('BRANCH')),
3513 _('show changesets within the given named branch'), _('BRANCH')),
3514 ('P', 'prune', [],
3514 ('P', 'prune', [],
3515 _('do not display revision or any of its ancestors'), _('REV')),
3515 _('do not display revision or any of its ancestors'), _('REV')),
3516 ] + logopts + walkopts,
3516 ] + logopts + walkopts,
3517 _('[OPTION]... [FILE]'),
3517 _('[OPTION]... [FILE]'),
3518 inferrepo=True,
3518 inferrepo=True,
3519 intents={INTENT_READONLY})
3519 intents={INTENT_READONLY})
3520 def log(ui, repo, *pats, **opts):
3520 def log(ui, repo, *pats, **opts):
3521 """show revision history of entire repository or files
3521 """show revision history of entire repository or files
3522
3522
3523 Print the revision history of the specified files or the entire
3523 Print the revision history of the specified files or the entire
3524 project.
3524 project.
3525
3525
3526 If no revision range is specified, the default is ``tip:0`` unless
3526 If no revision range is specified, the default is ``tip:0`` unless
3527 --follow is set, in which case the working directory parent is
3527 --follow is set, in which case the working directory parent is
3528 used as the starting revision.
3528 used as the starting revision.
3529
3529
3530 File history is shown without following rename or copy history of
3530 File history is shown without following rename or copy history of
3531 files. Use -f/--follow with a filename to follow history across
3531 files. Use -f/--follow with a filename to follow history across
3532 renames and copies. --follow without a filename will only show
3532 renames and copies. --follow without a filename will only show
3533 ancestors of the starting revision.
3533 ancestors of the starting revision.
3534
3534
3535 By default this command prints revision number and changeset id,
3535 By default this command prints revision number and changeset id,
3536 tags, non-trivial parents, user, date and time, and a summary for
3536 tags, non-trivial parents, user, date and time, and a summary for
3537 each commit. When the -v/--verbose switch is used, the list of
3537 each commit. When the -v/--verbose switch is used, the list of
3538 changed files and full commit message are shown.
3538 changed files and full commit message are shown.
3539
3539
3540 With --graph the revisions are shown as an ASCII art DAG with the most
3540 With --graph the revisions are shown as an ASCII art DAG with the most
3541 recent changeset at the top.
3541 recent changeset at the top.
3542 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3542 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3543 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3543 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3544 changeset from the lines below is a parent of the 'o' merge on the same
3544 changeset from the lines below is a parent of the 'o' merge on the same
3545 line.
3545 line.
3546 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3546 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3547 of a '|' indicates one or more revisions in a path are omitted.
3547 of a '|' indicates one or more revisions in a path are omitted.
3548
3548
3549 .. container:: verbose
3549 .. container:: verbose
3550
3550
3551 Use -L/--line-range FILE,M:N options to follow the history of lines
3551 Use -L/--line-range FILE,M:N options to follow the history of lines
3552 from M to N in FILE. With -p/--patch only diff hunks affecting
3552 from M to N in FILE. With -p/--patch only diff hunks affecting
3553 specified line range will be shown. This option requires --follow;
3553 specified line range will be shown. This option requires --follow;
3554 it can be specified multiple times. Currently, this option is not
3554 it can be specified multiple times. Currently, this option is not
3555 compatible with --graph. This option is experimental.
3555 compatible with --graph. This option is experimental.
3556
3556
3557 .. note::
3557 .. note::
3558
3558
3559 :hg:`log --patch` may generate unexpected diff output for merge
3559 :hg:`log --patch` may generate unexpected diff output for merge
3560 changesets, as it will only compare the merge changeset against
3560 changesets, as it will only compare the merge changeset against
3561 its first parent. Also, only files different from BOTH parents
3561 its first parent. Also, only files different from BOTH parents
3562 will appear in files:.
3562 will appear in files:.
3563
3563
3564 .. note::
3564 .. note::
3565
3565
3566 For performance reasons, :hg:`log FILE` may omit duplicate changes
3566 For performance reasons, :hg:`log FILE` may omit duplicate changes
3567 made on branches and will not show removals or mode changes. To
3567 made on branches and will not show removals or mode changes. To
3568 see all such changes, use the --removed switch.
3568 see all such changes, use the --removed switch.
3569
3569
3570 .. container:: verbose
3570 .. container:: verbose
3571
3571
3572 .. note::
3572 .. note::
3573
3573
3574 The history resulting from -L/--line-range options depends on diff
3574 The history resulting from -L/--line-range options depends on diff
3575 options; for instance if white-spaces are ignored, respective changes
3575 options; for instance if white-spaces are ignored, respective changes
3576 with only white-spaces in specified line range will not be listed.
3576 with only white-spaces in specified line range will not be listed.
3577
3577
3578 .. container:: verbose
3578 .. container:: verbose
3579
3579
3580 Some examples:
3580 Some examples:
3581
3581
3582 - changesets with full descriptions and file lists::
3582 - changesets with full descriptions and file lists::
3583
3583
3584 hg log -v
3584 hg log -v
3585
3585
3586 - changesets ancestral to the working directory::
3586 - changesets ancestral to the working directory::
3587
3587
3588 hg log -f
3588 hg log -f
3589
3589
3590 - last 10 commits on the current branch::
3590 - last 10 commits on the current branch::
3591
3591
3592 hg log -l 10 -b .
3592 hg log -l 10 -b .
3593
3593
3594 - changesets showing all modifications of a file, including removals::
3594 - changesets showing all modifications of a file, including removals::
3595
3595
3596 hg log --removed file.c
3596 hg log --removed file.c
3597
3597
3598 - all changesets that touch a directory, with diffs, excluding merges::
3598 - all changesets that touch a directory, with diffs, excluding merges::
3599
3599
3600 hg log -Mp lib/
3600 hg log -Mp lib/
3601
3601
3602 - all revision numbers that match a keyword::
3602 - all revision numbers that match a keyword::
3603
3603
3604 hg log -k bug --template "{rev}\\n"
3604 hg log -k bug --template "{rev}\\n"
3605
3605
3606 - the full hash identifier of the working directory parent::
3606 - the full hash identifier of the working directory parent::
3607
3607
3608 hg log -r . --template "{node}\\n"
3608 hg log -r . --template "{node}\\n"
3609
3609
3610 - list available log templates::
3610 - list available log templates::
3611
3611
3612 hg log -T list
3612 hg log -T list
3613
3613
3614 - check if a given changeset is included in a tagged release::
3614 - check if a given changeset is included in a tagged release::
3615
3615
3616 hg log -r "a21ccf and ancestor(1.9)"
3616 hg log -r "a21ccf and ancestor(1.9)"
3617
3617
3618 - find all changesets by some user in a date range::
3618 - find all changesets by some user in a date range::
3619
3619
3620 hg log -k alice -d "may 2008 to jul 2008"
3620 hg log -k alice -d "may 2008 to jul 2008"
3621
3621
3622 - summary of all changesets after the last tag::
3622 - summary of all changesets after the last tag::
3623
3623
3624 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3624 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3625
3625
3626 - changesets touching lines 13 to 23 for file.c::
3626 - changesets touching lines 13 to 23 for file.c::
3627
3627
3628 hg log -L file.c,13:23
3628 hg log -L file.c,13:23
3629
3629
3630 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3630 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3631 main.c with patch::
3631 main.c with patch::
3632
3632
3633 hg log -L file.c,13:23 -L main.c,2:6 -p
3633 hg log -L file.c,13:23 -L main.c,2:6 -p
3634
3634
3635 See :hg:`help dates` for a list of formats valid for -d/--date.
3635 See :hg:`help dates` for a list of formats valid for -d/--date.
3636
3636
3637 See :hg:`help revisions` for more about specifying and ordering
3637 See :hg:`help revisions` for more about specifying and ordering
3638 revisions.
3638 revisions.
3639
3639
3640 See :hg:`help templates` for more about pre-packaged styles and
3640 See :hg:`help templates` for more about pre-packaged styles and
3641 specifying custom templates. The default template used by the log
3641 specifying custom templates. The default template used by the log
3642 command can be customized via the ``ui.logtemplate`` configuration
3642 command can be customized via the ``ui.logtemplate`` configuration
3643 setting.
3643 setting.
3644
3644
3645 Returns 0 on success.
3645 Returns 0 on success.
3646
3646
3647 """
3647 """
3648 opts = pycompat.byteskwargs(opts)
3648 opts = pycompat.byteskwargs(opts)
3649 linerange = opts.get('line_range')
3649 linerange = opts.get('line_range')
3650
3650
3651 if linerange and not opts.get('follow'):
3651 if linerange and not opts.get('follow'):
3652 raise error.Abort(_('--line-range requires --follow'))
3652 raise error.Abort(_('--line-range requires --follow'))
3653
3653
3654 if linerange and pats:
3654 if linerange and pats:
3655 # TODO: take pats as patterns with no line-range filter
3655 # TODO: take pats as patterns with no line-range filter
3656 raise error.Abort(
3656 raise error.Abort(
3657 _('FILE arguments are not compatible with --line-range option')
3657 _('FILE arguments are not compatible with --line-range option')
3658 )
3658 )
3659
3659
3660 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3660 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3661 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3661 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3662 if linerange:
3662 if linerange:
3663 # TODO: should follow file history from logcmdutil._initialrevs(),
3663 # TODO: should follow file history from logcmdutil._initialrevs(),
3664 # then filter the result by logcmdutil._makerevset() and --limit
3664 # then filter the result by logcmdutil._makerevset() and --limit
3665 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3665 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3666
3666
3667 getrenamed = None
3667 getrenamed = None
3668 if opts.get('copies'):
3668 if opts.get('copies'):
3669 endrev = None
3669 endrev = None
3670 if revs:
3670 if revs:
3671 endrev = revs.max() + 1
3671 endrev = revs.max() + 1
3672 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3672 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3673
3673
3674 ui.pager('log')
3674 ui.pager('log')
3675 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3675 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3676 buffered=True)
3676 buffered=True)
3677 if opts.get('graph'):
3677 if opts.get('graph'):
3678 displayfn = logcmdutil.displaygraphrevs
3678 displayfn = logcmdutil.displaygraphrevs
3679 else:
3679 else:
3680 displayfn = logcmdutil.displayrevs
3680 displayfn = logcmdutil.displayrevs
3681 displayfn(ui, repo, revs, displayer, getrenamed)
3681 displayfn(ui, repo, revs, displayer, getrenamed)
3682
3682
3683 @command('manifest',
3683 @command('manifest',
3684 [('r', 'rev', '', _('revision to display'), _('REV')),
3684 [('r', 'rev', '', _('revision to display'), _('REV')),
3685 ('', 'all', False, _("list files from all revisions"))]
3685 ('', 'all', False, _("list files from all revisions"))]
3686 + formatteropts,
3686 + formatteropts,
3687 _('[-r REV]'),
3687 _('[-r REV]'),
3688 intents={INTENT_READONLY})
3688 intents={INTENT_READONLY})
3689 def manifest(ui, repo, node=None, rev=None, **opts):
3689 def manifest(ui, repo, node=None, rev=None, **opts):
3690 """output the current or given revision of the project manifest
3690 """output the current or given revision of the project manifest
3691
3691
3692 Print a list of version controlled files for the given revision.
3692 Print a list of version controlled files for the given revision.
3693 If no revision is given, the first parent of the working directory
3693 If no revision is given, the first parent of the working directory
3694 is used, or the null revision if no revision is checked out.
3694 is used, or the null revision if no revision is checked out.
3695
3695
3696 With -v, print file permissions, symlink and executable bits.
3696 With -v, print file permissions, symlink and executable bits.
3697 With --debug, print file revision hashes.
3697 With --debug, print file revision hashes.
3698
3698
3699 If option --all is specified, the list of all files from all revisions
3699 If option --all is specified, the list of all files from all revisions
3700 is printed. This includes deleted and renamed files.
3700 is printed. This includes deleted and renamed files.
3701
3701
3702 Returns 0 on success.
3702 Returns 0 on success.
3703 """
3703 """
3704 opts = pycompat.byteskwargs(opts)
3704 opts = pycompat.byteskwargs(opts)
3705 fm = ui.formatter('manifest', opts)
3705 fm = ui.formatter('manifest', opts)
3706
3706
3707 if opts.get('all'):
3707 if opts.get('all'):
3708 if rev or node:
3708 if rev or node:
3709 raise error.Abort(_("can't specify a revision with --all"))
3709 raise error.Abort(_("can't specify a revision with --all"))
3710
3710
3711 res = set()
3711 res = set()
3712 for rev in repo:
3712 for rev in repo:
3713 ctx = repo[rev]
3713 ctx = repo[rev]
3714 res |= set(ctx.files())
3714 res |= set(ctx.files())
3715
3715
3716 ui.pager('manifest')
3716 ui.pager('manifest')
3717 for f in sorted(res):
3717 for f in sorted(res):
3718 fm.startitem()
3718 fm.startitem()
3719 fm.write("path", '%s\n', f)
3719 fm.write("path", '%s\n', f)
3720 fm.end()
3720 fm.end()
3721 return
3721 return
3722
3722
3723 if rev and node:
3723 if rev and node:
3724 raise error.Abort(_("please specify just one revision"))
3724 raise error.Abort(_("please specify just one revision"))
3725
3725
3726 if not node:
3726 if not node:
3727 node = rev
3727 node = rev
3728
3728
3729 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3729 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3730 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3730 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3731 if node:
3731 if node:
3732 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3732 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3733 ctx = scmutil.revsingle(repo, node)
3733 ctx = scmutil.revsingle(repo, node)
3734 mf = ctx.manifest()
3734 mf = ctx.manifest()
3735 ui.pager('manifest')
3735 ui.pager('manifest')
3736 for f in ctx:
3736 for f in ctx:
3737 fm.startitem()
3737 fm.startitem()
3738 fm.context(ctx=ctx)
3738 fm.context(ctx=ctx)
3739 fl = ctx[f].flags()
3739 fl = ctx[f].flags()
3740 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3740 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3741 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3741 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3742 fm.write('path', '%s\n', f)
3742 fm.write('path', '%s\n', f)
3743 fm.end()
3743 fm.end()
3744
3744
3745 @command('^merge',
3745 @command('^merge',
3746 [('f', 'force', None,
3746 [('f', 'force', None,
3747 _('force a merge including outstanding changes (DEPRECATED)')),
3747 _('force a merge including outstanding changes (DEPRECATED)')),
3748 ('r', 'rev', '', _('revision to merge'), _('REV')),
3748 ('r', 'rev', '', _('revision to merge'), _('REV')),
3749 ('P', 'preview', None,
3749 ('P', 'preview', None,
3750 _('review revisions to merge (no merge is performed)')),
3750 _('review revisions to merge (no merge is performed)')),
3751 ('', 'abort', None, _('abort the ongoing merge')),
3751 ('', 'abort', None, _('abort the ongoing merge')),
3752 ] + mergetoolopts,
3752 ] + mergetoolopts,
3753 _('[-P] [[-r] REV]'))
3753 _('[-P] [[-r] REV]'))
3754 def merge(ui, repo, node=None, **opts):
3754 def merge(ui, repo, node=None, **opts):
3755 """merge another revision into working directory
3755 """merge another revision into working directory
3756
3756
3757 The current working directory is updated with all changes made in
3757 The current working directory is updated with all changes made in
3758 the requested revision since the last common predecessor revision.
3758 the requested revision since the last common predecessor revision.
3759
3759
3760 Files that changed between either parent are marked as changed for
3760 Files that changed between either parent are marked as changed for
3761 the next commit and a commit must be performed before any further
3761 the next commit and a commit must be performed before any further
3762 updates to the repository are allowed. The next commit will have
3762 updates to the repository are allowed. The next commit will have
3763 two parents.
3763 two parents.
3764
3764
3765 ``--tool`` can be used to specify the merge tool used for file
3765 ``--tool`` can be used to specify the merge tool used for file
3766 merges. It overrides the HGMERGE environment variable and your
3766 merges. It overrides the HGMERGE environment variable and your
3767 configuration files. See :hg:`help merge-tools` for options.
3767 configuration files. See :hg:`help merge-tools` for options.
3768
3768
3769 If no revision is specified, the working directory's parent is a
3769 If no revision is specified, the working directory's parent is a
3770 head revision, and the current branch contains exactly one other
3770 head revision, and the current branch contains exactly one other
3771 head, the other head is merged with by default. Otherwise, an
3771 head, the other head is merged with by default. Otherwise, an
3772 explicit revision with which to merge with must be provided.
3772 explicit revision with which to merge with must be provided.
3773
3773
3774 See :hg:`help resolve` for information on handling file conflicts.
3774 See :hg:`help resolve` for information on handling file conflicts.
3775
3775
3776 To undo an uncommitted merge, use :hg:`merge --abort` which
3776 To undo an uncommitted merge, use :hg:`merge --abort` which
3777 will check out a clean copy of the original merge parent, losing
3777 will check out a clean copy of the original merge parent, losing
3778 all changes.
3778 all changes.
3779
3779
3780 Returns 0 on success, 1 if there are unresolved files.
3780 Returns 0 on success, 1 if there are unresolved files.
3781 """
3781 """
3782
3782
3783 opts = pycompat.byteskwargs(opts)
3783 opts = pycompat.byteskwargs(opts)
3784 abort = opts.get('abort')
3784 abort = opts.get('abort')
3785 if abort and repo.dirstate.p2() == nullid:
3785 if abort and repo.dirstate.p2() == nullid:
3786 cmdutil.wrongtooltocontinue(repo, _('merge'))
3786 cmdutil.wrongtooltocontinue(repo, _('merge'))
3787 if abort:
3787 if abort:
3788 if node:
3788 if node:
3789 raise error.Abort(_("cannot specify a node with --abort"))
3789 raise error.Abort(_("cannot specify a node with --abort"))
3790 if opts.get('rev'):
3790 if opts.get('rev'):
3791 raise error.Abort(_("cannot specify both --rev and --abort"))
3791 raise error.Abort(_("cannot specify both --rev and --abort"))
3792 if opts.get('preview'):
3792 if opts.get('preview'):
3793 raise error.Abort(_("cannot specify --preview with --abort"))
3793 raise error.Abort(_("cannot specify --preview with --abort"))
3794 if opts.get('rev') and node:
3794 if opts.get('rev') and node:
3795 raise error.Abort(_("please specify just one revision"))
3795 raise error.Abort(_("please specify just one revision"))
3796 if not node:
3796 if not node:
3797 node = opts.get('rev')
3797 node = opts.get('rev')
3798
3798
3799 if node:
3799 if node:
3800 node = scmutil.revsingle(repo, node).node()
3800 node = scmutil.revsingle(repo, node).node()
3801
3801
3802 if not node and not abort:
3802 if not node and not abort:
3803 node = repo[destutil.destmerge(repo)].node()
3803 node = repo[destutil.destmerge(repo)].node()
3804
3804
3805 if opts.get('preview'):
3805 if opts.get('preview'):
3806 # find nodes that are ancestors of p2 but not of p1
3806 # find nodes that are ancestors of p2 but not of p1
3807 p1 = repo.lookup('.')
3807 p1 = repo.lookup('.')
3808 p2 = node
3808 p2 = node
3809 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3809 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3810
3810
3811 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3811 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3812 for node in nodes:
3812 for node in nodes:
3813 displayer.show(repo[node])
3813 displayer.show(repo[node])
3814 displayer.close()
3814 displayer.close()
3815 return 0
3815 return 0
3816
3816
3817 # ui.forcemerge is an internal variable, do not document
3817 # ui.forcemerge is an internal variable, do not document
3818 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
3818 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
3819 with ui.configoverride(overrides, 'merge'):
3819 with ui.configoverride(overrides, 'merge'):
3820 force = opts.get('force')
3820 force = opts.get('force')
3821 labels = ['working copy', 'merge rev']
3821 labels = ['working copy', 'merge rev']
3822 return hg.merge(repo, node, force=force, mergeforce=force,
3822 return hg.merge(repo, node, force=force, mergeforce=force,
3823 labels=labels, abort=abort)
3823 labels=labels, abort=abort)
3824
3824
3825 @command('outgoing|out',
3825 @command('outgoing|out',
3826 [('f', 'force', None, _('run even when the destination is unrelated')),
3826 [('f', 'force', None, _('run even when the destination is unrelated')),
3827 ('r', 'rev', [],
3827 ('r', 'rev', [],
3828 _('a changeset intended to be included in the destination'), _('REV')),
3828 _('a changeset intended to be included in the destination'), _('REV')),
3829 ('n', 'newest-first', None, _('show newest record first')),
3829 ('n', 'newest-first', None, _('show newest record first')),
3830 ('B', 'bookmarks', False, _('compare bookmarks')),
3830 ('B', 'bookmarks', False, _('compare bookmarks')),
3831 ('b', 'branch', [], _('a specific branch you would like to push'),
3831 ('b', 'branch', [], _('a specific branch you would like to push'),
3832 _('BRANCH')),
3832 _('BRANCH')),
3833 ] + logopts + remoteopts + subrepoopts,
3833 ] + logopts + remoteopts + subrepoopts,
3834 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3834 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3835 def outgoing(ui, repo, dest=None, **opts):
3835 def outgoing(ui, repo, dest=None, **opts):
3836 """show changesets not found in the destination
3836 """show changesets not found in the destination
3837
3837
3838 Show changesets not found in the specified destination repository
3838 Show changesets not found in the specified destination repository
3839 or the default push location. These are the changesets that would
3839 or the default push location. These are the changesets that would
3840 be pushed if a push was requested.
3840 be pushed if a push was requested.
3841
3841
3842 See pull for details of valid destination formats.
3842 See pull for details of valid destination formats.
3843
3843
3844 .. container:: verbose
3844 .. container:: verbose
3845
3845
3846 With -B/--bookmarks, the result of bookmark comparison between
3846 With -B/--bookmarks, the result of bookmark comparison between
3847 local and remote repositories is displayed. With -v/--verbose,
3847 local and remote repositories is displayed. With -v/--verbose,
3848 status is also displayed for each bookmark like below::
3848 status is also displayed for each bookmark like below::
3849
3849
3850 BM1 01234567890a added
3850 BM1 01234567890a added
3851 BM2 deleted
3851 BM2 deleted
3852 BM3 234567890abc advanced
3852 BM3 234567890abc advanced
3853 BM4 34567890abcd diverged
3853 BM4 34567890abcd diverged
3854 BM5 4567890abcde changed
3854 BM5 4567890abcde changed
3855
3855
3856 The action taken when pushing depends on the
3856 The action taken when pushing depends on the
3857 status of each bookmark:
3857 status of each bookmark:
3858
3858
3859 :``added``: push with ``-B`` will create it
3859 :``added``: push with ``-B`` will create it
3860 :``deleted``: push with ``-B`` will delete it
3860 :``deleted``: push with ``-B`` will delete it
3861 :``advanced``: push will update it
3861 :``advanced``: push will update it
3862 :``diverged``: push with ``-B`` will update it
3862 :``diverged``: push with ``-B`` will update it
3863 :``changed``: push with ``-B`` will update it
3863 :``changed``: push with ``-B`` will update it
3864
3864
3865 From the point of view of pushing behavior, bookmarks
3865 From the point of view of pushing behavior, bookmarks
3866 existing only in the remote repository are treated as
3866 existing only in the remote repository are treated as
3867 ``deleted``, even if it is in fact added remotely.
3867 ``deleted``, even if it is in fact added remotely.
3868
3868
3869 Returns 0 if there are outgoing changes, 1 otherwise.
3869 Returns 0 if there are outgoing changes, 1 otherwise.
3870 """
3870 """
3871 # hg._outgoing() needs to re-resolve the path in order to handle #branch
3871 # hg._outgoing() needs to re-resolve the path in order to handle #branch
3872 # style URLs, so don't overwrite dest.
3872 # style URLs, so don't overwrite dest.
3873 path = ui.paths.getpath(dest, default=('default-push', 'default'))
3873 path = ui.paths.getpath(dest, default=('default-push', 'default'))
3874 if not path:
3874 if not path:
3875 raise error.Abort(_('default repository not configured!'),
3875 raise error.Abort(_('default repository not configured!'),
3876 hint=_("see 'hg help config.paths'"))
3876 hint=_("see 'hg help config.paths'"))
3877
3877
3878 opts = pycompat.byteskwargs(opts)
3878 opts = pycompat.byteskwargs(opts)
3879 if opts.get('graph'):
3879 if opts.get('graph'):
3880 logcmdutil.checkunsupportedgraphflags([], opts)
3880 logcmdutil.checkunsupportedgraphflags([], opts)
3881 o, other = hg._outgoing(ui, repo, dest, opts)
3881 o, other = hg._outgoing(ui, repo, dest, opts)
3882 if not o:
3882 if not o:
3883 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3883 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3884 return
3884 return
3885
3885
3886 revdag = logcmdutil.graphrevs(repo, o, opts)
3886 revdag = logcmdutil.graphrevs(repo, o, opts)
3887 ui.pager('outgoing')
3887 ui.pager('outgoing')
3888 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
3888 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
3889 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3889 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3890 graphmod.asciiedges)
3890 graphmod.asciiedges)
3891 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3891 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3892 return 0
3892 return 0
3893
3893
3894 if opts.get('bookmarks'):
3894 if opts.get('bookmarks'):
3895 dest = path.pushloc or path.loc
3895 dest = path.pushloc or path.loc
3896 other = hg.peer(repo, opts, dest)
3896 other = hg.peer(repo, opts, dest)
3897 if 'bookmarks' not in other.listkeys('namespaces'):
3897 if 'bookmarks' not in other.listkeys('namespaces'):
3898 ui.warn(_("remote doesn't support bookmarks\n"))
3898 ui.warn(_("remote doesn't support bookmarks\n"))
3899 return 0
3899 return 0
3900 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3900 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3901 ui.pager('outgoing')
3901 ui.pager('outgoing')
3902 return bookmarks.outgoing(ui, repo, other)
3902 return bookmarks.outgoing(ui, repo, other)
3903
3903
3904 repo._subtoppath = path.pushloc or path.loc
3904 repo._subtoppath = path.pushloc or path.loc
3905 try:
3905 try:
3906 return hg.outgoing(ui, repo, dest, opts)
3906 return hg.outgoing(ui, repo, dest, opts)
3907 finally:
3907 finally:
3908 del repo._subtoppath
3908 del repo._subtoppath
3909
3909
3910 @command('parents',
3910 @command('parents',
3911 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3911 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3912 ] + templateopts,
3912 ] + templateopts,
3913 _('[-r REV] [FILE]'),
3913 _('[-r REV] [FILE]'),
3914 inferrepo=True)
3914 inferrepo=True)
3915 def parents(ui, repo, file_=None, **opts):
3915 def parents(ui, repo, file_=None, **opts):
3916 """show the parents of the working directory or revision (DEPRECATED)
3916 """show the parents of the working directory or revision (DEPRECATED)
3917
3917
3918 Print the working directory's parent revisions. If a revision is
3918 Print the working directory's parent revisions. If a revision is
3919 given via -r/--rev, the parent of that revision will be printed.
3919 given via -r/--rev, the parent of that revision will be printed.
3920 If a file argument is given, the revision in which the file was
3920 If a file argument is given, the revision in which the file was
3921 last changed (before the working directory revision or the
3921 last changed (before the working directory revision or the
3922 argument to --rev if given) is printed.
3922 argument to --rev if given) is printed.
3923
3923
3924 This command is equivalent to::
3924 This command is equivalent to::
3925
3925
3926 hg log -r "p1()+p2()" or
3926 hg log -r "p1()+p2()" or
3927 hg log -r "p1(REV)+p2(REV)" or
3927 hg log -r "p1(REV)+p2(REV)" or
3928 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3928 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3929 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3929 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3930
3930
3931 See :hg:`summary` and :hg:`help revsets` for related information.
3931 See :hg:`summary` and :hg:`help revsets` for related information.
3932
3932
3933 Returns 0 on success.
3933 Returns 0 on success.
3934 """
3934 """
3935
3935
3936 opts = pycompat.byteskwargs(opts)
3936 opts = pycompat.byteskwargs(opts)
3937 rev = opts.get('rev')
3937 rev = opts.get('rev')
3938 if rev:
3938 if rev:
3939 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3939 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3940 ctx = scmutil.revsingle(repo, rev, None)
3940 ctx = scmutil.revsingle(repo, rev, None)
3941
3941
3942 if file_:
3942 if file_:
3943 m = scmutil.match(ctx, (file_,), opts)
3943 m = scmutil.match(ctx, (file_,), opts)
3944 if m.anypats() or len(m.files()) != 1:
3944 if m.anypats() or len(m.files()) != 1:
3945 raise error.Abort(_('can only specify an explicit filename'))
3945 raise error.Abort(_('can only specify an explicit filename'))
3946 file_ = m.files()[0]
3946 file_ = m.files()[0]
3947 filenodes = []
3947 filenodes = []
3948 for cp in ctx.parents():
3948 for cp in ctx.parents():
3949 if not cp:
3949 if not cp:
3950 continue
3950 continue
3951 try:
3951 try:
3952 filenodes.append(cp.filenode(file_))
3952 filenodes.append(cp.filenode(file_))
3953 except error.LookupError:
3953 except error.LookupError:
3954 pass
3954 pass
3955 if not filenodes:
3955 if not filenodes:
3956 raise error.Abort(_("'%s' not found in manifest!") % file_)
3956 raise error.Abort(_("'%s' not found in manifest!") % file_)
3957 p = []
3957 p = []
3958 for fn in filenodes:
3958 for fn in filenodes:
3959 fctx = repo.filectx(file_, fileid=fn)
3959 fctx = repo.filectx(file_, fileid=fn)
3960 p.append(fctx.node())
3960 p.append(fctx.node())
3961 else:
3961 else:
3962 p = [cp.node() for cp in ctx.parents()]
3962 p = [cp.node() for cp in ctx.parents()]
3963
3963
3964 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3964 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3965 for n in p:
3965 for n in p:
3966 if n != nullid:
3966 if n != nullid:
3967 displayer.show(repo[n])
3967 displayer.show(repo[n])
3968 displayer.close()
3968 displayer.close()
3969
3969
3970 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True,
3970 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True,
3971 intents={INTENT_READONLY})
3971 intents={INTENT_READONLY})
3972 def paths(ui, repo, search=None, **opts):
3972 def paths(ui, repo, search=None, **opts):
3973 """show aliases for remote repositories
3973 """show aliases for remote repositories
3974
3974
3975 Show definition of symbolic path name NAME. If no name is given,
3975 Show definition of symbolic path name NAME. If no name is given,
3976 show definition of all available names.
3976 show definition of all available names.
3977
3977
3978 Option -q/--quiet suppresses all output when searching for NAME
3978 Option -q/--quiet suppresses all output when searching for NAME
3979 and shows only the path names when listing all definitions.
3979 and shows only the path names when listing all definitions.
3980
3980
3981 Path names are defined in the [paths] section of your
3981 Path names are defined in the [paths] section of your
3982 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3982 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3983 repository, ``.hg/hgrc`` is used, too.
3983 repository, ``.hg/hgrc`` is used, too.
3984
3984
3985 The path names ``default`` and ``default-push`` have a special
3985 The path names ``default`` and ``default-push`` have a special
3986 meaning. When performing a push or pull operation, they are used
3986 meaning. When performing a push or pull operation, they are used
3987 as fallbacks if no location is specified on the command-line.
3987 as fallbacks if no location is specified on the command-line.
3988 When ``default-push`` is set, it will be used for push and
3988 When ``default-push`` is set, it will be used for push and
3989 ``default`` will be used for pull; otherwise ``default`` is used
3989 ``default`` will be used for pull; otherwise ``default`` is used
3990 as the fallback for both. When cloning a repository, the clone
3990 as the fallback for both. When cloning a repository, the clone
3991 source is written as ``default`` in ``.hg/hgrc``.
3991 source is written as ``default`` in ``.hg/hgrc``.
3992
3992
3993 .. note::
3993 .. note::
3994
3994
3995 ``default`` and ``default-push`` apply to all inbound (e.g.
3995 ``default`` and ``default-push`` apply to all inbound (e.g.
3996 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3996 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3997 and :hg:`bundle`) operations.
3997 and :hg:`bundle`) operations.
3998
3998
3999 See :hg:`help urls` for more information.
3999 See :hg:`help urls` for more information.
4000
4000
4001 Returns 0 on success.
4001 Returns 0 on success.
4002 """
4002 """
4003
4003
4004 opts = pycompat.byteskwargs(opts)
4004 opts = pycompat.byteskwargs(opts)
4005 ui.pager('paths')
4005 ui.pager('paths')
4006 if search:
4006 if search:
4007 pathitems = [(name, path) for name, path in ui.paths.iteritems()
4007 pathitems = [(name, path) for name, path in ui.paths.iteritems()
4008 if name == search]
4008 if name == search]
4009 else:
4009 else:
4010 pathitems = sorted(ui.paths.iteritems())
4010 pathitems = sorted(ui.paths.iteritems())
4011
4011
4012 fm = ui.formatter('paths', opts)
4012 fm = ui.formatter('paths', opts)
4013 if fm.isplain():
4013 if fm.isplain():
4014 hidepassword = util.hidepassword
4014 hidepassword = util.hidepassword
4015 else:
4015 else:
4016 hidepassword = bytes
4016 hidepassword = bytes
4017 if ui.quiet:
4017 if ui.quiet:
4018 namefmt = '%s\n'
4018 namefmt = '%s\n'
4019 else:
4019 else:
4020 namefmt = '%s = '
4020 namefmt = '%s = '
4021 showsubopts = not search and not ui.quiet
4021 showsubopts = not search and not ui.quiet
4022
4022
4023 for name, path in pathitems:
4023 for name, path in pathitems:
4024 fm.startitem()
4024 fm.startitem()
4025 fm.condwrite(not search, 'name', namefmt, name)
4025 fm.condwrite(not search, 'name', namefmt, name)
4026 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
4026 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
4027 for subopt, value in sorted(path.suboptions.items()):
4027 for subopt, value in sorted(path.suboptions.items()):
4028 assert subopt not in ('name', 'url')
4028 assert subopt not in ('name', 'url')
4029 if showsubopts:
4029 if showsubopts:
4030 fm.plain('%s:%s = ' % (name, subopt))
4030 fm.plain('%s:%s = ' % (name, subopt))
4031 fm.condwrite(showsubopts, subopt, '%s\n', value)
4031 fm.condwrite(showsubopts, subopt, '%s\n', value)
4032
4032
4033 fm.end()
4033 fm.end()
4034
4034
4035 if search and not pathitems:
4035 if search and not pathitems:
4036 if not ui.quiet:
4036 if not ui.quiet:
4037 ui.warn(_("not found!\n"))
4037 ui.warn(_("not found!\n"))
4038 return 1
4038 return 1
4039 else:
4039 else:
4040 return 0
4040 return 0
4041
4041
4042 @command('phase',
4042 @command('phase',
4043 [('p', 'public', False, _('set changeset phase to public')),
4043 [('p', 'public', False, _('set changeset phase to public')),
4044 ('d', 'draft', False, _('set changeset phase to draft')),
4044 ('d', 'draft', False, _('set changeset phase to draft')),
4045 ('s', 'secret', False, _('set changeset phase to secret')),
4045 ('s', 'secret', False, _('set changeset phase to secret')),
4046 ('f', 'force', False, _('allow to move boundary backward')),
4046 ('f', 'force', False, _('allow to move boundary backward')),
4047 ('r', 'rev', [], _('target revision'), _('REV')),
4047 ('r', 'rev', [], _('target revision'), _('REV')),
4048 ],
4048 ],
4049 _('[-p|-d|-s] [-f] [-r] [REV...]'))
4049 _('[-p|-d|-s] [-f] [-r] [REV...]'))
4050 def phase(ui, repo, *revs, **opts):
4050 def phase(ui, repo, *revs, **opts):
4051 """set or show the current phase name
4051 """set or show the current phase name
4052
4052
4053 With no argument, show the phase name of the current revision(s).
4053 With no argument, show the phase name of the current revision(s).
4054
4054
4055 With one of -p/--public, -d/--draft or -s/--secret, change the
4055 With one of -p/--public, -d/--draft or -s/--secret, change the
4056 phase value of the specified revisions.
4056 phase value of the specified revisions.
4057
4057
4058 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
4058 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
4059 lower phase to a higher phase. Phases are ordered as follows::
4059 lower phase to a higher phase. Phases are ordered as follows::
4060
4060
4061 public < draft < secret
4061 public < draft < secret
4062
4062
4063 Returns 0 on success, 1 if some phases could not be changed.
4063 Returns 0 on success, 1 if some phases could not be changed.
4064
4064
4065 (For more information about the phases concept, see :hg:`help phases`.)
4065 (For more information about the phases concept, see :hg:`help phases`.)
4066 """
4066 """
4067 opts = pycompat.byteskwargs(opts)
4067 opts = pycompat.byteskwargs(opts)
4068 # search for a unique phase argument
4068 # search for a unique phase argument
4069 targetphase = None
4069 targetphase = None
4070 for idx, name in enumerate(phases.phasenames):
4070 for idx, name in enumerate(phases.phasenames):
4071 if opts.get(name, False):
4071 if opts.get(name, False):
4072 if targetphase is not None:
4072 if targetphase is not None:
4073 raise error.Abort(_('only one phase can be specified'))
4073 raise error.Abort(_('only one phase can be specified'))
4074 targetphase = idx
4074 targetphase = idx
4075
4075
4076 # look for specified revision
4076 # look for specified revision
4077 revs = list(revs)
4077 revs = list(revs)
4078 revs.extend(opts['rev'])
4078 revs.extend(opts['rev'])
4079 if not revs:
4079 if not revs:
4080 # display both parents as the second parent phase can influence
4080 # display both parents as the second parent phase can influence
4081 # the phase of a merge commit
4081 # the phase of a merge commit
4082 revs = [c.rev() for c in repo[None].parents()]
4082 revs = [c.rev() for c in repo[None].parents()]
4083
4083
4084 revs = scmutil.revrange(repo, revs)
4084 revs = scmutil.revrange(repo, revs)
4085
4085
4086 ret = 0
4086 ret = 0
4087 if targetphase is None:
4087 if targetphase is None:
4088 # display
4088 # display
4089 for r in revs:
4089 for r in revs:
4090 ctx = repo[r]
4090 ctx = repo[r]
4091 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4091 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4092 else:
4092 else:
4093 with repo.lock(), repo.transaction("phase") as tr:
4093 with repo.lock(), repo.transaction("phase") as tr:
4094 # set phase
4094 # set phase
4095 if not revs:
4095 if not revs:
4096 raise error.Abort(_('empty revision set'))
4096 raise error.Abort(_('empty revision set'))
4097 nodes = [repo[r].node() for r in revs]
4097 nodes = [repo[r].node() for r in revs]
4098 # moving revision from public to draft may hide them
4098 # moving revision from public to draft may hide them
4099 # We have to check result on an unfiltered repository
4099 # We have to check result on an unfiltered repository
4100 unfi = repo.unfiltered()
4100 unfi = repo.unfiltered()
4101 getphase = unfi._phasecache.phase
4101 getphase = unfi._phasecache.phase
4102 olddata = [getphase(unfi, r) for r in unfi]
4102 olddata = [getphase(unfi, r) for r in unfi]
4103 phases.advanceboundary(repo, tr, targetphase, nodes)
4103 phases.advanceboundary(repo, tr, targetphase, nodes)
4104 if opts['force']:
4104 if opts['force']:
4105 phases.retractboundary(repo, tr, targetphase, nodes)
4105 phases.retractboundary(repo, tr, targetphase, nodes)
4106 getphase = unfi._phasecache.phase
4106 getphase = unfi._phasecache.phase
4107 newdata = [getphase(unfi, r) for r in unfi]
4107 newdata = [getphase(unfi, r) for r in unfi]
4108 changes = sum(newdata[r] != olddata[r] for r in unfi)
4108 changes = sum(newdata[r] != olddata[r] for r in unfi)
4109 cl = unfi.changelog
4109 cl = unfi.changelog
4110 rejected = [n for n in nodes
4110 rejected = [n for n in nodes
4111 if newdata[cl.rev(n)] < targetphase]
4111 if newdata[cl.rev(n)] < targetphase]
4112 if rejected:
4112 if rejected:
4113 ui.warn(_('cannot move %i changesets to a higher '
4113 ui.warn(_('cannot move %i changesets to a higher '
4114 'phase, use --force\n') % len(rejected))
4114 'phase, use --force\n') % len(rejected))
4115 ret = 1
4115 ret = 1
4116 if changes:
4116 if changes:
4117 msg = _('phase changed for %i changesets\n') % changes
4117 msg = _('phase changed for %i changesets\n') % changes
4118 if ret:
4118 if ret:
4119 ui.status(msg)
4119 ui.status(msg)
4120 else:
4120 else:
4121 ui.note(msg)
4121 ui.note(msg)
4122 else:
4122 else:
4123 ui.warn(_('no phases changed\n'))
4123 ui.warn(_('no phases changed\n'))
4124 return ret
4124 return ret
4125
4125
4126 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
4126 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
4127 """Run after a changegroup has been added via pull/unbundle
4127 """Run after a changegroup has been added via pull/unbundle
4128
4128
4129 This takes arguments below:
4129 This takes arguments below:
4130
4130
4131 :modheads: change of heads by pull/unbundle
4131 :modheads: change of heads by pull/unbundle
4132 :optupdate: updating working directory is needed or not
4132 :optupdate: updating working directory is needed or not
4133 :checkout: update destination revision (or None to default destination)
4133 :checkout: update destination revision (or None to default destination)
4134 :brev: a name, which might be a bookmark to be activated after updating
4134 :brev: a name, which might be a bookmark to be activated after updating
4135 """
4135 """
4136 if modheads == 0:
4136 if modheads == 0:
4137 return
4137 return
4138 if optupdate:
4138 if optupdate:
4139 try:
4139 try:
4140 return hg.updatetotally(ui, repo, checkout, brev)
4140 return hg.updatetotally(ui, repo, checkout, brev)
4141 except error.UpdateAbort as inst:
4141 except error.UpdateAbort as inst:
4142 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
4142 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
4143 hint = inst.hint
4143 hint = inst.hint
4144 raise error.UpdateAbort(msg, hint=hint)
4144 raise error.UpdateAbort(msg, hint=hint)
4145 if modheads > 1:
4145 if modheads > 1:
4146 currentbranchheads = len(repo.branchheads())
4146 currentbranchheads = len(repo.branchheads())
4147 if currentbranchheads == modheads:
4147 if currentbranchheads == modheads:
4148 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4148 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4149 elif currentbranchheads > 1:
4149 elif currentbranchheads > 1:
4150 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4150 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4151 "merge)\n"))
4151 "merge)\n"))
4152 else:
4152 else:
4153 ui.status(_("(run 'hg heads' to see heads)\n"))
4153 ui.status(_("(run 'hg heads' to see heads)\n"))
4154 elif not ui.configbool('commands', 'update.requiredest'):
4154 elif not ui.configbool('commands', 'update.requiredest'):
4155 ui.status(_("(run 'hg update' to get a working copy)\n"))
4155 ui.status(_("(run 'hg update' to get a working copy)\n"))
4156
4156
4157 @command('^pull',
4157 @command('^pull',
4158 [('u', 'update', None,
4158 [('u', 'update', None,
4159 _('update to new branch head if new descendants were pulled')),
4159 _('update to new branch head if new descendants were pulled')),
4160 ('f', 'force', None, _('run even when remote repository is unrelated')),
4160 ('f', 'force', None, _('run even when remote repository is unrelated')),
4161 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4161 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4162 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4162 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4163 ('b', 'branch', [], _('a specific branch you would like to pull'),
4163 ('b', 'branch', [], _('a specific branch you would like to pull'),
4164 _('BRANCH')),
4164 _('BRANCH')),
4165 ] + remoteopts,
4165 ] + remoteopts,
4166 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4166 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
4167 def pull(ui, repo, source="default", **opts):
4167 def pull(ui, repo, source="default", **opts):
4168 """pull changes from the specified source
4168 """pull changes from the specified source
4169
4169
4170 Pull changes from a remote repository to a local one.
4170 Pull changes from a remote repository to a local one.
4171
4171
4172 This finds all changes from the repository at the specified path
4172 This finds all changes from the repository at the specified path
4173 or URL and adds them to a local repository (the current one unless
4173 or URL and adds them to a local repository (the current one unless
4174 -R is specified). By default, this does not update the copy of the
4174 -R is specified). By default, this does not update the copy of the
4175 project in the working directory.
4175 project in the working directory.
4176
4176
4177 When cloning from servers that support it, Mercurial may fetch
4177 When cloning from servers that support it, Mercurial may fetch
4178 pre-generated data. When this is done, hooks operating on incoming
4178 pre-generated data. When this is done, hooks operating on incoming
4179 changesets and changegroups may fire more than once, once for each
4179 changesets and changegroups may fire more than once, once for each
4180 pre-generated bundle and as well as for any additional remaining
4180 pre-generated bundle and as well as for any additional remaining
4181 data. See :hg:`help -e clonebundles` for more.
4181 data. See :hg:`help -e clonebundles` for more.
4182
4182
4183 Use :hg:`incoming` if you want to see what would have been added
4183 Use :hg:`incoming` if you want to see what would have been added
4184 by a pull at the time you issued this command. If you then decide
4184 by a pull at the time you issued this command. If you then decide
4185 to add those changes to the repository, you should use :hg:`pull
4185 to add those changes to the repository, you should use :hg:`pull
4186 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4186 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4187
4187
4188 If SOURCE is omitted, the 'default' path will be used.
4188 If SOURCE is omitted, the 'default' path will be used.
4189 See :hg:`help urls` for more information.
4189 See :hg:`help urls` for more information.
4190
4190
4191 Specifying bookmark as ``.`` is equivalent to specifying the active
4191 Specifying bookmark as ``.`` is equivalent to specifying the active
4192 bookmark's name.
4192 bookmark's name.
4193
4193
4194 Returns 0 on success, 1 if an update had unresolved files.
4194 Returns 0 on success, 1 if an update had unresolved files.
4195 """
4195 """
4196
4196
4197 opts = pycompat.byteskwargs(opts)
4197 opts = pycompat.byteskwargs(opts)
4198 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
4198 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
4199 msg = _('update destination required by configuration')
4199 msg = _('update destination required by configuration')
4200 hint = _('use hg pull followed by hg update DEST')
4200 hint = _('use hg pull followed by hg update DEST')
4201 raise error.Abort(msg, hint=hint)
4201 raise error.Abort(msg, hint=hint)
4202
4202
4203 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4203 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4204 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4204 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4205 other = hg.peer(repo, opts, source)
4205 other = hg.peer(repo, opts, source)
4206 try:
4206 try:
4207 revs, checkout = hg.addbranchrevs(repo, other, branches,
4207 revs, checkout = hg.addbranchrevs(repo, other, branches,
4208 opts.get('rev'))
4208 opts.get('rev'))
4209
4209
4210
4210
4211 pullopargs = {}
4211 pullopargs = {}
4212 if opts.get('bookmark'):
4212 if opts.get('bookmark'):
4213 if not revs:
4213 if not revs:
4214 revs = []
4214 revs = []
4215 # The list of bookmark used here is not the one used to actually
4215 # The list of bookmark used here is not the one used to actually
4216 # update the bookmark name. This can result in the revision pulled
4216 # update the bookmark name. This can result in the revision pulled
4217 # not ending up with the name of the bookmark because of a race
4217 # not ending up with the name of the bookmark because of a race
4218 # condition on the server. (See issue 4689 for details)
4218 # condition on the server. (See issue 4689 for details)
4219 remotebookmarks = other.listkeys('bookmarks')
4219 remotebookmarks = other.listkeys('bookmarks')
4220 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4220 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4221 pullopargs['remotebookmarks'] = remotebookmarks
4221 pullopargs['remotebookmarks'] = remotebookmarks
4222 for b in opts['bookmark']:
4222 for b in opts['bookmark']:
4223 b = repo._bookmarks.expandname(b)
4223 b = repo._bookmarks.expandname(b)
4224 if b not in remotebookmarks:
4224 if b not in remotebookmarks:
4225 raise error.Abort(_('remote bookmark %s not found!') % b)
4225 raise error.Abort(_('remote bookmark %s not found!') % b)
4226 revs.append(hex(remotebookmarks[b]))
4226 revs.append(hex(remotebookmarks[b]))
4227
4227
4228 if revs:
4228 if revs:
4229 try:
4229 try:
4230 # When 'rev' is a bookmark name, we cannot guarantee that it
4230 # When 'rev' is a bookmark name, we cannot guarantee that it
4231 # will be updated with that name because of a race condition
4231 # will be updated with that name because of a race condition
4232 # server side. (See issue 4689 for details)
4232 # server side. (See issue 4689 for details)
4233 oldrevs = revs
4233 oldrevs = revs
4234 revs = [] # actually, nodes
4234 revs = [] # actually, nodes
4235 for r in oldrevs:
4235 for r in oldrevs:
4236 with other.commandexecutor() as e:
4236 with other.commandexecutor() as e:
4237 node = e.callcommand('lookup', {'key': r}).result()
4237 node = e.callcommand('lookup', {'key': r}).result()
4238
4238
4239 revs.append(node)
4239 revs.append(node)
4240 if r == checkout:
4240 if r == checkout:
4241 checkout = node
4241 checkout = node
4242 except error.CapabilityError:
4242 except error.CapabilityError:
4243 err = _("other repository doesn't support revision lookup, "
4243 err = _("other repository doesn't support revision lookup, "
4244 "so a rev cannot be specified.")
4244 "so a rev cannot be specified.")
4245 raise error.Abort(err)
4245 raise error.Abort(err)
4246
4246
4247 wlock = util.nullcontextmanager()
4247 wlock = util.nullcontextmanager()
4248 if opts.get('update'):
4248 if opts.get('update'):
4249 wlock = repo.wlock()
4249 wlock = repo.wlock()
4250 with wlock:
4250 with wlock:
4251 pullopargs.update(opts.get('opargs', {}))
4251 pullopargs.update(opts.get('opargs', {}))
4252 modheads = exchange.pull(repo, other, heads=revs,
4252 modheads = exchange.pull(repo, other, heads=revs,
4253 force=opts.get('force'),
4253 force=opts.get('force'),
4254 bookmarks=opts.get('bookmark', ()),
4254 bookmarks=opts.get('bookmark', ()),
4255 opargs=pullopargs).cgresult
4255 opargs=pullopargs).cgresult
4256
4256
4257 # brev is a name, which might be a bookmark to be activated at
4257 # brev is a name, which might be a bookmark to be activated at
4258 # the end of the update. In other words, it is an explicit
4258 # the end of the update. In other words, it is an explicit
4259 # destination of the update
4259 # destination of the update
4260 brev = None
4260 brev = None
4261
4261
4262 if checkout:
4262 if checkout:
4263 checkout = repo.changelog.rev(checkout)
4263 checkout = repo.changelog.rev(checkout)
4264
4264
4265 # order below depends on implementation of
4265 # order below depends on implementation of
4266 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4266 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4267 # because 'checkout' is determined without it.
4267 # because 'checkout' is determined without it.
4268 if opts.get('rev'):
4268 if opts.get('rev'):
4269 brev = opts['rev'][0]
4269 brev = opts['rev'][0]
4270 elif opts.get('branch'):
4270 elif opts.get('branch'):
4271 brev = opts['branch'][0]
4271 brev = opts['branch'][0]
4272 else:
4272 else:
4273 brev = branches[0]
4273 brev = branches[0]
4274 repo._subtoppath = source
4274 repo._subtoppath = source
4275 try:
4275 try:
4276 ret = postincoming(ui, repo, modheads, opts.get('update'),
4276 ret = postincoming(ui, repo, modheads, opts.get('update'),
4277 checkout, brev)
4277 checkout, brev)
4278
4278
4279 finally:
4279 finally:
4280 del repo._subtoppath
4280 del repo._subtoppath
4281
4281
4282 finally:
4282 finally:
4283 other.close()
4283 other.close()
4284 return ret
4284 return ret
4285
4285
4286 @command('^push',
4286 @command('^push',
4287 [('f', 'force', None, _('force push')),
4287 [('f', 'force', None, _('force push')),
4288 ('r', 'rev', [],
4288 ('r', 'rev', [],
4289 _('a changeset intended to be included in the destination'),
4289 _('a changeset intended to be included in the destination'),
4290 _('REV')),
4290 _('REV')),
4291 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4291 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4292 ('b', 'branch', [],
4292 ('b', 'branch', [],
4293 _('a specific branch you would like to push'), _('BRANCH')),
4293 _('a specific branch you would like to push'), _('BRANCH')),
4294 ('', 'new-branch', False, _('allow pushing a new branch')),
4294 ('', 'new-branch', False, _('allow pushing a new branch')),
4295 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4295 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4296 ] + remoteopts,
4296 ] + remoteopts,
4297 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4297 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4298 def push(ui, repo, dest=None, **opts):
4298 def push(ui, repo, dest=None, **opts):
4299 """push changes to the specified destination
4299 """push changes to the specified destination
4300
4300
4301 Push changesets from the local repository to the specified
4301 Push changesets from the local repository to the specified
4302 destination.
4302 destination.
4303
4303
4304 This operation is symmetrical to pull: it is identical to a pull
4304 This operation is symmetrical to pull: it is identical to a pull
4305 in the destination repository from the current one.
4305 in the destination repository from the current one.
4306
4306
4307 By default, push will not allow creation of new heads at the
4307 By default, push will not allow creation of new heads at the
4308 destination, since multiple heads would make it unclear which head
4308 destination, since multiple heads would make it unclear which head
4309 to use. In this situation, it is recommended to pull and merge
4309 to use. In this situation, it is recommended to pull and merge
4310 before pushing.
4310 before pushing.
4311
4311
4312 Use --new-branch if you want to allow push to create a new named
4312 Use --new-branch if you want to allow push to create a new named
4313 branch that is not present at the destination. This allows you to
4313 branch that is not present at the destination. This allows you to
4314 only create a new branch without forcing other changes.
4314 only create a new branch without forcing other changes.
4315
4315
4316 .. note::
4316 .. note::
4317
4317
4318 Extra care should be taken with the -f/--force option,
4318 Extra care should be taken with the -f/--force option,
4319 which will push all new heads on all branches, an action which will
4319 which will push all new heads on all branches, an action which will
4320 almost always cause confusion for collaborators.
4320 almost always cause confusion for collaborators.
4321
4321
4322 If -r/--rev is used, the specified revision and all its ancestors
4322 If -r/--rev is used, the specified revision and all its ancestors
4323 will be pushed to the remote repository.
4323 will be pushed to the remote repository.
4324
4324
4325 If -B/--bookmark is used, the specified bookmarked revision, its
4325 If -B/--bookmark is used, the specified bookmarked revision, its
4326 ancestors, and the bookmark will be pushed to the remote
4326 ancestors, and the bookmark will be pushed to the remote
4327 repository. Specifying ``.`` is equivalent to specifying the active
4327 repository. Specifying ``.`` is equivalent to specifying the active
4328 bookmark's name.
4328 bookmark's name.
4329
4329
4330 Please see :hg:`help urls` for important details about ``ssh://``
4330 Please see :hg:`help urls` for important details about ``ssh://``
4331 URLs. If DESTINATION is omitted, a default path will be used.
4331 URLs. If DESTINATION is omitted, a default path will be used.
4332
4332
4333 .. container:: verbose
4333 .. container:: verbose
4334
4334
4335 The --pushvars option sends strings to the server that become
4335 The --pushvars option sends strings to the server that become
4336 environment variables prepended with ``HG_USERVAR_``. For example,
4336 environment variables prepended with ``HG_USERVAR_``. For example,
4337 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4337 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4338 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4338 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4339
4339
4340 pushvars can provide for user-overridable hooks as well as set debug
4340 pushvars can provide for user-overridable hooks as well as set debug
4341 levels. One example is having a hook that blocks commits containing
4341 levels. One example is having a hook that blocks commits containing
4342 conflict markers, but enables the user to override the hook if the file
4342 conflict markers, but enables the user to override the hook if the file
4343 is using conflict markers for testing purposes or the file format has
4343 is using conflict markers for testing purposes or the file format has
4344 strings that look like conflict markers.
4344 strings that look like conflict markers.
4345
4345
4346 By default, servers will ignore `--pushvars`. To enable it add the
4346 By default, servers will ignore `--pushvars`. To enable it add the
4347 following to your configuration file::
4347 following to your configuration file::
4348
4348
4349 [push]
4349 [push]
4350 pushvars.server = true
4350 pushvars.server = true
4351
4351
4352 Returns 0 if push was successful, 1 if nothing to push.
4352 Returns 0 if push was successful, 1 if nothing to push.
4353 """
4353 """
4354
4354
4355 opts = pycompat.byteskwargs(opts)
4355 opts = pycompat.byteskwargs(opts)
4356 if opts.get('bookmark'):
4356 if opts.get('bookmark'):
4357 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4357 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4358 for b in opts['bookmark']:
4358 for b in opts['bookmark']:
4359 # translate -B options to -r so changesets get pushed
4359 # translate -B options to -r so changesets get pushed
4360 b = repo._bookmarks.expandname(b)
4360 b = repo._bookmarks.expandname(b)
4361 if b in repo._bookmarks:
4361 if b in repo._bookmarks:
4362 opts.setdefault('rev', []).append(b)
4362 opts.setdefault('rev', []).append(b)
4363 else:
4363 else:
4364 # if we try to push a deleted bookmark, translate it to null
4364 # if we try to push a deleted bookmark, translate it to null
4365 # this lets simultaneous -r, -b options continue working
4365 # this lets simultaneous -r, -b options continue working
4366 opts.setdefault('rev', []).append("null")
4366 opts.setdefault('rev', []).append("null")
4367
4367
4368 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4368 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4369 if not path:
4369 if not path:
4370 raise error.Abort(_('default repository not configured!'),
4370 raise error.Abort(_('default repository not configured!'),
4371 hint=_("see 'hg help config.paths'"))
4371 hint=_("see 'hg help config.paths'"))
4372 dest = path.pushloc or path.loc
4372 dest = path.pushloc or path.loc
4373 branches = (path.branch, opts.get('branch') or [])
4373 branches = (path.branch, opts.get('branch') or [])
4374 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4374 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4375 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4375 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4376 other = hg.peer(repo, opts, dest)
4376 other = hg.peer(repo, opts, dest)
4377
4377
4378 if revs:
4378 if revs:
4379 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4379 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4380 if not revs:
4380 if not revs:
4381 raise error.Abort(_("specified revisions evaluate to an empty set"),
4381 raise error.Abort(_("specified revisions evaluate to an empty set"),
4382 hint=_("use different revision arguments"))
4382 hint=_("use different revision arguments"))
4383 elif path.pushrev:
4383 elif path.pushrev:
4384 # It doesn't make any sense to specify ancestor revisions. So limit
4384 # It doesn't make any sense to specify ancestor revisions. So limit
4385 # to DAG heads to make discovery simpler.
4385 # to DAG heads to make discovery simpler.
4386 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4386 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4387 revs = scmutil.revrange(repo, [expr])
4387 revs = scmutil.revrange(repo, [expr])
4388 revs = [repo[rev].node() for rev in revs]
4388 revs = [repo[rev].node() for rev in revs]
4389 if not revs:
4389 if not revs:
4390 raise error.Abort(_('default push revset for path evaluates to an '
4390 raise error.Abort(_('default push revset for path evaluates to an '
4391 'empty set'))
4391 'empty set'))
4392
4392
4393 repo._subtoppath = dest
4393 repo._subtoppath = dest
4394 try:
4394 try:
4395 # push subrepos depth-first for coherent ordering
4395 # push subrepos depth-first for coherent ordering
4396 c = repo['.']
4396 c = repo['.']
4397 subs = c.substate # only repos that are committed
4397 subs = c.substate # only repos that are committed
4398 for s in sorted(subs):
4398 for s in sorted(subs):
4399 result = c.sub(s).push(opts)
4399 result = c.sub(s).push(opts)
4400 if result == 0:
4400 if result == 0:
4401 return not result
4401 return not result
4402 finally:
4402 finally:
4403 del repo._subtoppath
4403 del repo._subtoppath
4404
4404
4405 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4405 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4406 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4406 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4407
4407
4408 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4408 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4409 newbranch=opts.get('new_branch'),
4409 newbranch=opts.get('new_branch'),
4410 bookmarks=opts.get('bookmark', ()),
4410 bookmarks=opts.get('bookmark', ()),
4411 opargs=opargs)
4411 opargs=opargs)
4412
4412
4413 result = not pushop.cgresult
4413 result = not pushop.cgresult
4414
4414
4415 if pushop.bkresult is not None:
4415 if pushop.bkresult is not None:
4416 if pushop.bkresult == 2:
4416 if pushop.bkresult == 2:
4417 result = 2
4417 result = 2
4418 elif not result and pushop.bkresult:
4418 elif not result and pushop.bkresult:
4419 result = 2
4419 result = 2
4420
4420
4421 return result
4421 return result
4422
4422
4423 @command('recover', [])
4423 @command('recover', [])
4424 def recover(ui, repo):
4424 def recover(ui, repo):
4425 """roll back an interrupted transaction
4425 """roll back an interrupted transaction
4426
4426
4427 Recover from an interrupted commit or pull.
4427 Recover from an interrupted commit or pull.
4428
4428
4429 This command tries to fix the repository status after an
4429 This command tries to fix the repository status after an
4430 interrupted operation. It should only be necessary when Mercurial
4430 interrupted operation. It should only be necessary when Mercurial
4431 suggests it.
4431 suggests it.
4432
4432
4433 Returns 0 if successful, 1 if nothing to recover or verify fails.
4433 Returns 0 if successful, 1 if nothing to recover or verify fails.
4434 """
4434 """
4435 if repo.recover():
4435 if repo.recover():
4436 return hg.verify(repo)
4436 return hg.verify(repo)
4437 return 1
4437 return 1
4438
4438
4439 @command('^remove|rm',
4439 @command('^remove|rm',
4440 [('A', 'after', None, _('record delete for missing files')),
4440 [('A', 'after', None, _('record delete for missing files')),
4441 ('f', 'force', None,
4441 ('f', 'force', None,
4442 _('forget added files, delete modified files')),
4442 _('forget added files, delete modified files')),
4443 ] + subrepoopts + walkopts + dryrunopts,
4443 ] + subrepoopts + walkopts + dryrunopts,
4444 _('[OPTION]... FILE...'),
4444 _('[OPTION]... FILE...'),
4445 inferrepo=True)
4445 inferrepo=True)
4446 def remove(ui, repo, *pats, **opts):
4446 def remove(ui, repo, *pats, **opts):
4447 """remove the specified files on the next commit
4447 """remove the specified files on the next commit
4448
4448
4449 Schedule the indicated files for removal from the current branch.
4449 Schedule the indicated files for removal from the current branch.
4450
4450
4451 This command schedules the files to be removed at the next commit.
4451 This command schedules the files to be removed at the next commit.
4452 To undo a remove before that, see :hg:`revert`. To undo added
4452 To undo a remove before that, see :hg:`revert`. To undo added
4453 files, see :hg:`forget`.
4453 files, see :hg:`forget`.
4454
4454
4455 .. container:: verbose
4455 .. container:: verbose
4456
4456
4457 -A/--after can be used to remove only files that have already
4457 -A/--after can be used to remove only files that have already
4458 been deleted, -f/--force can be used to force deletion, and -Af
4458 been deleted, -f/--force can be used to force deletion, and -Af
4459 can be used to remove files from the next revision without
4459 can be used to remove files from the next revision without
4460 deleting them from the working directory.
4460 deleting them from the working directory.
4461
4461
4462 The following table details the behavior of remove for different
4462 The following table details the behavior of remove for different
4463 file states (columns) and option combinations (rows). The file
4463 file states (columns) and option combinations (rows). The file
4464 states are Added [A], Clean [C], Modified [M] and Missing [!]
4464 states are Added [A], Clean [C], Modified [M] and Missing [!]
4465 (as reported by :hg:`status`). The actions are Warn, Remove
4465 (as reported by :hg:`status`). The actions are Warn, Remove
4466 (from branch) and Delete (from disk):
4466 (from branch) and Delete (from disk):
4467
4467
4468 ========= == == == ==
4468 ========= == == == ==
4469 opt/state A C M !
4469 opt/state A C M !
4470 ========= == == == ==
4470 ========= == == == ==
4471 none W RD W R
4471 none W RD W R
4472 -f R RD RD R
4472 -f R RD RD R
4473 -A W W W R
4473 -A W W W R
4474 -Af R R R R
4474 -Af R R R R
4475 ========= == == == ==
4475 ========= == == == ==
4476
4476
4477 .. note::
4477 .. note::
4478
4478
4479 :hg:`remove` never deletes files in Added [A] state from the
4479 :hg:`remove` never deletes files in Added [A] state from the
4480 working directory, not even if ``--force`` is specified.
4480 working directory, not even if ``--force`` is specified.
4481
4481
4482 Returns 0 on success, 1 if any warnings encountered.
4482 Returns 0 on success, 1 if any warnings encountered.
4483 """
4483 """
4484
4484
4485 opts = pycompat.byteskwargs(opts)
4485 opts = pycompat.byteskwargs(opts)
4486 after, force = opts.get('after'), opts.get('force')
4486 after, force = opts.get('after'), opts.get('force')
4487 dryrun = opts.get('dry_run')
4487 dryrun = opts.get('dry_run')
4488 if not pats and not after:
4488 if not pats and not after:
4489 raise error.Abort(_('no files specified'))
4489 raise error.Abort(_('no files specified'))
4490
4490
4491 m = scmutil.match(repo[None], pats, opts)
4491 m = scmutil.match(repo[None], pats, opts)
4492 subrepos = opts.get('subrepos')
4492 subrepos = opts.get('subrepos')
4493 return cmdutil.remove(ui, repo, m, "", after, force, subrepos,
4493 return cmdutil.remove(ui, repo, m, "", after, force, subrepos,
4494 dryrun=dryrun)
4494 dryrun=dryrun)
4495
4495
4496 @command('rename|move|mv',
4496 @command('rename|move|mv',
4497 [('A', 'after', None, _('record a rename that has already occurred')),
4497 [('A', 'after', None, _('record a rename that has already occurred')),
4498 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4498 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4499 ] + walkopts + dryrunopts,
4499 ] + walkopts + dryrunopts,
4500 _('[OPTION]... SOURCE... DEST'))
4500 _('[OPTION]... SOURCE... DEST'))
4501 def rename(ui, repo, *pats, **opts):
4501 def rename(ui, repo, *pats, **opts):
4502 """rename files; equivalent of copy + remove
4502 """rename files; equivalent of copy + remove
4503
4503
4504 Mark dest as copies of sources; mark sources for deletion. If dest
4504 Mark dest as copies of sources; mark sources for deletion. If dest
4505 is a directory, copies are put in that directory. If dest is a
4505 is a directory, copies are put in that directory. If dest is a
4506 file, there can only be one source.
4506 file, there can only be one source.
4507
4507
4508 By default, this command copies the contents of files as they
4508 By default, this command copies the contents of files as they
4509 exist in the working directory. If invoked with -A/--after, the
4509 exist in the working directory. If invoked with -A/--after, the
4510 operation is recorded, but no copying is performed.
4510 operation is recorded, but no copying is performed.
4511
4511
4512 This command takes effect at the next commit. To undo a rename
4512 This command takes effect at the next commit. To undo a rename
4513 before that, see :hg:`revert`.
4513 before that, see :hg:`revert`.
4514
4514
4515 Returns 0 on success, 1 if errors are encountered.
4515 Returns 0 on success, 1 if errors are encountered.
4516 """
4516 """
4517 opts = pycompat.byteskwargs(opts)
4517 opts = pycompat.byteskwargs(opts)
4518 with repo.wlock(False):
4518 with repo.wlock(False):
4519 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4519 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4520
4520
4521 @command('resolve',
4521 @command('resolve',
4522 [('a', 'all', None, _('select all unresolved files')),
4522 [('a', 'all', None, _('select all unresolved files')),
4523 ('l', 'list', None, _('list state of files needing merge')),
4523 ('l', 'list', None, _('list state of files needing merge')),
4524 ('m', 'mark', None, _('mark files as resolved')),
4524 ('m', 'mark', None, _('mark files as resolved')),
4525 ('u', 'unmark', None, _('mark files as unresolved')),
4525 ('u', 'unmark', None, _('mark files as unresolved')),
4526 ('n', 'no-status', None, _('hide status prefix')),
4526 ('n', 'no-status', None, _('hide status prefix')),
4527 ('', 're-merge', None, _('re-merge files'))]
4527 ('', 're-merge', None, _('re-merge files'))]
4528 + mergetoolopts + walkopts + formatteropts,
4528 + mergetoolopts + walkopts + formatteropts,
4529 _('[OPTION]... [FILE]...'),
4529 _('[OPTION]... [FILE]...'),
4530 inferrepo=True)
4530 inferrepo=True)
4531 def resolve(ui, repo, *pats, **opts):
4531 def resolve(ui, repo, *pats, **opts):
4532 """redo merges or set/view the merge status of files
4532 """redo merges or set/view the merge status of files
4533
4533
4534 Merges with unresolved conflicts are often the result of
4534 Merges with unresolved conflicts are often the result of
4535 non-interactive merging using the ``internal:merge`` configuration
4535 non-interactive merging using the ``internal:merge`` configuration
4536 setting, or a command-line merge tool like ``diff3``. The resolve
4536 setting, or a command-line merge tool like ``diff3``. The resolve
4537 command is used to manage the files involved in a merge, after
4537 command is used to manage the files involved in a merge, after
4538 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4538 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4539 working directory must have two parents). See :hg:`help
4539 working directory must have two parents). See :hg:`help
4540 merge-tools` for information on configuring merge tools.
4540 merge-tools` for information on configuring merge tools.
4541
4541
4542 The resolve command can be used in the following ways:
4542 The resolve command can be used in the following ways:
4543
4543
4544 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
4544 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
4545 the specified files, discarding any previous merge attempts. Re-merging
4545 the specified files, discarding any previous merge attempts. Re-merging
4546 is not performed for files already marked as resolved. Use ``--all/-a``
4546 is not performed for files already marked as resolved. Use ``--all/-a``
4547 to select all unresolved files. ``--tool`` can be used to specify
4547 to select all unresolved files. ``--tool`` can be used to specify
4548 the merge tool used for the given files. It overrides the HGMERGE
4548 the merge tool used for the given files. It overrides the HGMERGE
4549 environment variable and your configuration files. Previous file
4549 environment variable and your configuration files. Previous file
4550 contents are saved with a ``.orig`` suffix.
4550 contents are saved with a ``.orig`` suffix.
4551
4551
4552 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4552 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4553 (e.g. after having manually fixed-up the files). The default is
4553 (e.g. after having manually fixed-up the files). The default is
4554 to mark all unresolved files.
4554 to mark all unresolved files.
4555
4555
4556 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4556 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4557 default is to mark all resolved files.
4557 default is to mark all resolved files.
4558
4558
4559 - :hg:`resolve -l`: list files which had or still have conflicts.
4559 - :hg:`resolve -l`: list files which had or still have conflicts.
4560 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4560 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4561 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4561 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4562 the list. See :hg:`help filesets` for details.
4562 the list. See :hg:`help filesets` for details.
4563
4563
4564 .. note::
4564 .. note::
4565
4565
4566 Mercurial will not let you commit files with unresolved merge
4566 Mercurial will not let you commit files with unresolved merge
4567 conflicts. You must use :hg:`resolve -m ...` before you can
4567 conflicts. You must use :hg:`resolve -m ...` before you can
4568 commit after a conflicting merge.
4568 commit after a conflicting merge.
4569
4569
4570 Returns 0 on success, 1 if any files fail a resolve attempt.
4570 Returns 0 on success, 1 if any files fail a resolve attempt.
4571 """
4571 """
4572
4572
4573 opts = pycompat.byteskwargs(opts)
4573 opts = pycompat.byteskwargs(opts)
4574 confirm = ui.configbool('commands', 'resolve.confirm')
4574 confirm = ui.configbool('commands', 'resolve.confirm')
4575 flaglist = 'all mark unmark list no_status re_merge'.split()
4575 flaglist = 'all mark unmark list no_status re_merge'.split()
4576 all, mark, unmark, show, nostatus, remerge = \
4576 all, mark, unmark, show, nostatus, remerge = \
4577 [opts.get(o) for o in flaglist]
4577 [opts.get(o) for o in flaglist]
4578
4578
4579 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
4579 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
4580 if actioncount > 1:
4580 if actioncount > 1:
4581 raise error.Abort(_("too many actions specified"))
4581 raise error.Abort(_("too many actions specified"))
4582 elif (actioncount == 0
4582 elif (actioncount == 0
4583 and ui.configbool('commands', 'resolve.explicit-re-merge')):
4583 and ui.configbool('commands', 'resolve.explicit-re-merge')):
4584 hint = _('use --mark, --unmark, --list or --re-merge')
4584 hint = _('use --mark, --unmark, --list or --re-merge')
4585 raise error.Abort(_('no action specified'), hint=hint)
4585 raise error.Abort(_('no action specified'), hint=hint)
4586 if pats and all:
4586 if pats and all:
4587 raise error.Abort(_("can't specify --all and patterns"))
4587 raise error.Abort(_("can't specify --all and patterns"))
4588 if not (all or pats or show or mark or unmark):
4588 if not (all or pats or show or mark or unmark):
4589 raise error.Abort(_('no files or directories specified'),
4589 raise error.Abort(_('no files or directories specified'),
4590 hint=('use --all to re-merge all unresolved files'))
4590 hint=('use --all to re-merge all unresolved files'))
4591
4591
4592 if confirm:
4592 if confirm:
4593 if all:
4593 if all:
4594 if ui.promptchoice(_(b're-merge all unresolved files (yn)?'
4594 if ui.promptchoice(_(b're-merge all unresolved files (yn)?'
4595 b'$$ &Yes $$ &No')):
4595 b'$$ &Yes $$ &No')):
4596 raise error.Abort(_('user quit'))
4596 raise error.Abort(_('user quit'))
4597 if mark and not pats:
4597 if mark and not pats:
4598 if ui.promptchoice(_(b'mark all unresolved files as resolved (yn)?'
4598 if ui.promptchoice(_(b'mark all unresolved files as resolved (yn)?'
4599 b'$$ &Yes $$ &No')):
4599 b'$$ &Yes $$ &No')):
4600 raise error.Abort(_('user quit'))
4600 raise error.Abort(_('user quit'))
4601 if unmark and not pats:
4601 if unmark and not pats:
4602 if ui.promptchoice(_(b'mark all resolved files as unresolved (yn)?'
4602 if ui.promptchoice(_(b'mark all resolved files as unresolved (yn)?'
4603 b'$$ &Yes $$ &No')):
4603 b'$$ &Yes $$ &No')):
4604 raise error.Abort(_('user quit'))
4604 raise error.Abort(_('user quit'))
4605
4605
4606 if show:
4606 if show:
4607 ui.pager('resolve')
4607 ui.pager('resolve')
4608 fm = ui.formatter('resolve', opts)
4608 fm = ui.formatter('resolve', opts)
4609 ms = mergemod.mergestate.read(repo)
4609 ms = mergemod.mergestate.read(repo)
4610 wctx = repo[None]
4610 wctx = repo[None]
4611 m = scmutil.match(wctx, pats, opts)
4611 m = scmutil.match(wctx, pats, opts)
4612
4612
4613 # Labels and keys based on merge state. Unresolved path conflicts show
4613 # Labels and keys based on merge state. Unresolved path conflicts show
4614 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4614 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4615 # resolved conflicts.
4615 # resolved conflicts.
4616 mergestateinfo = {
4616 mergestateinfo = {
4617 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4617 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4618 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4618 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4619 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4619 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4620 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4620 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4621 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4621 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4622 'D'),
4622 'D'),
4623 }
4623 }
4624
4624
4625 for f in ms:
4625 for f in ms:
4626 if not m(f):
4626 if not m(f):
4627 continue
4627 continue
4628
4628
4629 label, key = mergestateinfo[ms[f]]
4629 label, key = mergestateinfo[ms[f]]
4630 fm.startitem()
4630 fm.startitem()
4631 fm.context(ctx=wctx)
4631 fm.context(ctx=wctx)
4632 fm.condwrite(not nostatus, 'status', '%s ', key, label=label)
4632 fm.condwrite(not nostatus, 'status', '%s ', key, label=label)
4633 fm.write('path', '%s\n', f, label=label)
4633 fm.write('path', '%s\n', f, label=label)
4634 fm.end()
4634 fm.end()
4635 return 0
4635 return 0
4636
4636
4637 with repo.wlock():
4637 with repo.wlock():
4638 ms = mergemod.mergestate.read(repo)
4638 ms = mergemod.mergestate.read(repo)
4639
4639
4640 if not (ms.active() or repo.dirstate.p2() != nullid):
4640 if not (ms.active() or repo.dirstate.p2() != nullid):
4641 raise error.Abort(
4641 raise error.Abort(
4642 _('resolve command not applicable when not merging'))
4642 _('resolve command not applicable when not merging'))
4643
4643
4644 wctx = repo[None]
4644 wctx = repo[None]
4645
4645
4646 if (ms.mergedriver
4646 if (ms.mergedriver
4647 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4647 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4648 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4648 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4649 ms.commit()
4649 ms.commit()
4650 # allow mark and unmark to go through
4650 # allow mark and unmark to go through
4651 if not mark and not unmark and not proceed:
4651 if not mark and not unmark and not proceed:
4652 return 1
4652 return 1
4653
4653
4654 m = scmutil.match(wctx, pats, opts)
4654 m = scmutil.match(wctx, pats, opts)
4655 ret = 0
4655 ret = 0
4656 didwork = False
4656 didwork = False
4657 runconclude = False
4657 runconclude = False
4658
4658
4659 tocomplete = []
4659 tocomplete = []
4660 hasconflictmarkers = []
4660 hasconflictmarkers = []
4661 if mark:
4661 if mark:
4662 markcheck = ui.config('commands', 'resolve.mark-check')
4662 markcheck = ui.config('commands', 'resolve.mark-check')
4663 if markcheck not in ['warn', 'abort']:
4663 if markcheck not in ['warn', 'abort']:
4664 # Treat all invalid / unrecognized values as 'none'.
4664 # Treat all invalid / unrecognized values as 'none'.
4665 markcheck = False
4665 markcheck = False
4666 for f in ms:
4666 for f in ms:
4667 if not m(f):
4667 if not m(f):
4668 continue
4668 continue
4669
4669
4670 didwork = True
4670 didwork = True
4671
4671
4672 # don't let driver-resolved files be marked, and run the conclude
4672 # don't let driver-resolved files be marked, and run the conclude
4673 # step if asked to resolve
4673 # step if asked to resolve
4674 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4674 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4675 exact = m.exact(f)
4675 exact = m.exact(f)
4676 if mark:
4676 if mark:
4677 if exact:
4677 if exact:
4678 ui.warn(_('not marking %s as it is driver-resolved\n')
4678 ui.warn(_('not marking %s as it is driver-resolved\n')
4679 % f)
4679 % f)
4680 elif unmark:
4680 elif unmark:
4681 if exact:
4681 if exact:
4682 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4682 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4683 % f)
4683 % f)
4684 else:
4684 else:
4685 runconclude = True
4685 runconclude = True
4686 continue
4686 continue
4687
4687
4688 # path conflicts must be resolved manually
4688 # path conflicts must be resolved manually
4689 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4689 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4690 mergemod.MERGE_RECORD_RESOLVED_PATH):
4690 mergemod.MERGE_RECORD_RESOLVED_PATH):
4691 if mark:
4691 if mark:
4692 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4692 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4693 elif unmark:
4693 elif unmark:
4694 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4694 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4695 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4695 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4696 ui.warn(_('%s: path conflict must be resolved manually\n')
4696 ui.warn(_('%s: path conflict must be resolved manually\n')
4697 % f)
4697 % f)
4698 continue
4698 continue
4699
4699
4700 if mark:
4700 if mark:
4701 if markcheck:
4701 if markcheck:
4702 with repo.wvfs(f) as fobj:
4702 with repo.wvfs(f) as fobj:
4703 fdata = fobj.read()
4703 fdata = fobj.read()
4704 if filemerge.hasconflictmarkers(fdata) and \
4704 if filemerge.hasconflictmarkers(fdata) and \
4705 ms[f] != mergemod.MERGE_RECORD_RESOLVED:
4705 ms[f] != mergemod.MERGE_RECORD_RESOLVED:
4706 hasconflictmarkers.append(f)
4706 hasconflictmarkers.append(f)
4707 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4707 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4708 elif unmark:
4708 elif unmark:
4709 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4709 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4710 else:
4710 else:
4711 # backup pre-resolve (merge uses .orig for its own purposes)
4711 # backup pre-resolve (merge uses .orig for its own purposes)
4712 a = repo.wjoin(f)
4712 a = repo.wjoin(f)
4713 try:
4713 try:
4714 util.copyfile(a, a + ".resolve")
4714 util.copyfile(a, a + ".resolve")
4715 except (IOError, OSError) as inst:
4715 except (IOError, OSError) as inst:
4716 if inst.errno != errno.ENOENT:
4716 if inst.errno != errno.ENOENT:
4717 raise
4717 raise
4718
4718
4719 try:
4719 try:
4720 # preresolve file
4720 # preresolve file
4721 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4721 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4722 with ui.configoverride(overrides, 'resolve'):
4722 with ui.configoverride(overrides, 'resolve'):
4723 complete, r = ms.preresolve(f, wctx)
4723 complete, r = ms.preresolve(f, wctx)
4724 if not complete:
4724 if not complete:
4725 tocomplete.append(f)
4725 tocomplete.append(f)
4726 elif r:
4726 elif r:
4727 ret = 1
4727 ret = 1
4728 finally:
4728 finally:
4729 ms.commit()
4729 ms.commit()
4730
4730
4731 # replace filemerge's .orig file with our resolve file, but only
4731 # replace filemerge's .orig file with our resolve file, but only
4732 # for merges that are complete
4732 # for merges that are complete
4733 if complete:
4733 if complete:
4734 try:
4734 try:
4735 util.rename(a + ".resolve",
4735 util.rename(a + ".resolve",
4736 scmutil.origpath(ui, repo, a))
4736 scmutil.origpath(ui, repo, a))
4737 except OSError as inst:
4737 except OSError as inst:
4738 if inst.errno != errno.ENOENT:
4738 if inst.errno != errno.ENOENT:
4739 raise
4739 raise
4740
4740
4741 if hasconflictmarkers:
4741 if hasconflictmarkers:
4742 ui.warn(_('warning: the following files still have conflict '
4742 ui.warn(_('warning: the following files still have conflict '
4743 'markers:\n ') + '\n '.join(hasconflictmarkers) + '\n')
4743 'markers:\n ') + '\n '.join(hasconflictmarkers) + '\n')
4744 if markcheck == 'abort' and not all:
4744 if markcheck == 'abort' and not all:
4745 raise error.Abort(_('conflict markers detected'),
4745 raise error.Abort(_('conflict markers detected'),
4746 hint=_('use --all to mark anyway'))
4746 hint=_('use --all to mark anyway'))
4747
4747
4748 for f in tocomplete:
4748 for f in tocomplete:
4749 try:
4749 try:
4750 # resolve file
4750 # resolve file
4751 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4751 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4752 with ui.configoverride(overrides, 'resolve'):
4752 with ui.configoverride(overrides, 'resolve'):
4753 r = ms.resolve(f, wctx)
4753 r = ms.resolve(f, wctx)
4754 if r:
4754 if r:
4755 ret = 1
4755 ret = 1
4756 finally:
4756 finally:
4757 ms.commit()
4757 ms.commit()
4758
4758
4759 # replace filemerge's .orig file with our resolve file
4759 # replace filemerge's .orig file with our resolve file
4760 a = repo.wjoin(f)
4760 a = repo.wjoin(f)
4761 try:
4761 try:
4762 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4762 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4763 except OSError as inst:
4763 except OSError as inst:
4764 if inst.errno != errno.ENOENT:
4764 if inst.errno != errno.ENOENT:
4765 raise
4765 raise
4766
4766
4767 ms.commit()
4767 ms.commit()
4768 ms.recordactions()
4768 ms.recordactions()
4769
4769
4770 if not didwork and pats:
4770 if not didwork and pats:
4771 hint = None
4771 hint = None
4772 if not any([p for p in pats if p.find(':') >= 0]):
4772 if not any([p for p in pats if p.find(':') >= 0]):
4773 pats = ['path:%s' % p for p in pats]
4773 pats = ['path:%s' % p for p in pats]
4774 m = scmutil.match(wctx, pats, opts)
4774 m = scmutil.match(wctx, pats, opts)
4775 for f in ms:
4775 for f in ms:
4776 if not m(f):
4776 if not m(f):
4777 continue
4777 continue
4778 def flag(o):
4778 def flag(o):
4779 if o == 're_merge':
4779 if o == 're_merge':
4780 return '--re-merge '
4780 return '--re-merge '
4781 return '-%s ' % o[0:1]
4781 return '-%s ' % o[0:1]
4782 flags = ''.join([flag(o) for o in flaglist if opts.get(o)])
4782 flags = ''.join([flag(o) for o in flaglist if opts.get(o)])
4783 hint = _("(try: hg resolve %s%s)\n") % (
4783 hint = _("(try: hg resolve %s%s)\n") % (
4784 flags,
4784 flags,
4785 ' '.join(pats))
4785 ' '.join(pats))
4786 break
4786 break
4787 ui.warn(_("arguments do not match paths that need resolving\n"))
4787 ui.warn(_("arguments do not match paths that need resolving\n"))
4788 if hint:
4788 if hint:
4789 ui.warn(hint)
4789 ui.warn(hint)
4790 elif ms.mergedriver and ms.mdstate() != 's':
4790 elif ms.mergedriver and ms.mdstate() != 's':
4791 # run conclude step when either a driver-resolved file is requested
4791 # run conclude step when either a driver-resolved file is requested
4792 # or there are no driver-resolved files
4792 # or there are no driver-resolved files
4793 # we can't use 'ret' to determine whether any files are unresolved
4793 # we can't use 'ret' to determine whether any files are unresolved
4794 # because we might not have tried to resolve some
4794 # because we might not have tried to resolve some
4795 if ((runconclude or not list(ms.driverresolved()))
4795 if ((runconclude or not list(ms.driverresolved()))
4796 and not list(ms.unresolved())):
4796 and not list(ms.unresolved())):
4797 proceed = mergemod.driverconclude(repo, ms, wctx)
4797 proceed = mergemod.driverconclude(repo, ms, wctx)
4798 ms.commit()
4798 ms.commit()
4799 if not proceed:
4799 if not proceed:
4800 return 1
4800 return 1
4801
4801
4802 # Nudge users into finishing an unfinished operation
4802 # Nudge users into finishing an unfinished operation
4803 unresolvedf = list(ms.unresolved())
4803 unresolvedf = list(ms.unresolved())
4804 driverresolvedf = list(ms.driverresolved())
4804 driverresolvedf = list(ms.driverresolved())
4805 if not unresolvedf and not driverresolvedf:
4805 if not unresolvedf and not driverresolvedf:
4806 ui.status(_('(no more unresolved files)\n'))
4806 ui.status(_('(no more unresolved files)\n'))
4807 cmdutil.checkafterresolved(repo)
4807 cmdutil.checkafterresolved(repo)
4808 elif not unresolvedf:
4808 elif not unresolvedf:
4809 ui.status(_('(no more unresolved files -- '
4809 ui.status(_('(no more unresolved files -- '
4810 'run "hg resolve --all" to conclude)\n'))
4810 'run "hg resolve --all" to conclude)\n'))
4811
4811
4812 return ret
4812 return ret
4813
4813
4814 @command('revert',
4814 @command('revert',
4815 [('a', 'all', None, _('revert all changes when no arguments given')),
4815 [('a', 'all', None, _('revert all changes when no arguments given')),
4816 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4816 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4817 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4817 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4818 ('C', 'no-backup', None, _('do not save backup copies of files')),
4818 ('C', 'no-backup', None, _('do not save backup copies of files')),
4819 ('i', 'interactive', None, _('interactively select the changes')),
4819 ('i', 'interactive', None, _('interactively select the changes')),
4820 ] + walkopts + dryrunopts,
4820 ] + walkopts + dryrunopts,
4821 _('[OPTION]... [-r REV] [NAME]...'))
4821 _('[OPTION]... [-r REV] [NAME]...'))
4822 def revert(ui, repo, *pats, **opts):
4822 def revert(ui, repo, *pats, **opts):
4823 """restore files to their checkout state
4823 """restore files to their checkout state
4824
4824
4825 .. note::
4825 .. note::
4826
4826
4827 To check out earlier revisions, you should use :hg:`update REV`.
4827 To check out earlier revisions, you should use :hg:`update REV`.
4828 To cancel an uncommitted merge (and lose your changes),
4828 To cancel an uncommitted merge (and lose your changes),
4829 use :hg:`merge --abort`.
4829 use :hg:`merge --abort`.
4830
4830
4831 With no revision specified, revert the specified files or directories
4831 With no revision specified, revert the specified files or directories
4832 to the contents they had in the parent of the working directory.
4832 to the contents they had in the parent of the working directory.
4833 This restores the contents of files to an unmodified
4833 This restores the contents of files to an unmodified
4834 state and unschedules adds, removes, copies, and renames. If the
4834 state and unschedules adds, removes, copies, and renames. If the
4835 working directory has two parents, you must explicitly specify a
4835 working directory has two parents, you must explicitly specify a
4836 revision.
4836 revision.
4837
4837
4838 Using the -r/--rev or -d/--date options, revert the given files or
4838 Using the -r/--rev or -d/--date options, revert the given files or
4839 directories to their states as of a specific revision. Because
4839 directories to their states as of a specific revision. Because
4840 revert does not change the working directory parents, this will
4840 revert does not change the working directory parents, this will
4841 cause these files to appear modified. This can be helpful to "back
4841 cause these files to appear modified. This can be helpful to "back
4842 out" some or all of an earlier change. See :hg:`backout` for a
4842 out" some or all of an earlier change. See :hg:`backout` for a
4843 related method.
4843 related method.
4844
4844
4845 Modified files are saved with a .orig suffix before reverting.
4845 Modified files are saved with a .orig suffix before reverting.
4846 To disable these backups, use --no-backup. It is possible to store
4846 To disable these backups, use --no-backup. It is possible to store
4847 the backup files in a custom directory relative to the root of the
4847 the backup files in a custom directory relative to the root of the
4848 repository by setting the ``ui.origbackuppath`` configuration
4848 repository by setting the ``ui.origbackuppath`` configuration
4849 option.
4849 option.
4850
4850
4851 See :hg:`help dates` for a list of formats valid for -d/--date.
4851 See :hg:`help dates` for a list of formats valid for -d/--date.
4852
4852
4853 See :hg:`help backout` for a way to reverse the effect of an
4853 See :hg:`help backout` for a way to reverse the effect of an
4854 earlier changeset.
4854 earlier changeset.
4855
4855
4856 Returns 0 on success.
4856 Returns 0 on success.
4857 """
4857 """
4858
4858
4859 opts = pycompat.byteskwargs(opts)
4859 opts = pycompat.byteskwargs(opts)
4860 if opts.get("date"):
4860 if opts.get("date"):
4861 if opts.get("rev"):
4861 if opts.get("rev"):
4862 raise error.Abort(_("you can't specify a revision and a date"))
4862 raise error.Abort(_("you can't specify a revision and a date"))
4863 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4863 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4864
4864
4865 parent, p2 = repo.dirstate.parents()
4865 parent, p2 = repo.dirstate.parents()
4866 if not opts.get('rev') and p2 != nullid:
4866 if not opts.get('rev') and p2 != nullid:
4867 # revert after merge is a trap for new users (issue2915)
4867 # revert after merge is a trap for new users (issue2915)
4868 raise error.Abort(_('uncommitted merge with no revision specified'),
4868 raise error.Abort(_('uncommitted merge with no revision specified'),
4869 hint=_("use 'hg update' or see 'hg help revert'"))
4869 hint=_("use 'hg update' or see 'hg help revert'"))
4870
4870
4871 rev = opts.get('rev')
4871 rev = opts.get('rev')
4872 if rev:
4872 if rev:
4873 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4873 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4874 ctx = scmutil.revsingle(repo, rev)
4874 ctx = scmutil.revsingle(repo, rev)
4875
4875
4876 if (not (pats or opts.get('include') or opts.get('exclude') or
4876 if (not (pats or opts.get('include') or opts.get('exclude') or
4877 opts.get('all') or opts.get('interactive'))):
4877 opts.get('all') or opts.get('interactive'))):
4878 msg = _("no files or directories specified")
4878 msg = _("no files or directories specified")
4879 if p2 != nullid:
4879 if p2 != nullid:
4880 hint = _("uncommitted merge, use --all to discard all changes,"
4880 hint = _("uncommitted merge, use --all to discard all changes,"
4881 " or 'hg update -C .' to abort the merge")
4881 " or 'hg update -C .' to abort the merge")
4882 raise error.Abort(msg, hint=hint)
4882 raise error.Abort(msg, hint=hint)
4883 dirty = any(repo.status())
4883 dirty = any(repo.status())
4884 node = ctx.node()
4884 node = ctx.node()
4885 if node != parent:
4885 if node != parent:
4886 if dirty:
4886 if dirty:
4887 hint = _("uncommitted changes, use --all to discard all"
4887 hint = _("uncommitted changes, use --all to discard all"
4888 " changes, or 'hg update %s' to update") % ctx.rev()
4888 " changes, or 'hg update %s' to update") % ctx.rev()
4889 else:
4889 else:
4890 hint = _("use --all to revert all files,"
4890 hint = _("use --all to revert all files,"
4891 " or 'hg update %s' to update") % ctx.rev()
4891 " or 'hg update %s' to update") % ctx.rev()
4892 elif dirty:
4892 elif dirty:
4893 hint = _("uncommitted changes, use --all to discard all changes")
4893 hint = _("uncommitted changes, use --all to discard all changes")
4894 else:
4894 else:
4895 hint = _("use --all to revert all files")
4895 hint = _("use --all to revert all files")
4896 raise error.Abort(msg, hint=hint)
4896 raise error.Abort(msg, hint=hint)
4897
4897
4898 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
4898 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
4899 **pycompat.strkwargs(opts))
4899 **pycompat.strkwargs(opts))
4900
4900
4901 @command('rollback', dryrunopts +
4901 @command('rollback', dryrunopts +
4902 [('f', 'force', False, _('ignore safety measures'))])
4902 [('f', 'force', False, _('ignore safety measures'))])
4903 def rollback(ui, repo, **opts):
4903 def rollback(ui, repo, **opts):
4904 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4904 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4905
4905
4906 Please use :hg:`commit --amend` instead of rollback to correct
4906 Please use :hg:`commit --amend` instead of rollback to correct
4907 mistakes in the last commit.
4907 mistakes in the last commit.
4908
4908
4909 This command should be used with care. There is only one level of
4909 This command should be used with care. There is only one level of
4910 rollback, and there is no way to undo a rollback. It will also
4910 rollback, and there is no way to undo a rollback. It will also
4911 restore the dirstate at the time of the last transaction, losing
4911 restore the dirstate at the time of the last transaction, losing
4912 any dirstate changes since that time. This command does not alter
4912 any dirstate changes since that time. This command does not alter
4913 the working directory.
4913 the working directory.
4914
4914
4915 Transactions are used to encapsulate the effects of all commands
4915 Transactions are used to encapsulate the effects of all commands
4916 that create new changesets or propagate existing changesets into a
4916 that create new changesets or propagate existing changesets into a
4917 repository.
4917 repository.
4918
4918
4919 .. container:: verbose
4919 .. container:: verbose
4920
4920
4921 For example, the following commands are transactional, and their
4921 For example, the following commands are transactional, and their
4922 effects can be rolled back:
4922 effects can be rolled back:
4923
4923
4924 - commit
4924 - commit
4925 - import
4925 - import
4926 - pull
4926 - pull
4927 - push (with this repository as the destination)
4927 - push (with this repository as the destination)
4928 - unbundle
4928 - unbundle
4929
4929
4930 To avoid permanent data loss, rollback will refuse to rollback a
4930 To avoid permanent data loss, rollback will refuse to rollback a
4931 commit transaction if it isn't checked out. Use --force to
4931 commit transaction if it isn't checked out. Use --force to
4932 override this protection.
4932 override this protection.
4933
4933
4934 The rollback command can be entirely disabled by setting the
4934 The rollback command can be entirely disabled by setting the
4935 ``ui.rollback`` configuration setting to false. If you're here
4935 ``ui.rollback`` configuration setting to false. If you're here
4936 because you want to use rollback and it's disabled, you can
4936 because you want to use rollback and it's disabled, you can
4937 re-enable the command by setting ``ui.rollback`` to true.
4937 re-enable the command by setting ``ui.rollback`` to true.
4938
4938
4939 This command is not intended for use on public repositories. Once
4939 This command is not intended for use on public repositories. Once
4940 changes are visible for pull by other users, rolling a transaction
4940 changes are visible for pull by other users, rolling a transaction
4941 back locally is ineffective (someone else may already have pulled
4941 back locally is ineffective (someone else may already have pulled
4942 the changes). Furthermore, a race is possible with readers of the
4942 the changes). Furthermore, a race is possible with readers of the
4943 repository; for example an in-progress pull from the repository
4943 repository; for example an in-progress pull from the repository
4944 may fail if a rollback is performed.
4944 may fail if a rollback is performed.
4945
4945
4946 Returns 0 on success, 1 if no rollback data is available.
4946 Returns 0 on success, 1 if no rollback data is available.
4947 """
4947 """
4948 if not ui.configbool('ui', 'rollback'):
4948 if not ui.configbool('ui', 'rollback'):
4949 raise error.Abort(_('rollback is disabled because it is unsafe'),
4949 raise error.Abort(_('rollback is disabled because it is unsafe'),
4950 hint=('see `hg help -v rollback` for information'))
4950 hint=('see `hg help -v rollback` for information'))
4951 return repo.rollback(dryrun=opts.get(r'dry_run'),
4951 return repo.rollback(dryrun=opts.get(r'dry_run'),
4952 force=opts.get(r'force'))
4952 force=opts.get(r'force'))
4953
4953
4954 @command('root', [], intents={INTENT_READONLY})
4954 @command('root', [], intents={INTENT_READONLY})
4955 def root(ui, repo):
4955 def root(ui, repo):
4956 """print the root (top) of the current working directory
4956 """print the root (top) of the current working directory
4957
4957
4958 Print the root directory of the current repository.
4958 Print the root directory of the current repository.
4959
4959
4960 Returns 0 on success.
4960 Returns 0 on success.
4961 """
4961 """
4962 ui.write(repo.root + "\n")
4962 ui.write(repo.root + "\n")
4963
4963
4964 @command('^serve',
4964 @command('^serve',
4965 [('A', 'accesslog', '', _('name of access log file to write to'),
4965 [('A', 'accesslog', '', _('name of access log file to write to'),
4966 _('FILE')),
4966 _('FILE')),
4967 ('d', 'daemon', None, _('run server in background')),
4967 ('d', 'daemon', None, _('run server in background')),
4968 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4968 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4969 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4969 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4970 # use string type, then we can check if something was passed
4970 # use string type, then we can check if something was passed
4971 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4971 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4972 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4972 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4973 _('ADDR')),
4973 _('ADDR')),
4974 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4974 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4975 _('PREFIX')),
4975 _('PREFIX')),
4976 ('n', 'name', '',
4976 ('n', 'name', '',
4977 _('name to show in web pages (default: working directory)'), _('NAME')),
4977 _('name to show in web pages (default: working directory)'), _('NAME')),
4978 ('', 'web-conf', '',
4978 ('', 'web-conf', '',
4979 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4979 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4980 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4980 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4981 _('FILE')),
4981 _('FILE')),
4982 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4982 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4983 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4983 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4984 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4984 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4985 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4985 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4986 ('', 'style', '', _('template style to use'), _('STYLE')),
4986 ('', 'style', '', _('template style to use'), _('STYLE')),
4987 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4987 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4988 ('', 'certificate', '', _('SSL certificate file'), _('FILE')),
4988 ('', 'certificate', '', _('SSL certificate file'), _('FILE')),
4989 ('', 'print-url', None, _('start and print only the URL'))]
4989 ('', 'print-url', None, _('start and print only the URL'))]
4990 + subrepoopts,
4990 + subrepoopts,
4991 _('[OPTION]...'),
4991 _('[OPTION]...'),
4992 optionalrepo=True)
4992 optionalrepo=True)
4993 def serve(ui, repo, **opts):
4993 def serve(ui, repo, **opts):
4994 """start stand-alone webserver
4994 """start stand-alone webserver
4995
4995
4996 Start a local HTTP repository browser and pull server. You can use
4996 Start a local HTTP repository browser and pull server. You can use
4997 this for ad-hoc sharing and browsing of repositories. It is
4997 this for ad-hoc sharing and browsing of repositories. It is
4998 recommended to use a real web server to serve a repository for
4998 recommended to use a real web server to serve a repository for
4999 longer periods of time.
4999 longer periods of time.
5000
5000
5001 Please note that the server does not implement access control.
5001 Please note that the server does not implement access control.
5002 This means that, by default, anybody can read from the server and
5002 This means that, by default, anybody can read from the server and
5003 nobody can write to it by default. Set the ``web.allow-push``
5003 nobody can write to it by default. Set the ``web.allow-push``
5004 option to ``*`` to allow everybody to push to the server. You
5004 option to ``*`` to allow everybody to push to the server. You
5005 should use a real web server if you need to authenticate users.
5005 should use a real web server if you need to authenticate users.
5006
5006
5007 By default, the server logs accesses to stdout and errors to
5007 By default, the server logs accesses to stdout and errors to
5008 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5008 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5009 files.
5009 files.
5010
5010
5011 To have the server choose a free port number to listen on, specify
5011 To have the server choose a free port number to listen on, specify
5012 a port number of 0; in this case, the server will print the port
5012 a port number of 0; in this case, the server will print the port
5013 number it uses.
5013 number it uses.
5014
5014
5015 Returns 0 on success.
5015 Returns 0 on success.
5016 """
5016 """
5017
5017
5018 opts = pycompat.byteskwargs(opts)
5018 opts = pycompat.byteskwargs(opts)
5019 if opts["stdio"] and opts["cmdserver"]:
5019 if opts["stdio"] and opts["cmdserver"]:
5020 raise error.Abort(_("cannot use --stdio with --cmdserver"))
5020 raise error.Abort(_("cannot use --stdio with --cmdserver"))
5021 if opts["print_url"] and ui.verbose:
5021 if opts["print_url"] and ui.verbose:
5022 raise error.Abort(_("cannot use --print-url with --verbose"))
5022 raise error.Abort(_("cannot use --print-url with --verbose"))
5023
5023
5024 if opts["stdio"]:
5024 if opts["stdio"]:
5025 if repo is None:
5025 if repo is None:
5026 raise error.RepoError(_("there is no Mercurial repository here"
5026 raise error.RepoError(_("there is no Mercurial repository here"
5027 " (.hg not found)"))
5027 " (.hg not found)"))
5028 s = wireprotoserver.sshserver(ui, repo)
5028 s = wireprotoserver.sshserver(ui, repo)
5029 s.serve_forever()
5029 s.serve_forever()
5030
5030
5031 service = server.createservice(ui, repo, opts)
5031 service = server.createservice(ui, repo, opts)
5032 return server.runservice(opts, initfn=service.init, runfn=service.run)
5032 return server.runservice(opts, initfn=service.init, runfn=service.run)
5033
5033
5034 _NOTTERSE = 'nothing'
5034 _NOTTERSE = 'nothing'
5035
5035
5036 @command('^status|st',
5036 @command('^status|st',
5037 [('A', 'all', None, _('show status of all files')),
5037 [('A', 'all', None, _('show status of all files')),
5038 ('m', 'modified', None, _('show only modified files')),
5038 ('m', 'modified', None, _('show only modified files')),
5039 ('a', 'added', None, _('show only added files')),
5039 ('a', 'added', None, _('show only added files')),
5040 ('r', 'removed', None, _('show only removed files')),
5040 ('r', 'removed', None, _('show only removed files')),
5041 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5041 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5042 ('c', 'clean', None, _('show only files without changes')),
5042 ('c', 'clean', None, _('show only files without changes')),
5043 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5043 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5044 ('i', 'ignored', None, _('show only ignored files')),
5044 ('i', 'ignored', None, _('show only ignored files')),
5045 ('n', 'no-status', None, _('hide status prefix')),
5045 ('n', 'no-status', None, _('hide status prefix')),
5046 ('t', 'terse', _NOTTERSE, _('show the terse output (EXPERIMENTAL)')),
5046 ('t', 'terse', _NOTTERSE, _('show the terse output (EXPERIMENTAL)')),
5047 ('C', 'copies', None, _('show source of copied files')),
5047 ('C', 'copies', None, _('show source of copied files')),
5048 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5048 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5049 ('', 'rev', [], _('show difference from revision'), _('REV')),
5049 ('', 'rev', [], _('show difference from revision'), _('REV')),
5050 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5050 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5051 ] + walkopts + subrepoopts + formatteropts,
5051 ] + walkopts + subrepoopts + formatteropts,
5052 _('[OPTION]... [FILE]...'),
5052 _('[OPTION]... [FILE]...'),
5053 inferrepo=True,
5053 inferrepo=True,
5054 intents={INTENT_READONLY})
5054 intents={INTENT_READONLY})
5055 def status(ui, repo, *pats, **opts):
5055 def status(ui, repo, *pats, **opts):
5056 """show changed files in the working directory
5056 """show changed files in the working directory
5057
5057
5058 Show status of files in the repository. If names are given, only
5058 Show status of files in the repository. If names are given, only
5059 files that match are shown. Files that are clean or ignored or
5059 files that match are shown. Files that are clean or ignored or
5060 the source of a copy/move operation, are not listed unless
5060 the source of a copy/move operation, are not listed unless
5061 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5061 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5062 Unless options described with "show only ..." are given, the
5062 Unless options described with "show only ..." are given, the
5063 options -mardu are used.
5063 options -mardu are used.
5064
5064
5065 Option -q/--quiet hides untracked (unknown and ignored) files
5065 Option -q/--quiet hides untracked (unknown and ignored) files
5066 unless explicitly requested with -u/--unknown or -i/--ignored.
5066 unless explicitly requested with -u/--unknown or -i/--ignored.
5067
5067
5068 .. note::
5068 .. note::
5069
5069
5070 :hg:`status` may appear to disagree with diff if permissions have
5070 :hg:`status` may appear to disagree with diff if permissions have
5071 changed or a merge has occurred. The standard diff format does
5071 changed or a merge has occurred. The standard diff format does
5072 not report permission changes and diff only reports changes
5072 not report permission changes and diff only reports changes
5073 relative to one merge parent.
5073 relative to one merge parent.
5074
5074
5075 If one revision is given, it is used as the base revision.
5075 If one revision is given, it is used as the base revision.
5076 If two revisions are given, the differences between them are
5076 If two revisions are given, the differences between them are
5077 shown. The --change option can also be used as a shortcut to list
5077 shown. The --change option can also be used as a shortcut to list
5078 the changed files of a revision from its first parent.
5078 the changed files of a revision from its first parent.
5079
5079
5080 The codes used to show the status of files are::
5080 The codes used to show the status of files are::
5081
5081
5082 M = modified
5082 M = modified
5083 A = added
5083 A = added
5084 R = removed
5084 R = removed
5085 C = clean
5085 C = clean
5086 ! = missing (deleted by non-hg command, but still tracked)
5086 ! = missing (deleted by non-hg command, but still tracked)
5087 ? = not tracked
5087 ? = not tracked
5088 I = ignored
5088 I = ignored
5089 = origin of the previous file (with --copies)
5089 = origin of the previous file (with --copies)
5090
5090
5091 .. container:: verbose
5091 .. container:: verbose
5092
5092
5093 The -t/--terse option abbreviates the output by showing only the directory
5093 The -t/--terse option abbreviates the output by showing only the directory
5094 name if all the files in it share the same status. The option takes an
5094 name if all the files in it share the same status. The option takes an
5095 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
5095 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
5096 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
5096 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
5097 for 'ignored' and 'c' for clean.
5097 for 'ignored' and 'c' for clean.
5098
5098
5099 It abbreviates only those statuses which are passed. Note that clean and
5099 It abbreviates only those statuses which are passed. Note that clean and
5100 ignored files are not displayed with '--terse ic' unless the -c/--clean
5100 ignored files are not displayed with '--terse ic' unless the -c/--clean
5101 and -i/--ignored options are also used.
5101 and -i/--ignored options are also used.
5102
5102
5103 The -v/--verbose option shows information when the repository is in an
5103 The -v/--verbose option shows information when the repository is in an
5104 unfinished merge, shelve, rebase state etc. You can have this behavior
5104 unfinished merge, shelve, rebase state etc. You can have this behavior
5105 turned on by default by enabling the ``commands.status.verbose`` option.
5105 turned on by default by enabling the ``commands.status.verbose`` option.
5106
5106
5107 You can skip displaying some of these states by setting
5107 You can skip displaying some of these states by setting
5108 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
5108 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
5109 'histedit', 'merge', 'rebase', or 'unshelve'.
5109 'histedit', 'merge', 'rebase', or 'unshelve'.
5110
5110
5111 Examples:
5111 Examples:
5112
5112
5113 - show changes in the working directory relative to a
5113 - show changes in the working directory relative to a
5114 changeset::
5114 changeset::
5115
5115
5116 hg status --rev 9353
5116 hg status --rev 9353
5117
5117
5118 - show changes in the working directory relative to the
5118 - show changes in the working directory relative to the
5119 current directory (see :hg:`help patterns` for more information)::
5119 current directory (see :hg:`help patterns` for more information)::
5120
5120
5121 hg status re:
5121 hg status re:
5122
5122
5123 - show all changes including copies in an existing changeset::
5123 - show all changes including copies in an existing changeset::
5124
5124
5125 hg status --copies --change 9353
5125 hg status --copies --change 9353
5126
5126
5127 - get a NUL separated list of added files, suitable for xargs::
5127 - get a NUL separated list of added files, suitable for xargs::
5128
5128
5129 hg status -an0
5129 hg status -an0
5130
5130
5131 - show more information about the repository status, abbreviating
5131 - show more information about the repository status, abbreviating
5132 added, removed, modified, deleted, and untracked paths::
5132 added, removed, modified, deleted, and untracked paths::
5133
5133
5134 hg status -v -t mardu
5134 hg status -v -t mardu
5135
5135
5136 Returns 0 on success.
5136 Returns 0 on success.
5137
5137
5138 """
5138 """
5139
5139
5140 opts = pycompat.byteskwargs(opts)
5140 opts = pycompat.byteskwargs(opts)
5141 revs = opts.get('rev')
5141 revs = opts.get('rev')
5142 change = opts.get('change')
5142 change = opts.get('change')
5143 terse = opts.get('terse')
5143 terse = opts.get('terse')
5144 if terse is _NOTTERSE:
5144 if terse is _NOTTERSE:
5145 if revs:
5145 if revs:
5146 terse = ''
5146 terse = ''
5147 else:
5147 else:
5148 terse = ui.config('commands', 'status.terse')
5148 terse = ui.config('commands', 'status.terse')
5149
5149
5150 if revs and change:
5150 if revs and change:
5151 msg = _('cannot specify --rev and --change at the same time')
5151 msg = _('cannot specify --rev and --change at the same time')
5152 raise error.Abort(msg)
5152 raise error.Abort(msg)
5153 elif revs and terse:
5153 elif revs and terse:
5154 msg = _('cannot use --terse with --rev')
5154 msg = _('cannot use --terse with --rev')
5155 raise error.Abort(msg)
5155 raise error.Abort(msg)
5156 elif change:
5156 elif change:
5157 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
5157 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
5158 ctx2 = scmutil.revsingle(repo, change, None)
5158 ctx2 = scmutil.revsingle(repo, change, None)
5159 ctx1 = ctx2.p1()
5159 ctx1 = ctx2.p1()
5160 else:
5160 else:
5161 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
5161 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
5162 ctx1, ctx2 = scmutil.revpair(repo, revs)
5162 ctx1, ctx2 = scmutil.revpair(repo, revs)
5163
5163
5164 if pats or ui.configbool('commands', 'status.relative'):
5164 if pats or ui.configbool('commands', 'status.relative'):
5165 cwd = repo.getcwd()
5165 cwd = repo.getcwd()
5166 else:
5166 else:
5167 cwd = ''
5167 cwd = ''
5168
5168
5169 if opts.get('print0'):
5169 if opts.get('print0'):
5170 end = '\0'
5170 end = '\0'
5171 else:
5171 else:
5172 end = '\n'
5172 end = '\n'
5173 copy = {}
5173 copy = {}
5174 states = 'modified added removed deleted unknown ignored clean'.split()
5174 states = 'modified added removed deleted unknown ignored clean'.split()
5175 show = [k for k in states if opts.get(k)]
5175 show = [k for k in states if opts.get(k)]
5176 if opts.get('all'):
5176 if opts.get('all'):
5177 show += ui.quiet and (states[:4] + ['clean']) or states
5177 show += ui.quiet and (states[:4] + ['clean']) or states
5178
5178
5179 if not show:
5179 if not show:
5180 if ui.quiet:
5180 if ui.quiet:
5181 show = states[:4]
5181 show = states[:4]
5182 else:
5182 else:
5183 show = states[:5]
5183 show = states[:5]
5184
5184
5185 m = scmutil.match(ctx2, pats, opts)
5185 m = scmutil.match(ctx2, pats, opts)
5186 if terse:
5186 if terse:
5187 # we need to compute clean and unknown to terse
5187 # we need to compute clean and unknown to terse
5188 stat = repo.status(ctx1.node(), ctx2.node(), m,
5188 stat = repo.status(ctx1.node(), ctx2.node(), m,
5189 'ignored' in show or 'i' in terse,
5189 'ignored' in show or 'i' in terse,
5190 clean=True, unknown=True,
5190 clean=True, unknown=True,
5191 listsubrepos=opts.get('subrepos'))
5191 listsubrepos=opts.get('subrepos'))
5192
5192
5193 stat = cmdutil.tersedir(stat, terse)
5193 stat = cmdutil.tersedir(stat, terse)
5194 else:
5194 else:
5195 stat = repo.status(ctx1.node(), ctx2.node(), m,
5195 stat = repo.status(ctx1.node(), ctx2.node(), m,
5196 'ignored' in show, 'clean' in show,
5196 'ignored' in show, 'clean' in show,
5197 'unknown' in show, opts.get('subrepos'))
5197 'unknown' in show, opts.get('subrepos'))
5198
5198
5199 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
5199 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
5200
5200
5201 if (opts.get('all') or opts.get('copies')
5201 if (opts.get('all') or opts.get('copies')
5202 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
5202 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
5203 copy = copies.pathcopies(ctx1, ctx2, m)
5203 copy = copies.pathcopies(ctx1, ctx2, m)
5204
5204
5205 ui.pager('status')
5205 ui.pager('status')
5206 fm = ui.formatter('status', opts)
5206 fm = ui.formatter('status', opts)
5207 fmt = '%s' + end
5207 fmt = '%s' + end
5208 showchar = not opts.get('no_status')
5208 showchar = not opts.get('no_status')
5209
5209
5210 for state, char, files in changestates:
5210 for state, char, files in changestates:
5211 if state in show:
5211 if state in show:
5212 label = 'status.' + state
5212 label = 'status.' + state
5213 for f in files:
5213 for f in files:
5214 fm.startitem()
5214 fm.startitem()
5215 fm.context(ctx=ctx2)
5215 fm.context(ctx=ctx2)
5216 fm.data(path=f)
5216 fm.data(path=f)
5217 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5217 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5218 fm.plain(fmt % repo.pathto(f, cwd), label=label)
5218 fm.plain(fmt % repo.pathto(f, cwd), label=label)
5219 if f in copy:
5219 if f in copy:
5220 fm.data(source=copy[f])
5220 fm.data(source=copy[f])
5221 fm.plain((' %s' + end) % repo.pathto(copy[f], cwd),
5221 fm.plain((' %s' + end) % repo.pathto(copy[f], cwd),
5222 label='status.copied')
5222 label='status.copied')
5223
5223
5224 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
5224 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
5225 and not ui.plain()):
5225 and not ui.plain()):
5226 cmdutil.morestatus(repo, fm)
5226 cmdutil.morestatus(repo, fm)
5227 fm.end()
5227 fm.end()
5228
5228
5229 @command('^summary|sum',
5229 @command('^summary|sum',
5230 [('', 'remote', None, _('check for push and pull'))],
5230 [('', 'remote', None, _('check for push and pull'))],
5231 '[--remote]',
5231 '[--remote]',
5232 intents={INTENT_READONLY})
5232 intents={INTENT_READONLY})
5233 def summary(ui, repo, **opts):
5233 def summary(ui, repo, **opts):
5234 """summarize working directory state
5234 """summarize working directory state
5235
5235
5236 This generates a brief summary of the working directory state,
5236 This generates a brief summary of the working directory state,
5237 including parents, branch, commit status, phase and available updates.
5237 including parents, branch, commit status, phase and available updates.
5238
5238
5239 With the --remote option, this will check the default paths for
5239 With the --remote option, this will check the default paths for
5240 incoming and outgoing changes. This can be time-consuming.
5240 incoming and outgoing changes. This can be time-consuming.
5241
5241
5242 Returns 0 on success.
5242 Returns 0 on success.
5243 """
5243 """
5244
5244
5245 opts = pycompat.byteskwargs(opts)
5245 opts = pycompat.byteskwargs(opts)
5246 ui.pager('summary')
5246 ui.pager('summary')
5247 ctx = repo[None]
5247 ctx = repo[None]
5248 parents = ctx.parents()
5248 parents = ctx.parents()
5249 pnode = parents[0].node()
5249 pnode = parents[0].node()
5250 marks = []
5250 marks = []
5251
5251
5252 ms = None
5252 ms = None
5253 try:
5253 try:
5254 ms = mergemod.mergestate.read(repo)
5254 ms = mergemod.mergestate.read(repo)
5255 except error.UnsupportedMergeRecords as e:
5255 except error.UnsupportedMergeRecords as e:
5256 s = ' '.join(e.recordtypes)
5256 s = ' '.join(e.recordtypes)
5257 ui.warn(
5257 ui.warn(
5258 _('warning: merge state has unsupported record types: %s\n') % s)
5258 _('warning: merge state has unsupported record types: %s\n') % s)
5259 unresolved = []
5259 unresolved = []
5260 else:
5260 else:
5261 unresolved = list(ms.unresolved())
5261 unresolved = list(ms.unresolved())
5262
5262
5263 for p in parents:
5263 for p in parents:
5264 # label with log.changeset (instead of log.parent) since this
5264 # label with log.changeset (instead of log.parent) since this
5265 # shows a working directory parent *changeset*:
5265 # shows a working directory parent *changeset*:
5266 # i18n: column positioning for "hg summary"
5266 # i18n: column positioning for "hg summary"
5267 ui.write(_('parent: %d:%s ') % (p.rev(), p),
5267 ui.write(_('parent: %d:%s ') % (p.rev(), p),
5268 label=logcmdutil.changesetlabels(p))
5268 label=logcmdutil.changesetlabels(p))
5269 ui.write(' '.join(p.tags()), label='log.tag')
5269 ui.write(' '.join(p.tags()), label='log.tag')
5270 if p.bookmarks():
5270 if p.bookmarks():
5271 marks.extend(p.bookmarks())
5271 marks.extend(p.bookmarks())
5272 if p.rev() == -1:
5272 if p.rev() == -1:
5273 if not len(repo):
5273 if not len(repo):
5274 ui.write(_(' (empty repository)'))
5274 ui.write(_(' (empty repository)'))
5275 else:
5275 else:
5276 ui.write(_(' (no revision checked out)'))
5276 ui.write(_(' (no revision checked out)'))
5277 if p.obsolete():
5277 if p.obsolete():
5278 ui.write(_(' (obsolete)'))
5278 ui.write(_(' (obsolete)'))
5279 if p.isunstable():
5279 if p.isunstable():
5280 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5280 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5281 for instability in p.instabilities())
5281 for instability in p.instabilities())
5282 ui.write(' ('
5282 ui.write(' ('
5283 + ', '.join(instabilities)
5283 + ', '.join(instabilities)
5284 + ')')
5284 + ')')
5285 ui.write('\n')
5285 ui.write('\n')
5286 if p.description():
5286 if p.description():
5287 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5287 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5288 label='log.summary')
5288 label='log.summary')
5289
5289
5290 branch = ctx.branch()
5290 branch = ctx.branch()
5291 bheads = repo.branchheads(branch)
5291 bheads = repo.branchheads(branch)
5292 # i18n: column positioning for "hg summary"
5292 # i18n: column positioning for "hg summary"
5293 m = _('branch: %s\n') % branch
5293 m = _('branch: %s\n') % branch
5294 if branch != 'default':
5294 if branch != 'default':
5295 ui.write(m, label='log.branch')
5295 ui.write(m, label='log.branch')
5296 else:
5296 else:
5297 ui.status(m, label='log.branch')
5297 ui.status(m, label='log.branch')
5298
5298
5299 if marks:
5299 if marks:
5300 active = repo._activebookmark
5300 active = repo._activebookmark
5301 # i18n: column positioning for "hg summary"
5301 # i18n: column positioning for "hg summary"
5302 ui.write(_('bookmarks:'), label='log.bookmark')
5302 ui.write(_('bookmarks:'), label='log.bookmark')
5303 if active is not None:
5303 if active is not None:
5304 if active in marks:
5304 if active in marks:
5305 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5305 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5306 marks.remove(active)
5306 marks.remove(active)
5307 else:
5307 else:
5308 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5308 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5309 for m in marks:
5309 for m in marks:
5310 ui.write(' ' + m, label='log.bookmark')
5310 ui.write(' ' + m, label='log.bookmark')
5311 ui.write('\n', label='log.bookmark')
5311 ui.write('\n', label='log.bookmark')
5312
5312
5313 status = repo.status(unknown=True)
5313 status = repo.status(unknown=True)
5314
5314
5315 c = repo.dirstate.copies()
5315 c = repo.dirstate.copies()
5316 copied, renamed = [], []
5316 copied, renamed = [], []
5317 for d, s in c.iteritems():
5317 for d, s in c.iteritems():
5318 if s in status.removed:
5318 if s in status.removed:
5319 status.removed.remove(s)
5319 status.removed.remove(s)
5320 renamed.append(d)
5320 renamed.append(d)
5321 else:
5321 else:
5322 copied.append(d)
5322 copied.append(d)
5323 if d in status.added:
5323 if d in status.added:
5324 status.added.remove(d)
5324 status.added.remove(d)
5325
5325
5326 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5326 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5327
5327
5328 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5328 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5329 (ui.label(_('%d added'), 'status.added'), status.added),
5329 (ui.label(_('%d added'), 'status.added'), status.added),
5330 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5330 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5331 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5331 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5332 (ui.label(_('%d copied'), 'status.copied'), copied),
5332 (ui.label(_('%d copied'), 'status.copied'), copied),
5333 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5333 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5334 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5334 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5335 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5335 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5336 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5336 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5337 t = []
5337 t = []
5338 for l, s in labels:
5338 for l, s in labels:
5339 if s:
5339 if s:
5340 t.append(l % len(s))
5340 t.append(l % len(s))
5341
5341
5342 t = ', '.join(t)
5342 t = ', '.join(t)
5343 cleanworkdir = False
5343 cleanworkdir = False
5344
5344
5345 if repo.vfs.exists('graftstate'):
5345 if repo.vfs.exists('graftstate'):
5346 t += _(' (graft in progress)')
5346 t += _(' (graft in progress)')
5347 if repo.vfs.exists('updatestate'):
5347 if repo.vfs.exists('updatestate'):
5348 t += _(' (interrupted update)')
5348 t += _(' (interrupted update)')
5349 elif len(parents) > 1:
5349 elif len(parents) > 1:
5350 t += _(' (merge)')
5350 t += _(' (merge)')
5351 elif branch != parents[0].branch():
5351 elif branch != parents[0].branch():
5352 t += _(' (new branch)')
5352 t += _(' (new branch)')
5353 elif (parents[0].closesbranch() and
5353 elif (parents[0].closesbranch() and
5354 pnode in repo.branchheads(branch, closed=True)):
5354 pnode in repo.branchheads(branch, closed=True)):
5355 t += _(' (head closed)')
5355 t += _(' (head closed)')
5356 elif not (status.modified or status.added or status.removed or renamed or
5356 elif not (status.modified or status.added or status.removed or renamed or
5357 copied or subs):
5357 copied or subs):
5358 t += _(' (clean)')
5358 t += _(' (clean)')
5359 cleanworkdir = True
5359 cleanworkdir = True
5360 elif pnode not in bheads:
5360 elif pnode not in bheads:
5361 t += _(' (new branch head)')
5361 t += _(' (new branch head)')
5362
5362
5363 if parents:
5363 if parents:
5364 pendingphase = max(p.phase() for p in parents)
5364 pendingphase = max(p.phase() for p in parents)
5365 else:
5365 else:
5366 pendingphase = phases.public
5366 pendingphase = phases.public
5367
5367
5368 if pendingphase > phases.newcommitphase(ui):
5368 if pendingphase > phases.newcommitphase(ui):
5369 t += ' (%s)' % phases.phasenames[pendingphase]
5369 t += ' (%s)' % phases.phasenames[pendingphase]
5370
5370
5371 if cleanworkdir:
5371 if cleanworkdir:
5372 # i18n: column positioning for "hg summary"
5372 # i18n: column positioning for "hg summary"
5373 ui.status(_('commit: %s\n') % t.strip())
5373 ui.status(_('commit: %s\n') % t.strip())
5374 else:
5374 else:
5375 # i18n: column positioning for "hg summary"
5375 # i18n: column positioning for "hg summary"
5376 ui.write(_('commit: %s\n') % t.strip())
5376 ui.write(_('commit: %s\n') % t.strip())
5377
5377
5378 # all ancestors of branch heads - all ancestors of parent = new csets
5378 # all ancestors of branch heads - all ancestors of parent = new csets
5379 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5379 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5380 bheads))
5380 bheads))
5381
5381
5382 if new == 0:
5382 if new == 0:
5383 # i18n: column positioning for "hg summary"
5383 # i18n: column positioning for "hg summary"
5384 ui.status(_('update: (current)\n'))
5384 ui.status(_('update: (current)\n'))
5385 elif pnode not in bheads:
5385 elif pnode not in bheads:
5386 # i18n: column positioning for "hg summary"
5386 # i18n: column positioning for "hg summary"
5387 ui.write(_('update: %d new changesets (update)\n') % new)
5387 ui.write(_('update: %d new changesets (update)\n') % new)
5388 else:
5388 else:
5389 # i18n: column positioning for "hg summary"
5389 # i18n: column positioning for "hg summary"
5390 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5390 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5391 (new, len(bheads)))
5391 (new, len(bheads)))
5392
5392
5393 t = []
5393 t = []
5394 draft = len(repo.revs('draft()'))
5394 draft = len(repo.revs('draft()'))
5395 if draft:
5395 if draft:
5396 t.append(_('%d draft') % draft)
5396 t.append(_('%d draft') % draft)
5397 secret = len(repo.revs('secret()'))
5397 secret = len(repo.revs('secret()'))
5398 if secret:
5398 if secret:
5399 t.append(_('%d secret') % secret)
5399 t.append(_('%d secret') % secret)
5400
5400
5401 if draft or secret:
5401 if draft or secret:
5402 ui.status(_('phases: %s\n') % ', '.join(t))
5402 ui.status(_('phases: %s\n') % ', '.join(t))
5403
5403
5404 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5404 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5405 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5405 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5406 numtrouble = len(repo.revs(trouble + "()"))
5406 numtrouble = len(repo.revs(trouble + "()"))
5407 # We write all the possibilities to ease translation
5407 # We write all the possibilities to ease translation
5408 troublemsg = {
5408 troublemsg = {
5409 "orphan": _("orphan: %d changesets"),
5409 "orphan": _("orphan: %d changesets"),
5410 "contentdivergent": _("content-divergent: %d changesets"),
5410 "contentdivergent": _("content-divergent: %d changesets"),
5411 "phasedivergent": _("phase-divergent: %d changesets"),
5411 "phasedivergent": _("phase-divergent: %d changesets"),
5412 }
5412 }
5413 if numtrouble > 0:
5413 if numtrouble > 0:
5414 ui.status(troublemsg[trouble] % numtrouble + "\n")
5414 ui.status(troublemsg[trouble] % numtrouble + "\n")
5415
5415
5416 cmdutil.summaryhooks(ui, repo)
5416 cmdutil.summaryhooks(ui, repo)
5417
5417
5418 if opts.get('remote'):
5418 if opts.get('remote'):
5419 needsincoming, needsoutgoing = True, True
5419 needsincoming, needsoutgoing = True, True
5420 else:
5420 else:
5421 needsincoming, needsoutgoing = False, False
5421 needsincoming, needsoutgoing = False, False
5422 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5422 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5423 if i:
5423 if i:
5424 needsincoming = True
5424 needsincoming = True
5425 if o:
5425 if o:
5426 needsoutgoing = True
5426 needsoutgoing = True
5427 if not needsincoming and not needsoutgoing:
5427 if not needsincoming and not needsoutgoing:
5428 return
5428 return
5429
5429
5430 def getincoming():
5430 def getincoming():
5431 source, branches = hg.parseurl(ui.expandpath('default'))
5431 source, branches = hg.parseurl(ui.expandpath('default'))
5432 sbranch = branches[0]
5432 sbranch = branches[0]
5433 try:
5433 try:
5434 other = hg.peer(repo, {}, source)
5434 other = hg.peer(repo, {}, source)
5435 except error.RepoError:
5435 except error.RepoError:
5436 if opts.get('remote'):
5436 if opts.get('remote'):
5437 raise
5437 raise
5438 return source, sbranch, None, None, None
5438 return source, sbranch, None, None, None
5439 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5439 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5440 if revs:
5440 if revs:
5441 revs = [other.lookup(rev) for rev in revs]
5441 revs = [other.lookup(rev) for rev in revs]
5442 ui.debug('comparing with %s\n' % util.hidepassword(source))
5442 ui.debug('comparing with %s\n' % util.hidepassword(source))
5443 repo.ui.pushbuffer()
5443 repo.ui.pushbuffer()
5444 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5444 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5445 repo.ui.popbuffer()
5445 repo.ui.popbuffer()
5446 return source, sbranch, other, commoninc, commoninc[1]
5446 return source, sbranch, other, commoninc, commoninc[1]
5447
5447
5448 if needsincoming:
5448 if needsincoming:
5449 source, sbranch, sother, commoninc, incoming = getincoming()
5449 source, sbranch, sother, commoninc, incoming = getincoming()
5450 else:
5450 else:
5451 source = sbranch = sother = commoninc = incoming = None
5451 source = sbranch = sother = commoninc = incoming = None
5452
5452
5453 def getoutgoing():
5453 def getoutgoing():
5454 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5454 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5455 dbranch = branches[0]
5455 dbranch = branches[0]
5456 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5456 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5457 if source != dest:
5457 if source != dest:
5458 try:
5458 try:
5459 dother = hg.peer(repo, {}, dest)
5459 dother = hg.peer(repo, {}, dest)
5460 except error.RepoError:
5460 except error.RepoError:
5461 if opts.get('remote'):
5461 if opts.get('remote'):
5462 raise
5462 raise
5463 return dest, dbranch, None, None
5463 return dest, dbranch, None, None
5464 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5464 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5465 elif sother is None:
5465 elif sother is None:
5466 # there is no explicit destination peer, but source one is invalid
5466 # there is no explicit destination peer, but source one is invalid
5467 return dest, dbranch, None, None
5467 return dest, dbranch, None, None
5468 else:
5468 else:
5469 dother = sother
5469 dother = sother
5470 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5470 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5471 common = None
5471 common = None
5472 else:
5472 else:
5473 common = commoninc
5473 common = commoninc
5474 if revs:
5474 if revs:
5475 revs = [repo.lookup(rev) for rev in revs]
5475 revs = [repo.lookup(rev) for rev in revs]
5476 repo.ui.pushbuffer()
5476 repo.ui.pushbuffer()
5477 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5477 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5478 commoninc=common)
5478 commoninc=common)
5479 repo.ui.popbuffer()
5479 repo.ui.popbuffer()
5480 return dest, dbranch, dother, outgoing
5480 return dest, dbranch, dother, outgoing
5481
5481
5482 if needsoutgoing:
5482 if needsoutgoing:
5483 dest, dbranch, dother, outgoing = getoutgoing()
5483 dest, dbranch, dother, outgoing = getoutgoing()
5484 else:
5484 else:
5485 dest = dbranch = dother = outgoing = None
5485 dest = dbranch = dother = outgoing = None
5486
5486
5487 if opts.get('remote'):
5487 if opts.get('remote'):
5488 t = []
5488 t = []
5489 if incoming:
5489 if incoming:
5490 t.append(_('1 or more incoming'))
5490 t.append(_('1 or more incoming'))
5491 o = outgoing.missing
5491 o = outgoing.missing
5492 if o:
5492 if o:
5493 t.append(_('%d outgoing') % len(o))
5493 t.append(_('%d outgoing') % len(o))
5494 other = dother or sother
5494 other = dother or sother
5495 if 'bookmarks' in other.listkeys('namespaces'):
5495 if 'bookmarks' in other.listkeys('namespaces'):
5496 counts = bookmarks.summary(repo, other)
5496 counts = bookmarks.summary(repo, other)
5497 if counts[0] > 0:
5497 if counts[0] > 0:
5498 t.append(_('%d incoming bookmarks') % counts[0])
5498 t.append(_('%d incoming bookmarks') % counts[0])
5499 if counts[1] > 0:
5499 if counts[1] > 0:
5500 t.append(_('%d outgoing bookmarks') % counts[1])
5500 t.append(_('%d outgoing bookmarks') % counts[1])
5501
5501
5502 if t:
5502 if t:
5503 # i18n: column positioning for "hg summary"
5503 # i18n: column positioning for "hg summary"
5504 ui.write(_('remote: %s\n') % (', '.join(t)))
5504 ui.write(_('remote: %s\n') % (', '.join(t)))
5505 else:
5505 else:
5506 # i18n: column positioning for "hg summary"
5506 # i18n: column positioning for "hg summary"
5507 ui.status(_('remote: (synced)\n'))
5507 ui.status(_('remote: (synced)\n'))
5508
5508
5509 cmdutil.summaryremotehooks(ui, repo, opts,
5509 cmdutil.summaryremotehooks(ui, repo, opts,
5510 ((source, sbranch, sother, commoninc),
5510 ((source, sbranch, sother, commoninc),
5511 (dest, dbranch, dother, outgoing)))
5511 (dest, dbranch, dother, outgoing)))
5512
5512
5513 @command('tag',
5513 @command('tag',
5514 [('f', 'force', None, _('force tag')),
5514 [('f', 'force', None, _('force tag')),
5515 ('l', 'local', None, _('make the tag local')),
5515 ('l', 'local', None, _('make the tag local')),
5516 ('r', 'rev', '', _('revision to tag'), _('REV')),
5516 ('r', 'rev', '', _('revision to tag'), _('REV')),
5517 ('', 'remove', None, _('remove a tag')),
5517 ('', 'remove', None, _('remove a tag')),
5518 # -l/--local is already there, commitopts cannot be used
5518 # -l/--local is already there, commitopts cannot be used
5519 ('e', 'edit', None, _('invoke editor on commit messages')),
5519 ('e', 'edit', None, _('invoke editor on commit messages')),
5520 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5520 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5521 ] + commitopts2,
5521 ] + commitopts2,
5522 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5522 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5523 def tag(ui, repo, name1, *names, **opts):
5523 def tag(ui, repo, name1, *names, **opts):
5524 """add one or more tags for the current or given revision
5524 """add one or more tags for the current or given revision
5525
5525
5526 Name a particular revision using <name>.
5526 Name a particular revision using <name>.
5527
5527
5528 Tags are used to name particular revisions of the repository and are
5528 Tags are used to name particular revisions of the repository and are
5529 very useful to compare different revisions, to go back to significant
5529 very useful to compare different revisions, to go back to significant
5530 earlier versions or to mark branch points as releases, etc. Changing
5530 earlier versions or to mark branch points as releases, etc. Changing
5531 an existing tag is normally disallowed; use -f/--force to override.
5531 an existing tag is normally disallowed; use -f/--force to override.
5532
5532
5533 If no revision is given, the parent of the working directory is
5533 If no revision is given, the parent of the working directory is
5534 used.
5534 used.
5535
5535
5536 To facilitate version control, distribution, and merging of tags,
5536 To facilitate version control, distribution, and merging of tags,
5537 they are stored as a file named ".hgtags" which is managed similarly
5537 they are stored as a file named ".hgtags" which is managed similarly
5538 to other project files and can be hand-edited if necessary. This
5538 to other project files and can be hand-edited if necessary. This
5539 also means that tagging creates a new commit. The file
5539 also means that tagging creates a new commit. The file
5540 ".hg/localtags" is used for local tags (not shared among
5540 ".hg/localtags" is used for local tags (not shared among
5541 repositories).
5541 repositories).
5542
5542
5543 Tag commits are usually made at the head of a branch. If the parent
5543 Tag commits are usually made at the head of a branch. If the parent
5544 of the working directory is not a branch head, :hg:`tag` aborts; use
5544 of the working directory is not a branch head, :hg:`tag` aborts; use
5545 -f/--force to force the tag commit to be based on a non-head
5545 -f/--force to force the tag commit to be based on a non-head
5546 changeset.
5546 changeset.
5547
5547
5548 See :hg:`help dates` for a list of formats valid for -d/--date.
5548 See :hg:`help dates` for a list of formats valid for -d/--date.
5549
5549
5550 Since tag names have priority over branch names during revision
5550 Since tag names have priority over branch names during revision
5551 lookup, using an existing branch name as a tag name is discouraged.
5551 lookup, using an existing branch name as a tag name is discouraged.
5552
5552
5553 Returns 0 on success.
5553 Returns 0 on success.
5554 """
5554 """
5555 opts = pycompat.byteskwargs(opts)
5555 opts = pycompat.byteskwargs(opts)
5556 with repo.wlock(), repo.lock():
5556 with repo.wlock(), repo.lock():
5557 rev_ = "."
5557 rev_ = "."
5558 names = [t.strip() for t in (name1,) + names]
5558 names = [t.strip() for t in (name1,) + names]
5559 if len(names) != len(set(names)):
5559 if len(names) != len(set(names)):
5560 raise error.Abort(_('tag names must be unique'))
5560 raise error.Abort(_('tag names must be unique'))
5561 for n in names:
5561 for n in names:
5562 scmutil.checknewlabel(repo, n, 'tag')
5562 scmutil.checknewlabel(repo, n, 'tag')
5563 if not n:
5563 if not n:
5564 raise error.Abort(_('tag names cannot consist entirely of '
5564 raise error.Abort(_('tag names cannot consist entirely of '
5565 'whitespace'))
5565 'whitespace'))
5566 if opts.get('rev') and opts.get('remove'):
5566 if opts.get('rev') and opts.get('remove'):
5567 raise error.Abort(_("--rev and --remove are incompatible"))
5567 raise error.Abort(_("--rev and --remove are incompatible"))
5568 if opts.get('rev'):
5568 if opts.get('rev'):
5569 rev_ = opts['rev']
5569 rev_ = opts['rev']
5570 message = opts.get('message')
5570 message = opts.get('message')
5571 if opts.get('remove'):
5571 if opts.get('remove'):
5572 if opts.get('local'):
5572 if opts.get('local'):
5573 expectedtype = 'local'
5573 expectedtype = 'local'
5574 else:
5574 else:
5575 expectedtype = 'global'
5575 expectedtype = 'global'
5576
5576
5577 for n in names:
5577 for n in names:
5578 if not repo.tagtype(n):
5578 if not repo.tagtype(n):
5579 raise error.Abort(_("tag '%s' does not exist") % n)
5579 raise error.Abort(_("tag '%s' does not exist") % n)
5580 if repo.tagtype(n) != expectedtype:
5580 if repo.tagtype(n) != expectedtype:
5581 if expectedtype == 'global':
5581 if expectedtype == 'global':
5582 raise error.Abort(_("tag '%s' is not a global tag") % n)
5582 raise error.Abort(_("tag '%s' is not a global tag") % n)
5583 else:
5583 else:
5584 raise error.Abort(_("tag '%s' is not a local tag") % n)
5584 raise error.Abort(_("tag '%s' is not a local tag") % n)
5585 rev_ = 'null'
5585 rev_ = 'null'
5586 if not message:
5586 if not message:
5587 # we don't translate commit messages
5587 # we don't translate commit messages
5588 message = 'Removed tag %s' % ', '.join(names)
5588 message = 'Removed tag %s' % ', '.join(names)
5589 elif not opts.get('force'):
5589 elif not opts.get('force'):
5590 for n in names:
5590 for n in names:
5591 if n in repo.tags():
5591 if n in repo.tags():
5592 raise error.Abort(_("tag '%s' already exists "
5592 raise error.Abort(_("tag '%s' already exists "
5593 "(use -f to force)") % n)
5593 "(use -f to force)") % n)
5594 if not opts.get('local'):
5594 if not opts.get('local'):
5595 p1, p2 = repo.dirstate.parents()
5595 p1, p2 = repo.dirstate.parents()
5596 if p2 != nullid:
5596 if p2 != nullid:
5597 raise error.Abort(_('uncommitted merge'))
5597 raise error.Abort(_('uncommitted merge'))
5598 bheads = repo.branchheads()
5598 bheads = repo.branchheads()
5599 if not opts.get('force') and bheads and p1 not in bheads:
5599 if not opts.get('force') and bheads and p1 not in bheads:
5600 raise error.Abort(_('working directory is not at a branch head '
5600 raise error.Abort(_('working directory is not at a branch head '
5601 '(use -f to force)'))
5601 '(use -f to force)'))
5602 node = scmutil.revsingle(repo, rev_).node()
5602 node = scmutil.revsingle(repo, rev_).node()
5603
5603
5604 if not message:
5604 if not message:
5605 # we don't translate commit messages
5605 # we don't translate commit messages
5606 message = ('Added tag %s for changeset %s' %
5606 message = ('Added tag %s for changeset %s' %
5607 (', '.join(names), short(node)))
5607 (', '.join(names), short(node)))
5608
5608
5609 date = opts.get('date')
5609 date = opts.get('date')
5610 if date:
5610 if date:
5611 date = dateutil.parsedate(date)
5611 date = dateutil.parsedate(date)
5612
5612
5613 if opts.get('remove'):
5613 if opts.get('remove'):
5614 editform = 'tag.remove'
5614 editform = 'tag.remove'
5615 else:
5615 else:
5616 editform = 'tag.add'
5616 editform = 'tag.add'
5617 editor = cmdutil.getcommiteditor(editform=editform,
5617 editor = cmdutil.getcommiteditor(editform=editform,
5618 **pycompat.strkwargs(opts))
5618 **pycompat.strkwargs(opts))
5619
5619
5620 # don't allow tagging the null rev
5620 # don't allow tagging the null rev
5621 if (not opts.get('remove') and
5621 if (not opts.get('remove') and
5622 scmutil.revsingle(repo, rev_).rev() == nullrev):
5622 scmutil.revsingle(repo, rev_).rev() == nullrev):
5623 raise error.Abort(_("cannot tag null revision"))
5623 raise error.Abort(_("cannot tag null revision"))
5624
5624
5625 tagsmod.tag(repo, names, node, message, opts.get('local'),
5625 tagsmod.tag(repo, names, node, message, opts.get('local'),
5626 opts.get('user'), date, editor=editor)
5626 opts.get('user'), date, editor=editor)
5627
5627
5628 @command('tags', formatteropts, '', intents={INTENT_READONLY})
5628 @command('tags', formatteropts, '', intents={INTENT_READONLY})
5629 def tags(ui, repo, **opts):
5629 def tags(ui, repo, **opts):
5630 """list repository tags
5630 """list repository tags
5631
5631
5632 This lists both regular and local tags. When the -v/--verbose
5632 This lists both regular and local tags. When the -v/--verbose
5633 switch is used, a third column "local" is printed for local tags.
5633 switch is used, a third column "local" is printed for local tags.
5634 When the -q/--quiet switch is used, only the tag name is printed.
5634 When the -q/--quiet switch is used, only the tag name is printed.
5635
5635
5636 Returns 0 on success.
5636 Returns 0 on success.
5637 """
5637 """
5638
5638
5639 opts = pycompat.byteskwargs(opts)
5639 opts = pycompat.byteskwargs(opts)
5640 ui.pager('tags')
5640 ui.pager('tags')
5641 fm = ui.formatter('tags', opts)
5641 fm = ui.formatter('tags', opts)
5642 contexthint = fm.contexthint('tag rev node type')
5643 hexfunc = fm.hexfunc
5642 hexfunc = fm.hexfunc
5644 tagtype = ""
5643 tagtype = ""
5645
5644
5646 for t, n in reversed(repo.tagslist()):
5645 for t, n in reversed(repo.tagslist()):
5647 hn = hexfunc(n)
5646 hn = hexfunc(n)
5648 label = 'tags.normal'
5647 label = 'tags.normal'
5649 tagtype = ''
5648 tagtype = ''
5650 if repo.tagtype(t) == 'local':
5649 if repo.tagtype(t) == 'local':
5651 label = 'tags.local'
5650 label = 'tags.local'
5652 tagtype = 'local'
5651 tagtype = 'local'
5653
5652
5654 fm.startitem()
5653 fm.startitem()
5655 if 'ctx' in contexthint:
5654 fm.context(repo=repo)
5656 fm.context(ctx=repo[n])
5657 fm.write('tag', '%s', t, label=label)
5655 fm.write('tag', '%s', t, label=label)
5658 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5656 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5659 fm.condwrite(not ui.quiet, 'rev node', fmt,
5657 fm.condwrite(not ui.quiet, 'rev node', fmt,
5660 repo.changelog.rev(n), hn, label=label)
5658 repo.changelog.rev(n), hn, label=label)
5661 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5659 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5662 tagtype, label=label)
5660 tagtype, label=label)
5663 fm.plain('\n')
5661 fm.plain('\n')
5664 fm.end()
5662 fm.end()
5665
5663
5666 @command('tip',
5664 @command('tip',
5667 [('p', 'patch', None, _('show patch')),
5665 [('p', 'patch', None, _('show patch')),
5668 ('g', 'git', None, _('use git extended diff format')),
5666 ('g', 'git', None, _('use git extended diff format')),
5669 ] + templateopts,
5667 ] + templateopts,
5670 _('[-p] [-g]'))
5668 _('[-p] [-g]'))
5671 def tip(ui, repo, **opts):
5669 def tip(ui, repo, **opts):
5672 """show the tip revision (DEPRECATED)
5670 """show the tip revision (DEPRECATED)
5673
5671
5674 The tip revision (usually just called the tip) is the changeset
5672 The tip revision (usually just called the tip) is the changeset
5675 most recently added to the repository (and therefore the most
5673 most recently added to the repository (and therefore the most
5676 recently changed head).
5674 recently changed head).
5677
5675
5678 If you have just made a commit, that commit will be the tip. If
5676 If you have just made a commit, that commit will be the tip. If
5679 you have just pulled changes from another repository, the tip of
5677 you have just pulled changes from another repository, the tip of
5680 that repository becomes the current tip. The "tip" tag is special
5678 that repository becomes the current tip. The "tip" tag is special
5681 and cannot be renamed or assigned to a different changeset.
5679 and cannot be renamed or assigned to a different changeset.
5682
5680
5683 This command is deprecated, please use :hg:`heads` instead.
5681 This command is deprecated, please use :hg:`heads` instead.
5684
5682
5685 Returns 0 on success.
5683 Returns 0 on success.
5686 """
5684 """
5687 opts = pycompat.byteskwargs(opts)
5685 opts = pycompat.byteskwargs(opts)
5688 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5686 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5689 displayer.show(repo['tip'])
5687 displayer.show(repo['tip'])
5690 displayer.close()
5688 displayer.close()
5691
5689
5692 @command('unbundle',
5690 @command('unbundle',
5693 [('u', 'update', None,
5691 [('u', 'update', None,
5694 _('update to new branch head if changesets were unbundled'))],
5692 _('update to new branch head if changesets were unbundled'))],
5695 _('[-u] FILE...'))
5693 _('[-u] FILE...'))
5696 def unbundle(ui, repo, fname1, *fnames, **opts):
5694 def unbundle(ui, repo, fname1, *fnames, **opts):
5697 """apply one or more bundle files
5695 """apply one or more bundle files
5698
5696
5699 Apply one or more bundle files generated by :hg:`bundle`.
5697 Apply one or more bundle files generated by :hg:`bundle`.
5700
5698
5701 Returns 0 on success, 1 if an update has unresolved files.
5699 Returns 0 on success, 1 if an update has unresolved files.
5702 """
5700 """
5703 fnames = (fname1,) + fnames
5701 fnames = (fname1,) + fnames
5704
5702
5705 with repo.lock():
5703 with repo.lock():
5706 for fname in fnames:
5704 for fname in fnames:
5707 f = hg.openpath(ui, fname)
5705 f = hg.openpath(ui, fname)
5708 gen = exchange.readbundle(ui, f, fname)
5706 gen = exchange.readbundle(ui, f, fname)
5709 if isinstance(gen, streamclone.streamcloneapplier):
5707 if isinstance(gen, streamclone.streamcloneapplier):
5710 raise error.Abort(
5708 raise error.Abort(
5711 _('packed bundles cannot be applied with '
5709 _('packed bundles cannot be applied with '
5712 '"hg unbundle"'),
5710 '"hg unbundle"'),
5713 hint=_('use "hg debugapplystreamclonebundle"'))
5711 hint=_('use "hg debugapplystreamclonebundle"'))
5714 url = 'bundle:' + fname
5712 url = 'bundle:' + fname
5715 try:
5713 try:
5716 txnname = 'unbundle'
5714 txnname = 'unbundle'
5717 if not isinstance(gen, bundle2.unbundle20):
5715 if not isinstance(gen, bundle2.unbundle20):
5718 txnname = 'unbundle\n%s' % util.hidepassword(url)
5716 txnname = 'unbundle\n%s' % util.hidepassword(url)
5719 with repo.transaction(txnname) as tr:
5717 with repo.transaction(txnname) as tr:
5720 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5718 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5721 url=url)
5719 url=url)
5722 except error.BundleUnknownFeatureError as exc:
5720 except error.BundleUnknownFeatureError as exc:
5723 raise error.Abort(
5721 raise error.Abort(
5724 _('%s: unknown bundle feature, %s') % (fname, exc),
5722 _('%s: unknown bundle feature, %s') % (fname, exc),
5725 hint=_("see https://mercurial-scm.org/"
5723 hint=_("see https://mercurial-scm.org/"
5726 "wiki/BundleFeature for more "
5724 "wiki/BundleFeature for more "
5727 "information"))
5725 "information"))
5728 modheads = bundle2.combinechangegroupresults(op)
5726 modheads = bundle2.combinechangegroupresults(op)
5729
5727
5730 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5728 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5731
5729
5732 @command('^update|up|checkout|co',
5730 @command('^update|up|checkout|co',
5733 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5731 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5734 ('c', 'check', None, _('require clean working directory')),
5732 ('c', 'check', None, _('require clean working directory')),
5735 ('m', 'merge', None, _('merge uncommitted changes')),
5733 ('m', 'merge', None, _('merge uncommitted changes')),
5736 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5734 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5737 ('r', 'rev', '', _('revision'), _('REV'))
5735 ('r', 'rev', '', _('revision'), _('REV'))
5738 ] + mergetoolopts,
5736 ] + mergetoolopts,
5739 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5737 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5740 def update(ui, repo, node=None, **opts):
5738 def update(ui, repo, node=None, **opts):
5741 """update working directory (or switch revisions)
5739 """update working directory (or switch revisions)
5742
5740
5743 Update the repository's working directory to the specified
5741 Update the repository's working directory to the specified
5744 changeset. If no changeset is specified, update to the tip of the
5742 changeset. If no changeset is specified, update to the tip of the
5745 current named branch and move the active bookmark (see :hg:`help
5743 current named branch and move the active bookmark (see :hg:`help
5746 bookmarks`).
5744 bookmarks`).
5747
5745
5748 Update sets the working directory's parent revision to the specified
5746 Update sets the working directory's parent revision to the specified
5749 changeset (see :hg:`help parents`).
5747 changeset (see :hg:`help parents`).
5750
5748
5751 If the changeset is not a descendant or ancestor of the working
5749 If the changeset is not a descendant or ancestor of the working
5752 directory's parent and there are uncommitted changes, the update is
5750 directory's parent and there are uncommitted changes, the update is
5753 aborted. With the -c/--check option, the working directory is checked
5751 aborted. With the -c/--check option, the working directory is checked
5754 for uncommitted changes; if none are found, the working directory is
5752 for uncommitted changes; if none are found, the working directory is
5755 updated to the specified changeset.
5753 updated to the specified changeset.
5756
5754
5757 .. container:: verbose
5755 .. container:: verbose
5758
5756
5759 The -C/--clean, -c/--check, and -m/--merge options control what
5757 The -C/--clean, -c/--check, and -m/--merge options control what
5760 happens if the working directory contains uncommitted changes.
5758 happens if the working directory contains uncommitted changes.
5761 At most of one of them can be specified.
5759 At most of one of them can be specified.
5762
5760
5763 1. If no option is specified, and if
5761 1. If no option is specified, and if
5764 the requested changeset is an ancestor or descendant of
5762 the requested changeset is an ancestor or descendant of
5765 the working directory's parent, the uncommitted changes
5763 the working directory's parent, the uncommitted changes
5766 are merged into the requested changeset and the merged
5764 are merged into the requested changeset and the merged
5767 result is left uncommitted. If the requested changeset is
5765 result is left uncommitted. If the requested changeset is
5768 not an ancestor or descendant (that is, it is on another
5766 not an ancestor or descendant (that is, it is on another
5769 branch), the update is aborted and the uncommitted changes
5767 branch), the update is aborted and the uncommitted changes
5770 are preserved.
5768 are preserved.
5771
5769
5772 2. With the -m/--merge option, the update is allowed even if the
5770 2. With the -m/--merge option, the update is allowed even if the
5773 requested changeset is not an ancestor or descendant of
5771 requested changeset is not an ancestor or descendant of
5774 the working directory's parent.
5772 the working directory's parent.
5775
5773
5776 3. With the -c/--check option, the update is aborted and the
5774 3. With the -c/--check option, the update is aborted and the
5777 uncommitted changes are preserved.
5775 uncommitted changes are preserved.
5778
5776
5779 4. With the -C/--clean option, uncommitted changes are discarded and
5777 4. With the -C/--clean option, uncommitted changes are discarded and
5780 the working directory is updated to the requested changeset.
5778 the working directory is updated to the requested changeset.
5781
5779
5782 To cancel an uncommitted merge (and lose your changes), use
5780 To cancel an uncommitted merge (and lose your changes), use
5783 :hg:`merge --abort`.
5781 :hg:`merge --abort`.
5784
5782
5785 Use null as the changeset to remove the working directory (like
5783 Use null as the changeset to remove the working directory (like
5786 :hg:`clone -U`).
5784 :hg:`clone -U`).
5787
5785
5788 If you want to revert just one file to an older revision, use
5786 If you want to revert just one file to an older revision, use
5789 :hg:`revert [-r REV] NAME`.
5787 :hg:`revert [-r REV] NAME`.
5790
5788
5791 See :hg:`help dates` for a list of formats valid for -d/--date.
5789 See :hg:`help dates` for a list of formats valid for -d/--date.
5792
5790
5793 Returns 0 on success, 1 if there are unresolved files.
5791 Returns 0 on success, 1 if there are unresolved files.
5794 """
5792 """
5795 rev = opts.get(r'rev')
5793 rev = opts.get(r'rev')
5796 date = opts.get(r'date')
5794 date = opts.get(r'date')
5797 clean = opts.get(r'clean')
5795 clean = opts.get(r'clean')
5798 check = opts.get(r'check')
5796 check = opts.get(r'check')
5799 merge = opts.get(r'merge')
5797 merge = opts.get(r'merge')
5800 if rev and node:
5798 if rev and node:
5801 raise error.Abort(_("please specify just one revision"))
5799 raise error.Abort(_("please specify just one revision"))
5802
5800
5803 if ui.configbool('commands', 'update.requiredest'):
5801 if ui.configbool('commands', 'update.requiredest'):
5804 if not node and not rev and not date:
5802 if not node and not rev and not date:
5805 raise error.Abort(_('you must specify a destination'),
5803 raise error.Abort(_('you must specify a destination'),
5806 hint=_('for example: hg update ".::"'))
5804 hint=_('for example: hg update ".::"'))
5807
5805
5808 if rev is None or rev == '':
5806 if rev is None or rev == '':
5809 rev = node
5807 rev = node
5810
5808
5811 if date and rev is not None:
5809 if date and rev is not None:
5812 raise error.Abort(_("you can't specify a revision and a date"))
5810 raise error.Abort(_("you can't specify a revision and a date"))
5813
5811
5814 if len([x for x in (clean, check, merge) if x]) > 1:
5812 if len([x for x in (clean, check, merge) if x]) > 1:
5815 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5813 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5816 "or -m/--merge"))
5814 "or -m/--merge"))
5817
5815
5818 updatecheck = None
5816 updatecheck = None
5819 if check:
5817 if check:
5820 updatecheck = 'abort'
5818 updatecheck = 'abort'
5821 elif merge:
5819 elif merge:
5822 updatecheck = 'none'
5820 updatecheck = 'none'
5823
5821
5824 with repo.wlock():
5822 with repo.wlock():
5825 cmdutil.clearunfinished(repo)
5823 cmdutil.clearunfinished(repo)
5826
5824
5827 if date:
5825 if date:
5828 rev = cmdutil.finddate(ui, repo, date)
5826 rev = cmdutil.finddate(ui, repo, date)
5829
5827
5830 # if we defined a bookmark, we have to remember the original name
5828 # if we defined a bookmark, we have to remember the original name
5831 brev = rev
5829 brev = rev
5832 if rev:
5830 if rev:
5833 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5831 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5834 ctx = scmutil.revsingle(repo, rev, rev)
5832 ctx = scmutil.revsingle(repo, rev, rev)
5835 rev = ctx.rev()
5833 rev = ctx.rev()
5836 hidden = ctx.hidden()
5834 hidden = ctx.hidden()
5837 overrides = {('ui', 'forcemerge'): opts.get(r'tool', '')}
5835 overrides = {('ui', 'forcemerge'): opts.get(r'tool', '')}
5838 with ui.configoverride(overrides, 'update'):
5836 with ui.configoverride(overrides, 'update'):
5839 ret = hg.updatetotally(ui, repo, rev, brev, clean=clean,
5837 ret = hg.updatetotally(ui, repo, rev, brev, clean=clean,
5840 updatecheck=updatecheck)
5838 updatecheck=updatecheck)
5841 if hidden:
5839 if hidden:
5842 ctxstr = ctx.hex()[:12]
5840 ctxstr = ctx.hex()[:12]
5843 ui.warn(_("updated to hidden changeset %s\n") % ctxstr)
5841 ui.warn(_("updated to hidden changeset %s\n") % ctxstr)
5844
5842
5845 if ctx.obsolete():
5843 if ctx.obsolete():
5846 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
5844 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
5847 ui.warn("(%s)\n" % obsfatemsg)
5845 ui.warn("(%s)\n" % obsfatemsg)
5848 return ret
5846 return ret
5849
5847
5850 @command('verify', [])
5848 @command('verify', [])
5851 def verify(ui, repo):
5849 def verify(ui, repo):
5852 """verify the integrity of the repository
5850 """verify the integrity of the repository
5853
5851
5854 Verify the integrity of the current repository.
5852 Verify the integrity of the current repository.
5855
5853
5856 This will perform an extensive check of the repository's
5854 This will perform an extensive check of the repository's
5857 integrity, validating the hashes and checksums of each entry in
5855 integrity, validating the hashes and checksums of each entry in
5858 the changelog, manifest, and tracked files, as well as the
5856 the changelog, manifest, and tracked files, as well as the
5859 integrity of their crosslinks and indices.
5857 integrity of their crosslinks and indices.
5860
5858
5861 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5859 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5862 for more information about recovery from corruption of the
5860 for more information about recovery from corruption of the
5863 repository.
5861 repository.
5864
5862
5865 Returns 0 on success, 1 if errors are encountered.
5863 Returns 0 on success, 1 if errors are encountered.
5866 """
5864 """
5867 return hg.verify(repo)
5865 return hg.verify(repo)
5868
5866
5869 @command('version', [] + formatteropts, norepo=True,
5867 @command('version', [] + formatteropts, norepo=True,
5870 intents={INTENT_READONLY})
5868 intents={INTENT_READONLY})
5871 def version_(ui, **opts):
5869 def version_(ui, **opts):
5872 """output version and copyright information"""
5870 """output version and copyright information"""
5873 opts = pycompat.byteskwargs(opts)
5871 opts = pycompat.byteskwargs(opts)
5874 if ui.verbose:
5872 if ui.verbose:
5875 ui.pager('version')
5873 ui.pager('version')
5876 fm = ui.formatter("version", opts)
5874 fm = ui.formatter("version", opts)
5877 fm.startitem()
5875 fm.startitem()
5878 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5876 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5879 util.version())
5877 util.version())
5880 license = _(
5878 license = _(
5881 "(see https://mercurial-scm.org for more information)\n"
5879 "(see https://mercurial-scm.org for more information)\n"
5882 "\nCopyright (C) 2005-2018 Matt Mackall and others\n"
5880 "\nCopyright (C) 2005-2018 Matt Mackall and others\n"
5883 "This is free software; see the source for copying conditions. "
5881 "This is free software; see the source for copying conditions. "
5884 "There is NO\nwarranty; "
5882 "There is NO\nwarranty; "
5885 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5883 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5886 )
5884 )
5887 if not ui.quiet:
5885 if not ui.quiet:
5888 fm.plain(license)
5886 fm.plain(license)
5889
5887
5890 if ui.verbose:
5888 if ui.verbose:
5891 fm.plain(_("\nEnabled extensions:\n\n"))
5889 fm.plain(_("\nEnabled extensions:\n\n"))
5892 # format names and versions into columns
5890 # format names and versions into columns
5893 names = []
5891 names = []
5894 vers = []
5892 vers = []
5895 isinternals = []
5893 isinternals = []
5896 for name, module in extensions.extensions():
5894 for name, module in extensions.extensions():
5897 names.append(name)
5895 names.append(name)
5898 vers.append(extensions.moduleversion(module) or None)
5896 vers.append(extensions.moduleversion(module) or None)
5899 isinternals.append(extensions.ismoduleinternal(module))
5897 isinternals.append(extensions.ismoduleinternal(module))
5900 fn = fm.nested("extensions", tmpl='{name}\n')
5898 fn = fm.nested("extensions", tmpl='{name}\n')
5901 if names:
5899 if names:
5902 namefmt = " %%-%ds " % max(len(n) for n in names)
5900 namefmt = " %%-%ds " % max(len(n) for n in names)
5903 places = [_("external"), _("internal")]
5901 places = [_("external"), _("internal")]
5904 for n, v, p in zip(names, vers, isinternals):
5902 for n, v, p in zip(names, vers, isinternals):
5905 fn.startitem()
5903 fn.startitem()
5906 fn.condwrite(ui.verbose, "name", namefmt, n)
5904 fn.condwrite(ui.verbose, "name", namefmt, n)
5907 if ui.verbose:
5905 if ui.verbose:
5908 fn.plain("%s " % places[p])
5906 fn.plain("%s " % places[p])
5909 fn.data(bundled=p)
5907 fn.data(bundled=p)
5910 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5908 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5911 if ui.verbose:
5909 if ui.verbose:
5912 fn.plain("\n")
5910 fn.plain("\n")
5913 fn.end()
5911 fn.end()
5914 fm.end()
5912 fm.end()
5915
5913
5916 def loadcmdtable(ui, name, cmdtable):
5914 def loadcmdtable(ui, name, cmdtable):
5917 """Load command functions from specified cmdtable
5915 """Load command functions from specified cmdtable
5918 """
5916 """
5919 overrides = [cmd for cmd in cmdtable if cmd in table]
5917 overrides = [cmd for cmd in cmdtable if cmd in table]
5920 if overrides:
5918 if overrides:
5921 ui.warn(_("extension '%s' overrides commands: %s\n")
5919 ui.warn(_("extension '%s' overrides commands: %s\n")
5922 % (name, " ".join(overrides)))
5920 % (name, " ".join(overrides)))
5923 table.update(cmdtable)
5921 table.update(cmdtable)
@@ -1,678 +1,656
1 # formatter.py - generic output formatting for mercurial
1 # formatter.py - generic output formatting for mercurial
2 #
2 #
3 # Copyright 2012 Matt Mackall <mpm@selenic.com>
3 # Copyright 2012 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 """Generic output formatting for Mercurial
8 """Generic output formatting for Mercurial
9
9
10 The formatter provides API to show data in various ways. The following
10 The formatter provides API to show data in various ways. The following
11 functions should be used in place of ui.write():
11 functions should be used in place of ui.write():
12
12
13 - fm.write() for unconditional output
13 - fm.write() for unconditional output
14 - fm.condwrite() to show some extra data conditionally in plain output
14 - fm.condwrite() to show some extra data conditionally in plain output
15 - fm.context() to provide changectx to template output
15 - fm.context() to provide changectx to template output
16 - fm.data() to provide extra data to JSON or template output
16 - fm.data() to provide extra data to JSON or template output
17 - fm.plain() to show raw text that isn't provided to JSON or template output
17 - fm.plain() to show raw text that isn't provided to JSON or template output
18
18
19 To show structured data (e.g. date tuples, dicts, lists), apply fm.format*()
19 To show structured data (e.g. date tuples, dicts, lists), apply fm.format*()
20 beforehand so the data is converted to the appropriate data type. Use
20 beforehand so the data is converted to the appropriate data type. Use
21 fm.isplain() if you need to convert or format data conditionally which isn't
21 fm.isplain() if you need to convert or format data conditionally which isn't
22 supported by the formatter API.
22 supported by the formatter API.
23
23
24 To build nested structure (i.e. a list of dicts), use fm.nested().
24 To build nested structure (i.e. a list of dicts), use fm.nested().
25
25
26 See also https://www.mercurial-scm.org/wiki/GenericTemplatingPlan
26 See also https://www.mercurial-scm.org/wiki/GenericTemplatingPlan
27
27
28 fm.condwrite() vs 'if cond:':
28 fm.condwrite() vs 'if cond:':
29
29
30 In most cases, use fm.condwrite() so users can selectively show the data
30 In most cases, use fm.condwrite() so users can selectively show the data
31 in template output. If it's costly to build data, use plain 'if cond:' with
31 in template output. If it's costly to build data, use plain 'if cond:' with
32 fm.write().
32 fm.write().
33
33
34 fm.nested() vs fm.formatdict() (or fm.formatlist()):
34 fm.nested() vs fm.formatdict() (or fm.formatlist()):
35
35
36 fm.nested() should be used to form a tree structure (a list of dicts of
36 fm.nested() should be used to form a tree structure (a list of dicts of
37 lists of dicts...) which can be accessed through template keywords, e.g.
37 lists of dicts...) which can be accessed through template keywords, e.g.
38 "{foo % "{bar % {...}} {baz % {...}}"}". On the other hand, fm.formatdict()
38 "{foo % "{bar % {...}} {baz % {...}}"}". On the other hand, fm.formatdict()
39 exports a dict-type object to template, which can be accessed by e.g.
39 exports a dict-type object to template, which can be accessed by e.g.
40 "{get(foo, key)}" function.
40 "{get(foo, key)}" function.
41
41
42 Doctest helper:
42 Doctest helper:
43
43
44 >>> def show(fn, verbose=False, **opts):
44 >>> def show(fn, verbose=False, **opts):
45 ... import sys
45 ... import sys
46 ... from . import ui as uimod
46 ... from . import ui as uimod
47 ... ui = uimod.ui()
47 ... ui = uimod.ui()
48 ... ui.verbose = verbose
48 ... ui.verbose = verbose
49 ... ui.pushbuffer()
49 ... ui.pushbuffer()
50 ... try:
50 ... try:
51 ... return fn(ui, ui.formatter(pycompat.sysbytes(fn.__name__),
51 ... return fn(ui, ui.formatter(pycompat.sysbytes(fn.__name__),
52 ... pycompat.byteskwargs(opts)))
52 ... pycompat.byteskwargs(opts)))
53 ... finally:
53 ... finally:
54 ... print(pycompat.sysstr(ui.popbuffer()), end='')
54 ... print(pycompat.sysstr(ui.popbuffer()), end='')
55
55
56 Basic example:
56 Basic example:
57
57
58 >>> def files(ui, fm):
58 >>> def files(ui, fm):
59 ... files = [(b'foo', 123, (0, 0)), (b'bar', 456, (1, 0))]
59 ... files = [(b'foo', 123, (0, 0)), (b'bar', 456, (1, 0))]
60 ... for f in files:
60 ... for f in files:
61 ... fm.startitem()
61 ... fm.startitem()
62 ... fm.write(b'path', b'%s', f[0])
62 ... fm.write(b'path', b'%s', f[0])
63 ... fm.condwrite(ui.verbose, b'date', b' %s',
63 ... fm.condwrite(ui.verbose, b'date', b' %s',
64 ... fm.formatdate(f[2], b'%Y-%m-%d %H:%M:%S'))
64 ... fm.formatdate(f[2], b'%Y-%m-%d %H:%M:%S'))
65 ... fm.data(size=f[1])
65 ... fm.data(size=f[1])
66 ... fm.plain(b'\\n')
66 ... fm.plain(b'\\n')
67 ... fm.end()
67 ... fm.end()
68 >>> show(files)
68 >>> show(files)
69 foo
69 foo
70 bar
70 bar
71 >>> show(files, verbose=True)
71 >>> show(files, verbose=True)
72 foo 1970-01-01 00:00:00
72 foo 1970-01-01 00:00:00
73 bar 1970-01-01 00:00:01
73 bar 1970-01-01 00:00:01
74 >>> show(files, template=b'json')
74 >>> show(files, template=b'json')
75 [
75 [
76 {
76 {
77 "date": [0, 0],
77 "date": [0, 0],
78 "path": "foo",
78 "path": "foo",
79 "size": 123
79 "size": 123
80 },
80 },
81 {
81 {
82 "date": [1, 0],
82 "date": [1, 0],
83 "path": "bar",
83 "path": "bar",
84 "size": 456
84 "size": 456
85 }
85 }
86 ]
86 ]
87 >>> show(files, template=b'path: {path}\\ndate: {date|rfc3339date}\\n')
87 >>> show(files, template=b'path: {path}\\ndate: {date|rfc3339date}\\n')
88 path: foo
88 path: foo
89 date: 1970-01-01T00:00:00+00:00
89 date: 1970-01-01T00:00:00+00:00
90 path: bar
90 path: bar
91 date: 1970-01-01T00:00:01+00:00
91 date: 1970-01-01T00:00:01+00:00
92
92
93 Nested example:
93 Nested example:
94
94
95 >>> def subrepos(ui, fm):
95 >>> def subrepos(ui, fm):
96 ... fm.startitem()
96 ... fm.startitem()
97 ... fm.write(b'reponame', b'[%s]\\n', b'baz')
97 ... fm.write(b'reponame', b'[%s]\\n', b'baz')
98 ... files(ui, fm.nested(b'files', tmpl=b'{reponame}'))
98 ... files(ui, fm.nested(b'files', tmpl=b'{reponame}'))
99 ... fm.end()
99 ... fm.end()
100 >>> show(subrepos)
100 >>> show(subrepos)
101 [baz]
101 [baz]
102 foo
102 foo
103 bar
103 bar
104 >>> show(subrepos, template=b'{reponame}: {join(files % "{path}", ", ")}\\n')
104 >>> show(subrepos, template=b'{reponame}: {join(files % "{path}", ", ")}\\n')
105 baz: foo, bar
105 baz: foo, bar
106 """
106 """
107
107
108 from __future__ import absolute_import, print_function
108 from __future__ import absolute_import, print_function
109
109
110 import contextlib
110 import contextlib
111 import itertools
111 import itertools
112 import os
112 import os
113
113
114 from .i18n import _
114 from .i18n import _
115 from .node import (
115 from .node import (
116 hex,
116 hex,
117 short,
117 short,
118 )
118 )
119 from .thirdparty import (
119 from .thirdparty import (
120 attr,
120 attr,
121 )
121 )
122
122
123 from . import (
123 from . import (
124 error,
124 error,
125 pycompat,
125 pycompat,
126 templatefilters,
126 templatefilters,
127 templatefuncs,
128 templatekw,
127 templatekw,
129 templater,
128 templater,
130 templateutil,
129 templateutil,
131 util,
130 util,
132 )
131 )
133 from .utils import dateutil
132 from .utils import dateutil
134
133
135 pickle = util.pickle
134 pickle = util.pickle
136
135
137 class _nullconverter(object):
136 class _nullconverter(object):
138 '''convert non-primitive data types to be processed by formatter'''
137 '''convert non-primitive data types to be processed by formatter'''
139
138
140 # set to True if context object should be stored as item
139 # set to True if context object should be stored as item
141 storecontext = False
140 storecontext = False
142
141
143 @staticmethod
142 @staticmethod
144 def wrapnested(data, tmpl, sep):
143 def wrapnested(data, tmpl, sep):
145 '''wrap nested data by appropriate type'''
144 '''wrap nested data by appropriate type'''
146 return data
145 return data
147 @staticmethod
146 @staticmethod
148 def formatdate(date, fmt):
147 def formatdate(date, fmt):
149 '''convert date tuple to appropriate format'''
148 '''convert date tuple to appropriate format'''
150 # timestamp can be float, but the canonical form should be int
149 # timestamp can be float, but the canonical form should be int
151 ts, tz = date
150 ts, tz = date
152 return (int(ts), tz)
151 return (int(ts), tz)
153 @staticmethod
152 @staticmethod
154 def formatdict(data, key, value, fmt, sep):
153 def formatdict(data, key, value, fmt, sep):
155 '''convert dict or key-value pairs to appropriate dict format'''
154 '''convert dict or key-value pairs to appropriate dict format'''
156 # use plain dict instead of util.sortdict so that data can be
155 # use plain dict instead of util.sortdict so that data can be
157 # serialized as a builtin dict in pickle output
156 # serialized as a builtin dict in pickle output
158 return dict(data)
157 return dict(data)
159 @staticmethod
158 @staticmethod
160 def formatlist(data, name, fmt, sep):
159 def formatlist(data, name, fmt, sep):
161 '''convert iterable to appropriate list format'''
160 '''convert iterable to appropriate list format'''
162 return list(data)
161 return list(data)
163
162
164 class baseformatter(object):
163 class baseformatter(object):
165 def __init__(self, ui, topic, opts, converter):
164 def __init__(self, ui, topic, opts, converter):
166 self._ui = ui
165 self._ui = ui
167 self._topic = topic
166 self._topic = topic
168 self._opts = opts
167 self._opts = opts
169 self._converter = converter
168 self._converter = converter
170 self._item = None
169 self._item = None
171 # function to convert node to string suitable for this output
170 # function to convert node to string suitable for this output
172 self.hexfunc = hex
171 self.hexfunc = hex
173 def __enter__(self):
172 def __enter__(self):
174 return self
173 return self
175 def __exit__(self, exctype, excvalue, traceback):
174 def __exit__(self, exctype, excvalue, traceback):
176 if exctype is None:
175 if exctype is None:
177 self.end()
176 self.end()
178 def _showitem(self):
177 def _showitem(self):
179 '''show a formatted item once all data is collected'''
178 '''show a formatted item once all data is collected'''
180 def startitem(self):
179 def startitem(self):
181 '''begin an item in the format list'''
180 '''begin an item in the format list'''
182 if self._item is not None:
181 if self._item is not None:
183 self._showitem()
182 self._showitem()
184 self._item = {}
183 self._item = {}
185 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
184 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
186 '''convert date tuple to appropriate format'''
185 '''convert date tuple to appropriate format'''
187 return self._converter.formatdate(date, fmt)
186 return self._converter.formatdate(date, fmt)
188 def formatdict(self, data, key='key', value='value', fmt=None, sep=' '):
187 def formatdict(self, data, key='key', value='value', fmt=None, sep=' '):
189 '''convert dict or key-value pairs to appropriate dict format'''
188 '''convert dict or key-value pairs to appropriate dict format'''
190 return self._converter.formatdict(data, key, value, fmt, sep)
189 return self._converter.formatdict(data, key, value, fmt, sep)
191 def formatlist(self, data, name, fmt=None, sep=' '):
190 def formatlist(self, data, name, fmt=None, sep=' '):
192 '''convert iterable to appropriate list format'''
191 '''convert iterable to appropriate list format'''
193 # name is mandatory argument for now, but it could be optional if
192 # name is mandatory argument for now, but it could be optional if
194 # we have default template keyword, e.g. {item}
193 # we have default template keyword, e.g. {item}
195 return self._converter.formatlist(data, name, fmt, sep)
194 return self._converter.formatlist(data, name, fmt, sep)
196 def contexthint(self, datafields):
197 '''set of context object keys to be required given datafields set'''
198 return set()
199 def context(self, **ctxs):
195 def context(self, **ctxs):
200 '''insert context objects to be used to render template keywords'''
196 '''insert context objects to be used to render template keywords'''
201 ctxs = pycompat.byteskwargs(ctxs)
197 ctxs = pycompat.byteskwargs(ctxs)
202 assert all(k in {'repo', 'ctx', 'fctx'} for k in ctxs)
198 assert all(k in {'repo', 'ctx', 'fctx'} for k in ctxs)
203 if self._converter.storecontext:
199 if self._converter.storecontext:
204 # populate missing resources in fctx -> ctx -> repo order
200 # populate missing resources in fctx -> ctx -> repo order
205 if 'fctx' in ctxs and 'ctx' not in ctxs:
201 if 'fctx' in ctxs and 'ctx' not in ctxs:
206 ctxs['ctx'] = ctxs['fctx'].changectx()
202 ctxs['ctx'] = ctxs['fctx'].changectx()
207 if 'ctx' in ctxs and 'repo' not in ctxs:
203 if 'ctx' in ctxs and 'repo' not in ctxs:
208 ctxs['repo'] = ctxs['ctx'].repo()
204 ctxs['repo'] = ctxs['ctx'].repo()
209 self._item.update(ctxs)
205 self._item.update(ctxs)
210 def datahint(self):
206 def datahint(self):
211 '''set of field names to be referenced'''
207 '''set of field names to be referenced'''
212 return set()
208 return set()
213 def data(self, **data):
209 def data(self, **data):
214 '''insert data into item that's not shown in default output'''
210 '''insert data into item that's not shown in default output'''
215 data = pycompat.byteskwargs(data)
211 data = pycompat.byteskwargs(data)
216 self._item.update(data)
212 self._item.update(data)
217 def write(self, fields, deftext, *fielddata, **opts):
213 def write(self, fields, deftext, *fielddata, **opts):
218 '''do default text output while assigning data to item'''
214 '''do default text output while assigning data to item'''
219 fieldkeys = fields.split()
215 fieldkeys = fields.split()
220 assert len(fieldkeys) == len(fielddata)
216 assert len(fieldkeys) == len(fielddata)
221 self._item.update(zip(fieldkeys, fielddata))
217 self._item.update(zip(fieldkeys, fielddata))
222 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
218 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
223 '''do conditional write (primarily for plain formatter)'''
219 '''do conditional write (primarily for plain formatter)'''
224 fieldkeys = fields.split()
220 fieldkeys = fields.split()
225 assert len(fieldkeys) == len(fielddata)
221 assert len(fieldkeys) == len(fielddata)
226 self._item.update(zip(fieldkeys, fielddata))
222 self._item.update(zip(fieldkeys, fielddata))
227 def plain(self, text, **opts):
223 def plain(self, text, **opts):
228 '''show raw text for non-templated mode'''
224 '''show raw text for non-templated mode'''
229 def isplain(self):
225 def isplain(self):
230 '''check for plain formatter usage'''
226 '''check for plain formatter usage'''
231 return False
227 return False
232 def nested(self, field, tmpl=None, sep=''):
228 def nested(self, field, tmpl=None, sep=''):
233 '''sub formatter to store nested data in the specified field'''
229 '''sub formatter to store nested data in the specified field'''
234 data = []
230 data = []
235 self._item[field] = self._converter.wrapnested(data, tmpl, sep)
231 self._item[field] = self._converter.wrapnested(data, tmpl, sep)
236 return _nestedformatter(self._ui, self._converter, data)
232 return _nestedformatter(self._ui, self._converter, data)
237 def end(self):
233 def end(self):
238 '''end output for the formatter'''
234 '''end output for the formatter'''
239 if self._item is not None:
235 if self._item is not None:
240 self._showitem()
236 self._showitem()
241
237
242 def nullformatter(ui, topic, opts):
238 def nullformatter(ui, topic, opts):
243 '''formatter that prints nothing'''
239 '''formatter that prints nothing'''
244 return baseformatter(ui, topic, opts, converter=_nullconverter)
240 return baseformatter(ui, topic, opts, converter=_nullconverter)
245
241
246 class _nestedformatter(baseformatter):
242 class _nestedformatter(baseformatter):
247 '''build sub items and store them in the parent formatter'''
243 '''build sub items and store them in the parent formatter'''
248 def __init__(self, ui, converter, data):
244 def __init__(self, ui, converter, data):
249 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
245 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
250 self._data = data
246 self._data = data
251 def _showitem(self):
247 def _showitem(self):
252 self._data.append(self._item)
248 self._data.append(self._item)
253
249
254 def _iteritems(data):
250 def _iteritems(data):
255 '''iterate key-value pairs in stable order'''
251 '''iterate key-value pairs in stable order'''
256 if isinstance(data, dict):
252 if isinstance(data, dict):
257 return sorted(data.iteritems())
253 return sorted(data.iteritems())
258 return data
254 return data
259
255
260 class _plainconverter(object):
256 class _plainconverter(object):
261 '''convert non-primitive data types to text'''
257 '''convert non-primitive data types to text'''
262
258
263 storecontext = False
259 storecontext = False
264
260
265 @staticmethod
261 @staticmethod
266 def wrapnested(data, tmpl, sep):
262 def wrapnested(data, tmpl, sep):
267 raise error.ProgrammingError('plainformatter should never be nested')
263 raise error.ProgrammingError('plainformatter should never be nested')
268 @staticmethod
264 @staticmethod
269 def formatdate(date, fmt):
265 def formatdate(date, fmt):
270 '''stringify date tuple in the given format'''
266 '''stringify date tuple in the given format'''
271 return dateutil.datestr(date, fmt)
267 return dateutil.datestr(date, fmt)
272 @staticmethod
268 @staticmethod
273 def formatdict(data, key, value, fmt, sep):
269 def formatdict(data, key, value, fmt, sep):
274 '''stringify key-value pairs separated by sep'''
270 '''stringify key-value pairs separated by sep'''
275 prefmt = pycompat.identity
271 prefmt = pycompat.identity
276 if fmt is None:
272 if fmt is None:
277 fmt = '%s=%s'
273 fmt = '%s=%s'
278 prefmt = pycompat.bytestr
274 prefmt = pycompat.bytestr
279 return sep.join(fmt % (prefmt(k), prefmt(v))
275 return sep.join(fmt % (prefmt(k), prefmt(v))
280 for k, v in _iteritems(data))
276 for k, v in _iteritems(data))
281 @staticmethod
277 @staticmethod
282 def formatlist(data, name, fmt, sep):
278 def formatlist(data, name, fmt, sep):
283 '''stringify iterable separated by sep'''
279 '''stringify iterable separated by sep'''
284 prefmt = pycompat.identity
280 prefmt = pycompat.identity
285 if fmt is None:
281 if fmt is None:
286 fmt = '%s'
282 fmt = '%s'
287 prefmt = pycompat.bytestr
283 prefmt = pycompat.bytestr
288 return sep.join(fmt % prefmt(e) for e in data)
284 return sep.join(fmt % prefmt(e) for e in data)
289
285
290 class plainformatter(baseformatter):
286 class plainformatter(baseformatter):
291 '''the default text output scheme'''
287 '''the default text output scheme'''
292 def __init__(self, ui, out, topic, opts):
288 def __init__(self, ui, out, topic, opts):
293 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
289 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
294 if ui.debugflag:
290 if ui.debugflag:
295 self.hexfunc = hex
291 self.hexfunc = hex
296 else:
292 else:
297 self.hexfunc = short
293 self.hexfunc = short
298 if ui is out:
294 if ui is out:
299 self._write = ui.write
295 self._write = ui.write
300 else:
296 else:
301 self._write = lambda s, **opts: out.write(s)
297 self._write = lambda s, **opts: out.write(s)
302 def startitem(self):
298 def startitem(self):
303 pass
299 pass
304 def data(self, **data):
300 def data(self, **data):
305 pass
301 pass
306 def write(self, fields, deftext, *fielddata, **opts):
302 def write(self, fields, deftext, *fielddata, **opts):
307 self._write(deftext % fielddata, **opts)
303 self._write(deftext % fielddata, **opts)
308 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
304 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
309 '''do conditional write'''
305 '''do conditional write'''
310 if cond:
306 if cond:
311 self._write(deftext % fielddata, **opts)
307 self._write(deftext % fielddata, **opts)
312 def plain(self, text, **opts):
308 def plain(self, text, **opts):
313 self._write(text, **opts)
309 self._write(text, **opts)
314 def isplain(self):
310 def isplain(self):
315 return True
311 return True
316 def nested(self, field, tmpl=None, sep=''):
312 def nested(self, field, tmpl=None, sep=''):
317 # nested data will be directly written to ui
313 # nested data will be directly written to ui
318 return self
314 return self
319 def end(self):
315 def end(self):
320 pass
316 pass
321
317
322 class debugformatter(baseformatter):
318 class debugformatter(baseformatter):
323 def __init__(self, ui, out, topic, opts):
319 def __init__(self, ui, out, topic, opts):
324 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
320 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
325 self._out = out
321 self._out = out
326 self._out.write("%s = [\n" % self._topic)
322 self._out.write("%s = [\n" % self._topic)
327 def _showitem(self):
323 def _showitem(self):
328 self._out.write(' %s,\n' % pycompat.byterepr(self._item))
324 self._out.write(' %s,\n' % pycompat.byterepr(self._item))
329 def end(self):
325 def end(self):
330 baseformatter.end(self)
326 baseformatter.end(self)
331 self._out.write("]\n")
327 self._out.write("]\n")
332
328
333 class pickleformatter(baseformatter):
329 class pickleformatter(baseformatter):
334 def __init__(self, ui, out, topic, opts):
330 def __init__(self, ui, out, topic, opts):
335 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
331 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
336 self._out = out
332 self._out = out
337 self._data = []
333 self._data = []
338 def _showitem(self):
334 def _showitem(self):
339 self._data.append(self._item)
335 self._data.append(self._item)
340 def end(self):
336 def end(self):
341 baseformatter.end(self)
337 baseformatter.end(self)
342 self._out.write(pickle.dumps(self._data))
338 self._out.write(pickle.dumps(self._data))
343
339
344 class jsonformatter(baseformatter):
340 class jsonformatter(baseformatter):
345 def __init__(self, ui, out, topic, opts):
341 def __init__(self, ui, out, topic, opts):
346 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
342 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
347 self._out = out
343 self._out = out
348 self._out.write("[")
344 self._out.write("[")
349 self._first = True
345 self._first = True
350 def _showitem(self):
346 def _showitem(self):
351 if self._first:
347 if self._first:
352 self._first = False
348 self._first = False
353 else:
349 else:
354 self._out.write(",")
350 self._out.write(",")
355
351
356 self._out.write("\n {\n")
352 self._out.write("\n {\n")
357 first = True
353 first = True
358 for k, v in sorted(self._item.items()):
354 for k, v in sorted(self._item.items()):
359 if first:
355 if first:
360 first = False
356 first = False
361 else:
357 else:
362 self._out.write(",\n")
358 self._out.write(",\n")
363 u = templatefilters.json(v, paranoid=False)
359 u = templatefilters.json(v, paranoid=False)
364 self._out.write(' "%s": %s' % (k, u))
360 self._out.write(' "%s": %s' % (k, u))
365 self._out.write("\n }")
361 self._out.write("\n }")
366 def end(self):
362 def end(self):
367 baseformatter.end(self)
363 baseformatter.end(self)
368 self._out.write("\n]\n")
364 self._out.write("\n]\n")
369
365
370 class _templateconverter(object):
366 class _templateconverter(object):
371 '''convert non-primitive data types to be processed by templater'''
367 '''convert non-primitive data types to be processed by templater'''
372
368
373 storecontext = True
369 storecontext = True
374
370
375 @staticmethod
371 @staticmethod
376 def wrapnested(data, tmpl, sep):
372 def wrapnested(data, tmpl, sep):
377 '''wrap nested data by templatable type'''
373 '''wrap nested data by templatable type'''
378 return templateutil.mappinglist(data, tmpl=tmpl, sep=sep)
374 return templateutil.mappinglist(data, tmpl=tmpl, sep=sep)
379 @staticmethod
375 @staticmethod
380 def formatdate(date, fmt):
376 def formatdate(date, fmt):
381 '''return date tuple'''
377 '''return date tuple'''
382 return templateutil.date(date)
378 return templateutil.date(date)
383 @staticmethod
379 @staticmethod
384 def formatdict(data, key, value, fmt, sep):
380 def formatdict(data, key, value, fmt, sep):
385 '''build object that can be evaluated as either plain string or dict'''
381 '''build object that can be evaluated as either plain string or dict'''
386 data = util.sortdict(_iteritems(data))
382 data = util.sortdict(_iteritems(data))
387 def f():
383 def f():
388 yield _plainconverter.formatdict(data, key, value, fmt, sep)
384 yield _plainconverter.formatdict(data, key, value, fmt, sep)
389 return templateutil.hybriddict(data, key=key, value=value, fmt=fmt,
385 return templateutil.hybriddict(data, key=key, value=value, fmt=fmt,
390 gen=f)
386 gen=f)
391 @staticmethod
387 @staticmethod
392 def formatlist(data, name, fmt, sep):
388 def formatlist(data, name, fmt, sep):
393 '''build object that can be evaluated as either plain string or list'''
389 '''build object that can be evaluated as either plain string or list'''
394 data = list(data)
390 data = list(data)
395 def f():
391 def f():
396 yield _plainconverter.formatlist(data, name, fmt, sep)
392 yield _plainconverter.formatlist(data, name, fmt, sep)
397 return templateutil.hybridlist(data, name=name, fmt=fmt, gen=f)
393 return templateutil.hybridlist(data, name=name, fmt=fmt, gen=f)
398
394
399 class templateformatter(baseformatter):
395 class templateformatter(baseformatter):
400 def __init__(self, ui, out, topic, opts):
396 def __init__(self, ui, out, topic, opts):
401 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
397 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
402 self._out = out
398 self._out = out
403 spec = lookuptemplate(ui, topic, opts.get('template', ''))
399 spec = lookuptemplate(ui, topic, opts.get('template', ''))
404 self._tref = spec.ref
400 self._tref = spec.ref
405 self._t = loadtemplater(ui, spec, defaults=templatekw.keywords,
401 self._t = loadtemplater(ui, spec, defaults=templatekw.keywords,
406 resources=templateresources(ui),
402 resources=templateresources(ui),
407 cache=templatekw.defaulttempl)
403 cache=templatekw.defaulttempl)
408 self._parts = templatepartsmap(spec, self._t,
404 self._parts = templatepartsmap(spec, self._t,
409 ['docheader', 'docfooter', 'separator'])
405 ['docheader', 'docfooter', 'separator'])
410 self._counter = itertools.count()
406 self._counter = itertools.count()
411 self._renderitem('docheader', {})
407 self._renderitem('docheader', {})
412
408
413 def _showitem(self):
409 def _showitem(self):
414 item = self._item.copy()
410 item = self._item.copy()
415 item['index'] = index = next(self._counter)
411 item['index'] = index = next(self._counter)
416 if index > 0:
412 if index > 0:
417 self._renderitem('separator', {})
413 self._renderitem('separator', {})
418 self._renderitem(self._tref, item)
414 self._renderitem(self._tref, item)
419
415
420 def _renderitem(self, part, item):
416 def _renderitem(self, part, item):
421 if part not in self._parts:
417 if part not in self._parts:
422 return
418 return
423 ref = self._parts[part]
419 ref = self._parts[part]
424 self._out.write(self._t.render(ref, item))
420 self._out.write(self._t.render(ref, item))
425
421
426 @util.propertycache
422 @util.propertycache
427 def _symbolsused(self):
423 def _symbolsused(self):
428 return self._t.symbolsused(self._tref)
424 return self._t.symbolsused(self._tref)
429
425
430 def contexthint(self, datafields):
431 '''set of context object keys to be required by the template, given
432 datafields overridden by immediate values'''
433 requires = set()
434 ksyms, fsyms = self._symbolsused
435 ksyms = ksyms - set(datafields.split()) # exclude immediate fields
436 symtables = [(ksyms, templatekw.keywords),
437 (fsyms, templatefuncs.funcs)]
438 for syms, table in symtables:
439 for k in syms:
440 f = table.get(k)
441 if not f:
442 continue
443 requires.update(getattr(f, '_requires', ()))
444 if 'repo' in requires:
445 requires.add('ctx') # there's no API to pass repo to formatter
446 return requires & {'ctx', 'fctx'}
447
448 def datahint(self):
426 def datahint(self):
449 '''set of field names to be referenced from the template'''
427 '''set of field names to be referenced from the template'''
450 return self._symbolsused[0]
428 return self._symbolsused[0]
451
429
452 def end(self):
430 def end(self):
453 baseformatter.end(self)
431 baseformatter.end(self)
454 self._renderitem('docfooter', {})
432 self._renderitem('docfooter', {})
455
433
456 @attr.s(frozen=True)
434 @attr.s(frozen=True)
457 class templatespec(object):
435 class templatespec(object):
458 ref = attr.ib()
436 ref = attr.ib()
459 tmpl = attr.ib()
437 tmpl = attr.ib()
460 mapfile = attr.ib()
438 mapfile = attr.ib()
461
439
462 def lookuptemplate(ui, topic, tmpl):
440 def lookuptemplate(ui, topic, tmpl):
463 """Find the template matching the given -T/--template spec 'tmpl'
441 """Find the template matching the given -T/--template spec 'tmpl'
464
442
465 'tmpl' can be any of the following:
443 'tmpl' can be any of the following:
466
444
467 - a literal template (e.g. '{rev}')
445 - a literal template (e.g. '{rev}')
468 - a map-file name or path (e.g. 'changelog')
446 - a map-file name or path (e.g. 'changelog')
469 - a reference to [templates] in config file
447 - a reference to [templates] in config file
470 - a path to raw template file
448 - a path to raw template file
471
449
472 A map file defines a stand-alone template environment. If a map file
450 A map file defines a stand-alone template environment. If a map file
473 selected, all templates defined in the file will be loaded, and the
451 selected, all templates defined in the file will be loaded, and the
474 template matching the given topic will be rendered. Aliases won't be
452 template matching the given topic will be rendered. Aliases won't be
475 loaded from user config, but from the map file.
453 loaded from user config, but from the map file.
476
454
477 If no map file selected, all templates in [templates] section will be
455 If no map file selected, all templates in [templates] section will be
478 available as well as aliases in [templatealias].
456 available as well as aliases in [templatealias].
479 """
457 """
480
458
481 # looks like a literal template?
459 # looks like a literal template?
482 if '{' in tmpl:
460 if '{' in tmpl:
483 return templatespec('', tmpl, None)
461 return templatespec('', tmpl, None)
484
462
485 # perhaps a stock style?
463 # perhaps a stock style?
486 if not os.path.split(tmpl)[0]:
464 if not os.path.split(tmpl)[0]:
487 mapname = (templater.templatepath('map-cmdline.' + tmpl)
465 mapname = (templater.templatepath('map-cmdline.' + tmpl)
488 or templater.templatepath(tmpl))
466 or templater.templatepath(tmpl))
489 if mapname and os.path.isfile(mapname):
467 if mapname and os.path.isfile(mapname):
490 return templatespec(topic, None, mapname)
468 return templatespec(topic, None, mapname)
491
469
492 # perhaps it's a reference to [templates]
470 # perhaps it's a reference to [templates]
493 if ui.config('templates', tmpl):
471 if ui.config('templates', tmpl):
494 return templatespec(tmpl, None, None)
472 return templatespec(tmpl, None, None)
495
473
496 if tmpl == 'list':
474 if tmpl == 'list':
497 ui.write(_("available styles: %s\n") % templater.stylelist())
475 ui.write(_("available styles: %s\n") % templater.stylelist())
498 raise error.Abort(_("specify a template"))
476 raise error.Abort(_("specify a template"))
499
477
500 # perhaps it's a path to a map or a template
478 # perhaps it's a path to a map or a template
501 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
479 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
502 # is it a mapfile for a style?
480 # is it a mapfile for a style?
503 if os.path.basename(tmpl).startswith("map-"):
481 if os.path.basename(tmpl).startswith("map-"):
504 return templatespec(topic, None, os.path.realpath(tmpl))
482 return templatespec(topic, None, os.path.realpath(tmpl))
505 with util.posixfile(tmpl, 'rb') as f:
483 with util.posixfile(tmpl, 'rb') as f:
506 tmpl = f.read()
484 tmpl = f.read()
507 return templatespec('', tmpl, None)
485 return templatespec('', tmpl, None)
508
486
509 # constant string?
487 # constant string?
510 return templatespec('', tmpl, None)
488 return templatespec('', tmpl, None)
511
489
512 def templatepartsmap(spec, t, partnames):
490 def templatepartsmap(spec, t, partnames):
513 """Create a mapping of {part: ref}"""
491 """Create a mapping of {part: ref}"""
514 partsmap = {spec.ref: spec.ref} # initial ref must exist in t
492 partsmap = {spec.ref: spec.ref} # initial ref must exist in t
515 if spec.mapfile:
493 if spec.mapfile:
516 partsmap.update((p, p) for p in partnames if p in t)
494 partsmap.update((p, p) for p in partnames if p in t)
517 elif spec.ref:
495 elif spec.ref:
518 for part in partnames:
496 for part in partnames:
519 ref = '%s:%s' % (spec.ref, part) # select config sub-section
497 ref = '%s:%s' % (spec.ref, part) # select config sub-section
520 if ref in t:
498 if ref in t:
521 partsmap[part] = ref
499 partsmap[part] = ref
522 return partsmap
500 return partsmap
523
501
524 def loadtemplater(ui, spec, defaults=None, resources=None, cache=None):
502 def loadtemplater(ui, spec, defaults=None, resources=None, cache=None):
525 """Create a templater from either a literal template or loading from
503 """Create a templater from either a literal template or loading from
526 a map file"""
504 a map file"""
527 assert not (spec.tmpl and spec.mapfile)
505 assert not (spec.tmpl and spec.mapfile)
528 if spec.mapfile:
506 if spec.mapfile:
529 frommapfile = templater.templater.frommapfile
507 frommapfile = templater.templater.frommapfile
530 return frommapfile(spec.mapfile, defaults=defaults, resources=resources,
508 return frommapfile(spec.mapfile, defaults=defaults, resources=resources,
531 cache=cache)
509 cache=cache)
532 return maketemplater(ui, spec.tmpl, defaults=defaults, resources=resources,
510 return maketemplater(ui, spec.tmpl, defaults=defaults, resources=resources,
533 cache=cache)
511 cache=cache)
534
512
535 def maketemplater(ui, tmpl, defaults=None, resources=None, cache=None):
513 def maketemplater(ui, tmpl, defaults=None, resources=None, cache=None):
536 """Create a templater from a string template 'tmpl'"""
514 """Create a templater from a string template 'tmpl'"""
537 aliases = ui.configitems('templatealias')
515 aliases = ui.configitems('templatealias')
538 t = templater.templater(defaults=defaults, resources=resources,
516 t = templater.templater(defaults=defaults, resources=resources,
539 cache=cache, aliases=aliases)
517 cache=cache, aliases=aliases)
540 t.cache.update((k, templater.unquotestring(v))
518 t.cache.update((k, templater.unquotestring(v))
541 for k, v in ui.configitems('templates'))
519 for k, v in ui.configitems('templates'))
542 if tmpl:
520 if tmpl:
543 t.cache[''] = tmpl
521 t.cache[''] = tmpl
544 return t
522 return t
545
523
546 # marker to denote a resource to be loaded on demand based on mapping values
524 # marker to denote a resource to be loaded on demand based on mapping values
547 # (e.g. (ctx, path) -> fctx)
525 # (e.g. (ctx, path) -> fctx)
548 _placeholder = object()
526 _placeholder = object()
549
527
550 class templateresources(templater.resourcemapper):
528 class templateresources(templater.resourcemapper):
551 """Resource mapper designed for the default templatekw and function"""
529 """Resource mapper designed for the default templatekw and function"""
552
530
553 def __init__(self, ui, repo=None):
531 def __init__(self, ui, repo=None):
554 self._resmap = {
532 self._resmap = {
555 'cache': {}, # for templatekw/funcs to store reusable data
533 'cache': {}, # for templatekw/funcs to store reusable data
556 'repo': repo,
534 'repo': repo,
557 'ui': ui,
535 'ui': ui,
558 }
536 }
559
537
560 def availablekeys(self, mapping):
538 def availablekeys(self, mapping):
561 return {k for k in self.knownkeys()
539 return {k for k in self.knownkeys()
562 if self._getsome(mapping, k) is not None}
540 if self._getsome(mapping, k) is not None}
563
541
564 def knownkeys(self):
542 def knownkeys(self):
565 return {'cache', 'ctx', 'fctx', 'repo', 'revcache', 'ui'}
543 return {'cache', 'ctx', 'fctx', 'repo', 'revcache', 'ui'}
566
544
567 def lookup(self, mapping, key):
545 def lookup(self, mapping, key):
568 if key not in self.knownkeys():
546 if key not in self.knownkeys():
569 return None
547 return None
570 v = self._getsome(mapping, key)
548 v = self._getsome(mapping, key)
571 if v is _placeholder:
549 if v is _placeholder:
572 v = mapping[key] = self._loadermap[key](self, mapping)
550 v = mapping[key] = self._loadermap[key](self, mapping)
573 return v
551 return v
574
552
575 def populatemap(self, context, origmapping, newmapping):
553 def populatemap(self, context, origmapping, newmapping):
576 mapping = {}
554 mapping = {}
577 if self._hasnodespec(newmapping):
555 if self._hasnodespec(newmapping):
578 mapping['revcache'] = {} # per-ctx cache
556 mapping['revcache'] = {} # per-ctx cache
579 if self._hasnodespec(origmapping) and self._hasnodespec(newmapping):
557 if self._hasnodespec(origmapping) and self._hasnodespec(newmapping):
580 orignode = templateutil.runsymbol(context, origmapping, 'node')
558 orignode = templateutil.runsymbol(context, origmapping, 'node')
581 mapping['originalnode'] = orignode
559 mapping['originalnode'] = orignode
582 # put marker to override 'ctx'/'fctx' in mapping if any, and flag
560 # put marker to override 'ctx'/'fctx' in mapping if any, and flag
583 # its existence to be reported by availablekeys()
561 # its existence to be reported by availablekeys()
584 if 'ctx' not in newmapping and self._hasliteral(newmapping, 'node'):
562 if 'ctx' not in newmapping and self._hasliteral(newmapping, 'node'):
585 mapping['ctx'] = _placeholder
563 mapping['ctx'] = _placeholder
586 if 'fctx' not in newmapping and self._hasliteral(newmapping, 'path'):
564 if 'fctx' not in newmapping and self._hasliteral(newmapping, 'path'):
587 mapping['fctx'] = _placeholder
565 mapping['fctx'] = _placeholder
588 return mapping
566 return mapping
589
567
590 def _getsome(self, mapping, key):
568 def _getsome(self, mapping, key):
591 v = mapping.get(key)
569 v = mapping.get(key)
592 if v is not None:
570 if v is not None:
593 return v
571 return v
594 return self._resmap.get(key)
572 return self._resmap.get(key)
595
573
596 def _hasliteral(self, mapping, key):
574 def _hasliteral(self, mapping, key):
597 """Test if a literal value is set or unset in the given mapping"""
575 """Test if a literal value is set or unset in the given mapping"""
598 return key in mapping and not callable(mapping[key])
576 return key in mapping and not callable(mapping[key])
599
577
600 def _getliteral(self, mapping, key):
578 def _getliteral(self, mapping, key):
601 """Return value of the given name if it is a literal"""
579 """Return value of the given name if it is a literal"""
602 v = mapping.get(key)
580 v = mapping.get(key)
603 if callable(v):
581 if callable(v):
604 return None
582 return None
605 return v
583 return v
606
584
607 def _hasnodespec(self, mapping):
585 def _hasnodespec(self, mapping):
608 """Test if context revision is set or unset in the given mapping"""
586 """Test if context revision is set or unset in the given mapping"""
609 return 'node' in mapping or 'ctx' in mapping
587 return 'node' in mapping or 'ctx' in mapping
610
588
611 def _loadctx(self, mapping):
589 def _loadctx(self, mapping):
612 repo = self._getsome(mapping, 'repo')
590 repo = self._getsome(mapping, 'repo')
613 node = self._getliteral(mapping, 'node')
591 node = self._getliteral(mapping, 'node')
614 if repo is None or node is None:
592 if repo is None or node is None:
615 return
593 return
616 try:
594 try:
617 return repo[node]
595 return repo[node]
618 except error.RepoLookupError:
596 except error.RepoLookupError:
619 return None # maybe hidden/non-existent node
597 return None # maybe hidden/non-existent node
620
598
621 def _loadfctx(self, mapping):
599 def _loadfctx(self, mapping):
622 ctx = self._getsome(mapping, 'ctx')
600 ctx = self._getsome(mapping, 'ctx')
623 path = self._getliteral(mapping, 'path')
601 path = self._getliteral(mapping, 'path')
624 if ctx is None or path is None:
602 if ctx is None or path is None:
625 return None
603 return None
626 try:
604 try:
627 return ctx[path]
605 return ctx[path]
628 except error.LookupError:
606 except error.LookupError:
629 return None # maybe removed file?
607 return None # maybe removed file?
630
608
631 _loadermap = {
609 _loadermap = {
632 'ctx': _loadctx,
610 'ctx': _loadctx,
633 'fctx': _loadfctx,
611 'fctx': _loadfctx,
634 }
612 }
635
613
636 def formatter(ui, out, topic, opts):
614 def formatter(ui, out, topic, opts):
637 template = opts.get("template", "")
615 template = opts.get("template", "")
638 if template == "json":
616 if template == "json":
639 return jsonformatter(ui, out, topic, opts)
617 return jsonformatter(ui, out, topic, opts)
640 elif template == "pickle":
618 elif template == "pickle":
641 return pickleformatter(ui, out, topic, opts)
619 return pickleformatter(ui, out, topic, opts)
642 elif template == "debug":
620 elif template == "debug":
643 return debugformatter(ui, out, topic, opts)
621 return debugformatter(ui, out, topic, opts)
644 elif template != "":
622 elif template != "":
645 return templateformatter(ui, out, topic, opts)
623 return templateformatter(ui, out, topic, opts)
646 # developer config: ui.formatdebug
624 # developer config: ui.formatdebug
647 elif ui.configbool('ui', 'formatdebug'):
625 elif ui.configbool('ui', 'formatdebug'):
648 return debugformatter(ui, out, topic, opts)
626 return debugformatter(ui, out, topic, opts)
649 # deprecated config: ui.formatjson
627 # deprecated config: ui.formatjson
650 elif ui.configbool('ui', 'formatjson'):
628 elif ui.configbool('ui', 'formatjson'):
651 return jsonformatter(ui, out, topic, opts)
629 return jsonformatter(ui, out, topic, opts)
652 return plainformatter(ui, out, topic, opts)
630 return plainformatter(ui, out, topic, opts)
653
631
654 @contextlib.contextmanager
632 @contextlib.contextmanager
655 def openformatter(ui, filename, topic, opts):
633 def openformatter(ui, filename, topic, opts):
656 """Create a formatter that writes outputs to the specified file
634 """Create a formatter that writes outputs to the specified file
657
635
658 Must be invoked using the 'with' statement.
636 Must be invoked using the 'with' statement.
659 """
637 """
660 with util.posixfile(filename, 'wb') as out:
638 with util.posixfile(filename, 'wb') as out:
661 with formatter(ui, out, topic, opts) as fm:
639 with formatter(ui, out, topic, opts) as fm:
662 yield fm
640 yield fm
663
641
664 @contextlib.contextmanager
642 @contextlib.contextmanager
665 def _neverending(fm):
643 def _neverending(fm):
666 yield fm
644 yield fm
667
645
668 def maybereopen(fm, filename):
646 def maybereopen(fm, filename):
669 """Create a formatter backed by file if filename specified, else return
647 """Create a formatter backed by file if filename specified, else return
670 the given formatter
648 the given formatter
671
649
672 Must be invoked using the 'with' statement. This will never call fm.end()
650 Must be invoked using the 'with' statement. This will never call fm.end()
673 of the given formatter.
651 of the given formatter.
674 """
652 """
675 if filename:
653 if filename:
676 return openformatter(fm._ui, filename, fm._topic, fm._opts)
654 return openformatter(fm._ui, filename, fm._topic, fm._opts)
677 else:
655 else:
678 return _neverending(fm)
656 return _neverending(fm)
General Comments 0
You need to be logged in to leave comments. Login now