##// END OF EJS Templates
cleanup: re-run black on the codebase...
Augie Fackler -
r44787:a0ec05d9 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,756 +1,756 b''
1 # branchmap.py - logic to computes, maintain and stores branchmap for local repo
1 # branchmap.py - logic to computes, maintain and stores branchmap for local repo
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 struct
10 import struct
11
11
12 from .node import (
12 from .node import (
13 bin,
13 bin,
14 hex,
14 hex,
15 nullid,
15 nullid,
16 nullrev,
16 nullrev,
17 )
17 )
18 from . import (
18 from . import (
19 encoding,
19 encoding,
20 error,
20 error,
21 pycompat,
21 pycompat,
22 scmutil,
22 scmutil,
23 util,
23 util,
24 )
24 )
25 from .utils import (
25 from .utils import (
26 repoviewutil,
26 repoviewutil,
27 stringutil,
27 stringutil,
28 )
28 )
29
29
30 if pycompat.TYPE_CHECKING:
30 if pycompat.TYPE_CHECKING:
31 from typing import (
31 from typing import (
32 Any,
32 Any,
33 Callable,
33 Callable,
34 Dict,
34 Dict,
35 Iterable,
35 Iterable,
36 List,
36 List,
37 Optional,
37 Optional,
38 Set,
38 Set,
39 Tuple,
39 Tuple,
40 Union,
40 Union,
41 )
41 )
42
42
43 assert any(
43 assert any(
44 (Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Union,)
44 (Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Union,)
45 )
45 )
46
46
47 subsettable = repoviewutil.subsettable
47 subsettable = repoviewutil.subsettable
48
48
49 calcsize = struct.calcsize
49 calcsize = struct.calcsize
50 pack_into = struct.pack_into
50 pack_into = struct.pack_into
51 unpack_from = struct.unpack_from
51 unpack_from = struct.unpack_from
52
52
53
53
54 class BranchMapCache(object):
54 class BranchMapCache(object):
55 """mapping of filtered views of repo with their branchcache"""
55 """mapping of filtered views of repo with their branchcache"""
56
56
57 def __init__(self):
57 def __init__(self):
58 self._per_filter = {}
58 self._per_filter = {}
59
59
60 def __getitem__(self, repo):
60 def __getitem__(self, repo):
61 self.updatecache(repo)
61 self.updatecache(repo)
62 return self._per_filter[repo.filtername]
62 return self._per_filter[repo.filtername]
63
63
64 def updatecache(self, repo):
64 def updatecache(self, repo):
65 """Update the cache for the given filtered view on a repository"""
65 """Update the cache for the given filtered view on a repository"""
66 # This can trigger updates for the caches for subsets of the filtered
66 # This can trigger updates for the caches for subsets of the filtered
67 # view, e.g. when there is no cache for this filtered view or the cache
67 # view, e.g. when there is no cache for this filtered view or the cache
68 # is stale.
68 # is stale.
69
69
70 cl = repo.changelog
70 cl = repo.changelog
71 filtername = repo.filtername
71 filtername = repo.filtername
72 bcache = self._per_filter.get(filtername)
72 bcache = self._per_filter.get(filtername)
73 if bcache is None or not bcache.validfor(repo):
73 if bcache is None or not bcache.validfor(repo):
74 # cache object missing or cache object stale? Read from disk
74 # cache object missing or cache object stale? Read from disk
75 bcache = branchcache.fromfile(repo)
75 bcache = branchcache.fromfile(repo)
76
76
77 revs = []
77 revs = []
78 if bcache is None:
78 if bcache is None:
79 # no (fresh) cache available anymore, perhaps we can re-use
79 # no (fresh) cache available anymore, perhaps we can re-use
80 # the cache for a subset, then extend that to add info on missing
80 # the cache for a subset, then extend that to add info on missing
81 # revisions.
81 # revisions.
82 subsetname = subsettable.get(filtername)
82 subsetname = subsettable.get(filtername)
83 if subsetname is not None:
83 if subsetname is not None:
84 subset = repo.filtered(subsetname)
84 subset = repo.filtered(subsetname)
85 bcache = self[subset].copy()
85 bcache = self[subset].copy()
86 extrarevs = subset.changelog.filteredrevs - cl.filteredrevs
86 extrarevs = subset.changelog.filteredrevs - cl.filteredrevs
87 revs.extend(r for r in extrarevs if r <= bcache.tiprev)
87 revs.extend(r for r in extrarevs if r <= bcache.tiprev)
88 else:
88 else:
89 # nothing to fall back on, start empty.
89 # nothing to fall back on, start empty.
90 bcache = branchcache()
90 bcache = branchcache()
91
91
92 revs.extend(cl.revs(start=bcache.tiprev + 1))
92 revs.extend(cl.revs(start=bcache.tiprev + 1))
93 if revs:
93 if revs:
94 bcache.update(repo, revs)
94 bcache.update(repo, revs)
95
95
96 assert bcache.validfor(repo), filtername
96 assert bcache.validfor(repo), filtername
97 self._per_filter[repo.filtername] = bcache
97 self._per_filter[repo.filtername] = bcache
98
98
99 def replace(self, repo, remotebranchmap):
99 def replace(self, repo, remotebranchmap):
100 """Replace the branchmap cache for a repo with a branch mapping.
100 """Replace the branchmap cache for a repo with a branch mapping.
101
101
102 This is likely only called during clone with a branch map from a
102 This is likely only called during clone with a branch map from a
103 remote.
103 remote.
104
104
105 """
105 """
106 cl = repo.changelog
106 cl = repo.changelog
107 clrev = cl.rev
107 clrev = cl.rev
108 clbranchinfo = cl.branchinfo
108 clbranchinfo = cl.branchinfo
109 rbheads = []
109 rbheads = []
110 closed = set()
110 closed = set()
111 for bheads in pycompat.itervalues(remotebranchmap):
111 for bheads in pycompat.itervalues(remotebranchmap):
112 rbheads += bheads
112 rbheads += bheads
113 for h in bheads:
113 for h in bheads:
114 r = clrev(h)
114 r = clrev(h)
115 b, c = clbranchinfo(r)
115 b, c = clbranchinfo(r)
116 if c:
116 if c:
117 closed.add(h)
117 closed.add(h)
118
118
119 if rbheads:
119 if rbheads:
120 rtiprev = max((int(clrev(node)) for node in rbheads))
120 rtiprev = max((int(clrev(node)) for node in rbheads))
121 cache = branchcache(
121 cache = branchcache(
122 remotebranchmap,
122 remotebranchmap,
123 repo[rtiprev].node(),
123 repo[rtiprev].node(),
124 rtiprev,
124 rtiprev,
125 closednodes=closed,
125 closednodes=closed,
126 )
126 )
127
127
128 # Try to stick it as low as possible
128 # Try to stick it as low as possible
129 # filter above served are unlikely to be fetch from a clone
129 # filter above served are unlikely to be fetch from a clone
130 for candidate in (b'base', b'immutable', b'served'):
130 for candidate in (b'base', b'immutable', b'served'):
131 rview = repo.filtered(candidate)
131 rview = repo.filtered(candidate)
132 if cache.validfor(rview):
132 if cache.validfor(rview):
133 self._per_filter[candidate] = cache
133 self._per_filter[candidate] = cache
134 cache.write(rview)
134 cache.write(rview)
135 return
135 return
136
136
137 def clear(self):
137 def clear(self):
138 self._per_filter.clear()
138 self._per_filter.clear()
139
139
140
140
141 def _unknownnode(node):
141 def _unknownnode(node):
142 """ raises ValueError when branchcache found a node which does not exists
142 """ raises ValueError when branchcache found a node which does not exists
143 """
143 """
144 raise ValueError('node %s does not exist' % pycompat.sysstr(hex(node)))
144 raise ValueError('node %s does not exist' % pycompat.sysstr(hex(node)))
145
145
146
146
147 def _branchcachedesc(repo):
147 def _branchcachedesc(repo):
148 if repo.filtername is not None:
148 if repo.filtername is not None:
149 return b'branch cache (%s)' % repo.filtername
149 return b'branch cache (%s)' % repo.filtername
150 else:
150 else:
151 return b'branch cache'
151 return b'branch cache'
152
152
153
153
154 class branchcache(object):
154 class branchcache(object):
155 """A dict like object that hold branches heads cache.
155 """A dict like object that hold branches heads cache.
156
156
157 This cache is used to avoid costly computations to determine all the
157 This cache is used to avoid costly computations to determine all the
158 branch heads of a repo.
158 branch heads of a repo.
159
159
160 The cache is serialized on disk in the following format:
160 The cache is serialized on disk in the following format:
161
161
162 <tip hex node> <tip rev number> [optional filtered repo hex hash]
162 <tip hex node> <tip rev number> [optional filtered repo hex hash]
163 <branch head hex node> <open/closed state> <branch name>
163 <branch head hex node> <open/closed state> <branch name>
164 <branch head hex node> <open/closed state> <branch name>
164 <branch head hex node> <open/closed state> <branch name>
165 ...
165 ...
166
166
167 The first line is used to check if the cache is still valid. If the
167 The first line is used to check if the cache is still valid. If the
168 branch cache is for a filtered repo view, an optional third hash is
168 branch cache is for a filtered repo view, an optional third hash is
169 included that hashes the hashes of all filtered revisions.
169 included that hashes the hashes of all filtered revisions.
170
170
171 The open/closed state is represented by a single letter 'o' or 'c'.
171 The open/closed state is represented by a single letter 'o' or 'c'.
172 This field can be used to avoid changelog reads when determining if a
172 This field can be used to avoid changelog reads when determining if a
173 branch head closes a branch or not.
173 branch head closes a branch or not.
174 """
174 """
175
175
176 def __init__(
176 def __init__(
177 self,
177 self,
178 entries=(),
178 entries=(),
179 tipnode=nullid,
179 tipnode=nullid,
180 tiprev=nullrev,
180 tiprev=nullrev,
181 filteredhash=None,
181 filteredhash=None,
182 closednodes=None,
182 closednodes=None,
183 hasnode=None,
183 hasnode=None,
184 ):
184 ):
185 # type: (Union[Dict[bytes, List[bytes]], Iterable[Tuple[bytes, List[bytes]]]], bytes, int, Optional[bytes], Optional[Set[bytes]], Optional[Callable[[bytes], bool]]) -> None
185 # type: (Union[Dict[bytes, List[bytes]], Iterable[Tuple[bytes, List[bytes]]]], bytes, int, Optional[bytes], Optional[Set[bytes]], Optional[Callable[[bytes], bool]]) -> None
186 """ hasnode is a function which can be used to verify whether changelog
186 """ hasnode is a function which can be used to verify whether changelog
187 has a given node or not. If it's not provided, we assume that every node
187 has a given node or not. If it's not provided, we assume that every node
188 we have exists in changelog """
188 we have exists in changelog """
189 self.tipnode = tipnode
189 self.tipnode = tipnode
190 self.tiprev = tiprev
190 self.tiprev = tiprev
191 self.filteredhash = filteredhash
191 self.filteredhash = filteredhash
192 # closednodes is a set of nodes that close their branch. If the branch
192 # closednodes is a set of nodes that close their branch. If the branch
193 # cache has been updated, it may contain nodes that are no longer
193 # cache has been updated, it may contain nodes that are no longer
194 # heads.
194 # heads.
195 if closednodes is None:
195 if closednodes is None:
196 self._closednodes = set()
196 self._closednodes = set()
197 else:
197 else:
198 self._closednodes = closednodes
198 self._closednodes = closednodes
199 self._entries = dict(entries)
199 self._entries = dict(entries)
200 # whether closed nodes are verified or not
200 # whether closed nodes are verified or not
201 self._closedverified = False
201 self._closedverified = False
202 # branches for which nodes are verified
202 # branches for which nodes are verified
203 self._verifiedbranches = set()
203 self._verifiedbranches = set()
204 self._hasnode = hasnode
204 self._hasnode = hasnode
205 if self._hasnode is None:
205 if self._hasnode is None:
206 self._hasnode = lambda x: True
206 self._hasnode = lambda x: True
207
207
208 def _verifyclosed(self):
208 def _verifyclosed(self):
209 """ verify the closed nodes we have """
209 """ verify the closed nodes we have """
210 if self._closedverified:
210 if self._closedverified:
211 return
211 return
212 for node in self._closednodes:
212 for node in self._closednodes:
213 if not self._hasnode(node):
213 if not self._hasnode(node):
214 _unknownnode(node)
214 _unknownnode(node)
215
215
216 self._closedverified = True
216 self._closedverified = True
217
217
218 def _verifybranch(self, branch):
218 def _verifybranch(self, branch):
219 """ verify head nodes for the given branch. """
219 """ verify head nodes for the given branch. """
220 if branch not in self._entries or branch in self._verifiedbranches:
220 if branch not in self._entries or branch in self._verifiedbranches:
221 return
221 return
222 for n in self._entries[branch]:
222 for n in self._entries[branch]:
223 if not self._hasnode(n):
223 if not self._hasnode(n):
224 _unknownnode(n)
224 _unknownnode(n)
225
225
226 self._verifiedbranches.add(branch)
226 self._verifiedbranches.add(branch)
227
227
228 def _verifyall(self):
228 def _verifyall(self):
229 """ verifies nodes of all the branches """
229 """ verifies nodes of all the branches """
230 needverification = set(self._entries.keys()) - self._verifiedbranches
230 needverification = set(self._entries.keys()) - self._verifiedbranches
231 for b in needverification:
231 for b in needverification:
232 self._verifybranch(b)
232 self._verifybranch(b)
233
233
234 def __iter__(self):
234 def __iter__(self):
235 return iter(self._entries)
235 return iter(self._entries)
236
236
237 def __setitem__(self, key, value):
237 def __setitem__(self, key, value):
238 self._entries[key] = value
238 self._entries[key] = value
239
239
240 def __getitem__(self, key):
240 def __getitem__(self, key):
241 self._verifybranch(key)
241 self._verifybranch(key)
242 return self._entries[key]
242 return self._entries[key]
243
243
244 def __contains__(self, key):
244 def __contains__(self, key):
245 self._verifybranch(key)
245 self._verifybranch(key)
246 return key in self._entries
246 return key in self._entries
247
247
248 def iteritems(self):
248 def iteritems(self):
249 for k, v in pycompat.iteritems(self._entries):
249 for k, v in pycompat.iteritems(self._entries):
250 self._verifybranch(k)
250 self._verifybranch(k)
251 yield k, v
251 yield k, v
252
252
253 items = iteritems
253 items = iteritems
254
254
255 def hasbranch(self, label):
255 def hasbranch(self, label):
256 """ checks whether a branch of this name exists or not """
256 """ checks whether a branch of this name exists or not """
257 self._verifybranch(label)
257 self._verifybranch(label)
258 return label in self._entries
258 return label in self._entries
259
259
260 @classmethod
260 @classmethod
261 def fromfile(cls, repo):
261 def fromfile(cls, repo):
262 f = None
262 f = None
263 try:
263 try:
264 f = repo.cachevfs(cls._filename(repo))
264 f = repo.cachevfs(cls._filename(repo))
265 lineiter = iter(f)
265 lineiter = iter(f)
266 cachekey = next(lineiter).rstrip(b'\n').split(b" ", 2)
266 cachekey = next(lineiter).rstrip(b'\n').split(b" ", 2)
267 last, lrev = cachekey[:2]
267 last, lrev = cachekey[:2]
268 last, lrev = bin(last), int(lrev)
268 last, lrev = bin(last), int(lrev)
269 filteredhash = None
269 filteredhash = None
270 hasnode = repo.changelog.hasnode
270 hasnode = repo.changelog.hasnode
271 if len(cachekey) > 2:
271 if len(cachekey) > 2:
272 filteredhash = bin(cachekey[2])
272 filteredhash = bin(cachekey[2])
273 bcache = cls(
273 bcache = cls(
274 tipnode=last,
274 tipnode=last,
275 tiprev=lrev,
275 tiprev=lrev,
276 filteredhash=filteredhash,
276 filteredhash=filteredhash,
277 hasnode=hasnode,
277 hasnode=hasnode,
278 )
278 )
279 if not bcache.validfor(repo):
279 if not bcache.validfor(repo):
280 # invalidate the cache
280 # invalidate the cache
281 raise ValueError('tip differs')
281 raise ValueError('tip differs')
282 bcache.load(repo, lineiter)
282 bcache.load(repo, lineiter)
283 except (IOError, OSError):
283 except (IOError, OSError):
284 return None
284 return None
285
285
286 except Exception as inst:
286 except Exception as inst:
287 if repo.ui.debugflag:
287 if repo.ui.debugflag:
288 msg = b'invalid %s: %s\n'
288 msg = b'invalid %s: %s\n'
289 repo.ui.debug(
289 repo.ui.debug(
290 msg
290 msg
291 % (
291 % (
292 _branchcachedesc(repo),
292 _branchcachedesc(repo),
293 pycompat.bytestr(
293 pycompat.bytestr(
294 inst # pytype: disable=wrong-arg-types
294 inst
295 ),
295 ), # pytype: disable=wrong-arg-types
296 )
296 )
297 )
297 )
298 bcache = None
298 bcache = None
299
299
300 finally:
300 finally:
301 if f:
301 if f:
302 f.close()
302 f.close()
303
303
304 return bcache
304 return bcache
305
305
306 def load(self, repo, lineiter):
306 def load(self, repo, lineiter):
307 """ fully loads the branchcache by reading from the file using the line
307 """ fully loads the branchcache by reading from the file using the line
308 iterator passed"""
308 iterator passed"""
309 for line in lineiter:
309 for line in lineiter:
310 line = line.rstrip(b'\n')
310 line = line.rstrip(b'\n')
311 if not line:
311 if not line:
312 continue
312 continue
313 node, state, label = line.split(b" ", 2)
313 node, state, label = line.split(b" ", 2)
314 if state not in b'oc':
314 if state not in b'oc':
315 raise ValueError('invalid branch state')
315 raise ValueError('invalid branch state')
316 label = encoding.tolocal(label.strip())
316 label = encoding.tolocal(label.strip())
317 node = bin(node)
317 node = bin(node)
318 self._entries.setdefault(label, []).append(node)
318 self._entries.setdefault(label, []).append(node)
319 if state == b'c':
319 if state == b'c':
320 self._closednodes.add(node)
320 self._closednodes.add(node)
321
321
322 @staticmethod
322 @staticmethod
323 def _filename(repo):
323 def _filename(repo):
324 """name of a branchcache file for a given repo or repoview"""
324 """name of a branchcache file for a given repo or repoview"""
325 filename = b"branch2"
325 filename = b"branch2"
326 if repo.filtername:
326 if repo.filtername:
327 filename = b'%s-%s' % (filename, repo.filtername)
327 filename = b'%s-%s' % (filename, repo.filtername)
328 return filename
328 return filename
329
329
330 def validfor(self, repo):
330 def validfor(self, repo):
331 """Is the cache content valid regarding a repo
331 """Is the cache content valid regarding a repo
332
332
333 - False when cached tipnode is unknown or if we detect a strip.
333 - False when cached tipnode is unknown or if we detect a strip.
334 - True when cache is up to date or a subset of current repo."""
334 - True when cache is up to date or a subset of current repo."""
335 try:
335 try:
336 return (self.tipnode == repo.changelog.node(self.tiprev)) and (
336 return (self.tipnode == repo.changelog.node(self.tiprev)) and (
337 self.filteredhash == scmutil.filteredhash(repo, self.tiprev)
337 self.filteredhash == scmutil.filteredhash(repo, self.tiprev)
338 )
338 )
339 except IndexError:
339 except IndexError:
340 return False
340 return False
341
341
342 def _branchtip(self, heads):
342 def _branchtip(self, heads):
343 '''Return tuple with last open head in heads and false,
343 '''Return tuple with last open head in heads and false,
344 otherwise return last closed head and true.'''
344 otherwise return last closed head and true.'''
345 tip = heads[-1]
345 tip = heads[-1]
346 closed = True
346 closed = True
347 for h in reversed(heads):
347 for h in reversed(heads):
348 if h not in self._closednodes:
348 if h not in self._closednodes:
349 tip = h
349 tip = h
350 closed = False
350 closed = False
351 break
351 break
352 return tip, closed
352 return tip, closed
353
353
354 def branchtip(self, branch):
354 def branchtip(self, branch):
355 '''Return the tipmost open head on branch head, otherwise return the
355 '''Return the tipmost open head on branch head, otherwise return the
356 tipmost closed head on branch.
356 tipmost closed head on branch.
357 Raise KeyError for unknown branch.'''
357 Raise KeyError for unknown branch.'''
358 return self._branchtip(self[branch])[0]
358 return self._branchtip(self[branch])[0]
359
359
360 def iteropen(self, nodes):
360 def iteropen(self, nodes):
361 return (n for n in nodes if n not in self._closednodes)
361 return (n for n in nodes if n not in self._closednodes)
362
362
363 def branchheads(self, branch, closed=False):
363 def branchheads(self, branch, closed=False):
364 self._verifybranch(branch)
364 self._verifybranch(branch)
365 heads = self._entries[branch]
365 heads = self._entries[branch]
366 if not closed:
366 if not closed:
367 heads = list(self.iteropen(heads))
367 heads = list(self.iteropen(heads))
368 return heads
368 return heads
369
369
370 def iterbranches(self):
370 def iterbranches(self):
371 for bn, heads in pycompat.iteritems(self):
371 for bn, heads in pycompat.iteritems(self):
372 yield (bn, heads) + self._branchtip(heads)
372 yield (bn, heads) + self._branchtip(heads)
373
373
374 def iterheads(self):
374 def iterheads(self):
375 """ returns all the heads """
375 """ returns all the heads """
376 self._verifyall()
376 self._verifyall()
377 return pycompat.itervalues(self._entries)
377 return pycompat.itervalues(self._entries)
378
378
379 def copy(self):
379 def copy(self):
380 """return an deep copy of the branchcache object"""
380 """return an deep copy of the branchcache object"""
381 return type(self)(
381 return type(self)(
382 self._entries,
382 self._entries,
383 self.tipnode,
383 self.tipnode,
384 self.tiprev,
384 self.tiprev,
385 self.filteredhash,
385 self.filteredhash,
386 self._closednodes,
386 self._closednodes,
387 )
387 )
388
388
389 def write(self, repo):
389 def write(self, repo):
390 try:
390 try:
391 f = repo.cachevfs(self._filename(repo), b"w", atomictemp=True)
391 f = repo.cachevfs(self._filename(repo), b"w", atomictemp=True)
392 cachekey = [hex(self.tipnode), b'%d' % self.tiprev]
392 cachekey = [hex(self.tipnode), b'%d' % self.tiprev]
393 if self.filteredhash is not None:
393 if self.filteredhash is not None:
394 cachekey.append(hex(self.filteredhash))
394 cachekey.append(hex(self.filteredhash))
395 f.write(b" ".join(cachekey) + b'\n')
395 f.write(b" ".join(cachekey) + b'\n')
396 nodecount = 0
396 nodecount = 0
397 for label, nodes in sorted(pycompat.iteritems(self._entries)):
397 for label, nodes in sorted(pycompat.iteritems(self._entries)):
398 label = encoding.fromlocal(label)
398 label = encoding.fromlocal(label)
399 for node in nodes:
399 for node in nodes:
400 nodecount += 1
400 nodecount += 1
401 if node in self._closednodes:
401 if node in self._closednodes:
402 state = b'c'
402 state = b'c'
403 else:
403 else:
404 state = b'o'
404 state = b'o'
405 f.write(b"%s %s %s\n" % (hex(node), state, label))
405 f.write(b"%s %s %s\n" % (hex(node), state, label))
406 f.close()
406 f.close()
407 repo.ui.log(
407 repo.ui.log(
408 b'branchcache',
408 b'branchcache',
409 b'wrote %s with %d labels and %d nodes\n',
409 b'wrote %s with %d labels and %d nodes\n',
410 _branchcachedesc(repo),
410 _branchcachedesc(repo),
411 len(self._entries),
411 len(self._entries),
412 nodecount,
412 nodecount,
413 )
413 )
414 except (IOError, OSError, error.Abort) as inst:
414 except (IOError, OSError, error.Abort) as inst:
415 # Abort may be raised by read only opener, so log and continue
415 # Abort may be raised by read only opener, so log and continue
416 repo.ui.debug(
416 repo.ui.debug(
417 b"couldn't write branch cache: %s\n"
417 b"couldn't write branch cache: %s\n"
418 % stringutil.forcebytestr(inst)
418 % stringutil.forcebytestr(inst)
419 )
419 )
420
420
421 def update(self, repo, revgen):
421 def update(self, repo, revgen):
422 """Given a branchhead cache, self, that may have extra nodes or be
422 """Given a branchhead cache, self, that may have extra nodes or be
423 missing heads, and a generator of nodes that are strictly a superset of
423 missing heads, and a generator of nodes that are strictly a superset of
424 heads missing, this function updates self to be correct.
424 heads missing, this function updates self to be correct.
425 """
425 """
426 starttime = util.timer()
426 starttime = util.timer()
427 cl = repo.changelog
427 cl = repo.changelog
428 # collect new branch entries
428 # collect new branch entries
429 newbranches = {}
429 newbranches = {}
430 getbranchinfo = repo.revbranchcache().branchinfo
430 getbranchinfo = repo.revbranchcache().branchinfo
431 for r in revgen:
431 for r in revgen:
432 branch, closesbranch = getbranchinfo(r)
432 branch, closesbranch = getbranchinfo(r)
433 newbranches.setdefault(branch, []).append(r)
433 newbranches.setdefault(branch, []).append(r)
434 if closesbranch:
434 if closesbranch:
435 self._closednodes.add(cl.node(r))
435 self._closednodes.add(cl.node(r))
436
436
437 # fetch current topological heads to speed up filtering
437 # fetch current topological heads to speed up filtering
438 topoheads = set(cl.headrevs())
438 topoheads = set(cl.headrevs())
439
439
440 # new tip revision which we found after iterating items from new
440 # new tip revision which we found after iterating items from new
441 # branches
441 # branches
442 ntiprev = self.tiprev
442 ntiprev = self.tiprev
443
443
444 # if older branchheads are reachable from new ones, they aren't
444 # if older branchheads are reachable from new ones, they aren't
445 # really branchheads. Note checking parents is insufficient:
445 # really branchheads. Note checking parents is insufficient:
446 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
446 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
447 for branch, newheadrevs in pycompat.iteritems(newbranches):
447 for branch, newheadrevs in pycompat.iteritems(newbranches):
448 bheads = self._entries.setdefault(branch, [])
448 bheads = self._entries.setdefault(branch, [])
449 bheadset = set(cl.rev(node) for node in bheads)
449 bheadset = set(cl.rev(node) for node in bheads)
450
450
451 # This have been tested True on all internal usage of this function.
451 # This have been tested True on all internal usage of this function.
452 # run it again in case of doubt
452 # run it again in case of doubt
453 # assert not (set(bheadrevs) & set(newheadrevs))
453 # assert not (set(bheadrevs) & set(newheadrevs))
454 bheadset.update(newheadrevs)
454 bheadset.update(newheadrevs)
455
455
456 # This prunes out two kinds of heads - heads that are superseded by
456 # This prunes out two kinds of heads - heads that are superseded by
457 # a head in newheadrevs, and newheadrevs that are not heads because
457 # a head in newheadrevs, and newheadrevs that are not heads because
458 # an existing head is their descendant.
458 # an existing head is their descendant.
459 uncertain = bheadset - topoheads
459 uncertain = bheadset - topoheads
460 if uncertain:
460 if uncertain:
461 floorrev = min(uncertain)
461 floorrev = min(uncertain)
462 ancestors = set(cl.ancestors(newheadrevs, floorrev))
462 ancestors = set(cl.ancestors(newheadrevs, floorrev))
463 bheadset -= ancestors
463 bheadset -= ancestors
464 bheadrevs = sorted(bheadset)
464 bheadrevs = sorted(bheadset)
465 self[branch] = [cl.node(rev) for rev in bheadrevs]
465 self[branch] = [cl.node(rev) for rev in bheadrevs]
466 tiprev = bheadrevs[-1]
466 tiprev = bheadrevs[-1]
467 if tiprev > ntiprev:
467 if tiprev > ntiprev:
468 ntiprev = tiprev
468 ntiprev = tiprev
469
469
470 if ntiprev > self.tiprev:
470 if ntiprev > self.tiprev:
471 self.tiprev = ntiprev
471 self.tiprev = ntiprev
472 self.tipnode = cl.node(ntiprev)
472 self.tipnode = cl.node(ntiprev)
473
473
474 if not self.validfor(repo):
474 if not self.validfor(repo):
475 # cache key are not valid anymore
475 # cache key are not valid anymore
476 self.tipnode = nullid
476 self.tipnode = nullid
477 self.tiprev = nullrev
477 self.tiprev = nullrev
478 for heads in self.iterheads():
478 for heads in self.iterheads():
479 tiprev = max(cl.rev(node) for node in heads)
479 tiprev = max(cl.rev(node) for node in heads)
480 if tiprev > self.tiprev:
480 if tiprev > self.tiprev:
481 self.tipnode = cl.node(tiprev)
481 self.tipnode = cl.node(tiprev)
482 self.tiprev = tiprev
482 self.tiprev = tiprev
483 self.filteredhash = scmutil.filteredhash(repo, self.tiprev)
483 self.filteredhash = scmutil.filteredhash(repo, self.tiprev)
484
484
485 duration = util.timer() - starttime
485 duration = util.timer() - starttime
486 repo.ui.log(
486 repo.ui.log(
487 b'branchcache',
487 b'branchcache',
488 b'updated %s in %.4f seconds\n',
488 b'updated %s in %.4f seconds\n',
489 _branchcachedesc(repo),
489 _branchcachedesc(repo),
490 duration,
490 duration,
491 )
491 )
492
492
493 self.write(repo)
493 self.write(repo)
494
494
495
495
496 class remotebranchcache(branchcache):
496 class remotebranchcache(branchcache):
497 """Branchmap info for a remote connection, should not write locally"""
497 """Branchmap info for a remote connection, should not write locally"""
498
498
499 def write(self, repo):
499 def write(self, repo):
500 pass
500 pass
501
501
502
502
503 # Revision branch info cache
503 # Revision branch info cache
504
504
505 _rbcversion = b'-v1'
505 _rbcversion = b'-v1'
506 _rbcnames = b'rbc-names' + _rbcversion
506 _rbcnames = b'rbc-names' + _rbcversion
507 _rbcrevs = b'rbc-revs' + _rbcversion
507 _rbcrevs = b'rbc-revs' + _rbcversion
508 # [4 byte hash prefix][4 byte branch name number with sign bit indicating open]
508 # [4 byte hash prefix][4 byte branch name number with sign bit indicating open]
509 _rbcrecfmt = b'>4sI'
509 _rbcrecfmt = b'>4sI'
510 _rbcrecsize = calcsize(_rbcrecfmt)
510 _rbcrecsize = calcsize(_rbcrecfmt)
511 _rbcnodelen = 4
511 _rbcnodelen = 4
512 _rbcbranchidxmask = 0x7FFFFFFF
512 _rbcbranchidxmask = 0x7FFFFFFF
513 _rbccloseflag = 0x80000000
513 _rbccloseflag = 0x80000000
514
514
515
515
516 class revbranchcache(object):
516 class revbranchcache(object):
517 """Persistent cache, mapping from revision number to branch name and close.
517 """Persistent cache, mapping from revision number to branch name and close.
518 This is a low level cache, independent of filtering.
518 This is a low level cache, independent of filtering.
519
519
520 Branch names are stored in rbc-names in internal encoding separated by 0.
520 Branch names are stored in rbc-names in internal encoding separated by 0.
521 rbc-names is append-only, and each branch name is only stored once and will
521 rbc-names is append-only, and each branch name is only stored once and will
522 thus have a unique index.
522 thus have a unique index.
523
523
524 The branch info for each revision is stored in rbc-revs as constant size
524 The branch info for each revision is stored in rbc-revs as constant size
525 records. The whole file is read into memory, but it is only 'parsed' on
525 records. The whole file is read into memory, but it is only 'parsed' on
526 demand. The file is usually append-only but will be truncated if repo
526 demand. The file is usually append-only but will be truncated if repo
527 modification is detected.
527 modification is detected.
528 The record for each revision contains the first 4 bytes of the
528 The record for each revision contains the first 4 bytes of the
529 corresponding node hash, and the record is only used if it still matches.
529 corresponding node hash, and the record is only used if it still matches.
530 Even a completely trashed rbc-revs fill thus still give the right result
530 Even a completely trashed rbc-revs fill thus still give the right result
531 while converging towards full recovery ... assuming no incorrectly matching
531 while converging towards full recovery ... assuming no incorrectly matching
532 node hashes.
532 node hashes.
533 The record also contains 4 bytes where 31 bits contains the index of the
533 The record also contains 4 bytes where 31 bits contains the index of the
534 branch and the last bit indicate that it is a branch close commit.
534 branch and the last bit indicate that it is a branch close commit.
535 The usage pattern for rbc-revs is thus somewhat similar to 00changelog.i
535 The usage pattern for rbc-revs is thus somewhat similar to 00changelog.i
536 and will grow with it but be 1/8th of its size.
536 and will grow with it but be 1/8th of its size.
537 """
537 """
538
538
539 def __init__(self, repo, readonly=True):
539 def __init__(self, repo, readonly=True):
540 assert repo.filtername is None
540 assert repo.filtername is None
541 self._repo = repo
541 self._repo = repo
542 self._names = [] # branch names in local encoding with static index
542 self._names = [] # branch names in local encoding with static index
543 self._rbcrevs = bytearray()
543 self._rbcrevs = bytearray()
544 self._rbcsnameslen = 0 # length of names read at _rbcsnameslen
544 self._rbcsnameslen = 0 # length of names read at _rbcsnameslen
545 try:
545 try:
546 bndata = repo.cachevfs.read(_rbcnames)
546 bndata = repo.cachevfs.read(_rbcnames)
547 self._rbcsnameslen = len(bndata) # for verification before writing
547 self._rbcsnameslen = len(bndata) # for verification before writing
548 if bndata:
548 if bndata:
549 self._names = [
549 self._names = [
550 encoding.tolocal(bn) for bn in bndata.split(b'\0')
550 encoding.tolocal(bn) for bn in bndata.split(b'\0')
551 ]
551 ]
552 except (IOError, OSError):
552 except (IOError, OSError):
553 if readonly:
553 if readonly:
554 # don't try to use cache - fall back to the slow path
554 # don't try to use cache - fall back to the slow path
555 self.branchinfo = self._branchinfo
555 self.branchinfo = self._branchinfo
556
556
557 if self._names:
557 if self._names:
558 try:
558 try:
559 data = repo.cachevfs.read(_rbcrevs)
559 data = repo.cachevfs.read(_rbcrevs)
560 self._rbcrevs[:] = data
560 self._rbcrevs[:] = data
561 except (IOError, OSError) as inst:
561 except (IOError, OSError) as inst:
562 repo.ui.debug(
562 repo.ui.debug(
563 b"couldn't read revision branch cache: %s\n"
563 b"couldn't read revision branch cache: %s\n"
564 % stringutil.forcebytestr(inst)
564 % stringutil.forcebytestr(inst)
565 )
565 )
566 # remember number of good records on disk
566 # remember number of good records on disk
567 self._rbcrevslen = min(
567 self._rbcrevslen = min(
568 len(self._rbcrevs) // _rbcrecsize, len(repo.changelog)
568 len(self._rbcrevs) // _rbcrecsize, len(repo.changelog)
569 )
569 )
570 if self._rbcrevslen == 0:
570 if self._rbcrevslen == 0:
571 self._names = []
571 self._names = []
572 self._rbcnamescount = len(self._names) # number of names read at
572 self._rbcnamescount = len(self._names) # number of names read at
573 # _rbcsnameslen
573 # _rbcsnameslen
574
574
575 def _clear(self):
575 def _clear(self):
576 self._rbcsnameslen = 0
576 self._rbcsnameslen = 0
577 del self._names[:]
577 del self._names[:]
578 self._rbcnamescount = 0
578 self._rbcnamescount = 0
579 self._rbcrevslen = len(self._repo.changelog)
579 self._rbcrevslen = len(self._repo.changelog)
580 self._rbcrevs = bytearray(self._rbcrevslen * _rbcrecsize)
580 self._rbcrevs = bytearray(self._rbcrevslen * _rbcrecsize)
581 util.clearcachedproperty(self, b'_namesreverse')
581 util.clearcachedproperty(self, b'_namesreverse')
582
582
583 @util.propertycache
583 @util.propertycache
584 def _namesreverse(self):
584 def _namesreverse(self):
585 return dict((b, r) for r, b in enumerate(self._names))
585 return dict((b, r) for r, b in enumerate(self._names))
586
586
587 def branchinfo(self, rev):
587 def branchinfo(self, rev):
588 """Return branch name and close flag for rev, using and updating
588 """Return branch name and close flag for rev, using and updating
589 persistent cache."""
589 persistent cache."""
590 changelog = self._repo.changelog
590 changelog = self._repo.changelog
591 rbcrevidx = rev * _rbcrecsize
591 rbcrevidx = rev * _rbcrecsize
592
592
593 # avoid negative index, changelog.read(nullrev) is fast without cache
593 # avoid negative index, changelog.read(nullrev) is fast without cache
594 if rev == nullrev:
594 if rev == nullrev:
595 return changelog.branchinfo(rev)
595 return changelog.branchinfo(rev)
596
596
597 # if requested rev isn't allocated, grow and cache the rev info
597 # if requested rev isn't allocated, grow and cache the rev info
598 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize:
598 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize:
599 return self._branchinfo(rev)
599 return self._branchinfo(rev)
600
600
601 # fast path: extract data from cache, use it if node is matching
601 # fast path: extract data from cache, use it if node is matching
602 reponode = changelog.node(rev)[:_rbcnodelen]
602 reponode = changelog.node(rev)[:_rbcnodelen]
603 cachenode, branchidx = unpack_from(
603 cachenode, branchidx = unpack_from(
604 _rbcrecfmt, util.buffer(self._rbcrevs), rbcrevidx
604 _rbcrecfmt, util.buffer(self._rbcrevs), rbcrevidx
605 )
605 )
606 close = bool(branchidx & _rbccloseflag)
606 close = bool(branchidx & _rbccloseflag)
607 if close:
607 if close:
608 branchidx &= _rbcbranchidxmask
608 branchidx &= _rbcbranchidxmask
609 if cachenode == b'\0\0\0\0':
609 if cachenode == b'\0\0\0\0':
610 pass
610 pass
611 elif cachenode == reponode:
611 elif cachenode == reponode:
612 try:
612 try:
613 return self._names[branchidx], close
613 return self._names[branchidx], close
614 except IndexError:
614 except IndexError:
615 # recover from invalid reference to unknown branch
615 # recover from invalid reference to unknown branch
616 self._repo.ui.debug(
616 self._repo.ui.debug(
617 b"referenced branch names not found"
617 b"referenced branch names not found"
618 b" - rebuilding revision branch cache from scratch\n"
618 b" - rebuilding revision branch cache from scratch\n"
619 )
619 )
620 self._clear()
620 self._clear()
621 else:
621 else:
622 # rev/node map has changed, invalidate the cache from here up
622 # rev/node map has changed, invalidate the cache from here up
623 self._repo.ui.debug(
623 self._repo.ui.debug(
624 b"history modification detected - truncating "
624 b"history modification detected - truncating "
625 b"revision branch cache to revision %d\n" % rev
625 b"revision branch cache to revision %d\n" % rev
626 )
626 )
627 truncate = rbcrevidx + _rbcrecsize
627 truncate = rbcrevidx + _rbcrecsize
628 del self._rbcrevs[truncate:]
628 del self._rbcrevs[truncate:]
629 self._rbcrevslen = min(self._rbcrevslen, truncate)
629 self._rbcrevslen = min(self._rbcrevslen, truncate)
630
630
631 # fall back to slow path and make sure it will be written to disk
631 # fall back to slow path and make sure it will be written to disk
632 return self._branchinfo(rev)
632 return self._branchinfo(rev)
633
633
634 def _branchinfo(self, rev):
634 def _branchinfo(self, rev):
635 """Retrieve branch info from changelog and update _rbcrevs"""
635 """Retrieve branch info from changelog and update _rbcrevs"""
636 changelog = self._repo.changelog
636 changelog = self._repo.changelog
637 b, close = changelog.branchinfo(rev)
637 b, close = changelog.branchinfo(rev)
638 if b in self._namesreverse:
638 if b in self._namesreverse:
639 branchidx = self._namesreverse[b]
639 branchidx = self._namesreverse[b]
640 else:
640 else:
641 branchidx = len(self._names)
641 branchidx = len(self._names)
642 self._names.append(b)
642 self._names.append(b)
643 self._namesreverse[b] = branchidx
643 self._namesreverse[b] = branchidx
644 reponode = changelog.node(rev)
644 reponode = changelog.node(rev)
645 if close:
645 if close:
646 branchidx |= _rbccloseflag
646 branchidx |= _rbccloseflag
647 self._setcachedata(rev, reponode, branchidx)
647 self._setcachedata(rev, reponode, branchidx)
648 return b, close
648 return b, close
649
649
650 def setdata(self, branch, rev, node, close):
650 def setdata(self, branch, rev, node, close):
651 """add new data information to the cache"""
651 """add new data information to the cache"""
652 if branch in self._namesreverse:
652 if branch in self._namesreverse:
653 branchidx = self._namesreverse[branch]
653 branchidx = self._namesreverse[branch]
654 else:
654 else:
655 branchidx = len(self._names)
655 branchidx = len(self._names)
656 self._names.append(branch)
656 self._names.append(branch)
657 self._namesreverse[branch] = branchidx
657 self._namesreverse[branch] = branchidx
658 if close:
658 if close:
659 branchidx |= _rbccloseflag
659 branchidx |= _rbccloseflag
660 self._setcachedata(rev, node, branchidx)
660 self._setcachedata(rev, node, branchidx)
661 # If no cache data were readable (non exists, bad permission, etc)
661 # If no cache data were readable (non exists, bad permission, etc)
662 # the cache was bypassing itself by setting:
662 # the cache was bypassing itself by setting:
663 #
663 #
664 # self.branchinfo = self._branchinfo
664 # self.branchinfo = self._branchinfo
665 #
665 #
666 # Since we now have data in the cache, we need to drop this bypassing.
666 # Since we now have data in the cache, we need to drop this bypassing.
667 if 'branchinfo' in vars(self):
667 if 'branchinfo' in vars(self):
668 del self.branchinfo
668 del self.branchinfo
669
669
670 def _setcachedata(self, rev, node, branchidx):
670 def _setcachedata(self, rev, node, branchidx):
671 """Writes the node's branch data to the in-memory cache data."""
671 """Writes the node's branch data to the in-memory cache data."""
672 if rev == nullrev:
672 if rev == nullrev:
673 return
673 return
674 rbcrevidx = rev * _rbcrecsize
674 rbcrevidx = rev * _rbcrecsize
675 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize:
675 if len(self._rbcrevs) < rbcrevidx + _rbcrecsize:
676 self._rbcrevs.extend(
676 self._rbcrevs.extend(
677 b'\0'
677 b'\0'
678 * (len(self._repo.changelog) * _rbcrecsize - len(self._rbcrevs))
678 * (len(self._repo.changelog) * _rbcrecsize - len(self._rbcrevs))
679 )
679 )
680 pack_into(_rbcrecfmt, self._rbcrevs, rbcrevidx, node, branchidx)
680 pack_into(_rbcrecfmt, self._rbcrevs, rbcrevidx, node, branchidx)
681 self._rbcrevslen = min(self._rbcrevslen, rev)
681 self._rbcrevslen = min(self._rbcrevslen, rev)
682
682
683 tr = self._repo.currenttransaction()
683 tr = self._repo.currenttransaction()
684 if tr:
684 if tr:
685 tr.addfinalize(b'write-revbranchcache', self.write)
685 tr.addfinalize(b'write-revbranchcache', self.write)
686
686
687 def write(self, tr=None):
687 def write(self, tr=None):
688 """Save branch cache if it is dirty."""
688 """Save branch cache if it is dirty."""
689 repo = self._repo
689 repo = self._repo
690 wlock = None
690 wlock = None
691 step = b''
691 step = b''
692 try:
692 try:
693 # write the new names
693 # write the new names
694 if self._rbcnamescount < len(self._names):
694 if self._rbcnamescount < len(self._names):
695 wlock = repo.wlock(wait=False)
695 wlock = repo.wlock(wait=False)
696 step = b' names'
696 step = b' names'
697 self._writenames(repo)
697 self._writenames(repo)
698
698
699 # write the new revs
699 # write the new revs
700 start = self._rbcrevslen * _rbcrecsize
700 start = self._rbcrevslen * _rbcrecsize
701 if start != len(self._rbcrevs):
701 if start != len(self._rbcrevs):
702 step = b''
702 step = b''
703 if wlock is None:
703 if wlock is None:
704 wlock = repo.wlock(wait=False)
704 wlock = repo.wlock(wait=False)
705 self._writerevs(repo, start)
705 self._writerevs(repo, start)
706
706
707 except (IOError, OSError, error.Abort, error.LockError) as inst:
707 except (IOError, OSError, error.Abort, error.LockError) as inst:
708 repo.ui.debug(
708 repo.ui.debug(
709 b"couldn't write revision branch cache%s: %s\n"
709 b"couldn't write revision branch cache%s: %s\n"
710 % (step, stringutil.forcebytestr(inst))
710 % (step, stringutil.forcebytestr(inst))
711 )
711 )
712 finally:
712 finally:
713 if wlock is not None:
713 if wlock is not None:
714 wlock.release()
714 wlock.release()
715
715
716 def _writenames(self, repo):
716 def _writenames(self, repo):
717 """ write the new branch names to revbranchcache """
717 """ write the new branch names to revbranchcache """
718 if self._rbcnamescount != 0:
718 if self._rbcnamescount != 0:
719 f = repo.cachevfs.open(_rbcnames, b'ab')
719 f = repo.cachevfs.open(_rbcnames, b'ab')
720 if f.tell() == self._rbcsnameslen:
720 if f.tell() == self._rbcsnameslen:
721 f.write(b'\0')
721 f.write(b'\0')
722 else:
722 else:
723 f.close()
723 f.close()
724 repo.ui.debug(b"%s changed - rewriting it\n" % _rbcnames)
724 repo.ui.debug(b"%s changed - rewriting it\n" % _rbcnames)
725 self._rbcnamescount = 0
725 self._rbcnamescount = 0
726 self._rbcrevslen = 0
726 self._rbcrevslen = 0
727 if self._rbcnamescount == 0:
727 if self._rbcnamescount == 0:
728 # before rewriting names, make sure references are removed
728 # before rewriting names, make sure references are removed
729 repo.cachevfs.unlinkpath(_rbcrevs, ignoremissing=True)
729 repo.cachevfs.unlinkpath(_rbcrevs, ignoremissing=True)
730 f = repo.cachevfs.open(_rbcnames, b'wb')
730 f = repo.cachevfs.open(_rbcnames, b'wb')
731 f.write(
731 f.write(
732 b'\0'.join(
732 b'\0'.join(
733 encoding.fromlocal(b)
733 encoding.fromlocal(b)
734 for b in self._names[self._rbcnamescount :]
734 for b in self._names[self._rbcnamescount :]
735 )
735 )
736 )
736 )
737 self._rbcsnameslen = f.tell()
737 self._rbcsnameslen = f.tell()
738 f.close()
738 f.close()
739 self._rbcnamescount = len(self._names)
739 self._rbcnamescount = len(self._names)
740
740
741 def _writerevs(self, repo, start):
741 def _writerevs(self, repo, start):
742 """ write the new revs to revbranchcache """
742 """ write the new revs to revbranchcache """
743 revs = min(len(repo.changelog), len(self._rbcrevs) // _rbcrecsize)
743 revs = min(len(repo.changelog), len(self._rbcrevs) // _rbcrecsize)
744 with repo.cachevfs.open(_rbcrevs, b'ab') as f:
744 with repo.cachevfs.open(_rbcrevs, b'ab') as f:
745 if f.tell() != start:
745 if f.tell() != start:
746 repo.ui.debug(
746 repo.ui.debug(
747 b"truncating cache/%s to %d\n" % (_rbcrevs, start)
747 b"truncating cache/%s to %d\n" % (_rbcrevs, start)
748 )
748 )
749 f.seek(start)
749 f.seek(start)
750 if f.tell() != start:
750 if f.tell() != start:
751 start = 0
751 start = 0
752 f.seek(start)
752 f.seek(start)
753 f.truncate()
753 f.truncate()
754 end = revs * _rbcrecsize
754 end = revs * _rbcrecsize
755 f.write(self._rbcrevs[start:end])
755 f.write(self._rbcrevs[start:end])
756 self._rbcrevslen = revs
756 self._rbcrevslen = revs
@@ -1,1691 +1,1691 b''
1 # changegroup.py - Mercurial changegroup manipulation functions
1 # changegroup.py - Mercurial changegroup manipulation functions
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 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 os
10 import os
11 import struct
11 import struct
12 import weakref
12 import weakref
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 hex,
16 hex,
17 nullid,
17 nullid,
18 nullrev,
18 nullrev,
19 short,
19 short,
20 )
20 )
21 from .pycompat import open
21 from .pycompat import open
22
22
23 from . import (
23 from . import (
24 error,
24 error,
25 match as matchmod,
25 match as matchmod,
26 mdiff,
26 mdiff,
27 phases,
27 phases,
28 pycompat,
28 pycompat,
29 util,
29 util,
30 )
30 )
31
31
32 from .interfaces import repository
32 from .interfaces import repository
33
33
34 _CHANGEGROUPV1_DELTA_HEADER = struct.Struct(b"20s20s20s20s")
34 _CHANGEGROUPV1_DELTA_HEADER = struct.Struct(b"20s20s20s20s")
35 _CHANGEGROUPV2_DELTA_HEADER = struct.Struct(b"20s20s20s20s20s")
35 _CHANGEGROUPV2_DELTA_HEADER = struct.Struct(b"20s20s20s20s20s")
36 _CHANGEGROUPV3_DELTA_HEADER = struct.Struct(b">20s20s20s20s20sH")
36 _CHANGEGROUPV3_DELTA_HEADER = struct.Struct(b">20s20s20s20s20sH")
37
37
38 LFS_REQUIREMENT = b'lfs'
38 LFS_REQUIREMENT = b'lfs'
39
39
40 readexactly = util.readexactly
40 readexactly = util.readexactly
41
41
42
42
43 def getchunk(stream):
43 def getchunk(stream):
44 """return the next chunk from stream as a string"""
44 """return the next chunk from stream as a string"""
45 d = readexactly(stream, 4)
45 d = readexactly(stream, 4)
46 l = struct.unpack(b">l", d)[0]
46 l = struct.unpack(b">l", d)[0]
47 if l <= 4:
47 if l <= 4:
48 if l:
48 if l:
49 raise error.Abort(_(b"invalid chunk length %d") % l)
49 raise error.Abort(_(b"invalid chunk length %d") % l)
50 return b""
50 return b""
51 return readexactly(stream, l - 4)
51 return readexactly(stream, l - 4)
52
52
53
53
54 def chunkheader(length):
54 def chunkheader(length):
55 """return a changegroup chunk header (string)"""
55 """return a changegroup chunk header (string)"""
56 return struct.pack(b">l", length + 4)
56 return struct.pack(b">l", length + 4)
57
57
58
58
59 def closechunk():
59 def closechunk():
60 """return a changegroup chunk header (string) for a zero-length chunk"""
60 """return a changegroup chunk header (string) for a zero-length chunk"""
61 return struct.pack(b">l", 0)
61 return struct.pack(b">l", 0)
62
62
63
63
64 def _fileheader(path):
64 def _fileheader(path):
65 """Obtain a changegroup chunk header for a named path."""
65 """Obtain a changegroup chunk header for a named path."""
66 return chunkheader(len(path)) + path
66 return chunkheader(len(path)) + path
67
67
68
68
69 def writechunks(ui, chunks, filename, vfs=None):
69 def writechunks(ui, chunks, filename, vfs=None):
70 """Write chunks to a file and return its filename.
70 """Write chunks to a file and return its filename.
71
71
72 The stream is assumed to be a bundle file.
72 The stream is assumed to be a bundle file.
73 Existing files will not be overwritten.
73 Existing files will not be overwritten.
74 If no filename is specified, a temporary file is created.
74 If no filename is specified, a temporary file is created.
75 """
75 """
76 fh = None
76 fh = None
77 cleanup = None
77 cleanup = None
78 try:
78 try:
79 if filename:
79 if filename:
80 if vfs:
80 if vfs:
81 fh = vfs.open(filename, b"wb")
81 fh = vfs.open(filename, b"wb")
82 else:
82 else:
83 # Increase default buffer size because default is usually
83 # Increase default buffer size because default is usually
84 # small (4k is common on Linux).
84 # small (4k is common on Linux).
85 fh = open(filename, b"wb", 131072)
85 fh = open(filename, b"wb", 131072)
86 else:
86 else:
87 fd, filename = pycompat.mkstemp(prefix=b"hg-bundle-", suffix=b".hg")
87 fd, filename = pycompat.mkstemp(prefix=b"hg-bundle-", suffix=b".hg")
88 fh = os.fdopen(fd, "wb")
88 fh = os.fdopen(fd, "wb")
89 cleanup = filename
89 cleanup = filename
90 for c in chunks:
90 for c in chunks:
91 fh.write(c)
91 fh.write(c)
92 cleanup = None
92 cleanup = None
93 return filename
93 return filename
94 finally:
94 finally:
95 if fh is not None:
95 if fh is not None:
96 fh.close()
96 fh.close()
97 if cleanup is not None:
97 if cleanup is not None:
98 if filename and vfs:
98 if filename and vfs:
99 vfs.unlink(cleanup)
99 vfs.unlink(cleanup)
100 else:
100 else:
101 os.unlink(cleanup)
101 os.unlink(cleanup)
102
102
103
103
104 class cg1unpacker(object):
104 class cg1unpacker(object):
105 """Unpacker for cg1 changegroup streams.
105 """Unpacker for cg1 changegroup streams.
106
106
107 A changegroup unpacker handles the framing of the revision data in
107 A changegroup unpacker handles the framing of the revision data in
108 the wire format. Most consumers will want to use the apply()
108 the wire format. Most consumers will want to use the apply()
109 method to add the changes from the changegroup to a repository.
109 method to add the changes from the changegroup to a repository.
110
110
111 If you're forwarding a changegroup unmodified to another consumer,
111 If you're forwarding a changegroup unmodified to another consumer,
112 use getchunks(), which returns an iterator of changegroup
112 use getchunks(), which returns an iterator of changegroup
113 chunks. This is mostly useful for cases where you need to know the
113 chunks. This is mostly useful for cases where you need to know the
114 data stream has ended by observing the end of the changegroup.
114 data stream has ended by observing the end of the changegroup.
115
115
116 deltachunk() is useful only if you're applying delta data. Most
116 deltachunk() is useful only if you're applying delta data. Most
117 consumers should prefer apply() instead.
117 consumers should prefer apply() instead.
118
118
119 A few other public methods exist. Those are used only for
119 A few other public methods exist. Those are used only for
120 bundlerepo and some debug commands - their use is discouraged.
120 bundlerepo and some debug commands - their use is discouraged.
121 """
121 """
122
122
123 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
123 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
124 deltaheadersize = deltaheader.size
124 deltaheadersize = deltaheader.size
125 version = b'01'
125 version = b'01'
126 _grouplistcount = 1 # One list of files after the manifests
126 _grouplistcount = 1 # One list of files after the manifests
127
127
128 def __init__(self, fh, alg, extras=None):
128 def __init__(self, fh, alg, extras=None):
129 if alg is None:
129 if alg is None:
130 alg = b'UN'
130 alg = b'UN'
131 if alg not in util.compengines.supportedbundletypes:
131 if alg not in util.compengines.supportedbundletypes:
132 raise error.Abort(_(b'unknown stream compression type: %s') % alg)
132 raise error.Abort(_(b'unknown stream compression type: %s') % alg)
133 if alg == b'BZ':
133 if alg == b'BZ':
134 alg = b'_truncatedBZ'
134 alg = b'_truncatedBZ'
135
135
136 compengine = util.compengines.forbundletype(alg)
136 compengine = util.compengines.forbundletype(alg)
137 self._stream = compengine.decompressorreader(fh)
137 self._stream = compengine.decompressorreader(fh)
138 self._type = alg
138 self._type = alg
139 self.extras = extras or {}
139 self.extras = extras or {}
140 self.callback = None
140 self.callback = None
141
141
142 # These methods (compressed, read, seek, tell) all appear to only
142 # These methods (compressed, read, seek, tell) all appear to only
143 # be used by bundlerepo, but it's a little hard to tell.
143 # be used by bundlerepo, but it's a little hard to tell.
144 def compressed(self):
144 def compressed(self):
145 return self._type is not None and self._type != b'UN'
145 return self._type is not None and self._type != b'UN'
146
146
147 def read(self, l):
147 def read(self, l):
148 return self._stream.read(l)
148 return self._stream.read(l)
149
149
150 def seek(self, pos):
150 def seek(self, pos):
151 return self._stream.seek(pos)
151 return self._stream.seek(pos)
152
152
153 def tell(self):
153 def tell(self):
154 return self._stream.tell()
154 return self._stream.tell()
155
155
156 def close(self):
156 def close(self):
157 return self._stream.close()
157 return self._stream.close()
158
158
159 def _chunklength(self):
159 def _chunklength(self):
160 d = readexactly(self._stream, 4)
160 d = readexactly(self._stream, 4)
161 l = struct.unpack(b">l", d)[0]
161 l = struct.unpack(b">l", d)[0]
162 if l <= 4:
162 if l <= 4:
163 if l:
163 if l:
164 raise error.Abort(_(b"invalid chunk length %d") % l)
164 raise error.Abort(_(b"invalid chunk length %d") % l)
165 return 0
165 return 0
166 if self.callback:
166 if self.callback:
167 self.callback()
167 self.callback()
168 return l - 4
168 return l - 4
169
169
170 def changelogheader(self):
170 def changelogheader(self):
171 """v10 does not have a changelog header chunk"""
171 """v10 does not have a changelog header chunk"""
172 return {}
172 return {}
173
173
174 def manifestheader(self):
174 def manifestheader(self):
175 """v10 does not have a manifest header chunk"""
175 """v10 does not have a manifest header chunk"""
176 return {}
176 return {}
177
177
178 def filelogheader(self):
178 def filelogheader(self):
179 """return the header of the filelogs chunk, v10 only has the filename"""
179 """return the header of the filelogs chunk, v10 only has the filename"""
180 l = self._chunklength()
180 l = self._chunklength()
181 if not l:
181 if not l:
182 return {}
182 return {}
183 fname = readexactly(self._stream, l)
183 fname = readexactly(self._stream, l)
184 return {b'filename': fname}
184 return {b'filename': fname}
185
185
186 def _deltaheader(self, headertuple, prevnode):
186 def _deltaheader(self, headertuple, prevnode):
187 node, p1, p2, cs = headertuple
187 node, p1, p2, cs = headertuple
188 if prevnode is None:
188 if prevnode is None:
189 deltabase = p1
189 deltabase = p1
190 else:
190 else:
191 deltabase = prevnode
191 deltabase = prevnode
192 flags = 0
192 flags = 0
193 return node, p1, p2, deltabase, cs, flags
193 return node, p1, p2, deltabase, cs, flags
194
194
195 def deltachunk(self, prevnode):
195 def deltachunk(self, prevnode):
196 l = self._chunklength()
196 l = self._chunklength()
197 if not l:
197 if not l:
198 return {}
198 return {}
199 headerdata = readexactly(self._stream, self.deltaheadersize)
199 headerdata = readexactly(self._stream, self.deltaheadersize)
200 header = self.deltaheader.unpack(headerdata)
200 header = self.deltaheader.unpack(headerdata)
201 delta = readexactly(self._stream, l - self.deltaheadersize)
201 delta = readexactly(self._stream, l - self.deltaheadersize)
202 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
202 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
203 return (node, p1, p2, cs, deltabase, delta, flags)
203 return (node, p1, p2, cs, deltabase, delta, flags)
204
204
205 def getchunks(self):
205 def getchunks(self):
206 """returns all the chunks contains in the bundle
206 """returns all the chunks contains in the bundle
207
207
208 Used when you need to forward the binary stream to a file or another
208 Used when you need to forward the binary stream to a file or another
209 network API. To do so, it parse the changegroup data, otherwise it will
209 network API. To do so, it parse the changegroup data, otherwise it will
210 block in case of sshrepo because it don't know the end of the stream.
210 block in case of sshrepo because it don't know the end of the stream.
211 """
211 """
212 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
212 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
213 # and a list of filelogs. For changegroup 3, we expect 4 parts:
213 # and a list of filelogs. For changegroup 3, we expect 4 parts:
214 # changelog, manifestlog, a list of tree manifestlogs, and a list of
214 # changelog, manifestlog, a list of tree manifestlogs, and a list of
215 # filelogs.
215 # filelogs.
216 #
216 #
217 # Changelog and manifestlog parts are terminated with empty chunks. The
217 # Changelog and manifestlog parts are terminated with empty chunks. The
218 # tree and file parts are a list of entry sections. Each entry section
218 # tree and file parts are a list of entry sections. Each entry section
219 # is a series of chunks terminating in an empty chunk. The list of these
219 # is a series of chunks terminating in an empty chunk. The list of these
220 # entry sections is terminated in yet another empty chunk, so we know
220 # entry sections is terminated in yet another empty chunk, so we know
221 # we've reached the end of the tree/file list when we reach an empty
221 # we've reached the end of the tree/file list when we reach an empty
222 # chunk that was proceeded by no non-empty chunks.
222 # chunk that was proceeded by no non-empty chunks.
223
223
224 parts = 0
224 parts = 0
225 while parts < 2 + self._grouplistcount:
225 while parts < 2 + self._grouplistcount:
226 noentries = True
226 noentries = True
227 while True:
227 while True:
228 chunk = getchunk(self)
228 chunk = getchunk(self)
229 if not chunk:
229 if not chunk:
230 # The first two empty chunks represent the end of the
230 # The first two empty chunks represent the end of the
231 # changelog and the manifestlog portions. The remaining
231 # changelog and the manifestlog portions. The remaining
232 # empty chunks represent either A) the end of individual
232 # empty chunks represent either A) the end of individual
233 # tree or file entries in the file list, or B) the end of
233 # tree or file entries in the file list, or B) the end of
234 # the entire list. It's the end of the entire list if there
234 # the entire list. It's the end of the entire list if there
235 # were no entries (i.e. noentries is True).
235 # were no entries (i.e. noentries is True).
236 if parts < 2:
236 if parts < 2:
237 parts += 1
237 parts += 1
238 elif noentries:
238 elif noentries:
239 parts += 1
239 parts += 1
240 break
240 break
241 noentries = False
241 noentries = False
242 yield chunkheader(len(chunk))
242 yield chunkheader(len(chunk))
243 pos = 0
243 pos = 0
244 while pos < len(chunk):
244 while pos < len(chunk):
245 next = pos + 2 ** 20
245 next = pos + 2 ** 20
246 yield chunk[pos:next]
246 yield chunk[pos:next]
247 pos = next
247 pos = next
248 yield closechunk()
248 yield closechunk()
249
249
250 def _unpackmanifests(self, repo, revmap, trp, prog):
250 def _unpackmanifests(self, repo, revmap, trp, prog):
251 self.callback = prog.increment
251 self.callback = prog.increment
252 # no need to check for empty manifest group here:
252 # no need to check for empty manifest group here:
253 # if the result of the merge of 1 and 2 is the same in 3 and 4,
253 # if the result of the merge of 1 and 2 is the same in 3 and 4,
254 # no new manifest will be created and the manifest group will
254 # no new manifest will be created and the manifest group will
255 # be empty during the pull
255 # be empty during the pull
256 self.manifestheader()
256 self.manifestheader()
257 deltas = self.deltaiter()
257 deltas = self.deltaiter()
258 repo.manifestlog.getstorage(b'').addgroup(deltas, revmap, trp)
258 repo.manifestlog.getstorage(b'').addgroup(deltas, revmap, trp)
259 prog.complete()
259 prog.complete()
260 self.callback = None
260 self.callback = None
261
261
262 def apply(
262 def apply(
263 self,
263 self,
264 repo,
264 repo,
265 tr,
265 tr,
266 srctype,
266 srctype,
267 url,
267 url,
268 targetphase=phases.draft,
268 targetphase=phases.draft,
269 expectedtotal=None,
269 expectedtotal=None,
270 ):
270 ):
271 """Add the changegroup returned by source.read() to this repo.
271 """Add the changegroup returned by source.read() to this repo.
272 srctype is a string like 'push', 'pull', or 'unbundle'. url is
272 srctype is a string like 'push', 'pull', or 'unbundle'. url is
273 the URL of the repo where this changegroup is coming from.
273 the URL of the repo where this changegroup is coming from.
274
274
275 Return an integer summarizing the change to this repo:
275 Return an integer summarizing the change to this repo:
276 - nothing changed or no source: 0
276 - nothing changed or no source: 0
277 - more heads than before: 1+added heads (2..n)
277 - more heads than before: 1+added heads (2..n)
278 - fewer heads than before: -1-removed heads (-2..-n)
278 - fewer heads than before: -1-removed heads (-2..-n)
279 - number of heads stays the same: 1
279 - number of heads stays the same: 1
280 """
280 """
281 repo = repo.unfiltered()
281 repo = repo.unfiltered()
282
282
283 def csmap(x):
283 def csmap(x):
284 repo.ui.debug(b"add changeset %s\n" % short(x))
284 repo.ui.debug(b"add changeset %s\n" % short(x))
285 return len(cl)
285 return len(cl)
286
286
287 def revmap(x):
287 def revmap(x):
288 return cl.rev(x)
288 return cl.rev(x)
289
289
290 try:
290 try:
291 # The transaction may already carry source information. In this
291 # The transaction may already carry source information. In this
292 # case we use the top level data. We overwrite the argument
292 # case we use the top level data. We overwrite the argument
293 # because we need to use the top level value (if they exist)
293 # because we need to use the top level value (if they exist)
294 # in this function.
294 # in this function.
295 srctype = tr.hookargs.setdefault(b'source', srctype)
295 srctype = tr.hookargs.setdefault(b'source', srctype)
296 tr.hookargs.setdefault(b'url', url)
296 tr.hookargs.setdefault(b'url', url)
297 repo.hook(
297 repo.hook(
298 b'prechangegroup', throw=True, **pycompat.strkwargs(tr.hookargs)
298 b'prechangegroup', throw=True, **pycompat.strkwargs(tr.hookargs)
299 )
299 )
300
300
301 # write changelog data to temp files so concurrent readers
301 # write changelog data to temp files so concurrent readers
302 # will not see an inconsistent view
302 # will not see an inconsistent view
303 cl = repo.changelog
303 cl = repo.changelog
304 cl.delayupdate(tr)
304 cl.delayupdate(tr)
305 oldheads = set(cl.heads())
305 oldheads = set(cl.heads())
306
306
307 trp = weakref.proxy(tr)
307 trp = weakref.proxy(tr)
308 # pull off the changeset group
308 # pull off the changeset group
309 repo.ui.status(_(b"adding changesets\n"))
309 repo.ui.status(_(b"adding changesets\n"))
310 clstart = len(cl)
310 clstart = len(cl)
311 progress = repo.ui.makeprogress(
311 progress = repo.ui.makeprogress(
312 _(b'changesets'), unit=_(b'chunks'), total=expectedtotal
312 _(b'changesets'), unit=_(b'chunks'), total=expectedtotal
313 )
313 )
314 self.callback = progress.increment
314 self.callback = progress.increment
315
315
316 efilesset = set()
316 efilesset = set()
317
317
318 def onchangelog(cl, node):
318 def onchangelog(cl, node):
319 efilesset.update(cl.readfiles(node))
319 efilesset.update(cl.readfiles(node))
320
320
321 self.changelogheader()
321 self.changelogheader()
322 deltas = self.deltaiter()
322 deltas = self.deltaiter()
323 cgnodes = cl.addgroup(deltas, csmap, trp, addrevisioncb=onchangelog)
323 cgnodes = cl.addgroup(deltas, csmap, trp, addrevisioncb=onchangelog)
324 efiles = len(efilesset)
324 efiles = len(efilesset)
325
325
326 if not cgnodes:
326 if not cgnodes:
327 repo.ui.develwarn(
327 repo.ui.develwarn(
328 b'applied empty changelog from changegroup',
328 b'applied empty changelog from changegroup',
329 config=b'warn-empty-changegroup',
329 config=b'warn-empty-changegroup',
330 )
330 )
331 clend = len(cl)
331 clend = len(cl)
332 changesets = clend - clstart
332 changesets = clend - clstart
333 progress.complete()
333 progress.complete()
334 self.callback = None
334 self.callback = None
335
335
336 # pull off the manifest group
336 # pull off the manifest group
337 repo.ui.status(_(b"adding manifests\n"))
337 repo.ui.status(_(b"adding manifests\n"))
338 # We know that we'll never have more manifests than we had
338 # We know that we'll never have more manifests than we had
339 # changesets.
339 # changesets.
340 progress = repo.ui.makeprogress(
340 progress = repo.ui.makeprogress(
341 _(b'manifests'), unit=_(b'chunks'), total=changesets
341 _(b'manifests'), unit=_(b'chunks'), total=changesets
342 )
342 )
343 self._unpackmanifests(repo, revmap, trp, progress)
343 self._unpackmanifests(repo, revmap, trp, progress)
344
344
345 needfiles = {}
345 needfiles = {}
346 if repo.ui.configbool(b'server', b'validate'):
346 if repo.ui.configbool(b'server', b'validate'):
347 cl = repo.changelog
347 cl = repo.changelog
348 ml = repo.manifestlog
348 ml = repo.manifestlog
349 # validate incoming csets have their manifests
349 # validate incoming csets have their manifests
350 for cset in pycompat.xrange(clstart, clend):
350 for cset in pycompat.xrange(clstart, clend):
351 mfnode = cl.changelogrevision(cset).manifest
351 mfnode = cl.changelogrevision(cset).manifest
352 mfest = ml[mfnode].readdelta()
352 mfest = ml[mfnode].readdelta()
353 # store file cgnodes we must see
353 # store file cgnodes we must see
354 for f, n in pycompat.iteritems(mfest):
354 for f, n in pycompat.iteritems(mfest):
355 needfiles.setdefault(f, set()).add(n)
355 needfiles.setdefault(f, set()).add(n)
356
356
357 # process the files
357 # process the files
358 repo.ui.status(_(b"adding file changes\n"))
358 repo.ui.status(_(b"adding file changes\n"))
359 newrevs, newfiles = _addchangegroupfiles(
359 newrevs, newfiles = _addchangegroupfiles(
360 repo, self, revmap, trp, efiles, needfiles
360 repo, self, revmap, trp, efiles, needfiles
361 )
361 )
362
362
363 # making sure the value exists
363 # making sure the value exists
364 tr.changes.setdefault(b'changegroup-count-changesets', 0)
364 tr.changes.setdefault(b'changegroup-count-changesets', 0)
365 tr.changes.setdefault(b'changegroup-count-revisions', 0)
365 tr.changes.setdefault(b'changegroup-count-revisions', 0)
366 tr.changes.setdefault(b'changegroup-count-files', 0)
366 tr.changes.setdefault(b'changegroup-count-files', 0)
367 tr.changes.setdefault(b'changegroup-count-heads', 0)
367 tr.changes.setdefault(b'changegroup-count-heads', 0)
368
368
369 # some code use bundle operation for internal purpose. They usually
369 # some code use bundle operation for internal purpose. They usually
370 # set `ui.quiet` to do this outside of user sight. Size the report
370 # set `ui.quiet` to do this outside of user sight. Size the report
371 # of such operation now happens at the end of the transaction, that
371 # of such operation now happens at the end of the transaction, that
372 # ui.quiet has not direct effect on the output.
372 # ui.quiet has not direct effect on the output.
373 #
373 #
374 # To preserve this intend use an inelegant hack, we fail to report
374 # To preserve this intend use an inelegant hack, we fail to report
375 # the change if `quiet` is set. We should probably move to
375 # the change if `quiet` is set. We should probably move to
376 # something better, but this is a good first step to allow the "end
376 # something better, but this is a good first step to allow the "end
377 # of transaction report" to pass tests.
377 # of transaction report" to pass tests.
378 if not repo.ui.quiet:
378 if not repo.ui.quiet:
379 tr.changes[b'changegroup-count-changesets'] += changesets
379 tr.changes[b'changegroup-count-changesets'] += changesets
380 tr.changes[b'changegroup-count-revisions'] += newrevs
380 tr.changes[b'changegroup-count-revisions'] += newrevs
381 tr.changes[b'changegroup-count-files'] += newfiles
381 tr.changes[b'changegroup-count-files'] += newfiles
382
382
383 deltaheads = 0
383 deltaheads = 0
384 if oldheads:
384 if oldheads:
385 heads = cl.heads()
385 heads = cl.heads()
386 deltaheads += len(heads) - len(oldheads)
386 deltaheads += len(heads) - len(oldheads)
387 for h in heads:
387 for h in heads:
388 if h not in oldheads and repo[h].closesbranch():
388 if h not in oldheads and repo[h].closesbranch():
389 deltaheads -= 1
389 deltaheads -= 1
390
390
391 # see previous comment about checking ui.quiet
391 # see previous comment about checking ui.quiet
392 if not repo.ui.quiet:
392 if not repo.ui.quiet:
393 tr.changes[b'changegroup-count-heads'] += deltaheads
393 tr.changes[b'changegroup-count-heads'] += deltaheads
394 repo.invalidatevolatilesets()
394 repo.invalidatevolatilesets()
395
395
396 if changesets > 0:
396 if changesets > 0:
397 if b'node' not in tr.hookargs:
397 if b'node' not in tr.hookargs:
398 tr.hookargs[b'node'] = hex(cl.node(clstart))
398 tr.hookargs[b'node'] = hex(cl.node(clstart))
399 tr.hookargs[b'node_last'] = hex(cl.node(clend - 1))
399 tr.hookargs[b'node_last'] = hex(cl.node(clend - 1))
400 hookargs = dict(tr.hookargs)
400 hookargs = dict(tr.hookargs)
401 else:
401 else:
402 hookargs = dict(tr.hookargs)
402 hookargs = dict(tr.hookargs)
403 hookargs[b'node'] = hex(cl.node(clstart))
403 hookargs[b'node'] = hex(cl.node(clstart))
404 hookargs[b'node_last'] = hex(cl.node(clend - 1))
404 hookargs[b'node_last'] = hex(cl.node(clend - 1))
405 repo.hook(
405 repo.hook(
406 b'pretxnchangegroup',
406 b'pretxnchangegroup',
407 throw=True,
407 throw=True,
408 **pycompat.strkwargs(hookargs)
408 **pycompat.strkwargs(hookargs)
409 )
409 )
410
410
411 added = [cl.node(r) for r in pycompat.xrange(clstart, clend)]
411 added = [cl.node(r) for r in pycompat.xrange(clstart, clend)]
412 phaseall = None
412 phaseall = None
413 if srctype in (b'push', b'serve'):
413 if srctype in (b'push', b'serve'):
414 # Old servers can not push the boundary themselves.
414 # Old servers can not push the boundary themselves.
415 # New servers won't push the boundary if changeset already
415 # New servers won't push the boundary if changeset already
416 # exists locally as secret
416 # exists locally as secret
417 #
417 #
418 # We should not use added here but the list of all change in
418 # We should not use added here but the list of all change in
419 # the bundle
419 # the bundle
420 if repo.publishing():
420 if repo.publishing():
421 targetphase = phaseall = phases.public
421 targetphase = phaseall = phases.public
422 else:
422 else:
423 # closer target phase computation
423 # closer target phase computation
424
424
425 # Those changesets have been pushed from the
425 # Those changesets have been pushed from the
426 # outside, their phases are going to be pushed
426 # outside, their phases are going to be pushed
427 # alongside. Therefor `targetphase` is
427 # alongside. Therefor `targetphase` is
428 # ignored.
428 # ignored.
429 targetphase = phaseall = phases.draft
429 targetphase = phaseall = phases.draft
430 if added:
430 if added:
431 phases.registernew(repo, tr, targetphase, added)
431 phases.registernew(repo, tr, targetphase, added)
432 if phaseall is not None:
432 if phaseall is not None:
433 phases.advanceboundary(repo, tr, phaseall, cgnodes)
433 phases.advanceboundary(repo, tr, phaseall, cgnodes)
434
434
435 if changesets > 0:
435 if changesets > 0:
436
436
437 def runhooks(unused_success):
437 def runhooks(unused_success):
438 # These hooks run when the lock releases, not when the
438 # These hooks run when the lock releases, not when the
439 # transaction closes. So it's possible for the changelog
439 # transaction closes. So it's possible for the changelog
440 # to have changed since we last saw it.
440 # to have changed since we last saw it.
441 if clstart >= len(repo):
441 if clstart >= len(repo):
442 return
442 return
443
443
444 repo.hook(b"changegroup", **pycompat.strkwargs(hookargs))
444 repo.hook(b"changegroup", **pycompat.strkwargs(hookargs))
445
445
446 for n in added:
446 for n in added:
447 args = hookargs.copy()
447 args = hookargs.copy()
448 args[b'node'] = hex(n)
448 args[b'node'] = hex(n)
449 del args[b'node_last']
449 del args[b'node_last']
450 repo.hook(b"incoming", **pycompat.strkwargs(args))
450 repo.hook(b"incoming", **pycompat.strkwargs(args))
451
451
452 newheads = [h for h in repo.heads() if h not in oldheads]
452 newheads = [h for h in repo.heads() if h not in oldheads]
453 repo.ui.log(
453 repo.ui.log(
454 b"incoming",
454 b"incoming",
455 b"%d incoming changes - new heads: %s\n",
455 b"%d incoming changes - new heads: %s\n",
456 len(added),
456 len(added),
457 b', '.join([hex(c[:6]) for c in newheads]),
457 b', '.join([hex(c[:6]) for c in newheads]),
458 )
458 )
459
459
460 tr.addpostclose(
460 tr.addpostclose(
461 b'changegroup-runhooks-%020i' % clstart,
461 b'changegroup-runhooks-%020i' % clstart,
462 lambda tr: repo._afterlock(runhooks),
462 lambda tr: repo._afterlock(runhooks),
463 )
463 )
464 finally:
464 finally:
465 repo.ui.flush()
465 repo.ui.flush()
466 # never return 0 here:
466 # never return 0 here:
467 if deltaheads < 0:
467 if deltaheads < 0:
468 ret = deltaheads - 1
468 ret = deltaheads - 1
469 else:
469 else:
470 ret = deltaheads + 1
470 ret = deltaheads + 1
471 return ret
471 return ret
472
472
473 def deltaiter(self):
473 def deltaiter(self):
474 """
474 """
475 returns an iterator of the deltas in this changegroup
475 returns an iterator of the deltas in this changegroup
476
476
477 Useful for passing to the underlying storage system to be stored.
477 Useful for passing to the underlying storage system to be stored.
478 """
478 """
479 chain = None
479 chain = None
480 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
480 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
481 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags)
481 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags)
482 yield chunkdata
482 yield chunkdata
483 chain = chunkdata[0]
483 chain = chunkdata[0]
484
484
485
485
486 class cg2unpacker(cg1unpacker):
486 class cg2unpacker(cg1unpacker):
487 """Unpacker for cg2 streams.
487 """Unpacker for cg2 streams.
488
488
489 cg2 streams add support for generaldelta, so the delta header
489 cg2 streams add support for generaldelta, so the delta header
490 format is slightly different. All other features about the data
490 format is slightly different. All other features about the data
491 remain the same.
491 remain the same.
492 """
492 """
493
493
494 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
494 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
495 deltaheadersize = deltaheader.size
495 deltaheadersize = deltaheader.size
496 version = b'02'
496 version = b'02'
497
497
498 def _deltaheader(self, headertuple, prevnode):
498 def _deltaheader(self, headertuple, prevnode):
499 node, p1, p2, deltabase, cs = headertuple
499 node, p1, p2, deltabase, cs = headertuple
500 flags = 0
500 flags = 0
501 return node, p1, p2, deltabase, cs, flags
501 return node, p1, p2, deltabase, cs, flags
502
502
503
503
504 class cg3unpacker(cg2unpacker):
504 class cg3unpacker(cg2unpacker):
505 """Unpacker for cg3 streams.
505 """Unpacker for cg3 streams.
506
506
507 cg3 streams add support for exchanging treemanifests and revlog
507 cg3 streams add support for exchanging treemanifests and revlog
508 flags. It adds the revlog flags to the delta header and an empty chunk
508 flags. It adds the revlog flags to the delta header and an empty chunk
509 separating manifests and files.
509 separating manifests and files.
510 """
510 """
511
511
512 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
512 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
513 deltaheadersize = deltaheader.size
513 deltaheadersize = deltaheader.size
514 version = b'03'
514 version = b'03'
515 _grouplistcount = 2 # One list of manifests and one list of files
515 _grouplistcount = 2 # One list of manifests and one list of files
516
516
517 def _deltaheader(self, headertuple, prevnode):
517 def _deltaheader(self, headertuple, prevnode):
518 node, p1, p2, deltabase, cs, flags = headertuple
518 node, p1, p2, deltabase, cs, flags = headertuple
519 return node, p1, p2, deltabase, cs, flags
519 return node, p1, p2, deltabase, cs, flags
520
520
521 def _unpackmanifests(self, repo, revmap, trp, prog):
521 def _unpackmanifests(self, repo, revmap, trp, prog):
522 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog)
522 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog)
523 for chunkdata in iter(self.filelogheader, {}):
523 for chunkdata in iter(self.filelogheader, {}):
524 # If we get here, there are directory manifests in the changegroup
524 # If we get here, there are directory manifests in the changegroup
525 d = chunkdata[b"filename"]
525 d = chunkdata[b"filename"]
526 repo.ui.debug(b"adding %s revisions\n" % d)
526 repo.ui.debug(b"adding %s revisions\n" % d)
527 deltas = self.deltaiter()
527 deltas = self.deltaiter()
528 if not repo.manifestlog.getstorage(d).addgroup(deltas, revmap, trp):
528 if not repo.manifestlog.getstorage(d).addgroup(deltas, revmap, trp):
529 raise error.Abort(_(b"received dir revlog group is empty"))
529 raise error.Abort(_(b"received dir revlog group is empty"))
530
530
531
531
532 class headerlessfixup(object):
532 class headerlessfixup(object):
533 def __init__(self, fh, h):
533 def __init__(self, fh, h):
534 self._h = h
534 self._h = h
535 self._fh = fh
535 self._fh = fh
536
536
537 def read(self, n):
537 def read(self, n):
538 if self._h:
538 if self._h:
539 d, self._h = self._h[:n], self._h[n:]
539 d, self._h = self._h[:n], self._h[n:]
540 if len(d) < n:
540 if len(d) < n:
541 d += readexactly(self._fh, n - len(d))
541 d += readexactly(self._fh, n - len(d))
542 return d
542 return d
543 return readexactly(self._fh, n)
543 return readexactly(self._fh, n)
544
544
545
545
546 def _revisiondeltatochunks(delta, headerfn):
546 def _revisiondeltatochunks(delta, headerfn):
547 """Serialize a revisiondelta to changegroup chunks."""
547 """Serialize a revisiondelta to changegroup chunks."""
548
548
549 # The captured revision delta may be encoded as a delta against
549 # The captured revision delta may be encoded as a delta against
550 # a base revision or as a full revision. The changegroup format
550 # a base revision or as a full revision. The changegroup format
551 # requires that everything on the wire be deltas. So for full
551 # requires that everything on the wire be deltas. So for full
552 # revisions, we need to invent a header that says to rewrite
552 # revisions, we need to invent a header that says to rewrite
553 # data.
553 # data.
554
554
555 if delta.delta is not None:
555 if delta.delta is not None:
556 prefix, data = b'', delta.delta
556 prefix, data = b'', delta.delta
557 elif delta.basenode == nullid:
557 elif delta.basenode == nullid:
558 data = delta.revision
558 data = delta.revision
559 prefix = mdiff.trivialdiffheader(len(data))
559 prefix = mdiff.trivialdiffheader(len(data))
560 else:
560 else:
561 data = delta.revision
561 data = delta.revision
562 prefix = mdiff.replacediffheader(delta.baserevisionsize, len(data))
562 prefix = mdiff.replacediffheader(delta.baserevisionsize, len(data))
563
563
564 meta = headerfn(delta)
564 meta = headerfn(delta)
565
565
566 yield chunkheader(len(meta) + len(prefix) + len(data))
566 yield chunkheader(len(meta) + len(prefix) + len(data))
567 yield meta
567 yield meta
568 if prefix:
568 if prefix:
569 yield prefix
569 yield prefix
570 yield data
570 yield data
571
571
572
572
573 def _sortnodesellipsis(store, nodes, cl, lookup):
573 def _sortnodesellipsis(store, nodes, cl, lookup):
574 """Sort nodes for changegroup generation."""
574 """Sort nodes for changegroup generation."""
575 # Ellipses serving mode.
575 # Ellipses serving mode.
576 #
576 #
577 # In a perfect world, we'd generate better ellipsis-ified graphs
577 # In a perfect world, we'd generate better ellipsis-ified graphs
578 # for non-changelog revlogs. In practice, we haven't started doing
578 # for non-changelog revlogs. In practice, we haven't started doing
579 # that yet, so the resulting DAGs for the manifestlog and filelogs
579 # that yet, so the resulting DAGs for the manifestlog and filelogs
580 # are actually full of bogus parentage on all the ellipsis
580 # are actually full of bogus parentage on all the ellipsis
581 # nodes. This has the side effect that, while the contents are
581 # nodes. This has the side effect that, while the contents are
582 # correct, the individual DAGs might be completely out of whack in
582 # correct, the individual DAGs might be completely out of whack in
583 # a case like 882681bc3166 and its ancestors (back about 10
583 # a case like 882681bc3166 and its ancestors (back about 10
584 # revisions or so) in the main hg repo.
584 # revisions or so) in the main hg repo.
585 #
585 #
586 # The one invariant we *know* holds is that the new (potentially
586 # The one invariant we *know* holds is that the new (potentially
587 # bogus) DAG shape will be valid if we order the nodes in the
587 # bogus) DAG shape will be valid if we order the nodes in the
588 # order that they're introduced in dramatis personae by the
588 # order that they're introduced in dramatis personae by the
589 # changelog, so what we do is we sort the non-changelog histories
589 # changelog, so what we do is we sort the non-changelog histories
590 # by the order in which they are used by the changelog.
590 # by the order in which they are used by the changelog.
591 key = lambda n: cl.rev(lookup(n))
591 key = lambda n: cl.rev(lookup(n))
592 return sorted(nodes, key=key)
592 return sorted(nodes, key=key)
593
593
594
594
595 def _resolvenarrowrevisioninfo(
595 def _resolvenarrowrevisioninfo(
596 cl,
596 cl,
597 store,
597 store,
598 ischangelog,
598 ischangelog,
599 rev,
599 rev,
600 linkrev,
600 linkrev,
601 linknode,
601 linknode,
602 clrevtolocalrev,
602 clrevtolocalrev,
603 fullclnodes,
603 fullclnodes,
604 precomputedellipsis,
604 precomputedellipsis,
605 ):
605 ):
606 linkparents = precomputedellipsis[linkrev]
606 linkparents = precomputedellipsis[linkrev]
607
607
608 def local(clrev):
608 def local(clrev):
609 """Turn a changelog revnum into a local revnum.
609 """Turn a changelog revnum into a local revnum.
610
610
611 The ellipsis dag is stored as revnums on the changelog,
611 The ellipsis dag is stored as revnums on the changelog,
612 but when we're producing ellipsis entries for
612 but when we're producing ellipsis entries for
613 non-changelog revlogs, we need to turn those numbers into
613 non-changelog revlogs, we need to turn those numbers into
614 something local. This does that for us, and during the
614 something local. This does that for us, and during the
615 changelog sending phase will also expand the stored
615 changelog sending phase will also expand the stored
616 mappings as needed.
616 mappings as needed.
617 """
617 """
618 if clrev == nullrev:
618 if clrev == nullrev:
619 return nullrev
619 return nullrev
620
620
621 if ischangelog:
621 if ischangelog:
622 return clrev
622 return clrev
623
623
624 # Walk the ellipsis-ized changelog breadth-first looking for a
624 # Walk the ellipsis-ized changelog breadth-first looking for a
625 # change that has been linked from the current revlog.
625 # change that has been linked from the current revlog.
626 #
626 #
627 # For a flat manifest revlog only a single step should be necessary
627 # For a flat manifest revlog only a single step should be necessary
628 # as all relevant changelog entries are relevant to the flat
628 # as all relevant changelog entries are relevant to the flat
629 # manifest.
629 # manifest.
630 #
630 #
631 # For a filelog or tree manifest dirlog however not every changelog
631 # For a filelog or tree manifest dirlog however not every changelog
632 # entry will have been relevant, so we need to skip some changelog
632 # entry will have been relevant, so we need to skip some changelog
633 # nodes even after ellipsis-izing.
633 # nodes even after ellipsis-izing.
634 walk = [clrev]
634 walk = [clrev]
635 while walk:
635 while walk:
636 p = walk[0]
636 p = walk[0]
637 walk = walk[1:]
637 walk = walk[1:]
638 if p in clrevtolocalrev:
638 if p in clrevtolocalrev:
639 return clrevtolocalrev[p]
639 return clrevtolocalrev[p]
640 elif p in fullclnodes:
640 elif p in fullclnodes:
641 walk.extend([pp for pp in cl.parentrevs(p) if pp != nullrev])
641 walk.extend([pp for pp in cl.parentrevs(p) if pp != nullrev])
642 elif p in precomputedellipsis:
642 elif p in precomputedellipsis:
643 walk.extend(
643 walk.extend(
644 [pp for pp in precomputedellipsis[p] if pp != nullrev]
644 [pp for pp in precomputedellipsis[p] if pp != nullrev]
645 )
645 )
646 else:
646 else:
647 # In this case, we've got an ellipsis with parents
647 # In this case, we've got an ellipsis with parents
648 # outside the current bundle (likely an
648 # outside the current bundle (likely an
649 # incremental pull). We "know" that we can use the
649 # incremental pull). We "know" that we can use the
650 # value of this same revlog at whatever revision
650 # value of this same revlog at whatever revision
651 # is pointed to by linknode. "Know" is in scare
651 # is pointed to by linknode. "Know" is in scare
652 # quotes because I haven't done enough examination
652 # quotes because I haven't done enough examination
653 # of edge cases to convince myself this is really
653 # of edge cases to convince myself this is really
654 # a fact - it works for all the (admittedly
654 # a fact - it works for all the (admittedly
655 # thorough) cases in our testsuite, but I would be
655 # thorough) cases in our testsuite, but I would be
656 # somewhat unsurprised to find a case in the wild
656 # somewhat unsurprised to find a case in the wild
657 # where this breaks down a bit. That said, I don't
657 # where this breaks down a bit. That said, I don't
658 # know if it would hurt anything.
658 # know if it would hurt anything.
659 for i in pycompat.xrange(rev, 0, -1):
659 for i in pycompat.xrange(rev, 0, -1):
660 if store.linkrev(i) == clrev:
660 if store.linkrev(i) == clrev:
661 return i
661 return i
662 # We failed to resolve a parent for this node, so
662 # We failed to resolve a parent for this node, so
663 # we crash the changegroup construction.
663 # we crash the changegroup construction.
664 raise error.Abort(
664 raise error.Abort(
665 b'unable to resolve parent while packing %r %r'
665 b'unable to resolve parent while packing %r %r'
666 b' for changeset %r' % (store.indexfile, rev, clrev)
666 b' for changeset %r' % (store.indexfile, rev, clrev)
667 )
667 )
668
668
669 return nullrev
669 return nullrev
670
670
671 if not linkparents or (store.parentrevs(rev) == (nullrev, nullrev)):
671 if not linkparents or (store.parentrevs(rev) == (nullrev, nullrev)):
672 p1, p2 = nullrev, nullrev
672 p1, p2 = nullrev, nullrev
673 elif len(linkparents) == 1:
673 elif len(linkparents) == 1:
674 (p1,) = sorted(local(p) for p in linkparents)
674 (p1,) = sorted(local(p) for p in linkparents)
675 p2 = nullrev
675 p2 = nullrev
676 else:
676 else:
677 p1, p2 = sorted(local(p) for p in linkparents)
677 p1, p2 = sorted(local(p) for p in linkparents)
678
678
679 p1node, p2node = store.node(p1), store.node(p2)
679 p1node, p2node = store.node(p1), store.node(p2)
680
680
681 return p1node, p2node, linknode
681 return p1node, p2node, linknode
682
682
683
683
684 def deltagroup(
684 def deltagroup(
685 repo,
685 repo,
686 store,
686 store,
687 nodes,
687 nodes,
688 ischangelog,
688 ischangelog,
689 lookup,
689 lookup,
690 forcedeltaparentprev,
690 forcedeltaparentprev,
691 topic=None,
691 topic=None,
692 ellipses=False,
692 ellipses=False,
693 clrevtolocalrev=None,
693 clrevtolocalrev=None,
694 fullclnodes=None,
694 fullclnodes=None,
695 precomputedellipsis=None,
695 precomputedellipsis=None,
696 ):
696 ):
697 """Calculate deltas for a set of revisions.
697 """Calculate deltas for a set of revisions.
698
698
699 Is a generator of ``revisiondelta`` instances.
699 Is a generator of ``revisiondelta`` instances.
700
700
701 If topic is not None, progress detail will be generated using this
701 If topic is not None, progress detail will be generated using this
702 topic name (e.g. changesets, manifests, etc).
702 topic name (e.g. changesets, manifests, etc).
703 """
703 """
704 if not nodes:
704 if not nodes:
705 return
705 return
706
706
707 cl = repo.changelog
707 cl = repo.changelog
708
708
709 if ischangelog:
709 if ischangelog:
710 # `hg log` shows changesets in storage order. To preserve order
710 # `hg log` shows changesets in storage order. To preserve order
711 # across clones, send out changesets in storage order.
711 # across clones, send out changesets in storage order.
712 nodesorder = b'storage'
712 nodesorder = b'storage'
713 elif ellipses:
713 elif ellipses:
714 nodes = _sortnodesellipsis(store, nodes, cl, lookup)
714 nodes = _sortnodesellipsis(store, nodes, cl, lookup)
715 nodesorder = b'nodes'
715 nodesorder = b'nodes'
716 else:
716 else:
717 nodesorder = None
717 nodesorder = None
718
718
719 # Perform ellipses filtering and revision massaging. We do this before
719 # Perform ellipses filtering and revision massaging. We do this before
720 # emitrevisions() because a) filtering out revisions creates less work
720 # emitrevisions() because a) filtering out revisions creates less work
721 # for emitrevisions() b) dropping revisions would break emitrevisions()'s
721 # for emitrevisions() b) dropping revisions would break emitrevisions()'s
722 # assumptions about delta choices and we would possibly send a delta
722 # assumptions about delta choices and we would possibly send a delta
723 # referencing a missing base revision.
723 # referencing a missing base revision.
724 #
724 #
725 # Also, calling lookup() has side-effects with regards to populating
725 # Also, calling lookup() has side-effects with regards to populating
726 # data structures. If we don't call lookup() for each node or if we call
726 # data structures. If we don't call lookup() for each node or if we call
727 # lookup() after the first pass through each node, things can break -
727 # lookup() after the first pass through each node, things can break -
728 # possibly intermittently depending on the python hash seed! For that
728 # possibly intermittently depending on the python hash seed! For that
729 # reason, we store a mapping of all linknodes during the initial node
729 # reason, we store a mapping of all linknodes during the initial node
730 # pass rather than use lookup() on the output side.
730 # pass rather than use lookup() on the output side.
731 if ellipses:
731 if ellipses:
732 filtered = []
732 filtered = []
733 adjustedparents = {}
733 adjustedparents = {}
734 linknodes = {}
734 linknodes = {}
735
735
736 for node in nodes:
736 for node in nodes:
737 rev = store.rev(node)
737 rev = store.rev(node)
738 linknode = lookup(node)
738 linknode = lookup(node)
739 linkrev = cl.rev(linknode)
739 linkrev = cl.rev(linknode)
740 clrevtolocalrev[linkrev] = rev
740 clrevtolocalrev[linkrev] = rev
741
741
742 # If linknode is in fullclnodes, it means the corresponding
742 # If linknode is in fullclnodes, it means the corresponding
743 # changeset was a full changeset and is being sent unaltered.
743 # changeset was a full changeset and is being sent unaltered.
744 if linknode in fullclnodes:
744 if linknode in fullclnodes:
745 linknodes[node] = linknode
745 linknodes[node] = linknode
746
746
747 # If the corresponding changeset wasn't in the set computed
747 # If the corresponding changeset wasn't in the set computed
748 # as relevant to us, it should be dropped outright.
748 # as relevant to us, it should be dropped outright.
749 elif linkrev not in precomputedellipsis:
749 elif linkrev not in precomputedellipsis:
750 continue
750 continue
751
751
752 else:
752 else:
753 # We could probably do this later and avoid the dict
753 # We could probably do this later and avoid the dict
754 # holding state. But it likely doesn't matter.
754 # holding state. But it likely doesn't matter.
755 p1node, p2node, linknode = _resolvenarrowrevisioninfo(
755 p1node, p2node, linknode = _resolvenarrowrevisioninfo(
756 cl,
756 cl,
757 store,
757 store,
758 ischangelog,
758 ischangelog,
759 rev,
759 rev,
760 linkrev,
760 linkrev,
761 linknode,
761 linknode,
762 clrevtolocalrev,
762 clrevtolocalrev,
763 fullclnodes,
763 fullclnodes,
764 precomputedellipsis,
764 precomputedellipsis,
765 )
765 )
766
766
767 adjustedparents[node] = (p1node, p2node)
767 adjustedparents[node] = (p1node, p2node)
768 linknodes[node] = linknode
768 linknodes[node] = linknode
769
769
770 filtered.append(node)
770 filtered.append(node)
771
771
772 nodes = filtered
772 nodes = filtered
773
773
774 # We expect the first pass to be fast, so we only engage the progress
774 # We expect the first pass to be fast, so we only engage the progress
775 # meter for constructing the revision deltas.
775 # meter for constructing the revision deltas.
776 progress = None
776 progress = None
777 if topic is not None:
777 if topic is not None:
778 progress = repo.ui.makeprogress(
778 progress = repo.ui.makeprogress(
779 topic, unit=_(b'chunks'), total=len(nodes)
779 topic, unit=_(b'chunks'), total=len(nodes)
780 )
780 )
781
781
782 configtarget = repo.ui.config(b'devel', b'bundle.delta')
782 configtarget = repo.ui.config(b'devel', b'bundle.delta')
783 if configtarget not in (b'', b'p1', b'full'):
783 if configtarget not in (b'', b'p1', b'full'):
784 msg = _("""config "devel.bundle.delta" as unknown value: %s""")
784 msg = _("""config "devel.bundle.delta" as unknown value: %s""")
785 repo.ui.warn(msg % configtarget)
785 repo.ui.warn(msg % configtarget)
786
786
787 deltamode = repository.CG_DELTAMODE_STD
787 deltamode = repository.CG_DELTAMODE_STD
788 if forcedeltaparentprev:
788 if forcedeltaparentprev:
789 deltamode = repository.CG_DELTAMODE_PREV
789 deltamode = repository.CG_DELTAMODE_PREV
790 elif configtarget == b'p1':
790 elif configtarget == b'p1':
791 deltamode = repository.CG_DELTAMODE_P1
791 deltamode = repository.CG_DELTAMODE_P1
792 elif configtarget == b'full':
792 elif configtarget == b'full':
793 deltamode = repository.CG_DELTAMODE_FULL
793 deltamode = repository.CG_DELTAMODE_FULL
794
794
795 revisions = store.emitrevisions(
795 revisions = store.emitrevisions(
796 nodes,
796 nodes,
797 nodesorder=nodesorder,
797 nodesorder=nodesorder,
798 revisiondata=True,
798 revisiondata=True,
799 assumehaveparentrevisions=not ellipses,
799 assumehaveparentrevisions=not ellipses,
800 deltamode=deltamode,
800 deltamode=deltamode,
801 )
801 )
802
802
803 for i, revision in enumerate(revisions):
803 for i, revision in enumerate(revisions):
804 if progress:
804 if progress:
805 progress.update(i + 1)
805 progress.update(i + 1)
806
806
807 if ellipses:
807 if ellipses:
808 linknode = linknodes[revision.node]
808 linknode = linknodes[revision.node]
809
809
810 if revision.node in adjustedparents:
810 if revision.node in adjustedparents:
811 p1node, p2node = adjustedparents[revision.node]
811 p1node, p2node = adjustedparents[revision.node]
812 revision.p1node = p1node
812 revision.p1node = p1node
813 revision.p2node = p2node
813 revision.p2node = p2node
814 revision.flags |= repository.REVISION_FLAG_ELLIPSIS
814 revision.flags |= repository.REVISION_FLAG_ELLIPSIS
815
815
816 else:
816 else:
817 linknode = lookup(revision.node)
817 linknode = lookup(revision.node)
818
818
819 revision.linknode = linknode
819 revision.linknode = linknode
820 yield revision
820 yield revision
821
821
822 if progress:
822 if progress:
823 progress.complete()
823 progress.complete()
824
824
825
825
826 class cgpacker(object):
826 class cgpacker(object):
827 def __init__(
827 def __init__(
828 self,
828 self,
829 repo,
829 repo,
830 oldmatcher,
830 oldmatcher,
831 matcher,
831 matcher,
832 version,
832 version,
833 builddeltaheader,
833 builddeltaheader,
834 manifestsend,
834 manifestsend,
835 forcedeltaparentprev=False,
835 forcedeltaparentprev=False,
836 bundlecaps=None,
836 bundlecaps=None,
837 ellipses=False,
837 ellipses=False,
838 shallow=False,
838 shallow=False,
839 ellipsisroots=None,
839 ellipsisroots=None,
840 fullnodes=None,
840 fullnodes=None,
841 ):
841 ):
842 """Given a source repo, construct a bundler.
842 """Given a source repo, construct a bundler.
843
843
844 oldmatcher is a matcher that matches on files the client already has.
844 oldmatcher is a matcher that matches on files the client already has.
845 These will not be included in the changegroup.
845 These will not be included in the changegroup.
846
846
847 matcher is a matcher that matches on files to include in the
847 matcher is a matcher that matches on files to include in the
848 changegroup. Used to facilitate sparse changegroups.
848 changegroup. Used to facilitate sparse changegroups.
849
849
850 forcedeltaparentprev indicates whether delta parents must be against
850 forcedeltaparentprev indicates whether delta parents must be against
851 the previous revision in a delta group. This should only be used for
851 the previous revision in a delta group. This should only be used for
852 compatibility with changegroup version 1.
852 compatibility with changegroup version 1.
853
853
854 builddeltaheader is a callable that constructs the header for a group
854 builddeltaheader is a callable that constructs the header for a group
855 delta.
855 delta.
856
856
857 manifestsend is a chunk to send after manifests have been fully emitted.
857 manifestsend is a chunk to send after manifests have been fully emitted.
858
858
859 ellipses indicates whether ellipsis serving mode is enabled.
859 ellipses indicates whether ellipsis serving mode is enabled.
860
860
861 bundlecaps is optional and can be used to specify the set of
861 bundlecaps is optional and can be used to specify the set of
862 capabilities which can be used to build the bundle. While bundlecaps is
862 capabilities which can be used to build the bundle. While bundlecaps is
863 unused in core Mercurial, extensions rely on this feature to communicate
863 unused in core Mercurial, extensions rely on this feature to communicate
864 capabilities to customize the changegroup packer.
864 capabilities to customize the changegroup packer.
865
865
866 shallow indicates whether shallow data might be sent. The packer may
866 shallow indicates whether shallow data might be sent. The packer may
867 need to pack file contents not introduced by the changes being packed.
867 need to pack file contents not introduced by the changes being packed.
868
868
869 fullnodes is the set of changelog nodes which should not be ellipsis
869 fullnodes is the set of changelog nodes which should not be ellipsis
870 nodes. We store this rather than the set of nodes that should be
870 nodes. We store this rather than the set of nodes that should be
871 ellipsis because for very large histories we expect this to be
871 ellipsis because for very large histories we expect this to be
872 significantly smaller.
872 significantly smaller.
873 """
873 """
874 assert oldmatcher
874 assert oldmatcher
875 assert matcher
875 assert matcher
876 self._oldmatcher = oldmatcher
876 self._oldmatcher = oldmatcher
877 self._matcher = matcher
877 self._matcher = matcher
878
878
879 self.version = version
879 self.version = version
880 self._forcedeltaparentprev = forcedeltaparentprev
880 self._forcedeltaparentprev = forcedeltaparentprev
881 self._builddeltaheader = builddeltaheader
881 self._builddeltaheader = builddeltaheader
882 self._manifestsend = manifestsend
882 self._manifestsend = manifestsend
883 self._ellipses = ellipses
883 self._ellipses = ellipses
884
884
885 # Set of capabilities we can use to build the bundle.
885 # Set of capabilities we can use to build the bundle.
886 if bundlecaps is None:
886 if bundlecaps is None:
887 bundlecaps = set()
887 bundlecaps = set()
888 self._bundlecaps = bundlecaps
888 self._bundlecaps = bundlecaps
889 self._isshallow = shallow
889 self._isshallow = shallow
890 self._fullclnodes = fullnodes
890 self._fullclnodes = fullnodes
891
891
892 # Maps ellipsis revs to their roots at the changelog level.
892 # Maps ellipsis revs to their roots at the changelog level.
893 self._precomputedellipsis = ellipsisroots
893 self._precomputedellipsis = ellipsisroots
894
894
895 self._repo = repo
895 self._repo = repo
896
896
897 if self._repo.ui.verbose and not self._repo.ui.debugflag:
897 if self._repo.ui.verbose and not self._repo.ui.debugflag:
898 self._verbosenote = self._repo.ui.note
898 self._verbosenote = self._repo.ui.note
899 else:
899 else:
900 self._verbosenote = lambda s: None
900 self._verbosenote = lambda s: None
901
901
902 def generate(
902 def generate(
903 self, commonrevs, clnodes, fastpathlinkrev, source, changelog=True
903 self, commonrevs, clnodes, fastpathlinkrev, source, changelog=True
904 ):
904 ):
905 """Yield a sequence of changegroup byte chunks.
905 """Yield a sequence of changegroup byte chunks.
906 If changelog is False, changelog data won't be added to changegroup
906 If changelog is False, changelog data won't be added to changegroup
907 """
907 """
908
908
909 repo = self._repo
909 repo = self._repo
910 cl = repo.changelog
910 cl = repo.changelog
911
911
912 self._verbosenote(_(b'uncompressed size of bundle content:\n'))
912 self._verbosenote(_(b'uncompressed size of bundle content:\n'))
913 size = 0
913 size = 0
914
914
915 clstate, deltas = self._generatechangelog(
915 clstate, deltas = self._generatechangelog(
916 cl, clnodes, generate=changelog
916 cl, clnodes, generate=changelog
917 )
917 )
918 for delta in deltas:
918 for delta in deltas:
919 for chunk in _revisiondeltatochunks(delta, self._builddeltaheader):
919 for chunk in _revisiondeltatochunks(delta, self._builddeltaheader):
920 size += len(chunk)
920 size += len(chunk)
921 yield chunk
921 yield chunk
922
922
923 close = closechunk()
923 close = closechunk()
924 size += len(close)
924 size += len(close)
925 yield closechunk()
925 yield closechunk()
926
926
927 self._verbosenote(_(b'%8.i (changelog)\n') % size)
927 self._verbosenote(_(b'%8.i (changelog)\n') % size)
928
928
929 clrevorder = clstate[b'clrevorder']
929 clrevorder = clstate[b'clrevorder']
930 manifests = clstate[b'manifests']
930 manifests = clstate[b'manifests']
931 changedfiles = clstate[b'changedfiles']
931 changedfiles = clstate[b'changedfiles']
932
932
933 # We need to make sure that the linkrev in the changegroup refers to
933 # We need to make sure that the linkrev in the changegroup refers to
934 # the first changeset that introduced the manifest or file revision.
934 # the first changeset that introduced the manifest or file revision.
935 # The fastpath is usually safer than the slowpath, because the filelogs
935 # The fastpath is usually safer than the slowpath, because the filelogs
936 # are walked in revlog order.
936 # are walked in revlog order.
937 #
937 #
938 # When taking the slowpath when the manifest revlog uses generaldelta,
938 # When taking the slowpath when the manifest revlog uses generaldelta,
939 # the manifest may be walked in the "wrong" order. Without 'clrevorder',
939 # the manifest may be walked in the "wrong" order. Without 'clrevorder',
940 # we would get an incorrect linkrev (see fix in cc0ff93d0c0c).
940 # we would get an incorrect linkrev (see fix in cc0ff93d0c0c).
941 #
941 #
942 # When taking the fastpath, we are only vulnerable to reordering
942 # When taking the fastpath, we are only vulnerable to reordering
943 # of the changelog itself. The changelog never uses generaldelta and is
943 # of the changelog itself. The changelog never uses generaldelta and is
944 # never reordered. To handle this case, we simply take the slowpath,
944 # never reordered. To handle this case, we simply take the slowpath,
945 # which already has the 'clrevorder' logic. This was also fixed in
945 # which already has the 'clrevorder' logic. This was also fixed in
946 # cc0ff93d0c0c.
946 # cc0ff93d0c0c.
947
947
948 # Treemanifests don't work correctly with fastpathlinkrev
948 # Treemanifests don't work correctly with fastpathlinkrev
949 # either, because we don't discover which directory nodes to
949 # either, because we don't discover which directory nodes to
950 # send along with files. This could probably be fixed.
950 # send along with files. This could probably be fixed.
951 fastpathlinkrev = fastpathlinkrev and (
951 fastpathlinkrev = fastpathlinkrev and (
952 b'treemanifest' not in repo.requirements
952 b'treemanifest' not in repo.requirements
953 )
953 )
954
954
955 fnodes = {} # needed file nodes
955 fnodes = {} # needed file nodes
956
956
957 size = 0
957 size = 0
958 it = self.generatemanifests(
958 it = self.generatemanifests(
959 commonrevs,
959 commonrevs,
960 clrevorder,
960 clrevorder,
961 fastpathlinkrev,
961 fastpathlinkrev,
962 manifests,
962 manifests,
963 fnodes,
963 fnodes,
964 source,
964 source,
965 clstate[b'clrevtomanifestrev'],
965 clstate[b'clrevtomanifestrev'],
966 )
966 )
967
967
968 for tree, deltas in it:
968 for tree, deltas in it:
969 if tree:
969 if tree:
970 assert self.version == b'03'
970 assert self.version == b'03'
971 chunk = _fileheader(tree)
971 chunk = _fileheader(tree)
972 size += len(chunk)
972 size += len(chunk)
973 yield chunk
973 yield chunk
974
974
975 for delta in deltas:
975 for delta in deltas:
976 chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
976 chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
977 for chunk in chunks:
977 for chunk in chunks:
978 size += len(chunk)
978 size += len(chunk)
979 yield chunk
979 yield chunk
980
980
981 close = closechunk()
981 close = closechunk()
982 size += len(close)
982 size += len(close)
983 yield close
983 yield close
984
984
985 self._verbosenote(_(b'%8.i (manifests)\n') % size)
985 self._verbosenote(_(b'%8.i (manifests)\n') % size)
986 yield self._manifestsend
986 yield self._manifestsend
987
987
988 mfdicts = None
988 mfdicts = None
989 if self._ellipses and self._isshallow:
989 if self._ellipses and self._isshallow:
990 mfdicts = [
990 mfdicts = [
991 (self._repo.manifestlog[n].read(), lr)
991 (self._repo.manifestlog[n].read(), lr)
992 for (n, lr) in pycompat.iteritems(manifests)
992 for (n, lr) in pycompat.iteritems(manifests)
993 ]
993 ]
994
994
995 manifests.clear()
995 manifests.clear()
996 clrevs = set(cl.rev(x) for x in clnodes)
996 clrevs = set(cl.rev(x) for x in clnodes)
997
997
998 it = self.generatefiles(
998 it = self.generatefiles(
999 changedfiles,
999 changedfiles,
1000 commonrevs,
1000 commonrevs,
1001 source,
1001 source,
1002 mfdicts,
1002 mfdicts,
1003 fastpathlinkrev,
1003 fastpathlinkrev,
1004 fnodes,
1004 fnodes,
1005 clrevs,
1005 clrevs,
1006 )
1006 )
1007
1007
1008 for path, deltas in it:
1008 for path, deltas in it:
1009 h = _fileheader(path)
1009 h = _fileheader(path)
1010 size = len(h)
1010 size = len(h)
1011 yield h
1011 yield h
1012
1012
1013 for delta in deltas:
1013 for delta in deltas:
1014 chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
1014 chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
1015 for chunk in chunks:
1015 for chunk in chunks:
1016 size += len(chunk)
1016 size += len(chunk)
1017 yield chunk
1017 yield chunk
1018
1018
1019 close = closechunk()
1019 close = closechunk()
1020 size += len(close)
1020 size += len(close)
1021 yield close
1021 yield close
1022
1022
1023 self._verbosenote(_(b'%8.i %s\n') % (size, path))
1023 self._verbosenote(_(b'%8.i %s\n') % (size, path))
1024
1024
1025 yield closechunk()
1025 yield closechunk()
1026
1026
1027 if clnodes:
1027 if clnodes:
1028 repo.hook(b'outgoing', node=hex(clnodes[0]), source=source)
1028 repo.hook(b'outgoing', node=hex(clnodes[0]), source=source)
1029
1029
1030 def _generatechangelog(self, cl, nodes, generate=True):
1030 def _generatechangelog(self, cl, nodes, generate=True):
1031 """Generate data for changelog chunks.
1031 """Generate data for changelog chunks.
1032
1032
1033 Returns a 2-tuple of a dict containing state and an iterable of
1033 Returns a 2-tuple of a dict containing state and an iterable of
1034 byte chunks. The state will not be fully populated until the
1034 byte chunks. The state will not be fully populated until the
1035 chunk stream has been fully consumed.
1035 chunk stream has been fully consumed.
1036
1036
1037 if generate is False, the state will be fully populated and no chunk
1037 if generate is False, the state will be fully populated and no chunk
1038 stream will be yielded
1038 stream will be yielded
1039 """
1039 """
1040 clrevorder = {}
1040 clrevorder = {}
1041 manifests = {}
1041 manifests = {}
1042 mfl = self._repo.manifestlog
1042 mfl = self._repo.manifestlog
1043 changedfiles = set()
1043 changedfiles = set()
1044 clrevtomanifestrev = {}
1044 clrevtomanifestrev = {}
1045
1045
1046 state = {
1046 state = {
1047 b'clrevorder': clrevorder,
1047 b'clrevorder': clrevorder,
1048 b'manifests': manifests,
1048 b'manifests': manifests,
1049 b'changedfiles': changedfiles,
1049 b'changedfiles': changedfiles,
1050 b'clrevtomanifestrev': clrevtomanifestrev,
1050 b'clrevtomanifestrev': clrevtomanifestrev,
1051 }
1051 }
1052
1052
1053 if not (generate or self._ellipses):
1053 if not (generate or self._ellipses):
1054 # sort the nodes in storage order
1054 # sort the nodes in storage order
1055 nodes = sorted(nodes, key=cl.rev)
1055 nodes = sorted(nodes, key=cl.rev)
1056 for node in nodes:
1056 for node in nodes:
1057 c = cl.changelogrevision(node)
1057 c = cl.changelogrevision(node)
1058 clrevorder[node] = len(clrevorder)
1058 clrevorder[node] = len(clrevorder)
1059 # record the first changeset introducing this manifest version
1059 # record the first changeset introducing this manifest version
1060 manifests.setdefault(c.manifest, node)
1060 manifests.setdefault(c.manifest, node)
1061 # Record a complete list of potentially-changed files in
1061 # Record a complete list of potentially-changed files in
1062 # this manifest.
1062 # this manifest.
1063 changedfiles.update(c.files)
1063 changedfiles.update(c.files)
1064
1064
1065 return state, ()
1065 return state, ()
1066
1066
1067 # Callback for the changelog, used to collect changed files and
1067 # Callback for the changelog, used to collect changed files and
1068 # manifest nodes.
1068 # manifest nodes.
1069 # Returns the linkrev node (identity in the changelog case).
1069 # Returns the linkrev node (identity in the changelog case).
1070 def lookupcl(x):
1070 def lookupcl(x):
1071 c = cl.changelogrevision(x)
1071 c = cl.changelogrevision(x)
1072 clrevorder[x] = len(clrevorder)
1072 clrevorder[x] = len(clrevorder)
1073
1073
1074 if self._ellipses:
1074 if self._ellipses:
1075 # Only update manifests if x is going to be sent. Otherwise we
1075 # Only update manifests if x is going to be sent. Otherwise we
1076 # end up with bogus linkrevs specified for manifests and
1076 # end up with bogus linkrevs specified for manifests and
1077 # we skip some manifest nodes that we should otherwise
1077 # we skip some manifest nodes that we should otherwise
1078 # have sent.
1078 # have sent.
1079 if (
1079 if (
1080 x in self._fullclnodes
1080 x in self._fullclnodes
1081 or cl.rev(x) in self._precomputedellipsis
1081 or cl.rev(x) in self._precomputedellipsis
1082 ):
1082 ):
1083
1083
1084 manifestnode = c.manifest
1084 manifestnode = c.manifest
1085 # Record the first changeset introducing this manifest
1085 # Record the first changeset introducing this manifest
1086 # version.
1086 # version.
1087 manifests.setdefault(manifestnode, x)
1087 manifests.setdefault(manifestnode, x)
1088 # Set this narrow-specific dict so we have the lowest
1088 # Set this narrow-specific dict so we have the lowest
1089 # manifest revnum to look up for this cl revnum. (Part of
1089 # manifest revnum to look up for this cl revnum. (Part of
1090 # mapping changelog ellipsis parents to manifest ellipsis
1090 # mapping changelog ellipsis parents to manifest ellipsis
1091 # parents)
1091 # parents)
1092 clrevtomanifestrev.setdefault(
1092 clrevtomanifestrev.setdefault(
1093 cl.rev(x), mfl.rev(manifestnode)
1093 cl.rev(x), mfl.rev(manifestnode)
1094 )
1094 )
1095 # We can't trust the changed files list in the changeset if the
1095 # We can't trust the changed files list in the changeset if the
1096 # client requested a shallow clone.
1096 # client requested a shallow clone.
1097 if self._isshallow:
1097 if self._isshallow:
1098 changedfiles.update(mfl[c.manifest].read().keys())
1098 changedfiles.update(mfl[c.manifest].read().keys())
1099 else:
1099 else:
1100 changedfiles.update(c.files)
1100 changedfiles.update(c.files)
1101 else:
1101 else:
1102 # record the first changeset introducing this manifest version
1102 # record the first changeset introducing this manifest version
1103 manifests.setdefault(c.manifest, x)
1103 manifests.setdefault(c.manifest, x)
1104 # Record a complete list of potentially-changed files in
1104 # Record a complete list of potentially-changed files in
1105 # this manifest.
1105 # this manifest.
1106 changedfiles.update(c.files)
1106 changedfiles.update(c.files)
1107
1107
1108 return x
1108 return x
1109
1109
1110 gen = deltagroup(
1110 gen = deltagroup(
1111 self._repo,
1111 self._repo,
1112 cl,
1112 cl,
1113 nodes,
1113 nodes,
1114 True,
1114 True,
1115 lookupcl,
1115 lookupcl,
1116 self._forcedeltaparentprev,
1116 self._forcedeltaparentprev,
1117 ellipses=self._ellipses,
1117 ellipses=self._ellipses,
1118 topic=_(b'changesets'),
1118 topic=_(b'changesets'),
1119 clrevtolocalrev={},
1119 clrevtolocalrev={},
1120 fullclnodes=self._fullclnodes,
1120 fullclnodes=self._fullclnodes,
1121 precomputedellipsis=self._precomputedellipsis,
1121 precomputedellipsis=self._precomputedellipsis,
1122 )
1122 )
1123
1123
1124 return state, gen
1124 return state, gen
1125
1125
1126 def generatemanifests(
1126 def generatemanifests(
1127 self,
1127 self,
1128 commonrevs,
1128 commonrevs,
1129 clrevorder,
1129 clrevorder,
1130 fastpathlinkrev,
1130 fastpathlinkrev,
1131 manifests,
1131 manifests,
1132 fnodes,
1132 fnodes,
1133 source,
1133 source,
1134 clrevtolocalrev,
1134 clrevtolocalrev,
1135 ):
1135 ):
1136 """Returns an iterator of changegroup chunks containing manifests.
1136 """Returns an iterator of changegroup chunks containing manifests.
1137
1137
1138 `source` is unused here, but is used by extensions like remotefilelog to
1138 `source` is unused here, but is used by extensions like remotefilelog to
1139 change what is sent based in pulls vs pushes, etc.
1139 change what is sent based in pulls vs pushes, etc.
1140 """
1140 """
1141 repo = self._repo
1141 repo = self._repo
1142 mfl = repo.manifestlog
1142 mfl = repo.manifestlog
1143 tmfnodes = {b'': manifests}
1143 tmfnodes = {b'': manifests}
1144
1144
1145 # Callback for the manifest, used to collect linkrevs for filelog
1145 # Callback for the manifest, used to collect linkrevs for filelog
1146 # revisions.
1146 # revisions.
1147 # Returns the linkrev node (collected in lookupcl).
1147 # Returns the linkrev node (collected in lookupcl).
1148 def makelookupmflinknode(tree, nodes):
1148 def makelookupmflinknode(tree, nodes):
1149 if fastpathlinkrev:
1149 if fastpathlinkrev:
1150 assert not tree
1150 assert not tree
1151 return (
1151 return (
1152 manifests.__getitem__ # pytype: disable=unsupported-operands
1152 manifests.__getitem__
1153 )
1153 ) # pytype: disable=unsupported-operands
1154
1154
1155 def lookupmflinknode(x):
1155 def lookupmflinknode(x):
1156 """Callback for looking up the linknode for manifests.
1156 """Callback for looking up the linknode for manifests.
1157
1157
1158 Returns the linkrev node for the specified manifest.
1158 Returns the linkrev node for the specified manifest.
1159
1159
1160 SIDE EFFECT:
1160 SIDE EFFECT:
1161
1161
1162 1) fclnodes gets populated with the list of relevant
1162 1) fclnodes gets populated with the list of relevant
1163 file nodes if we're not using fastpathlinkrev
1163 file nodes if we're not using fastpathlinkrev
1164 2) When treemanifests are in use, collects treemanifest nodes
1164 2) When treemanifests are in use, collects treemanifest nodes
1165 to send
1165 to send
1166
1166
1167 Note that this means manifests must be completely sent to
1167 Note that this means manifests must be completely sent to
1168 the client before you can trust the list of files and
1168 the client before you can trust the list of files and
1169 treemanifests to send.
1169 treemanifests to send.
1170 """
1170 """
1171 clnode = nodes[x]
1171 clnode = nodes[x]
1172 mdata = mfl.get(tree, x).readfast(shallow=True)
1172 mdata = mfl.get(tree, x).readfast(shallow=True)
1173 for p, n, fl in mdata.iterentries():
1173 for p, n, fl in mdata.iterentries():
1174 if fl == b't': # subdirectory manifest
1174 if fl == b't': # subdirectory manifest
1175 subtree = tree + p + b'/'
1175 subtree = tree + p + b'/'
1176 tmfclnodes = tmfnodes.setdefault(subtree, {})
1176 tmfclnodes = tmfnodes.setdefault(subtree, {})
1177 tmfclnode = tmfclnodes.setdefault(n, clnode)
1177 tmfclnode = tmfclnodes.setdefault(n, clnode)
1178 if clrevorder[clnode] < clrevorder[tmfclnode]:
1178 if clrevorder[clnode] < clrevorder[tmfclnode]:
1179 tmfclnodes[n] = clnode
1179 tmfclnodes[n] = clnode
1180 else:
1180 else:
1181 f = tree + p
1181 f = tree + p
1182 fclnodes = fnodes.setdefault(f, {})
1182 fclnodes = fnodes.setdefault(f, {})
1183 fclnode = fclnodes.setdefault(n, clnode)
1183 fclnode = fclnodes.setdefault(n, clnode)
1184 if clrevorder[clnode] < clrevorder[fclnode]:
1184 if clrevorder[clnode] < clrevorder[fclnode]:
1185 fclnodes[n] = clnode
1185 fclnodes[n] = clnode
1186 return clnode
1186 return clnode
1187
1187
1188 return lookupmflinknode
1188 return lookupmflinknode
1189
1189
1190 while tmfnodes:
1190 while tmfnodes:
1191 tree, nodes = tmfnodes.popitem()
1191 tree, nodes = tmfnodes.popitem()
1192
1192
1193 should_visit = self._matcher.visitdir(tree[:-1])
1193 should_visit = self._matcher.visitdir(tree[:-1])
1194 if tree and not should_visit:
1194 if tree and not should_visit:
1195 continue
1195 continue
1196
1196
1197 store = mfl.getstorage(tree)
1197 store = mfl.getstorage(tree)
1198
1198
1199 if not should_visit:
1199 if not should_visit:
1200 # No nodes to send because this directory is out of
1200 # No nodes to send because this directory is out of
1201 # the client's view of the repository (probably
1201 # the client's view of the repository (probably
1202 # because of narrow clones). Do this even for the root
1202 # because of narrow clones). Do this even for the root
1203 # directory (tree=='')
1203 # directory (tree=='')
1204 prunednodes = []
1204 prunednodes = []
1205 else:
1205 else:
1206 # Avoid sending any manifest nodes we can prove the
1206 # Avoid sending any manifest nodes we can prove the
1207 # client already has by checking linkrevs. See the
1207 # client already has by checking linkrevs. See the
1208 # related comment in generatefiles().
1208 # related comment in generatefiles().
1209 prunednodes = self._prunemanifests(store, nodes, commonrevs)
1209 prunednodes = self._prunemanifests(store, nodes, commonrevs)
1210
1210
1211 if tree and not prunednodes:
1211 if tree and not prunednodes:
1212 continue
1212 continue
1213
1213
1214 lookupfn = makelookupmflinknode(tree, nodes)
1214 lookupfn = makelookupmflinknode(tree, nodes)
1215
1215
1216 deltas = deltagroup(
1216 deltas = deltagroup(
1217 self._repo,
1217 self._repo,
1218 store,
1218 store,
1219 prunednodes,
1219 prunednodes,
1220 False,
1220 False,
1221 lookupfn,
1221 lookupfn,
1222 self._forcedeltaparentprev,
1222 self._forcedeltaparentprev,
1223 ellipses=self._ellipses,
1223 ellipses=self._ellipses,
1224 topic=_(b'manifests'),
1224 topic=_(b'manifests'),
1225 clrevtolocalrev=clrevtolocalrev,
1225 clrevtolocalrev=clrevtolocalrev,
1226 fullclnodes=self._fullclnodes,
1226 fullclnodes=self._fullclnodes,
1227 precomputedellipsis=self._precomputedellipsis,
1227 precomputedellipsis=self._precomputedellipsis,
1228 )
1228 )
1229
1229
1230 if not self._oldmatcher.visitdir(store.tree[:-1]):
1230 if not self._oldmatcher.visitdir(store.tree[:-1]):
1231 yield tree, deltas
1231 yield tree, deltas
1232 else:
1232 else:
1233 # 'deltas' is a generator and we need to consume it even if
1233 # 'deltas' is a generator and we need to consume it even if
1234 # we are not going to send it because a side-effect is that
1234 # we are not going to send it because a side-effect is that
1235 # it updates tmdnodes (via lookupfn)
1235 # it updates tmdnodes (via lookupfn)
1236 for d in deltas:
1236 for d in deltas:
1237 pass
1237 pass
1238 if not tree:
1238 if not tree:
1239 yield tree, []
1239 yield tree, []
1240
1240
1241 def _prunemanifests(self, store, nodes, commonrevs):
1241 def _prunemanifests(self, store, nodes, commonrevs):
1242 if not self._ellipses:
1242 if not self._ellipses:
1243 # In non-ellipses case and large repositories, it is better to
1243 # In non-ellipses case and large repositories, it is better to
1244 # prevent calling of store.rev and store.linkrev on a lot of
1244 # prevent calling of store.rev and store.linkrev on a lot of
1245 # nodes as compared to sending some extra data
1245 # nodes as compared to sending some extra data
1246 return nodes.copy()
1246 return nodes.copy()
1247 # This is split out as a separate method to allow filtering
1247 # This is split out as a separate method to allow filtering
1248 # commonrevs in extension code.
1248 # commonrevs in extension code.
1249 #
1249 #
1250 # TODO(augie): this shouldn't be required, instead we should
1250 # TODO(augie): this shouldn't be required, instead we should
1251 # make filtering of revisions to send delegated to the store
1251 # make filtering of revisions to send delegated to the store
1252 # layer.
1252 # layer.
1253 frev, flr = store.rev, store.linkrev
1253 frev, flr = store.rev, store.linkrev
1254 return [n for n in nodes if flr(frev(n)) not in commonrevs]
1254 return [n for n in nodes if flr(frev(n)) not in commonrevs]
1255
1255
1256 # The 'source' parameter is useful for extensions
1256 # The 'source' parameter is useful for extensions
1257 def generatefiles(
1257 def generatefiles(
1258 self,
1258 self,
1259 changedfiles,
1259 changedfiles,
1260 commonrevs,
1260 commonrevs,
1261 source,
1261 source,
1262 mfdicts,
1262 mfdicts,
1263 fastpathlinkrev,
1263 fastpathlinkrev,
1264 fnodes,
1264 fnodes,
1265 clrevs,
1265 clrevs,
1266 ):
1266 ):
1267 changedfiles = [
1267 changedfiles = [
1268 f
1268 f
1269 for f in changedfiles
1269 for f in changedfiles
1270 if self._matcher(f) and not self._oldmatcher(f)
1270 if self._matcher(f) and not self._oldmatcher(f)
1271 ]
1271 ]
1272
1272
1273 if not fastpathlinkrev:
1273 if not fastpathlinkrev:
1274
1274
1275 def normallinknodes(unused, fname):
1275 def normallinknodes(unused, fname):
1276 return fnodes.get(fname, {})
1276 return fnodes.get(fname, {})
1277
1277
1278 else:
1278 else:
1279 cln = self._repo.changelog.node
1279 cln = self._repo.changelog.node
1280
1280
1281 def normallinknodes(store, fname):
1281 def normallinknodes(store, fname):
1282 flinkrev = store.linkrev
1282 flinkrev = store.linkrev
1283 fnode = store.node
1283 fnode = store.node
1284 revs = ((r, flinkrev(r)) for r in store)
1284 revs = ((r, flinkrev(r)) for r in store)
1285 return dict(
1285 return dict(
1286 (fnode(r), cln(lr)) for r, lr in revs if lr in clrevs
1286 (fnode(r), cln(lr)) for r, lr in revs if lr in clrevs
1287 )
1287 )
1288
1288
1289 clrevtolocalrev = {}
1289 clrevtolocalrev = {}
1290
1290
1291 if self._isshallow:
1291 if self._isshallow:
1292 # In a shallow clone, the linknodes callback needs to also include
1292 # In a shallow clone, the linknodes callback needs to also include
1293 # those file nodes that are in the manifests we sent but weren't
1293 # those file nodes that are in the manifests we sent but weren't
1294 # introduced by those manifests.
1294 # introduced by those manifests.
1295 commonctxs = [self._repo[c] for c in commonrevs]
1295 commonctxs = [self._repo[c] for c in commonrevs]
1296 clrev = self._repo.changelog.rev
1296 clrev = self._repo.changelog.rev
1297
1297
1298 def linknodes(flog, fname):
1298 def linknodes(flog, fname):
1299 for c in commonctxs:
1299 for c in commonctxs:
1300 try:
1300 try:
1301 fnode = c.filenode(fname)
1301 fnode = c.filenode(fname)
1302 clrevtolocalrev[c.rev()] = flog.rev(fnode)
1302 clrevtolocalrev[c.rev()] = flog.rev(fnode)
1303 except error.ManifestLookupError:
1303 except error.ManifestLookupError:
1304 pass
1304 pass
1305 links = normallinknodes(flog, fname)
1305 links = normallinknodes(flog, fname)
1306 if len(links) != len(mfdicts):
1306 if len(links) != len(mfdicts):
1307 for mf, lr in mfdicts:
1307 for mf, lr in mfdicts:
1308 fnode = mf.get(fname, None)
1308 fnode = mf.get(fname, None)
1309 if fnode in links:
1309 if fnode in links:
1310 links[fnode] = min(links[fnode], lr, key=clrev)
1310 links[fnode] = min(links[fnode], lr, key=clrev)
1311 elif fnode:
1311 elif fnode:
1312 links[fnode] = lr
1312 links[fnode] = lr
1313 return links
1313 return links
1314
1314
1315 else:
1315 else:
1316 linknodes = normallinknodes
1316 linknodes = normallinknodes
1317
1317
1318 repo = self._repo
1318 repo = self._repo
1319 progress = repo.ui.makeprogress(
1319 progress = repo.ui.makeprogress(
1320 _(b'files'), unit=_(b'files'), total=len(changedfiles)
1320 _(b'files'), unit=_(b'files'), total=len(changedfiles)
1321 )
1321 )
1322 for i, fname in enumerate(sorted(changedfiles)):
1322 for i, fname in enumerate(sorted(changedfiles)):
1323 filerevlog = repo.file(fname)
1323 filerevlog = repo.file(fname)
1324 if not filerevlog:
1324 if not filerevlog:
1325 raise error.Abort(
1325 raise error.Abort(
1326 _(b"empty or missing file data for %s") % fname
1326 _(b"empty or missing file data for %s") % fname
1327 )
1327 )
1328
1328
1329 clrevtolocalrev.clear()
1329 clrevtolocalrev.clear()
1330
1330
1331 linkrevnodes = linknodes(filerevlog, fname)
1331 linkrevnodes = linknodes(filerevlog, fname)
1332 # Lookup for filenodes, we collected the linkrev nodes above in the
1332 # Lookup for filenodes, we collected the linkrev nodes above in the
1333 # fastpath case and with lookupmf in the slowpath case.
1333 # fastpath case and with lookupmf in the slowpath case.
1334 def lookupfilelog(x):
1334 def lookupfilelog(x):
1335 return linkrevnodes[x]
1335 return linkrevnodes[x]
1336
1336
1337 frev, flr = filerevlog.rev, filerevlog.linkrev
1337 frev, flr = filerevlog.rev, filerevlog.linkrev
1338 # Skip sending any filenode we know the client already
1338 # Skip sending any filenode we know the client already
1339 # has. This avoids over-sending files relatively
1339 # has. This avoids over-sending files relatively
1340 # inexpensively, so it's not a problem if we under-filter
1340 # inexpensively, so it's not a problem if we under-filter
1341 # here.
1341 # here.
1342 filenodes = [
1342 filenodes = [
1343 n for n in linkrevnodes if flr(frev(n)) not in commonrevs
1343 n for n in linkrevnodes if flr(frev(n)) not in commonrevs
1344 ]
1344 ]
1345
1345
1346 if not filenodes:
1346 if not filenodes:
1347 continue
1347 continue
1348
1348
1349 progress.update(i + 1, item=fname)
1349 progress.update(i + 1, item=fname)
1350
1350
1351 deltas = deltagroup(
1351 deltas = deltagroup(
1352 self._repo,
1352 self._repo,
1353 filerevlog,
1353 filerevlog,
1354 filenodes,
1354 filenodes,
1355 False,
1355 False,
1356 lookupfilelog,
1356 lookupfilelog,
1357 self._forcedeltaparentprev,
1357 self._forcedeltaparentprev,
1358 ellipses=self._ellipses,
1358 ellipses=self._ellipses,
1359 clrevtolocalrev=clrevtolocalrev,
1359 clrevtolocalrev=clrevtolocalrev,
1360 fullclnodes=self._fullclnodes,
1360 fullclnodes=self._fullclnodes,
1361 precomputedellipsis=self._precomputedellipsis,
1361 precomputedellipsis=self._precomputedellipsis,
1362 )
1362 )
1363
1363
1364 yield fname, deltas
1364 yield fname, deltas
1365
1365
1366 progress.complete()
1366 progress.complete()
1367
1367
1368
1368
1369 def _makecg1packer(
1369 def _makecg1packer(
1370 repo,
1370 repo,
1371 oldmatcher,
1371 oldmatcher,
1372 matcher,
1372 matcher,
1373 bundlecaps,
1373 bundlecaps,
1374 ellipses=False,
1374 ellipses=False,
1375 shallow=False,
1375 shallow=False,
1376 ellipsisroots=None,
1376 ellipsisroots=None,
1377 fullnodes=None,
1377 fullnodes=None,
1378 ):
1378 ):
1379 builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack(
1379 builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack(
1380 d.node, d.p1node, d.p2node, d.linknode
1380 d.node, d.p1node, d.p2node, d.linknode
1381 )
1381 )
1382
1382
1383 return cgpacker(
1383 return cgpacker(
1384 repo,
1384 repo,
1385 oldmatcher,
1385 oldmatcher,
1386 matcher,
1386 matcher,
1387 b'01',
1387 b'01',
1388 builddeltaheader=builddeltaheader,
1388 builddeltaheader=builddeltaheader,
1389 manifestsend=b'',
1389 manifestsend=b'',
1390 forcedeltaparentprev=True,
1390 forcedeltaparentprev=True,
1391 bundlecaps=bundlecaps,
1391 bundlecaps=bundlecaps,
1392 ellipses=ellipses,
1392 ellipses=ellipses,
1393 shallow=shallow,
1393 shallow=shallow,
1394 ellipsisroots=ellipsisroots,
1394 ellipsisroots=ellipsisroots,
1395 fullnodes=fullnodes,
1395 fullnodes=fullnodes,
1396 )
1396 )
1397
1397
1398
1398
1399 def _makecg2packer(
1399 def _makecg2packer(
1400 repo,
1400 repo,
1401 oldmatcher,
1401 oldmatcher,
1402 matcher,
1402 matcher,
1403 bundlecaps,
1403 bundlecaps,
1404 ellipses=False,
1404 ellipses=False,
1405 shallow=False,
1405 shallow=False,
1406 ellipsisroots=None,
1406 ellipsisroots=None,
1407 fullnodes=None,
1407 fullnodes=None,
1408 ):
1408 ):
1409 builddeltaheader = lambda d: _CHANGEGROUPV2_DELTA_HEADER.pack(
1409 builddeltaheader = lambda d: _CHANGEGROUPV2_DELTA_HEADER.pack(
1410 d.node, d.p1node, d.p2node, d.basenode, d.linknode
1410 d.node, d.p1node, d.p2node, d.basenode, d.linknode
1411 )
1411 )
1412
1412
1413 return cgpacker(
1413 return cgpacker(
1414 repo,
1414 repo,
1415 oldmatcher,
1415 oldmatcher,
1416 matcher,
1416 matcher,
1417 b'02',
1417 b'02',
1418 builddeltaheader=builddeltaheader,
1418 builddeltaheader=builddeltaheader,
1419 manifestsend=b'',
1419 manifestsend=b'',
1420 bundlecaps=bundlecaps,
1420 bundlecaps=bundlecaps,
1421 ellipses=ellipses,
1421 ellipses=ellipses,
1422 shallow=shallow,
1422 shallow=shallow,
1423 ellipsisroots=ellipsisroots,
1423 ellipsisroots=ellipsisroots,
1424 fullnodes=fullnodes,
1424 fullnodes=fullnodes,
1425 )
1425 )
1426
1426
1427
1427
1428 def _makecg3packer(
1428 def _makecg3packer(
1429 repo,
1429 repo,
1430 oldmatcher,
1430 oldmatcher,
1431 matcher,
1431 matcher,
1432 bundlecaps,
1432 bundlecaps,
1433 ellipses=False,
1433 ellipses=False,
1434 shallow=False,
1434 shallow=False,
1435 ellipsisroots=None,
1435 ellipsisroots=None,
1436 fullnodes=None,
1436 fullnodes=None,
1437 ):
1437 ):
1438 builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
1438 builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
1439 d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags
1439 d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags
1440 )
1440 )
1441
1441
1442 return cgpacker(
1442 return cgpacker(
1443 repo,
1443 repo,
1444 oldmatcher,
1444 oldmatcher,
1445 matcher,
1445 matcher,
1446 b'03',
1446 b'03',
1447 builddeltaheader=builddeltaheader,
1447 builddeltaheader=builddeltaheader,
1448 manifestsend=closechunk(),
1448 manifestsend=closechunk(),
1449 bundlecaps=bundlecaps,
1449 bundlecaps=bundlecaps,
1450 ellipses=ellipses,
1450 ellipses=ellipses,
1451 shallow=shallow,
1451 shallow=shallow,
1452 ellipsisroots=ellipsisroots,
1452 ellipsisroots=ellipsisroots,
1453 fullnodes=fullnodes,
1453 fullnodes=fullnodes,
1454 )
1454 )
1455
1455
1456
1456
1457 _packermap = {
1457 _packermap = {
1458 b'01': (_makecg1packer, cg1unpacker),
1458 b'01': (_makecg1packer, cg1unpacker),
1459 # cg2 adds support for exchanging generaldelta
1459 # cg2 adds support for exchanging generaldelta
1460 b'02': (_makecg2packer, cg2unpacker),
1460 b'02': (_makecg2packer, cg2unpacker),
1461 # cg3 adds support for exchanging revlog flags and treemanifests
1461 # cg3 adds support for exchanging revlog flags and treemanifests
1462 b'03': (_makecg3packer, cg3unpacker),
1462 b'03': (_makecg3packer, cg3unpacker),
1463 }
1463 }
1464
1464
1465
1465
1466 def allsupportedversions(repo):
1466 def allsupportedversions(repo):
1467 versions = set(_packermap.keys())
1467 versions = set(_packermap.keys())
1468 needv03 = False
1468 needv03 = False
1469 if (
1469 if (
1470 repo.ui.configbool(b'experimental', b'changegroup3')
1470 repo.ui.configbool(b'experimental', b'changegroup3')
1471 or repo.ui.configbool(b'experimental', b'treemanifest')
1471 or repo.ui.configbool(b'experimental', b'treemanifest')
1472 or b'treemanifest' in repo.requirements
1472 or b'treemanifest' in repo.requirements
1473 ):
1473 ):
1474 # we keep version 03 because we need to to exchange treemanifest data
1474 # we keep version 03 because we need to to exchange treemanifest data
1475 #
1475 #
1476 # we also keep vresion 01 and 02, because it is possible for repo to
1476 # we also keep vresion 01 and 02, because it is possible for repo to
1477 # contains both normal and tree manifest at the same time. so using
1477 # contains both normal and tree manifest at the same time. so using
1478 # older version to pull data is viable
1478 # older version to pull data is viable
1479 #
1479 #
1480 # (or even to push subset of history)
1480 # (or even to push subset of history)
1481 needv03 = True
1481 needv03 = True
1482 if b'exp-sidedata-flag' in repo.requirements:
1482 if b'exp-sidedata-flag' in repo.requirements:
1483 needv03 = True
1483 needv03 = True
1484 # don't attempt to use 01/02 until we do sidedata cleaning
1484 # don't attempt to use 01/02 until we do sidedata cleaning
1485 versions.discard(b'01')
1485 versions.discard(b'01')
1486 versions.discard(b'02')
1486 versions.discard(b'02')
1487 if not needv03:
1487 if not needv03:
1488 versions.discard(b'03')
1488 versions.discard(b'03')
1489 return versions
1489 return versions
1490
1490
1491
1491
1492 # Changegroup versions that can be applied to the repo
1492 # Changegroup versions that can be applied to the repo
1493 def supportedincomingversions(repo):
1493 def supportedincomingversions(repo):
1494 return allsupportedversions(repo)
1494 return allsupportedversions(repo)
1495
1495
1496
1496
1497 # Changegroup versions that can be created from the repo
1497 # Changegroup versions that can be created from the repo
1498 def supportedoutgoingversions(repo):
1498 def supportedoutgoingversions(repo):
1499 versions = allsupportedversions(repo)
1499 versions = allsupportedversions(repo)
1500 if b'treemanifest' in repo.requirements:
1500 if b'treemanifest' in repo.requirements:
1501 # Versions 01 and 02 support only flat manifests and it's just too
1501 # Versions 01 and 02 support only flat manifests and it's just too
1502 # expensive to convert between the flat manifest and tree manifest on
1502 # expensive to convert between the flat manifest and tree manifest on
1503 # the fly. Since tree manifests are hashed differently, all of history
1503 # the fly. Since tree manifests are hashed differently, all of history
1504 # would have to be converted. Instead, we simply don't even pretend to
1504 # would have to be converted. Instead, we simply don't even pretend to
1505 # support versions 01 and 02.
1505 # support versions 01 and 02.
1506 versions.discard(b'01')
1506 versions.discard(b'01')
1507 versions.discard(b'02')
1507 versions.discard(b'02')
1508 if repository.NARROW_REQUIREMENT in repo.requirements:
1508 if repository.NARROW_REQUIREMENT in repo.requirements:
1509 # Versions 01 and 02 don't support revlog flags, and we need to
1509 # Versions 01 and 02 don't support revlog flags, and we need to
1510 # support that for stripping and unbundling to work.
1510 # support that for stripping and unbundling to work.
1511 versions.discard(b'01')
1511 versions.discard(b'01')
1512 versions.discard(b'02')
1512 versions.discard(b'02')
1513 if LFS_REQUIREMENT in repo.requirements:
1513 if LFS_REQUIREMENT in repo.requirements:
1514 # Versions 01 and 02 don't support revlog flags, and we need to
1514 # Versions 01 and 02 don't support revlog flags, and we need to
1515 # mark LFS entries with REVIDX_EXTSTORED.
1515 # mark LFS entries with REVIDX_EXTSTORED.
1516 versions.discard(b'01')
1516 versions.discard(b'01')
1517 versions.discard(b'02')
1517 versions.discard(b'02')
1518
1518
1519 return versions
1519 return versions
1520
1520
1521
1521
1522 def localversion(repo):
1522 def localversion(repo):
1523 # Finds the best version to use for bundles that are meant to be used
1523 # Finds the best version to use for bundles that are meant to be used
1524 # locally, such as those from strip and shelve, and temporary bundles.
1524 # locally, such as those from strip and shelve, and temporary bundles.
1525 return max(supportedoutgoingversions(repo))
1525 return max(supportedoutgoingversions(repo))
1526
1526
1527
1527
1528 def safeversion(repo):
1528 def safeversion(repo):
1529 # Finds the smallest version that it's safe to assume clients of the repo
1529 # Finds the smallest version that it's safe to assume clients of the repo
1530 # will support. For example, all hg versions that support generaldelta also
1530 # will support. For example, all hg versions that support generaldelta also
1531 # support changegroup 02.
1531 # support changegroup 02.
1532 versions = supportedoutgoingversions(repo)
1532 versions = supportedoutgoingversions(repo)
1533 if b'generaldelta' in repo.requirements:
1533 if b'generaldelta' in repo.requirements:
1534 versions.discard(b'01')
1534 versions.discard(b'01')
1535 assert versions
1535 assert versions
1536 return min(versions)
1536 return min(versions)
1537
1537
1538
1538
1539 def getbundler(
1539 def getbundler(
1540 version,
1540 version,
1541 repo,
1541 repo,
1542 bundlecaps=None,
1542 bundlecaps=None,
1543 oldmatcher=None,
1543 oldmatcher=None,
1544 matcher=None,
1544 matcher=None,
1545 ellipses=False,
1545 ellipses=False,
1546 shallow=False,
1546 shallow=False,
1547 ellipsisroots=None,
1547 ellipsisroots=None,
1548 fullnodes=None,
1548 fullnodes=None,
1549 ):
1549 ):
1550 assert version in supportedoutgoingversions(repo)
1550 assert version in supportedoutgoingversions(repo)
1551
1551
1552 if matcher is None:
1552 if matcher is None:
1553 matcher = matchmod.always()
1553 matcher = matchmod.always()
1554 if oldmatcher is None:
1554 if oldmatcher is None:
1555 oldmatcher = matchmod.never()
1555 oldmatcher = matchmod.never()
1556
1556
1557 if version == b'01' and not matcher.always():
1557 if version == b'01' and not matcher.always():
1558 raise error.ProgrammingError(
1558 raise error.ProgrammingError(
1559 b'version 01 changegroups do not support sparse file matchers'
1559 b'version 01 changegroups do not support sparse file matchers'
1560 )
1560 )
1561
1561
1562 if ellipses and version in (b'01', b'02'):
1562 if ellipses and version in (b'01', b'02'):
1563 raise error.Abort(
1563 raise error.Abort(
1564 _(
1564 _(
1565 b'ellipsis nodes require at least cg3 on client and server, '
1565 b'ellipsis nodes require at least cg3 on client and server, '
1566 b'but negotiated version %s'
1566 b'but negotiated version %s'
1567 )
1567 )
1568 % version
1568 % version
1569 )
1569 )
1570
1570
1571 # Requested files could include files not in the local store. So
1571 # Requested files could include files not in the local store. So
1572 # filter those out.
1572 # filter those out.
1573 matcher = repo.narrowmatch(matcher)
1573 matcher = repo.narrowmatch(matcher)
1574
1574
1575 fn = _packermap[version][0]
1575 fn = _packermap[version][0]
1576 return fn(
1576 return fn(
1577 repo,
1577 repo,
1578 oldmatcher,
1578 oldmatcher,
1579 matcher,
1579 matcher,
1580 bundlecaps,
1580 bundlecaps,
1581 ellipses=ellipses,
1581 ellipses=ellipses,
1582 shallow=shallow,
1582 shallow=shallow,
1583 ellipsisroots=ellipsisroots,
1583 ellipsisroots=ellipsisroots,
1584 fullnodes=fullnodes,
1584 fullnodes=fullnodes,
1585 )
1585 )
1586
1586
1587
1587
1588 def getunbundler(version, fh, alg, extras=None):
1588 def getunbundler(version, fh, alg, extras=None):
1589 return _packermap[version][1](fh, alg, extras=extras)
1589 return _packermap[version][1](fh, alg, extras=extras)
1590
1590
1591
1591
1592 def _changegroupinfo(repo, nodes, source):
1592 def _changegroupinfo(repo, nodes, source):
1593 if repo.ui.verbose or source == b'bundle':
1593 if repo.ui.verbose or source == b'bundle':
1594 repo.ui.status(_(b"%d changesets found\n") % len(nodes))
1594 repo.ui.status(_(b"%d changesets found\n") % len(nodes))
1595 if repo.ui.debugflag:
1595 if repo.ui.debugflag:
1596 repo.ui.debug(b"list of changesets:\n")
1596 repo.ui.debug(b"list of changesets:\n")
1597 for node in nodes:
1597 for node in nodes:
1598 repo.ui.debug(b"%s\n" % hex(node))
1598 repo.ui.debug(b"%s\n" % hex(node))
1599
1599
1600
1600
1601 def makechangegroup(
1601 def makechangegroup(
1602 repo, outgoing, version, source, fastpath=False, bundlecaps=None
1602 repo, outgoing, version, source, fastpath=False, bundlecaps=None
1603 ):
1603 ):
1604 cgstream = makestream(
1604 cgstream = makestream(
1605 repo,
1605 repo,
1606 outgoing,
1606 outgoing,
1607 version,
1607 version,
1608 source,
1608 source,
1609 fastpath=fastpath,
1609 fastpath=fastpath,
1610 bundlecaps=bundlecaps,
1610 bundlecaps=bundlecaps,
1611 )
1611 )
1612 return getunbundler(
1612 return getunbundler(
1613 version,
1613 version,
1614 util.chunkbuffer(cgstream),
1614 util.chunkbuffer(cgstream),
1615 None,
1615 None,
1616 {b'clcount': len(outgoing.missing)},
1616 {b'clcount': len(outgoing.missing)},
1617 )
1617 )
1618
1618
1619
1619
1620 def makestream(
1620 def makestream(
1621 repo,
1621 repo,
1622 outgoing,
1622 outgoing,
1623 version,
1623 version,
1624 source,
1624 source,
1625 fastpath=False,
1625 fastpath=False,
1626 bundlecaps=None,
1626 bundlecaps=None,
1627 matcher=None,
1627 matcher=None,
1628 ):
1628 ):
1629 bundler = getbundler(version, repo, bundlecaps=bundlecaps, matcher=matcher)
1629 bundler = getbundler(version, repo, bundlecaps=bundlecaps, matcher=matcher)
1630
1630
1631 repo = repo.unfiltered()
1631 repo = repo.unfiltered()
1632 commonrevs = outgoing.common
1632 commonrevs = outgoing.common
1633 csets = outgoing.missing
1633 csets = outgoing.missing
1634 heads = outgoing.missingheads
1634 heads = outgoing.missingheads
1635 # We go through the fast path if we get told to, or if all (unfiltered
1635 # We go through the fast path if we get told to, or if all (unfiltered
1636 # heads have been requested (since we then know there all linkrevs will
1636 # heads have been requested (since we then know there all linkrevs will
1637 # be pulled by the client).
1637 # be pulled by the client).
1638 heads.sort()
1638 heads.sort()
1639 fastpathlinkrev = fastpath or (
1639 fastpathlinkrev = fastpath or (
1640 repo.filtername is None and heads == sorted(repo.heads())
1640 repo.filtername is None and heads == sorted(repo.heads())
1641 )
1641 )
1642
1642
1643 repo.hook(b'preoutgoing', throw=True, source=source)
1643 repo.hook(b'preoutgoing', throw=True, source=source)
1644 _changegroupinfo(repo, csets, source)
1644 _changegroupinfo(repo, csets, source)
1645 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1645 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1646
1646
1647
1647
1648 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
1648 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
1649 revisions = 0
1649 revisions = 0
1650 files = 0
1650 files = 0
1651 progress = repo.ui.makeprogress(
1651 progress = repo.ui.makeprogress(
1652 _(b'files'), unit=_(b'files'), total=expectedfiles
1652 _(b'files'), unit=_(b'files'), total=expectedfiles
1653 )
1653 )
1654 for chunkdata in iter(source.filelogheader, {}):
1654 for chunkdata in iter(source.filelogheader, {}):
1655 files += 1
1655 files += 1
1656 f = chunkdata[b"filename"]
1656 f = chunkdata[b"filename"]
1657 repo.ui.debug(b"adding %s revisions\n" % f)
1657 repo.ui.debug(b"adding %s revisions\n" % f)
1658 progress.increment()
1658 progress.increment()
1659 fl = repo.file(f)
1659 fl = repo.file(f)
1660 o = len(fl)
1660 o = len(fl)
1661 try:
1661 try:
1662 deltas = source.deltaiter()
1662 deltas = source.deltaiter()
1663 if not fl.addgroup(deltas, revmap, trp):
1663 if not fl.addgroup(deltas, revmap, trp):
1664 raise error.Abort(_(b"received file revlog group is empty"))
1664 raise error.Abort(_(b"received file revlog group is empty"))
1665 except error.CensoredBaseError as e:
1665 except error.CensoredBaseError as e:
1666 raise error.Abort(_(b"received delta base is censored: %s") % e)
1666 raise error.Abort(_(b"received delta base is censored: %s") % e)
1667 revisions += len(fl) - o
1667 revisions += len(fl) - o
1668 if f in needfiles:
1668 if f in needfiles:
1669 needs = needfiles[f]
1669 needs = needfiles[f]
1670 for new in pycompat.xrange(o, len(fl)):
1670 for new in pycompat.xrange(o, len(fl)):
1671 n = fl.node(new)
1671 n = fl.node(new)
1672 if n in needs:
1672 if n in needs:
1673 needs.remove(n)
1673 needs.remove(n)
1674 else:
1674 else:
1675 raise error.Abort(_(b"received spurious file revlog entry"))
1675 raise error.Abort(_(b"received spurious file revlog entry"))
1676 if not needs:
1676 if not needs:
1677 del needfiles[f]
1677 del needfiles[f]
1678 progress.complete()
1678 progress.complete()
1679
1679
1680 for f, needs in pycompat.iteritems(needfiles):
1680 for f, needs in pycompat.iteritems(needfiles):
1681 fl = repo.file(f)
1681 fl = repo.file(f)
1682 for n in needs:
1682 for n in needs:
1683 try:
1683 try:
1684 fl.rev(n)
1684 fl.rev(n)
1685 except error.LookupError:
1685 except error.LookupError:
1686 raise error.Abort(
1686 raise error.Abort(
1687 _(b'missing file data for %s:%s - run hg verify')
1687 _(b'missing file data for %s:%s - run hg verify')
1688 % (f, hex(n))
1688 % (f, hex(n))
1689 )
1689 )
1690
1690
1691 return revisions, files
1691 return revisions, files
@@ -1,4065 +1,4072 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in 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 copy as copymod
10 import copy as copymod
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 hex,
17 hex,
18 nullid,
18 nullid,
19 nullrev,
19 nullrev,
20 short,
20 short,
21 )
21 )
22 from .pycompat import (
22 from .pycompat import (
23 getattr,
23 getattr,
24 open,
24 open,
25 setattr,
25 setattr,
26 )
26 )
27 from .thirdparty import attr
27 from .thirdparty import attr
28
28
29 from . import (
29 from . import (
30 bookmarks,
30 bookmarks,
31 changelog,
31 changelog,
32 copies,
32 copies,
33 crecord as crecordmod,
33 crecord as crecordmod,
34 dirstateguard,
34 dirstateguard,
35 encoding,
35 encoding,
36 error,
36 error,
37 formatter,
37 formatter,
38 logcmdutil,
38 logcmdutil,
39 match as matchmod,
39 match as matchmod,
40 merge as mergemod,
40 merge as mergemod,
41 mergeutil,
41 mergeutil,
42 obsolete,
42 obsolete,
43 patch,
43 patch,
44 pathutil,
44 pathutil,
45 phases,
45 phases,
46 pycompat,
46 pycompat,
47 repair,
47 repair,
48 revlog,
48 revlog,
49 rewriteutil,
49 rewriteutil,
50 scmutil,
50 scmutil,
51 smartset,
51 smartset,
52 state as statemod,
52 state as statemod,
53 subrepoutil,
53 subrepoutil,
54 templatekw,
54 templatekw,
55 templater,
55 templater,
56 util,
56 util,
57 vfs as vfsmod,
57 vfs as vfsmod,
58 )
58 )
59
59
60 from .utils import (
60 from .utils import (
61 dateutil,
61 dateutil,
62 stringutil,
62 stringutil,
63 )
63 )
64
64
65 if pycompat.TYPE_CHECKING:
65 if pycompat.TYPE_CHECKING:
66 from typing import (
66 from typing import (
67 Any,
67 Any,
68 Dict,
68 Dict,
69 )
69 )
70
70
71 for t in (Any, Dict):
71 for t in (Any, Dict):
72 assert t
72 assert t
73
73
74 stringio = util.stringio
74 stringio = util.stringio
75
75
76 # templates of common command options
76 # templates of common command options
77
77
78 dryrunopts = [
78 dryrunopts = [
79 (b'n', b'dry-run', None, _(b'do not perform actions, just print output')),
79 (b'n', b'dry-run', None, _(b'do not perform actions, just print output')),
80 ]
80 ]
81
81
82 confirmopts = [
82 confirmopts = [
83 (b'', b'confirm', None, _(b'ask before applying actions')),
83 (b'', b'confirm', None, _(b'ask before applying actions')),
84 ]
84 ]
85
85
86 remoteopts = [
86 remoteopts = [
87 (b'e', b'ssh', b'', _(b'specify ssh command to use'), _(b'CMD')),
87 (b'e', b'ssh', b'', _(b'specify ssh command to use'), _(b'CMD')),
88 (
88 (
89 b'',
89 b'',
90 b'remotecmd',
90 b'remotecmd',
91 b'',
91 b'',
92 _(b'specify hg command to run on the remote side'),
92 _(b'specify hg command to run on the remote side'),
93 _(b'CMD'),
93 _(b'CMD'),
94 ),
94 ),
95 (
95 (
96 b'',
96 b'',
97 b'insecure',
97 b'insecure',
98 None,
98 None,
99 _(b'do not verify server certificate (ignoring web.cacerts config)'),
99 _(b'do not verify server certificate (ignoring web.cacerts config)'),
100 ),
100 ),
101 ]
101 ]
102
102
103 walkopts = [
103 walkopts = [
104 (
104 (
105 b'I',
105 b'I',
106 b'include',
106 b'include',
107 [],
107 [],
108 _(b'include names matching the given patterns'),
108 _(b'include names matching the given patterns'),
109 _(b'PATTERN'),
109 _(b'PATTERN'),
110 ),
110 ),
111 (
111 (
112 b'X',
112 b'X',
113 b'exclude',
113 b'exclude',
114 [],
114 [],
115 _(b'exclude names matching the given patterns'),
115 _(b'exclude names matching the given patterns'),
116 _(b'PATTERN'),
116 _(b'PATTERN'),
117 ),
117 ),
118 ]
118 ]
119
119
120 commitopts = [
120 commitopts = [
121 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
121 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
122 (b'l', b'logfile', b'', _(b'read commit message from file'), _(b'FILE')),
122 (b'l', b'logfile', b'', _(b'read commit message from file'), _(b'FILE')),
123 ]
123 ]
124
124
125 commitopts2 = [
125 commitopts2 = [
126 (
126 (
127 b'd',
127 b'd',
128 b'date',
128 b'date',
129 b'',
129 b'',
130 _(b'record the specified date as commit date'),
130 _(b'record the specified date as commit date'),
131 _(b'DATE'),
131 _(b'DATE'),
132 ),
132 ),
133 (
133 (
134 b'u',
134 b'u',
135 b'user',
135 b'user',
136 b'',
136 b'',
137 _(b'record the specified user as committer'),
137 _(b'record the specified user as committer'),
138 _(b'USER'),
138 _(b'USER'),
139 ),
139 ),
140 ]
140 ]
141
141
142 commitopts3 = [
142 commitopts3 = [
143 (b'D', b'currentdate', None, _(b'record the current date as commit date')),
143 (b'D', b'currentdate', None, _(b'record the current date as commit date')),
144 (b'U', b'currentuser', None, _(b'record the current user as committer')),
144 (b'U', b'currentuser', None, _(b'record the current user as committer')),
145 ]
145 ]
146
146
147 formatteropts = [
147 formatteropts = [
148 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
148 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
149 ]
149 ]
150
150
151 templateopts = [
151 templateopts = [
152 (
152 (
153 b'',
153 b'',
154 b'style',
154 b'style',
155 b'',
155 b'',
156 _(b'display using template map file (DEPRECATED)'),
156 _(b'display using template map file (DEPRECATED)'),
157 _(b'STYLE'),
157 _(b'STYLE'),
158 ),
158 ),
159 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
159 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
160 ]
160 ]
161
161
162 logopts = [
162 logopts = [
163 (b'p', b'patch', None, _(b'show patch')),
163 (b'p', b'patch', None, _(b'show patch')),
164 (b'g', b'git', None, _(b'use git extended diff format')),
164 (b'g', b'git', None, _(b'use git extended diff format')),
165 (b'l', b'limit', b'', _(b'limit number of changes displayed'), _(b'NUM')),
165 (b'l', b'limit', b'', _(b'limit number of changes displayed'), _(b'NUM')),
166 (b'M', b'no-merges', None, _(b'do not show merges')),
166 (b'M', b'no-merges', None, _(b'do not show merges')),
167 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
167 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
168 (b'G', b'graph', None, _(b"show the revision DAG")),
168 (b'G', b'graph', None, _(b"show the revision DAG")),
169 ] + templateopts
169 ] + templateopts
170
170
171 diffopts = [
171 diffopts = [
172 (b'a', b'text', None, _(b'treat all files as text')),
172 (b'a', b'text', None, _(b'treat all files as text')),
173 (b'g', b'git', None, _(b'use git extended diff format (DEFAULT: diff.git)')),
173 (
174 b'g',
175 b'git',
176 None,
177 _(b'use git extended diff format (DEFAULT: diff.git)'),
178 ),
174 (b'', b'binary', None, _(b'generate binary diffs in git mode (default)')),
179 (b'', b'binary', None, _(b'generate binary diffs in git mode (default)')),
175 (b'', b'nodates', None, _(b'omit dates from diff headers')),
180 (b'', b'nodates', None, _(b'omit dates from diff headers')),
176 ]
181 ]
177
182
178 diffwsopts = [
183 diffwsopts = [
179 (
184 (
180 b'w',
185 b'w',
181 b'ignore-all-space',
186 b'ignore-all-space',
182 None,
187 None,
183 _(b'ignore white space when comparing lines'),
188 _(b'ignore white space when comparing lines'),
184 ),
189 ),
185 (
190 (
186 b'b',
191 b'b',
187 b'ignore-space-change',
192 b'ignore-space-change',
188 None,
193 None,
189 _(b'ignore changes in the amount of white space'),
194 _(b'ignore changes in the amount of white space'),
190 ),
195 ),
191 (
196 (
192 b'B',
197 b'B',
193 b'ignore-blank-lines',
198 b'ignore-blank-lines',
194 None,
199 None,
195 _(b'ignore changes whose lines are all blank'),
200 _(b'ignore changes whose lines are all blank'),
196 ),
201 ),
197 (
202 (
198 b'Z',
203 b'Z',
199 b'ignore-space-at-eol',
204 b'ignore-space-at-eol',
200 None,
205 None,
201 _(b'ignore changes in whitespace at EOL'),
206 _(b'ignore changes in whitespace at EOL'),
202 ),
207 ),
203 ]
208 ]
204
209
205 diffopts2 = (
210 diffopts2 = (
206 [
211 [
207 (b'', b'noprefix', None, _(b'omit a/ and b/ prefixes from filenames')),
212 (b'', b'noprefix', None, _(b'omit a/ and b/ prefixes from filenames')),
208 (
213 (
209 b'p',
214 b'p',
210 b'show-function',
215 b'show-function',
211 None,
216 None,
212 _(b'show which function each change is in (DEFAULT: diff.showfunc)'),
217 _(
218 b'show which function each change is in (DEFAULT: diff.showfunc)'
219 ),
213 ),
220 ),
214 (b'', b'reverse', None, _(b'produce a diff that undoes the changes')),
221 (b'', b'reverse', None, _(b'produce a diff that undoes the changes')),
215 ]
222 ]
216 + diffwsopts
223 + diffwsopts
217 + [
224 + [
218 (
225 (
219 b'U',
226 b'U',
220 b'unified',
227 b'unified',
221 b'',
228 b'',
222 _(b'number of lines of context to show'),
229 _(b'number of lines of context to show'),
223 _(b'NUM'),
230 _(b'NUM'),
224 ),
231 ),
225 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
232 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
226 (
233 (
227 b'',
234 b'',
228 b'root',
235 b'root',
229 b'',
236 b'',
230 _(b'produce diffs relative to subdirectory'),
237 _(b'produce diffs relative to subdirectory'),
231 _(b'DIR'),
238 _(b'DIR'),
232 ),
239 ),
233 ]
240 ]
234 )
241 )
235
242
236 mergetoolopts = [
243 mergetoolopts = [
237 (b't', b'tool', b'', _(b'specify merge tool'), _(b'TOOL')),
244 (b't', b'tool', b'', _(b'specify merge tool'), _(b'TOOL')),
238 ]
245 ]
239
246
240 similarityopts = [
247 similarityopts = [
241 (
248 (
242 b's',
249 b's',
243 b'similarity',
250 b'similarity',
244 b'',
251 b'',
245 _(b'guess renamed files by similarity (0<=s<=100)'),
252 _(b'guess renamed files by similarity (0<=s<=100)'),
246 _(b'SIMILARITY'),
253 _(b'SIMILARITY'),
247 )
254 )
248 ]
255 ]
249
256
250 subrepoopts = [(b'S', b'subrepos', None, _(b'recurse into subrepositories'))]
257 subrepoopts = [(b'S', b'subrepos', None, _(b'recurse into subrepositories'))]
251
258
252 debugrevlogopts = [
259 debugrevlogopts = [
253 (b'c', b'changelog', False, _(b'open changelog')),
260 (b'c', b'changelog', False, _(b'open changelog')),
254 (b'm', b'manifest', False, _(b'open manifest')),
261 (b'm', b'manifest', False, _(b'open manifest')),
255 (b'', b'dir', b'', _(b'open directory manifest')),
262 (b'', b'dir', b'', _(b'open directory manifest')),
256 ]
263 ]
257
264
258 # special string such that everything below this line will be ingored in the
265 # special string such that everything below this line will be ingored in the
259 # editor text
266 # editor text
260 _linebelow = b"^HG: ------------------------ >8 ------------------------$"
267 _linebelow = b"^HG: ------------------------ >8 ------------------------$"
261
268
262
269
263 def check_at_most_one_arg(opts, *args):
270 def check_at_most_one_arg(opts, *args):
264 """abort if more than one of the arguments are in opts
271 """abort if more than one of the arguments are in opts
265
272
266 Returns the unique argument or None if none of them were specified.
273 Returns the unique argument or None if none of them were specified.
267 """
274 """
268
275
269 def to_display(name):
276 def to_display(name):
270 return pycompat.sysbytes(name).replace(b'_', b'-')
277 return pycompat.sysbytes(name).replace(b'_', b'-')
271
278
272 previous = None
279 previous = None
273 for x in args:
280 for x in args:
274 if opts.get(x):
281 if opts.get(x):
275 if previous:
282 if previous:
276 raise error.Abort(
283 raise error.Abort(
277 _(b'cannot specify both --%s and --%s')
284 _(b'cannot specify both --%s and --%s')
278 % (to_display(previous), to_display(x))
285 % (to_display(previous), to_display(x))
279 )
286 )
280 previous = x
287 previous = x
281 return previous
288 return previous
282
289
283
290
284 def check_incompatible_arguments(opts, first, others):
291 def check_incompatible_arguments(opts, first, others):
285 """abort if the first argument is given along with any of the others
292 """abort if the first argument is given along with any of the others
286
293
287 Unlike check_at_most_one_arg(), `others` are not mutually exclusive
294 Unlike check_at_most_one_arg(), `others` are not mutually exclusive
288 among themselves, and they're passed as a single collection.
295 among themselves, and they're passed as a single collection.
289 """
296 """
290 for other in others:
297 for other in others:
291 check_at_most_one_arg(opts, first, other)
298 check_at_most_one_arg(opts, first, other)
292
299
293
300
294 def resolvecommitoptions(ui, opts):
301 def resolvecommitoptions(ui, opts):
295 """modify commit options dict to handle related options
302 """modify commit options dict to handle related options
296
303
297 The return value indicates that ``rewrite.update-timestamp`` is the reason
304 The return value indicates that ``rewrite.update-timestamp`` is the reason
298 the ``date`` option is set.
305 the ``date`` option is set.
299 """
306 """
300 check_at_most_one_arg(opts, b'date', b'currentdate')
307 check_at_most_one_arg(opts, b'date', b'currentdate')
301 check_at_most_one_arg(opts, b'user', b'currentuser')
308 check_at_most_one_arg(opts, b'user', b'currentuser')
302
309
303 datemaydiffer = False # date-only change should be ignored?
310 datemaydiffer = False # date-only change should be ignored?
304
311
305 if opts.get(b'currentdate'):
312 if opts.get(b'currentdate'):
306 opts[b'date'] = b'%d %d' % dateutil.makedate()
313 opts[b'date'] = b'%d %d' % dateutil.makedate()
307 elif (
314 elif (
308 not opts.get(b'date')
315 not opts.get(b'date')
309 and ui.configbool(b'rewrite', b'update-timestamp')
316 and ui.configbool(b'rewrite', b'update-timestamp')
310 and opts.get(b'currentdate') is None
317 and opts.get(b'currentdate') is None
311 ):
318 ):
312 opts[b'date'] = b'%d %d' % dateutil.makedate()
319 opts[b'date'] = b'%d %d' % dateutil.makedate()
313 datemaydiffer = True
320 datemaydiffer = True
314
321
315 if opts.get(b'currentuser'):
322 if opts.get(b'currentuser'):
316 opts[b'user'] = ui.username()
323 opts[b'user'] = ui.username()
317
324
318 return datemaydiffer
325 return datemaydiffer
319
326
320
327
321 def checknotesize(ui, opts):
328 def checknotesize(ui, opts):
322 """ make sure note is of valid format """
329 """ make sure note is of valid format """
323
330
324 note = opts.get(b'note')
331 note = opts.get(b'note')
325 if not note:
332 if not note:
326 return
333 return
327
334
328 if len(note) > 255:
335 if len(note) > 255:
329 raise error.Abort(_(b"cannot store a note of more than 255 bytes"))
336 raise error.Abort(_(b"cannot store a note of more than 255 bytes"))
330 if b'\n' in note:
337 if b'\n' in note:
331 raise error.Abort(_(b"note cannot contain a newline"))
338 raise error.Abort(_(b"note cannot contain a newline"))
332
339
333
340
334 def ishunk(x):
341 def ishunk(x):
335 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
342 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
336 return isinstance(x, hunkclasses)
343 return isinstance(x, hunkclasses)
337
344
338
345
339 def newandmodified(chunks, originalchunks):
346 def newandmodified(chunks, originalchunks):
340 newlyaddedandmodifiedfiles = set()
347 newlyaddedandmodifiedfiles = set()
341 alsorestore = set()
348 alsorestore = set()
342 for chunk in chunks:
349 for chunk in chunks:
343 if (
350 if (
344 ishunk(chunk)
351 ishunk(chunk)
345 and chunk.header.isnewfile()
352 and chunk.header.isnewfile()
346 and chunk not in originalchunks
353 and chunk not in originalchunks
347 ):
354 ):
348 newlyaddedandmodifiedfiles.add(chunk.header.filename())
355 newlyaddedandmodifiedfiles.add(chunk.header.filename())
349 alsorestore.update(
356 alsorestore.update(
350 set(chunk.header.files()) - {chunk.header.filename()}
357 set(chunk.header.files()) - {chunk.header.filename()}
351 )
358 )
352 return newlyaddedandmodifiedfiles, alsorestore
359 return newlyaddedandmodifiedfiles, alsorestore
353
360
354
361
355 def parsealiases(cmd):
362 def parsealiases(cmd):
356 return cmd.split(b"|")
363 return cmd.split(b"|")
357
364
358
365
359 def setupwrapcolorwrite(ui):
366 def setupwrapcolorwrite(ui):
360 # wrap ui.write so diff output can be labeled/colorized
367 # wrap ui.write so diff output can be labeled/colorized
361 def wrapwrite(orig, *args, **kw):
368 def wrapwrite(orig, *args, **kw):
362 label = kw.pop('label', b'')
369 label = kw.pop('label', b'')
363 for chunk, l in patch.difflabel(lambda: args):
370 for chunk, l in patch.difflabel(lambda: args):
364 orig(chunk, label=label + l)
371 orig(chunk, label=label + l)
365
372
366 oldwrite = ui.write
373 oldwrite = ui.write
367
374
368 def wrap(*args, **kwargs):
375 def wrap(*args, **kwargs):
369 return wrapwrite(oldwrite, *args, **kwargs)
376 return wrapwrite(oldwrite, *args, **kwargs)
370
377
371 setattr(ui, 'write', wrap)
378 setattr(ui, 'write', wrap)
372 return oldwrite
379 return oldwrite
373
380
374
381
375 def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None):
382 def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None):
376 try:
383 try:
377 if usecurses:
384 if usecurses:
378 if testfile:
385 if testfile:
379 recordfn = crecordmod.testdecorator(
386 recordfn = crecordmod.testdecorator(
380 testfile, crecordmod.testchunkselector
387 testfile, crecordmod.testchunkselector
381 )
388 )
382 else:
389 else:
383 recordfn = crecordmod.chunkselector
390 recordfn = crecordmod.chunkselector
384
391
385 return crecordmod.filterpatch(
392 return crecordmod.filterpatch(
386 ui, originalhunks, recordfn, operation
393 ui, originalhunks, recordfn, operation
387 )
394 )
388 except crecordmod.fallbackerror as e:
395 except crecordmod.fallbackerror as e:
389 ui.warn(b'%s\n' % e)
396 ui.warn(b'%s\n' % e)
390 ui.warn(_(b'falling back to text mode\n'))
397 ui.warn(_(b'falling back to text mode\n'))
391
398
392 return patch.filterpatch(ui, originalhunks, match, operation)
399 return patch.filterpatch(ui, originalhunks, match, operation)
393
400
394
401
395 def recordfilter(ui, originalhunks, match, operation=None):
402 def recordfilter(ui, originalhunks, match, operation=None):
396 """ Prompts the user to filter the originalhunks and return a list of
403 """ Prompts the user to filter the originalhunks and return a list of
397 selected hunks.
404 selected hunks.
398 *operation* is used for to build ui messages to indicate the user what
405 *operation* is used for to build ui messages to indicate the user what
399 kind of filtering they are doing: reverting, committing, shelving, etc.
406 kind of filtering they are doing: reverting, committing, shelving, etc.
400 (see patch.filterpatch).
407 (see patch.filterpatch).
401 """
408 """
402 usecurses = crecordmod.checkcurses(ui)
409 usecurses = crecordmod.checkcurses(ui)
403 testfile = ui.config(b'experimental', b'crecordtest')
410 testfile = ui.config(b'experimental', b'crecordtest')
404 oldwrite = setupwrapcolorwrite(ui)
411 oldwrite = setupwrapcolorwrite(ui)
405 try:
412 try:
406 newchunks, newopts = filterchunks(
413 newchunks, newopts = filterchunks(
407 ui, originalhunks, usecurses, testfile, match, operation
414 ui, originalhunks, usecurses, testfile, match, operation
408 )
415 )
409 finally:
416 finally:
410 ui.write = oldwrite
417 ui.write = oldwrite
411 return newchunks, newopts
418 return newchunks, newopts
412
419
413
420
414 def dorecord(
421 def dorecord(
415 ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts
422 ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts
416 ):
423 ):
417 opts = pycompat.byteskwargs(opts)
424 opts = pycompat.byteskwargs(opts)
418 if not ui.interactive():
425 if not ui.interactive():
419 if cmdsuggest:
426 if cmdsuggest:
420 msg = _(b'running non-interactively, use %s instead') % cmdsuggest
427 msg = _(b'running non-interactively, use %s instead') % cmdsuggest
421 else:
428 else:
422 msg = _(b'running non-interactively')
429 msg = _(b'running non-interactively')
423 raise error.Abort(msg)
430 raise error.Abort(msg)
424
431
425 # make sure username is set before going interactive
432 # make sure username is set before going interactive
426 if not opts.get(b'user'):
433 if not opts.get(b'user'):
427 ui.username() # raise exception, username not provided
434 ui.username() # raise exception, username not provided
428
435
429 def recordfunc(ui, repo, message, match, opts):
436 def recordfunc(ui, repo, message, match, opts):
430 """This is generic record driver.
437 """This is generic record driver.
431
438
432 Its job is to interactively filter local changes, and
439 Its job is to interactively filter local changes, and
433 accordingly prepare working directory into a state in which the
440 accordingly prepare working directory into a state in which the
434 job can be delegated to a non-interactive commit command such as
441 job can be delegated to a non-interactive commit command such as
435 'commit' or 'qrefresh'.
442 'commit' or 'qrefresh'.
436
443
437 After the actual job is done by non-interactive command, the
444 After the actual job is done by non-interactive command, the
438 working directory is restored to its original state.
445 working directory is restored to its original state.
439
446
440 In the end we'll record interesting changes, and everything else
447 In the end we'll record interesting changes, and everything else
441 will be left in place, so the user can continue working.
448 will be left in place, so the user can continue working.
442 """
449 """
443 if not opts.get(b'interactive-unshelve'):
450 if not opts.get(b'interactive-unshelve'):
444 checkunfinished(repo, commit=True)
451 checkunfinished(repo, commit=True)
445 wctx = repo[None]
452 wctx = repo[None]
446 merge = len(wctx.parents()) > 1
453 merge = len(wctx.parents()) > 1
447 if merge:
454 if merge:
448 raise error.Abort(
455 raise error.Abort(
449 _(
456 _(
450 b'cannot partially commit a merge '
457 b'cannot partially commit a merge '
451 b'(use "hg commit" instead)'
458 b'(use "hg commit" instead)'
452 )
459 )
453 )
460 )
454
461
455 def fail(f, msg):
462 def fail(f, msg):
456 raise error.Abort(b'%s: %s' % (f, msg))
463 raise error.Abort(b'%s: %s' % (f, msg))
457
464
458 force = opts.get(b'force')
465 force = opts.get(b'force')
459 if not force:
466 if not force:
460 match = matchmod.badmatch(match, fail)
467 match = matchmod.badmatch(match, fail)
461
468
462 status = repo.status(match=match)
469 status = repo.status(match=match)
463
470
464 overrides = {(b'ui', b'commitsubrepos'): True}
471 overrides = {(b'ui', b'commitsubrepos'): True}
465
472
466 with repo.ui.configoverride(overrides, b'record'):
473 with repo.ui.configoverride(overrides, b'record'):
467 # subrepoutil.precommit() modifies the status
474 # subrepoutil.precommit() modifies the status
468 tmpstatus = scmutil.status(
475 tmpstatus = scmutil.status(
469 copymod.copy(status.modified),
476 copymod.copy(status.modified),
470 copymod.copy(status.added),
477 copymod.copy(status.added),
471 copymod.copy(status.removed),
478 copymod.copy(status.removed),
472 copymod.copy(status.deleted),
479 copymod.copy(status.deleted),
473 copymod.copy(status.unknown),
480 copymod.copy(status.unknown),
474 copymod.copy(status.ignored),
481 copymod.copy(status.ignored),
475 copymod.copy(status.clean), # pytype: disable=wrong-arg-count
482 copymod.copy(status.clean), # pytype: disable=wrong-arg-count
476 )
483 )
477
484
478 # Force allows -X subrepo to skip the subrepo.
485 # Force allows -X subrepo to skip the subrepo.
479 subs, commitsubs, newstate = subrepoutil.precommit(
486 subs, commitsubs, newstate = subrepoutil.precommit(
480 repo.ui, wctx, tmpstatus, match, force=True
487 repo.ui, wctx, tmpstatus, match, force=True
481 )
488 )
482 for s in subs:
489 for s in subs:
483 if s in commitsubs:
490 if s in commitsubs:
484 dirtyreason = wctx.sub(s).dirtyreason(True)
491 dirtyreason = wctx.sub(s).dirtyreason(True)
485 raise error.Abort(dirtyreason)
492 raise error.Abort(dirtyreason)
486
493
487 if not force:
494 if not force:
488 repo.checkcommitpatterns(wctx, match, status, fail)
495 repo.checkcommitpatterns(wctx, match, status, fail)
489 diffopts = patch.difffeatureopts(
496 diffopts = patch.difffeatureopts(
490 ui,
497 ui,
491 opts=opts,
498 opts=opts,
492 whitespace=True,
499 whitespace=True,
493 section=b'commands',
500 section=b'commands',
494 configprefix=b'commit.interactive.',
501 configprefix=b'commit.interactive.',
495 )
502 )
496 diffopts.nodates = True
503 diffopts.nodates = True
497 diffopts.git = True
504 diffopts.git = True
498 diffopts.showfunc = True
505 diffopts.showfunc = True
499 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
506 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
500 originalchunks = patch.parsepatch(originaldiff)
507 originalchunks = patch.parsepatch(originaldiff)
501 match = scmutil.match(repo[None], pats)
508 match = scmutil.match(repo[None], pats)
502
509
503 # 1. filter patch, since we are intending to apply subset of it
510 # 1. filter patch, since we are intending to apply subset of it
504 try:
511 try:
505 chunks, newopts = filterfn(ui, originalchunks, match)
512 chunks, newopts = filterfn(ui, originalchunks, match)
506 except error.PatchError as err:
513 except error.PatchError as err:
507 raise error.Abort(_(b'error parsing patch: %s') % err)
514 raise error.Abort(_(b'error parsing patch: %s') % err)
508 opts.update(newopts)
515 opts.update(newopts)
509
516
510 # We need to keep a backup of files that have been newly added and
517 # We need to keep a backup of files that have been newly added and
511 # modified during the recording process because there is a previous
518 # modified during the recording process because there is a previous
512 # version without the edit in the workdir. We also will need to restore
519 # version without the edit in the workdir. We also will need to restore
513 # files that were the sources of renames so that the patch application
520 # files that were the sources of renames so that the patch application
514 # works.
521 # works.
515 newlyaddedandmodifiedfiles, alsorestore = newandmodified(
522 newlyaddedandmodifiedfiles, alsorestore = newandmodified(
516 chunks, originalchunks
523 chunks, originalchunks
517 )
524 )
518 contenders = set()
525 contenders = set()
519 for h in chunks:
526 for h in chunks:
520 try:
527 try:
521 contenders.update(set(h.files()))
528 contenders.update(set(h.files()))
522 except AttributeError:
529 except AttributeError:
523 pass
530 pass
524
531
525 changed = status.modified + status.added + status.removed
532 changed = status.modified + status.added + status.removed
526 newfiles = [f for f in changed if f in contenders]
533 newfiles = [f for f in changed if f in contenders]
527 if not newfiles:
534 if not newfiles:
528 ui.status(_(b'no changes to record\n'))
535 ui.status(_(b'no changes to record\n'))
529 return 0
536 return 0
530
537
531 modified = set(status.modified)
538 modified = set(status.modified)
532
539
533 # 2. backup changed files, so we can restore them in the end
540 # 2. backup changed files, so we can restore them in the end
534
541
535 if backupall:
542 if backupall:
536 tobackup = changed
543 tobackup = changed
537 else:
544 else:
538 tobackup = [
545 tobackup = [
539 f
546 f
540 for f in newfiles
547 for f in newfiles
541 if f in modified or f in newlyaddedandmodifiedfiles
548 if f in modified or f in newlyaddedandmodifiedfiles
542 ]
549 ]
543 backups = {}
550 backups = {}
544 if tobackup:
551 if tobackup:
545 backupdir = repo.vfs.join(b'record-backups')
552 backupdir = repo.vfs.join(b'record-backups')
546 try:
553 try:
547 os.mkdir(backupdir)
554 os.mkdir(backupdir)
548 except OSError as err:
555 except OSError as err:
549 if err.errno != errno.EEXIST:
556 if err.errno != errno.EEXIST:
550 raise
557 raise
551 try:
558 try:
552 # backup continues
559 # backup continues
553 for f in tobackup:
560 for f in tobackup:
554 fd, tmpname = pycompat.mkstemp(
561 fd, tmpname = pycompat.mkstemp(
555 prefix=f.replace(b'/', b'_') + b'.', dir=backupdir
562 prefix=f.replace(b'/', b'_') + b'.', dir=backupdir
556 )
563 )
557 os.close(fd)
564 os.close(fd)
558 ui.debug(b'backup %r as %r\n' % (f, tmpname))
565 ui.debug(b'backup %r as %r\n' % (f, tmpname))
559 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
566 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
560 backups[f] = tmpname
567 backups[f] = tmpname
561
568
562 fp = stringio()
569 fp = stringio()
563 for c in chunks:
570 for c in chunks:
564 fname = c.filename()
571 fname = c.filename()
565 if fname in backups:
572 if fname in backups:
566 c.write(fp)
573 c.write(fp)
567 dopatch = fp.tell()
574 dopatch = fp.tell()
568 fp.seek(0)
575 fp.seek(0)
569
576
570 # 2.5 optionally review / modify patch in text editor
577 # 2.5 optionally review / modify patch in text editor
571 if opts.get(b'review', False):
578 if opts.get(b'review', False):
572 patchtext = (
579 patchtext = (
573 crecordmod.diffhelptext
580 crecordmod.diffhelptext
574 + crecordmod.patchhelptext
581 + crecordmod.patchhelptext
575 + fp.read()
582 + fp.read()
576 )
583 )
577 reviewedpatch = ui.edit(
584 reviewedpatch = ui.edit(
578 patchtext, b"", action=b"diff", repopath=repo.path
585 patchtext, b"", action=b"diff", repopath=repo.path
579 )
586 )
580 fp.truncate(0)
587 fp.truncate(0)
581 fp.write(reviewedpatch)
588 fp.write(reviewedpatch)
582 fp.seek(0)
589 fp.seek(0)
583
590
584 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
591 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
585 # 3a. apply filtered patch to clean repo (clean)
592 # 3a. apply filtered patch to clean repo (clean)
586 if backups:
593 if backups:
587 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
594 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
588 mergemod.revert_to(repo[b'.'], matcher=m)
595 mergemod.revert_to(repo[b'.'], matcher=m)
589
596
590 # 3b. (apply)
597 # 3b. (apply)
591 if dopatch:
598 if dopatch:
592 try:
599 try:
593 ui.debug(b'applying patch\n')
600 ui.debug(b'applying patch\n')
594 ui.debug(fp.getvalue())
601 ui.debug(fp.getvalue())
595 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
602 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
596 except error.PatchError as err:
603 except error.PatchError as err:
597 raise error.Abort(pycompat.bytestr(err))
604 raise error.Abort(pycompat.bytestr(err))
598 del fp
605 del fp
599
606
600 # 4. We prepared working directory according to filtered
607 # 4. We prepared working directory according to filtered
601 # patch. Now is the time to delegate the job to
608 # patch. Now is the time to delegate the job to
602 # commit/qrefresh or the like!
609 # commit/qrefresh or the like!
603
610
604 # Make all of the pathnames absolute.
611 # Make all of the pathnames absolute.
605 newfiles = [repo.wjoin(nf) for nf in newfiles]
612 newfiles = [repo.wjoin(nf) for nf in newfiles]
606 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
613 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
607 finally:
614 finally:
608 # 5. finally restore backed-up files
615 # 5. finally restore backed-up files
609 try:
616 try:
610 dirstate = repo.dirstate
617 dirstate = repo.dirstate
611 for realname, tmpname in pycompat.iteritems(backups):
618 for realname, tmpname in pycompat.iteritems(backups):
612 ui.debug(b'restoring %r to %r\n' % (tmpname, realname))
619 ui.debug(b'restoring %r to %r\n' % (tmpname, realname))
613
620
614 if dirstate[realname] == b'n':
621 if dirstate[realname] == b'n':
615 # without normallookup, restoring timestamp
622 # without normallookup, restoring timestamp
616 # may cause partially committed files
623 # may cause partially committed files
617 # to be treated as unmodified
624 # to be treated as unmodified
618 dirstate.normallookup(realname)
625 dirstate.normallookup(realname)
619
626
620 # copystat=True here and above are a hack to trick any
627 # copystat=True here and above are a hack to trick any
621 # editors that have f open that we haven't modified them.
628 # editors that have f open that we haven't modified them.
622 #
629 #
623 # Also note that this racy as an editor could notice the
630 # Also note that this racy as an editor could notice the
624 # file's mtime before we've finished writing it.
631 # file's mtime before we've finished writing it.
625 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
632 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
626 os.unlink(tmpname)
633 os.unlink(tmpname)
627 if tobackup:
634 if tobackup:
628 os.rmdir(backupdir)
635 os.rmdir(backupdir)
629 except OSError:
636 except OSError:
630 pass
637 pass
631
638
632 def recordinwlock(ui, repo, message, match, opts):
639 def recordinwlock(ui, repo, message, match, opts):
633 with repo.wlock():
640 with repo.wlock():
634 return recordfunc(ui, repo, message, match, opts)
641 return recordfunc(ui, repo, message, match, opts)
635
642
636 return commit(ui, repo, recordinwlock, pats, opts)
643 return commit(ui, repo, recordinwlock, pats, opts)
637
644
638
645
639 class dirnode(object):
646 class dirnode(object):
640 """
647 """
641 Represent a directory in user working copy with information required for
648 Represent a directory in user working copy with information required for
642 the purpose of tersing its status.
649 the purpose of tersing its status.
643
650
644 path is the path to the directory, without a trailing '/'
651 path is the path to the directory, without a trailing '/'
645
652
646 statuses is a set of statuses of all files in this directory (this includes
653 statuses is a set of statuses of all files in this directory (this includes
647 all the files in all the subdirectories too)
654 all the files in all the subdirectories too)
648
655
649 files is a list of files which are direct child of this directory
656 files is a list of files which are direct child of this directory
650
657
651 subdirs is a dictionary of sub-directory name as the key and it's own
658 subdirs is a dictionary of sub-directory name as the key and it's own
652 dirnode object as the value
659 dirnode object as the value
653 """
660 """
654
661
655 def __init__(self, dirpath):
662 def __init__(self, dirpath):
656 self.path = dirpath
663 self.path = dirpath
657 self.statuses = set()
664 self.statuses = set()
658 self.files = []
665 self.files = []
659 self.subdirs = {}
666 self.subdirs = {}
660
667
661 def _addfileindir(self, filename, status):
668 def _addfileindir(self, filename, status):
662 """Add a file in this directory as a direct child."""
669 """Add a file in this directory as a direct child."""
663 self.files.append((filename, status))
670 self.files.append((filename, status))
664
671
665 def addfile(self, filename, status):
672 def addfile(self, filename, status):
666 """
673 """
667 Add a file to this directory or to its direct parent directory.
674 Add a file to this directory or to its direct parent directory.
668
675
669 If the file is not direct child of this directory, we traverse to the
676 If the file is not direct child of this directory, we traverse to the
670 directory of which this file is a direct child of and add the file
677 directory of which this file is a direct child of and add the file
671 there.
678 there.
672 """
679 """
673
680
674 # the filename contains a path separator, it means it's not the direct
681 # the filename contains a path separator, it means it's not the direct
675 # child of this directory
682 # child of this directory
676 if b'/' in filename:
683 if b'/' in filename:
677 subdir, filep = filename.split(b'/', 1)
684 subdir, filep = filename.split(b'/', 1)
678
685
679 # does the dirnode object for subdir exists
686 # does the dirnode object for subdir exists
680 if subdir not in self.subdirs:
687 if subdir not in self.subdirs:
681 subdirpath = pathutil.join(self.path, subdir)
688 subdirpath = pathutil.join(self.path, subdir)
682 self.subdirs[subdir] = dirnode(subdirpath)
689 self.subdirs[subdir] = dirnode(subdirpath)
683
690
684 # try adding the file in subdir
691 # try adding the file in subdir
685 self.subdirs[subdir].addfile(filep, status)
692 self.subdirs[subdir].addfile(filep, status)
686
693
687 else:
694 else:
688 self._addfileindir(filename, status)
695 self._addfileindir(filename, status)
689
696
690 if status not in self.statuses:
697 if status not in self.statuses:
691 self.statuses.add(status)
698 self.statuses.add(status)
692
699
693 def iterfilepaths(self):
700 def iterfilepaths(self):
694 """Yield (status, path) for files directly under this directory."""
701 """Yield (status, path) for files directly under this directory."""
695 for f, st in self.files:
702 for f, st in self.files:
696 yield st, pathutil.join(self.path, f)
703 yield st, pathutil.join(self.path, f)
697
704
698 def tersewalk(self, terseargs):
705 def tersewalk(self, terseargs):
699 """
706 """
700 Yield (status, path) obtained by processing the status of this
707 Yield (status, path) obtained by processing the status of this
701 dirnode.
708 dirnode.
702
709
703 terseargs is the string of arguments passed by the user with `--terse`
710 terseargs is the string of arguments passed by the user with `--terse`
704 flag.
711 flag.
705
712
706 Following are the cases which can happen:
713 Following are the cases which can happen:
707
714
708 1) All the files in the directory (including all the files in its
715 1) All the files in the directory (including all the files in its
709 subdirectories) share the same status and the user has asked us to terse
716 subdirectories) share the same status and the user has asked us to terse
710 that status. -> yield (status, dirpath). dirpath will end in '/'.
717 that status. -> yield (status, dirpath). dirpath will end in '/'.
711
718
712 2) Otherwise, we do following:
719 2) Otherwise, we do following:
713
720
714 a) Yield (status, filepath) for all the files which are in this
721 a) Yield (status, filepath) for all the files which are in this
715 directory (only the ones in this directory, not the subdirs)
722 directory (only the ones in this directory, not the subdirs)
716
723
717 b) Recurse the function on all the subdirectories of this
724 b) Recurse the function on all the subdirectories of this
718 directory
725 directory
719 """
726 """
720
727
721 if len(self.statuses) == 1:
728 if len(self.statuses) == 1:
722 onlyst = self.statuses.pop()
729 onlyst = self.statuses.pop()
723
730
724 # Making sure we terse only when the status abbreviation is
731 # Making sure we terse only when the status abbreviation is
725 # passed as terse argument
732 # passed as terse argument
726 if onlyst in terseargs:
733 if onlyst in terseargs:
727 yield onlyst, self.path + b'/'
734 yield onlyst, self.path + b'/'
728 return
735 return
729
736
730 # add the files to status list
737 # add the files to status list
731 for st, fpath in self.iterfilepaths():
738 for st, fpath in self.iterfilepaths():
732 yield st, fpath
739 yield st, fpath
733
740
734 # recurse on the subdirs
741 # recurse on the subdirs
735 for dirobj in self.subdirs.values():
742 for dirobj in self.subdirs.values():
736 for st, fpath in dirobj.tersewalk(terseargs):
743 for st, fpath in dirobj.tersewalk(terseargs):
737 yield st, fpath
744 yield st, fpath
738
745
739
746
740 def tersedir(statuslist, terseargs):
747 def tersedir(statuslist, terseargs):
741 """
748 """
742 Terse the status if all the files in a directory shares the same status.
749 Terse the status if all the files in a directory shares the same status.
743
750
744 statuslist is scmutil.status() object which contains a list of files for
751 statuslist is scmutil.status() object which contains a list of files for
745 each status.
752 each status.
746 terseargs is string which is passed by the user as the argument to `--terse`
753 terseargs is string which is passed by the user as the argument to `--terse`
747 flag.
754 flag.
748
755
749 The function makes a tree of objects of dirnode class, and at each node it
756 The function makes a tree of objects of dirnode class, and at each node it
750 stores the information required to know whether we can terse a certain
757 stores the information required to know whether we can terse a certain
751 directory or not.
758 directory or not.
752 """
759 """
753 # the order matters here as that is used to produce final list
760 # the order matters here as that is used to produce final list
754 allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c')
761 allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c')
755
762
756 # checking the argument validity
763 # checking the argument validity
757 for s in pycompat.bytestr(terseargs):
764 for s in pycompat.bytestr(terseargs):
758 if s not in allst:
765 if s not in allst:
759 raise error.Abort(_(b"'%s' not recognized") % s)
766 raise error.Abort(_(b"'%s' not recognized") % s)
760
767
761 # creating a dirnode object for the root of the repo
768 # creating a dirnode object for the root of the repo
762 rootobj = dirnode(b'')
769 rootobj = dirnode(b'')
763 pstatus = (
770 pstatus = (
764 b'modified',
771 b'modified',
765 b'added',
772 b'added',
766 b'deleted',
773 b'deleted',
767 b'clean',
774 b'clean',
768 b'unknown',
775 b'unknown',
769 b'ignored',
776 b'ignored',
770 b'removed',
777 b'removed',
771 )
778 )
772
779
773 tersedict = {}
780 tersedict = {}
774 for attrname in pstatus:
781 for attrname in pstatus:
775 statuschar = attrname[0:1]
782 statuschar = attrname[0:1]
776 for f in getattr(statuslist, attrname):
783 for f in getattr(statuslist, attrname):
777 rootobj.addfile(f, statuschar)
784 rootobj.addfile(f, statuschar)
778 tersedict[statuschar] = []
785 tersedict[statuschar] = []
779
786
780 # we won't be tersing the root dir, so add files in it
787 # we won't be tersing the root dir, so add files in it
781 for st, fpath in rootobj.iterfilepaths():
788 for st, fpath in rootobj.iterfilepaths():
782 tersedict[st].append(fpath)
789 tersedict[st].append(fpath)
783
790
784 # process each sub-directory and build tersedict
791 # process each sub-directory and build tersedict
785 for subdir in rootobj.subdirs.values():
792 for subdir in rootobj.subdirs.values():
786 for st, f in subdir.tersewalk(terseargs):
793 for st, f in subdir.tersewalk(terseargs):
787 tersedict[st].append(f)
794 tersedict[st].append(f)
788
795
789 tersedlist = []
796 tersedlist = []
790 for st in allst:
797 for st in allst:
791 tersedict[st].sort()
798 tersedict[st].sort()
792 tersedlist.append(tersedict[st])
799 tersedlist.append(tersedict[st])
793
800
794 return scmutil.status(*tersedlist)
801 return scmutil.status(*tersedlist)
795
802
796
803
797 def _commentlines(raw):
804 def _commentlines(raw):
798 '''Surround lineswith a comment char and a new line'''
805 '''Surround lineswith a comment char and a new line'''
799 lines = raw.splitlines()
806 lines = raw.splitlines()
800 commentedlines = [b'# %s' % line for line in lines]
807 commentedlines = [b'# %s' % line for line in lines]
801 return b'\n'.join(commentedlines) + b'\n'
808 return b'\n'.join(commentedlines) + b'\n'
802
809
803
810
804 @attr.s(frozen=True)
811 @attr.s(frozen=True)
805 class morestatus(object):
812 class morestatus(object):
806 reporoot = attr.ib()
813 reporoot = attr.ib()
807 unfinishedop = attr.ib()
814 unfinishedop = attr.ib()
808 unfinishedmsg = attr.ib()
815 unfinishedmsg = attr.ib()
809 activemerge = attr.ib()
816 activemerge = attr.ib()
810 unresolvedpaths = attr.ib()
817 unresolvedpaths = attr.ib()
811 _formattedpaths = attr.ib(init=False, default=set())
818 _formattedpaths = attr.ib(init=False, default=set())
812 _label = b'status.morestatus'
819 _label = b'status.morestatus'
813
820
814 def formatfile(self, path, fm):
821 def formatfile(self, path, fm):
815 self._formattedpaths.add(path)
822 self._formattedpaths.add(path)
816 if self.activemerge and path in self.unresolvedpaths:
823 if self.activemerge and path in self.unresolvedpaths:
817 fm.data(unresolved=True)
824 fm.data(unresolved=True)
818
825
819 def formatfooter(self, fm):
826 def formatfooter(self, fm):
820 if self.unfinishedop or self.unfinishedmsg:
827 if self.unfinishedop or self.unfinishedmsg:
821 fm.startitem()
828 fm.startitem()
822 fm.data(itemtype=b'morestatus')
829 fm.data(itemtype=b'morestatus')
823
830
824 if self.unfinishedop:
831 if self.unfinishedop:
825 fm.data(unfinished=self.unfinishedop)
832 fm.data(unfinished=self.unfinishedop)
826 statemsg = (
833 statemsg = (
827 _(b'The repository is in an unfinished *%s* state.')
834 _(b'The repository is in an unfinished *%s* state.')
828 % self.unfinishedop
835 % self.unfinishedop
829 )
836 )
830 fm.plain(b'%s\n' % _commentlines(statemsg), label=self._label)
837 fm.plain(b'%s\n' % _commentlines(statemsg), label=self._label)
831 if self.unfinishedmsg:
838 if self.unfinishedmsg:
832 fm.data(unfinishedmsg=self.unfinishedmsg)
839 fm.data(unfinishedmsg=self.unfinishedmsg)
833
840
834 # May also start new data items.
841 # May also start new data items.
835 self._formatconflicts(fm)
842 self._formatconflicts(fm)
836
843
837 if self.unfinishedmsg:
844 if self.unfinishedmsg:
838 fm.plain(
845 fm.plain(
839 b'%s\n' % _commentlines(self.unfinishedmsg), label=self._label
846 b'%s\n' % _commentlines(self.unfinishedmsg), label=self._label
840 )
847 )
841
848
842 def _formatconflicts(self, fm):
849 def _formatconflicts(self, fm):
843 if not self.activemerge:
850 if not self.activemerge:
844 return
851 return
845
852
846 if self.unresolvedpaths:
853 if self.unresolvedpaths:
847 mergeliststr = b'\n'.join(
854 mergeliststr = b'\n'.join(
848 [
855 [
849 b' %s'
856 b' %s'
850 % util.pathto(self.reporoot, encoding.getcwd(), path)
857 % util.pathto(self.reporoot, encoding.getcwd(), path)
851 for path in self.unresolvedpaths
858 for path in self.unresolvedpaths
852 ]
859 ]
853 )
860 )
854 msg = (
861 msg = (
855 _(
862 _(
856 '''Unresolved merge conflicts:
863 '''Unresolved merge conflicts:
857
864
858 %s
865 %s
859
866
860 To mark files as resolved: hg resolve --mark FILE'''
867 To mark files as resolved: hg resolve --mark FILE'''
861 )
868 )
862 % mergeliststr
869 % mergeliststr
863 )
870 )
864
871
865 # If any paths with unresolved conflicts were not previously
872 # If any paths with unresolved conflicts were not previously
866 # formatted, output them now.
873 # formatted, output them now.
867 for f in self.unresolvedpaths:
874 for f in self.unresolvedpaths:
868 if f in self._formattedpaths:
875 if f in self._formattedpaths:
869 # Already output.
876 # Already output.
870 continue
877 continue
871 fm.startitem()
878 fm.startitem()
872 # We can't claim to know the status of the file - it may just
879 # We can't claim to know the status of the file - it may just
873 # have been in one of the states that were not requested for
880 # have been in one of the states that were not requested for
874 # display, so it could be anything.
881 # display, so it could be anything.
875 fm.data(itemtype=b'file', path=f, unresolved=True)
882 fm.data(itemtype=b'file', path=f, unresolved=True)
876
883
877 else:
884 else:
878 msg = _(b'No unresolved merge conflicts.')
885 msg = _(b'No unresolved merge conflicts.')
879
886
880 fm.plain(b'%s\n' % _commentlines(msg), label=self._label)
887 fm.plain(b'%s\n' % _commentlines(msg), label=self._label)
881
888
882
889
883 def readmorestatus(repo):
890 def readmorestatus(repo):
884 """Returns a morestatus object if the repo has unfinished state."""
891 """Returns a morestatus object if the repo has unfinished state."""
885 statetuple = statemod.getrepostate(repo)
892 statetuple = statemod.getrepostate(repo)
886 mergestate = mergemod.mergestate.read(repo)
893 mergestate = mergemod.mergestate.read(repo)
887 activemerge = mergestate.active()
894 activemerge = mergestate.active()
888 if not statetuple and not activemerge:
895 if not statetuple and not activemerge:
889 return None
896 return None
890
897
891 unfinishedop = unfinishedmsg = unresolved = None
898 unfinishedop = unfinishedmsg = unresolved = None
892 if statetuple:
899 if statetuple:
893 unfinishedop, unfinishedmsg = statetuple
900 unfinishedop, unfinishedmsg = statetuple
894 if activemerge:
901 if activemerge:
895 unresolved = sorted(mergestate.unresolved())
902 unresolved = sorted(mergestate.unresolved())
896 return morestatus(
903 return morestatus(
897 repo.root, unfinishedop, unfinishedmsg, activemerge, unresolved
904 repo.root, unfinishedop, unfinishedmsg, activemerge, unresolved
898 )
905 )
899
906
900
907
901 def findpossible(cmd, table, strict=False):
908 def findpossible(cmd, table, strict=False):
902 """
909 """
903 Return cmd -> (aliases, command table entry)
910 Return cmd -> (aliases, command table entry)
904 for each matching command.
911 for each matching command.
905 Return debug commands (or their aliases) only if no normal command matches.
912 Return debug commands (or their aliases) only if no normal command matches.
906 """
913 """
907 choice = {}
914 choice = {}
908 debugchoice = {}
915 debugchoice = {}
909
916
910 if cmd in table:
917 if cmd in table:
911 # short-circuit exact matches, "log" alias beats "log|history"
918 # short-circuit exact matches, "log" alias beats "log|history"
912 keys = [cmd]
919 keys = [cmd]
913 else:
920 else:
914 keys = table.keys()
921 keys = table.keys()
915
922
916 allcmds = []
923 allcmds = []
917 for e in keys:
924 for e in keys:
918 aliases = parsealiases(e)
925 aliases = parsealiases(e)
919 allcmds.extend(aliases)
926 allcmds.extend(aliases)
920 found = None
927 found = None
921 if cmd in aliases:
928 if cmd in aliases:
922 found = cmd
929 found = cmd
923 elif not strict:
930 elif not strict:
924 for a in aliases:
931 for a in aliases:
925 if a.startswith(cmd):
932 if a.startswith(cmd):
926 found = a
933 found = a
927 break
934 break
928 if found is not None:
935 if found is not None:
929 if aliases[0].startswith(b"debug") or found.startswith(b"debug"):
936 if aliases[0].startswith(b"debug") or found.startswith(b"debug"):
930 debugchoice[found] = (aliases, table[e])
937 debugchoice[found] = (aliases, table[e])
931 else:
938 else:
932 choice[found] = (aliases, table[e])
939 choice[found] = (aliases, table[e])
933
940
934 if not choice and debugchoice:
941 if not choice and debugchoice:
935 choice = debugchoice
942 choice = debugchoice
936
943
937 return choice, allcmds
944 return choice, allcmds
938
945
939
946
940 def findcmd(cmd, table, strict=True):
947 def findcmd(cmd, table, strict=True):
941 """Return (aliases, command table entry) for command string."""
948 """Return (aliases, command table entry) for command string."""
942 choice, allcmds = findpossible(cmd, table, strict)
949 choice, allcmds = findpossible(cmd, table, strict)
943
950
944 if cmd in choice:
951 if cmd in choice:
945 return choice[cmd]
952 return choice[cmd]
946
953
947 if len(choice) > 1:
954 if len(choice) > 1:
948 clist = sorted(choice)
955 clist = sorted(choice)
949 raise error.AmbiguousCommand(cmd, clist)
956 raise error.AmbiguousCommand(cmd, clist)
950
957
951 if choice:
958 if choice:
952 return list(choice.values())[0]
959 return list(choice.values())[0]
953
960
954 raise error.UnknownCommand(cmd, allcmds)
961 raise error.UnknownCommand(cmd, allcmds)
955
962
956
963
957 def changebranch(ui, repo, revs, label):
964 def changebranch(ui, repo, revs, label):
958 """ Change the branch name of given revs to label """
965 """ Change the branch name of given revs to label """
959
966
960 with repo.wlock(), repo.lock(), repo.transaction(b'branches'):
967 with repo.wlock(), repo.lock(), repo.transaction(b'branches'):
961 # abort in case of uncommitted merge or dirty wdir
968 # abort in case of uncommitted merge or dirty wdir
962 bailifchanged(repo)
969 bailifchanged(repo)
963 revs = scmutil.revrange(repo, revs)
970 revs = scmutil.revrange(repo, revs)
964 if not revs:
971 if not revs:
965 raise error.Abort(b"empty revision set")
972 raise error.Abort(b"empty revision set")
966 roots = repo.revs(b'roots(%ld)', revs)
973 roots = repo.revs(b'roots(%ld)', revs)
967 if len(roots) > 1:
974 if len(roots) > 1:
968 raise error.Abort(
975 raise error.Abort(
969 _(b"cannot change branch of non-linear revisions")
976 _(b"cannot change branch of non-linear revisions")
970 )
977 )
971 rewriteutil.precheck(repo, revs, b'change branch of')
978 rewriteutil.precheck(repo, revs, b'change branch of')
972
979
973 root = repo[roots.first()]
980 root = repo[roots.first()]
974 rpb = {parent.branch() for parent in root.parents()}
981 rpb = {parent.branch() for parent in root.parents()}
975 if label not in rpb and label in repo.branchmap():
982 if label not in rpb and label in repo.branchmap():
976 raise error.Abort(_(b"a branch of the same name already exists"))
983 raise error.Abort(_(b"a branch of the same name already exists"))
977
984
978 if repo.revs(b'obsolete() and %ld', revs):
985 if repo.revs(b'obsolete() and %ld', revs):
979 raise error.Abort(
986 raise error.Abort(
980 _(b"cannot change branch of a obsolete changeset")
987 _(b"cannot change branch of a obsolete changeset")
981 )
988 )
982
989
983 # make sure only topological heads
990 # make sure only topological heads
984 if repo.revs(b'heads(%ld) - head()', revs):
991 if repo.revs(b'heads(%ld) - head()', revs):
985 raise error.Abort(_(b"cannot change branch in middle of a stack"))
992 raise error.Abort(_(b"cannot change branch in middle of a stack"))
986
993
987 replacements = {}
994 replacements = {}
988 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
995 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
989 # mercurial.subrepo -> mercurial.cmdutil
996 # mercurial.subrepo -> mercurial.cmdutil
990 from . import context
997 from . import context
991
998
992 for rev in revs:
999 for rev in revs:
993 ctx = repo[rev]
1000 ctx = repo[rev]
994 oldbranch = ctx.branch()
1001 oldbranch = ctx.branch()
995 # check if ctx has same branch
1002 # check if ctx has same branch
996 if oldbranch == label:
1003 if oldbranch == label:
997 continue
1004 continue
998
1005
999 def filectxfn(repo, newctx, path):
1006 def filectxfn(repo, newctx, path):
1000 try:
1007 try:
1001 return ctx[path]
1008 return ctx[path]
1002 except error.ManifestLookupError:
1009 except error.ManifestLookupError:
1003 return None
1010 return None
1004
1011
1005 ui.debug(
1012 ui.debug(
1006 b"changing branch of '%s' from '%s' to '%s'\n"
1013 b"changing branch of '%s' from '%s' to '%s'\n"
1007 % (hex(ctx.node()), oldbranch, label)
1014 % (hex(ctx.node()), oldbranch, label)
1008 )
1015 )
1009 extra = ctx.extra()
1016 extra = ctx.extra()
1010 extra[b'branch_change'] = hex(ctx.node())
1017 extra[b'branch_change'] = hex(ctx.node())
1011 # While changing branch of set of linear commits, make sure that
1018 # While changing branch of set of linear commits, make sure that
1012 # we base our commits on new parent rather than old parent which
1019 # we base our commits on new parent rather than old parent which
1013 # was obsoleted while changing the branch
1020 # was obsoleted while changing the branch
1014 p1 = ctx.p1().node()
1021 p1 = ctx.p1().node()
1015 p2 = ctx.p2().node()
1022 p2 = ctx.p2().node()
1016 if p1 in replacements:
1023 if p1 in replacements:
1017 p1 = replacements[p1][0]
1024 p1 = replacements[p1][0]
1018 if p2 in replacements:
1025 if p2 in replacements:
1019 p2 = replacements[p2][0]
1026 p2 = replacements[p2][0]
1020
1027
1021 mc = context.memctx(
1028 mc = context.memctx(
1022 repo,
1029 repo,
1023 (p1, p2),
1030 (p1, p2),
1024 ctx.description(),
1031 ctx.description(),
1025 ctx.files(),
1032 ctx.files(),
1026 filectxfn,
1033 filectxfn,
1027 user=ctx.user(),
1034 user=ctx.user(),
1028 date=ctx.date(),
1035 date=ctx.date(),
1029 extra=extra,
1036 extra=extra,
1030 branch=label,
1037 branch=label,
1031 )
1038 )
1032
1039
1033 newnode = repo.commitctx(mc)
1040 newnode = repo.commitctx(mc)
1034 replacements[ctx.node()] = (newnode,)
1041 replacements[ctx.node()] = (newnode,)
1035 ui.debug(b'new node id is %s\n' % hex(newnode))
1042 ui.debug(b'new node id is %s\n' % hex(newnode))
1036
1043
1037 # create obsmarkers and move bookmarks
1044 # create obsmarkers and move bookmarks
1038 scmutil.cleanupnodes(
1045 scmutil.cleanupnodes(
1039 repo, replacements, b'branch-change', fixphase=True
1046 repo, replacements, b'branch-change', fixphase=True
1040 )
1047 )
1041
1048
1042 # move the working copy too
1049 # move the working copy too
1043 wctx = repo[None]
1050 wctx = repo[None]
1044 # in-progress merge is a bit too complex for now.
1051 # in-progress merge is a bit too complex for now.
1045 if len(wctx.parents()) == 1:
1052 if len(wctx.parents()) == 1:
1046 newid = replacements.get(wctx.p1().node())
1053 newid = replacements.get(wctx.p1().node())
1047 if newid is not None:
1054 if newid is not None:
1048 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
1055 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
1049 # mercurial.cmdutil
1056 # mercurial.cmdutil
1050 from . import hg
1057 from . import hg
1051
1058
1052 hg.update(repo, newid[0], quietempty=True)
1059 hg.update(repo, newid[0], quietempty=True)
1053
1060
1054 ui.status(_(b"changed branch on %d changesets\n") % len(replacements))
1061 ui.status(_(b"changed branch on %d changesets\n") % len(replacements))
1055
1062
1056
1063
1057 def findrepo(p):
1064 def findrepo(p):
1058 while not os.path.isdir(os.path.join(p, b".hg")):
1065 while not os.path.isdir(os.path.join(p, b".hg")):
1059 oldp, p = p, os.path.dirname(p)
1066 oldp, p = p, os.path.dirname(p)
1060 if p == oldp:
1067 if p == oldp:
1061 return None
1068 return None
1062
1069
1063 return p
1070 return p
1064
1071
1065
1072
1066 def bailifchanged(repo, merge=True, hint=None):
1073 def bailifchanged(repo, merge=True, hint=None):
1067 """ enforce the precondition that working directory must be clean.
1074 """ enforce the precondition that working directory must be clean.
1068
1075
1069 'merge' can be set to false if a pending uncommitted merge should be
1076 'merge' can be set to false if a pending uncommitted merge should be
1070 ignored (such as when 'update --check' runs).
1077 ignored (such as when 'update --check' runs).
1071
1078
1072 'hint' is the usual hint given to Abort exception.
1079 'hint' is the usual hint given to Abort exception.
1073 """
1080 """
1074
1081
1075 if merge and repo.dirstate.p2() != nullid:
1082 if merge and repo.dirstate.p2() != nullid:
1076 raise error.Abort(_(b'outstanding uncommitted merge'), hint=hint)
1083 raise error.Abort(_(b'outstanding uncommitted merge'), hint=hint)
1077 st = repo.status()
1084 st = repo.status()
1078 if st.modified or st.added or st.removed or st.deleted:
1085 if st.modified or st.added or st.removed or st.deleted:
1079 raise error.Abort(_(b'uncommitted changes'), hint=hint)
1086 raise error.Abort(_(b'uncommitted changes'), hint=hint)
1080 ctx = repo[None]
1087 ctx = repo[None]
1081 for s in sorted(ctx.substate):
1088 for s in sorted(ctx.substate):
1082 ctx.sub(s).bailifchanged(hint=hint)
1089 ctx.sub(s).bailifchanged(hint=hint)
1083
1090
1084
1091
1085 def logmessage(ui, opts):
1092 def logmessage(ui, opts):
1086 """ get the log message according to -m and -l option """
1093 """ get the log message according to -m and -l option """
1087
1094
1088 check_at_most_one_arg(opts, b'message', b'logfile')
1095 check_at_most_one_arg(opts, b'message', b'logfile')
1089
1096
1090 message = opts.get(b'message')
1097 message = opts.get(b'message')
1091 logfile = opts.get(b'logfile')
1098 logfile = opts.get(b'logfile')
1092
1099
1093 if not message and logfile:
1100 if not message and logfile:
1094 try:
1101 try:
1095 if isstdiofilename(logfile):
1102 if isstdiofilename(logfile):
1096 message = ui.fin.read()
1103 message = ui.fin.read()
1097 else:
1104 else:
1098 message = b'\n'.join(util.readfile(logfile).splitlines())
1105 message = b'\n'.join(util.readfile(logfile).splitlines())
1099 except IOError as inst:
1106 except IOError as inst:
1100 raise error.Abort(
1107 raise error.Abort(
1101 _(b"can't read commit message '%s': %s")
1108 _(b"can't read commit message '%s': %s")
1102 % (logfile, encoding.strtolocal(inst.strerror))
1109 % (logfile, encoding.strtolocal(inst.strerror))
1103 )
1110 )
1104 return message
1111 return message
1105
1112
1106
1113
1107 def mergeeditform(ctxorbool, baseformname):
1114 def mergeeditform(ctxorbool, baseformname):
1108 """return appropriate editform name (referencing a committemplate)
1115 """return appropriate editform name (referencing a committemplate)
1109
1116
1110 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
1117 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
1111 merging is committed.
1118 merging is committed.
1112
1119
1113 This returns baseformname with '.merge' appended if it is a merge,
1120 This returns baseformname with '.merge' appended if it is a merge,
1114 otherwise '.normal' is appended.
1121 otherwise '.normal' is appended.
1115 """
1122 """
1116 if isinstance(ctxorbool, bool):
1123 if isinstance(ctxorbool, bool):
1117 if ctxorbool:
1124 if ctxorbool:
1118 return baseformname + b".merge"
1125 return baseformname + b".merge"
1119 elif len(ctxorbool.parents()) > 1:
1126 elif len(ctxorbool.parents()) > 1:
1120 return baseformname + b".merge"
1127 return baseformname + b".merge"
1121
1128
1122 return baseformname + b".normal"
1129 return baseformname + b".normal"
1123
1130
1124
1131
1125 def getcommiteditor(
1132 def getcommiteditor(
1126 edit=False, finishdesc=None, extramsg=None, editform=b'', **opts
1133 edit=False, finishdesc=None, extramsg=None, editform=b'', **opts
1127 ):
1134 ):
1128 """get appropriate commit message editor according to '--edit' option
1135 """get appropriate commit message editor according to '--edit' option
1129
1136
1130 'finishdesc' is a function to be called with edited commit message
1137 'finishdesc' is a function to be called with edited commit message
1131 (= 'description' of the new changeset) just after editing, but
1138 (= 'description' of the new changeset) just after editing, but
1132 before checking empty-ness. It should return actual text to be
1139 before checking empty-ness. It should return actual text to be
1133 stored into history. This allows to change description before
1140 stored into history. This allows to change description before
1134 storing.
1141 storing.
1135
1142
1136 'extramsg' is a extra message to be shown in the editor instead of
1143 'extramsg' is a extra message to be shown in the editor instead of
1137 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
1144 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
1138 is automatically added.
1145 is automatically added.
1139
1146
1140 'editform' is a dot-separated list of names, to distinguish
1147 'editform' is a dot-separated list of names, to distinguish
1141 the purpose of commit text editing.
1148 the purpose of commit text editing.
1142
1149
1143 'getcommiteditor' returns 'commitforceeditor' regardless of
1150 'getcommiteditor' returns 'commitforceeditor' regardless of
1144 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
1151 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
1145 they are specific for usage in MQ.
1152 they are specific for usage in MQ.
1146 """
1153 """
1147 if edit or finishdesc or extramsg:
1154 if edit or finishdesc or extramsg:
1148 return lambda r, c, s: commitforceeditor(
1155 return lambda r, c, s: commitforceeditor(
1149 r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform
1156 r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform
1150 )
1157 )
1151 elif editform:
1158 elif editform:
1152 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
1159 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
1153 else:
1160 else:
1154 return commiteditor
1161 return commiteditor
1155
1162
1156
1163
1157 def _escapecommandtemplate(tmpl):
1164 def _escapecommandtemplate(tmpl):
1158 parts = []
1165 parts = []
1159 for typ, start, end in templater.scantemplate(tmpl, raw=True):
1166 for typ, start, end in templater.scantemplate(tmpl, raw=True):
1160 if typ == b'string':
1167 if typ == b'string':
1161 parts.append(stringutil.escapestr(tmpl[start:end]))
1168 parts.append(stringutil.escapestr(tmpl[start:end]))
1162 else:
1169 else:
1163 parts.append(tmpl[start:end])
1170 parts.append(tmpl[start:end])
1164 return b''.join(parts)
1171 return b''.join(parts)
1165
1172
1166
1173
1167 def rendercommandtemplate(ui, tmpl, props):
1174 def rendercommandtemplate(ui, tmpl, props):
1168 r"""Expand a literal template 'tmpl' in a way suitable for command line
1175 r"""Expand a literal template 'tmpl' in a way suitable for command line
1169
1176
1170 '\' in outermost string is not taken as an escape character because it
1177 '\' in outermost string is not taken as an escape character because it
1171 is a directory separator on Windows.
1178 is a directory separator on Windows.
1172
1179
1173 >>> from . import ui as uimod
1180 >>> from . import ui as uimod
1174 >>> ui = uimod.ui()
1181 >>> ui = uimod.ui()
1175 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
1182 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
1176 'c:\\foo'
1183 'c:\\foo'
1177 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
1184 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
1178 'c:{path}'
1185 'c:{path}'
1179 """
1186 """
1180 if not tmpl:
1187 if not tmpl:
1181 return tmpl
1188 return tmpl
1182 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
1189 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
1183 return t.renderdefault(props)
1190 return t.renderdefault(props)
1184
1191
1185
1192
1186 def rendertemplate(ctx, tmpl, props=None):
1193 def rendertemplate(ctx, tmpl, props=None):
1187 """Expand a literal template 'tmpl' byte-string against one changeset
1194 """Expand a literal template 'tmpl' byte-string against one changeset
1188
1195
1189 Each props item must be a stringify-able value or a callable returning
1196 Each props item must be a stringify-able value or a callable returning
1190 such value, i.e. no bare list nor dict should be passed.
1197 such value, i.e. no bare list nor dict should be passed.
1191 """
1198 """
1192 repo = ctx.repo()
1199 repo = ctx.repo()
1193 tres = formatter.templateresources(repo.ui, repo)
1200 tres = formatter.templateresources(repo.ui, repo)
1194 t = formatter.maketemplater(
1201 t = formatter.maketemplater(
1195 repo.ui, tmpl, defaults=templatekw.keywords, resources=tres
1202 repo.ui, tmpl, defaults=templatekw.keywords, resources=tres
1196 )
1203 )
1197 mapping = {b'ctx': ctx}
1204 mapping = {b'ctx': ctx}
1198 if props:
1205 if props:
1199 mapping.update(props)
1206 mapping.update(props)
1200 return t.renderdefault(mapping)
1207 return t.renderdefault(mapping)
1201
1208
1202
1209
1203 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
1210 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
1204 r"""Convert old-style filename format string to template string
1211 r"""Convert old-style filename format string to template string
1205
1212
1206 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
1213 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
1207 'foo-{reporoot|basename}-{seqno}.patch'
1214 'foo-{reporoot|basename}-{seqno}.patch'
1208 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
1215 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
1209 '{rev}{tags % "{tag}"}{node}'
1216 '{rev}{tags % "{tag}"}{node}'
1210
1217
1211 '\' in outermost strings has to be escaped because it is a directory
1218 '\' in outermost strings has to be escaped because it is a directory
1212 separator on Windows:
1219 separator on Windows:
1213
1220
1214 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
1221 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
1215 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
1222 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
1216 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
1223 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
1217 '\\\\\\\\foo\\\\bar.patch'
1224 '\\\\\\\\foo\\\\bar.patch'
1218 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
1225 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
1219 '\\\\{tags % "{tag}"}'
1226 '\\\\{tags % "{tag}"}'
1220
1227
1221 but inner strings follow the template rules (i.e. '\' is taken as an
1228 but inner strings follow the template rules (i.e. '\' is taken as an
1222 escape character):
1229 escape character):
1223
1230
1224 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
1231 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
1225 '{"c:\\tmp"}'
1232 '{"c:\\tmp"}'
1226 """
1233 """
1227 expander = {
1234 expander = {
1228 b'H': b'{node}',
1235 b'H': b'{node}',
1229 b'R': b'{rev}',
1236 b'R': b'{rev}',
1230 b'h': b'{node|short}',
1237 b'h': b'{node|short}',
1231 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
1238 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
1232 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
1239 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
1233 b'%': b'%',
1240 b'%': b'%',
1234 b'b': b'{reporoot|basename}',
1241 b'b': b'{reporoot|basename}',
1235 }
1242 }
1236 if total is not None:
1243 if total is not None:
1237 expander[b'N'] = b'{total}'
1244 expander[b'N'] = b'{total}'
1238 if seqno is not None:
1245 if seqno is not None:
1239 expander[b'n'] = b'{seqno}'
1246 expander[b'n'] = b'{seqno}'
1240 if total is not None and seqno is not None:
1247 if total is not None and seqno is not None:
1241 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
1248 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
1242 if pathname is not None:
1249 if pathname is not None:
1243 expander[b's'] = b'{pathname|basename}'
1250 expander[b's'] = b'{pathname|basename}'
1244 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
1251 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
1245 expander[b'p'] = b'{pathname}'
1252 expander[b'p'] = b'{pathname}'
1246
1253
1247 newname = []
1254 newname = []
1248 for typ, start, end in templater.scantemplate(pat, raw=True):
1255 for typ, start, end in templater.scantemplate(pat, raw=True):
1249 if typ != b'string':
1256 if typ != b'string':
1250 newname.append(pat[start:end])
1257 newname.append(pat[start:end])
1251 continue
1258 continue
1252 i = start
1259 i = start
1253 while i < end:
1260 while i < end:
1254 n = pat.find(b'%', i, end)
1261 n = pat.find(b'%', i, end)
1255 if n < 0:
1262 if n < 0:
1256 newname.append(stringutil.escapestr(pat[i:end]))
1263 newname.append(stringutil.escapestr(pat[i:end]))
1257 break
1264 break
1258 newname.append(stringutil.escapestr(pat[i:n]))
1265 newname.append(stringutil.escapestr(pat[i:n]))
1259 if n + 2 > end:
1266 if n + 2 > end:
1260 raise error.Abort(
1267 raise error.Abort(
1261 _(b"incomplete format spec in output filename")
1268 _(b"incomplete format spec in output filename")
1262 )
1269 )
1263 c = pat[n + 1 : n + 2]
1270 c = pat[n + 1 : n + 2]
1264 i = n + 2
1271 i = n + 2
1265 try:
1272 try:
1266 newname.append(expander[c])
1273 newname.append(expander[c])
1267 except KeyError:
1274 except KeyError:
1268 raise error.Abort(
1275 raise error.Abort(
1269 _(b"invalid format spec '%%%s' in output filename") % c
1276 _(b"invalid format spec '%%%s' in output filename") % c
1270 )
1277 )
1271 return b''.join(newname)
1278 return b''.join(newname)
1272
1279
1273
1280
1274 def makefilename(ctx, pat, **props):
1281 def makefilename(ctx, pat, **props):
1275 if not pat:
1282 if not pat:
1276 return pat
1283 return pat
1277 tmpl = _buildfntemplate(pat, **props)
1284 tmpl = _buildfntemplate(pat, **props)
1278 # BUG: alias expansion shouldn't be made against template fragments
1285 # BUG: alias expansion shouldn't be made against template fragments
1279 # rewritten from %-format strings, but we have no easy way to partially
1286 # rewritten from %-format strings, but we have no easy way to partially
1280 # disable the expansion.
1287 # disable the expansion.
1281 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1288 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1282
1289
1283
1290
1284 def isstdiofilename(pat):
1291 def isstdiofilename(pat):
1285 """True if the given pat looks like a filename denoting stdin/stdout"""
1292 """True if the given pat looks like a filename denoting stdin/stdout"""
1286 return not pat or pat == b'-'
1293 return not pat or pat == b'-'
1287
1294
1288
1295
1289 class _unclosablefile(object):
1296 class _unclosablefile(object):
1290 def __init__(self, fp):
1297 def __init__(self, fp):
1291 self._fp = fp
1298 self._fp = fp
1292
1299
1293 def close(self):
1300 def close(self):
1294 pass
1301 pass
1295
1302
1296 def __iter__(self):
1303 def __iter__(self):
1297 return iter(self._fp)
1304 return iter(self._fp)
1298
1305
1299 def __getattr__(self, attr):
1306 def __getattr__(self, attr):
1300 return getattr(self._fp, attr)
1307 return getattr(self._fp, attr)
1301
1308
1302 def __enter__(self):
1309 def __enter__(self):
1303 return self
1310 return self
1304
1311
1305 def __exit__(self, exc_type, exc_value, exc_tb):
1312 def __exit__(self, exc_type, exc_value, exc_tb):
1306 pass
1313 pass
1307
1314
1308
1315
1309 def makefileobj(ctx, pat, mode=b'wb', **props):
1316 def makefileobj(ctx, pat, mode=b'wb', **props):
1310 writable = mode not in (b'r', b'rb')
1317 writable = mode not in (b'r', b'rb')
1311
1318
1312 if isstdiofilename(pat):
1319 if isstdiofilename(pat):
1313 repo = ctx.repo()
1320 repo = ctx.repo()
1314 if writable:
1321 if writable:
1315 fp = repo.ui.fout
1322 fp = repo.ui.fout
1316 else:
1323 else:
1317 fp = repo.ui.fin
1324 fp = repo.ui.fin
1318 return _unclosablefile(fp)
1325 return _unclosablefile(fp)
1319 fn = makefilename(ctx, pat, **props)
1326 fn = makefilename(ctx, pat, **props)
1320 return open(fn, mode)
1327 return open(fn, mode)
1321
1328
1322
1329
1323 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1330 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1324 """opens the changelog, manifest, a filelog or a given revlog"""
1331 """opens the changelog, manifest, a filelog or a given revlog"""
1325 cl = opts[b'changelog']
1332 cl = opts[b'changelog']
1326 mf = opts[b'manifest']
1333 mf = opts[b'manifest']
1327 dir = opts[b'dir']
1334 dir = opts[b'dir']
1328 msg = None
1335 msg = None
1329 if cl and mf:
1336 if cl and mf:
1330 msg = _(b'cannot specify --changelog and --manifest at the same time')
1337 msg = _(b'cannot specify --changelog and --manifest at the same time')
1331 elif cl and dir:
1338 elif cl and dir:
1332 msg = _(b'cannot specify --changelog and --dir at the same time')
1339 msg = _(b'cannot specify --changelog and --dir at the same time')
1333 elif cl or mf or dir:
1340 elif cl or mf or dir:
1334 if file_:
1341 if file_:
1335 msg = _(b'cannot specify filename with --changelog or --manifest')
1342 msg = _(b'cannot specify filename with --changelog or --manifest')
1336 elif not repo:
1343 elif not repo:
1337 msg = _(
1344 msg = _(
1338 b'cannot specify --changelog or --manifest or --dir '
1345 b'cannot specify --changelog or --manifest or --dir '
1339 b'without a repository'
1346 b'without a repository'
1340 )
1347 )
1341 if msg:
1348 if msg:
1342 raise error.Abort(msg)
1349 raise error.Abort(msg)
1343
1350
1344 r = None
1351 r = None
1345 if repo:
1352 if repo:
1346 if cl:
1353 if cl:
1347 r = repo.unfiltered().changelog
1354 r = repo.unfiltered().changelog
1348 elif dir:
1355 elif dir:
1349 if b'treemanifest' not in repo.requirements:
1356 if b'treemanifest' not in repo.requirements:
1350 raise error.Abort(
1357 raise error.Abort(
1351 _(
1358 _(
1352 b"--dir can only be used on repos with "
1359 b"--dir can only be used on repos with "
1353 b"treemanifest enabled"
1360 b"treemanifest enabled"
1354 )
1361 )
1355 )
1362 )
1356 if not dir.endswith(b'/'):
1363 if not dir.endswith(b'/'):
1357 dir = dir + b'/'
1364 dir = dir + b'/'
1358 dirlog = repo.manifestlog.getstorage(dir)
1365 dirlog = repo.manifestlog.getstorage(dir)
1359 if len(dirlog):
1366 if len(dirlog):
1360 r = dirlog
1367 r = dirlog
1361 elif mf:
1368 elif mf:
1362 r = repo.manifestlog.getstorage(b'')
1369 r = repo.manifestlog.getstorage(b'')
1363 elif file_:
1370 elif file_:
1364 filelog = repo.file(file_)
1371 filelog = repo.file(file_)
1365 if len(filelog):
1372 if len(filelog):
1366 r = filelog
1373 r = filelog
1367
1374
1368 # Not all storage may be revlogs. If requested, try to return an actual
1375 # Not all storage may be revlogs. If requested, try to return an actual
1369 # revlog instance.
1376 # revlog instance.
1370 if returnrevlog:
1377 if returnrevlog:
1371 if isinstance(r, revlog.revlog):
1378 if isinstance(r, revlog.revlog):
1372 pass
1379 pass
1373 elif util.safehasattr(r, b'_revlog'):
1380 elif util.safehasattr(r, b'_revlog'):
1374 r = r._revlog # pytype: disable=attribute-error
1381 r = r._revlog # pytype: disable=attribute-error
1375 elif r is not None:
1382 elif r is not None:
1376 raise error.Abort(_(b'%r does not appear to be a revlog') % r)
1383 raise error.Abort(_(b'%r does not appear to be a revlog') % r)
1377
1384
1378 if not r:
1385 if not r:
1379 if not returnrevlog:
1386 if not returnrevlog:
1380 raise error.Abort(_(b'cannot give path to non-revlog'))
1387 raise error.Abort(_(b'cannot give path to non-revlog'))
1381
1388
1382 if not file_:
1389 if not file_:
1383 raise error.CommandError(cmd, _(b'invalid arguments'))
1390 raise error.CommandError(cmd, _(b'invalid arguments'))
1384 if not os.path.isfile(file_):
1391 if not os.path.isfile(file_):
1385 raise error.Abort(_(b"revlog '%s' not found") % file_)
1392 raise error.Abort(_(b"revlog '%s' not found") % file_)
1386 r = revlog.revlog(
1393 r = revlog.revlog(
1387 vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + b".i"
1394 vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + b".i"
1388 )
1395 )
1389 return r
1396 return r
1390
1397
1391
1398
1392 def openrevlog(repo, cmd, file_, opts):
1399 def openrevlog(repo, cmd, file_, opts):
1393 """Obtain a revlog backing storage of an item.
1400 """Obtain a revlog backing storage of an item.
1394
1401
1395 This is similar to ``openstorage()`` except it always returns a revlog.
1402 This is similar to ``openstorage()`` except it always returns a revlog.
1396
1403
1397 In most cases, a caller cares about the main storage object - not the
1404 In most cases, a caller cares about the main storage object - not the
1398 revlog backing it. Therefore, this function should only be used by code
1405 revlog backing it. Therefore, this function should only be used by code
1399 that needs to examine low-level revlog implementation details. e.g. debug
1406 that needs to examine low-level revlog implementation details. e.g. debug
1400 commands.
1407 commands.
1401 """
1408 """
1402 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1409 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1403
1410
1404
1411
1405 def copy(ui, repo, pats, opts, rename=False):
1412 def copy(ui, repo, pats, opts, rename=False):
1406 # called with the repo lock held
1413 # called with the repo lock held
1407 #
1414 #
1408 # hgsep => pathname that uses "/" to separate directories
1415 # hgsep => pathname that uses "/" to separate directories
1409 # ossep => pathname that uses os.sep to separate directories
1416 # ossep => pathname that uses os.sep to separate directories
1410 cwd = repo.getcwd()
1417 cwd = repo.getcwd()
1411 targets = {}
1418 targets = {}
1412 after = opts.get(b"after")
1419 after = opts.get(b"after")
1413 dryrun = opts.get(b"dry_run")
1420 dryrun = opts.get(b"dry_run")
1414 wctx = repo[None]
1421 wctx = repo[None]
1415
1422
1416 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1423 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1417
1424
1418 def walkpat(pat):
1425 def walkpat(pat):
1419 srcs = []
1426 srcs = []
1420 if after:
1427 if after:
1421 badstates = b'?'
1428 badstates = b'?'
1422 else:
1429 else:
1423 badstates = b'?r'
1430 badstates = b'?r'
1424 m = scmutil.match(wctx, [pat], opts, globbed=True)
1431 m = scmutil.match(wctx, [pat], opts, globbed=True)
1425 for abs in wctx.walk(m):
1432 for abs in wctx.walk(m):
1426 state = repo.dirstate[abs]
1433 state = repo.dirstate[abs]
1427 rel = uipathfn(abs)
1434 rel = uipathfn(abs)
1428 exact = m.exact(abs)
1435 exact = m.exact(abs)
1429 if state in badstates:
1436 if state in badstates:
1430 if exact and state == b'?':
1437 if exact and state == b'?':
1431 ui.warn(_(b'%s: not copying - file is not managed\n') % rel)
1438 ui.warn(_(b'%s: not copying - file is not managed\n') % rel)
1432 if exact and state == b'r':
1439 if exact and state == b'r':
1433 ui.warn(
1440 ui.warn(
1434 _(
1441 _(
1435 b'%s: not copying - file has been marked for'
1442 b'%s: not copying - file has been marked for'
1436 b' remove\n'
1443 b' remove\n'
1437 )
1444 )
1438 % rel
1445 % rel
1439 )
1446 )
1440 continue
1447 continue
1441 # abs: hgsep
1448 # abs: hgsep
1442 # rel: ossep
1449 # rel: ossep
1443 srcs.append((abs, rel, exact))
1450 srcs.append((abs, rel, exact))
1444 return srcs
1451 return srcs
1445
1452
1446 # abssrc: hgsep
1453 # abssrc: hgsep
1447 # relsrc: ossep
1454 # relsrc: ossep
1448 # otarget: ossep
1455 # otarget: ossep
1449 def copyfile(abssrc, relsrc, otarget, exact):
1456 def copyfile(abssrc, relsrc, otarget, exact):
1450 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1457 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1451 if b'/' in abstarget:
1458 if b'/' in abstarget:
1452 # We cannot normalize abstarget itself, this would prevent
1459 # We cannot normalize abstarget itself, this would prevent
1453 # case only renames, like a => A.
1460 # case only renames, like a => A.
1454 abspath, absname = abstarget.rsplit(b'/', 1)
1461 abspath, absname = abstarget.rsplit(b'/', 1)
1455 abstarget = repo.dirstate.normalize(abspath) + b'/' + absname
1462 abstarget = repo.dirstate.normalize(abspath) + b'/' + absname
1456 reltarget = repo.pathto(abstarget, cwd)
1463 reltarget = repo.pathto(abstarget, cwd)
1457 target = repo.wjoin(abstarget)
1464 target = repo.wjoin(abstarget)
1458 src = repo.wjoin(abssrc)
1465 src = repo.wjoin(abssrc)
1459 state = repo.dirstate[abstarget]
1466 state = repo.dirstate[abstarget]
1460
1467
1461 scmutil.checkportable(ui, abstarget)
1468 scmutil.checkportable(ui, abstarget)
1462
1469
1463 # check for collisions
1470 # check for collisions
1464 prevsrc = targets.get(abstarget)
1471 prevsrc = targets.get(abstarget)
1465 if prevsrc is not None:
1472 if prevsrc is not None:
1466 ui.warn(
1473 ui.warn(
1467 _(b'%s: not overwriting - %s collides with %s\n')
1474 _(b'%s: not overwriting - %s collides with %s\n')
1468 % (
1475 % (
1469 reltarget,
1476 reltarget,
1470 repo.pathto(abssrc, cwd),
1477 repo.pathto(abssrc, cwd),
1471 repo.pathto(prevsrc, cwd),
1478 repo.pathto(prevsrc, cwd),
1472 )
1479 )
1473 )
1480 )
1474 return True # report a failure
1481 return True # report a failure
1475
1482
1476 # check for overwrites
1483 # check for overwrites
1477 exists = os.path.lexists(target)
1484 exists = os.path.lexists(target)
1478 samefile = False
1485 samefile = False
1479 if exists and abssrc != abstarget:
1486 if exists and abssrc != abstarget:
1480 if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize(
1487 if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize(
1481 abstarget
1488 abstarget
1482 ):
1489 ):
1483 if not rename:
1490 if not rename:
1484 ui.warn(_(b"%s: can't copy - same file\n") % reltarget)
1491 ui.warn(_(b"%s: can't copy - same file\n") % reltarget)
1485 return True # report a failure
1492 return True # report a failure
1486 exists = False
1493 exists = False
1487 samefile = True
1494 samefile = True
1488
1495
1489 if not after and exists or after and state in b'mn':
1496 if not after and exists or after and state in b'mn':
1490 if not opts[b'force']:
1497 if not opts[b'force']:
1491 if state in b'mn':
1498 if state in b'mn':
1492 msg = _(b'%s: not overwriting - file already committed\n')
1499 msg = _(b'%s: not overwriting - file already committed\n')
1493 if after:
1500 if after:
1494 flags = b'--after --force'
1501 flags = b'--after --force'
1495 else:
1502 else:
1496 flags = b'--force'
1503 flags = b'--force'
1497 if rename:
1504 if rename:
1498 hint = (
1505 hint = (
1499 _(
1506 _(
1500 b"('hg rename %s' to replace the file by "
1507 b"('hg rename %s' to replace the file by "
1501 b'recording a rename)\n'
1508 b'recording a rename)\n'
1502 )
1509 )
1503 % flags
1510 % flags
1504 )
1511 )
1505 else:
1512 else:
1506 hint = (
1513 hint = (
1507 _(
1514 _(
1508 b"('hg copy %s' to replace the file by "
1515 b"('hg copy %s' to replace the file by "
1509 b'recording a copy)\n'
1516 b'recording a copy)\n'
1510 )
1517 )
1511 % flags
1518 % flags
1512 )
1519 )
1513 else:
1520 else:
1514 msg = _(b'%s: not overwriting - file exists\n')
1521 msg = _(b'%s: not overwriting - file exists\n')
1515 if rename:
1522 if rename:
1516 hint = _(
1523 hint = _(
1517 b"('hg rename --after' to record the rename)\n"
1524 b"('hg rename --after' to record the rename)\n"
1518 )
1525 )
1519 else:
1526 else:
1520 hint = _(b"('hg copy --after' to record the copy)\n")
1527 hint = _(b"('hg copy --after' to record the copy)\n")
1521 ui.warn(msg % reltarget)
1528 ui.warn(msg % reltarget)
1522 ui.warn(hint)
1529 ui.warn(hint)
1523 return True # report a failure
1530 return True # report a failure
1524
1531
1525 if after:
1532 if after:
1526 if not exists:
1533 if not exists:
1527 if rename:
1534 if rename:
1528 ui.warn(
1535 ui.warn(
1529 _(b'%s: not recording move - %s does not exist\n')
1536 _(b'%s: not recording move - %s does not exist\n')
1530 % (relsrc, reltarget)
1537 % (relsrc, reltarget)
1531 )
1538 )
1532 else:
1539 else:
1533 ui.warn(
1540 ui.warn(
1534 _(b'%s: not recording copy - %s does not exist\n')
1541 _(b'%s: not recording copy - %s does not exist\n')
1535 % (relsrc, reltarget)
1542 % (relsrc, reltarget)
1536 )
1543 )
1537 return True # report a failure
1544 return True # report a failure
1538 elif not dryrun:
1545 elif not dryrun:
1539 try:
1546 try:
1540 if exists:
1547 if exists:
1541 os.unlink(target)
1548 os.unlink(target)
1542 targetdir = os.path.dirname(target) or b'.'
1549 targetdir = os.path.dirname(target) or b'.'
1543 if not os.path.isdir(targetdir):
1550 if not os.path.isdir(targetdir):
1544 os.makedirs(targetdir)
1551 os.makedirs(targetdir)
1545 if samefile:
1552 if samefile:
1546 tmp = target + b"~hgrename"
1553 tmp = target + b"~hgrename"
1547 os.rename(src, tmp)
1554 os.rename(src, tmp)
1548 os.rename(tmp, target)
1555 os.rename(tmp, target)
1549 else:
1556 else:
1550 # Preserve stat info on renames, not on copies; this matches
1557 # Preserve stat info on renames, not on copies; this matches
1551 # Linux CLI behavior.
1558 # Linux CLI behavior.
1552 util.copyfile(src, target, copystat=rename)
1559 util.copyfile(src, target, copystat=rename)
1553 srcexists = True
1560 srcexists = True
1554 except IOError as inst:
1561 except IOError as inst:
1555 if inst.errno == errno.ENOENT:
1562 if inst.errno == errno.ENOENT:
1556 ui.warn(_(b'%s: deleted in working directory\n') % relsrc)
1563 ui.warn(_(b'%s: deleted in working directory\n') % relsrc)
1557 srcexists = False
1564 srcexists = False
1558 else:
1565 else:
1559 ui.warn(
1566 ui.warn(
1560 _(b'%s: cannot copy - %s\n')
1567 _(b'%s: cannot copy - %s\n')
1561 % (relsrc, encoding.strtolocal(inst.strerror))
1568 % (relsrc, encoding.strtolocal(inst.strerror))
1562 )
1569 )
1563 return True # report a failure
1570 return True # report a failure
1564
1571
1565 if ui.verbose or not exact:
1572 if ui.verbose or not exact:
1566 if rename:
1573 if rename:
1567 ui.status(_(b'moving %s to %s\n') % (relsrc, reltarget))
1574 ui.status(_(b'moving %s to %s\n') % (relsrc, reltarget))
1568 else:
1575 else:
1569 ui.status(_(b'copying %s to %s\n') % (relsrc, reltarget))
1576 ui.status(_(b'copying %s to %s\n') % (relsrc, reltarget))
1570
1577
1571 targets[abstarget] = abssrc
1578 targets[abstarget] = abssrc
1572
1579
1573 # fix up dirstate
1580 # fix up dirstate
1574 scmutil.dirstatecopy(
1581 scmutil.dirstatecopy(
1575 ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd
1582 ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd
1576 )
1583 )
1577 if rename and not dryrun:
1584 if rename and not dryrun:
1578 if not after and srcexists and not samefile:
1585 if not after and srcexists and not samefile:
1579 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
1586 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
1580 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1587 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1581 wctx.forget([abssrc])
1588 wctx.forget([abssrc])
1582
1589
1583 # pat: ossep
1590 # pat: ossep
1584 # dest ossep
1591 # dest ossep
1585 # srcs: list of (hgsep, hgsep, ossep, bool)
1592 # srcs: list of (hgsep, hgsep, ossep, bool)
1586 # return: function that takes hgsep and returns ossep
1593 # return: function that takes hgsep and returns ossep
1587 def targetpathfn(pat, dest, srcs):
1594 def targetpathfn(pat, dest, srcs):
1588 if os.path.isdir(pat):
1595 if os.path.isdir(pat):
1589 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1596 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1590 abspfx = util.localpath(abspfx)
1597 abspfx = util.localpath(abspfx)
1591 if destdirexists:
1598 if destdirexists:
1592 striplen = len(os.path.split(abspfx)[0])
1599 striplen = len(os.path.split(abspfx)[0])
1593 else:
1600 else:
1594 striplen = len(abspfx)
1601 striplen = len(abspfx)
1595 if striplen:
1602 if striplen:
1596 striplen += len(pycompat.ossep)
1603 striplen += len(pycompat.ossep)
1597 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1604 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1598 elif destdirexists:
1605 elif destdirexists:
1599 res = lambda p: os.path.join(
1606 res = lambda p: os.path.join(
1600 dest, os.path.basename(util.localpath(p))
1607 dest, os.path.basename(util.localpath(p))
1601 )
1608 )
1602 else:
1609 else:
1603 res = lambda p: dest
1610 res = lambda p: dest
1604 return res
1611 return res
1605
1612
1606 # pat: ossep
1613 # pat: ossep
1607 # dest ossep
1614 # dest ossep
1608 # srcs: list of (hgsep, hgsep, ossep, bool)
1615 # srcs: list of (hgsep, hgsep, ossep, bool)
1609 # return: function that takes hgsep and returns ossep
1616 # return: function that takes hgsep and returns ossep
1610 def targetpathafterfn(pat, dest, srcs):
1617 def targetpathafterfn(pat, dest, srcs):
1611 if matchmod.patkind(pat):
1618 if matchmod.patkind(pat):
1612 # a mercurial pattern
1619 # a mercurial pattern
1613 res = lambda p: os.path.join(
1620 res = lambda p: os.path.join(
1614 dest, os.path.basename(util.localpath(p))
1621 dest, os.path.basename(util.localpath(p))
1615 )
1622 )
1616 else:
1623 else:
1617 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1624 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1618 if len(abspfx) < len(srcs[0][0]):
1625 if len(abspfx) < len(srcs[0][0]):
1619 # A directory. Either the target path contains the last
1626 # A directory. Either the target path contains the last
1620 # component of the source path or it does not.
1627 # component of the source path or it does not.
1621 def evalpath(striplen):
1628 def evalpath(striplen):
1622 score = 0
1629 score = 0
1623 for s in srcs:
1630 for s in srcs:
1624 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1631 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1625 if os.path.lexists(t):
1632 if os.path.lexists(t):
1626 score += 1
1633 score += 1
1627 return score
1634 return score
1628
1635
1629 abspfx = util.localpath(abspfx)
1636 abspfx = util.localpath(abspfx)
1630 striplen = len(abspfx)
1637 striplen = len(abspfx)
1631 if striplen:
1638 if striplen:
1632 striplen += len(pycompat.ossep)
1639 striplen += len(pycompat.ossep)
1633 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1640 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1634 score = evalpath(striplen)
1641 score = evalpath(striplen)
1635 striplen1 = len(os.path.split(abspfx)[0])
1642 striplen1 = len(os.path.split(abspfx)[0])
1636 if striplen1:
1643 if striplen1:
1637 striplen1 += len(pycompat.ossep)
1644 striplen1 += len(pycompat.ossep)
1638 if evalpath(striplen1) > score:
1645 if evalpath(striplen1) > score:
1639 striplen = striplen1
1646 striplen = striplen1
1640 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1647 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1641 else:
1648 else:
1642 # a file
1649 # a file
1643 if destdirexists:
1650 if destdirexists:
1644 res = lambda p: os.path.join(
1651 res = lambda p: os.path.join(
1645 dest, os.path.basename(util.localpath(p))
1652 dest, os.path.basename(util.localpath(p))
1646 )
1653 )
1647 else:
1654 else:
1648 res = lambda p: dest
1655 res = lambda p: dest
1649 return res
1656 return res
1650
1657
1651 pats = scmutil.expandpats(pats)
1658 pats = scmutil.expandpats(pats)
1652 if not pats:
1659 if not pats:
1653 raise error.Abort(_(b'no source or destination specified'))
1660 raise error.Abort(_(b'no source or destination specified'))
1654 if len(pats) == 1:
1661 if len(pats) == 1:
1655 raise error.Abort(_(b'no destination specified'))
1662 raise error.Abort(_(b'no destination specified'))
1656 dest = pats.pop()
1663 dest = pats.pop()
1657 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1664 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1658 if not destdirexists:
1665 if not destdirexists:
1659 if len(pats) > 1 or matchmod.patkind(pats[0]):
1666 if len(pats) > 1 or matchmod.patkind(pats[0]):
1660 raise error.Abort(
1667 raise error.Abort(
1661 _(
1668 _(
1662 b'with multiple sources, destination must be an '
1669 b'with multiple sources, destination must be an '
1663 b'existing directory'
1670 b'existing directory'
1664 )
1671 )
1665 )
1672 )
1666 if util.endswithsep(dest):
1673 if util.endswithsep(dest):
1667 raise error.Abort(_(b'destination %s is not a directory') % dest)
1674 raise error.Abort(_(b'destination %s is not a directory') % dest)
1668
1675
1669 tfn = targetpathfn
1676 tfn = targetpathfn
1670 if after:
1677 if after:
1671 tfn = targetpathafterfn
1678 tfn = targetpathafterfn
1672 copylist = []
1679 copylist = []
1673 for pat in pats:
1680 for pat in pats:
1674 srcs = walkpat(pat)
1681 srcs = walkpat(pat)
1675 if not srcs:
1682 if not srcs:
1676 continue
1683 continue
1677 copylist.append((tfn(pat, dest, srcs), srcs))
1684 copylist.append((tfn(pat, dest, srcs), srcs))
1678 if not copylist:
1685 if not copylist:
1679 raise error.Abort(_(b'no files to copy'))
1686 raise error.Abort(_(b'no files to copy'))
1680
1687
1681 errors = 0
1688 errors = 0
1682 for targetpath, srcs in copylist:
1689 for targetpath, srcs in copylist:
1683 for abssrc, relsrc, exact in srcs:
1690 for abssrc, relsrc, exact in srcs:
1684 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1691 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1685 errors += 1
1692 errors += 1
1686
1693
1687 return errors != 0
1694 return errors != 0
1688
1695
1689
1696
1690 ## facility to let extension process additional data into an import patch
1697 ## facility to let extension process additional data into an import patch
1691 # list of identifier to be executed in order
1698 # list of identifier to be executed in order
1692 extrapreimport = [] # run before commit
1699 extrapreimport = [] # run before commit
1693 extrapostimport = [] # run after commit
1700 extrapostimport = [] # run after commit
1694 # mapping from identifier to actual import function
1701 # mapping from identifier to actual import function
1695 #
1702 #
1696 # 'preimport' are run before the commit is made and are provided the following
1703 # 'preimport' are run before the commit is made and are provided the following
1697 # arguments:
1704 # arguments:
1698 # - repo: the localrepository instance,
1705 # - repo: the localrepository instance,
1699 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1706 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1700 # - extra: the future extra dictionary of the changeset, please mutate it,
1707 # - extra: the future extra dictionary of the changeset, please mutate it,
1701 # - opts: the import options.
1708 # - opts: the import options.
1702 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1709 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1703 # mutation of in memory commit and more. Feel free to rework the code to get
1710 # mutation of in memory commit and more. Feel free to rework the code to get
1704 # there.
1711 # there.
1705 extrapreimportmap = {}
1712 extrapreimportmap = {}
1706 # 'postimport' are run after the commit is made and are provided the following
1713 # 'postimport' are run after the commit is made and are provided the following
1707 # argument:
1714 # argument:
1708 # - ctx: the changectx created by import.
1715 # - ctx: the changectx created by import.
1709 extrapostimportmap = {}
1716 extrapostimportmap = {}
1710
1717
1711
1718
1712 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1719 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1713 """Utility function used by commands.import to import a single patch
1720 """Utility function used by commands.import to import a single patch
1714
1721
1715 This function is explicitly defined here to help the evolve extension to
1722 This function is explicitly defined here to help the evolve extension to
1716 wrap this part of the import logic.
1723 wrap this part of the import logic.
1717
1724
1718 The API is currently a bit ugly because it a simple code translation from
1725 The API is currently a bit ugly because it a simple code translation from
1719 the import command. Feel free to make it better.
1726 the import command. Feel free to make it better.
1720
1727
1721 :patchdata: a dictionary containing parsed patch data (such as from
1728 :patchdata: a dictionary containing parsed patch data (such as from
1722 ``patch.extract()``)
1729 ``patch.extract()``)
1723 :parents: nodes that will be parent of the created commit
1730 :parents: nodes that will be parent of the created commit
1724 :opts: the full dict of option passed to the import command
1731 :opts: the full dict of option passed to the import command
1725 :msgs: list to save commit message to.
1732 :msgs: list to save commit message to.
1726 (used in case we need to save it when failing)
1733 (used in case we need to save it when failing)
1727 :updatefunc: a function that update a repo to a given node
1734 :updatefunc: a function that update a repo to a given node
1728 updatefunc(<repo>, <node>)
1735 updatefunc(<repo>, <node>)
1729 """
1736 """
1730 # avoid cycle context -> subrepo -> cmdutil
1737 # avoid cycle context -> subrepo -> cmdutil
1731 from . import context
1738 from . import context
1732
1739
1733 tmpname = patchdata.get(b'filename')
1740 tmpname = patchdata.get(b'filename')
1734 message = patchdata.get(b'message')
1741 message = patchdata.get(b'message')
1735 user = opts.get(b'user') or patchdata.get(b'user')
1742 user = opts.get(b'user') or patchdata.get(b'user')
1736 date = opts.get(b'date') or patchdata.get(b'date')
1743 date = opts.get(b'date') or patchdata.get(b'date')
1737 branch = patchdata.get(b'branch')
1744 branch = patchdata.get(b'branch')
1738 nodeid = patchdata.get(b'nodeid')
1745 nodeid = patchdata.get(b'nodeid')
1739 p1 = patchdata.get(b'p1')
1746 p1 = patchdata.get(b'p1')
1740 p2 = patchdata.get(b'p2')
1747 p2 = patchdata.get(b'p2')
1741
1748
1742 nocommit = opts.get(b'no_commit')
1749 nocommit = opts.get(b'no_commit')
1743 importbranch = opts.get(b'import_branch')
1750 importbranch = opts.get(b'import_branch')
1744 update = not opts.get(b'bypass')
1751 update = not opts.get(b'bypass')
1745 strip = opts[b"strip"]
1752 strip = opts[b"strip"]
1746 prefix = opts[b"prefix"]
1753 prefix = opts[b"prefix"]
1747 sim = float(opts.get(b'similarity') or 0)
1754 sim = float(opts.get(b'similarity') or 0)
1748
1755
1749 if not tmpname:
1756 if not tmpname:
1750 return None, None, False
1757 return None, None, False
1751
1758
1752 rejects = False
1759 rejects = False
1753
1760
1754 cmdline_message = logmessage(ui, opts)
1761 cmdline_message = logmessage(ui, opts)
1755 if cmdline_message:
1762 if cmdline_message:
1756 # pickup the cmdline msg
1763 # pickup the cmdline msg
1757 message = cmdline_message
1764 message = cmdline_message
1758 elif message:
1765 elif message:
1759 # pickup the patch msg
1766 # pickup the patch msg
1760 message = message.strip()
1767 message = message.strip()
1761 else:
1768 else:
1762 # launch the editor
1769 # launch the editor
1763 message = None
1770 message = None
1764 ui.debug(b'message:\n%s\n' % (message or b''))
1771 ui.debug(b'message:\n%s\n' % (message or b''))
1765
1772
1766 if len(parents) == 1:
1773 if len(parents) == 1:
1767 parents.append(repo[nullid])
1774 parents.append(repo[nullid])
1768 if opts.get(b'exact'):
1775 if opts.get(b'exact'):
1769 if not nodeid or not p1:
1776 if not nodeid or not p1:
1770 raise error.Abort(_(b'not a Mercurial patch'))
1777 raise error.Abort(_(b'not a Mercurial patch'))
1771 p1 = repo[p1]
1778 p1 = repo[p1]
1772 p2 = repo[p2 or nullid]
1779 p2 = repo[p2 or nullid]
1773 elif p2:
1780 elif p2:
1774 try:
1781 try:
1775 p1 = repo[p1]
1782 p1 = repo[p1]
1776 p2 = repo[p2]
1783 p2 = repo[p2]
1777 # Without any options, consider p2 only if the
1784 # Without any options, consider p2 only if the
1778 # patch is being applied on top of the recorded
1785 # patch is being applied on top of the recorded
1779 # first parent.
1786 # first parent.
1780 if p1 != parents[0]:
1787 if p1 != parents[0]:
1781 p1 = parents[0]
1788 p1 = parents[0]
1782 p2 = repo[nullid]
1789 p2 = repo[nullid]
1783 except error.RepoError:
1790 except error.RepoError:
1784 p1, p2 = parents
1791 p1, p2 = parents
1785 if p2.node() == nullid:
1792 if p2.node() == nullid:
1786 ui.warn(
1793 ui.warn(
1787 _(
1794 _(
1788 b"warning: import the patch as a normal revision\n"
1795 b"warning: import the patch as a normal revision\n"
1789 b"(use --exact to import the patch as a merge)\n"
1796 b"(use --exact to import the patch as a merge)\n"
1790 )
1797 )
1791 )
1798 )
1792 else:
1799 else:
1793 p1, p2 = parents
1800 p1, p2 = parents
1794
1801
1795 n = None
1802 n = None
1796 if update:
1803 if update:
1797 if p1 != parents[0]:
1804 if p1 != parents[0]:
1798 updatefunc(repo, p1.node())
1805 updatefunc(repo, p1.node())
1799 if p2 != parents[1]:
1806 if p2 != parents[1]:
1800 repo.setparents(p1.node(), p2.node())
1807 repo.setparents(p1.node(), p2.node())
1801
1808
1802 if opts.get(b'exact') or importbranch:
1809 if opts.get(b'exact') or importbranch:
1803 repo.dirstate.setbranch(branch or b'default')
1810 repo.dirstate.setbranch(branch or b'default')
1804
1811
1805 partial = opts.get(b'partial', False)
1812 partial = opts.get(b'partial', False)
1806 files = set()
1813 files = set()
1807 try:
1814 try:
1808 patch.patch(
1815 patch.patch(
1809 ui,
1816 ui,
1810 repo,
1817 repo,
1811 tmpname,
1818 tmpname,
1812 strip=strip,
1819 strip=strip,
1813 prefix=prefix,
1820 prefix=prefix,
1814 files=files,
1821 files=files,
1815 eolmode=None,
1822 eolmode=None,
1816 similarity=sim / 100.0,
1823 similarity=sim / 100.0,
1817 )
1824 )
1818 except error.PatchError as e:
1825 except error.PatchError as e:
1819 if not partial:
1826 if not partial:
1820 raise error.Abort(pycompat.bytestr(e))
1827 raise error.Abort(pycompat.bytestr(e))
1821 if partial:
1828 if partial:
1822 rejects = True
1829 rejects = True
1823
1830
1824 files = list(files)
1831 files = list(files)
1825 if nocommit:
1832 if nocommit:
1826 if message:
1833 if message:
1827 msgs.append(message)
1834 msgs.append(message)
1828 else:
1835 else:
1829 if opts.get(b'exact') or p2:
1836 if opts.get(b'exact') or p2:
1830 # If you got here, you either use --force and know what
1837 # If you got here, you either use --force and know what
1831 # you are doing or used --exact or a merge patch while
1838 # you are doing or used --exact or a merge patch while
1832 # being updated to its first parent.
1839 # being updated to its first parent.
1833 m = None
1840 m = None
1834 else:
1841 else:
1835 m = scmutil.matchfiles(repo, files or [])
1842 m = scmutil.matchfiles(repo, files or [])
1836 editform = mergeeditform(repo[None], b'import.normal')
1843 editform = mergeeditform(repo[None], b'import.normal')
1837 if opts.get(b'exact'):
1844 if opts.get(b'exact'):
1838 editor = None
1845 editor = None
1839 else:
1846 else:
1840 editor = getcommiteditor(
1847 editor = getcommiteditor(
1841 editform=editform, **pycompat.strkwargs(opts)
1848 editform=editform, **pycompat.strkwargs(opts)
1842 )
1849 )
1843 extra = {}
1850 extra = {}
1844 for idfunc in extrapreimport:
1851 for idfunc in extrapreimport:
1845 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1852 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1846 overrides = {}
1853 overrides = {}
1847 if partial:
1854 if partial:
1848 overrides[(b'ui', b'allowemptycommit')] = True
1855 overrides[(b'ui', b'allowemptycommit')] = True
1849 if opts.get(b'secret'):
1856 if opts.get(b'secret'):
1850 overrides[(b'phases', b'new-commit')] = b'secret'
1857 overrides[(b'phases', b'new-commit')] = b'secret'
1851 with repo.ui.configoverride(overrides, b'import'):
1858 with repo.ui.configoverride(overrides, b'import'):
1852 n = repo.commit(
1859 n = repo.commit(
1853 message, user, date, match=m, editor=editor, extra=extra
1860 message, user, date, match=m, editor=editor, extra=extra
1854 )
1861 )
1855 for idfunc in extrapostimport:
1862 for idfunc in extrapostimport:
1856 extrapostimportmap[idfunc](repo[n])
1863 extrapostimportmap[idfunc](repo[n])
1857 else:
1864 else:
1858 if opts.get(b'exact') or importbranch:
1865 if opts.get(b'exact') or importbranch:
1859 branch = branch or b'default'
1866 branch = branch or b'default'
1860 else:
1867 else:
1861 branch = p1.branch()
1868 branch = p1.branch()
1862 store = patch.filestore()
1869 store = patch.filestore()
1863 try:
1870 try:
1864 files = set()
1871 files = set()
1865 try:
1872 try:
1866 patch.patchrepo(
1873 patch.patchrepo(
1867 ui,
1874 ui,
1868 repo,
1875 repo,
1869 p1,
1876 p1,
1870 store,
1877 store,
1871 tmpname,
1878 tmpname,
1872 strip,
1879 strip,
1873 prefix,
1880 prefix,
1874 files,
1881 files,
1875 eolmode=None,
1882 eolmode=None,
1876 )
1883 )
1877 except error.PatchError as e:
1884 except error.PatchError as e:
1878 raise error.Abort(stringutil.forcebytestr(e))
1885 raise error.Abort(stringutil.forcebytestr(e))
1879 if opts.get(b'exact'):
1886 if opts.get(b'exact'):
1880 editor = None
1887 editor = None
1881 else:
1888 else:
1882 editor = getcommiteditor(editform=b'import.bypass')
1889 editor = getcommiteditor(editform=b'import.bypass')
1883 memctx = context.memctx(
1890 memctx = context.memctx(
1884 repo,
1891 repo,
1885 (p1.node(), p2.node()),
1892 (p1.node(), p2.node()),
1886 message,
1893 message,
1887 files=files,
1894 files=files,
1888 filectxfn=store,
1895 filectxfn=store,
1889 user=user,
1896 user=user,
1890 date=date,
1897 date=date,
1891 branch=branch,
1898 branch=branch,
1892 editor=editor,
1899 editor=editor,
1893 )
1900 )
1894 n = memctx.commit()
1901 n = memctx.commit()
1895 finally:
1902 finally:
1896 store.close()
1903 store.close()
1897 if opts.get(b'exact') and nocommit:
1904 if opts.get(b'exact') and nocommit:
1898 # --exact with --no-commit is still useful in that it does merge
1905 # --exact with --no-commit is still useful in that it does merge
1899 # and branch bits
1906 # and branch bits
1900 ui.warn(_(b"warning: can't check exact import with --no-commit\n"))
1907 ui.warn(_(b"warning: can't check exact import with --no-commit\n"))
1901 elif opts.get(b'exact') and (not n or hex(n) != nodeid):
1908 elif opts.get(b'exact') and (not n or hex(n) != nodeid):
1902 raise error.Abort(_(b'patch is damaged or loses information'))
1909 raise error.Abort(_(b'patch is damaged or loses information'))
1903 msg = _(b'applied to working directory')
1910 msg = _(b'applied to working directory')
1904 if n:
1911 if n:
1905 # i18n: refers to a short changeset id
1912 # i18n: refers to a short changeset id
1906 msg = _(b'created %s') % short(n)
1913 msg = _(b'created %s') % short(n)
1907 return msg, n, rejects
1914 return msg, n, rejects
1908
1915
1909
1916
1910 # facility to let extensions include additional data in an exported patch
1917 # facility to let extensions include additional data in an exported patch
1911 # list of identifiers to be executed in order
1918 # list of identifiers to be executed in order
1912 extraexport = []
1919 extraexport = []
1913 # mapping from identifier to actual export function
1920 # mapping from identifier to actual export function
1914 # function as to return a string to be added to the header or None
1921 # function as to return a string to be added to the header or None
1915 # it is given two arguments (sequencenumber, changectx)
1922 # it is given two arguments (sequencenumber, changectx)
1916 extraexportmap = {}
1923 extraexportmap = {}
1917
1924
1918
1925
1919 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1926 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1920 node = scmutil.binnode(ctx)
1927 node = scmutil.binnode(ctx)
1921 parents = [p.node() for p in ctx.parents() if p]
1928 parents = [p.node() for p in ctx.parents() if p]
1922 branch = ctx.branch()
1929 branch = ctx.branch()
1923 if switch_parent:
1930 if switch_parent:
1924 parents.reverse()
1931 parents.reverse()
1925
1932
1926 if parents:
1933 if parents:
1927 prev = parents[0]
1934 prev = parents[0]
1928 else:
1935 else:
1929 prev = nullid
1936 prev = nullid
1930
1937
1931 fm.context(ctx=ctx)
1938 fm.context(ctx=ctx)
1932 fm.plain(b'# HG changeset patch\n')
1939 fm.plain(b'# HG changeset patch\n')
1933 fm.write(b'user', b'# User %s\n', ctx.user())
1940 fm.write(b'user', b'# User %s\n', ctx.user())
1934 fm.plain(b'# Date %d %d\n' % ctx.date())
1941 fm.plain(b'# Date %d %d\n' % ctx.date())
1935 fm.write(b'date', b'# %s\n', fm.formatdate(ctx.date()))
1942 fm.write(b'date', b'# %s\n', fm.formatdate(ctx.date()))
1936 fm.condwrite(
1943 fm.condwrite(
1937 branch and branch != b'default', b'branch', b'# Branch %s\n', branch
1944 branch and branch != b'default', b'branch', b'# Branch %s\n', branch
1938 )
1945 )
1939 fm.write(b'node', b'# Node ID %s\n', hex(node))
1946 fm.write(b'node', b'# Node ID %s\n', hex(node))
1940 fm.plain(b'# Parent %s\n' % hex(prev))
1947 fm.plain(b'# Parent %s\n' % hex(prev))
1941 if len(parents) > 1:
1948 if len(parents) > 1:
1942 fm.plain(b'# Parent %s\n' % hex(parents[1]))
1949 fm.plain(b'# Parent %s\n' % hex(parents[1]))
1943 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name=b'node'))
1950 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name=b'node'))
1944
1951
1945 # TODO: redesign extraexportmap function to support formatter
1952 # TODO: redesign extraexportmap function to support formatter
1946 for headerid in extraexport:
1953 for headerid in extraexport:
1947 header = extraexportmap[headerid](seqno, ctx)
1954 header = extraexportmap[headerid](seqno, ctx)
1948 if header is not None:
1955 if header is not None:
1949 fm.plain(b'# %s\n' % header)
1956 fm.plain(b'# %s\n' % header)
1950
1957
1951 fm.write(b'desc', b'%s\n', ctx.description().rstrip())
1958 fm.write(b'desc', b'%s\n', ctx.description().rstrip())
1952 fm.plain(b'\n')
1959 fm.plain(b'\n')
1953
1960
1954 if fm.isplain():
1961 if fm.isplain():
1955 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1962 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1956 for chunk, label in chunkiter:
1963 for chunk, label in chunkiter:
1957 fm.plain(chunk, label=label)
1964 fm.plain(chunk, label=label)
1958 else:
1965 else:
1959 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1966 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1960 # TODO: make it structured?
1967 # TODO: make it structured?
1961 fm.data(diff=b''.join(chunkiter))
1968 fm.data(diff=b''.join(chunkiter))
1962
1969
1963
1970
1964 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1971 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1965 """Export changesets to stdout or a single file"""
1972 """Export changesets to stdout or a single file"""
1966 for seqno, rev in enumerate(revs, 1):
1973 for seqno, rev in enumerate(revs, 1):
1967 ctx = repo[rev]
1974 ctx = repo[rev]
1968 if not dest.startswith(b'<'):
1975 if not dest.startswith(b'<'):
1969 repo.ui.note(b"%s\n" % dest)
1976 repo.ui.note(b"%s\n" % dest)
1970 fm.startitem()
1977 fm.startitem()
1971 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1978 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1972
1979
1973
1980
1974 def _exportfntemplate(
1981 def _exportfntemplate(
1975 repo, revs, basefm, fntemplate, switch_parent, diffopts, match
1982 repo, revs, basefm, fntemplate, switch_parent, diffopts, match
1976 ):
1983 ):
1977 """Export changesets to possibly multiple files"""
1984 """Export changesets to possibly multiple files"""
1978 total = len(revs)
1985 total = len(revs)
1979 revwidth = max(len(str(rev)) for rev in revs)
1986 revwidth = max(len(str(rev)) for rev in revs)
1980 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1987 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1981
1988
1982 for seqno, rev in enumerate(revs, 1):
1989 for seqno, rev in enumerate(revs, 1):
1983 ctx = repo[rev]
1990 ctx = repo[rev]
1984 dest = makefilename(
1991 dest = makefilename(
1985 ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth
1992 ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth
1986 )
1993 )
1987 filemap.setdefault(dest, []).append((seqno, rev))
1994 filemap.setdefault(dest, []).append((seqno, rev))
1988
1995
1989 for dest in filemap:
1996 for dest in filemap:
1990 with formatter.maybereopen(basefm, dest) as fm:
1997 with formatter.maybereopen(basefm, dest) as fm:
1991 repo.ui.note(b"%s\n" % dest)
1998 repo.ui.note(b"%s\n" % dest)
1992 for seqno, rev in filemap[dest]:
1999 for seqno, rev in filemap[dest]:
1993 fm.startitem()
2000 fm.startitem()
1994 ctx = repo[rev]
2001 ctx = repo[rev]
1995 _exportsingle(
2002 _exportsingle(
1996 repo, ctx, fm, match, switch_parent, seqno, diffopts
2003 repo, ctx, fm, match, switch_parent, seqno, diffopts
1997 )
2004 )
1998
2005
1999
2006
2000 def _prefetchchangedfiles(repo, revs, match):
2007 def _prefetchchangedfiles(repo, revs, match):
2001 allfiles = set()
2008 allfiles = set()
2002 for rev in revs:
2009 for rev in revs:
2003 for file in repo[rev].files():
2010 for file in repo[rev].files():
2004 if not match or match(file):
2011 if not match or match(file):
2005 allfiles.add(file)
2012 allfiles.add(file)
2006 scmutil.prefetchfiles(repo, revs, scmutil.matchfiles(repo, allfiles))
2013 scmutil.prefetchfiles(repo, revs, scmutil.matchfiles(repo, allfiles))
2007
2014
2008
2015
2009 def export(
2016 def export(
2010 repo,
2017 repo,
2011 revs,
2018 revs,
2012 basefm,
2019 basefm,
2013 fntemplate=b'hg-%h.patch',
2020 fntemplate=b'hg-%h.patch',
2014 switch_parent=False,
2021 switch_parent=False,
2015 opts=None,
2022 opts=None,
2016 match=None,
2023 match=None,
2017 ):
2024 ):
2018 '''export changesets as hg patches
2025 '''export changesets as hg patches
2019
2026
2020 Args:
2027 Args:
2021 repo: The repository from which we're exporting revisions.
2028 repo: The repository from which we're exporting revisions.
2022 revs: A list of revisions to export as revision numbers.
2029 revs: A list of revisions to export as revision numbers.
2023 basefm: A formatter to which patches should be written.
2030 basefm: A formatter to which patches should be written.
2024 fntemplate: An optional string to use for generating patch file names.
2031 fntemplate: An optional string to use for generating patch file names.
2025 switch_parent: If True, show diffs against second parent when not nullid.
2032 switch_parent: If True, show diffs against second parent when not nullid.
2026 Default is false, which always shows diff against p1.
2033 Default is false, which always shows diff against p1.
2027 opts: diff options to use for generating the patch.
2034 opts: diff options to use for generating the patch.
2028 match: If specified, only export changes to files matching this matcher.
2035 match: If specified, only export changes to files matching this matcher.
2029
2036
2030 Returns:
2037 Returns:
2031 Nothing.
2038 Nothing.
2032
2039
2033 Side Effect:
2040 Side Effect:
2034 "HG Changeset Patch" data is emitted to one of the following
2041 "HG Changeset Patch" data is emitted to one of the following
2035 destinations:
2042 destinations:
2036 fntemplate specified: Each rev is written to a unique file named using
2043 fntemplate specified: Each rev is written to a unique file named using
2037 the given template.
2044 the given template.
2038 Otherwise: All revs will be written to basefm.
2045 Otherwise: All revs will be written to basefm.
2039 '''
2046 '''
2040 _prefetchchangedfiles(repo, revs, match)
2047 _prefetchchangedfiles(repo, revs, match)
2041
2048
2042 if not fntemplate:
2049 if not fntemplate:
2043 _exportfile(
2050 _exportfile(
2044 repo, revs, basefm, b'<unnamed>', switch_parent, opts, match
2051 repo, revs, basefm, b'<unnamed>', switch_parent, opts, match
2045 )
2052 )
2046 else:
2053 else:
2047 _exportfntemplate(
2054 _exportfntemplate(
2048 repo, revs, basefm, fntemplate, switch_parent, opts, match
2055 repo, revs, basefm, fntemplate, switch_parent, opts, match
2049 )
2056 )
2050
2057
2051
2058
2052 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
2059 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
2053 """Export changesets to the given file stream"""
2060 """Export changesets to the given file stream"""
2054 _prefetchchangedfiles(repo, revs, match)
2061 _prefetchchangedfiles(repo, revs, match)
2055
2062
2056 dest = getattr(fp, 'name', b'<unnamed>')
2063 dest = getattr(fp, 'name', b'<unnamed>')
2057 with formatter.formatter(repo.ui, fp, b'export', {}) as fm:
2064 with formatter.formatter(repo.ui, fp, b'export', {}) as fm:
2058 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
2065 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
2059
2066
2060
2067
2061 def showmarker(fm, marker, index=None):
2068 def showmarker(fm, marker, index=None):
2062 """utility function to display obsolescence marker in a readable way
2069 """utility function to display obsolescence marker in a readable way
2063
2070
2064 To be used by debug function."""
2071 To be used by debug function."""
2065 if index is not None:
2072 if index is not None:
2066 fm.write(b'index', b'%i ', index)
2073 fm.write(b'index', b'%i ', index)
2067 fm.write(b'prednode', b'%s ', hex(marker.prednode()))
2074 fm.write(b'prednode', b'%s ', hex(marker.prednode()))
2068 succs = marker.succnodes()
2075 succs = marker.succnodes()
2069 fm.condwrite(
2076 fm.condwrite(
2070 succs,
2077 succs,
2071 b'succnodes',
2078 b'succnodes',
2072 b'%s ',
2079 b'%s ',
2073 fm.formatlist(map(hex, succs), name=b'node'),
2080 fm.formatlist(map(hex, succs), name=b'node'),
2074 )
2081 )
2075 fm.write(b'flag', b'%X ', marker.flags())
2082 fm.write(b'flag', b'%X ', marker.flags())
2076 parents = marker.parentnodes()
2083 parents = marker.parentnodes()
2077 if parents is not None:
2084 if parents is not None:
2078 fm.write(
2085 fm.write(
2079 b'parentnodes',
2086 b'parentnodes',
2080 b'{%s} ',
2087 b'{%s} ',
2081 fm.formatlist(map(hex, parents), name=b'node', sep=b', '),
2088 fm.formatlist(map(hex, parents), name=b'node', sep=b', '),
2082 )
2089 )
2083 fm.write(b'date', b'(%s) ', fm.formatdate(marker.date()))
2090 fm.write(b'date', b'(%s) ', fm.formatdate(marker.date()))
2084 meta = marker.metadata().copy()
2091 meta = marker.metadata().copy()
2085 meta.pop(b'date', None)
2092 meta.pop(b'date', None)
2086 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
2093 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
2087 fm.write(
2094 fm.write(
2088 b'metadata', b'{%s}', fm.formatdict(smeta, fmt=b'%r: %r', sep=b', ')
2095 b'metadata', b'{%s}', fm.formatdict(smeta, fmt=b'%r: %r', sep=b', ')
2089 )
2096 )
2090 fm.plain(b'\n')
2097 fm.plain(b'\n')
2091
2098
2092
2099
2093 def finddate(ui, repo, date):
2100 def finddate(ui, repo, date):
2094 """Find the tipmost changeset that matches the given date spec"""
2101 """Find the tipmost changeset that matches the given date spec"""
2095
2102
2096 df = dateutil.matchdate(date)
2103 df = dateutil.matchdate(date)
2097 m = scmutil.matchall(repo)
2104 m = scmutil.matchall(repo)
2098 results = {}
2105 results = {}
2099
2106
2100 def prep(ctx, fns):
2107 def prep(ctx, fns):
2101 d = ctx.date()
2108 d = ctx.date()
2102 if df(d[0]):
2109 if df(d[0]):
2103 results[ctx.rev()] = d
2110 results[ctx.rev()] = d
2104
2111
2105 for ctx in walkchangerevs(repo, m, {b'rev': None}, prep):
2112 for ctx in walkchangerevs(repo, m, {b'rev': None}, prep):
2106 rev = ctx.rev()
2113 rev = ctx.rev()
2107 if rev in results:
2114 if rev in results:
2108 ui.status(
2115 ui.status(
2109 _(b"found revision %d from %s\n")
2116 _(b"found revision %d from %s\n")
2110 % (rev, dateutil.datestr(results[rev]))
2117 % (rev, dateutil.datestr(results[rev]))
2111 )
2118 )
2112 return b'%d' % rev
2119 return b'%d' % rev
2113
2120
2114 raise error.Abort(_(b"revision matching date not found"))
2121 raise error.Abort(_(b"revision matching date not found"))
2115
2122
2116
2123
2117 def increasingwindows(windowsize=8, sizelimit=512):
2124 def increasingwindows(windowsize=8, sizelimit=512):
2118 while True:
2125 while True:
2119 yield windowsize
2126 yield windowsize
2120 if windowsize < sizelimit:
2127 if windowsize < sizelimit:
2121 windowsize *= 2
2128 windowsize *= 2
2122
2129
2123
2130
2124 def _walkrevs(repo, opts):
2131 def _walkrevs(repo, opts):
2125 # Default --rev value depends on --follow but --follow behavior
2132 # Default --rev value depends on --follow but --follow behavior
2126 # depends on revisions resolved from --rev...
2133 # depends on revisions resolved from --rev...
2127 follow = opts.get(b'follow') or opts.get(b'follow_first')
2134 follow = opts.get(b'follow') or opts.get(b'follow_first')
2128 if opts.get(b'rev'):
2135 if opts.get(b'rev'):
2129 revs = scmutil.revrange(repo, opts[b'rev'])
2136 revs = scmutil.revrange(repo, opts[b'rev'])
2130 elif follow and repo.dirstate.p1() == nullid:
2137 elif follow and repo.dirstate.p1() == nullid:
2131 revs = smartset.baseset()
2138 revs = smartset.baseset()
2132 elif follow:
2139 elif follow:
2133 revs = repo.revs(b'reverse(:.)')
2140 revs = repo.revs(b'reverse(:.)')
2134 else:
2141 else:
2135 revs = smartset.spanset(repo)
2142 revs = smartset.spanset(repo)
2136 revs.reverse()
2143 revs.reverse()
2137 return revs
2144 return revs
2138
2145
2139
2146
2140 class FileWalkError(Exception):
2147 class FileWalkError(Exception):
2141 pass
2148 pass
2142
2149
2143
2150
2144 def walkfilerevs(repo, match, follow, revs, fncache):
2151 def walkfilerevs(repo, match, follow, revs, fncache):
2145 '''Walks the file history for the matched files.
2152 '''Walks the file history for the matched files.
2146
2153
2147 Returns the changeset revs that are involved in the file history.
2154 Returns the changeset revs that are involved in the file history.
2148
2155
2149 Throws FileWalkError if the file history can't be walked using
2156 Throws FileWalkError if the file history can't be walked using
2150 filelogs alone.
2157 filelogs alone.
2151 '''
2158 '''
2152 wanted = set()
2159 wanted = set()
2153 copies = []
2160 copies = []
2154 minrev, maxrev = min(revs), max(revs)
2161 minrev, maxrev = min(revs), max(revs)
2155
2162
2156 def filerevs(filelog, last):
2163 def filerevs(filelog, last):
2157 """
2164 """
2158 Only files, no patterns. Check the history of each file.
2165 Only files, no patterns. Check the history of each file.
2159
2166
2160 Examines filelog entries within minrev, maxrev linkrev range
2167 Examines filelog entries within minrev, maxrev linkrev range
2161 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
2168 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
2162 tuples in backwards order
2169 tuples in backwards order
2163 """
2170 """
2164 cl_count = len(repo)
2171 cl_count = len(repo)
2165 revs = []
2172 revs = []
2166 for j in pycompat.xrange(0, last + 1):
2173 for j in pycompat.xrange(0, last + 1):
2167 linkrev = filelog.linkrev(j)
2174 linkrev = filelog.linkrev(j)
2168 if linkrev < minrev:
2175 if linkrev < minrev:
2169 continue
2176 continue
2170 # only yield rev for which we have the changelog, it can
2177 # only yield rev for which we have the changelog, it can
2171 # happen while doing "hg log" during a pull or commit
2178 # happen while doing "hg log" during a pull or commit
2172 if linkrev >= cl_count:
2179 if linkrev >= cl_count:
2173 break
2180 break
2174
2181
2175 parentlinkrevs = []
2182 parentlinkrevs = []
2176 for p in filelog.parentrevs(j):
2183 for p in filelog.parentrevs(j):
2177 if p != nullrev:
2184 if p != nullrev:
2178 parentlinkrevs.append(filelog.linkrev(p))
2185 parentlinkrevs.append(filelog.linkrev(p))
2179 n = filelog.node(j)
2186 n = filelog.node(j)
2180 revs.append(
2187 revs.append(
2181 (linkrev, parentlinkrevs, follow and filelog.renamed(n))
2188 (linkrev, parentlinkrevs, follow and filelog.renamed(n))
2182 )
2189 )
2183
2190
2184 return reversed(revs)
2191 return reversed(revs)
2185
2192
2186 def iterfiles():
2193 def iterfiles():
2187 pctx = repo[b'.']
2194 pctx = repo[b'.']
2188 for filename in match.files():
2195 for filename in match.files():
2189 if follow:
2196 if follow:
2190 if filename not in pctx:
2197 if filename not in pctx:
2191 raise error.Abort(
2198 raise error.Abort(
2192 _(
2199 _(
2193 b'cannot follow file not in parent '
2200 b'cannot follow file not in parent '
2194 b'revision: "%s"'
2201 b'revision: "%s"'
2195 )
2202 )
2196 % filename
2203 % filename
2197 )
2204 )
2198 yield filename, pctx[filename].filenode()
2205 yield filename, pctx[filename].filenode()
2199 else:
2206 else:
2200 yield filename, None
2207 yield filename, None
2201 for filename_node in copies:
2208 for filename_node in copies:
2202 yield filename_node
2209 yield filename_node
2203
2210
2204 for file_, node in iterfiles():
2211 for file_, node in iterfiles():
2205 filelog = repo.file(file_)
2212 filelog = repo.file(file_)
2206 if not len(filelog):
2213 if not len(filelog):
2207 if node is None:
2214 if node is None:
2208 # A zero count may be a directory or deleted file, so
2215 # A zero count may be a directory or deleted file, so
2209 # try to find matching entries on the slow path.
2216 # try to find matching entries on the slow path.
2210 if follow:
2217 if follow:
2211 raise error.Abort(
2218 raise error.Abort(
2212 _(b'cannot follow nonexistent file: "%s"') % file_
2219 _(b'cannot follow nonexistent file: "%s"') % file_
2213 )
2220 )
2214 raise FileWalkError(b"Cannot walk via filelog")
2221 raise FileWalkError(b"Cannot walk via filelog")
2215 else:
2222 else:
2216 continue
2223 continue
2217
2224
2218 if node is None:
2225 if node is None:
2219 last = len(filelog) - 1
2226 last = len(filelog) - 1
2220 else:
2227 else:
2221 last = filelog.rev(node)
2228 last = filelog.rev(node)
2222
2229
2223 # keep track of all ancestors of the file
2230 # keep track of all ancestors of the file
2224 ancestors = {filelog.linkrev(last)}
2231 ancestors = {filelog.linkrev(last)}
2225
2232
2226 # iterate from latest to oldest revision
2233 # iterate from latest to oldest revision
2227 for rev, flparentlinkrevs, copied in filerevs(filelog, last):
2234 for rev, flparentlinkrevs, copied in filerevs(filelog, last):
2228 if not follow:
2235 if not follow:
2229 if rev > maxrev:
2236 if rev > maxrev:
2230 continue
2237 continue
2231 else:
2238 else:
2232 # Note that last might not be the first interesting
2239 # Note that last might not be the first interesting
2233 # rev to us:
2240 # rev to us:
2234 # if the file has been changed after maxrev, we'll
2241 # if the file has been changed after maxrev, we'll
2235 # have linkrev(last) > maxrev, and we still need
2242 # have linkrev(last) > maxrev, and we still need
2236 # to explore the file graph
2243 # to explore the file graph
2237 if rev not in ancestors:
2244 if rev not in ancestors:
2238 continue
2245 continue
2239 # XXX insert 1327 fix here
2246 # XXX insert 1327 fix here
2240 if flparentlinkrevs:
2247 if flparentlinkrevs:
2241 ancestors.update(flparentlinkrevs)
2248 ancestors.update(flparentlinkrevs)
2242
2249
2243 fncache.setdefault(rev, []).append(file_)
2250 fncache.setdefault(rev, []).append(file_)
2244 wanted.add(rev)
2251 wanted.add(rev)
2245 if copied:
2252 if copied:
2246 copies.append(copied)
2253 copies.append(copied)
2247
2254
2248 return wanted
2255 return wanted
2249
2256
2250
2257
2251 class _followfilter(object):
2258 class _followfilter(object):
2252 def __init__(self, repo, onlyfirst=False):
2259 def __init__(self, repo, onlyfirst=False):
2253 self.repo = repo
2260 self.repo = repo
2254 self.startrev = nullrev
2261 self.startrev = nullrev
2255 self.roots = set()
2262 self.roots = set()
2256 self.onlyfirst = onlyfirst
2263 self.onlyfirst = onlyfirst
2257
2264
2258 def match(self, rev):
2265 def match(self, rev):
2259 def realparents(rev):
2266 def realparents(rev):
2260 if self.onlyfirst:
2267 if self.onlyfirst:
2261 return self.repo.changelog.parentrevs(rev)[0:1]
2268 return self.repo.changelog.parentrevs(rev)[0:1]
2262 else:
2269 else:
2263 return filter(
2270 return filter(
2264 lambda x: x != nullrev, self.repo.changelog.parentrevs(rev)
2271 lambda x: x != nullrev, self.repo.changelog.parentrevs(rev)
2265 )
2272 )
2266
2273
2267 if self.startrev == nullrev:
2274 if self.startrev == nullrev:
2268 self.startrev = rev
2275 self.startrev = rev
2269 return True
2276 return True
2270
2277
2271 if rev > self.startrev:
2278 if rev > self.startrev:
2272 # forward: all descendants
2279 # forward: all descendants
2273 if not self.roots:
2280 if not self.roots:
2274 self.roots.add(self.startrev)
2281 self.roots.add(self.startrev)
2275 for parent in realparents(rev):
2282 for parent in realparents(rev):
2276 if parent in self.roots:
2283 if parent in self.roots:
2277 self.roots.add(rev)
2284 self.roots.add(rev)
2278 return True
2285 return True
2279 else:
2286 else:
2280 # backwards: all parents
2287 # backwards: all parents
2281 if not self.roots:
2288 if not self.roots:
2282 self.roots.update(realparents(self.startrev))
2289 self.roots.update(realparents(self.startrev))
2283 if rev in self.roots:
2290 if rev in self.roots:
2284 self.roots.remove(rev)
2291 self.roots.remove(rev)
2285 self.roots.update(realparents(rev))
2292 self.roots.update(realparents(rev))
2286 return True
2293 return True
2287
2294
2288 return False
2295 return False
2289
2296
2290
2297
2291 def walkchangerevs(repo, match, opts, prepare):
2298 def walkchangerevs(repo, match, opts, prepare):
2292 '''Iterate over files and the revs in which they changed.
2299 '''Iterate over files and the revs in which they changed.
2293
2300
2294 Callers most commonly need to iterate backwards over the history
2301 Callers most commonly need to iterate backwards over the history
2295 in which they are interested. Doing so has awful (quadratic-looking)
2302 in which they are interested. Doing so has awful (quadratic-looking)
2296 performance, so we use iterators in a "windowed" way.
2303 performance, so we use iterators in a "windowed" way.
2297
2304
2298 We walk a window of revisions in the desired order. Within the
2305 We walk a window of revisions in the desired order. Within the
2299 window, we first walk forwards to gather data, then in the desired
2306 window, we first walk forwards to gather data, then in the desired
2300 order (usually backwards) to display it.
2307 order (usually backwards) to display it.
2301
2308
2302 This function returns an iterator yielding contexts. Before
2309 This function returns an iterator yielding contexts. Before
2303 yielding each context, the iterator will first call the prepare
2310 yielding each context, the iterator will first call the prepare
2304 function on each context in the window in forward order.'''
2311 function on each context in the window in forward order.'''
2305
2312
2306 allfiles = opts.get(b'all_files')
2313 allfiles = opts.get(b'all_files')
2307 follow = opts.get(b'follow') or opts.get(b'follow_first')
2314 follow = opts.get(b'follow') or opts.get(b'follow_first')
2308 revs = _walkrevs(repo, opts)
2315 revs = _walkrevs(repo, opts)
2309 if not revs:
2316 if not revs:
2310 return []
2317 return []
2311 wanted = set()
2318 wanted = set()
2312 slowpath = match.anypats() or (not match.always() and opts.get(b'removed'))
2319 slowpath = match.anypats() or (not match.always() and opts.get(b'removed'))
2313 fncache = {}
2320 fncache = {}
2314 change = repo.__getitem__
2321 change = repo.__getitem__
2315
2322
2316 # First step is to fill wanted, the set of revisions that we want to yield.
2323 # First step is to fill wanted, the set of revisions that we want to yield.
2317 # When it does not induce extra cost, we also fill fncache for revisions in
2324 # When it does not induce extra cost, we also fill fncache for revisions in
2318 # wanted: a cache of filenames that were changed (ctx.files()) and that
2325 # wanted: a cache of filenames that were changed (ctx.files()) and that
2319 # match the file filtering conditions.
2326 # match the file filtering conditions.
2320
2327
2321 if match.always() or allfiles:
2328 if match.always() or allfiles:
2322 # No files, no patterns. Display all revs.
2329 # No files, no patterns. Display all revs.
2323 wanted = revs
2330 wanted = revs
2324 elif not slowpath:
2331 elif not slowpath:
2325 # We only have to read through the filelog to find wanted revisions
2332 # We only have to read through the filelog to find wanted revisions
2326
2333
2327 try:
2334 try:
2328 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2335 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2329 except FileWalkError:
2336 except FileWalkError:
2330 slowpath = True
2337 slowpath = True
2331
2338
2332 # We decided to fall back to the slowpath because at least one
2339 # We decided to fall back to the slowpath because at least one
2333 # of the paths was not a file. Check to see if at least one of them
2340 # of the paths was not a file. Check to see if at least one of them
2334 # existed in history, otherwise simply return
2341 # existed in history, otherwise simply return
2335 for path in match.files():
2342 for path in match.files():
2336 if path == b'.' or path in repo.store:
2343 if path == b'.' or path in repo.store:
2337 break
2344 break
2338 else:
2345 else:
2339 return []
2346 return []
2340
2347
2341 if slowpath:
2348 if slowpath:
2342 # We have to read the changelog to match filenames against
2349 # We have to read the changelog to match filenames against
2343 # changed files
2350 # changed files
2344
2351
2345 if follow:
2352 if follow:
2346 raise error.Abort(
2353 raise error.Abort(
2347 _(b'can only follow copies/renames for explicit filenames')
2354 _(b'can only follow copies/renames for explicit filenames')
2348 )
2355 )
2349
2356
2350 # The slow path checks files modified in every changeset.
2357 # The slow path checks files modified in every changeset.
2351 # This is really slow on large repos, so compute the set lazily.
2358 # This is really slow on large repos, so compute the set lazily.
2352 class lazywantedset(object):
2359 class lazywantedset(object):
2353 def __init__(self):
2360 def __init__(self):
2354 self.set = set()
2361 self.set = set()
2355 self.revs = set(revs)
2362 self.revs = set(revs)
2356
2363
2357 # No need to worry about locality here because it will be accessed
2364 # No need to worry about locality here because it will be accessed
2358 # in the same order as the increasing window below.
2365 # in the same order as the increasing window below.
2359 def __contains__(self, value):
2366 def __contains__(self, value):
2360 if value in self.set:
2367 if value in self.set:
2361 return True
2368 return True
2362 elif not value in self.revs:
2369 elif not value in self.revs:
2363 return False
2370 return False
2364 else:
2371 else:
2365 self.revs.discard(value)
2372 self.revs.discard(value)
2366 ctx = change(value)
2373 ctx = change(value)
2367 if allfiles:
2374 if allfiles:
2368 matches = list(ctx.manifest().walk(match))
2375 matches = list(ctx.manifest().walk(match))
2369 else:
2376 else:
2370 matches = [f for f in ctx.files() if match(f)]
2377 matches = [f for f in ctx.files() if match(f)]
2371 if matches:
2378 if matches:
2372 fncache[value] = matches
2379 fncache[value] = matches
2373 self.set.add(value)
2380 self.set.add(value)
2374 return True
2381 return True
2375 return False
2382 return False
2376
2383
2377 def discard(self, value):
2384 def discard(self, value):
2378 self.revs.discard(value)
2385 self.revs.discard(value)
2379 self.set.discard(value)
2386 self.set.discard(value)
2380
2387
2381 wanted = lazywantedset()
2388 wanted = lazywantedset()
2382
2389
2383 # it might be worthwhile to do this in the iterator if the rev range
2390 # it might be worthwhile to do this in the iterator if the rev range
2384 # is descending and the prune args are all within that range
2391 # is descending and the prune args are all within that range
2385 for rev in opts.get(b'prune', ()):
2392 for rev in opts.get(b'prune', ()):
2386 rev = repo[rev].rev()
2393 rev = repo[rev].rev()
2387 ff = _followfilter(repo)
2394 ff = _followfilter(repo)
2388 stop = min(revs[0], revs[-1])
2395 stop = min(revs[0], revs[-1])
2389 for x in pycompat.xrange(rev, stop - 1, -1):
2396 for x in pycompat.xrange(rev, stop - 1, -1):
2390 if ff.match(x):
2397 if ff.match(x):
2391 wanted = wanted - [x]
2398 wanted = wanted - [x]
2392
2399
2393 # Now that wanted is correctly initialized, we can iterate over the
2400 # Now that wanted is correctly initialized, we can iterate over the
2394 # revision range, yielding only revisions in wanted.
2401 # revision range, yielding only revisions in wanted.
2395 def iterate():
2402 def iterate():
2396 if follow and match.always():
2403 if follow and match.always():
2397 ff = _followfilter(repo, onlyfirst=opts.get(b'follow_first'))
2404 ff = _followfilter(repo, onlyfirst=opts.get(b'follow_first'))
2398
2405
2399 def want(rev):
2406 def want(rev):
2400 return ff.match(rev) and rev in wanted
2407 return ff.match(rev) and rev in wanted
2401
2408
2402 else:
2409 else:
2403
2410
2404 def want(rev):
2411 def want(rev):
2405 return rev in wanted
2412 return rev in wanted
2406
2413
2407 it = iter(revs)
2414 it = iter(revs)
2408 stopiteration = False
2415 stopiteration = False
2409 for windowsize in increasingwindows():
2416 for windowsize in increasingwindows():
2410 nrevs = []
2417 nrevs = []
2411 for i in pycompat.xrange(windowsize):
2418 for i in pycompat.xrange(windowsize):
2412 rev = next(it, None)
2419 rev = next(it, None)
2413 if rev is None:
2420 if rev is None:
2414 stopiteration = True
2421 stopiteration = True
2415 break
2422 break
2416 elif want(rev):
2423 elif want(rev):
2417 nrevs.append(rev)
2424 nrevs.append(rev)
2418 for rev in sorted(nrevs):
2425 for rev in sorted(nrevs):
2419 fns = fncache.get(rev)
2426 fns = fncache.get(rev)
2420 ctx = change(rev)
2427 ctx = change(rev)
2421 if not fns:
2428 if not fns:
2422
2429
2423 def fns_generator():
2430 def fns_generator():
2424 if allfiles:
2431 if allfiles:
2425
2432
2426 def bad(f, msg):
2433 def bad(f, msg):
2427 pass
2434 pass
2428
2435
2429 for f in ctx.matches(matchmod.badmatch(match, bad)):
2436 for f in ctx.matches(matchmod.badmatch(match, bad)):
2430 yield f
2437 yield f
2431 else:
2438 else:
2432 for f in ctx.files():
2439 for f in ctx.files():
2433 if match(f):
2440 if match(f):
2434 yield f
2441 yield f
2435
2442
2436 fns = fns_generator()
2443 fns = fns_generator()
2437 prepare(ctx, fns)
2444 prepare(ctx, fns)
2438 for rev in nrevs:
2445 for rev in nrevs:
2439 yield change(rev)
2446 yield change(rev)
2440
2447
2441 if stopiteration:
2448 if stopiteration:
2442 break
2449 break
2443
2450
2444 return iterate()
2451 return iterate()
2445
2452
2446
2453
2447 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2454 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2448 bad = []
2455 bad = []
2449
2456
2450 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2457 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2451 names = []
2458 names = []
2452 wctx = repo[None]
2459 wctx = repo[None]
2453 cca = None
2460 cca = None
2454 abort, warn = scmutil.checkportabilityalert(ui)
2461 abort, warn = scmutil.checkportabilityalert(ui)
2455 if abort or warn:
2462 if abort or warn:
2456 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2463 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2457
2464
2458 match = repo.narrowmatch(match, includeexact=True)
2465 match = repo.narrowmatch(match, includeexact=True)
2459 badmatch = matchmod.badmatch(match, badfn)
2466 badmatch = matchmod.badmatch(match, badfn)
2460 dirstate = repo.dirstate
2467 dirstate = repo.dirstate
2461 # We don't want to just call wctx.walk here, since it would return a lot of
2468 # We don't want to just call wctx.walk here, since it would return a lot of
2462 # clean files, which we aren't interested in and takes time.
2469 # clean files, which we aren't interested in and takes time.
2463 for f in sorted(
2470 for f in sorted(
2464 dirstate.walk(
2471 dirstate.walk(
2465 badmatch,
2472 badmatch,
2466 subrepos=sorted(wctx.substate),
2473 subrepos=sorted(wctx.substate),
2467 unknown=True,
2474 unknown=True,
2468 ignored=False,
2475 ignored=False,
2469 full=False,
2476 full=False,
2470 )
2477 )
2471 ):
2478 ):
2472 exact = match.exact(f)
2479 exact = match.exact(f)
2473 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2480 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2474 if cca:
2481 if cca:
2475 cca(f)
2482 cca(f)
2476 names.append(f)
2483 names.append(f)
2477 if ui.verbose or not exact:
2484 if ui.verbose or not exact:
2478 ui.status(
2485 ui.status(
2479 _(b'adding %s\n') % uipathfn(f), label=b'ui.addremove.added'
2486 _(b'adding %s\n') % uipathfn(f), label=b'ui.addremove.added'
2480 )
2487 )
2481
2488
2482 for subpath in sorted(wctx.substate):
2489 for subpath in sorted(wctx.substate):
2483 sub = wctx.sub(subpath)
2490 sub = wctx.sub(subpath)
2484 try:
2491 try:
2485 submatch = matchmod.subdirmatcher(subpath, match)
2492 submatch = matchmod.subdirmatcher(subpath, match)
2486 subprefix = repo.wvfs.reljoin(prefix, subpath)
2493 subprefix = repo.wvfs.reljoin(prefix, subpath)
2487 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2494 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2488 if opts.get('subrepos'):
2495 if opts.get('subrepos'):
2489 bad.extend(
2496 bad.extend(
2490 sub.add(ui, submatch, subprefix, subuipathfn, False, **opts)
2497 sub.add(ui, submatch, subprefix, subuipathfn, False, **opts)
2491 )
2498 )
2492 else:
2499 else:
2493 bad.extend(
2500 bad.extend(
2494 sub.add(ui, submatch, subprefix, subuipathfn, True, **opts)
2501 sub.add(ui, submatch, subprefix, subuipathfn, True, **opts)
2495 )
2502 )
2496 except error.LookupError:
2503 except error.LookupError:
2497 ui.status(
2504 ui.status(
2498 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2505 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2499 )
2506 )
2500
2507
2501 if not opts.get('dry_run'):
2508 if not opts.get('dry_run'):
2502 rejected = wctx.add(names, prefix)
2509 rejected = wctx.add(names, prefix)
2503 bad.extend(f for f in rejected if f in match.files())
2510 bad.extend(f for f in rejected if f in match.files())
2504 return bad
2511 return bad
2505
2512
2506
2513
2507 def addwebdirpath(repo, serverpath, webconf):
2514 def addwebdirpath(repo, serverpath, webconf):
2508 webconf[serverpath] = repo.root
2515 webconf[serverpath] = repo.root
2509 repo.ui.debug(b'adding %s = %s\n' % (serverpath, repo.root))
2516 repo.ui.debug(b'adding %s = %s\n' % (serverpath, repo.root))
2510
2517
2511 for r in repo.revs(b'filelog("path:.hgsub")'):
2518 for r in repo.revs(b'filelog("path:.hgsub")'):
2512 ctx = repo[r]
2519 ctx = repo[r]
2513 for subpath in ctx.substate:
2520 for subpath in ctx.substate:
2514 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2521 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2515
2522
2516
2523
2517 def forget(
2524 def forget(
2518 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
2525 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
2519 ):
2526 ):
2520 if dryrun and interactive:
2527 if dryrun and interactive:
2521 raise error.Abort(_(b"cannot specify both --dry-run and --interactive"))
2528 raise error.Abort(_(b"cannot specify both --dry-run and --interactive"))
2522 bad = []
2529 bad = []
2523 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2530 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2524 wctx = repo[None]
2531 wctx = repo[None]
2525 forgot = []
2532 forgot = []
2526
2533
2527 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2534 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2528 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2535 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2529 if explicitonly:
2536 if explicitonly:
2530 forget = [f for f in forget if match.exact(f)]
2537 forget = [f for f in forget if match.exact(f)]
2531
2538
2532 for subpath in sorted(wctx.substate):
2539 for subpath in sorted(wctx.substate):
2533 sub = wctx.sub(subpath)
2540 sub = wctx.sub(subpath)
2534 submatch = matchmod.subdirmatcher(subpath, match)
2541 submatch = matchmod.subdirmatcher(subpath, match)
2535 subprefix = repo.wvfs.reljoin(prefix, subpath)
2542 subprefix = repo.wvfs.reljoin(prefix, subpath)
2536 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2543 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2537 try:
2544 try:
2538 subbad, subforgot = sub.forget(
2545 subbad, subforgot = sub.forget(
2539 submatch,
2546 submatch,
2540 subprefix,
2547 subprefix,
2541 subuipathfn,
2548 subuipathfn,
2542 dryrun=dryrun,
2549 dryrun=dryrun,
2543 interactive=interactive,
2550 interactive=interactive,
2544 )
2551 )
2545 bad.extend([subpath + b'/' + f for f in subbad])
2552 bad.extend([subpath + b'/' + f for f in subbad])
2546 forgot.extend([subpath + b'/' + f for f in subforgot])
2553 forgot.extend([subpath + b'/' + f for f in subforgot])
2547 except error.LookupError:
2554 except error.LookupError:
2548 ui.status(
2555 ui.status(
2549 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2556 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2550 )
2557 )
2551
2558
2552 if not explicitonly:
2559 if not explicitonly:
2553 for f in match.files():
2560 for f in match.files():
2554 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2561 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2555 if f not in forgot:
2562 if f not in forgot:
2556 if repo.wvfs.exists(f):
2563 if repo.wvfs.exists(f):
2557 # Don't complain if the exact case match wasn't given.
2564 # Don't complain if the exact case match wasn't given.
2558 # But don't do this until after checking 'forgot', so
2565 # But don't do this until after checking 'forgot', so
2559 # that subrepo files aren't normalized, and this op is
2566 # that subrepo files aren't normalized, and this op is
2560 # purely from data cached by the status walk above.
2567 # purely from data cached by the status walk above.
2561 if repo.dirstate.normalize(f) in repo.dirstate:
2568 if repo.dirstate.normalize(f) in repo.dirstate:
2562 continue
2569 continue
2563 ui.warn(
2570 ui.warn(
2564 _(
2571 _(
2565 b'not removing %s: '
2572 b'not removing %s: '
2566 b'file is already untracked\n'
2573 b'file is already untracked\n'
2567 )
2574 )
2568 % uipathfn(f)
2575 % uipathfn(f)
2569 )
2576 )
2570 bad.append(f)
2577 bad.append(f)
2571
2578
2572 if interactive:
2579 if interactive:
2573 responses = _(
2580 responses = _(
2574 b'[Ynsa?]'
2581 b'[Ynsa?]'
2575 b'$$ &Yes, forget this file'
2582 b'$$ &Yes, forget this file'
2576 b'$$ &No, skip this file'
2583 b'$$ &No, skip this file'
2577 b'$$ &Skip remaining files'
2584 b'$$ &Skip remaining files'
2578 b'$$ Include &all remaining files'
2585 b'$$ Include &all remaining files'
2579 b'$$ &? (display help)'
2586 b'$$ &? (display help)'
2580 )
2587 )
2581 for filename in forget[:]:
2588 for filename in forget[:]:
2582 r = ui.promptchoice(
2589 r = ui.promptchoice(
2583 _(b'forget %s %s') % (uipathfn(filename), responses)
2590 _(b'forget %s %s') % (uipathfn(filename), responses)
2584 )
2591 )
2585 if r == 4: # ?
2592 if r == 4: # ?
2586 while r == 4:
2593 while r == 4:
2587 for c, t in ui.extractchoices(responses)[1]:
2594 for c, t in ui.extractchoices(responses)[1]:
2588 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
2595 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
2589 r = ui.promptchoice(
2596 r = ui.promptchoice(
2590 _(b'forget %s %s') % (uipathfn(filename), responses)
2597 _(b'forget %s %s') % (uipathfn(filename), responses)
2591 )
2598 )
2592 if r == 0: # yes
2599 if r == 0: # yes
2593 continue
2600 continue
2594 elif r == 1: # no
2601 elif r == 1: # no
2595 forget.remove(filename)
2602 forget.remove(filename)
2596 elif r == 2: # Skip
2603 elif r == 2: # Skip
2597 fnindex = forget.index(filename)
2604 fnindex = forget.index(filename)
2598 del forget[fnindex:]
2605 del forget[fnindex:]
2599 break
2606 break
2600 elif r == 3: # All
2607 elif r == 3: # All
2601 break
2608 break
2602
2609
2603 for f in forget:
2610 for f in forget:
2604 if ui.verbose or not match.exact(f) or interactive:
2611 if ui.verbose or not match.exact(f) or interactive:
2605 ui.status(
2612 ui.status(
2606 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2613 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2607 )
2614 )
2608
2615
2609 if not dryrun:
2616 if not dryrun:
2610 rejected = wctx.forget(forget, prefix)
2617 rejected = wctx.forget(forget, prefix)
2611 bad.extend(f for f in rejected if f in match.files())
2618 bad.extend(f for f in rejected if f in match.files())
2612 forgot.extend(f for f in forget if f not in rejected)
2619 forgot.extend(f for f in forget if f not in rejected)
2613 return bad, forgot
2620 return bad, forgot
2614
2621
2615
2622
2616 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2623 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2617 ret = 1
2624 ret = 1
2618
2625
2619 needsfctx = ui.verbose or {b'size', b'flags'} & fm.datahint()
2626 needsfctx = ui.verbose or {b'size', b'flags'} & fm.datahint()
2620 for f in ctx.matches(m):
2627 for f in ctx.matches(m):
2621 fm.startitem()
2628 fm.startitem()
2622 fm.context(ctx=ctx)
2629 fm.context(ctx=ctx)
2623 if needsfctx:
2630 if needsfctx:
2624 fc = ctx[f]
2631 fc = ctx[f]
2625 fm.write(b'size flags', b'% 10d % 1s ', fc.size(), fc.flags())
2632 fm.write(b'size flags', b'% 10d % 1s ', fc.size(), fc.flags())
2626 fm.data(path=f)
2633 fm.data(path=f)
2627 fm.plain(fmt % uipathfn(f))
2634 fm.plain(fmt % uipathfn(f))
2628 ret = 0
2635 ret = 0
2629
2636
2630 for subpath in sorted(ctx.substate):
2637 for subpath in sorted(ctx.substate):
2631 submatch = matchmod.subdirmatcher(subpath, m)
2638 submatch = matchmod.subdirmatcher(subpath, m)
2632 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2639 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2633 if subrepos or m.exact(subpath) or any(submatch.files()):
2640 if subrepos or m.exact(subpath) or any(submatch.files()):
2634 sub = ctx.sub(subpath)
2641 sub = ctx.sub(subpath)
2635 try:
2642 try:
2636 recurse = m.exact(subpath) or subrepos
2643 recurse = m.exact(subpath) or subrepos
2637 if (
2644 if (
2638 sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse)
2645 sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse)
2639 == 0
2646 == 0
2640 ):
2647 ):
2641 ret = 0
2648 ret = 0
2642 except error.LookupError:
2649 except error.LookupError:
2643 ui.status(
2650 ui.status(
2644 _(b"skipping missing subrepository: %s\n")
2651 _(b"skipping missing subrepository: %s\n")
2645 % uipathfn(subpath)
2652 % uipathfn(subpath)
2646 )
2653 )
2647
2654
2648 return ret
2655 return ret
2649
2656
2650
2657
2651 def remove(
2658 def remove(
2652 ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None
2659 ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None
2653 ):
2660 ):
2654 ret = 0
2661 ret = 0
2655 s = repo.status(match=m, clean=True)
2662 s = repo.status(match=m, clean=True)
2656 modified, added, deleted, clean = s.modified, s.added, s.deleted, s.clean
2663 modified, added, deleted, clean = s.modified, s.added, s.deleted, s.clean
2657
2664
2658 wctx = repo[None]
2665 wctx = repo[None]
2659
2666
2660 if warnings is None:
2667 if warnings is None:
2661 warnings = []
2668 warnings = []
2662 warn = True
2669 warn = True
2663 else:
2670 else:
2664 warn = False
2671 warn = False
2665
2672
2666 subs = sorted(wctx.substate)
2673 subs = sorted(wctx.substate)
2667 progress = ui.makeprogress(
2674 progress = ui.makeprogress(
2668 _(b'searching'), total=len(subs), unit=_(b'subrepos')
2675 _(b'searching'), total=len(subs), unit=_(b'subrepos')
2669 )
2676 )
2670 for subpath in subs:
2677 for subpath in subs:
2671 submatch = matchmod.subdirmatcher(subpath, m)
2678 submatch = matchmod.subdirmatcher(subpath, m)
2672 subprefix = repo.wvfs.reljoin(prefix, subpath)
2679 subprefix = repo.wvfs.reljoin(prefix, subpath)
2673 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2680 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2674 if subrepos or m.exact(subpath) or any(submatch.files()):
2681 if subrepos or m.exact(subpath) or any(submatch.files()):
2675 progress.increment()
2682 progress.increment()
2676 sub = wctx.sub(subpath)
2683 sub = wctx.sub(subpath)
2677 try:
2684 try:
2678 if sub.removefiles(
2685 if sub.removefiles(
2679 submatch,
2686 submatch,
2680 subprefix,
2687 subprefix,
2681 subuipathfn,
2688 subuipathfn,
2682 after,
2689 after,
2683 force,
2690 force,
2684 subrepos,
2691 subrepos,
2685 dryrun,
2692 dryrun,
2686 warnings,
2693 warnings,
2687 ):
2694 ):
2688 ret = 1
2695 ret = 1
2689 except error.LookupError:
2696 except error.LookupError:
2690 warnings.append(
2697 warnings.append(
2691 _(b"skipping missing subrepository: %s\n")
2698 _(b"skipping missing subrepository: %s\n")
2692 % uipathfn(subpath)
2699 % uipathfn(subpath)
2693 )
2700 )
2694 progress.complete()
2701 progress.complete()
2695
2702
2696 # warn about failure to delete explicit files/dirs
2703 # warn about failure to delete explicit files/dirs
2697 deleteddirs = pathutil.dirs(deleted)
2704 deleteddirs = pathutil.dirs(deleted)
2698 files = m.files()
2705 files = m.files()
2699 progress = ui.makeprogress(
2706 progress = ui.makeprogress(
2700 _(b'deleting'), total=len(files), unit=_(b'files')
2707 _(b'deleting'), total=len(files), unit=_(b'files')
2701 )
2708 )
2702 for f in files:
2709 for f in files:
2703
2710
2704 def insubrepo():
2711 def insubrepo():
2705 for subpath in wctx.substate:
2712 for subpath in wctx.substate:
2706 if f.startswith(subpath + b'/'):
2713 if f.startswith(subpath + b'/'):
2707 return True
2714 return True
2708 return False
2715 return False
2709
2716
2710 progress.increment()
2717 progress.increment()
2711 isdir = f in deleteddirs or wctx.hasdir(f)
2718 isdir = f in deleteddirs or wctx.hasdir(f)
2712 if f in repo.dirstate or isdir or f == b'.' or insubrepo() or f in subs:
2719 if f in repo.dirstate or isdir or f == b'.' or insubrepo() or f in subs:
2713 continue
2720 continue
2714
2721
2715 if repo.wvfs.exists(f):
2722 if repo.wvfs.exists(f):
2716 if repo.wvfs.isdir(f):
2723 if repo.wvfs.isdir(f):
2717 warnings.append(
2724 warnings.append(
2718 _(b'not removing %s: no tracked files\n') % uipathfn(f)
2725 _(b'not removing %s: no tracked files\n') % uipathfn(f)
2719 )
2726 )
2720 else:
2727 else:
2721 warnings.append(
2728 warnings.append(
2722 _(b'not removing %s: file is untracked\n') % uipathfn(f)
2729 _(b'not removing %s: file is untracked\n') % uipathfn(f)
2723 )
2730 )
2724 # missing files will generate a warning elsewhere
2731 # missing files will generate a warning elsewhere
2725 ret = 1
2732 ret = 1
2726 progress.complete()
2733 progress.complete()
2727
2734
2728 if force:
2735 if force:
2729 list = modified + deleted + clean + added
2736 list = modified + deleted + clean + added
2730 elif after:
2737 elif after:
2731 list = deleted
2738 list = deleted
2732 remaining = modified + added + clean
2739 remaining = modified + added + clean
2733 progress = ui.makeprogress(
2740 progress = ui.makeprogress(
2734 _(b'skipping'), total=len(remaining), unit=_(b'files')
2741 _(b'skipping'), total=len(remaining), unit=_(b'files')
2735 )
2742 )
2736 for f in remaining:
2743 for f in remaining:
2737 progress.increment()
2744 progress.increment()
2738 if ui.verbose or (f in files):
2745 if ui.verbose or (f in files):
2739 warnings.append(
2746 warnings.append(
2740 _(b'not removing %s: file still exists\n') % uipathfn(f)
2747 _(b'not removing %s: file still exists\n') % uipathfn(f)
2741 )
2748 )
2742 ret = 1
2749 ret = 1
2743 progress.complete()
2750 progress.complete()
2744 else:
2751 else:
2745 list = deleted + clean
2752 list = deleted + clean
2746 progress = ui.makeprogress(
2753 progress = ui.makeprogress(
2747 _(b'skipping'), total=(len(modified) + len(added)), unit=_(b'files')
2754 _(b'skipping'), total=(len(modified) + len(added)), unit=_(b'files')
2748 )
2755 )
2749 for f in modified:
2756 for f in modified:
2750 progress.increment()
2757 progress.increment()
2751 warnings.append(
2758 warnings.append(
2752 _(
2759 _(
2753 b'not removing %s: file is modified (use -f'
2760 b'not removing %s: file is modified (use -f'
2754 b' to force removal)\n'
2761 b' to force removal)\n'
2755 )
2762 )
2756 % uipathfn(f)
2763 % uipathfn(f)
2757 )
2764 )
2758 ret = 1
2765 ret = 1
2759 for f in added:
2766 for f in added:
2760 progress.increment()
2767 progress.increment()
2761 warnings.append(
2768 warnings.append(
2762 _(
2769 _(
2763 b"not removing %s: file has been marked for add"
2770 b"not removing %s: file has been marked for add"
2764 b" (use 'hg forget' to undo add)\n"
2771 b" (use 'hg forget' to undo add)\n"
2765 )
2772 )
2766 % uipathfn(f)
2773 % uipathfn(f)
2767 )
2774 )
2768 ret = 1
2775 ret = 1
2769 progress.complete()
2776 progress.complete()
2770
2777
2771 list = sorted(list)
2778 list = sorted(list)
2772 progress = ui.makeprogress(
2779 progress = ui.makeprogress(
2773 _(b'deleting'), total=len(list), unit=_(b'files')
2780 _(b'deleting'), total=len(list), unit=_(b'files')
2774 )
2781 )
2775 for f in list:
2782 for f in list:
2776 if ui.verbose or not m.exact(f):
2783 if ui.verbose or not m.exact(f):
2777 progress.increment()
2784 progress.increment()
2778 ui.status(
2785 ui.status(
2779 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2786 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2780 )
2787 )
2781 progress.complete()
2788 progress.complete()
2782
2789
2783 if not dryrun:
2790 if not dryrun:
2784 with repo.wlock():
2791 with repo.wlock():
2785 if not after:
2792 if not after:
2786 for f in list:
2793 for f in list:
2787 if f in added:
2794 if f in added:
2788 continue # we never unlink added files on remove
2795 continue # we never unlink added files on remove
2789 rmdir = repo.ui.configbool(
2796 rmdir = repo.ui.configbool(
2790 b'experimental', b'removeemptydirs'
2797 b'experimental', b'removeemptydirs'
2791 )
2798 )
2792 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2799 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2793 repo[None].forget(list)
2800 repo[None].forget(list)
2794
2801
2795 if warn:
2802 if warn:
2796 for warning in warnings:
2803 for warning in warnings:
2797 ui.warn(warning)
2804 ui.warn(warning)
2798
2805
2799 return ret
2806 return ret
2800
2807
2801
2808
2802 def _catfmtneedsdata(fm):
2809 def _catfmtneedsdata(fm):
2803 return not fm.datahint() or b'data' in fm.datahint()
2810 return not fm.datahint() or b'data' in fm.datahint()
2804
2811
2805
2812
2806 def _updatecatformatter(fm, ctx, matcher, path, decode):
2813 def _updatecatformatter(fm, ctx, matcher, path, decode):
2807 """Hook for adding data to the formatter used by ``hg cat``.
2814 """Hook for adding data to the formatter used by ``hg cat``.
2808
2815
2809 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2816 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2810 this method first."""
2817 this method first."""
2811
2818
2812 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2819 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2813 # wasn't requested.
2820 # wasn't requested.
2814 data = b''
2821 data = b''
2815 if _catfmtneedsdata(fm):
2822 if _catfmtneedsdata(fm):
2816 data = ctx[path].data()
2823 data = ctx[path].data()
2817 if decode:
2824 if decode:
2818 data = ctx.repo().wwritedata(path, data)
2825 data = ctx.repo().wwritedata(path, data)
2819 fm.startitem()
2826 fm.startitem()
2820 fm.context(ctx=ctx)
2827 fm.context(ctx=ctx)
2821 fm.write(b'data', b'%s', data)
2828 fm.write(b'data', b'%s', data)
2822 fm.data(path=path)
2829 fm.data(path=path)
2823
2830
2824
2831
2825 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2832 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2826 err = 1
2833 err = 1
2827 opts = pycompat.byteskwargs(opts)
2834 opts = pycompat.byteskwargs(opts)
2828
2835
2829 def write(path):
2836 def write(path):
2830 filename = None
2837 filename = None
2831 if fntemplate:
2838 if fntemplate:
2832 filename = makefilename(
2839 filename = makefilename(
2833 ctx, fntemplate, pathname=os.path.join(prefix, path)
2840 ctx, fntemplate, pathname=os.path.join(prefix, path)
2834 )
2841 )
2835 # attempt to create the directory if it does not already exist
2842 # attempt to create the directory if it does not already exist
2836 try:
2843 try:
2837 os.makedirs(os.path.dirname(filename))
2844 os.makedirs(os.path.dirname(filename))
2838 except OSError:
2845 except OSError:
2839 pass
2846 pass
2840 with formatter.maybereopen(basefm, filename) as fm:
2847 with formatter.maybereopen(basefm, filename) as fm:
2841 _updatecatformatter(fm, ctx, matcher, path, opts.get(b'decode'))
2848 _updatecatformatter(fm, ctx, matcher, path, opts.get(b'decode'))
2842
2849
2843 # Automation often uses hg cat on single files, so special case it
2850 # Automation often uses hg cat on single files, so special case it
2844 # for performance to avoid the cost of parsing the manifest.
2851 # for performance to avoid the cost of parsing the manifest.
2845 if len(matcher.files()) == 1 and not matcher.anypats():
2852 if len(matcher.files()) == 1 and not matcher.anypats():
2846 file = matcher.files()[0]
2853 file = matcher.files()[0]
2847 mfl = repo.manifestlog
2854 mfl = repo.manifestlog
2848 mfnode = ctx.manifestnode()
2855 mfnode = ctx.manifestnode()
2849 try:
2856 try:
2850 if mfnode and mfl[mfnode].find(file)[0]:
2857 if mfnode and mfl[mfnode].find(file)[0]:
2851 if _catfmtneedsdata(basefm):
2858 if _catfmtneedsdata(basefm):
2852 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2859 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2853 write(file)
2860 write(file)
2854 return 0
2861 return 0
2855 except KeyError:
2862 except KeyError:
2856 pass
2863 pass
2857
2864
2858 if _catfmtneedsdata(basefm):
2865 if _catfmtneedsdata(basefm):
2859 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2866 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2860
2867
2861 for abs in ctx.walk(matcher):
2868 for abs in ctx.walk(matcher):
2862 write(abs)
2869 write(abs)
2863 err = 0
2870 err = 0
2864
2871
2865 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2872 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2866 for subpath in sorted(ctx.substate):
2873 for subpath in sorted(ctx.substate):
2867 sub = ctx.sub(subpath)
2874 sub = ctx.sub(subpath)
2868 try:
2875 try:
2869 submatch = matchmod.subdirmatcher(subpath, matcher)
2876 submatch = matchmod.subdirmatcher(subpath, matcher)
2870 subprefix = os.path.join(prefix, subpath)
2877 subprefix = os.path.join(prefix, subpath)
2871 if not sub.cat(
2878 if not sub.cat(
2872 submatch,
2879 submatch,
2873 basefm,
2880 basefm,
2874 fntemplate,
2881 fntemplate,
2875 subprefix,
2882 subprefix,
2876 **pycompat.strkwargs(opts)
2883 **pycompat.strkwargs(opts)
2877 ):
2884 ):
2878 err = 0
2885 err = 0
2879 except error.RepoLookupError:
2886 except error.RepoLookupError:
2880 ui.status(
2887 ui.status(
2881 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2888 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2882 )
2889 )
2883
2890
2884 return err
2891 return err
2885
2892
2886
2893
2887 def commit(ui, repo, commitfunc, pats, opts):
2894 def commit(ui, repo, commitfunc, pats, opts):
2888 '''commit the specified files or all outstanding changes'''
2895 '''commit the specified files or all outstanding changes'''
2889 date = opts.get(b'date')
2896 date = opts.get(b'date')
2890 if date:
2897 if date:
2891 opts[b'date'] = dateutil.parsedate(date)
2898 opts[b'date'] = dateutil.parsedate(date)
2892 message = logmessage(ui, opts)
2899 message = logmessage(ui, opts)
2893 matcher = scmutil.match(repo[None], pats, opts)
2900 matcher = scmutil.match(repo[None], pats, opts)
2894
2901
2895 dsguard = None
2902 dsguard = None
2896 # extract addremove carefully -- this function can be called from a command
2903 # extract addremove carefully -- this function can be called from a command
2897 # that doesn't support addremove
2904 # that doesn't support addremove
2898 if opts.get(b'addremove'):
2905 if opts.get(b'addremove'):
2899 dsguard = dirstateguard.dirstateguard(repo, b'commit')
2906 dsguard = dirstateguard.dirstateguard(repo, b'commit')
2900 with dsguard or util.nullcontextmanager():
2907 with dsguard or util.nullcontextmanager():
2901 if dsguard:
2908 if dsguard:
2902 relative = scmutil.anypats(pats, opts)
2909 relative = scmutil.anypats(pats, opts)
2903 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2910 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2904 if scmutil.addremove(repo, matcher, b"", uipathfn, opts) != 0:
2911 if scmutil.addremove(repo, matcher, b"", uipathfn, opts) != 0:
2905 raise error.Abort(
2912 raise error.Abort(
2906 _(b"failed to mark all new/missing files as added/removed")
2913 _(b"failed to mark all new/missing files as added/removed")
2907 )
2914 )
2908
2915
2909 return commitfunc(ui, repo, message, matcher, opts)
2916 return commitfunc(ui, repo, message, matcher, opts)
2910
2917
2911
2918
2912 def samefile(f, ctx1, ctx2):
2919 def samefile(f, ctx1, ctx2):
2913 if f in ctx1.manifest():
2920 if f in ctx1.manifest():
2914 a = ctx1.filectx(f)
2921 a = ctx1.filectx(f)
2915 if f in ctx2.manifest():
2922 if f in ctx2.manifest():
2916 b = ctx2.filectx(f)
2923 b = ctx2.filectx(f)
2917 return not a.cmp(b) and a.flags() == b.flags()
2924 return not a.cmp(b) and a.flags() == b.flags()
2918 else:
2925 else:
2919 return False
2926 return False
2920 else:
2927 else:
2921 return f not in ctx2.manifest()
2928 return f not in ctx2.manifest()
2922
2929
2923
2930
2924 def amend(ui, repo, old, extra, pats, opts):
2931 def amend(ui, repo, old, extra, pats, opts):
2925 # avoid cycle context -> subrepo -> cmdutil
2932 # avoid cycle context -> subrepo -> cmdutil
2926 from . import context
2933 from . import context
2927
2934
2928 # amend will reuse the existing user if not specified, but the obsolete
2935 # amend will reuse the existing user if not specified, but the obsolete
2929 # marker creation requires that the current user's name is specified.
2936 # marker creation requires that the current user's name is specified.
2930 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2937 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2931 ui.username() # raise exception if username not set
2938 ui.username() # raise exception if username not set
2932
2939
2933 ui.note(_(b'amending changeset %s\n') % old)
2940 ui.note(_(b'amending changeset %s\n') % old)
2934 base = old.p1()
2941 base = old.p1()
2935
2942
2936 with repo.wlock(), repo.lock(), repo.transaction(b'amend'):
2943 with repo.wlock(), repo.lock(), repo.transaction(b'amend'):
2937 # Participating changesets:
2944 # Participating changesets:
2938 #
2945 #
2939 # wctx o - workingctx that contains changes from working copy
2946 # wctx o - workingctx that contains changes from working copy
2940 # | to go into amending commit
2947 # | to go into amending commit
2941 # |
2948 # |
2942 # old o - changeset to amend
2949 # old o - changeset to amend
2943 # |
2950 # |
2944 # base o - first parent of the changeset to amend
2951 # base o - first parent of the changeset to amend
2945 wctx = repo[None]
2952 wctx = repo[None]
2946
2953
2947 # Copy to avoid mutating input
2954 # Copy to avoid mutating input
2948 extra = extra.copy()
2955 extra = extra.copy()
2949 # Update extra dict from amended commit (e.g. to preserve graft
2956 # Update extra dict from amended commit (e.g. to preserve graft
2950 # source)
2957 # source)
2951 extra.update(old.extra())
2958 extra.update(old.extra())
2952
2959
2953 # Also update it from the from the wctx
2960 # Also update it from the from the wctx
2954 extra.update(wctx.extra())
2961 extra.update(wctx.extra())
2955
2962
2956 # date-only change should be ignored?
2963 # date-only change should be ignored?
2957 datemaydiffer = resolvecommitoptions(ui, opts)
2964 datemaydiffer = resolvecommitoptions(ui, opts)
2958
2965
2959 date = old.date()
2966 date = old.date()
2960 if opts.get(b'date'):
2967 if opts.get(b'date'):
2961 date = dateutil.parsedate(opts.get(b'date'))
2968 date = dateutil.parsedate(opts.get(b'date'))
2962 user = opts.get(b'user') or old.user()
2969 user = opts.get(b'user') or old.user()
2963
2970
2964 if len(old.parents()) > 1:
2971 if len(old.parents()) > 1:
2965 # ctx.files() isn't reliable for merges, so fall back to the
2972 # ctx.files() isn't reliable for merges, so fall back to the
2966 # slower repo.status() method
2973 # slower repo.status() method
2967 st = base.status(old)
2974 st = base.status(old)
2968 files = set(st.modified) | set(st.added) | set(st.removed)
2975 files = set(st.modified) | set(st.added) | set(st.removed)
2969 else:
2976 else:
2970 files = set(old.files())
2977 files = set(old.files())
2971
2978
2972 # add/remove the files to the working copy if the "addremove" option
2979 # add/remove the files to the working copy if the "addremove" option
2973 # was specified.
2980 # was specified.
2974 matcher = scmutil.match(wctx, pats, opts)
2981 matcher = scmutil.match(wctx, pats, opts)
2975 relative = scmutil.anypats(pats, opts)
2982 relative = scmutil.anypats(pats, opts)
2976 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2983 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2977 if opts.get(b'addremove') and scmutil.addremove(
2984 if opts.get(b'addremove') and scmutil.addremove(
2978 repo, matcher, b"", uipathfn, opts
2985 repo, matcher, b"", uipathfn, opts
2979 ):
2986 ):
2980 raise error.Abort(
2987 raise error.Abort(
2981 _(b"failed to mark all new/missing files as added/removed")
2988 _(b"failed to mark all new/missing files as added/removed")
2982 )
2989 )
2983
2990
2984 # Check subrepos. This depends on in-place wctx._status update in
2991 # Check subrepos. This depends on in-place wctx._status update in
2985 # subrepo.precommit(). To minimize the risk of this hack, we do
2992 # subrepo.precommit(). To minimize the risk of this hack, we do
2986 # nothing if .hgsub does not exist.
2993 # nothing if .hgsub does not exist.
2987 if b'.hgsub' in wctx or b'.hgsub' in old:
2994 if b'.hgsub' in wctx or b'.hgsub' in old:
2988 subs, commitsubs, newsubstate = subrepoutil.precommit(
2995 subs, commitsubs, newsubstate = subrepoutil.precommit(
2989 ui, wctx, wctx._status, matcher
2996 ui, wctx, wctx._status, matcher
2990 )
2997 )
2991 # amend should abort if commitsubrepos is enabled
2998 # amend should abort if commitsubrepos is enabled
2992 assert not commitsubs
2999 assert not commitsubs
2993 if subs:
3000 if subs:
2994 subrepoutil.writestate(repo, newsubstate)
3001 subrepoutil.writestate(repo, newsubstate)
2995
3002
2996 ms = mergemod.mergestate.read(repo)
3003 ms = mergemod.mergestate.read(repo)
2997 mergeutil.checkunresolved(ms)
3004 mergeutil.checkunresolved(ms)
2998
3005
2999 filestoamend = set(f for f in wctx.files() if matcher(f))
3006 filestoamend = set(f for f in wctx.files() if matcher(f))
3000
3007
3001 changes = len(filestoamend) > 0
3008 changes = len(filestoamend) > 0
3002 if changes:
3009 if changes:
3003 # Recompute copies (avoid recording a -> b -> a)
3010 # Recompute copies (avoid recording a -> b -> a)
3004 copied = copies.pathcopies(base, wctx, matcher)
3011 copied = copies.pathcopies(base, wctx, matcher)
3005 if old.p2:
3012 if old.p2:
3006 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
3013 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
3007
3014
3008 # Prune files which were reverted by the updates: if old
3015 # Prune files which were reverted by the updates: if old
3009 # introduced file X and the file was renamed in the working
3016 # introduced file X and the file was renamed in the working
3010 # copy, then those two files are the same and
3017 # copy, then those two files are the same and
3011 # we can discard X from our list of files. Likewise if X
3018 # we can discard X from our list of files. Likewise if X
3012 # was removed, it's no longer relevant. If X is missing (aka
3019 # was removed, it's no longer relevant. If X is missing (aka
3013 # deleted), old X must be preserved.
3020 # deleted), old X must be preserved.
3014 files.update(filestoamend)
3021 files.update(filestoamend)
3015 files = [
3022 files = [
3016 f
3023 f
3017 for f in files
3024 for f in files
3018 if (f not in filestoamend or not samefile(f, wctx, base))
3025 if (f not in filestoamend or not samefile(f, wctx, base))
3019 ]
3026 ]
3020
3027
3021 def filectxfn(repo, ctx_, path):
3028 def filectxfn(repo, ctx_, path):
3022 try:
3029 try:
3023 # If the file being considered is not amongst the files
3030 # If the file being considered is not amongst the files
3024 # to be amended, we should return the file context from the
3031 # to be amended, we should return the file context from the
3025 # old changeset. This avoids issues when only some files in
3032 # old changeset. This avoids issues when only some files in
3026 # the working copy are being amended but there are also
3033 # the working copy are being amended but there are also
3027 # changes to other files from the old changeset.
3034 # changes to other files from the old changeset.
3028 if path not in filestoamend:
3035 if path not in filestoamend:
3029 return old.filectx(path)
3036 return old.filectx(path)
3030
3037
3031 # Return None for removed files.
3038 # Return None for removed files.
3032 if path in wctx.removed():
3039 if path in wctx.removed():
3033 return None
3040 return None
3034
3041
3035 fctx = wctx[path]
3042 fctx = wctx[path]
3036 flags = fctx.flags()
3043 flags = fctx.flags()
3037 mctx = context.memfilectx(
3044 mctx = context.memfilectx(
3038 repo,
3045 repo,
3039 ctx_,
3046 ctx_,
3040 fctx.path(),
3047 fctx.path(),
3041 fctx.data(),
3048 fctx.data(),
3042 islink=b'l' in flags,
3049 islink=b'l' in flags,
3043 isexec=b'x' in flags,
3050 isexec=b'x' in flags,
3044 copysource=copied.get(path),
3051 copysource=copied.get(path),
3045 )
3052 )
3046 return mctx
3053 return mctx
3047 except KeyError:
3054 except KeyError:
3048 return None
3055 return None
3049
3056
3050 else:
3057 else:
3051 ui.note(_(b'copying changeset %s to %s\n') % (old, base))
3058 ui.note(_(b'copying changeset %s to %s\n') % (old, base))
3052
3059
3053 # Use version of files as in the old cset
3060 # Use version of files as in the old cset
3054 def filectxfn(repo, ctx_, path):
3061 def filectxfn(repo, ctx_, path):
3055 try:
3062 try:
3056 return old.filectx(path)
3063 return old.filectx(path)
3057 except KeyError:
3064 except KeyError:
3058 return None
3065 return None
3059
3066
3060 # See if we got a message from -m or -l, if not, open the editor with
3067 # See if we got a message from -m or -l, if not, open the editor with
3061 # the message of the changeset to amend.
3068 # the message of the changeset to amend.
3062 message = logmessage(ui, opts)
3069 message = logmessage(ui, opts)
3063
3070
3064 editform = mergeeditform(old, b'commit.amend')
3071 editform = mergeeditform(old, b'commit.amend')
3065
3072
3066 if not message:
3073 if not message:
3067 message = old.description()
3074 message = old.description()
3068 # Default if message isn't provided and --edit is not passed is to
3075 # Default if message isn't provided and --edit is not passed is to
3069 # invoke editor, but allow --no-edit. If somehow we don't have any
3076 # invoke editor, but allow --no-edit. If somehow we don't have any
3070 # description, let's always start the editor.
3077 # description, let's always start the editor.
3071 doedit = not message or opts.get(b'edit') in [True, None]
3078 doedit = not message or opts.get(b'edit') in [True, None]
3072 else:
3079 else:
3073 # Default if message is provided is to not invoke editor, but allow
3080 # Default if message is provided is to not invoke editor, but allow
3074 # --edit.
3081 # --edit.
3075 doedit = opts.get(b'edit') is True
3082 doedit = opts.get(b'edit') is True
3076 editor = getcommiteditor(edit=doedit, editform=editform)
3083 editor = getcommiteditor(edit=doedit, editform=editform)
3077
3084
3078 pureextra = extra.copy()
3085 pureextra = extra.copy()
3079 extra[b'amend_source'] = old.hex()
3086 extra[b'amend_source'] = old.hex()
3080
3087
3081 new = context.memctx(
3088 new = context.memctx(
3082 repo,
3089 repo,
3083 parents=[base.node(), old.p2().node()],
3090 parents=[base.node(), old.p2().node()],
3084 text=message,
3091 text=message,
3085 files=files,
3092 files=files,
3086 filectxfn=filectxfn,
3093 filectxfn=filectxfn,
3087 user=user,
3094 user=user,
3088 date=date,
3095 date=date,
3089 extra=extra,
3096 extra=extra,
3090 editor=editor,
3097 editor=editor,
3091 )
3098 )
3092
3099
3093 newdesc = changelog.stripdesc(new.description())
3100 newdesc = changelog.stripdesc(new.description())
3094 if (
3101 if (
3095 (not changes)
3102 (not changes)
3096 and newdesc == old.description()
3103 and newdesc == old.description()
3097 and user == old.user()
3104 and user == old.user()
3098 and (date == old.date() or datemaydiffer)
3105 and (date == old.date() or datemaydiffer)
3099 and pureextra == old.extra()
3106 and pureextra == old.extra()
3100 ):
3107 ):
3101 # nothing changed. continuing here would create a new node
3108 # nothing changed. continuing here would create a new node
3102 # anyway because of the amend_source noise.
3109 # anyway because of the amend_source noise.
3103 #
3110 #
3104 # This not what we expect from amend.
3111 # This not what we expect from amend.
3105 return old.node()
3112 return old.node()
3106
3113
3107 commitphase = None
3114 commitphase = None
3108 if opts.get(b'secret'):
3115 if opts.get(b'secret'):
3109 commitphase = phases.secret
3116 commitphase = phases.secret
3110 newid = repo.commitctx(new)
3117 newid = repo.commitctx(new)
3111
3118
3112 # Reroute the working copy parent to the new changeset
3119 # Reroute the working copy parent to the new changeset
3113 repo.setparents(newid, nullid)
3120 repo.setparents(newid, nullid)
3114 mapping = {old.node(): (newid,)}
3121 mapping = {old.node(): (newid,)}
3115 obsmetadata = None
3122 obsmetadata = None
3116 if opts.get(b'note'):
3123 if opts.get(b'note'):
3117 obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])}
3124 obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])}
3118 backup = ui.configbool(b'rewrite', b'backup-bundle')
3125 backup = ui.configbool(b'rewrite', b'backup-bundle')
3119 scmutil.cleanupnodes(
3126 scmutil.cleanupnodes(
3120 repo,
3127 repo,
3121 mapping,
3128 mapping,
3122 b'amend',
3129 b'amend',
3123 metadata=obsmetadata,
3130 metadata=obsmetadata,
3124 fixphase=True,
3131 fixphase=True,
3125 targetphase=commitphase,
3132 targetphase=commitphase,
3126 backup=backup,
3133 backup=backup,
3127 )
3134 )
3128
3135
3129 # Fixing the dirstate because localrepo.commitctx does not update
3136 # Fixing the dirstate because localrepo.commitctx does not update
3130 # it. This is rather convenient because we did not need to update
3137 # it. This is rather convenient because we did not need to update
3131 # the dirstate for all the files in the new commit which commitctx
3138 # the dirstate for all the files in the new commit which commitctx
3132 # could have done if it updated the dirstate. Now, we can
3139 # could have done if it updated the dirstate. Now, we can
3133 # selectively update the dirstate only for the amended files.
3140 # selectively update the dirstate only for the amended files.
3134 dirstate = repo.dirstate
3141 dirstate = repo.dirstate
3135
3142
3136 # Update the state of the files which were added and modified in the
3143 # Update the state of the files which were added and modified in the
3137 # amend to "normal" in the dirstate. We need to use "normallookup" since
3144 # amend to "normal" in the dirstate. We need to use "normallookup" since
3138 # the files may have changed since the command started; using "normal"
3145 # the files may have changed since the command started; using "normal"
3139 # would mark them as clean but with uncommitted contents.
3146 # would mark them as clean but with uncommitted contents.
3140 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
3147 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
3141 for f in normalfiles:
3148 for f in normalfiles:
3142 dirstate.normallookup(f)
3149 dirstate.normallookup(f)
3143
3150
3144 # Update the state of files which were removed in the amend
3151 # Update the state of files which were removed in the amend
3145 # to "removed" in the dirstate.
3152 # to "removed" in the dirstate.
3146 removedfiles = set(wctx.removed()) & filestoamend
3153 removedfiles = set(wctx.removed()) & filestoamend
3147 for f in removedfiles:
3154 for f in removedfiles:
3148 dirstate.drop(f)
3155 dirstate.drop(f)
3149
3156
3150 return newid
3157 return newid
3151
3158
3152
3159
3153 def commiteditor(repo, ctx, subs, editform=b''):
3160 def commiteditor(repo, ctx, subs, editform=b''):
3154 if ctx.description():
3161 if ctx.description():
3155 return ctx.description()
3162 return ctx.description()
3156 return commitforceeditor(
3163 return commitforceeditor(
3157 repo, ctx, subs, editform=editform, unchangedmessagedetection=True
3164 repo, ctx, subs, editform=editform, unchangedmessagedetection=True
3158 )
3165 )
3159
3166
3160
3167
3161 def commitforceeditor(
3168 def commitforceeditor(
3162 repo,
3169 repo,
3163 ctx,
3170 ctx,
3164 subs,
3171 subs,
3165 finishdesc=None,
3172 finishdesc=None,
3166 extramsg=None,
3173 extramsg=None,
3167 editform=b'',
3174 editform=b'',
3168 unchangedmessagedetection=False,
3175 unchangedmessagedetection=False,
3169 ):
3176 ):
3170 if not extramsg:
3177 if not extramsg:
3171 extramsg = _(b"Leave message empty to abort commit.")
3178 extramsg = _(b"Leave message empty to abort commit.")
3172
3179
3173 forms = [e for e in editform.split(b'.') if e]
3180 forms = [e for e in editform.split(b'.') if e]
3174 forms.insert(0, b'changeset')
3181 forms.insert(0, b'changeset')
3175 templatetext = None
3182 templatetext = None
3176 while forms:
3183 while forms:
3177 ref = b'.'.join(forms)
3184 ref = b'.'.join(forms)
3178 if repo.ui.config(b'committemplate', ref):
3185 if repo.ui.config(b'committemplate', ref):
3179 templatetext = committext = buildcommittemplate(
3186 templatetext = committext = buildcommittemplate(
3180 repo, ctx, subs, extramsg, ref
3187 repo, ctx, subs, extramsg, ref
3181 )
3188 )
3182 break
3189 break
3183 forms.pop()
3190 forms.pop()
3184 else:
3191 else:
3185 committext = buildcommittext(repo, ctx, subs, extramsg)
3192 committext = buildcommittext(repo, ctx, subs, extramsg)
3186
3193
3187 # run editor in the repository root
3194 # run editor in the repository root
3188 olddir = encoding.getcwd()
3195 olddir = encoding.getcwd()
3189 os.chdir(repo.root)
3196 os.chdir(repo.root)
3190
3197
3191 # make in-memory changes visible to external process
3198 # make in-memory changes visible to external process
3192 tr = repo.currenttransaction()
3199 tr = repo.currenttransaction()
3193 repo.dirstate.write(tr)
3200 repo.dirstate.write(tr)
3194 pending = tr and tr.writepending() and repo.root
3201 pending = tr and tr.writepending() and repo.root
3195
3202
3196 editortext = repo.ui.edit(
3203 editortext = repo.ui.edit(
3197 committext,
3204 committext,
3198 ctx.user(),
3205 ctx.user(),
3199 ctx.extra(),
3206 ctx.extra(),
3200 editform=editform,
3207 editform=editform,
3201 pending=pending,
3208 pending=pending,
3202 repopath=repo.path,
3209 repopath=repo.path,
3203 action=b'commit',
3210 action=b'commit',
3204 )
3211 )
3205 text = editortext
3212 text = editortext
3206
3213
3207 # strip away anything below this special string (used for editors that want
3214 # strip away anything below this special string (used for editors that want
3208 # to display the diff)
3215 # to display the diff)
3209 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3216 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3210 if stripbelow:
3217 if stripbelow:
3211 text = text[: stripbelow.start()]
3218 text = text[: stripbelow.start()]
3212
3219
3213 text = re.sub(b"(?m)^HG:.*(\n|$)", b"", text)
3220 text = re.sub(b"(?m)^HG:.*(\n|$)", b"", text)
3214 os.chdir(olddir)
3221 os.chdir(olddir)
3215
3222
3216 if finishdesc:
3223 if finishdesc:
3217 text = finishdesc(text)
3224 text = finishdesc(text)
3218 if not text.strip():
3225 if not text.strip():
3219 raise error.Abort(_(b"empty commit message"))
3226 raise error.Abort(_(b"empty commit message"))
3220 if unchangedmessagedetection and editortext == templatetext:
3227 if unchangedmessagedetection and editortext == templatetext:
3221 raise error.Abort(_(b"commit message unchanged"))
3228 raise error.Abort(_(b"commit message unchanged"))
3222
3229
3223 return text
3230 return text
3224
3231
3225
3232
3226 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3233 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3227 ui = repo.ui
3234 ui = repo.ui
3228 spec = formatter.templatespec(ref, None, None)
3235 spec = formatter.templatespec(ref, None, None)
3229 t = logcmdutil.changesettemplater(ui, repo, spec)
3236 t = logcmdutil.changesettemplater(ui, repo, spec)
3230 t.t.cache.update(
3237 t.t.cache.update(
3231 (k, templater.unquotestring(v))
3238 (k, templater.unquotestring(v))
3232 for k, v in repo.ui.configitems(b'committemplate')
3239 for k, v in repo.ui.configitems(b'committemplate')
3233 )
3240 )
3234
3241
3235 if not extramsg:
3242 if not extramsg:
3236 extramsg = b'' # ensure that extramsg is string
3243 extramsg = b'' # ensure that extramsg is string
3237
3244
3238 ui.pushbuffer()
3245 ui.pushbuffer()
3239 t.show(ctx, extramsg=extramsg)
3246 t.show(ctx, extramsg=extramsg)
3240 return ui.popbuffer()
3247 return ui.popbuffer()
3241
3248
3242
3249
3243 def hgprefix(msg):
3250 def hgprefix(msg):
3244 return b"\n".join([b"HG: %s" % a for a in msg.split(b"\n") if a])
3251 return b"\n".join([b"HG: %s" % a for a in msg.split(b"\n") if a])
3245
3252
3246
3253
3247 def buildcommittext(repo, ctx, subs, extramsg):
3254 def buildcommittext(repo, ctx, subs, extramsg):
3248 edittext = []
3255 edittext = []
3249 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3256 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3250 if ctx.description():
3257 if ctx.description():
3251 edittext.append(ctx.description())
3258 edittext.append(ctx.description())
3252 edittext.append(b"")
3259 edittext.append(b"")
3253 edittext.append(b"") # Empty line between message and comments.
3260 edittext.append(b"") # Empty line between message and comments.
3254 edittext.append(
3261 edittext.append(
3255 hgprefix(
3262 hgprefix(
3256 _(
3263 _(
3257 b"Enter commit message."
3264 b"Enter commit message."
3258 b" Lines beginning with 'HG:' are removed."
3265 b" Lines beginning with 'HG:' are removed."
3259 )
3266 )
3260 )
3267 )
3261 )
3268 )
3262 edittext.append(hgprefix(extramsg))
3269 edittext.append(hgprefix(extramsg))
3263 edittext.append(b"HG: --")
3270 edittext.append(b"HG: --")
3264 edittext.append(hgprefix(_(b"user: %s") % ctx.user()))
3271 edittext.append(hgprefix(_(b"user: %s") % ctx.user()))
3265 if ctx.p2():
3272 if ctx.p2():
3266 edittext.append(hgprefix(_(b"branch merge")))
3273 edittext.append(hgprefix(_(b"branch merge")))
3267 if ctx.branch():
3274 if ctx.branch():
3268 edittext.append(hgprefix(_(b"branch '%s'") % ctx.branch()))
3275 edittext.append(hgprefix(_(b"branch '%s'") % ctx.branch()))
3269 if bookmarks.isactivewdirparent(repo):
3276 if bookmarks.isactivewdirparent(repo):
3270 edittext.append(hgprefix(_(b"bookmark '%s'") % repo._activebookmark))
3277 edittext.append(hgprefix(_(b"bookmark '%s'") % repo._activebookmark))
3271 edittext.extend([hgprefix(_(b"subrepo %s") % s) for s in subs])
3278 edittext.extend([hgprefix(_(b"subrepo %s") % s) for s in subs])
3272 edittext.extend([hgprefix(_(b"added %s") % f) for f in added])
3279 edittext.extend([hgprefix(_(b"added %s") % f) for f in added])
3273 edittext.extend([hgprefix(_(b"changed %s") % f) for f in modified])
3280 edittext.extend([hgprefix(_(b"changed %s") % f) for f in modified])
3274 edittext.extend([hgprefix(_(b"removed %s") % f) for f in removed])
3281 edittext.extend([hgprefix(_(b"removed %s") % f) for f in removed])
3275 if not added and not modified and not removed:
3282 if not added and not modified and not removed:
3276 edittext.append(hgprefix(_(b"no files changed")))
3283 edittext.append(hgprefix(_(b"no files changed")))
3277 edittext.append(b"")
3284 edittext.append(b"")
3278
3285
3279 return b"\n".join(edittext)
3286 return b"\n".join(edittext)
3280
3287
3281
3288
3282 def commitstatus(repo, node, branch, bheads=None, opts=None):
3289 def commitstatus(repo, node, branch, bheads=None, opts=None):
3283 if opts is None:
3290 if opts is None:
3284 opts = {}
3291 opts = {}
3285 ctx = repo[node]
3292 ctx = repo[node]
3286 parents = ctx.parents()
3293 parents = ctx.parents()
3287
3294
3288 if (
3295 if (
3289 not opts.get(b'amend')
3296 not opts.get(b'amend')
3290 and bheads
3297 and bheads
3291 and node not in bheads
3298 and node not in bheads
3292 and not [
3299 and not [
3293 x for x in parents if x.node() in bheads and x.branch() == branch
3300 x for x in parents if x.node() in bheads and x.branch() == branch
3294 ]
3301 ]
3295 ):
3302 ):
3296 repo.ui.status(_(b'created new head\n'))
3303 repo.ui.status(_(b'created new head\n'))
3297 # The message is not printed for initial roots. For the other
3304 # The message is not printed for initial roots. For the other
3298 # changesets, it is printed in the following situations:
3305 # changesets, it is printed in the following situations:
3299 #
3306 #
3300 # Par column: for the 2 parents with ...
3307 # Par column: for the 2 parents with ...
3301 # N: null or no parent
3308 # N: null or no parent
3302 # B: parent is on another named branch
3309 # B: parent is on another named branch
3303 # C: parent is a regular non head changeset
3310 # C: parent is a regular non head changeset
3304 # H: parent was a branch head of the current branch
3311 # H: parent was a branch head of the current branch
3305 # Msg column: whether we print "created new head" message
3312 # Msg column: whether we print "created new head" message
3306 # In the following, it is assumed that there already exists some
3313 # In the following, it is assumed that there already exists some
3307 # initial branch heads of the current branch, otherwise nothing is
3314 # initial branch heads of the current branch, otherwise nothing is
3308 # printed anyway.
3315 # printed anyway.
3309 #
3316 #
3310 # Par Msg Comment
3317 # Par Msg Comment
3311 # N N y additional topo root
3318 # N N y additional topo root
3312 #
3319 #
3313 # B N y additional branch root
3320 # B N y additional branch root
3314 # C N y additional topo head
3321 # C N y additional topo head
3315 # H N n usual case
3322 # H N n usual case
3316 #
3323 #
3317 # B B y weird additional branch root
3324 # B B y weird additional branch root
3318 # C B y branch merge
3325 # C B y branch merge
3319 # H B n merge with named branch
3326 # H B n merge with named branch
3320 #
3327 #
3321 # C C y additional head from merge
3328 # C C y additional head from merge
3322 # C H n merge with a head
3329 # C H n merge with a head
3323 #
3330 #
3324 # H H n head merge: head count decreases
3331 # H H n head merge: head count decreases
3325
3332
3326 if not opts.get(b'close_branch'):
3333 if not opts.get(b'close_branch'):
3327 for r in parents:
3334 for r in parents:
3328 if r.closesbranch() and r.branch() == branch:
3335 if r.closesbranch() and r.branch() == branch:
3329 repo.ui.status(
3336 repo.ui.status(
3330 _(b'reopening closed branch head %d\n') % r.rev()
3337 _(b'reopening closed branch head %d\n') % r.rev()
3331 )
3338 )
3332
3339
3333 if repo.ui.debugflag:
3340 if repo.ui.debugflag:
3334 repo.ui.write(
3341 repo.ui.write(
3335 _(b'committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())
3342 _(b'committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())
3336 )
3343 )
3337 elif repo.ui.verbose:
3344 elif repo.ui.verbose:
3338 repo.ui.write(_(b'committed changeset %d:%s\n') % (ctx.rev(), ctx))
3345 repo.ui.write(_(b'committed changeset %d:%s\n') % (ctx.rev(), ctx))
3339
3346
3340
3347
3341 def postcommitstatus(repo, pats, opts):
3348 def postcommitstatus(repo, pats, opts):
3342 return repo.status(match=scmutil.match(repo[None], pats, opts))
3349 return repo.status(match=scmutil.match(repo[None], pats, opts))
3343
3350
3344
3351
3345 def revert(ui, repo, ctx, parents, *pats, **opts):
3352 def revert(ui, repo, ctx, parents, *pats, **opts):
3346 opts = pycompat.byteskwargs(opts)
3353 opts = pycompat.byteskwargs(opts)
3347 parent, p2 = parents
3354 parent, p2 = parents
3348 node = ctx.node()
3355 node = ctx.node()
3349
3356
3350 mf = ctx.manifest()
3357 mf = ctx.manifest()
3351 if node == p2:
3358 if node == p2:
3352 parent = p2
3359 parent = p2
3353
3360
3354 # need all matching names in dirstate and manifest of target rev,
3361 # need all matching names in dirstate and manifest of target rev,
3355 # so have to walk both. do not print errors if files exist in one
3362 # so have to walk both. do not print errors if files exist in one
3356 # but not other. in both cases, filesets should be evaluated against
3363 # but not other. in both cases, filesets should be evaluated against
3357 # workingctx to get consistent result (issue4497). this means 'set:**'
3364 # workingctx to get consistent result (issue4497). this means 'set:**'
3358 # cannot be used to select missing files from target rev.
3365 # cannot be used to select missing files from target rev.
3359
3366
3360 # `names` is a mapping for all elements in working copy and target revision
3367 # `names` is a mapping for all elements in working copy and target revision
3361 # The mapping is in the form:
3368 # The mapping is in the form:
3362 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3369 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3363 names = {}
3370 names = {}
3364 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
3371 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
3365
3372
3366 with repo.wlock():
3373 with repo.wlock():
3367 ## filling of the `names` mapping
3374 ## filling of the `names` mapping
3368 # walk dirstate to fill `names`
3375 # walk dirstate to fill `names`
3369
3376
3370 interactive = opts.get(b'interactive', False)
3377 interactive = opts.get(b'interactive', False)
3371 wctx = repo[None]
3378 wctx = repo[None]
3372 m = scmutil.match(wctx, pats, opts)
3379 m = scmutil.match(wctx, pats, opts)
3373
3380
3374 # we'll need this later
3381 # we'll need this later
3375 targetsubs = sorted(s for s in wctx.substate if m(s))
3382 targetsubs = sorted(s for s in wctx.substate if m(s))
3376
3383
3377 if not m.always():
3384 if not m.always():
3378 matcher = matchmod.badmatch(m, lambda x, y: False)
3385 matcher = matchmod.badmatch(m, lambda x, y: False)
3379 for abs in wctx.walk(matcher):
3386 for abs in wctx.walk(matcher):
3380 names[abs] = m.exact(abs)
3387 names[abs] = m.exact(abs)
3381
3388
3382 # walk target manifest to fill `names`
3389 # walk target manifest to fill `names`
3383
3390
3384 def badfn(path, msg):
3391 def badfn(path, msg):
3385 if path in names:
3392 if path in names:
3386 return
3393 return
3387 if path in ctx.substate:
3394 if path in ctx.substate:
3388 return
3395 return
3389 path_ = path + b'/'
3396 path_ = path + b'/'
3390 for f in names:
3397 for f in names:
3391 if f.startswith(path_):
3398 if f.startswith(path_):
3392 return
3399 return
3393 ui.warn(b"%s: %s\n" % (uipathfn(path), msg))
3400 ui.warn(b"%s: %s\n" % (uipathfn(path), msg))
3394
3401
3395 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3402 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3396 if abs not in names:
3403 if abs not in names:
3397 names[abs] = m.exact(abs)
3404 names[abs] = m.exact(abs)
3398
3405
3399 # Find status of all file in `names`.
3406 # Find status of all file in `names`.
3400 m = scmutil.matchfiles(repo, names)
3407 m = scmutil.matchfiles(repo, names)
3401
3408
3402 changes = repo.status(
3409 changes = repo.status(
3403 node1=node, match=m, unknown=True, ignored=True, clean=True
3410 node1=node, match=m, unknown=True, ignored=True, clean=True
3404 )
3411 )
3405 else:
3412 else:
3406 changes = repo.status(node1=node, match=m)
3413 changes = repo.status(node1=node, match=m)
3407 for kind in changes:
3414 for kind in changes:
3408 for abs in kind:
3415 for abs in kind:
3409 names[abs] = m.exact(abs)
3416 names[abs] = m.exact(abs)
3410
3417
3411 m = scmutil.matchfiles(repo, names)
3418 m = scmutil.matchfiles(repo, names)
3412
3419
3413 modified = set(changes.modified)
3420 modified = set(changes.modified)
3414 added = set(changes.added)
3421 added = set(changes.added)
3415 removed = set(changes.removed)
3422 removed = set(changes.removed)
3416 _deleted = set(changes.deleted)
3423 _deleted = set(changes.deleted)
3417 unknown = set(changes.unknown)
3424 unknown = set(changes.unknown)
3418 unknown.update(changes.ignored)
3425 unknown.update(changes.ignored)
3419 clean = set(changes.clean)
3426 clean = set(changes.clean)
3420 modadded = set()
3427 modadded = set()
3421
3428
3422 # We need to account for the state of the file in the dirstate,
3429 # We need to account for the state of the file in the dirstate,
3423 # even when we revert against something else than parent. This will
3430 # even when we revert against something else than parent. This will
3424 # slightly alter the behavior of revert (doing back up or not, delete
3431 # slightly alter the behavior of revert (doing back up or not, delete
3425 # or just forget etc).
3432 # or just forget etc).
3426 if parent == node:
3433 if parent == node:
3427 dsmodified = modified
3434 dsmodified = modified
3428 dsadded = added
3435 dsadded = added
3429 dsremoved = removed
3436 dsremoved = removed
3430 # store all local modifications, useful later for rename detection
3437 # store all local modifications, useful later for rename detection
3431 localchanges = dsmodified | dsadded
3438 localchanges = dsmodified | dsadded
3432 modified, added, removed = set(), set(), set()
3439 modified, added, removed = set(), set(), set()
3433 else:
3440 else:
3434 changes = repo.status(node1=parent, match=m)
3441 changes = repo.status(node1=parent, match=m)
3435 dsmodified = set(changes.modified)
3442 dsmodified = set(changes.modified)
3436 dsadded = set(changes.added)
3443 dsadded = set(changes.added)
3437 dsremoved = set(changes.removed)
3444 dsremoved = set(changes.removed)
3438 # store all local modifications, useful later for rename detection
3445 # store all local modifications, useful later for rename detection
3439 localchanges = dsmodified | dsadded
3446 localchanges = dsmodified | dsadded
3440
3447
3441 # only take into account for removes between wc and target
3448 # only take into account for removes between wc and target
3442 clean |= dsremoved - removed
3449 clean |= dsremoved - removed
3443 dsremoved &= removed
3450 dsremoved &= removed
3444 # distinct between dirstate remove and other
3451 # distinct between dirstate remove and other
3445 removed -= dsremoved
3452 removed -= dsremoved
3446
3453
3447 modadded = added & dsmodified
3454 modadded = added & dsmodified
3448 added -= modadded
3455 added -= modadded
3449
3456
3450 # tell newly modified apart.
3457 # tell newly modified apart.
3451 dsmodified &= modified
3458 dsmodified &= modified
3452 dsmodified |= modified & dsadded # dirstate added may need backup
3459 dsmodified |= modified & dsadded # dirstate added may need backup
3453 modified -= dsmodified
3460 modified -= dsmodified
3454
3461
3455 # We need to wait for some post-processing to update this set
3462 # We need to wait for some post-processing to update this set
3456 # before making the distinction. The dirstate will be used for
3463 # before making the distinction. The dirstate will be used for
3457 # that purpose.
3464 # that purpose.
3458 dsadded = added
3465 dsadded = added
3459
3466
3460 # in case of merge, files that are actually added can be reported as
3467 # in case of merge, files that are actually added can be reported as
3461 # modified, we need to post process the result
3468 # modified, we need to post process the result
3462 if p2 != nullid:
3469 if p2 != nullid:
3463 mergeadd = set(dsmodified)
3470 mergeadd = set(dsmodified)
3464 for path in dsmodified:
3471 for path in dsmodified:
3465 if path in mf:
3472 if path in mf:
3466 mergeadd.remove(path)
3473 mergeadd.remove(path)
3467 dsadded |= mergeadd
3474 dsadded |= mergeadd
3468 dsmodified -= mergeadd
3475 dsmodified -= mergeadd
3469
3476
3470 # if f is a rename, update `names` to also revert the source
3477 # if f is a rename, update `names` to also revert the source
3471 for f in localchanges:
3478 for f in localchanges:
3472 src = repo.dirstate.copied(f)
3479 src = repo.dirstate.copied(f)
3473 # XXX should we check for rename down to target node?
3480 # XXX should we check for rename down to target node?
3474 if src and src not in names and repo.dirstate[src] == b'r':
3481 if src and src not in names and repo.dirstate[src] == b'r':
3475 dsremoved.add(src)
3482 dsremoved.add(src)
3476 names[src] = True
3483 names[src] = True
3477
3484
3478 # determine the exact nature of the deleted changesets
3485 # determine the exact nature of the deleted changesets
3479 deladded = set(_deleted)
3486 deladded = set(_deleted)
3480 for path in _deleted:
3487 for path in _deleted:
3481 if path in mf:
3488 if path in mf:
3482 deladded.remove(path)
3489 deladded.remove(path)
3483 deleted = _deleted - deladded
3490 deleted = _deleted - deladded
3484
3491
3485 # distinguish between file to forget and the other
3492 # distinguish between file to forget and the other
3486 added = set()
3493 added = set()
3487 for abs in dsadded:
3494 for abs in dsadded:
3488 if repo.dirstate[abs] != b'a':
3495 if repo.dirstate[abs] != b'a':
3489 added.add(abs)
3496 added.add(abs)
3490 dsadded -= added
3497 dsadded -= added
3491
3498
3492 for abs in deladded:
3499 for abs in deladded:
3493 if repo.dirstate[abs] == b'a':
3500 if repo.dirstate[abs] == b'a':
3494 dsadded.add(abs)
3501 dsadded.add(abs)
3495 deladded -= dsadded
3502 deladded -= dsadded
3496
3503
3497 # For files marked as removed, we check if an unknown file is present at
3504 # For files marked as removed, we check if an unknown file is present at
3498 # the same path. If a such file exists it may need to be backed up.
3505 # the same path. If a such file exists it may need to be backed up.
3499 # Making the distinction at this stage helps have simpler backup
3506 # Making the distinction at this stage helps have simpler backup
3500 # logic.
3507 # logic.
3501 removunk = set()
3508 removunk = set()
3502 for abs in removed:
3509 for abs in removed:
3503 target = repo.wjoin(abs)
3510 target = repo.wjoin(abs)
3504 if os.path.lexists(target):
3511 if os.path.lexists(target):
3505 removunk.add(abs)
3512 removunk.add(abs)
3506 removed -= removunk
3513 removed -= removunk
3507
3514
3508 dsremovunk = set()
3515 dsremovunk = set()
3509 for abs in dsremoved:
3516 for abs in dsremoved:
3510 target = repo.wjoin(abs)
3517 target = repo.wjoin(abs)
3511 if os.path.lexists(target):
3518 if os.path.lexists(target):
3512 dsremovunk.add(abs)
3519 dsremovunk.add(abs)
3513 dsremoved -= dsremovunk
3520 dsremoved -= dsremovunk
3514
3521
3515 # action to be actually performed by revert
3522 # action to be actually performed by revert
3516 # (<list of file>, message>) tuple
3523 # (<list of file>, message>) tuple
3517 actions = {
3524 actions = {
3518 b'revert': ([], _(b'reverting %s\n')),
3525 b'revert': ([], _(b'reverting %s\n')),
3519 b'add': ([], _(b'adding %s\n')),
3526 b'add': ([], _(b'adding %s\n')),
3520 b'remove': ([], _(b'removing %s\n')),
3527 b'remove': ([], _(b'removing %s\n')),
3521 b'drop': ([], _(b'removing %s\n')),
3528 b'drop': ([], _(b'removing %s\n')),
3522 b'forget': ([], _(b'forgetting %s\n')),
3529 b'forget': ([], _(b'forgetting %s\n')),
3523 b'undelete': ([], _(b'undeleting %s\n')),
3530 b'undelete': ([], _(b'undeleting %s\n')),
3524 b'noop': (None, _(b'no changes needed to %s\n')),
3531 b'noop': (None, _(b'no changes needed to %s\n')),
3525 b'unknown': (None, _(b'file not managed: %s\n')),
3532 b'unknown': (None, _(b'file not managed: %s\n')),
3526 }
3533 }
3527
3534
3528 # "constant" that convey the backup strategy.
3535 # "constant" that convey the backup strategy.
3529 # All set to `discard` if `no-backup` is set do avoid checking
3536 # All set to `discard` if `no-backup` is set do avoid checking
3530 # no_backup lower in the code.
3537 # no_backup lower in the code.
3531 # These values are ordered for comparison purposes
3538 # These values are ordered for comparison purposes
3532 backupinteractive = 3 # do backup if interactively modified
3539 backupinteractive = 3 # do backup if interactively modified
3533 backup = 2 # unconditionally do backup
3540 backup = 2 # unconditionally do backup
3534 check = 1 # check if the existing file differs from target
3541 check = 1 # check if the existing file differs from target
3535 discard = 0 # never do backup
3542 discard = 0 # never do backup
3536 if opts.get(b'no_backup'):
3543 if opts.get(b'no_backup'):
3537 backupinteractive = backup = check = discard
3544 backupinteractive = backup = check = discard
3538 if interactive:
3545 if interactive:
3539 dsmodifiedbackup = backupinteractive
3546 dsmodifiedbackup = backupinteractive
3540 else:
3547 else:
3541 dsmodifiedbackup = backup
3548 dsmodifiedbackup = backup
3542 tobackup = set()
3549 tobackup = set()
3543
3550
3544 backupanddel = actions[b'remove']
3551 backupanddel = actions[b'remove']
3545 if not opts.get(b'no_backup'):
3552 if not opts.get(b'no_backup'):
3546 backupanddel = actions[b'drop']
3553 backupanddel = actions[b'drop']
3547
3554
3548 disptable = (
3555 disptable = (
3549 # dispatch table:
3556 # dispatch table:
3550 # file state
3557 # file state
3551 # action
3558 # action
3552 # make backup
3559 # make backup
3553 ## Sets that results that will change file on disk
3560 ## Sets that results that will change file on disk
3554 # Modified compared to target, no local change
3561 # Modified compared to target, no local change
3555 (modified, actions[b'revert'], discard),
3562 (modified, actions[b'revert'], discard),
3556 # Modified compared to target, but local file is deleted
3563 # Modified compared to target, but local file is deleted
3557 (deleted, actions[b'revert'], discard),
3564 (deleted, actions[b'revert'], discard),
3558 # Modified compared to target, local change
3565 # Modified compared to target, local change
3559 (dsmodified, actions[b'revert'], dsmodifiedbackup),
3566 (dsmodified, actions[b'revert'], dsmodifiedbackup),
3560 # Added since target
3567 # Added since target
3561 (added, actions[b'remove'], discard),
3568 (added, actions[b'remove'], discard),
3562 # Added in working directory
3569 # Added in working directory
3563 (dsadded, actions[b'forget'], discard),
3570 (dsadded, actions[b'forget'], discard),
3564 # Added since target, have local modification
3571 # Added since target, have local modification
3565 (modadded, backupanddel, backup),
3572 (modadded, backupanddel, backup),
3566 # Added since target but file is missing in working directory
3573 # Added since target but file is missing in working directory
3567 (deladded, actions[b'drop'], discard),
3574 (deladded, actions[b'drop'], discard),
3568 # Removed since target, before working copy parent
3575 # Removed since target, before working copy parent
3569 (removed, actions[b'add'], discard),
3576 (removed, actions[b'add'], discard),
3570 # Same as `removed` but an unknown file exists at the same path
3577 # Same as `removed` but an unknown file exists at the same path
3571 (removunk, actions[b'add'], check),
3578 (removunk, actions[b'add'], check),
3572 # Removed since targe, marked as such in working copy parent
3579 # Removed since targe, marked as such in working copy parent
3573 (dsremoved, actions[b'undelete'], discard),
3580 (dsremoved, actions[b'undelete'], discard),
3574 # Same as `dsremoved` but an unknown file exists at the same path
3581 # Same as `dsremoved` but an unknown file exists at the same path
3575 (dsremovunk, actions[b'undelete'], check),
3582 (dsremovunk, actions[b'undelete'], check),
3576 ## the following sets does not result in any file changes
3583 ## the following sets does not result in any file changes
3577 # File with no modification
3584 # File with no modification
3578 (clean, actions[b'noop'], discard),
3585 (clean, actions[b'noop'], discard),
3579 # Existing file, not tracked anywhere
3586 # Existing file, not tracked anywhere
3580 (unknown, actions[b'unknown'], discard),
3587 (unknown, actions[b'unknown'], discard),
3581 )
3588 )
3582
3589
3583 for abs, exact in sorted(names.items()):
3590 for abs, exact in sorted(names.items()):
3584 # target file to be touch on disk (relative to cwd)
3591 # target file to be touch on disk (relative to cwd)
3585 target = repo.wjoin(abs)
3592 target = repo.wjoin(abs)
3586 # search the entry in the dispatch table.
3593 # search the entry in the dispatch table.
3587 # if the file is in any of these sets, it was touched in the working
3594 # if the file is in any of these sets, it was touched in the working
3588 # directory parent and we are sure it needs to be reverted.
3595 # directory parent and we are sure it needs to be reverted.
3589 for table, (xlist, msg), dobackup in disptable:
3596 for table, (xlist, msg), dobackup in disptable:
3590 if abs not in table:
3597 if abs not in table:
3591 continue
3598 continue
3592 if xlist is not None:
3599 if xlist is not None:
3593 xlist.append(abs)
3600 xlist.append(abs)
3594 if dobackup:
3601 if dobackup:
3595 # If in interactive mode, don't automatically create
3602 # If in interactive mode, don't automatically create
3596 # .orig files (issue4793)
3603 # .orig files (issue4793)
3597 if dobackup == backupinteractive:
3604 if dobackup == backupinteractive:
3598 tobackup.add(abs)
3605 tobackup.add(abs)
3599 elif backup <= dobackup or wctx[abs].cmp(ctx[abs]):
3606 elif backup <= dobackup or wctx[abs].cmp(ctx[abs]):
3600 absbakname = scmutil.backuppath(ui, repo, abs)
3607 absbakname = scmutil.backuppath(ui, repo, abs)
3601 bakname = os.path.relpath(
3608 bakname = os.path.relpath(
3602 absbakname, start=repo.root
3609 absbakname, start=repo.root
3603 )
3610 )
3604 ui.note(
3611 ui.note(
3605 _(b'saving current version of %s as %s\n')
3612 _(b'saving current version of %s as %s\n')
3606 % (uipathfn(abs), uipathfn(bakname))
3613 % (uipathfn(abs), uipathfn(bakname))
3607 )
3614 )
3608 if not opts.get(b'dry_run'):
3615 if not opts.get(b'dry_run'):
3609 if interactive:
3616 if interactive:
3610 util.copyfile(target, absbakname)
3617 util.copyfile(target, absbakname)
3611 else:
3618 else:
3612 util.rename(target, absbakname)
3619 util.rename(target, absbakname)
3613 if opts.get(b'dry_run'):
3620 if opts.get(b'dry_run'):
3614 if ui.verbose or not exact:
3621 if ui.verbose or not exact:
3615 ui.status(msg % uipathfn(abs))
3622 ui.status(msg % uipathfn(abs))
3616 elif exact:
3623 elif exact:
3617 ui.warn(msg % uipathfn(abs))
3624 ui.warn(msg % uipathfn(abs))
3618 break
3625 break
3619
3626
3620 if not opts.get(b'dry_run'):
3627 if not opts.get(b'dry_run'):
3621 needdata = (b'revert', b'add', b'undelete')
3628 needdata = (b'revert', b'add', b'undelete')
3622 oplist = [actions[name][0] for name in needdata]
3629 oplist = [actions[name][0] for name in needdata]
3623 prefetch = scmutil.prefetchfiles
3630 prefetch = scmutil.prefetchfiles
3624 matchfiles = scmutil.matchfiles
3631 matchfiles = scmutil.matchfiles
3625 prefetch(
3632 prefetch(
3626 repo,
3633 repo,
3627 [ctx.rev()],
3634 [ctx.rev()],
3628 matchfiles(repo, [f for sublist in oplist for f in sublist]),
3635 matchfiles(repo, [f for sublist in oplist for f in sublist]),
3629 )
3636 )
3630 match = scmutil.match(repo[None], pats)
3637 match = scmutil.match(repo[None], pats)
3631 _performrevert(
3638 _performrevert(
3632 repo,
3639 repo,
3633 parents,
3640 parents,
3634 ctx,
3641 ctx,
3635 names,
3642 names,
3636 uipathfn,
3643 uipathfn,
3637 actions,
3644 actions,
3638 match,
3645 match,
3639 interactive,
3646 interactive,
3640 tobackup,
3647 tobackup,
3641 )
3648 )
3642
3649
3643 if targetsubs:
3650 if targetsubs:
3644 # Revert the subrepos on the revert list
3651 # Revert the subrepos on the revert list
3645 for sub in targetsubs:
3652 for sub in targetsubs:
3646 try:
3653 try:
3647 wctx.sub(sub).revert(
3654 wctx.sub(sub).revert(
3648 ctx.substate[sub], *pats, **pycompat.strkwargs(opts)
3655 ctx.substate[sub], *pats, **pycompat.strkwargs(opts)
3649 )
3656 )
3650 except KeyError:
3657 except KeyError:
3651 raise error.Abort(
3658 raise error.Abort(
3652 b"subrepository '%s' does not exist in %s!"
3659 b"subrepository '%s' does not exist in %s!"
3653 % (sub, short(ctx.node()))
3660 % (sub, short(ctx.node()))
3654 )
3661 )
3655
3662
3656
3663
3657 def _performrevert(
3664 def _performrevert(
3658 repo,
3665 repo,
3659 parents,
3666 parents,
3660 ctx,
3667 ctx,
3661 names,
3668 names,
3662 uipathfn,
3669 uipathfn,
3663 actions,
3670 actions,
3664 match,
3671 match,
3665 interactive=False,
3672 interactive=False,
3666 tobackup=None,
3673 tobackup=None,
3667 ):
3674 ):
3668 """function that actually perform all the actions computed for revert
3675 """function that actually perform all the actions computed for revert
3669
3676
3670 This is an independent function to let extension to plug in and react to
3677 This is an independent function to let extension to plug in and react to
3671 the imminent revert.
3678 the imminent revert.
3672
3679
3673 Make sure you have the working directory locked when calling this function.
3680 Make sure you have the working directory locked when calling this function.
3674 """
3681 """
3675 parent, p2 = parents
3682 parent, p2 = parents
3676 node = ctx.node()
3683 node = ctx.node()
3677 excluded_files = []
3684 excluded_files = []
3678
3685
3679 def checkout(f):
3686 def checkout(f):
3680 fc = ctx[f]
3687 fc = ctx[f]
3681 repo.wwrite(f, fc.data(), fc.flags())
3688 repo.wwrite(f, fc.data(), fc.flags())
3682
3689
3683 def doremove(f):
3690 def doremove(f):
3684 try:
3691 try:
3685 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
3692 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
3686 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3693 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3687 except OSError:
3694 except OSError:
3688 pass
3695 pass
3689 repo.dirstate.remove(f)
3696 repo.dirstate.remove(f)
3690
3697
3691 def prntstatusmsg(action, f):
3698 def prntstatusmsg(action, f):
3692 exact = names[f]
3699 exact = names[f]
3693 if repo.ui.verbose or not exact:
3700 if repo.ui.verbose or not exact:
3694 repo.ui.status(actions[action][1] % uipathfn(f))
3701 repo.ui.status(actions[action][1] % uipathfn(f))
3695
3702
3696 audit_path = pathutil.pathauditor(repo.root, cached=True)
3703 audit_path = pathutil.pathauditor(repo.root, cached=True)
3697 for f in actions[b'forget'][0]:
3704 for f in actions[b'forget'][0]:
3698 if interactive:
3705 if interactive:
3699 choice = repo.ui.promptchoice(
3706 choice = repo.ui.promptchoice(
3700 _(b"forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3707 _(b"forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3701 )
3708 )
3702 if choice == 0:
3709 if choice == 0:
3703 prntstatusmsg(b'forget', f)
3710 prntstatusmsg(b'forget', f)
3704 repo.dirstate.drop(f)
3711 repo.dirstate.drop(f)
3705 else:
3712 else:
3706 excluded_files.append(f)
3713 excluded_files.append(f)
3707 else:
3714 else:
3708 prntstatusmsg(b'forget', f)
3715 prntstatusmsg(b'forget', f)
3709 repo.dirstate.drop(f)
3716 repo.dirstate.drop(f)
3710 for f in actions[b'remove'][0]:
3717 for f in actions[b'remove'][0]:
3711 audit_path(f)
3718 audit_path(f)
3712 if interactive:
3719 if interactive:
3713 choice = repo.ui.promptchoice(
3720 choice = repo.ui.promptchoice(
3714 _(b"remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3721 _(b"remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3715 )
3722 )
3716 if choice == 0:
3723 if choice == 0:
3717 prntstatusmsg(b'remove', f)
3724 prntstatusmsg(b'remove', f)
3718 doremove(f)
3725 doremove(f)
3719 else:
3726 else:
3720 excluded_files.append(f)
3727 excluded_files.append(f)
3721 else:
3728 else:
3722 prntstatusmsg(b'remove', f)
3729 prntstatusmsg(b'remove', f)
3723 doremove(f)
3730 doremove(f)
3724 for f in actions[b'drop'][0]:
3731 for f in actions[b'drop'][0]:
3725 audit_path(f)
3732 audit_path(f)
3726 prntstatusmsg(b'drop', f)
3733 prntstatusmsg(b'drop', f)
3727 repo.dirstate.remove(f)
3734 repo.dirstate.remove(f)
3728
3735
3729 normal = None
3736 normal = None
3730 if node == parent:
3737 if node == parent:
3731 # We're reverting to our parent. If possible, we'd like status
3738 # We're reverting to our parent. If possible, we'd like status
3732 # to report the file as clean. We have to use normallookup for
3739 # to report the file as clean. We have to use normallookup for
3733 # merges to avoid losing information about merged/dirty files.
3740 # merges to avoid losing information about merged/dirty files.
3734 if p2 != nullid:
3741 if p2 != nullid:
3735 normal = repo.dirstate.normallookup
3742 normal = repo.dirstate.normallookup
3736 else:
3743 else:
3737 normal = repo.dirstate.normal
3744 normal = repo.dirstate.normal
3738
3745
3739 newlyaddedandmodifiedfiles = set()
3746 newlyaddedandmodifiedfiles = set()
3740 if interactive:
3747 if interactive:
3741 # Prompt the user for changes to revert
3748 # Prompt the user for changes to revert
3742 torevert = [f for f in actions[b'revert'][0] if f not in excluded_files]
3749 torevert = [f for f in actions[b'revert'][0] if f not in excluded_files]
3743 m = scmutil.matchfiles(repo, torevert)
3750 m = scmutil.matchfiles(repo, torevert)
3744 diffopts = patch.difffeatureopts(
3751 diffopts = patch.difffeatureopts(
3745 repo.ui,
3752 repo.ui,
3746 whitespace=True,
3753 whitespace=True,
3747 section=b'commands',
3754 section=b'commands',
3748 configprefix=b'revert.interactive.',
3755 configprefix=b'revert.interactive.',
3749 )
3756 )
3750 diffopts.nodates = True
3757 diffopts.nodates = True
3751 diffopts.git = True
3758 diffopts.git = True
3752 operation = b'apply'
3759 operation = b'apply'
3753 if node == parent:
3760 if node == parent:
3754 if repo.ui.configbool(
3761 if repo.ui.configbool(
3755 b'experimental', b'revert.interactive.select-to-keep'
3762 b'experimental', b'revert.interactive.select-to-keep'
3756 ):
3763 ):
3757 operation = b'keep'
3764 operation = b'keep'
3758 else:
3765 else:
3759 operation = b'discard'
3766 operation = b'discard'
3760
3767
3761 if operation == b'apply':
3768 if operation == b'apply':
3762 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3769 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3763 else:
3770 else:
3764 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3771 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3765 originalchunks = patch.parsepatch(diff)
3772 originalchunks = patch.parsepatch(diff)
3766
3773
3767 try:
3774 try:
3768
3775
3769 chunks, opts = recordfilter(
3776 chunks, opts = recordfilter(
3770 repo.ui, originalchunks, match, operation=operation
3777 repo.ui, originalchunks, match, operation=operation
3771 )
3778 )
3772 if operation == b'discard':
3779 if operation == b'discard':
3773 chunks = patch.reversehunks(chunks)
3780 chunks = patch.reversehunks(chunks)
3774
3781
3775 except error.PatchError as err:
3782 except error.PatchError as err:
3776 raise error.Abort(_(b'error parsing patch: %s') % err)
3783 raise error.Abort(_(b'error parsing patch: %s') % err)
3777
3784
3778 # FIXME: when doing an interactive revert of a copy, there's no way of
3785 # FIXME: when doing an interactive revert of a copy, there's no way of
3779 # performing a partial revert of the added file, the only option is
3786 # performing a partial revert of the added file, the only option is
3780 # "remove added file <name> (Yn)?", so we don't need to worry about the
3787 # "remove added file <name> (Yn)?", so we don't need to worry about the
3781 # alsorestore value. Ideally we'd be able to partially revert
3788 # alsorestore value. Ideally we'd be able to partially revert
3782 # copied/renamed files.
3789 # copied/renamed files.
3783 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(
3790 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(
3784 chunks, originalchunks
3791 chunks, originalchunks
3785 )
3792 )
3786 if tobackup is None:
3793 if tobackup is None:
3787 tobackup = set()
3794 tobackup = set()
3788 # Apply changes
3795 # Apply changes
3789 fp = stringio()
3796 fp = stringio()
3790 # chunks are serialized per file, but files aren't sorted
3797 # chunks are serialized per file, but files aren't sorted
3791 for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))):
3798 for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))):
3792 prntstatusmsg(b'revert', f)
3799 prntstatusmsg(b'revert', f)
3793 files = set()
3800 files = set()
3794 for c in chunks:
3801 for c in chunks:
3795 if ishunk(c):
3802 if ishunk(c):
3796 abs = c.header.filename()
3803 abs = c.header.filename()
3797 # Create a backup file only if this hunk should be backed up
3804 # Create a backup file only if this hunk should be backed up
3798 if c.header.filename() in tobackup:
3805 if c.header.filename() in tobackup:
3799 target = repo.wjoin(abs)
3806 target = repo.wjoin(abs)
3800 bakname = scmutil.backuppath(repo.ui, repo, abs)
3807 bakname = scmutil.backuppath(repo.ui, repo, abs)
3801 util.copyfile(target, bakname)
3808 util.copyfile(target, bakname)
3802 tobackup.remove(abs)
3809 tobackup.remove(abs)
3803 if abs not in files:
3810 if abs not in files:
3804 files.add(abs)
3811 files.add(abs)
3805 if operation == b'keep':
3812 if operation == b'keep':
3806 checkout(abs)
3813 checkout(abs)
3807 c.write(fp)
3814 c.write(fp)
3808 dopatch = fp.tell()
3815 dopatch = fp.tell()
3809 fp.seek(0)
3816 fp.seek(0)
3810 if dopatch:
3817 if dopatch:
3811 try:
3818 try:
3812 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3819 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3813 except error.PatchError as err:
3820 except error.PatchError as err:
3814 raise error.Abort(pycompat.bytestr(err))
3821 raise error.Abort(pycompat.bytestr(err))
3815 del fp
3822 del fp
3816 else:
3823 else:
3817 for f in actions[b'revert'][0]:
3824 for f in actions[b'revert'][0]:
3818 prntstatusmsg(b'revert', f)
3825 prntstatusmsg(b'revert', f)
3819 checkout(f)
3826 checkout(f)
3820 if normal:
3827 if normal:
3821 normal(f)
3828 normal(f)
3822
3829
3823 for f in actions[b'add'][0]:
3830 for f in actions[b'add'][0]:
3824 # Don't checkout modified files, they are already created by the diff
3831 # Don't checkout modified files, they are already created by the diff
3825 if f not in newlyaddedandmodifiedfiles:
3832 if f not in newlyaddedandmodifiedfiles:
3826 prntstatusmsg(b'add', f)
3833 prntstatusmsg(b'add', f)
3827 checkout(f)
3834 checkout(f)
3828 repo.dirstate.add(f)
3835 repo.dirstate.add(f)
3829
3836
3830 normal = repo.dirstate.normallookup
3837 normal = repo.dirstate.normallookup
3831 if node == parent and p2 == nullid:
3838 if node == parent and p2 == nullid:
3832 normal = repo.dirstate.normal
3839 normal = repo.dirstate.normal
3833 for f in actions[b'undelete'][0]:
3840 for f in actions[b'undelete'][0]:
3834 if interactive:
3841 if interactive:
3835 choice = repo.ui.promptchoice(
3842 choice = repo.ui.promptchoice(
3836 _(b"add back removed file %s (Yn)?$$ &Yes $$ &No") % f
3843 _(b"add back removed file %s (Yn)?$$ &Yes $$ &No") % f
3837 )
3844 )
3838 if choice == 0:
3845 if choice == 0:
3839 prntstatusmsg(b'undelete', f)
3846 prntstatusmsg(b'undelete', f)
3840 checkout(f)
3847 checkout(f)
3841 normal(f)
3848 normal(f)
3842 else:
3849 else:
3843 excluded_files.append(f)
3850 excluded_files.append(f)
3844 else:
3851 else:
3845 prntstatusmsg(b'undelete', f)
3852 prntstatusmsg(b'undelete', f)
3846 checkout(f)
3853 checkout(f)
3847 normal(f)
3854 normal(f)
3848
3855
3849 copied = copies.pathcopies(repo[parent], ctx)
3856 copied = copies.pathcopies(repo[parent], ctx)
3850
3857
3851 for f in (
3858 for f in (
3852 actions[b'add'][0] + actions[b'undelete'][0] + actions[b'revert'][0]
3859 actions[b'add'][0] + actions[b'undelete'][0] + actions[b'revert'][0]
3853 ):
3860 ):
3854 if f in copied:
3861 if f in copied:
3855 repo.dirstate.copy(copied[f], f)
3862 repo.dirstate.copy(copied[f], f)
3856
3863
3857
3864
3858 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3865 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3859 # commands.outgoing. "missing" is "missing" of the result of
3866 # commands.outgoing. "missing" is "missing" of the result of
3860 # "findcommonoutgoing()"
3867 # "findcommonoutgoing()"
3861 outgoinghooks = util.hooks()
3868 outgoinghooks = util.hooks()
3862
3869
3863 # a list of (ui, repo) functions called by commands.summary
3870 # a list of (ui, repo) functions called by commands.summary
3864 summaryhooks = util.hooks()
3871 summaryhooks = util.hooks()
3865
3872
3866 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3873 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3867 #
3874 #
3868 # functions should return tuple of booleans below, if 'changes' is None:
3875 # functions should return tuple of booleans below, if 'changes' is None:
3869 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3876 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3870 #
3877 #
3871 # otherwise, 'changes' is a tuple of tuples below:
3878 # otherwise, 'changes' is a tuple of tuples below:
3872 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3879 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3873 # - (desturl, destbranch, destpeer, outgoing)
3880 # - (desturl, destbranch, destpeer, outgoing)
3874 summaryremotehooks = util.hooks()
3881 summaryremotehooks = util.hooks()
3875
3882
3876
3883
3877 def checkunfinished(repo, commit=False, skipmerge=False):
3884 def checkunfinished(repo, commit=False, skipmerge=False):
3878 '''Look for an unfinished multistep operation, like graft, and abort
3885 '''Look for an unfinished multistep operation, like graft, and abort
3879 if found. It's probably good to check this right before
3886 if found. It's probably good to check this right before
3880 bailifchanged().
3887 bailifchanged().
3881 '''
3888 '''
3882 # Check for non-clearable states first, so things like rebase will take
3889 # Check for non-clearable states first, so things like rebase will take
3883 # precedence over update.
3890 # precedence over update.
3884 for state in statemod._unfinishedstates:
3891 for state in statemod._unfinishedstates:
3885 if (
3892 if (
3886 state._clearable
3893 state._clearable
3887 or (commit and state._allowcommit)
3894 or (commit and state._allowcommit)
3888 or state._reportonly
3895 or state._reportonly
3889 ):
3896 ):
3890 continue
3897 continue
3891 if state.isunfinished(repo):
3898 if state.isunfinished(repo):
3892 raise error.Abort(state.msg(), hint=state.hint())
3899 raise error.Abort(state.msg(), hint=state.hint())
3893
3900
3894 for s in statemod._unfinishedstates:
3901 for s in statemod._unfinishedstates:
3895 if (
3902 if (
3896 not s._clearable
3903 not s._clearable
3897 or (commit and s._allowcommit)
3904 or (commit and s._allowcommit)
3898 or (s._opname == b'merge' and skipmerge)
3905 or (s._opname == b'merge' and skipmerge)
3899 or s._reportonly
3906 or s._reportonly
3900 ):
3907 ):
3901 continue
3908 continue
3902 if s.isunfinished(repo):
3909 if s.isunfinished(repo):
3903 raise error.Abort(s.msg(), hint=s.hint())
3910 raise error.Abort(s.msg(), hint=s.hint())
3904
3911
3905
3912
3906 def clearunfinished(repo):
3913 def clearunfinished(repo):
3907 '''Check for unfinished operations (as above), and clear the ones
3914 '''Check for unfinished operations (as above), and clear the ones
3908 that are clearable.
3915 that are clearable.
3909 '''
3916 '''
3910 for state in statemod._unfinishedstates:
3917 for state in statemod._unfinishedstates:
3911 if state._reportonly:
3918 if state._reportonly:
3912 continue
3919 continue
3913 if not state._clearable and state.isunfinished(repo):
3920 if not state._clearable and state.isunfinished(repo):
3914 raise error.Abort(state.msg(), hint=state.hint())
3921 raise error.Abort(state.msg(), hint=state.hint())
3915
3922
3916 for s in statemod._unfinishedstates:
3923 for s in statemod._unfinishedstates:
3917 if s._opname == b'merge' or state._reportonly:
3924 if s._opname == b'merge' or state._reportonly:
3918 continue
3925 continue
3919 if s._clearable and s.isunfinished(repo):
3926 if s._clearable and s.isunfinished(repo):
3920 util.unlink(repo.vfs.join(s._fname))
3927 util.unlink(repo.vfs.join(s._fname))
3921
3928
3922
3929
3923 def getunfinishedstate(repo):
3930 def getunfinishedstate(repo):
3924 ''' Checks for unfinished operations and returns statecheck object
3931 ''' Checks for unfinished operations and returns statecheck object
3925 for it'''
3932 for it'''
3926 for state in statemod._unfinishedstates:
3933 for state in statemod._unfinishedstates:
3927 if state.isunfinished(repo):
3934 if state.isunfinished(repo):
3928 return state
3935 return state
3929 return None
3936 return None
3930
3937
3931
3938
3932 def howtocontinue(repo):
3939 def howtocontinue(repo):
3933 '''Check for an unfinished operation and return the command to finish
3940 '''Check for an unfinished operation and return the command to finish
3934 it.
3941 it.
3935
3942
3936 statemod._unfinishedstates list is checked for an unfinished operation
3943 statemod._unfinishedstates list is checked for an unfinished operation
3937 and the corresponding message to finish it is generated if a method to
3944 and the corresponding message to finish it is generated if a method to
3938 continue is supported by the operation.
3945 continue is supported by the operation.
3939
3946
3940 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3947 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3941 a boolean.
3948 a boolean.
3942 '''
3949 '''
3943 contmsg = _(b"continue: %s")
3950 contmsg = _(b"continue: %s")
3944 for state in statemod._unfinishedstates:
3951 for state in statemod._unfinishedstates:
3945 if not state._continueflag:
3952 if not state._continueflag:
3946 continue
3953 continue
3947 if state.isunfinished(repo):
3954 if state.isunfinished(repo):
3948 return contmsg % state.continuemsg(), True
3955 return contmsg % state.continuemsg(), True
3949 if repo[None].dirty(missing=True, merge=False, branch=False):
3956 if repo[None].dirty(missing=True, merge=False, branch=False):
3950 return contmsg % _(b"hg commit"), False
3957 return contmsg % _(b"hg commit"), False
3951 return None, None
3958 return None, None
3952
3959
3953
3960
3954 def checkafterresolved(repo):
3961 def checkafterresolved(repo):
3955 '''Inform the user about the next action after completing hg resolve
3962 '''Inform the user about the next action after completing hg resolve
3956
3963
3957 If there's a an unfinished operation that supports continue flag,
3964 If there's a an unfinished operation that supports continue flag,
3958 howtocontinue will yield repo.ui.warn as the reporter.
3965 howtocontinue will yield repo.ui.warn as the reporter.
3959
3966
3960 Otherwise, it will yield repo.ui.note.
3967 Otherwise, it will yield repo.ui.note.
3961 '''
3968 '''
3962 msg, warning = howtocontinue(repo)
3969 msg, warning = howtocontinue(repo)
3963 if msg is not None:
3970 if msg is not None:
3964 if warning:
3971 if warning:
3965 repo.ui.warn(b"%s\n" % msg)
3972 repo.ui.warn(b"%s\n" % msg)
3966 else:
3973 else:
3967 repo.ui.note(b"%s\n" % msg)
3974 repo.ui.note(b"%s\n" % msg)
3968
3975
3969
3976
3970 def wrongtooltocontinue(repo, task):
3977 def wrongtooltocontinue(repo, task):
3971 '''Raise an abort suggesting how to properly continue if there is an
3978 '''Raise an abort suggesting how to properly continue if there is an
3972 active task.
3979 active task.
3973
3980
3974 Uses howtocontinue() to find the active task.
3981 Uses howtocontinue() to find the active task.
3975
3982
3976 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3983 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3977 a hint.
3984 a hint.
3978 '''
3985 '''
3979 after = howtocontinue(repo)
3986 after = howtocontinue(repo)
3980 hint = None
3987 hint = None
3981 if after[1]:
3988 if after[1]:
3982 hint = after[0]
3989 hint = after[0]
3983 raise error.Abort(_(b'no %s in progress') % task, hint=hint)
3990 raise error.Abort(_(b'no %s in progress') % task, hint=hint)
3984
3991
3985
3992
3986 def abortgraft(ui, repo, graftstate):
3993 def abortgraft(ui, repo, graftstate):
3987 """abort the interrupted graft and rollbacks to the state before interrupted
3994 """abort the interrupted graft and rollbacks to the state before interrupted
3988 graft"""
3995 graft"""
3989 if not graftstate.exists():
3996 if not graftstate.exists():
3990 raise error.Abort(_(b"no interrupted graft to abort"))
3997 raise error.Abort(_(b"no interrupted graft to abort"))
3991 statedata = readgraftstate(repo, graftstate)
3998 statedata = readgraftstate(repo, graftstate)
3992 newnodes = statedata.get(b'newnodes')
3999 newnodes = statedata.get(b'newnodes')
3993 if newnodes is None:
4000 if newnodes is None:
3994 # and old graft state which does not have all the data required to abort
4001 # and old graft state which does not have all the data required to abort
3995 # the graft
4002 # the graft
3996 raise error.Abort(_(b"cannot abort using an old graftstate"))
4003 raise error.Abort(_(b"cannot abort using an old graftstate"))
3997
4004
3998 # changeset from which graft operation was started
4005 # changeset from which graft operation was started
3999 if len(newnodes) > 0:
4006 if len(newnodes) > 0:
4000 startctx = repo[newnodes[0]].p1()
4007 startctx = repo[newnodes[0]].p1()
4001 else:
4008 else:
4002 startctx = repo[b'.']
4009 startctx = repo[b'.']
4003 # whether to strip or not
4010 # whether to strip or not
4004 cleanup = False
4011 cleanup = False
4005 from . import hg
4012 from . import hg
4006
4013
4007 if newnodes:
4014 if newnodes:
4008 newnodes = [repo[r].rev() for r in newnodes]
4015 newnodes = [repo[r].rev() for r in newnodes]
4009 cleanup = True
4016 cleanup = True
4010 # checking that none of the newnodes turned public or is public
4017 # checking that none of the newnodes turned public or is public
4011 immutable = [c for c in newnodes if not repo[c].mutable()]
4018 immutable = [c for c in newnodes if not repo[c].mutable()]
4012 if immutable:
4019 if immutable:
4013 repo.ui.warn(
4020 repo.ui.warn(
4014 _(b"cannot clean up public changesets %s\n")
4021 _(b"cannot clean up public changesets %s\n")
4015 % b', '.join(bytes(repo[r]) for r in immutable),
4022 % b', '.join(bytes(repo[r]) for r in immutable),
4016 hint=_(b"see 'hg help phases' for details"),
4023 hint=_(b"see 'hg help phases' for details"),
4017 )
4024 )
4018 cleanup = False
4025 cleanup = False
4019
4026
4020 # checking that no new nodes are created on top of grafted revs
4027 # checking that no new nodes are created on top of grafted revs
4021 desc = set(repo.changelog.descendants(newnodes))
4028 desc = set(repo.changelog.descendants(newnodes))
4022 if desc - set(newnodes):
4029 if desc - set(newnodes):
4023 repo.ui.warn(
4030 repo.ui.warn(
4024 _(
4031 _(
4025 b"new changesets detected on destination "
4032 b"new changesets detected on destination "
4026 b"branch, can't strip\n"
4033 b"branch, can't strip\n"
4027 )
4034 )
4028 )
4035 )
4029 cleanup = False
4036 cleanup = False
4030
4037
4031 if cleanup:
4038 if cleanup:
4032 with repo.wlock(), repo.lock():
4039 with repo.wlock(), repo.lock():
4033 hg.updaterepo(repo, startctx.node(), overwrite=True)
4040 hg.updaterepo(repo, startctx.node(), overwrite=True)
4034 # stripping the new nodes created
4041 # stripping the new nodes created
4035 strippoints = [
4042 strippoints = [
4036 c.node() for c in repo.set(b"roots(%ld)", newnodes)
4043 c.node() for c in repo.set(b"roots(%ld)", newnodes)
4037 ]
4044 ]
4038 repair.strip(repo.ui, repo, strippoints, backup=False)
4045 repair.strip(repo.ui, repo, strippoints, backup=False)
4039
4046
4040 if not cleanup:
4047 if not cleanup:
4041 # we don't update to the startnode if we can't strip
4048 # we don't update to the startnode if we can't strip
4042 startctx = repo[b'.']
4049 startctx = repo[b'.']
4043 hg.updaterepo(repo, startctx.node(), overwrite=True)
4050 hg.updaterepo(repo, startctx.node(), overwrite=True)
4044
4051
4045 ui.status(_(b"graft aborted\n"))
4052 ui.status(_(b"graft aborted\n"))
4046 ui.status(_(b"working directory is now at %s\n") % startctx.hex()[:12])
4053 ui.status(_(b"working directory is now at %s\n") % startctx.hex()[:12])
4047 graftstate.delete()
4054 graftstate.delete()
4048 return 0
4055 return 0
4049
4056
4050
4057
4051 def readgraftstate(repo, graftstate):
4058 def readgraftstate(repo, graftstate):
4052 # type: (Any, statemod.cmdstate) -> Dict[bytes, Any]
4059 # type: (Any, statemod.cmdstate) -> Dict[bytes, Any]
4053 """read the graft state file and return a dict of the data stored in it"""
4060 """read the graft state file and return a dict of the data stored in it"""
4054 try:
4061 try:
4055 return graftstate.read()
4062 return graftstate.read()
4056 except error.CorruptedState:
4063 except error.CorruptedState:
4057 nodes = repo.vfs.read(b'graftstate').splitlines()
4064 nodes = repo.vfs.read(b'graftstate').splitlines()
4058 return {b'nodes': nodes}
4065 return {b'nodes': nodes}
4059
4066
4060
4067
4061 def hgabortgraft(ui, repo):
4068 def hgabortgraft(ui, repo):
4062 """ abort logic for aborting graft using 'hg abort'"""
4069 """ abort logic for aborting graft using 'hg abort'"""
4063 with repo.wlock():
4070 with repo.wlock():
4064 graftstate = statemod.cmdstate(repo, b'graftstate')
4071 graftstate = statemod.cmdstate(repo, b'graftstate')
4065 return abortgraft(ui, repo, graftstate)
4072 return abortgraft(ui, repo, graftstate)
@@ -1,7824 +1,7829 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __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 wdirhex,
22 wdirhex,
23 wdirrev,
23 wdirrev,
24 )
24 )
25 from .pycompat import open
25 from .pycompat import open
26 from . import (
26 from . import (
27 archival,
27 archival,
28 bookmarks,
28 bookmarks,
29 bundle2,
29 bundle2,
30 changegroup,
30 changegroup,
31 cmdutil,
31 cmdutil,
32 copies,
32 copies,
33 debugcommands as debugcommandsmod,
33 debugcommands as debugcommandsmod,
34 destutil,
34 destutil,
35 dirstateguard,
35 dirstateguard,
36 discovery,
36 discovery,
37 encoding,
37 encoding,
38 error,
38 error,
39 exchange,
39 exchange,
40 extensions,
40 extensions,
41 filemerge,
41 filemerge,
42 formatter,
42 formatter,
43 graphmod,
43 graphmod,
44 hbisect,
44 hbisect,
45 help,
45 help,
46 hg,
46 hg,
47 logcmdutil,
47 logcmdutil,
48 merge as mergemod,
48 merge as mergemod,
49 narrowspec,
49 narrowspec,
50 obsolete,
50 obsolete,
51 obsutil,
51 obsutil,
52 patch,
52 patch,
53 phases,
53 phases,
54 pycompat,
54 pycompat,
55 rcutil,
55 rcutil,
56 registrar,
56 registrar,
57 revsetlang,
57 revsetlang,
58 rewriteutil,
58 rewriteutil,
59 scmutil,
59 scmutil,
60 server,
60 server,
61 shelve as shelvemod,
61 shelve as shelvemod,
62 state as statemod,
62 state as statemod,
63 streamclone,
63 streamclone,
64 tags as tagsmod,
64 tags as tagsmod,
65 ui as uimod,
65 ui as uimod,
66 util,
66 util,
67 verify as verifymod,
67 verify as verifymod,
68 wireprotoserver,
68 wireprotoserver,
69 )
69 )
70 from .utils import (
70 from .utils import (
71 dateutil,
71 dateutil,
72 stringutil,
72 stringutil,
73 )
73 )
74
74
75 table = {}
75 table = {}
76 table.update(debugcommandsmod.command._table)
76 table.update(debugcommandsmod.command._table)
77
77
78 command = registrar.command(table)
78 command = registrar.command(table)
79 INTENT_READONLY = registrar.INTENT_READONLY
79 INTENT_READONLY = registrar.INTENT_READONLY
80
80
81 # common command options
81 # common command options
82
82
83 globalopts = [
83 globalopts = [
84 (
84 (
85 b'R',
85 b'R',
86 b'repository',
86 b'repository',
87 b'',
87 b'',
88 _(b'repository root directory or name of overlay bundle file'),
88 _(b'repository root directory or name of overlay bundle file'),
89 _(b'REPO'),
89 _(b'REPO'),
90 ),
90 ),
91 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
91 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
92 (
92 (
93 b'y',
93 b'y',
94 b'noninteractive',
94 b'noninteractive',
95 None,
95 None,
96 _(
96 _(
97 b'do not prompt, automatically pick the first choice for all prompts'
97 b'do not prompt, automatically pick the first choice for all prompts'
98 ),
98 ),
99 ),
99 ),
100 (b'q', b'quiet', None, _(b'suppress output')),
100 (b'q', b'quiet', None, _(b'suppress output')),
101 (b'v', b'verbose', None, _(b'enable additional output')),
101 (b'v', b'verbose', None, _(b'enable additional output')),
102 (
102 (
103 b'',
103 b'',
104 b'color',
104 b'color',
105 b'',
105 b'',
106 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
106 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
107 # and should not be translated
107 # and should not be translated
108 _(b"when to colorize (boolean, always, auto, never, or debug)"),
108 _(b"when to colorize (boolean, always, auto, never, or debug)"),
109 _(b'TYPE'),
109 _(b'TYPE'),
110 ),
110 ),
111 (
111 (
112 b'',
112 b'',
113 b'config',
113 b'config',
114 [],
114 [],
115 _(b'set/override config option (use \'section.name=value\')'),
115 _(b'set/override config option (use \'section.name=value\')'),
116 _(b'CONFIG'),
116 _(b'CONFIG'),
117 ),
117 ),
118 (b'', b'debug', None, _(b'enable debugging output')),
118 (b'', b'debug', None, _(b'enable debugging output')),
119 (b'', b'debugger', None, _(b'start debugger')),
119 (b'', b'debugger', None, _(b'start debugger')),
120 (
120 (
121 b'',
121 b'',
122 b'encoding',
122 b'encoding',
123 encoding.encoding,
123 encoding.encoding,
124 _(b'set the charset encoding'),
124 _(b'set the charset encoding'),
125 _(b'ENCODE'),
125 _(b'ENCODE'),
126 ),
126 ),
127 (
127 (
128 b'',
128 b'',
129 b'encodingmode',
129 b'encodingmode',
130 encoding.encodingmode,
130 encoding.encodingmode,
131 _(b'set the charset encoding mode'),
131 _(b'set the charset encoding mode'),
132 _(b'MODE'),
132 _(b'MODE'),
133 ),
133 ),
134 (b'', b'traceback', None, _(b'always print a traceback on exception')),
134 (b'', b'traceback', None, _(b'always print a traceback on exception')),
135 (b'', b'time', None, _(b'time how long the command takes')),
135 (b'', b'time', None, _(b'time how long the command takes')),
136 (b'', b'profile', None, _(b'print command execution profile')),
136 (b'', b'profile', None, _(b'print command execution profile')),
137 (b'', b'version', None, _(b'output version information and exit')),
137 (b'', b'version', None, _(b'output version information and exit')),
138 (b'h', b'help', None, _(b'display help and exit')),
138 (b'h', b'help', None, _(b'display help and exit')),
139 (b'', b'hidden', False, _(b'consider hidden changesets')),
139 (b'', b'hidden', False, _(b'consider hidden changesets')),
140 (
140 (
141 b'',
141 b'',
142 b'pager',
142 b'pager',
143 b'auto',
143 b'auto',
144 _(b"when to paginate (boolean, always, auto, or never)"),
144 _(b"when to paginate (boolean, always, auto, or never)"),
145 _(b'TYPE'),
145 _(b'TYPE'),
146 ),
146 ),
147 ]
147 ]
148
148
149 dryrunopts = cmdutil.dryrunopts
149 dryrunopts = cmdutil.dryrunopts
150 remoteopts = cmdutil.remoteopts
150 remoteopts = cmdutil.remoteopts
151 walkopts = cmdutil.walkopts
151 walkopts = cmdutil.walkopts
152 commitopts = cmdutil.commitopts
152 commitopts = cmdutil.commitopts
153 commitopts2 = cmdutil.commitopts2
153 commitopts2 = cmdutil.commitopts2
154 commitopts3 = cmdutil.commitopts3
154 commitopts3 = cmdutil.commitopts3
155 formatteropts = cmdutil.formatteropts
155 formatteropts = cmdutil.formatteropts
156 templateopts = cmdutil.templateopts
156 templateopts = cmdutil.templateopts
157 logopts = cmdutil.logopts
157 logopts = cmdutil.logopts
158 diffopts = cmdutil.diffopts
158 diffopts = cmdutil.diffopts
159 diffwsopts = cmdutil.diffwsopts
159 diffwsopts = cmdutil.diffwsopts
160 diffopts2 = cmdutil.diffopts2
160 diffopts2 = cmdutil.diffopts2
161 mergetoolopts = cmdutil.mergetoolopts
161 mergetoolopts = cmdutil.mergetoolopts
162 similarityopts = cmdutil.similarityopts
162 similarityopts = cmdutil.similarityopts
163 subrepoopts = cmdutil.subrepoopts
163 subrepoopts = cmdutil.subrepoopts
164 debugrevlogopts = cmdutil.debugrevlogopts
164 debugrevlogopts = cmdutil.debugrevlogopts
165
165
166 # Commands start here, listed alphabetically
166 # Commands start here, listed alphabetically
167
167
168
168
169 @command(
169 @command(
170 b'abort',
170 b'abort',
171 dryrunopts,
171 dryrunopts,
172 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
172 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
173 helpbasic=True,
173 helpbasic=True,
174 )
174 )
175 def abort(ui, repo, **opts):
175 def abort(ui, repo, **opts):
176 """abort an unfinished operation (EXPERIMENTAL)
176 """abort an unfinished operation (EXPERIMENTAL)
177
177
178 Aborts a multistep operation like graft, histedit, rebase, merge,
178 Aborts a multistep operation like graft, histedit, rebase, merge,
179 and unshelve if they are in an unfinished state.
179 and unshelve if they are in an unfinished state.
180
180
181 use --dry-run/-n to dry run the command.
181 use --dry-run/-n to dry run the command.
182 """
182 """
183 dryrun = opts.get('dry_run')
183 dryrun = opts.get('dry_run')
184 abortstate = cmdutil.getunfinishedstate(repo)
184 abortstate = cmdutil.getunfinishedstate(repo)
185 if not abortstate:
185 if not abortstate:
186 raise error.Abort(_(b'no operation in progress'))
186 raise error.Abort(_(b'no operation in progress'))
187 if not abortstate.abortfunc:
187 if not abortstate.abortfunc:
188 raise error.Abort(
188 raise error.Abort(
189 (
189 (
190 _(b"%s in progress but does not support 'hg abort'")
190 _(b"%s in progress but does not support 'hg abort'")
191 % (abortstate._opname)
191 % (abortstate._opname)
192 ),
192 ),
193 hint=abortstate.hint(),
193 hint=abortstate.hint(),
194 )
194 )
195 if dryrun:
195 if dryrun:
196 ui.status(
196 ui.status(
197 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
197 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
198 )
198 )
199 return
199 return
200 return abortstate.abortfunc(ui, repo)
200 return abortstate.abortfunc(ui, repo)
201
201
202
202
203 @command(
203 @command(
204 b'add',
204 b'add',
205 walkopts + subrepoopts + dryrunopts,
205 walkopts + subrepoopts + dryrunopts,
206 _(b'[OPTION]... [FILE]...'),
206 _(b'[OPTION]... [FILE]...'),
207 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
207 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
208 helpbasic=True,
208 helpbasic=True,
209 inferrepo=True,
209 inferrepo=True,
210 )
210 )
211 def add(ui, repo, *pats, **opts):
211 def add(ui, repo, *pats, **opts):
212 """add the specified files on the next commit
212 """add the specified files on the next commit
213
213
214 Schedule files to be version controlled and added to the
214 Schedule files to be version controlled and added to the
215 repository.
215 repository.
216
216
217 The files will be added to the repository at the next commit. To
217 The files will be added to the repository at the next commit. To
218 undo an add before that, see :hg:`forget`.
218 undo an add before that, see :hg:`forget`.
219
219
220 If no names are given, add all files to the repository (except
220 If no names are given, add all files to the repository (except
221 files matching ``.hgignore``).
221 files matching ``.hgignore``).
222
222
223 .. container:: verbose
223 .. container:: verbose
224
224
225 Examples:
225 Examples:
226
226
227 - New (unknown) files are added
227 - New (unknown) files are added
228 automatically by :hg:`add`::
228 automatically by :hg:`add`::
229
229
230 $ ls
230 $ ls
231 foo.c
231 foo.c
232 $ hg status
232 $ hg status
233 ? foo.c
233 ? foo.c
234 $ hg add
234 $ hg add
235 adding foo.c
235 adding foo.c
236 $ hg status
236 $ hg status
237 A foo.c
237 A foo.c
238
238
239 - Specific files to be added can be specified::
239 - Specific files to be added can be specified::
240
240
241 $ ls
241 $ ls
242 bar.c foo.c
242 bar.c foo.c
243 $ hg status
243 $ hg status
244 ? bar.c
244 ? bar.c
245 ? foo.c
245 ? foo.c
246 $ hg add bar.c
246 $ hg add bar.c
247 $ hg status
247 $ hg status
248 A bar.c
248 A bar.c
249 ? foo.c
249 ? foo.c
250
250
251 Returns 0 if all files are successfully added.
251 Returns 0 if all files are successfully added.
252 """
252 """
253
253
254 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
254 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
255 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
255 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
256 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
256 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
257 return rejected and 1 or 0
257 return rejected and 1 or 0
258
258
259
259
260 @command(
260 @command(
261 b'addremove',
261 b'addremove',
262 similarityopts + subrepoopts + walkopts + dryrunopts,
262 similarityopts + subrepoopts + walkopts + dryrunopts,
263 _(b'[OPTION]... [FILE]...'),
263 _(b'[OPTION]... [FILE]...'),
264 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
264 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
265 inferrepo=True,
265 inferrepo=True,
266 )
266 )
267 def addremove(ui, repo, *pats, **opts):
267 def addremove(ui, repo, *pats, **opts):
268 """add all new files, delete all missing files
268 """add all new files, delete all missing files
269
269
270 Add all new files and remove all missing files from the
270 Add all new files and remove all missing files from the
271 repository.
271 repository.
272
272
273 Unless names are given, new files are ignored if they match any of
273 Unless names are given, new files are ignored if they match any of
274 the patterns in ``.hgignore``. As with add, these changes take
274 the patterns in ``.hgignore``. As with add, these changes take
275 effect at the next commit.
275 effect at the next commit.
276
276
277 Use the -s/--similarity option to detect renamed files. This
277 Use the -s/--similarity option to detect renamed files. This
278 option takes a percentage between 0 (disabled) and 100 (files must
278 option takes a percentage between 0 (disabled) and 100 (files must
279 be identical) as its parameter. With a parameter greater than 0,
279 be identical) as its parameter. With a parameter greater than 0,
280 this compares every removed file with every added file and records
280 this compares every removed file with every added file and records
281 those similar enough as renames. Detecting renamed files this way
281 those similar enough as renames. Detecting renamed files this way
282 can be expensive. After using this option, :hg:`status -C` can be
282 can be expensive. After using this option, :hg:`status -C` can be
283 used to check which files were identified as moved or renamed. If
283 used to check which files were identified as moved or renamed. If
284 not specified, -s/--similarity defaults to 100 and only renames of
284 not specified, -s/--similarity defaults to 100 and only renames of
285 identical files are detected.
285 identical files are detected.
286
286
287 .. container:: verbose
287 .. container:: verbose
288
288
289 Examples:
289 Examples:
290
290
291 - A number of files (bar.c and foo.c) are new,
291 - A number of files (bar.c and foo.c) are new,
292 while foobar.c has been removed (without using :hg:`remove`)
292 while foobar.c has been removed (without using :hg:`remove`)
293 from the repository::
293 from the repository::
294
294
295 $ ls
295 $ ls
296 bar.c foo.c
296 bar.c foo.c
297 $ hg status
297 $ hg status
298 ! foobar.c
298 ! foobar.c
299 ? bar.c
299 ? bar.c
300 ? foo.c
300 ? foo.c
301 $ hg addremove
301 $ hg addremove
302 adding bar.c
302 adding bar.c
303 adding foo.c
303 adding foo.c
304 removing foobar.c
304 removing foobar.c
305 $ hg status
305 $ hg status
306 A bar.c
306 A bar.c
307 A foo.c
307 A foo.c
308 R foobar.c
308 R foobar.c
309
309
310 - A file foobar.c was moved to foo.c without using :hg:`rename`.
310 - A file foobar.c was moved to foo.c without using :hg:`rename`.
311 Afterwards, it was edited slightly::
311 Afterwards, it was edited slightly::
312
312
313 $ ls
313 $ ls
314 foo.c
314 foo.c
315 $ hg status
315 $ hg status
316 ! foobar.c
316 ! foobar.c
317 ? foo.c
317 ? foo.c
318 $ hg addremove --similarity 90
318 $ hg addremove --similarity 90
319 removing foobar.c
319 removing foobar.c
320 adding foo.c
320 adding foo.c
321 recording removal of foobar.c as rename to foo.c (94% similar)
321 recording removal of foobar.c as rename to foo.c (94% similar)
322 $ hg status -C
322 $ hg status -C
323 A foo.c
323 A foo.c
324 foobar.c
324 foobar.c
325 R foobar.c
325 R foobar.c
326
326
327 Returns 0 if all files are successfully added.
327 Returns 0 if all files are successfully added.
328 """
328 """
329 opts = pycompat.byteskwargs(opts)
329 opts = pycompat.byteskwargs(opts)
330 if not opts.get(b'similarity'):
330 if not opts.get(b'similarity'):
331 opts[b'similarity'] = b'100'
331 opts[b'similarity'] = b'100'
332 matcher = scmutil.match(repo[None], pats, opts)
332 matcher = scmutil.match(repo[None], pats, opts)
333 relative = scmutil.anypats(pats, opts)
333 relative = scmutil.anypats(pats, opts)
334 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
334 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
335 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
335 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
336
336
337
337
338 @command(
338 @command(
339 b'annotate|blame',
339 b'annotate|blame',
340 [
340 [
341 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
341 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
342 (
342 (
343 b'',
343 b'',
344 b'follow',
344 b'follow',
345 None,
345 None,
346 _(b'follow copies/renames and list the filename (DEPRECATED)'),
346 _(b'follow copies/renames and list the filename (DEPRECATED)'),
347 ),
347 ),
348 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
348 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
349 (b'a', b'text', None, _(b'treat all files as text')),
349 (b'a', b'text', None, _(b'treat all files as text')),
350 (b'u', b'user', None, _(b'list the author (long with -v)')),
350 (b'u', b'user', None, _(b'list the author (long with -v)')),
351 (b'f', b'file', None, _(b'list the filename')),
351 (b'f', b'file', None, _(b'list the filename')),
352 (b'd', b'date', None, _(b'list the date (short with -q)')),
352 (b'd', b'date', None, _(b'list the date (short with -q)')),
353 (b'n', b'number', None, _(b'list the revision number (default)')),
353 (b'n', b'number', None, _(b'list the revision number (default)')),
354 (b'c', b'changeset', None, _(b'list the changeset')),
354 (b'c', b'changeset', None, _(b'list the changeset')),
355 (
355 (
356 b'l',
356 b'l',
357 b'line-number',
357 b'line-number',
358 None,
358 None,
359 _(b'show line number at the first appearance'),
359 _(b'show line number at the first appearance'),
360 ),
360 ),
361 (
361 (
362 b'',
362 b'',
363 b'skip',
363 b'skip',
364 [],
364 [],
365 _(b'revset to not display (EXPERIMENTAL)'),
365 _(b'revset to not display (EXPERIMENTAL)'),
366 _(b'REV'),
366 _(b'REV'),
367 ),
367 ),
368 ]
368 ]
369 + diffwsopts
369 + diffwsopts
370 + walkopts
370 + walkopts
371 + formatteropts,
371 + formatteropts,
372 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
372 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
373 helpcategory=command.CATEGORY_FILE_CONTENTS,
373 helpcategory=command.CATEGORY_FILE_CONTENTS,
374 helpbasic=True,
374 helpbasic=True,
375 inferrepo=True,
375 inferrepo=True,
376 )
376 )
377 def annotate(ui, repo, *pats, **opts):
377 def annotate(ui, repo, *pats, **opts):
378 """show changeset information by line for each file
378 """show changeset information by line for each file
379
379
380 List changes in files, showing the revision id responsible for
380 List changes in files, showing the revision id responsible for
381 each line.
381 each line.
382
382
383 This command is useful for discovering when a change was made and
383 This command is useful for discovering when a change was made and
384 by whom.
384 by whom.
385
385
386 If you include --file, --user, or --date, the revision number is
386 If you include --file, --user, or --date, the revision number is
387 suppressed unless you also include --number.
387 suppressed unless you also include --number.
388
388
389 Without the -a/--text option, annotate will avoid processing files
389 Without the -a/--text option, annotate will avoid processing files
390 it detects as binary. With -a, annotate will annotate the file
390 it detects as binary. With -a, annotate will annotate the file
391 anyway, although the results will probably be neither useful
391 anyway, although the results will probably be neither useful
392 nor desirable.
392 nor desirable.
393
393
394 .. container:: verbose
394 .. container:: verbose
395
395
396 Template:
396 Template:
397
397
398 The following keywords are supported in addition to the common template
398 The following keywords are supported in addition to the common template
399 keywords and functions. See also :hg:`help templates`.
399 keywords and functions. See also :hg:`help templates`.
400
400
401 :lines: List of lines with annotation data.
401 :lines: List of lines with annotation data.
402 :path: String. Repository-absolute path of the specified file.
402 :path: String. Repository-absolute path of the specified file.
403
403
404 And each entry of ``{lines}`` provides the following sub-keywords in
404 And each entry of ``{lines}`` provides the following sub-keywords in
405 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
405 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
406
406
407 :line: String. Line content.
407 :line: String. Line content.
408 :lineno: Integer. Line number at that revision.
408 :lineno: Integer. Line number at that revision.
409 :path: String. Repository-absolute path of the file at that revision.
409 :path: String. Repository-absolute path of the file at that revision.
410
410
411 See :hg:`help templates.operators` for the list expansion syntax.
411 See :hg:`help templates.operators` for the list expansion syntax.
412
412
413 Returns 0 on success.
413 Returns 0 on success.
414 """
414 """
415 opts = pycompat.byteskwargs(opts)
415 opts = pycompat.byteskwargs(opts)
416 if not pats:
416 if not pats:
417 raise error.Abort(_(b'at least one filename or pattern is required'))
417 raise error.Abort(_(b'at least one filename or pattern is required'))
418
418
419 if opts.get(b'follow'):
419 if opts.get(b'follow'):
420 # --follow is deprecated and now just an alias for -f/--file
420 # --follow is deprecated and now just an alias for -f/--file
421 # to mimic the behavior of Mercurial before version 1.5
421 # to mimic the behavior of Mercurial before version 1.5
422 opts[b'file'] = True
422 opts[b'file'] = True
423
423
424 if (
424 if (
425 not opts.get(b'user')
425 not opts.get(b'user')
426 and not opts.get(b'changeset')
426 and not opts.get(b'changeset')
427 and not opts.get(b'date')
427 and not opts.get(b'date')
428 and not opts.get(b'file')
428 and not opts.get(b'file')
429 ):
429 ):
430 opts[b'number'] = True
430 opts[b'number'] = True
431
431
432 linenumber = opts.get(b'line_number') is not None
432 linenumber = opts.get(b'line_number') is not None
433 if (
433 if (
434 linenumber
434 linenumber
435 and (not opts.get(b'changeset'))
435 and (not opts.get(b'changeset'))
436 and (not opts.get(b'number'))
436 and (not opts.get(b'number'))
437 ):
437 ):
438 raise error.Abort(_(b'at least one of -n/-c is required for -l'))
438 raise error.Abort(_(b'at least one of -n/-c is required for -l'))
439
439
440 rev = opts.get(b'rev')
440 rev = opts.get(b'rev')
441 if rev:
441 if rev:
442 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
442 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
443 ctx = scmutil.revsingle(repo, rev)
443 ctx = scmutil.revsingle(repo, rev)
444
444
445 ui.pager(b'annotate')
445 ui.pager(b'annotate')
446 rootfm = ui.formatter(b'annotate', opts)
446 rootfm = ui.formatter(b'annotate', opts)
447 if ui.debugflag:
447 if ui.debugflag:
448 shorthex = pycompat.identity
448 shorthex = pycompat.identity
449 else:
449 else:
450
450
451 def shorthex(h):
451 def shorthex(h):
452 return h[:12]
452 return h[:12]
453
453
454 if ui.quiet:
454 if ui.quiet:
455 datefunc = dateutil.shortdate
455 datefunc = dateutil.shortdate
456 else:
456 else:
457 datefunc = dateutil.datestr
457 datefunc = dateutil.datestr
458 if ctx.rev() is None:
458 if ctx.rev() is None:
459 if opts.get(b'changeset'):
459 if opts.get(b'changeset'):
460 # omit "+" suffix which is appended to node hex
460 # omit "+" suffix which is appended to node hex
461 def formatrev(rev):
461 def formatrev(rev):
462 if rev == wdirrev:
462 if rev == wdirrev:
463 return b'%d' % ctx.p1().rev()
463 return b'%d' % ctx.p1().rev()
464 else:
464 else:
465 return b'%d' % rev
465 return b'%d' % rev
466
466
467 else:
467 else:
468
468
469 def formatrev(rev):
469 def formatrev(rev):
470 if rev == wdirrev:
470 if rev == wdirrev:
471 return b'%d+' % ctx.p1().rev()
471 return b'%d+' % ctx.p1().rev()
472 else:
472 else:
473 return b'%d ' % rev
473 return b'%d ' % rev
474
474
475 def formathex(h):
475 def formathex(h):
476 if h == wdirhex:
476 if h == wdirhex:
477 return b'%s+' % shorthex(hex(ctx.p1().node()))
477 return b'%s+' % shorthex(hex(ctx.p1().node()))
478 else:
478 else:
479 return b'%s ' % shorthex(h)
479 return b'%s ' % shorthex(h)
480
480
481 else:
481 else:
482 formatrev = b'%d'.__mod__
482 formatrev = b'%d'.__mod__
483 formathex = shorthex
483 formathex = shorthex
484
484
485 opmap = [
485 opmap = [
486 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
486 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
487 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
487 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
488 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
488 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
489 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
489 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
490 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
490 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
491 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
491 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
492 ]
492 ]
493 opnamemap = {
493 opnamemap = {
494 b'rev': b'number',
494 b'rev': b'number',
495 b'node': b'changeset',
495 b'node': b'changeset',
496 b'path': b'file',
496 b'path': b'file',
497 b'lineno': b'line_number',
497 b'lineno': b'line_number',
498 }
498 }
499
499
500 if rootfm.isplain():
500 if rootfm.isplain():
501
501
502 def makefunc(get, fmt):
502 def makefunc(get, fmt):
503 return lambda x: fmt(get(x))
503 return lambda x: fmt(get(x))
504
504
505 else:
505 else:
506
506
507 def makefunc(get, fmt):
507 def makefunc(get, fmt):
508 return get
508 return get
509
509
510 datahint = rootfm.datahint()
510 datahint = rootfm.datahint()
511 funcmap = [
511 funcmap = [
512 (makefunc(get, fmt), sep)
512 (makefunc(get, fmt), sep)
513 for fn, sep, get, fmt in opmap
513 for fn, sep, get, fmt in opmap
514 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
514 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
515 ]
515 ]
516 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
516 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
517 fields = b' '.join(
517 fields = b' '.join(
518 fn
518 fn
519 for fn, sep, get, fmt in opmap
519 for fn, sep, get, fmt in opmap
520 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
520 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
521 )
521 )
522
522
523 def bad(x, y):
523 def bad(x, y):
524 raise error.Abort(b"%s: %s" % (x, y))
524 raise error.Abort(b"%s: %s" % (x, y))
525
525
526 m = scmutil.match(ctx, pats, opts, badfn=bad)
526 m = scmutil.match(ctx, pats, opts, badfn=bad)
527
527
528 follow = not opts.get(b'no_follow')
528 follow = not opts.get(b'no_follow')
529 diffopts = patch.difffeatureopts(
529 diffopts = patch.difffeatureopts(
530 ui, opts, section=b'annotate', whitespace=True
530 ui, opts, section=b'annotate', whitespace=True
531 )
531 )
532 skiprevs = opts.get(b'skip')
532 skiprevs = opts.get(b'skip')
533 if skiprevs:
533 if skiprevs:
534 skiprevs = scmutil.revrange(repo, skiprevs)
534 skiprevs = scmutil.revrange(repo, skiprevs)
535
535
536 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
536 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
537 for abs in ctx.walk(m):
537 for abs in ctx.walk(m):
538 fctx = ctx[abs]
538 fctx = ctx[abs]
539 rootfm.startitem()
539 rootfm.startitem()
540 rootfm.data(path=abs)
540 rootfm.data(path=abs)
541 if not opts.get(b'text') and fctx.isbinary():
541 if not opts.get(b'text') and fctx.isbinary():
542 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
542 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
543 continue
543 continue
544
544
545 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
545 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
546 lines = fctx.annotate(
546 lines = fctx.annotate(
547 follow=follow, skiprevs=skiprevs, diffopts=diffopts
547 follow=follow, skiprevs=skiprevs, diffopts=diffopts
548 )
548 )
549 if not lines:
549 if not lines:
550 fm.end()
550 fm.end()
551 continue
551 continue
552 formats = []
552 formats = []
553 pieces = []
553 pieces = []
554
554
555 for f, sep in funcmap:
555 for f, sep in funcmap:
556 l = [f(n) for n in lines]
556 l = [f(n) for n in lines]
557 if fm.isplain():
557 if fm.isplain():
558 sizes = [encoding.colwidth(x) for x in l]
558 sizes = [encoding.colwidth(x) for x in l]
559 ml = max(sizes)
559 ml = max(sizes)
560 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
560 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
561 else:
561 else:
562 formats.append([b'%s'] * len(l))
562 formats.append([b'%s'] * len(l))
563 pieces.append(l)
563 pieces.append(l)
564
564
565 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
565 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
566 fm.startitem()
566 fm.startitem()
567 fm.context(fctx=n.fctx)
567 fm.context(fctx=n.fctx)
568 fm.write(fields, b"".join(f), *p)
568 fm.write(fields, b"".join(f), *p)
569 if n.skip:
569 if n.skip:
570 fmt = b"* %s"
570 fmt = b"* %s"
571 else:
571 else:
572 fmt = b": %s"
572 fmt = b": %s"
573 fm.write(b'line', fmt, n.text)
573 fm.write(b'line', fmt, n.text)
574
574
575 if not lines[-1].text.endswith(b'\n'):
575 if not lines[-1].text.endswith(b'\n'):
576 fm.plain(b'\n')
576 fm.plain(b'\n')
577 fm.end()
577 fm.end()
578
578
579 rootfm.end()
579 rootfm.end()
580
580
581
581
582 @command(
582 @command(
583 b'archive',
583 b'archive',
584 [
584 [
585 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
585 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
586 (
586 (
587 b'p',
587 b'p',
588 b'prefix',
588 b'prefix',
589 b'',
589 b'',
590 _(b'directory prefix for files in archive'),
590 _(b'directory prefix for files in archive'),
591 _(b'PREFIX'),
591 _(b'PREFIX'),
592 ),
592 ),
593 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
593 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
594 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
594 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
595 ]
595 ]
596 + subrepoopts
596 + subrepoopts
597 + walkopts,
597 + walkopts,
598 _(b'[OPTION]... DEST'),
598 _(b'[OPTION]... DEST'),
599 helpcategory=command.CATEGORY_IMPORT_EXPORT,
599 helpcategory=command.CATEGORY_IMPORT_EXPORT,
600 )
600 )
601 def archive(ui, repo, dest, **opts):
601 def archive(ui, repo, dest, **opts):
602 '''create an unversioned archive of a repository revision
602 '''create an unversioned archive of a repository revision
603
603
604 By default, the revision used is the parent of the working
604 By default, the revision used is the parent of the working
605 directory; use -r/--rev to specify a different revision.
605 directory; use -r/--rev to specify a different revision.
606
606
607 The archive type is automatically detected based on file
607 The archive type is automatically detected based on file
608 extension (to override, use -t/--type).
608 extension (to override, use -t/--type).
609
609
610 .. container:: verbose
610 .. container:: verbose
611
611
612 Examples:
612 Examples:
613
613
614 - create a zip file containing the 1.0 release::
614 - create a zip file containing the 1.0 release::
615
615
616 hg archive -r 1.0 project-1.0.zip
616 hg archive -r 1.0 project-1.0.zip
617
617
618 - create a tarball excluding .hg files::
618 - create a tarball excluding .hg files::
619
619
620 hg archive project.tar.gz -X ".hg*"
620 hg archive project.tar.gz -X ".hg*"
621
621
622 Valid types are:
622 Valid types are:
623
623
624 :``files``: a directory full of files (default)
624 :``files``: a directory full of files (default)
625 :``tar``: tar archive, uncompressed
625 :``tar``: tar archive, uncompressed
626 :``tbz2``: tar archive, compressed using bzip2
626 :``tbz2``: tar archive, compressed using bzip2
627 :``tgz``: tar archive, compressed using gzip
627 :``tgz``: tar archive, compressed using gzip
628 :``txz``: tar archive, compressed using lzma (only in Python 3)
628 :``txz``: tar archive, compressed using lzma (only in Python 3)
629 :``uzip``: zip archive, uncompressed
629 :``uzip``: zip archive, uncompressed
630 :``zip``: zip archive, compressed using deflate
630 :``zip``: zip archive, compressed using deflate
631
631
632 The exact name of the destination archive or directory is given
632 The exact name of the destination archive or directory is given
633 using a format string; see :hg:`help export` for details.
633 using a format string; see :hg:`help export` for details.
634
634
635 Each member added to an archive file has a directory prefix
635 Each member added to an archive file has a directory prefix
636 prepended. Use -p/--prefix to specify a format string for the
636 prepended. Use -p/--prefix to specify a format string for the
637 prefix. The default is the basename of the archive, with suffixes
637 prefix. The default is the basename of the archive, with suffixes
638 removed.
638 removed.
639
639
640 Returns 0 on success.
640 Returns 0 on success.
641 '''
641 '''
642
642
643 opts = pycompat.byteskwargs(opts)
643 opts = pycompat.byteskwargs(opts)
644 rev = opts.get(b'rev')
644 rev = opts.get(b'rev')
645 if rev:
645 if rev:
646 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
646 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
647 ctx = scmutil.revsingle(repo, rev)
647 ctx = scmutil.revsingle(repo, rev)
648 if not ctx:
648 if not ctx:
649 raise error.Abort(_(b'no working directory: please specify a revision'))
649 raise error.Abort(_(b'no working directory: please specify a revision'))
650 node = ctx.node()
650 node = ctx.node()
651 dest = cmdutil.makefilename(ctx, dest)
651 dest = cmdutil.makefilename(ctx, dest)
652 if os.path.realpath(dest) == repo.root:
652 if os.path.realpath(dest) == repo.root:
653 raise error.Abort(_(b'repository root cannot be destination'))
653 raise error.Abort(_(b'repository root cannot be destination'))
654
654
655 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
655 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
656 prefix = opts.get(b'prefix')
656 prefix = opts.get(b'prefix')
657
657
658 if dest == b'-':
658 if dest == b'-':
659 if kind == b'files':
659 if kind == b'files':
660 raise error.Abort(_(b'cannot archive plain files to stdout'))
660 raise error.Abort(_(b'cannot archive plain files to stdout'))
661 dest = cmdutil.makefileobj(ctx, dest)
661 dest = cmdutil.makefileobj(ctx, dest)
662 if not prefix:
662 if not prefix:
663 prefix = os.path.basename(repo.root) + b'-%h'
663 prefix = os.path.basename(repo.root) + b'-%h'
664
664
665 prefix = cmdutil.makefilename(ctx, prefix)
665 prefix = cmdutil.makefilename(ctx, prefix)
666 match = scmutil.match(ctx, [], opts)
666 match = scmutil.match(ctx, [], opts)
667 archival.archive(
667 archival.archive(
668 repo,
668 repo,
669 dest,
669 dest,
670 node,
670 node,
671 kind,
671 kind,
672 not opts.get(b'no_decode'),
672 not opts.get(b'no_decode'),
673 match,
673 match,
674 prefix,
674 prefix,
675 subrepos=opts.get(b'subrepos'),
675 subrepos=opts.get(b'subrepos'),
676 )
676 )
677
677
678
678
679 @command(
679 @command(
680 b'backout',
680 b'backout',
681 [
681 [
682 (
682 (
683 b'',
683 b'',
684 b'merge',
684 b'merge',
685 None,
685 None,
686 _(b'merge with old dirstate parent after backout'),
686 _(b'merge with old dirstate parent after backout'),
687 ),
687 ),
688 (
688 (
689 b'',
689 b'',
690 b'commit',
690 b'commit',
691 None,
691 None,
692 _(b'commit if no conflicts were encountered (DEPRECATED)'),
692 _(b'commit if no conflicts were encountered (DEPRECATED)'),
693 ),
693 ),
694 (b'', b'no-commit', None, _(b'do not commit')),
694 (b'', b'no-commit', None, _(b'do not commit')),
695 (
695 (
696 b'',
696 b'',
697 b'parent',
697 b'parent',
698 b'',
698 b'',
699 _(b'parent to choose when backing out merge (DEPRECATED)'),
699 _(b'parent to choose when backing out merge (DEPRECATED)'),
700 _(b'REV'),
700 _(b'REV'),
701 ),
701 ),
702 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
702 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
703 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
703 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
704 ]
704 ]
705 + mergetoolopts
705 + mergetoolopts
706 + walkopts
706 + walkopts
707 + commitopts
707 + commitopts
708 + commitopts2,
708 + commitopts2,
709 _(b'[OPTION]... [-r] REV'),
709 _(b'[OPTION]... [-r] REV'),
710 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
710 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
711 )
711 )
712 def backout(ui, repo, node=None, rev=None, **opts):
712 def backout(ui, repo, node=None, rev=None, **opts):
713 '''reverse effect of earlier changeset
713 '''reverse effect of earlier changeset
714
714
715 Prepare a new changeset with the effect of REV undone in the
715 Prepare a new changeset with the effect of REV undone in the
716 current working directory. If no conflicts were encountered,
716 current working directory. If no conflicts were encountered,
717 it will be committed immediately.
717 it will be committed immediately.
718
718
719 If REV is the parent of the working directory, then this new changeset
719 If REV is the parent of the working directory, then this new changeset
720 is committed automatically (unless --no-commit is specified).
720 is committed automatically (unless --no-commit is specified).
721
721
722 .. note::
722 .. note::
723
723
724 :hg:`backout` cannot be used to fix either an unwanted or
724 :hg:`backout` cannot be used to fix either an unwanted or
725 incorrect merge.
725 incorrect merge.
726
726
727 .. container:: verbose
727 .. container:: verbose
728
728
729 Examples:
729 Examples:
730
730
731 - Reverse the effect of the parent of the working directory.
731 - Reverse the effect of the parent of the working directory.
732 This backout will be committed immediately::
732 This backout will be committed immediately::
733
733
734 hg backout -r .
734 hg backout -r .
735
735
736 - Reverse the effect of previous bad revision 23::
736 - Reverse the effect of previous bad revision 23::
737
737
738 hg backout -r 23
738 hg backout -r 23
739
739
740 - Reverse the effect of previous bad revision 23 and
740 - Reverse the effect of previous bad revision 23 and
741 leave changes uncommitted::
741 leave changes uncommitted::
742
742
743 hg backout -r 23 --no-commit
743 hg backout -r 23 --no-commit
744 hg commit -m "Backout revision 23"
744 hg commit -m "Backout revision 23"
745
745
746 By default, the pending changeset will have one parent,
746 By default, the pending changeset will have one parent,
747 maintaining a linear history. With --merge, the pending
747 maintaining a linear history. With --merge, the pending
748 changeset will instead have two parents: the old parent of the
748 changeset will instead have two parents: the old parent of the
749 working directory and a new child of REV that simply undoes REV.
749 working directory and a new child of REV that simply undoes REV.
750
750
751 Before version 1.7, the behavior without --merge was equivalent
751 Before version 1.7, the behavior without --merge was equivalent
752 to specifying --merge followed by :hg:`update --clean .` to
752 to specifying --merge followed by :hg:`update --clean .` to
753 cancel the merge and leave the child of REV as a head to be
753 cancel the merge and leave the child of REV as a head to be
754 merged separately.
754 merged separately.
755
755
756 See :hg:`help dates` for a list of formats valid for -d/--date.
756 See :hg:`help dates` for a list of formats valid for -d/--date.
757
757
758 See :hg:`help revert` for a way to restore files to the state
758 See :hg:`help revert` for a way to restore files to the state
759 of another revision.
759 of another revision.
760
760
761 Returns 0 on success, 1 if nothing to backout or there are unresolved
761 Returns 0 on success, 1 if nothing to backout or there are unresolved
762 files.
762 files.
763 '''
763 '''
764 with repo.wlock(), repo.lock():
764 with repo.wlock(), repo.lock():
765 return _dobackout(ui, repo, node, rev, **opts)
765 return _dobackout(ui, repo, node, rev, **opts)
766
766
767
767
768 def _dobackout(ui, repo, node=None, rev=None, **opts):
768 def _dobackout(ui, repo, node=None, rev=None, **opts):
769 opts = pycompat.byteskwargs(opts)
769 opts = pycompat.byteskwargs(opts)
770 if opts.get(b'commit') and opts.get(b'no_commit'):
770 if opts.get(b'commit') and opts.get(b'no_commit'):
771 raise error.Abort(_(b"cannot use --commit with --no-commit"))
771 raise error.Abort(_(b"cannot use --commit with --no-commit"))
772 if opts.get(b'merge') and opts.get(b'no_commit'):
772 if opts.get(b'merge') and opts.get(b'no_commit'):
773 raise error.Abort(_(b"cannot use --merge with --no-commit"))
773 raise error.Abort(_(b"cannot use --merge with --no-commit"))
774
774
775 if rev and node:
775 if rev and node:
776 raise error.Abort(_(b"please specify just one revision"))
776 raise error.Abort(_(b"please specify just one revision"))
777
777
778 if not rev:
778 if not rev:
779 rev = node
779 rev = node
780
780
781 if not rev:
781 if not rev:
782 raise error.Abort(_(b"please specify a revision to backout"))
782 raise error.Abort(_(b"please specify a revision to backout"))
783
783
784 date = opts.get(b'date')
784 date = opts.get(b'date')
785 if date:
785 if date:
786 opts[b'date'] = dateutil.parsedate(date)
786 opts[b'date'] = dateutil.parsedate(date)
787
787
788 cmdutil.checkunfinished(repo)
788 cmdutil.checkunfinished(repo)
789 cmdutil.bailifchanged(repo)
789 cmdutil.bailifchanged(repo)
790 node = scmutil.revsingle(repo, rev).node()
790 node = scmutil.revsingle(repo, rev).node()
791
791
792 op1, op2 = repo.dirstate.parents()
792 op1, op2 = repo.dirstate.parents()
793 if not repo.changelog.isancestor(node, op1):
793 if not repo.changelog.isancestor(node, op1):
794 raise error.Abort(_(b'cannot backout change that is not an ancestor'))
794 raise error.Abort(_(b'cannot backout change that is not an ancestor'))
795
795
796 p1, p2 = repo.changelog.parents(node)
796 p1, p2 = repo.changelog.parents(node)
797 if p1 == nullid:
797 if p1 == nullid:
798 raise error.Abort(_(b'cannot backout a change with no parents'))
798 raise error.Abort(_(b'cannot backout a change with no parents'))
799 if p2 != nullid:
799 if p2 != nullid:
800 if not opts.get(b'parent'):
800 if not opts.get(b'parent'):
801 raise error.Abort(_(b'cannot backout a merge changeset'))
801 raise error.Abort(_(b'cannot backout a merge changeset'))
802 p = repo.lookup(opts[b'parent'])
802 p = repo.lookup(opts[b'parent'])
803 if p not in (p1, p2):
803 if p not in (p1, p2):
804 raise error.Abort(
804 raise error.Abort(
805 _(b'%s is not a parent of %s') % (short(p), short(node))
805 _(b'%s is not a parent of %s') % (short(p), short(node))
806 )
806 )
807 parent = p
807 parent = p
808 else:
808 else:
809 if opts.get(b'parent'):
809 if opts.get(b'parent'):
810 raise error.Abort(_(b'cannot use --parent on non-merge changeset'))
810 raise error.Abort(_(b'cannot use --parent on non-merge changeset'))
811 parent = p1
811 parent = p1
812
812
813 # the backout should appear on the same branch
813 # the backout should appear on the same branch
814 branch = repo.dirstate.branch()
814 branch = repo.dirstate.branch()
815 bheads = repo.branchheads(branch)
815 bheads = repo.branchheads(branch)
816 rctx = scmutil.revsingle(repo, hex(parent))
816 rctx = scmutil.revsingle(repo, hex(parent))
817 if not opts.get(b'merge') and op1 != node:
817 if not opts.get(b'merge') and op1 != node:
818 with dirstateguard.dirstateguard(repo, b'backout'):
818 with dirstateguard.dirstateguard(repo, b'backout'):
819 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
819 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
820 with ui.configoverride(overrides, b'backout'):
820 with ui.configoverride(overrides, b'backout'):
821 stats = mergemod.update(
821 stats = mergemod.update(
822 repo,
822 repo,
823 parent,
823 parent,
824 branchmerge=True,
824 branchmerge=True,
825 force=True,
825 force=True,
826 ancestor=node,
826 ancestor=node,
827 mergeancestor=False,
827 mergeancestor=False,
828 )
828 )
829 repo.setparents(op1, op2)
829 repo.setparents(op1, op2)
830 hg._showstats(repo, stats)
830 hg._showstats(repo, stats)
831 if stats.unresolvedcount:
831 if stats.unresolvedcount:
832 repo.ui.status(
832 repo.ui.status(
833 _(b"use 'hg resolve' to retry unresolved file merges\n")
833 _(b"use 'hg resolve' to retry unresolved file merges\n")
834 )
834 )
835 return 1
835 return 1
836 else:
836 else:
837 hg.clean(repo, node, show_stats=False)
837 hg.clean(repo, node, show_stats=False)
838 repo.dirstate.setbranch(branch)
838 repo.dirstate.setbranch(branch)
839 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
839 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
840
840
841 if opts.get(b'no_commit'):
841 if opts.get(b'no_commit'):
842 msg = _(b"changeset %s backed out, don't forget to commit.\n")
842 msg = _(b"changeset %s backed out, don't forget to commit.\n")
843 ui.status(msg % short(node))
843 ui.status(msg % short(node))
844 return 0
844 return 0
845
845
846 def commitfunc(ui, repo, message, match, opts):
846 def commitfunc(ui, repo, message, match, opts):
847 editform = b'backout'
847 editform = b'backout'
848 e = cmdutil.getcommiteditor(
848 e = cmdutil.getcommiteditor(
849 editform=editform, **pycompat.strkwargs(opts)
849 editform=editform, **pycompat.strkwargs(opts)
850 )
850 )
851 if not message:
851 if not message:
852 # we don't translate commit messages
852 # we don't translate commit messages
853 message = b"Backed out changeset %s" % short(node)
853 message = b"Backed out changeset %s" % short(node)
854 e = cmdutil.getcommiteditor(edit=True, editform=editform)
854 e = cmdutil.getcommiteditor(edit=True, editform=editform)
855 return repo.commit(
855 return repo.commit(
856 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
856 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
857 )
857 )
858
858
859 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
859 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
860 if not newnode:
860 if not newnode:
861 ui.status(_(b"nothing changed\n"))
861 ui.status(_(b"nothing changed\n"))
862 return 1
862 return 1
863 cmdutil.commitstatus(repo, newnode, branch, bheads)
863 cmdutil.commitstatus(repo, newnode, branch, bheads)
864
864
865 def nice(node):
865 def nice(node):
866 return b'%d:%s' % (repo.changelog.rev(node), short(node))
866 return b'%d:%s' % (repo.changelog.rev(node), short(node))
867
867
868 ui.status(
868 ui.status(
869 _(b'changeset %s backs out changeset %s\n')
869 _(b'changeset %s backs out changeset %s\n')
870 % (nice(repo.changelog.tip()), nice(node))
870 % (nice(repo.changelog.tip()), nice(node))
871 )
871 )
872 if opts.get(b'merge') and op1 != node:
872 if opts.get(b'merge') and op1 != node:
873 hg.clean(repo, op1, show_stats=False)
873 hg.clean(repo, op1, show_stats=False)
874 ui.status(
874 ui.status(
875 _(b'merging with changeset %s\n') % nice(repo.changelog.tip())
875 _(b'merging with changeset %s\n') % nice(repo.changelog.tip())
876 )
876 )
877 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
877 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
878 with ui.configoverride(overrides, b'backout'):
878 with ui.configoverride(overrides, b'backout'):
879 return hg.merge(repo, hex(repo.changelog.tip()))
879 return hg.merge(repo, hex(repo.changelog.tip()))
880 return 0
880 return 0
881
881
882
882
883 @command(
883 @command(
884 b'bisect',
884 b'bisect',
885 [
885 [
886 (b'r', b'reset', False, _(b'reset bisect state')),
886 (b'r', b'reset', False, _(b'reset bisect state')),
887 (b'g', b'good', False, _(b'mark changeset good')),
887 (b'g', b'good', False, _(b'mark changeset good')),
888 (b'b', b'bad', False, _(b'mark changeset bad')),
888 (b'b', b'bad', False, _(b'mark changeset bad')),
889 (b's', b'skip', False, _(b'skip testing changeset')),
889 (b's', b'skip', False, _(b'skip testing changeset')),
890 (b'e', b'extend', False, _(b'extend the bisect range')),
890 (b'e', b'extend', False, _(b'extend the bisect range')),
891 (
891 (
892 b'c',
892 b'c',
893 b'command',
893 b'command',
894 b'',
894 b'',
895 _(b'use command to check changeset state'),
895 _(b'use command to check changeset state'),
896 _(b'CMD'),
896 _(b'CMD'),
897 ),
897 ),
898 (b'U', b'noupdate', False, _(b'do not update to target')),
898 (b'U', b'noupdate', False, _(b'do not update to target')),
899 ],
899 ],
900 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
900 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
901 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
901 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
902 )
902 )
903 def bisect(
903 def bisect(
904 ui,
904 ui,
905 repo,
905 repo,
906 rev=None,
906 rev=None,
907 extra=None,
907 extra=None,
908 command=None,
908 command=None,
909 reset=None,
909 reset=None,
910 good=None,
910 good=None,
911 bad=None,
911 bad=None,
912 skip=None,
912 skip=None,
913 extend=None,
913 extend=None,
914 noupdate=None,
914 noupdate=None,
915 ):
915 ):
916 """subdivision search of changesets
916 """subdivision search of changesets
917
917
918 This command helps to find changesets which introduce problems. To
918 This command helps to find changesets which introduce problems. To
919 use, mark the earliest changeset you know exhibits the problem as
919 use, mark the earliest changeset you know exhibits the problem as
920 bad, then mark the latest changeset which is free from the problem
920 bad, then mark the latest changeset which is free from the problem
921 as good. Bisect will update your working directory to a revision
921 as good. Bisect will update your working directory to a revision
922 for testing (unless the -U/--noupdate option is specified). Once
922 for testing (unless the -U/--noupdate option is specified). Once
923 you have performed tests, mark the working directory as good or
923 you have performed tests, mark the working directory as good or
924 bad, and bisect will either update to another candidate changeset
924 bad, and bisect will either update to another candidate changeset
925 or announce that it has found the bad revision.
925 or announce that it has found the bad revision.
926
926
927 As a shortcut, you can also use the revision argument to mark a
927 As a shortcut, you can also use the revision argument to mark a
928 revision as good or bad without checking it out first.
928 revision as good or bad without checking it out first.
929
929
930 If you supply a command, it will be used for automatic bisection.
930 If you supply a command, it will be used for automatic bisection.
931 The environment variable HG_NODE will contain the ID of the
931 The environment variable HG_NODE will contain the ID of the
932 changeset being tested. The exit status of the command will be
932 changeset being tested. The exit status of the command will be
933 used to mark revisions as good or bad: status 0 means good, 125
933 used to mark revisions as good or bad: status 0 means good, 125
934 means to skip the revision, 127 (command not found) will abort the
934 means to skip the revision, 127 (command not found) will abort the
935 bisection, and any other non-zero exit status means the revision
935 bisection, and any other non-zero exit status means the revision
936 is bad.
936 is bad.
937
937
938 .. container:: verbose
938 .. container:: verbose
939
939
940 Some examples:
940 Some examples:
941
941
942 - start a bisection with known bad revision 34, and good revision 12::
942 - start a bisection with known bad revision 34, and good revision 12::
943
943
944 hg bisect --bad 34
944 hg bisect --bad 34
945 hg bisect --good 12
945 hg bisect --good 12
946
946
947 - advance the current bisection by marking current revision as good or
947 - advance the current bisection by marking current revision as good or
948 bad::
948 bad::
949
949
950 hg bisect --good
950 hg bisect --good
951 hg bisect --bad
951 hg bisect --bad
952
952
953 - mark the current revision, or a known revision, to be skipped (e.g. if
953 - mark the current revision, or a known revision, to be skipped (e.g. if
954 that revision is not usable because of another issue)::
954 that revision is not usable because of another issue)::
955
955
956 hg bisect --skip
956 hg bisect --skip
957 hg bisect --skip 23
957 hg bisect --skip 23
958
958
959 - skip all revisions that do not touch directories ``foo`` or ``bar``::
959 - skip all revisions that do not touch directories ``foo`` or ``bar``::
960
960
961 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
961 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
962
962
963 - forget the current bisection::
963 - forget the current bisection::
964
964
965 hg bisect --reset
965 hg bisect --reset
966
966
967 - use 'make && make tests' to automatically find the first broken
967 - use 'make && make tests' to automatically find the first broken
968 revision::
968 revision::
969
969
970 hg bisect --reset
970 hg bisect --reset
971 hg bisect --bad 34
971 hg bisect --bad 34
972 hg bisect --good 12
972 hg bisect --good 12
973 hg bisect --command "make && make tests"
973 hg bisect --command "make && make tests"
974
974
975 - see all changesets whose states are already known in the current
975 - see all changesets whose states are already known in the current
976 bisection::
976 bisection::
977
977
978 hg log -r "bisect(pruned)"
978 hg log -r "bisect(pruned)"
979
979
980 - see the changeset currently being bisected (especially useful
980 - see the changeset currently being bisected (especially useful
981 if running with -U/--noupdate)::
981 if running with -U/--noupdate)::
982
982
983 hg log -r "bisect(current)"
983 hg log -r "bisect(current)"
984
984
985 - see all changesets that took part in the current bisection::
985 - see all changesets that took part in the current bisection::
986
986
987 hg log -r "bisect(range)"
987 hg log -r "bisect(range)"
988
988
989 - you can even get a nice graph::
989 - you can even get a nice graph::
990
990
991 hg log --graph -r "bisect(range)"
991 hg log --graph -r "bisect(range)"
992
992
993 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
993 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
994
994
995 Returns 0 on success.
995 Returns 0 on success.
996 """
996 """
997 # backward compatibility
997 # backward compatibility
998 if rev in b"good bad reset init".split():
998 if rev in b"good bad reset init".split():
999 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
999 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
1000 cmd, rev, extra = rev, extra, None
1000 cmd, rev, extra = rev, extra, None
1001 if cmd == b"good":
1001 if cmd == b"good":
1002 good = True
1002 good = True
1003 elif cmd == b"bad":
1003 elif cmd == b"bad":
1004 bad = True
1004 bad = True
1005 else:
1005 else:
1006 reset = True
1006 reset = True
1007 elif extra:
1007 elif extra:
1008 raise error.Abort(_(b'incompatible arguments'))
1008 raise error.Abort(_(b'incompatible arguments'))
1009
1009
1010 incompatibles = {
1010 incompatibles = {
1011 b'--bad': bad,
1011 b'--bad': bad,
1012 b'--command': bool(command),
1012 b'--command': bool(command),
1013 b'--extend': extend,
1013 b'--extend': extend,
1014 b'--good': good,
1014 b'--good': good,
1015 b'--reset': reset,
1015 b'--reset': reset,
1016 b'--skip': skip,
1016 b'--skip': skip,
1017 }
1017 }
1018
1018
1019 enabled = [x for x in incompatibles if incompatibles[x]]
1019 enabled = [x for x in incompatibles if incompatibles[x]]
1020
1020
1021 if len(enabled) > 1:
1021 if len(enabled) > 1:
1022 raise error.Abort(
1022 raise error.Abort(
1023 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1023 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1024 )
1024 )
1025
1025
1026 if reset:
1026 if reset:
1027 hbisect.resetstate(repo)
1027 hbisect.resetstate(repo)
1028 return
1028 return
1029
1029
1030 state = hbisect.load_state(repo)
1030 state = hbisect.load_state(repo)
1031
1031
1032 # update state
1032 # update state
1033 if good or bad or skip:
1033 if good or bad or skip:
1034 if rev:
1034 if rev:
1035 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
1035 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
1036 else:
1036 else:
1037 nodes = [repo.lookup(b'.')]
1037 nodes = [repo.lookup(b'.')]
1038 if good:
1038 if good:
1039 state[b'good'] += nodes
1039 state[b'good'] += nodes
1040 elif bad:
1040 elif bad:
1041 state[b'bad'] += nodes
1041 state[b'bad'] += nodes
1042 elif skip:
1042 elif skip:
1043 state[b'skip'] += nodes
1043 state[b'skip'] += nodes
1044 hbisect.save_state(repo, state)
1044 hbisect.save_state(repo, state)
1045 if not (state[b'good'] and state[b'bad']):
1045 if not (state[b'good'] and state[b'bad']):
1046 return
1046 return
1047
1047
1048 def mayupdate(repo, node, show_stats=True):
1048 def mayupdate(repo, node, show_stats=True):
1049 """common used update sequence"""
1049 """common used update sequence"""
1050 if noupdate:
1050 if noupdate:
1051 return
1051 return
1052 cmdutil.checkunfinished(repo)
1052 cmdutil.checkunfinished(repo)
1053 cmdutil.bailifchanged(repo)
1053 cmdutil.bailifchanged(repo)
1054 return hg.clean(repo, node, show_stats=show_stats)
1054 return hg.clean(repo, node, show_stats=show_stats)
1055
1055
1056 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1056 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1057
1057
1058 if command:
1058 if command:
1059 changesets = 1
1059 changesets = 1
1060 if noupdate:
1060 if noupdate:
1061 try:
1061 try:
1062 node = state[b'current'][0]
1062 node = state[b'current'][0]
1063 except LookupError:
1063 except LookupError:
1064 raise error.Abort(
1064 raise error.Abort(
1065 _(
1065 _(
1066 b'current bisect revision is unknown - '
1066 b'current bisect revision is unknown - '
1067 b'start a new bisect to fix'
1067 b'start a new bisect to fix'
1068 )
1068 )
1069 )
1069 )
1070 else:
1070 else:
1071 node, p2 = repo.dirstate.parents()
1071 node, p2 = repo.dirstate.parents()
1072 if p2 != nullid:
1072 if p2 != nullid:
1073 raise error.Abort(_(b'current bisect revision is a merge'))
1073 raise error.Abort(_(b'current bisect revision is a merge'))
1074 if rev:
1074 if rev:
1075 node = repo[scmutil.revsingle(repo, rev, node)].node()
1075 node = repo[scmutil.revsingle(repo, rev, node)].node()
1076 with hbisect.restore_state(repo, state, node):
1076 with hbisect.restore_state(repo, state, node):
1077 while changesets:
1077 while changesets:
1078 # update state
1078 # update state
1079 state[b'current'] = [node]
1079 state[b'current'] = [node]
1080 hbisect.save_state(repo, state)
1080 hbisect.save_state(repo, state)
1081 status = ui.system(
1081 status = ui.system(
1082 command,
1082 command,
1083 environ={b'HG_NODE': hex(node)},
1083 environ={b'HG_NODE': hex(node)},
1084 blockedtag=b'bisect_check',
1084 blockedtag=b'bisect_check',
1085 )
1085 )
1086 if status == 125:
1086 if status == 125:
1087 transition = b"skip"
1087 transition = b"skip"
1088 elif status == 0:
1088 elif status == 0:
1089 transition = b"good"
1089 transition = b"good"
1090 # status < 0 means process was killed
1090 # status < 0 means process was killed
1091 elif status == 127:
1091 elif status == 127:
1092 raise error.Abort(_(b"failed to execute %s") % command)
1092 raise error.Abort(_(b"failed to execute %s") % command)
1093 elif status < 0:
1093 elif status < 0:
1094 raise error.Abort(_(b"%s killed") % command)
1094 raise error.Abort(_(b"%s killed") % command)
1095 else:
1095 else:
1096 transition = b"bad"
1096 transition = b"bad"
1097 state[transition].append(node)
1097 state[transition].append(node)
1098 ctx = repo[node]
1098 ctx = repo[node]
1099 ui.status(
1099 ui.status(
1100 _(b'changeset %d:%s: %s\n') % (ctx.rev(), ctx, transition)
1100 _(b'changeset %d:%s: %s\n') % (ctx.rev(), ctx, transition)
1101 )
1101 )
1102 hbisect.checkstate(state)
1102 hbisect.checkstate(state)
1103 # bisect
1103 # bisect
1104 nodes, changesets, bgood = hbisect.bisect(repo, state)
1104 nodes, changesets, bgood = hbisect.bisect(repo, state)
1105 # update to next check
1105 # update to next check
1106 node = nodes[0]
1106 node = nodes[0]
1107 mayupdate(repo, node, show_stats=False)
1107 mayupdate(repo, node, show_stats=False)
1108 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1108 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1109 return
1109 return
1110
1110
1111 hbisect.checkstate(state)
1111 hbisect.checkstate(state)
1112
1112
1113 # actually bisect
1113 # actually bisect
1114 nodes, changesets, good = hbisect.bisect(repo, state)
1114 nodes, changesets, good = hbisect.bisect(repo, state)
1115 if extend:
1115 if extend:
1116 if not changesets:
1116 if not changesets:
1117 extendnode = hbisect.extendrange(repo, state, nodes, good)
1117 extendnode = hbisect.extendrange(repo, state, nodes, good)
1118 if extendnode is not None:
1118 if extendnode is not None:
1119 ui.write(
1119 ui.write(
1120 _(b"Extending search to changeset %d:%s\n")
1120 _(b"Extending search to changeset %d:%s\n")
1121 % (extendnode.rev(), extendnode)
1121 % (extendnode.rev(), extendnode)
1122 )
1122 )
1123 state[b'current'] = [extendnode.node()]
1123 state[b'current'] = [extendnode.node()]
1124 hbisect.save_state(repo, state)
1124 hbisect.save_state(repo, state)
1125 return mayupdate(repo, extendnode.node())
1125 return mayupdate(repo, extendnode.node())
1126 raise error.Abort(_(b"nothing to extend"))
1126 raise error.Abort(_(b"nothing to extend"))
1127
1127
1128 if changesets == 0:
1128 if changesets == 0:
1129 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1129 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1130 else:
1130 else:
1131 assert len(nodes) == 1 # only a single node can be tested next
1131 assert len(nodes) == 1 # only a single node can be tested next
1132 node = nodes[0]
1132 node = nodes[0]
1133 # compute the approximate number of remaining tests
1133 # compute the approximate number of remaining tests
1134 tests, size = 0, 2
1134 tests, size = 0, 2
1135 while size <= changesets:
1135 while size <= changesets:
1136 tests, size = tests + 1, size * 2
1136 tests, size = tests + 1, size * 2
1137 rev = repo.changelog.rev(node)
1137 rev = repo.changelog.rev(node)
1138 ui.write(
1138 ui.write(
1139 _(
1139 _(
1140 b"Testing changeset %d:%s "
1140 b"Testing changeset %d:%s "
1141 b"(%d changesets remaining, ~%d tests)\n"
1141 b"(%d changesets remaining, ~%d tests)\n"
1142 )
1142 )
1143 % (rev, short(node), changesets, tests)
1143 % (rev, short(node), changesets, tests)
1144 )
1144 )
1145 state[b'current'] = [node]
1145 state[b'current'] = [node]
1146 hbisect.save_state(repo, state)
1146 hbisect.save_state(repo, state)
1147 return mayupdate(repo, node)
1147 return mayupdate(repo, node)
1148
1148
1149
1149
1150 @command(
1150 @command(
1151 b'bookmarks|bookmark',
1151 b'bookmarks|bookmark',
1152 [
1152 [
1153 (b'f', b'force', False, _(b'force')),
1153 (b'f', b'force', False, _(b'force')),
1154 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1154 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1155 (b'd', b'delete', False, _(b'delete a given bookmark')),
1155 (b'd', b'delete', False, _(b'delete a given bookmark')),
1156 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1156 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1157 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1157 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1158 (b'l', b'list', False, _(b'list existing bookmarks')),
1158 (b'l', b'list', False, _(b'list existing bookmarks')),
1159 ]
1159 ]
1160 + formatteropts,
1160 + formatteropts,
1161 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1161 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1162 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1162 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1163 )
1163 )
1164 def bookmark(ui, repo, *names, **opts):
1164 def bookmark(ui, repo, *names, **opts):
1165 '''create a new bookmark or list existing bookmarks
1165 '''create a new bookmark or list existing bookmarks
1166
1166
1167 Bookmarks are labels on changesets to help track lines of development.
1167 Bookmarks are labels on changesets to help track lines of development.
1168 Bookmarks are unversioned and can be moved, renamed and deleted.
1168 Bookmarks are unversioned and can be moved, renamed and deleted.
1169 Deleting or moving a bookmark has no effect on the associated changesets.
1169 Deleting or moving a bookmark has no effect on the associated changesets.
1170
1170
1171 Creating or updating to a bookmark causes it to be marked as 'active'.
1171 Creating or updating to a bookmark causes it to be marked as 'active'.
1172 The active bookmark is indicated with a '*'.
1172 The active bookmark is indicated with a '*'.
1173 When a commit is made, the active bookmark will advance to the new commit.
1173 When a commit is made, the active bookmark will advance to the new commit.
1174 A plain :hg:`update` will also advance an active bookmark, if possible.
1174 A plain :hg:`update` will also advance an active bookmark, if possible.
1175 Updating away from a bookmark will cause it to be deactivated.
1175 Updating away from a bookmark will cause it to be deactivated.
1176
1176
1177 Bookmarks can be pushed and pulled between repositories (see
1177 Bookmarks can be pushed and pulled between repositories (see
1178 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1178 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1179 diverged, a new 'divergent bookmark' of the form 'name@path' will
1179 diverged, a new 'divergent bookmark' of the form 'name@path' will
1180 be created. Using :hg:`merge` will resolve the divergence.
1180 be created. Using :hg:`merge` will resolve the divergence.
1181
1181
1182 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1182 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1183 the active bookmark's name.
1183 the active bookmark's name.
1184
1184
1185 A bookmark named '@' has the special property that :hg:`clone` will
1185 A bookmark named '@' has the special property that :hg:`clone` will
1186 check it out by default if it exists.
1186 check it out by default if it exists.
1187
1187
1188 .. container:: verbose
1188 .. container:: verbose
1189
1189
1190 Template:
1190 Template:
1191
1191
1192 The following keywords are supported in addition to the common template
1192 The following keywords are supported in addition to the common template
1193 keywords and functions such as ``{bookmark}``. See also
1193 keywords and functions such as ``{bookmark}``. See also
1194 :hg:`help templates`.
1194 :hg:`help templates`.
1195
1195
1196 :active: Boolean. True if the bookmark is active.
1196 :active: Boolean. True if the bookmark is active.
1197
1197
1198 Examples:
1198 Examples:
1199
1199
1200 - create an active bookmark for a new line of development::
1200 - create an active bookmark for a new line of development::
1201
1201
1202 hg book new-feature
1202 hg book new-feature
1203
1203
1204 - create an inactive bookmark as a place marker::
1204 - create an inactive bookmark as a place marker::
1205
1205
1206 hg book -i reviewed
1206 hg book -i reviewed
1207
1207
1208 - create an inactive bookmark on another changeset::
1208 - create an inactive bookmark on another changeset::
1209
1209
1210 hg book -r .^ tested
1210 hg book -r .^ tested
1211
1211
1212 - rename bookmark turkey to dinner::
1212 - rename bookmark turkey to dinner::
1213
1213
1214 hg book -m turkey dinner
1214 hg book -m turkey dinner
1215
1215
1216 - move the '@' bookmark from another branch::
1216 - move the '@' bookmark from another branch::
1217
1217
1218 hg book -f @
1218 hg book -f @
1219
1219
1220 - print only the active bookmark name::
1220 - print only the active bookmark name::
1221
1221
1222 hg book -ql .
1222 hg book -ql .
1223 '''
1223 '''
1224 opts = pycompat.byteskwargs(opts)
1224 opts = pycompat.byteskwargs(opts)
1225 force = opts.get(b'force')
1225 force = opts.get(b'force')
1226 rev = opts.get(b'rev')
1226 rev = opts.get(b'rev')
1227 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1227 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1228
1228
1229 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1229 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1230 if action:
1230 if action:
1231 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1231 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1232 elif names or rev:
1232 elif names or rev:
1233 action = b'add'
1233 action = b'add'
1234 elif inactive:
1234 elif inactive:
1235 action = b'inactive' # meaning deactivate
1235 action = b'inactive' # meaning deactivate
1236 else:
1236 else:
1237 action = b'list'
1237 action = b'list'
1238
1238
1239 cmdutil.check_incompatible_arguments(
1239 cmdutil.check_incompatible_arguments(
1240 opts, b'inactive', [b'delete', b'list']
1240 opts, b'inactive', [b'delete', b'list']
1241 )
1241 )
1242 if not names and action in {b'add', b'delete'}:
1242 if not names and action in {b'add', b'delete'}:
1243 raise error.Abort(_(b"bookmark name required"))
1243 raise error.Abort(_(b"bookmark name required"))
1244
1244
1245 if action in {b'add', b'delete', b'rename', b'inactive'}:
1245 if action in {b'add', b'delete', b'rename', b'inactive'}:
1246 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1246 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1247 if action == b'delete':
1247 if action == b'delete':
1248 names = pycompat.maplist(repo._bookmarks.expandname, names)
1248 names = pycompat.maplist(repo._bookmarks.expandname, names)
1249 bookmarks.delete(repo, tr, names)
1249 bookmarks.delete(repo, tr, names)
1250 elif action == b'rename':
1250 elif action == b'rename':
1251 if not names:
1251 if not names:
1252 raise error.Abort(_(b"new bookmark name required"))
1252 raise error.Abort(_(b"new bookmark name required"))
1253 elif len(names) > 1:
1253 elif len(names) > 1:
1254 raise error.Abort(_(b"only one new bookmark name allowed"))
1254 raise error.Abort(_(b"only one new bookmark name allowed"))
1255 oldname = repo._bookmarks.expandname(opts[b'rename'])
1255 oldname = repo._bookmarks.expandname(opts[b'rename'])
1256 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1256 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1257 elif action == b'add':
1257 elif action == b'add':
1258 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1258 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1259 elif action == b'inactive':
1259 elif action == b'inactive':
1260 if len(repo._bookmarks) == 0:
1260 if len(repo._bookmarks) == 0:
1261 ui.status(_(b"no bookmarks set\n"))
1261 ui.status(_(b"no bookmarks set\n"))
1262 elif not repo._activebookmark:
1262 elif not repo._activebookmark:
1263 ui.status(_(b"no active bookmark\n"))
1263 ui.status(_(b"no active bookmark\n"))
1264 else:
1264 else:
1265 bookmarks.deactivate(repo)
1265 bookmarks.deactivate(repo)
1266 elif action == b'list':
1266 elif action == b'list':
1267 names = pycompat.maplist(repo._bookmarks.expandname, names)
1267 names = pycompat.maplist(repo._bookmarks.expandname, names)
1268 with ui.formatter(b'bookmarks', opts) as fm:
1268 with ui.formatter(b'bookmarks', opts) as fm:
1269 bookmarks.printbookmarks(ui, repo, fm, names)
1269 bookmarks.printbookmarks(ui, repo, fm, names)
1270 else:
1270 else:
1271 raise error.ProgrammingError(b'invalid action: %s' % action)
1271 raise error.ProgrammingError(b'invalid action: %s' % action)
1272
1272
1273
1273
1274 @command(
1274 @command(
1275 b'branch',
1275 b'branch',
1276 [
1276 [
1277 (
1277 (
1278 b'f',
1278 b'f',
1279 b'force',
1279 b'force',
1280 None,
1280 None,
1281 _(b'set branch name even if it shadows an existing branch'),
1281 _(b'set branch name even if it shadows an existing branch'),
1282 ),
1282 ),
1283 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1283 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1284 (
1284 (
1285 b'r',
1285 b'r',
1286 b'rev',
1286 b'rev',
1287 [],
1287 [],
1288 _(b'change branches of the given revs (EXPERIMENTAL)'),
1288 _(b'change branches of the given revs (EXPERIMENTAL)'),
1289 ),
1289 ),
1290 ],
1290 ],
1291 _(b'[-fC] [NAME]'),
1291 _(b'[-fC] [NAME]'),
1292 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1292 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1293 )
1293 )
1294 def branch(ui, repo, label=None, **opts):
1294 def branch(ui, repo, label=None, **opts):
1295 """set or show the current branch name
1295 """set or show the current branch name
1296
1296
1297 .. note::
1297 .. note::
1298
1298
1299 Branch names are permanent and global. Use :hg:`bookmark` to create a
1299 Branch names are permanent and global. Use :hg:`bookmark` to create a
1300 light-weight bookmark instead. See :hg:`help glossary` for more
1300 light-weight bookmark instead. See :hg:`help glossary` for more
1301 information about named branches and bookmarks.
1301 information about named branches and bookmarks.
1302
1302
1303 With no argument, show the current branch name. With one argument,
1303 With no argument, show the current branch name. With one argument,
1304 set the working directory branch name (the branch will not exist
1304 set the working directory branch name (the branch will not exist
1305 in the repository until the next commit). Standard practice
1305 in the repository until the next commit). Standard practice
1306 recommends that primary development take place on the 'default'
1306 recommends that primary development take place on the 'default'
1307 branch.
1307 branch.
1308
1308
1309 Unless -f/--force is specified, branch will not let you set a
1309 Unless -f/--force is specified, branch will not let you set a
1310 branch name that already exists.
1310 branch name that already exists.
1311
1311
1312 Use -C/--clean to reset the working directory branch to that of
1312 Use -C/--clean to reset the working directory branch to that of
1313 the parent of the working directory, negating a previous branch
1313 the parent of the working directory, negating a previous branch
1314 change.
1314 change.
1315
1315
1316 Use the command :hg:`update` to switch to an existing branch. Use
1316 Use the command :hg:`update` to switch to an existing branch. Use
1317 :hg:`commit --close-branch` to mark this branch head as closed.
1317 :hg:`commit --close-branch` to mark this branch head as closed.
1318 When all heads of a branch are closed, the branch will be
1318 When all heads of a branch are closed, the branch will be
1319 considered closed.
1319 considered closed.
1320
1320
1321 Returns 0 on success.
1321 Returns 0 on success.
1322 """
1322 """
1323 opts = pycompat.byteskwargs(opts)
1323 opts = pycompat.byteskwargs(opts)
1324 revs = opts.get(b'rev')
1324 revs = opts.get(b'rev')
1325 if label:
1325 if label:
1326 label = label.strip()
1326 label = label.strip()
1327
1327
1328 if not opts.get(b'clean') and not label:
1328 if not opts.get(b'clean') and not label:
1329 if revs:
1329 if revs:
1330 raise error.Abort(_(b"no branch name specified for the revisions"))
1330 raise error.Abort(_(b"no branch name specified for the revisions"))
1331 ui.write(b"%s\n" % repo.dirstate.branch())
1331 ui.write(b"%s\n" % repo.dirstate.branch())
1332 return
1332 return
1333
1333
1334 with repo.wlock():
1334 with repo.wlock():
1335 if opts.get(b'clean'):
1335 if opts.get(b'clean'):
1336 label = repo[b'.'].branch()
1336 label = repo[b'.'].branch()
1337 repo.dirstate.setbranch(label)
1337 repo.dirstate.setbranch(label)
1338 ui.status(_(b'reset working directory to branch %s\n') % label)
1338 ui.status(_(b'reset working directory to branch %s\n') % label)
1339 elif label:
1339 elif label:
1340
1340
1341 scmutil.checknewlabel(repo, label, b'branch')
1341 scmutil.checknewlabel(repo, label, b'branch')
1342 if revs:
1342 if revs:
1343 return cmdutil.changebranch(ui, repo, revs, label)
1343 return cmdutil.changebranch(ui, repo, revs, label)
1344
1344
1345 if not opts.get(b'force') and label in repo.branchmap():
1345 if not opts.get(b'force') and label in repo.branchmap():
1346 if label not in [p.branch() for p in repo[None].parents()]:
1346 if label not in [p.branch() for p in repo[None].parents()]:
1347 raise error.Abort(
1347 raise error.Abort(
1348 _(b'a branch of the same name already exists'),
1348 _(b'a branch of the same name already exists'),
1349 # i18n: "it" refers to an existing branch
1349 # i18n: "it" refers to an existing branch
1350 hint=_(b"use 'hg update' to switch to it"),
1350 hint=_(b"use 'hg update' to switch to it"),
1351 )
1351 )
1352
1352
1353 repo.dirstate.setbranch(label)
1353 repo.dirstate.setbranch(label)
1354 ui.status(_(b'marked working directory as branch %s\n') % label)
1354 ui.status(_(b'marked working directory as branch %s\n') % label)
1355
1355
1356 # find any open named branches aside from default
1356 # find any open named branches aside from default
1357 for n, h, t, c in repo.branchmap().iterbranches():
1357 for n, h, t, c in repo.branchmap().iterbranches():
1358 if n != b"default" and not c:
1358 if n != b"default" and not c:
1359 return 0
1359 return 0
1360 ui.status(
1360 ui.status(
1361 _(
1361 _(
1362 b'(branches are permanent and global, '
1362 b'(branches are permanent and global, '
1363 b'did you want a bookmark?)\n'
1363 b'did you want a bookmark?)\n'
1364 )
1364 )
1365 )
1365 )
1366
1366
1367
1367
1368 @command(
1368 @command(
1369 b'branches',
1369 b'branches',
1370 [
1370 [
1371 (
1371 (
1372 b'a',
1372 b'a',
1373 b'active',
1373 b'active',
1374 False,
1374 False,
1375 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1375 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1376 ),
1376 ),
1377 (b'c', b'closed', False, _(b'show normal and closed branches')),
1377 (b'c', b'closed', False, _(b'show normal and closed branches')),
1378 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1378 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1379 ]
1379 ]
1380 + formatteropts,
1380 + formatteropts,
1381 _(b'[-c]'),
1381 _(b'[-c]'),
1382 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1382 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1383 intents={INTENT_READONLY},
1383 intents={INTENT_READONLY},
1384 )
1384 )
1385 def branches(ui, repo, active=False, closed=False, **opts):
1385 def branches(ui, repo, active=False, closed=False, **opts):
1386 """list repository named branches
1386 """list repository named branches
1387
1387
1388 List the repository's named branches, indicating which ones are
1388 List the repository's named branches, indicating which ones are
1389 inactive. If -c/--closed is specified, also list branches which have
1389 inactive. If -c/--closed is specified, also list branches which have
1390 been marked closed (see :hg:`commit --close-branch`).
1390 been marked closed (see :hg:`commit --close-branch`).
1391
1391
1392 Use the command :hg:`update` to switch to an existing branch.
1392 Use the command :hg:`update` to switch to an existing branch.
1393
1393
1394 .. container:: verbose
1394 .. container:: verbose
1395
1395
1396 Template:
1396 Template:
1397
1397
1398 The following keywords are supported in addition to the common template
1398 The following keywords are supported in addition to the common template
1399 keywords and functions such as ``{branch}``. See also
1399 keywords and functions such as ``{branch}``. See also
1400 :hg:`help templates`.
1400 :hg:`help templates`.
1401
1401
1402 :active: Boolean. True if the branch is active.
1402 :active: Boolean. True if the branch is active.
1403 :closed: Boolean. True if the branch is closed.
1403 :closed: Boolean. True if the branch is closed.
1404 :current: Boolean. True if it is the current branch.
1404 :current: Boolean. True if it is the current branch.
1405
1405
1406 Returns 0.
1406 Returns 0.
1407 """
1407 """
1408
1408
1409 opts = pycompat.byteskwargs(opts)
1409 opts = pycompat.byteskwargs(opts)
1410 revs = opts.get(b'rev')
1410 revs = opts.get(b'rev')
1411 selectedbranches = None
1411 selectedbranches = None
1412 if revs:
1412 if revs:
1413 revs = scmutil.revrange(repo, revs)
1413 revs = scmutil.revrange(repo, revs)
1414 getbi = repo.revbranchcache().branchinfo
1414 getbi = repo.revbranchcache().branchinfo
1415 selectedbranches = {getbi(r)[0] for r in revs}
1415 selectedbranches = {getbi(r)[0] for r in revs}
1416
1416
1417 ui.pager(b'branches')
1417 ui.pager(b'branches')
1418 fm = ui.formatter(b'branches', opts)
1418 fm = ui.formatter(b'branches', opts)
1419 hexfunc = fm.hexfunc
1419 hexfunc = fm.hexfunc
1420
1420
1421 allheads = set(repo.heads())
1421 allheads = set(repo.heads())
1422 branches = []
1422 branches = []
1423 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1423 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1424 if selectedbranches is not None and tag not in selectedbranches:
1424 if selectedbranches is not None and tag not in selectedbranches:
1425 continue
1425 continue
1426 isactive = False
1426 isactive = False
1427 if not isclosed:
1427 if not isclosed:
1428 openheads = set(repo.branchmap().iteropen(heads))
1428 openheads = set(repo.branchmap().iteropen(heads))
1429 isactive = bool(openheads & allheads)
1429 isactive = bool(openheads & allheads)
1430 branches.append((tag, repo[tip], isactive, not isclosed))
1430 branches.append((tag, repo[tip], isactive, not isclosed))
1431 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1431 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1432
1432
1433 for tag, ctx, isactive, isopen in branches:
1433 for tag, ctx, isactive, isopen in branches:
1434 if active and not isactive:
1434 if active and not isactive:
1435 continue
1435 continue
1436 if isactive:
1436 if isactive:
1437 label = b'branches.active'
1437 label = b'branches.active'
1438 notice = b''
1438 notice = b''
1439 elif not isopen:
1439 elif not isopen:
1440 if not closed:
1440 if not closed:
1441 continue
1441 continue
1442 label = b'branches.closed'
1442 label = b'branches.closed'
1443 notice = _(b' (closed)')
1443 notice = _(b' (closed)')
1444 else:
1444 else:
1445 label = b'branches.inactive'
1445 label = b'branches.inactive'
1446 notice = _(b' (inactive)')
1446 notice = _(b' (inactive)')
1447 current = tag == repo.dirstate.branch()
1447 current = tag == repo.dirstate.branch()
1448 if current:
1448 if current:
1449 label = b'branches.current'
1449 label = b'branches.current'
1450
1450
1451 fm.startitem()
1451 fm.startitem()
1452 fm.write(b'branch', b'%s', tag, label=label)
1452 fm.write(b'branch', b'%s', tag, label=label)
1453 rev = ctx.rev()
1453 rev = ctx.rev()
1454 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1454 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1455 fmt = b' ' * padsize + b' %d:%s'
1455 fmt = b' ' * padsize + b' %d:%s'
1456 fm.condwrite(
1456 fm.condwrite(
1457 not ui.quiet,
1457 not ui.quiet,
1458 b'rev node',
1458 b'rev node',
1459 fmt,
1459 fmt,
1460 rev,
1460 rev,
1461 hexfunc(ctx.node()),
1461 hexfunc(ctx.node()),
1462 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1462 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1463 )
1463 )
1464 fm.context(ctx=ctx)
1464 fm.context(ctx=ctx)
1465 fm.data(active=isactive, closed=not isopen, current=current)
1465 fm.data(active=isactive, closed=not isopen, current=current)
1466 if not ui.quiet:
1466 if not ui.quiet:
1467 fm.plain(notice)
1467 fm.plain(notice)
1468 fm.plain(b'\n')
1468 fm.plain(b'\n')
1469 fm.end()
1469 fm.end()
1470
1470
1471
1471
1472 @command(
1472 @command(
1473 b'bundle',
1473 b'bundle',
1474 [
1474 [
1475 (
1475 (
1476 b'f',
1476 b'f',
1477 b'force',
1477 b'force',
1478 None,
1478 None,
1479 _(b'run even when the destination is unrelated'),
1479 _(b'run even when the destination is unrelated'),
1480 ),
1480 ),
1481 (
1481 (
1482 b'r',
1482 b'r',
1483 b'rev',
1483 b'rev',
1484 [],
1484 [],
1485 _(b'a changeset intended to be added to the destination'),
1485 _(b'a changeset intended to be added to the destination'),
1486 _(b'REV'),
1486 _(b'REV'),
1487 ),
1487 ),
1488 (
1488 (
1489 b'b',
1489 b'b',
1490 b'branch',
1490 b'branch',
1491 [],
1491 [],
1492 _(b'a specific branch you would like to bundle'),
1492 _(b'a specific branch you would like to bundle'),
1493 _(b'BRANCH'),
1493 _(b'BRANCH'),
1494 ),
1494 ),
1495 (
1495 (
1496 b'',
1496 b'',
1497 b'base',
1497 b'base',
1498 [],
1498 [],
1499 _(b'a base changeset assumed to be available at the destination'),
1499 _(b'a base changeset assumed to be available at the destination'),
1500 _(b'REV'),
1500 _(b'REV'),
1501 ),
1501 ),
1502 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1502 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1503 (
1503 (
1504 b't',
1504 b't',
1505 b'type',
1505 b'type',
1506 b'bzip2',
1506 b'bzip2',
1507 _(b'bundle compression type to use'),
1507 _(b'bundle compression type to use'),
1508 _(b'TYPE'),
1508 _(b'TYPE'),
1509 ),
1509 ),
1510 ]
1510 ]
1511 + remoteopts,
1511 + remoteopts,
1512 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1512 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1513 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1513 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1514 )
1514 )
1515 def bundle(ui, repo, fname, dest=None, **opts):
1515 def bundle(ui, repo, fname, dest=None, **opts):
1516 """create a bundle file
1516 """create a bundle file
1517
1517
1518 Generate a bundle file containing data to be transferred to another
1518 Generate a bundle file containing data to be transferred to another
1519 repository.
1519 repository.
1520
1520
1521 To create a bundle containing all changesets, use -a/--all
1521 To create a bundle containing all changesets, use -a/--all
1522 (or --base null). Otherwise, hg assumes the destination will have
1522 (or --base null). Otherwise, hg assumes the destination will have
1523 all the nodes you specify with --base parameters. Otherwise, hg
1523 all the nodes you specify with --base parameters. Otherwise, hg
1524 will assume the repository has all the nodes in destination, or
1524 will assume the repository has all the nodes in destination, or
1525 default-push/default if no destination is specified, where destination
1525 default-push/default if no destination is specified, where destination
1526 is the repository you provide through DEST option.
1526 is the repository you provide through DEST option.
1527
1527
1528 You can change bundle format with the -t/--type option. See
1528 You can change bundle format with the -t/--type option. See
1529 :hg:`help bundlespec` for documentation on this format. By default,
1529 :hg:`help bundlespec` for documentation on this format. By default,
1530 the most appropriate format is used and compression defaults to
1530 the most appropriate format is used and compression defaults to
1531 bzip2.
1531 bzip2.
1532
1532
1533 The bundle file can then be transferred using conventional means
1533 The bundle file can then be transferred using conventional means
1534 and applied to another repository with the unbundle or pull
1534 and applied to another repository with the unbundle or pull
1535 command. This is useful when direct push and pull are not
1535 command. This is useful when direct push and pull are not
1536 available or when exporting an entire repository is undesirable.
1536 available or when exporting an entire repository is undesirable.
1537
1537
1538 Applying bundles preserves all changeset contents including
1538 Applying bundles preserves all changeset contents including
1539 permissions, copy/rename information, and revision history.
1539 permissions, copy/rename information, and revision history.
1540
1540
1541 Returns 0 on success, 1 if no changes found.
1541 Returns 0 on success, 1 if no changes found.
1542 """
1542 """
1543 opts = pycompat.byteskwargs(opts)
1543 opts = pycompat.byteskwargs(opts)
1544 revs = None
1544 revs = None
1545 if b'rev' in opts:
1545 if b'rev' in opts:
1546 revstrings = opts[b'rev']
1546 revstrings = opts[b'rev']
1547 revs = scmutil.revrange(repo, revstrings)
1547 revs = scmutil.revrange(repo, revstrings)
1548 if revstrings and not revs:
1548 if revstrings and not revs:
1549 raise error.Abort(_(b'no commits to bundle'))
1549 raise error.Abort(_(b'no commits to bundle'))
1550
1550
1551 bundletype = opts.get(b'type', b'bzip2').lower()
1551 bundletype = opts.get(b'type', b'bzip2').lower()
1552 try:
1552 try:
1553 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1553 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1554 except error.UnsupportedBundleSpecification as e:
1554 except error.UnsupportedBundleSpecification as e:
1555 raise error.Abort(
1555 raise error.Abort(
1556 pycompat.bytestr(e),
1556 pycompat.bytestr(e),
1557 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1557 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1558 )
1558 )
1559 cgversion = bundlespec.contentopts[b"cg.version"]
1559 cgversion = bundlespec.contentopts[b"cg.version"]
1560
1560
1561 # Packed bundles are a pseudo bundle format for now.
1561 # Packed bundles are a pseudo bundle format for now.
1562 if cgversion == b's1':
1562 if cgversion == b's1':
1563 raise error.Abort(
1563 raise error.Abort(
1564 _(b'packed bundles cannot be produced by "hg bundle"'),
1564 _(b'packed bundles cannot be produced by "hg bundle"'),
1565 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1565 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1566 )
1566 )
1567
1567
1568 if opts.get(b'all'):
1568 if opts.get(b'all'):
1569 if dest:
1569 if dest:
1570 raise error.Abort(
1570 raise error.Abort(
1571 _(b"--all is incompatible with specifying a destination")
1571 _(b"--all is incompatible with specifying a destination")
1572 )
1572 )
1573 if opts.get(b'base'):
1573 if opts.get(b'base'):
1574 ui.warn(_(b"ignoring --base because --all was specified\n"))
1574 ui.warn(_(b"ignoring --base because --all was specified\n"))
1575 base = [nullrev]
1575 base = [nullrev]
1576 else:
1576 else:
1577 base = scmutil.revrange(repo, opts.get(b'base'))
1577 base = scmutil.revrange(repo, opts.get(b'base'))
1578 if cgversion not in changegroup.supportedoutgoingversions(repo):
1578 if cgversion not in changegroup.supportedoutgoingversions(repo):
1579 raise error.Abort(
1579 raise error.Abort(
1580 _(b"repository does not support bundle version %s") % cgversion
1580 _(b"repository does not support bundle version %s") % cgversion
1581 )
1581 )
1582
1582
1583 if base:
1583 if base:
1584 if dest:
1584 if dest:
1585 raise error.Abort(
1585 raise error.Abort(
1586 _(b"--base is incompatible with specifying a destination")
1586 _(b"--base is incompatible with specifying a destination")
1587 )
1587 )
1588 common = [repo[rev].node() for rev in base]
1588 common = [repo[rev].node() for rev in base]
1589 heads = [repo[r].node() for r in revs] if revs else None
1589 heads = [repo[r].node() for r in revs] if revs else None
1590 outgoing = discovery.outgoing(repo, common, heads)
1590 outgoing = discovery.outgoing(repo, common, heads)
1591 else:
1591 else:
1592 dest = ui.expandpath(dest or b'default-push', dest or b'default')
1592 dest = ui.expandpath(dest or b'default-push', dest or b'default')
1593 dest, branches = hg.parseurl(dest, opts.get(b'branch'))
1593 dest, branches = hg.parseurl(dest, opts.get(b'branch'))
1594 other = hg.peer(repo, opts, dest)
1594 other = hg.peer(repo, opts, dest)
1595 revs = [repo[r].hex() for r in revs]
1595 revs = [repo[r].hex() for r in revs]
1596 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1596 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1597 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1597 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1598 outgoing = discovery.findcommonoutgoing(
1598 outgoing = discovery.findcommonoutgoing(
1599 repo,
1599 repo,
1600 other,
1600 other,
1601 onlyheads=heads,
1601 onlyheads=heads,
1602 force=opts.get(b'force'),
1602 force=opts.get(b'force'),
1603 portable=True,
1603 portable=True,
1604 )
1604 )
1605
1605
1606 if not outgoing.missing:
1606 if not outgoing.missing:
1607 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1607 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1608 return 1
1608 return 1
1609
1609
1610 if cgversion == b'01': # bundle1
1610 if cgversion == b'01': # bundle1
1611 bversion = b'HG10' + bundlespec.wirecompression
1611 bversion = b'HG10' + bundlespec.wirecompression
1612 bcompression = None
1612 bcompression = None
1613 elif cgversion in (b'02', b'03'):
1613 elif cgversion in (b'02', b'03'):
1614 bversion = b'HG20'
1614 bversion = b'HG20'
1615 bcompression = bundlespec.wirecompression
1615 bcompression = bundlespec.wirecompression
1616 else:
1616 else:
1617 raise error.ProgrammingError(
1617 raise error.ProgrammingError(
1618 b'bundle: unexpected changegroup version %s' % cgversion
1618 b'bundle: unexpected changegroup version %s' % cgversion
1619 )
1619 )
1620
1620
1621 # TODO compression options should be derived from bundlespec parsing.
1621 # TODO compression options should be derived from bundlespec parsing.
1622 # This is a temporary hack to allow adjusting bundle compression
1622 # This is a temporary hack to allow adjusting bundle compression
1623 # level without a) formalizing the bundlespec changes to declare it
1623 # level without a) formalizing the bundlespec changes to declare it
1624 # b) introducing a command flag.
1624 # b) introducing a command flag.
1625 compopts = {}
1625 compopts = {}
1626 complevel = ui.configint(
1626 complevel = ui.configint(
1627 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1627 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1628 )
1628 )
1629 if complevel is None:
1629 if complevel is None:
1630 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1630 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1631 if complevel is not None:
1631 if complevel is not None:
1632 compopts[b'level'] = complevel
1632 compopts[b'level'] = complevel
1633
1633
1634 # Allow overriding the bundling of obsmarker in phases through
1634 # Allow overriding the bundling of obsmarker in phases through
1635 # configuration while we don't have a bundle version that include them
1635 # configuration while we don't have a bundle version that include them
1636 if repo.ui.configbool(b'experimental', b'evolution.bundle-obsmarker'):
1636 if repo.ui.configbool(b'experimental', b'evolution.bundle-obsmarker'):
1637 bundlespec.contentopts[b'obsolescence'] = True
1637 bundlespec.contentopts[b'obsolescence'] = True
1638 if repo.ui.configbool(b'experimental', b'bundle-phases'):
1638 if repo.ui.configbool(b'experimental', b'bundle-phases'):
1639 bundlespec.contentopts[b'phases'] = True
1639 bundlespec.contentopts[b'phases'] = True
1640
1640
1641 bundle2.writenewbundle(
1641 bundle2.writenewbundle(
1642 ui,
1642 ui,
1643 repo,
1643 repo,
1644 b'bundle',
1644 b'bundle',
1645 fname,
1645 fname,
1646 bversion,
1646 bversion,
1647 outgoing,
1647 outgoing,
1648 bundlespec.contentopts,
1648 bundlespec.contentopts,
1649 compression=bcompression,
1649 compression=bcompression,
1650 compopts=compopts,
1650 compopts=compopts,
1651 )
1651 )
1652
1652
1653
1653
1654 @command(
1654 @command(
1655 b'cat',
1655 b'cat',
1656 [
1656 [
1657 (
1657 (
1658 b'o',
1658 b'o',
1659 b'output',
1659 b'output',
1660 b'',
1660 b'',
1661 _(b'print output to file with formatted name'),
1661 _(b'print output to file with formatted name'),
1662 _(b'FORMAT'),
1662 _(b'FORMAT'),
1663 ),
1663 ),
1664 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1664 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1665 (b'', b'decode', None, _(b'apply any matching decode filter')),
1665 (b'', b'decode', None, _(b'apply any matching decode filter')),
1666 ]
1666 ]
1667 + walkopts
1667 + walkopts
1668 + formatteropts,
1668 + formatteropts,
1669 _(b'[OPTION]... FILE...'),
1669 _(b'[OPTION]... FILE...'),
1670 helpcategory=command.CATEGORY_FILE_CONTENTS,
1670 helpcategory=command.CATEGORY_FILE_CONTENTS,
1671 inferrepo=True,
1671 inferrepo=True,
1672 intents={INTENT_READONLY},
1672 intents={INTENT_READONLY},
1673 )
1673 )
1674 def cat(ui, repo, file1, *pats, **opts):
1674 def cat(ui, repo, file1, *pats, **opts):
1675 """output the current or given revision of files
1675 """output the current or given revision of files
1676
1676
1677 Print the specified files as they were at the given revision. If
1677 Print the specified files as they were at the given revision. If
1678 no revision is given, the parent of the working directory is used.
1678 no revision is given, the parent of the working directory is used.
1679
1679
1680 Output may be to a file, in which case the name of the file is
1680 Output may be to a file, in which case the name of the file is
1681 given using a template string. See :hg:`help templates`. In addition
1681 given using a template string. See :hg:`help templates`. In addition
1682 to the common template keywords, the following formatting rules are
1682 to the common template keywords, the following formatting rules are
1683 supported:
1683 supported:
1684
1684
1685 :``%%``: literal "%" character
1685 :``%%``: literal "%" character
1686 :``%s``: basename of file being printed
1686 :``%s``: basename of file being printed
1687 :``%d``: dirname of file being printed, or '.' if in repository root
1687 :``%d``: dirname of file being printed, or '.' if in repository root
1688 :``%p``: root-relative path name of file being printed
1688 :``%p``: root-relative path name of file being printed
1689 :``%H``: changeset hash (40 hexadecimal digits)
1689 :``%H``: changeset hash (40 hexadecimal digits)
1690 :``%R``: changeset revision number
1690 :``%R``: changeset revision number
1691 :``%h``: short-form changeset hash (12 hexadecimal digits)
1691 :``%h``: short-form changeset hash (12 hexadecimal digits)
1692 :``%r``: zero-padded changeset revision number
1692 :``%r``: zero-padded changeset revision number
1693 :``%b``: basename of the exporting repository
1693 :``%b``: basename of the exporting repository
1694 :``\\``: literal "\\" character
1694 :``\\``: literal "\\" character
1695
1695
1696 .. container:: verbose
1696 .. container:: verbose
1697
1697
1698 Template:
1698 Template:
1699
1699
1700 The following keywords are supported in addition to the common template
1700 The following keywords are supported in addition to the common template
1701 keywords and functions. See also :hg:`help templates`.
1701 keywords and functions. See also :hg:`help templates`.
1702
1702
1703 :data: String. File content.
1703 :data: String. File content.
1704 :path: String. Repository-absolute path of the file.
1704 :path: String. Repository-absolute path of the file.
1705
1705
1706 Returns 0 on success.
1706 Returns 0 on success.
1707 """
1707 """
1708 opts = pycompat.byteskwargs(opts)
1708 opts = pycompat.byteskwargs(opts)
1709 rev = opts.get(b'rev')
1709 rev = opts.get(b'rev')
1710 if rev:
1710 if rev:
1711 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1711 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1712 ctx = scmutil.revsingle(repo, rev)
1712 ctx = scmutil.revsingle(repo, rev)
1713 m = scmutil.match(ctx, (file1,) + pats, opts)
1713 m = scmutil.match(ctx, (file1,) + pats, opts)
1714 fntemplate = opts.pop(b'output', b'')
1714 fntemplate = opts.pop(b'output', b'')
1715 if cmdutil.isstdiofilename(fntemplate):
1715 if cmdutil.isstdiofilename(fntemplate):
1716 fntemplate = b''
1716 fntemplate = b''
1717
1717
1718 if fntemplate:
1718 if fntemplate:
1719 fm = formatter.nullformatter(ui, b'cat', opts)
1719 fm = formatter.nullformatter(ui, b'cat', opts)
1720 else:
1720 else:
1721 ui.pager(b'cat')
1721 ui.pager(b'cat')
1722 fm = ui.formatter(b'cat', opts)
1722 fm = ui.formatter(b'cat', opts)
1723 with fm:
1723 with fm:
1724 return cmdutil.cat(
1724 return cmdutil.cat(
1725 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1725 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1726 )
1726 )
1727
1727
1728
1728
1729 @command(
1729 @command(
1730 b'clone',
1730 b'clone',
1731 [
1731 [
1732 (
1732 (
1733 b'U',
1733 b'U',
1734 b'noupdate',
1734 b'noupdate',
1735 None,
1735 None,
1736 _(
1736 _(
1737 b'the clone will include an empty working '
1737 b'the clone will include an empty working '
1738 b'directory (only a repository)'
1738 b'directory (only a repository)'
1739 ),
1739 ),
1740 ),
1740 ),
1741 (
1741 (
1742 b'u',
1742 b'u',
1743 b'updaterev',
1743 b'updaterev',
1744 b'',
1744 b'',
1745 _(b'revision, tag, or branch to check out'),
1745 _(b'revision, tag, or branch to check out'),
1746 _(b'REV'),
1746 _(b'REV'),
1747 ),
1747 ),
1748 (
1748 (
1749 b'r',
1749 b'r',
1750 b'rev',
1750 b'rev',
1751 [],
1751 [],
1752 _(
1752 _(
1753 b'do not clone everything, but include this changeset'
1753 b'do not clone everything, but include this changeset'
1754 b' and its ancestors'
1754 b' and its ancestors'
1755 ),
1755 ),
1756 _(b'REV'),
1756 _(b'REV'),
1757 ),
1757 ),
1758 (
1758 (
1759 b'b',
1759 b'b',
1760 b'branch',
1760 b'branch',
1761 [],
1761 [],
1762 _(
1762 _(
1763 b'do not clone everything, but include this branch\'s'
1763 b'do not clone everything, but include this branch\'s'
1764 b' changesets and their ancestors'
1764 b' changesets and their ancestors'
1765 ),
1765 ),
1766 _(b'BRANCH'),
1766 _(b'BRANCH'),
1767 ),
1767 ),
1768 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1768 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1769 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1769 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1770 (b'', b'stream', None, _(b'clone with minimal data processing')),
1770 (b'', b'stream', None, _(b'clone with minimal data processing')),
1771 ]
1771 ]
1772 + remoteopts,
1772 + remoteopts,
1773 _(b'[OPTION]... SOURCE [DEST]'),
1773 _(b'[OPTION]... SOURCE [DEST]'),
1774 helpcategory=command.CATEGORY_REPO_CREATION,
1774 helpcategory=command.CATEGORY_REPO_CREATION,
1775 helpbasic=True,
1775 helpbasic=True,
1776 norepo=True,
1776 norepo=True,
1777 )
1777 )
1778 def clone(ui, source, dest=None, **opts):
1778 def clone(ui, source, dest=None, **opts):
1779 """make a copy of an existing repository
1779 """make a copy of an existing repository
1780
1780
1781 Create a copy of an existing repository in a new directory.
1781 Create a copy of an existing repository in a new directory.
1782
1782
1783 If no destination directory name is specified, it defaults to the
1783 If no destination directory name is specified, it defaults to the
1784 basename of the source.
1784 basename of the source.
1785
1785
1786 The location of the source is added to the new repository's
1786 The location of the source is added to the new repository's
1787 ``.hg/hgrc`` file, as the default to be used for future pulls.
1787 ``.hg/hgrc`` file, as the default to be used for future pulls.
1788
1788
1789 Only local paths and ``ssh://`` URLs are supported as
1789 Only local paths and ``ssh://`` URLs are supported as
1790 destinations. For ``ssh://`` destinations, no working directory or
1790 destinations. For ``ssh://`` destinations, no working directory or
1791 ``.hg/hgrc`` will be created on the remote side.
1791 ``.hg/hgrc`` will be created on the remote side.
1792
1792
1793 If the source repository has a bookmark called '@' set, that
1793 If the source repository has a bookmark called '@' set, that
1794 revision will be checked out in the new repository by default.
1794 revision will be checked out in the new repository by default.
1795
1795
1796 To check out a particular version, use -u/--update, or
1796 To check out a particular version, use -u/--update, or
1797 -U/--noupdate to create a clone with no working directory.
1797 -U/--noupdate to create a clone with no working directory.
1798
1798
1799 To pull only a subset of changesets, specify one or more revisions
1799 To pull only a subset of changesets, specify one or more revisions
1800 identifiers with -r/--rev or branches with -b/--branch. The
1800 identifiers with -r/--rev or branches with -b/--branch. The
1801 resulting clone will contain only the specified changesets and
1801 resulting clone will contain only the specified changesets and
1802 their ancestors. These options (or 'clone src#rev dest') imply
1802 their ancestors. These options (or 'clone src#rev dest') imply
1803 --pull, even for local source repositories.
1803 --pull, even for local source repositories.
1804
1804
1805 In normal clone mode, the remote normalizes repository data into a common
1805 In normal clone mode, the remote normalizes repository data into a common
1806 exchange format and the receiving end translates this data into its local
1806 exchange format and the receiving end translates this data into its local
1807 storage format. --stream activates a different clone mode that essentially
1807 storage format. --stream activates a different clone mode that essentially
1808 copies repository files from the remote with minimal data processing. This
1808 copies repository files from the remote with minimal data processing. This
1809 significantly reduces the CPU cost of a clone both remotely and locally.
1809 significantly reduces the CPU cost of a clone both remotely and locally.
1810 However, it often increases the transferred data size by 30-40%. This can
1810 However, it often increases the transferred data size by 30-40%. This can
1811 result in substantially faster clones where I/O throughput is plentiful,
1811 result in substantially faster clones where I/O throughput is plentiful,
1812 especially for larger repositories. A side-effect of --stream clones is
1812 especially for larger repositories. A side-effect of --stream clones is
1813 that storage settings and requirements on the remote are applied locally:
1813 that storage settings and requirements on the remote are applied locally:
1814 a modern client may inherit legacy or inefficient storage used by the
1814 a modern client may inherit legacy or inefficient storage used by the
1815 remote or a legacy Mercurial client may not be able to clone from a
1815 remote or a legacy Mercurial client may not be able to clone from a
1816 modern Mercurial remote.
1816 modern Mercurial remote.
1817
1817
1818 .. note::
1818 .. note::
1819
1819
1820 Specifying a tag will include the tagged changeset but not the
1820 Specifying a tag will include the tagged changeset but not the
1821 changeset containing the tag.
1821 changeset containing the tag.
1822
1822
1823 .. container:: verbose
1823 .. container:: verbose
1824
1824
1825 For efficiency, hardlinks are used for cloning whenever the
1825 For efficiency, hardlinks are used for cloning whenever the
1826 source and destination are on the same filesystem (note this
1826 source and destination are on the same filesystem (note this
1827 applies only to the repository data, not to the working
1827 applies only to the repository data, not to the working
1828 directory). Some filesystems, such as AFS, implement hardlinking
1828 directory). Some filesystems, such as AFS, implement hardlinking
1829 incorrectly, but do not report errors. In these cases, use the
1829 incorrectly, but do not report errors. In these cases, use the
1830 --pull option to avoid hardlinking.
1830 --pull option to avoid hardlinking.
1831
1831
1832 Mercurial will update the working directory to the first applicable
1832 Mercurial will update the working directory to the first applicable
1833 revision from this list:
1833 revision from this list:
1834
1834
1835 a) null if -U or the source repository has no changesets
1835 a) null if -U or the source repository has no changesets
1836 b) if -u . and the source repository is local, the first parent of
1836 b) if -u . and the source repository is local, the first parent of
1837 the source repository's working directory
1837 the source repository's working directory
1838 c) the changeset specified with -u (if a branch name, this means the
1838 c) the changeset specified with -u (if a branch name, this means the
1839 latest head of that branch)
1839 latest head of that branch)
1840 d) the changeset specified with -r
1840 d) the changeset specified with -r
1841 e) the tipmost head specified with -b
1841 e) the tipmost head specified with -b
1842 f) the tipmost head specified with the url#branch source syntax
1842 f) the tipmost head specified with the url#branch source syntax
1843 g) the revision marked with the '@' bookmark, if present
1843 g) the revision marked with the '@' bookmark, if present
1844 h) the tipmost head of the default branch
1844 h) the tipmost head of the default branch
1845 i) tip
1845 i) tip
1846
1846
1847 When cloning from servers that support it, Mercurial may fetch
1847 When cloning from servers that support it, Mercurial may fetch
1848 pre-generated data from a server-advertised URL or inline from the
1848 pre-generated data from a server-advertised URL or inline from the
1849 same stream. When this is done, hooks operating on incoming changesets
1849 same stream. When this is done, hooks operating on incoming changesets
1850 and changegroups may fire more than once, once for each pre-generated
1850 and changegroups may fire more than once, once for each pre-generated
1851 bundle and as well as for any additional remaining data. In addition,
1851 bundle and as well as for any additional remaining data. In addition,
1852 if an error occurs, the repository may be rolled back to a partial
1852 if an error occurs, the repository may be rolled back to a partial
1853 clone. This behavior may change in future releases.
1853 clone. This behavior may change in future releases.
1854 See :hg:`help -e clonebundles` for more.
1854 See :hg:`help -e clonebundles` for more.
1855
1855
1856 Examples:
1856 Examples:
1857
1857
1858 - clone a remote repository to a new directory named hg/::
1858 - clone a remote repository to a new directory named hg/::
1859
1859
1860 hg clone https://www.mercurial-scm.org/repo/hg/
1860 hg clone https://www.mercurial-scm.org/repo/hg/
1861
1861
1862 - create a lightweight local clone::
1862 - create a lightweight local clone::
1863
1863
1864 hg clone project/ project-feature/
1864 hg clone project/ project-feature/
1865
1865
1866 - clone from an absolute path on an ssh server (note double-slash)::
1866 - clone from an absolute path on an ssh server (note double-slash)::
1867
1867
1868 hg clone ssh://user@server//home/projects/alpha/
1868 hg clone ssh://user@server//home/projects/alpha/
1869
1869
1870 - do a streaming clone while checking out a specified version::
1870 - do a streaming clone while checking out a specified version::
1871
1871
1872 hg clone --stream http://server/repo -u 1.5
1872 hg clone --stream http://server/repo -u 1.5
1873
1873
1874 - create a repository without changesets after a particular revision::
1874 - create a repository without changesets after a particular revision::
1875
1875
1876 hg clone -r 04e544 experimental/ good/
1876 hg clone -r 04e544 experimental/ good/
1877
1877
1878 - clone (and track) a particular named branch::
1878 - clone (and track) a particular named branch::
1879
1879
1880 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1880 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1881
1881
1882 See :hg:`help urls` for details on specifying URLs.
1882 See :hg:`help urls` for details on specifying URLs.
1883
1883
1884 Returns 0 on success.
1884 Returns 0 on success.
1885 """
1885 """
1886 opts = pycompat.byteskwargs(opts)
1886 opts = pycompat.byteskwargs(opts)
1887 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1887 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1888
1888
1889 # --include/--exclude can come from narrow or sparse.
1889 # --include/--exclude can come from narrow or sparse.
1890 includepats, excludepats = None, None
1890 includepats, excludepats = None, None
1891
1891
1892 # hg.clone() differentiates between None and an empty set. So make sure
1892 # hg.clone() differentiates between None and an empty set. So make sure
1893 # patterns are sets if narrow is requested without patterns.
1893 # patterns are sets if narrow is requested without patterns.
1894 if opts.get(b'narrow'):
1894 if opts.get(b'narrow'):
1895 includepats = set()
1895 includepats = set()
1896 excludepats = set()
1896 excludepats = set()
1897
1897
1898 if opts.get(b'include'):
1898 if opts.get(b'include'):
1899 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1899 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1900 if opts.get(b'exclude'):
1900 if opts.get(b'exclude'):
1901 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1901 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1902
1902
1903 r = hg.clone(
1903 r = hg.clone(
1904 ui,
1904 ui,
1905 opts,
1905 opts,
1906 source,
1906 source,
1907 dest,
1907 dest,
1908 pull=opts.get(b'pull'),
1908 pull=opts.get(b'pull'),
1909 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1909 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1910 revs=opts.get(b'rev'),
1910 revs=opts.get(b'rev'),
1911 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1911 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1912 branch=opts.get(b'branch'),
1912 branch=opts.get(b'branch'),
1913 shareopts=opts.get(b'shareopts'),
1913 shareopts=opts.get(b'shareopts'),
1914 storeincludepats=includepats,
1914 storeincludepats=includepats,
1915 storeexcludepats=excludepats,
1915 storeexcludepats=excludepats,
1916 depth=opts.get(b'depth') or None,
1916 depth=opts.get(b'depth') or None,
1917 )
1917 )
1918
1918
1919 return r is None
1919 return r is None
1920
1920
1921
1921
1922 @command(
1922 @command(
1923 b'commit|ci',
1923 b'commit|ci',
1924 [
1924 [
1925 (
1925 (
1926 b'A',
1926 b'A',
1927 b'addremove',
1927 b'addremove',
1928 None,
1928 None,
1929 _(b'mark new/missing files as added/removed before committing'),
1929 _(b'mark new/missing files as added/removed before committing'),
1930 ),
1930 ),
1931 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1931 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1932 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1932 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1933 (b's', b'secret', None, _(b'use the secret phase for committing')),
1933 (b's', b'secret', None, _(b'use the secret phase for committing')),
1934 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1934 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1935 (
1935 (
1936 b'',
1936 b'',
1937 b'force-close-branch',
1937 b'force-close-branch',
1938 None,
1938 None,
1939 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1939 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1940 ),
1940 ),
1941 (b'i', b'interactive', None, _(b'use interactive mode')),
1941 (b'i', b'interactive', None, _(b'use interactive mode')),
1942 ]
1942 ]
1943 + walkopts
1943 + walkopts
1944 + commitopts
1944 + commitopts
1945 + commitopts2
1945 + commitopts2
1946 + subrepoopts,
1946 + subrepoopts,
1947 _(b'[OPTION]... [FILE]...'),
1947 _(b'[OPTION]... [FILE]...'),
1948 helpcategory=command.CATEGORY_COMMITTING,
1948 helpcategory=command.CATEGORY_COMMITTING,
1949 helpbasic=True,
1949 helpbasic=True,
1950 inferrepo=True,
1950 inferrepo=True,
1951 )
1951 )
1952 def commit(ui, repo, *pats, **opts):
1952 def commit(ui, repo, *pats, **opts):
1953 """commit the specified files or all outstanding changes
1953 """commit the specified files or all outstanding changes
1954
1954
1955 Commit changes to the given files into the repository. Unlike a
1955 Commit changes to the given files into the repository. Unlike a
1956 centralized SCM, this operation is a local operation. See
1956 centralized SCM, this operation is a local operation. See
1957 :hg:`push` for a way to actively distribute your changes.
1957 :hg:`push` for a way to actively distribute your changes.
1958
1958
1959 If a list of files is omitted, all changes reported by :hg:`status`
1959 If a list of files is omitted, all changes reported by :hg:`status`
1960 will be committed.
1960 will be committed.
1961
1961
1962 If you are committing the result of a merge, do not provide any
1962 If you are committing the result of a merge, do not provide any
1963 filenames or -I/-X filters.
1963 filenames or -I/-X filters.
1964
1964
1965 If no commit message is specified, Mercurial starts your
1965 If no commit message is specified, Mercurial starts your
1966 configured editor where you can enter a message. In case your
1966 configured editor where you can enter a message. In case your
1967 commit fails, you will find a backup of your message in
1967 commit fails, you will find a backup of your message in
1968 ``.hg/last-message.txt``.
1968 ``.hg/last-message.txt``.
1969
1969
1970 The --close-branch flag can be used to mark the current branch
1970 The --close-branch flag can be used to mark the current branch
1971 head closed. When all heads of a branch are closed, the branch
1971 head closed. When all heads of a branch are closed, the branch
1972 will be considered closed and no longer listed.
1972 will be considered closed and no longer listed.
1973
1973
1974 The --amend flag can be used to amend the parent of the
1974 The --amend flag can be used to amend the parent of the
1975 working directory with a new commit that contains the changes
1975 working directory with a new commit that contains the changes
1976 in the parent in addition to those currently reported by :hg:`status`,
1976 in the parent in addition to those currently reported by :hg:`status`,
1977 if there are any. The old commit is stored in a backup bundle in
1977 if there are any. The old commit is stored in a backup bundle in
1978 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1978 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1979 on how to restore it).
1979 on how to restore it).
1980
1980
1981 Message, user and date are taken from the amended commit unless
1981 Message, user and date are taken from the amended commit unless
1982 specified. When a message isn't specified on the command line,
1982 specified. When a message isn't specified on the command line,
1983 the editor will open with the message of the amended commit.
1983 the editor will open with the message of the amended commit.
1984
1984
1985 It is not possible to amend public changesets (see :hg:`help phases`)
1985 It is not possible to amend public changesets (see :hg:`help phases`)
1986 or changesets that have children.
1986 or changesets that have children.
1987
1987
1988 See :hg:`help dates` for a list of formats valid for -d/--date.
1988 See :hg:`help dates` for a list of formats valid for -d/--date.
1989
1989
1990 Returns 0 on success, 1 if nothing changed.
1990 Returns 0 on success, 1 if nothing changed.
1991
1991
1992 .. container:: verbose
1992 .. container:: verbose
1993
1993
1994 Examples:
1994 Examples:
1995
1995
1996 - commit all files ending in .py::
1996 - commit all files ending in .py::
1997
1997
1998 hg commit --include "set:**.py"
1998 hg commit --include "set:**.py"
1999
1999
2000 - commit all non-binary files::
2000 - commit all non-binary files::
2001
2001
2002 hg commit --exclude "set:binary()"
2002 hg commit --exclude "set:binary()"
2003
2003
2004 - amend the current commit and set the date to now::
2004 - amend the current commit and set the date to now::
2005
2005
2006 hg commit --amend --date now
2006 hg commit --amend --date now
2007 """
2007 """
2008 with repo.wlock(), repo.lock():
2008 with repo.wlock(), repo.lock():
2009 return _docommit(ui, repo, *pats, **opts)
2009 return _docommit(ui, repo, *pats, **opts)
2010
2010
2011
2011
2012 def _docommit(ui, repo, *pats, **opts):
2012 def _docommit(ui, repo, *pats, **opts):
2013 if opts.get('interactive'):
2013 if opts.get('interactive'):
2014 opts.pop('interactive')
2014 opts.pop('interactive')
2015 ret = cmdutil.dorecord(
2015 ret = cmdutil.dorecord(
2016 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2016 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2017 )
2017 )
2018 # ret can be 0 (no changes to record) or the value returned by
2018 # ret can be 0 (no changes to record) or the value returned by
2019 # commit(), 1 if nothing changed or None on success.
2019 # commit(), 1 if nothing changed or None on success.
2020 return 1 if ret == 0 else ret
2020 return 1 if ret == 0 else ret
2021
2021
2022 opts = pycompat.byteskwargs(opts)
2022 opts = pycompat.byteskwargs(opts)
2023 if opts.get(b'subrepos'):
2023 if opts.get(b'subrepos'):
2024 if opts.get(b'amend'):
2024 if opts.get(b'amend'):
2025 raise error.Abort(_(b'cannot amend with --subrepos'))
2025 raise error.Abort(_(b'cannot amend with --subrepos'))
2026 # Let --subrepos on the command line override config setting.
2026 # Let --subrepos on the command line override config setting.
2027 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2027 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2028
2028
2029 cmdutil.checkunfinished(repo, commit=True)
2029 cmdutil.checkunfinished(repo, commit=True)
2030
2030
2031 branch = repo[None].branch()
2031 branch = repo[None].branch()
2032 bheads = repo.branchheads(branch)
2032 bheads = repo.branchheads(branch)
2033
2033
2034 extra = {}
2034 extra = {}
2035 if opts.get(b'close_branch') or opts.get(b'force_close_branch'):
2035 if opts.get(b'close_branch') or opts.get(b'force_close_branch'):
2036 extra[b'close'] = b'1'
2036 extra[b'close'] = b'1'
2037
2037
2038 if repo[b'.'].closesbranch():
2038 if repo[b'.'].closesbranch():
2039 raise error.Abort(
2039 raise error.Abort(
2040 _(b'current revision is already a branch closing head')
2040 _(b'current revision is already a branch closing head')
2041 )
2041 )
2042 elif not bheads:
2042 elif not bheads:
2043 raise error.Abort(_(b'branch "%s" has no heads to close') % branch)
2043 raise error.Abort(_(b'branch "%s" has no heads to close') % branch)
2044 elif (
2044 elif (
2045 branch == repo[b'.'].branch()
2045 branch == repo[b'.'].branch()
2046 and repo[b'.'].node() not in bheads
2046 and repo[b'.'].node() not in bheads
2047 and not opts.get(b'force_close_branch')
2047 and not opts.get(b'force_close_branch')
2048 ):
2048 ):
2049 hint = _(
2049 hint = _(
2050 b'use --force-close-branch to close branch from a non-head'
2050 b'use --force-close-branch to close branch from a non-head'
2051 b' changeset'
2051 b' changeset'
2052 )
2052 )
2053 raise error.Abort(_(b'can only close branch heads'), hint=hint)
2053 raise error.Abort(_(b'can only close branch heads'), hint=hint)
2054 elif opts.get(b'amend'):
2054 elif opts.get(b'amend'):
2055 if (
2055 if (
2056 repo[b'.'].p1().branch() != branch
2056 repo[b'.'].p1().branch() != branch
2057 and repo[b'.'].p2().branch() != branch
2057 and repo[b'.'].p2().branch() != branch
2058 ):
2058 ):
2059 raise error.Abort(_(b'can only close branch heads'))
2059 raise error.Abort(_(b'can only close branch heads'))
2060
2060
2061 if opts.get(b'amend'):
2061 if opts.get(b'amend'):
2062 if ui.configbool(b'ui', b'commitsubrepos'):
2062 if ui.configbool(b'ui', b'commitsubrepos'):
2063 raise error.Abort(_(b'cannot amend with ui.commitsubrepos enabled'))
2063 raise error.Abort(_(b'cannot amend with ui.commitsubrepos enabled'))
2064
2064
2065 old = repo[b'.']
2065 old = repo[b'.']
2066 rewriteutil.precheck(repo, [old.rev()], b'amend')
2066 rewriteutil.precheck(repo, [old.rev()], b'amend')
2067
2067
2068 # Currently histedit gets confused if an amend happens while histedit
2068 # Currently histedit gets confused if an amend happens while histedit
2069 # is in progress. Since we have a checkunfinished command, we are
2069 # is in progress. Since we have a checkunfinished command, we are
2070 # temporarily honoring it.
2070 # temporarily honoring it.
2071 #
2071 #
2072 # Note: eventually this guard will be removed. Please do not expect
2072 # Note: eventually this guard will be removed. Please do not expect
2073 # this behavior to remain.
2073 # this behavior to remain.
2074 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2074 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2075 cmdutil.checkunfinished(repo)
2075 cmdutil.checkunfinished(repo)
2076
2076
2077 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2077 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2078 if node == old.node():
2078 if node == old.node():
2079 ui.status(_(b"nothing changed\n"))
2079 ui.status(_(b"nothing changed\n"))
2080 return 1
2080 return 1
2081 else:
2081 else:
2082
2082
2083 def commitfunc(ui, repo, message, match, opts):
2083 def commitfunc(ui, repo, message, match, opts):
2084 overrides = {}
2084 overrides = {}
2085 if opts.get(b'secret'):
2085 if opts.get(b'secret'):
2086 overrides[(b'phases', b'new-commit')] = b'secret'
2086 overrides[(b'phases', b'new-commit')] = b'secret'
2087
2087
2088 baseui = repo.baseui
2088 baseui = repo.baseui
2089 with baseui.configoverride(overrides, b'commit'):
2089 with baseui.configoverride(overrides, b'commit'):
2090 with ui.configoverride(overrides, b'commit'):
2090 with ui.configoverride(overrides, b'commit'):
2091 editform = cmdutil.mergeeditform(
2091 editform = cmdutil.mergeeditform(
2092 repo[None], b'commit.normal'
2092 repo[None], b'commit.normal'
2093 )
2093 )
2094 editor = cmdutil.getcommiteditor(
2094 editor = cmdutil.getcommiteditor(
2095 editform=editform, **pycompat.strkwargs(opts)
2095 editform=editform, **pycompat.strkwargs(opts)
2096 )
2096 )
2097 return repo.commit(
2097 return repo.commit(
2098 message,
2098 message,
2099 opts.get(b'user'),
2099 opts.get(b'user'),
2100 opts.get(b'date'),
2100 opts.get(b'date'),
2101 match,
2101 match,
2102 editor=editor,
2102 editor=editor,
2103 extra=extra,
2103 extra=extra,
2104 )
2104 )
2105
2105
2106 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2106 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2107
2107
2108 if not node:
2108 if not node:
2109 stat = cmdutil.postcommitstatus(repo, pats, opts)
2109 stat = cmdutil.postcommitstatus(repo, pats, opts)
2110 if stat.deleted:
2110 if stat.deleted:
2111 ui.status(
2111 ui.status(
2112 _(
2112 _(
2113 b"nothing changed (%d missing files, see "
2113 b"nothing changed (%d missing files, see "
2114 b"'hg status')\n"
2114 b"'hg status')\n"
2115 )
2115 )
2116 % len(stat.deleted)
2116 % len(stat.deleted)
2117 )
2117 )
2118 else:
2118 else:
2119 ui.status(_(b"nothing changed\n"))
2119 ui.status(_(b"nothing changed\n"))
2120 return 1
2120 return 1
2121
2121
2122 cmdutil.commitstatus(repo, node, branch, bheads, opts)
2122 cmdutil.commitstatus(repo, node, branch, bheads, opts)
2123
2123
2124 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2124 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2125 status(
2125 status(
2126 ui,
2126 ui,
2127 repo,
2127 repo,
2128 modified=True,
2128 modified=True,
2129 added=True,
2129 added=True,
2130 removed=True,
2130 removed=True,
2131 deleted=True,
2131 deleted=True,
2132 unknown=True,
2132 unknown=True,
2133 subrepos=opts.get(b'subrepos'),
2133 subrepos=opts.get(b'subrepos'),
2134 )
2134 )
2135
2135
2136
2136
2137 @command(
2137 @command(
2138 b'config|showconfig|debugconfig',
2138 b'config|showconfig|debugconfig',
2139 [
2139 [
2140 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2140 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2141 (b'e', b'edit', None, _(b'edit user config')),
2141 (b'e', b'edit', None, _(b'edit user config')),
2142 (b'l', b'local', None, _(b'edit repository config')),
2142 (b'l', b'local', None, _(b'edit repository config')),
2143 (b'g', b'global', None, _(b'edit global config')),
2143 (b'g', b'global', None, _(b'edit global config')),
2144 ]
2144 ]
2145 + formatteropts,
2145 + formatteropts,
2146 _(b'[-u] [NAME]...'),
2146 _(b'[-u] [NAME]...'),
2147 helpcategory=command.CATEGORY_HELP,
2147 helpcategory=command.CATEGORY_HELP,
2148 optionalrepo=True,
2148 optionalrepo=True,
2149 intents={INTENT_READONLY},
2149 intents={INTENT_READONLY},
2150 )
2150 )
2151 def config(ui, repo, *values, **opts):
2151 def config(ui, repo, *values, **opts):
2152 """show combined config settings from all hgrc files
2152 """show combined config settings from all hgrc files
2153
2153
2154 With no arguments, print names and values of all config items.
2154 With no arguments, print names and values of all config items.
2155
2155
2156 With one argument of the form section.name, print just the value
2156 With one argument of the form section.name, print just the value
2157 of that config item.
2157 of that config item.
2158
2158
2159 With multiple arguments, print names and values of all config
2159 With multiple arguments, print names and values of all config
2160 items with matching section names or section.names.
2160 items with matching section names or section.names.
2161
2161
2162 With --edit, start an editor on the user-level config file. With
2162 With --edit, start an editor on the user-level config file. With
2163 --global, edit the system-wide config file. With --local, edit the
2163 --global, edit the system-wide config file. With --local, edit the
2164 repository-level config file.
2164 repository-level config file.
2165
2165
2166 With --debug, the source (filename and line number) is printed
2166 With --debug, the source (filename and line number) is printed
2167 for each config item.
2167 for each config item.
2168
2168
2169 See :hg:`help config` for more information about config files.
2169 See :hg:`help config` for more information about config files.
2170
2170
2171 .. container:: verbose
2171 .. container:: verbose
2172
2172
2173 Template:
2173 Template:
2174
2174
2175 The following keywords are supported. See also :hg:`help templates`.
2175 The following keywords are supported. See also :hg:`help templates`.
2176
2176
2177 :name: String. Config name.
2177 :name: String. Config name.
2178 :source: String. Filename and line number where the item is defined.
2178 :source: String. Filename and line number where the item is defined.
2179 :value: String. Config value.
2179 :value: String. Config value.
2180
2180
2181 Returns 0 on success, 1 if NAME does not exist.
2181 Returns 0 on success, 1 if NAME does not exist.
2182
2182
2183 """
2183 """
2184
2184
2185 opts = pycompat.byteskwargs(opts)
2185 opts = pycompat.byteskwargs(opts)
2186 if opts.get(b'edit') or opts.get(b'local') or opts.get(b'global'):
2186 if opts.get(b'edit') or opts.get(b'local') or opts.get(b'global'):
2187 if opts.get(b'local') and opts.get(b'global'):
2187 if opts.get(b'local') and opts.get(b'global'):
2188 raise error.Abort(_(b"can't use --local and --global together"))
2188 raise error.Abort(_(b"can't use --local and --global together"))
2189
2189
2190 if opts.get(b'local'):
2190 if opts.get(b'local'):
2191 if not repo:
2191 if not repo:
2192 raise error.Abort(_(b"can't use --local outside a repository"))
2192 raise error.Abort(_(b"can't use --local outside a repository"))
2193 paths = [repo.vfs.join(b'hgrc')]
2193 paths = [repo.vfs.join(b'hgrc')]
2194 elif opts.get(b'global'):
2194 elif opts.get(b'global'):
2195 paths = rcutil.systemrcpath()
2195 paths = rcutil.systemrcpath()
2196 else:
2196 else:
2197 paths = rcutil.userrcpath()
2197 paths = rcutil.userrcpath()
2198
2198
2199 for f in paths:
2199 for f in paths:
2200 if os.path.exists(f):
2200 if os.path.exists(f):
2201 break
2201 break
2202 else:
2202 else:
2203 if opts.get(b'global'):
2203 if opts.get(b'global'):
2204 samplehgrc = uimod.samplehgrcs[b'global']
2204 samplehgrc = uimod.samplehgrcs[b'global']
2205 elif opts.get(b'local'):
2205 elif opts.get(b'local'):
2206 samplehgrc = uimod.samplehgrcs[b'local']
2206 samplehgrc = uimod.samplehgrcs[b'local']
2207 else:
2207 else:
2208 samplehgrc = uimod.samplehgrcs[b'user']
2208 samplehgrc = uimod.samplehgrcs[b'user']
2209
2209
2210 f = paths[0]
2210 f = paths[0]
2211 fp = open(f, b"wb")
2211 fp = open(f, b"wb")
2212 fp.write(util.tonativeeol(samplehgrc))
2212 fp.write(util.tonativeeol(samplehgrc))
2213 fp.close()
2213 fp.close()
2214
2214
2215 editor = ui.geteditor()
2215 editor = ui.geteditor()
2216 ui.system(
2216 ui.system(
2217 b"%s \"%s\"" % (editor, f),
2217 b"%s \"%s\"" % (editor, f),
2218 onerr=error.Abort,
2218 onerr=error.Abort,
2219 errprefix=_(b"edit failed"),
2219 errprefix=_(b"edit failed"),
2220 blockedtag=b'config_edit',
2220 blockedtag=b'config_edit',
2221 )
2221 )
2222 return
2222 return
2223 ui.pager(b'config')
2223 ui.pager(b'config')
2224 fm = ui.formatter(b'config', opts)
2224 fm = ui.formatter(b'config', opts)
2225 for t, f in rcutil.rccomponents():
2225 for t, f in rcutil.rccomponents():
2226 if t == b'path':
2226 if t == b'path':
2227 ui.debug(b'read config from: %s\n' % f)
2227 ui.debug(b'read config from: %s\n' % f)
2228 elif t == b'resource':
2228 elif t == b'resource':
2229 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2229 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2230 elif t == b'items':
2230 elif t == b'items':
2231 # Don't print anything for 'items'.
2231 # Don't print anything for 'items'.
2232 pass
2232 pass
2233 else:
2233 else:
2234 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2234 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2235 untrusted = bool(opts.get(b'untrusted'))
2235 untrusted = bool(opts.get(b'untrusted'))
2236
2236
2237 selsections = selentries = []
2237 selsections = selentries = []
2238 if values:
2238 if values:
2239 selsections = [v for v in values if b'.' not in v]
2239 selsections = [v for v in values if b'.' not in v]
2240 selentries = [v for v in values if b'.' in v]
2240 selentries = [v for v in values if b'.' in v]
2241 uniquesel = len(selentries) == 1 and not selsections
2241 uniquesel = len(selentries) == 1 and not selsections
2242 selsections = set(selsections)
2242 selsections = set(selsections)
2243 selentries = set(selentries)
2243 selentries = set(selentries)
2244
2244
2245 matched = False
2245 matched = False
2246 for section, name, value in ui.walkconfig(untrusted=untrusted):
2246 for section, name, value in ui.walkconfig(untrusted=untrusted):
2247 source = ui.configsource(section, name, untrusted)
2247 source = ui.configsource(section, name, untrusted)
2248 value = pycompat.bytestr(value)
2248 value = pycompat.bytestr(value)
2249 defaultvalue = ui.configdefault(section, name)
2249 defaultvalue = ui.configdefault(section, name)
2250 if fm.isplain():
2250 if fm.isplain():
2251 source = source or b'none'
2251 source = source or b'none'
2252 value = value.replace(b'\n', b'\\n')
2252 value = value.replace(b'\n', b'\\n')
2253 entryname = section + b'.' + name
2253 entryname = section + b'.' + name
2254 if values and not (section in selsections or entryname in selentries):
2254 if values and not (section in selsections or entryname in selentries):
2255 continue
2255 continue
2256 fm.startitem()
2256 fm.startitem()
2257 fm.condwrite(ui.debugflag, b'source', b'%s: ', source)
2257 fm.condwrite(ui.debugflag, b'source', b'%s: ', source)
2258 if uniquesel:
2258 if uniquesel:
2259 fm.data(name=entryname)
2259 fm.data(name=entryname)
2260 fm.write(b'value', b'%s\n', value)
2260 fm.write(b'value', b'%s\n', value)
2261 else:
2261 else:
2262 fm.write(b'name value', b'%s=%s\n', entryname, value)
2262 fm.write(b'name value', b'%s=%s\n', entryname, value)
2263 if formatter.isprintable(defaultvalue):
2263 if formatter.isprintable(defaultvalue):
2264 fm.data(defaultvalue=defaultvalue)
2264 fm.data(defaultvalue=defaultvalue)
2265 elif isinstance(defaultvalue, list) and all(
2265 elif isinstance(defaultvalue, list) and all(
2266 formatter.isprintable(e) for e in defaultvalue
2266 formatter.isprintable(e) for e in defaultvalue
2267 ):
2267 ):
2268 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2268 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2269 # TODO: no idea how to process unsupported defaultvalue types
2269 # TODO: no idea how to process unsupported defaultvalue types
2270 matched = True
2270 matched = True
2271 fm.end()
2271 fm.end()
2272 if matched:
2272 if matched:
2273 return 0
2273 return 0
2274 return 1
2274 return 1
2275
2275
2276
2276
2277 @command(
2277 @command(
2278 b'continue',
2278 b'continue',
2279 dryrunopts,
2279 dryrunopts,
2280 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2280 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2281 helpbasic=True,
2281 helpbasic=True,
2282 )
2282 )
2283 def continuecmd(ui, repo, **opts):
2283 def continuecmd(ui, repo, **opts):
2284 """resumes an interrupted operation (EXPERIMENTAL)
2284 """resumes an interrupted operation (EXPERIMENTAL)
2285
2285
2286 Finishes a multistep operation like graft, histedit, rebase, merge,
2286 Finishes a multistep operation like graft, histedit, rebase, merge,
2287 and unshelve if they are in an interrupted state.
2287 and unshelve if they are in an interrupted state.
2288
2288
2289 use --dry-run/-n to dry run the command.
2289 use --dry-run/-n to dry run the command.
2290 """
2290 """
2291 dryrun = opts.get('dry_run')
2291 dryrun = opts.get('dry_run')
2292 contstate = cmdutil.getunfinishedstate(repo)
2292 contstate = cmdutil.getunfinishedstate(repo)
2293 if not contstate:
2293 if not contstate:
2294 raise error.Abort(_(b'no operation in progress'))
2294 raise error.Abort(_(b'no operation in progress'))
2295 if not contstate.continuefunc:
2295 if not contstate.continuefunc:
2296 raise error.Abort(
2296 raise error.Abort(
2297 (
2297 (
2298 _(b"%s in progress but does not support 'hg continue'")
2298 _(b"%s in progress but does not support 'hg continue'")
2299 % (contstate._opname)
2299 % (contstate._opname)
2300 ),
2300 ),
2301 hint=contstate.continuemsg(),
2301 hint=contstate.continuemsg(),
2302 )
2302 )
2303 if dryrun:
2303 if dryrun:
2304 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2304 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2305 return
2305 return
2306 return contstate.continuefunc(ui, repo)
2306 return contstate.continuefunc(ui, repo)
2307
2307
2308
2308
2309 @command(
2309 @command(
2310 b'copy|cp',
2310 b'copy|cp',
2311 [
2311 [
2312 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2312 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2313 (
2313 (
2314 b'f',
2314 b'f',
2315 b'force',
2315 b'force',
2316 None,
2316 None,
2317 _(b'forcibly copy over an existing managed file'),
2317 _(b'forcibly copy over an existing managed file'),
2318 ),
2318 ),
2319 ]
2319 ]
2320 + walkopts
2320 + walkopts
2321 + dryrunopts,
2321 + dryrunopts,
2322 _(b'[OPTION]... SOURCE... DEST'),
2322 _(b'[OPTION]... SOURCE... DEST'),
2323 helpcategory=command.CATEGORY_FILE_CONTENTS,
2323 helpcategory=command.CATEGORY_FILE_CONTENTS,
2324 )
2324 )
2325 def copy(ui, repo, *pats, **opts):
2325 def copy(ui, repo, *pats, **opts):
2326 """mark files as copied for the next commit
2326 """mark files as copied for the next commit
2327
2327
2328 Mark dest as having copies of source files. If dest is a
2328 Mark dest as having copies of source files. If dest is a
2329 directory, copies are put in that directory. If dest is a file,
2329 directory, copies are put in that directory. If dest is a file,
2330 the source must be a single file.
2330 the source must be a single file.
2331
2331
2332 By default, this command copies the contents of files as they
2332 By default, this command copies the contents of files as they
2333 exist in the working directory. If invoked with -A/--after, the
2333 exist in the working directory. If invoked with -A/--after, the
2334 operation is recorded, but no copying is performed.
2334 operation is recorded, but no copying is performed.
2335
2335
2336 This command takes effect with the next commit. To undo a copy
2336 This command takes effect with the next commit. To undo a copy
2337 before that, see :hg:`revert`.
2337 before that, see :hg:`revert`.
2338
2338
2339 Returns 0 on success, 1 if errors are encountered.
2339 Returns 0 on success, 1 if errors are encountered.
2340 """
2340 """
2341 opts = pycompat.byteskwargs(opts)
2341 opts = pycompat.byteskwargs(opts)
2342 with repo.wlock(False):
2342 with repo.wlock(False):
2343 return cmdutil.copy(ui, repo, pats, opts)
2343 return cmdutil.copy(ui, repo, pats, opts)
2344
2344
2345
2345
2346 @command(
2346 @command(
2347 b'debugcommands',
2347 b'debugcommands',
2348 [],
2348 [],
2349 _(b'[COMMAND]'),
2349 _(b'[COMMAND]'),
2350 helpcategory=command.CATEGORY_HELP,
2350 helpcategory=command.CATEGORY_HELP,
2351 norepo=True,
2351 norepo=True,
2352 )
2352 )
2353 def debugcommands(ui, cmd=b'', *args):
2353 def debugcommands(ui, cmd=b'', *args):
2354 """list all available commands and options"""
2354 """list all available commands and options"""
2355 for cmd, vals in sorted(pycompat.iteritems(table)):
2355 for cmd, vals in sorted(pycompat.iteritems(table)):
2356 cmd = cmd.split(b'|')[0]
2356 cmd = cmd.split(b'|')[0]
2357 opts = b', '.join([i[1] for i in vals[1]])
2357 opts = b', '.join([i[1] for i in vals[1]])
2358 ui.write(b'%s: %s\n' % (cmd, opts))
2358 ui.write(b'%s: %s\n' % (cmd, opts))
2359
2359
2360
2360
2361 @command(
2361 @command(
2362 b'debugcomplete',
2362 b'debugcomplete',
2363 [(b'o', b'options', None, _(b'show the command options'))],
2363 [(b'o', b'options', None, _(b'show the command options'))],
2364 _(b'[-o] CMD'),
2364 _(b'[-o] CMD'),
2365 helpcategory=command.CATEGORY_HELP,
2365 helpcategory=command.CATEGORY_HELP,
2366 norepo=True,
2366 norepo=True,
2367 )
2367 )
2368 def debugcomplete(ui, cmd=b'', **opts):
2368 def debugcomplete(ui, cmd=b'', **opts):
2369 """returns the completion list associated with the given command"""
2369 """returns the completion list associated with the given command"""
2370
2370
2371 if opts.get('options'):
2371 if opts.get('options'):
2372 options = []
2372 options = []
2373 otables = [globalopts]
2373 otables = [globalopts]
2374 if cmd:
2374 if cmd:
2375 aliases, entry = cmdutil.findcmd(cmd, table, False)
2375 aliases, entry = cmdutil.findcmd(cmd, table, False)
2376 otables.append(entry[1])
2376 otables.append(entry[1])
2377 for t in otables:
2377 for t in otables:
2378 for o in t:
2378 for o in t:
2379 if b"(DEPRECATED)" in o[3]:
2379 if b"(DEPRECATED)" in o[3]:
2380 continue
2380 continue
2381 if o[0]:
2381 if o[0]:
2382 options.append(b'-%s' % o[0])
2382 options.append(b'-%s' % o[0])
2383 options.append(b'--%s' % o[1])
2383 options.append(b'--%s' % o[1])
2384 ui.write(b"%s\n" % b"\n".join(options))
2384 ui.write(b"%s\n" % b"\n".join(options))
2385 return
2385 return
2386
2386
2387 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2387 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2388 if ui.verbose:
2388 if ui.verbose:
2389 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2389 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2390 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2390 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2391
2391
2392
2392
2393 @command(
2393 @command(
2394 b'diff',
2394 b'diff',
2395 [
2395 [
2396 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
2396 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
2397 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2397 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2398 ]
2398 ]
2399 + diffopts
2399 + diffopts
2400 + diffopts2
2400 + diffopts2
2401 + walkopts
2401 + walkopts
2402 + subrepoopts,
2402 + subrepoopts,
2403 _(b'[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
2403 _(b'[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
2404 helpcategory=command.CATEGORY_FILE_CONTENTS,
2404 helpcategory=command.CATEGORY_FILE_CONTENTS,
2405 helpbasic=True,
2405 helpbasic=True,
2406 inferrepo=True,
2406 inferrepo=True,
2407 intents={INTENT_READONLY},
2407 intents={INTENT_READONLY},
2408 )
2408 )
2409 def diff(ui, repo, *pats, **opts):
2409 def diff(ui, repo, *pats, **opts):
2410 """diff repository (or selected files)
2410 """diff repository (or selected files)
2411
2411
2412 Show differences between revisions for the specified files.
2412 Show differences between revisions for the specified files.
2413
2413
2414 Differences between files are shown using the unified diff format.
2414 Differences between files are shown using the unified diff format.
2415
2415
2416 .. note::
2416 .. note::
2417
2417
2418 :hg:`diff` may generate unexpected results for merges, as it will
2418 :hg:`diff` may generate unexpected results for merges, as it will
2419 default to comparing against the working directory's first
2419 default to comparing against the working directory's first
2420 parent changeset if no revisions are specified.
2420 parent changeset if no revisions are specified.
2421
2421
2422 When two revision arguments are given, then changes are shown
2422 When two revision arguments are given, then changes are shown
2423 between those revisions. If only one revision is specified then
2423 between those revisions. If only one revision is specified then
2424 that revision is compared to the working directory, and, when no
2424 that revision is compared to the working directory, and, when no
2425 revisions are specified, the working directory files are compared
2425 revisions are specified, the working directory files are compared
2426 to its first parent.
2426 to its first parent.
2427
2427
2428 Alternatively you can specify -c/--change with a revision to see
2428 Alternatively you can specify -c/--change with a revision to see
2429 the changes in that changeset relative to its first parent.
2429 the changes in that changeset relative to its first parent.
2430
2430
2431 Without the -a/--text option, diff will avoid generating diffs of
2431 Without the -a/--text option, diff will avoid generating diffs of
2432 files it detects as binary. With -a, diff will generate a diff
2432 files it detects as binary. With -a, diff will generate a diff
2433 anyway, probably with undesirable results.
2433 anyway, probably with undesirable results.
2434
2434
2435 Use the -g/--git option to generate diffs in the git extended diff
2435 Use the -g/--git option to generate diffs in the git extended diff
2436 format. For more information, read :hg:`help diffs`.
2436 format. For more information, read :hg:`help diffs`.
2437
2437
2438 .. container:: verbose
2438 .. container:: verbose
2439
2439
2440 Examples:
2440 Examples:
2441
2441
2442 - compare a file in the current working directory to its parent::
2442 - compare a file in the current working directory to its parent::
2443
2443
2444 hg diff foo.c
2444 hg diff foo.c
2445
2445
2446 - compare two historical versions of a directory, with rename info::
2446 - compare two historical versions of a directory, with rename info::
2447
2447
2448 hg diff --git -r 1.0:1.2 lib/
2448 hg diff --git -r 1.0:1.2 lib/
2449
2449
2450 - get change stats relative to the last change on some date::
2450 - get change stats relative to the last change on some date::
2451
2451
2452 hg diff --stat -r "date('may 2')"
2452 hg diff --stat -r "date('may 2')"
2453
2453
2454 - diff all newly-added files that contain a keyword::
2454 - diff all newly-added files that contain a keyword::
2455
2455
2456 hg diff "set:added() and grep(GNU)"
2456 hg diff "set:added() and grep(GNU)"
2457
2457
2458 - compare a revision and its parents::
2458 - compare a revision and its parents::
2459
2459
2460 hg diff -c 9353 # compare against first parent
2460 hg diff -c 9353 # compare against first parent
2461 hg diff -r 9353^:9353 # same using revset syntax
2461 hg diff -r 9353^:9353 # same using revset syntax
2462 hg diff -r 9353^2:9353 # compare against the second parent
2462 hg diff -r 9353^2:9353 # compare against the second parent
2463
2463
2464 Returns 0 on success.
2464 Returns 0 on success.
2465 """
2465 """
2466
2466
2467 opts = pycompat.byteskwargs(opts)
2467 opts = pycompat.byteskwargs(opts)
2468 revs = opts.get(b'rev')
2468 revs = opts.get(b'rev')
2469 change = opts.get(b'change')
2469 change = opts.get(b'change')
2470 stat = opts.get(b'stat')
2470 stat = opts.get(b'stat')
2471 reverse = opts.get(b'reverse')
2471 reverse = opts.get(b'reverse')
2472
2472
2473 if revs and change:
2473 if revs and change:
2474 msg = _(b'cannot specify --rev and --change at the same time')
2474 msg = _(b'cannot specify --rev and --change at the same time')
2475 raise error.Abort(msg)
2475 raise error.Abort(msg)
2476 elif change:
2476 elif change:
2477 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2477 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2478 ctx2 = scmutil.revsingle(repo, change, None)
2478 ctx2 = scmutil.revsingle(repo, change, None)
2479 ctx1 = ctx2.p1()
2479 ctx1 = ctx2.p1()
2480 else:
2480 else:
2481 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2481 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2482 ctx1, ctx2 = scmutil.revpair(repo, revs)
2482 ctx1, ctx2 = scmutil.revpair(repo, revs)
2483 node1, node2 = ctx1.node(), ctx2.node()
2483 node1, node2 = ctx1.node(), ctx2.node()
2484
2484
2485 if reverse:
2485 if reverse:
2486 node1, node2 = node2, node1
2486 node1, node2 = node2, node1
2487
2487
2488 diffopts = patch.diffallopts(ui, opts)
2488 diffopts = patch.diffallopts(ui, opts)
2489 m = scmutil.match(ctx2, pats, opts)
2489 m = scmutil.match(ctx2, pats, opts)
2490 m = repo.narrowmatch(m)
2490 m = repo.narrowmatch(m)
2491 ui.pager(b'diff')
2491 ui.pager(b'diff')
2492 logcmdutil.diffordiffstat(
2492 logcmdutil.diffordiffstat(
2493 ui,
2493 ui,
2494 repo,
2494 repo,
2495 diffopts,
2495 diffopts,
2496 node1,
2496 node1,
2497 node2,
2497 node2,
2498 m,
2498 m,
2499 stat=stat,
2499 stat=stat,
2500 listsubrepos=opts.get(b'subrepos'),
2500 listsubrepos=opts.get(b'subrepos'),
2501 root=opts.get(b'root'),
2501 root=opts.get(b'root'),
2502 )
2502 )
2503
2503
2504
2504
2505 @command(
2505 @command(
2506 b'export',
2506 b'export',
2507 [
2507 [
2508 (
2508 (
2509 b'B',
2509 b'B',
2510 b'bookmark',
2510 b'bookmark',
2511 b'',
2511 b'',
2512 _(b'export changes only reachable by given bookmark'),
2512 _(b'export changes only reachable by given bookmark'),
2513 _(b'BOOKMARK'),
2513 _(b'BOOKMARK'),
2514 ),
2514 ),
2515 (
2515 (
2516 b'o',
2516 b'o',
2517 b'output',
2517 b'output',
2518 b'',
2518 b'',
2519 _(b'print output to file with formatted name'),
2519 _(b'print output to file with formatted name'),
2520 _(b'FORMAT'),
2520 _(b'FORMAT'),
2521 ),
2521 ),
2522 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2522 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2523 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2523 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2524 ]
2524 ]
2525 + diffopts
2525 + diffopts
2526 + formatteropts,
2526 + formatteropts,
2527 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2527 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2528 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2528 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2529 helpbasic=True,
2529 helpbasic=True,
2530 intents={INTENT_READONLY},
2530 intents={INTENT_READONLY},
2531 )
2531 )
2532 def export(ui, repo, *changesets, **opts):
2532 def export(ui, repo, *changesets, **opts):
2533 """dump the header and diffs for one or more changesets
2533 """dump the header and diffs for one or more changesets
2534
2534
2535 Print the changeset header and diffs for one or more revisions.
2535 Print the changeset header and diffs for one or more revisions.
2536 If no revision is given, the parent of the working directory is used.
2536 If no revision is given, the parent of the working directory is used.
2537
2537
2538 The information shown in the changeset header is: author, date,
2538 The information shown in the changeset header is: author, date,
2539 branch name (if non-default), changeset hash, parent(s) and commit
2539 branch name (if non-default), changeset hash, parent(s) and commit
2540 comment.
2540 comment.
2541
2541
2542 .. note::
2542 .. note::
2543
2543
2544 :hg:`export` may generate unexpected diff output for merge
2544 :hg:`export` may generate unexpected diff output for merge
2545 changesets, as it will compare the merge changeset against its
2545 changesets, as it will compare the merge changeset against its
2546 first parent only.
2546 first parent only.
2547
2547
2548 Output may be to a file, in which case the name of the file is
2548 Output may be to a file, in which case the name of the file is
2549 given using a template string. See :hg:`help templates`. In addition
2549 given using a template string. See :hg:`help templates`. In addition
2550 to the common template keywords, the following formatting rules are
2550 to the common template keywords, the following formatting rules are
2551 supported:
2551 supported:
2552
2552
2553 :``%%``: literal "%" character
2553 :``%%``: literal "%" character
2554 :``%H``: changeset hash (40 hexadecimal digits)
2554 :``%H``: changeset hash (40 hexadecimal digits)
2555 :``%N``: number of patches being generated
2555 :``%N``: number of patches being generated
2556 :``%R``: changeset revision number
2556 :``%R``: changeset revision number
2557 :``%b``: basename of the exporting repository
2557 :``%b``: basename of the exporting repository
2558 :``%h``: short-form changeset hash (12 hexadecimal digits)
2558 :``%h``: short-form changeset hash (12 hexadecimal digits)
2559 :``%m``: first line of the commit message (only alphanumeric characters)
2559 :``%m``: first line of the commit message (only alphanumeric characters)
2560 :``%n``: zero-padded sequence number, starting at 1
2560 :``%n``: zero-padded sequence number, starting at 1
2561 :``%r``: zero-padded changeset revision number
2561 :``%r``: zero-padded changeset revision number
2562 :``\\``: literal "\\" character
2562 :``\\``: literal "\\" character
2563
2563
2564 Without the -a/--text option, export will avoid generating diffs
2564 Without the -a/--text option, export will avoid generating diffs
2565 of files it detects as binary. With -a, export will generate a
2565 of files it detects as binary. With -a, export will generate a
2566 diff anyway, probably with undesirable results.
2566 diff anyway, probably with undesirable results.
2567
2567
2568 With -B/--bookmark changesets reachable by the given bookmark are
2568 With -B/--bookmark changesets reachable by the given bookmark are
2569 selected.
2569 selected.
2570
2570
2571 Use the -g/--git option to generate diffs in the git extended diff
2571 Use the -g/--git option to generate diffs in the git extended diff
2572 format. See :hg:`help diffs` for more information.
2572 format. See :hg:`help diffs` for more information.
2573
2573
2574 With the --switch-parent option, the diff will be against the
2574 With the --switch-parent option, the diff will be against the
2575 second parent. It can be useful to review a merge.
2575 second parent. It can be useful to review a merge.
2576
2576
2577 .. container:: verbose
2577 .. container:: verbose
2578
2578
2579 Template:
2579 Template:
2580
2580
2581 The following keywords are supported in addition to the common template
2581 The following keywords are supported in addition to the common template
2582 keywords and functions. See also :hg:`help templates`.
2582 keywords and functions. See also :hg:`help templates`.
2583
2583
2584 :diff: String. Diff content.
2584 :diff: String. Diff content.
2585 :parents: List of strings. Parent nodes of the changeset.
2585 :parents: List of strings. Parent nodes of the changeset.
2586
2586
2587 Examples:
2587 Examples:
2588
2588
2589 - use export and import to transplant a bugfix to the current
2589 - use export and import to transplant a bugfix to the current
2590 branch::
2590 branch::
2591
2591
2592 hg export -r 9353 | hg import -
2592 hg export -r 9353 | hg import -
2593
2593
2594 - export all the changesets between two revisions to a file with
2594 - export all the changesets between two revisions to a file with
2595 rename information::
2595 rename information::
2596
2596
2597 hg export --git -r 123:150 > changes.txt
2597 hg export --git -r 123:150 > changes.txt
2598
2598
2599 - split outgoing changes into a series of patches with
2599 - split outgoing changes into a series of patches with
2600 descriptive names::
2600 descriptive names::
2601
2601
2602 hg export -r "outgoing()" -o "%n-%m.patch"
2602 hg export -r "outgoing()" -o "%n-%m.patch"
2603
2603
2604 Returns 0 on success.
2604 Returns 0 on success.
2605 """
2605 """
2606 opts = pycompat.byteskwargs(opts)
2606 opts = pycompat.byteskwargs(opts)
2607 bookmark = opts.get(b'bookmark')
2607 bookmark = opts.get(b'bookmark')
2608 changesets += tuple(opts.get(b'rev', []))
2608 changesets += tuple(opts.get(b'rev', []))
2609
2609
2610 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2610 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2611
2611
2612 if bookmark:
2612 if bookmark:
2613 if bookmark not in repo._bookmarks:
2613 if bookmark not in repo._bookmarks:
2614 raise error.Abort(_(b"bookmark '%s' not found") % bookmark)
2614 raise error.Abort(_(b"bookmark '%s' not found") % bookmark)
2615
2615
2616 revs = scmutil.bookmarkrevs(repo, bookmark)
2616 revs = scmutil.bookmarkrevs(repo, bookmark)
2617 else:
2617 else:
2618 if not changesets:
2618 if not changesets:
2619 changesets = [b'.']
2619 changesets = [b'.']
2620
2620
2621 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2621 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2622 revs = scmutil.revrange(repo, changesets)
2622 revs = scmutil.revrange(repo, changesets)
2623
2623
2624 if not revs:
2624 if not revs:
2625 raise error.Abort(_(b"export requires at least one changeset"))
2625 raise error.Abort(_(b"export requires at least one changeset"))
2626 if len(revs) > 1:
2626 if len(revs) > 1:
2627 ui.note(_(b'exporting patches:\n'))
2627 ui.note(_(b'exporting patches:\n'))
2628 else:
2628 else:
2629 ui.note(_(b'exporting patch:\n'))
2629 ui.note(_(b'exporting patch:\n'))
2630
2630
2631 fntemplate = opts.get(b'output')
2631 fntemplate = opts.get(b'output')
2632 if cmdutil.isstdiofilename(fntemplate):
2632 if cmdutil.isstdiofilename(fntemplate):
2633 fntemplate = b''
2633 fntemplate = b''
2634
2634
2635 if fntemplate:
2635 if fntemplate:
2636 fm = formatter.nullformatter(ui, b'export', opts)
2636 fm = formatter.nullformatter(ui, b'export', opts)
2637 else:
2637 else:
2638 ui.pager(b'export')
2638 ui.pager(b'export')
2639 fm = ui.formatter(b'export', opts)
2639 fm = ui.formatter(b'export', opts)
2640 with fm:
2640 with fm:
2641 cmdutil.export(
2641 cmdutil.export(
2642 repo,
2642 repo,
2643 revs,
2643 revs,
2644 fm,
2644 fm,
2645 fntemplate=fntemplate,
2645 fntemplate=fntemplate,
2646 switch_parent=opts.get(b'switch_parent'),
2646 switch_parent=opts.get(b'switch_parent'),
2647 opts=patch.diffallopts(ui, opts),
2647 opts=patch.diffallopts(ui, opts),
2648 )
2648 )
2649
2649
2650
2650
2651 @command(
2651 @command(
2652 b'files',
2652 b'files',
2653 [
2653 [
2654 (
2654 (
2655 b'r',
2655 b'r',
2656 b'rev',
2656 b'rev',
2657 b'',
2657 b'',
2658 _(b'search the repository as it is in REV'),
2658 _(b'search the repository as it is in REV'),
2659 _(b'REV'),
2659 _(b'REV'),
2660 ),
2660 ),
2661 (
2661 (
2662 b'0',
2662 b'0',
2663 b'print0',
2663 b'print0',
2664 None,
2664 None,
2665 _(b'end filenames with NUL, for use with xargs'),
2665 _(b'end filenames with NUL, for use with xargs'),
2666 ),
2666 ),
2667 ]
2667 ]
2668 + walkopts
2668 + walkopts
2669 + formatteropts
2669 + formatteropts
2670 + subrepoopts,
2670 + subrepoopts,
2671 _(b'[OPTION]... [FILE]...'),
2671 _(b'[OPTION]... [FILE]...'),
2672 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2672 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2673 intents={INTENT_READONLY},
2673 intents={INTENT_READONLY},
2674 )
2674 )
2675 def files(ui, repo, *pats, **opts):
2675 def files(ui, repo, *pats, **opts):
2676 """list tracked files
2676 """list tracked files
2677
2677
2678 Print files under Mercurial control in the working directory or
2678 Print files under Mercurial control in the working directory or
2679 specified revision for given files (excluding removed files).
2679 specified revision for given files (excluding removed files).
2680 Files can be specified as filenames or filesets.
2680 Files can be specified as filenames or filesets.
2681
2681
2682 If no files are given to match, this command prints the names
2682 If no files are given to match, this command prints the names
2683 of all files under Mercurial control.
2683 of all files under Mercurial control.
2684
2684
2685 .. container:: verbose
2685 .. container:: verbose
2686
2686
2687 Template:
2687 Template:
2688
2688
2689 The following keywords are supported in addition to the common template
2689 The following keywords are supported in addition to the common template
2690 keywords and functions. See also :hg:`help templates`.
2690 keywords and functions. See also :hg:`help templates`.
2691
2691
2692 :flags: String. Character denoting file's symlink and executable bits.
2692 :flags: String. Character denoting file's symlink and executable bits.
2693 :path: String. Repository-absolute path of the file.
2693 :path: String. Repository-absolute path of the file.
2694 :size: Integer. Size of the file in bytes.
2694 :size: Integer. Size of the file in bytes.
2695
2695
2696 Examples:
2696 Examples:
2697
2697
2698 - list all files under the current directory::
2698 - list all files under the current directory::
2699
2699
2700 hg files .
2700 hg files .
2701
2701
2702 - shows sizes and flags for current revision::
2702 - shows sizes and flags for current revision::
2703
2703
2704 hg files -vr .
2704 hg files -vr .
2705
2705
2706 - list all files named README::
2706 - list all files named README::
2707
2707
2708 hg files -I "**/README"
2708 hg files -I "**/README"
2709
2709
2710 - list all binary files::
2710 - list all binary files::
2711
2711
2712 hg files "set:binary()"
2712 hg files "set:binary()"
2713
2713
2714 - find files containing a regular expression::
2714 - find files containing a regular expression::
2715
2715
2716 hg files "set:grep('bob')"
2716 hg files "set:grep('bob')"
2717
2717
2718 - search tracked file contents with xargs and grep::
2718 - search tracked file contents with xargs and grep::
2719
2719
2720 hg files -0 | xargs -0 grep foo
2720 hg files -0 | xargs -0 grep foo
2721
2721
2722 See :hg:`help patterns` and :hg:`help filesets` for more information
2722 See :hg:`help patterns` and :hg:`help filesets` for more information
2723 on specifying file patterns.
2723 on specifying file patterns.
2724
2724
2725 Returns 0 if a match is found, 1 otherwise.
2725 Returns 0 if a match is found, 1 otherwise.
2726
2726
2727 """
2727 """
2728
2728
2729 opts = pycompat.byteskwargs(opts)
2729 opts = pycompat.byteskwargs(opts)
2730 rev = opts.get(b'rev')
2730 rev = opts.get(b'rev')
2731 if rev:
2731 if rev:
2732 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2732 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2733 ctx = scmutil.revsingle(repo, rev, None)
2733 ctx = scmutil.revsingle(repo, rev, None)
2734
2734
2735 end = b'\n'
2735 end = b'\n'
2736 if opts.get(b'print0'):
2736 if opts.get(b'print0'):
2737 end = b'\0'
2737 end = b'\0'
2738 fmt = b'%s' + end
2738 fmt = b'%s' + end
2739
2739
2740 m = scmutil.match(ctx, pats, opts)
2740 m = scmutil.match(ctx, pats, opts)
2741 ui.pager(b'files')
2741 ui.pager(b'files')
2742 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2742 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2743 with ui.formatter(b'files', opts) as fm:
2743 with ui.formatter(b'files', opts) as fm:
2744 return cmdutil.files(
2744 return cmdutil.files(
2745 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2745 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2746 )
2746 )
2747
2747
2748
2748
2749 @command(
2749 @command(
2750 b'forget',
2750 b'forget',
2751 [(b'i', b'interactive', None, _(b'use interactive mode')),]
2751 [(b'i', b'interactive', None, _(b'use interactive mode')),]
2752 + walkopts
2752 + walkopts
2753 + dryrunopts,
2753 + dryrunopts,
2754 _(b'[OPTION]... FILE...'),
2754 _(b'[OPTION]... FILE...'),
2755 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2755 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2756 helpbasic=True,
2756 helpbasic=True,
2757 inferrepo=True,
2757 inferrepo=True,
2758 )
2758 )
2759 def forget(ui, repo, *pats, **opts):
2759 def forget(ui, repo, *pats, **opts):
2760 """forget the specified files on the next commit
2760 """forget the specified files on the next commit
2761
2761
2762 Mark the specified files so they will no longer be tracked
2762 Mark the specified files so they will no longer be tracked
2763 after the next commit.
2763 after the next commit.
2764
2764
2765 This only removes files from the current branch, not from the
2765 This only removes files from the current branch, not from the
2766 entire project history, and it does not delete them from the
2766 entire project history, and it does not delete them from the
2767 working directory.
2767 working directory.
2768
2768
2769 To delete the file from the working directory, see :hg:`remove`.
2769 To delete the file from the working directory, see :hg:`remove`.
2770
2770
2771 To undo a forget before the next commit, see :hg:`add`.
2771 To undo a forget before the next commit, see :hg:`add`.
2772
2772
2773 .. container:: verbose
2773 .. container:: verbose
2774
2774
2775 Examples:
2775 Examples:
2776
2776
2777 - forget newly-added binary files::
2777 - forget newly-added binary files::
2778
2778
2779 hg forget "set:added() and binary()"
2779 hg forget "set:added() and binary()"
2780
2780
2781 - forget files that would be excluded by .hgignore::
2781 - forget files that would be excluded by .hgignore::
2782
2782
2783 hg forget "set:hgignore()"
2783 hg forget "set:hgignore()"
2784
2784
2785 Returns 0 on success.
2785 Returns 0 on success.
2786 """
2786 """
2787
2787
2788 opts = pycompat.byteskwargs(opts)
2788 opts = pycompat.byteskwargs(opts)
2789 if not pats:
2789 if not pats:
2790 raise error.Abort(_(b'no files specified'))
2790 raise error.Abort(_(b'no files specified'))
2791
2791
2792 m = scmutil.match(repo[None], pats, opts)
2792 m = scmutil.match(repo[None], pats, opts)
2793 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2793 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2794 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2794 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2795 rejected = cmdutil.forget(
2795 rejected = cmdutil.forget(
2796 ui,
2796 ui,
2797 repo,
2797 repo,
2798 m,
2798 m,
2799 prefix=b"",
2799 prefix=b"",
2800 uipathfn=uipathfn,
2800 uipathfn=uipathfn,
2801 explicitonly=False,
2801 explicitonly=False,
2802 dryrun=dryrun,
2802 dryrun=dryrun,
2803 interactive=interactive,
2803 interactive=interactive,
2804 )[0]
2804 )[0]
2805 return rejected and 1 or 0
2805 return rejected and 1 or 0
2806
2806
2807
2807
2808 @command(
2808 @command(
2809 b'graft',
2809 b'graft',
2810 [
2810 [
2811 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2811 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2812 (
2812 (
2813 b'',
2813 b'',
2814 b'base',
2814 b'base',
2815 b'',
2815 b'',
2816 _(b'base revision when doing the graft merge (ADVANCED)'),
2816 _(b'base revision when doing the graft merge (ADVANCED)'),
2817 _(b'REV'),
2817 _(b'REV'),
2818 ),
2818 ),
2819 (b'c', b'continue', False, _(b'resume interrupted graft')),
2819 (b'c', b'continue', False, _(b'resume interrupted graft')),
2820 (b'', b'stop', False, _(b'stop interrupted graft')),
2820 (b'', b'stop', False, _(b'stop interrupted graft')),
2821 (b'', b'abort', False, _(b'abort interrupted graft')),
2821 (b'', b'abort', False, _(b'abort interrupted graft')),
2822 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2822 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2823 (b'', b'log', None, _(b'append graft info to log message')),
2823 (b'', b'log', None, _(b'append graft info to log message')),
2824 (
2824 (
2825 b'',
2825 b'',
2826 b'no-commit',
2826 b'no-commit',
2827 None,
2827 None,
2828 _(b"don't commit, just apply the changes in working directory"),
2828 _(b"don't commit, just apply the changes in working directory"),
2829 ),
2829 ),
2830 (b'f', b'force', False, _(b'force graft')),
2830 (b'f', b'force', False, _(b'force graft')),
2831 (
2831 (
2832 b'D',
2832 b'D',
2833 b'currentdate',
2833 b'currentdate',
2834 False,
2834 False,
2835 _(b'record the current date as commit date'),
2835 _(b'record the current date as commit date'),
2836 ),
2836 ),
2837 (
2837 (
2838 b'U',
2838 b'U',
2839 b'currentuser',
2839 b'currentuser',
2840 False,
2840 False,
2841 _(b'record the current user as committer'),
2841 _(b'record the current user as committer'),
2842 ),
2842 ),
2843 ]
2843 ]
2844 + commitopts2
2844 + commitopts2
2845 + mergetoolopts
2845 + mergetoolopts
2846 + dryrunopts,
2846 + dryrunopts,
2847 _(b'[OPTION]... [-r REV]... REV...'),
2847 _(b'[OPTION]... [-r REV]... REV...'),
2848 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2848 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2849 )
2849 )
2850 def graft(ui, repo, *revs, **opts):
2850 def graft(ui, repo, *revs, **opts):
2851 '''copy changes from other branches onto the current branch
2851 '''copy changes from other branches onto the current branch
2852
2852
2853 This command uses Mercurial's merge logic to copy individual
2853 This command uses Mercurial's merge logic to copy individual
2854 changes from other branches without merging branches in the
2854 changes from other branches without merging branches in the
2855 history graph. This is sometimes known as 'backporting' or
2855 history graph. This is sometimes known as 'backporting' or
2856 'cherry-picking'. By default, graft will copy user, date, and
2856 'cherry-picking'. By default, graft will copy user, date, and
2857 description from the source changesets.
2857 description from the source changesets.
2858
2858
2859 Changesets that are ancestors of the current revision, that have
2859 Changesets that are ancestors of the current revision, that have
2860 already been grafted, or that are merges will be skipped.
2860 already been grafted, or that are merges will be skipped.
2861
2861
2862 If --log is specified, log messages will have a comment appended
2862 If --log is specified, log messages will have a comment appended
2863 of the form::
2863 of the form::
2864
2864
2865 (grafted from CHANGESETHASH)
2865 (grafted from CHANGESETHASH)
2866
2866
2867 If --force is specified, revisions will be grafted even if they
2867 If --force is specified, revisions will be grafted even if they
2868 are already ancestors of, or have been grafted to, the destination.
2868 are already ancestors of, or have been grafted to, the destination.
2869 This is useful when the revisions have since been backed out.
2869 This is useful when the revisions have since been backed out.
2870
2870
2871 If a graft merge results in conflicts, the graft process is
2871 If a graft merge results in conflicts, the graft process is
2872 interrupted so that the current merge can be manually resolved.
2872 interrupted so that the current merge can be manually resolved.
2873 Once all conflicts are addressed, the graft process can be
2873 Once all conflicts are addressed, the graft process can be
2874 continued with the -c/--continue option.
2874 continued with the -c/--continue option.
2875
2875
2876 The -c/--continue option reapplies all the earlier options.
2876 The -c/--continue option reapplies all the earlier options.
2877
2877
2878 .. container:: verbose
2878 .. container:: verbose
2879
2879
2880 The --base option exposes more of how graft internally uses merge with a
2880 The --base option exposes more of how graft internally uses merge with a
2881 custom base revision. --base can be used to specify another ancestor than
2881 custom base revision. --base can be used to specify another ancestor than
2882 the first and only parent.
2882 the first and only parent.
2883
2883
2884 The command::
2884 The command::
2885
2885
2886 hg graft -r 345 --base 234
2886 hg graft -r 345 --base 234
2887
2887
2888 is thus pretty much the same as::
2888 is thus pretty much the same as::
2889
2889
2890 hg diff -r 234 -r 345 | hg import
2890 hg diff -r 234 -r 345 | hg import
2891
2891
2892 but using merge to resolve conflicts and track moved files.
2892 but using merge to resolve conflicts and track moved files.
2893
2893
2894 The result of a merge can thus be backported as a single commit by
2894 The result of a merge can thus be backported as a single commit by
2895 specifying one of the merge parents as base, and thus effectively
2895 specifying one of the merge parents as base, and thus effectively
2896 grafting the changes from the other side.
2896 grafting the changes from the other side.
2897
2897
2898 It is also possible to collapse multiple changesets and clean up history
2898 It is also possible to collapse multiple changesets and clean up history
2899 by specifying another ancestor as base, much like rebase --collapse
2899 by specifying another ancestor as base, much like rebase --collapse
2900 --keep.
2900 --keep.
2901
2901
2902 The commit message can be tweaked after the fact using commit --amend .
2902 The commit message can be tweaked after the fact using commit --amend .
2903
2903
2904 For using non-ancestors as the base to backout changes, see the backout
2904 For using non-ancestors as the base to backout changes, see the backout
2905 command and the hidden --parent option.
2905 command and the hidden --parent option.
2906
2906
2907 .. container:: verbose
2907 .. container:: verbose
2908
2908
2909 Examples:
2909 Examples:
2910
2910
2911 - copy a single change to the stable branch and edit its description::
2911 - copy a single change to the stable branch and edit its description::
2912
2912
2913 hg update stable
2913 hg update stable
2914 hg graft --edit 9393
2914 hg graft --edit 9393
2915
2915
2916 - graft a range of changesets with one exception, updating dates::
2916 - graft a range of changesets with one exception, updating dates::
2917
2917
2918 hg graft -D "2085::2093 and not 2091"
2918 hg graft -D "2085::2093 and not 2091"
2919
2919
2920 - continue a graft after resolving conflicts::
2920 - continue a graft after resolving conflicts::
2921
2921
2922 hg graft -c
2922 hg graft -c
2923
2923
2924 - show the source of a grafted changeset::
2924 - show the source of a grafted changeset::
2925
2925
2926 hg log --debug -r .
2926 hg log --debug -r .
2927
2927
2928 - show revisions sorted by date::
2928 - show revisions sorted by date::
2929
2929
2930 hg log -r "sort(all(), date)"
2930 hg log -r "sort(all(), date)"
2931
2931
2932 - backport the result of a merge as a single commit::
2932 - backport the result of a merge as a single commit::
2933
2933
2934 hg graft -r 123 --base 123^
2934 hg graft -r 123 --base 123^
2935
2935
2936 - land a feature branch as one changeset::
2936 - land a feature branch as one changeset::
2937
2937
2938 hg up -cr default
2938 hg up -cr default
2939 hg graft -r featureX --base "ancestor('featureX', 'default')"
2939 hg graft -r featureX --base "ancestor('featureX', 'default')"
2940
2940
2941 See :hg:`help revisions` for more about specifying revisions.
2941 See :hg:`help revisions` for more about specifying revisions.
2942
2942
2943 Returns 0 on successful completion.
2943 Returns 0 on successful completion.
2944 '''
2944 '''
2945 with repo.wlock():
2945 with repo.wlock():
2946 return _dograft(ui, repo, *revs, **opts)
2946 return _dograft(ui, repo, *revs, **opts)
2947
2947
2948
2948
2949 def _dograft(ui, repo, *revs, **opts):
2949 def _dograft(ui, repo, *revs, **opts):
2950 opts = pycompat.byteskwargs(opts)
2950 opts = pycompat.byteskwargs(opts)
2951 if revs and opts.get(b'rev'):
2951 if revs and opts.get(b'rev'):
2952 ui.warn(
2952 ui.warn(
2953 _(
2953 _(
2954 b'warning: inconsistent use of --rev might give unexpected '
2954 b'warning: inconsistent use of --rev might give unexpected '
2955 b'revision ordering!\n'
2955 b'revision ordering!\n'
2956 )
2956 )
2957 )
2957 )
2958
2958
2959 revs = list(revs)
2959 revs = list(revs)
2960 revs.extend(opts.get(b'rev'))
2960 revs.extend(opts.get(b'rev'))
2961 basectx = None
2961 basectx = None
2962 if opts.get(b'base'):
2962 if opts.get(b'base'):
2963 basectx = scmutil.revsingle(repo, opts[b'base'], None)
2963 basectx = scmutil.revsingle(repo, opts[b'base'], None)
2964 # a dict of data to be stored in state file
2964 # a dict of data to be stored in state file
2965 statedata = {}
2965 statedata = {}
2966 # list of new nodes created by ongoing graft
2966 # list of new nodes created by ongoing graft
2967 statedata[b'newnodes'] = []
2967 statedata[b'newnodes'] = []
2968
2968
2969 cmdutil.resolvecommitoptions(ui, opts)
2969 cmdutil.resolvecommitoptions(ui, opts)
2970
2970
2971 editor = cmdutil.getcommiteditor(
2971 editor = cmdutil.getcommiteditor(
2972 editform=b'graft', **pycompat.strkwargs(opts)
2972 editform=b'graft', **pycompat.strkwargs(opts)
2973 )
2973 )
2974
2974
2975 cont = False
2975 cont = False
2976 if opts.get(b'no_commit'):
2976 if opts.get(b'no_commit'):
2977 if opts.get(b'edit'):
2977 if opts.get(b'edit'):
2978 raise error.Abort(
2978 raise error.Abort(
2979 _(b"cannot specify --no-commit and --edit together")
2979 _(b"cannot specify --no-commit and --edit together")
2980 )
2980 )
2981 if opts.get(b'currentuser'):
2981 if opts.get(b'currentuser'):
2982 raise error.Abort(
2982 raise error.Abort(
2983 _(b"cannot specify --no-commit and --currentuser together")
2983 _(b"cannot specify --no-commit and --currentuser together")
2984 )
2984 )
2985 if opts.get(b'currentdate'):
2985 if opts.get(b'currentdate'):
2986 raise error.Abort(
2986 raise error.Abort(
2987 _(b"cannot specify --no-commit and --currentdate together")
2987 _(b"cannot specify --no-commit and --currentdate together")
2988 )
2988 )
2989 if opts.get(b'log'):
2989 if opts.get(b'log'):
2990 raise error.Abort(
2990 raise error.Abort(
2991 _(b"cannot specify --no-commit and --log together")
2991 _(b"cannot specify --no-commit and --log together")
2992 )
2992 )
2993
2993
2994 graftstate = statemod.cmdstate(repo, b'graftstate')
2994 graftstate = statemod.cmdstate(repo, b'graftstate')
2995
2995
2996 if opts.get(b'stop'):
2996 if opts.get(b'stop'):
2997 if opts.get(b'continue'):
2997 if opts.get(b'continue'):
2998 raise error.Abort(
2998 raise error.Abort(
2999 _(b"cannot use '--continue' and '--stop' together")
2999 _(b"cannot use '--continue' and '--stop' together")
3000 )
3000 )
3001 if opts.get(b'abort'):
3001 if opts.get(b'abort'):
3002 raise error.Abort(_(b"cannot use '--abort' and '--stop' together"))
3002 raise error.Abort(_(b"cannot use '--abort' and '--stop' together"))
3003
3003
3004 if any(
3004 if any(
3005 (
3005 (
3006 opts.get(b'edit'),
3006 opts.get(b'edit'),
3007 opts.get(b'log'),
3007 opts.get(b'log'),
3008 opts.get(b'user'),
3008 opts.get(b'user'),
3009 opts.get(b'date'),
3009 opts.get(b'date'),
3010 opts.get(b'currentdate'),
3010 opts.get(b'currentdate'),
3011 opts.get(b'currentuser'),
3011 opts.get(b'currentuser'),
3012 opts.get(b'rev'),
3012 opts.get(b'rev'),
3013 )
3013 )
3014 ):
3014 ):
3015 raise error.Abort(_(b"cannot specify any other flag with '--stop'"))
3015 raise error.Abort(_(b"cannot specify any other flag with '--stop'"))
3016 return _stopgraft(ui, repo, graftstate)
3016 return _stopgraft(ui, repo, graftstate)
3017 elif opts.get(b'abort'):
3017 elif opts.get(b'abort'):
3018 if opts.get(b'continue'):
3018 if opts.get(b'continue'):
3019 raise error.Abort(
3019 raise error.Abort(
3020 _(b"cannot use '--continue' and '--abort' together")
3020 _(b"cannot use '--continue' and '--abort' together")
3021 )
3021 )
3022 if any(
3022 if any(
3023 (
3023 (
3024 opts.get(b'edit'),
3024 opts.get(b'edit'),
3025 opts.get(b'log'),
3025 opts.get(b'log'),
3026 opts.get(b'user'),
3026 opts.get(b'user'),
3027 opts.get(b'date'),
3027 opts.get(b'date'),
3028 opts.get(b'currentdate'),
3028 opts.get(b'currentdate'),
3029 opts.get(b'currentuser'),
3029 opts.get(b'currentuser'),
3030 opts.get(b'rev'),
3030 opts.get(b'rev'),
3031 )
3031 )
3032 ):
3032 ):
3033 raise error.Abort(
3033 raise error.Abort(
3034 _(b"cannot specify any other flag with '--abort'")
3034 _(b"cannot specify any other flag with '--abort'")
3035 )
3035 )
3036
3036
3037 return cmdutil.abortgraft(ui, repo, graftstate)
3037 return cmdutil.abortgraft(ui, repo, graftstate)
3038 elif opts.get(b'continue'):
3038 elif opts.get(b'continue'):
3039 cont = True
3039 cont = True
3040 if revs:
3040 if revs:
3041 raise error.Abort(_(b"can't specify --continue and revisions"))
3041 raise error.Abort(_(b"can't specify --continue and revisions"))
3042 # read in unfinished revisions
3042 # read in unfinished revisions
3043 if graftstate.exists():
3043 if graftstate.exists():
3044 statedata = cmdutil.readgraftstate(repo, graftstate)
3044 statedata = cmdutil.readgraftstate(repo, graftstate)
3045 if statedata.get(b'date'):
3045 if statedata.get(b'date'):
3046 opts[b'date'] = statedata[b'date']
3046 opts[b'date'] = statedata[b'date']
3047 if statedata.get(b'user'):
3047 if statedata.get(b'user'):
3048 opts[b'user'] = statedata[b'user']
3048 opts[b'user'] = statedata[b'user']
3049 if statedata.get(b'log'):
3049 if statedata.get(b'log'):
3050 opts[b'log'] = True
3050 opts[b'log'] = True
3051 if statedata.get(b'no_commit'):
3051 if statedata.get(b'no_commit'):
3052 opts[b'no_commit'] = statedata.get(b'no_commit')
3052 opts[b'no_commit'] = statedata.get(b'no_commit')
3053 nodes = statedata[b'nodes']
3053 nodes = statedata[b'nodes']
3054 revs = [repo[node].rev() for node in nodes]
3054 revs = [repo[node].rev() for node in nodes]
3055 else:
3055 else:
3056 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3056 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3057 else:
3057 else:
3058 if not revs:
3058 if not revs:
3059 raise error.Abort(_(b'no revisions specified'))
3059 raise error.Abort(_(b'no revisions specified'))
3060 cmdutil.checkunfinished(repo)
3060 cmdutil.checkunfinished(repo)
3061 cmdutil.bailifchanged(repo)
3061 cmdutil.bailifchanged(repo)
3062 revs = scmutil.revrange(repo, revs)
3062 revs = scmutil.revrange(repo, revs)
3063
3063
3064 skipped = set()
3064 skipped = set()
3065 if basectx is None:
3065 if basectx is None:
3066 # check for merges
3066 # check for merges
3067 for rev in repo.revs(b'%ld and merge()', revs):
3067 for rev in repo.revs(b'%ld and merge()', revs):
3068 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3068 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3069 skipped.add(rev)
3069 skipped.add(rev)
3070 revs = [r for r in revs if r not in skipped]
3070 revs = [r for r in revs if r not in skipped]
3071 if not revs:
3071 if not revs:
3072 return -1
3072 return -1
3073 if basectx is not None and len(revs) != 1:
3073 if basectx is not None and len(revs) != 1:
3074 raise error.Abort(_(b'only one revision allowed with --base '))
3074 raise error.Abort(_(b'only one revision allowed with --base '))
3075
3075
3076 # Don't check in the --continue case, in effect retaining --force across
3076 # Don't check in the --continue case, in effect retaining --force across
3077 # --continues. That's because without --force, any revisions we decided to
3077 # --continues. That's because without --force, any revisions we decided to
3078 # skip would have been filtered out here, so they wouldn't have made their
3078 # skip would have been filtered out here, so they wouldn't have made their
3079 # way to the graftstate. With --force, any revisions we would have otherwise
3079 # way to the graftstate. With --force, any revisions we would have otherwise
3080 # skipped would not have been filtered out, and if they hadn't been applied
3080 # skipped would not have been filtered out, and if they hadn't been applied
3081 # already, they'd have been in the graftstate.
3081 # already, they'd have been in the graftstate.
3082 if not (cont or opts.get(b'force')) and basectx is None:
3082 if not (cont or opts.get(b'force')) and basectx is None:
3083 # check for ancestors of dest branch
3083 # check for ancestors of dest branch
3084 ancestors = repo.revs(b'%ld & (::.)', revs)
3084 ancestors = repo.revs(b'%ld & (::.)', revs)
3085 for rev in ancestors:
3085 for rev in ancestors:
3086 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3086 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3087
3087
3088 revs = [r for r in revs if r not in ancestors]
3088 revs = [r for r in revs if r not in ancestors]
3089
3089
3090 if not revs:
3090 if not revs:
3091 return -1
3091 return -1
3092
3092
3093 # analyze revs for earlier grafts
3093 # analyze revs for earlier grafts
3094 ids = {}
3094 ids = {}
3095 for ctx in repo.set(b"%ld", revs):
3095 for ctx in repo.set(b"%ld", revs):
3096 ids[ctx.hex()] = ctx.rev()
3096 ids[ctx.hex()] = ctx.rev()
3097 n = ctx.extra().get(b'source')
3097 n = ctx.extra().get(b'source')
3098 if n:
3098 if n:
3099 ids[n] = ctx.rev()
3099 ids[n] = ctx.rev()
3100
3100
3101 # check ancestors for earlier grafts
3101 # check ancestors for earlier grafts
3102 ui.debug(b'scanning for duplicate grafts\n')
3102 ui.debug(b'scanning for duplicate grafts\n')
3103
3103
3104 # The only changesets we can be sure doesn't contain grafts of any
3104 # The only changesets we can be sure doesn't contain grafts of any
3105 # revs, are the ones that are common ancestors of *all* revs:
3105 # revs, are the ones that are common ancestors of *all* revs:
3106 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3106 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3107 ctx = repo[rev]
3107 ctx = repo[rev]
3108 n = ctx.extra().get(b'source')
3108 n = ctx.extra().get(b'source')
3109 if n in ids:
3109 if n in ids:
3110 try:
3110 try:
3111 r = repo[n].rev()
3111 r = repo[n].rev()
3112 except error.RepoLookupError:
3112 except error.RepoLookupError:
3113 r = None
3113 r = None
3114 if r in revs:
3114 if r in revs:
3115 ui.warn(
3115 ui.warn(
3116 _(
3116 _(
3117 b'skipping revision %d:%s '
3117 b'skipping revision %d:%s '
3118 b'(already grafted to %d:%s)\n'
3118 b'(already grafted to %d:%s)\n'
3119 )
3119 )
3120 % (r, repo[r], rev, ctx)
3120 % (r, repo[r], rev, ctx)
3121 )
3121 )
3122 revs.remove(r)
3122 revs.remove(r)
3123 elif ids[n] in revs:
3123 elif ids[n] in revs:
3124 if r is None:
3124 if r is None:
3125 ui.warn(
3125 ui.warn(
3126 _(
3126 _(
3127 b'skipping already grafted revision %d:%s '
3127 b'skipping already grafted revision %d:%s '
3128 b'(%d:%s also has unknown origin %s)\n'
3128 b'(%d:%s also has unknown origin %s)\n'
3129 )
3129 )
3130 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3130 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3131 )
3131 )
3132 else:
3132 else:
3133 ui.warn(
3133 ui.warn(
3134 _(
3134 _(
3135 b'skipping already grafted revision %d:%s '
3135 b'skipping already grafted revision %d:%s '
3136 b'(%d:%s also has origin %d:%s)\n'
3136 b'(%d:%s also has origin %d:%s)\n'
3137 )
3137 )
3138 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3138 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3139 )
3139 )
3140 revs.remove(ids[n])
3140 revs.remove(ids[n])
3141 elif ctx.hex() in ids:
3141 elif ctx.hex() in ids:
3142 r = ids[ctx.hex()]
3142 r = ids[ctx.hex()]
3143 if r in revs:
3143 if r in revs:
3144 ui.warn(
3144 ui.warn(
3145 _(
3145 _(
3146 b'skipping already grafted revision %d:%s '
3146 b'skipping already grafted revision %d:%s '
3147 b'(was grafted from %d:%s)\n'
3147 b'(was grafted from %d:%s)\n'
3148 )
3148 )
3149 % (r, repo[r], rev, ctx)
3149 % (r, repo[r], rev, ctx)
3150 )
3150 )
3151 revs.remove(r)
3151 revs.remove(r)
3152 if not revs:
3152 if not revs:
3153 return -1
3153 return -1
3154
3154
3155 if opts.get(b'no_commit'):
3155 if opts.get(b'no_commit'):
3156 statedata[b'no_commit'] = True
3156 statedata[b'no_commit'] = True
3157 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3157 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3158 desc = b'%d:%s "%s"' % (
3158 desc = b'%d:%s "%s"' % (
3159 ctx.rev(),
3159 ctx.rev(),
3160 ctx,
3160 ctx,
3161 ctx.description().split(b'\n', 1)[0],
3161 ctx.description().split(b'\n', 1)[0],
3162 )
3162 )
3163 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3163 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3164 if names:
3164 if names:
3165 desc += b' (%s)' % b' '.join(names)
3165 desc += b' (%s)' % b' '.join(names)
3166 ui.status(_(b'grafting %s\n') % desc)
3166 ui.status(_(b'grafting %s\n') % desc)
3167 if opts.get(b'dry_run'):
3167 if opts.get(b'dry_run'):
3168 continue
3168 continue
3169
3169
3170 source = ctx.extra().get(b'source')
3170 source = ctx.extra().get(b'source')
3171 extra = {}
3171 extra = {}
3172 if source:
3172 if source:
3173 extra[b'source'] = source
3173 extra[b'source'] = source
3174 extra[b'intermediate-source'] = ctx.hex()
3174 extra[b'intermediate-source'] = ctx.hex()
3175 else:
3175 else:
3176 extra[b'source'] = ctx.hex()
3176 extra[b'source'] = ctx.hex()
3177 user = ctx.user()
3177 user = ctx.user()
3178 if opts.get(b'user'):
3178 if opts.get(b'user'):
3179 user = opts[b'user']
3179 user = opts[b'user']
3180 statedata[b'user'] = user
3180 statedata[b'user'] = user
3181 date = ctx.date()
3181 date = ctx.date()
3182 if opts.get(b'date'):
3182 if opts.get(b'date'):
3183 date = opts[b'date']
3183 date = opts[b'date']
3184 statedata[b'date'] = date
3184 statedata[b'date'] = date
3185 message = ctx.description()
3185 message = ctx.description()
3186 if opts.get(b'log'):
3186 if opts.get(b'log'):
3187 message += b'\n(grafted from %s)' % ctx.hex()
3187 message += b'\n(grafted from %s)' % ctx.hex()
3188 statedata[b'log'] = True
3188 statedata[b'log'] = True
3189
3189
3190 # we don't merge the first commit when continuing
3190 # we don't merge the first commit when continuing
3191 if not cont:
3191 if not cont:
3192 # perform the graft merge with p1(rev) as 'ancestor'
3192 # perform the graft merge with p1(rev) as 'ancestor'
3193 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
3193 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
3194 base = ctx.p1() if basectx is None else basectx
3194 base = ctx.p1() if basectx is None else basectx
3195 with ui.configoverride(overrides, b'graft'):
3195 with ui.configoverride(overrides, b'graft'):
3196 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3196 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3197 # report any conflicts
3197 # report any conflicts
3198 if stats.unresolvedcount > 0:
3198 if stats.unresolvedcount > 0:
3199 # write out state for --continue
3199 # write out state for --continue
3200 nodes = [repo[rev].hex() for rev in revs[pos:]]
3200 nodes = [repo[rev].hex() for rev in revs[pos:]]
3201 statedata[b'nodes'] = nodes
3201 statedata[b'nodes'] = nodes
3202 stateversion = 1
3202 stateversion = 1
3203 graftstate.save(stateversion, statedata)
3203 graftstate.save(stateversion, statedata)
3204 hint = _(b"use 'hg resolve' and 'hg graft --continue'")
3204 hint = _(b"use 'hg resolve' and 'hg graft --continue'")
3205 raise error.Abort(
3205 raise error.Abort(
3206 _(b"unresolved conflicts, can't continue"), hint=hint
3206 _(b"unresolved conflicts, can't continue"), hint=hint
3207 )
3207 )
3208 else:
3208 else:
3209 cont = False
3209 cont = False
3210
3210
3211 # commit if --no-commit is false
3211 # commit if --no-commit is false
3212 if not opts.get(b'no_commit'):
3212 if not opts.get(b'no_commit'):
3213 node = repo.commit(
3213 node = repo.commit(
3214 text=message, user=user, date=date, extra=extra, editor=editor
3214 text=message, user=user, date=date, extra=extra, editor=editor
3215 )
3215 )
3216 if node is None:
3216 if node is None:
3217 ui.warn(
3217 ui.warn(
3218 _(b'note: graft of %d:%s created no changes to commit\n')
3218 _(b'note: graft of %d:%s created no changes to commit\n')
3219 % (ctx.rev(), ctx)
3219 % (ctx.rev(), ctx)
3220 )
3220 )
3221 # checking that newnodes exist because old state files won't have it
3221 # checking that newnodes exist because old state files won't have it
3222 elif statedata.get(b'newnodes') is not None:
3222 elif statedata.get(b'newnodes') is not None:
3223 statedata[b'newnodes'].append(node)
3223 statedata[b'newnodes'].append(node)
3224
3224
3225 # remove state when we complete successfully
3225 # remove state when we complete successfully
3226 if not opts.get(b'dry_run'):
3226 if not opts.get(b'dry_run'):
3227 graftstate.delete()
3227 graftstate.delete()
3228
3228
3229 return 0
3229 return 0
3230
3230
3231
3231
3232 def _stopgraft(ui, repo, graftstate):
3232 def _stopgraft(ui, repo, graftstate):
3233 """stop the interrupted graft"""
3233 """stop the interrupted graft"""
3234 if not graftstate.exists():
3234 if not graftstate.exists():
3235 raise error.Abort(_(b"no interrupted graft found"))
3235 raise error.Abort(_(b"no interrupted graft found"))
3236 pctx = repo[b'.']
3236 pctx = repo[b'.']
3237 hg.updaterepo(repo, pctx.node(), overwrite=True)
3237 hg.updaterepo(repo, pctx.node(), overwrite=True)
3238 graftstate.delete()
3238 graftstate.delete()
3239 ui.status(_(b"stopped the interrupted graft\n"))
3239 ui.status(_(b"stopped the interrupted graft\n"))
3240 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3240 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3241 return 0
3241 return 0
3242
3242
3243
3243
3244 statemod.addunfinished(
3244 statemod.addunfinished(
3245 b'graft',
3245 b'graft',
3246 fname=b'graftstate',
3246 fname=b'graftstate',
3247 clearable=True,
3247 clearable=True,
3248 stopflag=True,
3248 stopflag=True,
3249 continueflag=True,
3249 continueflag=True,
3250 abortfunc=cmdutil.hgabortgraft,
3250 abortfunc=cmdutil.hgabortgraft,
3251 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3251 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3252 )
3252 )
3253
3253
3254
3254
3255 @command(
3255 @command(
3256 b'grep',
3256 b'grep',
3257 [
3257 [
3258 (b'0', b'print0', None, _(b'end fields with NUL')),
3258 (b'0', b'print0', None, _(b'end fields with NUL')),
3259 (b'', b'all', None, _(b'print all revisions that match (DEPRECATED) ')),
3259 (b'', b'all', None, _(b'print all revisions that match (DEPRECATED) ')),
3260 (
3260 (
3261 b'',
3261 b'',
3262 b'diff',
3262 b'diff',
3263 None,
3263 None,
3264 _(
3264 _(
3265 b'search revision differences for when the pattern was added '
3265 b'search revision differences for when the pattern was added '
3266 b'or removed'
3266 b'or removed'
3267 ),
3267 ),
3268 ),
3268 ),
3269 (b'a', b'text', None, _(b'treat all files as text')),
3269 (b'a', b'text', None, _(b'treat all files as text')),
3270 (
3270 (
3271 b'f',
3271 b'f',
3272 b'follow',
3272 b'follow',
3273 None,
3273 None,
3274 _(
3274 _(
3275 b'follow changeset history,'
3275 b'follow changeset history,'
3276 b' or file history across copies and renames'
3276 b' or file history across copies and renames'
3277 ),
3277 ),
3278 ),
3278 ),
3279 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3279 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3280 (
3280 (
3281 b'l',
3281 b'l',
3282 b'files-with-matches',
3282 b'files-with-matches',
3283 None,
3283 None,
3284 _(b'print only filenames and revisions that match'),
3284 _(b'print only filenames and revisions that match'),
3285 ),
3285 ),
3286 (b'n', b'line-number', None, _(b'print matching line numbers')),
3286 (b'n', b'line-number', None, _(b'print matching line numbers')),
3287 (
3287 (
3288 b'r',
3288 b'r',
3289 b'rev',
3289 b'rev',
3290 [],
3290 [],
3291 _(b'search files changed within revision range'),
3291 _(b'search files changed within revision range'),
3292 _(b'REV'),
3292 _(b'REV'),
3293 ),
3293 ),
3294 (
3294 (
3295 b'',
3295 b'',
3296 b'all-files',
3296 b'all-files',
3297 None,
3297 None,
3298 _(
3298 _(
3299 b'include all files in the changeset while grepping (DEPRECATED)'
3299 b'include all files in the changeset while grepping (DEPRECATED)'
3300 ),
3300 ),
3301 ),
3301 ),
3302 (b'u', b'user', None, _(b'list the author (long with -v)')),
3302 (b'u', b'user', None, _(b'list the author (long with -v)')),
3303 (b'd', b'date', None, _(b'list the date (short with -q)')),
3303 (b'd', b'date', None, _(b'list the date (short with -q)')),
3304 ]
3304 ]
3305 + formatteropts
3305 + formatteropts
3306 + walkopts,
3306 + walkopts,
3307 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3307 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3308 helpcategory=command.CATEGORY_FILE_CONTENTS,
3308 helpcategory=command.CATEGORY_FILE_CONTENTS,
3309 inferrepo=True,
3309 inferrepo=True,
3310 intents={INTENT_READONLY},
3310 intents={INTENT_READONLY},
3311 )
3311 )
3312 def grep(ui, repo, pattern, *pats, **opts):
3312 def grep(ui, repo, pattern, *pats, **opts):
3313 """search for a pattern in specified files
3313 """search for a pattern in specified files
3314
3314
3315 Search the working directory or revision history for a regular
3315 Search the working directory or revision history for a regular
3316 expression in the specified files for the entire repository.
3316 expression in the specified files for the entire repository.
3317
3317
3318 By default, grep searches the repository files in the working
3318 By default, grep searches the repository files in the working
3319 directory and prints the files where it finds a match. To specify
3319 directory and prints the files where it finds a match. To specify
3320 historical revisions instead of the working directory, use the
3320 historical revisions instead of the working directory, use the
3321 --rev flag.
3321 --rev flag.
3322
3322
3323 To search instead historical revision differences that contains a
3323 To search instead historical revision differences that contains a
3324 change in match status ("-" for a match that becomes a non-match,
3324 change in match status ("-" for a match that becomes a non-match,
3325 or "+" for a non-match that becomes a match), use the --diff flag.
3325 or "+" for a non-match that becomes a match), use the --diff flag.
3326
3326
3327 PATTERN can be any Python (roughly Perl-compatible) regular
3327 PATTERN can be any Python (roughly Perl-compatible) regular
3328 expression.
3328 expression.
3329
3329
3330 If no FILEs are specified and the --rev flag isn't supplied, all
3330 If no FILEs are specified and the --rev flag isn't supplied, all
3331 files in the working directory are searched. When using the --rev
3331 files in the working directory are searched. When using the --rev
3332 flag and specifying FILEs, use the --follow argument to also
3332 flag and specifying FILEs, use the --follow argument to also
3333 follow the specified FILEs across renames and copies.
3333 follow the specified FILEs across renames and copies.
3334
3334
3335 .. container:: verbose
3335 .. container:: verbose
3336
3336
3337 Template:
3337 Template:
3338
3338
3339 The following keywords are supported in addition to the common template
3339 The following keywords are supported in addition to the common template
3340 keywords and functions. See also :hg:`help templates`.
3340 keywords and functions. See also :hg:`help templates`.
3341
3341
3342 :change: String. Character denoting insertion ``+`` or removal ``-``.
3342 :change: String. Character denoting insertion ``+`` or removal ``-``.
3343 Available if ``--diff`` is specified.
3343 Available if ``--diff`` is specified.
3344 :lineno: Integer. Line number of the match.
3344 :lineno: Integer. Line number of the match.
3345 :path: String. Repository-absolute path of the file.
3345 :path: String. Repository-absolute path of the file.
3346 :texts: List of text chunks.
3346 :texts: List of text chunks.
3347
3347
3348 And each entry of ``{texts}`` provides the following sub-keywords.
3348 And each entry of ``{texts}`` provides the following sub-keywords.
3349
3349
3350 :matched: Boolean. True if the chunk matches the specified pattern.
3350 :matched: Boolean. True if the chunk matches the specified pattern.
3351 :text: String. Chunk content.
3351 :text: String. Chunk content.
3352
3352
3353 See :hg:`help templates.operators` for the list expansion syntax.
3353 See :hg:`help templates.operators` for the list expansion syntax.
3354
3354
3355 Returns 0 if a match is found, 1 otherwise.
3355 Returns 0 if a match is found, 1 otherwise.
3356
3356
3357 """
3357 """
3358 opts = pycompat.byteskwargs(opts)
3358 opts = pycompat.byteskwargs(opts)
3359 diff = opts.get(b'all') or opts.get(b'diff')
3359 diff = opts.get(b'all') or opts.get(b'diff')
3360 if diff and opts.get(b'all_files'):
3360 if diff and opts.get(b'all_files'):
3361 raise error.Abort(_(b'--diff and --all-files are mutually exclusive'))
3361 raise error.Abort(_(b'--diff and --all-files are mutually exclusive'))
3362 if opts.get(b'all_files') is None and not diff:
3362 if opts.get(b'all_files') is None and not diff:
3363 opts[b'all_files'] = True
3363 opts[b'all_files'] = True
3364 plaingrep = opts.get(b'all_files') and not opts.get(b'rev')
3364 plaingrep = opts.get(b'all_files') and not opts.get(b'rev')
3365 all_files = opts.get(b'all_files')
3365 all_files = opts.get(b'all_files')
3366 if plaingrep:
3366 if plaingrep:
3367 opts[b'rev'] = [b'wdir()']
3367 opts[b'rev'] = [b'wdir()']
3368
3368
3369 reflags = re.M
3369 reflags = re.M
3370 if opts.get(b'ignore_case'):
3370 if opts.get(b'ignore_case'):
3371 reflags |= re.I
3371 reflags |= re.I
3372 try:
3372 try:
3373 regexp = util.re.compile(pattern, reflags)
3373 regexp = util.re.compile(pattern, reflags)
3374 except re.error as inst:
3374 except re.error as inst:
3375 ui.warn(
3375 ui.warn(
3376 _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
3376 _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
3377 )
3377 )
3378 return 1
3378 return 1
3379 sep, eol = b':', b'\n'
3379 sep, eol = b':', b'\n'
3380 if opts.get(b'print0'):
3380 if opts.get(b'print0'):
3381 sep = eol = b'\0'
3381 sep = eol = b'\0'
3382
3382
3383 getfile = util.lrucachefunc(repo.file)
3383 getfile = util.lrucachefunc(repo.file)
3384
3384
3385 def matchlines(body):
3385 def matchlines(body):
3386 begin = 0
3386 begin = 0
3387 linenum = 0
3387 linenum = 0
3388 while begin < len(body):
3388 while begin < len(body):
3389 match = regexp.search(body, begin)
3389 match = regexp.search(body, begin)
3390 if not match:
3390 if not match:
3391 break
3391 break
3392 mstart, mend = match.span()
3392 mstart, mend = match.span()
3393 linenum += body.count(b'\n', begin, mstart) + 1
3393 linenum += body.count(b'\n', begin, mstart) + 1
3394 lstart = body.rfind(b'\n', begin, mstart) + 1 or begin
3394 lstart = body.rfind(b'\n', begin, mstart) + 1 or begin
3395 begin = body.find(b'\n', mend) + 1 or len(body) + 1
3395 begin = body.find(b'\n', mend) + 1 or len(body) + 1
3396 lend = begin - 1
3396 lend = begin - 1
3397 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3397 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3398
3398
3399 class linestate(object):
3399 class linestate(object):
3400 def __init__(self, line, linenum, colstart, colend):
3400 def __init__(self, line, linenum, colstart, colend):
3401 self.line = line
3401 self.line = line
3402 self.linenum = linenum
3402 self.linenum = linenum
3403 self.colstart = colstart
3403 self.colstart = colstart
3404 self.colend = colend
3404 self.colend = colend
3405
3405
3406 def __hash__(self):
3406 def __hash__(self):
3407 return hash((self.linenum, self.line))
3407 return hash((self.linenum, self.line))
3408
3408
3409 def __eq__(self, other):
3409 def __eq__(self, other):
3410 return self.line == other.line
3410 return self.line == other.line
3411
3411
3412 def findpos(self):
3412 def findpos(self):
3413 """Iterate all (start, end) indices of matches"""
3413 """Iterate all (start, end) indices of matches"""
3414 yield self.colstart, self.colend
3414 yield self.colstart, self.colend
3415 p = self.colend
3415 p = self.colend
3416 while p < len(self.line):
3416 while p < len(self.line):
3417 m = regexp.search(self.line, p)
3417 m = regexp.search(self.line, p)
3418 if not m:
3418 if not m:
3419 break
3419 break
3420 yield m.span()
3420 yield m.span()
3421 p = m.end()
3421 p = m.end()
3422
3422
3423 matches = {}
3423 matches = {}
3424 copies = {}
3424 copies = {}
3425
3425
3426 def grepbody(fn, rev, body):
3426 def grepbody(fn, rev, body):
3427 matches[rev].setdefault(fn, [])
3427 matches[rev].setdefault(fn, [])
3428 m = matches[rev][fn]
3428 m = matches[rev][fn]
3429 if body is None:
3429 if body is None:
3430 return
3430 return
3431
3431
3432 for lnum, cstart, cend, line in matchlines(body):
3432 for lnum, cstart, cend, line in matchlines(body):
3433 s = linestate(line, lnum, cstart, cend)
3433 s = linestate(line, lnum, cstart, cend)
3434 m.append(s)
3434 m.append(s)
3435
3435
3436 def difflinestates(a, b):
3436 def difflinestates(a, b):
3437 sm = difflib.SequenceMatcher(None, a, b)
3437 sm = difflib.SequenceMatcher(None, a, b)
3438 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3438 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3439 if tag == 'insert':
3439 if tag == 'insert':
3440 for i in pycompat.xrange(blo, bhi):
3440 for i in pycompat.xrange(blo, bhi):
3441 yield (b'+', b[i])
3441 yield (b'+', b[i])
3442 elif tag == 'delete':
3442 elif tag == 'delete':
3443 for i in pycompat.xrange(alo, ahi):
3443 for i in pycompat.xrange(alo, ahi):
3444 yield (b'-', a[i])
3444 yield (b'-', a[i])
3445 elif tag == 'replace':
3445 elif tag == 'replace':
3446 for i in pycompat.xrange(alo, ahi):
3446 for i in pycompat.xrange(alo, ahi):
3447 yield (b'-', a[i])
3447 yield (b'-', a[i])
3448 for i in pycompat.xrange(blo, bhi):
3448 for i in pycompat.xrange(blo, bhi):
3449 yield (b'+', b[i])
3449 yield (b'+', b[i])
3450
3450
3451 uipathfn = scmutil.getuipathfn(repo)
3451 uipathfn = scmutil.getuipathfn(repo)
3452
3452
3453 def display(fm, fn, ctx, pstates, states):
3453 def display(fm, fn, ctx, pstates, states):
3454 rev = scmutil.intrev(ctx)
3454 rev = scmutil.intrev(ctx)
3455 if fm.isplain():
3455 if fm.isplain():
3456 formatuser = ui.shortuser
3456 formatuser = ui.shortuser
3457 else:
3457 else:
3458 formatuser = pycompat.bytestr
3458 formatuser = pycompat.bytestr
3459 if ui.quiet:
3459 if ui.quiet:
3460 datefmt = b'%Y-%m-%d'
3460 datefmt = b'%Y-%m-%d'
3461 else:
3461 else:
3462 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3462 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3463 found = False
3463 found = False
3464
3464
3465 @util.cachefunc
3465 @util.cachefunc
3466 def binary():
3466 def binary():
3467 flog = getfile(fn)
3467 flog = getfile(fn)
3468 try:
3468 try:
3469 return stringutil.binary(flog.read(ctx.filenode(fn)))
3469 return stringutil.binary(flog.read(ctx.filenode(fn)))
3470 except error.WdirUnsupported:
3470 except error.WdirUnsupported:
3471 return ctx[fn].isbinary()
3471 return ctx[fn].isbinary()
3472
3472
3473 fieldnamemap = {b'linenumber': b'lineno'}
3473 fieldnamemap = {b'linenumber': b'lineno'}
3474 if diff:
3474 if diff:
3475 iter = difflinestates(pstates, states)
3475 iter = difflinestates(pstates, states)
3476 else:
3476 else:
3477 iter = [(b'', l) for l in states]
3477 iter = [(b'', l) for l in states]
3478 for change, l in iter:
3478 for change, l in iter:
3479 fm.startitem()
3479 fm.startitem()
3480 fm.context(ctx=ctx)
3480 fm.context(ctx=ctx)
3481 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3481 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3482 fm.plain(uipathfn(fn), label=b'grep.filename')
3482 fm.plain(uipathfn(fn), label=b'grep.filename')
3483
3483
3484 cols = [
3484 cols = [
3485 (b'rev', b'%d', rev, not plaingrep, b''),
3485 (b'rev', b'%d', rev, not plaingrep, b''),
3486 (
3486 (
3487 b'linenumber',
3487 b'linenumber',
3488 b'%d',
3488 b'%d',
3489 l.linenum,
3489 l.linenum,
3490 opts.get(b'line_number'),
3490 opts.get(b'line_number'),
3491 b'',
3491 b'',
3492 ),
3492 ),
3493 ]
3493 ]
3494 if diff:
3494 if diff:
3495 cols.append(
3495 cols.append(
3496 (
3496 (
3497 b'change',
3497 b'change',
3498 b'%s',
3498 b'%s',
3499 change,
3499 change,
3500 True,
3500 True,
3501 b'grep.inserted '
3501 b'grep.inserted '
3502 if change == b'+'
3502 if change == b'+'
3503 else b'grep.deleted ',
3503 else b'grep.deleted ',
3504 )
3504 )
3505 )
3505 )
3506 cols.extend(
3506 cols.extend(
3507 [
3507 [
3508 (
3508 (
3509 b'user',
3509 b'user',
3510 b'%s',
3510 b'%s',
3511 formatuser(ctx.user()),
3511 formatuser(ctx.user()),
3512 opts.get(b'user'),
3512 opts.get(b'user'),
3513 b'',
3513 b'',
3514 ),
3514 ),
3515 (
3515 (
3516 b'date',
3516 b'date',
3517 b'%s',
3517 b'%s',
3518 fm.formatdate(ctx.date(), datefmt),
3518 fm.formatdate(ctx.date(), datefmt),
3519 opts.get(b'date'),
3519 opts.get(b'date'),
3520 b'',
3520 b'',
3521 ),
3521 ),
3522 ]
3522 ]
3523 )
3523 )
3524 for name, fmt, data, cond, extra_label in cols:
3524 for name, fmt, data, cond, extra_label in cols:
3525 if cond:
3525 if cond:
3526 fm.plain(sep, label=b'grep.sep')
3526 fm.plain(sep, label=b'grep.sep')
3527 field = fieldnamemap.get(name, name)
3527 field = fieldnamemap.get(name, name)
3528 label = extra_label + (b'grep.%s' % name)
3528 label = extra_label + (b'grep.%s' % name)
3529 fm.condwrite(cond, field, fmt, data, label=label)
3529 fm.condwrite(cond, field, fmt, data, label=label)
3530 if not opts.get(b'files_with_matches'):
3530 if not opts.get(b'files_with_matches'):
3531 fm.plain(sep, label=b'grep.sep')
3531 fm.plain(sep, label=b'grep.sep')
3532 if not opts.get(b'text') and binary():
3532 if not opts.get(b'text') and binary():
3533 fm.plain(_(b" Binary file matches"))
3533 fm.plain(_(b" Binary file matches"))
3534 else:
3534 else:
3535 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3535 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3536 fm.plain(eol)
3536 fm.plain(eol)
3537 found = True
3537 found = True
3538 if opts.get(b'files_with_matches'):
3538 if opts.get(b'files_with_matches'):
3539 break
3539 break
3540 return found
3540 return found
3541
3541
3542 def displaymatches(fm, l):
3542 def displaymatches(fm, l):
3543 p = 0
3543 p = 0
3544 for s, e in l.findpos():
3544 for s, e in l.findpos():
3545 if p < s:
3545 if p < s:
3546 fm.startitem()
3546 fm.startitem()
3547 fm.write(b'text', b'%s', l.line[p:s])
3547 fm.write(b'text', b'%s', l.line[p:s])
3548 fm.data(matched=False)
3548 fm.data(matched=False)
3549 fm.startitem()
3549 fm.startitem()
3550 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3550 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3551 fm.data(matched=True)
3551 fm.data(matched=True)
3552 p = e
3552 p = e
3553 if p < len(l.line):
3553 if p < len(l.line):
3554 fm.startitem()
3554 fm.startitem()
3555 fm.write(b'text', b'%s', l.line[p:])
3555 fm.write(b'text', b'%s', l.line[p:])
3556 fm.data(matched=False)
3556 fm.data(matched=False)
3557 fm.end()
3557 fm.end()
3558
3558
3559 skip = set()
3559 skip = set()
3560 revfiles = {}
3560 revfiles = {}
3561 match = scmutil.match(repo[None], pats, opts)
3561 match = scmutil.match(repo[None], pats, opts)
3562 found = False
3562 found = False
3563 follow = opts.get(b'follow')
3563 follow = opts.get(b'follow')
3564
3564
3565 getrenamed = scmutil.getrenamedfn(repo)
3565 getrenamed = scmutil.getrenamedfn(repo)
3566
3566
3567 def get_file_content(filename, filelog, filenode, context, revision):
3567 def get_file_content(filename, filelog, filenode, context, revision):
3568 try:
3568 try:
3569 content = filelog.read(filenode)
3569 content = filelog.read(filenode)
3570 except error.WdirUnsupported:
3570 except error.WdirUnsupported:
3571 content = context[filename].data()
3571 content = context[filename].data()
3572 except error.CensoredNodeError:
3572 except error.CensoredNodeError:
3573 content = None
3573 content = None
3574 ui.warn(
3574 ui.warn(
3575 _(b'cannot search in censored file: %(filename)s:%(revnum)s\n')
3575 _(b'cannot search in censored file: %(filename)s:%(revnum)s\n')
3576 % {b'filename': filename, b'revnum': pycompat.bytestr(revision)}
3576 % {b'filename': filename, b'revnum': pycompat.bytestr(revision)}
3577 )
3577 )
3578 return content
3578 return content
3579
3579
3580 def prep(ctx, fns):
3580 def prep(ctx, fns):
3581 rev = ctx.rev()
3581 rev = ctx.rev()
3582 pctx = ctx.p1()
3582 pctx = ctx.p1()
3583 parent = pctx.rev()
3583 parent = pctx.rev()
3584 matches.setdefault(rev, {})
3584 matches.setdefault(rev, {})
3585 matches.setdefault(parent, {})
3585 matches.setdefault(parent, {})
3586 files = revfiles.setdefault(rev, [])
3586 files = revfiles.setdefault(rev, [])
3587 for fn in fns:
3587 for fn in fns:
3588 flog = getfile(fn)
3588 flog = getfile(fn)
3589 try:
3589 try:
3590 fnode = ctx.filenode(fn)
3590 fnode = ctx.filenode(fn)
3591 except error.LookupError:
3591 except error.LookupError:
3592 continue
3592 continue
3593
3593
3594 copy = None
3594 copy = None
3595 if follow:
3595 if follow:
3596 copy = getrenamed(fn, rev)
3596 copy = getrenamed(fn, rev)
3597 if copy:
3597 if copy:
3598 copies.setdefault(rev, {})[fn] = copy
3598 copies.setdefault(rev, {})[fn] = copy
3599 if fn in skip:
3599 if fn in skip:
3600 skip.add(copy)
3600 skip.add(copy)
3601 if fn in skip:
3601 if fn in skip:
3602 continue
3602 continue
3603 files.append(fn)
3603 files.append(fn)
3604
3604
3605 if fn not in matches[rev]:
3605 if fn not in matches[rev]:
3606 content = get_file_content(fn, flog, fnode, ctx, rev)
3606 content = get_file_content(fn, flog, fnode, ctx, rev)
3607 grepbody(fn, rev, content)
3607 grepbody(fn, rev, content)
3608
3608
3609 pfn = copy or fn
3609 pfn = copy or fn
3610 if pfn not in matches[parent]:
3610 if pfn not in matches[parent]:
3611 try:
3611 try:
3612 pfnode = pctx.filenode(pfn)
3612 pfnode = pctx.filenode(pfn)
3613 pcontent = get_file_content(pfn, flog, pfnode, pctx, parent)
3613 pcontent = get_file_content(pfn, flog, pfnode, pctx, parent)
3614 grepbody(pfn, parent, pcontent)
3614 grepbody(pfn, parent, pcontent)
3615 except error.LookupError:
3615 except error.LookupError:
3616 pass
3616 pass
3617
3617
3618 ui.pager(b'grep')
3618 ui.pager(b'grep')
3619 fm = ui.formatter(b'grep', opts)
3619 fm = ui.formatter(b'grep', opts)
3620 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
3620 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
3621 rev = ctx.rev()
3621 rev = ctx.rev()
3622 parent = ctx.p1().rev()
3622 parent = ctx.p1().rev()
3623 for fn in sorted(revfiles.get(rev, [])):
3623 for fn in sorted(revfiles.get(rev, [])):
3624 states = matches[rev][fn]
3624 states = matches[rev][fn]
3625 copy = copies.get(rev, {}).get(fn)
3625 copy = copies.get(rev, {}).get(fn)
3626 if fn in skip:
3626 if fn in skip:
3627 if copy:
3627 if copy:
3628 skip.add(copy)
3628 skip.add(copy)
3629 continue
3629 continue
3630 pstates = matches.get(parent, {}).get(copy or fn, [])
3630 pstates = matches.get(parent, {}).get(copy or fn, [])
3631 if pstates or states:
3631 if pstates or states:
3632 r = display(fm, fn, ctx, pstates, states)
3632 r = display(fm, fn, ctx, pstates, states)
3633 found = found or r
3633 found = found or r
3634 if r and not diff and not all_files:
3634 if r and not diff and not all_files:
3635 skip.add(fn)
3635 skip.add(fn)
3636 if copy:
3636 if copy:
3637 skip.add(copy)
3637 skip.add(copy)
3638 del revfiles[rev]
3638 del revfiles[rev]
3639 # We will keep the matches dict for the duration of the window
3639 # We will keep the matches dict for the duration of the window
3640 # clear the matches dict once the window is over
3640 # clear the matches dict once the window is over
3641 if not revfiles:
3641 if not revfiles:
3642 matches.clear()
3642 matches.clear()
3643 fm.end()
3643 fm.end()
3644
3644
3645 return not found
3645 return not found
3646
3646
3647
3647
3648 @command(
3648 @command(
3649 b'heads',
3649 b'heads',
3650 [
3650 [
3651 (
3651 (
3652 b'r',
3652 b'r',
3653 b'rev',
3653 b'rev',
3654 b'',
3654 b'',
3655 _(b'show only heads which are descendants of STARTREV'),
3655 _(b'show only heads which are descendants of STARTREV'),
3656 _(b'STARTREV'),
3656 _(b'STARTREV'),
3657 ),
3657 ),
3658 (b't', b'topo', False, _(b'show topological heads only')),
3658 (b't', b'topo', False, _(b'show topological heads only')),
3659 (
3659 (
3660 b'a',
3660 b'a',
3661 b'active',
3661 b'active',
3662 False,
3662 False,
3663 _(b'show active branchheads only (DEPRECATED)'),
3663 _(b'show active branchheads only (DEPRECATED)'),
3664 ),
3664 ),
3665 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3665 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3666 ]
3666 ]
3667 + templateopts,
3667 + templateopts,
3668 _(b'[-ct] [-r STARTREV] [REV]...'),
3668 _(b'[-ct] [-r STARTREV] [REV]...'),
3669 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3669 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3670 intents={INTENT_READONLY},
3670 intents={INTENT_READONLY},
3671 )
3671 )
3672 def heads(ui, repo, *branchrevs, **opts):
3672 def heads(ui, repo, *branchrevs, **opts):
3673 """show branch heads
3673 """show branch heads
3674
3674
3675 With no arguments, show all open branch heads in the repository.
3675 With no arguments, show all open branch heads in the repository.
3676 Branch heads are changesets that have no descendants on the
3676 Branch heads are changesets that have no descendants on the
3677 same branch. They are where development generally takes place and
3677 same branch. They are where development generally takes place and
3678 are the usual targets for update and merge operations.
3678 are the usual targets for update and merge operations.
3679
3679
3680 If one or more REVs are given, only open branch heads on the
3680 If one or more REVs are given, only open branch heads on the
3681 branches associated with the specified changesets are shown. This
3681 branches associated with the specified changesets are shown. This
3682 means that you can use :hg:`heads .` to see the heads on the
3682 means that you can use :hg:`heads .` to see the heads on the
3683 currently checked-out branch.
3683 currently checked-out branch.
3684
3684
3685 If -c/--closed is specified, also show branch heads marked closed
3685 If -c/--closed is specified, also show branch heads marked closed
3686 (see :hg:`commit --close-branch`).
3686 (see :hg:`commit --close-branch`).
3687
3687
3688 If STARTREV is specified, only those heads that are descendants of
3688 If STARTREV is specified, only those heads that are descendants of
3689 STARTREV will be displayed.
3689 STARTREV will be displayed.
3690
3690
3691 If -t/--topo is specified, named branch mechanics will be ignored and only
3691 If -t/--topo is specified, named branch mechanics will be ignored and only
3692 topological heads (changesets with no children) will be shown.
3692 topological heads (changesets with no children) will be shown.
3693
3693
3694 Returns 0 if matching heads are found, 1 if not.
3694 Returns 0 if matching heads are found, 1 if not.
3695 """
3695 """
3696
3696
3697 opts = pycompat.byteskwargs(opts)
3697 opts = pycompat.byteskwargs(opts)
3698 start = None
3698 start = None
3699 rev = opts.get(b'rev')
3699 rev = opts.get(b'rev')
3700 if rev:
3700 if rev:
3701 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3701 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3702 start = scmutil.revsingle(repo, rev, None).node()
3702 start = scmutil.revsingle(repo, rev, None).node()
3703
3703
3704 if opts.get(b'topo'):
3704 if opts.get(b'topo'):
3705 heads = [repo[h] for h in repo.heads(start)]
3705 heads = [repo[h] for h in repo.heads(start)]
3706 else:
3706 else:
3707 heads = []
3707 heads = []
3708 for branch in repo.branchmap():
3708 for branch in repo.branchmap():
3709 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3709 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3710 heads = [repo[h] for h in heads]
3710 heads = [repo[h] for h in heads]
3711
3711
3712 if branchrevs:
3712 if branchrevs:
3713 branches = set(
3713 branches = set(
3714 repo[r].branch() for r in scmutil.revrange(repo, branchrevs)
3714 repo[r].branch() for r in scmutil.revrange(repo, branchrevs)
3715 )
3715 )
3716 heads = [h for h in heads if h.branch() in branches]
3716 heads = [h for h in heads if h.branch() in branches]
3717
3717
3718 if opts.get(b'active') and branchrevs:
3718 if opts.get(b'active') and branchrevs:
3719 dagheads = repo.heads(start)
3719 dagheads = repo.heads(start)
3720 heads = [h for h in heads if h.node() in dagheads]
3720 heads = [h for h in heads if h.node() in dagheads]
3721
3721
3722 if branchrevs:
3722 if branchrevs:
3723 haveheads = set(h.branch() for h in heads)
3723 haveheads = set(h.branch() for h in heads)
3724 if branches - haveheads:
3724 if branches - haveheads:
3725 headless = b', '.join(b for b in branches - haveheads)
3725 headless = b', '.join(b for b in branches - haveheads)
3726 msg = _(b'no open branch heads found on branches %s')
3726 msg = _(b'no open branch heads found on branches %s')
3727 if opts.get(b'rev'):
3727 if opts.get(b'rev'):
3728 msg += _(b' (started at %s)') % opts[b'rev']
3728 msg += _(b' (started at %s)') % opts[b'rev']
3729 ui.warn((msg + b'\n') % headless)
3729 ui.warn((msg + b'\n') % headless)
3730
3730
3731 if not heads:
3731 if not heads:
3732 return 1
3732 return 1
3733
3733
3734 ui.pager(b'heads')
3734 ui.pager(b'heads')
3735 heads = sorted(heads, key=lambda x: -(x.rev()))
3735 heads = sorted(heads, key=lambda x: -(x.rev()))
3736 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3736 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3737 for ctx in heads:
3737 for ctx in heads:
3738 displayer.show(ctx)
3738 displayer.show(ctx)
3739 displayer.close()
3739 displayer.close()
3740
3740
3741
3741
3742 @command(
3742 @command(
3743 b'help',
3743 b'help',
3744 [
3744 [
3745 (b'e', b'extension', None, _(b'show only help for extensions')),
3745 (b'e', b'extension', None, _(b'show only help for extensions')),
3746 (b'c', b'command', None, _(b'show only help for commands')),
3746 (b'c', b'command', None, _(b'show only help for commands')),
3747 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3747 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3748 (
3748 (
3749 b's',
3749 b's',
3750 b'system',
3750 b'system',
3751 [],
3751 [],
3752 _(b'show help for specific platform(s)'),
3752 _(b'show help for specific platform(s)'),
3753 _(b'PLATFORM'),
3753 _(b'PLATFORM'),
3754 ),
3754 ),
3755 ],
3755 ],
3756 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3756 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3757 helpcategory=command.CATEGORY_HELP,
3757 helpcategory=command.CATEGORY_HELP,
3758 norepo=True,
3758 norepo=True,
3759 intents={INTENT_READONLY},
3759 intents={INTENT_READONLY},
3760 )
3760 )
3761 def help_(ui, name=None, **opts):
3761 def help_(ui, name=None, **opts):
3762 """show help for a given topic or a help overview
3762 """show help for a given topic or a help overview
3763
3763
3764 With no arguments, print a list of commands with short help messages.
3764 With no arguments, print a list of commands with short help messages.
3765
3765
3766 Given a topic, extension, or command name, print help for that
3766 Given a topic, extension, or command name, print help for that
3767 topic.
3767 topic.
3768
3768
3769 Returns 0 if successful.
3769 Returns 0 if successful.
3770 """
3770 """
3771
3771
3772 keep = opts.get('system') or []
3772 keep = opts.get('system') or []
3773 if len(keep) == 0:
3773 if len(keep) == 0:
3774 if pycompat.sysplatform.startswith(b'win'):
3774 if pycompat.sysplatform.startswith(b'win'):
3775 keep.append(b'windows')
3775 keep.append(b'windows')
3776 elif pycompat.sysplatform == b'OpenVMS':
3776 elif pycompat.sysplatform == b'OpenVMS':
3777 keep.append(b'vms')
3777 keep.append(b'vms')
3778 elif pycompat.sysplatform == b'plan9':
3778 elif pycompat.sysplatform == b'plan9':
3779 keep.append(b'plan9')
3779 keep.append(b'plan9')
3780 else:
3780 else:
3781 keep.append(b'unix')
3781 keep.append(b'unix')
3782 keep.append(pycompat.sysplatform.lower())
3782 keep.append(pycompat.sysplatform.lower())
3783 if ui.verbose:
3783 if ui.verbose:
3784 keep.append(b'verbose')
3784 keep.append(b'verbose')
3785
3785
3786 commands = sys.modules[__name__]
3786 commands = sys.modules[__name__]
3787 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3787 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3788 ui.pager(b'help')
3788 ui.pager(b'help')
3789 ui.write(formatted)
3789 ui.write(formatted)
3790
3790
3791
3791
3792 @command(
3792 @command(
3793 b'identify|id',
3793 b'identify|id',
3794 [
3794 [
3795 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3795 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3796 (b'n', b'num', None, _(b'show local revision number')),
3796 (b'n', b'num', None, _(b'show local revision number')),
3797 (b'i', b'id', None, _(b'show global revision id')),
3797 (b'i', b'id', None, _(b'show global revision id')),
3798 (b'b', b'branch', None, _(b'show branch')),
3798 (b'b', b'branch', None, _(b'show branch')),
3799 (b't', b'tags', None, _(b'show tags')),
3799 (b't', b'tags', None, _(b'show tags')),
3800 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3800 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3801 ]
3801 ]
3802 + remoteopts
3802 + remoteopts
3803 + formatteropts,
3803 + formatteropts,
3804 _(b'[-nibtB] [-r REV] [SOURCE]'),
3804 _(b'[-nibtB] [-r REV] [SOURCE]'),
3805 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3805 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3806 optionalrepo=True,
3806 optionalrepo=True,
3807 intents={INTENT_READONLY},
3807 intents={INTENT_READONLY},
3808 )
3808 )
3809 def identify(
3809 def identify(
3810 ui,
3810 ui,
3811 repo,
3811 repo,
3812 source=None,
3812 source=None,
3813 rev=None,
3813 rev=None,
3814 num=None,
3814 num=None,
3815 id=None,
3815 id=None,
3816 branch=None,
3816 branch=None,
3817 tags=None,
3817 tags=None,
3818 bookmarks=None,
3818 bookmarks=None,
3819 **opts
3819 **opts
3820 ):
3820 ):
3821 """identify the working directory or specified revision
3821 """identify the working directory or specified revision
3822
3822
3823 Print a summary identifying the repository state at REV using one or
3823 Print a summary identifying the repository state at REV using one or
3824 two parent hash identifiers, followed by a "+" if the working
3824 two parent hash identifiers, followed by a "+" if the working
3825 directory has uncommitted changes, the branch name (if not default),
3825 directory has uncommitted changes, the branch name (if not default),
3826 a list of tags, and a list of bookmarks.
3826 a list of tags, and a list of bookmarks.
3827
3827
3828 When REV is not given, print a summary of the current state of the
3828 When REV is not given, print a summary of the current state of the
3829 repository including the working directory. Specify -r. to get information
3829 repository including the working directory. Specify -r. to get information
3830 of the working directory parent without scanning uncommitted changes.
3830 of the working directory parent without scanning uncommitted changes.
3831
3831
3832 Specifying a path to a repository root or Mercurial bundle will
3832 Specifying a path to a repository root or Mercurial bundle will
3833 cause lookup to operate on that repository/bundle.
3833 cause lookup to operate on that repository/bundle.
3834
3834
3835 .. container:: verbose
3835 .. container:: verbose
3836
3836
3837 Template:
3837 Template:
3838
3838
3839 The following keywords are supported in addition to the common template
3839 The following keywords are supported in addition to the common template
3840 keywords and functions. See also :hg:`help templates`.
3840 keywords and functions. See also :hg:`help templates`.
3841
3841
3842 :dirty: String. Character ``+`` denoting if the working directory has
3842 :dirty: String. Character ``+`` denoting if the working directory has
3843 uncommitted changes.
3843 uncommitted changes.
3844 :id: String. One or two nodes, optionally followed by ``+``.
3844 :id: String. One or two nodes, optionally followed by ``+``.
3845 :parents: List of strings. Parent nodes of the changeset.
3845 :parents: List of strings. Parent nodes of the changeset.
3846
3846
3847 Examples:
3847 Examples:
3848
3848
3849 - generate a build identifier for the working directory::
3849 - generate a build identifier for the working directory::
3850
3850
3851 hg id --id > build-id.dat
3851 hg id --id > build-id.dat
3852
3852
3853 - find the revision corresponding to a tag::
3853 - find the revision corresponding to a tag::
3854
3854
3855 hg id -n -r 1.3
3855 hg id -n -r 1.3
3856
3856
3857 - check the most recent revision of a remote repository::
3857 - check the most recent revision of a remote repository::
3858
3858
3859 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3859 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3860
3860
3861 See :hg:`log` for generating more information about specific revisions,
3861 See :hg:`log` for generating more information about specific revisions,
3862 including full hash identifiers.
3862 including full hash identifiers.
3863
3863
3864 Returns 0 if successful.
3864 Returns 0 if successful.
3865 """
3865 """
3866
3866
3867 opts = pycompat.byteskwargs(opts)
3867 opts = pycompat.byteskwargs(opts)
3868 if not repo and not source:
3868 if not repo and not source:
3869 raise error.Abort(
3869 raise error.Abort(
3870 _(b"there is no Mercurial repository here (.hg not found)")
3870 _(b"there is no Mercurial repository here (.hg not found)")
3871 )
3871 )
3872
3872
3873 default = not (num or id or branch or tags or bookmarks)
3873 default = not (num or id or branch or tags or bookmarks)
3874 output = []
3874 output = []
3875 revs = []
3875 revs = []
3876
3876
3877 if source:
3877 if source:
3878 source, branches = hg.parseurl(ui.expandpath(source))
3878 source, branches = hg.parseurl(ui.expandpath(source))
3879 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3879 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3880 repo = peer.local()
3880 repo = peer.local()
3881 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3881 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3882
3882
3883 fm = ui.formatter(b'identify', opts)
3883 fm = ui.formatter(b'identify', opts)
3884 fm.startitem()
3884 fm.startitem()
3885
3885
3886 if not repo:
3886 if not repo:
3887 if num or branch or tags:
3887 if num or branch or tags:
3888 raise error.Abort(
3888 raise error.Abort(
3889 _(b"can't query remote revision number, branch, or tags")
3889 _(b"can't query remote revision number, branch, or tags")
3890 )
3890 )
3891 if not rev and revs:
3891 if not rev and revs:
3892 rev = revs[0]
3892 rev = revs[0]
3893 if not rev:
3893 if not rev:
3894 rev = b"tip"
3894 rev = b"tip"
3895
3895
3896 remoterev = peer.lookup(rev)
3896 remoterev = peer.lookup(rev)
3897 hexrev = fm.hexfunc(remoterev)
3897 hexrev = fm.hexfunc(remoterev)
3898 if default or id:
3898 if default or id:
3899 output = [hexrev]
3899 output = [hexrev]
3900 fm.data(id=hexrev)
3900 fm.data(id=hexrev)
3901
3901
3902 @util.cachefunc
3902 @util.cachefunc
3903 def getbms():
3903 def getbms():
3904 bms = []
3904 bms = []
3905
3905
3906 if b'bookmarks' in peer.listkeys(b'namespaces'):
3906 if b'bookmarks' in peer.listkeys(b'namespaces'):
3907 hexremoterev = hex(remoterev)
3907 hexremoterev = hex(remoterev)
3908 bms = [
3908 bms = [
3909 bm
3909 bm
3910 for bm, bmr in pycompat.iteritems(
3910 for bm, bmr in pycompat.iteritems(
3911 peer.listkeys(b'bookmarks')
3911 peer.listkeys(b'bookmarks')
3912 )
3912 )
3913 if bmr == hexremoterev
3913 if bmr == hexremoterev
3914 ]
3914 ]
3915
3915
3916 return sorted(bms)
3916 return sorted(bms)
3917
3917
3918 if fm.isplain():
3918 if fm.isplain():
3919 if bookmarks:
3919 if bookmarks:
3920 output.extend(getbms())
3920 output.extend(getbms())
3921 elif default and not ui.quiet:
3921 elif default and not ui.quiet:
3922 # multiple bookmarks for a single parent separated by '/'
3922 # multiple bookmarks for a single parent separated by '/'
3923 bm = b'/'.join(getbms())
3923 bm = b'/'.join(getbms())
3924 if bm:
3924 if bm:
3925 output.append(bm)
3925 output.append(bm)
3926 else:
3926 else:
3927 fm.data(node=hex(remoterev))
3927 fm.data(node=hex(remoterev))
3928 if bookmarks or b'bookmarks' in fm.datahint():
3928 if bookmarks or b'bookmarks' in fm.datahint():
3929 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3929 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3930 else:
3930 else:
3931 if rev:
3931 if rev:
3932 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3932 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3933 ctx = scmutil.revsingle(repo, rev, None)
3933 ctx = scmutil.revsingle(repo, rev, None)
3934
3934
3935 if ctx.rev() is None:
3935 if ctx.rev() is None:
3936 ctx = repo[None]
3936 ctx = repo[None]
3937 parents = ctx.parents()
3937 parents = ctx.parents()
3938 taglist = []
3938 taglist = []
3939 for p in parents:
3939 for p in parents:
3940 taglist.extend(p.tags())
3940 taglist.extend(p.tags())
3941
3941
3942 dirty = b""
3942 dirty = b""
3943 if ctx.dirty(missing=True, merge=False, branch=False):
3943 if ctx.dirty(missing=True, merge=False, branch=False):
3944 dirty = b'+'
3944 dirty = b'+'
3945 fm.data(dirty=dirty)
3945 fm.data(dirty=dirty)
3946
3946
3947 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3947 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3948 if default or id:
3948 if default or id:
3949 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3949 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3950 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3950 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3951
3951
3952 if num:
3952 if num:
3953 numoutput = [b"%d" % p.rev() for p in parents]
3953 numoutput = [b"%d" % p.rev() for p in parents]
3954 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3954 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3955
3955
3956 fm.data(
3956 fm.data(
3957 parents=fm.formatlist(
3957 parents=fm.formatlist(
3958 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3958 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3959 )
3959 )
3960 )
3960 )
3961 else:
3961 else:
3962 hexoutput = fm.hexfunc(ctx.node())
3962 hexoutput = fm.hexfunc(ctx.node())
3963 if default or id:
3963 if default or id:
3964 output = [hexoutput]
3964 output = [hexoutput]
3965 fm.data(id=hexoutput)
3965 fm.data(id=hexoutput)
3966
3966
3967 if num:
3967 if num:
3968 output.append(pycompat.bytestr(ctx.rev()))
3968 output.append(pycompat.bytestr(ctx.rev()))
3969 taglist = ctx.tags()
3969 taglist = ctx.tags()
3970
3970
3971 if default and not ui.quiet:
3971 if default and not ui.quiet:
3972 b = ctx.branch()
3972 b = ctx.branch()
3973 if b != b'default':
3973 if b != b'default':
3974 output.append(b"(%s)" % b)
3974 output.append(b"(%s)" % b)
3975
3975
3976 # multiple tags for a single parent separated by '/'
3976 # multiple tags for a single parent separated by '/'
3977 t = b'/'.join(taglist)
3977 t = b'/'.join(taglist)
3978 if t:
3978 if t:
3979 output.append(t)
3979 output.append(t)
3980
3980
3981 # multiple bookmarks for a single parent separated by '/'
3981 # multiple bookmarks for a single parent separated by '/'
3982 bm = b'/'.join(ctx.bookmarks())
3982 bm = b'/'.join(ctx.bookmarks())
3983 if bm:
3983 if bm:
3984 output.append(bm)
3984 output.append(bm)
3985 else:
3985 else:
3986 if branch:
3986 if branch:
3987 output.append(ctx.branch())
3987 output.append(ctx.branch())
3988
3988
3989 if tags:
3989 if tags:
3990 output.extend(taglist)
3990 output.extend(taglist)
3991
3991
3992 if bookmarks:
3992 if bookmarks:
3993 output.extend(ctx.bookmarks())
3993 output.extend(ctx.bookmarks())
3994
3994
3995 fm.data(node=ctx.hex())
3995 fm.data(node=ctx.hex())
3996 fm.data(branch=ctx.branch())
3996 fm.data(branch=ctx.branch())
3997 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
3997 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
3998 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
3998 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
3999 fm.context(ctx=ctx)
3999 fm.context(ctx=ctx)
4000
4000
4001 fm.plain(b"%s\n" % b' '.join(output))
4001 fm.plain(b"%s\n" % b' '.join(output))
4002 fm.end()
4002 fm.end()
4003
4003
4004
4004
4005 @command(
4005 @command(
4006 b'import|patch',
4006 b'import|patch',
4007 [
4007 [
4008 (
4008 (
4009 b'p',
4009 b'p',
4010 b'strip',
4010 b'strip',
4011 1,
4011 1,
4012 _(
4012 _(
4013 b'directory strip option for patch. This has the same '
4013 b'directory strip option for patch. This has the same '
4014 b'meaning as the corresponding patch option'
4014 b'meaning as the corresponding patch option'
4015 ),
4015 ),
4016 _(b'NUM'),
4016 _(b'NUM'),
4017 ),
4017 ),
4018 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4018 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4019 (b'', b'secret', None, _(b'use the secret phase for committing')),
4019 (b'', b'secret', None, _(b'use the secret phase for committing')),
4020 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4020 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4021 (
4021 (
4022 b'f',
4022 b'f',
4023 b'force',
4023 b'force',
4024 None,
4024 None,
4025 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4025 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4026 ),
4026 ),
4027 (
4027 (
4028 b'',
4028 b'',
4029 b'no-commit',
4029 b'no-commit',
4030 None,
4030 None,
4031 _(b"don't commit, just update the working directory"),
4031 _(b"don't commit, just update the working directory"),
4032 ),
4032 ),
4033 (
4033 (
4034 b'',
4034 b'',
4035 b'bypass',
4035 b'bypass',
4036 None,
4036 None,
4037 _(b"apply patch without touching the working directory"),
4037 _(b"apply patch without touching the working directory"),
4038 ),
4038 ),
4039 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4039 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4040 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4040 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4041 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4041 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4042 (
4042 (
4043 b'',
4043 b'',
4044 b'import-branch',
4044 b'import-branch',
4045 None,
4045 None,
4046 _(b'use any branch information in patch (implied by --exact)'),
4046 _(b'use any branch information in patch (implied by --exact)'),
4047 ),
4047 ),
4048 ]
4048 ]
4049 + commitopts
4049 + commitopts
4050 + commitopts2
4050 + commitopts2
4051 + similarityopts,
4051 + similarityopts,
4052 _(b'[OPTION]... PATCH...'),
4052 _(b'[OPTION]... PATCH...'),
4053 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4053 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4054 )
4054 )
4055 def import_(ui, repo, patch1=None, *patches, **opts):
4055 def import_(ui, repo, patch1=None, *patches, **opts):
4056 """import an ordered set of patches
4056 """import an ordered set of patches
4057
4057
4058 Import a list of patches and commit them individually (unless
4058 Import a list of patches and commit them individually (unless
4059 --no-commit is specified).
4059 --no-commit is specified).
4060
4060
4061 To read a patch from standard input (stdin), use "-" as the patch
4061 To read a patch from standard input (stdin), use "-" as the patch
4062 name. If a URL is specified, the patch will be downloaded from
4062 name. If a URL is specified, the patch will be downloaded from
4063 there.
4063 there.
4064
4064
4065 Import first applies changes to the working directory (unless
4065 Import first applies changes to the working directory (unless
4066 --bypass is specified), import will abort if there are outstanding
4066 --bypass is specified), import will abort if there are outstanding
4067 changes.
4067 changes.
4068
4068
4069 Use --bypass to apply and commit patches directly to the
4069 Use --bypass to apply and commit patches directly to the
4070 repository, without affecting the working directory. Without
4070 repository, without affecting the working directory. Without
4071 --exact, patches will be applied on top of the working directory
4071 --exact, patches will be applied on top of the working directory
4072 parent revision.
4072 parent revision.
4073
4073
4074 You can import a patch straight from a mail message. Even patches
4074 You can import a patch straight from a mail message. Even patches
4075 as attachments work (to use the body part, it must have type
4075 as attachments work (to use the body part, it must have type
4076 text/plain or text/x-patch). From and Subject headers of email
4076 text/plain or text/x-patch). From and Subject headers of email
4077 message are used as default committer and commit message. All
4077 message are used as default committer and commit message. All
4078 text/plain body parts before first diff are added to the commit
4078 text/plain body parts before first diff are added to the commit
4079 message.
4079 message.
4080
4080
4081 If the imported patch was generated by :hg:`export`, user and
4081 If the imported patch was generated by :hg:`export`, user and
4082 description from patch override values from message headers and
4082 description from patch override values from message headers and
4083 body. Values given on command line with -m/--message and -u/--user
4083 body. Values given on command line with -m/--message and -u/--user
4084 override these.
4084 override these.
4085
4085
4086 If --exact is specified, import will set the working directory to
4086 If --exact is specified, import will set the working directory to
4087 the parent of each patch before applying it, and will abort if the
4087 the parent of each patch before applying it, and will abort if the
4088 resulting changeset has a different ID than the one recorded in
4088 resulting changeset has a different ID than the one recorded in
4089 the patch. This will guard against various ways that portable
4089 the patch. This will guard against various ways that portable
4090 patch formats and mail systems might fail to transfer Mercurial
4090 patch formats and mail systems might fail to transfer Mercurial
4091 data or metadata. See :hg:`bundle` for lossless transmission.
4091 data or metadata. See :hg:`bundle` for lossless transmission.
4092
4092
4093 Use --partial to ensure a changeset will be created from the patch
4093 Use --partial to ensure a changeset will be created from the patch
4094 even if some hunks fail to apply. Hunks that fail to apply will be
4094 even if some hunks fail to apply. Hunks that fail to apply will be
4095 written to a <target-file>.rej file. Conflicts can then be resolved
4095 written to a <target-file>.rej file. Conflicts can then be resolved
4096 by hand before :hg:`commit --amend` is run to update the created
4096 by hand before :hg:`commit --amend` is run to update the created
4097 changeset. This flag exists to let people import patches that
4097 changeset. This flag exists to let people import patches that
4098 partially apply without losing the associated metadata (author,
4098 partially apply without losing the associated metadata (author,
4099 date, description, ...).
4099 date, description, ...).
4100
4100
4101 .. note::
4101 .. note::
4102
4102
4103 When no hunks apply cleanly, :hg:`import --partial` will create
4103 When no hunks apply cleanly, :hg:`import --partial` will create
4104 an empty changeset, importing only the patch metadata.
4104 an empty changeset, importing only the patch metadata.
4105
4105
4106 With -s/--similarity, hg will attempt to discover renames and
4106 With -s/--similarity, hg will attempt to discover renames and
4107 copies in the patch in the same way as :hg:`addremove`.
4107 copies in the patch in the same way as :hg:`addremove`.
4108
4108
4109 It is possible to use external patch programs to perform the patch
4109 It is possible to use external patch programs to perform the patch
4110 by setting the ``ui.patch`` configuration option. For the default
4110 by setting the ``ui.patch`` configuration option. For the default
4111 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4111 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4112 See :hg:`help config` for more information about configuration
4112 See :hg:`help config` for more information about configuration
4113 files and how to use these options.
4113 files and how to use these options.
4114
4114
4115 See :hg:`help dates` for a list of formats valid for -d/--date.
4115 See :hg:`help dates` for a list of formats valid for -d/--date.
4116
4116
4117 .. container:: verbose
4117 .. container:: verbose
4118
4118
4119 Examples:
4119 Examples:
4120
4120
4121 - import a traditional patch from a website and detect renames::
4121 - import a traditional patch from a website and detect renames::
4122
4122
4123 hg import -s 80 http://example.com/bugfix.patch
4123 hg import -s 80 http://example.com/bugfix.patch
4124
4124
4125 - import a changeset from an hgweb server::
4125 - import a changeset from an hgweb server::
4126
4126
4127 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4127 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4128
4128
4129 - import all the patches in an Unix-style mbox::
4129 - import all the patches in an Unix-style mbox::
4130
4130
4131 hg import incoming-patches.mbox
4131 hg import incoming-patches.mbox
4132
4132
4133 - import patches from stdin::
4133 - import patches from stdin::
4134
4134
4135 hg import -
4135 hg import -
4136
4136
4137 - attempt to exactly restore an exported changeset (not always
4137 - attempt to exactly restore an exported changeset (not always
4138 possible)::
4138 possible)::
4139
4139
4140 hg import --exact proposed-fix.patch
4140 hg import --exact proposed-fix.patch
4141
4141
4142 - use an external tool to apply a patch which is too fuzzy for
4142 - use an external tool to apply a patch which is too fuzzy for
4143 the default internal tool.
4143 the default internal tool.
4144
4144
4145 hg import --config ui.patch="patch --merge" fuzzy.patch
4145 hg import --config ui.patch="patch --merge" fuzzy.patch
4146
4146
4147 - change the default fuzzing from 2 to a less strict 7
4147 - change the default fuzzing from 2 to a less strict 7
4148
4148
4149 hg import --config ui.fuzz=7 fuzz.patch
4149 hg import --config ui.fuzz=7 fuzz.patch
4150
4150
4151 Returns 0 on success, 1 on partial success (see --partial).
4151 Returns 0 on success, 1 on partial success (see --partial).
4152 """
4152 """
4153
4153
4154 opts = pycompat.byteskwargs(opts)
4154 opts = pycompat.byteskwargs(opts)
4155 if not patch1:
4155 if not patch1:
4156 raise error.Abort(_(b'need at least one patch to import'))
4156 raise error.Abort(_(b'need at least one patch to import'))
4157
4157
4158 patches = (patch1,) + patches
4158 patches = (patch1,) + patches
4159
4159
4160 date = opts.get(b'date')
4160 date = opts.get(b'date')
4161 if date:
4161 if date:
4162 opts[b'date'] = dateutil.parsedate(date)
4162 opts[b'date'] = dateutil.parsedate(date)
4163
4163
4164 exact = opts.get(b'exact')
4164 exact = opts.get(b'exact')
4165 update = not opts.get(b'bypass')
4165 update = not opts.get(b'bypass')
4166 if not update and opts.get(b'no_commit'):
4166 if not update and opts.get(b'no_commit'):
4167 raise error.Abort(_(b'cannot use --no-commit with --bypass'))
4167 raise error.Abort(_(b'cannot use --no-commit with --bypass'))
4168 if opts.get(b'secret') and opts.get(b'no_commit'):
4168 if opts.get(b'secret') and opts.get(b'no_commit'):
4169 raise error.Abort(_(b'cannot use --no-commit with --secret'))
4169 raise error.Abort(_(b'cannot use --no-commit with --secret'))
4170 try:
4170 try:
4171 sim = float(opts.get(b'similarity') or 0)
4171 sim = float(opts.get(b'similarity') or 0)
4172 except ValueError:
4172 except ValueError:
4173 raise error.Abort(_(b'similarity must be a number'))
4173 raise error.Abort(_(b'similarity must be a number'))
4174 if sim < 0 or sim > 100:
4174 if sim < 0 or sim > 100:
4175 raise error.Abort(_(b'similarity must be between 0 and 100'))
4175 raise error.Abort(_(b'similarity must be between 0 and 100'))
4176 if sim and not update:
4176 if sim and not update:
4177 raise error.Abort(_(b'cannot use --similarity with --bypass'))
4177 raise error.Abort(_(b'cannot use --similarity with --bypass'))
4178 if exact:
4178 if exact:
4179 if opts.get(b'edit'):
4179 if opts.get(b'edit'):
4180 raise error.Abort(_(b'cannot use --exact with --edit'))
4180 raise error.Abort(_(b'cannot use --exact with --edit'))
4181 if opts.get(b'prefix'):
4181 if opts.get(b'prefix'):
4182 raise error.Abort(_(b'cannot use --exact with --prefix'))
4182 raise error.Abort(_(b'cannot use --exact with --prefix'))
4183
4183
4184 base = opts[b"base"]
4184 base = opts[b"base"]
4185 msgs = []
4185 msgs = []
4186 ret = 0
4186 ret = 0
4187
4187
4188 with repo.wlock():
4188 with repo.wlock():
4189 if update:
4189 if update:
4190 cmdutil.checkunfinished(repo)
4190 cmdutil.checkunfinished(repo)
4191 if exact or not opts.get(b'force'):
4191 if exact or not opts.get(b'force'):
4192 cmdutil.bailifchanged(repo)
4192 cmdutil.bailifchanged(repo)
4193
4193
4194 if not opts.get(b'no_commit'):
4194 if not opts.get(b'no_commit'):
4195 lock = repo.lock
4195 lock = repo.lock
4196 tr = lambda: repo.transaction(b'import')
4196 tr = lambda: repo.transaction(b'import')
4197 dsguard = util.nullcontextmanager
4197 dsguard = util.nullcontextmanager
4198 else:
4198 else:
4199 lock = util.nullcontextmanager
4199 lock = util.nullcontextmanager
4200 tr = util.nullcontextmanager
4200 tr = util.nullcontextmanager
4201 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4201 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4202 with lock(), tr(), dsguard():
4202 with lock(), tr(), dsguard():
4203 parents = repo[None].parents()
4203 parents = repo[None].parents()
4204 for patchurl in patches:
4204 for patchurl in patches:
4205 if patchurl == b'-':
4205 if patchurl == b'-':
4206 ui.status(_(b'applying patch from stdin\n'))
4206 ui.status(_(b'applying patch from stdin\n'))
4207 patchfile = ui.fin
4207 patchfile = ui.fin
4208 patchurl = b'stdin' # for error message
4208 patchurl = b'stdin' # for error message
4209 else:
4209 else:
4210 patchurl = os.path.join(base, patchurl)
4210 patchurl = os.path.join(base, patchurl)
4211 ui.status(_(b'applying %s\n') % patchurl)
4211 ui.status(_(b'applying %s\n') % patchurl)
4212 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4212 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4213
4213
4214 haspatch = False
4214 haspatch = False
4215 for hunk in patch.split(patchfile):
4215 for hunk in patch.split(patchfile):
4216 with patch.extract(ui, hunk) as patchdata:
4216 with patch.extract(ui, hunk) as patchdata:
4217 msg, node, rej = cmdutil.tryimportone(
4217 msg, node, rej = cmdutil.tryimportone(
4218 ui, repo, patchdata, parents, opts, msgs, hg.clean
4218 ui, repo, patchdata, parents, opts, msgs, hg.clean
4219 )
4219 )
4220 if msg:
4220 if msg:
4221 haspatch = True
4221 haspatch = True
4222 ui.note(msg + b'\n')
4222 ui.note(msg + b'\n')
4223 if update or exact:
4223 if update or exact:
4224 parents = repo[None].parents()
4224 parents = repo[None].parents()
4225 else:
4225 else:
4226 parents = [repo[node]]
4226 parents = [repo[node]]
4227 if rej:
4227 if rej:
4228 ui.write_err(_(b"patch applied partially\n"))
4228 ui.write_err(_(b"patch applied partially\n"))
4229 ui.write_err(
4229 ui.write_err(
4230 _(
4230 _(
4231 b"(fix the .rej files and run "
4231 b"(fix the .rej files and run "
4232 b"`hg commit --amend`)\n"
4232 b"`hg commit --amend`)\n"
4233 )
4233 )
4234 )
4234 )
4235 ret = 1
4235 ret = 1
4236 break
4236 break
4237
4237
4238 if not haspatch:
4238 if not haspatch:
4239 raise error.Abort(_(b'%s: no diffs found') % patchurl)
4239 raise error.Abort(_(b'%s: no diffs found') % patchurl)
4240
4240
4241 if msgs:
4241 if msgs:
4242 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4242 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4243 return ret
4243 return ret
4244
4244
4245
4245
4246 @command(
4246 @command(
4247 b'incoming|in',
4247 b'incoming|in',
4248 [
4248 [
4249 (
4249 (
4250 b'f',
4250 b'f',
4251 b'force',
4251 b'force',
4252 None,
4252 None,
4253 _(b'run even if remote repository is unrelated'),
4253 _(b'run even if remote repository is unrelated'),
4254 ),
4254 ),
4255 (b'n', b'newest-first', None, _(b'show newest record first')),
4255 (b'n', b'newest-first', None, _(b'show newest record first')),
4256 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4256 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4257 (
4257 (
4258 b'r',
4258 b'r',
4259 b'rev',
4259 b'rev',
4260 [],
4260 [],
4261 _(b'a remote changeset intended to be added'),
4261 _(b'a remote changeset intended to be added'),
4262 _(b'REV'),
4262 _(b'REV'),
4263 ),
4263 ),
4264 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4264 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4265 (
4265 (
4266 b'b',
4266 b'b',
4267 b'branch',
4267 b'branch',
4268 [],
4268 [],
4269 _(b'a specific branch you would like to pull'),
4269 _(b'a specific branch you would like to pull'),
4270 _(b'BRANCH'),
4270 _(b'BRANCH'),
4271 ),
4271 ),
4272 ]
4272 ]
4273 + logopts
4273 + logopts
4274 + remoteopts
4274 + remoteopts
4275 + subrepoopts,
4275 + subrepoopts,
4276 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4276 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4277 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4277 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4278 )
4278 )
4279 def incoming(ui, repo, source=b"default", **opts):
4279 def incoming(ui, repo, source=b"default", **opts):
4280 """show new changesets found in source
4280 """show new changesets found in source
4281
4281
4282 Show new changesets found in the specified path/URL or the default
4282 Show new changesets found in the specified path/URL or the default
4283 pull location. These are the changesets that would have been pulled
4283 pull location. These are the changesets that would have been pulled
4284 by :hg:`pull` at the time you issued this command.
4284 by :hg:`pull` at the time you issued this command.
4285
4285
4286 See pull for valid source format details.
4286 See pull for valid source format details.
4287
4287
4288 .. container:: verbose
4288 .. container:: verbose
4289
4289
4290 With -B/--bookmarks, the result of bookmark comparison between
4290 With -B/--bookmarks, the result of bookmark comparison between
4291 local and remote repositories is displayed. With -v/--verbose,
4291 local and remote repositories is displayed. With -v/--verbose,
4292 status is also displayed for each bookmark like below::
4292 status is also displayed for each bookmark like below::
4293
4293
4294 BM1 01234567890a added
4294 BM1 01234567890a added
4295 BM2 1234567890ab advanced
4295 BM2 1234567890ab advanced
4296 BM3 234567890abc diverged
4296 BM3 234567890abc diverged
4297 BM4 34567890abcd changed
4297 BM4 34567890abcd changed
4298
4298
4299 The action taken locally when pulling depends on the
4299 The action taken locally when pulling depends on the
4300 status of each bookmark:
4300 status of each bookmark:
4301
4301
4302 :``added``: pull will create it
4302 :``added``: pull will create it
4303 :``advanced``: pull will update it
4303 :``advanced``: pull will update it
4304 :``diverged``: pull will create a divergent bookmark
4304 :``diverged``: pull will create a divergent bookmark
4305 :``changed``: result depends on remote changesets
4305 :``changed``: result depends on remote changesets
4306
4306
4307 From the point of view of pulling behavior, bookmark
4307 From the point of view of pulling behavior, bookmark
4308 existing only in the remote repository are treated as ``added``,
4308 existing only in the remote repository are treated as ``added``,
4309 even if it is in fact locally deleted.
4309 even if it is in fact locally deleted.
4310
4310
4311 .. container:: verbose
4311 .. container:: verbose
4312
4312
4313 For remote repository, using --bundle avoids downloading the
4313 For remote repository, using --bundle avoids downloading the
4314 changesets twice if the incoming is followed by a pull.
4314 changesets twice if the incoming is followed by a pull.
4315
4315
4316 Examples:
4316 Examples:
4317
4317
4318 - show incoming changes with patches and full description::
4318 - show incoming changes with patches and full description::
4319
4319
4320 hg incoming -vp
4320 hg incoming -vp
4321
4321
4322 - show incoming changes excluding merges, store a bundle::
4322 - show incoming changes excluding merges, store a bundle::
4323
4323
4324 hg in -vpM --bundle incoming.hg
4324 hg in -vpM --bundle incoming.hg
4325 hg pull incoming.hg
4325 hg pull incoming.hg
4326
4326
4327 - briefly list changes inside a bundle::
4327 - briefly list changes inside a bundle::
4328
4328
4329 hg in changes.hg -T "{desc|firstline}\\n"
4329 hg in changes.hg -T "{desc|firstline}\\n"
4330
4330
4331 Returns 0 if there are incoming changes, 1 otherwise.
4331 Returns 0 if there are incoming changes, 1 otherwise.
4332 """
4332 """
4333 opts = pycompat.byteskwargs(opts)
4333 opts = pycompat.byteskwargs(opts)
4334 if opts.get(b'graph'):
4334 if opts.get(b'graph'):
4335 logcmdutil.checkunsupportedgraphflags([], opts)
4335 logcmdutil.checkunsupportedgraphflags([], opts)
4336
4336
4337 def display(other, chlist, displayer):
4337 def display(other, chlist, displayer):
4338 revdag = logcmdutil.graphrevs(other, chlist, opts)
4338 revdag = logcmdutil.graphrevs(other, chlist, opts)
4339 logcmdutil.displaygraph(
4339 logcmdutil.displaygraph(
4340 ui, repo, revdag, displayer, graphmod.asciiedges
4340 ui, repo, revdag, displayer, graphmod.asciiedges
4341 )
4341 )
4342
4342
4343 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4343 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4344 return 0
4344 return 0
4345
4345
4346 if opts.get(b'bundle') and opts.get(b'subrepos'):
4346 if opts.get(b'bundle') and opts.get(b'subrepos'):
4347 raise error.Abort(_(b'cannot combine --bundle and --subrepos'))
4347 raise error.Abort(_(b'cannot combine --bundle and --subrepos'))
4348
4348
4349 if opts.get(b'bookmarks'):
4349 if opts.get(b'bookmarks'):
4350 source, branches = hg.parseurl(
4350 source, branches = hg.parseurl(
4351 ui.expandpath(source), opts.get(b'branch')
4351 ui.expandpath(source), opts.get(b'branch')
4352 )
4352 )
4353 other = hg.peer(repo, opts, source)
4353 other = hg.peer(repo, opts, source)
4354 if b'bookmarks' not in other.listkeys(b'namespaces'):
4354 if b'bookmarks' not in other.listkeys(b'namespaces'):
4355 ui.warn(_(b"remote doesn't support bookmarks\n"))
4355 ui.warn(_(b"remote doesn't support bookmarks\n"))
4356 return 0
4356 return 0
4357 ui.pager(b'incoming')
4357 ui.pager(b'incoming')
4358 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4358 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4359 return bookmarks.incoming(ui, repo, other)
4359 return bookmarks.incoming(ui, repo, other)
4360
4360
4361 repo._subtoppath = ui.expandpath(source)
4361 repo._subtoppath = ui.expandpath(source)
4362 try:
4362 try:
4363 return hg.incoming(ui, repo, source, opts)
4363 return hg.incoming(ui, repo, source, opts)
4364 finally:
4364 finally:
4365 del repo._subtoppath
4365 del repo._subtoppath
4366
4366
4367
4367
4368 @command(
4368 @command(
4369 b'init',
4369 b'init',
4370 remoteopts,
4370 remoteopts,
4371 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4371 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4372 helpcategory=command.CATEGORY_REPO_CREATION,
4372 helpcategory=command.CATEGORY_REPO_CREATION,
4373 helpbasic=True,
4373 helpbasic=True,
4374 norepo=True,
4374 norepo=True,
4375 )
4375 )
4376 def init(ui, dest=b".", **opts):
4376 def init(ui, dest=b".", **opts):
4377 """create a new repository in the given directory
4377 """create a new repository in the given directory
4378
4378
4379 Initialize a new repository in the given directory. If the given
4379 Initialize a new repository in the given directory. If the given
4380 directory does not exist, it will be created.
4380 directory does not exist, it will be created.
4381
4381
4382 If no directory is given, the current directory is used.
4382 If no directory is given, the current directory is used.
4383
4383
4384 It is possible to specify an ``ssh://`` URL as the destination.
4384 It is possible to specify an ``ssh://`` URL as the destination.
4385 See :hg:`help urls` for more information.
4385 See :hg:`help urls` for more information.
4386
4386
4387 Returns 0 on success.
4387 Returns 0 on success.
4388 """
4388 """
4389 opts = pycompat.byteskwargs(opts)
4389 opts = pycompat.byteskwargs(opts)
4390 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4390 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4391
4391
4392
4392
4393 @command(
4393 @command(
4394 b'locate',
4394 b'locate',
4395 [
4395 [
4396 (
4396 (
4397 b'r',
4397 b'r',
4398 b'rev',
4398 b'rev',
4399 b'',
4399 b'',
4400 _(b'search the repository as it is in REV'),
4400 _(b'search the repository as it is in REV'),
4401 _(b'REV'),
4401 _(b'REV'),
4402 ),
4402 ),
4403 (
4403 (
4404 b'0',
4404 b'0',
4405 b'print0',
4405 b'print0',
4406 None,
4406 None,
4407 _(b'end filenames with NUL, for use with xargs'),
4407 _(b'end filenames with NUL, for use with xargs'),
4408 ),
4408 ),
4409 (
4409 (
4410 b'f',
4410 b'f',
4411 b'fullpath',
4411 b'fullpath',
4412 None,
4412 None,
4413 _(b'print complete paths from the filesystem root'),
4413 _(b'print complete paths from the filesystem root'),
4414 ),
4414 ),
4415 ]
4415 ]
4416 + walkopts,
4416 + walkopts,
4417 _(b'[OPTION]... [PATTERN]...'),
4417 _(b'[OPTION]... [PATTERN]...'),
4418 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4418 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4419 )
4419 )
4420 def locate(ui, repo, *pats, **opts):
4420 def locate(ui, repo, *pats, **opts):
4421 """locate files matching specific patterns (DEPRECATED)
4421 """locate files matching specific patterns (DEPRECATED)
4422
4422
4423 Print files under Mercurial control in the working directory whose
4423 Print files under Mercurial control in the working directory whose
4424 names match the given patterns.
4424 names match the given patterns.
4425
4425
4426 By default, this command searches all directories in the working
4426 By default, this command searches all directories in the working
4427 directory. To search just the current directory and its
4427 directory. To search just the current directory and its
4428 subdirectories, use "--include .".
4428 subdirectories, use "--include .".
4429
4429
4430 If no patterns are given to match, this command prints the names
4430 If no patterns are given to match, this command prints the names
4431 of all files under Mercurial control in the working directory.
4431 of all files under Mercurial control in the working directory.
4432
4432
4433 If you want to feed the output of this command into the "xargs"
4433 If you want to feed the output of this command into the "xargs"
4434 command, use the -0 option to both this command and "xargs". This
4434 command, use the -0 option to both this command and "xargs". This
4435 will avoid the problem of "xargs" treating single filenames that
4435 will avoid the problem of "xargs" treating single filenames that
4436 contain whitespace as multiple filenames.
4436 contain whitespace as multiple filenames.
4437
4437
4438 See :hg:`help files` for a more versatile command.
4438 See :hg:`help files` for a more versatile command.
4439
4439
4440 Returns 0 if a match is found, 1 otherwise.
4440 Returns 0 if a match is found, 1 otherwise.
4441 """
4441 """
4442 opts = pycompat.byteskwargs(opts)
4442 opts = pycompat.byteskwargs(opts)
4443 if opts.get(b'print0'):
4443 if opts.get(b'print0'):
4444 end = b'\0'
4444 end = b'\0'
4445 else:
4445 else:
4446 end = b'\n'
4446 end = b'\n'
4447 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
4447 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
4448
4448
4449 ret = 1
4449 ret = 1
4450 m = scmutil.match(
4450 m = scmutil.match(
4451 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4451 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4452 )
4452 )
4453
4453
4454 ui.pager(b'locate')
4454 ui.pager(b'locate')
4455 if ctx.rev() is None:
4455 if ctx.rev() is None:
4456 # When run on the working copy, "locate" includes removed files, so
4456 # When run on the working copy, "locate" includes removed files, so
4457 # we get the list of files from the dirstate.
4457 # we get the list of files from the dirstate.
4458 filesgen = sorted(repo.dirstate.matches(m))
4458 filesgen = sorted(repo.dirstate.matches(m))
4459 else:
4459 else:
4460 filesgen = ctx.matches(m)
4460 filesgen = ctx.matches(m)
4461 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4461 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4462 for abs in filesgen:
4462 for abs in filesgen:
4463 if opts.get(b'fullpath'):
4463 if opts.get(b'fullpath'):
4464 ui.write(repo.wjoin(abs), end)
4464 ui.write(repo.wjoin(abs), end)
4465 else:
4465 else:
4466 ui.write(uipathfn(abs), end)
4466 ui.write(uipathfn(abs), end)
4467 ret = 0
4467 ret = 0
4468
4468
4469 return ret
4469 return ret
4470
4470
4471
4471
4472 @command(
4472 @command(
4473 b'log|history',
4473 b'log|history',
4474 [
4474 [
4475 (
4475 (
4476 b'f',
4476 b'f',
4477 b'follow',
4477 b'follow',
4478 None,
4478 None,
4479 _(
4479 _(
4480 b'follow changeset history, or file history across copies and renames'
4480 b'follow changeset history, or file history across copies and renames'
4481 ),
4481 ),
4482 ),
4482 ),
4483 (
4483 (
4484 b'',
4484 b'',
4485 b'follow-first',
4485 b'follow-first',
4486 None,
4486 None,
4487 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4487 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4488 ),
4488 ),
4489 (
4489 (
4490 b'd',
4490 b'd',
4491 b'date',
4491 b'date',
4492 b'',
4492 b'',
4493 _(b'show revisions matching date spec'),
4493 _(b'show revisions matching date spec'),
4494 _(b'DATE'),
4494 _(b'DATE'),
4495 ),
4495 ),
4496 (b'C', b'copies', None, _(b'show copied files')),
4496 (b'C', b'copies', None, _(b'show copied files')),
4497 (
4497 (
4498 b'k',
4498 b'k',
4499 b'keyword',
4499 b'keyword',
4500 [],
4500 [],
4501 _(b'do case-insensitive search for a given text'),
4501 _(b'do case-insensitive search for a given text'),
4502 _(b'TEXT'),
4502 _(b'TEXT'),
4503 ),
4503 ),
4504 (
4504 (
4505 b'r',
4505 b'r',
4506 b'rev',
4506 b'rev',
4507 [],
4507 [],
4508 _(b'show the specified revision or revset'),
4508 _(b'show the specified revision or revset'),
4509 _(b'REV'),
4509 _(b'REV'),
4510 ),
4510 ),
4511 (
4511 (
4512 b'L',
4512 b'L',
4513 b'line-range',
4513 b'line-range',
4514 [],
4514 [],
4515 _(b'follow line range of specified file (EXPERIMENTAL)'),
4515 _(b'follow line range of specified file (EXPERIMENTAL)'),
4516 _(b'FILE,RANGE'),
4516 _(b'FILE,RANGE'),
4517 ),
4517 ),
4518 (
4518 (
4519 b'',
4519 b'',
4520 b'removed',
4520 b'removed',
4521 None,
4521 None,
4522 _(b'include revisions where files were removed'),
4522 _(b'include revisions where files were removed'),
4523 ),
4523 ),
4524 (
4524 (
4525 b'm',
4525 b'm',
4526 b'only-merges',
4526 b'only-merges',
4527 None,
4527 None,
4528 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4528 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4529 ),
4529 ),
4530 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4530 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4531 (
4531 (
4532 b'',
4532 b'',
4533 b'only-branch',
4533 b'only-branch',
4534 [],
4534 [],
4535 _(
4535 _(
4536 b'show only changesets within the given named branch (DEPRECATED)'
4536 b'show only changesets within the given named branch (DEPRECATED)'
4537 ),
4537 ),
4538 _(b'BRANCH'),
4538 _(b'BRANCH'),
4539 ),
4539 ),
4540 (
4540 (
4541 b'b',
4541 b'b',
4542 b'branch',
4542 b'branch',
4543 [],
4543 [],
4544 _(b'show changesets within the given named branch'),
4544 _(b'show changesets within the given named branch'),
4545 _(b'BRANCH'),
4545 _(b'BRANCH'),
4546 ),
4546 ),
4547 (
4547 (
4548 b'P',
4548 b'P',
4549 b'prune',
4549 b'prune',
4550 [],
4550 [],
4551 _(b'do not display revision or any of its ancestors'),
4551 _(b'do not display revision or any of its ancestors'),
4552 _(b'REV'),
4552 _(b'REV'),
4553 ),
4553 ),
4554 ]
4554 ]
4555 + logopts
4555 + logopts
4556 + walkopts,
4556 + walkopts,
4557 _(b'[OPTION]... [FILE]'),
4557 _(b'[OPTION]... [FILE]'),
4558 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4558 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4559 helpbasic=True,
4559 helpbasic=True,
4560 inferrepo=True,
4560 inferrepo=True,
4561 intents={INTENT_READONLY},
4561 intents={INTENT_READONLY},
4562 )
4562 )
4563 def log(ui, repo, *pats, **opts):
4563 def log(ui, repo, *pats, **opts):
4564 """show revision history of entire repository or files
4564 """show revision history of entire repository or files
4565
4565
4566 Print the revision history of the specified files or the entire
4566 Print the revision history of the specified files or the entire
4567 project.
4567 project.
4568
4568
4569 If no revision range is specified, the default is ``tip:0`` unless
4569 If no revision range is specified, the default is ``tip:0`` unless
4570 --follow is set, in which case the working directory parent is
4570 --follow is set, in which case the working directory parent is
4571 used as the starting revision.
4571 used as the starting revision.
4572
4572
4573 File history is shown without following rename or copy history of
4573 File history is shown without following rename or copy history of
4574 files. Use -f/--follow with a filename to follow history across
4574 files. Use -f/--follow with a filename to follow history across
4575 renames and copies. --follow without a filename will only show
4575 renames and copies. --follow without a filename will only show
4576 ancestors of the starting revision.
4576 ancestors of the starting revision.
4577
4577
4578 By default this command prints revision number and changeset id,
4578 By default this command prints revision number and changeset id,
4579 tags, non-trivial parents, user, date and time, and a summary for
4579 tags, non-trivial parents, user, date and time, and a summary for
4580 each commit. When the -v/--verbose switch is used, the list of
4580 each commit. When the -v/--verbose switch is used, the list of
4581 changed files and full commit message are shown.
4581 changed files and full commit message are shown.
4582
4582
4583 With --graph the revisions are shown as an ASCII art DAG with the most
4583 With --graph the revisions are shown as an ASCII art DAG with the most
4584 recent changeset at the top.
4584 recent changeset at the top.
4585 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
4585 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
4586 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4586 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4587 changeset from the lines below is a parent of the 'o' merge on the same
4587 changeset from the lines below is a parent of the 'o' merge on the same
4588 line.
4588 line.
4589 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4589 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4590 of a '|' indicates one or more revisions in a path are omitted.
4590 of a '|' indicates one or more revisions in a path are omitted.
4591
4591
4592 .. container:: verbose
4592 .. container:: verbose
4593
4593
4594 Use -L/--line-range FILE,M:N options to follow the history of lines
4594 Use -L/--line-range FILE,M:N options to follow the history of lines
4595 from M to N in FILE. With -p/--patch only diff hunks affecting
4595 from M to N in FILE. With -p/--patch only diff hunks affecting
4596 specified line range will be shown. This option requires --follow;
4596 specified line range will be shown. This option requires --follow;
4597 it can be specified multiple times. Currently, this option is not
4597 it can be specified multiple times. Currently, this option is not
4598 compatible with --graph. This option is experimental.
4598 compatible with --graph. This option is experimental.
4599
4599
4600 .. note::
4600 .. note::
4601
4601
4602 :hg:`log --patch` may generate unexpected diff output for merge
4602 :hg:`log --patch` may generate unexpected diff output for merge
4603 changesets, as it will only compare the merge changeset against
4603 changesets, as it will only compare the merge changeset against
4604 its first parent. Also, only files different from BOTH parents
4604 its first parent. Also, only files different from BOTH parents
4605 will appear in files:.
4605 will appear in files:.
4606
4606
4607 .. note::
4607 .. note::
4608
4608
4609 For performance reasons, :hg:`log FILE` may omit duplicate changes
4609 For performance reasons, :hg:`log FILE` may omit duplicate changes
4610 made on branches and will not show removals or mode changes. To
4610 made on branches and will not show removals or mode changes. To
4611 see all such changes, use the --removed switch.
4611 see all such changes, use the --removed switch.
4612
4612
4613 .. container:: verbose
4613 .. container:: verbose
4614
4614
4615 .. note::
4615 .. note::
4616
4616
4617 The history resulting from -L/--line-range options depends on diff
4617 The history resulting from -L/--line-range options depends on diff
4618 options; for instance if white-spaces are ignored, respective changes
4618 options; for instance if white-spaces are ignored, respective changes
4619 with only white-spaces in specified line range will not be listed.
4619 with only white-spaces in specified line range will not be listed.
4620
4620
4621 .. container:: verbose
4621 .. container:: verbose
4622
4622
4623 Some examples:
4623 Some examples:
4624
4624
4625 - changesets with full descriptions and file lists::
4625 - changesets with full descriptions and file lists::
4626
4626
4627 hg log -v
4627 hg log -v
4628
4628
4629 - changesets ancestral to the working directory::
4629 - changesets ancestral to the working directory::
4630
4630
4631 hg log -f
4631 hg log -f
4632
4632
4633 - last 10 commits on the current branch::
4633 - last 10 commits on the current branch::
4634
4634
4635 hg log -l 10 -b .
4635 hg log -l 10 -b .
4636
4636
4637 - changesets showing all modifications of a file, including removals::
4637 - changesets showing all modifications of a file, including removals::
4638
4638
4639 hg log --removed file.c
4639 hg log --removed file.c
4640
4640
4641 - all changesets that touch a directory, with diffs, excluding merges::
4641 - all changesets that touch a directory, with diffs, excluding merges::
4642
4642
4643 hg log -Mp lib/
4643 hg log -Mp lib/
4644
4644
4645 - all revision numbers that match a keyword::
4645 - all revision numbers that match a keyword::
4646
4646
4647 hg log -k bug --template "{rev}\\n"
4647 hg log -k bug --template "{rev}\\n"
4648
4648
4649 - the full hash identifier of the working directory parent::
4649 - the full hash identifier of the working directory parent::
4650
4650
4651 hg log -r . --template "{node}\\n"
4651 hg log -r . --template "{node}\\n"
4652
4652
4653 - list available log templates::
4653 - list available log templates::
4654
4654
4655 hg log -T list
4655 hg log -T list
4656
4656
4657 - check if a given changeset is included in a tagged release::
4657 - check if a given changeset is included in a tagged release::
4658
4658
4659 hg log -r "a21ccf and ancestor(1.9)"
4659 hg log -r "a21ccf and ancestor(1.9)"
4660
4660
4661 - find all changesets by some user in a date range::
4661 - find all changesets by some user in a date range::
4662
4662
4663 hg log -k alice -d "may 2008 to jul 2008"
4663 hg log -k alice -d "may 2008 to jul 2008"
4664
4664
4665 - summary of all changesets after the last tag::
4665 - summary of all changesets after the last tag::
4666
4666
4667 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4667 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4668
4668
4669 - changesets touching lines 13 to 23 for file.c::
4669 - changesets touching lines 13 to 23 for file.c::
4670
4670
4671 hg log -L file.c,13:23
4671 hg log -L file.c,13:23
4672
4672
4673 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4673 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4674 main.c with patch::
4674 main.c with patch::
4675
4675
4676 hg log -L file.c,13:23 -L main.c,2:6 -p
4676 hg log -L file.c,13:23 -L main.c,2:6 -p
4677
4677
4678 See :hg:`help dates` for a list of formats valid for -d/--date.
4678 See :hg:`help dates` for a list of formats valid for -d/--date.
4679
4679
4680 See :hg:`help revisions` for more about specifying and ordering
4680 See :hg:`help revisions` for more about specifying and ordering
4681 revisions.
4681 revisions.
4682
4682
4683 See :hg:`help templates` for more about pre-packaged styles and
4683 See :hg:`help templates` for more about pre-packaged styles and
4684 specifying custom templates. The default template used by the log
4684 specifying custom templates. The default template used by the log
4685 command can be customized via the ``ui.logtemplate`` configuration
4685 command can be customized via the ``ui.logtemplate`` configuration
4686 setting.
4686 setting.
4687
4687
4688 Returns 0 on success.
4688 Returns 0 on success.
4689
4689
4690 """
4690 """
4691 opts = pycompat.byteskwargs(opts)
4691 opts = pycompat.byteskwargs(opts)
4692 linerange = opts.get(b'line_range')
4692 linerange = opts.get(b'line_range')
4693
4693
4694 if linerange and not opts.get(b'follow'):
4694 if linerange and not opts.get(b'follow'):
4695 raise error.Abort(_(b'--line-range requires --follow'))
4695 raise error.Abort(_(b'--line-range requires --follow'))
4696
4696
4697 if linerange and pats:
4697 if linerange and pats:
4698 # TODO: take pats as patterns with no line-range filter
4698 # TODO: take pats as patterns with no line-range filter
4699 raise error.Abort(
4699 raise error.Abort(
4700 _(b'FILE arguments are not compatible with --line-range option')
4700 _(b'FILE arguments are not compatible with --line-range option')
4701 )
4701 )
4702
4702
4703 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4703 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4704 revs, differ = logcmdutil.getrevs(repo, pats, opts)
4704 revs, differ = logcmdutil.getrevs(repo, pats, opts)
4705 if linerange:
4705 if linerange:
4706 # TODO: should follow file history from logcmdutil._initialrevs(),
4706 # TODO: should follow file history from logcmdutil._initialrevs(),
4707 # then filter the result by logcmdutil._makerevset() and --limit
4707 # then filter the result by logcmdutil._makerevset() and --limit
4708 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4708 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4709
4709
4710 getcopies = None
4710 getcopies = None
4711 if opts.get(b'copies'):
4711 if opts.get(b'copies'):
4712 endrev = None
4712 endrev = None
4713 if revs:
4713 if revs:
4714 endrev = revs.max() + 1
4714 endrev = revs.max() + 1
4715 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4715 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4716
4716
4717 ui.pager(b'log')
4717 ui.pager(b'log')
4718 displayer = logcmdutil.changesetdisplayer(
4718 displayer = logcmdutil.changesetdisplayer(
4719 ui, repo, opts, differ, buffered=True
4719 ui, repo, opts, differ, buffered=True
4720 )
4720 )
4721 if opts.get(b'graph'):
4721 if opts.get(b'graph'):
4722 displayfn = logcmdutil.displaygraphrevs
4722 displayfn = logcmdutil.displaygraphrevs
4723 else:
4723 else:
4724 displayfn = logcmdutil.displayrevs
4724 displayfn = logcmdutil.displayrevs
4725 displayfn(ui, repo, revs, displayer, getcopies)
4725 displayfn(ui, repo, revs, displayer, getcopies)
4726
4726
4727
4727
4728 @command(
4728 @command(
4729 b'manifest',
4729 b'manifest',
4730 [
4730 [
4731 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4731 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4732 (b'', b'all', False, _(b"list files from all revisions")),
4732 (b'', b'all', False, _(b"list files from all revisions")),
4733 ]
4733 ]
4734 + formatteropts,
4734 + formatteropts,
4735 _(b'[-r REV]'),
4735 _(b'[-r REV]'),
4736 helpcategory=command.CATEGORY_MAINTENANCE,
4736 helpcategory=command.CATEGORY_MAINTENANCE,
4737 intents={INTENT_READONLY},
4737 intents={INTENT_READONLY},
4738 )
4738 )
4739 def manifest(ui, repo, node=None, rev=None, **opts):
4739 def manifest(ui, repo, node=None, rev=None, **opts):
4740 """output the current or given revision of the project manifest
4740 """output the current or given revision of the project manifest
4741
4741
4742 Print a list of version controlled files for the given revision.
4742 Print a list of version controlled files for the given revision.
4743 If no revision is given, the first parent of the working directory
4743 If no revision is given, the first parent of the working directory
4744 is used, or the null revision if no revision is checked out.
4744 is used, or the null revision if no revision is checked out.
4745
4745
4746 With -v, print file permissions, symlink and executable bits.
4746 With -v, print file permissions, symlink and executable bits.
4747 With --debug, print file revision hashes.
4747 With --debug, print file revision hashes.
4748
4748
4749 If option --all is specified, the list of all files from all revisions
4749 If option --all is specified, the list of all files from all revisions
4750 is printed. This includes deleted and renamed files.
4750 is printed. This includes deleted and renamed files.
4751
4751
4752 Returns 0 on success.
4752 Returns 0 on success.
4753 """
4753 """
4754 opts = pycompat.byteskwargs(opts)
4754 opts = pycompat.byteskwargs(opts)
4755 fm = ui.formatter(b'manifest', opts)
4755 fm = ui.formatter(b'manifest', opts)
4756
4756
4757 if opts.get(b'all'):
4757 if opts.get(b'all'):
4758 if rev or node:
4758 if rev or node:
4759 raise error.Abort(_(b"can't specify a revision with --all"))
4759 raise error.Abort(_(b"can't specify a revision with --all"))
4760
4760
4761 res = set()
4761 res = set()
4762 for rev in repo:
4762 for rev in repo:
4763 ctx = repo[rev]
4763 ctx = repo[rev]
4764 res |= set(ctx.files())
4764 res |= set(ctx.files())
4765
4765
4766 ui.pager(b'manifest')
4766 ui.pager(b'manifest')
4767 for f in sorted(res):
4767 for f in sorted(res):
4768 fm.startitem()
4768 fm.startitem()
4769 fm.write(b"path", b'%s\n', f)
4769 fm.write(b"path", b'%s\n', f)
4770 fm.end()
4770 fm.end()
4771 return
4771 return
4772
4772
4773 if rev and node:
4773 if rev and node:
4774 raise error.Abort(_(b"please specify just one revision"))
4774 raise error.Abort(_(b"please specify just one revision"))
4775
4775
4776 if not node:
4776 if not node:
4777 node = rev
4777 node = rev
4778
4778
4779 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4779 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4780 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4780 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4781 if node:
4781 if node:
4782 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4782 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4783 ctx = scmutil.revsingle(repo, node)
4783 ctx = scmutil.revsingle(repo, node)
4784 mf = ctx.manifest()
4784 mf = ctx.manifest()
4785 ui.pager(b'manifest')
4785 ui.pager(b'manifest')
4786 for f in ctx:
4786 for f in ctx:
4787 fm.startitem()
4787 fm.startitem()
4788 fm.context(ctx=ctx)
4788 fm.context(ctx=ctx)
4789 fl = ctx[f].flags()
4789 fl = ctx[f].flags()
4790 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4790 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4791 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4791 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4792 fm.write(b'path', b'%s\n', f)
4792 fm.write(b'path', b'%s\n', f)
4793 fm.end()
4793 fm.end()
4794
4794
4795
4795
4796 @command(
4796 @command(
4797 b'merge',
4797 b'merge',
4798 [
4798 [
4799 (
4799 (
4800 b'f',
4800 b'f',
4801 b'force',
4801 b'force',
4802 None,
4802 None,
4803 _(b'force a merge including outstanding changes (DEPRECATED)'),
4803 _(b'force a merge including outstanding changes (DEPRECATED)'),
4804 ),
4804 ),
4805 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4805 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4806 (
4806 (
4807 b'P',
4807 b'P',
4808 b'preview',
4808 b'preview',
4809 None,
4809 None,
4810 _(b'review revisions to merge (no merge is performed)'),
4810 _(b'review revisions to merge (no merge is performed)'),
4811 ),
4811 ),
4812 (b'', b'abort', None, _(b'abort the ongoing merge')),
4812 (b'', b'abort', None, _(b'abort the ongoing merge')),
4813 ]
4813 ]
4814 + mergetoolopts,
4814 + mergetoolopts,
4815 _(b'[-P] [[-r] REV]'),
4815 _(b'[-P] [[-r] REV]'),
4816 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4816 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4817 helpbasic=True,
4817 helpbasic=True,
4818 )
4818 )
4819 def merge(ui, repo, node=None, **opts):
4819 def merge(ui, repo, node=None, **opts):
4820 """merge another revision into working directory
4820 """merge another revision into working directory
4821
4821
4822 The current working directory is updated with all changes made in
4822 The current working directory is updated with all changes made in
4823 the requested revision since the last common predecessor revision.
4823 the requested revision since the last common predecessor revision.
4824
4824
4825 Files that changed between either parent are marked as changed for
4825 Files that changed between either parent are marked as changed for
4826 the next commit and a commit must be performed before any further
4826 the next commit and a commit must be performed before any further
4827 updates to the repository are allowed. The next commit will have
4827 updates to the repository are allowed. The next commit will have
4828 two parents.
4828 two parents.
4829
4829
4830 ``--tool`` can be used to specify the merge tool used for file
4830 ``--tool`` can be used to specify the merge tool used for file
4831 merges. It overrides the HGMERGE environment variable and your
4831 merges. It overrides the HGMERGE environment variable and your
4832 configuration files. See :hg:`help merge-tools` for options.
4832 configuration files. See :hg:`help merge-tools` for options.
4833
4833
4834 If no revision is specified, the working directory's parent is a
4834 If no revision is specified, the working directory's parent is a
4835 head revision, and the current branch contains exactly one other
4835 head revision, and the current branch contains exactly one other
4836 head, the other head is merged with by default. Otherwise, an
4836 head, the other head is merged with by default. Otherwise, an
4837 explicit revision with which to merge must be provided.
4837 explicit revision with which to merge must be provided.
4838
4838
4839 See :hg:`help resolve` for information on handling file conflicts.
4839 See :hg:`help resolve` for information on handling file conflicts.
4840
4840
4841 To undo an uncommitted merge, use :hg:`merge --abort` which
4841 To undo an uncommitted merge, use :hg:`merge --abort` which
4842 will check out a clean copy of the original merge parent, losing
4842 will check out a clean copy of the original merge parent, losing
4843 all changes.
4843 all changes.
4844
4844
4845 Returns 0 on success, 1 if there are unresolved files.
4845 Returns 0 on success, 1 if there are unresolved files.
4846 """
4846 """
4847
4847
4848 opts = pycompat.byteskwargs(opts)
4848 opts = pycompat.byteskwargs(opts)
4849 abort = opts.get(b'abort')
4849 abort = opts.get(b'abort')
4850 if abort and repo.dirstate.p2() == nullid:
4850 if abort and repo.dirstate.p2() == nullid:
4851 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4851 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4852 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4852 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4853 if abort:
4853 if abort:
4854 state = cmdutil.getunfinishedstate(repo)
4854 state = cmdutil.getunfinishedstate(repo)
4855 if state and state._opname != b'merge':
4855 if state and state._opname != b'merge':
4856 raise error.Abort(
4856 raise error.Abort(
4857 _(b'cannot abort merge with %s in progress') % (state._opname),
4857 _(b'cannot abort merge with %s in progress') % (state._opname),
4858 hint=state.hint(),
4858 hint=state.hint(),
4859 )
4859 )
4860 if node:
4860 if node:
4861 raise error.Abort(_(b"cannot specify a node with --abort"))
4861 raise error.Abort(_(b"cannot specify a node with --abort"))
4862 return hg.abortmerge(repo.ui, repo)
4862 return hg.abortmerge(repo.ui, repo)
4863
4863
4864 if opts.get(b'rev') and node:
4864 if opts.get(b'rev') and node:
4865 raise error.Abort(_(b"please specify just one revision"))
4865 raise error.Abort(_(b"please specify just one revision"))
4866 if not node:
4866 if not node:
4867 node = opts.get(b'rev')
4867 node = opts.get(b'rev')
4868
4868
4869 if node:
4869 if node:
4870 node = scmutil.revsingle(repo, node).node()
4870 node = scmutil.revsingle(repo, node).node()
4871 else:
4871 else:
4872 if ui.configbool(b'commands', b'merge.require-rev'):
4872 if ui.configbool(b'commands', b'merge.require-rev'):
4873 raise error.Abort(
4873 raise error.Abort(
4874 _(
4874 _(
4875 b'configuration requires specifying revision to merge '
4875 b'configuration requires specifying revision to merge '
4876 b'with'
4876 b'with'
4877 )
4877 )
4878 )
4878 )
4879 node = repo[destutil.destmerge(repo)].node()
4879 node = repo[destutil.destmerge(repo)].node()
4880
4880
4881 if node is None:
4881 if node is None:
4882 raise error.Abort(_(b'merging with the working copy has no effect'))
4882 raise error.Abort(_(b'merging with the working copy has no effect'))
4883
4883
4884 if opts.get(b'preview'):
4884 if opts.get(b'preview'):
4885 # find nodes that are ancestors of p2 but not of p1
4885 # find nodes that are ancestors of p2 but not of p1
4886 p1 = repo[b'.'].node()
4886 p1 = repo[b'.'].node()
4887 p2 = node
4887 p2 = node
4888 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4888 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4889
4889
4890 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4890 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4891 for node in nodes:
4891 for node in nodes:
4892 displayer.show(repo[node])
4892 displayer.show(repo[node])
4893 displayer.close()
4893 displayer.close()
4894 return 0
4894 return 0
4895
4895
4896 # ui.forcemerge is an internal variable, do not document
4896 # ui.forcemerge is an internal variable, do not document
4897 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4897 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4898 with ui.configoverride(overrides, b'merge'):
4898 with ui.configoverride(overrides, b'merge'):
4899 force = opts.get(b'force')
4899 force = opts.get(b'force')
4900 labels = [b'working copy', b'merge rev']
4900 labels = [b'working copy', b'merge rev']
4901 return hg.merge(
4901 return hg.merge(
4902 repo, node, force=force, mergeforce=force, labels=labels
4902 repo, node, force=force, mergeforce=force, labels=labels
4903 )
4903 )
4904
4904
4905
4905
4906 statemod.addunfinished(
4906 statemod.addunfinished(
4907 b'merge',
4907 b'merge',
4908 fname=None,
4908 fname=None,
4909 clearable=True,
4909 clearable=True,
4910 allowcommit=True,
4910 allowcommit=True,
4911 cmdmsg=_(b'outstanding uncommitted merge'),
4911 cmdmsg=_(b'outstanding uncommitted merge'),
4912 abortfunc=hg.abortmerge,
4912 abortfunc=hg.abortmerge,
4913 statushint=_(
4913 statushint=_(
4914 b'To continue: hg commit\nTo abort: hg merge --abort'
4914 b'To continue: hg commit\nTo abort: hg merge --abort'
4915 ),
4915 ),
4916 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4916 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4917 )
4917 )
4918
4918
4919
4919
4920 @command(
4920 @command(
4921 b'outgoing|out',
4921 b'outgoing|out',
4922 [
4922 [
4923 (
4923 (
4924 b'f',
4924 b'f',
4925 b'force',
4925 b'force',
4926 None,
4926 None,
4927 _(b'run even when the destination is unrelated'),
4927 _(b'run even when the destination is unrelated'),
4928 ),
4928 ),
4929 (
4929 (
4930 b'r',
4930 b'r',
4931 b'rev',
4931 b'rev',
4932 [],
4932 [],
4933 _(b'a changeset intended to be included in the destination'),
4933 _(b'a changeset intended to be included in the destination'),
4934 _(b'REV'),
4934 _(b'REV'),
4935 ),
4935 ),
4936 (b'n', b'newest-first', None, _(b'show newest record first')),
4936 (b'n', b'newest-first', None, _(b'show newest record first')),
4937 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4937 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4938 (
4938 (
4939 b'b',
4939 b'b',
4940 b'branch',
4940 b'branch',
4941 [],
4941 [],
4942 _(b'a specific branch you would like to push'),
4942 _(b'a specific branch you would like to push'),
4943 _(b'BRANCH'),
4943 _(b'BRANCH'),
4944 ),
4944 ),
4945 ]
4945 ]
4946 + logopts
4946 + logopts
4947 + remoteopts
4947 + remoteopts
4948 + subrepoopts,
4948 + subrepoopts,
4949 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4949 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4950 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4950 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4951 )
4951 )
4952 def outgoing(ui, repo, dest=None, **opts):
4952 def outgoing(ui, repo, dest=None, **opts):
4953 """show changesets not found in the destination
4953 """show changesets not found in the destination
4954
4954
4955 Show changesets not found in the specified destination repository
4955 Show changesets not found in the specified destination repository
4956 or the default push location. These are the changesets that would
4956 or the default push location. These are the changesets that would
4957 be pushed if a push was requested.
4957 be pushed if a push was requested.
4958
4958
4959 See pull for details of valid destination formats.
4959 See pull for details of valid destination formats.
4960
4960
4961 .. container:: verbose
4961 .. container:: verbose
4962
4962
4963 With -B/--bookmarks, the result of bookmark comparison between
4963 With -B/--bookmarks, the result of bookmark comparison between
4964 local and remote repositories is displayed. With -v/--verbose,
4964 local and remote repositories is displayed. With -v/--verbose,
4965 status is also displayed for each bookmark like below::
4965 status is also displayed for each bookmark like below::
4966
4966
4967 BM1 01234567890a added
4967 BM1 01234567890a added
4968 BM2 deleted
4968 BM2 deleted
4969 BM3 234567890abc advanced
4969 BM3 234567890abc advanced
4970 BM4 34567890abcd diverged
4970 BM4 34567890abcd diverged
4971 BM5 4567890abcde changed
4971 BM5 4567890abcde changed
4972
4972
4973 The action taken when pushing depends on the
4973 The action taken when pushing depends on the
4974 status of each bookmark:
4974 status of each bookmark:
4975
4975
4976 :``added``: push with ``-B`` will create it
4976 :``added``: push with ``-B`` will create it
4977 :``deleted``: push with ``-B`` will delete it
4977 :``deleted``: push with ``-B`` will delete it
4978 :``advanced``: push will update it
4978 :``advanced``: push will update it
4979 :``diverged``: push with ``-B`` will update it
4979 :``diverged``: push with ``-B`` will update it
4980 :``changed``: push with ``-B`` will update it
4980 :``changed``: push with ``-B`` will update it
4981
4981
4982 From the point of view of pushing behavior, bookmarks
4982 From the point of view of pushing behavior, bookmarks
4983 existing only in the remote repository are treated as
4983 existing only in the remote repository are treated as
4984 ``deleted``, even if it is in fact added remotely.
4984 ``deleted``, even if it is in fact added remotely.
4985
4985
4986 Returns 0 if there are outgoing changes, 1 otherwise.
4986 Returns 0 if there are outgoing changes, 1 otherwise.
4987 """
4987 """
4988 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4988 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4989 # style URLs, so don't overwrite dest.
4989 # style URLs, so don't overwrite dest.
4990 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
4990 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
4991 if not path:
4991 if not path:
4992 raise error.Abort(
4992 raise error.Abort(
4993 _(b'default repository not configured!'),
4993 _(b'default repository not configured!'),
4994 hint=_(b"see 'hg help config.paths'"),
4994 hint=_(b"see 'hg help config.paths'"),
4995 )
4995 )
4996
4996
4997 opts = pycompat.byteskwargs(opts)
4997 opts = pycompat.byteskwargs(opts)
4998 if opts.get(b'graph'):
4998 if opts.get(b'graph'):
4999 logcmdutil.checkunsupportedgraphflags([], opts)
4999 logcmdutil.checkunsupportedgraphflags([], opts)
5000 o, other = hg._outgoing(ui, repo, dest, opts)
5000 o, other = hg._outgoing(ui, repo, dest, opts)
5001 if not o:
5001 if not o:
5002 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5002 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5003 return
5003 return
5004
5004
5005 revdag = logcmdutil.graphrevs(repo, o, opts)
5005 revdag = logcmdutil.graphrevs(repo, o, opts)
5006 ui.pager(b'outgoing')
5006 ui.pager(b'outgoing')
5007 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
5007 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
5008 logcmdutil.displaygraph(
5008 logcmdutil.displaygraph(
5009 ui, repo, revdag, displayer, graphmod.asciiedges
5009 ui, repo, revdag, displayer, graphmod.asciiedges
5010 )
5010 )
5011 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5011 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5012 return 0
5012 return 0
5013
5013
5014 if opts.get(b'bookmarks'):
5014 if opts.get(b'bookmarks'):
5015 dest = path.pushloc or path.loc
5015 dest = path.pushloc or path.loc
5016 other = hg.peer(repo, opts, dest)
5016 other = hg.peer(repo, opts, dest)
5017 if b'bookmarks' not in other.listkeys(b'namespaces'):
5017 if b'bookmarks' not in other.listkeys(b'namespaces'):
5018 ui.warn(_(b"remote doesn't support bookmarks\n"))
5018 ui.warn(_(b"remote doesn't support bookmarks\n"))
5019 return 0
5019 return 0
5020 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
5020 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
5021 ui.pager(b'outgoing')
5021 ui.pager(b'outgoing')
5022 return bookmarks.outgoing(ui, repo, other)
5022 return bookmarks.outgoing(ui, repo, other)
5023
5023
5024 repo._subtoppath = path.pushloc or path.loc
5024 repo._subtoppath = path.pushloc or path.loc
5025 try:
5025 try:
5026 return hg.outgoing(ui, repo, dest, opts)
5026 return hg.outgoing(ui, repo, dest, opts)
5027 finally:
5027 finally:
5028 del repo._subtoppath
5028 del repo._subtoppath
5029
5029
5030
5030
5031 @command(
5031 @command(
5032 b'parents',
5032 b'parents',
5033 [
5033 [
5034 (
5034 (
5035 b'r',
5035 b'r',
5036 b'rev',
5036 b'rev',
5037 b'',
5037 b'',
5038 _(b'show parents of the specified revision'),
5038 _(b'show parents of the specified revision'),
5039 _(b'REV'),
5039 _(b'REV'),
5040 ),
5040 ),
5041 ]
5041 ]
5042 + templateopts,
5042 + templateopts,
5043 _(b'[-r REV] [FILE]'),
5043 _(b'[-r REV] [FILE]'),
5044 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5044 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5045 inferrepo=True,
5045 inferrepo=True,
5046 )
5046 )
5047 def parents(ui, repo, file_=None, **opts):
5047 def parents(ui, repo, file_=None, **opts):
5048 """show the parents of the working directory or revision (DEPRECATED)
5048 """show the parents of the working directory or revision (DEPRECATED)
5049
5049
5050 Print the working directory's parent revisions. If a revision is
5050 Print the working directory's parent revisions. If a revision is
5051 given via -r/--rev, the parent of that revision will be printed.
5051 given via -r/--rev, the parent of that revision will be printed.
5052 If a file argument is given, the revision in which the file was
5052 If a file argument is given, the revision in which the file was
5053 last changed (before the working directory revision or the
5053 last changed (before the working directory revision or the
5054 argument to --rev if given) is printed.
5054 argument to --rev if given) is printed.
5055
5055
5056 This command is equivalent to::
5056 This command is equivalent to::
5057
5057
5058 hg log -r "p1()+p2()" or
5058 hg log -r "p1()+p2()" or
5059 hg log -r "p1(REV)+p2(REV)" or
5059 hg log -r "p1(REV)+p2(REV)" or
5060 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5060 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5061 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5061 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5062
5062
5063 See :hg:`summary` and :hg:`help revsets` for related information.
5063 See :hg:`summary` and :hg:`help revsets` for related information.
5064
5064
5065 Returns 0 on success.
5065 Returns 0 on success.
5066 """
5066 """
5067
5067
5068 opts = pycompat.byteskwargs(opts)
5068 opts = pycompat.byteskwargs(opts)
5069 rev = opts.get(b'rev')
5069 rev = opts.get(b'rev')
5070 if rev:
5070 if rev:
5071 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5071 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5072 ctx = scmutil.revsingle(repo, rev, None)
5072 ctx = scmutil.revsingle(repo, rev, None)
5073
5073
5074 if file_:
5074 if file_:
5075 m = scmutil.match(ctx, (file_,), opts)
5075 m = scmutil.match(ctx, (file_,), opts)
5076 if m.anypats() or len(m.files()) != 1:
5076 if m.anypats() or len(m.files()) != 1:
5077 raise error.Abort(_(b'can only specify an explicit filename'))
5077 raise error.Abort(_(b'can only specify an explicit filename'))
5078 file_ = m.files()[0]
5078 file_ = m.files()[0]
5079 filenodes = []
5079 filenodes = []
5080 for cp in ctx.parents():
5080 for cp in ctx.parents():
5081 if not cp:
5081 if not cp:
5082 continue
5082 continue
5083 try:
5083 try:
5084 filenodes.append(cp.filenode(file_))
5084 filenodes.append(cp.filenode(file_))
5085 except error.LookupError:
5085 except error.LookupError:
5086 pass
5086 pass
5087 if not filenodes:
5087 if not filenodes:
5088 raise error.Abort(_(b"'%s' not found in manifest!") % file_)
5088 raise error.Abort(_(b"'%s' not found in manifest!") % file_)
5089 p = []
5089 p = []
5090 for fn in filenodes:
5090 for fn in filenodes:
5091 fctx = repo.filectx(file_, fileid=fn)
5091 fctx = repo.filectx(file_, fileid=fn)
5092 p.append(fctx.node())
5092 p.append(fctx.node())
5093 else:
5093 else:
5094 p = [cp.node() for cp in ctx.parents()]
5094 p = [cp.node() for cp in ctx.parents()]
5095
5095
5096 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5096 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5097 for n in p:
5097 for n in p:
5098 if n != nullid:
5098 if n != nullid:
5099 displayer.show(repo[n])
5099 displayer.show(repo[n])
5100 displayer.close()
5100 displayer.close()
5101
5101
5102
5102
5103 @command(
5103 @command(
5104 b'paths',
5104 b'paths',
5105 formatteropts,
5105 formatteropts,
5106 _(b'[NAME]'),
5106 _(b'[NAME]'),
5107 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5107 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5108 optionalrepo=True,
5108 optionalrepo=True,
5109 intents={INTENT_READONLY},
5109 intents={INTENT_READONLY},
5110 )
5110 )
5111 def paths(ui, repo, search=None, **opts):
5111 def paths(ui, repo, search=None, **opts):
5112 """show aliases for remote repositories
5112 """show aliases for remote repositories
5113
5113
5114 Show definition of symbolic path name NAME. If no name is given,
5114 Show definition of symbolic path name NAME. If no name is given,
5115 show definition of all available names.
5115 show definition of all available names.
5116
5116
5117 Option -q/--quiet suppresses all output when searching for NAME
5117 Option -q/--quiet suppresses all output when searching for NAME
5118 and shows only the path names when listing all definitions.
5118 and shows only the path names when listing all definitions.
5119
5119
5120 Path names are defined in the [paths] section of your
5120 Path names are defined in the [paths] section of your
5121 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5121 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5122 repository, ``.hg/hgrc`` is used, too.
5122 repository, ``.hg/hgrc`` is used, too.
5123
5123
5124 The path names ``default`` and ``default-push`` have a special
5124 The path names ``default`` and ``default-push`` have a special
5125 meaning. When performing a push or pull operation, they are used
5125 meaning. When performing a push or pull operation, they are used
5126 as fallbacks if no location is specified on the command-line.
5126 as fallbacks if no location is specified on the command-line.
5127 When ``default-push`` is set, it will be used for push and
5127 When ``default-push`` is set, it will be used for push and
5128 ``default`` will be used for pull; otherwise ``default`` is used
5128 ``default`` will be used for pull; otherwise ``default`` is used
5129 as the fallback for both. When cloning a repository, the clone
5129 as the fallback for both. When cloning a repository, the clone
5130 source is written as ``default`` in ``.hg/hgrc``.
5130 source is written as ``default`` in ``.hg/hgrc``.
5131
5131
5132 .. note::
5132 .. note::
5133
5133
5134 ``default`` and ``default-push`` apply to all inbound (e.g.
5134 ``default`` and ``default-push`` apply to all inbound (e.g.
5135 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5135 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5136 and :hg:`bundle`) operations.
5136 and :hg:`bundle`) operations.
5137
5137
5138 See :hg:`help urls` for more information.
5138 See :hg:`help urls` for more information.
5139
5139
5140 .. container:: verbose
5140 .. container:: verbose
5141
5141
5142 Template:
5142 Template:
5143
5143
5144 The following keywords are supported. See also :hg:`help templates`.
5144 The following keywords are supported. See also :hg:`help templates`.
5145
5145
5146 :name: String. Symbolic name of the path alias.
5146 :name: String. Symbolic name of the path alias.
5147 :pushurl: String. URL for push operations.
5147 :pushurl: String. URL for push operations.
5148 :url: String. URL or directory path for the other operations.
5148 :url: String. URL or directory path for the other operations.
5149
5149
5150 Returns 0 on success.
5150 Returns 0 on success.
5151 """
5151 """
5152
5152
5153 opts = pycompat.byteskwargs(opts)
5153 opts = pycompat.byteskwargs(opts)
5154 ui.pager(b'paths')
5154 ui.pager(b'paths')
5155 if search:
5155 if search:
5156 pathitems = [
5156 pathitems = [
5157 (name, path)
5157 (name, path)
5158 for name, path in pycompat.iteritems(ui.paths)
5158 for name, path in pycompat.iteritems(ui.paths)
5159 if name == search
5159 if name == search
5160 ]
5160 ]
5161 else:
5161 else:
5162 pathitems = sorted(pycompat.iteritems(ui.paths))
5162 pathitems = sorted(pycompat.iteritems(ui.paths))
5163
5163
5164 fm = ui.formatter(b'paths', opts)
5164 fm = ui.formatter(b'paths', opts)
5165 if fm.isplain():
5165 if fm.isplain():
5166 hidepassword = util.hidepassword
5166 hidepassword = util.hidepassword
5167 else:
5167 else:
5168 hidepassword = bytes
5168 hidepassword = bytes
5169 if ui.quiet:
5169 if ui.quiet:
5170 namefmt = b'%s\n'
5170 namefmt = b'%s\n'
5171 else:
5171 else:
5172 namefmt = b'%s = '
5172 namefmt = b'%s = '
5173 showsubopts = not search and not ui.quiet
5173 showsubopts = not search and not ui.quiet
5174
5174
5175 for name, path in pathitems:
5175 for name, path in pathitems:
5176 fm.startitem()
5176 fm.startitem()
5177 fm.condwrite(not search, b'name', namefmt, name)
5177 fm.condwrite(not search, b'name', namefmt, name)
5178 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5178 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5179 for subopt, value in sorted(path.suboptions.items()):
5179 for subopt, value in sorted(path.suboptions.items()):
5180 assert subopt not in (b'name', b'url')
5180 assert subopt not in (b'name', b'url')
5181 if showsubopts:
5181 if showsubopts:
5182 fm.plain(b'%s:%s = ' % (name, subopt))
5182 fm.plain(b'%s:%s = ' % (name, subopt))
5183 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5183 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5184
5184
5185 fm.end()
5185 fm.end()
5186
5186
5187 if search and not pathitems:
5187 if search and not pathitems:
5188 if not ui.quiet:
5188 if not ui.quiet:
5189 ui.warn(_(b"not found!\n"))
5189 ui.warn(_(b"not found!\n"))
5190 return 1
5190 return 1
5191 else:
5191 else:
5192 return 0
5192 return 0
5193
5193
5194
5194
5195 @command(
5195 @command(
5196 b'phase',
5196 b'phase',
5197 [
5197 [
5198 (b'p', b'public', False, _(b'set changeset phase to public')),
5198 (b'p', b'public', False, _(b'set changeset phase to public')),
5199 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5199 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5200 (b's', b'secret', False, _(b'set changeset phase to secret')),
5200 (b's', b'secret', False, _(b'set changeset phase to secret')),
5201 (b'f', b'force', False, _(b'allow to move boundary backward')),
5201 (b'f', b'force', False, _(b'allow to move boundary backward')),
5202 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5202 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5203 ],
5203 ],
5204 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5204 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5205 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5205 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5206 )
5206 )
5207 def phase(ui, repo, *revs, **opts):
5207 def phase(ui, repo, *revs, **opts):
5208 """set or show the current phase name
5208 """set or show the current phase name
5209
5209
5210 With no argument, show the phase name of the current revision(s).
5210 With no argument, show the phase name of the current revision(s).
5211
5211
5212 With one of -p/--public, -d/--draft or -s/--secret, change the
5212 With one of -p/--public, -d/--draft or -s/--secret, change the
5213 phase value of the specified revisions.
5213 phase value of the specified revisions.
5214
5214
5215 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5215 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5216 lower phase to a higher phase. Phases are ordered as follows::
5216 lower phase to a higher phase. Phases are ordered as follows::
5217
5217
5218 public < draft < secret
5218 public < draft < secret
5219
5219
5220 Returns 0 on success, 1 if some phases could not be changed.
5220 Returns 0 on success, 1 if some phases could not be changed.
5221
5221
5222 (For more information about the phases concept, see :hg:`help phases`.)
5222 (For more information about the phases concept, see :hg:`help phases`.)
5223 """
5223 """
5224 opts = pycompat.byteskwargs(opts)
5224 opts = pycompat.byteskwargs(opts)
5225 # search for a unique phase argument
5225 # search for a unique phase argument
5226 targetphase = None
5226 targetphase = None
5227 for idx, name in enumerate(phases.cmdphasenames):
5227 for idx, name in enumerate(phases.cmdphasenames):
5228 if opts[name]:
5228 if opts[name]:
5229 if targetphase is not None:
5229 if targetphase is not None:
5230 raise error.Abort(_(b'only one phase can be specified'))
5230 raise error.Abort(_(b'only one phase can be specified'))
5231 targetphase = idx
5231 targetphase = idx
5232
5232
5233 # look for specified revision
5233 # look for specified revision
5234 revs = list(revs)
5234 revs = list(revs)
5235 revs.extend(opts[b'rev'])
5235 revs.extend(opts[b'rev'])
5236 if not revs:
5236 if not revs:
5237 # display both parents as the second parent phase can influence
5237 # display both parents as the second parent phase can influence
5238 # the phase of a merge commit
5238 # the phase of a merge commit
5239 revs = [c.rev() for c in repo[None].parents()]
5239 revs = [c.rev() for c in repo[None].parents()]
5240
5240
5241 revs = scmutil.revrange(repo, revs)
5241 revs = scmutil.revrange(repo, revs)
5242
5242
5243 ret = 0
5243 ret = 0
5244 if targetphase is None:
5244 if targetphase is None:
5245 # display
5245 # display
5246 for r in revs:
5246 for r in revs:
5247 ctx = repo[r]
5247 ctx = repo[r]
5248 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5248 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5249 else:
5249 else:
5250 with repo.lock(), repo.transaction(b"phase") as tr:
5250 with repo.lock(), repo.transaction(b"phase") as tr:
5251 # set phase
5251 # set phase
5252 if not revs:
5252 if not revs:
5253 raise error.Abort(_(b'empty revision set'))
5253 raise error.Abort(_(b'empty revision set'))
5254 nodes = [repo[r].node() for r in revs]
5254 nodes = [repo[r].node() for r in revs]
5255 # moving revision from public to draft may hide them
5255 # moving revision from public to draft may hide them
5256 # We have to check result on an unfiltered repository
5256 # We have to check result on an unfiltered repository
5257 unfi = repo.unfiltered()
5257 unfi = repo.unfiltered()
5258 getphase = unfi._phasecache.phase
5258 getphase = unfi._phasecache.phase
5259 olddata = [getphase(unfi, r) for r in unfi]
5259 olddata = [getphase(unfi, r) for r in unfi]
5260 phases.advanceboundary(repo, tr, targetphase, nodes)
5260 phases.advanceboundary(repo, tr, targetphase, nodes)
5261 if opts[b'force']:
5261 if opts[b'force']:
5262 phases.retractboundary(repo, tr, targetphase, nodes)
5262 phases.retractboundary(repo, tr, targetphase, nodes)
5263 getphase = unfi._phasecache.phase
5263 getphase = unfi._phasecache.phase
5264 newdata = [getphase(unfi, r) for r in unfi]
5264 newdata = [getphase(unfi, r) for r in unfi]
5265 changes = sum(newdata[r] != olddata[r] for r in unfi)
5265 changes = sum(newdata[r] != olddata[r] for r in unfi)
5266 cl = unfi.changelog
5266 cl = unfi.changelog
5267 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5267 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5268 if rejected:
5268 if rejected:
5269 ui.warn(
5269 ui.warn(
5270 _(
5270 _(
5271 b'cannot move %i changesets to a higher '
5271 b'cannot move %i changesets to a higher '
5272 b'phase, use --force\n'
5272 b'phase, use --force\n'
5273 )
5273 )
5274 % len(rejected)
5274 % len(rejected)
5275 )
5275 )
5276 ret = 1
5276 ret = 1
5277 if changes:
5277 if changes:
5278 msg = _(b'phase changed for %i changesets\n') % changes
5278 msg = _(b'phase changed for %i changesets\n') % changes
5279 if ret:
5279 if ret:
5280 ui.status(msg)
5280 ui.status(msg)
5281 else:
5281 else:
5282 ui.note(msg)
5282 ui.note(msg)
5283 else:
5283 else:
5284 ui.warn(_(b'no phases changed\n'))
5284 ui.warn(_(b'no phases changed\n'))
5285 return ret
5285 return ret
5286
5286
5287
5287
5288 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5288 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5289 """Run after a changegroup has been added via pull/unbundle
5289 """Run after a changegroup has been added via pull/unbundle
5290
5290
5291 This takes arguments below:
5291 This takes arguments below:
5292
5292
5293 :modheads: change of heads by pull/unbundle
5293 :modheads: change of heads by pull/unbundle
5294 :optupdate: updating working directory is needed or not
5294 :optupdate: updating working directory is needed or not
5295 :checkout: update destination revision (or None to default destination)
5295 :checkout: update destination revision (or None to default destination)
5296 :brev: a name, which might be a bookmark to be activated after updating
5296 :brev: a name, which might be a bookmark to be activated after updating
5297 """
5297 """
5298 if modheads == 0:
5298 if modheads == 0:
5299 return
5299 return
5300 if optupdate:
5300 if optupdate:
5301 try:
5301 try:
5302 return hg.updatetotally(ui, repo, checkout, brev)
5302 return hg.updatetotally(ui, repo, checkout, brev)
5303 except error.UpdateAbort as inst:
5303 except error.UpdateAbort as inst:
5304 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5304 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5305 hint = inst.hint
5305 hint = inst.hint
5306 raise error.UpdateAbort(msg, hint=hint)
5306 raise error.UpdateAbort(msg, hint=hint)
5307 if modheads is not None and modheads > 1:
5307 if modheads is not None and modheads > 1:
5308 currentbranchheads = len(repo.branchheads())
5308 currentbranchheads = len(repo.branchheads())
5309 if currentbranchheads == modheads:
5309 if currentbranchheads == modheads:
5310 ui.status(
5310 ui.status(
5311 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5311 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5312 )
5312 )
5313 elif currentbranchheads > 1:
5313 elif currentbranchheads > 1:
5314 ui.status(
5314 ui.status(
5315 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5315 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5316 )
5316 )
5317 else:
5317 else:
5318 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5318 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5319 elif not ui.configbool(b'commands', b'update.requiredest'):
5319 elif not ui.configbool(b'commands', b'update.requiredest'):
5320 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5320 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5321
5321
5322
5322
5323 @command(
5323 @command(
5324 b'pull',
5324 b'pull',
5325 [
5325 [
5326 (
5326 (
5327 b'u',
5327 b'u',
5328 b'update',
5328 b'update',
5329 None,
5329 None,
5330 _(b'update to new branch head if new descendants were pulled'),
5330 _(b'update to new branch head if new descendants were pulled'),
5331 ),
5331 ),
5332 (
5332 (
5333 b'f',
5333 b'f',
5334 b'force',
5334 b'force',
5335 None,
5335 None,
5336 _(b'run even when remote repository is unrelated'),
5336 _(b'run even when remote repository is unrelated'),
5337 ),
5337 ),
5338 (
5338 (
5339 b'r',
5339 b'r',
5340 b'rev',
5340 b'rev',
5341 [],
5341 [],
5342 _(b'a remote changeset intended to be added'),
5342 _(b'a remote changeset intended to be added'),
5343 _(b'REV'),
5343 _(b'REV'),
5344 ),
5344 ),
5345 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5345 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5346 (
5346 (
5347 b'b',
5347 b'b',
5348 b'branch',
5348 b'branch',
5349 [],
5349 [],
5350 _(b'a specific branch you would like to pull'),
5350 _(b'a specific branch you would like to pull'),
5351 _(b'BRANCH'),
5351 _(b'BRANCH'),
5352 ),
5352 ),
5353 ]
5353 ]
5354 + remoteopts,
5354 + remoteopts,
5355 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
5355 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
5356 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5356 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5357 helpbasic=True,
5357 helpbasic=True,
5358 )
5358 )
5359 def pull(ui, repo, source=b"default", **opts):
5359 def pull(ui, repo, source=b"default", **opts):
5360 """pull changes from the specified source
5360 """pull changes from the specified source
5361
5361
5362 Pull changes from a remote repository to a local one.
5362 Pull changes from a remote repository to a local one.
5363
5363
5364 This finds all changes from the repository at the specified path
5364 This finds all changes from the repository at the specified path
5365 or URL and adds them to a local repository (the current one unless
5365 or URL and adds them to a local repository (the current one unless
5366 -R is specified). By default, this does not update the copy of the
5366 -R is specified). By default, this does not update the copy of the
5367 project in the working directory.
5367 project in the working directory.
5368
5368
5369 When cloning from servers that support it, Mercurial may fetch
5369 When cloning from servers that support it, Mercurial may fetch
5370 pre-generated data. When this is done, hooks operating on incoming
5370 pre-generated data. When this is done, hooks operating on incoming
5371 changesets and changegroups may fire more than once, once for each
5371 changesets and changegroups may fire more than once, once for each
5372 pre-generated bundle and as well as for any additional remaining
5372 pre-generated bundle and as well as for any additional remaining
5373 data. See :hg:`help -e clonebundles` for more.
5373 data. See :hg:`help -e clonebundles` for more.
5374
5374
5375 Use :hg:`incoming` if you want to see what would have been added
5375 Use :hg:`incoming` if you want to see what would have been added
5376 by a pull at the time you issued this command. If you then decide
5376 by a pull at the time you issued this command. If you then decide
5377 to add those changes to the repository, you should use :hg:`pull
5377 to add those changes to the repository, you should use :hg:`pull
5378 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5378 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5379
5379
5380 If SOURCE is omitted, the 'default' path will be used.
5380 If SOURCE is omitted, the 'default' path will be used.
5381 See :hg:`help urls` for more information.
5381 See :hg:`help urls` for more information.
5382
5382
5383 Specifying bookmark as ``.`` is equivalent to specifying the active
5383 Specifying bookmark as ``.`` is equivalent to specifying the active
5384 bookmark's name.
5384 bookmark's name.
5385
5385
5386 Returns 0 on success, 1 if an update had unresolved files.
5386 Returns 0 on success, 1 if an update had unresolved files.
5387 """
5387 """
5388
5388
5389 opts = pycompat.byteskwargs(opts)
5389 opts = pycompat.byteskwargs(opts)
5390 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5390 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5391 b'update'
5391 b'update'
5392 ):
5392 ):
5393 msg = _(b'update destination required by configuration')
5393 msg = _(b'update destination required by configuration')
5394 hint = _(b'use hg pull followed by hg update DEST')
5394 hint = _(b'use hg pull followed by hg update DEST')
5395 raise error.Abort(msg, hint=hint)
5395 raise error.Abort(msg, hint=hint)
5396
5396
5397 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
5397 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
5398 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5398 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5399 other = hg.peer(repo, opts, source)
5399 other = hg.peer(repo, opts, source)
5400 try:
5400 try:
5401 revs, checkout = hg.addbranchrevs(
5401 revs, checkout = hg.addbranchrevs(
5402 repo, other, branches, opts.get(b'rev')
5402 repo, other, branches, opts.get(b'rev')
5403 )
5403 )
5404
5404
5405 pullopargs = {}
5405 pullopargs = {}
5406
5406
5407 nodes = None
5407 nodes = None
5408 if opts.get(b'bookmark') or revs:
5408 if opts.get(b'bookmark') or revs:
5409 # The list of bookmark used here is the same used to actually update
5409 # The list of bookmark used here is the same used to actually update
5410 # the bookmark names, to avoid the race from issue 4689 and we do
5410 # the bookmark names, to avoid the race from issue 4689 and we do
5411 # all lookup and bookmark queries in one go so they see the same
5411 # all lookup and bookmark queries in one go so they see the same
5412 # version of the server state (issue 4700).
5412 # version of the server state (issue 4700).
5413 nodes = []
5413 nodes = []
5414 fnodes = []
5414 fnodes = []
5415 revs = revs or []
5415 revs = revs or []
5416 if revs and not other.capable(b'lookup'):
5416 if revs and not other.capable(b'lookup'):
5417 err = _(
5417 err = _(
5418 b"other repository doesn't support revision lookup, "
5418 b"other repository doesn't support revision lookup, "
5419 b"so a rev cannot be specified."
5419 b"so a rev cannot be specified."
5420 )
5420 )
5421 raise error.Abort(err)
5421 raise error.Abort(err)
5422 with other.commandexecutor() as e:
5422 with other.commandexecutor() as e:
5423 fremotebookmarks = e.callcommand(
5423 fremotebookmarks = e.callcommand(
5424 b'listkeys', {b'namespace': b'bookmarks'}
5424 b'listkeys', {b'namespace': b'bookmarks'}
5425 )
5425 )
5426 for r in revs:
5426 for r in revs:
5427 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5427 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5428 remotebookmarks = fremotebookmarks.result()
5428 remotebookmarks = fremotebookmarks.result()
5429 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5429 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5430 pullopargs[b'remotebookmarks'] = remotebookmarks
5430 pullopargs[b'remotebookmarks'] = remotebookmarks
5431 for b in opts.get(b'bookmark', []):
5431 for b in opts.get(b'bookmark', []):
5432 b = repo._bookmarks.expandname(b)
5432 b = repo._bookmarks.expandname(b)
5433 if b not in remotebookmarks:
5433 if b not in remotebookmarks:
5434 raise error.Abort(_(b'remote bookmark %s not found!') % b)
5434 raise error.Abort(_(b'remote bookmark %s not found!') % b)
5435 nodes.append(remotebookmarks[b])
5435 nodes.append(remotebookmarks[b])
5436 for i, rev in enumerate(revs):
5436 for i, rev in enumerate(revs):
5437 node = fnodes[i].result()
5437 node = fnodes[i].result()
5438 nodes.append(node)
5438 nodes.append(node)
5439 if rev == checkout:
5439 if rev == checkout:
5440 checkout = node
5440 checkout = node
5441
5441
5442 wlock = util.nullcontextmanager()
5442 wlock = util.nullcontextmanager()
5443 if opts.get(b'update'):
5443 if opts.get(b'update'):
5444 wlock = repo.wlock()
5444 wlock = repo.wlock()
5445 with wlock:
5445 with wlock:
5446 pullopargs.update(opts.get(b'opargs', {}))
5446 pullopargs.update(opts.get(b'opargs', {}))
5447 modheads = exchange.pull(
5447 modheads = exchange.pull(
5448 repo,
5448 repo,
5449 other,
5449 other,
5450 heads=nodes,
5450 heads=nodes,
5451 force=opts.get(b'force'),
5451 force=opts.get(b'force'),
5452 bookmarks=opts.get(b'bookmark', ()),
5452 bookmarks=opts.get(b'bookmark', ()),
5453 opargs=pullopargs,
5453 opargs=pullopargs,
5454 ).cgresult
5454 ).cgresult
5455
5455
5456 # brev is a name, which might be a bookmark to be activated at
5456 # brev is a name, which might be a bookmark to be activated at
5457 # the end of the update. In other words, it is an explicit
5457 # the end of the update. In other words, it is an explicit
5458 # destination of the update
5458 # destination of the update
5459 brev = None
5459 brev = None
5460
5460
5461 if checkout:
5461 if checkout:
5462 checkout = repo.unfiltered().changelog.rev(checkout)
5462 checkout = repo.unfiltered().changelog.rev(checkout)
5463
5463
5464 # order below depends on implementation of
5464 # order below depends on implementation of
5465 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5465 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5466 # because 'checkout' is determined without it.
5466 # because 'checkout' is determined without it.
5467 if opts.get(b'rev'):
5467 if opts.get(b'rev'):
5468 brev = opts[b'rev'][0]
5468 brev = opts[b'rev'][0]
5469 elif opts.get(b'branch'):
5469 elif opts.get(b'branch'):
5470 brev = opts[b'branch'][0]
5470 brev = opts[b'branch'][0]
5471 else:
5471 else:
5472 brev = branches[0]
5472 brev = branches[0]
5473 repo._subtoppath = source
5473 repo._subtoppath = source
5474 try:
5474 try:
5475 ret = postincoming(
5475 ret = postincoming(
5476 ui, repo, modheads, opts.get(b'update'), checkout, brev
5476 ui, repo, modheads, opts.get(b'update'), checkout, brev
5477 )
5477 )
5478 except error.FilteredRepoLookupError as exc:
5478 except error.FilteredRepoLookupError as exc:
5479 msg = _(b'cannot update to target: %s') % exc.args[0]
5479 msg = _(b'cannot update to target: %s') % exc.args[0]
5480 exc.args = (msg,) + exc.args[1:]
5480 exc.args = (msg,) + exc.args[1:]
5481 raise
5481 raise
5482 finally:
5482 finally:
5483 del repo._subtoppath
5483 del repo._subtoppath
5484
5484
5485 finally:
5485 finally:
5486 other.close()
5486 other.close()
5487 return ret
5487 return ret
5488
5488
5489
5489
5490 @command(
5490 @command(
5491 b'push',
5491 b'push',
5492 [
5492 [
5493 (b'f', b'force', None, _(b'force push')),
5493 (b'f', b'force', None, _(b'force push')),
5494 (
5494 (
5495 b'r',
5495 b'r',
5496 b'rev',
5496 b'rev',
5497 [],
5497 [],
5498 _(b'a changeset intended to be included in the destination'),
5498 _(b'a changeset intended to be included in the destination'),
5499 _(b'REV'),
5499 _(b'REV'),
5500 ),
5500 ),
5501 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5501 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5502 (
5502 (
5503 b'b',
5503 b'b',
5504 b'branch',
5504 b'branch',
5505 [],
5505 [],
5506 _(b'a specific branch you would like to push'),
5506 _(b'a specific branch you would like to push'),
5507 _(b'BRANCH'),
5507 _(b'BRANCH'),
5508 ),
5508 ),
5509 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5509 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5510 (
5510 (
5511 b'',
5511 b'',
5512 b'pushvars',
5512 b'pushvars',
5513 [],
5513 [],
5514 _(b'variables that can be sent to server (ADVANCED)'),
5514 _(b'variables that can be sent to server (ADVANCED)'),
5515 ),
5515 ),
5516 (
5516 (
5517 b'',
5517 b'',
5518 b'publish',
5518 b'publish',
5519 False,
5519 False,
5520 _(b'push the changeset as public (EXPERIMENTAL)'),
5520 _(b'push the changeset as public (EXPERIMENTAL)'),
5521 ),
5521 ),
5522 ]
5522 ]
5523 + remoteopts,
5523 + remoteopts,
5524 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
5524 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
5525 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5525 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5526 helpbasic=True,
5526 helpbasic=True,
5527 )
5527 )
5528 def push(ui, repo, dest=None, **opts):
5528 def push(ui, repo, dest=None, **opts):
5529 """push changes to the specified destination
5529 """push changes to the specified destination
5530
5530
5531 Push changesets from the local repository to the specified
5531 Push changesets from the local repository to the specified
5532 destination.
5532 destination.
5533
5533
5534 This operation is symmetrical to pull: it is identical to a pull
5534 This operation is symmetrical to pull: it is identical to a pull
5535 in the destination repository from the current one.
5535 in the destination repository from the current one.
5536
5536
5537 By default, push will not allow creation of new heads at the
5537 By default, push will not allow creation of new heads at the
5538 destination, since multiple heads would make it unclear which head
5538 destination, since multiple heads would make it unclear which head
5539 to use. In this situation, it is recommended to pull and merge
5539 to use. In this situation, it is recommended to pull and merge
5540 before pushing.
5540 before pushing.
5541
5541
5542 Use --new-branch if you want to allow push to create a new named
5542 Use --new-branch if you want to allow push to create a new named
5543 branch that is not present at the destination. This allows you to
5543 branch that is not present at the destination. This allows you to
5544 only create a new branch without forcing other changes.
5544 only create a new branch without forcing other changes.
5545
5545
5546 .. note::
5546 .. note::
5547
5547
5548 Extra care should be taken with the -f/--force option,
5548 Extra care should be taken with the -f/--force option,
5549 which will push all new heads on all branches, an action which will
5549 which will push all new heads on all branches, an action which will
5550 almost always cause confusion for collaborators.
5550 almost always cause confusion for collaborators.
5551
5551
5552 If -r/--rev is used, the specified revision and all its ancestors
5552 If -r/--rev is used, the specified revision and all its ancestors
5553 will be pushed to the remote repository.
5553 will be pushed to the remote repository.
5554
5554
5555 If -B/--bookmark is used, the specified bookmarked revision, its
5555 If -B/--bookmark is used, the specified bookmarked revision, its
5556 ancestors, and the bookmark will be pushed to the remote
5556 ancestors, and the bookmark will be pushed to the remote
5557 repository. Specifying ``.`` is equivalent to specifying the active
5557 repository. Specifying ``.`` is equivalent to specifying the active
5558 bookmark's name.
5558 bookmark's name.
5559
5559
5560 Please see :hg:`help urls` for important details about ``ssh://``
5560 Please see :hg:`help urls` for important details about ``ssh://``
5561 URLs. If DESTINATION is omitted, a default path will be used.
5561 URLs. If DESTINATION is omitted, a default path will be used.
5562
5562
5563 .. container:: verbose
5563 .. container:: verbose
5564
5564
5565 The --pushvars option sends strings to the server that become
5565 The --pushvars option sends strings to the server that become
5566 environment variables prepended with ``HG_USERVAR_``. For example,
5566 environment variables prepended with ``HG_USERVAR_``. For example,
5567 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5567 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5568 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5568 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5569
5569
5570 pushvars can provide for user-overridable hooks as well as set debug
5570 pushvars can provide for user-overridable hooks as well as set debug
5571 levels. One example is having a hook that blocks commits containing
5571 levels. One example is having a hook that blocks commits containing
5572 conflict markers, but enables the user to override the hook if the file
5572 conflict markers, but enables the user to override the hook if the file
5573 is using conflict markers for testing purposes or the file format has
5573 is using conflict markers for testing purposes or the file format has
5574 strings that look like conflict markers.
5574 strings that look like conflict markers.
5575
5575
5576 By default, servers will ignore `--pushvars`. To enable it add the
5576 By default, servers will ignore `--pushvars`. To enable it add the
5577 following to your configuration file::
5577 following to your configuration file::
5578
5578
5579 [push]
5579 [push]
5580 pushvars.server = true
5580 pushvars.server = true
5581
5581
5582 Returns 0 if push was successful, 1 if nothing to push.
5582 Returns 0 if push was successful, 1 if nothing to push.
5583 """
5583 """
5584
5584
5585 opts = pycompat.byteskwargs(opts)
5585 opts = pycompat.byteskwargs(opts)
5586 if opts.get(b'bookmark'):
5586 if opts.get(b'bookmark'):
5587 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5587 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5588 for b in opts[b'bookmark']:
5588 for b in opts[b'bookmark']:
5589 # translate -B options to -r so changesets get pushed
5589 # translate -B options to -r so changesets get pushed
5590 b = repo._bookmarks.expandname(b)
5590 b = repo._bookmarks.expandname(b)
5591 if b in repo._bookmarks:
5591 if b in repo._bookmarks:
5592 opts.setdefault(b'rev', []).append(b)
5592 opts.setdefault(b'rev', []).append(b)
5593 else:
5593 else:
5594 # if we try to push a deleted bookmark, translate it to null
5594 # if we try to push a deleted bookmark, translate it to null
5595 # this lets simultaneous -r, -b options continue working
5595 # this lets simultaneous -r, -b options continue working
5596 opts.setdefault(b'rev', []).append(b"null")
5596 opts.setdefault(b'rev', []).append(b"null")
5597
5597
5598 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5598 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5599 if not path:
5599 if not path:
5600 raise error.Abort(
5600 raise error.Abort(
5601 _(b'default repository not configured!'),
5601 _(b'default repository not configured!'),
5602 hint=_(b"see 'hg help config.paths'"),
5602 hint=_(b"see 'hg help config.paths'"),
5603 )
5603 )
5604 dest = path.pushloc or path.loc
5604 dest = path.pushloc or path.loc
5605 branches = (path.branch, opts.get(b'branch') or [])
5605 branches = (path.branch, opts.get(b'branch') or [])
5606 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5606 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5607 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
5607 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
5608 other = hg.peer(repo, opts, dest)
5608 other = hg.peer(repo, opts, dest)
5609
5609
5610 if revs:
5610 if revs:
5611 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5611 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5612 if not revs:
5612 if not revs:
5613 raise error.Abort(
5613 raise error.Abort(
5614 _(b"specified revisions evaluate to an empty set"),
5614 _(b"specified revisions evaluate to an empty set"),
5615 hint=_(b"use different revision arguments"),
5615 hint=_(b"use different revision arguments"),
5616 )
5616 )
5617 elif path.pushrev:
5617 elif path.pushrev:
5618 # It doesn't make any sense to specify ancestor revisions. So limit
5618 # It doesn't make any sense to specify ancestor revisions. So limit
5619 # to DAG heads to make discovery simpler.
5619 # to DAG heads to make discovery simpler.
5620 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5620 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5621 revs = scmutil.revrange(repo, [expr])
5621 revs = scmutil.revrange(repo, [expr])
5622 revs = [repo[rev].node() for rev in revs]
5622 revs = [repo[rev].node() for rev in revs]
5623 if not revs:
5623 if not revs:
5624 raise error.Abort(
5624 raise error.Abort(
5625 _(b'default push revset for path evaluates to an empty set')
5625 _(b'default push revset for path evaluates to an empty set')
5626 )
5626 )
5627 elif ui.configbool(b'commands', b'push.require-revs'):
5627 elif ui.configbool(b'commands', b'push.require-revs'):
5628 raise error.Abort(
5628 raise error.Abort(
5629 _(b'no revisions specified to push'),
5629 _(b'no revisions specified to push'),
5630 hint=_(b'did you mean "hg push -r ."?'),
5630 hint=_(b'did you mean "hg push -r ."?'),
5631 )
5631 )
5632
5632
5633 repo._subtoppath = dest
5633 repo._subtoppath = dest
5634 try:
5634 try:
5635 # push subrepos depth-first for coherent ordering
5635 # push subrepos depth-first for coherent ordering
5636 c = repo[b'.']
5636 c = repo[b'.']
5637 subs = c.substate # only repos that are committed
5637 subs = c.substate # only repos that are committed
5638 for s in sorted(subs):
5638 for s in sorted(subs):
5639 result = c.sub(s).push(opts)
5639 result = c.sub(s).push(opts)
5640 if result == 0:
5640 if result == 0:
5641 return not result
5641 return not result
5642 finally:
5642 finally:
5643 del repo._subtoppath
5643 del repo._subtoppath
5644
5644
5645 opargs = dict(opts.get(b'opargs', {})) # copy opargs since we may mutate it
5645 opargs = dict(opts.get(b'opargs', {})) # copy opargs since we may mutate it
5646 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5646 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5647
5647
5648 pushop = exchange.push(
5648 pushop = exchange.push(
5649 repo,
5649 repo,
5650 other,
5650 other,
5651 opts.get(b'force'),
5651 opts.get(b'force'),
5652 revs=revs,
5652 revs=revs,
5653 newbranch=opts.get(b'new_branch'),
5653 newbranch=opts.get(b'new_branch'),
5654 bookmarks=opts.get(b'bookmark', ()),
5654 bookmarks=opts.get(b'bookmark', ()),
5655 publish=opts.get(b'publish'),
5655 publish=opts.get(b'publish'),
5656 opargs=opargs,
5656 opargs=opargs,
5657 )
5657 )
5658
5658
5659 result = not pushop.cgresult
5659 result = not pushop.cgresult
5660
5660
5661 if pushop.bkresult is not None:
5661 if pushop.bkresult is not None:
5662 if pushop.bkresult == 2:
5662 if pushop.bkresult == 2:
5663 result = 2
5663 result = 2
5664 elif not result and pushop.bkresult:
5664 elif not result and pushop.bkresult:
5665 result = 2
5665 result = 2
5666
5666
5667 return result
5667 return result
5668
5668
5669
5669
5670 @command(
5670 @command(
5671 b'recover',
5671 b'recover',
5672 [(b'', b'verify', True, b"run `hg verify` after successful recover"),],
5672 [(b'', b'verify', True, b"run `hg verify` after successful recover"),],
5673 helpcategory=command.CATEGORY_MAINTENANCE,
5673 helpcategory=command.CATEGORY_MAINTENANCE,
5674 )
5674 )
5675 def recover(ui, repo, **opts):
5675 def recover(ui, repo, **opts):
5676 """roll back an interrupted transaction
5676 """roll back an interrupted transaction
5677
5677
5678 Recover from an interrupted commit or pull.
5678 Recover from an interrupted commit or pull.
5679
5679
5680 This command tries to fix the repository status after an
5680 This command tries to fix the repository status after an
5681 interrupted operation. It should only be necessary when Mercurial
5681 interrupted operation. It should only be necessary when Mercurial
5682 suggests it.
5682 suggests it.
5683
5683
5684 Returns 0 if successful, 1 if nothing to recover or verify fails.
5684 Returns 0 if successful, 1 if nothing to recover or verify fails.
5685 """
5685 """
5686 ret = repo.recover()
5686 ret = repo.recover()
5687 if ret:
5687 if ret:
5688 if opts['verify']:
5688 if opts['verify']:
5689 return hg.verify(repo)
5689 return hg.verify(repo)
5690 else:
5690 else:
5691 msg = _(
5691 msg = _(
5692 b"(verify step skipped, run `hg verify` to check your "
5692 b"(verify step skipped, run `hg verify` to check your "
5693 b"repository content)\n"
5693 b"repository content)\n"
5694 )
5694 )
5695 ui.warn(msg)
5695 ui.warn(msg)
5696 return 0
5696 return 0
5697 return 1
5697 return 1
5698
5698
5699
5699
5700 @command(
5700 @command(
5701 b'remove|rm',
5701 b'remove|rm',
5702 [
5702 [
5703 (b'A', b'after', None, _(b'record delete for missing files')),
5703 (b'A', b'after', None, _(b'record delete for missing files')),
5704 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5704 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5705 ]
5705 ]
5706 + subrepoopts
5706 + subrepoopts
5707 + walkopts
5707 + walkopts
5708 + dryrunopts,
5708 + dryrunopts,
5709 _(b'[OPTION]... FILE...'),
5709 _(b'[OPTION]... FILE...'),
5710 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5710 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5711 helpbasic=True,
5711 helpbasic=True,
5712 inferrepo=True,
5712 inferrepo=True,
5713 )
5713 )
5714 def remove(ui, repo, *pats, **opts):
5714 def remove(ui, repo, *pats, **opts):
5715 """remove the specified files on the next commit
5715 """remove the specified files on the next commit
5716
5716
5717 Schedule the indicated files for removal from the current branch.
5717 Schedule the indicated files for removal from the current branch.
5718
5718
5719 This command schedules the files to be removed at the next commit.
5719 This command schedules the files to be removed at the next commit.
5720 To undo a remove before that, see :hg:`revert`. To undo added
5720 To undo a remove before that, see :hg:`revert`. To undo added
5721 files, see :hg:`forget`.
5721 files, see :hg:`forget`.
5722
5722
5723 .. container:: verbose
5723 .. container:: verbose
5724
5724
5725 -A/--after can be used to remove only files that have already
5725 -A/--after can be used to remove only files that have already
5726 been deleted, -f/--force can be used to force deletion, and -Af
5726 been deleted, -f/--force can be used to force deletion, and -Af
5727 can be used to remove files from the next revision without
5727 can be used to remove files from the next revision without
5728 deleting them from the working directory.
5728 deleting them from the working directory.
5729
5729
5730 The following table details the behavior of remove for different
5730 The following table details the behavior of remove for different
5731 file states (columns) and option combinations (rows). The file
5731 file states (columns) and option combinations (rows). The file
5732 states are Added [A], Clean [C], Modified [M] and Missing [!]
5732 states are Added [A], Clean [C], Modified [M] and Missing [!]
5733 (as reported by :hg:`status`). The actions are Warn, Remove
5733 (as reported by :hg:`status`). The actions are Warn, Remove
5734 (from branch) and Delete (from disk):
5734 (from branch) and Delete (from disk):
5735
5735
5736 ========= == == == ==
5736 ========= == == == ==
5737 opt/state A C M !
5737 opt/state A C M !
5738 ========= == == == ==
5738 ========= == == == ==
5739 none W RD W R
5739 none W RD W R
5740 -f R RD RD R
5740 -f R RD RD R
5741 -A W W W R
5741 -A W W W R
5742 -Af R R R R
5742 -Af R R R R
5743 ========= == == == ==
5743 ========= == == == ==
5744
5744
5745 .. note::
5745 .. note::
5746
5746
5747 :hg:`remove` never deletes files in Added [A] state from the
5747 :hg:`remove` never deletes files in Added [A] state from the
5748 working directory, not even if ``--force`` is specified.
5748 working directory, not even if ``--force`` is specified.
5749
5749
5750 Returns 0 on success, 1 if any warnings encountered.
5750 Returns 0 on success, 1 if any warnings encountered.
5751 """
5751 """
5752
5752
5753 opts = pycompat.byteskwargs(opts)
5753 opts = pycompat.byteskwargs(opts)
5754 after, force = opts.get(b'after'), opts.get(b'force')
5754 after, force = opts.get(b'after'), opts.get(b'force')
5755 dryrun = opts.get(b'dry_run')
5755 dryrun = opts.get(b'dry_run')
5756 if not pats and not after:
5756 if not pats and not after:
5757 raise error.Abort(_(b'no files specified'))
5757 raise error.Abort(_(b'no files specified'))
5758
5758
5759 m = scmutil.match(repo[None], pats, opts)
5759 m = scmutil.match(repo[None], pats, opts)
5760 subrepos = opts.get(b'subrepos')
5760 subrepos = opts.get(b'subrepos')
5761 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5761 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5762 return cmdutil.remove(
5762 return cmdutil.remove(
5763 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5763 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5764 )
5764 )
5765
5765
5766
5766
5767 @command(
5767 @command(
5768 b'rename|move|mv',
5768 b'rename|move|mv',
5769 [
5769 [
5770 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5770 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5771 (
5771 (
5772 b'f',
5772 b'f',
5773 b'force',
5773 b'force',
5774 None,
5774 None,
5775 _(b'forcibly move over an existing managed file'),
5775 _(b'forcibly move over an existing managed file'),
5776 ),
5776 ),
5777 ]
5777 ]
5778 + walkopts
5778 + walkopts
5779 + dryrunopts,
5779 + dryrunopts,
5780 _(b'[OPTION]... SOURCE... DEST'),
5780 _(b'[OPTION]... SOURCE... DEST'),
5781 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5781 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5782 )
5782 )
5783 def rename(ui, repo, *pats, **opts):
5783 def rename(ui, repo, *pats, **opts):
5784 """rename files; equivalent of copy + remove
5784 """rename files; equivalent of copy + remove
5785
5785
5786 Mark dest as copies of sources; mark sources for deletion. If dest
5786 Mark dest as copies of sources; mark sources for deletion. If dest
5787 is a directory, copies are put in that directory. If dest is a
5787 is a directory, copies are put in that directory. If dest is a
5788 file, there can only be one source.
5788 file, there can only be one source.
5789
5789
5790 By default, this command copies the contents of files as they
5790 By default, this command copies the contents of files as they
5791 exist in the working directory. If invoked with -A/--after, the
5791 exist in the working directory. If invoked with -A/--after, the
5792 operation is recorded, but no copying is performed.
5792 operation is recorded, but no copying is performed.
5793
5793
5794 This command takes effect at the next commit. To undo a rename
5794 This command takes effect at the next commit. To undo a rename
5795 before that, see :hg:`revert`.
5795 before that, see :hg:`revert`.
5796
5796
5797 Returns 0 on success, 1 if errors are encountered.
5797 Returns 0 on success, 1 if errors are encountered.
5798 """
5798 """
5799 opts = pycompat.byteskwargs(opts)
5799 opts = pycompat.byteskwargs(opts)
5800 with repo.wlock(False):
5800 with repo.wlock(False):
5801 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5801 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5802
5802
5803
5803
5804 @command(
5804 @command(
5805 b'resolve',
5805 b'resolve',
5806 [
5806 [
5807 (b'a', b'all', None, _(b'select all unresolved files')),
5807 (b'a', b'all', None, _(b'select all unresolved files')),
5808 (b'l', b'list', None, _(b'list state of files needing merge')),
5808 (b'l', b'list', None, _(b'list state of files needing merge')),
5809 (b'm', b'mark', None, _(b'mark files as resolved')),
5809 (b'm', b'mark', None, _(b'mark files as resolved')),
5810 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5810 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5811 (b'n', b'no-status', None, _(b'hide status prefix')),
5811 (b'n', b'no-status', None, _(b'hide status prefix')),
5812 (b'', b're-merge', None, _(b're-merge files')),
5812 (b'', b're-merge', None, _(b're-merge files')),
5813 ]
5813 ]
5814 + mergetoolopts
5814 + mergetoolopts
5815 + walkopts
5815 + walkopts
5816 + formatteropts,
5816 + formatteropts,
5817 _(b'[OPTION]... [FILE]...'),
5817 _(b'[OPTION]... [FILE]...'),
5818 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5818 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5819 inferrepo=True,
5819 inferrepo=True,
5820 )
5820 )
5821 def resolve(ui, repo, *pats, **opts):
5821 def resolve(ui, repo, *pats, **opts):
5822 """redo merges or set/view the merge status of files
5822 """redo merges or set/view the merge status of files
5823
5823
5824 Merges with unresolved conflicts are often the result of
5824 Merges with unresolved conflicts are often the result of
5825 non-interactive merging using the ``internal:merge`` configuration
5825 non-interactive merging using the ``internal:merge`` configuration
5826 setting, or a command-line merge tool like ``diff3``. The resolve
5826 setting, or a command-line merge tool like ``diff3``. The resolve
5827 command is used to manage the files involved in a merge, after
5827 command is used to manage the files involved in a merge, after
5828 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5828 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5829 working directory must have two parents). See :hg:`help
5829 working directory must have two parents). See :hg:`help
5830 merge-tools` for information on configuring merge tools.
5830 merge-tools` for information on configuring merge tools.
5831
5831
5832 The resolve command can be used in the following ways:
5832 The resolve command can be used in the following ways:
5833
5833
5834 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5834 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5835 the specified files, discarding any previous merge attempts. Re-merging
5835 the specified files, discarding any previous merge attempts. Re-merging
5836 is not performed for files already marked as resolved. Use ``--all/-a``
5836 is not performed for files already marked as resolved. Use ``--all/-a``
5837 to select all unresolved files. ``--tool`` can be used to specify
5837 to select all unresolved files. ``--tool`` can be used to specify
5838 the merge tool used for the given files. It overrides the HGMERGE
5838 the merge tool used for the given files. It overrides the HGMERGE
5839 environment variable and your configuration files. Previous file
5839 environment variable and your configuration files. Previous file
5840 contents are saved with a ``.orig`` suffix.
5840 contents are saved with a ``.orig`` suffix.
5841
5841
5842 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5842 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5843 (e.g. after having manually fixed-up the files). The default is
5843 (e.g. after having manually fixed-up the files). The default is
5844 to mark all unresolved files.
5844 to mark all unresolved files.
5845
5845
5846 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5846 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5847 default is to mark all resolved files.
5847 default is to mark all resolved files.
5848
5848
5849 - :hg:`resolve -l`: list files which had or still have conflicts.
5849 - :hg:`resolve -l`: list files which had or still have conflicts.
5850 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5850 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5851 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
5851 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
5852 the list. See :hg:`help filesets` for details.
5852 the list. See :hg:`help filesets` for details.
5853
5853
5854 .. note::
5854 .. note::
5855
5855
5856 Mercurial will not let you commit files with unresolved merge
5856 Mercurial will not let you commit files with unresolved merge
5857 conflicts. You must use :hg:`resolve -m ...` before you can
5857 conflicts. You must use :hg:`resolve -m ...` before you can
5858 commit after a conflicting merge.
5858 commit after a conflicting merge.
5859
5859
5860 .. container:: verbose
5860 .. container:: verbose
5861
5861
5862 Template:
5862 Template:
5863
5863
5864 The following keywords are supported in addition to the common template
5864 The following keywords are supported in addition to the common template
5865 keywords and functions. See also :hg:`help templates`.
5865 keywords and functions. See also :hg:`help templates`.
5866
5866
5867 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
5867 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
5868 :path: String. Repository-absolute path of the file.
5868 :path: String. Repository-absolute path of the file.
5869
5869
5870 Returns 0 on success, 1 if any files fail a resolve attempt.
5870 Returns 0 on success, 1 if any files fail a resolve attempt.
5871 """
5871 """
5872
5872
5873 opts = pycompat.byteskwargs(opts)
5873 opts = pycompat.byteskwargs(opts)
5874 confirm = ui.configbool(b'commands', b'resolve.confirm')
5874 confirm = ui.configbool(b'commands', b'resolve.confirm')
5875 flaglist = b'all mark unmark list no_status re_merge'.split()
5875 flaglist = b'all mark unmark list no_status re_merge'.split()
5876 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
5876 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
5877
5877
5878 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
5878 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
5879 if actioncount > 1:
5879 if actioncount > 1:
5880 raise error.Abort(_(b"too many actions specified"))
5880 raise error.Abort(_(b"too many actions specified"))
5881 elif actioncount == 0 and ui.configbool(
5881 elif actioncount == 0 and ui.configbool(
5882 b'commands', b'resolve.explicit-re-merge'
5882 b'commands', b'resolve.explicit-re-merge'
5883 ):
5883 ):
5884 hint = _(b'use --mark, --unmark, --list or --re-merge')
5884 hint = _(b'use --mark, --unmark, --list or --re-merge')
5885 raise error.Abort(_(b'no action specified'), hint=hint)
5885 raise error.Abort(_(b'no action specified'), hint=hint)
5886 if pats and all:
5886 if pats and all:
5887 raise error.Abort(_(b"can't specify --all and patterns"))
5887 raise error.Abort(_(b"can't specify --all and patterns"))
5888 if not (all or pats or show or mark or unmark):
5888 if not (all or pats or show or mark or unmark):
5889 raise error.Abort(
5889 raise error.Abort(
5890 _(b'no files or directories specified'),
5890 _(b'no files or directories specified'),
5891 hint=b'use --all to re-merge all unresolved files',
5891 hint=b'use --all to re-merge all unresolved files',
5892 )
5892 )
5893
5893
5894 if confirm:
5894 if confirm:
5895 if all:
5895 if all:
5896 if ui.promptchoice(
5896 if ui.promptchoice(
5897 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
5897 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
5898 ):
5898 ):
5899 raise error.Abort(_(b'user quit'))
5899 raise error.Abort(_(b'user quit'))
5900 if mark and not pats:
5900 if mark and not pats:
5901 if ui.promptchoice(
5901 if ui.promptchoice(
5902 _(
5902 _(
5903 b'mark all unresolved files as resolved (yn)?'
5903 b'mark all unresolved files as resolved (yn)?'
5904 b'$$ &Yes $$ &No'
5904 b'$$ &Yes $$ &No'
5905 )
5905 )
5906 ):
5906 ):
5907 raise error.Abort(_(b'user quit'))
5907 raise error.Abort(_(b'user quit'))
5908 if unmark and not pats:
5908 if unmark and not pats:
5909 if ui.promptchoice(
5909 if ui.promptchoice(
5910 _(
5910 _(
5911 b'mark all resolved files as unresolved (yn)?'
5911 b'mark all resolved files as unresolved (yn)?'
5912 b'$$ &Yes $$ &No'
5912 b'$$ &Yes $$ &No'
5913 )
5913 )
5914 ):
5914 ):
5915 raise error.Abort(_(b'user quit'))
5915 raise error.Abort(_(b'user quit'))
5916
5916
5917 uipathfn = scmutil.getuipathfn(repo)
5917 uipathfn = scmutil.getuipathfn(repo)
5918
5918
5919 if show:
5919 if show:
5920 ui.pager(b'resolve')
5920 ui.pager(b'resolve')
5921 fm = ui.formatter(b'resolve', opts)
5921 fm = ui.formatter(b'resolve', opts)
5922 ms = mergemod.mergestate.read(repo)
5922 ms = mergemod.mergestate.read(repo)
5923 wctx = repo[None]
5923 wctx = repo[None]
5924 m = scmutil.match(wctx, pats, opts)
5924 m = scmutil.match(wctx, pats, opts)
5925
5925
5926 # Labels and keys based on merge state. Unresolved path conflicts show
5926 # Labels and keys based on merge state. Unresolved path conflicts show
5927 # as 'P'. Resolved path conflicts show as 'R', the same as normal
5927 # as 'P'. Resolved path conflicts show as 'R', the same as normal
5928 # resolved conflicts.
5928 # resolved conflicts.
5929 mergestateinfo = {
5929 mergestateinfo = {
5930 mergemod.MERGE_RECORD_UNRESOLVED: (b'resolve.unresolved', b'U'),
5930 mergemod.MERGE_RECORD_UNRESOLVED: (b'resolve.unresolved', b'U'),
5931 mergemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
5931 mergemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
5932 mergemod.MERGE_RECORD_UNRESOLVED_PATH: (
5932 mergemod.MERGE_RECORD_UNRESOLVED_PATH: (
5933 b'resolve.unresolved',
5933 b'resolve.unresolved',
5934 b'P',
5934 b'P',
5935 ),
5935 ),
5936 mergemod.MERGE_RECORD_RESOLVED_PATH: (b'resolve.resolved', b'R'),
5936 mergemod.MERGE_RECORD_RESOLVED_PATH: (b'resolve.resolved', b'R'),
5937 mergemod.MERGE_RECORD_DRIVER_RESOLVED: (
5937 mergemod.MERGE_RECORD_DRIVER_RESOLVED: (
5938 b'resolve.driverresolved',
5938 b'resolve.driverresolved',
5939 b'D',
5939 b'D',
5940 ),
5940 ),
5941 }
5941 }
5942
5942
5943 for f in ms:
5943 for f in ms:
5944 if not m(f):
5944 if not m(f):
5945 continue
5945 continue
5946
5946
5947 label, key = mergestateinfo[ms[f]]
5947 label, key = mergestateinfo[ms[f]]
5948 fm.startitem()
5948 fm.startitem()
5949 fm.context(ctx=wctx)
5949 fm.context(ctx=wctx)
5950 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
5950 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
5951 fm.data(path=f)
5951 fm.data(path=f)
5952 fm.plain(b'%s\n' % uipathfn(f), label=label)
5952 fm.plain(b'%s\n' % uipathfn(f), label=label)
5953 fm.end()
5953 fm.end()
5954 return 0
5954 return 0
5955
5955
5956 with repo.wlock():
5956 with repo.wlock():
5957 ms = mergemod.mergestate.read(repo)
5957 ms = mergemod.mergestate.read(repo)
5958
5958
5959 if not (ms.active() or repo.dirstate.p2() != nullid):
5959 if not (ms.active() or repo.dirstate.p2() != nullid):
5960 raise error.Abort(
5960 raise error.Abort(
5961 _(b'resolve command not applicable when not merging')
5961 _(b'resolve command not applicable when not merging')
5962 )
5962 )
5963
5963
5964 wctx = repo[None]
5964 wctx = repo[None]
5965
5965
5966 if (
5966 if (
5967 ms.mergedriver
5967 ms.mergedriver
5968 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED
5968 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED
5969 ):
5969 ):
5970 proceed = mergemod.driverpreprocess(repo, ms, wctx)
5970 proceed = mergemod.driverpreprocess(repo, ms, wctx)
5971 ms.commit()
5971 ms.commit()
5972 # allow mark and unmark to go through
5972 # allow mark and unmark to go through
5973 if not mark and not unmark and not proceed:
5973 if not mark and not unmark and not proceed:
5974 return 1
5974 return 1
5975
5975
5976 m = scmutil.match(wctx, pats, opts)
5976 m = scmutil.match(wctx, pats, opts)
5977 ret = 0
5977 ret = 0
5978 didwork = False
5978 didwork = False
5979 runconclude = False
5979 runconclude = False
5980
5980
5981 tocomplete = []
5981 tocomplete = []
5982 hasconflictmarkers = []
5982 hasconflictmarkers = []
5983 if mark:
5983 if mark:
5984 markcheck = ui.config(b'commands', b'resolve.mark-check')
5984 markcheck = ui.config(b'commands', b'resolve.mark-check')
5985 if markcheck not in [b'warn', b'abort']:
5985 if markcheck not in [b'warn', b'abort']:
5986 # Treat all invalid / unrecognized values as 'none'.
5986 # Treat all invalid / unrecognized values as 'none'.
5987 markcheck = False
5987 markcheck = False
5988 for f in ms:
5988 for f in ms:
5989 if not m(f):
5989 if not m(f):
5990 continue
5990 continue
5991
5991
5992 didwork = True
5992 didwork = True
5993
5993
5994 # don't let driver-resolved files be marked, and run the conclude
5994 # don't let driver-resolved files be marked, and run the conclude
5995 # step if asked to resolve
5995 # step if asked to resolve
5996 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
5996 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
5997 exact = m.exact(f)
5997 exact = m.exact(f)
5998 if mark:
5998 if mark:
5999 if exact:
5999 if exact:
6000 ui.warn(
6000 ui.warn(
6001 _(b'not marking %s as it is driver-resolved\n')
6001 _(b'not marking %s as it is driver-resolved\n')
6002 % uipathfn(f)
6002 % uipathfn(f)
6003 )
6003 )
6004 elif unmark:
6004 elif unmark:
6005 if exact:
6005 if exact:
6006 ui.warn(
6006 ui.warn(
6007 _(b'not unmarking %s as it is driver-resolved\n')
6007 _(b'not unmarking %s as it is driver-resolved\n')
6008 % uipathfn(f)
6008 % uipathfn(f)
6009 )
6009 )
6010 else:
6010 else:
6011 runconclude = True
6011 runconclude = True
6012 continue
6012 continue
6013
6013
6014 # path conflicts must be resolved manually
6014 # path conflicts must be resolved manually
6015 if ms[f] in (
6015 if ms[f] in (
6016 mergemod.MERGE_RECORD_UNRESOLVED_PATH,
6016 mergemod.MERGE_RECORD_UNRESOLVED_PATH,
6017 mergemod.MERGE_RECORD_RESOLVED_PATH,
6017 mergemod.MERGE_RECORD_RESOLVED_PATH,
6018 ):
6018 ):
6019 if mark:
6019 if mark:
6020 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
6020 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
6021 elif unmark:
6021 elif unmark:
6022 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
6022 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
6023 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
6023 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
6024 ui.warn(
6024 ui.warn(
6025 _(b'%s: path conflict must be resolved manually\n')
6025 _(b'%s: path conflict must be resolved manually\n')
6026 % uipathfn(f)
6026 % uipathfn(f)
6027 )
6027 )
6028 continue
6028 continue
6029
6029
6030 if mark:
6030 if mark:
6031 if markcheck:
6031 if markcheck:
6032 fdata = repo.wvfs.tryread(f)
6032 fdata = repo.wvfs.tryread(f)
6033 if (
6033 if (
6034 filemerge.hasconflictmarkers(fdata)
6034 filemerge.hasconflictmarkers(fdata)
6035 and ms[f] != mergemod.MERGE_RECORD_RESOLVED
6035 and ms[f] != mergemod.MERGE_RECORD_RESOLVED
6036 ):
6036 ):
6037 hasconflictmarkers.append(f)
6037 hasconflictmarkers.append(f)
6038 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
6038 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
6039 elif unmark:
6039 elif unmark:
6040 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
6040 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
6041 else:
6041 else:
6042 # backup pre-resolve (merge uses .orig for its own purposes)
6042 # backup pre-resolve (merge uses .orig for its own purposes)
6043 a = repo.wjoin(f)
6043 a = repo.wjoin(f)
6044 try:
6044 try:
6045 util.copyfile(a, a + b".resolve")
6045 util.copyfile(a, a + b".resolve")
6046 except (IOError, OSError) as inst:
6046 except (IOError, OSError) as inst:
6047 if inst.errno != errno.ENOENT:
6047 if inst.errno != errno.ENOENT:
6048 raise
6048 raise
6049
6049
6050 try:
6050 try:
6051 # preresolve file
6051 # preresolve file
6052 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6052 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6053 with ui.configoverride(overrides, b'resolve'):
6053 with ui.configoverride(overrides, b'resolve'):
6054 complete, r = ms.preresolve(f, wctx)
6054 complete, r = ms.preresolve(f, wctx)
6055 if not complete:
6055 if not complete:
6056 tocomplete.append(f)
6056 tocomplete.append(f)
6057 elif r:
6057 elif r:
6058 ret = 1
6058 ret = 1
6059 finally:
6059 finally:
6060 ms.commit()
6060 ms.commit()
6061
6061
6062 # replace filemerge's .orig file with our resolve file, but only
6062 # replace filemerge's .orig file with our resolve file, but only
6063 # for merges that are complete
6063 # for merges that are complete
6064 if complete:
6064 if complete:
6065 try:
6065 try:
6066 util.rename(
6066 util.rename(
6067 a + b".resolve", scmutil.backuppath(ui, repo, f)
6067 a + b".resolve", scmutil.backuppath(ui, repo, f)
6068 )
6068 )
6069 except OSError as inst:
6069 except OSError as inst:
6070 if inst.errno != errno.ENOENT:
6070 if inst.errno != errno.ENOENT:
6071 raise
6071 raise
6072
6072
6073 if hasconflictmarkers:
6073 if hasconflictmarkers:
6074 ui.warn(
6074 ui.warn(
6075 _(
6075 _(
6076 b'warning: the following files still have conflict '
6076 b'warning: the following files still have conflict '
6077 b'markers:\n'
6077 b'markers:\n'
6078 )
6078 )
6079 + b''.join(
6079 + b''.join(
6080 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6080 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6081 )
6081 )
6082 )
6082 )
6083 if markcheck == b'abort' and not all and not pats:
6083 if markcheck == b'abort' and not all and not pats:
6084 raise error.Abort(
6084 raise error.Abort(
6085 _(b'conflict markers detected'),
6085 _(b'conflict markers detected'),
6086 hint=_(b'use --all to mark anyway'),
6086 hint=_(b'use --all to mark anyway'),
6087 )
6087 )
6088
6088
6089 for f in tocomplete:
6089 for f in tocomplete:
6090 try:
6090 try:
6091 # resolve file
6091 # resolve file
6092 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6092 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6093 with ui.configoverride(overrides, b'resolve'):
6093 with ui.configoverride(overrides, b'resolve'):
6094 r = ms.resolve(f, wctx)
6094 r = ms.resolve(f, wctx)
6095 if r:
6095 if r:
6096 ret = 1
6096 ret = 1
6097 finally:
6097 finally:
6098 ms.commit()
6098 ms.commit()
6099
6099
6100 # replace filemerge's .orig file with our resolve file
6100 # replace filemerge's .orig file with our resolve file
6101 a = repo.wjoin(f)
6101 a = repo.wjoin(f)
6102 try:
6102 try:
6103 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6103 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6104 except OSError as inst:
6104 except OSError as inst:
6105 if inst.errno != errno.ENOENT:
6105 if inst.errno != errno.ENOENT:
6106 raise
6106 raise
6107
6107
6108 ms.commit()
6108 ms.commit()
6109 ms.recordactions()
6109 ms.recordactions()
6110
6110
6111 if not didwork and pats:
6111 if not didwork and pats:
6112 hint = None
6112 hint = None
6113 if not any([p for p in pats if p.find(b':') >= 0]):
6113 if not any([p for p in pats if p.find(b':') >= 0]):
6114 pats = [b'path:%s' % p for p in pats]
6114 pats = [b'path:%s' % p for p in pats]
6115 m = scmutil.match(wctx, pats, opts)
6115 m = scmutil.match(wctx, pats, opts)
6116 for f in ms:
6116 for f in ms:
6117 if not m(f):
6117 if not m(f):
6118 continue
6118 continue
6119
6119
6120 def flag(o):
6120 def flag(o):
6121 if o == b're_merge':
6121 if o == b're_merge':
6122 return b'--re-merge '
6122 return b'--re-merge '
6123 return b'-%s ' % o[0:1]
6123 return b'-%s ' % o[0:1]
6124
6124
6125 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6125 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6126 hint = _(b"(try: hg resolve %s%s)\n") % (
6126 hint = _(b"(try: hg resolve %s%s)\n") % (
6127 flags,
6127 flags,
6128 b' '.join(pats),
6128 b' '.join(pats),
6129 )
6129 )
6130 break
6130 break
6131 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6131 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6132 if hint:
6132 if hint:
6133 ui.warn(hint)
6133 ui.warn(hint)
6134 elif ms.mergedriver and ms.mdstate() != b's':
6134 elif ms.mergedriver and ms.mdstate() != b's':
6135 # run conclude step when either a driver-resolved file is requested
6135 # run conclude step when either a driver-resolved file is requested
6136 # or there are no driver-resolved files
6136 # or there are no driver-resolved files
6137 # we can't use 'ret' to determine whether any files are unresolved
6137 # we can't use 'ret' to determine whether any files are unresolved
6138 # because we might not have tried to resolve some
6138 # because we might not have tried to resolve some
6139 if (runconclude or not list(ms.driverresolved())) and not list(
6139 if (runconclude or not list(ms.driverresolved())) and not list(
6140 ms.unresolved()
6140 ms.unresolved()
6141 ):
6141 ):
6142 proceed = mergemod.driverconclude(repo, ms, wctx)
6142 proceed = mergemod.driverconclude(repo, ms, wctx)
6143 ms.commit()
6143 ms.commit()
6144 if not proceed:
6144 if not proceed:
6145 return 1
6145 return 1
6146
6146
6147 # Nudge users into finishing an unfinished operation
6147 # Nudge users into finishing an unfinished operation
6148 unresolvedf = list(ms.unresolved())
6148 unresolvedf = list(ms.unresolved())
6149 driverresolvedf = list(ms.driverresolved())
6149 driverresolvedf = list(ms.driverresolved())
6150 if not unresolvedf and not driverresolvedf:
6150 if not unresolvedf and not driverresolvedf:
6151 ui.status(_(b'(no more unresolved files)\n'))
6151 ui.status(_(b'(no more unresolved files)\n'))
6152 cmdutil.checkafterresolved(repo)
6152 cmdutil.checkafterresolved(repo)
6153 elif not unresolvedf:
6153 elif not unresolvedf:
6154 ui.status(
6154 ui.status(
6155 _(
6155 _(
6156 b'(no more unresolved files -- '
6156 b'(no more unresolved files -- '
6157 b'run "hg resolve --all" to conclude)\n'
6157 b'run "hg resolve --all" to conclude)\n'
6158 )
6158 )
6159 )
6159 )
6160
6160
6161 return ret
6161 return ret
6162
6162
6163
6163
6164 @command(
6164 @command(
6165 b'revert',
6165 b'revert',
6166 [
6166 [
6167 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6167 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6168 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6168 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6169 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6169 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6170 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6170 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6171 (b'i', b'interactive', None, _(b'interactively select the changes')),
6171 (b'i', b'interactive', None, _(b'interactively select the changes')),
6172 ]
6172 ]
6173 + walkopts
6173 + walkopts
6174 + dryrunopts,
6174 + dryrunopts,
6175 _(b'[OPTION]... [-r REV] [NAME]...'),
6175 _(b'[OPTION]... [-r REV] [NAME]...'),
6176 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6176 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6177 )
6177 )
6178 def revert(ui, repo, *pats, **opts):
6178 def revert(ui, repo, *pats, **opts):
6179 """restore files to their checkout state
6179 """restore files to their checkout state
6180
6180
6181 .. note::
6181 .. note::
6182
6182
6183 To check out earlier revisions, you should use :hg:`update REV`.
6183 To check out earlier revisions, you should use :hg:`update REV`.
6184 To cancel an uncommitted merge (and lose your changes),
6184 To cancel an uncommitted merge (and lose your changes),
6185 use :hg:`merge --abort`.
6185 use :hg:`merge --abort`.
6186
6186
6187 With no revision specified, revert the specified files or directories
6187 With no revision specified, revert the specified files or directories
6188 to the contents they had in the parent of the working directory.
6188 to the contents they had in the parent of the working directory.
6189 This restores the contents of files to an unmodified
6189 This restores the contents of files to an unmodified
6190 state and unschedules adds, removes, copies, and renames. If the
6190 state and unschedules adds, removes, copies, and renames. If the
6191 working directory has two parents, you must explicitly specify a
6191 working directory has two parents, you must explicitly specify a
6192 revision.
6192 revision.
6193
6193
6194 Using the -r/--rev or -d/--date options, revert the given files or
6194 Using the -r/--rev or -d/--date options, revert the given files or
6195 directories to their states as of a specific revision. Because
6195 directories to their states as of a specific revision. Because
6196 revert does not change the working directory parents, this will
6196 revert does not change the working directory parents, this will
6197 cause these files to appear modified. This can be helpful to "back
6197 cause these files to appear modified. This can be helpful to "back
6198 out" some or all of an earlier change. See :hg:`backout` for a
6198 out" some or all of an earlier change. See :hg:`backout` for a
6199 related method.
6199 related method.
6200
6200
6201 Modified files are saved with a .orig suffix before reverting.
6201 Modified files are saved with a .orig suffix before reverting.
6202 To disable these backups, use --no-backup. It is possible to store
6202 To disable these backups, use --no-backup. It is possible to store
6203 the backup files in a custom directory relative to the root of the
6203 the backup files in a custom directory relative to the root of the
6204 repository by setting the ``ui.origbackuppath`` configuration
6204 repository by setting the ``ui.origbackuppath`` configuration
6205 option.
6205 option.
6206
6206
6207 See :hg:`help dates` for a list of formats valid for -d/--date.
6207 See :hg:`help dates` for a list of formats valid for -d/--date.
6208
6208
6209 See :hg:`help backout` for a way to reverse the effect of an
6209 See :hg:`help backout` for a way to reverse the effect of an
6210 earlier changeset.
6210 earlier changeset.
6211
6211
6212 Returns 0 on success.
6212 Returns 0 on success.
6213 """
6213 """
6214
6214
6215 opts = pycompat.byteskwargs(opts)
6215 opts = pycompat.byteskwargs(opts)
6216 if opts.get(b"date"):
6216 if opts.get(b"date"):
6217 if opts.get(b"rev"):
6217 if opts.get(b"rev"):
6218 raise error.Abort(_(b"you can't specify a revision and a date"))
6218 raise error.Abort(_(b"you can't specify a revision and a date"))
6219 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6219 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6220
6220
6221 parent, p2 = repo.dirstate.parents()
6221 parent, p2 = repo.dirstate.parents()
6222 if not opts.get(b'rev') and p2 != nullid:
6222 if not opts.get(b'rev') and p2 != nullid:
6223 # revert after merge is a trap for new users (issue2915)
6223 # revert after merge is a trap for new users (issue2915)
6224 raise error.Abort(
6224 raise error.Abort(
6225 _(b'uncommitted merge with no revision specified'),
6225 _(b'uncommitted merge with no revision specified'),
6226 hint=_(b"use 'hg update' or see 'hg help revert'"),
6226 hint=_(b"use 'hg update' or see 'hg help revert'"),
6227 )
6227 )
6228
6228
6229 rev = opts.get(b'rev')
6229 rev = opts.get(b'rev')
6230 if rev:
6230 if rev:
6231 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6231 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6232 ctx = scmutil.revsingle(repo, rev)
6232 ctx = scmutil.revsingle(repo, rev)
6233
6233
6234 if not (
6234 if not (
6235 pats
6235 pats
6236 or opts.get(b'include')
6236 or opts.get(b'include')
6237 or opts.get(b'exclude')
6237 or opts.get(b'exclude')
6238 or opts.get(b'all')
6238 or opts.get(b'all')
6239 or opts.get(b'interactive')
6239 or opts.get(b'interactive')
6240 ):
6240 ):
6241 msg = _(b"no files or directories specified")
6241 msg = _(b"no files or directories specified")
6242 if p2 != nullid:
6242 if p2 != nullid:
6243 hint = _(
6243 hint = _(
6244 b"uncommitted merge, use --all to discard all changes,"
6244 b"uncommitted merge, use --all to discard all changes,"
6245 b" or 'hg update -C .' to abort the merge"
6245 b" or 'hg update -C .' to abort the merge"
6246 )
6246 )
6247 raise error.Abort(msg, hint=hint)
6247 raise error.Abort(msg, hint=hint)
6248 dirty = any(repo.status())
6248 dirty = any(repo.status())
6249 node = ctx.node()
6249 node = ctx.node()
6250 if node != parent:
6250 if node != parent:
6251 if dirty:
6251 if dirty:
6252 hint = (
6252 hint = (
6253 _(
6253 _(
6254 b"uncommitted changes, use --all to discard all"
6254 b"uncommitted changes, use --all to discard all"
6255 b" changes, or 'hg update %d' to update"
6255 b" changes, or 'hg update %d' to update"
6256 )
6256 )
6257 % ctx.rev()
6257 % ctx.rev()
6258 )
6258 )
6259 else:
6259 else:
6260 hint = (
6260 hint = (
6261 _(
6261 _(
6262 b"use --all to revert all files,"
6262 b"use --all to revert all files,"
6263 b" or 'hg update %d' to update"
6263 b" or 'hg update %d' to update"
6264 )
6264 )
6265 % ctx.rev()
6265 % ctx.rev()
6266 )
6266 )
6267 elif dirty:
6267 elif dirty:
6268 hint = _(b"uncommitted changes, use --all to discard all changes")
6268 hint = _(b"uncommitted changes, use --all to discard all changes")
6269 else:
6269 else:
6270 hint = _(b"use --all to revert all files")
6270 hint = _(b"use --all to revert all files")
6271 raise error.Abort(msg, hint=hint)
6271 raise error.Abort(msg, hint=hint)
6272
6272
6273 return cmdutil.revert(
6273 return cmdutil.revert(
6274 ui, repo, ctx, (parent, p2), *pats, **pycompat.strkwargs(opts)
6274 ui, repo, ctx, (parent, p2), *pats, **pycompat.strkwargs(opts)
6275 )
6275 )
6276
6276
6277
6277
6278 @command(
6278 @command(
6279 b'rollback',
6279 b'rollback',
6280 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6280 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6281 helpcategory=command.CATEGORY_MAINTENANCE,
6281 helpcategory=command.CATEGORY_MAINTENANCE,
6282 )
6282 )
6283 def rollback(ui, repo, **opts):
6283 def rollback(ui, repo, **opts):
6284 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6284 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6285
6285
6286 Please use :hg:`commit --amend` instead of rollback to correct
6286 Please use :hg:`commit --amend` instead of rollback to correct
6287 mistakes in the last commit.
6287 mistakes in the last commit.
6288
6288
6289 This command should be used with care. There is only one level of
6289 This command should be used with care. There is only one level of
6290 rollback, and there is no way to undo a rollback. It will also
6290 rollback, and there is no way to undo a rollback. It will also
6291 restore the dirstate at the time of the last transaction, losing
6291 restore the dirstate at the time of the last transaction, losing
6292 any dirstate changes since that time. This command does not alter
6292 any dirstate changes since that time. This command does not alter
6293 the working directory.
6293 the working directory.
6294
6294
6295 Transactions are used to encapsulate the effects of all commands
6295 Transactions are used to encapsulate the effects of all commands
6296 that create new changesets or propagate existing changesets into a
6296 that create new changesets or propagate existing changesets into a
6297 repository.
6297 repository.
6298
6298
6299 .. container:: verbose
6299 .. container:: verbose
6300
6300
6301 For example, the following commands are transactional, and their
6301 For example, the following commands are transactional, and their
6302 effects can be rolled back:
6302 effects can be rolled back:
6303
6303
6304 - commit
6304 - commit
6305 - import
6305 - import
6306 - pull
6306 - pull
6307 - push (with this repository as the destination)
6307 - push (with this repository as the destination)
6308 - unbundle
6308 - unbundle
6309
6309
6310 To avoid permanent data loss, rollback will refuse to rollback a
6310 To avoid permanent data loss, rollback will refuse to rollback a
6311 commit transaction if it isn't checked out. Use --force to
6311 commit transaction if it isn't checked out. Use --force to
6312 override this protection.
6312 override this protection.
6313
6313
6314 The rollback command can be entirely disabled by setting the
6314 The rollback command can be entirely disabled by setting the
6315 ``ui.rollback`` configuration setting to false. If you're here
6315 ``ui.rollback`` configuration setting to false. If you're here
6316 because you want to use rollback and it's disabled, you can
6316 because you want to use rollback and it's disabled, you can
6317 re-enable the command by setting ``ui.rollback`` to true.
6317 re-enable the command by setting ``ui.rollback`` to true.
6318
6318
6319 This command is not intended for use on public repositories. Once
6319 This command is not intended for use on public repositories. Once
6320 changes are visible for pull by other users, rolling a transaction
6320 changes are visible for pull by other users, rolling a transaction
6321 back locally is ineffective (someone else may already have pulled
6321 back locally is ineffective (someone else may already have pulled
6322 the changes). Furthermore, a race is possible with readers of the
6322 the changes). Furthermore, a race is possible with readers of the
6323 repository; for example an in-progress pull from the repository
6323 repository; for example an in-progress pull from the repository
6324 may fail if a rollback is performed.
6324 may fail if a rollback is performed.
6325
6325
6326 Returns 0 on success, 1 if no rollback data is available.
6326 Returns 0 on success, 1 if no rollback data is available.
6327 """
6327 """
6328 if not ui.configbool(b'ui', b'rollback'):
6328 if not ui.configbool(b'ui', b'rollback'):
6329 raise error.Abort(
6329 raise error.Abort(
6330 _(b'rollback is disabled because it is unsafe'),
6330 _(b'rollback is disabled because it is unsafe'),
6331 hint=b'see `hg help -v rollback` for information',
6331 hint=b'see `hg help -v rollback` for information',
6332 )
6332 )
6333 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6333 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6334
6334
6335
6335
6336 @command(
6336 @command(
6337 b'root',
6337 b'root',
6338 [] + formatteropts,
6338 [] + formatteropts,
6339 intents={INTENT_READONLY},
6339 intents={INTENT_READONLY},
6340 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6340 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6341 )
6341 )
6342 def root(ui, repo, **opts):
6342 def root(ui, repo, **opts):
6343 """print the root (top) of the current working directory
6343 """print the root (top) of the current working directory
6344
6344
6345 Print the root directory of the current repository.
6345 Print the root directory of the current repository.
6346
6346
6347 .. container:: verbose
6347 .. container:: verbose
6348
6348
6349 Template:
6349 Template:
6350
6350
6351 The following keywords are supported in addition to the common template
6351 The following keywords are supported in addition to the common template
6352 keywords and functions. See also :hg:`help templates`.
6352 keywords and functions. See also :hg:`help templates`.
6353
6353
6354 :hgpath: String. Path to the .hg directory.
6354 :hgpath: String. Path to the .hg directory.
6355 :storepath: String. Path to the directory holding versioned data.
6355 :storepath: String. Path to the directory holding versioned data.
6356
6356
6357 Returns 0 on success.
6357 Returns 0 on success.
6358 """
6358 """
6359 opts = pycompat.byteskwargs(opts)
6359 opts = pycompat.byteskwargs(opts)
6360 with ui.formatter(b'root', opts) as fm:
6360 with ui.formatter(b'root', opts) as fm:
6361 fm.startitem()
6361 fm.startitem()
6362 fm.write(b'reporoot', b'%s\n', repo.root)
6362 fm.write(b'reporoot', b'%s\n', repo.root)
6363 fm.data(hgpath=repo.path, storepath=repo.spath)
6363 fm.data(hgpath=repo.path, storepath=repo.spath)
6364
6364
6365
6365
6366 @command(
6366 @command(
6367 b'serve',
6367 b'serve',
6368 [
6368 [
6369 (
6369 (
6370 b'A',
6370 b'A',
6371 b'accesslog',
6371 b'accesslog',
6372 b'',
6372 b'',
6373 _(b'name of access log file to write to'),
6373 _(b'name of access log file to write to'),
6374 _(b'FILE'),
6374 _(b'FILE'),
6375 ),
6375 ),
6376 (b'd', b'daemon', None, _(b'run server in background')),
6376 (b'd', b'daemon', None, _(b'run server in background')),
6377 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6377 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6378 (
6378 (
6379 b'E',
6379 b'E',
6380 b'errorlog',
6380 b'errorlog',
6381 b'',
6381 b'',
6382 _(b'name of error log file to write to'),
6382 _(b'name of error log file to write to'),
6383 _(b'FILE'),
6383 _(b'FILE'),
6384 ),
6384 ),
6385 # use string type, then we can check if something was passed
6385 # use string type, then we can check if something was passed
6386 (
6386 (
6387 b'p',
6387 b'p',
6388 b'port',
6388 b'port',
6389 b'',
6389 b'',
6390 _(b'port to listen on (default: 8000)'),
6390 _(b'port to listen on (default: 8000)'),
6391 _(b'PORT'),
6391 _(b'PORT'),
6392 ),
6392 ),
6393 (
6393 (
6394 b'a',
6394 b'a',
6395 b'address',
6395 b'address',
6396 b'',
6396 b'',
6397 _(b'address to listen on (default: all interfaces)'),
6397 _(b'address to listen on (default: all interfaces)'),
6398 _(b'ADDR'),
6398 _(b'ADDR'),
6399 ),
6399 ),
6400 (
6400 (
6401 b'',
6401 b'',
6402 b'prefix',
6402 b'prefix',
6403 b'',
6403 b'',
6404 _(b'prefix path to serve from (default: server root)'),
6404 _(b'prefix path to serve from (default: server root)'),
6405 _(b'PREFIX'),
6405 _(b'PREFIX'),
6406 ),
6406 ),
6407 (
6407 (
6408 b'n',
6408 b'n',
6409 b'name',
6409 b'name',
6410 b'',
6410 b'',
6411 _(b'name to show in web pages (default: working directory)'),
6411 _(b'name to show in web pages (default: working directory)'),
6412 _(b'NAME'),
6412 _(b'NAME'),
6413 ),
6413 ),
6414 (
6414 (
6415 b'',
6415 b'',
6416 b'web-conf',
6416 b'web-conf',
6417 b'',
6417 b'',
6418 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6418 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6419 _(b'FILE'),
6419 _(b'FILE'),
6420 ),
6420 ),
6421 (
6421 (
6422 b'',
6422 b'',
6423 b'webdir-conf',
6423 b'webdir-conf',
6424 b'',
6424 b'',
6425 _(b'name of the hgweb config file (DEPRECATED)'),
6425 _(b'name of the hgweb config file (DEPRECATED)'),
6426 _(b'FILE'),
6426 _(b'FILE'),
6427 ),
6427 ),
6428 (
6428 (
6429 b'',
6429 b'',
6430 b'pid-file',
6430 b'pid-file',
6431 b'',
6431 b'',
6432 _(b'name of file to write process ID to'),
6432 _(b'name of file to write process ID to'),
6433 _(b'FILE'),
6433 _(b'FILE'),
6434 ),
6434 ),
6435 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6435 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6436 (
6436 (
6437 b'',
6437 b'',
6438 b'cmdserver',
6438 b'cmdserver',
6439 b'',
6439 b'',
6440 _(b'for remote clients (ADVANCED)'),
6440 _(b'for remote clients (ADVANCED)'),
6441 _(b'MODE'),
6441 _(b'MODE'),
6442 ),
6442 ),
6443 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6443 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6444 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6444 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6445 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6445 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6446 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6446 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6447 (b'', b'print-url', None, _(b'start and print only the URL')),
6447 (b'', b'print-url', None, _(b'start and print only the URL')),
6448 ]
6448 ]
6449 + subrepoopts,
6449 + subrepoopts,
6450 _(b'[OPTION]...'),
6450 _(b'[OPTION]...'),
6451 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6451 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6452 helpbasic=True,
6452 helpbasic=True,
6453 optionalrepo=True,
6453 optionalrepo=True,
6454 )
6454 )
6455 def serve(ui, repo, **opts):
6455 def serve(ui, repo, **opts):
6456 """start stand-alone webserver
6456 """start stand-alone webserver
6457
6457
6458 Start a local HTTP repository browser and pull server. You can use
6458 Start a local HTTP repository browser and pull server. You can use
6459 this for ad-hoc sharing and browsing of repositories. It is
6459 this for ad-hoc sharing and browsing of repositories. It is
6460 recommended to use a real web server to serve a repository for
6460 recommended to use a real web server to serve a repository for
6461 longer periods of time.
6461 longer periods of time.
6462
6462
6463 Please note that the server does not implement access control.
6463 Please note that the server does not implement access control.
6464 This means that, by default, anybody can read from the server and
6464 This means that, by default, anybody can read from the server and
6465 nobody can write to it by default. Set the ``web.allow-push``
6465 nobody can write to it by default. Set the ``web.allow-push``
6466 option to ``*`` to allow everybody to push to the server. You
6466 option to ``*`` to allow everybody to push to the server. You
6467 should use a real web server if you need to authenticate users.
6467 should use a real web server if you need to authenticate users.
6468
6468
6469 By default, the server logs accesses to stdout and errors to
6469 By default, the server logs accesses to stdout and errors to
6470 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6470 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6471 files.
6471 files.
6472
6472
6473 To have the server choose a free port number to listen on, specify
6473 To have the server choose a free port number to listen on, specify
6474 a port number of 0; in this case, the server will print the port
6474 a port number of 0; in this case, the server will print the port
6475 number it uses.
6475 number it uses.
6476
6476
6477 Returns 0 on success.
6477 Returns 0 on success.
6478 """
6478 """
6479
6479
6480 opts = pycompat.byteskwargs(opts)
6480 opts = pycompat.byteskwargs(opts)
6481 if opts[b"stdio"] and opts[b"cmdserver"]:
6481 if opts[b"stdio"] and opts[b"cmdserver"]:
6482 raise error.Abort(_(b"cannot use --stdio with --cmdserver"))
6482 raise error.Abort(_(b"cannot use --stdio with --cmdserver"))
6483 if opts[b"print_url"] and ui.verbose:
6483 if opts[b"print_url"] and ui.verbose:
6484 raise error.Abort(_(b"cannot use --print-url with --verbose"))
6484 raise error.Abort(_(b"cannot use --print-url with --verbose"))
6485
6485
6486 if opts[b"stdio"]:
6486 if opts[b"stdio"]:
6487 if repo is None:
6487 if repo is None:
6488 raise error.RepoError(
6488 raise error.RepoError(
6489 _(b"there is no Mercurial repository here (.hg not found)")
6489 _(b"there is no Mercurial repository here (.hg not found)")
6490 )
6490 )
6491 s = wireprotoserver.sshserver(ui, repo)
6491 s = wireprotoserver.sshserver(ui, repo)
6492 s.serve_forever()
6492 s.serve_forever()
6493
6493
6494 service = server.createservice(ui, repo, opts)
6494 service = server.createservice(ui, repo, opts)
6495 return server.runservice(opts, initfn=service.init, runfn=service.run)
6495 return server.runservice(opts, initfn=service.init, runfn=service.run)
6496
6496
6497
6497
6498 @command(
6498 @command(
6499 b'shelve',
6499 b'shelve',
6500 [
6500 [
6501 (
6501 (
6502 b'A',
6502 b'A',
6503 b'addremove',
6503 b'addremove',
6504 None,
6504 None,
6505 _(b'mark new/missing files as added/removed before shelving'),
6505 _(b'mark new/missing files as added/removed before shelving'),
6506 ),
6506 ),
6507 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6507 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6508 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6508 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6509 (
6509 (
6510 b'',
6510 b'',
6511 b'date',
6511 b'date',
6512 b'',
6512 b'',
6513 _(b'shelve with the specified commit date'),
6513 _(b'shelve with the specified commit date'),
6514 _(b'DATE'),
6514 _(b'DATE'),
6515 ),
6515 ),
6516 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6516 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6517 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6517 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6518 (
6518 (
6519 b'k',
6519 b'k',
6520 b'keep',
6520 b'keep',
6521 False,
6521 False,
6522 _(b'shelve, but keep changes in the working directory'),
6522 _(b'shelve, but keep changes in the working directory'),
6523 ),
6523 ),
6524 (b'l', b'list', None, _(b'list current shelves')),
6524 (b'l', b'list', None, _(b'list current shelves')),
6525 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6525 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6526 (
6526 (
6527 b'n',
6527 b'n',
6528 b'name',
6528 b'name',
6529 b'',
6529 b'',
6530 _(b'use the given name for the shelved commit'),
6530 _(b'use the given name for the shelved commit'),
6531 _(b'NAME'),
6531 _(b'NAME'),
6532 ),
6532 ),
6533 (
6533 (
6534 b'p',
6534 b'p',
6535 b'patch',
6535 b'patch',
6536 None,
6536 None,
6537 _(
6537 _(
6538 b'output patches for changes (provide the names of the shelved '
6538 b'output patches for changes (provide the names of the shelved '
6539 b'changes as positional arguments)'
6539 b'changes as positional arguments)'
6540 ),
6540 ),
6541 ),
6541 ),
6542 (b'i', b'interactive', None, _(b'interactive mode')),
6542 (b'i', b'interactive', None, _(b'interactive mode')),
6543 (
6543 (
6544 b'',
6544 b'',
6545 b'stat',
6545 b'stat',
6546 None,
6546 None,
6547 _(
6547 _(
6548 b'output diffstat-style summary of changes (provide the names of '
6548 b'output diffstat-style summary of changes (provide the names of '
6549 b'the shelved changes as positional arguments)'
6549 b'the shelved changes as positional arguments)'
6550 ),
6550 ),
6551 ),
6551 ),
6552 ]
6552 ]
6553 + cmdutil.walkopts,
6553 + cmdutil.walkopts,
6554 _(b'hg shelve [OPTION]... [FILE]...'),
6554 _(b'hg shelve [OPTION]... [FILE]...'),
6555 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6555 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6556 )
6556 )
6557 def shelve(ui, repo, *pats, **opts):
6557 def shelve(ui, repo, *pats, **opts):
6558 '''save and set aside changes from the working directory
6558 '''save and set aside changes from the working directory
6559
6559
6560 Shelving takes files that "hg status" reports as not clean, saves
6560 Shelving takes files that "hg status" reports as not clean, saves
6561 the modifications to a bundle (a shelved change), and reverts the
6561 the modifications to a bundle (a shelved change), and reverts the
6562 files so that their state in the working directory becomes clean.
6562 files so that their state in the working directory becomes clean.
6563
6563
6564 To restore these changes to the working directory, using "hg
6564 To restore these changes to the working directory, using "hg
6565 unshelve"; this will work even if you switch to a different
6565 unshelve"; this will work even if you switch to a different
6566 commit.
6566 commit.
6567
6567
6568 When no files are specified, "hg shelve" saves all not-clean
6568 When no files are specified, "hg shelve" saves all not-clean
6569 files. If specific files or directories are named, only changes to
6569 files. If specific files or directories are named, only changes to
6570 those files are shelved.
6570 those files are shelved.
6571
6571
6572 In bare shelve (when no files are specified, without interactive,
6572 In bare shelve (when no files are specified, without interactive,
6573 include and exclude option), shelving remembers information if the
6573 include and exclude option), shelving remembers information if the
6574 working directory was on newly created branch, in other words working
6574 working directory was on newly created branch, in other words working
6575 directory was on different branch than its first parent. In this
6575 directory was on different branch than its first parent. In this
6576 situation unshelving restores branch information to the working directory.
6576 situation unshelving restores branch information to the working directory.
6577
6577
6578 Each shelved change has a name that makes it easier to find later.
6578 Each shelved change has a name that makes it easier to find later.
6579 The name of a shelved change defaults to being based on the active
6579 The name of a shelved change defaults to being based on the active
6580 bookmark, or if there is no active bookmark, the current named
6580 bookmark, or if there is no active bookmark, the current named
6581 branch. To specify a different name, use ``--name``.
6581 branch. To specify a different name, use ``--name``.
6582
6582
6583 To see a list of existing shelved changes, use the ``--list``
6583 To see a list of existing shelved changes, use the ``--list``
6584 option. For each shelved change, this will print its name, age,
6584 option. For each shelved change, this will print its name, age,
6585 and description; use ``--patch`` or ``--stat`` for more details.
6585 and description; use ``--patch`` or ``--stat`` for more details.
6586
6586
6587 To delete specific shelved changes, use ``--delete``. To delete
6587 To delete specific shelved changes, use ``--delete``. To delete
6588 all shelved changes, use ``--cleanup``.
6588 all shelved changes, use ``--cleanup``.
6589 '''
6589 '''
6590 opts = pycompat.byteskwargs(opts)
6590 opts = pycompat.byteskwargs(opts)
6591 allowables = [
6591 allowables = [
6592 (b'addremove', {b'create'}), # 'create' is pseudo action
6592 (b'addremove', {b'create'}), # 'create' is pseudo action
6593 (b'unknown', {b'create'}),
6593 (b'unknown', {b'create'}),
6594 (b'cleanup', {b'cleanup'}),
6594 (b'cleanup', {b'cleanup'}),
6595 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6595 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6596 (b'delete', {b'delete'}),
6596 (b'delete', {b'delete'}),
6597 (b'edit', {b'create'}),
6597 (b'edit', {b'create'}),
6598 (b'keep', {b'create'}),
6598 (b'keep', {b'create'}),
6599 (b'list', {b'list'}),
6599 (b'list', {b'list'}),
6600 (b'message', {b'create'}),
6600 (b'message', {b'create'}),
6601 (b'name', {b'create'}),
6601 (b'name', {b'create'}),
6602 (b'patch', {b'patch', b'list'}),
6602 (b'patch', {b'patch', b'list'}),
6603 (b'stat', {b'stat', b'list'}),
6603 (b'stat', {b'stat', b'list'}),
6604 ]
6604 ]
6605
6605
6606 def checkopt(opt):
6606 def checkopt(opt):
6607 if opts.get(opt):
6607 if opts.get(opt):
6608 for i, allowable in allowables:
6608 for i, allowable in allowables:
6609 if opts[i] and opt not in allowable:
6609 if opts[i] and opt not in allowable:
6610 raise error.Abort(
6610 raise error.Abort(
6611 _(
6611 _(
6612 b"options '--%s' and '--%s' may not be "
6612 b"options '--%s' and '--%s' may not be "
6613 b"used together"
6613 b"used together"
6614 )
6614 )
6615 % (opt, i)
6615 % (opt, i)
6616 )
6616 )
6617 return True
6617 return True
6618
6618
6619 if checkopt(b'cleanup'):
6619 if checkopt(b'cleanup'):
6620 if pats:
6620 if pats:
6621 raise error.Abort(_(b"cannot specify names when using '--cleanup'"))
6621 raise error.Abort(_(b"cannot specify names when using '--cleanup'"))
6622 return shelvemod.cleanupcmd(ui, repo)
6622 return shelvemod.cleanupcmd(ui, repo)
6623 elif checkopt(b'delete'):
6623 elif checkopt(b'delete'):
6624 return shelvemod.deletecmd(ui, repo, pats)
6624 return shelvemod.deletecmd(ui, repo, pats)
6625 elif checkopt(b'list'):
6625 elif checkopt(b'list'):
6626 return shelvemod.listcmd(ui, repo, pats, opts)
6626 return shelvemod.listcmd(ui, repo, pats, opts)
6627 elif checkopt(b'patch') or checkopt(b'stat'):
6627 elif checkopt(b'patch') or checkopt(b'stat'):
6628 return shelvemod.patchcmds(ui, repo, pats, opts)
6628 return shelvemod.patchcmds(ui, repo, pats, opts)
6629 else:
6629 else:
6630 return shelvemod.createcmd(ui, repo, pats, opts)
6630 return shelvemod.createcmd(ui, repo, pats, opts)
6631
6631
6632
6632
6633 _NOTTERSE = b'nothing'
6633 _NOTTERSE = b'nothing'
6634
6634
6635
6635
6636 @command(
6636 @command(
6637 b'status|st',
6637 b'status|st',
6638 [
6638 [
6639 (b'A', b'all', None, _(b'show status of all files')),
6639 (b'A', b'all', None, _(b'show status of all files')),
6640 (b'm', b'modified', None, _(b'show only modified files')),
6640 (b'm', b'modified', None, _(b'show only modified files')),
6641 (b'a', b'added', None, _(b'show only added files')),
6641 (b'a', b'added', None, _(b'show only added files')),
6642 (b'r', b'removed', None, _(b'show only removed files')),
6642 (b'r', b'removed', None, _(b'show only removed files')),
6643 (b'd', b'deleted', None, _(b'show only deleted (but tracked) files')),
6643 (b'd', b'deleted', None, _(b'show only deleted (but tracked) files')),
6644 (b'c', b'clean', None, _(b'show only files without changes')),
6644 (b'c', b'clean', None, _(b'show only files without changes')),
6645 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6645 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6646 (b'i', b'ignored', None, _(b'show only ignored files')),
6646 (b'i', b'ignored', None, _(b'show only ignored files')),
6647 (b'n', b'no-status', None, _(b'hide status prefix')),
6647 (b'n', b'no-status', None, _(b'hide status prefix')),
6648 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6648 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6649 (b'C', b'copies', None, _(b'show source of copied files (DEFAULT: ui.statuscopies)')),
6649 (
6650 b'C',
6651 b'copies',
6652 None,
6653 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6654 ),
6650 (
6655 (
6651 b'0',
6656 b'0',
6652 b'print0',
6657 b'print0',
6653 None,
6658 None,
6654 _(b'end filenames with NUL, for use with xargs'),
6659 _(b'end filenames with NUL, for use with xargs'),
6655 ),
6660 ),
6656 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6661 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6657 (
6662 (
6658 b'',
6663 b'',
6659 b'change',
6664 b'change',
6660 b'',
6665 b'',
6661 _(b'list the changed files of a revision'),
6666 _(b'list the changed files of a revision'),
6662 _(b'REV'),
6667 _(b'REV'),
6663 ),
6668 ),
6664 ]
6669 ]
6665 + walkopts
6670 + walkopts
6666 + subrepoopts
6671 + subrepoopts
6667 + formatteropts,
6672 + formatteropts,
6668 _(b'[OPTION]... [FILE]...'),
6673 _(b'[OPTION]... [FILE]...'),
6669 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6674 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6670 helpbasic=True,
6675 helpbasic=True,
6671 inferrepo=True,
6676 inferrepo=True,
6672 intents={INTENT_READONLY},
6677 intents={INTENT_READONLY},
6673 )
6678 )
6674 def status(ui, repo, *pats, **opts):
6679 def status(ui, repo, *pats, **opts):
6675 """show changed files in the working directory
6680 """show changed files in the working directory
6676
6681
6677 Show status of files in the repository. If names are given, only
6682 Show status of files in the repository. If names are given, only
6678 files that match are shown. Files that are clean or ignored or
6683 files that match are shown. Files that are clean or ignored or
6679 the source of a copy/move operation, are not listed unless
6684 the source of a copy/move operation, are not listed unless
6680 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6685 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6681 Unless options described with "show only ..." are given, the
6686 Unless options described with "show only ..." are given, the
6682 options -mardu are used.
6687 options -mardu are used.
6683
6688
6684 Option -q/--quiet hides untracked (unknown and ignored) files
6689 Option -q/--quiet hides untracked (unknown and ignored) files
6685 unless explicitly requested with -u/--unknown or -i/--ignored.
6690 unless explicitly requested with -u/--unknown or -i/--ignored.
6686
6691
6687 .. note::
6692 .. note::
6688
6693
6689 :hg:`status` may appear to disagree with diff if permissions have
6694 :hg:`status` may appear to disagree with diff if permissions have
6690 changed or a merge has occurred. The standard diff format does
6695 changed or a merge has occurred. The standard diff format does
6691 not report permission changes and diff only reports changes
6696 not report permission changes and diff only reports changes
6692 relative to one merge parent.
6697 relative to one merge parent.
6693
6698
6694 If one revision is given, it is used as the base revision.
6699 If one revision is given, it is used as the base revision.
6695 If two revisions are given, the differences between them are
6700 If two revisions are given, the differences between them are
6696 shown. The --change option can also be used as a shortcut to list
6701 shown. The --change option can also be used as a shortcut to list
6697 the changed files of a revision from its first parent.
6702 the changed files of a revision from its first parent.
6698
6703
6699 The codes used to show the status of files are::
6704 The codes used to show the status of files are::
6700
6705
6701 M = modified
6706 M = modified
6702 A = added
6707 A = added
6703 R = removed
6708 R = removed
6704 C = clean
6709 C = clean
6705 ! = missing (deleted by non-hg command, but still tracked)
6710 ! = missing (deleted by non-hg command, but still tracked)
6706 ? = not tracked
6711 ? = not tracked
6707 I = ignored
6712 I = ignored
6708 = origin of the previous file (with --copies)
6713 = origin of the previous file (with --copies)
6709
6714
6710 .. container:: verbose
6715 .. container:: verbose
6711
6716
6712 The -t/--terse option abbreviates the output by showing only the directory
6717 The -t/--terse option abbreviates the output by showing only the directory
6713 name if all the files in it share the same status. The option takes an
6718 name if all the files in it share the same status. The option takes an
6714 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6719 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6715 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6720 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6716 for 'ignored' and 'c' for clean.
6721 for 'ignored' and 'c' for clean.
6717
6722
6718 It abbreviates only those statuses which are passed. Note that clean and
6723 It abbreviates only those statuses which are passed. Note that clean and
6719 ignored files are not displayed with '--terse ic' unless the -c/--clean
6724 ignored files are not displayed with '--terse ic' unless the -c/--clean
6720 and -i/--ignored options are also used.
6725 and -i/--ignored options are also used.
6721
6726
6722 The -v/--verbose option shows information when the repository is in an
6727 The -v/--verbose option shows information when the repository is in an
6723 unfinished merge, shelve, rebase state etc. You can have this behavior
6728 unfinished merge, shelve, rebase state etc. You can have this behavior
6724 turned on by default by enabling the ``commands.status.verbose`` option.
6729 turned on by default by enabling the ``commands.status.verbose`` option.
6725
6730
6726 You can skip displaying some of these states by setting
6731 You can skip displaying some of these states by setting
6727 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6732 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6728 'histedit', 'merge', 'rebase', or 'unshelve'.
6733 'histedit', 'merge', 'rebase', or 'unshelve'.
6729
6734
6730 Template:
6735 Template:
6731
6736
6732 The following keywords are supported in addition to the common template
6737 The following keywords are supported in addition to the common template
6733 keywords and functions. See also :hg:`help templates`.
6738 keywords and functions. See also :hg:`help templates`.
6734
6739
6735 :path: String. Repository-absolute path of the file.
6740 :path: String. Repository-absolute path of the file.
6736 :source: String. Repository-absolute path of the file originated from.
6741 :source: String. Repository-absolute path of the file originated from.
6737 Available if ``--copies`` is specified.
6742 Available if ``--copies`` is specified.
6738 :status: String. Character denoting file's status.
6743 :status: String. Character denoting file's status.
6739
6744
6740 Examples:
6745 Examples:
6741
6746
6742 - show changes in the working directory relative to a
6747 - show changes in the working directory relative to a
6743 changeset::
6748 changeset::
6744
6749
6745 hg status --rev 9353
6750 hg status --rev 9353
6746
6751
6747 - show changes in the working directory relative to the
6752 - show changes in the working directory relative to the
6748 current directory (see :hg:`help patterns` for more information)::
6753 current directory (see :hg:`help patterns` for more information)::
6749
6754
6750 hg status re:
6755 hg status re:
6751
6756
6752 - show all changes including copies in an existing changeset::
6757 - show all changes including copies in an existing changeset::
6753
6758
6754 hg status --copies --change 9353
6759 hg status --copies --change 9353
6755
6760
6756 - get a NUL separated list of added files, suitable for xargs::
6761 - get a NUL separated list of added files, suitable for xargs::
6757
6762
6758 hg status -an0
6763 hg status -an0
6759
6764
6760 - show more information about the repository status, abbreviating
6765 - show more information about the repository status, abbreviating
6761 added, removed, modified, deleted, and untracked paths::
6766 added, removed, modified, deleted, and untracked paths::
6762
6767
6763 hg status -v -t mardu
6768 hg status -v -t mardu
6764
6769
6765 Returns 0 on success.
6770 Returns 0 on success.
6766
6771
6767 """
6772 """
6768
6773
6769 opts = pycompat.byteskwargs(opts)
6774 opts = pycompat.byteskwargs(opts)
6770 revs = opts.get(b'rev')
6775 revs = opts.get(b'rev')
6771 change = opts.get(b'change')
6776 change = opts.get(b'change')
6772 terse = opts.get(b'terse')
6777 terse = opts.get(b'terse')
6773 if terse is _NOTTERSE:
6778 if terse is _NOTTERSE:
6774 if revs:
6779 if revs:
6775 terse = b''
6780 terse = b''
6776 else:
6781 else:
6777 terse = ui.config(b'commands', b'status.terse')
6782 terse = ui.config(b'commands', b'status.terse')
6778
6783
6779 if revs and change:
6784 if revs and change:
6780 msg = _(b'cannot specify --rev and --change at the same time')
6785 msg = _(b'cannot specify --rev and --change at the same time')
6781 raise error.Abort(msg)
6786 raise error.Abort(msg)
6782 elif revs and terse:
6787 elif revs and terse:
6783 msg = _(b'cannot use --terse with --rev')
6788 msg = _(b'cannot use --terse with --rev')
6784 raise error.Abort(msg)
6789 raise error.Abort(msg)
6785 elif change:
6790 elif change:
6786 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6791 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6787 ctx2 = scmutil.revsingle(repo, change, None)
6792 ctx2 = scmutil.revsingle(repo, change, None)
6788 ctx1 = ctx2.p1()
6793 ctx1 = ctx2.p1()
6789 else:
6794 else:
6790 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6795 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6791 ctx1, ctx2 = scmutil.revpair(repo, revs)
6796 ctx1, ctx2 = scmutil.revpair(repo, revs)
6792
6797
6793 forcerelativevalue = None
6798 forcerelativevalue = None
6794 if ui.hasconfig(b'commands', b'status.relative'):
6799 if ui.hasconfig(b'commands', b'status.relative'):
6795 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6800 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6796 uipathfn = scmutil.getuipathfn(
6801 uipathfn = scmutil.getuipathfn(
6797 repo,
6802 repo,
6798 legacyrelativevalue=bool(pats),
6803 legacyrelativevalue=bool(pats),
6799 forcerelativevalue=forcerelativevalue,
6804 forcerelativevalue=forcerelativevalue,
6800 )
6805 )
6801
6806
6802 if opts.get(b'print0'):
6807 if opts.get(b'print0'):
6803 end = b'\0'
6808 end = b'\0'
6804 else:
6809 else:
6805 end = b'\n'
6810 end = b'\n'
6806 states = b'modified added removed deleted unknown ignored clean'.split()
6811 states = b'modified added removed deleted unknown ignored clean'.split()
6807 show = [k for k in states if opts.get(k)]
6812 show = [k for k in states if opts.get(k)]
6808 if opts.get(b'all'):
6813 if opts.get(b'all'):
6809 show += ui.quiet and (states[:4] + [b'clean']) or states
6814 show += ui.quiet and (states[:4] + [b'clean']) or states
6810
6815
6811 if not show:
6816 if not show:
6812 if ui.quiet:
6817 if ui.quiet:
6813 show = states[:4]
6818 show = states[:4]
6814 else:
6819 else:
6815 show = states[:5]
6820 show = states[:5]
6816
6821
6817 m = scmutil.match(ctx2, pats, opts)
6822 m = scmutil.match(ctx2, pats, opts)
6818 if terse:
6823 if terse:
6819 # we need to compute clean and unknown to terse
6824 # we need to compute clean and unknown to terse
6820 stat = repo.status(
6825 stat = repo.status(
6821 ctx1.node(),
6826 ctx1.node(),
6822 ctx2.node(),
6827 ctx2.node(),
6823 m,
6828 m,
6824 b'ignored' in show or b'i' in terse,
6829 b'ignored' in show or b'i' in terse,
6825 clean=True,
6830 clean=True,
6826 unknown=True,
6831 unknown=True,
6827 listsubrepos=opts.get(b'subrepos'),
6832 listsubrepos=opts.get(b'subrepos'),
6828 )
6833 )
6829
6834
6830 stat = cmdutil.tersedir(stat, terse)
6835 stat = cmdutil.tersedir(stat, terse)
6831 else:
6836 else:
6832 stat = repo.status(
6837 stat = repo.status(
6833 ctx1.node(),
6838 ctx1.node(),
6834 ctx2.node(),
6839 ctx2.node(),
6835 m,
6840 m,
6836 b'ignored' in show,
6841 b'ignored' in show,
6837 b'clean' in show,
6842 b'clean' in show,
6838 b'unknown' in show,
6843 b'unknown' in show,
6839 opts.get(b'subrepos'),
6844 opts.get(b'subrepos'),
6840 )
6845 )
6841
6846
6842 changestates = zip(
6847 changestates = zip(
6843 states,
6848 states,
6844 pycompat.iterbytestr(b'MAR!?IC'),
6849 pycompat.iterbytestr(b'MAR!?IC'),
6845 [getattr(stat, s.decode('utf8')) for s in states],
6850 [getattr(stat, s.decode('utf8')) for s in states],
6846 )
6851 )
6847
6852
6848 copy = {}
6853 copy = {}
6849 if (
6854 if (
6850 opts.get(b'all')
6855 opts.get(b'all')
6851 or opts.get(b'copies')
6856 or opts.get(b'copies')
6852 or ui.configbool(b'ui', b'statuscopies')
6857 or ui.configbool(b'ui', b'statuscopies')
6853 ) and not opts.get(b'no_status'):
6858 ) and not opts.get(b'no_status'):
6854 copy = copies.pathcopies(ctx1, ctx2, m)
6859 copy = copies.pathcopies(ctx1, ctx2, m)
6855
6860
6856 morestatus = None
6861 morestatus = None
6857 if (
6862 if (
6858 ui.verbose or ui.configbool(b'commands', b'status.verbose')
6863 ui.verbose or ui.configbool(b'commands', b'status.verbose')
6859 ) and not ui.plain():
6864 ) and not ui.plain():
6860 morestatus = cmdutil.readmorestatus(repo)
6865 morestatus = cmdutil.readmorestatus(repo)
6861
6866
6862 ui.pager(b'status')
6867 ui.pager(b'status')
6863 fm = ui.formatter(b'status', opts)
6868 fm = ui.formatter(b'status', opts)
6864 fmt = b'%s' + end
6869 fmt = b'%s' + end
6865 showchar = not opts.get(b'no_status')
6870 showchar = not opts.get(b'no_status')
6866
6871
6867 for state, char, files in changestates:
6872 for state, char, files in changestates:
6868 if state in show:
6873 if state in show:
6869 label = b'status.' + state
6874 label = b'status.' + state
6870 for f in files:
6875 for f in files:
6871 fm.startitem()
6876 fm.startitem()
6872 fm.context(ctx=ctx2)
6877 fm.context(ctx=ctx2)
6873 fm.data(itemtype=b'file', path=f)
6878 fm.data(itemtype=b'file', path=f)
6874 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6879 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6875 fm.plain(fmt % uipathfn(f), label=label)
6880 fm.plain(fmt % uipathfn(f), label=label)
6876 if f in copy:
6881 if f in copy:
6877 fm.data(source=copy[f])
6882 fm.data(source=copy[f])
6878 fm.plain(
6883 fm.plain(
6879 (b' %s' + end) % uipathfn(copy[f]),
6884 (b' %s' + end) % uipathfn(copy[f]),
6880 label=b'status.copied',
6885 label=b'status.copied',
6881 )
6886 )
6882 if morestatus:
6887 if morestatus:
6883 morestatus.formatfile(f, fm)
6888 morestatus.formatfile(f, fm)
6884
6889
6885 if morestatus:
6890 if morestatus:
6886 morestatus.formatfooter(fm)
6891 morestatus.formatfooter(fm)
6887 fm.end()
6892 fm.end()
6888
6893
6889
6894
6890 @command(
6895 @command(
6891 b'summary|sum',
6896 b'summary|sum',
6892 [(b'', b'remote', None, _(b'check for push and pull'))],
6897 [(b'', b'remote', None, _(b'check for push and pull'))],
6893 b'[--remote]',
6898 b'[--remote]',
6894 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6899 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6895 helpbasic=True,
6900 helpbasic=True,
6896 intents={INTENT_READONLY},
6901 intents={INTENT_READONLY},
6897 )
6902 )
6898 def summary(ui, repo, **opts):
6903 def summary(ui, repo, **opts):
6899 """summarize working directory state
6904 """summarize working directory state
6900
6905
6901 This generates a brief summary of the working directory state,
6906 This generates a brief summary of the working directory state,
6902 including parents, branch, commit status, phase and available updates.
6907 including parents, branch, commit status, phase and available updates.
6903
6908
6904 With the --remote option, this will check the default paths for
6909 With the --remote option, this will check the default paths for
6905 incoming and outgoing changes. This can be time-consuming.
6910 incoming and outgoing changes. This can be time-consuming.
6906
6911
6907 Returns 0 on success.
6912 Returns 0 on success.
6908 """
6913 """
6909
6914
6910 opts = pycompat.byteskwargs(opts)
6915 opts = pycompat.byteskwargs(opts)
6911 ui.pager(b'summary')
6916 ui.pager(b'summary')
6912 ctx = repo[None]
6917 ctx = repo[None]
6913 parents = ctx.parents()
6918 parents = ctx.parents()
6914 pnode = parents[0].node()
6919 pnode = parents[0].node()
6915 marks = []
6920 marks = []
6916
6921
6917 try:
6922 try:
6918 ms = mergemod.mergestate.read(repo)
6923 ms = mergemod.mergestate.read(repo)
6919 except error.UnsupportedMergeRecords as e:
6924 except error.UnsupportedMergeRecords as e:
6920 s = b' '.join(e.recordtypes)
6925 s = b' '.join(e.recordtypes)
6921 ui.warn(
6926 ui.warn(
6922 _(b'warning: merge state has unsupported record types: %s\n') % s
6927 _(b'warning: merge state has unsupported record types: %s\n') % s
6923 )
6928 )
6924 unresolved = []
6929 unresolved = []
6925 else:
6930 else:
6926 unresolved = list(ms.unresolved())
6931 unresolved = list(ms.unresolved())
6927
6932
6928 for p in parents:
6933 for p in parents:
6929 # label with log.changeset (instead of log.parent) since this
6934 # label with log.changeset (instead of log.parent) since this
6930 # shows a working directory parent *changeset*:
6935 # shows a working directory parent *changeset*:
6931 # i18n: column positioning for "hg summary"
6936 # i18n: column positioning for "hg summary"
6932 ui.write(
6937 ui.write(
6933 _(b'parent: %d:%s ') % (p.rev(), p),
6938 _(b'parent: %d:%s ') % (p.rev(), p),
6934 label=logcmdutil.changesetlabels(p),
6939 label=logcmdutil.changesetlabels(p),
6935 )
6940 )
6936 ui.write(b' '.join(p.tags()), label=b'log.tag')
6941 ui.write(b' '.join(p.tags()), label=b'log.tag')
6937 if p.bookmarks():
6942 if p.bookmarks():
6938 marks.extend(p.bookmarks())
6943 marks.extend(p.bookmarks())
6939 if p.rev() == -1:
6944 if p.rev() == -1:
6940 if not len(repo):
6945 if not len(repo):
6941 ui.write(_(b' (empty repository)'))
6946 ui.write(_(b' (empty repository)'))
6942 else:
6947 else:
6943 ui.write(_(b' (no revision checked out)'))
6948 ui.write(_(b' (no revision checked out)'))
6944 if p.obsolete():
6949 if p.obsolete():
6945 ui.write(_(b' (obsolete)'))
6950 ui.write(_(b' (obsolete)'))
6946 if p.isunstable():
6951 if p.isunstable():
6947 instabilities = (
6952 instabilities = (
6948 ui.label(instability, b'trouble.%s' % instability)
6953 ui.label(instability, b'trouble.%s' % instability)
6949 for instability in p.instabilities()
6954 for instability in p.instabilities()
6950 )
6955 )
6951 ui.write(b' (' + b', '.join(instabilities) + b')')
6956 ui.write(b' (' + b', '.join(instabilities) + b')')
6952 ui.write(b'\n')
6957 ui.write(b'\n')
6953 if p.description():
6958 if p.description():
6954 ui.status(
6959 ui.status(
6955 b' ' + p.description().splitlines()[0].strip() + b'\n',
6960 b' ' + p.description().splitlines()[0].strip() + b'\n',
6956 label=b'log.summary',
6961 label=b'log.summary',
6957 )
6962 )
6958
6963
6959 branch = ctx.branch()
6964 branch = ctx.branch()
6960 bheads = repo.branchheads(branch)
6965 bheads = repo.branchheads(branch)
6961 # i18n: column positioning for "hg summary"
6966 # i18n: column positioning for "hg summary"
6962 m = _(b'branch: %s\n') % branch
6967 m = _(b'branch: %s\n') % branch
6963 if branch != b'default':
6968 if branch != b'default':
6964 ui.write(m, label=b'log.branch')
6969 ui.write(m, label=b'log.branch')
6965 else:
6970 else:
6966 ui.status(m, label=b'log.branch')
6971 ui.status(m, label=b'log.branch')
6967
6972
6968 if marks:
6973 if marks:
6969 active = repo._activebookmark
6974 active = repo._activebookmark
6970 # i18n: column positioning for "hg summary"
6975 # i18n: column positioning for "hg summary"
6971 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
6976 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
6972 if active is not None:
6977 if active is not None:
6973 if active in marks:
6978 if active in marks:
6974 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
6979 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
6975 marks.remove(active)
6980 marks.remove(active)
6976 else:
6981 else:
6977 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
6982 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
6978 for m in marks:
6983 for m in marks:
6979 ui.write(b' ' + m, label=b'log.bookmark')
6984 ui.write(b' ' + m, label=b'log.bookmark')
6980 ui.write(b'\n', label=b'log.bookmark')
6985 ui.write(b'\n', label=b'log.bookmark')
6981
6986
6982 status = repo.status(unknown=True)
6987 status = repo.status(unknown=True)
6983
6988
6984 c = repo.dirstate.copies()
6989 c = repo.dirstate.copies()
6985 copied, renamed = [], []
6990 copied, renamed = [], []
6986 for d, s in pycompat.iteritems(c):
6991 for d, s in pycompat.iteritems(c):
6987 if s in status.removed:
6992 if s in status.removed:
6988 status.removed.remove(s)
6993 status.removed.remove(s)
6989 renamed.append(d)
6994 renamed.append(d)
6990 else:
6995 else:
6991 copied.append(d)
6996 copied.append(d)
6992 if d in status.added:
6997 if d in status.added:
6993 status.added.remove(d)
6998 status.added.remove(d)
6994
6999
6995 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7000 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
6996
7001
6997 labels = [
7002 labels = [
6998 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7003 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
6999 (ui.label(_(b'%d added'), b'status.added'), status.added),
7004 (ui.label(_(b'%d added'), b'status.added'), status.added),
7000 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7005 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7001 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7006 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7002 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7007 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7003 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7008 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7004 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7009 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7005 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7010 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7006 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7011 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7007 ]
7012 ]
7008 t = []
7013 t = []
7009 for l, s in labels:
7014 for l, s in labels:
7010 if s:
7015 if s:
7011 t.append(l % len(s))
7016 t.append(l % len(s))
7012
7017
7013 t = b', '.join(t)
7018 t = b', '.join(t)
7014 cleanworkdir = False
7019 cleanworkdir = False
7015
7020
7016 if repo.vfs.exists(b'graftstate'):
7021 if repo.vfs.exists(b'graftstate'):
7017 t += _(b' (graft in progress)')
7022 t += _(b' (graft in progress)')
7018 if repo.vfs.exists(b'updatestate'):
7023 if repo.vfs.exists(b'updatestate'):
7019 t += _(b' (interrupted update)')
7024 t += _(b' (interrupted update)')
7020 elif len(parents) > 1:
7025 elif len(parents) > 1:
7021 t += _(b' (merge)')
7026 t += _(b' (merge)')
7022 elif branch != parents[0].branch():
7027 elif branch != parents[0].branch():
7023 t += _(b' (new branch)')
7028 t += _(b' (new branch)')
7024 elif parents[0].closesbranch() and pnode in repo.branchheads(
7029 elif parents[0].closesbranch() and pnode in repo.branchheads(
7025 branch, closed=True
7030 branch, closed=True
7026 ):
7031 ):
7027 t += _(b' (head closed)')
7032 t += _(b' (head closed)')
7028 elif not (
7033 elif not (
7029 status.modified
7034 status.modified
7030 or status.added
7035 or status.added
7031 or status.removed
7036 or status.removed
7032 or renamed
7037 or renamed
7033 or copied
7038 or copied
7034 or subs
7039 or subs
7035 ):
7040 ):
7036 t += _(b' (clean)')
7041 t += _(b' (clean)')
7037 cleanworkdir = True
7042 cleanworkdir = True
7038 elif pnode not in bheads:
7043 elif pnode not in bheads:
7039 t += _(b' (new branch head)')
7044 t += _(b' (new branch head)')
7040
7045
7041 if parents:
7046 if parents:
7042 pendingphase = max(p.phase() for p in parents)
7047 pendingphase = max(p.phase() for p in parents)
7043 else:
7048 else:
7044 pendingphase = phases.public
7049 pendingphase = phases.public
7045
7050
7046 if pendingphase > phases.newcommitphase(ui):
7051 if pendingphase > phases.newcommitphase(ui):
7047 t += b' (%s)' % phases.phasenames[pendingphase]
7052 t += b' (%s)' % phases.phasenames[pendingphase]
7048
7053
7049 if cleanworkdir:
7054 if cleanworkdir:
7050 # i18n: column positioning for "hg summary"
7055 # i18n: column positioning for "hg summary"
7051 ui.status(_(b'commit: %s\n') % t.strip())
7056 ui.status(_(b'commit: %s\n') % t.strip())
7052 else:
7057 else:
7053 # i18n: column positioning for "hg summary"
7058 # i18n: column positioning for "hg summary"
7054 ui.write(_(b'commit: %s\n') % t.strip())
7059 ui.write(_(b'commit: %s\n') % t.strip())
7055
7060
7056 # all ancestors of branch heads - all ancestors of parent = new csets
7061 # all ancestors of branch heads - all ancestors of parent = new csets
7057 new = len(
7062 new = len(
7058 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7063 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7059 )
7064 )
7060
7065
7061 if new == 0:
7066 if new == 0:
7062 # i18n: column positioning for "hg summary"
7067 # i18n: column positioning for "hg summary"
7063 ui.status(_(b'update: (current)\n'))
7068 ui.status(_(b'update: (current)\n'))
7064 elif pnode not in bheads:
7069 elif pnode not in bheads:
7065 # i18n: column positioning for "hg summary"
7070 # i18n: column positioning for "hg summary"
7066 ui.write(_(b'update: %d new changesets (update)\n') % new)
7071 ui.write(_(b'update: %d new changesets (update)\n') % new)
7067 else:
7072 else:
7068 # i18n: column positioning for "hg summary"
7073 # i18n: column positioning for "hg summary"
7069 ui.write(
7074 ui.write(
7070 _(b'update: %d new changesets, %d branch heads (merge)\n')
7075 _(b'update: %d new changesets, %d branch heads (merge)\n')
7071 % (new, len(bheads))
7076 % (new, len(bheads))
7072 )
7077 )
7073
7078
7074 t = []
7079 t = []
7075 draft = len(repo.revs(b'draft()'))
7080 draft = len(repo.revs(b'draft()'))
7076 if draft:
7081 if draft:
7077 t.append(_(b'%d draft') % draft)
7082 t.append(_(b'%d draft') % draft)
7078 secret = len(repo.revs(b'secret()'))
7083 secret = len(repo.revs(b'secret()'))
7079 if secret:
7084 if secret:
7080 t.append(_(b'%d secret') % secret)
7085 t.append(_(b'%d secret') % secret)
7081
7086
7082 if draft or secret:
7087 if draft or secret:
7083 ui.status(_(b'phases: %s\n') % b', '.join(t))
7088 ui.status(_(b'phases: %s\n') % b', '.join(t))
7084
7089
7085 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7090 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7086 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7091 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7087 numtrouble = len(repo.revs(trouble + b"()"))
7092 numtrouble = len(repo.revs(trouble + b"()"))
7088 # We write all the possibilities to ease translation
7093 # We write all the possibilities to ease translation
7089 troublemsg = {
7094 troublemsg = {
7090 b"orphan": _(b"orphan: %d changesets"),
7095 b"orphan": _(b"orphan: %d changesets"),
7091 b"contentdivergent": _(b"content-divergent: %d changesets"),
7096 b"contentdivergent": _(b"content-divergent: %d changesets"),
7092 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7097 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7093 }
7098 }
7094 if numtrouble > 0:
7099 if numtrouble > 0:
7095 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7100 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7096
7101
7097 cmdutil.summaryhooks(ui, repo)
7102 cmdutil.summaryhooks(ui, repo)
7098
7103
7099 if opts.get(b'remote'):
7104 if opts.get(b'remote'):
7100 needsincoming, needsoutgoing = True, True
7105 needsincoming, needsoutgoing = True, True
7101 else:
7106 else:
7102 needsincoming, needsoutgoing = False, False
7107 needsincoming, needsoutgoing = False, False
7103 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7108 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7104 if i:
7109 if i:
7105 needsincoming = True
7110 needsincoming = True
7106 if o:
7111 if o:
7107 needsoutgoing = True
7112 needsoutgoing = True
7108 if not needsincoming and not needsoutgoing:
7113 if not needsincoming and not needsoutgoing:
7109 return
7114 return
7110
7115
7111 def getincoming():
7116 def getincoming():
7112 source, branches = hg.parseurl(ui.expandpath(b'default'))
7117 source, branches = hg.parseurl(ui.expandpath(b'default'))
7113 sbranch = branches[0]
7118 sbranch = branches[0]
7114 try:
7119 try:
7115 other = hg.peer(repo, {}, source)
7120 other = hg.peer(repo, {}, source)
7116 except error.RepoError:
7121 except error.RepoError:
7117 if opts.get(b'remote'):
7122 if opts.get(b'remote'):
7118 raise
7123 raise
7119 return source, sbranch, None, None, None
7124 return source, sbranch, None, None, None
7120 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7125 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7121 if revs:
7126 if revs:
7122 revs = [other.lookup(rev) for rev in revs]
7127 revs = [other.lookup(rev) for rev in revs]
7123 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
7128 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
7124 repo.ui.pushbuffer()
7129 repo.ui.pushbuffer()
7125 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7130 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7126 repo.ui.popbuffer()
7131 repo.ui.popbuffer()
7127 return source, sbranch, other, commoninc, commoninc[1]
7132 return source, sbranch, other, commoninc, commoninc[1]
7128
7133
7129 if needsincoming:
7134 if needsincoming:
7130 source, sbranch, sother, commoninc, incoming = getincoming()
7135 source, sbranch, sother, commoninc, incoming = getincoming()
7131 else:
7136 else:
7132 source = sbranch = sother = commoninc = incoming = None
7137 source = sbranch = sother = commoninc = incoming = None
7133
7138
7134 def getoutgoing():
7139 def getoutgoing():
7135 dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default'))
7140 dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default'))
7136 dbranch = branches[0]
7141 dbranch = branches[0]
7137 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
7142 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
7138 if source != dest:
7143 if source != dest:
7139 try:
7144 try:
7140 dother = hg.peer(repo, {}, dest)
7145 dother = hg.peer(repo, {}, dest)
7141 except error.RepoError:
7146 except error.RepoError:
7142 if opts.get(b'remote'):
7147 if opts.get(b'remote'):
7143 raise
7148 raise
7144 return dest, dbranch, None, None
7149 return dest, dbranch, None, None
7145 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
7150 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
7146 elif sother is None:
7151 elif sother is None:
7147 # there is no explicit destination peer, but source one is invalid
7152 # there is no explicit destination peer, but source one is invalid
7148 return dest, dbranch, None, None
7153 return dest, dbranch, None, None
7149 else:
7154 else:
7150 dother = sother
7155 dother = sother
7151 if source != dest or (sbranch is not None and sbranch != dbranch):
7156 if source != dest or (sbranch is not None and sbranch != dbranch):
7152 common = None
7157 common = None
7153 else:
7158 else:
7154 common = commoninc
7159 common = commoninc
7155 if revs:
7160 if revs:
7156 revs = [repo.lookup(rev) for rev in revs]
7161 revs = [repo.lookup(rev) for rev in revs]
7157 repo.ui.pushbuffer()
7162 repo.ui.pushbuffer()
7158 outgoing = discovery.findcommonoutgoing(
7163 outgoing = discovery.findcommonoutgoing(
7159 repo, dother, onlyheads=revs, commoninc=common
7164 repo, dother, onlyheads=revs, commoninc=common
7160 )
7165 )
7161 repo.ui.popbuffer()
7166 repo.ui.popbuffer()
7162 return dest, dbranch, dother, outgoing
7167 return dest, dbranch, dother, outgoing
7163
7168
7164 if needsoutgoing:
7169 if needsoutgoing:
7165 dest, dbranch, dother, outgoing = getoutgoing()
7170 dest, dbranch, dother, outgoing = getoutgoing()
7166 else:
7171 else:
7167 dest = dbranch = dother = outgoing = None
7172 dest = dbranch = dother = outgoing = None
7168
7173
7169 if opts.get(b'remote'):
7174 if opts.get(b'remote'):
7170 t = []
7175 t = []
7171 if incoming:
7176 if incoming:
7172 t.append(_(b'1 or more incoming'))
7177 t.append(_(b'1 or more incoming'))
7173 o = outgoing.missing
7178 o = outgoing.missing
7174 if o:
7179 if o:
7175 t.append(_(b'%d outgoing') % len(o))
7180 t.append(_(b'%d outgoing') % len(o))
7176 other = dother or sother
7181 other = dother or sother
7177 if b'bookmarks' in other.listkeys(b'namespaces'):
7182 if b'bookmarks' in other.listkeys(b'namespaces'):
7178 counts = bookmarks.summary(repo, other)
7183 counts = bookmarks.summary(repo, other)
7179 if counts[0] > 0:
7184 if counts[0] > 0:
7180 t.append(_(b'%d incoming bookmarks') % counts[0])
7185 t.append(_(b'%d incoming bookmarks') % counts[0])
7181 if counts[1] > 0:
7186 if counts[1] > 0:
7182 t.append(_(b'%d outgoing bookmarks') % counts[1])
7187 t.append(_(b'%d outgoing bookmarks') % counts[1])
7183
7188
7184 if t:
7189 if t:
7185 # i18n: column positioning for "hg summary"
7190 # i18n: column positioning for "hg summary"
7186 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7191 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7187 else:
7192 else:
7188 # i18n: column positioning for "hg summary"
7193 # i18n: column positioning for "hg summary"
7189 ui.status(_(b'remote: (synced)\n'))
7194 ui.status(_(b'remote: (synced)\n'))
7190
7195
7191 cmdutil.summaryremotehooks(
7196 cmdutil.summaryremotehooks(
7192 ui,
7197 ui,
7193 repo,
7198 repo,
7194 opts,
7199 opts,
7195 (
7200 (
7196 (source, sbranch, sother, commoninc),
7201 (source, sbranch, sother, commoninc),
7197 (dest, dbranch, dother, outgoing),
7202 (dest, dbranch, dother, outgoing),
7198 ),
7203 ),
7199 )
7204 )
7200
7205
7201
7206
7202 @command(
7207 @command(
7203 b'tag',
7208 b'tag',
7204 [
7209 [
7205 (b'f', b'force', None, _(b'force tag')),
7210 (b'f', b'force', None, _(b'force tag')),
7206 (b'l', b'local', None, _(b'make the tag local')),
7211 (b'l', b'local', None, _(b'make the tag local')),
7207 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7212 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7208 (b'', b'remove', None, _(b'remove a tag')),
7213 (b'', b'remove', None, _(b'remove a tag')),
7209 # -l/--local is already there, commitopts cannot be used
7214 # -l/--local is already there, commitopts cannot be used
7210 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7215 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7211 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7216 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7212 ]
7217 ]
7213 + commitopts2,
7218 + commitopts2,
7214 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7219 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7215 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7220 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7216 )
7221 )
7217 def tag(ui, repo, name1, *names, **opts):
7222 def tag(ui, repo, name1, *names, **opts):
7218 """add one or more tags for the current or given revision
7223 """add one or more tags for the current or given revision
7219
7224
7220 Name a particular revision using <name>.
7225 Name a particular revision using <name>.
7221
7226
7222 Tags are used to name particular revisions of the repository and are
7227 Tags are used to name particular revisions of the repository and are
7223 very useful to compare different revisions, to go back to significant
7228 very useful to compare different revisions, to go back to significant
7224 earlier versions or to mark branch points as releases, etc. Changing
7229 earlier versions or to mark branch points as releases, etc. Changing
7225 an existing tag is normally disallowed; use -f/--force to override.
7230 an existing tag is normally disallowed; use -f/--force to override.
7226
7231
7227 If no revision is given, the parent of the working directory is
7232 If no revision is given, the parent of the working directory is
7228 used.
7233 used.
7229
7234
7230 To facilitate version control, distribution, and merging of tags,
7235 To facilitate version control, distribution, and merging of tags,
7231 they are stored as a file named ".hgtags" which is managed similarly
7236 they are stored as a file named ".hgtags" which is managed similarly
7232 to other project files and can be hand-edited if necessary. This
7237 to other project files and can be hand-edited if necessary. This
7233 also means that tagging creates a new commit. The file
7238 also means that tagging creates a new commit. The file
7234 ".hg/localtags" is used for local tags (not shared among
7239 ".hg/localtags" is used for local tags (not shared among
7235 repositories).
7240 repositories).
7236
7241
7237 Tag commits are usually made at the head of a branch. If the parent
7242 Tag commits are usually made at the head of a branch. If the parent
7238 of the working directory is not a branch head, :hg:`tag` aborts; use
7243 of the working directory is not a branch head, :hg:`tag` aborts; use
7239 -f/--force to force the tag commit to be based on a non-head
7244 -f/--force to force the tag commit to be based on a non-head
7240 changeset.
7245 changeset.
7241
7246
7242 See :hg:`help dates` for a list of formats valid for -d/--date.
7247 See :hg:`help dates` for a list of formats valid for -d/--date.
7243
7248
7244 Since tag names have priority over branch names during revision
7249 Since tag names have priority over branch names during revision
7245 lookup, using an existing branch name as a tag name is discouraged.
7250 lookup, using an existing branch name as a tag name is discouraged.
7246
7251
7247 Returns 0 on success.
7252 Returns 0 on success.
7248 """
7253 """
7249 opts = pycompat.byteskwargs(opts)
7254 opts = pycompat.byteskwargs(opts)
7250 with repo.wlock(), repo.lock():
7255 with repo.wlock(), repo.lock():
7251 rev_ = b"."
7256 rev_ = b"."
7252 names = [t.strip() for t in (name1,) + names]
7257 names = [t.strip() for t in (name1,) + names]
7253 if len(names) != len(set(names)):
7258 if len(names) != len(set(names)):
7254 raise error.Abort(_(b'tag names must be unique'))
7259 raise error.Abort(_(b'tag names must be unique'))
7255 for n in names:
7260 for n in names:
7256 scmutil.checknewlabel(repo, n, b'tag')
7261 scmutil.checknewlabel(repo, n, b'tag')
7257 if not n:
7262 if not n:
7258 raise error.Abort(
7263 raise error.Abort(
7259 _(b'tag names cannot consist entirely of whitespace')
7264 _(b'tag names cannot consist entirely of whitespace')
7260 )
7265 )
7261 if opts.get(b'rev') and opts.get(b'remove'):
7266 if opts.get(b'rev') and opts.get(b'remove'):
7262 raise error.Abort(_(b"--rev and --remove are incompatible"))
7267 raise error.Abort(_(b"--rev and --remove are incompatible"))
7263 if opts.get(b'rev'):
7268 if opts.get(b'rev'):
7264 rev_ = opts[b'rev']
7269 rev_ = opts[b'rev']
7265 message = opts.get(b'message')
7270 message = opts.get(b'message')
7266 if opts.get(b'remove'):
7271 if opts.get(b'remove'):
7267 if opts.get(b'local'):
7272 if opts.get(b'local'):
7268 expectedtype = b'local'
7273 expectedtype = b'local'
7269 else:
7274 else:
7270 expectedtype = b'global'
7275 expectedtype = b'global'
7271
7276
7272 for n in names:
7277 for n in names:
7273 if repo.tagtype(n) == b'global':
7278 if repo.tagtype(n) == b'global':
7274 alltags = tagsmod.findglobaltags(ui, repo)
7279 alltags = tagsmod.findglobaltags(ui, repo)
7275 if alltags[n][0] == nullid:
7280 if alltags[n][0] == nullid:
7276 raise error.Abort(_(b"tag '%s' is already removed") % n)
7281 raise error.Abort(_(b"tag '%s' is already removed") % n)
7277 if not repo.tagtype(n):
7282 if not repo.tagtype(n):
7278 raise error.Abort(_(b"tag '%s' does not exist") % n)
7283 raise error.Abort(_(b"tag '%s' does not exist") % n)
7279 if repo.tagtype(n) != expectedtype:
7284 if repo.tagtype(n) != expectedtype:
7280 if expectedtype == b'global':
7285 if expectedtype == b'global':
7281 raise error.Abort(
7286 raise error.Abort(
7282 _(b"tag '%s' is not a global tag") % n
7287 _(b"tag '%s' is not a global tag") % n
7283 )
7288 )
7284 else:
7289 else:
7285 raise error.Abort(_(b"tag '%s' is not a local tag") % n)
7290 raise error.Abort(_(b"tag '%s' is not a local tag") % n)
7286 rev_ = b'null'
7291 rev_ = b'null'
7287 if not message:
7292 if not message:
7288 # we don't translate commit messages
7293 # we don't translate commit messages
7289 message = b'Removed tag %s' % b', '.join(names)
7294 message = b'Removed tag %s' % b', '.join(names)
7290 elif not opts.get(b'force'):
7295 elif not opts.get(b'force'):
7291 for n in names:
7296 for n in names:
7292 if n in repo.tags():
7297 if n in repo.tags():
7293 raise error.Abort(
7298 raise error.Abort(
7294 _(b"tag '%s' already exists (use -f to force)") % n
7299 _(b"tag '%s' already exists (use -f to force)") % n
7295 )
7300 )
7296 if not opts.get(b'local'):
7301 if not opts.get(b'local'):
7297 p1, p2 = repo.dirstate.parents()
7302 p1, p2 = repo.dirstate.parents()
7298 if p2 != nullid:
7303 if p2 != nullid:
7299 raise error.Abort(_(b'uncommitted merge'))
7304 raise error.Abort(_(b'uncommitted merge'))
7300 bheads = repo.branchheads()
7305 bheads = repo.branchheads()
7301 if not opts.get(b'force') and bheads and p1 not in bheads:
7306 if not opts.get(b'force') and bheads and p1 not in bheads:
7302 raise error.Abort(
7307 raise error.Abort(
7303 _(
7308 _(
7304 b'working directory is not at a branch head '
7309 b'working directory is not at a branch head '
7305 b'(use -f to force)'
7310 b'(use -f to force)'
7306 )
7311 )
7307 )
7312 )
7308 node = scmutil.revsingle(repo, rev_).node()
7313 node = scmutil.revsingle(repo, rev_).node()
7309
7314
7310 if not message:
7315 if not message:
7311 # we don't translate commit messages
7316 # we don't translate commit messages
7312 message = b'Added tag %s for changeset %s' % (
7317 message = b'Added tag %s for changeset %s' % (
7313 b', '.join(names),
7318 b', '.join(names),
7314 short(node),
7319 short(node),
7315 )
7320 )
7316
7321
7317 date = opts.get(b'date')
7322 date = opts.get(b'date')
7318 if date:
7323 if date:
7319 date = dateutil.parsedate(date)
7324 date = dateutil.parsedate(date)
7320
7325
7321 if opts.get(b'remove'):
7326 if opts.get(b'remove'):
7322 editform = b'tag.remove'
7327 editform = b'tag.remove'
7323 else:
7328 else:
7324 editform = b'tag.add'
7329 editform = b'tag.add'
7325 editor = cmdutil.getcommiteditor(
7330 editor = cmdutil.getcommiteditor(
7326 editform=editform, **pycompat.strkwargs(opts)
7331 editform=editform, **pycompat.strkwargs(opts)
7327 )
7332 )
7328
7333
7329 # don't allow tagging the null rev
7334 # don't allow tagging the null rev
7330 if (
7335 if (
7331 not opts.get(b'remove')
7336 not opts.get(b'remove')
7332 and scmutil.revsingle(repo, rev_).rev() == nullrev
7337 and scmutil.revsingle(repo, rev_).rev() == nullrev
7333 ):
7338 ):
7334 raise error.Abort(_(b"cannot tag null revision"))
7339 raise error.Abort(_(b"cannot tag null revision"))
7335
7340
7336 tagsmod.tag(
7341 tagsmod.tag(
7337 repo,
7342 repo,
7338 names,
7343 names,
7339 node,
7344 node,
7340 message,
7345 message,
7341 opts.get(b'local'),
7346 opts.get(b'local'),
7342 opts.get(b'user'),
7347 opts.get(b'user'),
7343 date,
7348 date,
7344 editor=editor,
7349 editor=editor,
7345 )
7350 )
7346
7351
7347
7352
7348 @command(
7353 @command(
7349 b'tags',
7354 b'tags',
7350 formatteropts,
7355 formatteropts,
7351 b'',
7356 b'',
7352 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7357 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7353 intents={INTENT_READONLY},
7358 intents={INTENT_READONLY},
7354 )
7359 )
7355 def tags(ui, repo, **opts):
7360 def tags(ui, repo, **opts):
7356 """list repository tags
7361 """list repository tags
7357
7362
7358 This lists both regular and local tags. When the -v/--verbose
7363 This lists both regular and local tags. When the -v/--verbose
7359 switch is used, a third column "local" is printed for local tags.
7364 switch is used, a third column "local" is printed for local tags.
7360 When the -q/--quiet switch is used, only the tag name is printed.
7365 When the -q/--quiet switch is used, only the tag name is printed.
7361
7366
7362 .. container:: verbose
7367 .. container:: verbose
7363
7368
7364 Template:
7369 Template:
7365
7370
7366 The following keywords are supported in addition to the common template
7371 The following keywords are supported in addition to the common template
7367 keywords and functions such as ``{tag}``. See also
7372 keywords and functions such as ``{tag}``. See also
7368 :hg:`help templates`.
7373 :hg:`help templates`.
7369
7374
7370 :type: String. ``local`` for local tags.
7375 :type: String. ``local`` for local tags.
7371
7376
7372 Returns 0 on success.
7377 Returns 0 on success.
7373 """
7378 """
7374
7379
7375 opts = pycompat.byteskwargs(opts)
7380 opts = pycompat.byteskwargs(opts)
7376 ui.pager(b'tags')
7381 ui.pager(b'tags')
7377 fm = ui.formatter(b'tags', opts)
7382 fm = ui.formatter(b'tags', opts)
7378 hexfunc = fm.hexfunc
7383 hexfunc = fm.hexfunc
7379
7384
7380 for t, n in reversed(repo.tagslist()):
7385 for t, n in reversed(repo.tagslist()):
7381 hn = hexfunc(n)
7386 hn = hexfunc(n)
7382 label = b'tags.normal'
7387 label = b'tags.normal'
7383 tagtype = b''
7388 tagtype = b''
7384 if repo.tagtype(t) == b'local':
7389 if repo.tagtype(t) == b'local':
7385 label = b'tags.local'
7390 label = b'tags.local'
7386 tagtype = b'local'
7391 tagtype = b'local'
7387
7392
7388 fm.startitem()
7393 fm.startitem()
7389 fm.context(repo=repo)
7394 fm.context(repo=repo)
7390 fm.write(b'tag', b'%s', t, label=label)
7395 fm.write(b'tag', b'%s', t, label=label)
7391 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7396 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7392 fm.condwrite(
7397 fm.condwrite(
7393 not ui.quiet,
7398 not ui.quiet,
7394 b'rev node',
7399 b'rev node',
7395 fmt,
7400 fmt,
7396 repo.changelog.rev(n),
7401 repo.changelog.rev(n),
7397 hn,
7402 hn,
7398 label=label,
7403 label=label,
7399 )
7404 )
7400 fm.condwrite(
7405 fm.condwrite(
7401 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7406 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7402 )
7407 )
7403 fm.plain(b'\n')
7408 fm.plain(b'\n')
7404 fm.end()
7409 fm.end()
7405
7410
7406
7411
7407 @command(
7412 @command(
7408 b'tip',
7413 b'tip',
7409 [
7414 [
7410 (b'p', b'patch', None, _(b'show patch')),
7415 (b'p', b'patch', None, _(b'show patch')),
7411 (b'g', b'git', None, _(b'use git extended diff format')),
7416 (b'g', b'git', None, _(b'use git extended diff format')),
7412 ]
7417 ]
7413 + templateopts,
7418 + templateopts,
7414 _(b'[-p] [-g]'),
7419 _(b'[-p] [-g]'),
7415 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7420 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7416 )
7421 )
7417 def tip(ui, repo, **opts):
7422 def tip(ui, repo, **opts):
7418 """show the tip revision (DEPRECATED)
7423 """show the tip revision (DEPRECATED)
7419
7424
7420 The tip revision (usually just called the tip) is the changeset
7425 The tip revision (usually just called the tip) is the changeset
7421 most recently added to the repository (and therefore the most
7426 most recently added to the repository (and therefore the most
7422 recently changed head).
7427 recently changed head).
7423
7428
7424 If you have just made a commit, that commit will be the tip. If
7429 If you have just made a commit, that commit will be the tip. If
7425 you have just pulled changes from another repository, the tip of
7430 you have just pulled changes from another repository, the tip of
7426 that repository becomes the current tip. The "tip" tag is special
7431 that repository becomes the current tip. The "tip" tag is special
7427 and cannot be renamed or assigned to a different changeset.
7432 and cannot be renamed or assigned to a different changeset.
7428
7433
7429 This command is deprecated, please use :hg:`heads` instead.
7434 This command is deprecated, please use :hg:`heads` instead.
7430
7435
7431 Returns 0 on success.
7436 Returns 0 on success.
7432 """
7437 """
7433 opts = pycompat.byteskwargs(opts)
7438 opts = pycompat.byteskwargs(opts)
7434 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7439 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7435 displayer.show(repo[b'tip'])
7440 displayer.show(repo[b'tip'])
7436 displayer.close()
7441 displayer.close()
7437
7442
7438
7443
7439 @command(
7444 @command(
7440 b'unbundle',
7445 b'unbundle',
7441 [
7446 [
7442 (
7447 (
7443 b'u',
7448 b'u',
7444 b'update',
7449 b'update',
7445 None,
7450 None,
7446 _(b'update to new branch head if changesets were unbundled'),
7451 _(b'update to new branch head if changesets were unbundled'),
7447 )
7452 )
7448 ],
7453 ],
7449 _(b'[-u] FILE...'),
7454 _(b'[-u] FILE...'),
7450 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7455 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7451 )
7456 )
7452 def unbundle(ui, repo, fname1, *fnames, **opts):
7457 def unbundle(ui, repo, fname1, *fnames, **opts):
7453 """apply one or more bundle files
7458 """apply one or more bundle files
7454
7459
7455 Apply one or more bundle files generated by :hg:`bundle`.
7460 Apply one or more bundle files generated by :hg:`bundle`.
7456
7461
7457 Returns 0 on success, 1 if an update has unresolved files.
7462 Returns 0 on success, 1 if an update has unresolved files.
7458 """
7463 """
7459 fnames = (fname1,) + fnames
7464 fnames = (fname1,) + fnames
7460
7465
7461 with repo.lock():
7466 with repo.lock():
7462 for fname in fnames:
7467 for fname in fnames:
7463 f = hg.openpath(ui, fname)
7468 f = hg.openpath(ui, fname)
7464 gen = exchange.readbundle(ui, f, fname)
7469 gen = exchange.readbundle(ui, f, fname)
7465 if isinstance(gen, streamclone.streamcloneapplier):
7470 if isinstance(gen, streamclone.streamcloneapplier):
7466 raise error.Abort(
7471 raise error.Abort(
7467 _(
7472 _(
7468 b'packed bundles cannot be applied with '
7473 b'packed bundles cannot be applied with '
7469 b'"hg unbundle"'
7474 b'"hg unbundle"'
7470 ),
7475 ),
7471 hint=_(b'use "hg debugapplystreamclonebundle"'),
7476 hint=_(b'use "hg debugapplystreamclonebundle"'),
7472 )
7477 )
7473 url = b'bundle:' + fname
7478 url = b'bundle:' + fname
7474 try:
7479 try:
7475 txnname = b'unbundle'
7480 txnname = b'unbundle'
7476 if not isinstance(gen, bundle2.unbundle20):
7481 if not isinstance(gen, bundle2.unbundle20):
7477 txnname = b'unbundle\n%s' % util.hidepassword(url)
7482 txnname = b'unbundle\n%s' % util.hidepassword(url)
7478 with repo.transaction(txnname) as tr:
7483 with repo.transaction(txnname) as tr:
7479 op = bundle2.applybundle(
7484 op = bundle2.applybundle(
7480 repo, gen, tr, source=b'unbundle', url=url
7485 repo, gen, tr, source=b'unbundle', url=url
7481 )
7486 )
7482 except error.BundleUnknownFeatureError as exc:
7487 except error.BundleUnknownFeatureError as exc:
7483 raise error.Abort(
7488 raise error.Abort(
7484 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7489 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7485 hint=_(
7490 hint=_(
7486 b"see https://mercurial-scm.org/"
7491 b"see https://mercurial-scm.org/"
7487 b"wiki/BundleFeature for more "
7492 b"wiki/BundleFeature for more "
7488 b"information"
7493 b"information"
7489 ),
7494 ),
7490 )
7495 )
7491 modheads = bundle2.combinechangegroupresults(op)
7496 modheads = bundle2.combinechangegroupresults(op)
7492
7497
7493 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7498 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7494
7499
7495
7500
7496 @command(
7501 @command(
7497 b'unshelve',
7502 b'unshelve',
7498 [
7503 [
7499 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7504 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7500 (
7505 (
7501 b'c',
7506 b'c',
7502 b'continue',
7507 b'continue',
7503 None,
7508 None,
7504 _(b'continue an incomplete unshelve operation'),
7509 _(b'continue an incomplete unshelve operation'),
7505 ),
7510 ),
7506 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7511 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7507 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7512 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7508 (
7513 (
7509 b'n',
7514 b'n',
7510 b'name',
7515 b'name',
7511 b'',
7516 b'',
7512 _(b'restore shelved change with given name'),
7517 _(b'restore shelved change with given name'),
7513 _(b'NAME'),
7518 _(b'NAME'),
7514 ),
7519 ),
7515 (b't', b'tool', b'', _(b'specify merge tool')),
7520 (b't', b'tool', b'', _(b'specify merge tool')),
7516 (
7521 (
7517 b'',
7522 b'',
7518 b'date',
7523 b'date',
7519 b'',
7524 b'',
7520 _(b'set date for temporary commits (DEPRECATED)'),
7525 _(b'set date for temporary commits (DEPRECATED)'),
7521 _(b'DATE'),
7526 _(b'DATE'),
7522 ),
7527 ),
7523 ],
7528 ],
7524 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7529 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7525 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7530 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7526 )
7531 )
7527 def unshelve(ui, repo, *shelved, **opts):
7532 def unshelve(ui, repo, *shelved, **opts):
7528 """restore a shelved change to the working directory
7533 """restore a shelved change to the working directory
7529
7534
7530 This command accepts an optional name of a shelved change to
7535 This command accepts an optional name of a shelved change to
7531 restore. If none is given, the most recent shelved change is used.
7536 restore. If none is given, the most recent shelved change is used.
7532
7537
7533 If a shelved change is applied successfully, the bundle that
7538 If a shelved change is applied successfully, the bundle that
7534 contains the shelved changes is moved to a backup location
7539 contains the shelved changes is moved to a backup location
7535 (.hg/shelve-backup).
7540 (.hg/shelve-backup).
7536
7541
7537 Since you can restore a shelved change on top of an arbitrary
7542 Since you can restore a shelved change on top of an arbitrary
7538 commit, it is possible that unshelving will result in a conflict
7543 commit, it is possible that unshelving will result in a conflict
7539 between your changes and the commits you are unshelving onto. If
7544 between your changes and the commits you are unshelving onto. If
7540 this occurs, you must resolve the conflict, then use
7545 this occurs, you must resolve the conflict, then use
7541 ``--continue`` to complete the unshelve operation. (The bundle
7546 ``--continue`` to complete the unshelve operation. (The bundle
7542 will not be moved until you successfully complete the unshelve.)
7547 will not be moved until you successfully complete the unshelve.)
7543
7548
7544 (Alternatively, you can use ``--abort`` to abandon an unshelve
7549 (Alternatively, you can use ``--abort`` to abandon an unshelve
7545 that causes a conflict. This reverts the unshelved changes, and
7550 that causes a conflict. This reverts the unshelved changes, and
7546 leaves the bundle in place.)
7551 leaves the bundle in place.)
7547
7552
7548 If bare shelved change (without interactive, include and exclude
7553 If bare shelved change (without interactive, include and exclude
7549 option) was done on newly created branch it would restore branch
7554 option) was done on newly created branch it would restore branch
7550 information to the working directory.
7555 information to the working directory.
7551
7556
7552 After a successful unshelve, the shelved changes are stored in a
7557 After a successful unshelve, the shelved changes are stored in a
7553 backup directory. Only the N most recent backups are kept. N
7558 backup directory. Only the N most recent backups are kept. N
7554 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7559 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7555 configuration option.
7560 configuration option.
7556
7561
7557 .. container:: verbose
7562 .. container:: verbose
7558
7563
7559 Timestamp in seconds is used to decide order of backups. More
7564 Timestamp in seconds is used to decide order of backups. More
7560 than ``maxbackups`` backups are kept, if same timestamp
7565 than ``maxbackups`` backups are kept, if same timestamp
7561 prevents from deciding exact order of them, for safety.
7566 prevents from deciding exact order of them, for safety.
7562
7567
7563 Selected changes can be unshelved with ``--interactive`` flag.
7568 Selected changes can be unshelved with ``--interactive`` flag.
7564 The working directory is updated with the selected changes, and
7569 The working directory is updated with the selected changes, and
7565 only the unselected changes remain shelved.
7570 only the unselected changes remain shelved.
7566 Note: The whole shelve is applied to working directory first before
7571 Note: The whole shelve is applied to working directory first before
7567 running interactively. So, this will bring up all the conflicts between
7572 running interactively. So, this will bring up all the conflicts between
7568 working directory and the shelve, irrespective of which changes will be
7573 working directory and the shelve, irrespective of which changes will be
7569 unshelved.
7574 unshelved.
7570 """
7575 """
7571 with repo.wlock():
7576 with repo.wlock():
7572 return shelvemod.dounshelve(ui, repo, *shelved, **opts)
7577 return shelvemod.dounshelve(ui, repo, *shelved, **opts)
7573
7578
7574
7579
7575 statemod.addunfinished(
7580 statemod.addunfinished(
7576 b'unshelve',
7581 b'unshelve',
7577 fname=b'shelvedstate',
7582 fname=b'shelvedstate',
7578 continueflag=True,
7583 continueflag=True,
7579 abortfunc=shelvemod.hgabortunshelve,
7584 abortfunc=shelvemod.hgabortunshelve,
7580 continuefunc=shelvemod.hgcontinueunshelve,
7585 continuefunc=shelvemod.hgcontinueunshelve,
7581 cmdmsg=_(b'unshelve already in progress'),
7586 cmdmsg=_(b'unshelve already in progress'),
7582 )
7587 )
7583
7588
7584
7589
7585 @command(
7590 @command(
7586 b'update|up|checkout|co',
7591 b'update|up|checkout|co',
7587 [
7592 [
7588 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7593 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7589 (b'c', b'check', None, _(b'require clean working directory')),
7594 (b'c', b'check', None, _(b'require clean working directory')),
7590 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7595 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7591 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7596 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7592 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7597 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7593 ]
7598 ]
7594 + mergetoolopts,
7599 + mergetoolopts,
7595 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7600 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7596 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7601 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7597 helpbasic=True,
7602 helpbasic=True,
7598 )
7603 )
7599 def update(ui, repo, node=None, **opts):
7604 def update(ui, repo, node=None, **opts):
7600 """update working directory (or switch revisions)
7605 """update working directory (or switch revisions)
7601
7606
7602 Update the repository's working directory to the specified
7607 Update the repository's working directory to the specified
7603 changeset. If no changeset is specified, update to the tip of the
7608 changeset. If no changeset is specified, update to the tip of the
7604 current named branch and move the active bookmark (see :hg:`help
7609 current named branch and move the active bookmark (see :hg:`help
7605 bookmarks`).
7610 bookmarks`).
7606
7611
7607 Update sets the working directory's parent revision to the specified
7612 Update sets the working directory's parent revision to the specified
7608 changeset (see :hg:`help parents`).
7613 changeset (see :hg:`help parents`).
7609
7614
7610 If the changeset is not a descendant or ancestor of the working
7615 If the changeset is not a descendant or ancestor of the working
7611 directory's parent and there are uncommitted changes, the update is
7616 directory's parent and there are uncommitted changes, the update is
7612 aborted. With the -c/--check option, the working directory is checked
7617 aborted. With the -c/--check option, the working directory is checked
7613 for uncommitted changes; if none are found, the working directory is
7618 for uncommitted changes; if none are found, the working directory is
7614 updated to the specified changeset.
7619 updated to the specified changeset.
7615
7620
7616 .. container:: verbose
7621 .. container:: verbose
7617
7622
7618 The -C/--clean, -c/--check, and -m/--merge options control what
7623 The -C/--clean, -c/--check, and -m/--merge options control what
7619 happens if the working directory contains uncommitted changes.
7624 happens if the working directory contains uncommitted changes.
7620 At most of one of them can be specified.
7625 At most of one of them can be specified.
7621
7626
7622 1. If no option is specified, and if
7627 1. If no option is specified, and if
7623 the requested changeset is an ancestor or descendant of
7628 the requested changeset is an ancestor or descendant of
7624 the working directory's parent, the uncommitted changes
7629 the working directory's parent, the uncommitted changes
7625 are merged into the requested changeset and the merged
7630 are merged into the requested changeset and the merged
7626 result is left uncommitted. If the requested changeset is
7631 result is left uncommitted. If the requested changeset is
7627 not an ancestor or descendant (that is, it is on another
7632 not an ancestor or descendant (that is, it is on another
7628 branch), the update is aborted and the uncommitted changes
7633 branch), the update is aborted and the uncommitted changes
7629 are preserved.
7634 are preserved.
7630
7635
7631 2. With the -m/--merge option, the update is allowed even if the
7636 2. With the -m/--merge option, the update is allowed even if the
7632 requested changeset is not an ancestor or descendant of
7637 requested changeset is not an ancestor or descendant of
7633 the working directory's parent.
7638 the working directory's parent.
7634
7639
7635 3. With the -c/--check option, the update is aborted and the
7640 3. With the -c/--check option, the update is aborted and the
7636 uncommitted changes are preserved.
7641 uncommitted changes are preserved.
7637
7642
7638 4. With the -C/--clean option, uncommitted changes are discarded and
7643 4. With the -C/--clean option, uncommitted changes are discarded and
7639 the working directory is updated to the requested changeset.
7644 the working directory is updated to the requested changeset.
7640
7645
7641 To cancel an uncommitted merge (and lose your changes), use
7646 To cancel an uncommitted merge (and lose your changes), use
7642 :hg:`merge --abort`.
7647 :hg:`merge --abort`.
7643
7648
7644 Use null as the changeset to remove the working directory (like
7649 Use null as the changeset to remove the working directory (like
7645 :hg:`clone -U`).
7650 :hg:`clone -U`).
7646
7651
7647 If you want to revert just one file to an older revision, use
7652 If you want to revert just one file to an older revision, use
7648 :hg:`revert [-r REV] NAME`.
7653 :hg:`revert [-r REV] NAME`.
7649
7654
7650 See :hg:`help dates` for a list of formats valid for -d/--date.
7655 See :hg:`help dates` for a list of formats valid for -d/--date.
7651
7656
7652 Returns 0 on success, 1 if there are unresolved files.
7657 Returns 0 on success, 1 if there are unresolved files.
7653 """
7658 """
7654 rev = opts.get('rev')
7659 rev = opts.get('rev')
7655 date = opts.get('date')
7660 date = opts.get('date')
7656 clean = opts.get('clean')
7661 clean = opts.get('clean')
7657 check = opts.get('check')
7662 check = opts.get('check')
7658 merge = opts.get('merge')
7663 merge = opts.get('merge')
7659 if rev and node:
7664 if rev and node:
7660 raise error.Abort(_(b"please specify just one revision"))
7665 raise error.Abort(_(b"please specify just one revision"))
7661
7666
7662 if ui.configbool(b'commands', b'update.requiredest'):
7667 if ui.configbool(b'commands', b'update.requiredest'):
7663 if not node and not rev and not date:
7668 if not node and not rev and not date:
7664 raise error.Abort(
7669 raise error.Abort(
7665 _(b'you must specify a destination'),
7670 _(b'you must specify a destination'),
7666 hint=_(b'for example: hg update ".::"'),
7671 hint=_(b'for example: hg update ".::"'),
7667 )
7672 )
7668
7673
7669 if rev is None or rev == b'':
7674 if rev is None or rev == b'':
7670 rev = node
7675 rev = node
7671
7676
7672 if date and rev is not None:
7677 if date and rev is not None:
7673 raise error.Abort(_(b"you can't specify a revision and a date"))
7678 raise error.Abort(_(b"you can't specify a revision and a date"))
7674
7679
7675 if len([x for x in (clean, check, merge) if x]) > 1:
7680 if len([x for x in (clean, check, merge) if x]) > 1:
7676 raise error.Abort(
7681 raise error.Abort(
7677 _(
7682 _(
7678 b"can only specify one of -C/--clean, -c/--check, "
7683 b"can only specify one of -C/--clean, -c/--check, "
7679 b"or -m/--merge"
7684 b"or -m/--merge"
7680 )
7685 )
7681 )
7686 )
7682
7687
7683 updatecheck = None
7688 updatecheck = None
7684 if check:
7689 if check:
7685 updatecheck = b'abort'
7690 updatecheck = b'abort'
7686 elif merge:
7691 elif merge:
7687 updatecheck = b'none'
7692 updatecheck = b'none'
7688
7693
7689 with repo.wlock():
7694 with repo.wlock():
7690 cmdutil.clearunfinished(repo)
7695 cmdutil.clearunfinished(repo)
7691 if date:
7696 if date:
7692 rev = cmdutil.finddate(ui, repo, date)
7697 rev = cmdutil.finddate(ui, repo, date)
7693
7698
7694 # if we defined a bookmark, we have to remember the original name
7699 # if we defined a bookmark, we have to remember the original name
7695 brev = rev
7700 brev = rev
7696 if rev:
7701 if rev:
7697 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7702 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7698 ctx = scmutil.revsingle(repo, rev, default=None)
7703 ctx = scmutil.revsingle(repo, rev, default=None)
7699 rev = ctx.rev()
7704 rev = ctx.rev()
7700 hidden = ctx.hidden()
7705 hidden = ctx.hidden()
7701 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7706 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7702 with ui.configoverride(overrides, b'update'):
7707 with ui.configoverride(overrides, b'update'):
7703 ret = hg.updatetotally(
7708 ret = hg.updatetotally(
7704 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7709 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7705 )
7710 )
7706 if hidden:
7711 if hidden:
7707 ctxstr = ctx.hex()[:12]
7712 ctxstr = ctx.hex()[:12]
7708 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7713 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7709
7714
7710 if ctx.obsolete():
7715 if ctx.obsolete():
7711 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7716 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7712 ui.warn(b"(%s)\n" % obsfatemsg)
7717 ui.warn(b"(%s)\n" % obsfatemsg)
7713 return ret
7718 return ret
7714
7719
7715
7720
7716 @command(
7721 @command(
7717 b'verify',
7722 b'verify',
7718 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7723 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7719 helpcategory=command.CATEGORY_MAINTENANCE,
7724 helpcategory=command.CATEGORY_MAINTENANCE,
7720 )
7725 )
7721 def verify(ui, repo, **opts):
7726 def verify(ui, repo, **opts):
7722 """verify the integrity of the repository
7727 """verify the integrity of the repository
7723
7728
7724 Verify the integrity of the current repository.
7729 Verify the integrity of the current repository.
7725
7730
7726 This will perform an extensive check of the repository's
7731 This will perform an extensive check of the repository's
7727 integrity, validating the hashes and checksums of each entry in
7732 integrity, validating the hashes and checksums of each entry in
7728 the changelog, manifest, and tracked files, as well as the
7733 the changelog, manifest, and tracked files, as well as the
7729 integrity of their crosslinks and indices.
7734 integrity of their crosslinks and indices.
7730
7735
7731 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7736 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7732 for more information about recovery from corruption of the
7737 for more information about recovery from corruption of the
7733 repository.
7738 repository.
7734
7739
7735 Returns 0 on success, 1 if errors are encountered.
7740 Returns 0 on success, 1 if errors are encountered.
7736 """
7741 """
7737 opts = pycompat.byteskwargs(opts)
7742 opts = pycompat.byteskwargs(opts)
7738
7743
7739 level = None
7744 level = None
7740 if opts[b'full']:
7745 if opts[b'full']:
7741 level = verifymod.VERIFY_FULL
7746 level = verifymod.VERIFY_FULL
7742 return hg.verify(repo, level)
7747 return hg.verify(repo, level)
7743
7748
7744
7749
7745 @command(
7750 @command(
7746 b'version',
7751 b'version',
7747 [] + formatteropts,
7752 [] + formatteropts,
7748 helpcategory=command.CATEGORY_HELP,
7753 helpcategory=command.CATEGORY_HELP,
7749 norepo=True,
7754 norepo=True,
7750 intents={INTENT_READONLY},
7755 intents={INTENT_READONLY},
7751 )
7756 )
7752 def version_(ui, **opts):
7757 def version_(ui, **opts):
7753 """output version and copyright information
7758 """output version and copyright information
7754
7759
7755 .. container:: verbose
7760 .. container:: verbose
7756
7761
7757 Template:
7762 Template:
7758
7763
7759 The following keywords are supported. See also :hg:`help templates`.
7764 The following keywords are supported. See also :hg:`help templates`.
7760
7765
7761 :extensions: List of extensions.
7766 :extensions: List of extensions.
7762 :ver: String. Version number.
7767 :ver: String. Version number.
7763
7768
7764 And each entry of ``{extensions}`` provides the following sub-keywords
7769 And each entry of ``{extensions}`` provides the following sub-keywords
7765 in addition to ``{ver}``.
7770 in addition to ``{ver}``.
7766
7771
7767 :bundled: Boolean. True if included in the release.
7772 :bundled: Boolean. True if included in the release.
7768 :name: String. Extension name.
7773 :name: String. Extension name.
7769 """
7774 """
7770 opts = pycompat.byteskwargs(opts)
7775 opts = pycompat.byteskwargs(opts)
7771 if ui.verbose:
7776 if ui.verbose:
7772 ui.pager(b'version')
7777 ui.pager(b'version')
7773 fm = ui.formatter(b"version", opts)
7778 fm = ui.formatter(b"version", opts)
7774 fm.startitem()
7779 fm.startitem()
7775 fm.write(
7780 fm.write(
7776 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7781 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7777 )
7782 )
7778 license = _(
7783 license = _(
7779 b"(see https://mercurial-scm.org for more information)\n"
7784 b"(see https://mercurial-scm.org for more information)\n"
7780 b"\nCopyright (C) 2005-2020 Matt Mackall and others\n"
7785 b"\nCopyright (C) 2005-2020 Matt Mackall and others\n"
7781 b"This is free software; see the source for copying conditions. "
7786 b"This is free software; see the source for copying conditions. "
7782 b"There is NO\nwarranty; "
7787 b"There is NO\nwarranty; "
7783 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7788 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7784 )
7789 )
7785 if not ui.quiet:
7790 if not ui.quiet:
7786 fm.plain(license)
7791 fm.plain(license)
7787
7792
7788 if ui.verbose:
7793 if ui.verbose:
7789 fm.plain(_(b"\nEnabled extensions:\n\n"))
7794 fm.plain(_(b"\nEnabled extensions:\n\n"))
7790 # format names and versions into columns
7795 # format names and versions into columns
7791 names = []
7796 names = []
7792 vers = []
7797 vers = []
7793 isinternals = []
7798 isinternals = []
7794 for name, module in extensions.extensions():
7799 for name, module in extensions.extensions():
7795 names.append(name)
7800 names.append(name)
7796 vers.append(extensions.moduleversion(module) or None)
7801 vers.append(extensions.moduleversion(module) or None)
7797 isinternals.append(extensions.ismoduleinternal(module))
7802 isinternals.append(extensions.ismoduleinternal(module))
7798 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7803 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7799 if names:
7804 if names:
7800 namefmt = b" %%-%ds " % max(len(n) for n in names)
7805 namefmt = b" %%-%ds " % max(len(n) for n in names)
7801 places = [_(b"external"), _(b"internal")]
7806 places = [_(b"external"), _(b"internal")]
7802 for n, v, p in zip(names, vers, isinternals):
7807 for n, v, p in zip(names, vers, isinternals):
7803 fn.startitem()
7808 fn.startitem()
7804 fn.condwrite(ui.verbose, b"name", namefmt, n)
7809 fn.condwrite(ui.verbose, b"name", namefmt, n)
7805 if ui.verbose:
7810 if ui.verbose:
7806 fn.plain(b"%s " % places[p])
7811 fn.plain(b"%s " % places[p])
7807 fn.data(bundled=p)
7812 fn.data(bundled=p)
7808 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7813 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7809 if ui.verbose:
7814 if ui.verbose:
7810 fn.plain(b"\n")
7815 fn.plain(b"\n")
7811 fn.end()
7816 fn.end()
7812 fm.end()
7817 fm.end()
7813
7818
7814
7819
7815 def loadcmdtable(ui, name, cmdtable):
7820 def loadcmdtable(ui, name, cmdtable):
7816 """Load command functions from specified cmdtable
7821 """Load command functions from specified cmdtable
7817 """
7822 """
7818 overrides = [cmd for cmd in cmdtable if cmd in table]
7823 overrides = [cmd for cmd in cmdtable if cmd in table]
7819 if overrides:
7824 if overrides:
7820 ui.warn(
7825 ui.warn(
7821 _(b"extension '%s' overrides commands: %s\n")
7826 _(b"extension '%s' overrides commands: %s\n")
7822 % (name, b" ".join(overrides))
7827 % (name, b" ".join(overrides))
7823 )
7828 )
7824 table.update(cmdtable)
7829 table.update(cmdtable)
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now